[gradle-1.12] 102/211: Import upstream 1.12

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:18:17 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 efaf20b6be391fbb48d9c27e498ba454ec0e829a
Author: Kai-Chung Yan <seamlikok at gmail.com>
Date:   Thu May 21 15:36:58 2015 +0800

    Import upstream 1.12
---
 build.gradle                                       |   35 +-
 buildSrc/build.gradle                              |    4 +-
 .../main/groovy/org/gradle/build/BuildTypes.groovy |    2 +-
 .../main/groovy/org/gradle/build/JarJarJar.groovy  |    6 +-
 .../org/gradle/build/ReleasedVersions.groovy       |   93 +
 .../gradle/build/docs/DocGenerationException.java  |    2 +-
 .../org/gradle/build/docs/Docbook2XHtml.groovy     |    2 +-
 .../build/docs/SampleElementValidator.groovy       |    2 -
 .../build/docs/UserGuideTransformTask.groovy       |    7 +-
 .../build/docs/dsl/docbook/BasicJavadocLexer.java  |   20 +
 .../build/docs/dsl/docbook/BlocksRenderer.java     |   48 +-
 .../docs/dsl/docbook/ClassDocMemberRenderer.java   |   25 +
 .../docs/dsl/docbook/ClassDocMethodsBuilder.java   |    3 +-
 .../dsl/docbook/ClassDocPropertiesBuilder.java     |   13 +-
 .../build/docs/dsl/docbook/ClassDocRenderer.java   |   34 +-
 .../docs/dsl/docbook/ClassDocSuperTypeBuilder.java |   19 +-
 .../build/docs/dsl/docbook/JavadocConverter.java   |   17 +
 .../build/docs/dsl/docbook/MethodsRenderer.java    |   48 +-
 .../build/docs/dsl/docbook/PropertiesRenderer.java |   46 +-
 .../build/docs/dsl/docbook/model/ClassDoc.groovy   |    5 +
 .../docs/dsl/source/ExtractDslMetaDataTask.groovy  |    4 +-
 .../dsl/source/GenerateDefaultImportsTask.java     |  148 +
 .../docs/model/SimpleClassMetaDataRepository.java  |   10 +-
 .../docs/dsl/docbook/BasicJavadocLexerTest.groovy  |   26 +
 .../dsl/docbook/ClassDocMethodsBuilderTest.groovy  |   12 +-
 .../docbook/ClassDocPropertiesBuilderTest.groovy   |   22 +-
 .../docs/dsl/docbook/ClassDocRendererTest.groovy   |  249 +-
 .../docs/dsl/docbook/JavadocConverterTest.groovy   |   20 +
 .../model/SimpleClassMetaDataRepositoryTest.groovy |   26 +-
 config/checkstyle/checkstyle-groovy.xml            |    3 +
 config/checkstyle/checkstyle.xml                   |    3 +
 gradle/buildReceipt.gradle                         |    1 +
 gradle/dependencies.gradle                         |   33 +-
 gradle/eclipse.gradle                              |    7 +
 gradle/groovyProject.gradle                        |   35 +-
 gradle/idea.gradle                                 |   14 +
 gradle/ideaTestSourcesWorkaround.gradle            |    3 +
 gradle/intTestImage.gradle                         |    2 +-
 gradle/integTest.gradle                            |   87 +-
 gradle/providedConfiguration.gradle                |    7 +-
 gradle/publish.gradle                              |    2 +-
 gradle/testGroupings.gradle                        |   13 +
 gradle/testSetup.gradle                            |    7 +
 gradle/versioning.gradle                           |    4 +
 gradle/wrapper.gradle                              |   30 +-
 gradle/wrapper/gradle-wrapper.jar                  |  Bin 0 -> 51365 bytes
 gradle/wrapper/gradle-wrapper.properties           |    4 +-
 settings.gradle                                    |    5 +
 subprojects/announce/announce.gradle               |    7 +-
 .../api/plugins/announce/AnnouncePlugin.groovy     |    5 +-
 .../announce/AnnouncePluginExtension.groovy        |   36 +-
 .../announce/internal/AnnouncerFactory.groovy      |    3 -
 .../internal/DefaultAnnouncerFactory.groovy        |    3 -
 .../plugins/announce/internal/NotifySend.groovy    |    3 -
 .../api/plugins/announce/internal/Twitter.groovy   |    8 +-
 .../announce/AnnouncePluginExtensionTest.groovy    |    8 +-
 .../api/plugins/announce/AnnouncePluginTest.groovy |    9 +-
 .../announce/BuildAnnouncementsPluginTest.groovy   |    4 +-
 .../internal/DefaultAnnouncerFactoryTest.groovy    |    7 +-
 subprojects/antlr/antlr.gradle                     |    2 +-
 .../org/gradle/api/plugins/antlr/AntlrPlugin.java  |    6 +-
 .../plugins/antlr/AntlrSourceVirtualDirectory.java |    2 -
 .../org/gradle/api/plugins/antlr/AntlrTask.java    |   14 +-
 .../internal/AntlrSourceVirtualDirectoryImpl.java  |    2 -
 .../api/plugins/antlr/internal/GenerationPlan.java |    2 -
 .../antlr/internal/GenerationPlanBuilder.java      |   12 +-
 .../plugins/antlr/internal/GrammarDelegate.java    |   10 +-
 .../antlr/internal/GrammarFileMetadata.java        |    6 +-
 .../plugins/antlr/internal/GrammarMetadata.java    |    8 +-
 .../plugins/antlr/internal/MetadataExtracter.java  |   10 +-
 .../gradle/api/plugins/antlr/internal/XRef.java    |    8 +-
 .../api/plugins/antlr/AntlrPluginTest.groovy       |    8 +-
 .../base-services-groovy.gradle                    |    2 +-
 .../main/groovy/org/gradle/api/specs/AndSpec.java  |    1 -
 .../api/specs/AbstractCompositeSpecTest.java       |    2 +-
 .../groovy/org/gradle/api/specs/AndSpecTest.java   |    4 +-
 subprojects/base-services/base-services.gradle     |    4 +-
 .../main/java/org/gradle/api/GradleException.java  |    2 -
 .../src/main/java/org/gradle/api/JavaVersion.java  |   26 +-
 .../main/java/org/gradle/api/internal/Actions.java |  208 -
 .../main/java/org/gradle/api/internal/Cast.java    |   46 -
 .../org/gradle/api/internal/ErroringAction.java    |   44 -
 .../main/java/org/gradle/api/internal/Factory.java |    4 +-
 .../gradle/api/internal/HasInternalProtocol.java   |   35 -
 .../java/org/gradle/api/internal/IoActions.java    |   93 -
 .../java/org/gradle/api/internal/Transformers.java |   95 -
 .../api/internal/project/ServiceRegistry.java      |    4 +-
 .../java/org/gradle/api/specs/CompositeSpec.java   |    1 -
 .../main/java/org/gradle/api/specs/NotSpec.java    |    1 -
 .../src/main/java/org/gradle/api/specs/OrSpec.java |    1 -
 .../src/main/java/org/gradle/api/specs/Spec.java   |    1 -
 .../src/main/java/org/gradle/internal/Actions.java |  208 +
 .../src/main/java/org/gradle/internal/Cast.java    |   46 +
 .../org/gradle/internal/CompositeStoppable.java    |  142 -
 .../java/org/gradle/internal/ErroringAction.java   |   43 +
 .../main/java/org/gradle/internal/Factories.java   |    8 +
 .../main/java/org/gradle/internal/FileUtils.java   |   57 +
 .../org/gradle/internal/HasInternalProtocol.java   |   35 +
 .../main/java/org/gradle/internal/IoActions.java   |  120 +
 .../java/org/gradle/internal/LazyIterable.java     |   33 -
 .../main/java/org/gradle/internal/Stoppable.java   |   28 -
 .../main/java/org/gradle/internal/Supplier.java    |   25 +
 .../main/java/org/gradle/internal/Suppliers.java   |   64 +
 .../java/org/gradle/internal/SystemProperties.java |   24 +-
 .../java/org/gradle/internal/Transformers.java     |  151 +
 .../internal/classloader/CachingClassLoader.java   |   66 +
 .../internal/classloader/ClassLoaderFactory.java   |   47 +
 .../internal/classloader/ClassLoaderHierarchy.java |   21 +
 .../internal/classloader/ClassLoaderSpec.java      |   45 +
 .../internal/classloader/ClassLoaderVisitor.java   |   52 +
 .../gradle/internal/classloader/ClasspathUtil.java |  105 +
 .../classloader/DefaultClassLoaderFactory.java     |  111 +
 .../internal/classloader/FilteringClassLoader.java |  258 ++
 .../classloader/MultiParentClassLoader.java        |  126 +
 .../classloader/MutableURLClassLoader.java         |   89 +
 .../classloader/TransformingClassLoader.java       |   63 +
 .../gradle/internal/concurrent/AsyncStoppable.java |    4 +-
 .../internal/concurrent/CompositeStoppable.java    |  133 +
 .../concurrent/DefaultExecutorFactory.java         |    2 -
 .../internal/concurrent/ServiceLifecycle.java      |  127 +
 .../org/gradle/internal/concurrent/Stoppable.java  |   28 +
 .../java/org/gradle/internal/hash/HashUtil.java    |   85 +
 .../java/org/gradle/internal/hash/HashValue.java   |   90 +
 .../gradle/internal/id/CompositeIdGenerator.java   |   12 +-
 .../internal/io/RandomAccessFileInputStream.java   |   49 +
 .../internal/io/RandomAccessFileOutputStream.java  |   43 +
 .../java/org/gradle/internal/io/TextStream.java    |   33 +
 .../org/gradle/internal/jvm/JavaHomeException.java |    3 -
 .../java/org/gradle/internal/jvm/JavaInfo.java     |    3 -
 .../src/main/java/org/gradle/internal/jvm/Jre.java |   26 +
 .../src/main/java/org/gradle/internal/jvm/Jvm.java |   80 +-
 .../org/gradle/internal/os/OperatingSystem.java    |   94 +-
 .../internal/reflect/DirectInstantiator.java       |    2 +-
 .../org/gradle/internal/reflect/Instantiator.java  |    2 +-
 .../org/gradle/internal/reflect/JavaMethod.java    |   79 +
 .../internal/reflect/JavaReflectionUtil.java       |  324 +-
 .../internal/reflect/NoSuchMethodException.java    |   26 +
 .../internal/reflect/NoSuchPropertyException.java  |   26 +
 .../gradle/internal/reflect/PropertyAccessor.java  |   25 +
 .../gradle/internal/reflect/PropertyMutator.java   |   25 +
 .../internal/service/AbstractServiceRegistry.java  |   74 -
 .../internal/service/DefaultServiceRegistry.java   |  904 +++-
 .../internal/service/ServiceCreationException.java |   30 +
 .../gradle/internal/service/ServiceLocator.java    |  106 +-
 .../internal/service/ServiceRegistration.java      |   43 +
 .../gradle/internal/service/ServiceRegistry.java   |   20 +
 .../internal/service/ServiceRegistryBuilder.java   |   56 +
 .../service/ServiceValidationException.java        |   26 +
 .../service/SynchronizedServiceRegistry.java       |   78 -
 .../internal/service/UnknownServiceException.java  |    8 +-
 .../main/java/org/gradle/util/CollectionUtils.java |  132 +-
 .../groovy/org/gradle/api/JavaVersionSpec.groovy   |   29 +-
 .../org/gradle/api/internal/ActionsTest.groovy     |  157 -
 .../groovy/org/gradle/api/internal/CastTest.groovy |   44 -
 .../gradle/api/internal/ErroringActionTest.groovy  |   52 -
 .../org/gradle/api/internal/IoActionsTest.groovy   |   92 -
 .../gradle/api/internal/TransformersTest.groovy    |   74 -
 .../groovy/org/gradle/internal/ActionsTest.groovy  |  157 +
 .../groovy/org/gradle/internal/CastTest.groovy     |   44 +
 .../gradle/internal/CompositeStoppableTest.groovy  |  145 -
 .../org/gradle/internal/ErroringActionTest.groovy  |   52 +
 .../org/gradle/internal/FileUtilsTest.groovy       |   54 +
 .../org/gradle/internal/IoActionsTest.groovy       |  167 +
 .../org/gradle/internal/LazyIterableTest.groovy    |   37 -
 .../org/gradle/internal/SuppliersTest.groovy       |   82 +
 .../org/gradle/internal/TransformersTest.groovy    |   98 +
 .../classloader/CachingClassLoaderTest.groovy      |   78 +
 .../DefaultClassLoaderFactoryTest.groovy           |   91 +
 .../DefaultClassLoaderFactoryTestHelper.java       |   35 +
 .../classloader/FilteringClassLoaderTest.groovy    |  261 ++
 .../classloader/MultiParentClassLoaderTest.groovy  |  123 +
 .../classloader/MutableURLClassLoaderTest.groovy   |   39 +
 .../concurrent/CompositeStoppableTest.groovy       |  145 +
 .../concurrent/DefaultExecutorFactorySpec.groovy   |   48 -
 .../concurrent/DefaultExecutorFactoryTest.groovy   |  181 +-
 .../concurrent/ServiceLifecycleTest.groovy         |  246 ++
 .../org/gradle/internal/hash/HashValueTest.groovy  |   64 +
 .../groovy/org/gradle/internal/jvm/JvmTest.groovy  |  224 +-
 .../gradle/internal/os/OperatingSystemTest.groovy  |   40 +
 .../internal/reflect/JavaReflectionUtilTest.groovy |  232 +-
 .../gradle/internal/reflect/JavaTestSubject.java   |  114 +
 .../internal/reflect/JavaTestSubjectSubclass.java  |   31 +
 .../DefaultServiceRegistryConcurrencyTest.groovy   |  134 +
 .../service/DefaultServiceRegistryTest.groovy      | 1239 ++++++
 .../service/DefaultServiceRegistryTest.java        |  579 ---
 .../gradle/internal/service/GenericRunnable.java   |   20 +
 .../internal/service/ProviderWithGenericType.java  |   31 +
 .../internal/service/ServiceLocatorTest.groovy     |  162 +-
 .../service/SynchronizedServiceRegistryTest.groovy |   44 -
 .../org/gradle/util/CollectionUtilsTest.groovy     |   59 +-
 .../resources/org/gradle/util/ClassLoaderTest.txt  |    0
 .../build-comparison/build-comparison.gradle       |    6 +-
 .../gradle/CompareGradleBuilds.java                |    2 +-
 .../internal/ComparableGradleBuildExecuter.java    |    2 +-
 .../internal/DefaultGradleBuildInvocationSpec.java |    3 -
 .../gradle/internal/GradleBuildComparison.java     |    4 +-
 .../internal/GradleBuildOutcomeSetInferrer.java    |   10 +-
 .../internal/GradleBuildOutcomeSetTransformer.java |   12 +-
 .../outcome/internal/FileOutcomeIdentifier.java    |   41 +
 .../archive/GeneratedArchiveBuildOutcome.java      |   10 +-
 .../tooling/DefaultGradleBuildOutcome.java         |   46 +
 .../tooling/DefaultGradleFileBuildOutcome.java     |   42 +
 .../internal/tooling/DefaultProjectOutcomes.java   |   79 +
 .../tooling/ProjectOutcomesModelBuilder.java       |   67 +
 ...blishArtifactToFileBuildOutcomeTransformer.java |   97 +
 .../tooling/ToolingRegistrationAction.java         |   27 +
 .../GradleBuildComparisonResultHtmlRenderer.groovy |    5 +-
 ...le.configuration.project.ProjectConfigureAction |    1 +
 .../DefaultGradleBuildInvocationSpecTest.groovy    |    4 +-
 .../GradleBuildOutcomeSetInferrerTest.groovy       |    2 +-
 .../GradleBuildOutcomeSetTransformerTest.groovy    |   26 +-
 ...neratedArchiveBuildOutcomeComparatorTest.groovy |    8 +-
 ...rtifactToFileBuildOutcomeTransformerTest.groovy |   87 +
 subprojects/build-init/build-init.gradle           |   86 +
 .../plugins/BuildInitPluginIntegrationTest.groovy  |  204 +
 .../GroovyLibraryInitIntegrationTest.groovy        |   76 +
 .../plugins/JavaLibraryInitIntegrationTest.groovy  |   75 +
 .../plugins/MavenConversionIntegrationTest.groovy  |  343 ++
 .../plugins/ScalaLibraryInitIntegrationTest.groovy |   78 +
 .../plugins/WrapperPluginIntegrationTest.groovy    |   35 +
 .../plugins/fixtures/WrapperTestFixture.groovy     |   50 +
 .../WrapperPluginAutoApplyActionIntegTest.groovy   |   99 +
 .../enforcerplugin/pom.xml                         |    0
 .../enforcerplugin/src/main/java/Foo.java          |    0
 .../expandProperties/pom.xml                       |   27 +
 .../expandProperties/src/main/java/Foo.java        |   10 +
 .../expandProperties/src/test/java/FooTest.java    |   10 +
 .../flatmultimodule/webinar-api/pom.xml            |    0
 .../src/main/java/webinar/Demoable.java            |    0
 .../flatmultimodule/webinar-impl/pom.xml           |    0
 .../src/main/java/webinar/Webinar.java             |    0
 .../src/test/java/webinar/WebinarTest.java         |    0
 .../flatmultimodule/webinar-parent/pom.xml         |    0
 .../flatmultimodule/webinar-war/pom.xml            |    0
 .../webinar-war/src/main/webapp/WEB-INF/web.xml    |    0
 .../webinar-war/src/main/webapp/index.jsp          |    0
 .../mavenExtensions/pom.xml                        |   47 +
 .../mavenExtensions/test-core/pom.xml              |   22 +
 .../multiModule/pom.xml                            |    0
 .../multiModule/webinar-api/pom.xml                |    0
 .../src/main/java/webinar/Demoable.java            |    0
 .../multiModule/webinar-impl/pom.xml               |    0
 .../src/main/java/webinar/Webinar.java             |    0
 .../src/test/java/webinar/WebinarTest.java         |    0
 .../multiModule/webinar-war/pom.xml                |    0
 .../webinar-war/src/main/webapp/WEB-INF/web.xml    |    0
 .../webinar-war/src/main/webapp/index.jsp          |    0
 .../nested-parent/pom.xml                          |   21 +
 .../multiModuleWithNestedParent/pom.xml            |   22 +
 .../webinar-api/pom.xml                            |    0
 .../src/main/java/webinar/Demoable.java            |    0
 .../webinar-impl/pom.xml                           |    0
 .../src/main/java/webinar/Webinar.java             |    0
 .../src/test/java/webinar/WebinarTest.java         |    0
 .../webinar-war/pom.xml                            |    0
 .../webinar-war/src/main/webapp/WEB-INF/web.xml    |    0
 .../webinar-war/src/main/webapp/index.jsp          |    0
 .../multiModuleWithRemoteParent/pom.xml            |   44 +
 .../util-parent/pom.xml                            |   13 +
 .../webinar-api/pom.xml                            |    0
 .../src/main/java/webinar/Demoable.java            |    0
 .../webinar-impl/pom.xml                           |   32 +
 .../src/main/java/webinar/Webinar.java             |    0
 .../src/test/java/webinar/WebinarTest.java         |    0
 .../webinar-war/pom.xml                            |    0
 .../webinar-war/src/main/webapp/WEB-INF/web.xml    |    0
 .../webinar-war/src/main/webapp/index.jsp          |    0
 .../providedNotWar/pom.xml                         |   30 +
 .../remoteparent/pom.xml                           |   21 +
 .../remoteparent/src/main/java/Bar.java            |    7 +
 .../maven_home/m2_home/conf/settings.xml           |   13 +
 .../util/parent/util-parent/3/util-parent-3.pom    |   17 +
 .../singleModule/pom.xml                           |    0
 .../singleModule/src/main/java/Foo.java            |    0
 .../singleModule/src/test/java/FooTest.java        |    0
 .../MavenConversionIntegrationTest/testjar/pom.xml |    0
 .../testjar/src/main/java/Foo.java                 |    0
 .../testjar/src/test/java/FooTest.java             |    0
 .../org/gradle/api/tasks/wrapper/Wrapper.java      |  279 ++
 .../org/gradle/api/tasks/wrapper/package-info.java |    0
 .../buildinit/plugins/BuildInitPlugin.groovy       |   62 +
 .../gradle/buildinit/plugins/WrapperPlugin.groovy  |   34 +
 .../BasicTemplateBasedProjectInitDescriptor.java   |   31 +
 .../plugins/internal/BuildInitAutoApplyAction.java |   33 +
 .../plugins/internal/BuildInitException.java       |   25 +
 .../plugins/internal/BuildInitServices.java        |   44 +
 .../plugins/internal/BuildInitTypeIds.java         |   29 +
 .../internal/ConditionalTemplateOperation.groovy   |   38 +
 .../DefaultTemplateLibraryVersionProvider.groovy   |   31 +
 .../GroovyLibraryProjectInitDescriptor.java        |   43 +
 .../internal/JavaLibraryProjectInitDescriptor.java |   44 +
 .../LanguageLibraryProjectInitDescriptor.java      |   49 +
 .../internal/PomProjectInitDescriptor.groovy       |   47 +
 .../plugins/internal/ProjectInitDescriptor.groovy  |   20 +
 .../internal/ProjectLayoutSetupRegistry.groovy     |   52 +
 .../ProjectLayoutSetupRegistryFactory.groovy       |   63 +
 .../ScalaLibraryProjectInitDescriptor.java         |   47 +
 .../SimpleGlobalFilesBuildSettingsDescriptor.java  |   32 +
 .../internal/SimpleTemplateOperation.groovy        |   47 +
 .../TemplateBasedProjectInitDescriptor.java        |   34 +
 .../internal/TemplateLibraryVersionProvider.groovy |   21 +
 .../plugins/internal/TemplateOperation.groovy      |   21 +
 .../plugins/internal/TemplateOperationFactory.java |  101 +
 .../buildinit/plugins/internal/TemplateValue.java  |   67 +
 .../internal/WrapperPluginAutoApplyAction.groovy   |   32 +
 .../plugins/internal/maven/Maven2Gradle.groovy     |  526 +++
 .../internal/maven/MavenConversionException.java   |   29 +
 .../internal/maven/MavenProjectXmlWriter.java      |   58 +
 .../internal/maven/MavenProjectsCreator.java       |   96 +
 .../org/gradle/buildinit/tasks/InitBuild.groovy    |   73 +
 .../META-INF/gradle-plugins/build-init.properties  |    1 +
 .../META-INF/gradle-plugins/wrapper.properties     |   17 +
 ...le.configuration.project.ProjectConfigureAction |    2 +
 ...e.internal.service.scopes.PluginServiceRegistry |    1 +
 .../tasks/templates/build.gradle.template          |   32 +
 .../groovylibrary/Library.groovy.template          |   11 +
 .../groovylibrary/LibraryTest.groovy.template      |   19 +
 .../templates/groovylibrary/build.gradle.template  |   28 +
 .../templates/javalibrary/Library.java.template    |   11 +
 .../javalibrary/LibraryTest.java.template          |   15 +
 .../templates/javalibrary/build.gradle.template    |   30 +
 .../templates/scalalibrary/Library.scala.template  |    9 +
 .../scalalibrary/LibrarySuite.scala.template       |   18 +
 .../templates/scalalibrary/build.gradle.template   |   29 +
 .../tasks/templates/settings.gradle.template       |   19 +
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |  161 +
 .../buildinit/plugins/BuildInitPluginSpec.groovy   |   77 +
 .../buildinit/plugins/WrapperPluginSpec.groovy     |   36 +
 .../internal/BuildInitAutoApplyActionSpec.groovy   |   72 +
 .../ConditionalTemplateOperationSpec.groovy        |   41 +
 ...LanguageLibraryProjectInitDescriptorSpec.groovy |   77 +
 .../ProjectLayoutSetupRegistryFactoryTest.groovy   |   50 +
 .../internal/ProjectLayoutSetupRegistrySpec.groovy |   61 +
 .../internal/SimpleTemplateOperationSpec.groovy    |   58 +
 .../TemplateBasedProjectInitDescriptorSpec.groovy  |   44 +
 .../internal/TemplateOperationFactorySpec.groovy   |   84 +
 .../plugins/internal/TemplateValueTest.groovy      |   48 +
 .../maven/MavenProjectXmlWriterTest.groovy         |   31 +
 .../internal/maven/MavenProjectsCreatorSpec.groovy |  117 +
 .../gradle/buildinit/tasks/InitBuildSpec.groovy    |   67 +
 subprojects/cli/cli.gradle                         |    2 +-
 .../gradle/cli/CommandLineArgumentException.java   |    2 -
 .../java/org/gradle/cli/CommandLineConverter.java  |    3 -
 .../java/org/gradle/cli/CommandLineOption.java     |   31 +-
 .../java/org/gradle/cli/CommandLineParser.java     |   36 +-
 .../java/org/gradle/cli/ParsedCommandLine.java     |    4 +
 .../org/gradle/cli/CommandLineParserTest.groovy    |  136 +-
 .../gradle/cli/ParsedCommandLineOptionSpec.groovy  |    3 -
 .../org/gradle/cli/ParsedCommandLineTest.groovy    |    3 -
 subprojects/code-quality/code-quality.gradle       |    2 +-
 .../quality/CheckstylePluginIntegrationTest.groovy |   24 +-
 .../quality/CodeNarcPluginIntegrationTest.groovy   |   36 +-
 .../CodeQualityPluginIntegrationTest.groovy        |   18 +-
 .../quality/FindBugsPluginIntegrationTest.groovy   |  182 +-
 .../quality/PmdPluginIntegrationTest.groovy        |   22 +-
 .../quality/PmdPluginVersionIntegrationTest.groovy |    4 +-
 .../internal/FindBugsSpecBuilderTest.groovy        |   36 +-
 .../api/plugins/quality/CheckstylePlugin.groovy    |   17 +-
 .../api/plugins/quality/CheckstyleReports.java     |    7 +-
 .../org/gradle/api/plugins/quality/CodeNarc.groovy |   22 +-
 .../api/plugins/quality/CodeNarcExtension.groovy   |   15 +
 .../api/plugins/quality/CodeNarcPlugin.groovy      |   24 +-
 .../api/plugins/quality/CodeNarcReports.java       |   11 +-
 .../api/plugins/quality/FindBugsPlugin.groovy      |   18 +-
 .../api/plugins/quality/FindBugsReports.java       |   27 +-
 .../api/plugins/quality/FindBugsXmlReport.java     |   46 +
 .../org/gradle/api/plugins/quality/JDepend.groovy  |    2 +-
 .../gradle/api/plugins/quality/JDependReports.java |    9 +-
 .../quality/JavaCodeQualityPluginConvention.groovy |    3 +-
 .../gradle/api/plugins/quality/PmdExtension.groovy |    1 +
 .../gradle/api/plugins/quality/PmdPlugin.groovy    |   25 +-
 .../org/gradle/api/plugins/quality/PmdReports.java |   10 +-
 .../internal/AbstractCodeQualityPlugin.groovy      |    4 +-
 .../quality/internal/FindBugsReportsImpl.java      |   27 +-
 .../internal/findbugs/FindBugsSpecBuilder.java     |   22 +-
 .../internal/findbugs/FindBugsWorkerManager.groovy |    3 +
 .../internal/findbugs/FindBugsWorkerServer.java    |    1 +
 .../internal/findbugs/FindBugsXmlReportImpl.java   |   39 +
 .../plugins/quality/CheckstylePluginTest.groovy    |   10 +-
 .../api/plugins/quality/CheckstyleTest.groovy      |    2 +-
 .../api/plugins/quality/CodeNarcPluginTest.groovy  |   37 +-
 .../plugins/quality/CodeQualityPluginTest.groovy   |   33 +-
 .../api/plugins/quality/FindBugsPluginTest.groovy  |   11 +-
 .../gradle/api/plugins/quality/FindBugsTest.groovy |    2 +-
 .../api/plugins/quality/JDependPluginTest.groovy   |   10 +-
 .../api/plugins/quality/PmdPluginTest.groovy       |   10 +-
 subprojects/core-impl/core-impl.gradle             |    9 +-
 .../ArtifactDeclarationIntegrationTest.groovy      |   38 +
 .../ArtifactDependenciesIntegrationTest.groovy     |   40 +-
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |   86 -
 .../resolve/CacheResolveIntegrationTest.groovy     |   20 +-
 ...ModuleDependenciesResolveIntegrationTest.groovy |   15 +-
 ...adataRulesChangingModulesIntegrationTest.groovy |  170 +
 .../ComponentMetadataRulesIntegrationTest.groovy   |  118 +
 ...ponentMetadataRulesStatusIntegrationTest.groovy |   45 +
 .../DependencyNotationIntegrationSpec.groovy       |   39 +-
 .../DependencyResolveRulesIntegrationTest.groovy   |   64 +-
 .../DetachedConfigurationsIntegrationTest.groovy   |   62 +
 .../ExtendingConfigurationsIntegrationTest.groovy  |   60 +
 ...LibraryArtifactResolutionIntegrationTest.groovy |  105 +
 .../resolve/ForcedModulesIntegrationTest.groovy    |    5 +-
 .../JvmLibraryArtifactResolveTestFixture.groovy    |  214 +
 .../ProjectDependenciesIntegrationTest.groovy      |    5 +-
 .../ProjectDependencyResolveIntegrationTest.groovy |  132 +-
 .../ResolutionResultApiIntegrationTest.groovy      |   80 +
 ...ResolutionStrategySamplesIntegrationTest.groovy |    7 +-
 .../ResolveCrossVersionIntegrationTest.groovy      |   20 +-
 .../integtests/resolve/ResolveTestFixture.groovy   |  333 ++
 .../ResolvedConfigurationIntegrationTest.groovy    |    6 +-
 ...VersionConflictResolutionIntegrationTest.groovy |  240 +-
 ...actCacheReuseCrossVersionIntegrationTest.groovy |    3 -
 ...AliasedArtifactResolutionIntegrationTest.groovy |   43 +-
 .../CacheReuseCrossVersionIntegrationTest.groovy   |    6 +-
 .../CachedChangingModulesIntegrationTest.groovy    |   20 +-
 ...achedDependencyResolutionIntegrationTest.groovy |   69 +-
 .../CachedMissingModulesIntegrationTest.groovy     |   51 +-
 ...ependencyMetadataInMemoryIntegrationTest.groovy |  197 +
 ...coverFromBrokenResolutionIntegrationTest.groovy |   57 +-
 .../FileSystemResolverIntegrationTest.groovy       |   12 +-
 .../custom/IvySFtpResolverIntegrationTest.groovy   |    8 +-
 .../custom/IvyUrlResolverIntegrationTest.groovy    |  176 +-
 .../ivy/IvyBrokenDescriptorIntegrationTest.groovy  |  119 +
 .../IvyBrokenRemoteResolveIntegrationTest.groovy   |   56 +-
 ...angingModuleRemoteResolveIntegrationTest.groovy |  109 +-
 ...adataRulesChangingModulesIntegrationTest.groovy |   41 +
 ...IvyComponentMetadataRulesIntegrationTest.groovy |   42 +
 ...ponentMetadataRulesStatusIntegrationTest.groovy |   98 +
 ...CustomStatusLatestVersionIntegrationTest.groovy |  107 +
 .../ivy/IvyDescriptorResolveIntegrationTest.groovy |  181 +-
 .../IvyDescriptorValidationIntegrationTest.groovy  |   54 +
 ...amicRevisionRemoteResolveIntegrationTest.groovy |  879 ++--
 ...IvyDynamicRevisionResolveIntegrationTest.groovy |  249 +-
 .../ivy/IvyHttpRepoResolveIntegrationTest.groovy   |  108 +-
 ...LibraryArtifactResolutionIntegrationTest.groovy |  325 ++
 .../ivy/IvyModuleResolveIntegrationTest.groovy     |  223 +
 .../resolve/ivy/IvyResolveIntegrationTest.groovy   |  164 +-
 .../maven/BadPomFileResolveIntegrationTest.groovy  |  148 +-
 .../LegacyMavenRepoResolveIntegrationTest.groovy   |   21 +-
 .../MavenBrokenRemoteResolveIntegrationTest.groovy |  101 +
 ...adataRulesChangingModulesIntegrationTest.groovy |   70 +
 ...venComponentMetadataRulesIntegrationTest.groovy |   42 +
 ...ponentMetadataRulesStatusIntegrationTest.groovy |   65 +
 ...venCustomPackagingResolveIntegrationTest.groovy |   68 +
 .../MavenDependencyResolveIntegrationTest.groovy   |   37 +
 .../MavenDynamicResolveIntegrationTest.groovy      |   72 +-
 .../MavenHttpRepoResolveIntegrationTest.groovy     |  156 +-
 ...nJcenterDependencyResolveIntegrationTest.groovy |   72 +
 ...LibraryArtifactResolutionIntegrationTest.groovy |  321 ++
 .../maven/MavenLatestResolveIntegrationTest.groovy |   98 +
 .../MavenLocalRepoResolveIntegrationTest.groovy    |  193 +-
 .../MavenParentPomResolveIntegrationTest.groovy    |  306 +-
 .../MavenPomPackagingResolveIntegrationTest.groovy |  245 +-
 .../maven/MavenPomResolveIntegrationTest.groovy    |   65 +
 .../MavenProfileResolveIntegrationTest.groovy      |  218 +
 .../MavenSnapshotResolveIntegrationTest.groovy     |  141 +-
 .../canNestModules/projectWithNestedModules.gradle |    2 +-
 .../projectWithConflicts.gradle                    |    2 +-
 .../artifacts/ArtifactDependencyResolver.java      |    8 +-
 .../artifacts/ArtifactPublicationServices.java     |   24 +
 .../api/internal/artifacts/ArtifactPublisher.java  |   26 +
 .../internal/artifacts/ConfigurationResolver.java  |    3 -
 .../artifacts/DefaultArtifactIdentifier.java       |  107 +
 .../artifacts/DefaultDependencyFactory.java        |   13 +-
 .../DefaultDependencyManagementServices.java       |  444 +-
 .../artifacts/DefaultModuleVersionIdentifier.java  |   15 +
 .../artifacts/DefaultModuleVersionSelector.java    |    8 +-
 .../artifacts/DefaultProjectDependencyFactory.java |    3 -
 .../artifacts/DefaultResolvedArtifact.java         |   64 +-
 .../artifacts/DefaultResolvedDependency.java       |   17 +-
 .../DependencyManagementBuildScopeServices.java    |  284 ++
 .../DependencyManagementGlobalScopeServices.java   |   86 +
 .../api/internal/artifacts/DependencyServices.java |   33 +
 .../artifacts/ModuleMetadataProcessor.java         |   22 +
 .../ModuleVersionIdentifierSerializer.java         |   26 +-
 .../internal/artifacts/ModuleVersionPublisher.java |   28 +
 .../artifacts/ModuleVersionSelectorSerializer.java |   41 +
 .../internal/artifacts/PlexusLoggerAdapter.java    |    3 -
 .../artifacts/ResolvedConfigurationIdentifier.java |    6 +-
 .../ResolvedConfigurationIdentifierSerializer.java |   39 +
 .../api/internal/artifacts/ResolverResults.java    |   48 +-
 .../component/ComponentIdentifierFactory.java      |   24 +
 .../DefaultComponentIdentifierFactory.java         |   32 +
 .../DefaultModuleComponentIdentifier.java          |   98 +
 .../component/DefaultModuleComponentSelector.java  |  107 +
 .../DefaultProjectComponentIdentifier.java         |   69 +
 .../component/DefaultProjectComponentSelector.java |   82 +
 .../artifacts/configurations/Configurations.java   |    8 -
 .../configurations/ConfigurationsProvider.java     |    3 -
 .../configurations/DefaultConfiguration.java       |    6 +-
 .../DefaultConfigurationContainer.java             |   10 +-
 .../DetachedConfigurationsProvider.java            |    3 -
 .../artifacts/dsl/DefaultArtifactHandler.groovy    |    9 +-
 .../dsl/DefaultComponentMetadataHandler.java       |   47 +
 .../dsl/ModuleVersionSelectorParsers.java          |   17 +-
 .../artifacts/dsl/ParsedModuleStringNotation.java  |    3 -
 .../dsl/PublishArtifactNotationParserFactory.java  |   20 +-
 .../ivyservice/ArtifactResolveContext.java         |   21 +
 .../artifacts/ivyservice/ArtifactResolver.java     |   12 +-
 .../ivyservice/ArtifactSetResolveResult.java       |   32 +
 .../ivyservice/ArtifactTypeResolveContext.java     |   38 +
 .../ivyservice/BuildableArtifactResolveResult.java |    6 +-
 .../BuildableArtifactSetResolveResult.java         |   29 +
 .../BuildableComponentResolveResult.java           |   42 +
 .../BuildableModuleVersionResolveResult.java       |   54 -
 .../internal/artifacts/ivyservice/CacheLayout.java |   59 +
 .../CacheLockingArtifactDependencyResolver.java    |   13 +-
 .../artifacts/ivyservice/CacheLockingManager.java  |   16 +-
 .../ivyservice/ComponentResolveResult.java         |   42 +
 .../ivyservice/ConfigurationResolveContext.java    |   41 +
 .../ivyservice/ContextualArtifactResolver.java     |   58 +
 .../DefaultBuildableArtifactResolveResult.java     |    4 +-
 .../DefaultBuildableArtifactSetResolveResult.java  |   64 +
 .../DefaultBuildableComponentResolveResult.java    |   73 +
 ...DefaultBuildableModuleVersionResolveResult.java |   95 -
 .../ivyservice/DefaultCacheLockingManager.java     |   35 +-
 .../ivyservice/DefaultConfigurationResolver.java   |   11 +-
 .../DefaultDependencyResolveDetails.java           |   11 +-
 .../ivyservice/DefaultIvyContextManager.java       |  104 +
 .../ivyservice/DefaultIvyDependencyPublisher.java  |  147 +-
 .../artifacts/ivyservice/DefaultIvyFactory.java    |   38 -
 .../ivyservice/DefaultLenientConfiguration.java    |  113 +-
 .../ivyservice/DefaultResolvedConfiguration.java   |   13 +-
 .../ivyservice/DefaultSettingsConverter.java       |   70 -
 .../ivyservice/DefaultUnresolvedDependency.java    |    3 +-
 .../ivyservice/DependencyToModuleResolver.java     |   28 -
 .../DependencyToModuleVersionIdResolver.java       |    2 +-
 .../DependencyToModuleVersionResolver.java         |   28 +
 .../ErrorHandlingArtifactDependencyResolver.java   |  120 +-
 .../ivyservice/IvyBackedArtifactPublisher.java     |   80 +-
 .../artifacts/ivyservice/IvyContextManager.java    |   39 +
 .../ivyservice/IvyDependencyPublisher.java         |   16 +-
 .../internal/artifacts/ivyservice/IvyFactory.java  |   26 -
 .../artifacts/ivyservice/IvyLoggingAdaper.java     |   18 +-
 .../ivyservice/IvyModuleDescriptorWriter.java      |    0
 .../IvyResolverBackedModuleVersionPublisher.java   |   67 +
 .../artifacts/ivyservice/IvySettingsFactory.java   |   40 -
 .../api/internal/artifacts/ivyservice/IvyUtil.java |   51 +-
 .../ivyservice/IvyXmlModuleDescriptorWriter.java   |  620 +--
 .../ivyservice/LocalComponentFactory.java          |   26 +
 .../ivyservice/ModuleToModuleVersionResolver.java  |   29 +
 .../ivyservice/ModuleVersionIdResolveResult.java   |    6 +-
 .../ivyservice/ModuleVersionNotFoundException.java |   10 +-
 .../ivyservice/ModuleVersionResolveException.java  |   24 +-
 .../ivyservice/ModuleVersionResolveResult.java     |   49 -
 .../ivyservice/ResolvedArtifactFactory.java        |   46 -
 .../ivyservice/ResolvedConfigurationBuilder.java   |   31 -
 .../SelfResolvingDependencyResolver.java           |   10 +-
 .../artifacts/ivyservice/SettingsConverter.java    |   34 -
 ...cuitEmptyConfigsArtifactDependencyResolver.java |   24 +-
 .../SubstitutedModuleVersionIdResolveResult.java   |   13 +-
 .../VersionForcingDependencyToModuleResolver.java  |    8 +-
 .../clientmodule/ClientModuleResolver.java         |   28 +-
 .../DefaultCachedModuleResolution.java             |   45 -
 .../DefaultCachedModuleVersionList.java            |   37 +
 .../DefaultResolvedModuleVersion.java              |   10 +-
 .../dynamicversions/ModuleResolutionCache.java     |   35 -
 .../ModuleResolutionCacheEntry.java                |   28 -
 .../dynamicversions/ModuleVersionsCache.java       |   33 +
 .../dynamicversions/ModuleVersionsCacheEntry.java  |   28 +
 .../SingleFileBackedModuleResolutionCache.java     |  139 -
 .../SingleFileBackedModuleVersionsCache.java       |  140 +
 .../AbstractDependencyResolverAdapter.java         |   55 -
 .../ivyresolve/ArtifactNotFoundException.java      |   14 +-
 .../ivyresolve/ArtifactResolveException.java       |   40 +-
 .../ivyresolve/BuildableModuleVersionMetaData.java |   78 -
 ...uildableModuleVersionMetaDataResolveResult.java |   72 +
 ...ildableModuleVersionSelectionResolveResult.java |   60 +
 .../CacheLockingModuleVersionRepository.java       |   39 +-
 .../ivyresolve/CachingModuleVersionRepository.java |  234 +-
 .../ivyresolve/ChangingModuleDetector.java         |   18 +-
 .../ConfiguredModuleVersionRepository.java         |   23 +
 .../DefaultBuildableModuleVersionMetaData.java     |  137 -
 ...uildableModuleVersionMetaDataResolveResult.java |   95 +
 ...ildableModuleVersionSelectionResolveResult.java |   58 +
 .../ivyresolve/DefaultDependencyMetaData.java      |   88 -
 .../ivyservice/ivyresolve/DefaultIvyAdapter.java   |   37 -
 .../ivyresolve/DefaultModuleVersionListing.java    |   94 +
 .../ivyresolve/DelegatingDependencyResolver.java   |  149 -
 .../ivyservice/ivyresolve/DependencyMetaData.java  |   43 -
 .../ivyresolve/DependencyResolverIdentifier.java   |   44 +-
 .../ivyresolve/ErrorHandlingArtifactResolver.java  |   47 +
 .../ExternalResourceResolverAdapter.java           |   47 -
 .../ivyservice/ivyresolve/IvyAdapter.java          |   25 -
 .../IvyAwareModuleVersionRepository.java           |    6 +-
 .../ivyservice/ivyresolve/IvyContextualiser.java   |   36 +-
 .../ivyresolve/IvyDependencyResolverAdapter.java   |   90 -
 .../IvyDynamicResolveModuleVersionRepository.java  |   36 +-
 .../ivyresolve/LazyDependencyToModuleResolver.java |   44 +-
 .../LocalArtifactsModuleVersionRepository.java     |   31 +
 .../LocalAwareModuleVersionRepository.java         |   19 +-
 .../ivyresolve/LocalModuleVersionRepository.java   |   33 +-
 .../ivyresolve/LoopbackDependencyResolver.java     |  120 +-
 .../ivyservice/ivyresolve/ModuleSource.java        |    8 +-
 .../ivyresolve/ModuleVersionListing.java           |   30 +
 .../ivyresolve/ModuleVersionMetaData.java          |   34 -
 .../ivyresolve/ModuleVersionRepository.java        |   18 +-
 .../ivyservice/ivyresolve/RepositoryChain.java     |   24 +
 .../RepositoryChainArtifactResolver.java           |   64 +
 .../RepositoryChainDependencyResolver.java         |  297 ++
 .../RepositoryChainModuleResolution.java           |   38 +
 .../ivyresolve/RepositoryChainModuleSource.java    |   34 +
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |  172 +-
 .../ivyresolve/RestrictedDependencyResolver.java   |   36 -
 .../StartParameterResolutionOverride.java          |   32 +-
 .../ivyservice/ivyresolve/UserResolverChain.java   |  206 +-
 .../ivyservice/ivyresolve/VersionInfo.java         |   39 +
 .../artifacts/ivyservice/ivyresolve/Versioned.java |   20 +
 .../memcache/CachedModuleVersionResult.java        |   55 +
 .../ivyresolve/memcache/CachedRepository.java      |   90 +
 .../memcache/DependencyMetadataCache.java          |  123 +
 .../memcache/DependencyMetadataCacheStats.java     |   29 +
 .../memcache/InMemoryDependencyMetadataCache.java  |   62 +
 .../parser/AbstractModuleDescriptorParser.java     |   55 +
 .../ivyresolve/parser/DescriptorParseContext.java  |   27 +
 .../parser/DisconnectedDescriptorParseContext.java |   37 +
 .../DisconnectedIvyXmlModuleDescriptorParser.java  |   65 +
 .../parser/DisconnectedParserSettings.java         |  110 -
 .../DownloadedIvyModuleDescriptorParser.java       |   17 +-
 .../parser/GradlePomModuleDescriptorBuilder.java   |  325 +-
 .../parser/GradlePomModuleDescriptorParser.java    |  392 +-
 .../parser/IvyXmlModuleDescriptorParser.java       |  583 ++-
 .../ivyresolve/parser/MetaDataParseException.java  |   32 +
 .../ivyresolve/parser/MetaDataParser.java          |   29 +
 .../parser/ModuleScopedParserSettings.java         |  102 -
 .../ivyresolve/parser/ParserRegistry.java          |   40 -
 .../ivyservice/ivyresolve/parser/PomParent.java    |   54 +
 .../ivyservice/ivyresolve/parser/PomReader.java    |  776 ++++
 .../ivyresolve/parser/RootPomParent.java           |   46 +
 .../UnresolvedDependencyVersionException.java      |   24 +
 .../ivyresolve/parser/data/MavenDependencyKey.java |   95 +
 .../ivyresolve/parser/data/PomDependencyMgt.java   |   32 +
 .../ivyresolve/parser/data/PomProfile.java         |   29 +
 .../ivyresolve/strategy/ChainVersionMatcher.java   |   64 +
 .../ivyresolve/strategy/ExactVersionMatcher.java   |  113 +
 .../ivyresolve/strategy/LatestStrategy.java        |   42 +
 .../ivyresolve/strategy/LatestVersionMatcher.java  |   47 +
 .../ivyresolve/strategy/LatestVersionStrategy.java |   65 +
 .../ivyresolve/strategy/ResolverStrategy.java      |   53 +
 .../ivyresolve/strategy/SubVersionMatcher.java     |   59 +
 .../ivyresolve/strategy/VersionMatcher.java        |   67 +
 .../ivyresolve/strategy/VersionRangeMatcher.java   |  180 +
 .../CachedModuleDescriptorParseContext.java        |   38 +
 .../modulecache/DefaultCachedMetaData.java         |   71 +
 .../modulecache/DefaultCachedModuleDescriptor.java |   70 -
 .../modulecache/DefaultModuleArtifactsCache.java   |  182 +
 .../modulecache/DefaultModuleDescriptorCache.java  |  188 -
 .../modulecache/DefaultModuleMetaDataCache.java    |  176 +
 .../modulecache/ModuleArtifactsCache.java          |   37 +
 .../modulecache/ModuleDescriptorCache.java         |   46 -
 .../modulecache/ModuleDescriptorCacheEntry.java    |    4 +-
 .../modulecache/ModuleDescriptorStore.java         |   43 +-
 .../modulecache/ModuleMetaDataCache.java           |   47 +
 .../ArtifactsExtraAttributesStrategy.java          |   27 -
 .../ArtifactsToModuleDescriptorConverter.java      |   26 -
 .../ConfigurationsToArtifactsConverter.java        |   23 +
 .../ConfigurationsToModuleDescriptorConverter.java |    3 -
 ...efaultArtifactsToModuleDescriptorConverter.java |   76 -
 .../DefaultConfigurationsToArtifactsConverter.java |   60 +
 ...tConfigurationsToModuleDescriptorConverter.java |    3 -
 .../DefaultExcludeRuleConverter.java               |    7 +-
 .../DefaultModuleDescriptorFactory.java            |   24 +-
 .../moduleconverter/ExcludeRuleConverter.java      |    3 -
 .../moduleconverter/ModuleDescriptorFactory.java   |    3 -
 .../PublishLocalComponentFactory.java              |   47 +
 .../PublishModuleDescriptorConverter.java          |   59 -
 .../ResolveLocalComponentFactory.java              |   55 +
 .../ResolveModuleDescriptorConverter.java          |   64 -
 .../AbstractIvyDependencyDescriptorFactory.java    |    3 -
 .../ClientModuleDependencyDescriptor.java          |    7 +-
 ...ClientModuleIvyDependencyDescriptorFactory.java |   14 +-
 .../dependencies/ClientModuleMetaDataFactory.java  |   26 +
 .../DefaultClientModuleMetaDataFactory.java        |   54 +
 ...ultDependenciesToModuleDescriptorConverter.java |    3 -
 .../DefaultDependencyDescriptorFactory.java        |    3 -
 ...aultModuleDescriptorFactoryForClientModule.java |   60 -
 .../DependenciesToModuleDescriptorConverter.java   |    3 -
 .../dependencies/DependencyDescriptorFactory.java  |    3 -
 ...ternalModuleIvyDependencyDescriptorFactory.java |    3 -
 .../ModuleDescriptorFactoryForClientModule.java    |   29 -
 .../ProjectIvyDependencyDescriptorFactory.java     |    9 +-
 .../DefaultProjectComponentRegistry.java           |   36 +
 .../DefaultProjectModuleRegistry.java              |   53 -
 .../projectmodule/DefaultProjectPublication.java   |   40 +
 .../DefaultProjectPublicationRegistry.java         |   33 +
 .../projectmodule/ProjectArtifactResolver.java     |   65 +
 .../projectmodule/ProjectComponentRegistry.java    |   22 +
 .../projectmodule/ProjectDependencyResolver.java   |   56 +-
 .../projectmodule/ProjectModuleRegistry.java       |   26 -
 .../projectmodule/ProjectPublication.java          |   28 +
 .../projectmodule/ProjectPublicationRegistry.java  |   29 +
 .../resolutionstrategy/DefaultCachePolicy.java     |   22 +-
 .../DefaultExternalResourceCachePolicy.java        |   22 +
 .../DefaultResolutionStrategy.java                 |    9 +-
 .../ExternalResourceCachePolicy.java               |   22 +
 .../LatestConflictResolution.java                  |    2 -
 .../ModuleForcingResolveRule.java                  |    3 -
 .../StrictConflictResolution.java                  |    2 -
 .../resolveengine/DefaultDependencyResolver.java   |  111 +-
 .../DefaultDependencyToConfigurationResolver.java  |   81 +
 .../resolveengine/DependencyGraphBuilder.java      |  634 +--
 .../DependencyToConfigurationResolver.java         |   30 +
 .../LatestModuleConflictResolver.java              |   19 +-
 .../resolveengine/ModuleConflictResolver.java      |    2 +-
 .../resolveengine/ModuleRevisionResolveState.java  |   11 +-
 .../resolveengine/ModuleVersionSpec.java           |    4 +-
 .../resolveengine/StrictConflictResolver.java      |    2 +-
 .../VersionSelectionReasonResolver.java            |    7 +-
 .../DefaultResolvedConfigurationBuilder.java       |  151 +
 .../DefaultTransientConfigurationResults.java      |   45 +
 .../oldresult/ResolvedConfigurationBuilder.java    |   44 +
 .../oldresult/ResolvedConfigurationResults.java    |   32 +
 .../oldresult/ResolvedContentsMapping.java         |   28 +
 .../oldresult/TransientConfigurationResults.java   |   32 +
 .../TransientConfigurationResultsBuilder.java      |  186 +
 .../result/CachingDependencyResultFactory.java     |   16 +-
 .../result/ComponentIdentifierSerializer.java      |   76 +
 .../result/ComponentSelectionReasonSerializer.java |   59 +
 .../result/ComponentSelectorSerializer.java        |   76 +
 .../result/DefaultInternalDependencyResult.java    |   59 +
 .../result/DefaultModuleVersionSelection.java      |   45 +
 .../result/DefaultResolutionResultBuilder.java     |   85 +
 .../result/InternalDependencyResult.java           |   18 +-
 .../result/InternalDependencyResultSerializer.java |   62 +
 .../result/ModuleVersionSelection.java             |   10 +-
 .../result/ModuleVersionSelectionSerializer.java   |   47 +
 .../result/ResolutionResultBuilder.java            |   63 +-
 .../result/ResolvedConfigurationListener.java      |   30 -
 .../result/StreamingResolutionResultBuilder.java   |  200 +
 .../result/VersionSelectionReasons.java            |   29 +-
 .../resolveengine/store/CachedStoreFactory.java    |  102 +
 .../resolveengine/store/DefaultBinaryStore.java    |  147 +
 .../store/ResolutionResultsStoreFactory.java       |  120 +
 .../ivyservice/resolveengine/store/StoreSet.java   |   29 +
 .../AbstractModuleDescriptorBackedMetaData.java    |  233 +
 .../BuildableModuleVersionPublishMetaData.java     |   27 +
 .../metadata/ComponentArtifactIdentifier.java      |   34 +
 .../metadata/ComponentArtifactMetaData.java        |   39 +
 .../artifacts/metadata/ComponentMetaData.java      |   85 +
 .../artifacts/metadata/ConfigurationMetaData.java  |   41 +
 .../metadata/DefaultDependencyMetaData.java        |  122 +
 .../artifacts/metadata/DefaultIvyArtifactName.java |   97 +
 .../metadata/DefaultLocalArtifactIdentifier.java   |   69 +
 .../metadata/DefaultLocalComponentMetaData.java    |  157 +
 .../DefaultModuleVersionArtifactIdentifier.java    |   79 +
 .../DefaultModuleVersionArtifactMetaData.java      |   63 +
 .../DefaultModuleVersionPublishMetaData.java       |   84 +
 .../artifacts/metadata/DependencyMetaData.java     |   65 +
 .../artifacts/metadata/IvyArtifactName.java        |   37 +
 .../artifacts/metadata/LocalArtifactMetaData.java  |   23 +
 .../artifacts/metadata/LocalComponentMetaData.java |   40 +
 .../metadata/ModuleDescriptorAdapter.java          |  112 +
 .../metadata/ModuleVersionArtifactIdentifier.java  |   29 +
 .../ModuleVersionArtifactIdentifierSerializer.java |   51 +
 .../metadata/ModuleVersionArtifactMetaData.java    |   38 +
 .../ModuleVersionArtifactPublishMetaData.java      |   35 +
 .../artifacts/metadata/ModuleVersionMetaData.java  |   41 +
 .../metadata/ModuleVersionPublishMetaData.java     |   28 +
 .../metadata/MutableLocalComponentMetaData.java    |   28 +
 .../metadata/MutableModuleVersionMetaData.java     |   34 +
 .../CannotLocateLocalMavenRepositoryException.java |    2 +-
 .../DefaultLocalMavenRepositoryLocator.java        |    7 +-
 .../mvnsettings/DefaultMavenFileLocations.java     |    3 -
 .../mvnsettings/DefaultMavenSettingsProvider.java  |    3 -
 .../CustomResolverArtifactRepository.java          |   72 -
 .../repositories/DefaultBaseRepositoryFactory.java |   56 +-
 .../DefaultFlatDirArtifactRepository.java          |   43 +-
 .../repositories/DefaultIvyArtifactRepository.java |   38 +-
 .../DefaultMavenArtifactRepository.java            |   38 +-
 .../DefaultMavenLocalArtifactRepository.java       |   50 +
 .../FixedResolverArtifactRepository.java           |   57 -
 .../repositories/LegacyDependencyResolver.java     |   44 +-
 .../repositories/LegacyMavenResolver.java          |    5 +-
 .../repositories/PublicationAwareRepository.java   |   23 +
 .../repositories/ResolutionAwareRepository.java    |    7 +-
 .../AbstractRepositoryCacheManager.java            |   86 -
 .../DownloadingRepositoryArtifactCache.java        |   73 +
 .../DownloadingRepositoryCacheManager.java         |  164 -
 .../EnhancedArtifactDownloadReport.java            |   38 -
 .../LocalFileRepositoryArtifactCache.java          |   43 +
 .../LocalFileRepositoryCacheManager.java           |   88 -
 .../cachemanager/RepositoryArtifactCache.java      |   45 +
 .../repositories/layout/MavenRepositoryLayout.java |    2 +-
 .../legacy/AbstractRepositoryCacheManager.java     |   85 +
 .../legacy/CustomIvyResolverRepositoryFactory.java |   41 +
 .../legacy/CustomResolverArtifactRepository.java   |   72 +
 .../legacy/DownloadingRepositoryCacheManager.java  |  161 +
 .../legacy/EnhancedArtifactDownloadReport.java     |   38 +
 .../legacy/FixedResolverArtifactRepository.java    |   60 +
 .../legacy/IvyDependencyResolverAdapter.java       |  202 +
 .../LegacyDependencyResolverRepositoryFactory.java |   24 +
 .../legacy/LegacyResolverParserSettings.java       |   98 +
 .../legacy/LocalFileRepositoryCacheManager.java    |   88 +
 .../repositories/resolver/AbstractVersionList.java |   40 +-
 .../resolver/ChainedVersionLister.java             |   20 +-
 .../resolver/ComponentMetadataDetailsAdapter.java  |   58 +
 .../repositories/resolver/DefaultVersionList.java  |   34 +-
 .../resolver/ExternalResourceResolver.java         |  913 ++--
 ...rnalResourceResolverDescriptorParseContext.java |   72 +
 .../repositories/resolver/IvyResolver.java         |   55 +-
 .../repositories/resolver/IvyResourcePattern.java  |   37 +-
 .../repositories/resolver/M2ResourcePattern.java   |   29 +-
 .../repositories/resolver/MavenLocalResolver.java  |   57 +
 .../repositories/resolver/MavenMetadataLoader.java |   37 +-
 .../repositories/resolver/MavenResolver.java       |  134 +-
 .../repositories/resolver/MavenVersionLister.java  |   15 +-
 .../resolver/PatternBasedResolver.java             |    4 +-
 .../repositories/resolver/ResourcePattern.java     |   16 +-
 .../resolver/ResourceVersionLister.java            |   21 +-
 .../VerifyingExternalResourceDownloader.java       |   86 +
 .../repositories/resolver/VersionList.java         |   47 +-
 .../repositories/resolver/VersionLister.java       |    6 +-
 .../transport/RepositoryTransportFactory.java      |   18 +-
 .../resolution/AbstractSoftwareArtifact.java       |   51 +
 .../resolution/AbstractSoftwareComponent.java      |   49 +
 .../resolution/ComponentMetaDataArtifact.java      |   21 +
 .../resolution/DefaultArtifactResolutionQuery.java |  156 +
 .../DefaultArtifactResolutionQueryFactory.java     |   46 +
 .../DefaultArtifactResolutionQueryResult.java      |   46 +
 .../artifacts/resolution/DefaultJvmLibrary.java    |   45 +
 .../DefaultJvmLibraryJavadocArtifact.java          |   31 +
 .../DefaultJvmLibrarySourcesArtifact.java          |   31 +
 .../DefaultUnresolvedSoftwareComponent.java        |   37 +
 .../resolution/IvyDescriptorArtifact.java          |   21 +
 .../artifacts/resolution/MavenPomArtifact.java     |   22 +
 .../artifacts/result/AbstractDependencyResult.java |   17 +-
 .../artifacts/result/DefaultResolutionResult.java  |   42 +-
 .../result/DefaultResolvedComponentResult.java     |   82 +
 .../result/DefaultResolvedDependencyResult.java    |   13 +-
 .../result/DefaultResolvedModuleVersionResult.java |   81 -
 .../result/DefaultUnresolvedDependencyResult.java  |   23 +-
 .../externalresource/AbstractExternalResource.java |   24 +-
 .../DefaultLocallyAvailableExternalResource.java   |   53 +
 .../externalresource/ExternalResource.java         |   60 +-
 .../LocalFileStandInExternalResource.java          |    4 +-
 .../LocallyAvailableExternalResource.java          |   34 +-
 .../externalresource/UrlExternalResource.java      |   65 +
 .../cached/ByUrlCachedExternalResourceIndex.java   |    6 +-
 .../cached/CachedExternalResource.java             |    8 -
 .../cached/DefaultCachedExternalResource.java      |   15 -
 .../cached/DefaultCachedExternalResourceIndex.java |    6 +-
 .../externalresource/ivy/AbstractCachedIndex.java  |    6 +-
 .../ArtifactAtRepositoryCachedArtifactIndex.java   |   62 +-
 .../ivy/ArtifactAtRepositoryKey.java               |   31 +-
 .../CompositeLocallyAvailableResourceFinder.java   |    3 +-
 .../local/DefaultLocallyAvailableResource.java     |   65 -
 .../LazyLocallyAvailableResourceCandidates.java    |    6 +-
 .../local/LocallyAvailableResource.java            |   31 -
 .../local/LocallyAvailableResourceCandidates.java  |    3 +-
 .../local/LocallyAvailableResourceFinder.java      |    2 +-
 ...leResourceFinderSearchableFileStoreAdapter.java |   10 +-
 .../ivy/LocallyAvailableResourceFinderFactory.java |   81 +-
 ...PatternBasedLocallyAvailableResourceFinder.java |   24 +-
 .../metadata/DefaultExternalResourceMetaData.java  |    2 +-
 .../metadata/ExternalResourceMetaData.java         |    2 +-
 .../DefaultCacheAwareExternalResourceAccessor.java |   22 +-
 .../transfer/ExternalResourceAccessor.java         |    2 +-
 .../ProgressLoggingExternalResourceAccessor.java   |   17 +-
 .../DefaultExternalResourceRepository.java         |    4 +-
 .../transport/file/FileResourceConnector.java      |   13 +-
 .../transport/file/FileTransport.java              |    6 +-
 .../http/ApacheDirectoryListingParser.java         |    9 +-
 .../transport/http/HttpRequestException.java       |    2 +-
 .../transport/http/HttpResourceAccessor.java       |    2 +-
 .../transport/http/HttpResourceLister.java         |   33 +-
 .../transport/http/HttpResponseResource.java       |    2 +-
 .../transport/http/HttpTransport.java              |   15 +-
 .../JavaSystemPropertiesHttpProxySettings.java     |    7 +-
 .../filestore/ivy/ArtifactIdentifierFileStore.java |   44 +
 .../filestore/ivy/ArtifactRevisionIdFileStore.java |   46 -
 .../ClientModuleNotationParserFactory.java         |   10 +-
 .../DependencyClassPathNotationParser.java         |    8 +-
 .../notations/DependencyFilesNotationParser.java   |    8 +-
 .../notations/DependencyMapNotationParser.java     |    9 +-
 .../notations/DependencyNotationParser.java        |   14 +-
 .../notations/DependencyProjectNotationParser.java |    5 +-
 .../notations/DependencyStringNotationParser.java  |    5 +-
 .../notations/ProjectDependencyFactory.java        |    7 +-
 ...e.internal.service.scopes.PluginServiceRegistry |    1 +
 .../gradle/api/artifacts/ArtifactsTestUtils.java   |   62 -
 .../artifacts/DefaultArtifactIdentifierTest.groovy |   46 +
 .../DefaultDependencyManagementServicesTest.groovy |  119 +-
 .../DefaultModuleVersionSelectorTest.groovy        |   16 +-
 .../artifacts/DefaultResolvedArtifactTest.groovy   |   53 +-
 .../artifacts/DefaultResolvedDependencySpec.groovy |    2 +-
 .../artifacts/DefaultResolvedDependencyTest.java   |   70 +-
 ...pendencyManagementBuildScopeServicesTest.groovy |   28 +
 ...endencyManagementGlobalScopeServicesTest.groovy |   31 +
 .../ModuleVersionSelectorSerializerTest.groovy     |   33 +
 ...vedConfigurationIdentifierSerializerTest.groovy |   37 +
 .../ResolvedConfigurationIdentifierSpec.groovy     |   14 +-
 .../internal/artifacts/ResolverResultsSpec.groovy  |    9 +-
 .../DefaultComponentIdentifierFactoryTest.groovy   |   53 +
 .../DefaultModuleComponentIdentifierTest.groovy    |   81 +
 .../DefaultModuleComponentSelectorTest.groovy      |  117 +
 .../DefaultProjectComponentIdentifierTest.groovy   |   58 +
 .../DefaultProjectComponentSelectorTest.groovy     |   91 +
 .../configurations/ConfigurationsTest.java         |    8 -
 .../DefaultConfigurationContainerSpec.groovy       |    2 +-
 .../DefaultConfigurationContainerTest.groovy       |   33 +-
 .../configurations/DefaultConfigurationSpec.groovy |    8 +-
 .../configurations/DefaultConfigurationTest.java   |   16 +-
 .../internal/artifacts/dsl/ArtifactFileTest.groovy |    3 -
 .../dsl/DefaultArtifactHandlerTest.groovy          |   12 +-
 .../dsl/DefaultComponentMetadataHandlerTest.groovy |   42 +
 .../dsl/ModuleVersionSelectorParsersTest.groovy    |    3 -
 ...PublishArtifactNotationParserFactoryTest.groovy |   23 +-
 .../artifacts/ivyservice/CacheLayoutTest.groovy    |   54 +
 ...cheLockingArtifactDependencyResolverTest.groovy |   13 +-
 ...efaultBuildableArtifactResolveResultTest.groovy |    5 +-
 ...ultBuildableArtifactSetResolveResultTest.groovy |   75 +
 ...faultBuildableComponentResolveResultTest.groovy |  109 +
 ...tBuildableModuleVersionResolveResultTest.groovy |  148 -
 .../DefaultCacheLockingManagerTest.groovy          |   74 +
 .../DefaultDependencyResolveDetailsSpec.groovy     |    5 +-
 .../ivyservice/DefaultIvyContextManagerTest.groovy |  225 +
 .../ivyservice/DefaultIvyFactoryTest.groovy        |   40 -
 .../ivyservice/DefaultSettingsConverterTest.groovy |  114 -
 .../DefaultUnresolvedDependencySpec.groovy         |    3 -
 ...orHandlingArtifactDependencyResolverTest.groovy |   88 +-
 .../ivyservice/IvyBackedArtifactPublisherTest.java |  173 -
 .../ivyservice/IvySettingsFactoryTest.groovy       |   37 -
 .../artifacts/ivyservice/IvyUtilTest.groovy        |    6 +-
 .../IvyXmlModuleDescriptorWriterTest.groovy        |   78 +-
 .../ModuleVersionNotFoundExceptionTest.groovy      |   18 +-
 .../ModuleVersionResolveExceptionTest.groovy       |   13 +-
 .../ivyservice/ResolvedArtifactFactoryTest.groovy  |   54 -
 .../SelfResolvingDependencyResolverTest.groovy     |   16 +-
 ...ptyConfigsArtifactDependencyResolverSpec.groovy |   47 +-
 ...ionForcingDependencyToModuleResolverSpec.groovy |    8 +-
 .../clientmodule/ClientModuleResolverTest.groovy   |   39 +-
 .../CachingModuleVersionRepositoryTest.groovy      |  109 +-
 ...leModuleVersionMetaDataResolveResultTest.groovy |  174 +
 ...efaultBuildableModuleVersionMetaDataTest.groovy |  244 --
 .../DefaultDependencyMetaDataTest.groovy           |   77 -
 .../DependencyResolverIdentifierTest.groovy        |   15 +-
 .../ErrorHandlingArtifactResolverTest.groovy       |   82 +
 ...ynamicResolveModuleVersionRepositoryTest.groovy |   18 +-
 .../LazyDependencyToModuleResolverTest.groovy      |  109 +-
 .../RepositoryChainDependencyResolverTest.groovy   |  639 +++
 .../ivyresolve/UserResolverChainTest.groovy        |  588 ---
 .../memcache/CachedModuleVersionResultTest.groovy  |   99 +
 .../memcache/CachedRepositoryTest.groovy           |  178 +
 .../memcache/DependencyMetadataCacheTest.groovy    |  225 +
 .../InMemoryDependencyMetadataCacheTest.groovy     |   76 +
 ...tractGradlePomModuleDescriptorParserTest.groovy |   76 +
 .../ivyresolve/parser/AbstractPomReaderTest.groovy |   71 +
 ...onnectedIvyXmlModuleDescriptorParserTest.groovy |   80 +
 .../DownloadedIvyModuleDescriptorParserTest.groovy |   14 +-
 ...adlePomModuleDescriptorParserProfileTest.groovy |  827 ++++
 .../GradlePomModuleDescriptorParserTest.groovy     | 2028 ++++++++-
 .../parser/IvyXmlModuleDescriptorParserTest.groovy |  451 +-
 .../ivyresolve/parser/PomReaderProfileTest.groovy  | 1566 +++++++
 .../ivyresolve/parser/PomReaderTest.groovy         |  818 ++++
 .../parser/data/MavenDependencyKeyTest.groovy      |   51 +
 .../strategy/ChainVersionMatcherTest.groovy        |  129 +
 .../strategy/ExactVersionMatcherTest.groovy        |  181 +
 .../strategy/LatestVersionMatcherTest.groovy       |   83 +
 .../strategy/LatestVersionStrategyTest.groovy      |   78 +
 .../strategy/SubVersionMatcherTest.groovy          |   84 +
 .../strategy/VersionRangeMatcherTest.groovy        |  208 +
 .../modulecache/ModuleDescriptorStoreTest.groovy   |   12 +-
 ...ltArtifactsToModuleDescriptorConverterTest.java |  129 -
 ...ltConfigurationsToArtifactsConverterTest.groovy |  103 +
 ...figurationsToModuleDescriptorConverterTest.java |   14 +-
 .../DefaultExcludeRuleConverterTest.java           |    3 -
 .../DefaultModuleDescriptorFactoryTest.groovy      |   38 +-
 .../moduleconverter/IvyConverterTestUtil.java      |    3 -
 .../PublishLocalComponentFactoryTest.groovy        |   56 +
 .../PublishModuleDescriptorConverterTest.groovy    |   54 -
 .../ResolveLocalComponentFactoryTest.groovy        |   91 +
 .../ResolveModuleDescriptorConverterTest.groovy    |   59 -
 ...actDependencyDescriptorFactoryInternalTest.java |   11 +-
 ...lientModuleDependencyDescriptorFactoryTest.java |   92 -
 ...ntModuleIvyDependencyDescriptorFactoryTest.java |   89 +
 .../DefaultClientModuleMetaDataFactoryTest.java    |   89 +
 ...ependenciesToModuleDescriptorConverterTest.java |    7 +-
 ...ModuleDescriptorFactoryForClientModuleTest.java |   89 -
 ...ernalModuleDependencyDescriptorFactoryTest.java |    3 -
 .../ProjectDependencyDescriptorFactoryTest.groovy  |   63 +
 .../ProjectDependencyDescriptorFactoryTest.java    |   65 -
 ...eflectiveDependencyDescriptorFactoryTest.groovy |   13 +-
 .../ProjectDependencyResolverTest.groovy           |   52 +-
 .../DefaultCachePolicySpec.groovy                  |   21 +-
 .../DefaultResolutionStrategySpec.groovy           |    3 -
 .../ModuleForcingResolveRuleSpec.groovy            |    3 -
 .../DependencyGraphBuilderTest.groovy              |  224 +-
 .../resolveengine/ModuleVersionSpecTest.groovy     |   49 +-
 .../VersionSelectionReasonResolverTest.groovy      |   10 +-
 .../CachingDependencyResultFactoryTest.groovy      |   24 +-
 .../ComponentIdentifierSerializerTest.groovy       |   60 +
 .../ComponentSelectionReasonSerializerTest.groovy  |   54 +
 .../result/ComponentSelectorSerializerTest.groovy  |   60 +
 .../DefaultResolutionResultBuilderSpec.groovy      |  284 ++
 .../resolveengine/result/DummyBinaryStore.groovy   |   48 +
 .../resolveengine/result/DummyStore.groovy         |   24 +
 .../InternalDependencyResultSerializerTest.groovy  |   80 +
 .../ModuleVersionSelectionSerializerTest.groovy    |   40 +
 .../result/ResolutionResultBuilderSpec.groovy      |  268 --
 .../result/ResolutionResultPrinter.groovy          |   47 +
 .../StreamingResolutionResultBuilderTest.groovy    |  139 +
 .../result/VersionSelectionReasonsTest.groovy      |    3 -
 .../store/CachedStoreFactoryTest.groovy            |   40 +
 .../store/DefaultBinaryStoreTest.groovy            |  108 +
 .../store/ResolutionResultsStoreFactoryTest.groovy |   93 +
 .../metadata/DefaultDependencyMetaDataTest.groovy  |  162 +
 .../metadata/DefaultIvyArtifactNameTest.groovy     |   53 +
 .../DefaultLocalArtifactIdentifierTest.groovy      |   67 +
 .../DefaultLocalComponentMetaDataTest.groovy       |  146 +
 ...faultModuleVersionArtifactIdentifierTest.groovy |   67 +
 ...DefaultModuleVersionArtifactMetaDataTest.groovy |   71 +
 .../DefaultModuleVersionPublishMetaDataTest.groovy |   42 +
 .../metadata/ModuleDescriptorAdapterTest.groovy    |  296 ++
 .../DefaultLocalMavenRepositoryLocatorTest.groovy  |    4 +-
 .../DefaultBaseRepositoryFactoryTest.groovy        |  167 +-
 .../DefaultFlatDirArtifactRepositoryTest.groovy    |   30 +-
 .../DefaultIvyArtifactRepositoryTest.groovy        |   76 +-
 .../DefaultMavenArtifactRepositoryTest.groovy      |   51 +-
 .../DefaultMavenLocalRepositoryTest.groovy         |   69 +
 .../DownloadingRepositoryCacheManagerTest.groovy   |   71 -
 .../DownloadingRepositoryCacheManagerTest.groovy   |   67 +
 .../resolver/ChainedVersionListerTest.groovy       |   44 +-
 .../resolver/ExternalResourceResolverTest.groovy   |   80 +-
 .../resolver/IvyResourcePatternTest.groovy         |   25 +-
 .../resolver/M2ResourcePatternTest.groovy          |   58 +-
 .../repositories/resolver/MavenResolverTest.groovy |    7 +-
 .../resolver/MavenVersionListerTest.groovy         |   77 +-
 .../resolver/ResourceVersionListerTest.groovy      |   65 +-
 .../DefaultArtifactResolutionCacheTest.groovy      |   80 -
 .../result/DefaultResolutionResultTest.groovy      |   28 +-
 .../DefaultResolvedModuleVersionResultSpec.groovy  |    4 -
 .../CachedExternalResourceAdapterTest.groovy       |   83 -
 .../CachedExternalResourceAdapterTest.groovy       |   78 +
 .../DefaultArtifactResolutionCacheTest.groovy      |   84 +
 ...ifactAtRepositoryCachedArtifactIndexTest.groovy |   18 +-
 ...positeLocallyAvailableResourceFinderTest.groovy |    3 +-
 .../DefaultLocallyAvailableResourceTest.groovy     |   66 -
 ...zyLocallyAvailableResourceCandidatesTest.groovy |    2 +-
 .../DefaultExternalResourceMetaDataTest.groovy     |    2 +-
 ...ltCacheAwareExternalResourceAccessorTest.groovy |   14 +-
 ...gressLoggingExternalResourceAccessorTest.groovy |   15 +-
 .../http/ApacheDirectoryListingParserTest.groovy   |   16 +-
 .../transport/http/HttpResourceListerTest.groovy   |    3 +-
 ...avaSystemPropertiesHttpProxySettingsTest.groovy |   15 +-
 .../DependencyMapNotationParserTest.groovy         |    8 +-
 .../notations/DependencyNotationParserTest.groovy  |    5 +-
 .../DependencyStringNotationParserTest.groovy      |   12 +-
 .../notations/ProjectDependencyFactoryTest.groovy  |    3 -
 .../ivyresolve/parser/test-bad-confs.xml           |   27 -
 .../ivyresolve/parser/test-cyclic-confs1.xml       |   28 -
 .../ivyresolve/parser/test-empty-dependencies.xml  |   28 -
 .../ivyservice/ivyresolve/parser/test-full.xml     |    4 +-
 .../result/ResolutionResultDataBuilder.groovy      |   26 +-
 subprojects/core/core.gradle                       |   17 +-
 .../DeprecationHandlingIntegrationTest.groovy      |  125 +
 .../org/gradle/api/ApplyPluginIntegSpec.groovy     |  129 +
 .../api/BuildEventsErrorIntegrationTest.groovy     |   94 +
 .../api/BuildScriptErrorIntegrationTest.groovy     |   98 +
 .../ConfigurationOnDemandIntegrationTest.groovy    |   99 +-
 .../api/CrossProcessFileLockIntegrationTest.groovy |   71 +
 ...rredConfigurableExtensionIntegrationTest.groovy |  174 +
 .../api/ExternalScriptErrorIntegrationTest.groovy  |   87 +
 .../gradle/api/FinalizerTaskIntegrationTest.groovy |  248 ++
 .../gradle/api/GradlePluginIntegrationTest.groovy  |   97 +
 .../api/InitScriptErrorIntegrationTest.groovy      |   58 +
 .../org/gradle/api/ProfilingIntegrationTest.groovy |   46 +
 .../api/ProjectConfigurationIntegrationTest.groovy |    3 -
 ...ojectConfigureEventsErrorIntegrationTest.groovy |   78 +
 .../api/SettingsPluginIntegrationSpec.groovy       |   86 +
 .../api/SettingsScriptErrorIntegrationTest.groovy  |   41 +
 .../api/dsl/ConcurrentClassDecorationSpec.groovy   |   48 +
 .../BuildExecutionEventsIntegrationTest.groovy     |   57 +
 .../gradle/api/tasks/ArchiveIntegrationTest.groovy |   80 +-
 .../api/tasks/CopyErrorIntegrationTest.groovy      |    3 +-
 .../api/tasks/CopyTaskIntegrationSpec.groovy       |  121 +
 .../api/tasks/CopyTaskIntegrationTest.groovy       |   74 +
 .../tasks/IncrementalTaskIntegrationTest.groovy    |   47 +
 ...kCommandLineConfigurationIntegrationSpec.groovy |  299 ++
 .../api/tasks/TaskRemovalIntegrationTest.groovy    |   88 +
 .../api/tasks/bundling/ZipIntegrationTest.groovy   |  119 +
 .../plugin/PluginHandlerScriptIntegTest.groovy     |  554 +++
 .../ScriptPluginClassLoadingIntegrationTest.groovy |   54 +
 .../BintrayPluginResolutionIntegTest.groovy        |  115 +
 .../internal/PathLimitationIntegTest.groovy        |  255 ++
 .../internal/WorkerProcessIntegrationTest.java     |   48 +-
 .../groovy/org/gradle/BuildExceptionReporter.java  |   52 +-
 .../src/main/groovy/org/gradle/BuildListener.java  |    4 +-
 .../src/main/groovy/org/gradle/CacheUsage.java     |    1 -
 .../src/main/groovy/org/gradle/GradleLauncher.java |   54 +-
 .../src/main/groovy/org/gradle/StartParameter.java |   90 +-
 .../groovy/org/gradle/TaskExecutionLogger.java     |    9 +-
 .../org/gradle/api/CircularReferenceException.java |    2 -
 .../main/groovy/org/gradle/api/DefaultTask.java    |   12 -
 ...ExtensiblePolymorphicDomainObjectContainer.java |   28 +
 .../org/gradle/api/GradleScriptException.java      |    4 +-
 .../org/gradle/api/IllegalDependencyNotation.java  |    2 -
 .../IllegalOperationAtExecutionTimeException.java  |    3 +-
 .../org/gradle/api/InvalidUserCodeException.java   |    2 +-
 .../org/gradle/api/InvalidUserDataException.java   |    2 -
 .../gradle/api/NamedDomainObjectCollection.java    |   14 +-
 .../org/gradle/api/NamedDomainObjectContainer.java |   11 +-
 .../main/groovy/org/gradle/api/NonExtensible.java  |   31 +
 .../src/main/groovy/org/gradle/api/Plugin.java     |    3 +-
 .../api/PolymorphicDomainObjectContainer.java      |   15 +
 .../src/main/groovy/org/gradle/api/Project.java    |   13 +-
 .../gradle/api/ProjectConfigurationException.java  |   29 +
 .../org/gradle/api/ProjectEvaluationListener.java  |    2 -
 .../main/groovy/org/gradle/api/ProjectState.java   |    3 +
 .../core/src/main/groovy/org/gradle/api/Rule.java  |    2 -
 .../src/main/groovy/org/gradle/api/Script.java     |    1 +
 .../core/src/main/groovy/org/gradle/api/Task.java  |  160 +-
 .../org/gradle/api/UnknownProjectException.java    |    2 -
 .../org/gradle/api/UnknownTaskException.java       |    2 -
 .../main/groovy/org/gradle/api/XmlProvider.java    |    2 -
 .../gradle/api/artifacts/ArtifactIdentifier.java   |   12 +-
 .../api/artifacts/ArtifactRepositoryContainer.java |   42 +-
 .../org/gradle/api/artifacts/ClientModule.java     |    2 -
 .../api/artifacts/ComponentMetadataDetails.java    |   86 +
 .../org/gradle/api/artifacts/Configuration.java    |    8 +-
 .../api/artifacts/ConfigurationContainer.java      |   16 +-
 .../org/gradle/api/artifacts/Dependency.java       |    5 +-
 .../gradle/api/artifacts/DependencyArtifact.java   |    6 +-
 .../org/gradle/api/artifacts/ExcludeRule.java      |    4 +-
 .../gradle/api/artifacts/ExcludeRuleContainer.java |    2 -
 .../gradle/api/artifacts/ExternalDependency.java   |    2 -
 .../api/artifacts/ExternalModuleDependency.java    |    2 -
 .../groovy/org/gradle/api/artifacts/Module.java    |    5 +-
 .../gradle/api/artifacts/ProjectDependency.java    |    4 +-
 .../org/gradle/api/artifacts/PublishArtifact.java  |    6 +-
 .../org/gradle/api/artifacts/PublishException.java |    2 +-
 .../org/gradle/api/artifacts/ResolveException.java |    6 +-
 .../org/gradle/api/artifacts/ResolvedArtifact.java |    2 -
 .../gradle/api/artifacts/ResolvedDependency.java   |    2 -
 .../api/artifacts/UnknownRepositoryException.java  |    2 -
 .../cache/DependencyResolutionControl.java         |    6 +-
 .../artifacts/component/ComponentIdentifier.java   |   35 +
 .../api/artifacts/component/ComponentSelector.java |   44 +
 .../component/ModuleComponentIdentifier.java       |   50 +
 .../component/ModuleComponentSelector.java         |   48 +
 .../component/ProjectComponentIdentifier.java      |   33 +
 .../component/ProjectComponentSelector.java        |   34 +
 .../api/artifacts/component/package-info.java      |   20 +
 .../gradle/api/artifacts/dsl/ArtifactHandler.java  |    2 -
 .../artifacts/dsl/ComponentMetadataHandler.java    |   54 +
 .../api/artifacts/dsl/DependencyHandler.java       |   39 +-
 .../api/artifacts/dsl/RepositoryHandler.java       |   68 +-
 .../artifacts/repositories/ArtifactRepository.java |    2 +-
 .../repositories/IvyArtifactRepository.java        |    2 +-
 .../resolution/ArtifactResolutionQuery.java        |   32 +
 .../resolution/ArtifactResolutionQueryResult.java  |   32 +
 .../api/artifacts/resolution/JvmLibrary.java       |   31 +
 .../artifacts/resolution/JvmLibraryArtifact.java   |   27 +
 .../resolution/JvmLibraryJavadocArtifact.java      |   27 +
 .../resolution/JvmLibrarySourcesArtifact.java      |   27 +
 .../api/artifacts/resolution/SoftwareArtifact.java |   45 +
 .../artifacts/resolution/SoftwareComponent.java    |   32 +
 .../resolution/UnresolvedSoftwareComponent.java    |   41 +
 .../api/artifacts/resolution/package-info.java     |   20 +
 .../artifacts/result/ComponentSelectionReason.java |   64 +
 .../api/artifacts/result/DependencyResult.java     |   18 +-
 .../result/ModuleVersionSelectionReason.java       |   56 -
 .../api/artifacts/result/ResolutionResult.java     |   32 +-
 .../artifacts/result/ResolvedComponentResult.java  |   85 +
 .../artifacts/result/ResolvedDependencyResult.java |    4 +-
 .../result/ResolvedModuleVersionResult.java        |   60 -
 .../result/UnresolvedDependencyResult.java         |    8 +-
 .../gradle/api/component/SoftwareComponent.java    |    3 +-
 .../org/gradle/api/dsl/ConventionProperty.java     |    2 -
 .../gradle/api/execution/TaskExecutionAdapter.java |    2 +-
 .../groovy/org/gradle/api/file/CopySourceSpec.java |    2 -
 .../main/groovy/org/gradle/api/file/CopySpec.java  |   53 +-
 .../groovy/org/gradle/api/file/DeleteAction.java   |    2 -
 .../api/file/DuplicateFileCopyingException.java    |   31 +
 .../org/gradle/api/file/DuplicatesStrategy.java    |   59 +
 .../org/gradle/api/file/EmptyFileVisitor.java      |    2 -
 .../org/gradle/api/file/FileCopyDetails.java       |   41 +
 .../main/groovy/org/gradle/api/file/FileTree.java  |    2 +-
 .../groovy/org/gradle/api/file/FileVisitor.java    |    2 -
 .../groovy/org/gradle/api/file/RelativePath.java   |    2 -
 .../api/initialization/ProjectDescriptor.java      |    2 -
 .../org/gradle/api/initialization/Settings.java    |    5 +-
 .../api/internal/AbstractClassGenerator.java       |   29 +-
 .../api/internal/AbstractMultiCauseException.java  |  101 -
 .../AbstractNamedDomainObjectContainer.java        |   13 +-
 .../AbstractPolymorphicDomainObjectContainer.java  |   17 +-
 .../org/gradle/api/internal/AbstractTask.java      |  169 +-
 .../api/internal/AsmBackedClassGenerator.java      |  181 +-
 .../org/gradle/api/internal/BeanDynamicObject.java |   37 +-
 .../api/internal/CachingDirectedGraphWalker.java   |  176 -
 .../internal/ClassGeneratorBackedInstantiator.java |    2 +-
 .../api/internal/CompositeDomainObjectSet.java     |    9 +-
 .../org/gradle/api/internal/ConfigureDelegate.java |   62 +-
 .../groovy/org/gradle/api/internal/Contextual.java |   29 -
 .../gradle/api/internal/ConventionAwareHelper.java |   17 +-
 .../org/gradle/api/internal/ConventionTask.java    |    3 -
 .../DefaultNamedDomainObjectCollection.java        |    8 +
 .../DefaultPolymorphicDomainObjectContainer.java   |   45 +-
 .../internal/DependencyInjectingInstantiator.java  |    2 +-
 .../org/gradle/api/internal/DirectedGraph.java     |   26 -
 .../api/internal/DirectedGraphWithEdgeValues.java  |   25 -
 .../gradle/api/internal/DocumentationRegistry.java |   31 +-
 .../org/gradle/api/internal/GradleInternal.java    |   22 +-
 .../org/gradle/api/internal/GraphAggregator.java   |   90 -
 .../org/gradle/api/internal/IConventionAware.java  |    2 -
 .../api/internal/LocationAwareException.java       |  179 -
 .../gradle/api/internal/MultiCauseException.java   |   22 -
 ...amedDomainObjectContainerConfigureDelegate.java |   17 +-
 ...phicDomainObjectContainerConfigureDelegate.java |   25 +-
 .../org/gradle/api/internal/SettingsInternal.java  |    9 +-
 .../org/gradle/api/internal/TaskInternal.java      |    8 +-
 .../gradle/api/internal/TaskOutputsInternal.java   |    1 +
 .../artifacts/ArtifactPublicationServices.java     |   31 -
 .../api/internal/artifacts/ArtifactPublisher.java  |   31 -
 .../internal/artifacts/BaseRepositoryFactory.java  |    5 +
 .../artifacts/CachingDependencyResolveContext.java |    6 +-
 .../artifacts/DefaultArtifactIdentifier.java       |   55 -
 .../DefaultArtifactRepositoryContainer.java        |   12 +-
 .../api/internal/artifacts/DefaultExcludeRule.java |    5 -
 .../artifacts/DefaultExcludeRuleContainer.java     |    7 +-
 .../api/internal/artifacts/DefaultModule.java      |   11 +-
 .../artifacts/DependencyManagementServices.java    |   14 +-
 .../artifacts/DependencyResolutionServices.java    |    7 -
 .../DependencyResolveDetailsInternal.java          |    7 +-
 .../artifacts/ExcludeRuleNotationParser.java       |   13 +-
 .../api/internal/artifacts/ModuleInternal.java     |   25 +
 .../artifacts/ModuleVersionSelectorStrictSpec.java |    3 -
 .../internal/artifacts/ProjectBackedModule.java    |   30 +-
 .../ConfigurationContainerInternal.java            |    2 +
 .../configurations/ConfigurationInternal.java      |    2 +
 .../configurations/DependencyMetaDataProvider.java |    7 +-
 .../configurations/dynamicversion/CachePolicy.java |    7 +-
 .../artifacts/dependencies/AbstractDependency.java |    5 +-
 .../dependencies/DefaultClientModule.java          |    3 -
 .../DefaultExternalModuleDependency.java           |    3 -
 .../dependencies/DefaultProjectDependency.java     |    9 +-
 .../dependencies/ProjectDependencyInternal.java    |   28 +
 .../artifacts/dsl/DefaultRepositoryHandler.java    |   27 +-
 .../ArtifactResolutionQueryFactory.java            |   22 +
 .../dependencies/DefaultDependencyHandler.groovy   |  109 -
 .../dsl/dependencies/DefaultDependencyHandler.java |  135 +
 .../dsl/dependencies/DependencyFactory.java        |    3 -
 .../dependencies/ModuleDescriptorDelegate.groovy   |    3 -
 .../dsl/dependencies/ModuleFactoryHelper.java      |    5 +-
 .../artifacts/dsl/dependencies/ProjectFinder.java  |    3 -
 .../ivyservice/ModuleDescriptorConverter.java      |   35 -
 .../artifacts/publish/AbstractPublishArtifact.java |    3 -
 .../artifacts/publish/ArchivePublishArtifact.java  |   21 +-
 .../artifacts/publish/DefaultPublishArtifact.java  |    3 -
 .../repositories/PublicationAwareRepository.java   |   23 -
 .../version/LatestVersionSemanticComparator.java   |   49 -
 .../org/gradle/api/internal/cache/BinaryStore.java |   42 +
 .../org/gradle/api/internal/cache/Store.java       |   22 +
 .../CacheBackedFileSnapshotRepository.java         |   44 -
 .../CacheBackedTaskHistoryRepository.java          |  174 -
 .../CacheLockHandlingTaskExecuter.java             |   38 -
 .../internal/changedetection/CachingHasher.java    |   78 -
 .../changedetection/CompositeUpToDateRule.java     |   52 -
 .../changedetection/DefaultFileCacheListener.java  |   45 -
 .../changedetection/DefaultFileSnapshotter.java    |  158 -
 .../internal/changedetection/DefaultHasher.java    |   26 -
 .../DefaultTaskArtifactStateCacheAccess.java       |   85 -
 .../DefaultTaskArtifactStateRepository.java        |  172 -
 ...eCacheBroadcastTaskArtifactStateRepository.java |   63 -
 .../changedetection/FileCacheListener.java         |   35 -
 .../changedetection/FileCollectionSnapshot.java    |   59 -
 .../changedetection/FileSnapshotRepository.java    |   24 -
 .../internal/changedetection/FileSnapshotter.java  |   35 -
 .../api/internal/changedetection/Hasher.java       |   22 -
 .../changedetection/InMemoryIndexedCache.java      |   65 -
 .../InputFilesChangedUpToDateRule.java             |   63 -
 .../InputPropertiesChangedUpToDateRule.java        |   55 -
 .../changedetection/MapMergeChangeListener.java    |   67 -
 .../OutputFilesChangedUpToDateRule.java            |   85 -
 .../changedetection/OutputFilesSnapshotter.java    |  154 -
 .../ShortCircuitTaskArtifactStateRepository.java   |   96 -
 .../changedetection/TaskArtifactState.java         |   11 +-
 .../TaskArtifactStateCacheAccess.java              |   50 -
 .../TaskCacheLockHandlingBuildExecuter.java        |   35 -
 .../internal/changedetection/TaskExecution.java    |   67 -
 .../changedetection/TaskHistoryRepository.java     |   30 -
 .../TaskTypeChangedUpToDateRule.java               |   42 -
 .../api/internal/changedetection/UpToDateRule.java |   48 -
 .../changes/ChangesOnlyIncrementalTaskInputs.java  |   57 +
 .../DefaultTaskArtifactStateRepository.java        |  132 +
 .../changes/NoHistoryArtifactState.java            |   52 +
 .../changes/RebuildIncrementalTaskInputs.java      |   73 +
 .../ShortCircuitTaskArtifactStateRepository.java   |   96 +
 .../changes/StatefulIncrementalTaskInputs.java     |   49 +
 .../rules/CachingTaskStateChanges.java             |   86 +
 .../internal/changedetection/rules/ChangeType.java |   33 +
 .../changedetection/rules/DescriptiveChange.java   |   29 +
 .../internal/changedetection/rules/FileChange.java |   60 +
 .../changedetection/rules/InputFileChange.java     |   31 +
 .../rules/InputFilesStateChangeRule.java           |   77 +
 .../rules/InputPropertiesStateChangeRule.java      |   54 +
 .../rules/NoHistoryStateChangeRule.java            |   35 +
 .../changedetection/rules/OutputFileChange.java    |   29 +
 .../rules/OutputFilesStateChangeRule.java          |  100 +
 .../rules/SimpleTaskStateChanges.java              |   38 +
 .../rules/SummaryTaskStateChanges.java             |   76 +
 .../changedetection/rules/TaskStateChange.java     |   21 +
 .../changedetection/rules/TaskStateChanges.java    |   25 +
 .../rules/TaskTypeStateChangeRule.java             |   43 +
 .../changedetection/rules/TaskUpToDateState.java   |   66 +
 .../state/CacheBackedFileSnapshotRepository.java   |   45 +
 .../state/CacheBackedTaskHistoryRepository.java    |  279 ++
 .../state/CachingFileSnapshotter.java              |   87 +
 .../state/DefaultFileCollectionSnapshotter.java    |  190 +
 .../state/DefaultFileSnapshotterSerializer.java    |   67 +
 .../state/DefaultTaskArtifactStateCacheAccess.java |   68 +
 .../state/FileCollectionSnapshot.java              |   61 +
 .../state/FileCollectionSnapshotter.java           |   41 +
 .../state/FileSnapshotRepository.java              |   24 +
 .../changedetection/state/FileSnapshotter.java     |   37 +
 .../state/InMemoryTaskArtifactCache.java           |  126 +
 .../state/MapMergeChangeListener.java              |   67 +
 .../changedetection/state/NoOpDecorator.java       |   26 +
 .../state/OutputFilesCollectionSnapshotter.java    |  226 +
 .../state/OutputFilesSnapshotSerializer.java       |   63 +
 .../state/TaskArtifactStateCacheAccess.java        |   25 +
 .../changedetection/state/TaskExecution.java       |   66 +
 .../state/TaskHistoryRepository.java               |   30 +
 .../internal/classpath/DefaultModuleRegistry.java  |    2 +-
 .../api/internal/classpath/EffectiveClassPath.java |    2 +-
 .../api/internal/classpath/ManifestUtil.java       |    1 -
 .../coerce/MethodArgumentsTransformer.java         |   36 +
 .../TypeCoercingMethodArgumentsTransformer.java    |   91 +
 .../collections/CollectionEventRegister.java       |    2 +-
 .../api/internal/file/AbstractFileResolver.java    |   16 +-
 .../api/internal/file/AbstractFileResource.java    |    3 -
 .../api/internal/file/AbstractFileTreeElement.java |   17 +-
 .../internal/file/AntFileCollectionBuilder.groovy  |    5 +-
 .../api/internal/file/BaseDirFileResolver.java     |    3 -
 .../file/CopyActionProcessingStreamAction.java     |   25 +
 .../internal/file/DefaultCompositeFileTree.java    |   45 +
 .../api/internal/file/DefaultFileLookup.java       |   43 +
 .../api/internal/file/DefaultFileOperations.java   |   66 +-
 .../api/internal/file/DefaultFileTreeElement.java  |   12 +-
 .../api/internal/file/DefaultFileVisitDetails.java |    6 +-
 .../file/DefaultTemporaryFileProvider.java         |    3 +-
 .../org/gradle/api/internal/file/FileLookup.java   |   38 +
 .../gradle/api/internal/file/FileOperations.java   |    5 +
 .../api/internal/file/FileOrUriNotationParser.java |    7 +-
 .../org/gradle/api/internal/file/FileResolver.java |   13 +-
 .../org/gradle/api/internal/file/FileResource.java |    3 -
 .../api/internal/file/IdentityFileResolver.java    |    9 +-
 .../internal/file/MaybeCompressedFileResource.java |    3 -
 .../api/internal/file/archive/TarCopyAction.java   |  114 +
 .../internal/file/archive/TarCopySpecVisitor.java  |   88 -
 .../api/internal/file/archive/TarFileTree.java     |   15 +-
 .../api/internal/file/archive/ZipCopyAction.java   |  101 +-
 .../internal/file/archive/ZipCopySpecVisitor.java  |   81 -
 .../api/internal/file/archive/ZipFileTree.java     |   14 +-
 .../file/archive/compression/Bzip2Archiver.java    |    3 -
 .../file/archive/compression/GzipArchiver.java     |    3 -
 .../file/archive/compression/SimpleCompressor.java |    3 -
 .../collections/DefaultConfigurableFileTree.java   |   37 +-
 .../file/collections/DelegatingFileCollection.java |  111 +
 .../file/collections/DirectoryFileTree.java        |   13 +-
 .../LazilyInitializedFileCollection.java           |   35 +
 .../api/internal/file/collections/MapFileTree.java |   18 +-
 .../collections/SingleIncludePatternFileTree.java  |   14 +-
 .../file/collections/SingletonFileTree.java        |   19 +-
 .../internal/file/copy/AbstractZipCompressor.java  |   37 -
 .../api/internal/file/copy/ArchiveCopyAction.java  |   25 -
 .../gradle/api/internal/file/copy/CopyAction.java  |   49 +-
 .../api/internal/file/copy/CopyActionExecuter.java |   41 +
 .../api/internal/file/copy/CopyActionImpl.java     |  235 +-
 .../file/copy/CopyActionProcessingStream.java      |   25 +
 .../internal/file/copy/CopyFileVisitorImpl.java    |   66 +
 .../api/internal/file/copy/CopySpecActionImpl.java |   39 +
 .../CopySpecBackedCopyActionProcessingStream.java  |   38 +
 .../api/internal/file/copy/CopySpecImpl.java       |  461 --
 .../api/internal/file/copy/CopySpecInternal.java   |   47 +
 .../api/internal/file/copy/CopySpecSource.java     |    2 +-
 .../api/internal/file/copy/CopySpecVisitor.java    |   36 -
 .../api/internal/file/copy/CopySpecWrapper.java    |  222 +
 .../api/internal/file/copy/DefaultCopySpec.java    |  469 ++
 .../internal/file/copy/DefaultFileCopyDetails.java |  217 +
 .../internal/file/copy/DefaultZipCompressor.java   |   45 +
 .../file/copy/DelegatingCopySpecInternal.java      |  228 +
 .../file/copy/DelegatingCopySpecVisitor.java       |   54 -
 .../api/internal/file/copy/DeleteActionImpl.java   |    3 -
 .../file/copy/DestinationRootCopySpec.java         |   51 +
 .../copy/DuplicateHandlingCopyActionDecorator.java |   66 +
 .../internal/file/copy/EmptyCopySpecVisitor.java   |   39 -
 .../gradle/api/internal/file/copy/FileCopier.java  |   67 +
 .../api/internal/file/copy/FileCopyAction.java     |   32 +-
 .../api/internal/file/copy/FileCopyActionImpl.java |   33 +-
 .../file/copy/FileCopyDetailsInternal.java         |   25 +
 .../internal/file/copy/FileCopySpecVisitor.java    |   61 -
 .../internal/file/copy/MappingCopySpecVisitor.java |  230 -
 .../api/internal/file/copy/MatchingCopyAction.java |   40 +
 .../file/copy/NormalizingCopyActionDecorator.java  |  193 +
 .../file/copy/NormalizingCopySpecVisitor.java      |  121 -
 .../api/internal/file/copy/PathNotationParser.java |    4 +-
 .../api/internal/file/copy/ReadableCopySpec.java   |   32 +-
 .../api/internal/file/copy/RegExpNameMapper.java   |    3 -
 .../internal/file/copy/RelativizedCopySpec.java    |   51 +
 .../file/copy/SyncCopyActionDecorator.java         |   92 +
 .../internal/file/copy/SyncCopySpecVisitor.java    |   90 -
 .../api/internal/file/copy/ZipCompressor.java      |    5 +-
 .../internal/file/copy/ZipDeflatedCompressor.java  |   31 -
 .../internal/file/copy/ZipStoredCompressor.java    |   28 -
 .../file/pattern/AnyWildcardPatternStep.java       |   23 +
 .../file/pattern/DefaultPatternMatcher.java        |  159 -
 .../internal/file/pattern/EndOfPathMatcher.java    |   35 +
 .../internal/file/pattern/FixedPatternStep.java    |   34 +
 .../file/pattern/FixedStepsPathMatcher.java        |   70 +
 .../internal/file/pattern/GreedyPathMatcher.java   |   48 +
 .../internal/file/pattern/GreedyPatternStep.java   |   29 -
 .../file/pattern/NameOnlyPatternMatcher.java       |   58 -
 .../api/internal/file/pattern/PathMatcher.java     |   39 +
 .../file/pattern/PatternMatcherFactory.java        |   69 +-
 .../api/internal/file/pattern/PatternStep.java     |    6 +-
 .../internal/file/pattern/PatternStepFactory.java  |   46 +-
 .../internal/file/pattern/RegExpPatternStep.java   |   21 +-
 .../file/pattern/WildcardPrefixPatternStep.java    |   36 +
 .../internal/filestore/AbstractFileStoreEntry.java |   28 -
 .../gradle/api/internal/filestore/FileStore.java   |   31 -
 .../api/internal/filestore/FileStoreEntry.java     |   29 -
 .../api/internal/filestore/FileStoreSearcher.java  |   25 -
 .../filestore/GroupedAndNamedUniqueFileStore.java  |   13 +-
 .../api/internal/filestore/PathKeyFileStore.java   |   29 +-
 .../filestore/PathNormalisingKeyFileStore.java     |   11 +-
 .../internal/filestore/UniquePathKeyFileStore.java |    7 +-
 .../gradle/api/internal/hash/DefaultHasher.java    |   26 +
 .../org/gradle/api/internal/hash/Hasher.java       |   22 +
 .../initialization/AbstractScriptHandler.java      |   14 +-
 .../internal/initialization/ClassLoaderCache.java  |   27 +
 .../internal/initialization/ClassLoaderScope.java  |   89 +
 .../initialization/DefaultClassLoaderCache.java    |  102 +
 .../initialization/DefaultClassLoaderScope.java    |  168 +
 .../initialization/DefaultScriptHandler.java       |   75 +-
 .../DefaultScriptHandlerFactory.java               |   24 +-
 .../NoClassLoaderUpdateScriptHandler.java          |   33 -
 .../initialization/RootClassLoaderScope.java       |   70 +
 .../internal/initialization/ScriptClassLoader.java |   41 +
 .../initialization/ScriptClassLoaderProvider.java  |   22 -
 .../ScriptHandlerClassLoaderFactory.java           |   43 +
 .../initialization/ScriptHandlerFactory.java       |    5 +-
 .../initialization/ScriptHandlerInternal.java      |   21 -
 .../internal/notations/NotationParserBuilder.java  |   84 -
 .../gradle/api/internal/notations/TypeInfo.java    |   35 -
 .../api/internal/notations/api/NotationParser.java |   28 -
 .../api/UnsupportedNotationException.java          |   28 -
 .../parsers/CharSequenceNotationParser.java        |   28 -
 .../parsers/ClosureToSpecNotationParser.java       |   41 -
 .../notations/parsers/CompositeNotationParser.java |   53 -
 .../parsers/ErrorHandlingNotationParser.java       |   71 -
 .../parsers/FlatteningNotationParser.java          |   51 -
 .../notations/parsers/JustReturningParser.java     |   44 -
 .../api/internal/notations/parsers/MapKey.java     |   28 -
 .../notations/parsers/MapNotationParser.java       |  133 -
 .../notations/parsers/NormalizedTimeUnit.java      |   45 -
 .../notations/parsers/TimeUnitsParser.java         |   46 -
 .../notations/parsers/TypedNotationParser.java     |   48 -
 .../ClassloaderBackedPluginDescriptorLocator.java  |   38 +
 .../api/internal/plugins/DefaultConvention.java    |   13 +-
 .../plugins/DefaultObjectConfigurationAction.java  |   39 +-
 .../internal/plugins/DefaultPluginContainer.java   |  107 +
 .../internal/plugins/DefaultPluginRegistry.java    |   44 +-
 .../plugins/DefaultProjectsPluginContainer.java    |  110 -
 .../org/gradle/api/internal/plugins/DslObject.java |    4 +
 .../plugins/ExtensionContainerInternal.java        |   29 +
 .../api/internal/plugins/ExtensionsStorage.java    |   31 +-
 .../api/internal/plugins/PluginDescriptor.java     |   41 +
 .../internal/plugins/PluginDescriptorLocator.java  |   23 +
 .../api/internal/plugins/PluginRegistry.java       |    9 +-
 .../api/internal/project/AbstractPluginAware.java  |   52 +
 .../api/internal/project/AbstractProject.java      |  180 +-
 .../api/internal/project/DefaultAntBuilder.groovy  |   96 -
 .../api/internal/project/DefaultAntBuilder.java    |  138 +
 .../internal/project/DefaultAntBuilderFactory.java |   12 +-
 .../project/DefaultIsolatedAntBuilder.groovy       |    4 +
 .../api/internal/project/DefaultProject.java       |    6 +-
 .../project/DefaultProjectAccessListener.java      |    3 -
 .../internal/project/DefaultProjectRegistry.java   |    9 +-
 .../internal/project/GlobalServicesRegistry.java   |  113 -
 .../project/GradleInternalServiceRegistry.java     |   92 -
 .../api/internal/project/IProjectFactory.java      |    5 +-
 .../api/internal/project/IProjectRegistry.java     |   40 -
 .../api/internal/project/ProjectFactory.java       |   18 +-
 .../api/internal/project/ProjectInternal.java      |   21 +-
 .../project/ProjectInternalServiceRegistry.java    |  180 -
 .../api/internal/project/ProjectRegistry.java      |   37 +
 .../api/internal/project/ProjectScript.groovy      |    2 +-
 .../api/internal/project/ProjectStateInternal.java |    4 +
 .../internal/project/ServiceRegistryFactory.java   |   31 -
 .../internal/project/TaskExecutionServices.java    |   86 -
 .../project/TaskInternalServiceRegistry.java       |   61 -
 .../project/TopLevelBuildServiceRegistry.java      |  253 --
 .../internal/project/ant/AntLoggingAdapter.java    |    3 -
 .../api/internal/project/ant/BasicAntBuilder.java  |    5 +
 .../AnnotationProcessingTaskFactory.java           |  200 +-
 .../internal/project/taskfactory/ITaskFactory.java |    3 -
 .../internal/project/taskfactory/TaskFactory.java  |   27 +-
 .../api/internal/resource/ResourceException.java   |    2 +-
 .../internal/resources/DefaultResourceHandler.java |    3 -
 .../gradle/api/internal/resources/URIBuilder.java  |    3 -
 .../gradle/api/internal/specs/ExplainingSpecs.java |    3 -
 .../tasks/CachingTaskDependencyResolveContext.java |    6 +-
 .../api/internal/tasks/CommandLineOption.java      |   40 -
 .../api/internal/tasks/ContextAwareTaskAction.java |   24 +
 .../api/internal/tasks/DefaultTaskContainer.java   |  124 +-
 .../api/internal/tasks/SimpleWorkResult.java       |   30 +
 .../api/internal/tasks/TaskContainerInternal.java  |   26 +-
 .../gradle/api/internal/tasks/TaskExecuter.java    |    2 +-
 .../api/internal/tasks/TaskExecutionContext.java   |   24 +
 .../api/internal/tasks/TaskStatusNagger.java       |    9 +-
 .../execution/DefaultTaskExecutionContext.java     |   31 +
 .../execution/ExecuteActionsTaskExecuter.java      |   26 +-
 .../execution/ExecuteAtMostOnceTaskExecuter.java   |    5 +-
 .../PostExecutionAnalysisTaskExecuter.java         |    5 +-
 .../SkipEmptySourceFilesTaskExecuter.java          |    5 +-
 .../tasks/execution/SkipOnlyIfTaskExecuter.java    |    5 +-
 .../execution/SkipTaskWithNoActionsExecuter.java   |    5 +-
 .../tasks/execution/SkipUpToDateTaskExecuter.java  |   39 +-
 .../tasks/execution/ValidatingTaskExecuter.java    |    5 +-
 .../tasks/options/AbstractOptionElement.java       |   96 +
 .../internal/tasks/options/FieldOptionElement.java |   85 +
 .../tasks/options/InstanceOptionDescriptor.java    |   85 +
 .../tasks/options/MethodOptionElement.java         |   81 +
 .../gradle/api/internal/tasks/options/Option.java  |   40 +
 .../internal/tasks/options/OptionDescriptor.java   |   38 +
 .../api/internal/tasks/options/OptionElement.java  |   40 +
 .../tasks/options/OptionNotationParserFactory.java |   83 +
 .../api/internal/tasks/options/OptionReader.java   |  148 +
 .../tasks/options/OptionValidationException.java   |   28 +
 .../api/internal/tasks/options/OptionValues.groovy |   34 +
 .../api/internal/xml/SimpleMarkupWriter.java       |    8 +-
 .../gradle/api/internal/xml/SimpleXmlWriter.java   |    4 +-
 .../gradle/api/internal/xml/XmlTransformer.java    |    2 +-
 .../groovy/org/gradle/api/invocation/Gradle.java   |    7 +-
 .../groovy/org/gradle/api/logging/LogLevel.java    |    2 -
 .../groovy/org/gradle/api/logging/Logging.java     |    2 -
 .../groovy/org/gradle/api/plugins/Convention.java  |    2 -
 .../org/gradle/api/plugins/ExtensionContainer.java |    2 +
 .../groovy/org/gradle/api/plugins/PluginAware.java |   62 +
 .../org/gradle/api/plugins/PluginCollection.java   |    8 +-
 .../org/gradle/api/plugins/PluginContainer.java    |    2 -
 .../api/plugins/PluginInstantiationException.java  |    2 -
 .../gradle/api/plugins/UnknownPluginException.java |    2 -
 .../main/groovy/org/gradle/api/specs/Specs.java    |    2 -
 .../org/gradle/api/tasks/AbstractCopyTask.java     |   83 +-
 .../org/gradle/api/tasks/AntBuilderAware.groovy    |    2 -
 .../org/gradle/api/tasks/ConventionValue.java      |    1 -
 .../src/main/groovy/org/gradle/api/tasks/Copy.java |   86 +-
 .../main/groovy/org/gradle/api/tasks/Delete.java   |    2 -
 .../src/main/groovy/org/gradle/api/tasks/Exec.java |    7 +-
 .../groovy/org/gradle/api/tasks/GradleBuild.java   |   12 +-
 .../main/groovy/org/gradle/api/tasks/JavaExec.java |   12 +-
 .../groovy/org/gradle/api/tasks/SourceTask.java    |    2 +-
 .../org/gradle/api/tasks/StopActionException.java  |    2 -
 .../gradle/api/tasks/StopExecutionException.java   |    2 -
 .../src/main/groovy/org/gradle/api/tasks/Sync.java |   31 +-
 .../groovy/org/gradle/api/tasks/TaskContainer.java |  123 +-
 .../gradle/api/tasks/TaskExecutionException.java   |    2 +-
 .../gradle/api/tasks/TaskValidationException.java  |    4 +-
 .../main/groovy/org/gradle/api/tasks/Upload.java   |  147 -
 .../groovy/org/gradle/api/tasks/ant/AntTarget.java |    7 +-
 .../api/tasks/bundling/AbstractArchiveTask.java    |    3 +-
 .../org/gradle/api/tasks/bundling/Compression.java |    2 -
 .../groovy/org/gradle/api/tasks/bundling/Tar.java  |   42 +-
 .../groovy/org/gradle/api/tasks/bundling/Zip.java  |   89 +-
 .../bundling/internal/Zip64RequiredException.java  |   26 +
 .../tasks/incremental/IncrementalTaskInputs.java   |  122 +
 .../api/tasks/incremental/InputFileDetails.java    |   50 +
 .../gradle/api/tasks/incremental/package-info.java |   20 +
 .../org/gradle/api/tasks/util/PatternSet.java      |    9 +-
 .../main/groovy/org/gradle/cache/CacheBuilder.java |   47 +-
 .../org/gradle/cache/CacheOpenException.java       |    2 +-
 .../groovy/org/gradle/cache/CacheRepository.java   |   59 +-
 .../groovy/org/gradle/cache/CacheValidator.java    |   11 +-
 .../org/gradle/cache/DirectoryCacheBuilder.java    |   49 -
 .../org/gradle/cache/ObjectCacheBuilder.java       |   39 -
 .../groovy/org/gradle/cache/PersistentCache.java   |   30 +-
 .../org/gradle/cache/PersistentIndexedCache.java   |    3 +
 .../cache/PersistentIndexedCacheParameters.java    |   66 +
 .../groovy/org/gradle/cache/PersistentStore.java   |   39 +
 .../gradle/cache/internal/CacheCoordinator.java    |   35 +
 .../org/gradle/cache/internal/CacheDecorator.java  |   25 +
 .../org/gradle/cache/internal/CacheFactory.java    |   15 +-
 .../cache/internal/CacheInitializationAction.java  |   30 +
 .../gradle/cache/internal/CacheScopeMapping.java   |   26 +
 .../gradle/cache/internal/DefaultCacheAccess.java  |  389 +-
 .../gradle/cache/internal/DefaultCacheFactory.java |  274 +-
 .../cache/internal/DefaultCacheRepository.java     |  234 +-
 .../cache/internal/DefaultCacheScopeMapping.java   |   77 +
 .../cache/internal/DefaultFileLockManager.java     |  317 +-
 ...aultMultiProcessSafePersistentIndexedCache.java |   99 +
 .../internal/DefaultPersistentDirectoryCache.java  |  113 +-
 .../internal/DefaultPersistentDirectoryStore.java  |   78 +-
 .../DelegateOnDemandPersistentDirectoryCache.java  |  114 -
 .../org/gradle/cache/internal/FileAccess.java      |    2 +-
 .../groovy/org/gradle/cache/internal/FileLock.java |   14 +-
 .../cache/internal/FileLockCommunicator.java       |   96 +
 .../org/gradle/cache/internal/FileLockManager.java |   20 +-
 .../cache/internal/GracefullyStoppedException.java |   19 +
 .../MultiProcessSafePersistentIndexedCache.java    |   90 +-
 .../gradle/cache/internal/OnDemandFileAccess.java  |    8 +-
 .../internal/ReferencablePersistentCache.java      |    6 +-
 .../gradle/cache/internal/SimpleStateCache.java    |   14 +-
 .../cache/internal/UnitOfWorkParticipant.java      |    7 +-
 .../btree/BTreePersistentIndexedCache.java         |   16 +-
 .../cache/internal/btree/FileBackedBlockStore.java |   49 +-
 .../cacheops/CacheAccessOperationsStack.java       |   68 +
 .../cache/internal/cacheops/CacheOperation.java    |   27 +
 .../internal/cacheops/CacheOperationStack.java     |   74 +
 .../filelock/DefaultLockStateSerializer.java       |   91 +
 .../cache/internal/filelock/LockFileAccess.java    |   92 +
 .../gradle/cache/internal/filelock/LockInfo.java   |   23 +
 .../cache/internal/filelock/LockInfoAccess.java    |   71 +
 .../internal/filelock/LockInfoSerializer.java      |   54 +
 .../cache/internal/filelock/LockOptions.java       |   30 +
 .../internal/filelock/LockOptionsBuilder.java      |   83 +
 .../gradle/cache/internal/filelock/LockState.java  |   36 +
 .../cache/internal/filelock/LockStateAccess.java   |   89 +
 .../internal/filelock/LockStateSerializer.java     |   49 +
 .../filelock/Version1LockStateSerializer.java      |   72 +
 .../DefaultFileLockContentionHandler.java          |  163 +
 .../locklistener/FileLockContentionHandler.java    |   27 +
 .../gradle/configuration/BuildScriptProcessor.java |   45 -
 .../configuration/DefaultBuildConfigurer.java      |   22 +-
 .../configuration/DefaultInitScriptProcessor.java  |   15 +-
 .../configuration/DefaultScriptPluginFactory.java  |  148 +-
 .../configuration/ImplicitTasksConfigurer.java     |   13 +-
 .../org/gradle/configuration/ImportsReader.java    |   34 +-
 .../configuration/LifecycleProjectEvaluator.java   |   51 -
 .../ProjectDependencies2TaskResolver.java          |   47 -
 .../org/gradle/configuration/ProjectEvaluator.java |   23 -
 .../org/gradle/configuration/ScriptPlugin.java     |   19 +-
 .../gradle/configuration/ScriptPluginFactory.java  |    5 +-
 .../project/BuildScriptProcessor.java              |   45 +
 .../project/ConfigureActionsProjectEvaluator.java  |   37 +
 ...DefaultProjectConfigurationActionContainer.java |   45 +
 .../project/DelayedConfigurationActions.java       |   33 +
 .../project/LifecycleProjectEvaluator.java         |   82 +
 .../project/PluginsProjectConfigureActions.java    |   34 +
 .../ProjectConfigurationActionContainer.java       |   40 +
 .../project/ProjectConfigureAction.java            |   30 +
 .../project/ProjectDependencies2TaskResolver.java  |   41 +
 .../configuration/project/ProjectEvaluator.java    |   23 +
 .../TaskModelRealizingConfigurationAction.java     |   31 +
 ...ludedTaskFilteringBuildConfigurationAction.java |    6 +-
 .../gradle/execution/MultipleBuildFailures.java    |    2 +-
 .../gradle/execution/ProjectEvaluatingAction.java  |    2 -
 .../org/gradle/execution/TaskNameResolver.java     |   47 +-
 .../TaskNameResolvingBuildConfigurationAction.java |   18 +-
 .../gradle/execution/TaskPathProjectEvaluator.java |    3 -
 .../gradle/execution/TaskSelectionException.java   |   14 +-
 .../org/gradle/execution/TaskSelectionResult.java  |   23 +
 .../groovy/org/gradle/execution/TaskSelector.java  |   27 +-
 .../commandline/CommandLineTaskConfigurer.java     |   68 +-
 .../commandline/CommandLineTaskParser.java         |    8 +-
 .../commandline/TaskConfigurationException.java    |   44 +
 .../taskgraph/AbstractTaskPlanExecutor.java        |   87 +
 .../taskgraph/DefaultTaskExecutionPlan.java        |  377 +-
 .../taskgraph/DefaultTaskGraphExecuter.java        |   42 +-
 .../taskgraph/DefaultTaskPlanExecutor.java         |   37 +-
 .../execution/taskgraph/ExecutionOptions.java      |   36 -
 .../taskgraph/ParallelTaskPlanExecutor.java        |   84 +-
 .../execution/taskgraph/TaskDependencyGraph.java   |   52 +
 .../taskgraph/TaskDependencyGraphEdge.groovy       |   36 +
 .../execution/taskgraph/TaskExecutionPlan.java     |   17 +-
 .../org/gradle/execution/taskgraph/TaskInfo.java   |  131 +-
 .../taskgraph/TaskPlanExecutorFactory.java         |   26 +-
 .../taskpath/ProjectFinderByTaskPath.java          |    3 -
 .../execution/taskpath/ResolvedTaskPath.java       |    3 -
 .../execution/taskpath/TaskPathResolver.java       |    3 -
 .../org/gradle/groovy/scripts/BasicScript.java     |    8 +-
 .../org/gradle/groovy/scripts/DefaultScript.java   |   44 +-
 .../scripts/DefaultScriptCompilerFactory.java      |    7 +-
 .../org/gradle/groovy/scripts/ScriptAware.java     |    6 +-
 .../groovy/scripts/ScriptCompilerFactory.java      |    2 -
 .../gradle/groovy/scripts/StringScriptSource.java  |    2 +-
 .../org/gradle/groovy/scripts/UriScriptSource.java |    2 +-
 .../internal/AbstractScriptTransformer.java        |   36 -
 .../internal/AsmBackedEmptyScriptGenerator.java    |   10 +-
 .../gradle/groovy/scripts/internal/AstUtils.java   |   91 +
 .../BuildScriptClasspathScriptTransformer.java     |   38 -
 .../scripts/internal/BuildScriptTransformer.java   |   13 +-
 .../internal/ClasspathScriptTransformer.java       |  177 -
 .../internal/DefaultScriptCompilationHandler.java  |    3 -
 .../FileCacheBackedScriptClassCompiler.java        |   41 +-
 .../scripts/internal/FilteredTransformer.java      |   28 +
 .../internal/FilteringStatementTransformer.java    |   43 +
 .../scripts/internal/FixMainScriptTransformer.java |    4 +-
 .../internal/PluginsAndBuildscriptTransformer.java |  127 +
 .../groovy/scripts/internal/ScriptBlock.java       |   37 +
 ...riptBlockToServiceConfigurationTransformer.java |   54 +
 .../scripts/internal/ScriptCompilationHandler.java |    3 -
 .../StatementExtractingScriptTransformer.java      |  145 +
 .../internal/StatementLabelsScriptTransformer.java |    2 +-
 .../scripts/internal/StatementTransformer.java     |   30 +
 .../internal/TaskDefinitionScriptTransformer.java  |    6 +-
 .../gradle/initialization/AbstractProjectSpec.java |    8 +-
 .../org/gradle/initialization/BaseSettings.java    |   81 +-
 .../org/gradle/initialization/BuildAction.java     |   31 +
 .../org/gradle/initialization/BuildController.java |   45 +
 .../initialization/BuildFileProjectSpec.java       |    4 +-
 .../initialization/BuildLayoutParameters.java      |   25 +-
 .../org/gradle/initialization/BuildLoader.java     |    3 +-
 .../gradle/initialization/BuildProgressLogger.java |   65 -
 .../gradle/initialization/BuildSourceBuilder.java  |  155 -
 .../gradle/initialization/ClassLoaderRegistry.java |   16 +-
 .../initialization/DefaultClassLoaderRegistry.java |   38 +-
 .../DefaultCommandLineConverter.java               |   49 +-
 .../initialization/DefaultExceptionAnalyser.java   |   20 +-
 .../initialization/DefaultGradleLauncher.java      |    4 +-
 .../DefaultGradleLauncherFactory.java              |   44 +-
 .../DefaultGradlePropertiesLoader.java             |    3 -
 .../initialization/DefaultProjectDescriptor.java   |   16 +-
 .../DefaultProjectDescriptorRegistry.java          |    5 +-
 .../gradle/initialization/DefaultProjectSpec.java  |    4 +-
 .../gradle/initialization/DefaultSettings.groovy   |   36 -
 .../org/gradle/initialization/DefaultSettings.java |   36 +
 .../initialization/DefaultSettingsFinder.java      |    3 -
 .../initialization/DependencyResolutionLogger.java |    5 +-
 .../initialization/GradleLauncherAction.java       |   43 -
 .../initialization/GradleLauncherFactory.java      |    2 -
 .../initialization/IGradlePropertiesLoader.java    |    3 -
 .../initialization/IProjectDescriptorRegistry.java |   26 -
 .../org/gradle/initialization/ISettingsFinder.java |    3 -
 .../initialization/InstantiatingBuildLoader.java   |   22 +-
 .../gradle/initialization/JdkToolsInitializer.java |   25 +
 .../initialization/LayoutCommandLineConverter.java |   19 +-
 .../initialization/ProjectAccessListener.java      |    2 -
 .../initialization/ProjectDescriptorRegistry.java  |   23 +
 .../ProjectDirectoryProjectSpec.java               |    4 +-
 .../initialization/ProjectPathProjectSpec.java     |   66 +
 .../ProjectPropertySettingBuildLoader.java         |    8 +-
 .../org/gradle/initialization/ProjectSpec.java     |    6 +-
 .../org/gradle/initialization/ProjectSpecs.java    |   36 +
 .../PropertiesLoadingSettingsProcessor.java        |    9 +-
 .../ScriptEvaluatingSettingsProcessor.java         |   27 +-
 .../org/gradle/initialization/SettingsFactory.java |   16 +-
 .../org/gradle/initialization/SettingsHandler.java |   18 +-
 .../gradle/initialization/SettingsProcessor.java   |    7 +-
 .../buildsrc/BuildSourceBuilder.java               |  101 +
 .../buildsrc/BuildSrcBuildListenerFactory.java     |   61 +
 .../buildsrc/BuildSrcUpdateFactory.java            |   61 +
 .../environment/GradleBuildEnvironment.java        |   22 +
 .../exceptions/AbstractMultiCauseException.java    |  111 +
 .../org/gradle/internal/exceptions/Contextual.java |   29 +
 .../exceptions/FailureResolutionAware.java         |   28 +
 .../exceptions/LocationAwareException.java         |  168 +
 .../internal/exceptions/MultiCauseException.java   |   22 +
 .../featurelifecycle/DeprecatedFeatureHandler.java |   26 +
 .../featurelifecycle/DeprecatedFeatureUsage.java   |   77 +
 .../LoggingDeprecatedFeatureHandler.java           |   76 +
 .../ScriptUsageLocationReporter.java               |   91 +
 .../featurelifecycle/UsageLocationReporter.java    |   21 +
 .../internal/graph/CachingDirectedGraphWalker.java |  203 +
 .../org/gradle/internal/graph/DirectedGraph.java   |   26 +
 .../internal/graph/DirectedGraphRenderer.java      |   80 +
 .../graph/DirectedGraphWithEdgeValues.java         |   25 +
 .../org/gradle/internal/graph/GraphAggregator.java |   90 +
 .../gradle/internal/graph/GraphNodeRenderer.java   |   22 +
 .../org/gradle/internal/graph/GraphRenderer.java   |   69 +
 .../internal/progress/BuildProgressFilter.java     |   94 +
 .../internal/progress/BuildProgressLogger.java     |  108 +
 .../gradle/internal/progress/LoggerProvider.java   |   30 +
 .../internal/progress/OperationIdentifier.java     |   40 +
 .../internal/progress/OperationsHierarchy.java     |   92 +
 .../progress/OperationsHierarchyKeeper.java        |   40 +
 .../progress/PercentageProgressFormatter.java      |   40 +
 .../internal/progress/ProgressFormatter.java       |   21 +
 .../internal/progress/ProgressLoggerProvider.java  |   33 +
 .../internal/progress/SimpleProgressFormatter.java |   39 +
 .../scopes/BuildScopeServiceRegistryFactory.java   |   50 +
 .../service/scopes/BuildScopeServices.java         |  299 ++
 .../service/scopes/GlobalScopeServices.java        |  172 +
 .../service/scopes/GradleScopeServices.java        |  120 +
 .../service/scopes/PluginServiceRegistry.java      |   43 +
 .../service/scopes/ProjectScopeServices.java       |  176 +
 .../service/scopes/ServiceRegistryFactory.java     |   31 +
 .../service/scopes/SettingsScopeServices.java      |   55 +
 .../service/scopes/TaskExecutionServices.java      |   97 +
 .../internal/service/scopes/TaskScopeServices.java |   58 +
 .../org/gradle/internal/text/TreeFormatter.java    |  146 +
 .../typeconversion/CharSequenceNotationParser.java |   28 +
 .../ClosureToSpecNotationParser.java               |   36 +
 .../typeconversion/CompositeNotationParser.java    |   46 +
 .../EnumFromCharSequenceNotationParser.java        |   63 +
 .../ErrorHandlingNotationParser.java               |   66 +
 .../typeconversion/FlatteningNotationParser.java   |   50 +
 .../typeconversion/JustReturningParser.java        |   38 +
 .../org/gradle/internal/typeconversion/MapKey.java |   28 +
 .../internal/typeconversion/MapNotationParser.java |  131 +
 .../typeconversion/NormalizedTimeUnit.java         |   42 +
 .../internal/typeconversion/NotationParser.java    |   32 +
 .../typeconversion/NotationParserBuilder.java      |   84 +
 .../internal/typeconversion/TimeUnitsParser.java   |   43 +
 .../typeconversion/TypeConversionException.java    |   26 +
 .../gradle/internal/typeconversion/TypeInfo.java   |   33 +
 .../typeconversion/TypedNotationParser.java        |   47 +
 .../UnsupportedNotationException.java              |   28 +
 .../typeconversion/ValueAwareNotationParser.java   |   23 +
 .../org/gradle/invocation/DefaultGradle.java       |   79 +-
 .../org/gradle/listener/ActionBroadcast.java       |   21 +-
 .../org/gradle/listener/BroadcastDispatch.java     |   42 +-
 .../org/gradle/listener/ListenerBroadcast.java     |   18 +-
 .../listener/ListenerNotificationException.java    |   10 +-
 .../org/gradle/logging/LoggingServiceRegistry.java |    2 +-
 .../groovy/org/gradle/logging/ProgressLogger.java  |    9 +
 .../org/gradle/logging/ProgressLoggerFactory.java  |    4 +
 .../org/gradle/logging/StandardOutputCapture.java  |    3 -
 .../AbstractLineChoppingStyledTextOutput.java      |    2 +-
 .../internal/ConsoleBackedProgressRenderer.java    |   65 +-
 .../logging/internal/DefaultLoggingConfigurer.java |    4 -
 .../logging/internal/DefaultLoggingManager.java    |    7 +-
 .../internal/DefaultProgressLoggerFactory.java     |   38 +-
 .../internal/DefaultStandardOutputRedirector.java  |   10 +-
 .../internal/DefaultStatusBarFormatter.java        |   24 +-
 .../logging/internal/EmbeddedLoggingServices.java  |    3 -
 .../internal/LoggingBackedStyledTextOutput.java    |   79 +-
 .../internal/LoggingCommandLineConverter.java      |   59 +-
 .../gradle/logging/internal/LoggingConfigurer.java |    3 -
 .../gradle/logging/internal/NoOpLoggingSystem.java |    3 -
 .../logging/internal/PrintStreamLoggingSystem.java |   10 +-
 .../logging/internal/ProgressCompleteEvent.java    |    8 +-
 .../org/gradle/logging/internal/ProgressEvent.java |    8 +-
 .../logging/internal/ProgressStartEvent.java       |   14 +-
 .../logging/internal/StatusBarFormatter.java       |   23 -
 .../logging/internal/StdErrLoggingSystem.java      |    3 -
 .../logging/internal/StdOutLoggingSystem.java      |    3 -
 .../internal/StyledTextOutputBackedRenderer.java   |    3 -
 .../internal/logback/LogbackLoggingConfigurer.java |    6 +-
 .../internal/progress/ProgressOperation.java       |   50 +
 .../internal/progress/ProgressOperations.java      |   52 +
 .../groovy/org/gradle/model/ModelFinalizer.java    |   30 +
 .../main/groovy/org/gradle/model/ModelPath.java    |   90 +
 .../main/groovy/org/gradle/model/ModelRule.java    |   40 +
 .../main/groovy/org/gradle/model/ModelRules.java   |   57 +
 .../src/main/groovy/org/gradle/model/Path.java     |   34 +
 .../main/groovy/org/gradle/model/dsl/ModelDsl.java |   26 +
 .../gradle/model/dsl/internal/GroovyModelDsl.java  |   84 +
 .../org/gradle/model/internal/DefaultInputs.java   |   42 +
 .../model/internal/DefaultModelRegistry.java       |  253 ++
 .../groovy/org/gradle/model/internal/Inputs.java   |   25 +
 .../model/internal/ModelCreationListener.java      |   25 +
 .../org/gradle/model/internal/ModelCreator.java    |   25 +
 .../org/gradle/model/internal/ModelMutation.java   |   39 +
 .../org/gradle/model/internal/ModelMutator.java    |   25 +
 .../org/gradle/model/internal/ModelRegistry.java   |   38 +
 .../internal/ModelRegistryBackedModelRules.java    |  129 +
 .../model/internal/rules/ReflectiveRule.java       |  207 +
 .../groovy/org/gradle/plugin/PluginHandler.java    |   31 +
 .../plugin/internal/DefaultPluginHandler.java      |   48 +
 .../internal/NonPluggableTargetPluginHandler.java  |   38 +
 .../plugin/internal/PluginRequestApplicator.java   |   50 +
 .../internal/PluginResolutionApplicator.java       |   39 +
 .../plugin/internal/PluginResolverFactory.java     |   95 +
 .../gradle/plugin/internal/PluginResolvers.java    |   59 +
 .../internal/ClassPathPluginResolution.java        |   46 +
 .../resolve/internal/CompositePluginResolver.java  |   48 +
 .../resolve/internal/DefaultPluginRequest.java     |   78 +
 .../DependencyResolvingClasspathProvider.java      |   46 +
 .../internal/InvalidPluginRequestException.java    |   25 +
 .../resolve/internal/JCenterPluginMapper.java      |  113 +
 .../internal/JCenterRepositoryConfigurer.java      |   26 +
 .../internal/ModuleMappingPluginResolver.java      |   68 +
 .../NotInPluginRegistryPluginResolverCheck.java    |   61 +
 .../internal/PluginRegistryPluginResolver.java     |   56 +
 .../plugin/resolve/internal/PluginRequest.java     |   33 +
 .../resolve/internal/PluginRequestSerializer.java  |   34 +
 .../plugin/resolve/internal/PluginResolution.java  |   31 +
 .../plugin/resolve/internal/PluginResolver.java    |   33 +
 .../resolve/internal/SimplePluginResolution.java   |   34 +
 .../groovy/org/gradle/process/BaseExecSpec.java    |    2 -
 .../main/groovy/org/gradle/process/ExecResult.java |    2 -
 .../main/groovy/org/gradle/process/ExecSpec.java   |    2 -
 .../groovy/org/gradle/process/JavaExecSpec.java    |    2 -
 .../internal/AbstractExecHandleBuilder.java        |    3 -
 .../process/internal/BadExitCodeException.java     |    3 -
 .../gradle/process/internal/DefaultExecAction.java |    8 -
 .../gradle/process/internal/DefaultExecHandle.java |   19 +-
 .../process/internal/DefaultJavaExecAction.java    |    3 -
 .../process/internal/DefaultWorkerProcess.java     |   11 +-
 .../internal/DefaultWorkerProcessFactory.java      |   15 +-
 .../org/gradle/process/internal/ExecAction.java    |    5 +-
 .../gradle/process/internal/ExecActionFactory.java |   21 +
 .../org/gradle/process/internal/ExecException.java |    3 -
 .../org/gradle/process/internal/ExecHandle.java    |    3 -
 .../gradle/process/internal/ExecHandleBuilder.java |    3 -
 .../process/internal/ExecHandleListener.java       |    3 -
 .../gradle/process/internal/ExecHandleRunner.java  |   22 +-
 .../internal/ExecHandleShutdownHookAction.java     |    2 -
 .../gradle/process/internal/ExecHandleState.java   |    3 -
 .../gradle/process/internal/JavaExecAction.java    |    5 +-
 .../process/internal/JavaExecHandleBuilder.java    |    6 +-
 .../org/gradle/process/internal/JvmOptions.java    |    2 +-
 .../process/internal/ProcessBuilderFactory.java    |    2 -
 .../internal/ProcessParentingInitializer.java      |   87 -
 .../gradle/process/internal/ProcessSettings.java   |    3 -
 .../org/gradle/process/internal/WorkerProcess.java |    9 +-
 .../process/internal/WorkerProcessBuilder.java     |   10 +
 .../process/internal/WorkerProcessContext.java     |    2 +-
 ...ionClassesInSystemClassLoaderWorkerFactory.java |    4 +-
 .../process/internal/child/EncodedStream.java      |    2 -
 .../child/ImplementationClassLoaderWorker.java     |    6 +-
 .../IsolatedApplicationClassLoaderWorker.java      |    2 +-
 .../child/SystemApplicationClassLoaderWorker.java  |    1 -
 .../child/WorkerProcessClassPathProvider.java      |   24 +-
 .../shutdown/ShutdownHookActionRegister.java       |   19 -
 .../internal/streams/ExecOutputHandleRunner.java   |    5 +-
 .../process/internal/streams/SafeStreams.java      |    3 -
 .../process/internal/streams/StreamsForwarder.java |    8 +-
 .../process/internal/streams/StreamsHandler.java   |    5 +-
 .../groovy/org/gradle/profile/BuildProfile.java    |  126 +-
 .../org/gradle/profile/CompositeOperation.java     |    4 +
 .../org/gradle/profile/ContinuousOperation.java    |   20 +-
 .../gradle/profile/DependencyResolveProfile.java   |   30 -
 .../org/gradle/profile/EvalutationOperation.java   |   31 -
 .../main/groovy/org/gradle/profile/Operation.java  |   21 +
 .../org/gradle/profile/ProfileEventAdapter.java    |   40 +-
 .../org/gradle/profile/ProfileReportRenderer.java  |   59 +-
 .../groovy/org/gradle/profile/ProjectProfile.java  |   54 +-
 .../groovy/org/gradle/profile/TaskExecution.java   |   23 +-
 .../org/gradle/reporting/HtmlReportRenderer.java   |    4 +-
 .../org/gradle/reporting/ReportRenderer.java       |    4 +-
 .../org/gradle/reporting/TabbedPageRenderer.java   |   12 +-
 .../org/gradle/reporting/TextReportRenderer.java   |    4 +-
 .../testfixtures/internal/GlobalTestServices.java  |   60 -
 .../internal/InMemoryCacheFactory.java             |   48 +-
 .../internal/InMemoryIndexedCache.java             |   70 +
 .../testfixtures/internal/ProjectBuilderImpl.java  |   31 +-
 .../internal/TestBuildScopeServices.java           |   46 +
 .../internal/TestGlobalScopeServices.java          |   67 +
 .../internal/TestTopLevelBuildServiceRegistry.java |   52 -
 .../provider/model/ToolingModelBuilder.java        |   29 +
 .../model/ToolingModelBuilderRegistry.java         |   29 +
 .../provider/model/UnknownModelException.java      |   31 +
 .../DefaultToolingModelBuilderRegistry.java        |   64 +
 .../model/internal/LegacyConsumerInterface.java    |   29 +
 .../tooling/provider/model/package-info.java       |   20 +
 .../src/main/groovy/org/gradle/util/AntUtil.java   |    7 +-
 .../org/gradle/util/AvailablePortFinder.java       |    2 -
 .../gradle/util/BuildCommencedTimeProvider.java    |    4 +-
 .../util/ClassLoaderBackedClasspathSource.java     |   43 -
 .../groovy/org/gradle/util/ClassLoaderFactory.java |   41 -
 .../groovy/org/gradle/util/ClasspathSource.java    |   23 -
 .../main/groovy/org/gradle/util/ClasspathUtil.java |   98 -
 .../src/main/groovy/org/gradle/util/Clock.java     |    3 -
 .../main/groovy/org/gradle/util/ConfigureUtil.java |    3 -
 .../org/gradle/util/DefaultClassLoaderFactory.java |   86 -
 .../main/groovy/org/gradle/util/DeleteOnExit.java  |   52 -
 .../org/gradle/util/DistributionLocator.java       |    4 +-
 .../org/gradle/util/FilteringClassLoader.java      |  212 -
 .../main/groovy/org/gradle/util/GFileUtils.java    |   18 +-
 .../src/main/groovy/org/gradle/util/GUtil.java     |   12 +-
 .../main/groovy/org/gradle/util/GradleVersion.java |  116 +-
 .../src/main/groovy/org/gradle/util/JarUtil.java   |    5 +-
 .../main/groovy/org/gradle/util/JavaMethod.java    |   75 -
 .../org/gradle/util/LineBufferingOutputStream.java |   27 +-
 .../util/LinePerThreadBufferingOutputStream.java   |   10 +-
 .../org/gradle/util/MultiParentClassLoader.java    |  103 -
 .../org/gradle/util/MutableURLClassLoader.java     |   48 -
 .../core/src/main/groovy/org/gradle/util/Path.java |    3 -
 .../groovy/org/gradle/util/ReflectionUtil.groovy   |   46 -
 .../org/gradle/util/SingleMessageLogger.java       |  187 +-
 .../main/groovy/org/gradle/util/StdoutSwapper.java |   39 -
 .../src/main/groovy/org/gradle/util/TextUtil.java  |    7 +
 .../main/groovy/org/gradle/util/VersionNumber.java |   19 +-
 .../main/groovy/org/gradle/util/hash/HashUtil.java |   84 -
 .../groovy/org/gradle/util/hash/HashValue.java     |   90 -
 .../gradle/util/internal/LimitedDescription.java   |    2 -
 .../org/gradle/configuration/default-imports.txt   |   38 -
 .../buildsrc/defaultBuildSourceScript.txt          |    6 +
 .../initialization/defaultBuildSourceScript.txt    |    5 -
 .../resources/org/gradle/reporting/base-style.css  |    2 +-
 .../org/gradle/BuildExceptionReporterTest.groovy   |   91 +-
 .../groovy/org/gradle/StartParameterTest.groovy    |   28 -
 .../org/gradle/TaskExecutionLoggerTest.groovy      |    6 +-
 .../org/gradle/api/AllGradleExceptionsTest.groovy  |    6 +-
 .../org/gradle/api/file/ProjectCopySpecTest.groovy |  104 +
 .../AbstractMultiCauseExceptionTest.groovy         |   97 -
 .../AbstractNamedDomainObjectContainerTest.groovy  |  263 +-
 .../gradle/api/internal/AbstractTaskSpec.groovy    |    4 +-
 .../AsmBackedClassGeneratorGroovyTest.groovy       |  221 +-
 .../api/internal/AsmBackedClassGeneratorTest.java  |   67 +-
 .../internal/CachingDirectedGraphWalkerTest.groovy |  197 -
 .../api/internal/ChainingTransformerTest.java      |   12 +-
 .../internal/CompositeDomainObjectSetTest.groovy   |   29 +-
 .../api/internal/ConventionAwareHelperTest.java    |   15 +-
 .../DefaultDomainObjectCollectionTest.java         |   24 +-
 .../DefaultNamedDomainObjectCollectionTest.groovy  |   57 +
 .../internal/DefaultNamedDomainObjectSetTest.java  |   20 +-
 ...PolymorphicDomainObjectContainerBaseTest.groovy |   42 +
 ...tPolymorphicDomainObjectContainerDslTest.groovy |   88 +-
 ...aultPolymorphicDomainObjectContainerTest.groovy |  101 +-
 .../org/gradle/api/internal/DefaultTaskTest.groovy |  218 +-
 .../api/internal/DocumentationRegistryTest.groovy  |   47 +-
 .../api/internal/ExtensibleDynamicObjectTest.java  |    6 +-
 .../gradle/api/internal/GraphAggregatorTest.groovy |   65 -
 .../api/internal/LocationAwareExceptionTest.groovy |  184 -
 .../org/gradle/api/internal/TestContainer.java     |    6 +-
 .../artifacts/DefaultExcludeRuleContainerTest.java |    3 -
 .../artifacts/ProjectBackedModuleTest.groovy       |    5 +-
 .../AbstractModuleDependencySpec.groovy            |    4 +-
 .../dependencies/AbstractModuleDependencyTest.java |    7 +-
 .../dependencies/DefaultClientModuleTest.java      |    3 -
 .../DefaultDependencyArtifactTest.java             |    6 +-
 .../DefaultExternalModuleDependencyTest.java       |    3 -
 .../DefaultProjectDependencyTest.groovy            |   14 +-
 .../DefaultDependencyHandlerTest.groovy            |   17 +-
 .../dependencies/ModuleFactoryDelegateTest.java    |    9 +-
 .../publish/AbstractPublishArtifactTest.java       |    6 +-
 .../publish/ArchivePublishArtifactTest.groovy      |   90 +-
 .../publish/DefaultPublishArtifactTest.java        |    3 -
 ...meAfterContainerInclusionDeprecationTest.groovy |   11 +-
 .../LatestVersionSemanticComparatorSpec.groovy     |   76 -
 .../CacheBackedFileSnapshotRepositoryTest.groovy   |   65 -
 .../changedetection/CachingHasherTest.java         |  108 -
 .../CompositeUpToDateRuleTest.groovy               |   69 -
 .../DefaultFileSnapshotterTest.groovy              |  330 --
 .../DefaultTaskArtifactStateCacheAccessTest.groovy |   53 -
 .../DefaultTaskArtifactStateRepositoryTest.java    |  606 ---
 ...BroadcastTaskArtifactStateRepositoryTest.groovy |  107 -
 ...hortCircuitTaskArtifactStateRepositoryTest.java |  165 -
 .../DefaultTaskArtifactStateRepositoryTest.groovy  |  695 +++
 ...rtCircuitTaskArtifactStateRepositoryTest.groovy |  112 +
 .../rules/CachingTaskStateChangesTest.groovy       |   84 +
 .../rules/InputFilesStateChangeRuleTest.groovy     |   78 +
 .../rules/OutputFilesStateChangeRuleTest.groovy    |   82 +
 .../rules/SimpleTaskStateChangesTest.groovy        |   56 +
 .../rules/SummaryTaskStateChangesTest.groovy       |   89 +
 .../CacheBackedFileSnapshotRepositoryTest.groovy   |   68 +
 .../state/CachingFileSnapshotterTest.groovy        |  106 +
 .../DefaultFileCollectionSnapshotterTest.groovy    |  352 ++
 .../DefaultFileSnapshotterSerializerTest.groovy    |   38 +
 .../DefaultTaskArtifactStateCacheAccessTest.groovy |   44 +
 .../state/InMemoryTaskArtifactCacheTest.groovy     |  136 +
 .../state/OutputFilesSnapshotSerializerTest.groovy |   41 +
 ...peCoercingMethodArgumentsTransformerTest.groovy |   68 +
 .../internal/file/AbstractFileCollectionTest.java  |   10 +-
 .../internal/file/AbstractFileTreeElementTest.java |    6 +-
 .../internal/file/BaseDirFileResolverSpec.groovy   |    8 +-
 .../internal/file/BaseDirFileResolverTest.groovy   |    6 +-
 .../internal/file/CompositeFileCollectionTest.java |    6 +-
 .../api/internal/file/CompositeFileTreeTest.java   |    6 +-
 .../file/DefaultCompositeFileTreeTest.groovy       |   67 +
 .../internal/file/DefaultFileOperationsTest.groovy |   48 +-
 .../file/DefaultFileTreeElementTest.groovy         |   15 +-
 .../file/DelegatingFileCollectionTest.groovy       |  104 +
 .../file/FileOrUriNotationParserTest.groovy        |    3 +-
 .../LazilyInitializedFileCollectionTest.groovy     |   52 +
 .../file/MaybeCompressedFileResourceTest.groovy    |    3 -
 .../internal/file/archive/TarCopyActionTest.java   |  245 ++
 .../file/archive/TarCopySpecVisitorTest.java       |  250 --
 .../api/internal/file/archive/TarFileTreeTest.java |    7 +-
 .../internal/file/archive/ZipCopyActionTest.groovy |  193 +
 .../file/archive/ZipCopySpecVisitorTest.java       |  245 --
 .../api/internal/file/archive/ZipFileTreeTest.java |    3 +-
 .../file/archive/compression/ArchiversTest.groovy  |    3 -
 .../DefaultConfigurableFileCollectionTest.java     |   12 +-
 .../DefaultConfigurableFileTreeTest.groovy         |   24 +-
 .../file/collections/DirectoryFileTreeTest.java    |    6 +-
 .../internal/file/collections/MapFileTreeTest.java |   11 +-
 .../file/copy/CopyActionExecuterTest.groovy        |   72 +
 .../internal/file/copy/CopyActionImplTest.groovy   |   95 -
 .../file/copy/CopyFileVisitorImplTest.groovy       |  106 +
 .../file/copy/CopySpecActionImplTest.groovy        |   45 +
 ...SpecBackedCopyActionProcessingStreamTest.groovy |   41 +
 .../api/internal/file/copy/CopySpecImplTest.groovy |  350 --
 .../internal/file/copy/CopySpecMatchingTest.groovy |   88 +
 .../internal/file/copy/DefaultCopySpecTest.groovy  |  505 +++
 .../internal/file/copy/DeleteActionImplTest.groovy |    3 -
 ...DuplicateHandlingCopyActionDecoratorTest.groovy |  196 +
 .../internal/file/copy/FileCopyActionImplTest.java |   54 -
 .../api/internal/file/copy/FileCopyActionTest.java |   66 +
 .../file/copy/FileCopySpecVisitorTest.java         |   92 -
 .../api/internal/file/copy/FilterChainTest.java    |    4 +-
 .../file/copy/MappingCopySpecVisitorTest.java      |  400 --
 .../copy/NormalizingCopyActionDecoratorTest.java   |  152 +
 .../file/copy/NormalizingCopySpecVisitorTest.java  |  226 -
 .../file/copy/SyncCopyActionDecoratorTest.groovy   |   57 +
 .../file/copy/SyncCopySpecVisitorTest.java         |  169 -
 .../file/pattern/DefaultPatternMatcherTest.java    |  218 -
 .../file/pattern/FixedPatternStepTest.groovy       |   42 +
 .../file/pattern/FixedStepsPathMatcherTest.groovy  |  112 +
 .../file/pattern/GreedyPathMatcherTest.groovy      |   69 +
 .../file/pattern/NameOnlyPatternMatcherTest.java   |   70 -
 .../file/pattern/PatternMatcherFactoryTest.java    |  195 +-
 .../file/pattern/PatternStepFactoryTest.groovy     |  116 +
 .../file/pattern/PatternStepFactoryTest.java       |   40 -
 .../file/pattern/RegExpPatternStepTest.java        |   66 +-
 .../pattern/WildcardPrefixPatternStepTest.groovy   |   47 +
 .../DefaultClassLoaderCacheTest.groovy             |   80 +
 .../DefaultClassLoaderScopeTest.groovy             |  241 ++
 .../DefaultScriptHandlerFactoryTest.groovy         |   48 +-
 .../initialization/DefaultScriptHandlerTest.groovy |   92 +-
 .../parsers/CharSequenceNotationParserTest.groovy  |   47 -
 .../parsers/ClosureToSpecNotationParserTest.groovy |   40 -
 .../parsers/ErrorHandlingNotationParserTest.groovy |   62 -
 .../notations/parsers/MapNotationParserTest.groovy |  110 -
 .../notations/parsers/TimeUnitsParserTest.groovy   |   54 -
 .../parsers/TypedNotationParserTest.groovy         |   53 -
 .../internal/plugins/DefaultConventionTest.groovy  |    6 +-
 .../DefaultObjectConfigurationActionTest.groovy    |   95 +-
 .../plugins/DefaultPluginContainerTest.java        |  128 +
 .../plugins/DefaultPluginRegistryTest.groovy       |   23 +-
 .../DefaultProjectsPluginContainerTest.java        |  131 -
 .../internal/plugins/ExtensionContainerTest.groovy |    3 -
 .../internal/plugins/ExtensionsStorageTest.groovy  |   76 +
 .../project/DefaultAntBuilderFactoryTest.groovy    |    4 +-
 .../internal/project/DefaultAntBuilderTest.groovy  |    4 +-
 .../project/DefaultIsolatedAntBuilderTest.groovy   |    4 +-
 .../project/DefaultProjectRegistryTest.java        |   23 +-
 .../api/internal/project/DefaultProjectTest.groovy |  413 +-
 .../project/GlobalServicesRegistryTest.java        |  142 -
 .../GradleInternalServiceRegistryTest.groovy       |  102 -
 .../internal/project/NewDefaultProjectTest.groovy  |    7 +-
 .../api/internal/project/ProjectFactoryTest.groovy |  100 +
 .../api/internal/project/ProjectFactoryTest.java   |  208 -
 .../ProjectInternalServiceRegistryTest.java        |  276 --
 .../project/TaskExecutionServicesTest.groovy       |   54 -
 .../project/TaskInternalServiceRegistryTest.java   |   90 -
 .../gradle/api/internal/project/TestPlugin1.groovy |    3 -
 .../gradle/api/internal/project/TestPlugin2.groovy |    3 -
 .../TopLevelBuildServiceRegistryTest.groovy        |  264 --
 .../AnnotationProcessingTaskFactoryTest.java       |  116 +-
 .../project/taskfactory/TaskFactoryTest.groovy     |   25 +-
 .../api/internal/resource/UriResourceTest.groovy   |    5 +-
 .../api/internal/resources/URIBuilderTest.groovy   |    3 -
 .../internal/tasks/DefaultTaskContainerTest.groovy |  144 +-
 .../execution/ExecuteActionsTaskExecuterTest.java  |   79 +-
 .../ExecuteAtMostOnceTaskExecuterTest.groovy       |   22 +-
 .../PostExecutionAnalysisTaskExecuterTest.groovy   |   66 +-
 .../SkipEmptySourceFilesTaskExecuterTest.groovy    |   12 +-
 .../execution/SkipOnlyIfTaskExecuterTest.java      |   11 +-
 .../SkipTaskWithNoActionsExecuterTest.groovy       |   14 +-
 .../execution/SkipUpToDateTaskExecuterTest.groovy  |  110 +
 .../execution/SkipUpToDateTaskExecuterTest.java    |  141 -
 .../execution/ValidatingTaskExecuterTest.groovy    |   16 +-
 .../options/InstanceOptionDescriptorSpec.groovy    |   54 +
 .../options/OptionNotationParserFactorySpec.groovy |   56 +
 .../internal/tasks/options/OptionReaderTest.groovy |  315 ++
 .../tasks/util/DefaultJavaForkOptionsTest.groovy   |    9 +-
 .../api/internal/xml/SimpleXmlWriterSpec.groovy    |   17 +-
 .../api/plugins/TestPluginConvention1.groovy       |    3 -
 .../api/plugins/TestPluginConvention2.groovy       |    3 -
 .../gradle/api/tasks/AbstractCopyTaskTest.groovy   |   80 +
 .../org/gradle/api/tasks/AbstractCopyTaskTest.java |   99 -
 .../groovy/org/gradle/api/tasks/CopyTest.groovy    |   67 +-
 .../groovy/org/gradle/api/tasks/DeleteTest.java    |   33 +-
 .../org/gradle/api/tasks/DirectoryTest.groovy      |    8 +-
 .../org/gradle/api/tasks/GradleBuildTest.groovy    |   63 +-
 .../groovy/org/gradle/api/tasks/SyncTest.groovy    |   27 +
 .../groovy/org/gradle/api/tasks/UploadTest.groovy  |   32 -
 .../org/gradle/api/tasks/ant/AntTargetTest.java    |   10 +-
 .../org/gradle/api/tasks/bundling/TarTest.groovy   |   11 +-
 .../org/gradle/api/tasks/bundling/ZipTest.groovy   |    7 +-
 .../tasks/util/AbstractTestForPatternSet.groovy    |   12 +-
 .../internal/AbstractFileLockManagerTest.groovy    |  532 +++
 .../cache/internal/DefaultCacheAccessTest.groovy   |  609 +--
 .../cache/internal/DefaultCacheFactoryTest.groovy  |  309 +-
 .../internal/DefaultCacheRepositoryTest.groovy     |  102 +-
 .../internal/DefaultCacheScopeMappingTest.groovy   |  142 +
 .../internal/DefaultFileLockManagerTest.groovy     |  474 --
 ...eLockManagerWithCrossVersionProtocolTest.groovy |   54 +
 ...efaultFileLockManagerWithNewProtocolTest.groovy |  281 ++
 .../DefaultPersistentDirectoryCacheSpec.groovy     |    3 +-
 .../DefaultPersistentDirectoryCacheTest.java       |   49 +-
 .../DefaultPersistentDirectoryStoreTest.groovy     |   10 +-
 ...gateOnDemandPersistentDirectoryCacheSpec.groovy |  103 -
 .../cache/internal/FileLockCommunicatorTest.groovy |   99 +
 ...ltiProcessSafePersistentIndexedCacheTest.groovy |   14 +-
 .../cache/internal/OnDemandFileAccessTest.groovy   |    8 +-
 .../cacheops/CacheAccessOperationsStackTest.groovy |   43 +
 .../cacheops/CacheOperationStackTest.groovy        |  117 +
 .../filelock/LockOptionsBuilderTest.groovy         |   35 +
 .../DefaultFileLockContentionHandlerTest.groovy    |  185 +
 .../configuration/BuildScriptProcessorTest.java    |   59 -
 .../DefaultBuildConfigurerTest.groovy              |   21 +-
 .../DefaultInitScriptProcessorTest.groovy          |   51 +
 .../DefaultInitScriptProcessorTest.java            |   51 -
 .../DefaultScriptPluginFactoryTest.groovy          |  119 +
 .../DefaultScriptPluginFactoryTest.java            |  194 -
 .../ImplicitTasksConfigurerTest.groovy             |   33 -
 .../gradle/configuration/ImportsReaderTest.groovy  |   33 +-
 .../LifecycleProjectEvaluatorTest.groovy           |   90 -
 .../ProjectDependencies2TaskResolverTest.groovy    |   40 -
 .../project/BuildScriptProcessorTest.groovy        |   51 +
 .../ConfigureActionsProjectEvaluatorTest.groovy    |   59 +
 ...tProjectConfigurationActionContainerTest.groovy |   52 +
 .../project/DelayedConfigurationActionsTest.groovy |   70 +
 .../project/LifecycleProjectEvaluatorTest.groovy   |  152 +
 .../PluginsProjectConfigureActionsTest.groovy      |   52 +
 .../ProjectDependencies2TaskResolverTest.groovy    |   37 +
 ...askFilteringBuildConfigurationActionTest.groovy |    7 +-
 .../execution/ProjectEvaluatingActionTest.groovy   |    3 -
 .../gradle/execution/TaskNameResolverTest.groovy   |   50 +-
 ...kNameResolvingBuildConfigurationActionTest.java |   61 +-
 .../execution/TaskPathProjectEvaluatorTest.groovy  |    3 -
 .../CommandLineTaskConfigurerSpec.groovy           |   86 +-
 .../commandline/CommandLineTaskParserSpec.groovy   |   34 +-
 .../taskgraph/DefaultTaskExecutionPlanTest.groovy  |  759 +++-
 .../taskgraph/DefaultTaskGraphExecuterTest.java    |   51 +-
 .../taskgraph/DefaultTaskPlanExecutorTest.groovy   |   58 +
 .../taskgraph/ParallelTaskExecutionPlanTest.groovy |   26 -
 .../taskgraph/TaskDependencyGraphTest.groovy       |   78 +
 .../taskgraph/TaskPlanExecutorFactoryTest.groovy   |    8 +-
 .../taskpath/ProjectFinderByTaskPathTest.groovy    |   11 +-
 .../execution/taskpath/ResolvedTaskPathTest.groovy |    3 -
 .../execution/taskpath/TaskPathResolverTest.groovy |    3 -
 .../gradle/groovy/scripts/DefaultScriptTest.groovy |   18 +-
 .../org/gradle/groovy/scripts/EmptyScript.java     |    3 -
 .../DefaultScriptCompilationHandlerTest.java       |    3 -
 .../FileCacheBackedScriptClassCompilerTest.groovy  |   42 +-
 .../initialization/BuildFileProjectSpecTest.java   |    6 +-
 .../BuildLayoutParametersTest.groovy               |   51 +
 .../initialization/BuildProgressLoggerTest.groovy  |   80 -
 .../initialization/BuildSourceBuilderTest.groovy   |  171 -
 .../DefaultCommandLineConverterTest.java           |    3 -
 .../DefaultExceptionAnalyserTest.java              |   61 +-
 .../DefaultGradleLauncherFactoryTest.groovy        |    8 +-
 .../initialization/DefaultGradleLauncherTest.java  |   32 +-
 .../DefaultGradlePropertiesLoaderTest.java         |    3 -
 .../DefaultProjectDescriptorRegistryTest.java      |   18 +-
 .../DefaultProjectDescriptorTest.java              |   41 +-
 .../initialization/DefaultSettingsTest.groovy      |  107 +-
 .../DependencyResolutionLoggerTest.groovy          |    4 +-
 .../InstantiatingBuildLoaderTest.groovy            |  112 +-
 .../LayoutCommandLineConverterTest.groovy          |   46 +-
 .../ProjectDirectoryProjectSpecTest.java           |    6 +-
 .../initialization/ProjectPathProjectSpecTest.java |  117 +
 .../ProjectPropertySettingBuildLoaderTest.groovy   |   17 +-
 .../gradle/initialization/ProjectSpecsTest.groovy  |   79 +
 .../PropertiesLoadingSettingsProcessorTest.java    |    9 +-
 .../ScriptEvaluatingSettingsProcessorTest.groovy   |  101 -
 .../initialization/SettingsFactoryTest.groovy      |   76 +
 .../gradle/initialization/SettingsFactoryTest.java |   70 -
 .../initialization/SettingsHandlerTest.groovy      |   64 +
 .../gradle/initialization/SettingsHandlerTest.java |  132 -
 .../buildsrc/BuildSourceBuilderTest.groovy         |   58 +
 .../BuildSrcBuildListenerFactoryTest.groovy        |   62 +
 .../buildsrc/BuildSrcUpdateFactoryTest.groovy      |   67 +
 .../AbstractMultiCauseExceptionTest.groovy         |  125 +
 .../exceptions/LocationAwareExceptionTest.groovy   |  184 +
 .../DeprecatedFeatureUsageTest.groovy              |   38 +
 .../LoggingDeprecatedFeatureHandlerTest.groovy     |   57 +
 .../ScriptUsageLocationReporterTest.groovy         |  126 +
 .../graph/CachingDirectedGraphWalkerTest.groovy    |  266 ++
 .../graph/DirectedGraphRendererTest.groovy         |  140 +
 .../internal/graph/GraphAggregatorTest.groovy      |   65 +
 .../progress/BuildProgressFilterTest.groovy        |   86 +
 .../progress/BuildProgressLoggerTest.groovy        |  175 +
 .../progress/OperationsHierarchyKeeperTest.groovy  |   64 +
 .../progress/OperationsHierarchyTest.groovy        |  136 +
 .../PercentageProgressFormatterTest.groovy         |   39 +
 .../progress/SimpleProgressFormatterTest.groovy    |   45 +
 .../service/scopes/BuildScopeServicesTest.groovy   |  305 ++
 .../service/scopes/GlobalScopeServicesTest.java    |  172 +
 .../service/scopes/GradleScopeServicesTest.groovy  |  153 +
 .../service/scopes/ProjectScopeServicesTest.groovy |  219 +
 .../scopes/SettingsScopeServicesTest.groovy        |   75 +
 .../scopes/TaskExecutionServicesTest.groovy        |   58 +
 .../service/scopes/TaskScopeServicesTest.java      |   91 +
 .../gradle/internal/text/TreeFormatterTest.groovy  |  144 +
 .../CharSequenceNotationParserTest.groovy          |   47 +
 .../ClosureToSpecNotationParserTest.groovy         |   36 +
 .../EnumFromCharSequenceNotationParserSpec.groovy  |   46 +
 .../ErrorHandlingNotationParserTest.groovy         |   60 +
 .../typeconversion/MapNotationParserTest.groovy    |  109 +
 .../NotationParserBuilderSpec.groovy               |   47 +
 .../typeconversion/TimeUnitsParserTest.groovy      |   51 +
 .../typeconversion/TypedNotationParserTest.groovy  |   49 +
 .../org/gradle/invocation/DefaultGradleTest.java   |   52 +-
 .../org/gradle/listener/ActionBroadcastTest.groovy |   53 +-
 .../org/gradle/listener/ListenerBroadcastTest.java |   46 +-
 .../ConsoleBackedProgressRendererTest.groovy       |   21 +-
 .../internal/DefaultLoggingManagerTest.java        |    6 +-
 .../DefaultProgressLoggerFactoryTest.groovy        |   10 +
 .../internal/DefaultStatusBarFormatterTest.groovy  |   33 +-
 .../LoggingBackedStyledTextOutputTest.groovy       |   14 +-
 .../LoggingCommandLineConverterTest.groovy         |   16 +-
 .../logging/internal/OutputSpecification.groovy    |   16 +-
 .../progress/ProgressOperationsTest.groovy         |  116 +
 .../model/dsl/internal/GroovyModelDslTest.groovy   |  101 +
 .../ModelRegistryBackedModelRulesTest.groovy       |  102 +
 .../plugin/bintray/JCenterPluginMapperSpec.groovy  |   80 +
 .../internal/DefaultPluginHandlerTest.groovy       |   62 +
 .../process/internal/DefaultExecHandleSpec.groovy  |    3 -
 .../internal/DefaultWorkerProcessTest.groovy       |    5 +-
 .../internal/JavaExecHandleBuilderTest.groovy      |   20 +-
 .../gradle/process/internal/JvmOptionsTest.groovy  |   30 +-
 .../child/ImplementationClassLoaderWorkerTest.java |    2 +-
 .../internal/child/SerializableMockHelper.groovy   |    8 +-
 .../WorkerProcessClassPathProviderTest.groovy      |    6 +-
 .../org/gradle/profile/BuildProfileTest.groovy     |   80 +-
 .../profile/ProfileReportRendererTest.groovy       |  227 +
 .../org/gradle/profile/ProjectProfileTest.groovy   |   34 +
 .../org/gradle/profile/TaskExecutionTest.groovy    |   43 +
 .../gradle/reporting/HtmlReportRendererTest.groovy |    2 +-
 .../DefaultToolingModelBuilderRegistryTest.groovy  |   76 +
 .../util/DefaultClassLoaderFactoryTest.groovy      |   91 -
 .../util/DefaultClassLoaderFactoryTestHelper.java  |   35 -
 .../gradle/util/FilteringClassLoaderTest.groovy    |  182 -
 .../groovy/org/gradle/util/GFileUtilsTest.groovy   |    3 -
 .../test/groovy/org/gradle/util/GUtilTest.groovy   |   19 +
 .../org/gradle/util/GradleVersionTest.groovy       |  124 +-
 .../groovy/org/gradle/util/JavaMethodTest.java     |   68 -
 .../gradle/util/LineBufferingOutputStreamTest.java |  112 +-
 .../LinePerThreadBufferingOutputStreamTest.groovy  |    4 +-
 .../groovy/org/gradle/util/MatchersTest.groovy     |    3 -
 .../gradle/util/MultiParentClassLoaderTest.groovy  |  152 -
 .../test/groovy/org/gradle/util/PathTest.groovy    |    6 +-
 .../org/gradle/util/SingleMessageLoggerTest.groovy |   49 +-
 .../test/groovy/org/gradle/util/StageTest.groovy   |    3 -
 .../org/gradle/util/StdoutSwapperTest.groovy       |   44 -
 .../groovy/org/gradle/util/TextUtilTest.groovy     |   26 +-
 .../org/gradle/util/VersionNumberTest.groovy       |   19 +-
 .../org/gradle/util/hash/HashValueTest.groovy      |   64 -
 .../util/internal/ArgumentsSplitterTest.groovy     |    4 +-
 .../util/internal/LimitedDescriptionTest.groovy    |    3 -
 .../api/internal/file/archive/nomodeinfos.zip      |  Bin 0 -> 276 bytes
 .../api/internal/file/archive/permissions.zip      |  Bin 0 -> 300 bytes
 .../gradle/api/RecordingAntBuildListener.groovy    |   60 +
 .../gradle/api/file/FileCollectionMatchers.java    |   71 +
 .../org/gradle/api/internal/file/TestFiles.java    |   18 +-
 .../internal/file/copy/CopyActionExecuterUtil.java |   40 +
 .../api/tasks/AbstractConventionTaskTest.java      |   12 +-
 .../api/tasks/AbstractCopyTaskContractTest.groovy  |  127 +
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |   25 +-
 .../org/gradle/api/tasks/AbstractTaskTest.java     |   22 +-
 .../gradle/api/tasks/TaskDependencyMatchers.java   |   98 +
 .../tasks/bundling/AbstractArchiveTaskTest.groovy  |   11 +-
 .../DefaultFileLockManagerTestHelper.groovy        |    9 +-
 .../NoOpFileLockContentionHandler.java             |   31 +
 .../org/gradle/util/ConcurrentSpecification.groovy |    3 -
 .../groovy/org/gradle/util/HelperUtil.groovy       |  143 -
 .../groovy/org/gradle/util/Matchers.java           |  429 --
 .../groovy/org/gradle/util/TestTask.groovy         |    3 -
 .../groovy/org/gradle/util/TestUtil.groovy         |  146 +
 subprojects/cpp/cpp.gradle                         |   14 +-
 ...alStudioFileCustomizationIntegrationTest.groovy |  207 +
 .../VisualStudioMultiProjectIntegrationTest.groovy |  316 ++
 ...VisualStudioSingleProjectIntegrationTest.groovy |  840 ++++
 .../VisualStudioPluginIntegrationTest.groovy       |   26 +
 .../CppAutoTestedSamplesIntegrationTest.groovy     |   31 +
 .../plugins/AssemblerPluginIntegrationTest.groovy  |   22 +
 .../c/plugins/CPluginIntegrationTest.groovy        |   22 +
 ...tLanguageIncrementalBuildIntegrationTest.groovy |  513 +++
 ...anguageIncrementalCompileIntegrationTest.groovy |  529 +++
 .../cpp/AbstractLanguageIntegrationTest.groovy     |  198 +
 ...yLanguageIncrementalBuildIntegrationTest.groovy |  136 +
 .../cpp/AssemblyLanguageIntegrationTest.groovy     |  110 +
 .../cpp/BinaryBuildTypesIntegrationTest.groovy     |  206 +
 .../cpp/BinaryFlavorsIntegrationTest.groovy        |  227 +
 .../cpp/BinaryPlatformIntegrationTest.groovy       |  349 ++
 ...llingMixedCAndCppLanguageIntegrationTest.groovy |   25 +
 ...CLanguageIncrementalBuildIntegrationTest.groovy |   27 +
 ...anguageIncrementalCompileIntegrationTest.groovy |   26 +
 .../language/cpp/CLanguageIntegrationTest.groovy   |  201 +
 .../language/cpp/CUnitIntegrationTest.groovy       |  387 ++
 .../language/cpp/CppBinariesIntegrationTest.groovy |  371 ++
 .../cpp/CppCallingCLanguageIntegrationTest.groovy  |   25 +
 ...pLanguageIncrementalBuildIntegrationTest.groovy |   25 +
 ...anguageIncrementalCompileIntegrationTest.groovy |   26 +
 .../language/cpp/CppLanguageIntegrationTest.groovy |  114 +
 .../language/cpp/CppPluginGoodBehaviourTest.groovy |   25 +
 .../cpp/DuplicateBaseNamesIntegrationTest.groovy   |  118 +
 ...GccToolChainCustomisationIntegrationTest.groovy |  193 +
 .../GccToolChainDiscoveryIntegrationTest.groovy    |  124 +
 .../cpp/GeneratedSourcesIntegrationTest.groovy     |  383 ++
 .../LibraryApiDependenciesIntegrationTest.groovy   |  237 +
 .../cpp/LibraryBinariesIntegrationTest.groovy      |  298 ++
 .../cpp/LibraryDependenciesIntegrationTest.groovy  |  350 ++
 .../cpp/MixedLanguageIntegrationTest.groovy        |  168 +
 .../cpp/MultipleToolChainIntegrationTest.groovy    |  115 +
 .../cpp/NativeBinariesPluginIntegrationTest.groovy |  226 +
 .../cpp/NativeSamplesIntegrationTest.groovy        |  409 ++
 .../cpp/PrebuiltLibrariesIntegrationTest.groovy    |  349 ++
 .../cpp/SharedLibrarySoNameIntegrationTest.groovy  |   80 +
 .../SourceSetDependenciesIntegrationTest.groovy    |  153 +
 .../cpp/ToolChainDiscoveryIntegrationTest.groovy   |   89 +
 ...ResourcesIncrementalBuildIntegrationTest.groovy |  162 +
 .../cpp/WindowsResourcesIntegrationTest.groovy     |  143 +
 ...ndowsResourcesUnsupportedIntegrationTest.groovy |   55 +
 ...bstractInstalledToolChainIntegrationSpec.groovy |   81 +
 .../cpp/fixtures/SingleToolChainTestRunner.java    |   77 +
 .../cpp/plugins/CppPluginIntegrationTest.groovy    |   22 +
 .../MixedObjectiveCIntegrationTest.groovy          |   33 +
 ...CLanguageIncrementalBuildIntegrationTest.groovy |   86 +
 ...anguageIncrementalCompileIntegrationTest.groovy |  120 +
 .../ObjectiveCLanguageIntegrationTest.groovy       |   32 +
 .../ObjectiveCUnsupportedIntegrationTest.groovy    |   53 +
 .../plugins/ObjectiveCPluginIntegrationTest.groovy |   26 +
 ...pLanguageIncrementalBuildIntegrationTest.groovy |   31 +
 ...anguageIncrementalCompileIntegrationTest.groovy |   31 +
 .../ObjectiveCppLanguageIntegrationTest.groovy     |   32 +
 .../ObjectiveCppUnsupportedIntegrationTest.groovy  |   54 +
 .../ObjectiveCppPluginIntegrationTest.groovy       |   26 +
 .../WindowsResourcesPluginIntegrationTest.groovy   |   26 +
 .../NativeBinariesPluginIntegrationTest.groovy     |   26 +
 .../plugins/CUnitPluginIntegrationTest.groovy      |   22 +
 .../cpp/AbstractBinariesIntegrationSpec.groovy     |   33 -
 .../org/gradle/plugins/cpp/AvailableCompilers.java |  171 -
 .../cpp/CppExePluginGoodBehaviourTest.groovy       |   25 -
 .../plugins/cpp/CppIntegrationTestRunner.java      |   96 -
 .../cpp/CppLibPluginGoodBehaviourTest.groovy       |   25 -
 .../plugins/cpp/CppPluginIntegrationTest.groovy    |  220 -
 .../plugins/cpp/CppSamplesIntegrationTest.groovy   |   85 -
 .../libs/cunit/2.1-2/include/CUnit/Automated.h     |   90 +
 .../shared/libs/cunit/2.1-2/include/CUnit/Basic.h  |  113 +
 .../libs/cunit/2.1-2/include/CUnit/CUError.h       |  199 +
 .../shared/libs/cunit/2.1-2/include/CUnit/CUnit.h  |  383 ++
 .../libs/cunit/2.1-2/include/CUnit/CUnit_intl.h    |   62 +
 .../libs/cunit/2.1-2/include/CUnit/Console.h       |   60 +
 .../shared/libs/cunit/2.1-2/include/CUnit/MyMem.h  |  104 +
 .../shared/libs/cunit/2.1-2/include/CUnit/TestDB.h |  914 ++++
 .../libs/cunit/2.1-2/include/CUnit/TestRun.h       |  444 ++
 .../shared/libs/cunit/2.1-2/include/CUnit/Util.h   |  158 +
 .../shared/libs/cunit/2.1-2/lib/cygwin/cunit.lib   |  Bin 0 -> 81868 bytes
 .../shared/libs/cunit/2.1-2/lib/linux/libcunit.a   |  Bin 0 -> 117622 bytes
 .../shared/libs/cunit/2.1-2/lib/mingw/cunit.lib    |  Bin 0 -> 75800 bytes
 .../shared/libs/cunit/2.1-2/lib/osx/libcunit.a     |  Bin 0 -> 85768 bytes
 .../shared/libs/cunit/2.1-2/lib/vs2010/cunit.lib   |  Bin 0 -> 117792 bytes
 .../shared/libs/cunit/2.1-2/lib/vs2013/cunit.lib   |  Bin 0 -> 116230 bytes
 .../groovy/org/gradle/ide/cdt/CdtIdePlugin.groovy  |   79 +
 .../gradle/ide/cdt/model/CprojectDescriptor.groovy |  117 +
 .../gradle/ide/cdt/model/CprojectSettings.groovy   |  103 +
 .../gradle/ide/cdt/model/ProjectDescriptor.groovy  |   48 +
 .../gradle/ide/cdt/model/ProjectSettings.groovy    |   43 +
 .../ide/cdt/tasks/GenerateMetadataFileTask.groovy  |   53 +
 .../org/gradle/ide/visualstudio/ConfigFile.java    |   37 +
 .../gradle/ide/visualstudio/TextConfigFile.java    |   32 +
 .../org/gradle/ide/visualstudio/TextProvider.java  |   40 +
 .../ide/visualstudio/VisualStudioExtension.java    |   36 +
 .../ide/visualstudio/VisualStudioProject.java      |   62 +
 .../ide/visualstudio/VisualStudioSolution.java     |   64 +
 .../org/gradle/ide/visualstudio/XmlConfigFile.java |   33 +
 .../internal/DefaultVisualStudioExtension.java     |   52 +
 .../internal/DefaultVisualStudioProject.groovy     |  154 +
 .../internal/DefaultVisualStudioSolution.groovy    |  114 +
 ...ecutableVisualStudioProjectConfiguration.groovy |   46 +
 .../VisualStudioProjectConfiguration.groovy        |  109 +
 .../internal/VisualStudioProjectMapper.java        |   90 +
 .../internal/VisualStudioProjectRegistry.java      |   65 +
 .../internal/VisualStudioProjectResolver.java      |   44 +
 .../internal/VisualStudioSolutionRegistry.java     |   43 +
 .../internal/rules/CreateVisualStudioModel.java    |   39 +
 .../internal/rules/CreateVisualStudioTasks.java    |   90 +
 .../org/gradle/ide/visualstudio/package-info.java  |   20 +
 .../visualstudio/plugins/VisualStudioPlugin.groovy |   73 +
 .../tasks/GenerateFiltersFileTask.groovy           |   65 +
 .../tasks/GenerateProjectFileTask.groovy           |  112 +
 .../tasks/GenerateSolutionFileTask.groovy          |   71 +
 .../internal/AbsoluteFileNameTransformer.java      |   26 +
 .../internal/RelativeFileNameTransformer.java      |   96 +
 .../tasks/internal/VisualStudioFiltersFile.groovy  |   58 +
 .../tasks/internal/VisualStudioProjectFile.groovy  |   98 +
 .../tasks/internal/VisualStudioSolutionFile.groovy |  114 +
 .../org/gradle/language/DependentSourceSet.java    |   57 +
 .../gradle/language/HeaderExportingSourceSet.java  |   45 +
 .../language/assembler/AssemblerSourceSet.java     |   42 +
 .../internal/DefaultAssemblerSourceSet.java        |   29 +
 .../gradle/language/assembler/package-info.java    |   20 +
 .../assembler/plugins/AssemblerLangPlugin.groovy   |   62 +
 .../groovy/org/gradle/language/c/CSourceSet.java   |   49 +
 .../language/c/internal/DefaultCSourceSet.java     |   32 +
 .../groovy/org/gradle/language/c/package-info.java |   20 +
 .../gradle/language/c/plugins/CLangPlugin.groovy   |   63 +
 .../org/gradle/language/cpp/CppSourceSet.java      |   49 +
 .../language/cpp/internal/DefaultCppSourceSet.java |   29 +
 .../org/gradle/language/cpp/package-info.java      |   20 +
 .../language/cpp/plugins/CppLangPlugin.groovy      |   62 +
 .../AbstractHeaderExportingDependentSourceSet.java |   64 +
 .../internal/AbstractHeaderExportingSourceSet.java |   53 +
 .../ConfigurationBasedNativeDependencySet.groovy   |   90 +
 .../language/objectivec/ObjectiveCSourceSet.java   |   52 +
 .../internal/DefaultObjectiveCSourceSet.java       |   32 +
 .../gradle/language/objectivec/package-info.java   |   20 +
 .../objectivec/plugins/ObjectiveCLangPlugin.groovy |   62 +
 .../objectivecpp/ObjectiveCppSourceSet.java        |   51 +
 .../internal/DefaultObjectiveCppSourceSet.java     |   32 +
 .../gradle/language/objectivecpp/package-info.java |   20 +
 .../plugins/ObjectiveCppLangPlugin.groovy          |   62 +
 .../groovy/org/gradle/language/package-info.java   |   20 +
 .../org/gradle/language/rc/WindowsResourceSet.java |   48 +
 .../rc/internal/DefaultWindowsResourceSet.java     |   28 +
 .../org/gradle/language/rc/package-info.java       |   20 +
 .../rc/plugins/WindowsResourceScriptPlugin.groovy  |   63 +
 .../org/gradle/nativebinaries/BuildType.java       |   30 +
 .../gradle/nativebinaries/BuildTypeContainer.java  |   27 +
 .../org/gradle/nativebinaries/Executable.java      |   26 +
 .../gradle/nativebinaries/ExecutableBinary.java    |   36 +
 .../gradle/nativebinaries/ExecutableContainer.java |   27 +
 .../groovy/org/gradle/nativebinaries/Flavor.java   |   31 +
 .../org/gradle/nativebinaries/FlavorContainer.java |   30 +
 .../groovy/org/gradle/nativebinaries/Library.java  |   39 +
 .../org/gradle/nativebinaries/LibraryBinary.java   |   26 +
 .../gradle/nativebinaries/LibraryContainer.java    |   27 +
 .../org/gradle/nativebinaries/NativeBinary.java    |   42 +
 .../gradle/nativebinaries/NativeBinaryTasks.java   |   43 +
 .../gradle/nativebinaries/NativeDependencySet.java |   40 +
 .../nativebinaries/NativeLibraryRequirement.java   |   40 +
 .../gradle/nativebinaries/PrebuiltLibraries.java   |   29 +
 .../org/gradle/nativebinaries/PrebuiltLibrary.java |   38 +
 .../gradle/nativebinaries/ProjectNativeBinary.java |  106 +
 .../nativebinaries/ProjectNativeComponent.java     |   66 +
 .../org/gradle/nativebinaries/Repositories.java    |   28 +
 .../gradle/nativebinaries/SharedLibraryBinary.java |   47 +
 .../gradle/nativebinaries/StaticLibraryBinary.java |   40 +
 .../nativebinaries/TargetedNativeComponent.java    |   42 +
 .../groovy/org/gradle/nativebinaries/Tool.java     |   39 +
 .../internal/AbstractBinaryToolSpec.java           |   59 +
 .../internal/AbstractProjectLibraryBinary.java     |  104 +
 .../internal/AbstractProjectNativeBinary.java      |  161 +
 .../internal/AbstractProjectNativeComponent.java   |   73 +
 .../AbstractTargetedProjectNativeComponent.java    |   82 +
 .../nativebinaries/internal/BinaryToolSpec.java    |   39 +
 .../nativebinaries/internal/DefaultBuildType.java  |   39 +
 .../internal/DefaultBuildTypeContainer.java        |   33 +
 .../nativebinaries/internal/DefaultExecutable.java |   28 +
 .../internal/DefaultExecutableContainer.java       |   38 +
 .../nativebinaries/internal/DefaultFlavor.java     |   41 +
 .../internal/DefaultFlavorContainer.java           |   33 +
 .../nativebinaries/internal/DefaultLibrary.java    |   42 +
 .../internal/DefaultLibraryContainer.java          |   38 +
 .../nativebinaries/internal/DefaultLinkerSpec.java |   68 +
 .../internal/DefaultNativeBinaryTasks.java         |   56 +
 .../internal/DefaultStaticLibraryArchiverSpec.java |   45 +
 .../nativebinaries/internal/DefaultTool.java       |   37 +
 .../internal/LibraryBinaryInternal.java            |   29 +
 .../gradle/nativebinaries/internal/LinkerSpec.java |   44 +
 .../internal/NativeBinaryServices.java             |   37 +
 .../internal/NativeProjectComponentIdentifier.java |   64 +
 .../internal/ProjectExecutableBinary.java          |   49 +
 .../internal/ProjectNativeBinaryInternal.java      |   36 +
 .../internal/ProjectNativeComponentInternal.java   |   24 +
 .../internal/ProjectNativeLibraryRequirement.java  |   48 +
 .../internal/ProjectSharedLibraryBinary.java       |  120 +
 .../internal/ProjectStaticLibraryBinary.java       |   86 +
 .../internal/SharedLibraryLinkerSpec.java          |   23 +
 .../internal/SourceSetNotationParser.java          |   73 +
 .../internal/StaticLibraryArchiverSpec.java        |   31 +
 .../internal/StaticLibraryBinaryInternal.java      |   26 +
 .../internal/TargetedNativeComponentInternal.java  |   29 +
 .../configure/ApplySourceSetConventions.java       |   50 +
 .../configure/ConfigureGeneratedSourceSets.java    |   54 +
 .../configure/CreateDefaultBuildTypes.java         |   30 +
 .../internal/configure/CreateDefaultFlavors.java   |   32 +
 .../internal/configure/CreateDefaultPlatform.java  |   29 +
 .../internal/configure/CreateNativeBinaries.java   |   79 +
 .../configure/DefaultNativeBinariesFactory.java    |   60 +
 .../internal/configure/NativeBinariesFactory.java  |   27 +
 .../configure/ProjectNativeBinaryInitializer.java  |   52 +
 .../ProjectNativeComponentInitializer.java         |   90 +
 .../internal/configure/RepositoriesFactory.java    |   69 +
 .../prebuilt/AbstractPrebuiltLibraryBinary.java    |  107 +
 .../prebuilt/DefaultPrebuiltLibraries.java         |   59 +
 .../internal/prebuilt/DefaultPrebuiltLibrary.java  |   50 +
 .../DefaultPrebuiltSharedLibraryBinary.java        |   66 +
 .../DefaultPrebuiltStaticLibraryBinary.java        |   55 +
 .../prebuilt/PrebuiltLibraryBinaryLocator.java     |   58 +
 .../prebuilt/PrebuiltLibraryInitializer.java       |   77 +
 .../prebuilt/PrebuiltLibraryResolveException.java  |   27 +
 .../ApiRequirementNativeDependencyResolver.java    |  104 +
 .../resolve/ChainedLibraryBinaryLocator.java       |   51 +
 .../internal/resolve/DefaultLibraryResolver.java   |  107 +
 .../resolve/DefaultNativeDependencySet.java        |   41 +
 .../internal/resolve/DefaultProjectLocator.java    |   41 +
 .../InputHandlingNativeDependencyResolver.java     |   36 +
 .../internal/resolve/LibraryBinaryLocator.java     |   25 +
 .../resolve/LibraryNativeDependencyResolver.java   |   36 +
 .../internal/resolve/LibraryResolveException.java  |   32 +
 .../NativeBinaryRequirementResolveResult.java      |   65 +
 .../resolve/NativeBinaryResolveResult.java         |   74 +
 .../resolve/NativeDependencyNotationParser.java    |   59 +
 .../internal/resolve/NativeDependencyResolver.java |   20 +
 .../resolve/NativeDependencyResolverServices.java  |   47 +
 .../resolve/ProjectLibraryBinaryLocator.java       |   44 +
 .../internal/resolve/ProjectLocator.java           |   22 +
 ...RequirementParsingNativeDependencyResolver.java |   38 +
 .../resolve/SourceSetNativeDependencyResolver.java |   97 +
 .../nativebinaries/language/PreprocessingTool.java |   45 +
 .../language/assembler/internal/AssembleSpec.java  |   35 +
 .../assembler/internal/DefaultAssembleSpec.java    |   47 +
 .../language/assembler/package-info.java           |   20 +
 .../plugins/AssemblerNativeBinariesPlugin.groovy   |   88 +
 .../assembler/plugins/AssemblerPlugin.groovy       |   35 +
 .../language/assembler/tasks/Assemble.groovy       |   95 +
 .../language/c/internal/CCompileSpec.java          |   23 +
 .../language/c/internal/DefaultCCompileSpec.java   |   22 +
 .../AbstractIncrementalNativeCompiler.java         |   86 +
 .../incremental/CleanCompilingNativeCompiler.java  |   57 +
 .../internal/incremental/CompilationFileState.java |   50 +
 .../c/internal/incremental/CompilationState.java   |   41 +
 .../incremental/CompilationStateSerializer.java    |  127 +
 .../incremental/DefaultIncrementalCompilation.java |   37 +
 .../incremental/DefaultSourceIncludes.java         |   79 +
 .../incremental/DefaultSourceIncludesParser.java   |   43 +
 .../incremental/DefaultSourceIncludesResolver.java |   67 +
 .../incremental/IncrementalCompilation.java        |   25 +
 .../incremental/IncrementalCompileProcessor.java   |  148 +
 .../incremental/IncrementalCompilerBuilder.java    |   75 +
 .../incremental/IncrementalNativeCompiler.java     |   45 +
 .../c/internal/incremental/ResolvedInclude.java    |   67 +
 .../c/internal/incremental/SourceIncludes.java     |   24 +
 .../internal/incremental/SourceIncludesParser.java |   24 +
 .../incremental/SourceIncludesResolver.java        |   23 +
 .../incremental/sourceparser/CSourceParser.java    |   33 +
 .../sourceparser/PreprocessingReader.java          |  145 +
 .../sourceparser/RegexBackedCSourceParser.java     |   82 +
 .../nativebinaries/language/c/package-info.java    |   20 +
 .../c/plugins/CNativeBinariesPlugin.groovy         |   91 +
 .../language/c/plugins/CPlugin.groovy              |   35 +
 .../c/tasks/AbstractNativeCompileTask.groovy       |  145 +
 .../language/c/tasks/CCompile.groovy               |   36 +
 .../language/cpp/internal/CppCompileSpec.java      |   23 +
 .../cpp/internal/DefaultCppCompileSpec.java        |   22 +
 .../nativebinaries/language/cpp/package-info.java  |   20 +
 .../cpp/plugins/CppNativeBinariesPlugin.groovy     |   89 +
 .../language/cpp/plugins/CppPlugin.groovy          |   35 +
 .../language/cpp/plugins/package-info.java         |   20 +
 .../language/cpp/tasks/CppCompile.groovy           |   38 +
 .../language/cpp/tasks/package-info.java           |   20 +
 .../internal/AbstractNativeCompileSpec.java        |  109 +
 .../internal/DefaultPreprocessingTool.java         |   39 +
 .../internal/DefaultObjectiveCCompileSpec.java     |   23 +
 .../objectivec/internal/ObjectiveCCompileSpec.java |   23 +
 .../language/objectivec/package-info.java          |   20 +
 .../plugins/ObjectiveCNativeBinariesPlugin.groovy  |   89 +
 .../objectivec/plugins/ObjectiveCPlugin.groovy     |   36 +
 .../language/objectivec/plugins/package-info.java  |   20 +
 .../objectivec/tasks/ObjectiveCCompile.groovy      |   38 +
 .../language/objectivec/tasks/package-info.java    |   20 +
 .../internal/DefaultObjectiveCppCompileSpec.java   |   23 +
 .../internal/ObjectiveCppCompileSpec.java          |   23 +
 .../language/objectivecpp/package-info.java        |   20 +
 .../ObjectiveCppNativeBinariesPlugin.groovy        |   90 +
 .../objectivecpp/plugins/ObjectiveCppPlugin.groovy |   36 +
 .../objectivecpp/plugins/package-info.java         |   20 +
 .../objectivecpp/tasks/ObjectiveCppCompile.groovy  |   38 +
 .../language/objectivecpp/tasks/package-info.java  |   20 +
 .../nativebinaries/language/package-info.java      |   20 +
 .../DefaultWindowsResourceCompileSpec.java         |   21 +
 .../rc/internal/WindowsResourceCompileSpec.java    |   21 +
 .../WindowsResourcesNativeBinariesPlugin.groovy    |  103 +
 .../rc/plugins/WindowsResourcesPlugin.groovy       |   36 +
 .../rc/tasks/WindowsResourceCompile.groovy         |  131 +
 .../org/gradle/nativebinaries/package-info.java    |   20 +
 .../nativebinaries/platform/Architecture.java      |   32 +
 .../nativebinaries/platform/OperatingSystem.java   |   60 +
 .../gradle/nativebinaries/platform/Platform.java   |  125 +
 .../nativebinaries/platform/PlatformContainer.java |   27 +
 .../platform/internal/ArchitectureInternal.java    |   38 +
 .../internal/ArchitectureNotationParser.java       |   91 +
 .../platform/internal/DefaultArchitecture.java     |  100 +
 .../platform/internal/DefaultOperatingSystem.java  |   68 +
 .../platform/internal/DefaultPlatform.java         |   74 +
 .../internal/DefaultPlatformContainer.java         |   39 +
 .../internal/OperatingSystemNotationParser.java    |   80 +
 .../platform/internal/PlatformInternal.java        |   22 +
 .../nativebinaries/platform/package-info.java      |   20 +
 .../plugins/NativeBinariesModelPlugin.java         |  142 +
 .../plugins/NativeBinariesPlugin.groovy            |  138 +
 .../nativebinaries/plugins/package-info.java       |   20 +
 .../nativebinaries/tasks/AbstractLinkTask.groovy   |  120 +
 .../nativebinaries/tasks/BuildBinaryTask.java      |   31 +
 .../tasks/CreateStaticLibrary.groovy               |   96 +
 .../nativebinaries/tasks/InstallExecutable.groovy  |  156 +
 .../nativebinaries/tasks/LinkExecutable.groovy     |   31 +
 .../nativebinaries/tasks/LinkSharedLibrary.groovy  |   43 +
 .../gradle/nativebinaries/tasks/package-info.java  |   20 +
 .../test/ProjectComponentTestSuite.java            |   31 +
 .../org/gradle/nativebinaries/test/TestSuite.java  |   26 +
 .../nativebinaries/test/TestSuiteContainer.java    |   27 +
 .../test/TestSuiteExecutableBinary.java            |   26 +
 .../nativebinaries/test/cunit/CUnitTestSuite.java  |   26 +
 .../cunit/internal/ConfigureCUnitTestSources.java  |   66 +
 .../test/cunit/internal/CreateCUnitBinaries.java   |   88 +
 .../test/cunit/internal/DefaultCUnitTestSuite.java |   39 +
 .../nativebinaries/test/cunit/package-info.java    |   20 +
 .../test/cunit/plugins/CUnitPlugin.groovy          |   72 +
 .../test/cunit/plugins/package-info.java           |   20 +
 .../test/cunit/tasks/GenerateCUnitLauncher.groovy  |   40 +
 .../test/cunit/tasks/package-info.java             |   20 +
 .../test/internal/DefaultTestSuiteContainer.java   |   30 +
 .../internal/DefaultTestSuiteExecutableBinary.java |   48 +
 .../gradle/nativebinaries/test/package-info.java   |   20 +
 .../test/plugins/NativeBinariesTestPlugin.groovy   |   75 +
 .../nativebinaries/test/plugins/package-info.java  |   20 +
 .../test/tasks/RunTestExecutable.groovy            |   79 +
 .../nativebinaries/test/tasks/package-info.java    |   20 +
 .../org/gradle/nativebinaries/toolchain/Clang.java |   41 +
 .../org/gradle/nativebinaries/toolchain/Gcc.java   |   41 +
 .../gradle/nativebinaries/toolchain/GccTool.java   |   42 +
 .../toolchain/PlatformConfigurableToolChain.java   |   55 +
 .../toolchain/TargetPlatformConfiguration.java     |   67 +
 .../gradle/nativebinaries/toolchain/ToolChain.java |   35 +
 .../toolchain/ToolChainRegistry.java               |   28 +
 .../gradle/nativebinaries/toolchain/VisualCpp.java |   47 +
 .../toolchain/internal/AbstractToolChain.java      |   73 +
 .../toolchain/internal/ArgsTransformer.java        |   25 +
 .../toolchain/internal/CommandLineTool.java        |  113 +
 .../CompileSpecToArgsTransformerChain.java         |   44 +
 .../internal/DefaultToolChainRegistry.java         |  149 +
 .../toolchain/internal/MacroArgsConverter.java     |   35 +
 .../toolchain/internal/NativeCompileSpec.java      |   63 +
 .../internal/OptionsFileArgsTransformer.java       |   64 +
 .../toolchain/internal/OutputCleaningCompiler.java |   67 +
 .../toolchain/internal/PlatformToolChain.java      |   40 +
 .../SingleSourceCompileArgTransformer.java         |   66 +
 .../toolchain/internal/ToolChainAvailability.java  |   68 +
 .../toolchain/internal/ToolChainInternal.java      |   41 +
 .../internal/ToolChainRegistryInternal.java        |   34 +
 .../toolchain/internal/ToolSearchResult.java       |   28 +
 .../toolchain/internal/ToolType.java               |   44 +
 .../internal/UnavailablePlatformToolChain.java     |   79 +
 .../toolchain/internal/clang/ClangToolChain.java   |   47 +
 .../gcc/AbstractGccCompatibleToolChain.java        |  267 ++
 .../internal/gcc/ArStaticLibraryArchiver.java      |   73 +
 .../toolchain/internal/gcc/Assembler.java          |   80 +
 .../toolchain/internal/gcc/CCompiler.java          |   36 +
 .../internal/gcc/CommandLineToolSearchResult.java  |   25 +
 .../toolchain/internal/gcc/CppCompiler.java        |   37 +
 .../internal/gcc/GccCompilerArgsTransformer.java   |   58 +
 .../toolchain/internal/gcc/GccLinker.java          |   94 +
 .../internal/gcc/GccOptionsFileArgTransformer.java |   52 +
 .../internal/gcc/GccPlatformToolChain.java         |  114 +
 .../toolchain/internal/gcc/GccToolChain.java       |   90 +
 .../toolchain/internal/gcc/GccToolSearchPath.java  |   49 +
 .../toolchain/internal/gcc/NativeCompiler.java     |   65 +
 .../toolchain/internal/gcc/ObjectiveCCompiler.java |   38 +
 .../internal/gcc/ObjectiveCppCompiler.java         |   37 +
 .../gcc/PostTransformActionArgsTransformer.java    |   39 +
 .../internal/gcc/ShortCircuitArgsTransformer.java  |   41 +
 .../internal/gcc/version/GccVersionDeterminer.java |  142 +
 .../internal/gcc/version/GccVersionResult.java     |   23 +
 .../toolchain/internal/msvcpp/Assembler.java       |   80 +
 .../toolchain/internal/msvcpp/CCompiler.java       |   33 +
 .../toolchain/internal/msvcpp/CppCompiler.java     |   33 +
 .../msvcpp/DefaultVisualStudioLocator.java         |  369 ++
 .../internal/msvcpp/DefaultWindowsSdkLocator.java  |  275 ++
 .../toolchain/internal/msvcpp/EscapeUserArgs.java  |   40 +
 .../internal/msvcpp/InstallationSearchResult.java  |   27 +
 .../msvcpp/LibExeStaticLibraryArchiver.java        |   58 +
 .../toolchain/internal/msvcpp/LinkExeLinker.java   |   70 +
 .../toolchain/internal/msvcpp/NativeCompiler.java  |   59 +
 .../msvcpp/VisualCppCompilerArgsTransformer.java   |   50 +
 .../internal/msvcpp/VisualCppInstall.java          |  116 +
 .../internal/msvcpp/VisualCppToolChain.java        |  245 ++
 .../internal/msvcpp/VisualStudioInstall.java       |   48 +
 .../internal/msvcpp/VisualStudioLocator.java       |   29 +
 .../internal/msvcpp/WindowsResourceCompiler.java   |   90 +
 .../toolchain/internal/msvcpp/WindowsSdk.java      |  138 +
 .../internal/msvcpp/WindowsSdkLocator.java         |   29 +
 .../internal/plugins/StandardToolChainsPlugin.java |   34 +
 .../toolchain/internal/tools/DefaultTool.java      |   58 +
 .../internal/tools/DefaultToolRegistry.java        |   35 +
 .../toolchain/internal/tools/GccToolInternal.java  |   29 +
 .../toolchain/internal/tools/PlatformGccTool.java  |   58 +
 .../internal/tools/PlatformToolRegistry.java       |   48 +
 .../toolchain/internal/tools/ToolRegistry.java     |   22 +
 .../toolchain/internal/tools/ToolSearchPath.java   |  135 +
 .../nativebinaries/toolchain/package-info.java     |   20 +
 .../toolchain/plugins/ClangCompilerPlugin.groovy   |   62 +
 .../toolchain/plugins/GccCompilerPlugin.groovy     |   63 +
 .../plugins/MicrosoftVisualCppPlugin.groovy        |   76 +
 .../toolchain/plugins/package-info.java            |   20 +
 .../gradle/plugins/binaries/BinariesPlugin.java    |   66 -
 .../org/gradle/plugins/binaries/model/Binary.java  |   39 -
 .../gradle/plugins/binaries/model/CompileSpec.java |   51 -
 .../gradle/plugins/binaries/model/Compiler.java    |   24 -
 .../plugins/binaries/model/CompilerRegistry.java   |   31 -
 .../gradle/plugins/binaries/model/Executable.java  |   23 -
 .../binaries/model/HeaderExportingSourceSet.java   |   27 -
 .../org/gradle/plugins/binaries/model/Library.java |   27 -
 .../plugins/binaries/model/LibraryCompileSpec.java |   32 -
 .../model/NativeDependencyCapableSourceSet.java    |   25 -
 .../binaries/model/NativeDependencySet.java        |   28 -
 .../gradle/plugins/binaries/model/SourceSet.java   |   25 -
 .../binaries/model/internal/BinaryCompileSpec.java |   23 -
 .../model/internal/BinaryCompileSpecFactory.java   |   27 -
 .../model/internal/CompileSpecFactory.java         |   29 -
 .../binaries/model/internal/CompileTaskAware.java  |   23 -
 .../binaries/model/internal/CompilerAdapter.java   |   32 -
 .../ConfigurationBasedNativeDependencySet.groovy   |   86 -
 .../binaries/model/internal/DefaultBinary.java     |   66 -
 .../model/internal/DefaultCompilerRegistry.java    |   90 -
 .../binaries/model/internal/DefaultExecutable.java |   31 -
 .../binaries/model/internal/DefaultLibrary.java    |   76 -
 .../binaries/model/internal/package-info.java      |   20 -
 .../plugins/binaries/model/package-info.java       |   20 -
 .../org/gradle/plugins/binaries/package-info.java  |   20 -
 .../plugins/binaries/tasks/package-info.java       |   20 -
 .../org/gradle/plugins/cpp/CppCompile.groovy       |   33 -
 .../plugins/cpp/CppExeConventionPlugin.groovy      |   58 -
 .../org/gradle/plugins/cpp/CppExtension.java       |   50 -
 .../plugins/cpp/CppLibConventionPlugin.groovy      |   68 -
 .../groovy/org/gradle/plugins/cpp/CppPlugin.groovy |   99 -
 .../org/gradle/plugins/cpp/CppSourceSet.java       |   65 -
 .../org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy |   77 -
 .../cpp/cdt/model/CprojectDescriptor.groovy        |  115 -
 .../plugins/cpp/cdt/model/CprojectSettings.groovy  |  107 -
 .../plugins/cpp/cdt/model/ProjectDescriptor.groovy |   46 -
 .../plugins/cpp/cdt/model/ProjectSettings.groovy   |   40 -
 .../cpp/cdt/tasks/GenerateMetadataFileTask.groovy  |   51 -
 .../cpp/compiler/capability/AgainstLibrary.java    |   31 -
 .../cpp/compiler/capability/CompilesCpp.java       |   30 -
 .../compiler/capability/StandardCppCompiler.java   |   23 -
 .../cpp/compiler/capability/package-info.java      |   20 -
 .../compiler/internal/CommandLineCppCompiler.java  |   68 -
 .../internal/CommandLineCppCompilerAdapter.java    |   59 -
 ...ommandLineCppCompilerArgumentsToOptionFile.java |   56 -
 .../plugins/cpp/compiler/internal/CppCompiler.java |   24 -
 .../gradle/plugins/cpp/gpp/GppCompileSpec.groovy   |  218 -
 .../plugins/cpp/gpp/GppCompilerPlugin.groovy       |   53 -
 .../plugins/cpp/gpp/GppLibraryCompileSpec.groovy   |   40 -
 .../cpp/gpp/internal/GppCompileSpecFactory.java    |   42 -
 .../gpp/internal/GppCompileSpecToArguments.java    |   55 -
 .../plugins/cpp/gpp/internal/GppCompiler.java      |   44 -
 .../cpp/gpp/internal/GppCompilerAdapter.java       |  106 -
 .../gpp/internal/version/GppVersionDeterminer.java |  105 -
 .../org/gradle/plugins/cpp/gpp/package-info.java   |   20 -
 .../plugins/cpp/internal/CppCompileSpec.java       |   33 -
 .../plugins/cpp/internal/DefaultCppSourceSet.java  |   90 -
 .../cpp/msvcpp/MicrosoftVisualCppPlugin.groovy     |   61 -
 .../internal/VisualCppCompileSpecToArguments.java  |   47 -
 .../cpp/msvcpp/internal/VisualCppCompiler.java     |   36 -
 .../msvcpp/internal/VisualCppCompilerAdapter.java  |   51 -
 .../org/gradle/plugins/cpp/package-info.java       |   20 -
 .../META-INF/gradle-plugins/assembler.properties   |    1 +
 .../META-INF/gradle-plugins/binaries.properties    |    1 -
 .../resources/META-INF/gradle-plugins/c.properties |    1 +
 .../META-INF/gradle-plugins/cpp-exe.properties     |    1 -
 .../META-INF/gradle-plugins/cpp-lib.properties     |    1 -
 .../META-INF/gradle-plugins/cpp.properties         |    2 +-
 .../META-INF/gradle-plugins/cunit.properties       |   17 +
 .../META-INF/gradle-plugins/eclipse-cdt.properties |    2 +-
 .../gradle-plugins/gpp-compiler.properties         |    1 -
 .../gradle-plugins/native-binaries.properties      |    1 +
 .../META-INF/gradle-plugins/objective-c.properties |    1 +
 .../gradle-plugins/objective-cpp.properties        |    1 +
 .../gradle-plugins/visual-studio.properties        |    1 +
 .../gradle-plugins/windows-resources.properties    |    1 +
 ...e.internal.service.scopes.PluginServiceRegistry |    1 +
 .../cdt/model/defaultCproject-linux.xml            |    0
 .../cdt/model/defaultCproject-macos.xml            |    0
 .../cpp => ide}/cdt/model/defaultProject.xml       |    0
 .../ide/visualstudio/tasks/internal/default.sln    |    2 +
 .../visualstudio/tasks/internal/default.vcxproj    |   17 +
 .../tasks/internal/default.vcxproj.filters         |   16 +
 .../test/cunit/tasks/gradle_cunit_main.c           |   48 +
 .../test/cunit/tasks/gradle_cunit_register.h       |    4 +
 .../ide/cdt/model/CprojectSettingsSpec.groovy      |   58 +
 .../ide/cdt/model/ProjectDescriptorSpec.groovy     |   48 +
 .../internal/DefaultVisualStudioProjectTest.groovy |  115 +
 .../VisualStudioProjectConfigurationTest.groovy    |  196 +
 .../internal/VisualStudioProjectMapperTest.groovy  |  142 +
 .../VisualStudioProjectRegistryTest.groovy         |  109 +
 .../RelativeFileNameTransformerTest.groovy         |  122 +
 .../internal/VisualStudioFiltersFileTest.groovy    |   97 +
 .../internal/VisualStudioProjectFileTest.groovy    |  118 +
 .../internal/VisualStudioSolutionFileTest.groovy   |  190 +
 .../plugins/AssemblerLangPluginTest.groovy         |   42 +
 .../language/c/plugins/CLangPluginTest.groovy      |   42 +
 .../language/cpp/plugins/CppLangPluginTest.groovy  |   42 +
 .../internal/DefaultBuildTypeTest.groovy           |   29 +
 .../internal/DefaultExecutableBinaryTest.groovy    |   39 +
 .../internal/DefaultExecutableTest.groovy          |   29 +
 .../internal/DefaultFlavorTest.groovy              |   29 +
 .../internal/DefaultLibraryTest.groovy             |   59 +
 .../internal/DefaultNativeBinaryTasksTest.groovy   |   55 +
 .../internal/DefaultNativeComponentTest.groovy     |   77 +
 .../internal/ProjectNativeBinaryTest.groovy        |  210 +
 .../internal/ProjectSharedLibraryBinaryTest.groovy |  123 +
 .../internal/ProjectStaticLibraryBinaryTest.groovy |  125 +
 .../internal/SourceSetNotationParserTest.groovy    |   53 +
 .../configure/CreateDefaultBuildTypesTest.groovy   |   44 +
 .../configure/CreateDefaultFlavorsTest.groovy      |   67 +
 .../configure/CreateDefaultPlatformTest.groovy     |   44 +
 .../DefaultNativeBinariesFactoryTest.groovy        |   93 +
 .../ProjectNativeBinaryInitializerTest.groovy      |   99 +
 .../ProjectNativeComponentInitializerTest.groovy   |  145 +
 .../DefaultPrebuiltSharedLibraryBinaryTest.groovy  |   74 +
 .../DefaultPrebuiltStaticLibraryBinaryTest.groovy  |   56 +
 .../NativeDependencyNotationParserTest.groovy      |   72 +
 .../resolve/ProjectLibraryBinaryLocatorTest.groovy |  136 +
 .../AbstractNativeBinariesPluginTest.groovy        |  160 +
 .../AssemblerNativeBinariesPluginTest.groovy       |  183 +
 .../language/assembler/tasks/AssemblerTest.groovy  |   65 +
 .../CleanCompilingNativeCompilerTest.groovy        |   68 +
 .../CompilationStateSerializerTest.groovy          |   90 +
 .../DefaultSourceIncludesParserTest.groovy         |   68 +
 .../DefaultSourceIncludesResolverTest.groovy       |  154 +
 .../IncrementalCompileProcessorTest.groovy         |  422 ++
 .../IncrementalNativeCompilerTest.groovy           |   54 +
 .../sourceparser/PreprocessingReaderTest.groovy    |   72 +
 .../RegexBackedCSourceParserTest.groovy            |  386 ++
 .../c/plugins/CNativeBinariesPluginTest.groovy     |   48 +
 .../language/c/tasks/CCompileTest.groovy           |   68 +
 .../binaryinfo/ReadelfBinaryInfoTest.groovy        |   56 +
 .../cpp/internal/DefaultCppSourceSetTest.groovy    |   66 +
 .../cpp/plugins/CppNativeBinariesPluginTest.groovy |  206 +
 .../language/cpp/tasks/CppCompileTest.groovy       |   67 +
 .../ObjectiveCNativeBinariesPluginTest.groovy      |   46 +
 .../ObjectiveCppNativeBinariesPluginTest.groovy    |   45 +
 .../internal/ArchitectureNotationParserTest.groovy |  115 +
 .../internal/DefaultArchitectureTest.groovy        |   29 +
 .../internal/DefaultOperatingSystemTest.groovy     |   30 +
 .../platform/internal/DefaultPlatformTest.groovy   |   61 +
 .../OperatingSystemNotationParserTest.groovy       |  100 +
 .../plugins/NativeBinariesModelPluginTest.groovy   |  238 +
 .../plugins/NativeBinariesPluginTest.groovy        |  126 +
 .../internal/DefaultToolChainRegistryTest.groovy   |  182 +
 .../internal/OutputCleaningCompilerTest.groovy     |  115 +
 .../internal/ToolChainAvailabilityTest.groovy      |   67 +
 .../UnavailablePlatformToolChainTest.groovy        |   48 +
 .../gcc/AbstractGccCompatibleToolChainTest.groovy  |  237 +
 .../toolchain/internal/gcc/AssemblerTest.groovy    |   83 +
 .../toolchain/internal/gcc/CCompilerTest.groovy    |   98 +
 .../internal/gcc/ClangToolChainTest.groovy         |   48 +
 .../toolchain/internal/gcc/GccLinkerTest.groovy    |   86 +
 .../toolchain/internal/gcc/GccToolChainTest.groovy |   68 +
 .../gcc/ShortCircuitArgsTransformerTest.groovy     |   64 +
 .../gcc/version/GccVersionDeterminerTest.groovy    |  139 +
 .../msvcpp/DefaultVisualStudioLocatorTest.groovy   |  202 +
 .../msvcpp/DefaultWindowsSdkLocatorTest.groovy     |  256 ++
 .../internal/msvcpp/VisualCppToolChainTest.groovy  |  206 +
 .../internal/tools/ToolSearchPathTest.groovy       |  125 +
 .../plugins/ClangCompilerPluginTest.groovy         |   57 +
 .../toolchain/plugins/GccCompilerPluginTest.groovy |   59 +
 .../plugins/MicrosoftVisualCppPluginTest.groovy    |   64 +
 .../toolchain/plugins/ToolchainPluginTest.groovy   |   70 +
 .../internal/DefaultCompilerRegistryTest.groovy    |  130 -
 .../plugins/cpp/CppExeConventionPluginTest.groovy  |   37 -
 .../plugins/cpp/CppLibConventionPluginTest.groovy  |   39 -
 .../org/gradle/plugins/cpp/CppPluginTest.groovy    |  235 -
 .../cpp/cdt/model/CprojectSettingsSpec.groovy      |   58 -
 .../cpp/cdt/model/ProjectDescriptorSpec.groovy     |   48 -
 .../plugins/cpp/gpp/GppCompileSpecTest.groovy      |   40 -
 .../cpp/gpp/GppLibraryCompileSpecTest.groovy       |   37 -
 .../version/GppVersionDeterminerTest.groovy        |  142 -
 .../ide/visualstudio/fixtures/FiltersFile.groovy   |   29 +
 .../ide/visualstudio/fixtures/ProjectFile.groovy   |  128 +
 .../ide/visualstudio/fixtures/SolutionFile.groovy  |   82 +
 .../language/cpp/fixtures/AvailableToolChains.java |  503 +++
 .../language/cpp/fixtures/ExecutableFixture.groovy |   35 +
 .../cpp/fixtures/NativeBinaryFixture.groovy        |   89 +
 .../cpp/fixtures/NativeInstallationFixture.groovy  |   73 +
 .../cpp/fixtures/RequiresInstalledToolChain.groovy |   30 +
 .../RequiresInstalledToolChainExtension.groovy     |   39 +
 .../cpp/fixtures/SharedLibraryFixture.groovy       |   47 +
 .../cpp/fixtures/StaticLibraryFixture.groovy       |   29 +
 .../cpp/fixtures/ToolChainRequirement.java         |   30 +
 .../app/CCallingMixedCAndCppHelloWorldApp.groovy   |   83 +
 .../fixtures/app/CCompilerDetectingTestApp.groovy  |   80 +
 .../cpp/fixtures/app/CHelloWorldApp.groovy         |  167 +
 .../fixtures/app/CppCallingCHelloWorldApp.groovy   |   75 +
 .../app/CppCompilerDetectingTestApp.groovy         |   80 +
 .../cpp/fixtures/app/CppHelloWorldApp.groovy       |  116 +
 .../app/DuplicateAssemblerBaseNamesTestApp.groovy  |   88 +
 .../fixtures/app/DuplicateCBaseNamesTestApp.groovy |   61 +
 .../app/DuplicateCppBaseNamesTestApp.groovy        |   64 +
 .../app/DuplicateMixedSameBaseNamesTestApp.groovy  |  140 +
 .../app/DuplicateObjectiveCBaseNamesTestApp.groovy |   80 +
 .../DuplicateObjectiveCppBaseNamesTestApp.groovy   |   80 +
 ...uplicateWindowsResourcesBaseNamesTestApp.groovy |   91 +
 .../ExeWithDiamondDependencyHelloWorldApp.groovy   |   56 +
 .../ExeWithLibraryUsingLibraryHelloWorldApp.groovy |  132 +
 .../language/cpp/fixtures/app/HelloWorldApp.java   |  117 +
 .../cpp/fixtures/app/IncrementalHelloWorldApp.java |   49 +
 .../fixtures/app/MixedLanguageHelloWorldApp.groovy |  143 +
 .../app/MixedObjectiveCHelloWorldApp.groovy        |  120 +
 .../fixtures/app/ObjectiveCHelloWorldApp.groovy    |  139 +
 .../fixtures/app/ObjectiveCppHelloWorldApp.groovy  |  146 +
 .../fixtures/app/PlatformDetectingTestApp.groovy   |   85 +
 .../language/cpp/fixtures/app/SourceFile.java      |   61 +
 .../language/cpp/fixtures/app/TestApp.java         |   72 +
 .../language/cpp/fixtures/app/TestComponent.groovy |   39 +
 .../app/WindowsResourceHelloWorldApp.groovy        |  129 +
 .../cpp/fixtures/binaryinfo/BinaryInfo.java        |   28 +
 .../fixtures/binaryinfo/DumpbinBinaryInfo.groovy   |   99 +
 .../cpp/fixtures/binaryinfo/OtoolBinaryInfo.groovy |   59 +
 .../fixtures/binaryinfo/ReadelfBinaryInfo.groovy   |   82 +
 .../test/cunit/CUnitTestResults.groovy             |  104 +
 subprojects/diagnostics/diagnostics.gradle         |    3 +-
 .../HtmlDependencyReportTaskIntegrationTest.groovy |  529 +++
 ...pendencyInsightReportTaskIntegrationTest.groovy |  423 +-
 .../DependencyReportTaskIntegrationTest.groovy     |  108 +-
 .../diagnostics/HelpTaskIntegrationTest.groovy     |  282 ++
 .../ResolutionResultApiIntegrationTest.groovy      |   79 -
 .../TaskReportTaskIntegrationTest.groovy           |    2 +-
 .../org/gradle/configuration/HelpTest.groovy       |   41 +
 .../listsCommonDynamicAvailableValues/build.gradle |   21 +
 .../settings.gradle                                |   18 +
 .../build.gradle                                   |   23 +
 .../settings.gradle                                |    2 +
 .../org/gradle/api/plugins/HelpTasksPlugin.groovy  |   23 +-
 .../gradle/api/plugins/ProjectReportsPlugin.java   |   27 +-
 .../plugins/internal/HelpTasksAutoApplyAction.java |   26 +
 .../dependencies/DependencyReportContainer.java    |   33 +
 .../dependencies/HtmlDependencyReportTask.java     |  115 +
 .../internal/DefaultDependencyReportContainer.java |   35 +
 .../internal/HtmlDependencyReporter.groovy         |  128 +
 .../JsonDependencyReportIndexRenderer.groovy       |   79 +
 .../internal/JsonProjectDependencyRenderer.groovy  |  253 ++
 .../internal/StrictDependencyResultSpec.java       |   69 +
 .../api/reporting/dependencies/package-info.java   |   20 +
 .../api/tasks/diagnostics/AbstractReportTask.java  |    2 +-
 .../diagnostics/DependencyInsightReportTask.groovy |   25 +-
 .../tasks/diagnostics/DependencyReportTask.java    |    6 +-
 .../api/tasks/diagnostics/ProjectReportTask.java   |    9 +-
 .../api/tasks/diagnostics/TaskReportTask.java      |    4 +-
 .../internal/DependencyReportRenderer.java         |    2 -
 .../tasks/diagnostics/internal/GraphRenderer.java  |   69 -
 .../internal/SingleProjectTaskReportModel.java     |    6 +-
 .../diagnostics/internal/TaskReportRenderer.java   |    2 -
 .../AsciiDependencyReportRenderer.java             |    4 +-
 .../internal/dsl/DependencyResultSpec.java         |   20 +-
 .../dsl/DependencyResultSpecNotationParser.java    |   17 +-
 .../internal/graph/DependencyGraphRenderer.groovy  |   13 +-
 .../diagnostics/internal/graph/NodeRenderer.groovy |    3 -
 .../internal/graph/SimpleNodeRenderer.java         |    3 -
 .../nodes/AbstractRenderableDependencyResult.java  |   64 +-
 .../nodes/AbstractRenderableModuleResult.java      |   15 +-
 .../internal/graph/nodes/DependencyEdge.java       |   14 +-
 .../graph/nodes/DependencyReportHeader.java        |   55 +
 .../nodes/InvertedRenderableModuleResult.java      |   10 +-
 .../internal/graph/nodes/RenderableDependency.java |    7 +-
 .../graph/nodes/RenderableDependencyResult.java    |   12 +-
 .../graph/nodes/RenderableModuleResult.java        |    7 +-
 .../RenderableUnresolvedDependencyResult.java      |   47 +-
 .../internal/graph/nodes/RequestedVersion.java     |   20 +-
 .../graph/nodes/ResolvedDependencyEdge.java        |   14 +-
 .../internal/graph/nodes/SimpleDependency.java     |   64 -
 .../graph/nodes/UnresolvedDependencyEdge.java      |   25 +-
 .../insight/DependencyInsightReporter.groovy       |   28 +-
 .../internal/insight/DependencyResultSorter.java   |  151 +-
 .../main/groovy/org/gradle/configuration/Help.java |   25 +
 .../gradle/configuration/TaskDetailPrinter.java    |  206 +
 ...le.configuration.project.ProjectConfigureAction |    1 +
 .../tasks/diagnostics/htmldependencyreport/d.gif   |  Bin 0 -> 2944 bytes
 .../tasks/diagnostics/htmldependencyreport/d.png   |  Bin 0 -> 7635 bytes
 .../diagnostics/htmldependencyreport/index.html    |   48 +
 .../htmldependencyreport/jquery-1.10.1.min.js      |    6 +
 .../htmldependencyreport/jquery.jstree.js          | 4564 ++++++++++++++++++++
 .../diagnostics/htmldependencyreport/script.js     |  225 +
 .../diagnostics/htmldependencyreport/style.css     |   85 +
 .../diagnostics/htmldependencyreport/template.html |   41 +
 .../diagnostics/htmldependencyreport/throbber.gif  |  Bin 0 -> 1849 bytes
 .../diagnostics/htmldependencyreport/tree.css      |  102 +
 .../gradle/api/plugins/HelpTasksPluginSpec.groovy  |   26 +-
 .../api/plugins/ProjectReportsPluginTest.groovy    |   72 +
 .../api/plugins/ProjectReportsPluginTest.java      |   68 -
 .../ReportingBasePluginConventionTest.groovy       |   64 -
 .../api/plugins/ReportingBasePluginTest.groovy     |   46 -
 .../internal/StrictDependencyResultSpecTest.groovy |   61 +
 .../tasks/diagnostics/AbstractReportTaskTest.java  |    8 +-
 .../DependencyInsightReportTaskSpec.groovy         |   15 +-
 .../diagnostics/DependencyReportTaskTest.groovy    |   19 +-
 .../tasks/diagnostics/ProjectReportTaskTest.groovy |   14 +-
 .../tasks/diagnostics/PropertyReportTaskTest.java  |    4 +-
 .../api/tasks/diagnostics/TaskReportTaskTest.java  |    4 +-
 .../internal/TaskReportRendererTest.groovy         |    3 -
 .../AsciiDependencyReportRendererTest.groovy       |    4 +-
 .../DependencyResultSpecNotationParserSpec.groovy  |   13 +-
 .../internal/dsl/DependencyResultSpecTest.groovy   |   13 +-
 .../graph/DependencyGraphRendererSpec.groovy       |    5 +-
 .../AbstractRenderableDependencyResultSpec.groovy  |   45 +-
 .../nodes/RenderableDependencyResultTest.groovy    |   11 +-
 ...RenderableUnresolvedDependencyResultTest.groovy |    6 +-
 .../internal/graph/nodes/SimpleDependency.java     |   64 +
 .../insight/DependencyInsightReporterSpec.groovy   |   28 +-
 .../insight/DependencyResultSorterSpec.groovy      |  210 +-
 subprojects/distributions/distributions.gradle     |   22 +-
 .../gradle/AllDistributionIntegrationSpec.groovy   |    9 +-
 .../org/gradle/DistributionIntegrationSpec.groovy  |    7 +-
 subprojects/docs/docs.gradle                       |   54 +-
 subprojects/docs/src/docs/css/release-notes.css    |   29 +-
 subprojects/docs/src/docs/dsl/dsl.xml              |  146 +-
 .../docs/src/docs/dsl/org.gradle.api.Task.xml      |   12 +
 ....gradle.api.artifacts.dsl.DependencyHandler.xml |    6 +
 ....gradle.api.artifacts.dsl.RepositoryHandler.xml |    3 +
 ...dle.api.plugins.ApplicationPluginConvention.xml |    6 +-
 ...adle.api.plugins.jetty.AbstractJettyRunTask.xml |    8 +
 .../org.gradle.api.plugins.jetty.JettyRunWar.xml   |    8 +
 .../org.gradle.api.plugins.quality.CodeNarc.xml    |   12 +
 ...radle.api.plugins.quality.CodeNarcExtension.xml |   15 +
 ...org.gradle.api.publish.PublicationContainer.xml |    3 -
 .../org.gradle.api.publish.ivy.IvyPublication.xml  |   24 +
 ...g.gradle.api.publish.maven.MavenPublication.xml |    9 +
 ...gradle.api.reporting.GenerateBuildDashboard.xml |    3 -
 .../docs/dsl/org.gradle.api.reporting.Report.xml   |   37 +
 .../org.gradle.api.reporting.ReportContainer.xml   |   25 +
 .../dsl/org.gradle.api.reporting.Reporting.xml     |   28 +
 ...org.gradle.api.reporting.ReportingExtension.xml |   28 +
 ...rting.dependencies.HtmlDependencyReportTask.xml |   27 +
 .../dsl/org.gradle.api.tasks.AbstractCopyTask.xml  |   10 +
 .../src/docs/dsl/org.gradle.api.tasks.Copy.xml     |    2 +-
 .../dsl/org.gradle.api.tasks.GroovyRuntime.xml     |   25 +
 .../docs/dsl/org.gradle.api.tasks.ScalaRuntime.xml |   31 +
 ...le.api.tasks.application.CreateStartScripts.xml |    3 +-
 .../docs/dsl/org.gradle.api.tasks.bundling.Zip.xml |    6 +-
 .../org.gradle.api.tasks.compile.GroovyCompile.xml |    2 +-
 ...api.tasks.incremental.IncrementalTaskInputs.xml |   25 +
 .../org.gradle.api.tasks.incremental.InputFile.xml |   26 +
 .../org.gradle.api.tasks.scala.ScalaCompile.xml    |    2 +-
 .../dsl/org.gradle.api.tasks.scala.ScalaDoc.xml    |    2 +-
 .../docs/dsl/org.gradle.api.tasks.testing.Test.xml |   13 +-
 .../dsl/org.gradle.api.tasks.wrapper.Wrapper.xml   |    4 +-
 .../dsl/org.gradle.buildinit.tasks.InitBuild.xml   |   43 +
 ...adle.ide.visualstudio.VisualStudioExtension.xml |   44 +
 ...gradle.ide.visualstudio.VisualStudioProject.xml |   44 +
 ...radle.ide.visualstudio.VisualStudioSolution.xml |   44 +
 .../dsl/org.gradle.language.DependentSourceSet.xml |   47 +
 ...rg.gradle.language.HeaderExportingSourceSet.xml |   44 +
 ...radle.language.assembler.AssemblerSourceSet.xml |   38 +
 .../org.gradle.language.base.BinaryContainer.xml   |   38 +
 ...rg.gradle.language.base.FunctionalSourceSet.xml |   38 +
 .../org.gradle.language.base.LanguageSourceSet.xml |   44 +
 .../org.gradle.language.base.ProjectSourceSet.xml  |   38 +
 .../docs/dsl/org.gradle.language.c.CSourceSet.xml  |   38 +
 .../dsl/org.gradle.language.cpp.CppSourceSet.xml   |   38 +
 ...dle.language.objectivec.ObjectiveCSourceSet.xml |   38 +
 ...language.objectivecpp.ObjectiveCppSourceSet.xml |   38 +
 .../org.gradle.language.rc.WindowsResourceSet.xml  |   38 +
 .../dsl/org.gradle.nativebinaries.BuildType.xml    |   38 +
 ...rg.gradle.nativebinaries.BuildTypeContainer.xml |   38 +
 .../dsl/org.gradle.nativebinaries.Executable.xml   |   38 +
 .../org.gradle.nativebinaries.ExecutableBinary.xml |   38 +
 ...g.gradle.nativebinaries.ExecutableContainer.xml |   38 +
 .../docs/dsl/org.gradle.nativebinaries.Flavor.xml  |   38 +
 .../org.gradle.nativebinaries.FlavorContainer.xml  |   38 +
 .../docs/dsl/org.gradle.nativebinaries.Library.xml |   47 +
 .../org.gradle.nativebinaries.LibraryBinary.xml    |   38 +
 .../org.gradle.nativebinaries.LibraryContainer.xml |   38 +
 .../dsl/org.gradle.nativebinaries.NativeBinary.xml |   47 +
 ...g.gradle.nativebinaries.ProjectNativeBinary.xml |   68 +
 ...radle.nativebinaries.ProjectNativeComponent.xml |   53 +
 ...g.gradle.nativebinaries.SharedLibraryBinary.xml |   44 +
 ...g.gradle.nativebinaries.StaticLibraryBinary.xml |   41 +
 ...adle.nativebinaries.TargetedNativeComponent.xml |   48 +
 .../docs/dsl/org.gradle.nativebinaries.Tool.xml    |   44 +
 ...e.nativebinaries.language.PreprocessingTool.xml |   44 +
 ...ebinaries.language.assembler.tasks.Assemble.xml |   47 +
 ....language.c.tasks.AbstractNativeCompileTask.xml |   68 +
 ...le.nativebinaries.language.c.tasks.CCompile.xml |   38 +
 ...language.cpp.plugins.CppExeConventionPlugin.xml |   38 +
 ...language.cpp.plugins.CppLibConventionPlugin.xml |   38 +
 ...tivebinaries.language.cpp.plugins.CppPlugin.xml |   38 +
 ...ativebinaries.language.cpp.tasks.CppCompile.xml |   38 +
 ...language.objectivec.tasks.ObjectiveCCompile.xml |   38 +
 ...uage.objectivecpp.tasks.ObjectiveCppCompile.xml |   38 +
 ...es.language.rc.tasks.WindowsResourceCompile.xml |   65 +
 ...org.gradle.nativebinaries.platform.Platform.xml |   50 +
 ...e.nativebinaries.platform.PlatformContainer.xml |   38 +
 ...radle.nativebinaries.tasks.AbstractLinkTask.xml |   59 +
 ...le.nativebinaries.tasks.CreateStaticLibrary.xml |   53 +
 ...adle.nativebinaries.tasks.InstallExecutable.xml |   50 +
 ....gradle.nativebinaries.tasks.LinkExecutable.xml |   38 +
 ...adle.nativebinaries.tasks.LinkSharedLibrary.xml |   38 +
 ...tivebinaries.test.ProjectComponentTestSuite.xml |   41 +
 .../org.gradle.nativebinaries.test.TestSuite.xml   |   38 +
 ...adle.nativebinaries.test.TestSuiteContainer.xml |   38 +
 ...tivebinaries.test.TestSuiteExecutableBinary.xml |   38 +
 ...le.nativebinaries.test.cunit.CUnitTestSuite.xml |   38 +
 .../org.gradle.nativebinaries.toolchain.Clang.xml  |   40 +
 .../org.gradle.nativebinaries.toolchain.Gcc.xml    |   40 +
 ...ies.toolchain.PlatformConfigurableToolChain.xml |   44 +
 ...g.gradle.nativebinaries.toolchain.ToolChain.xml |   38 +
 ....nativebinaries.toolchain.ToolChainRegistry.xml |   38 +
 ...g.gradle.nativebinaries.toolchain.VisualCpp.xml |   40 +
 ...inaries.toolchain.plugins.GppCompilerPlugin.xml |   38 +
 ....toolchain.plugins.MicrosoftVisualCppPlugin.xml |   38 +
 ...g.gradle.plugins.ide.idea.model.IdeaProject.xml |    2 +-
 ...esting.jacoco.plugins.JacocoPluginExtension.xml |   50 +
 ....testing.jacoco.plugins.JacocoTaskExtension.xml |   77 +
 .../org.gradle.testing.jacoco.tasks.JacocoBase.xml |   45 +
 ...org.gradle.testing.jacoco.tasks.JacocoMerge.xml |   50 +
 ...rg.gradle.testing.jacoco.tasks.JacocoReport.xml |   69 +
 subprojects/docs/src/docs/dsl/plugins.xml          |   41 +
 .../docs/src/docs/release/content/script.js        |   26 +-
 .../docs/src/docs/release/notes-template.md        |   14 +-
 subprojects/docs/src/docs/release/notes.md         |  485 +--
 subprojects/docs/src/docs/stylesheets/dslHtml.xsl  |   10 +-
 .../src/docs/stylesheets/userGuideHtmlCommon.xsl   |    2 +-
 .../docs/src/docs/userguide/applicationPlugin.xml  |   12 +-
 .../docs/src/docs/userguide/artifactMngmt.xml      |    4 +-
 .../docs/src/docs/userguide/bootstrapPlugin.xml    |   88 -
 .../docs/userguide/buildAnnouncementsPlugin.xml    |    2 +-
 .../src/docs/userguide/buildDashboardPlugin.xml    |   20 +-
 .../docs/src/docs/userguide/buildInitPlugin.xml    |  222 +
 .../docs/src/docs/userguide/buildLifecycle.xml     |   12 +-
 .../src/docs/userguide/buildScriptsTutorial.xml    |   30 +-
 .../src/docs/userguide/commandLineTutorial.xml     |   12 +
 .../docs/src/docs/userguide/comparingBuilds.xml    |    8 +-
 subprojects/docs/src/docs/userguide/cpp.xml        |  188 -
 .../docs/src/docs/userguide/customPlugins.xml      |    2 +-
 .../docs/src/docs/userguide/customTasks.xml        |  142 +
 subprojects/docs/src/docs/userguide/depMngmt.xml   |  146 +-
 .../docs/src/docs/userguide/distributionPlugin.xml |  206 +-
 .../docs/src/docs/userguide/eclipsePlugin.xml      |    4 +-
 .../docs/src/docs/userguide/gradleDaemon.xml       |    4 +-
 .../docs/src/docs/userguide/gradleWrapper.xml      |   64 +-
 .../docs/src/docs/userguide/groovyPlugin.xml       |   70 +-
 .../docs/src/docs/userguide/groovyTutorial.xml     |    4 +-
 subprojects/docs/src/docs/userguide/ideSupport.xml |    2 +-
 subprojects/docs/src/docs/userguide/ideaPlugin.xml |    4 +-
 .../src/docs/userguide/img/jacocoHtmlReport.png    |  Bin 0 -> 103803 bytes
 .../docs/src/docs/userguide/initscripts.xml        |   19 +-
 .../docs/src/docs/userguide/jacocoPlugin.xml       |  248 ++
 .../userguide/javaLibraryDistributionPlugin.xml    |    3 +-
 subprojects/docs/src/docs/userguide/javaPlugin.xml |  142 +-
 .../docs/src/docs/userguide/javaTutorial.xml       |    8 +-
 .../docs/src/docs/userguide/jettyPlugin.xml        |   14 +
 subprojects/docs/src/docs/userguide/logging.xml    |    7 +-
 .../docs/src/docs/userguide/mavenPlugin.xml        |    2 +-
 .../docs/src/docs/userguide/multiproject.xml       |    4 +-
 .../docs/src/docs/userguide/nativeBinaries.xml     |  682 +++
 .../docs/src/docs/userguide/organizeBuildLogic.xml |    2 +-
 subprojects/docs/src/docs/userguide/plugins.xml    |    6 +-
 .../docs/src/docs/userguide/projectReports.xml     |   12 +-
 .../docs/src/docs/userguide/publishingIvy.xml      |   49 +-
 .../docs/src/docs/userguide/publishingMaven.xml    |   39 +-
 .../docs/src/docs/userguide/scalaPlugin.xml        |   35 +-
 .../docs/src/docs/userguide/sonarPlugin.xml        |   40 +-
 .../docs/src/docs/userguide/sonarRunnerPlugin.xml  |   10 +-
 .../docs/src/docs/userguide/standardPlugins.xml    |  251 +-
 subprojects/docs/src/docs/userguide/tasks.xml      |  142 +-
 .../docs/src/docs/userguide/thisAndThat.xml        |    5 +-
 subprojects/docs/src/docs/userguide/userguide.xml  |    8 +-
 subprojects/docs/src/docs/userguide/warPlugin.xml  |    2 +-
 .../docs/src/docs/userguide/workingWithFiles.xml   |   41 +-
 .../docs/src/docs/userguide/wrapperPlugin.xml      |   59 +
 .../src/docs/userguide/writingBuildScripts.xml     |    2 +-
 .../docs/src/samples/application/build.gradle      |    4 +
 .../src/main/java/org/gradle/sample/Main.java      |    6 +-
 .../docs/src/samples/buildDashboard/build.gradle   |    2 +-
 .../src/main/java/org/gradle/sample/Person.java    |   15 +
 .../docs/src/samples/codeQuality/build.gradle      |    2 +-
 .../docs/src/samples/cpp/dependencies/build.gradle |   63 -
 .../cpp/dependencies/lib/src/main/cpp/hello.cpp    |    5 -
 .../cpp/dependencies/lib/src/main/headers/hello.h  |    1 -
 subprojects/docs/src/samples/cpp/exe/build.gradle  |   11 -
 .../docs/src/samples/cpp/exewithlib/build.gradle   |   18 -
 .../src/samples/cpp/exewithlib/settings.gradle     |    1 -
 .../groovy/org/gradle/samples/ProductPlugin.groovy |    2 +-
 .../samples/customDistribution/plugin/build.gradle |    2 +-
 .../src/samples/customPlugin/plugin/build.gradle   |    2 +-
 .../samples/groovy/customizedLayout/build.gradle   |    2 +-
 .../samples/groovy/mixedJavaAndGroovy/build.gradle |    2 +-
 .../multiproject/groovycDetector/build.gradle      |    2 +-
 .../groovy/multiproject/testproject/build.gradle   |    2 +-
 .../main/groovy/org/gradle/GroovyJavaPerson.java   |    3 -
 .../src/main/groovy/org/gradle/GroovyPerson.groovy |    3 -
 .../src/main/java/org/gradle/JavaPerson.java       |    3 -
 .../src/test/groovy/org/gradle/VersionTest.groovy  |    4 +-
 .../src/samples/groovy/quickstart/build.gradle     |    2 +-
 .../src/test/groovy/org/gradle/PersonTest.groovy   |    2 +-
 .../descriptor-customization/build.gradle          |    6 +-
 .../ivy-publish/java-multi-project/build.gradle    |    2 +-
 .../ivy-publish/multiple-publications/build.gradle |   83 +
 .../multiple-publications/output/project1.ivy.xml  |   15 +
 .../output/project2-api.ivy.xml                    |   13 +
 .../output/project2-impl.ivy.xml                   |   16 +
 .../multiple-publications/settings.gradle          |    2 +
 .../samples/ivy-publish/quickstart/build.gradle    |    2 +-
 .../java/org/gradle/webservice/TestTestTest.java   |    3 -
 .../docs/src/samples/java/quickstart/build.gradle  |    8 +-
 .../samples/maven-publish/javaProject/build.gradle |    2 +-
 .../multiple-publications/build.gradle             |   69 +
 .../multiple-publications/output/project1.pom.xml  |   17 +
 .../output/project2-api.pom.xml                    |    9 +
 .../output/project2-impl.pom.xml                   |   23 +
 .../multiple-publications/settings.gradle          |    2 +
 .../maven-publish/pomCustomization/build.gradle    |    8 +-
 .../samples/maven-publish/quickstart/build.gradle  |    2 +-
 .../maven/pomGeneration/lib/compile-1.0.jar        |  Bin 0 -> 144 bytes
 .../pomGeneration/lib/providedCompile-1.0.jar      |  Bin 0 -> 144 bytes
 .../pomGeneration/lib/providedRuntime-1.0.zip      |  Bin 0 -> 144 bytes
 .../pomGeneration/lib/providedRuntime-util-1.0.war |  Bin 0 -> 144 bytes
 .../maven/pomGeneration/lib/runtime-1.0.jar        |  Bin 0 -> 144 bytes
 .../maven/pomGeneration/lib/testCompile-1.0.jar    |  Bin 0 -> 144 bytes
 .../maven/pomGeneration/lib/testRuntime-1.0.jar    |  Bin 0 -> 144 bytes
 .../multiProjectBuildSrc/buildSrc/build.gradle     |    2 +-
 .../samples/native-binaries/assembler/build.gradle |   62 +
 .../assembler/src/main/asm_i386_gcc/sum.s          |    6 +
 .../assembler/src/main/asm_i386_masm/sum.s         |   12 +
 .../native-binaries/assembler/src/main/c/main.c    |    8 +
 .../assembler/src/main/headers/sum.h               |    6 +
 .../src/samples/native-binaries/c/build.gradle     |   61 +
 .../samples/native-binaries/c/src/hello/c/hello.c  |    6 +
 .../native-binaries/c/src/hello/headers/hello.h    |    8 +
 .../c/src/main/c/main.c}                           |    0
 .../samples/native-binaries/cpp-exe/build.gradle   |   27 +
 .../native-binaries/cpp-exe/settings.gradle        |    1 +
 .../cpp-exe}/src/main/cpp/hello.cpp                |    0
 .../samples/native-binaries/cpp-lib/build.gradle   |   14 +
 .../native-binaries/cpp-lib/settings.gradle        |    1 +
 .../native-binaries/cpp-lib/src/main/cpp/hello.cpp |   14 +
 .../cpp-lib}/src/main/headers/hello.h              |    0
 .../src/samples/native-binaries/cpp/build.gradle   |   51 +
 .../native-binaries/cpp/src/hello/cpp/hello.cpp    |    6 +
 .../native-binaries/cpp/src/hello/headers/hello.h  |    7 +
 .../cpp}/src/main/cpp/main.cpp                     |    0
 .../src/samples/native-binaries/cunit/README.md    |    7 +
 .../src/samples/native-binaries/cunit/build.gradle |   47 +
 .../lib/cunit/2.1-2/include/CUnit/Automated.h      |   90 +
 .../cunit/lib/cunit/2.1-2/include/CUnit/Basic.h    |  113 +
 .../cunit/lib/cunit/2.1-2/include/CUnit/CUError.h  |  199 +
 .../cunit/lib/cunit/2.1-2/include/CUnit/CUnit.h    |  383 ++
 .../lib/cunit/2.1-2/include/CUnit/CUnit_intl.h     |   62 +
 .../cunit/lib/cunit/2.1-2/include/CUnit/Console.h  |   60 +
 .../cunit/lib/cunit/2.1-2/include/CUnit/MyMem.h    |  104 +
 .../cunit/lib/cunit/2.1-2/include/CUnit/TestDB.h   |  914 ++++
 .../cunit/lib/cunit/2.1-2/include/CUnit/TestRun.h  |  444 ++
 .../cunit/lib/cunit/2.1-2/include/CUnit/Util.h     |  158 +
 .../cunit/lib/cunit/2.1-2/lib/cygwin/cunit.lib     |  Bin 0 -> 81868 bytes
 .../cunit/lib/cunit/2.1-2/lib/linux/libcunit.a     |  Bin 0 -> 117622 bytes
 .../cunit/lib/cunit/2.1-2/lib/mingw/cunit.lib      |  Bin 0 -> 75800 bytes
 .../cunit/lib/cunit/2.1-2/lib/osx/libcunit.a       |  Bin 0 -> 85768 bytes
 .../cunit/lib/cunit/2.1-2/lib/vs2010/cunit.lib     |  Bin 0 -> 117792 bytes
 .../cunit/lib/cunit/2.1-2/lib/vs2013/cunit.lib     |  Bin 0 -> 116230 bytes
 .../native-binaries/cunit/src/operators/c/minus.c  |    5 +
 .../native-binaries/cunit/src/operators/c/plus.c   |    9 +
 .../cunit/src/operators/headers/operators.h        |    2 +
 .../src/operatorsTest/cunit/suite_operators.c      |   17 +
 .../cunit/src/operatorsTest/cunit/test_minus.c     |    8 +
 .../cunit/src/operatorsTest/cunit/test_plus.c      |    8 +
 .../src/operatorsTest/headers/test_operators.h     |    2 +
 .../native-binaries/custom-layout/build.gradle     |   62 +
 .../custom-layout/src/include/hello.h              |    7 +
 .../custom-layout/src/source/hello.c               |    6 +
 .../custom-layout/src/source/main.cpp              |    8 +
 .../samples/native-binaries/flavors/build.gradle   |   37 +
 .../flavors/src/exe}/cpp/main.cpp                  |    0
 .../native-binaries/flavors/src/lib/cpp/hello.cpp  |   10 +
 .../flavors/src/lib/headers/hello.h                |   10 +
 .../src/samples/native-binaries/idl/build.gradle   |   44 +
 .../samples/native-binaries/idl/src/main/c/main.c  |    6 +
 .../native-binaries/idl/src/main/idl/hello.idl     |   16 +
 .../native-binaries/multi-project/build.gradle     |   24 +
 .../multi-project}/exe/src/main/cpp/main.cpp       |    0
 .../multi-project}/lib/src/main/cpp/hello.cpp      |    0
 .../multi-project}/lib/src/main/headers/hello.h    |    0
 .../multi-project}/settings.gradle                 |    0
 .../native-binaries/objective-c/build.gradle       |   23 +
 .../objective-c/src/main/objc/main.m               |   10 +
 .../native-binaries/objective-cpp/build.gradle     |   23 +
 .../objective-cpp/src/main/objcpp/main.mm          |   14 +
 .../prebuilt/3rd-party-lib/boost_1_55_0/README.txt |    2 +
 .../3rd-party-lib/boost_1_55_0/boost/version.hpp   |   32 +
 .../prebuilt/3rd-party-lib/util/README.txt         |    2 +
 .../prebuilt/3rd-party-lib/util/build.gradle       |   25 +
 .../prebuilt/3rd-party-lib/util/settings.gradle    |    1 +
 .../3rd-party-lib/util/src/util/cpp/util.cpp       |   10 +
 .../3rd-party-lib/util/src/util/headers/util.h     |    7 +
 .../samples/native-binaries/prebuilt/build.gradle  |   45 +
 .../native-binaries/prebuilt/src/main/cpp/main.cpp |    9 +
 .../native-binaries/tool-chains/build.gradle       |   29 +
 .../tool-chains/src/main/cpp/main.cpp              |   18 +
 .../samples/native-binaries/variants/build.gradle  |   76 +
 .../variants/src/hello/cpp/hello.cpp               |   10 +
 .../variants/src/hello/headers/hello.h             |   10 +
 .../variants}/src/main/cpp/main.cpp                |    0
 .../native-binaries/visual-studio/build.gradle     |   71 +
 .../visual-studio/src/hello/cpp/hello.cpp          |    6 +
 .../visual-studio/src/hello/headers/hello.h        |    7 +
 .../visual-studio}/src/main/cpp/main.cpp           |    0
 .../build-resource-only-dll.gradle                 |   27 +
 .../native-binaries/windows-resources/build.gradle |   33 +
 .../windows-resources/src/hello/cpp/hello.cpp      |   19 +
 .../windows-resources/src/hello/headers/hello.h    |    7 +
 .../src/hello/headers/resources.h                  |    1 +
 .../windows-resources/src/hello/rc/resources.rc    |    6 +
 .../windows-resources/src/main/cpp/main.cpp        |    6 +
 subprojects/docs/src/samples/osgi/build.gradle     |    4 +-
 .../main/groovy/org/gradle/GradleActivator.groovy  |    5 -
 .../samples/sonarRunner/multiProject/build.gradle  |    4 +-
 .../samples/sonarRunner/quickstart/build.gradle    |    4 +-
 .../src/main => src/main/cpp/library}/cpp/main.cpp |    0
 .../src/samples/testing/filtering/build.gradle     |   40 +
 .../filtering/src/test/java/SomeIntegTest.java     |    6 +
 .../filtering/src/test/java/SomeOtherTest.java     |    6 +
 .../testing/jacoco/application/build.gradle        |   35 +
 .../src/main/java/org/gradle/MyMain.java           |   30 +
 .../samples/testing/jacoco/quickstart/build.gradle |   58 +
 .../src/main/java/org/gradle/Person.java           |   24 +
 .../src/test/java/org/gradle/PersonTest.java       |   41 +
 .../samples/testing/junit/categories/build.gradle  |   18 +
 .../org/gradle/junit/CategorizedJUnitTest.java     |   35 +
 .../src/test/java/org/gradle/junit/CategoryA.java  |   21 +
 .../src/test/java/org/gradle/junit/CategoryB.java  |   21 +
 .../java/org/gradle/junit/SimpleJUnitTest.java     |   27 +
 .../src/samples/testing/testReport/build.gradle    |    2 +-
 .../src/samples/testing/testng/groups/build.gradle |   18 +
 .../org/gradle/testng/SimpleIntegrationTest.java   |   29 +
 .../java/org/gradle/testng/SimpleUnitTest.java     |   29 +
 .../src/main/java/org/gradle/testng/User.java      |    3 -
 .../src/main/java/org/gradle/testng/UserImpl.java  |    3 -
 .../test/java/org/gradle/testng/UserImplTest.java  |    3 -
 .../toolingApi/customModel/plugin/build.gradle     |   20 +
 .../java/org/gradle/sample/plugin/CustomModel.java |   14 +
 .../org/gradle/sample/plugin/CustomPlugin.java     |   39 +
 .../org/gradle/sample/plugin/DefaultModel.java     |   20 +
 .../gradle-plugins/custom-plugin.properties        |    1 +
 .../src/samples/toolingApi/customModel/readme.xml  |    3 +
 .../customModel/sampleBuild/build.gradle           |   10 +
 .../customModel/sampleBuild/settings.gradle        |   16 +
 .../toolingApi/customModel/tooling/build.gradle    |   22 +
 .../src/main/java/org/gradle/sample/Main.java      |   39 +
 .../src/samples/toolingApi/eclipse/build.gradle    |    2 +-
 .../src/main/java/org/gradle/sample/Main.java      |    5 +-
 .../docs/src/samples/toolingApi/idea/build.gradle  |    2 +-
 .../idea/src/main/java/org/gradle/sample/Main.java |    3 +-
 .../docs/src/samples/toolingApi/model/build.gradle |    2 +-
 .../src/main/java/org/gradle/sample/Main.java      |    5 +-
 .../src/samples/toolingApi/runBuild/build.gradle   |    2 +-
 .../src/main/java/org/gradle/sample/Main.java      |    3 -
 .../ant/useAntType/libs/test.jar}                  |    0
 .../artifacts/componentMetadata/build.gradle       |   41 +
 .../repo/air.birds/albatros/1.9/albatros-1.9.jar}  |    0
 .../repo/air.birds/albatros/1.9/ivy-1.9.xml        |    9 +
 .../repo/air.birds/albatros/2.0/albatros-2.0.jar}  |    0
 .../repo/air.birds/albatros/2.0/ivy-2.0.xml        |    9 +
 .../repo/sea.fish/tuna/1.3/ivy-1.3.xml             |    9 +
 .../repo/sea.fish/tuna/1.3/tuna-1.3.jar}           |    0
 .../repo/sea.fish/tuna/1.4/ivy-1.4.xml             |    9 +
 .../repo/sea.fish/tuna/1.4/tuna-1.4.jar}           |    0
 .../repo/sea.fish/tuna/1.5/ivy-1.5.xml             |    9 +
 .../repo/sea.fish/tuna/1.5/tuna-1.5.jar}           |    0
 .../repo/air.birds/albatros-1.0.jar}               |    0
 .../repo/sea.fish/herring-1.0.jar}                 |    0
 .../repo/sea.fish/shark-1.0.jar}                   |    0
 .../repo/sea.fish/tuna-1.0.jar}                    |    0
 .../repo/sea.mammals/orca-1.0.jar}                 |    0
 .../repo/sea.mammals/seal-1.0.jar}                 |    0
 .../repo/sea.mammals/seal-2.0.jar}                 |    0
 .../artifacts/defineRepository/build.gradle        |    6 +
 .../service-1.0-jdk14.jar}                         |    0
 .../service-1.0-jdk15.jar}                         |    0
 .../repo/org.gradle.test.excludes/api-1.0.jar}     |    0
 .../repo/org.gradle.test.excludes/commons-1.0.jar} |    0
 .../org.gradle.test.excludes/other-api-1.0.jar}    |    0
 .../repo/org.gradle.test.excludes/reports-1.0.jar} |    0
 .../repo/org.gradle.test.excludes/shared-1.0.jar}  |    0
 .../artifacts/externalDependencies/build.gradle    |   14 +-
 .../artifacts/resolutionStrategy/build.gradle      |    2 +-
 .../userguide/artifacts/uploading/build.gradle     |    4 +-
 .../samples/userguide/distribution/build.gradle    |   36 +-
 .../src/samples/userguide/files/copy/build.gradle  |   15 +
 .../userguide/files/copy/src/main/assets.zip       |  Bin 0 -> 332 bytes
 .../userguide/groovy/groovyDependency/build.gradle |    8 +-
 .../userguide/initScripts/customLogger/init.gradle |    3 +
 .../userguide/initScripts/plugins/build.gradle     |   27 +
 .../userguide/initScripts/plugins/init.gradle      |   34 +
 .../firstMessages/messages/build.gradle            |    1 +
 .../firstMessages/messages/consumer/build.gradle   |    3 +-
 .../messages/build.gradle                          |    1 +
 .../messages/consumer/build.gradle                 |    2 +-
 .../messages/build.gradle                          |    1 +
 .../messages/consumer/build.gradle                 |    3 +-
 .../messages/build.gradle                          |    1 +
 .../messages/consumer/build.gradle                 |    2 +-
 .../messagesHack/messages/build.gradle             |    1 +
 .../messagesHack/messages/consumer/build.gradle    |    3 +-
 .../messagesTaskDependencies/messages/build.gradle |    1 +
 .../messages/consumer/build.gradle                 |    3 +-
 .../messagesWithDependencies/messages/build.gradle |    1 +
 .../messages/consumer/build.gradle                 |    3 +-
 .../multiproject/standardLayouts/settings.gradle   |    2 +-
 .../userguide/organizeBuildLogic/build.gradle      |    2 +-
 .../tasks/addToTaskContainer/build.gradle          |    4 +-
 .../tasks/configureUsingConfigure/build.gradle     |    7 -
 .../tasks/configureUsingLiterateStyle/build.gradle |    4 -
 .../tasks/customTaskWithProperty/build.gradle      |    2 +-
 .../tasks/defineAndConfigure/build.gradle          |    2 +-
 .../userguide/tasks/finalizers/build.gradle        |    8 +
 .../tasks/finalizersWithFailure/build.gradle       |    9 +
 .../userguide/tasks/incrementalTask/build.gradle   |   66 +
 .../userguide/tasks/mustRunAfter/build.gradle      |    7 +
 .../userguide/tasks/shouldRunAfter/build.gradle    |    7 +
 .../tasks/shouldRunAfterWithCycle/build.gradle     |   12 +
 .../tutorial/groovyWithFlatDir/build.gradle        |   10 +-
 .../userguide/tutorial/projectReports/build.gradle |    2 +-
 .../tutorial/properties/gradle.properties          |    2 +-
 .../userguide/wrapper/customized/build.gradle      |    4 -
 .../samples/userguide/wrapper/simple/build.gradle  |    2 +-
 .../configurationHandlingAllFiles.out              |    4 +-
 .../samples/userguideOutput/customStatusScheme.out |    1 +
 .../userguideOutput/dependencyInsightReport.out    |    4 +-
 .../userguideOutput/dependencyListReport.out       |    6 +-
 .../userguideOutput/externalDependencies.out       |    4 +-
 .../incrementalTaskChangedProperty.out             |    4 +
 .../userguideOutput/incrementalTaskFirstRun.out    |    4 +
 .../incrementalTaskNoChange.out}                   |    0
 .../incrementalTaskRemovedInput.out                |    2 +
 .../incrementalTaskRemovedOutput.out               |    4 +
 .../incrementalTaskUpdatedInputs.out               |    3 +
 .../src/samples/userguideOutput/latestSelector.out |    3 +
 .../src/samples/userguideOutput/mustRunAfter.out   |    2 +
 .../userguideOutput/mustRunAfterSingleTask.out     |    1 +
 .../publishingIvyGenerateDescriptor.out            |    2 +-
 .../publishingIvyPublishLifecycle.out              |    2 +-
 .../userguideOutput/publishingIvyPublishSingle.out |    2 +-
 .../src/samples/userguideOutput/shouldRunAfter.out |    2 +
 .../userguideOutput/shouldRunAfterWithCycle.out    |    3 +
 .../src/samples/userguideOutput/taskFinalizers.out |    2 +
 .../userguideOutput/taskFinalizersWithFailure.out  |    2 +
 .../docs/src/samples/userguideOutput/taskHelp.out  |   12 +
 .../samples/userguideOutput/taskListAllReport.out  |    5 +
 .../src/samples/userguideOutput/taskListReport.out |    5 +
 .../userguideOutput/usePluginsInInitScripts.out    |    1 +
 .../samples/webApplication/customised/readme.xml   |    3 -
 .../customized/additionalLibs/additional-1.0.jar   |  Bin 0 -> 349 bytes
 .../{customised => customized}/build.gradle        |    0
 .../webApplication/customized/lib/compile-1.0.jar  |  Bin 0 -> 521 bytes
 .../customized/lib/compile-transitive-1.0.jar      |  Bin 0 -> 561 bytes
 .../webApplication/customized/lib/otherLib-1.0.jar |  Bin 0 -> 349 bytes
 .../customized/lib/providedCompile-1.0.jar         |  Bin 0 -> 552 bytes
 .../lib/providedCompile-transitive-1.0.jar         |  Bin 0 -> 577 bytes
 .../customized/lib/providedRuntime-1.0.jar         |  Bin 0 -> 553 bytes
 .../webApplication/customized/lib/runtime-1.0.jar  |  Bin 0 -> 1581 bytes
 .../samples/webApplication/customized/readme.xml   |    3 +
 .../src/additionalWebInf/additional.xml            |    0
 .../src/main/java/org/gradle/HelloServlet.java     |    0
 .../src/main/java/org/gradle/MyClass.java          |    0
 .../src/main/webapp/WEB-INF/webapp.xml             |    0
 .../src/main/webapp/webapp.html                    |    0
 .../src/rootContent/root.txt                       |    0
 .../{customised => customized}/src/someWeb.xml     |    0
 .../src/test/java/org/gradle/MyClassTest.java      |    0
 .../releasenotes/FunctionalReleaseNotesTest.groovy |    6 +-
 .../docs/src/transforms/release-notes.gradle       |   36 +-
 subprojects/ear/ear.gradle                         |    1 -
 .../plugins/ear/EarPluginIntegrationTest.groovy    |  111 +-
 .../main/groovy/org/gradle/plugins/ear/Ear.groovy  |   34 +-
 .../groovy/org/gradle/plugins/ear/EarPlugin.java   |    8 +-
 .../ear/descriptor/DeploymentDescriptor.java       |    2 -
 .../gradle/plugins/ear/descriptor/EarModule.java   |    2 -
 .../plugins/ear/descriptor/EarSecurityRole.java    |    2 -
 .../plugins/ear/descriptor/EarWebModule.java       |    2 -
 .../internal/DefaultDeploymentDescriptor.groovy    |    3 -
 .../descriptor/internal/DefaultEarModule.groovy    |    3 -
 .../internal/DefaultEarSecurityRole.groovy         |    3 -
 .../descriptor/internal/DefaultEarWebModule.groovy |    3 -
 .../org/gradle/plugins/ear/EarPluginTest.groovy    |   18 +-
 .../groovy/org/gradle/plugins/ear/EarTest.groovy   |    8 +-
 .../DefaultDeploymentDescriptorTest.groovy         |    3 -
 subprojects/ide/ide.gradle                         |    7 +-
 .../plugins/ide/AbstractIdeIntegrationSpec.groovy  |   33 +
 ...ractSourcesAndJavadocJarsIntegrationTest.groovy |  282 ++
 .../ide/AutoTestedSamplesIntegrationTest.groovy    |    3 -
 .../ide/eclipse/EclipseClasspathFixture.groovy     |   24 +-
 .../eclipse/EclipseClasspathIntegrationTest.groovy |    3 -
 ...ClasspathRemoteResolutionIntegrationTest.groovy |   71 -
 .../EclipseClasspathResolveIntegrationTest.groovy  |  108 -
 .../ide/eclipse/EclipseEarIntegrationTest.groovy   |    3 -
 .../EclipseMultiModuleIntegrationTest.groovy       |    3 -
 .../eclipse/EclipseProjectIntegrationTest.groovy   |    3 -
 ...ipseSourcesAndJavadocJarsIntegrationTest.groovy |   48 +
 .../eclipse/EclipseWtpModelIntegrationTest.groovy  |    3 -
 .../plugins/ide/idea/IdeaIntegrationTest.groovy    |   77 +-
 .../ide/idea/IdeaModuleIntegrationTest.groovy      |  198 +
 .../ide/idea/IdeaMultiModuleIntegrationTest.groovy |   53 +-
 ...IdeaSourcesAndJavadocJarsIntegrationTest.groovy |   55 +
 .../ide/idea/IdeaWorkspaceIntegrationTest.groovy   |    3 -
 .../expectedFiles/apiClasspath.xml                 |    6 +-
 .../expectedFiles/groovyprojectClasspath.xml       |    6 +-
 .../expectedFiles/webAppWithVarsClasspath.xml      |    8 +-
 .../expectedFiles/webAppWithVarsWtpComponent.xml   |    2 +-
 .../expectedFiles/webserviceClasspath.xml          |   12 +-
 .../expectedFiles/webserviceWtpComponent.xml       |    2 +-
 .../canCreateAndDeleteMetaData/master/build.gradle |    6 +-
 .../expectedFiles/project1/project1.iml.xml        |   32 -
 .../expectedFiles/project2/project2.iml.xml        |   32 -
 .../expectedFiles/project3/project3.iml.xml        |   32 -
 .../expectedFiles/root.ipr.xml                     |  123 -
 .../expectedFiles/api/api.iml.xml                  |    8 +-
 .../expectedFiles/webservice/webservice.iml.xml    |   24 +-
 .../expectedFiles/root.iml.xml                     |    8 +-
 .../plugins/ide/api/FileContentMerger.groovy       |    2 -
 .../org/gradle/plugins/ide/api/GeneratorTask.java  |    7 +-
 .../plugins/ide/api/XmlFileContentMerger.groovy    |    4 +-
 .../plugins/ide/eclipse/EclipsePlugin.groovy       |    6 +-
 .../plugins/ide/eclipse/EclipseWtpPlugin.groovy    |    9 +-
 .../ide/eclipse/GenerateEclipseClasspath.groovy    |    2 -
 .../ide/eclipse/GenerateEclipseProject.groovy      |    2 -
 .../ide/eclipse/GenerateEclipseWtpComponent.groovy |    2 -
 .../ide/eclipse/GenerateEclipseWtpFacet.groovy     |    2 -
 .../ide/eclipse/internal/EclipseNameDeduper.groovy |    3 -
 .../eclipse/internal/LinkedResourcesCreator.groovy |    5 +-
 .../eclipse/model/AbstractClasspathEntry.groovy    |    3 -
 .../ide/eclipse/model/AbstractLibrary.groovy       |    3 -
 .../plugins/ide/eclipse/model/AccessRule.groovy    |    3 -
 .../plugins/ide/eclipse/model/BuildCommand.groovy  |    3 -
 .../plugins/ide/eclipse/model/Classpath.groovy     |    4 +-
 .../plugins/ide/eclipse/model/ClasspathEntry.java  |    2 -
 .../plugins/ide/eclipse/model/Container.groovy     |    3 -
 .../ide/eclipse/model/EclipseClasspath.groovy      |    4 +-
 .../ide/eclipse/model/EclipseDomainModel.groovy    |    2 -
 .../plugins/ide/eclipse/model/EclipseJdt.groovy    |    2 -
 .../plugins/ide/eclipse/model/EclipseModel.groovy  |    2 -
 .../ide/eclipse/model/EclipseProject.groovy        |    8 +-
 .../plugins/ide/eclipse/model/EclipseWtp.groovy    |    2 -
 .../ide/eclipse/model/EclipseWtpComponent.groovy   |    8 +-
 .../ide/eclipse/model/EclipseWtpFacet.groovy       |    6 +-
 .../gradle/plugins/ide/eclipse/model/Facet.groovy  |    3 -
 .../plugins/ide/eclipse/model/Library.groovy       |    3 -
 .../gradle/plugins/ide/eclipse/model/Link.groovy   |    3 -
 .../gradle/plugins/ide/eclipse/model/Output.groovy |    3 -
 .../plugins/ide/eclipse/model/Project.groovy       |    2 -
 .../ide/eclipse/model/ProjectDependency.groovy     |    3 -
 .../plugins/ide/eclipse/model/SourceFolder.groovy  |    2 -
 .../plugins/ide/eclipse/model/Variable.groovy      |    3 -
 .../ide/eclipse/model/WbDependentModule.groovy     |    3 -
 .../plugins/ide/eclipse/model/WbProperty.groovy    |    3 -
 .../plugins/ide/eclipse/model/WbResource.groovy    |    3 -
 .../plugins/ide/eclipse/model/WtpComponent.groovy  |    2 -
 .../plugins/ide/eclipse/model/WtpFacet.groovy      |    2 -
 .../model/internal/ClassFoldersCreator.groovy      |    2 -
 .../eclipse/model/internal/ClasspathFactory.groovy |   17 +-
 .../model/internal/ExportedEntriesUpdater.groovy   |    3 -
 .../ide/eclipse/model/internal/PathUtil.groovy     |    3 -
 .../model/internal/ProjectDependencyBuilder.groovy |    3 -
 .../model/internal/SourceFoldersCreator.groovy     |    3 -
 .../model/internal/WtpComponentFactory.groovy      |    3 -
 .../plugins/ide/idea/GenerateIdeaModule.groovy     |    2 -
 .../plugins/ide/idea/GenerateIdeaProject.groovy    |    2 -
 .../plugins/ide/idea/GenerateIdeaWorkspace.groovy  |    2 -
 .../org/gradle/plugins/ide/idea/IdeaPlugin.groovy  |   22 +-
 .../ide/idea/internal/IdeaNameDeduper.groovy       |    3 -
 .../ide/idea/internal/IdeaScalaConfigurer.groovy   |   13 +-
 .../gradle/plugins/ide/idea/model/Dependency.java  |    2 -
 .../ide/idea/model/IdeaLanguageLevel.groovy        |    2 -
 .../gradle/plugins/ide/idea/model/IdeaModel.groovy |    1 -
 .../plugins/ide/idea/model/IdeaModule.groovy       |   10 +-
 .../plugins/ide/idea/model/IdeaModuleIml.groovy    |    2 -
 .../plugins/ide/idea/model/IdeaProject.groovy      |    6 +-
 .../plugins/ide/idea/model/IdeaWorkspace.groovy    |    6 +-
 .../plugins/ide/idea/model/JarDirectory.groovy     |    2 -
 .../org/gradle/plugins/ide/idea/model/Jdk.groovy   |    2 -
 .../gradle/plugins/ide/idea/model/Module.groovy    |    2 -
 .../plugins/ide/idea/model/ModuleDependency.groovy |    4 +-
 .../plugins/ide/idea/model/ModuleLibrary.groovy    |    4 +-
 .../org/gradle/plugins/ide/idea/model/Path.groovy  |    2 -
 .../gradle/plugins/ide/idea/model/Project.groovy   |    2 -
 .../gradle/plugins/ide/idea/model/Workspace.groovy |    2 -
 .../idea/model/internal/GeneratedIdeaScope.java    |   40 +
 .../model/internal/IdeaDependenciesProvider.groovy |   87 -
 .../model/internal/IdeaDependenciesProvider.java   |  267 ++
 .../idea/model/internal/IdeaScopeMappingRule.java  |   43 +
 .../model/internal/ModuleDependencyBuilder.groovy  |   34 -
 .../model/internal/ModuleDependencyBuilder.java    |   32 +
 .../ide/internal/IdeDependenciesExtractor.groovy   |  177 +-
 .../gradle/plugins/ide/internal/IdePlugin.groovy   |    2 +-
 .../internal/JavadocAndSourcesDownloader.groovy    |  125 -
 .../internal/configurer/DeduplicationTarget.groovy |    3 -
 .../internal/configurer/ModuleNameDeduper.groovy   |    1 -
 .../ide/internal/configurer/ProjectDeduper.groovy  |    3 -
 .../internal/tooling/BasicIdeaModelBuilder.java    |   39 +
 .../internal/tooling/BuildInvocationsBuilder.java  |   92 +
 .../ide/internal/tooling/EclipseModelBuilder.java  |  156 +
 .../ide/internal/tooling/GradleBuildBuilder.java   |   54 +
 .../ide/internal/tooling/GradleProjectBuilder.java |   84 +
 .../ide/internal/tooling/IdeaModelBuilder.java     |  143 +
 .../ide/internal/tooling/PublicationsBuilder.java  |   57 +
 .../plugins/ide/internal/tooling/TasksFactory.java |   46 +
 .../tooling/ToolingRegistrationAction.java         |   39 +
 .../eclipse/DefaultEclipseExternalDependency.java  |   54 +
 .../eclipse/DefaultEclipseLinkedResource.java      |   52 +
 .../tooling/eclipse/DefaultEclipseProject.java     |  135 +
 .../eclipse/DefaultEclipseProjectDependency.java   |   44 +
 .../eclipse/DefaultEclipseSourceDirectory.java     |   44 +
 .../tooling/eclipse/DefaultEclipseTask.java        |   56 +
 .../tooling/idea/DefaultIdeaCompilerOutput.java    |   65 +
 .../tooling/idea/DefaultIdeaContentRoot.java       |   80 +
 .../tooling/idea/DefaultIdeaDependency.java        |   22 +
 .../tooling/idea/DefaultIdeaDependencyScope.java   |   64 +
 .../tooling/idea/DefaultIdeaLanguageLevel.java     |   82 +
 .../internal/tooling/idea/DefaultIdeaModule.java   |  120 +
 .../tooling/idea/DefaultIdeaModuleDependency.java  |   61 +
 .../internal/tooling/idea/DefaultIdeaProject.java  |  107 +
 .../DefaultIdeaSingleEntryLibraryDependency.java   |   97 +
 .../tooling/idea/DefaultIdeaSourceDirectory.java   |   43 +
 .../internal/idea/DefaultIdeaModuleDependency.java |   27 +
 .../DefaultIdeaSingleEntryLibraryDependency.java   |   27 +
 .../internal/provider/BasicIdeaModelBuilder.java   |   36 -
 .../internal/provider/BuildModelAction.java        |   88 -
 .../tooling/internal/provider/BuildsModel.java     |   28 -
 .../internal/provider/EclipseModelBuilder.java     |  162 -
 .../internal/provider/FileOutcomeIdentifier.java   |   41 -
 .../internal/provider/GradleProjectBuilder.java    |   80 -
 .../internal/provider/IdeaModelBuilder.java        |  144 -
 .../internal/provider/NullResultBuilder.java       |   30 -
 .../provider/ProjectOutcomesModelBuilder.java      |   71 -
 ...blishArtifactToFileBuildOutcomeTransformer.java |   98 -
 .../tooling/internal/provider/TasksFactory.java    |   46 -
 .../resolver/DefaultIdeDependencyResolver.java     |  249 ++
 .../internal/resolver/IdeDependencyResolver.java   |   62 +
 .../ide/internal/resolver/model/IdeDependency.java |   31 +
 .../internal/resolver/model/IdeDependencyKey.java  |  157 +
 .../model/IdeExtendedRepoFileDependency.java       |   46 +
 .../resolver/model/IdeLocalFileDependency.java     |   34 +
 .../resolver/model/IdeProjectDependency.java       |   33 +
 .../resolver/model/IdeRepoFileDependency.java      |   44 +
 .../model/UnresolvedIdeRepoFileDependency.java     |   37 +
 .../ExternalModuleDependencyTranslator.java        |   27 +
 ...le.configuration.project.ProjectConfigureAction |    1 +
 .../plugins/ide/eclipse/EclipsePluginTest.groovy   |    7 +-
 .../ide/eclipse/EclipseWtpPluginTest.groovy        |    7 +-
 .../eclipse/GenerateEclipseClasspathTest.groovy    |    3 -
 .../eclipse/GenerateEclipseWtpComponentTest.groovy |    3 -
 .../ide/eclipse/GenerateEclipseWtpFacetTest.groovy |    3 -
 .../plugins/ide/eclipse/model/ClasspathTest.groovy |    3 -
 .../plugins/ide/eclipse/model/ContainerTest.groovy |    3 -
 .../ide/eclipse/model/EclipseModelTest.groovy      |    3 -
 .../ide/eclipse/model/EclipseProjectTest.groovy    |    3 -
 .../plugins/ide/eclipse/model/FacetTest.groovy     |    7 +-
 .../plugins/ide/eclipse/model/LibraryTest.groovy   |    3 -
 .../plugins/ide/eclipse/model/OutputTest.groovy    |    3 -
 .../ide/eclipse/model/ProjectDependencyTest.groovy |    3 -
 .../plugins/ide/eclipse/model/ProjectTest.groovy   |    3 -
 .../ide/eclipse/model/SourceFolderTest.groovy      |    3 -
 .../plugins/ide/eclipse/model/VariableTest.groovy  |    3 -
 .../ide/eclipse/model/WbDependentModuleTest.groovy |    3 -
 .../ide/eclipse/model/WbPropertyTest.groovy        |    3 -
 .../ide/eclipse/model/WbResourceTest.groovy        |    3 -
 .../ide/eclipse/model/WtpComponentTest.groovy      |    3 -
 .../plugins/ide/eclipse/model/WtpFacetTest.groovy  |    3 -
 .../internal/ProjectDependencyBuilderTest.groovy   |    7 +-
 .../ide/idea/ GenerateIdeaModuleTest.groovy        |   11 +-
 .../gradle/plugins/ide/idea/IdeaPluginTest.groovy  |   35 +-
 .../ide/idea/model/IdeaLanguageLevelTest.groovy    |    3 -
 .../ide/idea/model/ModuleDependencyTest.groovy     |    3 -
 .../ide/idea/model/ModuleLibraryTest.groovy        |    3 -
 .../plugins/ide/idea/model/ModuleTest.groovy       |    3 -
 .../ide/idea/model/ProjectLibraryTest.groovy       |    7 +-
 .../plugins/ide/idea/model/ProjectTest.groovy      |    3 -
 .../internal/IdeaDependenciesProviderTest.groovy   |  208 +
 .../internal/ModuleDependencyBuilderTest.groovy    |    7 +-
 .../plugins/ide/internal/GeneratorTaskTest.groovy  |    4 +-
 .../plugins/ide/internal/IdePluginTest.groovy      |    4 +-
 .../configurer/DeduplicationTargetTest.groovy      |   15 +-
 .../configurer/ModuleNameDeduperTest.groovy        |    3 -
 .../internal/configurer/ProjectDeduperTest.groovy  |   11 +-
 .../tooling/BuildInvocationsBuilderTest.groovy     |   79 +
 .../internal/tooling/GradleBuildBuilderTest.groovy |   46 +
 .../tooling/GradleProjectBuilderTest.groovy        |   42 +
 .../tooling/ProjectPublicationsBuilderTest.groovy  |   59 +
 .../ide/internal/tooling/TasksFactoryTest.groovy   |   52 +
 .../eclipse/DefaultEclipseProjectTest.groovy       |   27 +
 ...rtifactToFileBuildOutcomeTransformerTest.groovy |   86 -
 .../internal/provider/TasksFactoryTest.groovy      |   52 -
 subprojects/integ-test/integ-test.gradle           |    8 +-
 ...kCommandLineConfigurationIntegrationSpec.groovy |  252 --
 .../org/gradle/debug/GradleBuildRunner.groovy      |    2 -
 .../org/gradle/debug/GradleRunConfiguration.groovy |    2 -
 .../integtests/AntProjectIntegrationTest.groovy    |   63 +
 .../integtests/ApplicationIntegrationSpec.groovy   |    2 +-
 .../integtests/ApplicationIntegrationTest.groovy   |  146 +
 .../BuildAggregationIntegrationTest.groovy         |   26 +-
 .../BuildScriptClasspathIntegrationTest.java       |    3 +-
 .../BuildScriptErrorIntegrationTest.java           |  108 -
 .../BuildScriptExecutionIntegrationTest.groovy     |    1 -
 .../integtests/CacheProjectIntegrationTest.groovy  |   23 +-
 .../integtests/CommandLineIntegrationTest.groovy   |    9 +-
 .../integtests/CustomPluginIntegrationTest.groovy  |    4 +-
 .../DistributionLocatorIntegrationTest.groovy      |    8 +-
 .../ExternalScriptErrorIntegrationTest.groovy      |   93 -
 .../ExternalScriptExecutionIntegrationTest.groovy  |    3 -
 ...ementalGroovyProjectBuildIntegrationTest.groovy |    2 +-
 .../IncrementalTasksIntegrationTest.groovy         |  343 ++
 .../IncrementalTestIntegrationTest.groovy          |   94 -
 .../integtests/InitScriptErrorIntegrationTest.java |   51 -
 .../InitScriptExecutionIntegrationTest.groovy      |    1 -
 .../integtests/JavaProjectIntegrationTest.groovy   |   10 +-
 .../MixedNativeAndJvmProjectIntegrationTest.groovy |   41 +
 .../MultiProjectDependencyIntegrationTest.groovy   |   12 +-
 .../OsgiProjectSampleIntegrationTest.groovy        |    5 +-
 .../ParallelProjectExecutionIntegrationTest.groovy |   59 +-
 .../PluginCrossVersionIntegrationTest.groovy       |   72 -
 .../integtests/ProfilingIntegrationTest.groovy     |   41 -
 .../integtests/ProjectLayoutIntegrationTest.groovy |   21 +-
 .../SettingsScriptErrorIntegrationTest.java        |   39 -
 .../SettingsScriptExecutionIntegrationTest.groovy  |    1 -
 .../TaskDefinitionIntegrationSpec.groovy           |   30 +
 .../TaskErrorExecutionIntegrationTest.groovy       |   35 +-
 .../integtests/TaskExecutionIntegrationTest.groovy |  435 ++
 .../integtests/TaskExecutionIntegrationTest.java   |  180 -
 ...ssingBinaryCompatibilityCrossVersionSpec.groovy |   81 +
 .../integtests/WaterProjectIntegrationTest.groovy  |    3 -
 .../integtests/WebProjectIntegrationTest.java      |    2 +-
 .../BuildEnvironmentIntegrationTest.groovy         |    3 -
 .../fixture/TempDirIsUniquePerTestSpec.groovy      |    3 -
 .../logging/LoggingIntegrationTest.groovy          |   38 +-
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |  334 --
 .../IvyJavaProjectPublishIntegrationTest.groovy    |   63 -
 .../ivy/IvyLocalPublishIntegrationTest.groovy      |  116 -
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |   11 +-
 .../IvySingleProjectPublishIntegrationTest.groovy  |  128 -
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   37 -
 .../maven/MavenPublishIntegrationTest.groovy       |  361 --
 ...SamplesMavenPomGenerationIntegrationTest.groovy |  109 -
 .../SamplesMavenQuickstartIntegrationTest.groovy   |   82 -
 .../AutoTestedSamplesCoreIntegrationTest.groovy    |    3 -
 .../AutoTestedSamplesPluginsIntegrationTest.groovy |    3 -
 .../samples/JUnitSamplesIntegrationTest.groovy     |   42 +
 .../samples/SamplesAntlrIntegrationTest.groovy     |    4 +-
 .../SamplesCodeQualityIntegrationTest.groovy       |    3 -
 .../SamplesCustomPluginIntegrationTest.groovy      |    4 +-
 ...lesExcludesAndClassifiersIntegrationTest.groovy |    3 -
 ...lesGroovyCustomizedLayoutIntegrationTest.groovy |    4 +-
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |    3 -
 .../SamplesGroovyQuickstartIntegrationTest.groovy  |    4 +-
 .../samples/SamplesJUnitIntegrationTest.groovy     |   44 +
 .../samples/SamplesJavaBaseIntegrationTest.groovy  |    8 +-
 ...mplesJavaCustomizedLayoutIntegrationTest.groovy |    8 +-
 .../SamplesJavaMultiProjectIntegrationTest.groovy  |   11 +-
 ...esJavaProjectWithIntTestsIntegrationTest.groovy |    7 +-
 .../SamplesJavaQuickstartIntegrationTest.groovy    |    7 +-
 ...SamplesMixedJavaAndGroovyIntegrationTest.groovy |    4 +-
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   74 -
 .../SamplesRepositoriesIntegrationTest.groovy      |    3 -
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   50 -
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   66 -
 .../SamplesWebProjectIntegrationTest.groovy        |   11 +-
 .../SamplesWebQuickstartIntegrationTest.groovy     |    5 +-
 .../canExecuteCommands/canExecuteCommands.gradle   |    4 +-
 .../canExecuteJava/canExecuteJava.gradle           |    4 +-
 .../LoggingIntegrationTest/deprecated/build.gradle |    7 -
 .../logging/project1/build.gradle                  |    8 +-
 .../internal-integ-testing.gradle                  |   55 +-
 .../fixtures/AbstractAutoTestedSamplesTest.groovy  |    5 +-
 .../fixtures/AbstractCompatibilityTestRunner.java  |   22 +-
 .../fixtures/AbstractIntegrationSpec.groovy        |   34 +-
 .../fixtures/AbstractMultiTestRunner.java          |  329 +-
 .../fixtures/AutoTestedSamplesUtil.groovy          |    3 -
 .../integtests/fixtures/AvailableJavaHomes.java    |   41 +-
 .../fixtures/CrossVersionTestRunner.groovy         |    2 +-
 .../integtests/fixtures/IntegrationTestHint.java   |    3 -
 .../fixtures/KillProcessAvailability.groovy        |    3 -
 .../fixtures/MultiVersionSpecRunner.groovy         |   12 +-
 .../gradle/integtests/fixtures/TargetCoverage.java |   30 +
 .../fixtures/TestNGExecutionResult.groovy          |   49 +-
 .../fixtures/UserGuideSamplesRunner.groovy         |   14 +-
 .../fixtures/executer/AbstractGradleExecuter.java  |   25 +-
 .../fixtures/executer/AnyOrderOutputMatcher.groovy |   51 +
 .../fixtures/executer/DaemonGradleExecuter.java    |    3 +-
 .../executer/DefaultGradleDistribution.java        |   45 +-
 .../executer/DependencyResolutionFailure.groovy    |    3 -
 .../executer/DetailedExecutionFailure.groovy       |    5 +-
 .../executer/DownloadableGradleDistribution.groovy |    7 +-
 .../executer/EmbeddedDaemonGradleExecuter.java     |    5 +-
 .../fixtures/executer/ExecutionFailure.java        |   17 +
 .../fixtures/executer/ExecutionResult.java         |    2 +-
 .../fixtures/executer/ForkingGradleExecuter.java   |   14 +-
 .../fixtures/executer/GradleDistribution.java      |    8 +-
 .../fixtures/executer/GradleExecuter.java          |    5 +
 .../fixtures/executer/InProcessGradleExecuter.java |  130 +-
 .../executer/InitScriptExecuterFixture.groovy      |    7 +-
 .../executer/OutputScrapingExecutionFailure.java   |  104 +-
 .../executer/OutputScrapingExecutionResult.java    |   14 +-
 .../executer/ParallelForkingGradleHandle.java      |    5 +-
 .../fixtures/executer/ParallelOutputMatcher.groovy |   51 -
 .../executer/ProgressLoggingFixture.groovy         |    8 +
 .../versions/ClasspathVersionJsonSource.java       |   58 -
 .../fixtures/versions/ClasspathVersionSource.java  |   65 +
 .../versions/IsTestableGradleVersionSpec.groovy    |   39 -
 .../fixtures/versions/ReleasedGradleVersion.java   |   63 -
 .../versions/ReleasedVersionDistributions.java     |   47 +-
 .../versions/VersionWebServiceJsonParser.java      |   85 -
 .../org/gradle/test/fixtures/AbstractModule.groovy |   77 +
 .../org/gradle/test/fixtures/HttpModule.groovy     |   20 +
 .../org/gradle/test/fixtures/HttpRepository.groovy |   22 +
 .../groovy/org/gradle/test/fixtures/Module.groovy  |   23 +
 .../org/gradle/test/fixtures/Repository.groovy     |   24 +
 .../gradle/test/fixtures/bintray/BintrayApi.groovy |   83 +
 .../test/fixtures/bintray/BintrayTestServer.groovy |   62 +
 .../test/fixtures/ivy/AbstractIvyModule.groovy     |   25 -
 .../gradle/test/fixtures/ivy/IvyDescriptor.groovy  |    2 +
 .../gradle/test/fixtures/ivy/IvyFileModule.groovy  |  142 +-
 .../test/fixtures/ivy/IvyFileRepository.groovy     |   11 +-
 .../gradle/test/fixtures/ivy/IvyHttpModule.groovy  |  147 +-
 .../test/fixtures/ivy/IvyHttpRepository.groovy     |    9 +-
 .../org/gradle/test/fixtures/ivy/IvyModule.java    |   35 +-
 .../gradle/test/fixtures/ivy/IvyRepository.groovy  |    4 +-
 .../test/fixtures/maven/AbstractMavenModule.groovy |  341 ++
 .../test/fixtures/maven/BasicHttpResource.groovy   |    7 +-
 .../gradle/test/fixtures/maven/HttpArtifact.groovy |   15 +-
 .../gradle/test/fixtures/maven/HttpResource.groovy |   22 +-
 .../test/fixtures/maven/M2Installation.groovy      |    8 +-
 .../test/fixtures/maven/MavenDependency.groovy     |    5 +
 .../test/fixtures/maven/MavenFileModule.groovy     |  348 +-
 .../test/fixtures/maven/MavenFileRepository.groovy |    3 +-
 .../test/fixtures/maven/MavenHttpModule.groovy     |   30 +-
 .../test/fixtures/maven/MavenHttpRepository.groovy |   15 +-
 .../test/fixtures/maven/MavenLocalModule.groovy    |   67 +
 .../fixtures/maven/MavenLocalRepository.groovy     |   40 +
 .../gradle/test/fixtures/maven/MavenModule.groovy  |   23 +-
 .../test/fixtures/maven/MavenRepository.groovy     |    4 +-
 .../gradle/test/fixtures/maven/MavenScope.groovy   |    2 +-
 .../test/fixtures/plugin/PluginBuilder.groovy      |  104 +
 .../gradle/test/fixtures/publish/Identifier.java   |  101 -
 .../test/fixtures/server/http/HttpServer.groovy    |  100 +-
 .../fixtures/server/http/ServletContainer.groovy   |   43 +
 .../test/fixtures/server/sftp/SFTPServer.groovy    |   40 +-
 .../IsTestableGradleVersionSpecTest.groovy         |   59 -
 .../ReleasedVersionDistributionsTest.groovy        |   18 +-
 .../VersionWebServiceJsonParserTest.groovy         |  115 -
 .../test/fixtures/maven/MavenFileModuleTest.groovy |  228 +
 .../fixtures/maven/MavenLocalModuleTest.groovy     |  220 +
 .../internal-testing/internal-testing.gradle       |    7 +-
 .../fixtures/DefaultTestExecutionResult.groovy     |   17 +-
 .../fixtures/HtmlTestExecutionResult.groovy        |   83 +-
 .../fixtures/JUnitTestClassExecutionResult.groovy  |  173 +
 .../fixtures/JUnitXmlTestExecutionResult.groovy    |  135 +-
 .../fixtures/TestClassExecutionResult.java         |    4 +
 .../fixtures/TestResultOutputAssociation.java      |   22 +
 .../fixtures/archive/ArchiveTestFixture.groovy     |   81 +
 .../test/fixtures/archive/JarTestFixture.groovy    |   62 +
 .../test/fixtures/archive/TarTestFixture.groovy    |   41 +
 .../test/fixtures/archive/ZipTestFixture.groovy    |   37 +
 .../test/fixtures/concurrent/ConcurrentSpec.groovy |   18 +-
 .../test/fixtures/concurrent/Instants.groovy       |   45 +-
 .../test/fixtures/concurrent/TestExecutor.groovy   |   18 +-
 .../test/fixtures/concurrent/TestLogger.groovy     |   25 +
 .../fixtures/concurrent/TestThreadListener.groovy  |   22 -
 .../gradle/test/fixtures/encoding/Identifier.java  |  100 +
 .../gradle/test/fixtures/file/ExecOutput.groovy    |   29 +
 .../org/gradle/test/fixtures/file/TestFile.java    |   42 +-
 .../test/fixtures/file/TestFileHelper.groovy       |   18 +-
 .../gradle/test/fixtures/file/WorkspaceTest.groovy |   34 +
 .../gradle/testing/internal/util/Network.groovy    |   29 -
 .../main/groovy/org/gradle/util/Assertions.groovy  |    3 -
 .../org/gradle/util/JUnit4GroovyMockery.java       |    3 -
 .../src/main/groovy/org/gradle/util/Matchers.java  |  320 ++
 .../org/gradle/util/ReflectionEqualsMatcher.java   |    3 -
 .../src/main/groovy/org/gradle/util/Resources.java |    2 +-
 .../groovy/org/gradle/util/TestClassLoader.groovy  |   62 +
 .../groovy/org/gradle/util/TestPrecondition.groovy |   39 +-
 .../gradle/util/TestPreconditionExtension.groovy   |    4 +-
 .../fixtures/concurrent/ConcurrentSpecTest.groovy  |   25 +-
 .../groovy/org/gradle/util/AssertionsTest.groovy   |    3 -
 .../gradle/util/TempDirIsUniquePerTestSpec.groovy  |    3 -
 subprojects/ivy/ivy.gradle                         |    9 +-
 ...IvyPublishArtifactCustomisationIntegTest.groovy |  321 --
 ...IvyPublishArtifactCustomizationIntegTest.groovy |  324 ++
 .../publish/ivy/IvyPublishBasicIntegTest.groovy    |    9 +-
 .../ivy/IvyPublishCoordinatesIntegTest.groovy      |  132 +
 .../IvyPublishCrossVersionIntegrationTest.groovy   |   30 +-
 ...yPublishDescriptorCustomisationIntegTest.groovy |  147 -
 ...yPublishDescriptorCustomizationIntegTest.groovy |  151 +
 .../api/publish/ivy/IvyPublishEarIntegTest.groovy  |    2 +-
 .../api/publish/ivy/IvyPublishHttpIntegTest.groovy |  145 +-
 .../IvyPublishIdentifierValidationIntegTest.groovy |   11 +-
 .../api/publish/ivy/IvyPublishJavaIntegTest.groovy |    7 +-
 .../ivy/IvyPublishMultiProjectIntegTest.groovy     |   70 +-
 .../IvyPublishMultipleRepositoriesIntegTest.groovy |    4 +-
 .../api/publish/ivy/IvyPublishWarIntegTest.groovy  |    2 +-
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   56 +-
 .../ivy/IvyEarProjectPublishIntegrationTest.groovy |    0
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |  323 ++
 .../IvyJavaProjectPublishIntegrationTest.groovy    |   63 +
 .../ivy/IvyLocalPublishIntegrationTest.groovy      |  113 +
 .../IvySingleProjectPublishIntegrationTest.groovy  |  128 +
 .../IvyUrlResolverPublishIntegrationTest.groovy    |   75 +
 .../ivy/IvyWarProjectPublishIntegrationTest.groovy |    0
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   34 +
 .../org/gradle/api/publish/ivy/IvyArtifactSet.java |    2 +-
 .../api/publish/ivy/IvyConfigurationContainer.java |    2 +-
 .../org/gradle/api/publish/ivy/IvyDependency.java  |   17 +-
 .../api/publish/ivy/IvyModuleDescriptor.java       |   12 +-
 .../org/gradle/api/publish/ivy/IvyPublication.java |   54 +-
 .../ivy/internal/IvyPublicationTasksModelRule.java |   82 +
 .../ivy/internal/artifact/DefaultIvyArtifact.java  |    5 +
 .../internal/artifact/DefaultIvyArtifactSet.java   |    6 +-
 .../artifact/IvyArtifactNotationParserFactory.java |   65 +-
 .../internal/dependency/DefaultIvyDependency.java  |   40 +-
 .../internal/dependency/IvyDependencyInternal.java |    4 +-
 ...tionDynamicDescriptorGenerationTaskCreator.java |   73 -
 .../plugins/IvyPublishDynamicTaskCreator.java      |   90 -
 .../publication/DefaultIvyModuleDescriptor.java    |    3 +-
 .../publication/DefaultIvyPublication.java         |   53 +-
 .../publication/IvyModuleDescriptorInternal.java   |    4 -
 .../publication/IvyPublicationInternal.java        |    3 +-
 .../publisher/ContextualizingIvyPublisher.java     |   40 +
 .../publisher/DependencyResolverIvyPublisher.java  |   20 +-
 .../publisher/IvyDescriptorFileGenerator.java      |   22 +-
 .../internal/publisher/ValidatingIvyPublisher.java |   22 +-
 .../api/publish/ivy/plugins/IvyPublishPlugin.java  |   55 +-
 .../publish/ivy/tasks/PublishToIvyRepository.java  |   11 +-
 .../IvyArtifactNotationParserFactoryTest.groovy    |   13 +-
 ...namicDescriptorGenerationTaskCreatorTest.groovy |   88 -
 .../IvyPublishDynamicTaskCreatorTest.groovy        |  104 -
 .../publication/DefaultIvyPublicationTest.groovy   |  124 +-
 .../IvyDescriptorFileGeneratorTest.groovy          |   34 +-
 .../publisher/ValidatingIvyPublisherTest.groovy    |    3 +-
 .../ivy/plugins/IvyPublishPluginTest.groovy        |   19 +-
 .../ivy/tasks/PublishToIvyRepositoryTest.groovy    |    6 +-
 subprojects/jacoco/jacoco.gradle                   |   27 +
 .../plugins/JacocoPluginGoodBehaviourTest.groovy   |   33 +
 .../plugins/JacocoPluginIntegrationTest.groovy     |  233 +
 .../jacoco/plugins/JacocoVersionIntegTest.groovy   |   69 +
 .../gradle/internal/jacoco/JacocoAgentJar.groovy   |   57 +
 .../jacoco/JacocoReportsContainerImpl.java         |   49 +
 .../testing/jacoco/plugins/JacocoPlugin.groovy     |  180 +
 .../jacoco/plugins/JacocoPluginExtension.groovy    |   87 +
 .../jacoco/plugins/JacocoTaskExtension.groovy      |  189 +
 .../gradle/testing/jacoco/tasks/JacocoBase.groovy  |   33 +
 .../gradle/testing/jacoco/tasks/JacocoMerge.groovy |   92 +
 .../testing/jacoco/tasks/JacocoReport.groovy       |  258 ++
 .../jacoco/tasks/JacocoReportsContainer.java       |   47 +
 .../gradle/testing/jacoco/tasks/package-info.java  |   20 +
 .../META-INF/gradle-plugins/jacoco.properties      |    1 +
 .../testing/jacoco/plugins/JacocoPluginSpec.groovy |   60 +
 .../jacoco/plugins/JacocoTaskExtensionSpec.groovy  |   99 +
 subprojects/javascript/javascript.gradle           |    4 +-
 .../base/JavaScriptRepositoriesExtension.java      |    2 +-
 .../coffeescript/CoffeeScriptBasePlugin.groovy     |    2 +-
 .../plugins/javascript/envjs/EnvJsPlugin.groovy    |    2 +-
 .../envjs/http/simple/SimpleHttpFileServer.java    |    2 +-
 .../http/simple/SimpleHttpFileServerFactory.java   |    2 +-
 .../plugins/javascript/jshint/JsHintPlugin.groovy  |    2 +-
 .../worker/internal/DefaultRhinoWorkerHandle.java  |    5 +
 .../internal/DefaultRhinoWorkerHandleFactory.java  |    1 +
 .../rhino/worker/internal/RhinoServer.java         |    1 +
 subprojects/jetty/jetty.gradle                     |    2 +-
 .../api/plugins/jetty/AbstractJettyRunTask.java    |   54 +-
 .../org/gradle/api/plugins/jetty/JettyPlugin.java  |    8 +-
 .../api/plugins/jetty/JettyPluginConvention.java   |    2 -
 .../org/gradle/api/plugins/jetty/JettyRun.java     |    4 +-
 .../org/gradle/api/plugins/jetty/JettyStop.java    |    6 +-
 .../api/plugins/jetty/JettyPluginTest.groovy       |   19 +-
 subprojects/language-base/language-base.gradle     |    7 +
 .../groovy/org/gradle/language/base/Binary.java    |   33 +
 .../org/gradle/language/base/BinaryContainer.java  |   25 +
 .../language/base/BuildableModelElement.java       |   41 +
 .../gradle/language/base/FunctionalSourceSet.java  |   27 +
 .../gradle/language/base/LanguageSourceSet.java    |   47 +
 .../org/gradle/language/base/ProjectSourceSet.java |   26 +
 .../internal/AbstractBuildableModelElement.java    |   55 +
 .../base/internal/AbstractLanguageSourceSet.java   |   81 +
 .../language/base/internal/BinaryInternal.java     |   23 +
 .../language/base/internal/BinaryNamingScheme.java |   35 +
 .../base/internal/BinaryNamingSchemeBuilder.java   |   26 +
 .../base/internal/DefaultBinaryContainer.java      |   27 +
 .../base/internal/DefaultBinaryNamingScheme.java   |  112 +
 .../internal/DefaultBinaryNamingSchemeBuilder.java |   64 +
 .../base/internal/DefaultFunctionalSourceSet.java  |   39 +
 .../base/internal/DefaultProjectSourceSet.java     |   32 +
 .../base/internal/LanguageSourceSetInternal.java   |   37 +
 .../org/gradle/language/base/package-info.java     |   23 +
 .../language/base/plugins/LanguageBasePlugin.java  |   67 +
 .../gradle/language/base/plugins/package-info.java |   23 +
 .../gradle-plugins/language-base.properties        |    1 +
 .../base/internal/BuildableModelElementTest.groovy |   54 +
 .../internal/DefaultBinaryNamingSchemeTest.groovy  |   79 +
 .../internal/DefaultFunctionalSourceSetTest.groovy |   29 +
 subprojects/language-jvm/language-jvm.gradle       |    7 +
 .../org/gradle/language/java/JavaSourceSet.java    |   28 +
 .../java/internal/DefaultJavaSourceSet.java        |   52 +
 .../org/gradle/language/java/package-info.java     |   23 +
 .../gradle/language/jvm/ClassDirectoryBinary.java  |   40 +
 .../groovy/org/gradle/language/jvm/Classpath.java  |   28 +
 .../org/gradle/language/jvm/ResourceSet.java       |   25 +
 .../internal/ClassDirectoryBinaryNamingScheme.java |   70 +
 .../jvm/internal/DefaultClassDirectoryBinary.java  |   83 +
 .../language/jvm/internal/DefaultClasspath.java    |   39 +
 .../language/jvm/internal/DefaultResourceSet.java  |   28 +
 .../jvm/internal/SimpleStaleClassCleaner.java      |   43 +
 .../language/jvm/internal/StaleClassCleaner.java   |   43 +
 .../org/gradle/language/jvm/package-info.java      |   23 +
 .../language/jvm/plugins/JvmLanguagePlugin.java    |  107 +
 .../gradle/language/jvm/plugins/package-info.java  |   23 +
 .../language/jvm/tasks/ProcessResources.java       |   35 +
 .../gradle/language/jvm/tasks/package-info.java    |   23 +
 .../java/internal/DefaultJavaSourceSetTest.groovy  |   33 +
 .../ClassDirectoryBinaryNamingSchemeTest.groovy    |   55 +
 .../DefaultClassDirectoryBinaryTest.groovy         |   79 +
 .../jvm/internal/DefaultResourceSetTest.groovy     |   32 +
 subprojects/launcher/launcher.gradle               |    8 +-
 .../ConfigurationOnDemandIntegrationTest.groovy    |    3 -
 ...EnablingParallelExecutionIntegrationTest.groovy |    3 -
 .../GradleConfigurabilityIntegrationSpec.groovy    |   30 +-
 .../daemon/DaemonFeedbackIntegrationSpec.groovy    |    3 -
 ...itialCommunicationFailureIntegrationSpec.groovy |   53 +-
 .../launcher/daemon/DaemonIntegrationSpec.groovy   |    3 -
 .../launcher/daemon/DaemonLifecycleSpec.groovy     |    2 +-
 .../daemon/DispachingFailureIntegrationSpec.groovy |    3 -
 .../launcher/daemon/EmbeddedDaemonSmokeTest.groovy |    4 +-
 .../gradle/launcher/daemon/ExecuteBuildAction.java |   28 +
 .../daemon/SingleUseDaemonIntegrationTest.groovy   |    3 +-
 .../daemon/StoppingDaemonIntegrationSpec.groovy    |    3 -
 .../DaemonParametersIntegrationTest.groovy         |   36 +
 .../daemon/testing/DaemonContextParser.java        |    3 -
 .../daemon/testing/DaemonLogsAnalyzer.groovy       |   15 +-
 .../daemon/testing/DaemonsEventSequence.groovy     |    2 +-
 .../main/java/org/gradle/launcher/GradleMain.java  |    3 -
 .../src/main/java/org/gradle/launcher/Main.java    |    4 +-
 .../launcher/bootstrap/ProcessBootstrap.java       |    6 +-
 .../gradle/launcher/cli/BuildActionsFactory.java   |  100 +-
 .../launcher/cli/CommandLineActionFactory.java     |    2 +-
 .../gradle/launcher/cli/ExecuteBuildAction.java    |   20 +-
 .../org/gradle/launcher/cli/GuiActionsFactory.java |    2 +-
 .../org/gradle/launcher/cli/RunBuildAction.java    |    6 +-
 .../cli/converter/DaemonCommandLineConverter.java  |   50 +
 .../cli/converter/LayoutToPropertiesConverter.java |   73 +
 .../PropertiesToDaemonParametersConverter.java     |   68 +
 .../PropertiesToStartParameterConverter.java       |   36 +
 .../launcher/daemon/DaemonExecHandleBuilder.java   |    3 -
 .../launcher/daemon/bootstrap/DaemonGreeter.java   |    3 -
 .../daemon/bootstrap/DaemonOutputConsumer.java     |    3 -
 .../bootstrap/DaemonStartupCommunication.java      |    3 -
 .../launcher/daemon/client/DaemonClient.java       |   30 +-
 .../daemon/client/DaemonClientConnection.java      |   42 +-
 .../daemon/client/DaemonClientInputForwarder.java  |   86 +-
 .../daemon/client/DaemonClientServices.java        |   35 +-
 .../daemon/client/DaemonClientServicesSupport.java |   37 +-
 .../daemon/client/DaemonConnectionException.java   |   30 +
 .../launcher/daemon/client/DaemonConnector.java    |    9 +-
 .../client/DaemonInitialConnectException.java      |    4 +
 .../daemon/client/DefaultDaemonConnector.java      |   63 +-
 .../client/EmbeddedDaemonClientServices.java       |   28 +-
 .../daemon/client/EmbeddedDaemonStarter.java       |    4 +-
 .../launcher/daemon/client/InputForwarder.java     |   39 +-
 .../client/NoUsableDaemonFoundException.java       |    3 -
 .../daemon/client/SingleUseDaemonClient.java       |   17 +-
 .../daemon/client/StaleDaemonAddressException.java |   29 +
 .../daemon/client/StopDaemonClientServices.java    |    2 +-
 .../launcher/daemon/client/StopDispatcher.java     |    3 -
 .../daemon/configuration/DaemonParameters.java     |   40 +-
 .../configuration/DaemonServerConfiguration.java   |    3 -
 .../DefaultDaemonServerConfiguration.java          |    3 -
 .../ForegroundDaemonConfiguration.java             |    3 -
 .../daemon/configuration/GradleProperties.java     |  177 +-
 .../configuration/GradlePropertiesConfigurer.java  |   48 -
 .../daemon/diagnostics/DaemonDiagnostics.java      |    2 -
 .../daemon/diagnostics/DaemonStartupInfo.java      |    3 -
 .../org/gradle/launcher/daemon/protocol/Build.java |    8 +-
 .../launcher/daemon/protocol/BuildAndStop.java     |    4 +-
 .../daemon/registry/DaemonRegistryContent.java     |    3 -
 .../daemon/registry/DaemonRegistryServices.java    |   26 +-
 .../daemon/registry/PersistentDaemonRegistry.java  |   30 +-
 .../org/gradle/launcher/daemon/server/Daemon.java  |    6 +-
 .../daemon/server/DaemonServerConnector.java       |    2 +-
 .../launcher/daemon/server/DaemonServices.java     |   23 +-
 .../daemon/server/DaemonStateCoordinator.java      |    2 +-
 .../daemon/server/DaemonTcpServerConnector.java    |   13 +-
 .../daemon/server/DefaultDaemonConnection.java     |    4 +-
 .../server/DefaultIncomingConnectionHandler.java   |    2 +-
 .../daemon/server/DomainRegistryUpdater.java       |    5 +-
 .../server/SynchronizedDispatchConnection.java     |    2 -
 .../daemon/server/exec/DaemonConnection.java       |    2 +-
 .../daemon/server/exec/DaemonHygieneAction.java    |   50 +
 .../server/exec/DefaultDaemonCommandExecuter.java  |    6 +-
 .../server/exec/EstablishBuildEnvironment.java     |    1 +
 .../launcher/daemon/server/exec/ExecuteBuild.java  |    4 +-
 .../daemon/server/exec/ForwardClientInput.java     |    4 +-
 .../server/exec/NoOpDaemonCommandAction.java       |   22 +
 .../gradle/launcher/exec/BuildActionExecuter.java  |   29 +
 .../exec/GradleLauncherActionExecuter.java         |   29 -
 .../exec/InProcessBuildActionExecuter.java         |   77 +
 .../InProcessGradleLauncherActionExecuter.java     |   46 -
 .../gradle/launcher/exec/InitializationAware.java  |   22 -
 .../internal/impl/LaunchableGradleProjectTask.java |   32 +
 .../internal/impl/LaunchableGradleTask.java        |   76 +
 .../impl/LaunchableGradleTaskSelector.java         |   86 +
 .../internal/impl/LaunchableImplementation.java    |   31 +
 .../internal/provider/BuildActionResult.java       |   33 +
 .../internal/provider/BuildModelAction.java        |  105 +
 .../internal/provider/ClassLoaderDetails.java      |   36 +
 .../internal/provider/ClasspathInferer.java        |  131 +
 .../provider/ClientProvidedBuildAction.java        |   81 +
 .../ClientSidePayloadClassLoaderRegistry.java      |  142 +
 .../internal/provider/ConfiguringBuildAction.java  |   91 +-
 .../internal/provider/ConnectionScopeServices.java |   55 +
 .../provider/DaemonBuildActionExecuter.java        |   47 +
 .../DaemonGradleLauncherActionExecuter.java        |   47 -
 .../internal/provider/DefaultBuildController.java  |   70 +
 .../internal/provider/DefaultConnection.java       |  223 +-
 .../provider/DefaultConnectionMetaData.java        |   30 +
 .../DefaultPayloadClassLoaderRegistry.java         |  174 +
 .../provider/DelegatingBuildModelAction.java       |   58 -
 .../tooling/internal/provider/DeserializeMap.java  |   24 +
 .../internal/provider/EmbeddedExecuterSupport.java |   45 -
 .../internal/provider/ExecuteBuildAction.java      |   33 -
 .../LoggingBridgingBuildActionExecuter.java        |   75 +
 ...oggingBridgingGradleLauncherActionExecuter.java |   75 -
 .../internal/provider/ModelClassLoaderFactory.java |  126 +
 .../provider/PayloadClassLoaderRegistry.java       |   26 +
 .../internal/provider/PayloadSerializer.java       |  155 +
 .../internal/provider/ProviderConnection.java      |  165 +
 .../internal/provider/ReflectionClassLookup.java   |   44 +
 .../tooling/internal/provider/SerializeMap.java    |   36 +
 .../internal/provider/SerializedPayload.java       |   37 +
 .../provider/ToolingGlobalScopeServices.java       |   25 +
 .../tooling/internal/provider/ToolingServices.java |   32 +
 .../connection/AdaptedOperationParameters.java     |   11 +-
 .../connection/ProviderOperationParameters.java    |    5 +-
 .../internal/provider/jdk6/Jdk6ClassLookup.java    |   27 +
 ...e.internal.service.scopes.PluginServiceRegistry |    1 +
 .../groovy/org/gradle/launcher/MainTest.groovy     |    8 +-
 .../launcher/cli/BuildActionsFactoryTest.groovy    |   53 +-
 .../cli/CommandLineActionFactoryTest.groovy        |    2 +-
 .../gradle/launcher/cli/RunBuildActionTest.groovy  |    4 +-
 .../DaemonCommandLineConverterTest.groovy          |   39 +
 .../LayoutToPropertiesConverterTest.groovy         |   95 +
 ...ropertiesToDaemonParametersConverterTest.groovy |  106 +
 .../PropertiesToStartParameterConverterTest.groovy |   36 +
 .../daemon/DaemonExecHandleBuilderSpec.groovy      |    3 -
 .../daemon/bootstrap/DaemonGreeterTest.groovy      |    3 -
 .../bootstrap/DaemonOutputConsumerTest.groovy      |    3 -
 .../DaemonStartupCommunicationSpec.groovy          |    3 -
 .../client/DaemonClientConnectionTest.groovy       |   78 +-
 .../daemon/client/DaemonClientServicesTest.groovy  |    3 +-
 .../launcher/daemon/client/DaemonClientTest.groovy |   18 +-
 .../client/DefaultDaemonConnectorTest.groovy       |   47 +-
 .../daemon/client/InputForwarderTest.groovy        |   27 +-
 .../daemon/client/StopDispatcherTest.groovy        |    5 +-
 .../daemon/configuration/CurrentProcessTest.groovy |    3 +-
 .../configuration/DaemonParametersTest.groovy      |   82 +-
 .../GradlePropertiesConfigurerTest.groovy          |   77 -
 .../configuration/GradlePropertiesTest.groovy      |  218 -
 .../context/DaemonCompatibilitySpecSpec.groovy     |   10 +-
 .../diagnostics/DaemonDiagnosticsTest.groovy       |    3 -
 .../registry/DaemonRegistryServicesTest.groovy     |   14 +-
 .../registry/DomainRegistryUpdaterTest.groovy      |    3 -
 .../registry/PersistentDaemonRegistryTest.groovy   |   34 +-
 .../DaemonServerExceptionHandlingTest.groovy       |   21 +-
 .../daemon/server/DaemonServicesTest.groovy        |    1 -
 .../server/DaemonStateCoordinatorTest.groovy       |    3 -
 .../server/exec/DaemonHygieneActionTest.groovy     |   52 +
 .../exec/DefaultBuildActionParametersTest.groovy   |    8 +-
 .../exec/InProcessBuildActionExecuterTest.groovy   |  156 +
 ...nProcessGradleLauncherActionExecuterTest.groovy |  100 -
 .../provider/AbstractClassGraphSpec.groovy         |   63 +
 .../internal/provider/ClasspathInfererTest.groovy  |   66 +
 .../provider/ConfiguringBuildActionTest.groovy     |   51 +-
 .../provider/ConnectionScopeServicesTest.groovy    |   37 +
 .../tooling/internal/provider/CustomAction.java    |   34 +
 .../tooling/internal/provider/CustomModel.java     |   24 +
 .../tooling/internal/provider/CustomPayload.java   |   29 +
 .../provider/DaemonBuildActionExecuterTest.groovy  |   45 +
 .../DaemonGradleLauncherActionExecuterTest.groovy  |   45 -
 .../provider/DefaultBuildControllerTest.groovy     |  129 +
 .../provider/ExecuteBuildActionTest.groovy         |   37 -
 .../LoggingBridgingBuildActionExecuterTest.groovy  |   74 +
 ...BridgingGradleLauncherActionExecuterTest.groovy |   74 -
 .../provider/ModelClassLoaderFactoryTest.groovy    |   36 +
 .../internal/provider/PayloadInterface.java        |   21 +
 .../internal/provider/PayloadSerializerTest.groovy |  204 +
 .../provider/ToolingGlobalScopeServicesTest.groovy |   29 +
 .../tooling/internal/provider/WrapperPayload.java  |   27 +
 .../AdaptedOperationParametersTest.groovy          |    3 -
 .../connection/BuildLogLevelMixInTest.groovy       |    3 -
 subprojects/maven/maven.gradle                     |    4 +-
 .../maven/MavenConversionIntegrationTest.groovy    |  161 -
 .../maven/AbstractMavenPublishIntegTest.groovy     |   11 +-
 ...venPublishArtifactCustomisationIntegTest.groovy |  228 -
 ...venPublishArtifactCustomizationIntegTest.groovy |  255 ++
 .../maven/MavenPublishBasicIntegTest.groovy        |   53 +-
 .../maven/MavenPublishCoordinatesIntegTest.groovy  |  140 +
 .../MavenPublishCrossVersionIntegrationTest.groovy |   21 +-
 ...avenPublishIdentifierValidationIntegTest.groovy |    4 +-
 .../maven/MavenPublishIssuesIntegTest.groovy       |   51 +
 .../publish/maven/MavenPublishJavaIntegTest.groovy |   30 +-
 .../maven/MavenPublishMultiProjectIntegTest.groovy |   88 +-
 .../MavenPublishPomCustomisationIntegTest.groovy   |  174 -
 .../MavenPublishPomCustomizationIntegTest.groovy   |  212 +
 .../SamplesMavenPublishIntegrationTest.groovy      |   42 +-
 .../MavenEarProjectPublishIntegrationTest.groovy   |    0
 .../MavenJavaProjectPublishIntegrationTest.groovy  |    0
 .../MavenMultiProjectPublishIntegrationTest.groovy |    0
 .../maven/MavenPomGenerationIntegrationTest.groovy |    0
 .../MavenPublishIgnoresMavenSettingsTest.groovy    |    0
 .../maven/MavenPublishIntegrationTest.groovy       |  388 ++
 ...MavenPublishRespectsPomConfigurationTest.groovy |    0
 .../MavenWarProjectPublishIntegrationTest.groovy   |    0
 ...SamplesMavenPomGenerationIntegrationTest.groovy |  106 +
 .../SamplesMavenQuickstartIntegrationTest.groovy   |   79 +
 .../publish/maven/pomGeneration/expectedNewPom.txt |    0
 .../publish/maven/pomGeneration/expectedPom.txt    |    0
 .../maven/pomGeneration/expectedQuickstartPom.txt  |    0
 .../api/artifacts/maven/Conf2ScopeMapping.java     |    2 -
 .../maven/Conf2ScopeMappingContainer.java          |   10 +-
 .../api/artifacts/maven/GroovyMavenDeployer.java   |    2 -
 .../gradle/api/artifacts/maven/MavenDeployer.java  |    6 +-
 .../api/artifacts/maven/MavenDeployment.java       |    2 +-
 .../org/gradle/api/artifacts/maven/MavenPom.java   |   34 +-
 .../gradle/api/artifacts/maven/MavenResolver.java  |    4 +-
 .../api/artifacts/maven/PomFilterContainer.java    |   40 +-
 .../gradle/api/artifacts/maven/PublishFilter.java  |    2 -
 .../groovy/org/gradle/api/plugins/MavenPlugin.java |   69 +-
 .../gradle/api/plugins/MavenPluginConvention.java  |    2 -
 .../plugins/MavenRepositoryHandlerConvention.java  |    2 +-
 .../api/plugins/maven/ConvertMaven2Gradle.groovy   |   54 -
 .../api/plugins/maven/Maven2GradlePlugin.groovy    |   36 -
 .../api/plugins/maven/internal/Maven2Gradle.groovy |  552 ---
 .../maven/internal/MavenProjectXmlWriter.java      |   61 -
 .../maven/internal/MavenProjectsCreator.java       |   98 -
 .../publication/maven/internal/ArtifactPom.java    |    3 -
 .../maven/internal/ArtifactPomContainer.java       |    3 -
 .../maven/internal/ArtifactPomFactory.java         |    3 -
 .../maven/internal/BasePomFilterContainer.java     |    3 -
 .../internal/CustomTaskFactoryDeployerFactory.java |   43 -
 .../maven/internal/DefaultArtifactPom.java         |    6 +-
 .../internal/DefaultArtifactPomContainer.java      |    3 -
 .../maven/internal/DefaultArtifactPomFactory.java  |    3 -
 .../DefaultConf2ScopeMappingContainer.java         |    5 +-
 .../maven/internal/DefaultMavenDeployment.java     |    3 -
 .../maven/internal/DefaultMavenPom.java            |    9 +-
 .../maven/internal/DefaultMavenPomFactory.java     |    5 +-
 .../DefaultMavenRepositoryHandlerConvention.java   |    2 +-
 .../maven/internal/DefaultPomFilter.java           |    3 -
 .../maven/internal/ExcludeRuleConverter.java       |    3 -
 .../maven/internal/PomDependenciesConverter.java   |    3 -
 .../api/publication/maven/internal/PomFilter.java  |    3 -
 .../maven/internal/ant/AbstractMavenResolver.java  |   62 +-
 .../maven/internal/ant/BaseMavenDeployer.java      |   20 +-
 .../maven/internal/ant/BaseMavenInstaller.java     |   16 +-
 .../maven/internal/ant/CustomDeployTask.java       |    2 -
 .../ant/CustomInstallDeployTaskSupport.java        |    5 +-
 .../maven/internal/ant/CustomInstallTask.java      |    3 -
 .../internal/ant/DefaultDeployTaskFactory.java     |   27 -
 .../internal/ant/DefaultExcludeRuleConverter.java  |    3 -
 .../internal/ant/DefaultGroovyMavenDeployer.groovy |    4 -
 .../internal/ant/DefaultInstallTaskFactory.java    |   27 -
 .../ant/DefaultPomDependenciesConverter.java       |    3 -
 .../internal/ant/EmptyMavenSettingsSupplier.java   |    3 -
 .../maven/internal/ant/LoggingHelper.java          |    3 -
 .../maven/internal/ant/MavenSettingsSupplier.java  |    3 -
 .../ant/MaybeUserMavenSettingsSupplier.java        |    3 -
 .../internal/ant/NoInstallDeployTaskFactory.java   |   56 -
 .../ProjectDependencyArtifactIdExtractorHack.java  |    3 +-
 .../maven/internal/ant/RepositoryBuilder.java      |    3 -
 .../maven/internal/ant/RepositoryFactory.java      |    3 -
 .../gradle/api/publish/maven/MavenArtifactSet.java |    2 +-
 .../gradle/api/publish/maven/MavenDependency.java  |   41 +
 .../org/gradle/api/publish/maven/MavenPom.java     |   15 +-
 .../gradle/api/publish/maven/MavenPublication.java |   40 +-
 .../maven/internal/MavenPublishTaskModelRule.java  |  109 +
 .../internal/artifact/DefaultMavenArtifact.java    |    5 +
 .../internal/artifact/DefaultMavenArtifactSet.java |    6 +-
 .../MavenArtifactNotationParserFactory.java        |   26 +-
 .../dependencies/DefaultMavenDependency.java       |   56 +
 .../dependencies/MavenDependencyInternal.java      |   25 +
 .../internal/plugins/GeneratePomTaskCreator.java   |   71 -
 .../plugins/MavenPublishDynamicTaskCreator.java    |   86 -
 .../MavenPublishLocalDynamicTaskCreator.java       |   65 -
 .../internal/publication/DefaultMavenPom.java      |   14 +-
 .../publication/DefaultMavenPublication.java       |   70 +-
 .../internal/publication/MavenPomInternal.java     |    6 +-
 .../publication/MavenPublicationInternal.java      |    7 +-
 .../AbstractAntTaskBackedMavenPublisher.java       |  116 +
 .../AntTaskBackedMavenLocalPublisher.java          |   64 +
 .../publisher/AntTaskBackedMavenPublisher.java     |  103 +-
 .../publisher/MavenDeployerConfigurer.java         |   55 -
 .../publisher/MavenRemoteRepositoryFactory.java    |   50 +
 .../publisher/ValidatingMavenPublisher.java        |   22 +-
 .../internal/tasks/MavenPomFileGenerator.java      |   87 +-
 .../publish/maven/plugins/MavenPublishPlugin.java  |   46 +-
 .../api/publish/maven/tasks/GenerateMavenPom.java  |   39 +-
 .../publish/maven/tasks/PublishToMavenLocal.java   |   25 +-
 .../maven/tasks/PublishToMavenRepository.java      |    7 +-
 .../gradle-plugins/maven2Gradle.properties         |    1 -
 .../api/artifacts/maven/Conf2ScopeMappingTest.java |    3 -
 .../api/plugins/MavenPluginConventionTest.groovy   |   14 +-
 .../org/gradle/api/plugins/MavenPluginTest.java    |    9 +-
 .../plugins/maven/Maven2GradlePluginSpec.groovy    |   36 -
 .../internal/MavenProjectXmlWriterTest.groovy      |   34 -
 .../maven/internal/MavenProjectsCreatorSpec.groovy |  118 -
 .../maven/internal/BasePomFilterContainerTest.java |    3 -
 .../maven/internal/DefaultArtifactPomTest.java     |    7 +-
 .../DefaultConf2ScopeMappingContainerTest.java     |    3 -
 .../internal/DefaultMavenPomFactoryTest.groovy     |    7 +-
 ...aultMavenRepositoryHandlerConventionTest.groovy |    2 +-
 .../maven/internal/DefaultPomFilterTest.java       |    3 -
 .../internal/ant/AbstractMavenResolverTest.java    |  241 --
 .../maven/internal/ant/BaseMavenDeployerTest.java  |  108 -
 .../maven/internal/ant/BaseMavenInstallerTest.java |   72 -
 .../internal/ant/DefaultDeployTaskFactoryTest.java |   30 -
 .../ant/DefaultExcludeRuleConverterTest.java       |    3 -
 .../ant/DefaultGroovyMavenDeployerTest.groovy      |   26 +-
 .../ant/DefaultGroovyPomFilterContainerTest.groovy |    7 +-
 .../ant/DefaultPomDependenciesConverterTest.java   |    3 -
 .../ant/EmptyMavenSettingsSupplierTest.groovy      |    3 -
 .../ant/MaybeUserMavenSettingsSupplierTest.groovy  |    4 -
 ...ectDependencyArtifactIdExtractorHackTest.groovy |    5 +-
 .../MavenArtifactNotationParserFactoryTest.groovy  |    8 +-
 .../publication/DefaultMavenPublicationTest.groovy |  145 +-
 .../publisher/ValidatingMavenPublisherTest.groovy  |   41 +-
 .../tasks/MavenPomFileGeneratorTest.groovy         |   70 +-
 .../maven/plugins/MavenPublishPluginTest.groovy    |   51 +-
 .../tasks/PublishToMavenRepositoryTest.groovy      |    6 +-
 subprojects/messaging/messaging.gradle             |    2 +-
 .../remote/UnicastMessagingIntegrationTest.groovy  |   27 +-
 .../java/org/gradle/messaging/actor/Actor.java     |    2 +-
 .../actor/internal/DefaultActorFactory.java        |    4 +-
 .../gradle/messaging/dispatch/DelayedReceive.java  |    2 +-
 .../dispatch/ExceptionTrackingFailureHandler.java  |    2 +-
 .../gradle/messaging/dispatch/QueuingDispatch.java |    2 +-
 .../org/gradle/messaging/remote/Addressable.java   |   22 -
 .../org/gradle/messaging/remote/ConnectEvent.java  |   41 -
 .../gradle/messaging/remote/MessagingClient.java   |    2 +-
 .../gradle/messaging/remote/MessagingServer.java   |    8 +-
 .../gradle/messaging/remote/ObjectConnection.java  |   34 +-
 .../messaging/remote/ObjectConnectionBuilder.java  |   57 +
 .../remote/internal/AsyncConnectionAdapter.java    |    4 +-
 .../remote/internal/ConnectCompletion.java         |   37 +
 .../remote/internal/DefaultIncomingBroadcast.java  |   16 +-
 .../remote/internal/DefaultOutgoingBroadcast.java  |    6 +-
 .../remote/internal/IncomingConnector.java         |   15 +-
 .../gradle/messaging/remote/internal/Message.java  |  147 +-
 .../messaging/remote/internal/MessageHub.java      |   24 +-
 .../remote/internal/MessagingServices.java         |  103 +-
 .../remote/internal/OutgoingConnector.java         |   12 +-
 .../remote/internal/PlaceholderException.java      |   22 +-
 .../messaging/remote/internal/ProtocolStack.java   |    2 +-
 .../gradle/messaging/remote/internal/Router.java   |    2 +-
 .../internal/hub/InterHubMessageSerializer.java    |   61 +-
 .../internal/hub/MessageHubBackedClient.java       |   19 +-
 .../hub/MessageHubBackedObjectConnection.java      |   68 +-
 .../internal/hub/MessageHubBackedServer.java       |   37 +-
 .../internal/hub/MethodInvocationSerializer.java   |   59 +-
 .../remote/internal/inet/InetAddressFactory.java   |    7 +-
 .../internal/inet/SocketConnectCompletion.java     |   45 +
 .../remote/internal/inet/SocketConnection.java     |    2 +-
 .../remote/internal/inet/TcpIncomingConnector.java |   37 +-
 .../remote/internal/inet/TcpOutgoingConnector.java |   14 +-
 .../serialize/AbstractCollectionSerializer.java    |   40 +
 .../messaging/serialize/AbstractDecoder.java       |  105 +
 .../messaging/serialize/AbstractEncoder.java       |   80 +
 .../messaging/serialize/BaseSerializerFactory.java |   70 +
 .../serialize/DataStreamBackedSerializer.java      |   36 -
 .../org/gradle/messaging/serialize/Decoder.java    |  118 +
 .../messaging/serialize/DefaultSerializer.java     |   12 +-
 .../serialize/DefaultSerializerRegistry.java       |   85 +
 .../org/gradle/messaging/serialize/Encoder.java    |   91 +
 .../messaging/serialize/FlushableEncoder.java      |   24 +
 .../serialize/InputStreamBackedDecoder.java        |   65 +
 .../gradle/messaging/serialize/ListSerializer.java |   37 +
 .../gradle/messaging/serialize/LongSerializer.java |   29 +
 .../gradle/messaging/serialize/MapSerializer.java  |   48 +
 .../serialize/NullSafeStringSerializer.java        |   26 +
 .../serialize/OutputStreamBackedEncoder.java       |   65 +
 .../org/gradle/messaging/serialize/Serializer.java |    7 +-
 .../messaging/serialize/SerializerRegistry.java    |   23 +
 .../gradle/messaging/serialize/SetSerializer.java  |   38 +
 .../messaging/serialize/kryo/JavaSerializer.java   |   30 +-
 .../serialize/kryo/KryoAwareSerializer.java        |   28 -
 .../serialize/kryo/KryoBackedDecoder.java          |  156 +
 .../serialize/kryo/KryoBackedEncoder.java          |   91 +
 .../messaging/serialize/kryo/KryoSerializer.java   |   59 -
 .../serialize/kryo/StatefulSerializer.java         |   28 +
 .../serialize/kryo/TypeSafeSerializer.java         |   18 +-
 .../messaging/remote/internal/MessageTest.groovy   |  241 +-
 .../internal/PlaceholderExceptionTest.groovy       |    4 +-
 .../remote/internal/ProtocolStackTest.groovy       |    2 +-
 .../hub/InterHubMessageSerializerTest.groovy       |   13 +-
 .../internal/hub/MessageHubBackedClientTest.groovy |   16 +-
 .../internal/hub/MessageHubBackedServerTest.groovy |   33 +-
 .../hub/MethodInvocationSerializerTest.groovy      |   25 +-
 .../internal/inet/InetAddressFactoryTest.groovy    |   47 +
 .../remote/internal/inet/TcpConnectorTest.groovy   |   41 +-
 .../messaging/serialize/AbstractCodecTest.groovy   |  524 +++
 .../serialize/BaseSerializerFactoryTest.groovy     |   73 +
 .../serialize/DefaultSerializerRegistryTest.groovy |   92 +
 .../serialize/DefaultSerializerTest.groovy         |    8 +-
 .../messaging/serialize/ListSerializerTest.groovy  |   45 +
 .../messaging/serialize/LongSerializerTest.groovy  |   34 +
 .../messaging/serialize/MapSerializerTest.groovy   |   47 +
 .../messaging/serialize/SetSerializerTest.groovy   |   47 +
 .../serialize/StreamBackedCodecTest.groovy         |   31 +
 .../serialize/kryo/KryoBackedCodecTest.groovy      |   98 +
 .../messaging/serialize/SerializerSpec.groovy      |   39 +
 subprojects/native/native.gradle                   |    8 +-
 .../nativeplatform/ReflectiveEnvironment.java      |    2 -
 .../nativeplatform/filesystem/FileSystem.java      |    2 +-
 .../filesystem/FileSystemServices.java             |   72 +-
 .../nativeplatform/filesystem/FileSystems.java     |   33 -
 .../filesystem/GenericFileSystem.java              |    4 +-
 .../filesystem/MacFilePathEncoder.java             |   37 -
 .../filesystem/NativePlatformBackedChmod.java      |   34 +
 .../filesystem/NativePlatformBackedStat.java       |   34 +
 .../filesystem/NativePlatformBackedSymlink.java    |   35 +
 .../nativeplatform/jna/JnaBootPathConfigurer.java  |    3 -
 .../internal/nativeplatform/jna/Kernel32.java      |   66 -
 .../jna/WindowsHandlesManipulator.java             |   86 -
 .../jna/WindowsProcessEnvironment.java             |   61 -
 .../nativeplatform/services/FileSystems.java       |   24 +
 .../nativeplatform/services/NativeServices.java    |   69 +-
 .../filesystem/CommonFileSystemTest.groovy         |    3 +-
 .../FileSystemServicesOnLinuxTest.groovy           |   45 -
 .../filesystem/FileSystemServicesOnMacTest.groovy  |   45 -
 .../FileSystemServicesOnUnknownOsTest.groovy       |   55 -
 .../FileSystemServicesOnWindowsTest.groovy         |   45 -
 .../filesystem/LinuxFileSystemTest.groovy          |    3 +-
 .../filesystem/MacOsFileSystemTest.groovy          |    3 +-
 .../filesystem/WindowsFileSystemTest.groovy        |    3 +-
 .../jdk7/PosixFilePermissionConverterTest.groovy   |    2 +-
 .../services/NativeServicesTest.groovy             |   22 +
 subprojects/open-api/open-api.gradle               |    9 +-
 .../integtests/openapi/CrossVersionBuilder.java    |    6 +
 ...CrossVersionCompatibilityIntegrationTest.groovy |    4 +-
 .../integtests/openapi/GradleRunnerTest.groovy     |   12 +-
 .../gradle/integtests/openapi/OpenApiUiTest.groovy |    7 +
 .../integtests/openapi/OutputUILordTest.groovy     |    2 -
 .../TestAlternateUIInteractionVersion1.java        |    2 -
 .../openapi/TestSettingsNodeVersion1.java          |    2 -
 .../TestSingleDualPaneUIInteractionVersion1.java   |    1 -
 .../org/gradle/foundation/BootstrapLoader.java     |  189 -
 .../gradle/foundation/ParentLastClassLoader.java   |   73 -
 .../gradle/openapi/external/ExternalUtility.java   |  168 -
 .../foundation/GradleInterfaceVersion1.java        |   89 -
 .../foundation/GradleInterfaceVersion2.java        |   89 -
 .../external/foundation/ProjectVersion1.java       |   83 -
 .../foundation/RequestObserverVersion1.java        |   52 -
 .../external/foundation/RequestVersion1.java       |   62 -
 .../openapi/external/foundation/TaskVersion1.java  |   61 -
 .../foundation/favorites/FavoriteTaskVersion1.java |   48 -
 .../favorites/FavoritesEditorVersion1.java         |  105 -
 .../external/runner/GradleRunnerFactory.java       |  134 -
 .../runner/GradleRunnerInteractionVersion1.java    |   83 -
 .../external/runner/GradleRunnerVersion1.java      |   35 -
 .../ui/AlternateUIInteractionVersion1.java         |   59 -
 .../openapi/external/ui/BasicGradleUIVersion1.java |  199 -
 ...ommandLineArgumentAlteringListenerVersion1.java |   32 -
 .../external/ui/DualPaneUIInteractionVersion1.java |   29 -
 .../openapi/external/ui/DualPaneUIVersion1.java    |   56 -
 .../openapi/external/ui/GradleTabVersion1.java     |   51 -
 .../external/ui/GradleUIInteractionVersion1.java   |   42 -
 .../external/ui/OutputObserverVersion1.java        |   56 -
 .../openapi/external/ui/OutputUILordVersion1.java  |   70 -
 .../openapi/external/ui/SettingsNodeVersion1.java  |   67 -
 .../ui/SinglePaneUIInteractionVersion1.java        |   29 -
 .../openapi/external/ui/SinglePaneUIVersion1.java  |   39 -
 .../org/gradle/openapi/external/ui/UIFactory.java  |  239 -
 .../org/gradle/foundation/BootstrapLoader.java     |  187 +
 .../gradle/foundation/ParentLastClassLoader.java   |   72 +
 .../gradle/openapi/external/ExternalUtility.java   |  166 +
 .../foundation/GradleInterfaceVersion1.java        |   89 +
 .../foundation/GradleInterfaceVersion2.java        |   88 +
 .../external/foundation/ProjectVersion1.java       |   83 +
 .../foundation/RequestObserverVersion1.java        |   52 +
 .../external/foundation/RequestVersion1.java       |   62 +
 .../openapi/external/foundation/TaskVersion1.java  |   61 +
 .../foundation/favorites/FavoriteTaskVersion1.java |   45 +
 .../favorites/FavoritesEditorVersion1.java         |  105 +
 .../external/runner/GradleRunnerFactory.java       |  133 +
 .../runner/GradleRunnerInteractionVersion1.java    |   85 +
 .../external/runner/GradleRunnerVersion1.java      |   35 +
 .../ui/AlternateUIInteractionVersion1.java         |   59 +
 .../openapi/external/ui/BasicGradleUIVersion1.java |  199 +
 ...ommandLineArgumentAlteringListenerVersion1.java |   32 +
 .../external/ui/DualPaneUIInteractionVersion1.java |   30 +
 .../openapi/external/ui/DualPaneUIVersion1.java    |   59 +
 .../openapi/external/ui/GradleTabVersion1.java     |   48 +
 .../external/ui/GradleUIInteractionVersion1.java   |   41 +
 .../external/ui/OutputObserverVersion1.java        |   59 +
 .../openapi/external/ui/OutputUILordVersion1.java  |   70 +
 .../openapi/external/ui/SettingsNodeVersion1.java  |   67 +
 .../ui/SinglePaneUIInteractionVersion1.java        |   30 +
 .../openapi/external/ui/SinglePaneUIVersion1.java  |   40 +
 .../org/gradle/openapi/external/ui/UIFactory.java  |  238 +
 subprojects/osgi/osgi.gradle                       |    4 +-
 .../plugins/osgi/OsgiPluginIntegrationSpec.groovy  |    6 -
 .../plugins/osgi/ContainedVersionAnalyzer.java     |    2 +-
 .../plugins/osgi/DefaultAnalyzerFactory.java       |    3 -
 .../internal/plugins/osgi/DefaultOsgiManifest.java |   14 +-
 .../api/internal/plugins/osgi/OsgiHelper.java      |    3 -
 .../org/gradle/api/plugins/osgi/OsgiManifest.java  |    4 +-
 .../org/gradle/api/plugins/osgi/OsgiPlugin.groovy  |    2 -
 .../api/plugins/osgi/OsgiPluginConvention.java     |    2 -
 .../plugins/osgi/DefaultAnalyzerFactoryTest.java   |    4 +-
 .../plugins/osgi/DefaultOsgiManifestTest.groovy    |    8 +-
 .../plugins/osgi/OsgiPluginConventionTest.groovy   |   12 +-
 .../gradle/api/plugins/osgi/OsgiPluginTest.groovy  |    4 +-
 subprojects/performance/performance.gradle         |  118 +-
 .../performance/CleanBuildPerformanceTest.groovy   |   13 +-
 .../ConfigurationPerformanceTest.groovy            |   47 +
 .../DependencyReportPerformanceTest.groovy         |   15 +-
 .../DependencyResolutionStressTest.groovy          |    5 +-
 .../performance/FirstBuildPerformanceTest.groovy   |   46 +
 .../IdeIntegrationPerformanceTest.groovy           |   16 +-
 .../TestExecutionPerformanceTest.groovy            |   11 +-
 .../UpToDateBuildPerformanceTest.groovy            |   11 +-
 .../src/templates/config-inject/build.gradle       |    2 +-
 .../src/templates/project-with-source/build.gradle |    3 +-
 .../gradle/performance/ResultSpecification.groovy  |   52 +
 .../gradle/performance/fixture/AmountTest.groovy   |  231 -
 .../gradle/performance/fixture/DurationTest.groovy |   59 -
 .../fixture/GCLoggingCollectorTest.groovy          |   55 +
 .../fixture/PerformanceResultsTest.groovy          |  228 +-
 .../fixture/PerformanceTestRunnerTest.groovy       |  105 +
 .../fixture/PrettyCalculatorSpec.groovy            |    5 +-
 .../gradle/performance/fixture/UnitsTest.groovy    |   34 -
 .../gradle/performance/measure/AmountTest.groovy   |  231 +
 .../performance/measure/DataSeriesTest.groovy      |   56 +
 .../gradle/performance/measure/DurationTest.groovy |   59 +
 .../gradle/performance/measure/UnitsTest.groovy    |   34 +
 .../performance/results/ReportGeneratorTest.groovy |   44 +
 .../performance/results/ResultsStoreTest.groovy    |  248 ++
 .../org/gradle/performance/fixture/gc-1.txt        |   19 +
 .../org/gradle/performance/fixture/gc-2.txt        |   16 +
 .../org/gradle/performance/fixture/gc-3.txt        |   16 +
 .../fixture/AbstractPerformanceTest.groovy         |   27 +-
 .../org/gradle/performance/fixture/Amount.java     |  162 -
 .../performance/fixture/BaselineVersion.groovy     |   42 +-
 .../fixture/CompositeDataCollector.java            |   44 +
 .../performance/fixture/CompositeDataReporter.java |   39 +
 .../org/gradle/performance/fixture/DataAmount.java |   37 -
 .../gradle/performance/fixture/DataCollector.java  |    7 +-
 .../org/gradle/performance/fixture/Duration.java   |   45 -
 .../performance/fixture/GCLoggingCollector.java    |  141 +
 .../org/gradle/performance/fixture/Git.groovy      |   42 +
 .../performance/fixture/MeasuredOperation.groovy   |   41 -
 .../fixture/MeasuredOperationList.groovy           |   45 +-
 .../performance/fixture/MemoryInfoCollector.groovy |   11 +-
 .../gradle/performance/fixture/OperationTimer.java |   36 +
 .../performance/fixture/PerformanceResults.groovy  |   83 +-
 .../fixture/PerformanceTestRunner.groovy           |   74 +-
 .../performance/fixture/PrettyCalculator.groovy    |   15 +-
 .../performance/fixture/TestProjectLocator.groovy  |    5 +-
 .../fixture/TextFileDataReporter.groovy            |   13 +-
 .../org/gradle/performance/fixture/Units.java      |  193 -
 .../gradle/performance/fixture/VersionResults.java |   21 +
 .../org/gradle/performance/measure/Amount.java     |  172 +
 .../org/gradle/performance/measure/DataAmount.java |   41 +
 .../org/gradle/performance/measure/DataSeries.java |   68 +
 .../org/gradle/performance/measure/Duration.java   |   45 +
 .../performance/measure/MeasuredOperation.groovy   |   32 +
 .../org/gradle/performance/measure/Units.java      |  193 +
 .../gradle/performance/results/FileRenderer.java   |   34 +
 .../gradle/performance/results/FormatSupport.java  |   56 +
 .../performance/results/HtmlPageGenerator.java     |   54 +
 .../performance/results/IndexPageGenerator.java    |  108 +
 .../performance/results/ReportGenerator.java       |   54 +
 .../gradle/performance/results/ResultsStore.java   |  297 ++
 .../performance/results/TestDataGenerator.java     |   93 +
 .../performance/results/TestExecutionHistory.java  |   83 +
 .../performance/results/TestPageGenerator.java     |  221 +
 .../resources/org/gradle/reporting/report.js       |   63 +
 .../resources/org/gradle/reporting/style.css       |   89 +
 subprojects/plugins/plugins.gradle                 |    9 +-
 .../gradle/api/plugins/BuildSrcPluginTest.groovy   |    2 +-
 .../api/tasks/bundling/JarIntegrationTest.groovy   |  201 +-
 .../tasks/bundling/WarTaskIntegrationTest.groovy   |  181 +-
 .../groovy/GroovyBasePluginIntegrationTest.groovy  |   80 +-
 .../groovy/GroovyPluginIntegrationTest.groovy      |   52 +
 .../AntForkingGroovyCompilerIntegrationTest.groovy |    2 +-
 .../ApiGroovyCompilerIntegrationSpec.groovy        |    4 +-
 .../BasicGroovyCompilerIntegrationSpec.groovy      |    2 +-
 .../compile/GroovyCompilerIntegrationSpec.groovy   |   12 +
 .../compile/InvokeDynamicGroovyCompilerSpec.groovy |    2 +-
 .../JreJavaHomeGroovyIntegrationTest.groovy        |   29 +-
 .../gradle/java/JavaPluginGoodBehaviourTest.groovy |   22 +
 ...ncrementalJavaCompilationIntegrationTest.groovy |  219 +
 .../JreJavaHomeJavaIntegrationTest.groovy          |   30 +-
 .../testing/IncrementalTestIntegrationTest.groovy  |  123 +
 .../testing/SuiteTimestampIntegrationTest.groovy   |   52 +
 .../testing/TestEnvironmentIntegrationTest.groovy  |   14 +-
 .../testing/TestReportIntegrationTest.groovy       |  382 +-
 .../gradle/testing/TestTaskIntegrationTest.groovy  |   51 +
 .../gradle/testing/TestingIntegrationTest.groovy   |  188 +-
 .../CucumberJVMReportIntegrationTest.groovy        |   46 +
 .../AbstractTestFilteringIntegrationTest.groovy    |  187 +
 .../gradle/testing/fixture/JUnitCoverage.groovy    |   26 +
 .../gradle/testing/fixture/TestNGCoverage.groovy   |   22 +
 .../junit/JUnitAssumptionsIntegrationTest.groovy   |   47 +
 .../JUnitCategoriesCoverageIntegrationSpec.groovy  |   88 +
 .../junit/JUnitCategoriesIntegrationSpec.groovy    |   64 +
 .../junit/JUnitCrossVersionIntegrationSpec.groovy  |   61 -
 .../junit/JUnitFilteringIntegrationTest.groovy     |   32 +
 .../JUnitFilteringSupportIntegrationTest.groovy    |   62 +
 ...itIgnoreClassMultiVersionIntegrationSpec.groovy |   46 +
 .../testing/junit/JUnitIntegrationTest.groovy      |   27 +-
 .../junit/JUnitLoggingIntegrationTest.groovy       |    7 +-
 .../junit/JUnitMultiVersionIntegrationSpec.groovy  |   54 +
 ...JUnitTestFilteringSamplesIntegrationTest.groovy |   39 +
 .../testng/SampleTestNGIntegrationTest.groovy      |    7 +-
 .../testng/TestNGFilteringIntegrationTest.groovy   |   32 +
 .../testing/testng/TestNGIntegrationProject.groovy |   67 -
 .../testing/testng/TestNGIntegrationTest.groovy    |   23 +-
 .../testng/TestNGLoggingIntegrationTest.groovy     |  107 +-
 .../TestNGProducesOldReportsIntegrationTest.groovy |   22 +-
 ...TestNGSuiteInitialisationIntegrationTest.groovy |   53 +
 .../testng/TestNGSuiteIntegrationTest.groovy       |   74 +
 ...tNGXmlResultAndHtmlReportIntegrationTest.groovy |  299 +-
 .../shared/build.gradle                            |    2 +-
 .../build.gradle                                   |   10 +
 .../src/main/groovy/GroovyCode.groovy              |    1 +
 .../src/main/groovy/JavaCode.java                  |    3 +
 .../build.gradle                                   |    2 +-
 .../recompilesDependentClasses/build.gradle        |    2 +-
 .../build.gradle                                   |    2 +-
 .../doesNotRunStaleTests/src/test/java/Broken.java |    0
 .../NewMainClass.java                              |    0
 .../executesTestsWhenSourceChanges/NewOk.java      |    0
 .../src/main/java/MainClass.java                   |    0
 .../build.gradle                                   |    0
 .../src/test/java/JUnitExtra.java                  |    0
 .../src/test/java/JUnitTest.java                   |    0
 .../src/test/java/TestNGTest.java                  |    0
 .../shared/build.gradle                            |    0
 .../shared/src/test/java/Ok.java                   |    0
 .../build.gradle                                   |   34 +
 .../src/test/java/HelloStepdefs.java               |   20 +
 .../src/test/java/RunCukesTest.java                |    6 +
 .../src/test/resources/helloworld.feature          |    7 +
 .../supportsAssumptions/build.gradle               |   18 +
 .../test/java/org/gradle/TestWithAssumptions.java  |   32 +
 .../build.gradle                                   |   28 +
 .../src/test/java/org/gradle/CategoryA.java        |   20 +
 .../src/test/java/org/gradle/LocaleHolder.java     |   32 +
 .../src/test/java/org/gradle/Locales.java          |   80 +
 .../src/test/java/org/gradle/SomeLocaleTests.java  |   19 +
 .../test/java/org/gradle/SomeMoreLocalTests.java   |   35 +
 .../canSpecifyExcludesOnly/build.gradle            |   26 +
 .../src/test/java/org/gradle/CatATests.java        |   41 +
 .../src/test/java/org/gradle/CategoryA.java        |   20 +
 .../src/test/java/org/gradle/NoCatTests.java       |   30 +
 .../src/test/java/org/gradle/SomeOtherCat.java     |   20 +
 .../test/java/org/gradle/SomeOtherCatTests.java    |   32 +
 .../src/test/java/org/gradle/SomeTests.java        |   40 +
 .../build.gradle                                   |   28 +
 .../src/test/java/org/gradle/CatACTests.java       |   40 +
 .../src/test/java/org/gradle/CatADTests.java       |   42 +
 .../src/test/java/org/gradle/CatATests.java        |   40 +
 .../src/test/java/org/gradle/CatBTests.java        |   40 +
 .../src/test/java/org/gradle/CatCBTests.java       |   42 +
 .../src/test/java/org/gradle/CatCTests.java        |   40 +
 .../src/test/java/org/gradle/CatDTests.java        |   40 +
 .../src/test/java/org/gradle/CatZTests.java        |   40 +
 .../src/test/java/org/gradle/CategoryA.java        |   20 +
 .../src/test/java/org/gradle/CategoryB.java        |   20 +
 .../src/test/java/org/gradle/CategoryC.java        |   20 +
 .../src/test/java/org/gradle/CategoryD.java        |   20 +
 .../src/test/java/org/gradle/CategoryZ.java        |   20 +
 .../src/test/java/org/gradle/MixedTests.java       |   44 +
 .../src/test/java/org/gradle/NoCatTests.java       |   38 +
 .../reportsUnloadableCategories/build.gradle       |   27 +
 .../src/test/java/org/gradle/SomeTestClass.java    |   13 +
 .../build.gradle                                   |   16 +
 .../src/test/java/org/gradle/SomeTest.java         |   12 +
 .../canHandleClassLevelIgnoredTests/build.gradle   |   23 +
 .../test/java/org/gradle/CustomIgnoredTest.java    |   70 +
 .../src/test/java/org/gradle/IgnoredTest.java      |    0
 .../build.gradle                                   |    6 +-
 .../build.gradle                                   |    6 +-
 .../canRunTestsUsingJUnit3/build.gradle            |   25 +
 .../JUnitIntegrationTest/junit3Tests/build.gradle  |    9 -
 .../JUnitIntegrationTest/junit4Tests/build.gradle  |    9 -
 .../test/java/org/gradle/CustomIgnoredTest.java    |   71 -
 .../src/test/java/org/gradle/Junit4Test.java       |   11 +-
 .../build.gradle                                   |    8 +-
 .../supportsTestCategories/build.gradle            |   16 +
 .../src/test/java/org/gradle/CategoryA.java        |    4 +
 .../src/test/java/org/gradle/CategoryB.java        |    4 +
 .../src/test/java/org/gradle/CategoryC.java        |    4 +
 .../src/test/java/org/gradle/SomeTest.java         |   27 +
 .../canRunTestsUsingJUnit/build.gradle             |    0
 .../shared/build.gradle                            |   24 -
 .../src/test/groovy/org/gradle/TestNGTest.groovy   |   42 -
 .../standardOutputLogging/build.gradle             |   35 -
 .../org/gradle/TestNGStandardOutputTest.groovy     |   27 -
 .../org/gradle/api/distribution/Distribution.java  |   28 +-
 .../distribution/internal/DefaultDistribution.java |    8 +-
 .../internal/DefaultDistributionContainer.java     |   13 +-
 .../distribution/plugins/DistributionPlugin.groovy |   82 +-
 .../gradle/api/internal/java/WebApplication.java   |    2 +-
 .../internal/plugins/BuildConfigurationRule.java   |    2 +-
 .../org/gradle/api/internal/plugins/CleanRule.java |    2 +-
 .../api/internal/plugins/ProcessResources.java     |   21 +-
 .../internal/plugins/StartScriptGenerator.groovy   |   37 +
 .../gradle/api/internal/plugins/UploadRule.java    |    2 +-
 .../gradle/api/internal/tasks/CompileServices.java |   55 +
 .../internal/tasks/DefaultBinariesContainer.java   |   27 -
 .../tasks/DefaultClassDirectoryBinary.java         |  109 -
 .../api/internal/tasks/DefaultClasspath.java       |   38 -
 .../internal/tasks/DefaultFunctionalSourceSet.java |   34 -
 .../api/internal/tasks/DefaultJavaSourceSet.java   |   72 -
 .../internal/tasks/DefaultJvmBinaryContainer.java  |   36 -
 .../internal/tasks/DefaultProjectSourceSet.java    |   32 -
 .../api/internal/tasks/DefaultResourceSet.java     |   53 -
 .../api/internal/tasks/DefaultSourceSet.java       |    2 +-
 .../internal/tasks/DefaultSourceSetContainer.java  |    3 +
 .../api/internal/tasks/DefaultSourceSetOutput.java |    3 -
 .../internal/tasks/SourceSetCompileClasspath.java  |    5 +-
 .../compile/AntDependsStaleClassCleaner.groovy     |    6 +-
 .../tasks/compile/AntGroovyCompiler.groovy         |    2 -
 .../internal/tasks/compile/AntJavaCompiler.groovy  |    7 +-
 .../internal/tasks/compile/ApiGroovyCompiler.java  |    5 +-
 .../api/internal/tasks/compile/ArgCollector.java   |    2 +
 .../api/internal/tasks/compile/ArgWriter.java      |    7 +
 .../tasks/compile/CleaningGroovyCompiler.java      |   40 +
 .../tasks/compile/CleaningJavaCompiler.java        |   50 +
 .../tasks/compile/CleaningJavaCompilerSupport.java |   39 +
 .../tasks/compile/CommandLineJavaCompiler.java     |    4 +-
 .../CommandLineJavaCompilerArgumentsGenerator.java |    7 +-
 .../tasks/compile/DefaultJavaCompilerFactory.java  |   31 +-
 .../tasks/compile/ExecSpecBackedArgCollector.java  |    5 +
 .../GroovyCompileTransformingClassLoader.java      |  124 +
 .../tasks/compile/GroovyCompilerFactory.java       |   11 +-
 .../compile/InProcessJavaCompilerFactory.java      |    4 +-
 .../tasks/compile/IncrementalGroovyCompiler.java   |   38 -
 .../tasks/compile/IncrementalJavaCompiler.java     |   48 -
 .../compile/IncrementalJavaCompilerSupport.java    |   39 -
 .../tasks/compile/NoOpStaleClassCleaner.java       |    2 +
 .../tasks/compile/NormalizingGroovyCompiler.java   |    1 +
 .../tasks/compile/NormalizingJavaCompiler.java     |    1 +
 .../tasks/compile/SimpleStaleClassCleaner.java     |   38 -
 .../internal/tasks/compile/SimpleWorkResult.java   |   30 -
 .../internal/tasks/compile/StaleClassCleaner.java  |   53 -
 .../internal/tasks/compile/SunJavaCompiler.java    |    1 +
 .../tasks/compile/TransformingClassLoader.java     |  152 -
 .../compile/daemon/CompilerClientsManager.java     |   82 +
 .../tasks/compile/daemon/CompilerDaemonClient.java |   22 +-
 .../compile/daemon/CompilerDaemonFactory.java      |    5 +-
 .../compile/daemon/CompilerDaemonManager.java      |   96 +-
 .../tasks/compile/daemon/CompilerDaemonServer.java |    3 +-
 .../daemon/CompilerDaemonServerProtocol.java       |    2 +-
 .../compile/daemon/CompilerDaemonStarter.java      |   68 +
 .../tasks/compile/daemon/DaemonGroovyCompiler.java |    2 +-
 .../tasks/compile/daemon/DaemonJavaCompiler.java   |    6 +-
 .../daemon/InProcessCompilerDaemonFactory.java     |    7 +-
 .../compile/incremental/AllFromJarRebuildInfo.java |   41 +
 .../tasks/compile/incremental/ClassDependents.java |   44 +
 .../compile/incremental/ClassNameProvider.java     |   38 +
 .../compile/incremental/DefaultRebuildInfo.java    |   53 +
 .../tasks/compile/incremental/DummySerializer.java |   51 +
 .../incremental/IncrementalCompilationSupport.java |   51 +
 .../compile/incremental/InputOutputMapper.java     |   60 +
 .../tasks/compile/incremental/JarArchive.java      |   30 +
 .../compile/incremental/JarChangeProcessor.java    |   56 +
 .../tasks/compile/incremental/JarDelta.java        |   23 +
 .../tasks/compile/incremental/JarSnapshot.java     |   43 +
 .../compile/incremental/JarSnapshotCache.java      |   53 +
 .../compile/incremental/JarSnapshotFeeder.java     |   57 +
 .../tasks/compile/incremental/JarSnapshotter.java  |   46 +
 .../tasks/compile/incremental/JavaSourceClass.java |   41 +
 .../compile/incremental/OutputClassMapper.java     |   32 +
 .../tasks/compile/incremental/RebuildInfo.java     |   25 +
 .../compile/incremental/SelectiveCompilation.java  |  141 +
 .../compile/incremental/SelectiveJavaCompiler.java |   66 +
 .../incremental/SpecificClassesRebuildInfo.java    |   23 +
 .../incremental/analyzer/ClassAnalysis.java        |   37 +
 .../analyzer/ClassDependenciesAnalyzer.java        |   74 +
 .../analyzer/ClassDependenciesVisitor.java         |   58 +
 .../incremental/analyzer/ClassRelevancyFilter.java |   30 +
 .../incremental/graph/ClassDependencyInfo.java     |   59 +
 .../graph/ClassDependencyInfoExtractor.java        |   74 +
 .../graph/ClassDependencyInfoSerializer.java       |   47 +
 .../tasks/compile/jdk6/Jdk6JavaCompiler.java       |   26 +-
 .../tasks/testing/DefaultJUnitXmlReport.java       |   41 +
 .../tasks/testing/DefaultTestClassRunInfo.java     |    3 -
 .../tasks/testing/DefaultTestOutputEvent.java      |   28 +
 .../tasks/testing/DefaultTestTaskReports.java      |   46 +
 .../tasks/testing/NoMatchingTestsReporter.java     |   42 +
 .../tasks/testing/SuiteTestClassProcessor.java     |    5 +-
 .../internal/tasks/testing/TestClassProcessor.java |    4 +-
 .../internal/tasks/testing/TestClassRunInfo.java   |    3 -
 .../internal/tasks/testing/TestCompleteEvent.java  |    2 +
 .../api/internal/tasks/testing/TestFramework.java  |    5 +-
 .../api/internal/tasks/testing/TestStartEvent.java |    3 +
 .../detection/AbstractTestFrameworkDetector.java   |    3 -
 .../detection/ClassFileExtractionManager.java      |    2 -
 .../testing/detection/DefaultTestClassScanner.java |    2 -
 .../testing/detection/DefaultTestExecuter.java     |    2 -
 .../testing/detection/JarFilePackageListener.java  |    3 -
 .../testing/detection/JarFilePackageLister.java    |    3 -
 .../tasks/testing/detection/TestClassVisitor.java  |    2 -
 .../tasks/testing/detection/TestExecuter.java      |    3 -
 .../testing/detection/TestFrameworkDetector.java   |    3 -
 .../tasks/testing/filter/DefaultTestFilter.java    |   54 +
 .../tasks/testing/filter/TestSelectionMatcher.java |   62 +
 .../tasks/testing/junit/CategoryFilter.java        |  112 +
 .../tasks/testing/junit/JUnitDetector.java         |    3 -
 .../internal/tasks/testing/junit/JUnitSpec.java    |   50 +
 .../testing/junit/JUnitTestClassDetecter.java      |    3 -
 .../testing/junit/JUnitTestClassExecuter.java      |   62 +-
 .../testing/junit/JUnitTestClassProcessor.java     |   10 +-
 .../tasks/testing/junit/JUnitTestEventAdapter.java |    6 +-
 .../tasks/testing/junit/JUnitTestFramework.java    |   58 +-
 .../testing/junit/JUnitTestMethodDetecter.java     |    3 -
 .../tasks/testing/junit/report/AllTestResults.java |   13 +-
 .../testing/junit/report/ClassPageRenderer.java    |   33 +-
 .../testing/junit/report/ClassTestResults.java     |   14 +-
 .../testing/junit/report/CompositeTestResults.java |   75 +-
 .../testing/junit/report/DefaultTestReport.java    |   41 +-
 .../junit/report/LocaleSafeDecimalFormat.java      |    3 -
 .../testing/junit/report/OverviewPageRenderer.java |   13 +-
 .../testing/junit/report/PackagePageRenderer.java  |    9 +-
 .../testing/junit/report/PackageTestResults.java   |   12 +-
 .../tasks/testing/junit/report/PageRenderer.java   |   38 +-
 .../tasks/testing/junit/report/TestFailure.java    |   34 -
 .../tasks/testing/junit/report/TestResult.java     |   17 +-
 .../junit/result/AggregateTestResultsProvider.java |  120 +-
 .../result/Binary2JUnitXmlReportGenerator.java     |   13 +-
 .../BinaryResultBackedTestResultsProvider.java     |   36 +-
 .../testing/junit/result/CachingFileWriter.java    |  100 -
 .../junit/result/InMemoryTestResultsProvider.java  |   63 +
 .../testing/junit/result/JUnitXmlResultWriter.java |   90 +-
 .../testing/junit/result/TestClassResult.java      |   29 +-
 .../tasks/testing/junit/result/TestFailure.java    |   41 +
 .../testing/junit/result/TestMethodResult.java     |   47 +-
 .../junit/result/TestOutputAssociation.java        |   22 +
 .../testing/junit/result/TestOutputSerializer.java |   90 -
 .../testing/junit/result/TestOutputStore.java      |  388 ++
 .../junit/result/TestReportDataCollector.java      |  125 +-
 .../testing/junit/result/TestResultSerializer.java |  135 +-
 .../testing/junit/result/TestResultsProvider.java  |   23 +-
 .../tasks/testing/logging/AbstractTestLogger.java  |    2 +-
 .../processors/MaxNParallelTestClassProcessor.java |    2 +-
 .../internal/tasks/testing/results/TestState.java  |    5 -
 .../testing/results/UnknownTestDescriptor.java     |   10 +-
 .../tasks/testing/testng/TestNGDetector.java       |    3 -
 .../testng/TestNGListenerAdapterFactory.java       |   25 +-
 .../internal/tasks/testing/testng/TestNGSpec.java  |    9 +-
 .../testing/testng/TestNGTestClassDetecter.java    |    3 -
 .../testing/testng/TestNGTestClassProcessor.java   |   43 +-
 .../tasks/testing/testng/TestNGTestFramework.java  |   25 +-
 .../testing/testng/TestNGTestMethodDetecter.java   |    3 -
 .../testng/TestNGTestResultProcessorAdapter.java   |   42 +-
 .../testng/UnrepresentableParameterException.java  |   32 +
 .../testing/worker/ForkingTestClassProcessor.java  |   39 +-
 .../tasks/testing/worker/TestEventSerializer.java  |  265 ++
 .../internal/tasks/testing/worker/TestWorker.java  |    4 +-
 .../testing/worker/WorkerTestClassProcessor.java   |    6 +-
 .../org/gradle/api/java/archives/Attributes.java   |    2 -
 .../api/java/archives/ManifestException.java       |    2 -
 .../java/archives/internal/DefaultAttributes.java  |    3 -
 .../java/archives/internal/DefaultManifest.java    |    7 +-
 .../internal/DefaultManifestMergeDetails.java      |    3 -
 .../gradle/api/plugins/ApplicationPlugin.groovy    |   18 +-
 .../api/plugins/ApplicationPluginConvention.groovy |    7 +-
 .../groovy/org/gradle/api/plugins/BasePlugin.java  |   54 +-
 .../gradle/api/plugins/BasePluginConvention.groovy |    5 +-
 .../org/gradle/api/plugins/GroovyBasePlugin.java   |   91 +-
 .../org/gradle/api/plugins/GroovyPlugin.java       |    4 +-
 .../org/gradle/api/plugins/JavaBasePlugin.java     |   83 +-
 .../org/gradle/api/plugins/JavaLanguagePlugin.java |   77 +-
 .../plugins/JavaLibraryDistributionPlugin.groovy   |    2 -
 .../groovy/org/gradle/api/plugins/JavaPlugin.java  |   12 +-
 .../gradle/api/plugins/JavaPluginConvention.groovy |   11 +-
 .../org/gradle/api/plugins/JvmLanguagePlugin.java  |  128 -
 .../org/gradle/api/plugins/LanguageBasePlugin.java |   60 -
 .../groovy/org/gradle/api/plugins/WarPlugin.java   |   10 +-
 .../org/gradle/api/tasks/BinariesContainer.java    |   27 -
 .../org/gradle/api/tasks/ClassDirectoryBinary.java |   49 -
 .../groovy/org/gradle/api/tasks/Classpath.java     |   28 -
 .../org/gradle/api/tasks/FunctionalSourceSet.java  |   27 -
 .../groovy/org/gradle/api/tasks/GroovyRuntime.java |  131 +
 .../groovy/org/gradle/api/tasks/JavaSourceSet.java |   24 -
 .../org/gradle/api/tasks/JvmBinaryContainer.java   |   27 -
 .../org/gradle/api/tasks/JvmLanguageSourceSet.java |   26 -
 .../org/gradle/api/tasks/LanguageSourceSet.java    |   33 -
 .../org/gradle/api/tasks/ProjectSourceSet.java     |   26 -
 .../groovy/org/gradle/api/tasks/ResourceSet.java   |   24 -
 .../org/gradle/api/tasks/SourceSetContainer.java   |    4 +
 .../main/groovy/org/gradle/api/tasks/Upload.java   |  132 +
 .../tasks/application/CreateStartScripts.groovy    |   15 +-
 .../org/gradle/api/tasks/bundling/Jar.groovy       |   22 +-
 .../org/gradle/api/tasks/bundling/War.groovy       |    9 +-
 .../gradle/api/tasks/compile/AbstractCompile.java  |    1 -
 .../gradle/api/tasks/compile/AbstractOptions.java  |    6 +-
 .../gradle/api/tasks/compile/BaseForkOptions.java  |    3 -
 .../org/gradle/api/tasks/compile/Compile.java      |  143 +-
 .../gradle/api/tasks/compile/CompileOptions.java   |   47 +-
 .../org/gradle/api/tasks/compile/DebugOptions.java |    2 -
 .../gradle/api/tasks/compile/DependOptions.java    |    2 -
 .../org/gradle/api/tasks/compile/ForkOptions.java  |    2 -
 .../gradle/api/tasks/compile/GroovyCompile.java    |   21 +-
 .../api/tasks/compile/GroovyCompileOptions.java    |    5 +-
 .../api/tasks/compile/GroovyForkOptions.java       |    2 -
 .../org/gradle/api/tasks/compile/JavaCompile.java  |    8 +
 .../gradle/api/tasks/javadoc/AntGroovydoc.groovy   |    3 -
 .../org/gradle/api/tasks/javadoc/AntJavadoc.groovy |    3 +-
 .../org/gradle/api/tasks/javadoc/Groovydoc.java    |   16 +-
 .../org/gradle/api/tasks/javadoc/Javadoc.java      |    8 +-
 .../gradle/api/tasks/testing/JUnitXmlReport.java   |   39 +
 .../groovy/org/gradle/api/tasks/testing/Test.java  |  324 +-
 .../gradle/api/tasks/testing/TestDescriptor.java   |    3 +
 .../org/gradle/api/tasks/testing/TestFilter.java   |   87 +
 .../org/gradle/api/tasks/testing/TestListener.java |    4 +-
 .../org/gradle/api/tasks/testing/TestReport.java   |   39 +-
 .../gradle/api/tasks/testing/TestTaskReports.java  |   42 +
 .../api/tasks/testing/junit/JUnitOptions.groovy    |   51 +
 .../api/tasks/testing/junit/JUnitOptions.java      |   25 -
 .../api/tasks/testing/testng/TestNGOptions.groovy  |   40 +-
 .../org/gradle/api/tasks/wrapper/Wrapper.java      |  280 --
 .../external/javadoc/CoreJavadocOptions.java       |    2 -
 .../external/javadoc/JavadocMemberLevel.java       |    2 -
 .../external/javadoc/JavadocOfflineLink.java       |    2 -
 .../external/javadoc/JavadocOptionFileOption.java  |    1 -
 .../external/javadoc/JavadocOutputLevel.java       |    2 -
 .../external/javadoc/MinimalJavadocOptions.java    |    2 -
 .../javadoc/OptionLessJavadocOptionFileOption.java |    1 -
 .../javadoc/StandardJavadocDocletOptions.java      |   12 +-
 .../internal/AbstractJavadocOptionFileOption.java  |    1 -
 .../AbstractListJavadocOptionFileOption.java       |    3 +-
 .../internal/BooleanJavadocOptionFileOption.java   |    2 -
 .../internal/EnumJavadocOptionFileOption.java      |    1 -
 .../internal/FileJavadocOptionFileOption.java      |    2 -
 .../internal/GroupsJavadocOptionFileOption.java    |    2 -
 .../javadoc/internal/JavadocExecHandleBuilder.java |   14 +-
 .../javadoc/internal/JavadocOptionFile.java        |    3 -
 .../javadoc/internal/JavadocOptionFileWriter.java  |    7 +-
 .../internal/JavadocOptionFileWriterContext.java   |    5 +-
 .../LinksOfflineJavadocOptionFileOption.java       |    7 +-
 .../MultilineStringsJavadocOptionFileOption.java   |    7 +-
 .../OptionLessStringsJavadocOptionFileOption.java  |    7 +-
 .../internal/PathJavadocOptionFileOption.java      |    5 +-
 .../internal/StringJavadocOptionFileOption.java    |    3 -
 .../internal/StringsJavadocOptionFileOption.java   |    7 +-
 .../META-INF/gradle-plugins/jvm-lang.properties    |    2 +-
 .../META-INF/gradle-plugins/lang-base.properties   |    2 +-
 ...e.internal.service.scopes.PluginServiceRegistry |    1 +
 .../api/internal/plugins/unixStartScript.txt       |    2 +-
 .../api/internal/plugins/windowsStartScript.txt    |    2 +-
 .../internal/tasks/testing/junit/report/style.css  |    6 +-
 .../plugins/DistributionPluginTest.groovy          |    4 +-
 .../plugins/StartScriptGeneratorTest.groovy        |   99 +
 .../api/internal/tasks/DefaultSourceSetTest.groovy |    2 +-
 .../tasks/compile/CleaningJavaCompilerTest.groovy  |   61 +
 .../compile/DefaultJavaCompilerFactoryTest.groovy  |    5 +-
 ...GroovyCompileTransformingClassLoaderTest.groovy |   96 +
 .../InProcessJavaCompilerFactoryTest.groovy        |    2 +-
 .../compile/IncrementalJavaCompilerTest.groovy     |   60 -
 .../compile/SimpleStaleClassCleanerTest.groovy     |   20 +
 .../compile/TransformingClassLoaderTest.groovy     |   96 -
 .../daemon/CompilerClientsManagerTest.groovy       |   97 +
 .../daemon/CompilerDaemonManagerTest.groovy        |  100 +
 .../incremental/AllFromJarRebuildInfoTest.groovy   |   44 +
 .../incremental/ClassNameProviderTest.groovy       |   42 +
 .../IncrementalCompilationSupportTest.groovy       |   52 +
 .../incremental/InputOutputMapperTest.groovy       |   53 +
 .../incremental/JarSnapshotCacheTest.groovy        |   50 +
 .../incremental/JarSnapshotFeederTest.groovy       |   72 +
 .../compile/incremental/JarSnapshotTest.groovy     |   49 +
 .../compile/incremental/JarSnapshotterTest.groovy  |   52 +
 .../compile/incremental/JavaSourceClassTest.groovy |   38 +
 .../incremental/OutputClassMapperTest.groovy       |   28 +
 .../analyzer/AccessedFromPrivateClass.java         |   24 +
 .../analyzer/ClassDependenciesAnalyzerTest.groovy  |   74 +
 .../analyzer/HasNonPrivateConstants.java           |   21 +
 .../incremental/analyzer/HasPrivateConstants.java  |   22 +
 .../incremental/analyzer/HasPublicConstants.java   |   21 +
 .../compile/incremental/analyzer/SomeClass.java    |   35 +
 .../incremental/analyzer/SomeOtherClass.java       |   24 +
 .../analyzer/UsedByNonPrivateConstantsClass.java   |   20 +
 .../incremental/analyzer/YetAnotherClass.java      |   20 +
 .../analyzer/annotations/SomeClassAnnotation.java  |   24 +
 .../annotations/SomeRuntimeAnnotation.java         |   24 +
 .../analyzer/annotations/SomeSourceAnnotation.java |   24 +
 .../annotations/UsesAnnotationInField.java         |   21 +
 .../analyzer/annotations/UsesClassAnnotation.java  |   20 +
 .../annotations/UsesRuntimeAnnotation.java         |   20 +
 .../analyzer/annotations/UsesSourceAnnotation.java |   20 +
 .../graph/ClassDependencyInfoExtractorTest.groovy  |   37 +
 .../graph/ClassDependencyInfoSerializerTest.groovy |   38 +
 .../tasks/testing/AbstractTestFrameworkTest.java   |   68 -
 .../testing/filter/DefaultTestFilterTest.groovy    |   53 +
 .../testing/filter/TestSelectionMatcherTest.groovy |  129 +
 .../junit/JUnitTestClassProcessorData.groovy       |  291 ++
 .../junit/JUnitTestClassProcessorTest.groovy       | 1098 +----
 .../testing/junit/JUnitTestFrameworkTest.java      |   99 -
 .../testing/junit/report/AllTestResultsTest.groovy |    4 +-
 .../junit/report/ClassTestResultsTest.groovy       |    4 +-
 .../junit/report/CompositeTestResultsTest.groovy   |   64 +-
 .../junit/report/DefaultTestReportTest.groovy      |  764 ++--
 .../junit/report/LocaleSafeDecimalFormatTest.java  |    3 -
 .../result/AggregateTestResultsProviderTest.groovy |  187 +
 .../Binary2JUnitXmlReportGeneratorSpec.groovy      |   23 +-
 .../junit/result/CachingFileWriterSpec.groovy      |   83 -
 .../junit/result/JUnitXmlResultWriterSpec.groovy   |  124 +-
 .../junit/result/TestClassResultSpec.groovy        |   11 +-
 .../junit/result/TestOutputSerializerTest.groovy   |   72 -
 .../junit/result/TestOutputStoreSpec.groovy        |  180 +
 .../result/TestReportDataCollectorSpec.groovy      |  215 +-
 .../junit/result/TestResultSerializerTest.groovy   |   54 +-
 .../logging/FullExceptionFormatterTest.groovy      |    6 +-
 .../logging/ShortExceptionFormatterTest.groovy     |    4 +-
 .../tasks/testing/logging/SimpleTestResult.groovy  |   31 -
 .../testing/logging/TestEventLoggerTest.groovy     |    6 +-
 .../testing/results/DefaultTestResultTest.groovy   |    3 -
 .../testng/TestNGTestClassProcessorTest.groovy     |  316 +-
 .../testing/testng/TestNGTestFrameworkTest.groovy  |   26 +-
 .../worker/ForkingTestClassProcessorTest.groovy    |   46 +
 .../worker/ForkingTestClassProcessorTest.java      |  141 -
 .../testing/worker/TestEventSerializerTest.groovy  |  202 +
 .../tasks/testing/worker/TestWorkerTest.groovy     |    3 +
 .../archives/internal/DefaultAttributesTest.groovy |    5 +-
 .../internal/DefaultManifestMergeSpecTest.groovy   |    3 -
 .../archives/internal/DefaultManifestTest.groovy   |    3 -
 .../api/plugins/ApplicationPluginTest.groovy       |   31 +-
 .../api/plugins/BasePluginConventionTest.groovy    |    8 +-
 .../org/gradle/api/plugins/BasePluginTest.groovy   |   49 +-
 .../gradle/api/plugins/GroovyBasePluginTest.groovy |   33 +-
 .../org/gradle/api/plugins/GroovyPluginTest.groovy |    9 +-
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |   79 +-
 .../api/plugins/JavaLanguagePluginTest.groovy      |   17 +-
 .../JavaLibraryDistributionPluginTest.groovy       |    4 +-
 .../api/plugins/JavaPluginConventionTest.groovy    |    7 +-
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |   68 +-
 .../api/plugins/JvmLanguagePluginTest.groovy       |   64 +-
 .../api/plugins/LanguageBasePluginTest.groovy      |   29 +-
 .../org/gradle/api/plugins/WarPluginTest.groovy    |   14 +-
 .../org/gradle/api/tasks/GroovyRuntimeTest.groovy  |  116 +
 .../groovy/org/gradle/api/tasks/UploadTest.groovy  |   32 +
 .../application/CreateStartScriptsTest.groovy      |    4 +-
 .../org/gradle/api/tasks/bundling/JarTest.groovy   |    4 +-
 .../org/gradle/api/tasks/bundling/WarTest.groovy   |    4 +-
 .../api/tasks/compile/CompileOptionsTest.groovy    |    8 +-
 .../api/tasks/compile/DebugOptionsTest.groovy      |    3 -
 .../api/tasks/compile/ForkOptionsTest.groovy       |    6 +-
 .../tasks/compile/GroovyCompileOptionsTest.groovy  |    6 +-
 .../api/tasks/compile/GroovyCompileTest.java       |   16 +-
 .../api/tasks/compile/GroovyForkOptionsTest.groovy |    6 +-
 .../gradle/api/tasks/compile/JavaCompileTest.java  |    3 -
 .../gradle/api/tasks/javadoc/GroovydocTest.java    |    7 +-
 .../org/gradle/api/tasks/javadoc/JavadocTest.java  |    2 +-
 .../testing/AbstractTestFrameworkOptionsTest.java  |    3 -
 .../gradle/api/tasks/testing/TestReportTest.groovy |    6 +-
 .../gradle/api/tasks/testing/TestTaskSpec.groovy   |   19 +-
 .../org/gradle/api/tasks/testing/TestTest.java     |   30 +-
 .../tasks/testing/testng/TestNGOptionsTest.groovy  |   13 +-
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |  164 -
 .../javadoc/StandardJavadocDocletOptionsTest.java  |   20 +-
 .../BooleanJavadocOptionFileOptionTest.java        |    9 +-
 .../internal/EnumJavadocOptionFileOptionTest.java  |    7 +-
 .../internal/FileJavadocOptionFileOptionTest.java  |    7 +-
 .../GroupsJavadocOptionFileOptionTest.java         |    7 +-
 .../internal/JavadocExecHandleBuilderTest.groovy   |   35 +-
 .../javadoc/internal/JavadocOptionFileTest.java    |    3 -
 .../JavadocOptionFileWriterContextTest.java        |    7 +-
 .../LinksOfflineJavadocOptionFileOptionTest.java   |    7 +-
 ...ultilineStringsJavadocOptionFileOptionTest.java |    5 +-
 ...tionLessStringsJavadocOptionFileOptionTest.java |    5 +-
 .../internal/PathJavadocOptionFileOptionTest.java  |    7 +-
 .../StringJavadocOptionFileOptionTest.java         |    5 +-
 .../StringsJavadocOptionFileOptionTest.java        |    5 +-
 .../tasks/testing/BuildableTestMethodResult.groovy |   71 +
 .../testing/BuildableTestResultsProvider.groovy    |  163 +
 .../tasks/testing/MethodTestOutputEvent.groovy     |   29 +
 .../internal/tasks/testing/SimpleTestResult.groovy |   35 +
 .../junit/report/HtmlTestResultsFixture.groovy     |  285 ++
 .../api/tasks/compile/AbstractCompileTest.java     |    5 +-
 subprojects/publish/publish.gradle                 |    4 +-
 .../gradle/api/publish/PublicationContainer.java   |   55 +-
 .../gradle/api/publish/PublishingExtension.java    |   23 +
 .../internal/CompositePublicationFactory.java      |   39 -
 .../internal/DefaultPublicationContainer.java      |   25 +-
 .../internal/GroovyPublicationContainer.groovy     |   35 -
 .../ProjectDependencyPublicationResolver.java      |   57 +
 .../internal/PublicationContainerInternal.java     |   23 -
 .../api/publish/internal/PublicationFactory.java   |   22 -
 .../api/publish/internal/PublicationInternal.java  |   23 +
 .../api/publish/internal/PublishServices.java      |   32 +
 .../api/publish/plugins/PublishingPlugin.java      |   44 +-
 ...e.internal.service.scopes.PluginServiceRegistry |    1 +
 .../DefaultPublicationContainerTest.groovy         |   48 +-
 ...ProjectDependencyPublicationResolverTest.groovy |  134 +
 .../publish/plugins/PublishingPluginTest.groovy    |   10 +-
 subprojects/reporting/reporting.gradle             |    3 +-
 .../internal/TaskReportContainerIntegTest.groovy   |    0
 .../BuildDashboardPluginIntegrationTest.groovy     |  328 +-
 .../api/plugins/ReportingBasePluginConvention.java |    3 +-
 .../api/reporting/BuildDashboardReports.java       |    8 +-
 .../gradle/api/reporting/ConfigurableReport.java   |   34 +
 .../org/gradle/api/reporting/DirectoryReport.java  |   47 +
 .../api/reporting/GenerateBuildDashboard.java      |   60 +-
 .../groovy/org/gradle/api/reporting/Report.java    |   20 +-
 .../org/gradle/api/reporting/ReportContainer.java  |   14 +-
 .../groovy/org/gradle/api/reporting/Reporting.java |   40 +-
 .../gradle/api/reporting/ReportingExtension.java   |   22 +-
 .../org/gradle/api/reporting/SingleFileReport.java |   12 +-
 .../internal/BuildDashboardGenerator.java          |   70 +-
 .../internal/DefaultBuildDashboardReports.java     |   14 +-
 .../reporting/internal/DefaultReportContainer.java |    5 +-
 .../TaskGeneratedSingleDirectoryReport.java        |   45 +
 .../reporting/plugins/BuildDashboardPlugin.groovy  |   58 -
 .../reporting/plugins/BuildDashboardPlugin.java    |   73 +
 .../org/gradle/api/reporting/internal/style.css    |    3 +
 .../ReportingBasePluginConventionTest.groovy       |   64 +
 .../api/plugins/ReportingBasePluginTest.groovy     |   46 +
 .../reporting/GenerateBuildDashboardSpec.groovy    |    4 +-
 .../internal/BuildDashboardGeneratorSpec.groovy    |   22 +-
 .../internal/DefaultReportContainerTest.groovy     |   12 +-
 subprojects/resources/resources.gradle             |   11 +
 .../org/gradle/internal/filestore/FileStore.java   |   32 +
 .../internal/filestore/FileStoreSearcher.java      |   27 +
 .../local/AbstractLocallyAvailableResource.java    |   55 +
 .../local/DefaultLocallyAvailableResource.java     |   42 +
 .../resource/local/LocallyAvailableResource.java   |   31 +
 .../DefaultLocallyAvailableResourceTest.groovy     |   66 +
 subprojects/scala/scala.gradle                     |   13 +-
 .../integtests/ScalaPluginGoodBehaviourTest.groovy |    0
 .../integtests/ScalaProjectIntegrationTest.java    |    0
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   74 +
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   50 +
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   66 +
 .../samples/SamplesScalaZincIntegrationTest.groovy |    0
 .../scala/ScalaBasePluginIntegrationTest.groovy    |   90 +-
 .../ZincScalaCompilerJdk6IntegrationTest.groovy    |    2 +-
 .../JreJavaHomeScalaIntegrationTest.groovy         |   12 +-
 .../scala/test/ScalaTestIntegrationTest.groovy     |    4 +-
 .../internal/tasks/scala/AntScalaCompiler.groovy   |    2 +-
 .../tasks/scala/CleaningScalaCompiler.java         |   46 +
 .../internal/tasks/scala/DaemonScalaCompiler.java  |    2 +-
 .../tasks/scala/IncrementalScalaCompiler.java      |   44 -
 .../tasks/scala/NormalizingScalaCompiler.java      |    4 +-
 .../internal/tasks/scala/ScalaCompilerFactory.java |    8 +-
 .../tasks/scala/jdk6/ZincScalaCompiler.java        |   28 +-
 .../api/plugins/scala/ScalaBasePlugin.groovy       |  144 +-
 .../gradle/api/plugins/scala/ScalaPlugin.groovy    |    5 +-
 .../org/gradle/api/tasks/ScalaRuntime.groovy       |  159 +
 .../org/gradle/api/tasks/scala/ScalaCompile.java   |   13 +-
 .../api/plugins/scala/ScalaBasePluginTest.groovy   |  142 +-
 .../api/plugins/scala/ScalaPluginTest.groovy       |   12 +-
 .../org/gradle/api/tasks/ScalaRuntimeTest.groovy   |  124 +
 .../gradle/api/tasks/scala/ScalaCompileTest.java   |    9 +-
 subprojects/signing/signing.gradle                 |    2 +-
 .../gradle/plugins/signing/SigningExtension.groovy |    8 +-
 .../plugins/signing/SigningPluginConvention.groovy |    8 +-
 .../plugins/signing/SigningProjectSpec.groovy      |    4 +-
 subprojects/sonar/sonar.gradle                     |    3 +-
 .../plugins/sonar/SonarSmokeIntegrationTest.groovy |   16 +-
 .../runner/SonarRunnerSmokeIntegrationTest.groovy  |   16 +-
 .../shared/javaProject/build.gradle                |    2 -
 .../shared/javaProjectWithJacoco/build.gradle      |    5 +
 .../org/gradle/test/javaProject/Production1.java   |    0
 .../org/gradle/test/javaProject/Production10.java  |    0
 .../org/gradle/test/javaProject/Production2.java   |    0
 .../org/gradle/test/javaProject/Production3.java   |    0
 .../org/gradle/test/javaProject/Production4.java   |    0
 .../org/gradle/test/javaProject/Production5.java   |    0
 .../org/gradle/test/javaProject/Production6.java   |    0
 .../org/gradle/test/javaProject/Production7.java   |    0
 .../org/gradle/test/javaProject/Production8.java   |    0
 .../org/gradle/test/javaProject/Production9.java   |    0
 .../gradle/test/javaProject/productionResource.xml |    0
 .../java/org/gradle/test/javaProject/Test1.java    |    0
 .../java/org/gradle/test/javaProject/Test10.java   |    0
 .../java/org/gradle/test/javaProject/Test2.java    |    0
 .../java/org/gradle/test/javaProject/Test3.java    |    0
 .../java/org/gradle/test/javaProject/Test4.java    |    0
 .../java/org/gradle/test/javaProject/Test5.java    |    0
 .../java/org/gradle/test/javaProject/Test6.java    |    0
 .../java/org/gradle/test/javaProject/Test7.java    |    0
 .../java/org/gradle/test/javaProject/Test8.java    |    0
 .../java/org/gradle/test/javaProject/Test9.java    |    0
 .../org/gradle/test/javaProject/testResource.xml   |    0
 .../shared/settings.gradle                         |    2 +-
 .../shared/javaProject/build.gradle                |    2 -
 .../shared/javaProjectWithJacoco/build.gradle      |    5 +
 .../org/gradle/test/javaProject/Production1.java   |    0
 .../org/gradle/test/javaProject/Production10.java  |    0
 .../org/gradle/test/javaProject/Production2.java   |    0
 .../org/gradle/test/javaProject/Production3.java   |    0
 .../org/gradle/test/javaProject/Production4.java   |    0
 .../org/gradle/test/javaProject/Production5.java   |    0
 .../org/gradle/test/javaProject/Production6.java   |    0
 .../org/gradle/test/javaProject/Production7.java   |    0
 .../org/gradle/test/javaProject/Production8.java   |    0
 .../org/gradle/test/javaProject/Production9.java   |    0
 .../gradle/test/javaProject/productionResource.xml |    0
 .../java/org/gradle/test/javaProject/Test1.java    |    0
 .../java/org/gradle/test/javaProject/Test10.java   |    0
 .../java/org/gradle/test/javaProject/Test2.java    |    0
 .../java/org/gradle/test/javaProject/Test3.java    |    0
 .../java/org/gradle/test/javaProject/Test4.java    |    0
 .../java/org/gradle/test/javaProject/Test5.java    |    0
 .../java/org/gradle/test/javaProject/Test6.java    |    0
 .../java/org/gradle/test/javaProject/Test7.java    |    0
 .../java/org/gradle/test/javaProject/Test8.java    |    0
 .../java/org/gradle/test/javaProject/Test9.java    |    0
 .../org/gradle/test/javaProject/testResource.xml   |    0
 .../shared/settings.gradle                         |    2 +-
 .../gradle/api/plugins/sonar/SonarAnalyze.groovy   |    2 +-
 .../gradle/api/plugins/sonar/SonarPlugin.groovy    |   17 +-
 .../api/sonar/runner/SonarRunnerPlugin.groovy      |   39 +-
 .../api/plugins/sonar/SonarAnalyzeTest.groovy      |    2 +-
 .../api/plugins/sonar/SonarPluginTest.groovy       |   27 +-
 .../api/sonar/runner/SonarRunnerPluginTest.groovy  |   38 +-
 .../tooling/AutoTestedSamplesToolingApiTest.groovy |    5 +-
 .../ConcurrentToolingApiIntegrationSpec.groovy     |    4 +-
 .../SamplesToolingApiIntegrationTest.groovy        |   44 +-
 .../ToolingApiClasspathIntegrationTest.groovy      |    9 +-
 .../tooling/ToolingApiIntegrationTest.groovy       |   23 +-
 .../tooling/ToolingApiRemoteIntegrationTest.groovy |   25 +-
 ...lingApiUnsupportedVersionIntegrationTest.groovy |   62 +
 .../tooling/fixture/ConfigurableOperation.groovy   |    9 +-
 .../fixture/ExternalToolingApiDistribution.groovy  |   18 +-
 .../tooling/fixture/IncludeAllPermutations.java    |   23 -
 .../tooling/fixture/MaxTargetGradleVersion.java    |   25 -
 .../tooling/fixture/MinTargetGradleVersion.java    |   25 -
 .../tooling/fixture/MinToolingApiVersion.java      |   25 -
 .../tooling/fixture/TargetGradleVersion.java       |   31 +
 .../TestClasspathToolingApiDistribution.groovy     |    7 +-
 .../integtests/tooling/fixture/ToolingApi.groovy   |   36 +-
 .../ToolingApiCompatibilitySuiteRunner.groovy      |   79 +-
 .../tooling/fixture/ToolingApiDistribution.groovy  |    6 +-
 .../fixture/ToolingApiDistributionResolver.groovy  |   37 +-
 .../tooling/fixture/ToolingApiSpecification.groovy |   31 +-
 .../tooling/fixture/ToolingApiVersion.java         |   31 +
 .../m3/ToolingApiLoggingCrossVersionSpec.groovy    |  103 +
 ...piEclipseLinkedResourcesCrossVersionSpec.groovy |   11 +-
 ...ngApiEclipseMinimalModelCrossVersionSpec.groovy |    4 +-
 ...EclipseModelWithFlatRepoCrossVersionSpec.groovy |    4 +-
 ...ToolingApiBuildExecutionCrossVersionSpec.groovy |    8 +-
 ...ildableEclipseModelFixesCrossVersionSpec.groovy |    8 +-
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |    6 +-
 .../ToolingApiGradleProjectCrossVersionSpec.groovy |    6 +-
 ...orsProjectCustomizationsCrossVersionSpec.groovy |    8 +-
 .../m5/ToolingApiIdeaModelCrossVersionSpec.groovy  |    8 +-
 .../m5/ToolingApiModelCrossVersionSpec.groovy      |   10 +-
 ...ReceivingStandardStreamsCrossVersionSpec.groovy |    8 +-
 ...UnsupportedModelFeedbackCrossVersionSpec.groovy |   38 +
 .../BuildEnvironmentModelCrossVersionSpec.groovy   |    8 +-
 .../ConsumingStandardInputCrossVersionSpec.groovy  |    8 +-
 ...adlePropertiesToolingApiCrossVersionSpec.groovy |   72 -
 .../m8/JavaConfigurabilityCrossVersionSpec.groovy  |    8 +-
 ...rictLongRunningOperationCrossVersionSpec.groovy |   25 +-
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |   10 +-
 .../m8/ToolingApiLoggingCrossVersionSpec.groovy    |   67 +-
 .../m8/UnknownModelFeedbackCrossVersionSpec.groovy |   42 -
 ...sionOnlyBuildEnvironmentCrossVersionSpec.groovy |   10 +-
 .../m9/DaemonErrorFeedbackCrossVersionSpec.groovy  |   10 +-
 ...adlePropertiesToolingApiCrossVersionSpec.groovy |   72 +
 .../M9JavaConfigurabilityCrossVersionSpec.groovy   |   10 +-
 ...singCommandLineArgumentsCrossVersionSpec.groovy |   13 +-
 .../r112/BuildInvocationsCrossVersionSpec.groovy   |  351 ++
 .../r112/FetchAllTaskSelectorsBuildAction.java     |   43 +
 .../r112/FetchTaskSelectorsBuildAction.java        |   39 +
 .../tooling/r112/FetchTasksBuildAction.java        |   46 +
 .../r112/PublicationsCrossVersionSpec.groovy       |  204 +
 .../r112/TaskDisplayNameCrossVersionSpec.groovy    |   50 +
 .../r112/TestFilteringCrossVersionSpec.groovy      |   48 +
 .../ToolingApiDeprecationsCrossVersionSpec.groovy  |  167 +
 .../r112/UserHomeDirCrossVersionSpec.groovy        |   47 +
 .../DependencyMetaDataCrossVersionSpec.groovy      |    8 +-
 .../r12rc1/BuildModelCrossVersionSpec.groovy       |    8 +-
 .../ProjectOutcomesModuleCrossVersionSpec.groovy   |    8 +-
 ...pportedOperationFeedbackCrossVersionSpec.groovy |   10 +-
 ...ApiInitScriptCrossVersionIntegrationTest.groovy |    4 +-
 ...ningCommandLineArgumentsCrossVersionSpec.groovy |    8 +-
 ...ApiConfigurationOnDemandCrossVersionSpec.groovy |    8 +-
 .../gradle/integtests/tooling/r16/CustomModel.java |   31 +
 .../r16/CustomToolingModelCrossVersionSpec.groovy  |   81 +
 ...knownCustomModelFeedbackCrossVersionSpec.groovy |   58 +
 .../integtests/tooling/r18/BrokenAction.java       |   29 +
 .../tooling/r18/BuildActionCrossVersionSpec.groovy |  104 +
 .../r18/BuildScriptModelCrossVersionSpec.groovy    |   94 +
 .../gradle/integtests/tooling/r18/CustomModel.java |   27 +
 .../integtests/tooling/r18/FetchCustomModel.java   |   31 +
 .../integtests/tooling/r18/FetchIdeaModel.java     |   27 +
 .../integtests/tooling/r18/FetchUnknownModel.java  |   32 +
 .../r18/GradleBuildModelCrossVersionSpec.groovy    |   77 +
 .../gradle/integtests/tooling/r18/NullAction.java  |   26 +
 .../r18/ProjectLevelModelCrossVersionSpec.groovy   |  111 +
 .../r18/UseGradleBuildToFetchProjectModel.java     |   37 +
 .../r18/UseOtherTypesToFetchProjectModel.java      |   56 +
 .../main/java/org/gradle/tooling/BuildAction.java  |   41 +
 .../org/gradle/tooling/BuildActionExecuter.java    |   60 +
 .../tooling/BuildActionFailureException.java       |   31 +
 .../java/org/gradle/tooling/BuildController.java   |  114 +
 .../java/org/gradle/tooling/BuildException.java    |    4 +-
 .../java/org/gradle/tooling/BuildLauncher.java     |   42 +-
 .../gradle/tooling/GradleConnectionException.java  |    2 +
 .../org/gradle/tooling/LongRunningOperation.java   |   26 +-
 .../main/java/org/gradle/tooling/ModelBuilder.java |   29 +-
 .../java/org/gradle/tooling/ProjectConnection.java |   63 +-
 .../org/gradle/tooling/UnknownModelException.java  |    4 +
 .../tooling/UnsupportedVersionException.java       |    2 +
 .../UnsupportedBuildArgumentException.java         |    6 +-
 .../tooling/internal/adapter/CollectionMapper.java |   50 +
 .../internal/adapter/CompatibleIntrospector.java   |   76 +
 .../tooling/internal/adapter/MethodInvocation.java |   79 +
 .../tooling/internal/adapter/MethodInvoker.java    |   21 +
 .../internal/adapter/NoOpMethodInvoker.java        |   24 +
 .../internal/adapter/ProtocolToModelAdapter.java   |  496 +++
 .../internal/adapter/SourceObjectMapping.java      |   28 +
 .../internal/adapter/TargetTypeProvider.java       |   26 +
 .../internal/build/DefaultBuildEnvironment.java    |   19 +-
 .../build/VersionOnlyBuildEnvironment.java         |   19 +-
 .../consumer/AbstractLongRunningOperation.java     |   77 +
 .../internal/consumer/BlockingResultHandler.java   |   16 +-
 .../internal/consumer/ConnectionFactory.java       |   24 +-
 .../internal/consumer/ConnectionParameters.java    |    2 +-
 .../internal/consumer/ConnectorServices.java       |   13 +-
 .../consumer/DefaultBuildActionExecuter.java       |   64 +
 .../internal/consumer/DefaultBuildLauncher.java    |  121 +-
 .../consumer/DefaultConnectionParameters.java      |  120 +-
 .../internal/consumer/DefaultGradleConnector.java  |   21 +-
 .../internal/consumer/DefaultModelBuilder.java     |  110 +-
 .../consumer/DefaultProjectConnection.java         |   40 +-
 .../tooling/internal/consumer/Distribution.java    |    4 +-
 .../internal/consumer/DistributionFactory.java     |   19 +-
 .../tooling/internal/consumer/LoggingProvider.java |    3 -
 .../tooling/internal/consumer/ModelProvider.java   |   80 -
 .../internal/consumer/SynchronizedLogging.java     |    4 +-
 .../internal/consumer/async/AsyncConnection.java   |   30 -
 .../async/AsyncConsumerActionExecutor.java         |   39 +
 .../consumer/async/DefaultAsyncConnection.java     |   88 -
 .../async/DefaultAsyncConsumerActionExecutor.java  |   66 +
 .../connection/AbstractConsumerConnection.java     |   17 +-
 .../consumer/connection/AbstractModelProducer.java |   33 +
 .../AbstractPost12ConsumerConnection.java          |   39 +
 .../AbstractPre12ConsumerConnection.java           |   57 +
 .../connection/ActionAwareConsumerConnection.java  |   61 +
 .../consumer/connection/AdaptedConnection.java     |   55 -
 .../BuildActionRunnerBackedConsumerConnection.java |   80 +-
 .../connection/BuildControllerAdapter.java         |   81 +
 .../BuildInvocationsAdapterProducer.java           |   49 +
 ...ConnectionVersion4BackedConsumerConnection.java |  175 +
 .../consumer/connection/ConsumerAction.java        |   25 +
 .../connection/ConsumerActionExecutor.java         |   29 +
 .../consumer/connection/ConsumerConnection.java    |    8 +-
 .../connection/ConsumerConnectionMetadata.java     |   44 -
 .../connection/GradleBuildAdapterProducer.java     |   44 +
 ...InternalConnectionBackedConsumerConnection.java |   74 +-
 .../consumer/connection/LazyConnection.java        |  143 -
 .../connection/LazyConsumerActionExecutor.java     |  115 +
 .../connection/LoggingInitializerConnection.java   |   54 -
 .../LoggingInitializerConsumerActionExecutor.java  |   46 +
 .../ModelBuilderBackedConsumerConnection.java      |  107 +
 .../ModelBuilderBackedModelProducer.java           |   55 +
 .../consumer/connection/ModelProducer.java         |   23 +
 .../connection/NoToolingApiConnection.java         |   49 +
 .../connection/ProgressLoggingConnection.java      |  102 -
 .../ProgressLoggingConsumerActionExecutor.java     |   85 +
 .../converters/BuildInvocationsConverter.java      |   86 +
 .../converters/ConsumerTargetTypeProvider.java     |   47 +
 .../consumer/converters/GradleBuildConverter.java  |   60 +
 .../converters/GradleProjectConverter.java         |   23 +-
 .../converters/GradleProjectMixInHandler.java      |   30 +
 .../GradleTaskDisplayNameMixInHandler.java         |   31 +
 .../converters/PropertyHandlerFactory.java         |   44 +
 .../consumer/converters/TaskNameComparator.java    |   54 +
 .../converters/TaskPropertyHandlerFactory.java     |   44 +
 .../loader/CachingToolingImplementationLoader.java |    6 +-
 .../loader/DefaultToolingImplementationLoader.java |   52 +-
 .../SynchronizedToolingImplementationLoader.java   |    7 +-
 .../loader/ToolingImplementationLoader.java        |    4 +-
 .../parameters/ConsumerConnectionParameters.java   |   36 -
 .../parameters/ConsumerOperationParameters.java    |  140 +-
 .../protocoladapter/ConsumerPropertyHandler.java   |   41 -
 .../consumer/protocoladapter/MethodInvocation.java |   76 -
 .../consumer/protocoladapter/MethodInvoker.java    |   21 -
 .../protocoladapter/ProtocolToModelAdapter.java    |  358 --
 .../protocoladapter/TargetTypeProvider.java        |   55 -
 .../internal/consumer/versioning/ModelMapping.java |  118 +-
 .../consumer/versioning/VersionDetails.java        |   54 +-
 .../eclipse/DefaultEclipseExternalDependency.java  |   54 -
 .../eclipse/DefaultEclipseLinkedResource.java      |   55 -
 .../internal/eclipse/DefaultEclipseProject.java    |  134 -
 .../eclipse/DefaultEclipseProjectDependency.java   |   44 -
 .../eclipse/DefaultEclipseSourceDirectory.java     |   44 -
 .../internal/eclipse/DefaultEclipseTask.java       |   56 -
 .../internal/gradle/BasicGradleProject.java        |   55 +
 .../internal/gradle/BasicGradleTaskSelector.java   |   76 +
 .../internal/gradle/DefaultBuildInvocations.java   |   46 +
 .../gradle/DefaultConvertedGradleProject.java      |   58 +
 .../internal/gradle/DefaultGradleBuild.java        |   43 +
 .../gradle/DefaultGradleModuleVersion.java         |    3 -
 .../internal/gradle/DefaultGradleProject.java      |  102 +-
 .../internal/gradle/DefaultGradleProjectTask.java  |   30 +
 .../internal/gradle/DefaultGradlePublication.java  |   41 +
 .../internal/gradle/DefaultGradleScript.java       |   34 +
 .../tooling/internal/gradle/DefaultGradleTask.java |   41 +-
 .../gradle/DefaultProjectPublications.java         |   33 +
 .../internal/gradle/GradleProjectIdentity.java     |   21 +
 .../internal/gradle/PartialBasicGradleProject.java |   69 +
 .../internal/gradle/PartialGradleProject.java      |   95 +
 .../internal/gradle/TaskListingLaunchable.java     |   23 +
 .../internal/idea/DefaultIdeaCompilerOutput.java   |   68 -
 .../internal/idea/DefaultIdeaContentRoot.java      |   83 -
 .../internal/idea/DefaultIdeaDependencyScope.java  |   67 -
 .../internal/idea/DefaultIdeaLanguageLevel.java    |   85 -
 .../tooling/internal/idea/DefaultIdeaModule.java   |  125 -
 .../internal/idea/DefaultIdeaModuleDependency.java |   69 -
 .../tooling/internal/idea/DefaultIdeaProject.java  |  117 -
 .../DefaultIdeaSingleEntryLibraryDependency.java   |  103 -
 .../internal/idea/DefaultIdeaSourceDirectory.java  |   46 -
 .../outcomes/DefaultGradleBuildOutcome.java        |   46 -
 .../outcomes/DefaultGradleFileBuildOutcome.java    |   42 -
 .../internal/outcomes/DefaultProjectOutcomes.java  |   80 -
 .../internal/protocol/BuildActionRunner.java       |   16 +
 .../internal/protocol/BuildExceptionVersion1.java  |    5 +-
 .../protocol/BuildOperationParametersVersion1.java |   23 +
 .../tooling/internal/protocol/BuildParameters.java |    4 +-
 .../internal/protocol/BuildParametersVersion1.java |    7 +
 .../internal/protocol/ConfigurableConnection.java  |   14 +
 .../internal/protocol/ConnectionParameters.java    |    2 +-
 .../internal/protocol/ConnectionVersion4.java      |   59 +-
 .../protocol/InternalBasicIdeaProject.java         |    4 +-
 .../internal/protocol/InternalBuildAction.java     |   36 +
 .../protocol/InternalBuildActionExecutor.java      |   51 +
 .../InternalBuildActionFailureException.java       |   30 +
 .../internal/protocol/InternalBuildController.java |   53 +
 .../protocol/InternalBuildEnvironment.java         |    3 +-
 .../internal/protocol/InternalConnection.java      |   17 +-
 .../internal/protocol/InternalGradleProject.java   |    2 +
 .../internal/protocol/InternalIdeaProject.java     |    2 +
 .../internal/protocol/InternalLaunchable.java      |   29 +
 .../internal/protocol/InternalProjectOutcomes.java |    7 +-
 .../protocol/InternalProtocolInterface.java        |   12 +-
 .../internal/protocol/InternalTestModel.java       |   24 -
 .../InternalUnsupportedModelException.java         |   31 +
 .../LongRunningOperationParametersVersion1.java    |    8 +
 .../tooling/internal/protocol/ModelBuilder.java    |   51 +
 .../tooling/internal/protocol/ModelIdentifier.java |   42 +
 .../tooling/internal/protocol/ProjectVersion3.java |   17 +
 .../eclipse/EclipseLinkedResourceVersion1.java     |    2 -
 .../protocol/eclipse/EclipseProjectVersion3.java   |    2 +
 .../HierarchicalEclipseProjectVersion1.java        |    2 +
 .../internal/reflect/CompatibleIntrospector.java   |   71 -
 .../org/gradle/tooling/model/BuildableElement.java |    1 +
 .../java/org/gradle/tooling/model/Element.java     |    2 +
 .../gradle/tooling/model/ExternalDependency.java   |    2 +-
 .../gradle/tooling/model/GradleModuleVersion.java  |    2 +-
 .../org/gradle/tooling/model/GradleProject.java    |   21 +-
 .../java/org/gradle/tooling/model/GradleTask.java  |    1 +
 .../gradle/tooling/model/HierarchicalElement.java  |    2 +
 .../java/org/gradle/tooling/model/Launchable.java  |   47 +
 .../main/java/org/gradle/tooling/model/Model.java  |    1 +
 .../main/java/org/gradle/tooling/model/Task.java   |   16 +-
 .../org/gradle/tooling/model/TaskSelector.java     |   34 +
 .../tooling/model/build/BuildEnvironment.java      |    7 +-
 .../tooling/model/build/GradleEnvironment.java     |    2 +
 .../tooling/model/build/JavaEnvironment.java       |    4 +
 .../gradle/tooling/model/build/package-info.java   |    2 +-
 .../model/eclipse/EclipseLinkedResource.java       |   10 +-
 .../tooling/model/eclipse/EclipseProject.java      |   15 +-
 .../gradle/tooling/model/eclipse/EclipseTask.java  |    5 +-
 .../model/eclipse/HierarchicalEclipseProject.java  |    5 +
 .../gradle/tooling/model/eclipse/package-info.java |    2 +-
 .../tooling/model/gradle/BasicGradleProject.java   |   69 +
 .../tooling/model/gradle/BuildInvocations.java     |   52 +
 .../gradle/tooling/model/gradle/GradleBuild.java   |   42 +
 .../tooling/model/gradle/GradlePublication.java    |   35 +
 .../gradle/tooling/model/gradle/GradleScript.java  |   40 +
 .../tooling/model/gradle/ProjectPublications.java  |   34 +
 .../gradle/tooling/model/gradle/package-info.java  |   20 +
 .../tooling/model/idea/BasicIdeaProject.java       |    2 +
 .../gradle/tooling/model/idea/IdeaDependency.java  |    2 +-
 .../org/gradle/tooling/model/idea/IdeaModule.java  |    9 +-
 .../org/gradle/tooling/model/idea/IdeaProject.java |    7 +-
 .../gradle/tooling/model/idea/package-info.java    |    2 +-
 .../gradle/tooling/model/internal/Exceptions.java  |   41 +-
 .../ProjectSensitiveToolingModelBuilder.java       |   41 +
 .../gradle/tooling/model/internal/TestModel.java   |   26 -
 .../org/gradle/tooling/model/package-info.java     |    2 +-
 .../tooling/fixture/GradleVersionSpecTest.groovy   |  146 +
 .../internal/adapter/CollectionMapperTest.groovy   |   49 +
 .../adapter/CompatibleIntrospectorTest.groovy      |   79 +
 .../adapter/ProtocolToModelAdapterTest.groovy      |  501 +++
 .../internal/consumer/ConnectionFactoryTest.groovy |   19 +-
 .../internal/consumer/ConnectorServicesTest.groovy |    3 -
 .../consumer/DefaultBuildActionExecuterTest.groovy |  164 +
 .../consumer/DefaultBuildLauncherTest.groovy       |  281 +-
 .../consumer/DefaultGradleConnectorTest.groovy     |   18 +
 .../consumer/DefaultModelBuilderTest.groovy        |  159 +-
 .../consumer/DefaultProjectConnectionTest.groovy   |   30 +-
 .../consumer/DistributionFactoryTest.groovy        |   56 +-
 .../consumer/ProtocolToModelAdapterTest.groovy     |   68 -
 .../consumer/SynchronizedLoggingTest.groovy        |    3 -
 .../DefaultAsyncConsumerActionExecutorTest.groovy  |   81 +
 .../ActionAwareConsumerConnectionTest.groovy       |   77 +
 .../connection/AdaptedConnectionTest.groovy        |   50 -
 ...ActionRunnerBackedConsumerConnectionTest.groovy |  115 +-
 .../connection/BuildControllerAdapterTest.groovy   |  122 +
 ...tionVersion4BackedConsumerConnectionTest.groovy |  288 ++
 .../GradleBuildAdapterProducerTest.groovy          |   88 +
 ...alConnectionBackedConsumerConnectionTest.groovy |  112 +-
 .../consumer/connection/LazyConnectionTest.groovy  |  107 -
 .../LazyConsumerActionExecutorTest.groovy          |  102 +
 ...ModelBuilderBackedConsumerConnectionTest.groovy |  186 +
 .../ModelBuilderBackedModelProducerTest.groovy     |   74 +
 .../ProgressLoggingConnectionTest.groovy           |   55 -
 ...rogressLoggingConsumerActionExecutorTest.groovy |   55 +
 .../BuildInvocationsConverterTest.groovy           |  101 +
 .../converters/GradleBuildConverterTest.groovy     |   89 +
 .../converters/TaskNameComparatorTest.groovy       |   41 +
 .../CachingToolingImplementationLoaderTest.groovy  |   19 +-
 .../DefaultToolingImplementationLoaderTest.groovy  |  104 +-
 ...chronizedToolingImplementationLoaderTest.groovy |    7 +-
 .../ConsumerOperationParametersTest.groovy         |   17 +-
 .../ProtocolToModelAdapterTest.groovy              |  271 --
 .../consumer/versioning/ModelMappingTest.groovy    |  115 +
 .../eclipse/DefaultEclipseProjectTest.groovy       |   27 -
 .../gradle/DefaultGradleProjectTest.groovy         |   12 +-
 .../reflect/CompatibleIntrospectorTest.groovy      |   59 -
 .../tooling/fixture/GradleVersionSpec.java         |   83 +
 subprojects/tooling-api/tooling-api.gradle         |    9 +-
 .../integtests/FavoritesIntegrationTest.java       |    2 -
 .../integtests/LiveOutputIntegrationTest.groovy    |   16 +-
 ...projectProjectAndTaskListIntegrationTest.groovy |    3 -
 .../gradle/foundation/CommandLineAssistant.java    |    2 -
 .../org/gradle/foundation/PathParserPortion.java   |    2 -
 .../org/gradle/foundation/ProjectConverter.java    |    2 -
 .../java/org/gradle/foundation/ProjectView.java    |    2 -
 .../main/java/org/gradle/foundation/TaskView.java  |    2 -
 .../gradle/foundation/common/ListReorderer.java    |    2 -
 .../org/gradle/foundation/common/ObserverLord.java |    3 -
 .../gradle/foundation/common/ReorderableList.java  |    3 -
 .../gradle/foundation/ipc/basic/ClientProcess.java |    2 -
 .../gradle/foundation/ipc/basic/ExecutionInfo.java |    4 -
 .../gradle/foundation/ipc/basic/MessageObject.java |    2 -
 .../foundation/ipc/basic/ObjectSocketWrapper.java  |    2 -
 .../ipc/basic/ProcessLauncherServer.java           |    2 -
 .../org/gradle/foundation/ipc/basic/Server.java    |    2 -
 .../ipc/gradle/AbstractGradleServerProtocol.java   |    4 +-
 .../gradle/ExecuteGradleCommandClientProtocol.java |    2 -
 .../gradle/ExecuteGradleCommandServerProtocol.java |    2 -
 .../gradle/foundation/ipc/gradle/IPCUtilities.java |    2 -
 .../ipc/gradle/KillGradleClientProtocol.java       |    2 -
 .../ipc/gradle/KillGradleServerProtocol.java       |    2 -
 .../foundation/ipc/gradle/ProtocolConstants.java   |    2 -
 .../ipc/gradle/TaskListClientProtocol.java         |    2 -
 .../ipc/gradle/TaskListServerProtocol.java         |    2 -
 .../org/gradle/foundation/output/FileLink.java     |    2 -
 .../foundation/output/FileLinkDefinitionLord.java  |    2 -
 .../gradle/foundation/output/LiveOutputParser.java |    2 -
 .../org/gradle/foundation/output/OutputParser.java |    2 -
 .../definitions/ExtensionFileLinkDefinition.java   |    2 -
 .../output/definitions/FileLinkDefinition.java     |    2 -
 .../OptionalLineNumberFileLinkDefinition.java      |    2 -
 .../definitions/PrefixedFileLinkDefinition.java    |    2 -
 .../definitions/TestReportFileLinkDefinition.java  |    2 -
 .../gradle/foundation/queue/ExecutionQueue.java    |   18 +-
 .../visitors/AllProjectsAndTasksVisitor.java       |   10 -
 .../visitors/TaskTreePopulationVisitor.java        |   17 +-
 .../visitors/UniqueNameProjectAndTaskVisitor.java  |   14 +-
 .../CommandLineArgumentAlteringListener.java       |    2 -
 .../gradleplugin/foundation/DOM4JSerializer.java   |    2 -
 .../gradleplugin/foundation/Dom4JUtility.java      |    1 -
 .../foundation/ExtensionFileFilter.java            |    2 -
 .../gradleplugin/foundation/GradlePluginLord.java  |  129 +-
 .../foundation/favorites/FavoriteTask.java         |    2 -
 .../foundation/favorites/FavoritesEditor.java      |    2 -
 .../favorites/FavoritesSerializable.java           |    5 +-
 .../filters/AllowAllProjectAndTaskFilter.java      |    2 -
 .../foundation/filters/BasicFilterEditor.java      |    2 -
 .../filters/BasicProjectAndTaskFilter.java         |    2 -
 .../foundation/filters/ProjectAndTaskFilter.java   |    3 -
 .../foundation/request/AbstractRequest.java        |   10 +-
 .../foundation/request/ExecutionRequest.java       |    6 +-
 .../foundation/request/RefreshTaskListRequest.java |    6 +-
 .../gradleplugin/foundation/request/Request.java   |    2 -
 .../foundation/runner/GradleRunner.java            |    2 -
 .../foundation/search/BasicTextSearchCriteria.java |    2 -
 .../foundation/search/TextBlockSearchEditor.java   |    3 -
 .../foundation/settings/DOM4JSettingsNode.java     |    2 -
 .../foundation/settings/SettingsNode.java          |    2 -
 .../foundation/settings/SettingsSerializable.java  |    3 -
 .../userinterface/AlternateUIInteraction.java      |    3 -
 .../swing/common/BorderlessImageButton.java        |    2 -
 .../swing/common/BorderlessImageToggleButton.java  |    3 -
 .../swing/common/BorderlessUtility.java            |    2 -
 .../swing/common/PreferencesAssistant.java         |    2 -
 .../userinterface/swing/common/SearchPanel.java    |   21 +-
 .../swing/common/TextPaneSearchInteraction.java    |    4 +-
 .../swing/generic/AbstractGradleUIInstance.java    |    3 -
 .../userinterface/swing/generic/BasicGradleUI.java |   10 -
 .../swing/generic/DualPaneUIInstance.java          |    2 -
 .../swing/generic/MainGradlePanel.java             |   14 +-
 .../userinterface/swing/generic/OutputPanel.java   |   26 +-
 .../swing/generic/OutputPanelLord.java             |    9 +-
 .../userinterface/swing/generic/OutputTab.java     |   16 +-
 .../swing/generic/SinglePaneUIInstance.java        |    2 -
 .../SwingAddMultipleFavoritesInteraction.java      |    2 -
 .../generic/SwingEditFavoriteInteraction.java      |   10 +-
 .../swing/generic/SwingExportInteraction.java      |    8 +-
 .../swing/generic/SwingImportInteraction.java      |    2 -
 .../swing/generic/TaskTreeComponent.java           |    6 +-
 .../userinterface/swing/generic/Utility.java       |   18 +-
 .../generic/filter/AbstractFilterEditorPanel.java  |    2 -
 .../generic/filter/ProjectAndTaskFilterDialog.java |    2 -
 .../swing/generic/tabs/CommandLineTab.java         |    2 -
 .../swing/generic/tabs/FavoriteTasksTab.java       |    2 -
 .../swing/generic/tabs/GradleTab.java              |    3 -
 .../userinterface/swing/generic/tabs/SetupTab.java |    2 -
 .../swing/generic/tabs/TaskTreeTab.java            |   13 +-
 .../swing/standalone/Application.java              |    2 -
 .../swing/standalone/BlockingApplication.java      |    2 -
 .../openapi/wrappers/RunnerWrapperFactory.java     |    3 -
 .../gradle/openapi/wrappers/UIWrapperFactory.java  |    2 -
 .../foundation/GradleInterfaceWrapperVersion1.java |    2 -
 .../foundation/GradleInterfaceWrapperVersion2.java |    2 -
 .../wrappers/foundation/ProjectWrapper.java        |    2 -
 .../foundation/RequestObserverWrapper.java         |    2 -
 .../wrappers/foundation/RequestWrapper.java        |    2 -
 .../openapi/wrappers/foundation/TaskWrapper.java   |    2 -
 .../foundation/favorites/FavoriteTaskWrapper.java  |    2 -
 .../favorites/FavoritesEditorWrapper.java          |    2 -
 .../runner/GradleRunnerInteractionWrapper.java     |    2 -
 .../wrappers/runner/GradleRunnerWrapper.java       |    2 -
 .../wrappers/ui/AbstractOpenAPIUIWrapper.java      |    3 -
 .../ui/AlternateUIInteractionVersionWrapper.java   |    2 -
 ...CommandLineArgumentAlteringListenerWrapper.java |    2 -
 .../openapi/wrappers/ui/DualPaneUIWrapper.java     |    2 -
 .../wrappers/ui/GradleTabVersionWrapper.java       |    2 -
 .../openapi/wrappers/ui/OutputObserverWrapper.java |    2 -
 .../openapi/wrappers/ui/OutputUILordWrapper.java   |    2 -
 .../wrappers/ui/SettingsNodeVersionWrapper.java    |    2 -
 .../openapi/wrappers/ui/SinglePaneUIWrapper.java   |    2 -
 .../org/gradle/foundation/BuildInformation.java    |    7 +-
 .../gradle/foundation/CommandLineParsingTest.java  |    2 -
 .../gradle/foundation/DOM4JSettingsNodeTest.java   |    2 -
 .../org/gradle/foundation/FavoritesTest.java       |    2 -
 .../org/gradle/foundation/FileLinkTests.java       |    1 -
 .../groovy/org/gradle/foundation/FilterTest.java   |    2 -
 .../gradle/foundation/LiveOutputParserTests.java   |    2 -
 .../groovy/org/gradle/foundation/TestUtility.java  |    8 +-
 .../foundation/TextBlockSearchEditorTests.java     |    2 -
 subprojects/ui/ui.gradle                           |    2 +-
 .../WrapperConcurrentDownloadTest.groovy           |   89 +
 .../WrapperGenerationIntegrationTest.groovy        |   49 +
 .../integtests/WrapperHttpIntegrationTest.groovy   |  148 +
 .../WrapperProjectIntegrationTest.groovy           |  125 +-
 .../org/gradle/integtests/WrapperSetup.groovy      |   28 +
 .../WrapperUserHomeIntegrationTest.groovy          |   72 +
 .../org/gradle/wrapper/BootstrapMainStarter.java   |    3 -
 .../src/main/java/org/gradle/wrapper/Download.java |    7 -
 .../gradle/wrapper/ExclusiveFileAccessManager.java |   95 +
 .../org/gradle/wrapper/GradleUserHomeLookup.java   |   36 +
 .../java/org/gradle/wrapper/GradleWrapperMain.java |   51 +-
 .../main/java/org/gradle/wrapper/IDownload.java    |    3 -
 .../src/main/java/org/gradle/wrapper/Install.java  |  111 +-
 .../java/org/gradle/wrapper/PathAssembler.java     |    3 -
 .../gradle/wrapper/SystemPropertiesHandler.java    |    3 -
 .../org/gradle/wrapper/WrapperConfiguration.java   |   21 -
 .../java/org/gradle/wrapper/WrapperExecutor.java   |    3 -
 .../groovy/org/gradle/wrapper/DownloadTest.groovy  |    3 -
 .../groovy/org/gradle/wrapper/InstallTest.groovy   |   97 +-
 .../org/gradle/wrapper/PathAssemblerTest.java      |    3 -
 .../wrapper/SystemPropertiesHandlerTest.groovy     |    3 -
 subprojects/wrapper/wrapper.gradle                 |    3 +-
 version.txt                                        |    2 +-
 5736 files changed, 219741 insertions(+), 78490 deletions(-)

diff --git a/build.gradle b/build.gradle
index d5bca86..4de6d0f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,23 +28,29 @@ buildTypes {
     sanityCheck "classes", "doc:checkstyleApi", "codeQuality", "docs:check"
 
     // The minimum to be run before check-in
-    preCommitBuild "doc:checkstyleApi", "docs:check", "codeQuality", "classes", "test", noDistTests: true
-    quickCheck "doc:checkstyleApi", "docs:check", "codeQuality", "classes", "test", noDistTests: true
+    preCommitBuild "doc:checkstyleApi", "docs:check", "codeQuality", "classes", "test"
+    quickCheck "doc:checkstyleApi", "docs:check", "codeQuality", "classes", "test"
+
+    // For testing pull requests
+    pullRequestValidation "doc:checkstyleApi", "docs:check", "codeQuality", "classes"
 
     // A full (in-process) test
     developerBuild "check"
 
     // Used by the first phase of the build pipeline
-    quickTest "test", "integTest", noDistTests: true, noDocsTests: true
+    quickTest "runtimeTests", "runtimeIntegTests"
+
+    // Used for builds to run all tests, but not necessarily on all platforms
+    fullTest "runtimeTests", "runtimeIntegTests", useIncomingDistributions: true, defaultIntegTestExecuter: "forking"
 
     // Used for builds to test the code on certain platforms
-    platformTest "test", "forkingIntegTest", noDistTests: true, noDocsTests: true, useIncomingDistributions: true
+    platformTest "runtimeTests", "runtimeIntegTests", useIncomingDistributions: true, defaultIntegTestExecuter: "forking", testAllPlatforms: true
 
     // Tests using the daemon mode
-    daemonTest "daemonIntegTest", noDistTests: true, noDocsTests: true, useIncomingDistributions: true
+    daemonTest "runtimeIntegTests", useIncomingDistributions: true, defaultIntegTestExecuter: "daemon"
 
     // Run the integration tests using the parallel executer
-    parallelTest "parallelIntegTest", noDistTests: true, noDocsTests: true, useIncomingDistributions: true
+    parallelTest "runtimeIntegTests", useIncomingDistributions: true, defaultIntegTestExecuter: "parallel"
 
     // Run the performance tests
     performanceTest "performance:integTest", useIncomingDistributions: true
@@ -53,7 +59,7 @@ buildTypes {
     localPerformanceTest "performance:integTest"
 
     // Used for cross version tests on CI
-    crossVersionTest "forkingIntegTest", crossVersionTestsOnly: "", testAllVersions: "", noDistTests: true, useIncomingDistributions: true
+    crossVersionTest "crossVersionIntegTest", useIncomingDistributions: true
 
     // Used to build production distros and smoke test them
     packageBuild "verifyIsProductionBuildEnvironment", "clean", "buildDists", "distributions:integTest"
@@ -87,7 +93,7 @@ ext {
     pluginProjects = [
         'plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven',
         'ide', 'announce', 'scala', 'sonar', 'signing', 'cpp', 'ear', 'javascript', 'buildComparison',
-        'diagnostics', 'reporting', 'publish', 'ivy'
+        'diagnostics', 'reporting', 'publish', 'ivy', 'jacoco', 'buildInit', 'languageJvm', 'languageBase'
     ].collect {
         project(it)
     }
@@ -102,6 +108,8 @@ apply from: "gradle/idea.gradle"
 apply from: "gradle/eclipse.gradle"
 apply from: "gradle/classycle.gradle"
 apply from: "gradle/noDependencyResolutionDuringConfiguration.gradle"
+apply from: "gradle/testSetup.gradle"
+apply from: "gradle/testGroupings.gradle"
 
 allprojects {
     group = 'org.gradle'
@@ -136,20 +144,20 @@ configurations {
     runtime {
         visible = false
     }
-    plugins {
+    gradlePlugins {
         visible = false
     }
     testRuntime {
         extendsFrom runtime
-        extendsFrom plugins
+        extendsFrom gradlePlugins
     }
 }
 
 dependencies {
     runtime project(':launcher')
     runtime project(':wrapper')
-    plugins pluginProjects
-    plugins project(':coreImpl')
+    gradlePlugins pluginProjects
+    gradlePlugins project(':coreImpl')
 }
 
 task verifyIsProductionBuildEnvironment << {
@@ -160,7 +168,7 @@ task verifyIsProductionBuildEnvironment << {
 
 task waitForDaemonsToDie {
     if (!project.hasProperty("noWaitForDaemonsToDie")) {
-        if (isWindows && isCiServer) {
+        if (isWindows && isCiServer && buildTypes.activeNames.contains("crossVersionTest")) {
             gradle.startParameter.taskNames.add(0, it.path)
         }
         doLast {
@@ -193,4 +201,5 @@ task installAll(type: Install) {
     installDirPropertyName = 'gradle_installPath'
 }
 
+
 apply from: "gradle/intTestImage.gradle"
\ No newline at end of file
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 6973939..538676d 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -27,9 +27,9 @@ repositories {
 
 dependencies {
     compile gradleApi()
-    compile 'com.google.guava:guava:11.0.2 at jar'
+    compile 'com.google.guava:guava-jdk5:14.0.1 at jar'
     compile 'commons-lang:commons-lang:2.6 at jar'
-    groovy localGroovy()
+    compile localGroovy()
     testCompile 'junit:junit:4.11 at jar'
     testCompile 'org.spockframework:spock-core:0.7-groovy-1.8 at jar', 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2'
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
index 4490268..e582417 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
@@ -64,7 +64,7 @@ class BuildTypes {
                     if (!project.hasProperty(k)) {
                         project.ext."$k" = null
                     }
-                    project.properties."$k" = v
+                    project."$k" = v
                 }
             }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy b/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy
index 0af4c56..9b9e789 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy
@@ -17,14 +17,12 @@
 package org.gradle.build
 
 import org.gradle.api.tasks.bundling.Jar
-import org.gradle.api.*
-import org.gradle.api.file.*
-import org.gradle.api.tasks.*
 import com.tonicsystems.jarjar.Main as JarJarMain
+import org.gradle.api.tasks.Input
 
 /*
 * a Jar task that performs JarJar repackaging after archive is created
-* */
+ */
 class JarJarJar extends Jar {
     @Input def rules = [:]
     @Input def keeps = []
diff --git a/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy b/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy
new file mode 100644
index 0000000..96e21ab
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy
@@ -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
+
+import org.gradle.api.GradleException
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.util.GradleVersion
+
+class ReleasedVersions {
+    private static final Logger LOGGER = Logging.getLogger(ReleasedVersions.class)
+    private static final int MILLIS_PER_DAY = 24 * 60 * 60 * 1000
+
+    private lowestInterestingVersion = GradleVersion.version("0.8")
+    private def versions
+
+    File destFile
+    String url = "https://services.gradle.org/versions/all"
+    boolean offline
+
+    void prepare() {
+        download()
+        versions = calculateVersions()
+    }
+
+    private void download() {
+        if (offline) {
+            LOGGER.warn("Versions information will not be downloaded because --offline switch is used.\n"
+                    + "Without the version information certain integration tests may fail or use outdated version details.")
+            return
+        }
+        if (destFile.isFile() && destFile.lastModified() > System.currentTimeMillis() - MILLIS_PER_DAY) {
+            LOGGER.info("Don't download released versions from $url as the output file already exists and is not out-of-date.")
+            return
+        }
+        LOGGER.lifecycle "Downloading the released versions from: $url"
+
+        def json
+        try {
+            json = new URL(url).text
+        } catch (UnknownHostException e) {
+            throw new GradleException("Unable to acquire versions info. I've tried this url: '$url'.\n"
+                    + "If you don't have the network connection please run with '--offline' or exclude this task from execution via '-x'."
+                    , e)
+        }
+
+        destFile.parentFile.mkdirs()
+        destFile.text = json
+
+        LOGGER.info "Saved released versions information in: $destFile"
+    }
+
+    String getMostRecentFinalRelease() {
+        return versions.findAll { it.rcFor == "" }.first().version.version
+    }
+
+    List<String> getAllVersions() {
+        return versions*.version*.version
+    }
+
+    List<Map<String, ?>> calculateVersions() {
+        def versions = new groovy.json.JsonSlurper().parseText(destFile.text).findAll {
+            (it.activeRc == true || it.rcFor == "") && it.broken == false && it.snapshot == false
+        }.collect {
+            it.version = GradleVersion.version(it.version)
+            it
+        }.findAll {
+            it.version >= lowestInterestingVersion
+        }.sort {
+            it.version
+        }.reverse()
+
+        if (versions.size() < 10) {
+            throw new IllegalStateException("Too few previous releases found in ${destFile}: " + versions)
+        }
+
+        return versions
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java b/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java
index 07beede..874c572 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.build.docs;
 
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 @Contextual
 public class DocGenerationException extends RuntimeException {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy
index 2ddf336..e86a2b0 100755
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy
@@ -18,7 +18,7 @@ package org.gradle.build.docs
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.file.FileCollection
 import org.gradle.api.file.FileVisitDetails
-import org.gradle.util.ClasspathUtil
+import org.gradle.internal.classloader.ClasspathUtil
 import org.gradle.api.tasks.*
 import org.gradle.api.logging.LogLevel
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy
index ca0e38c..6a917b6 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy
@@ -19,8 +19,6 @@ import org.w3c.dom.Element
 
 /**
  * Validates sample element - looks for missing attributes.
- *
- * @author Tomek Kaczanowski
  */
 public class SampleElementValidator {
 
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 02dad83..b85b563 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
@@ -29,7 +29,7 @@ import org.w3c.dom.Element
 import org.gradle.api.tasks.*
 
 /**
- * Transforms userguide source into docbook, replacing custom xml elements.
+ * Transforms userguide source into docbook, replacing custom XML elements.
  *
  * Takes the following as input:
  * <ul>
@@ -227,8 +227,11 @@ public class UserGuideTransformTask extends DefaultTask {
                     String args = child.'@args'
                     String outputFile = child.'@outputFile' ?: "${sampleId}.out"
                     boolean ignoreExtraLines = child.'@ignoreExtraLines' ?: false
+                    boolean ignoreLineOrder = child.'@ignoreLineOrder' ?: false
+                    boolean expectFailure = child.'@expectFailure' ?: false
 
-                    samplesXml << { sample(id: sampleId, dir: srcDir, args: args, outputFile: outputFile, ignoreExtraLines: ignoreExtraLines) }
+                    samplesXml << { sample(id: sampleId, dir: srcDir, args: args, outputFile: outputFile,
+                                           ignoreExtraLines: ignoreExtraLines, ignoreLineOrder: ignoreLineOrder, expectFailure: expectFailure) }
 
                     Element outputTitle = doc.createElement("para")
                     outputTitle.appendChild(doc.createTextNode("Output of "))
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 9af98bf..aae62ba 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
@@ -34,6 +34,8 @@ class BasicJavadocLexer implements JavadocLexer {
     private static final Pattern TAG = Pattern.compile("(?s)\\{@.+?\\}");
     private static final Pattern END_TAG_NAME = Pattern.compile("(?s)\\s|}");
     private static final Pattern WHITESPACE_WITH_EOL = Pattern.compile("(?s)\\s+");
+    private static final String START_HTML_COMMENT = "<!--";
+    private static final String END_HTML_COMMENT = "-->";
     private static final Map<String, String> ENTITIES = new HashMap<String, String>();
 
     static {
@@ -56,6 +58,10 @@ class BasicJavadocLexer implements JavadocLexer {
 
     public void visit(TokenVisitor visitor) {
         while (!scanner.isEmpty()) {
+            if (scanner.lookingAt(START_HTML_COMMENT)) {
+                skipComment();
+                continue;
+            }
             if (scanner.lookingAt(HTML_ELEMENT)) {
                 parseStartElement(visitor);
                 continue;
@@ -67,6 +73,10 @@ class BasicJavadocLexer implements JavadocLexer {
 
             StringBuilder text = new StringBuilder();
             while (!scanner.isEmpty()) {
+                if (scanner.lookingAt(START_HTML_COMMENT)) {
+                    skipComment();
+                    continue;
+                }
                 if (scanner.lookingAt(HTML_ELEMENT)) {
                     break;
                 }
@@ -88,6 +98,16 @@ class BasicJavadocLexer implements JavadocLexer {
         visitor.onEnd();
     }
 
+    private void skipComment() {
+        scanner.next(4);
+        while (!scanner.isEmpty() && !scanner.lookingAt(END_HTML_COMMENT)) {
+            scanner.next();
+        }
+        if (!scanner.isEmpty()) {
+            scanner.next(3);
+        }
+    }
+
     private void parseHtmlEntity(StringBuilder buffer) {
         scanner.next();
         scanner.mark();
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
index 987786e..17b1a05 100644
--- 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
@@ -24,7 +24,7 @@ import org.w3c.dom.Element;
 
 import java.util.Collection;
 
-public class BlocksRenderer {
+class BlocksRenderer implements ClassDocMemberRenderer {
     private final BlockTableRenderer blockTableRenderer = new BlockTableRenderer();
     private final ExtensionBlocksSummaryRenderer extensionBlocksSummaryRenderer;
     private final BlockDetailRenderer blockDetailRenderer;
@@ -34,7 +34,7 @@ public class BlocksRenderer {
         extensionBlocksSummaryRenderer = new ExtensionBlocksSummaryRenderer(blockTableRenderer);
     }
 
-    public void renderTo(ClassDoc classDoc, Element parent) {
+    public void renderSummaryTo(ClassDoc classDoc, Element parent) {
         Document document = parent.getOwnerDocument();
 
         Element summarySection = document.createElement("section");
@@ -44,11 +44,8 @@ public class BlocksRenderer {
         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);
 
@@ -60,32 +57,47 @@ public class BlocksRenderer {
         }
 
         for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
-            hasBlocks |= !extensionDoc.getExtensionBlocks().isEmpty();
             extensionBlocksSummaryRenderer.renderTo(extensionDoc, summarySection);
         }
 
-        if (!hasBlocks) {
+        if (!hasBlocks(classDoc)) {
             Element para = document.createElement("para");
             summarySection.appendChild(para);
             para.appendChild(document.createTextNode("No script blocks"));
-            return;
         }
+    }
 
+    public void renderDetailsTo(ClassDoc classDoc, Element parent) {
+        if (hasBlocks(classDoc)) {
+            Document document = parent.getOwnerDocument();
 
-        Element detailsSection = document.createElement("section");
-        parent.appendChild(detailsSection);
+            Element detailsSection = document.createElement("section");
+            parent.appendChild(detailsSection);
 
-        title = document.createElement("title");
-        detailsSection.appendChild(title);
-        title.appendChild(document.createTextNode("Script block details"));
+            Element 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()) {
+            for (BlockDoc blockDoc : classDoc.getClassBlocks()) {
                 blockDetailRenderer.renderTo(blockDoc, detailsSection);
             }
+            for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+                for (BlockDoc blockDoc : extensionDoc.getExtensionBlocks()) {
+                    blockDetailRenderer.renderTo(blockDoc, detailsSection);
+                }
+            }
         }
     }
+
+    private boolean hasBlocks(ClassDoc classDoc) {
+        boolean hasBlocks = false;
+        if (!classDoc.getClassBlocks().isEmpty()) {
+            hasBlocks = true;
+        }
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            hasBlocks |= !extensionDoc.getExtensionBlocks().isEmpty();
+        }
+        return hasBlocks;
+    }
+
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMemberRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMemberRenderer.java
new file mode 100644
index 0000000..90c51a8
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMemberRenderer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface ClassDocMemberRenderer {
+    void renderSummaryTo(ClassDoc classDoc, Element parent);
+
+    void renderDetailsTo(ClassDoc classDoc, Element parent);
+}
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
index 01ee7fc..e45d5c1 100644
--- 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
@@ -80,8 +80,7 @@ public class ClassDocMethodsBuilder extends ModelBuilderSupport {
             }
         }
 
-        ClassDoc supertype = classDoc.getSuperClass();
-        if (supertype != null) {
+        for (ClassDoc supertype : classDoc.getSuperTypes()) {
             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
index 3e21967..e293adf 100644
--- 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
@@ -23,12 +23,15 @@ 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 org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 
 import java.util.*;
 
 public class ClassDocPropertiesBuilder extends ModelBuilderSupport {
     private final JavadocConverter javadocConverter;
     private final GenerationListener listener;
+    private static final Logger LOG = Logging.getLogger(ClassDocPropertiesBuilder.class);
 
     public ClassDocPropertiesBuilder(JavadocConverter javadocConverter, GenerationListener listener) {
         this.javadocConverter = javadocConverter;
@@ -65,12 +68,12 @@ public class ClassDocPropertiesBuilder extends ModelBuilderSupport {
             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()) {
+        List<ClassDoc> superTypes = classDoc.getSuperTypes();
+        for (ClassDoc superType : superTypes) {
+            LOG.info("Getting properties for {}", superType.getName());
+            for (PropertyDoc propertyDoc : superType.getClassProperties()) {
                 Map<String, ExtraAttributeDoc> additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>();
                 for (ExtraAttributeDoc attributeDoc : propertyDoc.getAdditionalValues()) {
                     String key = attributeDoc.getKey();
@@ -99,7 +102,7 @@ public class ClassDocPropertiesBuilder extends ModelBuilderSupport {
 
             Map<String, ExtraAttributeDoc> additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>();
 
-            if (superClass != null) {
+            if (!superTypes.isEmpty()) {
                 PropertyDoc overriddenProp = props.get(propName);
                 if (overriddenProp != null) {
                     for (ExtraAttributeDoc attributeDoc : overriddenProp.getAdditionalValues()) {
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
index f26b1d1..78baa15 100644
--- 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
@@ -19,17 +19,18 @@ package org.gradle.build.docs.dsl.docbook;
 import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
 import org.w3c.dom.Element;
 
+import java.util.ArrayList;
+import java.util.List;
+
 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;
+    private final List<ClassDocMemberRenderer> memberRenderers = new ArrayList<ClassDocMemberRenderer>();
 
     public ClassDocRenderer(LinkRenderer linkRenderer) {
-        propertiesRenderer = new PropertiesRenderer(linkRenderer, listener);
-        methodsRenderer = new MethodsRenderer(linkRenderer, listener);
-        blocksRenderer = new BlocksRenderer(linkRenderer, listener);
+        memberRenderers.add(new PropertiesRenderer(linkRenderer, listener));
+        memberRenderers.add(new MethodsRenderer(linkRenderer, listener));
+        memberRenderers.add(new BlocksRenderer(linkRenderer, listener));
     }
 
     public void mergeContent(ClassDoc classDoc, Element parent) {
@@ -39,23 +40,18 @@ public class ClassDocRenderer {
             parent.appendChild(chapter);
             chapter.setAttribute("id", classDoc.getId());
             descriptionRenderer.renderTo(classDoc, chapter);
-            mergeProperties(classDoc, chapter);
-            mergeBlocks(classDoc, chapter);
-            mergeMethods(classDoc, chapter);
+            merge(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);
+    void merge(ClassDoc classDoc, Element chapter) {
+        for (ClassDocMemberRenderer memberRenderer : memberRenderers) {
+            memberRenderer.renderSummaryTo(classDoc, chapter);
+        }
+        for (ClassDocMemberRenderer memberRenderer : memberRenderers) {
+            memberRenderer.renderDetailsTo(classDoc, chapter);
+        }
     }
 }
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
index bb00b36..3bdb457 100644
--- 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
@@ -19,7 +19,6 @@ 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 {
@@ -32,7 +31,7 @@ public class ClassDocSuperTypeBuilder {
     }
 
     /**
-     * Builds and attaches the supertype of the given class
+     * Builds and attaches the supertypes of the given class
      */
     void build(ClassDoc classDoc) {
         ClassMetaData classMetaData = classDoc.getClassMetaData();
@@ -41,29 +40,15 @@ public class ClassDocSuperTypeBuilder {
             // 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);
+                classDoc.getInterfaces().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/JavadocConverter.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverter.java
index 5685f02..64f27b6 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
@@ -123,6 +123,7 @@ public class JavadocConverter {
         handler.add(new LinkHandler(nodes, linkConverter, classMetaData, listener));
         handler.add(new InheritDocHandler(nodes, inheritedCommentSource));
         handler.add(new ValueTagHandler(nodes, linkConverter, classMetaData, listener));
+        handler.add(new LiteralTagHandler(nodes));
         handler.add(new TableHandler(nodes, document));
         handler.add(new DlElementHandler(nodes, document));
         handler.add(new AnchorElementHandler(nodes, document, classMetaData));
@@ -665,6 +666,22 @@ public class JavadocConverter {
         }
     }
 
+    private static class LiteralTagHandler implements JavadocTagHandler {
+        private final DocBookBuilder nodes;
+
+        private LiteralTagHandler(DocBookBuilder nodes) {
+            this.nodes = nodes;
+        }
+
+        public boolean onJavadocTag(String tag, String value) {
+            if (!tag.equals("literal")) {
+                return false;
+            }
+            nodes.appendChild(value);
+            return true;
+        }
+    }
+
     private static class LinkHandler implements JavadocTagHandler {
         private final DocBookBuilder nodes;
         private final JavadocLinkConverter linkConverter;
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
index 874d931..97a6635 100644
--- 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
@@ -24,7 +24,7 @@ import org.w3c.dom.Element;
 
 import java.util.Collection;
 
-public class MethodsRenderer {
+class MethodsRenderer implements ClassDocMemberRenderer {
     private final MethodTableRenderer methodTableRenderer = new MethodTableRenderer();
     private final ExtensionMethodsSummaryRenderer extensionMethodsSummaryRenderer;
     private final MethodDetailRenderer methodDetailRenderer;
@@ -34,7 +34,7 @@ public class MethodsRenderer {
         extensionMethodsSummaryRenderer = new ExtensionMethodsSummaryRenderer(methodTableRenderer);
     }
 
-    public void renderTo(ClassDoc classDoc, Element parent) {
+    public void renderSummaryTo(ClassDoc classDoc, Element parent) {
         Document document = parent.getOwnerDocument();
 
         Element summarySection = document.createElement("section");
@@ -44,11 +44,9 @@ public class MethodsRenderer {
         summarySection.appendChild(title);
         title.appendChild(document.createTextNode("Methods"));
 
-        boolean hasMethods = false;
         Collection<MethodDoc> classMethods = classDoc.getClassMethods();
-        if (!classMethods.isEmpty()) {
-            hasMethods = true;
 
+        if (!classMethods.isEmpty()) {
             Element table = document.createElement("table");
             summarySection.appendChild(table);
 
@@ -58,34 +56,46 @@ public class MethodsRenderer {
 
             methodTableRenderer.renderTo(classMethods, table);
         }
-
         for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
-            hasMethods |= !extensionDoc.getExtensionMethods().isEmpty();
             extensionMethodsSummaryRenderer.renderTo(extensionDoc, summarySection);
         }
 
-        if (!hasMethods) {
+        if (!hasMethods(classDoc)) {
             Element para = document.createElement("para");
             summarySection.appendChild(para);
             para.appendChild(document.createTextNode("No methods"));
-            return;
         }
+    }
 
+    public void renderDetailsTo(ClassDoc classDoc, Element parent) {
+        if (hasMethods(classDoc)) {
+            Document document = parent.getOwnerDocument();
+            Element detailsSection = document.createElement("section");
+            parent.appendChild(detailsSection);
 
-        Element detailsSection = document.createElement("section");
-        parent.appendChild(detailsSection);
+            Element title = document.createElement("title");
+            detailsSection.appendChild(title);
+            title.appendChild(document.createTextNode("Method details"));
 
-        title = document.createElement("title");
-        detailsSection.appendChild(title);
-        title.appendChild(document.createTextNode("Method details"));
+            for (MethodDoc methodDoc : classDoc.getClassMethods()) {
+                methodDetailRenderer.renderTo(methodDoc, detailsSection);
+            }
+            for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+                for (MethodDoc methodDoc : extensionDoc.getExtensionMethods()) {
+                    methodDetailRenderer.renderTo(methodDoc, detailsSection);
+                }
+            }
+        }
+    }
 
-        for (MethodDoc methodDoc : classMethods) {
-            methodDetailRenderer.renderTo(methodDoc, detailsSection);
+    private boolean hasMethods(ClassDoc classDoc) {
+        boolean hasMethods = false;
+        if (!classDoc.getClassMethods().isEmpty()) {
+            hasMethods = true;
         }
         for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
-            for (MethodDoc methodDoc : extensionDoc.getExtensionMethods()) {
-                methodDetailRenderer.renderTo(methodDoc, detailsSection);
-            }
+            hasMethods |= !extensionDoc.getExtensionMethods().isEmpty();
         }
+        return hasMethods;
     }
 }
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
index 7378ef8..bc4b755 100644
--- 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
@@ -24,7 +24,7 @@ import org.w3c.dom.Element;
 
 import java.util.Collection;
 
-public class PropertiesRenderer {
+class PropertiesRenderer implements ClassDocMemberRenderer {
     private final PropertyTableRenderer propertyTableRenderer = new PropertyTableRenderer();
     private final ExtensionPropertiesSummaryRenderer extensionPropertiesSummaryRenderer;
     private final PropertyDetailRenderer propertiesDetailRenderer;
@@ -34,7 +34,7 @@ public class PropertiesRenderer {
         extensionPropertiesSummaryRenderer = new ExtensionPropertiesSummaryRenderer(propertyTableRenderer);
     }
 
-    public void renderTo(ClassDoc classDoc, Element parent) {
+    public void renderSummaryTo(ClassDoc classDoc, Element parent) {
         Document document = parent.getOwnerDocument();
 
         Element summarySection = document.createElement("section");
@@ -44,10 +44,8 @@ public class PropertiesRenderer {
         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);
@@ -60,32 +58,46 @@ public class PropertiesRenderer {
         }
 
         for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
-            hasProperties |= !extensionDoc.getExtensionProperties().isEmpty();
             extensionPropertiesSummaryRenderer.renderTo(extensionDoc, summarySection);
         }
 
-        if (!hasProperties) {
+        if (!hasProperties(classDoc)) {
             Element para = document.createElement("para");
             summarySection.appendChild(para);
             para.appendChild(document.createTextNode("No properties"));
-            return;
         }
+    }
 
+    public void renderDetailsTo(ClassDoc classDoc, Element parent) {
+        if (hasProperties(classDoc)) {
+            Document document = parent.getOwnerDocument();
+            Element detailsSection = document.createElement("section");
+            parent.appendChild(detailsSection);
 
-        Element detailsSection = document.createElement("section");
-        parent.appendChild(detailsSection);
+            Element title = document.createElement("title");
+            detailsSection.appendChild(title);
+            title.appendChild(document.createTextNode("Property details"));
 
-        title = document.createElement("title");
-        detailsSection.appendChild(title);
-        title.appendChild(document.createTextNode("Property details"));
+            for (PropertyDoc classProperty : classDoc.getClassProperties()) {
+                propertiesDetailRenderer.renderTo(classProperty, detailsSection);
+            }
+            for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+                for (PropertyDoc propertyDoc : extensionDoc.getExtensionProperties()) {
+                    propertiesDetailRenderer.renderTo(propertyDoc, detailsSection);
+                }
+            }
+        }
+    }
 
-        for (PropertyDoc classProperty : classProperties) {
-            propertiesDetailRenderer.renderTo(classProperty, detailsSection);
+    private boolean hasProperties(ClassDoc classDoc) {
+        boolean hasProperties = false;
+        if (!classDoc.getClassProperties().isEmpty()) {
+            hasProperties = true;
         }
         for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
-            for (PropertyDoc propertyDoc : extensionDoc.getExtensionProperties()) {
-                propertiesDetailRenderer.renderTo(propertyDoc, detailsSection);
-            }
+            hasProperties |= !extensionDoc.getExtensionProperties().isEmpty();
         }
+        return hasProperties;
     }
+
 }
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
index c60521b..c2b8cf0 100644
--- 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
@@ -36,6 +36,7 @@ class ClassDoc implements DslElementDoc {
     private final Element propertiesSection
     private final Element methodsSection
     ClassDoc superClass
+    List<ClassDoc> interfaces = []
     List<Element> comment = []
 
     ClassDoc(String className, Element classContent, Document targetDocument, ClassMetaData classMetaData, ClassExtensionMetaData extensionMetaData) {
@@ -95,6 +96,10 @@ class ClassDoc implements DslElementDoc {
         classExtensions.add(extensionDoc)
     }
 
+    List<ClassDoc> getSuperTypes() {
+        return superClass == null ? interfaces : [superClass] + interfaces
+    }
+
     Element getClassSection() { return classSection }
 
     Element getPropertiesTable() { return propertiesTable }
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
index 9ca6c6c..8333f67 100644
--- 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
@@ -40,8 +40,8 @@ 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}.
+ * Extracts meta-data from the Groovy and Java source files which make up the Gradle API. Persists the meta-data to a file
+ * for later use in generating documentation for the DSL, such as by {@link org.gradle.build.docs.dsl.docbook.AssembleDslDocTask}.
  */
 class ExtractDslMetaDataTask extends SourceTask {
     @OutputFile
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/GenerateDefaultImportsTask.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/GenerateDefaultImportsTask.java
new file mode 100644
index 0000000..2d4a45d
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/GenerateDefaultImportsTask.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.build.docs.dsl.source.model.ClassMetaData;
+import org.gradle.build.docs.model.SimpleClassMetaDataRepository;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.*;
+
+public class GenerateDefaultImportsTask extends DefaultTask {
+    private File metaDataFile;
+    private File destFile;
+    private Set<String> excludePatterns = new HashSet<String>();
+    private Set<String> extraPackages = new HashSet<String>();
+
+    @InputFile
+    public File getMetaDataFile() {
+        return metaDataFile;
+    }
+
+    public void setMetaDataFile(File metaDataFile) {
+        this.metaDataFile = metaDataFile;
+    }
+
+    @OutputFile
+    public File getDestFile() {
+        return destFile;
+    }
+
+    public void setDestFile(File destFile) {
+        this.destFile = destFile;
+    }
+
+    @Input
+    public Set<String> getExcludedPackages() {
+        return excludePatterns;
+    }
+
+    public void setExcludedPackages(Set<String> excludedPackages) {
+        this.excludePatterns = excludedPackages;
+    }
+
+    /**
+     * Package name can end with '.**' to exclude subpackages as well.
+     */
+    public void excludePackage(String name) {
+        excludePatterns.add(name);
+    }
+
+    public Set<String> getExtraPackages() {
+        return extraPackages;
+    }
+
+    public void setExtraPackages(Set<String> extraPackages) {
+        this.extraPackages = extraPackages;
+    }
+
+    public void extraPackage(String name) {
+        extraPackages.add(name);
+    }
+
+    @TaskAction
+    public void generate() throws IOException {
+        SimpleClassMetaDataRepository<ClassMetaData> repository = new SimpleClassMetaDataRepository<ClassMetaData>();
+        repository.load(getMetaDataFile());
+
+        final Set<String> excludedPrefixes = new HashSet<String>();
+        final Set<String> excludedPackages = new HashSet<String>();
+        for (String excludePattern : excludePatterns) {
+            if (excludePattern.endsWith(".**")) {
+                String baseName = excludePattern.substring(0, excludePattern.length() - 3);
+                excludedPrefixes.add(baseName + '.');
+                excludedPackages.add(baseName);
+            } else {
+                excludedPackages.add(excludePattern);
+            }
+        }
+        final Set<String> packages = new TreeSet<String>();
+        packages.addAll(extraPackages);
+        final Multimap<String, String> simpleNames = HashMultimap.create();
+
+        repository.each(new Action<ClassMetaData>() {
+            public void execute(ClassMetaData classMetaData) {
+                if (classMetaData.getOuterClassName() != null) {
+                    // Ignore inner classes
+                    return;
+                }
+                String packageName = classMetaData.getPackageName();
+                if (excludedPackages.contains(packageName)) {
+                    return;
+                }
+                for (String excludedPrefix : excludedPrefixes) {
+                    if (packageName.startsWith(excludedPrefix)) {
+                        return;
+                    }
+                }
+                simpleNames.put(classMetaData.getSimpleName(), classMetaData.getClassName());
+                packages.add(packageName);
+            }
+        });
+
+        for (Map.Entry<String, Collection<String>> entry : simpleNames.asMap().entrySet()) {
+            if (entry.getValue().size() > 1) {
+                System.out.println(String.format("Multiple DSL types have short name '%s'", entry.getKey()));
+                for (String className : entry.getValue()) {
+                    System.out.println("    * " + className);
+                }
+            }
+        }
+
+        final PrintWriter writer = new PrintWriter(new FileWriter(getDestFile()));
+        try {
+            for (String packageName : packages) {
+                writer.print("import ");
+                writer.print(packageName);
+                writer.println(".*");
+            }
+        } finally {
+            writer.close();
+        }
+    }
+}
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 f5ec24e..3811902 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
@@ -16,12 +16,12 @@
 package org.gradle.build.docs.model;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.UnknownDomainObjectException;
 
 import java.io.*;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 public class SimpleClassMetaDataRepository<T extends Attachable<T>> implements ClassMetaDataRepository<T> {
     private final Map<String, T> classes = new HashMap<String, T>();
@@ -82,4 +82,10 @@ public class SimpleClassMetaDataRepository<T extends Attachable<T>> implements C
             cl.call(new Object[]{entry.getKey(), entry.getValue()});
         }
     }
+
+    public void each(Action<? super T> action) {
+        for (T t : classes.values()) {
+            action.execute(t);
+        }
+    }
 }
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 dd9df8b..216c81b 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
@@ -100,6 +100,32 @@ class BasicJavadocLexerTest extends Specification {
         0 * visitor._
     }
 
+    def discardsHtmlComments() {
+        when:
+        lexer.pushText("<p><!-- ignore me --></p>text <!-- -->2")
+        lexer.visit(visitor)
+
+        then:
+        1 * visitor.onStartHtmlElement('p')
+        1 * visitor.onStartHtmlElementComplete('p')
+        1 * visitor.onEndHtmlElement('p')
+        1 * visitor.onText("text 2")
+        1 * visitor.onEnd()
+        0 * visitor._
+    }
+
+    def handlesMissingEndOfComment() {
+        when:
+        lexer.pushText("<p><!-- ignore me ")
+        lexer.visit(visitor)
+
+        then:
+        1 * visitor.onStartHtmlElement('p')
+        1 * visitor.onStartHtmlElementComplete('p')
+        1 * visitor.onEnd()
+        0 * visitor._
+    }
+
     def parsesJavadocTags() {
         when:
         lexer.pushText("{@tag some value}")
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
index a514f97..b3e4948 100644
--- 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
@@ -32,7 +32,11 @@ class ClassDocMethodsBuilderTest extends XmlSpecification {
         MethodMetaData methodBOverload = method('b', classMetaData)
         MethodDoc methodAOverridden = methodDoc('a')
         MethodDoc methodC = methodDoc('c')
+        MethodDoc methodD = methodDoc('d')
+        MethodDoc methodE = methodDoc('e')
         ClassDoc superClass = classDoc('org.gradle.SuperClass')
+        ClassDoc superType1 = classDoc("org.gradle.SuperType1")
+        ClassDoc superType2 = classDoc("org.gradle.SuperType2")
 
         def content = parse('''
 <section>
@@ -51,23 +55,29 @@ class ClassDocMethodsBuilderTest extends XmlSpecification {
         ClassDoc doc = withCategories {
             def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
             doc.superClass = superClass
+            doc.interfaces << superType1
+            doc.interfaces << superType2
             builder.build(doc)
             doc
         }
 
         then:
-        doc.classMethods.size() == 4
+        doc.classMethods.size() == 6
 
         doc.classMethods[0].name == 'a'
         doc.classMethods[1].name == 'b'
         doc.classMethods[2].name == 'b'
         doc.classMethods[3].name == 'c'
+        doc.classMethods[4].name == 'd'
+        doc.classMethods[5].name == 'e'
 
         _ * 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]
+        _ * superType1.classMethods >> [methodD]
+        _ * superType2.classMethods >> [methodE]
     }
 
     def buildsBlocksForClass() {
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
index 22570d2..9cb0fc5 100644
--- 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
@@ -32,10 +32,13 @@ class ClassDocPropertiesBuilderTest extends XmlSpecification {
         ClassMetaData classMetaData = classMetaData()
         PropertyMetaData propertyA = property('a', classMetaData, comment: 'prop a')
         PropertyMetaData propertyB = property('b', classMetaData, comment: 'prop b')
-        ClassDoc superDoc = classDoc()
+        ClassDoc superDoc = classDoc('org.gradle.SuperClass')
         PropertyDoc propertyDocA = propertyDoc('a')
         PropertyDoc propertyDocC = propertyDoc('c')
 
+        ClassDoc superType1 = classDoc("org.gradle.SuperType1")
+        ClassDoc superType2 = classDoc("org.gradle.SuperType2")
+
         def content = parse('''
 <section>
     <section><title>Properties</title>
@@ -53,20 +56,26 @@ class ClassDocPropertiesBuilderTest extends XmlSpecification {
         ClassDoc doc = withCategories {
             def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
             doc.superClass = superDoc
+            doc.interfaces << superType1
+            doc.interfaces << superType2
             builder.build(doc)
             return doc
         }
 
         then:
-        doc.classProperties.size() == 3
+        doc.classProperties.size() == 5
         doc.classProperties[0].name == 'a'
         doc.classProperties[1].name == 'b'
         doc.classProperties[2].name == 'c'
+        doc.classProperties[3].name == 'd'
+        doc.classProperties[4].name == 'e'
 
         _ * classMetaData.findProperty('b') >> propertyB
         _ * classMetaData.findProperty('a') >> propertyA
-        _ * classMetaData.superClassName >> 'org.gradle.SuperType'
-        _ * superDoc.getClassProperties() >> [propertyDocC, propertyDocA]
+        _ * classMetaData.superClassName >> 'org.gradle.SuperClass'
+        _ * superDoc.classProperties >> [propertyDocC, propertyDocA]
+        _ * superType1.classProperties >> [propertyDoc('d')]
+        _ * superType2.classProperties >> [propertyDoc('d'), propertyDoc('e')]
     }
 
     def canAttachAdditionalValuesToProperty() {
@@ -74,6 +83,7 @@ class ClassDocPropertiesBuilderTest extends XmlSpecification {
         PropertyMetaData propertyA = property('a', classMetaData, comment: 'prop a')
         PropertyMetaData propertyB = property('b', classMetaData, comment: 'prop b')
         ClassDoc superDoc = classDoc()
+        ClassDoc superType = classDoc("org.gradle.SuperType")
         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])
@@ -97,6 +107,7 @@ class ClassDocPropertiesBuilderTest extends XmlSpecification {
         ClassDoc doc = withCategories {
             def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
             doc.superClass = superDoc
+            doc.interfaces << superType
             builder.build(doc)
             return doc
         }
@@ -133,7 +144,8 @@ class ClassDocPropertiesBuilderTest extends XmlSpecification {
         _ * classMetaData.findProperty('b') >> propertyB
         _ * classMetaData.findProperty('a') >> propertyA
         _ * classMetaData.superClassName >> 'org.gradle.SuperType'
-        _ * superDoc.classProperties >> [inheritedPropertyA, inheritedPropertyB, inheritedPropertyC]
+        _ * superDoc.classProperties >> [inheritedPropertyA, inheritedPropertyB]
+        _ * superType.classProperties >> [inheritedPropertyC]
     }
 
     def classMetaData(String name = 'org.gradle.Class') {
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 06824b8..f4d0881 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
@@ -68,13 +68,13 @@ class ClassDocRendererTest extends XmlSpecification {
             <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>
+        <section>
+            <title>Script blocks</title>
+            <para>No script blocks</para>
+        </section>
     </chapter>
 </root>'''
     }
@@ -117,13 +117,13 @@ class ClassDocRendererTest extends XmlSpecification {
             <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>
+        <section>
+            <title>Script blocks</title>
+            <para>No script blocks</para>
+        </section>
     </chapter>
 </root>'''
     }
@@ -144,12 +144,14 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc classDoc = classDoc('Class', content: content)
         PropertyDoc propDoc = propertyDoc('propName', id: 'propId', description: 'prop description', comment: 'prop comment', type: 'org.gradle.Type', attributes: [extraAttribute])
         _ * classDoc.classProperties >> [propDoc]
+        _ * classDoc.classMethods >> []
+        _ * classDoc.classBlocks >> []
         _ * classDoc.classExtensions >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeProperties(classDoc, result)
+            renderer.merge(classDoc, result)
         }
 
         then:
@@ -177,6 +179,14 @@ class ClassDocRendererTest extends XmlSpecification {
         </table>
     </section>
     <section>
+        <title>Methods</title>
+        <para>No methods</para>
+    </section>
+    <section>
+        <title>Script blocks</title>
+        <para>No script blocks</para>
+    </section>
+    <section>
         <title>Property details</title>
         <section id="propId" role="detail">
             <title><classname>org.gradle.Type</classname> <literal>propName</literal> (read-only)</title>
@@ -209,12 +219,14 @@ class ClassDocRendererTest extends XmlSpecification {
         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.classMethods >> []
+        _ * classDoc.classBlocks >> []
         _ * classDoc.classExtensions >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeProperties(classDoc, result)
+            renderer.merge(classDoc, result)
         }
 
         then:
@@ -254,6 +266,14 @@ class ClassDocRendererTest extends XmlSpecification {
         </table>
     </section>
     <section>
+        <title>Methods</title>
+        <para>No methods</para>
+    </section>
+    <section>
+        <title>Script blocks</title>
+        <para>No script blocks</para>
+    </section>
+    <section>
         <title>Property details</title>
         <section id="prop1" role="detail">
             <title><classname>org.gradle.Type</classname> <literal>deprecatedProperty</literal> (read-only)</title>
@@ -288,13 +308,17 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassExtensionDoc extensionDoc = extensionDoc('thingo')
         PropertyDoc propertyDoc = propertyDoc('propName', id: 'propId')
         _ * targetClassDoc.classProperties >> []
+        _ * targetClassDoc.classMethods >> []
+        _ * targetClassDoc.classBlocks >> []
         _ * targetClassDoc.classExtensions >> [extensionDoc]
         _ * extensionDoc.extensionProperties >> [propertyDoc]
+        _ * extensionDoc.extensionMethods >> []
+        _ * extensionDoc.extensionBlocks >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeProperties(targetClassDoc, result)
+            renderer.merge(targetClassDoc, result)
         }
 
         then:
@@ -326,6 +350,14 @@ class ClassDocRendererTest extends XmlSpecification {
         </section>
     </section>
     <section>
+        <title>Methods</title>
+        <para>No methods</para>
+    </section>
+    <section>
+        <title>Script blocks</title>
+        <para>No script blocks</para>
+    </section>
+    <section>
         <title>Property details</title>
         <section id="propId" role="detail">
             <title><classname>SomeType</classname> <literal>propName</literal> (read-only)</title>
@@ -350,18 +382,24 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc classDoc = classDoc('Class', content: content)
         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.classProperties >> []
         _ * classDoc.classMethods >> [method1, method2]
+        _ * classDoc.classBlocks >> []
         _ * classDoc.classExtensions >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeMethods(classDoc, result)
+            renderer.merge(classDoc, result)
         }
 
         then:
         formatTree(result) == '''<chapter>
     <section>
+        <title>Properties</title>
+        <para>No properties</para>
+    </section>
+    <section>
         <title>Methods</title>
         <table>
             <title>Methods - Class</title>
@@ -390,6 +428,10 @@ class ClassDocRendererTest extends XmlSpecification {
         </table>
     </section>
     <section>
+        <title>Script blocks</title>
+        <para>No script blocks</para>
+    </section>
+    <section>
         <title>Method details</title>
         <section id="method1Id" role="detail">
             <title><classname>ReturnType1</classname> <literal>methodName</literal>()</title>
@@ -419,18 +461,24 @@ class ClassDocRendererTest extends XmlSpecification {
         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.classProperties >> []
         _ * classDoc.classMethods >> [method1, method2]
+        _ * classDoc.classBlocks >> []
         _ * classDoc.classExtensions >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeMethods(classDoc, result)
+            renderer.merge(classDoc, result)
         }
 
         then:
         formatTree(result) == '''<chapter>
     <section>
+        <title>Properties</title>
+        <para>No properties</para>
+    </section>
+    <section>
         <title>Methods</title>
         <table>
             <title>Methods - Class</title>
@@ -461,6 +509,10 @@ class ClassDocRendererTest extends XmlSpecification {
         </table>
     </section>
     <section>
+        <title>Script blocks</title>
+        <para>No script blocks</para>
+    </section>
+    <section>
         <title>Method details</title>
         <section id="method1Id" role="detail">
             <title><classname>ReturnType1</classname> <literal>deprecated</literal>()</title>
@@ -495,19 +547,27 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc targetClassDoc = classDoc('Class', content: content)
         ClassExtensionDoc extensionDoc = extensionDoc('thingo')
         MethodDoc methodDoc = methodDoc('methodName', id: 'methodId')
+        _ * targetClassDoc.classProperties >> []
         _ * targetClassDoc.classMethods >> []
+        _ * targetClassDoc.classBlocks >> []
         _ * targetClassDoc.classExtensions >> [extensionDoc]
+        _ * extensionDoc.extensionProperties >> []
         _ * extensionDoc.extensionMethods >> [methodDoc]
+        _ * extensionDoc.extensionBlocks >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeMethods(targetClassDoc, result)
+            renderer.merge(targetClassDoc, result)
         }
 
         then:
         formatTree(result) == '''<chapter>
     <section>
+        <title>Properties</title>
+        <para>No properties</para>
+    </section>
+    <section>
         <title>Methods</title>
         <section>
             <title>Methods added by the <literal>thingo</literal> plugin</title>
@@ -532,6 +592,10 @@ class ClassDocRendererTest extends XmlSpecification {
         </section>
     </section>
     <section>
+        <title>Script blocks</title>
+        <para>No script blocks</para>
+    </section>
+    <section>
         <title>Method details</title>
         <section id="methodId" role="detail">
             <title><classname>ReturnType</classname> <literal>methodName</literal>()</title>
@@ -541,6 +605,137 @@ class ClassDocRendererTest extends XmlSpecification {
 </chapter>'''
     }
 
+
+    def mergesSummariesBeforeDetails() {
+        def content = parse('''
+            <chapter>
+                <section><title>Properties</title>
+                    <table>
+                        <thead><tr><td>Name</td><td>Extra column</td></tr></thead>
+                        <tr><td>propName</td><td>some value</td></tr>
+                    </table>
+                </section>
+                <section><title>Methods</title>
+                    <table>
+                        <thead><tr><td>Name</td></tr></thead>
+                        <tr><td>methodName</td></tr>
+                    </table>
+                </section>
+            </chapter>
+        ''')
+
+        ClassDoc classDoc = classDoc('Class', content: content)
+        PropertyDoc property1 = propertyDoc('propName', id: 'propId', type: 'org.gradle.Type', description: 'prop description', comment: 'prop comment')
+        MethodDoc method1 = methodDoc('methodName', id: 'methodId', returnType: 'ReturnType', description: 'method description', comment: 'method comment')
+        BlockDoc block1 = blockDoc('blockName', id: 'blockId', type: 'org.gradle.Type', description: 'block description', comment: 'block comment')
+        _ * classDoc.classProperties >> [property1]
+        _ * classDoc.classMethods >> [method1]
+        _ * classDoc.classBlocks >> [block1]
+        _ * classDoc.classExtensions >> []
+
+        when:
+        def result = parse('<chapter/>', document)
+        withCategories {
+            renderer.merge(classDoc, result)
+        }
+
+        then:
+        formatTree(result) == '''<chapter>
+    <section>
+        <title>Properties</title>
+        <table>
+            <title>Properties - Class</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend="propId">
+                        <literal>propName</literal>
+                    </link>
+                </td>
+                <td>
+                    <para>prop description</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <title>Methods - Class</title>
+            <thead>
+                <tr>
+                    <td>Method</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal><link linkend="methodId">methodName</link>()</literal>
+                </td>
+                <td>
+                    <para>method description</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Script blocks</title>
+        <table>
+            <title>Script blocks - Class</title>
+            <thead>
+                <tr>
+                    <td>Block</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend="blockId">
+                        <literal>blockName</literal>
+                    </link>
+                </td>
+                <td>
+                    <para>block description</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Property details</title>
+        <section id="propId" role="detail">
+            <title><classname>org.gradle.Type</classname> <literal>propName</literal> (read-only)</title>
+            <para>prop comment</para>
+        </section>
+    </section>
+    <section>
+        <title>Method details</title>
+        <section id="methodId" role="detail">
+            <title><classname>ReturnType</classname> <literal>methodName</literal>()</title>
+            <para>method comment</para>
+        </section>
+    </section>
+    <section>
+        <title>Script block details</title>
+        <section id="blockId" role="detail">
+            <title><literal>blockName</literal> { }</title>
+            <para>block comment</para>
+            <segmentedlist>
+                <segtitle>Delegates to</segtitle>
+                <seglistitem>
+                    <seg><classname>org.gradle.Type</classname> from <link linkend="blockName">
+                            <literal>blockName</literal></link></seg>
+                </seglistitem>
+            </segmentedlist>
+        </section>
+    </section>
+</chapter>'''
+    }
+
     def mergesBlockMetaDataIntoBlocksSection() {
         def content = parse('''
             <chapter>
@@ -552,18 +747,28 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc classDoc = classDoc('Class', content: content)
         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.classProperties >> []
+        _ * classDoc.classMethods >> []
         _ * classDoc.classBlocks >> [block1, block2]
         _ * classDoc.classExtensions >> []
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeBlocks(classDoc, result)
+            renderer.merge(classDoc, result)
         }
 
         then:
         formatTree(result) == '''<chapter>
     <section>
+        <title>Properties</title>
+        <para>No properties</para>
+    </section>
+    <section>
+        <title>Methods</title>
+        <para>No methods</para>
+    </section>
+    <section>
         <title>Script blocks</title>
         <table>
             <title>Script blocks - Class</title>
@@ -636,19 +841,31 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc targetClassDoc = classDoc('Class', content: content)
         ClassExtensionDoc extensionDoc = extensionDoc('thingo')
         BlockDoc blockDoc = blockDoc('blockName', id: 'blockId')
+        _ * targetClassDoc.classProperties >> []
+        _ * targetClassDoc.classMethods >> []
         _ * targetClassDoc.classBlocks >> []
         _ * targetClassDoc.classExtensions >> [extensionDoc]
+        _ * extensionDoc.extensionProperties >> []
+        _ * extensionDoc.extensionMethods >> []
         _ * extensionDoc.extensionBlocks >> [blockDoc]
 
         when:
         def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeBlocks(targetClassDoc, result)
+            renderer.merge(targetClassDoc, result)
         }
 
         then:
         formatTree(result) == '''<chapter>
     <section>
+        <title>Properties</title>
+        <para>No properties</para>
+    </section>
+    <section>
+        <title>Methods</title>
+        <para>No methods</para>
+    </section>
+    <section>
         <title>Script blocks</title>
         <section>
             <title>Script blocks added by the <literal>thingo</literal> plugin</title>
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 9749b9d..3124e3b 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
@@ -100,6 +100,16 @@ line 2</para>'''
         format(result.docbook) == '''<para><>& /></para>'''
     }
 
+    def ignoresHtmlComments() {
+        _ * classMetaData.rawCommentText >> '<!-- <p>ignore me</p> --><p>para 1'
+
+        when:
+        def result = parser.parse(classMetaData, listener)
+
+        then:
+        format(result.docbook) == '''<para>para 1</para>'''
+    }
+
     def convertsPElementsToParaElements() {
         _ * classMetaData.rawCommentText >> '<p>para 1</p><P>para 2</P>'
 
@@ -140,6 +150,16 @@ line 2</para>'''
         format(result.docbook) == '''<para>This is <literal>code</literal>. So is <literal>this</literal>.</para>'''
     }
 
+    def convertsLiteralTagsToText() {
+        _ * classMetaData.rawCommentText >> '{@literal <b>markup</b> {@ignore}}'
+
+        when:
+        def result = parser.parse(classMetaData, listener)
+
+        then:
+        format(result.docbook) == '''<para><b>markup</b> {@ignore}</para>'''
+    }
+
     def doesNotInterpretContentsOfCodeTagAsHtml() {
         _ * classMetaData.rawCommentText >> '{@code List<String> && a < 9} <code>&</code>'
 
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepositoryTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepositoryTest.groovy
index 23e867b..1ce3f08 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepositoryTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepositoryTest.groovy
@@ -15,10 +15,14 @@
  */
 package org.gradle.build.docs.model
 
-import spock.lang.Specification
+import org.gradle.api.Action
 import org.gradle.api.UnknownDomainObjectException
+import org.junit.Rule
+import org.junit.rules.TemporaryFolder
+import spock.lang.Specification
 
 class SimpleClassMetaDataRepositoryTest extends Specification {
+    @Rule TemporaryFolder tmpDir
     final SimpleClassMetaDataRepository<TestDomainObject> repository = new SimpleClassMetaDataRepository<TestDomainObject>()
 
     def canAddMetaData() {
@@ -46,7 +50,7 @@ class SimpleClassMetaDataRepositoryTest extends Specification {
         e.message == 'No meta-data is available for class \'unknown\'.'
     }
 
-    def canIterateOverClasses() {
+    def canIterateOverClassesUsingClosure() {
         TestDomainObject value1 = new TestDomainObject('a')
         TestDomainObject value2 = new TestDomainObject('a')
         repository.put('class1', value1)
@@ -62,9 +66,25 @@ class SimpleClassMetaDataRepositoryTest extends Specification {
         0 * cl._
     }
 
+    def canIterateOverClassesUsingAction() {
+        TestDomainObject value1 = new TestDomainObject('a')
+        TestDomainObject value2 = new TestDomainObject('a')
+        repository.put('class1', value1)
+        repository.put('class2', value2)
+        Action action = Mock()
+
+        when:
+        repository.each(action)
+
+        then:
+        1 * action.execute(value1)
+        1 * action.execute(value2)
+        0 * action._
+    }
+
     def canPersistMetaData() {
         TestDomainObject value = new TestDomainObject('a')
-        File file = File.createTempFile("test-repository", "bin")
+        File file = tmpDir.newFile()
         repository.put('class', value)
 
         when:
diff --git a/config/checkstyle/checkstyle-groovy.xml b/config/checkstyle/checkstyle-groovy.xml
index b07498f..f52691b 100644
--- a/config/checkstyle/checkstyle-groovy.xml
+++ b/config/checkstyle/checkstyle-groovy.xml
@@ -29,4 +29,7 @@
     <module name="RegexpSingleline">
         <property name="format" value="Created with IntelliJ IDEA"/>
     </module>
+    <module name="RegexpSingleline">
+        <property name="format" value="^\s*\*\s*@author"/>
+    </module>
 </module>
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 6b0b710..742953d 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -111,6 +111,9 @@
     <module name="RegexpSingleline">
         <property name="format" value="Created with IntelliJ IDEA"/>
     </module>
+    <module name="RegexpSingleline">
+        <property name="format" value="^\s*\*\s*@author"/>
+    </module>
 
     <!-- allows suppressing using the //CHECKSTYLE:ON //CHECKSTYLE:OFF -->
     <module name="SuppressionCommentFilter"/>
diff --git a/gradle/buildReceipt.gradle b/gradle/buildReceipt.gradle
index 3139797..eb0209e 100644
--- a/gradle/buildReceipt.gradle
+++ b/gradle/buildReceipt.gradle
@@ -118,6 +118,7 @@ task createBuildReceipt(dependsOn: determineCommitId) {
                 isSnapshot: isSnapshot,
                 rcNumber: rcNumber == null ? "" : rcNumber,
                 buildTimestamp: buildTimestamp,
+                buildNumber: System.properties["build.number"] ?: 'none',
                 username: System.properties["user.name"],
                 hostname: hostName,
                 javaVersion: System.properties["java.version"],
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 0face90..e8ff446 100755
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -21,34 +21,34 @@ 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'
+libraries.ant = dependencies.module('org.apache.ant:ant:1.9.3') {
+    dependency 'org.apache.ant:ant-launcher:1.9.3 at jar'
 }
 
-libraries.asm =  'org.ow2.asm:asm-all:4.0 at jar'
+libraries.asm =  'org.ow2.asm:asm-all:5.0_BETA 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"
+    dependency "com.jcraft:jsch:0.1.51"
 }
 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.7.2 at jar'
-libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.7.2') {
+libraries.slf4j_api = dependencies.module('org.slf4j:slf4j-api:1.7.5')
+libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.7.5') {
     dependency libraries.slf4j_api
 }
-libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.7.2') {
+libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.7.5') {
     dependency libraries.slf4j_api
 }
-libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.7.2') {
+libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.7.5') {
     dependency libraries.slf4j_api
 }
-libraries.logback_core = 'ch.qos.logback:logback-core:1.0.9 at jar'
-libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.9') {
+libraries.logback_core = dependencies.module('ch.qos.logback:logback-core:1.0.13')
+libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.13') {
     dependency libraries.logback_core
     dependency libraries.slf4j_api
 }
@@ -76,10 +76,10 @@ libraries.maven_ant_tasks = dependencies.module("org.apache.maven:maven-ant-task
 }
 
 libraries += [
-        ant_antlr: 'org.apache.ant:ant-antlr:1.8.4 at jar',
+        ant_antlr: 'org.apache.ant:ant-antlr:1.9.3 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',
+        guava: 'com.google.guava:guava-jdk5:14.0.1 at jar',
         jsr305: 'com.google.code.findbugs:jsr305:1.3.9 at jar',
         groovy: 'org.codehaus.groovy:groovy-all:1.8.6 at jar',
         jaxen: 'jaxen:jaxen:1.1 at jar',
@@ -89,7 +89,7 @@ libraries += [
         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.3-rc-2',
+        nativePlatform: 'net.rubygrapefruit:native-platform:0.10',
         xerces: "xerces:xercesImpl:2.9.1",
         objenesis: 'org.objenesis:objenesis:1.2 at jar',
         jsoup:'org.jsoup:jsoup:1.6.3'
@@ -121,13 +121,17 @@ libraries.maven3 = dependencies.module("org.apache.maven:maven-core:3.0.4") {
     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"
+
+    dependency 'org.apache.maven.wagon:wagon-http:2.4 at jar'
+    dependency 'org.apache.maven.wagon:wagon-provider-api:2.4 at jar'
+    dependency 'org.apache.maven.wagon:wagon-http-shared4:2.4 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"
+    dependency 'org.sonatype.aether:aether-connector-wagon:1.13.1 at jar'
 }
 
 libraries.spock = [
@@ -145,3 +149,4 @@ libraries.jmock = [
     libraries.objenesis,
     'cglib:cglib-nodep:2.2'
 ]
+libraries.gson = "com.google.code.gson:gson:2.2.4"
\ No newline at end of file
diff --git a/gradle/eclipse.gradle b/gradle/eclipse.gradle
index 7cd85cc..a5bd880 100644
--- a/gradle/eclipse.gradle
+++ b/gradle/eclipse.gradle
@@ -11,6 +11,13 @@ allprojects { project->
                     classpath.entries.removeAll { it.path.contains("$project.name/build") && it.kind=='lib' }
                 }
             }
+            jdt {
+                file.withProperties { properties ->
+                    //Eclipse's view of projects treat circular dependencies as errors by default
+                    //Treat these as warnings for now
+                    properties["org.eclipse.jdt.core.circularClasspath"] = "warning"
+                }
+            }
         }
     }
 }
diff --git a/gradle/groovyProject.gradle b/gradle/groovyProject.gradle
index f69306b..a1bcd7e 100644
--- a/gradle/groovyProject.gradle
+++ b/gradle/groovyProject.gradle
@@ -8,7 +8,6 @@ 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")
@@ -20,11 +19,6 @@ 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"
 
@@ -34,6 +28,19 @@ sourceSets {
     main.output.dir generatedResourcesDir, builtBy: classpathManifest
 }
 
+if (!javaVersion.java6Compatible) {
+    sourceSets.all {
+        java.exclude '**/jdk6/**'
+        groovy.exclude '**/jdk6/**'
+    }
+}
+if (!javaVersion.java7) {
+    sourceSets.all {
+        java.exclude '**/jdk7/**'
+        groovy.exclude '**/jdk7/**'
+    }
+}
+
 testTasks.all { task ->
     maxParallelForks = rootProject.maxParallelForks
     if (isCiServer) {
@@ -97,4 +104,18 @@ class ClasspathManifest extends DefaultTask {
         manifestFile.withOutputStream { properties.save(it, 'module definition') }
     }
 }
-ext.ClasspathManifest = ClasspathManifest
\ No newline at end of file
+ext.ClasspathManifest = ClasspathManifest
+
+// This is here mostly for the TravisCI build.
+// It advertises as an ANSI capable terminal but isn't, so we run with -q there to make
+// the output legible, but we still want test result info
+if (buildTypes.isActive("pullRequestValidation")) {
+    tasks.withType(Test) {
+        testLogging {
+            quiet {
+                events "failed"
+                exceptionFormat "full"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle/idea.gradle b/gradle/idea.gradle
index ab88ec9..dbfd2a6 100644
--- a/gradle/idea.gradle
+++ b/gradle/idea.gradle
@@ -196,6 +196,14 @@ idea {
           </component>
         ''')
                 node.append(codeFormatSettings)
+
+                node.append(new XmlParser().parseText('''
+                    <component name="GroovyCompilerProjectConfiguration">
+                      <excludes>
+                        <file url="file://$PROJECT_DIR$/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy" />
+                      </excludes>
+                    </component>
+                '''))
             }
         }
     }
@@ -203,6 +211,12 @@ idea {
     workspace.iws.withXml { provider ->
         Node node = provider.asNode()
 
+        node.append(new XmlParser().parseText('''
+            <component name="CompilerWorkspaceConfiguration">
+              <option name="USE_COMPILE_SERVER" value="false" />
+            </component>
+        '''))
+
         Node runManagerConfig = node.component.find { it.'@name' == 'RunManager' }
 
         // Add int test configuration to JUnit defaults
diff --git a/gradle/ideaTestSourcesWorkaround.gradle b/gradle/ideaTestSourcesWorkaround.gradle
new file mode 100644
index 0000000..e31ca97
--- /dev/null
+++ b/gradle/ideaTestSourcesWorkaround.gradle
@@ -0,0 +1,3 @@
+//Making all source dirs test source dirs to work around issues with IDEA 13 compilation
+idea.module.testSourceDirs = idea.module.testSourceDirs + idea.module.sourceDirs
+idea.module.sourceDirs = [] as Set
\ No newline at end of file
diff --git a/gradle/intTestImage.gradle b/gradle/intTestImage.gradle
index bfeb9c1..c7b6c37 100644
--- a/gradle/intTestImage.gradle
+++ b/gradle/intTestImage.gradle
@@ -38,7 +38,7 @@ if (useIncomingDistributions) {
     intTestImage {
         with project(":distributions").binDistImage
         into "samples", {
-            from { project(":docs").samples }
+            from { project(":docs").outputs.samples }
         }
         doLast { task ->
             ant.chmod(dir: "$destinationDir/bin", perm: "ugo+rx", includes: "**/*")
diff --git a/gradle/integTest.gradle b/gradle/integTest.gradle
index d5b36ca..244ed40 100644
--- a/gradle/integTest.gradle
+++ b/gradle/integTest.gradle
@@ -22,24 +22,79 @@ dependencies {
 
 ext.integTestTasks = tasks.withType(Test).matching { it.name.toLowerCase().endsWith('integtest') }
 
+import org.gradle.util.GradleVersion
+
+def removeOldVersionsFromDir(File dir, def shouldDelete, def dirPrefix = "", def dirSuffix = "") {
+    if (dir.directory) {
+
+        for (File cacheDir: dir.listFiles()) {
+            if (!cacheDir.name.startsWith(dirPrefix) || !cacheDir.name.endsWith(dirSuffix)) {
+                continue
+            }
+            def dirVersion = cacheDir.name.substring(dirPrefix.length(), cacheDir.name.length() - dirSuffix.length())
+            if (!dirVersion.matches("\\d+\\.\\d+(\\.\\d+)?(-\\w+)*(-\\d{14}[+-]\\d{4})?")) {
+                continue
+            }
+
+            def cacheVersion
+            try {
+                cacheVersion = GradleVersion.version(dirVersion)
+            } catch (IllegalArgumentException e) {
+                // Ignore
+                continue
+            }
+
+            if (shouldDelete(cacheVersion)) {
+                println "Removing old cache directory : ${cacheDir}"
+                delete(cacheDir)
+            }
+        }
+    }
+}
+
+task cleanUpCaches {
+    doLast {
+        def executingVersion = GradleVersion.version(gradle.gradleVersion)
+
+        def versionProps = readBuildReceipt("${rootProject.buildDir}/${buildReceiptFileName}")
+        def testedVersion = GradleVersion.version(versionProps.versionNumber)
+
+        // Expire .gradle cache where major version is older than executing version
+        def expireTaskCache = { def candidateVersion ->
+            return candidateVersion.baseVersion < executingVersion.baseVersion
+        }
+
+        // Expire intTestImage cache snapshots that are older than the tested version
+        def expireIntegTestCache = { def candidateVersion ->
+            return candidateVersion.snapshot && candidateVersion < testedVersion
+        }
+
+        removeOldVersionsFromDir(rootProject.file("buildSrc/.gradle"), expireTaskCache)
+        removeOldVersionsFromDir(rootProject.file(".gradle"), expireTaskCache)
+        removeOldVersionsFromDir(rootProject.file("intTestHomeDir/worker-1/caches"), expireIntegTestCache)
+        removeOldVersionsFromDir(rootProject.file("intTestHomeDir/worker-1/daemon"), expireIntegTestCache)
+        removeOldVersionsFromDir(rootProject.file("intTestHomeDir/worker-1/wrapper/dists"), expireIntegTestCache, "gradle-", "-bin")
+        delete(rootProject.file("intTestHomeDir/worker-1/wrapper/dists/dist"))
+    }
+}
+
 integTestTasks.all { Test task ->
-    dependsOn ':intTestImage'
+    dependsOn ':intTestImage', 'cleanUpCaches'
     testClassesDir = sourceSets.integTest.output.classesDir
     classpath = sourceSets.integTest.runtimeClasspath
     testSrcDirs = []
     jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m', '-XX:+HeapDumpOnOutOfMemoryError'
 
-    testResultsDir = file("${project.testResultsDir}/$name")
+    reports.junitXml.destination = file("${project.testResultsDir}/$name")
 
-    systemProperties['org.gradle.integtest.versions'] = project.hasProperty("testAllVersions") ? 'all' : 'latest'
-    if (project.hasProperty('crossVersionTestsOnly')) {
-        include '**/*CrossVersion*'
-        forkEvery = 1
-    }
+    // use -PtestVersions=all or -PtestVersions=1.2,1.3…
+    systemProperties['org.gradle.integtest.versions'] = project.hasProperty('testVersions') ? project.testVersions : 'latest'
+
+    systemProperties['org.gradle.integtest.cpp.toolChains'] = project.hasProperty("testAllPlatforms") ? 'all' : 'default';
 
     dependsOn project.task("configure${task.name.capitalize()}") << {
         configure(task) {
-            testReportDir = file("${project.reporting.baseDir}/$name")
+            reports.html.destination = 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')
@@ -63,11 +118,25 @@ check.dependsOn(integTest)
 
 ['embedded', 'forking', 'daemon', 'embeddedDaemon', 'parallel'].each { mode ->
     def taskName = "${mode}IntegTest"
-    tasks.add(taskName, Test).configure {
+    tasks.create(taskName, Test).configure {
         systemProperties['org.gradle.integtest.executer'] = mode
     }
 }
 
+task crossVersionIntegTest {
+    description "Runs the cross version tests against all Gradle versions"
+}
+
+releasedVersions.allVersions.each { targetVersion ->
+    tasks.create("gradle${targetVersion}IntegTest", Test).configure {
+        crossVersionIntegTest.dependsOn path
+        description "Runs the cross version tests against Gradle ${targetVersion}"
+        systemProperties['org.gradle.integtest.versions'] = targetVersion
+        systemProperties['org.gradle.integtest.executer'] = 'forking'
+        include '**/*CrossVersion*'
+    }
+}
+
 plugins.withType(org.gradle.plugins.ide.idea.IdeaPlugin) { // lazy as plugin not applied yet
     idea {
         module {
diff --git a/gradle/providedConfiguration.gradle b/gradle/providedConfiguration.gradle
index fea86a7..496fe08 100644
--- a/gradle/providedConfiguration.gradle
+++ b/gradle/providedConfiguration.gradle
@@ -6,12 +6,13 @@
  */
 
 configurations {
-    provided.extendsFrom(compile)
-    testCompile.extendsFrom(provided)
+    provided
+    providedPlusCompile.extendsFrom(compile, provided)
+    testCompile.extendsFrom(providedPlusCompile)
 }
 
 sourceSets.main {
-    compileClasspath = configurations.provided
+    compileClasspath = configurations.providedPlusCompile
 }
 
 plugins.withType(IdeaPlugin) {
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index fc23d30..c59328f 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -23,7 +23,7 @@ task generatePom {
             configurations.publishCompile.allDependencies.withType(ProjectDependency).each {
                 publishRuntime "org.gradle:${it.dependencyProject.archivesBaseName}:${version}"
             }
-            configurations.publishCompile.allDependencies.withType(ExternalModuleDependency).each {
+            configurations.publishCompile.allDependencies.withType(ExternalDependency).each {
                 publishRuntime it
             }
         }
diff --git a/gradle/testGroupings.gradle b/gradle/testGroupings.gradle
new file mode 100644
index 0000000..52945e1
--- /dev/null
+++ b/gradle/testGroupings.gradle
@@ -0,0 +1,13 @@
+// only the projects that contribute runtime code
+def runtimeProjects = subprojects - [':docs', ':distributions', ':performance'].collect { project(it) }
+
+def runtimeProjectTasks = { String taskName ->
+    runtimeProjects.collect { it.tasks.findByPath(taskName) }.findAll { it != null }
+}
+task runtimeTests {
+    dependsOn { runtimeProjectTasks("test") }
+}
+
+task runtimeIntegTests {
+    dependsOn { runtimeProjectTasks("integTest") }
+}
\ No newline at end of file
diff --git a/gradle/testSetup.gradle b/gradle/testSetup.gradle
new file mode 100644
index 0000000..f20e797
--- /dev/null
+++ b/gradle/testSetup.gradle
@@ -0,0 +1,7 @@
+
+// Calculate the set of released versions - do this at configuration time because we need this to create various tasks
+// At some point, make this a configuration rule
+ext.releasedVersions = new org.gradle.build.ReleasedVersions()
+releasedVersions.destFile = new File(buildDir, "all-released-versions.json")
+releasedVersions.offline = gradle.startParameter.offline
+releasedVersions.prepare()
diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle
index 7b715eb..1d93b09 100644
--- a/gradle/versioning.gradle
+++ b/gradle/versioning.gradle
@@ -1,6 +1,10 @@
 def timestampFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmssZ')
 timestampFormat.timeZone = TimeZone.getTimeZone("UTC")
 
+if (buildTypes.isActive("promotionBuild")) {
+    logger.lifecycle "Invocation tasks: $gradle.startParameter.taskNames\nInvocation properties: $gradle.startParameter.projectProperties"
+}
+
 if (incomingDistributionsBuildReceipt) {
     ext.versionBase = incomingDistributionsBuildReceipt.versionBase
     ext.buildTimestamp = incomingDistributionsBuildReceipt.buildTimestamp
diff --git a/gradle/wrapper.gradle b/gradle/wrapper.gradle
index 5fd0747..7e5636d 100644
--- a/gradle/wrapper.gradle
+++ b/gradle/wrapper.gradle
@@ -15,19 +15,27 @@
  */
 
 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")
+    def wrapperTaskName = "${name}Wrapper"
+    def configureWrapperTaskName = "configure${wrapperTaskName.capitalize()}"
+
+    task "$configureWrapperTaskName" {
+        doLast {
+            configure(tasks."$wrapperTaskName") {
+                def version = new groovy.json.JsonSlurper().parseText(new URL("https://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
             }
-            println "updating wrapper to $label version: $version.version (downloadUrl: $version.downloadUrl)"
-            distributionUrl version.downloadUrl
         }
+    }
+
+    task "${wrapperTaskName}"(type: Wrapper, dependsOn: configureWrapperTaskName) {
+        group = "wrapper"
+		def jvmOpts = "-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8"
+        inputs.property("jvmOpts", jvmOpts)        
         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")
@@ -37,4 +45,4 @@ def wrapperUpdateTask = { name, label ->
 
 wrapperUpdateTask "nightly", "nightly"
 wrapperUpdateTask "rc", "release-candidate"
-wrapperUpdateTask "current", "current"
\ No newline at end of file
+wrapperUpdateTask "current", "current"
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..1350737
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 8e1362d..07e3d3d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Mar 11 12:15:50 GMT 2013
+#Thu Mar 13 18:36:09 CET 2014
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.5-rc-1-bin.zip
+distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.12-20140312230025+0000-bin.zip
diff --git a/settings.gradle b/settings.gradle
index 801a90d..50074a8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -22,6 +22,7 @@ include 'wrapper'
 include 'cli'
 include 'launcher'
 include 'messaging'
+include 'resources'
 include 'plugins'
 include 'scala'
 include 'ide'
@@ -50,6 +51,10 @@ include 'reporting'
 include 'diagnostics'
 include 'publish'
 include 'ivy'
+include 'jacoco'
+include 'buildInit'
+include 'languageBase'
+include 'languageJvm'
 
 rootProject.name = 'gradle'
 rootProject.children.each {project ->
diff --git a/subprojects/announce/announce.gradle b/subprojects/announce/announce.gradle
index 68a1e47..6e68087 100644
--- a/subprojects/announce/announce.gradle
+++ b/subprojects/announce/announce.gradle
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy
-
+    compile libraries.groovy
     compile libraries.slf4j_api
     compile project(':core')
     integTestRuntime project(':plugins')
 }
 
-if (!javaVersion.java6Compatible) {
-    sourceSets.main.groovy.exclude '**/jdk6/**'
-}
-
 useTestFixtures()
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
index 87d879d..d9b630e 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
@@ -18,16 +18,13 @@ package org.gradle.api.plugins.announce
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.internal.project.ProjectInternal
 
 /**
  * This plugin allows to send announce messages to Twitter.
- *
- * @author hackergarten
  */
 class AnnouncePlugin implements Plugin<Project> {
     void apply(Project project) {
-        project.extensions.add("announce", new AnnouncePluginExtension((ProjectInternal)project))
+        project.extensions.create("announce", AnnouncePluginExtension.class, project)
     }
 }
 
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy
index ab6f62c..55ffd22 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy
@@ -37,14 +37,14 @@ class AnnouncePluginExtension {
      */
     String password
 
-    Announcer local
-
+    private final onDemandLocalAnnouncer
     private final Project project
     AnnouncerFactory announcerFactory
 
     AnnouncePluginExtension(ProjectInternal project) {
         this.project = project
         this.announcerFactory = new DefaultAnnouncerFactory(this, project, new DefaultIconProvider(project.services.get(GradleDistributionLocator)))
+        this.onDemandLocalAnnouncer = new LocalAnnouncer(this)
     }
 
     /**
@@ -53,14 +53,14 @@ class AnnouncePluginExtension {
      * @return The announcer.
      */
     Announcer getLocal() {
-        return new Announcer() {
-            void send(String title, String message) {
-                if (local == null) {
-                    local = announcerFactory.createAnnouncer("local")
-                }
-                local.send(title, message)
-            }
-        }
+        return onDemandLocalAnnouncer
+    }
+
+    /**
+     * Sets the {@link Announcer} that should be used to send announcements to the local desktop.
+     */
+    void setLocal(Announcer localAnnouncer) {
+        onDemandLocalAnnouncer.local = localAnnouncer
     }
 
     /**
@@ -76,4 +76,20 @@ class AnnouncePluginExtension {
             logger.warn("Failed to send message '$msg' to '$type'", e)
         }
     }
+
+    private static class LocalAnnouncer implements Announcer {
+        AnnouncePluginExtension extension
+        Announcer local
+
+        LocalAnnouncer(AnnouncePluginExtension extension) {
+            this.extension = extension
+        }
+
+        void send(String title, String message) {
+            if (local == null) {
+                local = extension.getAnnouncerFactory().createAnnouncer("local")
+            }
+            local.send(title, message)
+        }
+    }
 }
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerFactory.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerFactory.groovy
index edd5270..b11870f 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerFactory.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerFactory.groovy
@@ -17,9 +17,6 @@ package org.gradle.api.plugins.announce.internal
 
 import org.gradle.api.plugins.announce.Announcer
 
-/**
- * @author Hans Dockter
- */
 interface AnnouncerFactory {
     Announcer createAnnouncer(String type)
 }
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
index 8c5b687..d78f09a 100755
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
@@ -22,9 +22,6 @@ import org.gradle.api.plugins.announce.AnnouncePluginExtension
 import org.gradle.api.plugins.announce.Announcer
 import org.gradle.internal.os.OperatingSystem
 
-/**
- * @author Hans Dockter
- */
 class DefaultAnnouncerFactory implements AnnouncerFactory {
     private final AnnouncePluginExtension announcePluginConvention
     private final IconProvider iconProvider
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
index f74a00f..ebcc3d4 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
@@ -23,10 +23,7 @@ import org.gradle.internal.os.OperatingSystem
 
 /**
  * This class wraps the Ubuntu Notify Send functionality.
- *
- * @author Hackergarten
  */
-
 class NotifySend implements Announcer {
     private final IconProvider iconProvider
     private final ProcessOperations processOperations
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Twitter.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Twitter.groovy
index a697f15..70e0664 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Twitter.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Twitter.groovy
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package org.gradle.api.plugins.announce.internal;
+package org.gradle.api.plugins.announce.internal
 
+import org.gradle.api.plugins.announce.Announcer
+import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import sun.misc.BASE64Encoder
-import org.slf4j.Logger
-import org.gradle.api.plugins.announce.Announcer
 
 /**
  * This class allows to send announce messages to Twitter.
- *
- * @author Hackergarten
  */
 class Twitter implements Announcer {
     private static final Logger logger = LoggerFactory.getLogger(Twitter)
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy
index a2279f4..0d7b22f 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy
@@ -17,16 +17,12 @@ package org.gradle.api.plugins.announce
 
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.announce.internal.AnnouncerFactory
+import org.gradle.util.TestUtil
 import spock.lang.Specification
-import org.gradle.util.HelperUtil
-
-/**
- * @author Hans Dockter
- */
 
 class AnnouncePluginExtensionTest extends Specification {
     final AnnouncerFactory announcerFactory = Mock()
-    final ProjectInternal project = HelperUtil.createRootProject()
+    final ProjectInternal project = TestUtil.createRootProject()
     final AnnouncePluginExtension announcePluginConvention = new AnnouncePluginExtension(project)
 
     def setup() {
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
index 939c63b..b03bbe4 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
@@ -15,16 +15,13 @@
  */
 package org.gradle.api.plugins.announce
 
-import spock.lang.Specification
-import org.gradle.util.HelperUtil
 import org.gradle.api.Project
+import org.gradle.util.TestUtil
+import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class AnnouncePluginTest extends Specification {
     AnnouncePlugin announcePlugin = new AnnouncePlugin()
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def addExtensionObject() {
         when:
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy
index 3604998..178ea23 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy
@@ -16,11 +16,11 @@
 package org.gradle.api.plugins.announce
 
 import spock.lang.Specification
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.gradle.api.Project
 
 class BuildAnnouncementsPluginTest extends Specification {
-    final Project project = HelperUtil.createRootProject()
+    final Project project = TestUtil.createRootProject()
     final BuildAnnouncementsPlugin plugin = new BuildAnnouncementsPlugin()
 
     def "applies announce plugin"() {
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy
index 48dcbe3..97289bf 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy
@@ -18,14 +18,11 @@ package org.gradle.api.plugins.announce.internal
 import org.gradle.api.internal.ProcessOperations
 import org.gradle.api.plugins.announce.AnnouncePluginExtension
 import org.gradle.internal.os.OperatingSystem
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class DefaultAnnouncerFactoryTest extends Specification {
-    final project = HelperUtil.createRootProject()
+    final project = TestUtil.createRootProject()
     final extension = new AnnouncePluginExtension(project)
     final ProcessOperations processOperations = Mock()
     final IconProvider iconProvider = Mock()
diff --git a/subprojects/antlr/antlr.gradle b/subprojects/antlr/antlr.gradle
index 5c614f1..9a1eca4 100644
--- a/subprojects/antlr/antlr.gradle
+++ b/subprojects/antlr/antlr.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
index ee2a663..56dfe62 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
@@ -36,8 +36,6 @@ import static org.gradle.api.plugins.JavaPlugin.COMPILE_CONFIGURATION_NAME;
 
 /**
  * A plugin for adding Antlr support to {@link JavaPlugin java projects}.
- *
- * @author Steve Ebersole
  */
 public class AntlrPlugin implements Plugin<ProjectInternal> {
     public static final String ANTLR_CONFIGURATION_NAME = "antlr";
@@ -53,7 +51,7 @@ public class AntlrPlugin implements Plugin<ProjectInternal> {
 
         // set up a configuration named 'antlr' for the user to specify the antlr libs to use in case
         // they want a specific version etc.
-        Configuration antlrConfiguration = project.getConfigurations().add(ANTLR_CONFIGURATION_NAME).setVisible(false)
+        Configuration antlrConfiguration = project.getConfigurations().create(ANTLR_CONFIGURATION_NAME).setVisible(false)
                 .setTransitive(false).setDescription("The Antlr libraries to be used for this project.");
         project.getConfigurations().getByName(COMPILE_CONFIGURATION_NAME).extendsFrom(antlrConfiguration);
 
@@ -73,7 +71,7 @@ public class AntlrPlugin implements Plugin<ProjectInternal> {
                         // 2) create an AntlrTask for this sourceSet following the gradle
                         //    naming conventions via call to sourceSet.getTaskName()
                         final String taskName = sourceSet.getTaskName("generate", "GrammarSource");
-                        AntlrTask antlrTask = project.getTasks().add(taskName, AntlrTask.class);
+                        AntlrTask antlrTask = project.getTasks().create(taskName, AntlrTask.class);
                         antlrTask.setDescription(String.format("Processes the %s Antlr grammars.",
                                 sourceSet.getName()));
 
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java
index 2907b23..cd71f72 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java
@@ -24,8 +24,6 @@ import org.gradle.api.file.SourceDirectorySet;
  * injecting a virtual directory named 'antlr' into the project's various {@link org.gradle.api.tasks.SourceSet source
  * sets}.  Its implementation gets pushed onto the {@link org.gradle.api.internal.DynamicObjectAware} portion of the
  * source set under the name 'antlr'.
- *
- * @author Steve Ebersole
  */
 public interface AntlrSourceVirtualDirectory {
     public static final String NAME = "antlr";
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
index 452f97a..44a2696 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
@@ -16,24 +16,24 @@
 
 package org.gradle.api.plugins.antlr;
 
-import java.io.File;
-import java.util.List;
-
 import org.apache.tools.ant.taskdefs.optional.ANTLR;
 import org.apache.tools.ant.types.Path;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.plugins.antlr.internal.GenerationPlan;
+import org.gradle.api.plugins.antlr.internal.GenerationPlanBuilder;
+import org.gradle.api.plugins.antlr.internal.MetadataExtracter;
+import org.gradle.api.plugins.antlr.internal.XRef;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.SourceTask;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.plugins.antlr.internal.MetadataExtracter;
-import org.gradle.api.plugins.antlr.internal.XRef;
-import org.gradle.api.plugins.antlr.internal.GenerationPlanBuilder;
 import org.gradle.util.GFileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.util.List;
+
 /**
  * <p>Generates parsers from Antlr grammars.</p>
  *
@@ -42,8 +42,6 @@ import org.slf4j.LoggerFactory;
  * classpath it uses to perform generation execution.  This <b>should</b> really only require the antlr jar.  In {@link
  * AntlrPlugin} usage, this would happen simply by adding your antlr jar into the 'antlr' dependency configuration
  * created and exposed by the {@link AntlrPlugin} itself.</p>
- *
- * @author Steve Ebersole
  */
 public class AntlrTask extends SourceTask {
     private static final Logger LOGGER = LoggerFactory.getLogger(AntlrTask.class);
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/AntlrSourceVirtualDirectoryImpl.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/AntlrSourceVirtualDirectoryImpl.java
index 27dced5..006320d 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/AntlrSourceVirtualDirectoryImpl.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/AntlrSourceVirtualDirectoryImpl.java
@@ -24,8 +24,6 @@ import org.gradle.util.ConfigureUtil;
 
 /**
  * The implementation of the {@link org.gradle.api.plugins.antlr.AntlrSourceVirtualDirectory} contract.
- *
- * @author Steve Ebersole
  */
 public class AntlrSourceVirtualDirectoryImpl implements AntlrSourceVirtualDirectory {
     private final SourceDirectorySet antlr;
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlan.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlan.java
index a6b0db7..97d1247 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlan.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlan.java
@@ -19,8 +19,6 @@ import java.io.File;
 
 /**
  * Models information relevant to generation of a particular Antlr grammar file.
- *
- * @author Steve Ebersole
  */
 public class GenerationPlan {
     private final File source;
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlanBuilder.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlanBuilder.java
index 0cad3f1..2d82713 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlanBuilder.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GenerationPlanBuilder.java
@@ -15,21 +15,19 @@
  */
 package org.gradle.api.plugins.antlr.internal;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Iterator;
-import java.util.ArrayList;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Builder for the properly order list of {@link GenerationPlan generation plans}.
  *
  * <p>IMPL NOTE : Uses recursive calls to achieve ordering.</p>
- *
- * @author Steve Ebersole
  */
 public class GenerationPlanBuilder {
     private static final Logger LOGGER = LoggerFactory.getLogger(GenerationPlanBuilder.class);
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarDelegate.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarDelegate.java
index 255fd29..497eb88 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarDelegate.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarDelegate.java
@@ -15,19 +15,17 @@
  */
 package org.gradle.api.plugins.antlr.internal;
 
+import antlr.collections.impl.IndexedVector;
+import antlr.preprocessor.GrammarFile;
+
 import java.lang.reflect.Method;
-import java.util.Enumeration;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
 
-import antlr.collections.impl.IndexedVector;
-import antlr.preprocessor.GrammarFile;
-
 /**
  * Antlr defines its {@link antlr.preprocessor.Grammar} class as package-protected for some unfortunate reason. So this class acts as a delegate to the Antlr {@link antlr.preprocessor.Grammar} class,
  * hiding all the ugly necessary reflection code.
- *
- * @author Steve Ebersole
  */
 public class GrammarDelegate {
     public static List<GrammarDelegate> extractGrammarDelegates(GrammarFile antlrGrammarFile) {
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarFileMetadata.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarFileMetadata.java
index 0ef041f..7d3daa5 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarFileMetadata.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarFileMetadata.java
@@ -15,15 +15,13 @@
  */
 package org.gradle.api.plugins.antlr.internal;
 
-import java.util.List;
-import java.util.ArrayList;
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Models information about an Antlr grammar file, including the inidividual {@link #getGrammars grammars} (lexers,
  * parsers, etc) contained within it.
- *
- * @author Steve Ebersole
  */
 public class GrammarFileMetadata {
     private final File filePath;
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarMetadata.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarMetadata.java
index 796370f..371715e 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarMetadata.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/GrammarMetadata.java
@@ -15,15 +15,13 @@
  */
 package org.gradle.api.plugins.antlr.internal;
 
-import java.io.File;
-
-import antlr.TreeParser;
 import antlr.Parser;
+import antlr.TreeParser;
+
+import java.io.File;
 
 /**
  * Models a grammar defined within an Antlr grammar file.
- *
- * @author Steve Ebersole
  */
 public class GrammarMetadata {
     private final GrammarFileMetadata grammarFileMetadata;
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/MetadataExtracter.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/MetadataExtracter.java
index 35e480f..fee4fd8 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/MetadataExtracter.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/MetadataExtracter.java
@@ -15,20 +15,14 @@
  */
 package org.gradle.api.plugins.antlr.internal;
 
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileTree;
 
+import java.io.*;
+
 /**
  * Preprocess an Antlr grammar file so that dependencies between grammars can be properly determined such that they can
  * be processed for generation in proper order later.
- *
- * @author Steve Ebersole
  */
 public class MetadataExtracter {
 
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/XRef.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/XRef.java
index 2b73ded..976fee9 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/XRef.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/internal/XRef.java
@@ -15,17 +15,15 @@
  */
 package org.gradle.api.plugins.antlr.internal;
 
-import java.util.LinkedHashMap;
+import antlr.preprocessor.Hierarchy;
+
 import java.util.HashMap;
 import java.util.Iterator;
-
-import antlr.preprocessor.Hierarchy;
+import java.util.LinkedHashMap;
 
 /**
  * Models cross-reference (x-ref) info about {@link GrammarFileMetadata grammar files} such as {@link #filesByPath},
  * {@link #filesByExportVocab} and {@link #filesByClassName}.
- *
- * @author Steve Ebersole
  */
 public class XRef {
     private final Hierarchy antlrHierarchy;
diff --git a/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy b/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
index b94619d..42fccb2 100644
--- a/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
+++ b/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
@@ -18,10 +18,10 @@ package org.gradle.api.plugins.antlr
 
 import spock.lang.Specification
 import org.gradle.api.Project
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 
 class AntlrPluginTest extends Specification {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
 
     def addsAntlrPropertiesToEachSourceSet() {
         when:
@@ -35,7 +35,7 @@ class AntlrPluginTest extends Specification {
         test.antlr.srcDirs == [project.file('src/test/antlr')] as Set
 
         when:
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
 
         then:
         def custom = project.sourceSets.custom
@@ -56,7 +56,7 @@ class AntlrPluginTest extends Specification {
         project.tasks.compileTestJava.taskDependencies.getDependencies(null).contains(test)
 
         when:
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
 
         then:
         def custom = project.tasks.generateCustomGrammarSource
diff --git a/subprojects/base-services-groovy/base-services-groovy.gradle b/subprojects/base-services-groovy/base-services-groovy.gradle
index 90d6a16..c50fe5d 100644
--- a/subprojects/base-services-groovy/base-services-groovy.gradle
+++ b/subprojects/base-services-groovy/base-services-groovy.gradle
@@ -15,7 +15,7 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
     compile project(":baseServices")
 }
 
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
index 7da1f73..90cce09 100644
--- 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
@@ -26,7 +26,6 @@ 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> {
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
index b21d85c..98fb300 100644
--- 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
@@ -48,7 +48,7 @@ abstract public class AbstractCompositeSpecTest {
     @Test
     public void init() {
         org.gradle.api.specs.CompositeSpec<Object> compositeSpec = createCompositeSpec(spec1, spec2);
-        Assert.assertEquals(CollectionUtils.flattenToList(spec1, spec2), compositeSpec.getSpecs());
+        Assert.assertEquals(CollectionUtils.flattenCollections(spec1, spec2), compositeSpec.getSpecs());
     }
 
     protected Spec<Object>[] createAtomicElements(boolean... satisfies) {
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
index a26dda9..37f447b 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.specs;
 
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.junit.Test;
 
 import static org.junit.Assert.assertFalse;
@@ -51,7 +51,7 @@ public class AndSpecTest extends AbstractCompositeSpecTest {
     @Test
     public void canAddClosureAsASpec() {
         AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
-        spec = spec.and(HelperUtil.toClosure("{ false }"));
+        spec = spec.and(TestUtil.toClosure("{ false }"));
         assertFalse(spec.isSatisfiedBy(new Object()));
     }
 }
diff --git a/subprojects/base-services/base-services.gradle b/subprojects/base-services/base-services.gradle
index f825ab8..2a2dcff 100644
--- a/subprojects/base-services/base-services.gradle
+++ b/subprojects/base-services/base-services.gradle
@@ -5,9 +5,9 @@
  * application (eg as part of the tooling API).
  */
 dependencies {
-    groovy libraries.groovy
-
     publishCompile libraries.slf4j_api
+    compile libraries.guava
+    testCompile libraries.groovy
 }
 
 useTestFixtures()
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/GradleException.java b/subprojects/base-services/src/main/java/org/gradle/api/GradleException.java
index 788305f..430a702 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/GradleException.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/GradleException.java
@@ -18,8 +18,6 @@ package org.gradle.api;
 
 /**
  * <p><code>GradleException</code> is the base class of all exceptions thrown by Gradle.</p>
- *
- * @author Hans Dockter
  */
 public class GradleException extends RuntimeException {
     public GradleException() {
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 b1b430f..c6dff4e 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
@@ -15,8 +15,6 @@
  */
 package org.gradle.api;
 
-import org.gradle.internal.SystemProperties;
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -24,7 +22,7 @@ import java.util.regex.Pattern;
  * An enumeration of Java versions.
  */
 public enum JavaVersion {
-    VERSION_1_1(false), VERSION_1_2(false), VERSION_1_3(false), VERSION_1_4(false), VERSION_1_5(true), VERSION_1_6(true), VERSION_1_7(true), VERSION_1_8(true);
+    VERSION_1_1(false), VERSION_1_2(false), VERSION_1_3(false), VERSION_1_4(false), VERSION_1_5(true), VERSION_1_6(true), VERSION_1_7(true), VERSION_1_8(true), VERSION_1_9(true);
 
     private final boolean hasMajorVersion;
 
@@ -71,7 +69,7 @@ public enum JavaVersion {
      * @return The version of the current JVM.
      */
     public static JavaVersion current() {
-        return toVersion(SystemProperties.getJavaVersion());
+        return toVersion(System.getProperty("java.version"));
     }
 
     public boolean isJava5() {
@@ -90,20 +88,28 @@ public enum JavaVersion {
         return this == VERSION_1_8;
     }
 
+    private boolean isJava9() {
+        return this == VERSION_1_9;
+    }
+
     public boolean isJava5Compatible() {
-        return isJava5() || isJava6Compatible();
+        return this.compareTo(VERSION_1_5) >= 0;
     }
 
     public boolean isJava6Compatible() {
-        return isJava6() || isJava7Compatible();
+        return this.compareTo(VERSION_1_6) >= 0;
     }
 
     public boolean isJava7Compatible() {
-        return isJava7() || isJava8Compatible();
+        return this.compareTo(VERSION_1_7) >= 0;
     }
 
     public boolean isJava8Compatible() {
-        return isJava8();
+        return this.compareTo(VERSION_1_8) >= 0;
+    }
+
+    public boolean isJava9Compatible() {
+        return this.compareTo(VERSION_1_9) >= 0;
     }
 
     @Override
@@ -114,4 +120,8 @@ public enum JavaVersion {
     private String getName() {
         return name().substring("VERSION_".length()).replace('_', '.');
     }
+
+    public String getMajorVersion() {
+        return name().substring(10);
+    }
 }
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
deleted file mode 100644
index 20d3913..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java
+++ /dev/null
@@ -1,208 +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;
-
-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(Iterable<Action<? super T>> actions) {
-        return new CompositeAction<T>(actions);
-    }
-
-    /**
-     * 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 composite(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);
-            }
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            CompositeAction that = (CompositeAction) o;
-
-            if (!actions.equals(that.actions)) {
-                return false;
-            }
-
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            return actions.hashCode();
-        }
-    }
-
-    /**
-     * 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
deleted file mode 100644
index 505c6c7..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/Cast.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;
-
-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
deleted file mode 100644
index 24d846d..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/ErroringAction.java
+++ /dev/null
@@ -1,44 +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;
-
-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/Factory.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
index 0e4242e..7464292 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
@@ -16,8 +16,10 @@
 package org.gradle.api.internal;
 
 /**
+ * DO NOT REMOVE.
+ *
  * @deprecated This is here because tasks implemented in Groovy that are compiled against older versions of Gradle have this type baked into their byte-code, and cannot be loaded if it's not found.
  */
 @Deprecated
-public interface Factory<T> extends org.gradle.internal.Factory<T> {
+public interface Factory<T> {
 }
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
deleted file mode 100644
index 9702306..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/HasInternalProtocol.java
+++ /dev/null
@@ -1,35 +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;
-
-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
deleted file mode 100644
index 03f4fdc..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java
+++ /dev/null
@@ -1,93 +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;
-
-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 writeTextFile(File output, String encoding, Action<? super BufferedWriter> action) {
-        createTextFileWriteAction(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 writeTextFile(File output, Action<? super BufferedWriter> action) {
-        writeTextFile(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>> createTextFileWriteAction(File output, String encoding) {
-        return new TextFileWriterIoAction(output, encoding);
-    }
-
-    private static class TextFileWriterIoAction implements Action<Action<? super BufferedWriter>> {
-        private final File file;
-        private final String encoding;
-
-        private TextFileWriterIoAction(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
deleted file mode 100644
index c066c8e..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/Transformers.java
+++ /dev/null
@@ -1,95 +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;
-
-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/internal/project/ServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
index 92825aa..2f45094 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
@@ -16,8 +16,10 @@
 package org.gradle.api.internal.project;
 
 /**
+ * DO NOT REMOVE.
+ *
  * @deprecated This is here because tasks implemented in Groovy that are compiled against older versions of Gradle have this type baked into their byte-code, and cannot be loaded if it's not found.
  */
 @Deprecated
-public interface ServiceRegistry extends org.gradle.internal.service.ServiceRegistry {
+public interface ServiceRegistry {
 }
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
index f3ce645..b605164 100644
--- 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
@@ -23,7 +23,6 @@ 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> {
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
index 6938d8b..be6db06 100644
--- 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
@@ -18,7 +18,6 @@ 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> {
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
index 9f63a61..3c84c8c 100644
--- 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
@@ -21,7 +21,6 @@ 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> {
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/specs/Spec.java b/subprojects/base-services/src/main/java/org/gradle/api/specs/Spec.java
index a6522b4..ebb6ae7 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/specs/Spec.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/specs/Spec.java
@@ -18,7 +18,6 @@ package org.gradle.api.specs;
 /**
  * Represents some predicate against objects of type T.
  *
- * @author Hans Dockter
  * @param <T> The target type for this Spec
  */
 public interface Spec<T> {
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java b/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java
new file mode 100644
index 0000000..0fe20f7
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import org.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(Iterable<Action<? super T>> actions) {
+        return new CompositeAction<T>(actions);
+    }
+
+    /**
+     * 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 composite(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);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            CompositeAction that = (CompositeAction) o;
+
+            if (!actions.equals(that.actions)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return actions.hashCode();
+        }
+    }
+
+    /**
+     * 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/internal/Cast.java b/subprojects/base-services/src/main/java/org/gradle/internal/Cast.java
new file mode 100644
index 0000000..39ded66
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/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.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/internal/CompositeStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
deleted file mode 100644
index 74dcf22..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.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.internal;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public class CompositeStoppable implements Stoppable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeStoppable.class);
-    private final List<Stoppable> elements = new CopyOnWriteArrayList<Stoppable>();
-
-    public CompositeStoppable() {
-    }
-
-    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) {
-        for (Object element : elements) {
-            this.elements.add(toStoppable(element));
-        }
-        return this;
-    }
-
-    public CompositeStoppable add(Object... elements) {
-        for (Object closeable : elements) {
-            this.elements.add(toStoppable(closeable));
-        }
-        return this;
-    }
-
-    private static Object invoke(Method method, Object target, Object... args) {
-        try {
-            method.setAccessible(true);
-            return method.invoke(target, args);
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.throwAsUncheckedException(e.getCause());
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    private static Stoppable toStoppable(final Object object) {
-        if (object == null) {
-            return new Stoppable() {
-                public void stop() {
-                }
-            };
-        }
-        if (object instanceof Iterable) {
-            throw new UnsupportedOperationException("Not implemented yet.");
-        }
-        if (object instanceof Stoppable) {
-            return (Stoppable) object;
-        }
-        if (object instanceof Closeable) {
-            final Closeable closeable = (Closeable) object;
-            return new Stoppable() {
-                @Override
-                public String toString() {
-                    return closeable.toString();
-                }
-
-                public void stop() {
-                    try {
-                        closeable.close();
-                    } catch (IOException e) {
-                        throw UncheckedException.throwAsUncheckedException(e);
-                    }
-                }
-            };
-        }
-        return new Stoppable() {
-            @Override
-            public String toString() {
-                return object.toString();
-            }
-
-            public void stop() {
-                try {
-                    invoke(object.getClass().getMethod("stop"), object);
-                } catch (NoSuchMethodException e) {
-                    // ignore
-                }
-                try {
-                    invoke(object.getClass().getMethod("close"), object);
-                } catch (NoSuchMethodException e) {
-                    // ignore
-                }
-            }
-        };
-    }
-
-    public void stop() {
-        Throwable failure = null;
-        try {
-            for (Stoppable element : elements) {
-                try {
-                    element.stop();
-                } catch (Throwable throwable) {
-                    if (failure == null) {
-                        failure = throwable;
-                    } else {
-                        LOGGER.error(String.format("Could not stop %s.", element), throwable);
-                    }
-                }
-            }
-        } finally {
-            elements.clear();
-        }
-
-        if (failure != null) {
-            throw UncheckedException.throwAsUncheckedException(failure);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/ErroringAction.java b/subprojects/base-services/src/main/java/org/gradle/internal/ErroringAction.java
new file mode 100644
index 0000000..602e556
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/ErroringAction.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;
+
+import org.gradle.api.Action;
+
+/**
+ * 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/internal/Factories.java b/subprojects/base-services/src/main/java/org/gradle/internal/Factories.java
index 0754a14..1085221 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/Factories.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Factories.java
@@ -24,4 +24,12 @@ public abstract class Factories {
             }
         };
     }
+
+    public static <T> Factory<T> constant(final T item) {
+        return new Factory<T>() {
+            public T create() {
+                return item;
+            }
+        };
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/FileUtils.java b/subprojects/base-services/src/main/java/org/gradle/internal/FileUtils.java
new file mode 100644
index 0000000..507d22d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/FileUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import org.gradle.api.GradleException;
+
+import java.io.File;
+
+public class FileUtils {
+    public static final int WINDOWS_PATH_LIMIT = 260;
+
+    /**
+     * Converts a string into a string that is safe to use as a file name. The result will only include ascii
+     * characters and numbers, and the "-","_", #, $ and "." characters.
+     */
+    public static String toSafeFileName(String name) {
+        int size = name.length();
+        StringBuffer rc = new StringBuffer(size * 2);
+        for (int i = 0; i < size; i++) {
+            char c = name.charAt(i);
+            boolean valid = c >= 'a' && c <= 'z';
+            valid = valid || (c >= 'A' && c <= 'Z');
+            valid = valid || (c >= '0' && c <= '9');
+            valid = valid || (c == '_') || (c == '-') || (c == '.') || (c == '$');
+            if (valid) {
+                rc.append(c);
+            } else {
+                // Encode the character using hex notation
+                rc.append('#');
+                rc.append(Integer.toHexString(c));
+            }
+        }
+        return rc.toString();
+    }
+
+    public static File assertInWindowsPathLengthLimitation(File file){
+        if(file.getAbsolutePath().length() > WINDOWS_PATH_LIMIT){
+            throw new GradleException(String.format("Cannot create file. '%s' exceeds windows path limitation of %d character.", file.getAbsolutePath(), WINDOWS_PATH_LIMIT));
+
+        }
+        return file;
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/HasInternalProtocol.java b/subprojects/base-services/src/main/java/org/gradle/internal/HasInternalProtocol.java
new file mode 100644
index 0000000..65e1420
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/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.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/internal/IoActions.java b/subprojects/base-services/src/main/java/org/gradle/internal/IoActions.java
new file mode 100644
index 0000000..001f3ef
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/IoActions.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import org.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 writeTextFile(File output, String encoding, Action<? super BufferedWriter> action) {
+        createTextFileWriteAction(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 writeTextFile(File output, Action<? super BufferedWriter> action) {
+        writeTextFile(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>> createTextFileWriteAction(File output, String encoding) {
+        return new TextFileWriterIoAction(output, encoding);
+    }
+
+    /**
+     * Performs the given action against the given resource, then closes the resource. Ignores failure to close the resource when
+     * the action throws an exception.
+     *
+     * @param resource
+     * @param action
+     * @param <T>
+     */
+    public static <T extends Closeable> void withResource(T resource, Action<? super T> action) {
+        try {
+            action.execute(resource);
+        } catch(Throwable t) {
+            try {
+                resource.close();
+            } catch (IOException e) {
+                // Ignored
+            }
+            throw UncheckedException.throwAsUncheckedException(t);
+        }
+
+        try {
+            resource.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private static class TextFileWriterIoAction implements Action<Action<? super BufferedWriter>> {
+        private final File file;
+        private final String encoding;
+
+        private TextFileWriterIoAction(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/internal/LazyIterable.java b/subprojects/base-services/src/main/java/org/gradle/internal/LazyIterable.java
deleted file mode 100644
index 74ea26d..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/internal/LazyIterable.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.internal;
-
-import java.util.Iterator;
-
-public class LazyIterable<T> implements Iterable<T> {
-
-    private final Factory<Iterable<T>> factory;
-
-    public LazyIterable(Factory<Iterable<T>> factory) {
-        this.factory = factory;
-    }
-
-    public Iterator<T> iterator() {
-        return factory.create().iterator();
-    }
-
-}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Stoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/Stoppable.java
deleted file mode 100755
index af5dc11..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/internal/Stoppable.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.internal;
-
-/**
- * Represents an object which performs concurrent activity.
- */
-public interface Stoppable {
-    /**
-     * <p>Requests a graceful stop of this object. Blocks until all concurrent activity has been completed.</p>
-     *
-     * <p>If this object has already been stopped, this method does nothing.</p>
-     */
-    void stop();
-}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Supplier.java b/subprojects/base-services/src/main/java/org/gradle/internal/Supplier.java
new file mode 100644
index 0000000..7f64a15
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Supplier.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import org.gradle.api.Transformer;
+
+public interface Supplier<I> {
+
+    <R> R supplyTo(Transformer<R, ? super I> transformer);
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Suppliers.java b/subprojects/base-services/src/main/java/org/gradle/internal/Suppliers.java
new file mode 100644
index 0000000..d57f4ed
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Suppliers.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import org.gradle.api.Transformer;
+
+import java.io.Closeable;
+
+public abstract class Suppliers {
+
+    public static <I extends Closeable> Supplier<I> ofQuietlyClosed(final Factory<I> factory) {
+        return new Supplier<I>() {
+            public <R> R supplyTo(Transformer<R, ? super I> transformer) {
+                I thing = factory.create();
+                try {
+                    return transformer.transform(thing);
+                } finally {
+                    try {
+                        thing.close();
+                    } catch (Exception ignore) {
+                        // ignore
+                    }
+                }
+            }
+        };
+    }
+
+    public static <I> Supplier<I> of(final Factory<? extends I> factory) {
+        return new Supplier<I>() {
+            public <R> R supplyTo(Transformer<R, ? super I> transformer) {
+                I value = factory.create();
+                return transformer.transform(value);
+            }
+        };
+    }
+
+    public static <I, T> Supplier<T> wrap(final Supplier<? extends I> supplier, final Transformer<T, ? super I> suppliedTransformer) {
+        return new Supplier<T>() {
+            public <R> R supplyTo(final Transformer<R, ? super T> transformer) {
+                return supplier.supplyTo(new Transformer<R, I>() {
+                    public R transform(I original) {
+                        T transformedSupplied = suppliedTransformer.transform(original);
+                        return transformer.transform(transformedSupplied);
+                    }
+                });
+            }
+        };
+    }
+
+}
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 01a3951..b161335 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
@@ -16,16 +16,16 @@
 package org.gradle.internal;
 
 import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
+import static com.google.common.collect.ImmutableSet.of;
+
 /**
  * Provides access to frequently used system properties.
  */
 public class SystemProperties {
-    private static final Set<String> STANDARD_PROPERTIES = new HashSet<String>(Arrays.asList(
+    private static final Set<String> STANDARD_PROPERTIES = of(
             "java.version",
             "java.vendor",
             "java.vendor.url",
@@ -53,7 +53,12 @@ public class SystemProperties {
             "line.separator",
             "user.name",
             "user.home",
-            "user.dir"));
+            "user.dir"
+    );
+
+    private static final Set<String> IMPORTANT_NON_STANDARD_PROPERTIES = of(
+            "java.runtime.version"
+    );
 
     @SuppressWarnings("unchecked")
     public static Map<String, String> asMap() {
@@ -87,4 +92,15 @@ public class SystemProperties {
     public static Set<String> getStandardProperties() {
         return STANDARD_PROPERTIES;
     }
+
+    /**
+     * Returns the names of properties that are not guaranteed to be contained in System.getProperties()
+     * but are usually there and if there should not be adjusted.
+     *
+     * @return the set of keys of {@code System.getProperties()} which should not be adjusted
+     *   by client code. This method never returns {@code null}.
+     */
+    public static Set<String> getNonStandardImportantProperties() {
+        return IMPORTANT_NON_STANDARD_PROPERTIES;
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Transformers.java b/subprojects/base-services/src/main/java/org/gradle/internal/Transformers.java
new file mode 100644
index 0000000..edde1a0
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Transformers.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.Named;
+import org.gradle.api.Namer;
+import org.gradle.api.Transformer;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * 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);
+    }
+
+    //just returns the original object
+    public static <T> Transformer<T, T> noOpTransformer() {
+        return new Transformer<T, T>() {
+            public T transform(T original) {
+                return original;
+            }
+        };
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * A getClass() transformer.
+     *
+     * @param <T> The type of the object
+     * @return A getClass() transformer.
+     */
+    public static <T> Transformer<Class<T>, T> type() {
+        return new Transformer<Class<T>, T>() {
+            public Class<T> transform(T original) {
+                @SuppressWarnings("unchecked")
+                Class<T> aClass = (Class<T>) original.getClass();
+                return aClass;
+            }
+        };
+    }
+
+    /**
+     * Converts an {@link Action} to a {@link Transformer} that runs the action against the input value and returns {@code null}.
+     */
+    public static <R, I> Transformer<R, I> toTransformer(final Action<? super I> action) {
+        return new Transformer<R, I>() {
+            public R transform(I original) {
+                action.execute(original);
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Converts a URL to a URI
+     */
+    public static Transformer<URL, URI> toURL() {
+        return new Transformer<URL, URI>() {
+            public URL transform(URI original) {
+                try {
+                    return original.toURL();
+                } catch (MalformedURLException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        };
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/CachingClassLoader.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/CachingClassLoader.java
new file mode 100644
index 0000000..7bad876
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/CachingClassLoader.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import com.google.common.collect.MapMaker;
+
+import java.util.concurrent.ConcurrentMap;
+
+public class CachingClassLoader extends ClassLoader implements ClassLoaderHierarchy {
+    private static final Object MISSING_CLASS = new Object();
+    private final ConcurrentMap<String, Object> loadedClasses = new MapMaker().weakValues().makeMap();
+
+    public CachingClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        Object cachedValue = loadedClasses.get(name);
+        if (cachedValue instanceof Class) {
+            return (Class<?>) cachedValue;
+        } else if (cachedValue == MISSING_CLASS) {
+            throw new ClassNotFoundException(name);
+        }
+        Class<?> result;
+        try {
+            result = super.loadClass(name, resolve);
+        } catch (ClassNotFoundException e) {
+            loadedClasses.putIfAbsent(name, MISSING_CLASS);
+            throw e;
+        }
+        loadedClasses.putIfAbsent(name, result);
+        return result;
+    }
+
+    public void visit(ClassLoaderVisitor visitor) {
+        visitor.visitSpec(new Spec());
+        visitor.visitParent(getParent());
+    }
+
+    public static class Spec extends ClassLoaderSpec {
+        @Override
+        public boolean equals(Object obj) {
+            return obj != null && obj.getClass().equals(Spec.class);
+        }
+
+        @Override
+        public int hashCode() {
+            return getClass().getName().hashCode();
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderFactory.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderFactory.java
new file mode 100644
index 0000000..90d37a8
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.classloader;
+
+import org.gradle.internal.classpath.ClassPath;
+
+import java.net.URI;
+import java.util.List;
+
+public interface ClassLoaderFactory {
+    /**
+     * Creates a ClassLoader implementation which has only the classes from the specified URIs and the Java API visible.
+     */
+    ClassLoader createIsolatedClassLoader(ClassPath classPath);
+
+    /**
+     * Creates a ClassLoader implementation which has only the classes from the specified URIs and the Java API visible.
+     */
+    ClassLoader createIsolatedClassLoader(Iterable<URI> uris);
+
+    /**
+     * Creates a ClassLoader implementation which has, by default, only the classes from the Java API visible, but which can allow access
+     * to selected classes from the given parent ClassLoader.
+     *
+     * @param parent the parent ClassLoader
+     * @return The ClassLoader
+     */
+    FilteringClassLoader createFilteringClassLoader(ClassLoader parent);
+
+    /**
+     * Creates a ClassLoader from its spec.
+     */
+    ClassLoader createClassLoader(ClassLoaderSpec spec, List<? extends ClassLoader> parents);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderHierarchy.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderHierarchy.java
new file mode 100644
index 0000000..241c0ac
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderHierarchy.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+public interface ClassLoaderHierarchy {
+    void visit(ClassLoaderVisitor visitor);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderSpec.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderSpec.java
new file mode 100644
index 0000000..8b96204
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderSpec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import java.io.Serializable;
+
+/**
+ * An immutable description of a ClassLoader hierarchy that can be used to recreate the hierarchy in a different process.
+ *
+ * Subclasses should implement equals() and hashCode(), so that the spec can be used as a hashmap key.
+ */
+public abstract class ClassLoaderSpec implements Serializable {
+    public static final ClassLoaderSpec SYSTEM_CLASS_LOADER = new SystemClassLoaderSpec();
+
+    private static class SystemClassLoaderSpec extends ClassLoaderSpec {
+        @Override
+        public boolean equals(Object obj) {
+            return obj != null && obj.getClass().equals(getClass());
+        }
+
+        @Override
+        public String toString() {
+            return "system";
+        }
+
+        @Override
+        public int hashCode() {
+            return 121;
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderVisitor.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderVisitor.java
new file mode 100644
index 0000000..92cc137
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderVisitor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class ClassLoaderVisitor {
+    private final ClassLoader stopAt = ClassLoader.getSystemClassLoader() == null ? null : ClassLoader.getSystemClassLoader().getParent();
+
+    public void visit(ClassLoader classLoader) {
+        if (classLoader == stopAt) {
+            visitSpec(ClassLoaderSpec.SYSTEM_CLASS_LOADER);
+            return;
+        }
+
+        if (classLoader instanceof ClassLoaderHierarchy) {
+            ((ClassLoaderHierarchy) classLoader).visit(this);
+        } else {
+            if (classLoader instanceof URLClassLoader) {
+                visitClassPath(((URLClassLoader) classLoader).getURLs());
+            }
+            if (classLoader.getParent() != null) {
+                visitParent(classLoader.getParent());
+            }
+        }
+    }
+
+    public void visitSpec(ClassLoaderSpec spec) {
+    }
+
+    public void visitClassPath(URL[] classPath) {
+    }
+
+    public void visitParent(ClassLoader classLoader) {
+        visit(classLoader);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClasspathUtil.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClasspathUtil.java
new file mode 100644
index 0000000..eaa5af1
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClasspathUtil.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.classloader;
+
+import org.gradle.api.GradleException;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.reflect.JavaMethod;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+public class ClasspathUtil {
+    public static void addUrl(URLClassLoader classLoader, Iterable<URL> classpathElements) {
+        try {
+            Set<URI> original = new HashSet<URI>();
+            for (URL url : classLoader.getURLs()) {
+                original.add(url.toURI());
+            }
+            JavaMethod<URLClassLoader, Object> method = JavaReflectionUtil.method(URLClassLoader.class, Object.class, "addURL", URL.class);
+            for (URL classpathElement : classpathElements) {
+                if (original.add(classpathElement.toURI())) {
+                    method.invoke(classLoader, classpathElement);
+                }
+            }
+        } catch (Throwable t) {
+            throw new RuntimeException(String.format("Could not add URLs %s to class path for ClassLoader %s", classpathElements, classLoader), t);
+        }
+    }
+
+    public static List<URL> getClasspath(ClassLoader classLoader) {
+        final List<URL> implementationClassPath = new ArrayList<URL>();
+        new ClassLoaderVisitor() {
+            @Override
+            public void visitClassPath(URL[] classPath) {
+                implementationClassPath.addAll(Arrays.asList(classPath));
+            }
+        }.visit(classLoader);
+        return implementationClassPath;
+    }
+
+    public static File getClasspathForClass(Class<?> targetClass) {
+        URI location;
+        try {
+            location = targetClass.getProtectionDomain().getCodeSource().getLocation().toURI();
+        } catch (URISyntaxException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        if (!location.getScheme().equals("file")) {
+            throw new GradleException(String.format("Cannot determine classpath for %s from codebase '%s'.", targetClass.getName(), location));
+        }
+        return new File(location.getPath());
+    }
+
+    public static File getClasspathForResource(ClassLoader classLoader, String name) {
+        if (classLoader == null) {
+            return getClasspathForResource(ClassLoader.getSystemResource(name), name);
+        } else {
+            return getClasspathForResource(classLoader.getResource(name), name);
+        }
+    }
+
+    public static File getClasspathForResource(URL resource, String name) {
+        URI location;
+        try {
+            location = resource.toURI();
+            String path = location.getPath();
+            if (location.getScheme().equals("file")) {
+                assert path.endsWith("/" + name);
+                return new File(path.substring(0, path.length() - (name.length() + 1)));
+            } else if (location.getScheme().equals("jar")) {
+                String schemeSpecificPart = location.getRawSchemeSpecificPart();
+                int pos = schemeSpecificPart.indexOf("!");
+                if (pos > 0) {
+                    assert schemeSpecificPart.substring(pos + 1).equals("/" + name);
+                    URI jarFile = new URI(schemeSpecificPart.substring(0, pos));
+                    if (jarFile.getScheme().equals("file")) {
+                        return new File(jarFile.getPath());
+                    }
+                }
+            }
+        } catch (URISyntaxException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        throw new GradleException(String.format("Cannot determine classpath for resource '%s' from location '%s'.", name, location));
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/DefaultClassLoaderFactory.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/DefaultClassLoaderFactory.java
new file mode 100644
index 0000000..4e99d57
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/DefaultClassLoaderFactory.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.classloader;
+
+import org.gradle.internal.Transformers;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.service.ServiceLocator;
+import org.gradle.util.CollectionUtils;
+
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.List;
+
+public class DefaultClassLoaderFactory implements ClassLoaderFactory {
+    public ClassLoader createIsolatedClassLoader(Iterable<URI> uris) {
+        return doCreateIsolatedClassLoader(CollectionUtils.collect(uris, Transformers.toURL()));
+    }
+
+    public ClassLoader createIsolatedClassLoader(ClassPath classPath) {
+        return doCreateIsolatedClassLoader(classPath.getAsURLs());
+    }
+
+    private ClassLoader doCreateIsolatedClassLoader(Collection<URL> classpath) {
+        // This piece of ugliness copies the JAXP (ie XML API) provider, if any, from the system ClassLoader. Here's why:
+        //
+        // 1. When looking for a provider, JAXP looks for a service resource in the context ClassLoader, which is our isolated ClassLoader. If our classpath above does not contain a
+        //    provider, this returns null. If it does contain a provider, JAXP extracts the classname from the service resource.
+        // 2. If not found, JAXP looks for a service resource in the system ClassLoader. This happens to include all the application classes specified on the classpath. If the application
+        //    classpath does not contain a provider, this returns null. If it does contain a provider, JAXP extracts the implementation classname from the service resource.
+        // 3. If not found, JAXP uses a default classname
+        // 4. JAXP attempts to load the provider using the context ClassLoader. which is our isolated ClassLoader. This is fine if the classname came from step 1 or 3. It blows up if the
+        //    classname came from step 2.
+        //
+        // So, as a workaround, locate and include the JAXP provider jar in the classpath for our isolated ClassLoader.
+        //
+        // Note that in practise, this is only triggered when running in our tests
+
+        if (needJaxpImpl()) {
+            try {
+                classpath.add(ClasspathUtil.getClasspathForResource(ClassLoader.getSystemClassLoader(), "META-INF/services/javax.xml.parsers.SAXParserFactory").toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        return new URLClassLoader(classpath.toArray(new URL[classpath.size()]), ClassLoader.getSystemClassLoader().getParent());
+    }
+
+    public FilteringClassLoader createFilteringClassLoader(ClassLoader parent) {
+        // See the comment for {@link #createIsolatedClassLoader} above
+        FilteringClassLoader classLoader = new FilteringClassLoader(parent);
+        if (needJaxpImpl()) {
+            ServiceLocator locator = new ServiceLocator(ClassLoader.getSystemClassLoader());
+            makeServiceVisible(locator, classLoader, SAXParserFactory.class);
+            makeServiceVisible(locator, classLoader, DocumentBuilderFactory.class);
+            makeServiceVisible(locator, classLoader, DatatypeFactory.class);
+        }
+        return classLoader;
+    }
+
+    public ClassLoader createClassLoader(ClassLoaderSpec spec, List<? extends ClassLoader> parents) {
+        if (spec instanceof MultiParentClassLoader.Spec) {
+            return new MultiParentClassLoader(parents);
+        }
+        if (parents.size() != 1) {
+            throw new IllegalArgumentException("Expected a single parent.");
+        }
+        ClassLoader parent = parents.get(0);
+        if (spec instanceof MutableURLClassLoader.Spec) {
+            MutableURLClassLoader.Spec clSpec = (MutableURLClassLoader.Spec) spec;
+            return new MutableURLClassLoader(parent, clSpec);
+        }
+        if (spec instanceof CachingClassLoader.Spec) {
+            return new CachingClassLoader(parent);
+        }
+        if (spec instanceof FilteringClassLoader.Spec) {
+            FilteringClassLoader.Spec clSpec = (FilteringClassLoader.Spec) spec;
+            return new FilteringClassLoader(parent, clSpec);
+        }
+        throw new IllegalArgumentException(String.format("Don't know how to create a ClassLoader from spec %s", spec));
+    }
+
+    private void makeServiceVisible(ServiceLocator locator, FilteringClassLoader classLoader, Class<?> serviceType) {
+        classLoader.allowClass(locator.getFactory(serviceType).getImplementationClass());
+        classLoader.allowResource("META-INF/services/" + serviceType.getName());
+    }
+
+    private boolean needJaxpImpl() {
+        return ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory") != null;
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/FilteringClassLoader.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/FilteringClassLoader.java
new file mode 100644
index 0000000..95a1573
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/FilteringClassLoader.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import org.gradle.internal.reflect.JavaMethod;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * A ClassLoader which hides all non-system classes, packages and resources. Allows certain non-system packages and classes to be declared as visible. By default, only the Java system classes,
+ * packages and resources are visible.
+ */
+public class FilteringClassLoader extends ClassLoader implements ClassLoaderHierarchy {
+    private static final ClassLoader EXT_CLASS_LOADER;
+    private static final Set<String> SYSTEM_PACKAGES = new HashSet<String>();
+    private final Set<String> packageNames = new HashSet<String>();
+    private final Set<String> packagePrefixes = new HashSet<String>();
+    private final Set<String> resourcePrefixes = new HashSet<String>();
+    private final Set<String> resourceNames = new HashSet<String>();
+    private final Set<String> classNames = new HashSet<String>();
+    private final Set<String> disallowedClassNames = new HashSet<String>();
+
+    static {
+        EXT_CLASS_LOADER = ClassLoader.getSystemClassLoader().getParent();
+        JavaMethod<ClassLoader, Package[]> method = JavaReflectionUtil.method(ClassLoader.class, Package[].class, "getPackages");
+        Package[] systemPackages = method.invoke(EXT_CLASS_LOADER);
+        for (Package p : systemPackages) {
+            SYSTEM_PACKAGES.add(p.getName());
+        }
+    }
+
+    public FilteringClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    public FilteringClassLoader(ClassLoader parent, Spec spec) {
+        super(parent);
+        packageNames.addAll(spec.packageNames);
+        packagePrefixes.addAll(spec.packagePrefixes);
+        resourceNames.addAll(spec.resourceNames);
+        resourcePrefixes.addAll(spec.resourcePrefixes);
+        classNames.addAll(spec.classNames);
+        disallowedClassNames.addAll(spec.classNames);
+    }
+
+    public void visit(ClassLoaderVisitor visitor) {
+        visitor.visitSpec(new Spec(classNames, packageNames, packagePrefixes, resourcePrefixes, resourceNames, disallowedClassNames));
+        visitor.visitParent(getParent());
+    }
+
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        try {
+            return EXT_CLASS_LOADER.loadClass(name);
+        } catch (ClassNotFoundException ignore) {
+            // ignore
+        }
+
+        if (!classAllowed(name)) {
+            throw new ClassNotFoundException(String.format("%s not found.", name));
+        }
+
+        Class<?> cl = super.loadClass(name, false);
+        if (resolve) {
+            resolveClass(cl);
+        }
+
+        return cl;
+    }
+
+    @Override
+    protected Package getPackage(String name) {
+        Package p = super.getPackage(name);
+        if (p == null || !allowed(p)) {
+            return null;
+        }
+        return p;
+    }
+
+    @Override
+    protected Package[] getPackages() {
+        List<Package> packages = new ArrayList<Package>();
+        for (Package p : super.getPackages()) {
+            if (allowed(p)) {
+                packages.add(p);
+            }
+        }
+        return packages.toArray(new Package[packages.size()]);
+    }
+
+    @Override
+    public URL getResource(String name) {
+        if (allowed(name)) {
+            return super.getResource(name);
+        }
+        return EXT_CLASS_LOADER.getResource(name);
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        if (allowed(name)) {
+            return super.getResources(name);
+        }
+        return EXT_CLASS_LOADER.getResources(name);
+    }
+
+    private boolean allowed(String resourceName) {
+        if (resourceNames.contains(resourceName)) {
+            return true;
+        }
+        for (String resourcePrefix : resourcePrefixes) {
+            if (resourceName.startsWith(resourcePrefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean allowed(Package pkg) {
+        if (SYSTEM_PACKAGES.contains(pkg.getName())) {
+            return true;
+        }
+        if (packageNames.contains(pkg.getName())) {
+            return true;
+        }
+        for (String packagePrefix : packagePrefixes) {
+            if (pkg.getName().startsWith(packagePrefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean classAllowed(String className) {
+        if (disallowedClassNames.contains(className)) {
+            return false;
+        }
+        if (classNames.contains(className)) {
+            return true;
+        }
+        for (String packagePrefix : packagePrefixes) {
+            if (className.startsWith(packagePrefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Marks a package and all its sub-packages as visible. Also makes resources in those packages visible.
+     *
+     * @param packageName the package name
+     */
+    public void allowPackage(String packageName) {
+        packageNames.add(packageName);
+        packagePrefixes.add(packageName + ".");
+        resourcePrefixes.add(packageName.replace('.', '/') + '/');
+    }
+
+    /**
+     * Marks a single class as visible.
+     *
+     * @param clazz the class
+     */
+    public void allowClass(Class<?> clazz) {
+        classNames.add(clazz.getName());
+    }
+
+    /**
+     * Marks a single class as not visible.
+     *
+     * @param className the class name
+     */
+    public void disallowClass(String className) {
+        disallowedClassNames.add(className);
+    }
+
+    /**
+     * Marks all resources with the given prefix as visible.
+     *
+     * @param resourcePrefix the resource prefix
+     */
+    public void allowResources(String resourcePrefix) {
+        resourcePrefixes.add(resourcePrefix + "/");
+    }
+
+    /**
+     * Marks a single resource as visible.
+     *
+     * @param resourceName the resource name
+     */
+    public void allowResource(String resourceName) {
+        resourceNames.add(resourceName);
+    }
+
+    public static class Spec extends ClassLoaderSpec {
+
+        final Set<String> packageNames;
+        final Set<String> packagePrefixes;
+        final Set<String> resourcePrefixes;
+        final Set<String> resourceNames;
+        final Set<String> classNames;
+        final Set<String> disallowedClassNames;
+
+        public Spec(Collection<String> classNames, Collection<String> packageNames, Collection<String> packagePrefixes, Collection<String> resourcePrefixes, Collection<String> resourceNames, Collection<String> disallowedClassNames) {
+            this.classNames = new HashSet<String>(classNames);
+            this.packageNames = new HashSet<String>(packageNames);
+            this.packagePrefixes = new HashSet<String>(packagePrefixes);
+            this.resourcePrefixes = new HashSet<String>(resourcePrefixes);
+            this.resourceNames = new HashSet<String>(resourceNames);
+            this.disallowedClassNames = new HashSet<String>(disallowedClassNames);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj == null || obj.getClass() != getClass()) {
+                return false;
+            }
+            Spec other = (Spec) obj;
+            return other.packageNames.equals(packageNames)
+                    && other.packagePrefixes.equals(packagePrefixes)
+                    && other.resourceNames.equals(resourceNames)
+                    && other.resourcePrefixes.equals(resourcePrefixes)
+                    && other.classNames.equals(classNames)
+                    && other.disallowedClassNames.equals(disallowedClassNames);
+        }
+
+        @Override
+        public int hashCode() {
+            return packageNames.hashCode()
+                    ^ packagePrefixes.hashCode()
+                    ^ resourceNames.hashCode()
+                    ^ resourcePrefixes.hashCode()
+                    ^ classNames.hashCode()
+                    ^ disallowedClassNames.hashCode();
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/MultiParentClassLoader.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/MultiParentClassLoader.java
new file mode 100644
index 0000000..9031999
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/MultiParentClassLoader.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.reflect.JavaMethod;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A {@code ClassLoader} which delegates to multiple parent ClassLoaders.
+ *
+ * Note: It's usually a good idea to add a {@link CachingClassLoader} between this ClassLoader and any
+ * ClassLoaders that use it as a parent, to prevent every path in the ClassLoader graph being searched.
+ */
+public class MultiParentClassLoader extends ClassLoader implements ClassLoaderHierarchy {
+    private final List<ClassLoader> parents;
+    private final JavaMethod<ClassLoader, Package[]> getPackagesMethod;
+    private final JavaMethod<ClassLoader, Package> getPackageMethod;
+
+    public MultiParentClassLoader(ClassLoader... parents) {
+        this(Arrays.asList(parents));
+    }
+
+    public MultiParentClassLoader(Collection<? extends ClassLoader> parents) {
+        super(null);
+        this.parents = new CopyOnWriteArrayList<ClassLoader>(parents);
+        getPackagesMethod = JavaReflectionUtil.method(ClassLoader.class, Package[].class, "getPackages");
+        getPackageMethod = JavaReflectionUtil.method(ClassLoader.class, Package.class, "getPackage", String.class);
+    }
+
+    public void addParent(ClassLoader parent) {
+        parents.add(parent);
+    }
+
+    public void visit(ClassLoaderVisitor visitor) {
+        visitor.visitSpec(new Spec());
+        for (ClassLoader parent : parents) {
+            visitor.visitParent(parent);
+        }
+    }
+
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        for (ClassLoader parent : parents) {
+            try {
+                return parent.loadClass(name);
+            } catch (ClassNotFoundException e) {
+                // Expected
+            }
+        }
+        throw new ClassNotFoundException(String.format("%s not found.", name));
+    }
+
+    @Override
+    protected Package getPackage(String name) {
+        for (ClassLoader parent : parents) {
+            Package p = getPackageMethod.invoke(parent, name);
+            if (p != null) {
+                return p;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected Package[] getPackages() {
+        Set<Package> packages = new LinkedHashSet<Package>();
+        for (ClassLoader parent : parents) {
+            Package[] parentPackages = getPackagesMethod.invoke(parent);
+            packages.addAll(Arrays.asList(parentPackages));
+        }
+        return packages.toArray(new Package[packages.size()]);
+    }
+
+    @Override
+    public URL getResource(String name) {
+        for (ClassLoader parent : parents) {
+            URL resource = parent.getResource(name);
+            if (resource != null) {
+                return resource;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        Set<URL> resources = new LinkedHashSet<URL>();
+        for (ClassLoader parent : parents) {
+            Enumeration<URL> parentResources = parent.getResources(name);
+            while (parentResources.hasMoreElements()) {
+                resources.add(parentResources.nextElement());
+            }
+        }
+        return Collections.enumeration(resources);
+    }
+
+    public static class Spec extends ClassLoaderSpec {
+        @Override
+        public boolean equals(Object obj) {
+            return obj != null && obj.getClass().equals(Spec.class);
+        }
+
+        @Override
+        public int hashCode() {
+            return getClass().getName().hashCode();
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/MutableURLClassLoader.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/MutableURLClassLoader.java
new file mode 100755
index 0000000..4f5f6ea
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/MutableURLClassLoader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.util.CollectionUtils;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.List;
+
+public class MutableURLClassLoader extends URLClassLoader implements ClassLoaderHierarchy {
+    public MutableURLClassLoader(ClassLoader parent, URL... urls) {
+        super(urls, parent);
+    }
+
+    public MutableURLClassLoader(ClassLoader parent, Collection<URL> urls) {
+        super(urls.toArray(new URL[urls.size()]), parent);
+    }
+
+    public MutableURLClassLoader(ClassLoader parent, ClassPath classPath) {
+        super(classPath.getAsURLArray(), parent);
+    }
+
+    public MutableURLClassLoader(ClassLoader parent, Spec spec) {
+        this(parent, spec.classpath);
+    }
+
+    public void visit(ClassLoaderVisitor visitor) {
+        visitor.visitSpec(new Spec(CollectionUtils.toList(getURLs())));
+        visitor.visitClassPath(getURLs());
+        visitor.visitParent(getParent());
+    }
+
+    @Override
+    public void addURL(URL url) {
+        super.addURL(url);
+    }
+
+    public void addURLs(Iterable<URL> urls) {
+        for (URL url : urls) {
+            addURL(url);
+        }
+    }
+
+    public static class Spec extends ClassLoaderSpec {
+        final List<URL> classpath;
+
+        public Spec(List<URL> classpath) {
+            this.classpath = classpath;
+        }
+
+        public List<URL> getClasspath() {
+            return classpath;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj == null || obj.getClass() != getClass()) {
+                return false;
+            }
+            Spec other = (Spec) obj;
+            return classpath.equals(other.classpath);
+        }
+
+        @Override
+        public int hashCode() {
+            return classpath.hashCode();
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/TransformingClassLoader.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/TransformingClassLoader.java
new file mode 100644
index 0000000..41c4a8d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/TransformingClassLoader.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader;
+
+import com.google.common.io.ByteStreams;
+import org.gradle.api.GradleException;
+import org.gradle.internal.classpath.ClassPath;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+
+public abstract class TransformingClassLoader extends MutableURLClassLoader {
+    public TransformingClassLoader(ClassLoader parent, ClassPath classPath) {
+        super(parent, classPath);
+    }
+
+    public TransformingClassLoader(ClassLoader parent, Collection<URL> urls) {
+        super(parent, urls);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        URL resource = findResource(name.replace(".", "/") + ".class");
+        if (resource == null) {
+            throw new ClassNotFoundException(name);
+        }
+        byte[] bytes;
+        try {
+            bytes = loadBytecode(resource);
+            bytes = transform(bytes);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not load class '%s' from %s.", name, resource), e);
+        }
+        return defineClass(name, bytes, 0, bytes.length);
+    }
+
+    private byte[] loadBytecode(URL resource) throws IOException {
+        InputStream inputStream = resource.openStream();
+        try {
+            return ByteStreams.toByteArray(inputStream);
+        } finally {
+            inputStream.close();
+        }
+    }
+
+    protected abstract byte[] transform(byte[] bytes);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java
index 7eb8cae..ec141e4 100755
--- a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java
@@ -15,15 +15,13 @@
  */
 package org.gradle.internal.concurrent;
 
-import org.gradle.internal.Stoppable;
-
 /**
  * A {@link Stoppable} object whose stop process can be performed asynchronously.
  */
 public interface AsyncStoppable extends Stoppable {
     /**
      * <p>Requests that this stoppable commence a graceful stop. Does not block. You should call {@link
-     * org.gradle.internal.Stoppable#stop} to wait for the stop process to complete.</p>
+     * Stoppable#stop} to wait for the stop process to complete.</p>
      *
      * <p>Generally, an {@code AsyncStoppable} should continue to complete existing work after this method has returned.
      * It should, however, stop accepting new work.</p>
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/CompositeStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/CompositeStoppable.java
new file mode 100644
index 0000000..37e05f5
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/CompositeStoppable.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.concurrent;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CompositeStoppable implements Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeStoppable.class);
+    private final List<Stoppable> elements = new CopyOnWriteArrayList<Stoppable>();
+
+    public CompositeStoppable() {
+    }
+
+    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) {
+        for (Object element : elements) {
+            this.elements.add(toStoppable(element));
+        }
+        return this;
+    }
+
+    public CompositeStoppable add(Object... elements) {
+        for (Object closeable : elements) {
+            this.elements.add(toStoppable(closeable));
+        }
+        return this;
+    }
+
+    private static void invoke(Method method, Object target, Object... args) {
+        JavaReflectionUtil.method(target, Object.class, method).invoke(target, args);
+    }
+
+    private static Stoppable toStoppable(final Object object) {
+        if (object == null) {
+            return new Stoppable() {
+                public void stop() {
+                }
+            };
+        }
+        if (object instanceof Stoppable) {
+            return (Stoppable) object;
+        }
+        if (object instanceof Closeable) {
+            final Closeable closeable = (Closeable) object;
+            return new Stoppable() {
+                @Override
+                public String toString() {
+                    return closeable.toString();
+                }
+
+                public void stop() {
+                    try {
+                        closeable.close();
+                    } catch (IOException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+            };
+        }
+        return new Stoppable() {
+            @Override
+            public String toString() {
+                return object.toString();
+            }
+
+            public void stop() {
+                try {
+                    invoke(object.getClass().getMethod("stop"), object);
+                } catch (NoSuchMethodException e) {
+                    // ignore
+                }
+                try {
+                    invoke(object.getClass().getMethod("close"), object);
+                } catch (NoSuchMethodException e) {
+                    // ignore
+                }
+            }
+        };
+    }
+
+    public void stop() {
+        Throwable failure = null;
+        try {
+            for (Stoppable element : elements) {
+                try {
+                    element.stop();
+                } catch (Throwable throwable) {
+                    if (failure == null) {
+                        failure = throwable;
+                    } else {
+                        LOGGER.error(String.format("Could not stop %s.", element), throwable);
+                    }
+                }
+            }
+        } finally {
+            elements.clear();
+        }
+
+        if (failure != null) {
+            throw UncheckedException.throwAsUncheckedException(failure);
+        }
+    }
+}
\ No newline at end of file
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 95377e4..a9069c6 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
@@ -16,8 +16,6 @@
 
 package org.gradle.internal.concurrent;
 
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
 import org.gradle.internal.UncheckedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ServiceLifecycle.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ServiceLifecycle.java
new file mode 100644
index 0000000..44a7e3d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ServiceLifecycle.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.concurrent;
+
+import org.gradle.internal.Factories;
+import org.gradle.internal.Factory;
+import org.gradle.internal.UncheckedException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Manages the lifecycle of some thread-safe service or resource.
+ */
+public class ServiceLifecycle implements AsyncStoppable {
+    private enum State {RUNNING, STOPPING, STOPPED}
+
+    private final String displayName;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private State state = State.RUNNING;
+    private Map<Thread, Integer> usages = new HashMap<Thread, Integer>();
+
+    public ServiceLifecycle(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public void use(Runnable runnable) {
+        use(Factories.toFactory(runnable));
+    }
+
+    public <T> T use(Factory<T> factory) {
+        lock.lock();
+        try {
+            switch (state) {
+                case STOPPING:
+                    throw new IllegalStateException(String.format("Cannot use %s as it is currently stopping.", displayName));
+                case STOPPED:
+                    throw new IllegalStateException(String.format("Cannot use %s as it has been stopped.", displayName));
+            }
+            Integer depth = usages.get(Thread.currentThread());
+            if (depth == null) {
+                usages.put(Thread.currentThread(), 1);
+            } else {
+                usages.put(Thread.currentThread(), depth + 1);
+            }
+        } finally {
+            lock.unlock();
+        }
+
+        try {
+            return factory.create();
+        } finally {
+            lock.lock();
+            try {
+                Integer depth = usages.remove(Thread.currentThread());
+                if (depth > 1) {
+                    usages.put(Thread.currentThread(), depth - 1);
+                }
+                if (usages.isEmpty()) {
+                    condition.signalAll();
+                    if (state == State.STOPPING) {
+                        state = State.STOPPED;
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    public void requestStop() {
+        lock.lock();
+        try {
+            if (state == State.RUNNING) {
+                if (usages.isEmpty()) {
+                    state = State.STOPPED;
+                } else {
+                    state = State.STOPPING;
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void stop() {
+        lock.lock();
+        try {
+            if (usages.containsKey(Thread.currentThread())) {
+                throw new IllegalStateException(String.format("Cannot stop %s from a thread that is using it.", displayName));
+            }
+            if (state == State.RUNNING) {
+                state = State.STOPPING;
+            }
+            while (!usages.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+            if (state != State.STOPPED) {
+                state = State.STOPPED;
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/Stoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/Stoppable.java
new file mode 100755
index 0000000..2e7cf09
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/Stoppable.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.concurrent;
+
+/**
+ * Represents an object which performs concurrent activity.
+ */
+public interface Stoppable {
+    /**
+     * <p>Requests a graceful stop of this object. Blocks until all concurrent activity has been completed.</p>
+     *
+     * <p>If this object has already been stopped, this method does nothing.</p>
+     */
+    void stop();
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/hash/HashUtil.java b/subprojects/base-services/src/main/java/org/gradle/internal/hash/HashUtil.java
new file mode 100644
index 0000000..a13a6cc
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/hash/HashUtil.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.hash;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.UncheckedException;
+
+import java.io.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HashUtil {
+    public static HashValue createHash(String scriptText, String algorithm) {
+        MessageDigest messageDigest = createMessageDigest(algorithm);
+        messageDigest.update(scriptText.getBytes());
+        return new HashValue(messageDigest.digest());
+    }
+
+    public static HashValue createHash(File file, String algorithm) {
+        try {
+            return createHash(new FileInputStream(file), algorithm);
+        } catch (FileNotFoundException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static HashValue createHash(InputStream instr, String algorithm) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = createMessageDigest(algorithm);
+            byte[] buffer = new byte[4096];
+            try {
+                while (true) {
+                    int nread = instr.read(buffer);
+                    if (nread < 0) {
+                        break;
+                    }
+                    messageDigest.update(buffer, 0, nread);
+                }
+            } finally {
+                instr.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return new HashValue(messageDigest.digest());
+    }
+
+    private static MessageDigest createMessageDigest(String algorithm) {
+        try {
+            return MessageDigest.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public static String createCompactMD5(String scriptText) {
+        return createHash(scriptText, "MD5").asCompactString();
+    }
+
+    public static HashValue sha1(byte[] bytes) {
+        return createHash(new ByteArrayInputStream(bytes), "SHA1");
+    }
+
+    public static HashValue sha1(InputStream inputStream) {
+        return createHash(inputStream, "SHA1");
+    }
+
+    public static HashValue sha1(File file) {
+        return createHash(file, "SHA1");
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/hash/HashValue.java b/subprojects/base-services/src/main/java/org/gradle/internal/hash/HashValue.java
new file mode 100644
index 0000000..35cbf3c
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/hash/HashValue.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.hash;
+
+import java.math.BigInteger;
+
+public class HashValue {
+    private final BigInteger digest;
+
+    public HashValue(byte[] digest) {
+        this.digest = new BigInteger(1, digest);
+    }
+
+    public HashValue(String hexString) {
+        this.digest = new BigInteger(hexString, 16);
+    }
+
+    public static HashValue parse(String inputString) {
+        if (inputString == null || inputString.length() == 0) {
+            return null;
+        }
+        return new HashValue(parseInput(inputString));
+    }
+
+    private static String parseInput(String inputString) {
+        if (inputString == null) {
+            return null;
+        }
+        String cleaned = inputString.trim().toLowerCase();
+        int spaceIndex = cleaned.indexOf(' ');
+        if (spaceIndex != -1) {
+            String firstPart = cleaned.substring(0, spaceIndex);
+            if (firstPart.startsWith("md") || firstPart.startsWith("sha")) {
+                cleaned = cleaned.substring(cleaned.lastIndexOf(' ') + 1);
+            } else if (firstPart.endsWith(":")) {
+                cleaned = cleaned.substring(spaceIndex + 1).replace(" ", "");
+            } else {
+                cleaned = cleaned.substring(0, spaceIndex);
+            }
+        }
+        return cleaned;
+    }
+
+    public String asCompactString() {
+        return digest.toString(32);
+    }
+
+    public String asHexString() {
+        return digest.toString(16);
+    }
+
+    public byte[] asByteArray() {
+        return digest.toByteArray();
+    }
+
+    public BigInteger asBigInteger() {
+        return digest;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof HashValue)) {
+            return false;
+        }
+
+        HashValue otherHashValue = (HashValue) other;
+        return digest.equals(otherHashValue.digest);
+    }
+
+    @Override
+    public int hashCode() {
+        return digest.hashCode();
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java b/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java
index becd95a..e1af0a4 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java
@@ -31,11 +31,11 @@ public class CompositeIdGenerator implements IdGenerator<Object> {
         return new CompositeId(scope, generator.generateId());
     }
     
-    private static class CompositeId implements Serializable {
+    public static class CompositeId implements Serializable {
         private final Object scope;
         private final Object id;
 
-        private CompositeId(Object scope, Object id) {
+        public CompositeId(Object scope, Object id) {
             this.id = id;
             this.scope = scope;
         }
@@ -58,6 +58,14 @@ public class CompositeIdGenerator implements IdGenerator<Object> {
             return scope.hashCode() ^ id.hashCode();
         }
 
+        public Object getScope() {
+            return scope;
+        }
+
+        public Object getId() {
+            return id;
+        }
+
         @Override
         public String toString() {
             return String.format("%s.%s", scope, id);
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/io/RandomAccessFileInputStream.java b/subprojects/base-services/src/main/java/org/gradle/internal/io/RandomAccessFileInputStream.java
new file mode 100644
index 0000000..3191bb1
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/io/RandomAccessFileInputStream.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+public class RandomAccessFileInputStream extends InputStream {
+    private final RandomAccessFile file;
+
+    public RandomAccessFileInputStream(RandomAccessFile file) {
+        this.file = file;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        file.seek(file.getFilePointer() + n);
+        return n;
+    }
+
+    @Override
+    public int read(byte[] bytes) throws IOException {
+        return file.read(bytes);
+    }
+
+    @Override
+    public int read() throws IOException {
+        return file.read();
+    }
+
+    @Override
+    public int read(byte[] bytes, int offset, int length) throws IOException {
+        return file.read(bytes, offset, length);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/io/RandomAccessFileOutputStream.java b/subprojects/base-services/src/main/java/org/gradle/internal/io/RandomAccessFileOutputStream.java
new file mode 100644
index 0000000..549e6c0
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/io/RandomAccessFileOutputStream.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+public class RandomAccessFileOutputStream extends OutputStream {
+    private final RandomAccessFile file;
+
+    public RandomAccessFileOutputStream(RandomAccessFile file) {
+        this.file = file;
+    }
+
+    @Override
+    public void write(int i) throws IOException {
+        file.write(i);
+    }
+
+    @Override
+    public void write(byte[] bytes) throws IOException {
+        file.write(bytes);
+    }
+
+    @Override
+    public void write(byte[] bytes, int offset, int length) throws IOException {
+        file.write(bytes, offset, length);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/io/TextStream.java b/subprojects/base-services/src/main/java/org/gradle/internal/io/TextStream.java
new file mode 100644
index 0000000..776a98a
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/io/TextStream.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import org.gradle.api.Nullable;
+
+public interface TextStream {
+    /**
+     * Called when some chunk of text is available.
+     */
+    void text(String text);
+
+    /**
+     * Called when the end of the stream is reached for some reason.
+     *
+     * @param failure The failure, if any, which caused the end of the stream.
+     */
+    void endOfStream(@Nullable Throwable failure);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java
index 92335e4..57e9dda 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java
@@ -16,9 +16,6 @@
 
 package org.gradle.internal.jvm;
 
-/**
- * by Szczepan Faber, created at: 1/23/12
- */
 public class JavaHomeException extends RuntimeException {
     public JavaHomeException(String message) {
         super(message);
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
index cb4e842..0967b36 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
@@ -19,9 +19,6 @@ package org.gradle.internal.jvm;
 import java.io.File;
 import java.util.Map;
 
-/**
- * by Szczepan Faber, created at: 2/6/12
- */
 public interface JavaInfo {
     /**
      * @return the executable
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jre.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jre.java
new file mode 100644
index 0000000..af50c3c
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jre.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.jvm;
+
+import java.io.File;
+
+/**
+ * Represents a JRE installation.
+ */
+public abstract class Jre {
+    public abstract File getHomeDir();
+}
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 db4b1f9..bbca272 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
@@ -17,6 +17,7 @@
 package org.gradle.internal.jvm;
 
 import org.gradle.api.JavaVersion;
+import org.gradle.api.Nullable;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.os.OperatingSystem;
@@ -27,6 +28,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 public class Jvm implements JavaInfo {
     
@@ -39,12 +41,18 @@ public class Jvm implements JavaInfo {
     private final File javaHome;
     private final boolean userSupplied;
     private final JavaVersion javaVersion;
+    private static final AtomicReference<Jvm> CURRENT = new AtomicReference<Jvm>();
 
     public static Jvm current() {
-        return create(null);
+        Jvm jvm = CURRENT.get();
+        if (jvm == null) {
+            CURRENT.compareAndSet(null, create(null));
+            jvm = CURRENT.get();
+        }
+        return jvm;
     }
 
-    private static Jvm create(File javaBase) {
+    static Jvm create(File javaBase) {
         String vendor = System.getProperty("java.vm.vendor");
         if (vendor.toLowerCase().startsWith("apple inc.")) {
             return new AppleJvm(OperatingSystem.current(), javaBase);
@@ -143,6 +151,10 @@ public class Jvm implements JavaInfo {
         return findExecutable("java");
     }
 
+    public File getJavacExecutable() throws JavaHomeException {
+        return findExecutable("javac");
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -187,6 +199,10 @@ public class Jvm implements JavaInfo {
      */
     public File getRuntimeJar() {
         File runtimeJar = new File(javaBase, "lib/rt.jar");
+        if (runtimeJar.exists()) {
+            return runtimeJar;
+        }
+        runtimeJar = new File(javaBase, "jre/lib/rt.jar");
         return runtimeJar.exists() ? runtimeJar : null;
     }
 
@@ -197,6 +213,39 @@ public class Jvm implements JavaInfo {
         return findToolsJar(javaBase);
     }
 
+    /**
+     * Locates a stand-alone JRE installation for this JVM. Returns null if not found.
+     */
+    @Nullable
+    public Jre getStandaloneJre() {
+        if (os.isWindows()) {
+            File jreDir;
+            if (javaVersion.isJava5()) {
+                jreDir = new File(javaHome.getParentFile(), String.format("jre%s", SystemProperties.getJavaVersion()));
+            } else {
+                jreDir = new File(javaHome.getParentFile(), String.format("jre%s", javaVersion.getMajorVersion()));
+            }
+            if (jreDir.isDirectory()) {
+                return new DefaultJre(jreDir);
+            }
+        }
+        if (!new File(javaHome, "jre").isDirectory()) {
+            return new DefaultJre(javaHome);
+        }
+        return null;
+    }
+
+    /**
+     * Locates the JRE installation for this JVM.
+     */
+    public Jre getJre() {
+        File jreDir = new File(javaBase, "jre");
+        if (jreDir.isDirectory()) {
+            return new DefaultJre(jreDir);
+        }
+        return new DefaultJre(javaBase);
+    }
+
     private File findToolsJar(File javaHome) {
         File toolsJar = new File(javaHome, "lib/tools.jar");
         if (toolsJar.exists()) {
@@ -209,11 +258,15 @@ public class Jvm implements JavaInfo {
                 return toolsJar;
             }
         }
-        if (javaHome.getName().matches("jre\\d+") && os.isWindows()) {
-            javaHome = new File(javaHome.getParentFile(), String.format("jdk%s", SystemProperties.getJavaVersion()));
-            toolsJar = new File(javaHome, "lib/tools.jar");
-            if (toolsJar.exists()) {
-                return toolsJar;
+
+        if (os.isWindows()) {
+            String version = SystemProperties.getJavaVersion();
+            if (javaHome.getName().matches("jre\\d+") || javaHome.getName().equals(String.format("jre%s", version))) {
+                javaHome = new File(javaHome.getParentFile(), String.format("jdk%s", version));
+                toolsJar = new File(javaHome, "lib/tools.jar");
+                if (toolsJar.exists()) {
+                    return toolsJar;
+                }
             }
         }
 
@@ -288,4 +341,17 @@ public class Jvm implements JavaInfo {
             return vars;
         }
     }
+
+    private static class DefaultJre extends Jre {
+        private final File jreDir;
+
+        public DefaultJre(File jreDir) {
+            this.jreDir = jreDir;
+        }
+
+        @Override
+        public File getHomeDir() {
+            return jreDir;
+        }
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java b/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
index 08dd03d..7fcafaf 100755
--- a/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.internal.os;
 
+import org.gradle.api.Nullable;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -23,11 +25,12 @@ import java.util.List;
 import java.util.regex.Pattern;
 
 public abstract class OperatingSystem {
-    private static final Windows WINDOWS = new Windows();
-    private static final MacOs MAC_OS = new MacOs();
-    private static final Solaris SOLARIS = new Solaris();
-    private static final Linux LINUX = new Linux();
-    private static final Unix UNIX = new Unix();
+    public static final Windows WINDOWS = new Windows();
+    public static final MacOs MAC_OS = new MacOs();
+    public static final Solaris SOLARIS = new Solaris();
+    public static final Linux LINUX = new Linux();
+    public static final FreeBSD FREE_BSD = new FreeBSD();
+    public static final Unix UNIX = new Unix();
 
     public static OperatingSystem current() {
         String osName = System.getProperty("os.name").toLowerCase();
@@ -39,6 +42,8 @@ public abstract class OperatingSystem {
             return SOLARIS;
         } else if (osName.contains("linux")) {
             return LINUX;
+        } else if (osName.contains("freebsd")) {
+            return FREE_BSD;
         } else {
             // Not strictly true
             return UNIX;
@@ -82,9 +87,14 @@ public abstract class OperatingSystem {
 
     public abstract String getSharedLibraryName(String libraryName);
 
+    public abstract String getStaticLibraryName(String libraryName);
+
+    public abstract String getFamilyName();
+
     /**
      * Locates the given executable in the system path. Returns null if not found.
      */
+    @Nullable
     public File findInPath(String name) {
         String exeName = getExecutableName(name);
         if (exeName.contains(File.separator)) {
@@ -117,8 +127,8 @@ public abstract class OperatingSystem {
         return all;
     }
     
-    List<File> getPath() {                       
-        String path = System.getenv("PATH");
+    List<File> getPath() {
+        String path = System.getenv(getPathVar());
         if (path == null) {
             return Collections.emptyList();
         }
@@ -129,6 +139,10 @@ public abstract class OperatingSystem {
         return entries;
     }
 
+    public String getPathVar() {
+        return "PATH";
+    }
+
     static class Windows extends OperatingSystem {
         @Override
         public boolean isWindows() {
@@ -136,11 +150,13 @@ public abstract class OperatingSystem {
         }
 
         @Override
+        public String getFamilyName() {
+            return "windows";
+        }
+
+        @Override
         public String getScriptName(String scriptPath) {
-            if (scriptPath.toLowerCase().endsWith(".bat")) {
-                return scriptPath;
-            }
-            return scriptPath + ".bat";
+            return withSuffix(scriptPath, ".bat");
         }
 
         @Override
@@ -154,6 +170,11 @@ public abstract class OperatingSystem {
         }
 
         @Override
+        public String getStaticLibraryName(String libraryName) {
+            return withSuffix(libraryName, ".lib");
+        }
+
+        @Override
         public String getNativePrefix() {
             String arch = System.getProperty("os.arch");
             if ("i386".equals(arch)) {
@@ -166,7 +187,23 @@ public abstract class OperatingSystem {
             if (executablePath.toLowerCase().endsWith(extension)) {
                 return executablePath;
             }
-            return executablePath + extension;
+            return removeExtension(executablePath) + extension;
+        }
+
+        private String removeExtension(String executablePath) {
+            int fileNameStart = Math.max(executablePath.lastIndexOf('/'), executablePath.lastIndexOf('\\'));
+            int extensionPos = executablePath.lastIndexOf('.');
+
+            if (extensionPos > fileNameStart) {
+                return executablePath.substring(0, extensionPos);
+            }
+            return executablePath;
+        }
+
+
+        @Override
+        public String getPathVar() {
+            return "Path";
         }
     }
 
@@ -177,13 +214,21 @@ public abstract class OperatingSystem {
         }
 
         @Override
+        public String getFamilyName() {
+            return "unknown";
+        }
+
+        @Override
         public String getExecutableName(String executablePath) {
             return executablePath;
         }
 
         @Override
         public String getSharedLibraryName(String libraryName) {
-            String suffix = getSharedLibSuffix();
+            return getLibraryName(libraryName, getSharedLibSuffix());
+        }
+
+        private String getLibraryName(String libraryName, String suffix) {
             if (libraryName.endsWith(suffix)) {
                 return libraryName;
             }
@@ -200,6 +245,11 @@ public abstract class OperatingSystem {
         }
 
         @Override
+        public String getStaticLibraryName(String libraryName) {
+            return getLibraryName(libraryName, ".a");
+        }
+
+        @Override
         public boolean isUnix() {
             return true;
         }
@@ -243,6 +293,11 @@ public abstract class OperatingSystem {
         }
 
         @Override
+        public String getFamilyName() {
+            return "os x";
+        }
+
+        @Override
         protected String getSharedLibSuffix() {
             return ".dylib";
         }
@@ -258,10 +313,23 @@ public abstract class OperatingSystem {
         public boolean isLinux() {
             return true;
         }
+
+        @Override
+        public String getFamilyName() {
+            return "linux";
+        }
+    }
+
+    static class FreeBSD extends Unix {
     }
 
     static class Solaris extends Unix {
         @Override
+        public String getFamilyName() {
+            return "solaris";
+        }
+
+        @Override
         protected String getOsPrefix() {
             return "sunos";
         }
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 65a2ac0..cd7f87f 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
@@ -22,7 +22,7 @@ import java.util.Arrays;
 import java.util.List;
 
 public class DirectInstantiator implements Instantiator {
-    public <T> T newInstance(Class<T> type, Object... params) {
+    public <T> T newInstance(Class<? extends T> type, Object... params) {
         try {
             List<Constructor<?>> matches = new ArrayList<Constructor<?>>();
             for (Constructor<?> constructor : type.getConstructors()) {
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 6c50e84..a131ccd 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
@@ -25,6 +25,6 @@ public interface Instantiator {
      *
      * @throws ObjectInstantiationException On failure to create the new instance.
      */
-    <T> T newInstance(Class<T> type, Object... parameters) throws ObjectInstantiationException;
+    <T> T newInstance(Class<? extends 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/JavaMethod.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaMethod.java
new file mode 100644
index 0000000..6f40a89
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaMethod.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.internal.reflect;
+
+import com.google.common.base.Joiner;
+import org.gradle.api.GradleException;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public class JavaMethod<T, R> {
+    private final Method method;
+    private final Class<R> returnType;
+
+    public JavaMethod(Class<T> target, Class<R> returnType, String name, Class<?>... paramTypes) {
+        this.returnType = returnType;
+        method = findMethod(target, target, name, paramTypes);
+        method.setAccessible(true);
+    }
+
+    public JavaMethod(Class<T> target, Class<R> returnType, Method method) {
+        this.returnType = returnType;
+        this.method = method;
+        method.setAccessible(true);
+    }
+
+    private Method findMethod(Class origTarget, Class target, String name, Class<?>[] paramTypes) {
+        for (Method method : target.getDeclaredMethods()) {
+            if (Modifier.isStatic(method.getModifiers())) {
+                continue;
+            }
+            if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), paramTypes)) {
+                return method;
+            }
+        }
+
+        Class<?> parent = target.getSuperclass();
+        if (parent == null) {
+            throw new NoSuchMethodException(String.format("Could not find method %s(%s) on %s.", name, Joiner.on(", ").join(paramTypes), origTarget.getSimpleName()));
+        } else {
+            return findMethod(origTarget, parent, name, paramTypes);
+        }
+    }
+
+    public R invoke(T target, Object... args) {
+        try {
+            Object result = method.invoke(target, args);
+            return returnType.cast(result);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), e);
+        }
+    }
+
+    public Method getMethod() {
+        return method;
+    }
+
+    public Class<?>[] getParameterTypes(){
+        return method.getParameterTypes();
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java
index 215786c..a922a8a 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java
@@ -16,46 +16,100 @@
 
 package org.gradle.internal.reflect;
 
+import org.gradle.api.Transformer;
+import org.gradle.api.specs.Spec;
 import org.gradle.internal.UncheckedException;
+import org.gradle.util.CollectionUtils;
 
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
 
-/**
- * Simple implementations of some reflection capabilities. In contrast to org.gradle.util.ReflectionUtil,
- * this class doesn't make use of Groovy.
- */
 public class JavaReflectionUtil {
-    public static Object readProperty(Object target, String property) {
-        try {
-            Method getterMethod;
-            try {
-                getterMethod = target.getClass().getMethod(toMethodName("get", property));
-            } catch (NoSuchMethodException e) {
-                try {
-                    getterMethod = target.getClass().getMethod(toMethodName("is", property));
-                } catch (NoSuchMethodException e2) {
-                    throw e;
-                }
+    /**
+     * Locates the readable properties of the given type. Searches only public properties.
+     */
+    public static Map<String, PropertyAccessor> readableProperties(Class<?> target) {
+        HashMap<String, PropertyAccessor> properties = new HashMap<String, PropertyAccessor>();
+        for (Method method : target.getMethods()) {
+            if (method.getName().startsWith("get") && isGetter(method)) {
+                String propertyName = method.getName().substring(3);
+                propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
+                properties.put(propertyName, new GetterMethodBackedPropertyAccessor(propertyName, method));
+            } else if (method.getName().startsWith("is") && isBooleanGetter(method)) {
+                String propertyName = method.getName().substring(2);
+                propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
+                properties.put(propertyName, new GetterMethodBackedPropertyAccessor(propertyName, method));
             }
-            return getterMethod.invoke(target);
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
         }
+        return properties;
+    }
+
+    /**
+     * Locates the property with the given name as a readable property. Searches only public properties.
+     *
+     * @throws NoSuchPropertyException when the given property does not exist.
+     */
+    public static PropertyAccessor readableProperty(Class<?> target, String property) throws NoSuchPropertyException {
+        final Method getterMethod = findGetterMethod(target, property);
+        if (getterMethod == null) {
+            throw new NoSuchPropertyException(String.format("Could not find getter method for property '%s' on class %s.", property, target.getSimpleName()));
+        }
+        return new GetterMethodBackedPropertyAccessor(property, getterMethod);
     }
 
-    public static void writeProperty(Object target, String property, Object value) {
+    private static Method findGetterMethod(Class<?> target, String property) {
         try {
-            String setterName = toMethodName("set", property);
-            for (Method method: target.getClass().getMethods()) {
-                if (!method.getName().equals(setterName)) { continue; }
-                if (method.getParameterTypes().length != 1) { continue; }
-                method.invoke(target, value);
-                return;
+            Method getterMethod = target.getMethod(toMethodName("get", property));
+            if (isGetter(getterMethod)) {
+                return getterMethod;
+            }
+        } catch (java.lang.NoSuchMethodException e) {
+            // Ignore
+        }
+        try {
+            Method getterMethod = target.getMethod(toMethodName("is", property));
+            if (isBooleanGetter(getterMethod)) {
+                return getterMethod;
+            }
+        } catch (java.lang.NoSuchMethodException e2) {
+            // Ignore
+        }
+        return null;
+    }
+
+    private static boolean isGetter(Method method) {
+        return method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers()) && !method.getReturnType().equals(Void.TYPE);
+    }
+
+    private static boolean isBooleanGetter(Method method) {
+        Class<?> returnType = method.getReturnType();
+        return method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers()) && (returnType.equals(Boolean.TYPE) || returnType.equals(Boolean.class));
+    }
+
+    /**
+     * Locates the property with the given name as a writable property. Searches only public properties.
+     *
+     * @throws NoSuchPropertyException when the given property does not exist.
+     */
+    public static PropertyMutator writeableProperty(Class<?> target, String property) throws NoSuchPropertyException {
+        String setterName = toMethodName("set", property);
+        for (final Method method : target.getMethods()) {
+            if (!method.getName().equals(setterName)) {
+                continue;
+            }
+            if (method.getParameterTypes().length != 1) {
+                continue;
             }
-            throw new NoSuchMethodException(String.format("could not find setter method '%s'", setterName));
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
+            if (Modifier.isStatic(method.getModifiers())) {
+                continue;
+            }
+            return new MethodBackedPropertyMutator(property, method);
         }
+        throw new NoSuchPropertyException(String.format("Could not find setter method for property '%s' on class %s.", property, target.getSimpleName()));
     }
 
     private static String toMethodName(String prefix, String propertyName) {
@@ -80,4 +134,218 @@ public class JavaReflectionUtil {
         }
         throw new IllegalArgumentException(String.format("Don't know how wrapper type for primitive type %s.", type));
     }
+
+    /**
+     * Locates the given method. Searches all methods, including private methods.
+     */
+    public static <T, R> JavaMethod<T, R> method(Class<T> target, Class<R> returnType, String name, Class<?>... paramTypes) throws NoSuchMethodException {
+        return new JavaMethod<T, R>(target, returnType, name, paramTypes);
+    }
+
+    /**
+     * Locates the given method. Searches all methods, including private methods.
+     */
+    public static <T, R> JavaMethod<T, R> method(T target, Class<R> returnType, String name, Class<?>... paramTypes) throws NoSuchMethodException {
+        @SuppressWarnings("unchecked")
+        Class<T> targetClass = (Class<T>) target.getClass();
+        return method(targetClass, returnType, name, paramTypes);
+    }
+
+    /**
+     * Locates the given method. Searches all methods, including private methods.
+     */
+    public static <T, R> JavaMethod<T, R> method(Class<T> target, Class<R> returnType, Method method) throws NoSuchMethodException {
+        return new JavaMethod<T, R>(target, returnType, method);
+    }
+
+    /**
+     * Locates the given method. Searches all methods, including private methods.
+     */
+    public static <T, R> JavaMethod<T, R> method(T target, Class<R> returnType, Method method) throws NoSuchMethodException {
+        @SuppressWarnings("unchecked")
+        Class<T> targetClass = (Class<T>) target.getClass();
+        return new JavaMethod<T, R>(targetClass, returnType, method);
+    }
+
+    /**
+     * Search methods in an inheritance aware fashion, stopping when stopIndicator returns true.
+     */
+    public static void searchMethods(Class<?> target, final Transformer<Boolean, Method> stopIndicator) {
+        Spec<Method> stopIndicatorAsSpec = new Spec<Method>() {
+            public boolean isSatisfiedBy(Method element) {
+                return stopIndicator.transform(element);
+            }
+        };
+
+        findAllMethodsInternal(target, stopIndicatorAsSpec, new MultiMap<String, Method>(), new ArrayList<Method>(1), true);
+    }
+
+    public static Method findMethod(Class<?> target, Spec<Method> predicate) {
+        List<Method> methods = findAllMethodsInternal(target, predicate, new MultiMap<String, Method>(), new ArrayList<Method>(1), true);
+        return methods.isEmpty() ? null : methods.get(0);
+    }
+
+    // Not hasProperty() because that's awkward with Groovy objects implementing it
+    public static boolean propertyExists(Object target, String propertyName) {
+        Class<?> targetType = target.getClass();
+        Method getterMethod = findGetterMethod(target.getClass(), propertyName);
+        if (getterMethod == null) {
+            try {
+                targetType.getField(propertyName);
+                return true;
+            } catch (NoSuchFieldException ignore) {
+                // ignore
+            }
+        } else {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static class MultiMap<K, V> extends HashMap<K, List<V>> {
+        @Override
+        public List<V> get(Object key) {
+            if (!containsKey(key)) {
+                @SuppressWarnings("unchecked") K keyCast = (K) key;
+                put(keyCast, new LinkedList<V>());
+            }
+
+            return super.get(key);
+        }
+    }
+
+    private static List<Method> findAllMethodsInternal(Class<?> target, Spec<Method> predicate, MultiMap<String, Method> seen, List<Method> collector, boolean stopAtFirst) {
+        for (final Method method : target.getDeclaredMethods()) {
+            List<Method> seenWithName = seen.get(method.getName());
+            Method override = CollectionUtils.findFirst(seenWithName, new Spec<Method>() {
+                public boolean isSatisfiedBy(Method potentionOverride) {
+                    return potentionOverride.getName().equals(method.getName())
+                            && Arrays.equals(potentionOverride.getParameterTypes(), method.getParameterTypes());
+                }
+            });
+
+
+            if (override == null) {
+                seenWithName.add(method);
+                if (predicate.isSatisfiedBy(method)) {
+                    collector.add(method);
+                    if (stopAtFirst) {
+                        return collector;
+                    }
+                }
+            }
+        }
+
+        Class<?> parent = target.getSuperclass();
+        if (parent != null) {
+            return findAllMethodsInternal(parent, predicate, seen, collector, stopAtFirst);
+        }
+
+        return collector;
+    }
+
+    public static <A extends Annotation> A getAnnotation(Class<?> type, Class<A> annotationType) {
+        return getAnnotation(type, annotationType, true);
+    }
+
+    private static <A extends Annotation> A getAnnotation(Class<?> type, Class<A> annotationType, boolean checkType) {
+        A annotation;
+        if (checkType) {
+            annotation = type.getAnnotation(annotationType);
+            if (annotation != null) {
+                return annotation;
+            }
+        }
+
+        if (annotationType.getAnnotation(Inherited.class) != null) {
+            for (Class<?> anInterface : type.getInterfaces()) {
+                annotation = getAnnotation(anInterface, annotationType, true);
+                if (annotation != null) {
+                    return annotation;
+                }
+            }
+        }
+
+        if (type.isInterface() || type.equals(Object.class)) {
+            return null;
+        } else {
+            return getAnnotation(type.getSuperclass(), annotationType, false);
+        }
+    }
+
+    public static boolean isClassAvailable(String className) {
+        try {
+            JavaReflectionUtil.class.getClassLoader().loadClass(className);
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
+    private static class GetterMethodBackedPropertyAccessor implements PropertyAccessor {
+        private final String property;
+        private final Method method;
+
+        public GetterMethodBackedPropertyAccessor(String property, Method method) {
+            this.property = property;
+            this.method = method;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("property %s.%s", method.getDeclaringClass().getSimpleName(), property);
+        }
+
+        public String getName() {
+            return property;
+        }
+
+        public Class<?> getType() {
+            return method.getClass();
+        }
+
+        public Object getValue(Object target) {
+            try {
+                return method.invoke(target);
+            } catch (InvocationTargetException e) {
+                throw UncheckedException.unwrapAndRethrow(e);
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+    }
+
+    private static class MethodBackedPropertyMutator implements PropertyMutator {
+        private final String property;
+        private final Method method;
+
+        public MethodBackedPropertyMutator(String property, Method method) {
+            this.property = property;
+            this.method = method;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("property %s.%s", method.getDeclaringClass().getSimpleName(), property);
+        }
+
+        public String getName() {
+            return property;
+        }
+
+        public Class<?> getType() {
+            return method.getParameterTypes()[0];
+        }
+
+        public void setValue(Object target, Object value) {
+            try {
+                method.invoke(target, value);
+            } catch (InvocationTargetException e) {
+                throw UncheckedException.unwrapAndRethrow(e);
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/NoSuchMethodException.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/NoSuchMethodException.java
new file mode 100644
index 0000000..e61a1bb
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/NoSuchMethodException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Thrown when a requested method cannot be found.
+ */
+public class NoSuchMethodException extends RuntimeException {
+    public NoSuchMethodException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/NoSuchPropertyException.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/NoSuchPropertyException.java
new file mode 100644
index 0000000..9f96809
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/NoSuchPropertyException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Thrown when a requested property cannot be found.
+ */
+public class NoSuchPropertyException extends RuntimeException {
+    public NoSuchPropertyException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/PropertyAccessor.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/PropertyAccessor.java
new file mode 100644
index 0000000..3d02e6c
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/PropertyAccessor.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface PropertyAccessor {
+    String getName();
+
+    Class<?> getType();
+
+    Object getValue(Object target);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/PropertyMutator.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/PropertyMutator.java
new file mode 100644
index 0000000..9780742
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/PropertyMutator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface PropertyMutator {
+    String getName();
+
+    Class<?> getType();
+
+    void setValue(Object target, Object value);
+}
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
deleted file mode 100644
index 2a90634..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/AbstractServiceRegistry.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.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 fc455ee..5cc1ad0 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
@@ -15,10 +15,13 @@
  */
 package org.gradle.internal.service;
 
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.api.Action;
+import org.gradle.api.Nullable;
+import org.gradle.api.specs.Spec;
 import org.gradle.internal.Factory;
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.Stoppable;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 
 import java.lang.reflect.*;
 import java.util.*;
@@ -30,197 +33,383 @@ import java.util.*;
  *
  * <li>Calling {@link #add(Class, Object)} to register a service instance.</li>
  *
- * <li>Calling {@link #add(ServiceRegistry)} to register a set of services.</li>
+ * <li>Calling {@link #addProvider(Object)} to register a service provider bean. A provider bean may have factory, decorator and configuration methods as described below.</li>
  *
- * <li>Adding a factory method. A factory method should have a name that starts with 'create', take no parameters, and have a non-void return type. For example, <code>protected SomeService
- * createSomeService() { .... }</code>.</li>
+ * <li>Adding a factory method. A factory method should have a name that starts with 'create', and have a non-void return type. For example, <code>protected SomeService
+ * createSomeService() { .... }</code>. Parameters are injected using services from this registry or its parents.</li>
  *
- * <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single parameter, and a have a non-void return type. The before invoking the method, the
+ * <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single parameter, and a have a non-void return type. Before invoking the method, the
  * parameter is located in the parent service registry and then passed to the method.</li>
  *
+ * <li>Adding a configure method. A configure method should be called 'configure', take a {@link ServiceRegistration} parameter, and a have a void return type. Additional parameters
+ * are injected using services from this registry or its parents.</li>
+ *
  * </ul>
  *
- * <p>Service instances are created on demand. {@link #getFactory(Class)} looks for a service instance which implements {@code Factory<T>} where {@code T} is the expected type.</p>.
+ * <p>Service instances are created on demand. {@link #getFactory(Class)} looks for a service instance which implements {@code Factory<T>} where {@code T} is the expected type.</p>
  *
  * <p>Service registries are arranged in a hierarchy. If a service of a given type cannot be located, the registry uses its parent registry, if any, to locate the service.</p>
  */
-public class DefaultServiceRegistry extends AbstractServiceRegistry {
-    private final List<Provider> providers = new LinkedList<Provider>();
+public class DefaultServiceRegistry implements ServiceRegistry {
+    private final Object lock = new Object();
+    private final CompositeProvider allServices = new CompositeProvider();
     private final OwnServices ownServices;
-    private final List<Provider> registeredProviders;
-    private final ServiceRegistry parent;
+    private final CompositeProvider parentServices;
+    private final String displayName;
     private boolean closed;
 
     public DefaultServiceRegistry() {
-        this(null);
+        this(null, Collections.<ServiceRegistry>emptyList());
+    }
+
+    public DefaultServiceRegistry(String displayName) {
+        this(displayName, Collections.<ServiceRegistry>emptyList());
+    }
+
+    public DefaultServiceRegistry(ServiceRegistry... parents) {
+        this(null, parents);
+    }
+
+    public DefaultServiceRegistry(String displayName, ServiceRegistry... parents) {
+        this(displayName, Arrays.asList(parents));
     }
 
-    public DefaultServiceRegistry(ServiceRegistry parent) {
-        this.parent = parent;
+    public DefaultServiceRegistry(String displayName, Collection<? extends ServiceRegistry> parents) {
+        this.displayName = displayName != null ? displayName : getClass().getSimpleName();
+        this.parentServices = parents.isEmpty() ? null : new CompositeProvider();
         this.ownServices = new OwnServices();
-        providers.add(ownServices);
-        if (parent != null) {
-            providers.add(new ParentServices());
+        allServices.providers.add(ownServices);
+        if (parentServices != null) {
+            allServices.providers.add(parentServices);
+            for (ServiceRegistry parent : parents) {
+                parentServices.providers.add(new ParentServices(parent));
+            }
         }
-        registeredProviders = providers.subList(1, 1);
 
-        findProviderMethods();
+        findProviderMethods(this);
+    }
+
+    /**
+     * Creates a service registry that uses the given providers.
+     */
+    public static ServiceRegistry create(Object... providers) {
+        DefaultServiceRegistry registry = new DefaultServiceRegistry();
+        for (Object provider : providers) {
+            registry.addProvider(provider);
+        }
+        return registry;
     }
 
     @Override
     public String toString() {
-        return getClass().getSimpleName();
+        return displayName;
+    }
+
+    private void findProviderMethods(Object target) {
+        Set<String> methods = new HashSet<String>();
+        for (Class<?> type = target.getClass(); type != Object.class; type = type.getSuperclass()) {
+            findDecoratorMethods(target, type, methods, ownServices);
+            findFactoryMethods(target, type, methods, ownServices);
+        }
+        findConfigureMethod(target);
     }
 
-    private void findProviderMethods() {
-        Set<String> factoryMethods = new HashSet<String>();
-        Set<String> decoratorMethods = new HashSet<String>();
-        for (Class<?> type = getClass(); type != Object.class; type = type.getSuperclass()) {
-            findFactoryMethods(type, factoryMethods, ownServices);
-            findDecoratorMethods(type, decoratorMethods, ownServices);
+    private void findConfigureMethod(Object target) {
+        for (Class<?> type = target.getClass(); type != Object.class; type = type.getSuperclass()) {
+            for (Method method : type.getDeclaredMethods()) {
+                if (!method.getName().equals("configure")) {
+                    continue;
+                }
+                if (!method.getReturnType().equals(Void.TYPE)) {
+                    throw new ServiceLookupException(String.format("Method %s.%s() must return void.", type.getSimpleName(), method.getName()));
+                }
+                Object[] params = new Object[method.getGenericParameterTypes().length];
+                DefaultLookupContext context = new DefaultLookupContext();
+                for (int i = 0; i < method.getGenericParameterTypes().length; i++) {
+                    Type paramType = method.getGenericParameterTypes()[i];
+                    if (paramType.equals(ServiceRegistration.class)) {
+                        params[i] = newRegistration();
+                    } else {
+                        ServiceProvider paramProvider = context.find(paramType, allServices);
+                        if (paramProvider == null) {
+                            throw new ServiceLookupException(String.format("Cannot configure services using %s.%s() as required service of type %s is not available.",
+                                    method.getDeclaringClass().getSimpleName(),
+                                    method.getName(),
+                                    format(paramType)));
+                        }
+                        params[i] = paramProvider.get();
+                    }
+                }
+                try {
+                    invoke(method, target, params);
+                } catch (Exception e) {
+                    throw new ServiceLookupException(String.format("Could not configure services using %s.%s().",
+                            method.getDeclaringClass().getSimpleName(),
+                            method.getName()), e);
+                }
+                return;
+            }
         }
     }
 
-    private void findFactoryMethods(Class<?> type, Set<String> factoryMethods, OwnServices ownServices) {
+    private void findFactoryMethods(Object target, Class<?> type, Set<String> factoryMethods, OwnServices ownServices) {
         for (Method method : type.getDeclaredMethods()) {
             if (method.getName().startsWith("create")
-                    && method.getParameterTypes().length == 0
-                    && method.getReturnType() != Void.class) {
+                    && !Modifier.isStatic(method.getModifiers())) {
+                if (method.getReturnType().equals(Void.TYPE)) {
+                    throw new ServiceLookupException(String.format("Method %s.%s() must not return void.", type.getSimpleName(), method.getName()));
+                }
                 if (factoryMethods.add(method.getName())) {
-                    ownServices.add(new FactoryMethodService(method));
+                    ownServices.add(new FactoryMethodService(target, method));
                 }
             }
         }
     }
 
-    private void findDecoratorMethods(Class<?> type, Set<String> decoratorMethods, OwnServices ownServices) {
+    private void findDecoratorMethods(Object target, Class<?> type, Set<String> decoratorMethods, OwnServices ownServices) {
         for (Method method : type.getDeclaredMethods()) {
             if (method.getName().startsWith("create")
                     && method.getParameterTypes().length == 1
-                    && method.getReturnType() != Void.class
                     && method.getParameterTypes()[0].equals(method.getReturnType())) {
-                if (parent == null) {
-                    throw new ServiceLookupException("Cannot use decorator methods when no parent registry is provided.");
+                if (parentServices == null) {
+                    throw new ServiceLookupException(String.format("Cannot use decorator method %s.%s() when no parent registry is provided.", type.getSimpleName(), method.getName()));
+                }
+                if (method.getReturnType().equals(Void.TYPE)) {
+                    throw new ServiceLookupException(String.format("Method %s.%s() must not return void.", type.getSimpleName(), method.getName()));
                 }
                 if (decoratorMethods.add(method.getName())) {
-                    ownServices.add(new DecoratorMethodService(method));
+                    ownServices.add(new DecoratorMethodService(target, method));
                 }
             }
         }
     }
 
     /**
-     * Adds a set of services to this registry. The given registry is closed when this registry is closed.
+     * Adds services to this container using the given action.
      */
-    public void add(ServiceRegistry nested) {
-        registeredProviders.add(new NestedServices(nested));
+    public void register(Action<? super ServiceRegistration> action) {
+        action.execute(newRegistration());
+    }
+
+    private ServiceRegistration newRegistration() {
+        return new ServiceRegistration(){
+            public <T> void add(Class<T> serviceType, T serviceInstance) {
+                DefaultServiceRegistry.this.add(serviceType, serviceInstance);
+            }
+
+            public void add(Class<?> serviceType) {
+                ownServices.add(new ConstructorService(serviceType));
+            }
+
+            public void addProvider(Object provider) {
+                DefaultServiceRegistry.this.addProvider(provider);
+            }
+        };
     }
 
     /**
      * Adds a service to this registry. The given object is closed when this registry is closed.
      */
-    public <T> void add(Class<T> serviceType, final T serviceInstance) {
+    public <T> DefaultServiceRegistry add(Class<T> serviceType, final T serviceInstance) {
         ownServices.add(new FixedInstanceService<T>(serviceType, serviceInstance));
+        return this;
+    }
+
+    /**
+     * Adds a service provider bean to this registry. This provider may define factory and decorator methods.
+     */
+    public DefaultServiceRegistry addProvider(Object provider) {
+        findProviderMethods(provider);
+        return this;
     }
 
     /**
-     * Closes all services for this registry. For each service, if the service has a public void close() method, that method is called to close the service.
+     * Closes all services for this registry. For each service, if the service has a public void close() or stop() method, that method is called to close the service.
      */
     public void close() {
-        try {
-            CompositeStoppable.stoppable(providers).stop();
-        } finally {
-            closed = true;
-            providers.clear();
+        synchronized (lock) {
+            try {
+                CompositeStoppable.stoppable(allServices).stop();
+            } finally {
+                closed = true;
+            }
         }
     }
 
-    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));
+    public boolean isClosed() {
+        return closed;
+    }
+
+    private static String format(Type type) {
+        if (type instanceof Class) {
+            Class<?> aClass = (Class) type;
+            return aClass.getSimpleName();
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) type;
+            StringBuilder builder = new StringBuilder();
+            builder.append(format(parameterizedType.getRawType()));
+            builder.append("<");
+            for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) {
+                Type typeParam = parameterizedType.getActualTypeArguments()[i];
+                if (i > 0) {
+                    builder.append(", ");
+                }
+                builder.append(format(typeParam));
+            }
+            builder.append(">");
+            return builder.toString();
         }
 
-        for (Provider service : providers) {
-            T t = service.getService(serviceType);
-            if (t != null) {
-                return t;
+        return type.toString();
+    }
+
+    public <T> List<T> getAll(Class<T> serviceType) throws ServiceLookupException {
+        synchronized (lock) {
+            if (closed) {
+                throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.", format(serviceType), displayName));
             }
+            List<T> result = new ArrayList<T>();
+            DefaultLookupContext context = new DefaultLookupContext();
+            allServices.getAll(context, serviceType, result);
+            return result;
         }
+    }
 
-        throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.",
-                serviceType.getSimpleName(), this));
+    public <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException {
+        return serviceType.cast(doGet(serviceType));
     }
 
-    public <T> Factory<T> getFactory(Class<T> type) {
-        if (closed) {
-            throw new IllegalStateException(String.format("Cannot locate factory for objects of type %s, as %s has been closed.",
-                    type.getSimpleName(), this));
+    public Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException {
+        return doGet(serviceType);
+    }
+
+    private Object doGet(Type serviceType) throws IllegalArgumentException {
+        synchronized (lock) {
+            if (closed) {
+                throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.", format(serviceType), displayName));
+            }
+
+            DefaultLookupContext context = new DefaultLookupContext();
+            ServiceProvider provider = context.find(serviceType, allServices);
+            if (provider != null) {
+                return provider.get();
+            }
+
+            throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.", format(serviceType), displayName));
         }
+    }
+
+    public <T> Factory<T> getFactory(Class<T> type) {
+        synchronized (lock) {
+            if (closed) {
+                throw new IllegalStateException(String.format("Cannot locate factory for objects of type %s, as %s has been closed.", format(type), displayName));
+            }
 
-        for (Provider service : providers) {
-            Factory<T> factory = service.getFactory(type);
+            DefaultLookupContext context = new DefaultLookupContext();
+            ServiceProvider factory = allServices.getFactory(context, type);
             if (factory != null) {
-                return factory;
+                return (Factory<T>) factory.get();
             }
+
+            throw new UnknownServiceException(type, String.format("No factory for objects of type %s available in %s.", format(type), displayName));
         }
+    }
 
-        throw new UnknownServiceException(type, String.format("No factory for objects of type %s available in %s.",
-                type.getSimpleName(), this));
+    public <T> T newInstance(Class<T> type) {
+        return getFactory(type).create();
     }
 
     private static Object invoke(Method method, Object target, Object... args) {
-        try {
-            method.setAccessible(true);
-            return method.invoke(target, args);
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.throwAsUncheckedException(e.getCause());
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
+        return JavaReflectionUtil.method(target, Object.class, method).invoke(target, args);
+    }
+
+    interface ServiceProvider {
+        String getDisplayName();
+
+        Object get();
+
+        void requiredBy(Provider provider);
     }
 
     interface Provider extends Stoppable {
         /**
          * Locates a service instance of the given type. Returns null if this provider does not provide a service of this type.
          */
-        <T> T getService(Class<T> serviceType);
+        ServiceProvider getService(LookupContext context, TypeSpec serviceType);
 
         /**
          * Locates a factory for services of the given type. Returns null if this provider does not provide any services of this type.
          */
-        <T> Factory<T> getFactory(Class<T> type);
+        ServiceProvider getFactory(LookupContext context, Class<?> type);
+
+        <T> void getAll(LookupContext context, Class<T> serviceType, List<T> result);
     }
 
     private class OwnServices implements Provider {
         private final List<Provider> providers = new ArrayList<Provider>();
 
-        public <T> Factory<T> getFactory(Class<T> type) {
-            Factory<T> match = null;
+        public ServiceProvider getFactory(LookupContext context, Class<?> type) {
+            List<ServiceProvider> candidates = new ArrayList<ServiceProvider>();
             for (Provider provider : providers) {
-                Factory<T> factory = provider.getFactory(type);
+                ServiceProvider factory = provider.getFactory(context, type);
                 if (factory != null) {
-                    if (match != null) {
-                        throw new ServiceLookupException(String.format("Multiple factories for objects of type %s available in %s.", type.getSimpleName(), DefaultServiceRegistry.this.toString()));
-                    }
-                    match = factory;
+                    candidates.add(factory);
                 }
             }
-            return match;
+
+            if (candidates.size() == 0) {
+                return null;
+            }
+            if (candidates.size() == 1) {
+                return candidates.get(0);
+            }
+
+            Set<String> descriptions = new TreeSet<String>();
+            for (ServiceProvider candidate : candidates) {
+                descriptions.add(candidate.getDisplayName());
+            }
+
+            Formatter formatter = new Formatter();
+            formatter.format("Multiple factories for objects of type %s available in %s:", format(type), displayName);
+            for (String description : descriptions) {
+                formatter.format("%n   - %s", description);
+            }
+            throw new ServiceLookupException(formatter.toString());
         }
 
-        public <T> T getService(Class<T> serviceType) {
-            T match = null;
+        public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
+            List<ServiceProvider> candidates = new ArrayList<ServiceProvider>();
             for (Provider provider : providers) {
-                T service = provider.getService(serviceType);
+                ServiceProvider service = provider.getService(context, serviceType);
                 if (service != null) {
-                    if (match != null) {
-                        throw new ServiceLookupException(String.format("Multiple services of type %s available in %s.", serviceType.getSimpleName(), DefaultServiceRegistry.this.toString()));
-                    }
-                    match = service;
+                    candidates.add(service);
                 }
             }
-            return match;
+
+            if (candidates.size() == 0) {
+                return null;
+            }
+            if (candidates.size() == 1) {
+                return candidates.get(0);
+            }
+
+            Set<String> descriptions = new TreeSet<String>();
+            for (ServiceProvider candidate : candidates) {
+                descriptions.add(candidate.getDisplayName());
+            }
+
+            Formatter formatter = new Formatter();
+            formatter.format("Multiple services of type %s available in %s:", format(serviceType.getType()), displayName);
+            for (String description : descriptions) {
+                formatter.format("%n   - %s", description);
+            }
+            throw new ServiceLookupException(formatter.toString());
+        }
+
+        public <T> void getAll(LookupContext context, Class<T> serviceType, List<T> result) {
+            for (Provider provider : providers) {
+                provider.getAll(context, serviceType, result);
+            }
         }
 
         public void stop() {
@@ -234,6 +423,11 @@ public class DefaultServiceRegistry extends AbstractServiceRegistry {
 
     private static abstract class ManagedObjectProvider<T> implements Provider {
         private T instance;
+        private final Set<Provider> dependents = new HashSet<Provider>();
+
+        protected void setInstance(T instance) {
+            this.instance = instance;
+        }
 
         public T getInstance() {
             if (instance == null) {
@@ -245,18 +439,26 @@ public class DefaultServiceRegistry extends AbstractServiceRegistry {
 
         protected abstract T create();
 
+        public void requiredBy(Provider provider) {
+            dependents.add(provider);
+        }
+
         public void stop() {
             try {
-                CompositeStoppable.stoppable(instance).stop();
+                if (instance != null) {
+                    CompositeStoppable.stoppable(dependents).add(instance).stop();
+                }
             } finally {
+                dependents.clear();
                 instance = null;
             }
         }
     }
 
-    private static abstract class SingletonService extends ManagedObjectProvider<Object> {
+    private static abstract class SingletonService extends ManagedObjectProvider<Object> implements ServiceProvider {
         final Type serviceType;
         final Class serviceClass;
+        boolean bound;
 
         SingletonService(Type serviceType) {
             this.serviceType = serviceType;
@@ -265,28 +467,48 @@ public class DefaultServiceRegistry extends AbstractServiceRegistry {
 
         @Override
         public String toString() {
-            return String.format("Service %s", serviceType);
+            return getDisplayName();
         }
 
-        public <T> T getService(Class<T> serviceType) {
-            if (!serviceType.isAssignableFrom(this.serviceClass)) {
+        public Object get() {
+            return getInstance();
+        }
+
+        private ServiceProvider prepare(LookupContext context) {
+            if (!bound) {
+                bind(context);
+                bound = true;
+            }
+            return this;
+        }
+
+        protected void bind(LookupContext context) {
+        }
+
+        public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
+            if (!serviceType.isSatisfiedBy(this.serviceType)) {
                 return null;
             }
-            return serviceType.cast(getInstance());
+            return prepare(context);
         }
 
+        public <T> void getAll(LookupContext context, Class<T> serviceType, List<T> result) {
+            if (serviceType.isAssignableFrom(this.serviceClass)) {
+                result.add(serviceType.cast(prepare(context).get()));
+            }
+        }
 
-        public <T> Factory<T> getFactory(Class<T> elementType) {
-            if (!Factory.class.isAssignableFrom(serviceClass)) {
+        public ServiceProvider getFactory(LookupContext context, Class<?> elementType) {
+            if (!isFactory(serviceType, elementType)) {
                 return null;
             }
-            return getFactory(serviceType, elementType);
+            return prepare(context);
         }
 
-        private <T> Factory<T> getFactory(Type type, Class<T> elementType) {
+        private boolean isFactory(Type type, Class<?> elementType) {
             Class c = toClass(type);
             if (!Factory.class.isAssignableFrom(c)) {
-                return null;
+                return false;
             }
 
             if (type instanceof ParameterizedType) {
@@ -295,22 +517,19 @@ public class DefaultServiceRegistry extends AbstractServiceRegistry {
                 if (parameterizedType.getRawType().equals(Factory.class)) {
                     Type actualType = parameterizedType.getActualTypeArguments()[0];
                     if (actualType instanceof Class<?> && elementType.isAssignableFrom((Class<?>) actualType)) {
-                        @SuppressWarnings("unchecked")
-                        Factory<T> f = getService(Factory.class);
-                        return f;
+                        return true;
                     }
                 }
             }
 
             // Check if type extends Factory<? extends ElementType>
             for (Type interfaceType : c.getGenericInterfaces()) {
-                Factory<T> f = getFactory(interfaceType, elementType);
-                if (f != null) {
-                    return f;
+                if (isFactory(interfaceType, elementType)) {
+                    return true;
                 }
             }
 
-            return null;
+            return false;
         }
 
         private Class toClass(Type type) {
@@ -323,133 +542,468 @@ public class DefaultServiceRegistry extends AbstractServiceRegistry {
         }
     }
 
-    private class FactoryMethodService extends SingletonService {
+    private abstract class FactoryService extends SingletonService {
+        private ServiceProvider[] paramProviders;
+
+        protected FactoryService(Type serviceType) {
+            super(serviceType);
+        }
+
+        protected abstract Type[] getParameterTypes();
+
+        protected abstract Member getFactory();
+
+        @Override
+        protected void bind(LookupContext context) {
+            Type[] parameterTypes = getParameterTypes();
+            paramProviders = new ServiceProvider[parameterTypes.length];
+            for (int i = 0; i < parameterTypes.length; i++) {
+                Type paramType = parameterTypes[i];
+                try {
+                    if (paramType.equals(ServiceRegistry.class)) {
+                        paramProviders[i] = getThisAsProvider();
+                    } else {
+                        ServiceProvider paramProvider = context.find(paramType, allServices);
+                        if (paramProvider == null) {
+                            throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as required service of type %s is not available.",
+                                    format(serviceType),
+                                    getFactory().getDeclaringClass().getSimpleName(),
+                                    getFactory().getName(),
+                                    format(paramType)));
+
+                        }
+                        paramProviders[i] = paramProvider;
+                        paramProvider.requiredBy(this);
+                    }
+                } catch (ServiceValidationException e) {
+                    throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as there is a problem with parameter #%s of type %s.",
+                            format(serviceType),
+                            getFactory().getDeclaringClass().getSimpleName(),
+                            getFactory().getName(),
+                            i+1,
+                            format(paramType)), e);
+                }
+            }
+        }
+
+        @Override
+        protected Object create() {
+            Object[] params = assembleParameters();
+            Object result = invokeMethod(params);
+            // Can discard the state required to create instance
+            paramProviders = null;
+            return result;
+        }
+
+        private Object[] assembleParameters() {
+            Object[] params = new Object[paramProviders.length];
+            for (int i = 0; i < paramProviders.length; i++) {
+                ServiceProvider paramProvider = paramProviders[i];
+                params[i] = paramProvider.get();
+            }
+            return params;
+        }
+
+        protected abstract Object invokeMethod(Object[] params);
+    }
+
+    private class FactoryMethodService extends FactoryService {
         private final Method method;
+        private Object target;
 
-        public FactoryMethodService(Method method) {
+        public FactoryMethodService(Object target, Method method) {
             super(method.getGenericReturnType());
+            this.target = target;
             this.method = method;
         }
 
+        public String getDisplayName() {
+            return String.format("Service %s at %s.%s()", format(method.getGenericReturnType()), method.getDeclaringClass().getSimpleName(), method.getName());
+        }
+
+        protected Type[] getParameterTypes() {
+            return method.getGenericParameterTypes();
+        }
+
         @Override
-        protected Object create() {
-            return invoke(method, DefaultServiceRegistry.this);
+        protected Member getFactory() {
+            return method;
+        }
+
+        protected Object invokeMethod(Object[] params) {
+            Object result;
+            try {
+                result = invoke(method, target, params);
+            } catch (Exception e) {
+                throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s().",
+                        format(serviceType),
+                        method.getDeclaringClass().getSimpleName(),
+                        method.getName()),
+                        e);
+            }
+            try {
+                if (result == null) {
+                    throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s() as this method returned null.",
+                            format(serviceType),
+                            method.getDeclaringClass().getSimpleName(),
+                            method.getName()));
+                }
+                return result;
+            } finally {
+                // Can discard the state required to create instance
+                target = null;
+            }
         }
     }
 
-    private static class FixedInstanceService<T> extends SingletonService {
-        private final T serviceInstance;
+    private ServiceProvider getThisAsProvider() {
+        return new ServiceProvider() {
+            public String getDisplayName() {
+                return String.format("ServiceRegistry %s", displayName);
+            }
+
+            public Object get() {
+                return DefaultServiceRegistry.this;
+            }
 
+            public void requiredBy(Provider provider) {
+            }
+        };
+    }
+
+    private static class FixedInstanceService<T> extends SingletonService {
         public FixedInstanceService(Class<T> serviceType, T serviceInstance) {
             super(serviceType);
-            this.serviceInstance = serviceInstance;
-            getService(serviceType);
+            setInstance(serviceInstance);
+        }
+
+        public String getDisplayName() {
+            return String.format("Service %s with implementation %s", format(serviceType), getInstance());
         }
 
         @Override
         protected Object create() {
-            return serviceInstance;
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private class ConstructorService extends FactoryService {
+        private final Constructor<?> constructor;
+
+        private ConstructorService(Class<?> serviceType) {
+            super(serviceType);
+            Constructor<?>[] constructors = serviceType.getDeclaredConstructors();
+            if (constructors.length != 1) {
+                throw new ServiceValidationException(String.format("Expected a single constructor for %s.", format(serviceType)));
+            }
+            this.constructor = constructors[0];
+        }
+
+        @Override
+        protected Type[] getParameterTypes() {
+            return constructor.getGenericParameterTypes();
+        }
+
+        @Override
+        protected Member getFactory() {
+            return constructor;
+        }
+
+        @Override
+        protected Object invokeMethod(Object[] params) {
+            try {
+                return constructor.newInstance(params);
+            } catch (InvocationTargetException e) {
+                throw new ServiceCreationException(String.format("Could not create service of type %s.", format(serviceType)), e.getCause());
+            } catch (Exception e) {
+                throw new ServiceCreationException(String.format("Could not create service of type %s.", format(serviceType)), e);
+            }
+        }
+
+        public String getDisplayName() {
+            return String.format("Service %s", format(serviceType));
         }
     }
 
     private class DecoratorMethodService extends SingletonService {
         private final Method method;
+        private Object target;
+        private ServiceProvider paramProvider;
 
-        public DecoratorMethodService(Method method) {
+        public DecoratorMethodService(Object target, Method method) {
             super(method.getGenericReturnType());
+            this.target = target;
             this.method = method;
         }
 
+        public String getDisplayName() {
+            return String.format("Service %s at %s.%s()", format(method.getGenericReturnType()), method.getDeclaringClass().getSimpleName(), method.getName());
+        }
+
+        @Override
+        protected void bind(LookupContext context) {
+            Type paramType = method.getGenericParameterTypes()[0];
+            DefaultLookupContext parentLookupContext = new DefaultLookupContext();
+            paramProvider = parentLookupContext.find(paramType, parentServices);
+            if (paramProvider == null) {
+                throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as required service of type %s is not available in parent registries.",
+                        format(method.getGenericReturnType()),
+                        method.getDeclaringClass().getSimpleName(),
+                        method.getName(),
+                        format(paramType)));
+            }
+        }
+
         @Override
         protected Object create() {
-            Object value;
-            if (Factory.class.isAssignableFrom(method.getParameterTypes()[0])) {
-                ParameterizedType fatoryType = (ParameterizedType) method.getGenericParameterTypes()[0];
-                Type typeArg = fatoryType.getActualTypeArguments()[0];
-                Class<?> type;
-                if (typeArg instanceof WildcardType) {
-                    WildcardType wildcardType = (WildcardType) typeArg;
-                    type = (Class) wildcardType.getUpperBounds()[0];
-                } else {
-                    type = (Class) typeArg;
-                }
-                value = parent.getFactory(type);
-            } else {
-                value = parent.get(method.getParameterTypes()[0]);
+            Object param = paramProvider.get();
+            Object result;
+            try {
+                result = invoke(method, target, param);
+            } catch (Exception e) {
+                throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s().",
+                        format(method.getGenericReturnType()),
+                        method.getDeclaringClass().getSimpleName(),
+                        method.getName()),
+                        e);
+            }
+            try {
+                if (result == null) {
+                    throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s() as this method returned null.",
+                            format(method.getGenericReturnType()),
+                            method.getDeclaringClass().getSimpleName(),
+                            method.getName()));
+                }
+                return result;
+            } finally {
+                // Can discard state required to create instance
+                paramProvider = null;
+                target = null;
             }
-            return invoke(method, DefaultServiceRegistry.this, value);
         }
     }
 
-    private static class NestedServices extends ManagedObjectProvider<ServiceRegistry> {
-        private final ServiceRegistry nested;
+    private class CompositeProvider implements Provider {
+        private final List<Provider> providers = new LinkedList<Provider>();
 
-        public NestedServices(ServiceRegistry nested) {
-            this.nested = nested;
-            getInstance();
+        public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
+            for (Provider provider : providers) {
+                ServiceProvider service = provider.getService(context, serviceType);
+                if (service != null) {
+                    return service;
+                }
+            }
+            return null;
         }
 
-        @Override
-        protected ServiceRegistry create() {
-            return nested;
+        public ServiceProvider getFactory(LookupContext context, Class<?> type) {
+            for (Provider provider : providers) {
+                ServiceProvider factory = provider.getFactory(context, type);
+                if (factory != null) {
+                    return factory;
+                }
+            }
+            return null;
         }
 
-        public <T> Factory<T> getFactory(Class<T> type) {
-            try {
-                Factory<T> factory = getInstance().getFactory(type);
-                assert factory != null;
-                assert factory != null : String.format("nested registry returned null for factory type '%s'", type.getName());
-                return factory;
-            } catch (UnknownServiceException e) {
-                if (e.getType().equals(type)) {
-                    return null;
-                }
-                throw e;
+        public <T> void getAll(LookupContext context, Class<T> serviceType, List<T> result) {
+            for (Provider provider : providers) {
+                provider.getAll(context, serviceType, result);
             }
         }
 
-        public <T> T getService(Class<T> serviceType) {
+        public void stop() {
             try {
-                T service = getInstance().get(serviceType);
-                assert service != null : String.format("nested registry returned null for service type '%s'", serviceType.getName());
-                return service;
-            } catch (UnknownServiceException e) {
-                if (e.getType().equals(serviceType)) {
-                    return null;
-                }
-                throw e;
+                CompositeStoppable.stoppable(providers).stop();
+            } finally {
+                providers.clear();
             }
         }
     }
 
     private class ParentServices implements Provider {
-        public <T> Factory<T> getFactory(Class<T> type) {
+        private final ServiceRegistry parent;
+
+        private ParentServices(ServiceRegistry parent) {
+            this.parent = parent;
+        }
+
+        public ServiceProvider getFactory(LookupContext context, Class<?> type) {
             try {
-                Factory<T> factory = parent.getFactory(type);
+                Factory<?> factory = parent.getFactory(type);
                 assert factory != null : String.format("parent returned null for factory type '%s'", type.getName());
-                return factory;
+                return wrap(factory);
             } catch (UnknownServiceException e) {
-                if (e.getType().equals(type)) {
-                    return null;
+                if (!e.getType().equals(type)) {
+                    throw e;
                 }
-                throw e;
             }
+            return null;
         }
 
-        public <T> T getService(Class<T> serviceType) {
+        public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
             try {
-                T service = parent.get(serviceType);
-                assert service != null : String.format("parent returned null for service type '%s'", serviceType.getName());
-                return service;
+                Object service = parent.get(serviceType.getType());
+                assert service != null : String.format("parent returned null for service type %s", format(serviceType.getType()));
+                return wrap(service);
             } catch (UnknownServiceException e) {
-                if (e.getType().equals(serviceType)) {
-                    return null;
+                if (!e.getType().equals(serviceType.getType())) {
+                    throw e;
                 }
-                throw e;
             }
+            return null;
+        }
+
+        private ServiceProvider wrap(final Object instance) {
+            return new ServiceProvider() {
+                public String getDisplayName() {
+                    return String.format("ServiceRegistry %s", parent);
+                }
+
+                public Object get() {
+                    return instance;
+                }
+
+                public void requiredBy(Provider provider) {
+                    // Ignore
+                }
+            };
+        }
+
+        public <T> void getAll(LookupContext context, Class<T> serviceType, List<T> result) {
+            List<T> services = parent.getAll(serviceType);
+            assert services != null : String.format("parent returned null for services of type %s", format(serviceType));
+            result.addAll(services);
         }
 
         public void stop() {
         }
     }
+
+    interface LookupContext {
+        @Nullable
+        ServiceProvider find(Type type, Provider provider);
+    }
+
+    interface TypeSpec extends Spec<Type> {
+        Type getType();
+    }
+
+    private static class ClassSpec implements TypeSpec {
+        private final Class<?> type;
+
+        private ClassSpec(Class<?> type) {
+            this.type = type;
+        }
+
+        public Type getType() {
+            return type;
+        }
+
+        public boolean isSatisfiedBy(Type element) {
+            if (element instanceof ParameterizedType) {
+                ParameterizedType parameterizedType = (ParameterizedType) element;
+                if (parameterizedType.getRawType() instanceof Class) {
+                    return type.isAssignableFrom((Class) parameterizedType.getRawType());
+                }
+            } else if(element instanceof Class) {
+                Class<?> other = (Class<?>) element;
+                return type.isAssignableFrom(other);
+            }
+            return false;
+        }
+    }
+
+    private static class ParameterizedTypeSpec implements TypeSpec {
+        private final Type type;
+        private final TypeSpec rawType;
+        private final List<TypeSpec> paramSpecs;
+
+        private ParameterizedTypeSpec(Type type, TypeSpec rawType, List<TypeSpec> paramSpecs) {
+            this.type = type;
+            this.rawType = rawType;
+            this.paramSpecs = paramSpecs;
+        }
+
+        public Type getType() {
+            return type;
+        }
+
+        public boolean isSatisfiedBy(Type element) {
+            if (element instanceof ParameterizedType) {
+                ParameterizedType parameterizedType = (ParameterizedType) element;
+                if (!rawType.isSatisfiedBy(parameterizedType.getRawType())) {
+                    return false;
+                }
+                for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) {
+                    Type type = parameterizedType.getActualTypeArguments()[i];
+                    if (!paramSpecs.get(i).isSatisfiedBy(type)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private static class DefaultLookupContext implements LookupContext {
+        private final Set<Type> visiting = new HashSet<Type>();
+
+        public ServiceProvider find(Type serviceType, Provider provider) {
+            if (!visiting.add(serviceType)) {
+                throw new ServiceValidationException(String.format("Cycle in dependencies of service of type %s.", format(serviceType)));
+            }
+            try {
+                if (serviceType instanceof ParameterizedType) {
+                    ParameterizedType parameterizedType = (ParameterizedType) serviceType;
+                    if (parameterizedType.getRawType().equals(Factory.class)) {
+                        Type typeArg = parameterizedType.getActualTypeArguments()[0];
+                        if (typeArg instanceof Class) {
+                            return provider.getFactory(this, (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 provider.getFactory(this, (Class<Object>) wildcardType.getLowerBounds()[0]);
+                                }
+                            }
+                            if (wildcardType.getLowerBounds().length == 0 && wildcardType.getUpperBounds().length == 1) {
+                                if (wildcardType.getUpperBounds()[0] instanceof Class) {
+                                    return provider.getFactory(this, (Class<Object>) wildcardType.getUpperBounds()[0]);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                return provider.getService(this, toSpec(serviceType));
+            } finally {
+                visiting.remove(serviceType);
+            }
+        }
+
+        TypeSpec toSpec(Type serviceType) {
+            if (serviceType instanceof ParameterizedType) {
+                ParameterizedType parameterizedType = (ParameterizedType) serviceType;
+                List<TypeSpec> paramSpecs = new ArrayList<TypeSpec>();
+                for (Type paramType : parameterizedType.getActualTypeArguments()) {
+                    paramSpecs.add(toSpec(paramType));
+                }
+                return new ParameterizedTypeSpec(serviceType, toSpec(parameterizedType.getRawType()), paramSpecs);
+            } else if (serviceType instanceof Class) {
+                Class<?> serviceClass = (Class<?>) serviceType;
+                if (serviceClass.isArray()) {
+                    throw new ServiceValidationException("Locating services with array type is not supported.");
+                }
+                if (serviceClass.isAnnotation()) {
+                    throw new ServiceValidationException("Locating services with annotation type is not supported.");
+                }
+                return new ClassSpec(serviceClass);
+            }
+
+            throw new ServiceValidationException(String.format("Locating services with type %s is not supported.", format(serviceType)));
+        }
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceCreationException.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceCreationException.java
new file mode 100644
index 0000000..d102063
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceCreationException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Thrown when a service instance cannot be created.
+ */
+public class ServiceCreationException extends ServiceLookupException {
+    public ServiceCreationException(String message) {
+        super(message);
+    }
+
+    public ServiceCreationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
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 dacebfa..e52fbcb 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
@@ -25,29 +25,29 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URL;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.*;
 
 /**
  * Uses the Jar service resource specification to locate service implementations.
  */
-public class ServiceLocator extends AbstractServiceRegistry {
-    private final ClassLoader classLoader;
-    private final Map<Class<?>, Object> implementations = new ConcurrentHashMap<Class<?>, Object>();
+public class ServiceLocator {
+    private final List<ClassLoader> classLoaders;
 
-    public ServiceLocator(ClassLoader classLoader) {
-        this.classLoader = classLoader;
+    public ServiceLocator(ClassLoader... classLoaders) {
+        this.classLoaders = Arrays.asList(classLoaders);
     }
 
-    public <T> T doGet(Class<T> serviceType) throws UnknownServiceException {
-        synchronized (implementations) {
-            T implementation = serviceType.cast(implementations.get(serviceType));
-            if (implementation == null) {
-                implementation = getFactory(serviceType).create();
-                implementations.put(serviceType, implementation);
-            }
-            return implementation;
+    public <T> T get(Class<T> serviceType) throws UnknownServiceException {
+        return getFactory(serviceType).create();
+    }
+
+    public <T> List<T> getAll(Class<T> serviceType) throws UnknownServiceException {
+        List<ServiceFactory<T>> factories = findFactoriesForServiceType(serviceType);
+        ArrayList<T> services = new ArrayList<T>();
+        for (ServiceFactory<T> factory : factories) {
+            services.add(factory.create());
         }
+        return services;
     }
 
     public <T> ServiceFactory<T> getFactory(final Class<T> serviceType) throws UnknownServiceException {
@@ -62,55 +62,81 @@ public class ServiceLocator extends AbstractServiceRegistry {
      * Locates a factory for a given service. Returns null when no service implementation is available.
      */
     public <T> ServiceFactory<T> findFactory(Class<T> serviceType) {
-        Class<? extends T> implementationClass = findServiceImplementationClass(serviceType);
-        if (implementationClass == null) {
+        List<ServiceFactory<T>> factories = findFactoriesForServiceType(serviceType);
+        if (factories.isEmpty()) {
             return null;
         }
-        return new ServiceFactory<T>(serviceType, implementationClass);
+        return factories.get(0);
     }
 
-    <T> Class<? extends T> findServiceImplementationClass(Class<T> serviceType) {
-        String implementationClassName;
+    private <T> List<ServiceFactory<T>> findFactoriesForServiceType(Class<T> serviceType) {
+        List<Class<? extends T>> implementationClasses;
         try {
-            implementationClassName = findServiceImplementationClassName(serviceType);
+            implementationClasses = findServiceImplementations(serviceType);
+        } catch (ServiceLookupException e) {
+            throw e;
         } catch (Exception e) {
-            throw new ServiceLookupException(String.format("Could not determine implementation class for service '%s'.", serviceType.getName()), e);
+            throw new ServiceLookupException(String.format("Could not determine implementation classes for service '%s'.", serviceType.getName()), e);
         }
-        if (implementationClassName == null) {
-            return null;
-        }
-        try {
-            Class<?> implClass = classLoader.loadClass(implementationClassName);
-            if (!serviceType.isAssignableFrom(implClass)) {
-                throw new RuntimeException(String.format("Implementation class '%s' is not assignable to service class '%s'.", implementationClassName, serviceType.getName()));
-            }
-            return implClass.asSubclass(serviceType);
-        } catch (Throwable t) {
-            throw new ServiceLookupException(String.format("Could not load implementation class '%s' for service '%s'.", implementationClassName, serviceType.getName()), t);
+        List<ServiceFactory<T>> factories = new ArrayList<ServiceFactory<T>>();
+        for (Class<? extends T> implementationClass : implementationClasses) {
+            factories.add(new ServiceFactory<T>(serviceType, implementationClass));
         }
+        return factories;
     }
 
-    private String findServiceImplementationClassName(Class<?> serviceType) throws IOException {
+    private <T> List<Class<? extends T>> findServiceImplementations(Class<T> serviceType) throws IOException {
         String resourceName = "META-INF/services/" + serviceType.getName();
-        URL resource = classLoader.getResource(resourceName);
-        if (resource == null) {
-            return null;
+        Set<String> implementationClassNames = new HashSet<String>();
+        List<Class<? extends T>> implementations = new ArrayList<Class<? extends T>>();
+        for (ClassLoader classLoader : classLoaders) {
+            Enumeration<URL> resources = classLoader.getResources(resourceName);
+            while (resources.hasMoreElements()) {
+                URL resource = resources.nextElement();
+                List<String> implementationClassNamesFromResource;
+                try {
+                    implementationClassNamesFromResource = extractImplementationClassNames(resource);
+                    if (implementationClassNamesFromResource.isEmpty()) {
+                        throw new RuntimeException(String.format("No implementation class for service '%s' specified.", serviceType.getName()));
+                    }
+                } catch (Exception e) {
+                    throw new ServiceLookupException(String.format("Could not determine implementation class for service '%s' specified in resource '%s'.", serviceType.getName(), resource), e);
+                }
+
+                for (String implementationClassName : implementationClassNamesFromResource) {
+                    if (implementationClassNames.add(implementationClassName)) {
+                        try {
+                            Class<?> implClass = classLoader.loadClass(implementationClassName);
+                            if (!serviceType.isAssignableFrom(implClass)) {
+                                throw new RuntimeException(String.format("Implementation class '%s' is not assignable to service class '%s'.", implementationClassName, serviceType.getName()));
+                            }
+                            implementations.add(implClass.asSubclass(serviceType));
+                        } catch (Exception e) {
+                            throw new ServiceLookupException(String.format("Could not load implementation class '%s' for service '%s' specified in resource '%s'.", implementationClassName, serviceType.getName(), resource), e);
+                        }
+                    }
+                }
+            }
         }
+        return implementations;
+    }
 
+    private List<String> extractImplementationClassNames(URL resource) throws IOException {
         InputStream inputStream = resource.openStream();
         try {
-            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+            List<String> implemetationClassNames = new ArrayList<String>();
             String line;
             while ((line = reader.readLine()) != null) {
                 line = line.replaceAll("#.*", "").trim();
                 if (line.length() > 0) {
-                    return line;
+                    implemetationClassNames.add(line);
                 }
             }
+            return implemetationClassNames;
         } finally {
             inputStream.close();
         }
-        throw new RuntimeException(String.format("No implementation class for service '%s' specified in resource '%s'.", serviceType.getName(), resource));
     }
 
     public static class ServiceFactory<T> implements Factory<T> {
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistration.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistration.java
new file mode 100644
index 0000000..8b5abb8
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistration.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Allows services to be added to a registry.
+ */
+public interface ServiceRegistration {
+    /**
+     * Adds a service to this registry. The given object is closed when the associated registry is closed.
+     * @param serviceType The type to make this service visible as.
+     * @param serviceInstance The service implementation.
+     */
+    <T> void add(Class<T> serviceType, T serviceInstance);
+
+    /**
+     * Adds a service to this registry.
+     *
+     * Note: currently no dependencies are injected into the implementation.
+     *
+     * @param serviceType The service implementation to make visible. This class should have a public no-args constructor.
+     */
+    void add(Class<?> serviceType);
+
+    /**
+     * Adds a service provider bean to this registry. This provider may define factory and decorator methods. See {@link DefaultServiceRegistry} for details.
+     */
+    void addProvider(Object provider);
+}
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 350f1af..b0bd768 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
@@ -18,6 +18,7 @@ package org.gradle.internal.service;
 import org.gradle.internal.Factory;
 
 import java.lang.reflect.Type;
+import java.util.List;
 
 /**
  * A registry of services.
@@ -35,6 +36,15 @@ public interface ServiceRegistry {
     <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException;
 
     /**
+     * Locates all services of the given type.
+     *
+     * @param serviceType The service type.
+     * @param <T>         The service type.
+     * @throws ServiceLookupException On failure to lookup the specified service.
+     */
+    <T> List<T> getAll(Class<T> serviceType) throws ServiceLookupException;
+
+    /**
      * Locates the service of the given type.
      *
      * @param serviceType The service type.
@@ -65,4 +75,14 @@ public interface ServiceRegistry {
      * @throws ServiceLookupException On failure to lookup the specified service factory.
      */
     <T> T newInstance(Class<T> type) throws UnknownServiceException, ServiceLookupException;
+
+    /**
+     * Closes all services for this registry.
+     *
+     * For each service, if the service has a public void close() or stop() method, that method is called to close the service.
+     */
+    void close();
+
+    boolean isClosed();
+
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistryBuilder.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistryBuilder.java
new file mode 100644
index 0000000..7dcf7e7
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistryBuilder.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.ArrayList;
+import java.util.List;
+
+public class ServiceRegistryBuilder {
+    private final List<ServiceRegistry> parents = new ArrayList<ServiceRegistry>();
+    private final List<Object> providers = new ArrayList<Object>();
+    private String displayName;
+
+    private ServiceRegistryBuilder() {
+    }
+
+    public static ServiceRegistryBuilder builder() {
+        return new ServiceRegistryBuilder();
+    }
+
+    public ServiceRegistryBuilder displayName(String displayName) {
+        this.displayName = displayName;
+        return this;
+    }
+
+    public ServiceRegistryBuilder parent(ServiceRegistry parent) {
+        this.parents.add(parent);
+        return this;
+    }
+
+    public ServiceRegistryBuilder provider(Object provider) {
+        this.providers.add(provider);
+        return this;
+    }
+
+    public ServiceRegistry build() {
+        DefaultServiceRegistry registry = new DefaultServiceRegistry(displayName, parents);
+        for (Object provider : providers) {
+            registry.addProvider(provider);
+        }
+        return registry;
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceValidationException.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceValidationException.java
new file mode 100644
index 0000000..3f5bf29
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceValidationException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Thrown when there is some validation problem with a service.
+ */
+public class ServiceValidationException extends ServiceLookupException {
+    public ServiceValidationException(String message) {
+        super(message);
+    }
+}
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
deleted file mode 100644
index 868ba39..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java
+++ /dev/null
@@ -1,78 +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.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
- */
-public class SynchronizedServiceRegistry implements ServiceRegistry {
-    private final Synchronizer synchronizer = new Synchronizer();
-    private final ServiceRegistry delegate;
-
-    public SynchronizedServiceRegistry(ServiceRegistry delegate) {
-        this.delegate = delegate;
-    }
-
-    public <T> T get(final Class<T> serviceType) throws UnknownServiceException {
-        return synchronizer.synchronize(new Factory<T>() {
-            public T create() {
-                return delegate.get(serviceType);
-            }
-        });
-    }
-
-    public 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() {
-                return new SynchronizedFactory<T>(delegate.getFactory(type));
-            }
-        });
-    }
-
-    public <T> T newInstance(final Class<T> type) throws UnknownServiceException {
-        return synchronizer.synchronize(new Factory<T>() {
-            public T create() {
-                return delegate.newInstance(type);
-            }
-        });
-    }
-
-    private class SynchronizedFactory<T> implements Factory<T> {
-        private final Factory<T> delegateFactory;
-
-        public SynchronizedFactory(Factory<T> delegateFactory) {
-            this.delegateFactory = delegateFactory;
-        }
-
-        public T create() {
-            return synchronizer.synchronize(delegateFactory);
-        }
-    }
-}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java
index 8904621..95a54c6 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java
@@ -15,15 +15,17 @@
  */
 package org.gradle.internal.service;
 
+import java.lang.reflect.Type;
+
 public class UnknownServiceException extends IllegalArgumentException {
-    private final Class<?> type;
+    private final Type type;
 
-    public UnknownServiceException(Class<?> type, String message) {
+    public UnknownServiceException(Type type, String message) {
         super(message);
         this.type = type;
     }
 
-    public Class<?> getType() {
+    public Type getType() {
         return type;
     }
 }
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
index 3b790f3..e990673 100644
--- a/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
+++ b/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
@@ -17,13 +17,13 @@ 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 org.gradle.internal.Transformers;
 
 import java.lang.reflect.Array;
 import java.util.*;
 
-import static org.gradle.api.internal.Cast.cast;
+import static org.gradle.internal.Cast.cast;
 
 public abstract class CollectionUtils {
 
@@ -37,6 +37,24 @@ public abstract class CollectionUtils {
         return null;
     }
 
+    public static <T> T findFirst(T[] source, Spec<? super T> filter) {
+        for (T thing : source) {
+            if (filter.isSatisfiedBy(thing)) {
+                return thing;
+            }
+        }
+
+        return null;
+    }
+
+    public static <T> boolean any(Iterable<? extends T> source, Spec<? super T> filter) {
+        return findFirst(source, filter) != null;
+    }
+
+    public static <T> boolean any(T[] source, Spec<? super T> filter) {
+        return findFirst(source, filter) != null;
+    }
+
     public static <T> Set<T> filter(Set<? extends T> set, Spec<? super T> filter) {
         return filter(set, new LinkedHashSet<T>(), filter);
     }
@@ -45,18 +63,24 @@ public abstract class CollectionUtils {
         return filter(list, new LinkedList<T>(), filter);
     }
 
+    /**
+     * Returns a sorted copy of the provided collection of things. Uses the provided comparator to sort.
+     */
     public static <T> List<T> sort(Iterable<? extends T> things, Comparator<? super T> comparator) {
-        List<T> copy;
-        if (things instanceof Collection) {
-            //noinspection unchecked
-            copy = new ArrayList<T>((Collection<? extends T>) things);
-        } else {
-            copy = toList(things);
-        }
+        List<T> copy = toMutableList(things);
         Collections.sort(copy, comparator);
         return copy;
     }
 
+    /**
+     * Returns a sorted copy of the provided collection of things. Uses the natural ordering of the things.
+     */
+    public static <T extends Comparable> List<T> sort(Iterable<T> things) {
+        List<T> copy = toMutableList(things);
+        Collections.sort(copy);
+        return copy;
+    }
+
     public static <T, C extends Collection<T>> C filter(Iterable<? extends T> source, C destination, Spec<? super T> filter) {
         for (T item : source) {
             if (filter.isSatisfiedBy(item)) {
@@ -81,7 +105,8 @@ public abstract class CollectionUtils {
     }
 
     public static <R, I> R[] collectArray(I[] list, Class<R> newType, Transformer<? extends R, ? super I> transformer) {
-        return collectArray(list, (R[]) Array.newInstance(newType, list.length), transformer);
+        @SuppressWarnings("unchecked") R[] destination = (R[]) Array.newInstance(newType, list.length);
+        return collectArray(list, destination, transformer);
     }
 
     public static <R, I> R[] collectArray(I[] list, R[] destination, Transformer<? extends R, ? super I> transformer) {
@@ -104,6 +129,10 @@ public abstract class CollectionUtils {
         return collect(set, new HashSet<R>(), transformer);
     }
 
+    public static <R, I> List<R> collect(Iterable<? extends I> source, Transformer<? extends R, ? super I> transformer) {
+        return collect(source, new LinkedList<R>(), transformer);
+    }
+
     public static <R, I, C extends Collection<R>> C collect(Iterable<? extends I> source, C destination, Transformer<? extends R, ? super I> transformer) {
         for (I item : source) {
             destination.add(transformer.transform(item));
@@ -123,8 +152,8 @@ public abstract class CollectionUtils {
      * @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);
+    public static List<?> flattenCollections(Object... things) {
+        return flattenCollections(Object.class, things);
     }
 
     /**
@@ -138,7 +167,7 @@ public abstract class CollectionUtils {
      * @param <T> The target type in the flattened list
      * @return A flattened list of the given things
      */
-    public static <T> List<T> flattenToList(Class<T> type, Object... things) {
+    public static <T> List<T> flattenCollections(Class<T> type, Object... things) {
         if (things == null) {
             return Collections.singletonList(null);
         } else if (things.length == 0) {
@@ -150,20 +179,23 @@ public abstract class CollectionUtils {
                 return Collections.singletonList(null);
             }
 
+            // Casts to Class below are to workaround Eclipse compiler bug
+            // See: https://github.com/gradle/gradle/pull/200
+
             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));
+                    list.addAll(flattenCollections((Class) type, thingThing));
                 }
                 return list;
             }
 
-            if (thing instanceof Iterable) {
-                Iterable<?> iterableThing = (Iterable<?>) thing;
+            if (thing instanceof Collection) {
+                Collection<?> collection = (Collection<?>) thing;
                 List<T> list = new ArrayList<T>();
-                for (Object thingThing : iterableThing) {
-                    list.addAll(flattenToList(type, thingThing));
+                for (Object element : collection) {
+                    list.addAll(flattenCollections((Class) type, element));
                 }
                 return list;
             }
@@ -172,7 +204,7 @@ public abstract class CollectionUtils {
         } else {
             List<T> list = new ArrayList<T>();
             for (Object thing : things) {
-                list.addAll(flattenToList(type, thing));
+                list.addAll(flattenCollections((Class) type, thing));
             }
             return list;
         }
@@ -183,9 +215,23 @@ public abstract class CollectionUtils {
             return new ArrayList<T>(0);
         }
         if (things instanceof List) {
-            return (List<T>) things;
+            @SuppressWarnings("unchecked") List<T> castThings = (List<T>) things;
+            return castThings;
         }
+        if (things instanceof Collection) {
+            return new ArrayList<T>((Collection) things);
+        }
+        List<T> list = new ArrayList<T>();
+        for (T thing : things) {
+            list.add(thing);
+        }
+        return list;
+    }
 
+    private static <T> List<T> toMutableList(Iterable<? extends T> things) {
+        if (things == null) {
+            return new ArrayList<T>(0);
+        }
         List<T> list = new ArrayList<T>();
         for (T thing : things) {
             list.add(thing);
@@ -193,12 +239,41 @@ public abstract class CollectionUtils {
         return list;
     }
 
+
+    public static <T> List<T> intersection(Collection<? extends Collection<T>> availableValuesByDescriptor) {
+        List<T> result = new ArrayList<T>();
+        Iterator<? extends Collection<T>> iterator = availableValuesByDescriptor.iterator();
+        if (iterator.hasNext()) {
+            Collection<T> firstSet = iterator.next();
+            result.addAll(firstSet);
+            while (iterator.hasNext()) {
+                Collection<T> next = iterator.next();
+                result.retainAll(next);
+            }
+        }
+        return result;
+
+    }
+
+    public static <T> List<T> toList(T[] things) {
+        if (things == null || things.length == 0) {
+            return new ArrayList<T>(0);
+        }
+
+        List<T> list = new ArrayList<T>(things.length);
+        for (T thing : things) {
+            list.add(thing);
+        }
+        return list;
+    }
+
     public static <T> Set<T> toSet(Iterable<? extends T> things) {
         if (things == null) {
             return new HashSet<T>(0);
         }
         if (things instanceof Set) {
-            return (Set<T>) things;
+            @SuppressWarnings("unchecked") Set<T> castThings = (Set<T>) things;
+            return castThings;
         }
 
         Set<T> set = new LinkedHashSet<T>();
@@ -290,6 +365,21 @@ public abstract class CollectionUtils {
     }
 
     /**
+     * Utility for adding an array 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, 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
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 e8438ac..bd14af4 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
@@ -32,6 +32,7 @@ public class JavaVersionSpec extends Specification {
         JavaVersion.VERSION_1_6.toString() == "1.6"
         JavaVersion.VERSION_1_7.toString() == "1.7"
         JavaVersion.VERSION_1_8.toString() == "1.8"
+        JavaVersion.VERSION_1_9.toString() == "1.9"
     }
 
     def convertsStringToVersion() {
@@ -46,6 +47,8 @@ public class JavaVersionSpec extends Specification {
         JavaVersion.toVersion("6") == JavaVersion.VERSION_1_6
         JavaVersion.toVersion("7") == JavaVersion.VERSION_1_7
         JavaVersion.toVersion("8") == JavaVersion.VERSION_1_8
+        JavaVersion.toVersion("9") == JavaVersion.VERSION_1_9
+        JavaVersion.toVersion("1.9.0-internal") == JavaVersion.VERSION_1_9
     }
 
     def failsToConvertStringToVersionForUnknownVersion() {
@@ -60,7 +63,6 @@ public class JavaVersionSpec extends Specification {
         conversionFails("  ");
 
         conversionFails("1.54");
-        conversionFails("1.9");
         conversionFails("1.10");
         conversionFails("2.0");
         conversionFails("1_4");
@@ -80,6 +82,7 @@ public class JavaVersionSpec extends Specification {
         JavaVersion.toVersion(7) == JavaVersion.VERSION_1_7
         JavaVersion.toVersion(1.7) == JavaVersion.VERSION_1_7
         JavaVersion.toVersion(1.8) == JavaVersion.VERSION_1_8
+        JavaVersion.toVersion(1.9) == JavaVersion.VERSION_1_9
     }
     
     def failsToConvertNumberToVersionForUnknownVersion() {
@@ -118,11 +121,13 @@ public class JavaVersionSpec extends Specification {
         JavaVersion.current().java5
         !JavaVersion.current().java6
         !JavaVersion.current().java7
+        !JavaVersion.current().java8
 
         and:
         JavaVersion.current().java5Compatible
         !JavaVersion.current().java6Compatible
         !JavaVersion.current().java7Compatible
+        !JavaVersion.current().java8Compatible
     }
 
     def "uses system property to determine if compatible with Java 6"() {
@@ -132,11 +137,13 @@ public class JavaVersionSpec extends Specification {
         !JavaVersion.current().java5
         JavaVersion.current().java6
         !JavaVersion.current().java7
+        !JavaVersion.current().java8
 
         and:
         JavaVersion.current().java5Compatible
         JavaVersion.current().java6Compatible
         !JavaVersion.current().java7Compatible
+        !JavaVersion.current().java8Compatible
     }
 
     def "uses system property to determine if compatible with Java 7"() {
@@ -146,11 +153,13 @@ public class JavaVersionSpec extends Specification {
         !JavaVersion.current().java5
         !JavaVersion.current().java6
         JavaVersion.current().java7
+        !JavaVersion.current().java8
 
         and:
         JavaVersion.current().java5Compatible
         JavaVersion.current().java6Compatible
         JavaVersion.current().java7Compatible
+        !JavaVersion.current().java8Compatible
     }
 
     def "uses system property to determine if compatible with Java 8"() {
@@ -168,4 +177,22 @@ public class JavaVersionSpec extends Specification {
         JavaVersion.current().java7Compatible
         JavaVersion.current().java8Compatible
     }
+
+    def "uses system property to determine if compatible with Java 9"() {
+        System.properties['java.version'] = '1.9'
+
+        expect:
+        !JavaVersion.current().java5
+        !JavaVersion.current().java6
+        !JavaVersion.current().java7
+        !JavaVersion.current().java8
+        JavaVersion.current().java9
+
+        and:
+        JavaVersion.current().java5Compatible
+        JavaVersion.current().java6Compatible
+        JavaVersion.current().java7Compatible
+        JavaVersion.current().java8Compatible
+        JavaVersion.current().java9Compatible
+    }
 }
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
deleted file mode 100644
index 9fb2e97..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
+++ /dev/null
@@ -1,157 +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
-
-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 "composite actions"() {
-        def actions = [Mock(Action), Mock(Action)]
-
-        when:
-        composite(actions).execute("foo")
-
-        then:
-        actions.each { 1 * it.execute("foo") }
-        0 * _._
-    }
-
-    def "composite action equality"() {
-        given:
-        def actions = (1..3).collect { Mock(Action) }
-
-        expect:
-        composite(actions[0], actions[1]) == composite(actions[0], actions[1])
-        composite(actions[0], actions[1]) != composite(actions[1], actions[0])
-        composite(actions[0], actions[1]) != composite(actions[0], actions[2])
-        composite() == composite()
-        composite() != composite(actions[0])
-        composite(actions[0]) != composite()
-    }
-
-    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
deleted file mode 100644
index 049e1d3..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/CastTest.groovy
+++ /dev/null
@@ -1,44 +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
-
-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
deleted file mode 100644
index 01e6a36..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ErroringActionTest.groovy
+++ /dev/null
@@ -1,52 +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
-
-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
deleted file mode 100644
index 889de58..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
+++ /dev/null
@@ -1,92 +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
-
-import org.gradle.api.Action
-import org.gradle.api.UncheckedIOException
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-import static org.gradle.api.internal.IoActions.*
-
-class IoActionsTest extends Specification {
-
-    @Rule TestNameTestDirectoryProvider tmp
-
-    def "can use file action to write to text file"() {
-        given:
-        def file = tmp.file("foo.txt")
-
-        when:
-        createTextFileWriteAction(file, "UTF-8").execute(new Action<Writer>() {
-            void execute(Writer writer) {
-                writer.write("bar")
-            }
-        })
-
-        then:
-        file.text == "bar"
-    }
-
-    def "fails to write to text file when can't create parent dir"() {
-        given:
-        tmp.createFile("base")
-        def file = tmp.file("base/foo.txt")
-        def action = Mock(Action)
-
-        when:
-        writeTextFile(file, "UTF-8", action)
-
-        then:
-        0 * action.execute(_)
-        def e = thrown UncheckedIOException
-        e.cause instanceof IOException
-        e.cause.message.startsWith("Unable to create directory")
-    }
-
-    def "can write text file using specified encoding"() {
-        given:
-        def file = tmp.file("foo.txt")
-        def enc = "utf-8"
-
-        when:
-        writeTextFile(file, enc, new Action() {
-            void execute(writer) {
-                writer.append("bar⌘")
-            }
-        })
-
-        then:
-        file.getText(enc) == "bar⌘"
-    }
-
-    def "can write text file with default encoding"() {
-        given:
-        def file = tmp.file("foo.txt")
-
-        when:
-        writeTextFile(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
deleted file mode 100644
index 6a95706..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/TransformersTest.groovy
+++ /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.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/ActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/ActionsTest.groovy
new file mode 100644
index 0000000..6cb5089
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/ActionsTest.groovy
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal
+
+import org.gradle.api.Action
+import org.gradle.api.Transformer
+import org.gradle.api.specs.Spec
+import spock.lang.Specification
+
+import static 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 "composite actions"() {
+        def actions = [Mock(Action), Mock(Action)]
+
+        when:
+        composite(actions).execute("foo")
+
+        then:
+        actions.each { 1 * it.execute("foo") }
+        0 * _._
+    }
+
+    def "composite action equality"() {
+        given:
+        def actions = (1..3).collect { Mock(Action) }
+
+        expect:
+        composite(actions[0], actions[1]) == composite(actions[0], actions[1])
+        composite(actions[0], actions[1]) != composite(actions[1], actions[0])
+        composite(actions[0], actions[1]) != composite(actions[0], actions[2])
+        composite() == composite()
+        composite() != composite(actions[0])
+        composite(actions[0]) != composite()
+    }
+
+    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/internal/CastTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/CastTest.groovy
new file mode 100644
index 0000000..7df3a8a
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/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.internal
+
+import spock.lang.Specification
+
+import static 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/internal/CompositeStoppableTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/CompositeStoppableTest.groovy
deleted file mode 100644
index 0eb0f80..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/CompositeStoppableTest.groovy
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.internal
-
-import spock.lang.Specification
-
-class CompositeStoppableTest extends Specification {
-    private final CompositeStoppable stoppable = new CompositeStoppable()
-
-    def stopsAllElementsOnStop() {
-        Stoppable a = Mock()
-        Stoppable b = Mock()
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop()
-        1 * b.stop()
-    }
-
-    def stopsAllElementsWhenOneFailsToStop() {
-        Stoppable a = Mock()
-        Stoppable b = Mock()
-        RuntimeException failure = new RuntimeException()
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop() >> { throw failure }
-        1 * b.stop()
-        def e = thrown(RuntimeException)
-        e == failure
-    }
-
-    def stopsAllElementsWhenMultipleFailToStop() {
-        Stoppable a = Mock()
-        Stoppable b = Mock()
-        RuntimeException failure1 = new RuntimeException()
-        RuntimeException failure2 = new RuntimeException()
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop() >> { throw failure1 }
-        1 * b.stop() >> { throw failure2 }
-        def e = thrown(RuntimeException)
-        e == failure1
-    }
-
-    def closesACloseableElement() {
-        Closeable a = Mock()
-        Stoppable b = Mock()
-
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.close()
-        1 * b.stop()
-    }
-
-    def callsACloseMethodOnArbitraryObject() {
-        ClassWithCloseMethod a = Mock()
-
-        stoppable.add(a)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.close()
-    }
-
-    def callsAStopMethodOnArbitraryObject() {
-        ClassWithStopMethod a = Mock()
-
-        stoppable.add(a)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop()
-    }
-
-    def doesNothingWhenArbitraryObjectDoesNotHaveStopOrCloseMethod() {
-        Runnable r = Mock()
-
-        stoppable.add(r)
-
-        when:
-        stoppable.stop()
-
-        then:
-        0 * r._
-    }
-
-    def ignoresNullElements() {
-        Stoppable a = Mock()
-        stoppable.add([null])
-        stoppable.add(a)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop()
-    }
-
-    static class ClassWithCloseMethod {
-        void close() {
-        }
-    }
-
-    static class ClassWithStopMethod {
-        void stop() {
-        }
-    }
-}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/ErroringActionTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/ErroringActionTest.groovy
new file mode 100644
index 0000000..ce76bf6
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/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.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/internal/FileUtilsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/FileUtilsTest.groovy
new file mode 100644
index 0000000..3c445ab
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/FileUtilsTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal
+
+import org.apache.commons.lang.RandomStringUtils
+import org.gradle.api.GradleException
+import spock.lang.Specification
+
+import static FileUtils.toSafeFileName
+import static FileUtils.assertInWindowsPathLengthLimitation
+
+
+class FileUtilsTest extends Specification {
+    def "toSafeFileName encodes unsupported characters"() {
+        expect:
+        toSafeFileName(input) == output
+        where:
+        input         | output
+        'Test_$1-2.3' | 'Test_$1-2.3'
+        'with space'  | 'with#20space'
+        'with #'      | 'with#20#23'
+        'with /'      | 'with#20#2f'
+        'with \\'     | 'with#20#5c'
+        'with / \\ #' | 'with#20#2f#20#5c#20#23'
+    }
+
+    def "assertInWindowsPathLengthLimitation throws exception when path limit exceeded"(){
+        when:
+        File inputFile = new File(RandomStringUtils.randomAlphanumeric(10))
+        then:
+        inputFile == assertInWindowsPathLengthLimitation(inputFile);
+
+        when:
+        inputFile = new File(RandomStringUtils.randomAlphanumeric(261))
+        assertInWindowsPathLengthLimitation(inputFile);
+        then:
+        def e = thrown(GradleException);
+        e.message.contains("exceeds windows path limitation of 260 character.")
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/IoActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/IoActionsTest.groovy
new file mode 100644
index 0000000..6ad390f
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/IoActionsTest.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.internal
+
+import org.gradle.api.Action
+import org.gradle.api.UncheckedIOException
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static IoActions.*
+
+class IoActionsTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider tmp
+
+    def "can use file action to write to text file"() {
+        given:
+        def file = tmp.file("foo.txt")
+
+        when:
+        createTextFileWriteAction(file, "UTF-8").execute(new Action<Writer>() {
+            void execute(Writer writer) {
+                writer.write("bar")
+            }
+        })
+
+        then:
+        file.text == "bar"
+    }
+
+    def "fails to write to text file when can't create parent dir"() {
+        given:
+        tmp.createFile("base")
+        def file = tmp.file("base/foo.txt")
+        def action = Mock(Action)
+
+        when:
+        writeTextFile(file, "UTF-8", action)
+
+        then:
+        0 * action.execute(_)
+        def e = thrown UncheckedIOException
+        e.cause instanceof IOException
+        e.cause.message.startsWith("Unable to create directory")
+    }
+
+    def "can write text file using specified encoding"() {
+        given:
+        def file = tmp.file("foo.txt")
+        def enc = "utf-8"
+
+        when:
+        writeTextFile(file, enc, new Action() {
+            void execute(writer) {
+                writer.append("bar⌘")
+            }
+        })
+
+        then:
+        file.getText(enc) == "bar⌘"
+    }
+
+    def "can write text file with default encoding"() {
+        given:
+        def file = tmp.file("foo.txt")
+
+        when:
+        writeTextFile(file, new Action() {
+            void execute(writer) {
+                writer.append("bar⌘")
+            }
+        })
+
+        then:
+        file.text == "bar⌘"
+    }
+
+    def "closes resource after executing action"() {
+        def action = Mock(Action)
+        def resource = Mock(Closeable)
+
+        when:
+        withResource(resource, action)
+
+        then:
+        1 * action.execute(resource)
+
+        then:
+        1 * resource.close()
+        0 * _._
+    }
+
+    def "closes resource after action fails"() {
+        def action = Mock(Action)
+        def resource = Mock(Closeable)
+        def failure = new RuntimeException()
+
+        when:
+        withResource(resource, action)
+
+        then:
+        RuntimeException e = thrown()
+        e.is(failure)
+
+        then:
+        1 * action.execute(resource) >> { throw failure }
+
+        then:
+        1 * resource.close()
+        0 * _._
+    }
+
+    def "propagates failure to close resource"() {
+        def action = Mock(Action)
+        def resource = Mock(Closeable)
+        def failure = new RuntimeException()
+
+        when:
+        withResource(resource, action)
+
+        then:
+        RuntimeException e = thrown()
+        e.is(failure)
+
+        then:
+        1 * action.execute(resource)
+
+        then:
+        1 * resource.close() >> { throw failure }
+        0 * _._
+    }
+
+    def "discards IOException thrown when closing resource after action fails"() {
+        def action = Mock(Action)
+        def resource = Mock(Closeable)
+        def failure = new RuntimeException()
+
+        when:
+        withResource(resource, action)
+
+        then:
+        RuntimeException e = thrown()
+        e.is(failure)
+
+        then:
+        1 * action.execute(resource) >> { throw failure }
+
+        then:
+        1 * resource.close() >> { throw new IOException("ignore me") }
+        0 * _._
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/LazyIterableTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/LazyIterableTest.groovy
deleted file mode 100644
index 717b5ad..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/LazyIterableTest.groovy
+++ /dev/null
@@ -1,37 +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
-
-import spock.lang.Specification
-
-class LazyIterableTest extends Specification {
-
-    def "laziness"() {
-        given:
-        def l = [1]
-        def i = new LazyIterable({ l } as Factory)
-
-        expect:
-        i.collect() == [1]
-
-        when:
-        l << 2
-
-        then:
-        i.collect() == [1, 2]
-    }
-}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/SuppliersTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/SuppliersTest.groovy
new file mode 100644
index 0000000..b852d03
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/SuppliersTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal
+
+import org.gradle.api.Transformer
+import spock.lang.Specification
+
+class SuppliersTest extends Specification {
+
+    def "closeable is closed on success"() {
+        given:
+        def closeable = Mock(Closeable)
+        def supplier = Suppliers.ofQuietlyClosed(Factories.constant(closeable))
+
+        when:
+        def result = supplier.supplyTo({ 1 } as Transformer)
+
+        then:
+        result == 1
+        1 * closeable.close()
+    }
+
+    def "closeable is closed on failure"() {
+        given:
+        def closeable = Mock(Closeable)
+        def supplier = Suppliers.ofQuietlyClosed(Factories.constant(closeable))
+
+        when:
+        supplier.supplyTo({ throw new IllegalStateException("!!") } as Transformer)
+
+        then:
+        def e = thrown IllegalStateException
+        e.message == "!!"
+        1 * closeable.close()
+    }
+
+    def "exceptions thrown during close are ignored"() {
+        given:
+        def closeable = Mock(Closeable)
+        def supplier = Suppliers.ofQuietlyClosed(Factories.constant(closeable))
+        1 * closeable.close() >> { throw new IllegalStateException("on close") }
+
+        when:
+        supplier.supplyTo({ throw new IllegalStateException("!!") } as Transformer)
+
+        then:
+        def e = thrown IllegalStateException
+        e.message == "!!"
+    }
+
+    def "suppliers can transformed"() {
+        when:
+        def original = Suppliers.of(Factories.constant(1))
+        def wrapped = Suppliers.wrap(original, new Transformer<Integer, Integer>() {
+            Integer transform(Integer integer) {
+                integer * 2
+            }
+        })
+
+        then:
+        4 == wrapped.supplyTo(new Transformer<Integer, Integer>() {
+            Integer transform(Integer integer) {
+                integer * 2
+            }
+        })
+    }
+
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/TransformersTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/TransformersTest.groovy
new file mode 100644
index 0000000..4fafb05
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/TransformersTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal
+
+import org.gradle.api.Action
+import org.gradle.api.Named
+import org.gradle.api.Namer
+import spock.lang.Specification
+
+import static 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 "to URL"() {
+        expect:
+        toURL().transform(new URI("http://localhost:80/path")) == new URL("http://localhost:80/path")
+    }
+
+    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"
+    }
+
+    def "by type"() {
+        expect:
+        type().transform("foo") == String
+    }
+
+    def "action as transformer"() {
+        def action = Mock(Action)
+
+        when:
+        def result = toTransformer(action).transform("original")
+
+        then:
+        1 * action.execute("original")
+
+        and:
+        result == null
+    }
+
+    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/classloader/CachingClassLoaderTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/CachingClassLoaderTest.groovy
new file mode 100644
index 0000000..53991c6
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/CachingClassLoaderTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader
+
+import spock.lang.Specification
+
+class CachingClassLoaderTest extends Specification {
+    final parent = Mock(ClassLoader, useObjenesis: false)
+    final classLoader = new CachingClassLoader(parent)
+
+    def "loads class once and caches result"() {
+        when:
+        def cl = classLoader.loadClass("someClass")
+
+        then:
+        cl == String.class
+
+        and:
+        1 * parent.loadClass("someClass", false) >> String.class
+        0 * parent._
+
+        when:
+        cl = classLoader.loadClass("someClass")
+
+        then:
+        cl == String.class
+
+        and:
+        0 * parent._
+    }
+
+    def "caches missing classes"() {
+        when:
+        classLoader.loadClass("someClass")
+
+        then:
+        thrown(ClassNotFoundException)
+
+        and:
+        1 * parent.loadClass("someClass", false) >> { throw new ClassNotFoundException("broken") }
+        0 * parent._
+
+        when:
+        classLoader.loadClass("someClass")
+
+        then:
+        thrown(ClassNotFoundException)
+
+        and:
+        0 * parent._
+    }
+
+    def "visits self and parent"() {
+        def visitor = Mock(ClassLoaderVisitor)
+
+        when:
+        classLoader.visit(visitor)
+
+        then:
+        1 * visitor.visitSpec({it instanceof CachingClassLoader.Spec})
+        1 * visitor.visitParent(parent)
+        0 * visitor._
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/DefaultClassLoaderFactoryTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/DefaultClassLoaderFactoryTest.groovy
new file mode 100644
index 0000000..13248fa
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/DefaultClassLoaderFactoryTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.classloader
+
+import spock.lang.Specification
+
+class DefaultClassLoaderFactoryTest extends Specification {
+    final DefaultClassLoaderFactory factory = new DefaultClassLoaderFactory()
+    ClassLoader original
+
+    def setup() {
+        original = Thread.currentThread().contextClassLoader
+    }
+
+    def cleanup() {
+        Thread.currentThread().contextClassLoader = original
+    }
+
+    def "classes from specified URLs are visible in isolated ClassLoader"() {
+        when:
+        def cl = factory.createIsolatedClassLoader(classpath)
+        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
+
+        then:
+        c.name == DefaultClassLoaderFactoryTestHelper.name
+        c != DefaultClassLoaderFactoryTestHelper
+    }
+
+    def "application classes are not visible in isolated ClassLoader"() {
+        when:
+        def cl = factory.createIsolatedClassLoader(classpath)
+        cl.loadClass(Closure.name)
+
+        then:
+        thrown(ClassNotFoundException)
+    }
+
+    def "can use XML APIs from isolated ClassLoader when application classes include an XML provider"() {
+        assert ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory")
+
+        when:
+        def cl = factory.createIsolatedClassLoader(classpath)
+        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
+
+        then:
+        c != DefaultClassLoaderFactoryTestHelper
+
+        when:
+        Thread.currentThread().contextClassLoader = cl
+        c.newInstance().doStuff()
+
+        then:
+        notThrown()
+    }
+
+    def "can use XML APIs from filtering ClassLoader when application classes include an XML provider"() {
+        assert ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory")
+
+        when:
+        def cl = new URLClassLoader(classpath.collect { it.toURL() } as URL[], factory.createFilteringClassLoader(getClass().classLoader))
+        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
+
+        then:
+        c != DefaultClassLoaderFactoryTestHelper
+
+        when:
+        Thread.currentThread().contextClassLoader = cl
+        c.newInstance().doStuff()
+
+        then:
+        notThrown()
+    }
+
+    def getClasspath() {
+        return [ClasspathUtil.getClasspathForClass(DefaultClassLoaderFactoryTestHelper)].collect { it.toURI() }
+    }
+}
+
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/DefaultClassLoaderFactoryTestHelper.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/DefaultClassLoaderFactoryTestHelper.java
new file mode 100644
index 0000000..d1d4ee0
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/DefaultClassLoaderFactoryTestHelper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.classloader;
+
+import javax.xml.XMLConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.xpath.XPathFactory;
+
+public class DefaultClassLoaderFactoryTestHelper {
+    public void doStuff() throws Exception {
+        SAXParserFactory.newInstance().newSAXParser();
+        DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        DatatypeFactory.newInstance().newXMLGregorianCalendar();
+        TransformerFactory.newInstance().newTransformer();
+        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        XPathFactory.newInstance().newXPath();
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/FilteringClassLoaderTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/FilteringClassLoaderTest.groovy
new file mode 100644
index 0000000..92fa31b
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/FilteringClassLoaderTest.groovy
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runners.BlockJUnit4ClassRunner
+import spock.lang.Specification
+
+import static org.junit.Assert.fail
+
+class FilteringClassLoaderTest extends Specification {
+    private final FilteringClassLoader classLoader = new FilteringClassLoader(FilteringClassLoaderTest.class.getClassLoader())
+
+    void passesThroughSystemClasses() {
+        expect:
+        canLoadClass(String)
+    }
+
+    void passesThroughSystemPackages() {
+        expect:
+        canSeePackage('java.lang')
+    }
+
+    void passesThroughSystemResources() {
+        expect:
+        canSeeResource('com/sun/jndi/ldap/jndiprovider.properties')
+    }
+
+    void filtersClassesByDefault() {
+        given:
+        classLoader.parent.loadClass(Test.class.name)
+
+        when:
+        classLoader.loadClass(Test.class.name, false)
+
+        then:
+        ClassNotFoundException e = thrown()
+        e.message == "$Test.name not found."
+
+        when:
+        classLoader.loadClass(Test.class.name)
+
+        then:
+        ClassNotFoundException e2 = thrown()
+        e2.message == "$Test.name not found."
+    }
+
+    void filtersPackagesByDefault() {
+        given:
+        assert classLoader.parent.getPackage('org.junit') != null
+
+        expect:
+        cannotSeePackage('org.junit')
+    }
+
+    void filtersResourcesByDefault() {
+        given:
+        assert classLoader.parent.getResource('org/gradle/util/ClassLoaderTest.txt') != null
+
+        expect:
+        cannotSeeResource('org/gradle/util/ClassLoaderTest.txt')
+    }
+
+    void passesThroughClassesInSpecifiedPackagesAndSubPackages() {
+        given:
+        cannotLoadClass(Test)
+        cannotLoadClass(BlockJUnit4ClassRunner)
+
+        and:
+        classLoader.allowPackage('org.junit')
+
+        expect:
+        canLoadClass(Test)
+        canLoadClass(Before)
+        canLoadClass(BlockJUnit4ClassRunner)
+    }
+
+    void passesThroughSpecifiedClasses() {
+        given:
+        cannotLoadClass(Test)
+
+        and:
+        classLoader.allowClass(Test.class)
+
+        expect:
+        canLoadClass(Test)
+        cannotLoadClass(Before)
+    }
+
+    void filtersSpecifiedClasses() {
+        given:
+        cannotLoadClass(Test)
+        cannotLoadClass(Before)
+
+        and:
+        classLoader.allowPackage("org.junit")
+        classLoader.disallowClass("org.junit.Test")
+
+        expect:
+        canLoadClass(Before)
+        cannotLoadClass(Test)
+    }
+
+    void disallowClassWinsOverAllowClass() {
+        given:
+        classLoader.allowClass(Test)
+        classLoader.disallowClass(Test.name)
+
+        expect:
+        cannotLoadClass(Test)
+    }
+
+    void passesThroughSpecifiedPackagesAndSubPackages() {
+        given:
+        cannotSeePackage('org.junit')
+        cannotSeePackage('org.junit.runner')
+
+        and:
+        classLoader.allowPackage('org.junit')
+
+        expect:
+        canSeePackage('org.junit')
+        canSeePackage('org.junit.runner')
+    }
+
+    void passesThroughResourcesInSpecifiedPackages() {
+        given:
+        cannotSeeResource('org/gradle/util/ClassLoaderTest.txt')
+
+        classLoader.allowPackage('org.gradle')
+
+        expect:
+        canSeeResource('org/gradle/util/ClassLoaderTest.txt')
+    }
+
+    void passesThroughResourcesWithSpecifiedPrefix() {
+        given:
+        cannotSeeResource('org/gradle/util/ClassLoaderTest.txt')
+
+        and:
+        classLoader.allowResources('org/gradle')
+
+        expect:
+        canSeeResource('org/gradle/util/ClassLoaderTest.txt')
+    }
+
+    void passesThroughSpecifiedResources() {
+        given:
+        cannotSeeResource('org/gradle/util/ClassLoaderTest.txt')
+
+        and:
+        classLoader.allowResource('org/gradle/util/ClassLoaderTest.txt')
+
+        expect:
+        canSeeResource('org/gradle/util/ClassLoaderTest.txt')
+    }
+
+    void "visits self and parent"() {
+        def visitor = Mock(ClassLoaderVisitor)
+        given:
+        classLoader.allowClass(Test)
+        classLoader.allowPackage("org.junit")
+        classLoader.allowResource("a/b/c")
+        classLoader.disallowClass(Before.name)
+
+        when:
+        classLoader.visit(visitor)
+
+        then:
+        1 * visitor.visitSpec({ it instanceof FilteringClassLoader.Spec }) >> { FilteringClassLoader.Spec spec ->
+            spec.classNames == [Test.name]
+            spec.disallowedClassNames == [Before.name]
+            spec.packageNames == ["org.junit"]
+            spec.packagePrefixes == ["org.junit."]
+            spec.resourceNames == ["a/b/c"]
+            spec.resourcePrefixes == ["org/junit/"]
+        }
+        1 * visitor.visitParent(classLoader.parent)
+        0 * visitor._
+    }
+
+    void cannotSeeResource(String name) {
+        assert classLoader.getResource(name) == null
+        assert classLoader.getResourceAsStream(name) == null
+        assert !classLoader.getResources(name).hasMoreElements()
+    }
+
+    void canSeeResource(String name) {
+        assert classLoader.getResource(name) != null
+        def instr = classLoader.getResourceAsStream(name)
+        assert instr != null
+        instr.close()
+        assert classLoader.getResources(name).hasMoreElements()
+    }
+
+    void canSeePackage(String name) {
+        assert classLoader.getPackage(name) != null
+        assert classLoader.packages.any { it.name == name }
+    }
+
+    void cannotSeePackage(String name) {
+        assert classLoader.getPackage(name) == null
+        assert !classLoader.packages.any { it.name == name }
+    }
+
+    void canLoadClass(Class<?> clazz) {
+        assert classLoader.loadClass(clazz.name, false).is(clazz)
+        assert classLoader.loadClass(clazz.name).is(clazz)
+    }
+
+    void cannotLoadClass(Class<?> clazz) {
+        try {
+            classLoader.loadClass(clazz.name, false)
+            fail()
+        } catch (ClassNotFoundException expected) {}
+        try {
+            classLoader.loadClass(clazz.name)
+            fail()
+        } catch (ClassNotFoundException expected) {}
+    }
+
+    def "does not attempt to load not allowed class"() {
+        given:
+        def parent = Mock(ClassLoader, useObjenesis: false)
+        def loader = new FilteringClassLoader(parent)
+
+        and:
+        loader.allowPackage("good")
+
+        when:
+        loader.loadClass("good.Clazz")
+
+        //noinspection GroovyAccessibility
+        then:
+        1 * parent.loadClass("good.Clazz", false) >> String
+        0 * parent._
+
+        when:
+        loader.loadClass("bad.Clazz")
+
+        then:
+        thrown(ClassNotFoundException)
+
+        and:
+        0 * parent._
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/MultiParentClassLoaderTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/MultiParentClassLoaderTest.groovy
new file mode 100644
index 0000000..0faa891
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/MultiParentClassLoaderTest.groovy
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader
+
+import spock.lang.Specification
+
+class MultiParentClassLoaderTest extends Specification {
+    private ClassLoader parent1 = Mock()
+    private ClassLoader parent2 = Mock()
+    private MultiParentClassLoader loader = new MultiParentClassLoader(parent1, parent2)
+
+    public void parentsAreNotVisibleViaSuperClass() {
+        expect:
+        loader.parent == null
+    }
+
+    public void loadsClassFromParentsInOrderSpecified() {
+        given:
+        _ * parent1.loadClass('string') >> String
+        _ * parent1.loadClass('integer') >> { throw new ClassNotFoundException() }
+        _ * parent2.loadClass('integer') >> Integer
+
+        expect:
+        loader.loadClass('string') == String
+        loader.loadClass('string', true) == String
+        loader.loadClass('integer') == Integer
+        loader.loadClass('integer', true) == Integer
+    }
+
+    public void throwsCNFExceptionWhenClassNotFound() {
+        given:
+        _ * parent1.loadClass('string') >> { throw new ClassNotFoundException() }
+        _ * parent2.loadClass('string') >> { throw new ClassNotFoundException() }
+
+        when:
+        loader.loadClass('string')
+
+        then:
+        ClassNotFoundException e = thrown()
+        e.message == 'string not found.'
+    }
+    
+    public void loadsPackageFromParentsInOrderSpecified() {
+        def stringPackage = String.class.getPackage()
+        def listPackage = List.class.getPackage()
+
+        given:
+        _ * parent1.getPackage('string') >> stringPackage
+        _ * parent1.getPackage('list') >> null
+        _ * parent2.getPackage('list') >> listPackage
+
+        expect:
+        loader.getPackage('string') == stringPackage
+        loader.getPackage('list') == listPackage
+    }
+
+    public void containsUnionOfPackagesFromAllParents() {
+        def package1 = Stub(Package)
+        def package2 = Stub(Package)
+        def package3 = Stub(Package)
+
+        given:
+        _ * parent1.getPackages() >> ([package1] as Package[])
+        _ * parent2.getPackages() >> ([package1, package2, package3] as Package[])
+
+        expect:
+        loader.getPackages() == [package1, package2, package3] as Package[]
+    }
+
+    public void loadsResourceFromParentsInOrderSpecified() {
+        URL resource1 = new File('res1').toURI().toURL()
+        URL resource2 = new File('res2').toURI().toURL()
+
+        given:
+        _ * parent1.getResource('resource1') >> resource1
+        _ * parent1.getResource('resource2') >> null
+        _ * parent2.getResource('resource2') >> resource2
+
+        expect:
+        loader.getResource('resource1') == resource1
+        loader.getResource('resource2') == resource2
+    }
+    
+    public void containsUnionOfResourcesFromAllParents() {
+        URL resource1 = new File('res1').toURI().toURL()
+        URL resource2 = new File('res2').toURI().toURL()
+        URL resource3 = new File('res3').toURI().toURL()
+
+        given:
+        _ * parent1.getResources('resource') >> { return Collections.enumeration([resource1, resource2]) }
+        _ * parent2.getResources('resource') >> { return Collections.enumeration([resource1, resource3]) }
+
+        expect:
+        def resources = loader.getResources('resource').collect { it }
+        resources == [resource1, resource2, resource3]
+    }
+
+    public void visitsSelfAndParents() {
+        def visitor = Mock(ClassLoaderVisitor)
+
+        when:
+        loader.visit(visitor)
+
+        then:
+        1 * visitor.visitSpec({it instanceof MultiParentClassLoader.Spec})
+        1 * visitor.visitParent(parent1)
+        1 * visitor.visitParent(parent2)
+        0 * visitor._
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/MutableURLClassLoaderTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/MutableURLClassLoaderTest.groovy
new file mode 100644
index 0000000..5c04a80
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classloader/MutableURLClassLoaderTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classloader
+
+import spock.lang.Specification
+
+class MutableURLClassLoaderTest extends Specification {
+    def "visits self and parent"() {
+        def visitor = Mock(ClassLoaderVisitor)
+        def parent = new ClassLoader(null) { }
+        def classPath = [new File("a").toURI().toURL(), new File("b").toURI().toURL()]
+        def cl = new MutableURLClassLoader(parent, classPath)
+
+        when:
+        cl.visit(visitor)
+
+        then:
+        1 * visitor.visitSpec({it instanceof MutableURLClassLoader.Spec}) >> { MutableURLClassLoader.Spec spec ->
+            assert spec.classpath == classPath
+        }
+        1 * visitor.visitClassPath(classPath)
+        1 * visitor.visitParent(parent)
+        0 * visitor._
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/CompositeStoppableTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/CompositeStoppableTest.groovy
new file mode 100644
index 0000000..54e2b5a
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/CompositeStoppableTest.groovy
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.concurrent
+
+import spock.lang.Specification
+
+class CompositeStoppableTest extends Specification {
+    private final CompositeStoppable stoppable = new CompositeStoppable()
+
+    def stopsAllElementsOnStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+        1 * b.stop()
+    }
+
+    def stopsAllElementsWhenOneFailsToStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        RuntimeException failure = new RuntimeException()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop() >> { throw failure }
+        1 * b.stop()
+        def e = thrown(RuntimeException)
+        e == failure
+    }
+
+    def stopsAllElementsWhenMultipleFailToStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        RuntimeException failure1 = new RuntimeException()
+        RuntimeException failure2 = new RuntimeException()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop() >> { throw failure1 }
+        1 * b.stop() >> { throw failure2 }
+        def e = thrown(RuntimeException)
+        e == failure1
+    }
+
+    def closesACloseableElement() {
+        Closeable a = Mock()
+        Stoppable b = Mock()
+
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.close()
+        1 * b.stop()
+    }
+
+    def callsACloseMethodOnArbitraryObject() {
+        ClassWithCloseMethod a = Mock()
+
+        stoppable.add(a)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.close()
+    }
+
+    def callsAStopMethodOnArbitraryObject() {
+        ClassWithStopMethod a = Mock()
+
+        stoppable.add(a)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+    }
+
+    def doesNothingWhenArbitraryObjectDoesNotHaveStopOrCloseMethod() {
+        Runnable r = Mock()
+
+        stoppable.add(r)
+
+        when:
+        stoppable.stop()
+
+        then:
+        0 * r._
+    }
+
+    def ignoresNullElements() {
+        Stoppable a = Mock()
+        stoppable.add([null])
+        stoppable.add(a)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+    }
+
+    static class ClassWithCloseMethod {
+        void close() {
+        }
+    }
+
+    static class ClassWithStopMethod {
+        void stop() {
+        }
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactorySpec.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactorySpec.groovy
deleted file mode 100644
index 702a5b5..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactorySpec.groovy
+++ /dev/null
@@ -1,48 +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.concurrent
-
-import spock.lang.Specification
-
-class DefaultExecutorFactorySpec extends Specification {
-
-    def factory = new DefaultExecutorFactory()
-
-    def cleanup() {
-        factory.stop()
-    }
-
-    //TODO if you're looking here, please consider moving more tests from DefaultExecutorFactoryTest
-
-    def stopRethrowsFirstExecutionException() {
-        given:
-        def failure1 = new RuntimeException()
-        def runnable1 = { throw failure1 }
-
-        def failure2 = new RuntimeException()
-        def runnable2 = { Thread.sleep(100); throw failure2 }
-
-        when:
-        def executor = factory.create('')
-        executor.execute(runnable1)
-        executor.execute(runnable2)
-        executor.stop()
-
-        then:
-        def ex = thrown(RuntimeException)
-        ex.is(failure1)
-    }
-}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy
index 4454eb3..0482418 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2012 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,117 +15,130 @@
  */
 package org.gradle.internal.concurrent
 
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.junit.After
-import org.junit.Test
-import org.junit.runner.RunWith
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 
-import java.util.concurrent.ExecutorService
 import java.util.concurrent.TimeUnit
 
- at RunWith(JMock)
-class DefaultExecutorFactoryTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final DefaultExecutorFactory factory = new DefaultExecutorFactory() {
-        def ExecutorService createExecutor(String displayName) {
-            return getExecutor()
-        }
-    }
+class DefaultExecutorFactoryTest extends ConcurrentSpec {
 
-    @After
-    public void tearDown() {
+    def factory = new DefaultExecutorFactory()
+
+    def cleanup() {
         factory.stop()
     }
 
-    @Test
-    public void stopBlocksUntilAllJobsAreComplete() {
-        Runnable runnable = context.mock(Runnable.class)
+    def stopBlocksUntilAllJobsAreComplete() {
+        given:
+        def action1 = {
+            thread.block()
+            instant.completed1
+        }
+        def action2 = {
+            thread.block()
+            instant.completed2
+        }
 
-        context.checking {
-            one(runnable).run()
-            will {
-                syncAt(1)
-            }
+        when:
+        async {
+            def executor = factory.create('test')
+            executor.execute(action1)
+            executor.execute(action2)
+            executor.stop()
+            instant.stopped
         }
 
-        def executor = factory.create('<display-name>')
-        executor.execute(runnable)
+        then:
+        instant.stopped > instant.completed1
+        instant.stopped > instant.completed2
+    }
 
-        run {
-            expectBlocksUntil(1) {
-                executor.stop()
-            }
+    def factoryStopBlocksUntilAllJobsAreComplete() {
+        given:
+        def action1 = {
+            thread.block()
+            instant.completed1
         }
-    }
-    
-    @Test
-    public void factoryStopBlocksUntilAllJobsAreComplete() {
-        Runnable runnable = context.mock(Runnable.class)
-
-        context.checking {
-            one(runnable).run()
-            will {
-                syncAt(1)
-            }
+        def action2 = {
+            thread.block()
+            instant.completed2
         }
 
-        def executor = factory.create('<display-name>')
-        executor.execute(runnable)
-
-        run {
-            expectBlocksUntil(1) {
-                factory.stop()
-            }
+        when:
+        async {
+            factory.create("1").execute(action1)
+            factory.create("2").execute(action2)
+            factory.stop()
+            instant.stopped
         }
+
+        then:
+        instant.stopped > instant.completed1
+        instant.stopped > instant.completed2
     }
 
-    @Test
-    public void stopThrowsExceptionOnTimeout() {
-        Runnable runnable = context.mock(Runnable.class)
+    public void cannotStopExecutorFromAnExecutorThread() {
+        when:
+        def executor = factory.create('<display-name>')
+        def action = {
+            executor.stop()
+        }
+        executor.execute(action)
+        executor.stop()
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot stop this executor from an executor thread.'
+    }
 
-        context.checking {
-            one(runnable).run()
-            will {
-                Thread.sleep(1000)
-            }
+    def stopThrowsExceptionOnTimeout() {
+        def action = {
+            thread.block()
         }
 
+        when:
         def executor = factory.create('<display-name>')
-        executor.execute(runnable)
-
-        expectTimesOut(500, TimeUnit.MILLISECONDS) {
-            try {
-                executor.stop(500, TimeUnit.MILLISECONDS)
-                org.junit.Assert.fail()
-            } catch (IllegalStateException e) {
-                org.junit.Assert.assertThat(e.message, org.hamcrest.Matchers.equalTo('Timeout waiting for concurrent jobs to complete.'))
-            }
+        executor.execute(action)
+        operation.stop {
+            executor.stop(200, TimeUnit.MILLISECONDS)
         }
-    }
 
-    @Test
-    public void cannotStopExecutorFromAnExecutorThread() {
-        Runnable runnable = context.mock(Runnable.class)
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Timeout waiting for concurrent jobs to complete.'
 
-        def executor = factory.create('<display-name>')
+        and:
+        operation.stop.duration in approx(200)
+    }
 
-        context.checking {
-            one(runnable).run()
-            will {
-                executor.stop()
-            }
+    def stopRethrowsFirstExecutionException() {
+        given:
+        def failure1 = new RuntimeException()
+        def runnable1 = {
+            instant.broken1
+            throw failure1
         }
 
-        executor.execute(runnable)
-
-        try {
-            executor.stop()
-            org.junit.Assert.fail()
-        } catch (IllegalStateException e) {
-            org.junit.Assert.assertThat(e.message, org.hamcrest.Matchers.equalTo('Cannot stop this executor from an executor thread.'))
+        def failure2 = new RuntimeException()
+        def runnable2 = {
+            thread.blockUntil.broken1
+            instant.broken2
+            throw failure2
         }
 
+        when:
+        def executor = factory.create('test')
+        executor.execute(runnable1)
+        executor.execute(runnable2)
+        thread.blockUntil.broken2
+
+        then:
+        noExceptionThrown()
+
+        when:
+        executor.stop()
+
+        then:
+        def ex = thrown(RuntimeException)
+        ex.is(failure1)
     }
 }
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/ServiceLifecycleTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/ServiceLifecycleTest.groovy
new file mode 100644
index 0000000..397fc0b
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/ServiceLifecycleTest.groovy
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.concurrent
+
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+
+class ServiceLifecycleTest extends ConcurrentSpec {
+    def lifecycle = new ServiceLifecycle("[service]")
+
+    def "can use service when not stopped"() {
+        def action = Mock(Runnable)
+
+        when:
+        lifecycle.use(action)
+
+        then:
+        1 * action.run()
+        0 * _._
+    }
+
+    def "can use service concurrently from multiple threads"() {
+        given:
+        def action1 = {
+            instant.action1Started
+            thread.blockUntil.action2Done
+            instant.action1Done
+        }
+        def action2 = {
+            thread.blockUntil.action1Started
+            instant.action2Done
+        }
+
+        when:
+        async {
+            start {
+                lifecycle.use(action1)
+            }
+            start {
+                lifecycle.use(action2)
+            }
+        }
+
+        then:
+        instant.action1Done > instant.action2Done
+    }
+
+    def "can stop multiple times"() {
+        when:
+        lifecycle.stop()
+        lifecycle.stop()
+        lifecycle.requestStop()
+        lifecycle.requestStop()
+        lifecycle.stop()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "throws exception when attempting to use service after it has stopped"() {
+        when:
+        lifecycle.stop()
+        lifecycle.use { }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot use [service] as it has been stopped.'
+    }
+
+    def "throws exception when attempting to use service after stop has been requested"() {
+        when:
+        lifecycle.requestStop()
+        lifecycle.use { }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot use [service] as it has been stopped.'
+    }
+
+    def "throws exception when attempting to use service while it is stopping due to request stop"() {
+        when:
+        async {
+            start {
+                lifecycle.use {
+                    lifecycle.requestStop()
+                    instant.stopRequested
+                    thread.block()
+                }
+            }
+            thread.blockUntil.stopRequested
+            lifecycle.use {}
+        }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot use [service] as it is currently stopping.'
+    }
+
+    def "throws exception when attempting to use service while it is stopping"() {
+        when:
+        async {
+            start {
+                thread.blockUntil.running
+                lifecycle.stop()
+            }
+            start {
+                lifecycle.use {
+                    instant.running
+                    thread.blockUntil.failure
+                }
+            }
+            operation.failure {
+                thread.blockUntil.running
+                thread.block()
+                lifecycle.use {}
+            }
+        }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot use [service] as it is currently stopping.'
+    }
+
+    def "stop() blocks while service is in use"() {
+        when:
+        async {
+            start {
+                lifecycle.use {
+                    instant.running
+                    thread.block()
+                    instant.finished
+                }
+            }
+            thread.blockUntil.running
+            lifecycle.stop()
+            instant.stopped
+        }
+
+        then:
+        instant.finished < instant.stopped
+    }
+
+    def "multiple threads can call stop() concurrently"() {
+        expect:
+        async {
+            start {
+                lifecycle.use {
+                    instant.running
+                    thread.block()
+                }
+            }
+            2.times {
+                start {
+                    thread.blockUntil.running
+                    lifecycle.stop()
+                }
+            }
+        }
+    }
+
+    def "requestStop() does not block while service is in use"() {
+        when:
+        async {
+            start {
+                lifecycle.use {
+                    instant.running
+                    thread.blockUntil.requested
+                    instant.finished
+                }
+            }
+            thread.blockUntil.running
+            lifecycle.requestStop()
+            instant.requested
+        }
+
+        then:
+        instant.requested < instant.finished
+    }
+
+    def "cannot call stop() from thread that is using service"() {
+        when:
+        lifecycle.use {
+            lifecycle.stop()
+        }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot stop [service] from a thread that is using it.'
+    }
+
+    def "usage is re-entrant"() {
+        when:
+        async {
+            start {
+                lifecycle.use {
+                    lifecycle.use {
+                        instant.running
+                        thread.block()
+                    }
+                    thread.block()
+                    instant.finished
+                }
+            }
+            thread.blockUntil.running
+            lifecycle.stop()
+            instant.stopped
+        }
+
+        then:
+        instant.finished < instant.stopped
+    }
+
+    def "can call requestStop() from thread that is using service"() {
+        when:
+        async {
+            start {
+                lifecycle.use {
+                    lifecycle.requestStop()
+                    instant.stopRequested
+                    thread.block()
+                    instant.finished
+                }
+            }
+
+            thread.blockUntil.stopRequested
+            lifecycle.stop()
+            instant.stopped
+        }
+
+        then:
+        instant.finished < instant.stopped
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/hash/HashValueTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/hash/HashValueTest.groovy
new file mode 100644
index 0000000..2f05f02
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/hash/HashValueTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.hash
+
+import spock.lang.Specification
+
+class HashValueTest extends Specification {
+    def "parses hash value from input strings"() {
+        expect:
+        def digest = HashValue.parse(inputString)
+        digest.asHexString() == hexString
+
+        where:
+        hexString                                  | inputString
+        "1234"                                     | "1234"
+        "abc123"                                   | "ABC123"
+        "1"                                        | "000000000000001"
+        "123456"                                   | "md5 = 123456"
+        "123456"                                   | "sha1 = 123456"
+        "76be4c7459d7fb64bf638bac7accd9b6df728f2b" | "SHA1 (dummy.gz) = 76be4c7459d7fb64bf638bac7accd9b6df728f2b"
+        "687cab044c8f937b8957166272f1da3c"         | "fontbox-0.8.0-incubating.jar: 68 7C AB 04 4C 8F 93 7B  89 57 16 62 72 F1 DA 3C" // http://repo2.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
+        "f951934aa5ae5a88d7e6dfaa6d32307d834a88be" | "f951934aa5ae5a88d7e6dfaa6d32307d834a88be  /home/maven/repository-staging/to-ibiblio/maven2/commons-collections/commons-collections/3.2/commons-collections-3.2.jar"
+    }
+
+    def "creates compact string representation"() {
+        expect:
+        new HashValue(hexString).asCompactString() == compactString
+
+        where:
+        hexString                          | compactString
+        "1234"                             | "4hk"
+        "abc123"                           | "ang93"
+        "d41d8cd98f00b204e9800998ecf8427e" | "6k3m6dj3o0m82ej009j3mfggju"
+        "FFF"                              | "3vv"
+    }
+
+    def "can roundtrip compact sha1 representation"() {
+        given:
+        def hash = new HashValue("1234")
+        
+        expect:
+        hash.equals(new HashValue(hash.asHexString()))
+    }
+    
+    def "creates short MD5 for string input"() {
+        expect:
+        HashUtil.createCompactMD5("") == "6k3m6dj3o0m82ej009j3mfggju"
+        HashUtil.createCompactMD5("a") == "co5qrjg7hmqk33gsps9kne9j1"
+        HashUtil.createCompactMD5("i") == "46bg60milgs1hubil371u1l1q1"
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
index 91f81b2..8f9ca5b 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
@@ -26,7 +26,11 @@ import spock.lang.Specification
 class JvmTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     @Rule SetSystemProperties sysProp = new SetSystemProperties()
-    OperatingSystem os = Mock()
+    OperatingSystem os = Mock() {
+        getExecutableName(_) >> { String name ->
+            return "${name}.exe"
+        }
+    }
     OperatingSystem theOs = OperatingSystem.current()
 
     Jvm getJvm() {
@@ -48,104 +52,213 @@ class JvmTest extends Specification {
         7       | 5      | 6
     }
 
-    def "looks for runtime Jar in Java home directory"() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        TestFile runtimeJar = javaHomeDir.file('lib/rt.jar').createFile()
-        System.properties['java.home'] = javaHomeDir.absolutePath
-
-        expect:
-        jvm.javaHome == javaHomeDir
-        jvm.runtimeJar == runtimeJar
-    }
+    def "locates JDK and JRE installs when java.home points to a typical JRE installation embedded in a JDK installation"() {
+        given:
+        TestFile software = tmpDir.createDir('software')
+        software.create {
+            jdk {
+                lib {
+                    file 'tools.jar'
+                }
+                bin {
+                    file 'java.exe'
+                    file 'javac.exe'
+                    file 'javadoc.exe'
+                }
+                jre {
+                    lib { file 'rt.jar' }
+                    bin { file 'java.exe' }
+                }
+            }
+        }
 
-    def "looks for tools Jar in Java home directory"() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
-        System.properties['java.home'] = javaHomeDir.absolutePath
+        when:
+        System.properties['java.home'] = software.file('jdk/jre').absolutePath
 
-        expect:
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == toolsJar
+        then:
+        jvm.javaHome == software.file('jdk')
+        jvm.runtimeJar == software.file('jdk/jre/lib/rt.jar')
+        jvm.toolsJar == software.file('jdk/lib/tools.jar')
+        jvm.javaExecutable == software.file('jdk/bin/java.exe')
+        jvm.javacExecutable == software.file('jdk/bin/javac.exe')
+        jvm.javadocExecutable == software.file('jdk/bin/javadoc.exe')
+        jvm.jre.homeDir == software.file('jdk/jre')
+        jvm.standaloneJre == null
     }
 
-    def "provides information when typical jdk installed"() {
+    def "locates JDK and JRE installs when java.home points to a typical JDK installation"() {
         given:
         TestFile software = tmpDir.createDir('software')
         software.create {
             jdk {
-                jre { lib { file 'rt.jar' }}
-                lib { file 'tools.jar'}
+                lib {
+                    file 'tools.jar'
+                }
+                bin {
+                    file 'java.exe'
+                    file 'javac.exe'
+                    file 'javadoc.exe'
+                }
+                jre {
+                    lib { file 'rt.jar' }
+                    bin { file 'java.exe' }
+                }
             }
         }
 
         when:
-        System.properties['java.home'] = software.file('jdk/jre').absolutePath
+        System.properties['java.home'] = software.file('jdk').absolutePath
 
         then:
-        jvm.javaHome.absolutePath == software.file('jdk').absolutePath
+        jvm.javaHome == software.file('jdk')
         jvm.runtimeJar == software.file('jdk/jre/lib/rt.jar')
         jvm.toolsJar == software.file('jdk/lib/tools.jar')
+        jvm.javaExecutable == software.file('jdk/bin/java.exe')
+        jvm.javacExecutable == software.file('jdk/bin/javac.exe')
+        jvm.javadocExecutable == software.file('jdk/bin/javadoc.exe')
+        jvm.jre.homeDir == software.file('jdk/jre')
+        jvm.standaloneJre == null
     }
 
-    def "provides information when typical jre installed"() {
+    def "locates JDK and JRE installs when java.home points to a typical standalone JRE installation"() {
         given:
         TestFile software = tmpDir.createDir('software')
         software.create {
-            jre { lib { file 'rt.jar' }}
+            jre {
+                bin { file 'java.exe' }
+                lib { file 'rt.jar' }
+            }
         }
 
         when:
         System.properties['java.home'] = software.file('jre').absolutePath
 
         then:
-        jvm.javaHome.absolutePath == software.file('jre').absolutePath
+        jvm.javaHome == software.file('jre')
         jvm.runtimeJar == software.file('jre/lib/rt.jar')
         jvm.toolsJar == null
+        jvm.javaExecutable == software.file('jre/bin/java.exe')
+        jvm.javacExecutable == new File('javac.exe')
+        jvm.javadocExecutable == new File('javadoc.exe')
+        jvm.jre.homeDir == software.file('jre')
+        jvm.standaloneJre.homeDir == software.file('jre')
     }
 
-    def "looks for tools Jar in parent of JRE's Java home directory"() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
-        System.properties['java.home'] = javaHomeDir.file('jre').absolutePath
+    def "locates JDK and JRE installs when java.home points to a typical standalone JRE installation on Windows"() {
+        given:
+        TestFile software = tmpDir.createDir('software')
+        software.create {
+            "${jreDirName}" {
+                bin { file 'java.exe' }
+                lib { file 'rt.jar' }
+            }
+            "${jdkDirName}" {
+                bin {
+                    file 'java.exe'
+                    file 'javac.exe'
+                    file 'javadoc.exe'
+                }
+                lib { file 'tools.jar' }
+            }
+        }
+        def jreDir = software.file(jreDirName)
+        def jdkDir = software.file(jdkDirName)
+
+        and:
+        _ * os.windows >> true
 
-        expect:
-        def jvm = new Jvm(os)
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == toolsJar
+        when:
+        System.properties['java.home'] = jreDir.absolutePath
+        System.properties['java.version'] = version
+
+        then:
+        jvm.javaHome == jdkDir
+        jvm.runtimeJar == jreDir.file("lib/rt.jar")
+        jvm.toolsJar == jdkDir.file("lib/tools.jar")
+        jvm.javaExecutable == jdkDir.file('bin/java.exe')
+        jvm.javacExecutable == jdkDir.file('bin/javac.exe')
+        jvm.javadocExecutable == jdkDir.file('bin/javadoc.exe')
+        jvm.jre.homeDir == jreDir
+        jvm.standaloneJre.homeDir == jreDir
+
+        where:
+        version    | jreDirName    | jdkDirName
+        '1.6.0'    | 'jre6'        | 'jdk1.6.0'
+        '1.5.0_22' | 'jre1.5.0_22' | 'jdk1.5.0_22'
     }
 
-    def "looks for tools Jar in sibling of JRE's Java home directory on Windows"() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk1.6.0')
-        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
-        System.properties['java.home'] = tmpDir.createDir('jre6').absolutePath
-        System.properties['java.version'] = '1.6.0'
+    def "locates JDK and JRE installs when java.home points to a typical JDK installation on Windows"() {
+        given:
+        TestFile software = tmpDir.createDir('software')
+        software.create {
+            "${jreDirName}" {
+                bin { file 'java.exe' }
+                lib {
+                    file 'rt.jar'
+                }
+            }
+            "${jdkDirName}" {
+                bin {
+                    file 'java.exe'
+                    file 'javac.exe'
+                    file 'javadoc.exe'
+                }
+                jre {
+                    lib {
+                        file 'rt.jar'
+                    }
+                }
+                lib {
+                    file 'tools.jar'
+                }
+            }
+        }
+        def jreDir = software.file(jreDirName)
+        def jdkDir = software.file(jdkDirName)
+
+        and:
         _ * os.windows >> true
 
-        expect:
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == toolsJar
+        when:
+        System.properties['java.home'] = jdkDir.absolutePath
+        System.properties['java.version'] = version
+
+        then:
+        jvm.javaHome == jdkDir
+        jvm.runtimeJar == jdkDir.file("jre/lib/rt.jar")
+        jvm.toolsJar == jdkDir.file("lib/tools.jar")
+        jvm.javaExecutable == jdkDir.file('bin/java.exe')
+        jvm.javacExecutable == jdkDir.file('bin/javac.exe')
+        jvm.javadocExecutable == jdkDir.file('bin/javadoc.exe')
+        jvm.jre.homeDir == jdkDir.file('jre')
+        jvm.standaloneJre.homeDir == jreDir
+
+        where:
+        version    | jreDirName    | jdkDirName
+        '1.6.0'    | 'jre6'        | 'jdk1.6.0'
+        '1.5.0_22' | 'jre1.5.0_22' | 'jdk1.5.0_22'
     }
 
-    def "uses system property to locate Java home directory when tools Jar not found"() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        System.properties['java.home'] = javaHomeDir.absolutePath
+    def "uses system property to determine if Sun/Oracle JVM"() {
+        when:
+        System.properties['java.vm.vendor'] = 'Sun'
+        def jvm = Jvm.create(null)
 
-        expect:
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == null
+        then:
+        jvm.getClass() == Jvm
     }
 
     def "uses system property to determine if Apple JVM"() {
         when:
         System.properties['java.vm.vendor'] = 'Apple Inc.'
-        def jvm = Jvm.current()
+        def jvm = Jvm.create(null)
 
         then:
         jvm.getClass() == Jvm.AppleJvm
 
         when:
         System.properties['java.vm.vendor'] = 'Sun'
-        jvm = Jvm.current()
+        jvm = Jvm.create(null)
 
         then:
         jvm.getClass() == Jvm
@@ -154,13 +267,13 @@ class JvmTest extends Specification {
     def "uses system property to determine if IBM JVM"() {
         when:
         System.properties['java.vm.vendor'] = 'IBM Corporation'
-        def jvm = Jvm.current()
+        def jvm = Jvm.create(null)
 
         then:
         jvm.getClass() == Jvm.IbmJvm
     }
 
-    def "finds executable if for java home supplied"() {
+    def "finds executable for java home supplied"() {
         System.properties['java.vm.vendor'] = 'Sun'
 
         when:
@@ -176,7 +289,7 @@ class JvmTest extends Specification {
 
         then:
         home.file(theOs.getExecutableName("jre/bin/javadoc")).absolutePath ==
-            Jvm.forHome(home.file("jre")).getExecutable("javadoc").absolutePath
+                Jvm.forHome(home.file("jre")).getExecutable("javadoc").absolutePath
     }
 
     def "finds tools.jar if java home supplied"() {
@@ -193,7 +306,7 @@ class JvmTest extends Specification {
 
         then:
         home.file("jdk/lib/tools.jar").absolutePath ==
-            Jvm.forHome(home.file("jdk")).toolsJar.absolutePath
+                Jvm.forHome(home.file("jdk")).toolsJar.absolutePath
     }
 
     def "provides decent feedback if executable not found"() {
@@ -215,8 +328,7 @@ class JvmTest extends Specification {
         given:
         def home = tmpDir.createDir("home")
         System.properties['java.home'] = home.absolutePath
-        1 * os.getExecutableName(_ as String) >> "foobar.exe"
-        1 * os.findInPath("foobar") >> new File('/path/foobar.exe')
+        _ * os.findInPath("foobar") >> new File('/path/foobar.exe')
 
         when:
         def exec = jvm.getExecutable("foobar")
@@ -259,7 +371,7 @@ class JvmTest extends Specification {
         then:
         thrown(IllegalArgumentException)
     }
-    
+
     def "describes accurately when created for supplied java home"() {
         when:
         def jvm = new Jvm(theOs, new File('dummyFolder'))
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
index 71e41da..ddc6450 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
@@ -61,6 +61,9 @@ class OperatingSystemTest extends Specification {
         os.getScriptName("a.bat") == "a.bat"
         os.getScriptName("a.BAT") == "a.BAT"
         os.getScriptName("a") == "a.bat"
+        os.getScriptName("a.exe") == "a.bat"
+        os.getScriptName("a.b/c") == "a.b/c.bat"
+        os.getScriptName("a.b\\c") == "a.b\\c.bat"
     }
 
     def "windows transforms executable names"() {
@@ -70,6 +73,9 @@ class OperatingSystemTest extends Specification {
         os.getExecutableName("a.exe") == "a.exe"
         os.getExecutableName("a.EXE") == "a.EXE"
         os.getExecutableName("a") == "a.exe"
+        os.getExecutableName("a.bat") == "a.exe"
+        os.getExecutableName("a.b/c") == "a.b/c.exe"
+        os.getExecutableName("a.b\\c") == "a.b\\c.exe"
     }
 
     def "windows transforms shared library names"() {
@@ -79,6 +85,21 @@ class OperatingSystemTest extends Specification {
         os.getSharedLibraryName("a.dll") == "a.dll"
         os.getSharedLibraryName("a.DLL") == "a.DLL"
         os.getSharedLibraryName("a") == "a.dll"
+        os.getSharedLibraryName("a.lib") == "a.dll"
+        os.getSharedLibraryName("a.b/c") == "a.b/c.dll"
+        os.getSharedLibraryName("a.b\\c") == "a.b\\c.dll"
+    }
+
+    def "windows transforms static library names"() {
+        def os = new OperatingSystem.Windows()
+
+        expect:
+        os.getStaticLibraryName("a.lib") == "a.lib"
+        os.getStaticLibraryName("a.LIB") == "a.LIB"
+        os.getStaticLibraryName("a") == "a.lib"
+        os.getStaticLibraryName("a.dll") == "a.lib"
+        os.getStaticLibraryName("a.b/c") == "a.b/c.lib"
+        os.getStaticLibraryName("a.b\\c") == "a.b\\c.lib"
     }
 
     def "windows searches for executable in path"() {
@@ -134,6 +155,13 @@ class OperatingSystemTest extends Specification {
         OperatingSystem.current() instanceof OperatingSystem.Linux
     }
 
+    def "uses os.name property to determine if freebsd"() {
+        System.properties['os.name'] = 'FreeBSD'
+
+        expect:
+        OperatingSystem.current() instanceof OperatingSystem.FreeBSD
+    }
+
     def "uses default implementation for other os"() {
         System.properties['os.name'] = 'unknown'
 
@@ -178,6 +206,18 @@ class OperatingSystemTest extends Specification {
         os.getSharedLibraryName("path/a") == "path/liba.so"
     }
 
+    def "UNIX transforms static library names"() {
+        def os = new OperatingSystem.Unix()
+
+        expect:
+        os.getStaticLibraryName("a.a") == "a.a"
+        os.getStaticLibraryName("liba.a") == "liba.a"
+        os.getStaticLibraryName("a") == "liba.a"
+        os.getStaticLibraryName("lib1") == "liblib1.a"
+        os.getStaticLibraryName("path/liba.a") == "path/liba.a"
+        os.getStaticLibraryName("path/a") == "path/liba.a"
+    }
+
     def "UNIX searches for executable in path"() {
         def exe = tmpDir.createFile("bin/a")
         tmpDir.createFile("bin2/a")
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy
index 94995e5..f05e1df 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy
@@ -14,67 +14,251 @@
  * limitations under the License.
  */
 
-package org.gradle.internal.reflect;
+package org.gradle.internal.reflect
 
-import spock.lang.Specification
+import org.gradle.api.specs.Spec
 import org.gradle.internal.UncheckedException
+import spock.lang.Specification
+
+import java.lang.annotation.Inherited
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+
+import static org.gradle.internal.reflect.JavaReflectionUtil.*
 
 class JavaReflectionUtilTest extends Specification {
-    def myProperties = new MyProperties()
+    JavaTestSubject myProperties = new JavaTestSubject()
+
+    def "property exists"() {
+        expect:
+        propertyExists(new JavaTestSubject(), "myBooleanProperty")
+        propertyExists(new JavaTestSubject(), "myProperty")
+        propertyExists(new JavaTestSubject(), "publicField")
+        !propertyExists(new JavaTestSubject(), "myBooleanProp")
+        !propertyExists(new JavaTestSubject(), "protectedProperty")
+        !propertyExists(new JavaTestSubject(), "privateProperty")
+
+        and:
+        propertyExists(new JavaTestSubjectSubclass(), "myBooleanProperty")
+        propertyExists(new JavaTestSubjectSubclass(), "myProperty")
+        propertyExists(new JavaTestSubjectSubclass(), "publicField")
+        !propertyExists(new JavaTestSubjectSubclass(), "myBooleanProp")
+        !propertyExists(new JavaTestSubjectSubclass(), "protectedProperty")
+        !propertyExists(new JavaTestSubjectSubclass(), "privateProperty")
+
+        and:
+        propertyExists(new JavaTestSubjectSubclass(), "subclassBoolean")
+    }
+
+    def "readable properties"() {
+        expect:
+        def properties = readableProperties(JavaTestSubjectSubclass)
+        properties.size() == 5
+        properties.class
+        properties.myProperty
+        properties.myBooleanProperty
+        properties.myOtherBooleanProperty
+        properties.subclassBoolean
+    }
 
     def "read property"() {
         expect:
-        JavaReflectionUtil.readProperty(myProperties, "myProperty") == "myValue"
+        readableProperty(JavaTestSubject, "myProperty").getValue(myProperties) == "myValue"
     }
 
     def "write property"() {
         when:
-        JavaReflectionUtil.writeProperty(myProperties, "myProperty", "otherValue")
+        writeableProperty(JavaTestSubject, "myProperty").setValue(myProperties, "otherValue")
 
         then:
-        JavaReflectionUtil.readProperty(myProperties, "myProperty") == "otherValue"
+        readableProperty(JavaTestSubject, "myProperty").getValue(myProperties) == "otherValue"
     }
 
     def "read boolean property"() {
         expect:
-        JavaReflectionUtil.readProperty(myProperties, "myBooleanProperty") == true
+        readableProperty(JavaTestSubject, "myBooleanProperty").getValue(myProperties) == true
     }
 
     def "write boolean property"() {
         when:
-        JavaReflectionUtil.writeProperty(myProperties, "myBooleanProperty", false)
+        writeableProperty(JavaTestSubject, "myBooleanProperty").setValue(myProperties, false)
+
+        then:
+        readableProperty(JavaTestSubject, "myBooleanProperty").getValue(myProperties) == false
+    }
+
+    def "cannot read property that doesn't have a well formed getter"() {
+        when:
+        readableProperty(JavaTestSubject, property)
+
+        then:
+        NoSuchPropertyException e = thrown()
+        e.message == "Could not find getter method for property '${property}' on class JavaTestSubject."
+
+        where:
+        property              | _
+        "doesNotExist"        | _
+        "notABooleanProperty" | _
+        "staticProperty"      | _
+        "paramProperty"       | _
+        "voidProperty"        | _
+        "writeOnly"           | _
+    }
+
+    def "cannot read property that is not public"() {
+        when:
+        readableProperty(JavaTestSubject, property)
+
+        then:
+        NoSuchPropertyException e = thrown()
+        e.message == "Could not find getter method for property '${property}' on class JavaTestSubject."
+
+        where:
+        property            | _
+        "privateProperty"   | _
+        "protectedProperty" | _
+    }
+
+    def "cannot write property that doesn't have a well formed setter"() {
+        when:
+        writeableProperty(JavaTestSubject, property)
+
+        then:
+        NoSuchPropertyException e = thrown()
+        e.message == "Could not find setter method for property '${property}' on class JavaTestSubject."
+
+        where:
+        property                 | _
+        "doesNotExist"           | _
+        "myOtherBooleanProperty" | _
+        "staticProperty"         | _
+        "paramProperty"          | _
+    }
+
+    def "cannot write property that is not public"() {
+        when:
+        writeableProperty(JavaTestSubject, property)
 
         then:
-        JavaReflectionUtil.readProperty(myProperties, "myBooleanProperty") == false
+        NoSuchPropertyException e = thrown()
+        e.message == "Could not find setter method for property '${property}' on class JavaTestSubject."
+
+        where:
+        property            | _
+        "privateProperty"   | _
+        "protectedProperty" | _
     }
 
-    def "read property that doesn't exist"() {
+    def "call methods successfully reflectively"() {
+        expect:
+        method(myProperties.class, String, "getMyProperty").invoke(myProperties) == myProperties.myProp
+        method(myProperties.class, String, "doSomeStuff", int.class, Integer.class).invoke(myProperties, 1, 2) == "1.2"
+
         when:
-        JavaReflectionUtil.readProperty(myProperties, "unexisting")
+        method(myProperties.class, Void, "setMyProperty", String).invoke(myProperties, "foo")
 
         then:
-        UncheckedException e = thrown()
-        e.cause instanceof NoSuchMethodException
+        method(myProperties.class, String, "getMyProperty").invoke(myProperties) == "foo"
     }
 
-    def "write property that doesn't exist"() {
+    def "call failing methods reflectively"() {
         when:
-        JavaReflectionUtil.writeProperty(myProperties, "unexisting", "someValue")
+        method(myProperties.class, Void, "throwsException").invoke(myProperties)
 
         then:
-        UncheckedException e = thrown()
-        e.cause instanceof NoSuchMethodException
+        IllegalStateException e = thrown()
+        e == myProperties.failure
+
+        when:
+        method(myProperties.class, Void, "throwsCheckedException").invoke(myProperties)
+
+        then:
+        UncheckedException checkedFailure = thrown()
+        checkedFailure.cause instanceof JavaTestSubject.TestCheckedException
+        checkedFailure.cause.cause == myProperties.failure
+    }
+
+    def "call declared method that may not be public"() {
+        expect:
+        method(JavaTestSubjectSubclass, String, "protectedMethod").invoke(new JavaTestSubjectSubclass()) == "parent"
+        method(JavaTestSubjectSubclass, String, "overridden").invoke(new JavaTestSubjectSubclass()) == "subclass"
+    }
+
+    def "cannot call unknown method"() {
+        when:
+        method(JavaTestSubjectSubclass, String, "unknown")
+
+        then:
+        NoSuchMethodException e = thrown()
+        e.message == /Could not find method unknown() on JavaTestSubjectSubclass./
+    }
+
+    def "find method"() {
+        expect:
+        findMethod(String, { it.name == "toString" } as Spec) == String.declaredMethods.find { it.name == "toString" }
+        findMethod(String, { it.name == "getClass" } as Spec) == Object.declaredMethods.find { it.name == "getClass" }
     }
 
-    static class MyProperties {
-        private String myProp = "myValue"
-        private boolean myBooleanProp = true
+    def "get annotation"() {
+        expect:
+        getAnnotation(Root, InheritedAnnotation).value() == "default"
+        getAnnotation(Subclass, InheritedAnnotation).value() == "default"
+        getAnnotation(RootInterface, InheritedAnnotation).value() == "default"
+        getAnnotation(SubInterface, InheritedAnnotation).value() == "default"
+
+        getAnnotation(Root, NotInheritedAnnotation).value() == "default"
+        getAnnotation(Subclass, NotInheritedAnnotation) == null
+        getAnnotation(RootInterface, NotInheritedAnnotation).value() == "default"
+        getAnnotation(SubInterface, NotInheritedAnnotation) == null
+
+        getAnnotation(ImplementsRootInterface, InheritedAnnotation).value() == "default"
+        getAnnotation(ImplementsRootInterface, NotInheritedAnnotation) == null
+        getAnnotation(ImplementsSubInterface, InheritedAnnotation).value() == "default"
+        getAnnotation(ImplementsSubInterface, NotInheritedAnnotation) == null
+        getAnnotation(ImplementsBoth, InheritedAnnotation).value() == "default"
+        getAnnotation(ImplementsBoth, NotInheritedAnnotation) == null
 
-        String getMyProperty() {  myProp }
-        void setMyProperty(String value) { myProp = value }
+        getAnnotation(OverrideFirst, InheritedAnnotation).value() == "HasAnnotations"
+        getAnnotation(OverrideLast, InheritedAnnotation).value() == "default"
 
-        boolean isMyBooleanProperty() { myBooleanProp }
-        void setMyBooleanProperty(boolean value) { myBooleanProp = value }
+        getAnnotation(InheritsInterface, InheritedAnnotation).value() == "default"
+        getAnnotation(InheritsInterface, NotInheritedAnnotation) == null
     }
+
+}
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Inherited
+ at interface InheritedAnnotation {
+    String value() default "default"
 }
 
+ at Retention(RetentionPolicy.RUNTIME)
+ at interface NotInheritedAnnotation {
+    String value() default "default"
+}
+
+ at InheritedAnnotation
+ at NotInheritedAnnotation
+class Root {}
+
+class Subclass extends Root {}
+
+ at InheritedAnnotation
+ at NotInheritedAnnotation
+interface RootInterface {}
+
+interface SubInterface extends RootInterface {}
+
+class ImplementsRootInterface implements RootInterface {}
+class ImplementsSubInterface implements SubInterface {}
+class ImplementsBoth implements RootInterface, SubInterface {}
+
+ at InheritedAnnotation(value = "HasAnnotations")
+interface HasAnnotations {}
+
+class OverrideFirst implements HasAnnotations, RootInterface, SubInterface {}
+class OverrideLast implements RootInterface, SubInterface, HasAnnotations {}
+
+class SuperWithInterface implements RootInterface {}
+class InheritsInterface extends SuperWithInterface {}
\ No newline at end of file
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaTestSubject.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaTestSubject.java
new file mode 100644
index 0000000..aa3e8dd
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaTestSubject.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+ at SuppressWarnings("UnusedDeclaration")
+public class JavaTestSubject {
+
+    final IllegalStateException failure = new IllegalStateException();
+    private String myProp = "myValue";
+    private boolean myBooleanProp = true;
+
+    public int publicField;
+
+    public String doSomeStuff(int a, Integer b) {
+        return String.format("%s.%s", a, b);
+    }
+
+    public String getMyProperty() {
+        return myProp;
+    }
+
+    public void setMyProperty(String value) {
+        myProp = value;
+    }
+
+    public boolean isMyBooleanProperty() {
+        return myBooleanProp;
+    }
+
+    public void setMyBooleanProperty(boolean value) {
+        myBooleanProp = value;
+    }
+
+    public boolean getMyOtherBooleanProperty() {
+        return true;
+    }
+
+    public String isNotABooleanProperty() {
+        return null;
+    }
+
+    public static String getStaticProperty() {
+        return null;
+    }
+
+    public static void setStaticProperty(String value) {
+    }
+
+    public void getVoidProperty() {
+    }
+
+    public String getParamProperty(String param) {
+        return null;
+    }
+
+    public void setParamProperty() {
+    }
+
+    public void setParamProperty(String param, String someOther) {
+    }
+
+    public void setWriteOnly(String param) {
+    }
+
+    public void throwsException() {
+        throw failure;
+    }
+
+    static class TestCheckedException extends Exception {
+        public TestCheckedException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    public void throwsCheckedException() throws TestCheckedException {
+        throw new TestCheckedException(failure);
+    }
+
+    protected String protectedMethod() {
+        return "parent";
+    }
+
+    protected String overridden() {
+        return "parent";
+    }
+
+    protected String getProtectedProperty() {
+        return null;
+    }
+
+    protected void setProtectedProperty(String value) {
+    }
+
+    private String getPrivateProperty() {
+        return null;
+    }
+
+    private void setPrivateProperty(String value) {
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaTestSubjectSubclass.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaTestSubjectSubclass.java
new file mode 100644
index 0000000..f035d2f
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaTestSubjectSubclass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 JavaTestSubjectSubclass extends JavaTestSubject {
+
+    private String myProp = "subclass";
+
+    @Override
+    protected String overridden() {
+        return "subclass";
+    }
+
+    public boolean getSubclassBoolean() {
+        return false;
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryConcurrencyTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryConcurrencyTest.groovy
new file mode 100644
index 0000000..0916876
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryConcurrencyTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.test.fixtures.concurrent.ConcurrentSpec
+import org.gradle.internal.Factory
+
+class DefaultServiceRegistryConcurrencyTest extends ConcurrentSpec {
+    def "multiple threads can locate services"() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            String createString(Integer value) {
+                return value.toString()
+            }
+
+            Integer createInteger() {
+                return 12
+            }
+
+            Long createLong(BigDecimal value) {
+                return value.longValue()
+            }
+
+            BigDecimal createBigDecimal() {
+                return 123
+            }
+        })
+
+        expect:
+        10.times {
+            start {
+                assert registry.get(String) == "12"
+                assert registry.get(Long) == 123
+            }
+        }
+    }
+
+    def "multiple threads can locate factories"() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            Factory<String> createString(BigDecimal value) {
+                return { value.toString() } as Factory
+            }
+
+            Factory<Integer> createInteger(Long value) {
+                return { 12 } as Factory
+            }
+
+            Long createLong() {
+                return 2L
+            }
+
+            BigDecimal createBigDecimal() {
+                return 123
+            }
+        })
+
+        expect:
+        10.times {
+            start {
+                assert registry.getFactory(Integer).create() == 12
+                assert registry.getFactory(String).create() == "123"
+            }
+        }
+    }
+
+    def "multiple threads can locate all services"() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            String createString(Integer value) {
+                return value.toString()
+            }
+
+            Integer createInteger() {
+                return 12
+            }
+
+            String createOther(BigDecimal value) {
+                return value.toString()
+            }
+
+            BigDecimal createBigDecimal() {
+                return 123
+            }
+        })
+
+        expect:
+        10.times {
+            start {
+                assert registry.getAll(Number).sort() == [12, 123]
+                assert registry.getAll(String).sort() == ["12", "123"]
+            }
+        }
+    }
+
+    def "close blocks while other threads are locating services"() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            String createString() {
+                DefaultServiceRegistryConcurrencyTest.this.instant.constructing
+                DefaultServiceRegistryConcurrencyTest.this.thread.block()
+                DefaultServiceRegistryConcurrencyTest.this.instant.constructed
+                "hi"
+            }
+        })
+
+        when:
+        start {
+            assert registry.get(String) == "hi"
+        }
+        async {
+            thread.blockUntil.constructing
+            registry.close()
+            instant.stopped
+        }
+
+        then:
+        instant.constructed < instant.stopped
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.groovy
new file mode 100644
index 0000000..b72d4f6
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.groovy
@@ -0,0 +1,1239 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api.Action
+import org.gradle.internal.Factory
+import org.gradle.util.TextUtil
+import spock.lang.Specification
+
+import java.lang.reflect.Type
+import java.util.concurrent.Callable
+
+class DefaultServiceRegistryTest extends Specification {
+    def TestRegistry registry = new TestRegistry()
+
+    def throwsExceptionForUnknownService() {
+        when:
+        registry.get(StringBuilder.class)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "No service of type StringBuilder available in TestRegistry."
+    }
+
+    def delegatesToParentForUnknownService() {
+        def value = BigDecimal.TEN
+        def parent = Mock(ServiceRegistry)
+        def registry = new TestRegistry(parent)
+
+        when:
+        def result = registry.get(BigDecimal)
+
+        then:
+        result == value
+
+        and:
+        1 * parent.get(BigDecimal) >> value
+    }
+
+    def delegatesToParentsForUnknownService() {
+        def value = BigDecimal.TEN
+        def parent1 = Mock(ServiceRegistry)
+        def parent2 = Mock(ServiceRegistry)
+        def registry = new DefaultServiceRegistry(parent1, parent2)
+
+        when:
+        def result = registry.get(BigDecimal)
+
+        then:
+        result == value
+
+        and:
+        1 * parent1.get(BigDecimal) >> { throw new UnknownServiceException(BigDecimal, "fail") }
+        1 * parent2.get(BigDecimal) >> value
+    }
+
+    def throwsExceptionForUnknownParentService() {
+        def parent = Mock(ServiceRegistry);
+        def registry = new TestRegistry(parent)
+
+        given:
+        _ * parent.get(StringBuilder) >> { throw new UnknownServiceException(StringBuilder.class, "fail") }
+
+        when:
+        registry.get(StringBuilder)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "No service of type StringBuilder available in TestRegistry."
+    }
+
+    def returnsServiceInstanceThatHasBeenRegistered() {
+        def value = BigDecimal.TEN
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.add(BigDecimal, value)
+
+        expect:
+        registry.get(BigDecimal) == value
+        registry.get(Number) == value
+        registry.get(Object) == value
+    }
+
+    def createsInstanceOfServiceImplementation() {
+        def registry = new DefaultServiceRegistry()
+        registry.register({ ServiceRegistration registration ->
+            registration.add(TestServiceImpl)
+        } as Action)
+
+        expect:
+        registry.get(TestService) instanceof TestServiceImpl
+        registry.get(TestService) == registry.get(TestServiceImpl)
+    }
+
+    def injectsServicesIntoServiceImplementation() {
+        def registry = new DefaultServiceRegistry()
+        registry.register({ ServiceRegistration registration ->
+            registration.add(ServiceWithDependency)
+            registration.add(TestServiceImpl)
+        } as Action)
+
+        expect:
+        registry.get(ServiceWithDependency).service == registry.get(TestServiceImpl)
+    }
+
+    def usesFactoryMethodOnProviderToCreateServiceInstance() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new TestProvider())
+
+        expect:
+        registry.get(Integer) == 12
+        registry.get(Number) == 12
+    }
+
+    def injectsServicesIntoProviderFactoryMethod() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            Integer createInteger() {
+                return 12
+            }
+
+            String createString(Integer integer) {
+                return integer.toString()
+            }
+        })
+
+        expect:
+        registry.get(String) == "12"
+    }
+
+    def injectsGenericTypesIntoProviderFactoryMethod() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            Integer createInteger(Factory<String> factory) {
+                return factory.create().length()
+            }
+
+            Factory<String> createString(Callable<String> action) {
+                return { action.call() } as Factory
+            }
+
+            Callable<String> createAction() {
+                return { "hi" }
+            }
+        })
+
+        expect:
+        registry.get(Integer) == 2
+    }
+
+    def handlesInheritanceInGenericTypes() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new ProviderWithGenericTypes())
+
+        expect:
+        registry.get(Integer) == 123
+    }
+
+    def canHaveMultipleServicesWithParameterizedTypesAndSameRawType() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object() {
+            Integer createInteger(Callable<Integer> factory) {
+                return factory.call()
+            }
+
+            String createString(Callable<String> factory) {
+                return factory.call()
+            }
+
+            Callable<Integer> createIntFactory() {
+                return { 123 }
+            }
+
+            Callable<String> createStringFactory() {
+                return { "hi" }
+            }
+        })
+
+        expect:
+        registry.get(Integer) == 123
+        registry.get(String) == "hi"
+    }
+
+    def injectsParentServicesIntoProviderFactoryMethod() {
+        def parent = Mock(ServiceRegistry)
+        def registry = new DefaultServiceRegistry(parent)
+        registry.addProvider(new Object() {
+            String createString(Number n) {
+                return n.toString()
+            }
+        })
+
+        when:
+        def result = registry.get(String)
+
+        then:
+        result == '123'
+
+        and:
+        1 * parent.get(Number) >> 123
+    }
+
+    def injectsGenericTypesFromParentIntoProviderFactoryMethod() {
+        def parent = new DefaultServiceRegistry() {
+            Callable<String> createStringCallable() {
+                return { "hello" }
+            }
+            Factory<String> createStringFactory() {
+                return { "world" } as Factory
+            }
+        }
+        def registry = new DefaultServiceRegistry(parent)
+        registry.addProvider(new Object() {
+            String createString(Callable<String> callable, Factory<String> factory) {
+                return callable.call() + ' ' + factory.create()
+            }
+        })
+
+        expect:
+        registry.get(String) == 'hello world'
+    }
+
+    def injectsServiceRegistryIntoProviderFactoryMethod() {
+        def parent = Mock(ServiceRegistry)
+        def registry = new DefaultServiceRegistry(parent)
+        registry.addProvider(new Object() {
+            String createString(ServiceRegistry services) {
+                assert services.is(registry)
+                return services.get(Number).toString()
+            }
+        })
+        registry.add(Integer, 123)
+
+        expect:
+        registry.get(String) == '123'
+    }
+
+    def failsWhenProviderFactoryMethodRequiresUnknownService() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new StringProvider())
+
+        when:
+        registry.get(String)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Cannot create service of type String using StringProvider.createString() as required service of type Runnable is not available."
+
+        when:
+        registry.get(Number)
+
+        then:
+        e = thrown()
+        e.message == "Cannot create service of type String using StringProvider.createString() as required service of type Runnable is not available."
+    }
+
+    def failsWhenProviderFactoryMethodThrowsException() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new BrokenProvider())
+
+        when:
+        registry.get(String)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Could not create service of type String using BrokenProvider.createString()."
+        e.cause == BrokenProvider.failure
+
+        when:
+        registry.get(Number)
+
+        then:
+        e = thrown()
+        e.message == "Could not create service of type String using BrokenProvider.createString()."
+        e.cause == BrokenProvider.failure
+    }
+
+    def cachesInstancesCreatedUsingAProviderFactoryMethod() {
+        def registry = new DefaultServiceRegistry()
+        def provider = new Object() {
+            String createString(Number number) {
+                return number.toString()
+            }
+
+            Integer createInteger() {
+                return 12
+            }
+        }
+        registry.addProvider(provider)
+
+        expect:
+        registry.get(Integer).is(registry.get(Integer))
+        registry.get(Number).is(registry.get(Number))
+
+        and:
+        registry.get(String).is(registry.get(String))
+    }
+
+    def usesProviderDecoratorMethodToDecorateParentServiceInstance() {
+        def parent = Mock(ServiceRegistry)
+        def registry = new DefaultServiceRegistry(parent)
+        registry.addProvider(new TestDecoratingProvider())
+
+        given:
+        _ * parent.get(Long) >> 110L
+
+        expect:
+        registry.get(Long) == 112L
+        registry.get(Number) == 112L
+        registry.get(Object) == 112L
+    }
+
+    def cachesServiceCreatedUsingProviderDecoratorMethod() {
+        def parent = Mock(ServiceRegistry)
+        def registry = new DefaultServiceRegistry(parent)
+        registry.addProvider(new TestDecoratingProvider())
+
+        given:
+        _ * parent.get(Long) >> 11L
+
+        expect:
+        registry.get(Long).is(registry.get(Long))
+    }
+
+    def providerDecoratorMethodFailsWhenNoParentRegistry() {
+        def registry = new DefaultServiceRegistry()
+
+        when:
+        registry.addProvider(new TestDecoratingProvider())
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message == "Cannot use decorator method TestDecoratingProvider.createLong() when no parent registry is provided."
+    }
+
+    def failsWhenProviderDecoratorMethodRequiresUnknownService() {
+        def parent = Stub(ServiceRegistry) {
+            get(_) >> { throw new UnknownServiceException(it[0], "broken") }
+        }
+        def registry = new DefaultServiceRegistry(parent)
+
+        given:
+        registry.addProvider(new TestDecoratingProvider())
+
+        when:
+        registry.get(Long)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Cannot create service of type Long using TestDecoratingProvider.createLong() as required service of type Long is not available in parent registries."
+    }
+
+    def failsWhenProviderDecoratorMethodThrowsException() {
+        def parent = Stub(ServiceRegistry) {
+            get(Long) >> 12L
+        }
+        def registry = new DefaultServiceRegistry(parent)
+
+        given:
+        registry.addProvider(new BrokenDecoratingProvider())
+
+        when:
+        registry.get(Long)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Could not create service of type Long using BrokenDecoratingProvider.createLong()."
+        e.cause == BrokenDecoratingProvider.failure
+    }
+
+    def failsWhenThereIsACycleInDependenciesForProviderFactoryMethods() {
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.addProvider(new ProviderWithCycle())
+
+        when:
+        registry.get(String)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Cannot create service of type Integer using ProviderWithCycle.createInteger() as there is a problem with parameter #1 of type String."
+        e.cause.message == 'Cycle in dependencies of service of type String.'
+
+        when:
+        registry.getAll(Number)
+
+        then:
+        e = thrown()
+        e.message == "Cannot create service of type Integer using ProviderWithCycle.createInteger() as there is a problem with parameter #1 of type String."
+        e.cause.message == 'Cycle in dependencies of service of type String.'
+    }
+
+    def failsWhenAProviderFactoryMethodReturnsNull() {
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.addProvider(new NullProvider())
+
+        when:
+        registry.get(String)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Could not create service of type String using NullProvider.createString() as this method returned null."
+    }
+
+    def failsWhenAProviderDecoratorMethodReturnsNull() {
+        def parent = Stub(ServiceRegistry) {
+            get(String) >> "parent"
+        }
+        def registry = new DefaultServiceRegistry(parent)
+
+        given:
+        registry.addProvider(new NullDecorator())
+
+        when:
+        registry.get(String)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Could not create service of type String using NullDecorator.createString() as this method returned null."
+    }
+
+    def usesFactoryMethodToCreateServiceInstance() {
+        expect:
+        registry.get(String.class) == "12"
+        registry.get(Integer.class) == 12
+    }
+
+    def cachesInstancesCreatedUsingAFactoryMethod() {
+        expect:
+        registry.get(Integer).is(registry.get(Integer))
+        registry.get(Number).is(registry.get(Number))
+    }
+
+    def usesOverriddenFactoryMethodToCreateServiceInstance() {
+        def registry = new TestRegistry() {
+            @Override
+            protected String createString() {
+                return "overridden"
+            }
+        };
+
+        expect:
+        registry.get(String) == "overridden"
+    }
+
+    def failsWhenMultipleFactoryMethodsCanCreateRequestedServiceType() {
+        def registry = new DefaultServiceRegistry();
+        registry.addProvider(new TestProvider())
+
+        expect:
+        registry.get(String)
+
+        when:
+        registry.get(Object)
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message == TextUtil.toPlatformLineSeparators("""Multiple services of type Object available in DefaultServiceRegistry:
+   - Service Callable<BigDecimal> at TestProvider.createCallable()
+   - Service Factory<BigDecimal> at TestProvider.createTestFactory()
+   - Service Integer at TestProvider.createInt()
+   - Service String at TestProvider.createString()""")
+    }
+
+    def failsWhenArrayClassRequested() {
+        when:
+        registry.get(String[].class)
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message == "Locating services with array type is not supported."
+    }
+
+    def cannotInjectAnArrayType() {
+        given:
+        registry.addProvider(new UnsupportedInjectionProvider())
+
+        when:
+        registry.get(Number)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == "Cannot create service of type Number using UnsupportedInjectionProvider.create() as there is a problem with parameter #1 of type String[]."
+        e.cause.message == 'Locating services with array type is not supported.'
+    }
+
+    def usesDecoratorMethodToDecorateParentServiceInstance() {
+        def parent = Mock(ServiceRegistry)
+        def registry = new RegistryWithDecoratorMethods(parent)
+
+        when:
+        def result = registry.get(Long)
+
+        then:
+        result == 120L
+
+        and:
+        1 * parent.get(Long) >> 110L
+    }
+
+    def decoratorMethodFailsWhenNoParentRegistry() {
+        when:
+        new RegistryWithDecoratorMethods()
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message.matches(/Cannot use decorator method RegistryWithDecoratorMethods\..*() when no parent registry is provided./)
+    }
+
+    def canRegisterServicesUsingAction() {
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.register({ ServiceRegistration registration ->
+            registration.add(Number, 12)
+            registration.add(TestServiceImpl)
+            registration.addProvider(new Object() {
+                String createString() {
+                    return "hi"
+                }
+            })
+        } as Action)
+
+        expect:
+        registry.get(Number) == 12
+        registry.get(TestServiceImpl)
+        registry.get(String) == "hi"
+    }
+
+    def providerConfigureMethodCanRegisterServices() {
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.addProvider(new Object() {
+            void configure(ServiceRegistration registration, Number value) {
+                registration.addProvider(new Object() {
+                    String createString() {
+                        return value.toString()
+                    }
+                })
+            }
+
+            Integer createNumber() {
+                return 123
+            }
+        })
+
+        expect:
+        registry.get(Number) == 123
+        registry.get(String) == "123"
+    }
+
+    def failsWhenProviderConfigureMethodRequiresUnknownService() {
+        def registry = new DefaultServiceRegistry()
+
+        when:
+        registry.addProvider(new NoOpConfigureProvider())
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message == 'Cannot configure services using NoOpConfigureProvider.configure() as required service of type String is not available.'
+    }
+
+    def failsWhenProviderConfigureMethodFails() {
+        def registry = new DefaultServiceRegistry()
+
+        when:
+        registry.addProvider(new BrokenConfigureProvider())
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message == 'Could not configure services using BrokenConfigureProvider.configure().'
+        e.cause == BrokenConfigureProvider.failure
+    }
+
+    def failsWhenCannotCreateServiceInstanceFromImplementationClass() {
+        given:
+        registry.register({ registration -> registration.add(ClassWithBrokenConstructor)} as Action)
+
+        when:
+        registry.get(ClassWithBrokenConstructor)
+
+        then:
+        ServiceCreationException e = thrown()
+        e.message == 'Could not create service of type ClassWithBrokenConstructor.'
+        e.cause == ClassWithBrokenConstructor.failure
+    }
+
+    def canGetAllServicesOfAGivenType() {
+        registry.addProvider(new Object(){
+            String createOtherString() {
+                return "hi"
+            }
+        })
+
+        expect:
+        registry.getAll(String) == ["12", "hi"]
+        registry.getAll(Number) == [12]
+    }
+
+    def canGetAllServicesOfARawType() {
+        def registry = new DefaultServiceRegistry()
+        registry.addProvider(new Object(){
+            String createString() {
+                return "hi"
+            }
+            Factory<String> createFactory() {
+                return {} as Factory
+            }
+            Callable<String> createCallable() {
+                return {}
+            }
+        })
+
+        expect:
+        registry.getAll(Factory).size() == 1
+        registry.getAll(Object).size() == 3
+    }
+
+    def allServicesReturnsEmptyCollectionWhenNoServicesOfGivenType() {
+        expect:
+        registry.getAll(Long).empty
+    }
+
+    def allServicesIncludesServicesFromParents() {
+        def parent1 = Stub(ServiceRegistry)
+        def parent2 = Stub(ServiceRegistry)
+        def registry = new DefaultServiceRegistry(parent1, parent2)
+        registry.addProvider(new Object() {
+            Long createLong() {
+                return 12;
+            }
+        });
+
+        given:
+        _ * parent1.getAll(Number) >> [123L]
+        _ * parent2.getAll(Number) >> [456]
+
+        expect:
+        registry.getAll(Number) == [12, 123L, 456]
+    }
+
+    def canGetServiceAsFactoryWhenTheServiceImplementsFactoryInterface() {
+        expect:
+        registry.getFactory(BigDecimal) instanceof TestFactory
+        registry.getFactory(Number) instanceof TestFactory
+        registry.getFactory(BigDecimal).is(registry.getFactory(BigDecimal))
+        registry.getFactory(Number).is(registry.getFactory(BigDecimal))
+    }
+
+    def canLocateFactoryWhenServiceInterfaceExtendsFactory() {
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.add(StringFactory, new StringFactory() {
+            public String create() {
+                return "value"
+            }
+        })
+
+        expect:
+        registry.getFactory(String.class).create() == "value"
+    }
+
+    def canGetAFactoryUsingParameterizedFactoryType() {
+        def registry = new RegistryWithMultipleFactoryMethods()
+
+        expect:
+        def stringFactory = registry.get(stringFactoryType)
+        stringFactory.create() == "hello"
+
+        def numberFactory = registry.get(numberFactoryType)
+        numberFactory.create() == 12
+    }
+
+    def canGetAFactoryUsingFactoryTypeWithBounds() throws NoSuchFieldException {
+        expect:
+        def superBigDecimalFactory = registry.get(superBigDecimalFactoryType)
+        superBigDecimalFactory.create() == BigDecimal.valueOf(0)
+
+        def extendsBigDecimalFactory = registry.get(extendsBigDecimalFactoryType)
+        extendsBigDecimalFactory.create() == BigDecimal.valueOf(1)
+
+        def extendsNumberFactory = registry.get(extendsNumberFactoryType)
+        extendsNumberFactory.create() == BigDecimal.valueOf(2)
+    }
+
+    def usesAFactoryServiceToCreateInstances() {
+        expect:
+        registry.newInstance(BigDecimal) == BigDecimal.valueOf(0)
+        registry.newInstance(BigDecimal) == BigDecimal.valueOf(1)
+        registry.newInstance(BigDecimal) == BigDecimal.valueOf(2)
+    }
+
+    def throwsExceptionForUnknownFactory() {
+        when:
+        registry.getFactory(String)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "No factory for objects of type String available in TestRegistry."
+    }
+
+    def delegatesToParentForUnknownFactory() {
+        def factory = Mock(Factory)
+        def parent = Mock(ServiceRegistry)
+        def registry = new TestRegistry(parent)
+
+        when:
+        def result = registry.getFactory(Map)
+
+        then:
+        result == factory
+
+        and:
+        1 * parent.getFactory(Map) >> factory
+    }
+
+    def usesDecoratorMethodToDecorateParentFactoryInstance() {
+        def factory = Mock(Factory)
+        def parent = Mock(ServiceRegistry)
+        def registry = new RegistryWithDecoratorMethods(parent)
+
+        given:
+        _ * parent.getFactory(Long) >> factory
+        _ * factory.create() >>> [10L, 20L]
+
+        expect:
+        registry.newInstance(Long) == 12L
+        registry.newInstance(Long) == 22L
+    }
+
+    def failsWhenMultipleFactoriesAreAvailableForServiceType() {
+        def registry = new RegistryWithAmbiguousFactoryMethods()
+
+        when:
+        registry.getFactory(Object)
+
+        then:
+        ServiceLookupException e = thrown()
+        e.message == TextUtil.toPlatformLineSeparators("""Multiple factories for objects of type Object available in RegistryWithAmbiguousFactoryMethods:
+   - Service Factory<Object> at RegistryWithAmbiguousFactoryMethods.createObjectFactory()
+   - Service Factory<String> at RegistryWithAmbiguousFactoryMethods.createStringFactory()""")
+    }
+
+    def servicesCreatedByFactoryMethodsAreVisibleWhenUsingASubClass() {
+        def registry = new TestRegistry() {
+        }
+
+        expect:
+        registry.get(String) == "12"
+        registry.get(Integer) == 12
+    }
+
+    def closeInvokesCloseMethodOnEachService() {
+        def service = Mock(TestCloseService)
+
+        given:
+        registry.add(TestCloseService, service)
+
+        when:
+        registry.close()
+
+        then:
+        1 * service.close()
+    }
+
+    def closeInvokesStopMethodOnEachService() {
+        def service = Mock(TestStopService)
+
+        given:
+        registry.add(TestStopService, service)
+
+        when:
+        registry.close()
+
+        then:
+        1 * service.stop()
+    }
+
+    def closeIgnoresServiceWithNoCloseOrStopMethod() {
+        registry.add(String, "service")
+        registry.getAll(Object)
+
+        when:
+        registry.close()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def closeInvokesCloseMethodOnEachServiceCreatedFromImplementationClass() {
+        given:
+        registry.register({ registration -> registration.add(ClosableService)} as Action)
+        def service = registry.get(ClosableService)
+
+        when:
+        registry.close()
+
+        then:
+        service.closed
+    }
+
+    def closeInvokesCloseMethodOnEachServiceCreatedByProviderFactoryMethod() {
+        def service = Mock(TestStopService)
+
+        given:
+        registry.addProvider(new Object() {
+            TestStopService createServices() {
+                return service
+            }
+        })
+        registry.get(TestStopService)
+
+        when:
+        registry.close()
+
+        then:
+        1 * service.stop()
+    }
+
+    def closeClosesServicesInDependencyOrder() {
+        def service1 = Mock(TestCloseService)
+        def service2 = Mock(TestStopService)
+        def service3 = Mock(Closeable)
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.addProvider(new Object() {
+            TestStopService createService2(Closeable b) {
+                return service2
+            }
+            Closeable createService3() {
+                return service3
+            }
+        })
+        registry.addProvider(new Object() {
+            TestCloseService createService1(TestStopService a, Closeable b) {
+                return service1
+            }
+        })
+        registry.get(TestCloseService)
+
+        when:
+        registry.close()
+
+        then:
+        1 * service1.close()
+
+        then:
+        1 * service2.stop()
+
+        then:
+        1 * service3.close()
+        0 * _._
+    }
+
+    def closeContinuesToCloseServicesAfterFailingToStopSomeService() {
+        def service1 = Mock(TestCloseService)
+        def service2 = Mock(TestStopService)
+        def service3 = Mock(Closeable)
+        def failure = new RuntimeException()
+        def registry = new DefaultServiceRegistry()
+
+        given:
+        registry.addProvider(new Object() {
+            TestStopService createService2(Closeable b) {
+                return service2
+            }
+            TestCloseService createService1(TestStopService a, Closeable b) {
+                return service1
+            }
+            Closeable createService3() {
+                return service3
+            }
+        })
+        registry.get(TestCloseService)
+
+        when:
+        registry.close()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
+        1 * service1.close()
+        1 * service2.stop() >> { throw failure }
+        1 * service3.close()
+        0 * _._
+    }
+
+    def doesNotStopServiceThatHasNotBeenCreated() {
+        def service = Mock(TestStopService)
+
+        given:
+        registry.addProvider(new Object() {
+            TestStopService createServices() {
+                return service
+            }
+        })
+
+        when:
+        registry.close()
+
+        then:
+        0 * service.stop()
+    }
+
+    def canStopMultipleTimes() {
+        def service = Mock(TestCloseService)
+
+        given:
+        registry.add(TestCloseService, service)
+
+        when:
+        registry.close()
+
+        then:
+        1 * service.close()
+
+        when:
+        registry.close()
+
+        then:
+        0 * service._
+    }
+
+    def cannotLookupServicesWhenClosed() {
+        given:
+        registry.get(String)
+        registry.getAll(String)
+        registry.close()
+
+        when:
+        registry.get(String)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot locate service of type String, as TestRegistry has been closed."
+
+        when:
+        registry.getAll(String)
+
+        then:
+        e = thrown()
+        e.message == "Cannot locate service of type String, as TestRegistry has been closed."
+    }
+
+    def cannotLookupFactoriesWhenClosed() {
+        given:
+        registry.getFactory(BigDecimal)
+        registry.close()
+
+        when:
+        registry.getFactory(BigDecimal)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot locate factory for objects of type BigDecimal, as TestRegistry has been closed."
+    }
+
+    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() {
+        return getClass().getDeclaredField("numberFactory").getGenericType();
+    }
+
+    private Type getStringFactoryType() {
+        return getClass().getDeclaredField("stringFactory").getGenericType();
+    }
+
+    private Type getSuperBigDecimalFactoryType() {
+        return getClass().getDeclaredField("superBigDecimalFactory").getGenericType()
+    }
+
+    private Type getExtendsBigDecimalFactoryType() {
+        return getClass().getDeclaredField("extendsBigDecimalFactory").getGenericType()
+    }
+
+    private Type getExtendsNumberFactoryType() {
+        return getClass().getDeclaredField("extendsNumberFactory").getGenericType()
+    }
+
+    private static class TestFactory implements Factory<BigDecimal> {
+        int value;
+        public BigDecimal create() {
+            return BigDecimal.valueOf(value++)
+        }
+    }
+
+    private interface TestService {
+    }
+
+    private static class TestServiceImpl implements TestService {
+    }
+
+    private static class ServiceWithDependency {
+        final TestService service
+
+        ServiceWithDependency(TestService service) {
+            this.service = service
+        }
+    }
+
+    private interface StringFactory extends Factory<String> {
+    }
+
+    private static class TestRegistry extends DefaultServiceRegistry {
+        public TestRegistry() {
+        }
+
+        public TestRegistry(ServiceRegistry parent) {
+            super(parent)
+        }
+
+        protected String createString() {
+            return get(Integer).toString()
+        }
+
+        protected Integer createInt() {
+            return 12
+        }
+
+        protected Factory<BigDecimal> createTestFactory() {
+            return new TestFactory()
+        }
+    }
+
+    private static class TestProvider {
+        String createString(Integer integer) {
+            return integer.toString()
+        }
+
+        Integer createInt() {
+            return 12
+        }
+
+        Factory<BigDecimal> createTestFactory() {
+            return new TestFactory()
+        }
+
+        Callable<BigDecimal> createCallable() {
+            return { 12 }
+        }
+    }
+
+    private static class StringProvider {
+        String createString(Runnable r) {
+            return "hi"
+        }
+
+        Integer createInteger(String value) {
+            return value.length()
+        }
+    }
+
+    private static class ProviderWithCycle {
+        String createString(Integer value) {
+            return value.toString()
+        }
+
+        Integer createInteger(String value) {
+            return value.length()
+        }
+    }
+
+    private static class NullProvider {
+        String createString() {
+            return null
+        }
+    }
+
+    private static class UnsupportedInjectionProvider {
+        Number create(String[] values) {
+            return values.length
+        }
+    }
+
+    private static class NoOpConfigureProvider {
+        void configure(ServiceRegistration registration, String value) {
+        }
+    }
+
+    private static class BrokenConfigureProvider {
+        static def failure = new RuntimeException()
+
+        void configure(ServiceRegistration registration) {
+            throw failure
+        }
+    }
+
+    private static class BrokenProvider {
+        static def failure = new RuntimeException()
+
+        String createString() {
+            throw failure.fillInStackTrace()
+        }
+
+        Integer createInteger(String value) {
+            return value.length()
+        }
+    }
+
+    private static class TestDecoratingProvider {
+        Long createLong(Long value) {
+            return value + 2
+        }
+    }
+
+    private static class BrokenDecoratingProvider {
+        static def failure = new RuntimeException()
+
+        Long createLong(Long value) {
+            throw failure
+        }
+    }
+
+    private static class NullDecorator {
+        String createString(String value) {
+            return null
+        }
+    }
+
+    private static class RegistryWithAmbiguousFactoryMethods extends DefaultServiceRegistry {
+        Object createObject() {
+            return "hello"
+        }
+
+        String createString() {
+            return "hello"
+        }
+
+        Factory<Object> createObjectFactory() {
+            return new Factory<Object>() {
+                public Object create() {
+                    return createObject()
+                }
+            };
+        }
+
+        Factory<String> createStringFactory() {
+            return new Factory<String>() {
+                public String create() {
+                    return createString()
+                }
+            };
+        }
+    }
+
+    private static class RegistryWithDecoratorMethods extends DefaultServiceRegistry {
+        public RegistryWithDecoratorMethods() {
+        }
+
+        public RegistryWithDecoratorMethods(ServiceRegistry parent) {
+            super(parent)
+        }
+
+        protected Long createLong(Long value) {
+            return value + 10
+        }
+
+        protected Factory<Long> createLongFactory(final Factory<Long> factory) {
+            return new Factory<Long>() {
+                public Long create() {
+                    return factory.create() + 2
+                }
+            };
+        }
+    }
+
+    private static class 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"
+                }
+            };
+        }
+    }
+
+    public interface TestCloseService {
+        void close()
+    }
+
+    public interface TestStopService {
+        void stop()
+    }
+
+    public interface ClosableServiceRegistry extends ServiceRegistry {
+        void close()
+    }
+
+    static class ClassWithBrokenConstructor {
+        static def failure = new RuntimeException("broken")
+
+        ClassWithBrokenConstructor() {
+            throw failure
+        }
+    }
+
+    static class ClosableService {
+        boolean closed
+
+        void close() {
+            closed = true
+        }
+    }
+}
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
deleted file mode 100755
index d38d3e5..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.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.internal.service;
-
-import org.gradle.internal.Factory;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultServiceRegistryTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final TestRegistry registry = new TestRegistry();
-
-    @Test
-    public void throwsExceptionForUnknownService() {
-        try {
-            registry.get(StringBuilder.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type StringBuilder available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void delegatesToParentForUnknownService() {
-        final BigDecimal value = BigDecimal.TEN;
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations(){{
-            one(parent).get(BigDecimal.class);
-            will(returnValue(value));
-        }});
-
-        assertThat(registry.get(BigDecimal.class), sameInstance(value));
-    }
-
-    @Test
-    public void throwsExceptionForUnknownParentService() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations(){{
-            one(parent).get(StringBuilder.class);
-            will(throwException(new UnknownServiceException(StringBuilder.class, "fail")));
-        }});
-
-        try {
-            registry.get(StringBuilder.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type StringBuilder available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void returnsAddedServiceInstance() {
-        BigDecimal value = BigDecimal.TEN;
-        DefaultServiceRegistry registry = new DefaultServiceRegistry();
-        registry.add(BigDecimal.class, value);
-        assertThat(registry.get(BigDecimal.class), sameInstance(value));
-        assertThat(registry.get(Number.class), sameInstance((Object) value));
-    }
-
-    @Test
-    public void cachesRegisteredServiceInstance() {
-        assertThat(registry.get(Integer.class), sameInstance(registry.get(Integer.class)));
-        assertThat(registry.get(Integer.class), sameInstance((Object) registry.get(Integer.class)));
-    }
-
-    @Test
-    public void usesFactoryMethodToCreateServiceInstance() {
-        assertThat(registry.get(String.class), equalTo("12"));
-        assertThat(registry.get(Integer.class), equalTo(12));
-    }
-
-    @Test
-    public void usesOverriddenFactoryMethodToCreateServiceInstance() {
-        TestRegistry registry = new TestRegistry(){
-            @Override
-            protected String createString() {
-                return "overridden";
-            }
-        };
-        assertThat(registry.get(String.class), equalTo("overridden"));
-    }
-
-    @Test
-    public void failsWhenMultipleServiceFactoriesCanCreateRequestedServiceType() {
-        ServiceRegistry registry = new RegistryWithAmbiguousFactoryMethods();
-        assertThat(registry.get(String.class), equalTo("hello"));
-
-        try {
-            registry.get(Object.class);
-            fail();
-        } catch (ServiceLookupException e) {
-            assertThat(e.getMessage(), equalTo("Multiple services of type Object available in RegistryWithAmbiguousFactoryMethods."));
-        }
-    }
-
-    @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);
-
-        context.checking(new Expectations() {{
-            one(parent).get(Long.class);
-            will(returnValue(110L));
-        }});
-
-        assertThat(registry.get(Long.class), equalTo(120L));
-    }
-
-    @Test
-    public void decoratorMethodFailsWhenNoParentRegistry() {
-        try {
-            new RegistryWithDecoratorMethods();
-            fail();
-        } catch (ServiceLookupException e) {
-            assertThat(e.getMessage(), equalTo("Cannot use decorator methods when no parent registry is provided."));
-        }
-    }
-
-    @Test
-    public void canGetServiceAsFactoryWhenTheServiceImplementsFactoryInterface() {
-        assertThat(registry.getFactory(BigDecimal.class), instanceOf(TestFactory.class));
-        assertThat(registry.getFactory(BigDecimal.class), sameInstance((Object) registry.getFactory(BigDecimal.class)));
-        assertThat(registry.getFactory(Number.class), sameInstance((Object) registry.getFactory(BigDecimal.class)));
-    }
-
-    @Test
-    public void canLocateFactoryWhenServiceInterfaceExtendsFactory() {
-        registry.add(StringFactory.class, new StringFactory() {
-            public String create() {
-                return "value";
-            }
-        });
-        assertThat(registry.getFactory(String.class).create(), equalTo("value"));
-    }
-
-    @Test
-    public void 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)));
-        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(2)));
-    }
-
-    @Test
-    public void delegatesToParentForUnknownFactory() {
-        @SuppressWarnings("unchecked") final Factory<Map> factory = context.mock(Factory.class);
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations() {{
-            one(parent).getFactory(Map.class);
-            will(returnValue(factory));
-        }});
-
-        assertThat(registry.getFactory(Map.class), sameInstance((Object) factory));
-    }
-
-    @Test
-    public void usesDecoratorMethodToDecorateParentFactoryInstance() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        @SuppressWarnings("unchecked") final Factory<Long> factory = context.mock(Factory.class);
-        ServiceRegistry registry = new RegistryWithDecoratorMethods(parent);
-
-        context.checking(new Expectations() {{
-            one(parent).getFactory(Long.class);
-            will(returnValue(factory));
-            allowing(factory).create();
-            will(onConsecutiveCalls(returnValue(10L), returnValue(20L)));
-        }});
-
-        assertThat(registry.newInstance(Long.class), equalTo(12L));
-        assertThat(registry.newInstance(Long.class), equalTo(22L));
-    }
-    
-    @Test
-    public void throwsExceptionForUnknownFactory() {
-        try {
-            registry.getFactory(String.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No factory for objects of type String available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void failsWhenMultipleFactoriesAreAvailableForServiceType() {
-        ServiceRegistry registry = new RegistryWithAmbiguousFactoryMethods();
-
-        try {
-            registry.getFactory(Object.class);
-            fail();
-        } catch (ServiceLookupException e) {
-            assertThat(e.getMessage(), equalTo("Multiple factories for objects of type Object available in RegistryWithAmbiguousFactoryMethods."));
-        }
-    }
-
-    @Test
-    public void returnsServiceInstancesManagedByNestedServiceRegistry() {
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        final Runnable runnable = context.mock(Runnable.class);
-        registry.add(nested);
-
-        context.checking(new Expectations() {{
-            one(nested).get(Runnable.class);
-            will(returnValue(runnable));
-        }});
-
-        assertThat(registry.get(Runnable.class), sameInstance(runnable));
-    }
-
-    @Test
-    public void throwsExceptionForUnknownServiceInNestedRegistry() {
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        registry.add(nested);
-
-        context.checking(new Expectations(){{
-            one(nested).get(Runnable.class);
-            will(throwException(new UnknownServiceException(Runnable.class, "fail")));
-        }});
-
-        try {
-            registry.get(Runnable.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type Runnable available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void returnsServiceFactoriesManagedByNestedServiceRegistry() {
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        @SuppressWarnings("unchecked") final Factory<Runnable> factory = context.mock(Factory.class);
-        registry.add(nested);
-
-        context.checking(new Expectations() {{
-            one(nested).getFactory(Runnable.class);
-            will(returnValue(factory));
-        }});
-
-        assertThat(registry.getFactory(Runnable.class), sameInstance(factory));
-    }
-
-    @Test
-    public void throwsExceptionForUnknownFactoryInNestedRegistry() {
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        registry.add(nested);
-
-        context.checking(new Expectations(){{
-            one(nested).getFactory(Runnable.class);
-            will(throwException(new UnknownServiceException(Runnable.class, "fail")));
-        }});
-
-        try {
-            registry.getFactory(Runnable.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No factory for objects of type Runnable available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void servicesCreatedByFactoryMethodsAreVisibleWhenUsingASubClass() {
-        ServiceRegistry registry = new SubType();
-        assertThat(registry.get(String.class), equalTo("12"));
-        assertThat(registry.get(Integer.class), equalTo(12));
-    }
-    
-    @Test
-    public void closeInvokesCloseMethodOnEachService() {
-        final TestCloseService service = context.mock(TestCloseService.class);
-        registry.add(TestCloseService.class, service);
-
-        context.checking(new Expectations() {{
-            one(service).close();
-        }});
-
-        registry.close();
-    }
-
-    @Test
-    public void prefersServicesCreatedByFactoryMethodsOverNestedServices() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        final TestRegistry registry = new TestRegistry(parent);
-        registry.add(nested);
-
-        assertThat(registry.get(String.class), equalTo("12"));
-    }
-
-    @Test
-    public void prefersRegisteredServicesOverNestedServices() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        final TestRegistry registry = new TestRegistry(parent);
-        registry.add(nested);
-        registry.add(BigDecimal.class, BigDecimal.ONE);
-
-        assertThat(registry.get(BigDecimal.class), equalTo(BigDecimal.ONE));
-    }
-
-    @Test
-    public void prefersNestedServicesOverParentServices() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
-        final TestRegistry registry = new TestRegistry(parent);
-        final Runnable runnable = context.mock(Runnable.class);
-        registry.add(nested);
-
-        context.checking(new Expectations() {{
-            one(nested).get(Runnable.class);
-            will(returnValue(runnable));
-        }});
-
-        assertThat(registry.get(Runnable.class), sameInstance(runnable));
-    }
-
-    @Test
-    public void closeInvokesStopMethodOnEachService() {
-        final TestStopService service = context.mock(TestStopService.class);
-        registry.add(TestStopService.class, service);
-
-        context.checking(new Expectations() {{
-            one(service).stop();
-        }});
-
-        registry.close();
-    }
-
-    @Test
-    public void closeIgnoresServiceWithNoCloseOrStopMethod() {
-        registry.add(String.class, "service");
-
-        registry.close();
-    }
-
-    @Test
-    public void closeInvokesCloseMethodOnEachNestedServiceRegistry() {
-        final ClosableServiceRegistry nested = context.mock(ClosableServiceRegistry.class);
-        registry.add(nested);
-
-        context.checking(new Expectations() {{
-            one(nested).close();
-        }});
-
-        registry.close();
-    }
-
-    @Test
-    public void discardsServicesOnClose() {
-        registry.get(String.class);
-        registry.close();
-        try {
-            registry.get(String.class);
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo("Cannot locate service of type String, as TestRegistry has been closed."));
-        }
-    }
-
-    @Test
-    public void discardsFactoriesOnClose() {
-        registry.getFactory(BigDecimal.class);
-        registry.close();
-        try {
-            registry.getFactory(BigDecimal.class);
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo("Cannot locate factory for objects of type BigDecimal, as TestRegistry has been closed."));
-        }
-    }
-
-    private 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() {
-        }
-
-        public TestRegistry(ServiceRegistry parent) {
-            super(parent);
-        }
-
-        protected String createString() {
-            return get(Integer.class).toString();
-        }
-
-        protected Integer createInt() {
-            return 12;
-        }
-
-        protected Factory<BigDecimal> createTestFactory() {
-            return new TestFactory();
-        }
-    }
-
-    private static class RegistryWithDecoratorMethods extends DefaultServiceRegistry {
-        public RegistryWithDecoratorMethods() {
-        }
-
-        public RegistryWithDecoratorMethods(ServiceRegistry parent) {
-            super(parent);
-        }
-
-        protected Long createLong(Long value) {
-            return value + 10;
-        }
-
-        protected Factory<Long> createLongFactory(final Factory<Long> factory) {
-            return new Factory<Long>() {
-                public Long create() {
-                    return factory.create() + 2;
-                }
-            };
-        }
-    }
-
-    private static class SubType extends TestRegistry {
-    }
-
-    private static class RegistryWithAmbiguousFactoryMethods extends DefaultServiceRegistry {
-        Object createObject() {
-            return "hello";
-        }
-        
-        String createString() {
-            return "hello";
-        }
-        
-        Factory<Object> createObjectFactory() {
-            return new Factory<Object>() {
-                public Object create() {
-                    return createObject();
-                }
-            };
-        }
-        
-        Factory<String> createStringFactory() {
-            return new Factory<String>() {
-                public String create() {
-                    return createString();
-                }
-            };
-        }
-    }
-
-    private static class 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() {
-            return BigDecimal.valueOf(value++);
-        }
-    }
-
-    private interface StringFactory extends Factory<String> {
-    }
-
-    public interface TestCloseService {
-        void close();
-    }
-
-    public interface TestStopService {
-        void stop();
-    }
-
-    public interface ClosableServiceRegistry extends ServiceRegistry {
-        void close();
-    }
-}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/GenericRunnable.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/GenericRunnable.java
new file mode 100644
index 0000000..bc4a6de
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/GenericRunnable.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.service;
+
+public interface GenericRunnable<T> extends Runnable {
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ProviderWithGenericType.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ProviderWithGenericType.java
new file mode 100644
index 0000000..e9a8949
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ProviderWithGenericType.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+class ProviderWithGenericTypes {
+    Integer createInteger(Runnable action) {
+        action.run();
+        return 123;
+    }
+
+    GenericRunnable<String> createString() {
+        return new GenericRunnable<String>() {
+            public void run() {
+            }
+        };
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy
index 0b91e05..cae81ac 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy
@@ -25,21 +25,42 @@ class ServiceLocatorTest extends Specification {
         def serviceFile = stream('org.gradle.ImplClass')
 
         when:
-        def result = serviceLocator.findServiceImplementationClass(String.class)
+        def result = serviceLocator.findFactory(String.class).create()
 
         then:
-        result == String
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        result instanceof String
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([serviceFile])
         1 * classLoader.loadClass('org.gradle.ImplClass') >> String
     }
 
-    def "findServiceImplementationClass() returns null when no service meta data resource available"() {
+    def "uses union of resources found in all ClassLoaders"() {
+        def ClassLoader classLoader2 = Mock()
+        def serviceLocator = new ServiceLocator(classLoader, classLoader2)
+
+        def serviceFile1 = stream('org.gradle.ImplClass')
+        def serviceFile2 = stream('org.gradle.ImplClass2')
+        def serviceFile3 = stream('org.gradle.ImplClass')
+        def serviceFile4 = stream('org.gradle.ImplClass3')
+
         when:
-        def result = serviceLocator.findServiceImplementationClass(String.class)
+        def result = serviceLocator.getAll(CharSequence.class)
+
+        then:
+        result*.class == [String, StringBuilder, StringBuffer]
+        1 * classLoader.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([serviceFile1, serviceFile2])
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
+        1 * classLoader.loadClass('org.gradle.ImplClass2') >> StringBuilder
+        1 * classLoader2.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([serviceFile3, serviceFile4])
+        1 * classLoader2.loadClass('org.gradle.ImplClass3') >> StringBuffer
+    }
+
+    def "findFactory() returns null when no service meta data resource available"() {
+        when:
+        def result = serviceLocator.findFactory(String.class)
 
         then:
         result == null
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> null
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([])
     }
 
     def "wraps implementation class load failure"() {
@@ -47,13 +68,13 @@ class ServiceLocatorTest extends Specification {
         def failure = new ClassNotFoundException()
 
         when:
-        serviceLocator.findServiceImplementationClass(String.class)
+        serviceLocator.findFactory(String.class)
 
         then:
         RuntimeException e = thrown()
-        e.message == "Could not load implementation class 'org.gradle.ImplClass' for service 'java.lang.String'."
+        e.message == "Could not load implementation class 'org.gradle.ImplClass' for service 'java.lang.String' specified in resource '${serviceFile}'."
         e.cause == failure
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([serviceFile])
         1 * classLoader.loadClass('org.gradle.ImplClass') >> { throw failure }
     }
 
@@ -64,37 +85,51 @@ class ServiceLocatorTest extends Specification {
 ''')
 
         when:
-        def result = serviceLocator.findServiceImplementationClass(String.class)
+        def result = serviceLocator.findFactory(String.class).create()
 
         then:
-        result == String
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        result instanceof String
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([serviceFile])
         1 * classLoader.loadClass('org.gradle.ImplClass') >> String
     }
 
-    def "findServiceImplementationClass() fails when no implementation class specified in service meta data resource"() {
+    def "can locate multiple service implementations from one resource file"() {
+        def serviceFile = stream("""org.gradle.ImplClass1
+org.gradle.ImplClass2""")
+
+        when:
+        def result = serviceLocator.getAll(String.class)
+
+        then:
+        result.size() == 2
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([serviceFile])
+        1 * classLoader.loadClass('org.gradle.ImplClass1') >> String
+        1 * classLoader.loadClass('org.gradle.ImplClass2') >> String
+    }
+
+    def "findFactory() fails when no implementation class specified in service meta data resource"() {
         def serviceFile = stream('#empty!')
 
         when:
-        serviceLocator.findServiceImplementationClass(String.class)
+        serviceLocator.findFactory(String.class)
 
         then:
         RuntimeException e = thrown()
-        e.message == "Could not determine implementation class for service 'java.lang.String'."
-        e.cause.message == "No implementation class for service 'java.lang.String' specified in resource '${serviceFile}'."
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        e.message == "Could not determine implementation class for service 'java.lang.String' specified in resource '${serviceFile}'."
+        e.cause.message == "No implementation class for service 'java.lang.String' specified."
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([serviceFile])
     }
 
-    def "findServiceImplementationClass() fails when implementation class specified in service meta data resource is not assignable to service type"() {
+    def "findFactory() fails when implementation class specified in service meta data resource is not assignable to service type"() {
         given:
         implementationDeclared(String, Integer)
 
         when:
-        serviceLocator.findServiceImplementationClass(String)
+        serviceLocator.findFactory(String)
 
         then:
         RuntimeException e = thrown()
-        e.message == "Could not load implementation class 'java.lang.Integer' for service 'java.lang.String'."
+        e.message.startsWith("Could not load implementation class 'java.lang.Integer' for service 'java.lang.String' specified in resource '")
         e.cause.message == "Implementation class 'java.lang.Integer' is not assignable to service class 'java.lang.String'."
     }
 
@@ -109,18 +144,6 @@ class ServiceLocatorTest extends Specification {
         result instanceof String
     }
 
-    def "get() caches service implementation instances"() {
-        given:
-        implementationDeclared(CharSequence, String)
-
-        when:
-        def obj1 = serviceLocator.get(CharSequence)
-        def obj2 = serviceLocator.get(CharSequence)
-
-        then:
-        obj1.is(obj2)
-    }
-
     def "get() fails when no meta-data file found for service type"() {
         when:
         serviceLocator.get(CharSequence)
@@ -128,6 +151,7 @@ class ServiceLocatorTest extends Specification {
         then:
         UnknownServiceException e = thrown()
         e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
+        1 * classLoader.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([])
     }
 
     def "getFactory() returns a factory which creates instances of implementation class"() {
@@ -152,31 +176,79 @@ class ServiceLocatorTest extends Specification {
         then:
         UnknownServiceException e = thrown()
         e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
+        1 * classLoader.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([])
     }
 
-    def stream(String contents) {
-        URLStreamHandler handler = Mock()
-        URLConnection connection = Mock()
-        URL url = new URL("custom", "host", 12, "file", handler)
-        _ * handler.openConnection(url) >> connection
-        _ * connection.getInputStream() >> new ByteArrayInputStream(contents.bytes)
-        return url
+    def "getAll() returns an instance of each declared implementation"() {
+        def impl1 = stream("org.gradle.Impl1")
+        def impl2 = stream("org.gradle.Impl2")
+
+        when:
+        def result = serviceLocator.getAll(CharSequence)
+
+        then:
+        result.size() == 2
+        result[0] instanceof String
+        result[1] instanceof StringBuilder
+        1 * classLoader.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([impl1, impl2])
+        1 * classLoader.loadClass("org.gradle.Impl1") >> String
+        1 * classLoader.loadClass("org.gradle.Impl2") >> StringBuilder
     }
 
-    def "newInstance() creates instances of implementation class"() {
-        given:
-        implementationDeclared(CharSequence, String)
+    def "getAll() ignores duplicate implementation classes"() {
+        def impl1 = stream("org.gradle.Impl1")
+        def impl2 = stream("org.gradle.Impl2")
+        def impl3 = stream("org.gradle.Impl1")
 
         when:
-        def result = serviceLocator.newInstance(CharSequence)
+        def result = serviceLocator.getAll(CharSequence)
+
+        then:
+        result.size() == 2
+        result[0] instanceof String
+        result[1] instanceof StringBuilder
+        1 * classLoader.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([impl1, impl2, impl3])
+        1 * classLoader.loadClass("org.gradle.Impl1") >> String
+        1 * classLoader.loadClass("org.gradle.Impl2") >> StringBuilder
+    }
+
+    def "getAll() returns empty collection no meta-data file found for service type"() {
+        when:
+        def result = serviceLocator.getAll(CharSequence)
+
+        then:
+        result.empty
+        1 * classLoader.getResources("META-INF/services/java.lang.CharSequence") >> Collections.enumeration([])
+    }
+
+    def "servicelocator uses utf-8 encoding for reading serviceFile"() {
+        def serviceFile = stream(className)
+
+        when:
+        def result = serviceLocator.get(String.class)
 
         then:
         result instanceof String
+        1 * classLoader.getResources("META-INF/services/java.lang.String") >> Collections.enumeration([serviceFile])
+        1 * classLoader.loadClass(className) >> String
+
+        where:
+        className << ['org.gradle.κόσμε']
+    }
+
+
+    def stream(String contents) {
+        URLStreamHandler handler = Mock()
+        URLConnection connection = Mock()
+        URL url = new URL("custom", "host", 12, "file", handler)
+        _ * handler.openConnection(url) >> connection
+        _ * connection.getInputStream() >> new ByteArrayInputStream(contents.getBytes("UTF-8"))
+        return url
     }
-    
+
     def implementationDeclared(Class<?> serviceType, Class<?> implementationType) {
         def serviceFile = stream(implementationType.name)
-        _ * classLoader.getResource("META-INF/services/${serviceType.name}") >> serviceFile
+        _ * classLoader.getResources("META-INF/services/${serviceType.name}") >> Collections.enumeration([serviceFile])
         _ * classLoader.loadClass(implementationType.name) >> implementationType
     }
 }
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/SynchronizedServiceRegistryTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/SynchronizedServiceRegistryTest.groovy
deleted file mode 100644
index 3623ccc..0000000
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/SynchronizedServiceRegistryTest.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.internal.service;
-
-
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 11/24/11
- */
-public class SynchronizedServiceRegistryTest extends Specification {
-
-    def delegate = Mock(ServiceRegistry)
-    def reg = new SynchronizedServiceRegistry(delegate);
-
-    def "gets services from delegate"() {
-        when:
-        reg.get(Object)
-        then:
-        1 * delegate.get(Object)
-        when:
-        reg.getFactory(String)
-        then:
-        1 * delegate.getFactory(String)
-        when:
-        reg.newInstance(Integer)
-        then:
-        1 * delegate.newInstance(Integer)
-    }
-}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
index d9b96e2..ae8f0c5 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
@@ -94,6 +94,11 @@ class CollectionUtilsTest extends Specification {
         collect([1, 2, 3] as Object[], transformer { it * 2 }) == [2, 4, 6]
     }
 
+    def "collect iterable"() {
+        expect:
+        collect([1, 2, 3] as Iterable, transformer { it * 2 }) == [2, 4, 6]
+    }
+
     def "list stringize"() {
         expect:
         stringize([1, 2, 3]) == ["1", "2", "3"]
@@ -163,33 +168,43 @@ class CollectionUtilsTest extends Specification {
         every([], spec { false })
     }
 
+    def "intersection"() {
+        expect:
+        intersection([collA, collB]) == collC
+        where:
+        collA              | collB            | collC
+        []                 | ["a", "b", "c"]  | []
+        ['a', 'b', 'c']    | ["a", "b", "c"]  | ['a', 'b', 'c']
+        ['a', 'b', 'c']    | ["b", "c"]       | ['b', 'c']
+    }
+
     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) == []
+        flattenCollections([1, 2, 3] as Set) == [1, 2, 3]
+        flattenCollections("asdfa") == ["asdfa"]
+        flattenCollections(null) == [null]
+        flattenCollections([null, [null, null]]) == [null, null, null]
+        flattenCollections(integers) == integers
+        flattenCollections([1, 2, 3] as Set) == [1, 2, 3]
+        flattenCollections([] as Set) == []
 
         when:
-        flattenToList(Map, "foo")
+        flattenCollections(Map, "foo")
 
         then:
         thrown(ClassCastException)
 
         when:
-        flattenToList(Map, [[a: 1], "foo"])
+        flattenCollections(Map, [[a: 1], "foo"])
 
         then:
         thrown(ClassCastException)
 
         and:
-        flattenToList(Number, 1, [2, 3]) == [1, 2, 3]
+        flattenCollections(Number, 1, [2, 3]) == [1, 2, 3]
     }
 
     def "joining"() {
@@ -222,12 +237,18 @@ class CollectionUtilsTest extends Specification {
         null      | null as Object[]   | "separator"
     }
 
-    def "addAll"() {
+    def "addAll from iterable"() {
         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 "addAll from array"() {
+        expect:
+        addAll([], 1, 2, 3) == [1, 2, 3]
+        addAll([] as Set, 1, 2, 3, 1) == [1, 2, 3] as Set
+    }
+
     def "injection"() {
         expect:
         def target = []
@@ -268,9 +289,9 @@ class CollectionUtilsTest extends Specification {
         toSet([]).empty
     }
 
-    def "sorting"() {
+    def "sorting with comparator"() {
         given:
-        def naturalComparator = { a, b -> a <=> b } as Comparator
+        def naturalComparator = { a, b -> a<=>b } as Comparator
 
         expect:
         def l = [1, 2, 3]
@@ -283,6 +304,18 @@ class CollectionUtilsTest extends Specification {
         sort([] as Set, naturalComparator) == []
     }
 
+    def "sorting"() {
+        expect:
+        def l = [1, 2, 3]
+        !sort(l).is(l)
+
+        and:
+        sort([2, 1, 3]) == [1, 2, 3]
+        sort([2, 1, 3] as Set) == [1, 2, 3]
+        sort([]) == []
+        sort([] as Set) == []
+    }
+
     Spec<?> spec(Closure c) {
         Specs.convertClosureToSpec(c)
     }
diff --git a/subprojects/core/src/test/resources/org/gradle/util/ClassLoaderTest.txt b/subprojects/base-services/src/test/resources/org/gradle/util/ClassLoaderTest.txt
similarity index 100%
rename from subprojects/core/src/test/resources/org/gradle/util/ClassLoaderTest.txt
rename to subprojects/base-services/src/test/resources/org/gradle/util/ClassLoaderTest.txt
diff --git a/subprojects/build-comparison/build-comparison.gradle b/subprojects/build-comparison/build-comparison.gradle
index 9dd01ef..7e80092 100644
--- a/subprojects/build-comparison/build-comparison.gradle
+++ b/subprojects/build-comparison/build-comparison.gradle
@@ -15,12 +15,14 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
+    compile project(":resources")
     compile project(":core")
     compile project(":toolingApi")
     compile project(":reporting")
-    compile project(":ide") // for FileOutcomeIdentifier enum
+    compile project(':plugins')
+    compile project(':ear')
     compile libraries.guava
     compile libraries.slf4j_api
 
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
index c3f8da8..ede1389 100644
--- 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
@@ -18,7 +18,7 @@ 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.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;
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
index b60d456..a969718 100644
--- 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
@@ -57,7 +57,7 @@ public class ComparableGradleBuildExecuter {
             return true;
         } else {
             // Special handling for snapshots/RCs of the minimum version
-            return version.getVersionBase().equals(PROJECT_OUTCOMES_MINIMUM_VERSION.getVersionBase());
+            return version.getBaseVersion().equals(PROJECT_OUTCOMES_MINIMUM_VERSION);
         }
     }
 
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
index 8face84..77f76a4 100644
--- 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
@@ -59,9 +59,6 @@ public class DefaultGradleBuildInvocationSpec implements GradleBuildInvocationSp
             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();
     }
 
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
index af75bac..9f85e9e 100644
--- 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
@@ -19,8 +19,8 @@ package org.gradle.api.plugins.buildcomparison.gradle.internal;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.Transformer;
-import org.gradle.api.internal.IoActions;
-import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.internal.IoActions;
+import org.gradle.internal.filestore.FileStore;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.plugins.buildcomparison.compare.internal.*;
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
index 3b15fd3..98b320a 100644
--- 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
@@ -17,11 +17,11 @@
 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.internal.filestore.FileStore;
 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.internal.resource.local.LocallyAvailableResource;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
@@ -59,13 +59,13 @@ public class GradleBuildOutcomeSetInferrer implements Transformer<Set<BuildOutco
             // TODO - we are relying on knowledge that the name of the outcome is the task path
             String taskPath = outcome.getName();
 
-            FileStoreEntry fileStoreEntry = null;
+            LocallyAvailableResource resource = null;
             if (file.exists()) {
                 String filestoreDestination = String.format("%s/%s/%s", fileStorePrefix, taskPath, file.getName());
-                fileStoreEntry = fileStore.move(filestoreDestination, file);
+                resource = fileStore.move(filestoreDestination, file);
             }
 
-            return new GeneratedArchiveBuildOutcome(outcome.getName(), outcome.getDescription(), fileStoreEntry, rootRelativePath);
+            return new GeneratedArchiveBuildOutcome(outcome.getName(), outcome.getDescription(), resource, 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
index d4b3933..20765cc 100644
--- 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
@@ -17,12 +17,12 @@
 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.internal.filestore.FileStore;
 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.api.plugins.buildcomparison.outcome.internal.FileOutcomeIdentifier;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome;
 import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome;
 import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
@@ -79,13 +79,13 @@ public class GradleBuildOutcomeSetTransformer implements Transformer<Set<BuildOu
             File originalFile = outcome.getFile();
             String relativePath = GFileUtils.relativePath(rootProject.getProjectDirectory(), originalFile);
 
-            FileStoreEntry fileStoreEntry = null;
+            LocallyAvailableResource resource = null;
             if (originalFile.exists()) {
                 String filestoreDestination = String.format("%s/%s/%s", fileStorePrefix, outcome.getTaskPath(), originalFile.getName());
-                fileStoreEntry = fileStore.move(filestoreDestination, originalFile);
+                resource = fileStore.move(filestoreDestination, originalFile);
             }
 
-            BuildOutcome buildOutcome = new GeneratedArchiveBuildOutcome(outcome.getTaskPath(), outcome.getDescription(), fileStoreEntry, relativePath);
+            BuildOutcome buildOutcome = new GeneratedArchiveBuildOutcome(outcome.getTaskPath(), outcome.getDescription(), resource, 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/outcome/internal/FileOutcomeIdentifier.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/FileOutcomeIdentifier.java
new file mode 100644
index 0000000..b8a38e7
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/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.api.plugins.buildcomparison.outcome.internal;
+
+/**
+ * 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/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
index 357f8cd..2b6fc4b 100644
--- 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
@@ -16,19 +16,19 @@
 
 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 org.gradle.internal.resource.local.LocallyAvailableResource;
 
 import java.io.File;
 
 public class GeneratedArchiveBuildOutcome extends BuildOutcomeSupport {
 
-    private final FileStoreEntry fileStoreEntry;
+    private final LocallyAvailableResource resource;
     private final String rootRelativePath;
 
-    public GeneratedArchiveBuildOutcome(String name, String description, FileStoreEntry fileStoreEntry, String rootRelativePath) {
+    public GeneratedArchiveBuildOutcome(String name, String description, LocallyAvailableResource resource, String rootRelativePath) {
         super(name, description);
-        this.fileStoreEntry = fileStoreEntry;
+        this.resource = resource;
         this.rootRelativePath = rootRelativePath;
     }
 
@@ -40,7 +40,7 @@ public class GeneratedArchiveBuildOutcome extends BuildOutcomeSupport {
      * @return The generated archive, or null if no archive was generated.
      */
     public File getArchiveFile() {
-        return fileStoreEntry == null ? null : fileStoreEntry.getFile();
+        return resource == null ? null : resource.getFile();
     }
 
     /**
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultGradleBuildOutcome.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultGradleBuildOutcome.java
new file mode 100644
index 0000000..28edf41
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/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.api.plugins.buildcomparison.outcome.internal.tooling;
+
+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/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultGradleFileBuildOutcome.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultGradleFileBuildOutcome.java
new file mode 100644
index 0000000..1a09506
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/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.api.plugins.buildcomparison.outcome.internal.tooling;
+
+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/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultProjectOutcomes.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultProjectOutcomes.java
new file mode 100644
index 0000000..7e5d6fc
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/DefaultProjectOutcomes.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.plugins.buildcomparison.outcome.internal.tooling;
+
+import com.google.common.collect.Lists;
+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 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/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/ProjectOutcomesModelBuilder.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/ProjectOutcomesModelBuilder.java
new file mode 100644
index 0000000..15e9481
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/ProjectOutcomesModelBuilder.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.tooling;
+
+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.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 org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+import java.util.List;
+
+public class ProjectOutcomesModelBuilder implements ToolingModelBuilder {
+    private final PublishArtifactToFileBuildOutcomeTransformer artifactTransformer = new PublishArtifactToFileBuildOutcomeTransformer();
+
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.outcomes.ProjectOutcomes");
+    }
+
+    public Object buildAll(String modelName, Project project) {
+        return buildProjectOutput(project.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/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/PublishArtifactToFileBuildOutcomeTransformer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/PublishArtifactToFileBuildOutcomeTransformer.java
new file mode 100644
index 0000000..b853a6e
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/PublishArtifactToFileBuildOutcomeTransformer.java
@@ -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.plugins.buildcomparison.outcome.internal.tooling;
+
+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.model.internal.outcomes.GradleFileBuildOutcome;
+
+import java.net.URI;
+import java.util.Set;
+
+import static org.gradle.api.plugins.buildcomparison.outcome.internal.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/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/ToolingRegistrationAction.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/ToolingRegistrationAction.java
new file mode 100644
index 0000000..59add9a
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/ToolingRegistrationAction.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins.buildcomparison.outcome.internal.tooling;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.configuration.project.ProjectConfigureAction;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+public class ToolingRegistrationAction implements ProjectConfigureAction {
+    public void execute(ProjectInternal project) {
+        project.getServices().get(ToolingModelBuilderRegistry.class).register(new ProjectOutcomesModelBuilder());
+    }
+}
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
index 8c9f1b0..9611558 100644
--- 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
@@ -23,11 +23,10 @@ import org.gradle.api.plugins.buildcomparison.compare.internal.BuildComparisonRe
 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 org.gradle.api.plugins.buildcomparison.render.internal.*
 
 import java.nio.charset.Charset
 
-import org.gradle.api.plugins.buildcomparison.render.internal.*
-
 class GradleBuildComparisonResultHtmlRenderer implements BuildComparisonResultRenderer<Writer> {
 
     private final BuildOutcomeComparisonResultRendererFactory<HtmlRenderContext> comparisonRenderers
@@ -281,7 +280,7 @@ class GradleBuildComparisonResultHtmlRenderer implements BuildComparisonResultRe
         }
     }
 
-    String name(BuildOutcomeComparisonResult<?> comparisonResult) {
+    String name(BuildOutcomeComparisonResult<? extends BuildOutcome> comparisonResult) {
         comparisonResult.compared.source.name
     }
 
diff --git a/subprojects/build-comparison/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction b/subprojects/build-comparison/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
new file mode 100644
index 0000000..a2789fd
--- /dev/null
+++ b/subprojects/build-comparison/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
@@ -0,0 +1 @@
+org.gradle.api.plugins.buildcomparison.outcome.internal.tooling.ToolingRegistrationAction
\ No newline at end of file
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
index e64df57..4d5c4bd 100644
--- 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
@@ -17,12 +17,12 @@
 package org.gradle.api.plugins.buildcomparison.gradle.internal
 
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class DefaultGradleBuildInvocationSpecTest extends Specification {
 
-    FileResolver fileResolver = HelperUtil.createRootProject().fileResolver
+    FileResolver fileResolver = TestUtil.createRootProject().fileResolver
 
     def "equals and hashCode"() {
         given:
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
index 19ceb76..665f6ec 100644
--- 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
@@ -28,7 +28,7 @@ import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
 import org.junit.Rule
 import spock.lang.Specification
 
-import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
+import static org.gradle.api.plugins.buildcomparison.outcome.internal.FileOutcomeIdentifier.*
 
 class GradleBuildOutcomeSetInferrerTest extends Specification {
 
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
index 84529dd..b01fb24 100644
--- 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
@@ -17,12 +17,12 @@
 package org.gradle.api.plugins.buildcomparison.gradle.internal
 
 import org.gradle.api.Action
-import org.gradle.api.internal.filestore.AbstractFileStoreEntry
-import org.gradle.api.internal.filestore.FileStore
-import org.gradle.api.internal.filestore.FileStoreEntry
+import org.gradle.internal.filestore.FileStore
 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.internal.resource.local.DefaultLocallyAvailableResource
+import org.gradle.internal.resource.local.LocallyAvailableResource
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
@@ -31,32 +31,24 @@ import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
 import org.junit.Rule
 import spock.lang.Specification
 
-import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
+import static org.gradle.api.plugins.buildcomparison.outcome.internal.FileOutcomeIdentifier.*
 
 class GradleBuildOutcomeSetTransformerTest extends Specification {
 
     def store = new FileStore<String>() {
-        FileStoreEntry move(String key, File source) {
-            new AbstractFileStoreEntry() {
-                File getFile() {
-                    source
-                }
-            }
+        LocallyAvailableResource move(String key, File source) {
+            new DefaultLocallyAvailableResource(source)
         }
 
-        FileStoreEntry copy(String key, File source) {
-            new AbstractFileStoreEntry() {
-                File getFile() {
-                    source
-                }
-            }
+        LocallyAvailableResource copy(String key, File source) {
+            new DefaultLocallyAvailableResource(source)
         }
 
         void moveFilestore(File destination) {
             throw new UnsupportedOperationException()
         }
 
-        FileStoreEntry add(String key, Action<File> addAction) {
+        LocallyAvailableResource add(String key, Action<File> addAction) {
             throw new UnsupportedOperationException()
         }
     }
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
index b5a71bc..0eaa388 100644
--- 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
@@ -17,10 +17,10 @@
 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.internal.resource.local.DefaultLocallyAvailableResource
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
@@ -121,9 +121,7 @@ class GeneratedArchiveBuildOutcomeComparatorTest extends Specification {
     }
 
     GeneratedArchiveBuildOutcome outcome(String name, File file = dir.createFile(name)) {
-        def fileStoreEntry = new AbstractFileStoreEntry() {
-            File getFile() { file }
-        }
-        new GeneratedArchiveBuildOutcome(name, name, fileStoreEntry, name)
+        def resource = new DefaultLocallyAvailableResource(file)
+        new GeneratedArchiveBuildOutcome(name, name, resource, name)
     }
 }
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/PublishArtifactToFileBuildOutcomeTransformerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/PublishArtifactToFileBuildOutcomeTransformerTest.groovy
new file mode 100644
index 0000000..ad74474
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/tooling/PublishArtifactToFileBuildOutcomeTransformerTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins.buildcomparison.outcome.internal.tooling
+
+import org.gradle.api.Task
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
+import org.gradle.api.plugins.buildcomparison.outcome.internal.FileOutcomeIdentifier
+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.api.plugins.buildcomparison.outcome.internal.FileOutcomeIdentifier.*
+import org.gradle.util.TestUtil
+import org.gradle.api.Project
+
+class PublishArtifactToFileBuildOutcomeTransformerTest extends Specification {
+
+    def transformer = new PublishArtifactToFileBuildOutcomeTransformer()
+
+    Project project = TestUtil.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/build-init/build-init.gradle b/subprojects/build-init/build-init.gradle
new file mode 100644
index 0000000..045f462
--- /dev/null
+++ b/subprojects/build-init/build-init.gradle
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 {
+    compile libraries.groovy
+    compile project(':core')
+    compile project(':plugins')
+    compile project(':wrapper')
+    integTestRuntime project(':maven')
+    integTestRuntime project(':scala')
+}
+
+dependencies {
+    components {
+        eachComponent { ComponentMetadataDetails details ->
+            def version = details.id.version
+            if(version.matches("(\\d\\.?)+")){
+                details.status = "release"
+            }else{
+                details.status = "integration"
+            }
+            details.statusScheme = ["integration", "release"]
+        }
+    }
+}
+
+task generateTemplateVersionFile(type: GenerateVersionProperties) {
+    outputFile = new File(generatedResourcesDir, "org/gradle/buildinit/tasks/templates/library-versions.properties")
+}
+
+sourceSets.main.resources.srcDir generatedResourcesDir
+processResources.dependsOn generateTemplateVersionFile
+
+eclipseClasspath.dependsOn generateTemplateVersionFile
+ideaModule.dependsOn generateTemplateVersionFile
+
+class GenerateVersionProperties extends DefaultTask {
+
+    @OutputFile
+    File outputFile
+
+    @TaskAction
+    void generateFile() {
+        resolveFiles()
+    }
+
+    def resolveFiles() {
+        Properties versionProperties = new Properties()
+
+        findLatest('scala-library', 'org.scala-lang:scala-library:latest.release', versionProperties)
+        def scalaVersion = VersionNumber.parse(versionProperties['scala-library'])
+        versionProperties.put('scala', "${scalaVersion.major}.${scalaVersion.minor}" as String)
+        findLatest('scalatest', "org.scalatest:scalatest_${versionProperties.scala}:latest.release", versionProperties)
+        findLatest('scala-xml', "org.scala-lang.modules:scala-xml_${versionProperties.scala}:latest.release", versionProperties)
+        findLatest('groovy', 'org.codehaus.groovy:groovy:latest.release', versionProperties)
+        findLatest('junit', 'junit:junit:latest.release', versionProperties)
+
+        outputFile.withOutputStream { outputStream ->
+            versionProperties.store(outputStream, "Version values used in build-init templates")
+        }
+    }
+
+    private void findLatest(String name, String notation, Properties dest) {
+        def libDependencies = [ project.dependencies.create(notation) ]
+        def templateVersionConfiguration = project.configurations.detachedConfiguration(libDependencies as Dependency[])
+        templateVersionConfiguration.transitive = false
+        ResolutionResult resolutionResult = templateVersionConfiguration.incoming.resolutionResult
+        resolutionResult.allComponents.findAll { it != resolutionResult.root }. each { dep -> dest.put(name, dep.id.version) }
+    }
+}
+
+
+useTestFixtures()
+useClassycle()
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/BuildInitPluginIntegrationTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/BuildInitPluginIntegrationTest.groovy
new file mode 100644
index 0000000..e0b253f
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/BuildInitPluginIntegrationTest.groovy
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import org.gradle.test.fixtures.file.TestFile
+import org.hamcrest.Matcher
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+
+class BuildInitPluginIntegrationTest extends WellBehavedPluginTest {
+    final wrapper = new WrapperTestFixture(testDirectory)
+
+    @Override
+    String getMainTask() {
+        return "init"
+    }
+
+    @Override
+    String getPluginId() {
+        "build-init"
+    }
+
+    def "init shows up on tasks overview "() {
+        when:
+        run 'tasks'
+
+        then:
+        result.output.contains "init - Initializes a new Gradle build."
+    }
+
+    def "creates a simple project when no pom file present and no type specified"() {
+        given:
+        assert !buildFile.exists()
+        assert !settingsFile.exists()
+
+        when:
+        run 'init'
+
+        then:
+        wrapper.generated()
+        buildFile.exists()
+        settingsFile.exists()
+
+        expect:
+        succeeds 'tasks'
+    }
+
+    def "build file generation is skipped when build file already exists"() {
+        given:
+        buildFile.createFile()
+
+        when:
+        run('init')
+
+        then:
+        result.assertTasksExecuted(":init")
+        result.output.contains("The build file 'build.gradle' already exists. Skipping build initialization.")
+
+        and:
+        !settingsFile.exists()
+        wrapper.notGenerated()
+    }
+
+    def "build file generation is skipped when settings file already exists"() {
+        given:
+        settingsFile.createFile()
+
+        when:
+        run('init')
+
+        then:
+        result.assertTasksExecuted(":init")
+        result.output.contains("The settings file 'settings.gradle' already exists. Skipping build initialization.")
+
+        and:
+        !buildFile.exists()
+        wrapper.notGenerated()
+    }
+
+    def "build file generation is skipped when custom build file exists"() {
+        given:
+        def customBuildScript = testDirectory.file("customBuild.gradle").createFile()
+
+        when:
+        executer.usingBuildScript(customBuildScript)
+        run('init')
+
+        then:
+        result.assertTasksExecuted(":init")
+        result.output.contains("The build file 'customBuild.gradle' already exists. Skipping build initialization.")
+
+        and:
+        !buildFile.exists()
+        !settingsFile.exists()
+        wrapper.notGenerated()
+    }
+
+    def "build file generation is skipped when part of a multi-project build with non-standard settings file location"() {
+        given:
+        def customSettings = testDirectory.file("customSettings.gradle")
+        customSettings << """
+include 'child'
+"""
+
+        when:
+        executer.usingSettingsFile(customSettings)
+        run('init')
+
+        then:
+        result.assertTasksExecuted(":init")
+        result.output.contains("This Gradle project appears to be part of an existing multi-project Gradle build. Skipping build initialization.")
+
+        and:
+        !buildFile.exists()
+        !settingsFile.exists()
+        wrapper.notGenerated()
+    }
+
+    def "pom conversion is triggered when pom and no gradle file found"() {
+        given:
+        pom()
+
+        when:
+        run('init')
+
+        then:
+        pomValuesUsed()
+    }
+
+    def "pom conversion not triggered when build type is specified"() {
+        given:
+        pom()
+
+        when:
+        succeeds('init', '--type', 'java-library')
+
+        then:
+        pomValuesNotUsed()
+    }
+
+    def "gives decent error message when triggered with unknown init-type"() {
+        when:
+        fails('init', '--type', 'some-unknown-library')
+
+        then:
+        failure.assertHasCause("The requested build setup type 'some-unknown-library' is not supported.")
+    }
+
+    private TestFile pom() {
+        file("pom.xml") << """
+      <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>
+      </project>"""
+    }
+
+    private pomValuesUsed() {
+        buildFile.assertContents(containsPomGroup())
+        buildFile.assertContents(containsPomVersion())
+        settingsFile.assertContents(containsPomArtifactId())
+    }
+
+    private pomValuesNotUsed() {
+        buildFile.assertContents(not(containsPomGroup()))
+        buildFile.assertContents(not(containsPomVersion()))
+        settingsFile.assertContents(not(containsPomArtifactId()))
+    }
+
+    private Matcher<String> containsPomGroup() {
+        containsString("group = 'util'")
+    }
+
+    private Matcher<String> containsPomVersion() {
+        containsString("version = '2.5'")
+    }
+
+    private Matcher<String> containsPomArtifactId() {
+        containsString("rootProject.name = 'util'")
+    }
+
+}
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/GroovyLibraryInitIntegrationTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/GroovyLibraryInitIntegrationTest.groovy
new file mode 100644
index 0000000..ced4e04
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/GroovyLibraryInitIntegrationTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestExecutionResult
+
+
+class GroovyLibraryInitIntegrationTest extends AbstractIntegrationSpec {
+
+    public static final String SAMPLE_LIBRARY_CLASS = "src/main/groovy/Library.groovy"
+    public static final String SAMPLE_LIBRARY_TEST_CLASS = "src/test/groovy/LibraryTest.groovy"
+
+    final wrapper = new WrapperTestFixture(testDirectory)
+
+    def "creates sample source if no source present"() {
+        when:
+        succeeds('init', '--type', 'groovy-library')
+
+        then:
+        file(SAMPLE_LIBRARY_CLASS).exists()
+        file(SAMPLE_LIBRARY_TEST_CLASS).exists()
+        buildFile.exists()
+        settingsFile.exists()
+        wrapper.generated()
+
+        when:
+        succeeds("build")
+
+        then:
+        TestExecutionResult testResult = new DefaultTestExecutionResult(testDirectory)
+        testResult.assertTestClassesExecuted("LibraryTest")
+        testResult.testClass("LibraryTest").assertTestPassed("someLibraryMethod returns true")
+    }
+
+    def "setupProjectLayout is skipped when groovy sources detected"() {
+        setup:
+        file("src/main/groovy/org/acme/SampleMain.groovy") << """
+            package org.acme;
+
+            class SampleMain{
+            }
+    """
+        file("src/test/groovy/org/acme/SampleMainTest.groovy") << """
+                    package org.acme;
+
+                    class SampleMain{
+                    }
+            """
+        when:
+        succeeds('init', '--type', 'groovy-library')
+
+        then:
+        !file(SAMPLE_LIBRARY_CLASS).exists()
+        !file(SAMPLE_LIBRARY_TEST_CLASS).exists()
+        buildFile.exists()
+        settingsFile.exists()
+        wrapper.generated()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/JavaLibraryInitIntegrationTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/JavaLibraryInitIntegrationTest.groovy
new file mode 100644
index 0000000..bb5d3c0
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/JavaLibraryInitIntegrationTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestExecutionResult
+
+class JavaLibraryInitIntegrationTest extends AbstractIntegrationSpec {
+
+    public static final String SAMPLE_LIBRARY_CLASS = "src/main/java/Library.java"
+    public static final String SAMPLE_LIBRARY_TEST_CLASS = "src/test/java/LibraryTest.java"
+
+    final wrapper = new WrapperTestFixture(testDirectory)
+
+    def "creates sample source if no source present"() {
+        when:
+        succeeds('init', '--type', 'java-library')
+
+        then:
+        file(SAMPLE_LIBRARY_CLASS).exists()
+        file(SAMPLE_LIBRARY_TEST_CLASS).exists()
+        buildFile.exists()
+        settingsFile.exists()
+        wrapper.generated()
+
+        when:
+        succeeds("build")
+
+        then:
+        TestExecutionResult testResult = new DefaultTestExecutionResult(testDirectory)
+        testResult.assertTestClassesExecuted("LibraryTest")
+        testResult.testClass("LibraryTest").assertTestPassed("testSomeLibraryMethod")
+    }
+
+    def "setupProjectLayout is skipped when java sources detected"() {
+        setup:
+        file("src/main/java/org/acme/SampleMain.java") << """
+        package org.acme;
+
+        public class SampleMain{
+        }
+"""
+        file("src/test/java/org/acme/SampleMainTest.java") << """
+                package org.acme;
+
+                public class SampleMain{
+                }
+        """
+        when:
+        succeeds('init', '--type', 'java-library')
+
+        then:
+        !file(SAMPLE_LIBRARY_CLASS).exists()
+        !file(SAMPLE_LIBRARY_TEST_CLASS).exists()
+        buildFile.exists()
+        settingsFile.exists()
+        wrapper.generated()
+    }
+}
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/MavenConversionIntegrationTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/MavenConversionIntegrationTest.groovy
new file mode 100644
index 0000000..6d2561a
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/MavenConversionIntegrationTest.groovy
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.test.fixtures.maven.MavenHttpModule
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.maven.PomHttpArtifact
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Issue
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class MavenConversionIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final TestResources resources = new TestResources(temporaryFolder)
+
+    @Rule
+    public final SetSystemProperties systemProperties = new SetSystemProperties()
+
+    @Rule
+    public final HttpServer server = new HttpServer()
+
+    def setup() {
+        withLocalM2Installation()
+    }
+
+    def "multiModule"() {
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        run '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()
+
+        new DefaultTestExecutionResult(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 "multiModuleWithNestedParent"() {
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        run '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()
+
+        new DefaultTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+    }
+
+    def "flatmultimodule"() {
+        when:
+        executer.inDirectory(file("webinar-parent"))
+        run 'init'
+
+        then:
+        gradleFilesGenerated(file("webinar-parent"))
+
+        when:
+        executer.inDirectory(file("webinar-parent"))
+        run '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()
+
+        new DefaultTestExecutionResult(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"() {
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        //TODO 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"() {
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/testjar-2.5.jar").exists()
+        file("build/libs/testjar-2.5-tests.jar").exists()
+    }
+
+    def "enforcerplugin"() {
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        and:
+        buildFile.text.contains("""configurations.all {
+it.exclude group: 'org.apache.maven'
+it.exclude group: 'org.apache.maven', module: 'badArtifact'
+it.exclude group: '*', module: 'badArtifact'
+}""")
+        when:
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/enforcerExample-1.0.jar").exists()
+    }
+
+    def "providedNotWar"() {
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/myThing-0.0.1-SNAPSHOT.jar").exists()
+    }
+
+    def "provides decent error message when POM is invalid"() {
+        setup:
+        def pom = file("pom.xml")
+        pom << "<project>someInvalid pom content</project>"
+
+        when:
+        fails 'init'
+
+        then:
+        failure.assertHasCause("Could not convert Maven POM $pom to a Gradle build.")
+    }
+
+    def "mavenExtensions"() {
+        when:
+        run 'init'
+        then:
+        gradleFilesGenerated()
+
+        when:
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/testApp-1.0.jar").exists()
+    }
+
+    @Issue("GRADLE-2820")
+    def "remoteparent"() {
+        setup:
+        withSharedResources()
+        def repo = mavenHttpServer()
+        //update pom with test repo url
+        file("pom.xml").text = file("pom.xml").text.replaceAll('LOCAL_MAVEN_REPO_URL', repo.getUri().toString())
+
+        expectParentPomRequest(repo)
+
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        libRequest(repo, "commons-lang", "commons-lang", "2.6")
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/util-2.5.jar").exists()
+    }
+
+    @Issue("GRADLE-2872")
+    def "expandProperties"() {
+        setup:
+        withSharedResources()
+        executer.withArgument("-DCOMMONS_LANG_VERSION=2.6")
+
+        when:
+        run 'init'
+        then:
+        gradleFilesGenerated()
+
+        when:
+        run('clean', 'build')
+
+        then:
+        file("build/libs/util-3.2.1.jar").exists()
+    }
+
+    @Issue("GRADLE-2819")
+    def "multiModuleWithRemoteParent"() {
+        setup:
+        withSharedResources()
+        def repo = mavenHttpServer()
+        //update pom with test repo url
+        file("pom.xml").text = file("pom.xml").text.replaceAll('LOCAL_MAVEN_REPO_URL', repo.getUri().toString())
+
+        expectParentPomRequest(repo)
+
+        when:
+        run 'init'
+
+        then:
+        gradleFilesGenerated()
+
+        when:
+        libRequest(repo, "commons-lang", "commons-lang", 2.6)
+        libRequest(repo, "junit", "junit", 4.10)
+        libRequest(repo, "org.hamcrest", "hamcrest-core", 1.1)
+
+        run '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()
+
+        new DefaultTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+
+        when:
+        run 'projects'
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+Root project 'webinar-parent'
++--- Project ':util-parent'
++--- Project ':webinar-api' - Webinar APIs
++--- Project ':webinar-impl' - Webinar implementation
+\\--- Project ':webinar-war' - Webinar web application
+"""))
+    }
+
+    void gradleFilesGenerated(TestFile parentFolder = file(".")) {
+        assert parentFolder.file("build.gradle").exists()
+        assert parentFolder.file("settings.gradle").exists()
+        new WrapperTestFixture(parentFolder).generated()
+    }
+
+    def libRequest(MavenHttpRepository repo, String group, String name, Object version) {
+        MavenHttpModule module = repo.module(group, name, version)
+        module.allowAll()
+    }
+
+    def expectModule(MavenHttpRepository repo, String group, String name, String version) {
+        MavenHttpModule module1 = repo.module(group, name, version).publish()
+        module1.allowAll()
+    }
+
+    def withSharedResources() {
+        resources.maybeCopy('MavenConversionIntegrationTest/sharedResources')
+    }
+
+    M2Installation withLocalM2Installation() {
+        M2Installation m2Installation = new M2Installation(testDirectory)
+        m2Installation.generateUserSettingsFile(mavenLocal("local_m2"))
+        using m2Installation
+        m2Installation
+    }
+
+    PomHttpArtifact expectParentPomRequest(MavenHttpRepository repo) {
+        MavenHttpModule module = repo.module('util.util.parent', 'util-parent', '3')
+        module.pom.expectGet();
+        module.pom.sha1.expectGet();
+        module.pom.md5.expectGet();
+        module.pom
+    }
+
+    MavenHttpRepository mavenHttpServer() {
+        server.start()
+        new MavenHttpRepository(server, '/maven', maven(file("maven_repo")));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/ScalaLibraryInitIntegrationTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/ScalaLibraryInitIntegrationTest.groovy
new file mode 100644
index 0000000..6a00911
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/ScalaLibraryInitIntegrationTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestExecutionResult
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.JDK6_OR_LATER)
+class ScalaLibraryInitIntegrationTest extends AbstractIntegrationSpec {
+
+    public static final String SAMPLE_LIBRARY_CLASS = "src/main/scala/Library.scala"
+    public static final String SAMPLE_LIBRARY_TEST_CLASS = "src/test/scala/LibrarySuite.scala"
+
+    final wrapper = new WrapperTestFixture(testDirectory)
+
+    def "creates sample source if no source present"() {
+        when:
+        succeeds('init', '--type', 'scala-library')
+
+        then:
+        file(SAMPLE_LIBRARY_CLASS).exists()
+        file(SAMPLE_LIBRARY_TEST_CLASS).exists()
+        buildFile.exists()
+        settingsFile.exists()
+        wrapper.generated()
+
+        when:
+        succeeds("build")
+
+        then:
+        TestExecutionResult testResult = new DefaultTestExecutionResult(testDirectory)
+        testResult.assertTestClassesExecuted("LibrarySuite")
+        testResult.testClass("LibrarySuite").assertTestPassed("someLibraryMethod is always true")
+    }
+
+    def "setupProjectLayout is skipped when scala sources detected"() {
+        setup:
+        file("src/main/scala/org/acme/SampleMain.scala") << """
+            package org.acme;
+
+            class SampleMain{
+            }
+    """
+        file("src/test/scala/org/acme/SampleMainTest.scala") << """
+                    package org.acme;
+
+                    class SampleMainTest{
+                    }
+            """
+        when:
+        succeeds('init', '--type', 'scala-library')
+
+        then:
+        !file(SAMPLE_LIBRARY_CLASS).exists()
+        !file(SAMPLE_LIBRARY_TEST_CLASS).exists()
+        buildFile.exists()
+        settingsFile.exists()
+        wrapper.generated()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/WrapperPluginIntegrationTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/WrapperPluginIntegrationTest.groovy
new file mode 100644
index 0000000..8033856
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/WrapperPluginIntegrationTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class WrapperPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "wrapper"
+    }
+
+    def "wrapper task generates wrapper files"() {
+        when:
+        run 'wrapper'
+
+        then:
+        new WrapperTestFixture(testDirectory).generated()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/fixtures/WrapperTestFixture.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/fixtures/WrapperTestFixture.groovy
new file mode 100644
index 0000000..78aa0e4
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/fixtures/WrapperTestFixture.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.fixtures
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.GradleVersion
+
+
+class WrapperTestFixture {
+
+    public static final String GRADLEW_BASH_SCRIPT = "gradlew"
+    public static final String GRADLEW_BATCH_SCRIPT = "gradlew.bat"
+    public static final String GRADLEW_WRAPPER_JAR = "gradle/wrapper/gradle-wrapper.jar"
+    public static final String GRADLEW_PROPERTY_FILE = "gradle/wrapper/gradle-wrapper.properties"
+
+    private final TestFile projectDirectory
+
+    WrapperTestFixture(TestFile projectdirectory) {
+        this.projectDirectory = projectdirectory
+    }
+
+    public void generated(String version = GradleVersion.current().version) {
+        projectDirectory.file(GRADLEW_BASH_SCRIPT).assertExists()
+        projectDirectory.file(GRADLEW_BATCH_SCRIPT).assertExists()
+        projectDirectory.file(GRADLEW_WRAPPER_JAR).assertExists()
+        projectDirectory.file(GRADLEW_PROPERTY_FILE).assertExists()
+        projectDirectory.file(GRADLEW_PROPERTY_FILE).text.contains("gradle-${version}-bin.zip")
+    }
+
+    public void notGenerated() {
+        projectDirectory.file(GRADLEW_BASH_SCRIPT).assertDoesNotExist()
+        projectDirectory.file(GRADLEW_BATCH_SCRIPT).assertDoesNotExist()
+        projectDirectory.file(GRADLEW_WRAPPER_JAR).assertDoesNotExist()
+        projectDirectory.file(GRADLEW_PROPERTY_FILE).assertDoesNotExist()
+    }
+}
diff --git a/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/internal/WrapperPluginAutoApplyActionIntegTest.groovy b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/internal/WrapperPluginAutoApplyActionIntegTest.groovy
new file mode 100644
index 0000000..e3cdfdb
--- /dev/null
+++ b/subprojects/build-init/src/integTest/groovy/org/gradle/buildinit/plugins/internal/WrapperPluginAutoApplyActionIntegTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.buildinit.plugins.fixtures.WrapperTestFixture
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
+
+class WrapperPluginAutoApplyActionIntegTest extends AbstractIntegrationSpec {
+    final wrapper = new WrapperTestFixture(testDirectory)
+
+    def "can apply wrapper plugin dynamically"() {
+        when:
+        run 'tasks'
+        then:
+        output.contains("wrapper - Generates Gradle wrapper files.")
+
+        when:
+        run 'wrapper'
+        then:
+        wrapper.generated()
+    }
+
+    def "can use camel-case for dynamically applied wrapper plugin "() {
+        when:
+        run taskName
+        then:
+        wrapper.generated()
+        where:
+        taskName << ["wrapp"]//, "wrap", "w"]
+    }
+
+    def "wrapper plugin not applied on subprojects"() {
+        setup:
+        settingsFile << "include 'moduleA'"
+        TestFile subprojectsDir = file("moduleA").createDir()
+
+        when:
+        executer.inDirectory(subprojectsDir)
+        run 'tasks'
+        then:
+        !output.contains("wrapper - Generates Gradle wrapper files.")
+
+        when:
+        executer.inDirectory(subprojectsDir)
+        then:
+        fails("wrapper")
+    }
+
+    def "can reference dynamic applied wrapper plugin"() {
+        when:
+        buildFile << """
+            wrapper{
+                gradleVersion = '12.34'
+            }
+    """
+        run 'wrapper'
+        then:
+        wrapper.generated("12.34")
+    }
+
+    def "can depend on dynamic applied wrapper task"() {
+        when:
+        buildFile << """
+            task myTask(dependsOn:wrapper)
+        """
+        run 'myTask'
+        then:
+        wrapper.generated()
+    }
+
+    def "manually declared wrapper task is preferred"() {
+        when:
+        buildFile << """
+
+                task wrapper << {
+                    println "running custom wrapper task"
+                }
+            """
+        run 'wrapper'
+        then:
+        output.contains("running custom wrapper task")
+        wrapper.notGenerated()
+    }
+}
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/pom.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/pom.xml
new file mode 100644
index 0000000..f5078a1
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/pom.xml
@@ -0,0 +1,27 @@
+<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>3.2.1</version>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>${COMMONS_LANG_VERSION}</version><!-- reference system property-->
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>${project.version}</version><!--reference project property -->
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+            <version>${junit-version}</version><!--reference profile property -->
+        </dependency>
+    </dependencies>
+</project>
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/src/main/java/Foo.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/src/main/java/Foo.java
new file mode 100644
index 0000000..c964cbb
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/src/main/java/Foo.java
@@ -0,0 +1,10 @@
+import org.apache.commons.collections.map.MultiValueMap;
+
+public class Foo {
+
+  MultiValueMap mvp = new MultiValueMap();
+
+  public String toString() {
+    return "foo";
+  }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/src/test/java/FooTest.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/src/test/java/FooTest.java
new file mode 100644
index 0000000..03a25b0
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/expandProperties/src/test/java/FooTest.java
@@ -0,0 +1,10 @@
+import org.junit.Test;
+import static junit.framework.Assert.assertFalse;
+
+public class FooTest {
+
+  @Test
+  public void test() {
+    assertFalse(false);
+  }
+}
\ 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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-api/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-api/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/mavenExtensions/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/mavenExtensions/pom.xml
new file mode 100644
index 0000000..d4aa30d
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/mavenExtensions/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  This is the base POM for all dependent and inherited POMS of the DPJW application
+-->
+<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>
+    <groupId>org.test</groupId>
+    <artifactId>testApp</artifactId>
+    <version>1.0</version>
+    <packaging>pom</packaging>
+    <name>A Test Project</name>
+    <description>Some Test project</description>
+    <build>
+        <plugins>
+            <!-- set this project compiler to jdk version 1.6 -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+
+            </plugin>
+            <!-- the test plugin -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/HistoryConfigurationTest.java</exclude>
+                        <exclude>**/TestDataFactory.java</exclude>
+                    </excludes>
+                    <forkMode>once</forkMode>
+                    <reportFormat>xml</reportFormat>
+                </configuration>
+            </plugin>
+        </plugins>
+        <extensions>
+            <extension>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>5.0.7</version>
+            </extension>
+        </extensions>
+    </build>
+</project>
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/mavenExtensions/test-core/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/mavenExtensions/test-core/pom.xml
new file mode 100755
index 0000000..45f4d9c
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/mavenExtensions/test-core/pom.xml
@@ -0,0 +1,22 @@
+<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>
+  <parent>
+    <groupId>org.test</groupId>
+    <artifactId>testApp</artifactId>
+    <version>1.0</version>
+  </parent>
+
+  <artifactId>test-core</artifactId>
+  <name>test-core</name>
+  <packaging>jar</packaging>
+  <build>
+    <extensions>
+      <extension>
+        <groupId>mysql</groupId>
+        <artifactId>mysql-connector-java</artifactId>
+        <version>5.0.7</version>
+      </extension>
+    </extensions>
+  </build>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/pom.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-api/src/main/java/webinar/Demoable.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/src/main/java/webinar/Demoable.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-api/src/main/java/webinar/Demoable.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-impl/src/main/java/webinar/Webinar.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/main/java/webinar/Webinar.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-impl/src/main/java/webinar/Webinar.java
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-impl/src/test/java/webinar/WebinarTest.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/test/java/webinar/WebinarTest.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-impl/src/test/java/webinar/WebinarTest.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/WEB-INF/web.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/WEB-INF/web.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/WEB-INF/web.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/nested-parent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/nested-parent/pom.xml
new file mode 100644
index 0000000..5506a3b
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/nested-parent/pom.xml
@@ -0,0 +1,21 @@
+<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>nested-parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>junit</groupId>
+				<artifactId>junit</artifactId>
+				<version>4.10</version>
+				<scope>test</scope>
+			</dependency>
+		</dependencies>
+  </dependencyManagement>
+
+</project>
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml
new file mode 100644
index 0000000..549f8df
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml
@@ -0,0 +1,22 @@
+<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>
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>nested-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <relativePath>nested-parent</relativePath>
+  </parent>
+  <artifactId>webinar-parent</artifactId>
+  <packaging>pom</packaging>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-api/pom.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-api/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-api/src/main/java/webinar/Demoable.java
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-api/src/main/java/webinar/Demoable.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-impl/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-impl/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-impl/src/main/java/webinar/Webinar.java
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-impl/src/main/java/webinar/Webinar.java
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-impl/src/test/java/webinar/WebinarTest.java
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-impl/src/test/java/webinar/WebinarTest.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-war/pom.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-war/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-war/src/main/webapp/WEB-INF/web.xml
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-war/src/main/webapp/WEB-INF/web.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-war/src/main/webapp/index.jsp
similarity index 100%
copy from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
copy to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/webinar-war/src/main/webapp/index.jsp
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml
new file mode 100644
index 0000000..aaa9c76
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml
@@ -0,0 +1,44 @@
+<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">
+    <parent>
+        <groupId>util.util.parent</groupId>
+        <artifactId>util-parent</artifactId>
+        <version>3</version>
+    </parent>
+
+    <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>
+    <repositories>
+        <repository>
+            <id>local-remote</id>
+            <name>Local Test Repository</name>
+            <layout>default</layout>
+            <url>LOCAL_MAVEN_REPO_URL</url>
+        </repository>
+    </repositories>
+    <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>util-parent</module>
+        <module>webinar-impl</module>
+        <module>webinar-war</module>
+    </modules>
+</project>
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/util-parent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/util-parent/pom.xml
new file mode 100644
index 0000000..c49e505
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/util-parent/pom.xml
@@ -0,0 +1,13 @@
+<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>util-parent</artifactId>
+    <packaging>pom</packaging>
+    <name>Util Parent</name>
+    <parent>
+        <groupId>org.gradle.webinar</groupId>
+        <artifactId>webinar-parent</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-api/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-api/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-api/src/main/java/webinar/Demoable.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-api/src/main/java/webinar/Demoable.java
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/pom.xml
new file mode 100644
index 0000000..4a56e4a
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/pom.xml
@@ -0,0 +1,32 @@
+<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>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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/src/main/java/webinar/Webinar.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/src/main/java/webinar/Webinar.java
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/src/test/java/webinar/WebinarTest.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-impl/src/test/java/webinar/WebinarTest.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-war/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-war/pom.xml
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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-war/src/main/webapp/WEB-INF/web.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-war/src/main/webapp/WEB-INF/web.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-war/src/main/webapp/index.jsp
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/webinar-war/src/main/webapp/index.jsp
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/providedNotWar/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/providedNotWar/pom.xml
new file mode 100644
index 0000000..911e15d
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/providedNotWar/pom.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>myGroup</groupId>
+  <artifactId>myThing</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <dependencies>
+  	<dependency>
+  		<groupId>junit</groupId>
+  		<artifactId>junit</artifactId>
+  		<version>4.10</version>
+  		<scope>provided</scope>
+  	</dependency>
+  </dependencies>
+</project>
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml
new file mode 100644
index 0000000..097ecdc
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml
@@ -0,0 +1,21 @@
+<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">
+    <parent>
+        <groupId>util.util.parent</groupId>
+        <artifactId>util-parent</artifactId>
+        <version>3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>util</groupId>
+    <artifactId>util</artifactId>
+    <version>2.5</version>
+    <packaging>jar</packaging>
+    <repositories>
+        <repository>
+            <id>local-remote</id>
+            <name>Local Test Repository</name>
+            <layout>default</layout>
+            <url>LOCAL_MAVEN_REPO_URL</url>
+        </repository>
+    </repositories>
+</project>
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/src/main/java/Bar.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/src/main/java/Bar.java
new file mode 100644
index 0000000..9bdf33a
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/src/main/java/Bar.java
@@ -0,0 +1,7 @@
+import org.apache.commons.lang.StringUtils;
+
+public class Bar {
+    public String toString() {
+        return StringUtils.normalizeSpace("hi  there!");
+    }
+}
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/sharedResources/maven_home/m2_home/conf/settings.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/sharedResources/maven_home/m2_home/conf/settings.xml
new file mode 100644
index 0000000..903670e
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/sharedResources/maven_home/m2_home/conf/settings.xml
@@ -0,0 +1,13 @@
+<settings>
+    <profiles>
+        <profile>
+          <id>testprofile</id>
+          <properties>
+            <junit-version>4.10</junit-version>
+          </properties>
+        </profile>
+    </profiles>
+    <activeProfiles>
+        <activeProfile>testprofile</activeProfile>
+    </activeProfiles>
+</settings>
\ No newline at end of file
diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/sharedResources/maven_repo/util/util/parent/util-parent/3/util-parent-3.pom b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/sharedResources/maven_repo/util/util/parent/util-parent/3/util-parent-3.pom
new file mode 100644
index 0000000..0683d9b
--- /dev/null
+++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/sharedResources/maven_repo/util/util/parent/util-parent/3/util-parent-3.pom
@@ -0,0 +1,17 @@
+<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>
+    <groupId>util.util.parent</groupId>
+    <artifactId>util-parent</artifactId>
+    <version>3</version>
+    <packaging>pom</packaging>
+    <name>Test Parent Pom</name>
+    <description>Defaults for Test Maven Build with remote parent projects.</description>
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+    </dependencies>
+</project>
\ 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/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/pom.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testjar/pom.xml
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/pom.xml
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testjar/pom.xml
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java
similarity index 100%
rename from subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java
rename to subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java b/subprojects/build-init/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java
new file mode 100644
index 0000000..6340677
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.wrapper;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.plugins.StartScriptGenerator;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.util.*;
+import org.gradle.wrapper.GradleWrapperMain;
+import org.gradle.wrapper.Install;
+import org.gradle.wrapper.WrapperExecutor;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * <p>Generates scripts (for *nix and windows) which allow you to build your project with Gradle, without having to
+ * install Gradle.
+ *
+ * <p>When a user executes a wrapper script the first time, the script downloads and installs the appropriate Gradle
+ * distribution and runs the build against this downloaded distribution. Any installed Gradle distribution is ignored
+ * when using the wrapper scripts.
+ *
+ * <p>The scripts generated by this task are intended to be committed to your version control system. This task also
+ * generates a small {@code gradle-wrapper.jar} bootstrap JAR file and properties file which should also be committed to
+ * your VCS. The scripts delegates to this JAR.
+ */
+public class Wrapper extends DefaultTask {
+    public static final String DEFAULT_DISTRIBUTION_PARENT_NAME = Install.DEFAULT_DISTRIBUTION_PATH;
+
+    private String distributionUrl;
+
+    /**
+     * Specifies how the wrapper path should be interpreted.
+     */
+    public enum PathBase {
+        PROJECT, GRADLE_USER_HOME
+    }
+
+    private Object scriptFile;
+    private Object jarFile;
+
+    @Input
+    private String distributionPath;
+
+    @Input
+    private PathBase distributionBase = PathBase.GRADLE_USER_HOME;
+
+    private GradleVersion gradleVersion;
+
+    @Input
+    private String archivePath;
+
+    @Input
+    private PathBase archiveBase = PathBase.GRADLE_USER_HOME;
+
+    private final DistributionLocator locator = new DistributionLocator();
+
+    public Wrapper() {
+        scriptFile = "gradlew";
+        jarFile = "gradle/wrapper/gradle-wrapper.jar";
+        distributionPath = DEFAULT_DISTRIBUTION_PARENT_NAME;
+        archivePath = DEFAULT_DISTRIBUTION_PARENT_NAME;
+        gradleVersion = GradleVersion.current();
+    }
+
+    @TaskAction
+    void generate() {
+        File jarFileDestination = getJarFile();
+        File unixScript = getScriptFile();
+        FileResolver resolver = getServices().get(FileLookup.class).getFileResolver(unixScript.getParentFile());
+        String jarFileRelativePath = resolver.resolveAsRelativePath(jarFileDestination);
+
+        writeProperties(getPropertiesFile());
+
+        URL jarFileSource = Wrapper.class.getResource("/gradle-wrapper.jar");
+        if (jarFileSource == null) {
+            throw new GradleException("Cannot locate wrapper JAR resource.");
+        }
+        GFileUtils.copyURLToFile(jarFileSource, jarFileDestination);
+
+        StartScriptGenerator generator = new StartScriptGenerator();
+        generator.setApplicationName("Gradle");
+        generator.setMainClassName(GradleWrapperMain.class.getName());
+        generator.setClasspath(WrapUtil.toList(jarFileRelativePath));
+        generator.setOptsEnvironmentVar("GRADLE_OPTS");
+        generator.setExitEnvironmentVar("GRADLE_EXIT_CONSOLE");
+        generator.setAppNameSystemProperty("org.gradle.appname");
+        generator.setScriptRelPath(unixScript.getName());
+        generator.generateUnixScript(unixScript);
+        generator.generateWindowsScript(getBatchScript());
+    }
+
+    private void writeProperties(File propertiesFileDestination) {
+        Properties wrapperProperties = new Properties();
+        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_URL_PROPERTY, getDistributionUrl());
+        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY, distributionBase.toString());
+        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY, distributionPath);
+        wrapperProperties.put(WrapperExecutor.ZIP_STORE_BASE_PROPERTY, archiveBase.toString());
+        wrapperProperties.put(WrapperExecutor.ZIP_STORE_PATH_PROPERTY, archivePath);
+        GUtil.saveProperties(wrapperProperties, propertiesFileDestination);
+    }
+
+    /**
+     * Returns the file to write the wrapper script to.
+     */
+    @OutputFile
+    public File getScriptFile() {
+        return getProject().file(scriptFile);
+    }
+
+    public void setScriptFile(Object scriptFile) {
+        this.scriptFile = scriptFile;
+    }
+
+    /**
+     * Returns the file to write the wrapper batch script to.
+     */
+    @OutputFile
+    public File getBatchScript() {
+        File scriptFile = getScriptFile();
+        return new File(scriptFile.getParentFile(), scriptFile.getName().replaceFirst("(\\.[^\\.]+)?$", ".bat"));
+    }
+
+    /**
+     * Returns the file to write the wrapper jar file to.
+     */
+    @OutputFile
+    public File getJarFile() {
+        return getProject().file(jarFile);
+    }
+
+    public void setJarFile(Object jarFile) {
+        this.jarFile = jarFile;
+    }
+
+    /**
+     * Returns the file to write the wrapper properties to.
+     */
+    @OutputFile
+    public File getPropertiesFile() {
+        File jarFileDestination = getJarFile();
+        return new File(jarFileDestination.getParentFile(), jarFileDestination.getName().replaceAll("\\.jar$",
+                ".properties"));
+    }
+
+    /**
+     * Returns the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
+     * distribution base directory
+     *
+     * @see #setDistributionPath(String)
+     */
+    public String getDistributionPath() {
+        return distributionPath;
+    }
+
+    /**
+     * Sets the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
+     * distribution base directory
+     *
+     * @see #setDistributionPath(String)
+     */
+    public void setDistributionPath(String distributionPath) {
+        this.distributionPath = distributionPath;
+    }
+
+    /**
+     * Returns the gradle version for the wrapper.
+     *
+     * @see #setGradleVersion(String)
+     */
+    public String getGradleVersion() {
+        return gradleVersion.getVersion();
+    }
+
+    /**
+     * The version of the gradle distribution required by the wrapper. This is usually the same version of Gradle you
+     * use for building your project.
+     */
+    public void setGradleVersion(String gradleVersion) {
+        this.gradleVersion = GradleVersion.version(gradleVersion);
+    }
+
+    /**
+     * The URL to download the gradle distribution from.
+     *
+     * <p>If not set, the download URL is the default for the specified {@link #getGradleVersion()}.
+     *
+     * <p>If {@link #getGradleVersion()} is not set, will return null.
+     *
+     * <p>The wrapper downloads a certain distribution only once and caches it. If your distribution base is the
+     * project, you might submit the distribution to your version control system. That way no download is necessary at
+     * all. This might be in particular interesting, if you provide a custom gradle snapshot to the wrapper, because you
+     * don't need to provide a download server then.
+     */
+    @Input
+    public String getDistributionUrl() {
+        if (distributionUrl != null) {
+            return distributionUrl;
+        } else if (gradleVersion != null) {
+            return locator.getDistributionFor(gradleVersion).toString();
+        } else {
+            return null;
+        }
+    }
+
+    public void setDistributionUrl(String url) {
+        this.distributionUrl = url;
+    }
+
+    /**
+     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
+     * the gradle user home dir.
+     */
+    public PathBase getDistributionBase() {
+        return distributionBase;
+    }
+
+    /**
+     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
+     * the gradle user home dir.
+     */
+    public void setDistributionBase(PathBase distributionBase) {
+        this.distributionBase = distributionBase;
+    }
+
+    /**
+     * Returns the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is
+     * relative to the archive base directory.
+     */
+    public String getArchivePath() {
+        return archivePath;
+    }
+
+    /**
+     * Set's the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is relative
+     * to the parent dir specified with {@link #getArchiveBase()}.
+     */
+    public void setArchivePath(String archivePath) {
+        this.archivePath = archivePath;
+    }
+
+    /**
+     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
+     * gradle user home dir.
+     */
+    public PathBase getArchiveBase() {
+        return archiveBase;
+    }
+
+    /**
+     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
+     * gradle user home dir.
+     */
+    public void setArchiveBase(PathBase archiveBase) {
+        this.archiveBase = archiveBase;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java b/subprojects/build-init/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java
rename to subprojects/build-init/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/BuildInitPlugin.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/BuildInitPlugin.groovy
new file mode 100644
index 0000000..0120c8e
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/BuildInitPlugin.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.buildinit.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.buildinit.tasks.InitBuild
+
+ at Incubating
+class BuildInitPlugin implements Plugin<Project> {
+    public static final String INIT_BUILD_TASK_NAME = "init"
+    public static final String GROUP = 'Build Setup'
+
+    void apply(Project project) {
+        Task init = project.tasks.create(INIT_BUILD_TASK_NAME, InitBuild)
+        init.group = GROUP
+        init.description = "Initializes a new Gradle build. [incubating]"
+        Closure setupCanBeSkipped = {
+            if (project.file("build.gradle").exists()) {
+                return ("The build file 'build.gradle' already exists. Skipping build initialization.")
+            }
+            if (project.buildFile?.exists()) {
+                return ("The build file '$project.buildFile.name' already exists. Skipping build initialization.")
+            }
+            if (project.file("settings.gradle").exists()) {
+                return ("The settings file 'settings.gradle' already exists. Skipping build initialization.")
+            }
+            if (project.subprojects.size() > 0) {
+                return ("This Gradle project appears to be part of an existing multi-project Gradle build. Skipping build initialization.")
+            }
+            return null
+        }
+        init.onlyIf {
+            def skippedMsg = setupCanBeSkipped()
+            if (skippedMsg) {
+                project.logger.warn skippedMsg
+                return false
+            }
+            return true
+        }
+
+        if (!setupCanBeSkipped()) {
+            init.dependsOn("wrapper")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/WrapperPlugin.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/WrapperPlugin.groovy
new file mode 100644
index 0000000..46e6ea7
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/WrapperPlugin.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.buildinit.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.tasks.wrapper.Wrapper
+
+ at Incubating
+class WrapperPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        Task wrapper = project.tasks.create("wrapper", Wrapper)
+        wrapper.group = BuildInitPlugin.GROUP
+        wrapper.description = "Generates Gradle wrapper files. [incubating]"
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BasicTemplateBasedProjectInitDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BasicTemplateBasedProjectInitDescriptor.java
new file mode 100644
index 0000000..50405d4
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BasicTemplateBasedProjectInitDescriptor.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.util.GUtil;
+
+public class BasicTemplateBasedProjectInitDescriptor extends TemplateBasedProjectInitDescriptor{
+    public BasicTemplateBasedProjectInitDescriptor(TemplateOperationFactory templateOperationFactory , TemplateOperation settingsTemplateOperation) {
+        register(settingsTemplateOperation);
+        register(templateOperationFactory.newTemplateOperation()
+                        .withTemplate("build.gradle.template")
+                        .withTarget("build.gradle")
+                        .withDocumentationBindings(GUtil.map("ref_userguide_java_tutorial", "tutorial_java_projects"))
+                        .create()
+        );
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitAutoApplyAction.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitAutoApplyAction.java
new file mode 100644
index 0000000..b4eada8
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitAutoApplyAction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.configuration.project.ProjectConfigureAction;
+
+public class BuildInitAutoApplyAction implements ProjectConfigureAction {
+
+    public void execute(final ProjectInternal projectInternal) {
+        if (projectInternal.getParent() == null) {
+            projectInternal.getTasks().addPlaceholderAction("init", new Runnable() {
+                public void run() {
+                    projectInternal.getPlugins().apply("build-init");
+                }
+            });
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitException.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitException.java
new file mode 100644
index 0000000..5c4ee99
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.GradleException;
+
+class BuildInitException extends GradleException{
+    BuildInitException(String message){
+        super(message);
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitServices.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitServices.java
new file mode 100644
index 0000000..03d1db9
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitServices.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider;
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.PluginServiceRegistry;
+
+/**
+ * Provides the various build initialization services.
+ */
+public class BuildInitServices implements PluginServiceRegistry {
+    public void registerGlobalServices(ServiceRegistration registration) {
+    }
+
+    public void registerBuildServices(ServiceRegistration registration) {
+    }
+
+    public void registerProjectServices(ServiceRegistration registration) {
+        registration.addProvider(new ProjectScopeBuildInitServices());
+    }
+
+    private static class ProjectScopeBuildInitServices {
+        ProjectLayoutSetupRegistry createProjectLayoutSetupRegistry(MavenSettingsProvider mavenSettingsProvider, DocumentationRegistry documentationRegistry, GradleInternal gradle) {
+            return new ProjectLayoutSetupRegistryFactory(mavenSettingsProvider, documentationRegistry, gradle.getRootProject().getFileResolver()).createProjectLayoutSetupRegistry();
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitTypeIds.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitTypeIds.java
new file mode 100644
index 0000000..1f22da6
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/BuildInitTypeIds.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+public abstract class BuildInitTypeIds {
+
+    private BuildInitTypeIds() {}
+
+    public static final String BASIC = "basic";
+    public static final String POM = "pom";
+    public static final String JAVA_LIBRARY = "java-library";
+    public static final String GROOVY_LIBRARY = "groovy-library";
+    public static final String SCALA_LIBRARY = "scala-library";
+
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ConditionalTemplateOperation.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ConditionalTemplateOperation.groovy
new file mode 100644
index 0000000..2a4a019
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ConditionalTemplateOperation.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.internal.Factory;
+
+class ConditionalTemplateOperation implements TemplateOperation {
+
+    private final TemplateOperation[] optionalOperations
+    private final Factory<Boolean> condition
+
+    ConditionalTemplateOperation(Factory<Boolean> precondition, TemplateOperation... optionalOperations){
+        this.condition = precondition
+        this.optionalOperations = optionalOperations
+    }
+
+    void generate() {
+        if(condition.create()){
+            optionalOperations.each {
+                it.generate()
+            }
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/DefaultTemplateLibraryVersionProvider.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/DefaultTemplateLibraryVersionProvider.groovy
new file mode 100644
index 0000000..57f4b70
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/DefaultTemplateLibraryVersionProvider.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+
+class DefaultTemplateLibraryVersionProvider implements TemplateLibraryVersionProvider{
+
+    private final Properties libraryVersions = new Properties()
+
+    public DefaultTemplateLibraryVersionProvider() {
+        libraryVersions.load(getClass().getResourceAsStream("/org/gradle/buildinit/tasks/templates/library-versions.properties"))
+    }
+
+    String getVersion(String module){
+        libraryVersions.getProperty(module)
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/GroovyLibraryProjectInitDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/GroovyLibraryProjectInitDescriptor.java
new file mode 100644
index 0000000..67ff13d
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/GroovyLibraryProjectInitDescriptor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GUtil;
+
+public class GroovyLibraryProjectInitDescriptor extends LanguageLibraryProjectInitDescriptor{
+
+    public GroovyLibraryProjectInitDescriptor(TemplateOperationFactory templateOperationFactory, final FileResolver fileResolver, TemplateLibraryVersionProvider libraryVersionProvider, TemplateOperation delegate) {
+        super("groovy", templateOperationFactory, fileResolver);
+        register(delegate);
+
+        register(templateOperationFactory.newTemplateOperation()
+                .withTemplate("groovylibrary/build.gradle.template")
+                .withTarget("build.gradle")
+                .withDocumentationBindings(GUtil.map("ref_userguide_groovy_tutorial", "tutorial_groovy_projects"))
+                .withBindings(GUtil.map("groovyVersion", libraryVersionProvider.getVersion("groovy")))
+                .withBindings(GUtil.map("junitVersion", libraryVersionProvider.getVersion("junit")))
+                .create()
+        );
+
+        TemplateOperation groovyLibTemplateOperation = fromClazzTemplate("groovylibrary/Library.groovy.template", "main");
+        TemplateOperation groovyTestTemplateOperation = fromClazzTemplate("groovylibrary/LibraryTest.groovy.template", "test");
+
+        register(whenNoSourcesAvailable(groovyLibTemplateOperation, groovyTestTemplateOperation));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/JavaLibraryProjectInitDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/JavaLibraryProjectInitDescriptor.java
new file mode 100644
index 0000000..1206244
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/JavaLibraryProjectInitDescriptor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GUtil;
+
+public class JavaLibraryProjectInitDescriptor extends LanguageLibraryProjectInitDescriptor {
+
+    public JavaLibraryProjectInitDescriptor(TemplateOperationFactory templateOperationFactory,
+                                            FileResolver fileResolver,
+                                            TemplateLibraryVersionProvider libraryVersionProvider,
+                                            TemplateOperation delegate) {
+        super("java", templateOperationFactory, fileResolver);
+
+        register(delegate);
+
+        register(templateOperationFactory.newTemplateOperation()
+                .withTemplate("javalibrary/build.gradle.template")
+                .withTarget("build.gradle")
+                .withDocumentationBindings(GUtil.map("ref_userguide_java_tutorial", "tutorial_java_projects"))
+                .withBindings(GUtil.map("junitVersion", libraryVersionProvider.getVersion("junit")))
+                .create()
+        );
+
+        TemplateOperation javalibraryTemplateOperation = fromClazzTemplate("javalibrary/Library.java.template", "main");
+        TemplateOperation javalibraryTestTemplateOperation = fromClazzTemplate("javalibrary/LibraryTest.java.template", "test");
+        register(whenNoSourcesAvailable(javalibraryTemplateOperation, javalibraryTestTemplateOperation));
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/LanguageLibraryProjectInitDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/LanguageLibraryProjectInitDescriptor.java
new file mode 100644
index 0000000..c0fb366
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/LanguageLibraryProjectInitDescriptor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.Factory;
+
+public class LanguageLibraryProjectInitDescriptor extends TemplateBasedProjectInitDescriptor {
+
+    private final String language;
+    private final FileResolver fileResolver;
+    private final TemplateOperationFactory templateOperationFactory;
+
+    public LanguageLibraryProjectInitDescriptor(String language, TemplateOperationFactory templateOperationFactory, FileResolver fileResolver){
+        this.language = language;
+        this.fileResolver = fileResolver;
+        this.templateOperationFactory = templateOperationFactory;
+    }
+
+    protected TemplateOperation whenNoSourcesAvailable(TemplateOperation... operations) {
+        return new ConditionalTemplateOperation(new Factory<Boolean>() {
+            public Boolean create() {
+                return fileResolver.resolveFilesAsTree(String.format("src/main/%s", language)).isEmpty() || fileResolver.resolveFilesAsTree(String.format("src/test/%s", language)).isEmpty();
+            }
+        }, operations);
+    }
+
+    protected TemplateOperation fromClazzTemplate(String clazzTemplate, String sourceSetName) {
+        String targetFileName = clazzTemplate.substring(clazzTemplate.lastIndexOf("/") + 1).replace(".template", "");
+        return templateOperationFactory.newTemplateOperation()
+                .withTemplate(clazzTemplate)
+                .withTarget(String.format("src/%s/%s/%s", sourceSetName, language, targetFileName))
+                .create();
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/PomProjectInitDescriptor.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/PomProjectInitDescriptor.groovy
new file mode 100644
index 0000000..7d42420
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/PomProjectInitDescriptor.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.buildinit.plugins.internal
+
+import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.buildinit.plugins.internal.maven.Maven2Gradle
+import org.gradle.buildinit.plugins.internal.maven.MavenConversionException
+import org.gradle.buildinit.plugins.internal.maven.MavenProjectsCreator
+import org.gradle.util.SingleMessageLogger
+
+class PomProjectInitDescriptor implements ProjectInitDescriptor {
+    private final MavenSettingsProvider settingsProvider
+    private final FileResolver fileResolver
+
+
+    PomProjectInitDescriptor(FileResolver fileResolver, MavenSettingsProvider mavenSettingsProvider) {
+        this.fileResolver = fileResolver
+        this.settingsProvider = mavenSettingsProvider
+    }
+
+    void generate() {
+        SingleMessageLogger.incubatingFeatureUsed("Maven to Gradle conversion")
+        def pom = fileResolver.resolve("pom.xml")
+        try {
+            def settings = settingsProvider.buildSettings()
+            def mavenProjects = new MavenProjectsCreator().create(settings, pom)
+            new Maven2Gradle(mavenProjects).convert()
+        } catch (Exception exception) {
+            throw new MavenConversionException("Could not convert Maven POM $pom to a Gradle build.", exception)
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectInitDescriptor.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectInitDescriptor.groovy
new file mode 100644
index 0000000..d4a973c
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectInitDescriptor.groovy
@@ -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.buildinit.plugins.internal
+
+interface ProjectInitDescriptor extends TemplateOperation{
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistry.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistry.groovy
new file mode 100644
index 0000000..bd57748
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistry.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.buildinit.plugins.internal
+
+import org.gradle.api.GradleException
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.util.CollectionUtils
+
+class ProjectLayoutSetupRegistry{
+
+    private final Logger logger = Logging.getLogger(ProjectLayoutSetupRegistry.class);
+    private final Map<String, ProjectInitDescriptor> registeredProjectDescriptors = new HashMap<String, ProjectInitDescriptor>();
+
+    void add(String descriptorID, ProjectInitDescriptor descriptor) {
+        if (registeredProjectDescriptors.containsKey(descriptorID)) {
+            throw new GradleException("ProjectDescriptor with ID '${descriptorID}' already registered.")
+        }
+        registeredProjectDescriptors.put(descriptorID, descriptor)
+        logger.debug("registered setupDescriptor {}", descriptorID)
+    }
+
+    ProjectInitDescriptor get(String type) {
+        return registeredProjectDescriptors.get(type)
+    }
+
+    List<ProjectInitDescriptor> getAll() {
+        return CollectionUtils.toList(registeredProjectDescriptors.values())
+    }
+
+    List<String> getSupportedTypes() {
+        return CollectionUtils.sort(registeredProjectDescriptors.keySet())
+    }
+
+    boolean supports(String type) {
+        return get(type) != null
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistryFactory.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistryFactory.groovy
new file mode 100644
index 0000000..f310ed9
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistryFactory.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.buildinit.plugins.internal
+
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
+import org.gradle.api.internal.file.FileResolver
+
+class ProjectLayoutSetupRegistryFactory {
+    private final DocumentationRegistry documentationRegistry
+    private final MavenSettingsProvider mavenSettingsProvider
+    private final FileResolver fileResolver
+
+    ProjectLayoutSetupRegistry createProjectLayoutSetupRegistry() {
+        DefaultTemplateLibraryVersionProvider libraryVersionProvider = new DefaultTemplateLibraryVersionProvider();
+        ProjectLayoutSetupRegistry registry = new ProjectLayoutSetupRegistry()
+
+        // TODO maybe referencing the implementation class here is enough and instantiation
+        // should be defererred when descriptor is requested.
+        TemplateOperationFactory templateOperationBuilder = new TemplateOperationFactory("/org/gradle/buildinit/tasks/templates", fileResolver, documentationRegistry)
+
+        ProjectInitDescriptor simpleGlobalFilesBuildSettingsDescriptor = new SimpleGlobalFilesBuildSettingsDescriptor(templateOperationBuilder, fileResolver);
+
+        registry.add(BuildInitTypeIds.JAVA_LIBRARY,
+                new JavaLibraryProjectInitDescriptor(templateOperationBuilder, fileResolver, libraryVersionProvider, simpleGlobalFilesBuildSettingsDescriptor))
+
+
+        registry.add(BuildInitTypeIds.SCALA_LIBRARY,
+                new ScalaLibraryProjectInitDescriptor(templateOperationBuilder, fileResolver, libraryVersionProvider, simpleGlobalFilesBuildSettingsDescriptor));
+
+        registry.add(BuildInitTypeIds.GROOVY_LIBRARY,
+                        new GroovyLibraryProjectInitDescriptor(templateOperationBuilder, fileResolver, libraryVersionProvider, simpleGlobalFilesBuildSettingsDescriptor));
+
+        TemplateOperation basicBuildFile  =
+
+        registry.add(BuildInitTypeIds.BASIC, new BasicTemplateBasedProjectInitDescriptor(templateOperationBuilder, simpleGlobalFilesBuildSettingsDescriptor));
+        registry.add(BuildInitTypeIds.POM, new PomProjectInitDescriptor(fileResolver, mavenSettingsProvider))
+        return registry
+    }
+
+    public ProjectLayoutSetupRegistryFactory(MavenSettingsProvider mavenSettingsProvider,
+                                             DocumentationRegistry documentationRegistry,
+                                             FileResolver fileResolver) {
+        this.mavenSettingsProvider = mavenSettingsProvider
+        this.documentationRegistry = documentationRegistry
+        this.fileResolver = fileResolver
+    }
+
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ScalaLibraryProjectInitDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ScalaLibraryProjectInitDescriptor.java
new file mode 100644
index 0000000..84e9016
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/ScalaLibraryProjectInitDescriptor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GUtil;
+
+public class ScalaLibraryProjectInitDescriptor extends LanguageLibraryProjectInitDescriptor{
+
+    public ScalaLibraryProjectInitDescriptor(TemplateOperationFactory templateOperationFactory, final FileResolver fileResolver, TemplateLibraryVersionProvider libraryVersionProvider, TemplateOperation delegate) {
+        super("scala", templateOperationFactory, fileResolver);
+
+        register(delegate);
+        register(templateOperationFactory.newTemplateOperation()
+                .withTemplate("scalalibrary/build.gradle.template")
+                .withTarget("build.gradle")
+                .withDocumentationBindings(GUtil.map("ref_userguide_scala_plugin", "scala_plugin"))
+                .withBindings(GUtil.map("scalaVersion", libraryVersionProvider.getVersion("scala")))
+                .withBindings(GUtil.map("scalaLibraryVersion", libraryVersionProvider.getVersion("scala-library")))
+                .withBindings(GUtil.map("scalaTestModule", "scalatest_" + libraryVersionProvider.getVersion("scala")))
+                .withBindings(GUtil.map("scalaTestVersion", libraryVersionProvider.getVersion("scalatest")))
+                .withBindings(GUtil.map("scalaXmlModule", "scala-xml_" + libraryVersionProvider.getVersion("scala")))
+                .withBindings(GUtil.map("scalaXmlVersion", libraryVersionProvider.getVersion("scala-xml")))
+                .withBindings(GUtil.map("junitVersion", libraryVersionProvider.getVersion("junit")))
+                .create()
+        );
+
+        TemplateOperation scalaLibTemplateOperation = fromClazzTemplate("scalalibrary/Library.scala.template", "main");
+        TemplateOperation scalaTestTemplateOperation = fromClazzTemplate("scalalibrary/LibrarySuite.scala.template", "test");
+
+        register(whenNoSourcesAvailable(scalaLibTemplateOperation, scalaTestTemplateOperation));
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/SimpleGlobalFilesBuildSettingsDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/SimpleGlobalFilesBuildSettingsDescriptor.java
new file mode 100644
index 0000000..62278f8
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/SimpleGlobalFilesBuildSettingsDescriptor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GUtil;
+
+public class SimpleGlobalFilesBuildSettingsDescriptor extends TemplateBasedProjectInitDescriptor {
+    public SimpleGlobalFilesBuildSettingsDescriptor(TemplateOperationFactory templateOperationBuilder, FileResolver fileResolver) {
+        register(templateOperationBuilder.newTemplateOperation()
+                    .withTemplate("settings.gradle.template")
+                    .withTarget("settings.gradle")
+                    .withDocumentationBindings(GUtil.map("ref_userguide_multiproject", "multi_project_builds"))
+                    .withBindings(GUtil.map("rootProjectName", fileResolver.resolve(".").getName()))
+                    .create()
+        );
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperation.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperation.groovy
new file mode 100644
index 0000000..e733cf4
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperation.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import groovy.text.SimpleTemplateEngine
+import groovy.text.Template
+
+class SimpleTemplateOperation implements TemplateOperation {
+    private final URL templateURL
+    private final File target
+    private final Map<String, TemplateValue> bindings
+
+    public SimpleTemplateOperation(URL templateURL, File target, Map<String, TemplateValue>  bindings) {
+        if (templateURL == null) {
+            throw new BuildInitException("Template URL must not be null")
+        }
+        if (target == null) {
+            throw new BuildInitException("Target file must not be null")
+        }
+        this.templateURL = templateURL
+        this.bindings = bindings
+        this.target = target
+    }
+
+    void generate() {
+        target.parentFile.mkdirs()
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine()
+        Template template = templateEngine.createTemplate(templateURL.text)
+        target.withWriter("utf-8") { writer ->
+            template.make(bindings).writeTo(writer)
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateBasedProjectInitDescriptor.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateBasedProjectInitDescriptor.java
new file mode 100644
index 0000000..db67e35
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateBasedProjectInitDescriptor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class TemplateBasedProjectInitDescriptor implements ProjectInitDescriptor {
+    private List<TemplateOperation> templateOperations = new ArrayList<TemplateOperation>();
+
+    public void generate() {
+        for (TemplateOperation templateOperation : templateOperations) {
+            templateOperation.generate();
+        }
+    }
+
+    protected void register(TemplateOperation templateOperation) {
+        this.templateOperations.add(templateOperation);
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateLibraryVersionProvider.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateLibraryVersionProvider.groovy
new file mode 100644
index 0000000..3e34f28
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateLibraryVersionProvider.groovy
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+public interface TemplateLibraryVersionProvider {
+    String getVersion(String module)
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateOperation.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateOperation.groovy
new file mode 100644
index 0000000..a1a96e1
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateOperation.groovy
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+public interface TemplateOperation {
+    void generate()
+}
\ No newline at end of file
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateOperationFactory.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateOperationFactory.java
new file mode 100644
index 0000000..af316f8
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateOperationFactory.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.buildinit.plugins.internal;
+
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.net.URL;
+import java.text.DateFormat;
+import java.util.*;
+
+public class TemplateOperationFactory {
+
+    private final String templatepackage;
+    private final FileResolver fileResolver;
+    private final DocumentationRegistry documentationRegistry;
+    private final Map defaultBindings;
+
+    public TemplateOperationFactory(String templatepackage, FileResolver fileResolver, DocumentationRegistry documentationRegistry) {
+        this.documentationRegistry = documentationRegistry;
+        this.fileResolver = fileResolver;
+        this.templatepackage = templatepackage;
+        this.defaultBindings = loadDefaultBindings();
+    }
+
+    private Map<String, String> loadDefaultBindings() {
+        String now = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date());
+        Map<String, String> map = new LinkedHashMap<String, String>(3);
+        map.put("genDate", now);
+        map.put("genUser", System.getProperty("user.name"));
+        map.put("genGradleVersion", GradleVersion.current().toString());
+        return map;
+    }
+
+    public TemplateOperationBuilder newTemplateOperation() {
+        return new TemplateOperationBuilder(defaultBindings);
+    }
+
+    public class TemplateOperationBuilder {
+        private File target;
+        private Map<String, String> bindings =  new HashMap<String, String>();
+        private URL templateUrl;
+
+        public TemplateOperationBuilder(Map defaultBindings) {
+            this.bindings.putAll(defaultBindings);
+        }
+
+        public TemplateOperationBuilder withTemplate(final String relativeTemplatePath) {
+            this.templateUrl = getClass().getResource(templatepackage + "/" + relativeTemplatePath);
+            return this;
+        }
+
+        public TemplateOperationBuilder withTemplate(URL templateURL) {
+            this.templateUrl = templateURL;
+            return this;
+        }
+
+        public TemplateOperationBuilder withTarget(String targetFilePath) {
+            this.target = fileResolver.resolve(targetFilePath);
+            return this;
+        }
+
+        public TemplateOperationBuilder withDocumentationBindings(Map<String, String> documentationBindings) {
+            for (Map.Entry<String, String> entry : documentationBindings.entrySet()){
+                bindings.put(entry.getKey(), documentationRegistry.getDocumentationFor(entry.getValue()));
+            }
+            return this;
+        }
+
+        public TemplateOperationBuilder withBindings(Map<String, String> bindings) {
+            this.bindings.putAll(bindings);
+            return this;
+        }
+
+        public TemplateOperation create() {
+            final Set<Map.Entry<String, String>> entries = bindings.entrySet();
+            Map wrappedBindings = new HashMap(entries.size());
+            for (Map.Entry<String, String> entry : entries) {
+                wrappedBindings.put(entry.getKey(), new TemplateValue(entry.getValue()));
+            }
+            return new SimpleTemplateOperation(templateUrl, target, wrappedBindings);
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateValue.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateValue.java
new file mode 100644
index 0000000..d635689
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/TemplateValue.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal;
+
+public class TemplateValue {
+    private final String value;
+
+    public TemplateValue(String value) {
+        this.value = value;
+    }
+
+    public String getGroovyComment() {
+        return value.replace("\\", "\\\\");
+    }
+
+    public String getGroovyString() {
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < value.length(); i++) {
+            char ch = value.charAt(i);
+            switch (ch) {
+                case '\\':
+                    result.append('\\').append('\\');
+                    break;
+                case '\'':
+                    result.append('\\').append('\'');
+                    break;
+                case '\n':
+                    result.append('\\').append('n');
+                    break;
+                case '\r':
+                    result.append('\\').append('r');
+                    break;
+                case '\t':
+                    result.append('\\').append('t');
+                    break;
+                case '\f':
+                    result.append('\\').append('f');
+                    break;
+                case '\b':
+                    result.append('\\').append('b');
+                    break;
+                default:
+                    result.append(ch);
+            }
+        }
+        return result.toString();
+    }
+
+    @Override
+    public String toString() {
+        return String.format(">>>%s<<<", value);
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/WrapperPluginAutoApplyAction.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/WrapperPluginAutoApplyAction.groovy
new file mode 100644
index 0000000..818124a
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/WrapperPluginAutoApplyAction.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.configuration.project.ProjectConfigureAction
+
+class WrapperPluginAutoApplyAction implements ProjectConfigureAction {
+    void execute(ProjectInternal projectInternal) {
+        if (projectInternal.getParent() == null) {
+            projectInternal.tasks.addPlaceholderAction("wrapper", new Runnable() {
+                void run() {
+                    projectInternal.getPlugins().apply("wrapper")
+                }
+            })
+        }
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/Maven2Gradle.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/Maven2Gradle.groovy
new file mode 100644
index 0000000..b16a908
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/Maven2Gradle.groovy
@@ -0,0 +1,526 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.buildinit.plugins.internal.maven
+
+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.
+ */
+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
+
+        //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) {
+            def allProjects = this.effectivePom.project
+            qualifiedNames = generateSettings(workingDir.getName(), allProjects[0].artifactId, allProjects);
+            def dependencies = [:];
+            allProjects.each { project ->
+                dependencies[project.artifactId.text()] = getDependencies(project, allProjects)
+            }
+
+            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)
+                File submoduleBuildFile = new File(projectDir(module), 'build.gradle')
+                def group = ''
+                if (module.groupId != allProjects[0].groupId) {
+                    group = "group = '${module.groupId}'"
+                }
+                String moduleBuild = "${group}\n"
+                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 (module.name) {
+                    moduleBuild += "description = '${module.name}'\n"
+                }
+
+
+                if (hasDependencies) {
+                    moduleBuild += moduleDependencies
+                }
+
+                moduleBuild += testNg(moduleDependencies)
+
+                if (submoduleBuildFile.exists()) {
+                    submoduleBuildFile.renameTo(new File("build.gradle.bak"))
+                }
+                def packageTests = packageTests(module);
+                if (packageTests) {
+                    moduleBuild += packageTests;
+                }
+                submoduleBuildFile.text = moduleBuild
+            }
+            //TODO deployment
+        } else {//simple
+            build = """apply plugin: 'java'
+apply plugin: 'maven'
+
+${getArtifactData(this.effectivePom)}
+
+description = \"""${this.effectivePom.name}\"""
+
+${compilerSettings(this.effectivePom, "")}
+${globalExclusions(this.effectivePom)}
+
+"""
+
+            Set<String> repoSet = new LinkedHashSet<String>();
+            getRepositoriesForModule(this.effectivePom, repoSet)
+            String repos = """repositories {
+        $localRepoUri
+"""
+            repoSet.each {
+                repos = "${repos} ${it}\n"
+            }
+            build += "${repos}}\n"
+            String dependencies = getDependencies(this.effectivePom, null)
+            build += dependencies
+
+            String packageTests = packageTests(this.effectivePom);
+            if (packageTests) {
+                build += '//packaging tests'
+                build += packageTests;
+            }
+            generateSettings(workingDir.getName(), this.effectivePom.artifactId, null);
+        }
+        def buildFile = new File("build.gradle")
+        if (buildFile.exists()) {
+            buildFile.renameTo(new File("build.gradle.bak"))
+        }
+        buildFile.text = build
+    }
+
+    def globalExclusions = { project ->
+        def exclusions = ''
+        def enforcerPlugin = plugin('maven-enforcer-plugin', project)
+        def enforceGoal = pluginGoal('enforce', enforcerPlugin)
+        if (enforceGoal) {
+            exclusions += 'configurations.all {\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 ->
+            def parentIsPartOfThisBuild = allProjects.find { proj ->
+                proj.artifactId == project.parent.artifactId && proj.groupId == project.parent.groupId
+            }
+            project.parent.text().length() > 0 && parentIsPartOfThisBuild && (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()) {
+            def parentInBuild = allProjects.find { proj ->
+                proj.artifactId.text() == project.parent.artifactId.text()
+            }
+            if (parentInBuild) {
+                generateFqn(parentInBuild, allProjects, buffer)
+            }
+        }
+    }
+
+    def localRepoUri = {
+        """mavenLocal()
+    """
+    }
+
+    private String getArtifactData(project) {
+        return """group = '$project.groupId'
+version = '$project.version'""";
+    }
+
+    private String getRepositoriesForProjects(projects) {
+        String repos = """repositories {
+    ${localRepoUri()}
+"""
+        def repoSet = new LinkedHashSet<String>();
+        projects.each {
+            getRepositoriesForModule(it, repoSet)
+        }
+        repoSet.each {
+            repos = "${repos}${it}\n"
+        }
+        repos = "${repos}  }\n"
+        return repos
+    }
+
+    private void getRepositoriesForModule(module, repoSet) {
+        module.repositories.repository.each {
+            repoSet.add("    maven { 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 providedMessage = "";
+                if (!war && scope == 'providedCompile') {
+                    scope = 'compile'
+                    providedMessage = '''\
+                       /* This dependency was originally in the Maven provided scope, but the project was not of type war.
+                       This behavior is not yet supported by Gradle, so this dependency has been converted to a compile dependency.
+                       Please review and delete this closure when resolved. */
+                       '''.stripIndent(16)
+                }
+                def exclusions = mavenDependency.exclusions.exclusion
+                if (exclusions.size() > 0 || providedMessage != "") {
+                    createComplexDependency(mavenDependency, sb, scope, providedMessage)
+                } 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"""
+        }
+        sourceSetStr
+    }
+
+
+    def packageTests = { project ->
+        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) {
+            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)
+
+        List<String> moduleNames = new ArrayList<String>();
+        def artifactIdToDir = [:]
+        if (projects) {
+            modulePoms.each { project ->
+                def fqn = fqn(project, projects)
+                File projectDirectory = projectDir(project)
+                // don't add project if it's the rootproject
+                if (!workingDir.equals(projectDirectory)) {
+                    artifactIdToDir[fqn] = GFileUtils.relativePath(workingDir, projectDirectory)
+                    moduleNames.add(fqn)
+                }
+            }
+        }
+        File settingsFile = new File("settings.gradle")
+        if (settingsFile.exists()) {
+            settingsFile.renameTo(new File("settings.gradle.bak"))
+        }
+        StringBuffer settingsText = new StringBuffer(projectName)
+        if (moduleNames.size() > 0) {
+            moduleNames.each {
+                settingsText.append("include '$it'\n")
+            }
+        }
+
+        artifactIdToDir.each { entry ->
+            settingsText.append("""
+project('$entry.key').projectDir = """ + '"$rootDir/' + "${entry.value}" + '" as File')
+        }
+        settingsFile.text = settingsText.toString()
+        return qualifiedNames
+    }
+
+/**
+ * complex print statement does one extra task which is
+ * iterate over each <exclusion> node and print out the artifact id.
+ * It also provides review comments for the user.
+ */
+    private def createComplexDependency(it, build, scope, providedMessage) {
+        build.append("    ${scope}(${contructSignature(it)}) {\n")
+        it.exclusions.exclusion.each() {
+            build.append("exclude(module: '${it.artifactId}')\n")
+        }
+        if (providedMessage) {
+            build.append(providedMessage)
+        }
+        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 its 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/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenConversionException.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenConversionException.java
new file mode 100644
index 0000000..e2bb9f4
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenConversionException.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.buildinit.plugins.internal.maven;
+import org.gradle.internal.exceptions.Contextual;
+
+ at Contextual
+public class MavenConversionException extends RuntimeException {
+    public MavenConversionException(String message) {
+        super(message);
+    }
+
+    public MavenConversionException(String message, Throwable cause) {
+        super(message, cause);    
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectXmlWriter.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectXmlWriter.java
new file mode 100644
index 0000000..a304126
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/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.buildinit.plugins.internal.maven;
+
+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;
+
+public class MavenProjectXmlWriter {
+
+    //TODO this class attempts to mimic the behavior of the output of mvn help:effective-pom
+    //instead of this class we should walk the maven project object model (instead of parsing the xml!)
+
+    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 prepareXml(out.toString());
+    }
+
+    String prepareXml(String xml) {
+        return xml.replaceFirst("^<\\?xml.+?\\?>", "");
+    }
+}
diff --git a/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectsCreator.java b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectsCreator.java
new file mode 100644
index 0000000..ddd4924
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectsCreator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal.maven;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.api.Transformer;
+import org.gradle.internal.SystemProperties;
+import org.gradle.mvn3.org.apache.maven.execution.*;
+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.util.CollectionUtils;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+public class MavenProjectsCreator {
+
+    public Set<MavenProject> create(Settings mavenSettings, File pomFile) {
+        if (!pomFile.exists()) {
+            throw new MavenConversionException(String.format("Unable to create Maven project model. The POM file %s does not exist.", pomFile));
+        }
+        try {
+            return createNow(mavenSettings, pomFile);
+        } catch (Exception e) {
+            throw new MavenConversionException(String.format("Unable to create Maven project model using POM %s.", pomFile), 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();
+        final Properties properties = new Properties();
+        properties.putAll(SystemProperties.asMap());
+        executionRequest.setSystemProperties(properties);
+        MavenExecutionRequestPopulator populator = container.lookup(MavenExecutionRequestPopulator.class);
+        populator.populateFromSettings(executionRequest, settings);
+        populator.populateDefaults(executionRequest);
+        ProjectBuildingRequest buildingRequest = executionRequest.getProjectBuildingRequest();
+        buildingRequest.setProcessPlugins(false);
+        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/build-init/src/main/groovy/org/gradle/buildinit/tasks/InitBuild.groovy b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/tasks/InitBuild.groovy
new file mode 100644
index 0000000..91a18d0
--- /dev/null
+++ b/subprojects/build-init/src/main/groovy/org/gradle/buildinit/tasks/InitBuild.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Incubating
+import org.gradle.api.internal.tasks.options.Option
+import org.gradle.api.internal.tasks.options.OptionValues
+import org.gradle.api.tasks.TaskAction
+import org.gradle.buildinit.plugins.internal.BuildInitTypeIds
+import org.gradle.buildinit.plugins.internal.ProjectLayoutSetupRegistry
+
+/**
+ * Generates a Gradle project structure.
+  */
+ at Incubating
+class InitBuild extends DefaultTask {
+    private String type
+
+    ProjectLayoutSetupRegistry projectLayoutRegistry
+
+    /**
+     * The desired type of build to create, defaults to {@value BuildInitTypeIds#POM} if 'pom.xml' is found in project root
+     * if no pom.xml is found, it defaults to {@value BuildInitTypeIds#BASIC}.
+     *
+     * This property can be set via command-line option '--type'.
+     */
+    String getType() {
+        type ?: project.file("pom.xml").exists() ? BuildInitTypeIds.POM : BuildInitTypeIds.BASIC
+    }
+
+    ProjectLayoutSetupRegistry getProjectLayoutRegistry() {
+        if (projectLayoutRegistry == null) {
+            projectLayoutRegistry = services.get(ProjectLayoutSetupRegistry)
+        }
+        return projectLayoutRegistry
+    }
+
+    @TaskAction
+    void setupProjectLayout() {
+        def type = getType()
+        def projectLayoutRegistry = getProjectLayoutRegistry()
+        if (!projectLayoutRegistry.supports(type)) {
+            throw new GradleException("The requested build setup type '${type}' is not supported. Supported types: ${projectLayoutRegistry.supportedTypes.collect{"'$it'"}.sort().join(", ")}.")
+        }
+        projectLayoutRegistry.get(type).generate()
+    }
+
+    @Option(option = "type", description = "Set type of build to create.")
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    @OptionValues("type")
+    List<String> getAvailableBuildTypes(){
+        return getProjectLayoutRegistry().getSupportedTypes();
+    }
+}
diff --git a/subprojects/build-init/src/main/resources/META-INF/gradle-plugins/build-init.properties b/subprojects/build-init/src/main/resources/META-INF/gradle-plugins/build-init.properties
new file mode 100644
index 0000000..7025b44
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/META-INF/gradle-plugins/build-init.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.buildinit.plugins.BuildInitPlugin
diff --git a/subprojects/build-init/src/main/resources/META-INF/gradle-plugins/wrapper.properties b/subprojects/build-init/src/main/resources/META-INF/gradle-plugins/wrapper.properties
new file mode 100644
index 0000000..ad8586c
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/META-INF/gradle-plugins/wrapper.properties
@@ -0,0 +1,17 @@
+#
+# Copyright 2013 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+implementation-class=org.gradle.buildinit.plugins.WrapperPlugin
diff --git a/subprojects/build-init/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction b/subprojects/build-init/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
new file mode 100644
index 0000000..2ebf5cd
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
@@ -0,0 +1,2 @@
+org.gradle.buildinit.plugins.internal.BuildInitAutoApplyAction
+org.gradle.buildinit.plugins.internal.WrapperPluginAutoApplyAction
diff --git a/subprojects/build-init/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry b/subprojects/build-init/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
new file mode 100644
index 0000000..334a0eb
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
@@ -0,0 +1 @@
+org.gradle.buildinit.plugins.internal.BuildInitServices
\ No newline at end of file
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/build.gradle.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/build.gradle.template
new file mode 100644
index 0000000..bd238ce
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/build.gradle.template
@@ -0,0 +1,32 @@
+/*
+ * This build file was auto generated by running the Gradle 'init' task
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * This generated file contains a commented-out sample Java project to get you started.
+ * For more details take a look at the Java Quickstart chapter in the Gradle
+ * user guide available at ${ref_userguide_java_tutorial.groovyComment}
+ */
+
+/*
+// Apply the java plugin to add support for Java
+apply plugin: 'java'
+
+// In this section you declare where to find the dependencies of your project
+repositories {
+    // Use 'maven central' for resolving your dependencies.
+    // You can declare any Maven/Ivy/file repository here.
+    mavenCentral()
+}
+
+// In this section you declare the dependencies for your production and test code
+dependencies {
+    // The production code uses the SLF4J logging API at compile time
+    compile 'org.slf4j:slf4j-api:1.7.5'
+
+    // Declare the dependency for your favourite test framework you want to use in your tests.
+    // TestNG is also supported by the Gradle Test task. Just change the
+    // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
+    // 'test.useTestNG()' to your build script.
+    testCompile "junit:junit:4.11"
+}
+*/
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/Library.groovy.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/Library.groovy.template
new file mode 100644
index 0000000..d218ad7
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/Library.groovy.template
@@ -0,0 +1,11 @@
+/*
+ * This Groovy source file was auto generated by running 'gradle buildInit --type groovy-library'
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * @author ${genUser.groovyComment}, @date ${genDate.groovyComment}
+ */
+class Library {
+    boolean someLibraryMethod() {
+        true
+    }
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/LibraryTest.groovy.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/LibraryTest.groovy.template
new file mode 100644
index 0000000..abf9a29
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/LibraryTest.groovy.template
@@ -0,0 +1,19 @@
+/*
+ * This Spock specification was auto generated by running 'gradle init --type groovy-library'
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * @author ${genUser.groovyComment}, @date ${genDate.groovyComment}
+ */
+
+import spock.lang.Specification
+
+class LibraryTest extends Specification{
+    def "someLibraryMethod returns true"() {
+        setup:
+        Library lib = new Library()
+        when:
+        def result = lib.someLibraryMethod()
+        then:
+        result == true
+    }
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/build.gradle.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/build.gradle.template
new file mode 100644
index 0000000..863b94b
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/groovylibrary/build.gradle.template
@@ -0,0 +1,28 @@
+/*
+ * This build file was auto generated by running the Gradle 'init' task
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * This generated file contains a sample Groovy project to get you started.
+ * For more details take a look at the Groovy Quickstart chapter in the Gradle
+ * user guide available at ${ref_userguide_groovy_tutorial.groovyComment}
+ */
+
+// Apply the groovy plugin to add support for Groovy
+apply plugin: 'groovy'
+
+// In this section you declare where to find the dependencies of your project
+repositories {
+    // Use 'maven central' for resolving your dependencies.
+    // You can declare any Maven/Ivy/file repository here.
+    mavenCentral()
+}
+
+// In this section you declare the dependencies for your production and test code
+dependencies {
+    // We use the latest groovy 2.x version for building this library
+    compile 'org.codehaus.groovy:groovy:${groovyVersion.groovyString}'
+
+    // We use the awesome Spock testing and specification framework
+    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
+    testCompile 'junit:junit:${junitVersion.groovyString}'
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/Library.java.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/Library.java.template
new file mode 100644
index 0000000..7d81cf4
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/Library.java.template
@@ -0,0 +1,11 @@
+/*
+ * This Java source file was auto generated by running 'gradle buildInit --type java-library'
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * @author ${genUser.groovyComment}, @date ${genDate.groovyComment}
+ */
+public class Library {
+    public boolean someLibraryMethod() {
+        return true;
+    }
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/LibraryTest.java.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/LibraryTest.java.template
new file mode 100644
index 0000000..ffa01fa
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/LibraryTest.java.template
@@ -0,0 +1,15 @@
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/*
+ * This Java source file was auto generated by running 'gradle init --type java-library'
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * @author ${genUser.groovyComment}, @date ${genDate.groovyComment}
+ */
+public class LibraryTest {
+    @Test public void testSomeLibraryMethod() {
+        Library classUnderTest = new Library();
+        assertTrue("someLibraryMethod should return 'true'", classUnderTest.someLibraryMethod());
+    }
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/build.gradle.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/build.gradle.template
new file mode 100644
index 0000000..35b3e22
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/javalibrary/build.gradle.template
@@ -0,0 +1,30 @@
+/*
+ * This build file was auto generated by running the Gradle 'init' task
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * This generated file contains a sample Java project to get you started.
+ * For more details take a look at the Java Quickstart chapter in the Gradle
+ * user guide available at ${ref_userguide_java_tutorial.groovyComment}
+ */
+
+// Apply the java plugin to add support for Java
+apply plugin: 'java'
+
+// In this section you declare where to find the dependencies of your project
+repositories {
+    // Use 'maven central' for resolving your dependencies.
+    // You can declare any Maven/Ivy/file repository here.
+    mavenCentral()
+}
+
+// In this section you declare the dependencies for your production and test code
+dependencies {
+    // The production code uses the SLF4J logging API at compile time
+    compile 'org.slf4j:slf4j-api:1.7.5'
+
+    // Declare the dependency for your favourite test framework you want to use in your tests.
+    // TestNG is also supported by the Gradle Test task. Just change the
+    // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
+    // 'test.useTestNG()' to your build script.
+    testCompile 'junit:junit:${junitVersion.groovyComment}'
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/Library.scala.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/Library.scala.template
new file mode 100644
index 0000000..913495a
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/Library.scala.template
@@ -0,0 +1,9 @@
+/*
+ * This Scala source file was auto generated by running 'gradle buildInit --type scala-library'
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * @author ${genUser.groovyComment}, @date ${genDate.groovyComment}
+ */
+class Library {
+  def someLibraryMethod(): Boolean = true
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/LibrarySuite.scala.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/LibrarySuite.scala.template
new file mode 100644
index 0000000..c80f0b1
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/LibrarySuite.scala.template
@@ -0,0 +1,18 @@
+/*
+ * This Scala Testsuite was auto generated by running 'gradle init --type scala-library'
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * @author ${genUser.groovyComment}, @date ${genDate.groovyComment}
+ */
+
+import org.scalatest.FunSuite
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+ at RunWith(classOf[JUnitRunner])
+class LibrarySuite extends FunSuite {
+  test("someLibraryMethod is always true") {
+    def library = new Library()
+    assert(library.someLibraryMethod)
+  }
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/build.gradle.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/build.gradle.template
new file mode 100644
index 0000000..4b61ee7
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/scalalibrary/build.gradle.template
@@ -0,0 +1,29 @@
+/*
+ * This build file was auto generated by running the Gradle 'init' task
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * This generated file contains a sample Scala library project to get you started.
+ * For more details take a look at the Scala plugin chapter in the Gradle
+ * user guide available at ${ref_userguide_scala_plugin.groovyComment}
+ */
+
+// Apply the scala plugin to add support for Scala
+apply plugin: 'scala'
+
+// In this section you declare where to find the dependencies of your project
+repositories {
+    // Use 'maven central' for resolving your dependencies.
+    // You can declare any Maven/Ivy/file repository here.
+    mavenCentral()
+}
+
+// In this section you declare the dependencies for your production and test code
+dependencies {
+    // We use Scala ${scalaVersion.groovyString} in our library project
+    compile 'org.scala-lang:scala-library:${scalaLibraryVersion.groovyString}'
+
+    // We use Scalatest for testing our library
+    testCompile 'junit:junit:${junitVersion.groovyString}'
+    testCompile 'org.scalatest:${scalaTestModule.groovyString}:${scalaTestVersion.groovyString}'
+    testRuntime 'org.scala-lang.modules:${scalaXmlModule.groovyString}:${scalaXmlVersion.groovyString}'
+}
diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/settings.gradle.template b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/settings.gradle.template
new file mode 100644
index 0000000..c45b655
--- /dev/null
+++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/settings.gradle.template
@@ -0,0 +1,19 @@
+/*
+ * This settings file was auto generated by the Gradle buildInit task
+ * by '${genUser.groovyComment}' at '${genDate.groovyComment}' with ${genGradleVersion.groovyComment}
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * In a single project build this file can be empty or even removed.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user guide at ${ref_userguide_multiproject.groovyComment}
+ */
+
+/*
+// To declare projects as part of a multi-project build use the 'include' method
+include 'shared'
+include 'api'
+include 'services:webservice'
+*/
+
+rootProject.name = '${rootProjectName.groovyString}'
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java b/subprojects/build-init/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
new file mode 100644
index 0000000..feece13
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.wrapper;
+
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.AbstractTaskTest;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.WrapUtil;
+import org.gradle.wrapper.GradleWrapperMain;
+import org.gradle.wrapper.WrapperExecutor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+public class WrapperTest extends AbstractTaskTest {
+
+    private Wrapper wrapper;
+    private String targetWrapperJarPath;
+    private TestFile expectedTargetWrapperJar;
+    private File expectedTargetWrapperProperties;
+    @Rule
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+
+    @Before
+    public void setUp() {
+        wrapper = createTask(Wrapper.class);
+        wrapper.setGradleVersion("1.0");
+        targetWrapperJarPath = "gradle/wrapper";
+        expectedTargetWrapperJar = new TestFile(getProject().getProjectDir(),
+                targetWrapperJarPath + "/gradle-wrapper.jar");
+        expectedTargetWrapperProperties = new File(getProject().getProjectDir(),
+                targetWrapperJarPath + "/gradle-wrapper.properties");
+        new File(getProject().getProjectDir(), targetWrapperJarPath).mkdirs();
+        wrapper.setDistributionPath("somepath");
+    }
+
+    public AbstractTask getTask() {
+        return wrapper;
+    }
+
+    @Test
+    public void testWrapperDefaults() {
+        wrapper = createTask(Wrapper.class);
+        assertEquals(new File(getProject().getProjectDir(), "gradle/wrapper/gradle-wrapper.jar"), wrapper.getJarFile());
+        assertEquals(new File(getProject().getProjectDir(), "gradlew"), wrapper.getScriptFile());
+        assertEquals(new File(getProject().getProjectDir(), "gradlew.bat"), wrapper.getBatchScript());
+        assertEquals(GradleVersion.current().getVersion(), wrapper.getGradleVersion());
+        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getDistributionPath());
+        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getArchivePath());
+        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getDistributionBase());
+        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getArchiveBase());
+        assertNotNull(wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testDeterminesWindowsScriptPathFromUnixScriptPath() {
+        wrapper.setScriptFile("build/gradle.sh");
+        assertEquals(getProject().file("build/gradle.bat"), wrapper.getBatchScript());
+
+        wrapper.setScriptFile("build/gradle-wrapper");
+        assertEquals(getProject().file("build/gradle-wrapper.bat"), wrapper.getBatchScript());
+    }
+
+    @Test
+    public void testDeterminesPropertiesFilePathFromJarPath() {
+        wrapper.setJarFile("build/gradle-wrapper.jar");
+        assertEquals(getProject().file("build/gradle-wrapper.properties"), wrapper.getPropertiesFile());
+    }
+    
+    @Test
+    public void testDownloadsFromReleaseRepositoryForReleaseVersions() {
+        wrapper.setGradleVersion("0.9.1");
+        assertEquals("https://services.gradle.org/distributions/gradle-0.9.1-bin.zip", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testDownloadsFromReleaseRepositoryForPreviewReleaseVersions() {
+        wrapper.setGradleVersion("1.0-milestone-1");
+        assertEquals("https://services.gradle.org/distributions/gradle-1.0-milestone-1-bin.zip", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testDownloadsFromSnapshotRepositoryForSnapshotVersions() {
+        wrapper.setGradleVersion("0.9.1-20101224110000+1100");
+        assertEquals("https://services.gradle.org/distributions-snapshots/gradle-0.9.1-20101224110000+1100-bin.zip", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testUsesExplicitlyDefinedDistributionUrl() {
+        wrapper.setGradleVersion("0.9");
+        wrapper.setDistributionUrl("http://some-url");
+        assertEquals("http://some-url", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testExecuteWithNonExistingWrapperJarParentDir() throws IOException {
+        checkExecute();
+    }
+
+    @Test
+    public void testCheckInputs() throws IOException {
+        assertThat(wrapper.getInputs().getProperties().keySet(),
+                equalTo(WrapUtil.toSet("distributionBase", "distributionPath", "distributionUrl", "archiveBase", "archivePath")));
+    }
+
+    @Test
+    public void testExecuteWithExistingWrapperJarParentDirAndExistingWrapperJar() throws IOException {
+        File jarDir = new File(getProject().getProjectDir(), "lib");
+        jarDir.mkdirs();
+        File wrapperJar = new File(getProject().getProjectDir(), targetWrapperJarPath);
+        File parentFile = expectedTargetWrapperJar.getParentFile();
+        assertTrue(parentFile.isDirectory() || parentFile.mkdirs());
+        try {
+            assertTrue(expectedTargetWrapperJar.createNewFile());
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not create %s.", wrapperJar), e);
+        }
+        checkExecute();
+    }
+
+    private void checkExecute() throws IOException {
+        wrapper.execute();
+        TestFile unjarDir = tmpDir.createDir("unjar");
+        expectedTargetWrapperJar.unzipTo(unjarDir);
+        unjarDir.file(GradleWrapperMain.class.getName().replace(".", "/") + ".class").assertIsFile();
+        Properties properties = GUtil.loadProperties(expectedTargetWrapperProperties);
+        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY), wrapper.getDistributionUrl());
+        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY), wrapper.getDistributionBase().toString());
+        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY), wrapper.getDistributionPath());
+        assertEquals(properties.getProperty(WrapperExecutor.ZIP_STORE_BASE_PROPERTY), wrapper.getArchiveBase().toString());
+        assertEquals(properties.getProperty(WrapperExecutor.ZIP_STORE_PATH_PROPERTY), wrapper.getArchivePath());
+    }
+
+    private String toNative(String s) {
+        return s.replace("/", File.separator);
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/BuildInitPluginSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/BuildInitPluginSpec.groovy
new file mode 100644
index 0000000..9e05699
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/BuildInitPluginSpec.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.buildinit.plugins
+
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.api.tasks.wrapper.Wrapper
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class BuildInitPluginSpec extends Specification {
+    def project = TestUtil.createRootProject()
+
+    def "applies plugin"() {
+        when:
+        project.plugins.apply BuildInitPlugin
+        and:
+        project.evaluate()
+        then:
+        project.tasks.wrapper instanceof Wrapper
+        TaskDependencyMatchers.dependsOn("wrapper").matches(project.tasks.init)
+    }
+
+    def "no wrapper task configured if build file already exists"() {
+        setup:
+        TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
+        File projectDir = temporaryFileProvider.createTemporaryDirectory("gradle", "projectDir");
+        def buildFile = new File(projectDir, "build.gradle") << '// an empty build'
+        buildFile << '// an empty build'
+        project = TestUtil.builder().withProjectDir(projectDir).build()
+        when:
+        project.plugins.apply BuildInitPlugin
+
+        then:
+        project.init != null
+        project.tasks.collect { it.name } == ["init"]
+    }
+
+    def "no build file generation if settings file already exists"() {
+        setup:
+        project.file("settings.gradle") << '// an empty file'
+
+        when:
+        project.plugins.apply BuildInitPlugin
+
+        then:
+        project.init != null
+        project.tasks.collect { it.name } == ["init"]
+    }
+
+    def "no build file generation when part of multi-project build"() {
+        setup:
+        TestUtil.createChildProject(project, 'child')
+
+        when:
+        project.plugins.apply BuildInitPlugin
+
+        then:
+        project.init != null
+        project.tasks.collect { it.name } == ["init"]
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/WrapperPluginSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/WrapperPluginSpec.groovy
new file mode 100644
index 0000000..222f62f
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/WrapperPluginSpec.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.buildinit.plugins
+
+import org.gradle.api.tasks.wrapper.Wrapper
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class WrapperPluginSpec extends Specification {
+    def project = TestUtil.createRootProject()
+
+    def "adds 'wrapper' task"() {
+        when:
+        project.plugins.apply WrapperPlugin
+
+        then:
+        project.tasks.wrapper instanceof Wrapper
+        project.tasks.wrapper.group == BuildInitPlugin.GROUP
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/BuildInitAutoApplyActionSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/BuildInitAutoApplyActionSpec.groovy
new file mode 100644
index 0000000..2d0a7db
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/BuildInitAutoApplyActionSpec.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.TaskContainerInternal
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class BuildInitAutoApplyActionSpec extends Specification {
+
+    @Rule
+    final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    TestFile buildFile
+    ProjectInternal projectInternal
+    PluginContainer pluginContainer
+    TaskContainerInternal taskContainerInternal
+
+    public void setup() {
+        projectInternal = Mock(ProjectInternal)
+        pluginContainer = Mock(PluginContainer)
+        taskContainerInternal = Mock(TaskContainerInternal)
+        _ * projectInternal.getTasks() >> taskContainerInternal
+
+    }
+
+    def "applies placeholder action for init on taskcontainer"() {
+        when:
+        new BuildInitAutoApplyAction().execute(projectInternal)
+        then:
+        1 * taskContainerInternal.addPlaceholderAction("init", _) >> {args -> args[1].run()}
+        1 * projectInternal.getParent() >> null
+        1 * projectInternal.getPlugins() >> pluginContainer
+        1 * pluginContainer.apply("build-init")
+    }
+
+    def "is not applied on non rootprojects"() {
+        given:
+        isNotRootProject()
+        when:
+        new BuildInitAutoApplyAction().execute(projectInternal)
+        then:
+        0 * taskContainerInternal.addPlaceholderAction("init", _)
+        0 * projectInternal.getPlugins() >> pluginContainer
+        0 * pluginContainer.apply("build-init")
+    }
+
+    def isNotRootProject() {
+        projectInternal.getParent() >> Mock(ProjectInternal)
+    }
+
+    void noChildprojects() {
+        projectInternal.subprojects >> []
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ConditionalTemplateOperationSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ConditionalTemplateOperationSpec.groovy
new file mode 100644
index 0000000..c592a95
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ConditionalTemplateOperationSpec.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import spock.lang.Specification
+
+
+class ConditionalTemplateOperationSpec extends Specification {
+
+    def "triggers delegates depending on condition"(){
+        TemplateOperation operation1 = Mock(TemplateOperation)
+        TemplateOperation operation2 = Mock(TemplateOperation)
+
+        Mock(org.gradle.internal.Factory).create() >> true
+        when:
+        new ConditionalTemplateOperation({true} as org.gradle.internal.Factory, operation1, operation2).generate()
+        then:
+        1 * operation1.generate()
+        1 * operation2.generate()
+
+        when:
+        new ConditionalTemplateOperation({false} as org.gradle.internal.Factory, operation1, operation2).generate()
+        then:
+        0 * operation1.generate()
+        0 * operation2.generate()
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/LanguageLibraryProjectInitDescriptorSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/LanguageLibraryProjectInitDescriptorSpec.groovy
new file mode 100644
index 0000000..c60107e
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/LanguageLibraryProjectInitDescriptorSpec.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+
+
+class LanguageLibraryProjectInitDescriptorSpec extends Specification {
+
+    FileResolver fileResolver = Mock()
+    TemplateOperationFactory templateOperationFactory = Mock()
+    LanguageLibraryProjectInitDescriptor descriptor
+    TemplateOperationFactory.TemplateOperationBuilder templateOperationBuilder = Mock(TemplateOperationFactory.TemplateOperationBuilder)
+    def setup(){
+
+    }
+
+    def "generates from template within sourceSet"(){
+        setup:
+
+        descriptor = new LanguageLibraryProjectInitDescriptor(language, templateOperationFactory, fileResolver)
+        1 * templateOperationFactory.newTemplateOperation() >> templateOperationBuilder
+
+        when:
+        descriptor.fromClazzTemplate("someTemplate/SomeClazz.somelang.template", sourceSet)
+
+        then:
+        1 * templateOperationBuilder.withTemplate("someTemplate/SomeClazz.somelang.template") >> templateOperationBuilder
+        1 * templateOperationBuilder.withTarget(target) >> templateOperationBuilder
+        1 * templateOperationBuilder.create() >> Mock(TemplateOperation)
+        where:
+        language        |  sourceSet       |   target
+        "somelang"      |    "main"        |   "src/main/somelang/SomeClazz.somelang"
+        "someotherlang" |    "test"        |   "src/test/someotherlang/SomeClazz.somelang"
+        "somelang"      |    "integTest"   |   "src/integTest/somelang/SomeClazz.somelang"
+    }
+
+    def "whenNoSourcesAvailable creates template operation checking for sources"(){
+        setup:
+        def mainSourceDirectory = Mock(FileTree)
+        def testSourceDirectory = Mock(FileTree)
+        def delegate = Mock(TemplateOperation)
+
+        descriptor = new LanguageLibraryProjectInitDescriptor("somelang", templateOperationFactory, fileResolver)
+        when:
+        descriptor.whenNoSourcesAvailable(delegate).generate()
+        then:
+        1 * mainSourceDirectory.empty >> noMainSources
+        _ * testSourceDirectory.empty >> noTestSources
+        1 * fileResolver.resolveFilesAsTree("src/main/somelang") >> mainSourceDirectory
+        _ * fileResolver.resolveFilesAsTree("src/test/somelang") >> testSourceDirectory
+
+        delegateInvocation * delegate.generate()
+
+        where:
+        noMainSources | noTestSources |   delegateInvocation
+        true          |    true       |  1
+        true          |    false      |  1
+        false         |    false      |  0
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistryFactoryTest.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistryFactoryTest.groovy
new file mode 100644
index 0000000..89a875a
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistryFactoryTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ProjectLayoutSetupRegistryFactoryTest extends Specification {
+
+    ProjectLayoutSetupRegistryFactory projectLayoutSetupRegistry
+    DocumentationRegistry documentationRegistry
+    MavenSettingsProvider mavenSettingsProvider
+    FileResolver fileResolver
+
+    def setup() {
+        fileResolver = Mock()
+        documentationRegistry = Mock()
+        projectLayoutSetupRegistry = new ProjectLayoutSetupRegistryFactory(mavenSettingsProvider, documentationRegistry, fileResolver);
+        _ * documentationRegistry.getDocumentationFor(_) >> "SomeDocId"
+        _ * fileResolver.resolve(_) >> new File("someFile")
+    }
+
+    @Unroll
+    def "supports '#type' project descriptor type"() {
+        when:
+        projectLayoutSetupRegistry.createProjectLayoutSetupRegistry().supports(type)
+        ProjectInitDescriptor descriptor = projectLayoutSetupRegistry.createProjectLayoutSetupRegistry().get(type)
+        then:
+        descriptor != null
+        where:
+        type << [BuildInitTypeIds.POM, BuildInitTypeIds.BASIC, BuildInitTypeIds.JAVA_LIBRARY, BuildInitTypeIds.SCALA_LIBRARY, BuildInitTypeIds.GROOVY_LIBRARY]
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistrySpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistrySpec.groovy
new file mode 100644
index 0000000..829f3c3
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/ProjectLayoutSetupRegistrySpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.api.GradleException
+import spock.lang.Specification
+
+
+class ProjectLayoutSetupRegistrySpec extends Specification {
+
+
+    ProjectLayoutSetupRegistry registry = new ProjectLayoutSetupRegistry()
+
+    def "can add multiple projectlayoutdescriptors"() {
+        when:
+        registry.add("desc1", Mock(ProjectInitDescriptor))
+        registry.add("desc2", Mock(ProjectInitDescriptor))
+        registry.add("desc3", Mock(ProjectInitDescriptor))
+        then:
+        registry.supports("desc1")
+        registry.get("desc1") != null
+
+        registry.supports("desc2")
+        registry.get("desc2") != null
+
+        registry.supports("desc3")
+        registry.get("desc3") != null
+    }
+
+    def "cannot add multiple descriptors with same id"() {
+        when:
+        registry.add("desc1", Mock(ProjectInitDescriptor))
+        registry.add("desc1", Mock(ProjectInitDescriptor))
+        then:
+        def e = thrown(GradleException)
+        e.message == "ProjectDescriptor with ID 'desc1' already registered."
+    }
+
+    def "getSupportedTypes lists all registered types"() {
+        setup:
+        registry.add("desc1", Mock(ProjectInitDescriptor))
+        registry.add("desc2", Mock(ProjectInitDescriptor))
+        registry.add("desc3", Mock(ProjectInitDescriptor))
+        expect:
+        registry.getSupportedTypes() == ["desc1", "desc2", "desc3"]
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy
new file mode 100644
index 0000000..10064c8
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+import spock.lang.Specification
+
+class SimpleTemplateOperationSpec extends Specification {
+
+    def "Template URL must not be null"() {
+        when:
+        new SimpleTemplateOperation(null, new File("someFile"), [:])
+        then:
+        def e = thrown(BuildInitException)
+        e.message == "Template URL must not be null"
+    }
+
+    def "Target File must not be null"() {
+        when:
+        new SimpleTemplateOperation(new URL("file://some/file"), null, [:])
+        then:
+        def e = thrown(BuildInitException)
+        e.message == "Target file must not be null"
+    }
+
+    def "writes file from template with binding support"() {
+        setup:
+        def targetFile = GroovyMock(File)
+        def targetParent = Mock(File)
+        1 * targetFile.parentFile >> targetParent
+        def templateURL = GroovyMock(URL)
+        1 * templateURL.text >> '${someBindedValue.groovyComment}'
+        def templateOperation = new SimpleTemplateOperation(templateURL, targetFile, [someBindedValue: new TemplateValue("someTemplateValue")])
+
+        when:
+        templateOperation.generate()
+
+        then:
+        1 * targetParent.mkdirs() >> true
+        1 * targetFile.withWriter("utf-8", _) >> { encoding, writerClosure ->
+            def writerMock = new StringWriter()
+            writerClosure.call(writerMock)
+            assert writerMock.toString() == "someTemplateValue"
+        }
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateBasedProjectInitDescriptorSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateBasedProjectInitDescriptorSpec.groovy
new file mode 100644
index 0000000..6940406
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateBasedProjectInitDescriptorSpec.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import spock.lang.Specification
+
+class TemplateBasedProjectInitDescriptorSpec extends Specification {
+
+    def "executes all passed in template operations when generating project"() {
+        setup:
+        TemplateOperation templateOperation1 = Mock(TemplateOperation)
+        TemplateOperation templateOperation2 = Mock(TemplateOperation)
+        TemplateBasedProjectInitDescriptor descriptor = new TestTemplateBasedProjectInitDescriptor(templateOperation1, templateOperation2)
+        when:
+        descriptor.generate()
+        then:
+
+        then:
+        1 * templateOperation1.generate()
+        1 * templateOperation2.generate()
+    }
+
+    class TestTemplateBasedProjectInitDescriptor extends TemplateBasedProjectInitDescriptor{
+        TestTemplateBasedProjectInitDescriptor(TemplateOperation... templateOperations) {
+            templateOperations.each {
+                register(it)
+            }
+        }
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateOperationFactorySpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateOperationFactorySpec.groovy
new file mode 100644
index 0000000..56b863d
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateOperationFactorySpec.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+
+class TemplateOperationFactorySpec extends Specification {
+
+    FileResolver fileResolver = Mock()
+    DocumentationRegistry documentationRegistry = Mock()
+    TemplateOperationFactory.TemplateOperationBuilder builder
+    TemplateOperationFactory factory
+
+    def setup() {
+        factory = new TemplateOperationFactory("/some/template/package", fileResolver, documentationRegistry)
+        builder = factory.newTemplateOperation()
+        builder.withTemplate(GroovyMock(URL))
+        _ * fileResolver.resolve(_) >> { String fileName ->
+            File fileMock = Mock(File)
+            fileMock
+        }
+        builder.withTarget("someTarget")
+    }
+
+    def "factory creates isolated builders"() {
+
+        when:
+        def operation1 = factory.newTemplateOperation()
+        def operation2 = factory.newTemplateOperation()
+
+        operation1.withBindings(key:"value")
+        operation1.withTarget("atarget")
+
+        then:
+        operation1 != operation2
+        operation1.bindings.size() == 4
+        operation2.bindings.size() == 3
+        operation2.target == null
+    }
+
+
+    def "withDocumentationBindings looks up value in documentationRegistry"() {
+        when:
+        builder.withDocumentationBindings(someDocumentationKey: 'someDocID')
+        then:
+        1 * documentationRegistry.getDocumentationFor('someDocID') >> "resolvedDocumentationID"
+        and:
+        builder.create().bindings.someDocumentationKey.value == "resolvedDocumentationID"
+    }
+
+    def "declares default bindings"() {
+        when:
+        def templateOperation = builder.create()
+        then:
+        templateOperation.bindings.size() == 3
+        templateOperation.bindings.genDate.value != null
+        templateOperation.bindings.genUser.value != null
+        templateOperation.bindings.genGradleVersion.value != null
+    }
+
+    def "supports custom bindings"() {
+        when:
+        def templateOperation = builder.withBindings(someCustomBinding: "someCustomBindingValue").create()
+        then:
+        templateOperation.bindings.size() == 4
+        templateOperation.bindings.someCustomBinding.value == "someCustomBindingValue"
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateValueTest.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateValueTest.groovy
new file mode 100644
index 0000000..db2144a
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/TemplateValueTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal
+
+import spock.lang.Specification
+
+class TemplateValueTest extends Specification {
+    def "escapes value for inclusion in a comment"() {
+        expect:
+        new TemplateValue(value).groovyComment == escaped
+
+        where:
+        value   | escaped
+        ''      | ''
+        'abc'   | 'abc'
+        'a\n\t' | 'a\n\t'
+        /a\b/   | /a\\b/
+    }
+
+    def "escapes value for inclusion in a string"() {
+        expect:
+        new TemplateValue(value).groovyString == escaped
+
+        where:
+        value         | escaped
+        ''            | ''
+        'abc'         | 'abc'
+        'a\n\t\r\b\f' | /a\n\t\r\b\f/
+        /a\b/         | /a\\b/
+        /'/           | /\'/
+        /"/           | /"/
+        /\'/          | /\\\'/
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectXmlWriterTest.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectXmlWriterTest.groovy
new file mode 100644
index 0000000..101668c
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectXmlWriterTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal.maven
+
+import spock.lang.Specification
+
+class MavenProjectXmlWriterTest extends Specification {
+
+    def writer = new MavenProjectXmlWriter()
+
+    def "removes xml element"() {
+        expect:
+        writer.prepareXml('<?xml encoding="UTF-8"?><project/>') == "<project/>"
+        writer.prepareXml('<?xml version="1.0" encoding="UTF-8"?><project/>') == "<project/>"
+        writer.prepareXml('<?xml  version="1.0"  encoding="UTF-8"  ?><project/>') == "<project/>"
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectsCreatorSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectsCreatorSpec.groovy
new file mode 100644
index 0000000..2b40d04
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/maven/MavenProjectsCreatorSpec.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.buildinit.plugins.internal.maven
+
+import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider
+import org.gradle.api.internal.artifacts.mvnsettings.MavenFileLocations
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class MavenProjectsCreatorSpec extends Specification {
+
+    @Rule TestNameTestDirectoryProvider 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(MavenConversionException)
+        ex.message == "Unable to create Maven project model using POM $pom."
+    }
+
+    def "fails with decent exception if pom does not exist"() {
+        def pom = temp.file("pom.xml")
+
+        when:
+        creator.create(settings.buildSettings(), pom) as List
+
+        then:
+        def ex = thrown(MavenConversionException)
+        ex.message == "Unable to create Maven project model. The POM file $pom does not exist."
+    }
+}
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/tasks/InitBuildSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/tasks/InitBuildSpec.groovy
new file mode 100644
index 0000000..4d7f4c3
--- /dev/null
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/tasks/InitBuildSpec.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.buildinit.tasks
+
+import org.gradle.api.GradleException
+import org.gradle.buildinit.plugins.internal.BuildInitTypeIds
+import org.gradle.buildinit.plugins.internal.ProjectLayoutSetupRegistry
+import org.gradle.buildinit.plugins.internal.ProjectInitDescriptor
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class InitBuildSpec extends Specification {
+
+    InitBuild init;
+
+    ProjectLayoutSetupRegistry projectLayoutRegistry;
+
+    ProjectInitDescriptor projectSetupDescriptor1
+    ProjectInitDescriptor projectSetupDescriptor2
+    ProjectInitDescriptor projectSetupDescriptor3
+
+    def setup() {
+        init = TestUtil.createTask(InitBuild)
+        projectLayoutRegistry = Mock()
+        projectSetupDescriptor1 = Mock()
+        projectSetupDescriptor2 = Mock()
+        projectSetupDescriptor3 = Mock()
+        init.projectLayoutRegistry = projectLayoutRegistry
+    }
+
+    def "throws GradleException if requested setupDescriptor not supported"() {
+        setup:
+        _ * projectLayoutRegistry.get("aType") >> null
+        _ * projectLayoutRegistry.getSupportedTypes() >> ['supported-type', 'another-supported-type']
+        when:
+        init.type = "aType"
+        init.setupProjectLayout()
+        then:
+        def e = thrown(GradleException)
+        e.message == "The requested build setup type 'aType' is not supported. Supported types: 'another-supported-type', 'supported-type'."
+
+    }
+
+    def "delegates task action to referenced setupDescriptor"() {
+        setup:
+        1 * projectLayoutRegistry.supports(BuildInitTypeIds.BASIC) >> true
+        1 * projectLayoutRegistry.get(BuildInitTypeIds.BASIC) >> projectSetupDescriptor1
+        when:
+        init.setupProjectLayout()
+        then:
+        1 * projectSetupDescriptor1.generate()
+    }
+}
diff --git a/subprojects/cli/cli.gradle b/subprojects/cli/cli.gradle
index 68118d9..46b1219 100644
--- a/subprojects/cli/cli.gradle
+++ b/subprojects/cli/cli.gradle
@@ -21,7 +21,7 @@
     It has no dependencies, and should never have any.
 */
 dependencies {
-    groovy libraries.groovy
+    testCompile libraries.groovy
 }
 
 useClassycle()
\ No newline at end of file
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java
index 82a5ff3..b34d668 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java
@@ -17,8 +17,6 @@ package org.gradle.cli;
 
 /**
  * A {@code CommandLineArgumentException} is thrown when command-line arguments cannot be parsed.
- * 
- * @author Hans Dockter
  */
 public class CommandLineArgumentException extends RuntimeException {
     public CommandLineArgumentException(String message) {
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java
index bc9bac4..bfc2627 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java
@@ -15,9 +15,6 @@
  */
 package org.gradle.cli;
 
-/**
- * @author Hans Dockter
- */
 public interface CommandLineConverter<T> {
     T convert(Iterable<String> args) throws CommandLineArgumentException;
 
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 8520081..9f0cb4f 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
@@ -15,17 +15,17 @@
  */
 package org.gradle.cli;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.HashSet;
 
 public class CommandLineOption {
     private final Set<String> options = new HashSet<String>();
     private Class<?> argumentType = Void.TYPE;
     private String description;
-    private String subcommand;
     private String deprecationWarning;
     private boolean incubating;
+    private final Set<CommandLineOption> groupWith = new HashSet<CommandLineOption>();
 
     public CommandLineOption(Iterable<String> options) {
         for (String option : options) {
@@ -37,22 +37,18 @@ public class CommandLineOption {
         return options;
     }
 
-    public CommandLineOption hasArgument() {
-        argumentType = String.class;
+    public CommandLineOption hasArgument(Class<?> argumentType) {
+        this.argumentType = argumentType;
         return this;
     }
 
-    public CommandLineOption hasArguments() {
-        argumentType = List.class;
+    public CommandLineOption hasArgument() {
+        this.argumentType = String.class;
         return this;
     }
 
-    public String getSubcommand() {
-        return subcommand;
-    }
-
-    public CommandLineOption mapsToSubcommand(String command) {
-        this.subcommand = command;
+    public CommandLineOption hasArguments() {
+        argumentType = List.class;
         return this;
     }
 
@@ -100,8 +96,17 @@ public class CommandLineOption {
         incubating = true;
         return this;
     }
-    
+
     public String getDeprecationWarning() {
         return deprecationWarning;
     }
+
+    Set<CommandLineOption> getGroupWith() {
+        return groupWith;
+    }
+
+    void groupWith(Set<CommandLineOption> options) {
+        this.groupWith.addAll(options);
+        this.groupWith.remove(this);
+    }
 }
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java
index c238506..3996137 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java
@@ -17,6 +17,7 @@ package org.gradle.cli;
 
 import java.io.*;
 import java.util.*;
+import java.util.regex.Pattern;
 
 /**
  * <p>A command-line parser which supports a command/sub-command style command-line interface. Supports the following
@@ -51,6 +52,8 @@ import java.util.*;
  * </ul>
  */
 public class CommandLineParser {
+    private static final Pattern OPTION_NAME_PATTERN = Pattern.compile("(\\?|\\p{Alnum}[\\p{Alnum}-_]*)");
+
     private Map<String, CommandLineOption> optionsByString = new HashMap<String, CommandLineOption>();
     private boolean allowMixedOptions;
     private boolean allowUnknownOptions;
@@ -94,15 +97,15 @@ public class CommandLineParser {
                 } else if (arg.matches("--[^=]+")) {
                     OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2));
                     parseState = parsedOption.onStartNextArg();
-                } else if (arg.matches("--[^=]+=.*")) {
+                } else if (arg.matches("(?s)--[^=]+=.*")) {
                     int endArg = arg.indexOf('=');
                     OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2, endArg));
                     parseState = parsedOption.onArgument(arg.substring(endArg + 1));
-                } else if (arg.matches("-[^=]=.*")) {
+                } else if (arg.matches("(?s)-[^=]=.*")) {
                     OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(1, 2));
                     parseState = parsedOption.onArgument(arg.substring(3));
                 } else {
-                    assert arg.matches("-[^-].*");
+                    assert arg.matches("(?s)-[^-].*");
                     String option = arg.substring(1);
                     if (optionsByString.containsKey(option)) {
                         OptionParserState parsedOption = parseState.onStartOption(arg, option);
@@ -156,6 +159,21 @@ public class CommandLineParser {
     }
 
     /**
+     * Specifies that the given set of options are mutually-exclusive. Only one of the given options will be selected.
+     * The parser ignores all but the last of these options.
+     */
+    public CommandLineParser allowOneOf(String... options) {
+        Set<CommandLineOption> commandLineOptions = new HashSet<CommandLineOption>();
+        for (String option : options) {
+            commandLineOptions.add(optionsByString.get(option));
+        }
+        for (CommandLineOption commandLineOption : commandLineOptions) {
+            commandLineOption.groupWith(commandLineOptions);
+        }
+        return this;
+    }
+
+    /**
      * Prints a usage message to the given stream.
      *
      * @param out The output stream to write to.
@@ -231,10 +249,13 @@ public class CommandLineParser {
             if (option.startsWith("-")) {
                 throw new IllegalArgumentException(String.format("Cannot add option '%s' as an option cannot start with '-'.", option));
             }
+            if (!OPTION_NAME_PATTERN.matcher(option).matches()) {
+                throw new IllegalArgumentException(String.format("Cannot add option '%s' as an option can only contain alphanumeric characters or '-' or '_'.", option));
+            }
         }
         CommandLineOption option = new CommandLineOption(Arrays.asList(options));
         for (String optionStr : option.getOptions()) {
-            this.optionsByString.put(optionStr, option);
+            optionsByString.put(optionStr, option);
         }
         return option;
     }
@@ -262,7 +283,7 @@ public class CommandLineParser {
         public abstract boolean maybeStartOption(String arg);
 
         boolean isOption(String arg) {
-            return arg.matches("-.+");
+            return arg.matches("(?s)-.+");
         }
 
         public abstract OptionParserState onStartOption(String arg, String option);
@@ -444,8 +465,9 @@ public class CommandLineParser {
             if (option.getDeprecationWarning() != null) {
                 deprecationPrinter.println("The " + optionString + " option is deprecated - " + option.getDeprecationWarning());
             }
-            if (option.getSubcommand() != null) {
-                return state.onNonOption(option.getSubcommand());
+
+            for (CommandLineOption otherOption : option.getGroupWith()) {
+                commandLine.removeOption(otherOption);
             }
 
             return state;
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java
index 8179d31..3134649 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java
@@ -104,4 +104,8 @@ public class ParsedCommandLine {
         presentOptions.addAll(option.getOptions());
         return parsedOption;
     }
+
+    void removeOption(CommandLineOption option) {
+        presentOptions.removeAll(option.getOptions());
+    }
 }
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 d44dbac..87e6f2c 100644
--- a/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
@@ -15,7 +15,9 @@
  */
 package org.gradle.cli
 
-import spock.lang.*
+import spock.lang.Issue
+import spock.lang.Specification
+import spock.lang.Unroll
 
 class CommandLineParserTest extends Specification {
     private final CommandLineParser parser = new CommandLineParser()
@@ -90,6 +92,16 @@ class CommandLineParserTest extends Specification {
         result.option('a').values == ['arg']
     }
 
+    def parsesShortOptionWithEqualMultilineArgument() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse(['-a=1\n2\n3'])
+        result.hasOption('a')
+        result.option('a').value == '1\n2\n3'
+        result.option('a').values == ['1\n2\n3']
+    }
+
     def parsesShortOptionWithEqualsCharacterInAttachedArgument() {
         parser.option('a').hasArgument()
 
@@ -162,6 +174,19 @@ class CommandLineParserTest extends Specification {
         result.option('long-option-a').values == ['arg']
     }
 
+    @Unroll
+    def "parse fails for invalid option name #badOptionName"() {
+        when:
+        parser.option(badOptionName)
+
+        then:
+        def e = thrown(IllegalArgumentException)
+        e != null
+
+        where:
+        badOptionName << ['weird\nmulti\nline\noption', '!@#$', 'with space', '=', '-']
+    }
+
     def parsesMultipleOptions() {
         parser.option('a').hasArgument()
         parser.option('long-option')
@@ -210,6 +235,14 @@ class CommandLineParserTest extends Specification {
         result.option('a').values == ['arg1', 'arg2', 'arg3', 'arg4']
     }
 
+    def parsesHelpOption() {
+        parser.option('h', '?', 'help')
+
+        expect:
+        def result = parser.parse(['-?'])
+        result.hasOption('?')
+    }
+
     def parsesCommandLineWithSubcommand() {
         parser.option('a')
 
@@ -265,51 +298,49 @@ class CommandLineParserTest extends Specification {
         result.extraArguments == ['a', '--option', 'b']
     }
 
-    def canMapOptionToSubcommand() {
-        parser.option('a').mapsToSubcommand('subcmd')
-
-        expect:
-        def result = parser.parse(['-a', '--option', 'b'])
-        result.extraArguments == ['subcmd', '--option', 'b']
-        result.hasOption('a')
-    }
-
     def canCombineSubcommandShortOptionWithOtherShortOptions() {
-        parser.option('a').mapsToSubcommand('subcmd')
         parser.option('b')
+        parser.allowMixedSubcommandsAndOptions()
 
         when:
-        def result = parser.parse(['-abc', '--option', 'b'])
+        def result = parser.parse(['cmd', '-b', '-a'])
 
         then:
-        result.extraArguments == ['subcmd', '-b', '-c', '--option', 'b']
-        result.hasOption('a')
-        !result.hasOption('b')
+        result.extraArguments == ['cmd', '-a']
+        result.hasOption('b')
 
         when:
-        result = parser.parse(['-bac', '--option', 'b'])
+        result = parser.parse(['cmd', '-ba'])
 
         then:
-        result.extraArguments == ['subcmd', '-c', '--option', 'b']
-        result.hasOption('a')
+        result.extraArguments == ['cmd', '-a']
         result.hasOption('b')
+    }
+
+    def returnsLastMutuallyExclusiveOptionThatIsPresent() {
+        parser.option("a")
+        parser.option("b")
+        parser.option("c", "long-option")
+        parser.option("d")
+        parser.allowOneOf("a", "b", "c")
 
         when:
-        parser.allowMixedSubcommandsAndOptions()
-        result = parser.parse(['-abc', '--option', 'b'])
+        def result = parser.parse(['-a', '-b', '-c'])
 
         then:
-        result.extraArguments == ['subcmd', '-c', '--option', 'b']
-        result.hasOption('a')
-        result.hasOption('b')
+        !result.hasOption('a')
+        !result.hasOption('b')
+        result.hasOption('c')
+        result.hasOption('long-option')
 
         when:
-        result = parser.parse(['-bac', '--option', 'b'])
+        result = parser.parse(['-a', '-b', '--long-option'])
 
         then:
-        result.extraArguments == ['subcmd', '-c', '--option', 'b']
-        result.hasOption('a')
-        result.hasOption('b')
+        !result.hasOption('a')
+        !result.hasOption('b')
+        result.hasOption('c')
+        result.hasOption('long-option')
     }
 
     def singleDashIsNotConsideredAnOption() {
@@ -353,7 +384,7 @@ class CommandLineParserTest extends Specification {
                 '-y, -z, --end-option, --last-option  this is the last option'
         ]
     }
-    
+
     def formatsUsageMessageForDeprecatedAndIncubatingOptions() {
         parser.option('a', 'long-option').hasDescription('this is option a').deprecated("don't use this")
         parser.option('b').deprecated('will be removed')
@@ -444,6 +475,26 @@ class CommandLineParserTest extends Specification {
         e.message == 'Unknown command-line option \'-u\'.'
     }
 
+    @Unroll
+    def "parse fails when command line contains unknown option with newline #arg"() {
+        when:
+        parser.parse(arg)
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == "Unknown command-line option '${reportedAs}'."
+
+        where:
+        arg                | reportedAs
+        '-a\nb'            | '-a'
+        '--a\nb'           | '--a\nb'
+        '--a\nb=something' | '--a\nb'
+        '-\n'              | '-\n'
+        '-\na'             | '-\n'
+        '--\n'             | '--\n'
+        '--\n=nothing'     | '--\n'
+    }
+
     def parseFailsWhenCommandLineContainsUnknownLongOptionWithEqualsArgument() {
         when:
         parser.parse(['--unknown=arg'])
@@ -526,6 +577,22 @@ class CommandLineParserTest extends Specification {
         e.message == 'An empty argument was provided for command-line option \'-a\'.'
     }
 
+    def parseAcceptsMultilineArgument() {
+        parser.option('D').hasArgument()
+
+        expect:
+        def result = parser.parse(['-Dprops=a:1\nb:2\nc:3'])
+        result.option('D').values == ['props=a:1\nb:2\nc:3']
+    }
+
+    def parseAcceptsMultilineArgumentForLongOption() {
+        parser.option('a', 'long-option').hasArgument()
+
+        expect:
+        def result = parser.parse(['--long-option=a\nb\nc'])
+        result.option('long-option').values == ['a\nb\nc']
+    }
+
     def parseFailsWhenEmptyArgumentIsProvided() {
         parser.option('a').hasArgument()
 
@@ -581,7 +648,7 @@ class CommandLineParserTest extends Specification {
         def e = thrown(CommandLineArgumentException)
         e.message == 'Command-line option \'-a\' does not take an argument.'
     }
-    
+
     def "allow unknown options mode collects unknown options"() {
         given:
         parser.option("a")
@@ -594,7 +661,7 @@ class CommandLineParserTest extends Specification {
 
         then:
         result.option("a") != null
-        
+
         and:
         result.extraArguments == ['-b', '--long-option']
     }
@@ -631,9 +698,16 @@ class CommandLineParserTest extends Specification {
 
         then:
         result.option("a") != null
-        
+
         and:
         result.extraArguments == ['-ba', '-ba=c']
     }
 
+    def parseExtraArguments() {
+        when:
+        def result = parser.parse(['arg1', 'arg\ntwo'])
+
+        then:
+        result.extraArguments == ['arg1', 'arg\ntwo']
+    }
 }
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy
index 224a638..bb0bba1 100644
--- a/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy
@@ -18,9 +18,6 @@ package org.gradle.cli
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 9/5/12
- */
 class ParsedCommandLineOptionSpec extends Specification {
 
     final option = new ParsedCommandLineOption();
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy
index 1e3f52e..22851fe 100644
--- a/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.cli
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 4/16/12
- */
 class ParsedCommandLineTest extends Specification {
 
     def "knows if contains an option"() {
diff --git a/subprojects/code-quality/code-quality.gradle b/subprojects/code-quality/code-quality.gradle
index 90640da..0ecaff2 100644
--- a/subprojects/code-quality/code-quality.gradle
+++ b/subprojects/code-quality/code-quality.gradle
@@ -16,7 +16,7 @@
 apply from: "$rootDir/gradle/providedConfiguration.gradle"
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
index 2d8d517..6081732 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
@@ -33,6 +33,24 @@ class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
         writeConfigFile()
     }
 
+    def "allows configuring tool dependencies explicitly"() {
+        expect: //defaults exist and can be inspected
+        succeeds("dependencies", "--configuration", "checkstyle")
+        output.contains "com.puppycrawl.tools:checkstyle:"
+
+        when:
+        buildFile << """
+            dependencies {
+                //downgrade version:
+                checkstyle "com.puppycrawl.tools:checkstyle:5.5"
+            }
+        """
+
+        then:
+        succeeds("dependencies", "--configuration", "checkstyle")
+        output.contains "com.puppycrawl.tools:checkstyle:5.5"
+    }
+
     def "analyze good code"() {
         goodCode()
 
@@ -49,7 +67,7 @@ class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
 
         expect:
         fails("check")
-        failure.assertHasDescription("Execution failed for task ':checkstyleMain'")
+        failure.assertHasDescription("Execution failed for task ':checkstyleMain'.")
         failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at:"))
         failure.error.contains("Name 'class1' must match pattern")
         file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
@@ -65,7 +83,7 @@ class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
 
         then:
         fails("check")
-        failure.assertHasDescription("Execution failed for task ':checkstyleMain'")
+        failure.assertHasDescription("Execution failed for task ':checkstyleMain'.")
         failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at:"))
         !failure.error.contains("Name 'class1' must match pattern")
         file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
@@ -144,7 +162,7 @@ repositories {
 }
 
 dependencies {
-    groovy localGroovy()
+    compile localGroovy()
 }
         """
     }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
index 162f9d9..eade780 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
@@ -29,6 +29,24 @@ class CodeNarcPluginIntegrationTest extends WellBehavedPluginTest {
         writeConfigFile()
     }
 
+    def "allows configuring tool dependencies explicitly"() {
+        expect: //defaults exist and can be inspected
+        succeeds("dependencies", "--configuration", "codenarc")
+        output.contains "org.codenarc:CodeNarc:"
+
+        when:
+        buildFile << """
+            dependencies {
+                //downgrade version:
+                codenarc "org.codenarc:CodeNarc:0.17"
+            }
+        """
+
+        then:
+        succeeds("dependencies", "--configuration", "codenarc")
+        output.contains "org.codenarc:CodeNarc:0.17"
+    }
+
     def "analyze good code"() {
         goodCode()
 
@@ -78,7 +96,7 @@ class CodeNarcPluginIntegrationTest extends WellBehavedPluginTest {
 
         expect:
         fails("check")
-        failure.assertHasDescription("Execution failed for task ':codenarcTest'")
+        failure.assertHasDescription("Execution failed for task ':codenarcTest'.")
         failure.assertThatCause(startsWith("CodeNarc rule violations were found. See the report at:"))
         !file("build/reports/codenarc/main.html").text.contains("Class2")
         file("build/reports/codenarc/test.html").text.contains("testclass2")
@@ -100,6 +118,20 @@ class CodeNarcPluginIntegrationTest extends WellBehavedPluginTest {
 
     }
 
+    def "can configure max violations"() {
+        badCode()
+        buildFile << """
+            codenarcTest {
+                maxPriority2Violations = 1
+            }
+        """
+
+        expect:
+        succeeds("check")
+        !output.contains("CodeNarc rule violations were found. See the report at:")
+        file("build/reports/codenarc/test.html").text.contains("testclass2")
+    }
+
     private goodCode() {
         file("src/main/groovy/org/gradle/class1.java") << "package org.gradle; class class1 { }"
         file("src/test/groovy/org/gradle/testclass1.java") << "package org.gradle; class testclass1 { }"
@@ -124,7 +156,7 @@ repositories {
 }
 
 dependencies { 
-    groovy localGroovy()
+    compile localGroovy()
 }
         """
     }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
index 3ef3675..265fd0f 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
@@ -37,7 +37,7 @@ apply plugin: 'groovy'
 apply plugin: 'java'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
         inTestDirectory().withTasks('check').run()
     }
@@ -66,7 +66,7 @@ repositories { mavenCentral() }
 apply plugin: 'groovy'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
         writeCheckstyleConfig()
 
@@ -89,7 +89,7 @@ dependencies { groovy localGroovy() }
 apply plugin: 'groovy'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
         writeCheckstyleConfig()
 
@@ -109,7 +109,7 @@ dependencies { groovy localGroovy() }
 apply plugin: 'groovy'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
         writeCheckstyleConfig()
 
@@ -117,7 +117,7 @@ dependencies { groovy localGroovy() }
         testFile('src/main/groovy/org/gradle/class2.java') << 'package org.gradle; class class2 { }'
 
         ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
-        failure.assertHasDescription('Execution failed for task \':checkstyleMain\'')
+        failure.assertHasDescription('Execution failed for task \':checkstyleMain\'.')
         failure.assertThatCause(startsWith('Checkstyle rule violations were found. See the report at'))
 
         testFile('build/checkstyle/main.xml').assertExists()
@@ -129,7 +129,7 @@ dependencies { groovy localGroovy() }
 apply plugin: 'groovy'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
         writeCodeNarcConfigFile()
 
@@ -148,7 +148,7 @@ dependencies { groovy localGroovy() }
 apply plugin: 'groovy'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
 
         writeCodeNarcConfigFile()
@@ -167,7 +167,7 @@ dependencies { groovy localGroovy() }
 apply plugin: 'groovy'
 apply plugin: 'code-quality'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
+dependencies { compile localGroovy() }
 '''
 
         writeCodeNarcConfigFile()
@@ -175,7 +175,7 @@ dependencies { groovy localGroovy() }
         testFile('src/main/groovy/org/gradle/class1.groovy') << 'package org.gradle; class class1 { }'
 
         ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
-        failure.assertHasDescription('Execution failed for task \':codenarcMain\'')
+        failure.assertHasDescription('Execution failed for task \':codenarcMain\'.')
         failure.assertThatCause(startsWith('CodeNarc rule violations were found. See the report at:'))
 
         testFile('build/reports/codenarc/main.html').assertExists()
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 913f3a5..b9f7db8 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
@@ -32,6 +32,24 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         writeBuildFile()
     }
 
+    def "allows configuring tool dependencies explicitly"() {
+        expect: //defaults exist and can be inspected
+        succeeds("dependencies", "--configuration", "findbugs")
+        output.contains "com.google.code.findbugs:findbugs:"
+
+        when:
+        buildFile << """
+            dependencies {
+                //downgrade version:
+                findbugs "com.google.code.findbugs:findbugs:2.0.0"
+            }
+        """
+
+        then:
+        succeeds("dependencies", "--configuration", "findbugs")
+        output.contains "com.google.code.findbugs:findbugs:2.0.0"
+    }
+
     def "analyze good code"() {
         goodCode()
         expect:
@@ -45,7 +63,7 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
 
         expect:
         fails("check")
-        failure.assertHasDescription("Execution failed for task ':findbugsMain'")
+        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.BadClass"))
     }
@@ -146,6 +164,28 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         then:
         file("build/reports/findbugs/main.html").exists()
     }
+    
+    def "can generate xml with messages reports"() {
+        given:
+        buildFile << """
+            findbugsMain.reports {
+                xml.enabled true
+                xml.withMessages true
+                html.enabled false
+            }
+            findbugsMain.ignoreFailures true
+        """
+
+        and:
+        badCode()
+
+        when:
+        run "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        containsXmlMessages(file("build/reports/findbugs/main.xml"))
+    }
 
     def "can generate no reports"() {
         given:
@@ -177,6 +217,146 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class800Test"))
     }
 
+    def "is incremental for reporting settings"() {
+        given:
+        buildFile << """
+            findbugsMain.reports {
+                xml.enabled true
+            }
+        """
+
+        and:
+        goodCode()
+
+        when:
+        succeeds "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        ":findbugsMain" in nonSkippedTasks
+        !(":findbugsMain" in skippedTasks)
+
+        when:
+        succeeds "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        !(":findbugsMain" in nonSkippedTasks)
+        ":findbugsMain" in skippedTasks
+
+        when:
+        buildFile << """
+            findbugsMain.reports {
+                xml.enabled false
+            }
+        """
+
+        succeeds "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        ":findbugsMain" in nonSkippedTasks
+        !(":findbugsMain" in skippedTasks)
+    }
+
+    def "is incremental for withMessage"() {
+        given:
+        buildFile << """
+            findbugsMain {
+                reports {
+                    xml.enabled true
+                    xml.withMessages true
+                }
+
+                ignoreFailures true
+            }
+        """
+
+        and:
+        badCode()
+
+        when:
+        succeeds "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        containsXmlMessages(file("build/reports/findbugs/main.xml"))
+        ":findbugsMain" in nonSkippedTasks
+        !(":findbugsMain" in skippedTasks)
+
+        when:
+        succeeds "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        containsXmlMessages(file("build/reports/findbugs/main.xml"))
+        !(":findbugsMain" in nonSkippedTasks)
+        ":findbugsMain" in skippedTasks
+
+        when:
+        buildFile << """
+            findbugsMain {
+                reports {
+                    xml.enabled true
+                    xml.withMessages false
+                }
+
+                ignoreFailures true
+            }
+        """
+
+        succeeds "findbugsMain"
+
+        then:
+        file("build/reports/findbugs/main.xml").exists()
+        !containsXmlMessages(file("build/reports/findbugs/main.xml"))
+        ":findbugsMain" in nonSkippedTasks
+        !(":findbugsMain" in skippedTasks)
+    }
+
+    def "is withMessage ignored for non-XML report setting"() {
+        given:
+        buildFile << """
+            findbugsMain {
+                reports {
+                    xml.enabled false
+                    xml.withMessages true
+                    html.enabled true
+                }
+            }
+        """
+
+        and:
+        goodCode()
+
+        when:
+        succeeds "findbugsMain"
+
+        then:
+        !file("build/reports/findbugs/main.xml").exists()
+        file("build/reports/findbugs/main.html").exists()
+
+        when:
+        buildFile << """
+            findbugsMain.reports {
+                xml.withMessages false
+            }
+        """
+
+        and:
+        succeeds "findbugsMain"
+
+        then:
+        !file("build/reports/findbugs/main.xml").exists()
+        file("build/reports/findbugs/main.html").exists()
+        !(":findbugsMain" in nonSkippedTasks)
+        ":findbugsMain" in skippedTasks
+    }
+
+    private boolean containsXmlMessages(File xmlReportFile) {
+        new XmlSlurper().parseText(xmlReportFile.text).BugInstance.children().collect { it.name() }.containsAll(['ShortMessage', 'LongMessage'])
+    }
+
     private goodCode(int numberOfClasses = 1) {
         1.upto(numberOfClasses) {
             file("src/main/java/org/gradle/Class${it}.java") << "package org.gradle; public class Class${it} { public boolean isFoo(Object arg) { return true; } }"
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
index dd47625..04f2350 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
@@ -32,6 +32,24 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
         return "check"
     }
 
+    def "allows configuring tool dependencies explicitly"() {
+        expect: //defaults exist and can be inspected
+        succeeds("dependencies", "--configuration", "pmd")
+        output.contains "pmd:pmd:"
+
+        when:
+        buildFile << """
+            dependencies {
+                //downgrade version:
+                pmd "pmd:pmd:4.2"
+            }
+        """
+
+        then:
+        succeeds("dependencies", "--configuration", "pmd")
+        output.contains "pmd:pmd:4.2"
+    }
+
     def "analyze good code"() {
         goodCode()
 
@@ -46,7 +64,7 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
 
         expect:
         fails("check")
-        failure.assertHasDescription("Execution failed for task ':pmdTest'")
+        failure.assertHasDescription("Execution failed for task ':pmdTest'.")
         failure.assertThatCause(containsString("2 PMD rule violations were found. See the report at:"))
         file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
         file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
@@ -112,7 +130,7 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
 
         expect:
         fails("pmdMain")
-        failure.assertHasDescription("Execution failed for task ':pmdMain'")
+        failure.assertHasDescription("Execution failed for task ':pmdMain'.")
         failure.assertThatCause(containsString("1 PMD rule violations were found. See the report at:"))
         file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
         file("build/reports/pmd/main.xml").assertContents(containsClass("org.gradle.Class2"))
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy
index 5ca45d1..784bebd 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy
@@ -23,7 +23,7 @@ import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.not
 
- at TargetVersions(['4.3', '5.0.2'])
+ at TargetVersions(['4.3', '5.0.4'])
 class PmdPluginVersionIntegrationTest extends MultiVersionIntegrationSpec {
     def "can use different PMD versions"() {
         given:
@@ -43,7 +43,7 @@ class PmdPluginVersionIntegrationTest extends MultiVersionIntegrationSpec {
 
         expect:
         fails("check")
-        failure.assertHasDescription("Execution failed for task ':pmdTest'")
+        failure.assertHasDescription("Execution failed for task ':pmdTest'.")
         failure.assertThatCause(containsString("2 PMD rule violations were found. See the report at:"))
         file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
         file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
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 dc84dd5..faf4b9e 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
@@ -20,6 +20,7 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.NamedDomainObjectSet
 import org.gradle.api.file.FileCollection
 import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsXmlReportImpl;
 import org.gradle.api.reporting.SingleFileReport
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
@@ -92,7 +93,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 more than one report was enabled. You need to disable all but one of them."
     }
 
     def "with report configured"() {
@@ -121,7 +122,38 @@ class FindBugsSpecBuilderTest extends Specification {
         args.contains(destination.absolutePath)
 
         where:
-        reportType << ["xml", "html"]
+        reportType << ["xml", "html", "emacs", "text"]
+    }
+    
+    def "with xml with messages report configured"() {
+        setup:
+        FindBugsXmlReportImpl singleReport = Mock()
+        File destination = Mock()
+        NamedDomainObjectSet enabledReportSet = Mock()
+        FindBugsReportsImpl report = Mock()
+
+        report.enabled >> enabledReportSet
+        report.firstEnabled >> singleReport
+        singleReport.withMessages >> withMessages
+        singleReport.name >> "xml"
+        destination.absolutePath >> "/absolute/report/output"
+        singleReport.destination >> destination
+        enabledReportSet.empty >> false
+        enabledReportSet.size() >> 1
+
+
+        when:
+        builder.configureReports(report)
+        def args = builder.build().arguments
+
+        then:
+        args.contains(arg.toString())
+        args.contains("-outputFile")
+        args.contains(destination.absolutePath)
+        
+        where:
+        withMessages << [true, false]
+        arg << ['-xml:withMessages', '-xml']
     }
 
     def "configure effort"() {
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
index 7796ef0..aaaf457 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
@@ -45,16 +45,15 @@ class CheckstylePlugin extends AbstractCodeQualityPlugin<Checkstyle> {
 
     @Override
     protected void configureTaskDefaults(Checkstyle task, String baseName) {
-        task.conventionMapping.with {
-            checkstyleClasspath = {
-                def config = project.configurations['checkstyle']
-                if (config.dependencies.empty) {
-                    project.dependencies {
-                        checkstyle "com.puppycrawl.tools:checkstyle:$extension.toolVersion"
-                    }
-                }
-                config
+        def conf = project.configurations['checkstyle']
+        conf.incoming.beforeResolve {
+            if (conf.dependencies.empty) {
+                conf.dependencies.add(project.dependencies.create("com.puppycrawl.tools:checkstyle:$extension.toolVersion"))
             }
+        }
+
+        task.conventionMapping.with {
+            checkstyleClasspath = { conf }
             configFile = { extension.configFile }
             configProperties = { extension.configProperties }
             ignoreFailures = { extension.ignoreFailures }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java
index 2c8307a..d4d493a 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java
@@ -20,15 +20,14 @@ import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.reporting.SingleFileReport;
 
 /**
- * The reporting configuration for the the {@link Checkstyle} test.
+ * The reporting configuration for the {@link Checkstyle} test.
  */
 public interface CheckstyleReports extends ReportContainer<SingleFileReport> {
 
     /**
-     * The checkstyle xml report
+     * The checkstyle XML report
      *
-     * @return The checkstyle xml report
+     * @return The checkstyle XML report
      */
     SingleFileReport getXml();
-
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy
index 4c95f83..60d26e0 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
@@ -46,6 +46,24 @@ class CodeNarc extends SourceTask implements VerificationTask, Reporting<CodeNar
     File configFile
 
     /**
+     * The maximum number of priority 1 violations allowed before failing the build.
+     */
+    @Input
+    int maxPriority1Violations
+
+    /**
+     * The maximum number of priority 2 violations allowed before failing the build.
+     */
+    @Input
+    int maxPriority2Violations
+
+    /**
+     * The maximum number of priority 3 violations allowed before failing the build.
+     */
+    @Input
+    int maxPriority3Violations
+
+    /**
      * The format type of the CodeNarc report.
      *
      * @deprecated Use {@code reports.<report-type>.enabled} instead.
@@ -109,7 +127,7 @@ class CodeNarc extends SourceTask implements VerificationTask, Reporting<CodeNar
         antBuilder.withClasspath(getCodenarcClasspath()).execute {
             ant.taskdef(name: 'codenarc', classname: 'org.codenarc.ant.CodeNarcTask')
             try {
-                ant.codenarc(ruleSetFiles: "file:${getConfigFile()}", maxPriority1Violations: 0, maxPriority2Violations: 0, maxPriority3Violations: 0) {
+                ant.codenarc(ruleSetFiles: "file:${getConfigFile()}", maxPriority1Violations: getMaxPriority1Violations(), maxPriority2Violations: getMaxPriority2Violations(), maxPriority3Violations: getMaxPriority3Violations()) {
                     reports.enabled.each { Report r ->
                         report(type: r.name) {
                             option(name: 'outputFile', value: r.destination)
@@ -150,6 +168,4 @@ class CodeNarc extends SourceTask implements VerificationTask, Reporting<CodeNar
     CodeNarcReports reports(Closure closure) {
         reports.configure(closure)
     }
-
-
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy
index 86d2e57..f0af5e5 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy
@@ -24,6 +24,21 @@ class CodeNarcExtension extends CodeQualityExtension {
     File configFile
 
     /**
+     * The maximum number of priority 1 violations allowed before failing the build.
+     */
+    int maxPriority1Violations
+
+    /**
+     * The maximum number of priority 2 violations allowed before failing the build.
+     */
+    int maxPriority2Violations
+
+    /**
+     * The maximum number of priority 3 violations allowed before failing the build.
+     */
+    int maxPriority3Violations
+
+    /**
      * The format type of the CodeNarc report. One of <tt>html</tt>, <tt>xml</tt>, <tt>text</tt>, <tt>console</tt>.
      */
     String reportFormat
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
index fc3cc73..36ceb27 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
@@ -43,6 +43,9 @@ class CodeNarcPlugin extends AbstractCodeQualityPlugin<CodeNarc> {
         extension.with {
             toolVersion = "0.18"
             configFile = project.rootProject.file("config/codenarc/codenarc.xml")
+            maxPriority1Violations = 0
+            maxPriority2Violations = 0
+            maxPriority3Violations = 0
             reportFormat = "html"
         }
         return extension
@@ -50,17 +53,18 @@ class CodeNarcPlugin extends AbstractCodeQualityPlugin<CodeNarc> {
 
     @Override
     protected void configureTaskDefaults(CodeNarc task, String baseName) {
-        task.conventionMapping.with {
-            codenarcClasspath = {
-                def config = project.configurations['codenarc']
-                if (config.dependencies.empty) {
-                    project.dependencies {
-                        codenarc "org.codenarc:CodeNarc:$extension.toolVersion"
-                    }
-                }
-                config
+        def config = project.configurations['codenarc']
+        config.incoming.beforeResolve {
+            if (config.dependencies.empty) {
+                config.dependencies.add(project.dependencies.create("org.codenarc:CodeNarc:$extension.toolVersion"))
             }
+        }
+        task.conventionMapping.with {
+            codenarcClasspath = { config }
             configFile = { extension.configFile }
+            maxPriority1Violations = { extension.maxPriority1Violations }
+            maxPriority2Violations = { extension.maxPriority2Violations }
+            maxPriority3Violations = { extension.maxPriority3Violations }
             ignoreFailures = { extension.ignoreFailures }
         }
 
@@ -80,6 +84,6 @@ class CodeNarcPlugin extends AbstractCodeQualityPlugin<CodeNarc> {
         task.with {
             description = "Run CodeNarc analysis for $sourceSet.name classes"
         }
-        task.setSource( sourceSet.allGroovy )
+        task.setSource(sourceSet.allGroovy)
     }
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java
index 808dda9..db62ea8 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java
@@ -20,21 +20,21 @@ import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.reporting.SingleFileReport;
 
 /**
- * The reporting configuration for the the {@link CodeNarc} test.
+ * The reporting configuration for the {@link CodeNarc} test.
  */
 public interface CodeNarcReports extends ReportContainer<SingleFileReport> {
 
     /**
-     * The codenarc xml report
+     * The codenarc XML report
      *
-     * @return The codenarc xml report
+     * @return The codenarc XML report
      */
     SingleFileReport getXml();
 
     /**
-     * The codenarc html report
+     * The codenarc HTML report
      *
-     * @return The codenarc html report
+     * @return The codenarc HTML report
      */
     SingleFileReport getHtml();
 
@@ -44,5 +44,4 @@ public interface CodeNarcReports extends ReportContainer<SingleFileReport> {
      * @return The codenarc text report
      */
     SingleFileReport getText();
-
 }
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 b38df0a..b76152f 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
@@ -52,7 +52,7 @@ class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
     }
 
     private configureFindBugsConfigurations() {
-        project.configurations.add('findbugsPlugins').with {
+        project.configurations.create('findbugsPlugins').with {
             visible = false
             transitive = true
             description = 'The FindBugs plugins to be used for this project.'
@@ -73,16 +73,14 @@ class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
         task.with {
             pluginClasspath = project.configurations['findbugsPlugins']
         }
-        task.conventionMapping.with {
-            findbugsClasspath = {
-                def config = project.configurations['findbugs']
-                if (config.dependencies.empty) {
-                    project.dependencies {
-                        findbugs("com.google.code.findbugs:findbugs:$extension.toolVersion")
-                    }
-                }
-                config
+        def config = project.configurations['findbugs']
+        config.incoming.beforeResolve {
+            if (config.dependencies.empty) {
+                config.dependencies.add(project.dependencies.create("com.google.code.findbugs:findbugs:$extension.toolVersion"))
             }
+        }
+        task.conventionMapping.with {
+            findbugsClasspath = { config }
             ignoreFailures = { extension.ignoreFailures }
             effort = { extension.effort }
             reportLevel = { extension.reportLevel }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java
index 6c0a28c..8d1f8d1 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java
@@ -20,25 +20,38 @@ import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.reporting.SingleFileReport;
 
 /**
- * The reporting configuration for the the {@link FindBugs} task.
+ * The reporting configuration for the {@link FindBugs} task.
  *
- * Only one of the xml or html reports can be enabled when the task executes. If more than one is enabled, an {@link org.gradle.api.InvalidUserDataException}
+ * Only one of the reports can be enabled when the task executes. If more than one is enabled, an {@link org.gradle.api.InvalidUserDataException}
  * will be thrown.
  */
 public interface FindBugsReports extends ReportContainer<SingleFileReport> {
 
     /**
-     * The findbugs xml report
+     * The findbugs XML report
      *
-     * @return The findbugs xml report
+     * @return The findbugs XML report
      */
-    SingleFileReport getXml();
+    FindBugsXmlReport getXml();
 
     /**
-     * The findbugs html report
+     * The findbugs HTML report
      *
-     * @return The findbugs html report
+     * @return The findbugs HTML report
      */
     SingleFileReport getHtml();
     
+    /**
+     * The findbugs Text report
+     *
+     * @return The findbugs Text report
+     */
+    SingleFileReport getText();
+    
+    /**
+     * The findbugs Emacs report
+     *
+     * @return The findbugs Emacs report
+     */
+    SingleFileReport getEmacs();
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsXmlReport.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsXmlReport.java
new file mode 100644
index 0000000..9294381
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsXmlReport.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.plugins.quality;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.reporting.SingleFileReport;
+
+/**
+ * The single file XML report for FindBugs.
+ */
+ at Incubating
+public interface FindBugsXmlReport extends SingleFileReport {
+    /**
+     * Whether or not FindBugs should generate XML augmented with human-readable messages.
+     * You should use this format if you plan to generate a report using an XSL stylesheet. 
+     * <p>
+     * If {@code true}, FindBugs will augment the XML with human-readable messages.
+     * If {@code false}, FindBugs will not augment the XML with human-readable messages.
+     *
+     * @return Whether or not FindBugs should generate XML augmented with human-readable messages.
+     */
+    boolean isWithMessages();
+
+    /**
+     * Whether or not FindBugs should generate XML augmented with human-readable messages.
+     *
+     * @see #isWithMessages()
+     * @param withMessages Whether or not FindBugs should generate XML augmented with human-readable messages.
+     */
+    void setWithMessages(boolean withMessages);
+    
+}
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 e20f8a7..b493c44 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
@@ -42,7 +42,7 @@ class JDepend extends DefaultTask implements Reporting<JDependReports> {
     @InputDirectory
     File classesDir
 
-    // workaround for GRADLE-2020
+    // workaround for GRADLE-2026
     /**
      * Returns the directory containing the classes to be analyzed.
      */
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java
index e6d038d..a40c6f6 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java
@@ -20,17 +20,17 @@ import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.reporting.SingleFileReport;
 
 /**
- * The reporting configuration for the the {@link JDepend} task.
+ * The reporting configuration for the {@link JDepend} task.
  *
- * Exactly one of the xml or html reports can be enabled when the task executes. If more than one or none is enabled, an {@link org.gradle.api.InvalidUserDataException}
+ * Exactly one of the XML or HTML reports can be enabled when the task executes. If more than one or none is enabled, an {@link org.gradle.api.InvalidUserDataException}
  * will be thrown.
  */
 public interface JDependReports extends ReportContainer<SingleFileReport> {
 
     /**
-     * The jdepend xml report
+     * The jdepend XML report
      *
-     * @return The jdepend xml report
+     * @return The jdepend XML report
      */
     SingleFileReport getXml();
 
@@ -40,5 +40,4 @@ public interface JDependReports extends ReportContainer<SingleFileReport> {
      * @return The jdepend text report
      */
     SingleFileReport getText();
-     
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy
index 51c27ef..2bc63d9 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy
@@ -16,6 +16,7 @@
 package org.gradle.api.plugins.quality
 
 import org.gradle.api.Project
+import org.gradle.api.internal.file.FileLookup
 import org.gradle.api.internal.project.ProjectInternal
 
 class JavaCodeQualityPluginConvention {
@@ -54,6 +55,6 @@ class JavaCodeQualityPluginConvention {
      * The directory to write the Checkstyle results into.
      */
     File getCheckstyleResultsDir() {
-        project.fileResolver.withBaseDir(project.buildDir).resolve(checkstyleResultsDirName)
+        project.services.get(FileLookup).getFileResolver(project.buildDir).resolve(checkstyleResultsDirName)
     }
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
index 1af0df5..d36d82e 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
@@ -51,6 +51,7 @@ class PmdExtension extends CodeQualityExtension {
     void setTargetJdk(def value) {
         targetJdk = TargetJdk.toVersion(value)
     }
+
     /**
      * Convenience method for adding rule sets.
      *
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy
index 2116c66..da23b35 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy
@@ -74,22 +74,17 @@ class PmdPlugin extends AbstractCodeQualityPlugin<Pmd> {
 
     @Override
     protected void configureTaskDefaults(Pmd task, String baseName) {
-
-        task.conventionMapping.with {
-            pmdClasspath = {
-                def config = project.configurations['pmd']
-                if (config.dependencies.empty) {
-                    VersionNumber version = VersionNumber.parse(extension.toolVersion)
-                    project.dependencies {
-                        if (version < VersionNumber.parse("5.0.0")) {
-                            pmd "pmd:pmd:$extension.toolVersion"
-                        } else {
-                            pmd "net.sourceforge.pmd:pmd:$extension.toolVersion"
-                        }
-                    }
-                }
-                config
+        def config = project.configurations['pmd']
+        config.incoming.beforeResolve {
+            if (config.dependencies.empty) {
+                VersionNumber version = VersionNumber.parse(extension.toolVersion)
+                String dependency = (version < VersionNumber.parse("5.0.0"))?
+                    "pmd:pmd:$extension.toolVersion" : "net.sourceforge.pmd:pmd:$extension.toolVersion"
+                config.dependencies.add(project.dependencies.create(dependency))
             }
+        }
+        task.conventionMapping.with {
+            pmdClasspath = { config }
             ruleSets = { extension.ruleSets }
             ruleSetFiles = { extension.ruleSetFiles }
             ignoreFailures = { extension.ignoreFailures }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java
index 3307f0b..fe2adda 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java
@@ -20,21 +20,21 @@ import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.reporting.SingleFileReport;
 
 /**
- * The reporting configuration for the the {@link Pmd} task.
+ * The reporting configuration for the {@link Pmd} task.
  */
 public interface PmdReports extends ReportContainer<SingleFileReport> {
 
     /**
-     * The pmd (single file) html report
+     * The pmd (single file) HTML report
      *
-     * @return The pmd (single file) html report
+     * @return The pmd (single file) HTML report
      */
     SingleFileReport getHtml();
 
     /**
-     * The pmd (single file) xml report
+     * The pmd (single file) XML report
      *
-     * @return The pmd (single file) xml report
+     * @return The pmd (single file) XML report
      */
     SingleFileReport getXml();
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy
index 23aef56..7a7b4f8 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy
@@ -64,7 +64,7 @@ abstract class AbstractCodeQualityPlugin<T> implements Plugin<ProjectInternal> {
     }
 
     protected void createConfigurations() {
-        project.configurations.add(configurationName).with {
+        project.configurations.create(configurationName).with {
             visible = false
             transitive = true
             description = "The ${toolName} libraries to be used for this project."
@@ -109,7 +109,7 @@ abstract class AbstractCodeQualityPlugin<T> implements Plugin<ProjectInternal> {
     private void configureSourceSetRule() {
         project.plugins.withType(basePlugin) {
             project.sourceSets.all { SourceSet sourceSet ->
-                T task = project.tasks.add(sourceSet.getTaskName(taskBaseName, null), taskType)
+                T task = project.tasks.create(sourceSet.getTaskName(taskBaseName, null), taskType)
                 configureForSourceSet(sourceSet, task)
             }
         }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java
index 200d8a0..2be1b21 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java
@@ -18,24 +18,45 @@ package org.gradle.api.plugins.quality.internal;
 
 import org.gradle.api.Task;
 import org.gradle.api.plugins.quality.FindBugsReports;
+import org.gradle.api.plugins.quality.FindBugsXmlReport;
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsXmlReportImpl;
 import org.gradle.api.reporting.SingleFileReport;
 import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
 import org.gradle.api.reporting.internal.TaskReportContainer;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
 
 public class FindBugsReportsImpl extends TaskReportContainer<SingleFileReport> implements FindBugsReports {
 
     public FindBugsReportsImpl(Task task) {
         super(SingleFileReport.class, task);
 
-        add(TaskGeneratedSingleFileReport.class, "xml", task);
+        add(FindBugsXmlReportImpl.class, "xml", task);
         add(TaskGeneratedSingleFileReport.class, "html", task);
+        add(TaskGeneratedSingleFileReport.class, "text", task);
+        add(TaskGeneratedSingleFileReport.class, "emacs", task);
     }
 
-    public SingleFileReport getXml() {
-        return getByName("xml");
+    public FindBugsXmlReport getXml() {
+        return (FindBugsXmlReport) getByName("xml");
     }
 
     public SingleFileReport getHtml() {
         return getByName("html");
     }
+    
+    public SingleFileReport getText() {
+        return getByName("text");
+    }
+    
+    public SingleFileReport getEmacs() {
+        return getByName("emacs");
+    }
+
+    @Input
+    @Optional
+    public Boolean getWithMessagesFlag() {
+        FindBugsXmlReport report = (FindBugsXmlReport)getEnabled().findByName("xml");
+        return report != null ? report.isWithMessages() : Boolean.FALSE;
+    }
 }
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 d4dfda5..47daf3d 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,7 +16,11 @@
 
 package org.gradle.api.plugins.quality.internal.findbugs;
 
-import com.google.common.collect.ImmutableSet;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.plugins.quality.FindBugsReports;
@@ -24,10 +28,7 @@ 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;
+import com.google.common.collect.ImmutableSet;
 
 public class FindBugsSpecBuilder {
     private static final Set<String> VALID_EFFORTS = ImmutableSet.of("min", "default", "max");
@@ -143,11 +144,18 @@ public class FindBugsSpecBuilder {
         if (reports != null && !reports.getEnabled().isEmpty()) {
             if (reports.getEnabled().size() == 1) {
                 FindBugsReportsImpl reportsImpl = (FindBugsReportsImpl) reports;
-                args.add("-" + reportsImpl.getFirstEnabled().getName());
+                String outputArg = "-" + reportsImpl.getFirstEnabled().getName();
+                if (reportsImpl.getFirstEnabled() instanceof FindBugsXmlReportImpl) {
+                    FindBugsXmlReportImpl r = (FindBugsXmlReportImpl)reportsImpl.getFirstEnabled();
+                    if (r.isWithMessages()) {
+                        outputArg += ":withMessages";
+                    }
+                }
+                args.add(outputArg);
                 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 more than one report was enabled. You need to disable all but one of them.");
             }
         }
 
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 aaaca07..1c658b4 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
@@ -28,6 +28,8 @@ class FindBugsWorkerManager {
 
         FindBugsWorkerClient clientCallBack = new FindBugsWorkerClient()
         process.connection.addIncoming(FindBugsWorkerClientProtocol.class, clientCallBack);
+        process.connection.connect()
+
         FindBugsResult result = clientCallBack.getResult();
 
         process.waitForStop();
@@ -36,6 +38,7 @@ class FindBugsWorkerManager {
 
     private WorkerProcess createWorkerProcess(File workingDir, Factory<WorkerProcessBuilder> workerFactory, FileCollection findBugsClasspath, FindBugsSpec spec) {
         WorkerProcessBuilder builder = workerFactory.create();
+        builder.setBaseName("Gradle FindBugs Worker")
         builder.applicationClasspath(findBugsClasspath);
         builder.sharedPackages(Arrays.asList("edu.umd.cs.findbugs"));
         JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
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 ff0a641..0c8f9e1 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
@@ -34,6 +34,7 @@ public class FindBugsWorkerServer implements Action<WorkerProcessContext>, Seria
     public void execute(WorkerProcessContext context) {
         final FindBugsResult result = execute();
         final FindBugsWorkerClientProtocol clientProtocol = context.getServerConnection().addOutgoing(FindBugsWorkerClientProtocol.class);
+        context.getServerConnection().connect();
         clientProtocol.executed(result);
     }
 
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsXmlReportImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsXmlReportImpl.java
new file mode 100644
index 0000000..9b0b24d
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsXmlReportImpl.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.plugins.quality.internal.findbugs;
+
+import org.gradle.api.Task;
+import org.gradle.api.plugins.quality.FindBugsXmlReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+
+public class FindBugsXmlReportImpl extends TaskGeneratedSingleFileReport implements FindBugsXmlReport {
+
+    private boolean withMessages;
+    
+    public FindBugsXmlReportImpl(String name, Task task) {
+        super(name, task);
+    }
+
+    public boolean isWithMessages() {
+        return withMessages;
+    }
+
+    public void setWithMessages(boolean withMessages) {
+        this.withMessages = withMessages;
+    }
+
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
index afadcfd..d3afb34 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
@@ -18,17 +18,17 @@ package org.gradle.api.plugins.quality
 import org.gradle.api.Project
 import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.api.tasks.SourceSet
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.gradle.api.plugins.JavaBasePlugin
 
 import spock.lang.Specification
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.hamcrest.Matchers.*
 import static spock.util.matcher.HamcrestSupport.that
 
 class CheckstylePluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(CheckstylePlugin)
@@ -88,7 +88,7 @@ class CheckstylePluginTest extends Specification {
     }
     
     def "configures any additional checkstyle tasks"() {
-        def task = project.tasks.add("checkstyleCustom", Checkstyle)
+        def task = project.tasks.create("checkstyleCustom", Checkstyle)
 
         expect:
         task.description == null
@@ -153,7 +153,7 @@ class CheckstylePluginTest extends Specification {
     }
     
     def "can customize any additional checkstyle tasks via extension"() {
-        def task = project.tasks.add("checkstyleCustom", Checkstyle)
+        def task = project.tasks.create("checkstyleCustom", Checkstyle)
         project.checkstyle {
             configFile = project.file("checkstyle-config")
             configProperties = [foo: "foo"]
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy
index 6b72fd4..c53f5e8 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy
@@ -23,7 +23,7 @@ import spock.lang.Specification
 class CheckstyleTest extends Specification {
     def "default configuration"() {
         def project = ProjectBuilder.builder().build()
-        def checkstyle = project.tasks.add("checkstyle", Checkstyle)
+        def checkstyle = project.tasks.create("checkstyle", Checkstyle)
 
         expect:
         with(checkstyle) {
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy
index 17ac7b4..181a965 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy
@@ -18,15 +18,15 @@ package org.gradle.api.plugins.quality
 import org.gradle.api.Project
 import org.gradle.api.plugins.GroovyBasePlugin
 import org.gradle.api.tasks.SourceSet
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.hamcrest.Matchers.hasItems
 import static spock.util.matcher.HamcrestSupport.that
 import org.gradle.api.plugins.ReportingBasePlugin
 
 class CodeNarcPluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(CodeNarcPlugin)
@@ -51,6 +51,9 @@ class CodeNarcPluginTest extends Specification {
         expect:
         CodeNarcExtension codenarc = project.extensions.codenarc
         codenarc.configFile == project.file("config/codenarc/codenarc.xml")
+        codenarc.maxPriority1Violations == 0
+        codenarc.maxPriority2Violations == 0
+        codenarc.maxPriority3Violations == 0
         codenarc.reportFormat == "html"
         codenarc.reportsDir == project.file("build/reports/codenarc")
         codenarc.sourceSets == []
@@ -79,6 +82,9 @@ class CodeNarcPluginTest extends Specification {
             assert source as List == sourceSet.allGroovy  as List
             assert codenarcClasspath == project.configurations.codenarc
             assert configFile == project.file("config/codenarc/codenarc.xml")
+            assert maxPriority1Violations == 0
+            assert maxPriority2Violations == 0
+            assert maxPriority3Violations == 0
             assert reportFormat == "html"
             assert reportFile == project.file("build/reports/codenarc/${sourceSet.name}.html")
             assert ignoreFailures == false
@@ -96,6 +102,9 @@ class CodeNarcPluginTest extends Specification {
         project.codenarc {
             checkTasks = ["codenarcMain"]
             configFile = project.file("codenarc-config")
+            maxPriority1Violations = 10
+            maxPriority2Violations = 50
+            maxPriority3Violations = 200
             reportFormat = "xml"
             reportsDir = project.file("codenarc-reports")
             ignoreFailures = true
@@ -115,6 +124,9 @@ class CodeNarcPluginTest extends Specification {
             assert source as List == sourceSet.allGroovy as List
             assert codenarcClasspath == project.configurations.codenarc
             assert configFile == project.file("codenarc-config")
+            assert maxPriority1Violations == 10
+            assert maxPriority2Violations == 50
+            assert maxPriority3Violations == 200
             assert reportFormat == "xml"
             assert reportFile == project.file("codenarc-reports/${sourceSet.name}.xml")
             assert ignoreFailures == true
@@ -122,23 +134,29 @@ class CodeNarcPluginTest extends Specification {
     }
 
     def "configures any additional codenarc tasks"() {
-        def task = project.tasks.add("codenarcCustom", CodeNarc)
+        def task = project.tasks.create("codenarcCustom", CodeNarc)
 
         expect:
         task.description == null
         task.source.isEmpty()
         task.codenarcClasspath == project.configurations.codenarc
         task.configFile == project.file("config/codenarc/codenarc.xml")
+        task.maxPriority1Violations == 0
+        task.maxPriority2Violations == 0
+        task.maxPriority3Violations == 0
         task.reportFormat == "html"
         task.reportFile == project.file("build/reports/codenarc/custom.html")
         task.ignoreFailures == false
     }
 
     def "can customize additional tasks via extension"() {
-        def task = project.tasks.add("codenarcCustom", CodeNarc)
+        def task = project.tasks.create("codenarcCustom", CodeNarc)
 
         project.codenarc {
             configFile = project.file("codenarc-config")
+            maxPriority1Violations = 10
+            maxPriority2Violations = 50
+            maxPriority3Violations = 200
             reportFormat = "xml"
             reportsDir = project.file("codenarc-reports")
             ignoreFailures = true
@@ -149,6 +167,9 @@ class CodeNarcPluginTest extends Specification {
         task.source.isEmpty()
         task.codenarcClasspath == project.configurations.codenarc
         task.configFile == project.file("codenarc-config")
+        task.maxPriority1Violations == 10
+        task.maxPriority2Violations == 50
+        task.maxPriority3Violations == 200
         task.reportFormat == "xml"
         task.reportFile == project.file("codenarc-reports/custom.xml")
         task.ignoreFailures == true
@@ -162,7 +183,7 @@ class CodeNarcPluginTest extends Specification {
             other
         }
 
-        project.tasks.add("codenarcCustom", CodeNarc)
+        project.tasks.create("codenarcCustom", CodeNarc)
         
         expect:
         that(project.check, dependsOn(hasItems("codenarcMain", "codenarcTest", "codenarcOther")))
@@ -176,7 +197,7 @@ class CodeNarcPluginTest extends Specification {
             other
         }
 
-        project.tasks.add("codenarcCustom", CodeNarc)
+        project.tasks.create("codenarcCustom", CodeNarc)
 
         project.codenarc {
             sourceSets = [project.sourceSets.main]
@@ -187,7 +208,7 @@ class CodeNarcPluginTest extends Specification {
     }
 
     def "can customize task directly"() {
-        CodeNarc task = project.tasks.add("codenarcCustom", CodeNarc)
+        CodeNarc task = project.tasks.create("codenarcCustom", CodeNarc)
 
         task.reports.xml {
             enabled true
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
index 147bd99..274d893 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
@@ -19,7 +19,8 @@ import org.gradle.api.Project
 import org.gradle.api.plugins.GroovyPlugin
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.ReportingBasePlugin
-import org.gradle.util.HelperUtil
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.util.TestUtil
 import org.junit.Test
 import static org.gradle.util.Matchers.*
 import static org.hamcrest.Matchers.*
@@ -28,7 +29,7 @@ import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.GroovyBasePlugin
 
 class CodeQualityPluginTest {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final CodeQualityPlugin plugin = new CodeQualityPlugin()
 
     @Test public void appliesCheckstyleAndCodeNarcPlugins() {
@@ -67,7 +68,7 @@ class CodeQualityPluginTest {
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
         assertThat(task.resultFile, equalTo(project.file("build/checkstyle/main.xml")))
         assertThat(task.configProperties, equalTo(project.checkstyleProperties))
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.CLASSES_TASK_NAME))
 
         task = project.tasks[CodeQualityPlugin.CHECKSTYLE_TEST_TASK]
         assertThat(task, instanceOf(Checkstyle))
@@ -76,9 +77,9 @@ class CodeQualityPluginTest {
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
         assertThat(task.resultFile, equalTo(project.file("build/checkstyle/test.xml")))
         assertThat(task.properties, equalTo(project.checkstyleProperties))
-        assertThat(task, dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME))
 
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         task = project.tasks['checkstyleCustom']
         assertThat(task, instanceOf(Checkstyle))
         assertThat(task.source as List, equalTo(project.sourceSets.custom.allJava as List))
@@ -86,10 +87,10 @@ class CodeQualityPluginTest {
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
         assertThat(task.resultFile, equalTo(project.file("build/checkstyle/custom.xml")))
         assertThat(task.properties, equalTo(project.checkstyleProperties))
-        assertThat(task, dependsOn("customClasses"))
+        assertThat(task, TaskDependencyMatchers.dependsOn("customClasses"))
 
         task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
-        assertThat(task, dependsOn(hasItems(CodeQualityPlugin.CHECKSTYLE_MAIN_TASK, CodeQualityPlugin.CHECKSTYLE_TEST_TASK, 'checkstyleCustom')))
+        assertThat(task, TaskDependencyMatchers.dependsOn(hasItems(CodeQualityPlugin.CHECKSTYLE_MAIN_TASK, CodeQualityPlugin.CHECKSTYLE_TEST_TASK, 'checkstyleCustom')))
     }
 
     @Test public void createsTasksAndAppliesMappingsForEachGroovySourceSet() {
@@ -103,7 +104,7 @@ class CodeQualityPluginTest {
         assertThat(task.codenarcClasspath, equalTo(project.configurations.codenarc))
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
         assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/main.html")))
-        assertThat(task, dependsOn())
+        assertThat(task, TaskDependencyMatchers.dependsOn())
 
         task = project.tasks[CodeQualityPlugin.CODE_NARC_TEST_TASK]
         assertThat(task, instanceOf(CodeNarc))
@@ -112,9 +113,9 @@ class CodeQualityPluginTest {
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
         assertThat(task.reportFormat, equalTo(project.codeNarcReportsFormat))
         assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/test.html")))
-        assertThat(task, dependsOn())
+        assertThat(task, TaskDependencyMatchers.dependsOn())
 
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         task = project.tasks['codenarcCustom']
         assertThat(task, instanceOf(CodeNarc))
         assertThat(task.source as List, equalTo(project.sourceSets.custom.allGroovy as List))
@@ -122,12 +123,12 @@ class CodeQualityPluginTest {
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
         assertThat(task.reportFormat, equalTo(project.codeNarcReportsFormat))
         assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/custom.html")))
-        assertThat(task, dependsOn())
+        assertThat(task, TaskDependencyMatchers.dependsOn())
 
         task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
-        assertThat(task, dependsOn(hasItem(CodeQualityPlugin.CODE_NARC_MAIN_TASK)))
-        assertThat(task, dependsOn(hasItem(CodeQualityPlugin.CODE_NARC_TEST_TASK)))
-        assertThat(task, dependsOn(hasItem('codenarcCustom')))
+        assertThat(task, TaskDependencyMatchers.dependsOn(hasItem(CodeQualityPlugin.CODE_NARC_MAIN_TASK)))
+        assertThat(task, TaskDependencyMatchers.dependsOn(hasItem(CodeQualityPlugin.CODE_NARC_TEST_TASK)))
+        assertThat(task, TaskDependencyMatchers.dependsOn(hasItem('codenarcCustom')))
     }
 
     @Test public void appliesMappingsForCheckstyleTasksWithoutRequiringTheJavaBasePluginToBeApplied() {
@@ -135,7 +136,7 @@ class CodeQualityPluginTest {
 
         project.checkstyleProperties.someProp = 'someValue'
 
-        def task = project.tasks.add('checkstyleApi', Checkstyle)
+        def task = project.tasks.create('checkstyleApi', Checkstyle)
         assertThat(task.source, isEmpty())
         assertThat(task.checkstyleClasspath, equalTo(project.configurations.checkstyle))
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
@@ -148,7 +149,7 @@ class CodeQualityPluginTest {
 
         project.checkstyleProperties.someProp = 'someValue'
 
-        def task = project.tasks.add('codenarcApi', CodeNarc)
+        def task = project.tasks.create('codenarcApi', CodeNarc)
         assertThat(task.source, isEmpty())
         assertThat(task.codenarcClasspath, equalTo(project.configurations.codenarc))
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
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 e7640cd..ac2ebb9 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
@@ -19,16 +19,15 @@ import org.gradle.api.Project
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.api.tasks.SourceSet
-import org.gradle.util.HelperUtil
-
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.hamcrest.Matchers.*
 import static spock.util.matcher.HamcrestSupport.that
 
 class FindBugsPluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(FindBugsPlugin)
@@ -96,7 +95,7 @@ class FindBugsPluginTest extends Specification {
     }
 
     def "configures any additional FindBugs tasks"() {
-        def task = project.tasks.add("findbugsCustom", FindBugs)
+        def task = project.tasks.create("findbugsCustom", FindBugs)
 
         expect:
         with(task) {
@@ -176,7 +175,7 @@ class FindBugsPluginTest extends Specification {
     }
     
     def "can customize any additional FindBugs tasks via extension"() {
-        def task = project.tasks.add("findbugsCustom", FindBugs)
+        def task = project.tasks.create("findbugsCustom", FindBugs)
         project.findbugs {
             reportsDir = project.file("findbugs-reports")
             ignoreFailures = true
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 1c3077e..3a9d1d9 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
@@ -27,7 +27,7 @@ class FindBugsTest extends Specification {
 
     def setup() {
         def project = ProjectBuilder.builder().build()
-        findbugs = project.tasks.add("findbugs", FindBugs)
+        findbugs = project.tasks.create("findbugs", FindBugs)
     }
 
     def "fails when errorCount greater than zero"() {
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy
index 424a8eb..d28dd7c 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy
@@ -19,14 +19,14 @@ import org.gradle.api.Project
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.api.tasks.SourceSet
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.hamcrest.Matchers.*
 import static spock.util.matcher.HamcrestSupport.that
 
 class JDependPluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(JDependPlugin)
@@ -80,7 +80,7 @@ class JDependPluginTest extends Specification {
     }
 
     def "configures any additional JDepend tasks"() {
-        def task = project.tasks.add("jdependCustom", JDepend)
+        def task = project.tasks.create("jdependCustom", JDepend)
 
         expect:
         task.description == null
@@ -124,7 +124,7 @@ class JDependPluginTest extends Specification {
     }
 
     def "can customize any additional JDepend tasks via extension"() {
-        def task = project.tasks.add("jdependCustom", JDepend)
+        def task = project.tasks.create("jdependCustom", JDepend)
         project.jdepend {
             reportsDir = project.file("jdepend-reports")
         }
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
index b708011..2513e92 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
@@ -19,15 +19,15 @@ import org.gradle.api.Project
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.api.tasks.SourceSet
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.hamcrest.Matchers.*
 import static spock.util.matcher.HamcrestSupport.that
 
 class PmdPluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(PmdPlugin)
@@ -110,7 +110,7 @@ class PmdPluginTest extends Specification {
     }
 
     def "configures any additional PMD tasks"() {
-        def task = project.tasks.add("pmdCustom", Pmd)
+        def task = project.tasks.create("pmdCustom", Pmd)
 
         expect:
         task.description == null
@@ -175,7 +175,7 @@ class PmdPluginTest extends Specification {
     }
 
     def "can customize any additional PMD tasks via extension"() {
-        def task = project.tasks.add("pmdCustom", Pmd)
+        def task = project.tasks.create("pmdCustom", Pmd)
         project.pmd {
             ruleSets = ["braces", "unusedcode"]
             ruleSetFiles = project.files("my-ruleset.xml")
diff --git a/subprojects/core-impl/core-impl.gradle b/subprojects/core-impl/core-impl.gradle
index 495307e..e66ee48 100644
--- a/subprojects/core-impl/core-impl.gradle
+++ b/subprojects/core-impl/core-impl.gradle
@@ -7,8 +7,6 @@ configurations {
 }
 
 dependencies {
-    groovy libraries.groovy
-
     compile project(":core")
 
     compile libraries.commons_httpclient
@@ -26,6 +24,8 @@ dependencies {
 
     mvn3Input libraries.maven3
 
+    testCompile libraries.groovy
+
     //this dependency is necessary to run IvySFtpResolverIntegrationTest on ibm jdk
     integTestRuntime "org.bouncycastle:bcprov-jdk15:1.46 at jar"
 }
@@ -44,6 +44,10 @@ task jarJarMaven3(type: JarJar) {
     avoidConflictingPlexusComponents(it)
 }
 
+if(isWindows && javaVersion.java5){
+    compileTestGroovy.options.fork (memoryMaximumSize: '512m')
+}
+
 classpathManifest.dependsOn jarJarMaven3 //see GRADLE-2521
 
 //adding explicit task dependencies due to http://issues.gradle.org/browse/GRADLE-2481
@@ -51,6 +55,7 @@ def allJarJars = tasks.withType(JarJar)
 ideaModule.dependsOn allJarJars
 eclipseClasspath.dependsOn allJarJars
 useTestFixtures()
+useTestFixtures(project: ":messaging")
 
 def avoidConflictingPlexusComponents(JarJar task) {
     //DefaultSecDispatcher component is configured in 2 different jars (META-INF/plexus/components.xml).
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDeclarationIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDeclarationIntegrationTest.groovy
new file mode 100644
index 0000000..99c6546
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDeclarationIntegrationTest.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.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class ArtifactDeclarationIntegrationTest extends AbstractIntegrationSpec {
+
+    def "artifact file may not have an extension"() {
+        buildFile << """
+            apply plugin: 'java'
+            configurations { foo }
+            artifacts {
+                foo file("foo")
+                foo file("foo.txt")
+            }
+            task checkArtifacts << {
+                assert configurations.foo.artifacts.files*.name == ["foo", "foo.txt"]
+            }
+        """
+
+        expect: run "checkArtifacts"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
index 89a69ea..cf87638 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
@@ -120,10 +120,10 @@ task listMissingClassifier << { configurations.missingClassifier.each { } }
         inTestDirectory().withTasks('listJar').run()
 
         def result = inTestDirectory().withTasks('listMissingExt').runWithFailure()
-        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0 at zip' not found"))
+        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0:lib.zip' not found"))
 
         result = inTestDirectory().withTasks('listMissingClassifier').runWithFailure()
-        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0:classifier1 at jar' not found"))
+        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0:lib-classifier1.jar' not found"))
     }
 
     @Test
@@ -165,6 +165,36 @@ project(':b') {
     }
 
     @Test
+    public void artifactFilesPreserveFixedOrder() {
+        repo.module('org', 'leaf1').publish()
+        repo.module('org', 'leaf2').publish()
+        repo.module('org', 'leaf3').publish()
+        repo.module('org', 'leaf4').publish()
+
+        repo.module('org', 'middle1').dependsOn("leaf1", "leaf2").publish()
+        repo.module('org', 'middle2').dependsOn("leaf3", "leaf4").publish()
+
+        repo.module('org', 'top').dependsOn("middle1", "middle2").publish()
+
+        testFile('build.gradle') << """
+            repositories {
+                maven { url '${repo.uri}' }
+            }
+            configurations {
+                compile
+            }
+            dependencies {
+                compile "org:middle2:1.0", "org:middle1:1.0"
+            }
+            task test << {
+                assert configurations.compile.files.collect { it.name } == ['middle2-1.0.jar', 'middle1-1.0.jar', 'leaf3-1.0.jar', 'leaf4-1.0.jar', 'leaf1-1.0.jar', 'leaf2-1.0.jar']
+            }
+        """
+
+        executer.withTasks("test").run()
+    }
+
+    @Test
     public void exposesMetaDataAboutResolvedArtifactsInAFixedOrder() {
         def module = repo.module('org.gradle.test', 'lib', '1.0')
         module.artifact(type: 'zip')
@@ -186,7 +216,7 @@ dependencies {
     compile "org.gradle.test:dist:1.0"
 }
 task test << {
-    assert configurations.compile.files.collect { it.name } == ['lib-1.0.jar', 'lib-1.0.zip', 'lib-1.0-classifier.jar', 'dist-1.0.zip']
+    assert configurations.compile.files.collect { it.name } == ['lib-1.0.jar', 'lib-1.0-classifier.jar', 'lib-1.0.zip', 'dist-1.0.zip']
     def artifacts = configurations.compile.resolvedConfiguration.resolvedArtifacts as List
     assert artifacts.size() == 4
     assert artifacts[0].name == 'lib'
@@ -611,7 +641,7 @@ task test << {
 
         failure
                 .assertHasFileName("Build file '" + buildFile.getPath() + "'")
-                .assertHasDescription("Execution failed for task ':listJars'");
+                .assertHasDescription("Execution failed for task ':listJars'.");
 
         failure.assertResolutionFailure(':compile')
                 .assertHasCause("Could not find test:unknownProjectA:1.2.")
@@ -622,7 +652,7 @@ task test << {
     public void projectCanDependOnItself() {
         TestFile buildFile = testFile("build.gradle");
         buildFile << '''
-            configurations { compile; add('default') }
+            configurations { compile; create('default') }
             dependencies { compile project(':') }
             task jar1(type: Jar) { destinationDir = buildDir; baseName = '1' }
             task jar2(type: Jar) { destinationDir = buildDir; baseName = '2' }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
deleted file mode 100644
index 615b6c0..0000000
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,86 +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.resolve
-
-import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.test.fixtures.maven.MavenHttpModule
-import org.junit.Rule
-
-class ArtifactOnlyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule public final TestResources resources = new TestResources(temporaryFolder);
-
-    MavenHttpModule projectA
-
-    def "setup"() {
-        projectA = mavenHttpRepo.module('group', 'projectA').publish()
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url '${mavenHttpRepo.uri}' }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0 at jar'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-    }
-
-    def "can resolve and cache artifact-only dependencies from a HTTP repository"() {
-        when:
-        projectA.pom.expectGet()
-        projectA.artifact.expectGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-
-        and:
-        newRequestForModuleDoesNotContactServer()
-    }
-
-    def "can resolve and cache artifact-only dependencies from a HTTP repository with no descriptor"() {
-        when:
-        projectA.pom.expectGetMissing()
-        projectA.artifact.expectHead()
-        projectA.artifact.expectGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-
-        and:
-        newRequestForModuleDoesNotContactServer()
-    }
-
-    private newRequestForModuleDoesNotContactServer() {
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-        server.resetExpectations()
-        def result = run 'retrieve'
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-        return result
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
index 6c39df9..5a6f026 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
@@ -51,8 +51,8 @@ task deleteCacheFiles(type: Delete) {
         
         when:
         server.resetExpectations()
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         then:
         succeeds('listJars')
@@ -95,15 +95,15 @@ project('b') {
 """
 
         when:
-        module1.expectIvyGet()
-        module1.expectJarGet()
+        module1.ivy.expectGet()
+        module1.jar.expectGet()
 
-        module2.expectIvyHead()
-        module2.expectIvySha1Get()
-        module2.expectIvyGet()
-        module2.expectJarHead()
-        module2.expectJarSha1Get()
-        module2.expectJarGet()
+        module2.ivy.expectHead()
+        module2.ivy.sha1.expectGet()
+        module2.ivy.expectGet()
+        module2.jar.expectHead()
+        module2.jar.sha1.expectGet()
+        module2.jar.expectGet()
 
         then:
         succeeds 'retrieve'
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
index b347c86..f54052b 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.integtests.resolve
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
 public class ClientModuleDependenciesResolveIntegrationTest extends AbstractDependencyResolutionTest {
     @Test
     public void "uses metadata from Client Module and looks up artifact in declared repositories"() {
@@ -51,12 +48,12 @@ task listJars << {
 """
 
         when:
-        projectB.expectIvyGet()
-        projectB.expectJarGet()
-        projectAInRepo1.expectIvyGetMissing()
-        projectAInRepo1.expectJarHeadMissing()
-        projectAInRepo2.expectIvyGet()
-        projectAInRepo2.expectJarGet()
+        projectB.ivy.expectGet()
+        projectB.jar.expectGet()
+        projectAInRepo1.ivy.expectGetMissing()
+        projectAInRepo1.jar.expectHeadMissing()
+        projectAInRepo2.ivy.expectGet()
+        projectAInRepo2.jar.expectGet()
 
         then:
         succeeds('listJars')
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesChangingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesChangingModulesIntegrationTest.groovy
new file mode 100644
index 0000000..b319e82
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesChangingModulesIntegrationTest.groovy
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.HttpRepository
+
+abstract class ComponentMetadataRulesChangingModulesIntegrationTest extends AbstractDependencyResolutionTest {
+    abstract HttpRepository getRepo()
+    abstract String getRepoDeclaration()
+
+    def moduleA = getRepo().module('org.test', 'moduleA', '1.0')
+
+    def setup() {
+        server.start()
+        moduleA.publish()
+        moduleA.allowAll()
+    }
+
+    def "changing dependency doesn't affect changing flag"() {
+        buildFile <<
+"""
+$repoDeclaration
+configurations {
+    modules
+}
+dependencies {
+    modules("org.test:moduleA:1.0") { changing = true }
+    components {
+        eachComponent { details ->
+            assert !details.changing
+        }
+    }
+}
+task resolve << {
+    configurations.modules.resolve()
+}
+"""
+
+        expect:
+        succeeds("resolve")
+    }
+
+    def "static and dynamic dependencies have changing flag initialized to false"() {
+        buildFile <<
+"""
+$repoDeclaration
+configurations {
+    modules
+}
+dependencies {
+    modules "org.test:moduleA:$version"
+    components {
+        eachComponent { details ->
+            assert !details.changing
+        }
+    }
+}
+task resolve << {
+    configurations.modules.resolve()
+}
+"""
+
+        expect:
+        succeeds("resolve")
+
+        where:
+        version << ["1.0", "[1.0,2.0]"]
+    }
+
+
+    def "rule can make a component changing"() {
+        buildFile <<
+"""
+$repoDeclaration
+configurations {
+    modules {
+        resolutionStrategy.cacheChangingModulesFor 0, "seconds"
+    }
+}
+dependencies {
+    modules("org.test:moduleA:1.0")
+    components {
+        eachComponent { it.changing = true }
+    }
+}
+task resolve << {
+    copy {
+        from configurations.modules
+        into "modules"
+    }
+}
+"""
+
+        when:
+        run("resolve")
+        def artifact = file("modules/moduleA-1.0.jar")
+        def snapshot = artifact.snapshot()
+
+        and:
+        moduleA.publishWithChangedContent()
+        run("resolve")
+
+        then:
+        artifact.assertContentsHaveChangedSince(snapshot)
+
+        when:
+        buildFile << """
+dependencies.components.eachComponent { it.changing = false }
+"""
+        snapshot = artifact.snapshot()
+        server.resetExpectations()
+
+        and:
+        run("resolve")
+
+        then:
+        artifact.assertContentsHaveNotChangedSince(snapshot)
+    }
+
+    def "rule cannot make a dependency non-changing"() {
+        buildFile <<
+"""
+$repoDeclaration
+configurations {
+    modules {
+        resolutionStrategy.cacheChangingModulesFor 0, "seconds"
+    }
+}
+dependencies {
+    modules("org.test:moduleA:1.0") { changing = true }
+    components {
+        eachComponent { it.changing = false }
+    }
+}
+task resolve << {
+    copy {
+        from configurations.modules
+        into "modules"
+    }
+}
+"""
+
+        when:
+        run("resolve")
+        def artifact = file("modules/moduleA-1.0.jar")
+        def snapshot = artifact.snapshot()
+
+        and:
+        moduleA.publishWithChangedContent()
+        run("resolve")
+
+        then:
+        artifact.assertContentsHaveChangedSince(snapshot)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesIntegrationTest.groovy
new file mode 100644
index 0000000..ce5aabd
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesIntegrationTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.HttpRepository
+
+abstract class ComponentMetadataRulesIntegrationTest extends AbstractDependencyResolutionTest {
+    abstract HttpRepository getRepo()
+    abstract String getRepoDeclaration()
+    abstract String getDefaultStatus()
+
+    def setup() {
+        server.start()
+
+        buildFile <<
+"""
+$repoDeclaration
+
+configurations { compile }
+
+dependencies {
+    compile 'org.test:projectA:1.0'
+}
+
+task resolve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+    }
+
+
+    def "rule receives correct metadata"() {
+        repo.module('org.test', 'projectA', '1.0').publish().allowAll()
+        buildFile <<
+"""
+dependencies {
+    components {
+        eachComponent { details ->
+            assert details.id.group == "org.test"
+            assert details.id.name == "projectA"
+            assert details.id.version == "1.0"
+            assert details.status == "$defaultStatus"
+            assert details.statusScheme == ["integration", "milestone", "release"]
+            assert !details.changing
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'resolve'
+    }
+
+    def "changes made by a rule are visible to subsequent rules"() {
+        repo.module('org.test', 'projectA', '1.0').publish().allowAll()
+
+        buildFile <<
+                """
+dependencies {
+    components {
+        eachComponent { details ->
+            details.status "integration.changed" // verify that 'details' is enhanced
+            details.statusScheme = ["integration.changed", "milestone.changed", "release.changed"]
+            details.changing = true
+        }
+        eachComponent { details ->
+            assert details.status == "integration.changed"
+            assert details.statusScheme == ["integration.changed", "milestone.changed", "release.changed"]
+            assert details.changing
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'resolve'
+    }
+
+    def "changes made by a rule are not cached"() {
+        repo.module('org.test', 'projectA', '1.0').publish().allowAll()
+
+        buildFile <<
+                """
+dependencies {
+    components {
+        eachComponent { details ->
+            assert !details.changing
+            assert details.status == "$defaultStatus"
+            assert details.statusScheme == ["integration", "milestone", "release"]
+
+            details.changing = true
+            details.status = "release.changed"
+            details.statusScheme = ["integration.changed", "milestone.changed", "release.changed"]
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'resolve'
+        succeeds 'resolve'
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesStatusIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesStatusIntegrationTest.groovy
new file mode 100644
index 0000000..faa5b49
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ComponentMetadataRulesStatusIntegrationTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.HttpRepository
+
+abstract class ComponentMetadataRulesStatusIntegrationTest extends AbstractDependencyResolutionTest {
+    abstract HttpRepository getRepo()
+
+    abstract String getRepoDeclaration()
+
+    def setup() {
+        server.start()
+
+        buildFile <<
+"""
+$repoDeclaration
+
+configurations { compile }
+
+dependencies {
+    compile 'org.test:projectA:1.0'
+}
+
+task resolve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
index f2c2a12..770a261 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
@@ -19,9 +19,6 @@ package org.gradle.integtests.resolve
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.hamcrest.Matchers
 
-/**
- * by Szczepan Faber, created at: 11/9/11
- */
 class DependencyNotationIntegrationSpec extends AbstractIntegrationSpec {
 
     def "understands dependency notations"() {
@@ -150,4 +147,40 @@ task checkDeps
         fails 'checkDeps'
         failure.assertThatCause(Matchers.startsWith("Cannot convert the provided notation to an object of type Dependency: 100."))
     }
+
+    def "fails gracefully for single null notation"() {
+        when:
+        buildFile <<  """
+configurations {
+    conf
+}
+
+dependencies {
+    conf null
+}
+
+task checkDeps
+"""
+        then:
+        fails 'checkDeps'
+        failure.assertThatCause(Matchers.startsWith("Cannot convert a null value to an object of type Dependency"))
+    }
+
+    def "fails gracefully for null notation in list"() {
+        when:
+        buildFile <<  """
+configurations {
+    conf
+}
+
+dependencies {
+    conf "a:b:c", null, "d:e:f"
+}
+
+task checkDeps
+"""
+        then:
+        fails 'checkDeps'
+        failure.assertThatCause(Matchers.startsWith("Cannot convert a null value to an object of type Dependency"))
+    }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
index df3c85f..db1cb3c 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
 class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 
     void "forces multiple modules by rule"()
@@ -91,7 +88,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 
 	        task check << {
 	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
-	            assert deps*.selected.id.name == ['foo', 'impl', 'api']
+	            assert deps*.selected.id.module == ['foo', 'impl', 'api']
 	            assert deps*.selected.id.version == ['2.0', '1.5', '1.5']
 	            assert deps*.selected.selectionReason.forced         == [false, false, false]
 	            assert deps*.selected.selectionReason.selectedByRule == [false, true, true]
@@ -269,14 +266,14 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 	        task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies
                 assert deps.find {
-                    it.selected.id.name == 'impl' &&
+                    it.selected.id.module == 'impl' &&
                     it.selected.id.version == '1.5' &&
                     it.selected.selectionReason.forced &&
                     !it.selected.selectionReason.selectedByRule
                 }
 
                 assert deps.find {
-	                it.selected.id.name == 'api' &&
+	                it.selected.id.module == 'api' &&
                     it.selected.id.version == '1.5' &&
                     !it.selected.selectionReason.forced &&
                     it.selected.selectionReason.selectedByRule
@@ -347,8 +344,8 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 	        }
 
 	        task check << {
-                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
-                def a = modules.find { it.id.name == 'a' }
+                def modules = configurations.conf.incoming.resolutionResult.allComponents.findAll { it.id instanceof ModuleComponentIdentifier } as List
+                def a = modules.find { it.id.module == 'a' }
                 assert a.id.version == '1.4'
                 assert a.selectionReason.conflictResolution
                 assert a.selectionReason.selectedByRule
@@ -385,8 +382,8 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 	        }
 
 	        task check << {
-                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
-                def a = modules.find { it.id.name == 'a' }
+                def modules = configurations.conf.incoming.resolutionResult.allComponents.findAll { it.id instanceof ModuleComponentIdentifier } as List
+                def a = modules.find { it.id.module == 'a' }
                 assert a.id.version == '1.3'
                 assert a.selectionReason.conflictResolution
                 assert !a.selectionReason.selectedByRule
@@ -455,7 +452,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 	        task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 2
-                def api = deps.find { it.requested.name == 'api' }
+                def api = deps.find { it.requested.module == 'api' }
                 api.requested.version == 'default'
                 api.selected.id.version == '1.3'
                 api.selected.selectionReason.selectedByRule
@@ -488,7 +485,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 1
                 assert deps[0].attempted.group == 'org.utils'
-                assert deps[0].attempted.name == 'api'
+                assert deps[0].attempted.module == 'api'
                 assert deps[0].attempted.version == '1.123.15'
                 assert deps[0].attemptedReason.selectedByRule
                 assert deps[0].failure.message.contains('1.123.15')
@@ -608,9 +605,9 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 	        }
 
 	        task check << {
-                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
-                assert !modules.find { it.id.name == 'a' }
-                def b = modules.find { it.id.name == 'b' }
+                def modules = configurations.conf.incoming.resolutionResult.allComponents.findAll { it.id instanceof ModuleComponentIdentifier } as List
+                assert !modules.find { it.id.module == 'a' }
+                def b = modules.find { it.id.module == 'b' }
                 assert b.id.version == '2.1'
                 assert b.selectionReason.conflictResolution
                 assert b.selectionReason.selectedByRule
@@ -717,6 +714,39 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
         failure.assertResolutionFailure(":conf").assertHasCause("Invalid format: 'foobar'")
     }
 
+    def "substituted module version participates in conflict resolution"()
+    {
+        mavenRepo.module("org", "a", "2.0").dependsOn("org", "b", "2.0").publish()
+        mavenRepo.module("org", "b", "2.0").dependsOn("org", "c", "2.0").publish()
+        mavenRepo.module("org", "c", "2.0").publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org:a:1.0', 'org:a:2.0'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                if (it.requested.name == 'a' && it.requested.version == '1.0') {
+                    it.useTarget group: 'org', name: 'c', version: '1.1'
+                }
+	        }
+"""
+
+        when:
+        run("dependencies")
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+conf
++--- org:a:1.0 -> org:c:2.0
+\\--- org:a:2.0
+     \\--- org:b:2.0
+          \\--- org:c:2.0
+"""))
+    }
+
     def "module selected by conflict resolution can be selected again in a another pass of conflict resolution"()
     {
         mavenRepo.module("org", "a", "1.0").publish()
@@ -744,8 +774,8 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
             }
 
             task check << {
-                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
-                assert modules.find { it.id.name == 'b' && it.id.version == '4.0' && it.selectionReason.conflictResolution }
+                def modules = configurations.conf.incoming.resolutionResult.allComponents.findAll { it.id instanceof ModuleComponentIdentifier } as List
+                assert modules.find { it.id.module == 'b' && it.id.version == '4.0' && it.selectionReason.conflictResolution }
             }
 """
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.groovy
new file mode 100644
index 0000000..f3570f8
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.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.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class DetachedConfigurationsIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2889")
+    def "detached configurations may have separate dependencies"() {
+        settingsFile << "include 'a', 'b'"
+        mavenRepo.module("org", "foo").publish()
+        mavenRepo.module("org", "bar").publish()
+
+        buildFile << """
+            allprojects {
+                configurations {
+                    foo
+                }
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                task checkDependencies() << {
+                    configurations.each { conf ->
+                        def declared = conf.dependencies
+                        def detached = project.configurations.detachedConfiguration(declared as Dependency[])
+                        def resolved = detached.resolvedConfiguration.getFirstLevelModuleDependencies()
+                        assert declared*.name == resolved*.moduleName
+                    }
+                }
+            }
+            project(":a") {
+                dependencies {
+                    foo "org:foo:1.0"
+                }
+            }
+            project(":b") {
+                dependencies {
+                    foo "org:bar:1.0"
+                }
+            }
+        """
+
+        expect:
+        run "checkDependencies"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy
new file mode 100644
index 0000000..9dd235e
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve;
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Issue;
+
+public class ExtendingConfigurationsIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Issue("GRADLE-2873")
+    def "may replace configuration extension targets"() {
+        mavenRepo.module("org", "foo").publish()
+        mavenRepo.module("org", "bar").publish()
+
+        buildFile << """
+            configurations {
+                fooConf
+                barConf
+                conf
+            }
+
+            dependencies {
+                fooConf 'org:foo:1.0'
+                barConf 'org:bar:1.0'
+            }
+
+            task check << {
+                configurations.conf.extendsFrom(configurations.fooConf)
+                assert configurations.conf.allDependencies*.name == ['foo']
+
+                //purposefully again:
+                configurations.conf.extendsFrom(configurations.fooConf)
+                assert configurations.conf.allDependencies*.name == ['foo']
+
+                //replace:
+                configurations.conf.extendsFrom = [configurations.barConf] as Set
+                assert configurations.conf.allDependencies*.name == ['bar']
+            }
+        """
+
+        when:
+        run "check"
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirJvmLibraryArtifactResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirJvmLibraryArtifactResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..e4fe71a
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirJvmLibraryArtifactResolutionIntegrationTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class FlatDirJvmLibraryArtifactResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    JvmLibraryArtifactResolveTestFixture fixture
+
+    def setup() {
+        buildFile << """
+repositories {
+    flatDir { dir 'repo' }
+}
+configurations { compile }
+dependencies {
+    compile "some.group:some-artifact:1.0"
+}
+"""
+        fixture = new JvmLibraryArtifactResolveTestFixture(buildFile)
+    }
+
+    def "resolves and does not cache source and javadoc artifacts"() {
+        publishModule()
+        fixture.requestingTypes()
+                .expectSourceArtifact("sources")
+                .expectJavadocArtifact("javadoc")
+                .prepare()
+
+        when:
+        succeeds("verify")
+
+        and:
+        def snapshot = file("sources/some-artifact-1.0-sources.jar").snapshot()
+
+        and:
+        publishChanged()
+
+        then:
+        succeeds("verify")
+        file("sources/some-artifact-1.0-sources.jar").assertHasChangedSince(snapshot)
+    }
+
+    def "resolves artifacts of non-existing component"() {
+        fixture.expectComponentNotFound().prepare()
+
+        expect:
+        succeeds("verify")
+    }
+
+    def "resolve missing source and javadoc artifacts"() {
+        file("repo/some-artifact-1.0.jar").createFile()
+
+        fixture.requestingTypes().prepare()
+
+        expect:
+        succeeds("verify")
+    }
+
+    def "resolve partially missing artifacts"() {
+        file("repo/some-artifact-1.0.jar").createFile()
+        file("repo/some-artifact-1.0-sources.jar").createFile()
+
+        fixture.requestingTypes()
+                .expectSourceArtifact("sources")
+                .prepare()
+
+        expect:
+        succeeds("verify")
+    }
+
+    def "can only resolve component if main artifact exists"() {
+        file("repo/some-artifact-1.0-sources.jar").createFile()
+        file("repo/some-artifact-1.0-javadoc.jar").createFile()
+
+        fixture.expectComponentNotFound().prepare()
+
+        expect:
+        succeeds("verify")
+    }
+
+    private publishModule() {
+        file("repo/some-artifact-1.0.jar").createFile()
+        file("repo/some-artifact-1.0-sources.jar").createFile()
+        file("repo/some-artifact-1.0-javadoc.jar").createFile()
+    }
+
+    private publishChanged() {
+        file("repo/some-artifact-1.0.jar") << "more"
+        file("repo/some-artifact-1.0-sources.jar") << "more"
+        file("repo/some-artifact-1.0-javadoc.jar") << "more"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy
index 94bebef..d68511d 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy
@@ -17,10 +17,7 @@ package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
-class ForcedModulesIntegrationTest extends AbstractIntegrationSpec {
+public class ForcedModulesIntegrationTest extends AbstractIntegrationSpec {
 
     void "can force the version of a particular module"() {
         mavenRepo.module("org", "foo", '1.3.3').publish()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/JvmLibraryArtifactResolveTestFixture.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/JvmLibraryArtifactResolveTestFixture.groovy
new file mode 100644
index 0000000..cab646c
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/JvmLibraryArtifactResolveTestFixture.groovy
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve
+
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.api.artifacts.resolution.JvmLibraryArtifact
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionNotFoundException
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactNotFoundException
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier
+import org.gradle.test.fixtures.file.TestFile
+/**
+ * A test fixture that injects a task into a build that uses the Artifact Query API to download some artifacts, validating the results.
+ */
+class JvmLibraryArtifactResolveTestFixture {
+    private final TestFile buildFile
+    private final String config
+    private ModuleComponentIdentifier id = DefaultModuleComponentIdentifier.newId("some.group", "some-artifact", "1.0")
+    private artifactTypes = []
+    private expectedSources = []
+    Throwable expectedSourceFailure
+    private expectedJavadoc = []
+    Throwable expectedJavadocFailure
+    private Throwable unresolvedComponentFailure
+
+    JvmLibraryArtifactResolveTestFixture(TestFile buildFile, String config = "compile") {
+        this.buildFile = buildFile
+        this.config = config
+    }
+
+    JvmLibraryArtifactResolveTestFixture withComponentVersion(String group, String module, String version) {
+        this.id = DefaultModuleComponentIdentifier.newId(group, module, version)
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture requestingTypes(Class<? extends JvmLibraryArtifact>... artifactTypes) {
+        this.artifactTypes = artifactTypes as List
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture clearExpectations() {
+        this.unresolvedComponentFailure = null
+        this.expectedSources = []
+        this.expectedJavadoc = []
+        this.expectedSourceFailure = null
+        this.expectedJavadocFailure = null
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectComponentNotFound() {
+        this.unresolvedComponentFailure = new ModuleVersionNotFoundException(new DefaultModuleVersionSelector(id.group, id.module, id.version))
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectComponentResolutionFailure(Throwable failure) {
+        unresolvedComponentFailure = failure
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectSourceArtifact(String classifier) {
+        expectedSources << "${id.module}-${id.version}-${classifier}.jar"
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectSourceArtifactNotFound(String artifactClassifier) {
+        expectedSourceFailure = new ArtifactNotFoundException(new DefaultModuleVersionArtifactIdentifier(id, id.module, "jar", "jar", [classifier: artifactClassifier]))
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectSourceArtifactFailure(Throwable failure) {
+        expectedSourceFailure = failure
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectJavadocArtifact(String classifier) {
+        expectedJavadoc << "${id.module}-${id.version}-${classifier}.jar"
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectJavadocArtifactNotFound(String artifactClassifier) {
+        expectedJavadocFailure = new ArtifactNotFoundException(new DefaultModuleVersionArtifactIdentifier(id, id.module, "jar", "jar", [classifier: artifactClassifier]))
+        this
+    }
+
+    JvmLibraryArtifactResolveTestFixture expectJavadocArtifactFailure(Throwable failure) {
+        expectedJavadocFailure = failure
+        this
+    }
+
+    /**
+     * Injects the appropriate stuff into the build script.
+     */
+    void prepare() {
+        if (unresolvedComponentFailure != null) {
+            prepareComponentNotFound()
+        } else {
+            createVerifyTask("verify")
+        }
+    }
+
+    void createVerifyTask(def taskName) {
+        buildFile << """
+task $taskName << {
+    def deps = configurations.compile.incoming.resolutionResult.allDependencies as List
+    assert deps.size() == 1
+    def componentId = deps[0].selected.id
+
+    def result = dependencies.createArtifactResolutionQuery()
+        .forComponents(deps[0].selected.id)
+        .withArtifacts(JvmLibrary$artifactTypesString)
+        .execute()
+
+    assert result.components.size() == 1
+    def jvmLibrary = result.components.iterator().next()
+    assert jvmLibrary.id.group == "${id.group}"
+    assert jvmLibrary.id.module == "${id.module}"
+    assert jvmLibrary.id.version == "${id.version}"
+    assert jvmLibrary instanceof JvmLibrary
+
+    def sourceArtifactFiles = []
+    jvmLibrary.sourcesArtifacts.each { artifact ->
+        assert artifact instanceof JvmLibrarySourcesArtifact
+        if (artifact.failure != null) {
+            ${checkException("artifact.failure", expectedSourceFailure)}
+        } else {
+            copy {
+                from artifact.file
+                into "sources"
+            }
+            sourceArtifactFiles << artifact.file.name
+        }
+    }
+    assert sourceArtifactFiles as Set == ${toQuotedList(expectedSources)} as Set
+
+    def javadocArtifactFiles = []
+    jvmLibrary.javadocArtifacts.each { artifact ->
+        assert artifact instanceof JvmLibraryJavadocArtifact
+        if (artifact.failure != null) {
+            ${checkException("artifact.failure", expectedJavadocFailure)}
+        } else {
+            copy {
+                from artifact.file
+                into "javadoc"
+            }
+            javadocArtifactFiles << artifact.file.name
+        }
+    }
+    assert javadocArtifactFiles as Set == ${toQuotedList(expectedJavadoc)} as Set
+
+    assert result.unresolvedComponents.empty
+}
+"""
+    }
+
+    private static String checkException(String reference, Throwable expected) {
+        if (expected == null) {
+            return "throw $reference"
+        }
+        return """
+    assert ${reference} instanceof ${expected.class.name}
+    assert ${reference}.message == "${expected.message}"
+"""
+    }
+
+    private static String toQuotedList(def values) {
+        return values.collect({"\"$it\""}).toListString()
+    }
+
+    private void prepareComponentNotFound() {
+        buildFile << """
+task verify << {
+    def unknownComponentId = [getGroup: {'${id.group}'}, getModule: {'${id.module}'}, getVersion: {'${id.version}'}, getDisplayName: {'unknown'}] as ModuleComponentIdentifier
+    def result = dependencies.createArtifactResolutionQuery()
+        .forComponents(unknownComponentId)
+        .withArtifacts(JvmLibrary$artifactTypesString)
+        .execute()
+
+    assert result.components.empty
+    assert result.unresolvedComponents.size() == 1
+    for (component in result.unresolvedComponents) {
+        assert component.id.group == "${id.group}"
+        assert component.id.module == "${id.module}"
+        assert component.id.version == "${id.version}"
+        assert component.id.displayName == 'unknown'
+        ${checkException("component.failure", unresolvedComponentFailure)}
+    }
+}
+"""
+    }
+
+    private String getArtifactTypesString() {
+        def artifactTypesString = ""
+        for (Class<? extends JvmLibraryArtifact> type : artifactTypes) {
+            artifactTypesString += ", ${type.simpleName}"
+        }
+        return artifactTypesString
+    }
+}
+
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
index 23e3602..7c231ce 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
@@ -21,9 +21,6 @@ package org.gradle.integtests.resolve
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import spock.lang.Issue
 
-/**
- * by Szczepan Faber, created at: 11/21/12
- */
 class ProjectDependenciesIntegrationTest extends AbstractDependencyResolutionTest {
 
     @Issue("GRADLE-2477") //this is a feature on its own but also covers one of the reported issues
@@ -59,7 +56,7 @@ class ProjectDependenciesIntegrationTest extends AbstractDependencyResolutionTes
     def "configuring project dependencies by map is validated"() {
         settingsFile << "include 'impl'"
         buildFile << """
-            allprojects { configurations.add('conf') }
+            allprojects { configurations.create('conf') }
             task extraKey << {
                 def dep = dependencies.project(path: ":impl", configuration: ":conf", foo: "bar")
                 assert dep.foo == "bar"
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
index a7f7f2c..b12c551 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
 
 class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec {
     public void "project dependency includes artifacts and transitive dependencies of default configuration in target project"() {
@@ -44,14 +45,60 @@ project(":a") {
     artifacts { api jar }
 }
 project(":b") {
+    group = 'org.gradle'
+    version = '1.0'
+
     configurations {
         compile
     }
     dependencies {
         compile project(':a')
     }
+
     task check(dependsOn: configurations.compile) << {
         assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar', 'externalB-2.1.jar']
+        def result = configurations.compile.incoming.resolutionResult
+
+         // Check root component
+        def rootId = result.root.id
+        assert rootId instanceof ProjectComponentIdentifier
+        def rootPublishedAs = result.root.moduleVersion
+        assert rootPublishedAs.group == 'org.gradle'
+        assert rootPublishedAs.name == 'b'
+        assert rootPublishedAs.version == '1.0'
+
+        // Check project components
+        def projectComponents = result.root.dependencies.selected.findAll { it.id instanceof ProjectComponentIdentifier }
+        assert projectComponents.size() == 1
+        def projectA = projectComponents[0]
+        assert projectA.id.projectPath == ':a'
+        assert projectA.moduleVersion.group != null
+        assert projectA.moduleVersion.name == 'a'
+        assert projectA.moduleVersion.version == 'unspecified'
+
+        // Check project dependencies
+        def projectDependencies = result.root.dependencies.requested.findAll { it instanceof ProjectComponentSelector }
+        assert projectDependencies.size() == 1
+        def projectDependency = projectDependencies[0]
+        assert projectDependency.projectPath == ':a'
+
+        // Check external module components
+        def externalComponents = result.allDependencies.selected.findAll { it.id instanceof ModuleComponentIdentifier }
+        assert externalComponents.size() == 2
+        def externalA = externalComponents[0]
+        assert externalA.id.group == 'org.other'
+        assert externalA.id.module == 'externalA'
+        assert externalA.id.version == '1.2'
+        assert externalA.moduleVersion.group == 'org.other'
+        assert externalA.moduleVersion.name == 'externalA'
+        assert externalA.moduleVersion.version == '1.2'
+        def externalB = externalComponents[1]
+        assert externalB.id.group == 'org.other'
+        assert externalB.id.module == 'externalB'
+        assert externalB.id.version == '2.1'
+        assert externalB.moduleVersion.group == 'org.other'
+        assert externalB.moduleVersion.name == 'externalB'
+        assert externalB.moduleVersion.version == '2.1'
     }
 }
 """
@@ -100,6 +147,50 @@ project(":b") {
         succeeds "check"
     }
 
+    @Issue("GRADLE-2899")
+    public void "consuming project can refer to multiple configurations of target project"() {
+        given:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+project(':a') {
+    configurations {
+        configA1
+        configA2
+    }
+    task A1jar(type: Jar) {
+        archiveName = 'A1.jar'
+    }
+    task A2jar(type: Jar) {
+        archiveName = 'A2.jar'
+    }
+    artifacts {
+        configA1 A1jar
+        configA2 A2jar
+    }
+}
+
+project(':b') {
+    configurations {
+        configB1
+        configB2
+    }
+    dependencies {
+        configB1 project(path:':a', configuration:'configA1')
+        configB2 project(path:':a', configuration:'configA2')
+    }
+    task check << {
+        assert configurations.configB1.collect { it.name } == ['A1.jar']
+        assert configurations.configB2.collect { it.name } == ['A2.jar']
+    }
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
     public void "resolved project artifacts contain project version in their names"() {
         given:
         file('settings.gradle') << "include 'a', 'b'"
@@ -148,17 +239,18 @@ allprojects {
 project(":a") {
     configurations { 'default' {} }
     dependencies { 'default' 'group:externalA:1.5' }
-    task aJar(type: Jar) { }
-    artifacts { 'default' aJar }
+    task aJar(type: Jar) { baseName='a' }
+    task bJar(type: Jar) { baseName='b' }
+    artifacts { 'default' aJar, bJar }
 }
 
 project(":b") {
     configurations { compile }
-    dependencies { compile(project(':a')) { artifact { name = 'a'; type = 'jar' } } }
+    dependencies { compile(project(':a')) { artifact { name = 'b'; type = 'jar' } } }
     task test {
         inputs.files configurations.compile
         doFirst {
-            assert configurations.compile.files.collect { it.name } == ['a.jar', 'externalA-1.5.jar']
+            assert configurations.compile.files.collect { it.name } == ['b.jar', 'externalA-1.5.jar']
         }
     }
 }
@@ -168,6 +260,38 @@ project(":b") {
         succeeds 'test'
     }
 
+    public void "reports project dependency that refers to an unknown artifact"() {
+        given:
+        file('settings.gradle') << """
+include 'a', 'b'
+"""
+
+        and:
+        buildFile << """
+allprojects { group = 'test' }
+project(":a") {
+    configurations { 'default' {} }
+}
+
+project(":b") {
+    configurations { compile }
+    dependencies { compile(project(':a')) { artifact { name = 'b'; type = 'jar' } } }
+    task test {
+        inputs.files configurations.compile
+        doFirst {
+            assert configurations.compile.files.collect { it.name } == ['a-b.jar', 'externalA-1.5.jar']
+        }
+    }
+}
+"""
+
+        expect:
+        fails 'test'
+
+        and:
+        failure.assertResolutionFailure(":b:compile").assertHasCause("Artifact 'test:a:unspecified:b.jar' not found.")
+    }
+
     public void "non-transitive project dependency includes only the artifacts of the target configuration"() {
         given:
         mavenRepo.module("group", "externalA", 1.5).publish()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy
new file mode 100644
index 0000000..18ae13f
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ResolutionResultApiIntegrationTest extends AbstractDependencyResolutionTest {
+
+    /*
+    The ResolutionResult API is also covered by the dependency report integration tests.
+     */
+
+    def "selection reasons are described"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+        mavenRepo.module("org", "foo", 0.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()
+        mavenRepo.module("org", "baz", 1.0).dependsOn('org', 'foo',  '1.0').publish()
+
+        file("settings.gradle") << "rootProject.name = 'cool-project'"
+
+        file("build.gradle") << """
+            version = '5.0'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            configurations.conf.resolutionStrategy.force 'org:leaf:2.0'
+            dependencies {
+                conf 'org:foo:0.5', 'org:bar:1.0', 'org:baz:1.0'
+            }
+            task resolutionResult << {
+                def result = configurations.conf.incoming.resolutionResult
+                result.allComponents {
+                    if(it.id instanceof ModuleComponentIdentifier) {
+                        println it.id.module + ":" + it.id.version + " " + it.selectionReason.description
+                    }
+                    else if(it.id instanceof ProjectComponentIdentifier) {
+                        println it.moduleVersion.name + ":" + it.moduleVersion.version + " " + it.selectionReason.description
+                    }
+                }
+            }
+        """
+
+        when:
+        run "resolutionResult"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+cool-project:5.0 root
+foo:1.0 conflict resolution
+leaf:2.0 forced
+bar:1.0 requested
+baz:1.0 requested
+"""))
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
index aae6410..50903c5 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
@@ -20,10 +20,7 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
-class ResolutionStrategySamplesIntegrationTest extends AbstractIntegrationSpec {
+public class ResolutionStrategySamplesIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule public final Sample sample = new Sample(temporaryFolder, 'userguide/artifacts/resolutionStrategy')
 
@@ -34,7 +31,7 @@ class ResolutionStrategySamplesIntegrationTest extends AbstractIntegrationSpec {
         mavenRepo.module("org.gradle", "gradle-core", "1.4").publish()
         mavenRepo.module("org.software", "some-library", "1.2.1").publish()
         mavenRepo.module("org.codehaus", "groovy", "1.7").publish()
-        mavenRepo.module("org.slf4j", "log4j-over-slf4j", "1.7.2").publish()
+        mavenRepo.module("org.slf4j", "log4j-over-slf4j", "1.7.5").publish()
 
         sample.dir.file("build.gradle") << """
             configurations { conf }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
index 6883df0..81d178b 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
@@ -16,14 +16,26 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
 
 class ResolveCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    @Rule HttpServer server = new HttpServer()
 
     def "can upgrade and downgrade Gradle version"() {
         given:
+        mavenRepo.module("test", "io", "1.4").publish()
+        mavenRepo.module("test", "lang", "2.4").publish()
+        mavenRepo.module("test", "lang", "2.6").publish()
+
+        and:
+        server.start()
+        server.allowGetOrHead("/repo", mavenRepo.rootDir)
+
+        and:
         buildFile << """
 repositories {
-    mavenCentral()
+    mavenRepo urls: "http://localhost:${server.port}/repo"
 }
 
 configurations {
@@ -31,12 +43,12 @@ configurations {
 }
 
 dependencies {
-    compile 'commons-io:commons-io:1.4'
-    compile 'commons-lang:commons-lang:2.+'
+    compile 'test:io:1.4'
+    compile 'test:lang:2.+'
 }
 
 task check << {
-    assert configurations.compile*.name as Set == ['commons-io-1.4.jar', 'commons-lang-2.6.jar'] as Set
+    assert configurations.compile*.name as Set == ['io-1.4.jar', 'lang-2.6.jar'] as Set
 }
 """
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveTestFixture.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveTestFixture.groovy
new file mode 100644
index 0000000..ff4cc62
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveTestFixture.groovy
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.result.ComponentSelectionReason
+import org.gradle.api.artifacts.result.ResolvedComponentResult
+import org.gradle.api.tasks.TaskAction
+import org.gradle.internal.classloader.ClasspathUtil
+import org.gradle.test.fixtures.file.TestFile
+
+/**
+ * A test fixture that injects a task into a build that resolves a dependency configuration and does some validation of the resulting graph, to
+ * ensure that the old and new dependency graphs plus the artifacts and files are as expected and well-formed.
+ */
+class ResolveTestFixture {
+    private final TestFile buildFile
+    private final String config
+
+    ResolveTestFixture(TestFile buildFile, String config = "compile") {
+        this.config = config
+        this.buildFile = buildFile
+    }
+
+    /**
+     * Injects the appropriate stuff into the build script.
+     */
+    void prepare() {
+        buildFile << """
+buildscript {
+    dependencies.classpath files("${ClasspathUtil.getClasspathForClass(GenerateGraphTask).toURI()}")
+}
+
+allprojects {
+    tasks.addPlaceholderAction("checkDeps") {
+        tasks.create(name: "checkDeps", dependsOn: configurations.${config}, type: ${GenerateGraphTask.name}) {
+            outputFile = rootProject.file("\${rootProject.buildDir}/${config}.txt")
+            configuration = configurations.$config
+        }
+    }
+}
+"""
+    }
+
+    /**
+     * Verifies the result of executing the task injected by {@link #prepare()}. The closure delegates to a {@link GraphBuilder} instance.
+     */
+    void expectGraph(Closure closure) {
+        def graph = new GraphBuilder()
+        closure.resolveStrategy = Closure.DELEGATE_ONLY
+        closure.delegate = graph
+        closure.call()
+
+        if (graph.root == null) {
+            throw new IllegalArgumentException("No root node defined")
+        }
+
+        def configDetailsFile = buildFile.parentFile.file("build/${config}.txt")
+        def configDetails = configDetailsFile.text.readLines()
+
+        def actualArtifacts = configDetails.findAll { it.startsWith('artifact:') }.collect { it.substring(9) }
+        def expectedArtifacts = graph.artifactNodes.collect { "[${it.moduleVersionId}][${it.module}.jar]" }
+        assert actualArtifacts == expectedArtifacts
+
+        def actualFiles = configDetails.findAll { it.startsWith('file:') }.collect { it.substring(5) }
+        def expectedFiles = graph.artifactNodes.collect { it.fileName }
+        assert actualFiles == expectedFiles
+
+        def actualFirstLevel = configDetails.findAll { it.startsWith('first-level:') }.collect { it.substring(12) }
+        def expectedFirstLevel = graph.root.deps.collect { "[${it.selected.moduleVersionId}:default]" }
+        assert actualFirstLevel == expectedFirstLevel
+
+        def actualRoot = configDetails.find { it.startsWith('root:') }.substring(5)
+        def expectedRoot = "[id:${graph.root.id}][mv:${graph.root.moduleVersionId}][reason:${graph.root.reason}]"
+        assert actualRoot == expectedRoot
+
+        def actualNodes = configDetails.findAll { it.startsWith('component:') }.collect { it.substring(10) }
+        def expectedNodes = graph.nodes.values().collect { "[id:${it.id}][mv:${it.moduleVersionId}][reason:${it.reason}]" }
+        assert actualNodes == expectedNodes
+
+        def actualEdges = configDetails.findAll { it.startsWith('dependency:') }.collect { it.substring(11) }
+        def expectedEdges = graph.edges.collect { "[from:${it.from.id}][${it.requested}->${it.selected.id}]" }
+        assert actualEdges == expectedEdges
+    }
+
+    public static class GraphBuilder {
+        final Map<String, NodeBuilder> nodes = new LinkedHashMap<>()
+        NodeBuilder root
+
+        private getArtifactNodes() {
+            Set<NodeBuilder> result = new LinkedHashSet()
+            visitNodes(root, result)
+            return result
+        }
+
+        private void visitNodes(NodeBuilder node, Set<NodeBuilder> result) {
+            Set<NodeBuilder> nodesToVisit = []
+            for (EdgeBuilder edge: node.deps) {
+                if (result.add(edge.selected)) {
+                    nodesToVisit << edge.selected
+                }
+            }
+            for(NodeBuilder child: nodesToVisit) {
+                visitNodes(child, result)
+            }
+        }
+
+        private getEdges() {
+            Set<EdgeBuilder> result = new LinkedHashSet<>()
+            Set<NodeBuilder> seen = []
+            visitEdges(root, seen, result)
+            return result
+        }
+
+        private visitEdges(NodeBuilder node, Set<NodeBuilder> seenNodes, Set<EdgeBuilder> edges) {
+            for (EdgeBuilder edge: node.deps) {
+                edges.add(edge)
+                if (seenNodes.add(edge.selected)) {
+                    visitEdges(edge.selected, seenNodes, edges)
+                }
+            }
+        }
+
+        /**
+         * Defines the root node of the graph. The closure delegates to a {@link NodeBuilder} instance that represents the root node.
+         */
+        def root(String value, Closure cl) {
+            if (root != null) {
+                throw new IllegalStateException("Root node is already defined")
+            }
+            root = node(value, value)
+            cl.resolveStrategy = Closure.DELEGATE_ONLY
+            cl.delegate = root
+            cl.call()
+            return root
+        }
+
+        def root(String path, String value, Closure cl) {
+            if (root != null) {
+                throw new IllegalStateException("Root node is already defined")
+            }
+            root = node("project $path", value)
+            cl.resolveStrategy = Closure.DELEGATE_ONLY
+            cl.delegate = root
+            cl.call()
+            return root
+        }
+
+        def node(String id, String moduleVersion) {
+            def node = nodes[moduleVersion]
+            if (!node) {
+                node = new NodeBuilder(id, moduleVersion, this)
+                nodes[moduleVersion] = node
+            }
+            return node
+        }
+    }
+
+    public static class EdgeBuilder {
+        final String requested
+        final NodeBuilder from
+        final NodeBuilder selected
+
+        EdgeBuilder(NodeBuilder from, String requested, NodeBuilder selected) {
+            this.from = from
+            this.selected = selected
+            this.requested = requested
+        }
+    }
+
+    public static class NodeBuilder {
+        final List<EdgeBuilder> deps = []
+        private final GraphBuilder graph
+        final String id
+        final String moduleVersionId
+        final String group
+        final String module
+        final String version
+        private String reason
+
+        NodeBuilder(String id, String moduleVersionId, GraphBuilder graph) {
+            this.graph = graph
+            if (moduleVersionId.matches(':\\w+:')) {
+                def parts = moduleVersionId.split(':')
+                this.group = null
+                this.module = parts[1]
+                this.version = null
+                this.moduleVersionId = ":${module}:unspecified"
+            } else if (moduleVersionId.matches('\\w+:\\w+:')) {
+                def parts = moduleVersionId.split(':')
+                this.group = parts[0]
+                this.module = parts[1]
+                this.version = null
+                this.moduleVersionId = "${group}:${module}:unspecified"
+            } else {
+                def parts = moduleVersionId.split(':')
+                assert parts.length == 3
+                this.group = parts[0]
+                this.module = parts[1]
+                this.version = parts[2]
+                this.moduleVersionId = moduleVersionId
+            }
+            if (id == moduleVersionId) {
+                this.id = this.moduleVersionId
+            } else {
+                this.id = id
+            }
+        }
+
+        private def getReason() {
+            reason ?: (this == graph.root ? 'root:' : 'requested:')
+        }
+
+        private def getFileName() {
+            "$module${version ? '-' + version : ''}.jar"
+        }
+
+        private def addNode(String id, String moduleVersionId = id) {
+            def node = graph.node(id, moduleVersionId)
+            deps << new EdgeBuilder(this, node.id, node)
+            return node
+        }
+
+        /**
+         * Defines a dependency on the given external module.
+         */
+        def module(String moduleVersionId) {
+            return addNode(moduleVersionId)
+        }
+
+        /**
+         * Defines a dependency on the given external module. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
+         */
+        def module(String moduleVersionId, Closure cl) {
+            def node = module(moduleVersionId)
+            cl.resolveStrategy = Closure.DELEGATE_ONLY
+            cl.delegate = node
+            cl.call()
+            return node
+        }
+
+        /**
+         * Defines a dependency on the given project. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
+         */
+        def project(String path, String value, Closure cl) {
+            def node = addNode("project $path", value)
+            cl.resolveStrategy = Closure.DELEGATE_ONLY
+            cl.delegate = node
+            cl.call()
+            return node
+        }
+
+        /**
+         * Defines a dependency on the given node.
+         */
+        def edge(String requested, String selectedModuleVersionId) {
+            def node = graph.node(selectedModuleVersionId, selectedModuleVersionId)
+            deps << new EdgeBuilder(this, requested, node)
+            return node
+        }
+
+        /**
+         * Marks that this node was selected due to conflict resolution.
+         */
+        def byConflictResolution() {
+            reason = 'conflict resolution:conflict'
+            this
+        }
+    }
+}
+
+public class GenerateGraphTask extends DefaultTask {
+    File outputFile
+    Configuration configuration
+
+    @TaskAction
+    def generateOutput() {
+        outputFile.parentFile.mkdirs()
+
+        outputFile.withPrintWriter { writer ->
+            configuration.resolvedConfiguration.firstLevelModuleDependencies.each {
+                writer.println("first-level:[${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}:${it.configuration}]")
+            }
+            configuration.resolvedConfiguration.resolvedArtifacts.each {
+                writer.println("artifact:[${it.moduleVersion.id}][${it.name}${it.classifier ? "-" + it.classifier : ""}.${it.extension}]")
+            }
+            def root = configuration.incoming.resolutionResult.root
+            writer.println("root:${formatComponent(root)}")
+            configuration.incoming.resolutionResult.allComponents.each {
+                writer.println("component:${formatComponent(it)}")
+            }
+            configuration.incoming.resolutionResult.allDependencies.each {
+                writer.println("dependency:[from:${it.from.id}][${it.requested}->${it.selected.id}]")
+            }
+            configuration.files.each {
+                writer.println("file:${it.name}")
+            }
+        }
+    }
+
+    def formatComponent(ResolvedComponentResult result) {
+        return "[id:${result.id}][mv:${result.moduleVersion}][reason:${formatReason(result.selectionReason)}]"
+    }
+
+    def formatReason(ComponentSelectionReason reason) {
+        def reasons = []
+        if (reason.conflictResolution) {
+            reasons << "conflict"
+        }
+        if (reason.forced) {
+            reasons << "forced"
+        }
+        if (reason.selectedByRule) {
+            reasons << "selectedByRule"
+        }
+        return "${reason.description}:${reasons.join(',')}"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
index 8c1b112..4fbb47a 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
@@ -20,13 +20,13 @@ 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.util.HelperUtil
+import org.gradle.util.TestUtil
 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 DefaultProject project = TestUtil.createRootProject()
+    def Project childProject = TestUtil.createChildProject(project, "child", new File("."))
 
     @Before
     public void boringSetup() {
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
index 0d4da25..29e0938 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
@@ -21,10 +21,7 @@ import spock.lang.Issue
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 import static org.hamcrest.Matchers.containsString
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
-class VersionConflictResolutionIntegrationTest extends AbstractIntegrationSpec {
+public class VersionConflictResolutionIntegrationTest extends AbstractIntegrationSpec {
 
     void "strict conflict resolution should fail due to conflict"() {
         mavenRepo.module("org", "foo", '1.3.3').publish()
@@ -110,7 +107,10 @@ project(':tool') {
         mavenRepo.module("org", "foo", '1.3.3').publish()
         mavenRepo.module("org", "foo", '1.4.4').publish()
 
-        settingsFile << "include 'api', 'impl', 'tool'"
+        settingsFile << """
+rootProject.name = 'test'
+include 'api', 'impl', 'tool'
+"""
 
         buildFile << """
 allprojects {
@@ -137,20 +137,34 @@ project(':tool') {
 		compile project(':api')
 		compile project(':impl')
 	}
-    task checkDeps(dependsOn: configurations.compile) << {
-        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.4.4.jar']
-    }
 }
 """
 
-        expect:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
+        when:
         run("tool:checkDeps")
+
+        then:
+        resolve.expectGraph {
+            root(":tool", "test:tool:") {
+                project(":api", "test:api:") {
+                    edge("org:foo:1.3.3", "org:foo:1.4.4")
+                }
+                project(":impl", "test:impl:") {
+                    module("org:foo:1.4.4").byConflictResolution()
+                }
+            }
+        }
     }
 
     void "does not attempt to resolve an evicted dependency"() {
         mavenRepo.module("org", "external", "1.2").publish()
         mavenRepo.module("org", "dep", "2.2").dependsOn("org", "external", "1.0").publish()
 
+        settingsFile << "rootProject.name = 'test'"
+
         buildFile << """
 repositories {
     maven { url "${mavenRepo.uri}" }
@@ -162,9 +176,49 @@ dependencies {
     compile 'org:external:1.2'
     compile 'org:dep:2.2'
 }
+"""
 
-task checkDeps << {
-    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
+        when:
+        run("checkDeps")
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:") {
+                module("org:external:1.2").byConflictResolution()
+                module("org:dep:2.2") {
+                    edge("org:external:1.0", "org:external:1.2")
+                }
+            }
+        }
+    }
+
+    @Issue("GRADLE-2890")
+    void "selects latest from multiple conflicts"() {
+        mavenRepo.module("org", "child", '1').publish()
+        mavenRepo.module("org", "child", '2').publish()
+        mavenRepo.module("org", "parent", '1').dependsOn("org", "child", "1").publish()
+        mavenRepo.module("org", "parent", '2').dependsOn("org", "child", "2").publish()
+        mavenRepo.module("org", "dep", '2').dependsOn("org", "parent", "2").publish()
+
+        buildFile << """
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+configurations {
+    compile
+}
+dependencies {
+    compile 'org:parent:1'
+    compile 'org:child:2'
+    compile 'org:dep:2'
+}
+task checkDeps(dependsOn: configurations.compile) << {
+    assert configurations.compile*.name == ['child-2.jar', 'dep-2.jar', 'parent-2.jar']
+    configurations.compile.resolvedConfiguration.firstLevelModuleDependencies*.name
+    configurations.compile.incoming.resolutionResult.allComponents*.id
 }
 """
 
@@ -392,12 +446,12 @@ task checkDeps << {
             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
+                assert result.allComponents.size() == 3
                 def root = result.root
                 assert root.dependencies*.toString() == ['org:a:1.0 -> org:a:2.0', 'org:a:2.0']
-                def a = result.allModuleVersions.find { it.id.name == 'a' }
+                def a = result.allComponents.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'a' }
                 assert a.dependencies*.toString() == ['org:b:2.0']
-                def b = result.allModuleVersions.find { it.id.name == 'b' }
+                def b = result.allComponents.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'b' }
                 assert b.dependencies*.toString() == ['org:a:1.0 -> org:a:2.0']
             }
         """
@@ -445,14 +499,14 @@ task checkDeps << {
             }
 
         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']
+            assert configurations.conf*.name == ['a-1.0.jar', 'b-1.0.jar', 'b-child-1.0.jar', 'target-1.0.jar', 'in-conflict-2.0.jar', 'target-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 result.allComponents.size() == 7
+            def a = result.allComponents.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'a' }
             assert a.dependencies*.toString() == ['org:in-conflict:1.0 -> org:in-conflict:2.0']
-            def bChild = result.allModuleVersions.find { it.id.name == 'b-child' }
+            def bChild = result.allComponents.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'b-child' }
             assert bChild.dependencies*.toString() == ['org:in-conflict:2.0']
-            def target = result.allModuleVersions.find { it.id.name == 'target' }
+            def target = result.allComponents.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'target' }
             assert target.dependents*.from*.toString() == ['org:in-conflict:2.0']
         }
         """
@@ -506,7 +560,6 @@ task checkDeps << {
         run("dependencies")
 
         then:
-        //TODO SF proper assertions
         output.contains(toPlatformLineSeparators("""
 childFirst
 +--- org:d:1.0
@@ -530,4 +583,151 @@ parentFirst
 \\--- org:f:1.0
      \\--- org:x:2.0 FAILED"""))
     }
+
+    @Issue("GRADLE-2752")
+    void "selects root module when earlier version of module requested"() {
+        mavenRepo.module("org", "test", "1.2").publish()
+        mavenRepo.module("org", "other", "1.7").dependsOn("org", "test", "1.2").publish()
+
+        settingsFile << "rootProject.name= 'test'"
+
+        buildFile << """
+apply plugin: 'java'
+
+group "org"
+version "1.3"
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile "org:other:1.7"
+}
+"""
+
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
+        when:
+        run("checkDeps")
+
+        then:
+        resolve.expectGraph {
+            root(":", "org:test:1.3") {
+                module("org:other:1.7") {
+                    edge("org:test:1.2", "org:test:1.3")
+                }
+            }
+        }
+    }
+
+    @Issue("GRADLE-2920")
+    void "selects later version of root module when requested"() {
+        mavenRepo.module("org", "test", "2.1").publish()
+        mavenRepo.module("org", "other", "1.7").dependsOn("org", "test", "2.1").publish()
+
+        settingsFile << "rootProject.name = 'test'"
+
+        buildFile << """
+apply plugin: 'java'
+
+group "org"
+version "1.3"
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile "org:other:1.7"
+}
+"""
+
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
+        when:
+        run("checkDeps")
+
+        then:
+        resolve.expectGraph {
+            root(":", "org:test:1.3") {
+                module("org:other:1.7") {
+                    module("org:test:2.1").byConflictResolution()
+                }
+            }
+        }
+    }
+
+    void "module is required only by selected conflicting version and in turn requires evicted conflicting version"() {
+        /*
+            a2 -> b1 -> c1
+            a1
+            c2
+         */
+        mavenRepo.module("org", "a", "1").publish()
+        mavenRepo.module("org", "a", "2").dependsOn("org", "b", "1").publish()
+        mavenRepo.module("org", "b", "1").dependsOn("org", "c", "1").publish()
+        mavenRepo.module("org", "c", "1").publish()
+        mavenRepo.module("org", "c", "2").publish()
+
+        settingsFile << "rootProject.name= 'test'"
+
+        buildFile << """
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+configurations {
+    compile
+}
+dependencies {
+    compile "org:a:2"
+    compile "org:a:1"
+    compile "org:c:2"
+}
+
+task checkDeps(dependsOn: configurations.compile) << {
+    assert configurations.compile*.name == ['a-2.jar', 'c-2.jar', 'b-1.jar']
+    assert configurations.compile.incoming.resolutionResult.allComponents.find { it.id instanceof ModuleComponentIdentifier && it.id.module == 'b' }.dependencies.size() == 1
+}
+"""
+
+        expect:
+        run("checkDeps")
+    }
+
+    @Issue("GRADLE-2738")
+    def "resolution fails when any selector cannot be resolved"() {
+        given:
+        //only 1.5 published:
+        mavenRepo.module("org", "leaf", "1.5").publish()
+
+        mavenRepo.module("org", "c", "1.0").dependsOn("org", "leaf", "2.0+").publish()
+        mavenRepo.module("org", "a", "1.0").dependsOn("org", "leaf", "1.0").publish()
+        mavenRepo.module("org", "b", "1.0").dependsOn("org", "leaf", "[1.5,1.9]").publish()
+
+        settingsFile << "rootProject.name = 'broken'"
+        buildFile << """
+            version = 12
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:a:1.0', 'org:b:1.0', 'org:c:1.0'
+            }
+            task resolve << {
+                configurations.conf.files
+            }
+        """
+
+        when:
+        runAndFail "resolve"
+
+        then:
+        failure.assertResolutionFailure(":conf").assertFailedDependencyRequiredBy(":broken:12 > org:c:1.0")
+    }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy
index 285c1f5..0e2295d 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy
@@ -22,9 +22,6 @@ import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
 import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
 
-/**
- * by Szczepan Faber, created at: 11/27/12
- */
 abstract class AbstractCacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
 
     /**
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
index 8adfc88..dea3a73 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
@@ -64,11 +64,10 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
 
         when:
         def projectBModuleRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
-        def projectBArtifactRepo2 = projectBModuleRepo2.artifact
         projectBModuleRepo2.pom.expectHead()
         projectBModuleRepo2.pom.sha1.expectGet()
-        projectBArtifactRepo2.expectHead()
-        projectBArtifactRepo2.sha1.expectGet()
+        projectBModuleRepo2.artifact.expectHead()
+        projectBModuleRepo2.artifact.sha1.expectGet()
 
         then:
         succeedsWith 'mavenRepository2'
@@ -77,18 +76,18 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
     def "does not re-download ivy artifact downloaded from a different ivy repository when sha1 matches"() {
         when:
         def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectIvyGet()
-        projectBRepo1.expectJarGet()
+        projectBRepo1.ivy.expectGet()
+        projectBRepo1.jar.expectGet()
 
         then:
         succeedsWith 'ivyRepository1'
 
         when:
         def projectBRepo2 = ivyRepo2.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectIvyHead()
-        projectBRepo2.expectIvySha1Get()
-        projectBRepo2.expectJarHead()
-        projectBRepo2.expectJarSha1Get()
+        projectBRepo2.ivy.expectHead()
+        projectBRepo2.ivy.sha1.expectGet()
+        projectBRepo2.jar.expectHead()
+        projectBRepo2.jar.sha1.expectGet()
 
         then:
         succeedsWith 'ivyRepository2'
@@ -98,16 +97,16 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
         when:
         def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
         projectBRepo1.pom.expectGet()
-        projectBRepo1.getArtifact().expectGet()
+        projectBRepo1.artifact.expectGet()
 
         then:
         succeedsWith 'mavenRepository1'
 
         when:
         def projectBRepo2 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectIvyGet()
-        projectBRepo2.expectJarHead()
-        projectBRepo2.expectJarSha1Get()
+        projectBRepo2.ivy.expectGet()
+        projectBRepo2.jar.expectHead()
+        projectBRepo2.jar.sha1.expectGet()
 
         then:
         succeedsWith 'ivyRepository1'
@@ -116,8 +115,8 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
     def "does not re-download maven artifact downloaded from a ivy repository when sha1 matches"() {
         when:
         def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectIvyGet()
-        projectBRepo1.expectJarGet()
+        projectBRepo1.ivy.expectGet()
+        projectBRepo1.jar.expectGet()
 
         then:
         succeedsWith 'ivyRepository1'
@@ -125,9 +124,8 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
         when:
         def projectBRepo2 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
         projectBRepo2.pom.expectGet()
-        def projectBRepo2Artifact = projectBRepo2.artifact
-        projectBRepo2Artifact.expectHead()
-        projectBRepo2Artifact.sha1.expectGet()
+        projectBRepo2.artifact.expectHead()
+        projectBRepo2.artifact.sha1.expectGet()
 
         then:
         succeedsWith 'mavenRepository1'
@@ -153,7 +151,7 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
         when:
         def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
         projectBRepo1.pom.expectGet()
-        projectBRepo1.getArtifact().expectGet()
+        projectBRepo1.artifact.expectGet()
 
         then:
         succeedsWith 'mavenRepository1'
@@ -175,7 +173,7 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
         when:
         def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
         projectBRepo1.pom.expectGet()
-        projectBRepo1.getArtifact().expectGet()
+        projectBRepo1.artifact.expectGet()
 
         then:
         succeedsWith 'mavenRepository1'
@@ -186,10 +184,9 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
         projectBRepo2.pom.sha1.expectGet()
         projectBRepo2.pom.expectGet()
 
-        def projRepo2BArtifact = projectBRepo2.artifact
-        projRepo2BArtifact.expectHead()
+        projectBRepo2.artifact.expectHead()
         projectBRepo2.artifact.sha1.expectGet()
-        projRepo2BArtifact.expectGet()
+        projectBRepo2.artifact.expectGet()
 
         then:
         succeedsWith 'mavenRepository2'
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
index 81d7596..e33cf9d 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
@@ -25,7 +25,7 @@ import org.gradle.integtests.fixtures.IgnoreVersions
 import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 
 @TargetVersions('1.0-milestone-6+')
- at IgnoreVersions({ it.artifactCacheLayoutVersion == DefaultCacheLockingManager.CACHE_LAYOUT_VERSION })
+ at IgnoreVersions({ it.artifactCacheLayoutVersion == DefaultCacheLockingManager.CACHE_LAYOUT_VERSION || it.version.version == "1.9-rc-1" })
 class CacheReuseCrossVersionIntegrationTest extends AbstractCacheReuseCrossVersionIntegrationTest {
     @Rule public final HttpServer server = new HttpServer()
     final MavenHttpRepository httpRepo = new MavenHttpRepository(server, new MavenFileRepository(file("maven-repo")))
@@ -145,7 +145,7 @@ task retrieve(type: Sync) {
         def userHome = file('user-home')
 
         when:
-        httpRepo.expectMetaDataGet("org.name", "projectB")
+        httpRepo.getModuleMetaData("org.name", "projectB").expectGet()
         projectB.allowAll()
 
         and:
@@ -157,7 +157,7 @@ task retrieve(type: Sync) {
 
         when:
         server.resetExpectations()
-        httpRepo.expectMetaDataGet("org.name", "projectB")
+        httpRepo.getModuleMetaData("org.name", "projectB").expectGet()
         projectB.pom.expectHead()
         projectB.pom.sha1.expectGet()
         projectB.artifact.expectHead()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy
index d82d7ec..3031cb9 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy
@@ -195,29 +195,29 @@ public class CachedChangingModulesIntegrationTest extends AbstractDependencyReso
           }
           """
         when:
-        module.expectIvyGet()
-        module.expectArtifactGet(name: "projectA", classifier: "source")
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "source").expectGet()
 
         then:
         run 'retrieve'
 
         when:
         server.resetExpectations()
-        module.expectIvyHead()
-        module.expectArtifactHead(name: "projectA", classifier: 'source')
+        module.ivy.expectHead()
+        module.getArtifact(classifier: 'source').expectHead()
         then:
         run 'retrieve'
 
         when:
         module.publishWithChangedContent()
         server.resetExpectations()
-        module.expectIvyHead()
-        module.expectArtifactHead(name: "projectA", classifier: 'source')
+        module.ivy.expectHead()
+        module.getArtifact(classifier: 'source').expectHead()
 
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectArtifactGet(name: "projectA", classifier: 'source')
-        module.expectArtifactSha1Get(name: "projectA", classifier: 'source')
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.getArtifact(classifier: 'source').expectGet()
+        module.getArtifact(classifier: 'source').sha1.expectGet()
 
         then:
         run 'retrieve'
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
index 693a917..dfaaa68 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.ivy.IvyHttpModule
 import org.gradle.test.fixtures.server.http.HttpServer
+import spock.lang.Issue
 
 /**
  * We are using Ivy here, but the strategy is the same for any kind of repository.
@@ -59,9 +60,8 @@ task retrieve(type: Sync) {
     }
 
     void initialResolve() {
-        module.expectIvyGet()
-        module.expectJarGet()
-
+        module.ivy.expectGet()
+        module.jar.expectGet()
         resolve()
     }
 
@@ -74,47 +74,47 @@ task retrieve(type: Sync) {
     }
 
     void headOnlyRequests() {
-        module.expectIvyHead()
-        module.expectJarHead()
+        module.ivy.expectHead()
+        module.jar.expectHead()
     }
 
     void headSha1ThenGetRequests() {
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
 
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
+        module.jar.expectHead()
+        module.jar.sha1.expectGet()
+        module.jar.expectGet()
     }
 
     void sha1OnlyRequests() {
-        module.expectIvySha1Get()
-        module.expectJarSha1Get()
+        module.ivy.sha1.expectGet()
+        module.jar.sha1.expectGet()
     }
 
     void sha1ThenGetRequests() {
-        module.expectIvySha1Get()
-        module.expectIvyGet()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
 
-        module.expectJarSha1Get()
-        module.expectJarGet()
+        module.jar.sha1.expectGet()
+        module.jar.expectGet()
     }
 
     void headThenSha1Requests() {
-        module.expectIvyHead()
-        module.expectIvySha1Get()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
 
-        module.expectJarHead()
-        module.expectJarSha1Get()
+        module.jar.expectHead()
+        module.jar.sha1.expectGet()
     }
 
     void headThenGetRequests() {
-        module.expectIvyHead()
-        module.expectIvyGet()
+        module.ivy.expectHead()
+        module.ivy.expectGet()
 
-        module.expectJarHead()
-        module.expectJarGet()
+        module.jar.expectHead()
+        module.jar.expectGet()
     }
 
     void unchangedResolve() {
@@ -217,4 +217,23 @@ task retrieve(type: Sync) {
         headThenGetRequests()
         changedResolve()
     }
+
+    @Issue("GRADLE-2781")
+    def "no leading zeros in sha1 checksums supported"() {
+        given:
+        server.etags = null
+        server.sendLastModified = false
+        byte[] jarBytes = [0, 0, 0, 5]
+        module.jarFile.bytes = jarBytes
+        initialResolve()
+        expect:
+        headThenSha1Requests()
+        trimLeadingZerosFromSHA1()
+        unchangedResolve()
+    }
+
+    def trimLeadingZerosFromSHA1() {
+        //remove leading zeros from sha1 checksum
+        new File("${module.jarFile.absolutePath}.sha1").text = "e14c6ef59816760e2c9b5a57157e8ac9de4012"
+    }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
index 7cb709c..883cd01 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
@@ -22,6 +22,43 @@ import spock.lang.IgnoreIf
 
 class CachedMissingModulesIntegrationTest extends AbstractDependencyResolutionTest {
 
+    public void "caches missing module when module found in another repository"() {
+        server.start()
+
+        given:
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def moduleInRepo1 = repo1.module("group", "projectA", "1.2")
+        def moduleInRepo2 = repo2.module('group', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    ivy { url "${repo1.uri}"}
+    ivy { url "${repo2.uri}"}
+}
+configurations { missing }
+dependencies {
+    missing 'group:projectA:1.2'
+}
+task showMissing << { println configurations.missing.files }
+"""
+
+        when:
+        moduleInRepo1.ivy.expectGetMissing()
+        moduleInRepo1.jar.expectHeadMissing()
+        moduleInRepo2.ivy.expectGet()
+        moduleInRepo2.jar.expectGet()
+
+        then:
+        succeeds("showMissing")
+
+        when:
+        server.resetExpectations() // Missing status in repo1 is cached
+
+        then:
+        succeeds('showMissing')
+    }
+
     def "cached not-found information for dynamic version is ignored if module is not available in any repo"() {
         given:
         server.start()
@@ -53,13 +90,9 @@ class CachedMissingModulesIntegrationTest extends AbstractDependencyResolutionTe
             """
 
         when:
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectDirectoryListGet("group", "projectA")
+        repo1.getModuleMetaData("group", "projectA").expectGetMissing()
         repo1.expectDirectoryListGet("group", "projectA")
-        repo2.expectMetaDataGetMissing("group", "projectA")
-        repo2.expectMetaDataGetMissing("group", "projectA")
-        repo2.expectDirectoryListGet("group", "projectA")
+        repo2.getModuleMetaData("group", "projectA").expectGetMissing()
         repo2.expectDirectoryListGet("group", "projectA")
 
         then:
@@ -67,12 +100,10 @@ class CachedMissingModulesIntegrationTest extends AbstractDependencyResolutionTe
 
         when:
         server.resetExpectations()
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectDirectoryListGet("group", "projectA")
+        repo1.getModuleMetaData("group", "projectA").expectGetMissing()
         repo1.expectDirectoryListGet("group", "projectA")
         repo2Module.publish()
-        repo2.expectMetaDataGet("group", "projectA")
+        repo2.getModuleMetaData("group", "projectA").expectGet()
         repo2Module.pom.expectGet()
         repo2Module.getArtifact().expectGet()
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachingDependencyMetadataInMemoryIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachingDependencyMetadataInMemoryIntegrationTest.groovy
new file mode 100644
index 0000000..0723800
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachingDependencyMetadataInMemoryIntegrationTest.groovy
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.resolve.caching
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import spock.lang.Ignore
+
+class CachingDependencyMetadataInMemoryIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "version list, descriptor and artifact is cached in memory"() {
+        given:
+        mavenRepo.module("org", "lib").publish()
+
+        file("build.gradle") << """
+            configurations {
+                one
+                two
+            }
+            repositories {
+                ivy { url "${mavenRepo.uri}" }
+            }
+            dependencies {
+                one 'org:lib:1.+'
+                two 'org:lib:1.+'
+            }
+            //runs first and resolves
+            task resolveOne << {
+                configurations.one.files
+            }
+            //runs second, purges repo
+            task purgeRepo(type: Delete, dependsOn: resolveOne) {
+                delete "${mavenRepo.uri}"
+            }
+            //runs last, still works even thoug local repo is empty
+            task resolveTwo(dependsOn: purgeRepo) << {
+                println "Resolved " + configurations.two.files*.name
+            }
+        """
+
+        when:
+        run "resolveTwo"
+
+        then:
+        output.contains 'Resolved [lib-1.0.jar]'
+    }
+
+    def "descriptors and artifacts are cached across projects and repositories"() {
+        given:
+        ivyRepo.module("org", "lib").publish()
+
+        file("settings.gradle") << "include 'impl'"
+
+        file("build.gradle") << """
+            allprojects {
+                configurations { conf }
+                repositories { ivy { url "${ivyRepo.uri}" } }
+                dependencies { conf 'org:lib:1.0' }
+                task resolveConf << { println path + " " + configurations.conf.files*.name }
+            }
+            task purgeRepo(type: Delete, dependsOn: ':impl:resolveConf') {
+                delete "${ivyRepo.uri}"
+            }
+            resolveConf.dependsOn purgeRepo
+        """
+
+        when:
+        run "resolveConf"
+
+        then:
+        output.contains ':impl:resolveConf [lib-1.0.jar]'
+        output.contains ':resolveConf [lib-1.0.jar]'
+    }
+
+    def "descriptors and artifacts are separated for different repositories"() {
+        given:
+        ivyRepo.module("org", "lib").publish()
+        def ivyRepo2 = new IvyFileRepository(file("ivy-repo2"))
+        ivyRepo2.module("org", "lib", "2.0").publish() //different version of lib
+
+        file("settings.gradle") << "include 'impl'"
+
+        file("build.gradle") << """
+            allprojects {
+                configurations { conf }
+                dependencies { conf 'org:lib:1.0' }
+                task resolveConf << { println "\$path " + configurations.conf.files*.name }
+            }
+            repositories { ivy { url "${ivyRepo.uri}" } }
+            project(":impl") {
+                repositories { ivy { url "${ivyRepo2.uri}" } }
+                tasks.resolveConf.dependsOn(":resolveConf")
+            }
+        """
+
+        when:
+        runAndFail ":impl:resolveConf"
+
+        then:
+        output.contains ':resolveConf [lib-1.0.jar]'
+        //uses different repo that does not contain this dependency
+        failure.assertResolutionFailure(":impl:conf").assertHasCause("Could not find org:lib:1.0")
+    }
+
+    @Ignore //TODO SF rework or remove this test
+    def "snapshot artifacts are only cached per build"() {
+        given:
+        file("provider/build.gradle") << """
+            apply plugin: 'java'
+            apply plugin: 'maven'
+            group = 'org'
+            archivesBaseName = 'provider'
+            version = '1.0-SNAPSHOT'
+            repositories { maven { url "$mavenRepo.uri" } }
+        """
+        file("provider/src/main/java/Name.java") << """public class Name {
+            public String toString() { return "foo"; }
+        }"""
+
+        when:
+        inDirectory "provider"; run "install"
+
+        then:
+        noExceptionThrown()
+
+        when:
+        file("consumer/build.gradle") << """
+            buildscript {
+                repositories { mavenLocal() }
+                dependencies { classpath 'org:provider:1.0-SNAPSHOT' }
+            }
+            task printName << { println "Name: " + new Name() }
+        """
+
+        inDirectory("consumer"); run "printName"
+
+        then:
+        output.contains "Name: foo"
+
+        when:
+        //change the class
+        file("provider/src/main/java/Name.java").text = """public class Name {
+            public String toString() { return "updated"; }
+        }"""
+
+        inDirectory("provider"); run "install"
+
+        and:
+        inDirectory("consumer"); run "printName"
+
+        then:
+        output.contains "Name: updated" //uses updated artifact
+    }
+
+    def "cache expires at the end of build"() {
+        given:
+        ivyRepo.module("org", "dependency").publish()
+        ivyRepo.module("org", "lib").publish()
+
+        file("build.gradle") << """
+            configurations {
+                configurations { conf }
+                repositories { ivy { url "${ivyRepo.uri}" } }
+                dependencies { conf 'org:lib:1.0' }
+            }
+        """
+
+        when:
+        run "dependencies", "--configuration", "conf"
+
+        then:
+        !output.contains("org:dependency:1.0")
+
+        when:
+        ivyRepo.module("org", "lib").dependsOn("org", "dependency", "1.0").publish()
+        run "dependencies", "--configuration", "conf"
+
+        then:
+        output.contains("org:dependency:1.0")
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy
index 2c70307..4c829dd 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy
@@ -223,8 +223,8 @@ class RecoverFromBrokenResolutionIntegrationTest extends AbstractDependencyResol
                       from configurations.compile
                   }"""
         when:
-        ivyModule.expectIvyGet()
-        ivyModule.expectJarGet()
+        ivyModule.ivy.expectGet()
+        ivyModule.jar.expectGet()
 
         then:
         run 'retrieve'
@@ -252,6 +252,59 @@ class RecoverFromBrokenResolutionIntegrationTest extends AbstractDependencyResol
         file('libs/projectA-1.0.jar').assertIsCopyOf(ivyModule.jarFile)
     }
 
+    def "can run offline mode after connection problem with repo when using ivy dynamic version"() {
+        given:
+        def ivyRepo = ivyHttpRepo("ivyRepo")
+        def ivyModule = ivyRepo.module("group", "projectA", "1.1")
+        ivyModule.publish()
+        and:
+        buildFile << """
+                  repositories {
+                       ivy { url '${ivyRepo.uri}' }
+                  }
+                  configurations {
+                      compile
+                  }
+                  configurations.all {
+                      resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+                  }
+                  dependencies {
+                      compile group:'group', name:'projectA', version:'1.+'
+                  }
+
+                  task retrieve(type: Sync) {
+                      into 'libs'
+                      from configurations.compile
+                  }"""
+        when:
+        ivyModule.repository.expectDirectoryListGet('group', 'projectA')
+        ivyModule.ivy.expectGet()
+        ivyModule.jar.expectGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        int port = server.port
+        server.stop()
+        then:
+        fails 'retrieve'
+
+        and:
+        failure.assertHasDescription('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertHasCause("Could not list versions using Ivy pattern 'http://localhost:${port}/ivyRepo/[organisation]/[module]/[revision]/ivy-[revision].xml")
+        failure.assertHasCause("Connection to http://localhost:${port} refused")
+
+        when:
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+        and:
+        file('libs/projectA-1.1.jar').assertIsCopyOf(ivyModule.jarFile)
+    }
+
     def moduleAvailableViaHttp() {
         module.metaData.expectGet()
         module.pom.expectGet()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
index 1a8b161..652edfe 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
@@ -19,9 +19,8 @@ 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:
+        given:
         def module = ivyRepo.module("group", "projectA", "1.2")
         module.publish()
         def jar = module.jarFile
@@ -45,16 +44,21 @@ class FileSystemResolverIntegrationTest extends AbstractIntegrationSpec {
             }
         """
 
+        when:
+        executer.withDeprecationChecksDisabled()
+        run 'echoContent'
+
         then:
-        succeeds 'echoContent'
         scrapeValue("content") == "1"
         scrapeValue("path") == jar.canonicalPath
 
         when:
         jar.text = "2"
 
+        executer.withDeprecationChecksDisabled()
+        run 'echoContent'
+
         then:
-        succeeds 'echoContent'
         scrapeValue("content") == "2"
         scrapeValue("path") == jar.canonicalPath
     }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
index 67e2a21..2aed311 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
@@ -28,7 +28,7 @@ class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
-    def "setup"() {
+    def setup() {
         requireOwnGradleUserHomeDir()
     }
 
@@ -58,7 +58,8 @@ task listJars << {
 }
 """
         when:
-        succeeds 'listJars'
+        executer.withDeprecationChecksDisabled()
+        run 'listJars'
 
         then:
         server.fileRequests == ["repos/libs/group/projectA/1.2/ivy-1.2.xml",
@@ -70,7 +71,8 @@ task listJars << {
 
         when:
         server.clearRequests()
-        succeeds 'listJars'
+        executer.withDeprecationChecksDisabled()
+        run 'listJars'
 
         then:
         server.fileRequests.empty
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
index f9d2964..11d5efb 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 package org.gradle.integtests.resolve.custom
-
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
 import org.junit.Rule
 
 class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
@@ -24,48 +24,172 @@ class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
     @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     def setup() {
-        server.expectUserAgent(null) // custom resolver uses apache/ivy as useragent strings
+        server.expectUserAgent(null) // custom resolver uses apache/ivy as user agent strings
     }
 
     public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
         server.start()
         given:
-        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+        final IvyHttpRepository repo = ivyHttpRepo
+        def projectA = repo.module('group', 'projectA', '1.2').publish()
+        repo.module('group', 'projectB').publish()
+        def projectB11 = repo.module('group', 'projectB', '1.1').publish()
+        def projectB20 = repo.module('group', 'projectB', '2.0').publish()
 
         and:
         buildFile << """
 repositories {
     add(new org.apache.ivy.plugins.resolver.URLResolver()) {
         name = "repo"
-        addIvyPattern("${ivyHttpRepo.ivyPattern}")
-        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
+        addIvyPattern("${repo.ivyPattern}")
+        addArtifactPattern("${repo.artifactPattern}")
     }
 }
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+configurations {
+    simple
+    dynamic
+    dynamic2
+    dynamicMissing
+}
+dependencies {
+    simple 'group:projectA:1.2'
+    dynamic 'group:projectB:1.+'
+    dynamic2 'group:projectB:2.+'
+    dynamicMissing 'group:projectC:1.+'
+}
+task retrieveSimple(type: Sync) {
+  from configurations.simple
+  into 'simple'
+}
+task retrieveDynamic(type: Sync) {
+  from configurations.dynamic
+  into 'dynamic'
+}
+task retrieveDynamic2(type: Sync) {
+  from configurations.dynamic2
+  into 'dynamic2'
+}
+task retrieveDynamicMissing(type: Sync) {
+  from configurations.dynamicMissing
+  into 'dynamicMissing'
 }
 """
         when:
-        module.expectIvyHead()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarGet()
+        projectA.ivy.expectHead()
+        projectA.ivy.expectGet()
+        projectA.jar.expectHead()
+        projectA.jar.expectGet()
+        executer.withDeprecationChecksDisabled()
+
+        and:
+        succeeds 'retrieveSimple'
+
+        then:
+        file('simple').assertHasDescendants('projectA-1.2.jar')
+
+        and:
+        progressLogging.downloadProgressLogged(projectA.ivy.uri)
+        progressLogging.downloadProgressLogged(projectA.jar.uri)
+
+        when:
+        server.resetExpectations()
+
+        // No extra calls for cached dependencies
+        and:
+        executer.withDeprecationChecksDisabled()
+        succeeds 'retrieveSimple'
+
+        then:
+        file('simple').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        repo.expectDirectoryListGet('group', 'projectB')
+        projectB11.ivy.expectHead()
+        projectB11.ivy.expectGet()
+        projectB11.jar.expectHead()
+        projectB11.jar.expectGet()
+
+        projectB11.ivy.expectGet()
+
+        and:
+        executer.withDeprecationChecksDisabled()
+        succeeds 'retrieveDynamic'
 
         then:
-        succeeds 'listJars'
+        file('dynamic').assertHasDescendants('projectB-1.1.jar')
 
         and:
-        progressLogging.downloadProgressLogged(module.ivyFileUri)
-        progressLogging.downloadProgressLogged(module.jarFileUri)
+        progressLogging.downloadProgressLogged(projectB11.ivy.uri)
+        progressLogging.downloadProgressLogged(projectB11.jar.uri)
 
         when:
         server.resetExpectations()
 
         // No extra calls for cached dependencies
+        and:
+        executer.withDeprecationChecksDisabled()
+        succeeds 'retrieveDynamic'
+
+        then:
+        file('dynamic').assertHasDescendants('projectB-1.1.jar')
+
+        when:
+        repo.expectDirectoryListGet('group', 'projectB')
+        projectB20.ivy.expectHead()
+        projectB20.ivy.expectGet()
+        projectB20.jar.expectHead()
+        projectB20.jar.expectGet()
+        executer.withDeprecationChecksDisabled()
+
+        projectB20.ivy.expectGet()
+
+        and:
+        succeeds 'retrieveDynamic2'
+
+        then:
+        file('dynamic2').assertHasDescendants('projectB-2.0.jar')
+    }
+
+    public void "reports module missing when directory listing for module is empty or broken"() {
+        given:
+        server.start()
+        def repo = ivyHttpRepo
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+        name = "repo"
+        addIvyPattern("${repo.ivyPattern}")
+        addArtifactPattern("${repo.artifactPattern}")
+    }
+}
+configurations { compile }
+dependencies { compile 'org.gradle:projectA:1.+' }
+task testResolve(type: Sync) {
+  println configurations.compile.files
+}
+"""
+        when:
+        server.resetExpectations()
+        2.times {repo.expectDirectoryListGetBroken("org.gradle", "projectA") }
+        executer.withDeprecationChecksDisabled()
+
         then:
-        succeeds 'listJars'
+        fails 'testResolve'
+
+        and:
+        failure.assertHasCause "Could not find any version that matches org.gradle:projectA:1.+"
+
+        when:
+        2.times { repo.expectDirectoryListGetMissing("org.gradle", "projectA") }
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        fails 'testResolve'
+
+        and:
+        failure.assertHasCause "Could not find any version that matches org.gradle:projectA:1.+"
     }
 
     public void "honours changing patterns from custom resolver"() {
@@ -93,10 +217,11 @@ task retrieve(type: Sync) {
 }
 """
         when:
-        module.expectIvyHead()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarGet()
+        module.ivy.expectHead()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.expectGet()
+        executer.withDeprecationChecksDisabled()
 
         run 'retrieve'
 
@@ -110,10 +235,11 @@ task retrieve(type: Sync) {
 
         server.resetExpectations()
         // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarGet()
+        module.ivy.expectHead()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.expectGet()
+        executer.withDeprecationChecksDisabled()
 
         run 'retrieve'
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenDescriptorIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenDescriptorIntegrationTest.groovy
new file mode 100644
index 0000000..ba878f3
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenDescriptorIntegrationTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyBrokenDescriptorIntegrationTest extends AbstractDependencyResolutionTest {
+    def "reports Ivy descriptor that cannot be parsed"() {
+        server.start()
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task showBroken << { println configurations.compile.files }
+"""
+
+        and:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+        module.ivyFile.text = "<ivy-module>"
+
+        when:
+        module.ivy.expectGet()
+
+        then:
+        fails "showBroken"
+        failure
+            .assertResolutionFailure(":compile")
+            .assertHasCause("Could not parse Ivy file ${module.ivy.uri}")
+            .assertHasCause("invalid version null")
+    }
+
+    def "reports missing parent descriptor"() {
+        server.start()
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task showBroken << { println configurations.compile.files }
+"""
+
+        and:
+        def parent = ivyHttpRepo.module('group', 'parent', 'a')
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').extendsFrom(organisation: 'group', module: 'parent', revision: 'a').publish()
+
+        when:
+        module.ivy.expectGet()
+        parent.ivy.expectGetMissing()
+        parent.jar.expectHeadMissing()
+
+        then:
+        fails "showBroken"
+        failure
+            .assertResolutionFailure(":compile")
+            .assertHasCause("Could not parse Ivy file ${module.ivy.uri}")
+            .assertHasCause("Could not find any version that matches group:parent:a.")
+    }
+
+    def "reports parent descriptor that cannot be parsed"() {
+        server.start()
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task showBroken << { println configurations.compile.files }
+"""
+
+        and:
+        def parent = ivyHttpRepo.module('group', 'parent', 'a').publish()
+        parent.ivyFile.text = '<ivy-module/>'
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').extendsFrom(organisation: 'group', module: 'parent', revision: 'a').publish()
+
+        when:
+        module.ivy.expectGet()
+        parent.ivy.expectGet()
+
+        then:
+        fails "showBroken"
+        failure
+            .assertResolutionFailure(":compile")
+            .assertHasCause("Could not parse Ivy file ${module.ivy.uri}")
+            .assertHasCause("Could not parse Ivy file ${parent.ivy.uri}")
+            .assertHasCause("invalid version null")
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
index fca2690..bfb3295 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
@@ -21,19 +21,16 @@ import static org.hamcrest.Matchers.containsString
 
 class IvyBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
-    public void "reports and caches missing module"() {
+    public void "reports and recovers from missing module"() {
         server.start()
 
         given:
-        def repo1 = ivyHttpRepo("repo1")
-        def repo2 = ivyHttpRepo("repo2")
-        def moduleInRepo1 = repo1.module("group", "projectA", "1.2")
-        def moduleInRepo2 = repo2.module('group', 'projectA', '1.2').publish()
+        def repo = ivyHttpRepo("repo1")
+        def module = repo.module("group", "projectA", "1.2").publish()
 
         buildFile << """
 repositories {
-    ivy { url "${repo1.uri}"}
-    ivy { url "${repo2.uri}"}
+    ivy { url "${repo.uri}"}
 }
 configurations { missing }
 dependencies {
@@ -43,21 +40,26 @@ task showMissing << { println configurations.missing.files }
 """
 
         when:
-        moduleInRepo1.expectIvyGetMissing()
-        moduleInRepo1.expectJarHeadMissing()
-        moduleInRepo2.expectIvyGet()
-        moduleInRepo2.expectJarGet()
+        module.ivy.expectGetMissing()
+        module.jar.expectHeadMissing()
 
         then:
-        succeeds("showMissing")
+        fails("showMissing")
+        failure
+            .assertHasDescription('Execution failed for task \':showMissing\'.')
+            .assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
+            .assertHasCause('Could not find group:projectA:1.2.')
 
         when:
-        server.resetExpectations() // Missing status in repo1 is cached
+        server.resetExpectations()
+        module.ivy.expectGet()
+        module.jar.expectGet()
+
         then:
         succeeds('showMissing')
     }
 
-    public void "reports and recovers from broken module"() {
+    public void "reports and recovers from failed Ivy descriptor download"() {
         server.start()
 
         given:
@@ -77,7 +79,7 @@ task showBroken << { println configurations.broken.files }
 """
 
         when:
-        module.expectIvyGetBroken()
+        module.ivy.expectGetBroken()
         fails("showBroken")
 
         then:
@@ -85,12 +87,12 @@ task showBroken << { println configurations.broken.files }
             .assertHasDescription('Execution failed for task \':showBroken\'.')
             .assertResolutionFailure(':broken')
             .assertHasCause('Could not resolve group:projectA:1.3.')
-            .assertHasCause("Could not GET '${ivyHttpRepo.uri}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
+            .assertHasCause("Could not GET '${module.ivy.uri}'. Received status code 500 from server: broken")
 
         when:
         server.resetExpectations()
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         then:
         succeeds("showBroken")
@@ -120,20 +122,20 @@ task retrieve(type: Sync) {
         def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
 
         when:
-        module.expectIvyGet()
-        module.expectJarGetMissing()
+        module.ivy.expectGet()
+        module.jar.expectGetMissing()
 
         then:
         fails "retrieve"
 
-        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2:projectA.jar' not found"))
 
         when:
         server.resetExpectations()
 
         then:
         fails "retrieve"
-        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2:projectA.jar' not found"))
     }
 
     public void "reports and recovers from failed artifact download"() {
@@ -160,17 +162,17 @@ task retrieve(type: Sync) {
         def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
 
         when:
-        module.expectIvyGet()
-        module.expectJarGetBroken()
+        module.ivy.expectGet()
+        module.jar.expectGetBroken()
 
         then:
         fails "retrieve"
-        failure.assertHasCause("Could not download artifact 'group:projectA:1.2 at jar'")
-        failure.assertHasCause("Could not GET '${ivyHttpRepo.uri}/group/projectA/1.2/projectA-1.2.jar'. Received status code 500 from server: broken")
+        failure.assertHasCause("Could not download artifact 'group:projectA:1.2:projectA.jar'")
+        failure.assertHasCause("Could not GET '${module.jar.uri}'. Received status code 500 from server: broken")
 
         when:
         server.resetExpectations()
-        module.expectJarGet()
+        module.jar.expectGet()
 
         then:
         succeeds "retrieve"
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
index 2bcdea5..e8dbea5 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
@@ -48,8 +48,8 @@ task retrieve(type: Copy) {
         def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
 
         and: "Server handles requests"
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         and: "We request 1.1 (changing)"
         run 'retrieve'
@@ -66,14 +66,13 @@ task retrieve(type: Copy) {
         and: "Server handles requests"
         server.resetExpectations()
         // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectArtifactGet('other')
-        moduleB.expectIvyGet()
-        moduleB.expectJarGet()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.getArtifact(name: 'other').expectGet()
+        moduleB.ivy.expectGet()
+        moduleB.jar.expectGet()
 
         and: "We request 1.1 again"
         run 'retrieve'
@@ -117,12 +116,12 @@ task retrieve(type: Copy) {
         when:
         server.resetExpectations()
         module.publishWithChangedContent()
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.sha1.expectGet()
+        module.jar.expectGet()
 
         and:
         executer.withArguments('-PisChanging')
@@ -161,8 +160,8 @@ task retrieve(type: Copy) {
         def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
 
         when:
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         run 'retrieve'
 
@@ -176,12 +175,12 @@ task retrieve(type: Copy) {
 
         server.resetExpectations()
         // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.sha1.expectGet()
+        module.jar.expectGet()
 
         run 'retrieve'
 
@@ -223,8 +222,8 @@ task retrieve(type: Copy) {
         def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
 
         and: "Server handles requests"
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         and: "We request 1.1 (changing)"
         run 'retrieve'
@@ -250,13 +249,13 @@ task retrieve(type: Copy) {
         when: "Server handles requests"
         server.resetExpectations()
         // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-        module.expectArtifactGet('other')
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.sha1.expectGet()
+        module.jar.expectGet()
+        module.getArtifact(name: 'other').expectGet()
 
         and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
         executer.withArguments("-PdoNotCacheChangingModules")
@@ -310,8 +309,8 @@ task retrieve(type: Copy) {
         def module = ivyHttpRepo.module("group", "projectA", "1-CHANGING").publish()
 
         and: "Server handles requests"
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         and: "We request 1-CHANGING"
         run 'retrieve'
@@ -329,13 +328,13 @@ task retrieve(type: Copy) {
         and: "Server handles requests"
         server.resetExpectations()
         // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-        module.expectArtifactGet('other')
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.sha1.expectGet()
+        module.jar.expectGet()
+        module.getArtifact(name: 'other').expectGet()
 
         and: "We request 1-CHANGING again"
         executer.withArguments()
@@ -374,16 +373,10 @@ task retrieve(type: Copy) {
 
         and:
         def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-        
-        def base = "/repo/group/projectA/1.1"
-        def ivyPath = "$base/$module.ivyFile.name"
-        def ivySha1Path = "${ivyPath}.sha1"
-        def jarPath = "$base/$module.jarFile.name"
-        def jarSha1Path = "${jarPath}.sha1"
 
         when:
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         and:
         run 'retrieve'
@@ -395,8 +388,8 @@ task retrieve(type: Copy) {
 
         when:
         server.resetExpectations()
-        module.expectIvyHead()
-        module.expectJarHead()
+        module.ivy.expectHead()
+        module.jar.expectHead()
 
         and:
         run 'retrieve'
@@ -409,12 +402,12 @@ task retrieve(type: Copy) {
         module.publishWithChangedContent()
 
         server.resetExpectations()
-        module.expectIvyHead()
-        module.expectIvySha1GetMissing()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1GetMissing()
-        module.expectJarGet()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGetMissing()
+        module.ivy.expectGet()
+        module.jar.expectHead()
+        module.jar.sha1.expectGetMissing()
+        module.jar.expectGet()
 
         run 'retrieve'
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesChangingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesChangingModulesIntegrationTest.groovy
new file mode 100644
index 0000000..0e32ff4
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesChangingModulesIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ComponentMetadataRulesChangingModulesIntegrationTest
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+
+class IvyComponentMetadataRulesChangingModulesIntegrationTest extends ComponentMetadataRulesChangingModulesIntegrationTest {
+    IvyHttpRepository getRepo() {
+        ivyHttpRepo
+    }
+
+    String getRepoDeclaration() {
+"""
+repositories {
+    ivy {
+        url "$repo.uri"
+    }
+}
+"""
+    }
+
+    def setup() {
+        repo.allowDirectoryListGet("org.test", "moduleA")
+    }
+}
+
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesIntegrationTest.groovy
new file mode 100644
index 0000000..45ed7e2
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesIntegrationTest.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.resolve.ivy
+
+import org.gradle.integtests.resolve.ComponentMetadataRulesIntegrationTest
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+
+class IvyComponentMetadataRulesIntegrationTest extends ComponentMetadataRulesIntegrationTest {
+    @Override
+    IvyHttpRepository getRepo() {
+        ivyHttpRepo
+    }
+
+    @Override
+    String getRepoDeclaration() {
+"""
+repositories {
+    ivy {
+        url "$ivyHttpRepo.uri"
+    }
+}
+"""
+    }
+
+    @Override
+    String getDefaultStatus() {
+        "integration"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesStatusIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesStatusIntegrationTest.groovy
new file mode 100644
index 0000000..e4944a1
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyComponentMetadataRulesStatusIntegrationTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ComponentMetadataRulesStatusIntegrationTest
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+
+class IvyComponentMetadataRulesStatusIntegrationTest extends ComponentMetadataRulesStatusIntegrationTest {
+    @Override
+    IvyHttpRepository getRepo() {
+        ivyHttpRepo
+    }
+
+    @Override
+    String getRepoDeclaration() {
+"""
+repositories {
+    ivy {
+        url "$ivyHttpRepo.uri"
+    }
+}
+"""
+    }
+
+    def setup() {
+        repo.module('org.test', 'projectA', '1.0').withStatus("silver").publish().allowAll()
+    }
+
+    def "module with custom status can be resolved by adapting status scheme"() {
+        buildFile <<
+                """
+dependencies {
+    components {
+        eachComponent { details ->
+            assert details.status == "silver"
+            details.statusScheme = ["gold", "silver", "bronze"]
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'resolve'
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+    }
+
+    def "resolve fails if status doesn't match default status scheme"() {
+        expect:
+        fails 'resolve'
+        failure.assertHasCause(/Unexpected status 'silver' specified for org.test:projectA:1.0. Expected one of: [integration, milestone, release]/)
+    }
+
+    def "resolve fails if status doesn't match custom status scheme"() {
+        buildFile <<
+                """
+dependencies {
+    components {
+        eachComponent { details ->
+            details.statusScheme = ["gold", "bronze"]
+        }
+    }
+}
+"""
+
+        expect:
+        fails 'resolve'
+        failure.assertHasCause(/Unexpected status 'silver' specified for org.test:projectA:1.0. Expected one of: [gold, bronze]/)
+    }
+
+    def "rule can change status"() {
+        buildFile <<
+                """
+dependencies {
+    components {
+        eachComponent { details ->
+            details.status = "milestone"
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'resolve'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyCustomStatusLatestVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyCustomStatusLatestVersionIntegrationTest.groovy
new file mode 100644
index 0000000..c598206
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyCustomStatusLatestVersionIntegrationTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyCustomStatusLatestVersionIntegrationTest extends AbstractDependencyResolutionTest {
+    def "latest.xyz selects highest version with given or higher status"() {
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.test:projectA:latest.$status'
+    components {
+        eachComponent { details ->
+            details.statusScheme = ["bronze", "silver", "gold", "platin"]
+        }
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('bronze').publish()
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('silver').publish()
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('gold').publish()
+        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('platin').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants("projectA-${version}.jar")
+
+        where:
+        status   | version
+        "bronze" | "1.3"
+        "silver" | "1.3"
+        "gold"   | "1.2"
+        "platin" | "1.0"
+    }
+
+    def "uses status provided by component metadata rule for latest.xyz"() {
+        server.start()
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.test:projectA:latest.release'
+    components {
+        eachComponent { details ->
+            if (details.id.version == project.properties['releaseVersion']) {
+                details.status = 'release'
+            }
+        }
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        ivyHttpRepo.allowDirectoryListGet('org.test', 'projectA')
+        ivyHttpRepo.module('org.test', 'projectA', '1.0').withStatus("release").publish().allowAll()
+        ivyHttpRepo.module('org.test', 'projectA', '1.1').withStatus("integration").publish().allowAll()
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants("projectA-1.0.jar")
+
+        when:
+        executer.withArgument("-PreleaseVersion=1.1")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants("projectA-1.1.jar")
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy
index 858d735..aea2efb 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 package org.gradle.integtests.resolve.ivy
+
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Unroll
 
 class IvyDescriptorResolveIntegrationTest extends AbstractDependencyResolutionTest {
     def "substitutes system properties into ivy descriptor"() {
@@ -52,24 +54,61 @@ task check << {
         succeeds "check"
     }
 
-    def "merges values from included descriptor file"() {
+    def "merges values from parent descriptor file that is available locally"() {
         given:
-        final parentModule = ivyRepo.module("org.gradle.parent", "parent_module", "1.1").dependsOn("org.gradle.dep", "dep_module", "1.1").publish()
-        ivyRepo.module("org.gradle.dep", "dep_module", "1.1").publish()
+        server.start()
+        def parentModule = ivyHttpRepo.module("org.gradle.parent", "parent_module", "1.1").dependsOn("org.gradle.dep", "dep_module", "1.1").publish()
+        def depModule = ivyHttpRepo.module("org.gradle.dep", "dep_module", "1.1").publish()
 
-        final module = ivyRepo.module("org.gradle", "test", "1.45")
-        final extendAttributes = ["organisation": "org.gradle.parent", "module": "parent_module", "revision": "1.1"]
-        if (includeLocation) {
-            extendAttributes["location"] = parentModule.ivyFile.absolutePath
-        }
-        module.withXml {
-            asNode().info[0].appendNode("extends", extendAttributes)
-        }
+        def module = ivyHttpRepo.module("org.gradle", "test", "1.45")
+        module.extendsFrom(organisation: "org.gradle.parent", module: "parent_module", revision: "1.1", location: parentModule.ivyFile.toURI().toURL())
+        parentModule.publish()
         module.publish()
 
+        when:
+        buildFile << """
+repositories { ivy { url "${ivyHttpRepo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'dep_module-1.1.jar']
+}
+"""
+
         and:
+        module.ivy.expectGet()
+        depModule.ivy.expectGet()
+        module.jar.expectGet()
+        depModule.jar.expectGet()
+
+        then:
+        succeeds "check"
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds "check"
+    }
+
+    def "merges values from parent descriptor file"() {
+        given:
+        server.start()
+        final parentModule = ivyHttpRepo.module("org.gradle.parent", "parent_module", "1.1").dependsOn("org.gradle.dep", "dep_module", "1.1").publish()
+        final depModule = ivyHttpRepo.module("org.gradle.dep", "dep_module", "1.1").publish()
+
+        final module = ivyHttpRepo.module("org.gradle", "test", "1.45")
+        final extendAttributes = [organisation: "org.gradle.parent", module: "parent_module", revision: "1.1"]
+        module.extendsFrom(extendAttributes)
+        parentModule.publish()
+        module.publish()
+
+        when:
         buildFile << """
-repositories { ivy { url "${ivyRepo.uri}" } }
+repositories { ivy { url "${ivyHttpRepo.uri}" } }
 configurations { compile }
 dependencies {
     compile "org.gradle:test:1.45"
@@ -80,12 +119,122 @@ task check << {
 }
 """
 
-        expect:
+        and:
+        module.ivy.expectGet()
+        parentModule.ivy.expectGet()
+        depModule.ivy.expectGet()
+        module.jar.expectGet()
+        depModule.jar.expectGet()
+
+        then:
+        succeeds "check"
+
+        when:
+        server.resetExpectations()
+
+        then:
         succeeds "check"
+    }
+
+    @Unroll
+    def "excludes transitive dependencies when ivy.xml has dependency declared with #name"() {
+        given:
+
+        ivyRepo.module("org.gradle.dep", "dep_module", "1.134")
+                .dependsOn("org.gradle.one", "mod_one", "1.1")
+                .dependsOn("org.gradle.two", "mod_one", "2.1")
+                .dependsOn("org.gradle.two", "mod_two", "2.2")
+                .publish()
+        ivyRepo.module("org.gradle.one", "mod_one", "1.1").artifact([:]).artifact([type: 'war']).publish()
+        ivyRepo.module("org.gradle.two", "mod_one", "2.1").publish()
+        ivyRepo.module("org.gradle.two", "mod_two", "2.2").publish()
+
+        ivyRepo.module("org.gradle.test", "test_exclude", "1.134")
+                .dependsOn("org.gradle.dep", "dep_module", "1.134")
+                .withXml({
+            asNode().dependencies[0].dependency[0].appendNode("exclude", excludeAttributes)
+        })
+                .publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${ivyRepo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle.test:test_exclude:1.134"
+}
+
+task check(type: Sync) {
+    into "libs"
+    from configurations.compile
+}
+"""
+
+        when:
+        succeeds "check"
+
+        then:
+        def jars = ['test_exclude-1.134.jar', 'dep_module-1.134.jar'] + transitiveJars
+        file("libs").assertHasDescendants(jars.toArray(new String[0]))
 
         where:
-        name | includeLocation
-        "with explicit location" | true
-        "without explicit location" | false
+        name                       | excludeAttributes                          | transitiveJars
+        "empty exclude"            | [:]                                        | []
+        "unmatched exclude"        | [module: "different"]                      | ['mod_one-1.1.jar', 'mod_one-1.1.war', 'mod_one-2.1.jar', 'mod_two-2.2.jar']
+        "module exclude"           | [module: "mod_one"]                        | ['mod_two-2.2.jar']
+        "org exclude"              | [org: "org.gradle.two"]                    | ['mod_one-1.1.jar', 'mod_one-1.1.war']
+        "module and org exclude"   | [org: "org.gradle.two", module: "mod_one"] | ['mod_one-1.1.jar', 'mod_one-1.1.war', 'mod_two-2.2.jar']
+        "regex module exclude"     | [module: "mod.*"]                          | []
+        "matching config exclude"  | [module: "mod_one", conf: "default,other"] | ['mod_two-2.2.jar']
+        "unmatched config exclude" | [module: "mod_one", conf: "other"]         | ['mod_one-1.1.jar', 'mod_one-1.1.war', 'mod_one-2.1.jar', 'mod_two-2.2.jar']
+//  GRADLE-2674
+//        "type exclude"           | [type: "war"]                              | ['mod_one-1.1.jar', 'mod_one-2.1.jar', 'mod_two-2.2.jar']
+    }
+
+    def "transitive dependencies are only excluded if excluded from each dependency declaration"() {
+//        c -> d,e
+//        a -> c (excludes 'd')
+//        b -> c (excludes 'd', 'e')
+        given:
+        ivyRepo.module("d").publish()
+        ivyRepo.module("e").publish()
+        ivyRepo.module("c").dependsOn("d").dependsOn("e").publish()
+
+        ivyRepo.module("a")
+                .dependsOn("c")
+                .withXml({
+                    asNode().dependencies[0].dependency[0].appendNode("exclude", [module: "d"])
+                })
+                .publish()
+        ivyRepo.module("b")
+                .dependsOn("c")
+                .withXml({
+                    def dep = asNode().dependencies[0].dependency[0]
+                    dep.appendNode("exclude", [module: "d"])
+                    dep.appendNode("exclude", [module: "e"])
+                })
+                .publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${ivyRepo.uri}" } }
+configurations {
+    merged
+}
+dependencies {
+    merged "org.gradle.test:a:1.0", "org.gradle.test:b:1.0"
+}
+
+task syncMerged(type: Sync) {
+    from configurations.merged
+    into "libs"
+}
+"""
+
+        when:
+        succeeds "syncMerged"
+
+        then:
+        file("libs").assertHasDescendants(['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar', 'e-1.0.jar'] as String[])
     }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorValidationIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorValidationIntegrationTest.groovy
new file mode 100644
index 0000000..33ace34
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorValidationIntegrationTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+import static org.hamcrest.CoreMatchers.containsString
+
+class IvyDescriptorValidationIntegrationTest extends AbstractDependencyResolutionTest {
+    def "incorrect value for revision attribute is flagged"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:[1.3,1.5]'
+  }
+  task resolve << {
+      configurations.compile.resolve()
+  }
+  """
+
+        def module = ivyRepo.module('org.test', 'projectA', '1.4')
+        module.publish()
+
+        expect:
+        succeeds 'resolve'
+
+        when:
+        module.ivyFile.setText(module.ivyFile.text.replace('revision="1.4"', 'revision="1.6"'), "utf-8")
+
+        then:
+        fails 'resolve'
+        failure.assertThatCause(containsString("bad version: expected='1.4' found='1.6'"))
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
index a4af2af..e433c51 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
@@ -14,160 +14,110 @@
  * limitations under the License.
  */
 package org.gradle.integtests.resolve.ivy
-
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.resolve.ResolveTestFixture
+import org.gradle.test.fixtures.Repository
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import spock.lang.Unroll
 
 class IvyDynamicRevisionRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    ResolveTestFixture resolve
+
+    def setup() {
+        settingsFile << "rootProject.name = 'test' "
+
+        resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
 
-    def "uses latest version from version range and latest status"() {
         server.start()
+    }
 
+    def "uses latest version from version range and latest status"() {
         given:
+        useRepository ivyHttpRepo
         buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
+configurations { compile }
+if (project.hasProperty('refreshDynamicVersions')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
     }
 }
-
-configurations { compile }
-
 dependencies {
     compile group: "group", name: "projectA", version: "1.+"
     compile group: "group", name: "projectB", version: "latest.integration"
 }
-
-configurations.all {
-    resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
 """
-
-        when: "Version 1.1 is published"
+        when:
         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"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA1.expectIvyGet()
-        projectA1.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB1.expectIvyGet()
-        projectB1.expectJarGet()
-
         and:
-        run 'retrieve'
+        expectGetDynamicRevision(projectA1)
+        expectGetDynamicRevision(projectB1)
 
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.1",
+                     "group:projectB:latest.integration": "group:projectB:1.1"
 
-        when: "New versions are published"
+        when:
         def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
         def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
 
-        and: "Server handles requests"
-        server.resetExpectations()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA2.expectIvyGet()
-        projectA2.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB2.expectIvyGet()
-        projectB2.expectJarGet()
-
         and:
-        run 'retrieve'
+        server.resetExpectations()
+        expectGetDynamicRevision(projectA2)
+        expectGetDynamicRevision(projectB2)
 
-        then: "New versions are used"
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+        then:
+        executer.withArgument("-PrefreshDynamicVersions")
+        checkResolve "group:projectA:1.+": "group:projectA:1.2", "group:projectB:latest.integration": "group:projectB:2.2"
     }
 
     def "determines latest version with jar only"() {
-        server.start()
-
         given:
+        useRepository ivyHttpRepo
         buildFile << """
-repositories {
-  ivy {
-      url "${ivyHttpRepo.uri}"
-  }
-}
-
 configurations { compile }
-
 dependencies {
   compile group: "group", name: "projectA", version: "1.+"
 }
-
-task retrieve(type: Sync) {
-  from configurations.compile
-  into 'libs'
-}
 """
 
-        when: "Version 1.1 is published"
-        def projectA11 = ivyHttpRepo.module("group", "projectA", "1.1").withNoMetaData().publish()
+        when:
+        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"
+        and:
         ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGetMissing()
-        projectA11.expectIvyGetMissing()
+        projectA12.ivy.expectGetMissing()
+        projectA12.jar.expectHead()
+        projectA12.jar.expectGet()
 
-        // TODO - Should not list twice
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA12.expectJarHead()
-        projectA12.expectJarGet()
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
 
-        and:
-        run 'retrieve'
+        when: "result is cached"
+        server.resetExpectations()
 
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
     }
 
     def "uses latest version with correct status for latest.release and latest.milestone"() {
-        server.start()
-
         given:
+        useRepository ivyHttpRepo
         buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
-configurations {
-    release
-    milestone
-}
+def latestRevision = project.getProperty('latestRevision')
+configurations { compile }
 
 dependencies {
-    release group: "group", name: "projectA", version: "latest.release"
-    milestone group: "group", name: "projectA", version: "latest.milestone"
-}
-
-task retrieveRelease(type: Sync) {
-    from configurations.release
-    into 'release'
-}
-
-task retrieveMilestone(type: Sync) {
-    from configurations.milestone
-    into 'milestone'
+    compile group: "group", name: "projectA", version: "latest.\${latestRevision}"
 }
 """
 
-        when: "Versions are published"
+        when:
         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()
@@ -175,86 +125,111 @@ task retrieveMilestone(type: Sync) {
         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"
+        and:
         ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        integration.expectIvyGet()
-        milestone.expectIvyGet()
-        release.expectIvyGet()
-        release.expectJarGet()
+        integration.ivy.expectGet()
+        milestone.ivy.expectGet()
+        release.ivy.expectGet()
+        release.jar.expectGet()
 
         and:
-        run 'retrieveRelease'
+        executer.withArgument('-PlatestRevision=release')
 
         then:
-        file('release').assertHasDescendants('projectA-2.0.jar')
+        checkResolve "group:projectA:latest.release": "group:projectA:2.0"
 
         when:
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        integration.expectIvyHead()
-        milestone.expectIvyHead()
-        milestone.expectJarGet()
+        server.resetExpectations()
+        milestone.jar.expectGet()
+        executer.withArgument('-PlatestRevision=milestone')
 
-        and:
-        run 'retrieveMilestone'
+        then:
+        checkResolve "group:projectA:latest.milestone": "group:projectA:2.1"
+
+        when:
+        server.resetExpectations()
+        executer.withArgument('-PlatestRevision=milestone')
 
         then:
-        file('milestone').assertHasDescendants('projectA-2.1.jar')
+        checkResolve "group:projectA:latest.milestone": "group:projectA:2.1"
     }
 
     def "can use latest version from different remote repositories"() {
-        server.start()
         def repo1 = ivyHttpRepo("ivy1")
         def repo2 = ivyHttpRepo("ivy2")
 
         given:
+        useRepository repo1, repo2
         buildFile << """
-    repositories {
-        ivy {
-            url "${repo1.uri}"
-        }
-        ivy {
-            url "${repo2.uri}"
-        }
-    }
-
-    configurations {
-        milestone
-    }
-
+    configurations { compile }
     dependencies {
-        milestone group: "group", name: "projectA", version: "latest.milestone"
-    }
-
-    task retrieveMilestone(type: Sync) {
-        from configurations.milestone
-        into 'milestone'
+        compile group: "group", name: "projectA", version: "latest.milestone"
     }
     """
 
-        when: "Versions are published"
+        when:
         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
+        and:
+        expectGetDynamicRevision(version11)
+
         repo2.expectDirectoryListGet("group", "projectA")
-        // TODO - shouldn't need this
-        version12.expectJarGet()
+        version12.ivy.expectGet()
+
+        then:
+        checkResolve "group:projectA:latest.milestone": "group:projectA:1.1"
+
+        when:
+        server.resetExpectations()
+
+        then:
+        checkResolve "group:projectA:latest.milestone": "group:projectA:1.1"
+    }
+
+    def "can get latest version from repository with multiple ivyPatterns"() {
+        given:
+        def repo1 = ivyHttpRepo("ivyRepo1")
+        def repo1version2 = repo1.module('org.test', 'projectA', '1.2').withStatus("milestone").publish()
+        def repo1version3 = repo1.module('org.test', 'projectA', '1.3')
+        def repo2 = ivyHttpRepo("ivyRepo2")
+        repo2.module('org.test', 'projectA', '1.1').withStatus("integration").publish()
+        def repo2version3 = repo2.module('org.test', 'projectA', '1.3').withStatus("integration").publish()
 
         and:
-        run 'retrieveMilestone'
+        buildFile << """
+repositories {
+    ivy {
+        url "${repo1.uri}"
+        ivyPattern "${repo2.ivyPattern}"
+        artifactPattern "${repo2.artifactPattern}"
+    }
+}
+configurations { compile }
+dependencies {
+  compile 'org.test:projectA:latest.milestone'
+}
+"""
+        when:
+        repo1.expectDirectoryListGet("org.test", "projectA")
+        repo2.expectDirectoryListGet("org.test", "projectA")
+        // TODO - don't need this request
+        repo1version3.ivy.expectGetMissing()
+        repo2version3.ivy.expectGet()
+        repo1version2.ivy.expectGet()
+        repo1version2.jar.expectGet()
+
+        then:
+        checkResolve "org.test:projectA:latest.milestone": "org.test:projectA:1.2"
+
+        when:
+        server.resetExpectations()
 
         then:
-        file('milestone').assertHasDescendants('projectA-1.1.jar')
+        checkResolve "org.test:projectA:latest.milestone": "org.test:projectA:1.2"
     }
 
     def "checks new repositories before returning any cached value"() {
-        server.start()
         def repo1 = ivyHttpRepo("repo1")
         def repo2 = ivyHttpRepo("repo2")
 
@@ -271,68 +246,48 @@ if (project.hasProperty('addRepo2')) {
 }
 
 configurations { compile }
-
 dependencies {
     compile group: "group", name: "projectA", version: "1.+"
 }
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
 """
 
         when:
         def projectA11 = repo1.module("group", "projectA", "1.1").publish()
         def projectA12 = repo2.module("group", "projectA", "1.2").publish()
 
-        and: "Server handles requests"
-        repo1.expectDirectoryListGet("group", "projectA")
-        projectA11.expectIvyGet()
-        projectA11.expectJarGet()
-
-        and: "Retrieve with only repo1"
-        run 'retrieve'
+        and:
+        expectGetDynamicRevision(projectA11)
 
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.1"
 
-        when: "Server handles requests"
+        when:
         server.resetExpectations()
-        repo2.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
+        expectGetDynamicRevision(projectA12)
 
-        and: "Retrieve with both repos"
+        then:
         executer.withArguments("-PaddRepo2")
-        run 'retrieve'
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
 
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        when:
+        server.resetExpectations()
+
+        then:
+        executer.withArguments("-PaddRepo2")
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
     }
 
-    def "does not cache information about broken modules"() {
-        server.start()
+    def "recovers from broken modules in subsequent resolution"() {
         def repo1 = ivyHttpRepo("repo1")
         def repo2 = ivyHttpRepo("repo2")
 
         given:
+        useRepository repo1, repo2
         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:
@@ -341,107 +296,61 @@ task retrieve(type: Sync) {
 
         and: "projectA is broken in repo1"
         repo1.expectDirectoryListGetBroken("group", "projectA")
-        repo2.expectDirectoryListGet("group", "projectA")
-        projectA11.expectIvyGet()
-        projectA11.expectJarGet()
-
-        and: "Retrieve with only repo2"
-        run 'retrieve'
+        expectGetDynamicRevision(projectA11)
 
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.1"
 
-        when: "Server handles requests"
+        when:
         server.resetExpectations()
-        repo1.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
-
-        and: "Retrieve with both repos"
-        run 'retrieve'
+        expectGetDynamicRevision(projectA12)
 
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
     }
 
     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:
+        useRepository repo1, repo2, repo3
         buildFile << """
-repositories {
-    ivy { url "${repo1.uri}" }
-    ivy { url "${repo2.uri}" }
-    ivy { url "${repo3.uri}" }
-}
-
 configurations { compile }
-
 dependencies {
     compile group: "group", name: "projectA", version: "1.+"
 }
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
 """
 
-        when: "Versions are published"
+        when:
         def projectA11 = repo1.module("group", "projectA", "1.1").publish()
         def projectA12 = repo3.module("group", "projectA", "1.2").publish()
 
-        and: "Server handles requests"
+        and:
         repo1.expectDirectoryListGet("group", "projectA")
         // TODO Should not need to get this
-        projectA11.expectIvyGet()
-        // TODO Should only list missing directory once
-        repo2.expectDirectoryListGet("group", "projectA")
+        projectA11.ivy.expectGet()
         repo2.expectDirectoryListGet("group", "projectA")
-        repo3.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
+        expectGetDynamicRevision(projectA12)
 
-        and:
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
 
-        when: "Run again with cached dependencies"
+        when:
         server.resetExpectations()
-        def result = run 'retrieve'
 
-        then: "No server requests, task skipped"
-        result.assertTaskSkipped(':retrieve')
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
     }
 
     def "reuses cached artifacts that match multiple dynamic versions"() {
-        server.start()
-
         given:
+        useRepository ivyHttpRepo
         buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { deps1; deps2 }
-
+configurations { compile }
 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'
+    compile group: "org.test", name: "projectA", version: project.getProperty('dependencyVersion')
 }
 """
 
@@ -450,108 +359,70 @@ task retrieve2(type: Sync) {
         def projectA12 = ivyHttpRepo.module("org.test", "projectA", "1.2").publish()
 
         and:
-        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
+        expectGetDynamicRevision(projectA12)
 
         and:
-        run 'retrieve1'
+        executer.withArgument("-PdependencyVersion=1.+")
 
         then:
-        file('libs1').assertHasDescendants('projectA-1.2.jar')
+        checkResolve "org.test:projectA:1.+": "org.test:projectA:1.2"
 
         when:
         server.resetExpectations()
-        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
-        projectA12.expectIvyHead()
 
         and:
-        run 'retrieve2'
+        executer.withArgument("-PdependencyVersion=[1.0,2.0)")
 
         then:
-        file('libs1').assertHasDescendants('projectA-1.2.jar')
+        checkResolve "org.test:projectA:[1.0,2.0)": "org.test:projectA:1.2"
     }
 
     def "caches resolved revisions until cache expiry"() {
-        server.start()
-
         given:
+        useRepository ivyHttpRepo
         buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
 configurations { compile }
-
 dependencies {
     compile group: "group", name: "projectA", version: "1.+"
 }
-
 if (project.hasProperty('noDynamicRevisionCache')) {
     configurations.all {
         resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
     }
 }
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
 """
 
         when: "Version 1.1 is published"
         def version1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
 
-        and: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        version1.expectIvyGet()
-        version1.expectJarGet()
-
-        and: "We request 1.+"
-        run 'retrieve'
+        and:
+        expectGetDynamicRevision(version1)
 
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.1"
 
         when: "Version 1.2 is published"
+        server.resetExpectations()
         def version2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
 
-        and: "We request 1.+, with dynamic mappings cached. No server requests."
-        run 'retrieve'
-
         then: "Version 1.1 is still used, as the 1.+ -> 1.1 mapping is cached"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+        checkResolve "group:projectA:1.+": "group:projectA:1.1"
 
-        when: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        version2.expectIvyGet()
-        version2.expectJarGet()
+        when: "zero expiry for dynamic revision cache"
+        executer.withArguments("-PnoDynamicRevisionCache")
 
-        and: "We request 1.+, with zero expiry for dynamic revision cache"
-        executer.withArguments("-PnoDynamicRevisionCache").withTasks('retrieve').run()
+        and:
+        expectGetDynamicRevision(version2)
 
         then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(version2.jarFile)
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
     }
 
     def "uses and caches dynamic revisions for transitive dependencies"() {
-        server.start()
-
         given:
+        useRepository ivyHttpRepo
         buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
 configurations { compile }
-
 dependencies {
     compile group: "group", name: "main", version: "1.0"
 }
@@ -561,117 +432,104 @@ if (project.hasProperty('noDynamicRevisionCache')) {
         resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
     }
 }
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
 """
 
-        when: "Version is published"
+        when:
         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"
+        and:
         def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
         def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").publish()
 
-        and: "Server handles requests"
-        mainProject.expectIvyGet()
-        mainProject.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA1.expectIvyGet()
-        projectA1.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB1.expectIvyGet()
-        projectB1.expectJarGet()
-
         and:
-        run 'retrieve'
+        mainProject.ivy.expectGet()
+        mainProject.jar.expectGet()
+        expectGetDynamicRevision(projectA1)
+        expectGetDynamicRevision(projectB1)
 
-        then: "Initial transitive dependencies are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+        then:
+        succeeds('checkDeps')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                module("group:main:1.0") {
+                    edge("group:projectA:1.+", "group:projectA:1.1")
+                    edge("group:projectB:latest.integration", "group:projectB:1.1")
+                }
+            }
+        }
 
-        when: "New versions are published"
+        when:
         def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
         def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
 
-        and: "No server requests"
-        server.resetExpectations()
-
         and:
-        run 'retrieve'
+        server.resetExpectations()
 
-        then: "Cached versions are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+        then:
+        succeeds('checkDeps')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                module("group:main:1.0") {
+                    edge("group:projectA:1.+", "group:projectA:1.1")
+                    edge("group:projectB:latest.integration", "group:projectB:1.1")
+                }
+            }
+        }
 
         when: "Server handles requests"
         server.resetExpectations()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA2.expectIvyGet()
-        projectA2.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB2.expectIvyGet()
-        projectB2.expectJarGet()
+        expectGetDynamicRevision(projectA2)
+        expectGetDynamicRevision(projectB2)
 
         and: "DynamicRevisionCache is bypassed"
         executer.withArguments("-PnoDynamicRevisionCache")
-        run 'retrieve'
 
-        then: "New versions are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.2.jar', 'projectB-2.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+        then:
+        succeeds('checkDeps')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                module("group:main:1.0") {
+                    edge("group:projectA:1.+", "group:projectA:1.2")
+                    edge("group:projectB:latest.integration", "group:projectB:2.2")
+                }
+            }
+        }
     }
 
     public void "resolves dynamic version with 2 repositories where first repo results in 404 for directory listing"() {
-        server.start()
         given:
         def repo1 = ivyHttpRepo("repo1")
         def repo2 = ivyHttpRepo("repo2")
         def moduleA = repo2.module('group', 'projectA').publish()
 
         and:
+        useRepository repo1, repo2
         buildFile << """
-            repositories {
-                ivy { url "${repo1.uri}" }
-                ivy { url "${repo2.uri}" }
-            }
-            configurations { compile }
-            dependencies {
-                compile 'group:projectA:1.+'
-            }
-            task listJars << {
-                assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-            }
-            """
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.+'
+}
+"""
 
         when:
         repo1.expectDirectoryListGetMissing("group", "projectA")
-        // TODO - should only list versions once
-        repo1.expectDirectoryListGetMissing("group", "projectA")
-        repo2.expectDirectoryListGet("group", "projectA")
-        moduleA.expectIvyGet()
-        moduleA.expectJarGet()
+        expectGetDynamicRevision(moduleA)
 
         then:
-        succeeds('listJars')
+        checkResolve "group:projectA:1.+": "group:projectA:1.0"
 
         when:
         server.resetExpectations()
         // No extra calls for cached dependencies
+
         then:
-        succeeds('listJars')
+        checkResolve "group:projectA:1.+": "group:projectA:1.0"
     }
 
     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()
@@ -679,33 +537,20 @@ task retrieve(type: Sync) {
         assert ivyModule.jarFile.bytes == mavenModule.artifactFile.bytes
 
         given:
-        buildFile.text = """
-repositories {
-    ivy { url '${ivyRepo.uri}' }
-}
-
+        useRepository ivyRepo
+        buildFile << """
 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'
+        expectGetDynamicRevision(ivyModule)
 
         then:
-        file('build').assertHasDescendants('a-1.1.jar')
+        checkResolve "org.test:a:1+": "org.test:a:1.1"
 
         when:
         buildFile.text = """
@@ -718,23 +563,273 @@ configurations { compile }
 dependencies {
     compile 'org.test:a:[1.0,2.0)'
 }
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
 """
+        resolve.prepare()
 
         and:
-        mavenRepo.expectMetaDataGet("org.test", "a")
+        mavenRepo.getModuleMetaData("org.test", "a").expectGet()
         mavenModule.pom.expectGet()
         mavenModule.artifact.expectHead()
         mavenModule.artifact.sha1.expectGet()
 
+        then:
+        checkResolve "org.test:a:[1.0,2.0)": "org.test:a:1.1"
+    }
+
+    def "can resolve dynamic versions with multiple ivy patterns"() {
+        given:
+        def repo1versions = [:]
+        def repo1 = ivyHttpRepo("ivyRepo1")
+        def repo2versions = [:]
+        def repo2 = ivyHttpRepo("ivyRepo2")
+        repo1versions.A1 = repo1.module('org.test', 'projectA', '1.1').publish()
+        repo1versions.A2 = repo1.module('org.test', 'projectA', '1.2').publish()
+        repo1versions.A3 = repo1.module('org.test', 'projectA', '1.3') // unpublished
+
+        repo2versions.A1 = repo2.module('org.test', 'projectA', '1.1').publish()
+        repo2versions.A3 = repo2.module('org.test', 'projectA', '1.3').publish()
+
+        repo1versions.B1 = repo1.module('org.test', 'projectB', '1.1').withStatus("integration").publish()
+        repo1versions.B2 = repo1.module('org.test', 'projectB', '1.2').withStatus("milestone").publish()
+        repo1versions.B3 = repo1.module('org.test', 'projectB', '1.3') // unpublished
+
+        repo2versions.B1 = repo2.module('org.test', 'projectB', '1.1').withStatus("milestone").publish()
+        repo2versions.B3 = repo2.module('org.test', 'projectB', '1.3').withStatus("integration").publish()
+
         and:
-        run 'retrieve'
+        buildFile << """
+repositories {
+    ivy {
+        url "${repo1.uri}"
+        ivyPattern "${repo2.uri}/[organisation]/[module]/[revision]/ivy-[revision].xml"
+        artifactPattern "${repo2.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+configurations { compile }
+dependencies {
+  compile 'org.test:projectA:1.+'
+  compile 'org.test:projectB:latest.milestone'
+}
+"""
+
+        when:
+        repo1.expectDirectoryListGet("org.test", "projectA")
+        repo1versions.A3.ivy.expectGetMissing()
+        repo1versions.A3.jar.expectGetMissing()
+        expectGetDynamicRevision(repo2versions.A3)
+
+        and:
+        repo1versions.B3.ivy.expectGetMissing()
+        repo2.expectDirectoryListGet("org.test", "projectB")
+        repo2versions.B3.ivy.expectGet()
+        expectGetDynamicRevision(repo1versions.B2)
 
         then:
-        file('build').assertHasDescendants('a-1.1.jar')
+        checkResolve "org.test:projectA:1.+": "org.test:projectA:1.3",
+                     "org.test:projectB:latest.milestone": "org.test:projectB:1.2"
+
+        when: "resolve a second time"
+        server.resetExpectations()
+
+        then:
+        checkResolve "org.test:projectA:1.+": "org.test:projectA:1.3",
+                     "org.test:projectB:latest.milestone": "org.test:projectB:1.2"
+
     }
+
+    def "versions are listed once only per resolve"() {
+        given:
+        useRepository ivyHttpRepo
+        buildFile << """
+configurations { compile }
+dependencies {
+    compile group: "group", name: "main", version: "1.0"
+    compile group: "group", name: "projectA", version: "latest.integration"
+}
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+}
+"""
+
+        when:
+        def projectA0 = ivyHttpRepo.module("group", "projectA", "1.0").publish()
+        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        def mainProject = ivyHttpRepo.module("group", "main", "1.0")
+        mainProject.dependsOn("group", "projectA", "1.+")
+        mainProject.publish()
+
+        and:
+        mainProject.ivy.expectGet()
+        mainProject.jar.expectGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA1.ivy.expectGet()
+        projectA1.jar.expectGet()
+
+        then:
+        succeeds('checkDeps')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                module("group:main:1.0") {
+                    edge("group:projectA:1.+", "group:projectA:1.1")
+                }
+                edge("group:projectA:latest.integration", "group:projectA:1.1")
+            }
+        }
+
+        when:
+        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
+
+        and:
+        server.resetExpectations()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA2.ivy.expectGet()
+        projectA2.jar.expectGet()
+
+        then:
+        succeeds('checkDeps')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                module("group:main:1.0") {
+                    edge("group:projectA:1.+", "group:projectA:1.2")
+                }
+                edge("group:projectA:latest.integration", "group:projectA:1.2")
+            }
+        }
+    }
+
+    @Unroll
+    def "checks remote for dynamic version before failing due to #scenario"() {
+        given:
+        useRepository ivyHttpRepo
+        buildFile << """
+configurations { compile }
+dependencies {
+    compile group: "group", name: "projectA", version: "2.+"
+}
+"""
+
+        when: "no version > 2"
+        ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        if (isMissing) {
+            ivyHttpRepo.expectDirectoryListGetMissing("group", "projectA")
+        } else {
+            ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        }
+
+        then:
+        fails "checkDeps"
+        failure.assertHasCause("Could not find any version that matches group:projectA:2.+.")
+
+        when:
+        def projectA2 = ivyHttpRepo.module("group", "projectA", "2.2").publish()
+
+        and:
+        server.resetExpectations()
+        expectGetDynamicRevision(projectA2)
+
+        then:
+        checkResolve "group:projectA:2.+": "group:projectA:2.2"
+
+        where:
+        scenario | isMissing
+        "module missing in cache listing" | true
+        "no valid version in cache listing" | false
+    }
+
+    @Unroll
+    def "finds best matching version in local and remote repository with #order"() {
+        given:
+        def fileRepo = ivyRepo("fileRepo")
+        def httpModule = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        and:
+        if (localFirst) {
+            useRepository fileRepo, ivyHttpRepo
+        } else {
+            useRepository ivyHttpRepo, fileRepo
+        }
+        buildFile << """
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.+'
+}
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+}
+"""
+        when: "missing from local"
+        expectGetDynamicRevision(httpModule)
+
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.2"
+
+        when: "missing from remote"
+        fileRepo.module('group', 'projectA', '1.1').publish()
+        ivyHttpRepo.expectDirectoryListGetMissing("group", "projectA")
+
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.1"
+
+        when: "present in both"
+        server.resetExpectations()
+        httpModule = ivyHttpRepo.module('group', 'projectA', '1.3').publish()
+        expectGetDynamicRevision(httpModule)
+
+        then:
+        checkResolve "group:projectA:1.+": "group:projectA:1.3"
+
+        where:
+        order          | localFirst
+        "local first"  | true
+        "remote first" | false
+    }
+
+    def "fails with reasonable error message when no cached version list in offline mode"() {
+        given:
+        useRepository ivyHttpRepo
+        buildFile << """
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.+'
+}
+"""
+        when:
+        executer.withArgument "--offline"
+
+        then:
+        fails "checkDeps"
+        failure.assertHasCause "Could not resolve all dependencies for configuration ':compile'."
+        failure.assertHasCause "No cached version listing for group:projectA:1.+ available for offline mode."
+    }
+
+    def checkResolve(Map edges) {
+        assert succeeds('checkDeps')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edges.each {from, to ->
+                    edge(from, to)
+                }
+            }
+        }
+        true
+    }
+
+    def expectGetDynamicRevision(IvyHttpModule module) {
+        module.repository.expectDirectoryListGet(module.organisation, module.module)
+        module.ivy.expectGet()
+        module.jar.expectGet()
+    }
+
+    def useRepository(Repository... repo) {
+        buildFile << """
+repositories {
+"""
+        repo.each {
+            buildFile << "ivy { url '${it.uri}' }\n"
+        }
+        buildFile << """
+}
+"""
+    }
+
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
index b3d4176..1bdce10 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
@@ -16,11 +16,14 @@
 package org.gradle.integtests.resolve.ivy
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import spock.lang.Ignore
+import org.gradle.integtests.resolve.ResolveTestFixture
 import spock.lang.Issue
 
 class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Ignore
+    def setup() {
+        settingsFile << "rootProject.name = 'test' "
+    }
+
     @Issue("GRADLE-2502")
     def "latest.integration selects highest version regardless of status"() {
         given:
@@ -34,46 +37,62 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
   dependencies {
       compile 'org.test:projectA:latest.integration'
   }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
   """
 
+        and:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
         when:
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
-        failureHasCause 'Could not find any version that matches group:group, module:projectA, version:latest.integration.'
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.integration.'
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.0').withNoMetaData().publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.integration", "org.test:projectA:1.0")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.1').withStatus('integration').publish()
         ivyRepo.module('org.test', 'projectA', '1.2').withStatus('integration').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.integration", "org.test:projectA:1.2")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.3').withStatus('release').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.3.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.integration", "org.test:projectA:1.3")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.4').withNoMetaData().publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.4.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.integration", "org.test:projectA:1.4")
+            }
+        }
     }
 
     @Issue("GRADLE-2502")
@@ -89,27 +108,27 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
   dependencies {
       compile 'org.test:projectA:latest.milestone'
   }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
   """
+        and:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
         when:
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:latest.milestone.'
 
         when:
         ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:latest.milestone.'
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:latest.milestone.'
@@ -117,17 +136,36 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
         when:
         ivyRepo.module('org.test', 'projectA', '1.0').withStatus('milestone').publish()
         ivyRepo.module('org.test', 'projectA', '1.1').withStatus('milestone').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.milestone", "org.test:projectA:1.1")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.2').withStatus('release').publish()
-        run 'retrieve'
+        run 'checkDeps'
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.milestone", "org.test:projectA:1.2")
+            }
+        }
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.milestone", "org.test:projectA:1.2")
+            }
+        }
     }
 
     @Issue("GRADLE-2502")
@@ -143,21 +181,20 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
   dependencies {
       compile 'org.test:projectA:latest.release'
   }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
   """
+        and:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
 
         when:
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:latest.release.'
 
         when:
         ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:latest.release.'
@@ -165,7 +202,7 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
         when:
         ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
         ivyRepo.module('org.test', 'projectA', '1.2').withStatus('milestone').publish()
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:latest.release.'
@@ -173,14 +210,29 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
         when:
         ivyRepo.module('org.test', 'projectA', '1.0').withStatus('release').publish()
         ivyRepo.module('org.test', 'projectA', '1.1').withStatus('release').publish()
-        run 'retrieve'
+        run 'checkDeps'
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.release", "org.test:projectA:1.1")
+            }
+        }
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('milestone').publish()
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.release", "org.test:projectA:1.1")
+            }
+        }
     }
 
-    @Ignore
-    @Issue("GRADLE-2502")
+    @Issue(["GRADLE-2502", "GRADLE-2794"])
     def "version selector ending in + selects highest matching version"() {
         given:
         buildFile << """
@@ -193,51 +245,66 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
   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()
 
+        and:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
         when:
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:1.2+'
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:1.2+", "org.test:projectA:1.2")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.1.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:1.2+", "org.test:projectA:1.2.1")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.2.9').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.9.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:1.2+", "org.test:projectA:1.2.9")
+            }
+        }
 
         when:
-        ivyRepo.module('org.test', 'projectA', '1.2.12').withNoMetaData().publish()
-        run 'retrieve'
+        ivyRepo.module('org.test', 'projectA', '1.2.10').withNoMetaData().publish()
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.12.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:1.2+", "org.test:projectA:1.2.10")
+            }
+        }
     }
 
-    @Ignore
     @Issue("GRADLE-2502")
     def "version range selects highest matching version"() {
         given:
@@ -251,48 +318,64 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
   dependencies {
       compile 'org.test:projectA:[1.2,2.0]'
   }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
   """
         and:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
+        and:
         ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
-        ivyRepo.module('org.test', 'projectA', '2.0').publish()
+        ivyRepo.module('org.test', 'projectA', '2.1').publish()
 
         when:
-        runAndFail 'retrieve'
+        runAndFail 'checkDeps'
 
         then:
         failureHasCause 'Could not find any version that matches org.test:projectA:[1.2,2.0]'
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:[1.2,2.0]", "org.test:projectA:1.2")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.2.1.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:[1.2,2.0]", "org.test:projectA:1.2.1")
+            }
+        }
 
         when:
         ivyRepo.module('org.test', 'projectA', '1.3').publish()
-        run 'retrieve'
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.3.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:[1.2,2.0]", "org.test:projectA:1.3")
+            }
+        }
 
         when:
-        ivyRepo.module('org.test', 'projectA', '1.3.12').withNoMetaData().publish()
-        run 'retrieve'
+        ivyRepo.module('org.test', 'projectA', '1.4').withNoMetaData().publish()
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.3.12.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:[1.2,2.0]", "org.test:projectA:1.4")
+            }
+        }
     }
 
     @Issue("GRADLE-2502")
@@ -300,8 +383,6 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
         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 << """
@@ -318,16 +399,32 @@ class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolut
   dependencies {
       compile 'org.test:projectA:latest.milestone'
   }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
   """
+        and:
+        def resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+
+        when:
+        repo1.module('org.test', 'projectA', '1.1').withStatus("milestone").publish()
+        repo2.module('org.test', 'projectA', '1.2').withStatus("integration").publish()
+        run 'checkDeps'
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.milestone", "org.test:projectA:1.1")
+            }
+        }
 
         when:
-        run 'retrieve'
+        repo2.module('org.test', 'projectA', '1.3').withStatus("milestone").publish()
+        run 'checkDeps'
 
         then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
+        resolve.expectGraph {
+            root(":", ":test:") {
+                edge("org.test:projectA:latest.milestone", "org.test:projectA:1.3")
+            }
+        }
     }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
index 5f51151..d1cd07f 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
@@ -33,15 +33,19 @@ class IvyHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest
 repositories {
     ivy { url "${ivyHttpRepo.uri}" }
 }
-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:
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
 
         then:
         succeeds 'listJars'
@@ -64,7 +68,11 @@ task listJars << {
 repositories {
     ivy { url "${ivyHttpRepo.uri}" }
 }
-configurations { compile }
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
+}
 dependencies { compile 'group:projectA:1.2 at jar' }
 task listJars << {
     assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
@@ -73,11 +81,48 @@ task listJars << {
 
 
         when:
-        module.expectIvyGet()
-        module.expectJarGet()
+        module.ivy.expectGet()
+        module.jar.expectGet()
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    def "can resolve and cache artifact-only dependencies with no descriptor from a HTTP repository"() {
+        server.start()
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
+}
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+
+        when:
+        module.ivy.expectGetMissing()
+        module.jar.expectHead()
+        module.jar.expectGet()
 
         then:
-        executer.withArgument("-i")
         succeeds('listJars')
 
         when:
@@ -85,7 +130,6 @@ task listJars << {
         // No extra calls for cached dependencies
 
         then:
-        executer.withArgument("-i")
         succeeds('listJars')
     }
 
@@ -106,7 +150,11 @@ repositories {
     ivy { url "${repo1.uri}" }
     ivy { url "${repo2.uri}" }
 }
-configurations { compile }
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
+}
 dependencies {
     compile 'group:projectA:1.0', 'group:projectB:1.0', 'group:projectC:1.0'
 }
@@ -116,21 +164,21 @@ task listJars << {
 """
 
         when:
-        moduleA.expectIvyGet()
-        moduleA.expectJarGet()
+        moduleA.ivy.expectGet()
+        moduleA.jar.expectGet()
 
         // Handles missing in repo1
-        missingModuleB.expectIvyGetMissing()
-        missingModuleB.expectJarHeadMissing()
+        missingModuleB.ivy.expectGetMissing()
+        missingModuleB.jar.expectHeadMissing()
 
-        moduleB.expectIvyGet()
-        moduleB.expectJarGet()
+        moduleB.ivy.expectGet()
+        moduleB.jar.expectGet()
 
         // Handles from broken url in repo1 (but does not cache)
-        brokenModuleC.expectIvyGetBroken()
+        brokenModuleC.ivy.expectGetBroken()
 
-        moduleC.expectIvyGet()
-        moduleC.expectJarGet()
+        moduleC.ivy.expectGet()
+        moduleC.jar.expectGet()
 
         then:
         succeeds('listJars')
@@ -138,7 +186,7 @@ task listJars << {
         when:
         server.resetExpectations()
         // Will always re-attempt a broken repository
-        brokenModuleC.expectIvyHeadBroken()
+        brokenModuleC.ivy.expectHeadBroken()
         // No extra calls for cached dependencies
 
         then:
@@ -161,7 +209,11 @@ repositories {
         ivyPattern "http://localhost:${server.port}/third/[module]/[revision]/ivy.xml"
     }
 }
-configurations { compile }
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
+}
 dependencies {
     compile 'group:projectA:1.2'
 }
@@ -226,7 +278,9 @@ task retrieve(type: Sync) {
         server.start()
         buildFile << """
 configurations {
-    compile
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
 }
 dependencies {
     repositories {
@@ -253,10 +307,10 @@ task retrieve(type: Sync) {
                 .publish()
 
         when:
-        moduleA.expectIvyGet()
-        moduleA.expectJarGet()
-        moduleB15.expectIvyGet()
-        moduleB15.expectJarGet()
+        moduleA.ivy.expectGet()
+        moduleA.jar.expectGet()
+        moduleB15.ivy.expectGet()
+        moduleB15.jar.expectGet()
         run 'retrieve'
 
         then:
@@ -265,8 +319,8 @@ task retrieve(type: Sync) {
         when:
         server.resetExpectations()
         ivyHttpRepo.expectDirectoryListGet('org', 'projectB')
-        moduleB16.expectIvyGet()
-        moduleB16.expectJarGet()
+        moduleB16.ivy.expectGet()
+        moduleB16.jar.expectGet()
         executer.withArguments("-PuseDynamicResolve=true")
         run 'retrieve'
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyJvmLibraryArtifactResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyJvmLibraryArtifactResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..907163e
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyJvmLibraryArtifactResolutionIntegrationTest.groovy
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.api.artifacts.resolution.JvmLibraryJavadocArtifact
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.resolve.JvmLibraryArtifactResolveTestFixture
+import org.gradle.test.fixtures.ivy.IvyRepository
+import spock.lang.Unroll
+
+// TODO:DAZ Test can resolve multiple source/javadoc artifacts declared in 'sources'/'javadoc' configuration
+class IvyJvmLibraryArtifactResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    def fileRepo = ivyRepo
+    def httpRepo = ivyHttpRepo
+    def module = httpRepo.module("some.group", "some-artifact", "1.0")
+    JvmLibraryArtifactResolveTestFixture fixture
+
+    def setup() {
+        server.start()
+        initBuild(httpRepo)
+
+        fixture = new JvmLibraryArtifactResolveTestFixture(buildFile)
+
+        publishModule()
+    }
+
+    def initBuild(IvyRepository repo, String module = "some.group:some-artifact:1.0") {
+        buildFile.text = """
+repositories {
+    ivy { url '$repo.uri' }
+}
+configurations { compile }
+dependencies {
+    compile "${module}"
+}
+"""
+    }
+
+    def "resolve sources artifacts"() {
+        fixture.requestingTypes(JvmLibrarySourcesArtifact)
+                .expectSourceArtifact("my-sources")
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolve javadoc artifacts"() {
+        fixture.requestingTypes(JvmLibraryJavadocArtifact)
+                .expectJavadocArtifact("my-javadoc")
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-javadoc").expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolve all artifacts"() {
+        fixture.requestingTypes()
+                .expectSourceArtifact("my-sources")
+                .expectJavadocArtifact("my-javadoc")
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGet()
+        module.getArtifact(classifier: "my-javadoc").expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    @Unroll
+    def "fetches missing artifacts for module #condition"() {
+        fixture.requestingTypes(JvmLibrarySourcesArtifact)
+                .expectSourceArtifactNotFound("my-sources")
+                .prepare()
+        buildFile << """
+dependencies {
+    components {
+        eachComponent { ComponentMetadataDetails details ->
+            details.changing = true
+        }
+    }
+}
+
+if (project.hasProperty('nocache')) {
+    configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+"""
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGetMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+
+        when:
+        module.publishWithChangedContent()
+        fixture.clearExpectations()
+                .expectSourceArtifact("my-sources")
+                .createVerifyTask("verifyRefresh")
+
+        and:
+        server.resetExpectations()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGet()
+
+        then:
+        executer.withArgument(execArg)
+        succeeds("verifyRefresh")
+
+        where:
+        condition                     | execArg
+        "with --refresh-dependencies" | "--refresh-dependencies"
+        "when ivy descriptor changes" | "-Pnocache"
+    }
+
+    @Unroll
+    def "updates artifacts for module #condition"() {
+        buildFile << """
+dependencies {
+    components {
+        eachComponent { ComponentMetadataDetails details ->
+            details.changing = true
+        }
+    }
+}
+
+if (project.hasProperty('nocache')) {
+    configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+"""
+
+        final sourceArtifact = module.getArtifact(classifier: "my-sources")
+        fixture.requestingTypes(JvmLibrarySourcesArtifact)
+                .expectSourceArtifact("my-sources")
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        sourceArtifact.expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+
+        when:
+        def snapshot = file("sources/some-artifact-1.0-my-sources.jar").snapshot()
+        module.publishWithChangedContent()
+
+        and:
+        server.resetExpectations()
+        module.ivy.expectHead()
+        module.ivy.sha1.expectGet()
+        module.ivy.expectGet()
+        sourceArtifact.expectHead()
+        sourceArtifact.sha1.expectGet()
+        sourceArtifact.expectGet()
+
+        then:
+        executer.withArgument(execArg)
+        succeeds("verify")
+        file("sources/some-artifact-1.0-my-sources.jar").assertHasChangedSince(snapshot)
+
+        where:
+        condition                     | execArg
+        "with --refresh-dependencies" | "--refresh-dependencies"
+        "when ivy descriptor changes" | "-Pnocache"
+    }
+
+    def "reports failure to resolve artifacts of non-existing component"() {
+        fixture.expectComponentNotFound().prepare()
+
+        when:
+        module.ivy.expectGetMissing()
+        module.jar.expectHeadMissing()
+
+        then:
+        succeeds("verify")
+    }
+
+    def "reports failure to resolve missing artifacts"() {
+        fixture.expectSourceArtifactNotFound("my-sources")
+                .expectJavadocArtifactNotFound("my-javadoc")
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGetMissing()
+        module.getArtifact(classifier: "my-javadoc").expectGetMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolves when some artifacts are missing"() {
+        fixture.expectSourceArtifact("my-sources")
+                .expectJavadocArtifactNotFound("my-javadoc")
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGet()
+        module.getArtifact(classifier: "my-javadoc").expectGetMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolves when some artifacts are broken"() {
+        fixture.expectSourceArtifact("my-sources")
+                .expectJavadocArtifactFailure(new ArtifactResolveException("Could not download artifact 'some.group:some-artifact:1.0:some-artifact-my-javadoc.jar'"))
+                .prepare()
+
+        when:
+        module.ivy.expectGet()
+        module.getArtifact(classifier: "my-sources").expectGet()
+        module.getArtifact(classifier: "my-javadoc").expectGetBroken()
+
+        then:
+        succeeds("verify")
+
+        when:
+        fixture.clearExpectations()
+                .expectSourceArtifact("my-sources")
+                .expectJavadocArtifact("my-javadoc")
+                .createVerifyTask("verifyFixed")
+
+        and:
+        server.resetExpectations()
+        // Only the broken artifact is not cached
+        module.getArtifact(classifier: "my-javadoc").expectGet()
+
+        then:
+        succeeds("verifyFixed")
+    }
+
+    def "resolve and does not cache artifacts from local repository"() {
+        initBuild(fileRepo)
+
+        fixture.requestingTypes()
+                .expectSourceArtifact("my-sources")
+                .expectJavadocArtifact("my-javadoc")
+                .prepare()
+
+        when:
+        succeeds("verify")
+
+        and:
+        def snapshot = file("sources/some-artifact-1.0-my-sources.jar").snapshot()
+
+        and:
+        module.publishWithChangedContent()
+
+        then:
+        succeeds("verify")
+        file("sources/some-artifact-1.0-my-sources.jar").assertHasChangedSince(snapshot)
+    }
+
+    def "can resolve artifacts with maven scheme from ivy repository"() {
+        initBuild(httpRepo, "some.group:some-artifact:1.1")
+
+        // Published with no configurations, and a source artifact only
+        def moduleWithMavenScheme = httpRepo.module("some.group", "some-artifact", "1.1")
+        moduleWithMavenScheme.artifact(classifier: "sources")
+        moduleWithMavenScheme.publish()
+
+
+        fixture.withComponentVersion("some.group", "some-artifact", "1.1")
+                .requestingTypes(JvmLibrarySourcesArtifact, JvmLibraryJavadocArtifact)
+                .expectSourceArtifact("sources")
+                .prepare()
+
+        when:
+        moduleWithMavenScheme.ivy.expectGet()
+        moduleWithMavenScheme.getArtifact(classifier: "sources").expectHead()
+        moduleWithMavenScheme.getArtifact(classifier: "sources").expectGet()
+        moduleWithMavenScheme.getArtifact(classifier: "javadoc").expectHeadMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def checkArtifactsResolvedAndCached() {
+        assert succeeds("verify")
+        server.resetExpectations()
+        assert succeeds("verify")
+        true
+    }
+
+    private publishModule() {
+        module.configuration("sources")
+        module.configuration("javadoc")
+        // use uncommon classifiers that are different from those used by maven, 
+        // in order to prove that artifact names don't matter
+        module.artifact(type: "source", classifier: "my-sources", ext: "jar", conf: "sources")
+        module.artifact(type: "javadoc", classifier: "my-javadoc", ext: "jar", conf: "javadoc")
+        module.publish()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleResolveIntegrationTest.groovy
new file mode 100644
index 0000000..54adec2
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleResolveIntegrationTest.groovy
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Unroll
+
+class IvyModuleResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "wildcard on LHS of configuration mapping includes all public configurations of target module"() {
+        given:
+        buildFile << """
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy { url "${ivyRepo.uri}" }
+    }
+    compile 'ivy.configuration:projectA:1.2'
+}
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+        when: "projectA uses a wildcard configuration mapping for dependency on projectB"
+        def moduleA = ivyRepo.module('ivy.configuration', 'projectA', '1.2')
+                .configuration('parent')
+                .artifact()
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectB', revision: '1.5', conf: 'runtime->*')
+                .publish()
+
+        ivyRepo.module('ivy.configuration', 'projectB', '1.5')
+                .configuration('child')
+                .configuration('private', visibility: 'private')
+                .artifact()
+                .artifact([name: 'projectB', conf: 'runtime'])
+                .artifact([name: 'projectB-child', conf: 'child'])
+                .artifact([name: 'projectB-private', conf: 'private'])
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectC', revision: '1.7', conf: 'child->*')
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectD', revision: 'broken', conf: 'private->*')
+                .publish()
+
+        ivyRepo.module('ivy.configuration', 'projectC', '1.7')
+                .artifact()
+                .publish()
+
+        and:
+        succeeds 'retrieve'
+
+        then: "artifacts and dependencies from all configurations of projectB are included"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar', 'projectB-child-1.5.jar', 'projectC-1.7.jar')
+
+        when: "projectB-1.5 is replaced by conflict resolution with projectB-1.6 that has a different set of configurations"
+
+        ivyRepo.module('ivy.configuration', 'projectB', '1.6')
+                .configuration('other')
+                .artifact([name: 'projectB-other', conf: 'other'])
+                .publish()
+
+        ivyRepo.module('ivy.configuration', 'projectD', '1.0')
+                .dependsOn('ivy.configuration', 'projectB', '1.6')
+                .publish()
+
+        moduleA.dependsOn('ivy.configuration', 'projectD', '1.0').publish()
+
+        and:
+        succeeds 'retrieve'
+
+        then: "we resolve artifacts from projectB-1.6 only"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-other-1.6.jar', 'projectD-1.0.jar')
+    }
+
+    @Unroll
+    def "correctly handles configuration mapping rule '#rule'"() {
+        given:
+        server.start()
+
+        buildFile << """
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy { url "${ivyHttpRepo.uri}" }
+    }
+    compile group: 'ivy.configuration', name: 'projectA', version: '1.2', configuration: 'a'
+}
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+        def projectA = ivyHttpRepo.module('ivy.configuration', 'projectA', '1.2')
+                .configuration("parent")
+                .configuration("a", extendsFrom: ["parent"])
+                .configuration("b")
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectB', revision: '1.5', conf: rule)
+                .publish()
+
+        def projectB = ivyHttpRepo.module('ivy.configuration', 'projectB', '1.5')
+                .configuration('a')
+                .configuration('b')
+                .configuration('c')
+                .configuration('d', visibility: 'private')
+                .artifact([name: 'projectB-a', conf: 'a'])
+                .artifact([name: 'projectB-b', conf: 'b'])
+                .artifact([name: 'projectB-c', conf: 'c'])
+                .artifact([name: 'projectB-d', conf: 'd'])
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectC', revision: '1.7', conf: 'a->default')
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectD', revision: '1.7', conf: 'b->default')
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectE', revision: '1.7', conf: 'd->default')
+                .publish()
+
+        def projectC = ivyHttpRepo.module('ivy.configuration', 'projectC', '1.7').publish()
+        def projectD = ivyHttpRepo.module('ivy.configuration', 'projectD', '1.7').publish()
+
+        projectA.allowAll()
+        projectB.allowAll()
+        projectC.allowAll()
+        projectD.allowAll()
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants(* (['projectA-1.2.jar'] + jars))
+
+        when:
+        server.resetExpectations()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants(* (['projectA-1.2.jar'] + jars))
+
+        where:
+        rule                    | jars
+        "a"                     | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "a->b"                  | ["projectB-b-1.5.jar", "projectD-1.7.jar"]
+        "a,b->b"                | ["projectB-b-1.5.jar", "projectD-1.7.jar"]
+        "parent->a"             | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "a,parent->a"           | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "a->a,b"                | ["projectB-a-1.5.jar", "projectB-b-1.5.jar", "projectC-1.7.jar", "projectD-1.7.jar"]
+        "a;a->b"                | ["projectB-a-1.5.jar", "projectB-b-1.5.jar", "projectC-1.7.jar", "projectD-1.7.jar"]
+        "*->a"                  | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "*->*"                  | ["projectB-a-1.5.jar", "projectB-b-1.5.jar", "projectB-c-1.5.jar", "projectC-1.7.jar", "projectD-1.7.jar"]
+        "*->@"                  | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "a,b->@"                | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "runtime->unknown;%->@" | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "a->a;%->b"             | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "*,!b->b"               | ["projectB-b-1.5.jar", "projectD-1.7.jar"]
+        "b"                     | []
+        "*,!a->a"               | []
+        "a->#"                  | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "parent->#"             | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "*->#"                  | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "*->unknown(a)"         | ["projectB-a-1.5.jar", "projectC-1.7.jar"]
+        "a->unknown(*)"         | ["projectB-a-1.5.jar", "projectB-b-1.5.jar", "projectB-c-1.5.jar", "projectC-1.7.jar", "projectD-1.7.jar"]
+        "a->a(*),b(*);b->b(*)"  | ["projectB-a-1.5.jar", "projectB-b-1.5.jar", "projectC-1.7.jar", "projectD-1.7.jar"]
+    }
+
+    def "prefers revConstraint over rev when dynamic resolve mode is used"() {
+        given:
+        buildFile << """
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy {
+            url "${ivyRepo.uri}"
+            resolve.dynamicMode = project.hasProperty('useDynamicResolve')
+        }
+    }
+    compile 'org:projectA:1.2'
+}
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+        ivyRepo.module('org', 'projectA', '1.2')
+                .dependsOn(organisation: 'org', module: 'projectB', revision: '1.5', revConstraint: '1.6')
+                .dependsOn(organisation: 'org', module: 'projectC', revision: 'alpha-12')
+                .publish()
+
+        ivyRepo.module('org', 'projectB', '1.5')
+                .publish()
+
+        ivyRepo.module('org', 'projectB', '1.6')
+                .publish()
+
+        ivyRepo.module('org', 'projectC', 'alpha-12')
+                .publish()
+
+        when:
+        executer.withArguments("-PuseDynamicResolve=true")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar', 'projectC-alpha-12.jar')
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar', 'projectC-alpha-12.jar')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
index 5197a3b..bc9fb8d 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
@@ -22,6 +22,7 @@ class IvyResolveIntegrationTest extends AbstractDependencyResolutionTest {
         given:
         ivyRepo.module("org.gradle", "test", "1.45")
                 .dependsOn("org.gradle", "other", "preview-1")
+                .artifact()
                 .artifact(classifier: "classifier")
                 .artifact(name: "test-extra")
                 .publish()
@@ -29,7 +30,13 @@ class IvyResolveIntegrationTest extends AbstractDependencyResolutionTest {
         ivyRepo.module("org.gradle", "other", "preview-1").publish()
 
         and:
+        settingsFile << """
+rootProject.name = 'testproject'
+"""
+
         buildFile << """
+group = 'org.gradle'
+version = '1.0'
 repositories { ivy { url "${ivyRepo.uri}" } }
 configurations { compile }
 dependencies {
@@ -38,6 +45,34 @@ dependencies {
 
 task check << {
     assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'test-1.45-classifier.jar', 'test-extra-1.45.jar', 'other-preview-1.jar']
+    def result = configurations.compile.incoming.resolutionResult
+
+    // Check root component
+    def rootId = result.root.id
+    assert rootId instanceof ProjectComponentIdentifier
+    def rootPublishedAs = result.root.moduleVersion
+    assert rootPublishedAs.group == 'org.gradle'
+    assert rootPublishedAs.name == 'testproject'
+    assert rootPublishedAs.version == '1.0'
+
+    // Check external module components
+    def externalComponents = result.root.dependencies.selected.findAll { it.id instanceof ModuleComponentIdentifier }
+    assert externalComponents.size() == 1
+    def selectedExternalComponent = externalComponents[0]
+    assert selectedExternalComponent.id.group == 'org.gradle'
+    assert selectedExternalComponent.id.module == 'test'
+    assert selectedExternalComponent.id.version == '1.45'
+    assert selectedExternalComponent.moduleVersion.group == 'org.gradle'
+    assert selectedExternalComponent.moduleVersion.name == 'test'
+    assert selectedExternalComponent.moduleVersion.version == '1.45'
+
+    // Check external dependencies
+    def externalDependencies = result.root.dependencies.requested.findAll { it instanceof ModuleComponentSelector }
+    assert externalDependencies.size() == 1
+    def requestedExternalDependency = externalDependencies[0]
+    assert requestedExternalDependency.group == 'org.gradle'
+    assert requestedExternalDependency.module == 'test'
+    assert requestedExternalDependency.version == '1.45'
 }
 """
 
@@ -71,6 +106,28 @@ task check << {
         succeeds "check"
     }
 
+    def "dependency that references a classifier can resolve module with no metadata"() {
+        given:
+        def ivyModule = ivyRepo.module("org.gradle", "test", "1.45").withNoMetaData().artifact(classifier: "classifier").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${ivyRepo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45:classifier"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar']
+}
+"""
+
+        expect:
+        ivyModule.jarFile.assertDoesNotExist()
+        succeeds "check"
+    }
+
     def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
         given:
         ivyRepo.module("org.gradle", "test", "1.45")
@@ -137,111 +194,4 @@ task check << {
         expect:
         succeeds "check"
     }
-
-    def "correctly handles wildcard configuration mapping in transitive dependencies"() {
-        given:
-        buildFile << """
-configurations {
-    compile
-}
-dependencies {
-    repositories {
-        ivy { url "${ivyRepo.uri}" }
-    }
-    compile 'ivy.configuration:projectA:1.2'
-}
-task retrieve(type: Sync) {
-  from configurations.compile
-  into 'libs'
-}
-"""
-        when: "projectA uses a wildcard configuration mapping for dependency on projectB"
-        def moduleA = ivyRepo.module('ivy.configuration', 'projectA', '1.2')
-                .configuration('parent')
-                .artifact([:])
-                .dependsOn(organisation: 'ivy.configuration', module: 'projectB', revision: '1.5', conf: 'runtime->*')
-                .publish()
-
-        ivyRepo.module('ivy.configuration', 'projectB', '1.5')
-                .configuration('child')
-                .artifact([name: 'projectB', conf: 'runtime'])
-                .artifact([name: 'projectB-child', conf: 'child'])
-                .dependsOn(organisation: 'ivy.configuration', module: 'projectC', revision: '1.7', conf: 'child->*')
-                .publish()
-
-        ivyRepo.module('ivy.configuration', 'projectC', '1.7').artifact([:]).publish()
-
-        and:
-        succeeds 'retrieve'
-
-        then: "artifacts and dependencies from all configurations of projectB are included"
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar', 'projectB-child-1.5.jar', 'projectC-1.7.jar')
-
-        when: "projectB-1.5 is replaced by conflict resolution with projectB-1.6 that has a different set of configurations"
-
-        ivyRepo.module('ivy.configuration', 'projectB', '1.6')
-                .configuration('other')
-                .artifact([name: 'projectB-other', conf: 'other'])
-                .publish()
-
-        ivyRepo.module('ivy.configuration', 'projectD', '1.0')
-                .dependsOn('ivy.configuration', 'projectB', '1.6')
-                .publish()
-
-        moduleA.dependsOn('ivy.configuration', 'projectD', '1.0').publish()
-
-        and:
-        succeeds 'retrieve'
-
-        then: "we resolve artifacts from projectB-1.6 only"
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar', 'projectB-other-1.6.jar', 'projectD-1.0.jar')
-    }
-
-    def "prefers revConstraint over rev when dynamic resolve mode is used"() {
-        given:
-        buildFile << """
-configurations {
-    compile
-}
-dependencies {
-    repositories {
-        ivy {
-            url "${ivyRepo.uri}"
-            resolve.dynamicMode = project.hasProperty('useDynamicResolve')
-        }
-    }
-    compile 'org:projectA:1.2'
-}
-task retrieve(type: Sync) {
-  from configurations.compile
-  into 'libs'
-}
-"""
-        ivyRepo.module('org', 'projectA', '1.2')
-                .dependsOn(organisation: 'org', module: 'projectB', revision: '1.5', revConstraint: '1.6')
-                .dependsOn(organisation: 'org', module: 'projectC', revision: 'alpha-12')
-                .publish()
-
-        ivyRepo.module('org', 'projectB', '1.5')
-                .publish()
-
-        ivyRepo.module('org', 'projectB', '1.6')
-                .publish()
-
-        ivyRepo.module('org', 'projectC', 'alpha-12')
-                .publish()
-
-        when:
-        executer.withArguments("-PuseDynamicResolve=true")
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar', 'projectC-alpha-12.jar')
-
-        when:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar', 'projectC-alpha-12.jar')
-    }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
index c871a5d..c720ed1 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
@@ -19,13 +19,9 @@ import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import spock.lang.Issue
 
 class BadPomFileResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
     @Issue("http://issues.gradle.org/browse/GRADLE-1005")
     def "can handle self referencing dependency"() {
         given:
-        file("settings.gradle") << "include 'client'"
-
-        and:
         mavenRepo().module('group', 'artifact', '1.0').dependsOn('group', 'artifact', '1.0').publish()
 
         and:
@@ -43,4 +39,148 @@ class BadPomFileResolveIntegrationTest extends AbstractDependencyResolutionTest
         expect:
         succeeds ":libs"
     }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2861")
+    def "can handle pom with placeholders in dependency management"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module('group', 'parent', '1.0').publish()
+        parent.pomFile.text = parent.pomFile.text.replace("</project>", """
+<dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>\${some.group}</groupId>
+            <artifactId>\${some.artifact}</artifactId>
+            <version>\${some.version}</version>
+        </dependency>
+    </dependencies>
+</dependencyManagement>
+</project>
+""")
+
+        def module = mavenHttpRepo.module('group', 'artifact', '1.0').parent('group', 'parent', '1.0').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                maven { url "${mavenHttpRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile "group:artifact:1.0"
+            }
+            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar'] }
+        """
+
+        and:
+        parent.pom.expectGet()
+        module.pom.expectGet()
+        module.artifact.expectGet()
+
+        expect:
+        // have to run twice to trigger the failure, to parse the descriptor from the cache
+        succeeds ":libs"
+        succeeds ":libs"
+    }
+
+    public void "reports POM that cannot be parsed"() {
+        server.start()
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task showBroken << { println configurations.compile.files }
+"""
+
+        and:
+        def module = mavenHttpRepo.module('group', 'projectA', '1.2').publish()
+        module.pomFile.text = "<project/>"
+
+        when:
+        module.pom.expectGet()
+
+        then:
+        fails "showBroken"
+        failure.assertResolutionFailure(":compile")
+            .assertHasCause("Could not parse POM ${module.pom.uri}")
+            .assertHasCause("null name not allowed")
+    }
+
+    def "reports missing parent POM"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module("org", "parent", "1.0")
+
+        def child = mavenHttpRepo.module("org", "child", "1.0")
+        child.parent("org", "parent", "1.0")
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task showBroken << { println configurations.compile.files }
+"""
+
+        when:
+        child.pom.expectGet()
+        parent.pom.expectGetMissing()
+
+        // Will always check for a default artifact with a module with 'pom' packaging
+        // TODO - should not make this request
+        parent.artifact.expectHeadMissing()
+
+        and:
+        fails 'showBroken'
+
+        then:
+        failure.assertResolutionFailure(':compile')
+                .assertHasCause("Could not parse POM ${child.pom.uri}")
+                .assertHasCause("Could not find any version that matches org:parent:1.0.")
+    }
+
+    def "reports parent POM that cannot be parsed"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module("org", "parent", "1.0").publish()
+        parent.pomFile.text = "<project/>"
+
+        def child = mavenHttpRepo.module("org", "child", "1.0")
+        child.parent("org", "parent", "1.0")
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task showBroken << { println configurations.compile.files }
+"""
+
+        when:
+        child.pom.expectGet()
+        parent.pom.expectGet()
+
+        and:
+        fails 'showBroken'
+
+        then:
+        failure.assertResolutionFailure(":compile")
+            .assertHasCause("Could not parse POM ${child.pom.uri}")
+            .assertHasCause("Could not parse POM ${parent.pom.uri}")
+            .assertHasCause("null name not allowed")
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy
index 9d9470d..6e2ff2e 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy
@@ -49,6 +49,7 @@ task check << {
         module.artifact.expectGet()
         module.artifact.sha1.expectGetMissing()
         module.artifact.md5.expectGet()
+        executer.withDeprecationChecksDisabled()
 
         expect:
         succeeds 'check'
@@ -68,6 +69,7 @@ task check << {
         module.artifact.expectGet()
         // TODO - shouldn't get checksum twice
         module.artifact.sha1.expectGet()
+        executer.withDeprecationChecksDisabled()
 
         then:
         executer.withArguments("--refresh-dependencies")
@@ -102,12 +104,17 @@ task check << {
         module.pom.sha1.expectGet()
         module.artifact.expectGet()
         module.artifact.sha1.expectGet()
+
         and:
         module.artifact.sha1.file.text = '1234'
+
+        and:
+        executer.withDeprecationChecksDisabled()
+
         expect:
         fails 'check'
-        failureHasCause("Could not download artifact 'group:module:1.2 at jar'")
-        failureHasCause("invalid sha1: expected=1234 computed=5b253435f362abf1a12197966e332df7d2b153f5")
+        failureHasCause("Could not download artifact 'group:module:1.2:module.jar'")
+        failureHasCause("invalid sha1: expected=1234 computed=2ee22701c9bd9af023ed20917897da5ce77336bc")
     }
 
     def "can configure resolver to fail when descriptor is not present"() {
@@ -137,6 +144,9 @@ task check << {
         and:
         module.pom.expectGetMissing()
 
+        and:
+        executer.withDeprecationChecksDisabled()
+
         expect:
         fails 'check'
         failureHasCause("Could not find group:module:1.2.")
@@ -171,6 +181,9 @@ task check << {
         module.artifact.expectHead()
         module.artifact.expectGet()
 
+        and:
+        executer.withDeprecationChecksDisabled()
+
         expect:
         succeeds "check"
     }
@@ -203,8 +216,10 @@ task check << {
         module.pom.expectGet()
         module.artifact.expectGet()
 
-        expect:
+        and:
         executer.withDeprecationChecksDisabled()
+
+        expect:
         succeeds "check"
     }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBrokenRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBrokenRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..024afa0
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBrokenRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class MavenBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "reports and recovers from failed POM download"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module('group', 'projectA', '1.3').publish()
+
+        buildFile << """
+repositories {
+    maven {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { broken }
+dependencies {
+    broken 'group:projectA:1.3'
+}
+task showBroken << { println configurations.broken.files }
+"""
+
+        when:
+        module.pom.expectGetBroken()
+        fails("showBroken")
+
+        then:
+        failure
+            .assertHasDescription('Execution failed for task \':showBroken\'.')
+            .assertResolutionFailure(':broken')
+            .assertHasCause('Could not resolve group:projectA:1.3.')
+            .assertHasCause("Could not GET '${module.pom.uri}'. Received status code 500 from server: broken")
+
+        when:
+        server.resetExpectations()
+        module.pom.expectGet()
+        module.artifact.expectGet()
+
+        then:
+        succeeds("showBroken")
+    }
+
+    public void "reports and recovers from failed artifact download"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = mavenHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        when:
+        module.pom.expectGet()
+        module.artifact.expectGetBroken()
+
+        then:
+        fails "retrieve"
+        failure.assertHasCause("Could not download artifact 'group:projectA:1.2:projectA.jar'")
+        failure.assertHasCause("Could not GET '${module.artifact.uri}'. Received status code 500 from server: broken")
+
+        when:
+        server.resetExpectations()
+        module.artifact.expectGet()
+
+        then:
+        succeeds "retrieve"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesChangingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesChangingModulesIntegrationTest.groovy
new file mode 100644
index 0000000..75e0535
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesChangingModulesIntegrationTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ComponentMetadataRulesChangingModulesIntegrationTest
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+
+class MavenComponentMetadataRulesChangingModulesIntegrationTest extends ComponentMetadataRulesChangingModulesIntegrationTest {
+    MavenHttpRepository getRepo() {
+        mavenHttpRepo
+    }
+
+    String getRepoDeclaration() {
+"""
+repositories {
+    maven {
+        url "$repo.uri"
+    }
+}
+"""
+    }
+
+    def setup() {
+        moduleA.rootMetaData.allowGetOrHead()
+    }
+
+    def "snapshot dependencies have changing flag initialized to true"() {
+        def moduleB = repo.module("org.test", "moduleB", "1.0-SNAPSHOT").publish()
+        moduleB.allowAll()
+
+        buildFile <<
+"""
+$repoDeclaration
+configurations {
+    modules
+}
+dependencies {
+    modules "org.test:moduleB:1.0-SNAPSHOT"
+    components {
+        eachComponent { details ->
+            file(details.id.name).text = details.changing
+        }
+    }
+}
+task resolve << {
+    configurations.modules.files
+}
+"""
+
+        when:
+        run("resolve")
+
+        then:
+        file("moduleB").text == "true"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesIntegrationTest.groovy
new file mode 100644
index 0000000..cd8dac5
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesIntegrationTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ComponentMetadataRulesIntegrationTest
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+
+class MavenComponentMetadataRulesIntegrationTest extends ComponentMetadataRulesIntegrationTest {
+    @Override
+    MavenHttpRepository getRepo() {
+        mavenHttpRepo
+    }
+
+    @Override
+    String getRepoDeclaration() {
+"""
+repositories {
+    maven {
+        url "$repo.uri"
+    }
+}
+"""
+    }
+
+    @Override
+    String getDefaultStatus() {
+        "release"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesStatusIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesStatusIntegrationTest.groovy
new file mode 100644
index 0000000..7a8c9e4
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenComponentMetadataRulesStatusIntegrationTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ComponentMetadataRulesStatusIntegrationTest
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+
+class MavenComponentMetadataRulesStatusIntegrationTest extends ComponentMetadataRulesStatusIntegrationTest {
+    MavenHttpRepository getRepo() {
+        mavenHttpRepo
+    }
+
+    String getRepoDeclaration() {
+"""
+repositories {
+    maven {
+        url "$repo.uri"
+    }
+}
+"""
+    }
+
+    def "snapshot and release versions have correct status"() {
+        given:
+        repo.module('group1', 'projectA', '1.0').publish().allowAll()
+        repo.module('group2', 'projectB', '2.0-SNAPSHOT').publish().allowAll()
+
+        and:
+        buildFile.text =
+"""
+$repoDeclaration
+configurations { compile }
+dependencies {
+    compile 'group1:projectA:1.0'
+    compile 'group2:projectB:2.0-SNAPSHOT'
+    components {
+        eachComponent {
+            assert it.status == it.id.name == "projectA" ? "release" : "integration"
+            assert it.statusScheme == ['integration', 'milestone', 'release']
+        }
+    }
+}
+
+task resolve << {
+    configurations.compile.resolve()
+}
+"""
+
+        expect:
+        succeeds "resolve"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenCustomPackagingResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenCustomPackagingResolveIntegrationTest.groovy
new file mode 100644
index 0000000..642dc60
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenCustomPackagingResolveIntegrationTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Issue
+
+class MavenCustomPackagingResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2984")
+    def "can resolve dependency with custom packaging"() {
+        given:
+        executer.withDeprecationChecksDisabled()
+
+        def extension = "aar"
+        m2Installation.mavenRepo().module("local", "local", "1.0").hasType(extension).hasPackaging(extension).publish()
+        mavenHttpRepo.module("remote", "remote", "1.0").with {
+            allowAll()
+            hasType(extension).hasPackaging(extension).publish()
+        }
+
+        // Not publishing, just allowing the HTTP requests to 404
+        mavenHttpRepo.module("local", "local", "1.0").allowAll()
+
+        server.start()
+
+        when:
+        buildScript """
+            configurations {
+                local
+                remote
+            }
+            repositories {
+                mavenLocal() // there's a different code path for mavenLocal() so test it explicitly
+                maven { url "$mavenHttpRepo.uri" }
+            }
+            dependencies {
+                local "local:local:1.0"
+                remote "remote:remote:1.0"
+            }
+
+            task showDeps {
+                doLast {
+                    println configurations.local.files // trigger resolve
+                    println configurations.remote.files // trigger resolve
+                }
+            }
+        """
+
+        then:
+        succeeds("showDeps")
+    }
+
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
index 0cc4101..f1573d9 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
@@ -16,6 +16,8 @@
 package org.gradle.integtests.resolve.maven
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 
 class MavenDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
     def "dependency includes main artifact and runtime dependencies of referenced module"() {
@@ -27,7 +29,13 @@ class MavenDependencyResolveIntegrationTest extends AbstractDependencyResolution
         mavenRepo().module("org.gradle", "other", "preview-1").publish()
 
         and:
+        settingsFile << """
+rootProject.name = 'testproject'
+"""
+
         buildFile << """
+group = 'org.gradle'
+version = '1.0'
 repositories { maven { url "${mavenRepo().uri}" } }
 configurations { compile }
 dependencies {
@@ -36,6 +44,34 @@ dependencies {
 
 task check << {
     assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+    def result = configurations.compile.incoming.resolutionResult
+
+    // Check root component
+    def rootId = result.root.id
+    assert rootId instanceof ProjectComponentIdentifier
+    def rootPublishedAs = result.root.moduleVersion
+    assert rootPublishedAs.group == 'org.gradle'
+    assert rootPublishedAs.name == 'testproject'
+    assert rootPublishedAs.version == '1.0'
+
+    // Check external module components
+    def externalComponents = result.root.dependencies.selected.findAll { it.id instanceof ModuleComponentIdentifier }
+    assert externalComponents.size() == 1
+    def selectedExternalComponent = externalComponents[0]
+    assert selectedExternalComponent.id.group == 'org.gradle'
+    assert selectedExternalComponent.id.module == 'test'
+    assert selectedExternalComponent.id.version == '1.45'
+    assert selectedExternalComponent.moduleVersion.group == 'org.gradle'
+    assert selectedExternalComponent.moduleVersion.name == 'test'
+    assert selectedExternalComponent.moduleVersion.version == '1.45'
+
+    // Check external dependencies
+    def externalDependencies = result.root.dependencies.requested.findAll { it instanceof ModuleComponentSelector }
+    assert externalDependencies.size() == 1
+    def requestedExternalDependency = externalDependencies[0]
+    assert requestedExternalDependency.group == 'org.gradle'
+    assert requestedExternalDependency.module == 'test'
+    assert requestedExternalDependency.version == '1.45'
 }
 """
 
@@ -100,6 +136,7 @@ task check << {
         succeeds "check"
     }
 
+    @Requires(TestPrecondition.ONLINE)
     def "resolves dependencies on real projects"() {
         // Hibernate core brings in conflicts, exclusions and parent poms
         // Add a direct dependency on an earlier version of commons-collection than required by hibernate core
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
index 93442a0..bed461e 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
@@ -20,6 +20,68 @@ import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 
 class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
+    def "can resolve snapshot versions with version range"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenHttpRepo.uri}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "org.test", name: "projectA", version: "1.+"
+    compile group: "org.test", name: "projectB", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        mavenHttpRepo.module("org.test", "projectA", "1.0").publish()
+        mavenHttpRepo.module("org.test", "projectA", "1.1-SNAPSHOT").publish()
+        def matchingA = mavenHttpRepo.module("org.test", "projectA", "1.1").publish()
+        mavenHttpRepo.module("org.test", "projectA", "2.0").publish()
+
+        mavenHttpRepo.module("org.test", "projectB", "1.1").publish()
+        def matchingB = mavenHttpRepo.module("org.test", "projectB", "1.2-SNAPSHOT").publish()
+        mavenHttpRepo.module("org.test", "projectB", "2.0").publish()
+
+        and:
+        mavenHttpRepo.getModuleMetaData("org.test", "projectA").expectGet()
+        matchingA.pom.expectGet()
+        matchingA.artifact.expectGet()
+
+        mavenHttpRepo.getModuleMetaData("org.test", "projectB").expectGet()
+        matchingB.metaData.expectGet()
+        matchingB.pom.expectGet()
+        matchingB.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.2-SNAPSHOT.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(matchingA.artifactFile)
+        file('libs/projectB-1.2-SNAPSHOT.jar').assertIsCopyOf(matchingB.artifactFile)
+
+        when:
+        server.resetExpectations()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.2-SNAPSHOT.jar')
+    }
+
     def "can resolve dynamic version declared in pom as transitive dependency from HTTP Maven repository"() {
         given:
         server.start()
@@ -50,7 +112,7 @@ class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTes
         projectA.getArtifact().expectGet()
         projectB.pom.expectGet()
         projectB.getArtifact().expectGet()
-        mavenHttpRepo.expectMetaDataGet("org.test", "projectC")
+        mavenHttpRepo.getModuleMetaData("org.test", "projectC").expectGet()
         projectC.pom.expectGet()
         projectC.getArtifact().expectGet()
 
@@ -92,7 +154,7 @@ class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTes
     """
 
         when:
-        mavenHttpRepo.expectMetaDataGetMissing("org.test", "projectA")
+        mavenHttpRepo.getModuleMetaData("org.test", "projectA").expectGetMissing()
         mavenHttpRepo.expectDirectoryListGet("org.test", "projectA")
         projectA.pom.expectGet()
         projectA.getArtifact().expectGet()
@@ -138,11 +200,11 @@ class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTes
         """
 
         when:
-        repo1.expectMetaDataGet("group", "projectA")
+        repo1.getModuleMetaData("group", "projectA").expectGet()
         projectA1.pom.expectGet()
         projectA1.getArtifact().expectGet()
 
-        repo2.expectMetaDataGet("group", "projectA")
+        repo2.getModuleMetaData("group", "projectA")expectGet()
         projectA2.pom.expectGetBroken()
 
         and:
@@ -153,7 +215,7 @@ class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTes
 
         when:
         server.resetExpectations()
-        repo2.expectMetaDataGet("group", "projectA")
+        repo2.getModuleMetaData("group", "projectA").expectGet()
         projectA2.pom.expectGet()
         projectA2.artifact.expectGet()
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
index caaf70a..1eb8d15 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
@@ -27,14 +27,18 @@ class MavenHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTe
         given:
         server.start()
 
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+        def projectB = mavenHttpRepo.module('group', 'projectB', '1.0').publish()
+        def projectA = mavenHttpRepo.module('group', 'projectA').dependsOn('group', 'projectB', '1.0').publish()
 
         buildFile << """
 repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
 }
-configurations { compile }
 dependencies {
     compile 'group:projectA:1.0'
 }
@@ -46,10 +50,10 @@ task retrieve(type: Sync) {
 """
 
         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)
+        projectA.pom.expectGet()
+        projectA.artifact.expectGet()
+        projectB.pom.expectGet()
+        projectB.artifact.expectGet()
 
         and:
         run 'retrieve'
@@ -59,10 +63,10 @@ task retrieve(type: Sync) {
         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")
+        progressLogging.downloadProgressLogged(projectA.pom.uri)
+        progressLogging.downloadProgressLogged(projectA.artifact.uri)
+        progressLogging.downloadProgressLogged(projectB.pom.uri)
+        progressLogging.downloadProgressLogged(projectB.artifact.uri)
 
         when:
         server.resetExpectations()
@@ -76,16 +80,19 @@ task retrieve(type: Sync) {
     def "can resolve and cache artifact-only dependencies from an HTTP Maven repository"() {
         server.start()
         given:
-        def module = mavenRepo().module('group', 'projectA', '1.2')
+        def module = mavenHttpRepo.module('group', 'projectA', '1.2')
         module.publish()
 
         and:
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo1" }
-    maven { url "http://localhost:${server.port}/repo2" }
+    maven { url "${mavenHttpRepo.uri}" }
+}
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
 }
-configurations { compile }
 dependencies { compile 'group:projectA:1.2 at jar' }
 task listJars << {
     assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
@@ -94,11 +101,47 @@ task listJars << {
 
         when:
         // TODO: Should meta-data be fetched for an artifact-only dependency?
-        server.expectGetMissing('/repo1/group/projectA/1.2/projectA-1.2.pom')
-        server.expectHeadMissing('/repo1/group/projectA/1.2/projectA-1.2.jar')
+        module.pom.expectGet()
+        module.artifact.expectGet()
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    def "can resolve and cache artifact-only dependencies with no pom from an HTTP Maven repository"() {
+        server.start()
+        given:
+        def module = mavenHttpRepo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+configurations {
+    compile {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
+}
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
 
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.pom', module.pomFile)
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', module.artifactFile)
+        when:
+        // TODO: Should meta-data be fetched for an artifact-only dependency?
+        module.pom.expectGetMissing()
+        module.artifact.expectHead()
+        module.artifact.expectGet()
 
         then:
         succeeds('listJars')
@@ -114,13 +157,19 @@ task listJars << {
     def "can resolve and cache dependencies from multiple HTTP Maven repositories"() {
         given:
         server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
 
         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 {
+        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+    }
 }
-configurations { compile }
 dependencies {
     compile 'group:projectA:1.0', 'group:projectB:1.0'
 }
@@ -129,19 +178,20 @@ task listJars << {
 }
 """
 
-        def projectA = mavenRepo().module('group', 'projectA').publish()
-        def projectB = mavenRepo().module('group', 'projectB').publish()
+        def projectA = repo1.module('group', 'projectA').publish()
+        def missingProjectB = repo1.module('group', 'projectB')
+        def projectB = repo2.module('group', 'projectB').publish()
 
         when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        projectA.pom.expectGet()
 
         // Looks for POM and JAR in repo1 before looking in repo2 (jar is an attempt to handle publication without module descriptor)
-        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.pom')
-        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        missingProjectB.pom.expectGetMissing()
+        missingProjectB.artifact.expectHeadMissing()
+        projectB.pom.expectGet()
 
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+        projectA.artifact.expectGet()
+        projectB.artifact.expectGet()
 
         then:
         succeeds 'listJars'
@@ -157,12 +207,14 @@ task listJars << {
     def "uses artifactsUrl to resolve artifacts"() {
         given:
         server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
 
         buildFile << """
 repositories {
     maven {
-        url 'http://localhost:${server.port}/repo1'
-        artifactUrls 'http://localhost:${server.port}/repo2'
+        url '${repo1.uri}'
+        artifactUrls '${repo2.uri}'
     }
 }
 configurations { compile }
@@ -174,18 +226,17 @@ task listJars << {
 }
 """
 
-        def projectA = mavenRepo().module('group', 'projectA')
-        def projectB = mavenRepo().module('group', 'projectB')
-        projectA.publish()
-        projectB.publish()
+        def projectA = repo1.module('group', 'projectA').publish()
+        def projectB = repo1.module('group', 'projectB').publish()
+        def projectBArtifacts = repo2.module('group', 'projectB').publish()
 
         when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        projectA.pom.expectGet()
+        projectB.pom.expectGet()
 
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+        projectA.artifact.expectGet()
+        projectB.artifact.expectGetMissing()
+        projectBArtifacts.artifact.expectGet()
 
         then:
         succeeds 'listJars'
@@ -195,14 +246,18 @@ task listJars << {
         given:
         server.start()
 
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+        def projectB = mavenHttpRepo.module('group', 'projectB', '1.0').publish()
+        def projectA = mavenHttpRepo.module('group', 'projectA').dependsOn('group', 'projectB', '1.0').publish()
 
         buildFile << """
     repositories {
-        maven { url 'http://localhost:${server.port}/repo1' }
+        maven { url '${mavenHttpRepo.uri}' }
+    }
+    configurations {
+        compile {
+            resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+        }
     }
-    configurations { compile }
     dependencies {
         compile 'group:projectA:1.0'
     }
@@ -218,13 +273,12 @@ task listJars << {
         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)
+        projectA.pom.expectGet()
+        projectA.artifact.expectGet()
+        projectB.pom.expectGet()
+        projectB.artifact.expectGet()
 
         and:
-
         executer.withEnvironmentVars(M2_HOME: m2Home.absolutePath)
         run 'retrieve'
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenJcenterDependencyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenJcenterDependencyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..ed554ca
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenJcenterDependencyResolveIntegrationTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.ONLINE)
+class MavenJcenterDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "resolves a minimal dependency from bintray's jcenter"() {
+        given:
+        buildFile << """
+repositories {
+    jcenter()
+    jcenter { // just test this syntax works.
+        name = "otherJcenter"
+    }
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile "ch.qos.logback:logback-classic:1.0.13"
+}
+
+task check << {
+    def compile = configurations.compile
+    assert compile.resolvedConfiguration.firstLevelModuleDependencies.collect { it.name } == [
+        'ch.qos.logback:logback-classic:1.0.13',
+    ]
+
+    assert compile.collect { it.name } == [
+        'logback-classic-1.0.13.jar',
+        'logback-core-1.0.13.jar',
+        'slf4j-api-1.7.5.jar'
+    ]
+
+    assert compile.resolvedConfiguration.resolvedArtifacts.collect { it.file.name } == [
+        'logback-classic-1.0.13.jar',
+        'logback-core-1.0.13.jar',
+        'slf4j-api-1.7.5.jar'
+    ]
+}
+
+task repoNames << {
+    println repositories*.name
+}
+"""
+
+        expect:
+        succeeds "check", "repoNames"
+
+        and:
+        output.contains(["BintrayJCenter", "otherJcenter"].toString())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenJvmLibraryArtifactResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenJvmLibraryArtifactResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..dd37e57
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenJvmLibraryArtifactResolutionIntegrationTest.groovy
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.api.artifacts.resolution.JvmLibraryJavadocArtifact
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.resolve.JvmLibraryArtifactResolveTestFixture
+import org.gradle.test.fixtures.maven.MavenRepository
+import spock.lang.Unroll
+
+class MavenJvmLibraryArtifactResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    def repo = mavenHttpRepo
+    def fileRepo = mavenRepo
+    def module = repo.module("some.group", "some-artifact", "1.0")
+    def sourceArtifact = module.artifact(classifier: "sources")
+    def javadocArtifact = module.artifact(classifier: "javadoc")
+    JvmLibraryArtifactResolveTestFixture fixture
+
+    def setup() {
+        server.start()
+        initBuild(repo)
+
+        fixture = new JvmLibraryArtifactResolveTestFixture(buildFile)
+
+        module.publish()
+    }
+
+    def initBuild(MavenRepository repo, String module = "some.group:some-artifact:1.0") {
+        buildFile.text = """
+repositories {
+    maven { url '$repo.uri' }
+}
+configurations { compile }
+dependencies {
+    compile "${module}"
+}
+"""
+    }
+
+    def "resolves and caches source artifacts"() {
+        fixture.requestingTypes(JvmLibrarySourcesArtifact)
+                .expectSourceArtifact("sources")
+                .prepare()
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectHead()
+        sourceArtifact.expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolve javadoc artifacts"() {
+        fixture.requestingTypes(JvmLibraryJavadocArtifact)
+                .expectJavadocArtifact("javadoc")
+                .prepare()
+
+        when:
+        module.pom.expectGet()
+        javadocArtifact.expectHead()
+        javadocArtifact.expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolves and caches all artifacts"() {
+        fixture.requestingTypes()
+                .expectSourceArtifact("sources")
+                .expectJavadocArtifact("javadoc")
+                .prepare()
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectHead()
+        sourceArtifact.expectGet()
+        javadocArtifact.expectHead()
+        javadocArtifact.expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    @Unroll
+    def "fetches missing snapshot artifacts #condition"() {
+        initBuild(repo, "some.group:some-artifact:1.0-SNAPSHOT")
+        buildFile << """
+if (project.hasProperty('nocache')) {
+    configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+"""
+
+        def snapshotModule = repo.module("some.group", "some-artifact", "1.0-SNAPSHOT")
+        def snapshotSources = snapshotModule.artifact(classifier: "sources")
+        snapshotModule.publish()
+
+        fixture.withComponentVersion("some.group", "some-artifact", "1.0-SNAPSHOT")
+                .requestingTypes(JvmLibrarySourcesArtifact)
+                .prepare()
+
+        when:
+        snapshotModule.metaData.expectGet()
+        snapshotModule.pom.expectGet()
+        snapshotSources.expectHeadMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+
+        when:
+        snapshotModule.publishWithChangedContent()
+        fixture.clearExpectations()
+                .expectSourceArtifact("sources")
+                .createVerifyTask("verifyRefresh")
+
+        and:
+        server.resetExpectations()
+        snapshotModule.metaData.expectGet()
+        snapshotModule.pom.expectHead()
+        snapshotModule.pom.sha1.expectGet()
+        snapshotModule.pom.expectGet()
+        snapshotSources.expectHead()
+        snapshotSources.expectGet()
+
+        then:
+        executer.withArgument(execArg)
+        succeeds("verifyRefresh")
+
+        where:
+        condition | execArg
+        "with --refresh-dependencies" | "--refresh-dependencies"
+        "when snapshot pom changes" | "-Pnocache"
+    }
+
+    @Unroll
+    def "updates snapshot artifacts #condition"() {
+        initBuild(repo, "some.group:some-artifact:1.0-SNAPSHOT")
+        buildFile << """
+if (project.hasProperty('nocache')) {
+    configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+"""
+
+        def snapshotModule = repo.module("some.group", "some-artifact", "1.0-SNAPSHOT")
+        def snapshotSources = snapshotModule.artifact(classifier: "sources")
+        snapshotModule.publish()
+
+        fixture.withComponentVersion("some.group", "some-artifact", "1.0-SNAPSHOT")
+                .requestingTypes(JvmLibrarySourcesArtifact)
+                .expectSourceArtifact("sources")
+                .prepare()
+
+        when:
+        snapshotModule.metaData.expectGet()
+        snapshotModule.pom.expectGet()
+        snapshotSources.expectHead()
+        snapshotSources.expectGet()
+
+        then:
+        checkArtifactsResolvedAndCached()
+
+        when:
+        def snapshot = file("sources/some-artifact-1.0-SNAPSHOT-sources.jar").snapshot()
+        snapshotModule.publishWithChangedContent()
+
+        and:
+        server.resetExpectations()
+        snapshotModule.metaData.expectGet()
+        snapshotModule.pom.expectHead()
+        snapshotModule.pom.sha1.expectGet()
+        snapshotModule.pom.expectGet()
+        snapshotSources.expectHead()
+        // TODO:DAZ Extra head request should not be required
+        snapshotSources.expectHead()
+        snapshotSources.sha1.expectGet()
+        snapshotSources.expectGet()
+
+        then:
+        executer.withArgument(execArg)
+        succeeds("verify")
+        file("sources/some-artifact-1.0-SNAPSHOT-sources.jar").assertHasChangedSince(snapshot)
+
+        where:
+        condition | execArg
+        "with --refresh-dependencies" | "--refresh-dependencies"
+        "when snapshot pom changes" | "-Pnocache"
+    }
+
+    def "reports failure to resolve artifacts of non-existing component"() {
+        fixture.expectComponentNotFound().prepare()
+
+        when:
+        module.pom.expectGetMissing()
+        module.artifact.expectHeadMissing()
+
+        then:
+        succeeds("verify")
+    }
+
+    def "resolve and caches missing artifacts of existing component"() {
+        fixture.requestingTypes().prepare()
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectHeadMissing()
+        javadocArtifact.expectHeadMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "resolves and caches artifacts where some are present"() {
+        fixture.requestingTypes()
+                .expectSourceArtifact("sources")
+                .prepare()
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectHead()
+        sourceArtifact.expectGet()
+        javadocArtifact.expectHeadMissing()
+
+        then:
+        checkArtifactsResolvedAndCached()
+    }
+
+    def "reports on failure to list artifacts and recovers on subsequent resolve"() {
+        fixture.requestingTypes(JvmLibrarySourcesArtifact)
+                .expectComponentResolutionFailure(new ArtifactResolveException("Could not determine artifacts for component 'some.group:some-artifact:1.0'"))
+                .prepare()
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectHeadBroken()
+
+        then:
+        succeeds("verify")
+
+        when:
+        fixture.clearExpectations()
+                .expectSourceArtifact("sources")
+                .createVerifyTask("verifyFixed")
+
+        and:
+        server.resetExpectations()
+        sourceArtifact.expectHead()
+        sourceArtifact.expectGet()
+
+        then:
+        succeeds("verifyFixed")
+    }
+
+    def "resolves and recovers from broken artifacts"() {
+        fixture.requestingTypes(JvmLibraryJavadocArtifact)
+                .expectJavadocArtifactFailure(new ArtifactResolveException("Could not download artifact 'some.group:some-artifact:1.0:some-artifact-javadoc.jar'"))
+                .prepare()
+
+        when:
+        module.pom.expectGet()
+        javadocArtifact.expectHead()
+        javadocArtifact.expectGetBroken()
+
+        then:
+        succeeds("verify")
+
+        when:
+        fixture.clearExpectations()
+                .expectJavadocArtifact("javadoc")
+                .createVerifyTask("verifyFixed")
+
+        and:
+        server.resetExpectations()
+        javadocArtifact.expectGet()
+
+        then:
+        succeeds("verifyFixed")
+    }
+
+    def "resolve and does not cache artifacts from local repository"() {
+        initBuild(fileRepo)
+
+        fixture.requestingTypes(JvmLibrarySourcesArtifact)
+                .expectSourceArtifact("sources")
+                .prepare()
+
+        when:
+        succeeds("verify")
+
+        and:
+        def snapshot = file("sources/some-artifact-1.0-sources.jar").snapshot()
+
+        and:
+        module.publishWithChangedContent()
+
+        then:
+        succeeds("verify")
+        file("sources/some-artifact-1.0-sources.jar").assertHasChangedSince(snapshot)
+    }
+
+    def checkArtifactsResolvedAndCached() {
+        assert succeeds("verify")
+        server.resetExpectations()
+        assert succeeds("verify")
+        true
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLatestResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLatestResolveIntegrationTest.groovy
new file mode 100644
index 0000000..25e0eac
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLatestResolveIntegrationTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class MavenLatestResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "latest selector works correctly when no snapshot versions are present"() {
+        given:
+        mavenRepo().module('group', 'projectA', '1.0').publish()
+        def highest = mavenRepo().module('group', 'projectA', '2.2').publish()
+        mavenRepo().module('group', 'projectA', '1.4').publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:latest.$status' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants(highest.artifactFile.name)
+
+        where:
+        status << ["integration", "milestone", "release"]
+    }
+
+    def "latest selector with unknown status leads to failure"() {
+        mavenRepo().module('group', 'projectA', '1.0').publish()
+
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:latest.foo' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        expect:
+        fails 'retrieve'
+        // would be better if metadata validation failed (status not contained in status scheme)
+        failure.assertHasCause("Could not find any version that matches group:projectA:latest.foo.")
+    }
+
+    def "snapshot versions are considered integration status when using latest selector"() {
+        given:
+        server.start()
+        mavenHttpRepo.getModuleMetaData('group', 'projectA').allowGetOrHead()
+        mavenHttpRepo.module('group', 'projectA', '1.0').publish().allowAll()
+        mavenHttpRepo.module('group', 'projectA', '1.2-SNAPSHOT').publish().allowAll()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenHttpRepo.uri}" } }
+dependencies { compile 'group:projectA:latest.${status}' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run "retrieve"
+
+        then:
+        file("build").assertHasDescendants(latest)
+
+        where:
+        status        | latest
+        "release"     | "projectA-1.0.jar"
+        "milestone"   | "projectA-1.0.jar"
+        "integration" | "projectA-1.2-SNAPSHOT.jar"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
index b764ff5..d2f9b0d 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
@@ -19,6 +19,7 @@ import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import org.gradle.test.fixtures.maven.MavenModule
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
+import spock.lang.Issue
 
 import static org.hamcrest.Matchers.containsString
 
@@ -59,7 +60,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionT
 
     def "can resolve artifacts from local m2 with custom local repository defined in user settings.xml"() {
         given:
-        def artifactRepo = maven("artifactrepo")
+        def artifactRepo = mavenLocal("artifactrepo")
         m2Installation.generateUserSettingsFile(artifactRepo)
         def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
 
@@ -72,7 +73,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionT
 
     def "can resolve artifacts from local m2 with custom local repository defined in global settings.xml"() {
         given:
-        def artifactRepo = maven("artifactrepo")
+        def artifactRepo = mavenLocal("artifactrepo")
         m2Installation.generateGlobalSettingsFile(artifactRepo)
         def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
 
@@ -85,8 +86,8 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionT
 
     def "local repository in user settings take precedence over the local repository global settings"() {
         given:
-        def globalRepo = maven("globalArtifactRepo")
-        def userRepo = maven("userArtifactRepo")
+        def globalRepo = mavenLocal("globalArtifactRepo")
+        def userRepo = mavenLocal("userArtifactRepo")
         m2Installation.generateGlobalSettingsFile(globalRepo).generateUserSettingsFile(userRepo)
         def moduleA = userRepo.module('group', 'projectA', '1.2').publish()
         globalRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
@@ -103,7 +104,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionT
         m2Installation.userSettingsFile << "invalid content"
 
         when:
-        def failure = runAndFail('retrieve')
+        runAndFail 'retrieve'
 
         then:
         failure.assertThatCause(containsString(String.format("Non-parseable settings %s:", m2Installation.userSettingsFile.absolutePath)));
@@ -128,6 +129,188 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionT
         hasArtifact(moduleA)
     }
 
+    @Issue('GRADLE-2034')
+    def "mavenLocal fails to resolve artifact if contains pom but not artifact"() {
+        given:
+        m2Installation.mavenRepo().module('group', 'projectA', '1.2').publishPom()
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failure.assertHasCause('Could not find group:projectA:1.2')
+    }
+
+    @Issue('GRADLE-2034')
+    def "mavenLocal skipped if contains pom but no artifact"() {
+        given:
+        def anotherRepo = maven("another-local-repo")
+        m2Installation.mavenRepo().module('group', 'projectA', '1.2').publishPom()
+        def moduleARemote = anotherRepo.module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+        repositories{
+            maven { url "${anotherRepo.uri}" }
+        }
+        """
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleARemote)
+    }
+
+    def "mavenLocal includes jar for module with packaging 'pom'"() {
+        given:
+        m2Installation.mavenRepo().module('group', 'projectB', '1.2').publish()
+        def pomModule = m2Installation.mavenRepo().module('group', 'projectA', '1.2')
+        pomModule.packaging = 'pom'
+        pomModule.dependsOn('group', 'projectB', '1.2')
+        pomModule.artifact(type: "jar")
+        pomModule.publish()
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants("projectA-1.2.jar","projectB-1.2.jar")
+    }
+
+    def "mavenLocal ignores missing jar for module with packaging 'pom'"() {
+        given:
+        m2Installation.mavenRepo().module('group', 'projectB', '1.2').publish()
+        def pomModule = m2Installation.mavenRepo().module('group', 'projectA', '1.2')
+        pomModule.packaging = 'pom'
+        pomModule.dependsOn('group', 'projectB', '1.2')
+        pomModule.publishPom()
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants("projectB-1.2.jar")
+    }
+
+    @Issue('GRADLE-2034')
+    def "mavenLocal fails to resolve snapshot artifact if contains pom but not artifact"() {
+        given:
+        m2Installation.mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT').publishPom()
+
+        and:
+        buildFile.text = """
+                repositories {
+                    mavenLocal()
+                }
+                configurations { compile }
+                dependencies {
+                    compile 'group:projectA:1.2-SNAPSHOT'
+                }
+
+                task retrieve(type: Sync) {
+                    from configurations.compile
+                    into 'build'
+                }"""
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failure.assertHasCause('Could not find group:projectA:1.2-SNAPSHOT')
+    }
+
+    @Issue('GRADLE-2034')
+    def "mavenLocal fails to resolve non-unique snapshot artifact if contains pom but not artifact"() {
+        given:
+        m2Installation.mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT').withNonUniqueSnapshots().publishPom()
+
+        and:
+        buildFile.text = """
+                repositories {
+                    mavenLocal()
+                }
+                configurations { compile }
+                dependencies {
+                    compile 'group:projectA:1.2-SNAPSHOT'
+                }
+
+                task retrieve(type: Sync) {
+                    from configurations.compile
+                    into 'build'
+                }"""
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failure.assertHasCause('Could not find group:projectA:1.2-SNAPSHOT')
+    }
+
+    @Issue('GRADLE-2034')
+    def "mavenLocal skipped if contains pom but no artifact for snapshot"() {
+        given:
+        def anotherRepo = maven("another-local-repo")
+        m2Installation.mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT').publishPom()
+        def moduleARemote = anotherRepo.module('group', 'projectA', '1.2-SNAPSHOT').publish()
+
+        and:
+        buildFile.text = """
+                repositories {
+                    mavenLocal()
+                    maven { url "${anotherRepo.uri}" }
+                }
+                configurations { compile }
+                dependencies {
+                    compile 'group:projectA:1.2-SNAPSHOT'
+                }
+
+                task retrieve(type: Sync) {
+                    from configurations.compile
+                    into 'build'
+                }
+        """
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleARemote)
+    }
+
+    @Issue('GRADLE-2034')
+    def "mavenLocal skipped if contains pom but no artifact for non-unique snapshot"() {
+        given:
+        def anotherRepo = maven("another-local-repo")
+        m2Installation.mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT').withNonUniqueSnapshots().publishPom()
+        def moduleARemote = anotherRepo.module('group', 'projectA', '1.2-SNAPSHOT').withNonUniqueSnapshots().publish()
+
+        and:
+        buildFile.text = """
+                repositories {
+                    mavenLocal()
+                    maven { url "${anotherRepo.uri}" }
+                }
+                configurations { compile }
+                dependencies {
+                    compile 'group:projectA:1.2-SNAPSHOT'
+                }
+
+                task retrieve(type: Sync) {
+                    from configurations.compile
+                    into 'build'
+                }
+        """
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleARemote)
+    }
+
     def hasArtifact(MavenModule module) {
         def buildDir = file('build')
         def artifactName = module.artifactFile.name
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
index 4e6841c..2891987 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
@@ -53,10 +53,6 @@ task retrieve(type: Sync) {
         child.pom.expectGet()
         parent.pom.expectGet()
 
-        // Will always check for a default artifact with a module with 'pom' packaging
-        // TODO - should not make this request
-        parent.artifact.expectHeadMissing()
-
         child.artifact.expectGet()
 
         parentDep.pom.expectGet()
@@ -69,6 +65,16 @@ task retrieve(type: Sync) {
 
         then:
         file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.2.jar', 'child_dep-1.7.jar')
+
+        when:
+        server.resetExpectations()
+        file('libs').deleteDir()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.2.jar', 'child_dep-1.7.jar')
     }
 
     @Issue("GRADLE-2641")
@@ -101,10 +107,6 @@ task retrieve(type: Sync) {
         parent.metaData.expectGet()
         parent.pom.expectGet()
 
-        // Will always check for a default artifact with a module with 'pom' packaging
-        // TODO - should not make this request
-        parent.artifact.expectHeadMissing()
-
         child.artifact.expectGet()
 
         and:
@@ -151,8 +153,6 @@ task retrieve(type: Sync) {
         parentInRepo1.artifact.expectHeadMissing()
 
         parentInRepo2.pom.expectGet()
-         // TODO - should not make this request
-        parentInRepo2.artifact.expectHeadMissing()
 
         and:
         run 'retrieve'
@@ -160,4 +160,290 @@ task retrieve(type: Sync) {
         then:
         file('libs').assertHasDescendants('child-1.0.jar')
     }
+
+    def "fails with reasonable message if parent module is an ivy module"() {
+        given:
+        server.start()
+        def child = mavenHttpRepo.module("org", "child")
+        child.parent("org", "parent", "1.0")
+        child.publish()
+
+        def missingChild = ivyHttpRepo.module("org", "child")
+        def parent = ivyHttpRepo.module("org", "parent").publish()
+
+        buildFile << """
+repositories {
+    ivy { url '${ivyHttpRepo.uri}' }
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        missingChild.ivy.expectGetMissing()
+        missingChild.jar.expectHeadMissing()
+        child.pom.expectGet()
+        parent.ivy.expectGet()
+
+        and:
+        fails 'retrieve'
+
+        then:
+        failure.assertHasCause "Could not resolve org:child:1.0."
+        failure.assertHasCause "Could not determine artifacts for component 'org:parent:1.0': Cannot locate artifacts of type MavenPomArtifact for 'org:parent:1.0' in repository 'ivy'"
+    }
+
+    def "uses cached parent pom located in a different repository"() {
+        given:
+        server.start()
+
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
+
+        // Parent not found in repo1
+        def missingParent = repo1.module("org", "parent")
+        def parent = repo2.module("org", "parent", "1.0")
+        parent.dependsOn("org", "parent_dep", "1.2")
+                .hasPackaging('pom')
+                .publish()
+
+        def parentDep = repo1.module("org", "parent_dep", "1.2").publish()
+
+        def child1 = repo1.module("org", "child1", "1.0")
+        child1.parent("org", "parent", "1.0").publish()
+        def child2 = repo1.module("org", "child2", "1.0")
+        child2.parent("org", "parent", "1.0").publish()
+
+        buildFile << """
+repositories {
+    maven { url '${repo1.uri}' }
+    maven { url '${repo2.uri}' }
+}
+configurations {
+    child1
+    child2
+}
+dependencies {
+    child1 'org:child1:1.0'
+    child2 'org:child2:1.0'
+}
+task retrieveChild1(type: Sync) {
+    into 'libs/child1'
+    from configurations.child1
+}
+task retrieveChild2(type: Sync) {
+    into 'libs/child2'
+    from configurations.child2
+}
+"""
+
+        when:
+        child1.pom.expectGet()
+        missingParent.pom.expectGetMissing()
+        missingParent.artifact.expectHeadMissing()
+        parent.pom.expectGet()
+
+        child1.artifact.expectGet()
+
+        parentDep.pom.expectGet()
+        parentDep.artifact.expectGet()
+
+
+        and:
+        run 'retrieveChild1'
+
+        then:
+        file('libs/child1').assertHasDescendants('child1-1.0.jar', 'parent_dep-1.2.jar')
+
+        when:
+        server.resetExpectations()
+        child2.pom.expectGet()
+        child2.artifact.expectGet()
+
+        and:
+        run 'retrieveChild2'
+
+        then:
+        file('libs/child2').assertHasDescendants('child2-1.0.jar', 'parent_dep-1.2.jar')
+    }
+
+    @Issue("GRADLE-2861")
+    def "two modules that share common parent"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module("org", "parent", "1.0")
+        parent.hasPackaging('pom')
+        parent.publish()
+
+        def child1 = mavenHttpRepo.module("org", "child1", "1.0")
+        child1.parent("org", "parent", "1.0")
+        child1.publish()
+        def child2 = mavenHttpRepo.module("org", "child2", "1.0")
+        child2.parent("org", "parent", "1.0")
+        child2.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org:child1:1.0'
+    compile 'org:child2:1.0'
+}
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        child1.pom.expectGet()
+        child2.pom.expectGet()
+        parent.pom.expectGet()
+
+        child1.artifact.expectGet()
+        child2.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child1-1.0.jar', 'child2-1.0.jar')
+    }
+
+    @Issue("GRADLE-2861")
+    def "module that has a parent and grandparent module"() {
+        given:
+        server.start()
+
+        def grandParent = mavenHttpRepo.module("org", "grandparent", "1.0")
+        grandParent.hasPackaging('pom')
+        grandParent.publish()
+
+        def parent = mavenHttpRepo.module("org", "parent", "1.0")
+        parent.hasPackaging('pom')
+        parent.parent("org", "grandparent", "1.0")
+        parent.publish()
+
+        def child = mavenHttpRepo.module("org", "child", "1.0")
+        child.parent("org", "parent", "1.0")
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org:child:1.0'
+}
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        child.pom.expectGet()
+        parent.pom.expectGet()
+        grandParent.pom.expectGet()
+
+        child.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar')
+    }
+
+    def "parent pom parsing with custom properties for dependency coordinates"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module('group', 'parent', '1.0').publish()
+        parent.pomFile.text = parent.pomFile.text.replace("</project>", """
+<properties>
+    <some.group>my.group</some.group>
+    <some.artifact>myartifact</some.artifact>
+    <some.version>1.1</some.version>
+</properties>
+<dependencies>
+    <dependency>
+        <groupId>\${some.group}</groupId>
+        <artifactId>\${some.artifact}</artifactId>
+        <version>\${some.version}</version>
+    </dependency>
+</dependencies>
+</project>
+""")
+
+        def parentDepModule = mavenHttpRepo.module('my.group', 'myartifact', '1.1').publish()
+        def module = mavenHttpRepo.module('group', 'artifact', '1.0').parent('group', 'parent', '1.0').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                maven { url "${mavenHttpRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile "group:artifact:1.0"
+            }
+            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar', 'myartifact-1.1.jar'] }
+        """
+
+        and:
+        parent.pom.expectGet()
+        module.pom.expectGet()
+        module.artifact.expectGet()
+        parentDepModule.pom.expectGet()
+        parentDepModule.artifact.expectGet()
+
+        expect:
+        // have to run twice to trigger the failure, to parse the descriptor from the cache
+        succeeds ":libs"
+        succeeds ":libs"
+    }
+
+    def "dependency with same group ID and artifact ID defined in child and parent is used from child"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module('group', 'parent', '1.0').dependsOn('my.group', 'myartifact', '1.1').publish()
+        mavenHttpRepo.module('my.group', 'myartifact', '1.1').publish()
+        def depModule = mavenHttpRepo.module('my.group', 'myartifact', '1.3').publish()
+        def module = mavenHttpRepo.module('group', 'artifact', '1.0').parent('group', 'parent', '1.0').dependsOn('my.group', 'myartifact', '1.3').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                maven { url "${mavenHttpRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile "group:artifact:1.0"
+            }
+            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar', 'myartifact-1.3.jar'] }
+        """
+
+        and:
+        parent.pom.expectGet()
+        module.pom.expectGet()
+        module.artifact.expectGet()
+        depModule.pom.expectGet()
+        depModule.artifact.expectGet()
+
+        expect:
+        // have to run twice to trigger the failure, to parse the descriptor from the cache
+        succeeds ":libs"
+        succeeds ":libs"
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
index 18cabfa..9d86a62 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
@@ -16,81 +16,182 @@
 package org.gradle.integtests.resolve.maven
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.maven.MavenFileModule
+import org.gradle.test.fixtures.maven.MavenHttpModule
+import org.gradle.test.fixtures.maven.MavenHttpRepository
 import spock.lang.FailsWith
 import spock.lang.Issue
 
 class MavenPomPackagingResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    MavenFileModule projectA
+    MavenHttpRepository repo1
+    MavenHttpRepository repo2
+    MavenHttpModule projectARepo1
+    MavenHttpModule projectARepo2
 
     public setup() {
         server.start()
-
-        projectA = mavenRepo().module('group', 'projectA')
+        repo1 = mavenHttpRepo("repo1")
+        repo2 = mavenHttpRepo("repo2")
+        projectARepo1 = repo1.module('group', 'projectA')
+        projectARepo2 = repo2.module('group', 'projectA')
     }
 
     private void buildWithDependencies(def dependencies) {
         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 }
 dependencies {
     $dependencies
 }
-task retrieve(type: Sync) {
+task deleteDir(type: Delete) {
+    delete 'libs'
+}
+task retrieve(type: Copy, dependsOn: deleteDir) {
     into 'libs'
     from configurations.compile
 }
 """
     }
 
-    def "looks for jar artifact for pom with packaging of type 'pom' in the same repository only"() {
+    def "includes jar artifact if present for pom with packaging of type 'pom'"() {
         when:
         buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('pom')
+        projectARepo2.hasPackaging("pom").publish()
 
         and:
         // First attempts to resolve in repo1
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+        projectARepo1.pom.expectGetMissing()
+        projectARepo1.artifact.expectHeadMissing()
 
-        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHead('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        projectARepo2.pom.expectGet()
+        projectARepo2.artifact.expectHead()
+        projectARepo2.artifact.expectGet()
 
         and:
         run 'retrieve'
 
         then:
         file('libs').assertHasDescendants('projectA-1.0.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
 
         when:
         server.resetExpectations()
+        run 'retrieve'
+
+        then: // Uses cached artifacts
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+    }
+
+    def "ignores missing jar artifact for pom with packaging of type 'pom'"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        projectARepo1.hasPackaging("pom").publishPom()
+
+        and:
+        projectARepo1.pom.expectGet()
+        projectARepo1.artifact.expectHeadMissing()
+
         and:
         run 'retrieve'
 
+        then:
+        file('libs').assertIsEmptyDir()
+
+        when:
+        server.resetExpectations()
+        run 'retrieve'
+
         then: // Uses cached artifacts
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+        file('libs').assertIsEmptyDir()
+    }
+
+    def "for a snapshot module with with packaging of type 'pom', will check for jar artifact that was previously missing on cache expiry"() {
+        when:
+        def snapshotA = repo1.module('group', 'projectA', '1.1-SNAPSHOT')
+        snapshotA.hasPackaging("pom").publish()
+
+        and:
+        buildWithDependencies("compile 'group:projectA:1.1-SNAPSHOT'")
+        buildFile << """
+if (project.hasProperty('skipCache')) {
+    configurations.compile.resolutionStrategy.cacheChangingModulesFor(0, 'seconds')
+}
+"""
+
+        and:
+        snapshotA.metaData.expectGet()
+        snapshotA.pom.expectGet()
+        snapshotA.artifact.expectHeadMissing()
+
+        and:
+        run 'retrieve'
+
+        then:
+        skipped ':retrieve'
+
+        // Jar artifact presence is cached
+        when:
+        server.resetExpectations()
+
+        and:
+        run 'retrieve'
+
+        then:
+        skipped ':retrieve'
+
+        // New artifact is detected
+        when:
+        server.resetExpectations()
+        snapshotA.metaData.expectGet()
+        snapshotA.pom.expectHead()
+        snapshotA.artifact.expectHead()
+        snapshotA.artifact.expectGet()
+
+        and:
+        executer.withArgument('-PskipCache')
+        run 'retrieve'
+
+        then:
+        executed ':retrieve'
+        file('libs').assertHasDescendants('projectA-1.1-SNAPSHOT.jar')
+
+        // Jar artifact removal is detected
+        when:
+        server.resetExpectations()
+        snapshotA.metaData.expectGet()
+        snapshotA.pom.expectHead()
+        snapshotA.artifact.expectHeadMissing()
+
+        and:
+        executer.withArgument('-PskipCache')
+        run 'retrieve'
+
+        then:
+        skipped ':retrieve'
     }
 
     def "will use jar artifact for pom with packaging that maps to jar"() {
         when:
         buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging(packaging)
+        projectARepo1.hasPackaging(packaging).publish()
 
         and:
-        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)
+        projectARepo1.pom.expectGet()
+        projectARepo1.artifact.expectGet()
 
         then:
         succeeds 'retrieve'
 
         and:
         file('libs').assertHasDescendants('projectA-1.0.jar')
-        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
+        file('libs/projectA-1.0.jar').assertIsCopyOf(projectARepo1.artifactFile)
+
+        // Check caching
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'retrieve'
 
         where:
         packaging << ['', 'jar', 'eclipse-plugin', 'bundle']
@@ -101,31 +202,37 @@ task retrieve(type: Sync) {
     def "will use jar artifact for pom with packaging 'orbit'"() {
         when:
         buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('orbit')
+        projectARepo1.hasPackaging('orbit').publish()
 
         and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.orbit')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        projectARepo1.pom.expectGet()
+        projectARepo1.artifact(type: 'orbit').expectHeadMissing()
+        projectARepo1.artifact.expectGet()
 
         then:
         succeeds 'retrieve'
 
         and:
         file('libs').assertHasDescendants('projectA-1.0.jar')
-        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
+        file('libs/projectA-1.0.jar').assertIsCopyOf(projectARepo1.artifactFile)
+
+        // Check caching
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'retrieve'
     }
 
     @Issue('GRADLE-2188')
     def "where 'module.custom' exists, will use it as main artifact for pom with packaging 'custom' and emit deprecation warning"() {
         when:
         buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('custom', 'custom')
+        projectARepo1.hasPackaging("custom").hasType("custom").publish()
 
         and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHead('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
+        projectARepo1.pom.expectGet()
+        projectARepo1.artifact.expectHead()
+        projectARepo1.artifact.expectGet()
 
         and:
         executer.withDeprecationChecksDisabled()
@@ -135,27 +242,33 @@ task retrieve(type: Sync) {
 
         and:
         file('libs').assertHasDescendants('projectA-1.0.custom')
-        file('libs/projectA-1.0.custom').assertIsCopyOf(projectA.artifactFile)
+        file('libs/projectA-1.0.custom').assertIsCopyOf(projectARepo1.artifactFile)
 
         and:
         result.output.contains("Relying on packaging to define the extension of the main artifact has been deprecated")
+
+        // Check caching
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'retrieve'
     }
 
     def "fails and reports type-based location if neither packaging-based or type-based artifact can be located"() {
         when:
         buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('custom')
+        projectARepo1.hasPackaging("custom").publishPom()
 
         and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+        projectARepo1.pom.expectGet()
+        projectARepo1.artifact(type: 'custom').expectHeadMissing()
+        projectARepo1.artifact(type: 'jar').expectGetMissing()
 
         then:
         fails 'retrieve'
 
         and:
-        result.error.contains("Artifact 'group:projectA:1.0 at jar' not found.")
+        result.error.contains("Artifact 'group:projectA:1.0:projectA.jar' not found.")
     }
 
     def "will use non-jar dependency type to determine jar artifact location"() {
@@ -168,21 +281,29 @@ compile('group:projectA:1.0') {
     }
 }
 """)
-        publishWithPackaging('custom')
+        projectARepo1.hasPackaging("custom").hasType("custom")
+        projectARepo1.artifact(type: 'zip')
+        projectARepo1.publish()
 
         and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        projectARepo1.pom.expectGet()
 
         // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+        projectARepo1.artifact(type: 'custom').expectHeadMissing()
+        projectARepo1.artifact(type: 'zip').expectGet()
 
         then:
         succeeds 'retrieve'
 
         and:
         file('libs').assertHasDescendants('projectA-1.0.zip')
-        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectARepo1.artifact(type: 'zip').file)
+
+        // Check caching
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'retrieve'
     }
 
     def "will use non-jar maven dependency type to determine artifact location"() {
@@ -190,24 +311,32 @@ compile('group:projectA:1.0') {
         buildWithDependencies("""
 compile 'group:mavenProject:1.0'
 """)
-        def mavenProject = mavenRepo().module('group', 'mavenProject', '1.0').hasType('pom').dependsOn('group', 'projectA', '1.0', 'zip').publish()
-        publishWithPackaging('custom', 'zip')
+        def mavenProject = repo1.module('group', 'mavenProject', '1.0').hasPackaging('pom').dependsOn('group', 'projectA', '1.0', 'zip').publishPom()
+        projectARepo1.hasPackaging("custom")
+        projectARepo1.artifact(type: 'zip')
+        projectARepo1.publish()
 
         and:
-        server.expectGet('/repo1/group/mavenProject/1.0/mavenProject-1.0.pom', mavenProject.pomFile)
-        server.expectHeadMissing('/repo1/group/mavenProject/1.0/mavenProject-1.0.jar')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        mavenProject.pom.expectGet()
+        mavenProject.artifact.expectHeadMissing()
 
+        projectARepo1.pom.expectGet()
         // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+        projectARepo1.artifact(type: 'custom').expectHeadMissing()
+        projectARepo1.artifact(type: 'zip').expectGet()
 
         then:
         succeeds 'retrieve'
 
         and:
         file('libs').assertHasDescendants('projectA-1.0.zip')
-        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectARepo1.artifact(type: 'zip').file)
+
+        // Check caching
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'retrieve'
     }
 
     @FailsWith(value = AssertionError, reason = "Pending better fix for GRADLE-2188")
@@ -221,27 +350,29 @@ compile('group:projectA:1.0') {
     }
 }
 """)
-        publishWithPackaging('zip')
+        projectARepo1.hasPackaging("zip").hasType("zip").publish()
 
         and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+        projectARepo1.pom.expectGet()
+        // TODO - should not need this head request
+        projectARepo1.artifact.expectHead()
+        projectARepo1.artifact.expectGet()
 
         then:
         succeeds 'retrieve'
 
         and:
         file('libs').assertHasDescendants('projectA-1.0.zip')
-        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectARepo1.artifactFile)
 
         and: "Stop the http server here to allow failure to be declared (otherwise occurs in tearDown) - remove this when the test is fixed"
         server.stop()
-    }
 
-    private def publishWithPackaging(String packaging, String type = 'jar') {
-        projectA.packaging = packaging
-        projectA.type = type
-        projectA.publish()
+        // Check caching
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'retrieve'
     }
 
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomResolveIntegrationTest.groovy
new file mode 100644
index 0000000..957be86
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomResolveIntegrationTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class MavenPomResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "follows relocation to another group"() {
+        server.start()
+
+        given:
+        def original = mavenHttpRepo.module("groupA", "projectA", "1.2").publishPom()
+        original.pomFile.text = """
+<project>
+    <groupId>groupA</groupId>
+    <artifactId>projectA</artifactId>
+    <version>1.2</version>
+    <distributionManagement>
+        <relocation>
+            <groupId>newGroupA</groupId>
+        </relocation>
+    </distributionManagement>
+</project>
+"""
+
+        def newModule = mavenHttpRepo.module("newGroupA", "projectA", "1.2").publish()
+        newModule.publish()
+
+        and:
+        buildFile << """
+repositories { maven { url '${mavenHttpRepo.uri}' } }
+configurations { compile }
+dependencies { compile 'groupA:projectA:1.2' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and:
+        original.pom.expectGet()
+        newModule.pom.expectGet()
+        newModule.artifact.expectGet()
+
+        when:
+        run "retrieve"
+
+        then:
+        file("libs").assertHasDescendants("projectA-1.2.jar")
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenProfileResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenProfileResolveIntegrationTest.groovy
new file mode 100644
index 0000000..358a14c
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenProfileResolveIntegrationTest.groovy
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.resolve.ResolveTestFixture
+
+class MavenProfileResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    ResolveTestFixture resolve
+
+    def setup() {
+        settingsFile << "rootProject.name = 'test' "
+        resolve = new ResolveTestFixture(buildFile)
+        resolve.prepare()
+        server.start()
+    }
+
+    def "uses properties from active profile to resolve dependency"() {
+        given:
+        def requestedModule = mavenHttpRepo.module("groupA", "artifactA", "1.2").publish()
+        requestedModule.pomFile.text = """
+<project>
+    <groupId>groupA</groupId>
+    <artifactId>artifactA</artifactId>
+    <version>1.2</version>
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>groupB</groupId.prop>
+                <artifactId.prop>artifactB</artifactId.prop>
+                <version.prop>1.4</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def transitiveModule = mavenHttpRepo.module("groupB", "artifactB", "1.4").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url '${mavenHttpRepo.uri}' } }
+configurations { compile }
+dependencies { compile 'groupA:artifactA:1.2' }
+"""
+
+        and:
+        requestedModule.pom.expectGet()
+        requestedModule.artifact.expectGet()
+        transitiveModule.pom.expectGet()
+        transitiveModule.artifact.expectGet()
+
+        when:
+        run "checkDeps"
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:unspecified") {
+                module("groupA:artifactA:1.2") {
+                    module("groupB:artifactB:1.4")
+                }
+            }
+        }
+    }
+
+    def "uses dependency management defaults from active profile to resolve dependency"() {
+        given:
+        def requestedModule = mavenHttpRepo.module("groupA", "artifactA", "1.2").publish()
+        requestedModule.pomFile.text = """
+<project>
+    <groupId>groupA</groupId>
+    <artifactId>artifactA</artifactId>
+    <version>1.2</version>
+    <dependencies>
+        <dependency>
+            <groupId>groupB</groupId>
+            <artifactId>artifactB</artifactId>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>groupB</groupId>
+                        <artifactId>artifactB</artifactId>
+                        <version>1.4</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def transitiveModule = mavenHttpRepo.module("groupB", "artifactB", "1.4").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url '${mavenHttpRepo.uri}' } }
+configurations { compile }
+dependencies { compile 'groupA:artifactA:1.2' }
+"""
+
+        and:
+        requestedModule.pom.expectGet()
+        requestedModule.artifact.expectGet()
+        transitiveModule.pom.expectGet()
+        transitiveModule.artifact.expectGet()
+
+        when:
+        run "checkDeps"
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:unspecified") {
+                module("groupA:artifactA:1.2") {
+                    module("groupB:artifactB:1.4")
+                }
+            }
+        }
+    }
+
+    def "resolves dependency from active profile"() {
+        given:
+        def requestedModule = mavenHttpRepo.module("groupA", "artifactA", "1.2").publish()
+        requestedModule.pomFile.text = """
+<project>
+    <groupId>groupA</groupId>
+    <artifactId>artifactA</artifactId>
+    <version>1.2</version>
+    <dependencies>
+        <dependency>
+            <groupId>groupB</groupId>
+            <artifactId>artifactB</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>groupB</groupId>
+            <artifactId>artifactB</artifactId>
+            <version>1.7</version>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>groupB</groupId>
+                    <artifactId>artifactB</artifactId>
+                    <version>1.4</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def transitiveModule = mavenHttpRepo.module("groupB", "artifactB", "1.4").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url '${mavenHttpRepo.uri}' } }
+configurations { compile }
+dependencies { compile 'groupA:artifactA:1.2' }
+"""
+
+        and:
+        requestedModule.pom.expectGet()
+        requestedModule.artifact.expectGet()
+        transitiveModule.pom.expectGet()
+        transitiveModule.artifact.expectGet()
+
+        when:
+        run "checkDeps"
+
+        then:
+        resolve.expectGraph {
+            root(":", ":test:unspecified") {
+                module("groupA:artifactA:1.2") {
+                    module("groupB:artifactB:1.4")
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
index 6784b20..83cb4ec 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
@@ -17,6 +17,8 @@ package org.gradle.integtests.resolve.maven
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import org.gradle.test.fixtures.maven.MavenHttpModule
+import spock.lang.Ignore
+import spock.lang.Issue
 
 class MavenSnapshotResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
@@ -141,6 +143,56 @@ task retrieve(type: Sync) {
         file('libs/projectB-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotB);
     }
 
+    def "can find and cache snapshots in Maven HTTP repository with artifact classifier"() {
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "${repo1.uri}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle.integtests.resolve:projectA:1.0-SNAPSHOT:tests"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and:
+        def projectA = repo1.module("org.gradle.integtests.resolve", "projectA", "1.0-SNAPSHOT")
+        def classifierArtifact = projectA.artifact(classifier: "tests")
+        projectA.publish()
+
+        when:
+        projectA.metaData.expectGet()
+        projectA.pom.expectGet()
+        classifierArtifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT-tests.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT-tests.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        run 'retrieve'
+
+        then: "Everything is up to date"
+        skipped ':retrieve'
+        file('libs/projectA-1.0-SNAPSHOT-tests.jar').assertHasNotChangedSince(snapshotA);
+    }
+
     def "will detect changed snapshot artifacts when pom has not changed"() {
         server.start()
 
@@ -348,6 +400,10 @@ allprojects {
         from configurations.compile
     }
 }
+
+//imposing an artificial order so that the parallel build retrieves sequentially, GRADLE-2788
+retrieve.dependsOn ":a:retrieve"
+tasks.getByPath(":a:retrieve").dependsOn ":b:retrieve"
 """
         when: "Module is requested once"
         expectModuleServed(module)
@@ -361,6 +417,7 @@ allprojects {
         file('b/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
     }
 
+    @Ignore //TODO SF need to rework this test. First step might be turning off in-memory metadata caching for this test.
     def "can update snapshot artifact during build even if it is locked earlier in build"() {
         server.start()
         given:
@@ -506,6 +563,83 @@ project('second') {
         downloadedJarFile.assertIsCopyOf(module.artifactFile)
     }
 
+    @Issue("GRADLE-3017")
+    def "resolves changed metadata in snapshot dependency"() {
+        given:
+        server.start()
+
+        def projectB1 = mavenHttpRepo.module('group', 'projectB', '1.0').publish()
+        def projectB2 = mavenHttpRepo.module('group', 'projectB', '2.0').publish()
+        def projectA = mavenHttpRepo.module('group', 'projectA', "1.0-SNAPSHOT").dependsOn('group', 'projectB', '1.0').publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations {
+    compile {
+        if (project.hasProperty('bypassCache')) {
+            resolutionStrategy.cacheChangingModulesFor(0, "seconds")
+        }
+    }
+}
+dependencies {
+    compile 'group:projectA:1.0-SNAPSHOT'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        projectA.pom.expectGet()
+        projectA.metaData.expectGet()
+        projectA.artifact.expectGet()
+        projectB1.pom.expectGet()
+        projectB1.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0.jar')
+
+        when: "Project A is published with changed dependencies"
+        server.resetExpectations()
+        projectA = projectA.dependsOn('group', 'projectB', '2.0').publish()
+
+        and: "Resolve with caching"
+        run 'retrieve'
+
+        then: "Gets original ProjectA metadata from cache"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0.jar')
+
+        when: "Resolve without cache"
+        projectA.metaData.expectGet()
+        projectA.pom.expectHead()
+        projectA.pom.sha1.expectGet()
+        projectA.pom.expectGet()
+        projectA.artifact.expectHead()
+        projectB2.pom.expectGet()
+        projectB2.artifact.expectGet()
+
+        and:
+        executer.withArguments("-PbypassCache")
+        run 'retrieve'
+
+        then: "Gets updated metadata"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-2.0.jar')
+
+        when: "Resolve with caching"
+        server.resetExpectations()
+        run 'retrieve'
+
+        then: "Gets updated metadata from cache"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-2.0.jar')
+    }
+
     private expectModuleServed(MavenHttpModule module) {
         module.metaData.expectGet()
         module.pom.expectGet()
@@ -517,10 +651,9 @@ project('second') {
         module.pom.expectHead()
         module.pom.sha1.expectGet()
         module.pom.expectGet()
-        def artifact = module.artifact
-        artifact.expectHead()
-        artifact.sha1.expectGet()
-        artifact.expectGet()
+        module.artifact.expectHead()
+        module.artifact.sha1.expectGet()
+        module.artifact.expectGet()
     }
 
     private expectChangedArtifactServed(MavenHttpModule module) {
diff --git a/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
index d104d48..df5cea3 100644
--- a/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
+++ b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
@@ -22,5 +22,5 @@ file("projectC-2.0.jar").text = ''
 
 task listJars << {
     List jars = configurations.compile.collect { it.name }
-    assert jars == ['projectA-1.2.jar', 'projectC-2.0.jar', 'projectB-1.5.jar']
+    assert jars == ['projectA-1.2.jar', 'projectB-1.5.jar', 'projectC-2.0.jar']
 }
diff --git a/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
index 0deaf43..47b508d 100644
--- a/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
+++ b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
@@ -3,7 +3,7 @@ allprojects {
         evictedTransitive
         evictedDirect
         multiProject
-        add('default')
+        create('default')
     }
     dependencies {
         repositories {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
index 7b5dae9..929d327 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
@@ -21,9 +21,9 @@ import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public interface ArtifactDependencyResolver {
-    ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException;
+    void resolve(ConfigurationInternal configuration,
+                 List<? extends ResolutionAwareRepository> repositories,
+                 ModuleMetadataProcessor metadataProcessor,
+                 ResolverResults results) throws ResolveException;
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
new file mode 100644
index 0000000..52d4a7a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+
+public interface ArtifactPublicationServices {
+    RepositoryHandler createRepositoryHandler();
+
+    ArtifactPublisher createArtifactPublisher();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
new file mode 100644
index 0000000..15f28e0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+
+import java.io.File;
+
+public interface ArtifactPublisher {
+    void publish(Iterable<? extends PublicationAwareRepository> repositories, ModuleInternal module, Configuration configuration, File descriptor) throws PublishException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java
index 501959b..0751e90 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.artifacts;
 import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 
-/**
- * @author Hans Dockter
- */
 public interface ConfigurationResolver {
     ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException;
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java
new file mode 100644
index 0000000..e5ddfb1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId;
+
+public class DefaultArtifactIdentifier implements ArtifactIdentifier {
+    private final ModuleVersionIdentifier moduleVersionIdentifier;
+    private final String name;
+    private final String type;
+    private final String extension;
+    private final String classifier;
+
+    public DefaultArtifactIdentifier(ModuleVersionIdentifier moduleVersionIdentifier, String name, String type, String extension, String classifier) {
+        this.moduleVersionIdentifier = moduleVersionIdentifier;
+        this.name = name;
+        this.type = type;
+        this.extension = extension;
+        this.classifier = classifier;
+    }
+
+    public DefaultArtifactIdentifier(DefaultModuleVersionArtifactIdentifier id) {
+        this(newId(id.getComponentIdentifier()), id.getName().getName(), id.getName().getType(), id.getName().getExtension(), id.getName().getClassifier());
+    }
+
+    public ModuleVersionIdentifier getModuleVersionIdentifier() {
+        return moduleVersionIdentifier;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("module: %s, name: %s, ext: %s, classifier: %s", moduleVersionIdentifier, name, extension, classifier);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultArtifactIdentifier)) {
+            return false;
+        }
+
+        DefaultArtifactIdentifier that = (DefaultArtifactIdentifier) o;
+
+        if (classifier != null ? !classifier.equals(that.classifier) : that.classifier != null) {
+            return false;
+        }
+        if (extension != null ? !extension.equals(that.extension) : that.extension != null) {
+            return false;
+        }
+        if (moduleVersionIdentifier != null ? !moduleVersionIdentifier.equals(that.moduleVersionIdentifier) : that.moduleVersionIdentifier != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (type != null ? !type.equals(that.type) : that.type != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = moduleVersionIdentifier != null ? moduleVersionIdentifier.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (type != null ? type.hashCode() : 0);
+        result = 31 * result + (extension != null ? extension.hashCode() : 0);
+        result = 31 * result + (classifier != null ? classifier.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
index 69e7831..b29a57b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
@@ -24,20 +24,17 @@ import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryDelegate;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.notations.ProjectDependencyFactory;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
 
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultDependencyFactory implements DependencyFactory {
-    private final NotationParser<Dependency> dependencyNotationParser;
-    private final NotationParser<ClientModule> clientModuleNotationParser;
+    private final NotationParser<Object, Dependency> dependencyNotationParser;
+    private final NotationParser<Object, ClientModule> clientModuleNotationParser;
     private final ProjectDependencyFactory projectDependencyFactory;
 
-    public DefaultDependencyFactory(NotationParser<Dependency> dependencyNotationParser,
-                                    NotationParser<ClientModule> clientModuleNotationParser,
+    public DefaultDependencyFactory(NotationParser<Object, Dependency> dependencyNotationParser,
+                                    NotationParser<Object, ClientModule> clientModuleNotationParser,
                                     ProjectDependencyFactory projectDependencyFactory) {
         this.dependencyNotationParser = dependencyNotationParser;
         this.clientModuleNotationParser = clientModuleNotationParser;
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 59e2b1a..ff2b747 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
@@ -15,400 +15,174 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
-import org.gradle.StartParameter;
-import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
+import org.gradle.api.artifacts.dsl.ComponentMetadataHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
 import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
+import org.gradle.api.internal.artifacts.dsl.DefaultComponentMetadataHandler;
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
 import org.gradle.api.internal.artifacts.dsl.PublishArtifactNotationParserFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ArtifactResolutionQueryFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.artifacts.ivyservice.*;
-import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
-import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.SingleFileBackedModuleResolutionCache;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.StartParameterResolutionOverride;
-import org.gradle.api.internal.artifacts.ivyservice.modulecache.DefaultModuleDescriptorCache;
-import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.*;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.*;
-import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectModuleRegistry;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver;
-import org.gradle.api.internal.artifacts.mvnsettings.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.PublishLocalComponentFactory;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
 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.legacy.LegacyDependencyResolverRepositoryFactory;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
-import org.gradle.api.internal.externalresource.cached.ByUrlCachedExternalResourceIndex;
-import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryCachedArtifactIndex;
+import org.gradle.api.internal.artifacts.resolution.DefaultArtifactResolutionQueryFactory;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
-import org.gradle.api.internal.externalresource.local.ivy.LocallyAvailableResourceFinderFactory;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.internal.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.initialization.ProjectAccessListener;
-import org.gradle.internal.SystemProperties;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistration;
 import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.listener.ListenerManager;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.util.BuildCommencedTimeProvider;
-import org.gradle.util.WrapUtil;
 
-import java.io.File;
-import java.util.List;
+public class DefaultDependencyManagementServices implements DependencyManagementServices {
 
-public class DefaultDependencyManagementServices extends DefaultServiceRegistry implements DependencyManagementServices {
+    private final ServiceRegistry parent;
 
     public DefaultDependencyManagementServices(ServiceRegistry parent) {
-        super(parent);
-    }
-
-    public DependencyResolutionServices create(FileResolver resolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
-        return new DefaultDependencyResolutionServices(this, resolver, dependencyMetaDataProvider, projectFinder, domainObjectContext);
-    }
-
-    protected ResolveModuleDescriptorConverter createResolveModuleDescriptorConverter() {
-        return new ResolveModuleDescriptorConverter(
-                get(ModuleDescriptorFactory.class),
-                get(DependencyDescriptorFactory.class),
-                get(ConfigurationsToModuleDescriptorConverter.class),
-                new DefaultDependenciesToModuleDescriptorConverter(
-                        get(DependencyDescriptorFactory.class),
-                        get(ExcludeRuleConverter.class)));
-
-    }
-
-    protected PublishModuleDescriptorConverter createPublishModuleDescriptorConverter() {
-        return new PublishModuleDescriptorConverter(
-                get(ResolveModuleDescriptorConverter.class),
-                new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY));
-    }
-
-    protected ModuleDescriptorFactory createModuleDescriptorFactory() {
-        return new DefaultModuleDescriptorFactory(get(IvyFactory.class), get(SettingsConverter.class));
-    }
-
-    protected ExcludeRuleConverter createExcludeRuleConverter() {
-        return new DefaultExcludeRuleConverter();
-    }
-
-    protected ExternalModuleIvyDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory() {
-        return new ExternalModuleIvyDependencyDescriptorFactory(get(ExcludeRuleConverter.class));
-    }
-
-    protected ConfigurationsToModuleDescriptorConverter createConfigurationsToModuleDescriptorConverter() {
-        return new DefaultConfigurationsToModuleDescriptorConverter();
-    }
-
-    protected DependencyDescriptorFactory createDependencyDescriptorFactory() {
-        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory = new DefaultModuleDescriptorFactoryForClientModule();
-        DependencyDescriptorFactory dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
-                new ClientModuleIvyDependencyDescriptorFactory(
-                        get(ExcludeRuleConverter.class),
-                        clientModuleDescriptorFactory
-                ),
-                new ProjectIvyDependencyDescriptorFactory(
-                        get(ExcludeRuleConverter.class)),
-                get(ExternalModuleIvyDependencyDescriptorFactory.class));
-        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactory);
-        return dependencyDescriptorFactory;
-    }
-
-    protected DependencyFactory createDependencyFactory() {
-        Instantiator instantiator = get(Instantiator.class);
-
-        DefaultProjectDependencyFactory factory = new DefaultProjectDependencyFactory(
-                get(ProjectAccessListener.class), instantiator, get(StartParameter.class).isBuildProjectDependencies());
-
-        ProjectDependencyFactory projectDependencyFactory = new ProjectDependencyFactory(factory);
-        DependencyProjectNotationParser projParser = new DependencyProjectNotationParser(factory);
-
-        NotationParser<? extends Dependency> moduleMapParser = new DependencyMapNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
-        NotationParser<? extends Dependency> moduleStringParser = new DependencyStringNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
-        NotationParser<? extends Dependency> selfResolvingDependencyFactory = new DependencyFilesNotationParser(instantiator);
-
-        List<NotationParser<? extends Dependency>> notationParsers = WrapUtil.toList(
-                moduleStringParser,
-                moduleMapParser,
-                selfResolvingDependencyFactory,
-                projParser,
-                new DependencyClassPathNotationParser(instantiator, get(ClassPathRegistry.class), new IdentityFileResolver()));
-
-        return new DefaultDependencyFactory(
-                new DependencyNotationParser(notationParsers),
-                new ClientModuleNotationParserFactory(instantiator).create(),
-                projectDependencyFactory);
-    }
-
-    protected CacheLockingManager createCacheLockingManager() {
-        return new DefaultCacheLockingManager(
-                get(CacheRepository.class)
-        );
-    }
-
-    protected BuildCommencedTimeProvider createBuildTimeProvider() {
-        return new BuildCommencedTimeProvider();
-    }
-
-    protected ModuleResolutionCache createModuleResolutionCache() {
-        return new SingleFileBackedModuleResolutionCache(
-                get(ArtifactCacheMetaData.class),
-                get(BuildCommencedTimeProvider.class),
-                get(CacheLockingManager.class)
-        );
-    }
-
-    protected ModuleDescriptorCache createModuleDescriptorCache() {
-        return new DefaultModuleDescriptorCache(
-                get(ArtifactCacheMetaData.class),
-                get(BuildCommencedTimeProvider.class),
-                get(CacheLockingManager.class)
-        );
-    }
-
-    protected ArtifactAtRepositoryCachedArtifactIndex createArtifactAtRepositoryCachedResolutionIndex() {
-        return new ArtifactAtRepositoryCachedArtifactIndex(new File(get(ArtifactCacheMetaData.class).getCacheDir(), "artifact-at-repository.bin"),
-                get(BuildCommencedTimeProvider.class),
-                get(CacheLockingManager.class)
-        );
-    }
-
-    protected ByUrlCachedExternalResourceIndex createArtifactUrlCachedResolutionIndex() {
-        return new ByUrlCachedExternalResourceIndex(
-                new File(get(ArtifactCacheMetaData.class).getCacheDir(), "artifact-at-url.bin"),
-                get(BuildCommencedTimeProvider.class),
-                get(CacheLockingManager.class)
-        );
-    }
-
-    protected PathKeyFileStore createUniquePathFileStore() {
-        return new UniquePathKeyFileStore(new File(get(ArtifactCacheMetaData.class).getCacheDir(), "filestore"));
-    }
-
-    protected ArtifactRevisionIdFileStore createArtifactRevisionIdFileStore() {
-        return new ArtifactRevisionIdFileStore(get(PathKeyFileStore.class), new TmpDirTemporaryFileProvider());
-    }
-
-    protected SettingsConverter createSettingsConverter() {
-        return new DefaultSettingsConverter(
-                new IvySettingsFactory(
-                        get(ArtifactCacheMetaData.class)
-                )
-        );
-    }
-
-    protected IvyFactory createIvyFactory() {
-        return new DefaultIvyFactory();
-    }
-
-    protected MavenSettingsProvider createMavenSettingsProvider() {
-        return new DefaultMavenSettingsProvider(new DefaultMavenFileLocations());
-    }
-
-    protected LocalMavenRepositoryLocator createLocalMavenRepositoryLocator() {
-        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(LocalFileRepositoryCacheManager.class),
-                get(DownloadingRepositoryCacheManager.class),
-                new TmpDirTemporaryFileProvider(),
-                get(ByUrlCachedExternalResourceIndex.class)
-        );
-    }
-
-    protected ResolveIvyFactory createResolveIvyFactory() {
-        StartParameter startParameter = get(StartParameter.class);
-        StartParameterResolutionOverride startParameterResolutionOverride = new StartParameterResolutionOverride(startParameter);
-        return new ResolveIvyFactory(
-                get(IvyFactory.class),
-                get(SettingsConverter.class),
-                get(ModuleResolutionCache.class),
-                get(ModuleDescriptorCache.class),
-                get(ArtifactAtRepositoryCachedArtifactIndex.class),
-                get(CacheLockingManager.class),
-                startParameterResolutionOverride,
-                get(BuildCommencedTimeProvider.class));
-    }
-
-    protected ArtifactDependencyResolver createArtifactDependencyResolver() {
-        ArtifactDependencyResolver resolver = new DefaultDependencyResolver(
-                get(ResolveIvyFactory.class),
-                get(PublishModuleDescriptorConverter.class),
-                new ResolvedArtifactFactory(
-                        get(CacheLockingManager.class)
-                ),
-                new DefaultProjectModuleRegistry(
-                        get(PublishModuleDescriptorConverter.class)),
-                get(ProjectAccessListener.class),
-                get(CacheLockingManager.class)
-        );
-        return new ErrorHandlingArtifactDependencyResolver(
-                new ShortcircuitEmptyConfigsArtifactDependencyResolver(
-                        new SelfResolvingDependencyResolver(
-                                new CacheLockingArtifactDependencyResolver(
-                                        get(CacheLockingManager.class),
-                                        resolver))));
-    }
+        this.parent = parent;
+    }
+
+    public DependencyResolutionServices create(FileResolver fileResolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
+        DefaultServiceRegistry services = new DefaultServiceRegistry(parent);
+        services.add(FileResolver.class, fileResolver);
+        services.add(DependencyMetaDataProvider.class, dependencyMetaDataProvider);
+        services.add(ProjectFinder.class, projectFinder);
+        services.add(DomainObjectContext.class, domainObjectContext);
+        services.addProvider(new DependencyResolutionScopeServices());
+        return services.get(DependencyResolutionServices.class);
+    }
+
+    public void addDslServices(ServiceRegistration registration) {
+        registration.addProvider(new DependencyResolutionScopeServices());
+    }
+
+    private static class DependencyResolutionScopeServices {
+        BaseRepositoryFactory createBaseRepositoryFactory(LocalMavenRepositoryLocator localMavenRepositoryLocator, Instantiator instantiator, FileResolver fileResolver,
+                                                          RepositoryTransportFactory repositoryTransportFactory, LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                                                          LegacyDependencyResolverRepositoryFactory legacyDependencyResolverRepositoryFactory,
+                                                          ResolverStrategy resolverStrategy) {
+            return new DefaultBaseRepositoryFactory(
+                    localMavenRepositoryLocator,
+                    fileResolver,
+                    instantiator,
+                    repositoryTransportFactory,
+                    locallyAvailableResourceFinder,
+                    legacyDependencyResolverRepositoryFactory,
+                    resolverStrategy
+            );
+        }
 
-    private class DefaultDependencyResolutionServices implements DependencyResolutionServices {
-        private final ServiceRegistry parent;
-        private final FileResolver fileResolver;
-        private final DependencyMetaDataProvider dependencyMetaDataProvider;
-        private final ProjectFinder projectFinder;
-        private final DomainObjectContext domainObjectContext;
-        private DefaultRepositoryHandler repositoryHandler;
-        private ConfigurationContainerInternal configurationContainer;
-        private DependencyHandler dependencyHandler;
-        private DefaultArtifactHandler artifactHandler;
-        private BaseRepositoryFactory baseRepositoryFactory;
+        RepositoryHandler createRepositoryHandler(Instantiator instantiator, BaseRepositoryFactory baseRepositoryFactory) {
+            return instantiator.newInstance(DefaultRepositoryHandler.class, baseRepositoryFactory, instantiator);
+        }
 
-        private DefaultDependencyResolutionServices(ServiceRegistry parent, FileResolver fileResolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
-            this.parent = parent;
-            this.fileResolver = fileResolver;
-            this.dependencyMetaDataProvider = dependencyMetaDataProvider;
-            this.projectFinder = projectFinder;
-            this.domainObjectContext = domainObjectContext;
+        ConfigurationContainerInternal createConfigurationContainer(Instantiator instantiator, ConfigurationResolver configurationResolver, DomainObjectContext domainObjectContext,
+                                                                    ListenerManager listenerManager, DependencyMetaDataProvider metaDataProvider) {
+            return instantiator.newInstance(DefaultConfigurationContainer.class,
+                    configurationResolver,
+                    instantiator,
+                    domainObjectContext,
+                    listenerManager,
+                    metaDataProvider);
         }
 
-        public DefaultRepositoryHandler getResolveRepositoryHandler() {
-            if (repositoryHandler == null) {
-                repositoryHandler = createRepositoryHandler();
-            }
-            return repositoryHandler;
+        DependencyHandler createDependencyHandler(Instantiator instantiator, ConfigurationContainerInternal configurationContainer, DependencyFactory dependencyFactory,
+                                                  ProjectFinder projectFinder, ComponentMetadataHandler componentMetadataHandler, ArtifactResolutionQueryFactory resolutionQueryFactory) {
+            return instantiator.newInstance(DefaultDependencyHandler.class,
+                    configurationContainer,
+                    dependencyFactory,
+                    projectFinder,
+                    componentMetadataHandler,
+                    resolutionQueryFactory);
         }
 
-        public BaseRepositoryFactory getBaseRepositoryFactory() {
-            if (baseRepositoryFactory == null) {
-                Instantiator instantiator = parent.get(Instantiator.class);
-                //noinspection unchecked
-                baseRepositoryFactory = new DefaultBaseRepositoryFactory(
-                        get(LocalMavenRepositoryLocator.class),
-                        fileResolver,
-                        instantiator,
-                        get(RepositoryTransportFactory.class),
-                        get(LocallyAvailableResourceFinder.class),
-                        get(ProgressLoggerFactory.class),
-                        get(LocalFileRepositoryCacheManager.class),
-                        get(DownloadingRepositoryCacheManager.class)
-                );
-            }
+        DefaultComponentMetadataHandler createComponentMetadataHandler(Instantiator instantiator) {
+            return instantiator.newInstance(DefaultComponentMetadataHandler.class, instantiator);
+        }
 
-            return baseRepositoryFactory;
+        ArtifactHandler createArtifactHandler(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, ConfigurationContainerInternal configurationContainer) {
+            NotationParser<Object, PublishArtifact> publishArtifactNotationParser = new PublishArtifactNotationParserFactory(instantiator, dependencyMetaDataProvider).create();
+            return new DefaultArtifactHandler(configurationContainer, publishArtifactNotationParser);
         }
 
-        private DefaultRepositoryHandler createRepositoryHandler() {
-            Instantiator instantiator = parent.get(Instantiator.class);
-            return instantiator.newInstance(DefaultRepositoryHandler.class, getBaseRepositoryFactory(), instantiator);
+        ConfigurationResolver createDependencyResolver(ArtifactDependencyResolver artifactDependencyResolver, RepositoryHandler repositories,
+                                                       ModuleMetadataProcessor metadataProcessor) {
+            return new DefaultConfigurationResolver(artifactDependencyResolver, repositories, metadataProcessor);
         }
 
-        public ConfigurationContainerInternal getConfigurationContainer() {
-            if (configurationContainer == null) {
-                final Instantiator instantiator = parent.get(Instantiator.class);
-                ConfigurationResolver resolver = createDependencyResolver(getResolveRepositoryHandler());
-                configurationContainer = instantiator.newInstance(DefaultConfigurationContainer.class,
-                        resolver, instantiator, domainObjectContext, parent.get(ListenerManager.class),
-                        dependencyMetaDataProvider);
-            }
-            return configurationContainer;
+        ArtifactPublicationServices createArtifactPublicationServices(ServiceRegistry services) {
+            return new DefaultArtifactPublicationServices(services);
         }
 
-        public DependencyHandler getDependencyHandler() {
-            if (dependencyHandler == null) {
-                dependencyHandler = new DefaultDependencyHandler(getConfigurationContainer(), parent.get(DependencyFactory.class), projectFinder);
-            }
-            return dependencyHandler;
+        DependencyResolutionServices createDependencyResolutionServices(ServiceRegistry services) {
+            return new DefaultDependencyResolutionServices(services);
         }
 
-        public ArtifactHandler getArtifactHandler() {
-            if (artifactHandler == null) {
-                NotationParser<PublishArtifact> publishArtifactNotationParser = new PublishArtifactNotationParserFactory(get(Instantiator.class), dependencyMetaDataProvider).create();
-                artifactHandler = new DefaultArtifactHandler(getConfigurationContainer(), publishArtifactNotationParser);
-            }
-            return artifactHandler;
+        ArtifactResolutionQueryFactory createArtifactResolutionQueryFactory(ConfigurationContainerInternal configurationContainer, RepositoryHandler repositoryHandler,
+                                                                            ResolveIvyFactory ivyFactory, ModuleMetadataProcessor metadataProcessor,
+                                                                            CacheLockingManager cacheLockingManager) {
+            return new DefaultArtifactResolutionQueryFactory(configurationContainer, repositoryHandler, ivyFactory, metadataProcessor, cacheLockingManager);
+
         }
+    }
+
+    private static class DefaultDependencyResolutionServices implements DependencyResolutionServices {
+        private final ServiceRegistry services;
 
-        public ArtifactPublicationServices createArtifactPublicationServices() {
-                return new DefaultArtifactPublicationServices(DefaultDependencyResolutionServices.this);
+        private DefaultDependencyResolutionServices(ServiceRegistry services) {
+            this.services = services;
         }
 
-        ConfigurationResolver createDependencyResolver(DefaultRepositoryHandler repositories) {
-            return new DefaultConfigurationResolver(
-                    get(ArtifactDependencyResolver.class),
-                    repositories);
+        public RepositoryHandler getResolveRepositoryHandler() {
+            return services.get(RepositoryHandler.class);
         }
 
-        ArtifactPublisher createArtifactPublisher() {
-            return new IvyBackedArtifactPublisher(
-                    get(SettingsConverter.class),
-                    get(PublishModuleDescriptorConverter.class),
-                    get(IvyFactory.class),
-                    new DefaultIvyDependencyPublisher()
-            );
+        public ConfigurationContainerInternal getConfigurationContainer() {
+            return services.get(ConfigurationContainerInternal.class);
         }
 
+        public DependencyHandler getDependencyHandler() {
+            return services.get(DependencyHandler.class);
+        }
     }
 
     private static class DefaultArtifactPublicationServices implements ArtifactPublicationServices {
-        private final DefaultDependencyResolutionServices dependencyResolutionServices;
-
-        public DefaultArtifactPublicationServices(DefaultDependencyResolutionServices dependencyResolutionServices) {
-            this.dependencyResolutionServices = dependencyResolutionServices;
-        }
+        private final ServiceRegistry services;
 
-        public DefaultRepositoryHandler createRepositoryHandler() {
-            return dependencyResolutionServices.createRepositoryHandler();
+        public DefaultArtifactPublicationServices(ServiceRegistry services) {
+            this.services = services;
         }
 
-        public ModuleDescriptorConverter getDescriptorFileModuleConverter() {
-            return new PublishModuleDescriptorConverter(
-                    dependencyResolutionServices.parent.get(ResolveModuleDescriptorConverter.class),
-                    new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
-        }
-
-        public IvyModuleDescriptorWriter getIvyModuleDescriptorWriter() {
-            return new IvyXmlModuleDescriptorWriter();
+        public RepositoryHandler createRepositoryHandler() {
+            Instantiator instantiator = services.get(Instantiator.class);
+            BaseRepositoryFactory baseRepositoryFactory = services.get(BaseRepositoryFactory.class);
+            return instantiator.newInstance(DefaultRepositoryHandler.class, baseRepositoryFactory, instantiator);
         }
 
         public ArtifactPublisher createArtifactPublisher() {
-            return dependencyResolutionServices.createArtifactPublisher();
+            return new IvyBackedArtifactPublisher(
+                    services.get(PublishLocalComponentFactory.class),
+                    services.get(IvyContextManager.class),
+                    new DefaultIvyDependencyPublisher(),
+                    new IvyXmlModuleDescriptorWriter()
+            );
         }
     }
-
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
index 346f2be..5a78e91 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
@@ -15,9 +15,11 @@
  */
 package org.gradle.api.internal.artifacts;
 
+import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.ModuleIdentifier;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 
 public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier {
 
@@ -32,6 +34,11 @@ public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier {
         this.version = version;
     }
 
+    public DefaultModuleVersionIdentifier(ModuleIdentifier id, String version) {
+        this.id = new DefaultModuleIdentifier(id.getGroup(), id.getName());
+        this.version = version;
+    }
+
     public String getGroup() {
         return id.getGroup();
     }
@@ -83,4 +90,12 @@ public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier {
     public static ModuleVersionIdentifier newId(String group, String name, String version) {
         return new DefaultModuleVersionIdentifier(group, name, version);
     }
+
+    public static ModuleVersionIdentifier newId(ModuleRevisionId moduleRevisionId) {
+        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    }
+
+    public static ModuleVersionIdentifier newId(ModuleComponentIdentifier componentId) {
+        return new DefaultModuleVersionIdentifier(componentId.getGroup(), componentId.getModule(), componentId.getVersion());
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
index 17af643..b285c6f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
@@ -16,12 +16,10 @@
 
 package org.gradle.api.internal.artifacts;
 
+import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 
-/**
- * by Szczepan Faber, created at: 11/13/11
- */
 public class DefaultModuleVersionSelector implements ModuleVersionSelector {
 
     private String group;
@@ -105,4 +103,8 @@ public class DefaultModuleVersionSelector implements ModuleVersionSelector {
     public static ModuleVersionSelector newSelector(String group, String name, String version) {
         return new DefaultModuleVersionSelector(group, name, version);
     }
+
+    public static ModuleVersionSelector newSelector(ModuleRevisionId module) {
+        return newSelector(module.getOrganisation(), module.getName(), module.getRevision());
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java
index bf9301b..62af822 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java
@@ -23,9 +23,6 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.reflect.Instantiator;
 
-/**
- * by Szczepan Faber, created at: 2/5/13
- */
 public class DefaultProjectDependencyFactory {
     private final ProjectAccessListener projectAccessListener;
     private final Instantiator instantiator;
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 02cea03..70e0814 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
@@ -15,56 +15,51 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.artifacts.ResolvedDependency;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.metadata.IvyArtifactName;
 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 Map<String, String> extraAttributes;
-    private final String name;
-    private final String type;
-    private final String ext;
+    private final ResolvedModuleVersion owner;
+    private final IvyArtifactName artifact;
+    private long id;
+    private final Factory<ResolvedDependency> ownerSource;
     private Factory<File> artifactSource;
     private File file;
 
-    public DefaultResolvedArtifact(ResolvedDependency resolvedDependency, Artifact artifact, Factory<File> artifactSource) {
-        this.resolvedDependency = resolvedDependency;
-        // 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());
+    public DefaultResolvedArtifact(ResolvedModuleVersion owner, Factory<ResolvedDependency> ownerSource, IvyArtifactName artifact, Factory<File> artifactSource, long id) {
+        this.ownerSource = ownerSource;
+        this.owner = owner;
+        this.artifact = artifact;
+        this.id = id;
         this.artifactSource = artifactSource;
     }
 
+    public long getId() {
+        return id;
+    }
+
     public ResolvedDependency getResolvedDependency() {
         DeprecationLogger.nagUserOfDeprecated(
                 "ResolvedArtifact.getResolvedDependency()",
                 "For version info use ResolvedArtifact.getModuleVersion(), to access the dependency graph use ResolvedConfiguration.getFirstLevelModuleDependencies()"
         );
-        return resolvedDependency;
+        //resolvedDependency is expensive so lazily create it
+        return ownerSource.create();
     }
 
     public ResolvedModuleVersion getModuleVersion() {
-        return resolvedDependency.getModule();
+        return owner;
     }
 
     @Override
     public String toString() {
-        return String.format("[ResolvedArtifact dependency:%s name:%s classifier:%s extension:%s type:%s]", resolvedDependency, getName(), getClassifier(), getExtension(), getType());
+        return String.format("[ResolvedArtifact dependency:%s name:%s classifier:%s extension:%s type:%s]", owner, getName(), getClassifier(), getExtension(), getType());
     }
 
     @Override
@@ -76,19 +71,10 @@ public class DefaultResolvedArtifact implements ResolvedArtifact {
             return false;
         }
         DefaultResolvedArtifact other = (DefaultResolvedArtifact) obj;
-        if (!other.resolvedDependency.getModule().getId().equals(resolvedDependency.getModule().getId())) {
-            return false;
-        }
-        if (!other.getName().equals(getName())) {
-            return false;
-        }
-        if (!other.getType().equals(getType())) {
-            return false;
-        }
-        if (!other.getExtension().equals(getExtension())) {
+        if (!other.owner.getId().equals(owner.getId())) {
             return false;
         }
-        if (!other.extraAttributes.equals(extraAttributes)) {
+        if (!other.artifact.equals(artifact)) {
             return false;
         }
         return true;
@@ -96,23 +82,23 @@ public class DefaultResolvedArtifact implements ResolvedArtifact {
 
     @Override
     public int hashCode() {
-        return resolvedDependency.getModule().getId().hashCode() ^ getName().hashCode() ^ getType().hashCode() ^ getExtension().hashCode() ^ extraAttributes.hashCode();
+        return owner.getId().hashCode() ^ getName().hashCode() ^ getType().hashCode() ^ getExtension().hashCode() ^ artifact.hashCode();
     }
 
     public String getName() {
-        return name;
+        return artifact.getName();
     }
 
     public String getType() {
-        return type;
+        return artifact.getType();
     }
 
     public String getExtension() {
-        return ext;
+        return artifact.getExtension();
     }
 
     public String getClassifier() {
-        return extraAttributes.get(Dependency.CLASSIFIER);
+        return artifact.getClassifier();
     }
     
     public File getFile() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
index 3c8d725..fd83484 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
@@ -25,9 +25,6 @@ import org.gradle.api.artifacts.ResolvedModuleVersion;
 
 import java.util.*;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultResolvedDependency implements ResolvedDependency {
     private final Set<ResolvedDependency> children = new LinkedHashSet<ResolvedDependency>();
     private final Set<ResolvedDependency> parents = new LinkedHashSet<ResolvedDependency>();
@@ -38,16 +35,12 @@ public class DefaultResolvedDependency implements ResolvedDependency {
     private final Map<ResolvedDependency, Set<ResolvedArtifact>> allArtifactsCache = new HashMap<ResolvedDependency, Set<ResolvedArtifact>>();
     private Set<ResolvedArtifact> allModuleArtifactsCache;
 
-    public DefaultResolvedDependency(String name, String moduleGroup, String moduleName, String moduleVersion, String configuration) {
-        this.name = name;
-        id = new ResolvedConfigurationIdentifier(moduleGroup, moduleName, moduleVersion, configuration);
+    public DefaultResolvedDependency(ModuleVersionIdentifier moduleVersionIdentifier, String configuration) {
+        this.name = String.format("%s:%s:%s", moduleVersionIdentifier.getGroup(), moduleVersionIdentifier.getName(), moduleVersionIdentifier.getVersion());
+        id = new ResolvedConfigurationIdentifier(moduleVersionIdentifier, configuration);
         this.moduleArtifacts = new TreeSet<ResolvedArtifact>(new ResolvedArtifactComparator());
     }
 
-    public DefaultResolvedDependency(String moduleGroup, String moduleName, String moduleVersion, String configuration) {
-        this(moduleGroup + ":" + moduleName + ":" + moduleVersion, moduleGroup, moduleName, moduleVersion, configuration);
-    }
-
     public String getName() {
         return name;
     }
@@ -75,7 +68,7 @@ public class DefaultResolvedDependency implements ResolvedDependency {
     public ResolvedModuleVersion getModule() {
         return new ResolvedModuleVersion() {
             public ModuleVersionIdentifier getId() {
-                return new DefaultModuleVersionIdentifier(id.getModuleGroup(), id.getModuleName(), id.getModuleVersion());
+                return id.getId();
             }
         };
     }
@@ -102,7 +95,7 @@ public class DefaultResolvedDependency implements ResolvedDependency {
 
     public Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent) {
         if (!parents.contains(parent)) {
-            throw new InvalidUserDataException("Unknown Parent");
+            throw new InvalidUserDataException("Provided dependency (" + parent + ") must be a parent of: " + this);
         }
         Set<ResolvedArtifact> artifacts = parentArtifacts.get(parent);
         return artifacts == null ? Collections.<ResolvedArtifact>emptySet() : artifacts;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java
new file mode 100644
index 0000000..3e7ca5e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.StartParameter;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleVersionsCache;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.SingleFileBackedModuleVersionsCache;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.StartParameterResolutionOverride;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.memcache.InMemoryDependencyMetadataCache;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestVersionStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.DefaultModuleArtifactsCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.DefaultModuleMetaDataCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleArtifactsCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetaDataCache;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.PublishLocalComponentFactory;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectComponentRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectPublicationRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.ResolutionResultsStoreFactory;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.mvnsettings.*;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.DownloadingRepositoryArtifactCache;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryArtifactCache;
+import org.gradle.api.internal.artifacts.repositories.legacy.CustomIvyResolverRepositoryFactory;
+import org.gradle.api.internal.artifacts.repositories.legacy.DownloadingRepositoryCacheManager;
+import org.gradle.api.internal.artifacts.repositories.legacy.LegacyDependencyResolverRepositoryFactory;
+import org.gradle.api.internal.artifacts.repositories.legacy.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.ArtifactAtRepositoryCachedArtifactIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.local.ivy.LocallyAvailableResourceFinderFactory;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
+import org.gradle.api.internal.filestore.UniquePathKeyFileStore;
+import org.gradle.api.internal.filestore.ivy.ArtifactIdentifierFileStore;
+import org.gradle.api.internal.notations.*;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectRegistry;
+import org.gradle.cache.CacheRepository;
+import org.gradle.initialization.ProjectAccessListener;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.BuildCommencedTimeProvider;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The set of dependency management services that are created per build.
+ */
+class DependencyManagementBuildScopeServices {
+    InMemoryDependencyMetadataCache createInMemoryDependencyMetadataCache() {
+        return new InMemoryDependencyMetadataCache();
+    }
+
+    DependencyManagementServices createDependencyManagementServices(ServiceRegistry parent) {
+        return new DefaultDependencyManagementServices(parent);
+    }
+
+    DependencyFactory createDependencyFactory(Instantiator instantiator,
+                                              ProjectAccessListener projectAccessListener,
+                                              StartParameter startParameter,
+                                              ClassPathRegistry classPathRegistry,
+                                              FileLookup fileLookup) {
+        DefaultProjectDependencyFactory factory = new DefaultProjectDependencyFactory(
+                projectAccessListener, instantiator, startParameter.isBuildProjectDependencies());
+
+        ProjectDependencyFactory projectDependencyFactory = new ProjectDependencyFactory(factory);
+        DependencyProjectNotationParser projParser = new DependencyProjectNotationParser(factory);
+
+        NotationParser<Object, ? extends Dependency> moduleMapParser = new DependencyMapNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
+        NotationParser<Object, ? extends Dependency> moduleStringParser = new DependencyStringNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
+        NotationParser<Object, ? extends Dependency> selfResolvingDependencyFactory = new DependencyFilesNotationParser(instantiator);
+
+        List<NotationParser<Object, ? extends Dependency>> notationParsers = Arrays.asList(
+                moduleStringParser,
+                moduleMapParser,
+                selfResolvingDependencyFactory,
+                projParser,
+                new DependencyClassPathNotationParser(instantiator, classPathRegistry, fileLookup.getFileResolver()));
+
+        return new DefaultDependencyFactory(
+                new DependencyNotationParser(notationParsers),
+                new ClientModuleNotationParserFactory(instantiator).create(),
+                projectDependencyFactory);
+    }
+
+    CacheLockingManager createCacheLockingManager(CacheRepository cacheRepository) {
+        return new DefaultCacheLockingManager(cacheRepository);
+    }
+
+    BuildCommencedTimeProvider createBuildTimeProvider() {
+        return new BuildCommencedTimeProvider();
+    }
+
+    ModuleVersionsCache createModuleVersionsCache(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        return new SingleFileBackedModuleVersionsCache(
+                timeProvider,
+                cacheLockingManager
+        );
+    }
+
+    ModuleArtifactsCache createModuleArtifactsCache(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        return new DefaultModuleArtifactsCache(
+                timeProvider,
+                cacheLockingManager
+        );
+    }
+
+    ModuleMetaDataCache createModuleDescriptorCache(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager, ResolverStrategy resolverStrategy) {
+        return new DefaultModuleMetaDataCache(
+                timeProvider,
+                cacheLockingManager,
+                resolverStrategy
+        );
+    }
+
+    ArtifactAtRepositoryCachedArtifactIndex createArtifactAtRepositoryCachedResolutionIndex(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        return new ArtifactAtRepositoryCachedArtifactIndex(
+                "artifact-at-repository",
+                timeProvider,
+                cacheLockingManager
+        );
+    }
+
+    ByUrlCachedExternalResourceIndex createArtifactUrlCachedResolutionIndex(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        return new ByUrlCachedExternalResourceIndex(
+                "artifact-at-url",
+                timeProvider,
+                cacheLockingManager
+        );
+    }
+
+    ArtifactIdentifierFileStore createArtifactRevisionIdFileStore(CacheLockingManager cacheLockingManager) {
+        return new ArtifactIdentifierFileStore(new UniquePathKeyFileStore(cacheLockingManager.getFileStoreDirectory()), new TmpDirTemporaryFileProvider());
+    }
+
+    MavenSettingsProvider createMavenSettingsProvider() {
+        return new DefaultMavenSettingsProvider(new DefaultMavenFileLocations());
+    }
+
+    LocalMavenRepositoryLocator createLocalMavenRepositoryLocator(MavenSettingsProvider mavenSettingsProvider) {
+        return new DefaultLocalMavenRepositoryLocator(mavenSettingsProvider, SystemProperties.asMap(), System.getenv());
+    }
+
+    LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> createArtifactRevisionIdLocallyAvailableResourceFinder(ArtifactCacheMetaData artifactCacheMetaData, LocalMavenRepositoryLocator localMavenRepositoryLocator, ArtifactIdentifierFileStore fileStore) {
+        LocallyAvailableResourceFinderFactory finderFactory = new LocallyAvailableResourceFinderFactory(
+                artifactCacheMetaData,
+                localMavenRepositoryLocator,
+                fileStore);
+        return finderFactory.create();
+    }
+
+    ResolverStrategy createResolverStrategy() {
+        return new ResolverStrategy();
+    }
+
+    VersionMatcher createVersionMatcher(ResolverStrategy resolverStrategy) {
+        return resolverStrategy.getVersionMatcher();
+    }
+
+    LatestStrategy createLatestStrategy(VersionMatcher versionMatcher) {
+        return new LatestVersionStrategy(versionMatcher);
+    }
+
+    LocalFileRepositoryArtifactCache createLocalRepositoryArtifactCache() {
+        return new LocalFileRepositoryArtifactCache();
+    }
+
+    DownloadingRepositoryArtifactCache createDownloadingRepositoryArtifactCache(ArtifactIdentifierFileStore artifactIdentifierFileStore, ByUrlCachedExternalResourceIndex externalResourceIndex,
+                                                                                TemporaryFileProvider temporaryFileProvider, CacheLockingManager cacheLockingManager) {
+        return new DownloadingRepositoryArtifactCache(artifactIdentifierFileStore,
+                externalResourceIndex,
+                temporaryFileProvider,
+                cacheLockingManager);
+    }
+
+    RepositoryTransportFactory createRepositoryTransportFactory(ProgressLoggerFactory progressLoggerFactory, LocalFileRepositoryArtifactCache localFileRepositoryArtifactCache,
+                                                                DownloadingRepositoryArtifactCache downloadingRepositoryArtifactCache, TemporaryFileProvider temporaryFileProvider,
+                                                                ByUrlCachedExternalResourceIndex externalResourceIndex, BuildCommencedTimeProvider buildCommencedTimeProvider) {
+        return new RepositoryTransportFactory(
+                progressLoggerFactory,
+                localFileRepositoryArtifactCache,
+                downloadingRepositoryArtifactCache,
+                temporaryFileProvider,
+                externalResourceIndex,
+                buildCommencedTimeProvider
+        );
+    }
+
+    LegacyDependencyResolverRepositoryFactory createCustomerResolverRepositoryFactory(ProgressLoggerFactory progressLoggerFactory, ArtifactIdentifierFileStore artifactIdentifierFileStore,
+                                                                                      TemporaryFileProvider temporaryFileProvider, CacheLockingManager cacheLockingManager) {
+        return new CustomIvyResolverRepositoryFactory(
+                progressLoggerFactory,
+                new LocalFileRepositoryCacheManager("local"),
+                new DownloadingRepositoryCacheManager(
+                        "downloading",
+                        artifactIdentifierFileStore,
+                        temporaryFileProvider,
+                        cacheLockingManager
+                )
+        );
+    }
+
+    ResolveIvyFactory createResolveIvyFactory(StartParameter startParameter, ModuleVersionsCache moduleVersionsCache, ModuleMetaDataCache moduleMetaDataCache, ModuleArtifactsCache moduleArtifactsCache,
+                                              ArtifactAtRepositoryCachedArtifactIndex artifactAtRepositoryCachedArtifactIndex, CacheLockingManager cacheLockingManager,
+                                              BuildCommencedTimeProvider buildCommencedTimeProvider, InMemoryDependencyMetadataCache inMemoryDependencyMetadataCache,
+                                              VersionMatcher versionMatcher, LatestStrategy latestStrategy) {
+        StartParameterResolutionOverride startParameterResolutionOverride = new StartParameterResolutionOverride(startParameter);
+        return new ResolveIvyFactory(
+                moduleVersionsCache,
+                moduleMetaDataCache,
+                moduleArtifactsCache,
+                artifactAtRepositoryCachedArtifactIndex,
+                cacheLockingManager,
+                startParameterResolutionOverride,
+                buildCommencedTimeProvider,
+                inMemoryDependencyMetadataCache,
+                versionMatcher,
+                latestStrategy);
+    }
+
+    ArtifactDependencyResolver createArtifactDependencyResolver(ResolveIvyFactory resolveIvyFactory, PublishLocalComponentFactory publishModuleDescriptorConverter,
+                                                                CacheLockingManager cacheLockingManager, IvyContextManager ivyContextManager, ResolutionResultsStoreFactory resolutionResultsStoreFactory,
+                                                                VersionMatcher versionMatcher, LatestStrategy latestStrategy, ProjectRegistry<ProjectInternal> projectRegistry,
+                                                                ComponentIdentifierFactory componentIdentifierFactory) {
+        ArtifactDependencyResolver resolver = new DefaultDependencyResolver(
+                resolveIvyFactory,
+                publishModuleDescriptorConverter,
+                new DefaultProjectComponentRegistry(
+                        publishModuleDescriptorConverter,
+                        projectRegistry),
+                cacheLockingManager,
+                ivyContextManager,
+                resolutionResultsStoreFactory,
+                versionMatcher,
+                latestStrategy);
+        return new ErrorHandlingArtifactDependencyResolver(
+                new ShortcircuitEmptyConfigsArtifactDependencyResolver(
+                        new SelfResolvingDependencyResolver(
+                                new CacheLockingArtifactDependencyResolver(
+                                        cacheLockingManager,
+                                        resolver)),
+                        componentIdentifierFactory));
+    }
+
+    ResolutionResultsStoreFactory createResolutionResultsStoreFactory(TemporaryFileProvider temporaryFileProvider) {
+        return new ResolutionResultsStoreFactory(temporaryFileProvider);
+    }
+
+    ProjectPublicationRegistry createProjectPublicationRegistry() {
+        return new DefaultProjectPublicationRegistry();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java
new file mode 100644
index 0000000..77d9f07
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentIdentifierFactory;
+import org.gradle.api.internal.artifacts.component.DefaultComponentIdentifierFactory;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyContextManager;
+import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.*;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.*;
+
+class DependencyManagementGlobalScopeServices {
+    IvyContextManager createIvyContextManager() {
+        return new DefaultIvyContextManager();
+    }
+
+    ModuleDescriptorFactory createModuleDescriptorFactory() {
+        return new DefaultModuleDescriptorFactory();
+    }
+
+    ExcludeRuleConverter createExcludeRuleConverter() {
+        return new DefaultExcludeRuleConverter();
+    }
+
+    ComponentIdentifierFactory createComponentIdentifierFactory() {
+        return new DefaultComponentIdentifierFactory();
+    }
+
+    ExternalModuleIvyDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        return new ExternalModuleIvyDependencyDescriptorFactory(excludeRuleConverter);
+    }
+
+    ConfigurationsToModuleDescriptorConverter createConfigurationsToModuleDescriptorConverter() {
+        return new DefaultConfigurationsToModuleDescriptorConverter();
+    }
+
+    DependencyDescriptorFactory createDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ExternalModuleIvyDependencyDescriptorFactory descriptorFactory) {
+        DefaultClientModuleMetaDataFactory clientModuleDescriptorFactory = new DefaultClientModuleMetaDataFactory();
+        DependencyDescriptorFactory dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
+                new ClientModuleIvyDependencyDescriptorFactory(
+                        excludeRuleConverter,
+                        clientModuleDescriptorFactory
+                ),
+                new ProjectIvyDependencyDescriptorFactory(
+                        excludeRuleConverter),
+                descriptorFactory);
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactory);
+        return dependencyDescriptorFactory;
+    }
+
+    ResolveLocalComponentFactory createResolveModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
+                                                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
+                                                                            DependencyDescriptorFactory dependencyDescriptorFactory,
+                                                                            ExcludeRuleConverter excludeRuleConverter,
+                                                                            ComponentIdentifierFactory componentIdentifierFactory) {
+        return new ResolveLocalComponentFactory(
+                moduleDescriptorFactory,
+                configurationsToModuleDescriptorConverter,
+                new DefaultDependenciesToModuleDescriptorConverter(
+                        dependencyDescriptorFactory,
+                        excludeRuleConverter),
+                componentIdentifierFactory);
+
+    }
+
+    PublishLocalComponentFactory createPublishModuleDescriptorConverter(ResolveLocalComponentFactory moduleDescriptorConverter) {
+        return new PublishLocalComponentFactory(
+                moduleDescriptorConverter,
+                new DefaultConfigurationsToArtifactsConverter());
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyServices.java
new file mode 100644
index 0000000..a78f6b2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DependencyServices.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.PluginServiceRegistry;
+
+public class DependencyServices implements PluginServiceRegistry {
+    public void registerGlobalServices(ServiceRegistration registration) {
+        registration.addProvider(new DependencyManagementGlobalScopeServices());
+    }
+
+    public void registerBuildServices(ServiceRegistration registration) {
+        registration.addProvider(new DependencyManagementBuildScopeServices());
+    }
+
+    public void registerProjectServices(ServiceRegistration registration) {
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleMetadataProcessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleMetadataProcessor.java
new file mode 100644
index 0000000..3512dc5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleMetadataProcessor.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.metadata.ModuleVersionMetaData;
+
+public interface ModuleMetadataProcessor {
+    void process(ModuleVersionMetaData metadata);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java
index f27db28..4cd8137 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java
@@ -17,25 +17,23 @@
 package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.messaging.serialize.DataStreamBackedSerializer;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
 
-import java.io.DataInput;
-import java.io.DataOutput;
 import java.io.IOException;
 
-public class ModuleVersionIdentifierSerializer extends DataStreamBackedSerializer<ModuleVersionIdentifier> {
-    @Override
-    public void write(DataOutput dataOutput, ModuleVersionIdentifier value) throws IOException {
-        dataOutput.writeUTF(value.getGroup());
-        dataOutput.writeUTF(value.getName());
-        dataOutput.writeUTF(value.getVersion());
+public class ModuleVersionIdentifierSerializer implements Serializer<ModuleVersionIdentifier> {
+    public void write(Encoder encoder, ModuleVersionIdentifier value) throws IOException {
+        encoder.writeString(value.getGroup());
+        encoder.writeString(value.getName());
+        encoder.writeString(value.getVersion());
     }
 
-    @Override
-    public ModuleVersionIdentifier read(DataInput dataInput) throws IOException {
-        String group = dataInput.readUTF();
-        String module = dataInput.readUTF();
-        String version = dataInput.readUTF();
+    public ModuleVersionIdentifier read(Decoder decoder) throws IOException {
+        String group = decoder.readString();
+        String module = decoder.readString();
+        String version = decoder.readString();
         return DefaultModuleVersionIdentifier.newId(group, module, version);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionPublisher.java
new file mode 100644
index 0000000..da212d8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionPublisher.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionPublishMetaData;
+
+import java.io.IOException;
+
+public interface ModuleVersionPublisher {
+    void publish(ModuleVersionPublishMetaData moduleVersion) throws IOException;
+
+    void setSettings(IvySettings settings);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorSerializer.java
new file mode 100644
index 0000000..8e43bcf
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorSerializer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.IOException;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector;
+
+public class ModuleVersionSelectorSerializer implements Serializer<ModuleVersionSelector> {
+    public ModuleVersionSelector read(Decoder decoder) throws IOException {
+        String group = decoder.readString();
+        String name = decoder.readString();
+        String version = decoder.readString();
+        return newSelector(group, name, version);
+    }
+
+    public void write(Encoder encoder, ModuleVersionSelector value) throws IOException {
+        encoder.writeString(value.getGroup());
+        encoder.writeString(value.getName());
+        encoder.writeString(value.getVersion());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java
index 4bb5c77..e02ab3b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java
@@ -17,9 +17,6 @@ package org.gradle.api.internal.artifacts;
 
 import org.codehaus.plexus.logging.Logger;
 
-/**
- * @author Hans Dockter
- */
 public class PlexusLoggerAdapter implements Logger {
     org.slf4j.Logger logger;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
index a6710a6..1fbdb6f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
@@ -22,9 +22,9 @@ public class ResolvedConfigurationIdentifier {
     private final ModuleVersionIdentifier id;
     private final String configuration;
 
-    public ResolvedConfigurationIdentifier(String moduleGroup, String moduleName, String moduleVersion,
+    public ResolvedConfigurationIdentifier(ModuleVersionIdentifier moduleVersionIdentifier,
                                            String configuration) {
-        this.id = new DefaultModuleVersionIdentifier(moduleGroup, moduleName, moduleVersion);
+        this.id = moduleVersionIdentifier;
         this.configuration = configuration;
     }
 
@@ -50,7 +50,7 @@ public class ResolvedConfigurationIdentifier {
 
     @Override
     public String toString() {
-        return String.format("%s:%s:%s:%s", getModuleGroup(), getModuleVersion(), getModuleName(), configuration);
+        return String.format("%s:%s:%s:%s", getModuleGroup(), getModuleName(), getModuleVersion(), configuration);
     }
 
     @Override
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSerializer.java
new file mode 100644
index 0000000..ed9accc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSerializer.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;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.IOException;
+
+public class ResolvedConfigurationIdentifierSerializer implements Serializer<ResolvedConfigurationIdentifier> {
+    private final ModuleVersionIdentifierSerializer idSerializer = new ModuleVersionIdentifierSerializer();
+
+    public ResolvedConfigurationIdentifier read(Decoder decoder) throws IOException {
+        ModuleVersionIdentifier id = idSerializer.read(decoder);
+        String configuration = decoder.readString();
+        return new ResolvedConfigurationIdentifier(id, configuration);
+    }
+
+    public void write(Encoder encoder, ResolvedConfigurationIdentifier value) throws IOException {
+        idSerializer.write(encoder, value.getId());
+        encoder.writeString(value.getConfiguration());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
index 6811040..a230dd4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
@@ -20,43 +20,45 @@ 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;
-    }
+    private ResolvedConfiguration resolvedConfiguration;
+    private ResolutionResult resolutionResult;
+    private ResolveException fatalFailure;
 
     //old model, slowly being replaced by the new model
     public ResolvedConfiguration getResolvedConfiguration() {
+        assertHasResult();
         return resolvedConfiguration;
     }
 
     //new model
     public ResolutionResult getResolutionResult() {
+        assertHasResult();
         if (fatalFailure != null) {
             throw fatalFailure;
         }
         return resolutionResult;
     }
 
-    public ResolverResults withResolvedConfiguration(ResolvedConfiguration resolvedConfiguration) {
-        return new ResolverResults(resolvedConfiguration, resolutionResult);
+    private void assertHasResult() {
+        if (resolvedConfiguration == null) {
+            throw new IllegalStateException("Resolution result has not been attached.");
+        }
+    }
+
+    public void withResolvedConfiguration(ResolvedConfiguration resolvedConfiguration) {
+        this.resolvedConfiguration = resolvedConfiguration;
+    }
+
+    public void resolved(ResolvedConfiguration resolvedConfiguration, ResolutionResult resolutionResult) {
+        this.resolvedConfiguration = resolvedConfiguration;
+        this.resolutionResult = resolutionResult;
+        this.fatalFailure = null;
+    }
+
+    public void failed(ResolvedConfiguration resolvedConfiguration, ResolveException failure) {
+        this.resolvedConfiguration = resolvedConfiguration;
+        this.resolutionResult = null;
+        this.fatalFailure = failure;
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/ComponentIdentifierFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/ComponentIdentifierFactory.java
new file mode 100644
index 0000000..2333025
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/ComponentIdentifierFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.component;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+
+public interface ComponentIdentifierFactory {
+    ComponentIdentifier createComponentIdentifier(ModuleInternal module);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java
new file mode 100644
index 0000000..e78e615
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.component;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+
+public class DefaultComponentIdentifierFactory implements ComponentIdentifierFactory {
+    public ComponentIdentifier createComponentIdentifier(ModuleInternal module) {
+        String projectPath = module.getProjectPath();
+
+        if(projectPath != null) {
+            return new DefaultProjectComponentIdentifier(projectPath);
+        }
+
+        return new DefaultModuleComponentIdentifier(module.getGroup(), module.getName(), module.getVersion());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentIdentifier.java
new file mode 100644
index 0000000..3fa88f5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentIdentifier.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.component;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+
+public class DefaultModuleComponentIdentifier implements ModuleComponentIdentifier {
+    private final String displayName;
+    private final String group;
+    private final String module;
+    private final String version;
+
+    public DefaultModuleComponentIdentifier(String group, String module, String version) {
+        assert group != null : "group cannot be null";
+        assert module != null : "module cannot be null";
+        assert version != null : "version cannot be null";
+        displayName = String.format("%s:%s:%s", group, module, version);
+        this.group = group;
+        this.module = module;
+        this.version = version;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultModuleComponentIdentifier that = (DefaultModuleComponentIdentifier) o;
+
+        if (!group.equals(that.group)) {
+            return false;
+        }
+        if (!module.equals(that.module)) {
+            return false;
+        }
+        if (!version.equals(that.version)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = group.hashCode();
+        result = 31 * result + module.hashCode();
+        result = 31 * result + version.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public static ModuleComponentIdentifier newId(String group, String name, String version) {
+        return new DefaultModuleComponentIdentifier(group, name, version);
+    }
+
+    public static ModuleComponentIdentifier newId(ModuleVersionIdentifier moduleVersionIdentifier) {
+        return new DefaultModuleComponentIdentifier(moduleVersionIdentifier.getGroup(), moduleVersionIdentifier.getName(), moduleVersionIdentifier.getVersion());
+    }
+}
+
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentSelector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentSelector.java
new file mode 100644
index 0000000..480c3da
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentSelector.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.component;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+
+public class DefaultModuleComponentSelector implements ModuleComponentSelector {
+    private final String displayName;
+    private final String group;
+    private final String module;
+    private final String version;
+
+    public DefaultModuleComponentSelector(String group, String module, String version) {
+        assert group != null : "group cannot be null";
+        assert module != null : "module cannot be null";
+        assert version != null : "version cannot be null";
+        displayName = String.format("%s:%s:%s", group, module, version);
+        this.group = group;
+        this.module = module;
+        this.version = version;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public boolean matchesStrictly(ComponentIdentifier identifier) {
+        assert identifier != null : "identifier cannot be null";
+
+        if(identifier instanceof ModuleComponentIdentifier) {
+            ModuleComponentIdentifier moduleComponentIdentifier = (ModuleComponentIdentifier)identifier;
+            return module.equals(moduleComponentIdentifier.getModule())
+                    && group.equals(moduleComponentIdentifier.getGroup())
+                    && version.equals(moduleComponentIdentifier.getVersion());
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultModuleComponentSelector that = (DefaultModuleComponentSelector) o;
+
+        if (!group.equals(that.group)) {
+            return false;
+        }
+        if (!module.equals(that.module)) {
+            return false;
+        }
+        if (!version.equals(that.version)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = group.hashCode();
+        result = 31 * result + module.hashCode();
+        result = 31 * result + version.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public static ModuleComponentSelector newSelector(String group, String name, String version) {
+        return new DefaultModuleComponentSelector(group, name, version);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentIdentifier.java
new file mode 100644
index 0000000..81c138a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentIdentifier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.component;
+
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+
+public class DefaultProjectComponentIdentifier implements ProjectComponentIdentifier {
+    private final String projectPath;
+    private final String displayName;
+
+    public DefaultProjectComponentIdentifier(String projectPath) {
+        assert projectPath != null : "project path cannot be null";
+        this.projectPath = projectPath;
+        displayName = String.format("project %s", projectPath);
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultProjectComponentIdentifier that = (DefaultProjectComponentIdentifier) o;
+
+        if (!projectPath.equals(that.projectPath)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return projectPath.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public static ProjectComponentIdentifier newId(String projectPath) {
+        return new DefaultProjectComponentIdentifier(projectPath);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentSelector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentSelector.java
new file mode 100644
index 0000000..e75d84e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentSelector.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.component;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+import org.gradle.api.artifacts.component.ProjectComponentSelector;
+
+public class DefaultProjectComponentSelector implements ProjectComponentSelector {
+    private final String projectPath;
+    private final String displayName;
+
+    public DefaultProjectComponentSelector(String projectPath) {
+        assert projectPath != null : "project path cannot be null";
+        this.projectPath = projectPath;
+        displayName = String.format("project %s", projectPath);
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    public boolean matchesStrictly(ComponentIdentifier identifier) {
+        assert identifier != null : "identifier cannot be null";
+
+        if(identifier instanceof ProjectComponentIdentifier) {
+            ProjectComponentIdentifier projectComponentIdentifier = (ProjectComponentIdentifier)identifier;
+            return projectPath.equals(projectComponentIdentifier.getProjectPath());
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultProjectComponentSelector that = (DefaultProjectComponentSelector) o;
+
+        if (!projectPath.equals(that.projectPath)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return projectPath.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public static ProjectComponentSelector newSelector(String projectPath) {
+        return new DefaultProjectComponentSelector(projectPath);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
index c92b018..793c227 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
@@ -21,9 +21,6 @@ import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class Configurations {
     public static Set<String> getNames(Collection<? extends Configuration> configurations, boolean includeExtended) {
         Set<Configuration> allConfigurations = new HashSet<Configuration>(configurations);
@@ -49,15 +46,10 @@ public class Configurations {
         return allConfigurations;
     }
 
-    public static String uploadInternalTaskName(String configurationName) {
-        return String.format("upload%sInternal", getCapitalName(configurationName));
-    }
-
     public static String uploadTaskName(String configurationName) {
         return String.format("upload%s", getCapitalName(configurationName));
     }
 
-
     private static String getCapitalName(String configurationName) {
         return configurationName.substring(0, 1).toUpperCase() + configurationName.substring(1);
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
index 15b95cc..85bce22 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
@@ -19,9 +19,6 @@ import org.gradle.api.artifacts.Configuration;
 
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public interface ConfigurationsProvider {
     Set<Configuration> getAll();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
index 3042e4e..177b0c3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
@@ -108,7 +108,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
     }
 
-    public Module getModule() {
+    public ModuleInternal getModule() {
         return metaDataProvider.getModule();
     }
 
@@ -411,6 +411,10 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return resolutionStrategy;
     }
 
+    public String getPath() {
+        return path;
+    }
+
     public Configuration resolutionStrategy(Closure closure) {
         ConfigureUtil.configure(closure, resolutionStrategy);
         return this;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
index 0a2eb49..26d5085 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -27,14 +27,12 @@ import org.gradle.api.internal.artifacts.ConfigurationResolver;
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.listener.ListenerManager;
+import org.gradle.util.DeprecationLogger;
 
 import java.util.Collection;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
-public class DefaultConfigurationContainer extends AbstractNamedDomainObjectContainer<Configuration> 
+public class DefaultConfigurationContainer extends AbstractNamedDomainObjectContainer<Configuration>
         implements ConfigurationContainerInternal, ConfigurationsProvider {
     public static final String DETACHED_CONFIGURATION_DEFAULT_NAME = "detachedConfiguration";
     
@@ -69,10 +67,12 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
     }
 
     public Configuration add(String name) {
+        DeprecationLogger.nagUserOfReplacedMethod("ConfigurationContainer.add()", "create()");
         return create(name);
     }
 
     public Configuration add(String name, Closure closure) {
+        DeprecationLogger.nagUserOfReplacedMethod("ConfigurationContainer.add()", "create()");
         return create(name, closure);
     }
 
@@ -91,7 +91,7 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
         return new UnknownConfigurationException(String.format("Configuration with name '%s' not found.", name));
     }
 
-    public Configuration detachedConfiguration(Dependency... dependencies) {
+    public ConfigurationInternal detachedConfiguration(Dependency... dependencies) {
         String name = DETACHED_CONFIGURATION_DEFAULT_NAME + detachedConfigurationDefaultNameCounter++;
         DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
         DefaultConfiguration detachedConfiguration = new DefaultConfiguration(
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
index c8c83ff..7a61bae 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
@@ -20,9 +20,6 @@ import org.gradle.util.WrapUtil;
 
 import java.util.Set;
 
-/**
- * @author Hans Dockter
-*/
 class DetachedConfigurationsProvider implements ConfigurationsProvider {
     private Configuration theOnlyConfiguration;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
index 6fd0bb3..164cda6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
@@ -20,19 +20,16 @@ import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.PublishArtifact
 import org.gradle.api.artifacts.dsl.ArtifactHandler
+import org.gradle.internal.typeconversion.NotationParser
 import org.gradle.util.ConfigureUtil
 import org.gradle.util.GUtil
-import org.gradle.api.internal.notations.api.NotationParser
 
-/**
- * @author Hans Dockter
- */
 class DefaultArtifactHandler implements ArtifactHandler {
 
     ConfigurationContainer configurationContainer
-    NotationParser<PublishArtifact> publishArtifactFactory
+    NotationParser<Object, PublishArtifact> publishArtifactFactory
 
-    def DefaultArtifactHandler(ConfigurationContainer configurationContainer, NotationParser<PublishArtifact> publishArtifactFactory) {
+    def DefaultArtifactHandler(ConfigurationContainer configurationContainer, NotationParser<Object, PublishArtifact> publishArtifactFactory) {
         this.configurationContainer = configurationContainer;
         this.publishArtifactFactory = publishArtifactFactory;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java
new file mode 100644
index 0000000..0b717c6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ComponentMetadataDetails;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
+import org.gradle.api.artifacts.dsl.ComponentMetadataHandler;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.repositories.resolver.ComponentMetadataDetailsAdapter;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.listener.ActionBroadcast;
+
+public class DefaultComponentMetadataHandler implements ComponentMetadataHandler, ModuleMetadataProcessor {
+    private final Instantiator instantiator;
+    private final ActionBroadcast<ComponentMetadataDetails> moduleRules = new ActionBroadcast<ComponentMetadataDetails>();
+
+    public DefaultComponentMetadataHandler(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    public void eachComponent(Action<? super ComponentMetadataDetails> rule) {
+        moduleRules.add(rule);
+    }
+
+    public void process(ModuleVersionMetaData metadata) {
+        ComponentMetadataDetails details = instantiator.newInstance(ComponentMetadataDetailsAdapter.class, metadata);
+        moduleRules.execute(details);
+        if (!metadata.getStatusScheme().contains(metadata.getStatus())) {
+            throw new ModuleVersionResolveException(metadata.getId(), "Unexpected status '" + metadata.getStatus() + "' specified for %s. Expected one of: " +  metadata.getStatusScheme());
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java
index d852d60..b1fe488 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java
@@ -19,27 +19,24 @@ package org.gradle.api.internal.artifacts.dsl;
 import org.gradle.api.IllegalDependencyNotation;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 
 import java.util.Collection;
 import java.util.Set;
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector;
 
-/**
- * by Szczepan Faber, created at: 10/11/11
- */
 public class ModuleVersionSelectorParsers {
 
-    public static NotationParser<Set<ModuleVersionSelector>> multiParser() {
+    public static NotationParser<Object, Set<ModuleVersionSelector>> multiParser() {
         return builder().toFlatteningComposite();
     }
 
-    public static NotationParser<ModuleVersionSelector> parser() {
+    public static NotationParser<Object, ModuleVersionSelector> parser() {
         return builder().toComposite();
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java
index cb31a95..de16d59 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.artifacts.dsl;
 import org.gradle.api.IllegalDependencyNotation;
 import org.gradle.util.GUtil;
 
-/**
- * @author Hans Dockter
- */
 public class ParsedModuleStringNotation {
     private String group;
     private String name;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java
index 0064e2d..782389d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java
@@ -22,11 +22,11 @@ import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 import org.gradle.api.tasks.bundling.AbstractArchiveTask;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
@@ -34,10 +34,7 @@ import org.gradle.internal.reflect.Instantiator;
 import java.io.File;
 import java.util.Collection;
 
-/**
- * @author Hans Dockter
- */
-public class PublishArtifactNotationParserFactory implements Factory<NotationParser<PublishArtifact>> {
+public class PublishArtifactNotationParserFactory implements Factory<NotationParser<Object, PublishArtifact>> {
     private final Instantiator instantiator;
     private final DependencyMetaDataProvider metaDataProvider;
 
@@ -46,7 +43,7 @@ public class PublishArtifactNotationParserFactory implements Factory<NotationPar
         this.metaDataProvider = metaDataProvider;
     }
 
-    public NotationParser<PublishArtifact> create() {
+    public NotationParser<Object, PublishArtifact> create() {
         FileNotationParser fileParser = new FileNotationParser();
         return new NotationParserBuilder<PublishArtifact>()
                 .resultingType(PublishArtifact.class)
@@ -93,7 +90,8 @@ public class PublishArtifactNotationParserFactory implements Factory<NotationPar
         protected PublishArtifact parseType(File file) {
             Module module = metaDataProvider.getModule();
             ArtifactFile artifactFile = new ArtifactFile(file, module.getVersion());
-            return instantiator.newInstance(DefaultPublishArtifact.class, artifactFile.getName(), artifactFile.getExtension(), artifactFile.getExtension(),
+            return instantiator.newInstance(DefaultPublishArtifact.class, artifactFile.getName(), artifactFile.getExtension(),
+                                            artifactFile.getExtension() == null? "":artifactFile.getExtension(),
                                             artifactFile.getClassifier(), null, file, new Task[0]);
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveContext.java
new file mode 100644
index 0000000..30ba366
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveContext.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+public interface ArtifactResolveContext {
+    String getId();
+    String getDescription();
+}
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 cde8c47..cfa543b 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
@@ -15,11 +15,19 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
 
 public interface ArtifactResolver {
     /**
+     * Resolves a set of artifacts belonging to the given component, based on the supplied context. Any failures are packaged up in the result.
+     */
+    void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result);
+
+    /**
      * Resolves the given artifact. Any failures are packaged up in the result.
      */
-    void resolve(Artifact artifact, BuildableArtifactResolveResult result);
+    // TODO:DAZ Make this less ModuleVersion centric: ModuleSource should be an attribute of the component subtype only
+    void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactSetResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactSetResolveResult.java
new file mode 100644
index 0000000..ee9de55
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactSetResolveResult.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+
+import java.util.Set;
+
+public interface ArtifactSetResolveResult {
+    /**
+     * Returns the resolve failure, if any.
+     */
+    @Nullable
+    ArtifactResolveException getFailure();
+
+    Set<ComponentArtifactMetaData> getArtifacts();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactTypeResolveContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactTypeResolveContext.java
new file mode 100644
index 0000000..4854995
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactTypeResolveContext.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolution.SoftwareArtifact;
+
+public class ArtifactTypeResolveContext implements ArtifactResolveContext {
+    private final Class<? extends SoftwareArtifact> artifactType;
+
+    public ArtifactTypeResolveContext(Class<? extends SoftwareArtifact> artifactType) {
+        this.artifactType = artifactType;
+    }
+
+    public Class<? extends SoftwareArtifact> getArtifactType() {
+        return artifactType;
+    }
+
+    public String getId() {
+        return "artifacts:" + artifactType.getName();
+    }
+
+    public String getDescription() {
+        return String.format("artifacts of type %s", artifactType);
+    }
+}
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
index dd43274..3a9611f 100644
--- 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
@@ -16,8 +16,8 @@
 
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactIdentifier;
 
 import java.io.File;
 
@@ -33,7 +33,7 @@ public interface BuildableArtifactResolveResult extends ArtifactResolveResult {
     void failed(ArtifactResolveException failure);
 
     /**
-     * Marks the module version as not found.
+     * Marks the artifact as not found.
      */
-    void notFound(Artifact artifact);
+    void notFound(ComponentArtifactIdentifier artifact);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactSetResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactSetResolveResult.java
new file mode 100644
index 0000000..c601afd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactSetResolveResult.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+
+import java.util.Collection;
+
+public interface BuildableArtifactSetResolveResult extends ArtifactSetResolveResult {
+    void resolved(Collection<? extends ComponentArtifactMetaData> artifacts);
+
+    void failed(ArtifactResolveException failure);
+
+    boolean hasResult();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableComponentResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableComponentResolveResult.java
new file mode 100644
index 0000000..b402741
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableComponentResolveResult.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.ivyservice;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+public interface BuildableComponentResolveResult extends ComponentResolveResult {
+    /**
+     * Marks the component as resolved, with the given meta-data.
+     */
+    void resolved(ComponentMetaData metaData);
+
+    /**
+     * Marks the resolve as failed with the given exception.
+     */
+    BuildableComponentResolveResult failed(ModuleVersionResolveException failure);
+
+    /**
+     * Marks the component as not found.
+     */
+    void notFound(ModuleVersionSelector versionSelector);
+
+    /**
+     * Replaces the meta-data in the result. Result must already be resolved.
+     */
+    void setMetaData(ComponentMetaData metaData);
+}
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
deleted file mode 100644
index cf52b33..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
+++ /dev/null
@@ -1,54 +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.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
-
-public interface BuildableModuleVersionResolveResult extends ModuleVersionResolveResult {
-    /**
-     * Marks the module version as resolved, with the given meta-data and artifact resolver.
-     */
-    void resolved(ModuleVersionIdentifier moduleVersionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver);
-
-    /**
-     * Marks the module version as resolved, with the given meta-data and artifact resolver.
-     */
-    void resolved(ModuleVersionMetaData metaData, ArtifactResolver artifactResolver);
-
-    /**
-     * Marks the resolve as failed with the given exception.
-     */
-    BuildableModuleVersionResolveResult failed(ModuleVersionResolveException failure);
-
-    /**
-     * Marks the module version as not found.
-     */
-    void notFound(ModuleVersionSelector versionSelector);
-
-    /**
-     * Replaces the meta-data in the result. Result must already be resolved.
-     */
-    void setMetaData(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/CacheLayout.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLayout.java
new file mode 100644
index 0000000..4c6bbb4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLayout.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.VersionNumber;
+
+import java.io.File;
+
+public enum CacheLayout {
+    ROOT(null, "modules", 2),
+    FILE_STORE(ROOT, "files", 1),
+    META_DATA(ROOT, "metadata", 6);
+
+    private final String name;
+    private final CacheLayout parent;
+    private final int version;
+
+    private CacheLayout(CacheLayout parent, String name, int version) {
+        this.parent = parent;
+        this.name = name;
+        this.version = version;
+    }
+
+    public VersionNumber getVersion() {
+        return VersionNumber.parse(getFormattedVersion());
+    }
+
+    public String getKey() {
+        StringBuilder key = new StringBuilder();
+        key.append(name);
+        key.append("-");
+        key.append(getFormattedVersion());
+        return key.toString();
+    }
+
+    public String getFormattedVersion() {
+        if (parent == null) {
+            return String.valueOf(version);
+        }
+        return parent.getFormattedVersion() + '.' + String.valueOf(version);
+    }
+
+    public File getPath(File parentDir) {
+        return new File(parentDir, getKey());
+    }
+}
\ No newline at end of file
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 f34a8e4..5146b05 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
@@ -17,10 +17,10 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
-import org.gradle.internal.Factory;
 
 import java.util.List;
 
@@ -33,10 +33,13 @@ public class CacheLockingArtifactDependencyResolver implements ArtifactDependenc
         this.resolver = resolver;
     }
 
-    public ResolverResults resolve(final ConfigurationInternal configuration, final List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
-        return lockingManager.useCache(String.format("resolve %s", configuration), new Factory<ResolverResults>() {
-            public ResolverResults create() {
-                return resolver.resolve(configuration, repositories);
+    public void resolve(final ConfigurationInternal configuration,
+                                   final List<? extends ResolutionAwareRepository> repositories,
+                                   final ModuleMetadataProcessor metadataProcessor,
+                                   final ResolverResults results) throws ResolveException {
+        lockingManager.useCache(String.format("resolve %s", configuration), new Runnable() {
+            public void run() {
+                resolver.resolve(configuration, repositories, metadataProcessor, results);
             }
         });
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
index f731e43..f694329 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
@@ -35,15 +35,19 @@ public interface CacheLockingManager extends ArtifactCacheMetaData, CacheAccess
      *
      * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
      */
-    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType);
+    <K, V> PersistentIndexedCache<K, V> createCache(String cacheName, Serializer<K> keySerializer, Serializer<V> valueSerializer);
 
     /**
-     * Creates a cache implementation that is managed by this locking manager. This method may be used at any time.
+     * Returns the root directory for the file store.
      *
-     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
-     * In this instance, an exclusive lock will be held on the cache.
+     * @return File store location
+     */
+    File getFileStoreDirectory();
+
+    /**
+     * Returns the root directory for the meta-data file store.
      *
-     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
+     * @return Metadata store location
      */
-    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer);
+    File createMetaDataStore();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ComponentResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ComponentResolveResult.java
new file mode 100644
index 0000000..5f28733
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ComponentResolveResult.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.ivyservice;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+public interface ComponentResolveResult {
+    /**
+     * Returns the module version id of the component.
+     *
+     * @throws ModuleVersionResolveException If resolution was unsuccessful and the id is unknown.
+     */
+    ModuleVersionIdentifier getId() throws ModuleVersionResolveException;
+
+    /**
+     * Returns the meta-data for the component.
+     *
+     * @throws ModuleVersionResolveException If resolution was unsuccessful and the descriptor is not available.
+     */
+    ComponentMetaData getMetaData() throws ModuleVersionResolveException;
+
+    /**
+     * Returns the resolve failure, if any.
+     */
+    @Nullable
+    ModuleVersionResolveException getFailure();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ConfigurationResolveContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ConfigurationResolveContext.java
new file mode 100644
index 0000000..c922e2b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ConfigurationResolveContext.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+public class ConfigurationResolveContext implements ArtifactResolveContext {
+    private final String configurationName;
+
+    public ConfigurationResolveContext(String configurationName) {
+        this.configurationName = configurationName;
+    }
+
+    public String getConfigurationName() {
+        return configurationName;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("configuration '%s'", configurationName);
+    }
+
+    public String getId() {
+        return "configuration:" + configurationName;
+    }
+
+    public String getDescription() {
+        return String.format("artifacts for configuration '%s'", configurationName);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ContextualArtifactResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ContextualArtifactResolver.java
new file mode 100644
index 0000000..c503663
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ContextualArtifactResolver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.gradle.api.Action;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+public class ContextualArtifactResolver implements ArtifactResolver {
+    private final CacheLockingManager lockingManager;
+    private final IvyContextManager ivyContextManager;
+    private final ArtifactResolver delegate;
+
+    public ContextualArtifactResolver(CacheLockingManager lockingManager, IvyContextManager ivyContextManager, ArtifactResolver delegate) {
+        this.lockingManager = lockingManager;
+        this.ivyContextManager = ivyContextManager;
+        this.delegate = delegate;
+    }
+
+    public void resolveModuleArtifacts(final ComponentMetaData component, final ArtifactResolveContext context, final BuildableArtifactSetResolveResult result) {
+        lockingManager.useCache(String.format("Resolve %s for %s", context.getDescription(), component), new Runnable() {
+            public void run() {
+                ivyContextManager.withIvy(new Action<Ivy>() {
+                    public void execute(Ivy ivy) {
+                        delegate.resolveModuleArtifacts(component, context, result);
+                    }
+                });
+            }
+        });
+    }
+
+    public void resolveArtifact(final ComponentArtifactMetaData artifact, final ModuleSource moduleSource, final BuildableArtifactResolveResult result) {
+        lockingManager.useCache(String.format("Resolve %s", artifact), new Runnable() {
+            public void run() {
+                ivyContextManager.withIvy(new Action<Ivy>() {
+                    public void execute(Ivy ivy) {
+                        delegate.resolveArtifact(artifact, moduleSource, result);
+                    }
+                });
+            }
+        });
+    }
+}
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
index 9e1d486..3607724 100644
--- 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
@@ -16,9 +16,9 @@
 
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactNotFoundException;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactIdentifier;
 
 import java.io.File;
 
@@ -34,7 +34,7 @@ public class DefaultBuildableArtifactResolveResult implements BuildableArtifactR
         this.file = file;
     }
 
-    public void notFound(Artifact artifact) {
+    public void notFound(ComponentArtifactIdentifier artifact) {
         failed(new ArtifactNotFoundException(artifact));
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactSetResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactSetResolveResult.java
new file mode 100644
index 0000000..4fc532e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactSetResolveResult.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultBuildableArtifactSetResolveResult implements BuildableArtifactSetResolveResult {
+    private ArtifactResolveException failure;
+    private Set<ComponentArtifactMetaData> artifacts;
+
+    public void resolved(Collection<? extends ComponentArtifactMetaData> artifacts) {
+        this.artifacts = new LinkedHashSet<ComponentArtifactMetaData>(artifacts);
+    }
+
+    public void failed(ArtifactResolveException failure) {
+        this.failure = failure;
+    }
+
+    public boolean hasResult() {
+        return artifacts != null || failure != null;
+    }
+
+    public ArtifactResolveException getFailure() {
+        assertHasResult();
+        return failure;
+    }
+
+    public Set<ComponentArtifactMetaData> getArtifacts() {
+        assertResolved();
+        return artifacts;
+    }
+
+    private void assertResolved() {
+        assertHasResult();
+        if (failure != null) {
+            throw failure;
+        }
+    }
+
+    private void assertHasResult() {
+        if (!hasResult()) {
+            throw new IllegalStateException("No result has been specified.");
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableComponentResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableComponentResolveResult.java
new file mode 100644
index 0000000..57eb6c9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableComponentResolveResult.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.internal.artifacts.ivyservice;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+public class DefaultBuildableComponentResolveResult implements BuildableComponentResolveResult {
+    private ComponentMetaData metaData;
+    private ModuleVersionResolveException failure;
+
+    public DefaultBuildableComponentResolveResult failed(ModuleVersionResolveException failure) {
+        metaData = null;
+        this.failure = failure;
+        return this;
+    }
+
+    public void notFound(ModuleVersionSelector versionSelector) {
+        failed(new ModuleVersionNotFoundException(versionSelector));
+    }
+
+    public void resolved(ComponentMetaData metaData) {
+        this.metaData = metaData;
+    }
+
+    public void setMetaData(ComponentMetaData metaData) {
+        assertResolved();
+        this.metaData = metaData;
+    }
+
+    public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
+        assertResolved();
+        return metaData.getId();
+    }
+
+    public ComponentMetaData getMetaData() throws ModuleVersionResolveException {
+        assertResolved();
+        return metaData;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        assertHasResult();
+        return failure;
+    }
+
+    private void assertResolved() {
+        assertHasResult();
+        if (failure != null) {
+            throw failure;
+        }
+    }
+
+    private void assertHasResult() {
+        if (failure == null && metaData == 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
deleted file mode 100644
index 4410d69..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
+++ /dev/null
@@ -1,95 +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.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultBuildableModuleVersionMetaData;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
-
-public class DefaultBuildableModuleVersionResolveResult implements BuildableModuleVersionResolveResult {
-    private ModuleVersionMetaData metaData;
-    private ModuleVersionResolveException failure;
-    private ArtifactResolver artifactResolver;
-
-    public DefaultBuildableModuleVersionResolveResult failed(ModuleVersionResolveException failure) {
-        metaData = null;
-        this.failure = failure;
-        return this;
-    }
-
-    public void notFound(ModuleVersionSelector versionSelector) {
-        failed(new ModuleVersionNotFoundException(versionSelector));
-    }
-
-    public void resolved(ModuleVersionIdentifier moduleVersionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver) {
-        DefaultBuildableModuleVersionMetaData metaData = new DefaultBuildableModuleVersionMetaData();
-        metaData.resolved(moduleVersionId, descriptor, false, null);
-        resolved(metaData, artifactResolver);
-    }
-
-    public void resolved(ModuleVersionMetaData metaData, ArtifactResolver artifactResolver) {
-        this.metaData = metaData;
-        this.artifactResolver = artifactResolver;
-    }
-
-    public void setMetaData(ModuleDescriptor descriptor) {
-        assertResolved();
-        DefaultBuildableModuleVersionMetaData newMetaData = new DefaultBuildableModuleVersionMetaData();
-        newMetaData.resolved(metaData.getId(), descriptor, metaData.isChanging(), null);
-        this.metaData = newMetaData;
-    }
-
-    public void setArtifactResolver(ArtifactResolver artifactResolver) {
-        assertResolved();
-        this.artifactResolver = artifactResolver;
-    }
-
-    public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
-        assertResolved();
-        return metaData.getId();
-    }
-
-    public ModuleVersionMetaData getMetaData() throws ModuleVersionResolveException {
-        assertResolved();
-        return metaData;
-    }
-
-    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 && metaData == 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 8928b3e..f28845f 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
@@ -15,32 +15,40 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.gradle.cache.CacheBuilder;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentCache;
 import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentIndexedCacheParameters;
 import org.gradle.cache.internal.FileLockManager;
 import org.gradle.internal.Factory;
 import org.gradle.messaging.serialize.Serializer;
+import org.gradle.util.VersionNumber;
 
 import java.io.File;
 
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
+
 public class DefaultCacheLockingManager implements CacheLockingManager {
 
     // If you update this, also update DefaultGradleDistribution.getArtifactCacheLayoutVersion() (which is the historical record)
-    public static final int CACHE_LAYOUT_VERSION = 23;
+    // You should also update LocallyAvailableResourceFinderFactory
+    public static final VersionNumber CACHE_LAYOUT_VERSION = CacheLayout.META_DATA.getVersion();
 
     private final PersistentCache cache;
 
     public DefaultCacheLockingManager(CacheRepository cacheRepository) {
         cache = cacheRepository
-                .store(String.format("artifacts-%d", CACHE_LAYOUT_VERSION))
+                .store(CacheLayout.ROOT.getKey())
+                .withCrossVersionCache()
                 .withDisplayName("artifact cache")
-                .withVersionStrategy(CacheBuilder.VersionStrategy.SharedCache)
-                .withLockMode(FileLockManager.LockMode.None) // Don't need to lock anything until we use the caches
+                .withLockOptions(mode(FileLockManager.LockMode.None)) // Don't need to lock anything until we use the caches
                 .open();
     }
 
+    public void close() {
+        cache.close();
+    }
+
     public File getCacheDir() {
         return cache.getBaseDir();
     }
@@ -61,11 +69,20 @@ public class DefaultCacheLockingManager implements CacheLockingManager {
         return cache.longRunningOperation(operationDisplayName, action);
     }
 
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
-        return cache.createCache(cacheFile, keyType, valueType);
+    public <K, V> PersistentIndexedCache<K, V> createCache(String cacheName, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+        String cacheFileInMetaDataStore = CacheLayout.META_DATA.getKey() + "/" + cacheName;
+        return cache.createCache(new PersistentIndexedCacheParameters<K, V>(cacheFileInMetaDataStore, keySerializer, valueSerializer));
+    }
+
+    public File getFileStoreDirectory() {
+        return createCacheRelativeDir(CacheLayout.FILE_STORE);
+    }
+
+    public File createMetaDataStore() {
+        return new File(createCacheRelativeDir(CacheLayout.META_DATA), "descriptors");
     }
 
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
-        return cache.createCache(cacheFile, keySerializer, valueSerializer);
+    private File createCacheRelativeDir(CacheLayout cacheLayout) {
+        return cacheLayout.getPath(cache.getBaseDir());
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
index 54e8fdc..5af74e6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
@@ -18,7 +18,8 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.Transformers;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
+import org.gradle.internal.Transformers;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.ConfigurationResolver;
 import org.gradle.api.internal.artifacts.ResolverResults;
@@ -31,14 +32,18 @@ import java.util.List;
 public class DefaultConfigurationResolver implements ConfigurationResolver {
     private final ArtifactDependencyResolver resolver;
     private final RepositoryHandler repositories;
+    private final ModuleMetadataProcessor metadataProcessor;
 
-    public DefaultConfigurationResolver(ArtifactDependencyResolver resolver, RepositoryHandler repositories) {
+    public DefaultConfigurationResolver(ArtifactDependencyResolver resolver, RepositoryHandler repositories, ModuleMetadataProcessor metadataProcessor) {
         this.resolver = resolver;
         this.repositories = repositories;
+        this.metadataProcessor = metadataProcessor;
     }
 
     public ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException {
         List<ResolutionAwareRepository> resolutionAwareRepositories = CollectionUtils.collect(repositories, Transformers.cast(ResolutionAwareRepository.class));
-        return resolver.resolve(configuration, resolutionAwareRepositories);
+        ResolverResults results = new ResolverResults();
+        resolver.resolve(configuration, resolutionAwareRepositories, metadataProcessor, results);
+        return results;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
index b8fa9f1..11349ec 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
@@ -17,19 +17,16 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
 import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector;
 
-/**
-* by Szczepan Faber, created at: 11/29/12
-*/
 public class DefaultDependencyResolveDetails implements DependencyResolveDetailsInternal {
     private final ModuleVersionSelector requested;
-    private ModuleVersionSelectionReason selectionReason;
+    private ComponentSelectionReason selectionReason;
     private ModuleVersionSelector target;
 
     public DefaultDependencyResolveDetails(ModuleVersionSelector requested) {
@@ -45,7 +42,7 @@ public class DefaultDependencyResolveDetails implements DependencyResolveDetails
         useVersion(version, VersionSelectionReasons.SELECTED_BY_RULE);
     }
 
-    public void useVersion(String version, ModuleVersionSelectionReason selectionReason) {
+    public void useVersion(String version, ComponentSelectionReason selectionReason) {
         assert selectionReason != null;
         if (version == null) {
             throw new IllegalArgumentException("Configuring the dependency resolve details with 'null' version is not allowed.");
@@ -61,7 +58,7 @@ public class DefaultDependencyResolveDetails implements DependencyResolveDetails
         this.selectionReason = VersionSelectionReasons.SELECTED_BY_RULE;
     }
 
-    public ModuleVersionSelectionReason getSelectionReason() {
+    public ComponentSelectionReason getSelectionReason() {
         return selectionReason;
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyContextManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyContextManager.java
new file mode 100644
index 0000000..af170ec
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyContextManager.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.IvyContext;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.Message;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.internal.Transformers;
+
+import java.util.LinkedList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultIvyContextManager implements IvyContextManager {
+    private static final int MAX_CACHED_IVY_INSTANCES = 4;
+    private final Lock lock = new ReentrantLock();
+    private boolean messageAdapterAttached;
+    private final LinkedList<Ivy> cached = new LinkedList<Ivy>();
+    private final ThreadLocal<Integer> depth = new ThreadLocal<Integer>();
+
+    public void withIvy(final Action<? super Ivy> action) {
+        withIvy(Transformers.toTransformer(action));
+    }
+
+    public <T> T withIvy(Transformer<? extends T, ? super Ivy> action) {
+        Integer currentDepth = depth.get();
+
+        if (currentDepth != null) {
+            depth.set(currentDepth + 1);
+            try {
+                return action.transform(IvyContext.getContext().getIvy());
+            } finally {
+                depth.set(currentDepth);
+            }
+        }
+
+        IvyContext.pushNewContext();
+        try {
+            depth.set(1);
+            try {
+                Ivy ivy = getIvy();
+                try {
+                    IvyContext.getContext().setIvy(ivy);
+                    return action.transform(ivy);
+                } finally {
+                    releaseIvy(ivy);
+                }
+            } finally {
+                depth.set(null);
+            }
+        } finally {
+            IvyContext.popContext();
+        }
+    }
+
+    private Ivy getIvy() {
+        lock.lock();
+        try {
+            if (!cached.isEmpty()) {
+                return cached.removeFirst();
+            }
+            if (!messageAdapterAttached) {
+                Message.setDefaultLogger(new IvyLoggingAdaper());
+                messageAdapterAttached = true;
+            }
+        } finally {
+            lock.unlock();
+        }
+        return Ivy.newInstance(new IvySettings());
+    }
+
+    private void releaseIvy(Ivy ivy) {
+        // cleanup
+        ivy.getSettings().getResolvers().clear();
+        ivy.getSettings().setDefaultResolver(null);
+
+        lock.lock();
+        try {
+            if (cached.size() < MAX_CACHED_IVY_INSTANCES) {
+                cached.add(ivy);
+            }
+            // else, throw it away
+        } finally {
+            lock.unlock();
+        }
+    }
+}
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 6b78236..2dcb6b9 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
@@ -15,146 +15,57 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.IvyContext;
-import org.apache.ivy.core.event.EventManager;
-import org.apache.ivy.core.event.publish.EndArtifactPublishEvent;
-import org.apache.ivy.core.event.publish.StartArtifactPublishEvent;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.MDArtifact;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.util.ConfigurationUtils;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.metadata.*;
 import org.gradle.util.DeprecationLogger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
+import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultIvyDependencyPublisher implements IvyDependencyPublisher {
-    public static final String FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE = "filePath";
-
     private static Logger logger = LoggerFactory.getLogger(DefaultIvyDependencyPublisher.class);
 
-    public void publish(Set<String> configurations,
-                        List<DependencyResolver> publishResolvers,
-                        ModuleDescriptor moduleDescriptor,
-                        File descriptorDestination,
-                        EventManager eventManager) {
+    public void publish(List<ModuleVersionPublisher> publishResolvers,
+                        ModuleVersionPublishMetaData publishMetaData) {
         try {
-            Publication publication = new Publication(moduleDescriptor, eventManager, configurations, descriptorDestination);
-
-            for (DependencyResolver resolver : publishResolvers) {
-                logger.info("Publishing to repository {}", resolver);
-                publication.publishTo(resolver);
+            // Make a copy of the publication and filter missing artifacts
+            DefaultModuleVersionPublishMetaData publication = new DefaultModuleVersionPublishMetaData(publishMetaData.getId());
+            for (ModuleVersionArtifactPublishMetaData artifact: publishMetaData.getArtifacts()) {
+                addPublishedArtifact(artifact, publication);
+            }
+            for (ModuleVersionPublisher publisher : publishResolvers) {
+                logger.info("Publishing to {}", publisher);
+                publisher.publish(publication);
             }
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
     }
 
-    private static class Publication {
-        private final ModuleDescriptor moduleDescriptor;
-        private final Set<String> configurations;
-        private final File descriptorFile;
-
-        private final EventManager eventManager;
-
-        private Publication(ModuleDescriptor moduleDescriptor, EventManager eventManager, Set<String> configurations, File descriptorFile) {
-            this.moduleDescriptor = moduleDescriptor;
-            this.eventManager = eventManager;
-            this.configurations = configurations;
-            this.descriptorFile = descriptorFile;
-        }
-
-        public void publishTo(DependencyResolver resolver) throws IOException {
-            Set<Artifact> allArtifacts = getAllArtifacts(moduleDescriptor);
-
-            Map<Artifact, File> artifactsFiles = new LinkedHashMap<Artifact, File>();
-            for (Artifact artifact : allArtifacts) {
-                addPublishedArtifact(artifact, artifactsFiles);
-            }
-            if (descriptorFile != null) {
-                addPublishedDescriptor(artifactsFiles);
-            }
-
-            boolean successfullyPublished = false;
-            try {
-                resolver.beginPublishTransaction(moduleDescriptor.getModuleRevisionId(), true);
-                // for each declared published artifact in this descriptor, do:
-                for (Map.Entry<Artifact, File> entry : artifactsFiles.entrySet()) {
-                    Artifact artifact = entry.getKey();
-                    File artifactFile = entry.getValue();
-                    publish(artifact, artifactFile, resolver, true);
-                }
-                resolver.commitPublishTransaction();
-                successfullyPublished = true;
-            } finally {
-                if (!successfullyPublished) {
-                    resolver.abortPublishTransaction();
-                }
-            }
-        }
-
-        private void addPublishedDescriptor(Map<Artifact, File> artifactsFiles) {
-            Artifact artifact = MDArtifact.newIvyArtifact(moduleDescriptor);
-            checkArtifactFileExists(artifact, descriptorFile);
-            artifactsFiles.put(artifact, descriptorFile);
-        }
-
-        private void addPublishedArtifact(Artifact artifact, Map<Artifact, File> artifactsFiles) {
-            File artifactFile = new File(artifact.getExtraAttribute(FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE));
-            checkArtifactFileExists(artifact, artifactFile);
-            artifactsFiles.put(artifact, artifactFile);
-        }
-
-        private void checkArtifactFileExists(Artifact artifact, File artifactFile) {
-            if (!artifactFile.exists()) {
-                // TODO:DAZ This hack is required so that we don't log a warning when the Signing plugin is used. We need to allow conditional configurations so we can remove this.
-                if (isSigningArtifact(artifact)) {
-                    return;
-                }
-                String message = String.format("Attempted to publish an artifact '%s' that does not exist '%s'", artifact.getModuleRevisionId(), artifactFile);
-                DeprecationLogger.nagUserOfDeprecatedBehaviour(message);
-            }
-        }
-
-        private boolean isSigningArtifact(Artifact artifact) {
-            return artifact.getType().endsWith(".asc") || artifact.getType().endsWith(".sig");
+    private void addPublishedArtifact(ModuleVersionArtifactPublishMetaData artifact, BuildableModuleVersionPublishMetaData publication) {
+        if (checkArtifactFileExists(artifact)) {
+            publication.addArtifact(artifact);
         }
+    }
 
-        private Set<Artifact> getAllArtifacts(ModuleDescriptor moduleDescriptor) {
-            Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>();
-            String[] trueConfigurations = ConfigurationUtils.replaceWildcards(configurations.toArray(new String[configurations.size()]), moduleDescriptor);
-            for (String configuration : trueConfigurations) {
-                Collections.addAll(allArtifacts, moduleDescriptor.getArtifacts(configuration));
-            }
-            return allArtifacts;
+    private boolean checkArtifactFileExists(ModuleVersionArtifactPublishMetaData artifact) {
+        File artifactFile = artifact.getFile();
+        if (artifactFile.exists()) {
+            return true;
         }
-
-        private void publish(Artifact artifact, File src,
-                             DependencyResolver resolver, boolean overwrite) throws IOException {
-            IvyContext.getContext().checkInterrupted();
-            //notify triggers that an artifact is about to be published
-            eventManager.fireIvyEvent(
-                    new StartArtifactPublishEvent(resolver, artifact, src, overwrite));
-            boolean successful = false; //set to true once the publish succeeds
-            try {
-                if (src.exists()) {
-                    resolver.publish(artifact, src, overwrite);
-                    successful = true;
-                }
-            } finally {
-                //notify triggers that the publish is finished, successfully or not.
-                eventManager.fireIvyEvent(
-                        new EndArtifactPublishEvent(resolver, artifact, src, overwrite, successful));
-            }
+        // TODO:DAZ This hack is required so that we don't log a warning when the Signing plugin is used. We need to allow conditional configurations so we can remove this.
+        if (!isSigningArtifact(artifact.getArtifactName())) {
+            String message = String.format("Attempted to publish an artifact '%s' that does not exist '%s'", artifact.getId(), artifactFile);
+            DeprecationLogger.nagUserOfDeprecatedBehaviour(message);
         }
+        return false;
     }
 
+    private boolean isSigningArtifact(IvyArtifactName artifact) {
+        return artifact.getType().endsWith(".asc") || artifact.getType().endsWith(".sig");
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
deleted file mode 100644
index 7d42d31..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.settings.IvySettings;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyFactory implements IvyFactory {
-    private final Map<IvySettings, Ivy> instanceCache = new HashMap<IvySettings, Ivy>();
-    
-    public Ivy createIvy(IvySettings ivySettings) {
-        Ivy ivy = instanceCache.get(ivySettings);
-        if (ivy == null) {
-            ivy = Ivy.newInstance(ivySettings);
-            instanceCache.put(ivySettings, ivy);
-        }
-        return ivy;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
index 1cf335d..e296b08 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
@@ -16,37 +16,37 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.CachingDirectedGraphWalker;
-import org.gradle.api.internal.DirectedGraphWithEdgeValues;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedConfigurationResults;
 import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.internal.Factory;
+import org.gradle.internal.graph.CachingDirectedGraphWalker;
+import org.gradle.internal.graph.DirectedGraphWithEdgeValues;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.*;
 
-public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder, LenientConfiguration {
-    private final ResolvedDependency root;
+public class DefaultLenientConfiguration implements LenientConfiguration {
+    private CacheLockingManager cacheLockingManager;
     private final Configuration configuration;
-    private final Map<ModuleDependency, ResolvedDependency> firstLevelDependencies = new LinkedHashMap<ModuleDependency, ResolvedDependency>();
-    private final Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
-    private final Set<UnresolvedDependency> unresolvedDependencies = new LinkedHashSet<UnresolvedDependency>();
-    private final CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact> walker
-            = new CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact>(new ResolvedDependencyArtifactsGraph());
+    private ResolvedConfigurationResults results;
 
-    public DefaultLenientConfiguration(Configuration configuration, ResolvedDependency root) {
+    public DefaultLenientConfiguration(Configuration configuration, ResolvedConfigurationResults results, CacheLockingManager cacheLockingManager) {
         this.configuration = configuration;
-        this.root = root;
+        this.results = results;
+        this.cacheLockingManager = cacheLockingManager;
     }
 
     public boolean hasError() {
-        return !unresolvedDependencies.isEmpty();
+        return results.hasError();
     }
 
     public void rethrowFailure() throws ResolveException {
-        if (!unresolvedDependencies.isEmpty()) {
+        if (hasError()) {
             List<Throwable> failures = new ArrayList<Throwable>();
-            for (UnresolvedDependency unresolvedDependency : unresolvedDependencies) {
+            for (UnresolvedDependency unresolvedDependency : results.getUnresolvedDependencies()) {
                 failures.add(unresolvedDependency.getProblem());
             }
             throw new ResolveException(configuration, failures);
@@ -54,36 +54,16 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
     }
 
     public Set<UnresolvedDependency> getUnresolvedModuleDependencies() {
-        return unresolvedDependencies;
+        return results.getUnresolvedDependencies();
     }
 
     public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
-        return artifacts;
-    }
-
-    public ResolvedDependency getRoot() {
-        return root;
-    }
-
-    public void addFirstLevelDependency(ModuleDependency moduleDependency, ResolvedDependency refersTo) {
-        firstLevelDependencies.put(moduleDependency, refersTo);
-    }
-
-    public void addArtifact(ResolvedArtifact artifact) {
-        artifacts.add(artifact);
-    }
-
-    public void addUnresolvedDependency(UnresolvedDependency unresolvedDependency) {
-        unresolvedDependencies.add(unresolvedDependency);
-    }
-
-    public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
-        return root.getChildren();
+        return results.getArtifacts();
     }
 
     public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) {
         Set<ResolvedDependency> matches = new LinkedHashSet<ResolvedDependency>();
-        for (Map.Entry<ModuleDependency, ResolvedDependency> entry : firstLevelDependencies.entrySet()) {
+        for (Map.Entry<ModuleDependency, ResolvedDependency> entry : results.more().getFirstLevelDependencies().entrySet()) {
             if (dependencySpec.isSatisfiedBy(entry.getKey())) {
                 matches.add(entry.getValue());
             }
@@ -107,27 +87,35 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
      * @param dependencySpec dependency spec
      */
     public Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec) {
-        Set<ResolvedArtifact> allArtifacts = getAllArtifacts(dependencySpec);
-        return CollectionUtils.filter(allArtifacts, new Spec<ResolvedArtifact>() {
-            public boolean isSatisfiedBy(ResolvedArtifact element) {
-                try {
-                    File file = element.getFile();
-                    return file != null;
-                } catch (ArtifactResolveException e) {
-                    return false;
-                }
+        final Set<ResolvedArtifact> allArtifacts = getAllArtifacts(dependencySpec);
+        return cacheLockingManager.useCache("retrieve artifacts from " + configuration, new Factory<Set<ResolvedArtifact>>() {
+            public Set<ResolvedArtifact> create() {
+                return CollectionUtils.filter(allArtifacts, new Spec<ResolvedArtifact>() {
+                    public boolean isSatisfiedBy(ResolvedArtifact element) {
+                        try {
+                            File file = element.getFile();
+                            return file != null;
+                        } catch (ArtifactResolveException e) {
+                            return false;
+                        }
+                    }
+                });
             }
         });
     }
 
-    private Set<File> getFiles(Set<ResolvedArtifact> artifacts) {
-        Set<File> files = new LinkedHashSet<File>();
-        for (ResolvedArtifact artifact : artifacts) {
-            File depFile = artifact.getFile();
-            if (depFile != null) {
-                files.add(depFile);
+    private Set<File> getFiles(final Set<ResolvedArtifact> artifacts) {
+        final Set<File> files = new LinkedHashSet<File>();
+        cacheLockingManager.useCache("resolve files from " + configuration, new Runnable() {
+            public void run() {
+                for (ResolvedArtifact artifact : artifacts) {
+                    File depFile = artifact.getFile();
+                    if (depFile != null) {
+                        files.add(depFile);
+                    }
+                }
             }
-        }
+        });
         return files;
     }
 
@@ -137,12 +125,21 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
      * @param dependencySpec dependency spec
      */
     public Set<ResolvedArtifact> getAllArtifacts(Spec<? super Dependency> dependencySpec) {
+        //this is not very nice might be good enough until we get rid of ResolvedConfiguration and friends
+        //avoid traversing the graph causing the full ResolvedDependency graph to be loaded for the most typical scenario
+        if (dependencySpec == Specs.SATISFIES_ALL) {
+            return results.getArtifacts();
+        }
+
+        CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact> walker
+                = new CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact>(new ResolvedDependencyArtifactsGraph());
+
         Set<ResolvedDependency> firstLevelModuleDependencies = getFirstLevelModuleDependencies(dependencySpec);
 
         Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
 
         for (ResolvedDependency resolvedDependency : firstLevelModuleDependencies) {
-            artifacts.addAll(resolvedDependency.getParentArtifacts(root));
+            artifacts.addAll(resolvedDependency.getParentArtifacts(results.more().getRoot()));
             walker.add(resolvedDependency);
         }
 
@@ -154,9 +151,13 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
         return configuration;
     }
 
+    public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+        return results.more().getRoot().getChildren();
+    }
+
     private static class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
-        public void getNodeValues(ResolvedDependency node, Collection<ResolvedArtifact> values,
-                                  Collection<ResolvedDependency> connectedNodes) {
+        public void getNodeValues(ResolvedDependency node, Collection<? super ResolvedArtifact> values,
+                                  Collection<? super ResolvedDependency> connectedNodes) {
             connectedNodes.addAll(node.getChildren());
         }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
index a0869af..146ccbe 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
@@ -17,20 +17,15 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.*;
 import org.gradle.api.specs.Spec;
-import org.gradle.internal.Factory;
 
 import java.io.File;
 import java.util.Set;
 
-import static java.lang.String.format;
-
 public class DefaultResolvedConfiguration implements ResolvedConfiguration {
     private final DefaultLenientConfiguration configuration;
-    private final CacheLockingManager cacheLockingManager;
 
-    public DefaultResolvedConfiguration(DefaultLenientConfiguration configuration, CacheLockingManager cacheLockingManager) {
+    public DefaultResolvedConfiguration(DefaultLenientConfiguration configuration) {
         this.configuration = configuration;
-        this.cacheLockingManager = cacheLockingManager;
     }
 
     public boolean hasError() {
@@ -47,11 +42,7 @@ public class DefaultResolvedConfiguration implements ResolvedConfiguration {
 
     public Set<File> getFiles(final Spec<? super Dependency> dependencySpec) throws ResolveException {
         rethrowFailure();
-        return cacheLockingManager.useCache(format("resolving files from %s", configuration.getConfiguration()), new Factory<Set<File>>() {
-            public Set<File> create() {
-                return configuration.getFilesStrict(dependencySpec);
-            }
-        });
+        return configuration.getFilesStrict(dependencySpec);
     }
 
     public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
deleted file mode 100644
index 4c37d0b..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.util.Message;
-import org.gradle.internal.Factory;
-
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultSettingsConverter implements SettingsConverter {
-    private final Factory<IvySettings> settingsFactory;
-    private IvySettings publishSettings;
-    private IvySettings resolveSettings;
-
-    public DefaultSettingsConverter(Factory<IvySettings> settingsFactory) {
-        this.settingsFactory = settingsFactory;
-        Message.setDefaultLogger(new IvyLoggingAdaper());
-    }
-
-    public IvySettings convertForPublish(List<DependencyResolver> publishResolvers) {
-        if (publishSettings == null) {
-            publishSettings = settingsFactory.create();
-        } else {
-            publishSettings.getResolvers().clear();
-        }
-        for (DependencyResolver dependencyResolver : publishResolvers) {
-            dependencyResolver.setSettings(publishSettings);
-        }
-        return publishSettings;
-    }
-
-    public IvySettings convertForResolve(DependencyResolver defaultResolver) {
-        if (resolveSettings == null) {
-            resolveSettings = settingsFactory.create();
-        } else {
-            resolveSettings.getResolvers().clear();
-        }
-
-        resolveSettings.addResolver(defaultResolver);
-        resolveSettings.setDefaultResolver(defaultResolver.getName());
-        
-        return resolveSettings;
-    }
-
-    public IvySettings getForResolve() {
-        if (resolveSettings == null) {
-            resolveSettings = settingsFactory.create();
-        }
-        return resolveSettings;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
index a9c444c..3348705 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
@@ -15,7 +15,6 @@
  */
 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.util.DeprecationLogger;
@@ -31,7 +30,7 @@ public class DefaultUnresolvedDependency implements UnresolvedDependency {
 
     public String getId() {
         DeprecationLogger.nagUserOfReplacedMethod("UnresolvedDependency.getId()", "UnresolvedDependency.getSelector()");
-        return ModuleRevisionId.newInstance(selector.getGroup(), selector.getName(), selector.getVersion()).toString();
+        return IvyUtil.createModuleRevisionId(selector.getGroup(), selector.getName(), selector.getVersion()).toString();
     }
 
     public ModuleVersionSelector getSelector() {
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
deleted file mode 100755
index 73b9c3d..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.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.api.internal.artifacts.ivyservice;
-
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
-
-/**
- * Resolves a dependency to the meta-data for a module.
- */
-public interface DependencyToModuleResolver {
-    /**
-     * Resolves the given dependency to a module version id. Failures are packaged up in the returned result.
-     */
-    void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
index 91aa32d..92a30d8 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 
 /**
  * Resolves a dependency to the id for a module.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionResolver.java
new file mode 100755
index 0000000..1a86684
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionResolver.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.metadata.DependencyMetaData;
+
+/**
+ * Resolves a dependency to a component instance.
+ */
+public interface DependencyToModuleVersionResolver {
+    /**
+     * Resolves the given dependency to component instance. Failures are packaged up in the returned result.
+     */
+    void resolve(DependencyMetaData dependency, BuildableComponentResolveResult 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 444809a..97e8832 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
@@ -15,8 +15,14 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
@@ -33,15 +39,19 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         this.dependencyResolver = dependencyResolver;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
-        final ResolverResults results;
+    public void resolve(ConfigurationInternal configuration,
+                        List<? extends ResolutionAwareRepository> repositories,
+                        ModuleMetadataProcessor metadataProcessor,
+                        ResolverResults results) throws ResolveException {
         try {
-            results = dependencyResolver.resolve(configuration, repositories);
+            dependencyResolver.resolve(configuration, repositories, metadataProcessor, results);
         } catch (final Throwable e) {
-            return new ResolverResults(new BrokenResolvedConfiguration(e, configuration), wrapException(e, configuration));
+            results.failed(new BrokenResolvedConfiguration(e, configuration), wrapException(e, configuration));
+            return;
         }
-        ResolvedConfiguration withErrorHandling = new ErrorHandlingResolvedConfiguration(results.getResolvedConfiguration(), configuration);
-        return results.withResolvedConfiguration(withErrorHandling);
+        ResolvedConfiguration wrappedConfiguration = new ErrorHandlingResolvedConfiguration(results.getResolvedConfiguration(), configuration);
+        ResolutionResult wrappedResult = new ErrorHandlingResolutionResult(results.getResolutionResult(), configuration);
+        results.resolved(wrappedConfiguration, wrappedResult);
     }
 
     private static ResolveException wrapException(Throwable e, Configuration configuration) {
@@ -51,6 +61,98 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         return new ResolveException(configuration, e);
     }
 
+    private static class ErrorHandlingLenientConfiguration implements LenientConfiguration {
+        private final LenientConfiguration lenientConfiguration;
+        private final Configuration configuration;
+
+        private ErrorHandlingLenientConfiguration(LenientConfiguration lenientConfiguration, Configuration configuration) {
+            this.lenientConfiguration = lenientConfiguration;
+            this.configuration = configuration;
+        }
+
+        public Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec) {
+            try {
+                return lenientConfiguration.getArtifacts(dependencySpec);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) {
+            try {
+                return lenientConfiguration.getFirstLevelModuleDependencies(dependencySpec);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<UnresolvedDependency> getUnresolvedModuleDependencies() {
+            try {
+                return lenientConfiguration.getUnresolvedModuleDependencies();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+            try {
+                return lenientConfiguration.getFiles(dependencySpec);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+    }
+
+    private static class ErrorHandlingResolutionResult implements ResolutionResult {
+        private final ResolutionResult resolutionResult;
+        private final Configuration configuration;
+
+        public ErrorHandlingResolutionResult(ResolutionResult resolutionResult, Configuration configuration) {
+            this.resolutionResult = resolutionResult;
+            this.configuration = configuration;
+        }
+
+        public ResolvedComponentResult getRoot() {
+            try {
+                return resolutionResult.getRoot();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public void allDependencies(Action<? super DependencyResult> action) {
+            resolutionResult.allDependencies(action);
+        }
+
+        public Set<? extends DependencyResult> getAllDependencies() {
+            try {
+                return resolutionResult.getAllDependencies();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public void allDependencies(Closure closure) {
+            resolutionResult.allDependencies(closure);
+        }
+
+        public Set<ResolvedComponentResult> getAllComponents() {
+            try {
+                return resolutionResult.getAllComponents();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public void allComponents(Action<? super ResolvedComponentResult> action) {
+            resolutionResult.allComponents(action);
+        }
+
+        public void allComponents(Closure closure) {
+            resolutionResult.allComponents(closure);
+        }
+    }
+    
     private static class ErrorHandlingResolvedConfiguration implements ResolvedConfiguration {
         private final ResolvedConfiguration resolvedConfiguration;
         private final Configuration configuration;
@@ -66,7 +168,11 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         }
 
         public LenientConfiguration getLenientConfiguration() {
-            return resolvedConfiguration.getLenientConfiguration();
+            try {
+                return new ErrorHandlingLenientConfiguration(resolvedConfiguration.getLenientConfiguration(), configuration);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
         }
 
         public void rethrowFailure() throws ResolveException {
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 f34fe63..f6a2f9a 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
@@ -16,12 +16,17 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.Ivy;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.MDArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.Action;
 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.Configurations;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.metadata.BuildableModuleVersionPublishMetaData;
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData;
 import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 
 import java.io.File;
@@ -29,42 +34,51 @@ import java.util.ArrayList;
 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 IvyFactory ivyFactory;
+    private final LocalComponentFactory publishLocalComponentFactory;
+    private final IvyContextManager ivyContextManager;
     private final IvyDependencyPublisher dependencyPublisher;
+    private final IvyModuleDescriptorWriter ivyModuleDescriptorWriter;
 
-    public IvyBackedArtifactPublisher(SettingsConverter settingsConverter,
-                                      ModuleDescriptorConverter publishModuleDescriptorConverter,
-                                      IvyFactory ivyFactory,
-                                      IvyDependencyPublisher dependencyPublisher) {
-        this.settingsConverter = settingsConverter;
-        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
-        this.ivyFactory = ivyFactory;
+    public IvyBackedArtifactPublisher(LocalComponentFactory publishLocalComponentFactory,
+                                      IvyContextManager ivyContextManager,
+                                      IvyDependencyPublisher dependencyPublisher,
+                                      IvyModuleDescriptorWriter ivyModuleDescriptorWriter) {
+        this.publishLocalComponentFactory = publishLocalComponentFactory;
+        this.ivyContextManager = ivyContextManager;
         this.dependencyPublisher = dependencyPublisher;
+        this.ivyModuleDescriptorWriter = ivyModuleDescriptorWriter;
     }
 
-    private Ivy ivyForPublish(List<DependencyResolver> publishResolvers) {
-        return ivyFactory.createIvy(settingsConverter.convertForPublish(publishResolvers));
-    }
+    public void publish(final Iterable<? extends PublicationAwareRepository> repositories, final ModuleInternal module, final Configuration configuration, final File descriptor) throws PublishException {
+        ivyContextManager.withIvy(new Action<Ivy>() {
+            public void execute(Ivy ivy) {
+                Set<Configuration> allConfigurations = configuration.getAll();
+                Set<Configuration> configurationsToPublish = configuration.getHierarchy();
 
-    public void publish(Iterable<? extends PublicationAwareRepository> repositories, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException {
-        List<DependencyResolver> publishResolvers = new ArrayList<DependencyResolver>();
-        for (PublicationAwareRepository repository : repositories) {
-            publishResolvers.add(repository.createPublisher());
-        }
-        Ivy ivy = ivyForPublish(publishResolvers);
-        Set<String> confs = Configurations.getNames(configurations, false);
-        dependencyPublisher.publish(
-                confs,
-                publishResolvers,
-                publishModuleDescriptorConverter.convert(configurations, module),
-                descriptor,
-                ivy.getEventManager());
-    }
+                MutableLocalComponentMetaData componentMetaData = publishLocalComponentFactory.convert(allConfigurations, module);
+                if (descriptor != null) {
+                    ModuleDescriptor moduleDescriptor = componentMetaData.getModuleDescriptor();
+                    ivyModuleDescriptorWriter.write(moduleDescriptor, descriptor);
+                }
 
+                // Need to convert a second time, to determine which artifacts to publish (and yes, this isn't a great way to do things...)
+                componentMetaData = publishLocalComponentFactory.convert(configurationsToPublish, module);
+                BuildableModuleVersionPublishMetaData publishMetaData = componentMetaData.toPublishMetaData();
+                if (descriptor != null) {
+                    Artifact artifact = MDArtifact.newIvyArtifact(componentMetaData.getModuleDescriptor());
+                    publishMetaData.addArtifact(artifact, descriptor);
+                }
+
+                List<ModuleVersionPublisher> publishResolvers = new ArrayList<ModuleVersionPublisher>();
+                for (PublicationAwareRepository repository : repositories) {
+                    ModuleVersionPublisher publisher = repository.createPublisher();
+                    publisher.setSettings(ivy.getSettings());
+                    publishResolvers.add(publisher);
+                }
+
+                dependencyPublisher.publish(publishResolvers, publishMetaData);
+            }
+        });
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java
new file mode 100644
index 0000000..660f384
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import net.jcip.annotations.ThreadSafe;
+import org.apache.ivy.Ivy;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+
+ at ThreadSafe
+public interface IvyContextManager {
+    /**
+     * Executes the given action against an Ivy instance. Sets up the Ivy context before the action and cleans up at the end.
+     *
+     * <p>The Ivy instance of the calling thread is reused if the thread is already executing an action against an Ivy instance.
+     */
+    void withIvy(Action<? super Ivy> action);
+
+    /**
+     * Executes the given action against an Ivy instance and returns the result. Sets up the Ivy context before the action and cleans up at the end.
+     *
+     * <p>The Ivy instance of the calling thread is reused if the thread is already executing an action against an Ivy instance.
+     */
+    <T> T withIvy(Transformer<? extends T, ? super Ivy> action);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
index 650127c..f8d788f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
@@ -15,20 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.event.EventManager;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionPublishMetaData;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
 
-import java.io.File;
 import java.util.List;
-import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public interface IvyDependencyPublisher {
-    void publish(Set<String> configurations,
-                 List<DependencyResolver> publishResolvers,
-                 ModuleDescriptor moduleDescriptor,
-                 File descriptorDestination, EventManager eventManager);
+    void publish(List<ModuleVersionPublisher> publishResolvers,
+                 ModuleVersionPublishMetaData publishMetaData);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
deleted file mode 100644
index 4dfc079..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.settings.IvySettings;
-
-/**
- * @author Hans Dockter
- */
-public interface IvyFactory {
-    public Ivy createIvy(IvySettings ivySettings);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java
index 3daaaff..d072a42 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java
@@ -24,8 +24,6 @@ import org.gradle.api.logging.Logging;
  * logback. This would be bad for embedded usage. We only want one on slf4j. But slf4j has no constants for log levels.
  * As we want to avoid the execution of if statements for each Ivy request, we use Map which delegates Ivy log
  * statements to Sl4j action classes.
- *
- * @author Hans Dockter
  */
 public class IvyLoggingAdaper extends AbstractMessageLogger {
     private final Logger logger = Logging.getLogger(IvyLoggingAdaper.class);
@@ -38,6 +36,22 @@ public class IvyLoggingAdaper extends AbstractMessageLogger {
         log(msg, level);
     }
 
+    /**
+     * Overrides the default implementation, which doesn't delegate to {@link #log(String, int)}.
+     */
+    @Override
+    public void warn(String msg) {
+        logger.warn(msg);
+    }
+
+    /**
+     * Overrides the default implementation, which doesn't delegate to {@link #log(String, int)}.
+     */
+    @Override
+    public void error(String msg) {
+        logger.error(msg);
+    }
+
     public void doProgress() {
     }
 
diff --git a/subprojects/core/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
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyResolverBackedModuleVersionPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyResolverBackedModuleVersionPublisher.java
new file mode 100644
index 0000000..5fb10c3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyResolverBackedModuleVersionPublisher.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.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactPublishMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionPublishMetaData;
+
+import java.io.File;
+import java.io.IOException;
+
+public class IvyResolverBackedModuleVersionPublisher implements ModuleVersionPublisher {
+    private final DependencyResolver resolver;
+
+    public IvyResolverBackedModuleVersionPublisher(DependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("repository '%s'", resolver.getName());
+    }
+
+    public void setSettings(IvySettings settings) {
+        settings.addResolver(resolver);
+    }
+
+    public void publish(ModuleVersionPublishMetaData moduleVersion) throws IOException {
+        boolean successfullyPublished = false;
+        try {
+            ModuleVersionIdentifier id = moduleVersion.getId();
+            ModuleRevisionId ivyId = IvyUtil.createModuleRevisionId(id.getGroup(), id.getName(), id.getVersion());
+            resolver.beginPublishTransaction(ivyId, true);
+            for (ModuleVersionArtifactPublishMetaData artifactMetaData : moduleVersion.getArtifacts()) {
+                Artifact artifact = artifactMetaData.toIvyArtifact();
+                File artifactFile = artifactMetaData.getFile();
+                resolver.publish(artifact, artifactFile, true);
+            }
+            resolver.commitPublishTransaction();
+            successfullyPublished = true;
+        } finally {
+            if (!successfullyPublished) {
+                resolver.abortPublishTransaction();
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactory.java
deleted file mode 100644
index 11f49c3..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactory.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.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.ArtifactRepositoryContainer;
-import org.gradle.internal.Factory;
-
-import java.io.File;
-
-public class IvySettingsFactory implements Factory<IvySettings> {
-    private final ArtifactCacheMetaData cacheMetaData;
-
-    public IvySettingsFactory(ArtifactCacheMetaData cacheMetaData) {
-        this.cacheMetaData = cacheMetaData;
-    }
-
-    public IvySettings create() {
-        IvySettings ivySettings = new IvySettings();
-        ivySettings.setDefaultCache(new File(cacheMetaData.getCacheDir(), "ivy"));
-        ivySettings.setDefaultCacheIvyPattern(ArtifactRepositoryContainer.DEFAULT_CACHE_IVY_PATTERN);
-        ivySettings.setDefaultCacheArtifactPattern(ArtifactRepositoryContainer.DEFAULT_CACHE_ARTIFACT_PATTERN);
-        ivySettings.setVariable("ivy.log.modules.in.use", "false");
-
-        return ivySettings;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
index 856cd82..2e95e50 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
@@ -15,40 +15,63 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.util.GUtil;
 
-import java.util.HashMap;
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
+import static java.util.Collections.emptyMap;
+
 public class IvyUtil {
 
+    private static final Object MODULE_ID_LOCK = new Object(); //see GRADLE-3027
+
     public static ModuleRevisionId createModuleRevisionId(Module module) {
-        return createModuleRevisionId(module, new HashMap<String, String>());
+        return createModuleRevisionId(module.getGroup(), module.getName(), module.getVersion());
     }
 
-    public static ModuleRevisionId createModuleRevisionId(Module module, Map<String, String> extraAttributes) {
-        return createModuleRevisionId(module.getGroup(), module.getName(), module.getVersion(), extraAttributes);
+    public static ModuleRevisionId createModuleRevisionId(Dependency dependency) {
+        return createModuleRevisionId(dependency.getGroup(), dependency.getName(), dependency.getVersion());
     }
 
-    public static ModuleRevisionId createModuleRevisionId(Dependency dependency) {
-        return createModuleRevisionId(dependency, new HashMap<String, String>());
+    public static ModuleRevisionId createModuleRevisionId(String group, String name, String version) {
+        return createModuleRevisionId(emptyStringIfNull(group), name, null, emptyStringIfNull(version), emptyMap());
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(ModuleVersionIdentifier id) {
+        return createModuleRevisionId(id.getGroup(), id.getName(), id.getVersion());
     }
 
-    public static ModuleRevisionId createModuleRevisionId(Dependency dependency, Map<String, String> extraAttributes) {
-        return createModuleRevisionId(dependency.getGroup(), dependency.getName(), dependency.getVersion(), extraAttributes);
+    public static ModuleRevisionId createModuleRevisionId(ModuleComponentIdentifier id) {
+        return createModuleRevisionId(id.getGroup(), id.getModule(), id.getVersion());
     }
 
-    public static ModuleRevisionId createModuleRevisionId(String group, String name, String version, Map<String, String> extraAttributes) {
-        return ModuleRevisionId.newInstance(emptyStringIfNull(group), name, emptyStringIfNull(version), extraAttributes);
+    public static ModuleRevisionId createModuleRevisionId(ModuleRevisionId revId, String version) {
+        return createModuleRevisionId(revId.getOrganisation(), revId.getName(), revId.getBranch(), version, revId.getQualifiedExtraAttributes());
     }
 
     private static String emptyStringIfNull(String value) {
         return GUtil.elvis(value, "");
     }
-}
+
+    public static ModuleRevisionId createModuleRevisionId(String org, String name, String branch, String rev, Map extraAttributes) {
+        return createModuleRevisionId(org, name, branch, rev, extraAttributes, true);
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(String org, String name, String branch, String revConstraint, Map extraAttributes, boolean replaceNullBranchWithDefault) {
+        synchronized (MODULE_ID_LOCK) {
+            return ModuleRevisionId.newInstance(org, name, branch, revConstraint, extraAttributes, replaceNullBranchWithDefault);
+        }
+    }
+
+    public static ModuleId createModuleId(String org, String name) {
+        synchronized (MODULE_ID_LOCK) {
+            return ModuleId.newInstance(org, name);
+        }
+    }
+}
\ No newline at end of file
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
index 50bcbf7..2a57bdb 100644
--- 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
@@ -16,321 +16,225 @@
 
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.IvyPatternHelper;
+import com.google.common.base.Joiner;
 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.IoActions;
-import org.gradle.util.TextUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.Writer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.xml.SimpleXmlWriter;
+import org.gradle.internal.UncheckedException;
+
+import java.io.*;
+import java.lang.reflect.Field;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Map;
+import java.util.*;
 
 public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
-
     public static final String IVY_DATE_PATTERN = "yyyyMMddHHmmss";
+    private final Field dependencyConfigField;
 
-    private 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("\"");
-                }
+    public IvyXmlModuleDescriptorWriter() {
+        try {
+            dependencyConfigField = DefaultDependencyDescriptor.class.getDeclaredField("confs");
+        } catch (NoSuchFieldException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        dependencyConfigField.setAccessible(true);
+    }
 
-                String version = "2.0";
-                if (md.getInheritedDescriptors().length > 0) {
-                    version = "2.2";
-                }
+    private void writeTo(ModuleDescriptor md, SimpleXmlWriter writer) throws IOException {
+        writer.startElement("ivy-module");
+        writer.attribute("version", "2.0");
 
-                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());
-            }
-        };
+        Map<String, String> namespaces = md.getExtraAttributesNamespaces();
+        for (Map.Entry<String, String> entry : namespaces.entrySet()) {
+            writer.attribute("xmlns:" + entry.getKey(), entry.getValue());
+        }
+
+        printInfoTag(md, writer);
+        printConfigurations(md, writer);
+        printPublications(md, writer);
+        printDependencies(md, writer);
+
+        writer.endElement();
     }
 
     public void write(ModuleDescriptor md, File output) {
-        IoActions.writeTextFile(output, "UTF-8", createWriterAction(md));
+        try {
+            output.getParentFile().mkdirs();
+            OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
+            try {
+                SimpleXmlWriter xmlWriter = new SimpleXmlWriter(outputStream, "  ");
+                writeTo(md, xmlWriter);
+                xmlWriter.flush();
+            } finally {
+                outputStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 
-    private static void printDependencies(ModuleDescriptor md, Writer writer) throws IOException {
+    private void printDependencies(ModuleDescriptor md, SimpleXmlWriter writer) throws IOException {
         DependencyDescriptor[] dds = md.getDependencies();
         if (dds.length > 0) {
-            writer.write("\t<dependencies>");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.startElement("dependencies");
             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());
+            writer.endElement();
         }
     }
 
-    protected static void printDependency(ModuleDescriptor md, DependencyDescriptor dep,
-                                          Writer writer) throws IOException {
-        writer.write("<dependency");
+    protected void printDependency(ModuleDescriptor md, DependencyDescriptor dep,
+                                          SimpleXmlWriter writer) throws IOException {
+        writer.startElement("dependency");
+
         ModuleRevisionId dependencyRevisionId = dep.getDependencyRevisionId();
-        writer.write(" org=\""
-                + XMLHelper.escape(dependencyRevisionId.getOrganisation()) + "\"");
-        writer.write(" name=\""
-                + XMLHelper.escape(dependencyRevisionId.getName()) + "\"");
+        writer.attribute("org", dependencyRevisionId.getOrganisation());
+        writer.attribute("name", dependencyRevisionId.getName());
         if (dependencyRevisionId.getBranch() != null) {
-            writer.write(" branch=\""
-                    + XMLHelper.escape(dependencyRevisionId.getBranch()) + "\"");
+            writer.attribute("branch", dependencyRevisionId.getBranch());
         }
-        writer.write(" rev=\""
-                + XMLHelper.escape(dependencyRevisionId.getRevision()) + "\"");
-        if (!dep.getDynamicConstraintDependencyRevisionId()
-                .equals(dependencyRevisionId)) {
+        writer.attribute("rev", dependencyRevisionId.getRevision());
+        if (!dep.getDynamicConstraintDependencyRevisionId().equals(dependencyRevisionId)) {
             if (dep.getDynamicConstraintDependencyRevisionId().getBranch() != null) {
-                writer.write(" branchConstraint=\"" + XMLHelper.escape(
-                        dep.getDynamicConstraintDependencyRevisionId().getBranch()) + "\"");
+                writer.attribute("branchConstraint", dep.getDynamicConstraintDependencyRevisionId().getBranch());
             }
-            writer.write(" revConstraint=\"" + XMLHelper.escape(
-                    dep.getDynamicConstraintDependencyRevisionId().getRevision()) + "\"");
+            writer.attribute("revConstraint", dep.getDynamicConstraintDependencyRevisionId().getRevision());
         }
         if (dep.isForce()) {
-            writer.write(" force=\"" + dep.isForce() + "\"");
+            writer.attribute("force", "true");
         }
         if (dep.isChanging()) {
-            writer.write(" changing=\"" + dep.isChanging() + "\"");
+            writer.attribute("changing", "true");
         }
         if (!dep.isTransitive()) {
-            writer.write(" transitive=\"" + dep.isTransitive() + "\"");
+            writer.attribute("transitive", "false");
         }
-        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("\"");
+        writer.attribute("conf", getConfMapping(dep));
 
-        printExtraAttributes(dep, writer, " ");
+        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());
+
+        writer.endElement();
+    }
+
+    private String getConfMapping(DependencyDescriptor dep) {
+        StringBuilder confs = new StringBuilder();
+        String[] modConfs = dep.getModuleConfigurations();
+
+        Map<String, List<String>> configMappings;
+        if (dep instanceof DefaultDependencyDescriptor) {
+            // The `getDependencyConfigurations()` implementation for DefaultDependencyDescriptor does some interpretation of the RHS of the configuration
+            // mappings, and gets it wrong for mappings such as '*->@' pr '*->#'. So, instead, reach into the descriptor and get the raw mappings out.
+            try {
+                configMappings = (Map<String, List<String>>) dependencyConfigField.get(dep);
+            } catch (IllegalAccessException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
         } else {
-            writer.write("\t\t</dependency>");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            configMappings = new HashMap<String, List<String>>();
+            for (String modConf : modConfs) {
+                configMappings.put(modConf, Arrays.asList(dep.getDependencyConfigurations(modConfs)));
+            }
         }
-    }
 
-    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()) + "\"");
+        for (int j = 0; j < modConfs.length; j++) {
+            List<String> depConfs = configMappings.get(modConfs[j]);
+            confs.append(modConfs[j]).append("->");
+            for (int k = 0; k < depConfs.size(); k++) {
+                confs.append(depConfs.get(k));
+                if (k + 1 < depConfs.size()) {
+                    confs.append(",");
                 }
-                writer.write("/>");
-                writer.write(TextUtil.getPlatformLineSeparator());
-            } else {
-                Message.verbose("ignoring unhandled DependencyDescriptorMediator: "
-                        + mediator.getClass());
+            }
+            if (j + 1 < modConfs.length) {
+                confs.append(";");
             }
         }
+        return confs.toString();
     }
 
-    private static void printAllExcludes(ModuleDescriptor md, Writer writer) throws IOException {
+    private static void printAllExcludes(ModuleDescriptor md, SimpleXmlWriter 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());
+        for (ExcludeRule exclude : excludes) {
+            writer.startElement("exclude");
+            writer.attribute("org", exclude.getId().getModuleId().getOrganisation());
+            writer.attribute("module", exclude.getId().getModuleId().getName());
+            writer.attribute("artifact", exclude.getId().getName());
+            writer.attribute("type", exclude.getId().getType());
+            writer.attribute("ext", exclude.getId().getExt());
+            String[] ruleConfs = exclude.getConfigurations();
+            if (!Arrays.asList(ruleConfs).equals(
+                    Arrays.asList(md.getConfigurationsNames()))) {
+                writer.attribute("conf", Joiner.on(',').join(ruleConfs));
             }
+            writer.attribute("matcher", exclude.getMatcher().getName());
+            writer.endElement();
         }
     }
 
-    private static void printDependencyExcludeRules(ModuleDescriptor md, Writer writer,
+    private static void printDependencyExcludeRules(ModuleDescriptor md, SimpleXmlWriter 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());
+        for (ExcludeRule exclude : excludes) {
+            writer.startElement("exclude");
+            writer.attribute("org", exclude.getId().getModuleId().getOrganisation());
+            writer.attribute("module", exclude.getId().getModuleId().getName());
+            writer.attribute("name", exclude.getId().getName());
+            writer.attribute("type", exclude.getId().getType());
+            writer.attribute("ext", exclude.getId().getExt());
+            String[] ruleConfs = exclude.getConfigurations();
+            if (!Arrays.asList(ruleConfs).equals(
+                    Arrays.asList(md.getConfigurationsNames()))) {
+                writer.attribute("conf", Joiner.on(',').join(ruleConfs));
             }
+            writer.attribute("matcher", exclude.getMatcher().getName());
+            writer.endElement();
         }
     }
 
-    private static void printDependencyIncludeRules(ModuleDescriptor md, Writer writer,
+    private static void printDependencyIncludeRules(ModuleDescriptor md, SimpleXmlWriter 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());
+        for (IncludeRule include : includes) {
+            writer.startElement("include");
+            writer.attribute("name", include.getId().getName());
+            writer.attribute("type", include.getId().getType());
+            writer.attribute("ext", include.getId().getExt());
+            String[] ruleConfs = include.getConfigurations();
+            if (!Arrays.asList(ruleConfs).equals(
+                    Arrays.asList(md.getConfigurationsNames()))) {
+                writer.attribute("conf", Joiner.on(',').join(ruleConfs));
             }
+            writer.attribute("matcher", include.getMatcher().getName());
+            writer.endElement();
         }
     }
 
-    private static void printDependencyArtefacts(ModuleDescriptor md, Writer writer,
+    private static void printDependencyArtefacts(ModuleDescriptor md, SimpleXmlWriter 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());
+        for (DependencyArtifactDescriptor depArtifact : depArtifacts) {
+            writer.startElement("artifact");
+            writer.attribute("name", depArtifact.getName());
+            writer.attribute("type", depArtifact.getType());
+            writer.attribute("ext", depArtifact.getExt());
+            String[] dadconfs = depArtifact.getConfigurations();
+            if (!Arrays.asList(dadconfs).equals(
+                    Arrays.asList(md.getConfigurationsNames()))) {
+                writer.attribute("conf", Joiner.on(',').join(dadconfs));
             }
+            printExtraAttributes(depArtifact, writer);
+            writer.endElement();
         }
     }
 
@@ -339,10 +243,9 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
      *
      * @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);
+    private static void printExtraAttributes(ExtendableItem item, SimpleXmlWriter writer) throws IOException {
+        printExtraAttributes(item.getQualifiedExtraAttributes(), writer);
     }
 
     /**
@@ -350,210 +253,137 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
      *
      * @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 {
+    private static void printExtraAttributes(Map<String, ?> extra, SimpleXmlWriter writer) 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 = " ";
+        for (Map.Entry<String, ?> entry : extra.entrySet()) {
+            writer.attribute(entry.getKey(), entry.getValue().toString());
         }
     }
 
-    private static void printPublications(ModuleDescriptor md, Writer writer) throws IOException {
-        writer.write("\t<publications>");
-        writer.write(TextUtil.getPlatformLineSeparator());
+    private static void printPublications(ModuleDescriptor md, SimpleXmlWriter writer) throws IOException {
+        writer.startElement("publications");
         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());
+            writer.startElement("artifact");
+            writer.attribute("name", artifacts[i].getName());
+            writer.attribute("type", artifacts[i].getType());
+            writer.attribute("ext", artifacts[i].getExt());
+            writer.attribute("conf", getConfs(md, artifacts[i]));
+            printExtraAttributes(artifacts[i], writer);
+            writer.endElement();
+        }
+        writer.endElement();
     }
 
-    private static void printConfigurations(ModuleDescriptor md, Writer writer) throws IOException {
+    private static void printConfigurations(ModuleDescriptor md, SimpleXmlWriter 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");
+            writer.startElement("configurations");
+            for (Configuration conf : confs) {
                 printConfiguration(conf, writer);
             }
-            writer.write("\t</configurations>");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.endElement();
         }
     }
 
-    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()) + "\"");
+    private static void printConfiguration(Configuration conf, SimpleXmlWriter writer) throws IOException {
+        writer.startElement("conf");
+        writer.attribute("name", conf.getName());
+        writer.attribute("visibility", conf.getVisibility().toString());
         String description = conf.getDescription();
         if (description != null) {
-            writer.write(" description=\""
-                    + XMLHelper.escape(description) + "\"");
+            writer.attribute("description", 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("\"");
+            writer.attribute("extends", Joiner.on(',').join(exts));
         }
         if (!conf.isTransitive()) {
-            writer.write(" transitive=\"false\"");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.attribute("transitive", "false");
         }
         if (conf.getDeprecated() != null) {
-            writer.write(" deprecated=\"" + XMLHelper.escape(conf.getDeprecated()) + "\"");
+            writer.attribute("deprecated", conf.getDeprecated());
         }
-        printExtraAttributes(conf, writer, " ");
-        writer.write("/>");
-        writer.write(TextUtil.getPlatformLineSeparator());
+        printExtraAttributes(conf, writer);
+        writer.endElement();
     }
 
-    private static void printInfoTag(ModuleDescriptor md, Writer writer) throws IOException {
+    private static void printInfoTag(ModuleDescriptor md, SimpleXmlWriter 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());
+        writer.startElement("info");
+
+        writer.attribute("organisation", moduleRevisionId.getOrganisation());
+        writer.attribute("module", moduleRevisionId.getName());
 
         ModuleRevisionId resolvedModuleRevisionId = md.getResolvedModuleRevisionId();
         String branch = resolvedModuleRevisionId.getBranch();
         if (branch != null) {
-            writer.write("\t\tbranch=\"" + XMLHelper.escape(branch) + "\"");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.attribute("branch", branch);
         }
         String revision = resolvedModuleRevisionId.getRevision();
         if (revision != null) {
-            writer.write("\t\trevision=\"" + XMLHelper.escape(revision) + "\"");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.attribute("revision", revision);
         }
-        writer.write("\t\tstatus=\"" + XMLHelper.escape(md.getStatus()) + "\"");
-        writer.write(TextUtil.getPlatformLineSeparator());
+        writer.attribute("status", md.getStatus());
 
         SimpleDateFormat ivyDateFormat = new SimpleDateFormat(IVY_DATE_PATTERN);
-
-        writer.write("\t\tpublication=\""
-                + ivyDateFormat.format(md.getResolvedPublicationDate()) + "\"");
-        writer.write(TextUtil.getPlatformLineSeparator());
+        writer.attribute("publication", ivyDateFormat.format(md.getResolvedPublicationDate()));
         if (md.isDefault()) {
-            writer.write("\t\tdefault=\"true\"");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.attribute("default", "true");
         }
         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());
+                writer.attribute("namespace", dmd.getNamespace().getName());
             }
         }
         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());
+            printExtraAttributes(md, writer);
+        }
+
+        ExtendsDescriptor[] parents = md.getInheritedDescriptors();
+        if (parents.length != 0) {
+            throw new UnsupportedOperationException("Extends descriptors not supported.");
+        }
+
+        License[] licenses = md.getLicenses();
+        for (int i = 0; i < licenses.length; i++) {
+            License license = licenses[i];
+            writer.startElement("license");
+            if (license.getName() != null) {
+                writer.attribute("name", license.getName());
             }
-            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 (license.getUrl() != null) {
+                writer.attribute("url", license.getUrl());
             }
-            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());
-                }
+            writer.endElement();
+        }
+
+        if (md.getHomePage() != null || md.getDescription() != null) {
+            writer.startElement("description");
+            if (md.getHomePage() != null) {
+                writer.attribute("homepage", md.getHomePage());
             }
-            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());
+            if (md.getDescription() != null && md.getDescription().trim().length() > 0) {
+                writer.characters(md.getDescription());
             }
-            writer.write("\t</info>");
-            writer.write(TextUtil.getPlatformLineSeparator());
-        } else {
-            writer.write("\t/>");
-            writer.write(TextUtil.getPlatformLineSeparator());
+            writer.endElement();
+        }
+
+        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.startElement(extraDescr.getKey().toString());
+            writer.characters(extraDescr.getValue().toString());
+            writer.endElement();
         }
-    }
 
-    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;
+        writer.endElement();
     }
 
     private static String getConfs(ModuleDescriptor md, Artifact artifact) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java
new file mode 100644
index 0000000..80a5823
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData;
+
+import java.util.Set;
+
+public interface LocalComponentFactory {
+    MutableLocalComponentMetaData convert(Set<? extends Configuration> configurations, ModuleInternal module);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleToModuleVersionResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleToModuleVersionResolver.java
new file mode 100755
index 0000000..b477ab0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleToModuleVersionResolver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Configuration;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+
+import java.util.Set;
+
+/**
+ * Resolves a module to the meta-data for a module.
+ */
+public interface ModuleToModuleVersionResolver {
+    void resolve(ModuleInternal module, Set<? extends Configuration> configurations, BuildableComponentResolveResult result);
+}
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 ce6a4e0..02b08cc 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
@@ -17,7 +17,7 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
 public interface ModuleVersionIdResolveResult {
     /**
@@ -37,10 +37,10 @@ public interface ModuleVersionIdResolveResult {
      * Resolves the meta-data for this module version, if required. Failures are packaged up in the result.
      * @throws ModuleVersionResolveException If id resolution was unsuccessful and the id is unknown.
      */
-    ModuleVersionResolveResult resolve() throws ModuleVersionResolveException;
+    ComponentResolveResult resolve() throws ModuleVersionResolveException;
 
     /**
      * @return why given id was selected. Should return a value even if the resolve failed.
      */
-    ModuleVersionSelectionReason getSelectionReason();
+    ComponentSelectionReason getSelectionReason();
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
index 81b4c48..6bfe2ec 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
@@ -15,23 +15,15 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 
 public class ModuleVersionNotFoundException extends ModuleVersionResolveException {
+    @SuppressWarnings("UnusedDeclaration")
     public ModuleVersionNotFoundException(ModuleVersionSelector selector, String messageFormat) {
         super(selector, messageFormat);
     }
 
-    public ModuleVersionNotFoundException(ModuleRevisionId id, String messageFormat) {
-        super(id, messageFormat);
-    }
-
-    public ModuleVersionNotFoundException(ModuleRevisionId id) {
-        super(id, "Could not find %s.");
-    }
-
     public ModuleVersionNotFoundException(ModuleVersionSelector selector) {
         super(selector, "Could not find any version that matches %s.");
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
index a6a78c9..cf7cebb 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
@@ -15,13 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.AbstractMultiCauseException;
-import org.gradle.api.internal.Contextual;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.exceptions.AbstractMultiCauseException;
+import org.gradle.internal.exceptions.Contextual;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -30,7 +29,7 @@ import java.util.List;
 
 @Contextual
 public class ModuleVersionResolveException extends AbstractMultiCauseException {
-    private final List<List<ModuleRevisionId>> paths = new ArrayList<List<ModuleRevisionId>>();
+    private final List<List<ModuleVersionIdentifier>> paths = new ArrayList<List<ModuleVersionIdentifier>>();
     private final String messageFormat;
     private final ModuleVersionSelector selector;
 
@@ -40,10 +39,6 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
         this.messageFormat = messageFormat;
     }
 
-    public ModuleVersionResolveException(ModuleRevisionId id, String messageFormat) {
-        this(DefaultModuleVersionSelector.newSelector(id.getOrganisation(), id.getName(), id.getRevision()), messageFormat);
-    }
-
     public ModuleVersionResolveException(ModuleVersionIdentifier id, String messageFormat) {
         this(DefaultModuleVersionSelector.newSelector(id.getGroup(), id.getName(), id.getVersion()), messageFormat);
     }
@@ -53,11 +48,6 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
         initCause(cause);
     }
 
-    public ModuleVersionResolveException(ModuleRevisionId id, Throwable cause) {
-        this(id, "Could not resolve %s.");
-        initCause(cause);
-    }
-
     public ModuleVersionResolveException(ModuleVersionSelector selector, Iterable<? extends Throwable> causes) {
         this(selector, "Could not resolve %s.");
         initCauses(causes);
@@ -81,7 +71,7 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
     /**
      * Creates a copy of this exception, with the given incoming paths.
      */
-    public ModuleVersionResolveException withIncomingPaths(Collection<? extends List<ModuleRevisionId>> paths) {
+    public ModuleVersionResolveException withIncomingPaths(Collection<? extends List<ModuleVersionIdentifier>> paths) {
         ModuleVersionResolveException copy = createCopy();
         copy.paths.addAll(paths);
         copy.initCauses(getCauses());
@@ -96,7 +86,7 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
         }
         Formatter formatter = new Formatter();
         formatter.format("%s%nRequired by:", super.getMessage());
-        for (List<ModuleRevisionId> path : paths) {
+        for (List<ModuleVersionIdentifier> path : paths) {
             formatter.format("%n    %s", toString(path.get(0)));
             for (int i = 1; i < path.size(); i++) {
                 formatter.format(" > %s", toString(path.get(i)));
@@ -105,8 +95,8 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
         return formatter.toString();
     }
 
-    private String toString(ModuleRevisionId moduleRevisionId) {
-        return String.format("%s:%s:%s", moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    private String toString(ModuleVersionIdentifier identifier) {
+        return identifier.toString();
     }
 
     protected ModuleVersionResolveException createCopy() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
deleted file mode 100644
index e30a25a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
+++ /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.api.internal.artifacts.ivyservice;
-
-import org.gradle.api.Nullable;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
-
-public interface ModuleVersionResolveResult {
-    /**
-     * Returns the id of this module version.
-     *
-     * @throws ModuleVersionResolveException If resolution was unsuccessful and the id is unknown.
-     */
-    ModuleVersionIdentifier getId() throws ModuleVersionResolveException;
-
-    /**
-     * Returns the meta-data for this module version.
-     *
-     * @throws ModuleVersionResolveException If resolution was unsuccessful and the descriptor is not available.
-     */
-    ModuleVersionMetaData getMetaData() throws ModuleVersionResolveException;
-
-    /**
-     * Returns the resolve failure, if any.
-     */
-    @Nullable
-    ModuleVersionResolveException getFailure();
-
-    /**
-     * Returns the artifact resolver for this module.
-     *
-     * @throws ModuleVersionResolveException If resolution was unsuccessful and artifacts are not available.
-     */
-    ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException;
-}
diff --git a/subprojects/core-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
deleted file mode 100644
index 5b5b951..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
-import org.gradle.internal.Factory;
-
-import java.io.File;
-
-public class ResolvedArtifactFactory {
-    private final CacheLockingManager lockingManager;
-
-    public ResolvedArtifactFactory(CacheLockingManager lockingManager) {
-        this.lockingManager = lockingManager;
-    }
-
-    public ResolvedArtifact create(ResolvedDependency owner, final Artifact artifact, final ArtifactResolver resolver) {
-        return new DefaultResolvedArtifact(owner, artifact, new Factory<File>() {
-            public File create() {
-                return lockingManager.useCache(String.format("download %s", artifact), new Factory<File>() {
-                    public File create() {
-                        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/ResolvedConfigurationBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedConfigurationBuilder.java
deleted file mode 100644
index 1ecee6c..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedConfigurationBuilder.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.api.artifacts.UnresolvedDependency;
-
-public interface ResolvedConfigurationBuilder {
-    void addArtifact(ResolvedArtifact artifact);
-
-    void addFirstLevelDependency(ModuleDependency moduleDependency, ResolvedDependency dependency);
-
-    void addUnresolvedDependency(UnresolvedDependency unresolvedDependency);
-
-    ResolvedDependency getRoot();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
index 3e14ed4..106ecf7 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
@@ -19,6 +19,7 @@ import org.gradle.api.GradleException;
 import org.gradle.api.artifacts.*;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
@@ -37,14 +38,17 @@ public class SelfResolvingDependencyResolver implements ArtifactDependencyResolv
         this.resolver = resolver;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
-        ResolverResults results = resolver.resolve(configuration, repositories);
+    public void resolve(ConfigurationInternal configuration,
+                        List<? extends ResolutionAwareRepository> repositories,
+                        ModuleMetadataProcessor metadataProcessor,
+                        ResolverResults results) throws ResolveException {
+        resolver.resolve(configuration, repositories, metadataProcessor, results);
         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));
+        results.withResolvedConfiguration(new FilesAggregatingResolvedConfiguration(resolvedConfiguration, provider));
     }
 
     protected static class SelfResolvingFilesProvider {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
deleted file mode 100644
index 877ffde..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public interface SettingsConverter {
-    String LOOPBACK_RESOLVER_NAME = "main";
-
-    IvySettings convertForPublish(List<DependencyResolver> publishResolvers);
-
-    IvySettings convertForResolve(DependencyResolver defaultResolver);
-
-    IvySettings getForResolve();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
index f7820c0..72c3427 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
@@ -16,13 +16,16 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ResolutionResult;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.ResolverResults;
+import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DefaultResolutionResultBuilder;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
-import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
 import org.gradle.api.specs.Spec;
 
 import java.io.File;
@@ -32,18 +35,25 @@ import java.util.Set;
 
 public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements ArtifactDependencyResolver {
     private final ArtifactDependencyResolver dependencyResolver;
+    private final ComponentIdentifierFactory componentIdentifierFactory;
 
-    public ShortcircuitEmptyConfigsArtifactDependencyResolver(ArtifactDependencyResolver dependencyResolver) {
+    public ShortcircuitEmptyConfigsArtifactDependencyResolver(ArtifactDependencyResolver dependencyResolver, ComponentIdentifierFactory componentIdentifierFactory) {
         this.dependencyResolver = dependencyResolver;
+        this.componentIdentifierFactory = componentIdentifierFactory;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
+    public void resolve(ConfigurationInternal configuration,
+                        List<? extends ResolutionAwareRepository> repositories,
+                        ModuleMetadataProcessor metadataProcessor,
+                        ResolverResults results) throws ResolveException {
         if (configuration.getAllDependencies().isEmpty()) {
             ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(configuration.getModule());
-            DefaultResolutionResult emptyResult = new ResolutionResultBuilder().start(id).getResult();
-            return new ResolverResults(new EmptyResolvedConfiguration(), emptyResult);
+            ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(configuration.getModule());
+            ResolutionResult emptyResult = new DefaultResolutionResultBuilder().start(id, componentIdentifier).complete();
+            results.resolved(new EmptyResolvedConfiguration(), emptyResult);
+        } else {
+            dependencyResolver.resolve(configuration, repositories, metadataProcessor, results);
         }
-        return dependencyResolver.resolve(configuration, repositories);
     }
 
     private static class EmptyResolvedConfiguration implements ResolvedConfiguration {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java
index 8971b89..1066dce 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java
@@ -17,17 +17,14 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
-/**
- * by Szczepan Faber, created at: 8/29/12
- */
 class SubstitutedModuleVersionIdResolveResult implements ModuleVersionIdResolveResult {
 
     final ModuleVersionIdResolveResult result;
-    private final ModuleVersionSelectionReason selectionReason;
+    private final ComponentSelectionReason selectionReason;
 
-    public SubstitutedModuleVersionIdResolveResult(ModuleVersionIdResolveResult result, ModuleVersionSelectionReason selectionReason) {
+    public SubstitutedModuleVersionIdResolveResult(ModuleVersionIdResolveResult result, ComponentSelectionReason selectionReason) {
         this.result = result;
         this.selectionReason = selectionReason;
     }
@@ -40,11 +37,11 @@ class SubstitutedModuleVersionIdResolveResult implements ModuleVersionIdResolveR
         return result.getId();
     }
 
-    public ModuleVersionResolveResult resolve() {
+    public ComponentResolveResult resolve() {
         return result.resolve();
     }
 
-    public ModuleVersionSelectionReason getSelectionReason() {
+    public ComponentSelectionReason getSelectionReason() {
         return selectionReason;
     }
 }
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 ade158c..cd90152 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
@@ -18,9 +18,9 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 
 public class VersionForcingDependencyToModuleResolver implements DependencyToModuleVersionIdResolver {
@@ -64,11 +64,11 @@ public class VersionForcingDependencyToModuleResolver implements DependencyToMod
             throw failure;
         }
 
-        public ModuleVersionResolveResult resolve() throws ModuleVersionResolveException {
+        public ComponentResolveResult resolve() throws ModuleVersionResolveException {
             throw failure;
         }
 
-        public ModuleVersionSelectionReason getSelectionReason() {
+        public ComponentSelectionReason getSelectionReason() {
             return VersionSelectionReasons.REQUESTED;
         }
     }
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 cbf202d..c00f110 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,23 +17,21 @@
 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.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult;
-import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableComponentResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
 
-/**
- * @author Hans Dockter
- */
-public class ClientModuleResolver implements DependencyToModuleResolver {
-    private final DependencyToModuleResolver resolver;
+public class ClientModuleResolver implements DependencyToModuleVersionResolver {
+    private final DependencyToModuleVersionResolver resolver;
 
-    public ClientModuleResolver(DependencyToModuleResolver resolver) {
+    public ClientModuleResolver(DependencyToModuleVersionResolver resolver) {
         this.resolver = resolver;
     }
 
-    public void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result) {
+    public void resolve(DependencyMetaData dependency, BuildableComponentResolveResult result) {
         resolver.resolve(dependency, result);
 
         DependencyDescriptor descriptor = dependency.getDescriptor();
@@ -42,8 +40,8 @@ public class ClientModuleResolver implements DependencyToModuleResolver {
         }
 
         ClientModuleDependencyDescriptor clientModuleDependencyDescriptor = (ClientModuleDependencyDescriptor) descriptor;
-        ModuleDescriptor moduleDescriptor = clientModuleDependencyDescriptor.getTargetModule();
-
-        result.setMetaData(moduleDescriptor);
+        ModuleVersionMetaData clientModuleMetaData = clientModuleDependencyDescriptor.getTargetModule();
+        ModuleSource moduleSource = result.getMetaData().getSource();
+        result.setMetaData(clientModuleMetaData.withSource(moduleSource));
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
deleted file mode 100644
index 2a3f3da..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.internal.TimeProvider;
-
-class DefaultCachedModuleResolution implements ModuleResolutionCache.CachedModuleResolution {
-    private final ModuleVersionIdentifier requestedVersion;
-    private final ModuleVersionIdentifier resolvedVersion;
-    private final long ageMillis;
-
-    public DefaultCachedModuleResolution(ModuleRevisionId requestedVersion, ModuleResolutionCacheEntry entry, TimeProvider timeProvider) {
-        this.requestedVersion = new DefaultModuleVersionIdentifier(requestedVersion.getOrganisation(), requestedVersion.getName(), requestedVersion.getRevision());
-        this.resolvedVersion = entry.moduleVersionIdentifier;
-        ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
-    }
-
-    public ModuleVersionIdentifier getResolvedVersion() {
-        return resolvedVersion;
-    }
-
-    public long getAgeMillis() {
-        return ageMillis;
-    }
-
-    public boolean isDynamicVersion() {
-        return !requestedVersion.equals(resolvedVersion);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleVersionList.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleVersionList.java
new file mode 100644
index 0000000..c353522
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleVersionList.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionListing;
+import org.gradle.util.BuildCommencedTimeProvider;
+
+class DefaultCachedModuleVersionList implements ModuleVersionsCache.CachedModuleVersionList {
+    private final ModuleVersionListing moduleVersions;
+    private final long ageMillis;
+
+    public DefaultCachedModuleVersionList(ModuleVersionsCacheEntry entry, BuildCommencedTimeProvider timeProvider) {
+        this.moduleVersions = entry.moduleVersionListing;
+        ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
+    }
+
+    public ModuleVersionListing getModuleVersions() {
+        return moduleVersions;
+    }
+
+    public long getAgeMillis() {
+        return ageMillis;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java
index 706f4a1..30068d4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java
@@ -15,19 +15,17 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 
 public class DefaultResolvedModuleVersion implements ResolvedModuleVersion {
-    private final ModuleRevisionId moduleRevisionId;
+    private final ModuleVersionIdentifier identifier;
 
-    public DefaultResolvedModuleVersion(ModuleRevisionId moduleRevisionId) {
-        this.moduleRevisionId = moduleRevisionId;
+    public DefaultResolvedModuleVersion(ModuleVersionIdentifier identifier) {
+        this.identifier = identifier;
     }
 
     public ModuleVersionIdentifier getId() {
-        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+        return identifier;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
deleted file mode 100644
index ed9717f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
-
-public interface ModuleResolutionCache {
-
-    void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion, ModuleVersionIdentifier moduleVersionIdentifier);
-
-    CachedModuleResolution getCachedModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion);
-
-    interface CachedModuleResolution {
-        ModuleVersionIdentifier getResolvedVersion();
-
-        boolean isDynamicVersion();
-
-        long getAgeMillis();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
deleted file mode 100644
index 3781c0a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.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.api.internal.artifacts.ivyservice.dynamicversions;
-
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-class ModuleResolutionCacheEntry {
-    public ModuleVersionIdentifier moduleVersionIdentifier;
-    public long createTimestamp;
-
-    ModuleResolutionCacheEntry(ModuleVersionIdentifier moduleVersionIdentifier, long createTimestamp) {
-        this.moduleVersionIdentifier = moduleVersionIdentifier;
-        this.createTimestamp = createTimestamp;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleVersionsCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleVersionsCache.java
new file mode 100644
index 0000000..6d0fdb6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleVersionsCache.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
+
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionListing;
+
+public interface ModuleVersionsCache {
+
+    void cacheModuleVersionList(ModuleVersionRepository repository, ModuleIdentifier moduleId, ModuleVersionListing listedVersions);
+
+    CachedModuleVersionList getCachedModuleResolution(ModuleVersionRepository repository, ModuleIdentifier moduleId);
+
+    interface CachedModuleVersionList {
+        ModuleVersionListing getModuleVersions();
+
+        long getAgeMillis();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleVersionsCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleVersionsCacheEntry.java
new file mode 100644
index 0000000..913f207
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleVersionsCacheEntry.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionListing;
+
+class ModuleVersionsCacheEntry {
+    public ModuleVersionListing moduleVersionListing;
+    public long createTimestamp;
+
+    ModuleVersionsCacheEntry(ModuleVersionListing moduleVersionListing, long createTimestamp) {
+        this.moduleVersionListing = moduleVersionListing;
+        this.createTimestamp = createTimestamp;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
deleted file mode 100644
index 5aa0bac..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
+++ /dev/null
@@ -1,139 +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.dynamicversions;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
-import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
-import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.internal.TimeProvider;
-import org.gradle.messaging.serialize.DataStreamBackedSerializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-
-public class SingleFileBackedModuleResolutionCache implements ModuleResolutionCache {
-    private static final Logger LOGGER = LoggerFactory.getLogger(SingleFileBackedModuleResolutionCache.class);
-
-    private final TimeProvider timeProvider;
-    private final ArtifactCacheMetaData cacheMetadata;
-    private final CacheLockingManager cacheLockingManager;
-    private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> cache;
-
-    public SingleFileBackedModuleResolutionCache(ArtifactCacheMetaData cacheMetadata, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
-        this.timeProvider = timeProvider;
-        this.cacheLockingManager = cacheLockingManager;
-        this.cacheMetadata = cacheMetadata;
-    }
-
-    private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> getCache() {
-        if (cache == null) {
-            cache = initCache();
-        }
-        return cache;
-    }
-
-    private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> initCache() {
-        File dynamicRevisionsFile = new File(cacheMetadata.getCacheDir(), "dynamic-revisions.bin");
-        return cacheLockingManager.createCache(dynamicRevisionsFile, new RevisionKeySerializer(), new ModuleResolutionCacheEntrySerializer());
-    }
-
-    public void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId requestedVersion, ModuleVersionIdentifier moduleVersionIdentifier) {
-        if (requestedVersion.equals(moduleVersionIdentifier)) {
-            return;
-        }
-
-        LOGGER.debug("Caching resolved revision in dynamic revision cache: Will use '{}' for '{}'", moduleVersionIdentifier, requestedVersion);
-        getCache().put(createKey(repository, requestedVersion), createEntry(moduleVersionIdentifier));
-    }
-
-    public CachedModuleResolution getCachedModuleResolution(ModuleVersionRepository repository, ModuleRevisionId moduleId) {
-        ModuleResolutionCacheEntry moduleResolutionCacheEntry = getCache().get(createKey(repository, moduleId));
-        if (moduleResolutionCacheEntry == null) {
-            return null;
-        }
-        return new DefaultCachedModuleResolution(moduleId, moduleResolutionCacheEntry, timeProvider);
-    }
-
-    private RevisionKey createKey(ModuleVersionRepository repository, ModuleRevisionId revisionId) {
-        return new RevisionKey(repository.getId(), revisionId.encodeToString());
-    }
-
-    private ModuleResolutionCacheEntry createEntry(ModuleVersionIdentifier moduleVersionIdentifier) {
-        return new ModuleResolutionCacheEntry(moduleVersionIdentifier, timeProvider.getCurrentTime());
-    }
-
-    private static class RevisionKey {
-        private final String repositoryId;
-        private final String revisionId;
-
-        private RevisionKey(String repositoryId, String revisionId) {
-            this.repositoryId = repositoryId;
-            this.revisionId = revisionId;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == null || !(o instanceof RevisionKey)) {
-                return false;
-            }
-            RevisionKey other = (RevisionKey) o;
-            return repositoryId.equals(other.repositoryId) && revisionId.equals(other.revisionId);
-        }
-
-        @Override
-        public int hashCode() {
-            return repositoryId.hashCode() ^ revisionId.hashCode();
-        }
-    }
-
-    private static class RevisionKeySerializer extends DataStreamBackedSerializer<RevisionKey> {
-        @Override
-        public void write(DataOutput dataOutput, RevisionKey value) throws IOException {
-            dataOutput.writeUTF(value.repositoryId);
-            dataOutput.writeUTF(value.revisionId);
-        }
-
-        @Override
-        public RevisionKey read(DataInput dataInput) throws IOException {
-            String resolverId = dataInput.readUTF();
-            String revisionId = dataInput.readUTF();
-            return new RevisionKey(resolverId, revisionId);
-        }
-    }
-
-    private static class ModuleResolutionCacheEntrySerializer extends DataStreamBackedSerializer<ModuleResolutionCacheEntry> {
-        private final ModuleVersionIdentifierSerializer identifierSerializer = new ModuleVersionIdentifierSerializer();
-
-        @Override
-        public void write(DataOutput dataOutput, ModuleResolutionCacheEntry value) throws IOException {
-            identifierSerializer.write(dataOutput, value.moduleVersionIdentifier);
-            dataOutput.writeLong(value.createTimestamp);
-        }
-
-        @Override
-        public ModuleResolutionCacheEntry read(DataInput dataInput) throws IOException {
-            ModuleVersionIdentifier identifier = identifierSerializer.read(dataInput);
-            long createTimestamp = dataInput.readLong();
-            return new ModuleResolutionCacheEntry(identifier, createTimestamp);
-        }
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleVersionsCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleVersionsCache.java
new file mode 100644
index 0000000..bcb9628
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleVersionsCache.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
+
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultModuleVersionListing;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionListing;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.Versioned;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+import org.gradle.util.BuildCommencedTimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+public class SingleFileBackedModuleVersionsCache implements ModuleVersionsCache {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SingleFileBackedModuleVersionsCache.class);
+
+    private final BuildCommencedTimeProvider timeProvider;
+    private final CacheLockingManager cacheLockingManager;
+    private PersistentIndexedCache<ModuleKey, ModuleVersionsCacheEntry> cache;
+
+    public SingleFileBackedModuleVersionsCache(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        this.timeProvider = timeProvider;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    private PersistentIndexedCache<ModuleKey, ModuleVersionsCacheEntry> getCache() {
+        if (cache == null) {
+            cache = initCache();
+        }
+        return cache;
+    }
+
+    private PersistentIndexedCache<ModuleKey, ModuleVersionsCacheEntry> initCache() {
+        return cacheLockingManager.createCache("module-versions", new ModuleKeySerializer(), new ModuleVersionsCacheEntrySerializer());
+    }
+
+    public void cacheModuleVersionList(ModuleVersionRepository repository, ModuleIdentifier moduleId, ModuleVersionListing listedVersions) {
+        LOGGER.debug("Caching version list in module versions cache: Using '{}' for '{}'", listedVersions, moduleId);
+        getCache().put(createKey(repository, moduleId), createEntry(listedVersions));
+    }
+
+    public CachedModuleVersionList getCachedModuleResolution(ModuleVersionRepository repository, ModuleIdentifier moduleId) {
+        ModuleVersionsCacheEntry moduleVersionsCacheEntry = getCache().get(createKey(repository, moduleId));
+        if (moduleVersionsCacheEntry == null) {
+            return null;
+        }
+        return new DefaultCachedModuleVersionList(moduleVersionsCacheEntry, timeProvider);
+    }
+
+    private ModuleKey createKey(ModuleVersionRepository repository, ModuleIdentifier moduleId) {
+        return new ModuleKey(repository.getId(), moduleId);
+    }
+
+    private ModuleVersionsCacheEntry createEntry(ModuleVersionListing listedVersions) {
+        return new ModuleVersionsCacheEntry(listedVersions, timeProvider.getCurrentTime());
+    }
+
+    private static class ModuleKey {
+        private final String repositoryId;
+        private final ModuleIdentifier moduleId;
+
+        private ModuleKey(String repositoryId, ModuleIdentifier moduleId) {
+            this.repositoryId = repositoryId;
+            this.moduleId = moduleId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || !(o instanceof ModuleKey)) {
+                return false;
+            }
+            ModuleKey other = (ModuleKey) o;
+            return repositoryId.equals(other.repositoryId) && moduleId.equals(other.moduleId);
+        }
+
+        @Override
+        public int hashCode() {
+            return repositoryId.hashCode() ^ moduleId.hashCode();
+        }
+    }
+
+    private static class ModuleKeySerializer implements Serializer<ModuleKey> {
+        public void write(Encoder encoder, ModuleKey value) throws Exception {
+            encoder.writeString(value.repositoryId);
+            encoder.writeString(value.moduleId.getGroup());
+            encoder.writeString(value.moduleId.getName());
+        }
+
+        public ModuleKey read(Decoder decoder) throws Exception {
+            String resolverId = decoder.readString();
+            String group = decoder.readString();
+            String module = decoder.readString();
+            return new ModuleKey(resolverId, new DefaultModuleIdentifier(group, module));
+        }
+    }
+
+    private static class ModuleVersionsCacheEntrySerializer implements Serializer<ModuleVersionsCacheEntry> {
+
+        public void write(Encoder encoder, ModuleVersionsCacheEntry value) throws Exception {
+            Set<Versioned> versions = value.moduleVersionListing.getVersions();
+            encoder.writeInt(versions.size());
+            for (Versioned version : versions) {
+                encoder.writeString(version.getVersion());
+            }
+            encoder.writeLong(value.createTimestamp);
+        }
+
+        public ModuleVersionsCacheEntry read(Decoder decoder) throws Exception {
+            int size = decoder.readInt();
+            DefaultModuleVersionListing versions = new DefaultModuleVersionListing();
+            for (int i = 0; i < size; i++) {
+                versions.add(decoder.readString());
+            }
+            long createTimestamp = decoder.readLong();
+            return new ModuleVersionsCacheEntry(versions, createTimestamp);
+        }
+    }
+
+}
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
deleted file mode 100644
index 9280823..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
+++ /dev/null
@@ -1,55 +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.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
-
-public abstract class AbstractDependencyResolverAdapter implements IvyAwareModuleVersionRepository {
-    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 void setSettings(IvySettings settings) {
-        settings.addResolver(resolver);
-    }
-
-    public boolean isDynamicResolveMode() {
-        return false;
-    }
-
-    public boolean isLocal() {
-        return resolver.getRepositoryCacheManager() instanceof LocalFileRepositoryCacheManager;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java
index 902334b..291878d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java
@@ -15,18 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactIdentifier;
 
 public class ArtifactNotFoundException extends ArtifactResolveException {
-    public ArtifactNotFoundException(Artifact artifact) {
-        super(format(artifact));
-    }
-
-    private static String format(Artifact artifact) {
-        StringBuilder builder = new StringBuilder();
-        builder.append("Artifact '");
-        formatTo(artifact, builder);
-        builder.append("' not found.");
-        return builder.toString();
+    public ArtifactNotFoundException(ComponentArtifactIdentifier artifact) {
+        super(String.format("Artifact '%s' not found.", artifact.getDisplayName()));
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java
index 778e9a7..336b602 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java
@@ -15,10 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactIdentifier;
+import org.gradle.internal.exceptions.Contextual;
 import org.gradle.util.GUtil;
 
 @Contextual
@@ -27,18 +27,26 @@ public class ArtifactResolveException extends GradleException {
         super(message);
     }
 
-    public ArtifactResolveException(Artifact artifact, Throwable cause) {
+    public ArtifactResolveException(ComponentIdentifier component, Throwable cause) {
+        super(format(component, ""), cause);
+    }
+
+    public ArtifactResolveException(ComponentIdentifier component, String message) {
+        super(format(component, message));
+    }
+
+    public ArtifactResolveException(ComponentArtifactIdentifier artifact, Throwable cause) {
         super(format(artifact, ""), cause);
     }
 
-    public ArtifactResolveException(Artifact artifact, String message) {
+    public ArtifactResolveException(ComponentArtifactIdentifier artifact, String message) {
         super(format(artifact, message));
     }
 
-    private static String format(Artifact artifact, String message) {
+    private static String format(ComponentArtifactIdentifier artifact, String message) {
         StringBuilder builder = new StringBuilder();
         builder.append("Could not download artifact '");
-        formatTo(artifact, builder);
+        builder.append(artifact.getDisplayName());
         builder.append("'");
         if (GUtil.isTrue(message)) {
             builder.append(": ");
@@ -47,15 +55,15 @@ public class ArtifactResolveException extends GradleException {
         return builder.toString();
     }
 
-    protected static void formatTo(Artifact artifact, StringBuilder builder) {
-        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
-        builder.append(moduleRevisionId.getOrganisation())
-                .append(":").append(moduleRevisionId.getName())
-                .append(":").append(moduleRevisionId.getRevision());
-        String classifier = artifact.getExtraAttribute("classifier");
-        if (GUtil.isTrue(classifier)) {
-            builder.append(":").append(classifier);
+    private static String format(ComponentIdentifier component, String message) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Could not determine artifacts for component '");
+        builder.append(component.getDisplayName());
+        builder.append("'");
+        if (GUtil.isTrue(message)) {
+            builder.append(": ");
+            builder.append(message);
         }
-        builder.append("@").append(artifact.getExt());
+        return builder.toString();
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaData.java
deleted file mode 100644
index f731d7d..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaData.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.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.Nullable;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-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.
- *
- * TODO - detach this from ModuleVersionMetaData, so that it has-a instead of is-a.
- */
-public interface BuildableModuleVersionMetaData extends ModuleVersionMetaData {
-    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, ModuleSource moduleSource);
-
-    /**
-     * Marks the module version as resolved, with the given meta-data.
-     */
-    void resolved(ModuleVersionIdentifier id, ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource);
-
-    /**
-     * 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();
-
-    /**
-     * The repository-specific source for this module version.
-     */
-    public ModuleSource getModuleSource();
-
-    void setModuleSource(ModuleSource moduleSource);
-
-    /**
-     * Replaces the dependencies of this module version.
-     */
-    void setDependencies(Iterable<? extends DependencyMetaData> dependencies);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaDataResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaDataResolveResult.java
new file mode 100644
index 0000000..ba9f138
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaDataResolveResult.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.ivyresolve;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+/**
+ * The result of attempting to resolve a dependency descriptor to the meta-data for a module version.
+ */
+public interface BuildableModuleVersionMetaDataResolveResult {
+    static enum State {
+        Resolved, Missing, Failed, ProbablyMissing, Unknown
+    }
+
+    /**
+     * Returns the current state of this descriptor.
+     */
+    State getState();
+
+    /**
+     * Returns the meta-data.
+     *
+     * @throws ModuleVersionResolveException If the resolution was not successful.
+     */
+    MutableModuleVersionMetaData getMetaData() throws ModuleVersionResolveException;
+
+    @Nullable
+    ModuleVersionResolveException getFailure();
+
+    /**
+     * Marks the module version as resolved, with the given meta-data and source.
+     */
+    void resolved(MutableModuleVersionMetaData metaData, ModuleSource moduleSource);
+
+    /**
+     * 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();
+
+    /**
+     * The repository-specific source for this module version.
+     */
+    public ModuleSource getModuleSource();
+
+    void setModuleSource(ModuleSource moduleSource);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionSelectionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionSelectionResolveResult.java
new file mode 100644
index 0000000..a9f8b56
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionSelectionResolveResult.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.artifacts.ivyservice.ivyresolve;
+
+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 BuildableModuleVersionSelectionResolveResult {
+
+    static enum State {
+        Listed, ProbablyListed, Failed, Unknown
+    }
+
+    /**
+     * Returns the current state of this descriptor.
+     */
+    State getState();
+
+    /**
+     * Returns the versions that match the selector.
+     *
+     * @throws org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException If the resolution was not successful.
+     */
+    ModuleVersionListing getVersions() throws ModuleVersionResolveException;
+
+    @Nullable
+    ModuleVersionResolveException getFailure();
+
+    /**
+     * Marks the module as having been listed to have the specified versions available.
+     */
+    void listed(ModuleVersionListing versions);
+
+    /**
+     * Marks the module as probably having no versions available.
+     */
+    void probablyListed(ModuleVersionListing versions);
+
+    /**
+     * Marks the list as failed with the given exception.
+     */
+    void failed(ModuleVersionResolveException failure);
+}
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 514cced..8d33912 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
@@ -15,18 +15,19 @@
  */
 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.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 
 /**
  * A wrapper around a {@link ModuleVersionRepository} that handles locking/unlocking the cache.
  */
-public class CacheLockingModuleVersionRepository implements ModuleVersionRepository {
-    private final ModuleVersionRepository repository;
+public class CacheLockingModuleVersionRepository implements LocalArtifactsModuleVersionRepository {
+    private final LocalArtifactsModuleVersionRepository repository;
     private final CacheLockingManager cacheLockingManager;
 
-    public CacheLockingModuleVersionRepository(ModuleVersionRepository repository, CacheLockingManager cacheLockingManager) {
+    public CacheLockingModuleVersionRepository(LocalArtifactsModuleVersionRepository repository, CacheLockingManager cacheLockingManager) {
         this.repository = repository;
         this.cacheLockingManager = cacheLockingManager;
     }
@@ -39,7 +40,15 @@ public class CacheLockingModuleVersionRepository implements ModuleVersionReposit
         return repository.getName();
     }
 
-    public void getDependency(final DependencyMetaData dependency, final BuildableModuleVersionMetaData result) {
+    public void listModuleVersions(final DependencyMetaData dependency, final BuildableModuleVersionSelectionResolveResult result) {
+        cacheLockingManager.longRunningOperation(String.format("List %s using repository %s", dependency, getId()), new Runnable() {
+            public void run() {
+                repository.listModuleVersions(dependency, result);
+            }
+        });
+    }
+
+    public void getDependency(final DependencyMetaData dependency, final BuildableModuleVersionMetaDataResolveResult result) {
         cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", dependency, getId()), new Runnable() {
             public void run() {
                 repository.getDependency(dependency, result);
@@ -47,10 +56,22 @@ public class CacheLockingModuleVersionRepository implements ModuleVersionReposit
         });
     }
 
-    public void resolve(final Artifact artifact, final BuildableArtifactResolveResult result, final ModuleSource moduleSource) {
+    public void localResolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        repository.localResolveModuleArtifacts(component, context, result);
+    }
+
+    public void resolveModuleArtifacts(final ComponentMetaData component, final ArtifactResolveContext context, final BuildableArtifactSetResolveResult result) {
+        cacheLockingManager.longRunningOperation(String.format("Resolve %s for %s using repository %s", context, component, getId()), new Runnable() {
+            public void run() {
+                repository.resolveModuleArtifacts(component, context, result);
+            }
+        });
+    }
+
+    public void resolveArtifact(final ComponentArtifactMetaData artifact, final ModuleSource moduleSource, final BuildableArtifactResolveResult result) {
         cacheLockingManager.longRunningOperation(String.format("Download %s using repository %s", artifact, getId()), new Runnable() {
             public void run() {
-                repository.resolve(artifact, result, moduleSource);
+                repository.resolveArtifact(artifact, moduleSource, 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 8b82cb5..b7023b9 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
@@ -15,49 +15,64 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleIdentifier;
 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.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext;
 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 org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleVersionsCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleArtifactsCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetaDataCache;
+import org.gradle.api.internal.artifacts.metadata.*;
 import org.gradle.api.internal.externalresource.cached.CachedArtifact;
 import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
 import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
-import org.gradle.internal.TimeProvider;
+import org.gradle.util.BuildCommencedTimeProvider;
+import org.gradle.util.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.math.BigInteger;
+import java.util.Set;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId;
 
 public class CachingModuleVersionRepository implements LocalAwareModuleVersionRepository {
     private static final Logger LOGGER = LoggerFactory.getLogger(CachingModuleVersionRepository.class);
 
-    private final ModuleResolutionCache moduleResolutionCache;
-    private final ModuleDescriptorCache moduleDescriptorCache;
+    private final ModuleVersionsCache moduleVersionsCache;
+    private final ModuleMetaDataCache moduleMetaDataCache;
+    private final ModuleArtifactsCache moduleArtifactsCache;
     private final CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex;
 
     private final CachePolicy cachePolicy;
 
-    private final ModuleVersionRepository delegate;
-    private final TimeProvider timeProvider;
+    private final LocalArtifactsModuleVersionRepository delegate;
+    private final BuildCommencedTimeProvider timeProvider;
+    private final ModuleMetadataProcessor metadataProcessor;
+    private final Transformer<ModuleIdentifier, ModuleVersionSelector> moduleExtractor;
 
-    public CachingModuleVersionRepository(ModuleVersionRepository delegate, ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
-                                          CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
-                                          CachePolicy cachePolicy, TimeProvider timeProvider) {
+    public CachingModuleVersionRepository(LocalArtifactsModuleVersionRepository delegate, ModuleVersionsCache moduleVersionsCache, ModuleMetaDataCache moduleMetaDataCache,
+                                          ModuleArtifactsCache moduleArtifactsCache, CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
+                                          CachePolicy cachePolicy, BuildCommencedTimeProvider timeProvider,
+                                          ModuleMetadataProcessor metadataProcessor, Transformer<ModuleIdentifier, ModuleVersionSelector> moduleExtractor) {
         this.delegate = delegate;
-        this.moduleDescriptorCache = moduleDescriptorCache;
-        this.moduleResolutionCache = moduleResolutionCache;
+        this.moduleMetaDataCache = moduleMetaDataCache;
+        this.moduleVersionsCache = moduleVersionsCache;
+        this.moduleArtifactsCache = moduleArtifactsCache;
         this.artifactAtRepositoryCachedResolutionIndex = artifactAtRepositoryCachedResolutionIndex;
         this.timeProvider = timeProvider;
         this.cachePolicy = cachePolicy;
+        this.metadataProcessor = metadataProcessor;
+        this.moduleExtractor = moduleExtractor;
     }
 
     public String getId() {
@@ -73,25 +88,65 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         return "Caching " + delegate.toString();
     }
 
-    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
-        DependencyMetaData resolvedDependency = maybeUseCachedDynamicVersion(delegate, dependency);
-        lookupModuleInCache(delegate, resolvedDependency, result);
+    public void localListModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        ModuleVersionSelector requested = dependency.getRequested();
+        final ModuleIdentifier moduleId = moduleExtractor.transform(requested);
+        ModuleVersionsCache.CachedModuleVersionList cachedModuleVersionList = moduleVersionsCache.getCachedModuleResolution(delegate, moduleId);
+        if (cachedModuleVersionList != null) {
+            ModuleVersionListing versionList = cachedModuleVersionList.getModuleVersions();
+            Set<ModuleVersionIdentifier> versions = CollectionUtils.collect(versionList.getVersions(), new Transformer<ModuleVersionIdentifier, Versioned>() {
+                public ModuleVersionIdentifier transform(Versioned original) {
+                    return new DefaultModuleVersionIdentifier(moduleId, original.getVersion());
+                }
+            });
+            if (cachePolicy.mustRefreshVersionList(moduleId, versions, cachedModuleVersionList.getAgeMillis())) {
+                LOGGER.debug("Version listing in dynamic revision cache is expired: will perform fresh resolve of '{}' in '{}'", requested, delegate.getName());
+            } else {
+                if (cachedModuleVersionList.getAgeMillis() == 0) {
+                    // Verified since the start of this build, assume still missing
+                    result.listed(versionList);
+                } else {
+                    // Was missing last time we checked
+                    result.probablyListed(versionList);
+                }
+            }
+        }
     }
 
-    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+    public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        delegate.listModuleVersions(dependency, result);
+        switch (result.getState()) {
+            case Listed:
+                ModuleIdentifier moduleId = moduleExtractor.transform(dependency.getRequested());
+                ModuleVersionListing versionList = result.getVersions();
+                moduleVersionsCache.cacheModuleVersionList(delegate, moduleId, versionList);
+                break;
+            case Failed:
+                break;
+            default:
+                throw new IllegalStateException("Unexpected state on listModuleVersions: " + result.getState());
+        }
+    }
+
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+        lookupModuleInCache(delegate, dependency, result);
+    }
+
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
         DependencyMetaData forced = dependency.withChanging();
         delegate.getDependency(forced, result);
         switch (result.getState()) {
             case Missing:
-                final ModuleRevisionId dependencyRevisionId = dependency.getDescriptor().getDependencyRevisionId();
-                final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
-                moduleDescriptorCache.cacheModuleDescriptor(delegate, moduleVersionIdentifier, null, null, dependency.isChanging());
+                ModuleRevisionId dependencyRevisionId = dependency.getDescriptor().getDependencyRevisionId();
+                ModuleVersionIdentifier moduleVersionIdentifier = DefaultModuleVersionIdentifier.newId(dependencyRevisionId);
+                moduleMetaDataCache.cacheMissing(delegate, moduleVersionIdentifier);
                 break;
             case Resolved:
-                moduleResolutionCache.cacheModuleResolution(delegate, dependency.getDescriptor().getDependencyRevisionId(), result.getId());
-                final ModuleSource moduleSource = result.getModuleSource();
-                final ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.cacheModuleDescriptor(delegate, result.getId(), result.getDescriptor(), moduleSource, isChangingDependency(dependency, result));
-                result.setModuleSource(new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), moduleSource));
+                MutableModuleVersionMetaData metaData = result.getMetaData();
+                ModuleSource moduleSource = result.getModuleSource();
+                ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.cacheMetaData(delegate, metaData, moduleSource);
+                metadataProcessor.process(metaData);
+                result.setModuleSource(new CachingModuleSource(cachedMetaData.getDescriptorHash(), dependency.isChanging() || metaData.isChanging(), moduleSource));
                 break;
             case Failed:
                 break;
@@ -100,37 +155,20 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         }
     }
 
-    private DependencyMetaData maybeUseCachedDynamicVersion(ModuleVersionRepository repository, DependencyMetaData original) {
-        ModuleRevisionId originalId = original.getDescriptor().getDependencyRevisionId();
-        ModuleResolutionCache.CachedModuleResolution cachedModuleResolution = moduleResolutionCache.getCachedModuleResolution(repository, originalId);
-        if (cachedModuleResolution != null && cachedModuleResolution.isDynamicVersion()) {
-            ModuleVersionSelector selector = createModuleVersionSelector(originalId);
-            ModuleVersionIdentifier resolvedVersion = cachedModuleResolution.getResolvedVersion();
-            if (cachePolicy.mustRefreshDynamicVersion(selector, resolvedVersion, cachedModuleResolution.getAgeMillis())) {
-                LOGGER.debug("Resolved revision in dynamic revision cache is expired: will perform fresh resolve of '{}' in '{}'", selector, repository.getName());
-                return original;
-            } else {
-                LOGGER.debug("Found resolved revision in dynamic revision cache of '{}': Using '{}' for '{}'", repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId);
-                return original.withRequestedVersion(DefaultModuleVersionSelector.newSelector(resolvedVersion.getGroup(), resolvedVersion.getName(), resolvedVersion.getVersion()));
-            }
-        }
-        return original;
-    }
-
-    public void lookupModuleInCache(ModuleVersionRepository repository, DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+    private void lookupModuleInCache(ModuleVersionRepository repository, DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
         ModuleRevisionId resolvedModuleVersionId = dependency.getDescriptor().getDependencyRevisionId();
-        ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(resolvedModuleVersionId);
-        ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.getCachedModuleDescriptor(repository, moduleVersionIdentifier);
-        if (cachedModuleDescriptor == null) {
+        ModuleVersionIdentifier moduleVersionIdentifier = newId(resolvedModuleVersionId);
+        ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.getCachedModuleDescriptor(repository, moduleVersionIdentifier);
+        if (cachedMetaData == null) {
             return;
         }
-        if (cachedModuleDescriptor.isMissing()) {
-            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, null, resolvedModuleVersionId, cachedModuleDescriptor.getAgeMillis())) {
+        if (cachedMetaData.isMissing()) {
+            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, null, resolvedModuleVersionId, cachedMetaData.getAgeMillis())) {
                 LOGGER.debug("Cached meta-data for missing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
                 return;
             }
             LOGGER.debug("Detected non-existence of module '{}' in resolver cache '{}'", resolvedModuleVersionId, repository.getName());
-            if (cachedModuleDescriptor.getAgeMillis() == 0) {
+            if (cachedMetaData.getAgeMillis() == 0) {
                 // Verified since the start of this build, assume still missing
                 result.missing();
             } else {
@@ -139,79 +177,99 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
             }
             return;
         }
-        if (cachedModuleDescriptor.isChangingModule() || dependency.isChanging()) {
-            if (cachePolicy.mustRefreshChangingModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), cachedModuleDescriptor.getAgeMillis())) {
+        MutableModuleVersionMetaData metaData = cachedMetaData.getMetaData();
+        metadataProcessor.process(metaData);
+        if (dependency.isChanging() || metaData.isChanging()) {
+            if (cachePolicy.mustRefreshChangingModule(moduleVersionIdentifier, cachedMetaData.getModuleVersion(), cachedMetaData.getAgeMillis())) {
                 LOGGER.debug("Cached meta-data for changing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
                 return;
             }
             LOGGER.debug("Found cached version of changing module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
         } else {
-            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), null, cachedModuleDescriptor.getAgeMillis())) {
+            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, cachedMetaData.getModuleVersion(), null, cachedMetaData.getAgeMillis())) {
                 LOGGER.debug("Cached meta-data for module must be refreshed: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
                 return;
             }
         }
 
         LOGGER.debug("Using cached module metadata for module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
-        result.resolved(moduleVersionIdentifier, cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule(), new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), cachedModuleDescriptor.getModuleSource()));
+        result.resolved(metaData, new CachingModuleSource(cachedMetaData.getDescriptorHash(), metaData.isChanging(), cachedMetaData.getModuleSource()));
     }
 
-    private boolean isChangingDependency(DependencyMetaData dependency, ModuleVersionMetaData downloadedModule) {
-        if (dependency.isChanging()) {
-            return true;
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        final CachingModuleSource cachedModuleSource = (CachingModuleSource) component.getSource();
+
+        // First try to determine the artifacts locally (e.g using the metadata): don't use the cache in this case
+        delegate.localResolveModuleArtifacts(component.withSource(cachedModuleSource.getDelegate()), context, result);
+        if (result.hasResult()) {
+            return;
         }
-        return downloadedModule.isChanging();
+
+        resolveAndCacheModuleArtifacts(component, context, result);
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-        ArtifactAtRepositoryKey resolutionCacheIndexKey = new ArtifactAtRepositoryKey(delegate, artifact.getId());
+    private void resolveAndCacheModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        final CachingModuleSource cachedModuleSource = (CachingModuleSource) component.getSource();
+        ModuleArtifactsCache.CachedArtifacts cachedModuleArtifacts = moduleArtifactsCache.getCachedArtifacts(delegate, component.getId(), context.getId());
+        BigInteger moduleDescriptorHash = cachedModuleSource.getDescriptorHash();
+
+        if (cachedModuleArtifacts != null) {
+            if (!cachePolicy.mustRefreshModuleArtifacts(component.getId(), null, cachedModuleArtifacts.getAgeMillis(),
+                    cachedModuleSource.isChangingModule(), moduleDescriptorHash.equals(cachedModuleArtifacts.getDescriptorHash()))) {
+                Set<ModuleVersionArtifactMetaData> artifactMetaDataSet = CollectionUtils.collect(cachedModuleArtifacts.getArtifacts(), new ArtifactIdToMetaData());
+                result.resolved(artifactMetaDataSet);
+                return;
+            }
+
+            LOGGER.debug("Artifact listing has expired: will perform fresh resolve of '{}' for '{}' in '{}'", context.getDescription(), component.getId(), delegate.getName());
+        }
+
+        delegate.resolveModuleArtifacts(component.withSource(cachedModuleSource.getDelegate()), context, result);
+
+        if (result.getFailure() == null) {
+            Set<ModuleVersionArtifactIdentifier> artifactIdentifierSet = CollectionUtils.collect(result.getArtifacts(), new ArtifactMetaDataToId());
+            moduleArtifactsCache.cacheArtifacts(delegate, component.getId(), context.getId(), moduleDescriptorHash, artifactIdentifierSet);
+        }
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        // TODO:ADAM - Don't assume this
+        ModuleVersionArtifactMetaData moduleVersionArtifactMetaData = (ModuleVersionArtifactMetaData) artifact;
+        ArtifactAtRepositoryKey resolutionCacheIndexKey = new ArtifactAtRepositoryKey(delegate.getId(), moduleVersionArtifactMetaData.getId());
         // Look in the cache for this resolver
         CachedArtifact cached = artifactAtRepositoryCachedResolutionIndex.lookup(resolutionCacheIndexKey);
         final CachingModuleSource cachedModuleSource = (CachingModuleSource) moduleSource;
         final BigInteger descriptorHash = cachedModuleSource.getDescriptorHash();
         if (cached != null) {
-            ArtifactIdentifier artifactIdentifier = createArtifactIdentifier(artifact);
             long age = timeProvider.getCurrentTime() - cached.getCachedAt();
             final boolean isChangingModule = cachedModuleSource.isChangingModule();
+            ArtifactIdentifier artifactIdentifier = moduleVersionArtifactMetaData.toArtifactIdentifier();
             if (cached.isMissing()) {
                 if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, null, age, isChangingModule, descriptorHash.equals(cached.getDescriptorHash()))) {
-                    LOGGER.debug("Detected non-existence of artifact '{}' in resolver cache", artifact.getId());
-                    result.notFound(artifact);
+                    LOGGER.debug("Detected non-existence of artifact '{}' in resolver cache", artifact);
+                    result.notFound(artifact.getId());
                     return;
                 }
             } else {
                 File cachedArtifactFile = cached.getCachedFile();
                 if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, cachedArtifactFile, age, isChangingModule, descriptorHash.equals(cached.getDescriptorHash()))) {
-                    LOGGER.debug("Found artifact '{}' in resolver cache: {}", artifact.getId(), cachedArtifactFile);
+                    LOGGER.debug("Found artifact '{}' in resolver cache: {}", artifact, cachedArtifactFile);
                     result.resolved(cachedArtifactFile);
                     return;
                 }
             }
         }
 
-        delegate.resolve(artifact, result, cachedModuleSource.getDelegate());
-        LOGGER.debug("Downloaded artifact '{}' from resolver: {}", artifact.getId(), delegate);
+        delegate.resolveArtifact(artifact, cachedModuleSource.getDelegate(), result);
+        LOGGER.debug("Downloaded artifact '{}' from resolver: {}", artifact, delegate.getName());
 
-        if (result.getFailure() instanceof ArtifactNotFoundException) {
-            artifactAtRepositoryCachedResolutionIndex.storeMissing(resolutionCacheIndexKey, descriptorHash);
-        } else {
+        if (result.getFailure() == null) {
             artifactAtRepositoryCachedResolutionIndex.store(resolutionCacheIndexKey, result.getFile(), descriptorHash);
+        } else if (result.getFailure() instanceof ArtifactNotFoundException) {
+            artifactAtRepositoryCachedResolutionIndex.storeMissing(resolutionCacheIndexKey, descriptorHash);
         }
     }
 
-    private ModuleVersionSelector createModuleVersionSelector(ModuleRevisionId moduleRevisionId) {
-        return new DefaultModuleVersionSelector(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
-    }
-
-    private ModuleVersionIdentifier createModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
-        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
-    }
-
-    private ArtifactIdentifier createArtifactIdentifier(Artifact artifact) {
-        ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(artifact.getModuleRevisionId());
-        return new DefaultArtifactIdentifier(moduleVersionIdentifier, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttribute("classifier"));
-    }
-
     static class CachingModuleSource implements ModuleSource {
         private final BigInteger descriptorHash;
         private final boolean changingModule;
@@ -235,4 +293,16 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
             return delegate;
         }
     }
+
+    static class ArtifactIdToMetaData implements Transformer<ModuleVersionArtifactMetaData, ModuleVersionArtifactIdentifier> {
+        public ModuleVersionArtifactMetaData transform(ModuleVersionArtifactIdentifier original) {
+            return new DefaultModuleVersionArtifactMetaData(original);
+        }
+    }
+
+    static class ArtifactMetaDataToId implements Transformer<ModuleVersionArtifactIdentifier, ComponentArtifactMetaData> {
+        public ModuleVersionArtifactIdentifier transform(ComponentArtifactMetaData original) {
+            return ((ModuleVersionArtifactMetaData)original).getId();
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
index 5beef9e..aa7b592 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
@@ -21,9 +21,7 @@ import org.apache.ivy.plugins.matcher.NoMatcher;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.apache.ivy.plugins.resolver.AbstractResolver;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.GradleException;
-
-import java.lang.reflect.Method;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 
 public class ChangingModuleDetector {
     private final DependencyResolver resolver;
@@ -42,8 +40,8 @@ public class ChangingModuleDetector {
         }
 
         AbstractResolver abstractResolver = (AbstractResolver) resolver;
-        String changingMatcherName = readAbstractResolverProperty(resolver, "getChangingMatcherName");
-        String changingPattern = readAbstractResolverProperty(resolver, "getChangingPattern");
+        String changingMatcherName = JavaReflectionUtil.method(AbstractResolver.class, String.class, "getChangingMatcherName").invoke(abstractResolver);
+        String changingPattern = JavaReflectionUtil.method(AbstractResolver.class, String.class, "getChangingPattern").invoke(abstractResolver);
         if (changingMatcherName == null || changingPattern == null) {
             return NoMatcher.INSTANCE;
         }
@@ -54,14 +52,4 @@ public class ChangingModuleDetector {
         }
         return matcher.getMatcher(changingPattern);
     }
-
-    private String readAbstractResolverProperty(DependencyResolver resolver, String getter) {
-        try {
-            Method method = AbstractResolver.class.getDeclaredMethod(getter);
-            method.setAccessible(true);
-            return (String) method.invoke(resolver);
-        } catch (Exception e) {
-            throw new GradleException("Could not get cache options from AbstractResolver", e);
-        }
-    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ConfiguredModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ConfiguredModuleVersionRepository.java
new file mode 100644
index 0000000..76383be
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ConfiguredModuleVersionRepository.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+public interface ConfiguredModuleVersionRepository extends LocalArtifactsModuleVersionRepository {
+    boolean isDynamicResolveMode();
+
+    boolean isLocal();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaData.java
deleted file mode 100644
index ec67ca1..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaData.java
+++ /dev/null
@@ -1,137 +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.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
-import org.gradle.util.CollectionUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultBuildableModuleVersionMetaData implements BuildableModuleVersionMetaData {
-    private ModuleDescriptor moduleDescriptor;
-    private boolean changing;
-    private State state = State.Unknown;
-    private ModuleSource moduleSource;
-    private List<DependencyMetaData> dependencies;
-    private ModuleVersionResolveException failure;
-    private ModuleVersionIdentifier moduleVersionIdentifier;
-
-    public void reset(State state) {
-        this.state = state;
-        moduleDescriptor = null;
-        changing = false;
-        failure = null;
-    }
-
-    public void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource) {
-        ModuleRevisionId moduleRevisionId = descriptor.getModuleRevisionId();
-        DefaultModuleVersionIdentifier id = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
-        resolved(id, descriptor, changing, moduleSource);
-    }
-
-    public void resolved(ModuleVersionIdentifier id, ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource) {
-        reset(State.Resolved);
-        this.moduleVersionIdentifier = id;
-        this.moduleDescriptor = descriptor;
-        this.changing = changing;
-        this.moduleSource = moduleSource;
-    }
-
-    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 ModuleVersionIdentifier getId() {
-        assertResolved();
-        return moduleVersionIdentifier;
-    }
-
-    public ModuleDescriptor getDescriptor() {
-        assertResolved();
-        return moduleDescriptor;
-    }
-
-    public List<DependencyMetaData> getDependencies() {
-        assertResolved();
-        if (dependencies == null) {
-            dependencies = new ArrayList<DependencyMetaData>();
-            for (final DependencyDescriptor dependencyDescriptor : moduleDescriptor.getDependencies()) {
-                dependencies.add(new DefaultDependencyMetaData(dependencyDescriptor));
-            }
-        }
-        return dependencies;
-    }
-
-    public void setDependencies(Iterable<? extends DependencyMetaData> dependencies) {
-        assertResolved();
-        this.dependencies = CollectionUtils.toList(dependencies);
-    }
-
-    public boolean isChanging() {
-        assertResolved();
-        return changing;
-    }
-
-    public ModuleSource getModuleSource() {
-        assertResolved();
-        return moduleSource;
-    }
-
-    public void setModuleSource(ModuleSource moduleSource) {
-        assertResolved();
-        this.moduleSource = moduleSource;
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataResolveResult.java
new file mode 100644
index 0000000..5fda402
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataResolveResult.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.ivyservice.ivyresolve;
+
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+public class DefaultBuildableModuleVersionMetaDataResolveResult implements BuildableModuleVersionMetaDataResolveResult {
+    private State state = State.Unknown;
+    private ModuleSource moduleSource;
+    private ModuleVersionResolveException failure;
+    private MutableModuleVersionMetaData metaData;
+
+    private void reset(State state) {
+        this.state = state;
+        metaData = null;
+        failure = null;
+        moduleSource = null;
+    }
+
+    public void reset() {
+        reset(State.Unknown);
+    }
+
+    public void resolved(MutableModuleVersionMetaData metaData, ModuleSource moduleSource) {
+        reset(State.Resolved);
+        this.metaData = metaData;
+        this.moduleSource = moduleSource;
+    }
+
+    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;
+    }
+
+    public MutableModuleVersionMetaData getMetaData() throws ModuleVersionResolveException {
+        assertResolved();
+        return metaData;
+    }
+
+    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 ModuleSource getModuleSource() {
+        assertResolved();
+        return moduleSource;
+    }
+
+    public void setModuleSource(ModuleSource moduleSource) {
+        assertResolved();
+        this.moduleSource = moduleSource;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionSelectionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionSelectionResolveResult.java
new file mode 100644
index 0000000..b1a788d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionSelectionResolveResult.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+public class DefaultBuildableModuleVersionSelectionResolveResult implements BuildableModuleVersionSelectionResolveResult {
+    private State state = State.Unknown;
+    private ModuleVersionResolveException failure;
+    private ModuleVersionListing versions;
+
+    private void reset(State state) {
+        this.state = state;
+        versions = null;
+        failure = null;
+    }
+
+    public State getState() {
+        return state;
+    }
+
+    public ModuleVersionListing getVersions() throws ModuleVersionResolveException {
+        return versions;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        return failure;
+    }
+
+    public void listed(ModuleVersionListing versions) {
+        reset(State.Listed);
+        this.versions = versions;
+    }
+
+    public void probablyListed(ModuleVersionListing versions) {
+        reset(State.ProbablyListed);
+        this.versions = versions;
+    }
+
+    public void failed(ModuleVersionResolveException failure) {
+        reset(State.Failed);
+        this.failure = failure;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaData.java
deleted file mode 100644
index b442e75..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaData.java
+++ /dev/null
@@ -1,88 +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.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ReflectiveDependencyDescriptorFactory;
-import org.gradle.internal.UncheckedException;
-
-import java.lang.reflect.Field;
-
-class DefaultDependencyMetaData implements DependencyMetaData {
-    private final DependencyDescriptor dependencyDescriptor;
-    private DefaultModuleVersionSelector requested;
-
-    public DefaultDependencyMetaData(DependencyDescriptor dependencyDescriptor) {
-        this.dependencyDescriptor = dependencyDescriptor;
-        ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
-        requested = new DefaultModuleVersionSelector(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
-    }
-
-    @Override
-    public String toString() {
-        return dependencyDescriptor.toString();
-    }
-
-    public ModuleVersionSelector getRequested() {
-        return requested;
-    }
-
-    public boolean isChanging() {
-        return dependencyDescriptor.isChanging();
-    }
-
-    public DependencyDescriptor getDescriptor() {
-        return dependencyDescriptor;
-    }
-
-    public DependencyMetaData withRequestedVersion(String requestedVersion) {
-        if (requestedVersion.equals(requested.getVersion())) {
-            return this;
-        }
-        return new DefaultDependencyMetaData(dependencyDescriptor.clone(ModuleRevisionId.newInstance(dependencyDescriptor.getDependencyRevisionId(), requestedVersion)));
-    }
-
-    public DependencyMetaData withRequestedVersion(ModuleVersionSelector requestedVersion) {
-        if (requestedVersion.equals(requested)) {
-            return this;
-        }
-
-        ModuleRevisionId requestedId = ModuleRevisionId.newInstance(requestedVersion.getGroup(), requestedVersion.getName(), requestedVersion.getVersion());
-        DependencyDescriptor substitutedDescriptor = new ReflectiveDependencyDescriptorFactory().create(dependencyDescriptor, requestedId);
-        return new DefaultDependencyMetaData(substitutedDescriptor);
-    }
-
-    public DependencyMetaData withChanging() {
-        if (dependencyDescriptor.isChanging()) {
-            return this;
-        }
-
-        DependencyDescriptor forcedChanging = dependencyDescriptor.clone(dependencyDescriptor.getDependencyRevisionId());
-        try {
-            Field field = DefaultDependencyDescriptor.class.getDeclaredField("isChanging");
-            field.setAccessible(true);
-            field.set(forcedChanging, true);
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-        return new DefaultDependencyMetaData(forcedChanging);
-    }
-}
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
deleted file mode 100644
index 41e97ce..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultIvyAdapter.java
+++ /dev/null
@@ -1,37 +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.resolve.ResolveData;
-import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
-
-class DefaultIvyAdapter implements IvyAdapter {
-    private final ResolveData resolveData;
-    private final DependencyToModuleResolver userResolver;
-
-    public DefaultIvyAdapter(ResolveData resolveData, DependencyToModuleResolver userResolverChain) {
-        this.resolveData = resolveData;
-        userResolver = userResolverChain;
-    }
-
-    public ResolveData getResolveData() {
-        return resolveData;
-    }
-
-    public DependencyToModuleResolver getDependencyToModuleResolver() {
-        return userResolver;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionListing.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionListing.java
new file mode 100644
index 0000000..8a81e7a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionListing.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Transformer;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultModuleVersionListing implements ModuleVersionListing {
+    private final Set<Versioned> versions = new HashSet<Versioned>();
+
+    public DefaultModuleVersionListing(String version) {
+        add(version);
+    }
+
+    public DefaultModuleVersionListing() {
+    }
+
+    public void add(String version) {
+        versions.add(new DefaultAvailableVersion(version));
+    }
+
+    public Set<Versioned> getVersions() {
+        return versions;
+    }
+
+    public boolean isEmpty() {
+        return versions.isEmpty();
+    }
+
+    public List<Versioned> sortLatestFirst(LatestStrategy latestStrategy) {
+        List<Versioned> sorted = latestStrategy.sort(versions);
+        Collections.reverse(sorted);
+        return sorted;
+    }
+
+    @Override
+    public String toString() {
+        return CollectionUtils.collect(versions, new Transformer<String, Versioned>() {
+            public String transform(Versioned original) {
+                return original.getVersion();
+            }
+        }).toString();
+    }
+
+    private static class DefaultAvailableVersion implements Versioned {
+        private final String version;
+
+        public DefaultAvailableVersion(String version) {
+            this.version = version;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            return o instanceof DefaultAvailableVersion
+                    && version.equals(((DefaultAvailableVersion) o).version);
+        }
+
+        @Override
+        public int hashCode() {
+            return version.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("AvailableVersion '%s'", version);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DelegatingDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DelegatingDependencyResolver.java
deleted file mode 100644
index c40b50a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DelegatingDependencyResolver.java
+++ /dev/null
@@ -1,149 +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.cache.RepositoryCacheManager;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadReport;
-import org.apache.ivy.core.resolve.DownloadOptions;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.core.search.ModuleEntry;
-import org.apache.ivy.core.search.OrganisationEntry;
-import org.apache.ivy.core.search.RevisionEntry;
-import org.apache.ivy.plugins.namespace.Namespace;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.ResolverSettings;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.Map;
-
-abstract class DelegatingDependencyResolver implements DependencyResolver {
-    private final DependencyResolver resolver;
-    private ResolverSettings settings;
-
-    public DelegatingDependencyResolver(DependencyResolver resolver) {
-        this.resolver = resolver;
-    }
-
-    public DependencyResolver getResolver() {
-        return resolver;
-    }
-
-    public String getName() {
-        return resolver.getName();
-    }
-
-    public ResolverSettings getSettings() {
-        return settings;
-    }
-
-    public void setSettings(ResolverSettings settings) {
-        this.settings = settings;
-        resolver.setSettings(settings);
-    }
-
-    public Namespace getNamespace() {
-        return resolver.getNamespace();
-    }
-
-    public RepositoryCacheManager getRepositoryCacheManager() {
-        return resolver.getRepositoryCacheManager();
-    }
-
-    public ModuleEntry[] listModules(OrganisationEntry org) {
-        return resolver.listModules(org);
-    }
-
-    public OrganisationEntry[] listOrganisations() {
-        return resolver.listOrganisations();
-    }
-
-    public RevisionEntry[] listRevisions(ModuleEntry module) {
-        return resolver.listRevisions(module);
-    }
-
-    public String[] listTokenValues(String token, Map otherTokenValues) {
-        return resolver.listTokenValues(token, otherTokenValues);
-    }
-
-    public Map[] listTokenValues(String[] tokens, Map criteria) {
-        return resolver.listTokenValues(tokens, criteria);
-    }
-
-    public ArtifactOrigin locate(Artifact artifact) {
-        return resolver.locate(artifact);
-    }
-
-    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
-        return resolver.getDependency(dd, data);
-    }
-
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
-        return resolver.findIvyFileRef(dd, data);
-    }
-
-    public boolean exists(Artifact artifact) {
-        return resolver.exists(artifact);
-    }
-
-    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
-        return resolver.download(artifacts, options);
-    }
-
-    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
-        return resolver.download(artifact, options);
-    }
-
-    public void abortPublishTransaction() throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void setName(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void commitPublishTransaction() throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void reportFailure() {
-        throw new UnsupportedOperationException();
-    }
-
-    public void reportFailure(Artifact art) {
-        throw new UnsupportedOperationException();
-    }
-
-    public void dumpSettings() {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyMetaData.java
deleted file mode 100644
index 889998e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyMetaData.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.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-
-public interface DependencyMetaData {
-    ModuleVersionSelector getRequested();
-
-    DependencyDescriptor getDescriptor();
-
-    boolean isChanging();
-
-    /**
-     * Returns a copy of this dependency with the given requested version.
-     */
-    DependencyMetaData withRequestedVersion(String requestedVersion);
-
-    /**
-     * Returns a copy of this dependency with the given requested version.
-     */
-    DependencyMetaData withRequestedVersion(ModuleVersionSelector requestedVersion);
-
-    /**
-     * Returns a copy of this dependency with the changing flag set.
-     */
-    DependencyMetaData withChanging();
-}
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 d7e03a4..12c3d90 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
@@ -19,28 +19,16 @@ import org.apache.ivy.plugins.resolver.AbstractPatternsBasedResolver;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 import org.gradle.util.CollectionUtils;
-import org.gradle.util.hash.HashUtil;
+import org.gradle.internal.hash.HashUtil;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class DependencyResolverIdentifier {
-    private final String resolverId;
-    private final String resolverName;
-
-    public DependencyResolverIdentifier(DependencyResolver resolver) {
-        resolverName = resolver.getName();
-
+    public static String forIvyResolver(DependencyResolver resolver) {
         List<String> parts = new ArrayList<String>();
         parts.add(resolver.getClass().getName());
-        if (resolver instanceof ExternalResourceResolver) {
-            ExternalResourceResolver externalResourceResolver = (ExternalResourceResolver) resolver;
-            parts.add(joinPatterns(externalResourceResolver.getIvyPatterns()));
-            parts.add(joinPatterns(externalResourceResolver.getArtifactPatterns()));
-            if (externalResourceResolver.isM2compatible()) {
-                parts.add("m2compatible");
-            }
-        } else if (resolver instanceof AbstractPatternsBasedResolver) {
+        if (resolver instanceof AbstractPatternsBasedResolver) {
             AbstractPatternsBasedResolver patternsBasedResolver = (AbstractPatternsBasedResolver) resolver;
             parts.add(joinPatterns(patternsBasedResolver.getIvyPatterns()));
             parts.add(joinPatterns(patternsBasedResolver.getArtifactPatterns()));
@@ -52,23 +40,27 @@ public class DependencyResolverIdentifier {
             // TODO We should not be assuming equality between resolvers here based on name...
         }
 
-        resolverId = calculateId(parts);
+        return calculateId(parts);
     }
 
-    private String joinPatterns(List<String> patterns) {
+    // TODO: Move this logic into ExternalResourceResolver, and add some transport-specific information (bumping the cache version)
+    public static String forExternalResourceResolver(ExternalResourceResolver resolver) {
+        List<String> parts = new ArrayList<String>();
+        parts.add(resolver.getClass().getName());
+        parts.add(joinPatterns(resolver.getIvyPatterns()));
+        parts.add(joinPatterns(resolver.getArtifactPatterns()));
+        if (resolver.isM2compatible()) {
+            parts.add("m2compatible");
+        }
+        return calculateId(parts);
+    }
+
+    private static String joinPatterns(List<String> patterns) {
         return CollectionUtils.join(",", patterns);
     }
 
-    private String calculateId(List<String> parts) {
+    private static String calculateId(List<String> parts) {
         String idString = CollectionUtils.join("::", parts);
         return HashUtil.createHash(idString, "MD5").asHexString();
     }
-
-    public String getUniqueId() {
-        return resolverId;
-    }
-
-    public String getName() {
-        return resolverName;
-    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingArtifactResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingArtifactResolver.java
new file mode 100644
index 0000000..cdfac2c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingArtifactResolver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ArtifactResolveContext;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+public class ErrorHandlingArtifactResolver implements ArtifactResolver {
+    private final ArtifactResolver resolver;
+
+    public ErrorHandlingArtifactResolver(ArtifactResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        try {
+            resolver.resolveModuleArtifacts(component, context, result);
+        } catch (Throwable t) {
+            result.failed(new ArtifactResolveException(component.getComponentId(), t));
+        }
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        try {
+            resolver.resolveArtifact(artifact, moduleSource, result);
+        } catch (Throwable t) {
+            result.failed(new ArtifactResolveException(artifact.getId(), t));
+        }
+    }
+}
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
deleted file mode 100644
index e634fa2..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
+++ /dev/null
@@ -1,47 +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.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
-import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
-
-/**
- * A {@link ModuleVersionRepository} wrapper around an {@link ExternalResourceResolver}.
- */
-public class ExternalResourceResolverAdapter extends AbstractDependencyResolverAdapter {
-    private final ExternalResourceResolver resolver;
-    private final boolean dynamicResolve;
-
-    public ExternalResourceResolverAdapter(ExternalResourceResolver resolver, boolean dynamicResolve) {
-        super(resolver);
-        this.resolver = resolver;
-        this.dynamicResolve = dynamicResolve;
-    }
-
-    @Override
-    public boolean isDynamicResolveMode() {
-        return dynamicResolve;
-    }
-
-    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
-        resolver.getDependency(dependency.getDescriptor(), result);
-    }
-
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-        resolver.resolve(artifact, result, moduleSource);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAdapter.java
deleted file mode 100644
index 824b41b..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAdapter.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.resolve.ResolveData;
-import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
-
-public interface IvyAdapter {
-    ResolveData getResolveData();
-
-    DependencyToModuleResolver getDependencyToModuleResolver();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java
index b999020..1b66ddc 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java
@@ -16,13 +16,11 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
+import org.apache.ivy.core.resolve.ResolveData;
 import org.apache.ivy.core.settings.IvySettings;
 
 public interface IvyAwareModuleVersionRepository extends ModuleVersionRepository {
     void setSettings(IvySettings settings);
 
-    // TODO - move this
-    boolean isDynamicResolveMode();
-
-    boolean isLocal();
+    void setResolveData(ResolveData resolveData);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java
index b92dafb..f4c7a63 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java
@@ -15,46 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.Ivy;
 import org.apache.ivy.core.IvyContext;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.gradle.internal.UncheckedException;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 
 public class IvyContextualiser {
-    private final Ivy ivy;
-    private final ResolveData resolveData;
-
-    public IvyContextualiser(Ivy ivy, ResolveData resolveData) {
-        this.ivy = ivy;
-        this.resolveData = resolveData;
-    }
-    
-    public <T> T contextualise(Class<T> type, final T delegate) {
-        Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
-            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-                IvyContext context = IvyContext.pushNewCopyContext();
-                try {
-                    context.setIvy(ivy);
-                    context.setResolveData(resolveData);
-                    return method.invoke(delegate, args);
-                } catch (InvocationTargetException e) {
-                    throw UncheckedException.throwAsUncheckedException(e.getTargetException());
-                } finally {
-                    IvyContext.popContext();
-                }
-            }
-        });
-        return type.cast(proxy);
-    }
-
     public static IvyContext getIvyContext() {
         IvyContext context = IvyContext.getContext();
-        if (context.peekIvy() == null || context.getResolveData() == null) {
+        if (context.peekIvy() == null) {
             throw new IllegalStateException("Ivy context not established");
         }
         return context;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
deleted file mode 100644
index e4fe31b..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-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.ivyservice.BuildableArtifactResolveResult;
-import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
-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 IvyDependencyResolverAdapter extends AbstractDependencyResolverAdapter {
-    private static final Logger LOGGER = LoggerFactory.getLogger(IvyDependencyResolverAdapter.class);
-    private final DownloadOptions downloadOptions = new DownloadOptions();
-
-    public IvyDependencyResolverAdapter(DependencyResolver resolver) {
-        super(resolver);
-    }
-
-    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
-        ResolveData resolveData = IvyContextualiser.getIvyContext().getResolveData();
-        try {
-            ResolvedModuleRevision revision = resolver.getDependency(dependency.getDescriptor(), resolveData);
-            if (revision == null) {
-                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependency.getRequested(), getName());
-                result.missing();
-            } else {
-                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependency.getRequested(), getName());
-                result.resolved(revision.getDescriptor(), isChanging(revision), null);
-            }
-        } catch (ParseException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-        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;
-        }
-
-        File localFile = artifactDownloadReport.getLocalFile();
-        if (localFile != null) {
-            result.resolved(localFile);
-        } else {
-            result.notFound(artifact);
-        }
-    }
-
-    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);
-    }
-
-    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/IvyDynamicResolveModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepository.java
index 821e0d8..fec7c74 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepository.java
@@ -16,8 +16,10 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.metadata.*;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -37,29 +39,43 @@ public class IvyDynamicResolveModuleVersionRepository implements LocalAwareModul
         return repository.getName();
     }
 
-    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+    public void localListModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        repository.localListModuleVersions(dependency, result);
+    }
+
+    public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        repository.listModuleVersions(dependency, result);
+    }
+
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
         repository.getLocalDependency(dependency, result);
-        if (result.getState() == BuildableModuleVersionMetaData.State.Resolved) {
+        if (result.getState() == BuildableModuleVersionMetaDataResolveResult.State.Resolved) {
             transformDependencies(result);
         }
     }
 
-    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
         repository.getDependency(dependency, result);
-        if (result.getState() == BuildableModuleVersionMetaData.State.Resolved) {
+        if (result.getState() == BuildableModuleVersionMetaDataResolveResult.State.Resolved) {
             transformDependencies(result);
         }
     }
 
-    private void transformDependencies(BuildableModuleVersionMetaData result) {
+    private void transformDependencies(BuildableModuleVersionMetaDataResolveResult result) {
+        MutableModuleVersionMetaData metaData = result.getMetaData();
         List<DependencyMetaData> transformed = new ArrayList<DependencyMetaData>();
-        for (DependencyMetaData dependency : result.getDependencies()) {
+        for (DependencyMetaData dependency : metaData.getDependencies()) {
             transformed.add(dependency.withRequestedVersion(dependency.getDescriptor().getDynamicConstraintDependencyRevisionId().getRevision()));
         }
-        result.setDependencies(transformed);
+        metaData.setDependencies(transformed);
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-        repository.resolve(artifact, result, moduleSource);
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        repository.resolveModuleArtifacts(component, context, result);
     }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        repository.resolveArtifact(artifact, moduleSource, result);
+    }
+
 }
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 637cb5b..90705f1 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
@@ -15,32 +15,33 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.Configuration;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.plugins.version.VersionMatcher;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 
 /**
  * A {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionIdResolver} implementation which returns lazy resolvers that don't actually retrieve module descriptors until
  * required.
  */
 public class LazyDependencyToModuleResolver implements DependencyToModuleVersionIdResolver {
-    private final DependencyToModuleResolver dependencyResolver;
+    private final DependencyToModuleVersionResolver dependencyResolver;
     private final VersionMatcher versionMatcher;
 
-    public LazyDependencyToModuleResolver(DependencyToModuleResolver dependencyResolver, VersionMatcher versionMatcher) {
+    public LazyDependencyToModuleResolver(DependencyToModuleVersionResolver dependencyResolver, VersionMatcher versionMatcher) {
         this.dependencyResolver = dependencyResolver;
         this.versionMatcher = versionMatcher;
     }
 
     public ModuleVersionIdResolveResult resolve(DependencyMetaData dependency) {
-        if (versionMatcher.isDynamic(dependency.getDescriptor().getDependencyRevisionId())) {
+        if (versionMatcher.isDynamic(dependency.getRequested().getVersion())) {
             DynamicVersionResolveResult result = new DynamicVersionResolveResult(dependency);
             result.resolve();
             return result;
@@ -48,25 +49,9 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
         return new StaticVersionResolveResult(dependency);
     }
 
-    private static class ErrorHandlingArtifactResolver implements ArtifactResolver {
-        private final ArtifactResolver resolver;
-
-        private ErrorHandlingArtifactResolver(ArtifactResolver resolver) {
-            this.resolver = resolver;
-        }
-
-        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
-            try {
-                resolver.resolve(artifact, result);
-            } catch (Throwable t) {
-                result.failed(new ArtifactResolveException(artifact, t));
-            }
-        }
-    }
-
     private abstract class AbstractVersionResolveResult implements ModuleVersionIdResolveResult {
         final DependencyMetaData dependency;
-        private BuildableModuleVersionResolveResult resolveResult;
+        private BuildableComponentResolveResult resolveResult;
 
         public AbstractVersionResolveResult(DependencyMetaData dependency) {
             this.dependency = dependency;
@@ -76,9 +61,9 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             return null;
         }
 
-        public ModuleVersionResolveResult resolve() {
+        public ComponentResolveResult resolve() {
             if (resolveResult == null) {
-                resolveResult = new DefaultBuildableModuleVersionResolveResult();
+                resolveResult = new DefaultBuildableComponentResolveResult();
                 try {
                     try {
                         dependencyResolver.resolve(dependency, resolveResult);
@@ -92,7 +77,6 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
                         throw resolveResult.getFailure();
                     }
                     checkDescriptor(resolveResult.getMetaData());
-                    resolveResult.setArtifactResolver(new ErrorHandlingArtifactResolver(resolveResult.getArtifactResolver()));
                 } catch (ModuleVersionResolveException e) {
                     resolveResult.failed(e);
                 }
@@ -101,11 +85,11 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             return resolveResult;
         }
 
-        public ModuleVersionSelectionReason getSelectionReason() {
+        public ComponentSelectionReason getSelectionReason() {
             return VersionSelectionReasons.REQUESTED;
         }
 
-        protected void checkDescriptor(ModuleVersionMetaData metaData) {
+        protected void checkDescriptor(ComponentMetaData metaData) {
             ModuleDescriptor moduleDescriptor = metaData.getDescriptor();
             for (Configuration configuration : moduleDescriptor.getConfigurations()) {
                 for (String parent : configuration.getExtends()) {
@@ -132,12 +116,12 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             return id;
         }
 
-        public ModuleVersionSelectionReason getSelectionReason() {
+        public ComponentSelectionReason getSelectionReason() {
             return VersionSelectionReasons.REQUESTED;
         }
 
         @Override
-        protected void checkDescriptor(ModuleVersionMetaData metaData) {
+        protected void checkDescriptor(ComponentMetaData metaData) {
             if (!id.equals(metaData.getId())) {
                 throw new ModuleVersionResolveException(dependency.getRequested(), String.format("Received unexpected module descriptor %s for dependency %%s.", metaData.getId()));
             }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalArtifactsModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalArtifactsModuleVersionRepository.java
new file mode 100644
index 0000000..fe63cd9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalArtifactsModuleVersionRepository.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.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+// TODO This is a bit of a hack to allow us to avoid caching on the filesystem when the set of artifacts can be calculated cheaply.
+// A better long term solution might be to push the caching down to the resource layer, so that the answer to artifactExists() could be cached.
+public interface LocalArtifactsModuleVersionRepository extends ModuleVersionRepository {
+    /**
+     * Resolves a set of artifacts belonging to the gi  ven module, without making any network requests. Any failures are packaged up in the result.
+     * If no local resolution is possible, then the result should be set via {@link #resolveModuleArtifacts}.
+     */
+    void localResolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result);
+}
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
index 2066cb0..f910094 100644
--- 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
@@ -16,14 +16,27 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+
 public interface LocalAwareModuleVersionRepository extends ModuleVersionRepository {
     /**
+     * Lists the available versions for a module, using only local resources.
+     */
+    void localListModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result);
+
+    /**
+     * Lists the available versions for a module, using whichever resources are appropriate.
+     * Always called after {@link #localListModuleVersions(org.gradle.api.internal.artifacts.metadata.DependencyMetaData, BuildableModuleVersionSelectionResolveResult)}.
+     */
+    void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result);
+
+    /**
      * Locates the given dependency, using only local resources.
      */
-    void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result);
+    void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result);
 
     /**
-     * Locates the given dependency, using whichever resources are appropriate. Always called after {@link #getLocalDependency(DependencyMetaData, BuildableModuleVersionMetaData)}.
+     * Locates the given dependency, using whichever resources are appropriate. Always called after {@link #getLocalDependency(DependencyMetaData, BuildableModuleVersionMetaDataResolveResult)}.
      */
-    void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result);
+    void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult 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
index 93268b6..2ba8838 100644
--- 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
@@ -16,28 +16,49 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 
 public class LocalModuleVersionRepository implements LocalAwareModuleVersionRepository {
     private final ModuleVersionRepository delegate;
+    private final ModuleMetadataProcessor processor;
 
-    public LocalModuleVersionRepository(ModuleVersionRepository delegate) {
+    public LocalModuleVersionRepository(ModuleVersionRepository delegate, ModuleMetadataProcessor processor) {
         this.delegate = delegate;
+        this.processor = processor;
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-        delegate.resolve(artifact, result, moduleSource);
+    public void localListModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        delegate.listModuleVersions(dependency, result);
     }
 
-    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+    public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+    }
+
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
         delegate.getDependency(dependency, result);
+        if (result.getState() == BuildableModuleVersionMetaDataResolveResult.State.Resolved) {
+            processor.process(result.getMetaData());
+        }
     }
 
-    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
         result.missing();
     }
 
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        delegate.resolveModuleArtifacts(component, context, result);
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        delegate.resolveArtifact(artifact, moduleSource, result);
+    }
+
     public String getId() {
         return delegate.getId();
     }
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 38f1308..03ee5d8 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
@@ -17,57 +17,67 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.IvyContext;
 import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
 import org.apache.ivy.core.resolve.ResolveData;
 import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.ModuleEntry;
+import org.apache.ivy.core.search.OrganisationEntry;
+import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.namespace.Namespace;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.plugins.resolver.ResolverSettings;
-import org.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.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.DefaultDependencyMetaData;
 import org.gradle.internal.Factory;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.ParseException;
+import java.util.Map;
 
 /**
  * The main entry point for a {@link DependencyResolver} to call back into the dependency resolution mechanism.
  */
-public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
+public class LoopbackDependencyResolver implements DependencyResolver {
     private final String name;
-    private final UserResolverChain userResolverChain;
+    private final DependencyToModuleVersionResolver dependencyResolver;
+    private final ArtifactResolver artifactResolver;
     private final CacheLockingManager cacheLockingManager;
 
-    public LoopbackDependencyResolver(String name, UserResolverChain userResolverChain, CacheLockingManager cacheLockingManager) {
+    public LoopbackDependencyResolver(String name, RepositoryChain repositoryChain, CacheLockingManager cacheLockingManager) {
         this.name = name;
-        this.userResolverChain = userResolverChain;
+        this.dependencyResolver = repositoryChain.getDependencyResolver();
+        this.artifactResolver = repositoryChain.getArtifactResolver();
         this.cacheLockingManager = cacheLockingManager;
     }
 
-    @Override
     public String getName() {
         return name;
     }
 
-    @Override
     public void setSettings(ResolverSettings settings) {
         // don't care
     }
 
-    @Override
     public ResolvedModuleRevision getDependency(final DependencyDescriptor dd, final ResolveData data) throws ParseException {
         final DependencyResolver loopback = this;
         return cacheLockingManager.useCache(String.format("Resolve %s", dd), new Factory<ResolvedModuleRevision>() {
             public ResolvedModuleRevision create() {
-                DefaultBuildableModuleVersionResolveResult result = new DefaultBuildableModuleVersionResolveResult();
+                DefaultBuildableComponentResolveResult result = new DefaultBuildableComponentResolveResult();
                 DefaultDependencyMetaData dependency = new DefaultDependencyMetaData(dd);
                 IvyContext ivyContext = IvyContext.pushNewCopyContext();
                 try {
                     ivyContext.setResolveData(data);
-                    userResolverChain.resolve(dependency, result);
+                    dependencyResolver.resolve(dependency, result);
                 } finally {
                     IvyContext.popContext();
                 }
@@ -76,17 +86,17 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
         });
     }
 
-    @Override
     public ArtifactOrigin locate(final Artifact artifact) {
         return cacheLockingManager.useCache(String.format("Locate %s", artifact), new Factory<ArtifactOrigin>() {
             public ArtifactOrigin create() {
                 try {
                     DependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(artifact.getModuleRevisionId(), false);
-                    DefaultBuildableModuleVersionResolveResult resolveResult = new DefaultBuildableModuleVersionResolveResult();
+                    DefaultBuildableComponentResolveResult resolveResult = new DefaultBuildableComponentResolveResult();
                     DefaultDependencyMetaData dependency = new DefaultDependencyMetaData(dependencyDescriptor);
-                    userResolverChain.resolve(dependency, resolveResult);
+                    dependencyResolver.resolve(dependency, resolveResult);
                     DefaultBuildableArtifactResolveResult artifactResolveResult = new DefaultBuildableArtifactResolveResult();
-                    resolveResult.getArtifactResolver().resolve(artifact, artifactResolveResult);
+                    ComponentArtifactMetaData artifactMetaData = resolveResult.getMetaData().artifact(artifact);
+                    artifactResolver.resolveArtifact(artifactMetaData, resolveResult.getMetaData().getSource(), artifactResolveResult);
                     File artifactFile = artifactResolveResult.getFile();
                     return new ArtifactOrigin(artifact, false, artifactFile.getAbsolutePath());
                 } catch (ModuleVersionNotFoundException e) {
@@ -97,4 +107,80 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
             }
         });
     }
+
+    public void setName(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        throw new UnsupportedOperationException();
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        throw new UnsupportedOperationException();
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean exists(Artifact artifact) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void reportFailure() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void reportFailure(Artifact art) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        throw new UnsupportedOperationException();
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        throw new UnsupportedOperationException();
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        throw new UnsupportedOperationException();
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry module) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Namespace getNamespace() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void dumpSettings() {
+        throw new UnsupportedOperationException();
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java
index 61e2285..a2a980b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java
@@ -18,7 +18,11 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import java.io.Serializable;
 
+/**
+ * A memento for any resolution state that is relevant to locate the artifacts of a resolved module version.
+ *
+ * Implementations must retain as little state as possible and must be able to be serialized. Also note that
+ * a given instance may be passed to multiple repository instances.
+ */
 public interface ModuleSource extends Serializable {
 }
-
-
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionListing.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionListing.java
new file mode 100644
index 0000000..51b33e8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionListing.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ivyresolve.strategy.LatestStrategy;
+
+import java.util.List;
+import java.util.Set;
+
+public interface ModuleVersionListing {
+
+    Set<Versioned> getVersions();
+
+    boolean isEmpty();
+
+    List<Versioned> sortLatestFirst(LatestStrategy latestStrategy);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionMetaData.java
deleted file mode 100644
index db1ad9c..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionMetaData.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.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-import java.util.List;
-
-/**
- * The meta-data for a module version.
- */
-public interface ModuleVersionMetaData {
-    ModuleVersionIdentifier getId();
-
-    ModuleDescriptor getDescriptor();
-
-    List<DependencyMetaData> getDependencies();
-
-    boolean isChanging();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
index 8b6df7d..6ff7911 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
@@ -15,23 +15,27 @@
  */
 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.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 
 /**
  * A repository of module versions.
  *
- * The plan is to sync this with {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver}
+ * The plan is to sync this with {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver} and rename it
+ * to have 'resolver' instead of 'repository' in its name.
  */
-public interface ModuleVersionRepository {
+public interface ModuleVersionRepository extends ArtifactResolver {
     String getId();
 
     String getName();
 
-    void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result);
+    /**
+     * Resolves the given dependency to a list of module versions.
+     */
+    void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result);
 
     /**
-     * Downloads the given artifact. Any failures are packaged up in the result.
+     * Resolves the given dependency to the corresponding module version meta-data.
      */
-    void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource);
+    void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java
new file mode 100644
index 0000000..d861337
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver;
+
+public interface RepositoryChain {
+    public DependencyToModuleVersionResolver getDependencyResolver();
+    public ArtifactResolver getArtifactResolver();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java
new file mode 100644
index 0000000..063ec66
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ArtifactResolveContext;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.internal.Transformers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+// TODO:DAZ Unit test
+class RepositoryChainArtifactResolver implements ArtifactResolver {
+    private final Map<String, ModuleVersionRepository> repositories = new LinkedHashMap<String, ModuleVersionRepository>();
+
+    void add(ModuleVersionRepository repository) {
+        repositories.put(repository.getId(), repository);
+    }
+
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        findSourceRepository(component.getSource()).resolveModuleArtifacts(unpackSource(component), context, result);
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource source, BuildableArtifactResolveResult result) {
+        findSourceRepository(source).resolveArtifact(artifact, unpackSource(source), result);
+    }
+
+    private ModuleVersionRepository findSourceRepository(ModuleSource originalSource) {
+        ModuleVersionRepository moduleVersionRepository = repositories.get(repositorySource(originalSource).getRepositoryId());
+        if (moduleVersionRepository == null) {
+            throw new IllegalStateException("Attempting to resolve artifacts from invalid repository");
+        }
+        return moduleVersionRepository;
+    }
+
+    private RepositoryChainModuleSource repositorySource(ModuleSource original) {
+        return Transformers.cast(RepositoryChainModuleSource.class).transform(original);
+    }
+
+    private ModuleSource unpackSource(ModuleSource original) {
+        return repositorySource(original).getDelegate();
+    }
+
+    private ComponentMetaData unpackSource(ComponentMetaData component) {
+        return component.withSource(repositorySource(component.getSource()).getDelegate());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolver.java
new file mode 100644
index 0000000..9946d15
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolver.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Transformer;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableComponentResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+// TODO:DAZ This needs to be broken up
+public class RepositoryChainDependencyResolver implements DependencyToModuleVersionResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryChainDependencyResolver.class);
+
+    private final List<LocalAwareModuleVersionRepository> moduleVersionRepositories = new ArrayList<LocalAwareModuleVersionRepository>();
+    private final List<String> moduleVersionRepositoryNames = new ArrayList<String>();
+    private final VersionMatcher versionMatcher;
+    private final LatestStrategy latestStrategy;
+    private final Transformer<ModuleVersionMetaData, RepositoryChainModuleResolution> metaDataFactory;
+
+    public RepositoryChainDependencyResolver(VersionMatcher versionMatcher, LatestStrategy latestStrategy, Transformer<ModuleVersionMetaData, RepositoryChainModuleResolution> metaDataFactory) {
+        this.versionMatcher = versionMatcher;
+        this.latestStrategy = latestStrategy;
+        this.metaDataFactory = metaDataFactory;
+    }
+
+    public void add(LocalAwareModuleVersionRepository repository) {
+        moduleVersionRepositories.add(repository);
+        moduleVersionRepositoryNames.add(repository.getName());
+    }
+
+    public void resolve(DependencyMetaData dependency, BuildableComponentResolveResult result) {
+        ModuleVersionSelector requested = dependency.getRequested();
+        LOGGER.debug("Attempting to resolve module '{}' using repositories {}", requested, moduleVersionRepositoryNames);
+        List<Throwable> errors = new ArrayList<Throwable>();
+        final RepositoryChainModuleResolution latestResolved = findLatestModule(dependency, errors);
+        if (latestResolved != null) {
+            LOGGER.debug("Using module '{}' from repository '{}'", latestResolved.module.getId(), latestResolved.repository.getName());
+            for (Throwable error : errors) {
+                LOGGER.debug("Discarding resolve failure.", error);
+            }
+
+            result.resolved(metaDataFactory.transform(latestResolved));
+            return;
+        }
+        if (!errors.isEmpty()) {
+            result.failed(new ModuleVersionResolveException(requested, errors));
+        } else {
+            result.notFound(requested);
+        }
+    }
+
+    private RepositoryChainModuleResolution findLatestModule(DependencyMetaData dependency, Collection<Throwable> failures) {
+        LinkedList<RepositoryResolveState> queue = new LinkedList<RepositoryResolveState>();
+        for (LocalAwareModuleVersionRepository repository : moduleVersionRepositories) {
+            queue.add(createRepositoryResolveState(repository, dependency));
+        }
+        LinkedList<RepositoryResolveState> missing = new LinkedList<RepositoryResolveState>();
+
+        // A first pass to do local resolves only
+        RepositoryChainModuleResolution best = findLatestModule(dependency, queue, failures, missing);
+        if (best != null) {
+            return best;
+        }
+
+        // Nothing found - do a second pass
+        queue.addAll(missing);
+        missing.clear();
+        return findLatestModule(dependency, queue, failures, missing);
+    }
+
+    private RepositoryResolveState createRepositoryResolveState(LocalAwareModuleVersionRepository repository, DependencyMetaData dependency) {
+        if (versionMatcher.isDynamic(dependency.getRequested().getVersion())) {
+            return new DynamicVersionRepositoryResolveState(repository);
+        }
+        return new StaticVersionRepositoryResolveState(repository);
+    }
+
+    private RepositoryChainModuleResolution findLatestModule(DependencyMetaData dependency, LinkedList<RepositoryResolveState> queue, Collection<Throwable> failures, Collection<RepositoryResolveState> missing) {
+        boolean isStaticVersion = !versionMatcher.isDynamic(dependency.getRequested().getVersion());
+        RepositoryChainModuleResolution best = null;
+        while (!queue.isEmpty()) {
+            RepositoryResolveState request = queue.removeFirst();
+            try {
+                request.resolve(dependency);
+            } catch (Throwable t) {
+                failures.add(t);
+                continue;
+            }
+            switch (request.resolveResult.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:
+                    RepositoryChainModuleResolution moduleResolution = new RepositoryChainModuleResolution(request.repository, request.resolveResult.getMetaData(), request.resolveResult.getModuleSource());
+                    if (isStaticVersion && !moduleResolution.isGeneratedModuleDescriptor()) {
+                        return moduleResolution;
+                    }
+                    best = chooseBest(best, moduleResolution);
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected state for resolution: " + request.resolveResult.getState());
+            }
+        }
+
+        return best;
+    }
+
+    private RepositoryChainModuleResolution chooseBest(RepositoryChainModuleResolution one, RepositoryChainModuleResolution two) {
+        if (one == null || two == null) {
+            return two == null ? one : two;
+        }
+        if (one.module == null || two.module == null) {
+            return two.module == null ? one : two;
+        }
+
+        int comparison = latestStrategy.compare(one, two);
+
+        if (comparison == 0) {
+            if (one.isGeneratedModuleDescriptor() && !two.isGeneratedModuleDescriptor()) {
+                return two;
+            }
+            return one;
+        }
+
+        return comparison < 0 ? two : one;
+    }
+
+    public static abstract class RepositoryResolveState {
+        final LocalAwareModuleVersionRepository repository;
+        final DefaultBuildableModuleVersionSelectionResolveResult selectionResult = new DefaultBuildableModuleVersionSelectionResolveResult();
+        final DefaultBuildableModuleVersionMetaDataResolveResult resolveResult = new DefaultBuildableModuleVersionMetaDataResolveResult();
+        boolean searchedLocally;
+        boolean searchedRemotely;
+
+        public RepositoryResolveState(LocalAwareModuleVersionRepository repository) {
+            this.repository = repository;
+        }
+
+        void resolve(DependencyMetaData dependency) {
+            if (!searchedLocally) {
+                searchedLocally = true;
+                process(dependency, new LocalModuleAccess());
+            } else {
+                searchedRemotely = true;
+                process(dependency, new RemoteModuleAccess());
+            }
+            if (resolveResult.getState() == BuildableModuleVersionMetaDataResolveResult.State.Failed) {
+                throw resolveResult.getFailure();
+            }
+        }
+
+        protected abstract void process(DependencyMetaData dependency, ModuleAccess localModuleAccess);
+
+        public boolean canMakeFurtherAttempts() {
+            return !searchedRemotely;
+        }
+
+        protected interface ModuleAccess {
+            void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result);
+            void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result);
+        }
+
+        protected class LocalModuleAccess implements ModuleAccess {
+            public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+                repository.localListModuleVersions(dependency, result);
+            }
+
+            public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+                repository.getLocalDependency(dependency, result);
+            }
+        }
+
+        protected class RemoteModuleAccess implements ModuleAccess {
+            public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+                repository.listModuleVersions(dependency, result);
+            }
+
+            public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+                repository.getDependency(dependency, result);
+            }
+        }
+    }
+
+    private class StaticVersionRepositoryResolveState extends RepositoryResolveState {
+
+        public StaticVersionRepositoryResolveState(LocalAwareModuleVersionRepository repository) {
+            super(repository);
+        }
+
+        protected void process(DependencyMetaData dependency, ModuleAccess moduleAccess) {
+            moduleAccess.getDependency(dependency, resolveResult);
+        }
+    }
+
+    private class DynamicVersionRepositoryResolveState extends RepositoryResolveState {
+
+        public DynamicVersionRepositoryResolveState(LocalAwareModuleVersionRepository repository) {
+            super(repository);
+        }
+
+        protected void process(DependencyMetaData dependency, ModuleAccess moduleAccess) {
+            moduleAccess.listModuleVersions(dependency, selectionResult);
+            switch (selectionResult.getState()) {
+                case Failed:
+                    resolveResult.failed(selectionResult.getFailure());
+                    break;
+                case ProbablyListed:
+                    if (!resolveDependency(dependency, moduleAccess)) {
+                        resolveResult.probablyMissing();
+                    }
+                    break;
+                case Listed:
+                    if (!resolveDependency(dependency, moduleAccess)) {
+                        resolveResult.missing();
+                    }
+            }
+        }
+
+        private boolean resolveDependency(DependencyMetaData dependency, ModuleAccess moduleAccess) {
+            if (versionMatcher.needModuleMetadata(dependency.getRequested().getVersion())) {
+                return getBestMatchingDependencyWithMetaData(dependency, moduleAccess);
+            } else {
+                return getBestMatchingDependency(dependency, moduleAccess);
+            }
+        }
+
+        private boolean getBestMatchingDependency(DependencyMetaData dependency, ModuleAccess moduleAccess) {
+            ModuleVersionSelector selector = dependency.getRequested();
+            for (Versioned candidate : selectionResult.getVersions().sortLatestFirst(latestStrategy)) {
+                if (versionMatcher.accept(selector.getVersion(), candidate.getVersion())) {
+                    moduleAccess.getDependency(dependency.withRequestedVersion(candidate.getVersion()), resolveResult);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean getBestMatchingDependencyWithMetaData(DependencyMetaData dependency, ModuleAccess moduleAccess) {
+            ModuleVersionSelector selector = dependency.getRequested();
+            for (Versioned candidate : selectionResult.getVersions().sortLatestFirst(latestStrategy)) {
+                // Resolve the metadata
+                DependencyMetaData moduleVersionDependency = dependency.withRequestedVersion(candidate.getVersion());
+                moduleAccess.getDependency(moduleVersionDependency, resolveResult);
+                if (resolveResult.getState() != BuildableModuleVersionMetaDataResolveResult.State.Resolved) {
+                    // Couldn't load listed module
+                    LOGGER.warn("Could not load metadata for '{}' of listed module '{}' - ignoring.", candidate, selector);
+                    resolveResult.reset();
+                    return true;
+                }
+                if (versionMatcher.accept(selector.getVersion(), resolveResult.getMetaData())) {
+                    // We already resolved the correct module.
+                    return true;
+                }
+                resolveResult.reset();
+            }
+            return false;
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainModuleResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainModuleResolution.java
new file mode 100644
index 0000000..d018e2d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainModuleResolution.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.metadata.MutableModuleVersionMetaData;
+
+class RepositoryChainModuleResolution implements Versioned {
+    public final ModuleVersionRepository repository;
+    public final MutableModuleVersionMetaData module;
+    public final ModuleSource moduleSource;
+
+    public RepositoryChainModuleResolution(ModuleVersionRepository repository, MutableModuleVersionMetaData module, ModuleSource moduleSource) {
+        this.repository = repository;
+        this.module = module;
+        this.moduleSource = moduleSource;
+    }
+
+    public boolean isGeneratedModuleDescriptor() {
+        return module.getDescriptor().isDefault();
+    }
+
+    public String getVersion() {
+        return module.getId().getVersion();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainModuleSource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainModuleSource.java
new file mode 100644
index 0000000..b53f7b5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainModuleSource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+class RepositoryChainModuleSource implements ModuleSource {
+    private final String repositoryId;
+    private final ModuleSource delegate;
+
+    public RepositoryChainModuleSource(String repositoryId, ModuleSource delegate) {
+        this.repositoryId = repositoryId;
+        this.delegate = delegate;
+    }
+
+    public String getRepositoryId() {
+        return repositoryId;
+    }
+
+    public ModuleSource getDelegate() {
+        return delegate;
+    }
+}
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 e672c0e..e87d006 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
@@ -16,81 +16,112 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
 import org.apache.ivy.core.resolve.ResolveData;
 import org.apache.ivy.core.resolve.ResolveOptions;
 import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-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.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
-import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleVersionsCache;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.memcache.InMemoryDependencyMetadataCache;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleArtifactsCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetaDataCache;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
-import org.gradle.internal.TimeProvider;
+import org.gradle.util.BuildCommencedTimeProvider;
 import org.gradle.util.WrapUtil;
 
 public class ResolveIvyFactory {
-    private final IvyFactory ivyFactory;
-    private final SettingsConverter settingsConverter;
-    private final ModuleResolutionCache moduleResolutionCache;
-    private final ModuleDescriptorCache moduleDescriptorCache;
+    private final ModuleVersionsCache moduleVersionsCache;
+    private final ModuleMetaDataCache moduleMetaDataCache;
+    private final ModuleArtifactsCache moduleArtifactsCache;
     private final CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex;
     private final CacheLockingManager cacheLockingManager;
     private final StartParameterResolutionOverride startParameterResolutionOverride;
-    private final TimeProvider timeProvider;
+    private final BuildCommencedTimeProvider timeProvider;
+    private final InMemoryDependencyMetadataCache inMemoryCache;
+    private final VersionMatcher versionMatcher;
+    private final LatestStrategy latestStrategy;
 
-    public ResolveIvyFactory(IvyFactory ivyFactory, SettingsConverter settingsConverter,
-                             ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
+    public ResolveIvyFactory(ModuleVersionsCache moduleVersionsCache, ModuleMetaDataCache moduleMetaDataCache, ModuleArtifactsCache moduleArtifactsCache,
                              CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
                              CacheLockingManager cacheLockingManager, StartParameterResolutionOverride startParameterResolutionOverride,
-                             TimeProvider timeProvider) {
-        this.ivyFactory = ivyFactory;
-        this.settingsConverter = settingsConverter;
-        this.moduleResolutionCache = moduleResolutionCache;
-        this.moduleDescriptorCache = moduleDescriptorCache;
+                             BuildCommencedTimeProvider timeProvider, InMemoryDependencyMetadataCache inMemoryCache, VersionMatcher versionMatcher, LatestStrategy latestStrategy) {
+        this.moduleVersionsCache = moduleVersionsCache;
+        this.moduleMetaDataCache = moduleMetaDataCache;
+        this.moduleArtifactsCache = moduleArtifactsCache;
         this.artifactAtRepositoryCachedResolutionIndex = artifactAtRepositoryCachedResolutionIndex;
         this.cacheLockingManager = cacheLockingManager;
         this.startParameterResolutionOverride = startParameterResolutionOverride;
         this.timeProvider = timeProvider;
+        this.inMemoryCache = inMemoryCache;
+        this.versionMatcher = versionMatcher;
+        this.latestStrategy = latestStrategy;
     }
 
-    public IvyAdapter create(ConfigurationInternal configuration, Iterable<? extends ResolutionAwareRepository> repositories) {
-        UserResolverChain userResolverChain = new UserResolverChain();
+    public RepositoryChain create(ConfigurationInternal configuration,
+                                  Iterable<? extends ResolutionAwareRepository> repositories,
+                                  ModuleMetadataProcessor metadataProcessor) {
         ResolutionRules resolutionRules = configuration.getResolutionStrategy().getResolutionRules();
-        startParameterResolutionOverride.addResolutionRules(resolutionRules);
-
-        LoopbackDependencyResolver loopbackDependencyResolver = new LoopbackDependencyResolver(SettingsConverter.LOOPBACK_RESOLVER_NAME, userResolverChain, cacheLockingManager);
+        CachePolicy cachePolicy = configuration.getResolutionStrategy().getCachePolicy();
 
-        IvySettings ivySettings = settingsConverter.convertForResolve(loopbackDependencyResolver);
-        userResolverChain.setSettings(ivySettings);
+        startParameterResolutionOverride.addResolutionRules(resolutionRules);
 
-        Ivy ivy = ivyFactory.createIvy(ivySettings);
-        ResolveData resolveData = createResolveData(ivy, configuration.getName());
-        IvyContextualiser contextualiser = new IvyContextualiser(ivy, resolveData);
+        UserResolverChain userResolverChain = new UserResolverChain(versionMatcher, latestStrategy);
+        RepositoryChain parentLookupResolver = new ParentModuleLookupResolver(userResolverChain, cacheLockingManager);
 
         for (ResolutionAwareRepository repository : repositories) {
-            IvyAwareModuleVersionRepository moduleVersionRepository = repository.createResolver();
-            moduleVersionRepository.setSettings(ivySettings);
+            ConfiguredModuleVersionRepository moduleVersionRepository = repository.createResolver();
+
+            if (moduleVersionRepository instanceof IvyAwareModuleVersionRepository) {
+                ivyContextualize((IvyAwareModuleVersionRepository) moduleVersionRepository, userResolverChain, configuration.getName());
+            }
+            if (moduleVersionRepository instanceof ExternalResourceResolver) {
+                ((ExternalResourceResolver) moduleVersionRepository).setRepositoryChain(parentLookupResolver);
+            }
 
             LocalAwareModuleVersionRepository localAwareRepository;
             if (moduleVersionRepository.isLocal()) {
-                localAwareRepository = new LocalModuleVersionRepository(moduleVersionRepository);
+                localAwareRepository = new LocalModuleVersionRepository(moduleVersionRepository, metadataProcessor);
             } else {
-                ModuleVersionRepository wrapperRepository = new CacheLockingModuleVersionRepository(moduleVersionRepository, cacheLockingManager);
+                LocalArtifactsModuleVersionRepository wrapperRepository = new CacheLockingModuleVersionRepository(moduleVersionRepository, cacheLockingManager);
                 wrapperRepository = startParameterResolutionOverride.overrideModuleVersionRepository(wrapperRepository);
-                localAwareRepository = new CachingModuleVersionRepository(wrapperRepository, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCachedResolutionIndex,
-                        configuration.getResolutionStrategy().getCachePolicy(), timeProvider);
+                localAwareRepository = new CachingModuleVersionRepository(wrapperRepository, moduleVersionsCache, moduleMetaDataCache, moduleArtifactsCache, artifactAtRepositoryCachedResolutionIndex,
+                        cachePolicy, timeProvider, metadataProcessor, getModuleExtractor(moduleVersionRepository));
             }
             if (moduleVersionRepository.isDynamicResolveMode()) {
                 localAwareRepository = new IvyDynamicResolveModuleVersionRepository(localAwareRepository);
             }
-            localAwareRepository = contextualiser.contextualise(LocalAwareModuleVersionRepository.class, localAwareRepository);
+            localAwareRepository = inMemoryCache.cached(localAwareRepository);
             userResolverChain.add(localAwareRepository);
         }
 
-        return new DefaultIvyAdapter(resolveData, userResolverChain);
+        return userResolverChain;
+    }
+
+    private void ivyContextualize(IvyAwareModuleVersionRepository ivyAwareRepository, RepositoryChain userResolverChain, String configurationName) {
+        Ivy ivy = IvyContext.getContext().getIvy();
+        IvySettings ivySettings = ivy.getSettings();
+        LoopbackDependencyResolver loopbackDependencyResolver = new LoopbackDependencyResolver("main", userResolverChain, cacheLockingManager);
+        ivySettings.addResolver(loopbackDependencyResolver);
+        ivySettings.setDefaultResolver(loopbackDependencyResolver.getName());
+
+        ResolveData resolveData = createResolveData(ivy, configurationName);
+        ivyAwareRepository.setSettings(ivySettings);
+        ivyAwareRepository.setResolveData(resolveData);
     }
 
     private ResolveData createResolveData(Ivy ivy, String configurationName) {
@@ -99,4 +130,73 @@ public class ResolveIvyFactory {
         options.setConfs(WrapUtil.toArray(configurationName));
         return new ResolveData(ivy.getResolveEngine(), options);
     }
+
+    private Transformer<ModuleIdentifier, ModuleVersionSelector> getModuleExtractor(ConfiguredModuleVersionRepository rootRepository) {
+        // If the backing repository is a custom ivy resolver, then we don't get a full listing
+        if (rootRepository instanceof IvyAwareModuleVersionRepository) {
+            return new LegacyIvyResolverModuleIdExtractor();
+        }
+        return new DefaultModuleIdExtractor();
+    }
+
+    /**
+     * Provides access to the top-level resolver chain for looking up parent modules when parsing module descriptor files.
+     */
+    private static class ParentModuleLookupResolver implements RepositoryChain, DependencyToModuleVersionResolver, ArtifactResolver {
+        private final DependencyToModuleVersionResolver dependencyResolver;
+        private final ArtifactResolver artifactResolver;
+        private final CacheLockingManager cacheLockingManager;
+
+        public ParentModuleLookupResolver(RepositoryChain repositoryChain, CacheLockingManager cacheLockingManager) {
+            this.dependencyResolver = repositoryChain.getDependencyResolver();
+            this.artifactResolver = repositoryChain.getArtifactResolver();
+            this.cacheLockingManager = cacheLockingManager;
+        }
+
+        public ArtifactResolver getArtifactResolver() {
+            return this;
+        }
+
+        public DependencyToModuleVersionResolver getDependencyResolver() {
+            return this;
+        }
+
+        public void resolve(final DependencyMetaData dependency, final BuildableComponentResolveResult result) {
+            cacheLockingManager.useCache(String.format("Resolve %s", dependency), new Runnable() {
+                public void run() {
+                    dependencyResolver.resolve(dependency, result);
+                }
+            });
+        }
+
+        public void resolveModuleArtifacts(final ComponentMetaData component, final ArtifactResolveContext context, final BuildableArtifactSetResolveResult result) {
+            cacheLockingManager.useCache(String.format("Resolve %s for %s", context.getDescription(), component), new Runnable() {
+                public void run() {
+                    artifactResolver.resolveModuleArtifacts(component, context, result);
+                }
+            });
+        }
+
+        public void resolveArtifact(final ComponentArtifactMetaData artifact, final ModuleSource moduleSource, final BuildableArtifactResolveResult result) {
+            cacheLockingManager.useCache(String.format("Resolve %s", artifact), new Runnable() {
+                public void run() {
+                    artifactResolver.resolveArtifact(artifact, moduleSource, result);
+                }
+            });
+        }
+    }
+
+    private static class DefaultModuleIdExtractor implements Transformer<ModuleIdentifier, ModuleVersionSelector> {
+        public ModuleIdentifier transform(ModuleVersionSelector original) {
+            return new DefaultModuleIdentifier(original.getGroup(), original.getName());
+        }
+    }
+
+    // When caching module listing for ivy resolvers, we can only cache for a particular version request
+    // TODO: Remove this when we remove support for Ivy DependencyResolvers
+    private static class LegacyIvyResolverModuleIdExtractor implements Transformer<ModuleIdentifier, ModuleVersionSelector> {
+        public ModuleIdentifier transform(ModuleVersionSelector original) {
+            return new DefaultModuleIdentifier(original.getGroup(), original.getName() + ":" + original.getVersion());
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java
deleted file mode 100644
index fc308e2..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.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.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-
-public abstract class RestrictedDependencyResolver extends DelegatingDependencyResolver {
-    protected RestrictedDependencyResolver() {
-        super(createAngryDelegate());
-    }
-
-    private static DependencyResolver createAngryDelegate() {
-        return (DependencyResolver) Proxy.newProxyInstance(RestrictedDependencyResolver.class.getClassLoader(), new Class<?>[]{DependencyResolver.class}, new InvocationHandler() {
-            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-                throw new UnsupportedOperationException();
-            }
-        });
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
index b75aeec..3cf09f5 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
@@ -15,15 +15,19 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.StartParameter;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.cache.ArtifactResolutionControl;
 import org.gradle.api.artifacts.cache.DependencyResolutionControl;
 import org.gradle.api.artifacts.cache.ModuleResolutionControl;
 import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 
 import java.util.concurrent.TimeUnit;
 
@@ -70,17 +74,17 @@ public class StartParameterResolutionOverride {
         }
     }
 
-    public ModuleVersionRepository overrideModuleVersionRepository(ModuleVersionRepository original) {
+    public LocalArtifactsModuleVersionRepository overrideModuleVersionRepository(LocalArtifactsModuleVersionRepository original) {
         if (startParameter.isOffline()) {
             return new OfflineModuleVersionRepository(original);
         }
         return original;
     }
 
-    private static class OfflineModuleVersionRepository implements ModuleVersionRepository {
-        private final ModuleVersionRepository original;
+    private static class OfflineModuleVersionRepository implements ModuleVersionRepository, LocalArtifactsModuleVersionRepository {
+        private final LocalArtifactsModuleVersionRepository original;
 
-        public OfflineModuleVersionRepository(ModuleVersionRepository original) {
+        public OfflineModuleVersionRepository(LocalArtifactsModuleVersionRepository original) {
             this.original = original;
         }
 
@@ -96,12 +100,24 @@ public class StartParameterResolutionOverride {
             return false;
         }
 
-        public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+            result.failed(new ModuleVersionResolveException(dependency.getRequested(), "No cached version listing for %s available for offline mode."));
+        }
+
+        public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
             result.failed(new ModuleVersionResolveException(dependency.getRequested(), "No cached version of %s available for offline mode."));
         }
 
-        public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-            result.failed(new ArtifactResolveException(artifact, "No cached version available for offline mode"));
+        public void localResolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+            original.localResolveModuleArtifacts(component, context, result);
+        }
+
+        public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+            result.failed(new ArtifactResolveException(component.getComponentId(), "No cached version available for offline mode"));
+        }
+
+        public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+            result.failed(new ArtifactResolveException(artifact.getId(), "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 0364650..797fd3e 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,200 +16,38 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
-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.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.artifacts.ivyservice.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
 
-import java.util.*;
+public class UserResolverChain implements RepositoryChain {
+    private final RepositoryChainDependencyResolver dependencyResolver;
+    private final RepositoryChainArtifactResolver artifactResolver = new RepositoryChainArtifactResolver();
 
-public class UserResolverChain implements DependencyToModuleResolver {
-    private static final Logger LOGGER = LoggerFactory.getLogger(UserResolverChain.class);
-
-    private final List<LocalAwareModuleVersionRepository> moduleVersionRepositories = new ArrayList<LocalAwareModuleVersionRepository>();
-    private final List<String> moduleVersionRepositoryNames = new ArrayList<String>();
-    private ResolverSettings settings;
-
-    public void setSettings(ResolverSettings settings) {
-        this.settings = settings;
-    }
-
-    public void add(LocalAwareModuleVersionRepository repository) {
-        moduleVersionRepositories.add(repository);
-        moduleVersionRepositoryNames.add(repository.getName());
-    }
-
-    public void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result) {
-        ModuleVersionSelector requested = dependency.getRequested();
-        LOGGER.debug("Attempting to resolve module '{}' using repositories {}", requested, moduleVersionRepositoryNames);
-        List<Throwable> errors = new ArrayList<Throwable>();
-        final ModuleResolution latestResolved = findLatestModule(dependency, errors);
-        if (latestResolved != null) {
-            final ModuleVersionMetaData downloadedModule = latestResolved.module;
-            LOGGER.debug("Using module '{}' from repository '{}'", downloadedModule.getId(), latestResolved.repository.getName());
-            for (Throwable error : errors) {
-                LOGGER.debug("Discarding resolve failure.", error);
-            }
-            result.resolved(latestResolved.module, new ModuleVersionRepositoryArtifactResolverAdapter(latestResolved.repository, latestResolved.moduleSource));
-            return;
-        }
-        if (!errors.isEmpty()) {
-            result.failed(new ModuleVersionResolveException(requested, errors));
-        } else {
-            result.notFound(requested);
-        }
-    }
-
-    private ModuleResolution findLatestModule(DependencyMetaData dependency, 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(dependency, queue, failures, missing);
-        if (best != null) {
-            return best;
-        }
-
-        // Nothing found - do a second pass
-        queue.addAll(missing);
-        missing.clear();
-        return findLatestModule(dependency, queue, failures, missing);
+    public UserResolverChain(VersionMatcher versionMatcher, LatestStrategy latestStrategy) {
+        this.dependencyResolver = new RepositoryChainDependencyResolver(versionMatcher, latestStrategy, new ModuleTransformer());
     }
 
-    private ModuleResolution findLatestModule(DependencyMetaData dependency, LinkedList<RepositoryResolveState> queue, Collection<Throwable> failures, Collection<RepositoryResolveState> missing) {
-        boolean isStaticVersion = !settings.getVersionMatcher().isDynamic(dependency.getDescriptor().getDependencyRevisionId());
-        ModuleResolution best = null;
-        while (!queue.isEmpty()) {
-            RepositoryResolveState request = queue.removeFirst();
-            try {
-                request.resolve(dependency);
-            } 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, request.descriptor.getModuleSource());
-                    if (isStaticVersion && !moduleResolution.isGeneratedModuleDescriptor()) {
-                        return moduleResolution;
-                    }
-                    best = chooseBest(best, moduleResolution);
-                    break;
-                default:
-                    throw new IllegalStateException("Unexpected state for resolution: " + request.descriptor.getState());
-            }
-        }
-
-        return best;
-    }
-
-    private ModuleResolution chooseBest(ModuleResolution one, ModuleResolution two) {
-        if (one == null || two == null) {
-            return two == null ? one : two;
-        }
-        if (one.module == null || two.module == null) {
-            return two.module == null ? one : two;
-        }
-
-        ComparatorLatestStrategy latestStrategy = (ComparatorLatestStrategy) settings.getDefaultLatestStrategy();
-        Comparator<ArtifactInfo> comparator = latestStrategy.getComparator();
-        int comparison = comparator.compare(one, two);
-
-        if (comparison == 0) {
-            if (one.isGeneratedModuleDescriptor() && !two.isGeneratedModuleDescriptor()) {
-                return two;
-            }
-            return one;
-        }
-
-        return comparison < 0 ? two : one;
+    public DependencyToModuleVersionResolver getDependencyResolver() {
+        return dependencyResolver;
     }
 
-    private static class ModuleVersionRepositoryArtifactResolverAdapter implements ArtifactResolver {
-        private final ModuleVersionRepository delegate;
-        private final ModuleSource moduleSource;
-
-        public ModuleVersionRepositoryArtifactResolverAdapter(ModuleVersionRepository repository, ModuleSource moduleSource) {
-            this.delegate = repository;
-            this.moduleSource = moduleSource;
-        }
-
-        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
-            delegate.resolve(artifact, result, moduleSource);
-        }
+    public ArtifactResolver getArtifactResolver() {
+        return artifactResolver;
     }
 
-    private static class RepositoryResolveState {
-        final LocalAwareModuleVersionRepository repository;
-        final DefaultBuildableModuleVersionMetaData descriptor = new DefaultBuildableModuleVersionMetaData();
-
-        boolean searchedLocally;
-        boolean searchedRemotely;
-
-        private RepositoryResolveState(LocalAwareModuleVersionRepository repository) {
-            this.repository = repository;
-        }
-
-        void resolve(DependencyMetaData dependency) {
-            if (!searchedLocally) {
-                searchedLocally = true;
-                repository.getLocalDependency(dependency, descriptor);
-            } else {
-                searchedRemotely = true;
-                repository.getDependency(dependency, descriptor);
-            }
-            if (descriptor.getState() == BuildableModuleVersionMetaData.State.Failed) {
-                throw descriptor.getFailure();
-            }
-        }
-
-        public boolean canMakeFurtherAttempts() {
-            return !searchedRemotely;
-        }
+    public void add(LocalAwareModuleVersionRepository repository) {
+        dependencyResolver.add(repository);
+        artifactResolver.add(repository);
     }
 
-    private static class ModuleResolution implements ArtifactInfo {
-        public final ModuleVersionRepository repository;
-        public final ModuleVersionMetaData module;
-        public final ModuleSource moduleSource;
-
-        public ModuleResolution(ModuleVersionRepository repository, ModuleVersionMetaData module, ModuleSource moduleSource) {
-            this.repository = repository;
-            this.module = module;
-            this.moduleSource = moduleSource;
-        }
-
-        public boolean isGeneratedModuleDescriptor() {
-            return module.getDescriptor().isDefault();
-        }
-
-        public long getLastModified() {
-            return module.getDescriptor().getResolvedPublicationDate().getTime();
-        }
-
-        public String getRevision() {
-            return module.getId().getVersion();
+    private static class ModuleTransformer implements Transformer<ModuleVersionMetaData, RepositoryChainModuleResolution> {
+        public ModuleVersionMetaData transform(RepositoryChainModuleResolution original) {
+            RepositoryChainModuleSource moduleSource = new RepositoryChainModuleSource(original.repository.getId(), original.moduleSource);
+            return original.module.withSource(moduleSource);
         }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionInfo.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionInfo.java
new file mode 100644
index 0000000..8ba0d75
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+public class VersionInfo implements Versioned {
+    private final String version;
+
+    public VersionInfo(String version) {
+        this.version = version;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public boolean equals(Object other) {
+        if (!(other instanceof VersionInfo)) {
+            return false;
+        }
+        return version.equals(((VersionInfo) other).getVersion());
+    }
+
+    public int hashCode() {
+        return version.hashCode();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/Versioned.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/Versioned.java
new file mode 100644
index 0000000..7a4e207
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/Versioned.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+public interface Versioned {
+    String getVersion();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedModuleVersionResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedModuleVersionResult.java
new file mode 100644
index 0000000..ed10f9b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedModuleVersionResult.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+import static org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult.State.*;
+
+class CachedModuleVersionResult {
+    private final BuildableModuleVersionMetaDataResolveResult.State state;
+    private final MutableModuleVersionMetaData metaData;
+    private final ModuleSource moduleSource;
+
+    public CachedModuleVersionResult(BuildableModuleVersionMetaDataResolveResult result) {
+        this.state = result.getState();
+        if (state == Resolved) {
+            this.metaData = result.getMetaData().copy();
+            this.moduleSource = result.getModuleSource();
+        } else {
+            this.metaData = null;
+            this.moduleSource = null;
+        }
+    }
+
+    public boolean isCacheable() {
+        return state == Missing || state == ProbablyMissing || state == Resolved;
+    }
+
+    public void supply(BuildableModuleVersionMetaDataResolveResult result) {
+        assert isCacheable() : "Results are not cacheable, cannot supply the results.";
+        if (state == Resolved) {
+            result.resolved(metaData.copy(), moduleSource);
+        } else if (state == Missing) {
+            result.missing();
+        } else if (state == ProbablyMissing) {
+            result.probablyMissing();
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedRepository.java
new file mode 100644
index 0000000..bf0c0a5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedRepository.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache;
+
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionSelectionResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LocalAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+
+// TODO:DAZ Add in-memory caching for resolveModuleArtifacts()
+// TODO:DAZ Change this so that it's a decoration over the file-system caching, rather than something completely separate.
+class CachedRepository implements LocalAwareModuleVersionRepository {
+    final DependencyMetadataCache cache;
+    final LocalAwareModuleVersionRepository delegate;
+    final DependencyMetadataCacheStats stats;
+
+    public CachedRepository(DependencyMetadataCache cache, LocalAwareModuleVersionRepository delegate, DependencyMetadataCacheStats stats) {
+        this.cache = cache;
+        this.delegate = delegate;
+        this.stats = stats;
+    }
+
+    public String getId() {
+        return delegate.getId();
+    }
+
+    public String getName() {
+        return delegate.getName();
+    }
+
+    public void localListModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        if(!cache.supplyLocalModuleVersions(dependency.getRequested(), result)) {
+            delegate.localListModuleVersions(dependency, result);
+            cache.newLocalModuleVersions(dependency.getRequested(), result);
+        }
+    }
+
+    public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        if(!cache.supplyModuleVersions(dependency.getRequested(), result)) {
+            delegate.listModuleVersions(dependency, result);
+            cache.newModuleVersions(dependency.getRequested(), result);
+        }
+    }
+
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+        if(!cache.supplyLocalMetaData(dependency.getRequested(), result)) {
+            delegate.getLocalDependency(dependency, result);
+            cache.newLocalDependencyResult(dependency.getRequested(), result);
+        }
+    }
+
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+        if(!cache.supplyMetaData(dependency.getRequested(), result)) {
+            delegate.getDependency(dependency, result);
+            cache.newDependencyResult(dependency.getRequested(), result);
+        }
+    }
+
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        delegate.resolveModuleArtifacts(component, context, result);
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        if (!cache.supplyArtifact(artifact.getId(), result)) {
+            delegate.resolveArtifact(artifact, moduleSource, result);
+            cache.newArtifact(artifact.getId(), result);
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCache.java
new file mode 100644
index 0000000..b2d35ca
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCache.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionSelectionResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionListing;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactIdentifier;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionSelectionResolveResult.State.*;
+
+class DependencyMetadataCache {
+    private final Map<ModuleVersionSelector, ModuleVersionListing> localModuleVersionListing = new HashMap<ModuleVersionSelector, ModuleVersionListing>();
+    private final Map<ModuleVersionSelector, ModuleVersionListing> moduleVersionListing = new HashMap<ModuleVersionSelector, ModuleVersionListing>();
+    private final Map<ModuleVersionSelector, CachedModuleVersionResult> localMetaData = new HashMap<ModuleVersionSelector, CachedModuleVersionResult>();
+    private final Map<ModuleVersionSelector, CachedModuleVersionResult> metaData = new HashMap<ModuleVersionSelector, CachedModuleVersionResult>();
+    private final Map<ComponentArtifactIdentifier, File> artifacts = new HashMap<ComponentArtifactIdentifier, File>();
+    private DependencyMetadataCacheStats stats;
+
+    DependencyMetadataCache(DependencyMetadataCacheStats stats) {
+        this.stats = stats;
+    }
+
+    public boolean supplyLocalModuleVersions(ModuleVersionSelector requested, BuildableModuleVersionSelectionResolveResult result) {
+        return supply(requested, result, localModuleVersionListing);
+    }
+
+    public void newLocalModuleVersions(ModuleVersionSelector requested, BuildableModuleVersionSelectionResolveResult result) {
+        newResult(requested, result, localModuleVersionListing);
+    }
+
+    public boolean supplyModuleVersions(ModuleVersionSelector requested, BuildableModuleVersionSelectionResolveResult result) {
+        return supply(requested, result, moduleVersionListing);
+    }
+
+    public void newModuleVersions(ModuleVersionSelector requested, BuildableModuleVersionSelectionResolveResult result) {
+        newResult(requested, result, moduleVersionListing);
+    }
+
+    private boolean supply(ModuleVersionSelector requested, BuildableModuleVersionSelectionResolveResult result, Map<ModuleVersionSelector, ModuleVersionListing> map) {
+        ModuleVersionListing moduleVersionListing = map.get(requested);
+        if (moduleVersionListing == null) {
+            return false;
+        }
+        result.listed(moduleVersionListing);
+        return true;
+    }
+
+    private void newResult(ModuleVersionSelector requested, BuildableModuleVersionSelectionResolveResult result, Map<ModuleVersionSelector, ModuleVersionListing> map) {
+        if (result.getState() == Listed || result.getState() == ProbablyListed) {
+            map.put(requested, result.getVersions());
+        }
+    }
+
+    boolean supplyLocalMetaData(ModuleVersionSelector requested, BuildableModuleVersionMetaDataResolveResult result) {
+        return supply(requested, result, localMetaData, stats);
+    }
+
+    boolean supplyMetaData(ModuleVersionSelector requested, BuildableModuleVersionMetaDataResolveResult result) {
+        return supply(requested, result, metaData, stats);
+    }
+
+    private static boolean supply(ModuleVersionSelector requested, BuildableModuleVersionMetaDataResolveResult result, Map<ModuleVersionSelector, CachedModuleVersionResult> map, DependencyMetadataCacheStats stats) {
+        CachedModuleVersionResult fromCache = map.get(requested);
+        if (fromCache == null) {
+            return false;
+        }
+        fromCache.supply(result);
+        stats.metadataServed++;
+        return true;
+    }
+
+    void newLocalDependencyResult(ModuleVersionSelector requested, BuildableModuleVersionMetaDataResolveResult result) {
+        newResult(requested, result, localMetaData);
+    }
+
+    void newDependencyResult(ModuleVersionSelector requested, BuildableModuleVersionMetaDataResolveResult result) {
+        newResult(requested, result, metaData);
+    }
+
+    private static void newResult(ModuleVersionSelector requested, BuildableModuleVersionMetaDataResolveResult result, Map<ModuleVersionSelector, CachedModuleVersionResult> map) {
+        CachedModuleVersionResult cachedResult = new CachedModuleVersionResult(result);
+        if (cachedResult.isCacheable()) {
+            map.put(requested, cachedResult);
+        }
+    }
+
+    public boolean supplyArtifact(ComponentArtifactIdentifier id, BuildableArtifactResolveResult result) {
+        File fromCache = artifacts.get(id);
+        if (fromCache != null) {
+            result.resolved(fromCache);
+            stats.artifactsServed++;
+            return true;
+        }
+        return false;
+    }
+
+    public void newArtifact(ComponentArtifactIdentifier id, BuildableArtifactResolveResult result) {
+        if (result.getFailure() == null) {
+            artifacts.put(id, result.getFile());
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCacheStats.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCacheStats.java
new file mode 100644
index 0000000..127d5e6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCacheStats.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache;
+
+class DependencyMetadataCacheStats {
+    int cacheInstances;
+    int reposWrapped;
+    int metadataServed;
+    int artifactsServed;
+    public String toString() {
+        return String.format(
+                "Repos cached: %s, cache instances: %s, modules served from cache: %s, artifacts: %s",
+                reposWrapped, cacheInstances, metadataServed, artifactsServed);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryDependencyMetadataCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryDependencyMetadataCache.java
new file mode 100644
index 0000000..b4b07b8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryDependencyMetadataCache.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache;
+
+import com.google.common.collect.MapMaker;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LocalAwareModuleVersionRepository;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.concurrent.Stoppable;
+
+import java.util.Map;
+
+/**
+ * Caches the dependency metadata (descriptors, artifact files) in memory. Uses soft maps to reduce heap pressure.
+ */
+public class InMemoryDependencyMetadataCache implements Stoppable {
+
+    public final static String TOGGLE_PROPERTY = "org.gradle.resolution.memorycache";
+
+    private final static Logger LOG = Logging.getLogger(InMemoryDependencyMetadataCache.class);
+
+    Map<String, DependencyMetadataCache> cachePerRepo = new MapMaker().makeMap();
+
+    final DependencyMetadataCacheStats stats = new DependencyMetadataCacheStats();
+
+    public LocalAwareModuleVersionRepository cached(LocalAwareModuleVersionRepository input) {
+        if ("false".equalsIgnoreCase(System.getProperty(TOGGLE_PROPERTY))) {
+            return input;
+        }
+
+        DependencyMetadataCache dataCache = cachePerRepo.get(input.getId());
+        stats.reposWrapped++;
+        if (dataCache == null) {
+            LOG.debug("Creating new in-memory cache for repo '{}' [{}].", input.getName(), input.getId());
+            dataCache = new DependencyMetadataCache(stats);
+            stats.cacheInstances++;
+            cachePerRepo.put(input.getId(), dataCache);
+        } else {
+            LOG.debug("Reusing in-memory cache for repo '{}' [{}].", input.getName(), input.getId());
+        }
+        return new CachedRepository(dataCache, input, stats);
+    }
+
+    public void stop() {
+        cachePerRepo.clear();
+        LOG.debug("In-memory dependency metadata cache closed. {}", stats);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractModuleDescriptorParser.java
new file mode 100644
index 0000000..398a413
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractModuleDescriptorParser.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+
+import java.io.File;
+
+public abstract class AbstractModuleDescriptorParser implements MetaDataParser {
+    public MutableModuleVersionMetaData parseMetaData(DescriptorParseContext ivySettings, File descriptorFile, boolean validate) throws MetaDataParseException {
+        LocallyAvailableResource localResource = new DefaultLocallyAvailableResource(descriptorFile);
+        LocallyAvailableExternalResource resource = new DefaultLocallyAvailableExternalResource(descriptorFile.toURI().toString(), localResource);
+        return parseDescriptor(ivySettings, resource, validate);
+    }
+
+    public MutableModuleVersionMetaData parseMetaData(DescriptorParseContext ivySettings, File descriptorFile) throws MetaDataParseException {
+        return parseMetaData(ivySettings, descriptorFile, true);
+    }
+
+    public MutableModuleVersionMetaData parseMetaData(DescriptorParseContext ivySettings, LocallyAvailableExternalResource resource) throws MetaDataParseException {
+        return parseDescriptor(ivySettings, resource, true);
+    }
+
+    protected MutableModuleVersionMetaData parseDescriptor(DescriptorParseContext ivySettings, LocallyAvailableExternalResource resource, boolean validate) throws MetaDataParseException {
+        try {
+            return doParseDescriptor(ivySettings, resource, validate);
+        } catch (MetaDataParseException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new MetaDataParseException(getTypeName(), resource, e);
+        }
+    }
+
+    protected abstract String getTypeName();
+
+    protected abstract MutableModuleVersionMetaData doParseDescriptor(DescriptorParseContext ivySettings, LocallyAvailableExternalResource resource, boolean validate) throws Exception;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java
new file mode 100644
index 0000000..adabd7d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+
+public interface DescriptorParseContext {
+    boolean artifactExists(ModuleVersionArtifactMetaData artifact);
+
+    LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, Class<? extends SoftwareArtifact> artifactType);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java
new file mode 100644
index 0000000..dc7dab8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+
+/**
+ * An implementation of {@link DescriptorParseContext} that is useful for parsing an ivy.xml file without attempting to download
+ * other resources from a DependencyResolver.
+ */
+public class DisconnectedDescriptorParseContext implements DescriptorParseContext {
+    public boolean artifactExists(ModuleVersionArtifactMetaData artifact) {
+        return false;
+    }
+
+    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, Class<? extends SoftwareArtifact> artifactType) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedIvyXmlModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedIvyXmlModuleDescriptorParser.java
new file mode 100644
index 0000000..c311610
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedIvyXmlModuleDescriptorParser.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Map;
+
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleRevisionId;
+
+public class DisconnectedIvyXmlModuleDescriptorParser extends IvyXmlModuleDescriptorParser {
+    public DisconnectedIvyXmlModuleDescriptorParser(ResolverStrategy resolverStrategy) {
+        super(resolverStrategy);
+    }
+
+    @Override
+    protected Parser createParser(DescriptorParseContext parseContext, LocallyAvailableExternalResource resource, Map<String, String> properties, ResolverStrategy resolverStrategy) throws MalformedURLException {
+        return new DisconnectedParser(parseContext, resource, resource.getLocalResource().getFile().toURI().toURL(), properties, resolverStrategy);
+    }
+
+    private static class DisconnectedParser extends Parser {
+        public DisconnectedParser(DescriptorParseContext parseContext, ExternalResource res, URL descriptorURL, Map<String, String> properties, ResolverStrategy resolverStrategy) {
+            super(parseContext, res, descriptorURL, properties, resolverStrategy);
+        }
+
+        @Override
+        public Parser newParser(ExternalResource res, URL descriptorURL) {
+            Parser parser = new DisconnectedParser(getParseContext(), res, descriptorURL, properties, resolverStrategy);
+            parser.setValidate(isValidate());
+            return parser;
+        }
+
+        @Override
+        protected ModuleDescriptor parseOtherIvyFile(String parentOrganisation,
+                                                     String parentModule, String parentRevision) throws IOException, ParseException, SAXException {
+            ModuleRevisionId parentMrid = createModuleRevisionId(parentOrganisation, parentModule, parentRevision);
+            return new DefaultModuleDescriptor(parentMrid, "release", new Date());
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedParserSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedParserSettings.java
deleted file mode 100644
index 67fc733..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedParserSettings.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
-
-import org.apache.ivy.core.RelativeUrlResolver;
-import org.apache.ivy.core.cache.ResolutionCacheManager;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.module.status.StatusManager;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.conflict.ConflictManager;
-import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.namespace.Namespace;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RestrictedDependencyResolver;
-
-import java.io.File;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.Map;
-
-/**
- * An implementation of {@link ParserSettings} that is useful for parsing an ivy.xml file without attempting to download
- * other resources from a DependencyResolver.
- */
-public class DisconnectedParserSettings implements ParserSettings {
-    private final IvySettings ivySettings = new IvySettings();
-
-    /**
-     * This implementation will not attempt to download any parent modules.
-     * TODO:DAZ Work out how to do the actual download if we want to do more validation when publishing.
-     */
-    public DependencyResolver getResolver(final ModuleRevisionId mRevId) {
-        return new RestrictedDependencyResolver() {
-            @Override
-            public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
-                return new ResolvedModuleRevision(null, null, new DefaultModuleDescriptor(mRevId, "release", new Date()), null);
-            }
-        };
-    }
-
-    public String substitute(String value) {
-        return ivySettings.substitute(value);
-    }
-
-    public PatternMatcher getMatcher(String matcherName) {
-        return ivySettings.getMatcher(matcherName);
-    }
-
-    public StatusManager getStatusManager() {
-        return ivySettings.getStatusManager();
-    }
-
-    public ConflictManager getConflictManager(String name) {
-        return ivySettings.getConflictManager(name);
-    }
-
-    public Namespace getNamespace(String namespace) {
-        return ivySettings.getNamespace(namespace);
-    }
-
-    public Namespace getContextNamespace() {
-        return ivySettings.getContextNamespace();
-    }
-
-    // The reset of the methods are not used when parsing an ivy.xml
-    public Map substitute(Map strings) {
-        throw unsupported();
-    }
-
-    public ResolutionCacheManager getResolutionCacheManager() {
-        throw unsupported();
-    }
-
-    public RelativeUrlResolver getRelativeUrlResolver() {
-        throw unsupported();
-    }
-
-    public File resolveFile(String filename) {
-        throw unsupported();
-    }
-
-    public String getDefaultBranch(ModuleId moduleId) {
-        throw unsupported();
-    }
-
-    private UnsupportedOperationException unsupported() {
-        return new UnsupportedOperationException();
-    }
-
-}
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 f1758d3..31cddd7 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
@@ -17,18 +17,15 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.repository.Resource;
-
-import java.io.IOException;
-import java.net.URL;
-import java.text.ParseException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
 
 public class DownloadedIvyModuleDescriptorParser extends IvyXmlModuleDescriptorParser {
+    public DownloadedIvyModuleDescriptorParser(ResolverStrategy resolverStrategy) {
+        super(resolverStrategy);
+    }
+
     @Override
-    public DefaultModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
-        DefaultModuleDescriptor descriptor = super.parseDescriptor(ivySettings, xmlURL, res, validate);
-        descriptor.setDefault(false);
-        return descriptor;
+    protected void postProcess(DefaultModuleDescriptor moduleDescriptor) {
+        moduleDescriptor.setDefault(false);
     }
 }
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 f7b3517..6a253ce 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
@@ -16,7 +16,6 @@
 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.*;
 import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
 import org.apache.ivy.core.module.id.ArtifactId;
@@ -24,18 +23,20 @@ import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.parser.m2.DefaultPomDependencyMgt;
-import org.apache.ivy.plugins.parser.m2.PomDependencyMgt;
-import org.apache.ivy.plugins.parser.m2.PomReader.PomDependencyData;
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.util.Message;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.PomReader.PomDependencyData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomDependencyMgt;
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.util.DeprecationLogger;
 
 import java.util.*;
-import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 
 /**
@@ -43,9 +44,6 @@ import java.util.Map.Entry;
  * classifiers)
  */
 public class GradlePomModuleDescriptorBuilder {
-
-    private static final int DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT = 4;
-
     public static final Configuration[] MAVEN2_CONFIGURATIONS = new Configuration[]{
             new Configuration("default", Visibility.PUBLIC,
                     "runtime dependencies and master artifact can be used with this conf",
@@ -90,10 +88,8 @@ public class GradlePomModuleDescriptorBuilder {
 
     static final Map<String, ConfMapper> MAVEN2_CONF_MAPPING = new HashMap<String, ConfMapper>();
 
-    private static final String DEPENDENCY_MANAGEMENT = "m:dependency.management";
-    private static final String PROPERTIES = "m:properties";
-    private static final String EXTRA_INFO_DELIMITER = "__";
     private static final Collection<String> JAR_PACKAGINGS = Arrays.asList("ejb", "bundle", "maven-plugin", "eclipse-plugin");
+    private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("(.+)-\\d{8}\\.\\d{6}-\\d+");
 
     static interface ConfMapper {
         public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional);
@@ -164,15 +160,11 @@ public class GradlePomModuleDescriptorBuilder {
 
     private ModuleRevisionId mrid;
 
-    private ParserSettings parserSettings;
-
-    private static final String WRONG_NUMBER_OF_PARTS_MSG = "what seemed to be a dependency "
-            + "management extra info exclusion had the wrong number of parts (should have 2) ";
+    private DescriptorParseContext parserSettings;
+    private final PomReader pomReader;
 
-
-    public GradlePomModuleDescriptorBuilder(
-            ModuleDescriptorParser parser, Resource res, ParserSettings ivySettings) {
-        ivyModuleDescriptor = new DefaultModuleDescriptor(parser, res);
+    public GradlePomModuleDescriptorBuilder(ExternalResource res, DescriptorParseContext ivySettings, PomReader pomReader) {
+        ivyModuleDescriptor = new DefaultModuleDescriptor(XmlModuleDescriptorParser.getInstance(), null);
         ivyModuleDescriptor.setResolvedPublicationDate(new Date(res.getLastModified()));
         for (Configuration maven2Configuration : MAVEN2_CONFIGURATIONS) {
             ivyModuleDescriptor.addConfiguration(maven2Configuration);
@@ -180,17 +172,26 @@ public class GradlePomModuleDescriptorBuilder {
         ivyModuleDescriptor.setMappingOverride(true);
         ivyModuleDescriptor.addExtraAttributeNamespace("m", Ivy.getIvyHomeURL() + "maven");
         parserSettings = ivySettings;
+        this.pomReader = pomReader;
     }
 
-    public ModuleDescriptor getModuleDescriptor() {
+    public DefaultModuleDescriptor getModuleDescriptor() {
         return ivyModuleDescriptor;
     }
 
-    public void setModuleRevId(ModuleRevisionId mrid, String group, String module, String version) {
-        this.mrid = mrid;
-        ivyModuleDescriptor.setModuleRevisionId(ModuleRevisionId.newInstance(group, module, version));
+    public void setModuleRevId(String group, String module, String version) {
+        String effectiveVersion = version;
+        if (version != null) {
+            Matcher matcher = TIMESTAMP_PATTERN.matcher(version);
+            if (matcher.matches()) {
+                effectiveVersion = matcher.group(1) + "-SNAPSHOT";
+            }
+        }
 
-        if ((version == null) || version.endsWith("SNAPSHOT")) {
+        this.mrid = ModuleRevisionId.newInstance(group, module, effectiveVersion);
+        ivyModuleDescriptor.setModuleRevisionId(mrid);
+
+        if (effectiveVersion != null && effectiveVersion.endsWith("SNAPSHOT")) {
             ivyModuleDescriptor.setStatus("integration");
         } else {
             ivyModuleDescriptor.setStatus("release");
@@ -205,7 +206,7 @@ public class GradlePomModuleDescriptorBuilder {
         ivyModuleDescriptor.setDescription(description);
     }
 
-    public void setLicenses(License[] licenses) {
+    public void setLicenses(Iterable<License> licenses) {
         for (License license : licenses) {
             ivyModuleDescriptor.addLicense(license);
         }
@@ -213,34 +214,18 @@ public class GradlePomModuleDescriptorBuilder {
 
     public void addMainArtifact(String artifactId, String packaging) {
         if ("pom".equals(packaging)) {
-            // no artifact defined! Add the default artifact only if it exists
-            DependencyResolver resolver = parserSettings.getResolver(mrid);
-
-            if (resolver != null) {
-                DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, "jar", "jar");
-
-                if (!ArtifactOrigin.isUnknown(resolver.locate(artifact))) {
-                    ivyModuleDescriptor.addArtifact("master", artifact);
-                }
-            }
-
             return;
         }
 
         if (!isKnownJarPackaging(packaging)) {
-            // Look for an artifact with extension = packaging. This is deprecated.
-            DependencyResolver resolver = parserSettings.getResolver(mrid);
-
-            if (resolver != null) {
-                DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, packaging);
-
-                if (!ArtifactOrigin.isUnknown(resolver.locate(artifact))) {
-                    ivyModuleDescriptor.addArtifact("master", artifact);
-
-                    DeprecationLogger.nagUserOfDeprecated("Relying on packaging to define the extension of the main artifact");
-
-                    return;
-                }
+            ModuleComponentIdentifier componentIdentifier = DefaultModuleComponentIdentifier.newId(mrid.getOrganisation(), mrid.getName(), mrid.getRevision());
+            DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, packaging);
+            ModuleVersionArtifactMetaData artifactMetaData = new DefaultModuleVersionArtifactMetaData(componentIdentifier, artifact);
+            if (parserSettings.artifactExists(artifactMetaData)) {
+                ivyModuleDescriptor.addArtifact("master", artifact);
+
+                DeprecationLogger.nagUserOfDeprecated("Relying on packaging to define the extension of the main artifact");
+                return;
             }
         }
 
@@ -258,9 +243,8 @@ public class GradlePomModuleDescriptorBuilder {
             scope = "compile";
         }
 
-        String version = dep.getVersion();
-        version = (version == null || version.length() == 0) ? getDefaultVersion(dep) : version;
-        ModuleRevisionId moduleRevId = ModuleRevisionId.newInstance(dep.getGroupId(), dep.getArtifactId(), version);
+        String version = determineVersion(dep);
+        ModuleRevisionId moduleRevId = IvyUtil.createModuleRevisionId(dep.getGroupId(), dep.getArtifactId(), version);
 
         // Some POMs depend on themselves, don't add this dependency: Ivy doesn't allow this!
         // Example: http://repo2.maven.org/maven2/net/jini/jsk-platform/2.1/jsk-platform-2.1.pom
@@ -310,7 +294,7 @@ public class GradlePomModuleDescriptorBuilder {
         // is present, but empty.
         List /*<ModuleId>*/ excluded = dep.getExcludedModules();
         if (excluded.isEmpty()) {
-            excluded = getDependencyMgtExclusions(ivyModuleDescriptor, dep.getGroupId(), dep.getArtifactId());
+            excluded = getDependencyMgtExclusions(dep);
         }
         for (Object anExcluded : excluded) {
             ModuleId excludedModule = (ModuleId) anExcluded;
@@ -327,8 +311,26 @@ public class GradlePomModuleDescriptorBuilder {
         ivyModuleDescriptor.addDependency(dd);
     }
 
+    /**
+     * Determines the version of a dependency. Uses the specified version if declared for the as coordinate. If the version is not declared, try to resolve it from the dependency management section.
+     * In case the version cannot be resolved with any of these methods, throw an exception of type {@see org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.UnresolvedDependencyVersionException}.
+     *
+     * @param dependency Dependency
+     * @return Resolved dependency version
+     */
+    private String determineVersion(PomDependencyData dependency) {
+        String version = dependency.getVersion();
+        version = (version == null || version.length() == 0) ? getDefaultVersion(dependency) : version;
+
+        if (version == null) {
+            throw new UnresolvedDependencyVersionException(dependency.getId());
+        }
+
+        return version;
+    }
+
     public void addDependency(DependencyDescriptor descriptor) {
-        // Some POMs depend on theirselfves through their parent pom, don't add this dependency
+        // Some POMs depend on themselves through their parent POM, don't add this dependency
         // since Ivy doesn't allow this!
         // Example: http://repo2.maven.org/maven2/com/atomikos/atomikos-util/3.6.4/atomikos-util-3.6.4.pom
         ModuleId dependencyId = descriptor.getDependencyId();
@@ -340,207 +342,36 @@ public class GradlePomModuleDescriptorBuilder {
         ivyModuleDescriptor.addDependency(descriptor);
     }
 
-
-    public void addDependencyMgt(PomDependencyMgt dep) {
-        String key = getDependencyMgtExtraInfoKeyForVersion(dep.getGroupId(), dep.getArtifactId());
-        ivyModuleDescriptor.addExtraInfo(key, dep.getVersion());
-        if (dep.getScope() != null) {
-            String scopeKey = getDependencyMgtExtraInfoKeyForScope(dep.getGroupId(), dep.getArtifactId());
-            ivyModuleDescriptor.addExtraInfo(scopeKey, dep.getScope());
-        }
-        if (!dep.getExcludedModules().isEmpty()) {
-            final String exclusionPrefix = getDependencyMgtExtraInfoPrefixForExclusion(dep.getGroupId(), dep.getArtifactId());
-            int index = 0;
-            for (Object o : dep.getExcludedModules()) {
-                final ModuleId excludedModule = (ModuleId) o;
-                ivyModuleDescriptor.addExtraInfo(exclusionPrefix + index,
-                        excludedModule.getOrganisation() + EXTRA_INFO_DELIMITER + excludedModule.getName());
-                index += 1;
-            }
-        }
-        // dependency management info is also used for version mediation of transitive dependencies
-        ivyModuleDescriptor.addDependencyDescriptorMediator(
-                ModuleId.newInstance(dep.getGroupId(), dep.getArtifactId()),
-                ExactPatternMatcher.INSTANCE,
-                new OverrideDependencyDescriptorMediator(null, dep.getVersion()));
-    }
-
-    public void addPlugin(PomDependencyMgt plugin) {
-        String pluginValue = plugin.getGroupId() + EXTRA_INFO_DELIMITER + plugin.getArtifactId()
-                + EXTRA_INFO_DELIMITER + plugin.getVersion();
-        String pluginExtraInfo = (String) ivyModuleDescriptor.getExtraInfo().get("m:maven.plugins");
-        if (pluginExtraInfo == null) {
-            pluginExtraInfo = pluginValue;
-        } else {
-            pluginExtraInfo = pluginExtraInfo + "|" + pluginValue;
-        }
-        ivyModuleDescriptor.getExtraInfo().put("m:maven.plugins", pluginExtraInfo);
-    }
-
-    public static List<PomDependencyMgt> getPlugins(ModuleDescriptor md) {
-        List<PomDependencyMgt> result = new ArrayList<PomDependencyMgt>();
-        String plugins = (String) md.getExtraInfo().get("m:maven.plugins");
-        if (plugins == null) {
-            return new ArrayList<PomDependencyMgt>();
-        }
-        String[] pluginsArray = plugins.split("\\|");
-        for (String plugin : pluginsArray) {
-            String[] parts = plugin.split(EXTRA_INFO_DELIMITER);
-            result.add(new PomPluginElement(parts[0], parts[1], parts[2]));
-        }
-
-        return result;
-    }
-
-    private static class PomPluginElement implements PomDependencyMgt {
-        private String groupId;
-        private String artifactId;
-        private String version;
-
-        public PomPluginElement(String groupId, String artifactId, String version) {
-            this.groupId = groupId;
-            this.artifactId = artifactId;
-            this.version = version;
-        }
-
-        public String getGroupId() {
-            return groupId;
-        }
-
-        public String getArtifactId() {
-            return artifactId;
-        }
-
-        public String getVersion() {
-            return version;
-        }
-
-        public String getScope() {
-            return null;
-        }
-
-        public List /*<ModuleId>*/ getExcludedModules() {
-            return Collections.EMPTY_LIST; // probably not used?
-        }
-    }
-
     private String getDefaultVersion(PomDependencyData dep) {
-        String key = getDependencyMgtExtraInfoKeyForVersion(dep.getGroupId(), dep.getArtifactId());
-        return (String) ivyModuleDescriptor.getExtraInfo().get(key);
+        PomDependencyMgt pomDependencyMgt = findDependencyDefault(dep);
+        if (pomDependencyMgt != null) {
+            return pomDependencyMgt.getVersion();
+        }
+        return null;
     }
 
     private String getDefaultScope(PomDependencyData dep) {
-        String key = getDependencyMgtExtraInfoKeyForScope(dep.getGroupId(), dep.getArtifactId());
-        String result = (String) ivyModuleDescriptor.getExtraInfo().get(key);
+        PomDependencyMgt pomDependencyMgt = findDependencyDefault(dep);
+        String result = null;
+        if (pomDependencyMgt != null) {
+            result = pomDependencyMgt.getScope();
+        }
         if ((result == null) || !MAVEN2_CONF_MAPPING.containsKey(result)) {
             result = "compile";
         }
         return result;
     }
 
-    private static String getDependencyMgtExtraInfoKeyForVersion(
-            String groupId, String artifaceId) {
-        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId
-                + EXTRA_INFO_DELIMITER + artifaceId + EXTRA_INFO_DELIMITER + "version";
-    }
-
-    private static String getDependencyMgtExtraInfoKeyForScope(String groupId, String artifaceId) {
-        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId
-                + EXTRA_INFO_DELIMITER + artifaceId + EXTRA_INFO_DELIMITER + "scope";
-    }
-
-    private static String getPropertyExtraInfoKey(String propertyName) {
-        return PROPERTIES + EXTRA_INFO_DELIMITER + propertyName;
-    }
-
-    private static String getDependencyMgtExtraInfoPrefixForExclusion(
-            String groupId, String artifaceId) {
-        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId
-                + EXTRA_INFO_DELIMITER + artifaceId + EXTRA_INFO_DELIMITER + "exclusion_";
-    }
-
-    private static List<ModuleId> getDependencyMgtExclusions(ModuleDescriptor descriptor, String groupId, String artifactId) {
-        String exclusionPrefix = getDependencyMgtExtraInfoPrefixForExclusion(groupId, artifactId);
-        List<ModuleId> exclusionIds = new LinkedList<ModuleId>();
-        Map<String, String> extras = descriptor.getExtraInfo();
-        for (Entry<String, String> entry : extras.entrySet()) {
-            String key = entry.getKey();
-            if (key.startsWith(exclusionPrefix)) {
-                String fullExclusion = entry.getValue();
-                String[] exclusionParts = fullExclusion.split(EXTRA_INFO_DELIMITER);
-                if (exclusionParts.length != 2) {
-                    Message.error(WRONG_NUMBER_OF_PARTS_MSG + exclusionParts.length + " : "
-                            + fullExclusion);
-                    continue;
-                }
-                exclusionIds.add(ModuleId.newInstance(exclusionParts[0], exclusionParts[1]));
-            }
-        }
-
-        return exclusionIds;
-    }
-
-    public static List getDependencyManagements(ModuleDescriptor md) {
-        List result = new ArrayList();
-
-        for (Iterator iterator = md.getExtraInfo().entrySet().iterator(); iterator.hasNext();) {
-            Entry entry = (Entry) iterator.next();
-            String key = (String) entry.getKey();
-            if (key.startsWith(DEPENDENCY_MANAGEMENT)) {
-                String[] parts = key.split(EXTRA_INFO_DELIMITER);
-                if (parts.length != DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT) {
-                    Message.warn("what seem to be a dependency management extra info doesn't match expected pattern: " + key);
-                } else {
-                    String versionKey = DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + parts[1]
-                            + EXTRA_INFO_DELIMITER + parts[2]
-                            + EXTRA_INFO_DELIMITER + "version";
-                    String scopeKey = DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + parts[1]
-                            + EXTRA_INFO_DELIMITER + parts[2]
-                            + EXTRA_INFO_DELIMITER + "scope";
-
-                    String version = (String) md.getExtraInfo().get(versionKey);
-                    String scope = (String) md.getExtraInfo().get(scopeKey);
-
-                    List<ModuleId> exclusions = getDependencyMgtExclusions(md, parts[1], parts[2]);
-                    result.add(new DefaultPomDependencyMgt(parts[1], parts[2], version, scope, exclusions));
-                }
-            }
-        }
-
-        return result;
-    }
-
-
-    public void addExtraInfos(Map extraAttributes) {
-        for (Iterator it = extraAttributes.entrySet().iterator(); it.hasNext();) {
-            Entry entry = (Entry) it.next();
-            String key = (String) entry.getKey();
-            String value = (String) entry.getValue();
-            addExtraInfo(key, value);
-        }
-    }
-
-    private void addExtraInfo(String key, String value) {
-        if (!ivyModuleDescriptor.getExtraInfo().containsKey(key)) {
-            ivyModuleDescriptor.addExtraInfo(key, value);
+    private List<ModuleId> getDependencyMgtExclusions(PomDependencyData dep) {
+        PomDependencyMgt pomDependencyMgt = findDependencyDefault(dep);
+        if (pomDependencyMgt != null) {
+            return pomDependencyMgt.getExcludedModules();
         }
-    }
 
-    public static Map extractPomProperties(Map extraInfo) {
-        Map r = new HashMap();
-        for (Iterator it = extraInfo.entrySet().iterator(); it.hasNext();) {
-            Entry extraInfoEntry = (Entry) it.next();
-            if (((String) extraInfoEntry.getKey()).startsWith(PROPERTIES)) {
-                String prop = ((String) extraInfoEntry.getKey()).substring(PROPERTIES.length()
-                        + EXTRA_INFO_DELIMITER.length());
-                r.put(prop, extraInfoEntry.getValue());
-            }
-        }
-        return r;
+        return Collections.emptyList();
     }
 
-
-    public void addProperty(String propertyName, String value) {
-        addExtraInfo(getPropertyExtraInfoKey(propertyName), value);
+    private PomDependencyMgt findDependencyDefault(PomDependencyData dependency) {
+        return pomReader.findDependencyDefaults(dependency.getId());
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
index e9209fd..a1d0b1d 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
@@ -15,282 +15,202 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
-import org.apache.ivy.core.IvyContext;
-import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.descriptor.Configuration;
 import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolveEngine;
-import org.apache.ivy.core.resolve.ResolveOptions;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.namespace.NameSpaceHelper;
-import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.parser.m2.PomDependencyMgt;
-import org.apache.ivy.plugins.parser.m2.PomReader;
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.repository.url.URLResource;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.util.Message;
-import org.gradle.internal.UncheckedException;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.PomReader.PomDependencyData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomDependencyMgt;
+import org.gradle.api.internal.artifacts.metadata.ModuleDescriptorAdapter;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.resolution.MavenPomArtifact;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
 
-import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.net.URL;
 import java.text.ParseException;
-import java.util.Date;
-import java.util.List;
+import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
  * This a straight copy of org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser, with one change: we do NOT attempt to retrieve source and javadoc artifacts when parsing the POM. This cuts the
  * number of remote call in half to resolve a module.
  */
-public final class GradlePomModuleDescriptorParser implements ModuleDescriptorParser {
-    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
-            throws ParseException, IOException {
-        throw new UnsupportedOperationException();
-    }
+public final class GradlePomModuleDescriptorParser extends AbstractModuleDescriptorParser {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GradlePomModuleDescriptorParser.class);
+    private static final String DEPENDENCY_IMPORT_SCOPE = "import";
 
-    public boolean accept(Resource res) {
-        return res.getName().endsWith(".pom") || res.getName().endsWith("pom.xml")
-                || res.getName().endsWith("project.xml");
+    @Override
+    protected String getTypeName() {
+        return "POM";
     }
 
     public String toString() {
         return "gradle pom parser";
     }
 
-    public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) {
-        return DefaultArtifact.newPomArtifact(mrid, new Date(res.getLastModified()));
-    }
-
-    public String getType() {
-        return "pom";
-    }
-
-    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
-                                            boolean validate) throws ParseException, IOException {
-        URLResource resource = new URLResource(descriptorURL);
-        return parseDescriptor(ivySettings, descriptorURL, resource, validate);
-    }
-
-    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
-                                            Resource resource, boolean validate) throws ParseException, IOException {
+    protected MutableModuleVersionMetaData doParseDescriptor(DescriptorParseContext parserSettings, LocallyAvailableExternalResource resource, boolean validate) throws IOException, ParseException, SAXException {
+        PomReader pomReader = new PomReader(resource);
+        GradlePomModuleDescriptorBuilder mdBuilder = new GradlePomModuleDescriptorBuilder(resource, parserSettings, pomReader);
 
-        Resource res = encodedUrlResource(resource, descriptorURL);
-        GradlePomModuleDescriptorBuilder mdBuilder = new GradlePomModuleDescriptorBuilder(this, res, ivySettings);
+        doParsePom(parserSettings, mdBuilder, pomReader);
 
-        try {
-            PomReader domReader = new PomReader(descriptorURL, res);
-            domReader.setProperty("parent.version", domReader.getParentVersion());
-            domReader.setProperty("parent.groupId", domReader.getParentGroupId());
-            domReader.setProperty("project.parent.version", domReader.getParentVersion());
-            domReader.setProperty("project.parent.groupId", domReader.getParentGroupId());
+        String artifactId = pomReader.getArtifactId();
+        if (pomReader.getRelocation() == null) {
+            mdBuilder.addMainArtifact(artifactId, pomReader.getPackaging());
+        }
 
-            Map pomProperties = domReader.getPomProperties();
-            for (Object o : pomProperties.entrySet()) {
-                Map.Entry prop = (Map.Entry) o;
-                domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
-                mdBuilder.addProperty((String) prop.getKey(), (String) prop.getValue());
-            }
+        DefaultModuleDescriptor moduleDescriptor = mdBuilder.getModuleDescriptor();
+        ModuleDescriptorAdapter adapter = new ModuleDescriptorAdapter(moduleDescriptor);
+        if ("pom".equals(pomReader.getPackaging())) {
+            adapter.setMetaDataOnly(true);
+        }
+        return adapter;
+    }
 
-            ModuleDescriptor parentDescr = null;
-            if (domReader.hasParent()) {
-                //Is there any other parent properties?
+    private void doParsePom(DescriptorParseContext parserSettings, GradlePomModuleDescriptorBuilder mdBuilder, PomReader pomReader) throws IOException, SAXException {
+        if (pomReader.hasParent()) {
+            //Is there any other parent properties?
 
-                ModuleRevisionId parentModRevID = ModuleRevisionId.newInstance(
-                        domReader.getParentGroupId(),
-                        domReader.getParentArtifactId(),
-                        domReader.getParentVersion());
-                ResolvedModuleRevision parentModule = parseOtherPom(ivySettings, parentModRevID);
-                if (parentModule != null) {
-                    parentDescr = parentModule.getDescriptor();
-                } else {
-                    throw new IOException("Impossible to load parent for " + res.getName() + ". Parent=" + parentModRevID);
-                }
-                if (parentDescr != null) {
-                    Map parentPomProps = GradlePomModuleDescriptorBuilder.extractPomProperties(parentDescr.getExtraInfo());
-                    for (Object o : parentPomProps.entrySet()) {
-                        Map.Entry prop = (Map.Entry) o;
-                        domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
-                    }
+            ModuleVersionIdentifier parentId = DefaultModuleVersionIdentifier.newId(
+                    pomReader.getParentGroupId(),
+                    pomReader.getParentArtifactId(),
+                    pomReader.getParentVersion());
+            PomReader parentPomReader = parseOtherPom(parserSettings, parentId);
+            pomReader.setPomParent(parentPomReader);
+        }
+        pomReader.resolveGAV();
+
+        String groupId = pomReader.getGroupId();
+        String artifactId = pomReader.getArtifactId();
+        String version = pomReader.getVersion();
+        mdBuilder.setModuleRevId(groupId, artifactId, version);
+
+        mdBuilder.setHomePage(pomReader.getHomePage());
+        mdBuilder.setDescription(pomReader.getDescription());
+        mdBuilder.setLicenses(pomReader.getLicenses());
+
+        ModuleRevisionId relocation = pomReader.getRelocation();
+
+        if (relocation != null) {
+            if (groupId != null && artifactId != null
+                    && artifactId.equals(relocation.getName())
+                    && groupId.equals(relocation.getOrganisation())) {
+                LOGGER.error("POM relocation to an other version number is not fully supported in Gradle : {} relocated to {}.",
+                        mdBuilder.getModuleDescriptor().getModuleRevisionId(), relocation);
+                LOGGER.warn("Please update your dependency to directly use the correct version '{}'.", relocation);
+                LOGGER.warn("Resolution will only pick dependencies of the relocated element.  Artifacts and other metadata will be ignored.");
+                PomReader relocatedModule = parseOtherPom(parserSettings, DefaultModuleVersionIdentifier.newId(relocation));
+
+                Collection<PomDependencyData> pomDependencyDataList = relocatedModule.getDependencies().values();
+                for(PomDependencyData pomDependencyData : pomDependencyDataList) {
+                    mdBuilder.addDependency(pomDependencyData);
                 }
-            }
-
-            String groupId = domReader.getGroupId();
-            String artifactId = domReader.getArtifactId();
-            String version = domReader.getVersion();
-            ModuleScopedParserSettings scopedSettings = (ModuleScopedParserSettings) ivySettings;
-            mdBuilder.setModuleRevId(scopedSettings.getCurrentRevisionId(), groupId, artifactId, version);
 
-            mdBuilder.setHomePage(domReader.getHomePage());
-            mdBuilder.setDescription(domReader.getDescription());
-            mdBuilder.setLicenses(domReader.getLicenses());
-
-            ModuleRevisionId relocation = domReader.getRelocation();
-
-            if (relocation != null) {
-                if (groupId != null && artifactId != null
-                        && artifactId.equals(relocation.getName())
-                        && groupId.equals(relocation.getOrganisation())) {
-                    Message.error("Relocation to an other version number not supported in ivy : "
-                            + mdBuilder.getModuleDescriptor().getModuleRevisionId()
-                            + " relocated to " + relocation
-                            + ". Please update your dependency to directly use the right version.");
-                    Message.warn("Resolution will only pick dependencies of the relocated element."
-                            + "  Artefact and other metadata will be ignored.");
-                    ResolvedModuleRevision relocatedModule = parseOtherPom(ivySettings, relocation);
-                    if (relocatedModule == null) {
-                        throw new ParseException("impossible to load module "
-                                + relocation + " to which "
-                                + mdBuilder.getModuleDescriptor().getModuleRevisionId()
-                                + " has been relocated", 0);
-                    }
-                    DependencyDescriptor[] dds = relocatedModule.getDescriptor().getDependencies();
-                    for (DependencyDescriptor dd : dds) {
-                        mdBuilder.addDependency(dd);
-                    }
-                } else {
-                    Message.info(mdBuilder.getModuleDescriptor().getModuleRevisionId()
-                            + " is relocated to " + relocation
-                            + ". Please update your dependencies.");
-                    Message.verbose("Relocated module will be considered as a dependency");
-                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mdBuilder.getModuleDescriptor(), relocation, true, false, true);
-                    /* Map all public dependencies */
-                    Configuration[] m2Confs = GradlePomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS;
-                    for (Configuration m2Conf : m2Confs) {
-                        if (Visibility.PUBLIC.equals(m2Conf.getVisibility())) {
-                            dd.addDependencyConfiguration(m2Conf.getName(), m2Conf.getName());
-                        }
-                    }
-                    mdBuilder.addDependency(dd);
-                }
             } else {
-                domReader.setProperty("project.groupId", groupId);
-                domReader.setProperty("pom.groupId", groupId);
-                domReader.setProperty("groupId", groupId);
-                domReader.setProperty("project.artifactId", artifactId);
-                domReader.setProperty("pom.artifactId", artifactId);
-                domReader.setProperty("artifactId", artifactId);
-                domReader.setProperty("project.version", version);
-                domReader.setProperty("pom.version", version);
-                domReader.setProperty("version", version);
-
-                if (parentDescr != null) {
-                    mdBuilder.addExtraInfos(parentDescr.getExtraInfo());
-
-                    // add dependency management info from parent
-                    List depMgt = GradlePomModuleDescriptorBuilder.getDependencyManagements(parentDescr);
-                    for (Object aDepMgt : depMgt) {
-                        mdBuilder.addDependencyMgt((PomDependencyMgt) aDepMgt);
+                LOGGER.info(mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                        + " is relocated to " + relocation
+                        + ". Please update your dependencies.");
+                LOGGER.debug("Relocated module will be considered as a dependency");
+                DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mdBuilder.getModuleDescriptor(), relocation, true, false, true);
+                /* Map all public dependencies */
+                Configuration[] m2Confs = GradlePomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS;
+                for (Configuration m2Conf : m2Confs) {
+                    if (Visibility.PUBLIC.equals(m2Conf.getVisibility())) {
+                        dd.addDependencyConfiguration(m2Conf.getName(), m2Conf.getName());
                     }
-
-                    // add plugins from parent
-                    List /*<PomDependencyMgt>*/ plugins = GradlePomModuleDescriptorBuilder.getPlugins(parentDescr);
-                    for (Object plugin : plugins) {
-                        mdBuilder.addPlugin((PomDependencyMgt) plugin);
-                    }
-                }
-
-                for (Object o : domReader.getDependencyMgt()) {
-                    PomDependencyMgt dep = (PomDependencyMgt) o;
-                    if ("import".equals(dep.getScope())) {
-                        ModuleRevisionId importModRevID = ModuleRevisionId.newInstance(
-                                dep.getGroupId(),
-                                dep.getArtifactId(),
-                                dep.getVersion());
-                        ResolvedModuleRevision importModule = parseOtherPom(ivySettings, importModRevID);
-                        if (importModule != null) {
-                            ModuleDescriptor importDescr = importModule.getDescriptor();
-
-                            // add dependency management info from imported module
-                            List depMgt = GradlePomModuleDescriptorBuilder.getDependencyManagements(importDescr);
-                            for (Object aDepMgt : depMgt) {
-                                mdBuilder.addDependencyMgt((PomDependencyMgt) aDepMgt);
-                            }
-                        } else {
-                            throw new IOException("Impossible to import module for " + res.getName() + "."
-                                    + " Import=" + importModRevID);
-                        }
-
-                    } else {
-                        mdBuilder.addDependencyMgt(dep);
-                    }
-                }
-
-                for (Object o : domReader.getDependencies()) {
-                    PomReader.PomDependencyData dep = (PomReader.PomDependencyData) o;
-                    mdBuilder.addDependency(dep);
-                }
-
-                if (parentDescr != null) {
-                    for (int i = 0; i < parentDescr.getDependencies().length; i++) {
-                        mdBuilder.addDependency(parentDescr.getDependencies()[i]);
-                    }
-                }
-
-                for (Object o : domReader.getPlugins()) {
-                    PomReader.PomPluginElement plugin = (PomReader.PomPluginElement) o;
-                    mdBuilder.addPlugin(plugin);
                 }
+                mdBuilder.addDependency(dd);
+            }
+        } else {
+            overrideDependencyMgtsWithImported(parserSettings, pomReader);
 
-                mdBuilder.addMainArtifact(artifactId, domReader.getPackaging());
+            for (PomDependencyData dependency : pomReader.getDependencies().values()) {
+                mdBuilder.addDependency(dependency);
             }
-        } catch (SAXException e) {
-            throw newParserException(e);
         }
+    }
 
-        return mdBuilder.getModuleDescriptor();
+    /**
+     * Overrides existing dependency management information with imported ones if existing.
+     *
+     * @param parseContext Parse context
+     * @param pomReader POM reader
+     * @throws IOException
+     * @throws SAXException
+     */
+    private void overrideDependencyMgtsWithImported(DescriptorParseContext parseContext, PomReader pomReader) throws IOException, SAXException {
+        Map<MavenDependencyKey, PomDependencyMgt> importedDependencyMgts = parseImportedDependencyMgts(parseContext, pomReader.parseDependencyMgt());
+        pomReader.addImportedDependencyMgts(importedDependencyMgts);
     }
 
-    private ResolvedModuleRevision parseOtherPom(ParserSettings ivySettings,
-                                                 ModuleRevisionId parentModRevID) throws ParseException {
-        DependencyDescriptor dd = new DefaultDependencyDescriptor(parentModRevID, true);
-        ResolveData data = IvyContext.getContext().getResolveData();
-        if (data == null) {
-            ResolveEngine engine = IvyContext.getContext().getIvy().getResolveEngine();
-            ResolveOptions options = new ResolveOptions();
-            options.setDownload(false);
-            data = new ResolveData(engine, options);
+    /**
+     * Parses imported dependency management information.
+     *
+     * @param parseContext Parse context
+     * @param currentDependencyMgts Current dependency management information
+     * @return Imported dependency management information
+     * @throws IOException
+     * @throws SAXException
+     */
+    private Map<MavenDependencyKey, PomDependencyMgt> parseImportedDependencyMgts(DescriptorParseContext parseContext, Collection<PomDependencyMgt> currentDependencyMgts) throws IOException, SAXException {
+        Map<MavenDependencyKey, PomDependencyMgt> importedDependencyMgts = new LinkedHashMap<MavenDependencyKey, PomDependencyMgt>();
+
+        for(PomDependencyMgt currentDependencyMgt : currentDependencyMgts) {
+            if(isDependencyImportScoped(currentDependencyMgt)) {
+                PomReader importDescr = parseImportedPom(parseContext, currentDependencyMgt);
+                importedDependencyMgts.putAll(importDescr.getDependencyMgt());
+            }
         }
 
-        DependencyResolver resolver = ivySettings.getResolver(parentModRevID);
-        if (resolver == null) {
-            // TODO: Throw exception here?
-            return null;
-        } else {
-            dd = NameSpaceHelper.toSystem(dd, ivySettings.getContextNamespace());
-            return resolver.getDependency(dd, data);
-        }
+        return importedDependencyMgts;
     }
 
-    private ParseException newParserException(Exception e) {
-        Message.error(e.getMessage());
-        ParseException pe = new ParseException(e.getMessage(), 0);
-        pe.initCause(e);
-        return pe;
+    /**
+     * Checks if dependency has scope "import".
+     *
+     * @param dependencyMgt Dependency management element
+     * @return Flag
+     */
+    private boolean isDependencyImportScoped(PomDependencyMgt dependencyMgt) {
+        return DEPENDENCY_IMPORT_SCOPE.equals(dependencyMgt.getScope());
     }
 
-    private Resource encodedUrlResource(final Resource base, final URL url) {
-        Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Resource.class}, new InvocationHandler() {
-            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-                if ("getName".equals(method.getName())) {
-                    return url.toString();
-                }
-                try {
-                    return method.invoke(base, args);
-                } catch (InvocationTargetException e) {
-                    throw UncheckedException.throwAsUncheckedException(e.getTargetException());
-                }
-            }
-        });
-        return (Resource) proxy;
+    /**
+     * Parses imported POM.
+     *
+     * @param parseContext Parse context
+     * @param pomDependencyMgt Dependency management information
+     * @return POM reader
+     * @throws IOException
+     * @throws SAXException
+     */
+    private PomReader parseImportedPom(DescriptorParseContext parseContext, PomDependencyMgt pomDependencyMgt) throws IOException, SAXException {
+        ModuleVersionIdentifier importedId = DefaultModuleVersionIdentifier.newId(pomDependencyMgt.getGroupId(), pomDependencyMgt.getArtifactId(), pomDependencyMgt.getVersion());
+        return parseOtherPom(parseContext, importedId);
+    }
+
+    /**
+     * Parses other POM.
+     *
+     * @param parseContext Parse context
+     * @param parentId Parent module revision ID
+     * @return POM reader
+     * @throws IOException
+     * @throws SAXException
+     */
+    private PomReader parseOtherPom(DescriptorParseContext parseContext, ModuleVersionIdentifier parentId) throws IOException, SAXException {
+        LocallyAvailableExternalResource localResource = parseContext.getMetaDataArtifact(parentId, MavenPomArtifact.class);
+        PomReader pomReader = new PomReader(localResource);
+        GradlePomModuleDescriptorBuilder mdBuilder = new GradlePomModuleDescriptorBuilder(localResource, parseContext, pomReader);
+        doParsePom(parseContext, mdBuilder, pomReader);
+        return pomReader;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
index 4476247..e44ee58 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
@@ -16,34 +16,41 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
-import org.apache.ivy.core.IvyContext;
+import com.google.common.base.Joiner;
+import org.apache.ivy.core.IvyPatternHelper;
 import org.apache.ivy.core.NormalRelativeUrlResolver;
 import org.apache.ivy.core.RelativeUrlResolver;
 import org.apache.ivy.core.module.descriptor.*;
 import org.apache.ivy.core.module.id.ArtifactId;
 import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolveEngine;
-import org.apache.ivy.core.resolve.ResolveOptions;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.namespace.NameSpaceHelper;
 import org.apache.ivy.plugins.namespace.Namespace;
-import org.apache.ivy.plugins.parser.AbstractModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.repository.url.URLResource;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
 import org.apache.ivy.util.XMLHelper;
 import org.apache.ivy.util.extendable.DefaultExtendableItem;
-import org.apache.ivy.util.extendable.ExtendableItemHelper;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleDescriptorAdapter;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.resolution.IvyDescriptorArtifact;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.UrlExternalResource;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.DeprecationLogger;
+import org.gradle.util.TextUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
 
 import javax.xml.parsers.ParserConfigurationException;
 import java.io.File;
@@ -53,14 +60,12 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleRevisionId;
 
 /**
- * Copied from org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser into gradle codebase to make
- * it thread-safe.
+ * Copied from org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser into Gradle codebase.
  */
 public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
     static final String[] DEPENDENCY_REGULAR_ATTRIBUTES =
@@ -69,26 +74,340 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
     public static final String IVY_DATE_FORMAT_PATTERN = "yyyyMMddHHmmss";
 
     private static final Logger LOGGER = LoggerFactory.getLogger(IvyXmlModuleDescriptorParser.class);
+    private final ResolverStrategy resolverStrategy;
+
+    public IvyXmlModuleDescriptorParser(ResolverStrategy resolverStrategy) {
+        this.resolverStrategy = resolverStrategy;
+    }
+
+    protected MutableModuleVersionMetaData doParseDescriptor(DescriptorParseContext parseContext, LocallyAvailableExternalResource resource, boolean validate) throws IOException, ParseException {
+        Parser parser = createParser(parseContext, resource, populateProperties(), resolverStrategy);
+        return doParseDescriptorWithProvidedParser(parser, validate);
+    }
 
-    public DefaultModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
-        Parser parser = new Parser(this, ivySettings, res);
+    protected Parser createParser(DescriptorParseContext parseContext, LocallyAvailableExternalResource resource, Map<String, String> properties, ResolverStrategy resolverStrategy) throws MalformedURLException {
+        return new Parser(parseContext, resource, resource.getLocalResource().getFile().toURI().toURL(), properties, resolverStrategy);
+    }
+
+    private MutableModuleVersionMetaData doParseDescriptorWithProvidedParser(Parser parser, boolean validate) throws IOException, ParseException {
         parser.setValidate(validate);
-        parser.setInput(xmlURL);
         parser.parse();
-        return (DefaultModuleDescriptor) parser.getModuleDescriptor();
+        DefaultModuleDescriptor moduleDescriptor = parser.getModuleDescriptor();
+        postProcess(moduleDescriptor);
+        return new ModuleDescriptorAdapter(moduleDescriptor);
     }
 
-    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL, boolean validate) throws ParseException, IOException {
-        return parseDescriptor(ivySettings, descriptorURL, new URLResource(descriptorURL), validate);
+    protected void postProcess(DefaultModuleDescriptor moduleDescriptor) {
     }
 
-    public boolean accept(Resource res) {
-        return true; // this the default parser, it thus accepts all resources
+    @Override
+    protected String getTypeName() {
+        return "Ivy file";
     }
 
-    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
-            throws IOException, ParseException {
-        throw new UnsupportedOperationException();
+    private Map<String, String> populateProperties() {
+        HashMap<String, String> properties = new HashMap<String, String>();
+        String baseDir = new File(".").getAbsolutePath();
+        properties.put("ivy.default.settings.dir", baseDir);
+        properties.put("ivy.basedir", baseDir);
+
+        Set<String> propertyNames = CollectionUtils.collect(System.getProperties().entrySet(), new Transformer<String, Map.Entry<Object, Object>>() {
+            public String transform(Map.Entry<Object, Object> entry) {
+                return entry.getKey().toString();
+            }
+        });
+
+        for (String property : propertyNames) {
+            properties.put(property, System.getProperty(property));
+        }
+        return properties;
+    }
+
+    protected abstract static class AbstractParser extends DefaultHandler {
+        private static final String DEFAULT_CONF_MAPPING = "*->*";
+
+        private String defaultConf; // used only as defaultconf, not used for
+
+        // guessing right side part of a mapping
+        private String defaultConfMapping; // same as default conf but is used
+
+        // for guessing right side part of a mapping
+        private DefaultDependencyDescriptor defaultConfMappingDescriptor;
+
+        private final ExternalResource res;
+
+        private final List<String> errors = new ArrayList<String>();
+
+        private final DefaultModuleDescriptor md;
+
+        protected AbstractParser(ExternalResource resource) {
+            this.res = resource; // used for log and date only
+            md = new DefaultModuleDescriptor(XmlModuleDescriptorParser.getInstance(), null);
+            md.setLastModified(res.getLastModified());
+        }
+
+        protected void checkErrors() throws ParseException {
+            if (!errors.isEmpty()) {
+                throw new ParseException(Joiner.on(TextUtil.getPlatformLineSeparator()).join(errors), 0);
+            }
+        }
+
+        protected ExternalResource getResource() {
+            return res;
+        }
+
+        protected String getDefaultConfMapping() {
+            return defaultConfMapping;
+        }
+
+        protected void setDefaultConfMapping(String defaultConf) {
+            defaultConfMapping = defaultConf;
+        }
+
+        protected void parseDepsConfs(String confs, DefaultDependencyDescriptor dd) {
+            parseDepsConfs(confs, dd, defaultConfMapping != null);
+        }
+
+        protected void parseDepsConfs(String confs, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            parseDepsConfs(confs, dd, useDefaultMappingToGuessRightOperande, true);
+        }
+
+        protected void parseDepsConfs(String confs, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande, boolean evaluateConditions) {
+            if (confs == null) {
+                return;
+            }
+
+            String[] conf = confs.split(";");
+            parseDepsConfs(conf, dd, useDefaultMappingToGuessRightOperande, evaluateConditions);
+        }
+
+        protected void parseDepsConfs(String[] conf, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            parseDepsConfs(conf, dd, useDefaultMappingToGuessRightOperande, true);
+        }
+
+        protected void parseDepsConfs(String[] conf, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande, boolean evaluateConditions) {
+            replaceConfigurationWildcards(md);
+            for (int i = 0; i < conf.length; i++) {
+                String[] ops = conf[i].split("->");
+                if (ops.length == 1) {
+                    String[] modConfs = ops[0].split(",");
+                    if (!useDefaultMappingToGuessRightOperande) {
+                        for (int j = 0; j < modConfs.length; j++) {
+                            dd.addDependencyConfiguration(modConfs[j].trim(), modConfs[j].trim());
+                        }
+                    } else {
+                        for (int j = 0; j < modConfs.length; j++) {
+                            String[] depConfs = getDefaultConfMappingDescriptor()
+                                    .getDependencyConfigurations(modConfs[j]);
+                            if (depConfs.length > 0) {
+                                for (int k = 0; k < depConfs.length; k++) {
+                                    String mappedDependency = evaluateConditions
+                                    ? evaluateCondition(depConfs[k].trim(), dd)
+                                            : depConfs[k].trim();
+                                    if (mappedDependency != null) {
+                                        dd.addDependencyConfiguration(modConfs[j].trim(),
+                                            mappedDependency);
+                                    }
+                                }
+                            } else {
+                                // no default mapping found for this configuration, map
+                                // configuration to itself
+                                dd.addDependencyConfiguration(modConfs[j].trim(), modConfs[j]
+                                        .trim());
+                            }
+                        }
+                    }
+                } else if (ops.length == 2) {
+                    String[] modConfs = ops[0].split(",");
+                    String[] depConfs = ops[1].split(",");
+                    for (int j = 0; j < modConfs.length; j++) {
+                        for (int k = 0; k < depConfs.length; k++) {
+                            String mappedDependency = evaluateConditions ? evaluateCondition(
+                                depConfs[k].trim(), dd) : depConfs[k].trim();
+                            if (mappedDependency != null) {
+                                dd.addDependencyConfiguration(modConfs[j].trim(), mappedDependency);
+                            }
+                        }
+                    }
+                } else {
+                    addError("invalid conf " + conf[i] + " for " + dd);
+                }
+            }
+
+            if (md.isMappingOverride()) {
+                addExtendingConfigurations(conf, dd, useDefaultMappingToGuessRightOperande);
+            }
+        }
+
+        /**
+         * Evaluate the optional condition in the given configuration, like "[org=MYORG]confX". If
+         * the condition evaluates to true, the configuration is returned, if the condition
+         * evaluatate to false, null is returned. If there are no conditions, the configuration
+         * itself is returned.
+         *
+         * @param conf
+         *            the configuration to evaluate
+         * @param dd
+         *            the dependencydescriptor to which the configuration will be added
+         * @return the evaluated condition
+         */
+        private String evaluateCondition(String conf, DefaultDependencyDescriptor dd) {
+            if (conf.charAt(0) != '[') {
+                return conf;
+            }
+
+            int endConditionIndex = conf.indexOf(']');
+            if (endConditionIndex == -1) {
+                addError("invalid conf " + conf + " for " + dd);
+                return null;
+            }
+
+            String condition = conf.substring(1, endConditionIndex);
+
+            int notEqualIndex = condition.indexOf("!=");
+            if (notEqualIndex == -1) {
+                int equalIndex = condition.indexOf('=');
+                if (equalIndex == -1) {
+                    addError("invalid conf " + conf + " for " + dd.getDependencyRevisionId());
+                    return null;
+                }
+
+                String leftOp = condition.substring(0, equalIndex).trim();
+                String rightOp = condition.substring(equalIndex + 1).trim();
+
+                // allow organisation synonyms, like 'org' or 'organization'
+                if (leftOp.equals("org") || leftOp.equals("organization")) {
+                    leftOp = "organisation";
+                }
+
+                String attrValue = dd.getAttribute(leftOp);
+                if (!rightOp.equals(attrValue)) {
+                    return null;
+                }
+            } else {
+                String leftOp = condition.substring(0, notEqualIndex).trim();
+                String rightOp = condition.substring(notEqualIndex + 2).trim();
+
+                // allow organisation synonyms, like 'org' or 'organization'
+                if (leftOp.equals("org") || leftOp.equals("organization")) {
+                    leftOp = "organisation";
+                }
+
+                String attrValue = dd.getAttribute(leftOp);
+                if (rightOp.equals(attrValue)) {
+                    return null;
+                }
+            }
+
+            return conf.substring(endConditionIndex + 1);
+        }
+
+        private void addExtendingConfigurations(String[] confs, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            for (int i = 0; i < confs.length; i++) {
+                addExtendingConfigurations(confs[i], dd, useDefaultMappingToGuessRightOperande);
+            }
+        }
+
+        private void addExtendingConfigurations(String conf, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            Set configsToAdd = new HashSet();
+            Configuration[] configs = md.getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                String[] ext = configs[i].getExtends();
+                for (int j = 0; j < ext.length; j++) {
+                    if (conf.equals(ext[j])) {
+                        String configName = configs[i].getName();
+                        configsToAdd.add(configName);
+                        addExtendingConfigurations(configName, dd,
+                            useDefaultMappingToGuessRightOperande);
+                    }
+                }
+            }
+
+            String[] confs = (String[]) configsToAdd.toArray(new String[configsToAdd.size()]);
+            parseDepsConfs(confs, dd, useDefaultMappingToGuessRightOperande);
+        }
+
+        protected DependencyDescriptor getDefaultConfMappingDescriptor() {
+            if (defaultConfMappingDescriptor == null) {
+                defaultConfMappingDescriptor = new DefaultDependencyDescriptor(createModuleRevisionId("", "", ""), false);
+                parseDepsConfs(defaultConfMapping, defaultConfMappingDescriptor, false, false);
+            }
+            return defaultConfMappingDescriptor;
+        }
+
+        protected void addError(String msg) {
+            errors.add(msg + " in " + res.getName());
+        }
+
+        public void warning(SAXParseException ex) {
+            LOGGER.warn("xml parsing: " + getLocationString(ex) + ": " + ex.getMessage());
+        }
+
+        public void error(SAXParseException ex) {
+            addError("xml parsing: " + getLocationString(ex) + ": " + ex.getMessage());
+        }
+
+        public void fatalError(SAXParseException ex) throws SAXException {
+            addError("[Fatal Error] " + getLocationString(ex) + ": " + ex.getMessage());
+        }
+
+        /** Returns a string of the location. */
+        private String getLocationString(SAXParseException ex) {
+            StringBuffer str = new StringBuffer();
+
+            String systemId = ex.getSystemId();
+            if (systemId != null) {
+                int index = systemId.lastIndexOf('/');
+                if (index != -1) {
+                    systemId = systemId.substring(index + 1);
+                }
+                str.append(systemId);
+            } else {
+                str.append(getResource().getName());
+            }
+            str.append(':');
+            str.append(ex.getLineNumber());
+            str.append(':');
+            str.append(ex.getColumnNumber());
+
+            return str.toString();
+
+        } // getLocationString(SAXParseException):String
+
+        protected String getDefaultConf() {
+            return defaultConf != null ? defaultConf
+                    : (defaultConfMapping != null ? defaultConfMapping : DEFAULT_CONF_MAPPING);
+        }
+
+        protected void setDefaultConf(String defaultConf) {
+            this.defaultConf = defaultConf;
+        }
+
+        public DefaultModuleDescriptor getModuleDescriptor() throws ParseException {
+            checkErrors();
+            return md;
+        }
+
+        protected Date getDefaultPubDate() {
+            return new Date(md.getLastModified());
+        }
+
+        private void replaceConfigurationWildcards(ModuleDescriptor md) {
+            Configuration[] configs = md.getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                configs[i].replaceWildcards(md);
+            }
+        }
+
+        protected DefaultModuleDescriptor getMd() {
+            return md;
+        }
     }
 
     public static class Parser extends AbstractParser {
@@ -126,12 +445,10 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         private static final List ALLOWED_VERSIONS = Arrays.asList("1.0", "1.1", "1.2", "1.3", "1.4", "2.0", "2.1", "2.2");
 
         /* how and what do we have to parse */
-        private ParserSettings parserSettings;
+        private final DescriptorParseContext parseContext;
         private final RelativeUrlResolver relativeUrlResolver = new NormalRelativeUrlResolver();
+        private final URL descriptorURL;
         private boolean validate = true;
-        private URL descriptorURL;
-        private InputStream descriptorInput;
-
 
         /* Parsing state */
         private int state = State.NONE;
@@ -144,60 +461,60 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         private StringBuffer buffer;
         private String descriptorVersion;
         private String[] publicationsDefaultConf;
+        final Map<String, String> properties;
+        final ResolverStrategy resolverStrategy;
 
-        public Parser(ModuleDescriptorParser moduleDescriptorParser, ParserSettings ivySettings, Resource res) {
-            super(moduleDescriptorParser);
-            parserSettings = ivySettings;
-            setResource(res);
+        public Parser(DescriptorParseContext parseContext, ExternalResource res, URL descriptorURL, Map<String, String> properties, ResolverStrategy resolverStrategy) {
+            super(res);
+            this.parseContext = parseContext;
+            this.descriptorURL = descriptorURL;
+            this.properties = properties;
+            this.resolverStrategy = resolverStrategy;
         }
-        
-        public Parser newParser(Resource res) {
-            Parser parser = new Parser(getModuleDescriptorParser(), parserSettings, res);
+
+        public Parser newParser(ExternalResource res, URL descriptorURL) {
+            Parser parser = new Parser(parseContext, res, descriptorURL, properties, resolverStrategy);
             parser.setValidate(validate);
             return parser;
         }
 
-        public void setInput(InputStream descriptorInput) {
-            this.descriptorInput = descriptorInput;
+        public void setValidate(boolean validate) {
+            this.validate = validate;
         }
 
-        public void setInput(URL descriptorURL) {
-            this.descriptorURL = descriptorURL;
+        public boolean isValidate() {
+            return validate;
         }
 
-        public void setValidate(boolean validate) {
-            this.validate = validate;
+        public DescriptorParseContext getParseContext() {
+            return parseContext;
         }
 
-        public void parse() throws ParseException,
-                IOException {
-            try {
-                URL schemaURL = validate ? getSchemaURL() : null;
-                if (descriptorURL != null) {
-                    XMLHelper.parse(descriptorURL, schemaURL, this);
-                } else {
-                    XMLHelper.parse(descriptorInput, schemaURL, this, null);
-                }
-                checkConfigurations();
-                replaceConfigurationWildcards();
-                getMd().setModuleArtifact(DefaultArtifact.newIvyArtifact(getMd().getResolvedModuleRevisionId(), getMd().getPublicationDate()));
-                if (!artifactsDeclared) {
-                    String[] configurationNames = getMd().getConfigurationsNames();
-                    for (String configurationName : configurationNames) {
-                        getMd().addArtifact(configurationName, new MDArtifact(getMd(), getMd().getModuleRevisionId().getName(), "jar", "jar"));
+        public void parse() throws ParseException, IOException {
+            getResource().withContent(new Action<InputStream>() {
+                public void execute(InputStream inputStream) {
+                    URL schemaURL = validate ? getSchemaURL() : null;
+                    InputSource inSrc = new InputSource(inputStream);
+                    inSrc.setSystemId(descriptorURL.toExternalForm());
+                    try {
+                        XMLHelper.parse(inSrc, schemaURL, Parser.this, null);
+                    } catch (Exception e) {
+                        throw new MetaDataParseException("Ivy file", getResource(), e);
                     }
                 }
-                getMd().check();
-            } catch (ParserConfigurationException ex) {
-                IllegalStateException ise = new IllegalStateException(ex.getMessage() + " in " + descriptorURL);
-                ise.initCause(ex);
-                throw ise;
-            } catch (Exception ex) {
-                checkErrors();
-                ParseException pe = new ParseException(ex.getMessage() + " in " + descriptorURL, 0);
-                pe.initCause(ex);
-                throw pe;
+            });
+            checkErrors();
+            checkConfigurations();
+            replaceConfigurationWildcards();
+            getMd().setModuleArtifact(DefaultArtifact.newIvyArtifact(getMd().getResolvedModuleRevisionId(), getMd().getPublicationDate()));
+            if (!artifactsDeclared) {
+                String[] configurationNames = getMd().getConfigurationsNames();
+                for (String configurationName : configurationNames) {
+                    getMd().addArtifact(configurationName, new MDArtifact(getMd(), getMd().getModuleRevisionId().getName(), "jar", "jar"));
+                }
             }
+            checkErrors();
+            getMd().check();
         }
 
         public void startElement(String uri, String localName, String qName, Attributes attributes)
@@ -214,6 +531,12 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     extendsStarted(attributes);
                 } else if (state == State.INFO && "license".equals(qName)) {
                     getMd().addLicense(new License(substitute(attributes.getValue("name")), substitute(attributes.getValue("url"))));
+                } else if (state == State.INFO && "ivyauthor".equals(qName)) {
+                    // nothing to do, we don't store this
+                    return;
+                } else if (state == State.INFO && "repository".equals(qName)) {
+                    // nothing to do, we don't store this
+                    return;
                 } else if (state == State.INFO && "description".equals(qName)) {
                     getMd().setHomePage(substitute(attributes.getValue("homepage")));
                     state = State.DESCRIPTION;
@@ -229,7 +552,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     dependenciesStarted(attributes);
                 } else if ("conflicts".equals(qName)) {
                     if (!descriptorVersion.startsWith("1.")) {
-                        DeprecationLogger.nagUserWith("Using conflicts section in ivy.xml is deprecated: please use hints section instead. Ivy file URL: " + descriptorURL);
+                        DeprecationLogger.nagUserWith("Using conflicts section in ivy.xml is deprecated: please use hints section instead. Ivy file: " + getResource().getName());
                     }
                     state = State.CONFLICT;
                     checkConfigurations();
@@ -284,7 +607,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 parent = parseOtherIvyFileOnFileSystem(location);
 
                 //verify that the parsed descriptor is the correct parent module.
-                ModuleId expected = new ModuleId(parentOrganisation, parentModule);
+                ModuleId expected = IvyUtil.createModuleId(parentOrganisation, parentModule);
                 ModuleId pid = parent.getModuleRevisionId().getModuleId();
                 if (!expected.equals(pid)) {
                     LOGGER.warn("Ignoring parent Ivy file " + location + "; expected " + expected + " but found " + pid);
@@ -308,8 +631,12 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                                             + ";"
                                             + parentRevision);
                     parent = parseOtherIvyFile(parentOrganisation, parentModule, parentRevision);
+                } catch(IOException e) {
+                    LOGGER.warn("Unable to access included ivy file for " + parentOrganisation + "#" + parentModule + ";" + parentRevision);
                 } catch (ParseException e) {
                     LOGGER.warn("Unable to parse included ivy file for " + parentOrganisation + "#" + parentModule + ";" + parentRevision);
+                } catch(SAXException e) {
+                    LOGGER.warn("Unable to parse included ivy file for " + parentOrganisation + "#" + parentModule + ";" + parentRevision);
                 }
             }
 
@@ -317,13 +644,6 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 throw new ParseException("Unable to parse included ivy file for " + parentOrganisation + "#" + parentModule + ";" + parentRevision, 0);
             }
 
-            DefaultExtendsDescriptor ed = new DefaultExtendsDescriptor(
-                    parent.getModuleRevisionId(),
-                    parent.getResolvedModuleRevisionId(),
-                    attributes.getValue("location"),
-                    extendTypes.toArray(new String[extendTypes.size()]));
-            getMd().addInheritedDescriptor(ed);
-
             mergeWithOtherModuleDescriptor(extendTypes, parent);
         }
 
@@ -348,7 +668,6 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     mergeDescription(parent.getDescription());
                 }
             }
-
         }
 
         private void mergeAll(ModuleDescriptor parent) {
@@ -365,12 +684,12 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             DefaultModuleDescriptor descriptor = getMd();
             ModuleRevisionId currentMrid = descriptor.getModuleRevisionId();
 
-            ModuleRevisionId mergedMrid = ModuleRevisionId.newInstance(
-                mergeValue(parentMrid.getOrganisation(), currentMrid.getOrganisation()),
-                currentMrid.getName(),
-                mergeValue(parentMrid.getBranch(), currentMrid.getBranch()),
-                mergeValue(parentMrid.getRevision(), currentMrid.getRevision()),
-                mergeValues(parentMrid.getQualifiedExtraAttributes(), currentMrid.getQualifiedExtraAttributes())
+            ModuleRevisionId mergedMrid = createModuleRevisionId(
+                    mergeValue(parentMrid.getOrganisation(), currentMrid.getOrganisation()),
+                    currentMrid.getName(),
+                    mergeValue(parentMrid.getBranch(), currentMrid.getBranch()),
+                    mergeValue(parentMrid.getRevision(), currentMrid.getRevision()),
+                    mergeValues(parentMrid.getQualifiedExtraAttributes(), currentMrid.getQualifiedExtraAttributes())
             );
 
             descriptor.setModuleRevisionId(mergedMrid);
@@ -422,37 +741,20 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 throws ParseException, IOException {
             URL url = relativeUrlResolver.getURL(descriptorURL, location);
             LOGGER.debug("Trying to load included ivy file from " + url.toString());
-            Parser parser = newParser(new URLResource(url));
-            parser.parse();
-            return parser.getModuleDescriptor();
+            return parseModuleDescriptor(new UrlExternalResource(url), url);
         }
 
-        private ModuleDescriptor parseOtherIvyFile(String parentOrganisation,
-                String parentModule, String parentRevision) throws ParseException {
-            ModuleId parentModuleId = new ModuleId(parentOrganisation, parentModule);
-            ModuleRevisionId parentMrid = new ModuleRevisionId(parentModuleId, parentRevision);
+        protected ModuleDescriptor parseOtherIvyFile(String parentOrganisation, String parentModule, String parentRevision) throws IOException, ParseException, SAXException {
+            ModuleVersionIdentifier importedId = new DefaultModuleVersionIdentifier(parentOrganisation, parentModule, parentRevision);
+            LocallyAvailableExternalResource externalResource = parseContext.getMetaDataArtifact(importedId, IvyDescriptorArtifact.class);
 
-            DependencyDescriptor dd = new DefaultDependencyDescriptor(parentMrid, true);
-            ResolveData data = IvyContext.getContext().getResolveData();
-            if (data == null) {
-                ResolveEngine engine = IvyContext.getContext().getIvy().getResolveEngine();
-                ResolveOptions options = new ResolveOptions();
-                options.setDownload(false);
-                data = new ResolveData(engine, options);
-            }
+            return parseModuleDescriptor(externalResource, externalResource.getLocalResource().getFile().toURI().toURL());
+        }
 
-            DependencyResolver resolver = parserSettings.getResolver(parentMrid);
-            if (resolver == null) {
-                // TODO: Throw exception here?
-                return null;
-            } else {
-                dd = NameSpaceHelper.toSystem(dd, parserSettings.getContextNamespace());
-                ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
-                if (otherModule == null) {
-                    throw new ParseException("Unable to find " + parentMrid.toString(), 0);
-                }
-                return otherModule.getDescriptor();
-            }
+        private ModuleDescriptor parseModuleDescriptor(ExternalResource externalResource, URL descriptorURL) throws IOException, ParseException {
+            Parser parser = newParser(externalResource, descriptorURL);
+            parser.parse();
+            return parser.getModuleDescriptor();
         }
 
         private void publicationsStarted(Attributes attributes) {
@@ -478,8 +780,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
 
             // create a new temporary parser to read the configurations from
             // the specified file.
-            Parser parser = newParser(new URLResource(url));
-            parser.setInput(url);
+            Parser parser = newParser(new UrlExternalResource(url), url);
             XMLHelper.parse(url , null, parser);
 
             // add the configurations from this temporary parser to this module descriptor
@@ -572,19 +873,19 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             String[] ignoredAttributeNames = DEPENDENCY_REGULAR_ATTRIBUTES;
             Map extraAttributes = getExtraAttributes(attributes, ignoredAttributeNames);
 
-            ModuleRevisionId revId = ModuleRevisionId.newInstance(org, name, branch, rev, extraAttributes);
+            ModuleRevisionId revId = createModuleRevisionId(org, name, branch, rev, extraAttributes);
             ModuleRevisionId dynamicId;
             if ((revConstraint == null) && (branchConstraint == null)) {
                 // no dynamic constraints defined, so dynamicId equals revId
-                dynamicId = ModuleRevisionId.newInstance(org, name, branch, rev, extraAttributes, false);
+                dynamicId = createModuleRevisionId(org, name, branch, rev, extraAttributes, false);
             } else {
                 if (branchConstraint == null) {
                     // this situation occurs when there was no branch defined
                     // in the original dependency descriptor. So the dynamicId
                     // shouldn't contain a branch neither
-                    dynamicId = ModuleRevisionId.newInstance(org, name, null, revConstraint, extraAttributes, false);
+                    dynamicId = createModuleRevisionId(org, name, null, revConstraint, extraAttributes, false);
                 } else {
-                    dynamicId = ModuleRevisionId.newInstance(org, name, branchConstraint, revConstraint, extraAttributes);
+                    dynamicId = createModuleRevisionId(org, name, branchConstraint, revConstraint, extraAttributes);
                 }
             }
 
@@ -636,7 +937,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 setDefaultConf(defaultConf);
             }
             String defaultConfMapping = substitute(attributes.getValue("defaultconfmapping"));
-            if (defaultConf != null) {
+            if (defaultConfMapping != null) {
                 setDefaultConfMapping(defaultConfMapping);
             }
             String confMappingOverride = substitute(attributes.getValue("confmappingoverride"));
@@ -660,19 +961,9 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             String revision = substitute(attributes.getValue("revision"));
             String branch = substitute(attributes.getValue("branch"));
             Map extraAttributes = getExtraAttributes(attributes, new String[]{"organisation", "module", "revision", "status", "publication", "branch", "namespace", "default", "resolver"});
-            getMd().setModuleRevisionId(ModuleRevisionId.newInstance(org, module, branch, revision, extraAttributes));
-
-            String namespace = substitute(attributes.getValue("namespace"));
-            if (namespace != null) {
-                Namespace ns = parserSettings.getNamespace(namespace);
-                if (ns == null) {
-                    LOGGER.warn("namespace not found for " + getMd().getModuleRevisionId() + ": " + namespace);
-                } else {
-                    getMd().setNamespace(ns);
-                }
-            }
+            getMd().setModuleRevisionId(createModuleRevisionId(org, module, branch, revision, extraAttributes));
 
-            getMd().setStatus(elvis(substitute(attributes.getValue("status")), parserSettings.getStatusManager().getDefaultStatus()));
+            getMd().setStatus(elvis(substitute(attributes.getValue("status")), "integration"));
             getMd().setDefault(Boolean.valueOf(substitute(attributes.getValue("default"))));
             String pubDate = substitute(attributes.getValue("publication"));
             if (pubDate != null && pubDate.length() > 0) {
@@ -763,14 +1054,14 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
                 String org = elvis(substitute(attributes.getValue("org")), PatternMatcher.ANY_EXPRESSION);
                 String module = elvis(substitute(attributes.getValue("module")), PatternMatcher.ANY_EXPRESSION);
-                ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
+                ArtifactId aid = new ArtifactId(IvyUtil.createModuleId(org, module), name, type, ext);
                 Map extraAttributes = getExtraAttributes(attributes, new String[]{"org", "module", "name", "type", "ext", "matcher", "conf"});
                 confAware = new DefaultIncludeRule(aid, matcher, extraAttributes);
             } else { // _state == ARTIFACT_EXCLUDE || EXCLUDE
                 PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
                 String org = elvis(substitute(attributes.getValue("org")), PatternMatcher.ANY_EXPRESSION);
                 String module = elvis(substitute(attributes.getValue("module")), PatternMatcher.ANY_EXPRESSION);
-                ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
+                ArtifactId aid = new ArtifactId(IvyUtil.createModuleId(org, module), name, type, ext);
                 Map extraAttributes = getExtraAttributes(attributes, new String[]{"org", "module", "name", "type", "ext", "matcher", "conf"});
                 confAware = new DefaultExcludeRule(aid, matcher, extraAttributes);
             }
@@ -893,7 +1184,9 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         }
 
         private URL getSchemaURL() {
-            return getClass().getResource("ivy.xsd");
+            URL resource = getClass().getClassLoader().getResource("org/apache/ivy/plugins/parser/xml/ivy.xsd");
+            assert resource != null;
+            return resource;
         }
 
         private String elvis(String value, String defaultValue) {
@@ -901,11 +1194,18 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         }
 
         private String substitute(String name) {
-            return parserSettings.substitute(name);
+            return IvyPatternHelper.substituteVariables(name, properties);
         }
 
         private Map getExtraAttributes(Attributes attributes, String[] ignoredAttributeNames) {
-            return ExtendableItemHelper.getExtraAttributes(parserSettings, attributes, ignoredAttributeNames);
+            Map ret = new HashMap();
+            Collection ignored = Arrays.asList(ignoredAttributeNames);
+            for (int i = 0; i < attributes.getLength(); i++) {
+                if (!ignored.contains(attributes.getQName(i))) {
+                    ret.put(attributes.getQName(i), substitute(attributes.getValue(i)));
+                }
+            }
+            return ret;
         }
 
         private void fillExtraAttributes(DefaultExtendableItem item, Attributes attributes, String[] ignoredAttNames) {
@@ -916,9 +1216,8 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         }
 
         private PatternMatcher getMatcher(String matcherName) {
-            return parserSettings.getMatcher(matcherName);
+            return resolverStrategy.getPatternMatcher(matcherName);
         }
-
     }
 
     public String toString() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java
new file mode 100644
index 0000000..4307a09
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.GradleException;
+import org.gradle.internal.exceptions.Contextual;
+import org.gradle.api.internal.externalresource.ExternalResource;
+
+ at Contextual
+public class MetaDataParseException extends GradleException {
+    public MetaDataParseException(String message) {
+        super(message);
+    }
+
+    public MetaDataParseException(String typeName, ExternalResource resource, Throwable cause) {
+        super(String.format("Could not parse %s %s", typeName, resource.getName()), cause);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParser.java
new file mode 100644
index 0000000..584e45a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+
+import java.io.File;
+
+public interface MetaDataParser {
+    MutableModuleVersionMetaData parseMetaData(DescriptorParseContext context, LocallyAvailableExternalResource resource) throws MetaDataParseException;
+
+    MutableModuleVersionMetaData parseMetaData(DescriptorParseContext ivySettings, File descriptorFile) throws MetaDataParseException;
+
+    MutableModuleVersionMetaData parseMetaData(DescriptorParseContext ivySettings, File descriptorFile, boolean validate) throws MetaDataParseException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
deleted file mode 100644
index 82905c7..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
-
-import org.apache.ivy.core.RelativeUrlResolver;
-import org.apache.ivy.core.cache.ResolutionCacheManager;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.module.status.StatusManager;
-import org.apache.ivy.plugins.conflict.ConflictManager;
-import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.namespace.Namespace;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-import java.io.File;
-import java.util.Map;
-
-/**
- * ParserSettings that control the scope of searches carried out during parsing.
- * If the parser asks for a resolver for the currently resolving revision, the resolver scope is only the repository where the module was resolved.
- * If the parser asks for a resolver for a different revision, the resolver scope is all repositories.
- */
-public class ModuleScopedParserSettings implements ParserSettings {
-    private final ParserSettings settings;
-    private final DependencyResolver currentResolver;
-    private final ModuleRevisionId currentRevisionId;
-
-    public ModuleScopedParserSettings(ParserSettings settings, DependencyResolver currentResolver, ModuleRevisionId currentRevisionId) {
-        this.settings = settings;
-        this.currentResolver = currentResolver;
-        this.currentRevisionId = currentRevisionId;
-    }
-
-    public ModuleRevisionId getCurrentRevisionId() {
-        return currentRevisionId;
-    }
-
-    public DependencyResolver getResolver(ModuleRevisionId mRevId) {
-        if (mRevId.equals(currentRevisionId)) {
-            return currentResolver;
-        }
-        return settings.getResolver(mRevId);
-    }
-
-    public ConflictManager getConflictManager(String name) {
-        return settings.getConflictManager(name);
-    }
-
-    public String substitute(String value) {
-        return settings.substitute(value);
-    }
-
-    public Map substitute(Map strings) {
-        return settings.substitute(strings);
-    }
-
-    public ResolutionCacheManager getResolutionCacheManager() {
-        return settings.getResolutionCacheManager();
-    }
-
-    public PatternMatcher getMatcher(String matcherName) {
-        return settings.getMatcher(matcherName);
-    }
-
-    public Namespace getNamespace(String namespace) {
-        return settings.getNamespace(namespace);
-    }
-
-    public StatusManager getStatusManager() {
-        return settings.getStatusManager();
-    }
-
-    public RelativeUrlResolver getRelativeUrlResolver() {
-        return settings.getRelativeUrlResolver();
-    }
-
-    public File resolveFile(String filename) {
-        return settings.resolveFile(filename);
-    }
-
-    public String getDefaultBranch(ModuleId moduleId) {
-        return settings.getDefaultBranch(moduleId);
-    }
-
-    public Namespace getContextNamespace() {
-        return settings.getContextNamespace();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java
deleted file mode 100644
index efd9040..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.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.api.internal.artifacts.ivyservice.ivyresolve.parser;
-
-import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.repository.Resource;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ParserRegistry {
-    private List<ModuleDescriptorParser> parsers = new ArrayList<ModuleDescriptorParser>();
-
-    public ParserRegistry() {
-        parsers.add(new GradlePomModuleDescriptorParser());
-        parsers.add(new DownloadedIvyModuleDescriptorParser());
-    }
-
-    public ModuleDescriptorParser forResource(Resource resource) {
-        for (ModuleDescriptorParser parser : parsers) {
-            if (parser.accept(resource)) {
-                return parser;
-            }
-        }
-        throw new IllegalStateException("No registered parser for resource: " + resource.getName());
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParent.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParent.java
new file mode 100644
index 0000000..8286b48
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.PomReader.PomDependencyData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomDependencyMgt;
+
+import java.util.Map;
+
+public interface PomParent {
+    /**
+     * Gets POM, GAV and custom properties.
+     *
+     * @return Properties
+     */
+    Map<String, String> getProperties();
+
+    /**
+     * Gets declared dependencies.
+     *
+     * @return Dependencies
+     */
+    Map<MavenDependencyKey, PomDependencyData> getDependencies();
+
+    /**
+     * Gets declared dependency management.
+     *
+     * @return Dependency management
+     */
+    Map<MavenDependencyKey, PomDependencyMgt> getDependencyMgt();
+
+    /**
+     * Finds dependency management default coordinates for a dependency. Returns null if default cannot be found.
+     *
+     * @param dependencyKey Dependency key
+     * @return Dependency management element or null
+     */
+    PomDependencyMgt findDependencyDefaults(MavenDependencyKey dependencyKey);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReader.java
new file mode 100644
index 0000000..7ba8117
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReader.java
@@ -0,0 +1,776 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.XMLHelper;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomDependencyMgt;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomProfile;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import javax.xml.parsers.DocumentBuilder;
+import java.io.*;
+import java.util.*;
+
+
+/**
+ * Copied from org.apache.ivy.plugins.parser.m2.PomReader.
+ */
+public class PomReader implements PomParent {
+
+    private static final String PACKAGING = "packaging";
+    private static final String DEPENDENCY = "dependency";
+    private static final String DEPENDENCIES = "dependencies";
+    private static final String DEPENDENCY_MGT = "dependencyManagement";
+    private static final String PROJECT = "project";
+    private static final String MODEL = "model";
+    private static final String GROUP_ID = "groupId";
+    private static final String ARTIFACT_ID = "artifactId";
+    private static final String VERSION = "version";
+    private static final String DESCRIPTION = "description";
+    private static final String HOMEPAGE = "url";
+    private static final String LICENSES = "licenses";
+    private static final String LICENSE = "license";
+    private static final String LICENSE_NAME = "name";
+    private static final String LICENSE_URL = "url";
+    private static final String PARENT = "parent";
+    private static final String SCOPE = "scope";
+    private static final String CLASSIFIER = "classifier";
+    private static final String OPTIONAL = "optional";
+    private static final String EXCLUSIONS = "exclusions";
+    private static final String EXCLUSION = "exclusion";
+    private static final String DISTRIBUTION_MGT = "distributionManagement";
+    private static final String RELOCATION = "relocation";
+    private static final String PROPERTIES = "properties";
+    private static final String TYPE = "type";
+    private static final String PROFILES = "profiles";
+    private static final String PROFILE = "profile";
+    private static final String PROFILE_ID = "id";
+    private static final String PROFILE_ACTIVATION = "activation";
+    private static final String PROFILE_ACTIVATION_ACTIVE_BY_DEFAULT = "activeByDefault";
+
+    private PomParent pomParent = new RootPomParent();
+    private final Map<String, String> properties = new HashMap<String, String>();
+    private List<PomDependencyMgt> declaredDependencyMgts;
+    private List<PomProfile> declaredActivePomProfiles;
+    private Map<MavenDependencyKey, PomDependencyMgt> resolvedDependencyMgts;
+    private final Map<MavenDependencyKey, PomDependencyMgt> importedDependencyMgts = new LinkedHashMap<MavenDependencyKey, PomDependencyMgt>();
+    private Map<MavenDependencyKey, PomDependencyData> resolvedDependencies;
+
+    private final Element projectElement;
+    private final Element parentElement;
+
+    public PomReader(final LocallyAvailableExternalResource resource) throws IOException, SAXException {
+        final String systemId = resource.getLocalResource().getFile().toURI().toASCIIString();
+        Document pomDomDoc = resource.withContent(new Transformer<Document, InputStream>() {
+            public Document transform(InputStream inputStream) {
+                try {
+                    return parseToDom(inputStream, systemId);
+                } catch (Exception e) {
+                    throw new MetaDataParseException("POM", resource, e);
+                }
+            }
+        });
+        projectElement = pomDomDoc.getDocumentElement();
+        if (!PROJECT.equals(projectElement.getNodeName()) && !MODEL.equals(projectElement.getNodeName())) {
+            throw new SAXParseException("project must be the root tag", systemId, systemId, 0, 0);
+        }
+        parentElement = getFirstChildElement(projectElement, PARENT);
+
+        setDefaultParentGavProperties();
+        setPomProperties();
+        setActiveProfileProperties();
+    }
+
+    public void setPomParent(PomParent pomParent) {
+        this.pomParent = pomParent;
+        setPomParentProperties();
+    }
+
+    private void setPomParentProperties() {
+        Map<String, String> parentPomProps = pomParent.getProperties();
+
+        for(Map.Entry<String, String> entry : parentPomProps.entrySet()) {
+            setProperty(entry.getKey(), entry.getValue());
+        }
+    }
+
+    private void setPomProperties() {
+        for(Map.Entry<String, String> pomProperty : getPomProperties().entrySet()) {
+            setProperty(pomProperty.getKey(), pomProperty.getValue());
+        }
+    }
+
+    /**
+     * Sets properties for all active profiles. Properties from an active profile override existing POM properties.
+     */
+    private void setActiveProfileProperties() {
+        for(PomProfile activePomProfile : parseActivePomProfiles()) {
+            for(Map.Entry<String, String> property : activePomProfile.getProperties().entrySet()) {
+                properties.put(property.getKey(), property.getValue());
+            }
+        }
+    }
+
+    private void setDefaultParentGavProperties() {
+        setGavPropertyValueWithoutReplacement(GavProperty.PARENT_GROUP_ID, getParentGroupId());
+        setGavPropertyValueWithoutReplacement(GavProperty.PARENT_VERSION, getParentVersion());
+    }
+
+    private void setGavPropertyValueWithoutReplacement(GavProperty gavProperty, String propertyValue) {
+        for(String name : gavProperty.getNames()) {
+            setProperty(name, propertyValue);
+        }
+    }
+
+    private enum GavProperty {
+        PARENT_VERSION("parent.version", "project.parent.version"),
+        PARENT_GROUP_ID("parent.groupId", "project.parent.groupId"),
+        GROUP_ID("project.groupId", "pom.groupId", "groupId"),
+        ARTIFACT_ID("project.artifactId", "pom.artifactId", "artifactId"),
+        VERSION("project.version", "pom.version", "version");
+
+        private final String[] names;
+
+        private GavProperty(String... names) {
+            this.names = names;
+        }
+
+        public String[] getNames() {
+            return names;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return projectElement.getOwnerDocument().getDocumentURI();
+    }
+
+    public static Document parseToDom(InputStream stream, String systemId) throws IOException, SAXException {
+        EntityResolver entityResolver = new EntityResolver() {
+            public InputSource resolveEntity(String publicId, String systemId)
+                    throws SAXException, IOException {
+                if ((systemId != null) && systemId.endsWith("m2-entities.ent")) {
+                    return new InputSource(org.apache.ivy.plugins.parser.m2.PomReader.class.getResourceAsStream("m2-entities.ent"));
+                }
+                return null;
+            }
+        };
+        InputStream dtdStream = new AddDTDFilterInputStream(stream);
+        DocumentBuilder docBuilder = XMLHelper.getDocBuilder(entityResolver);
+        return docBuilder.parse(dtdStream, systemId);
+    }
+
+    public boolean hasParent() {
+        return parentElement != null;
+    }
+
+    /**
+     * Add a property if not yet set and value is not null.
+     * This guarantee that property keep the first value that is put on it and that the properties
+     * are never null.
+     */
+    public void setProperty(String prop, String val) {
+        if (!properties.containsKey(prop) && val != null) {
+            properties.put(prop, val);
+        }
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    public void addImportedDependencyMgts(Map<MavenDependencyKey, PomDependencyMgt> inherited) {
+        if (resolvedDependencyMgts != null) {
+            throw new IllegalStateException("Cannot add imported dependency management elements after dependency management elements have been resolved for this POM.");
+        }
+        importedDependencyMgts.putAll(inherited);
+    }
+
+    public String getGroupId() {
+        String groupId = getFirstChildText(projectElement , GROUP_ID);
+        if (groupId == null) {
+            groupId = getFirstChildText(parentElement, GROUP_ID);
+        }
+        return replaceProps(groupId);
+
+    }
+
+    public String getParentGroupId() {
+        String groupId = getFirstChildText(parentElement , GROUP_ID);
+        if (groupId == null) {
+            groupId = getFirstChildText(projectElement, GROUP_ID);
+        }
+        return replaceProps(groupId);
+    }
+
+    public String getArtifactId() {
+        String val = getFirstChildText(projectElement , ARTIFACT_ID);
+        if (val == null) {
+            val = getFirstChildText(parentElement, ARTIFACT_ID);
+        }
+        return replaceProps(val);
+    }
+
+    public String getParentArtifactId() {
+        String val = getFirstChildText(parentElement , ARTIFACT_ID);
+        if (val == null) {
+            val = getFirstChildText(projectElement, ARTIFACT_ID);
+        }
+        return replaceProps(val);
+    }
+
+    public String getVersion() {
+        String val = getFirstChildText(projectElement , VERSION);
+        if (val == null) {
+            val = getFirstChildText(parentElement, VERSION);
+        }
+        return replaceProps(val);
+    }
+
+    public String getParentVersion() {
+        String val = getFirstChildText(parentElement , VERSION);
+        if (val == null) {
+            val = getFirstChildText(projectElement, VERSION);
+        }
+        return replaceProps(val);
+    }
+
+    public String getPackaging() {
+        String val = getFirstChildText(projectElement , PACKAGING);
+        if (val == null) {
+            val = "jar";
+        }
+        return val;
+    }
+
+    public String getHomePage() {
+        String val = getFirstChildText(projectElement, HOMEPAGE);
+        if (val == null) {
+            val = "";
+        }
+        return val;
+    }
+
+    public String getDescription() {
+        String val = getFirstChildText(projectElement, DESCRIPTION);
+        if (val == null) {
+            val = "";
+        }
+        return val.trim();
+    }
+
+    public List<License> getLicenses() {
+        Element licenses = getFirstChildElement(projectElement, LICENSES);
+        if (licenses == null) {
+            return Collections.emptyList();
+        }
+        licenses.normalize();
+        List<License> lics = new ArrayList<License>();
+        for (Element license : getAllChilds(licenses)) {
+            if (LICENSE.equals(license.getNodeName())) {
+                String name = getFirstChildText(license, LICENSE_NAME);
+                String url = getFirstChildText(license, LICENSE_URL);
+
+                if ((name == null) && (url == null)) {
+                    // move to next license
+                    continue;
+                }
+
+                if (name == null) {
+                    // The license name is required in Ivy but not in a POM!
+                    name = "Unknown License";
+                }
+
+                lics.add(new License(name, url));
+            }
+        }
+        return lics;
+    }
+
+    public ModuleRevisionId getRelocation() {
+        Element distrMgt = getFirstChildElement(projectElement, DISTRIBUTION_MGT);
+        Element relocation = getFirstChildElement(distrMgt , RELOCATION);
+        if (relocation == null) {
+            return null;
+        } else {
+            String relocGroupId = getFirstChildText(relocation, GROUP_ID);
+            String relocArtId = getFirstChildText(relocation, ARTIFACT_ID);
+            String relocVersion = getFirstChildText(relocation, VERSION);
+            relocGroupId = relocGroupId == null ? getGroupId() : relocGroupId;
+            relocArtId = relocArtId == null ? getArtifactId() : relocArtId;
+            relocVersion = relocVersion == null ? getVersion() : relocVersion;
+            return IvyUtil.createModuleRevisionId(relocGroupId, relocArtId, relocVersion);
+        }
+    }
+
+    /**
+     * Returns all dependencies for this POM, including those inherited from parent POMs.
+     */
+    public Map<MavenDependencyKey, PomDependencyData> getDependencies() {
+        if (resolvedDependencies == null) {
+            resolvedDependencies = resolveDependencies();
+        }
+        return resolvedDependencies;
+    }
+
+    private Map<MavenDependencyKey, PomDependencyData> resolveDependencies() {
+        Map<MavenDependencyKey, PomDependencyData> dependencies = new LinkedHashMap<MavenDependencyKey, PomDependencyData>();
+
+        for(PomDependencyData dependency : getDependencyData(projectElement)) {
+            dependencies.put(dependency.getId(), dependency);
+        }
+
+        // Maven adds inherited dependencies last
+        for (Map.Entry<MavenDependencyKey, PomDependencyData> entry : pomParent.getDependencies().entrySet()) {
+            if (!dependencies.containsKey(entry.getKey())) {
+                dependencies.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        for(PomProfile pomProfile : parseActivePomProfiles()) {
+            for(PomDependencyData dependency : pomProfile.getDependencies()) {
+                dependencies.put(dependency.getId(), dependency);
+            }
+        }
+
+        return dependencies;
+    }
+
+    private List<PomDependencyData> getDependencyData(Element parentElement) {
+        List<PomDependencyData> depElements = new ArrayList<PomDependencyData>();
+        Element dependenciesElement = getFirstChildElement(parentElement, DEPENDENCIES);
+        if (dependenciesElement != null) {
+            NodeList childs = dependenciesElement.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element && DEPENDENCY.equals(node.getNodeName())) {
+                    depElements.add(new PomDependencyData((Element) node));
+                }
+            }
+        }
+
+        return depElements;
+    }
+
+    /**
+     * Returns all dependency management elements for this POM, including those inherited from parent and imported POMs.
+     */
+    public Map<MavenDependencyKey, PomDependencyMgt> getDependencyMgt() {
+        if(resolvedDependencyMgts == null) {
+            resolvedDependencyMgts = resolveDependencyMgt();
+        }
+        return resolvedDependencyMgts;
+    }
+
+    private Map<MavenDependencyKey, PomDependencyMgt> resolveDependencyMgt() {
+        Map<MavenDependencyKey, PomDependencyMgt> dependencies = new LinkedHashMap<MavenDependencyKey, PomDependencyMgt>();
+        dependencies.putAll(pomParent.getDependencyMgt());
+        dependencies.putAll(importedDependencyMgts);
+        for(PomDependencyMgt dependencyMgt : parseDependencyMgt()) {
+            dependencies.put(dependencyMgt.getId(), dependencyMgt);
+        }
+        return dependencies;
+    }
+
+    /**
+     * Parses the dependency management elements declared in this POM without removing the duplicates.
+     *
+     * @return Parsed dependency management elements
+     */
+    public List<PomDependencyMgt> parseDependencyMgt() {
+        if(declaredDependencyMgts == null) {
+            List<PomDependencyMgt> dependencyMgts = getDependencyMgt(projectElement);
+
+            for(PomProfile pomProfile : parseActivePomProfiles()) {
+                for(PomDependencyMgt dependencyMgt : pomProfile.getDependencyMgts()) {
+                    dependencyMgts.add(dependencyMgt);
+                }
+            }
+
+            declaredDependencyMgts = dependencyMgts;
+        }
+
+        return declaredDependencyMgts;
+    }
+
+    private List<PomDependencyMgt> getDependencyMgt(Element parentElement) {
+        List<PomDependencyMgt> depMgmtElements = new ArrayList<PomDependencyMgt>();
+        Element dependenciesElement = getFirstChildElement(parentElement, DEPENDENCY_MGT);
+        dependenciesElement = getFirstChildElement(dependenciesElement, DEPENDENCIES);
+
+        if (dependenciesElement != null) {
+            NodeList childs = dependenciesElement.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element && DEPENDENCY.equals(node.getNodeName())) {
+                    depMgmtElements.add(new PomDependencyMgtElement((Element) node));
+                }
+            }
+        }
+
+        return depMgmtElements;
+    }
+
+    public PomDependencyMgt findDependencyDefaults(MavenDependencyKey dependencyKey) {
+        return getDependencyMgt().get(dependencyKey);
+    }
+
+    public void resolveGAV() {
+        setGavPropertyValue(GavProperty.GROUP_ID, getGroupId());
+        setGavPropertyValue(GavProperty.ARTIFACT_ID, getArtifactId());
+        setGavPropertyValue(GavProperty.VERSION, getVersion());
+    }
+
+    private void setGavPropertyValue(GavProperty gavProperty, String propertyValue) {
+        for(String name : gavProperty.getNames()) {
+            properties.put(name, propertyValue);
+        }
+    }
+
+    public class PomDependencyMgtElement implements PomDependencyMgt {
+        private final Element depElement;
+
+        PomDependencyMgtElement(Element depElement) {
+            this.depElement = depElement;
+        }
+
+        public MavenDependencyKey getId() {
+            return new MavenDependencyKey(getGroupId(), getArtifactId(), getType(), getClassifier());
+        }
+
+        /* (non-Javadoc)
+         * @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getGroupId()
+         */
+        public String getGroupId() {
+            String val = getFirstChildText(depElement , GROUP_ID);
+            return replaceProps(val);
+        }
+
+        /* (non-Javadoc)
+         * @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getArtifaceId()
+         */
+        public String getArtifactId() {
+            String val = getFirstChildText(depElement , ARTIFACT_ID);
+            return replaceProps(val);
+        }
+
+        /* (non-Javadoc)
+         * @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getVersion()
+         */
+        public String getVersion() {
+            String val = getFirstChildText(depElement , VERSION);
+            return replaceProps(val);
+        }
+
+        public String getScope() {
+            String val = getFirstChildText(depElement , SCOPE);
+            return replaceProps(val);
+        }
+
+        public String getType() {
+            String val = getFirstChildText(depElement , TYPE);
+            val = replaceProps(val);
+
+            if(val == null) {
+                val = "jar";
+            }
+
+            return val;
+        }
+
+        public String getClassifier() {
+            String val = getFirstChildText(depElement , CLASSIFIER);
+            return replaceProps(val);
+        }
+
+        public List<ModuleId> getExcludedModules() {
+            Element exclusionsElement = getFirstChildElement(depElement, EXCLUSIONS);
+            List<ModuleId> exclusions = new LinkedList<ModuleId>();
+            if (exclusionsElement != null) {
+                NodeList childs = exclusionsElement.getChildNodes();
+                for (int i = 0; i < childs.getLength(); i++) {
+                    Node node = childs.item(i);
+                    if (node instanceof Element && EXCLUSION.equals(node.getNodeName())) {
+                        String groupId = getFirstChildText((Element) node, GROUP_ID);
+                        String artifactId = getFirstChildText((Element) node, ARTIFACT_ID);
+                        if ((groupId != null) && (artifactId != null)) {
+                            exclusions.add(IvyUtil.createModuleId(groupId, artifactId));
+                        }
+                    }
+                }
+            }
+            return exclusions;
+        }
+    }
+
+    public class PomDependencyData extends PomDependencyMgtElement {
+        private final Element depElement;
+        PomDependencyData(Element depElement) {
+            super(depElement);
+            this.depElement = depElement;
+        }
+
+        public boolean isOptional() {
+            Element e = getFirstChildElement(depElement, OPTIONAL);
+            return (e != null) && "true".equalsIgnoreCase(getTextContent(e));
+        }
+    }
+
+    public class PomProfileElement implements PomProfile {
+        private final Element element;
+        private List<PomDependencyMgt> declaredDependencyMgts;
+        private List<PomDependencyData> declaredDependencies;
+
+        PomProfileElement(Element element) {
+            this.element = element;
+        }
+
+        public String getId() {
+            return getFirstChildText(element, PROFILE_ID);
+        }
+
+        public Map<String, String> getProperties() {
+            return getPomProperties(element);
+        }
+
+        public List<PomDependencyMgt> getDependencyMgts() {
+            if(declaredDependencyMgts == null) {
+                declaredDependencyMgts = getDependencyMgt(element);
+            }
+
+            return declaredDependencyMgts;
+        }
+
+        public List<PomDependencyData> getDependencies() {
+            if(declaredDependencies == null) {
+                declaredDependencies = getDependencyData(element);
+            }
+
+            return declaredDependencies;
+        }
+    }
+
+    /**
+     * Parses all active profiles that can be found in POM.
+     *
+     * @return Active POM profiles
+     */
+    private List<PomProfile> parseActivePomProfiles() {
+        if(declaredActivePomProfiles == null) {
+            List<PomProfile> activePomProfiles = new ArrayList<PomProfile>();
+            Element profilesElement = getFirstChildElement(projectElement, PROFILES);
+
+            if(profilesElement != null) {
+                for(Element profileElement : getAllChilds(profilesElement)) {
+                    if(PROFILE.equals(profileElement.getNodeName())) {
+                        Element activationElement = getFirstChildElement(profileElement, PROFILE_ACTIVATION);
+
+                        if(activationElement != null) {
+                            String activeByDefault = getFirstChildText(activationElement, PROFILE_ACTIVATION_ACTIVE_BY_DEFAULT);
+
+                            if(activeByDefault != null && "true".equals(activeByDefault)) {
+                                activePomProfiles.add(new PomProfileElement(profileElement));
+                            }
+                        }
+                    }
+                }
+            }
+
+            declaredActivePomProfiles = activePomProfiles;
+        }
+
+        return declaredActivePomProfiles;
+    }
+
+    /**
+     * @return the content of the properties tag into the pom.
+     */
+    public Map<String, String> getPomProperties() {
+        return getPomProperties(projectElement);
+    }
+
+    private Map<String, String> getPomProperties(Element parentElement) {
+        Map<String, String> pomProperties = new HashMap<String, String>();
+        Element propsEl = getFirstChildElement(parentElement, PROPERTIES);
+        if (propsEl != null) {
+            propsEl.normalize();
+        }
+        for (Element prop : getAllChilds(propsEl)) {
+            pomProperties.put(prop.getNodeName(), getTextContent(prop));
+        }
+        return pomProperties;
+    }
+
+    private String replaceProps(String val) {
+        if (val == null) {
+            return null;
+        } else {
+            return IvyPatternHelper.substituteVariables(val, properties).trim();
+        }
+    }
+
+    private static String getTextContent(Element element) {
+        StringBuilder result = new StringBuilder();
+
+        NodeList childNodes = element.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node child = childNodes.item(i);
+
+            switch (child.getNodeType()) {
+                case Node.CDATA_SECTION_NODE:
+                case Node.TEXT_NODE:
+                    result.append(child.getNodeValue());
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return result.toString();
+    }
+
+    private static String getFirstChildText(Element parentElem, String name) {
+        Element node = getFirstChildElement(parentElem, name);
+        if (node != null) {
+            return getTextContent(node);
+        } else {
+            return null;
+        }
+    }
+
+    private static Element getFirstChildElement(Element parentElem, String name) {
+        if (parentElem == null) {
+            return null;
+        }
+        NodeList childs = parentElem.getChildNodes();
+        for (int i = 0; i < childs.getLength(); i++) {
+            Node node = childs.item(i);
+            if (node instanceof Element && name.equals(node.getNodeName())) {
+                return (Element) node;
+            }
+        }
+        return null;
+    }
+
+    private static List<Element> getAllChilds(Element parent) {
+        List<Element> r = new LinkedList<Element>();
+        if (parent != null) {
+            NodeList childs = parent.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element) {
+                    r.add((Element) node);
+                }
+            }
+        }
+        return r;
+    }
+
+    private static final class AddDTDFilterInputStream extends FilterInputStream {
+        private static final int MARK = 10000;
+        private static final String DOCTYPE = "<!DOCTYPE project SYSTEM \"m2-entities.ent\">\n";
+
+        private int count;
+        private byte[] prefix = DOCTYPE.getBytes();
+
+        private AddDTDFilterInputStream(InputStream in) throws IOException {
+            super(new BufferedInputStream(in));
+
+            this.in.mark(MARK);
+
+            // TODO: we should really find a better solution for this...
+            // maybe we could use a FilterReader instead of a FilterInputStream?
+            int byte1 = this.in.read();
+            int byte2 = this.in.read();
+            int byte3 = this.in.read();
+
+            if (byte1 == 239 && byte2 == 187 && byte3 == 191) {
+                // skip the UTF-8 BOM
+                this.in.mark(MARK);
+            } else {
+                this.in.reset();
+            }
+
+            int bytesToSkip = 0;
+            LineNumberReader reader = new LineNumberReader(new InputStreamReader(this.in, "UTF-8"), 100);
+            String firstLine = reader.readLine();
+            if (firstLine != null) {
+                String trimmed = firstLine.trim();
+                if (trimmed.startsWith("<?xml ")) {
+                    int endIndex = trimmed.indexOf("?>");
+                    String xmlDecl = trimmed.substring(0, endIndex + 2);
+                    prefix = (xmlDecl + "\n" + DOCTYPE).getBytes();
+                    bytesToSkip = xmlDecl.getBytes().length;
+                }
+            }
+
+            this.in.reset();
+            for (int i = 0; i < bytesToSkip; i++) {
+                this.in.read();
+            }
+        }
+
+        public int read() throws IOException {
+            if (count < prefix.length) {
+                return prefix[count++];
+            }
+
+            return super.read();
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (b == null) {
+                throw new NullPointerException();
+            } else if ((off < 0) || (off > b.length) || (len < 0)
+                    || ((off + len) > b.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return 0;
+            }
+
+            int nbrBytesCopied = 0;
+
+            if (count < prefix.length) {
+                int nbrBytesFromPrefix = Math.min(prefix.length - count, len);
+                System.arraycopy(prefix, count, b, off, nbrBytesFromPrefix);
+                nbrBytesCopied = nbrBytesFromPrefix;
+            }
+
+            if (nbrBytesCopied < len) {
+                nbrBytesCopied += in.read(b, off + nbrBytesCopied, len - nbrBytesCopied);
+            }
+
+            count += nbrBytesCopied;
+            return nbrBytesCopied;
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/RootPomParent.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/RootPomParent.java
new file mode 100644
index 0000000..363fb91
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/RootPomParent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.PomReader.PomDependencyData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomDependencyMgt;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class RootPomParent implements PomParent {
+    private final Map<String, String> properties = Collections.emptyMap();
+    private final Map<MavenDependencyKey, PomReader.PomDependencyData> dependencies = Collections.emptyMap();
+    private final Map<MavenDependencyKey, PomDependencyMgt> dependencyMgts = Collections.emptyMap();
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    public Map<MavenDependencyKey, PomDependencyData> getDependencies() {
+        return dependencies;
+    }
+
+    public Map<MavenDependencyKey, PomDependencyMgt> getDependencyMgt() {
+        return dependencyMgts;
+    }
+
+    public PomDependencyMgt findDependencyDefaults(MavenDependencyKey dependencyKey) {
+        return null;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/UnresolvedDependencyVersionException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/UnresolvedDependencyVersionException.java
new file mode 100644
index 0000000..0850be0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/UnresolvedDependencyVersionException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey;
+
+public class UnresolvedDependencyVersionException extends RuntimeException {
+    public UnresolvedDependencyVersionException(MavenDependencyKey key) {
+        super(String.format("Unable to resolve version for dependency '%s'", key.toString()));
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/MavenDependencyKey.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/MavenDependencyKey.java
new file mode 100644
index 0000000..0c033f1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/MavenDependencyKey.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data;
+
+public class MavenDependencyKey {
+    private static final String KEY_SEPARATOR = ":";
+    private final String groupId;
+    private final String artifactId;
+    private final String type;
+    private final String classifier;
+
+    public MavenDependencyKey(String groupId, String artifactId, String type, String classifier) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.type = type;
+        this.classifier = classifier;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        MavenDependencyKey that = (MavenDependencyKey) o;
+
+        if (!artifactId.equals(that.artifactId)) {
+            return false;
+        }
+        if (classifier != null ? !classifier.equals(that.classifier) : that.classifier != null) {
+            return false;
+        }
+        if (!groupId.equals(that.groupId)) {
+            return false;
+        }
+        if (type != null ? !type.equals(that.type) : that.type != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = groupId.hashCode();
+        result = 31 * result + artifactId.hashCode();
+        result = 31 * result + (type != null ? type.hashCode() : 0);
+        result = 31 * result + (classifier != null ? classifier.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder key = new StringBuilder();
+        key.append(groupId).append(KEY_SEPARATOR).append(artifactId).append(KEY_SEPARATOR).append(type);
+
+        if(classifier != null) {
+            key.append(KEY_SEPARATOR).append(classifier);
+        }
+
+        return key.toString();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/PomDependencyMgt.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/PomDependencyMgt.java
new file mode 100644
index 0000000..e8f34a0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/PomDependencyMgt.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data;
+
+import org.apache.ivy.core.module.id.ModuleId;
+
+import java.util.List;
+
+public interface PomDependencyMgt {
+    MavenDependencyKey getId();
+    String getGroupId();
+    String getArtifactId();
+    String getVersion();
+    String getScope();
+    String getType();
+    String getClassifier();
+    List<ModuleId> getExcludedModules();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/PomProfile.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/PomProfile.java
new file mode 100644
index 0000000..c08abff
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/PomProfile.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.PomReader.PomDependencyData;
+
+import java.util.List;
+import java.util.Map;
+
+public interface PomProfile {
+    String getId();
+    Map<String, String> getProperties();
+    List<PomDependencyMgt> getDependencyMgts();
+    List<PomDependencyData> getDependencies();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ChainVersionMatcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ChainVersionMatcher.java
new file mode 100644
index 0000000..8f69f51
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ChainVersionMatcher.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+
+import java.util.*;
+
+public class ChainVersionMatcher implements VersionMatcher {
+    private final List<VersionMatcher> matchers = Lists.newArrayList();
+
+    public void add(VersionMatcher matcher) {
+        matchers.add(matcher);
+    }
+
+    public boolean canHandle(String selector) {
+        // not expected to be called
+        throw new UnsupportedOperationException("canHandle");
+    }
+
+    public boolean isDynamic(String selector) {
+        return getCompatibleMatcher(selector).isDynamic(selector);
+    }
+
+    public boolean needModuleMetadata(String selector) {
+        return getCompatibleMatcher(selector).needModuleMetadata(selector);
+    }
+
+    public boolean accept(String selector, String candidate) {
+        return getCompatibleMatcher(selector).accept(selector, candidate);
+    }
+
+    public boolean accept(String selector, ModuleVersionMetaData candidate) {
+        return getCompatibleMatcher(selector).accept(selector, candidate);
+    }
+
+    public int compare(String selector, String candidate) {
+        return getCompatibleMatcher(selector).compare(selector, candidate);
+    }
+
+    private VersionMatcher getCompatibleMatcher(String selector) {
+        for (VersionMatcher matcher : matchers) {
+            if (matcher.canHandle(selector)) {
+                return matcher;
+            }
+        }
+        throw new IllegalArgumentException("Invalid version selector: " + selector);
+    }
+}
+
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ExactVersionMatcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ExactVersionMatcher.java
new file mode 100644
index 0000000..75b5a7d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ExactVersionMatcher.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Version matcher for "static" version selectors (1.0, 1.2.3, etc.).
+ */
+public class ExactVersionMatcher implements VersionMatcher {
+    private static final Map<String, Integer> SPECIAL_MEANINGS =
+            ImmutableMap.of("dev", new Integer(-1), "rc", new Integer(1), "final", new Integer(2));
+
+    public boolean canHandle(String selector) {
+        return true;
+    }
+
+    public boolean isDynamic(String selector) {
+        return false;
+    }
+
+    public boolean needModuleMetadata(String selector) {
+        return false;
+    }
+
+    public boolean accept(String selector, String candidate) {
+        return selector.equals(candidate);
+    }
+
+    public boolean accept(String selector, ModuleVersionMetaData candidate) {
+        return accept(selector, candidate.getId().getVersion());
+    }
+
+    /**
+     * Compares a static selector with a candidate version. Algorithm is inspired
+     * by PHP version_compare one.
+     *
+     * TODO: compare() is inconsistent with accept(), because not everything
+     * that compares equal is accepted (e.g. 1.0 vs. 1_0). Can this cause problems?
+     */
+    public int compare(String selector, String candidate) {
+        if (selector.equals(candidate)) {
+            return 0;
+        }
+
+        selector = selector.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
+        selector = selector.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
+        candidate = candidate.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
+        candidate = candidate.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
+
+        String[] parts1 = selector.split("[\\._\\-\\+]");
+        String[] parts2 = candidate.split("[\\._\\-\\+]");
+
+        int i = 0;
+        for (; i < parts1.length && i < parts2.length; i++) {
+            if (parts1[i].equals(parts2[i])) {
+                continue;
+            }
+            boolean is1Number = isNumber(parts1[i]);
+            boolean is2Number = isNumber(parts2[i]);
+            if (is1Number && !is2Number) {
+                return 1;
+            }
+            if (is2Number && !is1Number) {
+                return -1;
+            }
+            if (is1Number && is2Number) {
+                return Long.valueOf(parts1[i]).compareTo(Long.valueOf(parts2[i]));
+            }
+            // both are strings, we compare them taking into account special meaning
+            Integer sm1 = SPECIAL_MEANINGS.get(parts1[i].toLowerCase(Locale.US));
+            Integer sm2 = SPECIAL_MEANINGS.get(parts2[i].toLowerCase(Locale.US));
+            if (sm1 != null) {
+                sm2 = sm2 == null ? 0 : sm2;
+                return sm1 - sm2;
+            }
+            if (sm2 != null) {
+                return -sm2;
+            }
+            return parts1[i].compareTo(parts2[i]);
+        }
+        if (i < parts1.length) {
+            return isNumber(parts1[i]) ? 1 : -1;
+        }
+        if (i < parts2.length) {
+            return isNumber(parts2[i]) ? -1 : 1;
+        }
+
+        return 0;
+    }
+
+    private boolean isNumber(String str) {
+        return str.matches("\\d+");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestStrategy.java
new file mode 100644
index 0000000..c442e20
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestStrategy.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.Versioned;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+public interface LatestStrategy extends Comparator<Versioned> {
+    /**
+     * Finds the latest among the given versioned elements. The definition of 'latest'
+     * depends on the strategy itself.
+     */
+    <T extends Versioned> T findLatest(Collection<T> elements);
+
+    /**
+     * Sorts the given versioned elements from oldest to latest. The definition of
+     * 'latest' depends on the strategy itself.
+     */
+    <T extends Versioned> List<T> sort(Collection<T> elements);
+
+    /**
+     * Compares two versioned elements to see which is the 'latest'. The definition of
+     * 'latest' depends on the strategy itself.
+     */
+    int compare(Versioned element1, Versioned element2);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionMatcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionMatcher.java
new file mode 100644
index 0000000..20372db
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionMatcher.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+
+public class LatestVersionMatcher implements VersionMatcher {
+    public boolean canHandle(String selector) {
+        return selector.startsWith("latest.");
+    }
+
+    public boolean isDynamic(String selector) {
+        return true;
+    }
+
+    public boolean needModuleMetadata(String selector) {
+        return true;
+    }
+
+    public boolean accept(String selector, String candidate) {
+        throw new UnsupportedOperationException("accept(String, String)");
+    }
+
+    public boolean accept(String selector, ModuleVersionMetaData candidate) {
+        String selectorStatus = selector.substring("latest.".length());
+        int selectorStatusIndex = candidate.getStatusScheme().indexOf(selectorStatus);
+        int candidateStatusIndex = candidate.getStatusScheme().indexOf(candidate.getStatus());
+        return selectorStatusIndex >=0 && selectorStatusIndex <= candidateStatusIndex;
+    }
+
+    public int compare(String selector, String candidate) {
+        return 0;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionStrategy.java
new file mode 100644
index 0000000..1be6c40
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionStrategy.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.Versioned;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class LatestVersionStrategy implements LatestStrategy {
+    private final VersionMatcher versionMatcher;
+
+    public LatestVersionStrategy(VersionMatcher versionMatcher) {
+        this.versionMatcher = versionMatcher;
+    }
+
+    public <T extends Versioned> List<T> sort(Collection<T> versions) {
+        return CollectionUtils.sort(versions, this);
+    }
+
+    public <T extends Versioned> T findLatest(Collection<T> elements) {
+        return Collections.max(elements, this);
+    }
+
+    // TODO: Compared to Ivy's LatestRevisionStrategy.compare(), this method doesn't do any
+    // equivalent of ModuleRevisionId.normalizeRevision(). Should we add this functionality
+    // back in, either here or (probably better) in (Chain)VersionMatcher?
+    public int compare(Versioned element1, Versioned element2) {
+        String version1 = element1.getVersion();
+        String version2 = element2.getVersion();
+
+        /*
+         * The revisions can still be not resolved, so we use the current version matcher to
+         * know if one revision is dynamic, and in this case if it should be considered greater
+         * or lower than the other one. Note that if the version matcher compare method returns
+         * 0, it's because it's not possible to know which revision is greater. In this case we
+         * consider the dynamic one to be greater, because most of the time it will then be
+         * actually resolved and a real comparison will occur.
+         */
+        if (versionMatcher.isDynamic(version1)) {
+            int c = versionMatcher.compare(version1, version2);
+            return c >= 0 ? 1 : -1;
+        } else if (versionMatcher.isDynamic(version2)) {
+            int c = versionMatcher.compare(version2, version1);
+            return c >= 0 ? -1 : 1;
+        }
+
+        return versionMatcher.compare(version1, version2);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ResolverStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ResolverStrategy.java
new file mode 100644
index 0000000..9ff546e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ResolverStrategy.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import com.google.common.collect.Maps;
+import org.apache.ivy.plugins.matcher.*;
+
+import java.util.Map;
+
+public class ResolverStrategy {
+    private final VersionMatcher versionMatcher;
+    private final Map<String, PatternMatcher> matchers = Maps.newHashMap();
+
+    public ResolverStrategy() {
+        ChainVersionMatcher chain = new ChainVersionMatcher();
+        chain.add(new VersionRangeMatcher(new ExactVersionMatcher()));
+        chain.add(new SubVersionMatcher(new ExactVersionMatcher()));
+        chain.add(new LatestVersionMatcher());
+        chain.add(new ExactVersionMatcher());
+        versionMatcher = chain;
+
+        addMatcher(ExactPatternMatcher.INSTANCE);
+        addMatcher(RegexpPatternMatcher.INSTANCE);
+        addMatcher(ExactOrRegexpPatternMatcher.INSTANCE);
+        addMatcher(GlobPatternMatcher.INSTANCE);
+    }
+
+    private void addMatcher(PatternMatcher instance) {
+        matchers.put(instance.getName(), instance);
+    }
+
+    public VersionMatcher getVersionMatcher() {
+        return versionMatcher;
+    }
+
+    public PatternMatcher getPatternMatcher(String name) {
+        return matchers.get(name);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/SubVersionMatcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/SubVersionMatcher.java
new file mode 100644
index 0000000..e8ac493
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/SubVersionMatcher.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+
+import java.util.Comparator;
+
+/**
+ * Version matcher for dynamic version selectors ending in '+'.
+ */
+public class SubVersionMatcher implements VersionMatcher {
+    private final Comparator<String> staticVersionComparator;
+
+    public SubVersionMatcher(VersionMatcher staticVersionComparator) {
+        this.staticVersionComparator = staticVersionComparator;
+    }
+
+    public boolean canHandle(String selector) {
+        return selector.endsWith("+");
+    }
+
+    public boolean isDynamic(String selector) {
+        return true;
+    }
+
+    public boolean needModuleMetadata(String selector) {
+        return false;
+    }
+
+    public boolean accept(String selector, String candidate) {
+        String prefix = selector.substring(0, selector.length() - 1);
+        return candidate.startsWith(prefix);
+    }
+
+    public boolean accept(String selector, ModuleVersionMetaData candidate) {
+        return accept(selector, candidate.getId().getVersion());
+    }
+
+    public int compare(String selector, String candidate) {
+        if (accept(selector, candidate)) {
+            return 1;
+        }
+        return staticVersionComparator.compare(selector, candidate);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionMatcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionMatcher.java
new file mode 100644
index 0000000..374dc13
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionMatcher.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+
+import java.util.Comparator;
+
+/**
+ * Compares version selectors against candidate versions, indicating whether they match or not.
+ *
+ * <p>This interface was initially derived from {@code org.apache.ivy.plugins.version.VersionMatcher}.
+ */
+public interface VersionMatcher extends Comparator<String> {
+    /**
+     * Tells if this version matcher can handle the given version selector. If {@code false}
+     * is returned, none of the other methods will be called for the given selector.
+     */
+    public boolean canHandle(String selector);
+
+    /**
+     * Indicates if the given version selector is dynamic.
+     */
+    public boolean isDynamic(String selector);
+
+    /**
+     * Indicates if module metadata is required to determine if the given version
+     * selector matches a candidate version.
+     */
+    public boolean needModuleMetadata(String selector);
+
+    /**
+     * Indicates if the given version selector matches the given candidate version.
+     * Only called if {@link #needModuleMetadata} returned {@code false} for the given selector.
+     */
+    public boolean accept(String selector, String candidate);
+
+    /**
+     * Indicates if the given version selector matches the given given candidate version
+     * (whose metadata is provided). May also be called if {@link #isDynamic} returned
+     * {@code false} for the given selector, in which case it should return the same result as
+     * {@code accept(selector, candidate.getId().getVersion()}.
+     */
+    public boolean accept(String selector, ModuleVersionMetaData candidate);
+
+    /**
+     *
+     * Compares a version selector with a candidate version to indicate which is greater. If there is
+     * not enough information to tell which is greater, the version selector should be considered greater
+     * and this method should return 0.
+     */
+    public int compare(String selector, String candidate);
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeMatcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeMatcher.java
new file mode 100644
index 0000000..b05899b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeMatcher.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy;
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+
+import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Matches version ranges: [1.0,2.0] matches all versions greater or equal to 1.0 and lower or equal
+ * to 2.0 [1.0,2.0[ matches all versions greater or equal to 1.0 and lower than 2.0 ]1.0,2.0]
+ * matches all versions greater than 1.0 and lower or equal to 2.0 ]1.0,2.0[ matches all versions
+ * greater than 1.0 and lower than 2.0 [1.0,) matches all versions greater or equal to 1.0 ]1.0,)
+ * matches all versions greater than 1.0 (,2.0] matches all versions lower or equal to 2.0 (,2.0[
+ * matches all versions lower than 2.0 This class uses a latest strategy to compare revisions. If
+ * none is set, it uses the default one of the ivy instance set through setIvy(). If neither a
+ * latest strategy nor a ivy instance is set, an IllegalStateException will be thrown when calling
+ * accept(). Note that it can't work with latest time strategy, cause no time is known for the
+ * limits of the range. Therefore only purely revision based LatestStrategy can be used.
+ */
+public class VersionRangeMatcher implements VersionMatcher {
+    private static final String OPEN_INC = "[";
+
+    private static final String OPEN_EXC = "]";
+    private static final String OPEN_EXC_MAVEN = "(";
+
+    private static final String CLOSE_INC = "]";
+
+    private static final String CLOSE_EXC = "[";
+    private static final String CLOSE_EXC_MAVEN = ")";
+
+    private static final String LOWER_INFINITE = "(";
+
+    private static final String UPPER_INFINITE = ")";
+
+    private static final String SEPARATOR = ",";
+
+    // following patterns are built upon constants above and should not be modified
+    private static final String OPEN_INC_PATTERN = "\\" + OPEN_INC;
+
+    private static final String OPEN_EXC_PATTERN = "\\" + OPEN_EXC + "\\" + OPEN_EXC_MAVEN;
+
+    private static final String CLOSE_INC_PATTERN = "\\" + CLOSE_INC;
+
+    private static final String CLOSE_EXC_PATTERN = "\\" + CLOSE_EXC + "\\" + CLOSE_EXC_MAVEN;
+
+    private static final String LI_PATTERN = "\\" + LOWER_INFINITE;
+
+    private static final String UI_PATTERN = "\\" + UPPER_INFINITE;
+
+    private static final String SEP_PATTERN = "\\s*\\" + SEPARATOR + "\\s*";
+
+    private static final String OPEN_PATTERN = "[" + OPEN_INC_PATTERN + OPEN_EXC_PATTERN + "]";
+
+    private static final String CLOSE_PATTERN = "[" + CLOSE_INC_PATTERN + CLOSE_EXC_PATTERN + "]";
+
+    private static final String ANY_NON_SPECIAL_PATTERN = "[^\\s" + SEPARATOR + OPEN_INC_PATTERN
+            + OPEN_EXC_PATTERN + CLOSE_INC_PATTERN + CLOSE_EXC_PATTERN + LI_PATTERN + UI_PATTERN
+            + "]";
+
+    private static final String FINITE_PATTERN = OPEN_PATTERN + "\\s*(" + ANY_NON_SPECIAL_PATTERN
+            + "+)" + SEP_PATTERN + "(" + ANY_NON_SPECIAL_PATTERN + "+)\\s*" + CLOSE_PATTERN;
+
+    private static final String LOWER_INFINITE_PATTERN = LI_PATTERN + SEP_PATTERN + "("
+            + ANY_NON_SPECIAL_PATTERN + "+)\\s*" + CLOSE_PATTERN;
+
+    private static final String UPPER_INFINITE_PATTERN = OPEN_PATTERN + "\\s*("
+            + ANY_NON_SPECIAL_PATTERN + "+)" + SEP_PATTERN + UI_PATTERN;
+
+    private static final Pattern FINITE_RANGE = Pattern.compile(FINITE_PATTERN);
+
+    private static final Pattern LOWER_INFINITE_RANGE = Pattern.compile(LOWER_INFINITE_PATTERN);
+
+    private static final Pattern UPPER_INFINITE_RANGE = Pattern.compile(UPPER_INFINITE_PATTERN);
+
+    private static final Pattern ALL_RANGE = Pattern.compile(FINITE_PATTERN + "|"
+            + LOWER_INFINITE_PATTERN + "|" + UPPER_INFINITE_PATTERN);
+
+    private final Comparator<String> staticVersionComparator;
+
+    public VersionRangeMatcher(VersionMatcher staticVersionComparator) {
+        this.staticVersionComparator = staticVersionComparator;
+    }
+
+    public boolean canHandle(String selector) {
+        return ALL_RANGE.matcher(selector).matches();
+    }
+
+    public boolean isDynamic(String selector) {
+        return true;
+    }
+
+    public boolean needModuleMetadata(String selector) {
+        return false;
+    }
+
+    public boolean accept(String selector, String candidate) {
+        Matcher matcher;
+        matcher = FINITE_RANGE.matcher(selector);
+        if (matcher.matches()) {
+            String lower = matcher.group(1);
+            String upper = matcher.group(2);
+            return isHigher(candidate, lower, selector.startsWith(OPEN_INC))
+                    && isLower(candidate, upper, selector.endsWith(CLOSE_INC));
+        }
+        matcher = LOWER_INFINITE_RANGE.matcher(selector);
+        if (matcher.matches()) {
+            String upper = matcher.group(1);
+            return isLower(candidate, upper, selector.endsWith(CLOSE_INC));
+        }
+        matcher = UPPER_INFINITE_RANGE.matcher(selector);
+        if (matcher.matches()) {
+            String lower = matcher.group(1);
+            return isHigher(candidate, lower, selector.startsWith(OPEN_INC));
+        }
+        throw new IllegalArgumentException("Not a version range selector: " + selector);
+    }
+
+    public boolean accept(String selector, ModuleVersionMetaData candidate) {
+        return accept(selector, candidate.getId().getVersion());
+    }
+
+    // doesn't seem to be quite in sync with accept() (e.g. open/close is not distinguished here)
+    public int compare(String selector, String candidate) {
+        Matcher m;
+        m = UPPER_INFINITE_RANGE.matcher(selector);
+        if (m.matches()) {
+            // no upper limit, the selector can always be considered greater
+            return 1;
+        }
+        String upper;
+        m = FINITE_RANGE.matcher(selector);
+        if (m.matches()) {
+            upper = m.group(2);
+        } else {
+            m = LOWER_INFINITE_RANGE.matcher(selector);
+            if (m.matches()) {
+                upper = m.group(1);
+            } else {
+                throw new IllegalArgumentException("Not a version range selector: " + selector);
+            }
+        }
+        int c = staticVersionComparator.compare(upper, candidate);
+        // If the comparison considers them equal, we must return -1, because we can't consider the
+        // dynamic version selector to be greater. Otherwise we can safely return the result of the static
+        // comparison.
+        return c == 0 ? -1 : c;
+    }
+
+    /**
+     * Tells if version1 is lower than version2.
+     */
+    private boolean isLower(String version1, String version2, boolean inclusive) {
+        int result = staticVersionComparator.compare(version1, version2);
+        return result <= (inclusive ? 0 : -1);
+    }
+
+    /**
+     * Tells if version1 is higher than version2.
+     */
+    private boolean isHigher(String version1, String version2, boolean inclusive) {
+        int result = staticVersionComparator.compare(version1, version2);
+        return result >= (inclusive ? 0 : 1);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java
new file mode 100644
index 0000000..3345e16
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.modulecache;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+
+/**
+ * Context used for parsing cached module descriptor files.
+ * Will only be used for parsing ivy.xml files, as pom files are converted before caching.
+ */
+class CachedModuleDescriptorParseContext implements DescriptorParseContext {
+
+    public boolean artifactExists(ModuleVersionArtifactMetaData artifact) {
+        throw new UnsupportedOperationException();
+    }
+
+    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, Class<? extends SoftwareArtifact> artifactType) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedMetaData.java
new file mode 100644
index 0000000..37755d9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedMetaData.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.modulecache;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleDescriptorAdapter;
+import org.gradle.util.BuildCommencedTimeProvider;
+
+import java.math.BigInteger;
+
+class DefaultCachedMetaData implements ModuleMetaDataCache.CachedMetaData {
+    private final ModuleSource moduleSource;
+    private final BigInteger descriptorHash;
+    private final long ageMillis;
+    private final MutableModuleVersionMetaData metaData;
+
+    public DefaultCachedMetaData(ModuleDescriptorCacheEntry entry, ModuleDescriptor moduleDescriptor, BuildCommencedTimeProvider timeProvider) {
+        this.moduleSource = entry.moduleSource;
+        this.descriptorHash = entry.moduleDescriptorHash;
+        this.ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
+        if (moduleDescriptor == null) {
+            metaData = null;
+        } else {
+            ModuleDescriptorAdapter moduleDescriptorAdapter = new ModuleDescriptorAdapter(moduleDescriptor);
+            moduleDescriptorAdapter.setChanging(entry.isChanging);
+            moduleDescriptorAdapter.setMetaDataOnly(entry.isMetaDataOnly);
+            metaData = moduleDescriptorAdapter;
+        }
+    }
+
+    public boolean isMissing() {
+        return metaData == null;
+    }
+
+    public ModuleSource getModuleSource() {
+        return moduleSource;
+    }
+
+    public ResolvedModuleVersion getModuleVersion() {
+        return isMissing() ? null : new DefaultResolvedModuleVersion(getMetaData().getId());
+    }
+
+    public MutableModuleVersionMetaData getMetaData() {
+        return metaData;
+    }
+
+    public long getAgeMillis() {
+        return ageMillis;
+    }
+
+    public BigInteger getDescriptorHash() {
+        return descriptorHash;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
deleted file mode 100644
index 6596c0e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
+++ /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.api.internal.artifacts.ivyservice.modulecache;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ResolvedModuleVersion;
-import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
-import org.gradle.internal.TimeProvider;
-
-import java.math.BigInteger;
-
-class DefaultCachedModuleDescriptor implements ModuleDescriptorCache.CachedModuleDescriptor {
-    private final ModuleDescriptor moduleDescriptor;
-    private final ModuleSource moduleSource;
-    private final BigInteger descriptorHash;
-    private final boolean isChangingModule;
-    private final long ageMillis;
-
-    public DefaultCachedModuleDescriptor(ModuleDescriptorCacheEntry entry, ModuleDescriptor moduleDescriptor, TimeProvider timeProvider) {
-        this.moduleDescriptor = moduleDescriptor;
-        this.moduleSource = entry.moduleSource;
-        this.isChangingModule = entry.isChanging;
-        this.descriptorHash = entry.moduleDescriptorHash;
-        this.ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
-    }
-
-    public boolean isMissing() {
-        return moduleDescriptor == null;
-    }
-
-    public ModuleSource getModuleSource() {
-        return moduleSource;
-    }
-
-    public ResolvedModuleVersion getModuleVersion() {
-        ModuleRevisionId moduleRevisionId = isMissing() ? null : moduleDescriptor.getModuleRevisionId();
-        return new DefaultResolvedModuleVersion(moduleRevisionId);
-    }
-
-    public ModuleDescriptor getModuleDescriptor() {
-        return moduleDescriptor;
-    }
-
-    public boolean isChangingModule() {
-        return isChangingModule;
-    }
-
-    public long getAgeMillis() {
-        return ageMillis;
-    }
-
-    public BigInteger getDescriptorHash() {
-        return descriptorHash;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleArtifactsCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleArtifactsCache.java
new file mode 100644
index 0000000..eff486c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleArtifactsCache.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.modulecache;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifierSerializer;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+import org.gradle.messaging.serialize.SetSerializer;
+import org.gradle.util.BuildCommencedTimeProvider;
+
+import java.math.BigInteger;
+import java.util.Set;
+
+public class DefaultModuleArtifactsCache implements ModuleArtifactsCache {
+    private final BuildCommencedTimeProvider timeProvider;
+    private final CacheLockingManager cacheLockingManager;
+    private PersistentIndexedCache<ModuleArtifactsKey, ModuleArtifactsCacheEntry> cache;
+
+    public DefaultModuleArtifactsCache(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        this.timeProvider = timeProvider;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    private PersistentIndexedCache<ModuleArtifactsKey, ModuleArtifactsCacheEntry> getCache() {
+        if (cache == null) {
+            cache = initCache();
+        }
+        return cache;
+    }
+
+    private PersistentIndexedCache<ModuleArtifactsKey, ModuleArtifactsCacheEntry> initCache() {
+        return cacheLockingManager.createCache("module-artifacts", new ModuleArtifactsKeySerializer(), new ModuleArtifactsCacheEntrySerializer());
+    }
+
+    public CachedArtifacts cacheArtifacts(ModuleVersionRepository repository, ModuleVersionIdentifier moduleMetaDataId, String context, BigInteger descriptorHash, Set<ModuleVersionArtifactIdentifier> artifacts) {
+        ModuleArtifactsKey key = new ModuleArtifactsKey(repository.getId(), moduleMetaDataId, context);
+        ModuleArtifactsCacheEntry entry = new ModuleArtifactsCacheEntry(artifacts, timeProvider.getCurrentTime(), descriptorHash);
+        getCache().put(key, entry);
+        return createCacheArtifacts(entry);
+    }
+
+    public CachedArtifacts getCachedArtifacts(ModuleVersionRepository repository, ModuleVersionIdentifier moduleMetaDataId, String context) {
+        ModuleArtifactsKey key = new ModuleArtifactsKey(repository.getId(), moduleMetaDataId, context);
+        ModuleArtifactsCacheEntry entry = getCache().get(key);
+        if (entry == null) {
+            return null;
+        }
+        return createCacheArtifacts(entry);
+    }
+
+    private CachedArtifacts createCacheArtifacts(ModuleArtifactsCacheEntry entry) {
+        long entryAge = timeProvider.getCurrentTime() - entry.createTimestamp;
+        return new DefaultCachedArtifacts(entry.artifacts, entry.moduleDescriptorHash, entryAge);
+    }
+
+    private static class ModuleArtifactsKey {
+        private final String repositoryId;
+        private final ModuleVersionIdentifier moduleId;
+        private final String context;
+
+        private ModuleArtifactsKey(String repositoryId, ModuleVersionIdentifier moduleId, String context) {
+            this.repositoryId = repositoryId;
+            this.moduleId = moduleId;
+            this.context = context;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof ModuleArtifactsKey)) {
+                return false;
+            }
+
+            ModuleArtifactsKey that = (ModuleArtifactsKey) o;
+            return repositoryId.equals(that.repositoryId) && moduleId.equals(that.moduleId) && context.equals(that.context);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = repositoryId.hashCode();
+            result = 31 * result + moduleId.hashCode();
+            result = 31 * result + context.hashCode();
+            return result;
+        }
+    }
+
+    private static class ModuleArtifactsKeySerializer implements Serializer<ModuleArtifactsKey> {
+        private final ModuleVersionIdentifierSerializer identifierSerializer = new ModuleVersionIdentifierSerializer();
+
+        public void write(Encoder encoder, ModuleArtifactsKey value) throws Exception {
+            encoder.writeString(value.repositoryId);
+            identifierSerializer.write(encoder, value.moduleId);
+            encoder.writeString(value.context);
+        }
+
+        public ModuleArtifactsKey read(Decoder decoder) throws Exception {
+            String resolverId = decoder.readString();
+            ModuleVersionIdentifier moduleVersionIdentifier = identifierSerializer.read(decoder);
+            String context = decoder.readString();
+            return new ModuleArtifactsKey(resolverId, moduleVersionIdentifier, context);
+        }
+    }
+
+    private static class ModuleArtifactsCacheEntry {
+        private final Set<ModuleVersionArtifactIdentifier> artifacts;
+        public BigInteger moduleDescriptorHash;
+        public long createTimestamp;
+
+        ModuleArtifactsCacheEntry(Set<ModuleVersionArtifactIdentifier> artifacts, long createTimestamp, BigInteger moduleDescriptorHash) {
+            this.artifacts = artifacts;
+            this.createTimestamp = createTimestamp;
+            this.moduleDescriptorHash = moduleDescriptorHash;
+        }
+    }
+
+
+    private static class ModuleArtifactsCacheEntrySerializer implements Serializer<ModuleArtifactsCacheEntry> {
+        private final Serializer<Set<ModuleVersionArtifactIdentifier>> artifactsSerializer = 
+                new SetSerializer<ModuleVersionArtifactIdentifier>(new ModuleVersionArtifactIdentifierSerializer());
+        public void write(Encoder encoder, ModuleArtifactsCacheEntry value) throws Exception {
+            encoder.writeLong(value.createTimestamp);
+            byte[] hash = value.moduleDescriptorHash.toByteArray();
+            encoder.writeBinary(hash);
+            artifactsSerializer.write(encoder, value.artifacts);
+        }
+
+        public ModuleArtifactsCacheEntry read(Decoder decoder) throws Exception {
+            long createTimestamp = decoder.readLong();
+            byte[] encodedHash = decoder.readBinary();
+            BigInteger hash = new BigInteger(encodedHash);
+            Set<ModuleVersionArtifactIdentifier> artifacts = artifactsSerializer.read(decoder);
+            return new ModuleArtifactsCacheEntry(artifacts, createTimestamp, hash);
+        }
+    }
+
+    private static class DefaultCachedArtifacts implements ModuleArtifactsCache.CachedArtifacts {
+        private final Set<ModuleVersionArtifactIdentifier> artifacts;
+        private final BigInteger descriptorHash;
+        private final long ageMillis;
+
+        private DefaultCachedArtifacts(Set<ModuleVersionArtifactIdentifier> artifacts, BigInteger descriptorHash, long ageMillis) {
+            this.ageMillis = ageMillis;
+            this.artifacts = artifacts;
+            this.descriptorHash = descriptorHash;
+        }
+
+        public Set<ModuleVersionArtifactIdentifier> getArtifacts() {
+            return artifacts;
+        }
+
+        public BigInteger getDescriptorHash() {
+            return descriptorHash;
+        }
+
+        public long getAgeMillis() {
+            return ageMillis;
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
deleted file mode 100644
index 715c70c..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
+++ /dev/null
@@ -1,188 +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.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
-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.ModuleSource;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
-import org.gradle.api.internal.filestore.FileStoreEntry;
-import org.gradle.api.internal.filestore.PathKeyFileStore;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.internal.TimeProvider;
-import org.gradle.messaging.serialize.DataStreamBackedSerializer;
-import org.gradle.messaging.serialize.DefaultSerializer;
-import org.gradle.util.hash.HashValue;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.math.BigInteger;
-
-public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultModuleDescriptorCache.class);
-
-    private final TimeProvider timeProvider;
-    private final ArtifactCacheMetaData cacheMetadata;
-    private final CacheLockingManager cacheLockingManager;
-
-    private final ModuleDescriptorStore moduleDescriptorStore;
-    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> cache;
-
-    public DefaultModuleDescriptorCache(ArtifactCacheMetaData cacheMetadata, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
-        this.timeProvider = timeProvider;
-        this.cacheLockingManager = cacheLockingManager;
-        this.cacheMetadata = cacheMetadata;
-
-        moduleDescriptorStore = new ModuleDescriptorStore(new PathKeyFileStore(cacheMetadata.getCacheDir()), new IvyXmlModuleDescriptorWriter(), new IvyXmlModuleDescriptorParser());
-    }
-
-    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> getCache() {
-        if (cache == null) {
-            cache = initCache();
-        }
-        return cache;
-    }
-
-    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> initCache() {
-        File artifactResolutionCacheFile = new File(cacheMetadata.getCacheDir(), "module-metadata.bin");
-        return cacheLockingManager.createCache(artifactResolutionCacheFile, new RevisionKeySerializer(), new ModuleDescriptorCacheEntrySerializer());
-    }
-
-    public CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
-        ModuleDescriptorCacheEntry moduleDescriptorCacheEntry = getCache().get(createKey(repository, moduleVersionIdentifier));
-        if (moduleDescriptorCacheEntry == null) {
-            return null;
-        }
-        if (moduleDescriptorCacheEntry.isMissing) {
-            return new DefaultCachedModuleDescriptor(moduleDescriptorCacheEntry, null, timeProvider);
-        }
-        ModuleDescriptor descriptor = moduleDescriptorStore.getModuleDescriptor(repository, moduleVersionIdentifier);
-        if (descriptor == null) {
-            // Descriptor file has been manually deleted - ignore the entry
-            return null;
-        }
-        return new DefaultCachedModuleDescriptor(moduleDescriptorCacheEntry, descriptor, timeProvider);
-    }
-
-    public CachedModuleDescriptor cacheModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier, ModuleDescriptor moduleDescriptor, ModuleSource moduleSource, boolean isChanging) {
-        ModuleDescriptorCacheEntry entry;
-        if (moduleDescriptor == null) {
-            LOGGER.debug("Recording absence of module descriptor in cache: {} [changing = {}]", moduleVersionIdentifier, isChanging);
-            entry = createMissingEntry(isChanging);
-            getCache().put(createKey(repository, moduleVersionIdentifier), entry);
-        } else {
-            LOGGER.debug("Recording module descriptor in cache: {} [changing = {}]", moduleDescriptor.getModuleRevisionId(), isChanging);
-            FileStoreEntry fileStoreEntry = moduleDescriptorStore.putModuleDescriptor(repository, moduleDescriptor);
-            entry = createEntry(isChanging, fileStoreEntry.getSha1(), moduleSource);
-            getCache().put(createKey(repository, moduleVersionIdentifier), entry);
-        }
-        return new DefaultCachedModuleDescriptor(entry, null, timeProvider);
-    }
-
-    private RevisionKey createKey(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
-        return new RevisionKey(repository.getId(), moduleVersionIdentifier);
-    }
-
-    private ModuleDescriptorCacheEntry createMissingEntry(boolean changing) {
-        return new ModuleDescriptorCacheEntry(changing, true, timeProvider.getCurrentTime(), BigInteger.ZERO, null);
-    }
-
-    private ModuleDescriptorCacheEntry createEntry(boolean changing, HashValue moduleDescriptorHash, ModuleSource moduleSource) {
-        return new ModuleDescriptorCacheEntry(changing, false, timeProvider.getCurrentTime(), moduleDescriptorHash.asBigInteger(), moduleSource);
-    }
-
-    private static class RevisionKey {
-        private final String repositoryId;
-        private final ModuleVersionIdentifier moduleVersionIdentifier;
-
-        private RevisionKey(String repositoryId, ModuleVersionIdentifier moduleVersionIdentifier) {
-            this.repositoryId = repositoryId;
-            this.moduleVersionIdentifier = moduleVersionIdentifier;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == null || !(o instanceof RevisionKey)) {
-                return false;
-            }
-            RevisionKey other = (RevisionKey) o;
-            return repositoryId.equals(other.repositoryId) && moduleVersionIdentifier.equals(other.moduleVersionIdentifier);
-        }
-
-        @Override
-        public int hashCode() {
-            return repositoryId.hashCode() ^ moduleVersionIdentifier.hashCode();
-        }
-    }
-
-    private static class RevisionKeySerializer extends DataStreamBackedSerializer<RevisionKey> {
-        private final ModuleVersionIdentifierSerializer identifierSerializer = new ModuleVersionIdentifierSerializer();
-
-        @Override
-        public void write(DataOutput dataOutput, RevisionKey value) throws IOException {
-            dataOutput.writeUTF(value.repositoryId);
-            identifierSerializer.write(dataOutput, value.moduleVersionIdentifier);
-        }
-
-        @Override
-        public RevisionKey read(DataInput dataInput) throws IOException {
-            String resolverId = dataInput.readUTF();
-            ModuleVersionIdentifier identifier = identifierSerializer.read(dataInput);
-            return new RevisionKey(resolverId, identifier);
-        }
-    }
-
-    private static class ModuleDescriptorCacheEntrySerializer extends DataStreamBackedSerializer<ModuleDescriptorCacheEntry> {
-        private final DefaultSerializer<ModuleSource> moduleSourceSerializer = new DefaultSerializer<ModuleSource>(ModuleSource.class.getClassLoader());
-
-        @Override
-        public void write(DataOutput dataOutput, ModuleDescriptorCacheEntry value) throws IOException {
-            dataOutput.writeBoolean(value.isMissing);
-            dataOutput.writeBoolean(value.isChanging);
-            dataOutput.writeLong(value.createTimestamp);
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            moduleSourceSerializer.write(outputStream, value.moduleSource);
-            byte[] serializedModuleSource = outputStream.toByteArray();
-            dataOutput.writeInt(serializedModuleSource.length);
-            dataOutput.write(serializedModuleSource);
-            byte[] hash = value.moduleDescriptorHash.toByteArray();
-            dataOutput.writeInt(hash.length);
-            dataOutput.write(hash);
-        }
-
-        @Override
-        public ModuleDescriptorCacheEntry read(DataInput dataInput) throws Exception {
-            boolean isMissing = dataInput.readBoolean();
-            boolean isChanging = dataInput.readBoolean();
-            long createTimestamp = dataInput.readLong();
-            int count = dataInput.readInt();
-            byte[] serializedModuleSource = new byte[count];
-            dataInput.readFully(serializedModuleSource);
-            ModuleSource moduleSource = moduleSourceSerializer.read(new ByteArrayInputStream(serializedModuleSource));
-            count = dataInput.readInt();
-            byte[] encodedHash = new byte[count];
-            dataInput.readFully(encodedHash);
-            BigInteger hash = new BigInteger(encodedHash);
-            return new ModuleDescriptorCacheEntry(isChanging, isMissing, createTimestamp, hash, moduleSource);
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleMetaDataCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleMetaDataCache.java
new file mode 100644
index 0000000..05d953d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleMetaDataCache.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.modulecache;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.IvyXmlModuleDescriptorWriter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+import org.gradle.api.internal.filestore.PathKeyFileStore;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.hash.HashValue;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.DefaultSerializer;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+import org.gradle.util.BuildCommencedTimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+
+public class DefaultModuleMetaDataCache implements ModuleMetaDataCache {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultModuleMetaDataCache.class);
+
+    private final BuildCommencedTimeProvider timeProvider;
+    private final CacheLockingManager cacheLockingManager;
+
+    private final ModuleDescriptorStore moduleDescriptorStore;
+    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> cache;
+
+    public DefaultModuleMetaDataCache(BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager, ResolverStrategy resolverStrategy) {
+        this.timeProvider = timeProvider;
+        this.cacheLockingManager = cacheLockingManager;
+
+        moduleDescriptorStore = new ModuleDescriptorStore(new PathKeyFileStore(cacheLockingManager.createMetaDataStore()), new IvyXmlModuleDescriptorWriter(), new IvyXmlModuleDescriptorParser(resolverStrategy));
+    }
+
+    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> getCache() {
+        if (cache == null) {
+            cache = initCache();
+        }
+        return cache;
+    }
+
+    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> initCache() {
+        return cacheLockingManager.createCache("module-metadata", new RevisionKeySerializer(), new ModuleDescriptorCacheEntrySerializer());
+    }
+
+    public CachedMetaData getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
+        ModuleDescriptorCacheEntry moduleDescriptorCacheEntry = getCache().get(createKey(repository, moduleVersionIdentifier));
+        if (moduleDescriptorCacheEntry == null) {
+            return null;
+        }
+        if (moduleDescriptorCacheEntry.isMissing) {
+            return new DefaultCachedMetaData(moduleDescriptorCacheEntry, null, timeProvider);
+        }
+        ModuleDescriptor descriptor = moduleDescriptorStore.getModuleDescriptor(repository, moduleVersionIdentifier);
+        if (descriptor == null) {
+            // Descriptor file has been manually deleted - ignore the entry
+            return null;
+        }
+        return new DefaultCachedMetaData(moduleDescriptorCacheEntry, descriptor, timeProvider);
+    }
+
+    public CachedMetaData cacheMissing(ModuleVersionRepository repository, ModuleVersionIdentifier id) {
+        LOGGER.debug("Recording absence of module descriptor in cache: {} [changing = {}]", id, false);
+        ModuleDescriptorCacheEntry entry = createMissingEntry(false);
+        getCache().put(createKey(repository, id), entry);
+        return new DefaultCachedMetaData(entry, null, timeProvider);
+    }
+
+    public CachedMetaData cacheMetaData(ModuleVersionRepository repository, ModuleVersionMetaData metaData, ModuleSource moduleSource) {
+        ModuleDescriptor moduleDescriptor = metaData.getDescriptor();
+        LOGGER.debug("Recording module descriptor in cache: {} [changing = {}]", moduleDescriptor.getModuleRevisionId(), metaData.isChanging());
+        LocallyAvailableResource resource = moduleDescriptorStore.putModuleDescriptor(repository, moduleDescriptor);
+        ModuleDescriptorCacheEntry entry = createEntry(metaData.isChanging(), metaData.isMetaDataOnly(), resource.getSha1(), moduleSource);
+        getCache().put(createKey(repository, metaData.getId()), entry);
+        return new DefaultCachedMetaData(entry, null, timeProvider);
+    }
+
+    private RevisionKey createKey(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
+        return new RevisionKey(repository.getId(), moduleVersionIdentifier);
+    }
+
+    private ModuleDescriptorCacheEntry createMissingEntry(boolean changing) {
+        return new ModuleDescriptorCacheEntry(changing, false, true, timeProvider.getCurrentTime(), BigInteger.ZERO, null);
+    }
+
+    private ModuleDescriptorCacheEntry createEntry(boolean changing, boolean metaDataOnly, HashValue moduleDescriptorHash, ModuleSource moduleSource) {
+        return new ModuleDescriptorCacheEntry(changing, metaDataOnly, false, timeProvider.getCurrentTime(), moduleDescriptorHash.asBigInteger(), moduleSource);
+    }
+
+    private static class RevisionKey {
+        private final String repositoryId;
+        private final ModuleVersionIdentifier moduleVersionIdentifier;
+
+        private RevisionKey(String repositoryId, ModuleVersionIdentifier moduleVersionIdentifier) {
+            this.repositoryId = repositoryId;
+            this.moduleVersionIdentifier = moduleVersionIdentifier;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || !(o instanceof RevisionKey)) {
+                return false;
+            }
+            RevisionKey other = (RevisionKey) o;
+            return repositoryId.equals(other.repositoryId) && moduleVersionIdentifier.equals(other.moduleVersionIdentifier);
+        }
+
+        @Override
+        public int hashCode() {
+            return repositoryId.hashCode() ^ moduleVersionIdentifier.hashCode();
+        }
+    }
+
+    private static class RevisionKeySerializer implements Serializer<RevisionKey> {
+        private final ModuleVersionIdentifierSerializer identifierSerializer = new ModuleVersionIdentifierSerializer();
+
+        public void write(Encoder encoder, RevisionKey value) throws Exception {
+            encoder.writeString(value.repositoryId);
+            identifierSerializer.write(encoder, value.moduleVersionIdentifier);
+        }
+
+        public RevisionKey read(Decoder decoder) throws Exception {
+            String resolverId = decoder.readString();
+            ModuleVersionIdentifier identifier = identifierSerializer.read(decoder);
+            return new RevisionKey(resolverId, identifier);
+        }
+    }
+
+    private static class ModuleDescriptorCacheEntrySerializer implements Serializer<ModuleDescriptorCacheEntry> {
+        private final DefaultSerializer<ModuleSource> moduleSourceSerializer = new DefaultSerializer<ModuleSource>(ModuleSource.class.getClassLoader());
+
+        public void write(Encoder encoder, ModuleDescriptorCacheEntry value) throws Exception {
+            encoder.writeBoolean(value.isMissing);
+            encoder.writeBoolean(value.isChanging);
+            encoder.writeBoolean(value.isMetaDataOnly);
+            encoder.writeLong(value.createTimestamp);
+            moduleSourceSerializer.write(encoder, value.moduleSource);
+            byte[] hash = value.moduleDescriptorHash.toByteArray();
+            encoder.writeBinary(hash);
+        }
+
+        public ModuleDescriptorCacheEntry read(Decoder decoder) throws Exception {
+            boolean isMissing = decoder.readBoolean();
+            boolean isChanging = decoder.readBoolean();
+            boolean isMetaData = decoder.readBoolean();
+            long createTimestamp = decoder.readLong();
+            ModuleSource moduleSource = moduleSourceSerializer.read(decoder);
+            byte[] encodedHash = decoder.readBinary();
+            BigInteger hash = new BigInteger(encodedHash);
+            return new ModuleDescriptorCacheEntry(isChanging, isMetaData, isMissing, createTimestamp, hash, moduleSource);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleArtifactsCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleArtifactsCache.java
new file mode 100644
index 0000000..7ef8324
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleArtifactsCache.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.modulecache;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier;
+
+import java.math.BigInteger;
+import java.util.Set;
+
+public interface ModuleArtifactsCache {
+    CachedArtifacts cacheArtifacts(ModuleVersionRepository repository, ModuleVersionIdentifier moduleMetaDataId, String context, BigInteger descriptorHash, Set<ModuleVersionArtifactIdentifier> artifacts);
+
+    CachedArtifacts getCachedArtifacts(ModuleVersionRepository delegate, ModuleVersionIdentifier moduleMetaDataId, String context);
+
+    interface CachedArtifacts {
+        Set<ModuleVersionArtifactIdentifier> getArtifacts();
+
+        BigInteger getDescriptorHash();
+
+        long getAgeMillis();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
deleted file mode 100644
index 096535f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.modulecache;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ResolvedModuleVersion;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
-
-import java.math.BigInteger;
-
-public interface ModuleDescriptorCache {
-    CachedModuleDescriptor cacheModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier resolvedModuleVersionIdentifier, ModuleDescriptor moduleDescriptor, ModuleSource moduleSource, boolean isChanging);
-
-    CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier);
-
-    interface CachedModuleDescriptor {
-        ResolvedModuleVersion getModuleVersion();
-
-        ModuleDescriptor getModuleDescriptor();
-
-        boolean isChangingModule();
-
-        long getAgeMillis();
-
-        BigInteger getDescriptorHash();
-
-        boolean isMissing();
-
-        ModuleSource getModuleSource();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
index 461700c..b75f6b5 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
@@ -21,13 +21,15 @@ import java.math.BigInteger;
 
 class ModuleDescriptorCacheEntry {
     public boolean isChanging;
+    public boolean isMetaDataOnly;
     public boolean isMissing;
     public long createTimestamp;
     public ModuleSource moduleSource;
     public BigInteger moduleDescriptorHash;
 
-    ModuleDescriptorCacheEntry(boolean isChanging, boolean isMissing, long createTimestamp, BigInteger moduleDescriptorHash, ModuleSource moduleSource) {
+    ModuleDescriptorCacheEntry(boolean isChanging, boolean isMetaDataOnly, boolean isMissing, long createTimestamp, BigInteger moduleDescriptorHash, ModuleSource moduleSource) {
         this.isChanging = isChanging;
+        this.isMetaDataOnly = isMetaDataOnly;
         this.isMissing = isMissing;
         this.createTimestamp = createTimestamp;
         this.moduleSource = moduleSource;
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 03bf319..46dbfb3 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
@@ -17,48 +17,46 @@ package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.plugins.parser.ParserSettings;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 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.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
-import org.gradle.api.internal.filestore.FileStoreEntry;
 import org.gradle.api.internal.filestore.PathKeyFileStore;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 
 import java.io.File;
-import java.net.URL;
 
 public class ModuleDescriptorStore {
 
-    public static final String FILE_PATH_PATTERN = "module-metadata/%s/%s/%s/%s/ivy.xml";
-    private final IvyXmlModuleDescriptorParser parser;
-    private final PathKeyFileStore pathKeyFileStore;
-    private final IvyModuleDescriptorWriter ivyModuleDescriptorWriter;
+    public static final String FILE_PATH_PATTERN = "%s/%s/%s/%s/ivy.xml";
+    private final IvyXmlModuleDescriptorParser descriptorParser;
+    private final PathKeyFileStore metaDataStore;
+    private final IvyModuleDescriptorWriter descriptorWriter;
 
-    public ModuleDescriptorStore(PathKeyFileStore pathKeyFileStore, IvyModuleDescriptorWriter ivyModuleDescriptorWriter, IvyXmlModuleDescriptorParser ivyXmlModuleDescriptorParser) {
-        this.pathKeyFileStore = pathKeyFileStore;
-        this.ivyModuleDescriptorWriter = ivyModuleDescriptorWriter;
-        parser = ivyXmlModuleDescriptorParser;
+    public ModuleDescriptorStore(PathKeyFileStore metaDataStore, IvyModuleDescriptorWriter descriptorWriter, IvyXmlModuleDescriptorParser ivyXmlModuleDescriptorParser) {
+        this.metaDataStore = metaDataStore;
+        this.descriptorWriter = descriptorWriter;
+        this.descriptorParser = ivyXmlModuleDescriptorParser;
     }
 
     public ModuleDescriptor getModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
         String filePath = getFilePath(repository, moduleVersionIdentifier);
-        final FileStoreEntry fileStoreEntry = pathKeyFileStore.get(filePath);
-        if (fileStoreEntry != null) {
-            return parseModuleDescriptorFile(fileStoreEntry.getFile());
+        final LocallyAvailableResource resource = metaDataStore.get(filePath);
+        if (resource != null) {
+            return parseModuleDescriptorFile(resource.getFile());
         }
         return null;
     }
 
-    public FileStoreEntry putModuleDescriptor(ModuleVersionRepository repository, final ModuleDescriptor moduleDescriptor) {
+    public LocallyAvailableResource putModuleDescriptor(ModuleVersionRepository repository, final ModuleDescriptor moduleDescriptor) {
         String filePath = getFilePath(repository, moduleDescriptor.getModuleRevisionId());
-        return pathKeyFileStore.add(filePath, new Action<File>() {
+        return metaDataStore.add(filePath, new Action<File>() {
             public void execute(File moduleDescriptorFile) {
                 try {
-                    ivyModuleDescriptorWriter.write(moduleDescriptor, moduleDescriptorFile);
+                    descriptorWriter.write(moduleDescriptor, moduleDescriptorFile);
                 } catch (Exception e) {
                     throw UncheckedException.throwAsUncheckedException(e);
                 }
@@ -67,13 +65,8 @@ public class ModuleDescriptorStore {
     }
 
     private ModuleDescriptor parseModuleDescriptorFile(File moduleDescriptorFile) {
-        ParserSettings settings = IvyContextualiser.getIvyContext().getSettings();
-        try {
-            URL result = moduleDescriptorFile.toURI().toURL();
-            return parser.parseDescriptor(settings, result, false);
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
+        DescriptorParseContext parserSettings = new CachedModuleDescriptorParseContext();
+        return descriptorParser.parseMetaData(parserSettings, moduleDescriptorFile, false).getDescriptor();
     }
 
     private String getFilePath(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetaDataCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetaDataCache.java
new file mode 100644
index 0000000..7874cf0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetaDataCache.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.modulecache;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+import java.math.BigInteger;
+
+public interface ModuleMetaDataCache {
+    CachedMetaData cacheMissing(ModuleVersionRepository repository, ModuleVersionIdentifier id);
+
+    CachedMetaData cacheMetaData(ModuleVersionRepository repository, ModuleVersionMetaData metaData, ModuleSource moduleSource);
+
+    CachedMetaData getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier);
+
+    interface CachedMetaData {
+        ResolvedModuleVersion getModuleVersion();
+
+        MutableModuleVersionMetaData getMetaData();
+
+        long getAgeMillis();
+
+        BigInteger getDescriptorHash();
+
+        boolean isMissing();
+
+        ModuleSource getModuleSource();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
deleted file mode 100644
index 4828e51..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.gradle.api.artifacts.PublishArtifact;
-
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactsExtraAttributesStrategy {
-    Map<String, String> createExtraAttributes(PublishArtifact publishArtifact);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
deleted file mode 100644
index 112e678..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2007-2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactsToModuleDescriptorConverter {
-    void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToArtifactsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToArtifactsConverter.java
new file mode 100644
index 0000000..08bc815
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToArtifactsConverter.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData;
+
+public interface ConfigurationsToArtifactsConverter {
+    void addArtifacts(MutableLocalComponentMetaData metaData, Iterable<? extends Configuration> configurations);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
index 4e033fa..2b82fe3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.gradle.api.artifacts.Configuration;
 
-/**
- * @author Hans Dockter
- */
 public interface ConfigurationsToModuleDescriptorConverter {
     void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
deleted file mode 100644
index 48000c1..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2007-2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
-import org.gradle.util.GUtil;
-import org.gradle.util.WrapUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultArtifactsToModuleDescriptorConverter implements ArtifactsToModuleDescriptorConverter {
-    public final static ArtifactsExtraAttributesStrategy IVY_FILE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
-        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
-            return new HashMap<String, String>();
-        }
-    };
-
-    public final static ArtifactsExtraAttributesStrategy RESOLVE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
-        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
-            return WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
-        }
-    };
-
-    private ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy;
-
-    public DefaultArtifactsToModuleDescriptorConverter(ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy) {
-        this.artifactsExtraAttributesStrategy = artifactsExtraAttributesStrategy;
-    }
-
-    public void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations) {
-        for (Configuration configuration : configurations) {
-            for (PublishArtifact publishArtifact : configuration.getArtifacts()) {
-                Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleDescriptor.getModuleRevisionId());
-                moduleDescriptor.addArtifact(configuration.getName(), ivyArtifact);
-            }
-        }
-    }
-
-    public Artifact createIvyArtifact(PublishArtifact publishArtifact, ModuleRevisionId moduleRevisionId) {
-        Map<String, String> extraAttributes = artifactsExtraAttributesStrategy.createExtraAttributes(publishArtifact);
-        if (GUtil.isTrue(publishArtifact.getClassifier())) {
-            extraAttributes.put(Dependency.CLASSIFIER, publishArtifact.getClassifier());
-        }
-        return new DefaultArtifact(
-                moduleRevisionId,
-                publishArtifact.getDate(),
-                publishArtifact.getName(),
-                publishArtifact.getType(),
-                publishArtifact.getExtension(),
-                extraAttributes);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java
new file mode 100644
index 0000000..523488b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData;
+import org.gradle.util.GUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultConfigurationsToArtifactsConverter implements ConfigurationsToArtifactsConverter {
+
+    public void addArtifacts(MutableLocalComponentMetaData metaData, Iterable<? extends Configuration> configurations) {
+        DefaultModuleDescriptor moduleDescriptor = metaData.getModuleDescriptor();
+        for (Configuration configuration : configurations) {
+            for (PublishArtifact publishArtifact : configuration.getArtifacts()) {
+                Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleDescriptor.getModuleRevisionId());
+                metaData.addArtifact(configuration.getName(), ivyArtifact, publishArtifact.getFile());
+            }
+        }
+    }
+
+    public Artifact createIvyArtifact(PublishArtifact publishArtifact, ModuleRevisionId moduleRevisionId) {
+        Map<String, String> extraAttributes = new HashMap<String, String>();
+        if (GUtil.isTrue(publishArtifact.getClassifier())) {
+            extraAttributes.put(Dependency.CLASSIFIER, publishArtifact.getClassifier());
+        }
+        String name = publishArtifact.getName();
+        if (!GUtil.isTrue(name)) {
+            name = moduleRevisionId.getName();
+        }
+        return new DefaultArtifact(
+                moduleRevisionId,
+                publishArtifact.getDate(),
+                name,
+                publishArtifact.getType(),
+                publishArtifact.getExtension(),
+                extraAttributes);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
index 47dce97..c7cdc2d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
@@ -21,9 +21,6 @@ import org.gradle.api.internal.artifacts.configurations.Configurations;
 
 import java.util.Arrays;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultConfigurationsToModuleDescriptorConverter implements ConfigurationsToModuleDescriptorConverter {
     public void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations) {
         for (Configuration configuration : configurations) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
index 3b6392e..af31581 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
@@ -17,21 +17,18 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 
 import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
 import org.apache.ivy.core.module.id.ArtifactId;
-import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.util.GUtil;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
     public DefaultExcludeRule createExcludeRule(String configurationName, ExcludeRule excludeRule) {
         String org = GUtil.elvis(excludeRule.getGroup(), PatternMatcher.ANY_EXPRESSION);
         String module = GUtil.elvis(excludeRule.getModule(), PatternMatcher.ANY_EXPRESSION);
         DefaultExcludeRule ivyExcludeRule = new DefaultExcludeRule(new ArtifactId(
-                new ModuleId(org, module), PatternMatcher.ANY_EXPRESSION,
+                IvyUtil.createModuleId(org, module), PatternMatcher.ANY_EXPRESSION,
                 PatternMatcher.ANY_EXPRESSION,
                 PatternMatcher.ANY_EXPRESSION),
                 ExactPatternMatcher.INSTANCE, null);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
index 3abe439..5c1ffe3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
@@ -15,34 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.IvyContext;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.IvyFactory;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultModuleDescriptorFactory implements ModuleDescriptorFactory {
-    private final IvyFactory ivyFactory;
-    private final SettingsConverter settingsConverter;
-
-    public DefaultModuleDescriptorFactory(IvyFactory ivyFactory, SettingsConverter settingsConverter) {
-        this.ivyFactory = ivyFactory;
-        this.settingsConverter = settingsConverter;
-    }
-
     public DefaultModuleDescriptor createModuleDescriptor(Module module) {
-        IvyContext ivyContext = IvyContext.pushNewContext();
-        try {
-            Ivy ivy = ivyFactory.createIvy(settingsConverter.getForResolve());
-            ivyContext.setIvy(ivy);
-            return new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(module), module.getStatus(), null);
-        } finally {
-            IvyContext.popContext();
-        }
+        return new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(module), module.getStatus(), null);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
index 4ed4bc5..00c5612 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
@@ -17,9 +17,6 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 
 import org.apache.ivy.core.module.descriptor.ExcludeRule;
 
-/**
- * @author Hans Dockter
- */
 public interface ExcludeRuleConverter {
     ExcludeRule createExcludeRule(String configuration, org.gradle.api.artifacts.ExcludeRule excludeRule);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
index 1f271a9..afc2fe5 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.gradle.api.artifacts.Module;
 
-/**
- * @author Hans Dockter
- */
 public interface ModuleDescriptorFactory {
     DefaultModuleDescriptor createModuleDescriptor(Module module);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishLocalComponentFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishLocalComponentFactory.java
new file mode 100644
index 0000000..f9c9f5a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishLocalComponentFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData;
+
+import java.util.Set;
+
+public class PublishLocalComponentFactory implements LocalComponentFactory {
+    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
+    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
+
+    private LocalComponentFactory resolveLocalComponentFactory;
+    private ConfigurationsToArtifactsConverter configurationsToArtifactsConverter;
+
+    public PublishLocalComponentFactory(LocalComponentFactory resolveLocalComponentFactory,
+                                        ConfigurationsToArtifactsConverter configurationsToArtifactsConverter) {
+        this.resolveLocalComponentFactory = resolveLocalComponentFactory;
+        this.configurationsToArtifactsConverter = configurationsToArtifactsConverter;
+    }
+
+    public MutableLocalComponentMetaData convert(Set<? extends Configuration> configurations, ModuleInternal module) {
+        MutableLocalComponentMetaData publishMetaData = resolveLocalComponentFactory.convert(configurations, module);
+        DefaultModuleDescriptor moduleDescriptor = publishMetaData.getModuleDescriptor();
+        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
+        configurationsToArtifactsConverter.addArtifacts(publishMetaData, configurations);
+        return publishMetaData;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
deleted file mode 100644
index 9f1f656..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class PublishModuleDescriptorConverter implements ModuleDescriptorConverter {
-    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
-    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
-
-    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
-    private ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter;
-
-    public PublishModuleDescriptorConverter(ModuleDescriptorConverter resolveModuleDescriptorConverter,
-                                            ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
-        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
-        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
-    }
-
-    public ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module) {
-         DefaultModuleDescriptor moduleDescriptor = (DefaultModuleDescriptor) resolveModuleDescriptorConverter
-                .convert(configurations, module);
-        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
-        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, configurations);
-        return moduleDescriptor;
-    }
-
-    public ModuleDescriptor createModuleDescriptor(Module module) {
-        return resolveModuleDescriptorConverter.createModuleDescriptor(module);
-    }
-
-    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-        resolveModuleDescriptorConverter.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java
new file mode 100644
index 0000000..9c582b6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.metadata.DefaultLocalComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData;
+
+import java.util.Set;
+
+public class ResolveLocalComponentFactory implements LocalComponentFactory {
+    private final ModuleDescriptorFactory moduleDescriptorFactory;
+    private final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
+    private final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
+    private final ComponentIdentifierFactory componentIdentifierFactory;
+
+    public ResolveLocalComponentFactory(ModuleDescriptorFactory moduleDescriptorFactory,
+                                        ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
+                                        DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter,
+                                        ComponentIdentifierFactory componentIdentifierFactory) {
+        this.moduleDescriptorFactory = moduleDescriptorFactory;
+        this.configurationsToModuleDescriptorConverter = configurationsToModuleDescriptorConverter;
+        this.dependenciesToModuleDescriptorConverter = dependenciesToModuleDescriptorConverter;
+        this.componentIdentifierFactory = componentIdentifierFactory;
+    }
+
+    public MutableLocalComponentMetaData convert(Set<? extends Configuration> configurations, ModuleInternal module) {
+        assert configurations.size() > 0 : "No configurations found for module: " + module.getName() + ". Configure them or apply a plugin that does it.";
+        DefaultModuleDescriptor moduleDescriptor = moduleDescriptorFactory.createModuleDescriptor(module);
+        configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, configurations);
+        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(moduleDescriptor, configurations);
+        ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(module);
+        return new DefaultLocalComponentMetaData(moduleDescriptor, componentIdentifier);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
deleted file mode 100644
index c29ccb0..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class ResolveModuleDescriptorConverter implements ModuleDescriptorConverter {
-    private final ModuleDescriptorFactory moduleDescriptorFactory;
-    private final DependencyDescriptorFactory dependencyDescriptorFactory;
-    private final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
-    private final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
-
-    public ResolveModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
-                                            DependencyDescriptorFactory dependencyDescriptorFactory,
-                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
-                                            DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
-        this.moduleDescriptorFactory = moduleDescriptorFactory;
-        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
-        this.configurationsToModuleDescriptorConverter = configurationsToModuleDescriptorConverter;
-        this.dependenciesToModuleDescriptorConverter = dependenciesToModuleDescriptorConverter;
-    }
-
-    public ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module) {
-        assert configurations.size() > 0 : "No configurations found for module: " + module.getName() + ". Configure them or apply a plugin that does it.";
-        DefaultModuleDescriptor moduleDescriptor = moduleDescriptorFactory.createModuleDescriptor(module);
-        configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, configurations);
-        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(moduleDescriptor, configurations);
-        return moduleDescriptor;
-    }
-
-    public ModuleDescriptor createModuleDescriptor(Module module) {
-        return moduleDescriptorFactory.createModuleDescriptor(module);
-    }
-
-    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-        dependencyDescriptorFactory.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
index 4e8d20e..60d7fd6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
@@ -30,9 +30,6 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractIvyDependencyDescriptorFactory implements IvyDependencyDescriptorFactory {
     private ExcludeRuleConverter excludeRuleConverter;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java
index 77eed68..b5285ef 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java
@@ -18,16 +18,17 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencie
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
 
 public class ClientModuleDependencyDescriptor extends EnhancedDependencyDescriptor {
-    private final ModuleDescriptor targetModule;
+    private final ModuleVersionMetaData targetModule;
 
-    public ClientModuleDependencyDescriptor(ClientModule moduleDependency, ModuleDescriptor md, ModuleDescriptor targetModule, ModuleRevisionId mrid, boolean force, boolean changing, boolean transitive) {
+    public ClientModuleDependencyDescriptor(ClientModule moduleDependency, ModuleDescriptor md, ModuleVersionMetaData targetModule, ModuleRevisionId mrid, boolean force, boolean changing, boolean transitive) {
         super(moduleDependency, md, mrid, force, changing, transitive);
         this.targetModule = targetModule;
     }
 
-    public ModuleDescriptor getTargetModule() {
+    public ModuleVersionMetaData getTargetModule() {
         return targetModule;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java
index c4677cc..2ee9907 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java
@@ -21,16 +21,14 @@ import org.gradle.api.artifacts.ClientModule;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
 
-/**
- * @author Hans Dockter
-*/
 public class ClientModuleIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
-    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule;
+    private ClientModuleMetaDataFactory clientModuleMetaDataFactory;
 
-    public ClientModuleIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule) {
+    public ClientModuleIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ClientModuleMetaDataFactory clientModuleMetaDataFactory) {
         super(excludeRuleConverter);
-        this.moduleDescriptorFactoryForClientModule = moduleDescriptorFactoryForClientModule;
+        this.clientModuleMetaDataFactory = clientModuleMetaDataFactory;
     }
 
     private ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
@@ -40,13 +38,13 @@ public class ClientModuleIvyDependencyDescriptorFactory extends AbstractIvyDepen
     public EnhancedDependencyDescriptor createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
         ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
         ClientModule clientModule = getClientModule(dependency);
-        ModuleDescriptor moduleDescriptor = moduleDescriptorFactoryForClientModule.createModuleDescriptor(
+        MutableModuleVersionMetaData moduleVersionMetaData = clientModuleMetaDataFactory.createModuleDescriptor(
                 moduleRevisionId, clientModule.getDependencies());
 
         EnhancedDependencyDescriptor dependencyDescriptor = new ClientModuleDependencyDescriptor(
                 clientModule,
                 parent,
-                moduleDescriptor,
+                moduleVersionMetaData,
                 moduleRevisionId,
                 clientModule.isForce(),
                 false,
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleMetaDataFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleMetaDataFactory.java
new file mode 100644
index 0000000..7dd76ce
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleMetaDataFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+import java.util.Set;
+
+public interface ClientModuleMetaDataFactory {
+    MutableModuleVersionMetaData createModuleDescriptor(ModuleRevisionId moduleRevisionId, Set<ModuleDependency> dependencies);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultClientModuleMetaDataFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultClientModuleMetaDataFactory.java
new file mode 100644
index 0000000..05df71c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultClientModuleMetaDataFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.metadata.ModuleDescriptorAdapter;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+import java.util.Set;
+
+public class DefaultClientModuleMetaDataFactory implements ClientModuleMetaDataFactory {
+    // Because of bidirectional dependencies we need setter injection
+    private DependencyDescriptorFactory dependencyDescriptorFactory;
+
+    public MutableModuleVersionMetaData createModuleDescriptor(ModuleRevisionId moduleRevisionId, Set<ModuleDependency> dependencies) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(moduleRevisionId,
+                "release", null);
+        moduleDescriptor.addConfiguration(new Configuration(Dependency.DEFAULT_CONFIGURATION));
+        addDependencyDescriptors(moduleDescriptor, dependencies, dependencyDescriptorFactory);
+        moduleDescriptor.addArtifact(Dependency.DEFAULT_CONFIGURATION,
+                new DefaultArtifact(moduleRevisionId, null, moduleRevisionId.getName(), "jar", "jar"));
+        return new ModuleDescriptorAdapter(moduleDescriptor);
+    }
+
+    private void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<ModuleDependency> dependencies,
+                                          DependencyDescriptorFactory dependencyDescriptorFactory) {
+        for (ModuleDependency dependency : dependencies) {
+            dependencyDescriptorFactory.addDependencyDescriptor(Dependency.DEFAULT_CONFIGURATION, moduleDescriptor,
+                    dependency);
+        }
+    }
+
+    public void setDependencyDescriptorFactory(DependencyDescriptorFactory dependencyDescriptorFactory) {
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
index 625accc..c50beff 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
@@ -23,9 +23,6 @@ import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleC
 
 import java.util.Collection;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultDependenciesToModuleDescriptorConverter implements DependenciesToModuleDescriptorConverter {
     private DependencyDescriptorFactory dependencyDescriptorFactory;
     private ExcludeRuleConverter excludeRuleConverter;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
index 23bba49..8ac55c4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
@@ -23,9 +23,6 @@ import org.gradle.util.WrapUtil;
 
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultDependencyDescriptorFactory implements DependencyDescriptorFactory {
     private List<IvyDependencyDescriptorFactory> dependencyDescriptorFactories;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
deleted file mode 100644
index f55f989..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2007-2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.Configuration;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ModuleDependency;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultModuleDescriptorFactoryForClientModule implements ModuleDescriptorFactoryForClientModule {
-    // Because of bi directioal dependencies we need setter injection
-    private DependencyDescriptorFactory dependencyDescriptorFactory;
-
-    public ModuleDescriptor createModuleDescriptor(ModuleRevisionId moduleRevisionId, Set<ModuleDependency> dependencies) {
-        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(moduleRevisionId,
-                "release", null);
-        moduleDescriptor.addConfiguration(new Configuration(Dependency.DEFAULT_CONFIGURATION));
-        addDependencyDescriptors(moduleDescriptor, dependencies, dependencyDescriptorFactory);
-        moduleDescriptor.addArtifact(Dependency.DEFAULT_CONFIGURATION,
-                new DefaultArtifact(moduleRevisionId, null, moduleRevisionId.getName(), "jar", "jar"));
-        return moduleDescriptor;
-    }
-
-    private void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<ModuleDependency> dependencies,
-                                          DependencyDescriptorFactory dependencyDescriptorFactory) {
-        for (ModuleDependency dependency : dependencies) {
-            dependencyDescriptorFactory.addDependencyDescriptor(Dependency.DEFAULT_CONFIGURATION, moduleDescriptor,
-                    dependency);
-        }
-    }
-
-    public DependencyDescriptorFactory getDependencyDescriptorFactory() {
-        return dependencyDescriptorFactory;
-    }
-
-    public void setDependencyDescriptorFactory(DependencyDescriptorFactory dependencyDescriptorFactory) {
-        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
index 3dc12b0..91ce740 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
@@ -20,9 +20,6 @@ import org.gradle.api.artifacts.Configuration;
 
 import java.util.Collection;
 
-/**
- * @author Hans Dockter
- */
 public interface DependenciesToModuleDescriptorConverter {
     void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Collection<? extends Configuration> configurations);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
index c5dbf33..5073472 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencie
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.gradle.api.artifacts.ModuleDependency;
 
-/**
- * @author Hans Dockter
- */
 public interface DependencyDescriptorFactory {
     void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
index f885084..0cffb8b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
@@ -22,9 +22,6 @@ import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
 
-/**
- * @author Hans Dockter
-*/
 public class ExternalModuleIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
     public ExternalModuleIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
         super(excludeRuleConverter);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
deleted file mode 100644
index 760cf15..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2007-2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleDependency;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ModuleDescriptorFactoryForClientModule {
-    ModuleDescriptor createModuleDescriptor(ModuleRevisionId moduleRevisionId, Set<ModuleDependency> dependencies);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
index 33e755b..fb16663 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,21 +20,20 @@ import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.ProjectDependencyInternal;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
 import org.gradle.api.internal.project.ProjectInternal;
 
-/**
- * @author Hans Dockter
- */
 public class ProjectIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
     public ProjectIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
         super(excludeRuleConverter);
     }
 
     public EnhancedDependencyDescriptor createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
+        ProjectDependencyInternal projectDependency = (ProjectDependencyInternal) dependency;
+        projectDependency.beforeResolved();
         ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
-        ProjectDependency projectDependency = (ProjectDependency) dependency;
         ProjectDependencyDescriptor dependencyDescriptor = new ProjectDependencyDescriptor(projectDependency, parent, moduleRevisionId, false, false, dependency.isTransitive());
         addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
         return dependencyDescriptor;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java
new file mode 100644
index 0000000..58e04c9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.metadata.LocalComponentMetaData;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectRegistry;
+
+public class DefaultProjectComponentRegistry implements ProjectComponentRegistry {
+    private final LocalComponentFactory localComponentFactory;
+    private final ProjectRegistry<ProjectInternal> projectRegistry;
+
+    public DefaultProjectComponentRegistry(LocalComponentFactory localComponentFactory, ProjectRegistry<ProjectInternal> projectRegistry) {
+        this.localComponentFactory = localComponentFactory;
+        this.projectRegistry = projectRegistry;
+    }
+
+    public LocalComponentMetaData getProject(String projectPath) {
+        ProjectInternal project = projectRegistry.getProject(projectPath);
+        return localComponentFactory.convert(project.getConfigurations(), project.getModule());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
deleted file mode 100644
index 0b6b02c..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.util.ReflectionUtil;
-
-public class DefaultProjectModuleRegistry implements ProjectModuleRegistry {
-    private final ModuleDescriptorConverter moduleDescriptorConverter;
-
-    public DefaultProjectModuleRegistry(ModuleDescriptorConverter moduleDescriptorConverter) {
-        this.moduleDescriptorConverter = moduleDescriptorConverter;
-    }
-
-    public ModuleDescriptor findProject(ProjectDependencyDescriptor descriptor) {
-        ProjectInternal project = descriptor.getTargetProject();
-        Module projectModule = project.getModule();
-        ModuleDescriptor projectDescriptor = moduleDescriptorConverter.convert(project.getConfigurations(), projectModule);
-
-        for (DependencyArtifactDescriptor artifactDescriptor : descriptor.getAllDependencyArtifacts()) {
-            for (Artifact artifact : projectDescriptor.getAllArtifacts()) {
-                if (artifact.getName().equals(artifactDescriptor.getName()) && artifact.getExt().equals(
-                        artifactDescriptor.getExt())) {
-                    String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE);
-                    ReflectionUtil.invoke(artifactDescriptor, "setExtraAttribute",
-                            new Object[]{DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE, path});
-                }
-            }
-        }
-
-        return projectDescriptor;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectPublication.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectPublication.java
new file mode 100644
index 0000000..8f3d235
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectPublication.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+public class DefaultProjectPublication implements ProjectPublication {
+    private final ModuleVersionIdentifier id;
+
+    public DefaultProjectPublication(ModuleVersionIdentifier id) {
+        this.id = id;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
+    }
+
+    public boolean equals(Object other) {
+        if (!(other instanceof ProjectPublication)) { return false; }
+        return id.equals(((ProjectPublication) other).getId());
+    }
+
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectPublicationRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectPublicationRegistry.java
new file mode 100644
index 0000000..cf110a7
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectPublicationRegistry.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import com.google.common.collect.*;
+
+import java.util.Set;
+
+public class DefaultProjectPublicationRegistry implements ProjectPublicationRegistry {
+    private final SetMultimap<String, ProjectPublication> publicationsByProject = HashMultimap.create();
+
+    public Set<ProjectPublication> getPublications(String projectPath) {
+        return publicationsByProject.get(projectPath);
+    }
+
+    public void registerPublication(String projectPath, ProjectPublication publication) {
+        publicationsByProject.put(projectPath, publication);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java
new file mode 100644
index 0000000..e5cf966
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.LocalArtifactMetaData;
+
+import java.util.Set;
+
+public class ProjectArtifactResolver implements ArtifactResolver {
+    private final ArtifactResolver delegate;
+
+    public ProjectArtifactResolver(ArtifactResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        if (isProjectModule(component.getComponentId())) {
+            if (context instanceof ConfigurationResolveContext) {
+                String configurationName = ((ConfigurationResolveContext) context).getConfigurationName();
+                Set<ComponentArtifactMetaData> artifacts = component.getConfiguration(configurationName).getArtifacts();
+                result.resolved(artifacts);
+                return;
+            }
+            throw new UnsupportedOperationException(String.format("Resolving %s for project modules is not yet supported", context.getDescription()));
+        }
+
+        delegate.resolveModuleArtifacts(component, context, result);
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        if (isProjectModule(artifact.getComponentId())) {
+            LocalArtifactMetaData localArtifact = (LocalArtifactMetaData) artifact;
+            if (localArtifact.getFile() != null) {
+                result.resolved(localArtifact.getFile());
+            } else {
+                result.notFound(artifact.getId());
+            }
+        } else {
+            delegate.resolveArtifact(artifact, moduleSource, result);
+        }
+    }
+
+    private boolean isProjectModule(ComponentIdentifier componentId) {
+        return componentId instanceof ProjectComponentIdentifier;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectComponentRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectComponentRegistry.java
new file mode 100644
index 0000000..2a01fcf
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectComponentRegistry.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import org.gradle.api.internal.artifacts.metadata.LocalComponentMetaData;
+
+public interface ProjectComponentRegistry {
+    LocalComponentMetaData getProject(String projectPath);
+}
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 54d67c2..8bb755c 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,49 +15,43 @@
  */
 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.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.*;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableComponentResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleToModuleVersionResolver;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
-import org.gradle.initialization.ProjectAccessListener;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.LocalComponentMetaData;
 
-import java.io.File;
+import java.util.Set;
 
-public class ProjectDependencyResolver implements DependencyToModuleResolver {
-    private final ProjectModuleRegistry projectModuleRegistry;
-    private final DependencyToModuleResolver resolver;
-    private final ProjectArtifactResolver artifactResolver;
-    private final ProjectAccessListener projectAccessListener;
+public class ProjectDependencyResolver implements DependencyToModuleVersionResolver, ModuleToModuleVersionResolver {
+    private final ProjectComponentRegistry projectComponentRegistry;
+    private final DependencyToModuleVersionResolver delegate;
+    private final LocalComponentFactory localComponentFactory;
 
-    public ProjectDependencyResolver(ProjectModuleRegistry projectModuleRegistry, DependencyToModuleResolver resolver, ProjectAccessListener projectAccessListener) {
-        this.projectModuleRegistry = projectModuleRegistry;
-        this.resolver = resolver;
-        this.projectAccessListener = projectAccessListener;
-        artifactResolver = new ProjectArtifactResolver();
+    public ProjectDependencyResolver(ProjectComponentRegistry projectComponentRegistry, LocalComponentFactory localComponentFactory, DependencyToModuleVersionResolver delegate) {
+        this.projectComponentRegistry = projectComponentRegistry;
+        this.delegate = delegate;
+        this.localComponentFactory = localComponentFactory;
     }
 
-    public void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result) {
+    public void resolve(DependencyMetaData dependency, BuildableComponentResolveResult result) {
         DependencyDescriptor descriptor = dependency.getDescriptor();
         if (descriptor instanceof ProjectDependencyDescriptor) {
             ProjectDependencyDescriptor desc = (ProjectDependencyDescriptor) descriptor;
-            projectAccessListener.beforeResolvingProjectDependency(desc.getTargetProject());
-            ModuleDescriptor moduleDescriptor = projectModuleRegistry.findProject(desc);
-            final ModuleRevisionId moduleRevisionId = moduleDescriptor.getModuleRevisionId();
-            final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
-            result.resolved(moduleVersionIdentifier, moduleDescriptor, artifactResolver);
+            LocalComponentMetaData componentMetaData = projectComponentRegistry.getProject(desc.getTargetProject().getPath());
+            result.resolved(componentMetaData.toResolveMetaData());
         } else {
-            resolver.resolve(dependency, result);
+            delegate.resolve(dependency, result);
         }
     }
 
-    private static class ProjectArtifactResolver implements ArtifactResolver {
-        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
-            String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE);
-            result.resolved(new File(path));
-        }
+    public void resolve(ModuleInternal module, Set<? extends Configuration> configurations, BuildableComponentResolveResult result) {
+        LocalComponentMetaData componentMetaData = localComponentFactory.convert(configurations, module);
+        result.resolved(componentMetaData.toResolveMetaData());
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
deleted file mode 100644
index 6ffc502..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
+++ /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.api.internal.artifacts.ivyservice.projectmodule;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
-
-/**
- * TODO - this probably should lookup a project by id, much like ClientModuleRegistry. Then, there would be no dependency on ivy DependencyDescriptor.
- */
-public interface ProjectModuleRegistry {
-    ModuleDescriptor findProject(ProjectDependencyDescriptor descriptor);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectPublication.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectPublication.java
new file mode 100644
index 0000000..d5925bc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectPublication.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+/**
+ * Provides information on a "logical" publication of a project.
+ * The publication could have been declared via `publishing.publications`,
+ * an `uploadArchives` task, etc.
+ */
+public interface ProjectPublication {
+    ModuleVersionIdentifier getId();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectPublicationRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectPublicationRegistry.java
new file mode 100644
index 0000000..b51142f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectPublicationRegistry.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
+
+import java.util.Set;
+
+/**
+ * Collects information on the "logical" publications of each project.
+ * The information is gathered from multiple sources ({@code publishing.publications}
+ * container, {@code uploadArchives} task, etc.).
+ */
+public interface ProjectPublicationRegistry {
+    void registerPublication(String projectPath, ProjectPublication publication);
+    Set<ProjectPublication> getPublications(String projectPath);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
index 6d20567..df5082a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
@@ -18,15 +18,17 @@ package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleIdentifier;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
 import org.gradle.api.artifacts.cache.*;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
@@ -106,8 +108,8 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
         });
     }
 
-    public boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis) {
-        CachedDependencyResolutionControl dependencyResolutionControl = new CachedDependencyResolutionControl(selector, moduleId, ageMillis);
+    public boolean mustRefreshVersionList(final ModuleIdentifier moduleIdentifier, Set<ModuleVersionIdentifier> matchingVersions, long ageMillis) {
+        CachedDependencyResolutionControl dependencyResolutionControl = new CachedDependencyResolutionControl(moduleIdentifier, matchingVersions, ageMillis);
 
         for (Action<? super DependencyResolutionControl> rule : dependencyCacheRules) {
             rule.execute(dependencyResolutionControl);
@@ -140,6 +142,14 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
         return false;
     }
 
+    public boolean mustRefreshModuleArtifacts(ModuleVersionIdentifier moduleVersionId, Set<ArtifactIdentifier> artifacts,
+                                              long ageMillis, boolean belongsToChangingModule, boolean moduleDescriptorInSync) {
+        if (belongsToChangingModule && !moduleDescriptorInSync) {
+            return true;
+        }
+        return mustRefreshModule(moduleVersionId, new DefaultResolvedModuleVersion(moduleVersionId), ageMillis, belongsToChangingModule);
+    }
+
     public boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis, boolean belongsToChangingModule, boolean moduleDescriptorInSync) {
         CachedArtifactResolutionControl artifactResolutionControl = new CachedArtifactResolutionControl(artifactIdentifier, cachedArtifactFile, ageMillis, belongsToChangingModule);
         if(belongsToChangingModule && !moduleDescriptorInSync){
@@ -210,9 +220,9 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
         }
     }
 
-    private class CachedDependencyResolutionControl extends AbstractResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> implements DependencyResolutionControl {
-        private CachedDependencyResolutionControl(ModuleVersionSelector request, ModuleVersionIdentifier cachedVersion, long ageMillis) {
-            super(request, cachedVersion, ageMillis);
+    private class CachedDependencyResolutionControl extends AbstractResolutionControl<ModuleIdentifier, Set<ModuleVersionIdentifier>> implements DependencyResolutionControl {
+        private CachedDependencyResolutionControl(ModuleIdentifier request, Set<ModuleVersionIdentifier> result, long ageMillis) {
+            super(request, result, ageMillis);
         }
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultExternalResourceCachePolicy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultExternalResourceCachePolicy.java
new file mode 100644
index 0000000..34d89f1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultExternalResourceCachePolicy.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolutionstrategy;
+
+public class DefaultExternalResourceCachePolicy extends DefaultCachePolicy implements ExternalResourceCachePolicy {
+    public boolean mustRefreshExternalResource(long ageMillis) {
+        return ageMillis != 0 ? true : false;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
index 740b51a..ad5a7ab 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
@@ -22,12 +22,12 @@ import org.gradle.api.artifacts.DependencyResolveDetails;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.ResolutionStrategy;
 import org.gradle.api.artifacts.cache.ResolutionRules;
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
 import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal;
 import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
-import org.gradle.api.internal.notations.parsers.NormalizedTimeUnit;
-import org.gradle.api.internal.notations.parsers.TimeUnitsParser;
+import org.gradle.internal.typeconversion.NormalizedTimeUnit;
+import org.gradle.internal.typeconversion.TimeUnitsParser;
 
 import java.util.Collection;
 import java.util.LinkedHashSet;
@@ -36,9 +36,6 @@ import java.util.concurrent.TimeUnit;
 
 import static org.gradle.util.GUtil.flattenElements;
 
-/**
- * by Szczepan Faber, created at: 10/7/11
- */
 public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
 
     private Set<ModuleVersionSelector> forcedModules = new LinkedHashSet<ModuleVersionSelector>();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ExternalResourceCachePolicy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ExternalResourceCachePolicy.java
new file mode 100644
index 0000000..fd5600f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ExternalResourceCachePolicy.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolutionstrategy;
+
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+
+public interface ExternalResourceCachePolicy extends CachePolicy {
+    boolean mustRefreshExternalResource(long ageMillis);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java
index a8022db..860b490 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java
@@ -20,8 +20,6 @@ import org.gradle.api.artifacts.ConflictResolution;
 
 /**
  * Latest resolution strategy
- * <p>
- * by Szczepan Faber, created at: 10/5/11
  */
 public class LatestConflictResolution implements ConflictResolution {
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
index 4992512..a227620 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
@@ -27,9 +27,6 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
-/**
-* by Szczepan Faber, created at: 11/29/12
-*/
 public class ModuleForcingResolveRule implements Action<DependencyResolveDetailsInternal> {
 
     private final Map<ModuleIdentifier, String> forcedModules;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java
index 4ee1533..3ec3377 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java
@@ -20,8 +20,6 @@ import org.gradle.api.artifacts.ConflictResolution;
 
 /**
  * Strict type, allows configuring (forcing) certain dependency versions using dependency notation
- * <p>
- * by Szczepan Faber, created at: 10/5/11
  */
 public class StrictConflictResolution implements ConflictResolution {
 }
\ No newline at end of file
diff --git a/subprojects/core-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 e572e6b..f5f1bd9 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
@@ -15,21 +15,36 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
+import org.apache.ivy.Ivy;
+import org.gradle.api.Action;
 import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.ivyservice.*;
 import org.gradle.api.internal.artifacts.ivyservice.clientmodule.ClientModuleResolver;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAdapter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ErrorHandlingArtifactResolver;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LazyDependencyToModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChain;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentRegistry;
 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.resolutionstrategy.StrictConflictResolution;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.DefaultResolvedConfigurationBuilder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResults;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResultsBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.StreamingResolutionResultBuilder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.ResolutionResultsStoreFactory;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.StoreSet;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
-import org.gradle.initialization.ProjectAccessListener;
+import org.gradle.api.internal.cache.BinaryStore;
+import org.gradle.api.internal.cache.Store;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,45 +52,79 @@ import java.util.List;
 
 public class DefaultDependencyResolver implements ArtifactDependencyResolver {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDependencyResolver.class);
-    private final ModuleDescriptorConverter moduleDescriptorConverter;
-    private final ResolvedArtifactFactory resolvedArtifactFactory;
+    private final LocalComponentFactory localComponentFactory;
     private final ResolveIvyFactory ivyFactory;
-    private final ProjectModuleRegistry projectModuleRegistry;
-    private final ProjectAccessListener projectAccessListener;
+    private final ProjectComponentRegistry projectComponentRegistry;
     private final CacheLockingManager cacheLockingManager;
+    private final IvyContextManager ivyContextManager;
+    private final ResolutionResultsStoreFactory storeFactory;
+    private final VersionMatcher versionMatcher;
+    private final LatestStrategy latestStrategy;
 
-    public DefaultDependencyResolver(ResolveIvyFactory ivyFactory, ModuleDescriptorConverter moduleDescriptorConverter, ResolvedArtifactFactory resolvedArtifactFactory,
-                                     ProjectModuleRegistry projectModuleRegistry, ProjectAccessListener projectAccessListener, CacheLockingManager cacheLockingManager) {
+    public DefaultDependencyResolver(ResolveIvyFactory ivyFactory, LocalComponentFactory localComponentFactory,
+                                     ProjectComponentRegistry projectComponentRegistry, CacheLockingManager cacheLockingManager, IvyContextManager ivyContextManager,
+                                     ResolutionResultsStoreFactory storeFactory, VersionMatcher versionMatcher, LatestStrategy latestStrategy) {
         this.ivyFactory = ivyFactory;
-        this.moduleDescriptorConverter = moduleDescriptorConverter;
-        this.resolvedArtifactFactory = resolvedArtifactFactory;
-        this.projectModuleRegistry = projectModuleRegistry;
-        this.projectAccessListener = projectAccessListener;
+        this.localComponentFactory = localComponentFactory;
+        this.projectComponentRegistry = projectComponentRegistry;
         this.cacheLockingManager = cacheLockingManager;
+        this.ivyContextManager = ivyContextManager;
+        this.storeFactory = storeFactory;
+        this.versionMatcher = versionMatcher;
+        this.latestStrategy = latestStrategy;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
+    public void resolve(final ConfigurationInternal configuration,
+                        final List<? extends ResolutionAwareRepository> repositories,
+                        final ModuleMetadataProcessor metadataProcessor,
+                        final ResolverResults results) throws ResolveException {
         LOGGER.debug("Resolving {}", configuration);
+        ivyContextManager.withIvy(new Action<Ivy>() {
+            public void execute(Ivy ivy) {
+                RepositoryChain repositoryChain = ivyFactory.create(configuration, repositories, metadataProcessor);
 
-        IvyAdapter ivyAdapter = ivyFactory.create(configuration, repositories);
+                DependencyToModuleVersionResolver dependencyResolver = repositoryChain.getDependencyResolver();
+                dependencyResolver = new ClientModuleResolver(dependencyResolver);
+                ProjectDependencyResolver projectDependencyResolver = new ProjectDependencyResolver(projectComponentRegistry, localComponentFactory, dependencyResolver);
+                dependencyResolver = projectDependencyResolver;
+                DependencyToModuleVersionIdResolver idResolver = new LazyDependencyToModuleResolver(dependencyResolver, versionMatcher);
+                idResolver = new VersionForcingDependencyToModuleResolver(idResolver, configuration.getResolutionStrategy().getDependencyResolveRule());
 
-        DependencyToModuleResolver dependencyResolver = ivyAdapter.getDependencyToModuleResolver();
-        dependencyResolver = new ClientModuleResolver(dependencyResolver);
-        dependencyResolver = new ProjectDependencyResolver(projectModuleRegistry, dependencyResolver, projectAccessListener);
-        DependencyToModuleVersionIdResolver idResolver = new LazyDependencyToModuleResolver(dependencyResolver, ivyAdapter.getResolveData().getSettings().getVersionMatcher());
-        idResolver = new VersionForcingDependencyToModuleResolver(idResolver, configuration.getResolutionStrategy().getDependencyResolveRule());
+                ArtifactResolver artifactResolver = createArtifactResolver(repositoryChain);
 
-        ModuleConflictResolver conflictResolver;
-        if (configuration.getResolutionStrategy().getConflictResolution() instanceof StrictConflictResolution) {
-            conflictResolver = new StrictConflictResolver();
-        } else {
-            conflictResolver = new LatestModuleConflictResolver();
-        }
-        ModuleConflictResolver actualResolver = new VersionSelectionReasonResolver(conflictResolver);
+                ModuleConflictResolver conflictResolver;
+                if (configuration.getResolutionStrategy().getConflictResolution() instanceof StrictConflictResolution) {
+                    conflictResolver = new StrictConflictResolver();
+                } else {
+                    conflictResolver = new LatestModuleConflictResolver(latestStrategy);
+                }
+                conflictResolver = new VersionSelectionReasonResolver(conflictResolver);
 
-        DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, idResolver, actualResolver);
-        ResolutionResultBuilder resultBuilder = new ResolutionResultBuilder();
-        DefaultLenientConfiguration result = builder.resolve(configuration, ivyAdapter.getResolveData(), resultBuilder);
-        return new ResolverResults(new DefaultResolvedConfiguration(result, cacheLockingManager), resultBuilder.getResult());
+                DependencyGraphBuilder builder = new DependencyGraphBuilder(idResolver, projectDependencyResolver, artifactResolver, conflictResolver, new DefaultDependencyToConfigurationResolver());
+
+                StoreSet stores = storeFactory.createStoreSet();
+
+                BinaryStore newModelStore = stores.nextBinaryStore();
+                Store<ResolvedComponentResult> newModelCache = stores.oldModelStore();
+                ResolutionResultBuilder newModelBuilder = new StreamingResolutionResultBuilder(newModelStore, newModelCache);
+
+                BinaryStore oldModelStore = stores.nextBinaryStore();
+                Store<TransientConfigurationResults> oldModelCache = stores.newModelStore();
+                TransientConfigurationResultsBuilder oldTransientModelBuilder = new TransientConfigurationResultsBuilder(oldModelStore, oldModelCache);
+                DefaultResolvedConfigurationBuilder oldModelBuilder = new DefaultResolvedConfigurationBuilder(oldTransientModelBuilder);
+
+                builder.resolve(configuration, newModelBuilder, oldModelBuilder);
+                DefaultLenientConfiguration result = new DefaultLenientConfiguration(configuration, oldModelBuilder, cacheLockingManager);
+                results.resolved(new DefaultResolvedConfiguration(result), newModelBuilder.complete());
+            }
+        });
+    }
+
+    private ArtifactResolver createArtifactResolver(RepositoryChain repositoryChain) {
+        ArtifactResolver artifactResolver = repositoryChain.getArtifactResolver();
+        artifactResolver = new ProjectArtifactResolver(artifactResolver);
+        artifactResolver = new ContextualArtifactResolver(cacheLockingManager, ivyContextManager, artifactResolver);
+        artifactResolver = new ErrorHandlingArtifactResolver(artifactResolver);
+        return artifactResolver;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java
new file mode 100644
index 0000000..16b1b4b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.ConfigurationMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultDependencyToConfigurationResolver implements DependencyToConfigurationResolver {
+    // TODO - don't pass in 'from' configuration - the dependency should have whatever context it needs
+    public Set<ConfigurationMetaData> resolveTargetConfigurations(DependencyMetaData dependencyMetaData, ConfigurationMetaData fromConfiguration, ComponentMetaData targetComponent) {
+        // TODO - resolve directly to config meta data
+        ModuleDescriptor targetDescriptor = targetComponent.getDescriptor();
+        DependencyDescriptor dependencyDescriptor = dependencyMetaData.getDescriptor();
+        Set<String> targetConfigurationNames = new LinkedHashSet<String>();
+        for (String config : dependencyDescriptor.getModuleConfigurations()) {
+            if (config.equals("*") || config.equals("%")) {
+                collectTargetConfiguration(dependencyDescriptor, fromConfiguration, fromConfiguration.getName(), targetDescriptor, targetConfigurationNames);
+            } else if (fromConfiguration.getHierarchy().contains(config)) {
+                collectTargetConfiguration(dependencyDescriptor, fromConfiguration, config, targetDescriptor, targetConfigurationNames);
+            }
+        }
+
+        Set<ConfigurationMetaData> targets = new LinkedHashSet<ConfigurationMetaData>();
+        for (String targetConfigurationName : targetConfigurationNames) {
+            // TODO - move this down below
+            if (targetDescriptor.getConfiguration(targetConfigurationName) == null) {
+                throw new RuntimeException(String.format("Module version %s, configuration '%s' declares a dependency on configuration '%s' which is not declared in the module descriptor for %s",
+                        fromConfiguration.getComponent().getId(), fromConfiguration.getName(),
+                        targetConfigurationName, targetComponent.getId()));
+            }
+            ConfigurationMetaData targetConfiguration = targetComponent.getConfiguration(targetConfigurationName);
+            targets.add(targetConfiguration);
+        }
+        return targets;
+    }
+
+    private void collectTargetConfiguration(DependencyDescriptor dependencyDescriptor, ConfigurationMetaData fromConfiguration, String mappingRhs, ModuleDescriptor targetModule, Collection<String> targetConfigs) {
+        String[] dependencyConfigurations = dependencyDescriptor.getDependencyConfigurations(mappingRhs, fromConfiguration.getName());
+        for (String target : dependencyConfigurations) {
+            String candidate = target;
+            int startFallback = candidate.indexOf('(');
+            if (startFallback >= 0) {
+                if (candidate.charAt(candidate.length() - 1) == ')') {
+                    String preferred = candidate.substring(0, startFallback);
+                    if (targetModule.getConfiguration(preferred) != null) {
+                        targetConfigs.add(preferred);
+                        continue;
+                    }
+                    candidate = candidate.substring(startFallback + 1, candidate.length() - 1);
+                }
+            }
+            if (candidate.equals("*")) {
+                Collections.addAll(targetConfigs, targetModule.getPublicConfigurationsNames());
+                continue;
+            }
+            targetConfigs.add(candidate);
+        }
+    }
+}
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 f01fe53..d049cd5 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
@@ -15,70 +15,68 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
-import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.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.DefaultResolvedDependency;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
 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.ivyresolve.BuildableModuleVersionMetaData;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultBuildableModuleVersionMetaData;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedConfigurationBuilder;
 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.ResolutionResultBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.ConfigurationMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
 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;
-    private final ResolvedArtifactFactory resolvedArtifactFactory;
     private final DependencyToModuleVersionIdResolver dependencyResolver;
+    private final DependencyToConfigurationResolver dependencyToConfigurationResolver;
     private final InternalConflictResolver conflictResolver;
-
-    public DependencyGraphBuilder(ModuleDescriptorConverter moduleDescriptorConverter, ResolvedArtifactFactory resolvedArtifactFactory, DependencyToModuleVersionIdResolver dependencyResolver, ModuleConflictResolver conflictResolver) {
-        this.moduleDescriptorConverter = moduleDescriptorConverter;
-        this.resolvedArtifactFactory = resolvedArtifactFactory;
+    private final ModuleToModuleVersionResolver moduleResolver;
+    private final ArtifactResolver artifactResolver;
+
+    public DependencyGraphBuilder(DependencyToModuleVersionIdResolver dependencyResolver,
+                                  ModuleToModuleVersionResolver moduleResolver,
+                                  ArtifactResolver artifactResolver,
+                                  ModuleConflictResolver conflictResolver,
+                                  DependencyToConfigurationResolver dependencyToConfigurationResolver) {
         this.dependencyResolver = dependencyResolver;
+        this.moduleResolver = moduleResolver;
+        this.artifactResolver = artifactResolver;
+        this.dependencyToConfigurationResolver = dependencyToConfigurationResolver;
         this.conflictResolver = new InternalConflictResolver(conflictResolver);
     }
 
-    public DefaultLenientConfiguration resolve(ConfigurationInternal configuration, ResolveData resolveData, ResolvedConfigurationListener listener) throws ResolveException {
-        ModuleDescriptor rootModuleDescriptor = moduleDescriptorConverter.convert(configuration.getAll(), configuration.getModule());
-        BuildableModuleVersionMetaData rootMetaData = new DefaultBuildableModuleVersionMetaData();
-        rootMetaData.resolved(rootModuleDescriptor, false, null);
+    public void resolve(ConfigurationInternal configuration,
+                        ResolutionResultBuilder newModelBuilder,
+                        ResolvedConfigurationBuilder oldModelBuilder) throws ResolveException {
+        DefaultBuildableComponentResolveResult rootModule = new DefaultBuildableComponentResolveResult();
+        moduleResolver.resolve(configuration.getModule(), configuration.getAll(), rootModule);
 
-        ResolveState resolveState = new ResolveState(rootMetaData, configuration.getName(), dependencyResolver, resolveData);
+        ResolveState resolveState = new ResolveState(rootModule, configuration.getName(), dependencyResolver, dependencyToConfigurationResolver, artifactResolver, oldModelBuilder);
         traverseGraph(resolveState);
 
-        DefaultLenientConfiguration result = new DefaultLenientConfiguration(configuration, resolveState.root.getResult());
-        assembleResult(resolveState, result, listener);
-
-        return result;
+        assembleResult(resolveState, oldModelBuilder, newModelBuilder);
     }
 
     /**
      * Traverses the dependency graph, resolving conflicts and building the paths from the root configuration.
      */
     private void traverseGraph(ResolveState resolveState) {
-        Set<ModuleId> conflicts = new LinkedHashSet<ModuleId>();
+        Set<ModuleIdentifier> conflicts = new LinkedHashSet<ModuleIdentifier>();
 
         resolveState.onMoreSelected(resolveState.root);
 
@@ -96,20 +94,19 @@ public class DependencyGraphBuilder {
                     LOGGER.debug("Visiting dependency {}", dependency);
 
                     // Resolve dependency to a particular revision
-                    dependency.resolveModuleRevisionId();
-                    DefaultModuleRevisionResolveState moduleRevision = dependency.getTargetModuleRevision();
+                    ModuleVersionResolveState moduleRevision = dependency.resolveModuleRevisionId();
                     if (moduleRevision == null) {
                         // Failed to resolve.
                         continue;
                     }
-                    ModuleId moduleId= moduleRevision.id.getModuleId();
+                    ModuleIdentifier moduleId = moduleRevision.id.getModule();
 
                     // Check for a new conflict
                     if (moduleRevision.state == ModuleState.New) {
                         ModuleResolveState module = resolveState.getModule(moduleId);
 
                         // A new module revision. Check for conflict
-                        Collection<DefaultModuleRevisionResolveState> versions = module.getVersions();
+                        Collection<ModuleVersionResolveState> versions = module.getVersions();
                         if (versions.size() == 1) {
                             // First version of this module. Select it for now
                             LOGGER.debug("Selecting new module version {}", moduleRevision);
@@ -121,10 +118,10 @@ public class DependencyGraphBuilder {
 
                             // Deselect the currently selected version, and remove all outgoing edges from the version
                             // This will propagate through the graph and prune configurations that are no longer required
-                            DefaultModuleRevisionResolveState previouslySelected = module.clearSelection();
+                            ModuleVersionResolveState previouslySelected = module.clearSelection();
                             if (previouslySelected != null) {
                                 for (ConfigurationNode configuration : previouslySelected.configurations) {
-                                    configuration.removeOutgoingEdges();
+                                    configuration.deselect();
                                 }
                             }
                         }
@@ -134,10 +131,10 @@ public class DependencyGraphBuilder {
                 }
             } else {
                 // We have some batched up conflicts. Resolve the first, and continue traversing the graph
-                ModuleId moduleId = conflicts.iterator().next();
+                ModuleIdentifier moduleId = conflicts.iterator().next();
                 conflicts.remove(moduleId);
                 ModuleResolveState module = resolveState.getModule(moduleId);
-                DefaultModuleRevisionResolveState selected = conflictResolver.select(module.getVersions(), resolveState.root.moduleRevision);
+                ModuleVersionResolveState selected = conflictResolver.select(module.getVersions(), resolveState.root.moduleRevision);
                 LOGGER.debug("Selected {} from conflicting modules {}.", selected, module.getVersions());
 
                 // Restart each configuration. For the evicted configuration, this means moving incoming dependencies across to the
@@ -150,24 +147,28 @@ public class DependencyGraphBuilder {
     /**
      * Populates the result from the graph traversal state.
      */
-    private void assembleResult(ResolveState resolveState, ResolvedConfigurationBuilder result, ResolvedConfigurationListener listener) {
+    private void assembleResult(ResolveState resolveState, ResolvedConfigurationBuilder oldModelBuilder, ResolutionResultBuilder newModelBuilder) {
         FailureState failureState = new FailureState(resolveState.root);
-        ModuleVersionIdentifier root = resolveState.root.toId();
-        listener.start(root);
+        newModelBuilder.start(resolveState.root.moduleRevision.id, resolveState.root.metaData.getComponent().getComponentId());
 
+        // Visit the nodes
         for (ConfigurationNode resolvedConfiguration : resolveState.getConfigurationNodes()) {
             if (resolvedConfiguration.isSelected()) {
-                resolvedConfiguration.attachToParents(resolvedArtifactFactory, result);
+                resolvedConfiguration.validate();
+                oldModelBuilder.newResolvedDependency(resolvedConfiguration.id);
                 resolvedConfiguration.collectFailures(failureState);
-                listener.resolvedModuleVersion(resolvedConfiguration.moduleRevision);
+                newModelBuilder.resolvedModuleVersion(resolvedConfiguration.moduleRevision);
             }
         }
+        // Visit the edges
         for (ConfigurationNode resolvedConfiguration : resolveState.getConfigurationNodes()) {
             if (resolvedConfiguration.isSelected()) {
-                listener.resolvedConfiguration(resolvedConfiguration.toId(), resolvedConfiguration.outgoingEdges);
+                resolvedConfiguration.attachToParents(oldModelBuilder);
+                newModelBuilder.resolvedConfiguration(resolvedConfiguration.toId(), resolvedConfiguration.outgoingEdges);
             }
         }
-        failureState.attachFailures(result);
+        failureState.attachFailures(oldModelBuilder);
+        oldModelBuilder.done(resolveState.root.id);
     }
 
     private static class FailureState {
@@ -180,29 +181,29 @@ public class DependencyGraphBuilder {
 
         public void attachFailures(ResolvedConfigurationBuilder result) {
             for (Map.Entry<ModuleVersionSelector, BrokenDependency> entry : failuresByRevisionId.entrySet()) {
-                Collection<List<ModuleRevisionId>> paths = calculatePaths(entry.getValue());
+                Collection<List<ModuleVersionIdentifier>> paths = calculatePaths(entry.getValue());
                 result.addUnresolvedDependency(new DefaultUnresolvedDependency(entry.getKey(), entry.getValue().failure.withIncomingPaths(paths)));
             }
         }
 
-        private Collection<List<ModuleRevisionId>> calculatePaths(BrokenDependency brokenDependency) {
+        private Collection<List<ModuleVersionIdentifier>> calculatePaths(BrokenDependency brokenDependency) {
             // Include the shortest path from each version that has a direct dependency on the broken dependency, back to the root
             
-            Map<DefaultModuleRevisionResolveState, List<ModuleRevisionId>> shortestPaths = new LinkedHashMap<DefaultModuleRevisionResolveState, List<ModuleRevisionId>>();
-            List<ModuleRevisionId> rootPath = new ArrayList<ModuleRevisionId>();
+            Map<ModuleVersionResolveState, List<ModuleVersionIdentifier>> shortestPaths = new LinkedHashMap<ModuleVersionResolveState, List<ModuleVersionIdentifier>>();
+            List<ModuleVersionIdentifier> rootPath = new ArrayList<ModuleVersionIdentifier>();
             rootPath.add(root.moduleRevision.id);
             shortestPaths.put(root.moduleRevision, rootPath);
 
-            Set<DefaultModuleRevisionResolveState> directDependees = new LinkedHashSet<DefaultModuleRevisionResolveState>();
+            Set<ModuleVersionResolveState> directDependees = new LinkedHashSet<ModuleVersionResolveState>();
             for (ConfigurationNode node : brokenDependency.requiredBy) {
                 directDependees.add(node.moduleRevision);
             }
 
-            Set<DefaultModuleRevisionResolveState> seen = new HashSet<DefaultModuleRevisionResolveState>();
-            LinkedList<DefaultModuleRevisionResolveState> queue = new LinkedList<DefaultModuleRevisionResolveState>();
+            Set<ModuleVersionResolveState> seen = new HashSet<ModuleVersionResolveState>();
+            LinkedList<ModuleVersionResolveState> queue = new LinkedList<ModuleVersionResolveState>();
             queue.addAll(directDependees);
             while (!queue.isEmpty()) {
-                DefaultModuleRevisionResolveState version = queue.getFirst();
+                ModuleVersionResolveState version = queue.getFirst();
                 if (version == root.moduleRevision) {
                     queue.removeFirst();
                 } else if (seen.add(version)) {
@@ -213,10 +214,10 @@ public class DependencyGraphBuilder {
                     }
                 } else {
                     queue.remove();
-                    List<ModuleRevisionId> shortest = null;
+                    List<ModuleVersionIdentifier> shortest = null;
                     for (ConfigurationNode configuration : version.configurations) {
                         for (DependencyEdge dependencyEdge : configuration.incomingEdges) {
-                            List<ModuleRevisionId> candidate = shortestPaths.get(dependencyEdge.from.moduleRevision);
+                            List<ModuleVersionIdentifier> candidate = shortestPaths.get(dependencyEdge.from.moduleRevision);
                             if (candidate == null) {
                                 continue;
                             }
@@ -230,16 +231,16 @@ public class DependencyGraphBuilder {
                     if (shortest == null) {
                         continue;
                     }
-                    List<ModuleRevisionId> path = new ArrayList<ModuleRevisionId>();
+                    List<ModuleVersionIdentifier> path = new ArrayList<ModuleVersionIdentifier>();
                     path.addAll(shortest);
                     path.add(version.id);
                     shortestPaths.put(version, path);
                 }
             }
 
-            List<List<ModuleRevisionId>> paths = new ArrayList<List<ModuleRevisionId>>();
-            for (DefaultModuleRevisionResolveState version : directDependees) {
-                List<ModuleRevisionId> path = shortestPaths.get(version);
+            List<List<ModuleVersionIdentifier>> paths = new ArrayList<List<ModuleVersionIdentifier>>();
+            for (ModuleVersionResolveState version : directDependees) {
+                List<ModuleVersionIdentifier> path = shortestPaths.get(version);
                 paths.add(path);
             }
             return paths;
@@ -264,45 +265,46 @@ public class DependencyGraphBuilder {
         }
     }
 
+    /**
+     * Represents the edges in the dependency graph.
+     */
     private static class DependencyEdge implements InternalDependencyResult {
         private final ConfigurationNode from;
         private final DependencyDescriptor dependencyDescriptor;
         private final DependencyMetaData dependencyMetaData;
-        private final Set<String> targetConfigurationRules;
         private final ResolveState resolveState;
         private final ModuleVersionSpec selectorSpec;
         private final Set<ConfigurationNode> targetConfigurations = new LinkedHashSet<ConfigurationNode>();
-        private ModuleVersionSelectorResolveState selector;
-        private DefaultModuleRevisionResolveState targetModuleRevision;
+        private final ModuleVersionSelectorResolveState selector;
+        private ModuleVersionResolveState targetModuleRevision;
 
-        public DependencyEdge(ConfigurationNode from, DependencyMetaData dependencyMetaData, Set<String> targetConfigurationRules, ModuleVersionSpec selectorSpec, ResolveState resolveState) {
+        public DependencyEdge(ConfigurationNode from, DependencyMetaData dependencyMetaData, ModuleVersionSpec selectorSpec, ResolveState resolveState) {
             this.from = from;
             this.dependencyMetaData = dependencyMetaData;
             this.dependencyDescriptor = dependencyMetaData.getDescriptor();
-            this.targetConfigurationRules = targetConfigurationRules;
             this.selectorSpec = selectorSpec;
             this.resolveState = resolveState;
+            selector = resolveState.getSelector(dependencyMetaData);
         }
 
         @Override
         public String toString() {
-            return String.format("%s -> %s(%s)", from.toString(), dependencyMetaData.getRequested(), targetConfigurationRules);
-        }
-
-        public DefaultModuleRevisionResolveState getTargetModuleRevision() {
-            return targetModuleRevision;
+            return String.format("%s -> %s(%s)", from.toString(), dependencyMetaData.getRequested(), dependencyDescriptor);
         }
 
-        public void resolveModuleRevisionId() {
+        /**
+         * @return The resolved module version
+         */
+        public ModuleVersionResolveState resolveModuleRevisionId() {
             if (targetModuleRevision == null) {
-                selector = resolveState.getSelector(dependencyMetaData, dependencyDescriptor.getDependencyRevisionId());
                 targetModuleRevision = selector.resolveModuleRevisionId();
-                selector.module.addUnattachedDependency(this);
+                selector.getSelectedModule().addUnattachedDependency(this);
             }
+            return targetModuleRevision;
         }
 
         public boolean isTransitive() {
-            return from.isTransitive() && dependencyDescriptor.isTransitive();
+            return from.isTransitive() && dependencyMetaData.isTransitive();
         }
 
         public void attachToTargetConfigurations() {
@@ -314,7 +316,7 @@ public class DependencyGraphBuilder {
                 targetConfiguration.addIncomingEdge(this);
             }
             if (!targetConfigurations.isEmpty()) {
-                selector.module.removeUnattachedDependency(this);
+                selector.getSelectedModule().removeUnattachedDependency(this);
             }
         }
 
@@ -323,137 +325,120 @@ public class DependencyGraphBuilder {
                 targetConfiguration.removeIncomingEdge(this);
             }
             targetConfigurations.clear();
+            if (targetModuleRevision != null) {
+                selector.getSelectedModule().removeUnattachedDependency(this);
+            }
         }
 
-        public void restart(DefaultModuleRevisionResolveState selected) {
-            selector = selector.restart(selected);
+        public void restart(ModuleVersionResolveState selected) {
             targetModuleRevision = selected;
             attachToTargetConfigurations();
         }
 
         private void calculateTargetConfigurations() {
             targetConfigurations.clear();
-            ModuleVersionMetaData targetModuleVersion = targetModuleRevision.getMetaData();
+            ComponentMetaData targetModuleVersion = targetModuleRevision.getMetaData();
             if (targetModuleVersion == null) {
                 // Broken version
                 return;
             }
 
-            ModuleDescriptor targetDescriptor = targetModuleVersion.getDescriptor();
-            IvyNode node = new IvyNode(resolveState.resolveData, targetDescriptor);
-            Set<String> targets = new LinkedHashSet<String>();
-            for (String targetConfiguration : targetConfigurationRules) {
-                Collections.addAll(targets, node.getRealConfs(targetConfiguration));
-            }
-
-            for (String targetConfigurationName : targets) {
-                // TODO - this is the wrong spot for this check
-                if (targetDescriptor.getConfiguration(targetConfigurationName) == null) {
-                    throw new RuntimeException(String.format("Module version group:%s, module:%s, version:%s, configuration:%s declares a dependency on configuration '%s' which is not declared in the module descriptor for group:%s, module:%s, version:%s",
-                            from.moduleRevision.id.getOrganisation(), from.moduleRevision.id.getName(), from.moduleRevision.id.getRevision(), from.configurationName,
-                            targetConfigurationName, targetModuleRevision.id.getOrganisation(), targetModuleRevision.id.getName(), targetModuleRevision.id.getRevision()));
-                }
-                ConfigurationNode targetConfiguration = resolveState.getConfigurationNode(targetModuleRevision, targetConfigurationName);
-                targetConfigurations.add(targetConfiguration);
+            Set<ConfigurationMetaData> targetConfigurations = resolveState.dependencyToConfigurationResolver.resolveTargetConfigurations(dependencyMetaData, from.metaData, targetModuleVersion);
+            for (ConfigurationMetaData targetConfiguration : targetConfigurations) {
+                ConfigurationNode targetConfigurationNode = resolveState.getConfigurationNode(targetModuleRevision, targetConfiguration.getName());
+                this.targetConfigurations.add(targetConfigurationNode);
             }
         }
 
-        private Set<ResolvedArtifact> getArtifacts(ConfigurationNode childConfiguration, ResolvedArtifactFactory resolvedArtifactFactory) {
-            String[] targetConfigurations = from.heirarchy.toArray(new String[from.heirarchy.size()]);
-            DependencyArtifactDescriptor[] dependencyArtifacts = dependencyDescriptor.getDependencyArtifacts(targetConfigurations);
-            if (dependencyArtifacts.length == 0) {
+        private Set<ResolvedArtifact> getArtifacts(ConfigurationNode childConfiguration) {
+            Set<ComponentArtifactMetaData> dependencyArtifacts = dependencyMetaData.getArtifacts(from.metaData, childConfiguration.metaData);
+            if (dependencyArtifacts.isEmpty()) {
                 return Collections.emptySet();
             }
             Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
-            for (DependencyArtifactDescriptor artifactDescriptor : dependencyArtifacts) {
-                MDArtifact artifact = new MDArtifact(childConfiguration.descriptor, artifactDescriptor.getName(), artifactDescriptor.getType(), artifactDescriptor.getExt(), artifactDescriptor.getUrl(), artifactDescriptor.getQualifiedExtraAttributes());
-                artifacts.add(resolvedArtifactFactory.create(childConfiguration.getResult(), artifact, selector.resolve().getArtifactResolver()));
+            for (ComponentArtifactMetaData artifact : dependencyArtifacts) {
+                artifacts.add(resolveState.builder.newArtifact(childConfiguration.id, childConfiguration.metaData.getComponent(), artifact, resolveState.artifactResolver));
             }
             return artifacts;
         }
 
-        public void attachToParents(ConfigurationNode childConfiguration, ResolvedArtifactFactory artifactFactory, ResolvedConfigurationBuilder result) {
-            DefaultResolvedDependency parent = from.getResult();
-            DefaultResolvedDependency child = childConfiguration.getResult();
-            parent.addChild(child);
-
-            Set<ResolvedArtifact> artifacts = getArtifacts(childConfiguration, artifactFactory);
-            if (!artifacts.isEmpty()) {
-                child.addParentSpecificArtifacts(parent, artifacts);
-            }
+        public void attachToParents(ConfigurationNode childConfiguration, ResolvedConfigurationBuilder oldModelBuilder) {
+            ResolvedConfigurationIdentifier parent = from.id;
+            ResolvedConfigurationIdentifier child = childConfiguration.id;
+            oldModelBuilder.addChild(parent, child);
 
+            Set<ResolvedArtifact> artifacts = getArtifacts(childConfiguration);
             if (artifacts.isEmpty()) {
-                child.addParentSpecificArtifacts(parent, childConfiguration.getArtifacts(artifactFactory));
-            }
-            for (ResolvedArtifact artifact : child.getParentArtifacts(parent)) {
-                result.addArtifact(artifact);
+                artifacts = childConfiguration.getArtifacts();
             }
+            //TODO SF merge with addChild
+            oldModelBuilder.addParentSpecificArtifacts(child, parent, artifacts);
 
-            if (parent == result.getRoot()) {
+            if (parent == resolveState.root.id) {
                 EnhancedDependencyDescriptor enhancedDependencyDescriptor = (EnhancedDependencyDescriptor) dependencyDescriptor;
-                result.addFirstLevelDependency(enhancedDependencyDescriptor.getModuleDependency(), child);
+                oldModelBuilder.addFirstLevelDependency(enhancedDependencyDescriptor.getModuleDependency(), child);
             }
         }
 
         public ModuleVersionSpec getSelector() {
-            String[] configurations = from.heirarchy.toArray(new String[from.heirarchy.size()]);
+            String[] configurations = from.metaData.getHierarchy().toArray(new String[from.metaData.getHierarchy().size()]);
             ModuleVersionSpec selector = ModuleVersionSpec.forExcludes(dependencyDescriptor.getExcludeRules(configurations));
             return selector.intersect(selectorSpec);
         }
 
-        public boolean isFailed() {
-            return selector != null && selector.failure != null;
-        }
-
-        public ModuleVersionSelector getRequested() {
-            return dependencyMetaData.getRequested();
+        public ComponentSelector getRequested() {
+            return dependencyMetaData.getSelector();
         }
 
         public ModuleVersionResolveException getFailure() {
-            //see also getSelected(). For evicted targetModuleRevisions, we need to reach out to the failure of selected module
-            //covered in VersionConflictResolutionIntegrationTest
-            return selector.failure != null ? selector.failure : getSelected().resolver.failure;
+            return selector.getFailure();
         }
 
-        public DefaultModuleRevisionResolveState getSelected() {
-            //we cannot use the targetModuleRevision field because it may have been evicted
-            //covered in VersionConflictResolutionIntegrationTest
-            return selector.module.selected;
+        public ModuleVersionIdentifier getSelected() {
+            return selector.getSelected().getSelectedId();
         }
 
-        public ModuleVersionSelectionReason getReason() {
-            return getSelected() == null ? selector.idSelectionReason : getSelected().selectionReason;
+        public ComponentSelectionReason getReason() {
+            return selector.getSelectionReason();
         }
 
         public void collectFailures(FailureState failureState) {
-            if (isFailed()) {
-                failureState.addUnresolvedDependency(this, selector.dependencyMetaData.getRequested(), selector.failure);
+            ModuleVersionResolveException failure = getFailure();
+            if (failure != null) {
+                failureState.addUnresolvedDependency(this, selector.dependencyMetaData.getRequested(), failure);
             }
         }
-
     }
 
+    /**
+     * Global resolution state.
+     */
     private static class ResolveState {
-        private final Map<ModuleId, ModuleResolveState> modules = new LinkedHashMap<ModuleId, ModuleResolveState>();
+        private final Map<ModuleIdentifier, ModuleResolveState> modules = new LinkedHashMap<ModuleIdentifier, ModuleResolveState>();
         private final Map<ResolvedConfigurationIdentifier, ConfigurationNode> nodes = new LinkedHashMap<ResolvedConfigurationIdentifier, ConfigurationNode>();
-        private final Map<ModuleRevisionId, ModuleVersionSelectorResolveState> selectors = new LinkedHashMap<ModuleRevisionId, ModuleVersionSelectorResolveState>();
-        private final ConfigurationNode root;
+        private final Map<ModuleVersionSelector, ModuleVersionSelectorResolveState> selectors = new LinkedHashMap<ModuleVersionSelector, ModuleVersionSelectorResolveState>();
+        private final RootConfigurationNode root;
         private final DependencyToModuleVersionIdResolver resolver;
-        private final ResolveData resolveData;
+        private final DependencyToConfigurationResolver dependencyToConfigurationResolver;
+        private final ArtifactResolver artifactResolver;
+        private final ResolvedConfigurationBuilder builder;
         private final Set<ConfigurationNode> queued = new HashSet<ConfigurationNode>();
         private final LinkedList<ConfigurationNode> queue = new LinkedList<ConfigurationNode>();
 
-        public ResolveState(BuildableModuleVersionMetaData rootModule, String rootConfigurationName, DependencyToModuleVersionIdResolver resolver, ResolveData resolveData) {
+        public ResolveState(ComponentResolveResult rootResult, String rootConfigurationName, DependencyToModuleVersionIdResolver resolver,
+                            DependencyToConfigurationResolver dependencyToConfigurationResolver, ArtifactResolver artifactResolver, ResolvedConfigurationBuilder builder) {
             this.resolver = resolver;
-            this.resolveData = resolveData;
-            DefaultModuleRevisionResolveState rootVersion = getRevision(rootModule.getId());
-            rootVersion.setMetaData(rootModule);
-            root = getConfigurationNode(rootVersion, rootConfigurationName);
+            this.dependencyToConfigurationResolver = dependencyToConfigurationResolver;
+            this.artifactResolver = artifactResolver;
+            this.builder = builder;
+            ModuleVersionResolveState rootVersion = getRevision(rootResult.getId());
+            rootVersion.setResolveResult(rootResult);
+            root = new RootConfigurationNode(rootVersion, new ResolvedConfigurationIdentifier(rootVersion.id, rootConfigurationName), this);
+            nodes.put(root.id, root);
             root.moduleRevision.module.select(root.moduleRevision);
         }
 
-        public ModuleResolveState getModule(ModuleId moduleId) {
-            ModuleId id = new ModuleId(moduleId.getOrganisation(), moduleId.getName());
+        public ModuleResolveState getModule(ModuleIdentifier id) {
             ModuleResolveState module = modules.get(id);
             if (module == null) {
                 module = new ModuleResolveState(id, this);
@@ -462,32 +447,30 @@ public class DependencyGraphBuilder {
             return module;
         }
 
-        public DefaultModuleRevisionResolveState getRevision(ModuleVersionIdentifier moduleVersionIdentifier) {
-            ModuleRevisionId id = new ModuleRevisionId(new ModuleId(moduleVersionIdentifier.getGroup(), moduleVersionIdentifier.getName()), moduleVersionIdentifier.getVersion());
-            return getModule(id.getModuleId()).getVersion(id);
+        public ModuleVersionResolveState getRevision(ModuleVersionIdentifier id) {
+            return getModule(id.getModule()).getVersion(id);
         }
 
         public Collection<ConfigurationNode> getConfigurationNodes() {
             return nodes.values();
         }
 
-        public ConfigurationNode getConfigurationNode(DefaultModuleRevisionResolveState module, String configurationName) {
-            ModuleRevisionId original = module.id;
-            ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(original.getOrganisation(), original.getName(), original.getRevision(), configurationName);
+        public ConfigurationNode getConfigurationNode(ModuleVersionResolveState module, String configurationName) {
+            ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(module.id, configurationName);
             ConfigurationNode configuration = nodes.get(id);
             if (configuration == null) {
-                configuration = new ConfigurationNode(module, module.metaData, configurationName, this);
+                configuration = new ConfigurationNode(module, id, this);
                 nodes.put(id, configuration);
             }
             return configuration;
         }
 
-        public ModuleVersionSelectorResolveState getSelector(DependencyMetaData dependencyMetaData, ModuleRevisionId original) {
-            ModuleRevisionId selectorId = ModuleRevisionId.newInstance(original.getOrganisation(), original.getName(), original.getRevision());
-            ModuleVersionSelectorResolveState resolveState = selectors.get(selectorId);
+        public ModuleVersionSelectorResolveState getSelector(DependencyMetaData dependencyMetaData) {
+            ModuleVersionSelector requested = dependencyMetaData.getRequested();
+            ModuleVersionSelectorResolveState resolveState = selectors.get(requested);
             if (resolveState == null) {
-                resolveState = new ModuleVersionSelectorResolveState(dependencyMetaData, getModule(selectorId.getModuleId()), resolver, this);
-                selectors.put(selectorId, resolveState);
+                resolveState = new ModuleVersionSelectorResolveState(dependencyMetaData, resolver, this);
+                selectors.put(requested, resolveState);
             }
             return resolveState;
         }
@@ -531,45 +514,57 @@ public class DependencyGraphBuilder {
         Evicted
     }
 
+    /**
+     * Resolution state for a given module.
+     */
     private static class ModuleResolveState {
-        final ModuleId id;
+        final ModuleIdentifier id;
         final Set<DependencyEdge> unattachedDependencies = new LinkedHashSet<DependencyEdge>();
-        final Map<ModuleRevisionId, DefaultModuleRevisionResolveState> versions = new LinkedHashMap<ModuleRevisionId, DefaultModuleRevisionResolveState>();
+        final Map<ModuleVersionIdentifier, ModuleVersionResolveState> versions = new LinkedHashMap<ModuleVersionIdentifier, ModuleVersionResolveState>();
+        final Set<ModuleVersionSelectorResolveState> selectors = new HashSet<ModuleVersionSelectorResolveState>();
         final ResolveState resolveState;
-        DefaultModuleRevisionResolveState selected;
+        ModuleVersionResolveState selected;
 
-        private ModuleResolveState(ModuleId id, ResolveState resolveState) {
+        private ModuleResolveState(ModuleIdentifier id, ResolveState resolveState) {
             this.id = id;
             this.resolveState = resolveState;
         }
 
-        public Collection<DefaultModuleRevisionResolveState> getVersions() {
+        @Override
+        public String toString() {
+            return id.toString();
+        }
+
+        public Collection<ModuleVersionResolveState> getVersions() {
             return versions.values();
         }
 
-        public void select(DefaultModuleRevisionResolveState selected) {
+        public void select(ModuleVersionResolveState selected) {
             assert this.selected == null;
             this.selected = selected;
-            for (DefaultModuleRevisionResolveState version : versions.values()) {
+            for (ModuleVersionResolveState version : versions.values()) {
                 version.state = ModuleState.Evicted;
             }
             selected.state = ModuleState.Selected;
         }
 
-        public DefaultModuleRevisionResolveState clearSelection() {
-            DefaultModuleRevisionResolveState previousSelection = selected;
+        public ModuleVersionResolveState clearSelection() {
+            ModuleVersionResolveState previousSelection = selected;
             selected = null;
-            for (DefaultModuleRevisionResolveState version : versions.values()) {
+            for (ModuleVersionResolveState version : versions.values()) {
                 version.state = ModuleState.Conflict;
             }
             return previousSelection;
         }
 
-        public void restart(DefaultModuleRevisionResolveState selected) {
+        public void restart(ModuleVersionResolveState selected) {
             select(selected);
-            for (DefaultModuleRevisionResolveState version : versions.values()) {
+            for (ModuleVersionResolveState version : versions.values()) {
                 version.restart(selected);
             }
+            for (ModuleVersionSelectorResolveState selector : selectors) {
+                selector.restart(selected);
+            }
             for (DependencyEdge dependency : new ArrayList<DependencyEdge>(unattachedDependencies)) {
                 dependency.restart(selected);
             }
@@ -584,29 +579,37 @@ public class DependencyGraphBuilder {
             unattachedDependencies.remove(edge);
         }
 
-        public DefaultModuleRevisionResolveState getVersion(ModuleRevisionId id) {
-            DefaultModuleRevisionResolveState moduleRevision = versions.get(id);
+        public ModuleVersionResolveState getVersion(ModuleVersionIdentifier id) {
+            ModuleVersionResolveState moduleRevision = versions.get(id);
             if (moduleRevision == null) {
-                moduleRevision = new DefaultModuleRevisionResolveState(this, id, resolveState);
+                moduleRevision = new ModuleVersionResolveState(this, id, resolveState);
                 versions.put(id, moduleRevision);
             }
 
             return moduleRevision;
         }
+
+        public void addSelector(ModuleVersionSelectorResolveState selector) {
+            selectors.add(selector);
+        }
     }
 
-    private static class DefaultModuleRevisionResolveState implements ModuleRevisionResolveState, ModuleVersionSelection {
+    /**
+     * Resolution state for a given module version.
+     */
+    private static class ModuleVersionResolveState implements ModuleRevisionResolveState, ModuleVersionSelection {
         final ModuleResolveState module;
-        final ModuleRevisionId id;
+        final ModuleVersionIdentifier id;
         final ResolveState resolveState;
         final Set<ConfigurationNode> configurations = new LinkedHashSet<ConfigurationNode>();
-        List<DependencyMetaData> dependencies;
-        ModuleVersionMetaData metaData;
+        ComponentMetaData metaData;
         ModuleState state = ModuleState.New;
-        ModuleVersionSelectorResolveState resolver;
-        ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED;
+        ComponentSelectionReason selectionReason = VersionSelectionReasons.REQUESTED;
+        ModuleVersionIdResolveResult idResolveResult;
+        ComponentResolveResult resolveResult;
+        ModuleVersionResolveException failure;
 
-        private DefaultModuleRevisionResolveState(ModuleResolveState module, ModuleRevisionId id, ResolveState resolveState) {
+        private ModuleVersionResolveState(ModuleResolveState module, ModuleVersionIdentifier id, ResolveState resolveState) {
             this.module = module;
             this.id = id;
             this.resolveState = resolveState;
@@ -617,47 +620,58 @@ public class DependencyGraphBuilder {
             return id.toString();
         }
 
-        public String getRevision() {
-            return id.getRevision();
+        public String getVersion() {
+            return id.getVersion();
         }
 
-        public Iterable<DependencyMetaData> getDependencies() {
-            if (dependencies == null) {
-                dependencies = getMetaData().getDependencies();
-            }
-            return dependencies;
+        public String getId() {
+            return String.format("%s:%s:%s", id.getGroup(), id.getName(), id.getVersion());
         }
 
-        public String getId() {
-            return String.format("%s:%s:%s", id.getOrganisation(), id.getName(), id.getRevision());
+        public ModuleVersionResolveException getFailure() {
+            return failure;
         }
 
-        public void restart(DefaultModuleRevisionResolveState selected) {
-            for (ConfigurationNode conflictConfiguration : configurations) {
-                conflictConfiguration.restart(selected);
+        public void restart(ModuleVersionResolveState selected) {
+            for (ConfigurationNode configuration : configurations) {
+                configuration.restart(selected);
             }
         }
 
         public void addResolver(ModuleVersionSelectorResolveState resolver) {
-            if (this.resolver == null) {
-                this.resolver = resolver;
+            if (this.idResolveResult == null) {
+                idResolveResult = resolver.idResolveResult;
             }
         }
 
-        public ModuleVersionMetaData getMetaData() {
+        public ComponentResolveResult resolve() {
+            if (resolveResult != null) {
+                return resolveResult;
+            }
+            if (failure != null) {
+                return null;
+            }
+
+            resolveResult = idResolveResult.resolve();
+            if (resolveResult.getFailure() != null) {
+                failure = resolveResult.getFailure();
+                return null;
+            }
+            setResolveResult(resolveResult);
+            return resolveResult;
+        }
+
+        public ComponentMetaData getMetaData() {
             if (metaData == null) {
-                if (resolver == null) {
-                    throw new IllegalStateException(String.format("No resolver for %s.", this));
-                }
-                resolver.resolve();
+                resolve();
             }
             return metaData;
         }
 
-        public void setMetaData(ModuleVersionMetaData metaData) {
-            if (this.metaData == null) {
-                this.metaData = metaData;
-            }
+        public void setResolveResult(ComponentResolveResult resolveResult) {
+            this.resolveResult = resolveResult;
+            this.metaData = resolveResult.getMetaData();
+            this.failure = null;
         }
 
         public void addConfiguration(ConfigurationNode configurationNode) {
@@ -665,80 +679,64 @@ public class DependencyGraphBuilder {
         }
 
         public ModuleVersionIdentifier getSelectedId() {
-            return new DefaultModuleVersionIdentifier(
-                    id.getOrganisation(),
-                    id.getName(),
-                    id.getRevision());
+            return id;
         }
 
-        public ModuleVersionSelectionReason getSelectionReason() {
+        public ComponentSelectionReason getSelectionReason() {
             return selectionReason;
         }
 
-        public void setSelectionReason(ModuleVersionSelectionReason reason) {
+        public void setSelectionReason(ComponentSelectionReason reason) {
             this.selectionReason = reason;
         }
+
+        public ComponentIdentifier getComponentId() {
+            return getMetaData().getComponentId();
+        }
     }
 
+    /**
+     * Represents a node in the dependency graph.
+     */
     private static class ConfigurationNode {
-        final DefaultModuleRevisionResolveState moduleRevision;
+        final ModuleVersionResolveState moduleRevision;
+        final ConfigurationMetaData metaData;
         final ResolveState resolveState;
-        final DefaultModuleDescriptor descriptor;
-        final String configurationName;
-        final Set<String> heirarchy = new LinkedHashSet<String>();
         final Set<DependencyEdge> incomingEdges = new LinkedHashSet<DependencyEdge>();
         final Set<DependencyEdge> outgoingEdges = new LinkedHashSet<DependencyEdge>();
-        DefaultResolvedDependency result;
+        final ResolvedConfigurationIdentifier id;
         ModuleVersionSpec previousTraversal;
         Set<ResolvedArtifact> artifacts;
 
-        private ConfigurationNode(DefaultModuleRevisionResolveState moduleRevision, ModuleVersionMetaData moduleVersionMetaData, String configurationName, ResolveState resolveState) {
+        private ConfigurationNode(ModuleVersionResolveState moduleRevision, ResolvedConfigurationIdentifier id, ResolveState resolveState) {
             this.moduleRevision = moduleRevision;
             this.resolveState = resolveState;
-            this.descriptor = (DefaultModuleDescriptor) moduleVersionMetaData.getDescriptor();
-            this.configurationName = configurationName;
-            findAncestors(configurationName, resolveState, heirarchy);
+            this.metaData = moduleRevision.metaData.getConfiguration(id.getConfiguration());
+            this.id = id;
             moduleRevision.addConfiguration(this);
         }
 
-        void findAncestors(String config, ResolveState container, Set<String> ancestors) {
-            ancestors.add(config);
-            for (String parentConfig : descriptor.getConfiguration(config).getExtends()) {
-                ancestors.addAll(container.getConfigurationNode(moduleRevision, parentConfig).heirarchy);
-            }
-        }
-
         @Override
         public String toString() {
-            return String.format("%s(%s)", moduleRevision, configurationName);
+            return String.format("%s(%s)", moduleRevision, metaData.getName());
         }
 
-        public Set<ResolvedArtifact> getArtifacts(ResolvedArtifactFactory resolvedArtifactFactory) {
+        public Set<ResolvedArtifact> getArtifacts() {
             if (artifacts == null) {
                 artifacts = new LinkedHashSet<ResolvedArtifact>();
-                for (String config : heirarchy) {
-                    for (Artifact artifact : descriptor.getArtifacts(config)) {
-                        artifacts.add(resolvedArtifactFactory.create(getResult(), artifact, moduleRevision.resolver.resolve().getArtifactResolver()));
-                    }
+
+                BuildableArtifactSetResolveResult result = new DefaultBuildableArtifactSetResolveResult();
+                resolveState.artifactResolver.resolveModuleArtifacts(metaData.getComponent(), new ConfigurationResolveContext(metaData.getName()), result);
+
+                for (ComponentArtifactMetaData artifact : result.getArtifacts()) {
+                    artifacts.add(resolveState.builder.newArtifact(id, metaData.getComponent(), artifact, resolveState.artifactResolver));
                 }
             }
             return artifacts;
         }
 
-        public DefaultResolvedDependency getResult() {
-            if (result == null) {
-                result = new DefaultResolvedDependency(
-                        moduleRevision.id.getOrganisation(),
-                        moduleRevision.id.getName(),
-                        moduleRevision.id.getRevision(),
-                        configurationName);
-            }
-
-            return result;
-        }
-
         public boolean isTransitive() {
-            return descriptor.getConfiguration(configurationName).isTransitive();
+            return metaData.isTransitive();
         }
 
         public void visitOutgoingDependencies(Collection<DependencyEdge> target) {
@@ -747,7 +745,7 @@ public class DependencyGraphBuilder {
             // If traversed before, and the selected modules have changed, remove previous outgoing edges and add outgoing edges again with
             //    the new selections.
             // If traversed before, and the selected modules have not changed, ignore
-            // If none of the incoming edges is transitive, then the node has no outgoing edges
+            // If none of the incoming edges are transitive, then the node has no outgoing edges
 
             if (moduleRevision.state != ModuleState.Selected) {
                 LOGGER.debug("version for {} is not selected. ignoring.", this);
@@ -782,35 +780,20 @@ public class DependencyGraphBuilder {
                 removeOutgoingEdges();
             }
 
-            for (DependencyMetaData dependency : moduleRevision.getDependencies()) {
+            for (DependencyMetaData dependency : metaData.getDependencies()) {
                 DependencyDescriptor dependencyDescriptor = dependency.getDescriptor();
                 ModuleId targetModuleId = dependencyDescriptor.getDependencyRevisionId().getModuleId();
-                Set<String> targetConfigurations = getTargetConfigurations(dependencyDescriptor);
-                if (!targetConfigurations.isEmpty()) {
-                    if (!selectorSpec.isSatisfiedBy(targetModuleId)) {
-                        LOGGER.debug("{} is excluded from {}.", targetModuleId, this);
-                    } else {
-                        DependencyEdge dependencyEdge = new DependencyEdge(this, dependency, targetConfigurations, selectorSpec, resolveState);
-                        outgoingEdges.add(dependencyEdge);
-                        target.add(dependencyEdge);
-                    }
+                if (!selectorSpec.isSatisfiedBy(targetModuleId)) {
+                    LOGGER.debug("{} is excluded from {}.", targetModuleId, this);
+                    continue;
                 }
+                DependencyEdge dependencyEdge = new DependencyEdge(this, dependency, selectorSpec, resolveState);
+                outgoingEdges.add(dependencyEdge);
+                target.add(dependencyEdge);
             }
             previousTraversal = selectorSpec;
         }
 
-        Set<String> getTargetConfigurations(DependencyDescriptor dependencyDescriptor) {
-            Set<String> targetConfigurations = new LinkedHashSet<String>();
-            for (String moduleConfiguration : dependencyDescriptor.getModuleConfigurations()) {
-                if (moduleConfiguration.equals("*") || heirarchy.contains(moduleConfiguration)) {
-                    for (String targetConfiguration : dependencyDescriptor.getDependencyConfigurations(moduleConfiguration)) {
-                        targetConfigurations.add(targetConfiguration);
-                    }
-                }
-            }
-            return targetConfigurations;
-        }
-
         public void addIncomingEdge(DependencyEdge dependencyEdge) {
             incomingEdges.add(dependencyEdge);
             resolveState.onMoreSelected(this);
@@ -825,10 +808,10 @@ public class DependencyGraphBuilder {
             return moduleRevision.state == ModuleState.Selected;
         }
 
-        public void attachToParents(ResolvedArtifactFactory artifactFactory, ResolvedConfigurationBuilder result) {
+        public void attachToParents(ResolvedConfigurationBuilder oldModelBuilder) {
             LOGGER.debug("Attaching {} to its parents.", this);
             for (DependencyEdge dependency : incomingEdges) {
-                dependency.attachToParents(this, artifactFactory, result);
+                dependency.attachToParents(this, oldModelBuilder);
             }
         }
 
@@ -849,8 +832,7 @@ public class DependencyGraphBuilder {
                     selector = selector.union(dependencyEdge.getSelector());
                 }
             }
-            String[] configurations = heirarchy.toArray(new String[heirarchy.size()]);
-            selector = selector.intersect(ModuleVersionSpec.forExcludes(descriptor.getExcludeRules(configurations)));
+            selector = selector.intersect(ModuleVersionSpec.forExcludes(metaData.getExcludeRules()));
             return selector;
         }
 
@@ -862,43 +844,70 @@ public class DependencyGraphBuilder {
             previousTraversal = null;
         }
 
-        public void restart(DefaultModuleRevisionResolveState state) {
+        public void restart(ModuleVersionResolveState selected) {
             // Restarting this configuration after conflict resolution.
             // If this configuration belongs to the select version, queue ourselves up for traversal.
-            // If not, then move our incoming edges across to the selected configuration
-            if (moduleRevision == state) {
+            // If not, then remove our incoming edges, which triggers them to be moved across to the selected configuration
+            if (moduleRevision == selected) {
                 resolveState.onMoreSelected(this);
             } else {
                 for (DependencyEdge dependency : incomingEdges) {
-                    dependency.restart(state);
+                    dependency.restart(selected);
                 }
                 incomingEdges.clear();
             }
         }
 
         private ModuleVersionIdentifier toId() {
-            return newId(moduleRevision.id.getOrganisation(),
-                    moduleRevision.id.getName(),
-                    moduleRevision.id.getRevision());
+            return moduleRevision.id;
+        }
+
+        public void validate() {
+            for (DependencyEdge incomingEdge : incomingEdges) {
+                ConfigurationNode fromNode = incomingEdge.from;
+                if (!fromNode.isSelected()) {
+                    throw new IllegalStateException(String.format("Unexpected state %s for parent node for dependency from %s to %s.", fromNode.moduleRevision.state, fromNode, this));
+                }
+            }
+        }
+
+        public void deselect() {
+            removeOutgoingEdges();
+        }
+    }
+
+    private static class RootConfigurationNode extends ConfigurationNode {
+        private RootConfigurationNode(ModuleVersionResolveState moduleRevision, ResolvedConfigurationIdentifier id, ResolveState resolveState) {
+            super(moduleRevision, id, resolveState);
+        }
+
+        @Override
+        public boolean isSelected() {
+            return true;
+        }
+
+        @Override
+        public void deselect() {
         }
     }
 
+    /**
+     * Resolution state for a given module version selector.
+     */
     private static class ModuleVersionSelectorResolveState {
         final DependencyToModuleVersionIdResolver resolver;
         final ResolveState resolveState;
         final DependencyMetaData dependencyMetaData;
-        ModuleResolveState module;
         ModuleVersionResolveException failure;
-        ModuleVersionSelectionReason idSelectionReason;
-        DefaultModuleRevisionResolveState targetModuleRevision;
+        ModuleResolveState targetModule;
+        ModuleVersionResolveState targetModuleRevision;
         ModuleVersionIdResolveResult idResolveResult;
-        ModuleVersionResolveResult resolveResult;
 
-        private ModuleVersionSelectorResolveState(DependencyMetaData dependencyMetaData, ModuleResolveState module, DependencyToModuleVersionIdResolver resolver, ResolveState resolveState) {
+        private ModuleVersionSelectorResolveState(DependencyMetaData dependencyMetaData, DependencyToModuleVersionIdResolver resolver, ResolveState resolveState) {
             this.dependencyMetaData = dependencyMetaData;
-            this.module = module;
             this.resolver = resolver;
             this.resolveState = resolveState;
+            targetModule = resolveState.getModule(new DefaultModuleIdentifier(dependencyMetaData.getRequested().getGroup(), dependencyMetaData.getRequested().getName()));
         }
 
         @Override
@@ -906,10 +915,26 @@ public class DependencyGraphBuilder {
             return dependencyMetaData.toString();
         }
 
+        private ModuleVersionResolveException getFailure() {
+            return failure != null ? failure : targetModuleRevision.getFailure();
+        }
+
+        public ComponentSelectionReason getSelectionReason() {
+            return targetModuleRevision == null ? idResolveResult.getSelectionReason() : targetModuleRevision.getSelectionReason();
+        }
+
+        public ModuleVersionResolveState getSelected() {
+            return targetModule.selected;
+        }
+
+        public ModuleResolveState getSelectedModule() {
+            return targetModule;
+        }
+
         /**
          * @return The module version, or null if there is a failure to resolve this selector.
          */
-        public DefaultModuleRevisionResolveState resolveModuleRevisionId() {
+        public ModuleVersionResolveState resolveModuleRevisionId() {
             if (targetModuleRevision != null) {
                 return targetModuleRevision;
             }
@@ -918,7 +943,6 @@ public class DependencyGraphBuilder {
             }
 
             idResolveResult = resolver.resolve(dependencyMetaData);
-            idSelectionReason = idResolveResult.getSelectionReason();
             if (idResolveResult.getFailure() != null) {
                 failure = idResolveResult.getFailure();
                 return null;
@@ -927,33 +951,15 @@ public class DependencyGraphBuilder {
             targetModuleRevision = resolveState.getRevision(idResolveResult.getId());
             targetModuleRevision.addResolver(this);
             targetModuleRevision.selectionReason = idResolveResult.getSelectionReason();
-
-            //the target module details might have been substituted / forced when resolving ID
-            //so update the module:
-            this.module = targetModuleRevision.module;
+            targetModule = targetModuleRevision.module;
+            targetModule.addSelector(this);
 
             return targetModuleRevision;
         }
 
-        public ModuleVersionResolveResult resolve() {
-            if (resolveResult != null) {
-                return resolveResult;
-            }
-            if (failure != null) {
-                return null;
-            }
-
-            try {
-                resolveResult = idResolveResult.resolve();
-                resolveState.getRevision(resolveResult.getId()).setMetaData(resolveResult.getMetaData());
-            } catch (ModuleVersionResolveException e) {
-                failure = e;
-            }
-            return resolveResult;
-        }
-
-        public ModuleVersionSelectorResolveState restart(DefaultModuleRevisionResolveState moduleRevision) {
-            return resolveState.getSelector(dependencyMetaData.withRequestedVersion(moduleRevision.id.getRevision()), moduleRevision.id);
+        public void restart(ModuleVersionResolveState moduleRevision) {
+            this.targetModuleRevision = moduleRevision;
+            this.targetModule = moduleRevision.module;
         }
     }
 
@@ -964,7 +970,7 @@ public class DependencyGraphBuilder {
             this.resolver = resolver;
         }
 
-        DefaultModuleRevisionResolveState select(Collection<DefaultModuleRevisionResolveState> candidates, DefaultModuleRevisionResolveState root) {
+        ModuleVersionResolveState select(Collection<ModuleVersionResolveState> candidates, ModuleVersionResolveState root) {
             for (ConfigurationNode configuration : root.configurations) {
                 for (DependencyEdge outgoingEdge : configuration.outgoingEdges) {
                     if (outgoingEdge.dependencyDescriptor.isForce() && candidates.contains(outgoingEdge.targetModuleRevision)) {
@@ -973,7 +979,7 @@ public class DependencyGraphBuilder {
                     }
                 }
             }
-            return (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
+            return resolver.select(candidates);
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyToConfigurationResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyToConfigurationResolver.java
new file mode 100644
index 0000000..04bec5d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyToConfigurationResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
+
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.ConfigurationMetaData;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+
+import java.util.Set;
+
+/**
+ * Responsible for mapping a dependency definition to the set of configurations that it refers to.
+ */
+public interface DependencyToConfigurationResolver {
+    Set<ConfigurationMetaData> resolveTargetConfigurations(DependencyMetaData dependencyMetaData, ConfigurationMetaData fromConfiguration, ComponentMetaData targetComponent);
+}
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 14be289..e49b0ca 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,23 +15,18 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
-import org.gradle.api.internal.artifacts.version.LatestVersionSemanticComparator;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
 
 class LatestModuleConflictResolver implements ModuleConflictResolver {
-    public ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root) {
-        return Collections.max(candidates, new VersionComparator());
-    }
-
-    private class VersionComparator implements Comparator<ModuleRevisionResolveState> {
+    private final LatestStrategy latestStrategy;
 
-        LatestVersionSemanticComparator delegate = new LatestVersionSemanticComparator();
+        LatestModuleConflictResolver(LatestStrategy latestStrategy) {
+        this.latestStrategy = latestStrategy;
+    }
 
-        public int compare(ModuleRevisionResolveState left, ModuleRevisionResolveState right) {
-            return delegate.compare(left.getRevision(), right.getRevision());
-        }
+    public <T extends ModuleRevisionResolveState> T select(Collection<? extends T> candidates) {
+        return latestStrategy.findLatest(candidates);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java
index 4336b9e..e1c6784 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java
@@ -18,5 +18,5 @@ package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 import java.util.Collection;
 
 interface ModuleConflictResolver {
-    ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root);
+    <T extends ModuleRevisionResolveState> T select(Collection<? extends T> candidates);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
index 6decc2c..155a4b8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
@@ -15,14 +15,13 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.Versioned;
 
-interface ModuleRevisionResolveState {
+interface ModuleRevisionResolveState extends Versioned {
     String getId();
 
-    String getRevision();
+    ComponentSelectionReason getSelectionReason();
 
-    ModuleVersionSelectionReason getSelectionReason();
-
-    void setSelectionReason(ModuleVersionSelectionReason moduleVersionSelectionReason);
+    void setSelectionReason(ComponentSelectionReason componentSelectionReason);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
index f8c0471..307b8db 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
@@ -24,6 +24,8 @@ import org.gradle.api.specs.Spec;
 
 import java.util.*;
 
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleId;
+
 /**
  * Manages sets of exclude rules, allowing union and intersection operations on the rules.
  *
@@ -299,7 +301,7 @@ public abstract class ModuleVersionSpec implements Spec<ModuleId> {
                 }
             } else if (spec2 instanceof ModuleNameSpec) {
                 ModuleNameSpec moduleNameSpec = (ModuleNameSpec) spec2;
-                merged.add(new ModuleIdSpec(ModuleId.newInstance(spec1.group, moduleNameSpec.module)));
+                merged.add(new ModuleIdSpec(createModuleId(spec1.group, moduleNameSpec.module)));
             } else if (spec2 instanceof ModuleIdSpec) {
                 ModuleIdSpec moduleIdSpec = (ModuleIdSpec) spec2;
                 if (moduleIdSpec.moduleId.getOrganisation().equals(spec1.group)) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java
index 86f6daf..511a5d8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java
@@ -19,7 +19,7 @@ import java.util.Collection;
 import java.util.Formatter;
 
 class StrictConflictResolver implements ModuleConflictResolver {
-    public ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root) {
+    public <T extends ModuleRevisionResolveState> T select(Collection<? extends T> candidates) {
         Formatter formatter = new Formatter();
         formatter.format("A conflict was found between the following modules:");
         for (ModuleRevisionResolveState candidate : candidates) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java
index cb0d3c2..37588bf 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java
@@ -20,9 +20,6 @@ import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.Version
 
 import java.util.Collection;
 
-/**
-* by Szczepan Faber, created at: 1/29/13
-*/
 public class VersionSelectionReasonResolver implements ModuleConflictResolver {
 
     private final ModuleConflictResolver delegate;
@@ -31,8 +28,8 @@ public class VersionSelectionReasonResolver implements ModuleConflictResolver {
         this.delegate = delegate;
     }
 
-    public ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root) {
-        ModuleRevisionResolveState selected = delegate.select(candidates, root);
+    public <T extends ModuleRevisionResolveState> T select(Collection<? extends T> candidates) {
+        T selected = delegate.select(candidates);
         selected.setSelectionReason(VersionSelectionReasons.withConflictResolution(selected.getSelectionReason()));
         return selected;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java
new file mode 100644
index 0000000..1cc87e4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.oldresult;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.artifacts.UnresolvedDependency;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultBuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.internal.Factory;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.LongIdGenerator;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultResolvedConfigurationBuilder implements
+        ResolvedConfigurationBuilder, ResolvedConfigurationResults, ResolvedContentsMapping {
+
+    private final Map<Long, ResolvedArtifact> artifacts = new LinkedHashMap<Long, ResolvedArtifact>();
+    private final Set<UnresolvedDependency> unresolvedDependencies = new LinkedHashSet<UnresolvedDependency>();
+    private final IdGenerator<Long> idGenerator = new LongIdGenerator();
+    private final Map<ResolvedConfigurationIdentifier, ModuleDependency> modulesMap = new HashMap<ResolvedConfigurationIdentifier, ModuleDependency>();
+
+    private final TransientConfigurationResultsBuilder builder;
+
+    public DefaultResolvedConfigurationBuilder(TransientConfigurationResultsBuilder builder) {
+        this.builder = builder;
+    }
+
+    public void addUnresolvedDependency(UnresolvedDependency unresolvedDependency) {
+        unresolvedDependencies.add(unresolvedDependency);
+    }
+
+    public void addFirstLevelDependency(ModuleDependency moduleDependency, ResolvedConfigurationIdentifier dependency) {
+        builder.firstLevelDependency(dependency);
+        //we don't serialise the module dependencies at this stage so we need to keep track
+        //of the mapping module dependency <-> resolved dependency
+        modulesMap.put(dependency, moduleDependency);
+    }
+
+    public void done(ResolvedConfigurationIdentifier root) {
+        builder.done(root);
+    }
+
+    public void addChild(ResolvedConfigurationIdentifier parent, ResolvedConfigurationIdentifier child) {
+        builder.parentChildMapping(parent, child);
+    }
+
+    public void addParentSpecificArtifacts(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, Set<ResolvedArtifact> artifacts) {
+        for (ResolvedArtifact a : artifacts) {
+            builder.parentSpecificArtifact(child, parent, ((DefaultResolvedArtifact) a).getId());
+        }
+    }
+
+    public void newResolvedDependency(ResolvedConfigurationIdentifier id) {
+        builder.resolvedDependency(id);
+    }
+
+    public ResolvedArtifact newArtifact(ResolvedConfigurationIdentifier owner, ComponentMetaData compnent, ComponentArtifactMetaData artifact, ArtifactResolver artifactResolver) {
+        Factory<File> artifactSource = new LazyArtifactSource(artifact, compnent.getSource(), artifactResolver);
+        Factory<ResolvedDependency> dependencySource = new LazyResolvedDependencySource(owner, builder, this);
+        long id = idGenerator.generateId();
+        ResolvedArtifact newArtifact = new DefaultResolvedArtifact(new DefaultResolvedModuleVersion(owner.getId()), dependencySource, artifact.getName(), artifactSource, id);
+        artifacts.put(id, newArtifact);
+        return newArtifact;
+    }
+
+    public boolean hasError() {
+        return !unresolvedDependencies.isEmpty();
+    }
+
+    public TransientConfigurationResults more() {
+        return builder.load(this);
+    }
+
+    public Set<ResolvedArtifact> getArtifacts() {
+        return new LinkedHashSet<ResolvedArtifact>(artifacts.values());
+    }
+
+    public ResolvedArtifact getArtifact(long artifactId) {
+        ResolvedArtifact a = artifacts.get(artifactId);
+        assert a != null : "Unable to find artifact for id: " + artifactId;
+        return a;
+    }
+
+    public ModuleDependency getModuleDependency(ResolvedConfigurationIdentifier id) {
+        ModuleDependency m = modulesMap.get(id);
+        assert m != null : "Unable to find module dependency for id: " + id;
+        return m;
+    }
+
+    public Set<UnresolvedDependency> getUnresolvedDependencies() {
+        return unresolvedDependencies;
+    }
+
+    private static class LazyArtifactSource implements Factory<File> {
+        private final ArtifactResolver artifactResolver;
+        private final ModuleSource moduleSource;
+        private final ComponentArtifactMetaData artifact;
+
+        private LazyArtifactSource(ComponentArtifactMetaData artifact, ModuleSource moduleSource, ArtifactResolver artifactResolver) {
+            this.artifact = artifact;
+            this.artifactResolver = artifactResolver;
+            this.moduleSource = moduleSource;
+        }
+
+        public File create() {
+            DefaultBuildableArtifactResolveResult result = new DefaultBuildableArtifactResolveResult();
+            artifactResolver.resolveArtifact(artifact, moduleSource, result);
+            return result.getFile();
+        }
+    }
+
+    private static class LazyResolvedDependencySource implements Factory<ResolvedDependency> {
+        private final ResolvedConfigurationIdentifier owner;
+        private TransientConfigurationResultsBuilder builder;
+        private ResolvedContentsMapping mapping;
+
+        public LazyResolvedDependencySource(ResolvedConfigurationIdentifier owner, TransientConfigurationResultsBuilder builder, ResolvedContentsMapping mapping) {
+            this.owner = owner;
+            this.builder = builder;
+            this.mapping = mapping;
+        }
+
+        public ResolvedDependency create() {
+            return builder.load(mapping).getResolvedDependency(owner);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultTransientConfigurationResults.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultTransientConfigurationResults.java
new file mode 100644
index 0000000..0c8adad
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultTransientConfigurationResults.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.oldresult;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.internal.artifacts.DefaultResolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class DefaultTransientConfigurationResults implements TransientConfigurationResults {
+
+    final Map<ModuleDependency, ResolvedDependency> firstLevelDependencies = new LinkedHashMap<ModuleDependency, ResolvedDependency>();
+    ResolvedDependency root;
+    final Map<ResolvedConfigurationIdentifier, DefaultResolvedDependency> allDependencies = new HashMap<ResolvedConfigurationIdentifier, DefaultResolvedDependency>();
+
+    public Map<ModuleDependency, ResolvedDependency> getFirstLevelDependencies() {
+        return firstLevelDependencies;
+    }
+
+    public ResolvedDependency getRoot() {
+        return root;
+    }
+
+    public ResolvedDependency getResolvedDependency(ResolvedConfigurationIdentifier id) {
+        return allDependencies.get(id);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java
new file mode 100644
index 0000000..69c2473
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.UnresolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+
+import java.util.Set;
+
+//builds old model of resolved dependency graph based on the result events
+public interface ResolvedConfigurationBuilder {
+
+    void addFirstLevelDependency(ModuleDependency moduleDependency, ResolvedConfigurationIdentifier dependency);
+
+    void addUnresolvedDependency(UnresolvedDependency unresolvedDependency);
+
+    void addChild(ResolvedConfigurationIdentifier parent, ResolvedConfigurationIdentifier child);
+
+    void done(ResolvedConfigurationIdentifier root);
+
+    void addParentSpecificArtifacts(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, Set<ResolvedArtifact> artifacts);
+
+    void newResolvedDependency(ResolvedConfigurationIdentifier id);
+
+    ResolvedArtifact newArtifact(ResolvedConfigurationIdentifier owner, ComponentMetaData component, ComponentArtifactMetaData artifact, ArtifactResolver artifactResolver);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationResults.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationResults.java
new file mode 100644
index 0000000..34c0cb0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationResults.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.oldresult;
+
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.UnresolvedDependency;
+
+import java.util.Set;
+
+public interface ResolvedConfigurationResults {
+    boolean hasError();
+
+    Set<UnresolvedDependency> getUnresolvedDependencies();
+
+    Set<ResolvedArtifact> getArtifacts();
+
+    TransientConfigurationResults more();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java
new file mode 100644
index 0000000..7fb9266
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.oldresult;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+
+public interface ResolvedContentsMapping {
+
+    ResolvedArtifact getArtifact(long id);
+
+    ModuleDependency getModuleDependency(ResolvedConfigurationIdentifier id);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResults.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResults.java
new file mode 100644
index 0000000..634f969
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResults.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.oldresult;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+
+import java.util.Map;
+
+public interface TransientConfigurationResults {
+
+    Map<ModuleDependency, ResolvedDependency> getFirstLevelDependencies();
+
+    ResolvedDependency getRoot();
+
+    ResolvedDependency getResolvedDependency(ResolvedConfigurationIdentifier id);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java
new file mode 100644
index 0000000..b1f84d0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.oldresult;
+
+import org.gradle.api.internal.artifacts.DefaultResolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifierSerializer;
+import org.gradle.api.internal.cache.BinaryStore;
+import org.gradle.api.internal.cache.Store;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.util.Clock;
+
+import java.io.IOException;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
+
+//TODO SF unit coverage
+public class TransientConfigurationResultsBuilder {
+
+    private final static Logger LOG = Logging.getLogger(TransientConfigurationResultsBuilder.class);
+
+    private static final byte NEW_DEP = 1;
+    private static final byte ROOT = 2;
+    private static final byte FIRST_LVL = 3;
+    private static final byte PARENT_CHILD = 4;
+    private static final byte PARENT_ARTIFACT = 5;
+
+    private final Object lock = new Object();
+
+    private BinaryStore binaryStore;
+    private Store<TransientConfigurationResults> cache;
+    private final ResolvedConfigurationIdentifierSerializer resolvedConfigurationIdentifierSerializer = new ResolvedConfigurationIdentifierSerializer();
+    private BinaryStore.BinaryData binaryData;
+
+    public TransientConfigurationResultsBuilder(BinaryStore binaryStore, Store<TransientConfigurationResults> cache) {
+        this.binaryStore = binaryStore;
+        this.cache = cache;
+    }
+
+    private void writeId(final byte type, final ResolvedConfigurationIdentifier... ids) {
+        binaryStore.write(new BinaryStore.WriteAction() {
+            public void write(Encoder encoder) throws IOException {
+                encoder.writeByte(type);
+                for (ResolvedConfigurationIdentifier id : ids) {
+                    resolvedConfigurationIdentifierSerializer.write(encoder, id);
+                }
+            }
+        });
+    }
+
+    public void resolvedDependency(ResolvedConfigurationIdentifier id) {
+        writeId(NEW_DEP, id);
+    }
+
+    public void done(ResolvedConfigurationIdentifier id) {
+        writeId(ROOT, id);
+        LOG.debug("Flushing resolved configuration data in {}. Wrote root {}.", binaryStore, id);
+        binaryData = binaryStore.done();
+    }
+
+    public void firstLevelDependency(ResolvedConfigurationIdentifier id) {
+        writeId(FIRST_LVL, id);
+    }
+
+    public void parentChildMapping(ResolvedConfigurationIdentifier parent, ResolvedConfigurationIdentifier child) {
+        writeId(PARENT_CHILD, parent, child);
+    }
+
+    public void parentSpecificArtifact(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, final long artifactId) {
+        writeId(PARENT_ARTIFACT, child, parent);
+        binaryStore.write(new BinaryStore.WriteAction() {
+            public void write(Encoder encoder) throws IOException {
+                encoder.writeLong(artifactId);
+            }
+        });
+    }
+
+    public TransientConfigurationResults load(final ResolvedContentsMapping mapping) {
+        synchronized (lock) {
+            return cache.load(new Factory<TransientConfigurationResults>() {
+                public TransientConfigurationResults create() {
+                    try {
+                        return binaryData.read(new BinaryStore.ReadAction<TransientConfigurationResults>() {
+                            public TransientConfigurationResults read(Decoder decoder) throws IOException {
+                                return deserialize(decoder, mapping);
+                            }
+                        });
+                    } finally {
+                        try {
+                            binaryData.close();
+                        } catch (IOException e) {
+                            throw throwAsUncheckedException(e);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    private TransientConfigurationResults deserialize(Decoder decoder, ResolvedContentsMapping mapping) {
+        Clock clock = new Clock();
+        DefaultTransientConfigurationResults results = new DefaultTransientConfigurationResults();
+        int valuesRead = 0;
+        byte type = -1;
+        try {
+            while (true) {
+                type = decoder.readByte();
+                ResolvedConfigurationIdentifier id;
+                valuesRead++;
+                switch (type) {
+                    case NEW_DEP:
+                        id = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        results.allDependencies.put(id, new DefaultResolvedDependency(id.getId(), id.getConfiguration()));
+                        break;
+                    case ROOT:
+                        id = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        results.root = results.allDependencies.get(id);
+                        if (results.root == null) {
+                            throw new IllegalStateException(String.format("Unexpected root id %s. Seen ids: %s", id, results.allDependencies.keySet()));
+                        }
+                        //root should be the last
+                        LOG.debug("Loaded resolved configuration results ({}) from {}", clock.getTime(), binaryStore);
+                        return results;
+                    case FIRST_LVL:
+                        id = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        DefaultResolvedDependency dependency = results.allDependencies.get(id);
+                        if (dependency == null) {
+                            throw new IllegalStateException(String.format("Unexpected first level id %s. Seen ids: %s", id, results.allDependencies.keySet()));
+                        }
+                        results.firstLevelDependencies.put(mapping.getModuleDependency(id), dependency);
+                        break;
+                    case PARENT_CHILD:
+                        ResolvedConfigurationIdentifier parentId = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        ResolvedConfigurationIdentifier childId = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        DefaultResolvedDependency parent = results.allDependencies.get(parentId);
+                        DefaultResolvedDependency child = results.allDependencies.get(childId);
+                        if (parent == null) {
+                            throw new IllegalStateException(String.format("Unexpected parent dependency id %s. Seen ids: %s", parentId, results.allDependencies.keySet()));
+                        }
+                        if (child == null) {
+                            throw new IllegalStateException(String.format("Unexpected child dependency id %s. Seen ids: %s", childId, results.allDependencies.keySet()));
+                        }
+                        parent.addChild(child);
+                        break;
+                    case PARENT_ARTIFACT:
+                        ResolvedConfigurationIdentifier artifactParentId = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        ResolvedConfigurationIdentifier artifactChildId = resolvedConfigurationIdentifierSerializer.read(decoder);
+                        DefaultResolvedDependency artifactParent = results.allDependencies.get(artifactParentId);
+                        DefaultResolvedDependency artifactChild = results.allDependencies.get(artifactChildId);
+                        if (artifactParent == null) {
+                            throw new IllegalStateException(String.format("Unexpected parent dependency id %s. Seen ids: %s", artifactParentId, results.allDependencies.keySet()));
+                        }
+                        if (artifactChild == null) {
+                            throw new IllegalStateException(String.format("Unexpected child dependency id %s. Seen ids: %s", artifactChildId, results.allDependencies.keySet()));
+                        }
+                        artifactParent.addParentSpecificArtifacts(artifactChild, newHashSet(mapping.getArtifact(decoder.readLong())));
+                        break;
+                    default:
+                        throw new IOException("Unknown value type read from stream: " + type);
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Problems loading the resolved configuration. Read " + valuesRead + " values, last was: " + type, e);
+        }
+    }
+}
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
index 95d6eff..6dc5ecf 100644
--- 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
@@ -16,12 +16,13 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
 
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.result.DefaultResolvedComponentResult;
 import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult;
 import org.gradle.api.internal.artifacts.result.DefaultUnresolvedDependencyResult;
 
@@ -31,16 +32,13 @@ import java.util.Map;
 
 import static java.util.Arrays.asList;
 
-/**
- * by Szczepan Faber, created at: 10/1/12
- */
 public class CachingDependencyResultFactory {
 
     private final Map<List, DefaultUnresolvedDependencyResult> unresolvedDependencies = new HashMap<List, DefaultUnresolvedDependencyResult>();
     private final Map<List, DefaultResolvedDependencyResult> resolvedDependencies = new HashMap<List, DefaultResolvedDependencyResult>();
 
-    public UnresolvedDependencyResult createUnresolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from,
-                                                       ModuleVersionSelectionReason reason, ModuleVersionResolveException failure) {
+    public UnresolvedDependencyResult createUnresolvedDependency(ComponentSelector requested, ResolvedComponentResult from,
+                                                       ComponentSelectionReason reason, ModuleVersionResolveException failure) {
         List<Object> key = asList(requested, from);
         if (!unresolvedDependencies.containsKey(key)) {
             unresolvedDependencies.put(key, new DefaultUnresolvedDependencyResult(requested, reason, from, failure));
@@ -48,7 +46,7 @@ public class CachingDependencyResultFactory {
         return unresolvedDependencies.get(key);
     }
 
-    public ResolvedDependencyResult createResolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from, ResolvedModuleVersionResult selected) {
+    public ResolvedDependencyResult createResolvedDependency(ComponentSelector requested, ResolvedComponentResult from, DefaultResolvedComponentResult selected) {
         List<Object> key = asList(requested, from, selected);
         if (!resolvedDependencies.containsKey(key)) {
             resolvedDependencies.put(key, new DefaultResolvedDependencyResult(requested, selected, from));
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java
new file mode 100644
index 0000000..4976933
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ProjectComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.IOException;
+
+public class ComponentIdentifierSerializer implements Serializer<ComponentIdentifier> {
+    public ComponentIdentifier read(Decoder decoder) throws IOException {
+        byte id = decoder.readByte();
+
+        if(Implementation.BUILD.getId() == id) {
+            return new DefaultProjectComponentIdentifier(decoder.readString());
+        } else if(Implementation.MODULE.getId() == id) {
+            return new DefaultModuleComponentIdentifier(decoder.readString(), decoder.readString(), decoder.readString());
+        }
+
+        throw new IllegalArgumentException("Unable to find component identifier with id: " + id);
+    }
+
+    public void write(Encoder encoder, ComponentIdentifier value) throws IOException {
+        if(value == null) {
+            throw new IllegalArgumentException("Provided component identifier may not be null");
+        }
+
+        if(value instanceof DefaultModuleComponentIdentifier) {
+            ModuleComponentIdentifier moduleComponentIdentifier = (ModuleComponentIdentifier)value;
+            encoder.writeByte(Implementation.MODULE.getId());
+            encoder.writeString(moduleComponentIdentifier.getGroup());
+            encoder.writeString(moduleComponentIdentifier.getModule());
+            encoder.writeString(moduleComponentIdentifier.getVersion());
+        } else if(value instanceof DefaultProjectComponentIdentifier) {
+            ProjectComponentIdentifier projectComponentIdentifier = (ProjectComponentIdentifier)value;
+            encoder.writeByte(Implementation.BUILD.getId());
+            encoder.writeString(projectComponentIdentifier.getProjectPath());
+        } else {
+            throw new IllegalArgumentException("Unsupported component identifier class: " + value.getClass());
+        }
+    }
+
+    private static enum Implementation {
+        MODULE((byte) 1), BUILD((byte) 2);
+
+        private final byte id;
+
+        private Implementation(byte id) {
+            this.id = id;
+        }
+
+        private byte getId() {
+            return id;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasonSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasonSerializer.java
new file mode 100644
index 0000000..d041655
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasonSerializer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.IOException;
+
+import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.*;
+
+public class ComponentSelectionReasonSerializer implements Serializer<ComponentSelectionReason> {
+
+    private static final BiMap<Byte, ComponentSelectionReason> REASONS = HashBiMap.create(6);
+
+    static {
+        REASONS.put((byte) 1, REQUESTED);
+        REASONS.put((byte) 2, ROOT);
+        REASONS.put((byte) 3, FORCED);
+        REASONS.put((byte) 4, CONFLICT_RESOLUTION);
+        REASONS.put((byte) 5, SELECTED_BY_RULE);
+        REASONS.put((byte) 6, CONFLICT_RESOLUTION_BY_RULE);
+    }
+
+    public ComponentSelectionReason read(Decoder decoder) throws IOException {
+        byte id = decoder.readByte();
+        ComponentSelectionReason out = REASONS.get(id);
+        if (out == null) {
+            throw new IllegalArgumentException("Unable to find selection reason with id: " + id);
+        }
+        return out;
+    }
+
+    public void write(Encoder encoder, ComponentSelectionReason value) throws IOException {
+        Byte id = REASONS.inverse().get(value);
+        if (id == null) {
+            throw new IllegalArgumentException("Unknown selection reason: " + value);
+        }
+        encoder.writeByte(id);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java
new file mode 100644
index 0000000..0846947
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ProjectComponentSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentSelector;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.IOException;
+
+public class ComponentSelectorSerializer implements Serializer<ComponentSelector> {
+    public ComponentSelector read(Decoder decoder) throws IOException {
+        byte id = decoder.readByte();
+
+        if(Implementation.BUILD.getId() == id) {
+            return new DefaultProjectComponentSelector(decoder.readString());
+        } else if(Implementation.MODULE.getId() == id) {
+            return new DefaultModuleComponentSelector(decoder.readString(), decoder.readString(), decoder.readString());
+        }
+
+        throw new IllegalArgumentException("Unable to find component selector with id: " + id);
+    }
+
+    public void write(Encoder encoder, ComponentSelector value) throws IOException {
+        if(value == null) {
+            throw new IllegalArgumentException("Provided component selector may not be null");
+        }
+
+        if(value instanceof DefaultModuleComponentSelector) {
+            ModuleComponentSelector moduleComponentSelector = (ModuleComponentSelector)value;
+            encoder.writeByte(Implementation.MODULE.getId());
+            encoder.writeString(moduleComponentSelector.getGroup());
+            encoder.writeString(moduleComponentSelector.getModule());
+            encoder.writeString(moduleComponentSelector.getVersion());
+        } else if(value instanceof DefaultProjectComponentSelector) {
+            ProjectComponentSelector projectComponentSelector = (ProjectComponentSelector)value;
+            encoder.writeByte(Implementation.BUILD.getId());
+            encoder.writeString(projectComponentSelector.getProjectPath());
+        } else {
+            throw new IllegalArgumentException("Unsupported component selector class: " + value.getClass());
+        }
+    }
+
+    private static enum Implementation {
+        MODULE((byte) 1), BUILD((byte) 2);
+
+        private final byte id;
+
+        private Implementation(byte id) {
+            this.id = id;
+        }
+
+        private byte getId() {
+            return id;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultInternalDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultInternalDependencyResult.java
new file mode 100644
index 0000000..e87b0d4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultInternalDependencyResult.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+
+public class DefaultInternalDependencyResult implements InternalDependencyResult {
+
+    private final ComponentSelector requested;
+    private final ModuleVersionIdentifier selected;
+    private final ComponentSelectionReason reason;
+    private ModuleVersionResolveException failure;
+
+    public DefaultInternalDependencyResult(ComponentSelector requested,
+                                           ModuleVersionIdentifier selected,
+                                           ComponentSelectionReason reason,
+                                           ModuleVersionResolveException failure) {
+        assert requested != null;
+        assert failure != null || selected != null;
+
+        this.requested = requested;
+        this.reason = reason;
+        this.selected = selected;
+        this.failure = failure;
+    }
+
+    public ComponentSelector getRequested() {
+        return requested;
+    }
+
+    public ModuleVersionIdentifier getSelected() {
+        return selected;
+    }
+
+    public ComponentSelectionReason getReason() {
+        return reason;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        return failure;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultModuleVersionSelection.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultModuleVersionSelection.java
new file mode 100644
index 0000000..5e54b98
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultModuleVersionSelection.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+
+class DefaultModuleVersionSelection implements ModuleVersionSelection {
+    private ModuleVersionIdentifier id;
+    private ComponentSelectionReason reason;
+    private ComponentIdentifier componentIdentifier;
+
+    public DefaultModuleVersionSelection(ModuleVersionIdentifier id, ComponentSelectionReason reason, ComponentIdentifier componentIdentifier) {
+        this.id = id;
+        this.reason = reason;
+        this.componentIdentifier = componentIdentifier;
+    }
+
+    public ModuleVersionIdentifier getSelectedId() {
+        return id;
+    }
+
+    public ComponentSelectionReason getSelectionReason() {
+        return reason;
+    }
+
+    public ComponentIdentifier getComponentId() {
+        return componentIdentifier;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java
new file mode 100644
index 0000000..291404c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.*;
+import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
+import org.gradle.api.internal.artifacts.result.DefaultResolvedComponentResult;
+import org.gradle.internal.Factory;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class DefaultResolutionResultBuilder implements ResolutionResultBuilder {
+
+    private DefaultResolvedComponentResult rootModule;
+
+    private Map<ModuleVersionIdentifier, DefaultResolvedComponentResult> modules
+            = new LinkedHashMap<ModuleVersionIdentifier, DefaultResolvedComponentResult>();
+
+    CachingDependencyResultFactory dependencyResultFactory = new CachingDependencyResultFactory();
+
+    public DefaultResolutionResultBuilder start(ModuleVersionIdentifier root, ComponentIdentifier componentIdentifier) {
+        rootModule = createOrGet(root, VersionSelectionReasons.ROOT, componentIdentifier);
+        return this;
+    }
+
+    public ResolutionResult complete() {
+        return new DefaultResolutionResult(new RootFactory(rootModule));
+    }
+
+    public void resolvedModuleVersion(ModuleVersionSelection moduleVersion) {
+        createOrGet(moduleVersion.getSelectedId(), moduleVersion.getSelectionReason(), moduleVersion.getComponentId());
+    }
+
+    public void resolvedConfiguration(ModuleVersionIdentifier id, Collection<? extends InternalDependencyResult> dependencies) {
+        for (InternalDependencyResult d : dependencies) {
+            DefaultResolvedComponentResult from = modules.get(id);
+            DependencyResult dependency;
+            if (d.getFailure() != null) {
+                dependency = dependencyResultFactory.createUnresolvedDependency(d.getRequested(), from, d.getReason(), d.getFailure());
+            } else {
+                DefaultResolvedComponentResult selected = modules.get(d.getSelected());
+                dependency = dependencyResultFactory.createResolvedDependency(d.getRequested(), from, selected);
+                selected.addDependent((ResolvedDependencyResult) dependency);
+            }
+            from.addDependency(dependency);
+        }
+    }
+
+    private DefaultResolvedComponentResult createOrGet(ModuleVersionIdentifier id, ComponentSelectionReason selectionReason, ComponentIdentifier componentId) {
+        if (!modules.containsKey(id)) {
+            modules.put(id, new DefaultResolvedComponentResult(id, selectionReason, componentId));
+        }
+        return modules.get(id);
+    }
+
+    private static class RootFactory implements Factory<ResolvedComponentResult> {
+        private DefaultResolvedComponentResult rootModule;
+
+        public RootFactory(DefaultResolvedComponentResult rootModule) {
+            this.rootModule = rootModule;
+        }
+
+        public ResolvedComponentResult create() {
+            return rootModule;
+        }
+    }
+}
\ 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
index 13250d2..8b4c4c4 100644
--- 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
@@ -17,22 +17,24 @@
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
 
 import org.gradle.api.Nullable;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 
-/**
- * by Szczepan Faber, created at: 8/24/12
- */
 public interface InternalDependencyResult {
 
-    ModuleVersionSelector getRequested();
+    ComponentSelector getRequested();
 
     @Nullable
     ModuleVersionResolveException getFailure();
 
     @Nullable
-    ModuleVersionSelection getSelected();
+    ModuleVersionIdentifier getSelected();
 
-    ModuleVersionSelectionReason getReason();
+    /**
+     * Not null only when failure is not null.
+     */
+    @Nullable
+    ComponentSelectionReason getReason();
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResultSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResultSerializer.java
new file mode 100644
index 0000000..826def5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResultSerializer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class InternalDependencyResultSerializer {
+    private final static byte SUCCESSFUL = 0;
+    private final static byte FAILED = 1;
+    private final ComponentSelectorSerializer componentSelectorSerializer = new ComponentSelectorSerializer();
+    private final ComponentSelectionReasonSerializer componentSelectionReasonSerializer = new ComponentSelectionReasonSerializer();
+    private final ModuleVersionIdentifierSerializer moduleVersionIdentifierSerializer = new ModuleVersionIdentifierSerializer();
+
+    public InternalDependencyResult read(Decoder decoder, Map<ComponentSelector, ModuleVersionResolveException> failures) throws IOException {
+        ComponentSelector requested = componentSelectorSerializer.read(decoder);
+        byte resultByte = decoder.readByte();
+        if (resultByte == SUCCESSFUL) {
+            ModuleVersionIdentifier selected = moduleVersionIdentifierSerializer.read(decoder);
+            return new DefaultInternalDependencyResult(requested, selected, null, null);
+        } else if (resultByte == FAILED) {
+            ComponentSelectionReason reason = componentSelectionReasonSerializer.read(decoder);
+            ModuleVersionResolveException failure = failures.get(requested);
+            return new DefaultInternalDependencyResult(requested, null, reason, failure);
+        } else {
+            throw new IllegalArgumentException("Unknown result byte: " + resultByte);
+        }
+    }
+
+    public void write(Encoder encoder, InternalDependencyResult value) throws IOException {
+        componentSelectorSerializer.write(encoder, value.getRequested());
+        if (value.getFailure() == null) {
+            encoder.writeByte(SUCCESSFUL);
+            moduleVersionIdentifierSerializer.write(encoder, value.getSelected());
+        } else {
+            encoder.writeByte(FAILED);
+            componentSelectionReasonSerializer.write(encoder, value.getReason());
+        }
+    }
+}
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
index bf702f4..63845b1 100644
--- 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
@@ -17,14 +17,14 @@
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
-/**
-* by Szczepan Faber, created at: 8/31/12
-*/
 public interface ModuleVersionSelection {
 
     ModuleVersionIdentifier getSelectedId();
 
-    ModuleVersionSelectionReason getSelectionReason();
+    ComponentSelectionReason getSelectionReason();
+
+    ComponentIdentifier getComponentId();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelectionSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelectionSerializer.java
new file mode 100644
index 0000000..26a4b23
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelectionSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.IOException;
+
+public class ModuleVersionSelectionSerializer implements Serializer<ModuleVersionSelection> {
+
+    private final ModuleVersionIdentifierSerializer idSerializer = new ModuleVersionIdentifierSerializer();
+    private final ComponentSelectionReasonSerializer reasonSerializer = new ComponentSelectionReasonSerializer();
+    private final ComponentIdentifierSerializer componentIdSerializer = new ComponentIdentifierSerializer();
+
+    public ModuleVersionSelection read(Decoder decoder) throws IOException {
+        ModuleVersionIdentifier id = idSerializer.read(decoder);
+        ComponentSelectionReason reason = reasonSerializer.read(decoder);
+        ComponentIdentifier componentId = componentIdSerializer.read(decoder);
+        return new DefaultModuleVersionSelection(id, reason, componentId);
+    }
+
+    public void write(Encoder encoder, ModuleVersionSelection value) throws IOException {
+        idSerializer.write(encoder, value.getSelectedId());
+        reasonSerializer.write(encoder, value.getSelectionReason());
+        componentIdSerializer.write(encoder, value.getComponentId());
+    }
+}
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
index 2323616..34046b7 100644
--- 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
@@ -17,60 +17,15 @@
 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 org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ResolutionResult;
 
 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);
-            DependencyResult dependency;
-            if (d.getFailure() != null) {
-                dependency = dependencyResultFactory.createUnresolvedDependency(d.getRequested(), from, d.getReason(), d.getFailure());
-            } else {
-                DefaultResolvedModuleVersionResult selected = modules.get(d.getSelected().getSelectedId());
-                dependency = dependencyResultFactory.createResolvedDependency(d.getRequested(), from, selected);
-                selected.addDependent((ResolvedDependencyResult) dependency);
-            }
-            from.addDependency(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
+//builds new dependency graph model based on result events
+public interface ResolutionResultBuilder {
+    ResolutionResultBuilder start(ModuleVersionIdentifier root, ComponentIdentifier componentIdentifier);
+    void resolvedModuleVersion(ModuleVersionSelection moduleVersion);
+    void resolvedConfiguration(ModuleVersionIdentifier id, Collection<? extends InternalDependencyResult> dependencies);
+    ResolutionResult complete();
+}
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
deleted file mode 100644
index 1ac0e97..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolvedConfigurationListener.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.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/StreamingResolutionResultBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/StreamingResolutionResultBuilder.java
new file mode 100644
index 0000000..91f85f1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/StreamingResolutionResultBuilder.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
+import org.gradle.api.internal.cache.BinaryStore;
+import org.gradle.api.internal.cache.Store;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.util.Clock;
+
+import java.io.IOException;
+import java.util.*;
+
+import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
+
+public class StreamingResolutionResultBuilder implements ResolutionResultBuilder {
+
+    private final static byte ROOT = 1;
+    private final static byte MODULE = 2;
+    private final static byte DEPENDENCY = 3;
+    private final static byte DONE = 4;
+
+    private final Map<ComponentSelector, ModuleVersionResolveException> failures = new HashMap<ComponentSelector, ModuleVersionResolveException>();
+    private final BinaryStore store;
+    private final ModuleVersionIdentifierSerializer moduleVersionIdentifierSerializer = new ModuleVersionIdentifierSerializer();
+    private final ModuleVersionSelectionSerializer moduleVersionSelectionSerializer = new ModuleVersionSelectionSerializer();
+    private final Store<ResolvedComponentResult> cache;
+    private final InternalDependencyResultSerializer internalDependencyResultSerializer = new InternalDependencyResultSerializer();
+    private final ComponentIdentifierSerializer componentIdentifierSerializer = new ComponentIdentifierSerializer();
+
+    public StreamingResolutionResultBuilder(BinaryStore store, Store<ResolvedComponentResult> cache) {
+        this.store = store;
+        this.cache = cache;
+    }
+
+    public ResolutionResult complete() {
+        store.write(new BinaryStore.WriteAction() {
+            public void write(Encoder encoder) throws IOException {
+                encoder.writeByte(DONE);
+            }
+        });
+        BinaryStore.BinaryData data = store.done();
+        RootFactory rootSource = new RootFactory(data, failures, cache);
+        return new DefaultResolutionResult(rootSource);
+    }
+
+    public ResolutionResultBuilder start(final ModuleVersionIdentifier root, final ComponentIdentifier componentIdentifier) {
+        store.write(new BinaryStore.WriteAction() {
+            public void write(Encoder encoder) throws IOException {
+                encoder.writeByte(ROOT);
+                moduleVersionIdentifierSerializer.write(encoder, root);
+                componentIdentifierSerializer.write(encoder, componentIdentifier);
+            }
+        });
+        return this;
+    }
+
+    Set<ModuleVersionIdentifier> visitedModules = new HashSet<ModuleVersionIdentifier>();
+
+    public void resolvedModuleVersion(final ModuleVersionSelection moduleVersion) {
+        if (visitedModules.add(moduleVersion.getSelectedId())) {
+            store.write(new BinaryStore.WriteAction() {
+                public void write(Encoder encoder) throws IOException {
+                    encoder.writeByte(MODULE);
+                    moduleVersionSelectionSerializer.write(encoder, moduleVersion);
+                }
+            });
+        }
+    }
+
+    public void resolvedConfiguration(final ModuleVersionIdentifier from, final Collection<? extends InternalDependencyResult> dependencies) {
+        if (!dependencies.isEmpty()) {
+            store.write(new BinaryStore.WriteAction() {
+                public void write(Encoder encoder) throws IOException {
+                    encoder.writeByte(DEPENDENCY);
+                    moduleVersionIdentifierSerializer.write(encoder, from);
+                    encoder.writeSmallInt(dependencies.size());
+                    for (InternalDependencyResult dependency : dependencies) {
+                        internalDependencyResultSerializer.write(encoder, dependency);
+                        if (dependency.getFailure() != null) {
+                            //by keying the failures only be 'requested' we lose some precision
+                            //at edge case we'll lose info about a different exception if we have different failure for the same requested version
+                            failures.put(dependency.getRequested(), dependency.getFailure());
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    private static class RootFactory implements Factory<ResolvedComponentResult> {
+
+        private final static Logger LOG = Logging.getLogger(RootFactory.class);
+        private final ModuleVersionSelectionSerializer moduleVersionSelectionSerializer = new ModuleVersionSelectionSerializer();
+
+        private final BinaryStore.BinaryData data;
+        private final Map<ComponentSelector, ModuleVersionResolveException> failures;
+        private final Store<ResolvedComponentResult> cache;
+        private final Object lock = new Object();
+        private final ModuleVersionIdentifierSerializer moduleVersionIdentifierSerializer = new ModuleVersionIdentifierSerializer();
+        private final InternalDependencyResultSerializer internalDependencyResultSerializer = new InternalDependencyResultSerializer();
+        private final ComponentIdentifierSerializer componentIdentifierSerializer = new ComponentIdentifierSerializer();
+
+        public RootFactory(BinaryStore.BinaryData data, Map<ComponentSelector, ModuleVersionResolveException> failures,
+                           Store<ResolvedComponentResult> cache) {
+            this.data = data;
+            this.failures = failures;
+            this.cache = cache;
+        }
+
+        public ResolvedComponentResult create() {
+            synchronized (lock) {
+                return cache.load(new Factory<ResolvedComponentResult>() {
+                    public ResolvedComponentResult create() {
+                        try {
+                            return data.read(new BinaryStore.ReadAction<ResolvedComponentResult>() {
+                                public ResolvedComponentResult read(Decoder decoder) throws IOException {
+                                    return deserialize(decoder);
+                                }
+                            });
+                        } finally {
+                            try {
+                                data.close();
+                            } catch (IOException e) {
+                                throw throwAsUncheckedException(e);
+                            }
+                        }
+                    }
+                });
+            }
+        }
+
+        private ResolvedComponentResult deserialize(Decoder decoder) {
+            int valuesRead = 0;
+            byte type = -1;
+            Clock clock = new Clock();
+            try {
+                DefaultResolutionResultBuilder builder = new DefaultResolutionResultBuilder();
+                while (true) {
+                    type = decoder.readByte();
+                    valuesRead++;
+                    switch (type) {
+                        case ROOT:
+                            ModuleVersionIdentifier id = moduleVersionIdentifierSerializer.read(decoder);
+                            ComponentIdentifier componentIdentifier = componentIdentifierSerializer.read(decoder);
+                            builder.start(id, componentIdentifier);
+                            break;
+                        case MODULE:
+                            ModuleVersionSelection sel = moduleVersionSelectionSerializer.read(decoder);
+                            builder.resolvedModuleVersion(sel);
+                            break;
+                        case DEPENDENCY:
+                            id = moduleVersionIdentifierSerializer.read(decoder);
+                            int size = decoder.readSmallInt();
+                            List<InternalDependencyResult> deps = new LinkedList<InternalDependencyResult>();
+                            for (int i = 0; i < size; i++) {
+                                deps.add(internalDependencyResultSerializer.read(decoder, failures));
+                            }
+                            builder.resolvedConfiguration(id, deps);
+                            break;
+                        case DONE:
+                            ResolvedComponentResult root = builder.complete().getRoot();
+                            LOG.debug("Loaded resolution results ({}) from {}", clock.getTime(), data);
+                            return root;
+                        default:
+                            throw new IOException("Unknown value type read from stream: " + type);
+                    }
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Problems loading the resolution results (" + clock.getTime() + "). "
+                        + "Read " + valuesRead + " values, last was: " + type, e);
+            }
+        }
+    }
+}
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
index 9b9e45a..96cf754 100644
--- 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
@@ -16,20 +16,17 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
 
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
-/**
- * by Szczepan Faber, created at: 10/1/12
- */
 public class VersionSelectionReasons {
-    public static final ModuleVersionSelectionReason REQUESTED = new DefaultModuleVersionSelectionReason(false, false, false, "requested");
-    public static final ModuleVersionSelectionReason ROOT = new DefaultModuleVersionSelectionReason(false, false, false, "root");
-    public static final ModuleVersionSelectionReason FORCED = new DefaultModuleVersionSelectionReason(true, false, false, "forced");
-    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION = new DefaultModuleVersionSelectionReason(false, true, false, "conflict resolution");
-    public static final ModuleVersionSelectionReason SELECTED_BY_RULE = new DefaultModuleVersionSelectionReason(false, false, true, "selected by rule");
-    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION_BY_RULE = new DefaultModuleVersionSelectionReason(false, true, true, "selected by rule and conflict resolution");
+    public static final ComponentSelectionReason REQUESTED = new DefaultComponentSelectionReason(false, false, false, true, "requested");
+    public static final ComponentSelectionReason ROOT = new DefaultComponentSelectionReason(false, false, false, true, "root");
+    public static final ComponentSelectionReason FORCED = new DefaultComponentSelectionReason(true, false, false, false, "forced");
+    public static final ComponentSelectionReason CONFLICT_RESOLUTION = new DefaultComponentSelectionReason(false, true, false, false, "conflict resolution");
+    public static final ComponentSelectionReason SELECTED_BY_RULE = new DefaultComponentSelectionReason(false, false, true, false, "selected by rule");
+    public static final ComponentSelectionReason CONFLICT_RESOLUTION_BY_RULE = new DefaultComponentSelectionReason(false, true, true, false, "selected by rule and conflict resolution");
 
-    public static ModuleVersionSelectionReason withConflictResolution(ModuleVersionSelectionReason reason) {
+    public static ComponentSelectionReason withConflictResolution(ComponentSelectionReason reason) {
         if (reason.isConflictResolution()) {
             return reason;
         } else if (reason == SELECTED_BY_RULE) {
@@ -40,17 +37,19 @@ public class VersionSelectionReasons {
         throw new IllegalArgumentException("Cannot create conflict resolution selection reason for input: " + reason);
     }
 
-    private static class DefaultModuleVersionSelectionReason implements ModuleVersionSelectionReason {
+    private static class DefaultComponentSelectionReason implements ComponentSelectionReason {
 
         private final boolean forced;
         private final boolean conflictResolution;
         private final boolean selectedByRule;
+        private final boolean expected;
         private final String description;
 
-        private DefaultModuleVersionSelectionReason(boolean forced, boolean conflictResolution, boolean selectedByRule, String description) {
+        private DefaultComponentSelectionReason(boolean forced, boolean conflictResolution, boolean selectedByRule, boolean expected, String description) {
             this.forced = forced;
             this.conflictResolution = conflictResolution;
             this.selectedByRule = selectedByRule;
+            this.expected = expected;
             assert description != null;
             this.description = description;
         }
@@ -67,6 +66,10 @@ public class VersionSelectionReasons {
             return selectedByRule;
         }
 
+        public boolean isExpected() {
+            return expected;
+        }
+
         public String getDescription() {
             return description;
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/CachedStoreFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/CachedStoreFactory.java
new file mode 100644
index 0000000..931211b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/CachedStoreFactory.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.store;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.gradle.api.internal.cache.Store;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Factory;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.gradle.util.Clock.prettyTime;
+
+public class CachedStoreFactory<T> {
+
+    private static final Logger LOG = Logging.getLogger(CachedStoreFactory.class);
+
+    private final Cache<Object, T> cache;
+    private final Stats stats;
+    private String displayName;
+
+    public CachedStoreFactory(String displayName) {
+        this.displayName = displayName;
+        cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(10000, TimeUnit.MILLISECONDS).build();
+        stats = new Stats();
+    }
+
+    public Store<T> createCachedStore(final Object id) {
+        return new SimpleStore<T>(cache, id, stats);
+    }
+
+    public void close() {
+        LOG.debug(displayName + " cache closed. Cache reads: "
+                + stats.readsFromCache + ", disk reads: "
+                + stats.readsFromDisk + " (avg: " + prettyTime(stats.getDiskReadsAvgMs()) + ", total: " + prettyTime(stats.diskReadsTotalMs.get()) + ")");
+    }
+
+    private static class Stats {
+        private final AtomicLong diskReadsTotalMs = new AtomicLong();
+        private final AtomicLong readsFromCache = new AtomicLong();
+        private final AtomicLong readsFromDisk = new AtomicLong();
+
+        public void readFromDisk(long start) {
+            long duration = System.currentTimeMillis() - start;
+            readsFromDisk.incrementAndGet();
+            diskReadsTotalMs.addAndGet(duration);
+        }
+
+        public void readFromCache() {
+            readsFromCache.incrementAndGet();
+        }
+
+        public long getDiskReadsAvgMs() {
+            if (readsFromDisk.get() == 0) {
+                return 0;
+            }
+            return diskReadsTotalMs.get() / readsFromDisk.get();
+        }
+    }
+
+    private static class SimpleStore<T> implements Store<T> {
+        private Cache<Object, T> cache;
+        private final Object id;
+        private Stats stats;
+
+        public SimpleStore(Cache<Object, T> cache, Object id, Stats stats) {
+            this.cache = cache;
+            this.id = id;
+            this.stats = stats;
+        }
+
+        public T load(Factory<T> createIfNotPresent) {
+            T out = cache.getIfPresent(id);
+            if (out != null) {
+                stats.readFromCache();
+                return out;
+            }
+            long start = System.currentTimeMillis();
+            T value = createIfNotPresent.create();
+            stats.readFromDisk(start);
+            cache.put(id, value);
+            return value;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java
new file mode 100644
index 0000000..57df3b5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.store;
+
+import org.gradle.api.internal.cache.BinaryStore;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.io.RandomAccessFileInputStream;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedDecoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedEncoder;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.RandomAccessFile;
+
+import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
+
+class DefaultBinaryStore implements BinaryStore {
+    private File file;
+    private KryoBackedEncoder encoder;
+    private int offset = -1;
+
+    public DefaultBinaryStore(File file) {
+        this.file = file;
+    }
+
+    public void write(WriteAction write) {
+        if (encoder == null) {
+            try {
+                encoder = new KryoBackedEncoder(new FileOutputStream(file));
+            } catch (FileNotFoundException e) {
+                throw throwAsUncheckedException(e);
+            }
+        }
+        if (offset == -1) {
+            offset = encoder.getWritePosition();
+            if (offset == Integer.MAX_VALUE) {
+                throw new IllegalStateException("Unable to write to binary store. "
+                        + "The bytes offset has reached a point where using it is unsafe. Please report this error.");
+            }
+        }
+        try {
+            write.write(encoder);
+        } catch (Exception e) {
+            throw new RuntimeException("Problems writing to " + diagnose(), e);
+        }
+    }
+
+    private String diagnose() {
+        return toString() + " (exist: " + file.exists() + ")";
+    }
+
+    public String toString() {
+        return "Binary store in " + file;
+    }
+
+    public BinaryData done() {
+        try {
+            if (encoder != null) {
+                encoder.flush();
+            }
+            return new SimpleBinaryData(file, offset, diagnose());
+        } finally {
+            offset = -1;
+        }
+    }
+
+    public void close() {
+        try {
+            if (encoder != null) {
+                encoder.close();
+            }
+        } finally {
+            file.delete();
+            encoder = null;
+            file = null;
+        }
+    }
+
+    File getFile() {
+        return file;
+    }
+
+    long getSize() {
+        return file.length();
+    }
+
+    private static class SimpleBinaryData implements BinaryStore.BinaryData {
+        private final int offset;
+        private final File inputFile;
+        private final String sourceDescription;
+
+        private Decoder decoder;
+        private CompositeStoppable resources;
+
+        public SimpleBinaryData(File inputFile, int offset, String sourceDescription) {
+            this.inputFile = inputFile;
+            this.offset = offset;
+            this.sourceDescription = sourceDescription;
+        }
+
+        public <T> T read(BinaryStore.ReadAction<T> readAction) {
+            try {
+                if (decoder == null) {
+                    RandomAccessFile randomAccess = new RandomAccessFile(inputFile, "r");
+                    randomAccess.seek(offset);
+                    decoder = new KryoBackedDecoder(new RandomAccessFileInputStream(randomAccess));
+                    resources = new CompositeStoppable().add(randomAccess, decoder);
+                }
+                return readAction.read(decoder);
+            } catch (Exception e) {
+                throw new RuntimeException("Problems reading data from " + sourceDescription, e);
+            }
+        }
+
+        public void close() {
+            try {
+                if (resources != null) {
+                    resources.stop();
+                }
+            } catch (Exception e) {
+                throw new RuntimeException("Problems cleaning resources of " + sourceDescription, e);
+            } finally {
+                decoder = null;
+                resources = null;
+            }
+        }
+
+        public String toString() {
+            return sourceDescription;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/ResolutionResultsStoreFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/ResolutionResultsStoreFactory.java
new file mode 100644
index 0000000..172489b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/ResolutionResultsStoreFactory.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.store;
+
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResults;
+import org.gradle.api.internal.cache.Store;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.util.Clock;
+
+import java.io.Closeable;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ResolutionResultsStoreFactory implements Closeable {
+    private final static Logger LOG = Logging.getLogger(ResolutionResultsStoreFactory.class);
+    private static final int DEFAULT_MAX_SIZE = 2000000000; //2 gigs
+
+    private final TemporaryFileProvider temp;
+    private int maxSize;
+
+    private CachedStoreFactory oldModelCache;
+    private CachedStoreFactory newModelCache;
+
+    private int storeSetBaseId;
+
+    public ResolutionResultsStoreFactory(TemporaryFileProvider temp) {
+        this(temp, DEFAULT_MAX_SIZE);
+    }
+
+    /**
+     * @param temp
+     * @param maxSize - indicates the approx. maximum size of the binary store that will trigger rolling of the file
+     */
+    ResolutionResultsStoreFactory(TemporaryFileProvider temp, int maxSize) {
+        this.temp = temp;
+        this.maxSize = maxSize;
+    }
+
+    private final Map<String, DefaultBinaryStore> stores = new HashMap<String, DefaultBinaryStore>();
+    private final CompositeStoppable cleanUpLater = new CompositeStoppable();
+
+    private DefaultBinaryStore createBinaryStore(String storeKey) {
+        DefaultBinaryStore store = stores.get(storeKey);
+        if (store == null || isFull(store)) {
+            File storeFile = temp.createTemporaryFile("gradle", ".bin");
+            storeFile.deleteOnExit();
+            store = new DefaultBinaryStore(storeFile);
+            stores.put(storeKey, store);
+            cleanUpLater.add(store);
+        }
+        return store;
+    }
+
+    public StoreSet createStoreSet() {
+        return new StoreSet() {
+            int storeSetId = storeSetBaseId++;
+            int binaryStoreId;
+            public DefaultBinaryStore nextBinaryStore() {
+                //one binary store per id+threadId
+                String storeKey = Thread.currentThread().getId() + "-" + binaryStoreId++;
+                return createBinaryStore(storeKey);
+            }
+
+            public Store<ResolvedComponentResult> oldModelStore() {
+                if (oldModelCache == null) {
+                    oldModelCache = new CachedStoreFactory("Resolution result");
+                    cleanUpLater.add(oldModelCache);
+                }
+                return oldModelCache.createCachedStore(storeSetId);
+            }
+
+            public Store<TransientConfigurationResults> newModelStore() {
+                if (newModelCache == null) {
+                    newModelCache = new CachedStoreFactory("Resolved configuration");
+                    cleanUpLater.add(newModelCache);
+                }
+                return newModelCache.createCachedStore(storeSetId);
+            }
+        };
+    }
+
+    //offset based implementation is only safe up to certain figure
+    //because of the int max value
+    //for large streams/files (huge builds), we need to roll the file
+    //otherwise the stream.size() returns max integer and the offset is no longer correct
+    private boolean isFull(DefaultBinaryStore store) {
+        return store.getSize() > maxSize;
+    }
+
+    public void close() {
+        try {
+            Clock clock = new Clock();
+            cleanUpLater.stop();
+            LOG.debug("Deleted {} resolution results binary files in {}", stores.size(), clock.getTime());
+        } finally {
+            oldModelCache = null;
+            newModelCache = null;
+            stores.clear();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/StoreSet.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/StoreSet.java
new file mode 100644
index 0000000..bbe27de
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/StoreSet.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.store;
+
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResults;
+import org.gradle.api.internal.cache.BinaryStore;
+import org.gradle.api.internal.cache.Store;
+
+public interface StoreSet {
+    BinaryStore nextBinaryStore();
+
+    Store<ResolvedComponentResult> oldModelStore();
+
+    Store<TransientConfigurationResults> newModelStore();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/AbstractModuleDescriptorBackedMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/AbstractModuleDescriptorBackedMetaData.java
new file mode 100644
index 0000000..b4e208f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/AbstractModuleDescriptorBackedMetaData.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.*;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.util.CollectionUtils;
+
+import java.util.*;
+
+public abstract class AbstractModuleDescriptorBackedMetaData implements ComponentMetaData {
+    private static final List<String> DEFAULT_STATUS_SCHEME = Arrays.asList("integration", "milestone", "release");
+
+    private final ModuleVersionIdentifier moduleVersionIdentifier;
+    private final ModuleDescriptor moduleDescriptor;
+    private final ComponentIdentifier componentIdentifier;
+    private ModuleSource moduleSource;
+    private boolean changing;
+    private String status;
+    private List<String> statusScheme = DEFAULT_STATUS_SCHEME;
+    private List<DependencyMetaData> dependencies;
+    private Map<String, DefaultConfigurationMetaData> configurations = new HashMap<String, DefaultConfigurationMetaData>();
+
+    public AbstractModuleDescriptorBackedMetaData(ModuleVersionIdentifier moduleVersionIdentifier, ModuleDescriptor moduleDescriptor, ComponentIdentifier componentIdentifier) {
+        this.moduleVersionIdentifier = moduleVersionIdentifier;
+        this.moduleDescriptor = moduleDescriptor;
+        this.componentIdentifier = componentIdentifier;
+        status = moduleDescriptor.getStatus();
+    }
+
+    protected void copyTo(AbstractModuleDescriptorBackedMetaData copy) {
+        copy.dependencies = dependencies;
+        copy.changing = changing;
+        copy.status = status;
+        copy.statusScheme = statusScheme;
+        copy.moduleSource = moduleSource;
+    }
+
+    @Override
+    public String toString() {
+        return moduleVersionIdentifier.toString();
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return moduleVersionIdentifier;
+    }
+
+    public ModuleSource getSource() {
+        return moduleSource;
+    }
+
+    public void setModuleSource(ModuleSource moduleSource) {
+        this.moduleSource = moduleSource;
+    }
+
+    public ModuleDescriptor getDescriptor() {
+        return moduleDescriptor;
+    }
+
+    public boolean isChanging() {
+        return changing;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public List<String> getStatusScheme() {
+        return statusScheme;
+    }
+
+    public ComponentIdentifier getComponentId() {
+        return componentIdentifier;
+    }
+
+    public void setChanging(boolean changing) {
+        this.changing = changing;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void setStatusScheme(List<String> statusScheme) {
+        this.statusScheme = statusScheme;
+    }
+
+    public List<DependencyMetaData> getDependencies() {
+        if (dependencies == null) {
+            dependencies = new ArrayList<DependencyMetaData>();
+            for (final DependencyDescriptor dependencyDescriptor : moduleDescriptor.getDependencies()) {
+                dependencies.add(new DefaultDependencyMetaData(dependencyDescriptor));
+            }
+        }
+        return dependencies;
+    }
+
+    public void setDependencies(Iterable<? extends DependencyMetaData> dependencies) {
+        this.dependencies = CollectionUtils.toList(dependencies);
+        for (DefaultConfigurationMetaData configuration : configurations.values()) {
+            configuration.dependencies = null;
+        }
+    }
+
+    public DefaultConfigurationMetaData getConfiguration(final String name) {
+        DefaultConfigurationMetaData configuration = configurations.get(name);
+        if (configuration == null) {
+            Configuration descriptor = moduleDescriptor.getConfiguration(name);
+            if (descriptor == null) {
+                return null;
+            }
+            Set<String> hierarchy = new LinkedHashSet<String>();
+            hierarchy.add(name);
+            for (String parent : descriptor.getExtends()) {
+                hierarchy.addAll(getConfiguration(parent).hierarchy);
+            }
+            configuration = new DefaultConfigurationMetaData(name, descriptor, hierarchy);
+            configurations.put(name, configuration);
+        }
+        return configuration;
+    }
+
+    protected abstract Set<ComponentArtifactMetaData> getArtifactsForConfiguration(ConfigurationMetaData configuration);
+
+    private class DefaultConfigurationMetaData implements ConfigurationMetaData {
+        private final String name;
+        private final Configuration descriptor;
+        private final Set<String> hierarchy;
+        private List<DependencyMetaData> dependencies;
+        private Set<ComponentArtifactMetaData> artifacts;
+        private LinkedHashSet<ExcludeRule> excludeRules;
+
+        private DefaultConfigurationMetaData(String name, Configuration descriptor, Set<String> hierarchy) {
+            this.name = name;
+            this.descriptor = descriptor;
+            this.hierarchy = hierarchy;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s:%s", moduleVersionIdentifier, name);
+        }
+
+        public ComponentMetaData getComponent() {
+            return AbstractModuleDescriptorBackedMetaData.this;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Set<String> getHierarchy() {
+            return hierarchy;
+        }
+
+        public boolean isTransitive() {
+            return descriptor.isTransitive();
+        }
+
+        public List<DependencyMetaData> getDependencies() {
+            if (dependencies == null) {
+                dependencies = new ArrayList<DependencyMetaData>();
+                for (DependencyMetaData dependency : AbstractModuleDescriptorBackedMetaData.this.getDependencies()) {
+                    if (include(dependency)) {
+                        dependencies.add(dependency);
+                    }
+                }
+            }
+            return dependencies;
+        }
+
+        private boolean include(DependencyMetaData dependency) {
+            String[] moduleConfigurations = dependency.getDescriptor().getModuleConfigurations();
+            for (int i = 0; i < moduleConfigurations.length; i++) {
+                String moduleConfiguration = moduleConfigurations[i];
+                if (moduleConfiguration.equals("%") || hierarchy.contains(moduleConfiguration)) {
+                    return true;
+                }
+                if (moduleConfiguration.equals("*")) {
+                    boolean include = true;
+                    for (int j = i + 1; j < moduleConfigurations.length && moduleConfigurations[j].startsWith("!"); j++) {
+                        if (moduleConfigurations[j].substring(1).equals(getName())) {
+                            include = false;
+                            break;
+                        }
+                    }
+                    if (include) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        public Set<ExcludeRule> getExcludeRules() {
+            if (excludeRules == null) {
+                excludeRules = new LinkedHashSet<ExcludeRule>();
+                for (ExcludeRule excludeRule : moduleDescriptor.getAllExcludeRules()) {
+                    for (String config : excludeRule.getConfigurations()) {
+                        if (hierarchy.contains(config)) {
+                            excludeRules.add(excludeRule);
+                            break;
+                        }
+                    }
+                }
+            }
+            return excludeRules;
+        }
+
+        public Set<ComponentArtifactMetaData> getArtifacts() {
+            if (artifacts == null) {
+                artifacts = getArtifactsForConfiguration(this);
+            }
+            return artifacts;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/BuildableModuleVersionPublishMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/BuildableModuleVersionPublishMetaData.java
new file mode 100644
index 0000000..911aa07
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/BuildableModuleVersionPublishMetaData.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+import java.io.File;
+
+public interface BuildableModuleVersionPublishMetaData extends ModuleVersionPublishMetaData {
+    void addArtifact(ModuleVersionArtifactPublishMetaData artifact);
+
+    void addArtifact(Artifact artifact, File file);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentArtifactIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentArtifactIdentifier.java
new file mode 100644
index 0000000..d77e76e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentArtifactIdentifier.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+/**
+ * An immutable identifier for an artifact that belongs to some component instance.
+ */
+public interface ComponentArtifactIdentifier {
+    /**
+     * Returns the id of the component that this artifact belongs to.
+     */
+    ComponentIdentifier getComponentIdentifier();
+
+    /**
+     * Returns some human-consumable display name for this artifact.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentArtifactMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentArtifactMetaData.java
new file mode 100644
index 0000000..3a0f67a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentArtifactMetaData.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+/**
+ * Meta-data for an artifact that belongs to some component.
+ */
+public interface ComponentArtifactMetaData {
+    /**
+     * Returns the identifier for this artifact.
+     */
+    ComponentArtifactIdentifier getId();
+
+    /**
+     * Returns the identifier for the component that this artifact belongs to.
+     */
+    ComponentIdentifier getComponentId();
+
+    /**
+     * Returns this artifact as an Ivy artifact. This method is here to allow the artifact to be exposed in a backward-compatible way.
+     */
+    IvyArtifactName getName();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentMetaData.java
new file mode 100644
index 0000000..f5321c7
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ComponentMetaData.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The meta-data for a component instance that is required during dependency resolution.
+ */
+public interface ComponentMetaData {
+    /**
+     * Returns the identifier for this component.
+     */
+    ComponentIdentifier getComponentId();
+
+    /**
+     * Returns the module version identifier for this component. This is a legacy identifier and is here while we transition the meta-data away from ivy-like
+     * module versions to the more general component instances. Currently, the module version and component identifiers are used interchangeably, however, over
+     * time more things will use the component identifier. At some point, the module version identifier will become optional for a component.
+     */
+    ModuleVersionIdentifier getId();
+
+    /**
+     * Returns the source (eg location) for this component.
+     */
+    ModuleSource getSource();
+
+    /**
+     * Makes a copy of this meta-data with the given source.
+     */
+    ComponentMetaData withSource(ModuleSource source);
+
+    /**
+     * Returns this module version as an Ivy ModuleDescriptor. This method is here to allow us to migrate away from the Ivy types
+     * and will be removed.
+     */
+    ModuleDescriptor getDescriptor();
+
+    List<DependencyMetaData> getDependencies();
+
+    /**
+     * Locates the configuration with the given name, if any.
+     */
+    @Nullable
+    ConfigurationMetaData getConfiguration(String name);
+
+    /**
+     * Converts the given Ivy artifact to the corresponding artifact meta-data. This method is here to allow us to migrate away from the Ivy types and
+     * will be removed.
+     */
+    ComponentArtifactMetaData artifact(Artifact artifact);
+
+    /**
+     * Returns the known artifacts for this component. There may be additional component available that are not included in this set.
+     */
+    Set<? extends ComponentArtifactMetaData> getArtifacts();
+
+    boolean isChanging();
+
+    String getStatus();
+
+    List<String> getStatusScheme();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ConfigurationMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ConfigurationMetaData.java
new file mode 100644
index 0000000..da408e2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ConfigurationMetaData.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+
+import java.util.List;
+import java.util.Set;
+
+public interface ConfigurationMetaData {
+    /**
+     * The set of configurations that this configuration extends. Includes this configuration.
+     */
+    Set<String> getHierarchy();
+
+    String getName();
+
+    ComponentMetaData getComponent();
+
+    List<DependencyMetaData> getDependencies();
+
+    Set<ComponentArtifactMetaData> getArtifacts();
+
+    Set<ExcludeRule> getExcludeRules();
+
+    boolean isTransitive();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultDependencyMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultDependencyMetaData.java
new file mode 100644
index 0000000..c665773
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultDependencyMetaData.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector;
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentSelector;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ReflectiveDependencyDescriptorFactory;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultDependencyMetaData implements DependencyMetaData {
+    private final DependencyDescriptor dependencyDescriptor;
+    private final DefaultModuleVersionSelector requested;
+
+    public DefaultDependencyMetaData(DependencyDescriptor dependencyDescriptor) {
+        this.dependencyDescriptor = dependencyDescriptor;
+        ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
+        requested = new DefaultModuleVersionSelector(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
+    }
+
+    @Override
+    public String toString() {
+        return dependencyDescriptor.toString();
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public boolean isChanging() {
+        return dependencyDescriptor.isChanging();
+    }
+
+    public boolean isTransitive() {
+        return dependencyDescriptor.isTransitive();
+    }
+
+    public DependencyDescriptor getDescriptor() {
+        return dependencyDescriptor;
+    }
+
+    public Set<ComponentArtifactMetaData> getArtifacts(ConfigurationMetaData fromConfiguration, ConfigurationMetaData toConfiguration) {
+        String[] targetConfigurations = fromConfiguration.getHierarchy().toArray(new String[fromConfiguration.getHierarchy().size()]);
+        DependencyArtifactDescriptor[] dependencyArtifacts = dependencyDescriptor.getDependencyArtifacts(targetConfigurations);
+        if (dependencyArtifacts.length == 0) {
+            return Collections.emptySet();
+        }
+        Set<ComponentArtifactMetaData> artifacts = new LinkedHashSet<ComponentArtifactMetaData>();
+        for (DependencyArtifactDescriptor artifactDescriptor : dependencyArtifacts) {
+            ModuleRevisionId id = toConfiguration.getComponent().getDescriptor().getModuleRevisionId();
+            Artifact artifact = new DefaultArtifact(id, null, artifactDescriptor.getName(), artifactDescriptor.getType(), artifactDescriptor.getExt(), artifactDescriptor.getUrl(), artifactDescriptor.getQualifiedExtraAttributes());
+            artifacts.add(toConfiguration.getComponent().artifact(artifact));
+        }
+        return artifacts;
+    }
+
+    public DependencyMetaData withRequestedVersion(String requestedVersion) {
+        if (requestedVersion.equals(requested.getVersion())) {
+            return this;
+        }
+        return new DefaultDependencyMetaData(dependencyDescriptor.clone(IvyUtil.createModuleRevisionId(dependencyDescriptor.getDependencyRevisionId(), requestedVersion)));
+    }
+
+    public DependencyMetaData withRequestedVersion(ModuleVersionSelector requestedVersion) {
+        if (requestedVersion.equals(requested)) {
+            return this;
+        }
+
+        ModuleRevisionId requestedId = IvyUtil.createModuleRevisionId(requestedVersion.getGroup(), requestedVersion.getName(), requestedVersion.getVersion());
+        DependencyDescriptor substitutedDescriptor = new ReflectiveDependencyDescriptorFactory().create(dependencyDescriptor, requestedId);
+        return new DefaultDependencyMetaData(substitutedDescriptor);
+    }
+
+    public DependencyMetaData withChanging() {
+        if (dependencyDescriptor.isChanging()) {
+            return this;
+        }
+
+        DependencyDescriptor forcedChanging = dependencyDescriptor.clone(dependencyDescriptor.getDependencyRevisionId());
+        try {
+            Field field = DefaultDependencyDescriptor.class.getDeclaredField("isChanging");
+            field.setAccessible(true);
+            field.set(forcedChanging, true);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        return new DefaultDependencyMetaData(forcedChanging);
+    }
+
+    public ComponentSelector getSelector() {
+        if(dependencyDescriptor instanceof ProjectDependencyDescriptor) {
+            return new DefaultProjectComponentSelector(((ProjectDependencyDescriptor)dependencyDescriptor).getTargetProject().getPath());
+        }
+
+        return DefaultModuleComponentSelector.newSelector(requested.getGroup(), requested.getName(), requested.getVersion());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultIvyArtifactName.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultIvyArtifactName.java
new file mode 100644
index 0000000..37628ac
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultIvyArtifactName.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import com.google.common.base.Objects;
+import org.gradle.api.Nullable;
+import org.gradle.util.GUtil;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultIvyArtifactName implements IvyArtifactName {
+    private static final String CLASSIFIER = "classifier";
+    private final String name;
+    private final String type;
+    private final String extension;
+    private final Map<String, String> attributes;
+
+    public DefaultIvyArtifactName(String name, String type, @Nullable String extension, Map<String, String> attributes) {
+        this.name = name;
+        this.type = type;
+        this.extension = extension;
+        this.attributes = attributes.isEmpty() ? Collections.<String, String>emptyMap() : new HashMap<String, String>(attributes);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append(name);
+        String classifier = attributes.get(CLASSIFIER);
+        if (GUtil.isTrue(classifier)) {
+            result.append("-");
+            result.append(classifier);
+        }
+        if (GUtil.isTrue(extension)) {
+            result.append(".");
+            result.append(extension);
+        }
+        return result.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode() ^ type.hashCode() ^ (extension == null ? 0 : extension.hashCode()) ^ attributes.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultIvyArtifactName other = (DefaultIvyArtifactName) obj;
+        return other.name.equals(name)
+                && other.type.equals(type)
+                && Objects.equal(other.extension, extension)
+                && other.attributes.equals(attributes);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    @Nullable
+    public String getClassifier() {
+        return attributes.get(CLASSIFIER);
+    }
+
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalArtifactIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalArtifactIdentifier.java
new file mode 100644
index 0000000..c69a34f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalArtifactIdentifier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+import java.util.Map;
+
+public class DefaultLocalArtifactIdentifier implements ComponentArtifactIdentifier {
+    private final ComponentIdentifier componentIdentifier;
+    private final String componentDisplayName;
+    private final IvyArtifactName name;
+
+    // The componentDisplayName parameter is temporary
+    public DefaultLocalArtifactIdentifier(ComponentIdentifier componentIdentifier, String componentDisplayName, String name, String type, @Nullable String extension, Map<String, String> attributes) {
+        this.componentIdentifier = componentIdentifier;
+        this.componentDisplayName = componentDisplayName;
+        this.name = new DefaultIvyArtifactName(name, type, extension, attributes);
+    }
+
+    public String getDisplayName() {
+        return String.format("%s:%s", componentDisplayName, name);
+    }
+
+    public IvyArtifactName getName() {
+        return name;
+    }
+
+    public ComponentIdentifier getComponentIdentifier() {
+        return componentIdentifier;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    @Override
+    public int hashCode() {
+        return componentIdentifier.hashCode() ^ name.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultLocalArtifactIdentifier other = (DefaultLocalArtifactIdentifier) obj;
+        return other.componentIdentifier.equals(componentIdentifier) && other.name.equals(name);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalComponentMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalComponentMetaData.java
new file mode 100644
index 0000000..48b91ae
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalComponentMetaData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultLocalComponentMetaData implements MutableLocalComponentMetaData {
+    private final Map<ComponentArtifactIdentifier, DefaultLocalArtifactMetaData> artifactsById = new LinkedHashMap<ComponentArtifactIdentifier, DefaultLocalArtifactMetaData>();
+    private final Map<ArtifactRevisionId, DefaultLocalArtifactMetaData> artifactsByIvy = new LinkedHashMap<ArtifactRevisionId, DefaultLocalArtifactMetaData>();
+    private final Multimap<String, DefaultLocalArtifactMetaData> artifactsByConfig = LinkedHashMultimap.create();
+    private final DefaultModuleDescriptor moduleDescriptor;
+    private final ModuleVersionIdentifier id;
+    private final ComponentIdentifier componentIdentifier;
+
+    public DefaultLocalComponentMetaData(DefaultModuleDescriptor moduleDescriptor, ComponentIdentifier componentIdentifier) {
+        this.moduleDescriptor = moduleDescriptor;
+        id = DefaultModuleVersionIdentifier.newId(moduleDescriptor.getModuleRevisionId());
+        this.componentIdentifier = componentIdentifier;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
+    }
+
+    public DefaultModuleDescriptor getModuleDescriptor() {
+        return moduleDescriptor;
+    }
+
+    public void addArtifact(String configuration, Artifact artifact, File file) {
+        moduleDescriptor.addArtifact(configuration, artifact);
+        DefaultLocalArtifactMetaData artifactMetaData = new DefaultLocalArtifactMetaData(componentIdentifier, id.toString(), artifact, file);
+        artifactsById.put(artifactMetaData.id, artifactMetaData);
+        artifactsByConfig.put(configuration, artifactMetaData);
+        artifactsByIvy.put(artifact.getId(), artifactMetaData);
+    }
+
+    public Collection<? extends LocalArtifactMetaData> getArtifacts() {
+        return artifactsByConfig.values();
+    }
+
+    public LocalArtifactMetaData getArtifact(ComponentArtifactIdentifier artifactIdentifier) {
+        return artifactsById.get(artifactIdentifier);
+    }
+
+    public ComponentMetaData toResolveMetaData() {
+        return new LocalComponentResolveMetaData();
+    }
+
+    public BuildableModuleVersionPublishMetaData toPublishMetaData() {
+        DefaultModuleVersionPublishMetaData publishMetaData = new DefaultModuleVersionPublishMetaData(id);
+        for (DefaultLocalArtifactMetaData artifact : artifactsById.values()) {
+            publishMetaData.addArtifact(artifact.artifact, artifact.file);
+        }
+        return publishMetaData;
+    }
+
+    private static class DefaultLocalArtifactMetaData implements LocalArtifactMetaData {
+        private final ComponentIdentifier componentIdentifier;
+        private final DefaultLocalArtifactIdentifier id;
+        private final Artifact artifact;
+        private final File file;
+
+        private DefaultLocalArtifactMetaData(ComponentIdentifier componentIdentifier, String displayName, Artifact artifact, File file) {
+            this.componentIdentifier = componentIdentifier;
+            Map<String, String> attrs = new HashMap<String, String>();
+            attrs.putAll(artifact.getExtraAttributes());
+            attrs.put("file", file == null ? "null" : file.getAbsolutePath());
+            this.id = new DefaultLocalArtifactIdentifier(componentIdentifier, displayName, artifact.getName(), artifact.getType(), artifact.getExt(), attrs);
+            this.artifact = artifact;
+            this.file = file;
+        }
+
+        @Override
+        public String toString() {
+            return id.toString();
+        }
+
+        public IvyArtifactName getName() {
+            return id.getName();
+        }
+
+        public ComponentIdentifier getComponentId() {
+            return componentIdentifier;
+        }
+
+        public ComponentArtifactIdentifier getId() {
+            return id;
+        }
+
+        public File getFile() {
+            return file;
+        }
+    }
+
+    private class LocalComponentResolveMetaData extends AbstractModuleDescriptorBackedMetaData {
+        public LocalComponentResolveMetaData() {
+            // TODO:ADAM - need to clone the descriptor
+            super(id, moduleDescriptor, componentIdentifier);
+        }
+
+        public MutableModuleVersionMetaData copy() {
+            throw new UnsupportedOperationException();
+        }
+
+        public ModuleVersionMetaData withSource(ModuleSource source) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ComponentArtifactMetaData artifact(Artifact artifact) {
+            DefaultLocalArtifactMetaData candidate = artifactsByIvy.get(artifact.getId());
+            return candidate != null ? candidate : new DefaultLocalArtifactMetaData(componentIdentifier, id.toString(), artifact, null);
+        }
+
+        public Set<ComponentArtifactMetaData> getArtifacts() {
+            return new LinkedHashSet<ComponentArtifactMetaData>(artifactsById.values());
+        }
+
+        @Override
+        protected Set<ComponentArtifactMetaData> getArtifactsForConfiguration(ConfigurationMetaData configurationMetaData) {
+            Set<ComponentArtifactMetaData> result = new LinkedHashSet<ComponentArtifactMetaData>();
+            Set<ComponentArtifactIdentifier> seen = new HashSet<ComponentArtifactIdentifier>();
+            for (String configName : configurationMetaData.getHierarchy()) {
+                for (DefaultLocalArtifactMetaData localArtifactMetaData : artifactsByConfig.get(configName)) {
+                    if (seen.add(localArtifactMetaData.id)) {
+                        result.add(localArtifactMetaData);
+                    }
+                }
+            }
+            return result;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactIdentifier.java
new file mode 100644
index 0000000..8310733
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactIdentifier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class DefaultModuleVersionArtifactIdentifier implements ModuleVersionArtifactIdentifier {
+    private final ModuleComponentIdentifier componentIdentifier;
+    private final IvyArtifactName name;
+
+    public DefaultModuleVersionArtifactIdentifier(ModuleComponentIdentifier componentIdentifier, Artifact artifact) {
+        this(componentIdentifier, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttributes());
+    }
+
+    public DefaultModuleVersionArtifactIdentifier(ModuleVersionIdentifier moduleVersionIdentifier, String name, String type, @Nullable String extension) {
+        this(DefaultModuleComponentIdentifier.newId(moduleVersionIdentifier), name, type, extension, Collections.<String, String>emptyMap());
+    }
+
+    public DefaultModuleVersionArtifactIdentifier(ModuleComponentIdentifier componentIdentifier, String name, String type, @Nullable String extension, Map<String, String> attributes) {
+        this.componentIdentifier = componentIdentifier;
+        this.name = new DefaultIvyArtifactName(name, type, extension, attributes);
+    }
+
+    public String getDisplayName() {
+        return String.format("%s:%s", componentIdentifier, name);
+    }
+
+    public IvyArtifactName getName() {
+        return name;
+    }
+
+    public ModuleComponentIdentifier getComponentIdentifier() {
+        return componentIdentifier;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    @Override
+    public int hashCode() {
+        return componentIdentifier.hashCode() ^ name.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultModuleVersionArtifactIdentifier other = (DefaultModuleVersionArtifactIdentifier) obj;
+        return other.componentIdentifier.equals(componentIdentifier)
+                && other.name.equals(name);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactMetaData.java
new file mode 100644
index 0000000..5324c51
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactMetaData.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.DefaultArtifactIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+
+public class DefaultModuleVersionArtifactMetaData implements ModuleVersionArtifactMetaData {
+    private final DefaultModuleVersionArtifactIdentifier id;
+
+    public DefaultModuleVersionArtifactMetaData(ModuleComponentIdentifier componentIdentifier, Artifact artifact) {
+        this(new DefaultModuleVersionArtifactIdentifier(componentIdentifier, artifact));
+    }
+
+    public DefaultModuleVersionArtifactMetaData(ModuleVersionArtifactIdentifier moduleVersionArtifactIdentifier) {
+        this.id = (DefaultModuleVersionArtifactIdentifier) moduleVersionArtifactIdentifier;
+    }
+
+    @Override
+    public String toString() {
+        return id.toString();
+    }
+
+    public ModuleVersionArtifactIdentifier getId() {
+        return id;
+    }
+
+    public ComponentIdentifier getComponentId() {
+        return id.getComponentIdentifier();
+    }
+
+    public ArtifactIdentifier toArtifactIdentifier() {
+        return new DefaultArtifactIdentifier(id);
+    }
+
+    public Artifact toIvyArtifact() {
+        IvyArtifactName ivyArtifactName = id.getName();
+        return new DefaultArtifact(IvyUtil.createModuleRevisionId(id.getComponentIdentifier()), null, ivyArtifactName.getName(), ivyArtifactName.getType(), ivyArtifactName.getExtension(), ivyArtifactName.getAttributes());
+    }
+
+    public IvyArtifactName getName() {
+        return id.getName();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionPublishMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionPublishMetaData.java
new file mode 100644
index 0000000..6e45f24
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionPublishMetaData.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class DefaultModuleVersionPublishMetaData implements BuildableModuleVersionPublishMetaData {
+    private final ModuleVersionIdentifier id;
+    private final Map<ModuleVersionArtifactIdentifier, ModuleVersionArtifactPublishMetaData> artifactsById = new LinkedHashMap<ModuleVersionArtifactIdentifier, ModuleVersionArtifactPublishMetaData>();
+
+    public DefaultModuleVersionPublishMetaData(ModuleVersionIdentifier id) {
+        this.id = id;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
+    }
+
+    public void addArtifact(Artifact artifact, File file) {
+        DefaultModuleVersionArtifactPublishMetaData publishMetaData = new DefaultModuleVersionArtifactPublishMetaData(id, artifact, file);
+        artifactsById.put(publishMetaData.getId(), publishMetaData);
+    }
+
+    public void addArtifact(ModuleVersionArtifactPublishMetaData artifact) {
+        artifactsById.put(artifact.getId(), artifact);
+    }
+
+    public Collection<ModuleVersionArtifactPublishMetaData> getArtifacts() {
+        return artifactsById.values();
+    }
+
+    public ModuleVersionArtifactPublishMetaData getArtifact(ModuleVersionArtifactIdentifier artifactIdentifier) {
+        return artifactsById.get(artifactIdentifier);
+    }
+
+    private static class DefaultModuleVersionArtifactPublishMetaData implements ModuleVersionArtifactPublishMetaData {
+        private final DefaultModuleVersionArtifactIdentifier id;
+        private final Artifact artifact;
+        private final File file;
+
+        private DefaultModuleVersionArtifactPublishMetaData(ModuleVersionIdentifier moduleVersionIdentifier, Artifact artifact, File file) {
+            this.id = new DefaultModuleVersionArtifactIdentifier(DefaultModuleComponentIdentifier.newId(moduleVersionIdentifier), artifact);
+            this.artifact = artifact;
+            this.file = file;
+        }
+
+        public IvyArtifactName getArtifactName() {
+            return id.getName();
+        }
+
+        public Artifact toIvyArtifact() {
+            return artifact;
+        }
+
+        public ModuleVersionArtifactIdentifier getId() {
+            return id;
+        }
+
+        public File getFile() {
+            return file;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DependencyMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DependencyMetaData.java
new file mode 100644
index 0000000..3e36c66
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/DependencyMetaData.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.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
+
+import java.util.Set;
+
+public interface DependencyMetaData {
+    ModuleVersionSelector getRequested();
+
+    /**
+     * Returns this dependency as an Ivy DependencyDescriptor. This method is here to allow us to migrate away from the Ivy types
+     * and will be removed.
+     */
+    DependencyDescriptor getDescriptor();
+
+    boolean isChanging();
+
+    boolean isTransitive();
+
+    /**
+     * Returns the artifacts defined by this dependency, if any.
+     */
+    // TODO:ADAM - fromConfiguration should be implicit in this metadata
+    Set<ComponentArtifactMetaData> getArtifacts(ConfigurationMetaData fromConfiguration, ConfigurationMetaData toConfiguration);
+
+    /**
+     * Returns a copy of this dependency with the given requested version.
+     */
+    DependencyMetaData withRequestedVersion(String requestedVersion);
+
+    /**
+     * Returns a copy of this dependency with the given requested version.
+     */
+    DependencyMetaData withRequestedVersion(ModuleVersionSelector requestedVersion);
+
+    /**
+     * Returns a copy of this dependency with the changing flag set.
+     */
+    DependencyMetaData withChanging();
+
+    /**
+     * Returns the component selector for this dependency.
+     *
+     * @return Component selector
+     */
+    ComponentSelector getSelector();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/IvyArtifactName.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/IvyArtifactName.java
new file mode 100644
index 0000000..8e6cb07
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/IvyArtifactName.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.Nullable;
+
+import java.util.Map;
+
+/**
+ * Represents the 'name' part of an Ivy artifact, independent of which module version the artifact might belong to.
+ */
+public interface IvyArtifactName {
+    String getName();
+
+    String getType();
+
+    @Nullable
+    String getExtension();
+
+    String getClassifier();
+
+    Map<String, String> getAttributes();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/LocalArtifactMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/LocalArtifactMetaData.java
new file mode 100644
index 0000000..e962d4b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/LocalArtifactMetaData.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import java.io.File;
+
+public interface LocalArtifactMetaData extends ComponentArtifactMetaData {
+    File getFile();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/LocalComponentMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/LocalComponentMetaData.java
new file mode 100644
index 0000000..2a8987c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/LocalComponentMetaData.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+public interface LocalComponentMetaData {
+    ModuleVersionIdentifier getId();
+
+    ModuleDescriptor getModuleDescriptor();
+
+    /**
+     * Converts this component to resolve meta-data.
+     */
+    ComponentMetaData toResolveMetaData();
+
+    /**
+     * Converts this component to publication meta-data.
+     */
+    BuildableModuleVersionPublishMetaData toPublishMetaData();
+
+    @Nullable
+    LocalArtifactMetaData getArtifact(ComponentArtifactIdentifier artifactIdentifier);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleDescriptorAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleDescriptorAdapter.java
new file mode 100644
index 0000000..747f8ca
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleDescriptorAdapter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.*;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+
+import java.util.*;
+
+public class ModuleDescriptorAdapter extends AbstractModuleDescriptorBackedMetaData implements MutableModuleVersionMetaData {
+    private boolean metaDataOnly;
+    private Set<ModuleVersionArtifactMetaData> artifacts;
+
+    public static ModuleDescriptorAdapter defaultForDependency(DependencyMetaData dependencyMetaData) {
+        DependencyDescriptor dependencyDescriptor = dependencyMetaData.getDescriptor();
+        DefaultModuleDescriptor moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance(dependencyDescriptor.getDependencyRevisionId(), dependencyDescriptor.getAllDependencyArtifacts());
+        moduleDescriptor.setStatus("integration");
+        return new ModuleDescriptorAdapter(moduleDescriptor);
+    }
+
+    public ModuleDescriptorAdapter(ModuleDescriptor moduleDescriptor) {
+        this(DefaultModuleVersionIdentifier.newId(moduleDescriptor.getModuleRevisionId()), moduleDescriptor);
+    }
+
+    public ModuleDescriptorAdapter(ModuleVersionIdentifier identifier, ModuleDescriptor moduleDescriptor) {
+        this(identifier, moduleDescriptor, DefaultModuleComponentIdentifier.newId(identifier));
+    }
+
+    public ModuleDescriptorAdapter(ModuleVersionIdentifier moduleVersionIdentifier, ModuleDescriptor moduleDescriptor, ModuleComponentIdentifier componentIdentifier) {
+        super(moduleVersionIdentifier, moduleDescriptor, componentIdentifier);
+    }
+
+    public ModuleDescriptorAdapter copy() {
+        // TODO:ADAM - need to make a copy of the descriptor (it's effectively immutable at this point so it's not a problem yet)
+        ModuleDescriptorAdapter copy = new ModuleDescriptorAdapter(getId(), getDescriptor(), getComponentId());
+        copyTo(copy);
+        copy.metaDataOnly = metaDataOnly;
+        return copy;
+    }
+
+    public ModuleVersionMetaData withSource(ModuleSource source) {
+        ModuleDescriptorAdapter copy = copy();
+        copy.setModuleSource(source);
+        return copy;
+    }
+
+    @Override
+    public ModuleComponentIdentifier getComponentId() {
+        return (ModuleComponentIdentifier) super.getComponentId();
+    }
+
+    public boolean isMetaDataOnly() {
+        return metaDataOnly;
+    }
+
+    public void setMetaDataOnly(boolean metaDataOnly) {
+        this.metaDataOnly = metaDataOnly;
+    }
+
+    public ModuleVersionArtifactMetaData artifact(Artifact artifact) {
+        return new DefaultModuleVersionArtifactMetaData(getComponentId(), artifact);
+    }
+
+    public ModuleVersionArtifactMetaData artifact(String type, @Nullable String extension, @Nullable String classifier) {
+        Map extraAttributes = classifier == null ? Collections.emptyMap() : Collections.singletonMap("m:classifier", classifier);
+        Artifact artifact = new DefaultArtifact(getDescriptor().getModuleRevisionId(), null, getId().getName(), type, "jar", extraAttributes);
+        return new DefaultModuleVersionArtifactMetaData(getComponentId(), artifact);
+    }
+
+    public Set<ModuleVersionArtifactMetaData> getArtifacts() {
+        if (artifacts == null) {
+            artifacts = new LinkedHashSet<ModuleVersionArtifactMetaData>();
+            for (Artifact artifact : getDescriptor().getAllArtifacts()) {
+                artifacts.add(artifact(artifact));
+            }
+        }
+        return artifacts;
+    }
+
+    protected Set<ComponentArtifactMetaData> getArtifactsForConfiguration(ConfigurationMetaData configurationMetaData) {
+        Set<Artifact> artifacts = new HashSet<Artifact>();
+        Set<ComponentArtifactMetaData> artifactMetaData = new LinkedHashSet<ComponentArtifactMetaData>();
+        for (String ancestor : configurationMetaData.getHierarchy()) {
+            for (Artifact artifact : getDescriptor().getArtifacts(ancestor)) {
+                if (artifacts.add(artifact)) {
+                    artifactMetaData.add(new DefaultModuleVersionArtifactMetaData(getComponentId(), artifact));
+                }
+            }
+        }
+        return artifactMetaData;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactIdentifier.java
new file mode 100644
index 0000000..6b321bb
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactIdentifier.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+
+/**
+ * An immutable identifier for an artifact that belongs to some module version.
+ */
+public interface ModuleVersionArtifactIdentifier extends ComponentArtifactIdentifier {
+    /**
+     * Returns the id of the component that this artifact belongs to.
+     */
+    ModuleComponentIdentifier getComponentIdentifier();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactIdentifierSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactIdentifierSerializer.java
new file mode 100644
index 0000000..4e90f86
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactIdentifierSerializer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ComponentIdentifierSerializer;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.MapSerializer;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.util.Map;
+
+import static org.gradle.messaging.serialize.BaseSerializerFactory.STRING_SERIALIZER;
+
+public class ModuleVersionArtifactIdentifierSerializer implements Serializer<ModuleVersionArtifactIdentifier> {
+    private final ComponentIdentifierSerializer componentIdentifierSerializer = new ComponentIdentifierSerializer();
+    private final MapSerializer<String, String> attributesSerializer = new MapSerializer<String, String>(STRING_SERIALIZER, STRING_SERIALIZER);
+
+    public void write(Encoder encoder, ModuleVersionArtifactIdentifier value) throws Exception {
+        DefaultModuleVersionArtifactIdentifier artifact = (DefaultModuleVersionArtifactIdentifier) value;
+        componentIdentifierSerializer.write(encoder, artifact.getComponentIdentifier());
+        IvyArtifactName ivyArtifactName = artifact.getName();
+        encoder.writeString(ivyArtifactName.getName());
+        encoder.writeString(ivyArtifactName.getType());
+        encoder.writeNullableString(ivyArtifactName.getExtension());
+        attributesSerializer.write(encoder, ivyArtifactName.getAttributes());
+    }
+
+    public ModuleVersionArtifactIdentifier read(Decoder decoder) throws Exception {
+        ModuleComponentIdentifier componentIdentifier = (ModuleComponentIdentifier) componentIdentifierSerializer.read(decoder);
+        String artifactName = decoder.readString();
+        String type = decoder.readString();
+        String extension = decoder.readNullableString();
+        Map<String, String> attributes = attributesSerializer.read(decoder);
+        return new DefaultModuleVersionArtifactIdentifier(componentIdentifier, artifactName, type, extension, attributes);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactMetaData.java
new file mode 100644
index 0000000..35a67e9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactMetaData.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.ArtifactIdentifier;
+
+/**
+ * Meta-data for an artifact that belongs to some module version.
+ */
+public interface ModuleVersionArtifactMetaData extends ComponentArtifactMetaData {
+    ModuleVersionArtifactIdentifier getId();
+
+    /**
+     * Converts this artifact to an Ivy artifact. This method is here while we transition away from the Ivy types.
+     */
+    Artifact toIvyArtifact();
+
+    /**
+     * Produces an ArtifactIdentifier for this artifact (it's not actually an identifier - just a bucket of attributes).
+     * TODO:ADAM - remove this
+     */
+    ArtifactIdentifier toArtifactIdentifier();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactPublishMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactPublishMetaData.java
new file mode 100644
index 0000000..3320235
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionArtifactPublishMetaData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+import java.io.File;
+
+// TODO:ADAM - This is actually Ivy artifact publish meta data
+public interface ModuleVersionArtifactPublishMetaData {
+    ModuleVersionArtifactIdentifier getId();
+
+    /**
+     * Converts this artifact to an Ivy artifact. This method is here while we transition away from the Ivy types.
+     */
+    Artifact toIvyArtifact();
+
+    IvyArtifactName getArtifactName();
+
+    File getFile();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionMetaData.java
new file mode 100644
index 0000000..9f24472
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionMetaData.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.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+
+import java.util.Set;
+
+/**
+ * The meta-data for a module version that is required during dependency resolution.
+ */
+public interface ModuleVersionMetaData extends ComponentMetaData {
+    ModuleComponentIdentifier getComponentId();
+
+    ModuleVersionMetaData withSource(ModuleSource source);
+
+    Set<ModuleVersionArtifactMetaData> getArtifacts();
+
+    ModuleVersionArtifactMetaData artifact(Artifact artifact);
+
+    ModuleVersionArtifactMetaData artifact(String type, @Nullable String extension, @Nullable String classifier);
+
+    // TODO:DAZ This is not set correctly when read from cache
+    boolean isMetaDataOnly();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionPublishMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionPublishMetaData.java
new file mode 100644
index 0000000..358636e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/ModuleVersionPublishMetaData.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.util.Collection;
+
+// TODO:ADAM - This is actually Ivy module publish meta data
+public interface ModuleVersionPublishMetaData {
+    ModuleVersionIdentifier getId();
+
+    Collection<ModuleVersionArtifactPublishMetaData> getArtifacts();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/MutableLocalComponentMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/MutableLocalComponentMetaData.java
new file mode 100644
index 0000000..dca6877
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/MutableLocalComponentMetaData.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+
+import java.io.File;
+
+public interface MutableLocalComponentMetaData extends LocalComponentMetaData {
+    DefaultModuleDescriptor getModuleDescriptor();
+
+    void addArtifact(String configuration, Artifact artifact, File file);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/MutableModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/MutableModuleVersionMetaData.java
new file mode 100644
index 0000000..2b6910d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/metadata/MutableModuleVersionMetaData.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.metadata;
+
+import java.util.List;
+
+public interface MutableModuleVersionMetaData extends ModuleVersionMetaData {
+    /**
+     * Creates a deep copy of this meta-data.
+     */
+    MutableModuleVersionMetaData copy();
+
+    void setChanging(boolean changing);
+    void setStatus(String status);
+    void setStatusScheme(List<String> statusScheme);
+
+    /**
+     * Replaces the dependencies of this module version.
+     */
+    void setDependencies(Iterable<? extends DependencyMetaData> dependencies);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
index 5be1816..a1dfa13 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.artifacts.mvnsettings;
 
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 @Contextual
 public class CannotLocateLocalMavenRepositoryException extends GradleException {
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 4f232aa..4797f5e 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
@@ -16,7 +16,7 @@
 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.*;
+import org.gradle.mvn3.org.apache.maven.settings.building.SettingsBuildingException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -25,9 +25,6 @@ import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * @author Steve Ebersole
- */
 public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryLocator {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLocalMavenRepositoryLocator.class);
     private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^\\}]*)\\}");
@@ -55,7 +52,7 @@ public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryL
                 return defaultLocation;
             }
         } catch (SettingsBuildingException e) {
-            throw new CannotLocateLocalMavenRepositoryException("Unable to parse local maven settings.", e);
+            throw new CannotLocateLocalMavenRepositoryException("Unable to parse local Maven settings.", e);
         }
     }
 
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 ff01ffc..b07ce89 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
@@ -22,9 +22,6 @@ import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
 
-/**
-* @author Szczepan Faber, created at: 3/30/11
-*/
 public class DefaultMavenFileLocations implements MavenFileLocations {
     public File getUserMavenDir() {
         return new File(SystemProperties.getUserHome(), ".m2");
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
index 6846eb3..04ec293 100644
--- 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
@@ -18,9 +18,6 @@ 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;
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
deleted file mode 100644
index 46cf19a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CustomResolverArtifactRepository.java
+++ /dev/null
@@ -1,72 +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.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.ProgressLoggingTransferListener;
-import org.gradle.logging.ProgressLoggerFactory;
-
-import java.util.List;
-
-public class CustomResolverArtifactRepository extends FixedResolverArtifactRepository {
-    private final TransferListener transferListener;
-    private final RepositoryCacheManager downloadingCacheManager;
-    private final RepositoryCacheManager localCacheManager;
-
-    public CustomResolverArtifactRepository(DependencyResolver resolver, ProgressLoggerFactory progressLoggerFactory,
-                                            RepositoryCacheManager localCacheManager, RepositoryCacheManager downloadingCacheManager) {
-        super(resolver);
-        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(downloadingCacheManager);
-            }
-        }
-
-        if (dependencyResolver instanceof FileSystemResolver) {
-            ((FileSystemResolver) dependencyResolver).setLocal(true);
-            ((FileSystemResolver) dependencyResolver).setRepositoryCacheManager(localCacheManager);
-        }
-        if (dependencyResolver instanceof RepositoryResolver) {
-            Repository repository = ((RepositoryResolver) dependencyResolver).getRepository();
-            if (!repository.hasTransferListener(transferListener)) {
-                repository.addTransferListener(transferListener);
-            }
-        }
-        if (dependencyResolver instanceof DualResolver) {
-            DualResolver dualResolver = (DualResolver) dependencyResolver;
-            configureResolver(dualResolver.getIvyResolver(), false);
-            configureResolver(dualResolver.getArtifactResolver(), false);
-        }
-        if (dependencyResolver instanceof ChainResolver) {
-            ChainResolver chainResolver = (ChainResolver) dependencyResolver;
-            @SuppressWarnings("unchecked") List<DependencyResolver> resolvers = chainResolver.getResolvers();
-            for (DependencyResolver resolver : resolvers) {
-                configureResolver(resolver, false);
-            }
-        }
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
index c776957..c2a0e75 100644
--- 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
@@ -16,53 +16,49 @@
 
 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.BaseRepositoryFactory;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
+import org.gradle.api.internal.artifacts.repositories.legacy.FixedResolverArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.legacy.LegacyDependencyResolverRepositoryFactory;
 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 LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder;
+    private final LegacyDependencyResolverRepositoryFactory legacyDependencyResolverRepositoryFactory;
+    private final ResolverStrategy resolverStrategy;
 
     public DefaultBaseRepositoryFactory(LocalMavenRepositoryLocator localMavenRepositoryLocator,
                                         FileResolver fileResolver,
                                         Instantiator instantiator,
                                         RepositoryTransportFactory transportFactory,
-                                        LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
-                                        ProgressLoggerFactory progressLoggerFactory,
-                                        RepositoryCacheManager localCacheManager,
-                                        RepositoryCacheManager downloadingCacheManager) {
+                                        LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                                        LegacyDependencyResolverRepositoryFactory legacyDependencyResolverRepositoryFactory,
+                                        ResolverStrategy resolverStrategy) {
         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.legacyDependencyResolverRepositoryFactory = legacyDependencyResolverRepositoryFactory;
+        this.resolverStrategy = resolverStrategy;
     }
 
     public ArtifactRepository createRepository(Object userDescription) {
@@ -81,26 +77,32 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
             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 legacyDependencyResolverRepositoryFactory.createRepository((DependencyResolver) userDescription);
         }
-        return new CustomResolverArtifactRepository(result, progressLoggerFactory, localCacheManager, downloadingCacheManager);
+        throw new InvalidUserDataException(String.format("Cannot create a DependencyResolver instance from %s", userDescription));
     }
 
     public FlatDirectoryArtifactRepository createFlatDirRepository() {
-        return instantiator.newInstance(DefaultFlatDirArtifactRepository.class, fileResolver, localCacheManager);
+        return instantiator.newInstance(DefaultFlatDirArtifactRepository.class, fileResolver, transportFactory,
+                locallyAvailableResourceFinder, resolverStrategy);
     }
 
     public MavenArtifactRepository createMavenLocalRepository() {
-        MavenArtifactRepository mavenRepository = createMavenRepository();
+        MavenArtifactRepository mavenRepository = instantiator.newInstance(DefaultMavenLocalArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
+                locallyAvailableResourceFinder, resolverStrategy);
         final File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
         mavenRepository.setUrl(localMavenRepository);
         return mavenRepository;
     }
 
+    public MavenArtifactRepository createJCenterRepository() {
+        MavenArtifactRepository mavenRepository = createMavenRepository();
+        String url = System.getProperty(JCENTER_REPO_OVERRIDE_URL_PROPERTY, DefaultRepositoryHandler.BINTRAY_JCENTER_URL);
+        mavenRepository.setUrl(url);
+        return mavenRepository;
+    }
+
     public MavenArtifactRepository createMavenCentralRepository() {
         MavenArtifactRepository mavenRepository = createMavenRepository();
         mavenRepository.setUrl(RepositoryHandler.MAVEN_CENTRAL_URL);
@@ -109,13 +111,12 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
 
     public IvyArtifactRepository createIvyRepository() {
         return instantiator.newInstance(DefaultIvyArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
-                locallyAvailableResourceFinder, instantiator
-        );
+                locallyAvailableResourceFinder, instantiator, resolverStrategy);
     }
 
     public MavenArtifactRepository createMavenRepository() {
         return instantiator.newInstance(DefaultMavenArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
-                locallyAvailableResourceFinder
+                locallyAvailableResourceFinder, resolverStrategy
         );
     }
 
@@ -130,5 +131,4 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
     private PasswordCredentials createPasswordCredentials() {
         return instantiator.newInstance(DefaultPasswordCredentials.class);
     }
-
 }
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 9ba4a83..34e4138 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,13 +16,16 @@
 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.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyDependencyResolverAdapter;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver;
+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 java.io.File;
@@ -34,11 +37,18 @@ import java.util.Set;
 public class DefaultFlatDirArtifactRepository extends AbstractArtifactRepository implements FlatDirectoryArtifactRepository {
     private final FileResolver fileResolver;
     private List<Object> dirs = new ArrayList<Object>();
-    private final RepositoryCacheManager localCacheManager;
+    private final RepositoryTransportFactory transportFactory;
+    private final LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder;
+    private final ResolverStrategy resolverStrategy;
 
-    public DefaultFlatDirArtifactRepository(FileResolver fileResolver, RepositoryCacheManager localCacheManager) {
+    public DefaultFlatDirArtifactRepository(FileResolver fileResolver,
+                                            RepositoryTransportFactory transportFactory,
+                                            LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                                            ResolverStrategy resolverStrategy) {
         this.fileResolver = fileResolver;
-        this.localCacheManager = localCacheManager;
+        this.transportFactory = transportFactory;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.resolverStrategy = resolverStrategy;
     }
 
     public Set<File> getDirs() {
@@ -57,28 +67,31 @@ public class DefaultFlatDirArtifactRepository extends AbstractArtifactRepository
         this.dirs.addAll(Arrays.asList(dirs));
     }
 
-    public DependencyResolver createPublisher() {
-        return createLegacyDslObject();
+    public ModuleVersionPublisher createPublisher() {
+        return createRealResolver();
     }
 
-    public IvyAwareModuleVersionRepository createResolver() {
-        return new IvyDependencyResolverAdapter(createLegacyDslObject());
+    public ConfiguredModuleVersionRepository createResolver() {
+        return createRealResolver();
     }
 
     public DependencyResolver createLegacyDslObject() {
+        IvyResolver resolver = createRealResolver();
+        return new LegacyDependencyResolver(resolver);
+    }
+
+    private IvyResolver createRealResolver() {
         Set<File> dirs = getDirs();
         if (dirs.isEmpty()) {
             throw new InvalidUserDataException("You must specify at least one directory for a flat directory repository.");
         }
 
-        FileSystemResolver resolver = new FileSystemResolver();
-        resolver.setName(getName());
+        IvyResolver resolver = new IvyResolver(getName(), transportFactory.createFileTransport(getName()),
+                locallyAvailableResourceFinder, false, resolverStrategy);
         for (File root : dirs) {
             resolver.addArtifactPattern(root.getAbsolutePath() + "/[artifact]-[revision](-[classifier]).[ext]");
             resolver.addArtifactPattern(root.getAbsolutePath() + "/[artifact](-[classifier]).[ext]");
         }
-        resolver.setValidate(false);
-        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 0955405..7e928ea 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
@@ -16,17 +16,19 @@
 package org.gradle.api.internal.artifacts.repositories;
 
 import groovy.lang.Closure;
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepositoryMetaDataProvider;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ExternalResourceResolverAdapter;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 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.RepositoryTransport;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 import org.gradle.api.internal.file.FileResolver;
@@ -44,16 +46,19 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
     private final AdditionalPatternsRepositoryLayout additionalPatternsLayout;
     private final FileResolver fileResolver;
     private final RepositoryTransportFactory transportFactory;
-    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder;
     private final MetaDataProvider metaDataProvider;
     private final Instantiator instantiator;
+    private final ResolverStrategy resolverStrategy;
 
     public DefaultIvyArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
-                                        LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder, Instantiator instantiator) {
+                                        LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder, Instantiator instantiator,
+                                        ResolverStrategy resolverStrategy) {
         super(credentials);
         this.fileResolver = fileResolver;
         this.transportFactory = transportFactory;
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.resolverStrategy = resolverStrategy;
         this.additionalPatternsLayout = new AdditionalPatternsRepositoryLayout(fileResolver);
         this.layout = new GradleRepositoryLayout();
         this.metaDataProvider = new MetaDataProvider();
@@ -62,19 +67,15 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
 
     public DependencyResolver createLegacyDslObject() {
         IvyResolver resolver = createRealResolver();
-        return new LegacyDependencyResolver(resolver, wrapResolver(resolver));
+        return new LegacyDependencyResolver(resolver);
     }
 
-    public DependencyResolver createPublisher() {
+    public ModuleVersionPublisher createPublisher() {
         return createRealResolver();
     }
 
-    public IvyAwareModuleVersionRepository createResolver() {
-        return wrapResolver(createRealResolver());
-    }
-
-    private ExternalResourceResolverAdapter wrapResolver(IvyResolver resolver) {
-        return new ExternalResourceResolverAdapter(resolver, metaDataProvider.dynamicResolve);
+    public ConfiguredModuleVersionRepository createResolver() {
+        return createRealResolver();
     }
 
     protected IvyResolver createRealResolver() {
@@ -100,14 +101,21 @@ 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(getName(), transportFactory.createHttpTransport(getName(), getCredentials()), locallyAvailableResourceFinder);
+            return createResolver(transportFactory.createHttpTransport(getName(), getCredentials()));
         }
         if (WrapUtil.toSet("file").containsAll(schemes)) {
-            return new IvyResolver(getName(), transportFactory.createFileTransport(getName()), locallyAvailableResourceFinder);
+            return createResolver(transportFactory.createFileTransport(getName()));
         }
         throw new InvalidUserDataException("You cannot mix file and http(s) urls for a single Ivy repository. Please declare 2 separate repositories.");
     }
 
+    private IvyResolver createResolver(RepositoryTransport httpTransport) {
+        return new IvyResolver(
+                getName(), httpTransport,
+                locallyAvailableResourceFinder,
+                metaDataProvider.dynamicResolve, resolverStrategy);
+    }
+
     public URI getUrl() {
         return baseUrl == null ? null : fileResolver.resolveUri(baseUrl);
     }
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 60fe1b5..44e930f 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
@@ -16,13 +16,14 @@
 package org.gradle.api.internal.artifacts.repositories;
 
 import com.google.common.collect.Lists;
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ExternalResourceResolverAdapter;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 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;
@@ -40,14 +41,17 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
     private final RepositoryTransportFactory transportFactory;
     private Object url;
     private List<Object> additionalUrls = new ArrayList<Object>();
-    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder;
+    private final ResolverStrategy resolverStrategy;
 
     public DefaultMavenArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
-                                          LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder) {
+                                          LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                                          ResolverStrategy resolverStrategy) {
         super(credentials);
         this.fileResolver = fileResolver;
         this.transportFactory = transportFactory;
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.resolverStrategy = resolverStrategy;
     }
 
     public URI getUrl() {
@@ -74,21 +78,17 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
         additionalUrls = Lists.newArrayList(urls);
     }
 
-    public DependencyResolver createPublisher() {
+    public ModuleVersionPublisher createPublisher() {
         return createRealResolver();
     }
 
     public DependencyResolver createLegacyDslObject() {
         MavenResolver resolver = createRealResolver();
-        return new LegacyMavenResolver(resolver, wrapResolver(resolver));
+        return new LegacyMavenResolver(resolver);
     }
 
-    public IvyAwareModuleVersionRepository createResolver() {
-        return wrapResolver(createRealResolver());
-    }
-
-    private ExternalResourceResolverAdapter wrapResolver(MavenResolver resolver) {
-        return new ExternalResourceResolverAdapter(resolver, false);
+    public ConfiguredModuleVersionRepository createResolver() {
+        return createRealResolver();
     }
 
     protected MavenResolver createRealResolver() {
@@ -97,14 +97,15 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
             throw new InvalidUserDataException("You must specify a URL for a Maven repository.");
         }
 
-        MavenResolver resolver = new MavenResolver(getName(), rootUri, getTransport(rootUri.getScheme()), locallyAvailableResourceFinder);
+        MavenResolver resolver = new MavenResolver(getName(), rootUri, getTransport(rootUri.getScheme()),
+                locallyAvailableResourceFinder, resolverStrategy);
         for (URI repoUrl : getArtifactUrls()) {
             resolver.addArtifactLocation(repoUrl, null);
         }
         return resolver;
     }
 
-    private RepositoryTransport getTransport(String scheme) {
+    protected RepositoryTransport getTransport(String scheme) {
         if (scheme.equalsIgnoreCase("file")) {
             return transportFactory.createFileTransport(getName());
         } else {
@@ -112,4 +113,11 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
         }
     }
 
+    protected LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> getLocallyAvailableResourceFinder() {
+        return locallyAvailableResourceFinder;
+    }
+
+    protected ResolverStrategy getResolverStrategy() {
+        return resolverStrategy;
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java
new file mode 100644
index 0000000..bfcd254
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.InvalidUserDataException;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenLocalResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
+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 java.net.URI;
+
+public class DefaultMavenLocalArtifactRepository extends DefaultMavenArtifactRepository implements MavenArtifactRepository {
+    public DefaultMavenLocalArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
+                                        LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                                        ResolverStrategy resolverStrategy) {
+        super(fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, resolverStrategy);
+    }
+
+    protected MavenResolver createRealResolver() {
+        URI rootUri = getUrl();
+        if (rootUri == null) {
+            throw new InvalidUserDataException("You must specify a URL for a Maven repository.");
+        }
+
+        MavenResolver resolver = new MavenLocalResolver(getName(), rootUri, getTransport(rootUri.getScheme()), getLocallyAvailableResourceFinder(), getResolverStrategy());
+        for (URI repoUrl : getArtifactUrls()) {
+            resolver.addArtifactLocation(repoUrl, null);
+        }
+        return resolver;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
deleted file mode 100644
index d5ff8b0..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
+++ /dev/null
@@ -1,57 +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 org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyDependencyResolverAdapter;
-
-public class FixedResolverArtifactRepository extends AbstractArtifactRepository {
-    protected final DependencyResolver resolver;
-
-    public FixedResolverArtifactRepository(DependencyResolver resolver) {
-        this.resolver = resolver;
-    }
-
-    public String getName() {
-        return resolver.getName();
-    }
-
-    public void setName(String name) {
-        resolver.setName(name);
-
-        // 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 createPublisher() {
-        return resolver;
-    }
-
-    public IvyAwareModuleVersionRepository createResolver() {
-        // Handle a repository wrapped in a resolver for backwards compatibility
-        if (resolver instanceof ResolutionAwareRepository) {
-            ResolutionAwareRepository resolutionAwareRepository = (ResolutionAwareRepository) resolver;
-            return resolutionAwareRepository.createResolver();
-        }
-        return new IvyDependencyResolverAdapter(resolver);
-    }
-
-    public DependencyResolver createLegacyDslObject() {
-        return resolver;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java
index afca9f7..3bfa9a0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java
@@ -31,15 +31,17 @@ import org.apache.ivy.core.search.OrganisationEntry;
 import org.apache.ivy.core.search.RevisionEntry;
 import org.apache.ivy.plugins.latest.LatestStrategy;
 import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.BasicResolver;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.plugins.resolver.ResolverSettings;
 import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 
 import java.io.File;
 import java.io.IOException;
 import java.text.ParseException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -49,11 +51,10 @@ import java.util.Map;
  */
 public class LegacyDependencyResolver implements DependencyResolver, ResolutionAwareRepository {
     private final ExternalResourceResolver resolver;
-    private final IvyAwareModuleVersionRepository repository;
+    private ResolverSettings settings;
 
-    public LegacyDependencyResolver(ExternalResourceResolver resolver, IvyAwareModuleVersionRepository repository) {
+    public LegacyDependencyResolver(ExternalResourceResolver resolver) {
         this.resolver = resolver;
-        this.repository = repository;
     }
 
     public String getName() {
@@ -68,16 +69,16 @@ public class LegacyDependencyResolver implements DependencyResolver, ResolutionA
         return resolver.toString();
     }
 
-    public IvyAwareModuleVersionRepository createResolver() {
-        return repository;
+    public ConfiguredModuleVersionRepository createResolver() {
+        return resolver;
     }
 
-    public void setSettings(ResolverSettings ivy) {
-        resolver.setSettings(ivy);
+    public void setSettings(ResolverSettings settings) {
+        this.settings = settings;
     }
 
     public ResolverSettings getSettings() {
-        return resolver.getSettings();
+        return settings;
     }
 
     public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
@@ -230,7 +231,16 @@ public class LegacyDependencyResolver implements DependencyResolver, ResolutionA
      * @param descriptorRule the descriptor rule to use with this resolver.
      */
     public void setDescriptor(String descriptorRule) {
-        resolver.setDescriptor(descriptorRule);
+        if (BasicResolver.DESCRIPTOR_REQUIRED.equals(descriptorRule)) {
+            setAllownomd(false);
+        } else if (BasicResolver.DESCRIPTOR_OPTIONAL.equals(descriptorRule)) {
+            setAllownomd(true);
+        } else {
+            throw new IllegalArgumentException(
+                "unknown descriptor rule '" + descriptorRule
+                + "'. Allowed rules are: "
+                + Arrays.asList(BasicResolver.DESCRIPTOR_REQUIRED, BasicResolver.DESCRIPTOR_OPTIONAL));
+        }
     }
 
     public String[] getChecksumAlgorithms() {
@@ -242,19 +252,19 @@ public class LegacyDependencyResolver implements DependencyResolver, ResolutionA
     }
 
     public LatestStrategy getLatestStrategy() {
-        return resolver.getLatestStrategy();
+        throw new UnsupportedOperationException("getLatestStrategy");
     }
 
     public void setLatestStrategy(LatestStrategy latestStrategy) {
-        resolver.setLatestStrategy(latestStrategy);
+        throw new UnsupportedOperationException("setLatestStrategy");
     }
 
-    public void setLatest(String strategyName) {
-        resolver.setLatest(strategyName);
+    public String getLatest() {
+        throw new UnsupportedOperationException("getLatest");
     }
 
-    public String getLatest() {
-        return resolver.getLatest();
+    public void setLatest(String strategyName) {
+        throw new UnsupportedOperationException("setLatest");
     }
 
     public void setChangingMatcher(String changingMatcherName) {
@@ -274,7 +284,7 @@ public class LegacyDependencyResolver implements DependencyResolver, ResolutionA
     }
 
     public void setCheckmodified(boolean check) {
-        resolver.setCheckmodified(check);
+        throw new UnsupportedOperationException();
     }
 
     public RepositoryCacheManager getRepositoryCacheManager() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java
index f409088..0810786 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java
@@ -16,14 +16,13 @@
 
 package org.gradle.api.internal.artifacts.repositories;
 
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
 import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
 
 public class LegacyMavenResolver extends LegacyDependencyResolver {
     private final MavenResolver resolver;
 
-    public LegacyMavenResolver(MavenResolver resolver, IvyAwareModuleVersionRepository repository) {
-        super(resolver, repository);
+    public LegacyMavenResolver(MavenResolver resolver) {
+        super(resolver);
         this.resolver = resolver;
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java
new file mode 100644
index 0000000..4bed715
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.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;
+
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+
+public interface PublicationAwareRepository {
+    ModuleVersionPublisher createPublisher();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java
index ceb2598..f901ab8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java
@@ -16,8 +16,11 @@
 
 package org.gradle.api.internal.artifacts.repositories;
 
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
 
 public interface ResolutionAwareRepository {
-    IvyAwareModuleVersionRepository createResolver();
+    /**
+     * Creates a resolver for this repository. Result may also implement {@link org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository}.
+     */
+    ConfiguredModuleVersionRepository createResolver();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
deleted file mode 100644
index d87c553..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
+++ /dev/null
@@ -1,86 +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.cachemanager;
-
-import org.apache.ivy.core.cache.ArtifactOrigin;
-import org.apache.ivy.core.cache.CacheMetadataOptions;
-import org.apache.ivy.core.cache.ModuleDescriptorWriter;
-import org.apache.ivy.core.cache.RepositoryCacheManager;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyContextualiser;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleScopedParserSettings;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ParserRegistry;
-import org.gradle.internal.UncheckedException;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.text.ParseException;
-
-abstract class AbstractRepositoryCacheManager implements RepositoryCacheManager {
-    protected final String name;
-    private final ParserRegistry parserRegistry = new ParserRegistry();
-
-    public AbstractRepositoryCacheManager(String name) {
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName, String artifactResolverName) {
-    }
-
-    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
-        return null;
-    }
-
-    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) {
-        return null;
-    }
-
-    public void originalToCachedModuleDescriptor(DependencyResolver resolver, ResolvedResource originalMetadataRef, Artifact requestedMetadataArtifact, ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
-    }
-
-    public void clean() {
-    }
-
-    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision) {
-    }
-
-    protected ModuleDescriptor parseModuleDescriptor(DependencyResolver resolver, Artifact moduleArtifact, CacheMetadataOptions options, File artifactFile, Resource resource) throws ParseException {
-        ModuleRevisionId moduleRevisionId = moduleArtifact.getId().getModuleRevisionId();
-        try {
-            IvySettings ivySettings = IvyContextualiser.getIvyContext().getSettings();
-            ParserSettings parserSettings = new ModuleScopedParserSettings(ivySettings, resolver, moduleRevisionId);
-            ModuleDescriptorParser parser = parserRegistry.forResource(resource);
-            return parser.parseDescriptor(parserSettings, new URL(artifactFile.toURI().toASCIIString()), resource, options.isValidate());
-        } catch (IOException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryArtifactCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryArtifactCache.java
new file mode 100644
index 0000000..bc8d22f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryArtifactCache.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+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.internal.Factory;
+import org.gradle.internal.filestore.FileStore;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A cache manager for remote repositories, that downloads files and stores them in the FileStore provided.
+ */
+public class DownloadingRepositoryArtifactCache implements RepositoryArtifactCache {
+
+    private final FileStore<ModuleVersionArtifactMetaData> fileStore;
+    private final CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final CacheLockingManager cacheLockingManager;
+
+    public DownloadingRepositoryArtifactCache(FileStore<ModuleVersionArtifactMetaData> fileStore, CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex,
+                                              TemporaryFileProvider temporaryFileProvider, CacheLockingManager cacheLockingManager) {
+        this.fileStore = fileStore;
+        this.artifactUrlCachedResolutionIndex = artifactUrlCachedResolutionIndex;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    public boolean isLocal() {
+        return false;
+    }
+
+    public LocallyAvailableExternalResource downloadAndCacheArtifactFile(final ModuleVersionArtifactMetaData artifact, ExternalResourceDownloader resourceDownloader, final ExternalResource resource) throws IOException {
+        final File tmpFile = temporaryFileProvider.createTemporaryFile("gradle_download", "bin");
+        try {
+            resourceDownloader.download(resource, tmpFile);
+            return cacheLockingManager.useCache(String.format("Store %s", artifact), new Factory<LocallyAvailableExternalResource>() {
+                public LocallyAvailableExternalResource create() {
+                    LocallyAvailableResource cachedResource = fileStore.move(artifact, tmpFile);
+                    File fileInFileStore = cachedResource.getFile();
+                    ExternalResourceMetaData metaData = resource.getMetaData();
+                    artifactUrlCachedResolutionIndex.store(metaData.getLocation(), fileInFileStore, metaData);
+                    return new DefaultLocallyAvailableExternalResource(resource.getName(), cachedResource, metaData);
+                }
+            });
+        } finally {
+            tmpFile.delete();
+        }
+    }
+
+}
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
deleted file mode 100644
index 94c9032..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
+++ /dev/null
@@ -1,164 +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.cachemanager;
-
-import org.apache.ivy.core.cache.ArtifactOrigin;
-import org.apache.ivy.core.cache.CacheDownloadOptions;
-import org.apache.ivy.core.cache.CacheMetadataOptions;
-import org.apache.ivy.core.cache.DownloadListener;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
-import org.apache.ivy.plugins.repository.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.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;
-import java.text.ParseException;
-
-/**
- * A cache manager for remote repositories, that downloads files and stores them in the FileStore provided.
- */
-public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheManager {
-    private final FileStore<ArtifactRevisionId> fileStore;
-    private final CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex;
-    private final TemporaryFileProvider temporaryFileProvider;
-    private final CacheLockingManager cacheLockingManager;
-
-    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 EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver,
-                                                   ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
-        EnhancedArtifactDownloadReport adr = new EnhancedArtifactDownloadReport(artifact);
-
-        DownloadListener listener = options.getListener();
-        if (listener != null) {
-            listener.needArtifact(this, artifact);
-        }
-
-        long start = System.currentTimeMillis();
-        try {
-            ResolvedResource artifactRef = resourceResolver.resolve(artifact);
-            if (artifactRef != null) {
-                final Resource resource = artifactRef.getResource();
-                ArtifactOrigin origin = new ArtifactOrigin(artifact, resource.isLocal(), resource.getName());
-                if (listener != null) {
-                    listener.startArtifactDownload(this, artifactRef, artifact, origin);
-                }
-
-                File artifactFile = downloadArtifactFile(artifact, resourceDownloader, artifactRef);
-
-                adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
-                adr.setSize(artifactFile.length());
-                adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
-                adr.setArtifactOrigin(origin);
-                adr.setLocalFile(artifactFile);
-            } else {
-                adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
-                adr.setDownloadStatus(DownloadStatus.FAILED);
-                adr.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
-            }
-        } catch (Throwable throwable) {
-            adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
-            adr.failed(throwable);
-        }
-        if (listener != null) {
-            listener.endArtifactDownload(this, artifact, adr, adr.getLocalFile());
-        }
-        return adr;
-    }
-
-    private File downloadArtifactFile(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();
-        }
-    }
-
-    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, final ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
-        if (!moduleArtifact.isMetadata()) {
-            return null;
-        }
-
-        ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
-            public ResolvedResource resolve(Artifact artifact) {
-                return resolvedResource;
-            }
-        };
-        ArtifactDownloadReport report = download(moduleArtifact, artifactResourceResolver, downloader, new CacheDownloadOptions().setListener(options.getListener()).setForce(true));
-
-        if (report.getDownloadStatus() == DownloadStatus.FAILED) {
-            Message.warn("problem while downloading module descriptor: " + resolvedResource.getResource()
-                    + ": " + report.getDownloadDetails()
-                    + " (" + report.getDownloadTimeMillis() + "ms)");
-            return null;
-        }
-
-        ModuleDescriptor md = parseModuleDescriptor(resolver, moduleArtifact, options, report.getLocalFile(), resolvedResource.getResource());
-        Message.debug("\t" + getName() + ": parsed downloaded md file for " + moduleArtifact.getModuleRevisionId() + "; parsed=" + md.getModuleRevisionId());
-
-        MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(md.getMetadataArtifact());
-        madr.setSearched(true);
-        madr.setDownloadStatus(report.getDownloadStatus());
-        madr.setDownloadDetails(report.getDownloadDetails());
-        madr.setArtifactOrigin(report.getArtifactOrigin());
-        madr.setDownloadTimeMillis(report.getDownloadTimeMillis());
-        madr.setOriginalLocalFile(report.getLocalFile());
-        madr.setSize(report.getSize());
-
-        return new ResolvedModuleRevision(resolver, resolver, md, madr);
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java
deleted file mode 100644
index 3d270bf..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/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.cachemanager;
-
-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/cachemanager/LocalFileRepositoryArtifactCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryArtifactCache.java
new file mode 100644
index 0000000..07d923d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryArtifactCache.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A cache manager for local repositories. Doesn't cache anything, and uses artifacts from their origin.
+ */
+public class LocalFileRepositoryArtifactCache implements RepositoryArtifactCache {
+
+    public boolean isLocal() {
+        return true;
+    }
+
+    public LocallyAvailableExternalResource downloadAndCacheArtifactFile(ModuleVersionArtifactMetaData artifactId, ExternalResourceDownloader resourceDownloader, ExternalResource resource) throws IOException {
+        // Does not download, copy or cache local files.
+        assert resource.isLocal();
+        File file = new File(resource.getName());
+        assert file.isFile();
+        return new DefaultLocallyAvailableExternalResource(resource.getName(), new DefaultLocallyAvailableResource(file));
+    }
+}
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
deleted file mode 100644
index 5ac65cd..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
+++ /dev/null
@@ -1,88 +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.cachemanager;
-
-import org.apache.ivy.core.cache.ArtifactOrigin;
-import org.apache.ivy.core.cache.CacheDownloadOptions;
-import org.apache.ivy.core.cache.CacheMetadataOptions;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
-import org.apache.ivy.plugins.repository.ResourceDownloader;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-
-import java.io.File;
-import java.text.ParseException;
-
-/**
- * A cache manager for local repositories. Doesn't cache anything, and uses artifacts from their origin.
- */
-public class LocalFileRepositoryCacheManager extends AbstractRepositoryCacheManager {
-
-    public LocalFileRepositoryCacheManager(String name) {
-        super(name);
-    }
-
-    public EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
-        long start = System.currentTimeMillis();
-        EnhancedArtifactDownloadReport report = new EnhancedArtifactDownloadReport(artifact);
-        ResolvedResource resolvedResource = resourceResolver.resolve(artifact);
-        if (resolvedResource == null) {
-            report.setDownloadStatus(DownloadStatus.FAILED);
-            report.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
-            report.setDownloadTimeMillis(System.currentTimeMillis() - start);
-            return report;
-        }
-        assert resolvedResource.getResource().isLocal();
-        File file = new File(resolvedResource.getResource().getName());
-        assert file.isFile();
-
-        ArtifactOrigin origin = new ArtifactOrigin(artifact, true, file.getAbsolutePath());
-        report.setDownloadStatus(DownloadStatus.NO);
-        report.setArtifactOrigin(origin);
-        report.setSize(file.length());
-        report.setLocalFile(file);
-        return report;
-    }
-
-    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
-        if (!moduleArtifact.isMetadata()) {
-            return null;
-        }
-
-        assert resolvedResource.getResource().isLocal();
-        File file = new File(resolvedResource.getResource().getName());
-        assert file.isFile();
-
-        ArtifactOrigin origin = new ArtifactOrigin(moduleArtifact, true, file.getAbsolutePath());
-        MetadataArtifactDownloadReport report = new MetadataArtifactDownloadReport(moduleArtifact);
-        report.setDownloadStatus(DownloadStatus.NO);
-        report.setArtifactOrigin(origin);
-        report.setSize(file.length());
-        report.setLocalFile(file);
-        report.setSearched(false);
-        report.setOriginalLocalFile(file);
-
-        ModuleDescriptor descriptor = parseModuleDescriptor(resolver, moduleArtifact, options, file, resolvedResource.getResource());
-        return new ResolvedModuleRevision(resolver, resolver, descriptor, report);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/RepositoryArtifactCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/RepositoryArtifactCache.java
new file mode 100644
index 0000000..0a23579
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/RepositoryArtifactCache.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories.cachemanager;
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This is a transitional interface for moving away from the Ivy RepositoryCacheManager.
+ */
+public interface RepositoryArtifactCache {
+    boolean isLocal();
+
+    /**
+     * Downloads the artifact file, moving it into the correct location in the cache.
+     *
+     * @param artifact The id of the artifact this resource represents
+     * @param resourceDownloader An action to use for downloading the resource
+     * @param resource The artifact resource
+     * @return The cached resource
+     */
+    LocallyAvailableExternalResource downloadAndCacheArtifactFile(ModuleVersionArtifactMetaData artifact, ExternalResourceDownloader resourceDownloader, ExternalResource resource) throws IOException;
+
+    interface ExternalResourceDownloader {
+        public void download(ExternalResource resource, File dest) throws IOException;
+    }
+}
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 ae57ac4..97156fd 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
@@ -27,7 +27,7 @@ import java.net.URI;
  *     <li>Ivy: $baseUri/{@value IvyArtifactRepository#MAVEN_IVY_PATTERN}</li>
  * </ul>
  *
- * Following the maven convention, the 'organisation' value is further processed by replacing '.' with '/'.
+ * Following the Maven convention, the 'organisation' value is further processed by replacing '.' with '/'.
  * Note that the resolver will follow the layout only, but will <em>not</em> use .pom files for meta data. Ivy metadata files are required/published.
  */
 public class MavenRepositoryLayout extends RepositoryLayout {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/AbstractRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/AbstractRepositoryCacheManager.java
new file mode 100644
index 0000000..bbf1446
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/AbstractRepositoryCacheManager.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.cache.ModuleDescriptorWriter;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyContextualiser;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+
+abstract class AbstractRepositoryCacheManager implements RepositoryCacheManager {
+    protected final String name;
+
+    public AbstractRepositoryCacheManager(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName, String artifactResolverName) {
+    }
+
+    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
+        return null;
+    }
+
+    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) {
+        return null;
+    }
+
+    public void originalToCachedModuleDescriptor(DependencyResolver resolver, ResolvedResource originalMetadataRef, Artifact requestedMetadataArtifact, ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
+    }
+
+    public void clean() {
+    }
+
+    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision) {
+    }
+
+    protected ModuleDescriptor parseModuleDescriptor(DependencyResolver resolver, Artifact moduleArtifact, CacheMetadataOptions options, File artifactFile, Resource resource) throws ParseException {
+        ModuleRevisionId moduleRevisionId = moduleArtifact.getId().getModuleRevisionId();
+        try {
+            IvySettings ivySettings = IvyContextualiser.getIvyContext().getSettings();
+            ParserSettings parserSettings = new LegacyResolverParserSettings(ivySettings, resolver, moduleRevisionId);
+            ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(resource);
+            return parser.parseDescriptor(parserSettings, new URL(artifactFile.toURI().toASCIIString()), resource, options.isValidate());
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/CustomIvyResolverRepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/CustomIvyResolverRepositoryFactory.java
new file mode 100644
index 0000000..541cb0e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/CustomIvyResolverRepositoryFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.legacy;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.logging.ProgressLoggerFactory;
+
+public class CustomIvyResolverRepositoryFactory implements LegacyDependencyResolverRepositoryFactory {
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private final RepositoryCacheManager localCacheManager;
+    private final RepositoryCacheManager downloadingCacheManager;
+
+    public CustomIvyResolverRepositoryFactory(ProgressLoggerFactory progressLoggerFactory,
+                                              RepositoryCacheManager localCacheManager,
+                                              RepositoryCacheManager downloadingCacheManager) {
+        this.progressLoggerFactory = progressLoggerFactory;
+        this.localCacheManager = localCacheManager;
+        this.downloadingCacheManager = downloadingCacheManager;
+    }
+
+    public ArtifactRepository createRepository(DependencyResolver dependencyResolver) {
+        return new CustomResolverArtifactRepository(dependencyResolver, progressLoggerFactory, localCacheManager, downloadingCacheManager);
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/CustomResolverArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/CustomResolverArtifactRepository.java
new file mode 100644
index 0000000..abcef81
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/CustomResolverArtifactRepository.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+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.ProgressLoggingTransferListener;
+import org.gradle.logging.ProgressLoggerFactory;
+
+import java.util.List;
+
+public class CustomResolverArtifactRepository extends FixedResolverArtifactRepository {
+    private final TransferListener transferListener;
+    private final RepositoryCacheManager downloadingCacheManager;
+    private final RepositoryCacheManager localCacheManager;
+
+    public CustomResolverArtifactRepository(DependencyResolver resolver, ProgressLoggerFactory progressLoggerFactory,
+                                            RepositoryCacheManager localCacheManager, RepositoryCacheManager downloadingCacheManager) {
+        super(resolver);
+        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(downloadingCacheManager);
+            }
+        }
+
+        if (dependencyResolver instanceof FileSystemResolver) {
+            ((FileSystemResolver) dependencyResolver).setLocal(true);
+            ((FileSystemResolver) dependencyResolver).setRepositoryCacheManager(localCacheManager);
+        }
+        if (dependencyResolver instanceof RepositoryResolver) {
+            Repository repository = ((RepositoryResolver) dependencyResolver).getRepository();
+            if (!repository.hasTransferListener(transferListener)) {
+                repository.addTransferListener(transferListener);
+            }
+        }
+        if (dependencyResolver instanceof DualResolver) {
+            DualResolver dualResolver = (DualResolver) dependencyResolver;
+            configureResolver(dualResolver.getIvyResolver(), false);
+            configureResolver(dualResolver.getArtifactResolver(), false);
+        }
+        if (dependencyResolver instanceof ChainResolver) {
+            ChainResolver chainResolver = (ChainResolver) dependencyResolver;
+            @SuppressWarnings("unchecked") List<DependencyResolver> resolvers = chainResolver.getResolvers();
+            for (DependencyResolver resolver : resolvers) {
+                configureResolver(resolver, false);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/DownloadingRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/DownloadingRepositoryCacheManager.java
new file mode 100644
index 0000000..9d88092
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/DownloadingRepositoryCacheManager.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.cache.DownloadListener;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.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.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.internal.Factory;
+import org.gradle.internal.filestore.FileStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+/**
+ * A cache manager for remote repositories, that downloads files and stores them in the FileStore provided.
+ */
+public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheManager {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DownloadingRepositoryCacheManager.class);
+
+    private final FileStore<ModuleVersionArtifactMetaData> fileStore;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final CacheLockingManager cacheLockingManager;
+
+    public DownloadingRepositoryCacheManager(String name, FileStore<ModuleVersionArtifactMetaData> fileStore,
+                                             TemporaryFileProvider temporaryFileProvider, CacheLockingManager cacheLockingManager) {
+        super(name);
+        this.fileStore = fileStore;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    public boolean isLocal() {
+        return false;
+    }
+
+    public EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver,
+                                                   ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+        EnhancedArtifactDownloadReport adr = new EnhancedArtifactDownloadReport(artifact);
+
+        DownloadListener listener = options.getListener();
+        if (listener != null) {
+            listener.needArtifact(this, artifact);
+        }
+
+        long start = System.currentTimeMillis();
+        try {
+            ResolvedResource artifactRef = resourceResolver.resolve(artifact);
+            if (artifactRef != null) {
+                final Resource resource = artifactRef.getResource();
+                ArtifactOrigin origin = new ArtifactOrigin(artifact, resource.isLocal(), resource.getName());
+                if (listener != null) {
+                    listener.startArtifactDownload(this, artifactRef, artifact, origin);
+                }
+
+                ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
+                ModuleComponentIdentifier componentIdentifier = DefaultModuleComponentIdentifier.newId(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+                File artifactFile = downloadAndCacheArtifactFile(new DefaultModuleVersionArtifactMetaData(componentIdentifier, artifact), artifact, resourceDownloader, artifactRef.getResource());
+
+                adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                adr.setSize(artifactFile.length());
+                adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
+                adr.setArtifactOrigin(origin);
+                adr.setLocalFile(artifactFile);
+            } else {
+                adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                adr.setDownloadStatus(DownloadStatus.FAILED);
+                adr.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
+            }
+        } catch (Throwable throwable) {
+            adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+            adr.failed(throwable);
+        }
+        if (listener != null) {
+            listener.endArtifactDownload(this, artifact, adr, adr.getLocalFile());
+        }
+        return adr;
+    }
+
+    private File downloadAndCacheArtifactFile(final ModuleVersionArtifactMetaData id, Artifact artifact, ResourceDownloader resourceDownloader, Resource resource) throws IOException {
+        final File tmpFile = temporaryFileProvider.createTemporaryFile("gradle_download", "bin");
+        try {
+            resourceDownloader.download(artifact, resource, tmpFile);
+            return cacheLockingManager.useCache(String.format("Store %s", id), new Factory<File>() {
+                public File create() {
+                    return fileStore.move(id, tmpFile).getFile();
+                }
+            });
+        } finally {
+            tmpFile.delete();
+        }
+    }
+
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, final ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
+        if (!moduleArtifact.isMetadata()) {
+            return null;
+        }
+
+        ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
+            public ResolvedResource resolve(Artifact artifact) {
+                return resolvedResource;
+            }
+        };
+        ArtifactDownloadReport report = download(moduleArtifact, artifactResourceResolver, downloader, new CacheDownloadOptions().setListener(options.getListener()).setForce(true));
+
+        if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+            LOGGER.warn("problem while downloading module descriptor: {}: {} ({} ms)", resolvedResource.getResource(), report.getDownloadDetails(), report.getDownloadTimeMillis());
+            return null;
+        }
+
+        ModuleDescriptor md = parseModuleDescriptor(resolver, moduleArtifact, options, report.getLocalFile(), resolvedResource.getResource());
+        LOGGER.debug("\t{}: parsed downloaded md file for {}; parsed={}" + getName(), moduleArtifact.getModuleRevisionId(), md.getModuleRevisionId());
+
+        MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(md.getMetadataArtifact());
+        madr.setSearched(true);
+        madr.setDownloadStatus(report.getDownloadStatus());
+        madr.setDownloadDetails(report.getDownloadDetails());
+        madr.setArtifactOrigin(report.getArtifactOrigin());
+        madr.setDownloadTimeMillis(report.getDownloadTimeMillis());
+        madr.setOriginalLocalFile(report.getLocalFile());
+        madr.setSize(report.getSize());
+
+        return new ResolvedModuleRevision(resolver, resolver, md, madr);
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/EnhancedArtifactDownloadReport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/EnhancedArtifactDownloadReport.java
new file mode 100644
index 0000000..50ff9b9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/EnhancedArtifactDownloadReport.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+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/legacy/FixedResolverArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/FixedResolverArtifactRepository.java
new file mode 100644
index 0000000..e714d32
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/FixedResolverArtifactRepository.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.IvyResolverBackedModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
+import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
+
+public class FixedResolverArtifactRepository extends AbstractArtifactRepository {
+    protected final DependencyResolver resolver;
+
+    public FixedResolverArtifactRepository(DependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public String getName() {
+        return resolver.getName();
+    }
+
+    public void setName(String name) {
+        resolver.setName(name);
+
+        // 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 ModuleVersionPublisher createPublisher() {
+        return new IvyResolverBackedModuleVersionPublisher(resolver);
+    }
+
+    public ConfiguredModuleVersionRepository createResolver() {
+        // Handle a repository wrapped in a resolver for backwards compatibility
+        if (resolver instanceof ResolutionAwareRepository) {
+            ResolutionAwareRepository resolutionAwareRepository = (ResolutionAwareRepository) resolver;
+            return resolutionAwareRepository.createResolver();
+        }
+        return new IvyDependencyResolverAdapter(resolver);
+    }
+
+    public DependencyResolver createLegacyDslObject() {
+        return resolver;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/IvyDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/IvyDependencyResolverAdapter.java
new file mode 100644
index 0000000..968db04
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/IvyDependencyResolverAdapter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.Artifact;
+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.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.resolution.JvmLibraryJavadocArtifact;
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.*;
+import org.gradle.api.internal.artifacts.metadata.*;
+import org.gradle.api.internal.artifacts.resolution.IvyDescriptorArtifact;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A {@link org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository} wrapper around an Ivy {@link DependencyResolver}.
+ */
+public class IvyDependencyResolverAdapter implements ConfiguredModuleVersionRepository, IvyAwareModuleVersionRepository, LocalArtifactsModuleVersionRepository {
+    private static final Logger LOGGER = LoggerFactory.getLogger(IvyDependencyResolverAdapter.class);
+    private final DownloadOptions downloadOptions = new DownloadOptions();
+    private final String identifier;
+    private final DependencyResolver resolver;
+    private ResolveData resolveData;
+
+    public IvyDependencyResolverAdapter(DependencyResolver resolver) {
+        this.resolver = resolver;
+        identifier = DependencyResolverIdentifier.forIvyResolver(resolver);
+    }
+
+    public String getId() {
+        return identifier;
+    }
+
+    public String getName() {
+        return resolver.getName();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Repository '%s'", resolver.getName());
+    }
+
+    public boolean isLocal() {
+        return resolver.getRepositoryCacheManager() instanceof LocalFileRepositoryCacheManager;
+    }
+
+    public void setSettings(IvySettings settings) {
+        settings.addResolver(resolver);
+    }
+
+    public void setResolveData(ResolveData resolveData) {
+        this.resolveData = resolveData;
+    }
+
+    public boolean isDynamicResolveMode() {
+        return false;
+    }
+
+    public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        IvyContext.getContext().setResolveData(resolveData);
+        try {
+            ResolvedModuleRevision revision = resolver.getDependency(dependency.getDescriptor(), resolveData);
+            if (revision == null) {
+                result.listed(new DefaultModuleVersionListing());
+            } else {
+                result.listed(new DefaultModuleVersionListing(revision.getId().getRevision()));
+            }
+        } catch (ParseException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+        IvyContext.getContext().setResolveData(resolveData);
+        try {
+            ResolvedModuleRevision revision = resolver.getDependency(dependency.getDescriptor(), resolveData);
+            if (revision == null) {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependency.getRequested(), getName());
+                result.missing();
+            } else {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependency.getRequested(), getName());
+                ModuleDescriptorAdapter metaData = new ModuleDescriptorAdapter(revision.getDescriptor());
+                metaData.setChanging(isChanging(revision));
+                result.resolved(metaData, null);
+            }
+        } catch (ParseException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void resolveArtifact(ComponentArtifactMetaData artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        Artifact ivyArtifact = ((ModuleVersionArtifactMetaData) artifact).toIvyArtifact();
+        ArtifactDownloadReport artifactDownloadReport = resolver.download(new Artifact[]{ivyArtifact}, downloadOptions).getArtifactReport(ivyArtifact);
+        if (downloadFailed(artifactDownloadReport)) {
+            if (artifactDownloadReport instanceof EnhancedArtifactDownloadReport) {
+                EnhancedArtifactDownloadReport enhancedReport = (EnhancedArtifactDownloadReport) artifactDownloadReport;
+                result.failed(new ArtifactResolveException(artifact.getId(), enhancedReport.getFailure()));
+            } else {
+                result.failed(new ArtifactResolveException(artifact.getId(), artifactDownloadReport.getDownloadDetails()));
+            }
+            return;
+        }
+
+        File localFile = artifactDownloadReport.getLocalFile();
+        if (localFile != null) {
+            result.resolved(localFile);
+        } else {
+            result.notFound(artifact.getId());
+        }
+    }
+
+    public void localResolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        doResolveModuleArtifacts(component, context, result, true);
+    }
+
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        doResolveModuleArtifacts(component, context, result, false);
+    }
+
+    // TODO:DAZ This "local-only" pattern is quite ugly: improve it.
+    private void doResolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        ModuleVersionMetaData moduleVersion = (ModuleVersionMetaData) component;
+        if (context instanceof ConfigurationResolveContext) {
+            String configurationName = ((ConfigurationResolveContext) context).getConfigurationName();
+            result.resolved(component.getConfiguration(configurationName).getArtifacts());
+            return;
+        }
+
+        if (!localOnly && context instanceof ArtifactTypeResolveContext) {
+            Class<? extends SoftwareArtifact> artifactType = ((ArtifactTypeResolveContext) context).getArtifactType();
+            try {
+                result.resolved(doGetCandidateArtifacts(moduleVersion, artifactType));
+            } catch (Exception e) {
+                result.failed(new ArtifactResolveException(component.getComponentId(), e));
+            }
+        }
+    }
+
+    private Set<ModuleVersionArtifactMetaData> doGetCandidateArtifacts(ModuleVersionMetaData module, Class<? extends SoftwareArtifact> artifactType) {
+        if (artifactType == IvyDescriptorArtifact.class) {
+            Artifact metadataArtifact = module.getDescriptor().getMetadataArtifact();
+            return ImmutableSet.of(module.artifact(metadataArtifact));
+        }
+
+        if (artifactType == JvmLibraryJavadocArtifact.class) {
+            return createArtifactMetaData(module, "javadoc", "javadoc");
+        }
+
+        if (artifactType == JvmLibrarySourcesArtifact.class) {
+            return createArtifactMetaData(module, "source", "sources");
+        }
+
+        throw new IllegalArgumentException(String.format("Cannot find artifacts of type %s in %s", artifactType.getName(), module));
+    }
+
+    private Set<ModuleVersionArtifactMetaData> createArtifactMetaData(ModuleVersionMetaData module, String type, String classifier) {
+        ModuleVersionArtifactMetaData artifact = module.artifact(type, "jar", classifier);
+        if (resolver.exists(artifact.toIvyArtifact())) {
+            return ImmutableSet.of(artifact);
+        }
+        return Collections.emptySet();
+    }
+
+    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);
+    }
+
+    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/repositories/legacy/LegacyDependencyResolverRepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LegacyDependencyResolverRepositoryFactory.java
new file mode 100644
index 0000000..9a27910
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LegacyDependencyResolverRepositoryFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.legacy;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+
+public interface LegacyDependencyResolverRepositoryFactory {
+    ArtifactRepository createRepository(DependencyResolver dependencyResolver);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LegacyResolverParserSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LegacyResolverParserSettings.java
new file mode 100644
index 0000000..8030383
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LegacyResolverParserSettings.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * ParserSettings that control the scope of searches carried out during parsing.
+ * If the parser asks for a resolver for the currently resolving revision, the resolver scope is only the repository where the module was resolved.
+ * If the parser asks for a resolver for a different revision, the resolver scope is all repositories.
+ */
+public class LegacyResolverParserSettings implements ParserSettings {
+    private final ParserSettings settings;
+    private final DependencyResolver currentResolver;
+    private final ModuleRevisionId currentRevisionId;
+
+    public LegacyResolverParserSettings(ParserSettings settings, DependencyResolver currentResolver, ModuleRevisionId currentRevisionId) {
+        this.settings = settings;
+        this.currentResolver = currentResolver;
+        this.currentRevisionId = currentRevisionId;
+    }
+
+    public DependencyResolver getResolver(ModuleRevisionId mRevId) {
+        if (mRevId.equals(currentRevisionId)) {
+            return currentResolver;
+        }
+        return settings.getResolver(mRevId);
+    }
+
+    public ConflictManager getConflictManager(String name) {
+        return settings.getConflictManager(name);
+    }
+
+    public String substitute(String value) {
+        return settings.substitute(value);
+    }
+
+    public Map substitute(Map strings) {
+        return settings.substitute(strings);
+    }
+
+    public ResolutionCacheManager getResolutionCacheManager() {
+        return settings.getResolutionCacheManager();
+    }
+
+    public PatternMatcher getMatcher(String matcherName) {
+        return settings.getMatcher(matcherName);
+    }
+
+    public Namespace getNamespace(String namespace) {
+        return settings.getNamespace(namespace);
+    }
+
+    public StatusManager getStatusManager() {
+        return settings.getStatusManager();
+    }
+
+    public RelativeUrlResolver getRelativeUrlResolver() {
+        return settings.getRelativeUrlResolver();
+    }
+
+    public File resolveFile(String filename) {
+        return settings.resolveFile(filename);
+    }
+
+    public String getDefaultBranch(ModuleId moduleId) {
+        return settings.getDefaultBranch(moduleId);
+    }
+
+    public Namespace getContextNamespace() {
+        return settings.getContextNamespace();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LocalFileRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LocalFileRepositoryCacheManager.java
new file mode 100644
index 0000000..2e1520c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/legacy/LocalFileRepositoryCacheManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories.legacy;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+import java.io.File;
+import java.text.ParseException;
+
+/**
+ * A cache manager for local repositories. Doesn't cache anything, and uses artifacts from their origin.
+ */
+public class LocalFileRepositoryCacheManager extends AbstractRepositoryCacheManager {
+
+    public LocalFileRepositoryCacheManager(String name) {
+        super(name);
+    }
+
+    public EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+        ResolvedResource resolvedResource = resourceResolver.resolve(artifact);
+        long start = System.currentTimeMillis();
+        EnhancedArtifactDownloadReport report = new EnhancedArtifactDownloadReport(artifact);
+        if (resolvedResource == null) {
+            report.setDownloadStatus(DownloadStatus.FAILED);
+            report.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
+            report.setDownloadTimeMillis(System.currentTimeMillis() - start);
+            return report;
+        }
+        assert resolvedResource.getResource().isLocal();
+        File file = new File(resolvedResource.getResource().getName());
+        assert file.isFile();
+
+        ArtifactOrigin origin = new ArtifactOrigin(artifact, true, file.getAbsolutePath());
+        report.setDownloadStatus(DownloadStatus.NO);
+        report.setArtifactOrigin(origin);
+        report.setSize(file.length());
+        report.setLocalFile(file);
+        return report;
+    }
+
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
+        if (!moduleArtifact.isMetadata()) {
+            return null;
+        }
+
+        assert resolvedResource.getResource().isLocal();
+        File file = new File(resolvedResource.getResource().getName());
+        assert file.isFile();
+
+        ArtifactOrigin origin = new ArtifactOrigin(moduleArtifact, true, file.getAbsolutePath());
+        MetadataArtifactDownloadReport report = new MetadataArtifactDownloadReport(moduleArtifact);
+        report.setDownloadStatus(DownloadStatus.NO);
+        report.setArtifactOrigin(origin);
+        report.setSize(file.length());
+        report.setLocalFile(file);
+        report.setSearched(false);
+        report.setOriginalLocalFile(file);
+
+        ModuleDescriptor descriptor = parseModuleDescriptor(resolver, moduleArtifact, options, file, resolvedResource.getResource());
+        return new ResolvedModuleRevision(resolver, resolver, descriptor, report);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/AbstractVersionList.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/AbstractVersionList.java
index 695ecbe..f423aae 100644
--- 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
@@ -16,48 +16,22 @@
 
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
-import org.apache.ivy.plugins.latest.ArtifactInfo;
-import org.apache.ivy.plugins.latest.LatestStrategy;
+import com.google.common.collect.Lists;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 abstract class AbstractVersionList implements VersionList {
     public boolean isEmpty() {
-        return getVersionStrings().isEmpty();
+        return getVersions().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);
+    public List<ListedVersion> sortLatestFirst(LatestStrategy latestStrategy) {
+        List<ListedVersion> versions = Lists.newArrayList(getVersions());
+        List<ListedVersion> sorted = latestStrategy.sort(versions);
         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;
-        }
+        return sorted;
     }
 }
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
index fb1657c..45f6af0 100644
--- 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
@@ -16,8 +16,8 @@
 
 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.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 import org.gradle.api.internal.resource.ResourceException;
 import org.gradle.api.internal.resource.ResourceNotFoundException;
 import org.gradle.util.DeprecationLogger;
@@ -35,13 +35,13 @@ public class ChainedVersionLister implements VersionLister {
         this.versionListers = Arrays.asList(versionlisters);
     }
 
-    public VersionList getVersionList(final ModuleRevisionId moduleRevisionId)  {
+    public VersionList getVersionList(final ModuleIdentifier module)  {
         final List<VersionList> versionLists = new ArrayList<VersionList>();
         for (VersionLister lister : versionListers) {
-            versionLists.add(lister.getVersionList(moduleRevisionId));
+            versionLists.add(lister.getVersionList(module));
         }
         return new AbstractVersionList() {
-            public void visit(ResourcePattern pattern, Artifact artifact) throws ResourceNotFoundException, ResourceException {
+            public void visit(ResourcePattern pattern, ModuleVersionArtifactMetaData artifact) throws ResourceException {
                 final Iterator<VersionList> versionListIterator = versionLists.iterator();
                 while (versionListIterator.hasNext()) {
                     VersionList list = versionListIterator.next();
@@ -56,21 +56,21 @@ public class ChainedVersionLister implements VersionLister {
                         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()
+                                    module, list.getClass()
                             );
                             DeprecationLogger.nagUserOfDeprecatedBehaviour(deprecationMessage);
                             LOGGER.debug(deprecationMessage, e);
                         } else {
-                            throw new ResourceException(String.format("Failed to list versions for %s.", moduleRevisionId), e);
+                            throw new ResourceException(String.format("Failed to list versions for %s.", module), e);
                         }
                     }
                 }
             }
 
-            public Set<String> getVersionStrings() {
-                Set<String> allVersions = new HashSet<String>();
+            public Set<ListedVersion> getVersions() {
+                Set<ListedVersion> allVersions = new HashSet<ListedVersion>();
                 for (VersionList versionList : versionLists) {
-                    allVersions.addAll(versionList.getVersionStrings());
+                    allVersions.addAll(versionList.getVersions());
                 }
                 return allVersions;
             }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ComponentMetadataDetailsAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ComponentMetadataDetailsAdapter.java
new file mode 100644
index 0000000..abb1756
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ComponentMetadataDetailsAdapter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.artifacts.ComponentMetadataDetails;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+
+import java.util.List;
+
+public class ComponentMetadataDetailsAdapter implements ComponentMetadataDetails {
+    private final MutableModuleVersionMetaData metadata;
+
+    public ComponentMetadataDetailsAdapter(MutableModuleVersionMetaData metadata) {
+        this.metadata = metadata;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return metadata.getId();
+    }
+
+    public boolean isChanging() {
+        return metadata.isChanging();
+    }
+
+    public String getStatus() {
+        return metadata.getStatus();
+    }
+
+    public List<String> getStatusScheme() {
+        return metadata.getStatusScheme();
+    }
+
+    public void setChanging(boolean changing) {
+        metadata.setChanging(changing);
+    }
+
+    public void setStatus(String status) {
+        metadata.setStatus(status);
+    }
+
+    public void setStatusScheme(List<String> statusScheme) {
+        metadata.setStatusScheme(statusScheme);
+    }
+}
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
index 9eafb22..8f416d5 100644
--- 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
@@ -16,34 +16,22 @@
 
 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.HashMap;
 import java.util.HashSet;
-import java.util.List;
+import java.util.Map;
 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 abstract class DefaultVersionList extends AbstractVersionList {
+    private final Map<String, ListedVersion> versions = new HashMap<String, ListedVersion>();
 
-    public void visit(ResourcePattern pattern, Artifact artifact) throws ResourceNotFoundException, ResourceException {
-        throw new UnsupportedOperationException();
+    protected void add(ListedVersion newVersion) {
+        if (versions.containsKey(newVersion.getVersion())) {
+            return;
+        }
+        versions.put(newVersion.getVersion(), newVersion);
     }
 
-    public Set<String> getVersionStrings() {
-        return versions;
+    public Set<ListedVersion> getVersions() {
+        return new HashSet<ListedVersion>(versions.values());
     }
 }
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
index 3dfdda8..fd094f9 100644
--- 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
@@ -16,107 +16,90 @@
 
 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.CacheMetadataOptions;
-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.latest.LatestStrategy;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import org.apache.ivy.core.settings.IvySettings;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.namespace.Namespace;
-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.DependencyResolver;
-import org.apache.ivy.plugins.resolver.ResolverSettings;
-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.ChecksumHelper;
-import org.apache.ivy.util.Message;
-import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaData;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
-import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
+import org.gradle.api.Nullable;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.resolution.JvmLibraryJavadocArtifact;
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParseException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.*;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.RepositoryArtifactCache;
 import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
 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.gradle.util.GFileUtils;
+import org.gradle.internal.SystemProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
-import java.text.ParseException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
 
-public class ExternalResourceResolver implements DependencyResolver {
+import static org.gradle.api.internal.artifacts.repositories.cachemanager.RepositoryArtifactCache.ExternalResourceDownloader;
+
+public abstract class ExternalResourceResolver implements ModuleVersionPublisher, ConfiguredModuleVersionRepository, LocalArtifactsModuleVersionRepository {
     private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceResolver.class);
 
+    private final MetaDataParser metaDataParser;
+
     private List<String> ivyPatterns = new ArrayList<String>();
     private List<String> artifactPatterns = new ArrayList<String>();
-    private boolean m2compatible;
-    private boolean checkconsistency = true;
-    private boolean allownomd = true;
+    private boolean m2Compatible;
+    private boolean checkConsistency = true;
+    private boolean allowMissingDescriptor = true;
     private boolean force;
     private String checksums;
     private String name;
-    private ResolverSettings settings;
-    private LatestStrategy latestStrategy;
-    private String latestStrategyName;
-    private String cacheManagerName;
-    private RepositoryCacheManager repositoryCacheManager;
+    private RepositoryArtifactCache repositoryCacheManager;
     private String changingMatcherName;
     private String changingPattern;
-    private Boolean checkmodified;
+    private RepositoryChain repositoryChain;
 
     private final ExternalResourceRepository repository;
-    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder;
+    private final ResolverStrategy resolverStrategy;
+
     protected VersionLister versionLister;
-    private ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
-        public ResolvedResource resolve(Artifact artifact) {
-            return getArtifactRef(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
-    ) {
+                                    LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                                    MetaDataParser metaDataParser,
+                                    ResolverStrategy resolverStrategy) {
         this.name = name;
         this.versionLister = versionLister;
         this.repository = repository;
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.metaDataParser = metaDataParser;
+        this.resolverStrategy = resolverStrategy;
+    }
+
+    public String getId() {
+        return DependencyResolverIdentifier.forExternalResourceResolver(this);
     }
 
     public String getName() {
@@ -127,525 +110,295 @@ public class ExternalResourceResolver implements DependencyResolver {
         this.name = name;
     }
 
-    public String toString() {
-        return getName();
+    public boolean isDynamicResolveMode() {
+        return false;
     }
 
-    public void setSettings(ResolverSettings ivy) {
-        this.settings = ivy;
+    public String toString() {
+        return String.format("Repository '%s'", getName());
     }
 
-    public ResolverSettings getSettings() {
-        return settings;
+    public void setRepositoryChain(RepositoryChain resolver) {
+        this.repositoryChain = resolver;
     }
 
     protected ExternalResourceRepository getRepository() {
         return repository;
     }
 
-    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
-        // This is not used
-        throw new UnsupportedOperationException();
+    public boolean isLocal() {
+        return repositoryCacheManager.isLocal();
     }
 
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionMetaData result) {
-        ModuleRevisionId nsMrid = dependencyDescriptor.getDependencyRevisionId();
+    public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionSelectionResolveResult result) {
+        ModuleIdentifier module  = new DefaultModuleIdentifier(dependency.getRequested().getGroup(), dependency.getRequested().getName());
+        VersionList versionList = versionLister.getVersionList(module);
+        // List modules based on metadata files
+        ModuleVersionArtifactMetaData metaDataArtifact = getMetaDataArtifactFor(dependency);
+        listVersionsForAllPatterns(getIvyPatterns(), metaDataArtifact, versionList);
 
-        boolean isDynamic = getVersionMatcher().isDynamic(nsMrid);
-
-        ResolvedResource ivyRef = findIvyFileRef(dependencyDescriptor);
-
-        // get module descriptor
-        ModuleDescriptor nsMd;
-        if (ivyRef == null) {
-            if (!isAllownomd()) {
-                LOGGER.debug("No ivy file found for module '{}' in repository '{}'.", nsMrid, getName());
-                result.missing();
-                return;
-            }
-            nsMd = DefaultModuleDescriptor.newDefaultInstance(nsMrid, dependencyDescriptor.getAllDependencyArtifacts());
-            ResolvedResource artifactRef = findFirstArtifactRef(nsMd);
-            if (artifactRef == null) {
-                LOGGER.debug("No ivy file nor artifact found for module '{}' in repository '{}'.", nsMrid, getName());
-                result.missing();
-            } else {
-                long lastModified = artifactRef.getLastModified();
-                if (lastModified != 0 && nsMd instanceof DefaultModuleDescriptor) {
-                    ((DefaultModuleDescriptor) nsMd).setLastModified(lastModified);
-                }
-                LOGGER.debug("No ivy file found for module '{}' in repository '{}', using default data instead.", nsMrid, getName());
-                if (isDynamic) {
-                    nsMd.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(nsMrid, artifactRef.getRevision()));
-                }
-                result.resolved(nsMd, isChanging(nsMd), null);
-            }
-        } else {
-            try {
-                ResolvedModuleRevision rmr = null;
-                if (ivyRef instanceof MDResolvedResource) {
-                    rmr = ((MDResolvedResource) ivyRef).getResolvedModuleRevision();
-                }
-                if (rmr == null) {
-                    rmr = parse(ivyRef, dependencyDescriptor);
-                }
-
-                nsMd = rmr.getDescriptor();
-
-                // check descriptor data is in sync with resource revision and names
-                if (isCheckconsistency()) {
-                    checkDescriptorConsistency(nsMrid, nsMd, ivyRef);
-                }
-                LOGGER.debug("Ivy file found for module '{}' in repository '{}'.", nsMrid, getName());
-                result.resolved(nsMd, isChanging(nsMd), null);
-            } catch (ParseException e) {
-                result.failed(new ModuleVersionResolveException(nsMrid, e));
+        // List modules with missing metadata files
+        if (isAllownomd()) {
+            for (ModuleVersionArtifactMetaData otherArtifact : getDefaultMetaData(dependency).getArtifacts()) {
+                listVersionsForAllPatterns(getArtifactPatterns(), otherArtifact, versionList);
             }
         }
+        DefaultModuleVersionListing moduleVersions = new DefaultModuleVersionListing();
+        for (VersionList.ListedVersion listedVersion : versionList.getVersions()) {
+            moduleVersions.add(listedVersion.getVersion());
+        }
+        result.listed(moduleVersions);
     }
 
-    protected VersionMatcher getVersionMatcher() {
-        return getSettings().getVersionMatcher();
-    }
-
-    private ResolvedModuleRevision parse(final ResolvedResource mdRef, DependencyDescriptor dd) throws ParseException {
-        //TODO: check why we don't use our own ParserRegistry here.
-        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 (getVersionMatcher().isDynamic(mrid)) {
-            resolvedMrid = ModuleRevisionId.newInstance(mrid, mdRef.getRevision());
-        }
-
-        Artifact moduleArtifact = parser.getMetadataArtifact(resolvedMrid, mdRef.getResource());
-        return getRepositoryCacheManager().cacheModuleDescriptor(this, mdRef, dd, moduleArtifact, resourceDownloader, new CacheMetadataOptions());
-    }
-
-    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 (!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;
-        }
-        if (!ok) {
-            throw new ParseException("inconsistent module descriptor file found in '"
-                    + ivyRef.getResource() + "': " + errors, 0);
+    private void listVersionsForAllPatterns(List<String> patternList, ModuleVersionArtifactMetaData artifact, VersionList versionList) {
+        for (String pattern : patternList) {
+            ResourcePattern resourcePattern = toResourcePattern(pattern);
+            versionList.visit(resourcePattern, artifact);
         }
     }
 
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
-        // This is not used
-        throw new UnsupportedOperationException();
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+        resolveStaticDependency(dependency, result, createArtifactResolver());
     }
 
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd) {
-        ModuleRevisionId mrid = dd.getDependencyRevisionId();
-        Artifact artifact = DefaultArtifact.newIvyArtifact(mrid, null);
-        return findResourceUsingPatterns(mrid, ivyPatterns, artifact, getRMDParser(dd), null, true);
-    }
+    protected final void resolveStaticDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result, ArtifactResolver artifactResolver) {
+        MutableModuleVersionMetaData metaDataArtifactMetaData = findMetaDataArtifact(dependency, artifactResolver);
 
-    protected ResolvedResource findFirstArtifactRef(ModuleDescriptor md) {
-        for (String configuration : md.getConfigurationsNames()) {
-            for (Artifact artifact : md.getArtifacts(configuration)) {
-                ResolvedResource artifactRef = getArtifactRef(artifact, null, false);
-                if (artifactRef != null) {
-                    return artifactRef;
-                }
-            }
+        if (metaDataArtifactMetaData != null) {
+            LOGGER.debug("Metadata file found for module '{}' in repository '{}'.", dependency.getRequested(), getName());
+            result.resolved(metaDataArtifactMetaData, null);
+            return;
         }
-        return null;
-    }
 
-    public boolean exists(Artifact artifact) {
-        // This is never used
-        throw new UnsupportedOperationException();
-    }
-
-    public ArtifactOrigin locate(Artifact artifact) {
-        ResolvedResource artifactRef = getArtifactRef(artifact, null, false);
-        if (artifactRef != null && artifactRef.getResource().exists()) {
-            final Resource resource = artifactRef.getResource();
-            return new ArtifactOrigin(artifact, resource.isLocal(), resource.getName());
+        if (isAllownomd()) {
+            MutableModuleVersionMetaData defaultArtifactMetaData = findDefaultArtifact(dependency, artifactResolver);
+            if (defaultArtifactMetaData != null) {
+                LOGGER.debug("Artifact file found for module '{}' in repository '{}'.", dependency.getRequested(), getName());
+                result.resolved(defaultArtifactMetaData, null);
+                return;
+            }
         }
-        return null;
-    }
-
-    protected ResolvedResource getArtifactRef(Artifact artifact, Date date) {
-        return getArtifactRef(artifact, date, true);
-    }
 
-    protected ResolvedResource getArtifactRef(Artifact artifact, Date date, boolean forDownload) {
-        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
-        ResourceMDParser parser = getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId());
-        return findResourceUsingPatterns(moduleRevisionId, getArtifactPatterns(), artifact, parser, date, forDownload);
+        LOGGER.debug("No meta-data file or artifact found for module '{}' in repository '{}'.", dependency.getRequested(), getName());
+        result.missing();
     }
 
-    protected ResourceMDParser getRMDParser(final DependencyDescriptor dd) {
-        return new ResourceMDParser() {
-            public MDResolvedResource parse(Resource resource, String rev) {
-                try {
-                    ResolvedModuleRevision rmr = ExternalResourceResolver.this.parse(new ResolvedResource(resource, rev), dd);
-                    if (rmr == null) {
-                        return null;
-                    } else {
-                        return new MDResolvedResource(resource, rev, rmr);
-                    }
-                } catch (ParseException e) {
-                    Message.warn("Failed to parse the file '" + resource + "': "
-                            + e.getMessage());
-                    return null;
-                }
-            }
-
-        };
-    }
+    protected MutableModuleVersionMetaData findMetaDataArtifact(DependencyMetaData dependency, ArtifactResolver artifactResolver) {
+        ModuleVersionArtifactMetaData artifact = getMetaDataArtifactFor(dependency);
+        if (artifact == null) {
+            return null;
+        }
+        ExternalResource metaDataResource = artifactResolver.resolveMetaDataArtifact(artifact);
+        if (metaDataResource == null) {
+            return null;
+        }
+        MutableModuleVersionMetaData moduleVersionMetaData = getArtifactMetadata(artifact, metaDataResource);
 
-    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()));
-            }
-        };
+        if (isCheckconsistency()) {
+            ModuleVersionSelector requested = dependency.getRequested();
+            ModuleVersionIdentifier requestedId = DefaultModuleVersionIdentifier.newId(requested.getGroup(), requested.getName(), requested.getVersion());
+            checkMetadataConsistency(requestedId, moduleVersionMetaData);
+        }
+        return moduleVersionMetaData;
     }
 
-    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 = 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;
-                }
-            }
+    protected MutableModuleVersionMetaData getArtifactMetadata(ModuleVersionArtifactMetaData artifact, ExternalResource resource) {
+        ExternalResourceResolverDescriptorParseContext context = new ExternalResourceResolverDescriptorParseContext(repositoryChain, this);
+        LocallyAvailableExternalResource cachedResource;
+        try {
+            cachedResource = downloadAndCacheResource(artifact, resource);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
 
-        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;
-        }
+        MutableModuleVersionMetaData metaData =  metaDataParser.parseMetaData(context, cachedResource);
+        return processMetaData(metaData);
     }
 
-    public ResolvedResource findLatestResource(ModuleRevisionId mrid, VersionList versions, ResourceMDParser rmdparser, Date date, ResourcePattern pattern, Artifact artifact, boolean forDownload) {
-        String name = getName();
-        VersionMatcher versionMatcher = 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;
-            }
+    private MutableModuleVersionMetaData findDefaultArtifact(DependencyMetaData dependency, ArtifactResolver artifactResolver) {
+        MutableModuleVersionMetaData metaData = getDefaultMetaData(dependency);
 
-            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;
-                }
+        if (hasArtifacts(metaData, artifactResolver)) {
+            LOGGER.debug("No meta-data file found for module '{}' in repository '{}', using default data instead.", dependency.getRequested(), getName());
 
-                return parsedResource;
-            }
-            return new ResolvedResource(resource, version);
+            return metaData;
         }
         return null;
     }
 
-    protected ResolvedResource findResourceUsingPattern(ModuleRevisionId moduleRevisionId, ResourcePattern pattern, Artifact artifact, ResourceMDParser resourceParser, Date date, boolean forDownload) {
-        VersionMatcher versionMatcher = getVersionMatcher();
-        if (!versionMatcher.isDynamic(moduleRevisionId)) {
-            return findStaticResourceUsingPattern(moduleRevisionId, pattern, artifact, forDownload);
-        } else {
-            return findDynamicResourceUsingPattern(resourceParser, moduleRevisionId, pattern, artifact, date, forDownload);
-        }
+    protected MutableModuleVersionMetaData getDefaultMetaData(DependencyMetaData dependency) {
+        MutableModuleVersionMetaData metaData = ModuleDescriptorAdapter.defaultForDependency(dependency);
+        return processMetaData(metaData);
     }
 
-    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 MutableModuleVersionMetaData processMetaData(MutableModuleVersionMetaData metaData) {
+        metaData.setChanging(isChanging(metaData.getId().getVersion()));
+        return metaData;
     }
 
-    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);
+    private void checkMetadataConsistency(ModuleVersionIdentifier expectedId, ModuleVersionMetaData metadata) throws MetaDataParseException {
+        List<String> errors = new ArrayList<String>();
+        if (!expectedId.getGroup().equals(metadata.getId().getGroup())) {
+            errors.add("bad group: expected='" + expectedId.getGroup() + "' found='" + metadata.getId().getGroup() + "'");
         }
-        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);
-            }
+        if (!expectedId.getName().equals(metadata.getId().getName())) {
+            errors.add("bad module name: expected='" + expectedId.getName() + "' found='" + metadata.getId().getName() + "'");
         }
-    }
-
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
-        EnhancedArtifactDownloadReport artifactDownloadReport = download(artifact, moduleSource);
-        if (downloadFailed(artifactDownloadReport)) {
-            result.failed(new ArtifactResolveException(artifactDownloadReport.getArtifact(), artifactDownloadReport.getFailure()));
-            return;
+        if (!expectedId.getVersion().equals(metadata.getId().getVersion())) {
+            errors.add("bad version: expected='" + expectedId.getVersion() + "' found='" + metadata.getId().getVersion() + "'");
         }
-        File localFile = artifactDownloadReport.getLocalFile();
-        if (localFile != null) {
-            result.resolved(localFile);
-        } else {
-            result.notFound(artifact);
+        if (errors.size() > 0) {
+            throw new MetaDataParseException(String.format("inconsistent module metadata found. Descriptor: %s Errors: %s",
+                    metadata.getId(), Joiner.on(SystemProperties.getLineSeparator()).join(errors)));
         }
     }
 
-    protected EnhancedArtifactDownloadReport download(Artifact artifact, ModuleSource moduleSource) {
-        return download(artifact);
+    @Nullable
+    private ModuleVersionArtifactMetaData getMetaDataArtifactFor(DependencyMetaData dependency) {
+        return getMetaDataArtifactFor(DefaultModuleVersionIdentifier.newId(dependency.getDescriptor().getDependencyRevisionId()));
     }
 
-    public EnhancedArtifactDownloadReport download(Artifact artifact) {
-        RepositoryCacheManager cacheManager = getRepositoryCacheManager();
-
-        return (EnhancedArtifactDownloadReport) cacheManager.download(artifact, artifactResourceResolver, resourceDownloader, new CacheDownloadOptions());
+    public void localResolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        doResolveModuleArtifacts((ModuleVersionMetaData) component, context, result, true);
     }
 
-    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
-        // This is never used
-        throw new UnsupportedOperationException();
+    public void resolveModuleArtifacts(ComponentMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result) {
+        doResolveModuleArtifacts((ModuleVersionMetaData) component, context, result, false);
     }
 
-    public ArtifactDownloadReport download(ArtifactOrigin origin, DownloadOptions options) {
-        // This is never used
-        throw new UnsupportedOperationException();
-    }
+    // TODO:DAZ This "local-only" pattern is quite ugly: improve it.
+    private void doResolveModuleArtifacts(ModuleVersionMetaData component, ArtifactResolveContext context, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        try {
+            if (context instanceof ConfigurationResolveContext) {
+                String configurationName = ((ConfigurationResolveContext) context).getConfigurationName();
+                ConfigurationMetaData configuration = component.getConfiguration(configurationName);
+                resolveConfigurationArtifacts(component, configuration, result, localOnly);
+            } else {
+                Class<? extends SoftwareArtifact> artifactType = ((ArtifactTypeResolveContext) context).getArtifactType();
+                if (artifactType == JvmLibraryJavadocArtifact.class) {
+                    resolveJavadocArtifacts(component, result, localOnly);
+                } else if (artifactType == JvmLibrarySourcesArtifact.class) {
+                    resolveSourceArtifacts(component, result, localOnly);
+                } else if (isMetaDataArtifact(artifactType)) {
+                    resolveMetaDataArtifacts(component, result);
+                }
 
-    public void reportFailure() {
-        // This is never used
-        throw new UnsupportedOperationException();
+                if (!localOnly && !result.hasResult()) {
+                    result.failed(new ArtifactResolveException(component.getComponentId(),
+                            String.format("Cannot locate artifacts of type %s for '%s' in repository '%s'", artifactType.getSimpleName(), component, name)));
+                }
+            }
+        } catch (Exception e) {
+            result.failed(new ArtifactResolveException(component.getComponentId(), e));
+        }
     }
 
-    public void reportFailure(Artifact art) {
-        // This is never used
-        throw new UnsupportedOperationException();
+    protected void resolveConfigurationArtifacts(ModuleVersionMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        result.resolved(configuration.getArtifacts());
     }
 
-    public String[] listTokenValues(String token, Map otherTokenValues) {
-        // This is never used
-        throw new UnsupportedOperationException();
-    }
+    protected abstract boolean isMetaDataArtifact(Class<? extends SoftwareArtifact> artifactType);
 
-    public Map[] listTokenValues(String[] tokens, Map criteria) {
-        // This is never used
-        throw new UnsupportedOperationException();
+    protected void resolveMetaDataArtifacts(ModuleVersionMetaData module, BuildableArtifactSetResolveResult result) {
+        ModuleVersionArtifactMetaData artifact = getMetaDataArtifactFor(module.getId());
+        if (artifact != null) {
+            result.resolved(ImmutableSet.of(artifact));
+        }
     }
 
-    public OrganisationEntry[] listOrganisations() {
-        // This is never used
-        throw new UnsupportedOperationException();
+    protected void resolveJavadocArtifacts(ModuleVersionMetaData module, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        if (!localOnly) {
+            result.resolved(findOptionalArtifacts(module, "javadoc", "javadoc"));
+        }
     }
 
-    public ModuleEntry[] listModules(OrganisationEntry org) {
-        // This is never used
-        throw new UnsupportedOperationException();
+    protected void resolveSourceArtifacts(ModuleVersionMetaData module, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        if (!localOnly) {
+            result.resolved(findOptionalArtifacts(module, "source", "sources"));
+        }
     }
 
-    public RevisionEntry[] listRevisions(ModuleEntry mod) {
-        // This is never used
-        throw new UnsupportedOperationException();
+    protected Set<ModuleVersionArtifactMetaData> findOptionalArtifacts(ModuleVersionMetaData module, String type, String classifier) {
+        ModuleVersionArtifactMetaData artifact = module.artifact(type, "jar", classifier);
+        if (createArtifactResolver(module.getSource()).artifactExists(artifact)) {
+            return ImmutableSet.of(artifact);
+        }
+        return Collections.emptySet();
     }
 
-    public void abortPublishTransaction() throws IOException {
+    @Nullable
+    protected abstract ModuleVersionArtifactMetaData getMetaDataArtifactFor(ModuleVersionIdentifier moduleVersionIdentifier);
+
+    protected boolean hasArtifacts(ModuleVersionMetaData metaData, ArtifactResolver artifactResolver) {
+        for (ModuleVersionArtifactMetaData artifactMetaData : metaData.getArtifacts()) {
+            if (artifactResolver.artifactExists(artifactMetaData)) {
+                return true;
+            }
+        }
+        return false;
     }
 
-    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+    public boolean artifactExists(ModuleVersionArtifactMetaData artifact) {
+        return createArtifactResolver().artifactExists(artifact);
     }
 
-    public void commitPublishTransaction() throws IOException {
+    // TODO:DAZ This is currently required to handle maven snapshots: if the timestamp was part of the identifier this wouldn't be required
+    protected ArtifactResolver createArtifactResolver(ModuleSource moduleSource) {
+        return createArtifactResolver();
     }
 
-    public Namespace getNamespace() {
-        // This is never used
-        throw new UnsupportedOperationException();
+    private LocallyAvailableExternalResource downloadAndCacheResource(ModuleVersionArtifactMetaData artifact, ExternalResource resource) throws IOException {
+        final ExternalResourceDownloader resourceDownloader = new VerifyingExternalResourceDownloader(getChecksumAlgorithms(), getRepository());
+        return repositoryCacheManager.downloadAndCacheArtifactFile(artifact, resourceDownloader, resource);
     }
 
-    protected Resource getResource(String source, Artifact target, boolean forDownload) {
+    public void resolveArtifact(ComponentArtifactMetaData componentArtifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+        ModuleVersionArtifactMetaData artifact = (ModuleVersionArtifactMetaData) componentArtifact;
+
+        File localFile;
         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);
+            localFile = download(artifact, moduleSource);
+        } catch (Throwable e) {
+            result.failed(new ArtifactResolveException(artifact.getId(), e));
+            return;
         }
-    }
 
-    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());
+        if (localFile != null) {
+            result.resolved(localFile);
+        } else {
+            result.notFound(artifact.getId());
         }
     }
 
-    protected long get(Resource resource, File destination) throws IOException {
-        LOGGER.debug("Downloading {} to {}", resource.getName(), destination);
-        if (destination.getParentFile() != null) {
-            GFileUtils.mkdirs(destination.getParentFile());
-        }
+    protected File download(ModuleVersionArtifactMetaData artifact, ModuleSource moduleSource) throws IOException {
+        return downloadArtifact(artifact, createArtifactResolver(moduleSource));
+    }
 
-        if (!(resource instanceof ExternalResource)) {
-            throw new IllegalArgumentException("Can only download ExternalResource");
+    protected File downloadArtifact(ModuleVersionArtifactMetaData artifact, ArtifactResolver artifactResolver) throws IOException {
+        ExternalResource artifactResource = artifactResolver.resolveArtifact(artifact);
+        if (artifactResource == null) {
+            return null;
         }
 
-        ExternalResource externalResource = (ExternalResource) resource;
-        try {
-            externalResource.writeTo(destination);
-        } finally {
-            externalResource.close();
-        }
-        return destination.length();
+        return downloadAndCacheResource(artifact, artifactResource).getLocalResource().getFile();
     }
 
-    private long getAndCheck(Resource resource, File dest) throws IOException {
-        long size = get(resource, dest);
-        String[] checksums = getChecksumAlgorithms();
-        boolean checked = false;
-        for (int i = 0; i < checksums.length && !checked; i++) {
-            checked = check(resource, dest, checksums[i]);
-        }
-        return size;
+    protected ArtifactResolver createArtifactResolver() {
+        return new ArtifactResolver(getIvyPatterns(), getArtifactPatterns());
     }
 
-    private boolean check(Resource resource, File dest, String algorithm) throws IOException {
-        if (!ChecksumHelper.isKnownAlgorithm(algorithm)) {
-            throw new IllegalArgumentException("Unknown checksum algorithm: " + algorithm);
-        }
+    public void setSettings(IvySettings settings) {
+    }
 
-        ExternalResource checksumResource = repository.getResource(resource.getName() + "." + algorithm);
-        if (checksumResource != null && checksumResource.exists()) {
-            LOGGER.debug(algorithm + " file found for " + resource + ": checking...");
-            File csFile = File.createTempFile("ivytmp", algorithm);
-            try {
-                get(checksumResource, csFile);
-                ChecksumHelper.check(dest, csFile, algorithm);
-                Message.verbose(algorithm + " OK for " + resource);
-                return true;
-            } finally {
-                csFile.delete();
-            }
-        } else {
-            return false;
+    public void publish(ModuleVersionPublishMetaData moduleVersion) throws IOException {
+        for (ModuleVersionArtifactPublishMetaData artifact : moduleVersion.getArtifacts()) {
+            publish(new DefaultModuleVersionArtifactMetaData(artifact.getId()), artifact.getFile());
         }
     }
 
-    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+    private void publish(ModuleVersionArtifactMetaData artifact, File src) throws IOException {
         String destinationPattern;
-        if ("ivy".equals(artifact.getType()) && !getIvyPatterns().isEmpty()) {
+        if ("ivy".equals(artifact.getName().getType()) && !getIvyPatterns().isEmpty()) {
             destinationPattern = getIvyPatterns().get(0);
         } else if (!getArtifactPatterns().isEmpty()) {
             destinationPattern = getArtifactPatterns().get(0);
@@ -655,7 +408,7 @@ public class ExternalResourceResolver implements DependencyResolver {
         String destination = toResourcePattern(destinationPattern).toPath(artifact);
 
         put(src, destination);
-        LOGGER.info("Published {} to {}", artifact.getName(), destination);
+        LOGGER.info("Published {} to {}", artifact, destination);
     }
 
     private void put(File src, String destination) throws IOException {
@@ -692,25 +445,20 @@ public class ExternalResourceResolver implements DependencyResolver {
         artifactPatterns = patterns;
     }
 
-    public void dumpSettings() {
-        // this is not used
-        throw new UnsupportedOperationException();
-    }
-
     public boolean isM2compatible() {
-        return m2compatible;
+        return m2Compatible;
     }
 
     public void setM2compatible(boolean compatible) {
-        m2compatible = compatible;
+        m2Compatible = compatible;
     }
 
     public boolean isCheckconsistency() {
-        return checkconsistency;
+        return checkConsistency;
     }
 
     public void setCheckconsistency(boolean checkConsistency) {
-        checkconsistency = checkConsistency;
+        this.checkConsistency = checkConsistency;
     }
 
     public void setForce(boolean force) {
@@ -722,33 +470,11 @@ public class ExternalResourceResolver implements DependencyResolver {
     }
 
     public boolean isAllownomd() {
-        return allownomd;
-    }
-
-    public void setAllownomd(boolean b) {
-        Message.deprecated(
-            "allownomd is deprecated, please use descriptor=\""
-            + (b ? BasicResolver.DESCRIPTOR_OPTIONAL : BasicResolver.DESCRIPTOR_REQUIRED) + "\" instead");
-        allownomd = b;
-    }
-
-    /**
-     * Sets the module descriptor presence rule.
-     * Should be one of {@link BasicResolver#DESCRIPTOR_REQUIRED} or {@link BasicResolver#DESCRIPTOR_OPTIONAL}.
-     *
-     * @param descriptorRule the descriptor rule to use with this resolver.
-     */
-    public void setDescriptor(String descriptorRule) {
-        if (BasicResolver.DESCRIPTOR_REQUIRED.equals(descriptorRule)) {
-          allownomd = false;
-        } else if (BasicResolver.DESCRIPTOR_OPTIONAL.equals(descriptorRule)) {
-          allownomd = true;
-        } else {
-            throw new IllegalArgumentException(
-                "unknown descriptor rule '" + descriptorRule
-                + "'. Allowed rules are: "
-                + Arrays.asList(new String[] {BasicResolver.DESCRIPTOR_REQUIRED, BasicResolver.DESCRIPTOR_OPTIONAL}));
-        }
+        return allowMissingDescriptor;
+    }
+
+    public void setAllownomd(boolean allowMissingDescriptor) {
+        this.allowMissingDescriptor = allowMissingDescriptor;
     }
 
     public String[] getChecksumAlgorithms() {
@@ -772,97 +498,102 @@ public class ExternalResourceResolver implements DependencyResolver {
         this.checksums = checksums;
     }
 
-    public LatestStrategy getLatestStrategy() {
-        if (latestStrategy == null) {
-            initLatestStrategyFromSettings();
-        }
-        return latestStrategy;
-    }
-
-    private void initLatestStrategyFromSettings() {
-        if (getSettings() != null) {
-            if (latestStrategyName != null && !"default".equals(latestStrategyName)) {
-                latestStrategy = getSettings().getLatestStrategy(latestStrategyName);
-                if (latestStrategy == null) {
-                    throw new IllegalStateException(
-                        "unknown latest strategy '" + latestStrategyName + "'");
-                }
-            } else {
-                latestStrategy = getSettings().getDefaultLatestStrategy();
-                Message.debug(getName() + ": no latest strategy defined: using default");
-            }
-        } else {
-            throw new IllegalStateException(
-                "no ivy instance found: "
-                + "impossible to get a latest strategy without ivy instance");
-        }
-    }
-
-    public void setLatestStrategy(LatestStrategy latestStrategy) {
-        this.latestStrategy = latestStrategy;
-    }
-
-    public void setLatest(String strategyName) {
-        latestStrategyName = strategyName;
-    }
-
-    public String getLatest() {
-        if (latestStrategyName == null) {
-            latestStrategyName = "default";
-        }
-        return latestStrategyName;
-    }
-
-    public void setChangingMatcher(String changingMatcherName) {
-        this.changingMatcherName = changingMatcherName;
-    }
-
     public String getChangingMatcherName() {
         return changingMatcherName;
     }
 
-    public void setChangingPattern(String changingPattern) {
-        this.changingPattern = changingPattern;
+    public void setChangingMatcher(String changingMatcherName) {
+        this.changingMatcherName = changingMatcherName;
     }
 
     public String getChangingPattern() {
         return changingPattern;
     }
 
-    public void setCheckmodified(boolean check) {
-        checkmodified = Boolean.valueOf(check);
-    }
-
-    public RepositoryCacheManager getRepositoryCacheManager() {
-        return repositoryCacheManager;
+    public void setChangingPattern(String changingPattern) {
+        this.changingPattern = changingPattern;
     }
 
-    public void setRepositoryCacheManager(RepositoryCacheManager repositoryCacheManager) {
+    public void setRepositoryCacheManager(RepositoryArtifactCache repositoryCacheManager) {
         this.repositoryCacheManager = repositoryCacheManager;
     }
 
-    public void setCache(String cacheName) {
-        cacheManagerName = cacheName;
-    }
-
     protected ResourcePattern toResourcePattern(String pattern) {
         return isM2compatible() ? new M2ResourcePattern(pattern) : new IvyResourcePattern(pattern);
     }
 
-    protected boolean downloadFailed(ArtifactDownloadReport artifactReport) {
-        return artifactReport.getDownloadStatus() == DownloadStatus.FAILED
-                && !artifactReport.getDownloadDetails().equals(ArtifactDownloadReport.MISSING_ARTIFACT);
-    }
-
-    private boolean isChanging(ModuleDescriptor moduleDescriptor) {
+    private boolean isChanging(String version) {
         if (changingMatcherName == null || changingPattern == null) {
             return false;
         }
-        PatternMatcher matcher = settings.getMatcher(changingMatcherName);
+        PatternMatcher matcher = resolverStrategy.getPatternMatcher(changingMatcherName);
         if (matcher == null) {
             throw new IllegalStateException("unknown matcher '" + changingMatcherName
                     + "'. It is set as changing matcher in " + this);
         }
-        return matcher.getMatcher(changingPattern).matches(moduleDescriptor.getResolvedModuleRevisionId().getRevision());
+        return matcher.getMatcher(changingPattern).matches(version);
+    }
+
+    // TODO:DAZ Extract this properly: make this static
+    protected class ArtifactResolver {
+        private final List<String> ivyPatterns;
+        private final List<String> artifactPatterns;
+
+        public ArtifactResolver(List<String> ivyPatterns, List<String> artifactPatterns) {
+            this.ivyPatterns = ivyPatterns;
+            this.artifactPatterns = artifactPatterns;
+        }
+
+        public ExternalResource resolveMetaDataArtifact(ModuleVersionArtifactMetaData artifact) {
+            return findStaticResourceUsingPatterns(ivyPatterns, artifact, true);
+        }
+
+        public ExternalResource resolveArtifact(ModuleVersionArtifactMetaData artifact) {
+            return findStaticResourceUsingPatterns(artifactPatterns, artifact, true);
+        }
+
+        public boolean artifactExists(ModuleVersionArtifactMetaData artifact) {
+            return findStaticResourceUsingPatterns(artifactPatterns, artifact, false) != null;
+        }
+
+        private ExternalResource findStaticResourceUsingPatterns(List<String> patternList, ModuleVersionArtifactMetaData artifact, boolean forDownload) {
+            for (String pattern : patternList) {
+                ResourcePattern resourcePattern = toResourcePattern(pattern);
+                String resourceName = resourcePattern.toPath(artifact);
+                LOGGER.debug("Loading {}", resourceName);
+                ExternalResource resource = getResource(resourceName, artifact, forDownload);
+                if (resource.exists()) {
+                    return resource;
+                } else {
+                    LOGGER.debug("Resource not reachable for {}: res={}", artifact, resource);
+                    discardResource(resource);
+                }
+            }
+            return null;
+        }
+
+        private ExternalResource getResource(String source, ModuleVersionArtifactMetaData target, boolean forDownload) {
+            try {
+                if (forDownload) {
+                    LocallyAvailableResourceCandidates localCandidates = locallyAvailableResourceFinder.findCandidates(target);
+                    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 void discardResource(ExternalResource resource) {
+            try {
+                resource.close();
+            } catch (IOException e) {
+                LOGGER.warn("Exception closing resource " + resource.getName(), e);
+            }
+        }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java
new file mode 100644
index 0000000..df22bdd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.DefaultDependencyDescriptor;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChain;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.DefaultDependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+
+import java.io.File;
+
+/**
+ * ParserSettings that control the scope of searches carried out during parsing.
+ * If the parser asks for a resolver for the currently resolving revision, the resolver scope is only the repository where the module was resolved.
+ * If the parser asks for a resolver for a different revision, the resolver scope is all repositories.
+ */
+public class ExternalResourceResolverDescriptorParseContext implements DescriptorParseContext {
+    private final RepositoryChain mainResolvers;
+    private final ExternalResourceResolver moduleResolver;
+
+    public ExternalResourceResolverDescriptorParseContext(RepositoryChain mainResolvers, ExternalResourceResolver moduleResolver) {
+        this.mainResolvers = mainResolvers;
+        this.moduleResolver = moduleResolver;
+    }
+
+    public boolean artifactExists(ModuleVersionArtifactMetaData artifact) {
+        return moduleResolver.artifactExists(artifact);
+    }
+
+    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, Class<? extends SoftwareArtifact> artifactType) {
+        File resolvedArtifactFile = resolveMetaDataArtifactFile(moduleVersionIdentifier, mainResolvers.getDependencyResolver(), mainResolvers.getArtifactResolver(), artifactType);
+        LocallyAvailableResource localResource = new DefaultLocallyAvailableResource(resolvedArtifactFile);
+        return new DefaultLocallyAvailableExternalResource(resolvedArtifactFile.toURI().toString(), localResource);
+    }
+
+    private File resolveMetaDataArtifactFile(ModuleVersionIdentifier moduleVersionIdentifier, DependencyToModuleVersionResolver dependencyResolver,
+                                             ArtifactResolver artifactResolver, Class<? extends SoftwareArtifact> artifactType) {
+        BuildableComponentResolveResult moduleVersionResolveResult = new DefaultBuildableComponentResolveResult();
+        dependencyResolver.resolve(new DefaultDependencyMetaData(new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId(moduleVersionIdentifier), true)), moduleVersionResolveResult);
+
+        BuildableArtifactSetResolveResult moduleArtifactsResolveResult = new DefaultBuildableArtifactSetResolveResult();
+        ArtifactTypeResolveContext context = new ArtifactTypeResolveContext(artifactType);
+        artifactResolver.resolveModuleArtifacts(moduleVersionResolveResult.getMetaData(), context, moduleArtifactsResolveResult);
+
+        BuildableArtifactResolveResult artifactResolveResult = new DefaultBuildableArtifactResolveResult();
+        ComponentArtifactMetaData artifactMetaData = moduleArtifactsResolveResult.getArtifacts().iterator().next();
+        artifactResolver.resolveArtifact(artifactMetaData, moduleVersionResolveResult.getMetaData().getSource(), artifactResolveResult);
+        return artifactResolveResult.getFile();
+    }
+}
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
index e5fb66b..c1e9000 100644
--- 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
@@ -15,8 +15,15 @@
  */
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DownloadedIvyModuleDescriptorParser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.*;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.artifacts.resolution.IvyDescriptorArtifact;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 
 import java.net.URI;
@@ -24,13 +31,33 @@ import java.net.URI;
 public class IvyResolver extends ExternalResourceResolver implements PatternBasedResolver {
 
     private final RepositoryTransport transport;
+    private final boolean dynamicResolve;
 
     public IvyResolver(String name, RepositoryTransport transport,
-                       LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder
-    ) {
-        super(name, transport.getRepository(), new ResourceVersionLister(transport.getRepository()), locallyAvailableResourceFinder);
+                       LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                       boolean dynamicResolve, ResolverStrategy resolverStrategy) {
+        super(name, transport.getRepository(), new ResourceVersionLister(transport.getRepository()),
+                locallyAvailableResourceFinder, new DownloadedIvyModuleDescriptorParser(resolverStrategy),
+                resolverStrategy);
         this.transport = transport;
         this.transport.configureCacheManager(this);
+        this.dynamicResolve = dynamicResolve;
+    }
+
+    @Override
+    public boolean isDynamicResolveMode() {
+        return dynamicResolve;
+    }
+
+    @Override
+    protected boolean isMetaDataArtifact(Class<? extends SoftwareArtifact> artifactType) {
+        return artifactType == IvyDescriptorArtifact.class;
+    }
+
+    @Nullable
+    protected ModuleVersionArtifactMetaData getMetaDataArtifactFor(ModuleVersionIdentifier moduleVersionIdentifier) {
+        DefaultModuleVersionArtifactIdentifier artifactId = new DefaultModuleVersionArtifactIdentifier(moduleVersionIdentifier, "ivy", "ivy", "xml");
+        return new DefaultModuleVersionArtifactMetaData(artifactId);
     }
 
     public void addArtifactLocation(URI baseUri, String pattern) {
@@ -42,4 +69,24 @@ public class IvyResolver extends ExternalResourceResolver implements PatternBase
         String descriptorPattern = transport.convertToPath(baseUri) + pattern;
         addIvyPattern(descriptorPattern);
     }
+
+    @Override
+    protected void resolveJavadocArtifacts(ModuleVersionMetaData module, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        ConfigurationMetaData configuration = module.getConfiguration("javadoc");
+        if (configuration != null) {
+            result.resolved(configuration.getArtifacts());
+        } else {
+            super.resolveJavadocArtifacts(module, result, localOnly);
+        }
+    }
+
+    @Override
+    protected void resolveSourceArtifacts(ModuleVersionMetaData module, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        ConfigurationMetaData configuration = module.getConfiguration("sources");
+        if (configuration != null) {
+            result.resolved(configuration.getArtifacts());
+        } else {
+            super.resolveSourceArtifacts(module, result, localOnly);
+        }
+    }
 }
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
index 3e04de4..1771ed6 100644
--- 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
@@ -17,7 +17,10 @@
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
 import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.metadata.IvyArtifactName;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -38,26 +41,44 @@ public class IvyResourcePattern implements ResourcePattern {
         return String.format("Ivy pattern '%s'", pattern);
     }
 
-    public String toPath(Artifact artifact) {
+    public String toPath(ModuleVersionArtifactMetaData artifact) {
         Map<String, Object> attributes = toAttributes(artifact);
         return IvyPatternHelper.substituteTokens(pattern, attributes);
     }
 
-    public String toPathWithoutRevision(Artifact artifact) {
-        Map<String, Object> attributes = toAttributes(artifact);
+    public String toVersionListPattern(ModuleVersionArtifactMetaData artifactId) {
+        Map<String, Object> attributes = toAttributes(artifactId);
         attributes.remove(IvyPatternHelper.REVISION_KEY);
         return IvyPatternHelper.substituteTokens(pattern, attributes);
     }
 
-    public String toModulePath(Artifact artifact) {
+    public String toModulePath(ModuleIdentifier module) {
         throw new UnsupportedOperationException("not implemented yet.");
     }
 
-    public String toModuleVersionPath(Artifact artifact) {
+    public String toModuleVersionPath(ModuleVersionArtifactMetaData artifact) {
         throw new UnsupportedOperationException("not implemented yet.");
     }
 
-    protected Map<String, Object> toAttributes(Artifact artifact) {
-        return new HashMap<String, Object>(artifact.getAttributes());
+    // TODO:DAZ Handle extra attributes
+    protected Map<String, Object> toAttributes(ModuleVersionArtifactMetaData artifact) {
+        HashMap<String, Object> attributes = new HashMap<String, Object>();
+        ModuleComponentIdentifier moduleVersionIdentifier = artifact.getId().getComponentIdentifier();
+        IvyArtifactName ivyArtifact = artifact.getName();
+        attributes.put(IvyPatternHelper.ORGANISATION_KEY, moduleVersionIdentifier.getGroup());
+        attributes.put(IvyPatternHelper.MODULE_KEY, moduleVersionIdentifier.getModule());
+        attributes.put(IvyPatternHelper.REVISION_KEY, moduleVersionIdentifier.getVersion());
+        attributes.put(IvyPatternHelper.ARTIFACT_KEY, ivyArtifact.getName());
+        attributes.put(IvyPatternHelper.TYPE_KEY, ivyArtifact.getType());
+        attributes.put(IvyPatternHelper.EXT_KEY, ivyArtifact.getExtension());
+        attributes.put("classifier", ivyArtifact.getClassifier());
+        return attributes;
+    }
+
+    protected Map<String, Object> toAttributes(ModuleIdentifier module) {
+        HashMap<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put(IvyPatternHelper.ORGANISATION_KEY, module.getGroup());
+        attributes.put(IvyPatternHelper.MODULE_KEY, module.getName());
+        return attributes;
     }
 }
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
index 4233111..3df3368 100644
--- 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
@@ -17,10 +17,12 @@
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
 import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 
 import java.util.Map;
 
+// TODO:DAZ Should extend common base class
 public class M2ResourcePattern extends IvyResourcePattern {
     public M2ResourcePattern(String pattern) {
         super(pattern);
@@ -32,28 +34,23 @@ public class M2ResourcePattern extends IvyResourcePattern {
     }
 
     @Override
-    public String toModulePath(Artifact artifact) {
+    public String toModulePath(ModuleIdentifier module) {
         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));
+        return IvyPatternHelper.substituteTokens(metaDataPattern, toAttributes(module));
     }
 
     @Override
-    public String toPath(Artifact artifact) {
+    public String toPath(ModuleVersionArtifactMetaData artifact) {
         Map<String, Object> attributes = toAttributes(artifact);
-        if (artifact.getModuleRevisionId().getExtraAttributes().containsKey("timestamp")) {
-            final Object revisionValue = artifact.getModuleRevisionId().getExtraAttribute("timestamp");
-            String pattern = getPattern().replaceFirst("\\-\\[revision\\]", "-" + revisionValue);
-            return IvyPatternHelper.substituteTokens(pattern, attributes);
-        }
         return IvyPatternHelper.substituteTokens(getPattern(), attributes);
     }
 
     @Override
-    public String toModuleVersionPath(Artifact artifact) {
+    public String toModuleVersionPath(ModuleVersionArtifactMetaData artifact) {
         String pattern = getPattern();
         if (!pattern.endsWith(MavenPattern.M2_PATTERN)) {
             throw new UnsupportedOperationException("Cannot locate module version for non-maven layout.");
@@ -63,8 +60,16 @@ public class M2ResourcePattern extends IvyResourcePattern {
     }
 
     @Override
-    protected Map<String, Object> toAttributes(Artifact artifact) {
-        Map<String, Object> attributes = super.toAttributes(artifact);
+    protected Map<String, Object> toAttributes(ModuleVersionArtifactMetaData artifact) {
+        return mapOrganisation(super.toAttributes(artifact));
+    }
+
+    @Override
+    protected Map<String, Object> toAttributes(ModuleIdentifier module) {
+        return mapOrganisation(super.toAttributes(module));
+    }
+
+    private Map<String, Object> mapOrganisation(Map<String, Object> attributes) {
         String org = (String) attributes.get(IvyPatternHelper.ORGANISATION_KEY);
         if (org != null) {
             attributes.put(IvyPatternHelper.ORGANISATION_KEY, org.replace(".", "/"));
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenLocalResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenLocalResolver.java
new file mode 100644
index 0000000..3097423
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenLocalResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+
+public class MavenLocalResolver extends MavenResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MavenResolver.class);
+
+    public MavenLocalResolver(String name, URI rootUri, RepositoryTransport transport,
+                              LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                              ResolverStrategy resolverStrategy) {
+        super(name, rootUri, transport, locallyAvailableResourceFinder, resolverStrategy);
+    }
+
+    @Override
+    protected MutableModuleVersionMetaData findMetaDataArtifact(DependencyMetaData dependency, ArtifactResolver artifactResolver) {
+        MutableModuleVersionMetaData metaData = super.findMetaDataArtifact(dependency, artifactResolver);
+        if (isOrphanedPom(metaData, artifactResolver)) {
+            return null;
+        }
+        return metaData;
+    }
+
+    private boolean isOrphanedPom(ModuleVersionMetaData metaData, ArtifactResolver artifactResolver) {
+        if (!metaData.isMetaDataOnly()) {
+            if (!hasArtifacts(metaData, artifactResolver)) {
+                LOGGER.debug("POM file found for module '{}' in repository '{}' but no artifact found. Ignoring.", metaData.getId(), getName());
+                return true;
+            }
+        }
+        return false;
+    }
+}
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
index d4cb7d6..7f794a2 100644
--- 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
@@ -16,9 +16,9 @@
 
 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.internal.ErroringAction;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 import org.gradle.api.internal.resource.ResourceException;
@@ -64,23 +64,26 @@ class MavenMetadataLoader {
         }
     }
 
-    private void parseMavenMetadataInto(Resource metadataResource, final MavenMetadata mavenMetadata) throws IOException, SAXException, ParserConfigurationException {
+    private void parseMavenMetadataInto(ExternalResource 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);
+        metadataResource.withContent(new ErroringAction<InputStream>() {
+            public void doExecute(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
+                XMLHelper.parse(inputStream, 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);
             }
-        }, null);
+        });
     }
 }
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
index 71eb83f..ea84791 100644
--- 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
@@ -15,23 +15,25 @@
  */
 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.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaData;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
-import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradlePomModuleDescriptorParser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
+import org.gradle.api.internal.artifacts.metadata.*;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.artifacts.resolution.MavenPomArtifact;
 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.CollectionUtils;
 import org.gradle.util.DeprecationLogger;
-import org.gradle.util.WrapUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,11 +51,11 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
     private final MavenMetadataLoader mavenMetaDataLoader;
 
     public MavenResolver(String name, URI rootUri, RepositoryTransport transport,
-                         LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder) {
-        super(name,
-                transport.getRepository(),
+                         LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> locallyAvailableResourceFinder,
+                         ResolverStrategy resolverStrategy) {
+        super(name, transport.getRepository(),
                 new ChainedVersionLister(new MavenVersionLister(transport.getRepository()), new ResourceVersionLister(transport.getRepository())),
-                locallyAvailableResourceFinder);
+                locallyAvailableResourceFinder, new GradlePomModuleDescriptorParser(), resolverStrategy);
         transport.configureCacheManager(this);
 
         this.mavenMetaDataLoader = new MavenMetadataLoader(transport.getRepository());
@@ -69,60 +71,51 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         updatePatterns();
     }
 
-    public void getDependency(DependencyDescriptor dd, BuildableModuleVersionMetaData result) {
-        if (isSnapshotVersion(dd)) {
-            getSnapshotDependency(dd, result);
-        } else {
-            super.getDependency(dd, result);
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result) {
+        if (isSnapshotVersion(dependency)) {
+            final TimestampedModuleSource uniqueSnapshotVersion = findUniqueSnapshotVersion(dependency.getDescriptor().getDependencyRevisionId());
+            if (uniqueSnapshotVersion != null) {
+                resolveUniqueSnapshotDependency(dependency, result, uniqueSnapshotVersion);
+                return;
+            }
         }
+
+        resolveStaticDependency(dependency, result, createArtifactResolver());
     }
 
-    private void getSnapshotDependency(DependencyDescriptor dd, BuildableModuleVersionMetaData result) {
-        final ModuleRevisionId dependencyRevisionId = dd.getDependencyRevisionId();
-        final String uniqueSnapshotVersion = findUniqueSnapshotVersion(dependencyRevisionId);
-        if (uniqueSnapshotVersion != null) {
-            DependencyDescriptor enrichedDependencyDescriptor = enrichDependencyDescriptorWithSnapshotVersionInfo(dd, dependencyRevisionId, uniqueSnapshotVersion);
-            super.getDependency(enrichedDependencyDescriptor, result);
-            if (result.getState() == BuildableModuleVersionMetaData.State.Resolved) {
-                result.setModuleSource(new TimestampedModuleSource(uniqueSnapshotVersion));
-            }
-        } else {
-            super.getDependency(dd, result);
-        }
+    @Override
+    protected boolean isMetaDataArtifact(Class<? extends SoftwareArtifact> artifactType) {
+        return artifactType == MavenPomArtifact.class;
     }
 
-    private DependencyDescriptor enrichDependencyDescriptorWithSnapshotVersionInfo(DependencyDescriptor dd, ModuleRevisionId dependencyRevisionId, String uniqueSnapshotVersion) {
-        Map<String, String> extraAttributes = new HashMap<String, String>(1);
-        extraAttributes.put("timestamp", uniqueSnapshotVersion);
-        final ModuleRevisionId newModuleRevisionId = ModuleRevisionId.newInstance(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision(), extraAttributes);
-        return dd.clone(newModuleRevisionId);
+    private void resolveUniqueSnapshotDependency(DependencyMetaData dependency, BuildableModuleVersionMetaDataResolveResult result, TimestampedModuleSource snapshotSource) {
+        resolveStaticDependency(dependency, result, createArtifactResolver(snapshotSource));
+        if (result.getState() == BuildableModuleVersionMetaDataResolveResult.State.Resolved) {
+            result.setModuleSource(snapshotSource);
+        }
     }
 
-    private boolean isSnapshotVersion(DependencyDescriptor dd) {
-        return dd.getDependencyRevisionId().getRevision().endsWith("SNAPSHOT");
+    private boolean isSnapshotVersion(DependencyMetaData dd) {
+        return dd.getRequested().getVersion().endsWith("SNAPSHOT");
     }
 
-    protected EnhancedArtifactDownloadReport download(Artifact artifact, ModuleSource moduleSource) {
-        EnhancedArtifactDownloadReport artifactDownloadReport;
+    @Override
+    protected ArtifactResolver createArtifactResolver(ModuleSource moduleSource) {
 
         if (moduleSource instanceof TimestampedModuleSource) {
-            TimestampedModuleSource timestampedModuleSource = (TimestampedModuleSource) moduleSource;
-            String timestampedVersion = timestampedModuleSource.getTimestampedVersion();
-            artifactDownloadReport = downloadTimestampedVersion(artifact, timestampedVersion);
-        } else {
-            artifactDownloadReport = download(artifact);
+
+            final String timestampedVersion = ((TimestampedModuleSource) moduleSource).getTimestampedVersion();
+            Transformer<String, String> patternTransformer = new Transformer<String, String>() {
+                public String transform(String original) {
+                    return original.replaceFirst("\\-\\[revision\\]", "-" + timestampedVersion);
+                }
+            };
+            return new ArtifactResolver(
+                    CollectionUtils.collect(getIvyPatterns(), patternTransformer),
+                    CollectionUtils.collect(getArtifactPatterns(), patternTransformer));
         }
-        return artifactDownloadReport;
-    }
 
-    private EnhancedArtifactDownloadReport downloadTimestampedVersion(Artifact artifact, String timestampedVersion) {
-        final ModuleRevisionId artifactModuleRevisionId = artifact.getModuleRevisionId();
-        final ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance(artifactModuleRevisionId.getOrganisation(),
-                artifactModuleRevisionId.getName(),
-                artifactModuleRevisionId.getRevision(),
-                WrapUtil.toMap("timestamp", timestampedVersion));
-        final Artifact artifactWithResolvedModuleRevisionId = DefaultArtifact.cloneWithAnotherMrid(artifact, moduleRevisionId);
-        return download(artifactWithResolvedModuleRevisionId);
+        return super.createArtifactResolver(moduleSource);
     }
 
     public void addArtifactLocation(URI baseUri, String pattern) {
@@ -157,20 +150,20 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         setArtifactPatterns(artifactPatterns);
     }
 
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd) {
+    @Override
+    protected ModuleVersionArtifactMetaData getMetaDataArtifactFor(ModuleVersionIdentifier moduleVersionIdentifier) {
         if (isUsepoms()) {
-            ModuleRevisionId moduleRevisionId = dd.getDependencyRevisionId();
-            //we might need a own implementation of DefaultArtifact here as there is no way to pass extraAttributes AND isMetaData to DefaultArtifact
-            Artifact pomArtifact = new DefaultArtifact(moduleRevisionId, null, moduleRevisionId.getName(), "pom", "pom", moduleRevisionId.getExtraAttributes());
-            ResourceMDParser parser = getRMDParser(dd);
-            return findResourceUsingPatterns(moduleRevisionId, getIvyPatterns(), pomArtifact, parser, null, true);
+            DefaultModuleVersionArtifactIdentifier artifactId = new DefaultModuleVersionArtifactIdentifier(moduleVersionIdentifier, moduleVersionIdentifier.getName(), "pom", "pom");
+            return new DefaultModuleVersionArtifactMetaData(artifactId);
         }
 
         return null;
     }
 
-    private String findUniqueSnapshotVersion(ModuleRevisionId moduleRevisionId) {
-        Artifact pomArtifact = DefaultArtifact.newPomArtifact(moduleRevisionId, new Date());
+    private TimestampedModuleSource findUniqueSnapshotVersion(ModuleRevisionId moduleRevisionId) {
+        ModuleVersionIdentifier moduleVersionIdentifier = DefaultModuleVersionIdentifier.newId(moduleRevisionId);
+        ModuleVersionArtifactIdentifier artifactId = new DefaultModuleVersionArtifactIdentifier(moduleVersionIdentifier, moduleVersionIdentifier.getName(), "pom", "pom");
+        DefaultModuleVersionArtifactMetaData pomArtifact = new DefaultModuleVersionArtifactMetaData(artifactId);
         String metadataLocation = toResourcePattern(getWholePattern()).toModuleVersionPath(pomArtifact) + "/maven-metadata.xml";
         MavenMetadata mavenMetadata = parseMavenMetadata(metadataLocation);
 
@@ -179,7 +172,7 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
             String rev = moduleRevisionId.getRevision();
             rev = rev.substring(0, rev.length() - "SNAPSHOT".length());
             rev = rev + mavenMetadata.timestamp + "-" + mavenMetadata.buildNumber;
-            return rev;
+            return new TimestampedModuleSource(rev);
         }
         return null;
     }
@@ -191,7 +184,7 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
             } catch (ResourceNotFoundException e) {
                 return new MavenMetadata();
             } catch (ResourceException e) {
-                LOGGER.warn("impossible to access maven metadata file, ignored.", e);
+                LOGGER.warn("impossible to access Maven metadata file, ignored.", e);
             }
         }
         return new MavenMetadata();
@@ -255,6 +248,19 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         }
     }
 
+    @Override
+    protected void resolveConfigurationArtifacts(ModuleVersionMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result, boolean localOnly) {
+        if (module.isMetaDataOnly()) {
+            if (!localOnly) {
+                Set<ComponentArtifactMetaData> artifacts = new LinkedHashSet<ComponentArtifactMetaData>(configuration.getArtifacts());
+                artifacts.addAll(findOptionalArtifacts(module, "jar", null));
+                result.resolved(artifacts);
+            }
+        } else {
+            result.resolved(configuration.getArtifacts());
+        }
+    }
+
     protected static class TimestampedModuleSource implements ModuleSource {
         public String getTimestampedVersion() {
             return timestampedVersion;
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
index 2609b47..31f4521 100644
--- 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
@@ -16,11 +16,10 @@
 
 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.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 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;
@@ -32,17 +31,19 @@ public class MavenVersionLister implements VersionLister {
         this.mavenMetadataLoader = new MavenMetadataLoader(repository);
     }
 
-    public VersionList getVersionList(final ModuleRevisionId moduleRevisionId) {
+    public VersionList getVersionList(final ModuleIdentifier module) {
         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";
+            public void visit(ResourcePattern resourcePattern, ModuleVersionArtifactMetaData artifact) throws ResourceException {
+                String metadataLocation = resourcePattern.toModulePath(module) + "/maven-metadata.xml";
                 if (!searched.add(metadataLocation)) {
                     return;
                 }
                 MavenMetadata mavenMetaData = mavenMetadataLoader.load(metadataLocation);
-                add(mavenMetaData.versions);
+                for (String version : mavenMetaData.versions) {
+                    add(new ListedVersion(version, resourcePattern));
+                }
             }
         };
     }
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
index e71e0e0..3bdb961 100644
--- 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
@@ -15,12 +15,10 @@
  */
 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 {
+public interface PatternBasedResolver {
     void addArtifactLocation(URI baseUri, String pattern);
 
     void addDescriptorLocation(URI baseUri, String pattern);
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
index fe67b66..a22e1b8 100644
--- 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
@@ -16,26 +16,28 @@
 
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 
 public interface ResourcePattern {
     /**
      * Returns the path to the given artifact.
      */
-    String toPath(Artifact artifact);
+    String toPath(ModuleVersionArtifactMetaData artifact);
 
     /**
-     * Returns the path to the given artifact, without substituting the version placeholders.
+     * Returns the pattern which can be used to search for versions of the given artifact.
+     * The returned pattern should include at least one [revision] placeholder.
      */
-    String toPathWithoutRevision(Artifact artifact);
+    String toVersionListPattern(ModuleVersionArtifactMetaData artifact);
 
     /**
-     * Returns the path to the module for the given artifact.
+     * Returns the path to the given module.
      */
-    String toModulePath(Artifact artifact);
+    String toModulePath(ModuleIdentifier moduleIdentifier);
 
     /**
      * Returns the path to the module version for the given artifact.
      */
-    String toModuleVersionPath(Artifact artifact);
+    String toModuleVersionPath(ModuleVersionArtifactMetaData 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
index 3034b1a..8299605 100644
--- 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
@@ -17,11 +17,10 @@
 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.artifacts.ModuleIdentifier;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 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;
 
@@ -42,17 +41,19 @@ public class ResourceVersionLister implements VersionLister {
         this.repository = repository;
     }
 
-    public VersionList getVersionList(final ModuleRevisionId moduleRevisionId) {
+    public VersionList getVersionList(final ModuleIdentifier module) {
         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);
+            public void visit(ResourcePattern resourcePattern, ModuleVersionArtifactMetaData artifact) throws ResourceException {
+                String partiallyResolvedPattern = resourcePattern.toVersionListPattern(artifact);
                 LOGGER.debug("Listing all in {}", partiallyResolvedPattern);
                 try {
                     List<String> versionStrings = listRevisionToken(partiallyResolvedPattern);
-                    add(versionStrings);
-                } catch (ResourceNotFoundException e) {
+                    for (String versionString : versionStrings) {
+                        add(new ListedVersion(versionString, resourcePattern));
+                    }
+                } catch (ResourceException e) {
                     throw e;
                 } catch (Exception e) {
                     throw new ResourceException(String.format("Could not list versions using %s.", resourcePattern), e);
@@ -78,7 +79,7 @@ public class ResourceVersionLister implements VersionLister {
                     }
                     List<String> all = repository.list(revisionParentFolder);
                     if (all == null) {
-                        throw new ResourceNotFoundException(String.format("Cannot list versions from %s.", revisionParentFolder));
+                        return Collections.emptyList();
                     }
                     LOGGER.debug("found {} urls", all.size());
                     Pattern regexPattern = createRegexPattern(pattern, parentFolderSlashIndex);
@@ -142,7 +143,7 @@ public class ResourceVersionLister implements VersionLister {
                 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));
+                    return Collections.emptyList();
                 }
                 LOGGER.debug("found {} resources", fullPaths.size());
                 return extractVersionInfoFromPaths(fullPaths);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VerifyingExternalResourceDownloader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VerifyingExternalResourceDownloader.java
new file mode 100644
index 0000000..c3dd43a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VerifyingExternalResourceDownloader.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.ChecksumHelper;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.RepositoryArtifactCache;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
+import org.gradle.util.GFileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+class VerifyingExternalResourceDownloader implements RepositoryArtifactCache.ExternalResourceDownloader {
+    private static final Logger LOGGER = LoggerFactory.getLogger(VerifyingExternalResourceDownloader.class);
+
+    private final ExternalResourceRepository repository;
+    private final String[] checksumAlgorithms;
+
+    VerifyingExternalResourceDownloader(String[] checksumAlgorithms, ExternalResourceRepository repository) {
+        this.checksumAlgorithms = checksumAlgorithms;
+        this.repository = repository;
+    }
+
+    public void download(ExternalResource resource, File dest) throws IOException {
+        get(resource, dest);
+        verifyChecksums(resource, dest);
+    }
+
+    private void get(ExternalResource resource, File destination) throws IOException {
+        LOGGER.debug("Downloading {} to {}", resource.getName(), destination);
+        if (destination.getParentFile() != null) {
+            GFileUtils.mkdirs(destination.getParentFile());
+        }
+
+        try {
+            resource.writeTo(destination);
+        } finally {
+            resource.close();
+        }
+    }
+
+    private void verifyChecksums(ExternalResource resource, File dest) throws IOException {
+        boolean checked = false;
+        for (int i = 0; i < checksumAlgorithms.length && !checked; i++) {
+            checked = check(resource, dest, checksumAlgorithms[i]);
+        }
+    }
+
+    private boolean check(ExternalResource resource, File dest, String algorithm) throws IOException {
+        if (!ChecksumHelper.isKnownAlgorithm(algorithm)) {
+            throw new IllegalArgumentException("Unknown checksum algorithm: " + algorithm);
+        }
+
+        ExternalResource checksumResource = repository.getResource(resource.getName() + "." + algorithm);
+        if (checksumResource != null && checksumResource.exists()) {
+            LOGGER.debug(algorithm + " file found for " + resource + ": checking...");
+            File csFile = File.createTempFile("ivytmp", algorithm);
+            try {
+                get(checksumResource, csFile);
+                ChecksumHelper.check(dest, csFile, algorithm);
+                LOGGER.debug("{} OK for {}", algorithm, resource);
+                return true;
+            } finally {
+                csFile.delete();
+            }
+        } else {
+            return false;
+        }
+    }
+}
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
index 16bf49d..588c834 100644
--- 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
@@ -16,10 +16,10 @@
 
 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.artifacts.ivyservice.ivyresolve.Versioned;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
 import org.gradle.api.internal.resource.ResourceException;
-import org.gradle.api.internal.resource.ResourceNotFoundException;
 
 import java.util.List;
 import java.util.Set;
@@ -28,14 +28,47 @@ public interface VersionList {
     /**
      * <p>Adds those versions available for the given pattern.</p>
      *
-     * @throws ResourceNotFoundException If information for versions cannot be found.
+     * If no versions are listed with the given pattern, then no versions are added.
+     * 
      * @throws ResourceException If information for versions cannot be loaded.
      */
-    void visit(ResourcePattern pattern, Artifact artifact) throws ResourceNotFoundException, ResourceException;
+    void visit(ResourcePattern pattern, ModuleVersionArtifactMetaData artifact) throws ResourceException;
 
-    Set<String> getVersionStrings();
+    Set<ListedVersion> getVersions();
 
     boolean isEmpty();
 
-    List<String> sortLatestFirst(LatestStrategy latestStrategy);
+    List<ListedVersion> sortLatestFirst(LatestStrategy latestStrategy);
+
+    class ListedVersion implements Versioned {
+        private String version;
+        private ResourcePattern pattern;
+
+        public ListedVersion(String version, ResourcePattern pattern) {
+            this.pattern = pattern;
+            this.version = version;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        /**
+         * @return The pattern that can be used to load this version.
+         */
+        public ResourcePattern getPattern() {
+            return pattern;
+        }
+
+        public boolean equals(Object other) {
+            if (!(other instanceof ListedVersion)) {
+                return false;
+            }
+            return version.equals(((ListedVersion) other).getVersion());
+        }
+
+        public int hashCode() {
+            return version.hashCode();
+        }
+    }
 }
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
index 975c336..966950e 100644
--- 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
@@ -16,11 +16,11 @@
 
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleIdentifier;
 
 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.
+     * Creates an empty version list for the given module. Call {@link VersionList#visit(ResourcePattern, org.apache.ivy.core.module.descriptor.Artifact)} to search for versions.
      */
-    VersionList getVersionList(ModuleRevisionId moduleRevisionId);
+    VersionList getVersionList(ModuleIdentifier module);
 }
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 3a645bc..7a63dcb 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
@@ -15,35 +15,39 @@
  */
 package org.gradle.api.internal.artifacts.repositories.transport;
 
-import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.RepositoryArtifactCache;
 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.file.TemporaryFileProvider;
 import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.BuildCommencedTimeProvider;
 
 public class RepositoryTransportFactory {
-    private final RepositoryCacheManager downloadingCacheManager;
+    private final RepositoryArtifactCache downloadingCacheManager;
     private final TemporaryFileProvider temporaryFileProvider;
     private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
-    private final RepositoryCacheManager localCacheManager;
+    private final RepositoryArtifactCache localCacheManager;
     private final ProgressLoggerFactory progressLoggerFactory;
+    private final BuildCommencedTimeProvider timeProvider;
 
     public RepositoryTransportFactory(ProgressLoggerFactory progressLoggerFactory,
-                                      RepositoryCacheManager localCacheManager,
-                                      RepositoryCacheManager downloadingCacheManager,
+                                      RepositoryArtifactCache localCacheManager,
+                                      RepositoryArtifactCache downloadingCacheManager,
                                       TemporaryFileProvider temporaryFileProvider,
-                                      CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+                                      CachedExternalResourceIndex<String> cachedExternalResourceIndex,
+                                      BuildCommencedTimeProvider timeProvider) {
         this.progressLoggerFactory = progressLoggerFactory;
         this.localCacheManager = localCacheManager;
         this.downloadingCacheManager = downloadingCacheManager;
         this.temporaryFileProvider = temporaryFileProvider;
         this.cachedExternalResourceIndex = cachedExternalResourceIndex;
+        this.timeProvider = timeProvider;
     }
 
     public RepositoryTransport createHttpTransport(String name, PasswordCredentials credentials) {
-        return new HttpTransport(name, credentials, downloadingCacheManager, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex);
+        return new HttpTransport(name, credentials, downloadingCacheManager, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex, timeProvider);
     }
 
     public RepositoryTransport createFileTransport(String name) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/AbstractSoftwareArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/AbstractSoftwareArtifact.java
new file mode 100644
index 0000000..f3e3460
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/AbstractSoftwareArtifact.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+
+import java.io.File;
+
+public abstract class AbstractSoftwareArtifact implements SoftwareArtifact {
+    private final File file;
+    private final GradleException failure;
+
+    protected AbstractSoftwareArtifact(File file) {
+        this.file = file;
+        failure = null;
+    }
+
+    protected AbstractSoftwareArtifact(GradleException failure) {
+        file = null;
+        this.failure = failure;
+    }
+
+    public File getFile() throws GradleException {
+        assertNoFailure();
+        return file;
+    }
+
+    public GradleException getFailure() {
+        return failure;
+    }
+
+    private void assertNoFailure() {
+        if (failure != null) {
+            throw failure;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/AbstractSoftwareComponent.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/AbstractSoftwareComponent.java
new file mode 100644
index 0000000..829b214
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/AbstractSoftwareComponent.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.resolution.SoftwareComponent;
+
+public abstract class AbstractSoftwareComponent implements SoftwareComponent {
+    private final ComponentIdentifier componentId;
+
+    protected AbstractSoftwareComponent(ComponentIdentifier componentId) {
+        this.componentId = componentId;
+    }
+
+    public ComponentIdentifier getId() {
+        return componentId;
+    }
+
+    @Override
+    public final boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        return componentId.equals(((AbstractSoftwareComponent) other).componentId);
+    }
+
+    @Override
+    public final int hashCode() {
+        return componentId.hashCode();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/ComponentMetaDataArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/ComponentMetaDataArtifact.java
new file mode 100644
index 0000000..f9d42d9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/ComponentMetaDataArtifact.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+
+public interface ComponentMetaDataArtifact extends SoftwareArtifact {
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQuery.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQuery.java
new file mode 100644
index 0000000..dba4a2a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQuery.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import com.google.common.collect.Sets;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.resolution.*;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ErrorHandlingArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChain;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData;
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData;
+import org.gradle.api.internal.artifacts.metadata.DefaultDependencyMetaData;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
+import org.gradle.internal.Factory;
+import org.gradle.internal.Transformers;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultArtifactResolutionQuery implements ArtifactResolutionQuery {
+    private final ConfigurationContainerInternal configurationContainer;
+    private final RepositoryHandler repositoryHandler;
+    private final ResolveIvyFactory ivyFactory;
+    private final ModuleMetadataProcessor metadataProcessor;
+    private final CacheLockingManager lockingManager;
+
+    private Set<ComponentIdentifier> componentIds = Sets.newLinkedHashSet();
+    private Class<? extends SoftwareComponent> componentType;
+    private Set<Class<? extends SoftwareArtifact>> artifactTypes = Sets.newLinkedHashSet();
+
+    public DefaultArtifactResolutionQuery(ConfigurationContainerInternal configurationContainer, RepositoryHandler repositoryHandler,
+                                          ResolveIvyFactory ivyFactory, ModuleMetadataProcessor metadataProcessor, CacheLockingManager lockingManager) {
+        this.configurationContainer = configurationContainer;
+        this.repositoryHandler = repositoryHandler;
+        this.ivyFactory = ivyFactory;
+        this.metadataProcessor = metadataProcessor;
+        this.lockingManager = lockingManager;
+    }
+
+    public ArtifactResolutionQuery forComponents(Iterable<? extends ComponentIdentifier> componentIds) {
+        CollectionUtils.addAll(this.componentIds, componentIds);
+        return this;
+    }
+
+    public ArtifactResolutionQuery forComponents(ComponentIdentifier... componentIds) {
+        CollectionUtils.addAll(this.componentIds, componentIds);
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends SoftwareComponent, U extends SoftwareArtifact> ArtifactResolutionQuery withArtifacts(Class<T> componentType, Class<U>... artifactTypes) {
+        this.componentType = componentType;
+        if (artifactTypes.length == 0) {
+            this.artifactTypes = (Set) Sets.newHashSet(JvmLibrarySourcesArtifact.class, JvmLibraryJavadocArtifact.class);
+        } else {
+            this.artifactTypes.addAll(Arrays.asList(artifactTypes));
+        }
+        return this;
+    }
+
+    // TODO:DAZ This is ugly and needs a major cleanup and unit tests
+    // TODO:DAZ Also need to add a 'result' layer to the api
+    public ArtifactResolutionQueryResult execute() {
+        List<ResolutionAwareRepository> repositories = CollectionUtils.collect(repositoryHandler, Transformers.cast(ResolutionAwareRepository.class));
+        ConfigurationInternal configuration = configurationContainer.detachedConfiguration();
+        final RepositoryChain repositoryChain = ivyFactory.create(configuration, repositories, metadataProcessor);
+        final ArtifactResolver artifactResolver = new ErrorHandlingArtifactResolver(repositoryChain.getArtifactResolver());
+
+        return lockingManager.useCache("resolve artifacts", new Factory<ArtifactResolutionQueryResult>() {
+            public ArtifactResolutionQueryResult create() {
+                Set<JvmLibrary> jvmLibraries = Sets.newHashSet();
+                Set<UnresolvedSoftwareComponent> unresolvedComponents = Sets.newHashSet();
+
+                for (ComponentIdentifier componentId : componentIds) {
+                    if (!(componentId instanceof ModuleComponentIdentifier)) {
+                        throw new IllegalArgumentException(String.format("Cannot resolve the artifacts for component %s with unsupported type %s.", componentId.getDisplayName(), componentId.getClass().getName()));
+                    }
+                    ModuleComponentIdentifier moduleComponentId = (ModuleComponentIdentifier) componentId;
+                    BuildableComponentResolveResult moduleResolveResult = new DefaultBuildableComponentResolveResult();
+                    repositoryChain.getDependencyResolver().resolve(new DefaultDependencyMetaData(new DefaultDependencyDescriptor(toModuleRevisionId(moduleComponentId), true)), moduleResolveResult);
+
+                    try {
+                        DefaultJvmLibrary jvmLibrary = buildJvmLibrary(moduleResolveResult.getMetaData(), artifactResolver);
+                        jvmLibraries.add(jvmLibrary);
+                    } catch (Throwable t) {
+                        unresolvedComponents.add(new DefaultUnresolvedSoftwareComponent(moduleComponentId, t));
+                    }
+                }
+
+                return new DefaultArtifactResolutionQueryResult(jvmLibraries, unresolvedComponents);
+            }
+        });
+    }
+
+    private DefaultJvmLibrary buildJvmLibrary(ComponentMetaData component, ArtifactResolver artifactResolver) {
+        Set<JvmLibraryJavadocArtifact> javadocs = Sets.newLinkedHashSet();
+        Set<JvmLibrarySourcesArtifact> sources = Sets.newLinkedHashSet();
+        for (Class<? extends SoftwareArtifact> artifactType : artifactTypes) {
+            if (artifactType == JvmLibraryJavadocArtifact.class) {
+                addJvmLibraryArtifacts(javadocs, JvmLibraryJavadocArtifact.class, DefaultJvmLibraryJavadocArtifact.class, component, artifactResolver);
+            } else if (artifactType == JvmLibrarySourcesArtifact.class) {
+                addJvmLibraryArtifacts(sources, JvmLibrarySourcesArtifact.class, DefaultJvmLibrarySourcesArtifact.class, component, artifactResolver);
+            } else {
+                throw new IllegalArgumentException(String.format("Cannot resolve artifacts with unsupported type %s.", artifactType.getName()));
+            }
+        }
+        return new DefaultJvmLibrary(component.getComponentId(), sources, javadocs);
+    }
+
+    private <T extends JvmLibraryArtifact> void addJvmLibraryArtifacts(Set<T> artifacts, Class<T> type, Class<? extends T> implType, ComponentMetaData component, ArtifactResolver artifactResolver) {
+        ArtifactResolveContext context = new ArtifactTypeResolveContext(type);
+        BuildableArtifactSetResolveResult artifactSetResolveResult = new DefaultBuildableArtifactSetResolveResult();
+        artifactResolver.resolveModuleArtifacts(component, context, artifactSetResolveResult);
+
+        Instantiator instantiator = new DirectInstantiator();
+        for (ComponentArtifactMetaData artifactMetaData : artifactSetResolveResult.getArtifacts()) {
+            BuildableArtifactResolveResult resolveResult = new DefaultBuildableArtifactResolveResult();
+            artifactResolver.resolveArtifact(artifactMetaData, component.getSource(), resolveResult);
+            if (resolveResult.getFailure() != null) {
+                artifacts.add(instantiator.newInstance(implType, resolveResult.getFailure()));
+            } else {
+                artifacts.add(instantiator.newInstance(implType, resolveResult.getFile()));
+            }
+        }
+    }
+
+    private ModuleRevisionId toModuleRevisionId(ModuleComponentIdentifier componentId) {
+        return ModuleRevisionId.newInstance(componentId.getGroup(), componentId.getModule(), componentId.getVersion());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQueryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQueryFactory.java
new file mode 100644
index 0000000..ec9844a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQueryFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.resolution.ArtifactResolutionQuery;
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ArtifactResolutionQueryFactory;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
+
+public class DefaultArtifactResolutionQueryFactory implements ArtifactResolutionQueryFactory {
+    private final ConfigurationContainerInternal configurationContainer;
+    private final RepositoryHandler repositoryHandler;
+    private final ResolveIvyFactory ivyFactory;
+    private final ModuleMetadataProcessor metadataProcessor;
+    private final CacheLockingManager cacheLockingManager;
+
+    public DefaultArtifactResolutionQueryFactory(ConfigurationContainerInternal configurationContainer, RepositoryHandler repositoryHandler,
+                                                 ResolveIvyFactory ivyFactory, ModuleMetadataProcessor metadataProcessor,
+                                                 CacheLockingManager cacheLockingManager) {
+        this.configurationContainer = configurationContainer;
+        this.repositoryHandler = repositoryHandler;
+        this.ivyFactory = ivyFactory;
+        this.metadataProcessor = metadataProcessor;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    public ArtifactResolutionQuery createArtifactResolutionQuery() {
+        return new DefaultArtifactResolutionQuery(configurationContainer, repositoryHandler, ivyFactory, metadataProcessor, cacheLockingManager);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQueryResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQueryResult.java
new file mode 100644
index 0000000..f06fd9f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultArtifactResolutionQueryResult.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import org.gradle.api.artifacts.resolution.ArtifactResolutionQueryResult;
+import org.gradle.api.artifacts.resolution.SoftwareComponent;
+import org.gradle.api.artifacts.resolution.UnresolvedSoftwareComponent;
+
+import java.util.Set;
+
+public class DefaultArtifactResolutionQueryResult implements ArtifactResolutionQueryResult {
+    private final Set<? extends SoftwareComponent> components;
+    private final Set<UnresolvedSoftwareComponent> unresolvedComponents;
+
+    public DefaultArtifactResolutionQueryResult(Set<? extends SoftwareComponent> components, Set<UnresolvedSoftwareComponent> unresolvedComponents) {
+        this.components = components;
+        this.unresolvedComponents = unresolvedComponents;
+    }
+
+    public Set<? extends SoftwareComponent> getComponents() {
+        return components;
+    }
+
+    public <T extends SoftwareComponent> Set<T> getComponents(final Class<T> type) {
+        return Sets.newHashSet(Iterables.filter(components, type));
+    }
+
+    public Set<UnresolvedSoftwareComponent> getUnresolvedComponents() {
+        return unresolvedComponents;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibrary.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibrary.java
new file mode 100644
index 0000000..c17ae53
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibrary.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.resolution.JvmLibrary;
+import org.gradle.api.artifacts.resolution.JvmLibraryJavadocArtifact;
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultJvmLibrary extends AbstractSoftwareComponent implements JvmLibrary {
+    private final Set<JvmLibrarySourcesArtifact> sourceArtifacts;
+    private final Set<JvmLibraryJavadocArtifact> javadocArtifacts;
+
+    public DefaultJvmLibrary(ComponentIdentifier componentId,
+                             Set<? extends JvmLibrarySourcesArtifact> sourceArtifacts,
+                             Set<? extends JvmLibraryJavadocArtifact> javadocArtifacts) {
+        super(componentId);
+        this.sourceArtifacts = new LinkedHashSet<JvmLibrarySourcesArtifact>(sourceArtifacts);
+        this.javadocArtifacts = new LinkedHashSet<JvmLibraryJavadocArtifact>(javadocArtifacts);
+    }
+
+    public Set<JvmLibrarySourcesArtifact> getSourcesArtifacts() {
+        return sourceArtifacts;
+    }
+
+    public Set<JvmLibraryJavadocArtifact> getJavadocArtifacts() {
+        return javadocArtifacts;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibraryJavadocArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibraryJavadocArtifact.java
new file mode 100644
index 0000000..f52b904
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibraryJavadocArtifact.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.resolution.JvmLibraryJavadocArtifact;
+
+import java.io.File;
+
+public class DefaultJvmLibraryJavadocArtifact extends AbstractSoftwareArtifact implements JvmLibraryJavadocArtifact {
+    public DefaultJvmLibraryJavadocArtifact(File file) {
+        super(file);
+    }
+
+    public DefaultJvmLibraryJavadocArtifact(GradleException failure) {
+        super(failure);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibrarySourcesArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibrarySourcesArtifact.java
new file mode 100644
index 0000000..31082c8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultJvmLibrarySourcesArtifact.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact;
+
+import java.io.File;
+
+public class DefaultJvmLibrarySourcesArtifact extends AbstractSoftwareArtifact implements JvmLibrarySourcesArtifact {
+    public DefaultJvmLibrarySourcesArtifact(File file) {
+        super(file);
+    }
+
+    public DefaultJvmLibrarySourcesArtifact(GradleException failure) {
+        super(failure);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultUnresolvedSoftwareComponent.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultUnresolvedSoftwareComponent.java
new file mode 100644
index 0000000..96a026b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/DefaultUnresolvedSoftwareComponent.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.resolution.UnresolvedSoftwareComponent;
+
+public class DefaultUnresolvedSoftwareComponent implements UnresolvedSoftwareComponent {
+    private final ComponentIdentifier id;
+    private final Throwable failure;
+
+    public DefaultUnresolvedSoftwareComponent(ComponentIdentifier id, Throwable failure) {
+        this.id = id;
+        this.failure = failure;
+    }
+
+    public ComponentIdentifier getId() {
+        return id;
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/IvyDescriptorArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/IvyDescriptorArtifact.java
new file mode 100644
index 0000000..f00b5ae
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/IvyDescriptorArtifact.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+
+public interface IvyDescriptorArtifact extends SoftwareArtifact {
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/MavenPomArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/MavenPomArtifact.java
new file mode 100644
index 0000000..f47e69d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/resolution/MavenPomArtifact.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.resolution;
+
+import org.gradle.api.artifacts.resolution.SoftwareArtifact;
+
+public interface MavenPomArtifact extends SoftwareArtifact {
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java
index 4ccb18f..b60493f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java
@@ -16,18 +16,15 @@
 
 package org.gradle.api.internal.artifacts.result;
 
-import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
 import org.gradle.api.artifacts.result.DependencyResult;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 
-/**
- * by Szczepan Faber, created at: 7/26/12
- */
 public class AbstractDependencyResult implements DependencyResult {
-    private final ModuleVersionSelector requested;
-    private final ResolvedModuleVersionResult from;
+    private final ComponentSelector requested;
+    private final ResolvedComponentResult from;
 
-    public AbstractDependencyResult(ModuleVersionSelector requested, ResolvedModuleVersionResult from) {
+    public AbstractDependencyResult(ComponentSelector requested, ResolvedComponentResult from) {
         assert requested != null;
         assert from != null;
 
@@ -35,11 +32,11 @@ public class AbstractDependencyResult implements DependencyResult {
         this.requested = requested;
     }
 
-    public ModuleVersionSelector getRequested() {
+    public ComponentSelector getRequested() {
         return requested;
     }
 
-    public ResolvedModuleVersionResult getFrom() {
+    public ResolvedComponentResult getFrom() {
         return from;
     }
 }
\ No newline at end of file
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
index 09d2ce0..37e63f2 100644
--- 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
@@ -20,29 +20,27 @@ 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.ResolvedComponentResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.internal.Factory;
 
 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;
+    private Factory<ResolvedComponentResult> rootSource;
 
-    public DefaultResolutionResult(ResolvedModuleVersionResult root) {
-        assert root != null;
-        this.root = root;
+    public DefaultResolutionResult(Factory<ResolvedComponentResult> rootSource) {
+        assert rootSource != null;
+        this.rootSource = rootSource;
     }
 
-    public ResolvedModuleVersionResult getRoot() {
-        return root;
+    public ResolvedComponentResult getRoot() {
+        return rootSource.create();
     }
 
     public Set<? extends DependencyResult> getAllDependencies() {
@@ -56,16 +54,16 @@ public class DefaultResolutionResult implements ResolutionResult {
     }
 
     public void allDependencies(Action<? super DependencyResult> action) {
-        eachElement(root, Actions.doNothing(), action, new HashSet<ResolvedModuleVersionResult>());
+        eachElement(getRoot(), Actions.doNothing(), action, new HashSet<ResolvedComponentResult>());
     }
 
     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) {
+    private void eachElement(ResolvedComponentResult node,
+                             Action<? super ResolvedComponentResult> moduleAction, Action<? super DependencyResult> dependencyAction,
+                             Set<ResolvedComponentResult> visited) {
         if (!visited.add(node)) {
             return;
         }
@@ -78,18 +76,18 @@ public class DefaultResolutionResult implements ResolutionResult {
         }
     }
 
-    public Set<ResolvedModuleVersionResult> getAllModuleVersions() {
-        final Set<ResolvedModuleVersionResult> out = new LinkedHashSet<ResolvedModuleVersionResult>();
-        eachElement(root, Actions.doNothing(), Actions.doNothing(), out);
+    public Set<ResolvedComponentResult> getAllComponents() {
+        final Set<ResolvedComponentResult> out = new LinkedHashSet<ResolvedComponentResult>();
+        eachElement(getRoot(), 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 allComponents(final Action<? super ResolvedComponentResult> action) {
+        eachElement(getRoot(), action, Actions.doNothing(), new HashSet<ResolvedComponentResult>());
     }
 
-    public void allModuleVersions(final Closure closure) {
-        allModuleVersions(new ClosureBackedAction<ResolvedModuleVersionResult>(closure));
+    public void allComponents(final Closure closure) {
+        allComponents(new ClosureBackedAction<ResolvedComponentResult>(closure));
     }
 
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedComponentResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedComponentResult.java
new file mode 100644
index 0000000..bc27186
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedComponentResult.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.internal.artifacts.result;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultResolvedComponentResult implements ResolvedComponentResult {
+    private final ModuleVersionIdentifier id;
+    private final Set<DependencyResult> dependencies = new LinkedHashSet<DependencyResult>();
+    private final Set<ResolvedDependencyResult> dependents = new LinkedHashSet<ResolvedDependencyResult>();
+    private final ComponentSelectionReason selectionReason;
+    private final ComponentIdentifier componentId;
+
+    public DefaultResolvedComponentResult(ModuleVersionIdentifier id, ComponentSelectionReason selectionReason, ComponentIdentifier componentId) {
+        assert id != null;
+        assert selectionReason != null;
+
+        this.id = id;
+        this.selectionReason = selectionReason;
+        this.componentId = componentId;
+    }
+
+    public ComponentIdentifier getId() {
+        return componentId;
+    }
+
+    public Set<DependencyResult> getDependencies() {
+        return Collections.unmodifiableSet(dependencies);
+    }
+
+    public Set<ResolvedDependencyResult> getDependents() {
+        return Collections.unmodifiableSet(dependents);
+    }
+
+    public DefaultResolvedComponentResult addDependency(DependencyResult dependency) {
+        this.dependencies.add(dependency);
+        return this;
+    }
+
+    public DefaultResolvedComponentResult addDependent(ResolvedDependencyResult dependent) {
+        this.dependents.add(dependent);
+        return this;
+    }
+
+    public ComponentSelectionReason getSelectionReason() {
+        return selectionReason;
+    }
+
+    @Nullable
+    public ModuleVersionIdentifier getModuleVersion() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return getId().getDisplayName();
+    }
+}
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
index 074e5e4..1e2f8bb 100644
--- 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
@@ -16,22 +16,19 @@
 
 package org.gradle.api.internal.artifacts.result;
 
-import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 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 extends AbstractDependencyResult implements ResolvedDependencyResult {
-    private final ResolvedModuleVersionResult selected;
+    private final ResolvedComponentResult selected;
 
-    public DefaultResolvedDependencyResult(ModuleVersionSelector requested, ResolvedModuleVersionResult selected, ResolvedModuleVersionResult from) {
+    public DefaultResolvedDependencyResult(ComponentSelector requested, ResolvedComponentResult selected, ResolvedComponentResult from) {
         super(requested, from);
         this.selected = selected;
     }
 
-    public ResolvedModuleVersionResult getSelected() {
+    public ResolvedComponentResult getSelected() {
         return selected;
     }
 
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
deleted file mode 100644
index 0bff383..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.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.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
index 6b95007..3374c98 100644
--- 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
@@ -17,20 +17,20 @@
 package org.gradle.api.internal.artifacts.result;
 
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 
-/**
- * by Szczepan Faber, created at: 7/26/12
- */
 public class DefaultUnresolvedDependencyResult extends AbstractDependencyResult implements UnresolvedDependencyResult {
-    private final ModuleVersionSelectionReason reason;
+    private final ComponentSelectionReason reason;
     private final ModuleVersionResolveException failure;
 
-    public DefaultUnresolvedDependencyResult(ModuleVersionSelector requested, ModuleVersionSelectionReason reason,
-                                             ResolvedModuleVersionResult from, ModuleVersionResolveException failure) {
+    public DefaultUnresolvedDependencyResult(ComponentSelector requested, ComponentSelectionReason reason,
+                                             ResolvedComponentResult from, ModuleVersionResolveException failure) {
         super(requested, from);
         this.reason = reason;
         this.failure = failure;
@@ -40,11 +40,12 @@ public class DefaultUnresolvedDependencyResult extends AbstractDependencyResult
         return failure;
     }
 
-    public ModuleVersionSelector getAttempted() {
-        return failure.getSelector();
+    public ModuleComponentSelector getAttempted() {
+        ModuleVersionSelector moduleVersionSelector = failure.getSelector();
+        return DefaultModuleComponentSelector.newSelector(moduleVersionSelector.getGroup(), moduleVersionSelector.getName(), moduleVersionSelector.getVersion());
     }
 
-    public ModuleVersionSelectionReason getAttemptedReason() {
+    public ComponentSelectionReason getAttemptedReason() {
         return reason;
     }
 
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 72610c1..1142f22 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
@@ -16,11 +16,16 @@
 package org.gradle.api.internal.externalresource;
 
 import org.apache.commons.io.IOUtils;
-import org.apache.ivy.plugins.repository.Resource;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
 
 import java.io.*;
 
 public abstract class AbstractExternalResource implements ExternalResource {
+    /**
+     * Opens an unbuffered input stream to read the contents of this resource.
+     */
+    protected abstract InputStream openStream() throws IOException;
 
     public void writeTo(File destination) throws IOException {
         FileOutputStream output = new FileOutputStream(destination);
@@ -40,9 +45,22 @@ public abstract class AbstractExternalResource implements ExternalResource {
         }
     }
 
+    public void withContent(Action<? super InputStream> readAction) throws IOException {
+        InputStream input = openStream();
+        try {
+            readAction.execute(input);
+        } finally {
+            input.close();
+        }
+    }
 
-    public Resource clone(String cloneName) {
-        throw new UnsupportedOperationException();
+    public <T> T withContent(Transformer<? extends T, ? super InputStream> readAction) throws IOException {
+        InputStream input = openStream();
+        try {
+            return readAction.transform(input);
+        } finally {
+            input.close();
+        }
     }
 
     public void close() throws IOException {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/DefaultLocallyAvailableExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/DefaultLocallyAvailableExternalResource.java
new file mode 100644
index 0000000..7cb2d2b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/DefaultLocallyAvailableExternalResource.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.externalresource;
+
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.internal.hash.HashValue;
+
+public class DefaultLocallyAvailableExternalResource extends LocalFileStandInExternalResource implements LocallyAvailableExternalResource {
+    private final LocallyAvailableResource locallyAvailableResource;
+
+    public DefaultLocallyAvailableExternalResource(String source, LocallyAvailableResource locallyAvailableResource) {
+        this(source, locallyAvailableResource, null);
+    }
+
+    public DefaultLocallyAvailableExternalResource(String source, LocallyAvailableResource locallyAvailableResource, ExternalResourceMetaData metaData) {
+        super(source, locallyAvailableResource.getFile(), metaData);
+        this.locallyAvailableResource = locallyAvailableResource;
+    }
+
+    @Override
+    public String toString() {
+        return locallyAvailableResource.toString();
+    }
+
+    public LocallyAvailableResource getLocalResource() {
+        return locallyAvailableResource;
+    }
+
+    @Override
+    public long getContentLength() {
+        return locallyAvailableResource.getContentLength();
+    }
+
+    @Override
+    protected HashValue getLocalFileSha1() {
+        return locallyAvailableResource.getSha1();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java
index a588007..1e2ce4a 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
@@ -15,24 +15,74 @@
  */
 package org.gradle.api.internal.externalresource;
 
-import org.apache.ivy.plugins.repository.Resource;
-import org.gradle.api.Nullable;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 
-public interface ExternalResource extends Resource {
+public interface ExternalResource {
+    /**
+     * Get the name of the resource.
+     */
+    public String getName();
+
+    /**
+     * Get the date the resource was last modified
+     *
+     * @return A <code>long</code> value representing the time the file was last modified,
+     *         measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), or
+     *         <code>0L</code> if the file does not exist or if an I/O error occurs.
+     */
+    public long getLastModified();
+
+    /**
+     * Get the resource size
+     *
+     * @return a <code>long</code> value representing the size of the resource in bytes.
+     */
+    public long getContentLength();
+
+    /**
+     * Determine if the resource is available.
+     * </p>
+     * Note that this method only checks for availability, not for actual existence.
+     *
+     * @return <code>boolean</code> value indicating if the resource is available.
+     */
+    public boolean exists();
+
+    /**
+     * Is this resource local to this host, i.e. is it on the file system?
+     *
+     * @return <code>boolean</code> value indicating if the resource is local.
+     */
+    public boolean isLocal();
+
+    /**
+     * Copies the contents of this resource to the given file.
+     */
     void writeTo(File destination) throws IOException;
 
     /**
-     * Writes to the given stream. Does not close the stream.
+     * Copies the contents of this resource to the given stream. Does not close the stream.
      */
     void writeTo(OutputStream destination) throws IOException;
 
+    /**
+     * Executes the given action against the contents of this resource.
+     */
+    void withContent(Action<? super InputStream> readAction) throws IOException;
+
+    /**
+     * Executes the given action against the contents of this resource.
+     */
+    <T> T withContent(Transformer<? extends T, ? super InputStream> readAction) throws IOException;
+
     void close() throws IOException;
 
-    @Nullable
     ExternalResourceMetaData getMetaData();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
index dea8be3..26f60f6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
@@ -18,8 +18,8 @@ package org.gradle.api.internal.externalresource;
 
 import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.util.hash.HashUtil;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.internal.hash.HashValue;
 
 import java.io.File;
 import java.io.FileInputStream;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java
index 0b4cb72..0d7e582 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,30 +16,12 @@
 
 package org.gradle.api.internal.externalresource;
 
-import org.gradle.api.internal.externalresource.local.LocallyAvailableResource;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 
-public class LocallyAvailableExternalResource extends LocalFileStandInExternalResource {
-
-    private final LocallyAvailableResource locallyAvailableResource;
-
-    public LocallyAvailableExternalResource(String source, LocallyAvailableResource locallyAvailableResource) {
-        this(source, locallyAvailableResource, null);
-    }
-
-    public LocallyAvailableExternalResource(String source, LocallyAvailableResource locallyAvailableResource, ExternalResourceMetaData metaData) {
-        super(source, locallyAvailableResource.getFile(), metaData);
-        this.locallyAvailableResource = locallyAvailableResource;
-    }
-
-    @Override
-    public long getContentLength() {
-        return locallyAvailableResource.getContentLength();
-    }
-
-    @Override
-    protected HashValue getLocalFileSha1() {
-        return locallyAvailableResource.getSha1();
-    }
+/**
+ * Represents an external resource whose meta-data and content is available locally. The content and meta-data may be a copy of some original resource and the original may or may not be a local
+ * resource.
+ */
+public interface LocallyAvailableExternalResource extends ExternalResource {
+    LocallyAvailableResource getLocalResource();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/UrlExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/UrlExternalResource.java
new file mode 100644
index 0000000..f47a3bf
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/UrlExternalResource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource;
+
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+public class UrlExternalResource extends AbstractExternalResource {
+    private final URL url;
+    private final URLConnection connection;
+    private final DefaultExternalResourceMetaData metaData;
+
+    public UrlExternalResource(URL url) throws IOException {
+        this.url = url;
+        connection = url.openConnection();
+        metaData = new DefaultExternalResourceMetaData(url.toString(), connection.getLastModified(), connection.getContentLength(), null, null);
+    }
+
+    public String getName() {
+        return url.toExternalForm();
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        return metaData;
+    }
+
+    public boolean isLocal() {
+        return url.getProtocol().equalsIgnoreCase("file");
+    }
+
+    public long getContentLength() {
+        return connection.getContentLength();
+    }
+
+    public long getLastModified() {
+        return connection.getLastModified();
+    }
+
+    public boolean exists() {
+        return true;
+    }
+
+    public InputStream openStream() throws IOException {
+        return connection.getInputStream();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
index 5dc7061..e2d9109 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
@@ -17,13 +17,11 @@
 package org.gradle.api.internal.externalresource.cached;
 
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
-import org.gradle.internal.TimeProvider;
-
-import java.io.File;
+import org.gradle.util.BuildCommencedTimeProvider;
 
 public class ByUrlCachedExternalResourceIndex extends DefaultCachedExternalResourceIndex<String> {
 
-    public ByUrlCachedExternalResourceIndex(File persistentCacheFile, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+    public ByUrlCachedExternalResourceIndex(String persistentCacheFile, BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
         super(persistentCacheFile, String.class, timeProvider, cacheLockingManager);
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
index 7433766..4264581 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
@@ -18,7 +18,6 @@ package org.gradle.api.internal.externalresource.cached;
 
 import org.gradle.api.Nullable;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.util.hash.HashValue;
 
 import java.util.Date;
 
@@ -37,13 +36,6 @@ public interface CachedExternalResource extends CachedItem {
      */
     long getContentLength();
 
-    /**
-     * Always the actual checksum of the cached file, not the external source.
-     *
-     * @return The hash of the cached file.
-     */
-    HashValue getSha1();
-
     @Nullable
     ExternalResourceMetaData getExternalResourceMetaData();
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java
index 4062d03..b6f0cb7 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java
@@ -17,19 +17,15 @@
 package org.gradle.api.internal.externalresource.cached;
 
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.util.hash.HashUtil;
-import org.gradle.util.hash.HashValue;
 
 import java.io.File;
 import java.io.Serializable;
 import java.util.Date;
 
 public class DefaultCachedExternalResource implements CachedExternalResource, Serializable {
-
     private final File cachedFile;
     private final long cachedAt;
     private final ExternalResourceMetaData externalResourceMetaData;
-    private HashValue sha1;
 
     public DefaultCachedExternalResource(File cachedFile, long cachedAt, ExternalResourceMetaData externalResourceMetaData) {
         this.cachedFile = cachedFile;
@@ -73,15 +69,4 @@ public class DefaultCachedExternalResource implements CachedExternalResource, Se
         return isMissing() ? -1 : cachedFile.length();
     }
 
-    public HashValue getSha1() {
-        if (isMissing()) {
-            return null;
-        } else {
-            if (sha1 == null) {
-                sha1 = HashUtil.createHash(cachedFile, "SHA1");
-            }
-            return sha1;
-        }
-    }
-
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
index 7f36afe..7f51eea 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
@@ -19,17 +19,17 @@ package org.gradle.api.internal.externalresource.cached;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.externalresource.ivy.AbstractCachedIndex;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.internal.TimeProvider;
 import org.gradle.messaging.serialize.DefaultSerializer;
+import org.gradle.util.BuildCommencedTimeProvider;
 
 import java.io.File;
 import java.io.Serializable;
 
 public class DefaultCachedExternalResourceIndex<K extends Serializable> extends AbstractCachedIndex<K, CachedExternalResource> implements CachedExternalResourceIndex<K> {
 
-    private final TimeProvider timeProvider;
+    private final BuildCommencedTimeProvider timeProvider;
 
-    public DefaultCachedExternalResourceIndex(File persistentCacheFile, Class<K> keyType, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+    public DefaultCachedExternalResourceIndex(String persistentCacheFile, Class<K> keyType, BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
         super(persistentCacheFile, new DefaultSerializer<K>(keyType.getClassLoader()), new DefaultSerializer<CachedExternalResource>(CachedExternalResource.class.getClassLoader()), cacheLockingManager);
         this.timeProvider = timeProvider;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java
index 13bf9ad..88bfe6e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java
@@ -25,14 +25,14 @@ import org.gradle.messaging.serialize.Serializer;
 import java.io.File;
 
 abstract public class AbstractCachedIndex<K, V extends CachedItem> {
-    private final File persistentCacheFile;
+    private final String persistentCacheFile;
     private final Serializer<K> keySerializer;
     private final Serializer<V> valueSerializer;
     private final CacheLockingManager cacheLockingManager;
 
     private PersistentIndexedCache<K, V> persistentCache;
 
-    public AbstractCachedIndex(File persistentCacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer, CacheLockingManager cacheLockingManager) {
+    public AbstractCachedIndex(String persistentCacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer, CacheLockingManager cacheLockingManager) {
 
         this.persistentCacheFile = persistentCacheFile;
         this.keySerializer = keySerializer;
@@ -52,7 +52,7 @@ abstract public class AbstractCachedIndex<K, V extends CachedItem> {
     }
 
     private String operationName(String action) {
-        return String.format("%s artifact resolution cache '%s'", action, persistentCacheFile.getName());
+        return String.format("%s artifact resolution cache '%s'", action, persistentCacheFile);
     }
 
     public V lookup(final K key) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java
index 9f297a4..6a225df 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java
@@ -16,23 +16,24 @@
 
 package org.gradle.api.internal.externalresource.ivy;
 
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifierSerializer;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier;
 import org.gradle.api.internal.externalresource.cached.CachedArtifact;
 import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
 import org.gradle.api.internal.externalresource.cached.DefaultCachedArtifact;
-import org.gradle.internal.TimeProvider;
-import org.gradle.messaging.serialize.DataStreamBackedSerializer;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+import org.gradle.util.BuildCommencedTimeProvider;
 
-import java.io.DataInput;
-import java.io.DataOutput;
 import java.io.File;
-import java.io.IOException;
 import java.math.BigInteger;
 
 public class ArtifactAtRepositoryCachedArtifactIndex extends AbstractCachedIndex<ArtifactAtRepositoryKey, CachedArtifact> implements CachedArtifactIndex {
-    private final TimeProvider timeProvider;
+    private final BuildCommencedTimeProvider timeProvider;
 
-    public ArtifactAtRepositoryCachedArtifactIndex(File persistentCacheFile, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+    public ArtifactAtRepositoryCachedArtifactIndex(String persistentCacheFile, BuildCommencedTimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
         super(persistentCacheFile, new ArtifactAtRepositoryKeySerializer(), new CachedArtifactSerializer(), cacheLockingManager);
         this.timeProvider = timeProvider;
     }
@@ -55,47 +56,42 @@ public class ArtifactAtRepositoryCachedArtifactIndex extends AbstractCachedIndex
         return new DefaultCachedArtifact(timeProvider.getCurrentTime(), descriptorHash);
     }
 
-    private static class ArtifactAtRepositoryKeySerializer extends DataStreamBackedSerializer<ArtifactAtRepositoryKey> {
-        @Override
-        public void write(DataOutput dataOutput, ArtifactAtRepositoryKey value) throws IOException {
-            dataOutput.writeUTF(value.getRepositoryId());
-            dataOutput.writeUTF(value.getArtifactId());
+    private static class ArtifactAtRepositoryKeySerializer implements Serializer<ArtifactAtRepositoryKey> {
+        private final Serializer<ModuleVersionArtifactIdentifier> artifactIdSerializer = new ModuleVersionArtifactIdentifierSerializer();
+
+        public void write(Encoder encoder, ArtifactAtRepositoryKey value) throws Exception {
+            encoder.writeString(value.getRepositoryId());
+            artifactIdSerializer.write(encoder, value.getArtifactId());
         }
 
-        @Override
-        public ArtifactAtRepositoryKey read(DataInput dataInput) throws IOException {
-            String repositoryId = dataInput.readUTF();
-            String artifactId = dataInput.readUTF();
-            return new ArtifactAtRepositoryKey(repositoryId, artifactId);
+        public ArtifactAtRepositoryKey read(Decoder decoder) throws Exception {
+            String repositoryId = decoder.readString();
+            ModuleVersionArtifactIdentifier artifactIdentifier = artifactIdSerializer.read(decoder);
+            return new ArtifactAtRepositoryKey(repositoryId, artifactIdentifier);
         }
     }
 
-    private static class CachedArtifactSerializer extends DataStreamBackedSerializer<CachedArtifact> {
-        @Override
-        public void write(DataOutput dataOutput, CachedArtifact value) throws IOException {
-            dataOutput.writeBoolean(value.isMissing());
+    private static class CachedArtifactSerializer implements Serializer<CachedArtifact> {
+        public void write(Encoder encoder, CachedArtifact value) throws Exception {
+            encoder.writeBoolean(value.isMissing());
             if (!value.isMissing()) {
-                dataOutput.writeUTF(value.getCachedFile().getPath());
+                encoder.writeString(value.getCachedFile().getPath());
             }
-            dataOutput.writeLong(value.getCachedAt());
+            encoder.writeLong(value.getCachedAt());
             byte[] hash = value.getDescriptorHash().toByteArray();
-            dataOutput.writeInt(hash.length);
-            dataOutput.write(hash);
+            encoder.writeBinary(hash);
         }
 
-        @Override
-        public CachedArtifact read(DataInput dataInput) throws Exception {
-            boolean isMissing = dataInput.readBoolean();
+        public CachedArtifact read(Decoder decoder) throws Exception {
+            boolean isMissing = decoder.readBoolean();
             File file;
             if (!isMissing) {
-                file = new File(dataInput.readUTF());
+                file = new File(decoder.readString());
             } else {
                 file = null;
             }
-            long createTimestamp = dataInput.readLong();
-            int count = dataInput.readInt();
-            byte[] encodedHash = new byte[count];
-            dataInput.readFully(encodedHash);
+            long createTimestamp = decoder.readLong();
+            byte[] encodedHash = decoder.readBinary();
             BigInteger hash = new BigInteger(encodedHash);
             return new DefaultCachedArtifact(file, createTimestamp, hash);
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
index 760159f..6385f28 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
@@ -16,37 +16,18 @@
 
 package org.gradle.api.internal.externalresource.ivy;
 
-import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier;
 
 public class ArtifactAtRepositoryKey {
     private final String repositoryId;
-    private final String artifactId;
+    private final ModuleVersionArtifactIdentifier artifactId;
 
-    public ArtifactAtRepositoryKey(ModuleVersionRepository repository, ArtifactRevisionId artifactId) {
-        this(repository, getArtifactKey(artifactId));
-    }
-
-    public ArtifactAtRepositoryKey(String repositoryId, String artifactId) {
+    public ArtifactAtRepositoryKey(String repositoryId, ModuleVersionArtifactIdentifier artifactId) {
         this.repositoryId = repositoryId;
         this.artifactId = artifactId;
     }
 
-    private ArtifactAtRepositoryKey(ModuleVersionRepository repository, String artifactPath) {
-        this.repositoryId = repository.getId();
-        this.artifactId = artifactPath;
-    }
-
-    private static String getArtifactKey(ArtifactRevisionId artifactId) {
-        String format = "[organisation]/[module](/[branch])/[revision]/[type]/[artifact](-[classifier])(.[ext])";
-        Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
-        return IvyPatternHelper.substitute(format, dummyArtifact);
-    }
-
-    public String getArtifactId() {
+    public ModuleVersionArtifactIdentifier getArtifactId() {
         return artifactId;
     }
 
@@ -65,11 +46,11 @@ public class ArtifactAtRepositoryKey {
             return false;
         }
         ArtifactAtRepositoryKey other = (ArtifactAtRepositoryKey) o;
-        return toString().equals(other.toString());
+        return repositoryId.equals(other.repositoryId) && artifactId.equals(other.artifactId);
     }
 
     @Override
     public int hashCode() {
-        return toString().hashCode();
+        return repositoryId.hashCode() ^ artifactId.hashCode();
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java
index db6f783..5fcdf96 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java
@@ -16,7 +16,8 @@
 
 package org.gradle.api.internal.externalresource.local;
 
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.internal.hash.HashValue;
 
 import java.util.LinkedList;
 import java.util.List;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResource.java
deleted file mode 100644
index b6ff3d1..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResource.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.externalresource.local;
-
-import org.gradle.util.hash.HashUtil;
-import org.gradle.util.hash.HashValue;
-
-import java.io.File;
-
-public class DefaultLocallyAvailableResource implements LocallyAvailableResource {
-    private final File origin;
-    
-    // Calculated on demand
-    private HashValue sha1;
-    private Long contentLength;
-    private Long lastModified;
-
-    public DefaultLocallyAvailableResource(File origin) {
-        this.origin = origin;
-    }
-
-    public DefaultLocallyAvailableResource(File origin, HashValue sha1) {
-        this(origin);
-        this.sha1 = sha1;
-    }
-
-    public File getFile() {
-        return origin;
-    }
-
-    public HashValue getSha1() {
-        if (sha1 == null) {
-            this.sha1 = HashUtil.sha1(origin);
-        }
-        return sha1;
-    }
-
-    public long getContentLength() {
-        if (contentLength == null) {
-            contentLength = origin.length();
-        }
-        return contentLength;
-    }
-
-    public long getLastModified() {
-        if (lastModified == null) {
-            lastModified = origin.lastModified();
-        }
-        return lastModified;
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java
index f9a9b69..c7cece9 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java
@@ -17,8 +17,10 @@
 package org.gradle.api.internal.externalresource.local;
 
 import org.gradle.internal.Factory;
-import org.gradle.util.hash.HashUtil;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.internal.hash.HashValue;
 
 import java.io.File;
 import java.util.List;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResource.java
deleted file mode 100644
index 5113da1..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResource.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.externalresource.local;
-
-import org.gradle.util.hash.HashValue;
-
-import java.io.File;
-
-public interface LocallyAvailableResource {
-
-    File getFile();
-
-    HashValue getSha1();
-
-    long getLastModified();
-
-    long getContentLength();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java
index 5694ce0..3687182 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java
@@ -16,7 +16,8 @@
 
 package org.gradle.api.internal.externalresource.local;
 
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.internal.hash.HashValue;
 
 /**
  * A set of locally available resources that were “selected” through some means.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java
index 610c33e..5c84337 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java
@@ -21,7 +21,7 @@ package org.gradle.api.internal.externalresource.local;
  *
  * This is different to our caching in that we know very little about locally available resources, other than their
  * binary content. If we can determine the sha1 value of an external resource, we can search the local system to see
- * if a copy can be found (e.g. the local maven cache).
+ * if a copy can be found (e.g. the local Maven cache).
  *
  * @param <C> The type of the criterion object used to find candidates
  */
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java
index 436c373..6047525 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java
@@ -17,9 +17,9 @@
 package org.gradle.api.internal.externalresource.local;
 
 import org.gradle.api.Transformer;
-import org.gradle.api.internal.filestore.FileStoreEntry;
-import org.gradle.api.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.filestore.FileStoreSearcher;
 import org.gradle.internal.Factory;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
@@ -38,9 +38,9 @@ public class LocallyAvailableResourceFinderSearchableFileStoreAdapter<C> extends
             public Factory<List<File>> transform(final C criterion) {
                 return new Factory<List<File>>() {
                     public List<File> create() {
-                        Set<? extends FileStoreEntry> entries = fileStore.search(criterion);
-                        return CollectionUtils.collect(entries, new ArrayList<File>(entries.size()), new Transformer<File, FileStoreEntry>() {
-                            public File transform(FileStoreEntry original) {
+                        Set<? extends LocallyAvailableResource> entries = fileStore.search(criterion);
+                        return CollectionUtils.collect(entries, new ArrayList<File>(entries.size()), new Transformer<File, LocallyAvailableResource>() {
+                            public File transform(LocallyAvailableResource original) {
                                 return original.getFile();
                             }
                         });
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
index 3cec598..26cdfca 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
@@ -15,17 +15,21 @@
  */
 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.metadata.ModuleVersionArtifactMetaData;
 import org.gradle.api.internal.artifacts.mvnsettings.CannotLocateLocalMavenRepositoryException;
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
 import org.gradle.api.internal.artifacts.repositories.resolver.IvyResourcePattern;
 import org.gradle.api.internal.artifacts.repositories.resolver.M2ResourcePattern;
 import org.gradle.api.internal.artifacts.repositories.resolver.ResourcePattern;
-import org.gradle.api.internal.externalresource.local.*;
-import org.gradle.api.internal.filestore.FileStoreSearcher;
+import org.gradle.api.internal.externalresource.local.CompositeLocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinderSearchableFileStoreAdapter;
 import org.gradle.internal.Factory;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.hash.HashValue;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,49 +37,61 @@ import java.io.File;
 import java.util.LinkedList;
 import java.util.List;
 
-public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAvailableResourceFinder<ArtifactRevisionId>> {
+public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData>> {
     private static final Logger LOGGER = LoggerFactory.getLogger(LocallyAvailableResourceFinderFactory.class);
 
     private final File rootCachesDirectory;
     private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
-    private final FileStoreSearcher<ArtifactRevisionId> fileStore;
+    private final FileStoreSearcher<ModuleVersionArtifactMetaData> fileStore;
 
     public LocallyAvailableResourceFinderFactory(
-            ArtifactCacheMetaData artifactCacheMetaData, LocalMavenRepositoryLocator localMavenRepositoryLocator, FileStoreSearcher<ArtifactRevisionId> fileStore) {
+            ArtifactCacheMetaData artifactCacheMetaData, LocalMavenRepositoryLocator localMavenRepositoryLocator, FileStoreSearcher<ModuleVersionArtifactMetaData> fileStore) {
         this.rootCachesDirectory = artifactCacheMetaData.getCacheDir().getParentFile();
         this.localMavenRepositoryLocator = localMavenRepositoryLocator;
         this.fileStore = fileStore;
     }
 
-    public LocallyAvailableResourceFinder<ArtifactRevisionId> create() {
-        List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders = new LinkedList<LocallyAvailableResourceFinder<ArtifactRevisionId>>();
+    public LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> create() {
+        List<LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData>> finders = new LinkedList<LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData>>();
 
         // Order is important here, because they will be searched in that order
 
         // The current filestore
-        finders.add(new LocallyAvailableResourceFinderSearchableFileStoreAdapter<ArtifactRevisionId>(fileStore));
+        finders.add(new LocallyAvailableResourceFinderSearchableFileStoreAdapter<ModuleVersionArtifactMetaData>(fileStore));
+
+        // 1.9
+//        addForPattern(finders, "modules-2/files-2.1/[organisation]/[module](/[branch])/[revision]/*/[artifact]-[revision](-[classifier])(.[ext])");
+
+        // 1.8
+        addForPattern(finders, "artifacts-26/filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+
+        // 1.5
+        addForPattern(finders, "artifacts-24/filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+
+        // 1.4
+        addForPattern(finders, "artifacts-23/filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
 
         // 1.3
-        addForPattern(finders, "artifacts-15", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "artifacts-15/filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
 
         // 1.1, 1.2
-        addForPattern(finders, "artifacts-14", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+        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])");
+        addForPattern(finders, "artifacts-13/filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
 
         // Milestone 8 and 9
-        addForPattern(finders, "artifacts-8", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "artifacts-8/filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
 
         // Milestone 7
-        addForPattern(finders, "artifacts-7", "artifacts/*/[organisation]/[module](/[branch])/[revision]/[type]/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "artifacts-7/artifacts/*/[organisation]/[module](/[branch])/[revision]/[type]/[artifact]-[revision](-[classifier])(.[ext])");
 
         // Milestone 6
-        addForPattern(finders, "artifacts-4", "[organisation]/[module](/[branch])/*/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
-        addForPattern(finders, "artifacts-4", "[organisation]/[module](/[branch])/*/pom.originals/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "artifacts-4/[organisation]/[module](/[branch])/*/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "artifacts-4/[organisation]/[module](/[branch])/*/pom.originals/[artifact]-[revision](-[classifier])(.[ext])");
 
         // Milestone 3
-        addForPattern(finders, "../cache", "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "../cache/[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
 
         // Maven local
         try {
@@ -86,20 +102,35 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         } catch (CannotLocateLocalMavenRepositoryException ex) {
             finders.add(new NoMavenLocalRepositoryResourceFinder(ex));
         }
-        return new CompositeLocallyAvailableResourceFinder<ArtifactRevisionId>(finders);
+        return new CompositeLocallyAvailableResourceFinder<ModuleVersionArtifactMetaData>(finders);
     }
 
-    private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, String path, String pattern) {
-        addForPattern(finders, new File(rootCachesDirectory, path), new IvyResourcePattern(pattern));
+    private void addForPattern(List<LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData>> finders, String pattern) {
+        int wildcardPos = pattern.indexOf("/*/");
+        int patternPos = pattern.indexOf("/[");
+        if (wildcardPos < 0 && patternPos < 0) {
+            throw new IllegalArgumentException(String.format("Unsupported pattern '%s'", pattern));
+        }
+        int chopAt;
+        if (wildcardPos >= 0 && patternPos >= 0) {
+            chopAt = Math.min(wildcardPos, patternPos);
+        } else if (wildcardPos >= 0) {
+            chopAt = wildcardPos;
+        } else {
+            chopAt = patternPos;
+        }
+        String pathPart = pattern.substring(0, chopAt);
+        String patternPart = pattern.substring(chopAt + 1);
+        addForPattern(finders, new File(rootCachesDirectory, pathPart), new IvyResourcePattern(patternPart));
     }
 
-    private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, File baseDir, ResourcePattern pattern) {
+    private void addForPattern(List<LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData>> finders, File baseDir, ResourcePattern pattern) {
         if (baseDir.exists()) {
             finders.add(new PatternBasedLocallyAvailableResourceFinder(baseDir, pattern));
         }
     }
 
-    private class NoMavenLocalRepositoryResourceFinder implements LocallyAvailableResourceFinder<ArtifactRevisionId> {
+    private class NoMavenLocalRepositoryResourceFinder implements LocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> {
         private final CannotLocateLocalMavenRepositoryException ex;
         private boolean logged;
 
@@ -107,10 +138,10 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
             this.ex = ex;
         }
 
-        public LocallyAvailableResourceCandidates findCandidates(ArtifactRevisionId criterion) {
+        public LocallyAvailableResourceCandidates findCandidates(ModuleVersionArtifactMetaData criterion) {
             if(!logged){
                 LOGGER.warn("Unable to locate local Maven repository.");
-                LOGGER.debug("Problems while locating local maven repository.", ex);
+                LOGGER.debug("Problems while locating local Maven repository.", ex);
                 logged = true;
             }
             return new LocallyAvailableResourceCandidates() {
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 aeea5a0..7601cf5 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,12 +15,10 @@
  */
 package org.gradle.api.internal.externalresource.local.ivy;
 
-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.artifacts.metadata.ModuleVersionArtifactMetaData;
 import org.gradle.api.internal.artifacts.repositories.resolver.ResourcePattern;
 import org.gradle.api.internal.externalresource.local.AbstractLocallyAvailableResourceFinder;
 import org.gradle.api.internal.file.collections.MinimalFileTree;
@@ -31,20 +29,20 @@ import java.io.File;
 import java.util.LinkedList;
 import java.util.List;
 
-public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ArtifactRevisionId> {
+public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ModuleVersionArtifactMetaData> {
 
     public PatternBasedLocallyAvailableResourceFinder(File baseDir, ResourcePattern pattern) {
         super(createProducer(baseDir, pattern));
     }
 
-    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final File baseDir, final ResourcePattern pattern) {
-        return new Transformer<Factory<List<File>>, ArtifactRevisionId>() {
-            public Factory<List<File>> transform(final ArtifactRevisionId artifactId) {
+    private static Transformer<Factory<List<File>>, ModuleVersionArtifactMetaData> createProducer(final File baseDir, final ResourcePattern pattern) {
+        return new Transformer<Factory<List<File>>, ModuleVersionArtifactMetaData>() {
+            public Factory<List<File>> transform(final ModuleVersionArtifactMetaData artifact) {
                 return new Factory<List<File>>() {
                     public List<File> create() {
                         final List<File> files = new LinkedList<File>();
-                        if (artifactId != null) {
-                            getMatchingFiles(artifactId).visit(new EmptyFileVisitor() {
+                        if (artifact != null) {
+                            getMatchingFiles(artifact).visit(new EmptyFileVisitor() {
                                 public void visitFile(FileVisitDetails fileDetails) {
                                     files.add(fileDetails.getFile());
                                 }
@@ -55,15 +53,11 @@ public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyA
                 };
             }
 
-            private MinimalFileTree getMatchingFiles(ArtifactRevisionId artifact) {
-                String patternString = getArtifactPattern(artifact);
+            private MinimalFileTree getMatchingFiles(ModuleVersionArtifactMetaData artifact) {
+                String patternString = pattern.toPath(artifact);
                 return new SingleIncludePatternFileTree(baseDir, patternString);
             }
 
-            private String getArtifactPattern(ArtifactRevisionId artifactId) {
-                Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
-                return pattern.toPath(dummyArtifact);
-            }
         };
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java
index 2545c86..16505e9 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.externalresource.metadata;
 
 import org.gradle.api.Nullable;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashValue;
 
 import java.io.Serializable;
 import java.util.Date;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java
index c8386d5..e49d7fd 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.externalresource.metadata;
 
 import org.gradle.api.Nullable;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashValue;
 
 import java.util.Date;
 
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 97c97b0..e24fd2a 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
@@ -17,17 +17,20 @@
 package org.gradle.api.internal.externalresource.transfer;
 
 import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultExternalResourceCachePolicy;
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.ExternalResourceCachePolicy;
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource;
 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;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaDataCompare;
 import org.gradle.internal.Factory;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashValue;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.util.BuildCommencedTimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,10 +42,13 @@ public class DefaultCacheAwareExternalResourceAccessor implements CacheAwareExte
 
     private final ExternalResourceAccessor delegate;
     private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
+    private final BuildCommencedTimeProvider timeProvider;
+    private final ExternalResourceCachePolicy externalResourceCachePolicy = new DefaultExternalResourceCachePolicy();
 
-    public DefaultCacheAwareExternalResourceAccessor(ExternalResourceAccessor delegate, CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+    public DefaultCacheAwareExternalResourceAccessor(ExternalResourceAccessor delegate, CachedExternalResourceIndex<String> cachedExternalResourceIndex, BuildCommencedTimeProvider timeProvider) {
         this.delegate = delegate;
         this.cachedExternalResourceIndex = cachedExternalResourceIndex;
+        this.timeProvider = timeProvider;
     }
 
     public ExternalResource getResource(final String location, @Nullable LocallyAvailableResourceCandidates localCandidates) throws IOException {
@@ -55,6 +61,9 @@ public class DefaultCacheAwareExternalResourceAccessor implements CacheAwareExte
         }
 
         // We might be able to use a cached/locally available version
+        if (cached != null && !externalResourceCachePolicy.mustRefreshExternalResource(getAgeMillis(timeProvider, cached))) {
+            return new CachedExternalResourceAdapter(location, cached, delegate, cached.getExternalResourceMetaData());
+        }
 
         // Get the metadata first to see if it's there
         final ExternalResourceMetaData remoteMetaData = delegate.getMetaData(location);
@@ -94,7 +103,7 @@ public class DefaultCacheAwareExternalResourceAccessor implements CacheAwareExte
                 LocallyAvailableResource local = localCandidates.findByHashValue(remoteChecksum);
                 if (local != null) {
                     LOGGER.info("Found locally available resource with matching checksum: [{}, {}]", location, local.getFile());
-                    return new LocallyAvailableExternalResource(location, local, remoteMetaData);
+                    return new DefaultLocallyAvailableExternalResource(location, local, remoteMetaData);
                 }
             }
         }
@@ -103,4 +112,7 @@ public class DefaultCacheAwareExternalResourceAccessor implements CacheAwareExte
         return delegate.getResource(location);
     }
 
+    public long getAgeMillis(BuildCommencedTimeProvider timeProvider, CachedExternalResource cached) {
+        return timeProvider.getCurrentTime() - cached.getCachedAt();
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java
index 2bc60f5..e735e43 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java
@@ -19,7 +19,7 @@ package org.gradle.api.internal.externalresource.transfer;
 import org.gradle.api.Nullable;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashValue;
 
 import java.io.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
index 32182a8..df39de9 100644
--- 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
@@ -16,12 +16,13 @@
 
 package org.gradle.api.internal.externalresource.transfer;
 
-import org.apache.ivy.plugins.repository.Resource;
+import org.gradle.api.Action;
 import org.gradle.api.Nullable;
+import org.gradle.api.Transformer;
 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 org.gradle.internal.hash.HashValue;
 
 import java.io.*;
 
@@ -81,8 +82,12 @@ public class ProgressLoggingExternalResourceAccessor extends AbstractProgressLog
             }
         }
 
-        public Resource clone(String cloneName) {
-            return resource.clone(cloneName);
+        public void withContent(Action<? super InputStream> readAction) throws IOException {
+            resource.withContent(readAction);
+        }
+
+        public <T> T withContent(Transformer<? extends T, ? super InputStream> readAction) throws IOException {
+            return resource.withContent(readAction);
         }
 
         public void close() throws IOException {
@@ -114,10 +119,6 @@ public class ProgressLoggingExternalResourceAccessor extends AbstractProgressLog
             return resource.isLocal();
         }
 
-        public InputStream openStream() throws IOException {
-            return resource.openStream();
-        }
-
         public String toString(){
             return resource.toString();
         }
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
index c0ab6da..ea94d03 100644
--- 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
@@ -28,8 +28,8 @@ 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.gradle.internal.hash.HashUtil;
+import org.gradle.internal.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
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 c375699..4ed60f8 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
@@ -16,18 +16,16 @@
 package org.gradle.api.internal.externalresource.transport.file;
 
 import org.apache.commons.io.IOUtils;
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource;
 import org.gradle.api.internal.externalresource.ExternalResource;
-import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
-import org.gradle.api.internal.externalresource.local.DefaultLocallyAvailableResource;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 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.internal.resource.local.DefaultLocallyAvailableResource;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.hash.HashValue;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.gradle.internal.hash.HashValue;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -37,9 +35,6 @@ 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()) {
@@ -81,7 +76,7 @@ public class FileResourceConnector implements ExternalResourceLister, ExternalRe
         if (!localFile.exists()) {
             return null;
         }
-        return new LocallyAvailableExternalResource(location, new DefaultLocallyAvailableResource(localFile));
+        return new DefaultLocallyAvailableExternalResource(location, new DefaultLocallyAvailableResource(localFile));
     }
 
     public ExternalResourceMetaData getMetaData(String location) throws IOException {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
index 652e5d6..240e7d4 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
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.externalresource.transport.file;
 
-import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.RepositoryArtifactCache;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.externalresource.ExternalResource;
@@ -32,10 +32,10 @@ import java.net.URI;
 
 public class FileTransport implements RepositoryTransport {
     private final String name;
-    private final RepositoryCacheManager repositoryCacheManager;
+    private final RepositoryArtifactCache repositoryCacheManager;
     private final ExternalResourceRepository repository;
 
-    public FileTransport(String name, RepositoryCacheManager repositoryCacheManager, TemporaryFileProvider temporaryFileProvider) {
+    public FileTransport(String name, RepositoryArtifactCache repositoryCacheManager, TemporaryFileProvider temporaryFileProvider) {
         this.name = name;
         this.repositoryCacheManager = repositoryCacheManager;
         repository = createRepository(temporaryFileProvider);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java
index 7762d60..437d14e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java
@@ -25,8 +25,7 @@ import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
-import java.io.IOException;
-import java.io.StringReader;
+import java.io.*;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -40,14 +39,14 @@ public class ApacheDirectoryListingParser {
     public ApacheDirectoryListingParser() {
     }
 
-    public List<URI> parse(URI baseURI, byte[] content, String contentType) throws Exception {
+    public List<URI> parse(URI baseURI, InputStream content, String contentType) throws Exception {
         baseURI = addTrailingSlashes(baseURI);
         if (contentType == null || !contentType.startsWith("text/html")) {
             throw new ResourceException(String.format("Unsupported ContentType %s for DirectoryListing", contentType));
         }
         String contentEncoding = contentType.contains("charset=") ? contentType.substring(contentType.indexOf('=') + 1) : "utf-8";
-        final String htmlText = new String(content, contentEncoding);
-        final InputSource inputSource = new InputSource(new StringReader(htmlText));
+        final Reader htmlText = new InputStreamReader(content, contentEncoding);
+        final InputSource inputSource = new InputSource(htmlText);
         final SAXParser htmlParser = new SAXParser();
         final AnchorListerHandler anchorListerHandler = new AnchorListerHandler();
         htmlParser.setContentHandler(anchorListerHandler);
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
index 13cf3e9..78c2581 100644
--- 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
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.externalresource.transport.http;
 
 import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * Signals that some error occurred when making an HTTP request.
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 796fe03..87cce8a 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
@@ -21,7 +21,7 @@ import org.apache.http.util.EntityUtils;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
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 5905481..ad5179d 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
@@ -16,12 +16,12 @@
 
 package org.gradle.api.internal.externalresource.transport.http;
 
-import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.Transformer;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
 import org.gradle.api.internal.resource.ResourceException;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -35,7 +35,7 @@ public class HttpResourceLister implements ExternalResourceLister {
     }
 
     public List<String> list(String parent) throws IOException {
-        URI baseURI;
+        final URI baseURI;
         try {
             baseURI = new URI(parent);
         } catch (URISyntaxException ex) {
@@ -46,15 +46,18 @@ public class HttpResourceLister implements ExternalResourceLister {
             return null;
         }
         try {
-            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);
-            }
+            return resource.withContent(new Transformer<List<String>, InputStream>() {
+                public List<String> transform(InputStream inputStream) {
+                    String contentType = resource.getContentType();
+                    ApacheDirectoryListingParser directoryListingParser = new ApacheDirectoryListingParser();
+                    try {
+                        List<URI> uris = directoryListingParser.parse(baseURI, inputStream, contentType);
+                        return convertToStringList(uris);
+                    } catch (Exception e) {
+                        throw new ResourceException("Unable to parse Http directory listing", e);
+                    }
+                }
+            });
         } finally {
             resource.close();
         }
@@ -67,10 +70,4 @@ public class HttpResourceLister implements ExternalResourceLister {
         }
         return ret;
     }
-
-    private byte[] loadResourceContent(ExternalResource resource) throws IOException {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        resource.writeTo(outputStream);
-        return outputStream.toByteArray();
-    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
index 59c9d69..342016c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
@@ -23,7 +23,7 @@ import org.apache.http.util.EntityUtils;
 import org.gradle.api.internal.externalresource.AbstractExternalResource;
 import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.util.hash.HashValue;
+import org.gradle.internal.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
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 c23ea02..7696c25 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
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.externalresource.transport.http;
 
-import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.RepositoryArtifactCache;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
@@ -27,20 +27,21 @@ import org.gradle.api.internal.externalresource.transport.DefaultExternalResourc
 import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.BuildCommencedTimeProvider;
 
 import java.net.URI;
 
 public class HttpTransport implements RepositoryTransport {
     private final String name;
-    private final RepositoryCacheManager repositoryCacheManager;
+    private final RepositoryArtifactCache repositoryCacheManager;
     private final ExternalResourceRepository repository;
 
-    public HttpTransport(String name, PasswordCredentials credentials, RepositoryCacheManager repositoryCacheManager,
+    public HttpTransport(String name, PasswordCredentials credentials, RepositoryArtifactCache repositoryCacheManager,
                          ProgressLoggerFactory progressLoggerFactory, TemporaryFileProvider temporaryFileProvider,
-                         CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+                         CachedExternalResourceIndex<String> cachedExternalResourceIndex, BuildCommencedTimeProvider timeProvider) {
         this.name = name;
         this.repositoryCacheManager = repositoryCacheManager;
-        repository = createRepository(credentials, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex);
+        repository = createRepository(credentials, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex, timeProvider);
     }
 
     public ExternalResourceRepository getRepository() {
@@ -48,7 +49,7 @@ public class HttpTransport implements RepositoryTransport {
     }
 
     private ExternalResourceRepository createRepository(PasswordCredentials credentials, ProgressLoggerFactory progressLoggerFactory,
-                                                        TemporaryFileProvider temporaryFileProvider, CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+                                                        TemporaryFileProvider temporaryFileProvider, CachedExternalResourceIndex<String> cachedExternalResourceIndex, BuildCommencedTimeProvider timeProvider) {
         HttpClientHelper http = new HttpClientHelper(new DefaultHttpSettings(credentials));
         HttpResourceAccessor accessor = new HttpResourceAccessor(http);
         HttpResourceUploader uploader = new HttpResourceUploader(http);
@@ -59,7 +60,7 @@ public class HttpTransport implements RepositoryTransport {
                 new ProgressLoggingExternalResourceUploader(uploader, progressLoggerFactory),
                 new HttpResourceLister(accessor),
                 temporaryFileProvider,
-                new DefaultCacheAwareExternalResourceAccessor(loggingAccessor, cachedExternalResourceIndex)
+                new DefaultCacheAwareExternalResourceAccessor(loggingAccessor, cachedExternalResourceIndex, timeProvider)
         );
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java
index 5387e22..038f6ff 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.externalresource.transport.http;
 
+import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,7 +38,7 @@ public class JavaSystemPropertiesHttpProxySettings implements HttpProxySettings
     }
 
     JavaSystemPropertiesHttpProxySettings(String proxyHost, String proxyPortString, String proxyUser, String proxyPassword, String nonProxyHostsString) {
-        if (proxyHost == null) {
+        if (StringUtils.isBlank(proxyHost)) {
             this.proxy = null;
         } else {
             this.proxy = new HttpProxy(proxyHost, initProxyPort(proxyPortString), proxyUser, proxyPassword);
@@ -46,7 +47,7 @@ public class JavaSystemPropertiesHttpProxySettings implements HttpProxySettings
     }
 
     private int initProxyPort(String proxyPortString) {
-        if (proxyPortString == null) {
+        if (StringUtils.isBlank(proxyPortString)) {
             return DEFAULT_PROXY_PORT;
         }
         
@@ -59,7 +60,7 @@ public class JavaSystemPropertiesHttpProxySettings implements HttpProxySettings
     }
 
     private List<Pattern> initNonProxyHosts(String nonProxyHostsString) {
-        if (nonProxyHostsString == null) {
+        if (StringUtils.isBlank(nonProxyHostsString)) {
             return Collections.emptyList();
         }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactIdentifierFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactIdentifierFileStore.java
new file mode 100644
index 0000000..311941b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactIdentifierFileStore.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.filestore.ivy;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData;
+import org.gradle.api.internal.artifacts.repositories.resolver.IvyResourcePattern;
+import org.gradle.api.internal.artifacts.repositories.resolver.ResourcePattern;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.filestore.GroupedAndNamedUniqueFileStore;
+import org.gradle.api.internal.filestore.PathKeyFileStore;
+
+public class ArtifactIdentifierFileStore extends GroupedAndNamedUniqueFileStore<ModuleVersionArtifactMetaData> {
+
+    private static final String GROUP_PATTERN = "[organisation]/[module](/[branch])/[revision]";
+    private static final String NAME_PATTERN = "[artifact]-[revision](-[classifier])(.[ext])";
+
+    public ArtifactIdentifierFileStore(PathKeyFileStore pathKeyFileStore, TemporaryFileProvider temporaryFileProvider) {
+        super(pathKeyFileStore, temporaryFileProvider, toTransformer(GROUP_PATTERN), toTransformer(NAME_PATTERN));
+    }
+
+    private static Transformer<String, ModuleVersionArtifactMetaData> toTransformer(final String pattern) {
+        final ResourcePattern resourcePattern = new IvyResourcePattern(pattern);
+        return new Transformer<String, ModuleVersionArtifactMetaData>() {
+             public String transform(ModuleVersionArtifactMetaData artifact) {
+                 return resourcePattern.toPath(artifact);
+             }
+         };
+    }
+}
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
deleted file mode 100644
index 5157e28..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactRevisionIdFileStore.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.filestore.ivy;
-
-import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
-import org.gradle.api.Transformer;
-import org.gradle.api.internal.file.TemporaryFileProvider;
-import org.gradle.api.internal.filestore.GroupedAndNamedUniqueFileStore;
-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(PathKeyFileStore pathKeyFileStore, TemporaryFileProvider temporaryFileProvider) {
-        super(pathKeyFileStore, temporaryFileProvider, toTransformer(GROUP_PATTERN), toTransformer(NAME_PATTERN));
-    }
-
-    private static Transformer<String, ArtifactRevisionId> toTransformer(final String pattern) {
-        return new Transformer<String, ArtifactRevisionId>() {
-            public String transform(ArtifactRevisionId id) {
-                String substitute = pattern;
-                substitute = IvyPatternHelper.substituteToken(substitute, "organisation", id.getModuleRevisionId().getOrganisation().replace('/', '.'));
-                return IvyPatternHelper.substitute(substitute, new DefaultArtifact(id, null, null, false));
-            }
-        };
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java
index a4451a1..e3ec371 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java
@@ -17,14 +17,12 @@ package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.ClientModule;
 import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
 
-/**
- * @author Hans Dockter
- */
-public class ClientModuleNotationParserFactory implements Factory<NotationParser<ClientModule>> {
+public class ClientModuleNotationParserFactory implements Factory<NotationParser<Object, ClientModule>> {
 
     private final Instantiator instantiator;
 
@@ -32,7 +30,7 @@ public class ClientModuleNotationParserFactory implements Factory<NotationParser
         this.instantiator = instantiator;
     }
 
-    public NotationParser<ClientModule> create() {
+    public NotationParser<Object, ClientModule> create() {
         return new NotationParserBuilder<ClientModule>()
                 .resultingType(ClientModule.class)
                 .parser(new DependencyStringNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
index d6945a6..d88557b 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
@@ -18,19 +18,17 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.artifacts.SelfResolvingDependency;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 
 import java.io.File;
 import java.util.Collection;
 
 public class DependencyClassPathNotationParser
-        extends TypedNotationParser<DependencyFactory.ClassPathNotation, SelfResolvingDependency>
-        implements NotationParser<SelfResolvingDependency> {
+        extends TypedNotationParser<DependencyFactory.ClassPathNotation, SelfResolvingDependency> {
 
     private final ClassPathRegistry classPathRegistry;
     private final Instantiator instantiator;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
index 33dab35..bd19502 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
@@ -17,16 +17,14 @@ package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.SelfResolvingDependency;
 import org.gradle.api.file.FileCollection;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 
 import java.util.Collection;
 
 public class DependencyFilesNotationParser
-        extends TypedNotationParser<FileCollection, SelfResolvingDependency>
-        implements NotationParser<SelfResolvingDependency> {
+        extends TypedNotationParser<FileCollection, SelfResolvingDependency> {
 
     private final Instantiator instantiator;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
index c15e792..160137b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
@@ -16,17 +16,14 @@
 package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
 import org.gradle.api.tasks.Optional;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.util.Collection;
 
-/**
- * @author Hans Dockter
- */
 public class DependencyMapNotationParser<T extends ExternalDependency> extends MapNotationParser<T> {
 
     private final Instantiator instantiator;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
index 3f019dd..9d3b355 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
@@ -18,18 +18,16 @@ package org.gradle.api.internal.notations;
 
 import org.gradle.api.GradleException;
 import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
 
 import java.util.Collection;
 
-/**
- * by Szczepan Faber, created at: 11/8/11
- */
-public class DependencyNotationParser implements NotationParser<Dependency> {
+public class DependencyNotationParser implements NotationParser<Object, Dependency> {
 
-    private final NotationParser<Dependency> delegate;
+    private final NotationParser<Object, Dependency> delegate;
 
-    public DependencyNotationParser(Iterable<NotationParser<? extends Dependency>> compositeParsers) {
+    public DependencyNotationParser(Iterable<NotationParser<Object, ? extends Dependency>> compositeParsers) {
         delegate = new NotationParserBuilder<Dependency>()
                 .resultingType(Dependency.class)
                 .parsers(compositeParsers)
@@ -37,7 +35,7 @@ public class DependencyNotationParser implements NotationParser<Dependency> {
                 .toComposite();
     }
 
-    DependencyNotationParser(NotationParser<Dependency> delegate) {
+    DependencyNotationParser(NotationParser<Object, Dependency> delegate) {
         this.delegate = delegate;
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
index f4e0cd8..66c045d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
@@ -19,13 +19,10 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.DefaultProjectDependencyFactory;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 
 import java.util.Collection;
 
-/**
- * by Szczepan Faber, created at: 11/10/11
- */
 public class DependencyProjectNotationParser extends TypedNotationParser<Project, ProjectDependency> {
 
     private final DefaultProjectDependencyFactory factory;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
index 45f2f5e..8e79315 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
@@ -21,16 +21,13 @@ import org.gradle.api.artifacts.ClientModule;
 import org.gradle.api.artifacts.ExternalDependency;
 import org.gradle.api.internal.artifacts.dsl.ParsedModuleStringNotation;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 import org.gradle.internal.reflect.Instantiator;
 
 import java.util.Collection;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * by Szczepan Faber, created at: 11/10/11
- */
 public class DependencyStringNotationParser<T extends ExternalDependency> extends TypedNotationParser<CharSequence, T> {
 
     private final Instantiator instantiator;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
index 9995133..e518a67 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
@@ -18,16 +18,13 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.DefaultProjectDependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
 import org.gradle.api.tasks.Optional;
 
 import java.util.Collection;
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public class ProjectDependencyFactory {
     private final DefaultProjectDependencyFactory factory;
 
diff --git a/subprojects/core-impl/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry b/subprojects/core-impl/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
new file mode 100644
index 0000000..3b0295b
--- /dev/null
+++ b/subprojects/core-impl/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
@@ -0,0 +1 @@
+org.gradle.api.internal.artifacts.DependencyServices
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
deleted file mode 100644
index be631ac..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.artifacts;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
-import org.gradle.internal.Factory;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-
-import java.io.File;
-import java.util.Collections;
-
-public class ArtifactsTestUtils {
-    
-    public static DefaultResolvedArtifact createResolvedArtifact(final Mockery context, final String name, final String type, final String extension, final File file) {
-        final Artifact artifactStub = context.mock(Artifact.class, "artifact" + name);
-        context.checking(new Expectations() {{
-            allowing(artifactStub).getName();
-            will(returnValue(name));
-            allowing(artifactStub).getType();
-            will(returnValue(type));
-            allowing(artifactStub).getExt();
-            will(returnValue(extension));
-            allowing(artifactStub).getExtraAttributes();
-            will(returnValue(Collections.emptyMap()));
-            allowing(artifactStub).getQualifiedExtraAttributes();
-            will(returnValue(Collections.emptyMap()));
-            allowing(artifactStub).getExtraAttribute(with(org.hamcrest.Matchers.notNullValue(String.class)));
-            will(returnValue(null));
-        }});
-        final Factory artifactSource = context.mock(Factory.class);
-        context.checking(new Expectations() {{
-            allowing(artifactSource).create();
-            will(returnValue(file));
-        }});
-        final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
-        final ResolvedModuleVersion version = context.mock(ResolvedModuleVersion.class);
-        context.checking(new Expectations() {{
-            allowing(resolvedDependency).getModule();
-            will(returnValue(version));
-            allowing(version).getId();
-            will(returnValue(new DefaultModuleVersionIdentifier("group", name, "1.2")));
-        }});
-        return new DefaultResolvedArtifact(resolvedDependency, artifactStub, artifactSource);
-    }
-    
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifierTest.groovy
new file mode 100644
index 0000000..61dbe88
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifierTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.Matchers.strictlyEqual
+import static org.hamcrest.MatcherAssert.assertThat
+
+class DefaultArtifactIdentifierTest extends Specification {
+
+    def "equals"() {
+        def base = new DefaultArtifactIdentifier(newId("org", "lib", "1.0"), "someArtifact", "jar", "jar", "")
+        def same = new DefaultArtifactIdentifier(newId("org", "lib", "1.0"), "someArtifact", "jar", "jar", "")
+
+        def badId = new DefaultArtifactIdentifier(newId("org", "lib", "2.0"), "someArtifact", "jar", "jar", "")
+        def badName = new DefaultArtifactIdentifier(newId("org", "lib", "1.0"), "foo", "jar", "jar", "")
+        def badType = new DefaultArtifactIdentifier(newId("org", "lib", "1.0"), "someArtifact", "bar", "jar", "")
+        def badExtension = new DefaultArtifactIdentifier(newId("org", "lib", "1.0"), "someArtifact", "jar", "xxx", "")
+        def badClassifier = new DefaultArtifactIdentifier(newId("org", "lib", "1.0"), "someArtifact", "jar", "jar", "sources")
+
+        expect:
+        assertThat(base, strictlyEqual(same))
+
+        base != badId
+        base != badName
+        base != badType
+        base != badExtension
+        base != badClassifier
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
index 9c33caf..d441b67 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
@@ -15,115 +15,78 @@
  */
 package org.gradle.api.internal.artifacts
 
-import org.gradle.StartParameter
-import org.gradle.api.internal.ClassPathRegistry
-import org.gradle.api.internal.DomainObjectContext
-import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
+import org.gradle.api.Action
+import org.gradle.api.artifacts.dsl.ArtifactHandler
+import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
+import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler
+import org.gradle.api.internal.artifacts.dsl.DefaultComponentMetadataHandler
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler
-import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
-import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.file.TemporaryFileProvider
-import org.gradle.cache.CacheRepository
-import org.gradle.cache.DirectoryCacheBuilder
-import org.gradle.cache.PersistentCache
-import org.gradle.cache.internal.FileLockManager
-import org.gradle.initialization.ProjectAccessListener
-import org.gradle.internal.Factory
-import org.gradle.internal.TimeProvider
+import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler
+import org.gradle.api.internal.artifacts.ivyservice.IvyBackedArtifactPublisher
+import org.gradle.api.internal.artifacts.repositories.DefaultBaseRepositoryFactory
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.internal.service.ServiceRegistration
 import org.gradle.internal.service.ServiceRegistry
-import org.gradle.listener.ListenerManager
-import org.gradle.logging.LoggingManagerInternal
-import org.gradle.logging.ProgressLoggerFactory
 import spock.lang.Specification
 
+import java.lang.reflect.ParameterizedType
+
 class DefaultDependencyManagementServicesTest extends Specification {
     final ServiceRegistry parent = Mock()
-    final FileResolver fileResolver = Mock()
-    final DependencyMetaDataProvider dependencyMetaDataProvider = Mock()
-    final ProjectFinder projectFinder = Mock()
-    final Instantiator instantiator = Mock()
-    final DomainObjectContext domainObjectContext = Mock()
-    final DefaultRepositoryHandler repositoryHandler = Mock()
-    final ConfigurationContainerInternal configurationContainer = Mock()
-    final StartParameter startParameter = Mock()
-    final ListenerManager listenerManager = Mock()
+    final Instantiator instantiator = new DirectInstantiator()
     final DefaultDependencyManagementServices services = new DefaultDependencyManagementServices(parent)
 
     def setup() {
-        Factory<LoggingManagerInternal> loggingFactory = Mock()
-        _ * parent.getFactory(LoggingManagerInternal) >> loggingFactory
-        ProgressLoggerFactory progressLoggerFactory = Mock()
-        _ * parent.get(ProgressLoggerFactory) >> progressLoggerFactory
-        CacheRepository cacheRepository = initCacheRepository()
-        _ * parent.get(CacheRepository) >> cacheRepository
-        ClassPathRegistry classPathRegistry = Mock()
-        _ * parent.get(ClassPathRegistry) >> classPathRegistry
-        _ * parent.get(ListenerManager) >> listenerManager
-        _ * parent.get(FileLockManager) >> Mock(FileLockManager)
-        _ * parent.get(TimeProvider) >> Mock(TimeProvider)
-        _ * parent.get(TemporaryFileProvider) >> Mock(TemporaryFileProvider)
-        _ * parent.get(ProjectAccessListener) >> Mock(ProjectAccessListener)
-    }
-
-    private CacheRepository initCacheRepository() {
-        CacheRepository cacheRepository = Mock()
-        DirectoryCacheBuilder cacheBuilder = Mock()
-        _ * cacheRepository.store(_) >> cacheBuilder
-        _ * cacheBuilder.withVersionStrategy(_) >> cacheBuilder
-        _ * cacheBuilder.withLockMode(_) >> cacheBuilder
-        _ * cacheBuilder.withDisplayName(_) >> cacheBuilder
-        PersistentCache cache = Mock()
-        _ * cacheBuilder.open() >> cache
-        cache.baseDir >> new File("cache")
-        return cacheRepository
+        _ * parent.get(Instantiator) >> instantiator
+        _ * parent.get({it instanceof Class}) >> { Class t -> Stub(t) }
+        _ * parent.get({it instanceof ParameterizedType}) >> { ParameterizedType t -> Stub(t.rawType) }
     }
 
-    def "can create dependency resolution services"() {
+    def "can create dependency resolution DSL services"() {
         given:
-        _ * parent.get(Instantiator) >> instantiator
-        _ * parent.get(StartParameter) >> startParameter
-        1 * instantiator.newInstance(DefaultRepositoryHandler, _, _) >> repositoryHandler
-        1 * instantiator.newInstance(DefaultConfigurationContainer, !null, instantiator,
-                domainObjectContext, listenerManager, dependencyMetaDataProvider) >> configurationContainer
-        def strategy = new DefaultResolutionStrategy()
-        instantiator.newInstance(DefaultResolutionStrategy) >> strategy
+        def registry = new DefaultServiceRegistry(parent)
 
         when:
-        def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
+        registry.register({ ServiceRegistration registration -> services.addDslServices(registration) } as Action)
+        def resolutionServices = registry.get(DependencyResolutionServices)
 
         then:
-        resolutionServices.resolveRepositoryHandler
-        resolutionServices.configurationContainer
-        resolutionServices.dependencyHandler
-        resolutionServices.artifactHandler
-        resolutionServices.createArtifactPublicationServices()
+        resolutionServices.resolveRepositoryHandler instanceof DefaultRepositoryHandler
+        resolutionServices.configurationContainer instanceof DefaultConfigurationContainer
+        resolutionServices.dependencyHandler instanceof DefaultDependencyHandler
+        registry.get(ComponentMetadataHandler) instanceof DefaultComponentMetadataHandler
+        registry.get(ArtifactHandler) instanceof DefaultArtifactHandler
+        registry.get(BaseRepositoryFactory) instanceof DefaultBaseRepositoryFactory
     }
 
     def "publish services provide a repository handler"() {
-        DefaultRepositoryHandler publishRepositoryHandler = Mock()
-
         given:
-        _ * parent.get(Instantiator) >> instantiator
-        _ * instantiator.newInstance(DefaultRepositoryHandler, _, _) >> publishRepositoryHandler
+        def registry = new DefaultServiceRegistry(parent)
 
         when:
-        def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
-        def publishResolverHandler = resolutionServices.createArtifactPublicationServices().createRepositoryHandler()
+        registry.register({ ServiceRegistration registration -> services.addDslServices(registration) } as Action)
+        def publishServices = registry.get(ArtifactPublicationServices)
 
         then:
-        publishResolverHandler == publishRepositoryHandler
+        def publishResolverHandler = publishServices.createRepositoryHandler()
+        publishResolverHandler instanceof DefaultRepositoryHandler
+        !publishResolverHandler.is(publishServices.createRepositoryHandler())
     }
 
     def "publish services provide an ArtifactPublisher"() {
+        given:
+        def registry = new DefaultServiceRegistry(parent)
+
         when:
-        def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
-        def ivyService = resolutionServices.createArtifactPublicationServices().createArtifactPublisher()
+        registry.register({ ServiceRegistration registration -> services.addDslServices(registration) } as Action)
+        def publishServices = registry.get(ArtifactPublicationServices)
 
         then:
-        ivyService != null
+        def ivyService = publishServices.createArtifactPublisher()
+        ivyService instanceof IvyBackedArtifactPublisher
+        !ivyService.is(publishServices.createArtifactPublisher())
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy
index bf1032d..31698ca 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy
@@ -16,14 +16,12 @@
 
 package org.gradle.api.internal.artifacts
 
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
 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: 2/11/13
- */
 class DefaultModuleVersionSelectorTest extends Specification {
 
     def "equality"() {
@@ -56,4 +54,16 @@ class DefaultModuleVersionSelectorTest extends Specification {
         !selector.matchesStrictly(differentName)
         !selector.matchesStrictly(differentVersion)
     }
+
+    def "construct from ModuleRevisionId"() {
+        def module = IvyUtil.createModuleRevisionId("group", "name", "version")
+        def selector = newSelector(module)
+
+        expect:
+        with(selector) {
+            group == "group"
+            name == "name"
+            version == "version"
+        }
+    }
 }
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 eb7aba8..38dd852 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
@@ -15,9 +15,8 @@
  */
 package org.gradle.api.internal.artifacts
 
-import org.apache.ivy.core.module.descriptor.Artifact
-import org.gradle.api.artifacts.ResolvedDependency
 import org.gradle.api.artifacts.ResolvedModuleVersion
+import org.gradle.api.internal.artifacts.metadata.IvyArtifactName
 import org.gradle.internal.Factory
 import org.gradle.util.Matchers
 import spock.lang.Specification
@@ -25,55 +24,25 @@ import spock.lang.Specification
 class DefaultResolvedArtifactTest extends Specification {
     final Factory artifactSource = Mock()
 
-    def "uses extended attributes to determine classifier"() {
-        Artifact ivyArtifact = ivyArtifact("name", "type", "ext", ['m:classifier': 'classifier'])
-        ResolvedDependency dependency = Mock()
-        def artifact = new DefaultResolvedArtifact(dependency, ivyArtifact, artifactSource)
-
-        expect:
-        artifact.classifier == 'classifier'
-    }
-
-    def "attributes are equal when module, name, type, extension and extended attributes are equal"() {
-        ResolvedDependency dependency = dep("group", "module1", "1.2")
-        ResolvedDependency dependencySameModule = dep("group", "module1", "1.2")
-        ResolvedDependency dependency2 = dep("group", "module2", "1-beta")
-        Artifact ivyArt = ivyArtifact("name", "type", "ext", [attr: "value"])
-        Artifact ivyArtifactWithDifferentName = ivyArtifact("name2", "type", "ext", [attr: "value"])
-        Artifact ivyArtifactWithDifferentType = ivyArtifact("name", "type2", "ext", [attr: "value"])
-        Artifact ivyArtifactWithDifferentExt = ivyArtifact("name", "type", "ext2", [attr: "value"])
-        Artifact ivyArtWithDifferentAttributes = ivyArtifact("name", "type", "ext", [attr: "value2"])
-        def artifact = new DefaultResolvedArtifact(dependency, ivyArt, artifactSource)
-        def equalArtifact = new DefaultResolvedArtifact(dependencySameModule, ivyArt, artifactSource)
-        def differentModule = new DefaultResolvedArtifact(dependency2, ivyArt, artifactSource)
-        def differentName = new DefaultResolvedArtifact(dependency, ivyArtifactWithDifferentName, artifactSource)
-        def differentType = new DefaultResolvedArtifact(dependency, ivyArtifactWithDifferentType, artifactSource)
-        def differentExtension = new DefaultResolvedArtifact(dependency, ivyArtifactWithDifferentExt, artifactSource)
-        def differentAttributes = new DefaultResolvedArtifact(dependency, ivyArtWithDifferentAttributes, artifactSource)
+    def "artifacts are equal when module and name are equal"() {
+        def dependency = dep("group", "module1", "1.2")
+        def dependencySameModule = dep("group", "module1", "1.2")
+        def dependency2 = dep("group", "module2", "1-beta")
+        def ivyArt = Stub(IvyArtifactName)
+        def artifact = new DefaultResolvedArtifact(dependency, {} as Factory, ivyArt, artifactSource, 0)
+        def equalArtifact = new DefaultResolvedArtifact(dependencySameModule, {} as Factory, ivyArt, artifactSource, 0)
+        def differentModule = new DefaultResolvedArtifact(dependency2, {} as Factory, ivyArt, artifactSource, 0)
+        def differentName = new DefaultResolvedArtifact(dependency, {} as Factory, Stub(IvyArtifactName), artifactSource, 0)
 
         expect:
         artifact Matchers.strictlyEqual(equalArtifact)
         artifact != differentModule
         artifact != differentName
-        artifact != differentType
-        artifact != differentExtension
-        artifact != differentAttributes
-    }
-
-    def ivyArtifact(String name, String type, String extension, Map attributes) {
-        Artifact artifact = Mock()
-        _ * artifact.name >> name
-        _ * artifact.type >> type
-        _ * artifact.ext >> extension
-        _ * artifact.qualifiedExtraAttributes >> attributes
-        return artifact
     }
 
     def dep(String group, String moduleName, String version) {
-        ResolvedDependency dependency = Mock()
         ResolvedModuleVersion module = Mock()
-        _ * dependency.module >> module
         _ * module.id >> new DefaultModuleVersionIdentifier(group, moduleName, version)
-        return dependency
+        module
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy
index 35836cf..a28a53f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy
@@ -19,7 +19,7 @@ import org.gradle.api.artifacts.ResolvedArtifact
 import spock.lang.Specification
 
 class DefaultResolvedDependencySpec extends Specification {
-    final DefaultResolvedDependency dependency = new DefaultResolvedDependency("name", "group", "module", "version", "config")
+    final dependency = new DefaultResolvedDependency(DefaultModuleVersionIdentifier.newId("group", "module", "version"), "config")
 
     def "provides meta-data about the module"() {
         expect:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
index 1d110a5..f88f6a1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
@@ -18,7 +18,12 @@ package org.gradle.api.internal.artifacts;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.metadata.IvyArtifactName;
+import org.gradle.internal.Factory;
 import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
 
@@ -28,16 +33,13 @@ import java.util.Set;
 
 import static com.google.common.collect.Iterables.concat;
 import static com.google.common.collect.Sets.newHashSet;
-import static org.gradle.api.artifacts.ArtifactsTestUtils.createResolvedArtifact;
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId;
 import static org.gradle.util.Matchers.strictlyEqual;
 import static org.gradle.util.WrapUtil.toSet;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultResolvedDependencyTest {
     private JUnit4Mockery context = new JUnit4GroovyMockery();
 
@@ -47,7 +49,7 @@ public class DefaultResolvedDependencyTest {
         String someName = "someName";
         String someVersion = "someVersion";
         String someConfiguration = "someConfiguration";
-        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency(someGroup, someName, someVersion, someConfiguration);
+        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency(newId(someGroup, someName, someVersion), someConfiguration);
         assertThat(resolvedDependency.getName(), equalTo(someGroup + ":" + someName + ":" + someVersion));
         assertThat(resolvedDependency.getModuleGroup(), equalTo(someGroup));
         assertThat(resolvedDependency.getModuleName(), equalTo(someName));
@@ -62,9 +64,9 @@ public class DefaultResolvedDependencyTest {
     public void getAllModuleArtifacts() {
         ResolvedArtifact moduleArtifact = createArtifact("moduleArtifact");
         ResolvedArtifact childModuleArtifact = createArtifact("childModuleArtifact");
-        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration");
+        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency(newId("someGroup", "someName", "someVersion"), "someConfiguration");
         resolvedDependency.addModuleArtifact(moduleArtifact);
-        DefaultResolvedDependency childDependency = new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration");
+        DefaultResolvedDependency childDependency = new DefaultResolvedDependency(newId("someGroup", "someChild", "someVersion"), "someChildConfiguration");
         childDependency.addModuleArtifact(childModuleArtifact);
         resolvedDependency.getChildren().add(childDependency);
         assertThat(resolvedDependency.getAllModuleArtifacts(), equalTo(toSet(moduleArtifact, childModuleArtifact)));
@@ -88,8 +90,36 @@ public class DefaultResolvedDependencyTest {
         return createResolvedArtifact(context, name, "someType", "someExt", new File("pathTo" + name));
     }
 
+    public static DefaultResolvedArtifact createResolvedArtifact(final Mockery context, final String name, final String type, final String extension, final File file) {
+        final IvyArtifactName artifactStub = context.mock(IvyArtifactName.class, "artifact" + name);
+        context.checking(new Expectations() {{
+            allowing(artifactStub).getName();
+            will(returnValue(name));
+            allowing(artifactStub).getType();
+            will(returnValue(type));
+            allowing(artifactStub).getExtension();
+            will(returnValue(extension));
+            allowing(artifactStub).getClassifier();
+            will(returnValue(null));
+        }});
+        final Factory artifactSource = context.mock(Factory.class);
+        context.checking(new Expectations() {{
+            allowing(artifactSource).create();
+            will(returnValue(file));
+        }});
+        final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
+        final ResolvedModuleVersion version = context.mock(ResolvedModuleVersion.class);
+        context.checking(new Expectations() {{
+            allowing(resolvedDependency).getModule();
+            will(returnValue(version));
+            allowing(version).getId();
+            will(returnValue(new DefaultModuleVersionIdentifier("group", name, "1.2")));
+        }});
+        return new DefaultResolvedArtifact(resolvedDependency.getModule(), null , artifactStub, artifactSource, 0);
+    }
+
     private DefaultResolvedDependency createResolvedDependency() {
-        return new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration");
+        return new DefaultResolvedDependency(newId("someGroup", "someName", "someVersion"), "someConfiguration");
     }
 
     @Test
@@ -106,7 +136,7 @@ public class DefaultResolvedDependencyTest {
     public void getArtifactsWithParentWithoutParentArtifacts() {
         DefaultResolvedDependency resolvedDependency = createResolvedDependency();
 
-        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration");
+        DefaultResolvedDependency parent = new DefaultResolvedDependency(newId("someGroup", "parent", "someVersion"), "someConfiguration");
         resolvedDependency.getParents().add(parent);
         assertThat(resolvedDependency.getArtifacts(parent), equalTo(Collections.<ResolvedArtifact>emptySet()));
     }
@@ -115,7 +145,7 @@ public class DefaultResolvedDependencyTest {
     public void getParentArtifactsWithParentWithoutParentArtifacts() {
         DefaultResolvedDependency resolvedDependency = createResolvedDependency();
 
-        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration");
+        DefaultResolvedDependency parent = new DefaultResolvedDependency(newId("someGroup", "parent", "someVersion"), "someConfiguration");
         resolvedDependency.getParents().add(parent);
         assertThat(resolvedDependency.getParentArtifacts(parent), equalTo(Collections.<ResolvedArtifact>emptySet()));
     }
@@ -123,7 +153,7 @@ public class DefaultResolvedDependencyTest {
     @Test(expected = InvalidUserDataException.class)
     public void getParentArtifactsWithUnknownParent() {
         DefaultResolvedDependency resolvedDependency = createResolvedDependency();
-        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration");
+        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency(newId("someGroup", "parent2", "someVersion"), "someConfiguration");
         assertThat(resolvedDependency.getParentArtifacts(unknownParent),
                 equalTo(Collections.<ResolvedArtifact>emptySet()));
     }
@@ -133,7 +163,7 @@ public class DefaultResolvedDependencyTest {
         Set<ResolvedArtifact> someModuleArtifacts = toSet(createArtifact("someModuleResolvedArtifact"));
         DefaultResolvedDependency resolvedDependency = createResolvedDependency();
 
-        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration");
+        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency(newId("someGroup", "parent2", "someVersion"), "someConfiguration");
         assertThat(resolvedDependency.getParentArtifacts(unknownParent),
                 equalTo(someModuleArtifacts));
     }
@@ -147,7 +177,7 @@ public class DefaultResolvedDependencyTest {
 
         createAndAddParent("parent2", resolvedDependency, newHashSet(createArtifact("parent2Specific")));
 
-        DefaultResolvedDependency child = new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration");
+        DefaultResolvedDependency child = new DefaultResolvedDependency(newId("someGroup", "someChild", "someVersion"), "someChildConfiguration");
         resolvedDependency.getChildren().add(child);
 
         Set<ResolvedArtifact> childParent1SpecificArtifacts = newHashSet(createArtifact("childParent1Specific"));
@@ -162,12 +192,12 @@ public class DefaultResolvedDependencyTest {
 
     @Test
     public void equalsAndHashCode() {
-        DefaultResolvedDependency dependency = new DefaultResolvedDependency("group", "name", "version", "config");
-        DefaultResolvedDependency same = new DefaultResolvedDependency("group", "name", "version", "config");
-        DefaultResolvedDependency differentGroup = new DefaultResolvedDependency("other", "name", "version", "config");
-        DefaultResolvedDependency differentName = new DefaultResolvedDependency("group", "other", "version", "config");
-        DefaultResolvedDependency differentVersion = new DefaultResolvedDependency("group", "name", "other", "config");
-        DefaultResolvedDependency differentConfiguration = new DefaultResolvedDependency("group", "name", "version", "other");
+        DefaultResolvedDependency dependency = new DefaultResolvedDependency(newId("group", "name", "version"), "config");
+        DefaultResolvedDependency same = new DefaultResolvedDependency(newId("group", "name", "version"), "config");
+        DefaultResolvedDependency differentGroup = new DefaultResolvedDependency(newId("other", "name", "version"), "config");
+        DefaultResolvedDependency differentName = new DefaultResolvedDependency(newId("group", "other", "version"), "config");
+        DefaultResolvedDependency differentVersion = new DefaultResolvedDependency(newId("group", "name", "other"), "config");
+        DefaultResolvedDependency differentConfiguration = new DefaultResolvedDependency(newId("group", "name", "version"), "other");
 
         assertThat(dependency, strictlyEqual(same));
         assertThat(dependency, not(equalTo(differentGroup)));
@@ -177,7 +207,7 @@ public class DefaultResolvedDependencyTest {
     }
 
     private DefaultResolvedDependency createAndAddParent(String parentName, DefaultResolvedDependency resolvedDependency, Set<ResolvedArtifact> parentSpecificArtifacts) {
-        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", parentName, "someVersion", "someConfiguration");
+        DefaultResolvedDependency parent = new DefaultResolvedDependency(newId("someGroup", parentName, "someVersion"), "someConfiguration");
         resolvedDependency.getParents().add(parent);
         resolvedDependency.addParentSpecificArtifacts(parent, parentSpecificArtifacts);
         return parent;
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServicesTest.groovy
new file mode 100644
index 0000000..254c0ce
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServicesTest.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.service.DefaultServiceRegistry
+import spock.lang.Specification
+
+class DependencyManagementBuildScopeServicesTest extends Specification {
+    def services = DefaultServiceRegistry.create(new DependencyManagementBuildScopeServices())
+
+    def "provides a DependencyManagementServices"() {
+        expect:
+        services.get(DependencyManagementServices) instanceof DefaultDependencyManagementServices
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServicesTest.groovy
new file mode 100644
index 0000000..dc13e13
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServicesTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ivyservice.DefaultIvyContextManager
+import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager
+import org.gradle.internal.service.DefaultServiceRegistry
+import spock.lang.Specification
+
+class DependencyManagementGlobalScopeServicesTest extends Specification {
+    def services = DefaultServiceRegistry.create(new DependencyManagementGlobalScopeServices())
+
+    def "provides an IvyContextManager"() {
+        expect:
+        services.get(IvyContextManager) instanceof DefaultIvyContextManager
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorSerializerTest.groovy
new file mode 100644
index 0000000..40e3c28
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorSerializerTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.messaging.serialize.SerializerSpec
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class ModuleVersionSelectorSerializerTest extends SerializerSpec {
+    private serializer = new ModuleVersionSelectorSerializer()
+
+    def "serializes"() {
+        when:
+        def result = serialize(newSelector("org", "foo", "5.0"), serializer)
+
+        then:
+        result == newSelector("org", "foo", "5.0")
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSerializerTest.groovy
new file mode 100644
index 0000000..233d792
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSerializerTest.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.internal.artifacts
+
+import org.gradle.messaging.serialize.SerializerSpec
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+
+class ResolvedConfigurationIdentifierSerializerTest extends SerializerSpec {
+
+    def s = new ResolvedConfigurationIdentifierSerializer()
+
+    def "serializes"() {
+        def id = newId("org", "foo", "2.0")
+
+        when:
+        def out = serialize(new ResolvedConfigurationIdentifier(id, "conf"), s)
+
+        then:
+        out.configuration == "conf"
+        out.id == id
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy
index e975224..441ce80 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy
@@ -21,15 +21,17 @@ package org.gradle.api.internal.artifacts
 import spock.lang.Specification
 import org.gradle.util.Matchers
 
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.*
+
 class ResolvedConfigurationIdentifierSpec extends Specification {
     def equalsAndHashCode() {
         when:
-        ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
-        ResolvedConfigurationIdentifier same = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
-        ResolvedConfigurationIdentifier differentGroup = new ResolvedConfigurationIdentifier('other', 'name', 'version', 'config')
-        ResolvedConfigurationIdentifier differentName = new ResolvedConfigurationIdentifier('group', 'other', 'version', 'config')
-        ResolvedConfigurationIdentifier differentVersion = new ResolvedConfigurationIdentifier('group', 'name', 'other', 'config')
-        ResolvedConfigurationIdentifier differentConfig = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'other')
+        ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(newId('group', 'name', 'version'), 'config')
+        ResolvedConfigurationIdentifier same = new ResolvedConfigurationIdentifier(newId('group', 'name', 'version'), 'config')
+        ResolvedConfigurationIdentifier differentGroup = new ResolvedConfigurationIdentifier(newId('other', 'name', 'version'), 'config')
+        ResolvedConfigurationIdentifier differentName = new ResolvedConfigurationIdentifier(newId('group', 'other', 'version'), 'config')
+        ResolvedConfigurationIdentifier differentVersion = new ResolvedConfigurationIdentifier(newId('group', 'name', 'other'), 'config')
+        ResolvedConfigurationIdentifier differentConfig = new ResolvedConfigurationIdentifier(newId('group', 'name', 'version'), 'other')
 
         then:
         Matchers.strictlyEquals(id, same)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
index 00529ba..596d32b 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
@@ -21,18 +21,15 @@ 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)
+    private results = new ResolverResults()
 
     def "does not provide ResolutionResult in case of fatal failure"() {
         when:
-        def results = new ResolverResults(resolvedConfiguration, fatalFailure)
+        results.failed(resolvedConfiguration, fatalFailure)
 
         then:
         results.resolvedConfiguration
@@ -46,7 +43,7 @@ class ResolverResultsSpec extends Specification {
 
     def "provides resolve results"() {
         when:
-        def results = new ResolverResults(resolvedConfiguration, resolutionResult)
+        results.resolved(resolvedConfiguration, resolutionResult)
 
         then:
         results.resolvedConfiguration == resolvedConfiguration
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactoryTest.groovy
new file mode 100644
index 0000000..3353a45
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactoryTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.component
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.internal.artifacts.DefaultModule
+import org.gradle.api.internal.artifacts.ModuleInternal
+import org.gradle.api.internal.artifacts.ProjectBackedModule
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+class DefaultComponentIdentifierFactoryTest extends Specification {
+    ComponentIdentifierFactory componentIdentifierFactory = new DefaultComponentIdentifierFactory()
+
+    def "can create project component identifier"() {
+        given:
+        Project project = Mock(ProjectInternal)
+        ModuleInternal module = new ProjectBackedModule(project)
+
+        when:
+        ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(module)
+
+        then:
+        project.path >> ':a'
+        componentIdentifier == new DefaultProjectComponentIdentifier(':a')
+    }
+
+    def "can create module component identifier"() {
+        given:
+        ModuleInternal module = new DefaultModule('some-group', 'some-name', '1.0')
+
+        when:
+        ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(module)
+
+        then:
+        componentIdentifier == new DefaultModuleComponentIdentifier('some-group', 'some-name', '1.0')
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentIdentifierTest.groovy
new file mode 100644
index 0000000..4a3119d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentIdentifierTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.component
+
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class DefaultModuleComponentIdentifierTest extends Specification {
+    def "is instantiated with non-null constructor parameter values"() {
+        when:
+        ModuleComponentIdentifier defaultModuleComponentIdentifier = new DefaultModuleComponentIdentifier('some-group', 'some-name', '1.0')
+
+        then:
+        defaultModuleComponentIdentifier.group == 'some-group'
+        defaultModuleComponentIdentifier.module == 'some-name'
+        defaultModuleComponentIdentifier.version == '1.0'
+        defaultModuleComponentIdentifier.displayName == 'some-group:some-name:1.0'
+        defaultModuleComponentIdentifier.toString() == 'some-group:some-name:1.0'
+    }
+
+    @Unroll
+    def "is instantiated with null constructor parameter values (#group, #name, #version)"() {
+        when:
+        new DefaultModuleComponentIdentifier(group, name, version)
+
+        then:
+        thrown(AssertionError)
+
+        where:
+        group        | name        | version
+        null         | 'some-name' | '1.0'
+        'some-group' | null        | '1.0'
+        'some-group' | 'some-name' | null
+    }
+
+    @Unroll
+    def "can compare with other instance (#group, #name, #version)"() {
+        expect:
+        ModuleComponentIdentifier defaultModuleComponentIdentifier1 = new DefaultModuleComponentIdentifier('some-group', 'some-name', '1.0')
+        ModuleComponentIdentifier defaultModuleComponentIdentifier2 = new DefaultModuleComponentIdentifier(group, name, version)
+        strictlyEquals(defaultModuleComponentIdentifier1, defaultModuleComponentIdentifier2) == equality
+        (defaultModuleComponentIdentifier1.hashCode() == defaultModuleComponentIdentifier2.hashCode()) == hashCode
+        (defaultModuleComponentIdentifier1.toString() == defaultModuleComponentIdentifier2.toString()) == stringRepresentation
+
+        where:
+        group         | name         | version | equality | hashCode | stringRepresentation
+        'some-group'  | 'some-name'  | '1.0'   | true     | true     | true
+        'other-group' | 'some-name'  | '1.0'   | false    | false    | false
+        'some-group'  | 'other-name' | '1.0'   | false    | false    | false
+        'some-group'  | 'some-name'  | '2.0'   | false    | false    | false
+    }
+
+    def "can create new ID"() {
+        when:
+        ModuleComponentIdentifier defaultModuleComponentIdentifier = DefaultModuleComponentIdentifier.newId('some-group', 'some-name', '1.0')
+
+        then:
+        defaultModuleComponentIdentifier.group == 'some-group'
+        defaultModuleComponentIdentifier.module == 'some-name'
+        defaultModuleComponentIdentifier.version == '1.0'
+        defaultModuleComponentIdentifier.displayName == 'some-group:some-name:1.0'
+        defaultModuleComponentIdentifier.toString() == 'some-group:some-name:1.0'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentSelectorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentSelectorTest.groovy
new file mode 100644
index 0000000..a9eee5e
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultModuleComponentSelectorTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.component
+
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class DefaultModuleComponentSelectorTest extends Specification {
+    def "is instantiated with non-null constructor parameter values"() {
+        when:
+        ModuleComponentSelector defaultModuleComponentSelector = new DefaultModuleComponentSelector('some-group', 'some-name', '1.0')
+
+        then:
+        defaultModuleComponentSelector.group == 'some-group'
+        defaultModuleComponentSelector.module == 'some-name'
+        defaultModuleComponentSelector.version == '1.0'
+        defaultModuleComponentSelector.displayName == 'some-group:some-name:1.0'
+        defaultModuleComponentSelector.toString() == 'some-group:some-name:1.0'
+    }
+
+    @Unroll
+    def "is instantiated with null constructor parameter values (#group, #name, #version)"() {
+        when:
+        new DefaultModuleComponentSelector(group, name, version)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        assert t.message == assertionMessage
+
+        where:
+        group        | name        | version | assertionMessage
+        null         | 'some-name' | '1.0'   | 'group cannot be null'
+        'some-group' | null        | '1.0'   | 'module cannot be null'
+        'some-group' | 'some-name' | null    | 'version cannot be null'
+    }
+
+    @Unroll
+    def "can compare with other instance (#group, #name, #version)"() {
+        expect:
+        ModuleComponentSelector defaultModuleComponentSelector1 = new DefaultModuleComponentSelector('some-group', 'some-name', '1.0')
+        ModuleComponentSelector defaultModuleComponentSelector2 = new DefaultModuleComponentSelector(group, name, version)
+        strictlyEquals(defaultModuleComponentSelector1, defaultModuleComponentSelector2) == equality
+        (defaultModuleComponentSelector1.hashCode() == defaultModuleComponentSelector2.hashCode()) == hashCode
+        (defaultModuleComponentSelector1.toString() == defaultModuleComponentSelector2.toString()) == stringRepresentation
+
+        where:
+        group         | name         | version | equality | hashCode | stringRepresentation
+        'some-group'  | 'some-name'  | '1.0'   | true     | true     | true
+        'other-group' | 'some-name'  | '1.0'   | false    | false    | false
+        'some-group'  | 'other-name' | '1.0'   | false    | false    | false
+        'some-group'  | 'some-name'  | '2.0'   | false    | false    | false
+    }
+
+    def "can create new selector"() {
+        when:
+        ModuleComponentSelector defaultModuleComponentSelector = DefaultModuleComponentSelector.newSelector('some-group', 'some-name', '1.0')
+
+        then:
+        defaultModuleComponentSelector.group == 'some-group'
+        defaultModuleComponentSelector.module == 'some-name'
+        defaultModuleComponentSelector.version == '1.0'
+        defaultModuleComponentSelector.displayName == 'some-group:some-name:1.0'
+        defaultModuleComponentSelector.toString() == 'some-group:some-name:1.0'
+    }
+
+    def "prevents matching of null id"() {
+        when:
+        ModuleComponentSelector defaultModuleComponentSelector = new DefaultModuleComponentSelector('some-group', 'some-name', '1.0')
+        defaultModuleComponentSelector.matchesStrictly(null)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        assert t.message == 'identifier cannot be null'
+    }
+
+    def "does not match id for unexpected component selector type"() {
+        when:
+        ModuleComponentSelector defaultModuleComponentSelector = new DefaultModuleComponentSelector('some-group', 'some-name', '1.0')
+        boolean matches = defaultModuleComponentSelector.matchesStrictly(new DefaultProjectComponentIdentifier(':mypath'))
+
+        then:
+        assert !matches
+    }
+
+    @Unroll
+    def "matches id (#group, #name, #version)"() {
+        expect:
+        ModuleComponentSelector defaultModuleComponentSelector = new DefaultModuleComponentSelector('some-group', 'some-name', '1.0')
+        ModuleComponentIdentifier defaultModuleComponentIdentifier = new DefaultModuleComponentIdentifier(group, name, version)
+        defaultModuleComponentSelector.matchesStrictly(defaultModuleComponentIdentifier) == matchesId
+
+        where:
+        group         | name         | version | matchesId
+        'some-group'  | 'some-name'  | '1.0'   | true
+        'other-group' | 'some-name'  | '1.0'   | false
+        'some-group'  | 'other-name' | '1.0'   | false
+        'some-group'  | 'some-name'  | '2.0'   | false
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentIdentifierTest.groovy
new file mode 100644
index 0000000..ddf9516
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentIdentifierTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.component
+
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class DefaultProjectComponentIdentifierTest extends Specification {
+    def "is instantiated with non-null constructor parameter values"() {
+        when:
+        ProjectComponentIdentifier defaultBuildComponentIdentifier = new DefaultProjectComponentIdentifier(':myPath')
+
+        then:
+        defaultBuildComponentIdentifier.projectPath == ':myPath'
+        defaultBuildComponentIdentifier.displayName == 'project :myPath'
+        defaultBuildComponentIdentifier.toString() == 'project :myPath'
+    }
+
+    def "is instantiated with null constructor parameter value"() {
+        when:
+        new DefaultProjectComponentIdentifier(null)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        t.message == 'project path cannot be null'
+    }
+
+    @Unroll
+    def "can compare with other instance (#projectPath)"() {
+        expect:
+        ProjectComponentIdentifier defaultBuildComponentIdentifier1 = new DefaultProjectComponentIdentifier(':myProjectPath1')
+        ProjectComponentIdentifier defaultBuildComponentIdentifier2 = new DefaultProjectComponentIdentifier(projectPath)
+        strictlyEquals(defaultBuildComponentIdentifier1, defaultBuildComponentIdentifier2) == equality
+        (defaultBuildComponentIdentifier1.hashCode() == defaultBuildComponentIdentifier2.hashCode()) == hashCode
+        (defaultBuildComponentIdentifier1.toString() == defaultBuildComponentIdentifier2.toString()) == stringRepresentation
+
+        where:
+        projectPath       | equality | hashCode | stringRepresentation
+        ':myProjectPath1' | true     | true     | true
+        ':myProjectPath2' | false    | false    | false
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentSelectorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentSelectorTest.groovy
new file mode 100644
index 0000000..a0f1d22
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/component/DefaultProjectComponentSelectorTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.component
+
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier
+import org.gradle.api.artifacts.component.ProjectComponentSelector
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class DefaultProjectComponentSelectorTest extends Specification {
+    def "is instantiated with non-null constructor parameter values"() {
+        when:
+        ProjectComponentSelector defaultBuildComponentSelector = new DefaultProjectComponentSelector(':myPath')
+
+        then:
+        defaultBuildComponentSelector.projectPath == ':myPath'
+        defaultBuildComponentSelector.displayName == 'project :myPath'
+        defaultBuildComponentSelector.toString() == 'project :myPath'
+    }
+
+    def "is instantiated with null constructor parameter value"() {
+        when:
+        new DefaultProjectComponentSelector(null)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        t.message == 'project path cannot be null'
+    }
+
+    @Unroll
+    def "can compare with other instance (#projectPath)"() {
+        expect:
+        ProjectComponentSelector defaultBuildComponentSelector1 = new DefaultProjectComponentSelector(':myProjectPath1')
+        ProjectComponentSelector defaultBuildComponentSelector2 = new DefaultProjectComponentSelector(projectPath)
+        strictlyEquals(defaultBuildComponentSelector1, defaultBuildComponentSelector2) == equality
+        (defaultBuildComponentSelector1.hashCode() == defaultBuildComponentSelector2.hashCode()) == hashCode
+        (defaultBuildComponentSelector1.toString() == defaultBuildComponentSelector2.toString()) == stringRepresentation
+
+        where:
+        projectPath       | equality | hashCode | stringRepresentation
+        ':myProjectPath1' | true     | true     | true
+        ':myProjectPath2' | false    | false    | false
+    }
+
+    def "prevents matching of null id"() {
+        when:
+        ProjectComponentSelector defaultBuildComponentSelector = new DefaultProjectComponentSelector(':myPath')
+        defaultBuildComponentSelector.matchesStrictly(null)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        assert t.message == 'identifier cannot be null'
+    }
+
+    def "does not match id for unexpected component selector type"() {
+        when:
+        ProjectComponentSelector defaultBuildComponentSelector = new DefaultProjectComponentSelector(':myPath')
+        boolean matches = defaultBuildComponentSelector.matchesStrictly(new DefaultModuleComponentIdentifier('group', 'name', '1.0'))
+
+        then:
+        assert !matches
+    }
+
+    @Unroll
+    def "matches id (#projectPath)"() {
+        expect:
+        ProjectComponentSelector defaultBuildComponentSelector = new DefaultProjectComponentSelector(':myProjectPath1')
+        ProjectComponentIdentifier defaultBuildComponentIdentifier = new DefaultProjectComponentIdentifier(projectPath)
+        defaultBuildComponentSelector.matchesStrictly(defaultBuildComponentIdentifier) == matchesId
+
+        where:
+        projectPath       | matchesId
+        ':myProjectPath1' | true
+        ':myProjectPath2' | false
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
index 5d97ed4..3a3ecb1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
@@ -20,18 +20,10 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class ConfigurationsTest {
     private static final String TEST_CONF = "testConf";
 
     @Test
-    public void testUploadInternalTaskName() {
-        assertThat(Configurations.uploadInternalTaskName(TEST_CONF), Matchers.equalTo("uploadTestConfInternal"));
-    }
-
-    @Test
     public void testUploadTaskName() {
         assertThat(Configurations.uploadTaskName(TEST_CONF), Matchers.equalTo("uploadTestConf"));
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
index 7c63a99..848d31c 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
@@ -46,7 +46,7 @@ public class DefaultConfigurationContainerSpec extends Specification {
                 resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
 
         when:
-        def compile = configurationContainer.add("compile")
+        def compile = configurationContainer.create("compile")
 
         then:
         configurationContainer.getByName("compile") == compile
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
index 1cd233a..6efd9ed 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
@@ -35,9 +35,6 @@ import org.junit.runner.RunWith
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
-/**
- * @author Hans Dockter
- */
 
 @RunWith(JMock)
 class DefaultConfigurationContainerTest {
@@ -47,7 +44,7 @@ class DefaultConfigurationContainerTest {
     private ListenerManager listenerManager = context.mock(ListenerManager.class)
     private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class)
     private Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
-    private DefaultConfigurationContainer configurationHandler = instantiator.newInstance(DefaultConfigurationContainer.class,
+    private DefaultConfigurationContainer configurationContainer = instantiator.newInstance(DefaultConfigurationContainer.class,
             resolver, instantiator, { name -> name } as DomainObjectContext,
             listenerManager, metaDataProvider)
 
@@ -60,42 +57,42 @@ class DefaultConfigurationContainerTest {
 
     @Test
     void addsNewConfigurationWhenConfiguringSelf() {
-        configurationHandler.configure {
+        configurationContainer.configure {
             newConf
         }
-        assertThat(configurationHandler.findByName('newConf'), notNullValue())
-        assertThat(configurationHandler.newConf, notNullValue())
+        assertThat(configurationContainer.findByName('newConf'), notNullValue())
+        assertThat(configurationContainer.newConf, notNullValue())
     }
 
     @Test(expected = UnknownConfigurationException)
     void doesNotAddNewConfigurationWhenNotConfiguringSelf() {
-        configurationHandler.getByName('unknown')
+        configurationContainer.getByName('unknown')
     }
 
     @Test
     void makesExistingConfigurationAvailableAsProperty() {
-        Configuration configuration = configurationHandler.add('newConf')
+        Configuration configuration = configurationContainer.create('newConf')
         assertThat(configuration, notNullValue())
-        assertThat(configurationHandler.getByName("newConf"), sameInstance(configuration))
-        assertThat(configurationHandler.newConf, sameInstance(configuration))
+        assertThat(configurationContainer.getByName("newConf"), sameInstance(configuration))
+        assertThat(configurationContainer.newConf, sameInstance(configuration))
     }
 
     @Test
     void addsNewConfigurationWithClosureWhenConfiguringSelf() {
         String someDesc = 'desc1'
-        configurationHandler.configure {
+        configurationContainer.configure {
             newConf {
                 description = someDesc
             }
         }
-        assertThat(configurationHandler.newConf.getDescription(), equalTo(someDesc))
+        assertThat(configurationContainer.newConf.getDescription(), equalTo(someDesc))
     }
 
     @Test
     void makesExistingConfigurationAvailableAsConfigureMethod() {
         String someDesc = 'desc1'
-        configurationHandler.add('newConf')
-        Configuration configuration = configurationHandler.newConf {
+        configurationContainer.create('newConf')
+        Configuration configuration = configurationContainer.newConf {
             description = someDesc
         }
         assertThat(configuration.getDescription(), equalTo(someDesc))
@@ -104,8 +101,8 @@ class DefaultConfigurationContainerTest {
     @Test
     void makesExistingConfigurationAvailableAsConfigureMethodWhenConfiguringSelf() {
         String someDesc = 'desc1'
-        Configuration configuration = configurationHandler.add('newConf')
-        configurationHandler.configure {
+        Configuration configuration = configurationContainer.create('newConf')
+        configurationContainer.configure {
             newConf {
                 description = someDesc
             }
@@ -115,6 +112,6 @@ class DefaultConfigurationContainerTest {
 
     @Test(expected = MissingMethodException)
     void newConfigurationWithNonClosureParametersShouldThrowMissingMethodEx() {
-        configurationHandler.newConf('a', 'b')
+        configurationContainer.newConf('a', 'b')
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
index abe706e..0d01f1c 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
@@ -58,7 +58,9 @@ class DefaultConfigurationSpec extends Specification {
     // You need to wrap this in an interaction {} block when calling it
     ResolvedConfiguration resolvedConfiguration(Configuration config, ConfigurationResolver dependencyResolver = resolver) {
         ResolvedConfiguration resolvedConfiguration = Mock()
-        1 * dependencyResolver.resolve(config) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        def results = new ResolverResults()
+        results.resolved(resolvedConfiguration, Mock(ResolutionResult))
+        1 * dependencyResolver.resolve(config) >> results
         resolvedConfiguration
     }
 
@@ -305,12 +307,14 @@ class DefaultConfigurationSpec extends Specification {
     def "provides resolution result"() {
         def config = conf("conf")
         def result = Mock(ResolutionResult)
+        def resolverResults = new ResolverResults()
+        resolverResults.resolved(Mock(ResolvedConfiguration), result)
 
         when:
         def out = config.incoming.resolutionResult
 
         then:
-        1 * resolver.resolve(config) >> new ResolverResults(Mock(ResolvedConfiguration), result)
+        1 * resolver.resolve(config) >> resolverResults
         out == result
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
index 5384568..76d3270 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
@@ -270,7 +270,7 @@ public class DefaultConfigurationTest {
 
     @Test
     public void filesWithClosureSpec() {
-        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
+        Closure closure = TestUtil.toClosure("{ dep -> dep.group == 'group1' }");
         final Set<File> fileSet = toSet(new File("somePath"));
         prepareForFilesBySpec(fileSet);
         assertThat(configuration.files(closure), equalTo(fileSet));
@@ -279,7 +279,7 @@ public class DefaultConfigurationTest {
 
     @Test
     public void fileCollectionWithClosureSpec() {
-        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
+        Closure closure = TestUtil.toClosure("{ dep -> dep.group == 'group1' }");
         DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
                 configuration.fileCollection(closure);
         assertThat(fileCollection.getDependencySpec().isSatisfiedBy(createDependency("group1", "name", "version")),
@@ -332,8 +332,10 @@ public class DefaultConfigurationTest {
 
     private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
         context.checking(new Expectations() {{
+            ResolverResults result = new ResolverResults();
+            result.resolved(resolvedConfiguration, context.mock(ResolutionResult.class));
             allowing(dependencyResolver).resolve(configuration);
-            will(returnValue(new ResolverResults(resolvedConfiguration, context.mock(ResolutionResult.class))));
+            will(returnValue(result));
             allowing(resolvedConfiguration).hasError();
             will(returnValue(withErrors));
         }});
@@ -680,7 +682,7 @@ public class DefaultConfigurationTest {
             one(closure).call(artifact);
         }});
 
-        configuration.getArtifacts().whenObjectAdded(HelperUtil.toClosure(closure));
+        configuration.getArtifacts().whenObjectAdded(TestUtil.toClosure(closure));
         configuration.getArtifacts().add(artifact);
     }
 
@@ -695,7 +697,7 @@ public class DefaultConfigurationTest {
             one(closure).call(artifact);
         }});
 
-        configuration.getArtifacts().whenObjectRemoved(HelperUtil.toClosure(closure));
+        configuration.getArtifacts().whenObjectRemoved(TestUtil.toClosure(closure));
 
         configuration.getArtifacts().remove(artifact);
     }
@@ -755,7 +757,7 @@ public class DefaultConfigurationTest {
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
         configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
 
-        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
+        Closure specClosure = TestUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
         Configuration copiedConfiguration = configuration.copy(specClosure);
 
         assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
@@ -800,7 +802,7 @@ public class DefaultConfigurationTest {
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
         configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
 
-        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
+        Closure specClosure = TestUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
         Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
 
         assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy
index 878e386..6326326 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy
@@ -16,9 +16,6 @@
 package org.gradle.api.internal.artifacts.dsl
 
 import spock.lang.Specification
-/**
- * @author Hans Dockter
- */
 public class ArtifactFileTest extends Specification {
     final String module = '1.2'
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
index a31f49b..1128c6e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
@@ -16,26 +16,20 @@
 
 package org.gradle.api.internal.artifacts.dsl
 
-import org.gradle.api.artifacts.PublishArtifactSet
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.PublishArtifactSet
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.internal.typeconversion.NotationParser
 import spock.lang.Specification
-import org.gradle.api.internal.notations.api.NotationParser
 
-/**
- * @author Hans Dockter
- */
 class DefaultArtifactHandlerTest extends Specification {
 
     private static final String TEST_CONF_NAME = "someConf"
 
-    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
     private ConfigurationContainer configurationContainerStub = Mock()
-    private NotationParser<PublishArtifact> artifactFactoryStub = Mock()
+    private NotationParser<Object, PublishArtifact> artifactFactoryStub = Mock()
     private Configuration configurationMock = Mock()
     private PublishArtifactSet artifactsMock = Mock()
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandlerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandlerTest.groovy
new file mode 100644
index 0000000..974615b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandlerTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
+import org.gradle.internal.reflect.DirectInstantiator
+import spock.lang.Specification
+
+class DefaultComponentMetadataHandlerTest extends Specification {
+    def handler = new DefaultComponentMetadataHandler(new DirectInstantiator())
+
+    def "processing fails when status is not present in status scheme"() {
+        def metadata = Stub(MutableModuleVersionMetaData) {
+            getId() >> new DefaultModuleVersionIdentifier("group", "module", "version")
+            getStatus() >> "green"
+            getStatusScheme() >> ["alpha", "beta"]
+        }
+
+        when:
+        handler.process(metadata)
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e.message == /Unexpected status 'green' specified for group:module:version. Expected one of: [alpha, beta]/
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy
index f42522c..03dbf4b 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy
@@ -24,9 +24,6 @@ import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.new
 import static org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers.multiParser
 import static org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers.parser
 
-/**
- * by Szczepan Faber, created at: 10/14/11
- */
 public class ModuleVersionSelectorParsersTest extends Specification {
 
     def "understands group:name:version notation"() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy
index 4d2bf8f..1afbfac 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy
@@ -17,30 +17,27 @@ package org.gradle.api.internal.artifacts.dsl
 
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Task
-import org.gradle.api.artifacts.Module
 import org.gradle.api.artifacts.PublishArtifact
 import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.artifacts.ModuleInternal
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.api.internal.notations.api.NotationParser
 import org.gradle.api.tasks.bundling.AbstractArchiveTask
 import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.typeconversion.NotationParser
 import spock.lang.Specification
 
 import java.awt.*
 
-/**
- * @author Hans Dockter
- */
 public class PublishArtifactNotationParserFactoryTest extends Specification {
     final DependencyMetaDataProvider provider = Mock()
     final Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
     final PublishArtifactNotationParserFactory publishArtifactNotationParserFactory = new PublishArtifactNotationParserFactory(instantiator, provider)
-    final NotationParser<PublishArtifact> publishArtifactNotationParser = publishArtifactNotationParserFactory.create();
+    final NotationParser<Object, PublishArtifact> publishArtifactNotationParser = publishArtifactNotationParserFactory.create();
 
     def setup() {
-        Module module = Mock()
+        ModuleInternal module = Mock()
         _ * provider.module >> module
         _ * module.version >> '1.2'
     }
@@ -78,6 +75,18 @@ public class PublishArtifactNotationParserFactoryTest extends Specification {
         publishArtifact.file == file
     }
 
+    def "creates artifact from extension-less file"() {
+        def file = new File("someFile")
+
+        when:
+        def publishArtifact = publishArtifactNotationParser.parseNotation(file)
+
+        then:
+        publishArtifact instanceof DefaultPublishArtifact
+        publishArtifact.file == file
+        publishArtifact.type == ''
+    }
+
     def createArtifactFromFileInMap() {
         Task task = Mock()
         def file = new File("some-file-1.2-classifier.zip")
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLayoutTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLayoutTest.groovy
new file mode 100644
index 0000000..037e189
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLayoutTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.VersionNumber
+import spock.lang.Specification
+
+class CacheLayoutTest extends Specification {
+    def "use root layout"() {
+        when:
+        CacheLayout cacheLayout = CacheLayout.ROOT
+
+        then:
+        cacheLayout.key == 'modules-2'
+        cacheLayout.version == VersionNumber.parse("2.0.0")
+        cacheLayout.formattedVersion == '2'
+        cacheLayout.getPath(new File('some/dir')) == new File('some/dir/modules-2')
+    }
+
+    def "use file store layout"() {
+        when:
+        CacheLayout cacheLayout = CacheLayout.FILE_STORE
+
+        then:
+        cacheLayout.key == 'files-2.1'
+        cacheLayout.version == VersionNumber.parse("2.1.0")
+        cacheLayout.formattedVersion == '2.1'
+        cacheLayout.getPath(new File('some/dir')) == new File('some/dir/files-2.1')
+    }
+
+    def "use metadata store layout"() {
+        when:
+        CacheLayout cacheLayout = CacheLayout.META_DATA
+
+        then:
+        cacheLayout.key == 'metadata-2.6'
+        cacheLayout.version == VersionNumber.parse("2.6.0")
+        cacheLayout.formattedVersion == '2.6'
+        cacheLayout.getPath(new File('some/dir')) == new File('some/dir/metadata-2.6')
+    }
+}
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 bdded94..34a3818 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,6 +16,7 @@
 package org.gradle.api.internal.artifacts.ivyservice
 
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
@@ -24,6 +25,7 @@ import spock.lang.Specification
 class CacheLockingArtifactDependencyResolverTest extends Specification {
     final CacheLockingManager lockingManager = Mock()
     final ArtifactDependencyResolver target = Mock()
+    final ModuleMetadataProcessor metadataProcessor = Stub()
     final List<ResolutionAwareRepository> repositories = [Mock(ResolutionAwareRepository)]
     final CacheLockingArtifactDependencyResolver resolver = new CacheLockingArtifactDependencyResolver(lockingManager, target)
 
@@ -32,15 +34,12 @@ class CacheLockingArtifactDependencyResolverTest extends Specification {
         ResolverResults resolverResults = Mock()
 
         when:
-        def results = resolver.resolve(configuration, repositories)
+        resolver.resolve(configuration, repositories, metadataProcessor, resolverResults)
 
         then:
-        results == resolverResults
-
-        and:
-        1 * lockingManager.useCache("resolve $configuration", !null) >> {
-            it[1].create()
+        1 * lockingManager.useCache("resolve $configuration", !null) >> { String s, Runnable r ->
+            r.run()
         }
-        1 * target.resolve(configuration, repositories) >> resolverResults
+        1 * target.resolve(configuration, repositories, metadataProcessor, 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
index 292b4bc..8872dbe 100644
--- 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
@@ -16,10 +16,9 @@
 
 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 org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
 import spock.lang.Specification
 
 class DefaultBuildableArtifactResolveResultTest extends Specification {
@@ -57,7 +56,7 @@ class DefaultBuildableArtifactResolveResultTest extends Specification {
 
     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"))
+        result.notFound(Stub(ModuleVersionArtifactIdentifier))
 
         then:
         result.failure instanceof ArtifactNotFoundException
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactSetResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactSetResolveResultTest.groovy
new file mode 100644
index 0000000..c8b8257
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactSetResolveResultTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData
+import spock.lang.Specification
+
+class DefaultBuildableArtifactSetResolveResultTest extends Specification {
+    final result = new DefaultBuildableArtifactSetResolveResult()
+
+    def "cannot get artifacts when no result specified"() {
+        when:
+        result.artifacts
+
+        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 artifacts when resolve failed"() {
+        def failure = new ArtifactResolveException("broken")
+
+        when:
+        result.failed(failure)
+        result.artifacts
+
+        then:
+        ArtifactResolveException e = thrown()
+        e == failure
+    }
+
+    def "has result when artifacts set"() {
+        when:
+        def artifact = Mock(ComponentArtifactMetaData)
+        result.resolved([artifact])
+
+        then:
+        result.hasResult()
+        result.failure == null
+        result.artifacts == [artifact] as Set
+    }
+
+    def "has result when failure set"() {
+        when:
+        final failure = new ArtifactResolveException("bad")
+        result.failed(failure)
+
+        then:
+        result.hasResult()
+        result.failure == failure
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableComponentResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableComponentResolveResultTest.groovy
new file mode 100644
index 0000000..c4d9eb3
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableComponentResolveResultTest.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.api.internal.artifacts.ivyservice
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class DefaultBuildableComponentResolveResultTest extends Specification {
+    def result = new DefaultBuildableComponentResolveResult()
+
+    def "can query id and meta-data when resolved"() {
+        ModuleVersionIdentifier id = Stub()
+        ModuleVersionMetaData metaData = Stub() {
+            getId() >> id
+        }
+
+        when:
+        result.resolved(metaData)
+
+        then:
+        result.id == id
+        result.metaData == metaData
+    }
+
+    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 meta-data when no result has been specified"() {
+        when:
+        result.metaData
+
+        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(newSelector("a", "b", "c"), "broken")
+
+        when:
+        result.failed(failure)
+        result.id
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get meta-data when resolve failed"() {
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+
+        when:
+        result.failed(failure)
+        result.metaData
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "failure is null when successfully resolved"() {
+        when:
+        result.resolved(Mock(ModuleVersionMetaData))
+
+        then:
+        result.failure == null
+    }
+
+    def "fails with a not found exception when not found"() {
+        when:
+        result.notFound(Mock(ModuleVersionSelector))
+
+        then:
+        result.failure instanceof ModuleVersionNotFoundException
+    }
+}
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
deleted file mode 100644
index 88adc16..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
+++ /dev/null
@@ -1,148 +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.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ModuleVersionSelector
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData
-import spock.lang.Specification
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-
-class DefaultBuildableModuleVersionResolveResultTest extends Specification {
-    def result = new DefaultBuildableModuleVersionResolveResult()
-
-    def "can query id and meta-data when resolved"() {
-        ModuleVersionIdentifier id = Stub()
-        ModuleVersionMetaData metaData = Stub() {
-            getId() >> id
-        }
-        ArtifactResolver resolver = Stub()
-
-        when:
-        result.resolved(metaData, resolver)
-
-        then:
-        result.id == id
-        result.artifactResolver == resolver
-        result.metaData == metaData
-    }
-
-    def "can resolve using id and ivy descriptor"() {
-        ModuleVersionIdentifier id = Mock()
-        ModuleDescriptor descriptor = Mock()
-        ArtifactResolver resolver = Mock()
-
-        when:
-        result.resolved(id, descriptor, resolver)
-
-        then:
-        result.id == id
-        result.artifactResolver == resolver
-        result.metaData.id == id
-        result.metaData.descriptor == descriptor
-        !result.metaData.changing
-    }
-
-    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 meta-data when no result has been specified"() {
-        when:
-        result.metaData
-
-        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(newSelector("a", "b", "c"), "broken")
-
-        when:
-        result.failed(failure)
-        result.id
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot get meta-data when resolve failed"() {
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-
-        when:
-        result.failed(failure)
-        result.metaData
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot get artifact resolver when resolve failed"() {
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-
-        when:
-        result.failed(failure)
-        result.artifactResolver
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "failure is null when successfully resolved"() {
-        when:
-        result.resolved(Mock(ModuleVersionIdentifier), Mock(ModuleDescriptor), Mock(ArtifactResolver))
-
-        then:
-        result.failure == null
-    }
-
-    def "fails with a not found exception when not found"() {
-        when:
-        result.notFound(Mock(ModuleVersionSelector))
-
-        then:
-        result.failure instanceof ModuleVersionNotFoundException
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManagerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManagerTest.groovy
new file mode 100644
index 0000000..175b9eb
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManagerTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.cache.CacheBuilder
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.FileLockManager
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
+
+class DefaultCacheLockingManagerTest extends Specification {
+    CacheRepository cacheRepository = Mock()
+    CacheBuilder directoryCacheBuilder = Mock()
+    PersistentCache persistentCache = Mock()
+    @Rule TestNameTestDirectoryProvider temporaryFolder
+
+    def "Create file store"() {
+        given:
+        TestFile testCacheDir = temporaryFolder.file("test/cache")
+
+        when:
+        CacheLockingManager cacheLockingManager = new DefaultCacheLockingManager(cacheRepository)
+        File fileStore = cacheLockingManager.getFileStoreDirectory()
+
+        then:
+        fileStore == new File(testCacheDir, CacheLayout.FILE_STORE.key)
+
+        and:
+        1 * cacheRepository.store(CacheLayout.ROOT.getKey()) >> directoryCacheBuilder
+        1 * directoryCacheBuilder.withDisplayName("artifact cache") >> directoryCacheBuilder
+        1 * directoryCacheBuilder.withCrossVersionCache() >> directoryCacheBuilder
+        1 * directoryCacheBuilder.withLockOptions(mode(FileLockManager.LockMode.None)) >> directoryCacheBuilder
+        1 * directoryCacheBuilder.open() >> persistentCache
+        _ * persistentCache.baseDir >> testCacheDir
+    }
+
+    def "Create metadata store"() {
+        given:
+        TestFile testCacheDir = temporaryFolder.file("test/cache")
+
+        when:
+        CacheLockingManager cacheLockingManager = new DefaultCacheLockingManager(cacheRepository)
+        File fileStore = cacheLockingManager.createMetaDataStore()
+
+        then:
+        fileStore == new File(testCacheDir, CacheLayout.META_DATA.key + System.properties['file.separator'] + 'descriptors')
+
+        and:
+        1 * cacheRepository.store(CacheLayout.ROOT.getKey()) >> directoryCacheBuilder
+        1 * directoryCacheBuilder.withDisplayName("artifact cache") >> directoryCacheBuilder
+        1 * directoryCacheBuilder.withCrossVersionCache() >> directoryCacheBuilder
+        1 * directoryCacheBuilder.withLockOptions(mode(FileLockManager.LockMode.None)) >> directoryCacheBuilder
+        1 * directoryCacheBuilder.open() >> persistentCache
+        _ * persistentCache.baseDir >> testCacheDir
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
index 3cb3df0..16e513a 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
@@ -16,14 +16,11 @@
 
 package org.gradle.api.internal.artifacts.ivyservice
 
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
 
-/**
- * by Szczepan Faber, created at: 12/13/12
- */
 class DefaultDependencyResolveDetailsSpec extends Specification {
 
     def "can specify version to use"() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyContextManagerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyContextManagerTest.groovy
new file mode 100644
index 0000000..099072c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyContextManagerTest.groovy
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.IvyContext
+import org.apache.ivy.plugins.resolver.DependencyResolver
+import org.gradle.api.Action
+import org.gradle.api.Transformer
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+
+class DefaultIvyContextManagerTest extends ConcurrentSpec {
+    final manager = new DefaultIvyContextManager()
+
+    def setup() {
+        IvyContext.currentStack.clear()
+    }
+
+    def "executes action against an Ivy instance"() {
+        def action = Mock(Action)
+
+        when:
+        manager.withIvy(action)
+
+        then:
+        1 * action.execute({it != null})
+        0 * action._
+    }
+
+    def "executes action against an Ivy instance and returns the result"() {
+        def action = Mock(Transformer)
+
+        when:
+        def result = manager.withIvy(action)
+
+        then:
+        result == "result"
+
+        and:
+        1 * action.transform({it != null}) >> "result"
+        0 * action._
+    }
+
+    def "nested actions are executed against the same Ivy instance"() {
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+        def transformer = Mock(Transformer)
+        def ivy
+
+        when:
+        manager.withIvy(action1)
+
+        then:
+        1 * action1.execute(_) >> { Ivy param ->
+            ivy = param
+            manager.withIvy(transformer)
+        }
+        1 * transformer.transform(_) >> { Ivy param ->
+            assert param.is(ivy)
+            manager.withIvy(action2)
+        }
+        1 * action2.execute(_) >> { Ivy param ->
+            assert param.is(ivy)
+        }
+        0 * _._
+    }
+
+    def "sets up Ivy context stack and cleans up after action"() {
+        given:
+        def action = Mock(Action)
+
+        when:
+        manager.withIvy(action)
+
+        then:
+        1 * action.execute(_) >> { Ivy ivy ->
+            assert IvyContext.context.ivy.is(ivy)
+        }
+
+        and:
+        IvyContext.currentStack.empty()
+    }
+
+    def "cleans up after failed action"() {
+        given:
+        def action = Mock(Action)
+        def failure = new RuntimeException()
+
+        and:
+        action.execute(_) >> { Ivy ivy ->
+            throw failure
+        }
+
+        when:
+        manager.withIvy(action)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
+        IvyContext.currentStack.empty()
+    }
+
+    def "reuses Ivy and IvySettings instances"() {
+        given:
+        def action = Mock(Action)
+        def ivy
+        def ivySettings
+
+        when:
+        manager.withIvy(action)
+
+        then:
+        1 * action.execute(_) >> { Ivy param ->
+            ivy = param
+            ivySettings = param.settings
+        }
+
+        when:
+        manager.withIvy(action)
+
+        then:
+        1 * action.execute(_) >> { Ivy param ->
+            assert param.is(ivy)
+            assert param.settings.is(ivySettings)
+            assert IvyContext.context.ivy.is(ivy)
+        }
+
+        when:
+        async {
+            start {
+                manager.withIvy(action)
+            }
+        }
+
+        then:
+        1 * action.execute(_) >> { Ivy param ->
+            assert param.is(ivy)
+            assert param.settings.is(ivySettings)
+            assert IvyContext.context.ivy.is(ivy)
+        }
+
+        and:
+        IvyContext.currentStack.empty()
+    }
+
+    def "resets Ivy settings on reuse"() {
+        given:
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+        def ivy
+        def ivySettings
+
+        when:
+        manager.withIvy(action1)
+
+        then:
+        1 * action1.execute(_) >> { Ivy param ->
+            ivy = param
+            ivySettings = param.settings
+            ivySettings.addResolver(Stub(DependencyResolver) { getName() >> "some-resolver" })
+            ivySettings.setDefaultResolver("some-resolver")
+        }
+
+        when:
+        manager.withIvy(action2)
+
+        then:
+        1 * action2.execute(_) >> { Ivy param ->
+            assert param.settings.is(ivySettings)
+            assert ivySettings.defaultResolverName == null
+            assert ivySettings.resolvers.empty
+            assert ivySettings.resolverNames.empty
+        }
+    }
+
+    def "each thread is given a separate Ivy instance and context"() {
+        given:
+        def ivy1
+        def ivySettings1
+        def ivy2
+        def ivySettings2
+
+        when:
+        async {
+            start {
+                manager.withIvy({ param ->
+                    instant.action1
+                    thread.blockUntil.action2
+                    ivy1 = param
+                    ivySettings1 = param.settings
+                    assert IvyContext.context.ivy.is(ivy1)
+                } as Action)
+            }
+            start {
+                manager.withIvy({ param ->
+                    instant.action2
+                    thread.blockUntil.action1
+                    ivy2 = param
+                    ivySettings2 = param.settings
+                    assert IvyContext.context.ivy.is(ivy2)
+                } as Action)
+            }
+        }
+
+        then:
+        !ivy1.is(ivy2)
+        !ivySettings1.is(ivySettings2)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.groovy
deleted file mode 100644
index 521ee61..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice
-
-import spock.lang.Specification
-import org.apache.ivy.core.settings.IvySettings
-
-class DefaultIvyFactoryTest extends Specification {
-    final DefaultIvyFactory factory = new DefaultIvyFactory()
-
-    def "creates Ivy instance for IvySettings"() {
-        def ivySettings = new IvySettings()
-
-        expect:
-        def ivy = factory.createIvy(ivySettings)
-        ivy.settings == ivySettings
-    }
-
-    def "caches Ivy instance for given IvySettings"() {
-        def ivySettings = new IvySettings()
-
-        expect:
-        def ivy1 = factory.createIvy(ivySettings)
-        def ivy2 = factory.createIvy(ivySettings)
-        ivy1.is(ivy2)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
deleted file mode 100644
index aed2959..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice
-
-import org.apache.ivy.core.settings.IvySettings
-import org.apache.ivy.plugins.resolver.DependencyResolver
-import org.apache.ivy.plugins.resolver.IBiblioResolver
-import org.gradle.internal.Factory
-import spock.lang.Specification
-
-class DefaultSettingsConverterTest extends Specification {
-    final DependencyResolver defaultResolver = Mock()
-    final IBiblioResolver testResolver = new IBiblioResolver()
-    final IBiblioResolver testResolver2 = new IBiblioResolver()
-
-    final Factory<IvySettings> ivySettingsFactory = Mock()
-    final IvySettings ivySettings = new IvySettings()
-
-    DefaultSettingsConverter converter = new DefaultSettingsConverter(ivySettingsFactory)
-
-    public void setup() {
-        testResolver.name = 'resolver'
-    }
-
-    public void testConvertForResolve() {
-        when:
-        IvySettings settings = converter.convertForResolve(defaultResolver)
-
-        then:
-        1 * ivySettingsFactory.create() >> ivySettings
-        1 * defaultResolver.setSettings(ivySettings)
-        _ * defaultResolver.getName() >> 'default'
-        0 * _._
-
-        assert settings.is(ivySettings)
-
-        assert settings.defaultResolver == defaultResolver
-        assert settings.resolvers.size() == 1
-    }
-
-    public void shouldReuseResolveSettings() {
-        given:
-        1 * ivySettingsFactory.create() >> ivySettings
-        _ * defaultResolver.getName() >> 'default'
-        IvySettings settings = converter.convertForResolve(defaultResolver)
-        settings.addResolver(testResolver)
-        settings.addResolver(testResolver2)
-
-        when:
-        settings = converter.convertForResolve(defaultResolver)
-
-        then:
-        assert settings.is(ivySettings)
-
-        assert settings.defaultResolver == defaultResolver
-        assert settings.resolvers.size() == 1
-    }
-
-    public void testConvertForPublish() {
-        when:
-        IvySettings settings = converter.convertForPublish([testResolver, testResolver2])
-
-        then:
-        settings.is(ivySettings)
-
-        and:
-        [testResolver, testResolver2].each {
-            it.settings == settings
-            it.repositoryCacheManager.settings == settings
-        }
-
-        and:
-        1 * ivySettingsFactory.create() >> ivySettings
-        0 * _._
-    }
-
-    public void reusesPublishSettings() {
-        when:
-        IvySettings settings = converter.convertForPublish([testResolver])
-
-        then:
-        settings.is(ivySettings)
-
-        and:
-        1 * ivySettingsFactory.create() >> ivySettings
-        0 * _._
-
-        when:
-        settings = converter.convertForPublish([testResolver, testResolver2])
-
-        then:
-        settings.is(ivySettings)
-
-        and:
-            [testResolver, testResolver2].each {
-            it.settings == settings
-            it.repositoryCacheManager.settings == settings
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
index 585e5c3..93ee3b5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
@@ -19,9 +19,6 @@ package org.gradle.api.internal.artifacts.ivyservice
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 5/11/12
- */
 class DefaultUnresolvedDependencySpec extends Specification {
 
     def "provides module details"() {
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 d587aa7..592cedc 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
@@ -13,13 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.artifacts.ivyservice;
-
+package org.gradle.api.internal.artifacts.ivyservice
 
+import org.gradle.api.artifacts.LenientConfiguration
 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.ModuleMetadataProcessor
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
@@ -28,39 +29,33 @@ import spock.lang.Specification
 
 import static org.junit.Assert.fail
 
-public class ErrorHandlingArtifactDependencyResolverTest extends Specification {
-
+class ErrorHandlingArtifactDependencyResolverTest extends Specification {
     private delegate = Mock(ArtifactDependencyResolver)
     private resolvedConfiguration = Mock(ResolvedConfiguration)
     private resolutionResult = Mock(ResolutionResult)
     private configuration = Mock(ConfigurationInternal.class, name: 'coolConf')
     private repositories = [Mock(ResolutionAwareRepository)]
+    private metadataProcessor = Stub(ModuleMetadataProcessor)
+    private results = new ResolverResults()
     private resolver = new ErrorHandlingArtifactDependencyResolver(delegate);
 
     void "delegates to backing service"() {
-        given:
-        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, resolutionResult)
-
         when:
-        ResolverResults outerResults = resolver.resolve(configuration, repositories);
-        outerResults.resolvedConfiguration.hasError()
-        outerResults.resolvedConfiguration.rethrowFailure()
-        outerResults.resolvedConfiguration.getFiles(Specs.satisfyAll())
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
-        1 * resolvedConfiguration.hasError()
-        1 * resolvedConfiguration.rethrowFailure()
-        1 * resolvedConfiguration.getFiles(Specs.satisfyAll())
-        outerResults.resolutionResult == resolutionResult
+        1 * delegate.resolve(configuration, repositories, metadataProcessor, results) >> {
+            results.resolved(resolvedConfiguration, resolutionResult)
+        }
     }
 
     void "wraps operations with the failure"() {
         given:
         def failure = new RuntimeException()
-        delegate.resolve(configuration, repositories) >> { throw failure }
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { throw failure }
 
         when:
-        ResolverResults results = resolver.resolve(configuration, repositories);
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
         results.resolvedConfiguration.hasError()
@@ -79,19 +74,66 @@ public class ErrorHandlingArtifactDependencyResolverTest extends Specification {
         resolvedConfiguration.rethrowFailure() >> { throw failure }
         resolvedConfiguration.getFiles(Specs.satisfyAll()) >> { throw failure }
         resolvedConfiguration.getFirstLevelModuleDependencies() >> { throw failure }
+        resolvedConfiguration.getFirstLevelModuleDependencies(_) >> { throw failure }
         resolvedConfiguration.getResolvedArtifacts() >> { throw failure }
+        resolvedConfiguration.getLenientConfiguration() >> { throw failure }
+
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { results.resolved(resolvedConfiguration, resolutionResult) }
+
+        when:
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
+
+        then:
+        def result = results.resolvedConfiguration
+        failsWith(failure)
+                .when { result.rethrowFailure() }
+                .when { result.getFiles(Specs.satisfyAll()) }
+                .when { result.firstLevelModuleDependencies }
+                .when { result.getFirstLevelModuleDependencies(Specs.satisfyAll()) }
+                .when { result.resolvedArtifacts }
+                .when { result.lenientConfiguration }
+    }
+
+    void "wraps exceptions thrown by resolved lenient configuration"() {
+        given:
+        def failure = new RuntimeException()
+        def lenientConfiguration = Stub(LenientConfiguration)
+
+        resolvedConfiguration.getLenientConfiguration() >> lenientConfiguration
+        lenientConfiguration.getFiles(_) >> { throw failure }
+        lenientConfiguration.getFirstLevelModuleDependencies(_) >> { throw failure }
+        lenientConfiguration.getArtifacts(_) >> { throw failure }
+        lenientConfiguration.getUnresolvedModuleDependencies() >> { throw failure }
+
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { results.resolved(resolvedConfiguration, resolutionResult) }
+
+        when:
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
+
+        then:
+        def result = results.resolvedConfiguration.lenientConfiguration
+        failsWith(failure)
+                .when { result.getFiles(Specs.satisfyAll()) }
+                .when { result.getFirstLevelModuleDependencies(Specs.satisfyAll()) }
+                .when { result.getArtifacts(Specs.satisfyAll()) }
+                .when { result.unresolvedModuleDependencies }
+    }
+
+    void "wraps exceptions thrown by resolution result"() {
+        given:
+        def failure = new RuntimeException()
+
+        resolutionResult.root >> { throw failure }
 
-        delegate.resolve(configuration, repositories) >> { new ResolverResults(resolvedConfiguration, resolutionResult) }
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { results.resolved(resolvedConfiguration, resolutionResult) }
 
         when:
-        ResolverResults results = resolver.resolve(configuration, repositories);
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
+        def result = results.resolutionResult
         failsWith(failure)
-                .when { results.resolvedConfiguration.rethrowFailure(); }
-                .when { results.resolvedConfiguration.getFiles(Specs.satisfyAll()); }
-                .when { results.resolvedConfiguration.getFirstLevelModuleDependencies(); }
-                .when { results.resolvedConfiguration.getResolvedArtifacts(); }
+                .when { result.root }
     }
 
     ExceptionFixture failsWith(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
deleted file mode 100644
index 272474e..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.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.ConfigurationInternal;
-import org.gradle.api.internal.artifacts.configurations.Configurations;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
-import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
-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 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 DependencyResolver resolver1 = context.mock(DependencyResolver.class);
-    private DependencyResolver resolver2 = context.mock(DependencyResolver.class);
-    private PublicationAwareRepository repo1 = repo(resolver1);
-    private PublicationAwareRepository repo2 = repo(resolver2);
-    final List<DependencyResolver> publishResolversDummy = WrapUtil.toList(resolver1, resolver2);
-    final List<PublicationAwareRepository> publishRepositoriesDummy = WrapUtil.toList(repo1, repo2);
-
-    @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 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(configuration).getResolutionStrategy();
-            will(returnValue(new DefaultResolutionStrategy()));
-            one(ivyDependencyPublisherMock).publish(expectedConfigurations,
-                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, ivyEventManagerDummy);
-        }});
-
-        ivyService.publish(publishRepositoriesDummy, configuration.getModule(), configuration.getHierarchy(), someDescriptorDestination);
-    }
-
-    private IvyBackedArtifactPublisher createIvyService() {
-        return new IvyBackedArtifactPublisher(
-                settingsConverterStub,
-                publishModuleDescriptorConverter,
-                ivyFactoryStub,
-                ivyDependencyPublisherMock);
-    }
-
-    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;
-    }
-
-    private PublicationAwareRepository repo(final DependencyResolver resolver) {
-        final PublicationAwareRepository repository = context.mock(PublicationAwareRepository.class);
-        context.checking(new Expectations() {{
-            one(repository).createPublisher();
-            will(returnValue(resolver));
-        }});
-        return repository;
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactoryTest.groovy
deleted file mode 100644
index b0cbbdc..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactoryTest.groovy
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice
-
-import org.gradle.api.artifacts.ArtifactRepositoryContainer
-import spock.lang.Specification
-
-class IvySettingsFactoryTest extends Specification {
-    final File cacheDir = new File('user-dir')
-    final ArtifactCacheMetaData cacheMetaData = Mock()
-    final IvySettingsFactory factory = new IvySettingsFactory(cacheMetaData)
-
-    def "creates and configures an IvySettings instance"() {
-        given:
-        _ * cacheMetaData.cacheDir >> cacheDir
-
-        when:
-        def settings = factory.create()
-
-        then:
-        settings.defaultCache == new File(cacheDir, 'ivy')
-        settings.defaultCacheArtifactPattern == ArtifactRepositoryContainer.DEFAULT_CACHE_ARTIFACT_PATTERN
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
index 6dbe063..57f9a9f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
@@ -18,15 +18,13 @@ package org.gradle.api.internal.artifacts.ivyservice
 
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.junit.Test
+
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 class IvyUtilTest {
     @Test public void testModuleRevisionId() {
         List l = ['myorg', 'myname', 'myrev']
-        ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance(*l)
+        ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(*l)
         assertEquals(l[0], moduleRevisionId.organisation)
         assertEquals(l[1], moduleRevisionId.name)
         assertEquals(l[2], moduleRevisionId.revision)
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
index 0208cb7..6ef3b64 100644
--- 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
@@ -16,17 +16,19 @@
 
 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.descriptor.*
+import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 import java.text.SimpleDateFormat
 
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleId
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleRevisionId
+
 class IvyXmlModuleDescriptorWriterTest extends Specification {
 
     private @Rule TestNameTestDirectoryProvider temporaryFolder;
@@ -35,33 +37,13 @@ class IvyXmlModuleDescriptorWriterTest extends Specification {
     private ModuleRevisionId resolvedModuleRevisionId = 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:
+        assertMockMethodInvocations()
         def dependency1 = mockDependencyDescriptor("Dep1")
         def dependency2 = mockDependencyDescriptor("Dep2")
-        2 * dependency2.force >> true
-        2 * dependency2.changing >> true
+        1 * dependency2.force >> true
+        1 * dependency2.changing >> true
         1 * md.dependencies >> [dependency1, dependency2]
         1 * dependency1.transitive >> true
         when:
@@ -81,6 +63,48 @@ class IvyXmlModuleDescriptorWriterTest extends Specification {
         assert ivyModule.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:Dep1:1.0", "org.test:Dep2:1.0"]
     }
 
+    private void assertMockMethodInvocations() {
+        1 * md.extraAttributesNamespaces >> [:]
+        1 * md.extraAttributes >> [buildNr: "815"]
+        1 * md.qualifiedExtraAttributes >> [buildNr: "815"]
+        1 * md.getExtraInfo() >> Collections.emptyMap()
+        1 * md.getLicenses() >> Collections.emptyList()
+        1 * 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()]
+        0 * md.allDependencyDescriptorMediators
+    }
+
+    def "does not evaluate dependency descriptor mediators"() {
+        given:
+        ModuleRevisionId moduleRevisionId = createModuleRevisionId('org.test', 'projectA', '2.0')
+        ModuleDescriptor moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance(moduleRevisionId)
+        ModuleId moduleId = createModuleId('org.test', 'projectA')
+        DependencyDescriptorMediator mediator = DefaultModuleDescriptor.newDefaultInstance(moduleRevisionId)
+        moduleDescriptor.addDependencyDescriptorMediator(moduleId, new ExactPatternMatcher(), mediator)
+        assert moduleDescriptor.allDependencyDescriptorMediators.allRules.size() == 1
+
+        when:
+        File ivyFile = temporaryFolder.file("test/ivy/ivy.xml")
+        ivyXmlModuleDescriptorWriter.write(moduleDescriptor, ivyFile)
+
+        then:
+        def ivyModule = new XmlSlurper().parse(ivyFile)
+        notThrown(UnsupportedOperationException)
+        assert ivyModule.info. at organisation == 'org.test'
+        assert ivyModule.info. at module == 'projectA'
+        assert ivyModule.info. at revision == '2.0'
+    }
+
     def date(String timestamp) {
         def format = new SimpleDateFormat("yyyyMMddHHmmss")
         format.parse(timestamp)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy
index 2de2301..6090360 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy
@@ -17,28 +17,26 @@ package org.gradle.api.internal.artifacts.ivyservice
 
 import spock.lang.Specification
 
-import static org.apache.ivy.core.module.id.ModuleRevisionId.newInstance
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class ModuleVersionNotFoundExceptionTest extends Specification {
     def "formats message to include selector"() {
-        def exception1 = new ModuleVersionNotFoundException(newInstance("org", "a", "1.2"))
-        def exception2 = new ModuleVersionNotFoundException(newId("org", "a", "1.2"))
-        def exception3 = new ModuleVersionNotFoundException(newInstance("org", "a", "1.2"), "nothing matches %s")
+        def exception1 = new ModuleVersionNotFoundException(newId("org", "a", "1.2"))
+        def exception2 = new ModuleVersionNotFoundException(newSelector("org", "a", "1.2"))
 
         expect:
         exception1.message == 'Could not find org:a:1.2.'
-        exception2.message == 'Could not find org:a:1.2.'
-        exception3.message == 'nothing matches org:a:1.2'
+        exception2.message == 'Could not find any version that matches org:a:1.2.'
     }
 
     def "can add incoming paths to exception"() {
-        def a = newInstance("org", "a", "1.2")
-        def b = newInstance("org", "b", "5")
-        def c = newInstance("org", "c", "1.0")
+        def a = newId("org", "a", "1.2")
+        def b = newId("org", "b", "5")
+        def c = newId("org", "c", "1.0")
 
-        def exception = new ModuleVersionNotFoundException(newInstance("a", "b", "c"))
+        def exception = new ModuleVersionNotFoundException(newId("a", "b", "c"))
         def onePath = exception.withIncomingPaths([[a, b, c]])
 
         expect:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
index 6cbeda6..eefb341 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
@@ -17,32 +17,29 @@ package org.gradle.api.internal.artifacts.ivyservice
 
 import spock.lang.Specification
 
-import static org.apache.ivy.core.module.id.ModuleRevisionId.newInstance
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class ModuleVersionResolveExceptionTest extends Specification {
     def "formats message to include selector"() {
-        def exception1 = new ModuleVersionResolveException(newInstance("org", "a", "1.2"), new RuntimeException())
+        def exception1 = new ModuleVersionResolveException(newSelector("org", "a", "1.2"), new RuntimeException())
         def exception2 = new ModuleVersionResolveException(newSelector("org", "a", "1.2+"), "%s is broken")
         def exception3 = new ModuleVersionResolveException(newId("org", "a", "1.2"), "%s is broken")
-        def exception4 = new ModuleVersionResolveException(newInstance("org", "a", "1.2"), "%s is broken")
 
         expect:
         exception1.message == 'Could not resolve org:a:1.2.'
         exception2.message == 'org:a:1.2+ is broken'
         exception3.message == 'org:a:1.2 is broken'
-        exception4.message == 'org:a:1.2 is broken'
     }
 
     def "can add incoming paths to exception"() {
-        def a = newInstance("org", "a", "1.2")
-        def b = newInstance("org", "b", "5")
-        def c = newInstance("org", "c", "1.0")
+        def a = newId("org", "a", "1.2")
+        def b = newId("org", "b", "5")
+        def c = newId("org", "c", "1.0")
 
         def cause = new RuntimeException()
-        def exception = new ModuleVersionResolveException(newInstance("a", "b", "c"), cause)
+        def exception = new ModuleVersionResolveException(newSelector("a", "b", "c"), cause)
         def onePath = exception.withIncomingPaths([[a, b, c]])
         def twoPaths = exception.withIncomingPaths([[a, b, c], [a, c]])
 
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
deleted file mode 100644
index 243015e..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice
-
-import org.apache.ivy.core.module.descriptor.Artifact
-import org.gradle.api.artifacts.ResolvedArtifact
-import org.gradle.api.artifacts.ResolvedDependency
-import org.gradle.internal.Factory
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
-import spock.lang.Specification
-
-class ResolvedArtifactFactoryTest extends Specification {
-    final CacheLockingManager lockingManager = Mock()
-    final ResolvedArtifactFactory factory = new ResolvedArtifactFactory(lockingManager)
-
-    def "creates an artifact backed by module resolve result"() {
-        Artifact artifact = Mock()
-        ArtifactResolver artifactResolver = Mock()
-        ResolvedDependency resolvedDependency = Mock()
-        File file = new File("something.jar")
-
-        given:
-        artifact.qualifiedExtraAttributes >> [:]
-
-        when:
-        ResolvedArtifact resolvedArtifact = factory.create(resolvedDependency, artifact, artifactResolver)
-
-        then:
-        resolvedArtifact instanceof DefaultResolvedArtifact
-
-        when:
-        resolvedArtifact.file
-
-        then:
-        1 * lockingManager.useCache(!null, !null) >> {String displayName, Factory<?> action ->
-            return action.create()
-        }
-        1 * artifactResolver.resolve(artifact, _) >> { args -> args[1].resolved(file) }
-        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
index 27a0654..0c2fbed 100644
--- 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
@@ -23,6 +23,7 @@ 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.ModuleMetadataProcessor
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
@@ -38,17 +39,18 @@ public class SelfResolvingDependencyResolverTest extends Specification {
     private configuration = Mock(ConfigurationInternal)
     private repositories = [Mock(ResolutionAwareRepository)]
     private dependencies = Mock(DependencySet)
-
+    private metadataProcessor = Stub(ModuleMetadataProcessor)
+    private results = new ResolverResults()
     private resolver = new SelfResolvingDependencyResolver(delegate);
 
     void "returns correct resolved configuration"() {
         given:
-        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { results.resolved(resolvedConfiguration, Mock(ResolutionResult)) }
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> true
 
         when:
-        def results = resolver.resolve(configuration, repositories)
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
         def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
@@ -60,12 +62,12 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "uses configuration transitive setting"() {
         given:
-        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { results.resolved(resolvedConfiguration, Mock(ResolutionResult)) }
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> false
 
         when:
-        def results = resolver.resolve(configuration, repositories)
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
         def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
@@ -74,12 +76,12 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "delegates to provided resolved configuration"() {
         given:
-        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        delegate.resolve(configuration, repositories, metadataProcessor, results) >> { results.resolved(resolvedConfiguration, Mock(ResolutionResult)) }
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> true
 
         when:
-        def results = resolver.resolve(configuration, repositories)
+        resolver.resolve(configuration, repositories, metadataProcessor, results)
         results.resolvedConfiguration.getFirstLevelModuleDependencies(Specs.satisfyAll())
         results.resolvedConfiguration.getResolvedArtifacts()
         results.resolvedConfiguration.hasError()
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
index c6e5ac1..95e1fde 100644
--- 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
@@ -13,15 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.artifacts.ivyservice;
-
+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.ModuleMetadataProcessor
 import org.gradle.api.internal.artifacts.ResolverResults
+import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
 import org.gradle.api.specs.Specs
@@ -29,44 +28,50 @@ import spock.lang.Specification
 
 class ShortcircuitEmptyConfigsArtifactDependencyResolverSpec extends Specification {
 
-    final ArtifactDependencyResolver delegate = Mock()
-    final ConfigurationInternal configuration = Mock()
-    final List<ResolutionAwareRepository> repositories = [Mock(ResolutionAwareRepository)]
-    final DependencySet dependencies = Mock()
-
-    final ShortcircuitEmptyConfigsArtifactDependencyResolver dependencyResolver = new ShortcircuitEmptyConfigsArtifactDependencyResolver(delegate);
+    def delegate = Mock(ArtifactDependencyResolver)
+    def configuration = Stub(ConfigurationInternal)
+    def repositories = [Stub(ResolutionAwareRepository)]
+    def metadataProcessor = Stub(ModuleMetadataProcessor)
+    def dependencies = Stub(DependencySet)
+    def componentIdentifierFactory = Mock(ComponentIdentifierFactory)
+    def results = new ResolverResults()
+    def dependencyResolver = new ShortcircuitEmptyConfigsArtifactDependencyResolver(delegate, componentIdentifierFactory);
 
-    def "returns empty config when no dependencies"() {
+    def "returns empty result when no dependencies"() {
         given:
         dependencies.isEmpty() >> true
         configuration.getAllDependencies() >> dependencies
-        configuration.getModule() >> new DefaultModule("org", "foo", "1.0")
 
         when:
-        ResolverResults results = dependencyResolver.resolve(configuration, repositories);
-        ResolvedConfiguration resolvedConfig = results.resolvedConfiguration
+        dependencyResolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
+        def resolvedConfig = results.resolvedConfiguration
         !resolvedConfig.hasError()
         resolvedConfig.rethrowFailure();
 
-        resolvedConfig.getFiles(Specs.<Dependency>satisfyAll()).isEmpty()
+        resolvedConfig.getFiles(Specs.<Dependency> satisfyAll()).isEmpty()
         resolvedConfig.getFirstLevelModuleDependencies().isEmpty()
         resolvedConfig.getResolvedArtifacts().isEmpty()
+
+        and:
+        def result = results.resolutionResult
+        result.allComponents.size() == 1
+        result.allDependencies.empty
+
+        and:
+        0 * delegate._
     }
 
-    def "delegates to backing service"() {
+    def "delegates to backing service when there are one or more dependencies"() {
         given:
-        def resultsDummy = Mock(ResolverResults)
-
         dependencies.isEmpty() >> false
         configuration.getAllDependencies() >> dependencies
-        delegate.resolve(configuration, repositories) >> resultsDummy
 
         when:
-        def out = dependencyResolver.resolve(configuration, repositories)
+        dependencyResolver.resolve(configuration, repositories, metadataProcessor, results)
 
         then:
-        out == resultsDummy
+        1 * delegate.resolve(configuration, repositories, metadataProcessor, results)
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
index 5a72899..5b06b2f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
@@ -15,18 +15,18 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice
 
-import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.Action
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
 import spock.lang.Specification
 
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleRevisionId
+
 class VersionForcingDependencyToModuleResolverSpec extends Specification {
     final target = Mock(DependencyToModuleVersionIdResolver)
     final resolvedVersion = Mock(ModuleVersionIdResolveResult)
-    final forced = new ModuleRevisionId(new ModuleId('group', 'module'), 'forced')
+    final forced = createModuleRevisionId('group', 'module', 'forced')
 
     def "passes through dependency when it does not match any rule"() {
         def dep = dependency('org', 'module', '1.0')
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 dd8b5df..1b72673 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,43 +17,44 @@
 package org.gradle.api.internal.artifacts.ivyservice.clientmodule
 
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
-import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
+import org.gradle.api.internal.artifacts.ivyservice.BuildableComponentResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
-/**
- * @author Hans Dockter
- */
 class ClientModuleResolverTest extends Specification {
-    final ModuleDescriptor module = Mock()
-    final ModuleRevisionId moduleId = new ModuleRevisionId(new ModuleId("org", "name"), "1.0")
-    final DependencyToModuleResolver target = Mock()
+    final ModuleSource moduleSource = Mock()
+    final ModuleVersionMetaData moduleMetaData = Mock()
+    final DependencyToModuleVersionResolver target = Mock()
     final ClientModuleResolver resolver = new ClientModuleResolver(target)
 
     def "replaces meta-data for a client module dependency"() {
         ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
         DependencyMetaData dependencyMetaData = Mock()
-        BuildableModuleVersionResolveResult result = Mock()
+        BuildableComponentResolveResult result = Mock()
+        ModuleVersionMetaData resolvedMetaData = Mock()
 
         given:
+
         _ * dependencyMetaData.descriptor >> dependencyDescriptor
-        _ * dependencyDescriptor.targetModule >> module
-        _ * module.moduleRevisionId >> moduleId
+        _ * dependencyDescriptor.getTargetModule() >> moduleMetaData
 
         when:
         resolver.resolve(dependencyMetaData, result)
 
         then:
         1 * target.resolve(dependencyMetaData, result)
-        1 * result.setMetaData(module)
+        1 * result.getMetaData() >> resolvedMetaData
+        1 * resolvedMetaData.getSource() >> moduleSource
+        1 * dependencyDescriptor.getTargetModule() >> moduleMetaData
+        1 * moduleMetaData.withSource(moduleSource) >> moduleMetaData
+        1 * result.setMetaData(moduleMetaData)
         _ * result.failure >> null
         0 * result._
     }
@@ -61,7 +62,7 @@ class ClientModuleResolverTest extends Specification {
     def "does not replace meta-data for unknown module version"() {
         DependencyDescriptor dependencyDescriptor = Mock()
         DependencyMetaData dependencyMetaData = Mock()
-        BuildableModuleVersionResolveResult result = Mock()
+        BuildableComponentResolveResult result = Mock()
 
         given:
         _ * dependencyMetaData.descriptor >> dependencyDescriptor
@@ -78,7 +79,7 @@ class ClientModuleResolverTest extends Specification {
     def "does not replace meta-data for broken module version"() {
         ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
         DependencyMetaData dependencyMetaData = Mock()
-        BuildableModuleVersionResolveResult result = Mock()
+        BuildableComponentResolveResult result = Mock()
 
         given:
         _ * dependencyMetaData.descriptor >> dependencyDescriptor
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 81d2d7a..d6b9591 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
@@ -15,80 +15,87 @@
  */
 
 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.Transformer
+import org.gradle.api.internal.artifacts.ModuleMetadataProcessor
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext
 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 org.gradle.api.internal.artifacts.ivyservice.DefaultBuildableArtifactSetResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleVersionsCache
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleArtifactsCache
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetaDataCache
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
 import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex
 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 org.gradle.util.BuildCommencedTimeProvider
 import spock.lang.Specification
 import spock.lang.Unroll
 
 class CachingModuleVersionRepositoryTest extends Specification {
-
-    ModuleVersionRepository realRepo = Mock()
-    ModuleResolutionCache moduleResolutionCache = Mock()
-    ModuleDescriptorCache moduleDescriptorCache = Mock()
-    CachedArtifactIndex artifactAtRepositoryCache = Mock()
-    CachePolicy cachePolicy = Mock()
-    CachingModuleVersionRepository repo = new CachingModuleVersionRepository(realRepo, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCache, cachePolicy, new TrueTimeProvider())
-    ModuleRevisionId moduleRevisionId = Mock()
-    int descriptorHash = 1234
-    CachingModuleVersionRepository.CachingModuleSource moduleSource = Mock()
+    def realRepo = Stub(LocalArtifactsModuleVersionRepository) {
+        getId() >> "repo-id"
+    }
+    def moduleResolutionCache = Stub(ModuleVersionsCache)
+    def moduleDescriptorCache = Mock(ModuleMetaDataCache)
+    def moduleArtifactsCache = Mock(ModuleArtifactsCache)
+    def artifactAtRepositoryCache = Mock(CachedArtifactIndex)
+    def cachePolicy = Stub(CachePolicy)
+    def metadataProcessor = Stub(ModuleMetadataProcessor)
+    def moduleExtractor = Mock(Transformer)
+    def repo = new CachingModuleVersionRepository(realRepo, moduleResolutionCache, moduleDescriptorCache, moduleArtifactsCache, artifactAtRepositoryCache,
+            cachePolicy, new BuildCommencedTimeProvider(), metadataProcessor, moduleExtractor)
 
     @Unroll
-    "last modified date is cached - lastModified = #lastModified"(Date lastModified) {
+    def "last modified date is cached - lastModified = #lastModified"() {
         given:
-        ExternalResourceMetaData externalResourceMetaData = new DefaultExternalResourceMetaData("remote url", lastModified, -1, null, null)
-        File file = new File("local")
-        BuildableArtifactResolveResult result = Mock()
-        Artifact artifact = Mock()
-        ArtifactRevisionId id = arid()
-        ArtifactAtRepositoryKey atRepositoryKey = new ArtifactAtRepositoryKey(realRepo, id)
+        def artifactId = Stub(ModuleVersionArtifactIdentifier)
+        def artifact = Stub(ModuleVersionArtifactMetaData) {
+            getId() >> artifactId
+        }
+
+        def file = new File("local")
+        def result = Stub(BuildableArtifactResolveResult) {
+            getFile() >> file
+            getFailure() >> null
+        }
+
+        def descriptorHash = 1234G
+        def moduleSource = Stub(CachingModuleVersionRepository.CachingModuleSource) {
+            getDescriptorHash() >> descriptorHash
+        }
 
-        and:
-        _ * artifact.getModuleRevisionId() >> moduleRevisionId;
-        _ * realRepo.isLocal() >> false
-        _ * moduleSource.descriptorHash >> descriptorHash
-        _ * moduleSource.isChangingModule >> true
-        _ * artifactAtRepositoryCache.lookup(atRepositoryKey) >> null
-        _ * realRepo.resolve(artifact, result, null)
-        _ * result.file >> file
-        _ * result.externalResourceMetaData >> externalResourceMetaData
-        _ * artifact.getId() >> id
+        ArtifactAtRepositoryKey atRepositoryKey = new ArtifactAtRepositoryKey("repo-id", artifactId)
 
         when:
-        repo.resolve(artifact, result, moduleSource)
+        repo.resolveArtifact(artifact, moduleSource, result)
 
         then:
         1 * artifactAtRepositoryCache.store(atRepositoryKey, file, descriptorHash)
         0 * moduleDescriptorCache._
+
         where:
         lastModified << [new Date(), null]
     }
 
-    ArtifactRevisionId arid(Map attrs = [:]) {
-        Map defaults = [
-                org: "org", name: "name", revision: "1.0",
-                type: "type", ext: "ext"
-        ]
+    def "does not use cache when artifact set can be determined locally"() {
+        def component = Mock(ComponentMetaData)
+        def source = Mock(ModuleSource)
+        def cachingSource = new CachingModuleVersionRepository.CachingModuleSource(BigInteger.ONE, false, source)
+        def context = Mock(ArtifactResolveContext)
+        def result = new DefaultBuildableArtifactSetResolveResult()
 
-        attrs = defaults + attrs
+        when:
+        repo.resolveModuleArtifacts(component, context, result)
 
-        ModuleId mid = new ModuleId(attrs.org, attrs.name)
-        new ArtifactRevisionId(
-                new ArtifactId(mid, mid.name, attrs.type, attrs.ext),
-                new ModuleRevisionId(mid, attrs.revision)
-        )
+        then:
+        1 * component.getSource() >> cachingSource
+        1 * component.withSource(source) >> component
+        realRepo.localResolveModuleArtifacts(component, context, result) >> {
+            result.resolved([Mock(ComponentArtifactMetaData)])
+        }
+        0 * _
     }
-
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataResolveResultTest.groovy
new file mode 100644
index 0000000..1388a95
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataResolveResultTest.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.ivyservice.ivyresolve
+
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class DefaultBuildableModuleVersionMetaDataResolveResultTest extends Specification {
+    def descriptor = new DefaultBuildableModuleVersionMetaDataResolveResult()
+    def moduleSource = Stub(ModuleSource)
+
+    def "has unknown state by default"() {
+        expect:
+        descriptor.state == BuildableModuleVersionMetaDataResolveResult.State.Unknown
+    }
+
+    def "can mark as missing"() {
+        when:
+        descriptor.missing()
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaDataResolveResult.State.Missing
+        descriptor.failure == null
+    }
+
+    def "can mark as probably missing"() {
+        when:
+        descriptor.probablyMissing()
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaDataResolveResult.State.ProbablyMissing
+        descriptor.failure == null
+    }
+
+    def "can mark as failed"() {
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+
+        when:
+        descriptor.failed(failure)
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaDataResolveResult.State.Failed
+        descriptor.failure == failure
+    }
+
+    def "can mark as resolved using meta-data"() {
+        def metaData = Stub(MutableModuleVersionMetaData)
+
+        when:
+        descriptor.resolved(metaData, moduleSource)
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaDataResolveResult.State.Resolved
+        descriptor.failure == null
+        descriptor.metaData == metaData
+        descriptor.moduleSource == moduleSource
+    }
+
+    def "cannot get failure when not resolved"() {
+        when:
+        descriptor.failure
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get meta-data when not resolved"() {
+        when:
+        descriptor.metaData
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get meta-data when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.metaData
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get module source when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot set module source when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.setModuleSource(Mock(ModuleSource))
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get module source when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot set module source when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.setModuleSource(Mock(ModuleSource))
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get module source when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot set module source when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.setModuleSource(Mock(ModuleSource))
+
+        then:
+        thrown(IllegalStateException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataTest.groovy
deleted file mode 100644
index df18c01..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataTest.groovy
+++ /dev/null
@@ -1,244 +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.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
-import spock.lang.Specification
-
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-
-class DefaultBuildableModuleVersionMetaDataTest extends Specification {
-    final DefaultBuildableModuleVersionMetaData descriptor = new DefaultBuildableModuleVersionMetaData()
-    ModuleSource moduleSource = Mock()
-
-    def "has unknown state by default"() {
-        expect:
-        descriptor.state == BuildableModuleVersionMetaData.State.Unknown
-    }
-
-    def "can mark as missing"() {
-        when:
-        descriptor.missing()
-
-        then:
-        descriptor.state == BuildableModuleVersionMetaData.State.Missing
-        descriptor.failure == null
-    }
-
-    def "can mark as probably missing"() {
-        when:
-        descriptor.probablyMissing()
-
-        then:
-        descriptor.state == BuildableModuleVersionMetaData.State.ProbablyMissing
-        descriptor.failure == null
-    }
-
-    def "can mark as failed"() {
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-
-        when:
-        descriptor.failed(failure)
-
-        then:
-        descriptor.state == BuildableModuleVersionMetaData.State.Failed
-        descriptor.failure == failure
-    }
-
-    def "can mark as resolved"() {
-        def id = Mock(ModuleVersionIdentifier)
-        def moduleDescriptor = Mock(ModuleDescriptor)
-
-        when:
-        descriptor.resolved(id, moduleDescriptor, true, moduleSource)
-
-        then:
-        descriptor.state == BuildableModuleVersionMetaData.State.Resolved
-        descriptor.failure == null
-        descriptor.id == id
-        descriptor.descriptor == moduleDescriptor
-        descriptor.changing
-        descriptor.moduleSource == moduleSource
-    }
-
-    def "builds and caches the dependency meta-data from the module descriptor"() {
-        def id = Mock(ModuleVersionIdentifier)
-        def moduleDescriptor = Mock(ModuleDescriptor)
-        def dependency1 = new DefaultDependencyDescriptor(ModuleRevisionId.newInstance("org", "module", "1.2"), false)
-        def dependency2 = new DefaultDependencyDescriptor(ModuleRevisionId.newInstance("org", "module", "1.2"), false)
-
-        given:
-        moduleDescriptor.dependencies >> ([dependency1, dependency2] as DependencyDescriptor[])
-
-        and:
-        descriptor.resolved(id, moduleDescriptor, true, moduleSource)
-
-        when:
-        def deps = descriptor.dependencies
-
-        then:
-        deps.size() == 2
-        deps[0].descriptor == dependency1
-        deps[1].descriptor == dependency2
-
-        and:
-        descriptor.dependencies.is(deps)
-    }
-
-    def "can replace the dependencies for the module version"() {
-        def id = Mock(ModuleVersionIdentifier)
-        def moduleDescriptor = Mock(ModuleDescriptor)
-        def dependency1 = Mock(DependencyMetaData)
-        def dependency2 = Mock(DependencyMetaData)
-
-        given:
-        descriptor.resolved(id, moduleDescriptor, true, moduleSource)
-
-        when:
-        descriptor.dependencies = [dependency1, dependency2]
-
-        then:
-        descriptor.dependencies == [dependency1, dependency2]
-
-        and:
-        0 * moduleDescriptor._
-    }
-
-    def "cannot get descriptor when not resolved"() {
-        when:
-        descriptor.descriptor
-
-        then:
-        thrown(IllegalStateException)
-
-        when:
-        descriptor.failure
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get descriptor when failed"() {
-        given:
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-        descriptor.failed(failure)
-
-        when:
-        descriptor.descriptor
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot get descriptor when missing"() {
-        given:
-        descriptor.missing()
-
-        when:
-        descriptor.descriptor
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get descriptor when probably missing"() {
-        given:
-        descriptor.probablyMissing()
-
-        when:
-        descriptor.descriptor
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get module source when failed"() {
-        given:
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-        descriptor.failed(failure)
-
-        when:
-        descriptor.getModuleSource()
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot set module source when failed"() {
-        given:
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-        descriptor.failed(failure)
-
-        when:
-        descriptor.setModuleSource(Mock(ModuleSource))
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot get module source when missing"() {
-        given:
-        descriptor.missing()
-
-        when:
-        descriptor.getModuleSource()
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot set module source when missing"() {
-        given:
-        descriptor.missing()
-
-        when:
-        descriptor.setModuleSource(Mock(ModuleSource))
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get module source when probably missing"() {
-        given:
-        descriptor.probablyMissing()
-
-        when:
-        descriptor.getModuleSource()
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot set module source when probably missing"() {
-        given:
-        descriptor.probablyMissing()
-
-        when:
-        descriptor.setModuleSource(Mock(ModuleSource))
-
-        then:
-        thrown(IllegalStateException)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaDataTest.groovy
deleted file mode 100644
index 183e345..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaDataTest.groovy
+++ /dev/null
@@ -1,77 +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.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import spock.lang.Specification
-
-class DefaultDependencyMetaDataTest extends Specification {
-    final requestedModuleId = ModuleRevisionId.newInstance("org", "module", "1.2+")
-
-    def "constructs selector from descriptor"() {
-        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
-        def metaData = new DefaultDependencyMetaData(descriptor)
-
-        expect:
-        metaData.requested == DefaultModuleVersionSelector.newSelector("org", "module", "1.2+")
-    }
-
-    def "creates a copy with new requested version"() {
-        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
-        def metaData = new DefaultDependencyMetaData(descriptor)
-
-        given:
-
-        when:
-        def copy = metaData.withRequestedVersion("1.3+")
-
-        then:
-        copy.requested == DefaultModuleVersionSelector.newSelector("org", "module", "1.3+")
-        copy.descriptor.dependencyRevisionId == ModuleRevisionId.newInstance("org", "module", "1.3+")
-    }
-
-    def "returns this if new requested version is the same as current requested version"() {
-        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
-        def metaData = new DefaultDependencyMetaData(descriptor)
-
-        expect:
-        metaData.withRequestedVersion("1.2+").is(metaData)
-        metaData.withRequestedVersion(DefaultModuleVersionSelector.newSelector("org", "module", "1.2+")).is(metaData)
-    }
-
-    def "can set changing flag"() {
-        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, false)
-        def metaData = new DefaultDependencyMetaData(descriptor)
-
-        when:
-        def copy = metaData.withChanging()
-
-        then:
-        copy.descriptor.dependencyRevisionId == requestedModuleId
-        copy.descriptor.changing
-    }
-
-    def "returns this when changing is already true"() {
-        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, true)
-        def metaData = new DefaultDependencyMetaData(descriptor)
-
-        expect:
-        metaData.withChanging().is(metaData)
-    }
-}
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 00268c2..952294e 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
@@ -21,15 +21,6 @@ import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceR
 import spock.lang.Specification
 
 public class DependencyResolverIdentifierTest extends Specification {
-    def "uses dependency resolver name"() {
-        given:
-        DependencyResolver resolver = Mock()
-        resolver.name >> "resolver-name"
-
-        expect:
-        new DependencyResolverIdentifier(resolver).name == "resolver-name"
-    }
-
     def "dependency resolvers of unknown type are identified by their name"() {
         given:
         DependencyResolver resolver1 = Mock()
@@ -123,6 +114,10 @@ public class DependencyResolverIdentifierTest extends Specification {
     }
 
     def id(DependencyResolver resolver) {
-        return new DependencyResolverIdentifier(resolver).uniqueId
+        return DependencyResolverIdentifier.forIvyResolver(resolver)
+    }
+
+    def id(ExternalResourceResolver resolver) {
+        return DependencyResolverIdentifier.forExternalResourceResolver(resolver)
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingArtifactResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingArtifactResolverTest.groovy
new file mode 100644
index 0000000..3cc195b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingArtifactResolverTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.artifacts.component.ComponentIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.ComponentArtifactMetaData
+import org.gradle.api.internal.artifacts.metadata.ComponentMetaData
+import spock.lang.Specification
+
+class ErrorHandlingArtifactResolverTest extends Specification {
+    def delegate = Mock(ArtifactResolver)
+    def artifactResolver = new ErrorHandlingArtifactResolver(delegate)
+    def artifactSetResolveResult = Mock(BuildableArtifactSetResolveResult)
+
+    def "wraps resolveArtifact exception as failure"() {
+        def componentArtifactId = Stub(ComponentArtifactIdentifier) {
+            getDisplayName() >> "component-artifact"
+        }
+        def componentArtifact = Stub(ComponentArtifactMetaData) {
+            getId() >> componentArtifactId
+        }
+        def moduleSource = Mock(ModuleSource)
+        def artifactResolveResult = Mock(BuildableArtifactResolveResult)
+        def failure = new RuntimeException("foo")
+
+        when:
+        delegate.resolveArtifact(componentArtifact, moduleSource, artifactResolveResult) >> { throw failure }
+
+        and:
+        artifactResolver.resolveArtifact(componentArtifact, moduleSource, artifactResolveResult)
+
+        then:
+        1 * artifactResolveResult.failed(_ as ArtifactResolveException) >> { ArtifactResolveException e ->
+            assert e.message == "Could not download artifact 'component-artifact'"
+            assert e.cause == failure
+        }
+    }
+
+    def "wraps resolveModuleArtifacts exception as failure"() {
+        def componentId = Stub(ComponentIdentifier) {
+            getDisplayName() >> "component"
+        }
+        def component = Stub(ComponentMetaData) {
+            getComponentId() >> componentId
+        }
+        def context = Stub(ArtifactResolveContext) {
+            getId() >> "artifact-context"
+        }
+        def result = Mock(BuildableArtifactSetResolveResult)
+        def failure = new RuntimeException("foo")
+
+        when:
+        delegate.resolveModuleArtifacts(component, context, result) >> { throw failure }
+
+        and:
+        artifactResolver.resolveModuleArtifacts(component, context, result)
+
+        then:
+        1 * result.failed(_ as ArtifactResolveException) >> { ArtifactResolveException e ->
+            assert e.message == "Could not determine artifacts for component 'component'"
+            assert e.cause == failure
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy
index 2da5d0a..3433bd7 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy
@@ -17,13 +17,16 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
 import spock.lang.Specification
 
 class IvyDynamicResolveModuleVersionRepositoryTest extends Specification {
     final target = Mock(LocalAwareModuleVersionRepository)
+    final metaData = Mock(MutableModuleVersionMetaData)
     final requestedDependency = Mock(DependencyMetaData)
-    final result = Mock(BuildableModuleVersionMetaData)
+    final result = Mock(BuildableModuleVersionMetaDataResolveResult)
     final repository = new IvyDynamicResolveModuleVersionRepository(target)
 
     def "replaces each dependency version with revConstraint"() {
@@ -31,7 +34,8 @@ class IvyDynamicResolveModuleVersionRepositoryTest extends Specification {
         def transformed = dependency()
 
         given:
-        result.state >> BuildableModuleVersionMetaData.State.Resolved
+        result.state >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+        result.metaData >> metaData
 
         when:
         repository.getLocalDependency(requestedDependency, result)
@@ -40,9 +44,9 @@ class IvyDynamicResolveModuleVersionRepositoryTest extends Specification {
         1 * target.getLocalDependency(requestedDependency, result)
 
         and:
-        1 * result.dependencies >> [original]
+        1 * metaData.dependencies >> [original]
         1 * original.withRequestedVersion('1.2+') >> transformed
-        1 * result.setDependencies([transformed])
+        1 * metaData.setDependencies([transformed])
     }
 
     def "does nothing when dependency has not been resolved"() {
@@ -51,14 +55,14 @@ class IvyDynamicResolveModuleVersionRepositoryTest extends Specification {
 
         then:
         1 * target.getLocalDependency(requestedDependency, result)
-        _ * result.state >> BuildableModuleVersionMetaData.State.Missing
+        _ * result.state >> BuildableModuleVersionMetaDataResolveResult.State.Missing
         0 * result._
     }
 
     def dependency(String revConstraint = '1.0') {
         def dep = Mock(DependencyMetaData)
         def descriptor = Mock(DependencyDescriptor)
-        _ * descriptor.dynamicConstraintDependencyRevisionId >> ModuleRevisionId.newInstance('org', 'module', revConstraint)
+        _ * descriptor.dynamicConstraintDependencyRevisionId >> IvyUtil.createModuleRevisionId('org', 'module', revConstraint)
         _ * dep.descriptor >> descriptor
         return dep
     }
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 8828189..e768248 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
@@ -14,38 +14,39 @@
  * limitations under the License.
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
-
 import org.apache.ivy.core.module.descriptor.Artifact
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.plugins.version.VersionMatcher
 import org.gradle.api.artifacts.ModuleVersionSelector
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import org.gradle.api.internal.artifacts.ivyservice.*
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleDescriptorAdapter
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
 import spock.lang.Specification
 
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
 class LazyDependencyToModuleResolverTest extends Specification {
-    final DependencyToModuleResolver target = Mock()
-    final VersionMatcher matcher = Mock()
+    final target = Mock(DependencyToModuleVersionResolver)
+    final matcher = Mock(VersionMatcher)
     final LazyDependencyToModuleResolver resolver = new LazyDependencyToModuleResolver(target, matcher)
 
     def "does not resolve module for static version dependency until requested"() {
         def dependency = dependency()
-        def module = module()
+        def metaData = module()
 
         when:
         def idResolveResult = resolver.resolve(dependency)
 
         then:
-        idResolveResult.id.group == module.moduleRevisionId.organisation
-        idResolveResult.id.name == module.moduleRevisionId.name
-        idResolveResult.id.version == module.moduleRevisionId.revision
+        idResolveResult.id == metaData.id
 
         and:
         0 * target._
@@ -54,20 +55,18 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def moduleResolveResult = idResolveResult.resolve()
 
         then:
-        moduleResolveResult.id.group == module.moduleRevisionId.organisation
-        moduleResolveResult.id.name == module.moduleRevisionId.name
-        moduleResolveResult.id.version == module.moduleRevisionId.revision
+        moduleResolveResult.id == metaData.id
 
-        moduleResolveResult.metaData.id == moduleResolveResult.id
-        moduleResolveResult.metaData.descriptor == module
+        moduleResolveResult.metaData == metaData
 
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, Mock(ArtifactResolver))}
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(metaData)}
         0 * target._
     }
 
     def "resolves module for dynamic version dependency immediately"() {
         def dependency = dependency()
-        def module = module()
+        def metaData = module()
+
         given:
         matcher.isDynamic(_) >> true
 
@@ -76,18 +75,20 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def id = idResolveResult.id
 
         then:
-        id.group == module.moduleRevisionId.organisation
-        id.name == module.moduleRevisionId.name
-        id.version == module.moduleRevisionId.revision
+        id == metaData.id
 
         and:
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, Mock(ArtifactResolver))}
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(metaData)}
         0 * target._
 
         when:
-        idResolveResult.resolve()
+        def moduleResolveResult = idResolveResult.resolve()
 
         then:
+        moduleResolveResult.id == metaData.id
+        moduleResolveResult.metaData == metaData
+
+        and:
         0 * target._
     }
 
@@ -212,64 +213,14 @@ class LazyDependencyToModuleResolverTest extends Specification {
         e.is(idResolveResult.failure)
     }
 
-    def "can resolve artifact for a module version"() {
-        def dependency = dependency()
-        def module = module()
-        def artifact = artifact()
-        ArtifactResolver targetResolver = Mock()
-        BuildableArtifactResolveResult result = Mock()
-
-        when:
-        def resolveResult = resolver.resolve(dependency).resolve()
-
-        then:
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, targetResolver)}
-
-        when:
-        resolveResult.artifactResolver.resolve(artifact, result)
-
-        then:
-        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, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, targetResolver)}
-
-        when:
-        resolveResult.artifactResolver.resolve(artifact, result)
-
-        then:
-        1 * result.failed(_) >> { ArtifactResolveException e ->
-            assert e.message == "Could not download artifact 'group:module:1.0 at zip'"
-            assert e.cause == failure
-        }
-
-        and:
-        _ * targetResolver.resolve(artifact, result) >> { throw failure }
-    }
-
     def module() {
-        ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
-        return new DefaultModuleDescriptor(id, "release", new Date())
+        ModuleRevisionId id = IvyUtil.createModuleRevisionId("group", "module", "1.0")
+        return new ModuleDescriptorAdapter(new DefaultModuleDescriptor(id, "release", new Date()))
     }
 
     def dependency() {
         DependencyDescriptor descriptor = Mock()
-        ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
+        ModuleRevisionId id = IvyUtil.createModuleRevisionId("group", "module", "1.0")
         _ * descriptor.dependencyRevisionId >> id
         DependencyMetaData metaData = Mock()
         _ * metaData.descriptor >> descriptor
@@ -280,10 +231,14 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
     def artifact() {
         Artifact artifact = Mock()
-        ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
-        _ * artifact.moduleRevisionId >> id
+        _ * artifact.moduleRevisionId >> IvyUtil.createModuleRevisionId("group", "module", "1.0")
         _ * artifact.name >> 'artifact'
         _ * artifact.ext >> 'zip'
-        return artifact
+        return Stub(ModuleVersionArtifactMetaData) {
+            getArtifact() >> artifact
+            getId() >> Stub(ModuleVersionArtifactIdentifier) {
+                getDisplayName() >> "artifact.zip"
+            }
+        }
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolverTest.groovy
new file mode 100644
index 0000000..b4aa7d3
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolverTest.groovy
@@ -0,0 +1,639 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.module.descriptor.ModuleDescriptor
+import org.gradle.api.Transformer
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.BuildableComponentResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestStrategy
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
+import spock.lang.Ignore
+import spock.lang.Specification
+
+class RepositoryChainDependencyResolverTest extends Specification {
+    final metaData = metaData("1.2")
+    final dependencyId = Stub(ModuleVersionSelector)
+    final dependency = Stub(DependencyMetaData)
+    final dependencyDescriptor = Stub(DependencyDescriptor)
+    final matcher = Stub(VersionMatcher)
+    final latestStrategy = Stub(LatestStrategy) {
+        compare(_, _) >> { a, b ->
+            a.version.compareTo(b.version)
+        }
+    }
+    final Transformer<ModuleVersionMetaData, RepositoryChainModuleResolution> transformer = Mock(Transformer)
+    final result = Mock(BuildableComponentResolveResult)
+    final moduleSource = Mock(ModuleSource)
+
+    final RepositoryChainDependencyResolver resolver = new RepositoryChainDependencyResolver(matcher, latestStrategy, transformer)
+
+    ModuleVersionIdentifier moduleVersionIdentifier(ModuleDescriptor moduleDescriptor) {
+        def moduleRevId = moduleDescriptor.moduleRevisionId
+        new DefaultModuleVersionIdentifier(moduleRevId.organisation, moduleRevId.name, moduleRevId.revision)
+    }
+
+    def setup() {
+        _ * dependencyId.group >> "group"
+        _ * dependencyId.name >> "project"
+        _ * dependencyId.version >> "1.0"
+        _ * dependency.requested >> dependencyId
+        _ * dependency.descriptor >> dependencyDescriptor
+    }
+
+    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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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._
+    }
+
+    // TODO:DAZ Add more tests for dynamic versions and fix this
+    @Ignore
+    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 = metaData("1.2")
+        resolver.add(repo1)
+        resolver.add(repo2)
+        resolver.add(repo3)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(metaData("1.1"), null)
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(version2, moduleSource)
+        }
+        1 * repo3.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(metaData("1.0"), null)
+        }
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData == version2
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo1
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo1
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(metaData, moduleSource)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.moduleSource == moduleSource
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleVersionMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        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(String version) {
+        def descriptor = Stub(ModuleDescriptor)
+        descriptor.resolvedModuleRevisionId >> IvyUtil.createModuleRevisionId("org", "module", version)
+        return descriptor
+    }
+
+    def metaData(String version) {
+        return Stub(MutableModuleVersionMetaData) {
+            toString() >> version
+            getId() >> DefaultModuleVersionIdentifier.newId("org", "module", version)
+            getDescriptor() >> descriptor(version)
+        }
+    }
+}
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
deleted file mode 100644
index 2afc920..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
+++ /dev/null
@@ -1,588 +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.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.LatestRevisionStrategy
-import org.apache.ivy.plugins.resolver.ResolverSettings
-import org.apache.ivy.plugins.version.VersionMatcher
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ModuleVersionSelector
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
-import spock.lang.Specification
-
-class UserResolverChainTest extends Specification {
-    final UserResolverChain resolver = new UserResolverChain()
-    final ModuleVersionSelector dependencyId = Stub()
-    final DependencyMetaData dependency = Stub()
-    final DependencyDescriptor dependencyDescriptor = Stub()
-    final ModuleDescriptor descriptor = descriptor("1.2")
-    final ModuleVersionIdentifier resolvedId = moduleVersionIdentifier(descriptor)
-
-    ModuleVersionIdentifier moduleVersionIdentifier(ModuleDescriptor moduleDescriptor) {
-        def moduleRevId = moduleDescriptor.moduleRevisionId
-        new DefaultModuleVersionIdentifier(moduleRevId.organisation, moduleRevId.name, moduleRevId.revision)
-    }
-
-    final BuildableModuleVersionResolveResult result = Mock()
-    final VersionMatcher matcher = Stub()
-    final ModuleSource moduleSource = Mock()
-
-    def setup() {
-        _ * dependencyId.group >> "group"
-        _ * dependencyId.name >> "project"
-        _ * dependencyId.version >> "1.0"
-        _ * dependency.requested >> dependencyId
-        _ * dependency.descriptor >> dependencyDescriptor
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo
-            assert source.moduleSource == moduleSource
-        }
-
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo
-            assert source.moduleSource == moduleSource
-        }
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo
-            assert source.moduleSource == moduleSource
-        }
-
-        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"), null)
-        }
-        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(version2, true, moduleSource)
-        }
-        1 * repo3.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor("1.0"), true, null)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == moduleVersionIdentifier(version2)
-            assert metaData.descriptor == version2
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo1
-            assert source.moduleSource == moduleSource
-        }
-
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo1
-            assert source.moduleSource == moduleSource
-        }
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-
-        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, moduleSource)
-        }
-        1 * result.resolved(_, _) >> { metaData, source ->
-            assert metaData.id == resolvedId
-            assert metaData.descriptor == descriptor
-            assert source.delegate == repo2
-            assert source.moduleSource == moduleSource
-        }
-
-        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/ivyresolve/memcache/CachedModuleVersionResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedModuleVersionResultTest.groovy
new file mode 100644
index 0000000..c0b631d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedModuleVersionResultTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
+import spock.lang.Specification
+
+class CachedModuleVersionResultTest extends Specification {
+
+    def "knows if result is cachable"() {
+        def resolved = Mock(BuildableModuleVersionMetaDataResolveResult) {
+            getState() >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+            getMetaData() >> Stub(MutableModuleVersionMetaData)
+        }
+        def missing = Mock(BuildableModuleVersionMetaDataResolveResult) { getState() >> BuildableModuleVersionMetaDataResolveResult.State.Missing }
+        def probablyMissing = Mock(BuildableModuleVersionMetaDataResolveResult) { getState() >> BuildableModuleVersionMetaDataResolveResult.State.ProbablyMissing }
+        def failed = Mock(BuildableModuleVersionMetaDataResolveResult) { getState() >> BuildableModuleVersionMetaDataResolveResult.State.Failed }
+
+        expect:
+        new CachedModuleVersionResult(resolved).cacheable
+        new CachedModuleVersionResult(missing).cacheable
+        new CachedModuleVersionResult(probablyMissing).cacheable
+        !new CachedModuleVersionResult(failed).cacheable
+    }
+
+    def "interrogates result only when resolved"() {
+        def resolved = Mock(BuildableModuleVersionMetaDataResolveResult)
+        def missing = Mock(BuildableModuleVersionMetaDataResolveResult)
+
+        when:
+        new CachedModuleVersionResult(missing)
+
+        then:
+        1 * missing.getState() >> BuildableModuleVersionMetaDataResolveResult.State.Missing
+        0 * missing._
+
+        when:
+        new CachedModuleVersionResult(resolved)
+
+        then:
+        1 * resolved.getState() >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+        1 * resolved.getMetaData() >> Stub(MutableModuleVersionMetaData)
+    }
+
+    def "supplies cached data"() {
+        def suppliedMetaData = Mock(MutableModuleVersionMetaData)
+        def cachedMetaData = Mock(MutableModuleVersionMetaData)
+        def metaData = Mock(MutableModuleVersionMetaData)
+        def source = Mock(ModuleSource)
+        def resolved = Mock(BuildableModuleVersionMetaDataResolveResult) {
+            getState() >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+            getMetaData() >> metaData
+            getModuleSource() >> source
+        }
+        def missing = Mock(BuildableModuleVersionMetaDataResolveResult) { getState() >> BuildableModuleVersionMetaDataResolveResult.State.Missing }
+        def probablyMissing = Mock(BuildableModuleVersionMetaDataResolveResult) { getState() >> BuildableModuleVersionMetaDataResolveResult.State.ProbablyMissing }
+
+        def result = Mock(BuildableModuleVersionMetaDataResolveResult)
+
+        when:
+        def cached = new CachedModuleVersionResult(resolved)
+
+        then:
+        1 * metaData.copy() >> cachedMetaData
+
+        when:
+        cached.supply(result)
+
+        then:
+        1 * cachedMetaData.copy() >> suppliedMetaData
+        1 * result.resolved(suppliedMetaData, source)
+
+        when:
+        new CachedModuleVersionResult(missing).supply(result)
+        then:
+        1 * result.missing()
+
+        when:
+        new CachedModuleVersionResult(probablyMissing).supply(result)
+        then:
+        1 * result.probablyMissing()
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedRepositoryTest.groovy
new file mode 100644
index 0000000..5e9b898
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/CachedRepositoryTest.groovy
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolveContext
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactSetResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionSelectionResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LocalAwareModuleVersionRepository
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class CachedRepositoryTest extends Specification {
+
+    def stats = new DependencyMetadataCacheStats()
+    def cache = Mock(DependencyMetadataCache)
+    def delegate = Mock(LocalAwareModuleVersionRepository)
+    def repo = new CachedRepository(cache, delegate, stats)
+
+    def lib = newSelector("org", "lib", "1.0")
+    def dep = Stub(DependencyMetaData) { getRequested() >> lib }
+    def listingResult = Mock(BuildableModuleVersionSelectionResolveResult)
+    def metaDataResult = Mock(BuildableModuleVersionMetaDataResolveResult)
+
+    def "delegates"() {
+        when:
+        def id = repo.getId()
+        def name = repo.getName()
+
+        then:
+        id == "x"
+        name == "localRepo"
+        1 * delegate.getId() >> "x"
+        1 * delegate.getName() >> "localRepo"
+    }
+
+    def "retrieves and caches module version listings"() {
+        when:
+        repo.localListModuleVersions(dep, listingResult)
+
+        then:
+        1 * cache.supplyLocalModuleVersions(lib, listingResult) >> false
+        1 * delegate.localListModuleVersions(dep, listingResult)
+        1 * cache.newLocalModuleVersions(lib, listingResult)
+        0 * _
+
+        when:
+        repo.listModuleVersions(dep, listingResult)
+
+        then:
+        1 * cache.supplyModuleVersions(lib, listingResult) >> false
+        1 * delegate.listModuleVersions(dep, listingResult)
+        1 * cache.newModuleVersions(lib, listingResult)
+        0 * _
+    }
+
+    def "uses module version listings from cache"() {
+        when:
+        repo.localListModuleVersions(dep, listingResult)
+
+        then:
+        1 * cache.supplyLocalModuleVersions(lib, listingResult) >> true
+        0 * _
+
+        when:
+        repo.listModuleVersions(dep, listingResult)
+
+        then:
+        1 * cache.supplyModuleVersions(lib, listingResult) >> true
+        0 * _
+    }
+
+    def "retrieves and caches local dependencies"() {
+        when:
+        repo.getLocalDependency(dep, metaDataResult)
+
+        then:
+        1 * cache.supplyLocalMetaData(lib, metaDataResult) >> false
+        1 * delegate.getLocalDependency(dep, metaDataResult)
+        1 * cache.newLocalDependencyResult(lib, metaDataResult)
+        0 * _
+    }
+
+    def "uses local dependencies from cache"() {
+        when:
+        repo.getLocalDependency(dep, metaDataResult)
+
+        then:
+        1 * cache.supplyLocalMetaData(lib, metaDataResult) >> true
+        0 * _
+    }
+
+    def "retrieves and caches dependencies"() {
+        when:
+        repo.getDependency(dep, metaDataResult)
+
+        then:
+        1 * cache.supplyMetaData(lib, metaDataResult) >> false
+        1 * delegate.getDependency(dep, metaDataResult)
+        1 * cache.newDependencyResult(lib, metaDataResult)
+        0 * _
+    }
+
+    def "uses dependencies from cache"() {
+        when:
+        repo.getDependency(dep, metaDataResult)
+
+        then:
+        1 * cache.supplyMetaData(lib, metaDataResult) >> true
+        0 * _
+    }
+
+    def "delegates request for module artifacts"() {
+        def moduleMetaData = Stub(ModuleVersionMetaData)
+        def context = Stub(ArtifactResolveContext)
+        def result = Mock(BuildableArtifactSetResolveResult)
+
+        when:
+        repo.resolveModuleArtifacts(moduleMetaData, context, result)
+
+        then:
+        1 * delegate.resolveModuleArtifacts(moduleMetaData, context, result)
+        0 * _
+    }
+
+    def "retrieves and caches artifacts"() {
+        def result = Mock(BuildableArtifactResolveResult)
+        def artifactId = Stub(ModuleVersionArtifactIdentifier)
+        def artifact = Stub(ModuleVersionArtifactMetaData) {
+            getId() >> artifactId
+        }
+        def moduleSource = Mock(ModuleSource)
+
+        when:
+        repo.resolveArtifact(artifact, moduleSource, result)
+
+        then:
+        1 * cache.supplyArtifact(artifactId, result) >> false
+        1 * delegate.resolveArtifact(artifact, moduleSource, result)
+        1 * cache.newArtifact(artifactId, result)
+        0 * _
+    }
+
+    def "uses artifacts from cache"() {
+        def result = Mock(BuildableArtifactResolveResult)
+        def artifactId = Stub(ModuleVersionArtifactIdentifier)
+        def artifact = Stub(ModuleVersionArtifactMetaData) {
+            getId() >> artifactId
+        }
+        def moduleSource = Mock(ModuleSource)
+
+        when:
+        repo.resolveArtifact(artifact, moduleSource, result)
+
+        then:
+        1 * cache.supplyArtifact(artifactId, result) >> true
+        0 * _
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCacheTest.groovy
new file mode 100644
index 0000000..e04bf62
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/DependencyMetadataCacheTest.groovy
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaDataResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionSelectionResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionListing
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class DependencyMetadataCacheTest extends Specification {
+
+    def stats = new DependencyMetadataCacheStats()
+    def cache = new DependencyMetadataCache(stats)
+
+    def "caches and supplies local and remote module versions"() {
+        def remoteListing = Mock(ModuleVersionListing)
+        def localListing = Mock(ModuleVersionListing)
+        def result = Mock(BuildableModuleVersionSelectionResolveResult)
+        def missingResult = Mock(BuildableModuleVersionSelectionResolveResult)
+
+        given:
+        cache.newModuleVersions(newSelector("org", "foo-remote", "1.0"), Stub(BuildableModuleVersionSelectionResolveResult) {
+            getState() >> BuildableModuleVersionSelectionResolveResult.State.Listed
+            getVersions() >> remoteListing
+        })
+        cache.newLocalModuleVersions(newSelector("org", "foo-local", "1.0"), Stub(BuildableModuleVersionSelectionResolveResult) {
+            getState() >> BuildableModuleVersionSelectionResolveResult.State.Listed
+            getVersions() >> localListing
+        })
+
+        when:
+        def local = cache.supplyLocalModuleVersions(newSelector("org", "foo-local", "1.0"), result)
+        def missingLocal = cache.supplyLocalModuleVersions(newSelector("org", "foo-remote", "1.0"), missingResult)
+
+        then:
+        local
+        1 * result.listed(localListing)
+
+        and:
+        !missingLocal
+        0 * missingResult._
+
+        when:
+        def remote = cache.supplyModuleVersions(newSelector("org", "foo-remote", "1.0"), result)
+        def missingRemote = cache.supplyModuleVersions(newSelector("org", "foo-local", "1.0"), missingResult)
+
+        then:
+        remote
+        1 * result.listed(remoteListing)
+
+        and:
+        !missingRemote
+        0 * missingResult._
+    }
+
+    def "does not cache failed module version listing"() {
+        def failedResult = Stub(BuildableModuleVersionSelectionResolveResult) {
+            getState() >> BuildableModuleVersionSelectionResolveResult.State.Failed
+        }
+        cache.newModuleVersions(newSelector("org", "lib", "1.0"), failedResult)
+        cache.newLocalModuleVersions(newSelector("org", "lib", "1.0"), failedResult)
+
+        def result = Mock(BuildableModuleVersionSelectionResolveResult)
+
+        when:
+        def remoteFromCache = cache.supplyModuleVersions(newSelector("org", "lib", "1.0"), result)
+        def localFromCache = cache.supplyLocalModuleVersions(newSelector("org", "lib", "1.0"), result)
+
+        then:
+        !remoteFromCache
+        !localFromCache
+        0 * result._
+    }
+
+    def "caches and supplies remote metadata"() {
+        def suppliedMetaData = Stub(MutableModuleVersionMetaData)
+        def cachedCopy = Stub(MutableModuleVersionMetaData)
+        def originalMetaData = Stub(MutableModuleVersionMetaData)
+        def source = Stub(ModuleSource)
+        def resolvedResult = Mock(BuildableModuleVersionMetaDataResolveResult.class) {
+            getState() >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+            getMetaData() >> originalMetaData
+            getModuleSource() >> source
+        }
+        def result = Mock(BuildableModuleVersionMetaDataResolveResult.class)
+
+        given:
+        _ * originalMetaData.copy() >> cachedCopy
+        cache.newDependencyResult(newSelector("org", "foo", "1.0"), resolvedResult)
+
+        when:
+        def local = cache.supplyLocalMetaData(newSelector("org", "foo", "1.0"), result)
+        def differentSelector = cache.supplyMetaData(newSelector("org", "XXX", "1.0"), result)
+
+        then:
+        !local
+        !differentSelector
+        stats.metadataServed == 0
+        0 * result._
+
+        when:
+        def match = cache.supplyMetaData(newSelector("org", "foo", "1.0"), result)
+
+        then:
+        match
+        stats.metadataServed == 1
+        _ * cachedCopy.copy() >> suppliedMetaData
+        1 * result.resolved(suppliedMetaData, source)
+    }
+
+    def "caches and supplies remote and local metadata"() {
+        def localSource = Stub(ModuleSource)
+        def localMetaData = Stub(MutableModuleVersionMetaData)
+        _ * localMetaData.copy() >> localMetaData
+        def remoteSource = Stub(ModuleSource)
+        def remoteMetaData = Stub(MutableModuleVersionMetaData)
+        _ * remoteMetaData.copy() >> remoteMetaData
+        def resolvedLocal = Mock(BuildableModuleVersionMetaDataResolveResult.class) {
+            getMetaData() >> localMetaData
+            getModuleSource() >> localSource
+            getState() >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+        }
+        def resolvedRemote = Mock(BuildableModuleVersionMetaDataResolveResult.class) {
+            getMetaData() >> remoteMetaData
+            getModuleSource() >> remoteSource
+            getState() >> BuildableModuleVersionMetaDataResolveResult.State.Resolved
+        }
+
+        cache.newDependencyResult(newSelector("org", "remote", "1.0"), resolvedRemote)
+        cache.newLocalDependencyResult(newSelector("org", "local", "1.0"), resolvedLocal)
+
+        def result = Mock(BuildableModuleVersionMetaDataResolveResult.class)
+
+        when:
+        def local = cache.supplyLocalMetaData(newSelector("org", "local", "1.0"), result)
+
+        then:
+        local
+        stats.metadataServed == 1
+        1 * result.resolved(localMetaData, localSource)
+
+        when:
+        def remote = cache.supplyMetaData(newSelector("org", "remote", "1.0"), result)
+
+        then:
+        remote
+        stats.metadataServed == 2
+        1 * result.resolved(remoteMetaData, remoteSource)
+    }
+
+    def "does not cache failed resolves"() {
+        def failedResult = Mock(BuildableModuleVersionMetaDataResolveResult.class) { getState() >> BuildableModuleVersionMetaDataResolveResult.State.Failed }
+        cache.newDependencyResult(newSelector("org", "lib", "1.0"), failedResult)
+
+        def result = Mock(BuildableModuleVersionMetaDataResolveResult.class)
+
+        when:
+        def fromCache = cache.supplyMetaData(newSelector("org", "lib", "1.0"), result)
+
+        then:
+        !fromCache
+        0 * result._
+    }
+
+    def "caches and supplies artifacts"() {
+        def fooId = Stub(ModuleVersionArtifactIdentifier)
+        def fooFile = new File("foo")
+        def fooResult = Mock(BuildableArtifactResolveResult) { getFile() >> fooFile }
+        def anotherFooResult = Mock(BuildableArtifactResolveResult)
+
+        def differentId = Stub(ModuleVersionArtifactIdentifier)
+        def differentResult = Mock(BuildableArtifactResolveResult)
+
+        cache.newArtifact(fooId, fooResult)
+
+        when:
+        def differentCached = cache.supplyArtifact(differentId, differentResult )
+
+        then:
+        !differentCached
+        0 * differentResult._
+
+        when:
+        def fooCached = cache.supplyArtifact(fooId, anotherFooResult )
+
+        then:
+        fooCached
+        1 * anotherFooResult.resolved(fooFile)
+    }
+
+    def "does not cache failed artifact resolves"() {
+        def artifactId = Stub(ModuleVersionArtifactIdentifier)
+        def failedResult = Stub(BuildableArtifactResolveResult) { getFailure() >> new ArtifactResolveException("bad") }
+        cache.newArtifact(artifactId, failedResult)
+
+        def result = Mock(BuildableArtifactResolveResult)
+
+        when:
+        def fromCache = cache.supplyArtifact(artifactId, result)
+
+        then:
+        !fromCache
+        0 * result._
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryDependencyMetadataCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryDependencyMetadataCacheTest.groovy
new file mode 100644
index 0000000..fe3067b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryDependencyMetadataCacheTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.memcache
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LocalAwareModuleVersionRepository
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+class InMemoryDependencyMetadataCacheTest extends Specification {
+
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+    def cache = new InMemoryDependencyMetadataCache()
+
+    def "can be turned off via system property"() {
+        System.properties.setProperty(InMemoryDependencyMetadataCache.TOGGLE_PROPERTY, "false")
+        def repo = Mock(LocalAwareModuleVersionRepository) { getId() >> "mavenCentral" }
+
+        when:
+        def out = cache.cached(repo)
+
+        then:
+        out.is(repo)
+    }
+
+    def "wraps repositories"() {
+        def repo1 = Mock(LocalAwareModuleVersionRepository) { getId() >> "mavenCentral" }
+        def repo2 = Mock(LocalAwareModuleVersionRepository) { getId() >> "localRepo" }
+        def repo3 = Mock(LocalAwareModuleVersionRepository) { getId() >> "mavenCentral" }
+
+        when:
+        CachedRepository c1 = cache.cached(repo1)
+        CachedRepository c2 = cache.cached(repo2)
+        CachedRepository c3 = cache.cached(repo3)
+
+        then:
+        c1.delegate == repo1
+        c2.delegate == repo2
+        c3.delegate == repo3
+
+        c1.cache == c3.cache //same repo id, same cache
+        c2.cache != c1.cache
+
+        cache.stats.reposWrapped == 3
+        cache.stats.cacheInstances == 2
+
+        cache.cachePerRepo.size() == 2
+    }
+
+    def "cleans cache on close"() {
+        when:
+        cache.cached(Mock(LocalAwareModuleVersionRepository) { getId() >> "x"} )
+        cache.stop()
+
+        then:
+        cache.cachePerRepo.isEmpty()
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractGradlePomModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractGradlePomModuleDescriptorParserTest.groovy
new file mode 100644
index 0000000..7063de6
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractGradlePomModuleDescriptorParserTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+abstract class AbstractGradlePomModuleDescriptorParserTest extends Specification {
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    final GradlePomModuleDescriptorParser parser = new GradlePomModuleDescriptorParser()
+    final parseContext = Mock(DescriptorParseContext)
+    TestFile pomFile
+
+    def "setup"() {
+        pomFile = tmpDir.file('foo')
+    }
+
+    protected ModuleDescriptor parsePom() {
+        parseMetaData().descriptor
+    }
+
+    protected MutableModuleVersionMetaData parseMetaData() {
+        parser.parseMetaData(parseContext, pomFile, true)
+    }
+
+    protected void hasArtifact(ModuleDescriptor descriptor, String name, String type, String ext, String classifier = null) {
+        assert descriptor.allArtifacts.length == 1
+        def artifact = descriptor.allArtifacts.first()
+        assert artifact.id == artifactId(descriptor.moduleRevisionId, name, type, ext)
+        assert artifact.extraAttributes['classifier'] == classifier
+    }
+
+    protected void hasDefaultDependencyArtifact(DependencyDescriptor descriptor) {
+        assert descriptor.allDependencyArtifacts.length == 0
+    }
+
+    protected void hasDependencyArtifact(DependencyDescriptor descriptor, String name, String type, String ext, String classifier = null) {
+        assert descriptor.allDependencyArtifacts.length == 1
+        def artifact = descriptor.allDependencyArtifacts.first()
+        assert artifact.name == name
+        assert artifact.type == type
+        assert artifact.ext == ext
+        assert artifact.extraAttributes['classifier'] == classifier
+    }
+
+    protected ModuleRevisionId moduleId(String group, String name, String version) {
+        IvyUtil.createModuleRevisionId(group, name, version)
+    }
+
+    protected ArtifactRevisionId artifactId(ModuleRevisionId moduleId, String name, String type, String ext) {
+        ArtifactRevisionId.newInstance(moduleId, name, type, ext)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractPomReaderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractPomReaderTest.groovy
new file mode 100644
index 0000000..d7f16f9
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/AbstractPomReaderTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomDependencyMgt
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource
+import org.gradle.internal.resource.local.LocallyAvailableResource
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+abstract class AbstractPomReaderTest extends Specification {
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    PomReader pomReader
+    TestFile pomFile
+    LocallyAvailableExternalResource locallyAvailableExternalResource
+
+    def setup() {
+        pomFile = tmpDir.file('pom.xml')
+        pomFile.createFile()
+        LocallyAvailableResource locallyAvailableResource = new DefaultLocallyAvailableResource(pomFile)
+        locallyAvailableExternalResource = new DefaultLocallyAvailableExternalResource(pomFile.toURI().toURL().toString(), locallyAvailableResource)
+    }
+
+    protected void assertResolvedPomDependency(MavenDependencyKey key, String version) {
+        assert pomReader.dependencies.containsKey(key)
+        PomReader.PomDependencyData dependency = pomReader.dependencies[key]
+        assertPomDependencyValues(key, version, dependency)
+    }
+
+    protected void assertResolvedPomDependencyManagement(MavenDependencyKey key, String version) {
+        assert pomReader.dependencyMgt.containsKey(key)
+        PomDependencyMgt dependency = pomReader.dependencyMgt[key]
+        assertPomDependencyValues(key, version, dependency)
+    }
+
+    protected void assertPomDependencyValues(MavenDependencyKey key, String version, PomDependencyMgt dependency) {
+        assert dependency.groupId == key.groupId
+        assert dependency.artifactId == key.artifactId
+        assert dependency.type == key.type
+        assert dependency.classifier == key.classifier
+        assert dependency.version == version
+    }
+
+    protected PomReader createPomReader(String pomFileName, String pomDefinition) {
+        TestFile pomFile = tmpDir.file(pomFileName)
+        pomFile.createFile()
+        pomFile << pomDefinition
+        LocallyAvailableResource locallyAvailableResource = new DefaultLocallyAvailableResource(pomFile)
+        LocallyAvailableExternalResource locallyAvailableExternalResource = new DefaultLocallyAvailableExternalResource(pomFile.toURI().toURL().toString(), locallyAvailableResource)
+        return new PomReader(locallyAvailableExternalResource)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedIvyXmlModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedIvyXmlModuleDescriptorParserTest.groovy
new file mode 100644
index 0000000..b6ac55f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedIvyXmlModuleDescriptorParserTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
+import org.gradle.api.internal.externalresource.ExternalResource
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource
+import org.gradle.internal.resource.local.LocallyAvailableResource
+import spock.lang.Specification
+
+class DisconnectedIvyXmlModuleDescriptorParserTest extends Specification {
+    LocallyAvailableExternalResource localExternalResource = Mock()
+    LocallyAvailableResource localResource = Mock()
+    ExternalResource externalResource = Mock()
+    ResolverStrategy resolverStrategy = Stub()
+    IvyXmlModuleDescriptorParser parser = new DisconnectedIvyXmlModuleDescriptorParser(resolverStrategy)
+    DescriptorParseContext parseContext = Mock()
+
+    def "creates overridden internal Ivy parser"() throws Exception {
+        when:
+        IvyXmlModuleDescriptorParser.Parser disconnectedParser = parser.createParser(parseContext, localExternalResource, [:], resolverStrategy)
+
+        then:
+        localExternalResource.getLocalResource() >> localResource
+        localResource.getFile() >> new File('ivy.xml')
+        disconnectedParser != null
+        disconnectedParser instanceof DisconnectedIvyXmlModuleDescriptorParser.DisconnectedParser
+    }
+
+    def "creates new internal Ivy parser"() throws Exception {
+        when:
+        IvyXmlModuleDescriptorParser.Parser disconnectedParser = parser.createParser(parseContext, localExternalResource, [:], resolverStrategy)
+
+        and:
+        IvyXmlModuleDescriptorParser.Parser newDisconnectedParser = disconnectedParser.newParser(externalResource, new File('ivy.xml').toURI().toURL())
+
+        then:
+        localExternalResource.getLocalResource() >> localResource
+        localResource.getFile() >> new File('ivy.xml')
+        disconnectedParser != null
+        disconnectedParser instanceof DisconnectedIvyXmlModuleDescriptorParser.DisconnectedParser
+        newDisconnectedParser != null
+        newDisconnectedParser instanceof DisconnectedIvyXmlModuleDescriptorParser.DisconnectedParser
+    }
+
+    def "parses other Ivy file and return with standard module descriptor"() throws Exception {
+        when:
+        IvyXmlModuleDescriptorParser.Parser disconnectedParser = parser.createParser(parseContext, localExternalResource, [:], resolverStrategy)
+
+        and:
+        ModuleDescriptor moduleDescriptor = disconnectedParser.parseOtherIvyFile('myorg', 'parentMod', 'parentRev')
+
+        then:
+        localExternalResource.getLocalResource() >> localResource
+        localResource.getFile() >> new File('ivy.xml')
+        disconnectedParser
+        disconnectedParser instanceof DisconnectedIvyXmlModuleDescriptorParser.DisconnectedParser
+        moduleDescriptor != null
+        moduleDescriptor.status == 'release'
+        moduleDescriptor.publicationDate != null
+        moduleDescriptor.moduleRevisionId.organisation == 'myorg'
+        moduleDescriptor.moduleRevisionId.moduleId.name == 'parentMod'
+        moduleDescriptor.moduleRevisionId.revision == 'parentRev'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
index a7162e4..da56917 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
@@ -16,16 +16,15 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
 
-import org.apache.ivy.core.settings.IvySettings
-import org.apache.ivy.plugins.parser.ParserSettings
-import org.apache.ivy.plugins.repository.Resource
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class DownloadedIvyModuleDescriptorParserTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmpDir
-    final DownloadedIvyModuleDescriptorParser parser = new DownloadedIvyModuleDescriptorParser()
+    final DownloadedIvyModuleDescriptorParser parser = new DownloadedIvyModuleDescriptorParser(Stub(ResolverStrategy))
+    final parserSettings = Mock(DescriptorParseContext)
 
     def "discards the default attribute"() {
         def ivyFile = tmpDir.createFile("ivy.xml")
@@ -34,12 +33,9 @@ class DownloadedIvyModuleDescriptorParserTest extends Specification {
     <info organisation="org" module="someModule" revision="1.2" default="true"/>
 </ivy-module>
 """
-        def url = ivyFile.toURI().toURL()
-        ParserSettings settings = new IvySettings()
-        Resource resource = Mock()
-
         when:
-        def descriptor = parser.parseDescriptor(settings, url, resource, true)
+        parserSettings.substitute(_ as String) >> {String value -> value}
+        def descriptor = parser.parseMetaData(parserSettings, ivyFile, true).descriptor
 
         then:
         !descriptor.default
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy
new file mode 100644
index 0000000..e19dbfa
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy
@@ -0,0 +1,827 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.gradle.api.internal.artifacts.resolution.MavenPomArtifact
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource
+
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleId
+
+class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModuleDescriptorParserTest {
+    def "pom with project coordinates defined by active profile properties"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>\${some.group}</groupId>
+    <artifactId>\${some.artifact}</artifactId>
+    <version>\${some.version}</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <some.group>group-one</some.group>
+                <some.artifact>artifact-one</some.artifact>
+                <some.version>version-one</some.version>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 0
+    }
+
+    def "pom with dependency coordinates defined by active profile properties"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${some.group}</groupId>
+            <artifactId>\${some.artifact}</artifactId>
+            <version>\${some.version}</version>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <some.group>group-two</some.group>
+                <some.artifact>artifact-two</some.artifact>
+                <some.version>version-two</some.version>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    def "uses parent properties from active profile to provide default values for a dependency"() {
+        given:
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <some.group>group-two</some.group>
+                <some.artifact>artifact-two</some.artifact>
+                <some.version>version-two</some.version>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${some.group}</groupId>
+            <artifactId>\${some.artifact}</artifactId>
+            <version>\${some.version}</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact.class) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses grand parent properties from active profile to provide default values for a dependency"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <some.group>group-two</some.group>
+                <some.artifact>artifact-two</some.artifact>
+                <some.version>version-two</some.version>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${some.group}</groupId>
+            <artifactId>\${some.artifact}</artifactId>
+            <version>\${some.version}</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses dependency management section in active profile to provide default values for a dependency"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>1.2</version>
+                        <scope>test</scope>
+                        <exclusions>
+                            <exclusion>
+                                <groupId>group-three</groupId>
+                                <artifactId>artifact-three</artifactId>
+                            </exclusion>
+                        </exclusions>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['test']
+        dep.allExcludeRules.length == 1
+        dep.allExcludeRules.first().id.moduleId == createModuleId('group-three', 'artifact-three')
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses parent pom dependency management section in active profile to provide default values for a dependency"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>1.2</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses grand parent pom dependency management section in active profile to provide default values for a dependency"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>1.2</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses dependency management section from imported POM in active profile to define defaults for main POM body dependency"() {
+        given:
+        def imported = tmpDir.file("imported.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>imported</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>different-group</groupId>
+                        <artifactId>imported</artifactId>
+                        <version>different-version</version>
+                        <type>pom</type>
+                        <scope>import</scope>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(imported.toURI().toURL().toString(), new DefaultLocallyAvailableResource(imported)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "pom with dependency defined by active profile"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    def "uses parent dependency from active profile"() {
+        given:
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses grand parent dependency from active profile"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>1.2</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses parent dependency over grand parent dependency with same groupId and artifactId from active profile"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>1.2</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <profiles>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>1.3</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.3')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses dependency management section from imported POM in active profile to define defaults for profile dependency"() {
+        given:
+        def imported = tmpDir.file("imported.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>imported</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>different-group</groupId>
+                        <artifactId>imported</artifactId>
+                        <version>different-version</version>
+                        <type>pom</type>
+                        <scope>import</scope>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(imported.toURI().toURL().toString(), new DefaultLocallyAvailableResource(imported)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
index 57a5d3a..274f3e8 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
@@ -15,27 +15,14 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.module.id.ArtifactRevisionId
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.plugins.parser.ParserSettings
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
+import org.gradle.api.internal.artifacts.resolution.MavenPomArtifact
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource
 import spock.lang.Issue
-import spock.lang.Specification
 
-class GradlePomModuleDescriptorParserTest extends Specification {
-    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    final GradlePomModuleDescriptorParser parser = new GradlePomModuleDescriptorParser()
-    final ModuleScopedParserSettings ivySettings = Mock()
-    TestFile pomFile
-
-    def "setup"() {
-        pomFile = tmpDir.file('foo')
-    }
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleId
 
+class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescriptorParserTest {
     def "parses simple pom"() {
         given:
         pomFile << """
@@ -56,8 +43,6 @@ class GradlePomModuleDescriptorParserTest extends Specification {
     </dependencies>
 </project>
 """
-        and:
-        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
 
         when:
         def descriptor = parsePom()
@@ -68,44 +53,169 @@ class GradlePomModuleDescriptorParserTest extends Specification {
         descriptor.dependencies.length == 1
         descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
         hasDefaultDependencyArtifact(descriptor.dependencies.first())
+        parser.typeName == 'POM'
+        parser.toString() == 'gradle pom parser'
     }
 
-    def "pom with dependency with classifier"() {
+    def "converts timestamp version to SNAPSHOT version"() {
         given:
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
     <groupId>group-one</groupId>
     <artifactId>artifact-one</artifactId>
+    <version>my-version-20141012.121000-1</version>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'my-version-SNAPSHOT')
+    }
+
+    def "merges dependencies declared in pom with those declared in parent"() {
+        given:
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
     <version>version-one</version>
 
     <dependencies>
         <dependency>
             <groupId>group-two</groupId>
             <artifactId>artifact-two</artifactId>
-            <version>version-two</version>
-            <classifier>classifier-two</classifier>
+            <version>1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-three</artifactId>
+            <version>1.2</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-one</artifactId>
+            <version>11</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-three</artifactId>
+            <version>11</version>
         </dependency>
     </dependencies>
 </project>
 """
         and:
-        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 3
+
+        def dep1 = descriptor.dependencies[0]
+        dep1.dependencyRevisionId == moduleId('group-two', 'artifact-one', '11')
+        dep1.moduleConfigurations == ['compile', 'runtime']
+
+        def dep2 = descriptor.dependencies[1]
+        dep2.dependencyRevisionId == moduleId('group-two', 'artifact-three', '11')
+        dep2.moduleConfigurations == ['compile', 'runtime']
+
+        def inheritedDep = descriptor.dependencies[2]
+        inheritedDep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        inheritedDep.moduleConfigurations == ['compile', 'runtime']
+    }
+
+    def "uses dependency management section to provide default values for a dependency"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+                <scope>test</scope>
+                <exclusions>
+                    <exclusion>
+                        <groupId>group-three</groupId>
+                        <artifactId>artifact-three</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
 
         when:
         def descriptor = parsePom()
 
         then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
         descriptor.dependencies.length == 1
-        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
-        hasDependencyArtifact(descriptor.dependencies.first(), 'artifact-two', 'jar', 'jar', 'classifier-two')
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['test']
+        dep.allExcludeRules.length == 1
+        dep.allExcludeRules.first().id.moduleId == createModuleId('group-three', 'artifact-three')
+        hasDefaultDependencyArtifact(dep)
     }
 
-    @Issue("GRADLE-2068")
-    def "pom with dependency with empty classifier is treated like dependency without classifier"() {
+    def "throws exception if parent pom dependency management section does not provide default values for dependency"() {
         given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-three</groupId>
+                <artifactId>artifact-three</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
@@ -113,83 +223,1865 @@ class GradlePomModuleDescriptorParserTest extends Specification {
     <artifactId>artifact-one</artifactId>
     <version>version-one</version>
 
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        parsePom()
+
+        then:
+        Throwable t = thrown(MetaDataParseException)
+        t.cause instanceof UnresolvedDependencyVersionException
+        t.cause.message == "Unable to resolve version for dependency 'group-two:artifact-two:jar'"
+    }
+
+    def "uses parent pom dependency management section to provide default values for a dependency"() {
+        given:
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+                <scope>test</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
     <dependencies>
         <dependency>
             <groupId>group-two</groupId>
             <artifactId>artifact-two</artifactId>
-            <version>version-two</version>
-            <classifier></classifier>
         </dependency>
     </dependencies>
 </project>
 """
         and:
-        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
 
         when:
         def descriptor = parsePom()
 
         then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
         descriptor.dependencies.length == 1
-        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
-        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['test']
+        hasDefaultDependencyArtifact(dep)
     }
 
-    @Issue("GRADLE-2076")
-    def "pom with packaging of type eclipse-plugin creates jar artifact"() {
+    def "uses parent pom dependency management section with multiple versions of same dependency"() {
         given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.3</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.1</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
     <groupId>group-one</groupId>
     <artifactId>artifact-one</artifactId>
     <version>version-one</version>
-    <packaging>eclipse-plugin</packaging>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
 </project>
 """
         and:
-        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
 
         when:
         def descriptor = parsePom()
 
         then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'eclipse-plugin', 'jar')
-        descriptor.dependencies.length == 0
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.1')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
     }
 
-    private ModuleDescriptor parsePom() {
-        parser.parseDescriptor(ivySettings, pomFile.toURI().toURL(), false)
-    }
+    def "uses imported child pom over parent pom dependency management section"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
 
-    private void hasArtifact(ModuleDescriptor descriptor, String name, String type, String ext, String classifier = null) {
-        descriptor.allArtifacts.length == 1
-        def artifact = descriptor.allArtifacts.first()
-        assert artifact.id == artifactId(descriptor.moduleRevisionId, name, type, ext)
-        assert artifact.extraAttributes['classifier'] == classifier
-    }
-    
-    private void hasDefaultDependencyArtifact(DependencyDescriptor descriptor) {
-        descriptor.allDependencyArtifacts.length == 0
-    }
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        def imported = tmpDir.file("imported.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>imported</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.5</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>different-group</groupId>
+                <artifactId>imported</artifactId>
+                <version>different-version</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(imported.toURI().toURL().toString(), new DefaultLocallyAvailableResource(imported)) }
+
+        when:
+        def descriptor = parsePom()
 
-    private void hasDependencyArtifact(DependencyDescriptor descriptor, String name, String type, String ext, String classifier = null) {
-        descriptor.allDependencyArtifacts.length == 1
-        def artifact = descriptor.allDependencyArtifacts.first()
-        assert artifact.name == name
-        assert artifact.type == type
-        assert artifact.ext == ext
-        assert artifact.extraAttributes['classifier'] == classifier
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.5')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
     }
 
-    private ModuleRevisionId moduleId(String group, String name, String version) {
-        ModuleRevisionId.newInstance(group, name, version)
+    def "uses importing pom dependency management over imported pom definition with same group ID and artifact ID "() {
+        given:
+
+        def imported = tmpDir.file("imported.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>imported</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.5</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>different-group</groupId>
+                <artifactId>imported</artifactId>
+                <version>different-version</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(imported.toURI().toURL().toString(), new DefaultLocallyAvailableResource(imported)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
     }
 
-    private ArtifactRevisionId artifactId(ModuleRevisionId moduleId, String name, String type, String ext) {
-        ArtifactRevisionId.newInstance(moduleId, name, type, ext)
+    def "uses child dependency over parent dependency with same group ID and artifact ID"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.5</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.2</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses parent pom dependency with multiple versions of same dependency"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.5</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.4</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.4')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses parent pom over grand parent pom dependency management section"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.5</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses grand parent pom properties for parent pom dependency management section"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <properties>
+        <grandparent.groupid>group-two</grandparent.groupid>
+        <grandparent.artifactid>artifact-two</grandparent.artifactid>
+    </properties>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>\${grandparent.groupid}</groupId>
+                <artifactId>\${grandparent.artifactid}</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses parent pom properties over grand parent pom properties for dependency management if overridden"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <properties>
+        <my.groupid>group-three</my.groupid>
+        <my.artifactid>artifact-three</my.artifactid>
+    </properties>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <properties>
+        <my.groupid>group-two</my.groupid>
+        <my.artifactid>artifact-two</my.artifactid>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>\${my.groupid}</groupId>
+                <artifactId>\${my.artifactid}</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses properties from parent pom to replace variable placeholders in pom"() {
+        given:
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <properties>
+        <artifact-two.version>v2</artifact-two.version>
+        <artifact-three.version>ignore-me</artifact-three.version>
+    </properties>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <properties>
+        <artifact-three.version>v3</artifact-three.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>\${artifact-two.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>group-three</groupId>
+            <artifactId>artifact-three</artifactId>
+            <version>\${artifact-three.version}</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 2
+        def artifactTwo = descriptor.dependencies[0]
+        artifactTwo.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'v2')
+        hasDefaultDependencyArtifact(artifactTwo)
+        def artifactThree = descriptor.dependencies[1]
+        artifactThree.dependencyRevisionId == moduleId('group-three', 'artifact-three', 'v3')
+        hasDefaultDependencyArtifact(artifactThree)
+    }
+
+    def "replaces variable placeholders in parent pom dependency management section"() {
+        given:
+        def grandParent = tmpDir.file("grand-parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>\${project.groupId}</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>\${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>\${project.groupId}</groupId>
+                <artifactId>artifact-three</artifactId>
+                <version>\${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${project.groupId}</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-one</groupId>
+            <artifactId>artifact-three</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 2
+        def artifactTwo = descriptor.dependencies[0]
+        artifactTwo.dependencyRevisionId == moduleId('group-one', 'artifact-two', 'version-one')
+        artifactTwo.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(artifactTwo)
+        def artifactThree = descriptor.dependencies[1]
+        artifactThree.dependencyRevisionId == moduleId('group-one', 'artifact-three', 'version-one')
+        artifactThree.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(artifactThree)
+    }
+
+    @Issue("GRADLE-2918")
+    def "uses imported BOM in grand parent dependency management section"() {
+        given:
+        def imported = tmpDir.file("imported.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>imported</artifactId>
+    <version>different-version</version>
+
+    <properties>
+        <myversion>some-version</myversion>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-one</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>\${myversion}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        def grandParent = tmpDir.file("grand-parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>different-group</groupId>
+                <artifactId>imported</artifactId>
+                <version>different-version</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xlm") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-one</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(imported.toURI().toURL().toString(), new DefaultLocallyAvailableResource(imported)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def artifactTwo = descriptor.dependencies[0]
+        artifactTwo.dependencyRevisionId == moduleId('group-one', 'artifact-two', 'some-version')
+        artifactTwo.moduleConfigurations == ['compile', 'runtime']
+    }
+
+
+
+    def "pom with dependency with classifier"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+            <classifier>classifier-two</classifier>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDependencyArtifact(descriptor.dependencies.first(), 'artifact-two', 'jar', 'jar', 'classifier-two')
+    }
+
+    @Issue("GRADLE-2068")
+    def "pom with dependency with empty classifier is treated like dependency without classifier"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+            <classifier></classifier>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    @Issue("GRADLE-2076")
+    def "pom with packaging of type eclipse-plugin creates jar artifact"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <packaging>eclipse-plugin</packaging>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'eclipse-plugin', 'jar')
+        descriptor.dependencies.length == 0
+    }
+
+    def "fails when POM is not well formed XML"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion
+</project>
+"""
+
+        when:
+        parseMetaData()
+
+        then:
+        def e = thrown(MetaDataParseException)
+        e.message == "Could not parse POM ${pomFile.toURI()}"
+        e.cause.message.contains('Element type "modelVersion"')
+    }
+
+
+    def "pom with no dependencies "() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <packaging>pom</packaging>
+</project>
+"""
+
+        when:
+        def metaData = parseMetaData()
+        def descriptor = metaData.descriptor
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        descriptor.dependencies.length == 0
+    }
+
+    def "pom with packaging 'pom'"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <packaging>pom</packaging>
+</project>
+"""
+
+        when:
+        def metaData = parseMetaData()
+        def descriptor = metaData.descriptor
+
+        then:
+        descriptor.allArtifacts.length == 0
+        metaData.metaDataOnly //flag to indicate that there may not actually be any artifacts
+    }
+
+    def "pom with project coordinates defined by custom properties"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>\${some.group}</groupId>
+    <artifactId>\${some.artifact}</artifactId>
+    <version>\${some.version}</version>
+    <properties>
+        <some.group>group-one</some.group>
+        <some.artifact>artifact-one</some.artifact>
+        <some.version>version-one</some.version>
+    </properties>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 0
+    }
+
+    def "pom with dependency coordinates defined by custom properties"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <properties>
+        <some.group>group-two</some.group>
+        <some.artifact>artifact-two</some.artifact>
+        <some.version>version-two</some.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${some.group}</groupId>
+            <artifactId>\${some.artifact}</artifactId>
+            <version>\${some.version}</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    def "uses parent pom over grand parent pom dependency"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.5</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>1.2</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses grand parent pom properties for parent pom dependency"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <properties>
+        <grandparent.groupid>group-two</grandparent.groupid>
+        <grandparent.artifactid>artifact-two</grandparent.artifactid>
+    </properties>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${grandparent.groupid}</groupId>
+            <artifactId>\${grandparent.artifactid}</artifactId>
+            <version>1.2</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "uses parent pom properties over grand parent pom properties for dependency if overridden"() {
+        given:
+        def grandParent = tmpDir.file("grandparent.xml") << """
+<project>
+    <groupId>different-group</groupId>
+    <artifactId>grandparent</artifactId>
+    <version>different-version</version>
+
+    <properties>
+        <my.groupid>group-three</my.groupid>
+        <my.artifactid>artifact-three</my.artifactid>
+    </properties>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>different-group</groupId>
+        <artifactId>grandparent</artifactId>
+        <version>different-version</version>
+    </parent>
+
+    <properties>
+        <my.groupid>group-two</my.groupid>
+        <my.artifactid>artifact-two</my.artifactid>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${my.groupid}</groupId>
+            <artifactId>\${my.artifactid}</artifactId>
+            <version>1.2</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(grandParent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
+    }
+
+    def "defines relocation but doesn't include any explicit dependencies or artifacts"() {
+        given:
+        def relocated = tmpDir.file("relocated.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-relocated</groupId>
+    <artifactId>relocated</artifactId>
+    <version>version-one</version>
+</project>
+"""
+
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-three</groupId>
+            <artifactId>artifact-three</artifactId>
+            <version>1.3</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <distributionManagement>
+        <relocation>
+            <groupId>group-relocated</groupId>
+            <artifactId>relocated</artifactId>
+        </relocation>
+    </distributionManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.name == 'relocated' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(relocated.toURI().toURL().toString(), new DefaultLocallyAvailableResource(relocated)) }
+
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-relocated', 'relocated', 'version-one')
+        dep.moduleConfigurations == ['default', 'master', 'compile', 'provided', 'runtime', 'system', 'sources', 'javadoc', 'optional']
+        hasDefaultDependencyArtifact(dep)
+
+        and:
+        descriptor.allArtifacts.length == 0
+    }
+
+    @Issue("GRADLE-2931")
+    def "handles dependencies with same group ID and artifact ID but different type and classifier"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.1</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>test-jar</type>
+                <version>1.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <artifactId>artifact-one</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 2
+        def depCompile = descriptor.dependencies[0]
+        depCompile.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.1')
+        depCompile.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(depCompile)
+        def depTest = descriptor.dependencies[1]
+        depTest.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.2')
+        depTest.moduleConfigurations == ['test']
+        hasDependencyArtifact(depTest, 'artifact-two', 'test-jar', 'jar', 'tests')
+    }
+
+    @Issue("GRADLE-2931")
+    def "throws exception if parent dependency management doesn't provide correct defaults for dependency"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>parent</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.1</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <artifactId>artifact-one</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        parsePom()
+
+        then:
+        Throwable t = thrown(MetaDataParseException)
+        t.cause instanceof UnresolvedDependencyVersionException
+        t.cause.message == "Unable to resolve version for dependency 'group-two:artifact-two:test-jar'"
+    }
+
+    @Issue("GRADLE-2931")
+    def "picks version of last dependency defined by artifact ID, group ID, type and classifier"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-two</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-three</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-four</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies[0]
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-four')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDependencyArtifact(dep, 'artifact-two', 'jar', 'jar', 'myjar')
+    }
+
+    @Issue("GRADLE-2931")
+    def "can declare multiple dependencies with same artifact ID and group ID but different type and classifier"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-three</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <version>version-four</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <classifier>test</classifier>
+            <version>version-five</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>ejb-client</type>
+            <classifier>client</classifier>
+            <version>version-six</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 5
+        def defDep = descriptor.dependencies[0]
+        defDep.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        defDep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(defDep)
+        def depJar = descriptor.dependencies[1]
+        depJar.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-three')
+        depJar.moduleConfigurations == ['compile', 'runtime']
+        hasDependencyArtifact(depJar, 'artifact-two', 'jar', 'jar', 'myjar')
+        def depTestJar = descriptor.dependencies[2]
+        depTestJar.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-four')
+        depTestJar.moduleConfigurations == ['compile', 'runtime']
+        hasDependencyArtifact(depTestJar, 'artifact-two', 'test-jar', 'jar', 'tests')
+        def depTestJarWithClassifier = descriptor.dependencies[3]
+        depTestJarWithClassifier.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-five')
+        depTestJarWithClassifier.moduleConfigurations == ['compile', 'runtime']
+        hasDependencyArtifact(depTestJarWithClassifier, 'artifact-two', 'test-jar', 'jar', 'test')
+        def depEjbClient = descriptor.dependencies[4]
+        depEjbClient.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-six')
+        depEjbClient.moduleConfigurations == ['compile', 'runtime']
+        hasDependencyArtifact(depEjbClient, 'artifact-two', 'ejb-client', 'ejb-client', 'client')
+    }
+
+    @Issue("GRADLE-2371")
+    def "can declare multiple dependencies with same artifact ID and group ID but different type and classifier and scope"() {
+        given:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>group-one</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-one</version>
+        </dependency>
+        <dependency>
+            <groupId>group-one</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <version>version-one</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${project.groupId}</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>\${project.groupId}</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
+"""
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 2
+        def defDep = descriptor.dependencies[0]
+        defDep.dependencyRevisionId == moduleId('group-one', 'artifact-two', 'version-one')
+        defDep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(defDep)
+        def depJar = descriptor.dependencies[1]
+        depJar.dependencyRevisionId == moduleId('group-one', 'artifact-two', 'version-one')
+        depJar.moduleConfigurations == ['test']
+        hasDependencyArtifact(depJar, 'artifact-two', 'test-jar', 'jar', 'tests')
+    }
+
+    @Issue("GRADLE-2938")
+    def "uses default dependency type if only the dependency management or dependency element declares it"() {
+        given:
+        def parent = tmpDir.file("parent.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+                <type>jar</type>
+            </dependency>
+            <dependency>
+                <groupId>group-three</groupId>
+                <artifactId>artifact-three</artifactId>
+                <version>version-three</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-one</groupId>
+        <artifactId>parent</artifactId>
+        <version>version-one</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-three</groupId>
+            <artifactId>artifact-three</artifactId>
+            <type>jar</type>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(parent.toURI().toURL().toString(), new DefaultLocallyAvailableResource(parent)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 2
+        def depGroupTwo = descriptor.dependencies[0]
+        depGroupTwo.dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        depGroupTwo.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(depGroupTwo)
+        def depGroupThree = descriptor.dependencies[1]
+        depGroupThree.dependencyRevisionId == moduleId('group-three', 'artifact-three', 'version-three')
+        depGroupThree.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(depGroupThree)
+    }
+
+    @Issue("GRADLE-2982")
+    def "use imported pom even though dependency is declared multiple times with different scopes"() {
+        given:
+        def imported = tmpDir.file("imported.xml") << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>different-group</groupId>
+    <artifactId>imported</artifactId>
+    <version>different-version</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>1.5</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>different-group</groupId>
+                <artifactId>imported</artifactId>
+                <version>different-version</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>different-group</groupId>
+                <artifactId>imported</artifactId>
+                <version>different-version</version>
+                <type>pom</type>
+                <scope>provided</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MavenPomArtifact) >> { new DefaultLocallyAvailableExternalResource(imported.toURI().toURL().toString(), new DefaultLocallyAvailableResource(imported)) }
+
+        when:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.dependencies.length == 1
+        def dep = descriptor.dependencies.first()
+        dep.dependencyRevisionId == moduleId('group-two', 'artifact-two', '1.5')
+        dep.moduleConfigurations == ['compile', 'runtime']
+        hasDefaultDependencyArtifact(dep)
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
index b8f0ba2..f4568d2 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
@@ -16,70 +16,152 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
 import org.apache.ivy.core.module.descriptor.*
-import org.apache.ivy.core.settings.IvySettings
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.GlobPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.apache.ivy.plugins.matcher.RegexpPatternMatcher
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
+import org.gradle.api.internal.artifacts.resolution.IvyDescriptorArtifact
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource
+import org.gradle.internal.resource.local.DefaultLocallyAvailableResource
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Resources
-import org.hamcrest.Matchers
 import org.junit.Rule
+import spock.lang.Issue
 import spock.lang.Specification
 
-import java.text.ParseException
-
-import static junit.framework.Assert.*
-import static org.junit.Assert.assertThat
+import static org.junit.Assert.*
 
 class IvyXmlModuleDescriptorParserTest extends Specification {
-    @Rule public final Resources resources = new Resources()
+    @Rule
+    public final Resources resources = new Resources()
     @Rule TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
 
-    IvyXmlModuleDescriptorParser parser = new IvyXmlModuleDescriptorParser()
-    IvySettings settings = new IvySettings()
+    ResolverStrategy resolverStrategy = Stub()
+    IvyXmlModuleDescriptorParser parser = new IvyXmlModuleDescriptorParser(resolverStrategy)
+    DescriptorParseContext parseContext = Mock()
 
     def setup() {
-        settings.setDefaultCache(temporaryFolder.createDir("ivy/cache"))
+        resolverStrategy.getPatternMatcher("exact") >> ExactPatternMatcher.INSTANCE
+        resolverStrategy.getPatternMatcher("glob") >> GlobPatternMatcher.INSTANCE
+        resolverStrategy.getPatternMatcher("regexp") >> RegexpPatternMatcher.INSTANCE
     }
 
-    def testEmptyDependencies() throws Exception {
+    def "parses Ivy descriptor with empty dependencies section"() throws Exception {
         when:
-        ModuleDescriptor md = parser.parseDescriptor(settings, resources.getResource("test-empty-dependencies.xml").toURI().toURL(), true)
+        def file = temporaryFolder.file("ivy.xml") << """
+<ivy-module version="1.0">
+    <info organisation="myorg"
+          module="mymodule"
+          revision="myrev"
+          status="integration"
+          publication="20041101110000"
+    />
+    <dependencies>
+    </dependencies>
+</ivy-module>
+"""
+        ModuleDescriptor md = parser.parseMetaData(parseContext, file, true).descriptor
+
         then:
         md != null
-        "myorg" == md.getModuleRevisionId().getOrganisation()
-        "mymodule" == md.getModuleRevisionId().getName()
-        "myrev" == md.getModuleRevisionId().getRevision()
-        "integration" == md.getStatus()
-        md.getConfigurations() != null
-        Arrays.asList(new Configuration("default")) == Arrays.asList(md.getConfigurations())
+        md.moduleRevisionId.organisation == "myorg"
+        md.moduleRevisionId.name == "mymodule"
+        md.moduleRevisionId.revision == "myrev"
+        md.status == "integration"
+        md.configurations*.name == ["default"]
         md.getArtifacts("default") != null
-        1 == md.getArtifacts("default").length
-        "mymodule" == md.getArtifacts("default")[0].getName()
-        "jar" == md.getArtifacts("default")[0].getType()
-        md.getDependencies() != null
-        0 == md.getDependencies().length
+        md.getArtifacts("default").length == 1
+        md.getArtifacts("default")[0].name == "mymodule"
+        md.getArtifacts("default")[0].type == "jar"
+        md.dependencies.length == 0
+        md.inheritedDescriptors.length == 0
+    }
+
+    public void "fails when configuration extends an unknown configuration"() throws IOException {
+        def file = temporaryFolder.file("ivy.xml") << """
+<ivy-module version="1.0">
+    <info organisation="myorg"
+          module="mymodule"
+          status="integration"
+    />
+    <configurations>
+        <conf name="A" extends="invalidConf"/>
+    </configurations>
+</ivy-module>
+"""
+
+        when:
+        parser.parseMetaData(parseContext, file, true)
+
+        then:
+        def e = thrown(MetaDataParseException)
+        e.message == "Could not parse Ivy file ${file.toURI()}"
+        e.cause.message == "unknown configuration 'invalidConf'. It is extended by A"
+    }
+
+    public void "fails when there is a cycle in configuration hierarchy"() throws IOException {
+        def file = temporaryFolder.file("ivy.xml") << """
+<ivy-module version="1.0">
+    <info organisation="myorg"
+          module="mymodule"
+          status="integration"
+    />
+    <configurations>
+        <conf name="A" extends="B"/>
+        <conf name="B" extends="A"/>
+    </configurations>
+</ivy-module>
+"""
+
+        when:
+        parser.parseMetaData(parseContext, file, true)
+
+        then:
+        def e = thrown(MetaDataParseException)
+        e.message == "Could not parse Ivy file ${file.toURI()}"
+        e.cause.message == "illegal cycle detected in configuration extension: A => B => A"
     }
 
-    public void testBadConfs() throws IOException {
+    public void "fails when descriptor contains badly formed XML"() {
+        def file = temporaryFolder.file("ivy.xml") << """
+<ivy-module version="1.0">
+    <info
+</ivy-module>
+"""
+
         when:
-        parser.parseDescriptor(settings, resources.getResource("test-bad-confs.xml").toURI().toURL(), true)
+        parser.parseMetaData(parseContext, file, true)
+
         then:
-        def e = thrown(ParseException)
-        assertThat(e.message, Matchers.startsWith("unknown configuration 'invalidConf'"))
+        def e = thrown(MetaDataParseException)
+        e.message == "Could not parse Ivy file ${file.toURI()}"
+        e.cause.message.contains('Element type "info"')
     }
 
-    public void testCyclicConfs() throws IOException {
+    public void "fails when descriptor does not match schema"() {
+        def file = temporaryFolder.file("ivy.xml") << """
+<ivy-module version="1.0">
+    <not-an-ivy-file/>
+</ivy-module>
+"""
+
         when:
-        parser.parseDescriptor(settings, resources.getResource("test-cyclic-confs1.xml").toURI().toURL(), true)
+        parser.parseMetaData(parseContext, file, true)
+
         then:
-        def e = thrown(ParseException)
-        assertThat(e.message, Matchers.startsWith("illegal cycle detected in configuration extension: A => B => A"))
+        def e = thrown(MetaDataParseException)
+        e.message == "Could not parse Ivy file ${file.toURI()}"
+        e.cause.message.contains('unknown tag not-an-ivy-file')
     }
 
-    public void testFull() throws Exception {
+    public void "parses a full Ivy descriptor"() throws Exception {
+        def file = temporaryFolder.file("ivy.xml")
+        file.text = resources.getResource("test-full.xml").text
+
         when:
-        ModuleDescriptor md = parser.parseDescriptor(settings, resources.getResource("test-full.xml").toURI().toURL(), false)
+        ModuleDescriptor md = parser.parseMetaData(parseContext, file, true).descriptor
+
         then:
         assertNotNull(md)
         assertEquals("myorg", md.getModuleRevisionId().getOrganisation())
@@ -133,7 +215,308 @@ class IvyXmlModuleDescriptorParserTest extends Specification {
         assertEquals(Arrays.asList("myconf1"), Arrays.asList(rules[0]
                 .getConfigurations()))
         assertEquals(Arrays.asList("myconf1", "myconf2", "myconf3", "myconf4", "myoldconf"), Arrays.asList(rules[1].getConfigurations()))
-        true
+        md.inheritedDescriptors.length == 0
+    }
+
+    def "merges values from parent Ivy descriptor"() throws Exception {
+        given:
+        def parentFile = temporaryFolder.file("parent.xml") << """
+<ivy-module version="1.0">
+    <info organisation="myorg"
+          module="parent"
+          revision="parentrev"
+          status="integration"
+          publication="20041101110000"
+    />
+    <configurations>
+        <conf name='default'/>
+    </configurations>
+    <dependencies>
+        <dependency conf="*->*" org="deporg" name="depname" rev="deprev"/>
+    </dependencies>
+</ivy-module>
+"""
+        def file = temporaryFolder.file("ivy.xml") << """
+<ivy-module version="1.0">
+    <info module="mymodule" revision="myrev">
+        <extends organisation="myorg" module="parent" revision="parentrev"/>
+    </info>
+</ivy-module>
+"""
+        and:
+        parseContext.getMetaDataArtifact(_, IvyDescriptorArtifact.class) >> new DefaultLocallyAvailableExternalResource(parentFile.toURI().toString(), new DefaultLocallyAvailableResource(parentFile))
+
+        when:
+        ModuleDescriptor md = parser.parseMetaData(parseContext, file, true).descriptor
+
+        then:
+        md != null
+        md.moduleRevisionId.organisation == "myorg"
+        md.moduleRevisionId.name == "mymodule"
+        md.moduleRevisionId.revision == "myrev"
+        md.status == "integration"
+        md.configurations*.name == ["default"]
+        md.dependencies.length == 1
+        md.dependencies[0].dependencyRevisionId.organisation == 'deporg'
+        md.dependencies[0].dependencyRevisionId.name == 'depname'
+        md.dependencies[0].dependencyRevisionId.revision == 'deprev'
+        md.inheritedDescriptors.length == 0
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2766")
+    def "defaultconfmapping is respected"() {
+        given:
+        def file = temporaryFolder.createFile("ivy.xml")
+        file.text = """
+           <ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
+                <info organisation="myorg"
+                      module="mymodule"
+                      revision="myrev"
+                      status="integration"
+                      publication="20041101110000">
+                </info>
+                <configurations>
+                    <conf name="myconf" />
+                </configurations>
+                <publications/>
+                <dependencies defaultconfmapping="myconf->default">
+                    <dependency name="mymodule2"/>
+                </dependencies>
+            </ivy-module>
+        """
+
+        when:
+        def descriptor = parser.parseMetaData(parseContext, file, false).descriptor
+        def dependency = descriptor.dependencies.first()
+
+        then:
+        dependency.moduleConfigurations == ["myconf"]
+    }
+
+    def "parses dependency config mappings"() {
+        given:
+        def file = temporaryFolder.createFile("ivy.xml")
+        file.text = """
+           <ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
+                <info organisation="myorg"
+                      module="mymodule"
+                      revision="myrev">
+                </info>
+                <configurations>
+                    <conf name="a" />
+                    <conf name="b" />
+                    <conf name="c" />
+                </configurations>
+                <publications/>
+                <dependencies>
+                    <dependency name="mymodule2" conf="a"/>
+                    <dependency name="mymodule2" conf="a->other"/>
+                    <dependency name="mymodule2" conf="*->@"/>
+                    <dependency name="mymodule2" conf="a->other;%->@"/>
+                    <dependency name="mymodule2" conf="*,!a->@"/>
+                    <dependency name="mymodule2" conf="a->*"/>
+                    <dependency name="mymodule2" conf="a->one,two;a,b->three;*->four;%->none"/>
+                    <dependency name="mymodule2" conf="a->#"/>
+                    <dependency name="mymodule2" conf="a->a;%->@"/>
+                    <dependency name="mymodule2" conf="a->a;*,!a->b"/>
+                    <dependency name="mymodule2" conf="*->*"/>
+                    <dependency name="mymodule2" conf=""/>
+                    <dependency name="mymodule2"/>
+                </dependencies>
+            </ivy-module>
+        """
+
+        when:
+        def descriptor = parser.parseMetaData(parseContext, file, false).descriptor
+
+        then:
+        def dependency1 = descriptor.dependencies[0]
+        dependency1.moduleConfigurations == ["a"]
+        dependency1.getDependencyConfigurations("a") == ["a"]
+        dependency1.getDependencyConfigurations("a", "requested") == ["a"]
+
+        def dependency2 = descriptor.dependencies[1]
+        dependency2.moduleConfigurations == ["a"]
+        dependency2.getDependencyConfigurations("a") == ["other"]
+        dependency2.getDependencyConfigurations("a", "requested") == ["other"]
+
+        def dependency3 = descriptor.dependencies[2]
+        dependency3.moduleConfigurations == ["*"]
+        dependency3.getDependencyConfigurations("a") == ["a"]
+        dependency3.getDependencyConfigurations("a", "requested") == ["a"]
+
+        def dependency4 = descriptor.dependencies[3]
+        dependency4.moduleConfigurations == ["a", "%"]
+        dependency4.getDependencyConfigurations("a") == ["other"]
+        dependency4.getDependencyConfigurations("a", "requested") == ["other"]
+        dependency4.getDependencyConfigurations("b") == ["b"]
+        dependency4.getDependencyConfigurations("b", "requested") == ["b"]
+
+        def dependency5 = descriptor.dependencies[4]
+        dependency5.moduleConfigurations == ["*", "!a"]
+        dependency5.getDependencyConfigurations("a") == ["a"]
+        dependency5.getDependencyConfigurations("a", "requested") == ["a"]
+
+        def dependency6 = descriptor.dependencies[5]
+        dependency6.moduleConfigurations == ["a"]
+        dependency6.getDependencyConfigurations("a") == ["*"]
+        dependency6.getDependencyConfigurations("a", "requested") == ["*"]
+
+        def dependency7 = descriptor.dependencies[6]
+        dependency7.moduleConfigurations == ["a", "b", "*", "%"]
+        dependency7.getDependencyConfigurations("a") == ["one", "two", "three", "four"]
+        dependency7.getDependencyConfigurations("b") == ["three", "four"]
+        dependency7.getDependencyConfigurations("c") == ["none", "four"]
+
+        def dependency8 = descriptor.dependencies[7]
+        dependency8.moduleConfigurations == ["a"]
+        dependency8.getDependencyConfigurations("a") == ["a"]
+        dependency8.getDependencyConfigurations("a", "requested") == ["requested"]
+
+        def dependency9 = descriptor.dependencies[8]
+        dependency9.moduleConfigurations == ["a", "%"]
+        dependency9.getDependencyConfigurations("a") == ["a"]
+        dependency9.getDependencyConfigurations("a", "requested") == ["a"]
+        dependency9.getDependencyConfigurations("b") == ["b"]
+        dependency9.getDependencyConfigurations("b", "requested") == ["b"]
+
+        def dependency10 = descriptor.dependencies[9]
+        dependency10.moduleConfigurations == ["a", "*", "!a"]
+        dependency10.getDependencyConfigurations("a") == ["a", "b"]
+        dependency10.getDependencyConfigurations("a", "requested") == ["a", "b"]
+        dependency10.getDependencyConfigurations("b") == ["b"]
+        dependency10.getDependencyConfigurations("b", "requested") == ["b"]
+
+        def dependency11 = descriptor.dependencies[10]
+        dependency11.moduleConfigurations == ["*"]
+        dependency11.getDependencyConfigurations("a") == ["*"]
+        dependency11.getDependencyConfigurations("a", "requested") == ["*"]
+
+        def dependency12 = descriptor.dependencies[11]
+        dependency12.moduleConfigurations == ["*"]
+        dependency12.getDependencyConfigurations("a") == ["*"]
+        dependency12.getDependencyConfigurations("a", "requested") == ["*"]
+
+        def dependency13 = descriptor.dependencies[12]
+        dependency13.moduleConfigurations == ["*"]
+        dependency13.getDependencyConfigurations("a") == ["*"]
+        dependency13.getDependencyConfigurations("a", "requested") == ["*"]
+    }
+
+    def "parses dependency config mappings with defaults"() {
+        given:
+        def file = temporaryFolder.createFile("ivy.xml")
+        file.text = """
+           <ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
+                <info organisation="myorg"
+                      module="mymodule"
+                      revision="myrev">
+                </info>
+                <configurations>
+                    <conf name="a" />
+                    <conf name="b" />
+                    <conf name="c" />
+                </configurations>
+                <publications/>
+                <dependencies defaultconf="a" defaultconfmapping="a->a1;b->b1,b2">
+                    <dependency name="mymodule2"/>
+                    <dependency name="mymodule2" conf=""/>
+                    <dependency name="mymodule2" conf="a"/>
+                    <dependency name="mymodule2" conf="b"/>
+                    <dependency name="mymodule2" conf="a->other"/>
+                    <dependency name="mymodule2" conf="*->@"/>
+                    <dependency name="mymodule2" conf="c->other"/>
+                    <dependency name="mymodule2" conf="a->"/>
+                </dependencies>
+            </ivy-module>
+        """
+
+        when:
+        def descriptor = parser.parseMetaData(parseContext, file, false).descriptor
+
+        then:
+        def dependency1 = descriptor.dependencies[0]
+        dependency1.moduleConfigurations == ["a"]
+        dependency1.getDependencyConfigurations("a") == ["a1"]
+        dependency1.getDependencyConfigurations("a", "requested") == ["a1"]
+
+        def dependency2 = descriptor.dependencies[1]
+        dependency2.moduleConfigurations == ["a"]
+        dependency2.getDependencyConfigurations("a") == ["a1"]
+        dependency2.getDependencyConfigurations("a", "requested") == ["a1"]
+
+        def dependency3 = descriptor.dependencies[2]
+        dependency3.moduleConfigurations == ["a"]
+        dependency3.getDependencyConfigurations("a") == ["a1"]
+        dependency3.getDependencyConfigurations("a", "requested") == ["a1"]
+
+        def dependency4 = descriptor.dependencies[3]
+        dependency4.moduleConfigurations == ["b"]
+        dependency4.getDependencyConfigurations("b") == ["b1", "b2"]
+        dependency4.getDependencyConfigurations("b", "requested") == ["b1", "b2"]
+
+        def dependency5 = descriptor.dependencies[4]
+        dependency5.moduleConfigurations == ["a"]
+        dependency5.getDependencyConfigurations("a") == ["other"]
+        dependency5.getDependencyConfigurations("a", "requested") == ["other"]
+
+        def dependency6 = descriptor.dependencies[5]
+        dependency6.moduleConfigurations == ["*"]
+        dependency6.getDependencyConfigurations("a") == ["a"]
+        dependency6.getDependencyConfigurations("a", "requested") == ["a"]
+
+        def dependency7 = descriptor.dependencies[6]
+        dependency7.moduleConfigurations == ["c"]
+        dependency7.getDependencyConfigurations("c") == ["other"]
+        dependency7.getDependencyConfigurations("c", "requested") == ["other"]
+
+        def dependency8 = descriptor.dependencies[7]
+        dependency8.moduleConfigurations == ["a"]
+        dependency8.getDependencyConfigurations("a") == ["a1"]
+        dependency8.getDependencyConfigurations("a", "requested") == ["a1"]
+    }
+
+    def "parses artifact config mappings"() {
+        given:
+        def file = temporaryFolder.createFile("ivy.xml")
+        file.text = """
+           <ivy-module version="2.0">
+                <info organisation="myorg"
+                      module="mymodule"
+                      revision="myrev">
+                </info>
+                <configurations>
+                    <conf name="a" />
+                    <conf name="b" />
+                    <conf name="c" extends="a"/>
+                    <conf name="d" />
+                </configurations>
+                <publications>
+                    <artifact/>
+                    <artifact name='art2' type='type' ext='ext' conf='*'/>
+                    <artifact name='art3' type='type2' conf='a, b'/>
+                </publications>
+            </ivy-module>
+        """
+
+        when:
+        def descriptor = parser.parseMetaData(parseContext, file, false).descriptor
+
+        then:
+        descriptor.allArtifacts.length == 3
+        descriptor.allArtifacts[0].configurations == ['a', 'b', 'c', 'd']
+        descriptor.allArtifacts[1].configurations == ['a', 'b', 'c', 'd']
+        descriptor.allArtifacts[2].configurations == ['a', 'b']
+
+        and:
+        descriptor.getArtifacts("a")*.name == ['mymodule', 'art2', 'art3']
+        descriptor.getArtifacts("a")*.type == ['jar', 'type', 'type2']
+        descriptor.getArtifacts("a")*.ext == ['jar', 'ext', 'type2']
+
+        and:
+        descriptor.getArtifacts("b")*.name == ['mymodule', 'art2', 'art3']
+        descriptor.getArtifacts("c")*.name == ['mymodule', 'art2']
+        descriptor.getArtifacts("d")*.name == ['mymodule', 'art2']
     }
 
     def verifyFullDependencies(DependencyDescriptor[] dependencies) {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReaderProfileTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReaderProfileTest.groovy
new file mode 100644
index 0000000..f040a30
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReaderProfileTest.groovy
@@ -0,0 +1,1566 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.PomProfile
+
+class PomReaderProfileTest extends AbstractPomReaderTest {
+    def "parse POM without active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation/>
+        </profile>
+        <profile>
+            <id>profile-3</id>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.parseActivePomProfiles().size() == 0
+    }
+
+    def "parse POM with multiple active profiles having the same ID"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.parseActivePomProfiles().size() == 4
+    }
+
+    def "parse POM with a mix of active and inactive profiles"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <properties>
+                <prop1>myproperty1</prop1>
+            </properties>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-two</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <prop2>myproperty2</prop2>
+            </properties>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-three</groupId>
+                        <artifactId>artifact-three</artifactId>
+                        <version>version-three</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+        <profile>
+            <id>profile-3</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <properties>
+                <prop3>myproperty3</prop3>
+            </properties>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-four</groupId>
+                        <artifactId>artifact-four</artifactId>
+                        <version>version-four</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.parseActivePomProfiles().size() == 1
+        pomReader.properties.size() == 5
+        !pomReader.properties.containsKey('prop1')
+        !pomReader.properties.containsKey('prop3')
+        pomReader.properties['prop2'] == 'myproperty2'
+        !pomReader.dependencyMgt.containsKey(new MavenDependencyKey('group-two', 'artifact-two', 'jar', null))
+        !pomReader.dependencyMgt.containsKey(new MavenDependencyKey('group-four', 'artifact-four', 'jar', null))
+        pomReader.dependencyMgt.containsKey(new MavenDependencyKey('group-three', 'artifact-three', 'jar', null))
+    }
+
+    def "cannot use POM property to set profile ID"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <properties>
+        <activate.id>profile-1</activate.id>
+    </properties>
+    <profiles>
+        <profile>
+            <id>\${activate.id}</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        List<PomProfile> activePomProfiles = pomReader.parseActivePomProfiles()
+        activePomProfiles.size() == 1
+        activePomProfiles[0].id == '${activate.id}'
+    }
+
+    def "cannot use POM property to control profile activation"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <properties>
+        <activate.profile>true</activate.profile>
+    </properties>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>\${activate.profile}</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        List<PomProfile> activePomProfiles = pomReader.parseActivePomProfiles()
+        activePomProfiles.size() == 0
+        !pomReader.properties.containsKey('groupId.prop')
+        !pomReader.properties.containsKey('artifactId.prop')
+        !pomReader.properties.containsKey('version.prop')
+    }
+
+    def "parse POM with active profile providing properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        List<PomProfile> activePomProfiles = pomReader.parseActivePomProfiles()
+        activePomProfiles.size() == 1
+        PomProfile pomProfile = activePomProfiles[0]
+        pomProfile
+        pomProfile.id == 'profile-1'
+        pomProfile.properties.size() == 3
+        pomProfile.properties['groupId.prop'] == 'group-two'
+        pomProfile.properties['artifactId.prop'] == 'artifact-two'
+        pomProfile.properties['version.prop'] == 'version-two'
+        pomReader.properties.size() == 7
+        pomReader.properties['groupId.prop'] == 'group-two'
+        pomReader.properties['artifactId.prop'] == 'artifact-two'
+        pomReader.properties['version.prop'] == 'version-two'
+    }
+
+    def "parse POM with multiple active profile providing same properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.parseActivePomProfiles().size() == 2
+        pomReader.properties.size() == 7
+        pomReader.properties['groupId.prop'] == 'group-two'
+        pomReader.properties['artifactId.prop'] == 'artifact-two'
+        pomReader.properties['version.prop'] == 'version-two'
+    }
+
+    def "get dependencies with custom properties of active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-two')
+    }
+
+    def "custom properties from last active profile determines dependency coordinates"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-three</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-three')
+    }
+
+    def "properties from an active profile override existing POM properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <properties>
+        <groupId.prop>group-two</groupId.prop>
+        <artifactId.prop>artifact-two</artifactId.prop>
+        <version.prop>version-two</version.prop>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-three</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-three')
+    }
+
+    def "multiple active profiles can determine different dependency coordinates"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+        <dependency>
+            <groupId>\${otherGroupId.prop}</groupId>
+            <artifactId>\${otherArtifactId.prop}</artifactId>
+            <version>\${otherVersion.prop}</version>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <otherGroupId.prop>group-three</otherGroupId.prop>
+                <otherArtifactId.prop>artifact-three</otherArtifactId.prop>
+                <otherVersion.prop>version-three</otherVersion.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyDep1 = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyDep2 = new MavenDependencyKey('group-three', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 2
+        assertResolvedPomDependency(keyDep1, 'version-two')
+        assertResolvedPomDependency(keyDep2, 'version-three')
+    }
+
+    def "get dependencies management without properties from active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-two</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        List<PomProfile> activePomProfiles = pomReader.parseActivePomProfiles()
+        activePomProfiles.size() == 1
+        activePomProfiles[0].getDependencyMgts().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+    }
+
+    def "get dependencies management with properties from active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>\${groupId.prop}</groupId>
+                        <artifactId>\${artifactId.prop}</artifactId>
+                        <version>\${version.prop}</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        List<PomProfile> activePomProfiles = pomReader.parseActivePomProfiles()
+        activePomProfiles.size() == 1
+        activePomProfiles[0].getDependencyMgts().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+    }
+
+    def "finds dependency default with properties defined in main body of POM"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <properties>
+        <groupId.prop>group-two</groupId.prop>
+        <artifactId.prop>artifact-two</artifactId.prop>
+        <version.prop>version-two</version.prop>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>\${groupId.prop}</groupId>
+                        <artifactId>\${artifactId.prop}</artifactId>
+                        <version>\${version.prop}</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+        pomReader.findDependencyDefaults(key) == pomReader.dependencyMgt[key]
+    }
+
+    def "finds dependency default if declared in active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-three</artifactId>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-two</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-two', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-two')
+        pomReader.findDependencyDefaults(keyGroupTwo) == pomReader.dependencyMgt[keyGroupTwo]
+        !pomReader.findDependencyDefaults(keyGroupThree)
+    }
+
+    def "uses dependency default from active profile over POM dependency default"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-three</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-three')
+        pomReader.findDependencyDefaults(keyGroupTwo) == pomReader.dependencyMgt[keyGroupTwo]
+    }
+
+    def "finds dependency default if declared in multiple active profiles"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-three</artifactId>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-two</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-three</artifactId>
+                        <version>version-three</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-two', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 2
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-two')
+        pomReader.findDependencyDefaults(keyGroupTwo) == pomReader.dependencyMgt[keyGroupTwo]
+        assertResolvedPomDependencyManagement(keyGroupThree, 'version-three')
+        pomReader.findDependencyDefaults(keyGroupThree) == pomReader.dependencyMgt[keyGroupThree]
+    }
+
+    def "finds dependency default if declared in parent POM active profile"() {
+        when:
+        String parentPom = """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-two</groupId>
+    <artifactId>artifact-two</artifactId>
+    <version>version-two</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-two</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+        </profile>
+    </profiles>
+</project>
+"""
+        PomReader parentPomReader = createPomReader('parent-pom.xml', parentPom)
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-four</groupId>
+            <artifactId>artifact-four</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-five</groupId>
+            <artifactId>artifact-five</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.setPomParent(parentPomReader)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-two', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-two')
+        pomReader.findDependencyDefaults(keyGroupTwo) == pomReader.dependencyMgt[keyGroupTwo]
+        !pomReader.findDependencyDefaults(keyGroupThree)
+    }
+
+    def "uses child properties over parent properties if defined in active profile with the different values"() {
+        when:
+        String parentPom = """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-two</groupId>
+    <artifactId>artifact-two</artifactId>
+    <version>version-two</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-three</groupId.prop>
+                <artifactId.prop>artifact-three</artifactId.prop>
+                <version.prop>version-three</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+        PomReader parentPomReader = createPomReader('parent-pom.xml', parentPom)
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-four</groupId.prop>
+                <artifactId.prop>artifact-four</artifactId.prop>
+                <version.prop>version-four</version.prop>
+            </properties>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.setPomParent(parentPomReader)
+        MavenDependencyKey key = new MavenDependencyKey('group-four', 'artifact-four', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-four')
+    }
+
+    def "gets dependencies declared in a single active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+                <dependency>
+                    <groupId>group-three</groupId>
+                    <artifactId>artifact-three</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyArtifactTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyArtifactThree = new MavenDependencyKey('group-three', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 2
+        assertResolvedPomDependency(keyArtifactTwo, 'version-two')
+        assertResolvedPomDependency(keyArtifactThree, 'version-three')
+    }
+
+    def "uses last declaration of dependency with same groupId and artifactId in a single active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-three')
+    }
+
+    def "gets dependencies declared in a multiple active profiles"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-three</groupId>
+                    <artifactId>artifact-three</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>profile-3</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-four</groupId>
+                    <artifactId>artifact-four</artifactId>
+                    <version>version-four</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyArtifactTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyArtifactThree = new MavenDependencyKey('group-three', 'artifact-three', 'jar', null)
+        MavenDependencyKey keyArtifactFour = new MavenDependencyKey('group-four', 'artifact-four', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 3
+        assertResolvedPomDependency(keyArtifactTwo, 'version-two')
+        assertResolvedPomDependency(keyArtifactThree, 'version-three')
+        assertResolvedPomDependency(keyArtifactFour, 'version-four')
+    }
+
+    def "uses last declaration of dependency with same groupId and artifactId in multiple active profiles"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-three')
+    }
+
+    def "gets dependency with provided properties declared in active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <groupId.prop>group-two</groupId.prop>
+                <artifactId.prop>artifact-two</artifactId.prop>
+                <version.prop>version-two</version.prop>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>\${groupId.prop}</groupId>
+                    <artifactId>\${artifactId.prop}</artifactId>
+                    <version>\${version.prop}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-two')
+    }
+
+    def "gets dependency in active profile with provided properties in POM main body"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <properties>
+        <groupId.prop>group-two</groupId.prop>
+        <artifactId.prop>artifact-two</artifactId.prop>
+        <version.prop>version-two</version.prop>
+    </properties>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>\${groupId.prop}</groupId>
+                    <artifactId>\${artifactId.prop}</artifactId>
+                    <version>\${version.prop}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-two')
+    }
+
+    def "gets dependency with provided defaults declared in active profile"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencyManagement>
+                <dependencies>
+                    <dependency>
+                        <groupId>group-two</groupId>
+                        <artifactId>artifact-two</artifactId>
+                        <version>version-two</version>
+                    </dependency>
+                </dependencies>
+            </dependencyManagement>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+        pomReader.findDependencyDefaults(key) == pomReader.dependencyMgt[key]
+    }
+
+    def "gets dependency with provided defaults declared in POM main body"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+        pomReader.findDependencyDefaults(key) == pomReader.dependencyMgt[key]
+    }
+
+    def "uses active profile dependency over dependency with same groupId and artifactId declared in POM main body"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+
+    <dependency>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </dependency>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-three')
+    }
+
+    def "uses dependency if declared in parent POM active profile"() {
+        when:
+        String parentPom = """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-two</groupId>
+    <artifactId>artifact-two</artifactId>
+    <version>version-two</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+        PomReader parentPomReader = createPomReader('parent-pom.xml', parentPom)
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.setPomParent(parentPomReader)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-two')
+    }
+
+    def "uses dependency if declared in parent and child POM active profile"() {
+        when:
+        String parentPom = """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-two</groupId>
+    <artifactId>artifact-two</artifactId>
+    <version>version-two</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+        PomReader parentPomReader = createPomReader('parent-pom.xml', parentPom)
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+
+    <profiles>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-three</groupId>
+                    <artifactId>artifact-three</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.setPomParent(parentPomReader)
+        MavenDependencyKey keyArtifactTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyArtifactThree = new MavenDependencyKey('group-three', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 2
+        assertResolvedPomDependency(keyArtifactTwo, 'version-two')
+        assertResolvedPomDependency(keyArtifactThree, 'version-three')
+    }
+
+    def "uses child dependency over parent dependency in POM active profile"() {
+        when:
+        String parentPom = """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-two</groupId>
+    <artifactId>artifact-two</artifactId>
+    <version>version-two</version>
+
+    <profiles>
+        <profile>
+            <id>profile-1</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-two</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+        PomReader parentPomReader = createPomReader('parent-pom.xml', parentPom)
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+
+    <profiles>
+        <profile>
+            <id>profile-2</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>group-two</groupId>
+                    <artifactId>artifact-two</artifactId>
+                    <version>version-three</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.setPomParent(parentPomReader)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-three')
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReaderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReaderTest.groovy
new file mode 100644
index 0000000..0cbe355
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomReaderTest.groovy
@@ -0,0 +1,818 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.apache.ivy.core.module.descriptor.License
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data.MavenDependencyKey
+import org.xml.sax.SAXParseException
+import spock.lang.Issue
+
+class PomReaderTest extends AbstractPomReaderTest {
+    def "parse POM with invalid XML"() {
+        when:
+        pomFile << """
+<projectx>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <description>The first test artifact</description>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        thrown(MetaDataParseException)
+    }
+
+    def "parse malformed POM"() {
+        when:
+        pomFile << """
+<someothertag>
+    <project>
+        <modelVersion>4.0.0</modelVersion>
+        <groupId>group-one</groupId>
+        <artifactId>artifact-one</artifactId>
+        <version>version-one</version>
+        <name>Test Artifact One</name>
+        <description>The first test artifact</description>
+    </project>
+</someothertag>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        Throwable t = thrown(SAXParseException)
+        t.message == 'project must be the root tag'
+    }
+
+    def "parse simple POM"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <description>The first test artifact</description>
+    <url>http://www.myproject.com</url>
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.description == 'The first test artifact'
+        pomReader.homePage == 'http://www.myproject.com'
+        pomReader.packaging == 'jar'
+        pomReader.licenses.size() == 1
+        pomReader.licenses[0].name == 'The Apache Software License, Version 2.0'
+        pomReader.licenses[0].url == 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 0
+        pomReader.properties.size() == 4
+        pomReader.properties['parent.version'] == 'version-one'
+        pomReader.properties['parent.groupId'] == 'group-one'
+        pomReader.properties['project.parent.version'] == 'version-one'
+        pomReader.properties['project.parent.groupId'] == 'group-one'
+        pomReader.relocation == null
+    }
+
+    def "use custom properties in POM project coordinates"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>\${groupId.prop}</groupId>
+    <artifactId>\${artifactId.prop}</artifactId>
+    <version>\${version.prop}</version>
+    <name>Test Artifact One</name>
+    <description>The first test artifact</description>
+    <properties>
+        <some.prop1>test1</some.prop1>
+        <some.prop2>test2</some.prop2>
+        <groupId.prop>group-one</groupId.prop>
+        <artifactId.prop>artifact-one</artifactId.prop>
+        <version.prop>version-one</version.prop>
+    </properties>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.description == 'The first test artifact'
+        pomReader.packaging == 'jar'
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 5
+        pomReader.pomProperties.containsKey('some.prop1')
+        pomReader.pomProperties.containsKey('some.prop2')
+        pomReader.properties.size() == 9
+        pomReader.properties.containsKey('some.prop1')
+        pomReader.properties.containsKey('some.prop2')
+    }
+
+    def "get dependencies without custom properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-two')
+    }
+
+    def "get dependencies with custom properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <properties>
+        <groupId.prop>group-two</groupId.prop>
+        <artifactId.prop>artifact-two</artifactId.prop>
+        <version.prop>version-two</version.prop>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>\${groupId.prop}</groupId>
+            <artifactId>\${artifactId.prop}</artifactId>
+            <version>\${version.prop}</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-two')
+    }
+
+    @Issue("GRADLE-2931")
+    def "picks version of last dependency defined by artifact ID, group ID, type and classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-two</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-three</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-four</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', 'myjar')
+
+        then:
+        pomReader.getDependencies().size() == 1
+        assertResolvedPomDependency(key, 'version-four')
+    }
+
+    @Issue("GRADLE-2931")
+    def "can declare multiple dependencies with same artifact ID and group ID but different type and classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>jar</type>
+            <classifier>myjar</classifier>
+            <version>version-two</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>test-jar</type>
+            <classifier>test</classifier>
+            <version>version-three</version>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <type>ejb-client</type>
+            <classifier>client</classifier>
+            <version>version-four</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey dependencyJarkey = new MavenDependencyKey('group-two', 'artifact-two', 'jar', 'myjar')
+        MavenDependencyKey dependencyTestJarkey = new MavenDependencyKey('group-two', 'artifact-two', 'test-jar', 'test')
+        MavenDependencyKey dependencyEjbClientKey = new MavenDependencyKey('group-two', 'artifact-two', 'ejb-client', 'client')
+
+        then:
+        pomReader.getDependencies().size() == 3
+        assertResolvedPomDependency(dependencyJarkey, 'version-two')
+        assertResolvedPomDependency(dependencyTestJarkey, 'version-three')
+        assertResolvedPomDependency(dependencyEjbClientKey, 'version-four')
+    }
+
+    def "get dependencies management without custom properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+    }
+
+    def "get dependencies management with custom properties"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <properties>
+        <groupId.prop>group-two</groupId.prop>
+        <artifactId.prop>artifact-two</artifactId.prop>
+        <version.prop>version-two</version.prop>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>\${groupId.prop}</groupId>
+                <artifactId>\${artifactId.prop}</artifactId>
+                <version>\${version.prop}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-two')
+    }
+
+    def "picks version of last dependency management defined by artifact ID, group ID, type and classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>jar</type>
+                <classifier>myjar</classifier>
+                <version>version-two</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>jar</type>
+                <classifier>myjar</classifier>
+                <version>version-three</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>jar</type>
+                <classifier>myjar</classifier>
+                <version>version-four</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey key = new MavenDependencyKey('group-two', 'artifact-two', 'jar', 'myjar')
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(key, 'version-four')
+    }
+
+    def "can declare multiple dependency managements with same artifact ID and group ID but different type and classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>jar</type>
+                <classifier>myjar</classifier>
+                <version>version-two</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>test-jar</type>
+                <classifier>test</classifier>
+                <version>version-three</version>
+            </dependency>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <type>ejb-client</type>
+                <classifier>client</classifier>
+                <version>version-four</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey dependencyJarkey = new MavenDependencyKey('group-two', 'artifact-two', 'jar', 'myjar')
+        MavenDependencyKey dependencyTestJarkey = new MavenDependencyKey('group-two', 'artifact-two', 'test-jar', 'test')
+        MavenDependencyKey dependencyEjbClientKey = new MavenDependencyKey('group-two', 'artifact-two', 'ejb-client', 'client')
+
+        then:
+        pomReader.getDependencyMgt().size() == 3
+        assertResolvedPomDependencyManagement(dependencyJarkey, 'version-two')
+        assertResolvedPomDependencyManagement(dependencyTestJarkey, 'version-three')
+        assertResolvedPomDependencyManagement(dependencyEjbClientKey, 'version-four')
+    }
+
+    def "parse POM with parent POM"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>artifact-one</artifactId>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-two'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-two'
+        pomReader.parentGroupId == 'group-two'
+        pomReader.parentArtifactId == 'artifact-two'
+        pomReader.parentVersion == 'version-two'
+    }
+
+    def "Parse minimal POM and resolve GAV"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.resolveGAV()
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.packaging == 'jar'
+        pomReader.homePage == ''
+        pomReader.description == ''
+        pomReader.licenses == new License[0]
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 0
+        pomReader.properties.size() == 13
+        pomReader.properties['parent.version'] == 'version-one'
+        pomReader.properties['parent.groupId'] == 'group-one'
+        pomReader.properties['project.parent.version'] == 'version-one'
+        pomReader.properties['project.parent.groupId'] == 'group-one'
+        pomReader.properties['project.groupId'] == 'group-one'
+        pomReader.properties['pom.groupId'] == 'group-one'
+        pomReader.properties['groupId'] == 'group-one'
+        pomReader.properties['project.artifactId'] == 'artifact-one'
+        pomReader.properties['pom.artifactId'] == 'artifact-one'
+        pomReader.properties['artifactId'] == 'artifact-one'
+        pomReader.properties['project.version'] == 'version-one'
+        pomReader.properties['pom.version'] == 'version-one'
+        pomReader.properties['version'] == 'version-one'
+    }
+
+    def "Parse relocated POM without provided coordinates"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <distributionManagement>
+        <relocation>
+        </relocation>
+    </distributionManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.packaging == 'jar'
+        pomReader.homePage == ''
+        pomReader.description == ''
+        pomReader.licenses == new License[0]
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 0
+        pomReader.relocation != null
+        pomReader.relocation == IvyUtil.createModuleRevisionId('group-one', 'artifact-one', 'version-one')
+    }
+
+    def "Parse relocated POM with provided group ID"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <distributionManagement>
+        <relocation>
+            <groupId>group-two</groupId>
+        </relocation>
+    </distributionManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.packaging == 'jar'
+        pomReader.homePage == ''
+        pomReader.description == ''
+        pomReader.licenses == new License[0]
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 0
+        pomReader.relocation != null
+        pomReader.relocation == IvyUtil.createModuleRevisionId('group-two', 'artifact-one', 'version-one')
+    }
+
+    def "Parse relocated POM with provided group ID and artifact ID"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <distributionManagement>
+        <relocation>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </relocation>
+    </distributionManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.packaging == 'jar'
+        pomReader.homePage == ''
+        pomReader.description == ''
+        pomReader.licenses == new License[0]
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 0
+        pomReader.relocation != null
+        pomReader.relocation == IvyUtil.createModuleRevisionId('group-two', 'artifact-two', 'version-one')
+    }
+
+    def "Parse relocated POM with all provided coordinates"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <distributionManagement>
+        <relocation>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+        </relocation>
+    </distributionManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+
+        then:
+        pomReader.groupId == 'group-one'
+        pomReader.artifactId == 'artifact-one'
+        pomReader.version == 'version-one'
+        pomReader.packaging == 'jar'
+        pomReader.homePage == ''
+        pomReader.description == ''
+        pomReader.licenses == new License[0]
+        !pomReader.hasParent()
+        pomReader.pomProperties.size() == 0
+        pomReader.relocation != null
+        pomReader.relocation == IvyUtil.createModuleRevisionId('group-two', 'artifact-two', 'version-two')
+    }
+
+    @Issue("GRADLE-2938")
+    def "uses default type for dependency if not declared"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+        </dependency>
+        <dependency>
+            <groupId>group-three</groupId>
+            <artifactId>artifact-three</artifactId>
+            <version>version-three</version>
+            <type>jar</type>
+        </dependency>
+        <dependency>
+            <groupId>group-four</groupId>
+            <artifactId>artifact-four</artifactId>
+            <version>version-four</version>
+            <type>ejb-client</type>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-three', 'artifact-three', 'jar', null)
+        MavenDependencyKey keyGroupFour = new MavenDependencyKey('group-four', 'artifact-four', 'ejb-client', null)
+
+        then:
+        pomReader.getDependencies().size() == 3
+        assertResolvedPomDependency(keyGroupTwo, 'version-two')
+        assertResolvedPomDependency(keyGroupThree, 'version-three')
+        assertResolvedPomDependency(keyGroupFour, 'version-four')
+    }
+
+    @Issue("GRADLE-2938")
+    def "uses default type for dependency management if not declared"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+            </dependency>
+            <dependency>
+                <groupId>group-three</groupId>
+                <artifactId>artifact-three</artifactId>
+                <version>version-three</version>
+                <type>jar</type>
+            </dependency>
+            <dependency>
+                <groupId>group-four</groupId>
+                <artifactId>artifact-four</artifactId>
+                <version>version-four</version>
+                <type>ejb-client</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-three', 'artifact-three', 'jar', null)
+        MavenDependencyKey keyGroupFour = new MavenDependencyKey('group-four', 'artifact-four', 'ejb-client', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 3
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-two')
+        assertResolvedPomDependencyManagement(keyGroupThree, 'version-three')
+        assertResolvedPomDependencyManagement(keyGroupFour, 'version-four')
+    }
+
+    def "finds dependency default if declared in same POM"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-three</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-two', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-two')
+        pomReader.findDependencyDefaults(keyGroupTwo) == pomReader.dependencyMgt[keyGroupTwo]
+        !pomReader.findDependencyDefaults(keyGroupThree)
+    }
+
+    def "finds dependency default if declared in parent POM"() {
+        when:
+        String parentPom = """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-two</groupId>
+    <artifactId>artifact-two</artifactId>
+    <version>version-two</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>group-two</groupId>
+                <artifactId>artifact-two</artifactId>
+                <version>version-two</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
+"""
+        PomReader parentPomReader = createPomReader('parent-pom.xml', parentPom)
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <parent>
+        <groupId>group-two</groupId>
+        <artifactId>artifact-two</artifactId>
+        <version>version-two</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-four</groupId>
+            <artifactId>artifact-four</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>group-five</groupId>
+            <artifactId>artifact-five</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        pomReader = new PomReader(locallyAvailableExternalResource)
+        pomReader.setPomParent(parentPomReader)
+        MavenDependencyKey keyGroupTwo = new MavenDependencyKey('group-two', 'artifact-two', 'jar', null)
+        MavenDependencyKey keyGroupThree = new MavenDependencyKey('group-two', 'artifact-three', 'jar', null)
+
+        then:
+        pomReader.getDependencyMgt().size() == 1
+        assertResolvedPomDependencyManagement(keyGroupTwo, 'version-two')
+        pomReader.findDependencyDefaults(keyGroupTwo) == pomReader.dependencyMgt[keyGroupTwo]
+        !pomReader.findDependencyDefaults(keyGroupThree)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/MavenDependencyKeyTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/MavenDependencyKeyTest.groovy
new file mode 100644
index 0000000..f159df6
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/data/MavenDependencyKeyTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.data
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class MavenDependencyKeyTest extends Specification {
+    @Unroll
+    def "can compare with other instance (#groupId, #artifactId, #type, #classifier)"() {
+        expect:
+        MavenDependencyKey key1 = new MavenDependencyKey('group-one', 'artifact-one', 'jar', 'sources')
+        MavenDependencyKey key2 = new MavenDependencyKey(groupId, artifactId, type, classifier)
+        strictlyEquals(key1, key2) == equality
+        (key1.hashCode() == key2.hashCode()) == hashCode
+        (key1.toString() == key2.toString()) == stringRepresentation
+
+        where:
+        groupId     | artifactId     | type  | classifier | equality | hashCode | stringRepresentation
+        'group-one' | 'artifact-one' | 'jar' | null       | false    | false    | false
+        'group-one' | 'artifact-one' | 'jar' | 'sources'  | true     | true     | true
+    }
+
+    @Unroll
+    def "builds String representation (#groupId, #artifactId, #type, #classifier)"() {
+        expect:
+        MavenDependencyKey key = new MavenDependencyKey(groupId, artifactId, type, classifier)
+        key.toString() == stringRepresentation
+
+        where:
+        groupId     | artifactId     | type  | classifier | stringRepresentation
+        'group-one' | 'artifact-one' | 'jar' | null       | 'group-one:artifact-one:jar'
+        'group-one' | 'artifact-one' | 'jar' | 'sources'  | 'group-one:artifact-one:jar:sources'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ChainVersionMatcherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ChainVersionMatcherTest.groovy
new file mode 100644
index 0000000..bfb45ff
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ChainVersionMatcherTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+import spock.lang.Specification
+
+class ChainVersionMatcherTest extends Specification {
+    def chain = new ChainVersionMatcher()
+    def matcher1 = Mock(VersionMatcher)
+    def matcher2 = Mock(VersionMatcher)
+    def matcher3 = Mock(VersionMatcher)
+
+    def setup() {
+        chain.add(matcher1)
+        chain.add(matcher2)
+        chain.add(matcher3)
+    }
+
+    def "doesn't support canHandle method (no known use case)"() {
+        when:
+        chain.canHandle("1")
+
+        then:
+        UnsupportedOperationException e = thrown()
+        e.message.contains("canHandle")
+    }
+
+    def "delegates isDynamic to first matcher that can handle the selector"() {
+        when:
+        def result = chain.isDynamic("1+")
+
+        then:
+        1 * matcher1.canHandle("1+") >> false
+        1 * matcher2.canHandle("1+") >> true
+        1 * matcher2.isDynamic("1+") >> true
+        0 * _
+
+        and:
+        result
+    }
+
+    def "delegates needModuleMetadata to first matcher that can handle the selector"() {
+        when:
+        def result = chain.needModuleMetadata("1+")
+
+        then:
+        1 * matcher1.canHandle("1+") >> false
+        1 * matcher2.canHandle("1+") >> true
+        1 * matcher2.needModuleMetadata("1+") >> false
+        0 * _
+
+        and:
+        !result
+    }
+
+    def "delegates accept to first matcher that can handle the selector"() {
+        when:
+        def result = chain.accept("1+", "2")
+
+        then:
+        1 * matcher1.canHandle("1+") >> false
+        1 * matcher2.canHandle("1+") >> true
+        1 * matcher2.accept("1+", "2") >> false
+        0 * _
+
+        and:
+        !result
+    }
+
+    def "delegates metadata-aware accept to first matcher that can handle the selector"() {
+        def metadata = Stub(ModuleVersionMetaData) {
+            getId() >> Stub(ModuleVersionIdentifier) {
+                getVersion() >> "2"
+            }
+        }
+
+        when:
+        def result = chain.accept("1+", metadata)
+
+        then:
+        1 * matcher1.canHandle("1+") >> false
+        1 * matcher2.canHandle("1+") >> true
+        1 * matcher2.accept("1+", metadata) >> false
+        0 * _
+
+        and:
+        !result
+    }
+
+    def "delegates compare to first matcher that can handle the selector"() {
+        def comparator = Stub(Comparator)
+
+        when:
+        def result = chain.compare("1+", "2")
+
+        then:
+        1 * matcher1.canHandle("1+") >> false
+        1 * matcher2.canHandle("1+") >> true
+        1 * matcher2.compare("1+", "2") >> -3
+        0 * _
+
+        and:
+        result == -3
+    }
+
+    def "complains if a version selector isn't matched by any matcher"() {
+        when:
+        chain.accept("1+", "1")
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Invalid version selector: 1+"
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ExactVersionMatcherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ExactVersionMatcherTest.groovy
new file mode 100644
index 0000000..b486d71
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/ExactVersionMatcherTest.groovy
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+
+import spock.lang.Specification
+
+class ExactVersionMatcherTest extends Specification {
+    def matcher = new ExactVersionMatcher()
+
+    def "can handle any version selector"() {
+        expect:
+        matcher.canHandle("1.0")
+        matcher.canHandle("[1.0,2.0]")
+        matcher.canHandle("!@#%")
+    }
+
+    def "considers selector as static"() {
+        expect:
+        !matcher.isDynamic("1.0")
+        !matcher.isDynamic("[1.0,2.0]")
+    }
+
+    def "doesn't need metadata"() {
+        expect:
+        !matcher.needModuleMetadata("1.0")
+        !matcher.needModuleMetadata("[1.0,2.0]")
+    }
+
+    def "accepts candidate version iff it literally matches the selector"() {
+        expect:
+        matcher.accept("", "")
+        matcher.accept("1.0", "1.0")
+        matcher.accept("2.0", "2.0")
+        matcher.accept("!@#%", "!@#%")
+        matcher.accept("hey joe", "hey joe")
+        !matcher.accept("1.0", "1.1")
+        !matcher.accept("2.0", "3.0")
+        !matcher.accept("!@#%", "%#@!")
+        !matcher.accept("hey joe", "hoe hey")
+    }
+
+    def "does not accept candidate version that differs in separator"() {
+        expect:
+        !matcher.accept("1.0", "1_0")
+        !matcher.accept("1_0", "1-0")
+        !matcher.accept("1-0", "1+0")
+        !matcher.accept("1.a.2", "1a2")
+    }
+
+    def "does not accept candidate version that has different number of trailing .0's"() {
+        expect:
+        !matcher.accept("1.0.0", "1.0")
+        !matcher.accept("1", "1.0.0")
+    }
+
+    def "does not accept candidate version that has different capitalization"() {
+        !matcher.accept("1.0-alpha", "1.0-ALPHA")
+    }
+
+    def "supports metadata-aware accept method (with same result)"() {
+        def metadata = Stub(ModuleVersionMetaData) {
+            getId() >> Stub(ModuleVersionIdentifier) {
+                getVersion() >> metadataVersion
+            }
+        }
+
+        expect:
+        matcher.accept("1.0", metadata) == result
+
+        where:
+        metadataVersion | result
+        "1.0"           | true
+        "2.0"           | false
+    }
+
+    def "compares versions lexicographically"() {
+        expect:
+        matcher.compare(v1, v2) < 0
+        matcher.compare(v2, v1) > 0
+        matcher.compare(v1, v1) == 0
+        matcher.compare(v2, v2) == 0
+
+        where:
+        v1          | v2
+        "1.0"       | "2.0"
+        "1.0"       | "1.1"
+        "1.0.1"     | "1.1.0"
+        "1.2"       | "1.2.3"
+        "1.0-1"     | "1.0-2"
+        "1.0.a"     | "1.0.b"
+        "1.0-alpha" | "1.0"
+        "1.0-alpha" | "1.0-beta"
+        "1.0.alpha" | "1.0.b"
+    }
+
+    def "gives some special treatment to 'dev', 'rc', and 'final' qualifiers"() {
+        expect:
+        matcher.compare(v1, v2) < 0
+        matcher.compare(v2, v1) > 0
+        matcher.compare(v1, v1) == 0
+        matcher.compare(v2, v2) == 0
+
+        where:
+        v1          | v2
+        "1.0-dev-1" | "1.0"
+        "1.0-dev-1" | "1.0-dev-2"
+        "1.0-rc-1"  | "1.0"
+        "1.0-rc-1"  | "1.0-rc-2"
+        "1.0-final" | "1.0"
+        "1.0-dev-1" | "1.0-rc-1"
+        "1.0-rc-1"  | "1.0-final"
+        "1.0-dev-1" | "1.0-final"
+    }
+
+    def "compares identical versions equal"() {
+        expect:
+        matcher.compare(v1, v2) == 0
+        matcher.compare(v2, v1) == 0
+
+        where:
+        v1        | v2
+        ""        | ""
+        "1"       | "1"
+        "1.0.0"   | "1.0.0"
+        "!@#%"    | "!@#%"
+        "hey joe" | "hey joe"
+    }
+
+    def "compares versions that differ only in separators equal"() {
+        expect:
+        matcher.compare("1.0", "1_0") == 0
+        matcher.compare("1_0", "1-0") == 0
+        matcher.compare("1-0", "1+0") == 0
+        matcher.compare("1.a.2", "1a2") == 0 // number-word and word-number boundaries are considered separators
+    }
+
+    def "compares unrelated versions unequal"() {
+        expect:
+        matcher.compare("1.0", "") != 0
+        matcher.compare("1.0", "!@#%") != 0
+        matcher.compare("1.0", "hey joe") != 0
+    }
+
+    // original Ivy behavior - should we change it?
+    def "does not compare versions with different number of trailing .0's equal"() {
+        expect:
+        matcher.compare(v1, v2) > 0
+        matcher.compare(v2, v1) < 0
+
+        where:
+        v1          | v2
+        "1.0.0"     | "1.0"
+        "1.0.0"     | "1"
+    }
+
+    def "does not compare versions with different capitalization equal"() {
+        expect:
+        matcher.compare(v1, v2) > 0
+        matcher.compare(v2, v1) < 0
+
+        where:
+        v1          | v2
+        "1.0-alpha" | "1.0-ALPHA"
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionMatcherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionMatcherTest.groovy
new file mode 100644
index 0000000..529b3cd
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionMatcherTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy
+
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+import spock.lang.Specification
+
+class LatestVersionMatcherTest extends Specification {
+    def matcher = new LatestVersionMatcher()
+
+    def "handles selectors starting with 'latest.'"() {
+        expect:
+        matcher.canHandle("latest.integration")
+        matcher.canHandle("latest.foo")
+        matcher.canHandle("latest.123")
+        !matcher.canHandle("1.0")
+        !matcher.canHandle("[1.0,2.0]")
+    }
+
+    def "all handled selectors are dynamic"() {
+        expect:
+        matcher.isDynamic("latest.integration")
+        matcher.isDynamic("latest.foo")
+        matcher.isDynamic("latest.123")
+    }
+
+    def "always needs metadata"() {
+        expect:
+        matcher.needModuleMetadata("latest.integration")
+        matcher.needModuleMetadata("latest.foo")
+        matcher.needModuleMetadata("latest.123")
+    }
+
+    def "only supports metadata-aware accept method"() {
+        when:
+        matcher.accept("latest.integration", "1.0")
+
+        then:
+        UnsupportedOperationException e = thrown()
+        e.message.contains("accept")
+    }
+
+    def "accepts a candidate version iff its status is equal to or higher than the selector's status"() {
+        def metadata = Stub(ModuleVersionMetaData) {
+            getStatus() >> "silver"
+            getStatusScheme() >> ["bronze", "silver", "gold"]
+        }
+
+        expect:
+        matcher.accept("latest.bronze", metadata)
+        matcher.accept("latest.silver", metadata)
+        !matcher.accept("latest.gold", metadata)
+    }
+
+    def "rejects a candidate version if selector's status is not contained in candidate's status scheme"() {
+        def metadata = Stub(ModuleVersionMetaData) {
+            getStatus() >> "silver"
+            getStatusScheme() >> ["bronze", "silver", "gold"]
+        }
+
+        expect:
+        !matcher.accept("latest.other", metadata)
+    }
+
+    def "cannot tell which of version selector and candidate version is greater"() {
+        expect:
+        matcher.compare("latest.integration", "1.0") == 0
+        matcher.compare("latest.release", "2.0") == 0
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionStrategyTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionStrategyTest.groovy
new file mode 100644
index 0000000..36db721
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/LatestVersionStrategyTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.VersionInfo
+
+import spock.lang.Specification
+
+class LatestVersionStrategyTest extends Specification {
+    def chain = new ChainVersionMatcher()
+    def strategy = new LatestVersionStrategy(chain)
+    def matcher = Mock(VersionMatcher)
+
+    def setup() {
+        chain.add(new SubVersionMatcher(new ExactVersionMatcher()))
+        chain.add(matcher)
+        chain.add(new ExactVersionMatcher())
+    }
+
+    def "compares static versions according to version matcher"() {
+        expect:
+        strategy.compare(new VersionInfo("1.0"), new VersionInfo("2.0")) < 0
+        strategy.compare(new VersionInfo("1.0"), new VersionInfo("1.0")) == 0
+        strategy.compare(new VersionInfo("2.0"), new VersionInfo("1.0")) > 0
+    }
+
+    def "compares dynamic and static version according to version matcher"() {
+        expect:
+        strategy.compare(new VersionInfo("1.+"), new VersionInfo("2.0")) < 0
+        strategy.compare(new VersionInfo("2.0"), new VersionInfo("1.+")) > 0
+        strategy.compare(new VersionInfo("1.+"), new VersionInfo("1.11")) > 0
+        strategy.compare(new VersionInfo("1.11"), new VersionInfo("1.+")) < 0
+    }
+
+    def "considers dynamic version greater if it compares equal according to version matcher"() {
+        matcher.canHandle("foo") >> true
+        matcher.isDynamic("foo") >> true
+        matcher.isDynamic("1.0") >> false
+        matcher.compare(_, _) >> 0
+
+        expect:
+        strategy.compare(new VersionInfo("foo"), new VersionInfo("1.0")) > 0
+        strategy.compare(new VersionInfo("1.0"), new VersionInfo("foo")) < 0
+    }
+
+    def "sorts elements in ascending order according to version matcher"() {
+        def version1 = new VersionInfo("1.0")
+        def version2 = new VersionInfo("1.+")
+        def version3 = new VersionInfo("2.2")
+        def version4 = new VersionInfo("2.+")
+
+        expect:
+        strategy.sort([version3, version1, version2, version4]) == [version1, version2, version3, version4]
+    }
+
+    def "finds latest element according to version matcher"() {
+        def version1 = new VersionInfo("1.0")
+        def version2 = new VersionInfo("1.+")
+        def version3 = new VersionInfo("2.2")
+        def version4 = new VersionInfo("2.+")
+
+        expect:
+        strategy.findLatest([version3, version1, version2, version4]) == version4
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/SubVersionMatcherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/SubVersionMatcherTest.groovy
new file mode 100644
index 0000000..bf719ff
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/SubVersionMatcherTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+
+import spock.lang.Specification
+
+class SubVersionMatcherTest extends Specification {
+    def matcher = new SubVersionMatcher(new ExactVersionMatcher())
+
+    def "handles selectors that end in '+'"() {
+        expect:
+        matcher.canHandle("1+")
+        matcher.canHandle("1.2.3+")
+        !matcher.canHandle("1")
+        !matcher.canHandle("1.+.3")
+    }
+
+    def "all handled selectors are dynamic"() {
+        expect:
+        matcher.isDynamic("1+")
+        matcher.isDynamic("1.2.3+")
+    }
+
+    def "never needs metadata"() {
+        expect:
+        !matcher.needModuleMetadata("1+")
+        !matcher.needModuleMetadata("1.2.3+")
+    }
+
+    def "accepts candidate versions that literally match the selector up until the trailing '+'"() {
+        expect:
+        matcher.accept("1+", "11")
+        matcher.accept("1.+", "1.2")
+        matcher.accept("1.2.3+", "1.2.3.11")
+        !matcher.accept("1+", "2")
+        !matcher.accept("1.+", "11")
+        !matcher.accept("1.2.3+", "1.2")
+    }
+
+    def "metadata-aware accept method delivers same results"() {
+        def metadata = Stub(ModuleVersionMetaData) {
+            getId() >> Stub(ModuleVersionIdentifier) {
+                getVersion() >> metadataVersion
+            }
+        }
+
+        expect:
+        matcher.accept("1.+", metadata) == result
+
+        where:
+        metadataVersion | result
+        "1.5"           | true
+        "2.5"           | false
+    }
+
+    def "considers a '+' selector greater than any matching candidate version"() {
+        expect:
+        matcher.compare("1+", "11") > 0
+        matcher.compare("1.+", "1.2") > 0
+        matcher.compare("1.2.3+", "1.2.3.11") > 0
+    }
+
+    def "falls back to the provided comparator if selector doesn't match candidate version"() {
+        expect:
+        matcher.compare("1+", "2") < 0
+        matcher.compare("1+", "0.5") > 0
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeMatcherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeMatcherTest.groovy
new file mode 100644
index 0000000..31395ac
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeMatcherTest.groovy
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.strategy
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+
+import spock.lang.Specification
+
+public class VersionRangeMatcherTest extends Specification {
+    def matcher = new VersionRangeMatcher(new ExactVersionMatcher())
+
+    def "handles selectors that use valid version range syntax"() {
+        expect:
+        matcher.canHandle("[1.0,2.0]")
+        matcher.canHandle("[1.0,2.0[")
+        matcher.canHandle("]1.0,2.0]")
+        matcher.canHandle("]1.0,2.0[")
+        matcher.canHandle("[1.0,)")
+        matcher.canHandle("]1.0,)")
+        matcher.canHandle("(,2.0]")
+        matcher.canHandle("(,2.0[")
+    }
+
+    def "does not handle selectors that use no or invalid version range syntax"() {
+        expect:
+        !matcher.canHandle("1")
+        !matcher.canHandle("1+")
+        !matcher.canHandle("[1")
+        !matcher.canHandle("[]")
+        !matcher.canHandle("[1,2,3]")
+    }
+
+    def "all handled selectors are dynamic"() {
+        expect:
+        matcher.isDynamic("[1.0,2.0]")
+        matcher.isDynamic("[1.0,)")
+    }
+
+    def "never needs metadata"() {
+        expect:
+        !matcher.needModuleMetadata("[1.0,2.0]")
+        !matcher.needModuleMetadata("[1.0,)")
+        !matcher.needModuleMetadata("1")
+    }
+
+    def "accepts candidate versions that fall into the selector's range"() {
+        expect:
+        matcher.accept("[1.0,2.0]", "1.0")
+        matcher.accept("[1.0,2.0]", "1.2.3")
+        matcher.accept("[1.0,2.0]", "2.0")
+
+        matcher.accept("[1.0,2.0[", "1.0")
+        matcher.accept("[1.0,2.0[", "1.2.3")
+        matcher.accept("[1.0,2.0[", "1.99")
+
+        matcher.accept("]1.0,2.0]", "1.0.1")
+        matcher.accept("]1.0,2.0]", "1.2.3")
+        matcher.accept("]1.0,2.0]", "2.0")
+
+        matcher.accept("]1.0,2.0[", "1.0.1")
+        matcher.accept("]1.0,2.0[", "1.2.3")
+        matcher.accept("]1.0,2.0[", "1.99")
+
+        matcher.accept("[1.0,)", "1.0")
+        matcher.accept("[1.0,)", "1.2.3")
+        matcher.accept("[1.0,)", "2.3.4")
+
+        matcher.accept("]1.0,)", "1.0.1")
+        matcher.accept("]1.0,)", "1.2.3")
+        matcher.accept("]1.0,)", "2.3.4")
+
+        matcher.accept("(,2.0]", "0")
+        matcher.accept("(,2.0]", "0.1.2")
+        matcher.accept("(,2.0]", "2.0")
+
+        matcher.accept("(,2.0[", "0")
+        matcher.accept("(,2.0[", "0.1.2")
+        matcher.accept("(,2.0[", "1.99")
+    }
+
+    def "accepts candidate versions that fall into the selector's range (adding qualifiers to the mix)"() {
+        expect:
+        matcher.accept("[1.0,2.0]", "1.5-dev-1")
+        matcher.accept("[1.0,2.0]", "1.2.3-rc-2")
+        matcher.accept("[1.0,2.0]", "2.0-final")
+
+        matcher.accept("[1.0-dev-1,2.0[", "1.0")
+        matcher.accept("[1.0,2.0-rc-2[", "2.0-rc-1")
+        matcher.accept("[1.0,2.0[", "2.0-final")
+
+        matcher.accept("]1.0-dev-1,2.0]", "1.0")
+        matcher.accept("]1.0-rc-2,2.0]", "1.0-rc-3")
+
+        matcher.accept("]1.0-dev-1,1.0-dev-3[", "1.0-dev-2")
+        matcher.accept("]1.0-dev-1,1.0-rc-1[", "1.0-dev-99")
+    }
+
+    def "rejects candidate versions that don't fall into the selector's range"() {
+        expect:
+        !matcher.accept("[1.0,2.0]", "0.99")
+        !matcher.accept("[1.0,2.0]", "2.0.1")
+        !matcher.accept("[1.0,2.0]", "42")
+
+        !matcher.accept("[1.0,2.0[", "0.99")
+        !matcher.accept("[1.0,2.0[", "2.0")
+        !matcher.accept("[1.0,2.0[", "42")
+
+        !matcher.accept("]1.0,2.0]", "1.0")
+        !matcher.accept("]1.0,2.0]", "2.0.1")
+        !matcher.accept("]1.0,2.0]", "42")
+
+        !matcher.accept("]1.0,2.0[", "1.0")
+        !matcher.accept("]1.0,2.0[", "2.0")
+        !matcher.accept("]1.0,2.0[", "42")
+
+        !matcher.accept("[1.0,)", "0")
+        !matcher.accept("[1.0,)", "0.99")
+
+        !matcher.accept("]1.0,)", "0")
+        !matcher.accept("]1.0,)", "1")
+        !matcher.accept("]1.0,)", "1.0")
+
+        !matcher.accept("(,2.0]", "2.0.1")
+        !matcher.accept("(,2.0]", "42")
+
+        !matcher.accept("(,2.0[", "2.0")
+        !matcher.accept("(,2.0[", "42")
+    }
+
+    def "rejects candidate versions that don't fall into the selector's range (adding qualifiers to the mix)"() {
+        expect:
+        !matcher.accept("[1.0,2.0]", "2.5-dev-1")
+        !matcher.accept("[1.0,2.0]", "1.0-rc-2")
+        !matcher.accept("[1.0,2.0]", "1.0-final")
+
+        !matcher.accept("[1.0-dev-2,2.0[", "1.0-dev-1")
+        !matcher.accept("[1.0,2.0-rc-2[", "2.0-rc-2")
+        !matcher.accept("[1.0,2.0-final[", "2.0")
+
+        !matcher.accept("]1.0-dev-1,2.0]", "1.0-dev-1")
+        !matcher.accept("]1.0-rc-2,2.0]", "1.0-dev-3")
+
+        !matcher.accept("]1.0-dev-1,1.0-dev-3[", "1.0-dev-3")
+        !matcher.accept("]1.0-dev-1,1.0-rc-1[", "1.0-final-0")
+    }
+
+    def "metadata-aware accept method delivers same results"() {
+        def metadata = Stub(ModuleVersionMetaData) {
+            getId() >> Stub(ModuleVersionIdentifier) {
+                getVersion() >> metadataVersion
+            }
+        }
+
+        expect:
+        matcher.accept("[1.0,2.0]", metadata) == result
+
+        where:
+        metadataVersion | result
+        "1.5"           | true
+        "2.5"           | false
+    }
+
+    def "compares candidate versions against the selector's upper bound"() {
+        expect:
+        matcher.compare(range, "0.5") > 0
+        matcher.compare(range, "1.0") > 0
+        matcher.compare(range, "1.5") > 0
+        matcher.compare(range, "2.0") < 0 // unsure why [1.0,2.0] isn't considered equal to 2.0 (apparently never returns 0)
+        matcher.compare(range, "2.5") < 0
+
+        where:
+        range       | _
+        "[1.0,2.0]" | _
+        "[1.0,2.0[" | _
+        "]1.0,2.0]" | _
+        "]1.0,2.0[" | _
+        "(,2.0]"    | _
+        "(,2.0["    | _
+    }
+
+    def "selectors with infinite upper bound compare greater than any candidate version"() {
+        expect:
+        matcher.compare(range, "0.5") > 0
+        matcher.compare(range, "1.0") > 0
+        matcher.compare(range, "1.5") > 0
+        matcher.compare(range, "2.0") > 0
+        matcher.compare(range, "2.5") > 0
+
+        where:
+        range    | _
+        "[1.0,)" | _
+        "]1.0,)" | _
+    }
+}
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
index b5f32cb..2ed8810 100644
--- 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
@@ -19,11 +19,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.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver
 import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser
-import org.gradle.api.internal.filestore.FileStoreEntry
 import org.gradle.api.internal.filestore.PathKeyFileStore
+import org.gradle.internal.resource.local.LocallyAvailableResource
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
@@ -35,11 +36,12 @@ class ModuleDescriptorStoreTest extends Specification {
     PathKeyFileStore pathKeyFileStore = Mock()
     ModuleRevisionId moduleRevisionId = Mock()
     ModuleVersionRepository repository = Mock()
-    FileStoreEntry fileStoreEntry = Mock()
+    LocallyAvailableResource fileStoreEntry = Mock()
     ModuleDescriptor moduleDescriptor = Mock()
     IvyModuleDescriptorWriter ivyModuleDescriptorWriter = Mock()
     IvyXmlModuleDescriptorParser ivyXmlModuleDescriptorParser = Mock()
     ModuleVersionIdentifier moduleVersionIdentifier = Mock()
+    def resolver = Mock(DependencyToModuleVersionResolver)
 
     def setup() {
         store = new ModuleDescriptorStore(pathKeyFileStore, ivyModuleDescriptorWriter, ivyXmlModuleDescriptorParser);
@@ -52,7 +54,7 @@ class ModuleDescriptorStoreTest extends Specification {
 
     def "getModuleDescriptorFile returns null for not cached descriptors"() {
         when:
-        pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId/ivy.xml") >> null
+        pathKeyFileStore.get("org.test/testArtifact/1.0/repositoryId/ivy.xml") >> null
         then:
         null == store.getModuleDescriptor(repository, moduleVersionIdentifier)
     }
@@ -61,7 +63,7 @@ class ModuleDescriptorStoreTest extends Specification {
         when:
         store.getModuleDescriptor(repository, moduleVersionIdentifier);
         then:
-        1 * pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId/ivy.xml") >> null
+        1 * pathKeyFileStore.get("org.test/testArtifact/1.0/repositoryId/ivy.xml") >> null
     }
 
     def "putModuleDescriptor uses PathKeyFileStore to write file"() {
@@ -73,7 +75,7 @@ class ModuleDescriptorStoreTest extends Specification {
         when:
         store.putModuleDescriptor(repository, moduleDescriptor);
         then:
-        1 * pathKeyFileStore.add("module-metadata/org.test/testArtifact/1.0/repositoryId/ivy.xml", _) >> { path, action ->
+        1 * pathKeyFileStore.add("org.test/testArtifact/1.0/repositoryId/ivy.xml", _) >> { path, action ->
             action.execute(descriptorFile); fileStoreEntry
         };
         1 * ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorFile)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
deleted file mode 100644
index 8e973ca..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2007-2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.PublishArtifactSet;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultArtifactsToModuleDescriptorConverterTest {
-    private JUnit4Mockery context = new JUnit4GroovyMockery();
-
-    @Test
-    public void testAddArtifacts() {
-        final PublishArtifact publishArtifactConf1 = createNamedPublishArtifact("conf1");
-        Configuration configurationStub1 = createConfigurationStub(publishArtifactConf1);
-        final PublishArtifact publishArtifactConf2 = createNamedPublishArtifact("conf2");
-        Configuration configurationStub2 = createConfigurationStub(publishArtifactConf2);
-        final ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategyMock = context.mock(ArtifactsExtraAttributesStrategy.class);
-        final Map<String, String> extraAttributesArtifact1 = WrapUtil.toMap("name", publishArtifactConf1.getName());
-        final Map<String, String> extraAttributesArtifact2 = WrapUtil.toMap("name", publishArtifactConf2.getName());
-        context.checking(new Expectations() {{
-            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf1);
-            will(returnValue(extraAttributesArtifact1));
-            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf2);
-            will(returnValue(extraAttributesArtifact2));
-        }});
-        DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(configurationStub1.getName(),
-                configurationStub2.getName()));
-
-        DefaultArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter =
-                new DefaultArtifactsToModuleDescriptorConverter(artifactsExtraAttributesStrategyMock);
-
-        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, WrapUtil.toSet(configurationStub1, configurationStub2));
-
-        assertArtifactIsAdded(configurationStub1, moduleDescriptor, extraAttributesArtifact1);
-        assertArtifactIsAdded(configurationStub2, moduleDescriptor, extraAttributesArtifact2);
-        assertThat(moduleDescriptor.getAllArtifacts().length, equalTo(2));
-    }
-
-    @Test
-    public void testIvyFileStrategy() {
-        assertThat(
-                DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY.createExtraAttributes(context.mock(PublishArtifact.class)),
-                equalTo((Map) new HashMap<String, String>()));
-    }
-
-    @Test
-    public void testResolveStrategy() {
-        PublishArtifact publishArtifact = createNamedPublishArtifact("someName");
-        Map<String, String> expectedExtraAttributes = WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
-        assertThat(
-                DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY.createExtraAttributes(publishArtifact),
-                equalTo(expectedExtraAttributes));
-    }
-
-    private void assertArtifactIsAdded(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, Map<String, String> extraAttributes) {
-        assertThat(moduleDescriptor.getArtifacts(configuration.getName()),
-                equalTo(WrapUtil.toArray(expectedIvyArtifact(configuration, moduleDescriptor, extraAttributes))));
-    }
-
-    private Artifact expectedIvyArtifact(Configuration configuration, ModuleDescriptor moduleDescriptor, Map<String, String> additionalExtraAttributes) {
-        PublishArtifact publishArtifact = configuration.getArtifacts().iterator().next();
-        Map<String, String> extraAttributes = WrapUtil.toMap(Dependency.CLASSIFIER, publishArtifact.getClassifier());
-        extraAttributes.putAll(additionalExtraAttributes);
-        return new DefaultArtifact(moduleDescriptor.getModuleRevisionId(),
-                publishArtifact.getDate(),
-                publishArtifact.getName(),
-                publishArtifact.getType(),
-                publishArtifact.getExtension(),
-                extraAttributes);
-    }
-
-    private Configuration createConfigurationStub(final PublishArtifact publishArtifact) {
-        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(publishArtifact.getName(), context);
-        final PublishArtifactSet artifacts = context.mock(PublishArtifactSet.class);
-        context.checking(new Expectations() {{
-            allowing(configurationStub).getArtifacts();
-            will(returnValue(artifacts));
-            allowing(artifacts).iterator();
-            will(returnIterator(publishArtifact));
-        }});
-        return configurationStub;
-    }
-
-    private PublishArtifact createNamedPublishArtifact(String name) {
-        return new DefaultPublishArtifact(name, "ext", "type", "classifier", new Date(), new File("somePath"));
-    }
-
-
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy
new file mode 100644
index 0000000..b933f91
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData
+import spock.lang.Specification
+
+class DefaultConfigurationsToArtifactsConverterTest extends Specification {
+    def converter = new DefaultConfigurationsToArtifactsConverter()
+
+    def "adds artifacts from each configuration"() {
+        def metaData = Mock(MutableLocalComponentMetaData)
+        def config1 = Stub(Configuration)
+        def config2 = Stub(Configuration)
+        def artifact1 = Stub(PublishArtifact)
+        def artifact2 = Stub(PublishArtifact)
+        def file1 = new File("file-1")
+        def file2 = new File("file-2")
+
+        given:
+        config1.name >> "config1"
+        config1.artifacts >> new DefaultPublishArtifactSet("art1", new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact, [artifact1]))
+        config2.name >> "config2"
+        config2.artifacts >> new DefaultPublishArtifactSet("art1", new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact, [artifact2]))
+
+        and:
+        artifact1.name >> 'art1'
+        artifact1.type >> 'type1'
+        artifact1.extension >> 'ext1'
+        artifact2.name >> 'art2'
+        artifact2.type >> 'type2'
+        artifact2.extension >> 'ext2'
+        artifact2.classifier >> 'classifier'
+
+        when:
+        converter.addArtifacts(metaData, [config1, config2])
+
+        then:
+        1 * metaData.addArtifact("config1", _, file1) >> { name, Artifact artifact, file ->
+            assert artifact.name == 'art1'
+            assert artifact.type == 'type1'
+            assert artifact.ext == 'ext1'
+            assert artifact.qualifiedExtraAttributes == [:]
+        }
+        1 * metaData.addArtifact("config2", _, file2) >> { name, Artifact artifact, file ->
+            assert artifact.name == 'art2'
+            assert artifact.type == 'type2'
+            assert artifact.ext == 'ext2'
+            assert artifact.qualifiedExtraAttributes == [(Dependency.CLASSIFIER): 'classifier']
+        }
+        _ * metaData.moduleDescriptor >> Stub(DefaultModuleDescriptor)
+        0 * metaData._
+    }
+
+    def "artifact name defaults to module name when not specified"() {
+        def metaData = Mock(MutableLocalComponentMetaData)
+        def config = Stub(Configuration)
+        def artifact = Stub(PublishArtifact)
+        def file = new File("file-1")
+
+        given:
+        config.name >> "config1"
+        config.artifacts >> new DefaultPublishArtifactSet("art1", new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact, [artifact]))
+
+        and:
+        artifact.type >> 'type1'
+        artifact.extension >> 'ext1'
+
+        when:
+        converter.addArtifacts(metaData, [config])
+
+        then:
+        1 * metaData.addArtifact("config1", _, file) >> { name, Artifact ivyArtifact, f ->
+            assert ivyArtifact.name == 'module'
+        }
+        _ * metaData.moduleDescriptor >> Stub(DefaultModuleDescriptor) {
+            getModuleRevisionId() >> IvyUtil.createModuleRevisionId("group", "module", "version")
+        }
+        0 * metaData._
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
index 3b27122..69688de 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
@@ -18,21 +18,19 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.internal.artifacts.configurations.Configurations;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Collections;
 
-/**
- * @author Hans Dockter
- */
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
 @RunWith(JMock.class)
 public class DefaultConfigurationsToModuleDescriptorConverterTest {
     private DefaultConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter = new DefaultConfigurationsToModuleDescriptorConverter();
@@ -43,7 +41,7 @@ public class DefaultConfigurationsToModuleDescriptorConverterTest {
     public void testAddConfigurations() {
         Configuration configurationStub1 = createNamesAndExtendedConfigurationStub("conf1");
         Configuration configurationStub2 = createNamesAndExtendedConfigurationStub("conf2", configurationStub1);
-        final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(Collections.EMPTY_SET);
+        final DefaultModuleDescriptor moduleDescriptor = TestUtil.createModuleDescriptor(Collections.EMPTY_SET);
 
         configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, WrapUtil.toSet(configurationStub1, configurationStub2));
 
@@ -80,7 +78,7 @@ public class DefaultConfigurationsToModuleDescriptorConverterTest {
             will(returnValue(true));
 
             allowing(configurationStub).getDescription();
-            will(returnValue(HelperUtil.createUniqueId()));
+            will(returnValue(TestUtil.createUniqueId()));
 
             allowing(configurationStub).isVisible();
             will(returnValue(true));
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
index 6af0160..61df00e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
@@ -24,9 +24,6 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExcludeRuleConverterTest {
 
     @Test
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy
index 808f5cc..49ec853 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy
@@ -13,52 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter
 
-
-import org.apache.ivy.Ivy
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.core.module.status.StatusManager
-import org.apache.ivy.core.settings.IvySettings
 import org.gradle.api.artifacts.Module
 import org.gradle.api.internal.artifacts.DefaultModule
-import org.gradle.api.internal.artifacts.ivyservice.IvyFactory
-import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class DefaultModuleDescriptorFactoryTest extends Specification {
-    final IvyFactory ivyFactory = Mock()
-    final SettingsConverter settingsConverter = Mock()
-    final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(ivyFactory, settingsConverter)
+    final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory()
 
     public void testCreateModuleDescriptor() {
         given:
-        IvySettings ivySettings = Mock()
-        Ivy ivy = Mock()
-        StatusManager statusManager = Mock()
         Module module = new DefaultModule("org", "name", "version", "status");
 
         when:
         DefaultModuleDescriptor moduleDescriptor = factory.createModuleDescriptor(module);
 
         then:
-        1 * settingsConverter.getForResolve() >> ivySettings
-        1 * ivyFactory.createIvy(ivySettings) >> ivy
-        _ * ivy.settings >> ivySettings
-        1 * ivySettings.statusManager >> statusManager
-        1 * statusManager.defaultStatus >> "default status"
-        1 * ivySettings.getDefaultBranch(ModuleId.newInstance("org", "name")) >> "default branch"
-        0 * _._
-        
-        and:
-        moduleDescriptor.moduleRevisionId.organisation == module.group;
-        moduleDescriptor.moduleRevisionId.name == module.name;
-        moduleDescriptor.moduleRevisionId.revision == module.version;
-        moduleDescriptor.status == module.getStatus();
-        moduleDescriptor.publicationDate == null;
+        moduleDescriptor.moduleRevisionId.organisation == module.group
+        moduleDescriptor.moduleRevisionId.name == module.name
+        moduleDescriptor.moduleRevisionId.revision == module.version
+        moduleDescriptor.status == module.status
+        moduleDescriptor.publicationDate == null
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
index 9cea636..3a06d68 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
@@ -19,9 +19,6 @@ import org.gradle.api.artifacts.Configuration;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 
-/**
- * @author Hans Dockter
- */
 public class IvyConverterTestUtil {
     public static Configuration createNamedConfigurationStub(final String name, Mockery context) {
         final Configuration configurationStub = context.mock(Configuration.class, name);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishLocalComponentFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishLocalComponentFactoryTest.groovy
new file mode 100644
index 0000000..7d292cb
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishLocalComponentFactoryTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.artifacts.ModuleInternal
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData
+import spock.lang.Specification
+
+public class PublishLocalComponentFactoryTest extends Specification {
+
+    def "converts"() {
+        given:
+        def configurationsDummy = [Mock(Configuration)] as Set
+        def moduleDummy = Mock(ModuleInternal)
+        def moduleDescriptorDummy = Mock(DefaultModuleDescriptor)
+        def componentMetaDataDummy = Mock(MutableLocalComponentMetaData)
+        def artifactsToModuleDescriptorConverter = Mock(ConfigurationsToArtifactsConverter)
+        def resolveModuleDescriptorConverter = Mock(LocalComponentFactory)
+
+        def publishModuleDescriptorConverter = new PublishLocalComponentFactory(
+                resolveModuleDescriptorConverter,
+                artifactsToModuleDescriptorConverter);
+
+        and:
+        componentMetaDataDummy.moduleDescriptor >> moduleDescriptorDummy
+        resolveModuleDescriptorConverter.convert(configurationsDummy, moduleDummy) >> componentMetaDataDummy
+
+        when:
+        def actualMetaData = publishModuleDescriptorConverter.convert(configurationsDummy, moduleDummy);
+
+        then:
+        1 * moduleDescriptorDummy.addExtraAttributeNamespace(PublishLocalComponentFactory.IVY_MAVEN_NAMESPACE_PREFIX,
+                    PublishLocalComponentFactory.IVY_MAVEN_NAMESPACE);
+        1 * artifactsToModuleDescriptorConverter.addArtifacts(componentMetaDataDummy, configurationsDummy)
+
+        and:
+        actualMetaData == componentMetaDataDummy
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.groovy
deleted file mode 100644
index 1c0e2ff..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.groovy
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.Module
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter, Szczepan
- */
-public class PublishModuleDescriptorConverterTest extends Specification {
-
-    def "converts"() {
-        given:
-        def configurationsDummy = [Mock(Configuration)] as Set
-        def moduleDummy = Mock(Module)
-        def moduleDescriptorDummy = Mock(DefaultModuleDescriptor)
-        def artifactsToModuleDescriptorConverter = Mock(ArtifactsToModuleDescriptorConverter)
-        def resolveModuleDescriptorConverter = Mock(ModuleDescriptorConverter)
-
-        def publishModuleDescriptorConverter = new PublishModuleDescriptorConverter(
-                resolveModuleDescriptorConverter,
-                artifactsToModuleDescriptorConverter);
-
-        resolveModuleDescriptorConverter.convert(configurationsDummy, moduleDummy) >> moduleDescriptorDummy
-
-        when:
-        def actualModuleDescriptor = publishModuleDescriptorConverter.convert(configurationsDummy, moduleDummy);
-
-        then:
-        1 * moduleDescriptorDummy.addExtraAttributeNamespace(PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE_PREFIX,
-                    PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE);
-        1 * artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptorDummy, configurationsDummy)
-        actualModuleDescriptor == moduleDescriptorDummy
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy
new file mode 100644
index 0000000..15aac06
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.artifacts.DefaultModule
+import org.gradle.api.internal.artifacts.ProjectBackedModule
+import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter
+import org.gradle.api.internal.artifacts.metadata.DefaultLocalComponentMetaData
+import spock.lang.Specification
+
+public class ResolveLocalComponentFactoryTest extends Specification {
+    def moduleDescriptor = Mock(DefaultModuleDescriptor)
+    def moduleDescriptorFactory = Mock(ModuleDescriptorFactory)
+    def configurationsConverter = Mock(ConfigurationsToModuleDescriptorConverter)
+    def dependenciesConverter = Mock(DependenciesToModuleDescriptorConverter)
+    def componentIdentifierFactory = Mock(ComponentIdentifierFactory)
+
+    ResolveLocalComponentFactory resolveModuleDescriptorConverter = new ResolveLocalComponentFactory(
+            moduleDescriptorFactory,
+            configurationsConverter,
+            dependenciesConverter,
+            componentIdentifierFactory);
+
+    def "converts for provided default module"() {
+        given:
+        def configurations = [Mock(Configuration), Mock(Configuration)] as Set
+        def module = new DefaultModule('group-one', 'name-one', 'version-one')
+
+        and:
+        moduleDescriptor.moduleRevisionId >> IvyUtil.createModuleRevisionId("group", "module", "version")
+
+        when:
+        def actualDescriptor = resolveModuleDescriptorConverter.convert(configurations, module);
+
+        then:
+        1 * moduleDescriptorFactory.createModuleDescriptor(module) >> moduleDescriptor
+        1 * configurationsConverter.addConfigurations(moduleDescriptor, configurations)
+        1 * dependenciesConverter.addDependencyDescriptors(moduleDescriptor, configurations)
+        1 * componentIdentifierFactory.createComponentIdentifier(module) >> new DefaultModuleComponentIdentifier('group-one', 'name-one', 'version-one')
+
+        and:
+        actualDescriptor instanceof DefaultLocalComponentMetaData
+        actualDescriptor.moduleDescriptor == moduleDescriptor
+        actualDescriptor.toResolveMetaData().componentId == new DefaultModuleComponentIdentifier('group-one', 'name-one', 'version-one')
+    }
+
+    def "converts for provided project backed module"() {
+        given:
+        def configurations = [Mock(Configuration), Mock(Configuration)] as Set
+        def project = Mock(Project)
+        def module = new ProjectBackedModule(project)
+
+        and:
+        moduleDescriptor.moduleRevisionId >> IvyUtil.createModuleRevisionId("group", "module", "version")
+
+        when:
+        def actualDescriptor = resolveModuleDescriptorConverter.convert(configurations, module);
+
+        then:
+        1 * moduleDescriptorFactory.createModuleDescriptor(module) >> moduleDescriptor
+        1 * configurationsConverter.addConfigurations(moduleDescriptor, configurations)
+        1 * dependenciesConverter.addDependencyDescriptors(moduleDescriptor, configurations)
+        1 * componentIdentifierFactory.createComponentIdentifier(module) >> new DefaultProjectComponentIdentifier(':myPath')
+
+        and:
+        actualDescriptor instanceof DefaultLocalComponentMetaData
+        actualDescriptor.moduleDescriptor == moduleDescriptor
+        actualDescriptor.toResolveMetaData().componentId == new DefaultProjectComponentIdentifier(':myPath')
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
deleted file mode 100644
index b28e9a1..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.Module
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter, Szczepan
- */
-public class ResolveModuleDescriptorConverterTest extends Specification {
-
-    def "converts"() {
-        given:
-        def configurations = [Mock(Configuration), Mock(Configuration)] as Set
-        def module = Mock(Module)
-        def moduleDescriptor = Mock(DefaultModuleDescriptor)
-        def moduleDescriptorFactory = Mock(ModuleDescriptorFactory)
-        def dependencyDescriptorFactory = Mock(DependencyDescriptorFactory)
-        def configurationsConverter = Mock(ConfigurationsToModuleDescriptorConverter)
-        def dependenciesConverter = Mock(DependenciesToModuleDescriptorConverter)
-
-        ResolveModuleDescriptorConverter resolveModuleDescriptorConverter = new ResolveModuleDescriptorConverter(
-                moduleDescriptorFactory,
-                dependencyDescriptorFactory,
-                configurationsConverter,
-                dependenciesConverter);
-
-        moduleDescriptorFactory.createModuleDescriptor(module) >> moduleDescriptor
-
-        when:
-        def actualDescriptor = resolveModuleDescriptorConverter.convert(configurations, module);
-
-        then:
-        1 * configurationsConverter.addConfigurations(moduleDescriptor, configurations)
-        1 * dependenciesConverter.addDependencyDescriptors(moduleDescriptor, configurations)
-
-        actualDescriptor == moduleDescriptor
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
index 3d70134..d4fbbf3 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
@@ -21,7 +21,6 @@ import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
 import org.apache.ivy.core.module.id.ArtifactId;
-import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.gradle.api.artifacts.Dependency;
@@ -29,8 +28,9 @@ import org.gradle.api.artifacts.DependencyArtifact;
 import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -47,9 +47,6 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public abstract class AbstractDependencyDescriptorFactoryInternalTest {
     private JUnit4Mockery context = new JUnit4Mockery();
@@ -60,7 +57,7 @@ public abstract class AbstractDependencyDescriptorFactoryInternalTest {
     protected static final ExcludeRule TEST_EXCLUDE_RULE = new org.gradle.api.internal.artifacts.DefaultExcludeRule("testOrg", null);
     protected static final org.apache.ivy.core.module.descriptor.ExcludeRule TEST_IVY_EXCLUDE_RULE = getTestExcludeRule();
     protected ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
-    protected final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(TEST_CONF));
+    protected final DefaultModuleDescriptor moduleDescriptor = TestUtil.createModuleDescriptor(WrapUtil.toSet(TEST_CONF));
     private DefaultDependencyArtifact artifact = new DefaultDependencyArtifact("name", "type", null, null, null);
     private DefaultDependencyArtifact artifactWithClassifiers = new DefaultDependencyArtifact("name2", "type2", "ext2", "classifier2", "http://www.url2.com");
 
@@ -129,7 +126,7 @@ public abstract class AbstractDependencyDescriptorFactoryInternalTest {
 
     private static DefaultExcludeRule getTestExcludeRule() {
         return new DefaultExcludeRule(new ArtifactId(
-                new ModuleId("org", "testOrg"), PatternMatcher.ANY_EXPRESSION,
+                IvyUtil.createModuleId("org", "testOrg"), PatternMatcher.ANY_EXPRESSION,
                 PatternMatcher.ANY_EXPRESSION,
                 PatternMatcher.ANY_EXPRESSION),
                 ExactPatternMatcher.INSTANCE, null);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
deleted file mode 100644
index 6f21ece..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Matchers;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule = context.mock(ModuleDescriptorFactoryForClientModule.class);
-    private ClientModuleIvyDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleIvyDependencyDescriptorFactory(
-            excludeRuleConverterStub,
-            moduleDescriptorFactoryForClientModule
-    );
-
-    @Test
-    public void canConvert() {
-        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
-        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ClientModule.class)), Matchers.equalTo(true));
-    }
-
-    @Test
-    public void testAddDependencyDescriptorForClientModule() {
-        final ModuleDependency dependencyDependency = context.mock(ModuleDependency.class, "dependencyDependency");
-        final DefaultClientModule clientModule = new DefaultClientModule("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
-        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(clientModule);
-
-        setUpDependency(clientModule);
-        clientModule.addDependency(dependencyDependency);
-        final ModuleDescriptor moduleDescriptorForClientModule = context.mock(ModuleDescriptor.class);
-        context.checking(new Expectations() {{
-            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
-                    testModuleRevisionId,
-                    WrapUtil.toSet(dependencyDependency)
-            );
-            will(returnValue(moduleDescriptorForClientModule));
-        }});
-
-        DefaultDependencyDescriptor dependencyDescriptor = clientModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, clientModule, moduleDescriptor);
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertEquals(testModuleRevisionId, dependencyDescriptor.getDependencyRevisionId());
-        assertFalse(dependencyDescriptor.isChanging());
-    }
-
-    @Test
-    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
-        final ClientModule clientModule = new DefaultClientModule(null, "gradle-core", null, TEST_DEP_CONF);
-        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(clientModule);
-        final ModuleDescriptor moduleDescriptorForClientModule = context.mock(ModuleDescriptor.class);
-        context.checking(new Expectations() {{
-            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
-                    testModuleRevisionId,
-                    WrapUtil.<ModuleDependency>toSet()
-            );
-            will(returnValue(moduleDescriptorForClientModule));
-        }});
-
-        DefaultDependencyDescriptor dependencyDescriptor = clientModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, clientModule, moduleDescriptor);
-        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(testModuleRevisionId));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..9a81239
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactoryTest.java
@@ -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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+public class ClientModuleIvyDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ClientModuleMetaDataFactory clientModuleMetaDataFactory = context.mock(ClientModuleMetaDataFactory.class);
+    private ClientModuleIvyDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleIvyDependencyDescriptorFactory(
+            excludeRuleConverterStub,
+            clientModuleMetaDataFactory
+    );
+
+    @Test
+    public void canConvert() {
+        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
+        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ClientModule.class)), Matchers.equalTo(true));
+    }
+
+    @Test
+    public void testAddDependencyDescriptorForClientModule() {
+        final ModuleDependency dependencyDependency = context.mock(ModuleDependency.class, "dependencyDependency");
+        final DefaultClientModule clientModule = new DefaultClientModule("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
+        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(clientModule);
+
+        setUpDependency(clientModule);
+        clientModule.addDependency(dependencyDependency);
+        final MutableModuleVersionMetaData moduleVersionMetaData = context.mock(MutableModuleVersionMetaData.class);
+        context.checking(new Expectations() {{
+            allowing(clientModuleMetaDataFactory).createModuleDescriptor(
+                    testModuleRevisionId,
+                    WrapUtil.toSet(dependencyDependency)
+            );
+            will(returnValue(moduleVersionMetaData));
+        }});
+
+        DefaultDependencyDescriptor dependencyDescriptor = clientModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, clientModule, moduleDescriptor);
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertEquals(testModuleRevisionId, dependencyDescriptor.getDependencyRevisionId());
+        assertFalse(dependencyDescriptor.isChanging());
+    }
+
+    @Test
+    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
+        final ClientModule clientModule = new DefaultClientModule(null, "gradle-core", null, TEST_DEP_CONF);
+        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(clientModule);
+        final MutableModuleVersionMetaData moduleVersionMetaData = context.mock(MutableModuleVersionMetaData.class);
+        context.checking(new Expectations() {{
+            allowing(clientModuleMetaDataFactory).createModuleDescriptor(
+                    testModuleRevisionId,
+                    WrapUtil.<ModuleDependency>toSet()
+            );
+            will(returnValue(moduleVersionMetaData));
+        }});
+
+        DefaultDependencyDescriptor dependencyDescriptor = clientModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, clientModule, moduleDescriptor);
+        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(testModuleRevisionId));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultClientModuleMetaDataFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultClientModuleMetaDataFactoryTest.java
new file mode 100644
index 0000000..bacaee7
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultClientModuleMetaDataFactoryTest.java
@@ -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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.metadata.MutableModuleVersionMetaData;
+import org.gradle.util.WrapUtil;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class DefaultClientModuleMetaDataFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testCreateModuleDescriptor() {
+        DependencyDescriptor dependencyDescriptorDummy = context.mock(DependencyDescriptor.class);
+        DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy = new DependencyDescriptorFactorySpy(dependencyDescriptorDummy);
+        DefaultClientModuleMetaDataFactory clientModuleDescriptorFactory =
+                new DefaultClientModuleMetaDataFactory();
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactorySpy);
+        ModuleDependency dependencyMock = context.mock(ModuleDependency.class);
+        final ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId("org", "name", "version");
+
+        MutableModuleVersionMetaData metaData = clientModuleDescriptorFactory.createModuleDescriptor(
+                moduleRevisionId,
+                WrapUtil.toSet(dependencyMock));
+
+        ModuleDescriptor moduleDescriptor = metaData.getDescriptor();
+        assertThat(moduleDescriptor.getModuleRevisionId(), equalTo(moduleRevisionId));
+        assertThatDescriptorHasOnlyDefaultConfiguration(moduleDescriptor);
+        assertCorrectCallToDependencyDescriptorFactory(dependencyDescriptorFactorySpy, Dependency.DEFAULT_CONFIGURATION, moduleDescriptor, dependencyMock);
+    }
+
+    private void assertThatDescriptorHasOnlyDefaultConfiguration(ModuleDescriptor moduleDescriptor) {
+        assertThat(moduleDescriptor.getConfigurations().length, equalTo(1));
+        assertThat(moduleDescriptor.getConfigurationsNames()[0], equalTo(Dependency.DEFAULT_CONFIGURATION));
+    }
+
+    private void assertCorrectCallToDependencyDescriptorFactory(DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy,
+                                                                String configuration,
+                                                                ModuleDescriptor parent,
+                                                                Dependency dependency) {
+        assertThat(dependencyDescriptorFactorySpy.configuration, equalTo(configuration));
+        assertThat(dependencyDescriptorFactorySpy.parent, equalTo(parent));
+        assertThat(dependencyDescriptorFactorySpy.dependency, equalTo(dependency));
+    }
+
+    private static class DependencyDescriptorFactorySpy implements DependencyDescriptorFactory {
+        DependencyDescriptor dependencyDescriptor;
+
+        String configuration;
+        ModuleDescriptor parent;
+        Dependency dependency;
+
+        private DependencyDescriptorFactorySpy(DependencyDescriptor dependencyDescriptor) {
+            this.dependencyDescriptor = dependencyDescriptor;
+        }
+
+        public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                            ModuleDependency dependency) {
+            this.configuration = configuration;
+            this.parent = moduleDescriptor;
+            this.dependency = dependency;
+        }
+
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
index 12f2ca1..1ac36a0 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
@@ -24,7 +24,7 @@ import org.gradle.api.internal.artifacts.DefaultExcludeRule;
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.IvyConverterTestUtil;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
@@ -40,9 +40,6 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultDependenciesToModuleDescriptorConverterTest {
     private JUnit4Mockery context = new JUnit4GroovyMockery();
@@ -68,7 +65,7 @@ public class DefaultDependenciesToModuleDescriptorConverterTest {
         Configuration configurationStub1 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf1", GRADLE_EXCLUDE_RULE_DUMMY_1, dependencyDummy1, similarDependency1);
         Configuration configurationStub2 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf2", GRADLE_EXCLUDE_RULE_DUMMY_2, dependencyDummy2, similarDependency2);
         Configuration configurationStub3 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf3", null, similarDependency3);
-        final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(toSet(configurationStub1.getName(),
+        final DefaultModuleDescriptor moduleDescriptor = TestUtil.createModuleDescriptor(toSet(configurationStub1.getName(),
                 configurationStub2.getName()));
         associateDependencyWithDescriptor(dependencyDummy1, moduleDescriptor, configurationStub1);
         associateDependencyWithDescriptor(dependencyDummy2, moduleDescriptor, configurationStub2);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
deleted file mode 100644
index bfa2b1c..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
+++ /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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.util.WrapUtil;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultModuleDescriptorFactoryForClientModuleTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void testCreateModuleDescriptor() {
-        DependencyDescriptor dependencyDescriptorDummy = context.mock(DependencyDescriptor.class);
-        DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy = new DependencyDescriptorFactorySpy(dependencyDescriptorDummy);
-        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory =
-                new DefaultModuleDescriptorFactoryForClientModule();
-        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactorySpy);
-        ModuleDependency dependencyMock = context.mock(ModuleDependency.class);
-        final ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance("org", "name", "version");
-
-        ModuleDescriptor moduleDescriptor = clientModuleDescriptorFactory.createModuleDescriptor(
-                moduleRevisionId,
-                WrapUtil.toSet(dependencyMock));
-
-        assertThat(moduleDescriptor.getModuleRevisionId(), equalTo(moduleRevisionId));
-        assertThatDescriptorHasOnlyDefaultConfiguration(moduleDescriptor);
-        assertCorrectCallToDependencyDescriptorFactory(dependencyDescriptorFactorySpy, Dependency.DEFAULT_CONFIGURATION, moduleDescriptor, dependencyMock);
-    }
-
-    private void assertThatDescriptorHasOnlyDefaultConfiguration(ModuleDescriptor moduleDescriptor) {
-        assertThat(moduleDescriptor.getConfigurations().length, equalTo(1));
-        assertThat(moduleDescriptor.getConfigurationsNames()[0], equalTo(Dependency.DEFAULT_CONFIGURATION));
-    }
-
-    private void assertCorrectCallToDependencyDescriptorFactory(DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy,
-                                                                String configuration,
-                                                                ModuleDescriptor parent,
-                                                                Dependency dependency) {
-        assertThat(dependencyDescriptorFactorySpy.configuration, equalTo(configuration));
-        assertThat(dependencyDescriptorFactorySpy.parent, equalTo(parent));
-        assertThat(dependencyDescriptorFactorySpy.dependency, equalTo(dependency));
-    }
-
-    private static class DependencyDescriptorFactorySpy implements DependencyDescriptorFactory {
-        DependencyDescriptor dependencyDescriptor;
-
-        String configuration;
-        ModuleDescriptor parent;
-        Dependency dependency;
-
-        private DependencyDescriptorFactorySpy(DependencyDescriptor dependencyDescriptor) {
-            this.dependencyDescriptor = dependencyDescriptor;
-        }
-
-        public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
-                                            ModuleDependency dependency) {
-            this.configuration = configuration;
-            this.parent = moduleDescriptor;
-            this.dependency = dependency;
-        }
-
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
index 1b58c4c..7cd5814 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
@@ -30,9 +30,6 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
     private JUnit4Mockery context = new JUnit4Mockery();
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy
new file mode 100644
index 0000000..ab40b8a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies
+
+import org.gradle.api.artifacts.ExternalModuleDependency
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.project.AbstractProject
+import org.gradle.initialization.ProjectAccessListener
+import org.gradle.util.TestUtil
+import org.jmock.integration.junit4.JUnit4Mockery
+import org.junit.Test
+
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.*
+
+//TODO SF spockify
+public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ProjectIvyDependencyDescriptorFactory projectDependencyDescriptorFactory =
+            new ProjectIvyDependencyDescriptorFactory(excludeRuleConverterStub);
+
+    @Test
+    public void canConvert() {
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
+    }
+
+    @Test
+    public void testCreateFromProjectDependency() {
+        ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
+        setUpDependency(projectDependency);
+        ProjectDependencyDescriptor dependencyDescriptor = (ProjectDependencyDescriptor) projectDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, projectDependency, moduleDescriptor);
+
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertFalse(dependencyDescriptor.isChanging());
+        assertFalse(dependencyDescriptor.isForce());
+        assertEquals(IvyUtil.createModuleRevisionId("someGroup", "test", "someVersion"), dependencyDescriptor.getDependencyRevisionId());
+        assertSame(projectDependency.getDependencyProject(), dependencyDescriptor.getTargetProject());
+    }
+
+    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
+        AbstractProject dependencyProject = TestUtil.createRootProject();
+        dependencyProject.setGroup("someGroup");
+        dependencyProject.setVersion("someVersion");
+        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, {} as ProjectAccessListener, true);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
deleted file mode 100644
index e5b7fb9..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.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.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
-import org.gradle.api.internal.project.AbstractProject;
-import org.gradle.util.HelperUtil;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-//TODO SF spockify
-public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private ProjectIvyDependencyDescriptorFactory projectDependencyDescriptorFactory =
-            new ProjectIvyDependencyDescriptorFactory(excludeRuleConverterStub);
-
-    @Test
-    public void canConvert() {
-        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
-        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
-    }
-
-    @Test
-    public void testCreateFromProjectDependency() {
-        ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
-        setUpDependency(projectDependency);
-        ProjectDependencyDescriptor dependencyDescriptor = (ProjectDependencyDescriptor) projectDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, projectDependency, moduleDescriptor);
-
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertFalse(dependencyDescriptor.isChanging());
-        assertFalse(dependencyDescriptor.isForce());
-        assertEquals(ModuleRevisionId.newInstance("someGroup", "test", "someVersion"), dependencyDescriptor.getDependencyRevisionId());
-        assertSame(projectDependency.getDependencyProject(), dependencyDescriptor.getTargetProject());
-    }
-
-    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
-        AbstractProject dependencyProject = HelperUtil.createRootProject();
-        dependencyProject.setGroup("someGroup");
-        dependencyProject.setVersion("someVersion");
-        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, null, true);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy
index 9b92d9b..5a3bb3f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy
@@ -16,19 +16,16 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies
 
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import spock.lang.Specification
 import org.apache.ivy.core.module.descriptor.*
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 class ReflectiveDependencyDescriptorFactoryTest extends Specification {
 
     def factory = new ReflectiveDependencyDescriptorFactory()
-    def mrid = ModuleRevisionId.newInstance("org.gradle", "gradle-core", "1.0")
-    def target = ModuleRevisionId.newInstance("org.gradle", "gradle-core", "2.0")
-    def dynamicId = ModuleRevisionId.newInstance("org.gradle", "gradle-core", "latest-integration")
+    def mrid = IvyUtil.createModuleRevisionId("org.gradle", "gradle-core", "1.0")
+    def target = IvyUtil.createModuleRevisionId("org.gradle", "gradle-core", "2.0")
+    def dynamicId = IvyUtil.createModuleRevisionId("org.gradle", "gradle-core", "latest-integration")
 
     ModuleDescriptor md = DefaultModuleDescriptor.newBasicInstance(mrid, new Date())
 
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 30d2d65..790f3c6 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
@@ -16,33 +16,32 @@
 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.artifacts.ModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
-import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData
+import org.gradle.api.internal.artifacts.ivyservice.BuildableComponentResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionResolver
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor
+import org.gradle.api.internal.artifacts.metadata.DependencyMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
+import org.gradle.api.internal.artifacts.metadata.MutableLocalComponentMetaData
 import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.initialization.ProjectAccessListener
 import spock.lang.Specification
 
 class ProjectDependencyResolverTest extends Specification {
-    final ProjectModuleRegistry registry = Mock()
-    final ModuleRevisionId moduleRevisionId = Mock()
-    final DependencyToModuleResolver target = Mock()
-    final ProjectAccessListener projectAccessListener = Mock()
-    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, target, projectAccessListener)
+    final ProjectComponentRegistry registry = Mock()
+    final DependencyToModuleVersionResolver target = Mock()
+    final LocalComponentFactory converter = Mock()
+    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, converter, target)
 
     def "resolves project dependency"() {
         setup:
-        1 * moduleRevisionId.organisation >> "group"
-        1 * moduleRevisionId.name >> "project"
-        1 * moduleRevisionId.revision >> "1.0"
-
-        def moduleDescriptor = Mock(ModuleDescriptor)
-        def result = Mock(BuildableModuleVersionResolveResult)
-        def dependencyProject = Mock(ProjectInternal)
+        def resolveMetaData = Stub(ModuleVersionMetaData)
+        def componentMetaData = Stub(MutableLocalComponentMetaData) {
+            toResolveMetaData() >> resolveMetaData
+        }
+        def result = Mock(BuildableComponentResolveResult)
+        def dependencyProject = Stub(ProjectInternal) {
+            getPath() >> ":project"
+        }
         def dependencyDescriptor = Stub(ProjectDependencyDescriptor) {
             getTargetProject() >> dependencyProject
         }
@@ -54,21 +53,14 @@ class ProjectDependencyResolverTest extends Specification {
         resolver.resolve(dependencyMetaData, result)
 
         then:
-        1 * registry.findProject(dependencyDescriptor) >> moduleDescriptor
-        _ * moduleDescriptor.moduleRevisionId >> moduleRevisionId
-        1 * result.resolved(_, moduleDescriptor, _) >> { args ->
-            ModuleVersionIdentifier moduleVersionIdentifier = args[0]
-            moduleVersionIdentifier.group == "group"
-            moduleVersionIdentifier.name == "project"
-            moduleVersionIdentifier.version == "1.0"
-        }
-        1 * projectAccessListener.beforeResolvingProjectDependency(dependencyProject)
+        1 * registry.getProject(":project") >> componentMetaData
+        1 * result.resolved(resolveMetaData)
         0 * result._
     }
 
     def "delegates to backing resolver for non-project dependency"() {
-        def result = Mock(BuildableModuleVersionResolveResult)
-        def dependencyDescriptor = Mock(DependencyDescriptor)
+        def result = Mock(BuildableComponentResolveResult)
+        def dependencyDescriptor = Stub(DependencyDescriptor)
         def dependencyMetaData = Stub(DependencyMetaData) {
             getDescriptor() >> dependencyDescriptor
         }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
index 10664b7..8a70368 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
@@ -83,7 +83,7 @@ public class DefaultCachePolicySpec extends Specification {
         })
 
         then:
-        cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
+        cachePolicy.mustRefreshVersionList(null, null, 2 * SECOND)
     }
 
     def "applies useCachedResult for dynamic versions"() {
@@ -95,7 +95,7 @@ public class DefaultCachePolicySpec extends Specification {
         })
 
         then:
-        !cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
+        !cachePolicy.mustRefreshVersionList(null, null, 2 * SECOND)
     }
 
     def "applies cacheFor rules for dynamic versions"() {
@@ -110,16 +110,17 @@ public class DefaultCachePolicySpec extends Specification {
         hasDynamicVersionTimeout(100 * SECOND)
     }
 
-    def "provides details of cached dynamic version"() {
+    def "provides details of cached version list"() {
         expect:
         cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
             void execute(DependencyResolutionControl t) {
-                assertId(t.request, 'g', 'n', 'v')
-                assertId(t.cachedResult, 'group', 'name', 'version')
+                assert t.request.group == 'g'
+                assert t.request.name == 'n'
+                assertId(t.cachedResult.iterator().next(), 'group', 'name', 'version')
                 t.refresh()
             }
         })
-        cachePolicy.mustRefreshDynamicVersion(moduleSelector('g', 'n', 'v'), moduleIdentifier('group', 'name', 'version'), 0)
+        cachePolicy.mustRefreshVersionList(moduleIdentifier('g', 'n', 'v').module, [moduleIdentifier('group', 'name', 'version')] as Set, 0)
     }
 
     def "provides details of cached module"() {
@@ -211,10 +212,10 @@ public class DefaultCachePolicySpec extends Specification {
 
     private def hasDynamicVersionTimeout(int timeout) {
         def moduleId = moduleIdentifier('group', 'name', 'version')
-        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, 100)
-        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout);
-        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout - 1)
-        cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout + 1)
+        assert !cachePolicy.mustRefreshVersionList(null, [moduleId] as Set, 100)
+        assert !cachePolicy.mustRefreshVersionList(null, [moduleId] as Set, timeout);
+        assert !cachePolicy.mustRefreshVersionList(null, [moduleId] as Set, timeout - 1)
+        cachePolicy.mustRefreshVersionList(null, [moduleId] as Set, timeout + 1)
     }
 
     private def hasChangingModuleTimeout(int timeout) {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
index 0d8a100..577f4eb 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
@@ -27,9 +27,6 @@ import java.util.concurrent.TimeUnit
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.util.Assertions.assertThat
 
-/**
- * by Szczepan Faber, created at: 11/2/11
- */
 public class DefaultResolutionStrategySpec extends Specification {
 
     def cachePolicy = Mock(DefaultCachePolicy)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
index 29b75eb..e806a87 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
@@ -22,9 +22,6 @@ import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
-/**
- * by Szczepan Faber, created at: 11/29/12
- */
 class ModuleForcingResolveRuleSpec extends Specification {
 
     def "forces modules"() {
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 48e159f..c9b42b8 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
@@ -17,47 +17,49 @@ package org.gradle.api.internal.artifacts.ivyservice.resolveengine
 
 import org.apache.ivy.core.module.descriptor.*
 import org.apache.ivy.core.module.id.ArtifactId
-import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.core.resolve.ResolveData
-import org.apache.ivy.core.resolve.ResolveEngine
-import org.apache.ivy.core.resolve.ResolveOptions
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
 import org.gradle.api.artifacts.*
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.ivyservice.*
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultBuildableModuleVersionMetaData
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolvedConfigurationListener
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.DefaultResolvedConfigurationBuilder
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResultsBuilder
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DummyBinaryStore
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DummyStore
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.api.internal.artifacts.metadata.ModuleDescriptorAdapter
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionMetaData
 import org.gradle.api.specs.Spec
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleRevisionId
 
 class DependencyGraphBuilderTest extends Specification {
-    final ModuleDescriptorConverter moduleDescriptorConverter = Mock()
-    final ResolvedArtifactFactory resolvedArtifactFactory = Mock()
     final ConfigurationInternal configuration = Mock()
-    final ResolveEngine resolveEngine = Mock()
-    final ResolveData resolveData = new ResolveData(resolveEngine, new ResolveOptions())
     final ModuleConflictResolver conflictResolver = Mock()
     final DependencyToModuleVersionIdResolver dependencyResolver = Mock()
-    final ResolvedConfigurationListener listener = Mock()
+    final ArtifactResolver artifactResolver = Mock()
+    final ResolutionResultBuilder resultBuilder = Mock()
     final ModuleVersionMetaData root = revision('root')
-    final DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, dependencyResolver, conflictResolver)
+    final ModuleToModuleVersionResolver moduleResolver = Mock()
+    final DependencyToConfigurationResolver dependencyToConfigurationResolver = new DefaultDependencyToConfigurationResolver()
+    final DependencyGraphBuilder builder = new DependencyGraphBuilder(dependencyResolver, moduleResolver, artifactResolver, conflictResolver, dependencyToConfigurationResolver)
 
     def setup() {
         config(root, 'root', 'default')
         _ * configuration.name >> 'root'
-        _ * moduleDescriptorConverter.convert(_, _) >> root.descriptor
-        _ * resolvedArtifactFactory.create(_, _, _) >> { owner, artifact, resolver ->
-            return new DefaultResolvedArtifact(owner, artifact, null)
+        _ * configuration.path >> 'root'
+        _ * moduleResolver.resolve(_, _, _) >> { it[2].resolved(root) }
+
+        _ * artifactResolver.resolveModuleArtifacts(_, _, _,) >> { ModuleVersionMetaData module, ArtifactResolveContext context, BuildableArtifactSetResolveResult result ->
+            result.resolved(module.artifacts)
         }
     }
 
@@ -72,14 +74,20 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
         modules(result) == ids(a, b, c)
     }
 
-    def "correctly notifies the resolved configuration listener"() {
+    private DefaultLenientConfiguration resolve() {
+        def results = new DefaultResolvedConfigurationBuilder(new TransientConfigurationResultsBuilder(new DummyBinaryStore(), new DummyStore()))
+        builder.resolve(configuration, resultBuilder, results)
+        new DefaultLenientConfiguration(configuration, results, Stub(CacheLockingManager))
+    }
+
+    def "correctly notifies the resolution result builder"() {
         given:
         def a = revision("a")
         def b = revision("b")
@@ -91,14 +99,14 @@ class DependencyGraphBuilderTest extends Specification {
         traversesMissing a, d
 
         when:
-        builder.resolve(configuration, resolveData, listener)
+        resolve()
 
         then:
-        1 * listener.start(newId("group", "root", "1.0"))
+        1 * resultBuilder.start(newId("group", "root", "1.0"), new DefaultModuleComponentIdentifier("group", "root", "1.0"))
         then:
-        1 * listener.resolvedConfiguration({ it.name == 'root' }, { it*.requested.name == ['a', 'b'] })
+        1 * resultBuilder.resolvedConfiguration({ it.name == 'root' }, { it*.requested.module == ['a', 'b'] })
         then:
-        1 * listener.resolvedConfiguration({ it.name == 'a' }, { it*.requested.name == ['c', 'd'] && it*.failure.count { it != null } == 1 })
+        1 * resultBuilder.resolvedConfiguration({ it.name == 'a' }, { it*.requested.module == ['c', 'd'] && it*.failure.count { it != null } == 1 })
     }
 
     def "does not resolve a given dynamic module selector more than once"() {
@@ -115,7 +123,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve c, d
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -138,13 +146,14 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve evicted, e
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            assert candidates*.revision == ['1.2', '1.1']
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            assert candidates*.version == ['1.2', '1.1']
+            return candidates.find { it.version == '1.2' }
         }
         0 * conflictResolver._
 
@@ -168,13 +177,14 @@ class DependencyGraphBuilderTest extends Specification {
         traverses selected, e
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            assert candidates*.revision == ['1.1', '1.2']
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            assert candidates*.version == ['1.1', '1.2']
+            return candidates.find { it.version == '1.2' }
         }
         0 * conflictResolver._
 
@@ -198,13 +208,14 @@ class DependencyGraphBuilderTest extends Specification {
         traverses selected, e
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            assert candidates*.revision == ['1.1', '1.2']
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            assert candidates*.version == ['1.1', '1.2']
+            return candidates.find { it.version == '1.2' }
         }
         0 * conflictResolver._
 
@@ -224,13 +235,14 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve evicted, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            assert candidates*.revision == ['1.2', '1.1']
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            assert candidates*.version == ['1.2', '1.1']
+            return candidates.find { it.version == '1.2' }
         }
         0 * conflictResolver._
 
@@ -253,13 +265,14 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, evicted
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            assert candidates*.revision == ['1.1', '1.2']
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            assert candidates*.version == ['1.1', '1.2']
+            return candidates.find { it.version == '1.2' }
         }
         0 * conflictResolver._
 
@@ -283,18 +296,21 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve selectedB, evictedA2
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select({ it*.revision == ['1.1', '1.2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select({ it*.version == ['1.1', '1.2'] }) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
-        1 * conflictResolver.select({ it*.revision == ['2.1', '2.2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '2.2' }
+        1 * conflictResolver.select({ it*.version == ['2.1', '2.2'] }) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '2.2' }
         }
-        1 * conflictResolver.select({ it*.revision == ['1.1', '1.2', '1.0'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select({ it*.version == ['1.1', '1.2', '1.0'] }) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
         0 * conflictResolver._
 
@@ -319,12 +335,13 @@ 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, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select({ it*.revision == ['1', '2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '2' }
+        1 * conflictResolver.select({ it*.version == ['1', '2'] }) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '2' }
         }
         0 * conflictResolver._
 
@@ -349,12 +366,13 @@ 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, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select({ it*.revision == ['1', '2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '2' }
+        1 * conflictResolver.select({ it*.version == ['1', '2'] }) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '2' }
         }
         0 * conflictResolver._
 
@@ -372,7 +390,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -387,12 +405,13 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve root, evicted
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
 
         and:
@@ -412,7 +431,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve d, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -433,7 +452,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses d, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -452,7 +471,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve c, a
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -475,7 +494,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve d, e
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -494,7 +513,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses a, b
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -512,7 +531,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -532,7 +551,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
         result.unresolvedModuleDependencies.size() == 1
@@ -562,7 +581,7 @@ class DependencyGraphBuilderTest extends Specification {
         brokenSelector a, 'unknown'
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
         result.unresolvedModuleDependencies.size() == 1
@@ -591,7 +610,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
         result.unresolvedModuleDependencies.size() == 1
@@ -621,7 +640,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
         result.unresolvedModuleDependencies.size() == 1
@@ -651,7 +670,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
         result.unresolvedModuleDependencies.size() == 1
@@ -680,7 +699,7 @@ class DependencyGraphBuilderTest extends Specification {
         traversesMissing b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
         result.unresolvedModuleDependencies.size() == 1
@@ -714,11 +733,12 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve selected, c
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
 
         when:
@@ -744,18 +764,19 @@ class DependencyGraphBuilderTest extends Specification {
         traversesMissing b, selected
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
 
         and:
         ResolveException e = thrown()
         e.cause instanceof ModuleVersionNotFoundException
-        e.cause.message.contains("group:root:1.0 > group:b:1.0")
+        e.cause.message.contains("group:root:1.0")
     }
 
     def "does not fail when conflict resolution evicts a version that does not exist"() {
@@ -768,12 +789,13 @@ class DependencyGraphBuilderTest extends Specification {
         traverses b, selected
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
 
         and:
@@ -792,12 +814,13 @@ class DependencyGraphBuilderTest extends Specification {
         traverses c, selected
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
-            return candidates.find { it.revision == '1.2' }
+        1 * conflictResolver.select(!null) >> {
+            Collection<ModuleRevisionResolveState> candidates = it[0]
+            return candidates.find { it.version == '1.2' }
         }
 
         and:
@@ -814,7 +837,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, evicted
 
         when:
-        def result = builder.resolve(configuration, resolveData, listener)
+        def result = resolve()
         result.rethrowFailure()
 
         then:
@@ -822,9 +845,8 @@ class DependencyGraphBuilderTest extends Specification {
     }
 
     def revision(String name, String revision = '1.0') {
-        DefaultModuleDescriptor descriptor = new DefaultModuleDescriptor(new ModuleRevisionId(new ModuleId("group", name), revision), "release", new Date())
-        DefaultBuildableModuleVersionMetaData metaData = new DefaultBuildableModuleVersionMetaData()
-        metaData.resolved(descriptor, false, null)
+        DefaultModuleDescriptor descriptor = new DefaultModuleDescriptor(createModuleRevisionId("group", name, revision), "release", new Date())
+        ModuleVersionMetaData metaData = new ModuleDescriptorAdapter(descriptor)
         config(metaData, 'default')
         descriptor.addArtifact('default', new DefaultArtifact(descriptor.moduleRevisionId, new Date(), "art1", "art", "zip"))
         return metaData
@@ -839,17 +861,17 @@ class DependencyGraphBuilderTest extends Specification {
     def traverses(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
         def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
         def idResolveResult = selectorResolvesTo(descriptor, to.id);
-        ModuleVersionResolveResult resolveResult = Mock()
+        ComponentResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> to.id
-        1 * resolveResult.metaData >> to
+        _ * resolveResult.id >> to.id
+        _ * resolveResult.metaData >> to
     }
 
     def doesNotResolve(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
         def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
         ModuleVersionIdResolveResult result = Mock()
         (0..1) * dependencyResolver.resolve({it.descriptor == descriptor}) >> result
-        (0..1) * result.id >> to.id;
+        _ * result.id >> to.id;
         _ * result.failure >> null
         _ * result.selectionReason >> null
         0 * result._
@@ -858,17 +880,17 @@ class DependencyGraphBuilderTest extends Specification {
     def traversesMissing(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
         def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
         def idResolveResult = selectorResolvesTo(descriptor, to.id)
-        ModuleVersionResolveResult resolveResult = Mock()
+        ComponentResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> { throw new ModuleVersionNotFoundException(newId("org", "a", "1.2")) }
+        _ * resolveResult.failure >> { return new ModuleVersionNotFoundException(newId("org", "a", "1.2")) }
     }
 
     def traversesBroken(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
         def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
         def idResolveResult = selectorResolvesTo(descriptor, to.id)
-        ModuleVersionResolveResult resolveResult = Mock()
+        ComponentResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> { throw new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken") }
+        _ * resolveResult.failure >> { return new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken") }
     }
 
     ModuleVersionIdentifier toModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
@@ -880,7 +902,7 @@ class DependencyGraphBuilderTest extends Specification {
     }
 
     def brokenSelector(Map<String, ?> args = [:], ModuleVersionMetaData from, String to) {
-        def descriptor = dependsOn(args, from.descriptor, ModuleRevisionId.newInstance("group", to, "1.0"))
+        def descriptor = dependsOn(args, from.descriptor, createModuleRevisionId("group", to, "1.0"))
         ModuleVersionIdResolveResult result = Mock()
         (0..1) * dependencyResolver.resolve({it.descriptor == descriptor}) >> result
         _ * result.failure >> new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
@@ -890,7 +912,7 @@ class DependencyGraphBuilderTest extends Specification {
 
     def dependsOn(Map<String, ?> args = [:], DefaultModuleDescriptor from, ModuleRevisionId to) {
         ModuleDependency moduleDependency = Mock()
-        def dependencyId = args.revision ? new ModuleRevisionId(to.moduleId, args.revision) : to
+        def dependencyId = args.revision ? createModuleRevisionId(to.moduleId.organisation, to.moduleId.name, args.revision) : to
         boolean transitive = args.transitive == null || args.transitive
         boolean force = args.force
         def descriptor = new EnhancedDependencyDescriptor(moduleDependency, from, dependencyId, force, false, transitive)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy
index f41d1a5..e9a4ca1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy
@@ -17,17 +17,18 @@ package org.gradle.api.internal.artifacts.ivyservice.resolveengine
 
 import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
 import org.apache.ivy.core.module.id.ArtifactId
-import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
-import spock.lang.Specification
 import org.apache.ivy.plugins.matcher.RegexpPatternMatcher
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.ivyservice.IvyUtil.createModuleId
 
 class ModuleVersionSpecTest extends Specification {
     def "accepts all module by default"() {
         def spec = ModuleVersionSpec.forExcludes()
 
         expect:
-        spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+        spec.isSatisfiedBy(createModuleId("org", "module"))
     }
 
     def "default specs accept the same modules as each other"() {
@@ -44,14 +45,14 @@ class ModuleVersionSpecTest extends Specification {
         def spec = ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4, rule5)
 
         expect:
-        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
-        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
-        !spec.isSatisfiedBy(ModuleId.newInstance("org2", "anything"))
-        !spec.isSatisfiedBy(ModuleId.newInstance("other", "module4"))
-        !spec.isSatisfiedBy(ModuleId.newInstance("regexp-72", "module12"))
-        spec.isSatisfiedBy(ModuleId.newInstance("org", "other"))
-        spec.isSatisfiedBy(ModuleId.newInstance("regexp-72", "other"))
-        spec.isSatisfiedBy(ModuleId.newInstance("regexp", "module2"))
+        !spec.isSatisfiedBy(createModuleId("org", "module"))
+        !spec.isSatisfiedBy(createModuleId("org", "module2"))
+        !spec.isSatisfiedBy(createModuleId("org2", "anything"))
+        !spec.isSatisfiedBy(createModuleId("other", "module4"))
+        !spec.isSatisfiedBy(createModuleId("regexp-72", "module12"))
+        spec.isSatisfiedBy(createModuleId("org", "other"))
+        spec.isSatisfiedBy(createModuleId("regexp-72", "other"))
+        spec.isSatisfiedBy(createModuleId("regexp", "module2"))
     }
 
     def "specs with the same set of exclude rules accept the same modules as each other"() {
@@ -263,11 +264,11 @@ class ModuleVersionSpecTest extends Specification {
         expect:
         def union = spec.union(spec2)
 
-        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
-        !union.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+        !spec.isSatisfiedBy(createModuleId("org", "module"))
+        !union.isSatisfiedBy(createModuleId("org", "module"))
 
-        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
-        union.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+        !spec.isSatisfiedBy(createModuleId("org", "module2"))
+        union.isSatisfiedBy(createModuleId("org", "module2"))
     }
 
     def "unions accepts same modules when original specs accept same modules"() {
@@ -306,15 +307,15 @@ class ModuleVersionSpecTest extends Specification {
         expect:
         def intersect = spec.intersect(spec2)
 
-        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
-        !intersect.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+        !spec.isSatisfiedBy(createModuleId("org", "module"))
+        !intersect.isSatisfiedBy(createModuleId("org", "module"))
 
-        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
-        !intersect.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+        !spec.isSatisfiedBy(createModuleId("org", "module2"))
+        !intersect.isSatisfiedBy(createModuleId("org", "module2"))
 
-        spec.isSatisfiedBy(ModuleId.newInstance("org", "module3"))
-        spec2.isSatisfiedBy(ModuleId.newInstance("org", "module3"))
-        intersect.isSatisfiedBy(ModuleId.newInstance("org", "module3"))
+        spec.isSatisfiedBy(createModuleId("org", "module3"))
+        spec2.isSatisfiedBy(createModuleId("org", "module3"))
+        intersect.isSatisfiedBy(createModuleId("org", "module3"))
     }
 
     def "intersection of a spec with itself returns the original spec"() {
@@ -358,10 +359,10 @@ class ModuleVersionSpecTest extends Specification {
     }
 
     def excludeRule(String org, String module) {
-        return new DefaultExcludeRule(new ArtifactId(ModuleId.newInstance(org, module), "ivy", "ivy", "ivy"), ExactPatternMatcher.INSTANCE, [:])
+        return new DefaultExcludeRule(new ArtifactId(createModuleId(org, module), "ivy", "ivy", "ivy"), ExactPatternMatcher.INSTANCE, [:])
     }
 
     def regExpExcludeRule(String org, String module) {
-        return new DefaultExcludeRule(new ArtifactId(ModuleId.newInstance(org, module), "ivy", "ivy", "ivy"), RegexpPatternMatcher.INSTANCE, [:])
+        return new DefaultExcludeRule(new ArtifactId(createModuleId(org, module), "ivy", "ivy", "ivy"), RegexpPatternMatcher.INSTANCE, [:])
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy
index 062c409..ff4ada1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy
@@ -16,30 +16,26 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine
 
-import spock.lang.Specification
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 class VersionSelectionReasonResolverTest extends Specification {
 
     def "configures selection reason"() {
         def delegate = Mock(ModuleConflictResolver)
         VersionSelectionReasonResolver resolver = new VersionSelectionReasonResolver(delegate)
 
-        def root = Mock(ModuleRevisionResolveState)
         def candidate1 = Mock(ModuleRevisionResolveState)
         def candidate2 = Mock(ModuleRevisionResolveState)
 
         when:
-        def out = resolver.select([candidate1, candidate2], root)
+        def out = resolver.select([candidate1, candidate2])
 
         then:
         out == candidate2
 
         and:
-        1 * delegate.select([candidate1, candidate2], root) >> candidate2
+        1 * delegate.select([candidate1, candidate2]) >> candidate2
         1 * candidate2.getSelectionReason() >> VersionSelectionReasons.REQUESTED
         1 * candidate2.setSelectionReason(VersionSelectionReasons.CONFLICT_RESOLUTION)
         0 * _._
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
index 1c1521c..7e1d0b2 100644
--- 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
@@ -16,16 +16,14 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result
 
+import org.gradle.api.artifacts.result.ComponentSelectionReason
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
 
-/**
- * by Szczepan Faber, created at: 10/1/12
- */
 class CachingDependencyResultFactoryTest extends Specification {
 
     CachingDependencyResultFactory factory = new CachingDependencyResultFactory()
@@ -51,15 +49,15 @@ class CachingDependencyResultFactoryTest extends Specification {
 
     def "creates and caches unresolved dependencies"() {
         def fromModule = newModule('from')
-        def selectedModule = Mock(ModuleVersionSelectionReason)
+        def selectedModule = Mock(ComponentSelectionReason)
 
         when:
-        def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
-        def same = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
+        def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo"))
+        def same = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo"))
 
-        def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, selectedModule, new ModuleVersionResolveException(selector('xxx'), "foo"))
-        def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
-        def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
+        def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('xxx'), "foo"))
+        def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo"))
+        def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo"))
 
         then:
         dep.is(same)
@@ -69,6 +67,10 @@ class CachingDependencyResultFactoryTest extends Specification {
     }
 
     def selector(String group='a', String module='a', String version='1') {
+        DefaultModuleComponentSelector.newSelector(group, module, version)
+    }
+
+    def moduleVersionSelector(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/ComponentIdentifierSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializerTest.groovy
new file mode 100644
index 0000000..b410cf2
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializerTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ModuleComponentIdentifier
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentIdentifier
+import org.gradle.messaging.serialize.SerializerSpec
+
+class ComponentIdentifierSerializerTest extends SerializerSpec {
+    ComponentIdentifierSerializer serializer = new ComponentIdentifierSerializer()
+
+    def "throws exception if null is provided"() {
+        when:
+        serialize(null, serializer)
+
+        then:
+        Throwable t = thrown(IllegalArgumentException)
+        t.message == 'Provided component identifier may not be null'
+    }
+
+    def "serializes ModuleComponentIdentifier"() {
+        given:
+        ModuleComponentIdentifier selection = new DefaultModuleComponentIdentifier('group-one', 'name-one', 'version-one')
+
+        when:
+        ModuleComponentIdentifier result = serialize(selection, serializer)
+
+        then:
+        result.group == 'group-one'
+        result.module == 'name-one'
+        result.version == 'version-one'
+    }
+
+    def "serializes BuildComponentIdentifier"() {
+        given:
+        ProjectComponentIdentifier selection = new DefaultProjectComponentIdentifier(':myPath')
+
+        when:
+        ProjectComponentIdentifier result = serialize(selection, serializer)
+
+        then:
+        result.projectPath == ':myPath'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasonSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasonSerializerTest.groovy
new file mode 100644
index 0000000..6e3a776
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasonSerializerTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ComponentSelectionReason
+import org.gradle.messaging.serialize.InputStreamBackedDecoder
+import org.gradle.messaging.serialize.SerializerSpec
+
+class ComponentSelectionReasonSerializerTest extends SerializerSpec {
+
+    private serializer = new ComponentSelectionReasonSerializer()
+
+    def "serializes"() {
+        expect:
+        check(VersionSelectionReasons.CONFLICT_RESOLUTION)
+        check(VersionSelectionReasons.CONFLICT_RESOLUTION_BY_RULE)
+        check(VersionSelectionReasons.FORCED)
+        check(VersionSelectionReasons.REQUESTED)
+        check(VersionSelectionReasons.ROOT)
+        check(VersionSelectionReasons.SELECTED_BY_RULE)
+    }
+
+    def "yields informative message on incorrect input"() {
+        when:
+        check({} as ComponentSelectionReason)
+        then:
+        thrown(IllegalArgumentException)
+
+        when:
+        serializer.read(new InputStreamBackedDecoder(new ByteArrayInputStream("foo".bytes)))
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+
+    void check(ComponentSelectionReason reason) {
+        def result = serialize(reason, serializer)
+        assert result == reason
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy
new file mode 100644
index 0000000..8171354
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ProjectComponentSelector
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.messaging.serialize.SerializerSpec
+
+public class ComponentSelectorSerializerTest extends SerializerSpec {
+    ComponentSelectorSerializer serializer = new ComponentSelectorSerializer()
+
+    def "throws exception if null is provided"() {
+        when:
+        serialize(null, serializer)
+
+        then:
+        Throwable t = thrown(IllegalArgumentException)
+        t.message == 'Provided component selector may not be null'
+    }
+
+    def "serializes ModuleComponentSelector"() {
+        given:
+        ModuleComponentSelector selection = new DefaultModuleComponentSelector('group-one', 'name-one', 'version-one')
+
+        when:
+        ModuleComponentSelector result = serialize(selection, serializer)
+
+        then:
+        result.group == 'group-one'
+        result.module == 'name-one'
+        result.version == 'version-one'
+    }
+
+    def "serializes BuildComponentSelector"() {
+        given:
+        ProjectComponentSelector selection = new DefaultProjectComponentSelector(':myPath')
+
+        when:
+        ProjectComponentSelector result = serialize(selection, serializer)
+
+        then:
+        result.projectPath == ':myPath'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilderSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilderSpec.groovy
new file mode 100644
index 0000000..feef498
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilderSpec.groovy
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.api.artifacts.result.ComponentSelectionReason
+import org.gradle.api.artifacts.result.ResolvedDependencyResult
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+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.ResolutionResultPrinter.printGraph
+
+class DefaultResolutionResultBuilderSpec extends Specification {
+
+    def builder = new DefaultResolutionResultBuilder()
+
+    def "builds basic graph"() {
+        given:
+        builder.start(confId("root"), createComponentIdentifier("root"))
+
+        node("root")
+        node("mid1")
+        node("mid2")
+        node("leaf1")
+        node("leaf2")
+        node("leaf3")
+        node("leaf4")
+
+        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.complete()
+
+        then:
+        printGraph(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"), createComponentIdentifier("a"))
+
+        node("a")
+        node("b1")
+        node("b2")
+        node("b3")
+
+        resolvedConf("a", [dep("b1"), dep("b2"), dep("b3")])
+
+        resolvedConf("b1", [dep("b2"), dep("b3")])
+        resolvedConf("b2", [dep("b3")])
+        resolvedConf("b3", [])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(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"), createComponentIdentifier("a"))
+        node("a")
+        node("b")
+        node("c")
+        resolvedConf("a", [dep("b")])
+        resolvedConf("b", [dep("c")])
+        resolvedConf("c", [dep("a")])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(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"), createComponentIdentifier("a"))
+        node("b", VersionSelectionReasons.FORCED)
+        node("c", VersionSelectionReasons.CONFLICT_RESOLUTION)
+        node("d")
+        resolvedConf("a", [dep("b"), dep("c"), dep("d", new RuntimeException("Boo!"))])
+        resolvedConf("b", [])
+        resolvedConf("c", [])
+        resolvedConf("d", [])
+
+        when:
+        def deps = builder.complete().root.dependencies
+
+        then:
+        def b = deps.find { it.selected.id.module == 'b' }
+        def c = deps.find { it.selected.id.module == 'c' }
+
+        b.selected.selectionReason.forced
+        c.selected.selectionReason.conflictResolution
+    }
+
+    def "links dependents correctly"() {
+        given:
+        builder.start(confId("a"), createComponentIdentifier("a"))
+        node("a")
+        node("b")
+        node("c")
+        resolvedConf("a", [dep("b")])
+        resolvedConf("b", [dep("c")])
+        resolvedConf("c", [dep("a")])
+
+        when:
+        def a = builder.complete().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"), createComponentIdentifier("root"))
+        node("root")
+        node("mid1")
+        node("leaf1")
+        node("leaf2")
+
+        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.complete()
+
+        then:
+        printGraph(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"), createComponentIdentifier("root"))
+        node("mid1")
+        node("leaf1")
+        node("leaf2")
+        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.complete()
+
+        then:
+        def mid1 = first(result.root.dependencies)
+        mid1.selected.dependencies.size() == 2
+        mid1.selected.dependencies*.requested.module == ['leaf1', 'leaf2']
+    }
+
+    def "graph includes unresolved deps"() {
+        given:
+        builder.start(confId("a"), createComponentIdentifier("a"))
+        node("b")
+        node("c")
+        resolvedConf("a", [dep("b"), dep("c"), dep("U", new RuntimeException("unresolved!"))])
+        resolvedConf("b", [])
+        resolvedConf("c", [])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(result.root) == """x:a:1
+  x:b:1 [a]
+  x:c:1 [a]
+  x:U:1 -> x:U:1 - Could not resolve x:U:1.
+"""
+    }
+
+    private void node(String module, ComponentSelectionReason reason = VersionSelectionReasons.REQUESTED) {
+        def moduleVersion = new DummyModuleVersionSelection(selectedId: newId("x", module, "1"), selectionReason: reason, componentId: new DefaultModuleComponentIdentifier("x", module, "1"))
+        builder.resolvedModuleVersion(moduleVersion)
+    }
+
+    private void resolvedConf(String module, List<InternalDependencyResult> deps) {
+        builder.resolvedConfiguration(confId(module), deps)
+    }
+
+    private InternalDependencyResult dep(String requested, Exception failure = null, String selected = requested) {
+        def selection = newId("x", selected, "1")
+        def selector = new DefaultModuleComponentSelector("x", requested, "1")
+        def moduleVersionSelector = newSelector("x", requested, "1")
+        failure = failure == null ? null : new ModuleVersionResolveException(moduleVersionSelector, failure)
+        new DummyInternalDependencyResult(requested: selector, selected: selection, failure: failure)
+    }
+
+    private ModuleVersionIdentifier confId(String module) {
+        newId("x", module, "1")
+    }
+
+    private ComponentIdentifier createComponentIdentifier(String module) {
+        new DefaultModuleComponentIdentifier("x", module, "1")
+    }
+
+    class DummyModuleVersionSelection implements ModuleVersionSelection {
+        ModuleVersionIdentifier selectedId
+        ComponentSelectionReason selectionReason
+        ComponentIdentifier componentId
+    }
+
+    class DummyInternalDependencyResult implements InternalDependencyResult {
+        ComponentSelector requested
+        ModuleVersionIdentifier selected
+        ModuleVersionResolveException failure
+        ComponentSelectionReason reason
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DummyBinaryStore.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DummyBinaryStore.groovy
new file mode 100644
index 0000000..7661972
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DummyBinaryStore.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.cache.BinaryStore
+import org.gradle.messaging.serialize.Decoder
+import org.gradle.messaging.serialize.Encoder
+import org.gradle.messaging.serialize.InputStreamBackedDecoder
+import org.gradle.messaging.serialize.OutputStreamBackedEncoder
+
+public class DummyBinaryStore implements BinaryStore {
+
+    private final ByteArrayOutputStream bytes = new ByteArrayOutputStream()
+    private Encoder output = new OutputStreamBackedEncoder(bytes)
+
+    void write(BinaryStore.WriteAction write) {
+        write.write(output)
+    }
+
+    BinaryStore.BinaryData done() {
+        new BinaryStore.BinaryData() {
+            Decoder decoder
+            def <T> T read(BinaryStore.ReadAction<T> readAction) {
+                if (decoder == null) {
+                    decoder = new InputStreamBackedDecoder(new ByteArrayInputStream(bytes.toByteArray()))
+                }
+                readAction.read(decoder)
+            }
+
+            void close() {
+                decoder = null
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DummyStore.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DummyStore.groovy
new file mode 100644
index 0000000..03ee7aa
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DummyStore.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.cache.Store
+
+class DummyStore implements Store<Object> {
+    Object load(org.gradle.internal.Factory<Object> createIfNotPresent) {
+        return createIfNotPresent.create();
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResultSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResultSerializerTest.groovy
new file mode 100644
index 0000000..d575fbc
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResultSerializerTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.component.ModuleComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+import org.gradle.messaging.serialize.InputStreamBackedDecoder
+import org.gradle.messaging.serialize.OutputStreamBackedEncoder
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class InternalDependencyResultSerializerTest extends Specification {
+
+    def serializer = new InternalDependencyResultSerializer()
+
+    def "serializes successful dependency result"() {
+        def successful = Mock(InternalDependencyResult) {
+            getRequested() >> DefaultModuleComponentSelector.newSelector("org", "foo", "1.0")
+            getFailure() >> null
+            getSelected() >> newId("org", "foo", "1.0")
+            getReason() >> VersionSelectionReasons.REQUESTED
+        }
+
+        when:
+        def bytes = new ByteArrayOutputStream()
+        def encoder = new OutputStreamBackedEncoder(bytes)
+        serializer.write(encoder, successful)
+        encoder.flush()
+        def out = serializer.read(new InputStreamBackedDecoder(new ByteArrayInputStream(bytes.toByteArray())), [:])
+
+        then:
+        out.requested == DefaultModuleComponentSelector.newSelector("org", "foo", "1.0")
+        out.failure == null
+        out.selected == newId("org", "foo", "1.0")
+    }
+
+    def "serializes failed dependency result"() {
+        ModuleComponentSelector requested = DefaultModuleComponentSelector.newSelector("x", "y", "1.0")
+        def failure = new ModuleVersionResolveException(newSelector("x", "y", "1.2"), new RuntimeException("Boo!"))
+
+        def failed = Mock(InternalDependencyResult) {
+            getRequested() >> requested
+            getFailure() >> failure
+            getSelected() >> null
+            getReason() >> VersionSelectionReasons.CONFLICT_RESOLUTION
+        }
+
+        when:
+        def bytes = new ByteArrayOutputStream()
+        def encoder = new OutputStreamBackedEncoder(bytes)
+        serializer.write(encoder, failed)
+        encoder.flush()
+        Map<ModuleComponentSelector, ModuleVersionResolveException> map = new HashMap<>()
+        map.put(requested, failure)
+        def out = serializer.read(new InputStreamBackedDecoder(new ByteArrayInputStream(bytes.toByteArray())), map)
+
+        then:
+        out.requested == DefaultModuleComponentSelector.newSelector("x", "y", "1.0")
+        out.failure.cause.message == "Boo!"
+        out.selected == null
+        out.reason == VersionSelectionReasons.CONFLICT_RESOLUTION
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelectionSerializerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelectionSerializerTest.groovy
new file mode 100644
index 0000000..499ecb8
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelectionSerializerTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.messaging.serialize.SerializerSpec
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+
+class ModuleVersionSelectionSerializerTest extends SerializerSpec {
+
+    def serializer = new ModuleVersionSelectionSerializer()
+
+    def "serializes"() {
+        def componentIdentifier = new DefaultModuleComponentIdentifier('group', 'module', 'version')
+        def selection = new DefaultModuleVersionSelection(newId("org", "foo", "2.0"), VersionSelectionReasons.REQUESTED, componentIdentifier)
+
+        when:
+        def result = serialize(selection, serializer)
+
+        then:
+        result.selectionReason == VersionSelectionReasons.REQUESTED
+        result.selectedId == newId("org", "foo", "2.0")
+        result.componentId == componentIdentifier
+    }
+}
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
deleted file mode 100644
index 68749dd..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy
+++ /dev/null
@@ -1,268 +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.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
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
-
-/**
- * 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 -> x:U:1 - Could not resolve x:U:1.
-"""
-    }
-
-    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 = new DummyModuleVersionSelection(selectedId: newId("x", selected, "1"), selectionReason: selectionReason)
-        def selector = newSelector("x", requested, "1")
-        failure = failure == null ? null : new ModuleVersionResolveException(selector, failure)
-        new DummyInternalDependencyResult(requested: selector, 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
-        ModuleVersionResolveException failure
-        ModuleVersionSelectionReason reason
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultPrinter.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultPrinter.groovy
new file mode 100644
index 0000000..7042d4f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultPrinter.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.DependencyResult
+import org.gradle.api.artifacts.result.ResolvedComponentResult
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+
+public class ResolutionResultPrinter {
+    private static void printNode(DependencyResult dep, StringBuilder sb, Set visited, String indent) {
+        if (dep instanceof UnresolvedDependencyResult) {
+            sb.append(indent + dep + "\n");
+            return
+        }
+        if (!visited.add(dep.getSelected())) {
+            return
+        }
+        String reason = dep.selected.selectionReason.conflictResolution? "(C)" : "";
+        sb.append(indent + dep + reason + " [" + dep.selected.dependents*.from.id.module.join(",") + "]\n");
+        for (DependencyResult d : dep.getSelected().getDependencies()) {
+            printNode(d, sb, visited, "  " + indent);
+        }
+    }
+
+    static String printGraph(ResolvedComponentResult root) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(root).append("\n");
+        for (DependencyResult d : root.getDependencies()) {
+            printNode(d, sb, new HashSet(), "  ");
+        }
+
+        sb.toString();
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/StreamingResolutionResultBuilderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/StreamingResolutionResultBuilderTest.groovy
new file mode 100644
index 0000000..1efdecc
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/StreamingResolutionResultBuilderTest.groovy
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ComponentSelectionReason
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+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.ResolutionResultPrinter.printGraph
+import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.CONFLICT_RESOLUTION
+import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.REQUESTED
+
+class StreamingResolutionResultBuilderTest extends Specification {
+
+    StreamingResolutionResultBuilder builder = new StreamingResolutionResultBuilder(new DummyBinaryStore(), new DummyStore())
+
+    def "result can be read multiple times"() {
+        builder.start(newId("org", "root", "1.0"), new DefaultModuleComponentIdentifier("org", "root", "1.0"))
+
+        when:
+        def result = builder.complete()
+
+        then:
+        with(result) {
+            root.id == DefaultModuleComponentIdentifier.newId("org", "root", "1.0")
+            root.selectionReason == VersionSelectionReasons.ROOT
+        }
+        printGraph(result.root) == """org:root:1.0
+"""
+    }
+
+    def "maintains graph in byte stream"() {
+        builder.start(newId("org", "root", "1.0"), new DefaultModuleComponentIdentifier("org", "root", "1.0"))
+
+        builder.resolvedModuleVersion(sel("org", "dep1", "2.0", CONFLICT_RESOLUTION))
+        builder.resolvedConfiguration(newId("org", "root", "1.0"), [
+                new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep1", "2.0"), newId("org", "dep1", "2.0"), CONFLICT_RESOLUTION, null),
+                new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep2", "3.0"), null, CONFLICT_RESOLUTION, new ModuleVersionResolveException(newSelector("org", "dep2", "3.0"), new RuntimeException("Boo!")))
+        ])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(result.root) == """org:root:1.0
+  org:dep1:2.0(C) [root]
+  org:dep2:3.0 -> org:dep2:3.0 - Could not resolve org:dep2:3.0.
+"""
+    }
+
+    def "visiting resolved module version again has no effect"() {
+        builder.start(newId("org", "root", "1.0"), new DefaultModuleComponentIdentifier("org", "root", "1.0"))
+        builder.resolvedModuleVersion(sel("org", "root", "1.0", REQUESTED)) //it's fine
+
+        builder.resolvedModuleVersion(sel("org", "dep1", "2.0", CONFLICT_RESOLUTION))
+        builder.resolvedModuleVersion(sel("org", "dep1", "2.0", REQUESTED)) //will be ignored
+
+        builder.resolvedConfiguration(newId("org", "root", "1.0"),
+                [new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep1", "2.0"), newId("org", "dep1", "2.0"), CONFLICT_RESOLUTION, null)])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(result.root) == """org:root:1.0
+  org:dep1:2.0(C) [root]
+"""
+    }
+
+    def "visiting resolved configuration again accumulates dependencies"() {
+        builder.start(newId("org", "root", "1.0"), new DefaultModuleComponentIdentifier("org", "root", "1.0"))
+
+        builder.resolvedModuleVersion(sel("org", "dep1", "2.0", REQUESTED))
+        builder.resolvedModuleVersion(sel("org", "dep2", "2.0", REQUESTED))
+
+        builder.resolvedConfiguration(newId("org", "root", "1.0"), [
+                new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep1", "2.0"), newId("org", "dep1", "2.0"), REQUESTED, null),
+        ])
+        builder.resolvedConfiguration(newId("org", "root", "1.0"), [
+                new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep2", "2.0"), newId("org", "dep2", "2.0"), REQUESTED, null),
+        ])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(result.root) == """org:root:1.0
+  org:dep1:2.0 [root]
+  org:dep2:2.0 [root]
+"""
+    }
+
+    def "dependency failures are remembered"() {
+        builder.start(newId("org", "root", "1.0"), new DefaultModuleComponentIdentifier("org", "root", "1.0"))
+
+        builder.resolvedModuleVersion(sel("org", "dep1", "2.0", REQUESTED))
+        builder.resolvedModuleVersion(sel("org", "dep2", "2.0", REQUESTED))
+
+        builder.resolvedConfiguration(newId("org", "root", "1.0"), [
+            new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep1", "2.0"), null, REQUESTED, new ModuleVersionResolveException(newSelector("org", "dep1", "1.0"), new RuntimeException())),
+            new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep2", "2.0"), newId("org", "dep2", "2.0"), REQUESTED, null),
+        ])
+        builder.resolvedConfiguration(newId("org", "dep2", "2.0"), [
+            new DefaultInternalDependencyResult(DefaultModuleComponentSelector.newSelector("org", "dep1", "5.0"), null, REQUESTED, new ModuleVersionResolveException(newSelector("org", "dep1", "5.0"), new RuntimeException())),
+        ])
+
+        when:
+        def result = builder.complete()
+
+        then:
+        printGraph(result.root) == """org:root:1.0
+  org:dep1:2.0 -> org:dep1:1.0 - Could not resolve org:dep1:1.0.
+  org:dep2:2.0 [root]
+    org:dep1:5.0 -> org:dep1:5.0 - Could not resolve org:dep1:5.0.
+"""
+    }
+
+    private DefaultModuleVersionSelection sel(String org, String name, String ver, ComponentSelectionReason reason) {
+        new DefaultModuleVersionSelection(newId(org, name, ver), reason, new DefaultModuleComponentIdentifier(org, name, ver))
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy
index 1584706..164491e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy
@@ -20,9 +20,6 @@ import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.*
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 class VersionSelectionReasonsTest extends Specification {
 
     def "decorates with conflict resolution"() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/CachedStoreFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/CachedStoreFactoryTest.groovy
new file mode 100644
index 0000000..6bf7e78
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/CachedStoreFactoryTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.store
+
+import org.gradle.internal.Factory
+import spock.lang.Specification
+
+class CachedStoreFactoryTest extends Specification {
+
+    def "stores results"() {
+        def factory = new CachedStoreFactory("some cache")
+
+        def results1 = new Object()
+        def results2 = new Object()
+
+        def store1 = factory.createCachedStore("conf1")
+        def store1b = factory.createCachedStore("conf1")
+        def store2 = factory.createCachedStore("conf2")
+
+        expect:
+        store1.load({results1} as Factory) == results1
+        store1.load({assert false} as Factory) == results1
+        store1b.load({assert false} as Factory) == results1
+        store2.load({results2} as Factory) == results2
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy
new file mode 100644
index 0000000..c12aead
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine.store
+
+import org.gradle.api.internal.cache.BinaryStore
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultBinaryStoreTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def "stores binary data"() {
+        def store = new DefaultBinaryStore(temp.file("foo.bin"))
+
+        when:
+        store.write({ it.writeInt(10) } as BinaryStore.WriteAction)
+        store.write({ it.writeString("x") } as BinaryStore.WriteAction)
+        def data1 = store.done()
+        store.write({ it.writeString("y") } as BinaryStore.WriteAction)
+        def data2 = store.done()
+
+        then:
+        data1.read({ it.readInt() } as BinaryStore.ReadAction) == 10
+        data1.read({ it.readString() } as BinaryStore.ReadAction) == "x"
+        data1.close()
+
+        data2.read({ it.readString() } as BinaryStore.ReadAction) == "y"
+        data2.close()
+
+        cleanup:
+        store.close()
+    }
+
+    def "data can be re-read"() {
+        def store = new DefaultBinaryStore(temp.file("foo.bin"))
+
+        when:
+        store.write({ it.writeInt(10) } as BinaryStore.WriteAction)
+        store.write({ it.writeString("x") } as BinaryStore.WriteAction)
+        def data = store.done()
+
+        then:
+        data.read({ it.readInt() } as BinaryStore.ReadAction) == 10
+        data.read({ it.readString() } as BinaryStore.ReadAction) == "x"
+        data.close()
+
+        then:
+        data.read({ it.readInt() } as BinaryStore.ReadAction) == 10
+        data.read({ it.readString() } as BinaryStore.ReadAction) == "x"
+        data.close()
+
+        cleanup:
+        store.close()
+    }
+
+    class SomeException extends RuntimeException {}
+
+    def "write action exception is propagated to the client"() {
+        def store = new DefaultBinaryStore(temp.file("foo.bin"))
+
+        when:
+        store.write({ throw new SomeException() } as BinaryStore.WriteAction)
+
+        then:
+        def e = thrown(Exception)
+        e.cause.class == SomeException
+    }
+
+    def "read action exception is propagated to the client"() {
+        def store = new DefaultBinaryStore(temp.file("foo.bin"))
+        store.write({ it.writeInt(10) } as BinaryStore.WriteAction)
+        def data = store.done()
+
+        when:
+        data.read({ throw new SomeException() } as BinaryStore.ReadAction)
+
+        then:
+        def e = thrown(Exception)
+        e.cause.class == SomeException
+    }
+
+    def "may be empty"() {
+        def store = new DefaultBinaryStore(temp.file("foo.bin"))
+
+        when:
+        def data = store.done()
+        store.close()
+
+        then:
+        data.close()
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/ResolutionResultsStoreFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/ResolutionResultsStoreFactoryTest.groovy
new file mode 100644
index 0000000..d6e513a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/ResolutionResultsStoreFactoryTest.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine.store
+
+import org.gradle.api.internal.cache.BinaryStore
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider
+import org.gradle.internal.concurrent.CompositeStoppable
+import spock.lang.Specification
+
+class ResolutionResultsStoreFactoryTest extends Specification {
+
+    def f = new ResolutionResultsStoreFactory(new TmpDirTemporaryFileProvider())
+
+    def "provides binary stores"() {
+        def stores = f.createStoreSet()
+        def store1 = stores.nextBinaryStore()
+        def store2 = stores.nextBinaryStore()
+
+        expect:
+        store1 != store2
+        store1 == f.createStoreSet().nextBinaryStore()
+    }
+
+    def "rolls the file"() {
+        f = new ResolutionResultsStoreFactory(new TmpDirTemporaryFileProvider(), 2)
+
+        when:
+        def store = f.createStoreSet().nextBinaryStore()
+        store.write({it.writeByte((byte) 1); it.writeByte((byte) 2) } as BinaryStore.WriteAction)
+        store.done()
+        def store2 = f.createStoreSet().nextBinaryStore()
+
+        then:
+        store.file == store2.file
+
+        when:
+        store2.write({it.writeByte((byte) 4)} as BinaryStore.WriteAction)
+        store2.done()
+
+        then:
+        f.createStoreSet().nextBinaryStore().file != store2.file
+    }
+
+    def "cleans up binary files"() {
+        f = new ResolutionResultsStoreFactory(new TmpDirTemporaryFileProvider(), 1);
+        def stores1 = f.createStoreSet()
+
+        when:
+        def store = stores1.nextBinaryStore()
+        store.write({it.writeByte((byte) 1); it.writeByte((byte) 2) } as BinaryStore.WriteAction)
+        store.done()
+        def store2 = stores1.nextBinaryStore() // rolled
+        def store3 = f.createStoreSet().nextBinaryStore()
+
+        then:
+        store.file != store2.file //rolled
+        [store.file, store2.file, store3.file].each { it.exists() }
+
+        when:
+        new CompositeStoppable().add(store, store2, store3)
+
+        then:
+        [store.file, store2.file, store3.file].each { !it.exists() }
+    }
+
+    def "provides stores"() {
+        def set1 = f.createStoreSet()
+        def set2 = f.createStoreSet()
+
+        expect:
+        set1.newModelStore().load({"1"} as org.gradle.internal.Factory) == "1"
+        set1.newModelStore().load({"2"} as org.gradle.internal.Factory) == "1"
+        set2.newModelStore().load({"3"} as org.gradle.internal.Factory) == "3"
+
+        set1.oldModelStore().load({"1"} as org.gradle.internal.Factory) == "1"
+        set1.oldModelStore().load({"2"} as org.gradle.internal.Factory) == "1"
+        set2.oldModelStore().load({"3"} as org.gradle.internal.Factory) == "3"
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultDependencyMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultDependencyMetaDataTest.groovy
new file mode 100644
index 0000000..d181764
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultDependencyMetaDataTest.groovy
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+import org.gradle.api.artifacts.component.ProjectComponentSelector
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.initialization.ProjectAccessListener
+import spock.lang.Specification
+
+class DefaultDependencyMetaDataTest extends Specification {
+    final requestedModuleId = IvyUtil.createModuleRevisionId("org", "module", "1.2+")
+
+    def "constructs selector from descriptor"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        expect:
+        metaData.requested == DefaultModuleVersionSelector.newSelector("org", "module", "1.2+")
+    }
+
+    def "creates a copy with new requested version"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        given:
+
+        when:
+        def copy = metaData.withRequestedVersion("1.3+")
+
+        then:
+        copy.requested == DefaultModuleVersionSelector.newSelector("org", "module", "1.3+")
+        copy.descriptor.dependencyRevisionId == IvyUtil.createModuleRevisionId("org", "module", "1.3+")
+    }
+
+    def "returns this if new requested version is the same as current requested version"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        expect:
+        metaData.withRequestedVersion("1.2+").is(metaData)
+        metaData.withRequestedVersion(DefaultModuleVersionSelector.newSelector("org", "module", "1.2+")).is(metaData)
+    }
+
+    def "can set changing flag"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        when:
+        def copy = metaData.withChanging()
+
+        then:
+        copy.descriptor.dependencyRevisionId == requestedModuleId
+        copy.descriptor.changing
+    }
+
+    def "returns this when changing is already true"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, true)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        expect:
+        metaData.withChanging().is(metaData)
+    }
+
+    def "returns empty set of artifacts when dependency descriptor does not declare any artifacts"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+        def fromConfiguration = Stub(ConfigurationMetaData)
+        def toConfiguration = Stub(ConfigurationMetaData)
+
+        expect:
+        metaData.getArtifacts(fromConfiguration, toConfiguration).empty
+    }
+
+    def "returns empty set of artifacts when dependency descriptor does not declare any artifacts for source configuration"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+        def fromConfiguration = Stub(ConfigurationMetaData)
+        def toConfiguration = Stub(ConfigurationMetaData)
+
+        given:
+        descriptor.addDependencyArtifact("other", new DefaultDependencyArtifactDescriptor(descriptor, "art", "type", "ext", null, [:]))
+
+        expect:
+        metaData.getArtifacts(fromConfiguration, toConfiguration).empty
+    }
+
+    def "uses artifacts defined by dependency descriptor"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+        def fromConfiguration = Stub(ConfigurationMetaData)
+        def targetComponent = Stub(ComponentMetaData)
+        def toConfiguration = Stub(ConfigurationMetaData)
+        def artifact1 = Stub(ComponentArtifactMetaData)
+        def artifact2 = Stub(ComponentArtifactMetaData)
+
+        given:
+        fromConfiguration.hierarchy >> (['config', 'super'] as LinkedHashSet)
+        toConfiguration.component >> targetComponent
+        descriptor.addDependencyArtifact("config", new DefaultDependencyArtifactDescriptor(descriptor, "art1", "type", "ext", null, [:]))
+        descriptor.addDependencyArtifact("other", new DefaultDependencyArtifactDescriptor(descriptor, "art2", "type", "ext", null, [:]))
+        descriptor.addDependencyArtifact("super", new DefaultDependencyArtifactDescriptor(descriptor, "art3", "type", "ext", null, [:]))
+        targetComponent.artifact({it.name == 'art1'}) >> artifact1
+        targetComponent.artifact({it.name == 'art3'}) >> artifact2
+
+        expect:
+        metaData.getArtifacts(fromConfiguration, toConfiguration) == [artifact1, artifact2] as Set
+    }
+
+    def "returns a build component selector if descriptor indicates a project dependency"() {
+        given:
+        def project = Mock(ProjectInternal)
+        def projectDependency = new DefaultProjectDependency(project, 'conf1', {} as ProjectAccessListener, true)
+        def descriptor = new ProjectDependencyDescriptor(projectDependency, DefaultModuleDescriptor.newDefaultInstance(requestedModuleId), requestedModuleId, false, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        when:
+        ComponentSelector componentSelector = metaData.getSelector()
+
+        then:
+        1 * project.path >> ':myPath'
+        componentSelector instanceof ProjectComponentSelector
+        componentSelector.projectPath == ':myPath'
+    }
+
+    def "returns a module component selector if descriptor indicates a default dependency"() {
+        given:
+        def descriptor = new DefaultDependencyDescriptor(DefaultModuleDescriptor.newDefaultInstance(requestedModuleId), requestedModuleId, false, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        when:
+        ComponentSelector componentSelector = metaData.getSelector()
+
+        then:
+        componentSelector instanceof ModuleComponentSelector
+        componentSelector.group == 'org'
+        componentSelector.module == 'module'
+        componentSelector.version == '1.2+'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultIvyArtifactNameTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultIvyArtifactNameTest.groovy
new file mode 100644
index 0000000..c54be68
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultIvyArtifactNameTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+class DefaultIvyArtifactNameTest extends Specification {
+    def "has useful string representation"() {
+        expect:
+        def name = new DefaultIvyArtifactName("name", "type", "ext", [attr1: "attr"])
+        name.toString() == "name.ext"
+    }
+
+    def "is equal when all fields are equal"() {
+        def name = new DefaultIvyArtifactName("name", "type", "ext", [attr1: "attr"])
+        def same = new DefaultIvyArtifactName("name", "type", "ext", [attr1: "attr"])
+        def differentName = new DefaultIvyArtifactName("other", "type", "ext", [attr1: "attr"])
+        def differentType = new DefaultIvyArtifactName("name", "other", "ext", [attr1: "attr"])
+        def differentExt = new DefaultIvyArtifactName("name", "type", "other", [attr1: "attr"])
+        def differentAttr = new DefaultIvyArtifactName("name", "type", "ext", [attr1: "other"])
+        def differentAttrs = new DefaultIvyArtifactName("name", "type", "ext", [other: null])
+
+        expect:
+        name Matchers.strictlyEqual(same)
+        name != differentName
+        name != differentType
+        name != differentExt
+        name != differentAttr
+        name != differentAttrs
+    }
+
+    def "uses extended attributes to determine classifier"() {
+        def name = new DefaultIvyArtifactName("name", "type", "ext", ['classifier': 'classifier'])
+
+        expect:
+        name.classifier == 'classifier'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalArtifactIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalArtifactIdentifierTest.groovy
new file mode 100644
index 0000000..eacb21d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalArtifactIdentifierTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+class DefaultLocalArtifactIdentifierTest extends Specification {
+    def "has useful string representation"() {
+        def componentId = Stub(ComponentIdentifier)
+
+        expect:
+        def noClassifier = new DefaultLocalArtifactIdentifier(componentId, "<comp>", "name", "type", "ext", [:])
+        noClassifier.displayName == "<comp>:name.ext"
+        noClassifier.toString() == "<comp>:name.ext"
+
+        def withClassifier = new DefaultLocalArtifactIdentifier(componentId, "<comp>", "name", "type", "ext", ['classifier': 'classifier'])
+        withClassifier.displayName == "<comp>:name-classifier.ext"
+        withClassifier.toString() == "<comp>:name-classifier.ext"
+
+        def noExtension = new DefaultLocalArtifactIdentifier(componentId, "<comp>", "name", "type", null, ['classifier': 'classifier'])
+        noExtension.displayName == "<comp>:name-classifier"
+        noExtension.toString() == "<comp>:name-classifier"
+    }
+
+    def "is equal when all attributes and module version are the same"() {
+        def moduleVersion = DefaultModuleVersionIdentifier.newId("group", "module", "version")
+        def componentId = DefaultModuleComponentIdentifier.newId(moduleVersion)
+
+        def withClassifier = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "type", "ext", ['classifier': 'classifier'])
+        def same = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "type", "ext", ['classifier': 'classifier'])
+        def differentName = new DefaultLocalArtifactIdentifier(componentId, "comp", "2", "type", "ext", ['classifier': 'classifier'])
+        def differentType = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "2", "ext", ['classifier': 'classifier'])
+        def differentExt = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "type", "2", ['classifier': 'classifier'])
+        def differentAttributes = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "type", "ext", ['classifier': '2'])
+        def emptyParts = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "type", null, [:])
+        def emptyPartsSame = new DefaultLocalArtifactIdentifier(componentId, "comp", "name", "type", null, [:])
+
+        expect:
+        withClassifier Matchers.strictlyEqual(same)
+        withClassifier != differentName
+        withClassifier != differentType
+        withClassifier != differentExt
+        withClassifier != differentAttributes
+        withClassifier != emptyParts
+
+        emptyParts Matchers.strictlyEqual(emptyPartsSame)
+        emptyParts != withClassifier
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalComponentMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalComponentMetaDataTest.groovy
new file mode 100644
index 0000000..31e6563
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultLocalComponentMetaDataTest.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.apache.ivy.core.module.descriptor.Configuration
+import org.apache.ivy.core.module.descriptor.DefaultArtifact
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import spock.lang.Specification
+
+class DefaultLocalComponentMetaDataTest extends Specification {
+    def moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance(IvyUtil.createModuleRevisionId("group", "module", "version"))
+    def componentIdentifier = Mock(ComponentIdentifier)
+    def metaData = new DefaultLocalComponentMetaData(moduleDescriptor, componentIdentifier)
+
+    def "can add artifacts"() {
+        def artifact = artifact()
+        def file = new File("artifact.zip")
+
+        given:
+        moduleDescriptor.addConfiguration(new Configuration("conf"))
+
+        when:
+        metaData.addArtifact("conf", artifact, file)
+
+        then:
+        metaData.artifacts.size() == 1
+        def publishArtifact = (metaData.artifacts as List).first()
+        publishArtifact.id
+        publishArtifact.file == file
+
+        and:
+        metaData.getArtifact(publishArtifact.id) == publishArtifact
+
+        and:
+        def resolveMetaData = metaData.toResolveMetaData()
+        resolveMetaData.artifacts.size() == 1
+        def resolveArtifact = (resolveMetaData.artifacts as List).first()
+        resolveArtifact.componentId == resolveMetaData.componentId
+
+        and:
+        moduleDescriptor.getArtifacts("conf") == [artifact]
+    }
+
+    def "can lookup an artifact given an Ivy artifact"() {
+        def artifact = artifact()
+        def file = new File("artifact.zip")
+
+        given:
+        moduleDescriptor.addConfiguration(new Configuration("conf"))
+
+        and:
+        metaData.addArtifact("conf", artifact, file)
+
+        expect:
+        def resolveArtifact = metaData.toResolveMetaData().artifact(artifact)
+        resolveArtifact.file == file
+        resolveArtifact == metaData.getArtifact(resolveArtifact.id)
+    }
+
+    def "can lookup an unknown artifact given an Ivy artifact"() {
+        def artifact = artifact()
+
+        expect:
+        def resolveArtifact = metaData.toResolveMetaData().artifact(artifact)
+        resolveArtifact != null
+        resolveArtifact.file == null
+        metaData.getArtifact(resolveArtifact.id) == null
+    }
+
+    def "handles artifacts with duplicate attributes and different files"() {
+        def artifact1 = artifact()
+        def artifact2 = artifact()
+        def file1 = new File("artifact-1.zip")
+        def file2 = new File("artifact-2.zip")
+
+        given:
+        moduleDescriptor.addConfiguration(new Configuration("conf1"))
+        moduleDescriptor.addConfiguration(new Configuration("conf2"))
+        metaData.addArtifact("conf1", artifact1, file1)
+        metaData.addArtifact("conf2", artifact2, file2)
+
+        when:
+        def resolveMetaData = metaData.toResolveMetaData()
+
+        then:
+        def conf1Artifacts = resolveMetaData.getConfiguration("conf1").artifacts as List
+        conf1Artifacts.size() == 1
+        def artifactMetadata1 = conf1Artifacts[0]
+
+        def conf2Artifacts = resolveMetaData.getConfiguration("conf2").artifacts as List
+        conf2Artifacts.size() == 1
+        def artifactMetadata2 = conf2Artifacts[0]
+
+        and:
+        artifactMetadata1.id != artifactMetadata2.id
+
+        and:
+        resolveMetaData.artifacts == [artifactMetadata1, artifactMetadata2] as Set
+
+        and:
+        metaData.getArtifact(artifactMetadata1.id).file == file1
+        metaData.getArtifact(artifactMetadata2.id).file == file2
+    }
+
+    def "can convert to publish meta-data"() {
+        def artifact = artifact()
+        def file = new File("artifact.zip")
+
+        given:
+        moduleDescriptor.addConfiguration(new Configuration("conf"))
+        metaData.addArtifact("conf", artifact, file)
+
+        when:
+        def publishMetaData = metaData.toPublishMetaData()
+
+        then:
+        publishMetaData.id == metaData.id
+
+        and:
+        publishMetaData.artifacts.size() == 1
+        def artifacts = publishMetaData.artifacts as List
+        def publishArtifact = artifacts[0]
+        publishArtifact.artifact == artifact
+        publishArtifact.file == file
+    }
+
+    def artifact() {
+        return new DefaultArtifact(moduleDescriptor.getModuleRevisionId(), null, "artifact", "type", "ext")
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactIdentifierTest.groovy
new file mode 100644
index 0000000..695885b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactIdentifierTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+class DefaultModuleVersionArtifactIdentifierTest extends Specification {
+    def "has useful string representation"() {
+        def componentId = DefaultModuleComponentIdentifier.newId("group", "module", "version")
+
+        expect:
+        def noClassifier = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", "ext", [:])
+        noClassifier.displayName == "group:module:version:name.ext"
+        noClassifier.toString() == "group:module:version:name.ext"
+
+        def withClassifier = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", "ext", ['classifier': 'classifier'])
+        withClassifier.displayName == "group:module:version:name-classifier.ext"
+        withClassifier.toString() == "group:module:version:name-classifier.ext"
+
+        def noExtension = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", null, ['classifier': 'classifier'])
+        noExtension.displayName == "group:module:version:name-classifier"
+        noExtension.toString() == "group:module:version:name-classifier"
+    }
+
+    def "is equal when all attributes and module version are the same"() {
+        def componentId = DefaultModuleComponentIdentifier.newId("group", "module", "version")
+        def otherComponentId = DefaultModuleComponentIdentifier.newId("group", "module", "2")
+
+        def withClassifier = new DefaultModuleVersionArtifactIdentifier(componentId,  "name", "type", "ext", ['classifier': 'classifier'])
+        def same = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", "ext", ['classifier': 'classifier'])
+        def differentModule = new DefaultModuleVersionArtifactIdentifier(otherComponentId, "name", "type", "ext", ['classifier': 'classifier'])
+        def differentName = new DefaultModuleVersionArtifactIdentifier(componentId, "2", "type", "ext", ['classifier': 'classifier'])
+        def differentType = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "2", "ext", ['classifier': 'classifier'])
+        def differentExt = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", "2", ['classifier': 'classifier'])
+        def differentAttributes = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", "ext", ['classifier': '2'])
+        def emptyParts = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", null, [:])
+        def emptyPartsSame = new DefaultModuleVersionArtifactIdentifier(componentId, "name", "type", null, [:])
+
+        expect:
+        withClassifier Matchers.strictlyEqual(same)
+        withClassifier != differentModule
+        withClassifier != differentName
+        withClassifier != differentType
+        withClassifier != differentExt
+        withClassifier != differentAttributes
+        withClassifier != emptyParts
+
+        emptyParts Matchers.strictlyEqual(emptyPartsSame)
+        emptyParts != withClassifier
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactMetaDataTest.groovy
new file mode 100644
index 0000000..020124e
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionArtifactMetaDataTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import spock.lang.Specification
+
+class DefaultModuleVersionArtifactMetaDataTest extends Specification {
+    def "has reasonable string representation"() {
+        expect:
+        def artifact = new DefaultModuleVersionArtifactMetaData(Stub(ModuleComponentIdentifier), ivyArtifact("name", "type", "ext", ['classifier': 'classifier']))
+        artifact.toString() == artifact.id.toString()
+    }
+
+    def "extracts attributes from provided artifact instance"() {
+        expect:
+        def artifact = new DefaultModuleVersionArtifactMetaData(Stub(ModuleComponentIdentifier), ivyArtifact("name", "type", "ext", ['classifier': 'classifier']))
+        artifact.name.name == "name"
+        artifact.name.type == "type"
+        artifact.name.extension == "ext"
+        artifact.name.classifier == "classifier"
+
+        and:
+        def noClassifier = new DefaultModuleVersionArtifactMetaData(Stub(ModuleComponentIdentifier), ivyArtifact("name", "type", "ext", [:]))
+        noClassifier.name.name == "name"
+        noClassifier.name.type == "type"
+        noClassifier.name.extension == "ext"
+        noClassifier.name.classifier == null
+
+        and:
+        def noExtension = new DefaultModuleVersionArtifactMetaData(Stub(ModuleComponentIdentifier), ivyArtifact("name", "type", null, [:]))
+        noExtension.name.name == "name"
+        noExtension.name.type == "type"
+        noExtension.name.extension == null
+        noExtension.name.classifier == null
+    }
+
+    def "converts to Ivy artifact"() {
+        expect:
+        def original = ivyArtifact("name", "type", "ext", ['classifier': 'classifier'])
+        def artifact = new DefaultModuleVersionArtifactMetaData(Stub(ModuleComponentIdentifier), original)
+        def ivyArtifact = artifact.toIvyArtifact()
+        ivyArtifact.name == "name"
+        ivyArtifact.type == "type"
+        ivyArtifact.ext == "ext"
+        ivyArtifact.extraAttributes == [classifier: "classifier"]
+    }
+
+    def ivyArtifact(String name, String type, String extension, Map attributes) {
+        def artifact = Mock(Artifact)
+        _ * artifact.name >> name
+        _ * artifact.type >> type
+        _ * artifact.ext >> extension
+        _ * artifact.extraAttributes >> attributes
+        return artifact
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionPublishMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionPublishMetaDataTest.groovy
new file mode 100644
index 0000000..17a2e9e
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/DefaultModuleVersionPublishMetaDataTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import spock.lang.Specification
+
+class DefaultModuleVersionPublishMetaDataTest extends Specification {
+    def metaData = new DefaultModuleVersionPublishMetaData(Stub(ModuleVersionIdentifier))
+
+    def "can add artifacts"() {
+        def artifact = Stub(Artifact)
+        def file = new File("artifact.zip")
+
+        when:
+        metaData.addArtifact(artifact, file)
+
+        then:
+        metaData.artifacts.size() == 1
+        def publishArtifact = metaData.artifacts.iterator().next()
+        publishArtifact.artifact == artifact
+        publishArtifact.file == file
+
+        and:
+        metaData.getArtifact(publishArtifact.id) == publishArtifact
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/ModuleDescriptorAdapterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/ModuleDescriptorAdapterTest.groovy
new file mode 100644
index 0000000..de16c66
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/metadata/ModuleDescriptorAdapterTest.groovy
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.internal.artifacts.metadata
+
+import org.apache.ivy.core.module.descriptor.*
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import spock.lang.Specification
+
+class ModuleDescriptorAdapterTest extends Specification {
+    def id = Stub(ModuleVersionIdentifier)
+    def moduleDescriptor = Mock(ModuleDescriptor)
+    def metaData = new ModuleDescriptorAdapter(id, moduleDescriptor)
+
+    def "has useful string representation"() {
+        given:
+        def config = Stub(Configuration)
+        moduleDescriptor.getConfiguration('config') >> config
+        id.toString() >> 'group:module:version'
+
+        expect:
+        metaData.toString() == 'group:module:version'
+        metaData.getConfiguration('config').toString() == 'group:module:version:config'
+    }
+
+    def "builds and caches the dependency meta-data from the module descriptor"() {
+        def dependency1 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+        def dependency2 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+
+        given:
+        moduleDescriptor.dependencies >> ([dependency1, dependency2] as DependencyDescriptor[])
+
+        when:
+        def deps = metaData.dependencies
+
+        then:
+        deps.size() == 2
+        deps[0].descriptor == dependency1
+        deps[1].descriptor == dependency2
+
+        when:
+        def deps2 = metaData.dependencies
+
+        then:
+        deps2.is(deps)
+
+        and:
+        0 * moduleDescriptor._
+    }
+
+    def "builds and caches the configuration meta-data from the module descriptor"() {
+        when:
+        def config = metaData.getConfiguration("conf")
+
+        then:
+        1 * moduleDescriptor.getConfiguration("conf") >> Stub(Configuration)
+
+        when:
+        def config2 = metaData.getConfiguration("conf")
+
+        then:
+        config2.is(config)
+
+        and:
+        0 * moduleDescriptor._
+    }
+
+    def "returns null for unknown configuration"() {
+        given:
+        moduleDescriptor.getConfiguration("conf") >> null
+
+        expect:
+        metaData.getConfiguration("conf") == null
+    }
+
+    def "builds and caches dependencies for a configuration"() {
+        def config = Stub(Configuration)
+        def parent = Stub(Configuration)
+        def dependency1 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+        dependency1.addDependencyConfiguration("conf", "a")
+        def dependency2 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+        dependency2.addDependencyConfiguration("*", "b")
+        def dependency3 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+        dependency3.addDependencyConfiguration("super", "c")
+        def dependency4 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+        dependency4.addDependencyConfiguration("other", "d")
+        def dependency5 = new DefaultDependencyDescriptor(IvyUtil.createModuleRevisionId("org", "module", "1.2"), false)
+        dependency5.addDependencyConfiguration("%", "e")
+
+        given:
+        moduleDescriptor.dependencies >> ([dependency1, dependency2, dependency3, dependency4, dependency5] as DependencyDescriptor[])
+        moduleDescriptor.getConfiguration("conf") >> config
+        moduleDescriptor.getConfiguration("super") >> parent
+        config.extends >> ["super"]
+
+        when:
+        def dependencies = metaData.getConfiguration("conf").dependencies
+
+        then:
+        dependencies*.descriptor == [dependency1, dependency2, dependency3, dependency5]
+
+        and:
+        metaData.getConfiguration("conf").dependencies.is(dependencies)
+
+        when:
+        metaData.setDependencies([])
+
+        then:
+        metaData.getConfiguration("conf").dependencies == []
+    }
+
+    def "builds and caches artifacts from the module descriptor"() {
+        def artifact1 = artifact("one")
+        def artifact2 = artifact("two")
+
+        given:
+        moduleDescriptor.getAllArtifacts() >> ([artifact1, artifact2] as Artifact[])
+
+        when:
+        def artifacts = metaData.artifacts
+
+        then:
+        artifacts*.name.name == ["one", "two"]
+
+        and:
+        metaData.artifacts.is(artifacts)
+    }
+
+    Artifact artifact(String name) {
+        return Stub(Artifact) {
+            getName() >> name
+            getType() >> "type"
+            getExt() >> "ext"
+            getExtraAttributes() >> [classifier: "classifier"]
+        }
+    }
+
+    def "builds and caches artifacts for a configuration"() {
+        def artifact1 = artifact("one")
+        def artifact2 = artifact("two")
+        def config = Stub(Configuration)
+
+        given:
+        moduleDescriptor.getConfiguration("conf") >> config
+        moduleDescriptor.allArtifacts >> ([artifact1, artifact2] as Artifact[])
+        moduleDescriptor.getArtifacts("conf") >> ([artifact1, artifact2] as Artifact[])
+
+        when:
+        def artifacts = metaData.getConfiguration("conf").artifacts
+
+        then:
+        artifacts*.name.name == ["one", "two"]
+        metaData.artifacts*.name.name == ["one", "two"]
+
+        and:
+        metaData.getConfiguration("conf").artifacts.is(artifacts)
+    }
+
+    def "can adapt an Ivy artifact to a Gradle artifact"() {
+        def artifact = artifact("one")
+
+        expect:
+        def artifactMetaData = metaData.artifact(artifact)
+        artifactMetaData.componentId == metaData.componentId
+        artifactMetaData.id.componentIdentifier == metaData.componentId
+        artifactMetaData.name.name == "one"
+        artifactMetaData.name.type == "type"
+        artifactMetaData.name.extension == "ext"
+        artifactMetaData.name.classifier == "classifier"
+    }
+
+    def "artifacts include union of those inherited from other configurations"() {
+        def config = Stub(Configuration)
+        def parent = Stub(Configuration)
+        def artifact1 = artifact("one")
+        def artifact2 = artifact("two")
+        def artifact3 = artifact("three")
+
+        given:
+        moduleDescriptor.getConfiguration("conf") >> config
+        moduleDescriptor.getConfiguration("super") >> parent
+        config.extends >> ["super"]
+        moduleDescriptor.allArtifacts >> ([artifact1, artifact2, artifact3] as Artifact[])
+        moduleDescriptor.getArtifacts("conf") >> ([artifact1, artifact2] as Artifact[])
+        moduleDescriptor.getArtifacts("super") >> ([artifact2, artifact3] as Artifact[])
+
+        when:
+        def artifacts = metaData.getConfiguration("conf").artifacts
+
+        then:
+        artifacts*.name.name == ["one", "two", "three"]
+    }
+
+    def "builds and caches exclude rules for a configuration"() {
+        def rule1 = Stub(ExcludeRule)
+        def rule2 = Stub(ExcludeRule)
+        def rule3 = Stub(ExcludeRule)
+        def config = Stub(Configuration)
+        def parent = Stub(Configuration)
+
+        given:
+        rule1.configurations >> ["conf"]
+        rule2.configurations >> ["super"]
+        rule3.configurations >> ["other"]
+
+        and:
+        moduleDescriptor.getConfiguration("conf") >> config
+        moduleDescriptor.getConfiguration("super") >> parent
+        config.extends >> ["super"]
+        moduleDescriptor.allExcludeRules >> ([rule1, rule2, rule3] as ExcludeRule[])
+
+        when:
+        def excludeRules = metaData.getConfiguration("conf").excludeRules
+
+        then:
+        excludeRules as List == [rule1, rule2]
+
+        and:
+        metaData.getConfiguration("conf").excludeRules.is(excludeRules)
+    }
+
+    def "can replace the dependencies for the module version"() {
+        def dependency1 = Stub(DependencyMetaData)
+        def dependency2 = Stub(DependencyMetaData)
+
+        when:
+        metaData.dependencies = [dependency1, dependency2]
+
+        then:
+        metaData.dependencies == [dependency1, dependency2]
+
+        and:
+        0 * moduleDescriptor._
+    }
+
+    def "can make a copy"() {
+        def dependency1 = Stub(DependencyMetaData)
+        def dependency2 = Stub(DependencyMetaData)
+
+        given:
+        metaData.changing = true
+        metaData.metaDataOnly = true
+        metaData.dependencies = [dependency1, dependency2]
+        metaData.status = 'a'
+        metaData.statusScheme = ['a', 'b', 'c']
+
+        when:
+        def copy = metaData.copy()
+
+        then:
+        copy != metaData
+        copy.descriptor == moduleDescriptor
+        copy.changing
+        copy.metaDataOnly
+        copy.dependencies == [dependency1, dependency2]
+        copy.status == 'a'
+        copy.statusScheme == ['a', 'b', 'c']
+    }
+
+    def "creates ModuleComponentIdentifier as component ID if not provided in constructor"() {
+        when:
+        ModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier('group', 'name', 'version')
+        def metaData = new ModuleDescriptorAdapter(moduleVersionIdentifier, moduleDescriptor)
+
+        then:
+        metaData.componentId == new DefaultModuleComponentIdentifier('group', 'name', 'version')
+    }
+
+    def "uses component ID if provided in constructor"() {
+        when:
+        def moduleVersionIdentifier = new DefaultModuleVersionIdentifier('group', 'name', 'version')
+        def componentIdentifier = new DefaultModuleComponentIdentifier('group', 'override', '1.2')
+        def metaData = new ModuleDescriptorAdapter(moduleVersionIdentifier, moduleDescriptor, componentIdentifier)
+
+        then:
+        metaData.componentId == componentIdentifier
+    }
+}
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 18896b8..32615ae 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
@@ -52,7 +52,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)
     }
 
@@ -64,7 +64,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
index d3f1c8e..f1e72dd 100644
--- 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
@@ -16,138 +16,147 @@
 
 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.dsl.DefaultRepositoryHandler
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator
+import org.gradle.api.internal.artifacts.repositories.legacy.LegacyDependencyResolverRepositoryFactory
 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 {
+import spock.lang.Specification
+
+class DefaultBaseRepositoryFactoryTest extends Specification {
     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 RepositoryCacheManager localCacheManager = context.mock(RepositoryCacheManager)
-    final RepositoryCacheManager downloadingCacheManager = context.mock(RepositoryCacheManager)
-    final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory)
+
+    final LocalMavenRepositoryLocator localMavenRepoLocator = Mock()
+    final FileResolver fileResolver = Mock()
+    final RepositoryTransportFactory transportFactory = Mock()
+    final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final LegacyDependencyResolverRepositoryFactory legacyDependencyResolverRepositoryFactory = Mock()
+    final ResolverStrategy resolverStrategy = Mock()
 
     final DefaultBaseRepositoryFactory factory = new DefaultBaseRepositoryFactory(
             localMavenRepoLocator, fileResolver, new DirectInstantiator(), transportFactory, locallyAvailableResourceFinder,
-            progressLoggerFactory, localCacheManager, downloadingCacheManager
+            legacyDependencyResolverRepositoryFactory, resolverStrategy
     )
 
-    @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 }
-        }
-    }
+    def testCreateResolverWithStringDescription() {
+        when:
+        fileResolver.resolveUri('uri') >> RESOLVER_URL
 
-    @Test public void testCreateResolverWithStringDescription() {
+        then:
         def repository = factory.createRepository('uri')
 
-        assert repository instanceof DefaultMavenArtifactRepository
-        assert repository.url == RESOLVER_URL
-        assert repository.name == null
-        assert repository.artifactUrls.isEmpty()
+        repository instanceof DefaultMavenArtifactRepository
+        repository.name == null
+        repository.url == RESOLVER_URL
+        repository.artifactUrls.isEmpty()
     }
 
-    @Test public void testCreateResolverWithMapDescription() {
+    def testCreateResolverWithMapDescription() {
+        when:
+        fileResolver.resolveUri('uri') >> RESOLVER_URL
+
+        then:
         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()
+        repository instanceof DefaultMavenArtifactRepository
+        repository.name == 'name'
+        repository.url == RESOLVER_URL
+        repository.artifactUrls.isEmpty()
     }
 
-    @Test public void testCreateResolverWithResolverDescription() {
-        DependencyResolver resolver = context.mock(DependencyResolver)
-        
-        ArtifactRepository repository = factory.createRepository(resolver)
+    def testCreateResolverWithResolverDescription() {
+        def repository = Mock(ArtifactRepository)
+        def resolver = Mock(DependencyResolver)
+
+        when:
+        legacyDependencyResolverRepositoryFactory.createRepository(resolver) >> repository
 
-        assert repository instanceof FixedResolverArtifactRepository
-        assert repository.resolver == resolver
+        then:
+        factory.createRepository(resolver) == repository
     }
 
-    @Test public void testCreateResolverWithArtifactRepositoryDescription() {
-        ArtifactRepository repo = context.mock(ArtifactRepository)
+    def testCreateResolverWithArtifactRepositoryDescription() {
+        when:
+        ArtifactRepository repo = Mock(ArtifactRepository)
 
-        assert factory.createRepository(repo) == repo
+        then:
+        factory.createRepository(repo) == repo
     }
 
-    @Test(expected = InvalidUserDataException) public void testCreateResolverForUnknownDescription() {
+    def testCreateResolverForUnknownDescription() {
+        when:
         def someIllegalDescription = new NullPointerException()
+
         factory.createRepository(someIllegalDescription)
+
+        then:
+        thrown InvalidUserDataException
     }
 
-    @Test public void testCreateFlatDirResolver() {
+    def testCreateFlatDirResolver() {
+        expect:
         def repo =factory.createFlatDirRepository()
-        assert repo instanceof DefaultFlatDirArtifactRepository
+        repo instanceof DefaultFlatDirArtifactRepository
     }
 
-    @Test public void testCreateLocalMavenRepo() {
+    def testCreateLocalMavenRepo() {
+        given:
         File repoDir = new File(".m2/repository")
 
-        context.checking {
-            one(localMavenRepoLocator).getLocalMavenRepository()
-            will(returnValue(repoDir))
-            allowing(fileResolver).resolveUri(repoDir)
-            will(returnValue(repoDir.toURI()))
-        }
+        when:
+        localMavenRepoLocator.getLocalMavenRepository() >> repoDir
+        fileResolver.resolveUri(repoDir) >> repoDir.toURI()
 
+        then:
         def repo = factory.createMavenLocalRepository()
-        assert repo instanceof DefaultMavenArtifactRepository
-        assert repo.url == repoDir.toURI()
+        repo instanceof DefaultMavenLocalArtifactRepository
+        repo.url == repoDir.toURI()
+    }
+
+    def testCreateJCenterRepo() {
+        given:
+        def jcenterUrl = new URI(DefaultRepositoryHandler.BINTRAY_JCENTER_URL)
+
+        when:
+        fileResolver.resolveUri(DefaultRepositoryHandler.BINTRAY_JCENTER_URL) >> jcenterUrl
+
+        then:
+        def repo = factory.createJCenterRepository()
+        repo instanceof DefaultMavenArtifactRepository
+        repo.url == jcenterUrl
     }
 
-    @Test public void testCreateMavenCentralRepo() {
+    def testCreateMavenCentralRepo() {
+        given:
         def centralUrl = new URI(RepositoryHandler.MAVEN_CENTRAL_URL)
 
-        context.checking {
-            allowing(fileResolver).resolveUri(RepositoryHandler.MAVEN_CENTRAL_URL)
-            will(returnValue(centralUrl))
-        }
+        when:
+        fileResolver.resolveUri(RepositoryHandler.MAVEN_CENTRAL_URL) >> centralUrl
 
+        then:
         def repo = factory.createMavenCentralRepository()
-        assert repo instanceof DefaultMavenArtifactRepository
-        assert repo.url == centralUrl
+        repo instanceof DefaultMavenArtifactRepository
+        repo.url == centralUrl
     }
 
-    @Test public void createIvyRepository() {
+    def createIvyRepository() {
+        expect:
         def repo = factory.createIvyRepository()
-        assert repo instanceof DefaultIvyArtifactRepository
+        repo instanceof DefaultIvyArtifactRepository
     }
 
-    @Test public void createMavenRepository() {
+    def createMavenRepository() {
+        expect:
         def repo = factory.createMavenRepository()
-        assert repo instanceof DefaultMavenArtifactRepository
+        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 9fcf452..d05e927 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
@@ -15,32 +15,48 @@
  */
 package org.gradle.api.internal.artifacts.repositories
 
-import org.apache.ivy.core.cache.RepositoryCacheManager
-import org.apache.ivy.plugins.resolver.FileSystemResolver
+import org.apache.ivy.core.module.id.ArtifactRevisionId
 import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
+import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
 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 RepositoryCacheManager localCacheManager = Mock()
-    final DefaultFlatDirArtifactRepository repository = new DefaultFlatDirArtifactRepository(fileResolver, localCacheManager)
+    final ExternalResourceRepository resourceRepository = Mock()
+    final RepositoryTransport repositoryTransport = Mock()
+    final RepositoryTransportFactory transportFactory = Mock()
+    final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder = Mock()
+    final ResolverStrategy resolverStrategy = Stub()
+
+    final DefaultFlatDirArtifactRepository repository = new DefaultFlatDirArtifactRepository(
+            fileResolver, transportFactory, locallyAvailableResourceFinder, resolverStrategy)
 
     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)
+        _ * repositoryTransport.repository >> resourceRepository
 
         and:
+        repository.name = 'repo-name'
         repository.dirs('a', 'b')
 
         when:
-        def repo = repository.createLegacyDslObject()
+        def repo = repository.createResolver()
 
         then:
-        repo instanceof FileSystemResolver
+        1 * transportFactory.createFileTransport("repo-name") >> repositoryTransport
+
+        and:
+        repo instanceof IvyResolver
         def expectedPatterns = [
                 "$dir1.absolutePath/[artifact]-[revision](-[classifier]).[ext]",
                 "$dir1.absolutePath/[artifact](-[classifier]).[ext]",
@@ -57,7 +73,7 @@ class DefaultFlatDirArtifactRepositoryTest extends Specification {
         _ * fileResolver.resolveFiles(_) >> new SimpleFileCollection()
 
         when:
-        repository.createLegacyDslObject()
+        repository.createResolver()
 
         then:
         InvalidUserDataException e = thrown()
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 44adf6b..76ac7ce 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
@@ -15,19 +15,15 @@
  */
 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.ivyservice.ivyresolve.ExternalResourceResolverAdapter
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
 import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver
+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.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.file.FileResolver
-import org.gradle.api.internal.file.TemporaryFileProvider
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.logging.ProgressLoggerFactory
 import spock.lang.Specification
@@ -36,13 +32,14 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
     final FileResolver fileResolver = Mock()
     final PasswordCredentials credentials = Mock()
     final RepositoryTransportFactory transportFactory = Mock()
-    final RepositoryCacheManager cacheManager = Mock()
     final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
-    final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
+    final ExternalResourceRepository resourceRepository = Mock()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final ResolverStrategy resolverStrategy = Stub()
 
     final DefaultIvyArtifactRepository repository = new DefaultIvyArtifactRepository(
-            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, new DirectInstantiator()
+            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder,
+            new DirectInstantiator(), resolverStrategy
     )
 
     def "default values"() {
@@ -59,7 +56,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         fileResolver.resolveUri('pattern1') >> new URI('scheme:resource1')
 
         when:
-        repository.createRealResolver()
+        repository.createResolver()
 
         then:
         InvalidUserDataException e = thrown()
@@ -76,7 +73,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         fileResolver.resolveUri('pattern2') >> new URI('file:resource2')
 
         when:
-        repository.createRealResolver()
+        repository.createResolver()
 
         then:
         InvalidUserDataException e = thrown()
@@ -92,15 +89,15 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         given:
         fileResolver.resolveUri('http://host/') >> new URI('http://host/')
         fileResolver.resolveUri('http://other/') >> new URI('http://other/')
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
             it instanceof IvyResolver
-            repository instanceof ExternalResourceRepository
+            repository == resourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[organisation]/[artifact]-[revision].[ext]', 'http://other/[module]/[artifact]-[revision].[ext]']
             ivyPatterns == ['http://host/[module]/ivy-[revision].xml']
@@ -117,18 +114,18 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('repo/') >> fileUri
-        transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager, Mock(TemporaryFileProvider))
+        transportFactory.createFileTransport('name') >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
             it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
-            artifactPatterns == ["${file.absolutePath}/[organisation]/[artifact]-[revision].[ext]", "${file.absolutePath}/[organisation]/[module]/[artifact]-[revision].[ext]"]
-            ivyPatterns == ["${file.absolutePath}/[organisation]/[module]/ivy-[revision].xml"]
+            artifactPatterns == ["${fileUri}/[organisation]/[artifact]-[revision].[ext]", "${fileUri}/[organisation]/[module]/[artifact]-[revision].[ext]"]
+            ivyPatterns == ["${fileUri}/[organisation]/[module]/ivy-[revision].xml"]
         }
     }
 
@@ -140,7 +137,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('repo/') >> fileUri
-        transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager, Mock(TemporaryFileProvider))
+        transportFactory.createFileTransport('name') >> transport()
 
         when:
         def wrapper = repository.createLegacyDslObject()
@@ -155,8 +152,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def repo = wrapper.createResolver()
 
         then:
-        repo instanceof ExternalResourceResolverAdapter
-        repo.resolver.is(wrapper.resolver)
+        repo.is(wrapper.resolver)
     }
 
     def "uses gradle patterns with specified url and default layout"() {
@@ -165,10 +161,10 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('http://host') >> new URI('http://host/')
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
@@ -187,10 +183,10 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('http://host') >> new URI('http://host/')
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
@@ -213,10 +209,10 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('http://host') >> new URI('http://host/')
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
@@ -240,10 +236,10 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('http://host') >> new URI('http://host/')
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
@@ -264,10 +260,10 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('http://host/') >> new URI('http://host/')
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         with(resolver) {
@@ -286,14 +282,14 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
             artifact '[layoutPattern]'
         }
         repository.artifactPattern 'http://other/[additionalPattern]'
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         given:
         fileResolver.resolveUri('http://host') >> new URI('http://host')
         fileResolver.resolveUri('http://other/') >> new URI('http://other/')
 
         when:
-        def resolver = repository.createRealResolver()
+        def resolver = repository.createResolver()
 
         then:
         resolver.artifactPatterns == ['http://host/[layoutPattern]', 'http://other/[additionalPattern]']
@@ -302,17 +298,23 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
     def "fails when no artifact patterns specified"() {
         given:
-        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+        transportFactory.createHttpTransport('name', credentials) >> transport()
 
         when:
-        repository.createRealResolver()
+        repository.createResolver()
 
         then:
         InvalidUserDataException e = thrown()
         e.message == 'You must specify a base url or at least one artifact pattern for an Ivy repository.'
     }
 
-    private HttpTransport createHttpTransport(String name, PasswordCredentials credentials) {
-        return new HttpTransport(name, credentials, cacheManager, progressLoggerFactory, Mock(TemporaryFileProvider), cachedExternalResourceIndex)
+    private RepositoryTransport transport() {
+        return Mock(RepositoryTransport) {
+            getRepository() >> resourceRepository
+            convertToPath(_) >> { URI uri ->
+                def result = uri.toString()
+                return result.endsWith('/') ? result : result + '/'
+            }
+        }
     }
 }
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 7ce96a5..62d9fcb 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
@@ -15,39 +15,34 @@
  */
 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.ivyservice.ivyresolve.ExternalResourceResolverAdapter
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
 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.transport.file.FileTransport
-import org.gradle.api.internal.externalresource.transport.http.HttpTransport
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
 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()
     final PasswordCredentials credentials = Mock()
     final RepositoryTransportFactory transportFactory = Mock()
-    final RepositoryCacheManager cacheManager = Mock()
     final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
-    final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
-
-    final DefaultMavenArtifactRepository repository = new DefaultMavenArtifactRepository(resolver, credentials, transportFactory, locallyAvailableResourceFinder)
-    final ProgressLoggerFactory progressLoggerFactory = Mock();
+    final ExternalResourceRepository resourceRepository = Mock()
+    final ResolverStrategy resolverStrategy = Stub()
 
+    final DefaultMavenArtifactRepository repository = new DefaultMavenArtifactRepository(
+            resolver, credentials, transportFactory, locallyAvailableResourceFinder, resolverStrategy)
 
     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, Mock(TemporaryFileProvider))
+        transportFactory.createFileTransport('repo') >> transport()
 
         and:
         repository.name = 'repo'
@@ -58,7 +53,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
 
         then:
         repo instanceof MavenResolver
-        repo.root == "${file.absolutePath}/"
+        repo.root == "${uri}/"
     }
 
     def "creates http repository"() {
@@ -67,9 +62,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         _ * resolver.resolveUri('repo-dir') >> uri
         _ * credentials.getUsername() >> 'username'
         _ * credentials.getPassword() >> 'password'
-        transportFactory.createHttpTransport('repo', credentials) >> createHttpTransport("repo", credentials)
-        cacheManager.name >> 'cache'
-        0 * _._
+        transportFactory.createHttpTransport('repo', credentials) >> transport()
 
         and:
         repository.name = 'repo'
@@ -88,7 +81,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         def file = new File('repo')
         def uri = file.toURI()
         _ * this.resolver.resolveUri('repo-dir') >> uri
-        transportFactory.createFileTransport('repo') >> new FileTransport('repo', cacheManager, Mock(TemporaryFileProvider))
+        transportFactory.createFileTransport('repo') >> transport()
 
         and:
         repository.name = 'repo'
@@ -105,8 +98,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         def repo = resolver.createResolver()
 
         then:
-        repo instanceof ExternalResourceResolverAdapter
-        repo.resolver.is(resolver.resolver)
+        repo.is(resolver.resolver)
     }
 
     def "creates repository with additional artifact URLs"() {
@@ -117,7 +109,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         _ * resolver.resolveUri('repo-dir') >> uri
         _ * resolver.resolveUri('repo1') >> uri1
         _ * resolver.resolveUri('repo2') >> uri2
-        transportFactory.createHttpTransport('repo', credentials) >> createHttpTransport("repo", credentials)
+        transportFactory.createHttpTransport('repo', credentials) >> transport()
 
         and:
         repository.name = 'repo'
@@ -136,10 +128,6 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         repo.artifactPatterns.any { it.startsWith uri2.toString() }
     }
 
-    private HttpTransport createHttpTransport(String repo, PasswordCredentials credentials) {
-        return new HttpTransport(repo, credentials, cacheManager, progressLoggerFactory, Mock(TemporaryFileProvider), cachedExternalResourceIndex)
-    }
-
     def "fails when no root url specified"() {
         when:
         repository.createLegacyDslObject()
@@ -148,4 +136,15 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         InvalidUserDataException e = thrown()
         e.message == 'You must specify a URL for a Maven repository.'
     }
+
+    private RepositoryTransport transport() {
+        return Mock(RepositoryTransport) {
+            getRepository() >> resourceRepository
+            convertToPath(_) >> { URI uri ->
+                def result = uri.toString()
+                return result.endsWith('/') ? result : result + '/'
+            }
+        }
+    }
+
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy
new file mode 100644
index 0000000..65e0f55
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenLocalResolver
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.logging.ProgressLoggerFactory
+import spock.lang.Specification
+
+class DefaultMavenLocalRepositoryTest extends Specification {
+    final FileResolver resolver = Mock()
+    final PasswordCredentials credentials = Mock()
+    final RepositoryTransportFactory transportFactory = Mock()
+    final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
+    final ExternalResourceRepository resourceRepository = Mock()
+    final ResolverStrategy resolverStrategy = Stub()
+
+    final DefaultMavenArtifactRepository repository = new DefaultMavenLocalArtifactRepository(
+            resolver, credentials, transportFactory, locallyAvailableResourceFinder, resolverStrategy)
+    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') >> transport()
+
+        and:
+        repository.name = 'repo'
+        repository.url = 'repo-dir'
+
+        when:
+        def repo = repository.createRealResolver()
+
+        then:
+        repo instanceof MavenLocalResolver
+        repo.root == "${uri}/"
+    }
+
+    private RepositoryTransport transport() {
+        return Mock(RepositoryTransport) {
+            getRepository() >> resourceRepository
+            convertToPath(_) >> { URI uri ->
+                def result = uri.toString()
+                return result.endsWith('/') ? result : result + '/'
+            }
+        }
+    }
+}
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
deleted file mode 100644
index 61bff84..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy
+++ /dev/null
@@ -1,71 +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.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.test.fixtures.file.TestNameTestDirectoryProvider
-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 TestNameTestDirectoryProvider 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/legacy/DownloadingRepositoryCacheManagerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/legacy/DownloadingRepositoryCacheManagerTest.groovy
new file mode 100644
index 0000000..5bee2fa
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/legacy/DownloadingRepositoryCacheManagerTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.legacy
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.plugins.repository.Resource
+import org.apache.ivy.plugins.repository.ResourceDownloader
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.internal.filestore.FileStore
+import org.gradle.internal.resource.local.LocallyAvailableResource
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class DownloadingRepositoryCacheManagerTest extends Specification {
+    FileStore<ModuleVersionArtifactMetaData> fileStore = Mock()
+    CacheLockingManager lockingManager = Mock()
+    TemporaryFileProvider tmpFileProvider = Mock()
+    ModuleVersionArtifactMetaData artifactMetaData = new DefaultModuleVersionArtifactMetaData(new DefaultModuleVersionArtifactIdentifier(DefaultModuleVersionIdentifier.newId("group", "module", "version"), "name", "type", "ext"))
+    Artifact artifact = Mock()
+    ResourceDownloader resourceDownloader = Mock()
+    Resource resource = Mock();
+    LocallyAvailableResource fileStoreEntry = Mock()
+    DownloadingRepositoryCacheManager downloadingRepositoryCacheManager = new DownloadingRepositoryCacheManager("TestCacheManager", fileStore, tmpFileProvider, lockingManager)
+
+    @Rule TestNameTestDirectoryProvider temporaryFolder;
+
+    void "downloads artifact to temporary file and then moves it into the file store"() {
+        setup:
+
+        def downloadFile = temporaryFolder.createFile("download")
+        def storeFile = temporaryFolder.createFile("store")
+
+        _ * fileStoreEntry.file >> storeFile;
+        _ * tmpFileProvider._ >> downloadFile
+
+        when:
+        downloadingRepositoryCacheManager.downloadAndCacheArtifactFile(artifactMetaData, artifact, resourceDownloader, resource)
+
+        then:
+        1 * lockingManager.useCache(_, _) >> {name, action ->
+            return action.create()
+        }
+        1 * resourceDownloader.download(artifact, resource, downloadFile)
+        1 * fileStore.move(artifactMetaData, 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
index 2cac12d..8be66c7 100644
--- 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
@@ -16,12 +16,12 @@
 
 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.artifacts.ModuleIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
+import org.gradle.api.internal.resource.ResourceException
 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 {
 
@@ -32,18 +32,18 @@ class ChainedVersionListerTest extends Specification {
     VersionList versionList2 = Mock()
 
     ResourcePattern pattern = Mock()
-    Artifact artifact = Mock()
-    ModuleRevisionId moduleRevisionId = Mock()
+    ModuleIdentifier module = Mock()
+    ModuleVersionArtifactMetaData artifact = Mock()
 
-    def chainedVersionLister = new org.gradle.api.internal.artifacts.repositories.resolver.ChainedVersionLister(lister1, lister2)
+    def chainedVersionLister = new ChainedVersionLister(lister1, lister2)
 
     def "visit stops listing after first success"() {
         when:
-        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId);
+        VersionList versionList = chainedVersionLister.getVersionList(module);
 
         then:
-        1 * lister1.getVersionList(moduleRevisionId) >> versionList1
-        1 * lister2.getVersionList(moduleRevisionId) >> versionList2
+        1 * lister1.getVersionList(module) >> versionList1
+        1 * lister2.getVersionList(module) >> versionList2
 
         when:
         versionList.visit(pattern, artifact)
@@ -53,23 +53,23 @@ class ChainedVersionListerTest extends Specification {
         0 * _._
 
         when:
-        def result = versionList.versionStrings
+        def result = versionList.versions
 
         then:
         result == ["1.0", "1.2"] as Set
 
         and:
-        versionList1.versionStrings >> ["1.0", "1.2"]
-        versionList2.versionStrings >> []
+        versionList1.versions >> ["1.0", "1.2"]
+        versionList2.versions >> []
     }
 
     @Unroll
     def "visit ignores #exception.class.simpleName of failed VersionLister"() {
         given:
-        lister1.getVersionList(moduleRevisionId) >> versionList1
-        lister2.getVersionList(moduleRevisionId) >> versionList2
+        lister1.getVersionList(module) >> versionList1
+        lister2.getVersionList(module) >> versionList2
 
-        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId)
+        VersionList versionList = chainedVersionLister.getVersionList(module)
 
         when:
         versionList.visit(pattern, artifact)
@@ -85,10 +85,10 @@ class ChainedVersionListerTest extends Specification {
     def "visit rethrows ResourceNotFoundException of failed last VersionLister"() {
         given:
         def exception = new ResourceNotFoundException("not found")
-        lister1.getVersionList(moduleRevisionId) >> versionList1
-        lister2.getVersionList(moduleRevisionId) >> versionList2
+        lister1.getVersionList(module) >> versionList1
+        lister2.getVersionList(module) >> versionList2
 
-        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId)
+        VersionList versionList = chainedVersionLister.getVersionList(module)
 
         when:
         versionList.visit(pattern, artifact)
@@ -105,17 +105,17 @@ class ChainedVersionListerTest extends Specification {
     def "visit wraps failed last VersionLister"() {
         given:
         def exception = new RuntimeException("broken")
-        lister1.getVersionList(moduleRevisionId) >> versionList1
-        lister2.getVersionList(moduleRevisionId) >> versionList2
+        lister1.getVersionList(module) >> versionList1
+        lister2.getVersionList(module) >> versionList2
 
-        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId)
+        VersionList versionList = chainedVersionLister.getVersionList(module)
 
         when:
         versionList.visit(pattern, artifact)
 
         then:
         def e = thrown(ResourceException)
-        e.message == "Failed to list versions for ${moduleRevisionId}."
+        e.message == "Failed to list versions for ${module}."
         e.cause == exception
 
         and:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy
index b2211cf..2345e28 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy
@@ -16,14 +16,13 @@
 
 package org.gradle.api.internal.artifacts.repositories.resolver
 
-import org.apache.ivy.core.module.descriptor.Artifact
-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.DownloadStatus
+import org.gradle.api.artifacts.ArtifactIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
-import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
 import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
 import spock.lang.Specification
@@ -32,19 +31,23 @@ class ExternalResourceResolverTest extends Specification {
     String name = "TestResolver"
     ExternalResourceRepository repository = Mock()
     VersionLister versionLister = Mock()
-    LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder = Mock()
+    LocallyAvailableResourceFinder<ArtifactIdentifier> locallyAvailableResourceFinder = Mock()
     BuildableArtifactResolveResult result = Mock()
-    Artifact artifact = Mock()
+    MetaDataParser parser = Mock()
+    final ResolverStrategy resolverStrategy = Mock()
+    ModuleVersionArtifactIdentifier artifactIdentifier = Stub() {
+        getDisplayName() >> 'some-artifact'
+    }
+    ModuleVersionArtifactMetaData artifact = Stub() {
+        getId() >> artifactIdentifier
+    }
     MavenResolver.TimestampedModuleSource moduleSource = Mock()
-    EnhancedArtifactDownloadReport downloadReport = Mock()
-
+    File downloadedFile = Mock(File)
     ExternalResourceResolver resolver
-    ModuleRevisionId artifactModuleRevisionId = Mock()
 
     def setup() {
         //We use a spy here to avoid dealing with all the overhead ivys basicresolver brings in here.
-        resolver = Spy(ExternalResourceResolver, constructorArgs: [name, repository, versionLister, locallyAvailableResourceFinder])
-        resolver.download(_) >> downloadReport
+        resolver = Spy(ExternalResourceResolver, constructorArgs: [name, repository, versionLister, locallyAvailableResourceFinder, parser, resolverStrategy])
     }
 
     def reportsNotFoundArtifactResolveResult() {
@@ -52,23 +55,24 @@ class ExternalResourceResolverTest extends Specification {
         artifactIsMissing()
 
         when:
-        resolver.resolve(artifact, result, moduleSource)
+        resolver.resolveArtifact(artifact, moduleSource, result)
 
         then:
-        1 * result.notFound(artifact)
+        1 * result.notFound(artifactIdentifier)
         0 * result._
     }
 
     def reportsFailedArtifactResolveResult() {
         given:
-        downloadIsFailing()
+        downloadIsFailing(new IOException("DOWNLOAD FAILURE"))
 
         when:
-        resolver.resolve(artifact, result, moduleSource)
+        resolver.resolveArtifact(artifact, moduleSource, result)
 
         then:
-        1 * result.failed(_) >> { exception ->
-            assert exception.message.toString() == "[Could not download artifact 'group:projectA:1.0 at jar']"
+        1 * result.failed(_) >> { ArtifactResolveException exception ->
+            assert exception.message == "Could not download artifact 'some-artifact'"
+            assert exception.cause.message == "DOWNLOAD FAILURE"
         }
         0 * result._
     }
@@ -78,7 +82,7 @@ class ExternalResourceResolverTest extends Specification {
         artifactCanBeResolved()
 
         when:
-        resolver.resolve(artifact, result, moduleSource)
+        resolver.resolveArtifact(artifact, moduleSource, result)
 
         then:
         1 * result.resolved(_)
@@ -91,7 +95,7 @@ class ExternalResourceResolverTest extends Specification {
         artifactCanBeResolved()
 
         when:
-        resolver.resolve(artifact, result, moduleSource)
+        resolver.resolveArtifact(artifact, moduleSource, result)
 
         then:
         1 * result.resolved(_)
@@ -100,39 +104,19 @@ class ExternalResourceResolverTest extends Specification {
 
     def artifactIsTimestampedSnapshotVersion() {
         _ * moduleSource.timestampedVersion >> "1.0-20100101.120001-1"
-        _ * artifact.getModuleRevisionId() >> artifactModuleRevisionId
-        _ * artifactModuleRevisionId.organisation >> "group"
-        _ * artifactModuleRevisionId.name >> "projectA"
-        _ * artifactModuleRevisionId.revision >> "1.0"
-        ModuleId moduleId = Mock()
-        _ * moduleId.equals(_) >> true
-        _ * artifactModuleRevisionId.moduleId >> moduleId
-        _ * artifactModuleRevisionId.qualifiedExtraAttributes >> [:]
     }
 
     def artifactIsMissing() {
-        1 * downloadReport.getDownloadStatus() >> DownloadStatus.FAILED
-        1 * downloadReport.getDownloadDetails() >> ArtifactDownloadReport.MISSING_ARTIFACT;
+        resolver.download(_, _) >> null
     }
 
-    def downloadIsFailing() {
-        1 * downloadReport.getDownloadStatus() >> DownloadStatus.FAILED
-        1 * downloadReport.getDownloadDetails() >> "Broken Connection";
-        1 * downloadReport.getArtifact() >> artifact
-        1 * artifact.getModuleRevisionId() >> {
-            ModuleRevisionId moduleRevisionId = Mock()
-            1 * moduleRevisionId.organisation >> "group"
-            1 * moduleRevisionId.name >> "projectA"
-            1 * moduleRevisionId.revision >> "1.0"
-            1 * artifact.ext >> "jar"
-            moduleRevisionId
-        };
+    def downloadIsFailing(IOException failure) {
+        resolver.download(_, _) >> {
+            throw failure
+        }
     }
 
-
     def artifactCanBeResolved() {
-        1 * downloadReport.getDownloadStatus() >> DownloadStatus.SUCCESSFUL
-        1 * downloadReport.getLocalFile() >> Mock(File);
-
+        resolver.download(_, _) >> downloadedFile
     }
 }
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
index 9b503db..7164801 100644
--- 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
@@ -16,30 +16,35 @@
 
 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.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
 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())
+        def artifact1 = artifact("group", "projectA", "1.2")
+        def artifact2 = artifact("org.group", "projectA", "1.2")
 
         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())
+        def artifact1 = artifact("group", "projectA", "1.2")
+        def artifact2 = artifact("org.group", "projectA", "1.2")
 
         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.toVersionListPattern(artifact1) == 'prefix/group-projectA/[revision]/ivys/[revision]/ivy.xml'
+        pattern.toVersionListPattern(artifact2) == 'prefix/org.group-projectA/[revision]/ivys/[revision]/ivy.xml'
+    }
+
+    private static ModuleVersionArtifactMetaData artifact(String group, String name, String version) {
+        final moduleVersionId = DefaultModuleVersionIdentifier.newId(group, name, version)
+        return new DefaultModuleVersionArtifactMetaData(new DefaultModuleVersionArtifactIdentifier(moduleVersionId, "ivy", "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
index 7969433..216cd52 100644
--- 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
@@ -14,75 +14,71 @@
  * 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.artifacts.DefaultModuleIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactMetaData
 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())
+        final String group = "group"
+        final String name = "projectA"
+        final String version = "1.2"
+        def artifact1 = artifact(group, name, version)
+        def artifact2 = artifact("org.group", "projectA", "1.2")
 
         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 "substitutes module attributes into pattern to determine module 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())
+        def artifact1 = artifact("group", "projectA", "1.2")
+        def artifact2 = artifact("org.group", "projectA", "1.2")
 
         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'
+        pattern.toVersionListPattern(artifact1) == 'prefix/group/projectA/[revision]/ivys/[revision]/ivy.xml'
+        pattern.toVersionListPattern(artifact2) == 'prefix/org/group/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())
+        def module1 = new DefaultModuleIdentifier("group", "projectA")
+        def module2 = new DefaultModuleIdentifier("org.group", "projectA")
 
         expect:
-        pattern.toModulePath(artifact1) == 'prefix/group/projectA'
-        pattern.toModulePath(artifact2) == 'prefix/org/group/projectA'
+        pattern.toModulePath(module1) == 'prefix/group/projectA'
+        pattern.toModulePath(module2) == '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())
+        def artifact1 = artifact("group", "projectA", "1.2")
+        def artifact2 = artifact("org.group", "projectA", "1.2")
 
         expect:
         pattern.toModuleVersionPath(artifact1) == 'prefix/group/projectA/1.2'
         pattern.toModuleVersionPath(artifact2) == 'prefix/org/group/projectA/1.2'
     }
 
-    def "can substitute revision in filename by timestamped version when provided"() {
-        def pattern = new M2ResourcePattern("prefix/" + MavenPattern.M2_PATTERN)
-        def artifact1 = new DefaultArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2-SNAPSHOT",  [timestamp: "1.2-20100101.120001-1"]), new Date(), "projectA", "ivy", "ivy")
-        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2-SNAPSHOT"), new Date())
-        expect:
-        pattern.toPath(artifact1) == 'prefix/group/projectA/1.2-SNAPSHOT/projectA-1.2-20100101.120001-1.ivy'
-        pattern.toPath(artifact2) == 'prefix/org/group/projectA/1.2-SNAPSHOT/ivy-1.2-SNAPSHOT.xml'
-    }
-
     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()))
+        pattern.toModulePath(new DefaultModuleIdentifier("group", "module"))
 
         then:
         thrown(UnsupportedOperationException)
     }
+
+    private static ModuleVersionArtifactMetaData artifact(String group, String name, String version) {
+        final moduleVersionId = DefaultModuleVersionIdentifier.newId(group, name, version)
+        return new DefaultModuleVersionArtifactMetaData(new DefaultModuleVersionArtifactIdentifier(moduleVersionId, "ivy", "ivy", "xml"))
+    }
 }
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
index bde5c11..f900983 100644
--- 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
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.artifacts.repositories.resolver
 
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
 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
@@ -28,6 +30,8 @@ class MavenResolverTest extends Specification {
 
     def rootUri = URI.create("localhost:8081:/testrepo/")
     def locallyAvailableResourceFinder = Mock(LocallyAvailableResourceFinder)
+    def parser = Mock(MetaDataParser)
+    def resolverStrategy = Stub(ResolverStrategy)
 
     def setup() {
         repositoryTransport.getRepository() >> repository
@@ -36,7 +40,8 @@ class MavenResolverTest extends Specification {
     @Unroll
     def "setUseMavenMetaData '#value' adapts versionLister to #classname"() {
         setup:
-        MavenResolver testresolver = new MavenResolver("test maven resolver", rootUri, repositoryTransport, locallyAvailableResourceFinder)
+        MavenResolver testresolver = new MavenResolver("test maven resolver", rootUri, repositoryTransport,
+                locallyAvailableResourceFinder, resolverStrategy)
         when:
         testresolver.setUseMavenMetadata(value)
         then:
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
index 1538348..0d3d608 100644
--- 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
@@ -15,40 +15,48 @@
  */
 
 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.Action
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ExactVersionMatcher
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestVersionStrategy
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData
 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.gradle.internal.UncheckedException
 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 moduleRevisionId = IvyUtil.createModuleRevisionId("org.acme", "testproject", "1.0")
+    def module = new DefaultModuleIdentifier("org.acme", "testproject")
+    def moduleVersion = new DefaultModuleVersionIdentifier(module, "1.0")
+    def artifact = new DefaultModuleVersionArtifactMetaData(new DefaultModuleVersionArtifactIdentifier(moduleVersion, "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)
+    final MavenVersionLister lister = new MavenVersionLister(repository)
 
     def "visit parses maven-metadata.xml"() {
         ExternalResource resource = Mock()
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern, artifact)
 
         then:
-        versionList.versionStrings == ['1.1', '1.2'] as Set
+        sort(versionList).collect {it.version} == ['1.2', '1.1']
 
         and:
         1 * repository.getResource(metaDataResource) >> resource
-        1 * resource.openStream() >> new ByteArrayInputStream("""
+        1 * resource.withContent(_) >> { Action action -> action.execute(new ByteArrayInputStream("""
 <metadata>
     <versioning>
         <versions>
@@ -56,7 +64,8 @@ class MavenVersionListerTest extends Specification {
             <version>1.2</version>
         </versions>
     </versioning>
-</metadata>""".bytes)
+</metadata>""".bytes))
+        }
         1 * resource.close()
         0 * repository._
         0 * resource._
@@ -67,16 +76,19 @@ class MavenVersionListerTest extends Specification {
         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)
+        def versionList = lister.getVersionList(module)
+        final pattern1 = pattern("prefix1/" + MavenPattern.M2_PATTERN)
+        versionList.visit(pattern1, artifact)
+        final pattern2 = pattern("prefix2/" + MavenPattern.M2_PATTERN)
+        versionList.visit(pattern2, artifact)
 
         then:
-        versionList.versionStrings == ['1.1', '1.2', '1.3'] as Set
+        sort(versionList).collect {it.version} == ['1.3', '1.2', '1.1']
+        sort(versionList).collect {it.pattern} == [pattern2, pattern1, pattern1]
 
         and:
         1 * repository.getResource('prefix1/org/acme/testproject/maven-metadata.xml') >> resource1
-        1 * resource1.openStream() >> new ByteArrayInputStream("""
+        1 * resource1.withContent(_) >> { Action action -> action.execute(new ByteArrayInputStream("""
 <metadata>
     <versioning>
         <versions>
@@ -84,9 +96,10 @@ class MavenVersionListerTest extends Specification {
             <version>1.2</version>
         </versions>
     </versioning>
-</metadata>""".bytes)
+</metadata>""".bytes))
+        }
         1 * repository.getResource('prefix2/org/acme/testproject/maven-metadata.xml') >> resource2
-        1 * resource2.openStream() >> new ByteArrayInputStream("""
+        1 * resource2.withContent(_) >> { Action action -> action.execute(new ByteArrayInputStream("""
 <metadata>
     <versioning>
         <versions>
@@ -94,23 +107,24 @@ class MavenVersionListerTest extends Specification {
             <version>1.3</version>
         </versions>
     </versioning>
-</metadata>""".bytes)
+</metadata>""".bytes))
+        }
     }
 
     def "visit ignores duplicate patterns"() {
         ExternalResource resource = Mock()
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern, artifact)
         versionList.visit(pattern, artifact)
 
         then:
-        versionList.versionStrings == ['1.1', '1.2'] as Set
+        sort(versionList).collect {it.version} == ['1.2', '1.1']
 
         and:
         1 * repository.getResource(metaDataResource) >> resource
-        1 * resource.openStream() >> new ByteArrayInputStream("""
+        1 * resource.withContent(_) >> { Action action -> action.execute(new ByteArrayInputStream("""
 <metadata>
     <versioning>
         <versions>
@@ -118,15 +132,21 @@ class MavenVersionListerTest extends Specification {
             <version>1.2</version>
         </versions>
     </versioning>
-</metadata>""".bytes)
+</metadata>""".bytes))
+        }
         1 * resource.close()
         0 * repository._
         0 * resource._
     }
 
+    private static List<VersionList.ListedVersion> sort(VersionList versionList) {
+        def latestStrategy = new LatestVersionStrategy(new ExactVersionMatcher())
+        versionList.sortLatestFirst(latestStrategy)
+    }
+
     def "visit throws ResourceNotFoundException when maven-metadata not available"() {
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern, artifact)
 
         then:
@@ -140,16 +160,17 @@ class MavenVersionListerTest extends Specification {
         ExternalResource resource = Mock()
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern, artifact)
 
         then:
         ResourceException e = thrown()
         e.message == "Unable to load Maven meta-data from $metaDataResource."
-        e.cause instanceof SAXParseException
+        e.cause instanceof UncheckedException
+        e.cause.cause instanceof SAXParseException
         1 * resource.close()
         1 * repository.getResource(metaDataResource) >> resource;
-        1 * resource.openStream() >> new ByteArrayInputStream("yo".bytes)
+        1 * resource.withContent(_) >> { Action action -> action.execute(new ByteArrayInputStream("yo".bytes)) }
         0 * repository._
     }
 
@@ -157,7 +178,7 @@ class MavenVersionListerTest extends Specification {
         def failure = new IOException()
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern, artifact)
 
         then:
@@ -169,6 +190,6 @@ class MavenVersionListerTest extends Specification {
     }
 
     def pattern(String pattern) {
-        return new org.gradle.api.internal.artifacts.repositories.resolver.M2ResourcePattern(pattern)
+        return new 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
index 42128df..3433fcc 100644
--- 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
@@ -16,24 +16,30 @@
 
 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.artifacts.DefaultModuleIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ExactVersionMatcher
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.LatestVersionStrategy
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactIdentifier
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionArtifactMetaData
 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 moduleRevisionId = IvyUtil.createModuleRevisionId("org.acme", "proj1", "1.0")
+    def module = new DefaultModuleIdentifier("org.acme", "proj1")
+    def moduleVersion = new DefaultModuleVersionIdentifier(module, "1.0")
+    def artifact = new DefaultModuleVersionArtifactMetaData(new DefaultModuleVersionArtifactIdentifier(moduleVersion, "proj1", "jar", "jar"))
 
-    def org.gradle.api.internal.artifacts.repositories.resolver.ResourceVersionLister lister;
+    def ResourceVersionLister lister;
 
     def setup() {
-        lister = new org.gradle.api.internal.artifacts.repositories.resolver.ResourceVersionLister(repo)
+        lister = new ResourceVersionLister(repo)
     }
 
     def "visit propagates Exceptions as ResourceException"() {
@@ -43,7 +49,7 @@ class ResourceVersionListerTest extends Specification {
         1 * repo.list(_) >> { throw failure }
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(testPattern, artifact)
 
         then:
@@ -52,17 +58,16 @@ class ResourceVersionListerTest extends Specification {
         e.cause == failure
     }
 
-    def "visit throws ResourceNotFoundException for missing resource"() {
+    def "visit produces empty versionList for missing resource"() {
         setup:
         1 * repo.list(_) >> null
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern(testPattern), artifact)
 
         then:
-        ResourceNotFoundException e = thrown()
-        e.message == "Cannot list versions from /some/."
+        versionList.empty
 
         where:
         testPattern << ["/some/[revision]", "/some/version-[revision]"]
@@ -73,7 +78,7 @@ class ResourceVersionListerTest extends Specification {
         1 * repo.list(_) >> []
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern("/some/[revision]"), artifact)
 
         then:
@@ -83,11 +88,11 @@ class ResourceVersionListerTest extends Specification {
     @Unroll
     def "visit resolves versions from from pattern with '#testPattern'"() {
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern(testPattern), artifact)
 
         then:
-        versionList.versionStrings == ["1", "2.1", "a-version"] as Set
+        sort(versionList).collect { it.version } == ["2.1", "1", "a-version"]
 
         and:
         1 * repo.list(repoListingPath) >> repoResult
@@ -116,12 +121,15 @@ class ResourceVersionListerTest extends Specification {
 
     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)
+        def versionList = lister.getVersionList(module)
+        def pattern1 = pattern("/[revision]/[artifact]-[revision].[ext]")
+        def pattern2 = pattern("/[organisation]/[revision]/[artifact]-[revision].[ext]")
+        versionList.visit(pattern1, artifact)
+        versionList.visit(pattern2, artifact)
 
         then:
-        versionList.versionStrings == ["1.2", "1.3", "1.4"] as Set
+        sort(versionList).collect { it.version } == ["1.4", "1.3", "1.2"]
+        sort(versionList).collect { it.pattern } == [pattern2, pattern1, pattern1]
 
         and:
         1 * repo.list("/") >> ["1.2", "1.3"]
@@ -131,21 +139,28 @@ class ResourceVersionListerTest extends Specification {
 
     def "visit ignores duplicate patterns"() {
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
-        versionList.visit(pattern("/a/[revision]/[artifact]-[revision].[ext]"), artifact)
+        def versionList = lister.getVersionList(module)
+        final patternA = pattern("/a/[revision]/[artifact]-[revision].[ext]")
+        versionList.visit(patternA, artifact)
         versionList.visit(pattern("/a/[revision]/[artifact]-[revision]"), artifact)
 
         then:
-        versionList.versionStrings == ["1.2", "1.3"] as Set
+        sort(versionList).collect { it.version } == ["1.3", "1.2"]
+        sort(versionList).collect { it.pattern } == [patternA, patternA]
 
         and:
         1 * repo.list("/a/") >> ["1.2", "1.3"]
         0 * repo._
     }
 
+    private static List<VersionList.ListedVersion> sort(VersionList versionList) {
+        def latestStrategy = new LatestVersionStrategy(new ExactVersionMatcher())
+        versionList.sortLatestFirst(latestStrategy)
+    }
+
     def "visit substitutes non revision placeholders from pattern before hitting repository"() {
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern(inputPattern), artifact)
 
         then:
@@ -167,7 +182,7 @@ class ResourceVersionListerTest extends Specification {
         repo.list(_) >> repoResult
 
         when:
-        def versionList = lister.getVersionList(moduleRevisionId)
+        def versionList = lister.getVersionList(module)
         versionList.visit(pattern(testPattern), artifact)
 
         then:
@@ -179,6 +194,6 @@ class ResourceVersionListerTest extends Specification {
     }
 
     def pattern(String pattern) {
-        return new org.gradle.api.internal.artifacts.repositories.resolver.IvyResourcePattern(pattern)
+        return new IvyResourcePattern(pattern)
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
deleted file mode 100644
index f352560..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
+++ /dev/null
@@ -1,80 +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.resolutioncache
-
-import org.gradle.CacheUsage
-import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
-import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
-import org.gradle.cache.internal.CacheFactory
-import org.gradle.cache.internal.DefaultCacheRepository
-import org.gradle.internal.TimeProvider
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.testfixtures.internal.InMemoryCacheFactory
-import org.junit.Rule
-import spock.lang.Specification
-import spock.lang.Unroll
-
-class DefaultArtifactResolutionCacheTest extends Specification {
-
-    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
-
-    Date time = new Date(0)
-    TimeProvider timeProvider = new TimeProvider() {
-        long getCurrentTime() { time.getTime() }
-    }
-
-    CacheFactory cacheFactory = new InMemoryCacheFactory()
-    DefaultCacheRepository cacheRepository
-    DefaultCacheLockingManager cacheLockingManager
-    
-    DefaultCachedExternalResourceIndex<String> index
-
-    def setup() {
-        cacheRepository = new DefaultCacheRepository(tmp.createDir('user-home'), tmp.createDir('project-cache'), CacheUsage.ON, cacheFactory)
-        cacheLockingManager = new DefaultCacheLockingManager(cacheRepository)
-        index = new DefaultCachedExternalResourceIndex(tmp.createFile("index"), String, timeProvider, cacheLockingManager)
-    }
-
-    @Unroll "stores entry - lastModified = #lastModified, artifactUrl = #artifactUrl"() {
-        given:
-        def key = "key"
-        def artifactFile = tmp.createFile("artifact") << "content"
-        
-        when:
-        index.store(key, artifactFile, new DefaultExternalResourceMetaData(artifactUrl, lastModified, 100, null, null))
-        
-        then:
-        def cached = index.lookup(key)
-        
-        and:
-        cached != null
-        cached.cachedFile == artifactFile
-        cached.externalResourceMetaData != null
-        cached.externalResourceMetaData.lastModified == lastModified
-        cached.externalResourceMetaData.location == artifactUrl
-
-
-        where:
-        lastModified | artifactUrl
-        new Date()   | null
-        null         | "abc"
-        null         | null
-        new Date()   | "abc"
-    }
-
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy
index 919106c..0a13a29 100644
--- 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
@@ -16,14 +16,12 @@
 
 package org.gradle.api.internal.artifacts.result
 
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.internal.Factory
 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"() {
@@ -39,8 +37,8 @@ class DefaultResolutionResultTest extends Specification {
         dep2.selected.addDependency(dep3).addDependency(dep4)
 
         when:
-        def deps = new DefaultResolutionResult(root).allDependencies
-        def modules = new DefaultResolutionResult(root).allModuleVersions
+        def deps = new DefaultResolutionResult({root} as Factory).allDependencies
+        def modules = new DefaultResolutionResult({root} as Factory).allComponents
 
         then:
         deps == [dep1, dep2, dep3, dep4] as Set
@@ -58,13 +56,13 @@ class DefaultResolutionResultTest extends Specification {
         def root = newModule('root').addDependency(dep).addDependency(newDependency('dep2')).addDependency(dep3)
         dep.selected.addDependency(dep3)
 
-        def result = new DefaultResolutionResult(root)
+        def result = new DefaultResolutionResult({root} as Factory)
 
         when:
         def deps = []
         def modules = []
         result.allDependencies { deps << it }
-        result.allModuleVersions { modules << it }
+        result.allComponents { modules << it }
 
         then:
         deps*.requested.group == ['dep1', 'dep3', 'dep2', 'dep3']
@@ -79,11 +77,11 @@ class DefaultResolutionResultTest extends Specification {
         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))
+        dep1.selected.addDependency(new DefaultResolvedDependencyResult(DefaultModuleComponentSelector.newSelector('a', 'a', '1'), root, dep1.selected))
 
         when:
-        def deps = new DefaultResolutionResult(root).allDependencies
-        def modules = new DefaultResolutionResult(root).allModuleVersions
+        def deps = new DefaultResolutionResult({root} as Factory).allDependencies
+        def modules = new DefaultResolutionResult({root} as Factory).allComponents
 
         then:
         deps.size() == 2
@@ -98,18 +96,18 @@ class DefaultResolutionResultTest extends Specification {
         def root = newModule('root').addDependency(dep1).addDependency(dep2)
 
         when:
-        def result = new DefaultResolutionResult(root)
+        def result = new DefaultResolutionResult({root} as Factory)
 
         then:
         result.allDependencies == [dep1, dep2] as Set
-        result.allModuleVersions == [root, dep1.selected, dep2.selected] as Set
+        result.allComponents == [root, dep1.selected, dep2.selected] as Set
 
         when:
         result.allDependencies << newDependency('dep3')
-        result.allModuleVersions << newModule('foo')
+        result.allComponents << newModule('foo')
 
         then:
         result.allDependencies == [dep1, dep2] as Set
-        result.allModuleVersions == [root, dep1.selected, dep2.selected] as Set
+        result.allComponents == [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
index 127622c..e19bc8c 100644
--- 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
@@ -20,10 +20,6 @@ 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"() {
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
deleted file mode 100644
index 282e36b..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy
+++ /dev/null
@@ -1,83 +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.externalresource
-
-import org.gradle.api.internal.externalresource.cached.CachedExternalResource
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceAdapter
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
-import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.hash.HashUtil
-import org.gradle.util.hash.HashValue
-import org.junit.Rule
-import spock.lang.Specification
-
-public class CachedExternalResourceAdapterTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-
-    ExternalResourceAccessor accessor = Mock()
-    CachedExternalResource cachedExternalResource = Mock()
-    CachedExternalResourceAdapter cachedResource
-    def origin = tmpDir.file('origin')
-    def destination = tmpDir.file('destination')
-    def download = tmpDir.file('download')
-
-    def setup() {
-        cachedExternalResource.cachedFile >> origin
-        cachedExternalResource.sha1 >> { HashUtil.createHash(origin, "SHA1") }
-        cachedResource = new CachedExternalResourceAdapter("resource-source", cachedExternalResource, accessor)
-    }
-
-    def "delegates to cached artifact"() {
-        given:
-        cachedExternalResource.contentLength >> 22
-        cachedExternalResource.externalResourceMetaData >> new DefaultExternalResourceMetaData("url")
-        cachedExternalResource.externalLastModifiedAsTimestamp >> 33
-
-        expect:
-        cachedResource.contentLength == 22
-        cachedResource.lastModified == 33
-    }
-
-    def "will copy cache file to destination"() {
-        given:
-        origin << "some content"
-
-        when:
-        cachedResource.writeTo(destination)
-
-        then:
-        destination.assertIsCopyOf(origin)
-    }
-
-    def "will copy download resource if destination does not match original sha1 after copy"() {
-        given:
-        origin << "some content"
-        download << "some other content"
-        ExternalResource resource = Mock()
-
-        when:
-        cachedResource.writeTo(destination)
-
-        then:
-        cachedExternalResource.cachedFile >> origin
-        cachedExternalResource.sha1 >> new HashValue("1234")
-
-        and:
-        accessor.getResource("resource-source") >> resource
-        resource.writeTo(destination)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceAdapterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceAdapterTest.groovy
new file mode 100644
index 0000000..988904c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceAdapterTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.externalresource.cached
+
+import org.gradle.api.internal.externalresource.ExternalResource
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+public class CachedExternalResourceAdapterTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    ExternalResourceAccessor accessor = Mock()
+    CachedExternalResource cachedExternalResource = Mock()
+    CachedExternalResourceAdapter cachedResource
+    def origin = tmpDir.file('origin')
+    def destination = tmpDir.file('destination')
+    def download = tmpDir.file('download')
+
+    def setup() {
+        cachedExternalResource.cachedFile >> origin
+        cachedResource = new CachedExternalResourceAdapter("resource-source", cachedExternalResource, accessor)
+    }
+
+    def "delegates to cached artifact"() {
+        given:
+        cachedExternalResource.contentLength >> 22
+        cachedExternalResource.externalResourceMetaData >> new DefaultExternalResourceMetaData("url")
+        cachedExternalResource.externalLastModifiedAsTimestamp >> 33
+
+        expect:
+        cachedResource.contentLength == 22
+        cachedResource.lastModified == 33
+    }
+
+    def "will copy cache file to destination"() {
+        given:
+        origin << "some content"
+
+        when:
+        cachedResource.writeTo(destination)
+
+        then:
+        destination.assertIsCopyOf(origin)
+    }
+
+    def "will copy download resource if destination does not match original sha1 after copy"() {
+        given:
+        origin << "some content"
+        download << "some other content"
+        ExternalResource resource = Mock()
+
+        when:
+        cachedResource.writeTo(destination)
+
+        then:
+        cachedExternalResource.cachedFile >> origin
+
+        and:
+        accessor.getResource("resource-source") >> resource
+        resource.writeTo(destination)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/cached/DefaultArtifactResolutionCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/cached/DefaultArtifactResolutionCacheTest.groovy
new file mode 100644
index 0000000..8d72fd0
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/cached/DefaultArtifactResolutionCacheTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.cached
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
+import org.gradle.messaging.serialize.Serializer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.internal.InMemoryIndexedCache
+import org.gradle.util.BuildCommencedTimeProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DefaultArtifactResolutionCacheTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
+
+    BuildCommencedTimeProvider timeProvider = Stub(BuildCommencedTimeProvider) {
+        getCurrentTime() >> 0
+    }
+
+    def cacheLockingManager = Stub(CacheLockingManager) {
+        useCache(_, _) >> { displayName, action ->
+            if (action instanceof org.gradle.internal.Factory) {
+                return action.create()
+            } else {
+                action.run()
+            }
+        }
+
+        createCache(_, _, _) >> { String file, Serializer keySerializer, Serializer valueSerializer ->
+            return new InMemoryIndexedCache<>(valueSerializer)
+        }
+    }
+    
+    DefaultCachedExternalResourceIndex<String> index
+
+    def setup() {
+        index = new DefaultCachedExternalResourceIndex("index", String, timeProvider, cacheLockingManager)
+    }
+
+    @Unroll "stores entry - lastModified = #lastModified, artifactUrl = #artifactUrl"() {
+        given:
+        def key = "key"
+        def artifactFile = tmp.createFile("artifact") << "content"
+        
+        when:
+        index.store(key, artifactFile, new DefaultExternalResourceMetaData(artifactUrl, lastModified, 100, null, null))
+        
+        then:
+        def cached = index.lookup(key)
+        
+        and:
+        cached != null
+        cached.cachedFile == artifactFile
+        cached.externalResourceMetaData != null
+        cached.externalResourceMetaData.lastModified == lastModified
+        cached.externalResourceMetaData.location == artifactUrl
+
+
+        where:
+        lastModified | artifactUrl
+        new Date()   | null
+        null         | "abc"
+        null         | null
+        new Date()   | "abc"
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy
index 7af9d1b..fb4c835 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy
@@ -17,34 +17,32 @@
 package org.gradle.api.internal.externalresource.ivy
 
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.CachingModuleVersionRepository
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactIdentifier
 import org.gradle.api.internal.externalresource.cached.CachedArtifact
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
 import org.gradle.cache.PersistentIndexedCache
-import org.gradle.internal.TimeProvider
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.BuildCommencedTimeProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class ArtifactAtRepositoryCachedArtifactIndexTest extends Specification {
 
     CacheLockingManager cacheLockingManager = Mock()
-    TimeProvider timeProvider = Mock()
+    BuildCommencedTimeProvider timeProvider = Mock()
     ArtifactAtRepositoryKey key = Mock()
     ExternalResourceMetaData metaData = Mock()
-    CachingModuleVersionRepository moduleVersionRepository = Mock()
-
     @Rule TestNameTestDirectoryProvider folder = new TestNameTestDirectoryProvider();
 
     PersistentIndexedCache persistentIndexedCache = Mock()
 
     ArtifactAtRepositoryCachedArtifactIndex index
-    File persistentCacheFile
+    String persistentCacheFile
     CachedArtifact cachedArtifact = Mock()
 
 
     def setup() {
-        persistentCacheFile = folder.createFile("cacheFile")
+        persistentCacheFile = "cacheFile"
         index = new ArtifactAtRepositoryCachedArtifactIndex(persistentCacheFile, timeProvider, cacheLockingManager)
     }
 
@@ -67,9 +65,8 @@ class ArtifactAtRepositoryCachedArtifactIndexTest extends Specification {
 
     def "stored artifact is put into persistentIndexedCache"() {
         setup:
-        1 * moduleVersionRepository.getId() >> "RepoID"
         1 * cacheLockingManager.createCache(persistentCacheFile, _, _) >> persistentIndexedCache
-        def key = new ArtifactAtRepositoryKey(moduleVersionRepository, "artifactId");
+        def key = new ArtifactAtRepositoryKey("RepoID", Stub(ModuleVersionArtifactIdentifier));
         def testFile = folder.createFile("aTestFile");
         when:
         index.store(key, testFile, BigInteger.TEN)
@@ -122,10 +119,9 @@ class ArtifactAtRepositoryCachedArtifactIndexTest extends Specification {
     }
 
     def createEntryInPersistentCache() {
-        1 * moduleVersionRepository.getId() >> "RepoID"
         1 * cacheLockingManager.createCache(persistentCacheFile, _, _) >> persistentIndexedCache
         1 * cacheLockingManager.useCache("lookup from artifact resolution cache \'cacheFile\'", _) >> {descr, factory -> factory.create()}
-        def key = new ArtifactAtRepositoryKey(moduleVersionRepository, "artifactId");
+        def key = new ArtifactAtRepositoryKey("RepoID", Stub(ModuleVersionArtifactIdentifier));
         1 * persistentIndexedCache.get(key) >> cachedArtifact;
         key
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy
index cb6f7c1..d2e0b5f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy
@@ -16,8 +16,9 @@
 
 package org.gradle.api.internal.externalresource.local
 
+import org.gradle.internal.resource.local.LocallyAvailableResource
 import spock.lang.Specification
-import org.gradle.util.hash.HashUtil
+import org.gradle.internal.hash.HashUtil
 
 class CompositeLocallyAvailableResourceFinderTest extends Specification {
     
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
deleted file mode 100644
index 3db4adc..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
+++ /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.api.internal.externalresource.local
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.hash.HashUtil
-import org.junit.Rule
-import spock.lang.Specification
-
-public class DefaultLocallyAvailableResourceTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-
-    def "uses value from origin file"() {
-        given:
-        def origin = tmpDir.file("origin")
-        origin << "some text"
-
-        when:
-        def DefaultLocallyAvailableResource resource = new DefaultLocallyAvailableResource(origin)
-
-        then:
-        resource.sha1 == HashUtil.createHash(origin, 'SHA1')
-        resource.contentLength == origin.length()
-        resource.lastModified == origin.lastModified()
-    }
-
-    def "sha1 content length and last modified do not change when file is subsequently modified"() {
-        given:
-        def origin = tmpDir.file("origin")
-        origin << "some text"
-
-
-        when:
-        def DefaultLocallyAvailableResource resource = new DefaultLocallyAvailableResource(origin)
-        def originalSha1 = resource.sha1
-        def originalContentLength = resource.contentLength
-        def originalLastModified = resource.lastModified
-
-        and:
-        origin << "some more text"
-        origin.setLastModified(11)
-
-        then:
-        resource.sha1 != HashUtil.createHash(origin, 'SHA1')
-        resource.contentLength != origin.length()
-        resource.lastModified != origin.lastModified()
-
-        and:
-        resource.sha1 == originalSha1
-        resource.contentLength == originalContentLength
-        resource.lastModified == originalLastModified
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
index 3536f60..add05c1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.externalresource.local
 
 import org.gradle.internal.Factory
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.hash.HashUtil
+import org.gradle.internal.hash.HashUtil
 import org.junit.Rule
 import spock.lang.Specification
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy
index 206ea2f..57b1d0b 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.externalresource.metadata
 
 import spock.lang.Specification
-import org.gradle.util.hash.HashValue
+import org.gradle.internal.hash.HashValue
 
 class DefaultExternalResourceMetaDataTest extends Specification {
 
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 13312f4..be40d75 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
@@ -17,18 +17,20 @@
 package org.gradle.api.internal.externalresource.transfer
 
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.util.BuildCommencedTimeProvider
 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.metadata.ExternalResourceMetaData
-import org.gradle.util.hash.HashValue
-import org.gradle.api.internal.externalresource.local.LocallyAvailableResource
-import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource
+import org.gradle.internal.hash.HashValue
+import org.gradle.internal.resource.local.LocallyAvailableResource
+import org.gradle.api.internal.externalresource.DefaultLocallyAvailableExternalResource
 
 class DefaultCacheAwareExternalResourceAccessorTest extends Specification {
     final accessor = Mock(ExternalResourceAccessor)
     final index = Mock(CachedExternalResourceIndex)
-    final cache = new DefaultCacheAwareExternalResourceAccessor(accessor, index)
+    final timeProvider = Mock(BuildCommencedTimeProvider)
+    final cache = new DefaultCacheAwareExternalResourceAccessor(accessor, index, timeProvider)
 
     def "will use sha1 from metadata for finding candidates if available"() {
         given:
@@ -50,10 +52,12 @@ class DefaultCacheAwareExternalResourceAccessorTest extends Specification {
         def foundResource = cache.getResource("location", localCandidates)
 
         then:
+        1 * timeProvider.currentTime >> new Date().time
+        1 * cached.cachedAt >> new Date().time + 2
         0 * accessor.getResourceSha1(_)
         1 * localCandidates.findByHashValue(sha1) >> localCandidate
 
         and:
-        foundResource instanceof LocallyAvailableExternalResource
+        foundResource instanceof DefaultLocallyAvailableExternalResource
     }
 }
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
index 259dcc3..e5168cd 100644
--- 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
@@ -106,19 +106,6 @@ class ProgressLoggingExternalResourceAccessorTest extends Specification {
         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']
+        method << ['close', 'getMetaData', 'getName', 'getLastModified', 'getContentLength', 'isLocal']
     }
 }
\ No newline at end of file
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 d8eb05f..62ef640 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
@@ -33,7 +33,7 @@ class ApacheDirectoryListingParserTest extends Specification {
 
     def "parse returns empty List if no link can be found"() {
         expect:
-        List urls = parser.parse(baseUrl, "<html>no link here</html>".bytes, CONTENT_TYPE)
+        List urls = parser.parse(baseUrl, new ByteArrayInputStream("<html>no link here</html>".bytes), CONTENT_TYPE)
         assertNotNull(urls)
         urls.isEmpty()
     }
@@ -55,7 +55,7 @@ class ApacheDirectoryListingParserTest extends Specification {
         <a href="directory3">directory3</a>
         <a href="directory4"/>"""
         expect:
-        def uris = parser.parse(baseUrl, html.bytes, CONTENT_TYPE)
+        def uris = parser.parse(baseUrl, new ByteArrayInputStream(html.bytes), CONTENT_TYPE)
         assertNotNull(uris)
         uris.collect {it.toString()} == ["http://testrepo/directory1", "http://testrepo/directory2", "http://testrepo/directory3", "http://testrepo/directory4"]
     }
@@ -65,7 +65,7 @@ class ApacheDirectoryListingParserTest extends Specification {
         <a href="directory1">directory1</a>
         <a href="directory2">directory2</a>"""
         when:
-        parser.parse(baseUrl, html.bytes, contentType)
+        parser.parse(baseUrl, new ByteArrayInputStream(html.bytes), contentType)
         then:
         thrown(ResourceException)
         where:
@@ -80,7 +80,7 @@ class ApacheDirectoryListingParserTest extends Specification {
         assert !Arrays.equals(encodedHtml, html.getBytes("utf-8"))
 
         expect:
-        def uris = parser.parse(baseUrl, encodedHtml, 'text/html;charset=ISO-8859-1')
+        def uris = parser.parse(baseUrl, new ByteArrayInputStream(encodedHtml), 'text/html;charset=ISO-8859-1')
         uris.collect {it.toString()} == ["http://testrepo/\u00c1\u00d2"]
     }
 
@@ -91,14 +91,14 @@ class ApacheDirectoryListingParserTest extends Specification {
         def encodedHtml = html.getBytes('utf-8')
 
         expect:
-        def uris = parser.parse(baseUrl, encodedHtml, 'text/html')
+        def uris = parser.parse(baseUrl, new ByteArrayInputStream(encodedHtml), 'text/html')
         uris.collect {it.toString()} == ["http://testrepo/\u0321\u0322"]
     }
 
     @Unroll
     def "parse ignores #descr"() {
         expect:
-        parser.parse(baseUrl, "<a href=\"${href}\">link</a>".toString().bytes, CONTENT_TYPE).isEmpty()
+        parser.parse(baseUrl, new ByteArrayInputStream("<a href=\"${href}\">link</a>".toString().bytes), CONTENT_TYPE).isEmpty()
         where:
         href                                                | descr
         "http://anothertestrepo/"                           | "URLs which aren't children of base URL"
@@ -115,7 +115,7 @@ class ApacheDirectoryListingParserTest extends Specification {
     def "parseLink handles #urlDescr"() {
         def listingParser = new ApacheDirectoryListingParser()
         expect:
-        def foundURIs = listingParser.parse(URI.create(baseUri), "<a href=\"${href}\">link</a>".toString().bytes, CONTENT_TYPE)
+        def foundURIs = listingParser.parse(URI.create(baseUri), new ByteArrayInputStream("<a href=\"${href}\">link</a>".toString().bytes), CONTENT_TYPE)
         !foundURIs.isEmpty()
         foundURIs.collect {it.toString()} == ["${baseUri}/directory1"]
         where:
@@ -137,7 +137,7 @@ class ApacheDirectoryListingParserTest extends Specification {
         setup:
         def byte[] content = resources.getResource("${repoType}_dirlisting.html").bytes
         expect:
-        List<URI> urls = new ApacheDirectoryListingParser().parse(new URI(artifactRootURI), content, CONTENT_TYPE)
+        List<URI> urls = new ApacheDirectoryListingParser().parse(new URI(artifactRootURI), new ByteArrayInputStream(content), CONTENT_TYPE)
         urls.collect {it.toString()} as Set == ["${artifactRootURI}3.7/",
                 "${artifactRootURI}3.8/",
                 "${artifactRootURI}3.8.1/",
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 147bdd2..e68c664 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,6 +16,7 @@
 
 package org.gradle.api.internal.externalresource.transport.http
 
+import org.gradle.api.Transformer
 import spock.lang.Specification
 
 class HttpResourceListerTest extends Specification {
@@ -30,7 +31,7 @@ class HttpResourceListerTest extends Specification {
         when:
         lister.list("http://testrepo/")
         then:
-        1 * externalResource.writeTo(_) >> {OutputStream outputStream -> outputStream.write("<a href='child'/>".bytes)}
+        1 * externalResource.withContent(_) >> {Transformer action -> return action.transform(new ByteArrayInputStream("<a href='child'/>".bytes))}
         1 * externalResource.getContentType() >> "text/html"
         1 * externalResource.close()
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy
index 13ec285..10a3c85 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy
@@ -21,15 +21,18 @@ import spock.lang.Specification
 class JavaSystemPropertiesHttpProxySettingsTest extends Specification {
     def "proxy is not configured when proxyHost property not set"() {
         expect:
-        def settings = settings(null, proxyPort, nonProxyHosts)
+        def settings = settings(proxyHost, proxyPort, nonProxyHosts)
         settings.getProxy(requestHost) == null
 
         where:
-        proxyPort | nonProxyHosts | requestHost
-        null      | null          | null
-        null      | null          | "foo"
-        "111"     | null          | "foo"
-        null      | "foo|bar|baz" | "foo"
+        proxyHost | proxyPort | nonProxyHosts | requestHost
+        null      | null      | null          | null
+        null      | null      | null          | "foo"
+        null      | "111"     | null          | "foo"
+        null      | null      | "foo|bar|baz" | "foo"
+        ""        | null      | null          | null
+        ""        | ""        | null          | null
+        null      | ""        | null          | null
     }
 
     private JavaSystemPropertiesHttpProxySettings settings(host, proxyPort, nonProxyHosts) {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
index c5b2231..8d71ab8 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-package org.gradle.api.internal.notations;
-
+package org.gradle.api.internal.notations
 
 import org.gradle.api.artifacts.DependencyArtifact
-import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.internal.reflect.DirectInstantiator
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 11/10/11
- */
 public class DependencyMapNotationParserTest extends Specification {
 
     def parser = new DependencyMapNotationParser<DefaultExternalModuleDependency>(new DirectInstantiator(), DefaultExternalModuleDependency.class);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
index 2fdda25..a550d19 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
@@ -18,12 +18,9 @@ package org.gradle.api.internal.notations;
 
 
 import org.gradle.api.GradleException
-import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.internal.typeconversion.NotationParser
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 11/9/11
- */
 public class DependencyNotationParserTest extends Specification {
 
     def notationParser = Mock(NotationParser)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
index 387701c..a6c99b5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
@@ -14,19 +14,15 @@
  * limitations under the License.
  */
 
-package org.gradle.api.internal.notations;
-
+package org.gradle.api.internal.notations
 
 import org.gradle.api.artifacts.DependencyArtifact
-import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.util.HelperUtil
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 11/10/11
- */
 public class DependencyStringNotationParserTest extends Specification {
 
     def parser = new DependencyStringNotationParser(new DirectInstantiator(), DefaultExternalModuleDependency.class);
@@ -85,7 +81,7 @@ public class DependencyStringNotationParserTest extends Specification {
 
     def "with 3-element GString"() {
         when:
-        def gstring = HelperUtil.createScript("descriptor = 'org.gradle:gradle-core:1.0'; \"\$descriptor\"").run()
+        def gstring = TestUtil.createScript("descriptor = 'org.gradle:gradle-core:1.0'; \"\$descriptor\"").run()
         def d = parser.parseNotation(gstring);
 
         then:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
index 7b877c6..32a6d94 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
@@ -25,9 +25,6 @@ import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.util.GUtil
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class ProjectDependencyFactoryTest extends Specification {
 
     def projectDummy = Mock(ProjectInternal)
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-bad-confs.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-bad-confs.xml
deleted file mode 100644
index 7287882..0000000
--- a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-bad-confs.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing,
-   software distributed under the License is distributed on an
-   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-   KIND, either express or implied.  See the License for the
-   specific language governing permissions and limitations
-   under the License.    
--->
-<ivy-module version="1.0">
-	<info  organisation="myorg"
-	       module="mymodule"
-	       status="integration"
-	/>
-	<configurations>
-		<conf name="A" extends="invalidConf"/>
-	</configurations>
-</ivy-module>
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-cyclic-confs1.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-cyclic-confs1.xml
deleted file mode 100644
index 3890369..0000000
--- a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-cyclic-confs1.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing,
-   software distributed under the License is distributed on an
-   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-   KIND, either express or implied.  See the License for the
-   specific language governing permissions and limitations
-   under the License.    
--->
-<ivy-module version="1.0">
-	<info  organisation="myorg"
-	       module="mymodule"
-	       status="integration"
-	/>
-	<configurations>
-		<conf name="A" extends="B"/>
-		<conf name="B" extends="A"/>
-	</configurations>
-</ivy-module>
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-empty-dependencies.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-empty-dependencies.xml
deleted file mode 100644
index 3240dc4..0000000
--- a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-empty-dependencies.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing,
-   software distributed under the License is distributed on an
-   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-   KIND, either express or implied.  See the License for the
-   specific language governing permissions and limitations
-   under the License.    
--->
-<ivy-module version="1.0">
-	<info organisation="myorg"
-	       module="mymodule"
-	       revision="myrev"
-	       status="integration"
-	       publication="20041101110000"
-	/>
-	<dependencies>
-	</dependencies>
-</ivy-module>
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml
index dfb4a55..dbc6812 100644
--- a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml
@@ -26,12 +26,10 @@
 	       e:attr1="value1">
 	       
 		<license name="MyLicense" url="http://www.my.org/mymodule/mylicense.html"/>
-		<repository name="ivyrep" url="http://www.jayasoft.fr/org/ivyrep/" pattern="[organisation]/[module]/ivy-[revision].xml" ivys="true" artifacts="false"/>
-		
 		<ivyauthor name="jayasoft" url="http://www.jayasoft.org/"/>
 		<ivyauthor name="myorg" url="http://www.myorg.org/"/>
+		<repository name="ivyrep" url="http://www.jayasoft.fr/org/ivyrep/" pattern="[organisation]/[module]/ivy-[revision].xml" ivys="true" artifacts="false"/>
 
-		
 		<description homepage="http://www.my.org/mymodule/">			
 	This module is <b>great</b> !<br/>
 	You can use it especially with myconf1 and myconf2, and myconf4 is not too bad too.
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
index da4afe7..81409ed 100644
--- 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
@@ -16,29 +16,33 @@
 
 package org.gradle.api.internal.artifacts.result
 
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.api.artifacts.result.ComponentSelectionReason
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 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
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 
-/**
- * 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())
+        new DefaultResolvedDependencyResult(DefaultModuleComponentSelector.newSelector(group, module, version), newModule(group, module, selectedVersion), newModule())
     }
 
     static DefaultUnresolvedDependencyResult newUnresolvedDependency(String group='x', String module='x', String version='1', String selectedVersion='1') {
-        def requested = newSelector(group, module, version)
-        new DefaultUnresolvedDependencyResult(requested, VersionSelectionReasons.REQUESTED, newModule(group, module, selectedVersion), new ModuleVersionResolveException(requested, "broken"))
+        def requested = DefaultModuleComponentSelector.newSelector(group, module, version)
+        new DefaultUnresolvedDependencyResult(requested, VersionSelectionReasons.REQUESTED, newModule(group, module, selectedVersion), new ModuleVersionResolveException(newSelector(group, module, version), "broken"))
+    }
+
+    static DefaultResolvedComponentResult newModule(String group='a', String module='a', String version='1',
+                                                        ComponentSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
+        new DefaultResolvedComponentResult(newId(group, module, version), selectionReason, new DefaultModuleComponentIdentifier(group, module, version))
     }
 
-    static DefaultResolvedModuleVersionResult newModule(String group='a', String module='a', String version='1',
-                                                        ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
-        new DefaultResolvedModuleVersionResult(newId(group, module, version), selectionReason)
+    static DefaultResolvedDependencyResult newDependency(ComponentSelector componentSelector, String group='a', String module='a', String selectedVersion='1') {
+        new DefaultResolvedDependencyResult(componentSelector, newModule(group, module, selectedVersion), newModule())
     }
 }
diff --git a/subprojects/core/core.gradle b/subprojects/core/core.gradle
index fd5a202..4a9667d 100755
--- a/subprojects/core/core.gradle
+++ b/subprojects/core/core.gradle
@@ -21,13 +21,14 @@ configurations {
 }
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     publishCompile libraries.slf4j_api
     publishCompile project(":baseServices")
     publishCompile project(":messaging")
 
     compile project(":baseServicesGroovy")
+    compile project(":resources")
     compile libraries.asm
     compile libraries.ant
     compile libraries.commons_collections
@@ -45,6 +46,19 @@ dependencies {
     compile project(":cli")
     compile project(":native")
 
+    runtime project(":docs")
+
+    compile(group: 'com.jfrog.bintray.client', name: 'bintray-client-java-impl', version: '0.1.0') {
+        exclude module:'groovy-all'
+        exclude module:'groovy'
+        exclude group: 'org.slf4j'
+        exclude module: 'commons-collections'
+        exclude module: 'commons-lang'
+        exclude module: 'httpclient'
+        exclude module: 'nekohtml'
+    }
+    compile libraries.commons_httpclient // Needed by bintray client
+
     runtime libraries.log4j_to_slf4j
     runtime libraries.jcl_to_slf4j
 
@@ -64,6 +78,7 @@ dependencies {
 }
 
 useTestFixtures()
+useTestFixtures(project: ":messaging")
 
 [compileGroovy, compileTestGroovy]*.groovyOptions*.fork(memoryInitialSize: '128M', memoryMaximumSize: '1G')
 
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/DeprecationHandlingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/DeprecationHandlingIntegrationTest.groovy
new file mode 100644
index 0000000..031ee8d
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/DeprecationHandlingIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.AbstractIntegrationSpec
+
+class DeprecationHandlingIntegrationTest extends AbstractIntegrationSpec {
+    def "reports first usage of deprecated feature from a build script"() {
+        buildFile << """
+
+someFeature()
+someFeature()
+task broken(type: DeprecatedTask) {
+    otherFeature()
+}
+
+repositories {
+    mavenRepo url: 'build/repo'
+}
+
+def someFeature() {
+    DeprecationLogger.nagUserOfDiscontinuedMethod("someFeature()")
+}
+
+class DeprecatedTask extends DefaultTask {
+    def otherFeature() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("otherFeature()")
+    }
+}
+"""
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        run()
+
+        then:
+        output.contains("Build file '$buildFile': line 3")
+        output.count("The someFeature() method has been deprecated") == 1
+        output.contains("Build file '$buildFile': line 6")
+        output.count("The otherFeature() method has been deprecated") == 1
+        output.contains("Build file '$buildFile': line 10")
+        output.count("The RepositoryHandler.mavenRepo() method has been deprecated") == 1
+
+        // Run again to ensure logging is reset
+        when:
+        executer.withDeprecationChecksDisabled()
+        run()
+
+        then:
+        output.contains("Build file '$buildFile': line 3")
+        output.count("The someFeature() method has been deprecated") == 1
+        output.contains("Build file '$buildFile': line 6")
+        output.count("The otherFeature() method has been deprecated") == 1
+        output.contains("Build file '$buildFile': line 10")
+        output.count("The RepositoryHandler.mavenRepo() method has been deprecated") == 1
+
+        // Not shown at quiet level
+        when:
+        executer.withArgument("--quiet")
+        run()
+
+        then:
+        output.count("The someFeature() method has been deprecated") == 0
+        output.count("The otherFeature() method has been deprecated") == 0
+        output.count("The RepositoryHandler.mavenRepo() method has been deprecated") == 0
+        errorOutput == ""
+    }
+
+    def "reports usage of deprecated feature from an init script"() {
+        def initScript = file("init.gradle") << """
+allprojects {
+    someFeature()
+}
+
+def someFeature() {
+    DeprecationLogger.nagUserOfDiscontinuedMethod("someFeature()")
+}
+
+"""
+
+        when:
+        executer.withDeprecationChecksDisabled().usingInitScript(initScript)
+        run()
+
+        then:
+        output.contains("Initialization script '$initScript': line 3")
+        output.count("The someFeature() method has been deprecated") == 1
+        errorOutput == ""
+    }
+
+    def "reports usage of deprecated feature from an applied script"() {
+        def script = file("project.gradle") << """
+
+def someFeature() {
+    DeprecationLogger.nagUserOfDiscontinuedMethod("someFeature()")
+}
+
+someFeature()
+"""
+        buildFile << "allprojects { apply from: 'project.gradle' }"
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        run()
+
+        then:
+        output.contains("Script '$script': line 7")
+        output.count("The someFeature() method has been deprecated") == 1
+        errorOutput == ""
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ApplyPluginIntegSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ApplyPluginIntegSpec.groovy
new file mode 100644
index 0000000..e3f3daa
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ApplyPluginIntegSpec.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.UnexpectedBuildFailure
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.FailsWith
+import spock.lang.Issue
+
+// TODO: This needs a better home - Possibly in the test kit package in the future
+class ApplyPluginIntegSpec extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2358")
+    @FailsWith(UnexpectedBuildFailure) // Test is currently failing
+    def "can reference plugin by id in unit test"() {
+
+        given:
+        file("src/main/groovy/org/acme/TestPlugin.groovy") << """
+            package com.acme
+            import org.gradle.api.*
+
+            class TestPlugin implements Plugin<Project> {
+                void apply(Project project) {
+                    println "testplugin applied"
+                }
+            }
+        """
+
+        file("src/main/resources/META-INF/gradle-plugins/testplugin.properties") << "implementation-class=org.acme.TestPlugin"
+
+        file("src/test/groovy/org/acme/TestPluginSpec.groovy") << """
+            import spock.lang.Specification
+            import ${ProjectBuilder.name}
+            import ${Project.name}
+            import com.acme.TestPlugin
+
+            class TestPluginSpec extends Specification {
+                def "can apply plugin by id"() {
+                    when:
+                    Project project = ProjectBuilder.builder().build()
+                    project.apply(plugin: "testplugin")
+
+                    then:
+                    project.plugins.withType(TestPlugin).size() == 1
+                }
+            }
+        """
+
+        and:
+        buildFile << '''
+            apply plugin: 'groovy'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile gradleApi()
+                compile localGroovy()
+                compile "org.spockframework:spock-core:0.7-groovy-1.8", {
+                    exclude module: "groovy-all"
+                }
+            }
+        '''
+
+        expect:
+        succeeds("test")
+    }
+
+    @Issue("GRADLE-3068")
+    def "can use gradleApi in test"() {
+        given:
+        file("src/test/groovy/org/acme/BreakingTest.groovy") << """
+            package com.acme
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.*
+
+class BreakingTest extends Specification {
+  Project project
+
+  def setup() {
+    project = ProjectBuilder.builder().build()
+  }
+
+  def "can evaluate ProjectBuilder"() {
+    expect:
+    project.apply(plugin: 'groovy')
+    project.evaluate()
+  }
+}
+        """
+
+        and:
+        buildFile << '''
+            apply plugin: 'groovy'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile gradleApi()
+                compile localGroovy()
+                testCompile "org.spockframework:spock-core:0.7-groovy-1.8", {
+                    exclude module: "groovy-all"
+                }
+            }
+        '''
+
+        expect:
+        succeeds("test")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/BuildEventsErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/BuildEventsErrorIntegrationTest.groovy
new file mode 100755
index 0000000..2fa881b
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/BuildEventsErrorIntegrationTest.groovy
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Ignore
+
+public class BuildEventsErrorIntegrationTest extends AbstractIntegrationSpec {
+
+    def "produces reasonable error message when taskGraph.whenReady action fails"() {
+        buildFile << """
+    gradle.taskGraph.whenReady {
+        throw new RuntimeException('broken closure')
+    }
+    task a
+"""
+
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("broken closure")
+                .assertHasNoCause()
+                .assertHasFileName("Build file '$buildFile'")
+                .assertHasLineNumber(3);
+    }
+
+    def "produces reasonable error message when task dependency closure throws exception"() {
+        buildFile << """
+    task a
+    a.dependsOn {
+        throw new RuntimeException('broken')
+    }
+"""
+        when:
+        fails "a"
+
+        then:
+        failure.assertHasDescription("Could not determine the dependencies of task ':a'.")
+                .assertHasCause('broken')
+                .assertHasFileName("Build file '$buildFile'")
+                .assertHasLineNumber(4)
+    }
+
+    def "produces reasonable error when Gradle.allprojects action fails"() {
+        def initScript = file("init.gradle") << """
+allprojects {
+    throw new RuntimeException("broken closure")
+}
+"""
+        when:
+        executer.usingInitScript(initScript)
+        fails "a"
+
+        then:
+        failure.assertHasDescription("broken closure")
+                .assertHasNoCause()
+                .assertHasFileName("Initialization script '$initScript'")
+                .assertHasLineNumber(3);
+    }
+
+    @Ignore
+    def "produces reasonable error when Gradle.buildFinished action fails"() {
+        def initScript = file("init.gradle") << """
+rootProject { task a }
+buildFinished {
+    throw new RuntimeException("broken closure")
+}
+"""
+        when:
+        executer.usingInitScript(initScript)
+        fails "a"
+
+        then:
+        failure.assertHasDescription("broken closure")
+                .assertHasNoCause()
+                .assertHasFileName("Initialization script '$initScript'")
+                .assertHasLineNumber(3);
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/BuildScriptErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/BuildScriptErrorIntegrationTest.groovy
new file mode 100755
index 0000000..f4df3c8
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/BuildScriptErrorIntegrationTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+import static org.hamcrest.Matchers.containsString
+
+public class BuildScriptErrorIntegrationTest extends AbstractIntegrationSpec {
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'ProjectError'"
+    }
+
+    def "produces reasonable error message when buildFile evaluation fails with Groovy Exception"() {
+        buildFile << """
+    createTakk('do-stuff')
+"""
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("A problem occurred evaluating root project 'ProjectError'.")
+                .assertHasCause("Could not find method createTakk() for arguments [do-stuff] on root project 'ProjectError")
+                .assertHasFileName("Build file '$buildFile'")
+                .assertHasLineNumber(2)
+    }
+
+    def "produces reasonable error message when buildFile evaluation fails on script compilation"() {
+        buildFile << """
+    // a comment
+    import org.gradle.unknown.Unknown
+    new Unknown()
+"""
+
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("Could not compile build file '${buildFile}'.")
+                .assertThatCause(containsString("build file '$buildFile': 3: unable to resolve class org.gradle.unknown.Unknown"))
+                .assertHasFileName("Build file '$buildFile'")
+                .assertHasLineNumber(3)
+    }
+
+    def "produces reasonable error message when buildFile evaluation fails with exception"() {
+        when:
+        buildFile << """
+    throw new RuntimeException("script failure")
+    task test
+"""
+        then:
+        fails('test')
+        failure.assertHasDescription("A problem occurred evaluating root project 'ProjectError'.")
+                .assertHasCause("script failure")
+                .assertHasFileName("Build file '${buildFile.path}'")
+                .assertHasLineNumber(2)
+    }
+
+    def "produces reasonable error message when nested buildFile evaluation fails"() {
+        settingsFile << """
+include 'child'
+"""
+        buildFile << """
+    evaluationDependsOn 'child'
+    task t
+"""
+        final childBuildFile = file("child/build.gradle")
+        childBuildFile << """
+    def broken = { ->
+        throw new RuntimeException('failure')
+    }
+    broken()
+"""
+
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("A problem occurred evaluating project ':child'.")
+                .assertHasCause("failure")
+                .assertHasFileName("Build file '$childBuildFile'")
+                .assertHasLineNumber(3)
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
index 6d50251..41e4b21 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
@@ -17,12 +17,11 @@
 package org.gradle.api
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.fixtures.executer.ProjectLifecycleFixture
 import org.junit.Rule
+import spock.lang.IgnoreIf
 
-/**
- * by Szczepan Faber, created at: 11/21/12
- */
 class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule ProjectLifecycleFixture fixture = new ProjectLifecycleFixture(executer, temporaryFolder)
@@ -31,16 +30,48 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         file("gradle.properties") << "org.gradle.configureondemand=true"
     }
 
-    def "works with single-module project"() {
+    @IgnoreIf({ GradleContextualExecuter.isParallel() }) //parallel mode hides incubating message
+    def "presents incubating message"() {
+        file("gradle.properties") << "org.gradle.configureondemand=false"
         buildFile << "task foo"
+
         when:
-        run("foo")
+        run("foo", "--configure-on-demand")
+
+        then:
+        fixture.assertProjectsConfigured(":")
+        output.count("Configuration on demand is an incubating feature") == 1
+    }
+
+    @IgnoreIf({ GradleContextualExecuter.isParallel() }) //parallel mode hides incubating message
+    def "presents incubating message with parallel mode"() {
+        file("gradle.properties") << "org.gradle.configureondemand=false"
+        buildFile << "task foo"
+
+        when:
+        run("foo", "--configure-on-demand", "--parallel")
+
+        then:
+        fixture.assertProjectsConfigured(":")
+        output.count("Parallel execution with configuration on demand is an incubating feature") == 1
+    }
+
+    def "can be enabled from command line for a single module build"() {
+        file("gradle.properties") << "org.gradle.configureondemand=false"
+        buildFile << "task foo"
+
+        when:
+        run("foo", "--configure-on-demand")
+
         then:
         fixture.assertProjectsConfigured(":")
-        assert output.contains("Configuration on demand is an incubating feature")
     }
 
     def "evaluates only project referenced in the task list"() {
+        // The util project's classloaders will be created eagerly because util:impl
+        // will be evaluated before it
+        executer.withEagerClassLoaderCreationCheckDisabled()
+
         settingsFile << "include 'api', 'impl', 'util', 'util:impl'"
         buildFile << "allprojects { task foo }"
 
@@ -49,15 +80,14 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         fixture.assertProjectsConfigured(":", ":util:impl")
-        assert output.contains("Configuration on demand is an incubating feature")
     }
 
-    def "does not show configuration on demand message in a regular mode"() {
+    def "does not show configuration on demand incubating message in a regular mode"() {
         file("gradle.properties").text = "org.gradle.configureondemand=false"
         when:
         run()
         then:
-        assert !output.contains("Configuration on demand is an incubating feature")
+        !output.contains("Configuration on demand is incubating")
     }
 
     def "follows java project dependencies"() {
@@ -106,6 +136,26 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         fixture.assertProjectsConfigured(":", ":util", ":impl", ":api")
     }
 
+    def "can have cycles in project dependencies"() {
+        settingsFile << "include 'api', 'impl', 'util'"
+        buildFile << """
+allprojects { apply plugin: 'java' }
+project(':impl') {
+    dependencies { compile project(path: ':api', configuration: 'archives') }
+}
+project(':api') {
+    dependencies { runtime project(':impl') }
+    task run(dependsOn: configurations.runtime)
+}
+"""
+
+        when:
+        run(":api:run")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":api", ':impl')
+    }
+
     def "follows project dependencies when ran in subproject"() {
         settingsFile << "include 'api', 'impl', 'util'"
 
@@ -220,12 +270,9 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run("impl:build", "--no-rebuild") // impl -> api
 
         then:
-        //api tasks are not executed
+        //api tasks are not executed and api is not configured
         !result.executedTasks.find { it.startsWith ":api" }
-        //but the api project is still configured
-        //ideally, the ':api' project is not configured in the configure on demand mode
-        //but this is complicated to implement so lets leave it for now
-        fixture.assertProjectsConfigured(":", ":impl", ":api")
+        fixture.assertProjectsConfigured(":", ":impl")
     }
 
     def "respects external task dependencies"() {
@@ -258,8 +305,30 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         buildFile << "task foo(type: FooTask)"
 
         when:
-        run("foo")
+        run("foo", "-s")
         then:
         output.contains "Horray!!!"
     }
+
+    def "may configure project at execution time"() {
+        settingsFile << "include 'a', 'b', 'c'"
+        file('a/build.gradle') << """
+            configurations { conf }
+            dependencies { conf project(path: ":b", configuration: "conf") }
+            task resolveConf << {
+              //resolves at execution time, forcing 'b' to get configured
+              configurations.conf.files
+            }
+        """
+
+        file('b/build.gradle') << """
+            configurations { conf }
+        """
+
+        when:
+        run(":a:resolveConf", "-i")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":a", ":b")
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/CrossProcessFileLockIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/CrossProcessFileLockIntegrationTest.groovy
new file mode 100644
index 0000000..769cada
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/CrossProcessFileLockIntegrationTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api;
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
+
+public class CrossProcessFileLockIntegrationTest extends AbstractIntegrationSpec {
+
+    def "the task history lock can be acquired when the initial owner is busy executing tasks"() {
+        settingsFile << "include 'a', 'b'"
+
+        file("a/src/main/java/A.java") << "public class A {}"
+        file("b/src/main/java/B.java") << "public class B {}"
+
+        when:
+        buildFile << """
+            def waitForStop() {
+              def sanityWaitUntil = System.currentTimeMillis() + 120000
+              println 'waiting for file...'
+              while(!file('stop.txt').exists()) {
+                Thread.sleep(300)
+                assert System.currentTimeMillis() < sanityWaitUntil : "Timeout waiting for file"
+              }
+              println 'no more waiting!'
+            }
+            def stopNow() {
+              assert file('stop.txt').createNewFile()
+            }
+            subprojects {
+                apply plugin: 'java'
+            }
+            project(":a") {
+                compileJava.doFirst { waitForStop() }
+            }
+            project(":b") {
+                compileJava.doFirst { stopNow() }
+            }
+        """
+
+        then:
+        def handle1 = executer.withArguments(':a:build', '-i').start()
+        poll(120) {
+            assert handle1.standardOutput.contains('waiting for file...')
+        }
+        //first build is waiting for file, so the lock should be releasable now (for example: the task history lock)
+
+        and:
+        def handle2 = executer.withArguments('b:build', '-is').start()
+        handle2.waitForFinish()
+        handle1.waitForFinish()
+
+        and:
+        file("a/build/libs/a.jar").assertExists()
+        file("b/build/libs/b.jar").assertExists()
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/DeferredConfigurableExtensionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/DeferredConfigurableExtensionIntegrationTest.groovy
new file mode 100755
index 0000000..f70c11a
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/DeferredConfigurableExtensionIntegrationTest.groovy
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+public class DeferredConfigurableExtensionIntegrationTest extends AbstractIntegrationSpec {
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'customProject'"
+
+        buildFile << """
+public class CustomPlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        project.getExtensions().create("custom", CustomExtension.class)
+    }
+}
+
+public class BrokenCustomPlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        project.getPlugins().apply(CustomPlugin)
+        project.getExtensions().configure(CustomExtension, {
+            throw new RuntimeException("broken configuration in plugin")
+        } as Action)
+    }
+}
+
+ at org.gradle.api.plugins.DeferredConfigurable
+public class CustomExtension {
+    private final StringBuilder builder = new StringBuilder()
+    public void append(String value) {
+        builder.append(value)
+    }
+
+    public String getString() {
+        return builder.toString()
+    }
+}
+"""
+    }
+
+    private static def buildFileLine(int number) {
+        return number + 28 // Number of lines added to build script in setup
+    }
+
+    def "configure actions on deferred configurable extension are deferred until access"() {
+        when:
+        buildFile << '''
+apply plugin: CustomPlugin
+
+version = "1"
+custom {
+    append project.version
+}
+
+version = "2"
+custom {
+    append project.version
+}
+
+assert custom.string == "22"
+task test
+'''
+        then:
+        succeeds('test')
+    }
+
+    def "configure actions on deferred configurable extension are not applied if extension is not referenced"() {
+        when:
+        buildFile << '''
+apply plugin: CustomPlugin
+
+custom {
+    throw new RuntimeException()
+}
+
+task test
+'''
+        then:
+        succeeds('test')
+    }
+
+    def "reports on failure in deferred configurable that is referenced in the build"() {
+        when:
+        buildFile << '''
+apply plugin: CustomPlugin
+custom {
+    throw new RuntimeException("deferred configuration failure")
+}
+assert custom.string == "22"
+task test
+'''
+        then:
+        fails 'test'
+        failure.assertHasDescription("A problem occurred evaluating root project 'customProject'.")
+                .assertHasCause("deferred configuration failure")
+                .assertHasFileName("Build file '${buildFile.path}'")
+                .assertHasLineNumber(buildFileLine(3))
+    }
+
+    def "reports on failure in deferred configurable that is configured in plugin"() {
+        when:
+        buildFile << '''
+apply plugin: BrokenCustomPlugin
+print custom.string
+task test
+'''
+        then:
+        fails 'test'
+        failure.assertHasDescription("A problem occurred evaluating root project 'customProject'.")
+                .assertHasCause("broken configuration in plugin")
+                .assertHasFileName("Build file '${buildFile.path}'")
+                .assertHasLineNumber(12)
+    }
+
+    def "does not report on deferred configuration failure in case of another configuration failure"() {
+        when:
+        buildFile << '''
+apply plugin: BrokenCustomPlugin
+custom {
+    throw new RuntimeException("deferred configuration failure")
+}
+task test {
+    throw new RuntimeException("task configuration failure")
+}
+afterEvaluate {
+    project.custom
+}
+'''
+        then:
+        fails 'test'
+        failure.assertHasDescription("A problem occurred evaluating root project 'customProject'.")
+                .assertHasCause("task configuration failure")
+                .assertHasFileName("Build file '${buildFile.path}'")
+                .assertHasLineNumber(buildFileLine(6))
+    }
+
+    def "cannot configure deferred configurable extension after access"() {
+        when:
+        buildFile << '''
+apply plugin: CustomPlugin
+
+version = "1"
+custom {
+    append project.version
+}
+
+assert custom.string == "1"
+
+custom {
+    append project.version
+}
+task test
+'''
+        then:
+        fails('test')
+        failure.assertHasDescription("A problem occurred evaluating root project 'customProject'.")
+                .assertHasCause("Cannot configure the 'custom' extension after it has been accessed.")
+                .assertHasFileName("Build file '${buildFile.path}'")
+                .assertHasLineNumber(buildFileLine(10))
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptErrorIntegrationTest.groovy
new file mode 100755
index 0000000..a3e0a21
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptErrorIntegrationTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+import static org.hamcrest.Matchers.containsString
+
+class ExternalScriptErrorIntegrationTest extends AbstractIntegrationSpec {
+    def externalScript
+
+    def "setup"() {
+        externalScript = file('other.gradle')
+        settingsFile << "rootProject.name = 'project'"
+        buildFile << """
+    apply { from 'other.gradle' }
+"""
+    }
+
+    def "produces reasonable error message when external script fails with Groovy exception"() {
+        externalScript << '''
+
+doStuff()
+'''
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription('A problem occurred evaluating script.')
+                .assertHasCause('Could not find method doStuff() for arguments [] on root project')
+                .assertHasFileName("Script '${externalScript}'")
+                .assertHasLineNumber(3)
+    }
+
+    def "produces reasonable error message when external script fails on compilation"() {
+        externalScript << 'import org.gradle()'
+
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("Could not compile script '$externalScript'.")
+                .assertThatCause(containsString("script '${externalScript}': 1: unexpected token: ("))
+                .assertHasFileName("Script '$externalScript'")
+                .assertHasLineNumber(1)
+    }
+
+    def "reports missing script"() {
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("A problem occurred evaluating root project 'project'.")
+                .assertHasCause("Could not read script '${externalScript}' as it does not exist.")
+                .assertHasFileName("Build file '${buildFile}'")
+                .assertHasLineNumber(2)
+    }
+
+    def "produces reasonable error message when task execution fails"() {
+        externalScript << '''
+task doStuff << {
+    throw new RuntimeException('fail')
+}
+'''
+        when:
+        fails 'doStuff'
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':doStuff\'.')
+                .assertHasCause('fail')
+                .assertHasFileName("Script '${externalScript}'")
+                .assertHasLineNumber(3)
+    }
+}
+
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/FinalizerTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/FinalizerTaskIntegrationTest.groovy
new file mode 100644
index 0000000..f9806f4
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/FinalizerTaskIntegrationTest.groovy
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TextUtil
+import spock.lang.Ignore
+import spock.lang.Unroll
+
+class FinalizerTaskIntegrationTest extends AbstractIntegrationSpec {
+    @Unroll
+    void 'finalizer tasks are scheduled as expected'() {
+        setupProject()
+
+        when:
+        succeeds(*requestedTasks)
+
+        then:
+        executedTasks == expectedExecutedTasks
+
+        where:
+        requestedTasks | expectedExecutedTasks
+        ['a']          | [':c', ':a', ':d', ':b']
+        ['a', 'b']     | [':c', ':a', ':d', ':b']
+        ['d', 'a']     | [':d', ':c', ':a', ':b']
+    }
+
+    @Unroll
+    void 'finalizer tasks work with task excluding'() {
+        setupProject()
+        executer.withArguments('-x', excludedTask)
+
+        tasksNotInGraph.each { task ->
+            buildFile << """
+                gradle.taskGraph.whenReady { graph ->
+                    assert !graph.hasTask('$task')
+                }
+            """
+        }
+
+        when:
+        succeeds 'a'
+
+        then:
+        executedTasks == expectedExecutedTasks
+
+        where:
+        excludedTask | expectedExecutedTasks
+        'b'          | [':c', ':a']
+        'd'          | [':c', ':a', ':b']
+        'a'          | []
+
+
+        tasksNotInGraph = [':a', ':b', ':c', ':d'] - expectedExecutedTasks
+    }
+
+    @Unroll
+    void 'finalizer tasks work with --continue'() {
+        setupProject()
+        executer.withArguments('--continue')
+
+        buildFile << """
+            ${failingTask}.doLast { throw new RuntimeException() }
+        """
+
+        when:
+        fails(*requestedTasks)
+
+        then:
+        executedTasks == expectedExecutedTasks
+
+        where:
+        requestedTasks | failingTask | expectedExecutedTasks
+        ['a']          | 'c'         | [':c']
+        ['a', 'b']     | 'a'         | [':c', ':a', ':d', ':b']
+        ['a', 'b']     | 'c'         | [':c', ':d', ':b']
+    }
+
+    @Ignore
+    @Unroll
+    void 'finalizer tasks work with task disabling'() {
+        setupProject()
+        buildFile << """
+            $taskDisablingStatement
+
+            gradle.taskGraph.whenReady { graph ->
+                assert [a, b, c, d].every { graph.hasTask(it) }
+            }
+        """
+
+        when:
+        succeeds 'a'
+
+        then:
+        executedTasks == [':c']
+
+        where:
+        taskDisablingStatement << ['a.enabled = false', 'a.onlyIf {false}']
+    }
+
+    @Ignore
+    void 'requesting to run finalizer task before finalized results in a circular dependency failure'() {
+        setupProject()
+
+        expect:
+        fails 'b', 'a'
+    }
+
+    void 'finalizer tasks are executed as expected in parallel builds'() {
+        setupMultipleProjects()
+        executer.withArguments('--parallel')
+
+        when:
+        succeeds 'a'
+
+        then:
+        executedTasks == [':a:c', ':a:a', ':b:d', ':b:b']
+    }
+
+    void 'finalizers for finalizers are executed when finalized is executed'() {
+        buildFile << """
+            task a {
+                finalizedBy 'b'
+            }
+            task b {
+                finalizedBy 'c'
+            }
+            task c
+        """
+
+        when:
+        succeeds 'a'
+
+        then:
+        executedTasks == [':a', ':b', ':c']
+    }
+
+    void 'finalizer tasks are executed after their dependencies'() {
+        buildFile << """
+            task a {
+                dependsOn 'b', 'c'
+            }
+            task b
+            task c {
+                finalizedBy 'b'
+            }
+        """
+
+        when:
+        succeeds 'a'
+
+        then:
+        executedTasks == [':c', ':b', ':a']
+    }
+
+    void 'circular dependency errors are detected for finalizer tasks'() {
+        buildFile << """
+            task a {
+                finalizedBy 'b'
+                dependsOn 'c'
+            }
+            task b
+            task c {
+                mustRunAfter 'b'
+            }
+        """
+
+        when:
+        fails 'a'
+
+        then:
+        failure.assertHasDescription TextUtil.toPlatformLineSeparators("""Circular dependency between the following tasks:
+:a
+\\--- :c
+     \\--- :b
+          \\--- :a (*)
+
+(*) - details omitted (listed previously)""")
+    }
+
+    void 'finalizer task can be used by multiple tasks that depend on one another'(){
+        buildFile << """
+            task a {
+                finalizedBy 'c'
+            }
+            task b {
+                dependsOn 'a'
+                finalizedBy 'c'
+            }
+            task c
+        """
+
+        when:
+        succeeds 'b'
+
+        then:
+        executedTasks == [':a', ':b', ':c']
+    }
+
+    private void setupProject() {
+        buildFile << """
+            task a {
+                finalizedBy 'b'
+                dependsOn 'c'
+            }
+            task b {
+                dependsOn 'd'
+            }
+            task c
+            task d
+        """
+    }
+
+    private void setupMultipleProjects() {
+        settingsFile << """
+            include 'a', 'b'
+        """
+
+        file('a/build.gradle') << """
+            task a {
+                finalizedBy ':b:b'
+                dependsOn 'c'
+            }
+            task c
+        """
+
+        file('b/build.gradle') << """
+            task b {
+                dependsOn 'd'
+            }
+            task d
+        """
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/GradlePluginIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/GradlePluginIntegrationTest.groovy
new file mode 100644
index 0000000..01c22e8
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/GradlePluginIntegrationTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class GradlePluginIntegrationTest extends AbstractIntegrationSpec {
+    File initFile;
+
+    def setup() {
+        initFile = temporaryFolder.createFile("initscripts/init.gradle")
+        executer.usingInitScript(initFile);
+    }
+
+    def "can apply binary plugin from init script"() {
+        when:
+        initFile << """
+        apply plugin:SimpleGradlePlugin
+
+        class SimpleGradlePlugin implements Plugin<Gradle> {
+            void apply(Gradle aGradle) {
+                aGradle.buildFinished {
+                    println "Gradle Plugin received build finished!"
+                }
+            }
+        }
+        """
+        then:
+        def executed = succeeds('tasks')
+        executed.output.contains("Gradle Plugin received build finished!")
+    }
+
+    def "can apply script with relative path"() {
+        setup:
+        def externalInitFile = temporaryFolder.createFile("initscripts/somePath/anInit.gradle")
+        externalInitFile << """
+        buildFinished {
+            println "Gradle Plugin received build finished!"
+        }
+        """
+        when:
+        initFile << """
+        apply from: "somePath/anInit.gradle"
+        """
+        then:
+        def executed = succeeds('tasks')
+        executed.output.contains("Gradle Plugin received build finished!")
+    }
+
+    def "cannot apply script with relative path on Gradle instance"() {
+        when:
+        initFile << """
+            gradle.apply(from: "somePath/anInit.gradle")
+            """
+        then:
+        fails('tasks')
+        failure.assertHasCause("Cannot convert relative path somePath${File.separator}anInit.gradle to an absolute file")
+    }
+
+    def "path to script is interpreted relative to the applying script"() {
+        setup:
+        def externalInitFile = temporaryFolder.createFile("initscripts/path1/anInit.gradle")
+        externalInitFile << """
+            buildFinished {
+                println "Gradle Plugin received build finished!"
+            }
+        """
+        def anotherExternalInitFile = temporaryFolder.createFile("initscripts/path2/anotherInit.gradle")
+        anotherExternalInitFile << """
+            apply from: '../path1/anInit.gradle'
+            """
+
+        when:
+        initFile << """
+            apply from: "path2/anotherInit.gradle"
+            """
+        then:
+        def executed = succeeds('tasks')
+        executed.output.contains("Gradle Plugin received build finished!")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptErrorIntegrationTest.groovy
new file mode 100644
index 0000000..3d1f7d6
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptErrorIntegrationTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+import static org.hamcrest.Matchers.containsString
+
+public class InitScriptErrorIntegrationTest extends AbstractIntegrationSpec {
+    def initScript
+
+    def "setup"() {
+        initScript = file('init.gradle')
+        executer.usingInitScript(initScript)
+    }
+
+    def "produces reasonable error message when init script evaluation fails with GroovyException"() {
+        initScript << """
+    createTakk('do-stuff')
+"""
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("A problem occurred evaluating initialization script.")
+                .assertHasCause("Could not find method createTakk() for arguments [do-stuff] on build.")
+                .assertHasFileName("Initialization script '$initScript'")
+                .assertHasLineNumber(2)
+    }
+
+    def "produces reasonable error message when init script compilation fails"() {
+        initScript << """
+    // a comment
+    import org.gradle.unknown.Unknown
+    new Unknown()
+"""
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("Could not compile initialization script '$initScript'.")
+                .assertThatCause(containsString("initialization script '$initScript': 3: unable to resolve class org.gradle.unknown.Unknown"))
+                .assertHasFileName("Initialization script '$initScript'")
+                .assertHasLineNumber(3)
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ProfilingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ProfilingIntegrationTest.groovy
new file mode 100644
index 0000000..0f301ca
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ProfilingIntegrationTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+
+class ProfilingIntegrationTest extends AbstractIntegrationSpec {
+
+    def "can generate profiling report"() {
+        file('settings.gradle') << 'include "a", "b", "c"'
+        buildFile << '''
+allprojects {
+    apply plugin: 'java'
+    task fooTask
+    task barTask
+}
+'''
+        when:
+        executer.withArguments("--profile").withTasks("build", "fooTask", "-x", "barTask").run()
+
+        then:
+        def reportFile = file('build/reports/profile').listFiles().find { it.name ==~ /profile-.+.html/ }
+        Document document = Jsoup.parse(reportFile, null);
+        !document.select("TD:contains(:jar)").isEmpty()
+        !document.select("TD:contains(:a:jar)").isEmpty()
+        !document.select("TD:contains(:b:jar)").isEmpty()
+        !document.select("TD:contains(:c:jar)").isEmpty()
+        document.text().contains("build fooTask")
+        document.text().contains("-x barTask")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy
index 88b916a..adc2e86 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.api
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
-/**
- * by Szczepan Faber, created at: 11/21/12
- */
 class ProjectConfigurationIntegrationTest extends AbstractIntegrationSpec {
 
     def "accessing the task by path from containing project is safe"() {
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigureEventsErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigureEventsErrorIntegrationTest.groovy
new file mode 100755
index 0000000..ca04c9b
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigureEventsErrorIntegrationTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+public class ProjectConfigureEventsErrorIntegrationTest extends AbstractIntegrationSpec {
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'projectConfigure'"
+    }
+
+    def "produces reasonable error message when beforeProject action fails"() {
+        when:
+        settingsFile << """
+    gradle.beforeProject {
+        throw new RuntimeException("beforeProject failure")
+    }
+"""
+        buildFile << """
+    task test
+"""
+        then:
+        fails('test')
+        failure.assertHasDescription("A problem occurred configuring root project 'projectConfigure'.")
+                .assertHasCause("beforeProject failure")
+                .assertHasFileName("Settings file '${settingsFile.path}'")
+                .assertHasLineNumber(3)
+    }
+
+    def "produces reasonable error message when afterProject action fails"() {
+        when:
+        settingsFile << """
+    gradle.afterProject {
+        throw new RuntimeException("afterProject failure")
+    }
+"""
+        buildFile << """
+    task test
+"""
+        then:
+        fails('test')
+        failure.assertHasDescription("A problem occurred configuring root project 'projectConfigure'.")
+                .assertHasCause("afterProject failure")
+                .assertHasFileName("Settings file '${settingsFile.path}'")
+                .assertHasLineNumber(3)
+    }
+
+    def "produces reasonable error message when afterEvaluate action fails"() {
+        when:
+        buildFile << """
+    project.afterEvaluate {
+        throw new RuntimeException("afterEvaluate failure")
+    }
+    task test
+"""
+        then:
+        fails('test')
+        failure.assertHasDescription("A problem occurred configuring root project 'projectConfigure'.")
+                .assertHasCause("afterEvaluate failure")
+                .assertHasFileName("Build file '${buildFile.path}'")
+                .assertHasLineNumber(3)
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsPluginIntegrationSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsPluginIntegrationSpec.groovy
new file mode 100644
index 0000000..bf78805
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsPluginIntegrationSpec.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
+
+class SettingsPluginIntegrationSpec extends AbstractIntegrationSpec {
+
+    def setup(){
+        executer.usingSettingsFile(settingsFile)
+        settingsFile << "rootProject.projectDir = file('..')\n"
+    }
+
+    def "can apply plugin class from settings.gradle"() {
+        when:
+        settingsFile << """
+        apply plugin: SimpleSettingsPlugin
+
+        class SimpleSettingsPlugin implements Plugin<Settings> {
+            void apply(Settings mySettings) {
+                mySettings.include("moduleA");
+            }
+        }
+        """
+
+        then:
+        succeeds(':moduleA:dependencies')
+    }
+
+    def "can apply plugin class from buildSrc"() {
+        setup:
+        file("settings/buildSrc/src/main/java/test/SimpleSettingsPlugin.java").createFile().text = """
+            package test;
+
+            import org.gradle.api.Plugin;
+            import org.gradle.api.initialization.Settings;
+
+            public class SimpleSettingsPlugin implements Plugin<Settings> {
+                public void apply(Settings settings) {
+                    settings.include(new String[]{"moduleA"});
+                }
+            }
+
+            """
+        file("settings/buildSrc/src/main/resources/META-INF/gradle-plugins/simple-plugin.properties").createFile().text = """
+        implementation-class=test.SimpleSettingsPlugin
+        """
+
+        when:
+        settingsFile << "apply plugin: 'simple-plugin'"
+
+        then:
+        succeeds(':moduleA:dependencies')
+    }
+
+    def "can apply script with relative path"() {
+        setup:
+        testDirectory.createFile("settings/somePath/settingsPlugin.gradle") << "apply from: 'path2/settings.gradle'";
+        testDirectory.createFile("settings/somePath/path2/settings.gradle") << "include 'moduleA'";
+
+        when:
+        settingsFile << "apply from: 'somePath/settingsPlugin.gradle'"
+
+        then:
+        succeeds(':moduleA:dependencies')
+    }
+
+    protected TestFile getSettingsFile() {
+        testDirectory.file('settings/settings.gradle')
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptErrorIntegrationTest.groovy
new file mode 100755
index 0000000..e28304f
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptErrorIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+public class SettingsScriptErrorIntegrationTest extends AbstractIntegrationSpec {
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'ProjectError'"
+    }
+
+    def "produces reasonable error message when settings file evaluation fails"() {
+        settingsFile << """
+
+throw new RuntimeException('<failure message>')
+"""
+        when:
+        fails()
+
+        then:
+        failure.assertHasDescription("A problem occurred evaluating settings 'ProjectError'.")
+                .assertHasCause("<failure message>")
+                .assertHasFileName("Settings file '$settingsFile'")
+                .assertHasLineNumber(3)
+
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/ConcurrentClassDecorationSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/ConcurrentClassDecorationSpec.groovy
new file mode 100644
index 0000000..836b0b8
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/ConcurrentClassDecorationSpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.dsl
+
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Issue
+
+class ConcurrentClassDecorationSpec extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2836")
+    def "can decorate classes concurrently"() {
+        given:
+        file("buildSrc/src/main/java/Thing.java") << "public class Thing {}"
+        ("a".."d").each { name ->
+            settingsFile << "include '$name'\n"
+            file("$name/build.gradle") << """
+                task decorateClass << {
+                    def instantiator = project.services.get(${Instantiator.name})
+                    def thing = instantiator.newInstance(Thing)
+                    assert thing instanceof ${ExtensionAware.name}
+                }
+            """
+        }
+
+        when:
+        args "--parallel"
+
+        then:
+        succeeds "decorateClass"
+    }
+
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/events/BuildExecutionEventsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/events/BuildExecutionEventsIntegrationTest.groovy
new file mode 100644
index 0000000..a5ef5e4
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/events/BuildExecutionEventsIntegrationTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.events
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class BuildExecutionEventsIntegrationTest extends AbstractIntegrationSpec {
+
+    def "events passed to any task execution listener are synchronised"() {
+        settingsFile << "include 'a', 'b', 'c'"
+        buildFile << """
+            def listener = new MyListener()
+            gradle.addListener(listener)
+
+            allprojects {
+                task foo << {}
+            }
+
+            class MyListener implements TaskExecutionListener {
+                def called = []
+                void beforeExecute(Task task) {
+                    check(task)
+                }
+                void afterExecute(Task task, TaskState state) {
+                    check(task)
+                }
+                void check(task) {
+                    called << task
+                    Thread.sleep(100)
+                    //the last task added to the list should be exactly what we have added before sleep
+                    //this way we assert that events passed to the listener are synchronised and any listener implementation is thread safe
+                    assert called[-1] == task
+                }
+            }
+        """
+
+        when:
+        run("foo")
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
index 6aa4069..9637d29 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
@@ -13,16 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
-
 package org.gradle.api.tasks
 
 import org.apache.commons.lang.RandomStringUtils
-import org.apache.commons.lang.StringUtils
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.archive.TarTestFixture
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 
@@ -71,8 +67,6 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         file('build/test.tar').assertDoesNotExist()
     }
 
-
-
     def canCopyFromATar() {
         given:
         createTar('test.tar') {
@@ -222,7 +216,8 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         file('dest').assertHasDescendants('someDir/1.txt')
     }
 
-    @Rule public final TestResources resources = new TestResources(temporaryFolder)
+    @Rule
+    public final TestResources resources = new TestResources(temporaryFolder)
 
     def "tarTreeFailsGracefully"() {
         given:
@@ -344,11 +339,11 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         run 'uncompressedZip'
         run 'compressedZip'
         then:
-	def uncompressedSize = file('build/uncompressedTest.zip').length()
-	def compressedSize = file('build/compressedTest.zip').length()
-	println "uncompressed" + uncompressedSize
-	println "compressed" + compressedSize
-    assert compressedSize < uncompressedSize
+        def uncompressedSize = file('build/uncompressedTest.zip').length()
+        def compressedSize = file('build/compressedTest.zip').length()
+        println "uncompressed" + uncompressedSize
+        println "compressed" + compressedSize
+        assert compressedSize < uncompressedSize
 
         def expandDir = file('expandedUncompressed')
         file('build/uncompressedTest.zip').unzipTo(expandDir)
@@ -598,10 +593,67 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         expandDir.assertHasDescendants('shared/zip.txt', 'zipdir1/file1.txt', 'shared/tar.txt', 'tardir1/file1.txt', 'shared/dir.txt', 'dir1/file1.txt')
     }
 
-    def createTar(String name, Closure cl) {
+
+    def ensureDuplicatesIncludedInTarByDefault() {
+        given:
+        createFilesStructureForDupeTests();
+        buildFile << '''
+            task tar(type: Tar) {
+                from 'dir1'
+                from 'dir2'
+                from 'dir3'
+                destinationDir = buildDir
+                archiveName = 'test.tar'
+            }
+            '''
+        when:
+        run 'tar'
+
+        then:
+        def tar = new TarTestFixture(file("build/test.tar"))
+        tar.assertContainsFile('file1.txt', 2)
+        tar.assertContainsFile('file2.txt')
+    }
+
+    def ensureDuplicatesCanBeExcludedFromTar() {
+        given:
+        createFilesStructureForDupeTests()
+        buildFile << '''
+            task tar(type: Tar) {
+                from 'dir1'
+                from 'dir2'
+                from 'dir3'
+                destinationDir = buildDir
+                archiveName = 'test.tar'
+                eachFile { it.duplicatesStrategy = 'exclude' }
+            }
+            '''
+        when:
+        run 'tar'
+
+        then:
+        def tar = new TarTestFixture(file("build/test.tar"))
+        tar.assertContainsFile('file1.txt')
+        tar.assertContainsFile('file2.txt')
+        tar.content("file1.txt") == "dir1/file1.txt"
+    }
+
+    private def createTar(String name, Closure cl) {
         TestFile tarRoot = file("${name}.root")
         TestFile tar = file(name)
         tarRoot.create(cl)
         tarRoot.tarTo(tar)
     }
+
+    private def createFilesStructureForDupeTests() {
+        createDir('dir1', {
+            file('file1.txt').text = "dir1/file1.txt"
+        })
+        createDir('dir2', {
+            file 'file2.txt'
+        })
+        createDir('dir3', {
+            file('file1.txt').text = "dir3/file1.txt"
+        })
+    }
 }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
index 391a6d6..6885f50 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
@@ -17,7 +17,6 @@ package org.gradle.api.tasks
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.executer.ExecutionFailure
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.PreconditionVerifier
 import org.gradle.util.Requires
@@ -33,7 +32,7 @@ class CopyErrorIntegrationTest extends AbstractIntegrationTest {
     @Requires(TestPrecondition.SYMLINKS)
     public void reportsSymLinkWhichPointsToNothing() {
         TestFile link = testFile('src/file')
-        FileSystems.default.createSymbolicLink(link, testFile('missing'))
+        link.createLink(testFile('missing'))
 
         Assert.assertFalse(link.isDirectory())
         Assert.assertFalse(link.isFile())
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy
index afb84fb..ed8cb8b 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.api.tasks
 
+import org.gradle.api.plugins.ExtensionAware
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import spock.lang.Issue
 
@@ -67,4 +68,124 @@ class CopyTaskIntegrationSpec extends AbstractIntegrationSpec {
         then:
         file("build/resources", weirdFileName).exists()
     }
+
+    def "nested specs and details arent extensible objects"() {
+        given:
+        file("a/a.txt").touch()
+
+        buildScript """
+            task copy(type: Copy) {
+                assert delegate instanceof ${ExtensionAware.name}
+                into "out"
+                from "a", {
+                    assert !(delegate instanceof ${ExtensionAware.name})
+                    eachFile {
+                        it.name = "rename"
+                        assert !(delegate instanceof ${ExtensionAware.name})
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds "copy"
+
+        then:
+        file("out/rename").exists()
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2838")
+    def "include empty dirs works when nested"() {
+        given:
+        file("a/a.txt") << "foo"
+        file("a/dirA").createDir()
+        file("b/b.txt") << "foo"
+        file("b/dirB").createDir()
+
+        buildScript """
+            task copyTask(type: Copy) {
+                into "out"
+                from "b", {
+                    includeEmptyDirs = false
+                }
+                from "a"
+                from "c", {}
+            }
+        """
+
+        when:
+        succeeds "copyTask"
+
+        then:
+        ":copyTask" in nonSkippedTasks
+        def destinationDir = file("out")
+        destinationDir.assertHasDescendants("a.txt", "b.txt")
+        destinationDir.listFiles().findAll { it.directory }*.name.toSet() == ["dirA"].toSet()
+    }
+
+    def "include empty dirs is overridden by subsequent"() {
+        given:
+        file("a/a.txt") << "foo"
+        file("a/dirA").createDir()
+        file("b/b.txt") << "foo"
+        file("b/dirB").createDir()
+
+
+        buildScript """
+            task copyTask(type: Copy) {
+                into "out"
+                from "b", {
+                    includeEmptyDirs = false
+                }
+                from "a"
+                from "c", {}
+                from "b", {
+                    includeEmptyDirs = true
+                }
+            }
+        """
+
+        when:
+        succeeds "copyTask"
+
+        then:
+        ":copyTask" in nonSkippedTasks
+
+        def destinationDir = file("out")
+        destinationDir.assertHasDescendants("a.txt", "b.txt")
+        destinationDir.listFiles().findAll { it.directory }*.name.toSet() == ["dirA", "dirB"].toSet()
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2902")
+    def "internal copy spec methods are not visible to users"() {
+        when:
+        file("res/foo.txt") << "bar"
+
+        buildScript """
+            task copyAction {
+                ext.source = 'res'
+                doLast {
+                    copy {
+                        from source
+                        into 'action'
+                    }
+                }
+            }
+            task copyTask(type: Copy) {
+                ext.children = 'res'
+                into "task"
+                into "dir", {
+                    from children
+                }
+            }
+        """
+
+        then:
+        succeeds "copyAction", "copyTask"
+
+        and:
+        file("action/foo.txt").exists()
+        file("task/dir/foo.txt").exists()
+    }
+
 }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
index 2c420e1..9d43b3d 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
@@ -18,11 +18,13 @@ package org.gradle.api.tasks
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Matchers
 import org.junit.Rule
 import org.junit.Test
 
 import static org.hamcrest.Matchers.equalTo
 import static org.hamcrest.Matchers.startsWith
+import static org.junit.Assert.assertFalse
 import static org.junit.Assert.assertThat
 
 public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
@@ -435,4 +437,76 @@ public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
         assert !file("dest", "emptyDir").exists()
         assert !file("dest", "yet", "another", "veryEmptyDir").exists()
     }
+
+    @Test
+    public void testCopyExcludeDuplicates() {
+        file('dir1', 'path', 'file.txt').createFile() << "f1"
+        file('dir2', 'path', 'file.txt').createFile() << "f2"
+
+
+        def buildFile = testFile('build.gradle') <<
+                '''
+            task copy(type: Copy) {
+                from 'dir1'
+                from 'dir2'
+                into 'dest'
+
+                eachFile { it.duplicatesStrategy = 'exclude' }
+            }
+
+        '''
+
+        def result = usingBuildFile(buildFile).withTasks("copy").run()
+        file('dest').assertHasDescendants('path/file.txt')
+        file('dest/path/file.txt').assertContents(Matchers.containsText("f1"))
+        assertFalse(result.output.contains('deprecated'))
+    }
+
+    @Test
+    public void renamedFileCanBeTreatedAsDuplicate() {
+        File file1 = file('dir1', 'path', 'file.txt').createFile()
+        File file2 = file('dir2', 'path', 'file2.txt').createFile()
+        file1.text = "file1"
+        file2.text = "file2"
+
+        def buildFile = testFile('build.gradle') <<
+                '''
+                task copy(type: Copy) {
+                    from 'dir1'
+                    from 'dir2'
+                    rename 'file2.txt', 'file.txt'
+                    into 'dest'
+
+                    eachFile { it.duplicatesStrategy = 'exclude' }
+                }
+            '''
+        def result = usingBuildFile(buildFile).withTasks("copy").run()
+        file('dest').assertHasDescendants('path/file.txt')
+        file('dest/path/file.txt').assertContents(Matchers.containsText("file1"))
+        assertFalse(result.output.contains('deprecated'))
+    }
+
+    @Test
+    public void testChainMatchingRules() {
+        file('path/abc.txt').createFile().write('test file with $attr')
+        file('path/bcd.txt').createFile()
+
+        def buildFile = testFile('build.gradle') <<
+            '''
+            task copy(type: Copy) {
+                from 'path'
+                into 'dest'
+                filesMatching ('**/a*') {
+                    path = path + '.template'
+                }
+                filesMatching ('**/*.template') {
+                    expand(attr: 'some value')
+                    path = path.replace('template', 'concrete')
+                }
+            }'''
+
+        usingBuildFile(buildFile).withTasks('copy').run();
+        file('dest').assertHasDescendants('bcd.txt', 'abc.txt.concrete')
+        file('dest/abc.txt.concrete').text = 'test file with some value'
+    }
 }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTaskIntegrationTest.groovy
new file mode 100644
index 0000000..bcb82bd
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTaskIntegrationTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+class IncrementalTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    def "consecutively failing task has correct up-to-date status and failure"() {
+        buildFile << """
+            task foo {
+                outputs.file("out.txt")
+                doLast {
+                    if (project.file("out.txt").exists()) {
+                        throw new RuntimeException("Boo!")
+                    }
+                    project.file("out.txt") << "xxx"
+                }
+            }
+        """
+
+        when:
+        run "foo"
+        file("out.txt") << "force rerun"
+        def failure1 = runAndFail "foo"
+        def failure2 = runAndFail "foo"
+
+        then:
+        failure1.assertHasCause("Boo!")
+        failure2.assertHasCause("Boo!")
+        //this exposes an issue we used to have with in-memory cache.
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
new file mode 100644
index 0000000..ad33dff
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+class TaskCommandLineConfigurationIntegrationSpec extends AbstractIntegrationSpec {
+
+    final String someConfigurableTaskType = """
+    import org.gradle.api.internal.tasks.options.Option
+
+    class SomeTask extends DefaultTask {
+        boolean first
+        String second
+        TestEnum third
+
+        @Option(option = "first", description = "configures 'first' field")
+        void setFirst(boolean first) {
+            this.first = first
+        }
+
+        @Option(option = "second", description = "configures 'second' field")
+        void setSecond(String second) {
+            this.second = second
+        }
+
+        //more stress
+        void setSecond(Object second) {
+            this.second = second.toString()
+        }
+
+        @Option(option = "third", description = "configures 'third' field")
+        void setThird(TestEnum blubb) {
+            this.third = blubb
+        }
+
+        @TaskAction
+        void renderFields() {
+            println "first=" + first + ",second=" + second + ",third=" + third
+        }
+
+
+        enum TestEnum {
+            valid1, valid2, valid3
+        }
+    }
+
+
+    """
+
+    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.")
+        failure.assertHasCause("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.")
+        failure.assertHasCause("Unknown command-line option '--secon'.")
+
+        //TODO it's not fixable easily we would need to change some stuff in options parsing. See also ignored 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.")
+        failure.assertHasCause("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.")
+        failure.assertHasCause("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("Problem configuring task :tasks from command line.")
+        failure.assertHasCause("Unknown command-line option '-l'.")
+    }
+
+    @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'.")
+    }
+
+
+    def "decent error for invalid enum value"() {
+        given:
+        file("build.gradle") << """
+            task someTask(type: SomeTask)
+            $someConfigurableTaskType
+"""
+
+        when:
+        runAndFail 'someTask', '--third', 'unsupportedValue'
+
+        then:
+        failure.assertHasDescription("Problem configuring option 'third' on task ':someTask' from command line.")
+        failure.assertHasCause("Cannot coerce string value 'unsupportedValue' to an enum value of type 'SomeTask\$TestEnum' (valid case insensitive values: [valid1, valid2, valid3])")
+    }
+
+    def "can set enum value from commandline"() {
+        given:
+        file("build.gradle") << """
+            task someTask(type: SomeTask)
+            $someConfigurableTaskType
+"""
+
+        when:
+        run 'someTask', '--third', 'valid1'
+
+        then:
+        output.contains 'third=valid1'
+        result.assertTasksExecuted(":someTask")
+    }
+
+    @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/core/src/integTest/groovy/org/gradle/api/tasks/TaskRemovalIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskRemovalIntegrationTest.groovy
new file mode 100644
index 0000000..10ad45a
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskRemovalIntegrationTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.hamcrest.Matchers
+import spock.lang.Unroll
+
+class TaskRemovalIntegrationTest extends AbstractIntegrationSpec {
+
+    def "can remove task"() {
+        given:
+        buildScript """
+            task foo {}
+            tasks.remove(foo)
+            task foo {}
+            tasks.remove(foo)
+        """
+
+        when:
+        fails "foo"
+
+        then:
+        failure.assertThatDescription(Matchers.startsWith("Task 'foo' not found in root project"))
+    }
+
+    def "can remove task in after evaluate"() {
+        given:
+        buildScript """
+            task foo {}
+            afterEvaluate {
+                tasks.remove(foo)
+            }
+        """
+
+        when:
+        fails "foo"
+
+        then:
+        failure.assertThatDescription(Matchers.startsWith("Task 'foo' not found in root project"))
+    }
+
+    @Unroll
+    def "cant remove task in after evaluate if task is used by a #ruleClass"() {
+        given:
+        buildScript """
+            import org.gradle.model.*
+
+            task foo {}
+            task bar {}
+
+            afterEvaluate {
+                tasks.remove(foo)
+            }
+
+            // No DSL for rules dependent on other model yet
+            project.services.get(ModelRules).rule(new $ruleClass() {
+                void linkFooToBar(@Path("tasks.bar") Task bar, @Path("tasks.foo") Task foo) {
+                    // do nothing
+                }
+            })
+        """
+
+        when:
+        fails "foo"
+
+        then:
+        failure.assertThatCause(Matchers.startsWith("Tried to remove model tasks.foo but it is depended on by other model elements"))
+
+        where:
+        ruleClass << ["ModelRule", "ModelFinalizer"]
+    }
+
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/bundling/ZipIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/bundling/ZipIntegrationTest.groovy
new file mode 100644
index 0000000..0f2917b
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/bundling/ZipIntegrationTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.bundling
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.archive.ZipTestFixture
+
+class ZipIntegrationTest extends AbstractIntegrationSpec {
+
+    def ensureDuplicatesIncludedWithoutWarning() {
+        given:
+        createTestFiles()
+        buildFile << '''
+            task zip(type: Zip) {
+                from 'dir1'
+                from 'dir2'
+                from 'dir3'
+                destinationDir = buildDir
+                archiveName = 'test.zip'
+            }
+            '''
+        when:
+        run 'zip'
+
+        then:
+        def theZip = new ZipTestFixture(file('build/test.zip'))
+        theZip.hasDescendants('file1.txt', 'file1.txt', 'file2.txt')
+    }
+
+    def ensureDuplicatesCanBeExcluded() {
+        given:
+        createTestFiles()
+        buildFile << '''
+            task zip(type: Zip) {
+                from 'dir1'
+                from 'dir2'
+                from 'dir3'
+                destinationDir = buildDir
+                archiveName = 'test.zip'
+                eachFile { it.duplicatesStrategy = 'exclude' }
+            }
+            '''
+        when:
+        run 'zip'
+
+        then:
+        def theZip = new ZipTestFixture(file('build/test.zip'))
+        theZip.hasDescendants('file1.txt', 'file2.txt')
+    }
+
+    def renamedFileWillBeTreatedAsDuplicateZip() {
+        given:
+        createTestFiles()
+        buildFile << '''
+                task zip(type: Zip) {
+                    from 'dir1'
+                    from 'dir2'
+                    destinationDir = buildDir
+                    rename 'file2.txt', 'file1.txt'
+                    archiveName = 'test.zip'
+                    eachFile { it.duplicatesStrategy = 'exclude' }
+                }
+                '''
+        when:
+        run 'zip'
+
+        then:
+        def theZip = new ZipTestFixture(file('build/test.zip'))
+        theZip.hasDescendants('file1.txt')
+        theZip.assertFileContent('file1.txt', "dir1/file1.txt")
+    }
+
+    def zip64Support() {
+        given:
+        createTestFiles()
+        buildFile << '''
+            task zip(type: Zip) {
+                from 'dir1'
+                from 'dir2'
+                destinationDir = buildDir
+                archiveName = 'test.zip'
+                zip64 = true
+            }
+            '''
+        when:
+        run 'zip'
+
+        then:
+        def theZip = new ZipTestFixture(file('build/test.zip'))
+        theZip.hasDescendants('file1.txt', 'file2.txt')
+    }
+
+    private def createTestFiles() {
+        createDir('dir1', {
+            file('file1.txt').text = "dir1/file1.txt"
+        })
+        createDir('dir2', {
+            file('file2.txt').text = "dir2/file2.txt"
+        })
+        createDir('dir3', {
+            file('file1.txt').text = "dir3/file1.txt"
+        })
+    }
+
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/plugin/PluginHandlerScriptIntegTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/plugin/PluginHandlerScriptIntegTest.groovy
new file mode 100644
index 0000000..81bfed3
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/plugin/PluginHandlerScriptIntegTest.groovy
@@ -0,0 +1,554 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.bintray.BintrayApi
+import org.gradle.test.fixtures.bintray.BintrayTestServer
+import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.gradle.util.GradleVersion
+import org.junit.Rule
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class PluginHandlerScriptIntegTest extends AbstractIntegrationSpec {
+
+    private static final String SCRIPT = "plugins { println 'in' }; println 'out'"
+
+    @Rule BintrayTestServer bintray = new BintrayTestServer(executer, mavenRepo) // provides a double for JCenter
+
+    int pluginCounter = 0
+
+    def pluginMessage = "from plugin"
+    def pluginTaskName = "pluginTask"
+    def pluginVersion = "1.0"
+
+    PluginBuilder pluginBuilder() {
+        new PluginBuilder(executer, file("plugin${++pluginCounter}"))
+    }
+
+    def setup() {
+        executer.requireOwnGradleUserHomeDir() // to negate caching effects
+    }
+
+    def "build scripts have plugin blocks"() {
+        when:
+        buildFile << SCRIPT
+
+        then:
+        executesCorrectly()
+    }
+
+    def "settings scripts have plugin blocks"() {
+        when:
+        settingsFile << SCRIPT
+
+        then:
+        executesCorrectly()
+    }
+
+    def "init scripts have plugin blocks"() {
+        def initScript = file("init.gradle")
+
+        when:
+        initScript << SCRIPT
+
+        then:
+        args "-I", initScript.absolutePath
+        executesCorrectly()
+    }
+
+    def "cannot use plugin block when script target is not plugin capable"() {
+        buildFile << """
+            task foo {}
+            apply {
+                from "plugin.gradle"
+                to foo
+            }
+        """
+
+        file("plugin.gradle") << """
+            plugins {
+                apply plugin: "foo"
+            }
+        """
+
+        when:
+        fails "foo"
+
+        then:
+        errorOutput.contains("cannot have plugins applied to it")
+    }
+
+
+    def void executesCorrectly() {
+        succeeds "tasks"
+        assert output.contains(toPlatformLineSeparators("in\nout\n"))
+    }
+
+    void "plugins block has no implicit access to owner context"() {
+        when:
+        buildScript """
+            plugins {
+                try {
+                    buildscript {}
+                } catch(MissingMethodException e) {
+                    // ok
+                }
+
+                try {
+                    version
+                } catch(MissingPropertyException e) {
+                    // ok
+                }
+
+                assert delegate == null
+                assert this instanceof ${PluginHandler.name}
+                assert owner == this
+
+                println "end-of-plugins"
+            }
+        """
+
+        then:
+        succeeds "tasks"
+        and:
+        output.contains("end-of-plugins")
+    }
+
+    void "can resolve core plugins"() {
+        when:
+        buildScript """
+            plugins {
+              apply plugin: 'java'
+            }
+        """
+
+        then:
+        succeeds "javadoc"
+    }
+
+    void "core plugins cannot have a version number"() {
+        given:
+        buildScript """
+            plugins {
+                apply plugin: "java", version: "1.0"
+            }
+        """
+
+        when:
+        fails "tasks"
+
+        then:
+        failure.assertHasCause("Plugin 'java' is a core Gradle plugin, which cannot be specified with a version number")
+    }
+
+    def void publishPluginToBintray(PluginBuilder pluginBuilder, String group, String name, String version = pluginVersion) {
+        def module = bintray.jcenter.module(group, name, version)
+        module.allowAll()
+        def artifact = module.artifact([:])
+        module.publish()
+        pluginBuilder.publishTo(artifact.file)
+    }
+
+    void "can use plugin classes in script"() {
+        given:
+        bintray.start()
+        def pb = pluginBuilder()
+        pb.groovy("EchoTask.groovy") << """
+            package $pb.packageName
+
+            class EchoTask extends ${DefaultTask.name} {
+                @${TaskAction.name}
+                void doEcho() {
+                    println "$pluginMessage"
+                }
+            }
+        """
+
+        pb.addPlugin("", "test")
+        publishPluginToBintray(pb, "test", "test")
+        bintray.api.expectPackageSearch("test", new BintrayApi.FoundPackage("foo", "test:test"))
+
+        buildScript """
+          plugins {
+            apply plugin: "test", version: "$pluginVersion"
+          }
+
+          task echo(type: ${pb.packageName}.EchoTask) {}
+        """
+
+        when:
+        succeeds "echo"
+
+        then:
+        output.contains pluginMessage
+    }
+
+    void "plugins block does not leak into build script proper"() {
+        given:
+        buildFile << """
+            configurations {
+                plugins {
+                    transitive = false // just use some configuration specific API here
+                }
+            }
+
+            task showConfigurationsSize << {
+                println "configurations: " + configurations.size()
+                println "plugins transitive: " + configurations.plugins.transitive
+            }
+        """
+
+        when:
+        succeeds "sCS"
+
+        then:
+        output.contains("configurations: 1")
+        output.contains("plugins transitive: false")
+    }
+
+    def "buildscript blocks are allowed before plugin statements"() {
+        when:
+        buildScript """
+            buildscript {}
+            plugins {}
+        """
+
+        then:
+        succeeds "tasks"
+    }
+
+    def "buildscript blocks are not allowed after plugin blocks"() {
+        when:
+        buildScript """
+            plugins {}
+            buildscript {}
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasLineNumber 3
+        errorOutput.contains("all buildscript {} blocks must appear before any plugins {} blocks")
+    }
+
+    def "build logic cannot precede plugins block"() {
+        when:
+        buildScript """
+            someThing()
+            plugins {}
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasLineNumber 3
+        errorOutput.contains "only buildscript {} and and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed"
+    }
+
+    def "build logic cannot precede any plugins block"() {
+        when:
+        buildScript """
+            plugins {}
+            someThing()
+            plugins {}
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasLineNumber 4
+        errorOutput.contains "only buildscript {} and and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed"
+    }
+
+    def "failed resolution provides helpful error message"() {
+        given:
+        bintray.start()
+
+        buildScript """
+            plugins {
+                apply plugin: "foo"
+            }
+        """
+
+        and:
+        bintray.api.expectPackageSearch("foo")
+
+        when:
+        fails "tasks"
+
+        then:
+        errorOutput.contains "Cannot resolve plugin request [plugin: 'foo'] from plugin repositories:"
+        errorOutput.contains "- Gradle Distribution Plugins (listing: http://gradle.org/docs/${GradleVersion.current().version}/userguide/standard_plugins.html)"
+        errorOutput.contains "- Gradle Bintray Plugin Repository (listing: https://bintray.com/gradle-plugins-development/gradle-plugins)"
+    }
+
+    private publishTestPlugin() {
+        def pluginBuilder = new PluginBuilder(executer, testDirectory.file("plugin"))
+
+        def module = mavenRepo.module("plugin", "plugin")
+        def artifactFile = module.artifact([:]).artifactFile
+        module.publish()
+
+        def message = "from plugin"
+        def taskName = "pluginTask"
+        pluginBuilder.addPluginWithPrintlnTask(taskName, message, "plugin")
+        pluginBuilder.publishTo(artifactFile)
+    }
+
+    private testPluginBuildscriptBlock() {
+        return """
+            buildscript {
+                repositories {
+                    maven { url "$mavenRepo.uri" }
+                }
+                dependencies {
+                    classpath "plugin:plugin:1.0"
+                }
+            }
+        """
+    }
+
+    private testPluginPluginsBlock() {
+        return """
+            plugins {
+                apply plugin: "plugin", version: "1.0"
+            }
+        """
+    }
+
+    def "cannot apply plugins added to buildscript classpath in plugins block"() {
+        given:
+        publishTestPlugin()
+
+        when:
+        buildScript """
+            ${testPluginBuildscriptBlock()}
+            ${testPluginPluginsBlock()}
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        errorOutput.contains "Plugin 'plugin' is already on the script classpath (plugins on the script classpath cannot be used in a plugins {} block; move \"apply plugin: 'plugin'\" outside of the plugins {} block)"
+    }
+
+    def "cannot apply plugins added to parent buildscript classpath in plugins block"() {
+        given:
+        publishTestPlugin()
+
+        when:
+        buildScript """
+            ${testPluginBuildscriptBlock()}
+        """
+
+        settingsFile << "include 'sub'"
+
+        file("sub/build.gradle") << """
+            ${testPluginPluginsBlock()}
+        """
+
+        then:
+        fails "sub:tasks"
+
+        and:
+        errorOutput.contains "Plugin 'plugin' is already on the script classpath (plugins on the script classpath cannot be used in a plugins {} block; move \"apply plugin: 'plugin'\" outside of the plugins {} block)"
+    }
+
+    def "plugin classes are reused across projects and resolution is cached"() {
+        when:
+        bintray.start()
+        def pb = pluginBuilder().addPlugin("", "test")
+        publishPluginToBintray(pb, "test", "test")
+
+        // Only receiving one search request verifies that the result is cached
+        bintray.api.expectPackageSearch("test", new BintrayApi.FoundPackage("foo", "test:test"))
+
+        settingsFile << "include 'sub1', 'sub2'"
+
+        ["sub1/build.gradle", "sub2/build.gradle", "build.gradle"].each {
+            file(it) << """
+                plugins {
+                    apply plugin: "test", version: "$pluginVersion"
+                }
+                ext.pluginClass = org.gradle.test.TestPlugin
+            """
+        }
+
+        file("build.gradle") << """
+            evaluationDependsOnChildren()
+
+            pluginClass.is project(":sub1").pluginClass
+            pluginClass.is project(":sub2").pluginClass
+            project(":sub1").pluginClass.is project(":sub2").pluginClass
+        """
+
+        then:
+        succeeds "tasks"
+    }
+
+    def "classes from plugin block are not visible to classes from buildscript block"() {
+        given:
+        def pb1 = pluginBuilder()
+        def pb2 = pluginBuilder()
+
+        pb1.addPlugin("getClass().classLoader.loadClass('org.gradle.test.PluginOne')", "p1", "PluginOne")
+        pb2.addPlugin("getClass().classLoader.loadClass('org.gradle.test.PluginOne')", "p2", "PluginTwo")
+
+        bintray.start()
+
+        publishPluginToBintray(pb1, "p1", "p1")
+        bintray.api.expectPackageSearch("p1", new BintrayApi.FoundPackage(pluginVersion, "p1:p1"))
+
+        publishPluginToBintray(pb2, "p2", "p2")
+
+        when:
+        settingsFile << "rootProject.name = 'tp'"
+        buildScript """
+            buildscript {
+                repositories {
+                    jcenter()
+                }
+                dependencies {
+                    classpath "p2:p2:$pluginVersion"
+                }
+            }
+            plugins {
+                apply plugin: "p1", version: "1.0"
+            }
+
+            apply plugin: "p2"
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasDescription("A problem occurred evaluating root project 'tp'.")
+        failure.assertHasCause("org.gradle.test.PluginOne") // message of ClassNotFoundException
+        failure.assertHasLineNumber(14) // asserts that  'apply plugin: "p2"' is the line that fails
+    }
+
+    def "plugin classes are not available to child projects"() {
+        given:
+        bintray.start()
+        def pb = pluginBuilder().addPlugin("", "plugin")
+        publishPluginToBintray(pb, "plugin", "plugin")
+        bintray.api.expectPackageSearch("plugin", new BintrayApi.FoundPackage(pluginVersion, "plugin:plugin"))
+
+        when:
+        settingsFile << "include 'child'"
+        buildScript """
+            ${testPluginPluginsBlock()}
+            import org.gradle.test.TestPlugin
+        """
+        file("child/build.gradle") << """
+            import org.gradle.test.TestPlugin
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasDescription("Could not compile build file '${file("child/build.gradle")}'.")
+    }
+
+    def "plugins cannot be applied by child projects"() {
+        given:
+        bintray.start()
+        def pb = pluginBuilder().addPlugin("", "plugin")
+        publishPluginToBintray(pb, "plugin", "plugin")
+        bintray.api.expectPackageSearch("plugin", new BintrayApi.FoundPackage(pluginVersion, "plugin:plugin"))
+
+        when:
+        settingsFile << "include 'child'"
+        buildScript """
+            ${testPluginPluginsBlock()}
+            import org.gradle.test.TestPlugin
+        """
+        file("child/build.gradle") << """
+            apply plugin: 'plugin'
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasDescription("A problem occurred evaluating project ':child'.")
+        failure.assertHasCause("Plugin with id 'plugin' not found.")
+    }
+
+    def "plugins added via plugins block cannot be applied with project.apply"() {
+        given:
+        bintray.start()
+        def pb = pluginBuilder().addPlugin("project.task('foo')", "plugin")
+        publishPluginToBintray(pb, "plugin", "plugin")
+        bintray.api.expectPackageSearch("plugin", new BintrayApi.FoundPackage(pluginVersion, "plugin:plugin"))
+
+        when:
+        settingsFile << 'rootProject.name = "tp"'
+        buildScript """
+            ${testPluginPluginsBlock()}
+            apply plugin: 'plugin'
+        """
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasDescription("A problem occurred evaluating root project 'tp'.")
+        failure.assertHasCause("Plugin with id 'plugin' not found.")
+    }
+
+    def "plugins added in root buildscript can be applied in subprojects"() {
+        given:
+        executer.withEagerClassLoaderCreationCheckDisabled()
+        pluginBuilder().addPlugin("project.task('foo')", "plugin").publishTo(mavenRepo.module("g", "a").artifactFile)
+
+        settingsFile << "include 'sub'"
+
+        buildScript """
+            buildscript {
+                repositories {
+                    maven { url "$mavenRepo.uri" }
+                }
+                dependencies {
+                    classpath "g:a:1.0"
+                }
+            }
+
+            apply plugin: 'plugin'
+
+            subprojects {
+                apply plugin: 'plugin'
+            }
+        """
+
+        when:
+        succeeds "sub:foo"
+
+        then:
+        ":sub:foo" in executedTasks
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy
new file mode 100644
index 0000000..058edd5
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class ScriptPluginClassLoadingIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-3069")
+    def "second level and beyond script plugins have same class loader scope as original target"() {
+        when:
+        file("buildSrc/src/main/java/pkg/Thing.java") << """
+            package pkg;
+            public class Thing {
+              public String getMessage() { return "hello"; }
+            }
+        """
+
+        file("plugin1.gradle") << """
+            task sayMessageFrom1 << { println new pkg.Thing().getMessage() }
+            apply from: 'plugin2.gradle'
+        """
+
+        file("plugin2.gradle") << """
+            task sayMessageFrom2 << { println new pkg.Thing().getMessage() }
+            apply from: 'plugin3.gradle'
+        """
+
+        file("plugin3.gradle") << """
+            task sayMessageFrom3 << { println new pkg.Thing().getMessage() }
+        """
+
+        buildScript "apply from: 'plugin1.gradle'"
+
+        then:
+        succeeds "sayMessageFrom1", "sayMessageFrom2", "sayMessageFrom3"
+        output.contains "hello"
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/plugin/bintray/BintrayPluginResolutionIntegTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/plugin/bintray/BintrayPluginResolutionIntegTest.groovy
new file mode 100644
index 0000000..e4cd608
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/plugin/bintray/BintrayPluginResolutionIntegTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.bintray
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.bintray.BintrayApi
+import org.gradle.test.fixtures.bintray.BintrayTestServer
+import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.hamcrest.Matchers
+import org.junit.Rule
+
+class BintrayPluginResolutionIntegTest extends AbstractIntegrationSpec {
+
+    @Rule BintrayTestServer bintray = new BintrayTestServer(executer, mavenRepo)
+    def pluginBuilder = new PluginBuilder(executer, file("plugin"))
+
+    def setup() {
+        executer.requireOwnGradleUserHomeDir() // negate caching behaviour
+        bintray.start()
+    }
+
+    def "can resolve and use plugin from bintray"() {
+        given:
+        def group = "org.gradle.test"
+        def name = "foo"
+        def id = "fake-plugin"
+        def version = "2.0"
+
+        bintray.api.expectPackageSearch(id, new BintrayApi.FoundPackage(version, "$group:$name"))
+
+        def module = bintray.jcenter.module(group, name, version)
+        module.allowAll()
+        def artifact = module.artifact([:])
+        module.publish()
+
+        def message = "from plugin"
+        def taskName = "pluginTask"
+        pluginBuilder.addPluginWithPrintlnTask(taskName, message, id)
+        pluginBuilder.publishTo(artifact.file)
+
+        buildScript """
+          plugins {
+            apply plugin: "$id", version: "2.0"
+          }
+        """
+
+        when:
+        succeeds taskName
+
+        then:
+        output.contains message
+    }
+
+    def "plugin version number is required"() {
+        given:
+        def group = "org.gradle.test"
+        def name = "foo"
+        def id = "fake-plugin"
+        def version = "2.0"
+
+        bintray.api.expectPackageSearch(id, new BintrayApi.FoundPackage(version, "$group:$name"))
+
+        def module = bintray.jcenter.module(group, name, version)
+        module.allowAll()
+        def artifact = module.artifact([:])
+        module.publish()
+
+        pluginBuilder.addPlugin("", id)
+        pluginBuilder.publishTo(artifact.file)
+
+        buildScript """
+          plugins {
+            apply plugin: "$id"
+          }
+        """
+
+        when:
+        fails "tasks"
+
+        then:
+        failure.assertHasCause("No version number supplied for plugin 'fake-plugin'. A version number must be supplied for plugins resolved from 'https://bintray.com/gradle-plugins-development/gradle-plugins'.")
+    }
+
+    def "does not assert has version number if plugin not found"() {
+        given:
+        def id = "fake-plugin"
+        bintray.api.expectPackageSearch(id)
+        buildScript """
+          plugins {
+            apply plugin: "$id"
+          }
+        """
+
+        when:
+        fails "tasks"
+
+        then:
+        failure.assertThatCause(Matchers.startsWith("Cannot resolve plugin request"))
+    }
+
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/process/internal/PathLimitationIntegTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/PathLimitationIntegTest.groovy
new file mode 100644
index 0000000..2d0b4af
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/PathLimitationIntegTest.groovy
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.process.internal
+
+import org.apache.commons.lang.RandomStringUtils
+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.classpath.DefaultModuleRegistry
+import org.gradle.api.internal.classpath.ModuleRegistry
+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.cache.internal.locklistener.NoOpFileLockContentionHandler
+import org.gradle.internal.id.LongIdGenerator
+import org.gradle.internal.jvm.Jvm
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.messaging.remote.MessagingServer
+import org.gradle.messaging.remote.internal.MessagingServices
+import org.gradle.process.internal.child.WorkerProcessClassPathProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.GradleVersion
+import org.junit.Rule
+import spock.lang.Ignore
+import spock.lang.IgnoreIf
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertTrue
+
+class PathLimitationIntegTest extends Specification {
+    private final TestListenerInterface listenerMock = Mock(TestListenerInterface.class);
+    private final MessagingServices messagingServices = new MessagingServices(getClass().getClassLoader());
+    private final MessagingServer server = messagingServices.get(MessagingServer.class);
+
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    private final ProcessMetaDataProvider metaDataProvider = new DefaultProcessMetaDataProvider(NativeServices.getInstance().get(org.gradle.internal.nativeplatform.ProcessEnvironment.class));
+    private final CacheFactory factory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider, new NoOpFileLockContentionHandler()));
+    private final CacheRepository cacheRepository = new DefaultCacheRepository(new DefaultCacheScopeMapping(tmpDir.getTestDirectory(), null, GradleVersion.current()), 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, TestFiles.resolver(tmpDir.getTestDirectory()), new LongIdGenerator());
+    private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(TestListenerInterface.class);
+    private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast.source);
+
+    public void setup() {
+        broadcast.add(listenerMock);
+    }
+
+    public void after() {
+        messagingServices.stop();
+    }
+
+    @IgnoreIf({OperatingSystem.current().isWindows()})
+    @Unroll
+    def "WorkerProcessBuilder handles workingDir with absolute path length #absolutePathLength"() throws Throwable {
+        when:
+        def testWorkingDir = generateTestWorkingDirectory(absolutePathLength)
+        then:
+        assert testWorkingDir.exists()
+        execute(worker(new HelloWorldRemoteProcess(), testWorkingDir))
+        where:
+        absolutePathLength << [258, 259, 260]
+    }
+
+    @IgnoreIf({OperatingSystem.current().isWindows()})
+    @Unroll
+    def "JavaProcessBuilder handles workingDir with absolute path length #absolutePathLength"() throws Throwable {
+        when:
+        def testWorkingDir = generateTestWorkingDirectory(absolutePathLength)
+
+        assert testWorkingDir.exists()
+
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        processBuilder.directory(testWorkingDir)
+
+        processBuilder.command(Jvm.current().getJavaExecutable().absolutePath, "-version")
+        then:
+        Process process = processBuilder.start()
+
+        process.waitFor() == 0
+        process.exitValue() == 0
+        and:
+        process.errorStream.text.contains("java version")
+
+        where:
+        absolutePathLength << [258, 259, 260]
+    }
+
+    @Ignore
+    @Unroll
+    def "OS handles workingDir with absolute path length #absolutePathLength"() throws Throwable {
+        setup:
+        def testWorkingDir = generateTestWorkingDirectory(absolutePathLength)
+        TestFile testBatchScript = tmpDir.getTestDirectory().createFile("testBatch.cmd")
+        testBatchScript.text = """
+        cd ${testWorkingDir.name}
+        java -version > o.txt 2>&1
+"""
+
+        when:
+
+        assert testWorkingDir.exists()
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        processBuilder.directory(tmpDir.getTestDirectory())
+        processBuilder.command("CMD", "/C", "testBatch.cmd")
+
+        and:
+        Process process = processBuilder.start()
+
+        then:
+        process.waitFor() == 0
+        process.exitValue() == 0
+        and:
+        def outputText = new File(testWorkingDir, "o.txt").text
+        println outputText
+        assert outputText.contains("java version")
+        where:
+        absolutePathLength << [250, 255, 260] // 250 succeeds
+                                              // 255 fails because path + "/o.txt" >= 260
+                                              // 260 fails different because path >= 260
+    }
+
+
+    TestFile generateTestWorkingDirectory(int absolutePathLength) {
+        // windows can handle a path up to 260 characters (259 + NUL)
+        // we create a path that is 260 +1 (absolutePathLength + "/" + randompath)
+        def testDirectory = tmpDir.getTestDirectory()
+        def pathoffset = absolutePathLength - 1 - testDirectory.getAbsolutePath().length()
+        def alphanumeric = RandomStringUtils.randomAlphanumeric(pathoffset)
+        return testDirectory.createDir("$alphanumeric")
+    }
+
+    public static class HelloWorldRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            println "hello World!"
+        }
+    }
+
+    private ChildProcess worker(Action<? super WorkerProcessContext> action, File workingDirectory = tmpDir.getTestDirectory()) {
+
+        return new ChildProcess(action, workingDirectory);
+    }
+
+    void execute(ChildProcess... processes) throws Throwable {
+        for (ChildProcess process : processes) {
+            process.start();
+        }
+        for (ChildProcess process : processes) {
+            process.waitForStop();
+        }
+        messagingServices.stop();
+        exceptionListener.rethrow();
+    }
+
+    private class ChildProcess {
+        private boolean stopFails;
+        private boolean startFails;
+        private WorkerProcess proc;
+        private Action<? super WorkerProcessContext> action;
+        private List<String> jvmArgs = Collections.emptyList();
+        private final File workingDirectory
+
+        public ChildProcess(Action<? super WorkerProcessContext> action, File workingDirectory) {
+            this.workingDirectory = workingDirectory
+            this.action = action;
+        }
+
+        public void start() {
+            WorkerProcessBuilder builder = workerFactory.create();
+            builder.worker(action);
+            builder.getJavaCommand().jvmArgs(jvmArgs);
+            builder.getJavaCommand().setWorkingDir(workingDirectory)
+
+            proc = builder.build();
+            try {
+                proc.start();
+                assertFalse(startFails);
+            } catch (ExecException e) {
+                e.printStackTrace()
+                assertTrue(startFails);
+                return;
+            }
+            proc.getConnection().addIncoming(TestListenerInterface.class, exceptionListener);
+            proc.getConnection().connect()
+        }
+
+        public void waitForStop() {
+            if (startFails) {
+                return;
+            }
+            try {
+                proc.waitForStop();
+                assertFalse("Expected process to fail", stopFails);
+            } catch (ExecException e) {
+                assertTrue("Unexpected failure in worker process", stopFails);
+            }
+        }
+
+        public ChildProcess jvmArgs(String... jvmArgs) {
+            this.jvmArgs = Arrays.asList(jvmArgs);
+            return this;
+        }
+    }
+
+
+    public static class RemoteExceptionListener implements TestListenerInterface {
+        Throwable ex;
+        final TestListenerInterface dispatch;
+
+        public RemoteExceptionListener(TestListenerInterface dispatch) {
+            this.dispatch = dispatch;
+        }
+
+        void send(String message, int count) {
+            try {
+                dispatch.send(message, count);
+            } catch (Throwable e) {
+                ex = e;
+            }
+        }
+
+        public void rethrow() throws Throwable {
+            if (ex != null) {
+                throw ex;
+            }
+        }
+    }
+
+    public interface TestListenerInterface {
+        public void send(String message, int count);
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java
index ad73aa9..a1ca484 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java
+++ b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java
@@ -19,7 +19,6 @@ package org.gradle.process.internal;
 import org.apache.tools.ant.Project;
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.DefaultClassPathProvider;
 import org.gradle.api.internal.DefaultClassPathRegistry;
@@ -29,17 +28,18 @@ 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.cache.internal.locklistener.NoOpFileLockContentionHandler;
+import org.gradle.internal.Actions;
 import org.gradle.internal.id.LongIdGenerator;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.listener.ListenerBroadcast;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.ObjectConnectionBuilder;
 import org.gradle.messaging.remote.internal.MessagingServices;
 import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.GradleVersion;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
 import org.jmock.integration.junit4.JMock;
@@ -71,14 +71,14 @@ public class WorkerProcessIntegrationTest {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     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.getTestDirectory(), null, CacheUsage.ON, factory);
+    private final CacheFactory factory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider, new NoOpFileLockContentionHandler()));
+    private final CacheScopeMapping scopeMapping = new DefaultCacheScopeMapping(tmpDir.getTestDirectory(), null, GradleVersion.current());
+    private final CacheRepository cacheRepository = new DefaultCacheRepository(scopeMapping, 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, TestFiles.resolver(tmpDir.getTestDirectory()), new LongIdGenerator());
-    private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(
-            TestListenerInterface.class);
-    private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast);
+    private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(TestListenerInterface.class);
+    private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast.getSource());
 
     @Before
     public void setUp() {
@@ -105,8 +105,8 @@ public class WorkerProcessIntegrationTest {
 
     @Test
     public void thisProcessCanSendEventsToWorkerProcess() throws Throwable {
-        execute(worker(new PingRemoteProcess()).onServer(new Action<ObjectConnection>() {
-            public void execute(ObjectConnection objectConnection) {
+        execute(worker(new PingRemoteProcess()).onServer(new Action<ObjectConnectionBuilder>() {
+            public void execute(ObjectConnectionBuilder objectConnection) {
                 TestListenerInterface listener = objectConnection.addOutgoing(TestListenerInterface.class);
                 listener.send("1", 0);
                 listener.send("1", 1);
@@ -190,7 +190,7 @@ public class WorkerProcessIntegrationTest {
         private WorkerProcess proc;
         private Action<? super WorkerProcessContext> action;
         private List<String> jvmArgs = Collections.emptyList();
-        private Action<ObjectConnection> serverAction;
+        private Action<ObjectConnectionBuilder> serverAction;
 
         public ChildProcess(Action<? super WorkerProcessContext> action) {
             this.action = action;
@@ -228,6 +228,7 @@ public class WorkerProcessIntegrationTest {
             if (serverAction != null) {
                 serverAction.execute(proc.getConnection());
             }
+            proc.getConnection().connect();
         }
 
         public void waitForStop() {
@@ -242,7 +243,7 @@ public class WorkerProcessIntegrationTest {
             }
         }
 
-        public ChildProcess onServer(Action<ObjectConnection> action) {
+        public ChildProcess onServer(Action<ObjectConnectionBuilder> action) {
             this.serverAction = action;
             return this;
         }
@@ -253,17 +254,17 @@ public class WorkerProcessIntegrationTest {
         }
     }
 
-    public static class RemoteExceptionListener implements Dispatch<MethodInvocation> {
+    public static class RemoteExceptionListener implements TestListenerInterface {
         Throwable ex;
-        final Dispatch<MethodInvocation> dispatch;
+        final TestListenerInterface dispatch;
 
-        public RemoteExceptionListener(Dispatch<MethodInvocation> dispatch) {
+        public RemoteExceptionListener(TestListenerInterface dispatch) {
             this.dispatch = dispatch;
         }
 
-        public void dispatch(MethodInvocation message) {
+        public void send(String message, int count) {
             try {
-                dispatch.dispatch(message);
+                dispatch.send(message, count);
             } catch (Throwable e) {
                 ex = e;
             }
@@ -297,8 +298,8 @@ public class WorkerProcessIntegrationTest {
             }
 
             // Send some messages
-            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
-                    TestListenerInterface.class);
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            workerProcessContext.getServerConnection().connect();
             sender.send("message 1", 1);
             sender.send("message 2", 2);
         }
@@ -307,6 +308,7 @@ public class WorkerProcessIntegrationTest {
     public static class OtherRemoteProcess implements Action<WorkerProcessContext>, Serializable {
         public void execute(WorkerProcessContext workerProcessContext) {
             TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            workerProcessContext.getServerConnection().connect();
             sender.send("other 1", 1);
             sender.send("other 2", 2);
         }
@@ -322,8 +324,8 @@ public class WorkerProcessIntegrationTest {
                 }
             }).start();
 
-            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
-                    TestListenerInterface.class);
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            workerProcessContext.getServerConnection().connect();
             sender.send("message 1", 1);
             sender.send("message 2", 2);
         }
@@ -345,6 +347,7 @@ public class WorkerProcessIntegrationTest {
         public void execute(WorkerProcessContext workerProcessContext) {
             stopReceived = new CountDownLatch(1);
             workerProcessContext.getServerConnection().addIncoming(TestListenerInterface.class, this);
+            workerProcessContext.getServerConnection().connect();
             try {
                 stopReceived.await();
             } catch (InterruptedException e) {
@@ -356,6 +359,7 @@ public class WorkerProcessIntegrationTest {
     public static class CrashingRemoteProcess implements Action<WorkerProcessContext>, Serializable {
         public void execute(WorkerProcessContext workerProcessContext) {
             TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            workerProcessContext.getServerConnection().connect();
             sender.send("message 1", 1);
             sender.send("message 2", 2);
             // crash
diff --git a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
index 265cd9e..c3358f8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
@@ -17,13 +17,11 @@ package org.gradle;
 
 import org.codehaus.groovy.runtime.StackTraceUtils;
 import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.internal.LocationAwareException;
+import org.gradle.internal.exceptions.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.internal.exceptions.FailureResolutionAware;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.ShowStacktrace;
 import org.gradle.logging.StyledTextOutput;
@@ -112,15 +110,11 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
 
     private FailureDetails constructFailureDetails(String granularity, Throwable failure) {
         FailureDetails details = new FailureDetails(failure);
-        if (failure instanceof GradleException) {
-            reportBuildFailure(granularity, (GradleException) failure, details);
-        } else {
-            reportInternalError(details);
-        }
+        reportBuildFailure(granularity, failure, details);
         return details;
     }
 
-    private void reportBuildFailure(String granularity, GradleException failure, FailureDetails details) {
+    private void reportBuildFailure(String granularity, Throwable failure, FailureDetails details) {
         if (loggingConfiguration.getShowStacktrace() == ShowStacktrace.ALWAYS || loggingConfiguration.getLogLevel() == LogLevel.DEBUG) {
             details.exceptionStyle = ExceptionStyle.SANITIZED;
         }
@@ -128,23 +122,10 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
             details.exceptionStyle = ExceptionStyle.FULL;
         }
 
-        if (failure instanceof TaskSelectionException) {
-            formatTaskSelectionFailure((TaskSelectionException) failure, details);
-        } else {
-            formatGenericFailure(granularity, failure, details);
-        }
+        formatGenericFailure(granularity, failure, details);
     }
 
-    private void formatTaskSelectionFailure(TaskSelectionException failure, FailureDetails details) {
-        assert failure.getCause() == null;
-        details.summary.text("Could not determine which tasks to execute.");
-        details.details.text(getMessage(failure));
-        details.resolution.text("Run ");
-        clientMetaData.describeCommand(details.resolution.withStyle(UserInput), ImplicitTasksConfigurer.TASKS_TASK);
-        details.resolution.text(" to get a list of available tasks.");
-    }
-
-    private void formatGenericFailure(String granularity, GradleException failure, final FailureDetails details) {
+    private void formatGenericFailure(String granularity, Throwable failure, final FailureDetails details) {
         details.summary.format("%s failed with an exception.", granularity);
 
         fillInFailureResolution(details);
@@ -161,7 +142,7 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
                 @Override
                 public void node(final Throwable node) {
                     if (node == scriptException) {
-                        details.details.text(scriptException.getOriginalMessage());
+                        details.details.text(getMessage(scriptException.getCause()));
                     } else {
                         details.details.format("%n");
                         StringBuilder prefix = new StringBuilder();
@@ -193,12 +174,17 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
     }
 
     private void fillInFailureResolution(FailureDetails details) {
+        if (details.failure instanceof FailureResolutionAware) {
+            ((FailureResolutionAware) details.failure).appendResolution(details.resolution, clientMetaData);
+            if (details.resolution.getHasContent()) {
+                details.resolution.append(' ');
+            }
+        }
         if (details.exceptionStyle == ExceptionStyle.NONE) {
             details.resolution.text("Run with ");
             details.resolution.withStyle(UserInput).format("--%s", LoggingCommandLineConverter.STACKTRACE_LONG);
             details.resolution.text(" option to get the stack trace. ");
         }
-
         if (loggingConfiguration.getLogLevel() != LogLevel.DEBUG) {
             details.resolution.text("Run with ");
             if (loggingConfiguration.getLogLevel() != LogLevel.INFO) {
@@ -218,18 +204,6 @@ 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();
diff --git a/subprojects/core/src/main/groovy/org/gradle/BuildListener.java b/subprojects/core/src/main/groovy/org/gradle/BuildListener.java
index a27aaad..3bb5583 100644
--- a/subprojects/core/src/main/groovy/org/gradle/BuildListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/BuildListener.java
@@ -19,10 +19,8 @@ import org.gradle.api.initialization.Settings;
 import org.gradle.api.invocation.Gradle;
 
 /**
- * <p>A {@code BuildListener} is notified of the major lifecycle events as a {@link GradleLauncher} instance executes a
- * build.</p>
+ * <p>A {@code BuildListener} is notified of the major lifecycle events as a build is executed.</p>
  *
- * @author Hans Dockter
  * @see org.gradle.api.invocation.Gradle#addListener(Object)
  */
 public interface BuildListener {
diff --git a/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java b/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
index 913c4af..0d044a9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
+++ b/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
@@ -21,7 +21,6 @@ import org.gradle.api.InvalidUserDataException;
  * <p>{@code CacheUsage} specifies how compiled scripts should be cached.</p>
  *
  * @deprecated This enum has been deprecated. Use {@link StartParameter#isRerunTasks()} and {@link StartParameter#isRecompileScripts()} instead.
- * @author Hans Dockter
  */
 @Deprecated
 public enum CacheUsage {
diff --git a/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java b/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java
index 7d58db8..442e50b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java
@@ -16,15 +16,21 @@
 package org.gradle;
 
 import org.gradle.api.logging.StandardOutputListener;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
 import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.ServiceRegistryBuilder;
+import org.gradle.internal.service.scopes.GlobalScopeServices;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.util.DeprecationLogger;
 
 /**
- * <p>{@code GradleLauncher} is mildly deprecated. It is being replaced by the Tooling API.
- * If you're interested in embedding Gradle you should read the new user guide chapter on embedding Gradle.
+ * <p>Executes a Gradle build.
+ *
+ * <p>{@code GradleLauncher} is deprecated. It has been replaced by the Tooling API.
+ * If you're interested in embedding Gradle you should read the user guide chapter on embedding Gradle.
  * The main entry point to the Tooling API (and embedding Gradle) is {@code org.gradle.tooling.GradleConnector}.
  *
- * <p>You should try using the Tooling API ({@code GradleConnector}) instead of {@code GradleLauncher}.
+ * <p>You should try using the Tooling API (via {@code GradleConnector}) instead of {@code GradleLauncher}.
  * However, if you need some capability that isn't yet implemented in the Tooling API here is how you use {@code GradleLauncher}:
  *
  * <ol>
@@ -46,8 +52,9 @@ import org.gradle.initialization.GradleLauncherFactory;
  *
  * </ol>
  *
- * @author Hans Dockter
+ * @deprecated Use the tooling API instead.
  */
+ at Deprecated
 public abstract class GradleLauncher {
 
     private static GradleLauncherFactory factory;
@@ -82,14 +89,29 @@ public abstract class GradleLauncher {
      *
      * @param startParameter The start parameter object the GradleLauncher instance is initialized with
      * @return The {@code GradleLauncher}. Never returns null.
+     * @deprecated Use the tooling API instead.
      */
-    public static GradleLauncher newInstance(final StartParameter startParameter) {
-        return getFactory().newInstance(startParameter);
+    @Deprecated
+    public static GradleLauncher newInstance(StartParameter startParameter) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GradleLauncher.newInstance()");
+        return doGetFactory().newInstance(startParameter);
+    }
+
+    @Deprecated
+    public static GradleLauncherFactory getFactory() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GradleLauncher.getFactory()");
+        return doGetFactory();
     }
 
-    public static synchronized GradleLauncherFactory getFactory() {
+    private static synchronized GradleLauncherFactory doGetFactory() {
         if (factory == null) {
-            factory = new DefaultGradleLauncherFactory();
+            factory = ServiceRegistryBuilder.builder()
+                    .displayName("Global services")
+                    .parent(LoggingServiceRegistry.newProcessLogging())
+                    .parent(NativeServices.getInstance())
+                    .provider(new GlobalScopeServices(false))
+                    .build()
+                    .get(GradleLauncherFactory.class);
         }
         return factory;
     }
@@ -99,9 +121,13 @@ public abstract class GradleLauncher {
      *
      * @param commandLineArgs A String array where each element denotes an entry of the Gradle command line syntax
      * @return The {@code GradleLauncher}. Never returns null.
+     * @deprecated Use the tooling API instead.
      */
+    @Deprecated
     public static GradleLauncher newInstance(String... commandLineArgs) {
-        return newInstance(createStartParameter(commandLineArgs));
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GradleLauncher.newInstance()");
+        GradleLauncherFactory gradleLauncherFactory = doGetFactory();
+        return gradleLauncherFactory.newInstance(gradleLauncherFactory.createStartParameter(commandLineArgs));
     }
 
     /**
@@ -110,11 +136,15 @@ public abstract class GradleLauncher {
      *
      * @param commandLineArgs A String array where each element denotes an entry of the Gradle command line syntax
      * @return The {@code GradleLauncher}. Never returns null.
+     * @deprecated No replacement.
      */
-    public static StartParameter createStartParameter(final String... commandLineArgs) {
-        return getFactory().createStartParameter(commandLineArgs);
+    @Deprecated
+    public static StartParameter createStartParameter(String... commandLineArgs) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GradleLauncher.createStartParameter()");
+        return doGetFactory().createStartParameter(commandLineArgs);
     }
 
+    @Deprecated
     public static synchronized void injectCustomFactory(GradleLauncherFactory gradleLauncherFactory) {
         factory = gradleLauncherFactory;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index ab9741d..577e073 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -22,11 +22,13 @@ import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
 import org.gradle.api.Incubating;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.initialization.BuildLayoutParameters;
 import org.gradle.initialization.CompositeInitScriptFinder;
 import org.gradle.initialization.DistributionInitScriptFinder;
 import org.gradle.initialization.UserHomeInitScriptFinder;
 import org.gradle.internal.SystemProperties;
 import org.gradle.logging.LoggingConfiguration;
+import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
@@ -34,12 +36,13 @@ import java.io.Serializable;
 import java.util.*;
 
 /**
- * <p>{@code StartParameter} defines the configuration used by a {@link GradleLauncher} instance to execute a build. The properties of {@code StartParameter} generally correspond to the command-line
- * options of Gradle. You pass a {@code StartParameter} instance to {@link GradleLauncher#newInstance(StartParameter)} when you create a new {@code Gradle} instance.</p>
+ * <p>{@code StartParameter} defines the configuration used by a Gradle instance to execute a build. The properties of {@code StartParameter} generally
+ * correspond to the command-line options of Gradle.
+ *
+ * <p>You pass a {@code StartParameter} instance to {@link GradleLauncher#newInstance(StartParameter)} when you create a new Gradle instance.</p>
  *
  * <p>You can obtain an instance of a {@code StartParameter} by either creating a new one, or duplicating an existing one using {@link #newInstance} or {@link #newBuild}.</p>
  *
- * @author Hans Dockter
  * @see GradleLauncher
  */
 public class StartParameter extends LoggingConfiguration implements Serializable {
@@ -50,11 +53,12 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     public static final File DEFAULT_GRADLE_USER_HOME = new File(SystemProperties.getUserHome() + "/.gradle");
 
     private List<String> taskNames = new ArrayList<String>();
-    private Set<String> excludedTaskNames = new HashSet<String>();
+    private Set<String> excludedTaskNames = new LinkedHashSet<String>();
     private boolean buildProjectDependencies = true;
     private File currentDir;
     private File projectDir;
-    private boolean searchUpwards = true;
+    private String projectPath;
+    private boolean searchUpwards;
     private Map<String, String> projectProperties = new HashMap<String, String>();
     private Map<String, String> systemPropertiesArgs = new HashMap<String, String>();
     private File gradleUserHomeDir;
@@ -74,7 +78,6 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     private boolean recompileScripts;
     private int parallelThreadCount;
     private boolean configureOnDemand;
-    private boolean parallelThreadCountConfigured;
 
     /**
      * Sets the project's cache location. Set to null to use the default location.
@@ -96,18 +99,12 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * Creates a {@code StartParameter} with default values. This is roughly equivalent to running Gradle on the command-line with no arguments.
      */
     public StartParameter() {
-        String gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY);
-        if (gradleUserHome == null) {
-            gradleUserHome = System.getenv("GRADLE_USER_HOME");
-            if (gradleUserHome == null) {
-                gradleUserHome = DEFAULT_GRADLE_USER_HOME.getAbsolutePath();
-            }
-        }
-
-        gradleUserHomeDir = GFileUtils.canonicalise(new File(gradleUserHome));
         gradleHomeDir = new DefaultModuleRegistry().getGradleHome();
 
-        setCurrentDir(null);
+        BuildLayoutParameters layoutDefaults = new BuildLayoutParameters();
+        searchUpwards = layoutDefaults.getSearchUpwards();
+        currentDir = layoutDefaults.getProjectDir();
+        gradleUserHomeDir = layoutDefaults.getGradleUserHomeDir();
     }
 
     /**
@@ -116,14 +113,18 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * @return the new parameters.
      */
     public StartParameter newInstance() {
-        StartParameter p = newBuild();
+        return prepareNewInstance(new StartParameter());
+    }
 
+    protected StartParameter prepareNewInstance(StartParameter p) {
+        prepareNewBuild(p);
         p.buildFile = buildFile;
         p.projectDir = projectDir;
+        p.projectPath = projectPath;
         p.settingsFile = settingsFile;
         p.useEmptySettings = useEmptySettings;
         p.taskNames = new ArrayList<String>(taskNames);
-        p.excludedTaskNames = new HashSet<String>(excludedTaskNames);
+        p.excludedTaskNames = new LinkedHashSet<String>(excludedTaskNames);
         p.buildProjectDependencies = buildProjectDependencies;
         p.currentDir = currentDir;
         p.searchUpwards = searchUpwards;
@@ -133,19 +134,20 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         p.initScripts = new ArrayList<File>(initScripts);
         p.dryRun = dryRun;
         p.projectCacheDir = projectCacheDir;
-
         return p;
     }
 
     /**
-     * <p>Creates the parameters for a new build, using these parameters as a template. Copies the environmental properties from this parameter (eg gradle user home dir, etc), but does not copy the
+     * <p>Creates the parameters for a new build, using these parameters as a template. Copies the environmental properties from this parameter (eg Gradle user home dir, etc), but does not copy the
      * build specific properties (eg task names).</p>
      *
      * @return The new parameters.
      */
     public StartParameter newBuild() {
-        StartParameter p = new StartParameter();
+        return prepareNewBuild(new StartParameter());
+    }
 
+    protected StartParameter prepareNewBuild(StartParameter p) {
         p.gradleUserHomeDir = gradleUserHomeDir;
         p.cacheUsage = cacheUsage;
         p.setLogLevel(getLogLevel());
@@ -159,7 +161,6 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         p.refreshDependencies = refreshDependencies;
         p.parallelThreadCount = parallelThreadCount;
         p.configureOnDemand = configureOnDemand;
-
         return p;
     }
 
@@ -285,7 +286,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         if (currentDir != null) {
             this.currentDir = GFileUtils.canonicalise(currentDir);
         } else {
-            this.currentDir = GFileUtils.canonicalise(SystemProperties.getCurrentDir());
+            this.currentDir = new BuildLayoutParameters().getProjectDir();
         }
     }
 
@@ -314,11 +315,17 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     }
 
     /**
+     * Deprecated. It is no longer used internally and there's no good reason to keep it.
+     * There is no replacement method.
+     *
      * Returns a newly constructed map that is the JVM system properties merged with the system property args. <p> System property args take precedence over JVM system properties.
      *
      * @return The merged system properties
+     * @deprecated No replacement
      */
+    @Deprecated
     public Map<String, String> getMergedSystemProperties() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("StartParameter.getMergedSystemProperties()");
         Map<String, String> merged = new HashMap<String, String>();
         merged.putAll((Map) System.getProperties());
         merged.putAll(getSystemPropertiesArgs());
@@ -363,7 +370,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     /**
      *  Returns the configured CacheUsage.
      *  @deprecated Use {@link #isRecompileScripts} and/or {@link #isRerunTasks} instead.
-     * */
+      */
     @Deprecated
     public CacheUsage getCacheUsage() {
         return cacheUsage;
@@ -372,7 +379,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     /**
      *  Sets the Cache usage.
      *  @deprecated Use {@link #setRecompileScripts} and/or {@link #setRerunTasks} instead.
-     * */
+      */
     @Deprecated
     public void setCacheUsage(CacheUsage cacheUsage) {
         this.cacheUsage = cacheUsage;
@@ -390,7 +397,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * Returns task optimization disabled flag.
      *
      * @deprecated Use {@link #isRerunTasks} instead.
-     * */
+      */
     @Deprecated
     public boolean isNoOpt() {
         return rerunTasks;
@@ -400,7 +407,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     * Get task optimization disabled.
     *
     * @param noOpt The boolean value for disabling task optimization.
-    * @deprecated Use {@link #setRefreshDependencies(boolean)} instead.
+    * @deprecated Use {@link #setRerunTasks(boolean)} instead.
     */
     @Deprecated
     public void setNoOpt(boolean noOpt) {
@@ -506,6 +513,26 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     }
 
     /**
+     * Sets the project path to use to select the default project. Use null to use the default criteria for selecting the default project.
+     *
+     * @param projectPath The project path. May be null.
+     */
+    @Incubating
+    public void setProjectPath(String projectPath) {
+        this.projectPath = projectPath;
+    }
+
+    /**
+     * Returns the project path to use to select the default project.
+     *
+     * @return The project path. May be null when the project path is not used to select the default project.
+     */
+    @Incubating
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    /**
      * Specifies if a profile report should be generated.
      *
      * @param profile true if a profile report should be generated
@@ -626,7 +653,6 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * @see #getParallelThreadCount()
      */
     public void setParallelThreadCount(int parallelThreadCount) {
-        this.parallelThreadCountConfigured = true;
         this.parallelThreadCount = parallelThreadCount;
     }
 
@@ -638,14 +664,6 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         return configureOnDemand;
     }
 
-    @Incubating
-    public boolean isParallelThreadCountConfigured() {
-        //This is not beautiful. As the number of gradle properties grows we may something like:
-        //1. Make StartParameter an interface
-        //2. StartParameter (StartParameterInternal) needs to inform if certain property was configured or not
-        return parallelThreadCountConfigured;
-    }
-
     @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 af71a38..6e7775e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/TaskExecutionLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/TaskExecutionLogger.java
@@ -21,6 +21,7 @@ import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionListener;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.tasks.TaskState;
+import org.gradle.internal.progress.LoggerProvider;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 
@@ -31,18 +32,20 @@ import java.util.Map;
  * A listener which logs the execution of tasks.
  */
 public class TaskExecutionLogger implements TaskExecutionListener {
-    // 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;
+    private LoggerProvider parentLoggerPovider;
 
-    public TaskExecutionLogger(ProgressLoggerFactory progressLoggerFactory) {
+    public TaskExecutionLogger(ProgressLoggerFactory progressLoggerFactory, LoggerProvider parentLoggerPovider) {
         this.progressLoggerFactory = progressLoggerFactory;
+        this.parentLoggerPovider = parentLoggerPovider;
     }
 
     public void beforeExecute(Task task) {
         assert !currentTasks.containsKey(task);
 
-        ProgressLogger currentTask = progressLoggerFactory.newOperation(TaskExecutionLogger.class);
+        ProgressLogger currentTask = progressLoggerFactory.newOperation(TaskExecutionLogger.class, parentLoggerPovider.getLogger());
         String displayName = getDisplayName(task);
         currentTask.setDescription(String.format("Execute %s", displayName));
         currentTask.setShortDescription(displayName);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/CircularReferenceException.java b/subprojects/core/src/main/groovy/org/gradle/api/CircularReferenceException.java
index 34b7d5c..a7f9244 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/CircularReferenceException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/CircularReferenceException.java
@@ -19,8 +19,6 @@ package org.gradle.api;
 /**
  * <p>A <code>CircularReferenceException</code> is thrown if circular references exists between tasks, the project
  * evaluation order or the project dependsOn order.</p>
- *
- * @author Hans Dockter
  */
 public class CircularReferenceException extends GradleException {
     public CircularReferenceException(String message) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java b/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java
index e34301e..c41c01e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java
@@ -16,24 +16,12 @@
 
 package org.gradle.api;
 
-import groovy.lang.GroovyObject;
-import org.codehaus.groovy.runtime.InvokerHelper;
 import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.NoConventionMapping;
 
 /**
  * {@code DefaultTask} is the standard {@link Task} implementation. You can extend this to implement your own task types.
- *
- * @author Hans Dockter
  */
 @NoConventionMapping
 public class DefaultTask extends AbstractTask {
-    public DefaultTask() {
-        if (this instanceof GroovyObject) {
-            GroovyObject groovyObject = (GroovyObject) this;
-            if (groovyObject.getMetaClass() == null) {
-                groovyObject.setMetaClass(InvokerHelper.getMetaClass(getClass()));
-            }
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
index 5df7fe2..39ebe9c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api;
 
+import groovy.lang.Closure;
+
 /**
  * A {@link org.gradle.api.PolymorphicDomainObjectContainer} that can be extended at runtime to
  * create elements of new types.
@@ -34,4 +36,30 @@ public interface ExtensiblePolymorphicDomainObjectContainer<T> extends Polymorph
      * @throws IllegalArgumentException if the specified type is not a subtype of the container element type
      */
     public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory);
+
+    /**
+     * Registers a factory for creating elements of the specified type.
+     * Typically, the specified type is an interface type.
+     *
+     * @param type the type of objects created by the factory
+     * @param factory the factory to register
+     * @param <U> the type of objects created by the factory
+     *
+     * @throws IllegalArgumentException if the specified type is not a subtype of the container element type
+     */
+    public <U extends T> void registerFactory(Class<U> type, final Closure<? extends U> factory);
+
+    /**
+     * Registers a binding from the specified "public" domain object type to the specified implementation type.
+     * Whenever the container is asked to create an element with the binding's public type, it will instantiate
+     * the binding's implementation type. If the implementation type has a constructor annotated with
+     * {@link javax.inject.Inject}, its arguments will be injected.
+     *
+     * <p>In general, registering a binding is preferable over implementing and registering a factory.
+     *
+     * @param type a public domain object type
+     * @param implementationType the corresponding implementation type
+     * @param <U> a public domain object type
+     */
+    public <U extends T> void registerBinding(Class<U> type, final Class<? extends U> implementationType);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/GradleScriptException.java b/subprojects/core/src/main/groovy/org/gradle/api/GradleScriptException.java
index 544a249..2a7d34f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/GradleScriptException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/GradleScriptException.java
@@ -15,13 +15,11 @@
  */
 package org.gradle.api;
 
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * <p>A <code>GradleScriptException</code> is thrown when an exception occurs in the compilation or execution of a
  * script.</p>
- *
- * @author Hans Dockter
  */
 @Contextual
 public class GradleScriptException extends GradleException {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java b/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
index 8d965a8..569babf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
@@ -17,8 +17,6 @@ package org.gradle.api;
 
 /**
  * This exceptions is thrown, if a dependency is declared with a illegal notation.
- * 
- * @author Hans Dockter
  */
 public class IllegalDependencyNotation extends GradleException {
     public IllegalDependencyNotation() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java b/subprojects/core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java
index 412c520..8391b3f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java
@@ -19,8 +19,9 @@ package org.gradle.api;
  * A <code>IllegalOperationAtExecutionTimeException</code> is thrown if you try to trigger an operation at runtime,
  * which is only allowed at configuration time.
  *
- * @author Hans Dockter
+ * @deprecated No replacement
  */
+ at Deprecated
 public class IllegalOperationAtExecutionTimeException extends InvalidUserDataException {
     public IllegalOperationAtExecutionTimeException(String message) {
         super(message);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java b/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java
index 6d63a5f..90f54e2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api;
 
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * A <code>InvalidUserCodeException</code> is thrown when user-provided code cannot be executed.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserDataException.java b/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserDataException.java
index f166d61..0c5865f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserDataException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserDataException.java
@@ -18,8 +18,6 @@ package org.gradle.api;
 
 /**
  * A <code>InvalidUserDataException</code> is thrown, if a user is providing illegal data for the build.
- *
- * @author Hans Dockter
  */
 public class InvalidUserDataException extends GradleException {
     public InvalidUserDataException() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
index aabc1a6..d5d5e82 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
@@ -21,6 +21,7 @@ import org.gradle.api.specs.Spec;
 import java.util.Collection;
 import java.util.List;
 import java.util.SortedMap;
+import java.util.SortedSet;
 
 /**
  * <p>A {@code NamedDomainObjectCollection} represents a collection of domain objects that have an inherent, constant, name.</p>
@@ -102,6 +103,15 @@ public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T
     SortedMap<String, T> getAsMap();
 
     /**
+     * <p>Returns the names of the objects in this collection as a Set of Strings.</p>
+     *
+     * <p>The set of names is in <em>natural ordering</em>.</p>
+     *
+     * @return The names. Returns an empty set if this collection is empty.
+     */
+    SortedSet<String> getNames();
+
+    /**
      * Locates an object by name, returning null if there is no such object.
      *
      * @param name The object name
@@ -120,7 +130,7 @@ public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T
 
     /**
      * Locates an object by name, failing if there is no such object. The given configure closure is executed against
-     * the object before it is returned from this method. The object is passed to the closure as it's delegate.
+     * the object before it is returned from this method. The object is passed to the closure as its delegate.
      *
      * @param name The object name
      * @param configureClosure The closure to use to configure the object.
@@ -179,4 +189,4 @@ public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T
      */
     NamedDomainObjectCollection<T> matching(Closure spec);
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
index fa836d8..a36d62a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
@@ -47,7 +47,6 @@ public interface NamedDomainObjectContainer<T> extends NamedDomainObjectSet<T>,
      * @param name The name to find or assign to the created object
      * @return The found or created object. Never null.
      */
-    @Incubating
     T maybeCreate(String name);
 
     /**
@@ -61,6 +60,16 @@ public interface NamedDomainObjectContainer<T> extends NamedDomainObjectSet<T>,
     T create(String name, Closure configureClosure) throws InvalidUserDataException;
 
     /**
+     * Creates a new item with the given name, adding it to this container, then configuring it with the given action.
+     *
+     * @param name The name to assign to the created object
+     * @param configureAction The action to configure the created object with
+     * @return The created object. Never null.
+     * @throws InvalidUserDataException if an object with the given name already exists in this container.
+     */
+    T create(String name, Action<? super T> configureAction) throws InvalidUserDataException;
+
+    /**
      * <p>Allows the container to be configured, creating missing objects as they are referenced.</p>
      * 
      * <p>TODO: example usage</p>
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NonExtensible.java b/subprojects/core/src/main/groovy/org/gradle/api/NonExtensible.java
new file mode 100644
index 0000000..d927381
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NonExtensible.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.*;
+
+/**
+ * Indicates that the type, when DSL enhanced, does not support extensibility.
+ * <p>
+ * This means that it will not be enhanced with {@link org.gradle.api.plugins.ExtensionAware}.
+ */
+ at Incubating
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Inherited
+ at Target({ElementType.TYPE})
+public @interface NonExtensible {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Plugin.java b/subprojects/core/src/main/groovy/org/gradle/api/Plugin.java
index 67e207f..6ed1d38 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Plugin.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Plugin.java
@@ -20,8 +20,7 @@ package org.gradle.api;
  * Usually, this target object is a {@link org.gradle.api.Project}, but plugins can be applied to any type of
  * objects.</p>
  *
- * @author Hans Dockter
- * @param <T> The type of object which this plugin can configure. 
+ * @param <T> The type of object which this plugin can configure.
  */
 public interface Plugin<T> {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java
index cf47a0e..8e85746 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java
@@ -39,6 +39,21 @@ public interface PolymorphicDomainObjectContainer<T> extends NamedDomainObjectCo
     <U extends T> U create(String name, Class<U> type) throws InvalidUserDataException;
 
     /**
+     * Looks for an item with the given name and type, creating and adding it to this container if it does not exist.
+     *
+     * @param name the name of the domain object to be created
+     * @param type the type of the domain object to be created
+     * @param <U> the type of the domain object to be created
+     *
+     * @return the found or created domain object, never <code>null</code>.
+     *
+     * @throws InvalidUserDataException if the container does not support creating a domain object with the specified type
+     * @throws ClassCastException if a domain object with the specified name exists with a different type
+     */
+    @Incubating
+    <U extends T> U maybeCreate(String name, Class<U> type) throws InvalidUserDataException;
+
+    /**
      * Creates a domain object with the specified name and type, adds it to the container, and configures
      * it with the specified action.
      *
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 8d97a1e..ecde8ef 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -28,14 +28,11 @@ 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.internal.HasInternalProtocol;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.LoggingManager;
-import org.gradle.api.plugins.Convention;
-import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.api.plugins.ExtensionContainer;
-import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.plugins.*;
 import org.gradle.api.resources.ResourceHandler;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.tasks.WorkResult;
@@ -190,11 +187,9 @@ import java.util.Set;
  * <li>The parent project, recursively up to the root project.</li>
  *
  * </ul>
- *
- * @author Hans Dockter
  */
 @HasInternalProtocol
-public interface Project extends Comparable<Project>, ExtensionAware {
+public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
     /**
      * The default project build file name.
      */
@@ -973,7 +968,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * </target>
      * </pre>
      *
-     * Here's how it would look like in gradle. Observe how the ant xml is represented in groovy by the ant builder
+     * Here's how it would look like in gradle. Observe how the ant XML is represented in groovy by the ant builder
      * <pre autoTested=''>
      * task printChecksum {
      *   doLast {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/ProjectConfigurationException.java b/subprojects/core/src/main/groovy/org/gradle/api/ProjectConfigurationException.java
new file mode 100644
index 0000000..d9e3b6d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/ProjectConfigurationException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api;
+
+import org.gradle.internal.exceptions.Contextual;
+
+/**
+ * Indicates a problem that occurs during project configuration (evaluation) phase.
+ */
+ at Contextual
+public class ProjectConfigurationException extends GradleException {
+    public ProjectConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java b/subprojects/core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java
index f91cdff..f5e9f74 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java
@@ -19,8 +19,6 @@ package org.gradle.api;
  * <p>An {@code ProjectEvaluationListener} is notified when a project is evaluated. You add can add an {@code
  * ProjectEvaluationListener} to a {@link org.gradle.api.invocation.Gradle} using {@link
  * org.gradle.api.invocation.Gradle#addProjectEvaluationListener(ProjectEvaluationListener)}.</p>
- *
- * @author Hans Dockter
  */
 public interface ProjectEvaluationListener {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/ProjectState.java b/subprojects/core/src/main/groovy/org/gradle/api/ProjectState.java
index 5fb4a1d..1027dfe 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/ProjectState.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/ProjectState.java
@@ -16,9 +16,12 @@
 
 package org.gradle.api;
 
+import org.gradle.internal.HasInternalProtocol;
+
 /**
  * {@code ProjectState} provides information about the execution state of a project.
  */
+ at HasInternalProtocol
 public interface ProjectState {
     /**
      * <p>Returns true if this project has been evaluated.</p>
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Rule.java b/subprojects/core/src/main/groovy/org/gradle/api/Rule.java
index b589e76..34d6e91 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Rule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Rule.java
@@ -18,8 +18,6 @@ package org.gradle.api;
 /**
  * <p>A {@code Rule} represents some action to perform when an unknown domain object is referenced. The rule can use the
  * domain object name to add an implicit domain object.</p>
- *
- * @author Hans Dockter
  */
 public interface Rule {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Script.java b/subprojects/core/src/main/groovy/org/gradle/api/Script.java
index abe3980..5c7722c 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/Script.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Script.java
@@ -366,4 +366,5 @@ public interface Script {
      * @return Returned instance contains various resource-specific utility methods.
      */
     ResourceHandler getResources();
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Task.java b/subprojects/core/src/main/groovy/org/gradle/api/Task.java
index 6d24910..3f64d82 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Task.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Task.java
@@ -69,12 +69,16 @@ import java.util.Set;
  * next task by throwing a {@link org.gradle.api.tasks.StopExecutionException}. Using these exceptions allows you to
  * have precondition actions which skip execution of the task, or part of the task, if not true.</p>
  *
- * <a name="dependencies"/><h3>Dependencies</h3>
+ * <a name="dependencies"/><h3>Task Dependencies and Task Ordering</h3>
  *
- * <p>A task may have dependencies on other tasks. Gradle ensures that tasks are executed in dependency order, so that
- * the dependencies of a task are executed before the task is executed.  You can add dependencies to a task using {@link
- * #dependsOn(Object...)} or {@link #setDependsOn(Iterable)}.  You can add objects of any of the following types as a
- * dependency:</p>
+ * <p>A task may have dependencies on other tasks or might be scheduled to always run after another task.
+ * Gradle ensures that all task dependencies and ordering rules are honored when executing tasks, so that the task is executed after
+ * all of its dependencies and any "must run after" tasks have been executed.</p>
+ *
+ * <p>Dependencies to a task are controlled using {@link #dependsOn(Object...)} or {@link #setDependsOn(Iterable)},
+ * and {@link #mustRunAfter(Object...)}, {@link #setMustRunAfter(Iterable)}, {@link #shouldRunAfter(Object...)} and
+ * {@link #setShouldRunAfter(Iterable)} are used to specify ordering between tasks. You can use objects of any of
+ * the following types to specify dependencies and ordering:</p>
  *
  * <ul>
  *
@@ -127,8 +131,6 @@ import java.util.Set;
  * <h4>Dynamic Methods</h4>
  *
  * <p>A {@link Plugin} may add methods to a {@code Task} using its {@link Convention} object.</p>
- *
- * @author Hans Dockter
  */
 public interface Task extends Comparable<Task>, ExtensionAware {
     public static final String TASK_NAME = "name";
@@ -538,5 +540,149 @@ public interface Task extends Comparable<Task>, ExtensionAware {
      * @return The directory. Never returns null. The directory will already exist.
      */
     File getTemporaryDir();
+
+    /**
+     * <p>Specifies that this task must run after all of the supplied tasks.</p>
+     *
+     * <pre autoTested="true">
+     * task taskY {
+     *     mustRunAfter "taskX"
+     * }
+     * </pre>
+     *
+     * <p>For each supplied task, this action adds a task 'ordering', and does not specify a 'dependency' between the tasks.
+     * As such, it is still possible to execute 'taskY' without first executing the 'taskX' in the example.</p>
+     *
+     * <p>See <a href="#dependencies">here</a> for a description of the types of objects which can be used to specify
+     * an ordering relationship.</p>
+     *
+     * @param paths The tasks this task must run after.
+     *
+     * @return the task object this method is applied to
+     */
+    @Incubating
+    Task mustRunAfter(Object... paths);
+
+    /**
+     * <p>Specifies the set of tasks that this task must run after.</p>
+     *
+     * <pre autoTested="true">
+     * task taskY {
+     *     mustRunAfter = ["taskX1", "taskX2"]
+     * }
+     * </pre>
+     *
+     * <p>For each supplied task, this action adds a task 'ordering', and does not specify a 'dependency' between the tasks.
+     * As such, it is still possible to execute 'taskY' without first executing the 'taskX' in the example.</p>
+     *
+     * <p>See <a href="#dependencies">here</a> for a description of the types of objects which can be used to specify
+     * an ordering relationship.</p>
+     *
+     * @param mustRunAfter The set of task paths this task must run after.
+     */
+    @Incubating
+    void setMustRunAfter(Iterable<?> mustRunAfter);
+
+    /**
+     * <p>Returns tasks that this task must run after.</p>
+     *
+     * @return The tasks that this task must run after. Returns an empty set if this task has no tasks it must run after.
+     */
+    @Incubating
+    TaskDependency getMustRunAfter();
+
+    /**
+     * <p>Adds the given finalizer tasks for this task.</p>
+     *
+     * <pre autoTested="true">
+     * task taskY {
+     *     finalizedBy "taskX"
+     * }
+     * </pre>
+     *
+     * <p>See <a href="#dependencies">here</a> for a description of the types of objects which can be used to specify
+     * a finalizer task.</p>
+     *
+     * @param paths The tasks that finalize this task.
+     *
+     * @return the task object this method is applied to
+     */
+    @Incubating
+    Task finalizedBy(Object... paths);
+
+    /**
+     * <p>Specifies the set of finalizer tasks for this task.</p>
+     *
+     * <pre autoTested="true">
+     * task taskY {
+     *     finalizedBy = ["taskX1", "taskX2"]
+     * }
+     * </pre>
+     *
+     * <p>See <a href="#dependencies">here</a> for a description of the types of objects which can be used to specify
+     * a finalizer task.</p>
+     *
+     * @param finalizedBy The tasks that finalize this task.
+     */
+    @Incubating
+    void setFinalizedBy(Iterable<?> finalizedBy);
+
+    /**
+     * <p>Returns tasks that finalize this task.</p>
+     *
+     * @return The tasks that finalize this task. Returns an empty set if there are no finalising tasks for this task.
+     */
+    @Incubating
+    TaskDependency getFinalizedBy();
+
+    /**
+     * <p>Specifies that this task should run after all of the supplied tasks.</p>
+     *
+     * <pre autoTested="true">
+     * task taskY {
+     *     shouldRunAfter "taskX"
+     * }
+     * </pre>
+     *
+     * <p>For each supplied task, this action adds a task 'ordering', and does not specify a 'dependency' between the tasks.
+     * As such, it is still possible to execute 'taskY' without first executing the 'taskX' in the example.</p>
+     *
+     * <p>See <a href="#dependencies">here</a> for a description of the types of objects which can be used to specify
+     * an ordering relationship.</p>
+     *
+     * @param paths The tasks this task should run after.
+     *
+     * @return the task object this method is applied to
+     */
+    @Incubating
+    TaskDependency shouldRunAfter(Object... paths);
+
+    /**
+     * <p>Specifies the set of tasks that this task should run after.</p>
+     *
+     * <pre autoTested="true">
+     * task taskY {
+     *     shouldRunAfter = ["taskX1", "taskX2"]
+     * }
+     * </pre>
+     *
+     * <p>For each supplied task, this action adds a task 'ordering', and does not specify a 'dependency' between the tasks.
+     * As such, it is still possible to execute 'taskY' without first executing the 'taskX' in the example.</p>
+     *
+     * <p>See <a href="#dependencies">here</a> for a description of the types of objects which can be used to specify
+     * an ordering relationship.</p>
+     *
+     * @param shouldRunAfter The set of task paths this task should run after.
+     */
+    @Incubating
+    void setShouldRunAfter(Iterable<?> shouldRunAfter);
+
+    /**
+     * <p>Returns tasks that this task should run after.</p>
+     *
+     * @return The tasks that this task should run after. Returns an empty set if this task has no tasks it must run after.
+     */
+    @Incubating
+    TaskDependency getShouldRunAfter();
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/UnknownProjectException.java b/subprojects/core/src/main/groovy/org/gradle/api/UnknownProjectException.java
index 1653dbb..519213a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/UnknownProjectException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/UnknownProjectException.java
@@ -18,8 +18,6 @@ package org.gradle.api;
 
 /**
  * <p>An <code>UnknownProjectException</code> is thrown when a project referenced by path cannot be found.</p>
- *
- * @author Hans Dockter
  */
 public class UnknownProjectException extends UnknownDomainObjectException {
     public UnknownProjectException(String message) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/UnknownTaskException.java b/subprojects/core/src/main/groovy/org/gradle/api/UnknownTaskException.java
index 70aa432..2a87031 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/UnknownTaskException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/UnknownTaskException.java
@@ -18,8 +18,6 @@ package org.gradle.api;
 
 /**
  * <p>An <code>UnknownTaskException</code> is thrown when a task referenced by path cannot be found.</p>
- *
- * @author Hans Dockter
  */
 public class UnknownTaskException extends UnknownDomainObjectException {
     public UnknownTaskException(String message) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java
index 7f2e961..4958ab0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java
@@ -20,8 +20,6 @@ import org.w3c.dom.Element;
 
 /**
  * Provides various ways to access the content of an XML document.
- *
- * @author Hans Dockter
  */
 public interface XmlProvider {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java
index 85cc620..cd02402 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java
@@ -25,13 +25,13 @@ public interface ArtifactIdentifier {
     ModuleVersionIdentifier getModuleVersionIdentifier();
 
     /**
-     * Returns the name of the dependency artifact.
+     * Returns the name of this artifact.
      */
     String getName();
 
     /**
-     * Returns the type of the dependency artifact. Often the type is the same as the extension,
-     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * Returns the type of this artifact. Often the type is the same as the extension,
+     * but sometimes this is not the case. For example for an ivy XML module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
      * @see #getExtension()
@@ -39,8 +39,8 @@ public interface ArtifactIdentifier {
     String getType();
 
     /**
-     * Returns the extension of this dependency artifact. Often the extension is the same as the type,
-     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * Returns the extension of this artifact. Often the extension is the same as the type,
+     * but sometimes this is not the case. For example for an ivy XML module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
      * @see #getType()
@@ -48,7 +48,7 @@ public interface ArtifactIdentifier {
     String getExtension();
 
     /**
-     * Returns the classifier of this dependency artifact.
+     * Returns the classifier of this artifact, if any.
      */
     String getClassifier();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
index 14cbc80..9640e79 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
@@ -33,33 +33,37 @@ import java.util.List;
  * <p>The resolvers in a container are accessible as read-only properties of the container, using the name of the
  * resolver as the property name. For example:</p>
  *
- * <pre>
- * resolvers.addLast(name: 'myResolver')
- * resolvers.myResolver.url = 'some-url'
+ * <pre autoTested=''>
+ * repositories.maven { name 'myResolver' }
+ * repositories.myResolver.url = 'some-url'
  * </pre>
  *
  * <p>A dynamic method is added for each resolver which takes a configuration closure. This is equivalent to calling
  * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
  *
- * <pre>
- * resolvers.addLast(name: 'myResolver')
- * resolvers.myResolver {
+ * <pre autoTested=''>
+ * repositories.maven { name 'myResolver' }
+ * repositories.myResolver {
  *     url 'some-url'
  * }
  * </pre>
- *
- * @author Hans Dockter
  */
 public interface ArtifactRepositoryContainer extends NamedDomainObjectList<ArtifactRepository>, Configurable<ArtifactRepositoryContainer> {
     String DEFAULT_MAVEN_CENTRAL_REPO_NAME = "MavenRepo";
     String DEFAULT_MAVEN_LOCAL_REPO_NAME = "MavenLocal";
     String MAVEN_CENTRAL_URL = "http://repo1.maven.org/maven2/";
+    @Deprecated
     String MAVEN_REPO_PATTERN = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]";
+    @Deprecated
     String DEFAULT_CACHE_ARTIFACT_PATTERN
             = "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])";
+    @Deprecated
     String DEFAULT_CACHE_IVY_PATTERN = "[organisation]/[module](/[branch])/ivy-[revision].xml";
+    @Deprecated
     String INTERNAL_REPOSITORY_NAME = "internal-repository";
+    @Deprecated
     String RESOLVER_NAME = "name";
+    @Deprecated
     String RESOLVER_URL = "url";
 
     /**
@@ -87,7 +91,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * Adds a repository to this container, at the end of the repository sequence.
      *
      * @param resolver The repository to add, represented as an Ivy {@link DependencyResolver}.
+     * @deprecated Use one of the repository methods on {@link org.gradle.api.artifacts.dsl.RepositoryHandler} or {@link #add(ArtifactRepository)} instead.
      */
+    @Deprecated
     boolean add(DependencyResolver resolver);
 
     /**
@@ -95,7 +101,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      *
      * @param resolver The repository to add, represented as an Ivy {@link DependencyResolver}.
      * @param configureClosure The closure to use to configure the repository.
+     * @deprecated Use one of the repository methods on {@link org.gradle.api.artifacts.dsl.RepositoryHandler} or {@link #add(ArtifactRepository)} instead.
      */
+    @Deprecated
     boolean add(DependencyResolver resolver, Closure configureClosure);
 
     /**
@@ -104,12 +112,12 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      *
      * <ul>
      *
-     * <li>A String. This is treated as a URL, and used to create a maven repository.</li>
+     * <li>A String. This is treated as a URL, and used to create a Maven repository.</li>
      *
-     * <li>A map. This is used to create a maven maven repository. The map must contain an {@value #RESOLVER_NAME} entry and a
+     * <li>A map. This is used to create a Maven repository. The map must contain an {@value #RESOLVER_NAME} entry and a
      * {@value #RESOLVER_URL} entry.</li>
      *
-     * <li>A {@link org.apache.ivy.plugins.resolver.DependencyResolver}.</li>
+     * <li>A {@link DependencyResolver}.</li>
      *
      * <li>A {@link ArtifactRepository}.</li>
      *
@@ -144,7 +152,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * @return The added resolver.
      * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
      * @throws UnknownRepositoryException when the given next resolver does not exist in this container.
+     * @deprecated No replacement
      */
+    @Deprecated
     DependencyResolver addBefore(Object userDescription, String nextResolver) throws InvalidUserDataException;
 
     /**
@@ -157,7 +167,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * @return The added resolver.
      * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
      * @throws UnknownRepositoryException when the given next resolver does not exist in this container.
+     * @deprecated No replacement
      */
+    @Deprecated
     DependencyResolver addBefore(Object userDescription, String nextResolver, Closure configureClosure)
             throws InvalidUserDataException;
 
@@ -169,7 +181,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * @return The added resolver.
      * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
      * @throws UnknownRepositoryException when the given previous resolver does not exist in this container.
+     * @deprecated No replacement
      */
+    @Deprecated
     DependencyResolver addAfter(Object userDescription, String previousResolver) throws InvalidUserDataException;
 
     /**
@@ -182,7 +196,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * @return The added resolver.
      * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
      * @throws UnknownRepositoryException when the given previous resolver does not exist in this container.
+     * @deprecated No replacement
      */
+    @Deprecated
     DependencyResolver addAfter(Object userDescription, String previousResolver, Closure configureClosure)
             throws InvalidUserDataException;
 
@@ -192,7 +208,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
      * @return The added resolver.
      * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @deprecated Use {@link #addFirst(ArtifactRepository)} instead.
      */
+    @Deprecated
     DependencyResolver addFirst(Object userDescription) throws InvalidUserDataException;
 
     /**
@@ -203,7 +221,9 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * @param configureClosure The closure to use to configure the resolver.
      * @return The added resolver.
      * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @deprecated Use {@link #addFirst(ArtifactRepository)} instead.
      */
+    @Deprecated
     DependencyResolver addFirst(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
index e4869e6..198c13d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
@@ -22,8 +22,6 @@ import java.util.Set;
  * artifact or you declare a module dependency that depends on a module descriptor in a repository. With
  * a client module you can declare a module dependency without the need of a module descriptor in a
  * remote repository.
- *
- * @author Hans Dockter
  */
 public interface ClientModule extends ExternalDependency {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentMetadataDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentMetadataDetails.java
new file mode 100644
index 0000000..2dd34c3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentMetadataDetails.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.artifacts;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NonExtensible;
+
+import java.util.List;
+
+/**
+ * Describes a resolved component's metadata, which typically originates from
+ * a component descriptor (Ivy file, Maven POM). Some parts of the metadata can be changed
+ * via metadata rules (see {@link org.gradle.api.artifacts.dsl.ComponentMetadataHandler}.
+ *
+ * @since 1.8
+ */
+ at Incubating
+ at NonExtensible
+public interface ComponentMetadataDetails {
+    /**
+     * Returns the identifier of the component.
+     *
+     * @return the identifier of the component.
+     */
+    ModuleVersionIdentifier getId();
+
+    /**
+     * Tells whether the component is changing or immutable.
+     *
+     * @return whether the component is changing or immutable.
+     */
+    boolean isChanging();
+
+    /**
+     * Returns the status of the component. Must
+     * match one of the values in {@link #getStatusScheme()}.
+     *
+     * @return the status of the component
+     */
+    String getStatus();
+
+    /**
+     * Returns the status scheme of the component. Values are
+     * ordered from least to most mature status.
+     * Defaults to {@code ["integration", "milestone", "release"]}.
+     *
+     * @return the status scheme of the component
+     */
+    List<String> getStatusScheme();
+
+    /**
+     * Sets whether the component is changing or immutable.
+     *
+     * @param changing whether the component is changing or immutable
+     */
+    void setChanging(boolean changing);
+
+    /**
+     * Sets the status of the component. Must
+     * match one of the values in {@link #getStatusScheme()}.
+     *
+     * @param status the status of the component
+     */
+    void setStatus(String status);
+
+    /**
+     * Sets the status scheme of the component. Values are ordered
+     * from least to most mature status.
+     *
+     * @param statusScheme the status scheme of the component
+     */
+    void setStatusScheme(List<String> statusScheme);
+}
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 5e774f2..be8aab1 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,7 +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.internal.HasInternalProtocol;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 
@@ -257,7 +257,7 @@ public interface Configuration extends FileCollection {
 
     /**
      * Returns a {@code TaskDependency} object containing all required dependencies to build the internal dependencies
-     * (e.g. project dependencies) belonging to this configuration or to one of its super configurations.
+     * (e.g.<!-- --> project dependencies) belonging to this configuration or to one of its super configurations.
      *
      * @return a TaskDependency object
      */
@@ -334,13 +334,13 @@ public interface Configuration extends FileCollection {
     /**
      * Returns the incoming dependencies of this configuration.
      *
-     * @return The incoming dependencies of this configuration. Never null.
+     * @return The incoming dependencies of this configuration. Never {@code null}.
      */
     ResolvableDependencies getIncoming();
 
     /**
      * Creates a copy of this configuration that only contains the dependencies directly in this configuration
-     * (without contributions from superconfigurations).  The new configuation will be in the
+     * (without contributions from superconfigurations).  The new configuration will be in the
      * UNRESOLVED state, but will retain all other attributes of this configuration except superconfigurations.
      * {@link #getHierarchy()} for the copy will not include any superconfigurations.
      * @return copy of this configuration
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 db1890f..c30e0ea 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,7 +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;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * <p>A {@code ConfigurationContainer} is responsible for declaring and managing configurations. See also {@link Configuration}.</p>
@@ -29,16 +29,16 @@ import org.gradle.api.internal.HasInternalProtocol;
  * <p>The configurations in a container are accessible as read-only properties of the container, using the name of the
  * configuration as the property name. For example:</p>
  *
- * <pre>
- * configurations.add('myConfiguration')
+ * <pre autoTested='true'>
+ * configurations.create('myConfiguration')
  * configurations.myConfiguration.transitive = false
  * </pre>
  *
  * <p>A dynamic method is added for each configuration which takes a configuration closure. This is equivalent to
  * calling {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
  *
- * <pre>
- * configurations.add('myConfiguration')
+ * <pre autoTested='true'>
+ * configurations.create('myConfiguration')
  * configurations.myConfiguration {
  *     transitive = false
  * }
@@ -79,8 +79,6 @@ import org.gradle.api.internal.HasInternalProtocol;
  * </pre>
  *
  * Examples on configuring the <b>resolution strategy</b> - see docs for {@link ResolutionStrategy}
- *
- * @author Hans Dockter
  */
 @HasInternalProtocol
 public interface ConfigurationContainer extends NamedDomainObjectContainer<Configuration> {
@@ -105,7 +103,9 @@ public interface ConfigurationContainer extends NamedDomainObjectContainer<Confi
      * @param name The name of the new configuration.
      * @return The newly added configuration.
      * @throws InvalidUserDataException when a configuration with the given name already exists in this container.
+     * @deprecated use {@link #create(String)} instead
      */
+    @Deprecated
     Configuration add(String name) throws InvalidUserDataException;
 
     /**
@@ -116,7 +116,9 @@ public interface ConfigurationContainer extends NamedDomainObjectContainer<Confi
      * @param configureClosure The closure to use to configure the configuration.
      * @return The newly added configuration.
      * @throws InvalidUserDataException when a configuration with the given name already exists in this container.
+     * @deprecated use {@link #create(String, groovy.lang.Closure)} instead
      */
+    @Deprecated
     Configuration add(String name, Closure configureClosure) throws InvalidUserDataException;
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Dependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Dependency.java
index bdcf798..267a13c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Dependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Dependency.java
@@ -17,14 +17,11 @@ package org.gradle.api.artifacts;
 
 /**
  * A {@code Dependency} represents a dependency on the artifacts from a particular source. A source can be an Ivy
- * module, a Maven pom, another Gradle project, a collection of Files, etc... A source can have zero or more artifacts.
- *
- * @author Hans Dockter
+ * module, a Maven POM, another Gradle project, a collection of Files, etc... A source can have zero or more artifacts.
  */
 public interface Dependency {
     String DEFAULT_CONFIGURATION = "default";
     String ARCHIVES_CONFIGURATION = "archives";
-    // todo Remove to ivy layer in 1.0
     String CLASSIFIER = "m:classifier";
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java
index 9e8c9c9..4c89482 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java
@@ -18,8 +18,6 @@ package org.gradle.api.artifacts;
 /**
  * <p>An {@code Artifact} represents an artifact included in a {@link org.gradle.api.artifacts.Dependency}.</p>
  * An artifact is an (immutable) value object.
- *
- * @author Hans Dockter
  */
 public interface DependencyArtifact {
     String DEFAULT_TYPE = "jar";
@@ -31,7 +29,7 @@ public interface DependencyArtifact {
 
     /**
      * Returns the type of the dependency artifact. Often the type is the same as the extension,
-     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * but sometimes this is not the case. For example for an ivy XML module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
      * @see #getExtension() 
@@ -40,7 +38,7 @@ public interface DependencyArtifact {
 
     /**
      * Returns the extension of this dependency artifact. Often the extension is the same as the type,
-     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * but sometimes this is not the case. For example for an ivy XML module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
      * @see #getType() 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
index 9262a7a..bbd4f38 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
@@ -20,8 +20,6 @@ import java.util.Map;
 /**
  * An {@code ExcludeRule} is used to describe transitive dependencies that should be excluded when resolving
  * dependencies.
- *
- * @author Hans Dockter
  */
 public interface ExcludeRule {
     String GROUP_KEY = "group";
@@ -29,7 +27,7 @@ public interface ExcludeRule {
 
     /**
      * The exact name of the organization or group that should be excluded.
-     * */
+      */
     String getGroup();
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java
index 58e0425..aef1b20 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java
@@ -20,8 +20,6 @@ import java.util.Set;
 
 /**
  * <p>A container for adding exclude rules for dependencies.</p>
- *
- * @author Hans Dockter
  */
 public interface ExcludeRuleContainer {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
index f403492..0a68c02 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
@@ -17,8 +17,6 @@ package org.gradle.api.artifacts;
 
 /**
  * <p>An {@code ExternalDependency} is a {@link Dependency} on a source outside the current project hierarchy.</p>
- *
- * @author Hans Dockter
  */
 public interface ExternalDependency extends ModuleDependency, ModuleVersionSelector {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
index 356be1c..8759431 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
@@ -17,8 +17,6 @@ package org.gradle.api.artifacts;
 
 /**
  * <p>A {@code ModuleDependency} is a {@link Dependency} on a module outside the current project hierarchy.</p>
- *
- * @author Hans Dockter
  */
 public interface ExternalModuleDependency extends ExternalDependency {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Module.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Module.java
index 484ca60..a771cf1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Module.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Module.java
@@ -15,12 +15,13 @@
  */
 package org.gradle.api.artifacts;
 
+import org.gradle.internal.HasInternalProtocol;
+
 /**
  * <p>A {@code Module} represents the meta-information about a project which should be used when publishing the
  * module.</p>
- *
- * @author Hans Dockter
  */
+ at HasInternalProtocol
 public interface Module {
     public static final String DEFAULT_STATUS = "integration";
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java
index 364da14..75eb561 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java
@@ -16,12 +16,12 @@
 package org.gradle.api.artifacts;
 
 import org.gradle.api.Project;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * <p>A {@code ProjectDependency} is a {@link Dependency} on another project in the current project hierarchy.</p>
- *
- * @author Hans Dockter
  */
+ at HasInternalProtocol
 public interface ProjectDependency extends ModuleDependency, SelfResolvingDependency {
     /**
      * Returns the project associated with this project dependency.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
index 0bc99d4..117285b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
@@ -23,8 +23,6 @@ import java.util.Date;
 
 /**
  * <p>A {@code PublishArtifact} is an artifact produced by a project.</p>
- *
- * @author Hans Dockter
  */
 public interface PublishArtifact extends Buildable {
     /**
@@ -36,7 +34,7 @@ public interface PublishArtifact extends Buildable {
 
     /**
      * Returns the extension of this published artifact. Often the extension is the same as the type,
-     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * but sometimes this is not the case. For example for an ivy XML module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
      * @return The extension. Never null.
@@ -45,7 +43,7 @@ public interface PublishArtifact extends Buildable {
 
     /**
      * Returns the type of the published artifact. Often the type is the same as the extension,
-     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * but sometimes this is not the case. For example for an ivy XML module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
      * @return The type. Never null.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishException.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishException.java
index e37998e..974ccee 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishException.java
@@ -17,7 +17,7 @@
 package org.gradle.api.artifacts;
 
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * <p>A <code>PublishException</code> is thrown when a dependency configuration cannot be published for some reason.</p>
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
index 553395e..2810b24 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
@@ -16,13 +16,11 @@
 
 package org.gradle.api.artifacts;
 
-import org.gradle.api.internal.AbstractMultiCauseException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.AbstractMultiCauseException;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * <p>A <code>ResolveException</code> is thrown when a dependency configuration cannot be resolved for some reason.</p>
- *
- * @author Hans Dockter
  */
 @Contextual
 public class ResolveException extends AbstractMultiCauseException {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
index 486fa6f..13d50ce 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
@@ -19,8 +19,6 @@ import java.io.File;
 
 /**
  * Information about a resolved artifact.
- * 
- * @author Hans Dockter
  */
 public interface ResolvedArtifact {
     File getFile();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
index 2fc35c4..22fc196 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
@@ -19,8 +19,6 @@ import java.util.Set;
 
 /**
  * Information about a resolved dependency.
- *
- * @author Hans Dockter
  */
 public interface ResolvedDependency {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java
index 7908ced..120b6bc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java
@@ -19,8 +19,6 @@ import org.gradle.api.UnknownDomainObjectException;
 
 /**
  * An {@code UnknownRepositoryException} is thrown when a repository referenced by name cannot be found.
- *
- * @author Hans Dockter
  */
 public class UnknownRepositoryException extends UnknownDomainObjectException {
     public UnknownRepositoryException(String message) {
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 bd44899..1e7b72f 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
@@ -16,12 +16,14 @@
 package org.gradle.api.artifacts.cache;
 
 import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.ModuleIdentifier;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
+
+import java.util.Set;
 
 /**
  * Command methods for controlling dependency resolution via the DSL.
  */
 @Incubating
-public interface DependencyResolutionControl extends ResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> {
+public interface DependencyResolutionControl extends ResolutionControl<ModuleIdentifier, Set<ModuleVersionIdentifier>> {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ComponentIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ComponentIdentifier.java
new file mode 100644
index 0000000..1c3d8a2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ComponentIdentifier.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An opaque identifier for a component instance. There are various sub-interfaces that expose specific details
+ * about the identifier.
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface ComponentIdentifier {
+    /**
+     * Returns a human-consumable display name for this identifier.
+     *
+     * @return Component identifier display name
+     * @since 1.10
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ComponentSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ComponentSelector.java
new file mode 100644
index 0000000..6b77a7e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ComponentSelector.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Represents some opaque criteria used to select a component instance during dependency resolution. Various sub-interfaces
+ * expose specific details about the criteria.
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface ComponentSelector {
+    /**
+     * Returns a human-consumable display name for this selector.
+     *
+     * @return Display name
+     * @since 1.10
+     */
+    String getDisplayName();
+
+    /**
+     * Checks if selector matches component identifier.
+     *
+     * @param identifier Component identifier
+     * @return if this selector matches exactly the given component identifier.
+     * @since 1.10
+     */
+    boolean matchesStrictly(ComponentIdentifier identifier);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ModuleComponentIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ModuleComponentIdentifier.java
new file mode 100644
index 0000000..81ab384
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ModuleComponentIdentifier.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An identifier for a component instance which is available as a module version.
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface ModuleComponentIdentifier extends ComponentIdentifier {
+    /**
+     * The module group of the component.
+     *
+     * @return Component group
+     * @since 1.10
+     */
+    String getGroup();
+
+    /**
+     * The module name of the component.
+     *
+     * @return Component module
+     * @since 1.10
+     */
+    String getModule();
+
+    /**
+     * The module version of the component.
+     *
+     * @return Component version
+     * @since 1.10
+     */
+    String getVersion();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ModuleComponentSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ModuleComponentSelector.java
new file mode 100644
index 0000000..f485420
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ModuleComponentSelector.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Criteria for selecting a component instance that is available as a module version.
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface ModuleComponentSelector extends ComponentSelector {
+    /**
+     * The group of the module to select the component from.
+     *
+     * @return Module group
+     * @since 1.10
+     */
+    String getGroup();
+
+    /**
+     * The name of the module to select the component from.
+     *
+     * @return Module name
+     */
+    String getModule();
+
+    /**
+     * The version of the module to select the component from.
+     *
+     * @return Module version
+     */
+    String getVersion();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ProjectComponentIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ProjectComponentIdentifier.java
new file mode 100644
index 0000000..ac25ca7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ProjectComponentIdentifier.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An identifier for a component instance that is built as part of the current build.
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface ProjectComponentIdentifier extends ComponentIdentifier {
+    /**
+     * Returns the path of the project which the component belongs to.
+     *
+     * @since 1.10
+     */
+    String getProjectPath();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ProjectComponentSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ProjectComponentSelector.java
new file mode 100644
index 0000000..bdc7346
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/ProjectComponentSelector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Criteria for selecting a component instance that is built as part of the current build.
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface ProjectComponentSelector extends ComponentSelector {
+    /**
+     * The path of the project to select the component from.
+     *
+     * @return Project path
+     * @since 1.10
+     */
+    String getProjectPath();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/package-info.java
new file mode 100644
index 0000000..916d32d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 provide meta-data about software components.
+ */
+package org.gradle.api.artifacts.component;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
index 376cb32..ed74047 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
@@ -67,8 +67,6 @@ import org.gradle.api.artifacts.PublishArtifact;
  *   schema schemaJar
  * }
  * </pre>
- *
- * @author Hans Dockter
  */
 public interface ArtifactHandler {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.java
new file mode 100644
index 0000000..39f21af
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.artifacts.dsl;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.ComponentMetadataDetails;
+
+/**
+ * Allows to modify the metadata of depended-on software components.
+ *
+ * <p> Example:
+ * <pre autoTested=''>
+ * dependencies {
+ *     components {
+ *         eachComponent { ComponentMetadataDetails details ->
+ *             if (details.id.group == "org.foo") {
+ *                 def version = details.id.version
+ *                 // assuming status is last part of version string
+ *                 details.status = version.substring(version.lastIndexOf("-") + 1)
+ *                 details.statusScheme = ["bronze", "silver", "gold", "platinum"]
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * @since 1.8
+ */
+ at Incubating
+public interface ComponentMetadataHandler {
+    /**
+     * Adds a rule to modify the metadata of depended-on software components.
+     * For example, this allows to set a component's status and status scheme
+     * from within the build script, overriding any value specified in the
+     * component descriptor.
+     *
+     * @param rule the rule to be added
+     */
+    void eachComponent(Action<? super ComponentMetadataDetails> rule);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
index 1cef039..4522d20 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
@@ -16,7 +16,10 @@
 package org.gradle.api.artifacts.dsl;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.resolution.ArtifactResolutionQuery;
 
 import java.util.Map;
 
@@ -111,8 +114,10 @@ import java.util.Map;
  *   compile(group: 'org.myorg', name: 'someLib', version:'1.0') {
  *     //explicitly adding the dependency artifact:
  *     artifact {
+ *       //useful when some artifact properties unconventional
  *       name = 'someArtifact' //artifact name different than module name
- *       type = 'jar'
+ *       extension = 'someExt'
+ *       type = 'someType'
  *       classifier = 'someClassifier'
  *     }
  *   }
@@ -213,11 +218,11 @@ import java.util.Map;
  * <pre autoTested=''>
  * //Our Gradle plugin is written in groovy
  * apply plugin: 'groovy'
- * //now we can use 'groovy' and 'compile' configuration for declaring dependencies
+ * //now we can use the 'compile' configuration for declaring dependencies
  *
  * dependencies {
- *   //we will use groovy that ships with Gradle:
- *   groovy localGroovy()
+ *   //we will use the Groovy version that ships with Gradle:
+ *   compile localGroovy()
  *
  *   //our plugin requires Gradle API interfaces and classes to compile:
  *   compile gradleApi()
@@ -236,8 +241,6 @@ import java.util.Map;
  *
  * The module notation is the same as the dependency notations described above, except that the classifier property is
  * not available. Client modules are represented using a {@link org.gradle.api.artifacts.ClientModule}.
- *
- * @author Hans Dockter
  */
 public interface DependencyHandler {
     /**
@@ -318,4 +321,28 @@ public interface DependencyHandler {
      * @return The dependency.
      */
     Dependency localGroovy();
+
+    /**
+     * Returns the component metadata handler for this project. The returned handler can be used for adding rules
+     * that modify the metadata of depended-on software components.
+     *
+     * @return the component metadata handler for this project
+     * @since 1.8
+     */
+    @Incubating
+    ComponentMetadataHandler getComponents();
+
+    /**
+     * Configures module metadata for this project.
+     *
+     * <p>This method executes the given action against the {@link org.gradle.api.artifacts.dsl.ComponentMetadataHandler} for this project.
+     *
+     * @param configureAction the action to use to configure module metadata
+     * @since 1.8
+     */
+    @Incubating
+    void components(Action<? super ComponentMetadataHandler> configureAction);
+
+    @Incubating
+    ArtifactResolutionQuery createArtifactResolutionQuery();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
index 1c0c851..4f2caa1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
@@ -27,8 +27,6 @@ import java.util.Map;
 
 /**
  * A {@code RepositoryHandler} manages a set of repositories, allowing repositories to be defined and queried.
- *
- * @author Hans Dockter
  */
 public interface RepositoryHandler extends ArtifactRepositoryContainer {
 
@@ -52,7 +50,7 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * </table>
      *
      * <p>Examples:
-     * <pre>
+     * <pre autoTested=''>
      * repositories {
      *     flatDir name: 'libs', dirs: "$projectDir/libs"
      *     flatDir dirs: ["$projectDir/libs1", "$projectDir/libs2"]
@@ -84,6 +82,48 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
     FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action);
 
     /**
+     * Adds a repository which looks in Bintray's JCenter repository for dependencies.
+     * <p>
+     * The URL used to access this repository is {@literal "http://jcenter.bintray.com/"}.
+     * The behavior of this resolver is otherwise the same as the ones added by {@link #maven(org.gradle.api.Action)}.
+     * <p>
+     * Examples:
+     * <pre autoTested="">
+     * repositories {
+     *   jcenter {
+     *     artifactUrls = ["http://www.mycompany.com/artifacts1", "http://www.mycompany.com/artifacts2"]
+     *   }
+     *   jcenter {
+     *     name = "nonDefaultName"
+     *     artifactUrls = ["http://www.mycompany.com/artifacts1"]
+     *   }
+     * }
+     * </pre>
+     *
+     * @param action a configuration action
+     * @return the added repository
+     */
+    MavenArtifactRepository jcenter(Action<? super MavenArtifactRepository> action);
+
+    /**
+     * Adds a repository which looks in Bintray's JCenter repository for dependencies.
+     * <p>
+     * The URL used to access this repository is {@literal "http://jcenter.bintray.com/"}.
+     * The behavior of this resolver is otherwise the same as the ones added by {@link #mavenCentral()}.
+     * <p>
+     * Examples:
+     * <pre autoTested="">
+     * repositories {
+     *     jcenter()
+     * }
+     * </pre>
+     *
+     * @return the added resolver
+     * @see #jcenter(Action)
+     */
+    MavenArtifactRepository jcenter();
+
+    /**
      * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
      * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The behavior of this resolver
      * is otherwise the same as the ones added by {@link #mavenRepo(java.util.Map)}.
@@ -99,13 +139,13 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * must be unique amongst a repository group.
      * </td></tr>
      * <tr><td><code>artifactUrls</code></td>
-     *     <td>A single jar repository or a collection of jar repositories containing additional artifacts not found in the maven central repository.
-     * But be aware that the POM must exist in maven central.
+     *     <td>A single jar repository or a collection of jar repositories containing additional artifacts not found in the Maven central repository.
+     * But be aware that the POM must exist in Maven central.
      * The provided values are evaluated as per {@link org.gradle.api.Project#uri(Object)}.</td></tr>
      * </table>
      *
      * <p>Examples:
-     * <pre>
+     * <pre autoTested="">
      * repositories {
      *     mavenCentral artifactUrls: ["http://www.mycompany.com/artifacts1", "http://www.mycompany.com/artifacts2"]
      *     mavenCentral name: "nonDefaultName", artifactUrls: ["http://www.mycompany.com/artifacts1"]
@@ -124,7 +164,7 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME}.
      *
      * <p>Examples:
-     * <pre>
+     * <pre autoTested="">
      * repositories {
      *     mavenCentral()
      * }
@@ -141,7 +181,7 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}.
      *
      * <p>Examples:
-     * <pre>
+     * <pre autoTested="">
      * repositories {
      *     mavenLocal()
      * }
@@ -158,10 +198,10 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * to a Maven repository, have a look at {@link org.gradle.api.plugins.MavenRepositoryHandlerConvention#mavenDeployer(java.util.Map)} or
      * {@link org.gradle.api.plugins.MavenRepositoryHandlerConvention#mavenInstaller(java.util.Map)}.
      *
-     * By default the repository accepts to resolve artifacts without a pom. The repository always looks first for the pom
+     * By default the repository accepts to resolve artifacts without a POM. The repository always looks first for the POM
      * in the root repository. It then looks for the artifact in the root repository. Sometimes the artifact
-     * lives in a different repository than the pom. In such a case you can specify further locations to look for an artifact.
-     * But be aware that the pom is only looked up in the root repository.
+     * lives in a different repository than the POM. In such a case you can specify further locations to look for an artifact.
+     * But be aware that the POM is only looked up in the root repository.
      *
      * The following parameter are accepted as keys for the map:
      *
@@ -197,18 +237,22 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      *
      * @param args The argument to create the repository
      * @return the added repository
+     * @deprecated Use {@link #maven(groovy.lang.Closure)} instead.
      */
     @SuppressWarnings("JavadocReference")
+    @Deprecated
     DependencyResolver mavenRepo(Map<String, ?> args);
 
     /**
      * Adds a repository which is Maven compatible.
-     * 
+     *
      * @param args The argument to create the repository
      * @param configClosure Further configuration of the dependency resolver
      * @return The created dependency resolver
      * @see #mavenRepo(java.util.Map)
+     * @deprecated Use {@link #maven(groovy.lang.Closure)} instead.
      */
+    @Deprecated
     DependencyResolver mavenRepo(Map<String, ?> args, Closure configClosure);
 
     /**
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 88c9715..aa21ff4 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,7 +15,7 @@
  */
 package org.gradle.api.artifacts.repositories;
 
-import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * A repository for resolving and publishing artifacts.
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 9eb30de..bf92523 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
@@ -106,7 +106,7 @@ public interface IvyArtifactRepository extends ArtifactRepository, Authenticatio
      *     <li>Artifacts: <code>$baseUri/{@value #MAVEN_ARTIFACT_PATTERN}</code></li>
      *     <li>Ivy: <code>$baseUri/{@value #MAVEN_IVY_PATTERN}</code></li>
      * </ul>
-     * Following the maven convention, the 'organisation' value is further processed by replacing '.' with '/'.
+     * Following the Maven convention, the 'organisation' value is further processed by replacing '.' with '/'.
      *
      * <h4>'pattern'</h4>
      * A repository layout that allows custom patterns to be defined. eg:
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/ArtifactResolutionQuery.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/ArtifactResolutionQuery.java
new file mode 100644
index 0000000..c26823c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/ArtifactResolutionQuery.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+/**
+ * Resolves selected software artifacts of the given components.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface ArtifactResolutionQuery {
+    ArtifactResolutionQuery forComponents(Iterable<? extends ComponentIdentifier> componentIds);
+    ArtifactResolutionQuery forComponents(ComponentIdentifier... componentIds);
+    <T extends SoftwareComponent, U extends SoftwareArtifact> ArtifactResolutionQuery withArtifacts(Class<T> componentType, Class<U>... artifactTypes);
+    ArtifactResolutionQueryResult execute();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/ArtifactResolutionQueryResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/ArtifactResolutionQueryResult.java
new file mode 100644
index 0000000..b6cdb15
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/ArtifactResolutionQueryResult.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+
+import java.util.Set;
+
+/**
+ * The result of executing an artifact resolution query.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface ArtifactResolutionQueryResult {
+    Set<? extends SoftwareComponent> getComponents();
+    <T extends SoftwareComponent> Set<T> getComponents(Class<T> type);
+    Set<UnresolvedSoftwareComponent> getUnresolvedComponents();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibrary.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibrary.java
new file mode 100644
index 0000000..007c283
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibrary.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+
+import java.util.Set;
+
+/**
+ * Software component representing a JVM library.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface JvmLibrary extends SoftwareComponent {
+    Set<JvmLibrarySourcesArtifact> getSourcesArtifacts();
+    Set<JvmLibraryJavadocArtifact> getJavadocArtifacts();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibraryArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibraryArtifact.java
new file mode 100644
index 0000000..1341ee6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibraryArtifact.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Base interface for the artifacts of a JVM library software component.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface JvmLibraryArtifact extends SoftwareArtifact {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibraryJavadocArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibraryJavadocArtifact.java
new file mode 100644
index 0000000..f8868c1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibraryJavadocArtifact.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An artifact containing sources for a JVM library software component.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface JvmLibraryJavadocArtifact extends JvmLibraryArtifact {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibrarySourcesArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibrarySourcesArtifact.java
new file mode 100644
index 0000000..0788768
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/JvmLibrarySourcesArtifact.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An artifact containing sources for a JVM library software component.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface JvmLibrarySourcesArtifact extends JvmLibraryArtifact {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/SoftwareArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/SoftwareArtifact.java
new file mode 100644
index 0000000..e8c2356
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/SoftwareArtifact.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+/**
+ * An artifact of a software component.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface SoftwareArtifact {
+    /**
+     * The file for the artifact. If resolving the artifact caused a failure, that failure will be rethrown.
+     *
+     * @return the file for the artifact
+     */
+    File getFile() throws GradleException;
+
+    /**
+     * Returns the failure that occurred when the artifact was resolved, or {@code null} if no failure occurred.
+     *
+     * @return the failure that occurred when the artifact was resolved, or {@code null} if no failure occurred
+     */
+    @Nullable
+    GradleException getFailure();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/SoftwareComponent.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/SoftwareComponent.java
new file mode 100644
index 0000000..08c5ffc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/SoftwareComponent.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+/**
+ * A software component with resolved artifacts.
+ *
+ * Implementations have the following equals contract:
+ * {@code other != null && getClass() == other.getClass() && getId().equals(other.getId());}
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface SoftwareComponent {
+    ComponentIdentifier getId();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/UnresolvedSoftwareComponent.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/UnresolvedSoftwareComponent.java
new file mode 100644
index 0000000..530fc7f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/UnresolvedSoftwareComponent.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolution;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+/**
+ * A software component that couldn't be resolved.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface UnresolvedSoftwareComponent {
+    /**
+     * Returns the ID of the component.
+     *
+     * @return the ID of the component
+     */
+    ComponentIdentifier getId();
+
+    /**
+     * Returns the failure that occurred when trying to resolve the component.
+     *
+     * @return the failure that occurred when trying to resolve the component
+     */
+    Throwable getFailure();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/package-info.java
new file mode 100644
index 0000000..9d4903f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/resolution/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 comprising the artifact resolution API.
+ */
+package org.gradle.api.artifacts.resolution;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ComponentSelectionReason.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ComponentSelectionReason.java
new file mode 100644
index 0000000..3e66244
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ComponentSelectionReason.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.artifacts.result;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Answers the question why a component was selected during the dependency resolution.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public interface ComponentSelectionReason {
+
+    /**
+     * Informs whether the component was forced.
+     * Users can force components via {@link org.gradle.api.artifacts.ResolutionStrategy}
+     * or when declaring dependencies (see {@link org.gradle.api.artifacts.dsl.DependencyHandler}).
+     */
+    boolean isForced();
+
+    /**
+     * Informs whether the component 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();
+
+    /**
+     * Informs whether the component was selected by the dependency resolve rule.
+     * Users can configure dependency resolve rules via {@link org.gradle.api.artifacts.ResolutionStrategy#eachDependency(org.gradle.api.Action)}
+     *
+     * @since 1.4
+     */
+    boolean isSelectedByRule();
+
+    /**
+     * Informs whether the component is an expected selection.
+     *
+     * @return Flag
+     * @since 1.11
+     */
+    boolean isExpected();
+
+    /**
+     * Returns a human-consumable description of this selection reason.
+     */
+    String getDescription();
+}
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
index 5ce86d8..e7f541e 100644
--- 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
@@ -17,26 +17,32 @@
 package org.gradle.api.artifacts.result;
 
 import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
 
 /**
- * An edge in the dependency graph. Provides information about the origin of the dependency and the requested module version.
+ * An edge in the dependency graph. Provides information about the origin of the dependency and the requested component.
  *
  * @see ResolutionResult
  */
 @Incubating
 public interface DependencyResult {
     /**
-     * Returns the requested module version.
+     * <p>Returns the requested component.
      *
-     * @return the requested module version
+     * <p>The return type is declared as an opaque {@link org.gradle.api.artifacts.component.ComponentSelector}, however the selector may also implement one of the following interfaces:</p>
+     *
+     * <ul>
+     *     <li>{@link org.gradle.api.artifacts.component.ProjectComponentSelector} for those dependencies that request a component from some other project in the current build.</li>
+     *     <li>{@link org.gradle.api.artifacts.component.ModuleComponentSelector} for those dependencies that request a component to be found in some repository.</li>
+     * </ul>
+     * @return the requested component
      */
-    ModuleVersionSelector getRequested();
+    ComponentSelector getRequested();
 
     /**
      * Returns the origin of the dependency.
      *
      * @return the origin of the dependency
      */
-    ResolvedModuleVersionResult getFrom();
+    ResolvedComponentResult 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
deleted file mode 100644
index e9013d3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java
+++ /dev/null
@@ -1,56 +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.artifacts.result;
-
-import org.gradle.api.Incubating;
-
-/**
- * Answers the question why given module version was selected during the dependency resolution
- *
- * @since 1.3
- */
- 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();
-
-    /**
-     * Informs whether the module was selected by the dependency resolve rule.
-     * Users can configure dependency resolve rules via {@link org.gradle.api.artifacts.ResolutionStrategy#eachDependency(org.gradle.api.Action)}
-     *
-     * @since 1.4
-     */
-    boolean isSelectedByRule();
-
-    /**
-     * Describes this selection reason.
-     */
-    String getDescription();
-}
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
index b8f009d..f91f840 100644
--- 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
@@ -23,22 +23,20 @@ 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.
+ * Contains the information about the result of dependency resolution. You can use this type to determine all the component instances that are included
+ * in the resolved dependency graph, and the dependencies between them.
  */
 @Incubating
 public interface ResolutionResult {
 
     /**
-     * Gives access to the resolved dependency graph.
+     * Gives access to the root of 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.
+     * For example, Gradle's built-in 'dependencies' task uses this to render the dependency tree.
      *
      * @return the root node of the resolved dependency graph
      */
-    ResolvedModuleVersionResult getRoot();
+    ResolvedComponentResult getRoot();
 
     /**
      * Retrieves all dependencies, including unresolved dependencies.
@@ -68,26 +66,26 @@ public interface ResolutionResult {
     void allDependencies(Closure closure);
 
     /**
-     * Retrieves all instances of {@link ResolvedModuleVersionResult} from the graph,
+     * Retrieves all instances of {@link ResolvedComponentResult} from the graph,
      * e.g. all nodes of the dependency graph.
      *
      * @return all nodes of the dependency graph.
      */
-    Set<ResolvedModuleVersionResult> getAllModuleVersions();
+    Set<ResolvedComponentResult> getAllComponents();
 
     /**
-     * Applies given action for each module.
-     * An instance of {@link ResolvedModuleVersionResult} is passed as parameter to the action.
+     * Applies given action for each component.
+     * An instance of {@link ResolvedComponentResult} is passed as parameter to the action.
      *
-     * @param action - action that is applied for each module
+     * @param action - action that is applied for each component
      */
-    void allModuleVersions(Action<? super ResolvedModuleVersionResult> action);
+    void allComponents(Action<? super ResolvedComponentResult> action);
 
     /**
-     * Applies given closure for each module.
-     * An instance of {@link ResolvedModuleVersionResult} is passed as parameter to the closure.
+     * Applies given closure for each component.
+     * An instance of {@link ResolvedComponentResult} is passed as parameter to the closure.
      *
-     * @param closure - closure that is applied for each module
+     * @param closure - closure that is applied for each component
      */
-    void allModuleVersions(Closure closure);
+    void allComponents(Closure closure);
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedComponentResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedComponentResult.java
new file mode 100644
index 0000000..74c27af
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedComponentResult.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.artifacts.result;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+
+import java.util.Set;
+
+/**
+ * Represents a component instance in the resolved dependency graph. Provides some basic identity and dependency information about the component.
+ */
+ at Incubating
+public interface ResolvedComponentResult {
+
+    /**
+     * <p>Returns the identifier of this component. This can be used to uniquely identify the component within the current build, but it is not necessarily unique between
+     * different builds.
+     *
+     * <p>The return type is declared as an opaque {@link ComponentIdentifier}, however the identifier may also implement one of the following interfaces:</p>
+     *
+     * <ul>
+     *     <li>{@link org.gradle.api.artifacts.component.ProjectComponentIdentifier} for those component instances which are produced by the current build.</li>
+     *     <li>{@link org.gradle.api.artifacts.component.ModuleComponentIdentifier} for those component instances which are found in some repository.</li>
+     * </ul>
+     *
+     * @return the identifier of this component
+     */
+    ComponentIdentifier getId();
+
+    /**
+     * <p>Returns the dependencies of this component. Includes resolved and unresolved dependencies (if any).
+     *
+     * <p>The elements of the returned collection are declared as {@link DependencyResult}, however the dependency instances will also implement one of the
+     * following instances:</p>
+     *
+     * <ul>
+     *     <li>{@link ResolvedDependencyResult} for dependencies which were successfully resolved.</li>
+     *     <li>{@link UnresolvedDependencyResult} for dependencies which could not be resolved for some reason.</li>
+     * </ul>
+     *
+     * @return the dependencies of this component
+     */
+    Set<? extends DependencyResult> getDependencies();
+
+    /**
+     * Returns the incoming dependencies of this component.
+     *
+     * @return the dependents of this component
+     */
+    Set<? extends ResolvedDependencyResult> getDependents();
+
+    /**
+     * Returns the reason why this particular component was selected in the result.
+     * Useful if multiple candidate components were found during dependency resolution.
+     *
+     * @return the reason for selecting the component
+     */
+    ComponentSelectionReason getSelectionReason();
+
+    /**
+     * Returns the module version which this component belongs to, if any. A component will belong to a module version if it was found in some repository, or if the
+     * module version for the component has been declared, usually by declaring how the component should be published.
+     *
+     * @return the module version of the component, or {@code null} if this component has no associated module version.
+     */
+    @Nullable
+    ModuleVersionIdentifier getModuleVersion();
+}
\ 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
index 20b756e..f376682 100644
--- 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
@@ -24,8 +24,8 @@ import org.gradle.api.Incubating;
 @Incubating
 public interface ResolvedDependencyResult extends DependencyResult {
     /**
-     * Returns the selected module version. This may not necessarily be the same as the requested module version. For example, a dynamic version
+     * Returns the selected component. This may not necessarily be the same as the requested component. For example, a dynamic version
      * may have been requested, or the version may have been substituted due to conflict resolution, or by being forced, or for some other reason.
      */
-    ResolvedModuleVersionResult getSelected();
+    ResolvedComponentResult 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
deleted file mode 100644
index d0625ae..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java
+++ /dev/null
@@ -1,60 +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.artifacts.result;
-
-import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-import java.util.Set;
-
-/**
- * A node in the resolved dependency graph.
- * Contains the identifier of the module and its dependencies.
- */
- at Incubating
-public interface ResolvedModuleVersionResult {
-
-    /**
-     * Returns the identifier of the resolved module.
-     *
-     * @return the identifier of the resolved module
-     */
-    ModuleVersionIdentifier getId();
-
-    /**
-     * Returns the dependencies of the resolved module.
-     * Includes resolved and unresolved dependencies (if any).
-     *
-     * @return the dependencies of the resolved module
-     */
-    Set<? extends DependencyResult> getDependencies();
-
-    /**
-     * Returns the dependents of the resolved module.
-     *
-     * @return the dependents of the resolved module
-     */
-    Set<? extends ResolvedDependencyResult> getDependents();
-
-    /**
-     * Returns the reason for selecting the module.
-     * Useful if multiple candidate versions were found during dependency resolution.
-     *
-     * @return the reason for selecting the module
-     */
-    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
index 63e6b87..39a08c6 100644
--- 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
@@ -17,7 +17,7 @@
 package org.gradle.api.artifacts.result;
 
 import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
 
 /**
  * A dependency that could not be resolved.
@@ -25,14 +25,14 @@ import org.gradle.api.artifacts.ModuleVersionSelector;
 @Incubating
 public interface UnresolvedDependencyResult extends DependencyResult {
     /**
-     * Returns the module version selector that was attempted to be resolved. This may not be the same as the requested module version.
+     * Returns the selector that was attempted to be resolved. This may not be the same as the requested component.
      */
-    ModuleVersionSelector getAttempted();
+    ComponentSelector getAttempted();
 
     /**
      * Returns the reasons why the failed selector was attempted.
      */
-    ModuleVersionSelectionReason getAttemptedReason();
+    ComponentSelectionReason getAttemptedReason();
 
     /**
      * The failure that occurred.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java b/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java
index a83c2d2..2ffd726 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java
@@ -18,10 +18,11 @@ package org.gradle.api.component;
 
 import org.gradle.api.Incubating;
 import org.gradle.api.Named;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * A software component produced by a Gradle software project.
  */
- at Incubating
+ at Incubating @HasInternalProtocol
 public interface SoftwareComponent extends Named {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java b/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java
index 12c0569..c33a103 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java
@@ -70,7 +70,5 @@ package org.gradle.api.dsl;
  * Thanks to the 'lazy' evaluation of the convention properties
  * the user can reconfigure the sourceSets anywhere in the gradle script -
  * and still the test.testClassesDir will point to the right folder.
- *
- * @author Szczepan Faber, created at: 4/19/11
  */
 public class ConventionProperty {}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java b/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java
index c4091c1..3f4ecce 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author 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/file/CopySourceSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
index 2ef2794..4ca0cca 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
@@ -19,8 +19,6 @@ import groovy.lang.Closure;
 
 /**
  * Specifies sources for a file copy.
- *
- * @author Steve Appling
  */
 public interface CopySourceSpec {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
index 256a4a0..7a52935 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
@@ -17,11 +17,14 @@ package org.gradle.api.file;
 
 import groovy.lang.Closure;
 import org.gradle.api.Action;
-import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+import org.gradle.internal.HasInternalProtocol;
 import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternFilterable;
 
-import java.util.Map;
 import java.io.FilterReader;
+import java.util.Map;
 import java.util.regex.Pattern;
 
 /**
@@ -62,10 +65,10 @@ import java.util.regex.Pattern;
  * In this example, the <code>into</code> and <code>exclude</code> specifications at the root level are inherited by the
  * two child CopySpecs.
  *
- * @author Steve Appling
  * @see org.gradle.api.tasks.Copy Copy Task
  * @see org.gradle.api.Project#copy(groovy.lang.Closure) Project.copy()
  */
+ at HasInternalProtocol
 public interface CopySpec extends CopySourceSpec, CopyProcessingSpec, PatternFilterable {
     /**
      * Specifies whether case-sensitive pattern matching should be used.
@@ -96,7 +99,51 @@ public interface CopySpec extends CopySourceSpec, CopyProcessingSpec, PatternFil
     void setIncludeEmptyDirs(boolean includeEmptyDirs);
 
     /**
+     * Returns the strategy to use when trying to copy more than one file to the same destination.
+     * <p>
+     * The value can be set with a case insensitive string of the enum value (e.g. {@code 'exclude'} for {@link DuplicatesStrategy#EXCLUDE}).
+     * <p>
+     * This strategy can be overridden for individual files by using {@link #eachFile(org.gradle.api.Action)} or {@link #filesMatching(String, org.gradle.api.Action)}.
+     *
+     * @return the strategy to use for files included by this copy spec.
+     * @see DuplicatesStrategy
+     */
+    @Incubating
+    DuplicatesStrategy getDuplicatesStrategy();
+
+    /**
+     * The strategy to use when trying to copy more than one file to the same destination. Set to {@code null} to use the default strategy, which is inherited
+     * from the parent copy spec, if any, or {@link DuplicatesStrategy#INCLUDE} if this copy spec has no parent.
+     */
+    @Incubating
+    void setDuplicatesStrategy(@Nullable DuplicatesStrategy strategy);
+
+    /**
+     * Configure the {@link org.gradle.api.file.FileCopyDetails} for each file whose path matches the specified Ant-style pattern.
+     * This is equivalent to using eachFile() and selectively applying a configuration based on the file's path.
+     *
+     * @param pattern Ant-style pattern used to match against files' relative paths
+     * @param action action called for the FileCopyDetails of each file matching pattern
+     * @return this
+     */
+    @Incubating
+    CopySpec filesMatching(String pattern, Action<? super FileCopyDetails> action);
+
+    /**
+     * Configure the {@link org.gradle.api.file.FileCopyDetails} for each file whose path does not match the specified
+     * Ant-style pattern. This is equivalent to using eachFile() and selectively applying a configuration based on the
+     * file's path.
+     *
+     * @param pattern Ant-style pattern used to match against files' relative paths
+     * @param action action called for the FileCopyDetails of each file that does not match pattern
+     * @return this
+     */
+    @Incubating
+    CopySpec filesNotMatching(String pattern, Action<? super FileCopyDetails> action);
+
+    /**
      * Adds the given specs as a child of this spec.
+     *
      * @param sourceSpecs The specs to add
      * @return this
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/DeleteAction.java b/subprojects/core/src/main/groovy/org/gradle/api/file/DeleteAction.java
index 4be5015..0a70840 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/DeleteAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/DeleteAction.java
@@ -17,8 +17,6 @@ package org.gradle.api.file;
 
 /**
  * Deletes files and directories.
- *
- * @author Hans Dockter
  */
 public interface DeleteAction {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/DuplicateFileCopyingException.java b/subprojects/core/src/main/groovy/org/gradle/api/file/DuplicateFileCopyingException.java
new file mode 100644
index 0000000..802005c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/DuplicateFileCopyingException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.file;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Incubating;
+
+/**
+ * Thrown when more than one file with the same relative path name is to be copied
+ * and the {@link DuplicatesStrategy} is set to DuplicatesStrategy.FAIL
+ */
+ at Incubating
+public class DuplicateFileCopyingException extends GradleException {
+    public DuplicateFileCopyingException(String desc) {
+        super(desc);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/DuplicatesStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/file/DuplicatesStrategy.java
new file mode 100644
index 0000000..c36600c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/DuplicatesStrategy.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.file;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Strategies for dealing with the potential creation of duplicate files for or archive entries.
+ */
+ at Incubating
+public enum DuplicatesStrategy {
+
+    /**
+     * Do not attempt to prevent duplicates.
+     * <p>
+     * If the destination of the operation supports duplicates (e.g. zip files) then a duplicate entry will be created.
+     * If the destination does not support duplicates, the existing destination entry will be overridden with the duplicate.
+     */
+    INCLUDE,
+
+    /**
+     * Do not allow duplicates by ignoring subsequent items to be created at the same path.
+     * <p>
+     * If an attempt is made to create a duplicate file/entry during an operation, ignore the item.
+     * This will leave the file/entry that was first copied/created in place.
+     */
+    EXCLUDE,
+
+    /**
+     * Do not attempt to prevent duplicates, but log a warning message when multiple items 
+     * are to be created at the same path.
+     * <p>
+     * This behaves exactly as INCLUDE otherwise.
+     */
+    WARN,
+
+    /**
+     * Throw a {@link DuplicateFileCopyingException} when subsequent items are to be created at the same path.
+     * <p> 
+     * Use this strategy when duplicates are an error condition that should cause the build to fail.
+     */
+    FAIL
+
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java
index 4c386df..1dd32e9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java
@@ -18,8 +18,6 @@ package org.gradle.api.file;
 /**
  * The EmptyFileVisitor can be extends by implementations that only require to implement one of the 2 visit methods
  * (dir or file). This is just to limit the amount of code clutter when not both visit methods need to be implemented.
- *
- * @author Tom Eyckmans
  */
 public class EmptyFileVisitor implements FileVisitor {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
index 59a2ab9..925ad24 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
@@ -15,6 +15,10 @@
  */
 package org.gradle.api.file;
 
+import org.gradle.api.Incubating;
+import org.gradle.api.NonExtensible;
+import org.gradle.internal.HasInternalProtocol;
+
 /**
  * <p>Provides details about a file or directory about to be copied, and allows some aspects of the destination file to
  * be modified.</p>
@@ -22,6 +26,8 @@ package org.gradle.api.file;
  * <p>Using this interface, you can change the destination path of the file, filter the content of the file, or exclude
  * the file from the result entirely.</p>
  */
+ at HasInternalProtocol
+ at NonExtensible
 public interface FileCopyDetails extends FileTreeElement, ContentFilterable {
     /**
      * Excludes this file from the copy.
@@ -55,4 +61,39 @@ public interface FileCopyDetails extends FileTreeElement, ContentFilterable {
      * @param mode the Unix permissions, e.g. {@code 0644}.
      */
     void setMode(int mode);
+
+    /**
+     * The strategy to use if there is already a file at this file's destination.
+     */
+    @Incubating
+    void setDuplicatesStrategy(DuplicatesStrategy strategy);
+
+    /**
+     * The strategy to use if there is already a file at this file's destination.
+     * <p>
+     * The value can be set with a case insensitive string of the enum value (e.g. {@code 'exclude'} for {@link DuplicatesStrategy#EXCLUDE}).
+     *
+     * @see DuplicatesStrategy
+     * @return the strategy to use for this file.
+     */
+    @Incubating
+    DuplicatesStrategy getDuplicatesStrategy();
+
+    /**
+     * Returns the path of this file, relative to the root of the copy destination.
+     * <p>
+     * Always uses '/' as the hierarchy separator, regardless of platform file separator.
+     * Same as calling <code>getRelativePath().getPathString()</code>.
+     *
+     * @return The path. Never returns null.
+     */
+    String getPath();
+
+    /**
+     * Returns the path of this file, relative to the root of the copy destination.
+     *
+     * @return The path. Never returns null.
+     */
+    RelativePath getRelativePath();
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java
index 92ba383..b9b1764 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java
@@ -33,7 +33,7 @@ public interface FileTree extends FileCollection {
      * that any changes to this tree are reflected in the filtered tree.</p>
      *
      * <p>The given closure is used to configure the filter. A {@link org.gradle.api.tasks.util.PatternFilterable} is
-     * passed to the closure as it's delegate. Only files which match the specified include patterns will be included in
+     * passed to the closure as its delegate. Only files which match the specified include patterns will be included in
      * the filtered tree. Any files which match the specified exclude patterns will be excluded from the filtered
      * tree.</p>
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileVisitor.java
index 0972d74..e789709 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileVisitor.java
@@ -17,8 +17,6 @@ package org.gradle.api.file;
 
 /**
  * <p>A {@code FileVisitor} is used to visit each of the files in a {@link FileTree}.</p>
- *
- * @author Steve Appling
  */
 public interface FileVisitor {
     /**
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 7fa733f..f91e4fb 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
@@ -29,8 +29,6 @@ import java.util.ListIterator;
  * and target file path when copying files.</p>
  *
  * <p>{@code RelativePath} instances are immutable.</p>
- *
- * @author Steve Appling
  */
 public class RelativePath implements Serializable {
     private final boolean endsWithFile;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java b/subprojects/core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java
index cffbe5e..7e763bd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java
@@ -25,8 +25,6 @@ import java.util.Set;
  * <p> A {@code ProjectDescriptor} is created when you add a project to the build from the settings script, using {@link
  * Settings#include(String[])} or {@link Settings#includeFlat(String[])}. You can access the descriptors using one of
  * the lookup methods on the {@link Settings} object.</p>
- *
- * @author Hans Dockter
  */
 public interface ProjectDescriptor {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java b/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java
index ad79d28..97ffeb2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java
@@ -19,6 +19,7 @@ package org.gradle.api.initialization;
 import org.gradle.StartParameter;
 import org.gradle.api.UnknownProjectException;
 import org.gradle.api.invocation.Gradle;
+import org.gradle.api.plugins.PluginAware;
 
 import java.io.File;
 
@@ -59,10 +60,8 @@ import java.io.File;
  * <li>Provided on the command-line using the -P option.</li>
  *
  * </ul>
- *
- * @author Hans Dockter
  */
-public interface Settings {
+public interface Settings extends PluginAware {
     /**
      * <p>The default name for the settings file.</p>
      */
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 897c0a7..cf3b497 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
@@ -31,9 +31,12 @@ import org.gradle.internal.reflect.Instantiator;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Modifier;
 import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 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 Lock CACHE_LOCK = new ReentrantLock();
 
     public <T> T newInstance(Class<T> type, Object... parameters) {
         Instantiator instantiator = new DirectInstantiator();
@@ -41,6 +44,15 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
     }
 
     public <T> Class<? extends T> generate(Class<T> type) {
+        try {
+            CACHE_LOCK.lock();
+            return generateUnderLock(type);
+        } finally {
+            CACHE_LOCK.unlock();
+        }
+    }
+
+    private <T> Class<? extends T> generateUnderLock(Class<T> type) {
         Map<Class<?>, Class<?>> cache = GENERATED_CLASSES.get(getClass());
         if (cache == null) {
             // WeakHashMap won't work here. It keeps a strong reference to the mapping value, which is the generated class in this case
@@ -179,15 +191,22 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
                 }
             }
 
+            // Adds a set method for each mutable property
             for (MetaBeanProperty property : settableProperties) {
                 Collection<MetaMethod> methodsForProperty = methods.get(property.getName());
-                if (methodsForProperty.isEmpty()) {
-                    builder.addSetMethod(property);
-                } else if (conventionProperties.contains(property)) {
-                    for (MetaMethod method : methodsForProperty) {
-                        builder.overrideSetMethod(property, method);
+                boolean hasSetMethod = false;
+                for (MetaMethod method : methodsForProperty) {
+                    if (method.getParameterTypes().length == 1) {
+                        if (conventionProperties.contains(property)) {
+                            builder.overrideSetMethod(property, method);
+                        }
+                        hasSetMethod = true;
                     }
                 }
+
+                if (!hasSetMethod) {
+                    builder.addSetMethod(property);
+                }
             }
 
             for (Constructor<?> constructor : type.getConstructors()) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java
deleted file mode 100644
index 0100438..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal;
-
-import org.gradle.api.GradleException;
-
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public class AbstractMultiCauseException extends GradleException implements MultiCauseException {
-    private final List<Throwable> causes = new CopyOnWriteArrayList<Throwable>();
-    private final ThreadLocal<Boolean> hideCause = new ThreadLocal<Boolean>() {
-        @Override
-        protected Boolean initialValue() {
-            return false;
-        }
-    };
-
-    public AbstractMultiCauseException(String message) {
-        super(message);
-    }
-
-    public AbstractMultiCauseException(String message, Throwable... causes) {
-        super(message);
-        this.causes.addAll(Arrays.asList(causes));
-    }
-
-    public AbstractMultiCauseException(String message, Iterable<? extends Throwable> causes) {
-        super(message);
-        initCauses(causes);
-    }
-
-    public List<? extends Throwable> getCauses() {
-        return causes;
-    }
-
-    @Override
-    public Throwable initCause(Throwable throwable) {
-        causes.clear();
-        causes.add(throwable);
-        return null;
-    }
-
-    public void initCauses(Iterable<? extends Throwable> causes) {
-        this.causes.clear();
-        for (Throwable cause : causes) {
-            this.causes.add(cause);
-        }
-    }
-
-    @Override
-    public Throwable getCause() {
-        if (hideCause.get()) {
-            return null;
-        }
-        return causes.isEmpty() ? null : causes.get(0);
-    }
-
-    @Override
-    public void printStackTrace(PrintStream printStream) {
-        PrintWriter writer = new PrintWriter(printStream);
-        printStackTrace(writer);
-        writer.flush();
-    }
-
-    @Override
-    public void printStackTrace(PrintWriter printWriter) {
-        if (causes.size() <= 1) {
-            super.printStackTrace(printWriter);
-            return;
-        }
-
-        hideCause.set(true);
-        try {
-            super.printStackTrace(printWriter);
-            for (int i = 0; i < causes.size(); i++) {
-                Throwable cause = causes.get(i);
-                printWriter.format("Cause %s: ", i + 1);
-                cause.printStackTrace(printWriter);
-            }
-        } finally {
-            hideCause.set(false);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
index 2d64cb1..63f81f4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
@@ -16,9 +16,8 @@
 package org.gradle.api.internal;
 
 import groovy.lang.Closure;
-import org.gradle.api.Named;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Namer;
+import org.gradle.api.*;
+import org.gradle.internal.Actions;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
@@ -38,7 +37,7 @@ public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamed
     protected abstract T doCreate(String name);
 
     public T create(String name) {
-        return create(name, null);
+        return create(name, Actions.doNothing());
     }
 
     public T maybeCreate(String name) {
@@ -50,10 +49,14 @@ public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamed
     }
 
     public T create(String name, Closure configureClosure) {
+        return create(name, new ClosureBackedAction<T>(configureClosure));
+    }
+
+    public T create(String name, Action<? super T> configureAction) throws InvalidUserDataException {
         assertCanAdd(name);
         T object = doCreate(name);
         add(object);
-        ConfigureUtil.configure(configureClosure, object);
+        configureAction.execute(object);
         return object;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java
index 1f5046e..72d35c4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal;
 import org.gradle.api.*;
 import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.plugins.Convention;
+import org.gradle.internal.Transformers;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
@@ -49,6 +50,14 @@ public abstract class AbstractPolymorphicDomainObjectContainer<T>
         return create(name, type, null);
     }
 
+    public <U extends T> U maybeCreate(String name, Class<U> type) throws InvalidUserDataException {
+        T item = findByName(name);
+        if (item != null) {
+            return Transformers.cast(type).transform(item);
+        }
+        return create(name, type);
+    }
+
     public <U extends T> U create(String name, Class<U> type, Action<? super U> configuration) {
         assertCanAdd(name);
         U object = doCreate(name, type);
@@ -123,7 +132,12 @@ public abstract class AbstractPolymorphicDomainObjectContainer<T>
         @Override
         public Object invokeMethod(String name, Object... arguments) throws groovy.lang.MissingMethodException {
             if (isConfigureMethod(name, arguments)) {
-                return ConfigureUtil.configure((Closure) arguments[arguments.length - 1], getByName(name));
+                T element = getByName(name);
+                Object lastArgument = arguments[arguments.length - 1];
+                if (lastArgument instanceof Closure) {
+                    ConfigureUtil.configure((Closure) lastArgument, element);
+                }
+                return element;
             } else {
                 return super.invokeMethod(name, arguments);
             }
@@ -131,6 +145,7 @@ public abstract class AbstractPolymorphicDomainObjectContainer<T>
 
         private boolean isConfigureMethod(String name, Object... arguments) {
             return (arguments.length == 1 && arguments[0] instanceof Closure
+                    || arguments.length == 1 && arguments[0] instanceof Class
                     || arguments.length == 2 && arguments[0] instanceof Class && arguments[1] instanceof Closure)
                     && hasProperty(name);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
index 368316c..e53e9ec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
 import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
 import groovy.util.ObservableList;
@@ -24,6 +26,7 @@ import org.gradle.api.*;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.*;
+import org.gradle.api.internal.tasks.execution.DefaultTaskExecutionContext;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -48,13 +51,11 @@ import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     private static Logger buildLogger = Logging.getLogger(Task.class);
     private static ThreadLocal<TaskInfo> nextInstance = new ThreadLocal<TaskInfo>();
@@ -62,7 +63,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
 
     private String name;
 
-    private List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>();
+    private List<ContextAwareTaskAction> actions = new ArrayList<ContextAwareTaskAction>();
 
     private String path;
 
@@ -70,6 +71,12 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
 
     private DefaultTaskDependency dependencies;
 
+    private DefaultTaskDependency mustRunAfter;
+
+    private DefaultTaskDependency finalizedBy;
+
+    private DefaultTaskDependency shouldRunAfter;
+
     private ExtensibleDynamicObject extensibleDynamicObject;
 
     private String description;
@@ -115,14 +122,17 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         path = project.absoluteProjectPath(name);
         state = new TaskStateInternal(toString());
         dependencies = new DefaultTaskDependency(project.getTasks());
-        services = project.getServices().createFor(this);
+        mustRunAfter = new DefaultTaskDependency(project.getTasks());
+        finalizedBy = new DefaultTaskDependency(project.getTasks());
+        shouldRunAfter = new DefaultTaskDependency(project.getTasks());
+        services = project.getServiceRegistryFactory().createFor(this);
         extensibleDynamicObject = new ExtensibleDynamicObject(this, getServices().get(Instantiator.class));
         taskStatusNagger = services.get(TaskStatusNagger.class);
         outputs = services.get(TaskOutputsInternal.class);
         inputs = services.get(TaskInputs.class);
         executer = services.get(TaskExecuter.class);
 
-        observableActionList = new ObservableList(actions);
+        observableActionList = new ObservableActionWrapperList(actions);
         observableActionList.addPropertyChangeListener(new PropertyChangeListener() {
             public void propertyChange(PropertyChangeEvent evt) {
                 taskStatusNagger.nagAboutMutatingListIfTaskNotInConfigurableState("Task.getActions()", evt);
@@ -178,6 +188,10 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return observableActionList;
     }
 
+    public List<ContextAwareTaskAction> getTaskActions() {
+        return observableActionList;
+    }
+
     public void setActions(final List<Action<? super Task>> actions) {
         taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setActions(Actions<Task>)");
         taskStatusNagger.whileDisabled(new Runnable() {
@@ -272,7 +286,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public void executeWithoutThrowingTaskFailure() {
-        executer.execute(this, state);
+        executer.execute(this, state, new DefaultTaskExecutionContext());
     }
 
     public TaskExecuter getExecuter() {
@@ -448,11 +462,11 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return validators;
     }
 
-    private Action<Task> convertClosureToAction(Closure actionClosure) {
+    private ContextAwareTaskAction convertClosureToAction(Closure actionClosure) {
         return new ClosureTaskAction(actionClosure);
     }
 
-    private Action<Task> wrap(final Action<? super Task> action) {
+    private ContextAwareTaskAction wrap(final Action<? super Task> action) {
         return new TaskActionWrapper(action);
     }
 
@@ -466,13 +480,16 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         }
     }
 
-    private static class ClosureTaskAction implements Action<Task> {
+    private static class ClosureTaskAction implements ContextAwareTaskAction {
         private final Closure closure;
 
         private ClosureTaskAction(Closure closure) {
             this.closure = closure;
         }
 
+        public void contextualise(TaskExecutionContext context) {
+        }
+
         public void execute(Task task) {
             closure.setDelegate(task);
             closure.setResolveStrategy(Closure.DELEGATE_FIRST);
@@ -496,13 +513,19 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         }
     }
 
-    private static class TaskActionWrapper implements Action<Task> {
+    private static class TaskActionWrapper implements ContextAwareTaskAction {
         private final Action<? super Task> action;
 
         public TaskActionWrapper(Action<? super Task> action) {
             this.action = action;
         }
 
+        public void contextualise(TaskExecutionContext context) {
+            if (action instanceof ContextAwareTaskAction) {
+                ((ContextAwareTaskAction) action).contextualise(context);
+            }
+        }
+
         public void execute(Task task) {
             ClassLoader original = Thread.currentThread().getContextClassLoader();
             Thread.currentThread().setContextClassLoader(action.getClass().getClassLoader());
@@ -512,5 +535,129 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
                 Thread.currentThread().setContextClassLoader(original);
             }
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof TaskActionWrapper)) {
+                return false;
+            }
+
+            TaskActionWrapper that = (TaskActionWrapper) o;
+
+            if (action != null ? !action.equals(that.action) : that.action != null) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return action != null ? action.hashCode() : 0;
+        }
+    }
+
+    public void setMustRunAfter(Iterable<?> mustRunAfterTasks) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setMustRunAfter(Iterable)");
+        mustRunAfter.setValues(mustRunAfterTasks);
+    }
+
+    public Task mustRunAfter(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.mustRunAfter(Object...)");
+        mustRunAfter.add(paths);
+        return this;
+    }
+
+    public TaskDependency getMustRunAfter() {
+        return mustRunAfter;
+    }
+
+    public void setFinalizedBy(Iterable<?> finalizedByTasks) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setFinalizedBy(Iterable)");
+        finalizedBy.setValues(finalizedByTasks);
+    }
+
+    public Task finalizedBy(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.finalizedBy(Object...)");
+        finalizedBy.add(paths);
+        return this;
+    }
+
+    public TaskDependency getFinalizedBy() {
+        return finalizedBy;
+    }
+
+    public TaskDependency shouldRunAfter(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.shouldRunAfter(Object...)");
+        shouldRunAfter.add(paths);
+        return shouldRunAfter;
+    }
+
+    public void setShouldRunAfter(Iterable<?> shouldRunAfterTasks) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setShouldRunAfter(Iterable)");
+        shouldRunAfter.setValues(shouldRunAfterTasks);
+    }
+
+    public TaskDependency getShouldRunAfter() {
+        return shouldRunAfter;
+    }
+
+    private class ObservableActionWrapperList extends ObservableList {
+        public ObservableActionWrapperList(List delegate) {
+            super(delegate);
+        }
+
+        @Override
+        public boolean add(Object action) {
+            if (action == null) {
+                throw new InvalidUserDataException("Action must not be null!");
+            }
+            return super.add(wrap((Action<? super Task>) action));
+        }
+
+        @Override
+        public void add(int index, Object action) {
+            if (action == null) {
+                throw new InvalidUserDataException("Action must not be null!");
+            }
+            super.add(index, wrap((Action<? super Task>) action));
+        }
+
+        @Override
+        public boolean addAll(Collection actions) {
+            if (actions == null) {
+                throw new InvalidUserDataException("Actions must not be null!");
+            }
+            return super.addAll(transformToContextAwareTaskActions(actions));
+        }
+
+        @Override
+        public boolean addAll(int index, Collection actions) {
+            if (actions == null) {
+                throw new InvalidUserDataException("Actions must not be null!");
+            }
+            return super.addAll(index, transformToContextAwareTaskActions(actions));
+        }
+
+        @Override
+        public boolean removeAll(Collection actions) {
+            return super.removeAll(transformToContextAwareTaskActions(actions));
+        }
+
+        @Override
+        public boolean remove(Object action) {
+            return super.remove(wrap((Action<? super Task>)action));
+        }
+
+        private Collection<ContextAwareTaskAction> transformToContextAwareTaskActions(Collection<Object> c) {
+            return Collections2.transform(c, new Function<Object, ContextAwareTaskAction>() {
+                public ContextAwareTaskAction apply(@Nullable Object input) {
+                    return wrap((Action<? super Task>) input);
+                }
+            });
+        }
     }
 }
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 0dcb801..7e87be7 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,12 +16,13 @@
 package org.gradle.api.internal;
 
 import groovy.lang.*;
+import org.gradle.api.NonExtensible;
 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.CollectionUtils;
-import org.gradle.util.JavaMethod;
+import org.gradle.internal.reflect.JavaMethod;
 import org.objectweb.asm.*;
 import org.objectweb.asm.Type;
 
@@ -34,7 +35,7 @@ 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);
+    private static final JavaMethod<ClassLoader, Class> DEFINE_CLASS_METHOD = JavaReflectionUtil.method(ClassLoader.class, Class.class, "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
 
     @Override
     protected <T> ClassBuilder<T> start(Class<T> type) {
@@ -58,6 +59,10 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         private final Type conventionMappingType = Type.getType(ConventionMapping.class);
         private final Type groovyObjectType = Type.getType(GroovyObject.class);
         private final Type conventionType = Type.getType(Convention.class);
+        private final Type extensibleDynamicObjectHelperType = Type.getType(MixInExtensibleDynamicObject.class);
+        private final Type nonExtensibleDynamicObjectHelperType = Type.getType(BeanDynamicObject.class);
+
+        private final boolean extensible;
 
         private ClassBuilderImpl(Class<T> type) {
             this.type = type;
@@ -66,16 +71,22 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             typeName = type.getName() + "_Decorated";
             generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";");
             superclassType = Type.getType(type);
+
+            extensible = JavaReflectionUtil.getAnnotation(type, NonExtensible.class) == null;
         }
 
         public void startClass(boolean isConventionAware) {
             List<String> interfaceTypes = new ArrayList<String>();
-            if (isConventionAware) {
+            if (isConventionAware && extensible) {
                 interfaceTypes.add(conventionAwareType.getInternalName());
             }
+
+            if (extensible) {
+                interfaceTypes.add(extensionAwareType.getInternalName());
+                interfaceTypes.add(hasConventionType.getInternalName());
+            }
+
             interfaceTypes.add(dynamicObjectAwareType.getInternalName());
-            interfaceTypes.add(extensionAwareType.getInternalName());
-            interfaceTypes.add(hasConventionType.getInternalName());
             interfaceTypes.add(groovyObjectType.getInternalName());
 
             visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
@@ -109,7 +120,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             // this.super(p0 .. pn)
             methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
             for (int i = 0; i < constructor.getParameterTypes().length; i++) {
-                methodVisitor.visitVarInsn(Type.getType(constructor.getParameterTypes()[i]).getOpcode(Opcodes.ILOAD), i+1);
+                methodVisitor.visitVarInsn(Type.getType(constructor.getParameterTypes()[i]).getOpcode(Opcodes.ILOAD), i + 1);
             }
             methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
                     methodDescriptor);
@@ -225,108 +236,135 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         }
 
         public void mixInDynamicAware() throws Exception {
-            final Type helperType = Type.getType(MixInExtensibleDynamicObject.class);
 
             // GENERATE private MixInExtensibleDynamicObject dynamicObjectHelper = new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
 
-            final String fieldSignature = "L" + MixInExtensibleDynamicObject.class.getName().replaceAll("\\.", "/") + ";";
+            Class<?> extensibleObjectFieldType = extensible ? MixInExtensibleDynamicObject.class : BeanDynamicObject.class;
+            final String fieldSignature = Type.getDescriptor(extensibleObjectFieldType);
             visitor.visitField(Opcodes.ACC_PRIVATE, "dynamicObjectHelper", fieldSignature, null, null);
             initDynamicObjectHelper = new MethodCodeBody() {
                 public void add(MethodVisitor visitor) throws Exception {
-                    String helperTypeConstructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
-                            Type.getType(Object.class), dynamicObjectType
-                    });
-
-                    // GENERATE dynamicObjectHelper = new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
+                    generateCreateDynamicObject(visitor);
+                    visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
+                            fieldSignature);
+                    // END
+                }
+            };
 
-                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+            // END
 
-                    // GENERATE new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
-                    visitor.visitTypeInsn(Opcodes.NEW, helperType.getInternalName());
-                    visitor.visitInsn(Opcodes.DUP);
+            if (extensible) {
+                // GENERATE public Convention getConvention() { return dynamicObjectHelper.getConvention(); }
 
-                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                addGetter(HasConvention.class.getDeclaredMethod("getConvention"), new MethodCodeBody() {
+                    public void add(MethodVisitor visitor) throws Exception {
 
-                    boolean useInheritedDynamicObject = GroovySystem.getMetaClassRegistry().getMetaClass(type).pickMethod("getAsDynamicObject", new Class[0]) != null;
+                        // GENERATE dynamicObjectHelper.getConvention()
 
-                    if (useInheritedDynamicObject) {
-                        // GENERATE super.getAsDynamicObject()
                         visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                        visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(type).getInternalName(),
-                                "getAsDynamicObject", Type.getMethodDescriptor(dynamicObjectType, new Type[0]));
-                    } else {
-                        // GENERATE null
-                        visitor.visitInsn(Opcodes.ACONST_NULL);
+                        visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
+                                fieldSignature);
+                        String getterDescriptor = Type.getMethodDescriptor(ExtensibleDynamicObject.class.getDeclaredMethod(
+                                "getConvention"));
+                        visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, extensibleDynamicObjectHelperType.getInternalName(), "getConvention",
+                                getterDescriptor);
                     }
+                });
 
-                    visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, helperType.getInternalName(), "<init>",
-                            helperTypeConstructorDesc);
-                    // END
+                // END
 
-                    visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
-                            fieldSignature);
-                    // END
-                }
-            };
+                // GENERATE public ExtensionContainer getExtensions() { return getConvention(); }
 
-            // END
+                addGetter(ExtensionAware.class.getDeclaredMethod("getExtensions"), new MethodCodeBody() {
+                    public void add(MethodVisitor visitor) throws Exception {
 
-            // GENERATE public Convention getConvention() { return dynamicObjectHelper.getConvention(); }
+                        // GENERATE getConvention()
 
-            addGetter(HasConvention.class.getDeclaredMethod("getConvention"), new MethodCodeBody() {
-                public void add(MethodVisitor visitor) throws Exception {
+                        visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                        String getterDescriptor = Type.getMethodDescriptor(ExtensibleDynamicObject.class.getDeclaredMethod(
+                                "getConvention"));
+                        visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), "getConvention",
+                                getterDescriptor);
+                    }
+                });
+            }
 
-                    // GENERATE dynamicObjectHelper.getConvention()
+            // END
 
+            // GENERATE public DynamicObject.getAsDynamicObject() { return dynamicObjectHelper; }
+
+            addGetter(DynamicObjectAware.class.getDeclaredMethod("getAsDynamicObject"), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) {
                     visitor.visitVarInsn(Opcodes.ALOAD, 0);
                     visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
                             fieldSignature);
-                    String getterDescriptor = Type.getMethodDescriptor(ExtensibleDynamicObject.class.getDeclaredMethod(
-                            "getConvention"));
-                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(), "getConvention",
-                            getterDescriptor);
                 }
             });
 
             // END
+        }
 
-            // GENERATE public ExtensionContainer getExtensions() { return getConvention(); }
+        private void generateCreateDynamicObject(MethodVisitor visitor) {
+            if (extensible) {
 
-            addGetter(ExtensionAware.class.getDeclaredMethod("getExtensions"), new MethodCodeBody() {
-                public void add(MethodVisitor visitor) throws Exception {
+                String helperTypeConstructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
+                        Type.getType(Object.class), dynamicObjectType
+                });
 
-                    // GENERATE getConvention()
+                // GENERATE dynamicObjectHelper = new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
+
+                visitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+                // GENERATE new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
+                visitor.visitTypeInsn(Opcodes.NEW, extensibleDynamicObjectHelperType.getInternalName());
+                visitor.visitInsn(Opcodes.DUP);
 
+                visitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+                boolean useInheritedDynamicObject = GroovySystem.getMetaClassRegistry().getMetaClass(type).pickMethod("getAsDynamicObject", new Class[0]) != null;
+
+                if (useInheritedDynamicObject) {
+                    // GENERATE super.getAsDynamicObject()
                     visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                    String getterDescriptor = Type.getMethodDescriptor(ExtensibleDynamicObject.class.getDeclaredMethod(
-                            "getConvention"));
-                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), "getConvention",
-                            getterDescriptor);
+                    visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(type).getInternalName(),
+                            "getAsDynamicObject", Type.getMethodDescriptor(dynamicObjectType, new Type[0]));
+                } else {
+                    // GENERATE null
+                    visitor.visitInsn(Opcodes.ACONST_NULL);
                 }
-            });
 
-            // END
+                visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, extensibleDynamicObjectHelperType.getInternalName(), "<init>",
+                        helperTypeConstructorDesc);
+                // END
+            } else {
+                String helperTypeConstructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
+                        Type.getType(Object.class)
+                });
 
-            // GENERATE public DynamicObject.getAsDynamicObject() { return dynamicObjectHelper; }
+                // GENERATE new BeanDynamicObject(this)
 
-            addGetter(DynamicObjectAware.class.getDeclaredMethod("getAsDynamicObject"), new MethodCodeBody() {
-                public void add(MethodVisitor visitor) {
+                visitor.visitVarInsn(Opcodes.ALOAD, 0);
 
-                    // GENERATE dynamicObjectHelper
+                // GENERATE new BeanDynamicObject(this)
+                visitor.visitTypeInsn(Opcodes.NEW, nonExtensibleDynamicObjectHelperType.getInternalName());
+                visitor.visitInsn(Opcodes.DUP);
 
-                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                    visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
-                            fieldSignature);
-                }
-            });
+                visitor.visitVarInsn(Opcodes.ALOAD, 0);
 
-            // END
+                visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, nonExtensibleDynamicObjectHelperType.getInternalName(), "<init>",
+                        helperTypeConstructorDesc);
+                // END
+            }
         }
 
         public void mixInConventionAware() throws Exception {
+            if (!extensible) {
+                return;
+            }
+
             // GENERATE private ConventionMapping mapping = new ConventionAwareHelper(this, getConvention())
 
-            final String mappingFieldSignature = "L" + ConventionMapping.class.getName().replaceAll("\\.", "/") + ";";
+            final String mappingFieldSignature = Type.getDescriptor(ConventionMapping.class);
             final String getConventionDesc = Type.getMethodDescriptor(conventionType, new Type[0]);
 
             visitor.visitField(Opcodes.ACC_PRIVATE, "mapping", mappingFieldSignature, null, null);
@@ -395,7 +433,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
 
             // GENERATE private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass())
 
-            final String metaClassFieldSignature = "L" + MetaClass.class.getName().replaceAll("\\.", "/") + ";";
+            final String metaClassFieldSignature = Type.getDescriptor(MetaClass.class);
             visitor.visitField(Opcodes.ACC_PRIVATE, "metaClass", metaClassFieldSignature, null, null);
 
             initMetaClass = new MethodCodeBody() {
@@ -598,6 +636,9 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         }
 
         public void addGetter(MetaBeanProperty property) throws Exception {
+            if (!extensible) {
+                return;
+            }
             MetaMethod getter = property.getGetter();
 
             // GENERATE private boolean <prop>Set;
@@ -606,7 +647,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             visitor.visitField(Opcodes.ACC_PRIVATE, flagName, Type.BOOLEAN_TYPE.getDescriptor(), null, null);
 
             addConventionGetter(getter.getName(), flagName, property);
-    
+
             String getterName = getter.getName();
             Class<?> returnType = getter.getReturnType();
 
@@ -681,6 +722,9 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         }
 
         public void addSetter(MetaBeanProperty property) throws Exception {
+            if (!extensible) {
+                return;
+            }
             MetaMethod setter = property.getSetter();
 
             // GENERATE public <return-type> <setter>(<type> v) { <return-type> v = super.<setter>(v); <prop>Set = true; return v; }
@@ -740,6 +784,11 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         }
 
         public void overrideSetMethod(MetaBeanProperty property, MetaMethod metaMethod) throws Exception {
+            if (metaMethod.getParameterTypes().length != 1) {
+                throw new IllegalArgumentException("Can only override set methods that take one argument: " + metaMethod.toString());
+            } else if (!extensible) {
+                return;
+            }
             Type paramType = Type.getType(metaMethod.getParameterTypes()[0].getTheClass());
             Type returnType = Type.getType(metaMethod.getReturnType());
             String methodDescriptor = Type.getMethodDescriptor(returnType, new Type[]{paramType});
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 01b2bc3..3ee214c 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
@@ -18,6 +18,8 @@ package org.gradle.api.internal;
 import groovy.lang.*;
 import groovy.lang.MissingMethodException;
 import org.codehaus.groovy.runtime.InvokerInvocationException;
+import org.gradle.api.internal.coerce.MethodArgumentsTransformer;
+import org.gradle.api.internal.coerce.TypeCoercingMethodArgumentsTransformer;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -28,12 +30,15 @@ import java.util.Map;
  * A {@link DynamicObject} which uses groovy reflection to provide access to the properties and methods of a bean.
  */
 public class BeanDynamicObject extends AbstractDynamicObject {
-    
+
     private final Object bean;
     private final boolean includeProperties;
     private final DynamicObject delegate;
     private final boolean implementsMissing;
 
+    // NOTE: If this guy starts caching internally, consider sharing an instance
+    private final MethodArgumentsTransformer argsTransformer = new TypeCoercingMethodArgumentsTransformer();
+
     public BeanDynamicObject(Object bean) {
         this(bean, true);
     }
@@ -56,7 +61,7 @@ public class BeanDynamicObject extends AbstractDynamicObject {
             return new GroovyObjectAdapter();
         }
     }
-    
+
     public BeanDynamicObject withNoProperties() {
         return new BeanDynamicObject(bean, false);
     }
@@ -94,7 +99,7 @@ public class BeanDynamicObject extends AbstractDynamicObject {
 
     @Override
     public boolean hasProperty(String name) {
-        return delegate.hasProperty(name);    
+        return delegate.hasProperty(name);
     }
 
     @Override
@@ -104,22 +109,24 @@ public class BeanDynamicObject extends AbstractDynamicObject {
 
     @Override
     public void setProperty(final String name, Object value) throws MissingPropertyException {
-        delegate.setProperty(name, value); 
+        delegate.setProperty(name, value);
     }
 
     @Override
     public Map<String, ?> getProperties() {
-        return delegate.getProperties();        
+        return delegate.getProperties();
     }
 
     @Override
     public boolean hasMethod(String name, Object... arguments) {
-        return delegate.hasMethod(name, arguments);                        
+        return delegate.hasMethod(name, arguments);
     }
 
     @Override
     public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
-        return delegate.invokeMethod(name, arguments);        
+        // Maybe transform the arguments before calling the method (e.g. type coercion)
+        arguments = argsTransformer.transform(bean, name, arguments);
+        return delegate.invokeMethod(name, arguments);
     }
 
     private class MetaClassAdapter implements DynamicObject {
@@ -160,9 +167,7 @@ public class BeanDynamicObject extends AbstractDynamicObject {
             MetaClass metaClass = getMetaClass();
             MetaProperty property = metaClass.hasProperty(bean, name);
             if (property == null) {
-                if (property == null) {
-                    getMetaClass().invokeMissingProperty(bean, name, null, false);
-                }
+                getMetaClass().invokeMissingProperty(bean, name, null, false);
             }
 
             if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getSetter() == null) {
@@ -175,6 +180,10 @@ public class BeanDynamicObject extends AbstractDynamicObject {
                 };
             }
             try {
+
+                // Attempt type coercion before trying to set the property
+                value = argsTransformer.transform(bean, MetaProperty.getSetterName(name), value)[0];
+
                 metaClass.setProperty(bean, name, value);
             } catch (InvokerInvocationException e) {
                 if (e.getCause() instanceof RuntimeException) {
@@ -207,11 +216,11 @@ public class BeanDynamicObject extends AbstractDynamicObject {
             return properties;
         }
 
-        public boolean hasMethod(String name, Object... arguments) {
+        public boolean hasMethod(final String name, final Object... arguments) {
             return !getMetaClass().respondsTo(bean, name, arguments).isEmpty();
         }
 
-        public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+        public Object invokeMethod(final String name, final Object... arguments) throws MissingMethodException {
             try {
                 return getMetaClass().invokeMethod(bean, name, arguments);
             } catch (InvokerInvocationException e) {
@@ -219,8 +228,6 @@ public class BeanDynamicObject extends AbstractDynamicObject {
                     throw (RuntimeException) e.getCause();
                 }
                 throw e;
-            } catch (MissingMethodException e) {
-                throw methodMissingException(name, arguments);
             }
         }
 
@@ -242,7 +249,7 @@ public class BeanDynamicObject extends AbstractDynamicObject {
        So in this case we use these methods directly on the GroovyObject in case it does implement logic at this level.
      */
     private class GroovyObjectAdapter extends MetaClassAdapter {
-        private final GroovyObject groovyObject = (GroovyObject)bean;
+        private final GroovyObject groovyObject = (GroovyObject) bean;
 
 
         @Override
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/CachingDirectedGraphWalker.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/CachingDirectedGraphWalker.java
deleted file mode 100644
index 379a79b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/CachingDirectedGraphWalker.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal;
-
-import org.gradle.util.GUtil;
-
-import java.util.*;
-
-/**
- * A graph walker which collects the values reachable from a given set of start nodes. Handles cycles in the graph. Can
- * be reused to perform multiple searches, and reuses the results of previous searches.
- *
- * Uses a variation of Tarjan's algorithm: http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
- */
-public class CachingDirectedGraphWalker<N, T> {
-    private final DirectedGraphWithEdgeValues<N, T> graph;
-    private List<N> startNodes = new LinkedList<N>();
-    private final Map<N, Set<T>> cachedNodeValues = new HashMap<N, Set<T>>();
-
-    public CachingDirectedGraphWalker(DirectedGraph<N, T> graph) {
-        this.graph = new GraphWithEmpyEdges<N, T>(graph);
-    }
-
-    public CachingDirectedGraphWalker(DirectedGraphWithEdgeValues<N, T> graph) {
-        this.graph = graph;
-    }
-
-    /**
-     * Adds some start nodes.
-     */
-    public CachingDirectedGraphWalker<N, T> add(N... values) {
-        add(Arrays.asList(values));
-        return this;
-    }
-
-    /**
-     * Adds some start nodes.
-     */
-    public CachingDirectedGraphWalker add(Iterable<? extends N> values) {
-        GUtil.addToCollection(startNodes, values);
-        return this;
-    }
-
-    /**
-     * Calculates the set of values of nodes reachable from the start nodes.
-     */
-    public Set<T> findValues() {
-        try {
-            return doSearch();
-        } finally {
-            startNodes.clear();
-        }
-    }
-
-    private Set<T> doSearch() {
-        int componentCount = 0;
-        Map<N, NodeDetails<N, T>> seenNodes = new HashMap<N, NodeDetails<N, T>>();
-        Map<Integer, NodeDetails<N, T>> components = new HashMap<Integer, NodeDetails<N, T>>();
-        LinkedList<N> queue = new LinkedList<N>(startNodes);
-
-        while (!queue.isEmpty()) {
-            N node = queue.getFirst();
-            NodeDetails<N, T> details = seenNodes.get(node);
-            if (details == null) {
-                // Have not visited this node yet. Push its successors onto the queue in front of this node and visit
-                // them
-
-                details = new NodeDetails<N, T>(node, componentCount++);
-                seenNodes.put(node, details);
-                components.put(details.component, details);
-
-                Set<T> cacheValues = cachedNodeValues.get(node);
-                if (cacheValues != null) {
-                    // Already visited this node
-                    details.values = cacheValues;
-                    details.finished = true;
-                    queue.removeFirst();
-                    continue;
-                }
-
-                graph.getNodeValues(node, details.values, details.successors);
-                for (N connectedNode : details.successors) {
-                    if (!seenNodes.containsKey(connectedNode)) {
-                        queue.add(0, connectedNode);
-                    }
-                    // Else, already visiting or have visited the successor node (we're in a cycle)
-                }
-            } else {
-                // Have visited all of this node's successors
-                queue.removeFirst();
-
-                if (cachedNodeValues.containsKey(node)) {
-                    continue;
-                }
-
-                for (N connectedNode : details.successors) {
-                    NodeDetails<N, T> connectedNodeDetails = seenNodes.get(connectedNode);
-                    if (!connectedNodeDetails.finished) {
-                        // part of a cycle
-                        details.minSeen = Math.min(details.minSeen, connectedNodeDetails.minSeen);
-                    }
-                    details.values.addAll(connectedNodeDetails.values);
-                    graph.getEdgeValues(node, connectedNode, details.values);
-                }
-
-                if (details.minSeen != details.component) {
-                    // Part of a strongly connected component (ie cycle) - move values to root of the component
-                    // The root is the first node of the component we encountered
-                    NodeDetails<N, T> rootDetails = components.get(details.minSeen);
-                    rootDetails.values.addAll(details.values);
-                    details.values.clear();
-                    rootDetails.strongComponentMembers.addAll(details.strongComponentMembers);
-                } else {
-                    // Not part of a strongly connected component or the root of a strongly connected component
-                    for (NodeDetails<N, T> componentMember : details.strongComponentMembers) {
-                        cachedNodeValues.put(componentMember.node, details.values);
-                        componentMember.finished = true;
-                        components.remove(componentMember.component);
-                    }
-                }
-            }
-        }
-
-        Set<T> values = new LinkedHashSet<T>();
-        for (N startNode : startNodes) {
-            values.addAll(cachedNodeValues.get(startNode));
-        }
-        return values;
-    }
-
-    private static class NodeDetails<N, T> {
-        private final int component;
-        private final N node;
-        private Set<T> values = new LinkedHashSet<T>();
-        private List<N> successors = new ArrayList<N>();
-        private Set<NodeDetails<N, T>> strongComponentMembers = new LinkedHashSet<NodeDetails<N, T>>();
-        private int minSeen;
-        private boolean finished;
-
-        public NodeDetails(N node, int component) {
-            this.node = node;
-            this.component = component;
-            minSeen = component;
-            strongComponentMembers.add(this);
-        }
-    }
-
-    private static class GraphWithEmpyEdges<N, T> implements DirectedGraphWithEdgeValues<N, T> {
-        private final DirectedGraph<N, T> graph;
-
-        public GraphWithEmpyEdges(DirectedGraph<N, T> graph) {
-            this.graph = graph;
-        }
-
-        public void getEdgeValues(N from, N to, Collection<T> values) {
-        }
-
-        public void getNodeValues(N node, Collection<T> values, Collection<N> connectedNodes) {
-            graph.getNodeValues(node, values, connectedNodes);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
index 2a28305..4ac370e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
@@ -26,7 +26,7 @@ public class ClassGeneratorBackedInstantiator implements Instantiator {
         this.instantiator = instantiator;
     }
 
-    public <T> T newInstance(Class<T> type, Object... parameters) {
+    public <T> T newInstance(Class<? extends T> type, Object... parameters) {
         // During the construction of the object, it will look for this global instantiator.
         // This is to support ExtensionContainer.add(String, Class, Object...) which facilitates
         // making extensions ExtensionAware themselves.
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 9e8f4de..e028783 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
@@ -19,6 +19,7 @@ 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.gradle.internal.Actions;
 
 import java.util.Collection;
 import java.util.Iterator;
@@ -81,9 +82,11 @@ public class CompositeDomainObjectSet<T> extends DefaultDomainObjectSet<T> {
     }
     
     public void addCollection(DomainObjectCollection<? extends T> collection) {
-        getStore().addComposited(collection);
-        collection.all(getEventRegister().getAddAction());
-        collection.whenObjectRemoved(getEventRegister().getRemoveAction());
+        if (!getStore().getCollections().contains(collection)) {
+            getStore().addComposited(collection);
+            collection.all(getEventRegister().getAddAction());
+            collection.whenObjectRemoved(getEventRegister().getRemoveAction());
+        }
     }
 
     public void removeCollection(DomainObjectCollection<? extends T> collection) {
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 0a1e81e..cf4de20 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
@@ -16,8 +16,9 @@
 
 package org.gradle.api.internal;
 
-import groovy.lang.Closure;
 import groovy.lang.GroovyObjectSupport;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
 
 public class ConfigureDelegate extends GroovyObjectSupport {
     private static final Object[] EMPTY_PARAMS = new Object[0];
@@ -36,22 +37,31 @@ public class ConfigureDelegate extends GroovyObjectSupport {
         _delegate = DynamicObjectUtil.asDynamicObject(delegate);
     }
 
+    @Override
+    public String toString() {
+        return _delegate.toString();
+    }
+
     protected boolean _isConfigureMethod(String name, Object[] params) {
-        return params.length == 1 && params[0] instanceof Closure;
+        return false;
     }
 
-    protected void _configure(String name, Object[] params) {
+    protected Object _configure(String name, Object[] params) {
         // do nothing
+        return null;
     }
 
     public Object invokeMethod(String name, Object paramsObj) {
         Object[] params = (Object[])paramsObj;
 
-        boolean isTopLevelCall = !_configuring.get();
+        boolean isAlreadyConfiguring = _configuring.get();
         _configuring.set(true);
         try {
-            if (_delegate.hasMethod(name, params)) {
+            MissingMethodException failure;
+            try {
                 return _delegate.invokeMethod(name, params);
+            } catch (groovy.lang.MissingMethodException e) {
+                failure = e;
             }
 
             // try the owner
@@ -61,31 +71,41 @@ public class ConfigureDelegate extends GroovyObjectSupport {
                 // ignore
             }
 
-            if (isTopLevelCall && _isConfigureMethod(name, params)) {
-                _configure(name, params);
+            if (isAlreadyConfiguring || !_isConfigureMethod(name, params)) {
+                throw failure;
             }
 
-            return _delegate.invokeMethod(name, params);
+            return _configure(name, params);
         } finally {
-            _configuring.set(!isTopLevelCall);
+            _configuring.set(isAlreadyConfiguring);
         }
     }
 
     public Object get(String name) {
-        if (_delegate.hasProperty(name)) {
-            return _delegate.getProperty(name);
-        }
-
-        // try the owner
+        boolean isAlreadyConfiguring = _configuring.get();
+        _configuring.set(true);
         try {
-            return _owner.getProperty(name);
-        } catch (groovy.lang.MissingPropertyException e) {
-            // Ignore
-        }
+            MissingPropertyException failure;
+            try {
+                return _delegate.getProperty(name);
+            } catch (MissingPropertyException e) {
+                failure = e;
+            }
+
+            // try the owner
+            try {
+                return _owner.getProperty(name);
+            } catch (MissingPropertyException e) {
+                // Ignore
+            }
 
-        _configure(name, EMPTY_PARAMS);
+            if (isAlreadyConfiguring) {
+                throw failure;
+            }
 
-        // try the delegate again
-        return _delegate.getProperty(name);
+            return _configure(name, EMPTY_PARAMS);
+        } finally {
+            _configuring.set(isAlreadyConfiguring);
+        }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/Contextual.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/Contextual.java
deleted file mode 100644
index 36b140e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/Contextual.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal;
-
-import java.lang.annotation.*;
-
-/**
- * This annotation is attached to an exception class to indicate that it provides contextual information about the
- * exception which might help the user determine what the failed operation was, or where it took place. Generally, this
- * annotation is only attached to exceptions which chain lower-level exceptions.
- */
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.TYPE)
- at Inherited
-public @interface Contextual {
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
index 05e5925..b80f13d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
@@ -22,20 +22,17 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.plugins.Convention;
 import org.gradle.internal.UncheckedException;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
-/**
- * @author Hans Dockter
- */
 public class ConventionAwareHelper implements ConventionMapping, HasConvention {
     //prefix internal fields with _ so that they don't get into the way of propertyMissing()
     private final Convention _convention;
-    private IConventionAware _source;
+    private final IConventionAware _source;
     private final Map<String, MappedPropertyImpl> _mappings = new HashMap<String, MappedPropertyImpl>();
 
     /**
@@ -55,7 +52,7 @@ public class ConventionAwareHelper implements ConventionMapping, HasConvention {
     }
 
     private MappedProperty map(String propertyName, Value<?> value) {
-        if (!ReflectionUtil.hasProperty(_source, propertyName)) {
+        if (!JavaReflectionUtil.propertyExists(_source, propertyName)) {
             throw new InvalidUserDataException(
                     "You can't map a property that does not exist: propertyName=" + propertyName);
         }
@@ -124,14 +121,6 @@ public class ConventionAwareHelper implements ConventionMapping, HasConvention {
         return _convention;
     }
 
-    public IConventionAware getSource() {
-        return _source;
-    }
-
-    public void setSource(IConventionAware source) {
-        this._source = source;
-    }
-
     private static class MappedPropertyImpl implements MappedProperty {
         private final Value<?> value;
         private boolean haveValue;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
index f79245a..c5e36de 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
@@ -22,9 +22,6 @@ import org.gradle.api.Task;
 
 import java.util.concurrent.Callable;
 
-/**
- * @author Hans Dockter
- */
 public abstract class ConventionTask extends DefaultTask implements IConventionAware {
     private ConventionMapping conventionMapping;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
index 5d6dfbc..77a7b2b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
@@ -130,6 +130,14 @@ public class DefaultNamedDomainObjectCollection<T> extends DefaultDomainObjectCo
         return map;
     }
 
+    public SortedSet<String> getNames() {
+        SortedSet<String> set = new TreeSet<String>();
+        for (T o : getStore()) {
+            set.add(namer.determineName(o));
+        }
+        return set;
+    }
+
     public <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type) {
         return filtered(createFilter(type));
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainer.java
index 06e2635..5d7ddaa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainer.java
@@ -15,10 +15,15 @@
  */
 package org.gradle.api.internal;
 
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import groovy.lang.Closure;
 import org.gradle.api.*;
 import org.gradle.internal.reflect.Instantiator;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorphicDomainObjectContainer<T>
@@ -39,8 +44,9 @@ public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorp
 
     protected T doCreate(String name) {
         if (defaultFactory == null) {
-            throw new InvalidUserDataException("This container does not support "
-                    + "creating domain objects without specifying a type.");
+            throw new InvalidUserDataException(String.format("Cannot create a %s named '%s' because this container "
+                    + "does not support creating elements by name alone. Please specify which subtype of %s to create. "
+                    + "Known subtypes are: %s", getTypeDisplayName(), name, getTypeDisplayName(), getSupportedTypeNames()));
         }
         return defaultFactory.create(name);
     }
@@ -49,8 +55,8 @@ public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorp
         @SuppressWarnings("unchecked")
         NamedDomainObjectFactory<U> factory = (NamedDomainObjectFactory<U>) factories.get(type);
         if (factory == null) {
-            throw new InvalidUserDataException(String.format("This container does not support "
-                    + "creating domain objects of type '%s'.", type.getName()));
+            throw new InvalidUserDataException(String.format("Cannot create a %s because this type is not known "
+                    + "to this container. Known types are: %s", type.getSimpleName(), getSupportedTypeNames()));
         }
         return factory.create(name);
     }
@@ -61,9 +67,36 @@ public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorp
 
     public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
         if (!getType().isAssignableFrom(type)) {
-            throw new IllegalArgumentException(String.format("Factory element type '%s' is not a subtype of "
-                    + "container element type '%s'", type.getName(), getType().getName()));
+            throw new IllegalArgumentException(String.format("Cannot register a factory for type %s because "
+                    + "it is not a subtype of container element type %s.", type.getSimpleName(), getTypeDisplayName()));
         }
         factories.put(type, factory);
     }
+
+    public <U extends T> void registerFactory(Class<U> type, final Closure<? extends U> factory) {
+        registerFactory(type, new NamedDomainObjectFactory<U>() {
+            public U create(String name) {
+                return factory.call(name);
+            }
+        });
+    }
+
+    public <U extends T> void registerBinding(Class<U> type, final Class<? extends U> implementationType) {
+        registerFactory(type, new NamedDomainObjectFactory<U>() {
+            boolean named = Named.class.isAssignableFrom(implementationType);
+            public U create(String name) {
+                return named ? getInstantiator().newInstance(implementationType, name)
+                        : getInstantiator().newInstance(implementationType);
+            }
+        });
+    }
+
+    private String getSupportedTypeNames() {
+        List<String> names = Lists.newArrayList();
+        for (Class<?> clazz : factories.keySet()) {
+            names.add(clazz.getSimpleName());
+        }
+        Collections.sort(names);
+        return names.isEmpty() ? "(None)" : Joiner.on(", ").join(names);
+    }
 }
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
index 168b46f..2d220b9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyInjectingInstantiator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyInjectingInstantiator.java
@@ -52,7 +52,7 @@ public class DependencyInjectingInstantiator implements Instantiator {
         this.onDeprecationWarning = onDeprecationWarning;
     }
 
-    public <T> T newInstance(Class<T> type, Object... parameters) {
+    public <T> T newInstance(Class<? extends T> type, Object... parameters) {
         try {
             validateType(type);
             Constructor<?> constructor = selectConstructor(type, parameters);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectedGraph.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectedGraph.java
deleted file mode 100644
index 930dd5c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectedGraph.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal;
-
-import java.util.Collection;
-
-/**
- * A directed graph with nodes of type N. Each node has a collection of values of type V.
- */
-public interface DirectedGraph<N, V> {
-    void getNodeValues(N node, Collection<V> values, Collection<N> connectedNodes);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectedGraphWithEdgeValues.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectedGraphWithEdgeValues.java
deleted file mode 100644
index 16a5d0d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectedGraphWithEdgeValues.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal;
-
-import java.util.Collection;
-
-/**
- * A directed graph with nodes of type N. Each edge has a collection of values of type V
- */
-public interface DirectedGraphWithEdgeValues<N, V> extends DirectedGraph<N, V> {
-    void getEdgeValues(N from, N to, Collection<V> values);
-}
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 ba7d428..2213414 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
@@ -18,45 +18,26 @@ package org.gradle.api.internal;
 
 import org.gradle.util.GradleVersion;
 
-import java.io.File;
-
 /**
  * Locates documentation for various features.
  */
 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;
+    public DocumentationRegistry() {
+        this.gradleVersion = GradleVersion.current();
     }
 
     /**
      * Returns the location the documentation for the given feature, referenced by id. The location may be local or remote.
      */
     public String getDocumentationFor(String id) {
-        if (locator.getGradleHome() != null) {
-            File pageLocation = new File(locator.getGradleHome(), String.format("docs/userguide/%s.html", id));
-            File userGuideLocation = new File(locator.getGradleHome(), "docs/userguide/userguide.html");
-            if (pageLocation.isFile() && userGuideLocation.isFile()) {
-                return pageLocation.getAbsolutePath();
-            }
-            if (!pageLocation.isFile() && userGuideLocation.isFile()) {
-                throw new IllegalArgumentException(String.format("User guide page '%s' not found.", pageLocation));
-            }
-            if (pageLocation.isFile() && !userGuideLocation.isFile()) {
-                throw new IllegalArgumentException(String.format("User guide page '%s' not found.", userGuideLocation));
-            }
-        }
         return String.format("http://gradle.org/docs/%s/userguide/%s.html", gradleVersion.getVersion(), id);
     }
 
-    public String getFeatureLifecycle() {
-        return getDocumentationFor("feature_lifecycle");
+    public String getDslRefForProperty(Class<?> clazz, String property) {
+        String className = clazz.getName();
+        return String.format("http://gradle.org/docs/%s/dsl/%s.html#%s:%s", gradleVersion.getVersion(), className, className, property);
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleInternal.java
index fbc439d..ddfab12 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleInternal.java
@@ -15,12 +15,14 @@
  */
 package org.gradle.api.internal;
 
+import org.gradle.BuildListener;
 import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.internal.project.*;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.execution.TaskGraphExecuter;
-import org.gradle.BuildListener;
-import org.gradle.util.MultiParentClassLoader;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 
 /**
  * An internal interface for Gradle that exposed objects and concepts that are not intended for public
@@ -42,13 +44,6 @@ public interface GradleInternal extends Gradle {
      */
     ProjectInternal getDefaultProject();
 
-    IProjectRegistry<ProjectInternal> getProjectRegistry();
-
-    /**
-     * Returns the root {@code ClassLoader} to use for the scripts of this build.
-     */
-    MultiParentClassLoader getScriptClassLoader();
-
     /**
      * Returns the broadcaster for {@link ProjectEvaluationListener} events for this build
      */
@@ -73,5 +68,10 @@ public interface GradleInternal extends Gradle {
      */
     BuildListener getBuildListenerBroadcaster();
 
-    ServiceRegistryFactory getServices();
+    ServiceRegistry getServices();
+
+    ServiceRegistryFactory getServiceRegistryFactory();
+
+    ClassLoaderScope getClassLoaderScope();
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/GraphAggregator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/GraphAggregator.java
deleted file mode 100644
index 68d5c2c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/GraphAggregator.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal;
-
-import java.util.*;
-
-/**
- * Groups the nodes of a graph based on their reachability from a set of starting nodes.
- */
-public class GraphAggregator<N> {
-    private final CachingDirectedGraphWalker<N, N> graphWalker;
-
-    public GraphAggregator(DirectedGraph<N, ?> graph) {
-        graphWalker = new CachingDirectedGraphWalker<N, N>(new ConnectedNodesAsValuesDirectedGraph<N>(graph));
-    }
-
-    public Result<N> group(Collection<? extends N> startNodes, Collection<? extends N> allNodes) {
-        Map<N, Set<N>> reachableByNode = new HashMap<N, Set<N>>();
-        Set<N> topLevelNodes = new LinkedHashSet<N>(allNodes);
-        for (N node : allNodes) {
-            Set<N> reachableNodes = graphWalker.add(node).findValues();
-            reachableByNode.put(node, reachableNodes);
-            topLevelNodes.removeAll(reachableNodes);
-        }
-        topLevelNodes.addAll(startNodes);
-        Map<N, Set<N>> nodes = new HashMap<N, Set<N>>();
-        for (N node : topLevelNodes) {
-            nodes.put(node, calculateReachableNodes(reachableByNode, node, topLevelNodes));
-        }
-        return new Result<N>(nodes, topLevelNodes);
-    }
-
-    private Set<N> calculateReachableNodes(Map<N, Set<N>> nodes, N node, Set<N> topLevelNodes) {
-        Set<N> reachableNodes = nodes.get(node);
-        reachableNodes.add(node);
-        Set<N> reachableStartNodes = new LinkedHashSet<N>(topLevelNodes);
-        reachableStartNodes.retainAll(reachableNodes);
-        reachableStartNodes.remove(node);
-        for (N startNode : reachableStartNodes) {
-            reachableNodes.removeAll(calculateReachableNodes(nodes, startNode, topLevelNodes));
-        }
-        return reachableNodes;
-    }
-
-    public static class Result<N> {
-        private final Map<N, Set<N>> nodes;
-        private final Set<N> topLevelNodes;
-
-        public Result(Map<N, Set<N>> nodes, Set<N> topLevelNodes) {
-            this.nodes = nodes;
-            this.topLevelNodes = topLevelNodes;
-        }
-
-        public Set<N> getNodes(N startNode) {
-            return nodes.get(startNode);
-        }
-
-        public Set<N> getTopLevelNodes() {
-            return topLevelNodes;
-        }
-    }
-
-    private static class ConnectedNodesAsValuesDirectedGraph<N> implements DirectedGraph<N, N> {
-        private final DirectedGraph<N, ?> graph;
-
-        private ConnectedNodesAsValuesDirectedGraph(DirectedGraph<N, ?> graph) {
-            this.graph = graph;
-        }
-
-        public void getNodeValues(N node, Collection<N> values, Collection<N> connectedNodes) {
-            Set<N> edges = new LinkedHashSet<N>();
-            graph.getNodeValues(node, new ArrayList(), edges);
-            values.addAll(edges);
-            connectedNodes.addAll(edges);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
index a4139a7..eeb4553 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
@@ -22,8 +22,6 @@ package org.gradle.api.internal;
  *
  * <p>Each getter of an {@code IConventionAware} object should use the mappings to determine the value for the property,
  * when no value has been explicitly set for the property.</p>
- *
- * @author Hans Dockter
  */
 public interface IConventionAware {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
deleted file mode 100755
index 9044f3c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
+++ /dev/null
@@ -1,179 +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;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.GradleException;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.TreeVisitor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A {@code LocationAwareException} is an exception which can be annotated with a location in a script.
- */
-public class LocationAwareException extends GradleException {
-    private final Throwable target;
-    private final ScriptSource source;
-    private final Integer lineNumber;
-
-    public LocationAwareException(Throwable cause, Throwable target, ScriptSource source, Integer lineNumber) {
-        this.source = source;
-        this.lineNumber = lineNumber;
-        this.target = target;
-        initCause(cause);
-    }
-
-    /**
-     * Returns the target exception.
-     *
-     * @return The target exception. Not null
-     */
-    public Throwable getTarget() {
-        return target;
-    }
-
-    /**
-     * <p>Returns the undecorated message of this exception.</p>
-     *
-     * @return The undecorated message. May return null.
-     */
-    public String getOriginalMessage() {
-        return target.getMessage();
-    }
-
-    /**
-     * <p>Returns the source of the script where this exception occurred.</p>
-     *
-     * @return The source. May return null.
-     */
-    public ScriptSource getScriptSource() {
-        return source;
-    }
-
-    /**
-     * <p>Returns a description of the location of where this exception occurred.</p>
-     *
-     * @return The location description. May return null.
-     */
-    public String getLocation() {
-        if (source == null) {
-            return null;
-        }
-        String sourceMsg = StringUtils.capitalize(source.getDisplayName());
-        if (lineNumber == null) {
-            return sourceMsg;
-        }
-        return String.format("%s line: %d", sourceMsg, lineNumber);
-    }
-
-    /**
-     * Returns the line in the script where this exception occurred, if known.
-     *
-     * @return The line number, or null if not known.
-     */
-    public Integer getLineNumber() {
-        return lineNumber;
-    }
-
-    /**
-     * Returns the fully formatted error message, including the location.
-     *
-     * @return the message. May return null.
-     */
-    public String getMessage() {
-        String location = getLocation();
-        String message = target.getMessage();
-        if (location == null && message == null) {
-            return null;
-        }
-        if (location == null) {
-            return message;
-        }
-        if (message == null) {
-            return location;
-        }
-        return String.format("%s%n%s", location, message);
-    }
-
-    /**
-     * Returns the reportable causes for this failure.
-     *
-     * @return The causes. Never returns null, returns an empty list if this exception has no reportable causes.
-     */
-    public List<Throwable> getReportableCauses() {
-        final List<Throwable> causes = new ArrayList<Throwable>();
-        visitCauses(target, new TreeVisitor<Throwable>(){
-            @Override
-            public void node(Throwable node) {
-                causes.add(node);
-            }
-        });
-        return causes;
-    }
-
-    /**
-     * Visits the reportable causes for this failure.
-     */
-    public void visitReportableCauses(TreeVisitor<? super Throwable> visitor) {
-        visitor.node(this);
-        visitCauses(target, visitor);
-    }
-
-    private void visitCauses(Throwable t, TreeVisitor<? super Throwable> visitor) {
-        if (t instanceof MultiCauseException) {
-            MultiCauseException multiCauseException = (MultiCauseException) t;
-            List<? extends Throwable> causes = multiCauseException.getCauses();
-            if (!causes.isEmpty()) {
-                visitor.startChildren();
-                for (Throwable cause : causes) {
-                    visitor.node(cause);
-                    if (cause.getClass().getAnnotation(Contextual.class) != null) {
-                        visitCauses(cause, visitor);
-                    }
-                }
-                visitor.endChildren();
-            }
-            return;
-        }
-
-        if (t.getCause() != null) {
-            visitor.startChildren();
-            Throwable next = findNearestContextualCause(t);
-            if (next != null) {
-                // Show any contextual cause recursively
-                visitor.node(next);
-                visitCauses(next, visitor);
-            } else {
-                // Show the direct cause of the last contextual cause.
-                visitor.node(t.getCause());
-            }
-            visitor.endChildren();
-        }
-    }
-
-    private Throwable findNearestContextualCause(Throwable t) {
-        if (t.getCause() == null) {
-            return null;
-        }
-        Throwable cause = t.getCause();
-        if (cause.getClass().getAnnotation(Contextual.class) != null) {
-            return cause;
-        }
-        return findNearestContextualCause(cause);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/MultiCauseException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/MultiCauseException.java
deleted file mode 100644
index 95822b2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/MultiCauseException.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal;
-
-import java.util.List;
-
-public interface MultiCauseException {
-    List<? extends Throwable> getCauses();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
index ce26dc4..9571f2d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
@@ -15,18 +15,27 @@
  */
 package org.gradle.api.internal;
 
+import groovy.lang.Closure;
 import org.gradle.api.NamedDomainObjectContainer;
 
 public class NamedDomainObjectContainerConfigureDelegate extends ConfigureDelegate {
-    private final NamedDomainObjectContainer container;
+    private final NamedDomainObjectContainer _container;
 
     public NamedDomainObjectContainerConfigureDelegate(Object owner, NamedDomainObjectContainer container) {
         super(owner, container);
-        this.container = container;
+        _container = container;
     }
 
     @Override
-    protected void _configure(String name, Object[] params) {
-        container.create(name);
+    protected boolean _isConfigureMethod(String name, Object[] params) {
+        return params.length == 1 && params[0] instanceof Closure;
+    }
+
+    @Override
+    protected Object _configure(String name, Object[] params) {
+        if (params.length == 0) {
+            return _container.create(name);
+        }
+        return _container.create(name, (Closure) params[0]);
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java
index f07befc..661202a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java
@@ -15,30 +15,35 @@
  */
 package org.gradle.api.internal;
 
-import org.gradle.api.PolymorphicDomainObjectContainer;
-
 import groovy.lang.Closure;
+import org.gradle.api.PolymorphicDomainObjectContainer;
 
-public class PolymorphicDomainObjectContainerConfigureDelegate extends NamedDomainObjectContainerConfigureDelegate {
-    private final PolymorphicDomainObjectContainer container;
+public class PolymorphicDomainObjectContainerConfigureDelegate extends ConfigureDelegate {
+    private final PolymorphicDomainObjectContainer _container;
 
     public PolymorphicDomainObjectContainerConfigureDelegate(Object owner, PolymorphicDomainObjectContainer container) {
         super(owner, container);
-        this.container = container;
+        this._container = container;
     }
 
     @Override
     protected boolean _isConfigureMethod(String name, Object[] params) {
-        return super._isConfigureMethod(name, params) || params.length == 2 && params[0] instanceof Class && params[1] instanceof Closure;
+        return params.length == 1 && params[0] instanceof Closure
+                || params.length == 1 && params[0] instanceof Class
+                || params.length == 2 && params[0] instanceof Class && params[1] instanceof Closure;
     }
 
     @Override
     @SuppressWarnings("unchecked")
-    protected void _configure(String name, Object[] params) {
-        if (params.length <= 1) {
-            container.create(name);
+    protected Object _configure(String name, Object[] params) {
+        if (params.length == 0) {
+            return _container.create(name);
+        } else if (params.length == 1 && params[0] instanceof Closure) {
+            return _container.create(name, (Closure) params[0]);
+        } else if (params.length == 1 && params[0] instanceof Class) {
+            return _container.create(name, (Class) params[0]);
         } else {
-            container.create(name, (Class) params[0]);
+            return _container.create(name, (Class) params[0], new ClosureBackedAction((Closure) params[1]));
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java
index 04575f4..9db7540 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java
@@ -18,16 +18,19 @@ package org.gradle.api.internal;
 
 import org.gradle.StartParameter;
 import org.gradle.api.initialization.Settings;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.initialization.DefaultProjectDescriptor;
 
 public interface SettingsInternal extends Settings {
-    ClassLoader getClassLoader();
+
+    ClassLoaderScope getClassLoaderScope();
 
     StartParameter getStartParameter();
 
     ScriptSource getSettingsScript();
 
-    IProjectRegistry<DefaultProjectDescriptor> getProjectRegistry();
+    ProjectRegistry<DefaultProjectDescriptor> getProjectRegistry();
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
index d062bf4..37eaaa0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal;
 
 import org.gradle.api.Task;
+import org.gradle.api.internal.tasks.ContextAwareTaskAction;
 import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
@@ -25,10 +26,15 @@ import org.gradle.internal.Factory;
 import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.Configurable;
 
-import java.util.List;
 import java.io.File;
+import java.util.List;
 
 public interface TaskInternal extends Task, Configurable<Task> {
+
+    // Can we just override Task.getActions()?
+    // Would need to change return type on Task API to: List<? super Action<? super Task>> : not certain this is back-compatible
+    List<ContextAwareTaskAction> getTaskActions();
+
     Spec<? super TaskInternal> getOnlyIf();
 
     void execute();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java
index 5a079cb..1ff75d1 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java
@@ -26,4 +26,5 @@ public interface TaskOutputsInternal extends TaskOutputs {
     FileCollection getPreviousFiles();
 
     void setHistory(TaskExecutionHistory history);
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
deleted file mode 100644
index 5291a1f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts;
-
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-
-public interface ArtifactPublicationServices {
-
-    RepositoryHandler createRepositoryHandler();
-
-    ModuleDescriptorConverter getDescriptorFileModuleConverter();
-
-    IvyModuleDescriptorWriter getIvyModuleDescriptorWriter();
-
-    ArtifactPublisher createArtifactPublisher();
-}
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
deleted file mode 100644
index 64f14c0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactPublisher {
-    void publish(Iterable<? extends PublicationAwareRepository> repositories, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException;
-}
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
index b265e39..31a2661 100644
--- 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
@@ -20,18 +20,23 @@ 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.plugin.internal.PluginResolverFactory;
 
 /**
  * Factory for {@link ArtifactRepository} implementations.
  */
 public interface BaseRepositoryFactory {
 
+    String JCENTER_REPO_OVERRIDE_URL_PROPERTY = PluginResolverFactory.class.getName() + ".jcenter.override";
+
     ArtifactRepository createRepository(Object userDescription);
 
     FlatDirectoryArtifactRepository createFlatDirRepository();
 
     MavenArtifactRepository createMavenLocalRepository();
 
+    MavenArtifactRepository createJCenterRepository();
+
     MavenArtifactRepository createMavenCentralRepository();
 
     IvyArtifactRepository createIvyRepository();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
index 660bca5..ce4ef94 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
@@ -17,8 +17,8 @@
 package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.CachingDirectedGraphWalker;
-import org.gradle.api.internal.DirectedGraph;
+import org.gradle.internal.graph.CachingDirectedGraphWalker;
+import org.gradle.internal.graph.DirectedGraph;
 import org.gradle.api.internal.file.UnionFileCollection;
 
 import java.util.ArrayList;
@@ -52,7 +52,7 @@ public class CachingDependencyResolveContext implements DependencyResolveContext
     }
 
     private class DependencyGraph implements DirectedGraph<Object, FileCollection> {
-        public void getNodeValues(Object node, Collection<FileCollection> values, Collection<Object> connectedNodes) {
+        public void getNodeValues(Object node, Collection<? super FileCollection> values, Collection<? super Object> connectedNodes) {
             if (node instanceof FileCollection) {
                 FileCollection fileCollection = (FileCollection) node;
                 values.add(fileCollection);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java
deleted file mode 100644
index b9cbe72..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts;
-
-import org.gradle.api.artifacts.ArtifactIdentifier;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-public class DefaultArtifactIdentifier implements ArtifactIdentifier {
-    private final ModuleVersionIdentifier moduleVersionIdentifier;
-    private final String name;
-    private final String type;
-    private final String extension;
-    private final String classifier;
-
-    public DefaultArtifactIdentifier(ModuleVersionIdentifier moduleVersionIdentifier, String name, String type, String extension, String classifier) {
-        this.moduleVersionIdentifier = moduleVersionIdentifier;
-        this.name = name;
-        this.type = type;
-        this.extension = extension;
-        this.classifier = classifier;
-    }
-
-    public ModuleVersionIdentifier getModuleVersionIdentifier() {
-        return moduleVersionIdentifier;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public String getExtension() {
-        return extension;
-    }
-
-    public String getClassifier() {
-        return classifier;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
index a5badfc..14b76d0 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
@@ -25,7 +25,7 @@ import org.gradle.api.UnknownDomainObjectException;
 import org.gradle.api.artifacts.ArtifactRepositoryContainer;
 import org.gradle.api.artifacts.UnknownRepositoryException;
 import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.api.internal.DefaultNamedDomainObjectList;
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
 import org.gradle.internal.reflect.Instantiator;
@@ -36,11 +36,8 @@ import org.gradle.util.GUtil;
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.gradle.api.internal.Cast.cast;
+import static org.gradle.internal.Cast.cast;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObjectList<ArtifactRepository>
         implements ArtifactRepositoryContainer {
 
@@ -85,11 +82,13 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
     }
 
     public boolean add(DependencyResolver resolver, Closure configureClosure) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("ArtifactRepositoryContainer.add(DependencyResolver, Closure)");
         addCustomDependencyResolver(resolver, configureClosure, addLastAction);
         return true;
     }
 
     public boolean add(DependencyResolver resolver) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("ArtifactRepositoryContainer.add(DependencyResolver)");
         addCustomDependencyResolver(resolver, null, addLastAction);
         return true;
     }
@@ -99,6 +98,7 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
     }
 
     public DependencyResolver addFirst(Object userDescription, Closure configureClosure) {
+        DeprecationLogger.nagUserOfReplacedMethod("ArtifactRepositoryContainer.addFirst(Object)", "addFirst(ArtifactRepository");
         return addCustomDependencyResolver(userDescription, configureClosure, addFirstAction);
     }
 
@@ -122,6 +122,7 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
         if (!GUtil.isTrue(afterResolverName)) {
             throw new InvalidUserDataException("You must specify afterResolverName");
         }
+        DeprecationLogger.nagUserOfDiscontinuedMethod("ArtifactRepositoryContainer.addBefore()");
         final ArtifactRepository after = getByName(afterResolverName);
         return addCustomDependencyResolver(userDescription, configureClosure, new Action<ArtifactRepository>() {
             public void execute(ArtifactRepository repository) {
@@ -138,6 +139,7 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
         if (!GUtil.isTrue(beforeResolverName)) {
             throw new InvalidUserDataException("You must specify beforeResolverName");
         }
+        DeprecationLogger.nagUserOfDiscontinuedMethod("ArtifactRepositoryContainer.addAfter()");
         final ArtifactRepository before = getByName(beforeResolverName);
 
         return addCustomDependencyResolver(userDescription, configureClosure, new Action<ArtifactRepository>() {
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 244f338..249e3bd 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
@@ -21,11 +21,6 @@ import org.gradle.util.DeprecationLogger;
 import java.util.HashMap;
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- *
- *         DefaultExcludeRule is a value object
- */
 public class DefaultExcludeRule implements ExcludeRule {
     private String group;
     private String module;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
index ebae94d..b1a0f73 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
@@ -17,19 +17,16 @@ package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.artifacts.ExcludeRuleContainer;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
 
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExcludeRuleContainer implements ExcludeRuleContainer {
     private Set<ExcludeRule> addedRules = new LinkedHashSet<ExcludeRule>();
-    private NotationParser<ExcludeRule> notationParser = new ExcludeRuleNotationParser<ExcludeRule>();
+    private NotationParser<Object, ExcludeRule> notationParser = new ExcludeRuleNotationParser();
     //TODO has usage of NotationParserBuilder here any advantage?
 
     public DefaultExcludeRuleContainer() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java
index 5190958..32c5d48 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java
@@ -15,12 +15,7 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.gradle.api.artifacts.Module;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultModule implements Module {
+public class DefaultModule implements ModuleInternal {
     private String group;
     private String name;
     private String version;
@@ -54,4 +49,8 @@ public class DefaultModule implements Module {
     public String getStatus() {
         return status;
     }
+
+    public String getProjectPath() {
+        return null;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java
index 9b71f59..cd2c9b4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java
@@ -19,17 +19,17 @@ import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistration;
 
 /**
  * Factory for various types related to dependency management.
- *
- * <p>The motivation for having this factory is to allow implementation
- * types, and more importantly their dependencies, to be loaded from a
- * different class loader. This helps to prevent version conflicts,
- * for example between Maven 2 and Maven 3 libraries.
  */
-public interface DependencyManagementServices extends ServiceRegistry {
+public interface DependencyManagementServices {
+    /**
+     * Registers the dependency management DSL services.
+     */
+    void addDslServices(ServiceRegistration registration);
+
     DependencyResolutionServices create(FileResolver resolver, DependencyMetaDataProvider dependencyMetaDataProvider,
                                         ProjectFinder projectFinder, DomainObjectContext domainObjectContext);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
index 8aa927a..df3e931 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
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
@@ -26,10 +25,4 @@ public interface DependencyResolutionServices {
     ConfigurationContainerInternal getConfigurationContainer();
 
     DependencyHandler getDependencyHandler();
-
-    ArtifactHandler getArtifactHandler();
-
-    ArtifactPublicationServices createArtifactPublicationServices();
-
-    BaseRepositoryFactory getBaseRepositoryFactory();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java
index 15738f2..7c29c81 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java
@@ -17,13 +17,10 @@
 package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.artifacts.DependencyResolveDetails;
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
-/**
- * by Szczepan Faber, created at: 12/13/12
- */
 public interface DependencyResolveDetailsInternal extends DependencyResolveDetails {
 
-    void useVersion(String version, ModuleVersionSelectionReason selectionReason);
+    void useVersion(String version, ComponentSelectionReason selectionReason);
 
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java
index a135639..766e3af 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java
@@ -18,23 +18,20 @@ package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
 import org.gradle.api.tasks.Optional;
 
 import java.util.Collection;
 
-/**
- * @author Rene Groeschke
- */
-public class ExcludeRuleNotationParser<T extends ExcludeRule> extends MapNotationParser<T> {
+public class ExcludeRuleNotationParser extends MapNotationParser<ExcludeRule> {
 
     @Override
     public void describe(Collection<String> candidateFormats) {
         candidateFormats.add("Maps, e.g. [group: 'org.gradle', module:'gradle-core'].");
     }
 
-    protected T parseMap(@MapKey(ExcludeRule.GROUP_KEY) @Optional String group,
+    protected ExcludeRule parseMap(@MapKey(ExcludeRule.GROUP_KEY) @Optional String group,
                          @MapKey(ExcludeRule.MODULE_KEY) @Optional String module) {
         if (group == null && module == null) {
             throw new InvalidUserDataException("Either a group or module must be specified. For example: [group:'org.gradle']");
@@ -42,6 +39,6 @@ public class ExcludeRuleNotationParser<T extends ExcludeRule> extends MapNotatio
         DefaultExcludeRule excluderule = new DefaultExcludeRule();
         excluderule.setGroup(group);
         excluderule.setModule(module);
-        return (T) excluderule;
+        return excluderule;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleInternal.java
new file mode 100644
index 0000000..94d7bf3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleInternal.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Nullable;
+import org.gradle.api.artifacts.Module;
+
+public interface ModuleInternal extends Module {
+    @Nullable
+    String getProjectPath();
+}
\ No newline at end of file
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
index 91c395e..93f2169 100644
--- 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
@@ -20,9 +20,6 @@ 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;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java
index 6242f57..5c73c9c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java
@@ -17,9 +17,8 @@
 package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.Project;
-import org.gradle.api.artifacts.Module;
 
-public class ProjectBackedModule implements Module {
+public class ProjectBackedModule implements ModuleInternal {
 
     private final Project project;
 
@@ -42,4 +41,31 @@ public class ProjectBackedModule implements Module {
     public String getStatus() {
         return project.getStatus().toString();
     }
+
+    public String getProjectPath() {
+        return project.getPath();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ProjectBackedModule that = (ProjectBackedModule) o;
+
+        if (project != null ? !project.equals(that.project) : that.project != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return project != null ? project.hashCode() : 0;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java
index d27f2f9..9d48149 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java
@@ -16,8 +16,10 @@
 package org.gradle.api.internal.artifacts.configurations;
 
 import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.UnknownConfigurationException;
 
 public interface ConfigurationContainerInternal extends ConfigurationContainer {
     ConfigurationInternal getByName(String name) throws UnknownConfigurationException;
+    ConfigurationInternal detachedConfiguration(Dependency... dependencies);
 }
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 6beda87..2995f7d 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,6 @@ public interface ConfigurationInternal extends Configuration, DependencyMetaData
     DependencyResolutionListener getDependencyResolutionBroadcast();
 
     ResolutionStrategyInternal getResolutionStrategy();
+
+    String getPath();
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
index a5b6ef0..8770ce8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
@@ -15,11 +15,8 @@
  */
 package org.gradle.api.internal.artifacts.configurations;
 
-import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ModuleInternal;
 
-/**
- * @author Hans Dockter
- */
 public interface DependencyMetaDataProvider {
-    Module getModule();
+    ModuleInternal getModule();
 }
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 f2cad6e..2fa2ad7 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
@@ -17,18 +17,21 @@ 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.ModuleIdentifier;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
 
 import java.io.File;
+import java.util.Set;
 
 public interface CachePolicy {
-    boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis);
+    boolean mustRefreshVersionList(ModuleIdentifier selector, Set<ModuleVersionIdentifier> moduleVersions, long ageMillis);
 
     boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, ModuleRevisionId moduleRevisionId, long ageMillis);
 
     boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis);
 
+    boolean mustRefreshModuleArtifacts(ModuleVersionIdentifier moduleVersionId, Set<ArtifactIdentifier> artifacts, long ageMillis, boolean belongsToChangingModule, boolean moduleDescriptorInSync);
+
     boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis, boolean belongsToChangingModule, boolean moduleDescriptorInSync);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
index 07dfb00..f4d0922 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
@@ -17,12 +17,9 @@
 package org.gradle.api.internal.artifacts.dependencies;
 
 import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.artifacts.ResolvableDependency;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.artifacts.ResolvableDependency;
 
-/**
-* @author Hans Dockter
-*/
 public abstract class AbstractDependency implements ResolvableDependency, Dependency {
     protected void copyTo(AbstractDependency target) {
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
index c0cda2b..3de7134 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
@@ -24,9 +24,6 @@ import org.gradle.api.artifacts.ModuleDependency;
 import java.util.HashSet;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultClientModule extends AbstractExternalDependency implements ClientModule {
 
     private String group;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
index 9ed7976..4de8446 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
@@ -20,9 +20,6 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ExternalModuleDependency;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExternalModuleDependency extends AbstractExternalDependency implements ExternalModuleDependency {
     private String group;
     private String name;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
index 76f9bde..62c2acd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
@@ -31,10 +31,7 @@ import org.gradle.initialization.ProjectAccessListener;
 import java.io.File;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectDependency extends AbstractModuleDependency implements ProjectDependency {
+public class DefaultProjectDependency extends AbstractModuleDependency implements ProjectDependencyInternal {
     private ProjectInternal dependencyProject;
     private final boolean buildProjectDependencies;
     private final TaskDependencyImpl taskDependency = new TaskDependencyImpl();
@@ -89,6 +86,10 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
         return context.resolve().getFiles();
     }
 
+    public void beforeResolved() {
+        projectAccessListener.beforeResolvingProjectDependency(dependencyProject);
+    }
+
     @Override
     public void resolve(DependencyResolveContext context) {
         boolean transitive = isTransitive() && context.isTransitive();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/ProjectDependencyInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/ProjectDependencyInternal.java
new file mode 100644
index 0000000..a1236f3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/ProjectDependencyInternal.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dependencies;
+
+import org.gradle.api.artifacts.ProjectDependency;
+
+public interface ProjectDependencyInternal extends ProjectDependency {
+
+    /**
+     * This method is called when the project dependency is resolved
+     */
+    public void beforeResolved();
+
+}
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 a7058ad..5ca739b 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
@@ -34,13 +34,13 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static org.gradle.util.CollectionUtils.flattenToList;
+import static org.gradle.util.CollectionUtils.flattenCollections;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer implements RepositoryHandler {
 
+    public static final String DEFAULT_BINTRAY_JCENTER_REPO_NAME = "BintrayJCenter";
+    public static final String BINTRAY_JCENTER_URL = "http://jcenter.bintray.com/";
+
     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";
@@ -63,7 +63,7 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
     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")));
+            modifiedArgs.put("dirs", flattenCollections(modifiedArgs.get("dirs")));
         }
         return flatDir(new ConfigureByMapAction<FlatDirectoryArtifactRepository>(modifiedArgs));
     }
@@ -72,6 +72,14 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
         return addRepository(repositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME);
     }
 
+    public MavenArtifactRepository jcenter() {
+        return addRepository(repositoryFactory.createJCenterRepository(), DEFAULT_BINTRAY_JCENTER_REPO_NAME);
+    }
+
+    public MavenArtifactRepository jcenter(Action<? super MavenArtifactRepository> action) {
+        return addRepository(repositoryFactory.createJCenterRepository(), DEFAULT_BINTRAY_JCENTER_REPO_NAME, action);
+    }
+
     public MavenArtifactRepository mavenCentral(Map<String, ?> args) {
         Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
         if (modifiedArgs.containsKey("urls")) {
@@ -79,7 +87,7 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
                     "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"));
+            List<?> urls = flattenCollections(modifiedArgs.remove("urls"));
             modifiedArgs.put("artifactUrls", urls);
         }
 
@@ -95,14 +103,11 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
     }
 
     public DependencyResolver mavenRepo(Map<String, ?> args, Closure configClosure) {
+        DeprecationLogger.nagUserOfReplacedMethod("RepositoryHandler.mavenRepo()", "maven()");
         Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
         if (modifiedArgs.containsKey("urls")) {
-            List<?> urls = flattenToList(modifiedArgs.remove("urls"));
+            List<?> urls = flattenCollections(modifiedArgs.remove("urls"));
             if (!urls.isEmpty()) {
-                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<?> extraUrls = urls.subList(1, urls.size());
                 modifiedArgs.put("artifactUrls", extraUrls);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ArtifactResolutionQueryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ArtifactResolutionQueryFactory.java
new file mode 100644
index 0000000..af9ded5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ArtifactResolutionQueryFactory.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.dsl.dependencies;
+
+import org.gradle.api.artifacts.resolution.ArtifactResolutionQuery;
+
+public interface ArtifactResolutionQueryFactory {
+    ArtifactResolutionQuery createArtifactResolutionQuery();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
deleted file mode 100644
index 9cb99db..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl.dependencies
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.Dependency
-import org.gradle.api.artifacts.dsl.DependencyHandler
-import org.gradle.util.ConfigureUtil
-import org.gradle.util.GUtil
-
-/**
- * @author Hans Dockter
- */
-class DefaultDependencyHandler implements DependencyHandler {
-    ConfigurationContainer configurationContainer
-    DependencyFactory dependencyFactory
-    ProjectFinder projectFinder
-
-    def DefaultDependencyHandler(ConfigurationContainer configurationContainer, DependencyFactory dependencyFactory,
-                                 ProjectFinder projectFinder) {
-        this.configurationContainer = configurationContainer
-        this.dependencyFactory = dependencyFactory
-        this.projectFinder = projectFinder
-    }
-
-    public Dependency add(String configurationName, Object dependencyNotation) {
-        doAdd(configurationContainer[configurationName], dependencyNotation, null)
-    }
-
-    public Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure) {
-        doAdd(configurationContainer[configurationName], dependencyNotation, configureClosure)
-    }
-
-    Dependency create(Object dependencyNotation, Closure configureClosure = null) {
-        def dependency = dependencyFactory.createDependency(dependencyNotation)
-        ConfigureUtil.configure(configureClosure, dependency)
-        dependency
-    }
-
-    private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) {
-        if (dependencyNotation instanceof Configuration) {
-            Configuration other = (Configuration) dependencyNotation;
-            if (!configurationContainer.contains(other)) {
-                throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.")
-            }
-            configuration.extendsFrom(other)
-            return
-        }
-
-        def dependency = create(dependencyNotation, configureClosure)
-        configuration.dependencies << dependency
-        dependency
-    }
-
-    public Dependency module(Object notation) {
-        module(notation, null)
-    }
-
-    public Dependency project(Map notation) {
-        return dependencyFactory.createProjectDependencyFromMap(projectFinder, notation)
-    }
-
-    public Dependency module(Object notation, Closure configureClosure) {
-        return dependencyFactory.createModule(notation, configureClosure)
-    }
-
-    public Dependency gradleApi() {
-        return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.GRADLE_API);
-    }
-
-    public Dependency localGroovy() {
-        return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY);
-    }
-
-    public Object methodMissing(String name, Object args) {
-        Configuration configuration = configurationContainer.findByName(name)
-        if (configuration == null) {
-            if (!getMetaClass().respondsTo(this, name, args.size())) {
-                throw new MissingMethodException(name, this.getClass(), args);
-            }
-        }
-
-        Object[] normalizedArgs = GUtil.collectionize(args)
-        if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
-            return doAdd(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
-        } else if (normalizedArgs.length == 1) {
-            return doAdd(configuration, normalizedArgs[0], (Closure) null)
-        }
-        normalizedArgs.each {notation ->
-            doAdd(configuration, notation, null)
-        }
-        return null;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java
new file mode 100644
index 0000000..c53f183
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl.dependencies;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import groovy.lang.MissingMethodException;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.dsl.ComponentMetadataHandler;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.resolution.ArtifactResolutionQuery;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.List;
+import java.util.Map;
+
+public class DefaultDependencyHandler extends GroovyObjectSupport implements DependencyHandler {
+    private final ConfigurationContainer configurationContainer;
+    private final DependencyFactory dependencyFactory;
+    private final ProjectFinder projectFinder;
+    private final ComponentMetadataHandler metadataHandler;
+    private final ArtifactResolutionQueryFactory resolutionQueryFactory;
+
+    public DefaultDependencyHandler(ConfigurationContainer configurationContainer, DependencyFactory dependencyFactory,
+                                    ProjectFinder projectFinder, ComponentMetadataHandler metadataHandler,
+                                    ArtifactResolutionQueryFactory resolutionQueryFactory) {
+        this.configurationContainer = configurationContainer;
+        this.dependencyFactory = dependencyFactory;
+        this.projectFinder = projectFinder;
+        this.metadataHandler = metadataHandler;
+        this.resolutionQueryFactory = resolutionQueryFactory;
+    }
+
+    public Dependency add(String configurationName, Object dependencyNotation) {
+        return add(configurationName, dependencyNotation, null);
+    }
+
+    public Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure) {
+        return doAdd(configurationContainer.findByName(configurationName), dependencyNotation, configureClosure);
+    }
+
+    public Dependency create(Object dependencyNotation) {
+        return create(dependencyNotation, null);
+    }
+
+    public Dependency create(Object dependencyNotation, Closure configureClosure) {
+        Dependency dependency = dependencyFactory.createDependency(dependencyNotation);
+        return ConfigureUtil.configure(configureClosure, dependency);
+    }
+
+    private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) {
+        if (dependencyNotation instanceof Configuration) {
+            Configuration other = (Configuration) dependencyNotation;
+            if (!configurationContainer.contains(other)) {
+                throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.");
+            }
+            configuration.extendsFrom(other);
+            return null;
+        }
+
+        Dependency dependency = create(dependencyNotation, configureClosure);
+        configuration.getDependencies().add(dependency);
+        return dependency;
+    }
+
+    public Dependency module(Object notation) {
+        return module(notation, null);
+    }
+
+    public Dependency project(Map<String, ?> notation) {
+        return dependencyFactory.createProjectDependencyFromMap(projectFinder, notation);
+    }
+
+    public Dependency module(Object notation, Closure configureClosure) {
+        return dependencyFactory.createModule(notation, configureClosure);
+    }
+
+    public Dependency gradleApi() {
+        return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.GRADLE_API);
+    }
+
+    public Dependency localGroovy() {
+        return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY);
+    }
+
+    public Object methodMissing(String name, Object args) {
+        Object[] argsArray = (Object[]) args;
+        Configuration configuration = configurationContainer.findByName(name);
+        if (configuration == null) {
+            throw new MissingMethodException(name, this.getClass(), argsArray);
+        }
+
+        List<?> normalizedArgs = CollectionUtils.flattenCollections(argsArray);
+        if (normalizedArgs.size() == 2 && normalizedArgs.get(1) instanceof Closure) {
+            return doAdd(configuration, normalizedArgs.get(0), (Closure) normalizedArgs.get(1));
+        } else if (normalizedArgs.size() == 1) {
+            return doAdd(configuration, normalizedArgs.get(0), null);
+        } else {
+            for (Object arg : normalizedArgs) {
+                doAdd(configuration, arg, null);
+            }
+            return null;
+        }
+    }
+
+    public void components(Action<? super ComponentMetadataHandler> configureAction) {
+        configureAction.execute(getComponents());
+    }
+
+    public ComponentMetadataHandler getComponents() {
+        return metadataHandler;
+    }
+
+    public ArtifactResolutionQuery createArtifactResolutionQuery() {
+        return resolutionQueryFactory.createArtifactResolutionQuery();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
index 890c4ec..b2ba01f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
@@ -22,9 +22,6 @@ import org.gradle.api.artifacts.ProjectDependency;
 
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public interface DependencyFactory {
     //for gradle distribution specific dependencies
     enum ClassPathNotation {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy
index 4ea3563..513ebb8 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
@@ -19,9 +19,6 @@ package org.gradle.api.internal.artifacts.dsl.dependencies
 import org.gradle.api.artifacts.ClientModule
 import org.gradle.util.ConfigureUtil
 
-/**
- * @author Hans Dockter
- */
 class ModuleFactoryDelegate {
   ClientModule clientModule
   DependencyFactory dependencyFactory
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java
index f9ce356..1946e14 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java
@@ -15,13 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.dsl.dependencies;
 
-import org.gradle.api.artifacts.ExternalDependency;
 import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExternalDependency;
 import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
 
-/**
- * @author Hans Dockter
- */
 public class ModuleFactoryHelper {
     public static void addExplicitArtifactsIfDefined(ExternalDependency moduleDependency, String artifactType, String classifier) {
         String actualArtifactType = artifactType;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
index 12f736c..440b513 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
@@ -17,9 +17,6 @@ package org.gradle.api.internal.artifacts.dsl.dependencies;
 
 import org.gradle.api.internal.project.ProjectInternal;
 
-/**
- * @author Hans Dockter
-*/
 public interface ProjectFinder {
     /**
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
deleted file mode 100644
index 4b4a995..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ModuleDependency;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ModuleDescriptorConverter {
-    ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module);
-
-    ModuleDescriptor createModuleDescriptor(Module module);
-
-    void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
index abdc592..46e8df8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
@@ -19,9 +19,6 @@ import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.internal.tasks.DefaultTaskDependency;
 import org.gradle.api.tasks.TaskDependency;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractPublishArtifact implements PublishArtifact {
     private final DefaultTaskDependency taskDependency;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
index eb4fa9a..8791dcf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
@@ -21,9 +21,6 @@ import org.gradle.util.GUtil;
 import java.io.File;
 import java.util.Date;
 
-/**
- * @author Hans Dockter
- */
 public class ArchivePublishArtifact extends AbstractPublishArtifact {
     private String name;
     private String extension;
@@ -40,7 +37,17 @@ public class ArchivePublishArtifact extends AbstractPublishArtifact {
     }
 
     public String getName() {
-        return GUtil.elvis(name, archiveTask.getBaseName() + (GUtil.isTrue(archiveTask.getAppendix()) ? "-" + archiveTask.getAppendix() : ""));
+        if (name != null) {
+            return name;
+        }
+        if (archiveTask.getBaseName() != null) {
+            return withAppendix(archiveTask.getBaseName());
+        }
+        return archiveTask.getAppendix();
+    }
+
+    private String withAppendix(String baseName) {
+        return baseName + (GUtil.isTrue(archiveTask.getAppendix())? "-" + archiveTask.getAppendix() : "");
     }
 
     public String getExtension() {
@@ -67,8 +74,9 @@ public class ArchivePublishArtifact extends AbstractPublishArtifact {
         return archiveTask;
     }
 
-    public void setName(String name) {
+    public ArchivePublishArtifact setName(String name) {
         this.name = name;
+        return this;
     }
 
     public void setExtension(String extension) {
@@ -90,5 +98,4 @@ public class ArchivePublishArtifact extends AbstractPublishArtifact {
     public void setFile(File file) {
         this.file = file;
     }
-
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
index 8591a4c..092bd3e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
@@ -21,9 +21,6 @@ import org.gradle.api.artifacts.ConfigurablePublishArtifact;
 import java.io.File;
 import java.util.Date;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultPublishArtifact extends AbstractPublishArtifact implements ConfigurablePublishArtifact {
     private String name;
     private String extension;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java
deleted file mode 100644
index 9c493a5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.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.api.internal.artifacts.repositories;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-public interface PublicationAwareRepository {
-    DependencyResolver createPublisher();
-}
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
deleted file mode 100644
index 2ea8205..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparator.java
+++ /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.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/cache/BinaryStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/BinaryStore.java
new file mode 100644
index 0000000..eb77db5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/BinaryStore.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.cache;
+
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public interface BinaryStore {
+    void write(WriteAction write);
+
+    //done writing data, release any resources
+    BinaryData done();
+
+    public static interface WriteAction {
+        void write(Encoder encoder) throws IOException;
+    }
+
+    public static interface ReadAction<T> {
+        T read(Decoder decoder) throws IOException;
+    }
+
+    public static interface BinaryData extends Closeable {
+        <T> T read(ReadAction<T> readAction);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/Store.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/Store.java
new file mode 100644
index 0000000..ddd0a5e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/Store.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.cache;
+
+import org.gradle.internal.Factory;
+
+public interface Store<T> {
+    T load(Factory<T> createIfNotPresent);
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.java
deleted file mode 100644
index a93221e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.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.api.internal.changedetection;
-
-import org.gradle.cache.PersistentIndexedCache;
-
-public class CacheBackedFileSnapshotRepository implements FileSnapshotRepository {
-    private final PersistentIndexedCache<Object, Object> cache;
-
-    public CacheBackedFileSnapshotRepository(TaskArtifactStateCacheAccess cacheAccess) {
-        cache = cacheAccess.createCache("fileSnapshots", Object.class, Object.class);
-    }
-
-    public Long add(FileCollectionSnapshot snapshot) {
-        Long id = (Long) cache.get("nextId");
-        if (id == null) {
-            id = 1L;
-        }
-        cache.put("nextId", id + 1);
-        cache.put(id, snapshot);
-        return id;
-    }
-
-    public FileCollectionSnapshot get(Long id) {
-        return (FileCollectionSnapshot) cache.get(id);
-    }
-
-    public void remove(Long id) {
-        cache.remove(id);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
deleted file mode 100644
index ac68218..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
+++ /dev/null
@@ -1,174 +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.changedetection;
-
-import org.gradle.internal.Factory;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.messaging.serialize.DefaultSerializer;
-import org.gradle.cache.PersistentIndexedCache;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class CacheBackedTaskHistoryRepository implements TaskHistoryRepository {
-    private final TaskArtifactStateCacheAccess cacheAccess;
-    private final FileSnapshotRepository snapshotRepository;
-    private final PersistentIndexedCache<String, TaskHistory> taskHistoryCache;
-    private final DefaultSerializer<TaskHistory> serializer = new DefaultSerializer<TaskHistory>();
-
-    public CacheBackedTaskHistoryRepository(TaskArtifactStateCacheAccess cacheAccess, FileSnapshotRepository snapshotRepository) {
-        this.cacheAccess = cacheAccess;
-        this.snapshotRepository = snapshotRepository;
-        taskHistoryCache = cacheAccess.createCache("taskArtifacts", String.class, TaskHistory.class, serializer);
-    }
-
-    public History getHistory(final TaskInternal task) {
-        final TaskHistory history = loadHistory(task);
-        final LazyTaskExecution currentExecution = new LazyTaskExecution();
-        currentExecution.snapshotRepository = snapshotRepository;
-        currentExecution.cacheAccess = cacheAccess;
-        currentExecution.setOutputFiles(outputFiles(task));
-        final LazyTaskExecution previousExecution = findPreviousExecution(currentExecution, history);
-        if (previousExecution != null) {
-            previousExecution.snapshotRepository = snapshotRepository;
-            previousExecution.cacheAccess = cacheAccess;
-        }
-        history.configurations.add(0, currentExecution);
-
-        return new History() {
-            public TaskExecution getPreviousExecution() {
-                return previousExecution;
-            }
-
-            public TaskExecution getCurrentExecution() {
-                return currentExecution;
-            }
-
-            public void update() {
-                if (currentExecution.inputFilesSnapshotId == null && currentExecution.inputFilesSnapshot != null) {
-                    currentExecution.inputFilesSnapshotId = snapshotRepository.add(currentExecution.inputFilesSnapshot);
-                }
-                if (currentExecution.outputFilesSnapshotId == null && currentExecution.outputFilesSnapshot != null) {
-                    currentExecution.outputFilesSnapshotId = snapshotRepository.add(currentExecution.outputFilesSnapshot);
-                }
-                while (history.configurations.size() > TaskHistory.MAX_HISTORY_ENTRIES) {
-                    LazyTaskExecution execution = history.configurations.remove(history.configurations.size() - 1);
-                    if (execution.inputFilesSnapshotId != null) {
-                        snapshotRepository.remove(execution.inputFilesSnapshotId);
-                    }
-                    if (execution.outputFilesSnapshotId != null) {
-                        snapshotRepository.remove(execution.outputFilesSnapshotId);
-                    }
-                }
-                taskHistoryCache.put(task.getPath(), history);
-            }
-        };
-    }
-
-    private TaskHistory loadHistory(TaskInternal task) {
-        ClassLoader original = serializer.getClassLoader();
-        serializer.setClassLoader(task.getClass().getClassLoader());
-        try {
-            TaskHistory history = taskHistoryCache.get(task.getPath());
-            return history == null ? new TaskHistory() : history;
-        } finally {
-            serializer.setClassLoader(original);
-        }
-    }
-
-    private static Set<String> outputFiles(TaskInternal task) {
-        Set<String> outputFiles = new HashSet<String>();
-        for (File file : task.getOutputs().getFiles()) {
-            outputFiles.add(file.getAbsolutePath());
-        }
-        return outputFiles;
-    }
-
-    private LazyTaskExecution findPreviousExecution(TaskExecution currentExecution, TaskHistory history) {
-        Set<String> outputFiles = currentExecution.getOutputFiles();
-        LazyTaskExecution bestMatch = null;
-        int bestMatchOverlap = 0;
-        for (LazyTaskExecution configuration : history.configurations) {
-            if (outputFiles.size() == 0) {
-                if (configuration.getOutputFiles().size() == 0) {
-                    bestMatch = configuration;
-                    break;
-                }
-            }
-
-            Set<String> intersection = new HashSet<String>(outputFiles);
-            intersection.retainAll(configuration.getOutputFiles());
-            if (intersection.size() > bestMatchOverlap) {
-                bestMatch = configuration;
-                bestMatchOverlap = intersection.size();
-            }
-            if (bestMatchOverlap == outputFiles.size()) {
-                break;
-            }
-        }
-        return bestMatch;
-    }
-
-    private static class TaskHistory implements Serializable {
-        private static final int MAX_HISTORY_ENTRIES = 3;
-        private final List<LazyTaskExecution> configurations = new ArrayList<LazyTaskExecution>();
-    }
-
-    private static class LazyTaskExecution extends TaskExecution {
-        private Long inputFilesSnapshotId;
-        private Long outputFilesSnapshotId;
-        private transient FileSnapshotRepository snapshotRepository;
-        private transient FileCollectionSnapshot inputFilesSnapshot;
-        private transient FileCollectionSnapshot outputFilesSnapshot;
-        private transient TaskArtifactStateCacheAccess cacheAccess;
-
-        @Override
-        public FileCollectionSnapshot getInputFilesSnapshot() {
-            if (inputFilesSnapshot == null) {
-                inputFilesSnapshot = snapshotRepository.get(inputFilesSnapshotId);
-            }
-            return inputFilesSnapshot;
-        }
-
-        @Override
-        public void setInputFilesSnapshot(FileCollectionSnapshot inputFilesSnapshot) {
-            this.inputFilesSnapshot = inputFilesSnapshot;
-            this.inputFilesSnapshotId = null;
-        }
-
-        @Override
-        public FileCollectionSnapshot getOutputFilesSnapshot() {
-            if (outputFilesSnapshot == null) {
-                outputFilesSnapshot = cacheAccess.useCache("fetch output files", new Factory<FileCollectionSnapshot>() {
-                    public FileCollectionSnapshot create() {
-                        return snapshotRepository.get(outputFilesSnapshotId);
-                    }
-                });
-            }
-            return outputFilesSnapshot;
-        }
-
-        @Override
-        public void setOutputFilesSnapshot(FileCollectionSnapshot outputFilesSnapshot) {
-            this.outputFilesSnapshot = outputFilesSnapshot;
-            outputFilesSnapshotId = null;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheLockHandlingTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheLockHandlingTaskExecuter.java
deleted file mode 100644
index 9663558..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheLockHandlingTaskExecuter.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.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.api.internal.tasks.TaskStateInternal;
-
-public class CacheLockHandlingTaskExecuter implements TaskExecuter {
-    private final TaskExecuter executer;
-    private final TaskArtifactStateCacheAccess cacheAccess;
-
-    public CacheLockHandlingTaskExecuter(TaskExecuter executer, TaskArtifactStateCacheAccess cacheAccess) {
-        this.executer = executer;
-        this.cacheAccess = cacheAccess;
-    }
-
-    public void execute(final TaskInternal task, final TaskStateInternal state) {
-        cacheAccess.longRunningOperation(String.format("execute %s", task), new Runnable() {
-            public void run() {
-                executer.execute(task, state);
-            }
-        });
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
deleted file mode 100644
index f779d8d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.messaging.serialize.DataStreamBackedSerializer;
-
-import java.io.*;
-
-public class CachingHasher implements Hasher {
-    private final PersistentIndexedCache<File, FileInfo> cache;
-    private final Hasher hasher;
-    private long timestamp;
-
-    public CachingHasher(Hasher hasher, TaskArtifactStateCacheAccess cacheAccess) {
-        this.hasher = hasher;
-        cache = cacheAccess.createCache("fileHashes", File.class, FileInfo.class, new FileInfoSerializer());
-    }
-
-    public byte[] hash(File file) {
-        FileInfo info = cache.get(file);
-
-        long length = file.length();
-        timestamp = file.lastModified();
-        if (info != null && length == info.length && timestamp == info.timestamp) {
-            return info.hash;
-        }
-
-        byte[] hash = hasher.hash(file);
-        cache.put(file, new FileInfo(hash, length, timestamp));
-        return hash;
-    }
-
-    public static class FileInfo implements Serializable {
-        private final byte[] hash;
-        private final long timestamp;
-        private final long length;
-
-        public FileInfo(byte[] hash, long length, long timestamp) {
-            this.hash = hash;
-            this.length = length;
-            this.timestamp = timestamp;
-        }
-    }
-
-    private static class FileInfoSerializer extends DataStreamBackedSerializer<FileInfo> {
-        @Override
-        public FileInfo read(DataInput input) throws IOException {
-            int hashLength = input.readInt();
-            byte[] hash = new byte[hashLength];
-            input.readFully(hash);
-            long timestamp = input.readLong();
-            long length = input.readLong();
-            return new FileInfo(hash, length, timestamp);
-        }
-
-        @Override
-        public void write(DataOutput output, FileInfo value) throws IOException {
-            output.writeInt(value.hash.length);
-            output.write(value.hash);
-            output.writeLong(value.timestamp);
-            output.writeLong(value.length);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CompositeUpToDateRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CompositeUpToDateRule.java
deleted file mode 100644
index d739b3b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CompositeUpToDateRule.java
+++ /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.internal.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-public class CompositeUpToDateRule implements UpToDateRule {
-    private final List<UpToDateRule> rules;
-
-    public CompositeUpToDateRule(UpToDateRule... rules) {
-        this.rules = new ArrayList<UpToDateRule>(Arrays.asList(rules));
-    }
-
-    public TaskUpToDateState create(TaskInternal task, TaskExecution previousExecution, TaskExecution currentExecution) {
-        final List<TaskUpToDateState> states = new ArrayList<TaskUpToDateState>();
-        for (UpToDateRule rule : rules) {
-            states.add(rule.create(task, previousExecution, currentExecution));
-        }
-        return new TaskUpToDateState() {
-            public void checkUpToDate(Collection<String> messages) {
-                for (int i = 0; messages.isEmpty() && i < states.size(); i++) {
-                    TaskUpToDateState state = states.get(i);
-                    state.checkUpToDate(messages);
-                }
-            }
-
-            public void snapshotAfterTask() {
-                for (TaskUpToDateState state : states) {
-                    state.snapshotAfterTask();
-                }
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileCacheListener.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileCacheListener.java
deleted file mode 100644
index 50038d2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileCacheListener.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.collections.DefaultFileCollectionResolveContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-
-public class DefaultFileCacheListener implements FileCacheListener {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileCacheListener.class);
-
-    public void cacheable(FileCollection files) {
-        List<FileCollection> collections = new DefaultFileCollectionResolveContext().add(files).resolveAsFileCollections();
-        for (FileCollection collection : collections) {
-            LOGGER.debug("Can cache files for {}", collection);
-        }
-    }
-
-    public void invalidate(FileCollection files) {
-        List<FileCollection> collections = new DefaultFileCollectionResolveContext().add(files).resolveAsFileCollections();
-        for (FileCollection collection : collections) {
-            LOGGER.debug("Invalidate cached files for {}", collection);
-        }
-    }
-
-    public void invalidateAll() {
-        LOGGER.debug("Invalidate all cached files");
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java
deleted file mode 100755
index abd727b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.collections.SimpleFileCollection;
-import org.gradle.util.ChangeListener;
-import org.gradle.util.NoOpChangeListener;
-
-import java.io.File;
-import java.io.Serializable;
-import java.math.BigInteger;
-import java.util.*;
-
-public class DefaultFileSnapshotter implements FileSnapshotter {
-    private final Hasher hasher;
-
-    public DefaultFileSnapshotter(Hasher hasher) {
-        this.hasher = hasher;
-    }
-
-    public FileCollectionSnapshot emptySnapshot() {
-        return new FileCollectionSnapshotImpl(new HashMap<String, FileSnapshot>());
-    }
-
-    public FileCollectionSnapshot snapshot(FileCollection sourceFiles) {
-        Map<String, FileSnapshot> snapshots = new HashMap<String, FileSnapshot>();
-        for (File file : sourceFiles.getAsFileTree()) {
-            if (file.isFile()) {
-                snapshots.put(file.getAbsolutePath(), new FileHashSnapshot(hasher.hash(file)));
-            } else if (file.isDirectory()) {
-                snapshots.put(file.getAbsolutePath(), new DirSnapshot());
-            } else {
-                snapshots.put(file.getAbsolutePath(), new MissingFileSnapshot());
-            }
-        }
-        return new FileCollectionSnapshotImpl(snapshots);
-    }
-
-    private interface FileSnapshot extends Serializable {
-        boolean isUpToDate(FileSnapshot snapshot);
-    }
-
-    private static class FileHashSnapshot implements FileSnapshot {
-        private final byte[] hash;
-
-        public FileHashSnapshot(byte[] hash) {
-            this.hash = hash;
-        }
-
-        public boolean isUpToDate(FileSnapshot snapshot) {
-            if (!(snapshot instanceof FileHashSnapshot)) {
-                return false;
-            }
-
-            FileHashSnapshot other = (FileHashSnapshot) snapshot;
-            return Arrays.equals(hash, other.hash);
-        }
-
-        @Override
-        public String toString() {
-            return new BigInteger(1, hash).toString(16);
-        }
-    }
-
-    private static class DirSnapshot implements FileSnapshot {
-        public boolean isUpToDate(FileSnapshot snapshot) {
-            return snapshot instanceof DirSnapshot;
-        }
-    }
-
-    private static class MissingFileSnapshot implements FileSnapshot {
-        public boolean isUpToDate(FileSnapshot snapshot) {
-            return snapshot instanceof MissingFileSnapshot;
-        }
-    }
-
-    private static class FileCollectionSnapshotImpl implements FileCollectionSnapshot {
-        private final Map<String, FileSnapshot> snapshots;
-
-        public FileCollectionSnapshotImpl(Map<String, FileSnapshot> snapshots) {
-            this.snapshots = snapshots;
-        }
-
-        public FileCollection getFiles() {
-            List<File> files = new ArrayList<File>();
-            for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
-                if (entry.getValue() instanceof FileHashSnapshot) {
-                    files.add(new File(entry.getKey()));
-                }
-            }
-            return new SimpleFileCollection(files);
-        }
-
-        public void changesSince(FileCollectionSnapshot oldSnapshot, final ChangeListener<File> listener) {
-            FileCollectionSnapshotImpl other = (FileCollectionSnapshotImpl) oldSnapshot;
-            diff(snapshots, other.snapshots, new ChangeListener<Map.Entry<String, FileSnapshot>>() {
-                public void added(Map.Entry<String, FileSnapshot> element) {
-                    listener.added(new File(element.getKey()));
-                }
-
-                public void removed(Map.Entry<String, FileSnapshot> element) {
-                    listener.removed(new File(element.getKey()));
-                }
-
-                public void changed(Map.Entry<String, FileSnapshot> element) {
-                    listener.changed(new File(element.getKey()));
-                }
-            });
-        }
-
-        private void diff(Map<String, FileSnapshot> snapshots, Map<String, FileSnapshot> oldSnapshots,
-                          ChangeListener<Map.Entry<String, FileSnapshot>> listener) {
-            Map<String, FileSnapshot> otherSnapshots = new HashMap<String, FileSnapshot>(oldSnapshots);
-            for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
-                FileSnapshot otherFile = otherSnapshots.remove(entry.getKey());
-                if (otherFile == null) {
-                    listener.added(entry);
-                } else if (!entry.getValue().isUpToDate(otherFile)) {
-                    listener.changed(entry);
-                }
-            }
-            for (Map.Entry<String, FileSnapshot> entry : otherSnapshots.entrySet()) {
-                listener.removed(entry);
-            }
-        }
-
-        public Diff changesSince(final FileCollectionSnapshot oldSnapshot) {
-            final FileCollectionSnapshotImpl other = (FileCollectionSnapshotImpl) oldSnapshot;
-            return new Diff() {
-                public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot) {
-                    return applyTo(snapshot, new NoOpChangeListener<Merge>());
-                }
-
-                public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot, final ChangeListener<Merge> listener) {
-                    FileCollectionSnapshotImpl target = (FileCollectionSnapshotImpl) snapshot;
-                    final Map<String, FileSnapshot> newSnapshots = new HashMap<String, FileSnapshot>(target.snapshots);
-                    diff(snapshots, other.snapshots, new MapMergeChangeListener<String, FileSnapshot>(listener, newSnapshots));
-                    return new FileCollectionSnapshotImpl(newSnapshots);
-                }
-            };
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
deleted file mode 100644
index 23bfc30..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.util.hash.HashUtil;
-
-import java.io.File;
-
-public class DefaultHasher implements Hasher {
-    public byte[] hash(File file) {
-        return HashUtil.createHash(file, "MD5").asByteArray();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
deleted file mode 100644
index 310b159..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.internal.Factory;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.PersistentCache;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.messaging.serialize.Serializer;
-import org.gradle.cache.internal.FileLockManager;
-import org.gradle.listener.LazyCreationProxy;
-
-import java.io.File;
-
-public class DefaultTaskArtifactStateCacheAccess implements TaskArtifactStateCacheAccess {
-    private final Gradle gradle;
-    private final CacheRepository cacheRepository;
-    private PersistentCache cache;
-
-    public DefaultTaskArtifactStateCacheAccess(Gradle gradle, CacheRepository cacheRepository) {
-        this.gradle = gradle;
-        this.cacheRepository = cacheRepository;
-    }
-
-    private PersistentCache getCache() {
-        if (cache == null) {
-            cache = cacheRepository
-                    .cache("taskArtifacts")
-                    .forObject(gradle)
-                    .withDisplayName("task artifact state cache")
-                    .withLockMode(FileLockManager.LockMode.Exclusive)
-                    .open();
-        }
-        return cache;
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> createCache(final String cacheName, final Class<K> keyType, final Class<V> valueType) {
-        Factory<PersistentIndexedCache> factory = new Factory<PersistentIndexedCache>() {
-            public PersistentIndexedCache create() {
-                return getCache().createCache(cacheFile(cacheName), keyType, valueType);
-            }
-        };
-        return new LazyCreationProxy<PersistentIndexedCache>(PersistentIndexedCache.class, factory).getSource();
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> createCache(final String cacheName, final Class<K> keyType, final Class<V> valueType, final Serializer<V> valueSerializer) {
-        Factory<PersistentIndexedCache> factory = new Factory<PersistentIndexedCache>() {
-            public PersistentIndexedCache create() {
-                return getCache().createCache(cacheFile(cacheName), keyType, valueSerializer);
-            }
-        };
-        return new LazyCreationProxy<PersistentIndexedCache>(PersistentIndexedCache.class, factory).getSource();
-
-    }
-
-    private File cacheFile(String cacheName) {
-        return new File(getCache().getBaseDir(), cacheName + ".bin");
-    }
-
-    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
-        return getCache().useCache(operationDisplayName, action);
-    }
-
-    public void useCache(String operationDisplayName, Runnable action) {
-        getCache().useCache(operationDisplayName, action);
-    }
-
-    public void longRunningOperation(String operationDisplayName, Runnable action) {
-        getCache().longRunningOperation(operationDisplayName, action);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
deleted file mode 100644
index 458fb87..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
+++ /dev/null
@@ -1,172 +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.changedetection;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.TaskExecutionHistory;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.file.collections.SimpleFileCollection;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-
-import java.util.ArrayList;
-import java.util.Formatter;
-import java.util.List;
-
-import static java.util.Collections.singletonList;
-
-public class DefaultTaskArtifactStateRepository implements TaskArtifactStateRepository {
-    private static final int MAX_OUT_OF_DATE_MESSAGES = 10;
-    private static final Logger LOGGER = Logging.getLogger(DefaultTaskArtifactStateRepository.class);
-    private final TaskHistoryRepository taskHistoryRepository;
-    private final UpToDateRule upToDateRule;
-
-    public DefaultTaskArtifactStateRepository(TaskHistoryRepository taskHistoryRepository, FileSnapshotter inputFilesSnapshotter, FileSnapshotter outputFilesSnapshotter) {
-        this.taskHistoryRepository = taskHistoryRepository;
-        upToDateRule = new CompositeUpToDateRule(
-                new TaskTypeChangedUpToDateRule(),
-                new InputPropertiesChangedUpToDateRule(),
-                new OutputFilesChangedUpToDateRule(outputFilesSnapshotter),
-                new InputFilesChangedUpToDateRule(inputFilesSnapshotter));
-    }
-
-    public TaskArtifactState getStateFor(final TaskInternal task) {
-        return new TaskArtifactStateImpl(task, taskHistoryRepository.getHistory(task));
-    }
-
-    private interface TaskExecutionState {
-        List<String> isUpToDate();
-
-        boolean snapshot();
-
-        FileCollection getPreviousOutputFiles();
-    }
-
-    private static class HistoricExecution implements TaskExecutionState {
-        private final TaskInternal task;
-        private final TaskExecution lastExecution;
-        private boolean upToDate;
-        private final UpToDateRule rule;
-        private TaskExecution thisExecution;
-        private UpToDateRule.TaskUpToDateState upToDateState;
-
-        public HistoricExecution(TaskInternal task, TaskHistoryRepository.History history, UpToDateRule rule) {
-            this.task = task;
-            this.lastExecution = history.getPreviousExecution();
-            this.thisExecution = history.getCurrentExecution();
-            this.rule = rule;
-        }
-
-        private void calcCurrentState() {
-            if (upToDateState != null) {
-                return;
-            }
-
-            // Calculate initial state - note this is potentially expensive
-            upToDateState = rule.create(task, lastExecution, thisExecution);
-        }
-
-        public FileCollection getPreviousOutputFiles() {
-            return lastExecution != null && lastExecution.getOutputFilesSnapshot() != null ? lastExecution.getOutputFilesSnapshot().getFiles() : new SimpleFileCollection();
-        }
-
-        public List<String> isUpToDate() {
-            calcCurrentState();
-
-            // Now determine if we're out of date
-            if (lastExecution == null) {
-                return singletonList(String.format("No history is available for %s.", task));
-            }
-
-            List<String> messages = new ArrayList<String>();
-            upToDateState.checkUpToDate(messages);
-
-            if (messages.isEmpty()) {
-                upToDate = true;
-            }
-            return messages;
-        }
-
-        public boolean snapshot() {
-            calcCurrentState();
-            
-            if (upToDate) {
-                return false;
-            }
-
-            upToDateState.snapshotAfterTask();
-            return true;
-        }
-    }
-
-    private class TaskArtifactStateImpl implements TaskArtifactState, TaskExecutionHistory {
-        private final TaskInternal task;
-        private final TaskHistoryRepository.History history;
-        private final TaskExecutionState execution;
-
-        public TaskArtifactStateImpl(TaskInternal task, TaskHistoryRepository.History history) {
-            this.task = task;
-            this.history = history;
-            execution = getExecution();
-        }
-
-        public boolean isUpToDate() {
-            List<String> messages = execution.isUpToDate();
-            if (messages == null || messages.isEmpty()) {
-                LOGGER.info("Skipping {} as it is up-to-date.", task);
-                return true;
-            }
-            if (LOGGER.isInfoEnabled()) {
-                Formatter formatter = new Formatter();
-                formatter.format("Executing %s due to:", task);
-                for (int i = 0; i < messages.size() && i < MAX_OUT_OF_DATE_MESSAGES; i++) {
-                    String message = messages.get(i);
-                    formatter.format("%n%s", message);
-                }
-                if (messages.size() > MAX_OUT_OF_DATE_MESSAGES) {
-                    formatter.format("%n%d more ...", messages.size() - MAX_OUT_OF_DATE_MESSAGES);
-                }
-                LOGGER.info(formatter.toString());
-            }
-            return false;
-        }
-
-        public FileCollection getOutputFiles() {
-            return execution.getPreviousOutputFiles();
-        }
-
-        public TaskExecutionHistory getExecutionHistory() {
-            return this;
-        }
-
-        public TaskExecutionState getExecution() {
-            return new HistoricExecution(task, history, upToDateRule);
-        }
-
-        public void afterTask() {
-            if (execution.snapshot()) {
-                history.update();
-            }
-        }
-
-        public void beforeTask() {
-        }
-
-        public void finished() {
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCacheBroadcastTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCacheBroadcastTaskArtifactStateRepository.java
deleted file mode 100644
index c23ccad..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCacheBroadcastTaskArtifactStateRepository.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.internal.TaskExecutionHistory;
-import org.gradle.api.internal.TaskInternal;
-
-public class FileCacheBroadcastTaskArtifactStateRepository implements TaskArtifactStateRepository {
-    private final TaskArtifactStateRepository repository;
-    private final FileCacheListener listener;
-
-    public FileCacheBroadcastTaskArtifactStateRepository(TaskArtifactStateRepository repository, FileCacheListener listener) {
-        this.repository = repository;
-        this.listener = listener;
-    }
-
-    public TaskArtifactState getStateFor(final TaskInternal task) {
-        final TaskArtifactState state = repository.getStateFor(task);
-        return new TaskArtifactState() {
-            public boolean isUpToDate() {
-                listener.cacheable(task.getInputs().getFiles());
-                listener.cacheable(task.getOutputs().getFiles());
-
-                return state.isUpToDate();
-            }
-
-            public void beforeTask() {
-                if (task.getOutputs().getHasOutput()) {
-                    listener.invalidate(task.getOutputs().getFiles());
-                } else {
-                    listener.invalidateAll();
-                }
-                state.beforeTask();
-            }
-
-            public void afterTask() {
-                listener.cacheable(task.getOutputs().getFiles());
-                state.afterTask();
-            }
-
-            public void finished() {
-                state.finished();
-            }
-
-            public TaskExecutionHistory getExecutionHistory() {
-                return state.getExecutionHistory();
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCacheListener.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCacheListener.java
deleted file mode 100644
index 1906d55..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCacheListener.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.file.FileCollection;
-
-public interface FileCacheListener {
-    /**
-     * Indicates that the given files are cacheable, and are not expected to change until invalidated.
-     */
-    void cacheable(FileCollection files);
-
-    /**
-     * Indicates that the given files may have changed, and should no longer be considered cacheable.
-     */
-    void invalidate(FileCollection files);
-
-    /**
-     * Indicates that any file may have changed, and should no longer be considered cacheable.
-     */
-    void invalidateAll();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java
deleted file mode 100755
index b3f0cac..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.util.ChangeListener;
-
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * An immutable snapshot of the contents of a collection of files.
- */
-public interface FileCollectionSnapshot extends Serializable {
-    void changesSince(FileCollectionSnapshot oldSnapshot, ChangeListener<File> listener);
-
-    Diff changesSince(FileCollectionSnapshot oldSnapshot);
-
-    FileCollection getFiles();
-
-    public interface Diff {
-        /**
-         * Applies this diff to the given snapshot. Adds any added or changed files in this diff to the given snapshot.
-         * Removes any removed files in this diff from the given snapshot.
-         *
-         * @param snapshot the snapshot to apply the changes to.
-         * @param listener the listener to notify of changes. The listener can veto a particular change.
-         * @return an updated copy of the provided snapshot
-         */
-        FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot, ChangeListener<Merge> listener);
-
-        /**
-         * Applies this diff to the given snapshot. Adds any added or changed files in this diff to the given snapshot.
-         * Removes any removed files in this diff from the given snapshot.
-         *
-         * @param snapshot the snapshot to apply the changes to.
-         * @return an updated copy of the provided snapshot
-         */
-        FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot);
-    }
-
-    public interface Merge {
-        void ignore();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotRepository.java
deleted file mode 100644
index b83e755..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotRepository.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.changedetection;
-
-public interface FileSnapshotRepository {
-    FileCollectionSnapshot get(Long id);
-
-    Long add(FileCollectionSnapshot snapshot);
-
-    void remove(Long id);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java
deleted file mode 100755
index fa65cfc..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.file.FileCollection;
-
-public interface FileSnapshotter {
-    /**
-     * Creates an empty snapshot, which changes can be later merged into.
-     *
-     * @return The snapshot.
-     */
-    FileCollectionSnapshot emptySnapshot();
-
-    /**
-     * Creates a snapshot of the contents of the given collection
-     *
-     * @param files The files to snapshot
-     * @return The snapshot.
-     */
-    FileCollectionSnapshot snapshot(FileCollection files);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/Hasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/Hasher.java
deleted file mode 100644
index db4ac84..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/Hasher.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import java.io.File;
-
-public interface Hasher {
-    byte[] hash(File file);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
deleted file mode 100644
index e03e6c3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.serialize.Serializer;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A simple in-memory cache, used by the testing fixtures.
- */
-public class InMemoryIndexedCache<K, V> implements PersistentIndexedCache<K, V> {
-    private final Map<Object, byte[]> entries = new HashMap<Object, byte[]>();
-    private final Serializer<V> valueSerializer;
-
-    public InMemoryIndexedCache(Serializer<V> valueSerializer) {
-        this.valueSerializer = valueSerializer;
-    }
-
-    public V get(K key) {
-        byte[] serialised = entries.get(key);
-        if (serialised == null) {
-            return null;
-        }
-        try {
-            ByteArrayInputStream instr = new ByteArrayInputStream(serialised);
-            return valueSerializer.read(instr);
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public void put(K key, V value) {
-        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
-        try {
-            valueSerializer.write(outstr, value);
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-
-        entries.put(key, outstr.toByteArray());
-    }
-
-    public void remove(K key) {
-        entries.remove(key);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InputFilesChangedUpToDateRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InputFilesChangedUpToDateRule.java
deleted file mode 100644
index 7df4355..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InputFilesChangedUpToDateRule.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.util.ChangeListener;
-
-import java.io.File;
-import java.util.Collection;
-
-/**
- * A rule which marks a task out-of-date when its input files change.
- */
-public class InputFilesChangedUpToDateRule implements UpToDateRule {
-    private final FileSnapshotter inputFilesSnapshotter;
-
-    public InputFilesChangedUpToDateRule(FileSnapshotter inputFilesSnapshotter) {
-        this.inputFilesSnapshotter = inputFilesSnapshotter;
-    }
-
-    public TaskUpToDateState create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution) {
-        final FileCollectionSnapshot inputFilesSnapshot = inputFilesSnapshotter.snapshot(task.getInputs().getFiles());
-
-        return new TaskUpToDateState() {
-            public void checkUpToDate(final Collection<String> messages) {
-                if (previousExecution.getInputFilesSnapshot() == null) {
-                    messages.add(String.format("Input file history is not available for %s.", task));
-                    return;
-                }
-                inputFilesSnapshot.changesSince(previousExecution.getInputFilesSnapshot(), new ChangeListener<File>() {
-                    public void added(File file) {
-                        messages.add(String.format("Input file %s for %s added.", file, task));
-                    }
-
-                    public void removed(File file) {
-                        messages.add(String.format("Input file %s for %s removed.", file, task));
-                    }
-
-                    public void changed(File file) {
-                        messages.add(String.format("Input file %s for %s has changed.", file, task));
-                    }
-                });
-            }
-
-            public void snapshotAfterTask() {
-                currentExecution.setInputFilesSnapshot(inputFilesSnapshot);
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InputPropertiesChangedUpToDateRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InputPropertiesChangedUpToDateRule.java
deleted file mode 100644
index cbff546..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InputPropertiesChangedUpToDateRule.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.util.ChangeListener;
-import org.gradle.util.DiffUtil;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A rule which marks a task out-of-date when its input properties change.
- */
-public class InputPropertiesChangedUpToDateRule implements UpToDateRule {
-    public TaskUpToDateState create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution) {
-        final Map<String, Object> properties = new HashMap<String, Object>(task.getInputs().getProperties());
-        currentExecution.setInputProperties(properties);
-
-        return new TaskUpToDateState() {
-            public void checkUpToDate(final Collection<String> messages) {
-                DiffUtil.diff(properties, previousExecution.getInputProperties(), new ChangeListener<Map.Entry<String, Object>>() {
-                    public void added(Map.Entry<String, Object> element) {
-                        messages.add(String.format("Input property '%s' has been added for %s", element.getKey(), task));
-                    }
-
-                    public void removed(Map.Entry<String, Object> element) {
-                        messages.add(String.format("Input property '%s' has been removed for %s", element.getKey(), task));
-                    }
-
-                    public void changed(Map.Entry<String, Object> element) {
-                        messages.add(String.format("Value of input property '%s' has changed for %s", element.getKey(), task));
-                    }
-                });
-            }
-
-            public void snapshotAfterTask() {
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/MapMergeChangeListener.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/MapMergeChangeListener.java
deleted file mode 100644
index 4c1e52e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/MapMergeChangeListener.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.util.ChangeListener;
-
-import java.util.Map;
-
-class MapMergeChangeListener<K, V> implements ChangeListener<Map.Entry<K, V>> {
-    private final ChangeListener<FileCollectionSnapshot.Merge> listener;
-    private final Map<K, V> newSnapshots;
-
-    public MapMergeChangeListener(ChangeListener<FileCollectionSnapshot.Merge> listener, Map<K, V> targetMap) {
-        this.listener = listener;
-        this.newSnapshots = targetMap;
-    }
-
-    public void added(Map.Entry<K, V> element) {
-        DefaultMerge merge = new DefaultMerge();
-        listener.added(merge);
-        if (!merge.isIgnore()) {
-            newSnapshots.put(element.getKey(), element.getValue());
-        }
-    }
-
-    public void removed(Map.Entry<K, V> element) {
-        DefaultMerge merge = new DefaultMerge();
-        listener.removed(merge);
-        if (!merge.isIgnore()) {
-            newSnapshots.remove(element.getKey());
-        }
-    }
-
-    public void changed(Map.Entry<K, V> element) {
-        DefaultMerge merge = new DefaultMerge();
-        listener.changed(merge);
-        if (!merge.isIgnore()) {
-            newSnapshots.put(element.getKey(), element.getValue());
-        }
-    }
-
-    private static class DefaultMerge implements FileCollectionSnapshot.Merge {
-        private boolean ignore;
-
-        public boolean isIgnore() {
-            return ignore;
-        }
-
-        public void ignore() {
-            ignore = true;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesChangedUpToDateRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesChangedUpToDateRule.java
deleted file mode 100644
index 57eca84..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesChangedUpToDateRule.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.util.ChangeListener;
-
-import java.io.File;
-import java.util.Collection;
-
-/**
- * A rule which marks a task out-of-date when its output files change.
- */
-public class OutputFilesChangedUpToDateRule implements UpToDateRule {
-    private final FileSnapshotter outputFilesSnapshotter;
-
-    public OutputFilesChangedUpToDateRule(FileSnapshotter outputFilesSnapshotter) {
-        this.outputFilesSnapshotter = outputFilesSnapshotter;
-    }
-
-    public TaskUpToDateState create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution) {
-        final FileCollectionSnapshot outputFilesBefore = outputFilesSnapshotter.snapshot(task.getOutputs().getFiles());
-
-        return new TaskUpToDateState() {
-            public void checkUpToDate(final Collection<String> messages) {
-                if (previousExecution.getOutputFilesSnapshot() == null) {
-                    messages.add(String.format("Output file history is not available for %s.", task));
-                    return;
-                }
-                outputFilesBefore.changesSince(previousExecution.getOutputFilesSnapshot(), new ChangeListener<File>() {
-                    public void added(File element) {
-                        messages.add(String.format("Output file '%s' has been added for %s.", element, task));
-                    }
-
-                    public void removed(File element) {
-                        messages.add(String.format("Output file %s has been removed for %s.", element.getAbsolutePath(), task));
-                    }
-
-                    public void changed(File element) {
-                        messages.add(String.format("Output file %s for %s has changed.", element.getAbsolutePath(), task));
-                    }
-                });
-            }
-
-            public void snapshotAfterTask() {
-                FileCollectionSnapshot lastExecutionOutputFiles;
-                if (previousExecution == null || previousExecution.getOutputFilesSnapshot() == null) {
-                    lastExecutionOutputFiles = outputFilesSnapshotter.emptySnapshot();
-                } else {
-                    lastExecutionOutputFiles = previousExecution.getOutputFilesSnapshot();
-                }
-                FileCollectionSnapshot newOutputFiles = outputFilesBefore.changesSince(lastExecutionOutputFiles).applyTo(
-                        lastExecutionOutputFiles, new ChangeListener<FileCollectionSnapshot.Merge>() {
-                            public void added(FileCollectionSnapshot.Merge element) {
-                                // Ignore added files
-                                element.ignore();
-                            }
-
-                            public void removed(FileCollectionSnapshot.Merge element) {
-                                // Discard any files removed since the task was last executed
-                            }
-
-                            public void changed(FileCollectionSnapshot.Merge element) {
-                                // Update any files which were change since the task was last executed
-                            }
-                        });
-                FileCollectionSnapshot outputFilesAfter = outputFilesSnapshotter.snapshot(task.getOutputs().getFiles());
-                currentExecution.setOutputFilesSnapshot(outputFilesAfter.changesSince(outputFilesBefore).applyTo(newOutputFiles));
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
deleted file mode 100644
index 21dfeb3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.util.ChangeListener;
-import org.gradle.util.DiffUtil;
-import org.gradle.util.NoOpChangeListener;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Takes a snapshot of the output files of a task. 2 parts to the algorithm:
- *
- * <ul>
- * <li>Collect the unique id for each output file and directory. The unique id is generated when we notice that
- * a file/directory has been created. The id is regenerated when the file/directory is deleted.</li>
- *
- * <li>Collect the hash of each output file and each file in each output directory.</li>
- * </ul>
- *
- */
-public class OutputFilesSnapshotter implements FileSnapshotter {
-    private final FileSnapshotter snapshotter;
-    private final IdGenerator<Long> idGenerator;
-    private final PersistentIndexedCache<String, Long> dirIdentiferCache;
-
-    public OutputFilesSnapshotter(FileSnapshotter snapshotter, IdGenerator<Long> idGenerator,
-                                  TaskArtifactStateCacheAccess cacheAccess) {
-        this.snapshotter = snapshotter;
-        this.idGenerator = idGenerator;
-        dirIdentiferCache = cacheAccess.createCache("outputFileStates", String.class, Long.class);
-    }
-
-    public FileCollectionSnapshot emptySnapshot() {
-        return new OutputFilesSnapshot(new HashMap<String, Long>(), snapshotter.emptySnapshot());
-    }
-
-    public FileCollectionSnapshot snapshot(FileCollection files) {
-        Map<String, Long> snapshotDirIds = new HashMap<String, Long>();
-        for (File file : files) {
-            Long dirId;
-            if (file.exists()) {
-                dirId = dirIdentiferCache.get(file.getAbsolutePath());
-                if (dirId == null) {
-                    dirId = idGenerator.generateId();
-                    dirIdentiferCache.put(file.getAbsolutePath(), dirId);
-                }
-            } else {
-                dirIdentiferCache.remove(file.getAbsolutePath());
-                dirId = null;
-            }
-            snapshotDirIds.put(file.getAbsolutePath(), dirId);
-        }
-        return new OutputFilesSnapshot(snapshotDirIds, snapshotter.snapshot(files));
-    }
-
-    private static class OutputFilesSnapshot implements FileCollectionSnapshot {
-        private final Map<String, Long> rootFileIds;
-        private final FileCollectionSnapshot filesSnapshot;
-
-        public OutputFilesSnapshot(Map<String, Long> rootFileIds, FileCollectionSnapshot filesSnapshot) {
-            this.rootFileIds = rootFileIds;
-            this.filesSnapshot = filesSnapshot;
-        }
-
-        public FileCollection getFiles() {
-            return filesSnapshot.getFiles();
-        }
-
-        public Diff changesSince(final FileCollectionSnapshot oldSnapshot) {
-            OutputFilesSnapshot other = (OutputFilesSnapshot) oldSnapshot;
-            return new OutputFilesDiff(rootFileIds, other.rootFileIds, filesSnapshot.changesSince(other.filesSnapshot));
-        }
-
-        public void changesSince(FileCollectionSnapshot oldSnapshot, final ChangeListener<File> listener) {
-            final OutputFilesSnapshot other = (OutputFilesSnapshot) oldSnapshot;
-            DiffUtil.diff(rootFileIds, other.rootFileIds, new ChangeListener<Map.Entry<String, Long>>() {
-                public void added(Map.Entry<String, Long> element) {
-                    listener.added(new File(element.getKey()));
-                }
-
-                public void removed(Map.Entry<String, Long> element) {
-                    listener.removed(new File(element.getKey()));
-                }
-
-                public void changed(Map.Entry<String, Long> element) {
-                    if (other.rootFileIds.get(element.getKey()) == null) {
-                        // Dir used to not exist, now does. Don't care
-                        return;
-                    }
-                    listener.changed(new File(element.getKey()));
-                }
-            });
-            filesSnapshot.changesSince(other.filesSnapshot, new ChangeListener<File>() {
-                public void added(File element) {
-                    // Ignore files added to output dirs which have been added since last time task executed
-                }
-
-                public void removed(File element) {
-                    listener.removed(element);
-                }
-
-                public void changed(File element) {
-                    listener.changed(element);
-                }
-            });
-        }
-    }
-
-    private static class OutputFilesDiff implements FileCollectionSnapshot.Diff {
-        private final Map<String, Long> newFileIds;
-        private final Map<String, Long> oldFileIds;
-        private final FileCollectionSnapshot.Diff filesDiff;
-
-        public OutputFilesDiff(Map<String, Long> newFileIds, Map<String, Long> oldFileIds,
-                               FileCollectionSnapshot.Diff filesDiff) {
-            this.newFileIds = newFileIds;
-            this.oldFileIds = oldFileIds;
-            this.filesDiff = filesDiff;
-        }
-
-        public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot,
-                                              ChangeListener<FileCollectionSnapshot.Merge> listener) {
-            OutputFilesSnapshot other = (OutputFilesSnapshot) snapshot;
-            Map<String, Long> dirIds = new HashMap<String, Long>(other.rootFileIds);
-            DiffUtil.diff(newFileIds, oldFileIds, new MapMergeChangeListener<String, Long>(
-                    new NoOpChangeListener<FileCollectionSnapshot.Merge>(), dirIds));
-            return new OutputFilesSnapshot(newFileIds, filesDiff.applyTo(other.filesSnapshot, listener));
-        }
-
-        public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot) {
-            return applyTo(snapshot, new NoOpChangeListener<FileCollectionSnapshot.Merge>());
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
deleted file mode 100755
index 7b490be..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
+++ /dev/null
@@ -1,96 +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.changedetection;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.StartParameter;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.TaskExecutionHistory;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-
-public class ShortCircuitTaskArtifactStateRepository implements TaskArtifactStateRepository {
-    private static final Logger LOGGER = Logging.getLogger(ShortCircuitTaskArtifactStateRepository.class);
-    private final StartParameter startParameter;
-    private final TaskArtifactStateRepository repository;
-
-    public ShortCircuitTaskArtifactStateRepository(StartParameter startParameter, TaskArtifactStateRepository repository) {
-        this.startParameter = startParameter;
-        this.repository = repository;
-    }
-
-    public TaskArtifactState getStateFor(final TaskInternal task) {
-        if (task.getOutputs().getHasOutput()) {
-            return new ShortCircuitArtifactState(task, repository.getStateFor(task));
-        }
-        LOGGER.info(String.format("%s has not declared any outputs, assuming that it is out-of-date.", StringUtils.capitalize(task.toString())));
-        return new NoHistoryArtifactState();
-    }
-
-    private static class NoHistoryArtifactState implements TaskArtifactState, TaskExecutionHistory {
-        public boolean isUpToDate() {
-            return false;
-        }
-
-        public void beforeTask() {
-        }
-
-        public void afterTask() {
-        }
-
-        public void finished() {
-        }
-
-        public TaskExecutionHistory getExecutionHistory() {
-            return this;
-        }
-
-        public FileCollection getOutputFiles() {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    private class ShortCircuitArtifactState implements TaskArtifactState {
-        private final TaskInternal task;
-        private final TaskArtifactState state;
-
-        public ShortCircuitArtifactState(TaskInternal task, TaskArtifactState state) {
-            this.task = task;
-            this.state = state;
-        }
-
-        public boolean isUpToDate() {
-            return !startParameter.isRerunTasks() && task.getOutputs().getUpToDateSpec().isSatisfiedBy(task) && state.isUpToDate();
-        }
-
-        public TaskExecutionHistory getExecutionHistory() {
-            return state.getExecutionHistory();
-        }
-
-        public void beforeTask() {
-            state.beforeTask();
-        }
-
-        public void afterTask() {
-            state.afterTask();
-        }
-
-        public void finished() {
-            state.finished();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java
index 5fe4110..e095d26 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java
@@ -16,6 +16,9 @@
 package org.gradle.api.internal.changedetection;
 
 import org.gradle.api.internal.TaskExecutionHistory;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+
+import java.util.Collection;
 
 /**
  * Encapsulates the state of the task when its outputs were last generated.
@@ -23,11 +26,15 @@ import org.gradle.api.internal.TaskExecutionHistory;
 public interface TaskArtifactState {
     /**
      * Returns true if the task outputs were generated using the given task inputs.
+     *
+     * @param messages a collection to add messages which explain why the task is out-of-date.
      */
-    boolean isUpToDate();
+    boolean isUpToDate(Collection<String> messages);
+
+    IncrementalTaskInputs getInputChanges();
 
     /**
-     * Called before the task is to be executed. Note that {@link #isUpToDate()} may not necessarily have been called.
+     * Called before the task is to be executed. Note that {@link #isUpToDate(java.util.Collection)} may not necessarily have been called.
      */
     void beforeTask();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
deleted file mode 100644
index bf1525d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.internal.Factory;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.messaging.serialize.Serializer;
-
-public interface TaskArtifactStateCacheAccess {
-    /**
-     * Performs some work against the cache. Acquires exclusive locks the appropriate resources, so that the given action is the only
-     * action to execute across all processes (including this one). Releases the locks and all resources at the end of the action.
-     *
-     * <p>This method is re-entrant, so that an action can call back into this method.</p>
-     */
-    <T> T useCache(String operationDisplayName, Factory<? extends T> action);
-
-    /**
-     * Performs some work against the cache. Acquires exclusive locks the appropriate resources, so that the given action is the only
-     * action to execute across all processes (including this one). Releases the locks and all resources at the end of the action.
-     *
-     * <p>This method is re-entrant, so that an action can call back into this method.</p>
-     */
-    void useCache(String operationDisplayName, Runnable action);
-
-    /**
-     * Performs some long running operation. Releases all locks while the operation is running, and reacquires the locks at the end of
-     * the long running operation.
-     *
-     * <p>This method is re-entrant, so that an action can call back into this method.</p>
-     */
-    void longRunningOperation(String operationDisplayName, Runnable action);
-
-    <K, V> PersistentIndexedCache createCache(String cacheName, Class<K> keyType, Class<V> valueType);
-
-    <K, V> PersistentIndexedCache<K, V> createCache(String cacheName, Class<K> keyType, Class<V> valueType, Serializer<V> valueSerializer);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskCacheLockHandlingBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskCacheLockHandlingBuildExecuter.java
deleted file mode 100644
index 3dfbf20..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskCacheLockHandlingBuildExecuter.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.execution.BuildExecutionAction;
-import org.gradle.execution.BuildExecutionContext;
-
-public class TaskCacheLockHandlingBuildExecuter implements BuildExecutionAction {
-    private final TaskArtifactStateCacheAccess cacheAccess;
-
-    public TaskCacheLockHandlingBuildExecuter(TaskArtifactStateCacheAccess cacheAccess) {
-        this.cacheAccess = cacheAccess;
-    }
-
-    public void execute(final BuildExecutionContext context) {
-        cacheAccess.useCache("execute tasks", new Runnable(){
-            public void run() {
-                context.proceed();
-            }
-        });
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskExecution.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskExecution.java
deleted file mode 100644
index 16aff96..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskExecution.java
+++ /dev/null
@@ -1,67 +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.changedetection;
-
-import java.io.Serializable;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The persistent state for a single task execution.
- */
-public abstract class TaskExecution implements Serializable {
-    private String taskClass;
-    private Map<String, Object> inputProperties;
-    private Set<String> outputFiles;
-
-    public Set<String> getOutputFiles() {
-        return outputFiles;
-    }
-
-    public void setOutputFiles(Set<String> outputFiles) {
-        this.outputFiles = outputFiles;
-    }
-
-    public String getTaskClass() {
-        return taskClass;
-    }
-
-    public void setTaskClass(String taskClass) {
-        this.taskClass = taskClass;
-    }
-
-    public Map<String, Object> getInputProperties() {
-        return inputProperties;
-    }
-
-    public void setInputProperties(Map<String, Object> inputProperties) {
-        this.inputProperties = inputProperties;
-    }
-
-    /**
-     * @return May return null.
-     */
-    public abstract FileCollectionSnapshot getOutputFilesSnapshot();
-
-    public abstract void setOutputFilesSnapshot(FileCollectionSnapshot outputFilesSnapshot);
-
-    /**
-     * @return May return null.
-     */
-    public abstract FileCollectionSnapshot getInputFilesSnapshot();
-
-    public abstract void setInputFilesSnapshot(FileCollectionSnapshot inputFilesSnapshot);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskHistoryRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskHistoryRepository.java
deleted file mode 100644
index 9f71553..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskHistoryRepository.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-
-public interface TaskHistoryRepository {
-    History getHistory(TaskInternal task);
-
-    interface History {
-        TaskExecution getPreviousExecution();
-
-        TaskExecution getCurrentExecution();
-
-        void update();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskTypeChangedUpToDateRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskTypeChangedUpToDateRule.java
deleted file mode 100644
index c166808..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskTypeChangedUpToDateRule.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.api.internal.changedetection;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.internal.TaskInternal;
-
-import java.util.Collection;
-
-/**
- * A rule which marks a task out-of-date when its implementation class changes.
- */
-public class TaskTypeChangedUpToDateRule implements UpToDateRule {
-    public TaskUpToDateState create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution) {
-        final String taskClass = task.getClass().getName();
-        currentExecution.setTaskClass(taskClass);
-
-        return new TaskUpToDateState() {
-            public void checkUpToDate(Collection<String> messages) {
-                if (!taskClass.equals(previousExecution.getTaskClass())) {
-                    messages.add(String.format("%s has changed type from '%s' to '%s'.", StringUtils.capitalize(task.toString()), previousExecution.getTaskClass(), task.getClass().getName()));
-                }
-            }
-
-            public void snapshotAfterTask() {
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/UpToDateRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/UpToDateRule.java
deleted file mode 100644
index 2f47511..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/UpToDateRule.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.changedetection;
-
-import org.gradle.api.internal.TaskInternal;
-
-import java.util.Collection;
-
-public interface UpToDateRule {
-    /**
-     * Creates the transient state for the given task.
-     *
-     * @param task The task to be executed.
-     * @param previousExecution The previous execution for this task, if any. May be null.
-     * @param currentExecution The current execution. The rule may mutate this.
-     * @return The state.
-     */
-    TaskUpToDateState create(TaskInternal task, TaskExecution previousExecution, TaskExecution currentExecution);
-
-    interface TaskUpToDateState {
-        /**
-         * Checks if the task is up-to-date. If so, this method must add at least 1 message explaining why the task is out-of-date to the given collection. Note that this method may not be called for
-         * a given execution. Also note, this method is called only when the previous execution is not null.
-         *
-         * @param messages The out-of-date messages.
-         */
-        void checkUpToDate(Collection<String> messages);
-
-        /**
-         * Snapshot any final state after the task has executed. This method is executed only if the task is to be executed. Any persistent state should be added to the {@link TaskExecution} object
-         * passed to {@link UpToDateRule#create}.
-         */
-        void snapshotAfterTask();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java
new file mode 100644
index 0000000..590f179
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.changes;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.changedetection.rules.TaskStateChange;
+import org.gradle.api.internal.changedetection.rules.TaskStateChanges;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChangesOnlyIncrementalTaskInputs extends StatefulIncrementalTaskInputs {
+    private final TaskStateChanges inputFilesState;
+    private List<InputFileDetails> removedFiles = new ArrayList<InputFileDetails>();
+
+    public ChangesOnlyIncrementalTaskInputs(TaskStateChanges inputFilesState) {
+        this.inputFilesState = inputFilesState;
+    }
+
+    public boolean isIncremental() {
+        return true;
+    }
+
+    @Override
+    protected void doOutOfDate(final Action<? super InputFileDetails> outOfDateAction) {
+        for (TaskStateChange change : inputFilesState) {
+            InputFileDetails fileChange = (InputFileDetails) change;
+            if (fileChange.isRemoved()) {
+                removedFiles.add(fileChange);
+            } else {
+                outOfDateAction.execute(fileChange);
+            }
+        }
+    }
+
+    @Override
+    protected void doRemoved(Action<? super InputFileDetails> removedAction) {
+        for (InputFileDetails removedFile : removedFiles) {
+            removedAction.execute(removedFile);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/DefaultTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/DefaultTaskArtifactStateRepository.java
new file mode 100644
index 0000000..4a727c9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/DefaultTaskArtifactStateRepository.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.changes;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskExecutionHistory;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
+import org.gradle.api.internal.changedetection.rules.TaskStateChange;
+import org.gradle.api.internal.changedetection.rules.TaskStateChanges;
+import org.gradle.api.internal.changedetection.rules.TaskUpToDateState;
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+import org.gradle.api.internal.changedetection.state.TaskHistoryRepository;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class DefaultTaskArtifactStateRepository implements TaskArtifactStateRepository {
+
+    private final TaskHistoryRepository taskHistoryRepository;
+    private final FileCollectionSnapshotter outputFilesSnapshotter;
+    private final FileCollectionSnapshotter inputFilesSnapshotter;
+    private final Instantiator instantiator;
+
+    public DefaultTaskArtifactStateRepository(TaskHistoryRepository taskHistoryRepository, Instantiator instantiator,
+                                              FileCollectionSnapshotter outputFilesSnapshotter, FileCollectionSnapshotter inputFilesSnapshotter) {
+        this.taskHistoryRepository = taskHistoryRepository;
+        this.instantiator = instantiator;
+        this.outputFilesSnapshotter = outputFilesSnapshotter;
+        this.inputFilesSnapshotter = inputFilesSnapshotter;
+    }
+
+    public TaskArtifactState getStateFor(final TaskInternal task) {
+        return new TaskArtifactStateImpl(task, taskHistoryRepository.getHistory(task));
+    }
+
+    private class TaskArtifactStateImpl implements TaskArtifactState, TaskExecutionHistory {
+        private final TaskInternal task;
+        private final TaskHistoryRepository.History history;
+        private boolean upToDate;
+        private TaskUpToDateState states;
+
+        public TaskArtifactStateImpl(TaskInternal task, TaskHistoryRepository.History history) {
+            this.task = task;
+            this.history = history;
+        }
+
+        public boolean isUpToDate(Collection<String> messages) {
+            final List<String> reasons = getChangeMessages(getStates().getAllTaskChanges());
+            messages.addAll(reasons);
+            if (reasons.isEmpty()) {
+                upToDate = true;
+                return true;
+            }
+            return false;
+        }
+
+        private List<String> getChangeMessages(TaskStateChanges stateChanges) {
+            final List<String> messages = new ArrayList<String>();
+            for (TaskStateChange stateChange : stateChanges) {
+                messages.add(stateChange.getMessage());
+            }
+            return messages;
+        }
+
+        public IncrementalTaskInputs getInputChanges() {
+            assert !upToDate : "Should not be here if the task is up-to-date";
+
+            if (canPerformIncrementalBuild()) {
+                return instantiator.newInstance(ChangesOnlyIncrementalTaskInputs.class, getStates().getInputFilesChanges());
+            }
+            return instantiator.newInstance(RebuildIncrementalTaskInputs.class, task);
+        }
+
+        private boolean canPerformIncrementalBuild() {
+            final List<String> messages = getChangeMessages(getStates().getRebuildChanges());
+            return messages.isEmpty();
+        }
+
+        public FileCollection getOutputFiles() {
+            TaskExecution lastExecution = history.getPreviousExecution();
+            return lastExecution != null && lastExecution.getOutputFilesSnapshot() != null ? lastExecution.getOutputFilesSnapshot().getFiles() : new SimpleFileCollection();
+        }
+
+        public TaskExecutionHistory getExecutionHistory() {
+            return this;
+        }
+
+        public void beforeTask() {
+        }
+
+        public void afterTask() {
+            if (upToDate) {
+                return;
+            }
+
+            getStates().getAllTaskChanges().snapshotAfterTask();
+            history.update();
+        }
+
+        public void finished() {}
+
+        private TaskUpToDateState getStates() {
+            if (states == null) {
+                // Calculate initial state - note this is potentially expensive
+                states = new TaskUpToDateState(task, history, outputFilesSnapshotter, inputFilesSnapshotter);
+            }
+            return states;
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/NoHistoryArtifactState.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/NoHistoryArtifactState.java
new file mode 100644
index 0000000..3577a34
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/NoHistoryArtifactState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.changes;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskExecutionHistory;
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+
+import java.util.Collection;
+
+class NoHistoryArtifactState implements TaskArtifactState, TaskExecutionHistory {
+    public boolean isUpToDate(Collection<String> messages) {
+        messages.add("Task has not declared any outputs.");
+        return false;
+    }
+
+    public IncrementalTaskInputs getInputChanges() {
+        throw new UnsupportedOperationException();
+    }
+
+    public TaskExecutionHistory getExecutionHistory() {
+        return this;
+    }
+
+    public void beforeTask() {
+    }
+
+    public void afterTask() {
+    }
+
+    public void finished() {
+    }
+
+    public FileCollection getOutputFiles() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java
new file mode 100644
index 0000000..e227766
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.changes;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class RebuildIncrementalTaskInputs extends StatefulIncrementalTaskInputs {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RebuildIncrementalTaskInputs.class);
+
+    private final Task task;
+
+    public RebuildIncrementalTaskInputs(Task task) {
+        LOGGER.info("All input files are considered out-of-date for incremental {}.", task);
+        this.task = task;
+    }
+
+    public boolean isIncremental() {
+        return false;
+    }
+
+    public void doOutOfDate(Action<? super InputFileDetails> outOfDateAction) {
+        for (File file : task.getInputs().getFiles()) {
+            outOfDateAction.execute(new RebuildInputFile(file));
+        }
+    }
+
+    public void doRemoved(Action<? super InputFileDetails> removedAction) {
+    }
+
+    private static class RebuildInputFile implements InputFileDetails {
+        private final File file;
+
+        private RebuildInputFile(File file) {
+            this.file = file;
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        public boolean isAdded() {
+            return false;
+        }
+
+        public boolean isModified() {
+            return false;
+        }
+
+        public boolean isRemoved() {
+            return false;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/ShortCircuitTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/ShortCircuitTaskArtifactStateRepository.java
new file mode 100755
index 0000000..9bb3ad8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/ShortCircuitTaskArtifactStateRepository.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.changes;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.TaskExecutionHistory;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.Collection;
+
+public class ShortCircuitTaskArtifactStateRepository implements TaskArtifactStateRepository {
+
+    private final StartParameter startParameter;
+    private final TaskArtifactStateRepository repository;
+    private final Instantiator instantiator;
+
+    public ShortCircuitTaskArtifactStateRepository(StartParameter startParameter, Instantiator instantiator, TaskArtifactStateRepository repository) {
+        this.startParameter = startParameter;
+        this.instantiator = instantiator;
+        this.repository = repository;
+    }
+
+    public TaskArtifactState getStateFor(final TaskInternal task) {
+
+        if (!task.getOutputs().getHasOutput()) { // Only false if no declared outputs AND no Task.upToDateWhen spec. We force to true for incremental tasks.
+            return new NoHistoryArtifactState();
+        }
+
+        final TaskArtifactState state = repository.getStateFor(task);
+
+        if (startParameter.isRerunTasks()) {
+            return new RerunTaskArtifactState(state, task, "Executed with '--rerun-tasks'.");
+        }
+
+        if (!task.getOutputs().getUpToDateSpec().isSatisfiedBy(task)) {
+            return new RerunTaskArtifactState(state, task, "Task.upToDateWhen is false.");
+        }
+
+        return state;
+    }
+
+    private class RerunTaskArtifactState implements TaskArtifactState {
+        private final TaskArtifactState delegate;
+        private final TaskInternal task;
+        private final String reason;
+
+        private RerunTaskArtifactState(TaskArtifactState delegate, TaskInternal task, String reason) {
+            this.delegate = delegate;
+            this.task = task;
+            this.reason = reason;
+        }
+
+        public boolean isUpToDate(Collection<String> messages) {
+            messages.add(reason);
+            return false;
+        }
+
+        public IncrementalTaskInputs getInputChanges() {
+            return instantiator.newInstance(RebuildIncrementalTaskInputs.class, task);
+        }
+
+        public TaskExecutionHistory getExecutionHistory() {
+            return delegate.getExecutionHistory();
+        }
+
+        public void beforeTask() {
+            delegate.beforeTask();
+        }
+
+        public void afterTask() {
+            delegate.afterTask();
+        }
+
+        public void finished() {
+            delegate.finished();
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/StatefulIncrementalTaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/StatefulIncrementalTaskInputs.java
new file mode 100644
index 0000000..35bca19
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/changes/StatefulIncrementalTaskInputs.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.changes;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+abstract class StatefulIncrementalTaskInputs implements IncrementalTaskInputs {
+    private boolean outOfDateProcessed;
+    private boolean removedProcessed;
+
+    public void outOfDate(final Action<? super InputFileDetails> outOfDateAction) {
+        if (outOfDateProcessed) {
+            throw new IllegalStateException("Cannot process outOfDate files multiple times");
+        }
+        doOutOfDate(outOfDateAction);
+        outOfDateProcessed = true;
+    }
+
+    protected abstract void doOutOfDate(Action<? super InputFileDetails> outOfDateAction);
+
+    public void removed(Action<? super InputFileDetails> removedAction) {
+        if (!outOfDateProcessed) {
+            throw new IllegalStateException("Must first process outOfDate files before processing removed files");
+        }
+        if (removedProcessed) {
+            throw new IllegalStateException("Cannot process removed files multiple times");
+        }
+        doRemoved(removedAction);
+        removedProcessed = true;
+    }
+
+    protected abstract void doRemoved(Action<? super InputFileDetails> removedAction);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/CachingTaskStateChanges.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/CachingTaskStateChanges.java
new file mode 100644
index 0000000..cda5490
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/CachingTaskStateChanges.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import com.google.common.collect.AbstractIterator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An implementation that will cache a number of results of iteration, and make them available for subsequent iterations.
+ * This allows a single underlying iterator to be used for multiple iterations, up til the first iteration that exceeds the cache size.
+ * When the cache is overrun, it is cleared so that any subsequent iteration will require a fresh delegate iterator.
+ */
+public class CachingTaskStateChanges implements TaskStateChanges {
+    private final TaskStateChanges delegate;
+    private final List<TaskStateChange> cache = new ArrayList<TaskStateChange>();
+    private final int maxCachedChanges;
+    private Iterator<TaskStateChange> delegateIterator;
+    boolean overrun;
+
+    public CachingTaskStateChanges(int maxCachedChanges, TaskStateChanges delegate) {
+        this.maxCachedChanges = maxCachedChanges;
+        this.delegate = delegate;
+    }
+
+    public Iterator<TaskStateChange> iterator() {
+        if (delegateIterator == null || overrun) {
+            reset();
+        }
+
+        return new AbstractIterator<TaskStateChange>() {
+            final Iterator<TaskStateChange> cacheIterator = new ArrayList<TaskStateChange>(cache).iterator();
+
+            @Override
+            protected TaskStateChange computeNext() {
+                if (cacheIterator.hasNext()) {
+                    return cacheIterator.next();
+                }
+                if (delegateIterator.hasNext()) {
+                    TaskStateChange next = delegateIterator.next();
+                    maybeCache(next);
+                    return next;
+                }
+                return endOfData();
+            }
+        };
+    }
+
+    private void maybeCache(TaskStateChange next) {
+        if (overrun) {
+            return;
+        }
+
+        if (cache.size() < maxCachedChanges) {
+            cache.add(next);
+        } else {
+            overrun = true;
+        }
+    }
+
+    private void reset() {
+        cache.clear();
+        delegateIterator = delegate.iterator();
+        overrun = false;
+    }
+
+    public void snapshotAfterTask() {
+        delegate.snapshotAfterTask();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/ChangeType.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/ChangeType.java
new file mode 100644
index 0000000..3f59e2c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/ChangeType.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+enum ChangeType {
+    ADDED("has been added"),
+    MODIFIED("has changed"),
+    REMOVED("has been removed");
+
+    private final String description;
+
+    private ChangeType(String description) {
+        this.description = description;
+    }
+
+    public String describe() {
+        return description;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/DescriptiveChange.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/DescriptiveChange.java
new file mode 100644
index 0000000..321e392
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/DescriptiveChange.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+class DescriptiveChange implements TaskStateChange {
+    private final String message;
+
+    public DescriptiveChange(String message, Object... params) {
+        this.message = String.format(message, params);
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/FileChange.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/FileChange.java
new file mode 100644
index 0000000..f73a71e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/FileChange.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import java.io.File;
+
+abstract class FileChange implements TaskStateChange {
+    private final String path;
+    private final ChangeType change;
+
+    public FileChange(String path, ChangeType change) {
+        this.path = path;
+        this.change = change;
+    }
+
+    public String getMessage() {
+        return String.format("%s file %s %s.", getFileType(), path, change.describe());
+    }
+
+    @Override
+    public String toString() {
+        return getMessage();
+    }
+
+    protected abstract String getFileType();
+
+    public String getPath() {
+        return path;
+    }
+
+    public File getFile() {
+        return new File(path);
+    }
+
+    public boolean isAdded() {
+        return change == ChangeType.ADDED;
+    }
+
+    public boolean isModified() {
+        return change == ChangeType.MODIFIED;
+    }
+
+    public boolean isRemoved() {
+        return change == ChangeType.REMOVED;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputFileChange.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputFileChange.java
new file mode 100644
index 0000000..a67677a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputFileChange.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+class InputFileChange extends FileChange implements InputFileDetails {
+
+    public InputFileChange(String path, ChangeType change) {
+        super(path, change);
+    }
+
+    @Override
+    protected String getFileType() {
+        return "Input";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputFilesStateChangeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputFilesStateChangeRule.java
new file mode 100644
index 0000000..648a599
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputFilesStateChangeRule.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.rules;
+
+import com.google.common.collect.AbstractIterator;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshot;
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+import org.gradle.util.ChangeListener;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * A rule which detects changes in the input files of a task.
+ */
+class InputFilesStateChangeRule {
+    public static TaskStateChanges create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution, final FileCollectionSnapshotter inputFilesSnapshotter) {
+        final FileCollectionSnapshot inputFilesSnapshot = inputFilesSnapshotter.snapshot(task.getInputs().getFiles());
+
+        return new TaskStateChanges() {
+
+            public Iterator<TaskStateChange> iterator() {
+                if (previousExecution.getInputFilesSnapshot() == null) {
+                    return Collections.<TaskStateChange>singleton(new DescriptiveChange("Input file history is not available.")).iterator();
+                }
+
+                return new AbstractIterator<TaskStateChange>() {
+                    final FileCollectionSnapshot.ChangeIterator<String> changeIterator = inputFilesSnapshot.iterateChangesSince(previousExecution.getInputFilesSnapshot());
+                    final ChangeListenerAdapter listenerAdapter = new ChangeListenerAdapter();
+
+                    @Override
+                    protected TaskStateChange computeNext() {
+                        if (changeIterator.next(listenerAdapter)) {
+                            return listenerAdapter.lastChange;
+                        }
+                        return endOfData();
+                    }
+                };
+            }
+
+            public void snapshotAfterTask() {
+                currentExecution.setInputFilesSnapshot(inputFilesSnapshot);
+            }
+        };
+    }
+
+    private static class ChangeListenerAdapter implements ChangeListener<String> {
+        public InputFileChange lastChange;
+
+        public void added(String fileName) {
+            lastChange = new InputFileChange(fileName, ChangeType.ADDED);
+        }
+
+        public void removed(String fileName) {
+            lastChange = new InputFileChange(fileName, ChangeType.REMOVED);
+        }
+
+        public void changed(String fileName) {
+            lastChange = new InputFileChange(fileName, ChangeType.MODIFIED);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputPropertiesStateChangeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputPropertiesStateChangeRule.java
new file mode 100644
index 0000000..57d0c38
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/InputPropertiesStateChangeRule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.rules;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+import org.gradle.util.ChangeListener;
+import org.gradle.util.DiffUtil;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A rule which detects changes in the input properties of a task.
+ */
+class InputPropertiesStateChangeRule {
+    public static TaskStateChanges create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution) {
+        final Map<String, Object> properties = new HashMap<String, Object>(task.getInputs().getProperties());
+        currentExecution.setInputProperties(properties);
+
+        return new SimpleTaskStateChanges() {
+            @Override
+            protected void addAllChanges(final List<TaskStateChange> changes) {
+                DiffUtil.diff(properties, previousExecution.getInputProperties(), new ChangeListener<Map.Entry<String, Object>>() {
+                    public void added(Map.Entry<String, Object> element) {
+                        changes.add(new DescriptiveChange("Input property '%s' has been added for %s", element.getKey(), task));
+                    }
+
+                    public void removed(Map.Entry<String, Object> element) {
+                        changes.add(new DescriptiveChange("Input property '%s' has been removed for %s", element.getKey(), task));
+                    }
+
+                    public void changed(Map.Entry<String, Object> element) {
+                        changes.add(new DescriptiveChange("Value of input property '%s' has changed for %s", element.getKey(), task));
+                    }
+                });
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/NoHistoryStateChangeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/NoHistoryStateChangeRule.java
new file mode 100644
index 0000000..0f27534
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/NoHistoryStateChangeRule.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+
+import java.util.List;
+
+class NoHistoryStateChangeRule {
+    public static TaskStateChanges create(final TaskInternal task, final TaskExecution previousExecution) {
+        return new SimpleTaskStateChanges() {
+            @Override
+            protected void addAllChanges(List<TaskStateChange> changes) {
+                if (previousExecution == null) {
+                    changes.add(new DescriptiveChange("No history is available."));
+                }
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/OutputFileChange.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/OutputFileChange.java
new file mode 100644
index 0000000..ab4fbad
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/OutputFileChange.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+class OutputFileChange extends FileChange {
+
+    public OutputFileChange(String path, ChangeType change) {
+        super(path, change);
+    }
+
+    @Override
+    protected String getFileType() {
+        return "Output";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/OutputFilesStateChangeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/OutputFilesStateChangeRule.java
new file mode 100644
index 0000000..7ad384f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/OutputFilesStateChangeRule.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.rules;
+
+import com.google.common.collect.AbstractIterator;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshot;
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+import org.gradle.util.ChangeListener;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * A rule which detects changes in output files.
+ */
+class OutputFilesStateChangeRule {
+
+    public static TaskStateChanges create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution, final FileCollectionSnapshotter outputFilesSnapshotter) {
+        final FileCollectionSnapshot outputFilesBefore = outputFilesSnapshotter.snapshot(task.getOutputs().getFiles());
+
+        return new TaskStateChanges() {
+
+            public Iterator<TaskStateChange> iterator() {
+                if (previousExecution.getOutputFilesSnapshot() == null) {
+                    return Collections.<TaskStateChange>singleton(new DescriptiveChange("Output file history is not available.")).iterator();
+                }
+
+                return new AbstractIterator<TaskStateChange>() {
+                    final FileCollectionSnapshot.ChangeIterator<String> changeIterator = outputFilesBefore.iterateChangesSince(previousExecution.getOutputFilesSnapshot());
+                    final ChangeListenerAdapter listenerAdapter = new ChangeListenerAdapter();
+
+                    @Override
+                    protected TaskStateChange computeNext() {
+                        if (changeIterator.next(listenerAdapter)) {
+                            return listenerAdapter.lastChange;
+                        }
+                        return endOfData();
+                    }
+                };
+            }
+
+            public void snapshotAfterTask() {
+                FileCollectionSnapshot lastExecutionOutputFiles;
+                if (previousExecution == null || previousExecution.getOutputFilesSnapshot() == null) {
+                    lastExecutionOutputFiles = outputFilesSnapshotter.emptySnapshot();
+                } else {
+                    lastExecutionOutputFiles = previousExecution.getOutputFilesSnapshot();
+                }
+                FileCollectionSnapshot newOutputFiles = outputFilesBefore.changesSince(lastExecutionOutputFiles).applyTo(
+                        lastExecutionOutputFiles, new ChangeListener<FileCollectionSnapshot.Merge>() {
+                            public void added(FileCollectionSnapshot.Merge element) {
+                                // Ignore added files
+                                element.ignore();
+                            }
+
+                            public void removed(FileCollectionSnapshot.Merge element) {
+                                // Discard any files removed since the task was last executed
+                            }
+
+                            public void changed(FileCollectionSnapshot.Merge element) {
+                                // Update any files which were change since the task was last executed
+                            }
+                        });
+                FileCollectionSnapshot outputFilesAfter = outputFilesSnapshotter.snapshot(task.getOutputs().getFiles());
+                currentExecution.setOutputFilesSnapshot(outputFilesAfter.changesSince(outputFilesBefore).applyTo(newOutputFiles));
+            }
+        };
+    }
+
+    private static class ChangeListenerAdapter implements ChangeListener<String> {
+        public OutputFileChange lastChange;
+
+        public void added(String fileName) {
+            lastChange = new OutputFileChange(fileName, ChangeType.ADDED);
+        }
+
+        public void removed(String fileName) {
+            lastChange = new OutputFileChange(fileName, ChangeType.REMOVED);
+        }
+
+        public void changed(String fileName) {
+            lastChange = new OutputFileChange(fileName, ChangeType.MODIFIED);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/SimpleTaskStateChanges.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/SimpleTaskStateChanges.java
new file mode 100644
index 0000000..a2488b1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/SimpleTaskStateChanges.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+abstract class SimpleTaskStateChanges implements TaskStateChanges {
+    private List<TaskStateChange> changes;
+
+    public Iterator<TaskStateChange> iterator() {
+        if (changes == null) {
+            changes = new ArrayList<TaskStateChange>();
+            addAllChanges(changes);
+        }
+        return changes.iterator();
+    }
+
+    protected abstract void addAllChanges(List<TaskStateChange> changes);
+
+    public void snapshotAfterTask() {
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/SummaryTaskStateChanges.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/SummaryTaskStateChanges.java
new file mode 100644
index 0000000..ba85d4b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/SummaryTaskStateChanges.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import com.google.common.collect.AbstractIterator;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+class SummaryTaskStateChanges implements TaskStateChanges {
+    private final int maxReportedChanges;
+    private final List<TaskStateChanges> sources;
+
+
+    public SummaryTaskStateChanges(int maxReportedChanges, TaskStateChanges... sources) {
+        this.maxReportedChanges = maxReportedChanges;
+        this.sources = Arrays.asList(sources);
+    }
+
+    /**
+     * Provides an efficient summary of the changes, without doing too much unnecessary work.
+     * - Will only emit changes of a single type (from a single delegate change set)
+     * - Will return no more than the specified maximum of number of changes
+     */
+    public Iterator<TaskStateChange> iterator() {
+
+        return new AbstractIterator<TaskStateChange>() {
+            Iterator<TaskStateChange> changes;
+            int count;
+
+            @Override
+            protected TaskStateChange computeNext() {
+                if (changes == null) {
+                    changes = firstDirtyIterator();
+                }
+
+                if (count < maxReportedChanges && changes != null && changes.hasNext()) {
+                    count++;
+                    return changes.next();
+                }
+                return endOfData();
+            }
+        };
+    }
+
+    private Iterator<TaskStateChange> firstDirtyIterator() {
+        for (TaskStateChanges source : sources) {
+            Iterator<TaskStateChange> sourceIterator = source.iterator();
+            if (sourceIterator.hasNext()) {
+                return sourceIterator;
+            }
+        }
+        return null;
+    }
+
+    public void snapshotAfterTask() {
+        for (TaskStateChanges state : sources) {
+            state.snapshotAfterTask();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskStateChange.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskStateChange.java
new file mode 100644
index 0000000..af2f2f4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskStateChange.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+public interface TaskStateChange {
+    String getMessage();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskStateChanges.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskStateChanges.java
new file mode 100644
index 0000000..d75a122
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskStateChanges.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+public interface TaskStateChanges extends Iterable<TaskStateChange> {
+    /**
+     * Snapshot any final state after the task has executed. This method is executed only if the task is to be executed.
+     * Any persistent state should be added to the {@link org.gradle.api.internal.changedetection.state.TaskExecution} object for the current execution.
+     */
+    void snapshotAfterTask();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskTypeStateChangeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskTypeStateChangeRule.java
new file mode 100644
index 0000000..182ce28
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskTypeStateChangeRule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.rules;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+
+import java.util.List;
+
+/**
+ * A rule which detects changes in the task implementation class.
+ */
+class TaskTypeStateChangeRule {
+    public static TaskStateChanges create(final TaskInternal task, final TaskExecution previousExecution, final TaskExecution currentExecution) {
+        final String taskClass = task.getClass().getName();
+        currentExecution.setTaskClass(taskClass);
+
+        return new SimpleTaskStateChanges() {
+            @Override
+            protected void addAllChanges(List<TaskStateChange> changes) {
+                if (!taskClass.equals(previousExecution.getTaskClass())) {
+                    changes.add(new DescriptiveChange("%s has changed type from '%s' to '%s'.",
+                            StringUtils.capitalize(task.toString()), previousExecution.getTaskClass(), task.getClass().getName()));
+                }
+            }
+        };
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskUpToDateState.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskUpToDateState.java
new file mode 100644
index 0000000..7cbd09b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/rules/TaskUpToDateState.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskExecution;
+import org.gradle.api.internal.changedetection.state.TaskHistoryRepository;
+
+/**
+ * Represents the complete changes in a tasks state
+ */
+public class TaskUpToDateState {
+    private static final int MAX_OUT_OF_DATE_MESSAGES = 3;
+
+    private TaskStateChanges noHistoryState;
+    private TaskStateChanges inputFilesState;
+    private TaskStateChanges inputPropertiesState;
+    private TaskStateChanges taskTypeState;
+    private TaskStateChanges outputFilesState;
+    private SummaryTaskStateChanges allTaskChanges;
+    private SummaryTaskStateChanges rebuildChanges;
+
+    public TaskUpToDateState(TaskInternal task, TaskHistoryRepository.History history, FileCollectionSnapshotter outputFilesSnapshotter, FileCollectionSnapshotter inputFilesSnapshotter) {
+        TaskExecution thisExecution = history.getCurrentExecution();
+        TaskExecution lastExecution = history.getPreviousExecution();
+
+        noHistoryState = NoHistoryStateChangeRule.create(task, lastExecution);
+        taskTypeState = TaskTypeStateChangeRule.create(task, lastExecution, thisExecution);
+        inputPropertiesState = InputPropertiesStateChangeRule.create(task, lastExecution, thisExecution);
+        outputFilesState = caching(OutputFilesStateChangeRule.create(task, lastExecution, thisExecution, outputFilesSnapshotter));
+        inputFilesState = caching(InputFilesStateChangeRule.create(task, lastExecution, thisExecution, inputFilesSnapshotter));
+        allTaskChanges = new SummaryTaskStateChanges(MAX_OUT_OF_DATE_MESSAGES, noHistoryState, taskTypeState, inputPropertiesState, outputFilesState, inputFilesState);
+        rebuildChanges = new SummaryTaskStateChanges(1, noHistoryState, taskTypeState, inputPropertiesState, outputFilesState);
+    }
+
+    private TaskStateChanges caching(TaskStateChanges wrapped) {
+        return new CachingTaskStateChanges(MAX_OUT_OF_DATE_MESSAGES, wrapped);
+    }
+
+    public TaskStateChanges getInputFilesChanges() {
+        return inputFilesState;
+    }
+
+    public TaskStateChanges getAllTaskChanges() {
+        return allTaskChanges;
+    }
+
+    public TaskStateChanges getRebuildChanges() {
+        return rebuildChanges;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CacheBackedFileSnapshotRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CacheBackedFileSnapshotRepository.java
new file mode 100644
index 0000000..7b146f9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CacheBackedFileSnapshotRepository.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.changedetection.state;
+
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.RandomLongIdGenerator;
+import org.gradle.messaging.serialize.Serializer;
+
+public class CacheBackedFileSnapshotRepository implements FileSnapshotRepository {
+    private final PersistentIndexedCache<Long, FileCollectionSnapshot> cache;
+    private IdGenerator<Long> idGenerator = new RandomLongIdGenerator();
+
+    public CacheBackedFileSnapshotRepository(TaskArtifactStateCacheAccess cacheAccess, Serializer<FileCollectionSnapshot> serializer, IdGenerator<Long> idGenerator) {
+        this.idGenerator = idGenerator;
+        cache = cacheAccess.createCache("fileSnapshots", Long.class, serializer);
+    }
+
+    public Long add(FileCollectionSnapshot snapshot) {
+        Long id = idGenerator.generateId();
+        cache.put(id, snapshot);
+        return id;
+    }
+
+    public FileCollectionSnapshot get(Long id) {
+        return cache.get(id);
+    }
+
+    public void remove(Long id) {
+        cache.remove(id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CacheBackedTaskHistoryRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CacheBackedTaskHistoryRepository.java
new file mode 100644
index 0000000..dd6795a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CacheBackedTaskHistoryRepository.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.DefaultSerializer;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.File;
+import java.util.*;
+
+public class CacheBackedTaskHistoryRepository implements TaskHistoryRepository {
+    private final TaskArtifactStateCacheAccess cacheAccess;
+    private final FileSnapshotRepository snapshotRepository;
+    private final PersistentIndexedCache<String, TaskHistory> taskHistoryCache;
+    private final TaskHistorySerializer serializer = new TaskHistorySerializer();
+
+    public CacheBackedTaskHistoryRepository(TaskArtifactStateCacheAccess cacheAccess, FileSnapshotRepository snapshotRepository) {
+        this.cacheAccess = cacheAccess;
+        this.snapshotRepository = snapshotRepository;
+        taskHistoryCache = cacheAccess.createCache("taskArtifacts", String.class, serializer);
+    }
+
+    public History getHistory(final TaskInternal task) {
+        final TaskHistory history = loadHistory(task);
+        final LazyTaskExecution currentExecution = new LazyTaskExecution();
+        currentExecution.snapshotRepository = snapshotRepository;
+        currentExecution.cacheAccess = cacheAccess;
+        currentExecution.setOutputFiles(outputFiles(task));
+        final LazyTaskExecution previousExecution = findPreviousExecution(currentExecution, history);
+        if (previousExecution != null) {
+            previousExecution.snapshotRepository = snapshotRepository;
+            previousExecution.cacheAccess = cacheAccess;
+        }
+
+        return new History() {
+            public TaskExecution getPreviousExecution() {
+                return previousExecution;
+            }
+
+            public TaskExecution getCurrentExecution() {
+                return currentExecution;
+            }
+
+            public void update() {
+                cacheAccess.useCache("Update task history", new Runnable() {
+                    public void run() {
+                        history.configurations.add(0, currentExecution);
+                        if (currentExecution.inputFilesSnapshotId == null && currentExecution.inputFilesSnapshot != null) {
+                            currentExecution.inputFilesSnapshotId = snapshotRepository.add(currentExecution.inputFilesSnapshot);
+                        }
+                        if (currentExecution.outputFilesSnapshotId == null && currentExecution.outputFilesSnapshot != null) {
+                            currentExecution.outputFilesSnapshotId = snapshotRepository.add(currentExecution.outputFilesSnapshot);
+                        }
+                        while (history.configurations.size() > TaskHistory.MAX_HISTORY_ENTRIES) {
+                            LazyTaskExecution execution = history.configurations.remove(history.configurations.size() - 1);
+                            if (execution.inputFilesSnapshotId != null) {
+                                snapshotRepository.remove(execution.inputFilesSnapshotId);
+                            }
+                            if (execution.outputFilesSnapshotId != null) {
+                                snapshotRepository.remove(execution.outputFilesSnapshotId);
+                            }
+                        }
+                        history.beforeSerialized();
+                        taskHistoryCache.put(task.getPath(), history);
+                    }
+                });
+            }
+        };
+    }
+
+    private TaskHistory loadHistory(final TaskInternal task) {
+        return cacheAccess.useCache("Load task history", new Factory<TaskHistory>() {
+            public TaskHistory create() {
+                ClassLoader original = serializer.getClassLoader();
+                serializer.setClassLoader(task.getClass().getClassLoader());
+                try {
+                    TaskHistory history = taskHistoryCache.get(task.getPath());
+                    return history == null ? new TaskHistory() : history;
+                } finally {
+                    serializer.setClassLoader(original);
+                }
+            }
+        });
+    }
+
+    private static Set<String> outputFiles(TaskInternal task) {
+        Set<String> outputFiles = new HashSet<String>();
+        for (File file : task.getOutputs().getFiles()) {
+            outputFiles.add(file.getAbsolutePath());
+        }
+        return outputFiles;
+    }
+
+    private LazyTaskExecution findPreviousExecution(TaskExecution currentExecution, TaskHistory history) {
+        Set<String> outputFiles = currentExecution.getOutputFiles();
+        LazyTaskExecution bestMatch = null;
+        int bestMatchOverlap = 0;
+        for (LazyTaskExecution configuration : history.configurations) {
+            if (outputFiles.size() == 0) {
+                if (configuration.getOutputFiles().size() == 0) {
+                    bestMatch = configuration;
+                    break;
+                }
+            }
+
+            Set<String> intersection = new HashSet<String>(outputFiles);
+            intersection.retainAll(configuration.getOutputFiles());
+            if (intersection.size() > bestMatchOverlap) {
+                bestMatch = configuration;
+                bestMatchOverlap = intersection.size();
+            }
+            if (bestMatchOverlap == outputFiles.size()) {
+                break;
+            }
+        }
+        return bestMatch;
+    }
+
+    private static class TaskHistorySerializer implements Serializer<TaskHistory> {
+
+        private ClassLoader classLoader;
+
+        public TaskHistory read(Decoder decoder) throws Exception {
+            byte executions = decoder.readByte();
+            TaskHistory history = new TaskHistory();
+            LazyTaskExecution.TaskHistorySerializer executionSerializer = new LazyTaskExecution.TaskHistorySerializer(classLoader);
+            for (int i = 0; i < executions; i++) {
+                LazyTaskExecution exec = executionSerializer.read(decoder);
+                history.configurations.add(exec);
+            }
+            return history;
+        }
+
+        public void write(Encoder encoder, TaskHistory value) throws Exception {
+            int size = value.configurations.size();
+            encoder.writeByte((byte) size);
+            LazyTaskExecution.TaskHistorySerializer executionSerializer = new LazyTaskExecution.TaskHistorySerializer(classLoader);
+            for (LazyTaskExecution execution : value.configurations) {
+                executionSerializer.write(encoder, execution);
+            }
+        }
+
+        public ClassLoader getClassLoader() {
+            return classLoader;
+        }
+
+        public void setClassLoader(ClassLoader classLoader) {
+            this.classLoader = classLoader;
+        }
+    }
+
+    private static class TaskHistory {
+        private static final int MAX_HISTORY_ENTRIES = 3;
+        private final List<LazyTaskExecution> configurations = new ArrayList<LazyTaskExecution>();
+        public String toString() {
+            return super.toString() + "[" + configurations.size() + "]";
+        }
+
+        public void beforeSerialized() {
+            //cleaning up the transient fields, so that any in-memory caching is happy
+            for (LazyTaskExecution c : configurations) {
+                c.cacheAccess = null;
+                c.snapshotRepository = null;
+            }
+        }
+    }
+
+    //TODO SF extract & unit test
+    private static class LazyTaskExecution extends TaskExecution {
+        private Long inputFilesSnapshotId;
+        private Long outputFilesSnapshotId;
+        private transient FileSnapshotRepository snapshotRepository;
+        private transient FileCollectionSnapshot inputFilesSnapshot;
+        private transient FileCollectionSnapshot outputFilesSnapshot;
+        private transient TaskArtifactStateCacheAccess cacheAccess;
+
+        @Override
+        public FileCollectionSnapshot getInputFilesSnapshot() {
+            if (inputFilesSnapshot == null) {
+                inputFilesSnapshot = cacheAccess.useCache("fetch input files", new Factory<FileCollectionSnapshot>() {
+                    public FileCollectionSnapshot create() {
+                        return snapshotRepository.get(inputFilesSnapshotId);
+                    }
+                });
+            }
+            return inputFilesSnapshot;
+        }
+
+        @Override
+        public void setInputFilesSnapshot(FileCollectionSnapshot inputFilesSnapshot) {
+            this.inputFilesSnapshot = inputFilesSnapshot;
+            this.inputFilesSnapshotId = null;
+        }
+
+        @Override
+        public FileCollectionSnapshot getOutputFilesSnapshot() {
+            if (outputFilesSnapshot == null) {
+                outputFilesSnapshot = cacheAccess.useCache("fetch output files", new Factory<FileCollectionSnapshot>() {
+                    public FileCollectionSnapshot create() {
+                        return snapshotRepository.get(outputFilesSnapshotId);
+                    }
+                });
+            }
+            return outputFilesSnapshot;
+        }
+
+        @Override
+        public void setOutputFilesSnapshot(FileCollectionSnapshot outputFilesSnapshot) {
+            this.outputFilesSnapshot = outputFilesSnapshot;
+            outputFilesSnapshotId = null;
+        }
+
+        static class TaskHistorySerializer implements Serializer<LazyTaskExecution> {
+            private ClassLoader classLoader;
+
+            public TaskHistorySerializer(ClassLoader classLoader) {
+                this.classLoader = classLoader;
+            }
+
+            public LazyTaskExecution read(Decoder decoder) throws Exception {
+                LazyTaskExecution execution = new LazyTaskExecution();
+                execution.inputFilesSnapshotId = decoder.readLong();
+                execution.outputFilesSnapshotId = decoder.readLong();
+                execution.setTaskClass(decoder.readString());
+                int outputFiles = decoder.readInt();
+                Set<String> files = new HashSet<String>();
+                for (int j = 0; j < outputFiles; j++) {
+                    files.add(decoder.readString());
+                }
+                execution.setOutputFiles(files);
+
+                boolean inputProperties = decoder.readBoolean();
+                if (inputProperties) {
+                    DefaultSerializer<Map> defaultSerializer = new DefaultSerializer<Map>(classLoader);
+                    Map<String, Object> map = defaultSerializer.read(decoder);
+                    execution.setInputProperties(map);
+                } else {
+                    execution.setInputProperties(new HashMap<String, Object>());
+                }
+                return execution;
+            }
+
+            public void write(Encoder encoder, LazyTaskExecution execution) throws Exception {
+                encoder.writeLong(execution.inputFilesSnapshotId);
+                encoder.writeLong(execution.outputFilesSnapshotId);
+                encoder.writeString(execution.getTaskClass());
+                encoder.writeInt(execution.getOutputFiles().size());
+                for (String outputFile : execution.getOutputFiles()) {
+                    encoder.writeString(outputFile);
+                }
+                if (execution.getInputProperties() == null || execution.getInputProperties().isEmpty()) {
+                    encoder.writeBoolean(false);
+                } else {
+                    encoder.writeBoolean(true);
+                    DefaultSerializer<Map> defaultSerializer = new DefaultSerializer<Map>(classLoader);
+                    defaultSerializer.write(encoder, execution.getInputProperties());
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CachingFileSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CachingFileSnapshotter.java
new file mode 100644
index 0000000..c258f39
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/CachingFileSnapshotter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.internal.hash.Hasher;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentStore;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+import org.gradle.messaging.serialize.SerializerRegistry;
+
+import java.io.File;
+
+public class CachingFileSnapshotter implements FileSnapshotter {
+    private final PersistentIndexedCache<File, FileInfo> cache;
+    private final Hasher hasher;
+    private final FileInfoSerializer serializer = new FileInfoSerializer();
+
+    public CachingFileSnapshotter(Hasher hasher, PersistentStore store) {
+        this.hasher = hasher;
+        this.cache = store.createCache("fileHashes", File.class, serializer);
+    }
+
+    public void registerSerializers(SerializerRegistry<FileSnapshot> registry) {
+        registry.register(FileInfo.class, serializer);
+    }
+
+    public FileInfo snapshot(File file) {
+        FileInfo info = cache.get(file);
+
+        long length = file.length();
+        long timestamp = file.lastModified();
+        if (info != null && length == info.length && timestamp == info.timestamp) {
+            return info;
+        }
+
+        byte[] hash = hasher.hash(file);
+        info = new FileInfo(hash, length, timestamp);
+        cache.put(file, info);
+        return info;
+    }
+
+    public static class FileInfo implements FileSnapshot {
+        private final byte[] hash;
+        private final long timestamp;
+        private final long length;
+
+        public FileInfo(byte[] hash, long length, long timestamp) {
+            this.hash = hash;
+            this.length = length;
+            this.timestamp = timestamp;
+        }
+
+        public byte[] getHash() {
+            return hash;
+        }
+    }
+
+    private static class FileInfoSerializer implements Serializer<FileInfo> {
+        public FileInfo read(Decoder decoder) throws Exception {
+            byte[] hash = decoder.readBinary();
+            long timestamp = decoder.readLong();
+            long length = decoder.readLong();
+            return new FileInfo(hash, length, timestamp);
+        }
+
+        public void write(Encoder encoder, FileInfo value) throws Exception {
+            encoder.writeBinary(value.hash);
+            encoder.writeLong(value.timestamp);
+            encoder.writeLong(value.length);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultFileCollectionSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultFileCollectionSnapshotter.java
new file mode 100755
index 0000000..d6442ad
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultFileCollectionSnapshotter.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.messaging.serialize.SerializerRegistry;
+import org.gradle.util.ChangeListener;
+import org.gradle.util.NoOpChangeListener;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.util.*;
+
+public class DefaultFileCollectionSnapshotter implements FileCollectionSnapshotter {
+    private final FileSnapshotter snapshotter;
+    private TaskArtifactStateCacheAccess cacheAccess;
+
+    public DefaultFileCollectionSnapshotter(FileSnapshotter snapshotter, TaskArtifactStateCacheAccess cacheAccess) {
+        this.snapshotter = snapshotter;
+        this.cacheAccess = cacheAccess;
+    }
+
+    public void registerSerializers(SerializerRegistry<FileCollectionSnapshot> registry) {
+        registry.register(FileCollectionSnapshotImpl.class, new DefaultFileSnapshotterSerializer());
+    }
+
+    public FileCollectionSnapshot emptySnapshot() {
+        return new FileCollectionSnapshotImpl(new HashMap<String, FileSnapshot>());
+    }
+
+    public FileCollectionSnapshot snapshot(FileCollection sourceFiles) {
+        final Map<String, FileSnapshot> snapshots = new HashMap<String, FileSnapshot>();
+        final Set<File> theFiles = sourceFiles.getAsFileTree().getFiles();
+        cacheAccess.useCache("Create file snapshot", new Runnable() {
+            public void run() {
+                for (File file : theFiles) {
+                    if (file.isFile()) {
+                        snapshots.put(file.getAbsolutePath(), new FileHashSnapshot(snapshotter.snapshot(file).getHash()));
+                    } else if (file.isDirectory()) {
+                        snapshots.put(file.getAbsolutePath(), new DirSnapshot());
+                    } else {
+                        snapshots.put(file.getAbsolutePath(), new MissingFileSnapshot());
+                    }
+                }
+            }
+        });
+        return new FileCollectionSnapshotImpl(snapshots);
+    }
+
+    static interface FileSnapshot {
+        boolean isUpToDate(FileSnapshot snapshot);
+    }
+
+    static class FileHashSnapshot implements FileSnapshot {
+        final byte[] hash;
+
+        public FileHashSnapshot(byte[] hash) {
+            this.hash = hash;
+        }
+
+        public boolean isUpToDate(FileSnapshot snapshot) {
+            if (!(snapshot instanceof FileHashSnapshot)) {
+                return false;
+            }
+
+            FileHashSnapshot other = (FileHashSnapshot) snapshot;
+            return Arrays.equals(hash, other.hash);
+        }
+
+        @Override
+        public String toString() {
+            return new BigInteger(1, hash).toString(16);
+        }
+    }
+
+    static class DirSnapshot implements FileSnapshot {
+        public boolean isUpToDate(FileSnapshot snapshot) {
+            return snapshot instanceof DirSnapshot;
+        }
+    }
+
+    static class MissingFileSnapshot implements FileSnapshot {
+        public boolean isUpToDate(FileSnapshot snapshot) {
+            return snapshot instanceof MissingFileSnapshot;
+        }
+    }
+
+    static class FileCollectionSnapshotImpl implements FileCollectionSnapshot {
+        final Map<String, FileSnapshot> snapshots;
+
+        public FileCollectionSnapshotImpl(Map<String, FileSnapshot> snapshots) {
+            this.snapshots = snapshots;
+        }
+
+        public FileCollection getFiles() {
+            List<File> files = new ArrayList<File>();
+            for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
+                if (entry.getValue() instanceof FileHashSnapshot) {
+                    files.add(new File(entry.getKey()));
+                }
+            }
+            return new SimpleFileCollection(files);
+        }
+
+        public ChangeIterator<String> iterateChangesSince(FileCollectionSnapshot oldSnapshot) {
+            FileCollectionSnapshotImpl other = (FileCollectionSnapshotImpl) oldSnapshot;
+            final Map<String, FileSnapshot> otherSnapshots = new HashMap<String, FileSnapshot>(other.snapshots);
+            final Iterator<String> currentFiles = snapshots.keySet().iterator();
+
+            return new ChangeIterator<String>() {
+                private Iterator<String> removedFiles;
+
+                public boolean next(ChangeListener<String> listener) {
+                    while (currentFiles.hasNext()) {
+                        String currentFile = currentFiles.next();
+                        FileSnapshot otherFile = otherSnapshots.remove(currentFile);
+
+                        if (otherFile == null) {
+                            listener.added(currentFile);
+                            return true;
+                        } else if (!snapshots.get(currentFile).isUpToDate(otherFile)) {
+                            listener.changed(currentFile);
+                            return true;
+                        }
+                    }
+
+                    // Create a single iterator to use for all of the removed files
+                    if (removedFiles == null) {
+                        removedFiles = otherSnapshots.keySet().iterator();
+                    }
+
+                    if (removedFiles.hasNext()) {
+                        listener.removed(removedFiles.next());
+                        return true;
+                    }
+
+                    return false;
+                }
+            };
+        }
+
+        public Diff changesSince(final FileCollectionSnapshot oldSnapshot) {
+            final FileCollectionSnapshotImpl other = (FileCollectionSnapshotImpl) oldSnapshot;
+            return new Diff() {
+                public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot) {
+                    return applyTo(snapshot, new NoOpChangeListener<Merge>());
+                }
+
+                public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot, final ChangeListener<Merge> listener) {
+                    FileCollectionSnapshotImpl target = (FileCollectionSnapshotImpl) snapshot;
+                    final Map<String, FileSnapshot> newSnapshots = new HashMap<String, FileSnapshot>(target.snapshots);
+                    diff(snapshots, other.snapshots, new MapMergeChangeListener<String, FileSnapshot>(listener, newSnapshots));
+                    return new FileCollectionSnapshotImpl(newSnapshots);
+                }
+            };
+        }
+
+        private void diff(Map<String, FileSnapshot> snapshots, Map<String, FileSnapshot> oldSnapshots,
+                          ChangeListener<Map.Entry<String, FileSnapshot>> listener) {
+            Map<String, FileSnapshot> otherSnapshots = new HashMap<String, FileSnapshot>(oldSnapshots);
+            for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
+                FileSnapshot otherFile = otherSnapshots.remove(entry.getKey());
+                if (otherFile == null) {
+                    listener.added(entry);
+                } else if (!entry.getValue().isUpToDate(otherFile)) {
+                    listener.changed(entry);
+                }
+            }
+            for (Map.Entry<String, FileSnapshot> entry : otherSnapshots.entrySet()) {
+                listener.removed(entry);
+            }
+        }
+
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultFileSnapshotterSerializer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultFileSnapshotterSerializer.java
new file mode 100644
index 0000000..aa69ce1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultFileSnapshotterSerializer.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class DefaultFileSnapshotterSerializer implements Serializer<DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl> {
+    public DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl read(Decoder decoder) throws Exception {
+        Map<String, DefaultFileCollectionSnapshotter.FileSnapshot> snapshots = new HashMap<String, DefaultFileCollectionSnapshotter.FileSnapshot>();
+        DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl snapshot = new DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl(snapshots);
+        int snapshotsCount = decoder.readSmallInt();
+        for (int i = 0; i < snapshotsCount; i++) {
+            String key = decoder.readString();
+            byte fileSnapshotKind = decoder.readByte();
+            if (fileSnapshotKind == 1) {
+                snapshots.put(key, new DefaultFileCollectionSnapshotter.DirSnapshot());
+            } else if (fileSnapshotKind == 2) {
+                snapshots.put(key, new DefaultFileCollectionSnapshotter.MissingFileSnapshot());
+            } else if (fileSnapshotKind == 3) {
+                byte hashSize = decoder.readByte();
+                byte[] hash = new byte[hashSize];
+                decoder.readBytes(hash);
+                snapshots.put(key, new DefaultFileCollectionSnapshotter.FileHashSnapshot(hash));
+            } else {
+                throw new RuntimeException("Unable to read serialized file collection snapshot. Unrecognized value found in the data stream.");
+            }
+        }
+        return snapshot;
+    }
+
+    public void write(Encoder encoder, DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl value) throws Exception {
+        encoder.writeSmallInt(value.snapshots.size());
+        for (String key : value.snapshots.keySet()) {
+            encoder.writeString(key);
+            DefaultFileCollectionSnapshotter.FileSnapshot fileSnapshot = value.snapshots.get(key);
+            if (fileSnapshot instanceof DefaultFileCollectionSnapshotter.DirSnapshot) {
+                encoder.writeByte((byte) 1);
+            } else if (fileSnapshot instanceof DefaultFileCollectionSnapshotter.MissingFileSnapshot) {
+                encoder.writeByte((byte) 2);
+            } else if (fileSnapshot instanceof DefaultFileCollectionSnapshotter.FileHashSnapshot) {
+                encoder.writeByte((byte) 3);
+                byte[] hash = ((DefaultFileCollectionSnapshotter.FileHashSnapshot) fileSnapshot).hash;
+                encoder.writeByte((byte) hash.length);
+                encoder.writeBytes(hash);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultTaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultTaskArtifactStateCacheAccess.java
new file mode 100644
index 0000000..8e6845e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/DefaultTaskArtifactStateCacheAccess.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentIndexedCacheParameters;
+import org.gradle.cache.internal.CacheDecorator;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Serializer;
+
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
+
+public class DefaultTaskArtifactStateCacheAccess implements TaskArtifactStateCacheAccess {
+    private final CacheDecorator inMemoryDecorator;
+    private final PersistentCache cache;
+
+    public DefaultTaskArtifactStateCacheAccess(Gradle gradle, CacheRepository cacheRepository, CacheDecorator decorator) {
+        this.inMemoryDecorator = decorator;
+        cache = cacheRepository
+                .cache(gradle, "taskArtifacts")
+                .withDisplayName("task history cache")
+                .withLockOptions(mode(FileLockManager.LockMode.None)) // Lock on demand
+                .open();
+    }
+
+    public void close() {
+        cache.close();
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(final String cacheName, final Class<K> keyType, final Serializer<V> valueSerializer) {
+        PersistentIndexedCacheParameters<K, V> parameters = new PersistentIndexedCacheParameters<K, V>(cacheName, keyType, valueSerializer)
+                .cacheDecorator(inMemoryDecorator);
+        return cache.createCache(parameters);
+    }
+
+    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+        return cache.useCache(operationDisplayName, action);
+    }
+
+    public void useCache(String operationDisplayName, Runnable action) {
+        cache.useCache(operationDisplayName, action);
+    }
+
+    public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
+        return cache.longRunningOperation(operationDisplayName, action);
+    }
+
+    public void longRunningOperation(String operationDisplayName, Runnable action) {
+        cache.longRunningOperation(operationDisplayName, action);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileCollectionSnapshot.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileCollectionSnapshot.java
new file mode 100755
index 0000000..e5bbf38
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileCollectionSnapshot.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.util.ChangeListener;
+
+/**
+ * An immutable snapshot of the contents of a collection of files.
+ */
+public interface FileCollectionSnapshot {
+
+    ChangeIterator<String> iterateChangesSince(FileCollectionSnapshot oldSnapshot);
+
+    Diff changesSince(FileCollectionSnapshot oldSnapshot);
+
+    FileCollection getFiles();
+
+    public interface Diff {
+        /**
+         * Applies this diff to the given snapshot. Adds any added or changed files in this diff to the given snapshot.
+         * Removes any removed files in this diff from the given snapshot.
+         *
+         * @param snapshot the snapshot to apply the changes to.
+         * @param listener the listener to notify of changes. The listener can veto a particular change.
+         * @return an updated copy of the provided snapshot
+         */
+        FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot, ChangeListener<Merge> listener);
+
+        /**
+         * Applies this diff to the given snapshot. Adds any added or changed files in this diff to the given snapshot.
+         * Removes any removed files in this diff from the given snapshot.
+         *
+         * @param snapshot the snapshot to apply the changes to.
+         * @return an updated copy of the provided snapshot
+         */
+        FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot);
+    }
+
+    public interface Merge {
+        void ignore();
+    }
+
+    interface ChangeIterator<T> {
+        boolean next(ChangeListener<T> listener);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileCollectionSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileCollectionSnapshotter.java
new file mode 100755
index 0000000..e7e023e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileCollectionSnapshotter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.messaging.serialize.SerializerRegistry;
+
+public interface FileCollectionSnapshotter {
+    /**
+     * Registers the serializer(s) that can be used to serialize the {@link FileCollectionSnapshot} implementations produced by this snapshotter.
+     */
+    void registerSerializers(SerializerRegistry<FileCollectionSnapshot> registry);
+
+    /**
+     * Creates an empty snapshot, which changes can be later merged into.
+     *
+     * @return The snapshot.
+     */
+    FileCollectionSnapshot emptySnapshot();
+
+    /**
+     * Creates a snapshot of the contents of the given collection
+     *
+     * @param files The files to snapshot
+     * @return The snapshot.
+     */
+    FileCollectionSnapshot snapshot(FileCollection files);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileSnapshotRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileSnapshotRepository.java
new file mode 100644
index 0000000..bd0fca2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileSnapshotRepository.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+public interface FileSnapshotRepository {
+    FileCollectionSnapshot get(Long id);
+
+    Long add(FileCollectionSnapshot snapshot);
+
+    void remove(Long id);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileSnapshotter.java
new file mode 100644
index 0000000..43e709e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/FileSnapshotter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.messaging.serialize.SerializerRegistry;
+
+import java.io.File;
+
+public interface FileSnapshotter {
+    /**
+     * Registers Serializers to use to persist the {@link FileSnapshot} instances that this snapshotter produces.
+     */
+    void registerSerializers(SerializerRegistry<FileSnapshot> registry);
+
+    /**
+     * Takes a snapshot of the current content of the given file. The provided file must exist and be a file (rather than, say, a directory).
+     */
+    FileSnapshot snapshot(File file);
+
+    interface FileSnapshot {
+        byte[] getHash();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/InMemoryTaskArtifactCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/InMemoryTaskArtifactCache.java
new file mode 100644
index 0000000..ba75905
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/InMemoryTaskArtifactCache.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.cache.internal.CacheDecorator;
+import org.gradle.cache.internal.FileLock;
+import org.gradle.cache.internal.MultiProcessSafePersistentIndexedCache;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class InMemoryTaskArtifactCache implements CacheDecorator {
+    private final static Logger LOG = Logging.getLogger(InMemoryTaskArtifactCache.class);
+    private final static Object NULL = new Object();
+
+    private static final Map<String, Integer> CACHE_CAPS = new HashMap<String, Integer>();
+
+    static {
+        //it's the simplest implementation, not very nice
+        //at very least, the max size should be provided at creation, by the creator of the cache
+        //however, max size is a bit awkward in general and we should look into other options,
+        //like using the Weighter and relate the cache size to the available heap, etc.
+        CACHE_CAPS.put("fileSnapshots", 10000);
+        CACHE_CAPS.put("taskArtifacts", 2000);
+        CACHE_CAPS.put("outputFileStates", 3000);
+        CACHE_CAPS.put("fileHashes", 140000);
+        CACHE_CAPS.put("compilationState", 1000);
+
+        //In general, the in-memory cache must be capped at some level, otherwise it is reduces performance in truly gigantic builds
+    }
+
+    private final Object lock = new Object();
+    private final Cache<String, Cache<Object, Object>> cache = CacheBuilder.newBuilder()
+            .maximumSize(CACHE_CAPS.size() * 2) //X2 to factor in a child build (for example buildSrc)
+            .build();
+
+    private final Map<String, FileLock.State> states = new HashMap<String, FileLock.State>();
+
+    public <K, V> MultiProcessSafePersistentIndexedCache<K, V> decorate(final String cacheId, String cacheName, final MultiProcessSafePersistentIndexedCache<K, V> original) {
+        final Cache<Object, Object> data = loadData(cacheId, cacheName);
+
+        return new MultiProcessSafePersistentIndexedCache<K, V>() {
+            public void close() {
+                original.close();
+            }
+
+            public V get(K key) {
+                assert key instanceof String || key instanceof Long || key instanceof File : "Unsupported key type: " + key;
+                Object value = data.getIfPresent(key);
+                if (value == NULL) {
+                    return null;
+                }
+                if (value != null) {
+                    return (V) value;
+                }
+                V out = original.get(key);
+                data.put(key, out == null ? NULL : out);
+                return out;
+            }
+
+            public void put(K key, V value) {
+                original.put(key, value);
+                data.put(key, value);
+            }
+
+            public void remove(K key) {
+                data.put(key, NULL);
+                original.remove(key);
+            }
+
+            public void onStartWork(String operationDisplayName, FileLock.State currentCacheState) {
+                boolean outOfDate;
+                synchronized (lock) {
+                    FileLock.State previousState = states.get(cacheId);
+                    outOfDate = previousState == null || currentCacheState.hasBeenUpdatedSince(previousState);
+                }
+
+                if (outOfDate) {
+                    LOG.info("Invalidating in-memory cache of {}", cacheId);
+                    data.invalidateAll();
+                }
+            }
+
+            public void onEndWork(FileLock.State currentCacheState) {
+                synchronized (lock) {
+                    states.put(cacheId, currentCacheState);
+                }
+            }
+        };
+    }
+
+    private Cache<Object, Object> loadData(String cacheId, String cacheName) {
+        Cache<Object, Object> theData;
+        synchronized (lock) {
+            theData = this.cache.getIfPresent(cacheId);
+            if (theData != null) {
+                LOG.info("In-memory cache of {}: Size{{}}, {}", cacheId, theData.size() , theData.stats());
+            } else {
+                Integer maxSize = CACHE_CAPS.get(cacheName);
+                assert maxSize != null : "Unknown cache.";
+                theData = CacheBuilder.newBuilder().maximumSize(maxSize).build();
+                this.cache.put(cacheId, theData);
+            }
+        }
+        return theData;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/MapMergeChangeListener.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/MapMergeChangeListener.java
new file mode 100644
index 0000000..bdd0917
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/MapMergeChangeListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.util.ChangeListener;
+
+import java.util.Map;
+
+class MapMergeChangeListener<K, V> implements ChangeListener<Map.Entry<K, V>> {
+    private final ChangeListener<FileCollectionSnapshot.Merge> listener;
+    private final Map<K, V> newSnapshots;
+
+    public MapMergeChangeListener(ChangeListener<FileCollectionSnapshot.Merge> listener, Map<K, V> targetMap) {
+        this.listener = listener;
+        this.newSnapshots = targetMap;
+    }
+
+    public void added(Map.Entry<K, V> element) {
+        DefaultMerge merge = new DefaultMerge();
+        listener.added(merge);
+        if (!merge.isIgnore()) {
+            newSnapshots.put(element.getKey(), element.getValue());
+        }
+    }
+
+    public void removed(Map.Entry<K, V> element) {
+        DefaultMerge merge = new DefaultMerge();
+        listener.removed(merge);
+        if (!merge.isIgnore()) {
+            newSnapshots.remove(element.getKey());
+        }
+    }
+
+    public void changed(Map.Entry<K, V> element) {
+        DefaultMerge merge = new DefaultMerge();
+        listener.changed(merge);
+        if (!merge.isIgnore()) {
+            newSnapshots.put(element.getKey(), element.getValue());
+        }
+    }
+
+    private static class DefaultMerge implements FileCollectionSnapshot.Merge {
+        private boolean ignore;
+
+        public boolean isIgnore() {
+            return ignore;
+        }
+
+        public void ignore() {
+            ignore = true;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/NoOpDecorator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/NoOpDecorator.java
new file mode 100644
index 0000000..5290c34
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/NoOpDecorator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.cache.internal.CacheDecorator;
+import org.gradle.cache.internal.MultiProcessSafePersistentIndexedCache;
+
+public class NoOpDecorator implements CacheDecorator {
+    public <K, V> MultiProcessSafePersistentIndexedCache<K, V> decorate(String cacheId, String cacheName, MultiProcessSafePersistentIndexedCache<K, V> original) {
+        return original;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/OutputFilesCollectionSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/OutputFilesCollectionSnapshotter.java
new file mode 100644
index 0000000..491d012
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/OutputFilesCollectionSnapshotter.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.messaging.serialize.DefaultSerializerRegistry;
+import org.gradle.messaging.serialize.LongSerializer;
+import org.gradle.messaging.serialize.SerializerRegistry;
+import org.gradle.util.ChangeListener;
+import org.gradle.util.DiffUtil;
+import org.gradle.util.NoOpChangeListener;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Takes a snapshot of the output files of a task. 2 parts to the algorithm:
+ *
+ * <ul>
+ * <li>Collect the unique id for each output file and directory. The unique id is generated when we notice that
+ * a file/directory has been created. The id is regenerated when the file/directory is deleted.</li>
+ *
+ * <li>Collect the hash of each output file and each file in each output directory.</li>
+ * </ul>
+ *
+ */
+public class OutputFilesCollectionSnapshotter implements FileCollectionSnapshotter {
+    private final FileCollectionSnapshotter snapshotter;
+    private final IdGenerator<Long> idGenerator;
+    private final TaskArtifactStateCacheAccess cacheAccess;
+    private final PersistentIndexedCache<String, Long> dirIdentifierCache;
+
+    public OutputFilesCollectionSnapshotter(FileCollectionSnapshotter snapshotter, IdGenerator<Long> idGenerator,
+                                            TaskArtifactStateCacheAccess cacheAccess) {
+        this.snapshotter = snapshotter;
+        this.idGenerator = idGenerator;
+        this.cacheAccess = cacheAccess;
+        dirIdentifierCache = cacheAccess.createCache("outputFileStates", String.class, new LongSerializer());
+    }
+
+    public void registerSerializers(SerializerRegistry<FileCollectionSnapshot> registry) {
+        DefaultSerializerRegistry<FileCollectionSnapshot> nested = new DefaultSerializerRegistry<FileCollectionSnapshot>();
+        snapshotter.registerSerializers(nested);
+        registry.register(OutputFilesSnapshot.class, new OutputFilesSnapshotSerializer(nested.build()));
+    }
+
+    public FileCollectionSnapshot emptySnapshot() {
+        return new OutputFilesSnapshot(new HashMap<String, Long>(), snapshotter.emptySnapshot());
+    }
+
+    public OutputFilesSnapshot snapshot(final FileCollection files) {
+        final Map<String, Long> snapshotDirIds = new HashMap<String, Long>();
+        final Set<File> theFiles = files.getFiles();
+        cacheAccess.useCache("create dir snapshots", new Runnable() {
+            public void run() {
+                for (File file : theFiles) {
+                    Long dirId;
+                    if (file.exists()) {
+                        dirId = dirIdentifierCache.get(file.getAbsolutePath());
+                        if (dirId == null) {
+                            dirId = idGenerator.generateId();
+                            dirIdentifierCache.put(file.getAbsolutePath(), dirId);
+                        }
+                    } else {
+                        dirIdentifierCache.remove(file.getAbsolutePath());
+                        dirId = null;
+                    }
+                    snapshotDirIds.put(file.getAbsolutePath(), dirId);
+                }
+
+            }
+        });
+        return new OutputFilesSnapshot(snapshotDirIds, snapshotter.snapshot(files));
+    }
+
+    static class OutputFilesSnapshot implements FileCollectionSnapshot {
+        final Map<String, Long> rootFileIds;
+        final FileCollectionSnapshot filesSnapshot;
+
+        public OutputFilesSnapshot(Map<String, Long> rootFileIds, FileCollectionSnapshot filesSnapshot) {
+            this.rootFileIds = rootFileIds;
+            this.filesSnapshot = filesSnapshot;
+        }
+
+        public FileCollection getFiles() {
+            return filesSnapshot.getFiles();
+        }
+
+        public Diff changesSince(final FileCollectionSnapshot oldSnapshot) {
+            OutputFilesSnapshot other = (OutputFilesSnapshot) oldSnapshot;
+            return new OutputFilesDiff(rootFileIds, other.rootFileIds, filesSnapshot.changesSince(other.filesSnapshot));
+        }
+
+        public ChangeIterator<String> iterateChangesSince(FileCollectionSnapshot oldSnapshot) {
+            final OutputFilesSnapshot other = (OutputFilesSnapshot) oldSnapshot;
+            final ChangeIterator<String> rootFileIdIterator = iterateRootFileIdChanges(other);
+            final ChangeIterator<String> fileIterator = filesSnapshot.iterateChangesSince(other.filesSnapshot);
+
+            final AddIgnoreChangeListenerAdapter listenerAdapter = new AddIgnoreChangeListenerAdapter();
+            return new ChangeIterator<String>() {
+                public boolean next(final ChangeListener<String> listener) {
+                    listenerAdapter.withDelegate(listener);
+                    if (rootFileIdIterator.next(listener)) {
+                        return true;
+                    }
+
+                    while (fileIterator.next(listenerAdapter)) {
+                        if (!listenerAdapter.wasIgnored) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            };
+        }
+
+        private ChangeIterator<String> iterateRootFileIdChanges(final OutputFilesSnapshot other) {
+            // Inlining DiffUtil.diff makes the inefficiencies here a bit more explicit
+            Map<String, Long> added = new HashMap<String, Long>(rootFileIds);
+            added.keySet().removeAll(other.rootFileIds.keySet());
+            final Iterator<String> addedIterator = added.keySet().iterator();
+
+            Map<String, Long> removed = new HashMap<String, Long>(other.rootFileIds);
+            removed.keySet().removeAll(rootFileIds.keySet());
+            final Iterator<String> removedIterator = removed.keySet().iterator();
+
+            Set<String> changed = new HashSet<String>();
+            for (Map.Entry<String, Long> current : rootFileIds.entrySet()) {
+                 // Only care about rootIds that used to exist, and have changed or been removed
+                Long otherValue = other.rootFileIds.get(current.getKey());
+                if (otherValue != null && !otherValue.equals(current.getValue())) {
+                    changed.add(current.getKey());
+                }
+            }
+            final Iterator<String> changedIterator = changed.iterator();
+
+            return new ChangeIterator<String>() {
+                public boolean next(ChangeListener<String> listener) {
+                    if (addedIterator.hasNext()) {
+                        listener.added(addedIterator.next());
+                        return true;
+                    }
+                    if (removedIterator.hasNext()) {
+                        listener.removed(removedIterator.next());
+                        return true;
+                    }
+                    if (changedIterator.hasNext()) {
+                        listener.changed(changedIterator.next());
+                        return true;
+                    }
+
+                    return false;
+                }
+            };
+        }
+    }
+
+    /**
+     * A flyweight wrapper that is used to ignore any added files called.
+     */
+    private static class AddIgnoreChangeListenerAdapter implements ChangeListener<String> {
+        private ChangeListener<String> delegate;
+        boolean wasIgnored;
+
+        private void withDelegate(ChangeListener<String> delegate) {
+            this.delegate = delegate;
+        }
+
+        public void added(String element) {
+            wasIgnored = true;
+        }
+
+        public void removed(String element) {
+            delegate.removed(element);
+            wasIgnored = false;
+        }
+
+        public void changed(String element) {
+            delegate.changed(element);
+            wasIgnored = false;
+        }
+    }
+
+    private static class OutputFilesDiff implements FileCollectionSnapshot.Diff {
+        private final Map<String, Long> newFileIds;
+        private final Map<String, Long> oldFileIds;
+        private final FileCollectionSnapshot.Diff filesDiff;
+
+        public OutputFilesDiff(Map<String, Long> newFileIds, Map<String, Long> oldFileIds,
+                               FileCollectionSnapshot.Diff filesDiff) {
+            this.newFileIds = newFileIds;
+            this.oldFileIds = oldFileIds;
+            this.filesDiff = filesDiff;
+        }
+
+        public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot,
+                                              ChangeListener<FileCollectionSnapshot.Merge> listener) {
+            OutputFilesSnapshot other = (OutputFilesSnapshot) snapshot;
+            Map<String, Long> dirIds = new HashMap<String, Long>(other.rootFileIds);
+            DiffUtil.diff(newFileIds, oldFileIds, new MapMergeChangeListener<String, Long>(
+                    new NoOpChangeListener<FileCollectionSnapshot.Merge>(), dirIds));
+            return new OutputFilesSnapshot(newFileIds, filesDiff.applyTo(other.filesSnapshot, listener));
+        }
+
+        public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot) {
+            return applyTo(snapshot, new NoOpChangeListener<FileCollectionSnapshot.Merge>());
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/OutputFilesSnapshotSerializer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/OutputFilesSnapshotSerializer.java
new file mode 100644
index 0000000..faad603
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/OutputFilesSnapshotSerializer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class OutputFilesSnapshotSerializer implements Serializer<OutputFilesCollectionSnapshotter.OutputFilesSnapshot> {
+    private final Serializer<FileCollectionSnapshot> serializer;
+
+    public OutputFilesSnapshotSerializer(Serializer<FileCollectionSnapshot> serializer) {
+        this.serializer = serializer;
+    }
+
+    public OutputFilesCollectionSnapshotter.OutputFilesSnapshot read(Decoder decoder) throws Exception {
+        Map<String, Long> rootFileIds = new HashMap<String, Long>();
+        int rootFileIdsCount = decoder.readSmallInt();
+        for (int i = 0; i < rootFileIdsCount; i++) {
+            String key = decoder.readString();
+            boolean notNull = decoder.readBoolean();
+            Long value = notNull? decoder.readLong() : null;
+            rootFileIds.put(key, value);
+        }
+        FileCollectionSnapshot snapshot = serializer.read(decoder);
+
+        return new OutputFilesCollectionSnapshotter.OutputFilesSnapshot(rootFileIds, snapshot);
+    }
+
+    public void write(Encoder encoder, OutputFilesCollectionSnapshotter.OutputFilesSnapshot value) throws Exception {
+        int rootFileIds = value.rootFileIds.size();
+        encoder.writeSmallInt(rootFileIds);
+        for (String key : value.rootFileIds.keySet()) {
+            Long id = value.rootFileIds.get(key);
+            encoder.writeString(key);
+            if (id == null) {
+                encoder.writeBoolean(false);
+            } else {
+                encoder.writeBoolean(true);
+                encoder.writeLong(id);
+            }
+        }
+
+        serializer.write(encoder, value.filesSnapshot);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskArtifactStateCacheAccess.java
new file mode 100644
index 0000000..6997238
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskArtifactStateCacheAccess.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.cache.CacheAccess;
+import org.gradle.cache.PersistentStore;
+
+/**
+ * Provides access to the task history cache.
+ */
+public interface TaskArtifactStateCacheAccess extends PersistentStore, CacheAccess {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskExecution.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskExecution.java
new file mode 100644
index 0000000..7d70ecb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskExecution.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The persistent state for a single task execution.
+ */
+public abstract class TaskExecution {
+    private String taskClass;
+    private Map<String, Object> inputProperties;
+    private Set<String> outputFiles;
+
+    public Set<String> getOutputFiles() {
+        return outputFiles;
+    }
+
+    public void setOutputFiles(Set<String> outputFiles) {
+        this.outputFiles = outputFiles;
+    }
+
+    public String getTaskClass() {
+        return taskClass;
+    }
+
+    public void setTaskClass(String taskClass) {
+        this.taskClass = taskClass;
+    }
+
+    public Map<String, Object> getInputProperties() {
+        return inputProperties;
+    }
+
+    public void setInputProperties(Map<String, Object> inputProperties) {
+        this.inputProperties = inputProperties;
+    }
+
+    /**
+     * @return May return null.
+     */
+    public abstract FileCollectionSnapshot getOutputFilesSnapshot();
+
+    public abstract void setOutputFilesSnapshot(FileCollectionSnapshot outputFilesSnapshot);
+
+    /**
+     * @return May return null.
+     */
+    public abstract FileCollectionSnapshot getInputFilesSnapshot();
+
+    public abstract void setInputFilesSnapshot(FileCollectionSnapshot inputFilesSnapshot);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskHistoryRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskHistoryRepository.java
new file mode 100644
index 0000000..4d6cfc9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/state/TaskHistoryRepository.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state;
+
+import org.gradle.api.internal.TaskInternal;
+
+public interface TaskHistoryRepository {
+    History getHistory(TaskInternal task);
+
+    interface History {
+        TaskExecution getPreviousExecution();
+
+        TaskExecution getCurrentExecution();
+
+        void update();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
index 0ba1e15..9edcde8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
@@ -19,7 +19,7 @@ import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.GradleDistributionLocator;
 import org.gradle.internal.classpath.ClassPath;
 import org.gradle.internal.classpath.DefaultClassPath;
-import org.gradle.util.ClasspathUtil;
+import org.gradle.internal.classloader.ClasspathUtil;
 import org.gradle.util.GUtil;
 
 import java.io.File;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
index e95b9ed..fe14cc8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.classpath;
 
 import org.gradle.api.UncheckedIOException;
 import org.gradle.internal.classpath.DefaultClassPath;
-import org.gradle.util.ClasspathUtil;
+import org.gradle.internal.classloader.ClasspathUtil;
 
 import java.io.File;
 import java.net.URI;
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 3c121c8..193f910 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
@@ -43,7 +43,6 @@ public class ManifestUtil {
         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.
     private static String constructRelativeClasspathUri(File jarFile, File file) {
         URI jarFileUri = jarFile.getParentFile().toURI();
         URI fileUri = file.toURI();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/coerce/MethodArgumentsTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/coerce/MethodArgumentsTransformer.java
new file mode 100644
index 0000000..54ad179
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/coerce/MethodArgumentsTransformer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.coerce;
+
+/**
+ * Potentially transforms arguments to call a method with.
+ */
+public interface MethodArgumentsTransformer {
+
+    /**
+     * Transforms an argument list to call a method with.
+     *
+     * May return {@code args} if no transform is necessary.
+     *
+     * @param target The object to call the method on
+     * @param methodName The name of the method to call
+     * @param args The args to call the method with
+     * @return The args transformed, or args. Never null.
+     */
+    Object[] transform(Object target, String methodName, Object... args);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/coerce/TypeCoercingMethodArgumentsTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/coerce/TypeCoercingMethodArgumentsTransformer.java
new file mode 100644
index 0000000..344bfbe
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/coerce/TypeCoercingMethodArgumentsTransformer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.coerce;
+
+import org.gradle.api.Transformer;
+import org.gradle.internal.typeconversion.EnumFromCharSequenceNotationParser;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+    This guy is hardcoded to just deal with coercing calls to one arg enum methods, from char sequence values.
+    It will need to be made pluggable as more coercions come online.
+
+    Also need to consider some caching in here as there's potentially a lot of repetitive reflection.
+ */
+public class TypeCoercingMethodArgumentsTransformer implements MethodArgumentsTransformer {
+
+    public Object[] transform(Object target, String methodName, Object... args) {
+        return maybeTransformForEnum(target, methodName, args);
+    }
+
+    private Object[] maybeTransformForEnum(Object target, final String methodName, Object... args) {
+        if (args.length != 1 || !(args[0] instanceof CharSequence)) {
+            return args;
+        }
+
+        final CharSequence charSequenceArg = (CharSequence) args[0];
+
+        final List<Method> enumMethodHolder = new ArrayList<Method>(2);
+        final List<Method> stringMethodHolder = new ArrayList<Method>(1);
+
+        JavaReflectionUtil.searchMethods(target.getClass(), new Transformer<Boolean, Method>() {
+            public Boolean transform(Method method) {
+                Class<?>[] parameterTypes = method.getParameterTypes();
+                if (method.getName().equals(methodName) && parameterTypes.length == 1) {
+                    Class<?> parameterType = parameterTypes[0];
+
+                    if (parameterType.isAssignableFrom(charSequenceArg.getClass())) {
+                        stringMethodHolder.add(method);
+                        return true; // stop searching
+                    } else if (parameterType.isEnum()) {
+                        enumMethodHolder.add(method);
+                        if (enumMethodHolder.size() > 1) {
+                            return true; // stop searching
+                        }
+                    }
+                }
+
+                return false;
+            }
+        });
+
+        // There's a method that takes the uncoerced type
+        if (!stringMethodHolder.isEmpty()) {
+            return args;
+        }
+
+        // There's either no enum method, or more than one
+        if (enumMethodHolder.size() != 1) {
+            return args;
+        }
+
+        // Match, we can try and coerce
+        Method match = enumMethodHolder.get(0);
+        @SuppressWarnings("unchecked")
+        Class<? extends Enum> enumType = (Class<? extends Enum>) match.getParameterTypes()[0];
+        return new Object[]{toEnumValue(enumType, charSequenceArg)};
+    }
+
+    public <T extends Enum<T>> T toEnumValue(Class<T> enumType, CharSequence charSequence) {
+        EnumFromCharSequenceNotationParser<T> notationParser = new EnumFromCharSequenceNotationParser<T>(enumType);
+        return notationParser.parseNotation(charSequence);
+    }
+}
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 1b7cf03..f9e8925 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
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.collections;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.api.specs.Specs;
 import org.gradle.listener.ActionBroadcast;
 
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 c43f0bd..e793a5f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
@@ -22,8 +22,8 @@ import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
@@ -48,6 +48,10 @@ public abstract class AbstractFileResolver implements FileResolver {
         this.fileNotationParser = new FileOrUriNotationParser(fileSystem);
     }
 
+    public FileSystem getFileSystem() {
+        return fileSystem;
+    }
+
     public FileResolver withBaseDir(Object path) {
         return new BaseDirFileResolver(fileSystem, resolve(path));
     }
@@ -56,8 +60,8 @@ public abstract class AbstractFileResolver implements FileResolver {
         return resolve(path, PathValidation.NONE);
     }
 
-    public NotationParser<File> asNotationParser() {
-        return new NotationParser<File>() {
+    public NotationParser<Object, File> asNotationParser() {
+        return new NotationParser<Object, File>() {
             public File parseNotation(Object notation) throws UnsupportedNotationException {
                 // TODO Further differentiate between unsupported notation errors and others (particularly when we remove the deprecated 'notation.toString()' resolution)
                 return resolve(notation, PathValidation.NONE);
@@ -244,6 +248,10 @@ public abstract class AbstractFileResolver implements FileResolver {
         return resolveFiles(paths).getAsFileTree();
     }
 
+    public FileTree compositeFileTree(List<FileTree> fileTrees) {
+        return new DefaultCompositeFileTree(fileTrees);
+    }
+
     public ReadableResource resolveResource(Object path) {
         if (path instanceof ReadableResource) {
             return (ReadableResource) path;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java
index 2bc0f45..731611a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java
@@ -21,9 +21,6 @@ import org.gradle.api.resources.ReadableResource;
 import java.io.File;
 import java.net.URI;
 
-/**
- * by Szczepan Faber, created at: 11/23/11
- */
 public abstract class AbstractFileResource implements ReadableResource {
 
     protected final File file;
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 32c3744..242ce8d 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
@@ -21,14 +21,23 @@ 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 org.gradle.util.GFileUtils;
 
 import java.io.*;
 
 public abstract class AbstractFileTreeElement implements FileTreeElement {
+    private final Chmod chmod;
+
     public abstract String getDisplayName();
 
+    protected AbstractFileTreeElement(Chmod chmod) {
+        this.chmod = chmod;
+    }
+
+    protected Chmod getChmod() {
+        return chmod;
+    }
+
     @Override
     public String toString() {
         return getDisplayName();
@@ -64,17 +73,13 @@ public abstract class AbstractFileTreeElement implements FileTreeElement {
                 GFileUtils.mkdirs(target.getParentFile());
                 copyFile(target);
             }
-            getChmod().chmod(target, getMode());
+            chmod.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/AntFileCollectionBuilder.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionBuilder.groovy
index f7d47cf..931fee7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionBuilder.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionBuilder.groovy
@@ -16,12 +16,9 @@
  
 package org.gradle.api.internal.file
 
-import org.gradle.api.tasks.AntBuilderAware
 import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.AntBuilderAware
 
-/**
- * @author Hans Dockter
- */
 class AntFileCollectionBuilder implements AntBuilderAware {
     private final FileCollection files
 
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 49c4394..e67d58c 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
@@ -26,9 +26,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public class BaseDirFileResolver extends AbstractFileResolver {
     private final File baseDir;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CopyActionProcessingStreamAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CopyActionProcessingStreamAction.java
new file mode 100644
index 0000000..a8fb36c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CopyActionProcessingStreamAction.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file;
+
+import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
+
+public interface CopyActionProcessingStreamAction {
+
+    void processFile(FileCopyDetailsInternal details);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultCompositeFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultCompositeFileTree.java
new file mode 100644
index 0000000..19aa306
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultCompositeFileTree.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
+
+import java.util.List;
+
+public class DefaultCompositeFileTree extends CompositeFileTree {
+    private final List<FileTree> fileTrees;
+
+    public DefaultCompositeFileTree(List<FileTree> fileTrees) {
+        this.fileTrees = fileTrees;
+    }
+
+    @Override
+    public void resolve(FileCollectionResolveContext context) {
+        context.add(fileTrees);
+    }
+
+    @Override
+    protected List<FileTree> getSourceCollections() {
+        return fileTrees;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "file tree";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileLookup.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileLookup.java
new file mode 100644
index 0000000..f781b7e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileLookup.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FileSystem;
+
+import java.io.File;
+
+public class DefaultFileLookup implements FileLookup {
+    private final FileSystem fileSystem;
+    private final IdentityFileResolver fileResolver;
+
+    public DefaultFileLookup(FileSystem fileSystem) {
+        this.fileSystem = fileSystem;
+        fileResolver = new IdentityFileResolver(this.fileSystem);
+    }
+
+    public FileSystem getFileSystem() {
+        return fileSystem;
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public FileResolver getFileResolver(File baseDirectory) {
+        return fileResolver.withBaseDir(baseDirectory);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
index 7a19cbf..065d6f0 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
@@ -16,25 +16,28 @@
 package org.gradle.api.internal.file;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.PathValidation;
 import org.gradle.api.file.*;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.ProcessOperations;
 import org.gradle.api.internal.file.archive.TarFileTree;
 import org.gradle.api.internal.file.archive.ZipFileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileTree;
 import org.gradle.api.internal.file.collections.FileTreeAdapter;
-import org.gradle.api.internal.file.copy.*;
+import org.gradle.api.internal.file.copy.DefaultCopySpec;
+import org.gradle.api.internal.file.copy.DeleteActionImpl;
+import org.gradle.api.internal.file.copy.FileCopier;
 import org.gradle.api.internal.resources.DefaultResourceHandler;
 import org.gradle.api.internal.tasks.TaskResolver;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.process.ExecResult;
-import org.gradle.process.internal.DefaultExecAction;
-import org.gradle.process.internal.DefaultJavaExecAction;
-import org.gradle.process.internal.ExecAction;
-import org.gradle.process.internal.JavaExecAction;
+import org.gradle.process.internal.*;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.GFileUtils;
 
@@ -45,19 +48,25 @@ import java.util.Map;
 
 import static org.gradle.util.ConfigureUtil.configure;
 
-public class DefaultFileOperations implements FileOperations, ProcessOperations {
+public class DefaultFileOperations implements FileOperations, ProcessOperations, ExecActionFactory {
     private final FileResolver fileResolver;
     private final TaskResolver taskResolver;
     private final TemporaryFileProvider temporaryFileProvider;
-    private DeleteAction deleteAction;
+    private final Instantiator instantiator;
+    private final DeleteAction deleteAction;
     private final DefaultResourceHandler resourceHandler;
+    private final FileCopier fileCopier;
+    private final FileSystem fileSystem;
 
-    public DefaultFileOperations(FileResolver fileResolver, TaskResolver taskResolver, TemporaryFileProvider temporaryFileProvider) {
+    public DefaultFileOperations(FileResolver fileResolver, TaskResolver taskResolver, TemporaryFileProvider temporaryFileProvider, Instantiator instantiator, FileLookup fileLookup) {
         this.fileResolver = fileResolver;
         this.taskResolver = taskResolver;
         this.temporaryFileProvider = temporaryFileProvider;
+        this.instantiator = instantiator;
         this.deleteAction = new DeleteActionImpl(fileResolver);
         this.resourceHandler = new DefaultResourceHandler(fileResolver);
+        fileCopier = new FileCopier(this.instantiator, this.fileResolver, fileLookup);
+        fileSystem = fileLookup.getFileSystem();
     }
 
     public File file(Object path) {
@@ -81,7 +90,7 @@ public class DefaultFileOperations implements FileOperations, ProcessOperations
     }
 
     public ConfigurableFileTree fileTree(Object baseDir) {
-        return new DefaultConfigurableFileTree(baseDir, fileResolver, taskResolver);
+        return new DefaultConfigurableFileTree(baseDir, fileResolver, taskResolver, fileCopier);
     }
 
     public ConfigurableFileTree fileTree(Object baseDir, Closure closure) {
@@ -89,22 +98,23 @@ public class DefaultFileOperations implements FileOperations, ProcessOperations
     }
 
     public ConfigurableFileTree fileTree(Map<String, ?> args) {
-        return new DefaultConfigurableFileTree(args, fileResolver, taskResolver);
+        return new DefaultConfigurableFileTree(args, fileResolver, taskResolver, fileCopier);
     }
 
+    @Deprecated
     public ConfigurableFileTree fileTree(Closure closure) {
         // This method is deprecated, but the deprecation warning is added on public classes that delegate to this. 
-        return configure(closure, new DefaultConfigurableFileTree(Collections.emptyMap(), fileResolver, taskResolver));
+        return configure(closure, new DefaultConfigurableFileTree(Collections.emptyMap(), fileResolver, taskResolver, fileCopier));
     }
 
     public FileTree zipTree(Object zipPath) {
-        return new FileTreeAdapter(new ZipFileTree(file(zipPath), getExpandDir()));
+        return new FileTreeAdapter(new ZipFileTree(file(zipPath), getExpandDir(), fileSystem));
     }
 
     public FileTree tarTree(Object tarPath) {
         ReadableResource res = getResources().maybeCompressed(tarPath);
 
-        TarFileTree tarTree = new TarFileTree(res, getExpandDir());
+        TarFileTree tarTree = new TarFileTree(res, getExpandDir(), fileSystem);
         return new FileTreeAdapter(tarTree);
     }
 
@@ -130,37 +140,41 @@ public class DefaultFileOperations implements FileOperations, ProcessOperations
     }
 
     public WorkResult copy(Closure closure) {
-        CopyActionImpl action = configure(closure, new FileCopyActionImpl(fileResolver, new FileCopySpecVisitor()));
-        action.execute();
-        return action;
+        return fileCopier.copy(new ClosureBackedAction<CopySpec>(closure));
     }
 
-    public CopySpec copySpec(Closure closure) {
-        return configure(closure, new CopySpecImpl(fileResolver));
+    public WorkResult sync(Action<? super CopySpec> action) {
+        return fileCopier.sync(action);
     }
 
-    public FileResolver getFileResolver() {
-        return fileResolver;
+    public CopySpec copySpec(Closure closure) {
+        return copySpec(new ClosureBackedAction<CopySpec>(closure));
     }
 
-    public DeleteAction getDeleteAction() {
-        return deleteAction;
+    public CopySpec copySpec(Action<? super CopySpec> action) {
+        DefaultCopySpec copySpec = instantiator.newInstance(DefaultCopySpec.class, fileResolver, instantiator);
+        action.execute(copySpec);
+        return copySpec;
     }
 
-    public void setDeleteAction(DeleteAction deleteAction) {
-        this.deleteAction = deleteAction;
+    public FileResolver getFileResolver() {
+        return fileResolver;
     }
 
     public ExecResult javaexec(Closure cl) {
-        JavaExecAction javaExecAction = ConfigureUtil.configure(cl, new DefaultJavaExecAction(fileResolver));
+        JavaExecAction javaExecAction = ConfigureUtil.configure(cl, instantiator.newInstance(DefaultJavaExecAction.class, fileResolver));
         return javaExecAction.execute();
     }
 
     public ExecResult exec(Closure cl) {
-        ExecAction execAction = ConfigureUtil.configure(cl, new DefaultExecAction(fileResolver));
+        ExecAction execAction = ConfigureUtil.configure(cl, instantiator.newInstance(DefaultExecAction.class, fileResolver));
         return execAction.execute();
     }
 
+    public ExecAction newExecAction() {
+        return new DefaultExecAction(fileResolver);
+    }
+
     public DefaultResourceHandler getResources() {
         return resourceHandler;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
index 19081a6..f549091 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
@@ -17,20 +17,24 @@ package org.gradle.api.internal.file;
 
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.RelativePath;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
+import org.gradle.internal.nativeplatform.filesystem.Stat;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 
 public class DefaultFileTreeElement extends AbstractFileTreeElement {
     private final File file;
     private final RelativePath relativePath;
+    private final Stat stat;
 
-    public DefaultFileTreeElement(File file, RelativePath relativePath) {
+    public DefaultFileTreeElement(File file, RelativePath relativePath, Chmod chmod, Stat stat) {
+        super(chmod);
         this.file = file;
         this.relativePath = relativePath;
+        this.stat = stat;
     }
 
     public File getFile() {
@@ -63,7 +67,7 @@ public class DefaultFileTreeElement extends AbstractFileTreeElement {
 
     public int getMode() {
         try {
-            return FileSystems.getDefault().getUnixMode(file);
+            return stat.getUnixMode(file);
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java
index 2cbccbd..f8caa25 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java
@@ -17,6 +17,8 @@ package org.gradle.api.internal.file;
 
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
+import org.gradle.internal.nativeplatform.filesystem.Stat;
 
 import java.io.File;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -24,8 +26,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class DefaultFileVisitDetails extends DefaultFileTreeElement implements FileVisitDetails {
     private final AtomicBoolean stop;
 
-    public DefaultFileVisitDetails(File file, RelativePath relativePath, AtomicBoolean stop) {
-        super(file, relativePath);
+    public DefaultFileVisitDetails(File file, RelativePath relativePath, AtomicBoolean stop, Chmod chmod, Stat stat) {
+        super(file, relativePath, chmod, stat);
         this.stop = stop;
     }
 
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 5e0d564..96311f5 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
@@ -24,8 +24,9 @@ import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.Serializable;
 
-public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
+public class DefaultTemporaryFileProvider implements TemporaryFileProvider, Serializable {
     private final Factory<File> baseDirFactory;
 
     public DefaultTemporaryFileProvider(final Factory<File> fileFactory) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileLookup.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileLookup.java
new file mode 100644
index 0000000..b57a934
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileLookup.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FileSystem;
+
+import java.io.File;
+
+/**
+ * Provides access to various services to resolve and locate files.
+ */
+public interface FileLookup {
+    FileSystem getFileSystem();
+
+    /**
+     * Returns a file resolver with no base directory.
+     */
+    FileResolver getFileResolver();
+
+    /**
+     * Returns a file resolver with the given base directory.
+     */
+    FileResolver getFileResolver(File baseDirectory);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
index 30ec18b..71e21cf 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.file;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.PathValidation;
 import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.ConfigurableFileTree;
@@ -58,8 +59,12 @@ public interface FileOperations {
 
     CopySpec copySpec(Closure closure);
 
+    CopySpec copySpec(Action<? super CopySpec> action);
+
     WorkResult copy(Closure closure);
 
+    WorkResult sync(Action<? super CopySpec> action);
+
     File mkdir(Object path);
 
     boolean delete(Object... paths);
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 2b9d3c8..f5675f6 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
@@ -17,9 +17,8 @@
 package org.gradle.api.internal.file;
 
 import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
@@ -31,7 +30,7 @@ import java.util.Collection;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class FileOrUriNotationParser<T extends Serializable> implements NotationParser<T> {
+public class FileOrUriNotationParser<T extends Serializable> implements NotationParser<Object, T> {
 
     private static final Pattern URI_SCHEME = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-\\.]*:.+");
     private static final Pattern ENCODED_URI = Pattern.compile("%([0-9a-fA-F]{2})");
@@ -45,7 +44,7 @@ public class FileOrUriNotationParser<T extends Serializable> implements Notation
         candidateFormats.add("File, URI, URL or CharSequence is supported");
     }
 
-    public T parseNotation(Object notation) throws UnsupportedNotationException {
+    public T parseNotation(Object notation) {
         if (notation instanceof File) {
             return (T) notation;
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
index 0d5c954..434cd32 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
@@ -18,12 +18,13 @@ package org.gradle.api.internal.file;
 import org.gradle.api.PathValidation;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.internal.Factory;
+import org.gradle.internal.typeconversion.NotationParser;
 
 import java.io.File;
 import java.net.URI;
+import java.util.List;
 
 public interface FileResolver {
     File resolve(Object path);
@@ -38,15 +39,11 @@ public interface FileResolver {
 
     FileTree resolveFilesAsTree(Object... paths);
 
+    FileTree compositeFileTree(List<FileTree> fileTrees);
+
     URI resolveUri(Object path);
 
     String resolveAsRelativePath(Object path);
 
-    /**
-     * Creates a new resolver with the given base directory.
-     * @param path The path for the base directory. Resolved relative to the current base directory (if any).
-     */
-    FileResolver withBaseDir(Object path);
-
-    NotationParser<File> asNotationParser();
+    NotationParser<Object, File> asNotationParser();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java
index 55c0a62..35472cf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java
@@ -21,9 +21,6 @@ import org.gradle.api.resources.MissingResourceException;
 
 import java.io.*;
 
-/**
- * by Szczepan Faber, created at: 11/22/11
- */
 public class FileResource extends AbstractFileResource {
 
     public FileResource(File file) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
index 3f1068d..02ab3e4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
@@ -15,21 +15,24 @@
  */
 package org.gradle.api.internal.file;
 
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.services.FileSystems;
 
 import java.io.File;
 
 /**
  * FileResolver that uses the file provided to it or constructs one from the toString of the provided object. Used in cases where a FileResolver is needed by the infrastructure, but no base directory
  * can be known.
- *
- * @author Steve Appling
  */
 public class IdentityFileResolver extends AbstractFileResolver {
     public IdentityFileResolver() {
         super(FileSystems.getDefault());
     }
 
+    public IdentityFileResolver(FileSystem fileSystem) {
+        super(fileSystem);
+    }
+
     @Override
     protected File doResolve(Object path) {
         File file = convertObjectToFile(path);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java
index cbc1a15..27fee31 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java
@@ -26,9 +26,6 @@ import org.gradle.api.tasks.bundling.Compression;
 import java.io.InputStream;
 import java.net.URI;
 
-/**
- * by Szczepan Faber, created at: 11/23/11
- */
 public class MaybeCompressedFileResource implements ReadableResource {
 
     private final ReadableResource resource;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
new file mode 100644
index 0000000..af4dc71
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.archive;
+
+import org.apache.tools.tar.TarEntry;
+import org.apache.tools.tar.TarOutputStream;
+import org.apache.tools.zip.UnixStat;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.internal.ErroringAction;
+import org.gradle.internal.IoActions;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
+import org.gradle.api.internal.file.copy.CopyAction;
+import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
+import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+
+import java.io.File;
+import java.io.OutputStream;
+
+public class TarCopyAction implements CopyAction {
+    private final File tarFile;
+    private final ArchiveOutputStreamFactory compressor;
+
+    public TarCopyAction(File tarFile, ArchiveOutputStreamFactory compressor) {
+        this.tarFile = tarFile;
+        this.compressor = compressor;
+    }
+
+    public WorkResult execute(final CopyActionProcessingStream stream) {
+
+        final OutputStream outStr;
+        try {
+            outStr = compressor.createArchiveOutputStream(tarFile);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not create TAR '%s'.", tarFile), e);
+        }
+
+        IoActions.withResource(outStr, new ErroringAction<OutputStream>() {
+            @Override
+            protected void doExecute(final OutputStream outStr) throws Exception {
+                TarOutputStream tarOutStr;
+                try {
+                    tarOutStr = new TarOutputStream(outStr);
+                } catch (Exception e) {
+                    throw new GradleException(String.format("Could not create TAR '%s'.", tarFile), e);
+                }
+                tarOutStr.setLongFileMode(TarOutputStream.LONGFILE_GNU);
+                stream.process(new StreamAction(tarOutStr));
+                tarOutStr.close();
+            }
+        });
+
+        return new SimpleWorkResult(true);
+    }
+
+    private class StreamAction implements CopyActionProcessingStreamAction {
+        private final TarOutputStream tarOutStr;
+
+        public StreamAction(TarOutputStream tarOutStr) {
+            this.tarOutStr = tarOutStr;
+        }
+
+        public void processFile(FileCopyDetailsInternal details) {
+            if (details.isDirectory()) {
+                visitDir(details);
+            } else {
+                visitFile(details);
+            }
+        }
+
+        private void visitFile(FileCopyDetails fileDetails) {
+            try {
+                TarEntry archiveEntry = new TarEntry(fileDetails.getRelativePath().getPathString());
+                archiveEntry.setModTime(fileDetails.getLastModified());
+                archiveEntry.setSize(fileDetails.getSize());
+                archiveEntry.setMode(UnixStat.FILE_FLAG | fileDetails.getMode());
+                tarOutStr.putNextEntry(archiveEntry);
+                fileDetails.copyTo(tarOutStr);
+                tarOutStr.closeEntry();
+            } catch (Exception e) {
+                throw new GradleException(String.format("Could not add %s to TAR '%s'.", fileDetails, tarFile), e);
+            }
+        }
+
+        private void visitDir(FileCopyDetails dirDetails) {
+            try {
+                // Trailing slash on name indicates entry is a directory
+                TarEntry archiveEntry = new TarEntry(dirDetails.getRelativePath().getPathString() + '/');
+                archiveEntry.setModTime(dirDetails.getLastModified());
+                archiveEntry.setMode(UnixStat.DIR_FLAG | dirDetails.getMode());
+                tarOutStr.putNextEntry(archiveEntry);
+                tarOutStr.closeEntry();
+            } catch (Exception e) {
+                throw new GradleException(String.format("Could not add %s to TAR '%s'.", dirDetails, tarFile), e);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
deleted file mode 100644
index 506dc4c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
+++ /dev/null
@@ -1,88 +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.file.archive;
-
-import org.apache.tools.tar.TarEntry;
-import org.apache.tools.tar.TarOutputStream;
-import org.apache.tools.zip.UnixStat;
-import org.gradle.api.GradleException;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
-import org.gradle.api.internal.file.copy.CopyAction;
-import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class TarCopySpecVisitor extends EmptyCopySpecVisitor {
-    private TarOutputStream tarOutStr;
-    private File tarFile;
-
-    public void startVisit(CopyAction action) {
-        ArchiveCopyAction archiveAction = (ArchiveCopyAction) action;
-        try {
-            tarFile = archiveAction.getArchivePath();
-            OutputStream outStr = archiveAction.getCompressor().createArchiveOutputStream(tarFile);
-            tarOutStr = new TarOutputStream(outStr);
-            tarOutStr.setLongFileMode(TarOutputStream.LONGFILE_GNU);
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not create TAR '%s'.", tarFile), e);
-        }
-    }
-
-    public void endVisit() {
-        try {
-            tarOutStr.close();
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        } finally {
-            tarOutStr = null;
-        }
-    }
-
-    public void visitFile(FileVisitDetails fileDetails) {
-        try {
-            TarEntry archiveEntry = new TarEntry(fileDetails.getRelativePath().getPathString());
-            archiveEntry.setModTime(fileDetails.getLastModified());
-            archiveEntry.setSize(fileDetails.getSize());
-            archiveEntry.setMode(UnixStat.FILE_FLAG | fileDetails.getMode());
-            tarOutStr.putNextEntry(archiveEntry);
-            fileDetails.copyTo(tarOutStr);
-            tarOutStr.closeEntry();
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not add %s to TAR '%s'.", fileDetails, tarFile), e);
-        }
-    }
-
-    public void visitDir(FileVisitDetails dirDetails) {
-        try {
-            // Trailing slash on name indicates entry is a directory
-            TarEntry archiveEntry = new TarEntry(dirDetails.getRelativePath().getPathString() + '/');
-            archiveEntry.setModTime(dirDetails.getLastModified());
-            archiveEntry.setMode(UnixStat.DIR_FLAG | dirDetails.getMode());
-            tarOutStr.putNextEntry(archiveEntry);
-            tarOutStr.closeEntry();
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not add %s to TAR '%s'.", dirDetails, tarFile), e);
-        }
-    }
-
-    public boolean getDidWork() {
-        return true;
-    }
-}
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 ed016c5..3815558 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
@@ -29,9 +29,10 @@ import org.gradle.api.internal.file.collections.MinimalFileTree;
 import org.gradle.api.resources.MissingResourceException;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.api.resources.ResourceException;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
 import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.hash.HashUtil;
+import org.gradle.internal.hash.HashUtil;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,10 +41,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
     private final ReadableResource resource;
+    private final Chmod chmod;
     private final File tmpDir;
 
-    public TarFileTree(ReadableResource resource, File tmpDir) {
+    public TarFileTree(ReadableResource resource, File tmpDir, Chmod chmod) {
         this.resource = resource;
+        this.chmod = chmod;
         String expandDirName = String.format("%s_%s", resource.getBaseName(), HashUtil.createCompactMD5(resource.getURI().toString()));
         this.tmpDir = new File(tmpDir, expandDirName);
     }
@@ -91,11 +94,10 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         TarEntry entry;
         while (!stopFlag.get() && (entry = tar.getNextEntry()) != null) {
             if (entry.isDirectory()) {
-                visitor.visitDir(new DetailsImpl(entry, tar, stopFlag));
+                visitor.visitDir(new DetailsImpl(entry, tar, stopFlag, chmod));
             } else {
-                visitor.visitFile(new DetailsImpl(entry, tar, stopFlag));
+                visitor.visitFile(new DetailsImpl(entry, tar, stopFlag, chmod));
             }
-
         }
     }
 
@@ -106,7 +108,8 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         private File file;
         private boolean read;
 
-        public DetailsImpl(TarEntry entry, NoCloseTarInputStream tar, AtomicBoolean stopFlag) {
+        public DetailsImpl(TarEntry entry, NoCloseTarInputStream tar, AtomicBoolean stopFlag, Chmod chmod) {
+            super(chmod);
             this.entry = entry;
             this.tar = tar;
             this.stopFlag = stopFlag;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java
index 14ebd22..bce8bf5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2010 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,10 +15,103 @@
  */
 package org.gradle.api.internal.file.archive;
 
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.apache.tools.zip.UnixStat;
+import org.apache.tools.zip.Zip64RequiredException;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipOutputStream;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.file.copy.CopyAction;
+import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
+import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
 import org.gradle.api.internal.file.copy.ZipCompressor;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.bundling.Zip;
+import org.gradle.internal.IoActions;
 
-public interface ZipCopyAction extends ArchiveCopyAction {
+import java.io.File;
 
-    public ZipCompressor getCompressor();
+public class ZipCopyAction implements CopyAction {
+    private final File zipFile;
+    private final ZipCompressor compressor;
+    private final DocumentationRegistry documentationRegistry;
+
+    public ZipCopyAction(File zipFile, ZipCompressor compressor, DocumentationRegistry documentationRegistry) {
+        this.zipFile = zipFile;
+        this.compressor = compressor;
+        this.documentationRegistry = documentationRegistry;
+    }
+
+    public WorkResult execute(final CopyActionProcessingStream stream) {
+        final ZipOutputStream zipOutStr;
+
+        try {
+            zipOutStr = compressor.createArchiveOutputStream(zipFile);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not create ZIP '%s'.", zipFile), e);
+        }
+
+        try {
+            IoActions.withResource(zipOutStr, new Action<ZipOutputStream>() {
+                public void execute(ZipOutputStream outputStream) {
+                    stream.process(new StreamAction(outputStream));
+                }
+            });
+        } catch (UncheckedIOException e) {
+            if (e.getCause() instanceof Zip64RequiredException) {
+                throw new org.gradle.api.tasks.bundling.internal.Zip64RequiredException(
+                        String.format("%s\n\nTo build this archive, please enable the zip64 extension.\nSee: %s", e.getCause().getMessage(), documentationRegistry.getDslRefForProperty(Zip.class, "zip64"))
+                );
+            }
+        }
+
+        return new SimpleWorkResult(true);
+    }
+
+    private class StreamAction implements CopyActionProcessingStreamAction {
+        private final ZipOutputStream zipOutStr;
+
+        public StreamAction(ZipOutputStream zipOutStr) {
+            this.zipOutStr = zipOutStr;
+        }
+
+        public void processFile(FileCopyDetailsInternal details) {
+            if (details.isDirectory()) {
+                visitDir(details);
+            } else {
+                visitFile(details);
+            }
+        }
+
+        private void visitFile(FileCopyDetails fileDetails) {
+            try {
+                ZipEntry archiveEntry = new ZipEntry(fileDetails.getRelativePath().getPathString());
+                archiveEntry.setTime(fileDetails.getLastModified());
+                archiveEntry.setUnixMode(UnixStat.FILE_FLAG | fileDetails.getMode());
+                zipOutStr.putNextEntry(archiveEntry);
+                fileDetails.copyTo(zipOutStr);
+                zipOutStr.closeEntry();
+            } catch (Exception e) {
+                throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e);
+            }
+        }
+
+        private void visitDir(FileCopyDetails dirDetails) {
+            try {
+                // Trailing slash in name indicates that entry is a directory
+                ZipEntry archiveEntry = new ZipEntry(dirDetails.getRelativePath().getPathString() + '/');
+                archiveEntry.setTime(dirDetails.getLastModified());
+                archiveEntry.setUnixMode(UnixStat.DIR_FLAG | dirDetails.getMode());
+                zipOutStr.putNextEntry(archiveEntry);
+                zipOutStr.closeEntry();
+            } catch (Exception e) {
+                throw new GradleException(String.format("Could not add %s to ZIP '%s'.", dirDetails, zipFile), e);
+            }
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
deleted file mode 100644
index 1491f4b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.archive;
-
-import org.apache.tools.zip.*;
-import org.gradle.api.GradleException;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.file.copy.CopyAction;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
-
-import java.io.File;
-import java.io.IOException;
-
-public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
-    private ZipOutputStream zipOutStr;
-    private File zipFile;
-
-    public void startVisit(CopyAction action) {
-        ZipCopyAction archiveAction = (ZipCopyAction) action;
-        zipFile = archiveAction.getArchivePath();
-        try {
-            zipOutStr = archiveAction.getCompressor().createArchiveOutputStream(zipFile);
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not create ZIP '%s'.", zipFile), e);
-        }
-    }
-
-    public void endVisit() {
-        try {
-            zipOutStr.close();
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        } finally {
-            zipOutStr = null;
-        }
-    }
-
-    public void visitFile(FileVisitDetails fileDetails) {
-        try {
-            ZipEntry archiveEntry = new ZipEntry(fileDetails.getRelativePath().getPathString());
-            archiveEntry.setTime(fileDetails.getLastModified());
-            archiveEntry.setUnixMode(UnixStat.FILE_FLAG | fileDetails.getMode());
-            zipOutStr.putNextEntry(archiveEntry);
-            fileDetails.copyTo(zipOutStr);
-            zipOutStr.closeEntry();
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e);
-        }
-    }
-
-    public void visitDir(FileVisitDetails dirDetails) {
-        try {
-            // Trailing slash in name indicates that entry is a directory
-            ZipEntry archiveEntry = new ZipEntry(dirDetails.getRelativePath().getPathString() + '/');
-            archiveEntry.setTime(dirDetails.getLastModified());
-            archiveEntry.setUnixMode(UnixStat.DIR_FLAG | dirDetails.getMode());
-            zipOutStr.putNextEntry(archiveEntry);
-            zipOutStr.closeEntry();
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not add %s to ZIP '%s'.", dirDetails, zipFile), e);
-        }
-    }
-
-    public boolean getDidWork() {
-        return true;
-    }
-}
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 d229bf4..e46dfb5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
@@ -27,9 +27,10 @@ import org.gradle.api.internal.file.AbstractFileTreeElement;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
 import org.gradle.api.internal.file.collections.FileSystemMirroringFileTree;
 import org.gradle.api.internal.file.collections.MinimalFileTree;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.util.DeprecationLogger;
-import org.gradle.util.hash.HashUtil;
+import org.gradle.internal.hash.HashUtil;
 
 import java.io.File;
 import java.io.IOException;
@@ -42,10 +43,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
     private final File zipFile;
+    private final Chmod chmod;
     private final File tmpDir;
 
-    public ZipFileTree(File zipFile, File tmpDir) {
+    public ZipFileTree(File zipFile, File tmpDir, Chmod chmod) {
         this.zipFile = zipFile;
+        this.chmod = chmod;
         String expandDirName = String.format("%s_%s", zipFile.getName(), HashUtil.createCompactMD5(zipFile.getAbsolutePath()));
         this.tmpDir = new File(tmpDir, expandDirName);
     }
@@ -86,9 +89,9 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
                 while (!stopFlag.get() && sortedEntries.hasNext()) {
                     ZipEntry entry = sortedEntries.next();
                     if (entry.isDirectory()) {
-                        visitor.visitDir(new DetailsImpl(entry, zip, stopFlag));
+                        visitor.visitDir(new DetailsImpl(entry, zip, stopFlag, chmod));
                     } else {
-                        visitor.visitFile(new DetailsImpl(entry, zip, stopFlag));
+                        visitor.visitFile(new DetailsImpl(entry, zip, stopFlag, chmod));
                     }
                 }
             } finally {
@@ -105,7 +108,8 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         private final AtomicBoolean stopFlag;
         private File file;
 
-        public DetailsImpl(ZipEntry entry, ZipFile zip, AtomicBoolean stopFlag) {
+        public DetailsImpl(ZipEntry entry, ZipFile zip, AtomicBoolean stopFlag, Chmod chmod) {
+            super(chmod);
             this.entry = entry;
             this.zip = zip;
             this.stopFlag = stopFlag;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java
index b923418..fb4f1d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java
@@ -28,9 +28,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URI;
 
-/**
- * by Szczepan Faber, created at: 11/16/11
- */
 public class Bzip2Archiver implements ReadableResource {
 
     private final ReadableResource resource;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java
index 619d812..4cef989 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java
@@ -28,9 +28,6 @@ import java.net.URI;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
-/**
- * by Szczepan Faber, created at: 11/16/11
- */
 public class GzipArchiver implements ReadableResource {
 
     private ReadableResource resource;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
index c47a5df..f886266 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
@@ -20,9 +20,6 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStream;
 
-/**
- * by Szczepan Faber, created at: 11/16/11
- */
 public class SimpleCompressor implements ArchiveOutputStreamFactory {
 
     public OutputStream createArchiveOutputStream(File destination) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTree.java
index 21acdcf..87362dc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTree.java
@@ -16,15 +16,14 @@
 package org.gradle.api.internal.file.collections;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.CopySpec;
 import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.internal.file.CompositeFileTree;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.internal.file.copy.CopyActionImpl;
-import org.gradle.api.internal.file.copy.FileCopyActionImpl;
-import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
+import org.gradle.api.internal.file.copy.FileCopier;
 import org.gradle.api.internal.tasks.DefaultTaskDependency;
 import org.gradle.api.internal.tasks.TaskResolver;
 import org.gradle.api.specs.Spec;
@@ -38,21 +37,20 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultConfigurableFileTree extends CompositeFileTree implements ConfigurableFileTree {
     private PatternSet patternSet = new PatternSet();
     private Object dir;
     private final FileResolver resolver;
+    private final FileCopier fileCopier;
     private final DefaultTaskDependency buildDependency;
 
-    public DefaultConfigurableFileTree(Object dir, FileResolver resolver, TaskResolver taskResolver) {
-        this(Collections.singletonMap("dir", dir), resolver, taskResolver);
+    public DefaultConfigurableFileTree(Object dir, FileResolver resolver, TaskResolver taskResolver, FileCopier fileCopier) {
+        this(Collections.singletonMap("dir", dir), resolver, taskResolver, fileCopier);
     }
 
-    public DefaultConfigurableFileTree(Map<String, ?> args, FileResolver resolver, TaskResolver taskResolver) {
-        this.resolver = resolver != null ? resolver : new IdentityFileResolver();
+    public DefaultConfigurableFileTree(Map<String, ?> args, FileResolver resolver, TaskResolver taskResolver, FileCopier fileCopier) {
+        this.resolver = resolver;
+        this.fileCopier = fileCopier;
         ConfigureUtil.configureByMap(args, this);
         buildDependency = new DefaultTaskDependency(taskResolver);
     }
@@ -82,12 +80,13 @@ public class DefaultConfigurableFileTree extends CompositeFileTree implements Co
         return String.format("directory '%s'", dir);
     }
 
-    public WorkResult copy(Closure closure) {
-        CopyActionImpl action = new FileCopyActionImpl(resolver, new FileCopySpecVisitor());
-        action.from(this);
-        ConfigureUtil.configure(closure, action);
-        action.execute();
-        return action;
+    public WorkResult copy(final Closure closure) {
+        return fileCopier.copy(new Action<CopySpec>() {
+            public void execute(CopySpec copySpec) {
+                copySpec.from(DefaultConfigurableFileTree.this);
+                ConfigureUtil.configure(closure, copySpec);
+            }
+        });
     }
 
     public Set<String> getIncludes() {
@@ -108,7 +107,7 @@ public class DefaultConfigurableFileTree extends CompositeFileTree implements Co
         return this;
     }
 
-    public DefaultConfigurableFileTree include(String ... includes) {
+    public DefaultConfigurableFileTree include(String... includes) {
         patternSet.include(includes);
         return this;
     }
@@ -128,7 +127,7 @@ public class DefaultConfigurableFileTree extends CompositeFileTree implements Co
         return this;
     }
 
-    public DefaultConfigurableFileTree exclude(String ... excludes) {
+    public DefaultConfigurableFileTree exclude(String... excludes) {
         patternSet.exclude(excludes);
         return this;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileCollection.java
new file mode 100644
index 0000000..ac291ce
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileCollection.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.collections;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * A file collection that delegates each method call to the
+ * file collection returned by {@link #getDelegate()}.
+ */
+public abstract class DelegatingFileCollection implements FileCollection, MinimalFileSet {
+    public abstract FileCollection getDelegate();
+
+    public File getSingleFile() throws IllegalStateException {
+        return getDelegate().getSingleFile();
+    }
+
+    public Set<File> getFiles() {
+        return getDelegate().getFiles();
+    }
+
+    public boolean contains(File file) {
+        return getDelegate().contains(file);
+    }
+
+    public String getAsPath() {
+        return getDelegate().getAsPath();
+    }
+
+    public FileCollection plus(FileCollection collection) {
+        return getDelegate().plus(collection);
+    }
+
+    public FileCollection minus(FileCollection collection) {
+        return getDelegate().minus(collection);
+    }
+
+    public FileCollection filter(Closure filterClosure) {
+        return getDelegate().filter(filterClosure);
+    }
+
+    public FileCollection filter(Spec<? super File> filterSpec) {
+        return getDelegate().filter(filterSpec);
+    }
+
+    public Object asType(Class<?> type) throws UnsupportedOperationException {
+        return getDelegate().asType(type);
+    }
+
+    public FileCollection add(FileCollection collection) throws UnsupportedOperationException {
+        return getDelegate().add(collection);
+    }
+
+    public boolean isEmpty() {
+        return getDelegate().isEmpty();
+    }
+
+    public FileCollection stopExecutionIfEmpty() throws StopExecutionException {
+        return getDelegate().stopExecutionIfEmpty();
+    }
+
+    public FileTree getAsFileTree() {
+        return getDelegate().getAsFileTree();
+    }
+
+    public void addToAntBuilder(Object builder, String nodeName, AntType type) {
+        getDelegate().addToAntBuilder(builder, nodeName, type);
+    }
+
+    public Object addToAntBuilder(Object builder, String nodeName) {
+        return getDelegate().addToAntBuilder(builder, nodeName);
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return getDelegate().getBuildDependencies();
+    }
+
+    public Iterator<File> iterator() {
+        return getDelegate().iterator();
+    }
+
+    public String getDisplayName() {
+        FileCollection delegate = getDelegate();
+        if (delegate instanceof MinimalFileSet) {
+            return ((MinimalFileSet) delegate).getDisplayName();
+        }
+        return getDelegate().toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java
index 157cb4e..65e38ec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java
@@ -25,6 +25,8 @@ import org.gradle.api.logging.Logging;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.services.FileSystems;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.GUtil;
 
@@ -43,8 +45,6 @@ import java.util.regex.Pattern;
  *
  * A file or directory will only be visited if it matches all includes and no
  * excludes.
- *
- * @author Steve Appling
  */
 public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFileTree, RandomAccessFileCollection, LocalFileTree, DirectoryTree {
     private static final Logger LOGGER = Logging.getLogger(DirectoryFileTree.class);
@@ -52,6 +52,7 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
     private final File dir;
     private PatternSet patternSet;
     private boolean postfix;
+    private final FileSystem fileSystem = FileSystems.getDefault();
 
     public DirectoryFileTree(File dir) {
         this(dir, new PatternSet());
@@ -101,12 +102,12 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
         }
         RelativePath path = new RelativePath(true, file.getAbsolutePath().substring(prefix.length()).split(
                 Pattern.quote(File.separator)));
-        return patternSet.getAsSpec().isSatisfiedBy(new DefaultFileTreeElement(file, path));
+        return patternSet.getAsSpec().isSatisfiedBy(new DefaultFileTreeElement(file, path, fileSystem, fileSystem));
     }
 
     /**
      * Process the specified file or directory.  Note that the startFile parameter
-     * may be either a directory or a file.  If it is a directory, then it's contents
+     * may be either a directory or a file.  If it is a directory, then its contents
      * (but not the directory itself) will be checked with isAllowed and notified to
      * the listener.  If it is a file, the file will be checked and notified.
      */
@@ -130,7 +131,7 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
 
     private void processSingleFile(File file, FileVisitor visitor, Spec<FileTreeElement> spec, AtomicBoolean stopFlag) {
         RelativePath path = new RelativePath(true, file.getName());
-        FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag);
+        FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag, fileSystem, fileSystem);
         if (isAllowed(details, spec)) {
             visitor.visitFile(details);
         }
@@ -150,7 +151,7 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
             File child = children[i];
             boolean isFile = child.isFile();
             RelativePath childPath = path.append(isFile, child.getName());
-            FileVisitDetails details = new DefaultFileVisitDetails(child, childPath, stopFlag);
+            FileVisitDetails details = new DefaultFileVisitDetails(child, childPath, stopFlag, fileSystem, fileSystem);
             if (isAllowed(details, spec)) {
                 if (isFile) {
                     visitor.visitFile(details);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileCollection.java
new file mode 100644
index 0000000..cef9e92
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileCollection.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.collections;
+
+import org.gradle.api.file.FileCollection;
+
+/**
+ * A {@link DelegatingFileCollection} whose delegate is created lazily.
+ */
+public abstract class LazilyInitializedFileCollection extends DelegatingFileCollection {
+    private FileCollection delegate;
+
+    public abstract FileCollection createDelegate();
+
+    @Override
+    public final synchronized FileCollection getDelegate() {
+        if (delegate == null) {
+            delegate = createDelegate();
+        }
+        return delegate;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java
index ab9509c..c454b50 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java
@@ -19,8 +19,9 @@ import groovy.lang.Closure;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
 
 import java.io.File;
 import java.io.InputStream;
@@ -37,13 +38,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
     private final Map<RelativePath, Closure> elements = new LinkedHashMap<RelativePath, Closure>();
     private final Factory<File> tmpDirSource;
+    private final Chmod chmod;
 
-    public MapFileTree(final File tmpDir) {
-        this(new Factory<File>() { public File create() { return tmpDir; }});
+    public MapFileTree(final File tmpDir, Chmod chmod) {
+        this(new Factory<File>() { public File create() { return tmpDir; }}, chmod);
     }
 
-    public MapFileTree(Factory<File> tmpDirSource) {
+    public MapFileTree(Factory<File> tmpDirSource, Chmod chmod) {
         this.tmpDirSource = tmpDirSource;
+        this.chmod = chmod;
     }
 
     private File getTmpDir() {
@@ -95,12 +98,12 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             }
 
             visitDirs(path.getParent(), visitor);
-            visitor.visitDir(new FileVisitDetailsImpl(path, null, stopFlag));
+            visitor.visitDir(new FileVisitDetailsImpl(path, null, stopFlag, chmod));
         }
 
         public void visit(RelativePath path, Closure generator) {
             visitDirs(path.getParent(), visitor);
-            visitor.visitFile(new FileVisitDetailsImpl(path, generator, stopFlag));
+            visitor.visitFile(new FileVisitDetailsImpl(path, generator, stopFlag, chmod));
         }
     }
 
@@ -111,7 +114,8 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         private final AtomicBoolean stopFlag;
         private File file;
 
-        public FileVisitDetailsImpl(RelativePath path, Closure generator, AtomicBoolean stopFlag) {
+        public FileVisitDetailsImpl(RelativePath path, Closure generator, AtomicBoolean stopFlag, Chmod chmod) {
+            super(chmod);
             this.path = path;
             this.generator = generator;
             this.stopFlag = stopFlag;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java
index 04daa6e..24fbc0c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java
@@ -22,10 +22,12 @@ import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.DefaultFileVisitDetails;
 import org.gradle.api.internal.file.pattern.PatternStep;
-import org.gradle.api.internal.file.pattern.RegExpPatternStep;
+import org.gradle.api.internal.file.pattern.PatternStepFactory;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.services.FileSystems;
 
 import java.io.File;
 import java.util.Arrays;
@@ -44,6 +46,7 @@ public class SingleIncludePatternFileTree implements MinimalFileTree {
     private final String includePattern;
     private final List<String> patternSegments;
     private final Spec<FileTreeElement> excludeSpec;
+    private final FileSystem fileSystem = FileSystems.getDefault();
 
     public SingleIncludePatternFileTree(File baseDir, String includePattern) {
         this(baseDir, includePattern, Specs.<FileTreeElement>satisfyNone());
@@ -59,7 +62,6 @@ public class SingleIncludePatternFileTree implements MinimalFileTree {
         this.excludeSpec = excludeSpec;
     }
 
-    // TODO: important to visit files in prefix order? (contract says breadth-wise but probably means prefix.)
     public void visit(FileVisitor visitor) {
         doVisit(visitor, baseDir, new LinkedList<String>(), 0, new AtomicBoolean());
     }
@@ -78,7 +80,7 @@ public class SingleIncludePatternFileTree implements MinimalFileTree {
             DirectoryFileTree fileTree = new DirectoryFileTree(baseDir, patternSet);
             fileTree.visitFrom(visitor, file, new RelativePath(file.isFile(), relativePath.toArray(new String[relativePath.size()])));
         } else if (segment.contains("*") || segment.contains("?")) {
-            PatternStep step = new RegExpPatternStep(segment, false);
+            PatternStep step = PatternStepFactory.getStep(segment, false);
             File[] children = file.listFiles();
             if (children == null) {
                 if (!file.canRead()) {
@@ -89,7 +91,7 @@ public class SingleIncludePatternFileTree implements MinimalFileTree {
             }
             for (File child : children) {
                 if (stopFlag.get()) { break; }
-                if (step.matches(child.getName(), child.isFile())) {
+                if (step.matches(child.getName())) {
                     relativePath.addLast(child.getName());
                     doVisitDirOrFile(visitor, child, relativePath, segmentIndex + 1, stopFlag);
                     relativePath.removeLast();
@@ -106,14 +108,14 @@ public class SingleIncludePatternFileTree implements MinimalFileTree {
         if (file.isFile()) {
             if (segmentIndex == patternSegments.size()) {
                 RelativePath path = new RelativePath(true, relativePath.toArray(new String[relativePath.size()]));
-                FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag);
+                FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag, fileSystem, fileSystem);
                 if (!excludeSpec.isSatisfiedBy(details)) {
                     visitor.visitFile(details);
                 }
             }
         } else if (file.isDirectory()) {
             RelativePath path = new RelativePath(false, relativePath.toArray(new String[relativePath.size()]));
-            FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag);
+            FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag, fileSystem, fileSystem);
             if (!excludeSpec.isSatisfiedBy(details)) {
                 visitor.visitDir(details);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingletonFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingletonFileTree.java
index 348723c..35cfb2d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingletonFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingletonFileTree.java
@@ -15,18 +15,21 @@
  */
 package org.gradle.api.internal.file.collections;
 
-import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.DefaultFileTreeElement;
+import org.gradle.api.internal.file.DefaultFileVisitDetails;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.services.FileSystems;
 
 import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * A file tree with a single file entry.
  */
 public class SingletonFileTree implements MinimalFileTree {
     private final File file;
+    private final FileSystem fileSystem = FileSystems.getDefault();
 
     public SingletonFileTree(File file) {
         this.file = file;
@@ -37,16 +40,6 @@ public class SingletonFileTree implements MinimalFileTree {
     }
 
     public void visit(FileVisitor visitor) {
-        visitor.visitFile(new FileVisitDetailsImpl());
+        visitor.visitFile(new DefaultFileVisitDetails(file, new RelativePath(true, file.getName()), new AtomicBoolean(), fileSystem, fileSystem));
     }
-
-    private class FileVisitDetailsImpl extends DefaultFileTreeElement implements FileVisitDetails {
-        private FileVisitDetailsImpl() {
-            super(file, new RelativePath(true, file.getName()));
-        }
-
-        public void stopVisiting() {
-        }
-    }
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/AbstractZipCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/AbstractZipCompressor.java
deleted file mode 100644
index 4d1667a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/AbstractZipCompressor.java
+++ /dev/null
@@ -1,37 +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.file.copy;
-
-import org.apache.tools.zip.ZipOutputStream;
-import org.gradle.api.UncheckedIOException;
-
-import java.io.File;
-
-abstract class AbstractZipCompressor implements ZipCompressor {
-
-    abstract public int getCompressedMethod();
-
-    public ZipOutputStream createArchiveOutputStream(File destination) {
-        try {
-            ZipOutputStream outStream = new ZipOutputStream(destination);
-            outStream.setMethod(getCompressedMethod());
-            return outStream;
-        } catch (Exception e) {
-            String message = String.format("Unable to create ZIP output stream for file %s.", destination);
-            throw new UncheckedIOException(message, e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
deleted file mode 100644
index bccdf42..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import java.io.File;
-
-import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
-
-public interface ArchiveCopyAction extends CopyAction {
-    File getArchivePath();
-    ArchiveOutputStreamFactory getCompressor();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyAction.java
index 5c3a686..55bdcac 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyAction.java
@@ -1,25 +1,24 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.file.CopySpec;
-import org.gradle.api.tasks.WorkResult;
-
-/**
- * @author Steve Appling
- */
-public interface CopyAction extends CopySpec, WorkResult {
-}
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.tasks.WorkResult;
+
+public interface CopyAction {
+
+    public WorkResult execute(CopyActionProcessingStream stream);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionExecuter.java
new file mode 100644
index 0000000..886b73d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionExecuter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
+
+public class CopyActionExecuter {
+
+    private final Instantiator instantiator;
+    private final FileSystem fileSystem;
+
+    public CopyActionExecuter(Instantiator instantiator, FileSystem fileSystem) {
+        this.instantiator = instantiator;
+        this.fileSystem = fileSystem;
+    }
+
+    public WorkResult execute(final CopySpecInternal spec, CopyAction action) {
+        final CopyAction effectiveVisitor = new DuplicateHandlingCopyActionDecorator(
+                new NormalizingCopyActionDecorator(action, fileSystem)
+        );
+
+        CopyActionProcessingStream processingStream = new CopySpecBackedCopyActionProcessingStream(spec, instantiator, fileSystem);
+        return effectiveVisitor.execute(processingStream);
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
index 36bf5ce..b108479 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,233 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.file.copy;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.file.*;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.specs.Spec;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 
-import java.io.FilterReader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
+package org.gradle.api.internal.file.copy;
 
 /**
- * @author Steve Appling
+ * DO NOT REMOVE.
+ *
+ * Prior to 1.8, Copy leaked this as a parameter type on one of its methods.
+ * Has to exist to maintain binary compatibility.
  */
-public class CopyActionImpl implements CopyAction, CopySpecSource {
-    private final CopySpecVisitor visitor;
-    private final CopySpecImpl root;
-    private final CopySpecImpl mainContent;
-    private final FileResolver resolver;
-
-    public CopyActionImpl(FileResolver resolver, CopySpecVisitor visitor) {
-        this.resolver = resolver;
-        root = new CopySpecImpl(resolver);
-        mainContent = root.addChild();
-        this.visitor = new MappingCopySpecVisitor(new NormalizingCopySpecVisitor(visitor), FileSystems.getDefault());
-    }
-
-    public FileResolver getResolver() {
-        return resolver;
-    }
-
-    public CopySpecImpl getRootSpec() {
-        return root;
-    }
-
-    public CopySpecImpl getMainSpec() {
-        return mainContent;
-    }
-
-    public void execute() {
-        visitor.startVisit(this);
-        for (ReadableCopySpec spec : root.getAllSpecs()) {
-            visitor.visitSpec(spec);
-            spec.getSource().visit(visitor);
-        }
-        visitor.endVisit();
-    }
-
-    public boolean getDidWork() {
-        return visitor.getDidWork();
-    }
-
-    public FileTree getAllSource() {
-        List<FileTree> sources = new ArrayList<FileTree>();
-        for (ReadableCopySpec spec : root.getAllSpecs()) {
-            FileTree source = spec.getSource();
-            sources.add(source);
-        }
-        return resolver.resolveFilesAsTree(sources);
-    }
-
-    public boolean hasSource() {
-        return root.hasSource();
-    }
-
-    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
-        mainContent.eachFile(action);
-        return this;
-    }
-
-    public CopySpec eachFile(Closure closure) {
-        mainContent.eachFile(closure);
-        return this;
-    }
-
-    public CopySpec exclude(Iterable<String> excludes) {
-        mainContent.exclude(excludes);
-        return this;
-    }
-
-    public CopySpec exclude(String... excludes) {
-        mainContent.exclude(excludes);
-        return this;
-    }
-
-    public CopySpec exclude(Closure excludeSpec) {
-        mainContent.exclude(excludeSpec);
-        return this;
-    }
-
-    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
-        mainContent.exclude(excludeSpec);
-        return this;
-    }
-
-    public CopySpec expand(Map<String, ?> properties) {
-        mainContent.expand(properties);
-        return this;
-    }
-
-    public CopySpec filter(Closure closure) {
-        mainContent.filter(closure);
-        return this;
-    }
-
-    public CopySpec filter(Class<? extends FilterReader> filterType) {
-        mainContent.filter(filterType);
-        return this;
-    }
-
-    public CopySpec filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
-        mainContent.filter(properties, filterType);
-        return this;
-    }
-
-    public CopySpec from(Object sourcePath, Closure c) {
-        return mainContent.from(sourcePath, c);
-    }
-
-    public CopySpec from(Object... sourcePaths) {
-        mainContent.from(sourcePaths);
-        return this;
-    }
-
-    public Set<String> getExcludes() {
-        return mainContent.getExcludes();
-    }
-
-    public Set<String> getIncludes() {
-        return mainContent.getIncludes();
-    }
-
-    public CopySpec include(Iterable<String> includes) {
-        mainContent.include(includes);
-        return this;
-    }
-
-    public CopySpec include(String... includes) {
-        mainContent.include(includes);
-        return this;
-    }
-
-    public CopySpec include(Closure includeSpec) {
-        mainContent.include(includeSpec);
-        return this;
-    }
-
-    public CopySpec include(Spec<FileTreeElement> includeSpec) {
-        mainContent.include(includeSpec);
-        return this;
-    }
-
-    public CopySpec into(Object destDir) {
-        mainContent.into(destDir);
-        return this;
-    }
-
-    public CopySpec into(Object destPath, Closure configureClosure) {
-        return mainContent.into(destPath, configureClosure);
-    }
-
-    public boolean isCaseSensitive() {
-        return mainContent.isCaseSensitive();
-    }
-
-    public boolean getIncludeEmptyDirs() {
-        return mainContent.getIncludeEmptyDirs();
-    }
-
-    public void setIncludeEmptyDirs(boolean includeEmptyDirs) {
-        mainContent.setIncludeEmptyDirs(includeEmptyDirs);
-    }
-
-    public CopySpec rename(Closure closure) {
-        mainContent.rename(closure);
-        return this;
-    }
-
-    public CopySpec rename(Pattern sourceRegEx, String replaceWith) {
-        mainContent.rename(sourceRegEx, replaceWith);
-        return this;
-    }
-
-    public CopySpec rename(String sourceRegEx, String replaceWith) {
-        mainContent.rename(sourceRegEx, replaceWith);
-        return this;
-    }
-
-    public void setCaseSensitive(boolean caseSensitive) {
-        mainContent.setCaseSensitive(caseSensitive);
-    }
-
-    public Integer getDirMode() {
-        return mainContent.getDirMode();
-    }
-
-    public CopyProcessingSpec setDirMode(Integer mode) {
-        mainContent.setDirMode(mode);
-        return this;
-    }
-
-    public CopySpec setExcludes(Iterable<String> excludes) {
-        mainContent.setExcludes(excludes);
-        return this;
-    }
-
-    public Integer getFileMode() {
-        return mainContent.getFileMode();
-    }
-
-    public CopyProcessingSpec setFileMode(Integer mode) {
-        mainContent.setFileMode(mode);
-        return this;
-    }
-
-    public CopySpec setIncludes(Iterable<String> includes) {
-        mainContent.setIncludes(includes);
-        return this;
-    }
-
-    public CopySpec with(CopySpec... copySpecs) {
-        mainContent.with(copySpecs);
-        return this;
-    }
+ at SuppressWarnings("UnusedDeclaration")
+ at Deprecated
+public interface CopyActionImpl {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionProcessingStream.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionProcessingStream.java
new file mode 100644
index 0000000..f94eab7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionProcessingStream.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+
+public interface CopyActionProcessingStream {
+
+    void process(CopyActionProcessingStreamAction action);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyFileVisitorImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyFileVisitorImpl.java
new file mode 100644
index 0000000..6b22189
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyFileVisitorImpl.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
+
+public class CopyFileVisitorImpl implements FileVisitor {
+    private final CopySpecInternal spec;
+    private final CopyActionProcessingStreamAction action;
+    private final Instantiator instantiator;
+    private final FileSystem fileSystem;
+
+    public CopyFileVisitorImpl(CopySpecInternal spec, CopyActionProcessingStreamAction action, Instantiator instantiator, FileSystem fileSystem) {
+        this.spec = spec;
+        this.action = action;
+        this.instantiator = instantiator;
+        this.fileSystem = fileSystem;
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+        processDir(dirDetails);
+    }
+
+    public void visitFile(FileVisitDetails fileDetails) {
+        processFile(fileDetails);
+    }
+
+    private void processDir(FileVisitDetails visitDetails) {
+        DefaultFileCopyDetails details = createDefaultFileCopyDetails(visitDetails);
+        action.processFile(details);
+    }
+
+    private void processFile(FileVisitDetails visitDetails) {
+        DefaultFileCopyDetails details = createDefaultFileCopyDetails(visitDetails);
+        for (Action<? super FileCopyDetails> action : spec.getAllCopyActions()) {
+            action.execute(details);
+            if (details.isExcluded()) {
+                return;
+            }
+        }
+        action.processFile(details);
+    }
+
+    private DefaultFileCopyDetails createDefaultFileCopyDetails(FileVisitDetails visitDetails) {
+        return instantiator.newInstance(DefaultFileCopyDetails.class, visitDetails, spec, fileSystem);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecActionImpl.java
new file mode 100644
index 0000000..b4dbf05
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecActionImpl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
+
+public class CopySpecActionImpl implements Action<CopySpecInternal> {
+    private final CopyActionProcessingStreamAction action;
+    private final Instantiator instantiator;
+    private final FileSystem fileSystem;
+
+    public CopySpecActionImpl(CopyActionProcessingStreamAction action, Instantiator instantiator, FileSystem fileSystem) {
+        this.action = action;
+        this.instantiator = instantiator;
+        this.fileSystem = fileSystem;
+    }
+
+    public void execute(final CopySpecInternal spec) {
+        FileTree source = spec.getSource();
+        source.visit(new CopyFileVisitorImpl(spec, action, instantiator, fileSystem));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecBackedCopyActionProcessingStream.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecBackedCopyActionProcessingStream.java
new file mode 100644
index 0000000..c9657ea
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecBackedCopyActionProcessingStream.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
+
+public class CopySpecBackedCopyActionProcessingStream implements CopyActionProcessingStream {
+
+    private final CopySpecInternal spec;
+    private final Instantiator instantiator;
+    private final FileSystem fileSystem;
+
+    public CopySpecBackedCopyActionProcessingStream(CopySpecInternal spec, Instantiator instantiator, FileSystem fileSystem) {
+        this.spec = spec;
+        this.instantiator = instantiator;
+        this.fileSystem = fileSystem;
+    }
+
+    public void process(final CopyActionProcessingStreamAction action) {
+        spec.walk(new CopySpecActionImpl(action, instantiator, fileSystem));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
deleted file mode 100644
index 6d0d428..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.file.*;
-import org.gradle.api.internal.ChainingTransformer;
-import org.gradle.api.internal.ClosureBackedAction;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.util.PatternSet;
-import org.gradle.util.ConfigureUtil;
-
-import java.io.File;
-import java.io.FilterReader;
-import java.util.*;
-import java.util.regex.Pattern;
-
-/**
- * @author Steve Appling
- */
-public class CopySpecImpl implements CopySpec, ReadableCopySpec {
-    private final FileResolver resolver;
-    private final Set<Object> sourcePaths;
-    private Object destDir;
-    private final PatternSet patternSet;
-    private final List<ReadableCopySpec> childSpecs;
-    private final CopySpecImpl parentSpec;
-    private final List<Action<? super FileCopyDetails>> actions = new ArrayList<Action<? super FileCopyDetails>>();
-    private Integer dirMode;
-    private Integer fileMode;
-    private Boolean caseSensitive;
-    private Boolean includeEmptyDirs;
-    private PathNotationParser<String> pathNotationParser;
-
-    private CopySpecImpl(FileResolver resolver, CopySpecImpl parentSpec) {
-        this.parentSpec = parentSpec;
-        this.resolver = resolver;
-        this.pathNotationParser = new PathNotationParser<String>();
-        sourcePaths = new LinkedHashSet<Object>();
-        childSpecs = new ArrayList<ReadableCopySpec>();
-        patternSet = new PatternSet();
-    }
-
-    public CopySpecImpl(FileResolver resolver) {
-        this(resolver, null);
-    }
-
-    protected FileResolver getResolver() {
-        return resolver;
-    }
-
-    public CopySpec with(CopySpec... copySpecs) {
-        for (CopySpec copySpec : copySpecs) {
-            ReadableCopySpec readableCopySpec;
-            if (copySpec instanceof CopySpecSource) {
-                CopySpecSource copySpecSource = (CopySpecSource) copySpec;
-                readableCopySpec = copySpecSource.getRootSpec();
-            } else {
-                readableCopySpec = (ReadableCopySpec) copySpec;
-            }
-            childSpecs.add(new WrapperCopySpec(this, readableCopySpec));
-        }
-        return this;
-    }
-
-    public CopySpec from(Object... sourcePaths) {
-        for (Object sourcePath : sourcePaths) {
-            this.sourcePaths.add(sourcePath);
-        }
-        return this;
-    }
-
-    public CopySpec from(Object sourcePath, Closure c) {
-        if (c == null) {
-            from(sourcePath);
-            return this;
-        } else {
-            CopySpecImpl child = addChild();
-            child.from(sourcePath);
-            ConfigureUtil.configure(c, child);
-            return child;
-        }
-    }
-
-    public CopySpecImpl addFirst() {
-        CopySpecImpl child = new CopySpecImpl(resolver, this);
-        childSpecs.add(0, child);
-        return child;
-    }
-
-    public CopySpecImpl addChild() {
-        CopySpecImpl child = new CopySpecImpl(resolver, this);
-        childSpecs.add(child);
-        return child;
-    }
-
-    public Set<Object> getSourcePaths() {
-        return sourcePaths;
-    }
-
-    public FileTree getSource() {
-        return resolver.resolveFilesAsTree(sourcePaths).matching(getPatternSet());
-    }
-
-    public List<ReadableCopySpec> getAllSpecs() {
-        List<ReadableCopySpec> result = new ArrayList<ReadableCopySpec>();
-        result.add(this);
-        for (ReadableCopySpec childSpec : childSpecs) {
-            result.addAll(childSpec.getAllSpecs());
-        }
-        return result;
-    }
-
-    public CopySpecImpl into(Object destDir) {
-        this.destDir = destDir;
-        return this;
-    }
-
-    public CopySpecImpl into(Object destPath, Closure configureClosure) {
-        if (configureClosure == null) {
-            into(destPath);
-            return this;
-        } else {
-            CopySpecImpl child = addChild();
-            child.into(destPath);
-            ConfigureUtil.configure(configureClosure, child);
-            return child;
-        }
-    }
-
-    public RelativePath getDestPath() {
-        RelativePath parentPath;
-        if (parentSpec == null) {
-            parentPath = new RelativePath(false);
-        } else {
-            parentPath = parentSpec.getDestPath();
-        }
-        if (destDir == null) {
-            return parentPath;
-        }
-
-        String path = resolveToPath(destDir);
-        if (path.startsWith("/") || path.startsWith(File.separator)) {
-            return RelativePath.parse(false, path);
-        }
-
-        return RelativePath.parse(false, parentPath, path);
-    }
-
-    private String resolveToPath(Object destDir) {
-        return pathNotationParser.parseNotation(destDir);
-    }
-
-    public PatternSet getPatternSet() {
-        PatternSet patterns = new PatternSet();
-        patterns.setCaseSensitive(isCaseSensitive());
-        patterns.include(getAllIncludes());
-        patterns.includeSpecs(getAllIncludeSpecs());
-        patterns.exclude(getAllExcludes());
-        patterns.excludeSpecs(getAllExcludeSpecs());
-        return patterns;
-    }
-
-    public boolean isCaseSensitive() {
-        if (caseSensitive != null) {
-            return caseSensitive;
-        }
-        if (parentSpec != null) {
-            return parentSpec.isCaseSensitive();
-        }
-        return true;
-    }
-
-    public void setCaseSensitive(boolean caseSensitive) {
-        this.caseSensitive = caseSensitive;
-    }
-
-    public boolean getIncludeEmptyDirs() {
-        if (includeEmptyDirs != null) {
-            return includeEmptyDirs;
-        }
-        if (parentSpec != null) {
-            return parentSpec.getIncludeEmptyDirs();
-        }
-        return true;
-    }
-
-    public void setIncludeEmptyDirs(boolean includeEmptyDirs) {
-        this.includeEmptyDirs = includeEmptyDirs;
-    }
-
-    public CopySpec include(String... includes) {
-        patternSet.include(includes);
-        return this;
-    }
-
-    public CopySpec include(Iterable<String> includes) {
-        patternSet.include(includes);
-        return this;
-    }
-
-    public CopySpec include(Spec<FileTreeElement> includeSpec) {
-        patternSet.include(includeSpec);
-        return this;
-    }
-
-    public CopySpec include(Closure includeSpec) {
-        patternSet.include(includeSpec);
-        return this;
-    }
-
-    public Set<String> getIncludes() {
-        return patternSet.getIncludes();
-    }
-
-    public CopySpec setIncludes(Iterable<String> includes) {
-        patternSet.setIncludes(includes);
-        return this;
-    }
-
-    public List<String> getAllIncludes() {
-        List<String> result = new ArrayList<String>();
-        if (parentSpec != null) {
-            result.addAll(parentSpec.getAllIncludes());
-        }
-        result.addAll(getIncludes());
-        return result;
-    }
-
-    public List<Spec<FileTreeElement>> getAllIncludeSpecs() {
-        List<Spec<FileTreeElement>> result = new ArrayList<Spec<FileTreeElement>>();
-        if (parentSpec != null) {
-            result.addAll(parentSpec.getAllIncludeSpecs());
-        }
-        result.addAll(patternSet.getIncludeSpecs());
-        return result;
-    }
-
-    public CopySpec exclude(String... excludes) {
-        patternSet.exclude(excludes);
-        return this;
-    }
-
-    public CopySpec exclude(Iterable<String> excludes) {
-        patternSet.exclude(excludes);
-        return this;
-    }
-
-    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
-        patternSet.exclude(excludeSpec);
-        return this;
-    }
-
-    public CopySpec exclude(Closure excludeSpec) {
-        patternSet.exclude(excludeSpec);
-        return this;
-    }
-
-    public Set<String> getExcludes() {
-        return patternSet.getExcludes();
-    }
-
-    public CopySpecImpl setExcludes(Iterable<String> excludes) {
-        patternSet.setExcludes(excludes);
-        return this;
-    }
-
-    public CopySpec rename(String sourceRegEx, String replaceWith) {
-        actions.add(new RenamingCopyAction(new RegExpNameMapper(sourceRegEx, replaceWith)));
-        return this;
-    }
-
-    public CopySpec rename(Pattern sourceRegEx, String replaceWith) {
-        actions.add(new RenamingCopyAction(new RegExpNameMapper(sourceRegEx, replaceWith)));
-        return this;
-    }
-
-    public CopySpec filter(final Class<? extends FilterReader> filterType) {
-        actions.add(new Action<FileCopyDetails>() {
-            public void execute(FileCopyDetails fileCopyDetails) {
-                fileCopyDetails.filter(filterType);
-            }
-        });
-        return this;
-    }
-
-    public CopySpec filter(final Closure closure) {
-        actions.add(new Action<FileCopyDetails>() {
-            public void execute(FileCopyDetails fileCopyDetails) {
-                fileCopyDetails.filter(closure);
-            }
-        });
-        return this;
-    }
-
-    public CopySpec filter(final Map<String, ?> properties, final Class<? extends FilterReader> filterType) {
-        actions.add(new Action<FileCopyDetails>() {
-            public void execute(FileCopyDetails fileCopyDetails) {
-                fileCopyDetails.filter(properties, filterType);
-            }
-        });
-        return this;
-    }
-
-    public CopySpec expand(final Map<String, ?> properties) {
-        actions.add(new Action<FileCopyDetails>() {
-            public void execute(FileCopyDetails fileCopyDetails) {
-                fileCopyDetails.expand(properties);
-            }
-        });
-        return this;
-    }
-
-    public CopySpec rename(Closure closure) {
-        ChainingTransformer<String> transformer = new ChainingTransformer<String>(String.class);
-        transformer.add(closure);
-        actions.add(new RenamingCopyAction(transformer));
-        return this;
-    }
-
-    public Integer getDirMode() {
-        if (dirMode != null) {
-            return dirMode;
-        }
-        if (parentSpec != null) {
-            return parentSpec.getDirMode();
-        }
-        return null;
-    }
-
-    public Integer getFileMode() {
-        if (fileMode != null) {
-            return fileMode;
-        }
-        if (parentSpec != null) {
-            return parentSpec.getFileMode();
-        }
-        return null;
-    }
-
-    public CopyProcessingSpec setDirMode(Integer mode) {
-        dirMode = mode;
-        return this;
-    }
-
-    public CopyProcessingSpec setFileMode(Integer mode) {
-        fileMode = mode;
-        return this;
-    }
-
-    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
-        actions.add(action);
-        return this;
-    }
-
-    public CopySpec eachFile(Closure closure) {
-        actions.add(new ClosureBackedAction<FileCopyDetails>(closure));
-        return this;
-    }
-
-    public List<String> getAllExcludes() {
-        List<String> result = new ArrayList<String>();
-        if (parentSpec != null) {
-            result.addAll(parentSpec.getAllExcludes());
-        }
-        result.addAll(getExcludes());
-        return result;
-    }
-
-    public List<Spec<FileTreeElement>> getAllExcludeSpecs() {
-        List<Spec<FileTreeElement>> result = new ArrayList<Spec<FileTreeElement>>();
-        if (parentSpec != null) {
-            result.addAll(parentSpec.getAllExcludeSpecs());
-        }
-        result.addAll(patternSet.getExcludeSpecs());
-        return result;
-    }
-
-    public List<Action<? super FileCopyDetails>> getAllCopyActions() {
-        if (parentSpec == null) {
-            return actions;
-        }
-        List<Action<? super FileCopyDetails>> allActions = new ArrayList<Action<? super FileCopyDetails>>();
-        allActions.addAll(parentSpec.getAllCopyActions());
-        allActions.addAll(actions);
-        return allActions;
-    }
-
-    public boolean hasSource() {
-        if (!sourcePaths.isEmpty()) {
-            return true;
-        }
-        for (ReadableCopySpec spec : childSpecs) {
-            if (spec.hasSource()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static class WrapperCopySpec implements ReadableCopySpec {
-        private final ReadableCopySpec root;
-        private final ReadableCopySpec spec;
-
-        public WrapperCopySpec(ReadableCopySpec root, ReadableCopySpec spec) {
-            this.root = root;
-            this.spec = spec;
-        }
-
-        public RelativePath getDestPath() {
-            return root.getDestPath().append(spec.getDestPath());
-        }
-
-        public Integer getFileMode() {
-            return spec.getFileMode();
-        }
-
-        public Integer getDirMode() {
-            return spec.getDirMode();
-        }
-
-        public FileTree getSource() {
-            return spec.getSource();
-        }
-
-        public Collection<? extends ReadableCopySpec> getAllSpecs() {
-            List<WrapperCopySpec> specs = new ArrayList<WrapperCopySpec>();
-            for (ReadableCopySpec child : spec.getAllSpecs()) {
-                specs.add(new WrapperCopySpec(root, child));
-            }
-            return specs;
-        }
-
-        public boolean hasSource() {
-            return spec.hasSource();
-        }
-
-        public Collection<? extends Action<? super FileCopyDetails>> getAllCopyActions() {
-            return spec.getAllCopyActions();
-        }
-
-        public boolean getIncludeEmptyDirs() {
-            return spec.getIncludeEmptyDirs();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecInternal.java
new file mode 100644
index 0000000..0d82f79
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecInternal.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.RelativePath;
+
+import java.util.Collection;
+
+public interface CopySpecInternal extends CopySpec {
+
+    RelativePath getDestPath();
+
+    FileTree getSource();
+
+    FileTree getAllSource();
+
+    boolean hasSource();
+
+    Collection<? extends Action<? super FileCopyDetails>> getAllCopyActions();
+
+    Iterable<CopySpecInternal> getChildren();
+
+    void walk(Action<? super CopySpecInternal> action);
+
+    CopySpecInternal addChild();
+
+    CopySpecInternal addChildBeforeSpec(CopySpecInternal childSpec);
+
+    CopySpecInternal addFirst();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java
index 38f27a2..381b9a9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java
@@ -16,5 +16,5 @@
 package org.gradle.api.internal.file.copy;
 
 public interface CopySpecSource {
-    ReadableCopySpec getRootSpec();
+    CopySpecInternal getRootSpec();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecVisitor.java
deleted file mode 100644
index 46f2cf9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecVisitor.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.api.internal.file.copy;
-
-import org.gradle.api.file.FileVisitor;
-import org.gradle.api.tasks.WorkResult;
-
-public interface CopySpecVisitor extends FileVisitor, WorkResult {
-    /**
-     * Called at the start of the visit.
-     */
-    void startVisit(CopyAction action);
-
-    /**
-     * Called at the end of the visit.
-     */
-    void endVisit();
-
-    /**
-     * Visits a spec. Called before any of the files or directories of the spec are visited.
-     */
-    void visitSpec(ReadableCopySpec spec);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecWrapper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecWrapper.java
new file mode 100644
index 0000000..1035821
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecWrapper.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.NonExtensible;
+import org.gradle.api.Nullable;
+import org.gradle.api.file.*;
+import org.gradle.api.specs.Spec;
+
+import java.io.FilterReader;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Wraps another CopySpec impl, only exposing the CopySpec API.
+ *
+ * Prevents users from accessing "internal" methods on implementations.
+ */
+ at NonExtensible
+public class CopySpecWrapper implements CopySpec {
+
+    private final CopySpec delegate;
+
+    public CopySpecWrapper(CopySpec delegate) {
+        this.delegate = delegate;
+    }
+
+    public boolean isCaseSensitive() {
+        return delegate.isCaseSensitive();
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        delegate.setCaseSensitive(caseSensitive);
+    }
+
+    public boolean getIncludeEmptyDirs() {
+        return delegate.getIncludeEmptyDirs();
+    }
+
+    public void setIncludeEmptyDirs(boolean includeEmptyDirs) {
+        delegate.setIncludeEmptyDirs(includeEmptyDirs);
+    }
+
+    public DuplicatesStrategy getDuplicatesStrategy() {
+        return delegate.getDuplicatesStrategy();
+    }
+
+    public void setDuplicatesStrategy(@Nullable DuplicatesStrategy strategy) {
+        delegate.setDuplicatesStrategy(strategy);
+    }
+
+    public CopySpec filesMatching(String pattern, Action<? super FileCopyDetails> action) {
+        delegate.filesMatching(pattern, action);
+        return this;
+    }
+
+    public CopySpec filesNotMatching(String pattern, Action<? super FileCopyDetails> action) {
+        delegate.filesNotMatching(pattern, action);
+        return this;
+    }
+
+    public CopySpec with(CopySpec... sourceSpecs) {
+        delegate.with(sourceSpecs);
+        return this;
+    }
+
+    public CopySpec from(Object... sourcePaths) {
+        delegate.from(sourcePaths);
+        return this;
+    }
+
+    public CopySpec from(Object sourcePath, Closure c) {
+        return delegate.from(sourcePath, c);
+    }
+
+    public CopySpec setIncludes(Iterable<String> includes) {
+        delegate.setIncludes(includes);
+        return this;
+    }
+
+    public CopySpec setExcludes(Iterable<String> excludes) {
+        delegate.setExcludes(excludes);
+        return this;
+    }
+
+    public CopySpec include(String... includes) {
+        delegate.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Iterable<String> includes) {
+        delegate.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Spec<FileTreeElement> includeSpec) {
+        delegate.include(includeSpec);
+        return this;
+    }
+
+    public CopySpec include(Closure includeSpec) {
+        delegate.include(includeSpec);
+        return this;
+    }
+
+    public CopySpec exclude(String... excludes) {
+        delegate.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Iterable<String> excludes) {
+        delegate.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
+        delegate.exclude(excludeSpec);
+        return this;
+    }
+
+    public CopySpec exclude(Closure excludeSpec) {
+        delegate.exclude(excludeSpec);
+        return this;
+    }
+
+    public CopySpec into(Object destPath) {
+        delegate.into(destPath);
+        return this;
+    }
+
+    public CopySpec into(Object destPath, Closure configureClosure) {
+        return delegate.into(destPath, configureClosure);
+    }
+
+    public CopySpec rename(Closure closure) {
+        delegate.rename(closure);
+        return this;
+    }
+
+    public CopySpec rename(String sourceRegEx, String replaceWith) {
+        delegate.rename(sourceRegEx, replaceWith);
+        return this;
+    }
+
+    public CopyProcessingSpec rename(Pattern sourceRegEx, String replaceWith) {
+        delegate.rename(sourceRegEx, replaceWith);
+        return this;
+    }
+
+    public CopySpec filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+        delegate.filter(properties, filterType);
+        return this;
+    }
+
+    public CopySpec filter(Class<? extends FilterReader> filterType) {
+        delegate.filter(filterType);
+        return this;
+    }
+
+    public CopySpec filter(Closure closure) {
+        delegate.filter(closure);
+        return this;
+    }
+
+    public CopySpec expand(Map<String, ?> properties) {
+        delegate.expand(properties);
+        return this;
+    }
+
+    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
+        delegate.eachFile(action);
+        return this;
+    }
+
+    public CopySpec eachFile(Closure closure) {
+        delegate.eachFile(closure);
+        return this;
+    }
+
+    public Integer getFileMode() {
+        return delegate.getFileMode();
+    }
+
+    public CopyProcessingSpec setFileMode(Integer mode) {
+        delegate.setFileMode(mode);
+        return this;
+    }
+
+    public Integer getDirMode() {
+        return delegate.getDirMode();
+    }
+
+    public CopyProcessingSpec setDirMode(Integer mode) {
+        delegate.setDirMode(mode);
+        return this;
+    }
+
+    public Set<String> getIncludes() {
+        return delegate.getIncludes();
+    }
+
+    public Set<String> getExcludes() {
+        return delegate.getExcludes();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultCopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultCopySpec.java
new file mode 100644
index 0000000..5b74c08
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultCopySpec.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import com.google.common.collect.ImmutableList;
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.NonExtensible;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.ChainingTransformer;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.pattern.PatternMatcherFactory;
+import org.gradle.api.specs.NotSpec;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+import java.io.FilterReader;
+import java.util.*;
+import java.util.regex.Pattern;
+
+ at NonExtensible
+public class DefaultCopySpec implements CopySpecInternal {
+    private final FileResolver resolver;
+    private final Set<Object> sourcePaths;
+    private Object destDir;
+    private final PatternSet patternSet;
+    private final List<CopySpecInternal> childSpecs;
+    private final Instantiator instantiator;
+    private final DefaultCopySpec parentSpec;
+    private final List<Action<? super FileCopyDetails>> actions = new ArrayList<Action<? super FileCopyDetails>>();
+    private Integer dirMode;
+    private Integer fileMode;
+    private Boolean caseSensitive;
+    private Boolean includeEmptyDirs;
+    private PathNotationParser<String> pathNotationParser;
+    private DuplicatesStrategy duplicatesStrategy;
+
+    public DefaultCopySpec(FileResolver resolver, Instantiator instantiator, DefaultCopySpec parentSpec) {
+        this.parentSpec = parentSpec;
+        this.resolver = resolver;
+        this.instantiator = instantiator;
+        this.pathNotationParser = new PathNotationParser<String>();
+        sourcePaths = new LinkedHashSet<Object>();
+        childSpecs = new ArrayList<CopySpecInternal>();
+        patternSet = new PatternSet();
+        duplicatesStrategy = null; //inherit from parent
+    }
+
+    public DefaultCopySpec(FileResolver resolver, Instantiator instantiator) {
+        this(resolver, instantiator, null);
+    }
+
+    protected FileResolver getResolver() {
+        return resolver;
+    }
+
+    public CopySpec with(CopySpec... copySpecs) {
+        for (CopySpec copySpec : copySpecs) {
+            CopySpecInternal copySpecInternal;
+            if (copySpec instanceof CopySpecSource) {
+                CopySpecSource copySpecSource = (CopySpecSource) copySpec;
+                copySpecInternal = copySpecSource.getRootSpec();
+            } else {
+                copySpecInternal = (CopySpecInternal) copySpec;
+            }
+            childSpecs.add(new RelativizedCopySpec(this, copySpecInternal));
+        }
+        return this;
+    }
+
+    public CopySpec from(Object... sourcePaths) {
+        for (Object sourcePath : sourcePaths) {
+            this.sourcePaths.add(sourcePath);
+        }
+        return this;
+    }
+
+    public CopySpec from(Object sourcePath, Closure c) {
+        if (c == null) {
+            from(sourcePath);
+            return this;
+        } else {
+            CopySpecInternal child = addChild();
+            child.from(sourcePath);
+            return ConfigureUtil.configure(c, instantiator.newInstance(CopySpecWrapper.class, child));
+        }
+    }
+
+    public CopySpecInternal addFirst() {
+        return addChildAtPosition(0);
+    }
+
+    private CopySpecInternal addChildAtPosition(int position) {
+        DefaultCopySpec child = instantiator.newInstance(DefaultCopySpec.class, resolver, instantiator, this);
+        childSpecs.add(position, child);
+        return child;
+    }
+
+    public CopySpecInternal addChild() {
+        DefaultCopySpec child = new DefaultCopySpec(resolver, instantiator, this);
+        childSpecs.add(child);
+        return child;
+    }
+
+    public CopySpecInternal addChildBeforeSpec(CopySpecInternal childSpec) {
+        int position = childSpecs.indexOf(childSpec);
+        return position != -1 ? addChildAtPosition(position) : addChild();
+    }
+
+    public Set<Object> getSourcePaths() {
+        return sourcePaths;
+    }
+
+    public FileTree getSource() {
+        return resolver.resolveFilesAsTree(sourcePaths).matching(getPatternSet());
+    }
+
+    public FileTree getAllSource() {
+        final ImmutableList.Builder<FileTree> builder = ImmutableList.builder();
+        walk(new Action<CopySpecInternal>() {
+            public void execute(CopySpecInternal copySpecInternal) {
+                builder.add(copySpecInternal.getSource());
+            }
+        });
+
+        return resolver.compositeFileTree(builder.build());
+    }
+
+    public DefaultCopySpec into(Object destDir) {
+        this.destDir = destDir;
+        return this;
+    }
+
+    public CopySpec into(Object destPath, Closure configureClosure) {
+        if (configureClosure == null) {
+            into(destPath);
+            return this;
+        } else {
+            CopySpecInternal child = addChild();
+            child.into(destPath);
+            return ConfigureUtil.configure(configureClosure, instantiator.newInstance(CopySpecWrapper.class, child));
+        }
+    }
+
+    public RelativePath getDestPath() {
+        RelativePath parentPath;
+        if (parentSpec == null) {
+            parentPath = new RelativePath(false);
+        } else {
+            parentPath = parentSpec.getDestPath();
+        }
+        if (destDir == null) {
+            return parentPath;
+        }
+
+        String path = resolveToPath(destDir);
+        if (path.startsWith("/") || path.startsWith(File.separator)) {
+            return RelativePath.parse(false, path);
+        }
+
+        return RelativePath.parse(false, parentPath, path);
+    }
+
+    private String resolveToPath(Object destDir) {
+        return pathNotationParser.parseNotation(destDir);
+    }
+
+    public PatternSet getPatternSet() {
+        PatternSet patterns = new PatternSet();
+        patterns.setCaseSensitive(isCaseSensitive());
+        patterns.include(getAllIncludes());
+        patterns.includeSpecs(getAllIncludeSpecs());
+        patterns.exclude(getAllExcludes());
+        patterns.excludeSpecs(getAllExcludeSpecs());
+        return patterns;
+    }
+
+    public boolean isCaseSensitive() {
+        if (caseSensitive != null) {
+            return caseSensitive;
+        }
+        if (parentSpec != null) {
+            return parentSpec.isCaseSensitive();
+        }
+        return true;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+    }
+
+    public boolean getIncludeEmptyDirs() {
+        if (includeEmptyDirs != null) {
+            return includeEmptyDirs;
+        }
+        if (parentSpec != null) {
+            return parentSpec.getIncludeEmptyDirs();
+        }
+        return true;
+    }
+
+    public void setIncludeEmptyDirs(boolean includeEmptyDirs) {
+        this.includeEmptyDirs = includeEmptyDirs;
+    }
+
+    public DuplicatesStrategy getDuplicatesStrategy() {
+        if (duplicatesStrategy != null) {
+            return duplicatesStrategy;
+        }
+        if (parentSpec != null) {
+            return parentSpec.getDuplicatesStrategy();
+        }
+        return DuplicatesStrategy.INCLUDE;
+    }
+
+    public void setDuplicatesStrategy(DuplicatesStrategy strategy) {
+        this.duplicatesStrategy = strategy;
+    }
+
+    public CopySpec filesMatching(String pattern, Action<? super FileCopyDetails> action) {
+        Spec<RelativePath> matcher = PatternMatcherFactory.getPatternMatcher(true, isCaseSensitive(), pattern);
+        return eachFile(
+                new MatchingCopyAction(matcher, action));
+    }
+
+    public CopySpec filesNotMatching(String pattern, Action<? super FileCopyDetails> action) {
+        Spec<RelativePath> matcher = PatternMatcherFactory.getPatternMatcher(true, isCaseSensitive(), pattern);
+        return eachFile(
+                new MatchingCopyAction(new NotSpec<RelativePath>(matcher), action));
+    }
+
+    public CopySpec include(String... includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Iterable<String> includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Spec<FileTreeElement> includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public CopySpec include(Closure includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public Set<String> getIncludes() {
+        return patternSet.getIncludes();
+    }
+
+    public CopySpec setIncludes(Iterable<String> includes) {
+        patternSet.setIncludes(includes);
+        return this;
+    }
+
+    public List<String> getAllIncludes() {
+        List<String> result = new ArrayList<String>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllIncludes());
+        }
+        result.addAll(getIncludes());
+        return result;
+    }
+
+    public List<Spec<FileTreeElement>> getAllIncludeSpecs() {
+        List<Spec<FileTreeElement>> result = new ArrayList<Spec<FileTreeElement>>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllIncludeSpecs());
+        }
+        result.addAll(patternSet.getIncludeSpecs());
+        return result;
+    }
+
+    public CopySpec exclude(String... excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Iterable<String> excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public CopySpec exclude(Closure excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public Set<String> getExcludes() {
+        return patternSet.getExcludes();
+    }
+
+    public DefaultCopySpec setExcludes(Iterable<String> excludes) {
+        patternSet.setExcludes(excludes);
+        return this;
+    }
+
+    public CopySpec rename(String sourceRegEx, String replaceWith) {
+        actions.add(new RenamingCopyAction(new RegExpNameMapper(sourceRegEx, replaceWith)));
+        return this;
+    }
+
+    public CopySpec rename(Pattern sourceRegEx, String replaceWith) {
+        actions.add(new RenamingCopyAction(new RegExpNameMapper(sourceRegEx, replaceWith)));
+        return this;
+    }
+
+    public CopySpec filter(final Class<? extends FilterReader> filterType) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.filter(filterType);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec filter(final Closure closure) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.filter(closure);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec filter(final Map<String, ?> properties, final Class<? extends FilterReader> filterType) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.filter(properties, filterType);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec expand(final Map<String, ?> properties) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.expand(properties);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec rename(Closure closure) {
+        ChainingTransformer<String> transformer = new ChainingTransformer<String>(String.class);
+        transformer.add(closure);
+        actions.add(new RenamingCopyAction(transformer));
+        return this;
+    }
+
+    public Integer getDirMode() {
+        if (dirMode != null) {
+            return dirMode;
+        }
+        if (parentSpec != null) {
+            return parentSpec.getDirMode();
+        }
+        return null;
+    }
+
+    public Integer getFileMode() {
+        if (fileMode != null) {
+            return fileMode;
+        }
+        if (parentSpec != null) {
+            return parentSpec.getFileMode();
+        }
+        return null;
+    }
+
+    public CopyProcessingSpec setDirMode(Integer mode) {
+        dirMode = mode;
+        return this;
+    }
+
+    public CopyProcessingSpec setFileMode(Integer mode) {
+        fileMode = mode;
+        return this;
+    }
+
+    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
+        actions.add(action);
+        return this;
+    }
+
+    public CopySpec eachFile(Closure closure) {
+        actions.add(new ClosureBackedAction<FileCopyDetails>(closure));
+        return this;
+    }
+
+    public List<String> getAllExcludes() {
+        List<String> result = new ArrayList<String>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllExcludes());
+        }
+        result.addAll(getExcludes());
+        return result;
+    }
+
+    public List<Spec<FileTreeElement>> getAllExcludeSpecs() {
+        List<Spec<FileTreeElement>> result = new ArrayList<Spec<FileTreeElement>>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllExcludeSpecs());
+        }
+        result.addAll(patternSet.getExcludeSpecs());
+        return result;
+    }
+
+    public List<Action<? super FileCopyDetails>> getAllCopyActions() {
+        if (parentSpec == null) {
+            return actions;
+        }
+        List<Action<? super FileCopyDetails>> allActions = new ArrayList<Action<? super FileCopyDetails>>();
+        allActions.addAll(parentSpec.getAllCopyActions());
+        allActions.addAll(actions);
+        return allActions;
+    }
+
+    public Iterable<CopySpecInternal> getChildren() {
+        return childSpecs;
+    }
+
+    public void walk(Action<? super CopySpecInternal> action) {
+        action.execute(this);
+        for (CopySpecInternal child : getChildren()) {
+            child.walk(action);
+        }
+    }
+
+    public boolean hasSource() {
+        if (!sourcePaths.isEmpty()) {
+            return true;
+        }
+        for (CopySpecInternal spec : childSpecs) {
+            if (spec.hasSource()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultFileCopyDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultFileCopyDetails.java
new file mode 100644
index 0000000..3494dca
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultFileCopyDetails.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import groovy.lang.Closure;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.ContentFilterable;
+import org.gradle.api.file.DuplicatesStrategy;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
+
+import java.io.*;
+import java.util.Map;
+
+public class DefaultFileCopyDetails extends AbstractFileTreeElement implements FileVisitDetails, FileCopyDetailsInternal {
+    private final FileVisitDetails fileDetails;
+    private final CopySpecInternal spec;
+    private final FilterChain filterChain = new FilterChain();
+    private RelativePath relativePath;
+    private boolean excluded;
+    private Integer mode;
+    private DuplicatesStrategy duplicatesStrategy;
+
+    public DefaultFileCopyDetails(FileVisitDetails fileDetails, CopySpecInternal spec, Chmod chmod) {
+        super(chmod);
+        this.fileDetails = fileDetails;
+        this.spec = spec;
+        this.duplicatesStrategy = spec.getDuplicatesStrategy();
+    }
+
+    public boolean isIncludeEmptyDirs() {
+        return spec.getIncludeEmptyDirs();
+    }
+
+    public String getDisplayName() {
+        return fileDetails.toString();
+    }
+
+    public void stopVisiting() {
+        fileDetails.stopVisiting();
+    }
+
+    public File getFile() {
+        if (filterChain.hasFilters()) {
+            throw new UnsupportedOperationException();
+        } else {
+            return fileDetails.getFile();
+        }
+    }
+
+    public boolean isDirectory() {
+        return fileDetails.isDirectory();
+    }
+
+    public long getLastModified() {
+        return fileDetails.getLastModified();
+    }
+
+    public long getSize() {
+        if (filterChain.hasFilters()) {
+            ByteCountingOutputStream outputStream = new ByteCountingOutputStream();
+            copyTo(outputStream);
+            return outputStream.size;
+        } else {
+            return fileDetails.getSize();
+        }
+    }
+
+    public InputStream open() {
+        if (filterChain.hasFilters()) {
+            return filterChain.transform(fileDetails.open());
+        } else {
+            return fileDetails.open();
+        }
+    }
+
+    public void copyTo(OutputStream outstr) {
+        if (filterChain.hasFilters()) {
+            super.copyTo(outstr);
+        } else {
+            fileDetails.copyTo(outstr);
+        }
+    }
+
+    public boolean copyTo(File target) {
+        if (filterChain.hasFilters()) {
+            return super.copyTo(target);
+        } else {
+            final boolean copied = fileDetails.copyTo(target);
+            adaptPermissions(target);
+            return copied;
+        }
+    }
+
+    private void adaptPermissions(File target) {
+        final Integer specMode = getMode();
+        if(specMode !=null){
+            try {
+                getChmod().chmod(target, specMode);
+            } catch (IOException e) {
+                throw new GradleException(String.format("Could not set permission %s on '%s'.", specMode, target), e);
+            }
+        }
+    }
+
+    public RelativePath getRelativePath() {
+        if (relativePath == null) {
+            RelativePath path = fileDetails.getRelativePath();
+            relativePath = spec.getDestPath().append(path.isFile(), path.getSegments());
+        }
+        return relativePath;
+    }
+
+    public int getMode() {
+        if (mode != null) {
+            return mode;
+        }
+
+        Integer specMode = getSpecMode();
+        if (specMode != null) {
+            return specMode;
+        }
+
+        return fileDetails.getMode();
+    }
+
+    private Integer getSpecMode() {
+        return fileDetails.isDirectory() ? spec.getDirMode() : spec.getFileMode();
+    }
+
+    public void setRelativePath(RelativePath path) {
+        this.relativePath = path;
+    }
+
+    public void setName(String name) {
+        relativePath = getRelativePath().replaceLastName(name);
+    }
+
+    public void setPath(String path) {
+        relativePath = RelativePath.parse(getRelativePath().isFile(), path);
+    }
+
+    boolean isExcluded() {
+        return excluded;
+    }
+
+    public void exclude() {
+        excluded = true;
+    }
+
+    public void setMode(int mode) {
+        this.mode = mode;
+    }
+
+    public ContentFilterable filter(Closure closure) {
+        filterChain.add(closure);
+        return this;
+    }
+
+    public ContentFilterable filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+        filterChain.add(filterType, properties);
+        return this;
+    }
+
+    public ContentFilterable filter(Class<? extends FilterReader> filterType) {
+        filterChain.add(filterType);
+        return this;
+    }
+
+    public ContentFilterable expand(Map<String, ?> properties) {
+        filterChain.expand(properties);
+        return this;
+    }
+
+    public void setDuplicatesStrategy(DuplicatesStrategy strategy) {
+        this.duplicatesStrategy = strategy;
+    }
+
+    public DuplicatesStrategy getDuplicatesStrategy() {
+        return this.duplicatesStrategy;
+    }
+
+    private static class ByteCountingOutputStream extends OutputStream {
+        long size;
+
+        @Override
+        public void write(int b) throws IOException {
+            size++;
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            size += b.length;
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            size += len;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultZipCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultZipCompressor.java
new file mode 100644
index 0000000..2bbaad2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DefaultZipCompressor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.apache.tools.zip.Zip64Mode;
+import org.apache.tools.zip.ZipOutputStream;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.File;
+
+public class DefaultZipCompressor implements ZipCompressor {
+    private final int entryCompressionMethod;
+    private final Zip64Mode zip64Mode;
+
+    public DefaultZipCompressor(boolean allowZip64Mode, int entryCompressionMethod) {
+        this.entryCompressionMethod = entryCompressionMethod;
+        zip64Mode = allowZip64Mode ? Zip64Mode.AsNeeded : Zip64Mode.Never;
+    }
+
+    public ZipOutputStream createArchiveOutputStream(File destination) {
+        try {
+            ZipOutputStream outStream = new ZipOutputStream(destination);
+            outStream.setUseZip64(zip64Mode);
+            outStream.setMethod(entryCompressionMethod);
+            return outStream;
+        } catch (Exception e) {
+            String message = String.format("Unable to create ZIP output stream for file %s.", destination);
+            throw new UncheckedIOException(message, e);
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecInternal.java
new file mode 100644
index 0000000..60ec285
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecInternal.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.file.*;
+import org.gradle.api.specs.Spec;
+
+import java.io.FilterReader;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+abstract public class DelegatingCopySpecInternal implements CopySpecInternal {
+
+    abstract protected CopySpecInternal getDelegateCopySpec();
+
+    public RelativePath getDestPath() {
+        return getDelegateCopySpec().getDestPath();
+    }
+
+    public FileTree getSource() {
+        return getDelegateCopySpec().getSource();
+    }
+
+    public boolean hasSource() {
+        return getDelegateCopySpec().hasSource();
+    }
+
+    public Collection<? extends Action<? super FileCopyDetails>> getAllCopyActions() {
+        return getDelegateCopySpec().getAllCopyActions();
+    }
+
+    public boolean isCaseSensitive() {
+        return getDelegateCopySpec().isCaseSensitive();
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        getDelegateCopySpec().setCaseSensitive(caseSensitive);
+    }
+
+    public boolean getIncludeEmptyDirs() {
+        return getDelegateCopySpec().getIncludeEmptyDirs();
+    }
+
+    public void setIncludeEmptyDirs(boolean includeEmptyDirs) {
+        getDelegateCopySpec().setIncludeEmptyDirs(includeEmptyDirs);
+    }
+
+    public DuplicatesStrategy getDuplicatesStrategy() {
+        return getDelegateCopySpec().getDuplicatesStrategy();
+    }
+
+    public void setDuplicatesStrategy(DuplicatesStrategy strategy) {
+        getDelegateCopySpec().setDuplicatesStrategy(strategy);
+    }
+
+    public CopySpec filesMatching(String pattern, Action<? super FileCopyDetails> action) {
+        return getDelegateCopySpec().filesMatching(pattern, action);
+    }
+
+     public CopySpec filesNotMatching(String pattern, Action<? super FileCopyDetails> action) {
+        return getDelegateCopySpec().filesNotMatching(pattern, action);
+    }
+
+    public CopySpec with(CopySpec... sourceSpecs) {
+        return getDelegateCopySpec().with(sourceSpecs);
+    }
+
+    public CopySpec from(Object... sourcePaths) {
+        return getDelegateCopySpec().from(sourcePaths);
+    }
+
+    public CopySpec from(Object sourcePath, Closure c) {
+        return getDelegateCopySpec().from(sourcePath, c);
+    }
+
+    public CopySpec setIncludes(Iterable<String> includes) {
+        return getDelegateCopySpec().setIncludes(includes);
+    }
+
+    public CopySpec setExcludes(Iterable<String> excludes) {
+        return getDelegateCopySpec().setExcludes(excludes);
+    }
+
+    public CopySpec include(String... includes) {
+        return getDelegateCopySpec().include(includes);
+    }
+
+    public CopySpec include(Iterable<String> includes) {
+        return getDelegateCopySpec().include(includes);
+    }
+
+    public CopySpec include(Spec<FileTreeElement> includeSpec) {
+        return getDelegateCopySpec().include(includeSpec);
+    }
+
+    public CopySpec include(Closure includeSpec) {
+        return getDelegateCopySpec().include(includeSpec);
+    }
+
+    public CopySpec exclude(String... excludes) {
+        return getDelegateCopySpec().exclude(excludes);
+    }
+
+    public CopySpec exclude(Iterable<String> excludes) {
+        return getDelegateCopySpec().exclude(excludes);
+    }
+
+    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
+        return getDelegateCopySpec().exclude(excludeSpec);
+    }
+
+    public CopySpec exclude(Closure excludeSpec) {
+        return getDelegateCopySpec().exclude(excludeSpec);
+    }
+
+    public CopySpec into(Object destPath) {
+        return getDelegateCopySpec().into(destPath);
+    }
+
+    public CopySpec into(Object destPath, Closure configureClosure) {
+        return getDelegateCopySpec().into(destPath, configureClosure);
+    }
+
+    public CopySpec rename(Closure closure) {
+        return getDelegateCopySpec().rename(closure);
+    }
+
+    public CopySpec rename(String sourceRegEx, String replaceWith) {
+        return getDelegateCopySpec().rename(sourceRegEx, replaceWith);
+    }
+
+    public CopyProcessingSpec rename(Pattern sourceRegEx, String replaceWith) {
+        return getDelegateCopySpec().rename(sourceRegEx, replaceWith);
+    }
+
+    public CopySpec filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+        return getDelegateCopySpec().filter(properties, filterType);
+    }
+
+    public CopySpec filter(Class<? extends FilterReader> filterType) {
+        return getDelegateCopySpec().filter(filterType);
+    }
+
+    public CopySpec filter(Closure closure) {
+        return getDelegateCopySpec().filter(closure);
+    }
+
+    public CopySpec expand(Map<String, ?> properties) {
+        return getDelegateCopySpec().expand(properties);
+    }
+
+    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
+        return getDelegateCopySpec().eachFile(action);
+    }
+
+    public CopySpec eachFile(Closure closure) {
+        return getDelegateCopySpec().eachFile(closure);
+    }
+
+    public Integer getFileMode() {
+        return getDelegateCopySpec().getFileMode();
+    }
+
+    public CopyProcessingSpec setFileMode(Integer mode) {
+        return getDelegateCopySpec().setFileMode(mode);
+    }
+
+    public Integer getDirMode() {
+        return getDelegateCopySpec().getDirMode();
+    }
+
+    public CopyProcessingSpec setDirMode(Integer mode) {
+        return getDelegateCopySpec().setDirMode(mode);
+    }
+
+    public Set<String> getIncludes() {
+        return getDelegateCopySpec().getIncludes();
+    }
+
+    public Set<String> getExcludes() {
+        return getDelegateCopySpec().getExcludes();
+    }
+
+    public Iterable<CopySpecInternal> getChildren() {
+        return getDelegateCopySpec().getChildren();
+    }
+
+    public FileTree getAllSource() {
+        return getDelegateCopySpec().getAllSource();
+    }
+
+    public CopySpecInternal addChild() {
+        return getDelegateCopySpec().addChild();
+    }
+
+    public CopySpecInternal addChildBeforeSpec(CopySpecInternal spec) {
+        return getDelegateCopySpec().addChildBeforeSpec(spec);
+    }
+
+    public CopySpecInternal addFirst() {
+        return getDelegateCopySpec().addFirst();
+    }
+
+    public void walk(Action<? super CopySpecInternal> action) {
+        action.execute(this);
+        for (CopySpecInternal child : getChildren()) {
+            child.walk(action);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecVisitor.java
deleted file mode 100644
index 0a5ee3a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecVisitor.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.file.FileVisitDetails;
-
-public class DelegatingCopySpecVisitor implements CopySpecVisitor {
-    private final CopySpecVisitor visitor;
-
-    public DelegatingCopySpecVisitor(CopySpecVisitor visitor) {
-        this.visitor = visitor;
-    }
-
-    protected CopySpecVisitor getVisitor() {
-        return visitor;
-    }
-
-    public void startVisit(CopyAction action) {
-        getVisitor().startVisit(action);
-    }
-
-    public void endVisit() {
-        getVisitor().endVisit();
-    }
-
-    public void visitSpec(ReadableCopySpec spec) {
-        getVisitor().visitSpec(spec);
-    }
-
-    public void visitDir(FileVisitDetails dirDetails) {
-        getVisitor().visitDir(dirDetails);
-    }
-
-    public void visitFile(FileVisitDetails fileDetails) {
-        getVisitor().visitFile(fileDetails);
-    }
-
-    public boolean getDidWork() {
-        return getVisitor().getDidWork();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
index 923fc72..f3c72c9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
@@ -24,9 +24,6 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 
-/**
- * @author Hans Dockter
- */
 public class DeleteActionImpl implements DeleteAction {
     private static Logger logger = LoggerFactory.getLogger(DeleteActionImpl.class);
     
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DestinationRootCopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DestinationRootCopySpec.java
new file mode 100644
index 0000000..c101d26
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DestinationRootCopySpec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+
+public class DestinationRootCopySpec extends DelegatingCopySpecInternal {
+
+    private final FileResolver fileResolver;
+    private final CopySpecInternal delegate;
+
+    private Object destinationDir;
+
+    public DestinationRootCopySpec(FileResolver fileResolver, CopySpecInternal delegate) {
+        this.fileResolver = fileResolver;
+        this.delegate = delegate;
+    }
+
+    @Override
+    protected CopySpecInternal getDelegateCopySpec() {
+        return delegate;
+    }
+
+    @Override
+    public CopySpec into(Object destinationDir) {
+        this.destinationDir = destinationDir;
+        return this;
+    }
+
+    public File getDestinationDir() {
+        return destinationDir == null ? null : fileResolver.resolve(destinationDir);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecorator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecorator.java
new file mode 100644
index 0000000..d53a048
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecorator.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.file.DuplicateFileCopyingException;
+import org.gradle.api.file.DuplicatesStrategy;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.WorkResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DuplicateHandlingCopyActionDecorator implements CopyAction {
+
+    private final static Logger LOGGER = Logging.getLogger(DuplicateHandlingCopyActionDecorator.class);
+    private final CopyAction delegate;
+
+    public DuplicateHandlingCopyActionDecorator(CopyAction delegate) {
+        this.delegate = delegate;
+    }
+
+    public WorkResult execute(final CopyActionProcessingStream stream) {
+        final Set<RelativePath> visitedFiles = new HashSet<RelativePath>();
+
+        return delegate.execute(new CopyActionProcessingStream() {
+            public void process(final CopyActionProcessingStreamAction action) {
+                stream.process(new CopyActionProcessingStreamAction() {
+                    public void processFile(FileCopyDetailsInternal details) {
+                        if (!details.isDirectory()) {
+                            DuplicatesStrategy strategy = details.getDuplicatesStrategy();
+
+                            if (!visitedFiles.add(details.getRelativePath())) {
+                                if (strategy == DuplicatesStrategy.EXCLUDE) {
+                                    return;
+                                } else if (strategy == DuplicatesStrategy.FAIL) {
+                                    throw new DuplicateFileCopyingException(String.format("Encountered duplicate path \"%s\" during copy operation configured with DuplicatesStrategy.FAIL", details.getRelativePath()));
+                                } else if (strategy == DuplicatesStrategy.WARN) {
+                                    LOGGER.warn("Encountered duplicate path \"{}\" during copy operation configured with DuplicatesStrategy.WARN", details.getRelativePath());
+                                }
+                            }
+                        }
+
+                        action.processFile(details);
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/EmptyCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/EmptyCopySpecVisitor.java
deleted file mode 100644
index 0c9dbce..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/EmptyCopySpecVisitor.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.internal.file.copy;
-
-import org.gradle.api.file.FileVisitDetails;
-
-public class EmptyCopySpecVisitor implements CopySpecVisitor {
-    public boolean getDidWork() {
-        return false;
-    }
-
-    public void startVisit(CopyAction action) {
-    }
-
-    public void visitDir(FileVisitDetails dirDetails) {
-    }
-
-    public void endVisit() {
-    }
-
-    public void visitFile(FileVisitDetails fileDetails) {
-    }
-
-    public void visitSpec(ReadableCopySpec spec) {
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopier.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopier.java
new file mode 100644
index 0000000..67d521c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopier.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+
+public class FileCopier {
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+    private final FileLookup fileLookup;
+
+    public FileCopier(Instantiator instantiator, FileResolver fileResolver, FileLookup fileLookup) {
+        this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
+        this.fileLookup = fileLookup;
+    }
+
+    private DestinationRootCopySpec createCopySpec(Action<? super CopySpec> action) {
+        DefaultCopySpec copySpec = new DefaultCopySpec(this.fileResolver, instantiator);
+        DestinationRootCopySpec destinationRootCopySpec = new DestinationRootCopySpec(fileResolver, copySpec);
+        CopySpec wrapped = instantiator.newInstance(CopySpecWrapper.class, destinationRootCopySpec);
+        action.execute(wrapped);
+        return destinationRootCopySpec;
+    }
+
+    public WorkResult copy(Action<? super CopySpec> action) {
+        DestinationRootCopySpec copySpec = createCopySpec(action);
+        File destinationDir = copySpec.getDestinationDir();
+        return doCopy(copySpec, getCopyVisitor(destinationDir));
+    }
+
+    public WorkResult sync(Action<? super CopySpec> action) {
+        DestinationRootCopySpec copySpec = createCopySpec(action);
+        File destinationDir = copySpec.getDestinationDir();
+        return doCopy(copySpec, new SyncCopyActionDecorator(destinationDir, getCopyVisitor(destinationDir)));
+    }
+
+    private FileCopyAction getCopyVisitor(File destination) {
+        return new FileCopyAction(fileLookup.getFileResolver(destination));
+    }
+
+    private WorkResult doCopy(CopySpecInternal copySpec, CopyAction visitor) {
+        CopyActionExecuter visitorDriver = new CopyActionExecuter(instantiator, fileLookup.getFileSystem());
+        return visitorDriver.execute(copySpec, visitor);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java
index 7040c9d..00e8b1c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java
@@ -15,8 +15,36 @@
  */
 package org.gradle.api.internal.file.copy;
 
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+
 import java.io.File;
 
-public interface FileCopyAction extends CopyAction {
-    File getDestinationDir();
+public class FileCopyAction implements CopyAction {
+
+    private final FileResolver fileResolver;
+
+    public FileCopyAction(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+    }
+
+    public WorkResult execute(CopyActionProcessingStream stream) {
+        FileCopyDetailsInternalAction action = new FileCopyDetailsInternalAction();
+        stream.process(action);
+        return new SimpleWorkResult(action.didWork);
+    }
+
+    private class FileCopyDetailsInternalAction implements CopyActionProcessingStreamAction {
+        private boolean didWork;
+
+        public void processFile(FileCopyDetailsInternal details) {
+            File target = fileResolver.resolve(details.getRelativePath().getPathString());
+            boolean copied = details.copyTo(target);
+            if (copied) {
+                didWork = true;
+            }
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java
index 1a35e8c..659fc1e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,27 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.file.CopySpec;
-import org.gradle.api.internal.file.FileResolver;
-
-import java.io.File;
-
-public class FileCopyActionImpl extends CopyActionImpl implements FileCopyAction {
-    private Object destDir;
 
-    public FileCopyActionImpl(FileResolver resolver, CopySpecVisitor visitor) {
-        super(resolver, visitor);
-    }
-
-    @Override
-    public CopySpec into(Object destDir) {
-        this.destDir = destDir;
-        return this;
-    }
+package org.gradle.api.internal.file.copy;
 
-    public File getDestinationDir() {
-        return destDir == null ? null : getResolver().resolve(destDir);
-    }
+/**
+ * DO NOT REMOVE.
+ *
+ * Prior to 1.8, Copy leaked this as a parameter type on one of its methods.
+ * Has to exist to maintain binary compatibility.
+ */
+ at SuppressWarnings("UnusedDeclaration")
+ at Deprecated
+public class FileCopyActionImpl {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyDetailsInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyDetailsInternal.java
new file mode 100644
index 0000000..8de9bfd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyDetailsInternal.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.file.FileCopyDetails;
+
+public interface FileCopyDetailsInternal extends FileCopyDetails {
+
+    boolean isIncludeEmptyDirs();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitor.java
deleted file mode 100644
index 712e986..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitor.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.file.FileTreeElement;
-import org.gradle.api.file.FileVisitDetails;
-
-import java.io.File;
-
-/**
- * @author Steve Appling
- */
-public class FileCopySpecVisitor extends EmptyCopySpecVisitor {
-    private File baseDestDir;
-    private boolean didWork;
-
-    public void startVisit(CopyAction action) {
-        baseDestDir = ((FileCopyAction) action).getDestinationDir();
-        if (baseDestDir == null) {
-            throw new InvalidUserDataException("No copy destination directory has been specified, use 'into' to specify a target directory.");
-        }
-    }
-
-    public void visitFile(FileVisitDetails source) {
-        visitFileOrDir(source);
-    }
-
-    public void visitDir(FileVisitDetails source) {
-        visitFileOrDir(source);
-    }
-
-    public boolean getDidWork() {
-        return didWork;
-    }
-
-    private void visitFileOrDir(FileVisitDetails source) {
-        File target = source.getRelativePath().getFile(baseDestDir);
-        copyFile(source, target);
-    }
-
-    private void copyFile(FileTreeElement srcFile, File destFile) {
-        boolean copied = srcFile.copyTo(destFile);
-        if (copied) {
-            didWork = true;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
deleted file mode 100644
index a662c9d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
+++ /dev/null
@@ -1,230 +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.file.copy;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.file.ContentFilterable;
-import org.gradle.api.file.FileCopyDetails;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.AbstractFileTreeElement;
-import org.gradle.internal.nativeplatform.filesystem.FileSystem;
-
-import java.io.*;
-import java.util.Map;
-
-public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
-    private ReadableCopySpec spec;
-    private FileSystem fileSystem;
-
-    public MappingCopySpecVisitor(CopySpecVisitor visitor, FileSystem fileSystem) {
-        super(visitor);
-        this.fileSystem = fileSystem;
-    }
-
-    public void visitSpec(ReadableCopySpec spec) {
-        this.spec = spec;
-        getVisitor().visitSpec(spec);
-    }
-
-    public void visitDir(FileVisitDetails dirDetails) {
-        getVisitor().visitDir(new FileVisitDetailsImpl(dirDetails, spec, fileSystem));
-    }
-
-    public void visitFile(final FileVisitDetails fileDetails) {
-        FileVisitDetailsImpl details = new FileVisitDetailsImpl(fileDetails, spec, fileSystem);
-        for (Action<? super FileCopyDetails> action : spec.getAllCopyActions()) {
-            action.execute(details);
-            if (details.excluded) {
-                return;
-            }
-        }
-        getVisitor().visitFile(details);
-    }
-
-    private static class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails, FileCopyDetails {
-        private final FileVisitDetails fileDetails;
-        private final ReadableCopySpec spec;
-        private FileSystem fileSystem;
-        private final FilterChain filterChain = new FilterChain();
-        private RelativePath relativePath;
-        private boolean excluded;
-        private Integer mode;
-
-        public FileVisitDetailsImpl(FileVisitDetails fileDetails, ReadableCopySpec spec, FileSystem fileSystem) {
-            this.fileDetails = fileDetails;
-            this.spec = spec;
-            this.fileSystem = fileSystem;
-        }
-
-        public String getDisplayName() {
-            return fileDetails.toString();
-        }
-
-        public void stopVisiting() {
-            fileDetails.stopVisiting();
-        }
-
-        public File getFile() {
-            if (filterChain.hasFilters()) {
-                throw new UnsupportedOperationException();
-            } else {
-                return fileDetails.getFile();
-            }
-        }
-
-        public boolean isDirectory() {
-            return fileDetails.isDirectory();
-        }
-
-        public long getLastModified() {
-            return fileDetails.getLastModified();
-        }
-
-        public long getSize() {
-            if (filterChain.hasFilters()) {
-                ByteCountingOutputStream outputStream = new ByteCountingOutputStream();
-                copyTo(outputStream);
-                return outputStream.size;
-            } else {
-                return fileDetails.getSize();
-            }
-        }
-
-        public InputStream open() {
-            if (filterChain.hasFilters()) {
-                return filterChain.transform(fileDetails.open());
-            } else {
-                return fileDetails.open();
-            }
-        }
-
-        public void copyTo(OutputStream outstr) {
-            if (filterChain.hasFilters()) {
-                super.copyTo(outstr);
-            } else {
-                fileDetails.copyTo(outstr);
-            }
-        }
-
-        public boolean copyTo(File target) {
-            if (filterChain.hasFilters()) {
-                return super.copyTo(target);
-            } else {
-                final boolean copied = fileDetails.copyTo(target);
-                adaptPermissions(target);
-                return copied;
-            }
-        }
-
-        private void adaptPermissions(File target) {
-            final Integer specMode = getMode();
-            if(specMode !=null){
-                try {
-                    fileSystem.chmod(target, specMode);
-                } catch (IOException e) {
-                    throw new GradleException(String.format("Could not set permission %s on '%s'.", specMode, target), e);
-                }
-            }
-        }
-
-        public RelativePath getRelativePath() {
-            if (relativePath == null) {
-                RelativePath path = fileDetails.getRelativePath();
-                relativePath = spec.getDestPath().append(path.isFile(), path.getSegments());
-            }
-            return relativePath;
-        }
-
-        public int getMode() {
-            if (mode != null) {
-                return mode;
-            }
-
-            Integer specMode = getSpecMode();
-            if (specMode != null) {
-                return specMode;
-            }
-
-            return fileDetails.getMode();
-        }
-
-        private Integer getSpecMode() {
-            return fileDetails.isDirectory() ? spec.getDirMode() : spec.getFileMode();
-        }
-
-        public void setRelativePath(RelativePath path) {
-            this.relativePath = path;
-        }
-
-        public void setName(String name) {
-            relativePath = getRelativePath().replaceLastName(name);
-        }
-
-        public void setPath(String path) {
-            relativePath = RelativePath.parse(getRelativePath().isFile(), path);
-        }
-
-        public void exclude() {
-            excluded = true;
-        }
-
-        public void setMode(int mode) {
-            this.mode = mode;
-        }
-
-        public ContentFilterable filter(Closure closure) {
-            filterChain.add(closure);
-            return this;
-        }
-
-        public ContentFilterable filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
-            filterChain.add(filterType, properties);
-            return this;
-        }
-
-        public ContentFilterable filter(Class<? extends FilterReader> filterType) {
-            filterChain.add(filterType);
-            return this;
-        }
-
-        public ContentFilterable expand(Map<String, ?> properties) {
-            filterChain.expand(properties);
-            return this;
-        }
-    }
-
-    private static class ByteCountingOutputStream extends OutputStream {
-        long size;
-
-        @Override
-        public void write(int b) throws IOException {
-            size++;
-        }
-
-        @Override
-        public void write(byte[] b) throws IOException {
-            size += b.length;
-        }
-
-        @Override
-        public void write(byte[] b, int off, int len) throws IOException {
-            size += len;
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MatchingCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MatchingCopyAction.java
new file mode 100644
index 0000000..74329be
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MatchingCopyAction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+public class MatchingCopyAction implements Action<FileCopyDetails> {
+
+    private final Spec<RelativePath> matchSpec;
+
+    private final Action<? super FileCopyDetails> toApply;
+
+    public MatchingCopyAction(Spec<RelativePath> matchSpec, Action<? super FileCopyDetails> toApply) {
+        this.matchSpec = matchSpec;
+        this.toApply = toApply;
+    }
+
+    public void execute(FileCopyDetails details) {
+        if (matchSpec.isSatisfiedBy(details.getRelativePath())) {
+            toApply.execute(details);
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopyActionDecorator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopyActionDecorator.java
new file mode 100644
index 0000000..bdc267a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopyActionDecorator.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import groovy.lang.Closure;
+import org.gradle.api.file.ContentFilterable;
+import org.gradle.api.file.DuplicatesStrategy;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
+
+import java.io.File;
+import java.io.FilterReader;
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * A {@link CopyAction} which cleans up the tree as it is visited. Removes duplicate directories and adds in missing directories. Removes empty directories if instructed to do so by copy
+ * spec.
+ */
+public class NormalizingCopyActionDecorator implements CopyAction {
+
+    private final CopyAction delegate;
+    private final Chmod chmod;
+
+    public NormalizingCopyActionDecorator(CopyAction delegate, Chmod chmod) {
+        this.delegate = delegate;
+        this.chmod = chmod;
+    }
+
+    public WorkResult execute(final CopyActionProcessingStream stream) {
+        final Set<RelativePath> visitedDirs = new HashSet<RelativePath>();
+        final ListMultimap<RelativePath, FileCopyDetailsInternal> pendingDirs = ArrayListMultimap.create();
+
+        WorkResult result = delegate.execute(new CopyActionProcessingStream() {
+            public void process(final CopyActionProcessingStreamAction action) {
+
+
+                stream.process(new CopyActionProcessingStreamAction() {
+                    public void processFile(FileCopyDetailsInternal details) {
+                        if (details.isDirectory()) {
+                            RelativePath path = details.getRelativePath();
+                            if (!visitedDirs.contains(path)) {
+                                pendingDirs.put(path, details);
+                            }
+                        } else {
+                            maybeVisit(details.getRelativePath().getParent(), details.isIncludeEmptyDirs(), action);
+                            action.processFile(details);
+                        }
+                    }
+                });
+
+                for (RelativePath path : new LinkedHashSet<RelativePath>(pendingDirs.keySet())) {
+                    List<FileCopyDetailsInternal> detailsList = new ArrayList<FileCopyDetailsInternal>(pendingDirs.get(path));
+                    for (FileCopyDetailsInternal details : detailsList) {
+                        if (details.isIncludeEmptyDirs()) {
+                            maybeVisit(path, details.isIncludeEmptyDirs(), action);
+                        }
+                    }
+                }
+
+                visitedDirs.clear();
+                pendingDirs.clear();
+            }
+
+            private void maybeVisit(RelativePath path, boolean includeEmptyDirs, CopyActionProcessingStreamAction delegateAction) {
+                if (path == null || path.getParent() == null || !visitedDirs.add(path)) {
+                    return;
+                }
+                maybeVisit(path.getParent(), includeEmptyDirs, delegateAction);
+                List<FileCopyDetailsInternal> detailsForPath = pendingDirs.removeAll(path);
+
+                FileCopyDetailsInternal dir;
+                if (detailsForPath.isEmpty()) {
+                    // TODO - this is pretty nasty, look at avoiding using a time bomb stub here
+                    dir = new StubbedFileCopyDetails(path, includeEmptyDirs, chmod);
+                } else {
+                    dir = detailsForPath.get(0);
+                }
+                delegateAction.processFile(dir);
+            }
+        });
+
+        return result;
+    }
+
+
+    private static class StubbedFileCopyDetails extends AbstractFileTreeElement implements FileCopyDetailsInternal {
+        private final RelativePath path;
+        private final boolean includeEmptyDirs;
+        private long lastModified = System.currentTimeMillis();
+
+        private StubbedFileCopyDetails(RelativePath path, boolean includeEmptyDirs, Chmod chmod) {
+            super(chmod);
+            this.path = path;
+            this.includeEmptyDirs = includeEmptyDirs;
+        }
+
+        public boolean isIncludeEmptyDirs() {
+            return includeEmptyDirs;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return path.toString();
+        }
+
+        public File getFile() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isDirectory() {
+            return !path.isFile();
+        }
+
+        public long getLastModified() {
+            return lastModified;
+        }
+
+        public long getSize() {
+            throw new UnsupportedOperationException();
+        }
+
+        public InputStream open() {
+            throw new UnsupportedOperationException();
+        }
+
+        public RelativePath getRelativePath() {
+            return path;
+        }
+
+        public void exclude() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setName(String name) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setPath(String path) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setRelativePath(RelativePath path) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setMode(int mode) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setDuplicatesStrategy(DuplicatesStrategy strategy) {
+            throw new UnsupportedOperationException();
+        }
+
+        public DuplicatesStrategy getDuplicatesStrategy() {
+            throw new UnsupportedOperationException();
+        }
+
+        public ContentFilterable filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ContentFilterable filter(Class<? extends FilterReader> filterType) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ContentFilterable filter(Closure closure) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ContentFilterable expand(Map<String, ?> properties) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitor.java
deleted file mode 100644
index f35c3e6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitor.java
+++ /dev/null
@@ -1,121 +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.file.copy;
-
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.AbstractFileTreeElement;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.*;
-
-/**
- * A {@link CopySpecVisitor} which cleans up the tree as it is visited. Removes duplicate directories and
- * adds in missing directories. Removes empty directories if instructed to do so by copy spec.
- */
-public class NormalizingCopySpecVisitor extends DelegatingCopySpecVisitor {
-    private ReadableCopySpec spec;
-    private final Set<RelativePath> visitedDirs = new HashSet<RelativePath>();
-    private final Map<RelativePath, FileVisitDetails> pendingDirs = new HashMap<RelativePath, FileVisitDetails>();
-
-    public NormalizingCopySpecVisitor(CopySpecVisitor visitor) {
-        super(visitor);
-    }
-
-    @Override
-    public void visitSpec(ReadableCopySpec spec) {
-        this.spec = spec;
-        getVisitor().visitSpec(spec);
-    }
-
-    public void endVisit() {
-        if (spec.getIncludeEmptyDirs()) {
-            for (RelativePath path : new ArrayList<RelativePath>(pendingDirs.keySet())) {
-                maybeVisit(path);
-            }
-        }
-        visitedDirs.clear();
-        pendingDirs.clear();
-        getVisitor().endVisit();
-    }
-
-    private void maybeVisit(RelativePath path) {
-        if (path == null || path.getParent() == null || !visitedDirs.add(path)) {
-            return;
-        }
-        maybeVisit(path.getParent());
-        FileVisitDetails dir = pendingDirs.remove(path);
-        if (dir == null) {
-            dir = new FileVisitDetailsImpl(path);
-        }
-        getVisitor().visitDir(dir);
-    }
-
-    public void visitFile(FileVisitDetails fileDetails) {
-        maybeVisit(fileDetails.getRelativePath().getParent());
-        getVisitor().visitFile(fileDetails);
-    }
-
-    public void visitDir(FileVisitDetails dirDetails) {
-        RelativePath path = dirDetails.getRelativePath();
-        if (!visitedDirs.contains(path)) {
-            pendingDirs.put(path, dirDetails);
-        }
-    }
-
-    private static class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails {
-        private final RelativePath path;
-        private long lastModified = System.currentTimeMillis();
-
-        private FileVisitDetailsImpl(RelativePath path) {
-            this.path = path;
-        }
-
-        @Override
-        public String getDisplayName() {
-            return path.toString();
-        }
-
-        public void stopVisiting() {
-            throw new UnsupportedOperationException();
-        }
-
-        public File getFile() {
-            throw new UnsupportedOperationException();
-        }
-
-        public boolean isDirectory() {
-            return !path.isFile();
-        }
-
-        public long getLastModified() {
-            return lastModified;
-        }
-
-        public long getSize() {
-            throw new UnsupportedOperationException();
-        }
-
-        public InputStream open() {
-            throw new UnsupportedOperationException();
-        }
-
-        public RelativePath getRelativePath() {
-            return path;
-        }
-    }
-}
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 43fa312..975da75 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
@@ -17,14 +17,14 @@
 package org.gradle.api.internal.file.copy;
 
 import groovy.lang.Closure;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.internal.UncheckedException;
 import org.gradle.util.DeprecationLogger;
 
 import java.util.Collection;
 import java.util.concurrent.Callable;
 
-public class PathNotationParser<T extends String> implements NotationParser<T> {
+public class PathNotationParser<T extends String> implements NotationParser<Object, T> {
 
     public void describe(Collection<String> candidateFormats) {
         candidateFormats.add("Strings, Boolean, Number like: 'path/to', true, Boolean.TRUE, 42, 3.14");
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
index 6a623b8..82334d8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,29 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.Action;
-import org.gradle.api.file.FileCopyDetails;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.file.RelativePath;
 
-import java.util.Collection;
+package org.gradle.api.internal.file.copy;
 
+/**
+ * DO NOT REMOVE.
+ *
+ * Prior to 1.8, Copy implemented this so it is needed to keep backwards compatibility.
+ */
+ at SuppressWarnings("UnusedDeclaration")
+ at Deprecated
 public interface ReadableCopySpec {
-    RelativePath getDestPath();
-
-    Integer getFileMode();
-
-    Integer getDirMode();
-
-    FileTree getSource();
-
-    Collection<? extends ReadableCopySpec> getAllSpecs();
-
-    boolean hasSource();
-
-    Collection<? extends Action<? super FileCopyDetails>> getAllCopyActions();
-
-    boolean getIncludeEmptyDirs();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
index c871185..55df249 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
@@ -20,9 +20,6 @@ import org.gradle.api.Transformer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * @author Steve Appling
- */
 public class RegExpNameMapper implements Transformer<String, String> {
     private Matcher matcher;
     private String replacement;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RelativizedCopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RelativizedCopySpec.java
new file mode 100644
index 0000000..429735d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RelativizedCopySpec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.file.RelativePath;
+
+import static org.gradle.util.CollectionUtils.collect;
+
+public class RelativizedCopySpec extends DelegatingCopySpecInternal {
+
+    private final CopySpecInternal parent;
+    private final CopySpecInternal child;
+
+    public RelativizedCopySpec(CopySpecInternal parent, CopySpecInternal child) {
+        this.parent = parent;
+        this.child = child;
+    }
+
+    @Override
+    protected CopySpecInternal getDelegateCopySpec() {
+        return child;
+    }
+
+    public RelativePath getDestPath() {
+        return parent.getDestPath().append(child.getDestPath());
+    }
+
+    @Override
+    public Iterable<CopySpecInternal> getChildren() {
+        return collect(super.getChildren(), new Transformer<CopySpecInternal, CopySpecInternal>() {
+            public CopySpecInternal transform(CopySpecInternal original) {
+                return new RelativizedCopySpec(parent, original);
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java
new file mode 100644
index 0000000..68a12b1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.internal.file.collections.MinimalFileTree;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SyncCopyActionDecorator implements CopyAction {
+    private final File baseDestDir;
+    private final CopyAction delegate;
+
+    public SyncCopyActionDecorator(File baseDestDir, CopyAction delegate) {
+        this.baseDestDir = baseDestDir;
+        this.delegate = delegate;
+    }
+
+    public WorkResult execute(final CopyActionProcessingStream stream) {
+        final Set<RelativePath> visited = new HashSet<RelativePath>();
+
+        WorkResult didWork = delegate.execute(new CopyActionProcessingStream() {
+            public void process(final CopyActionProcessingStreamAction action) {
+                stream.process(new CopyActionProcessingStreamAction() {
+                    public void processFile(FileCopyDetailsInternal details) {
+                        visited.add(details.getRelativePath());
+                        action.processFile(details);
+                    }
+                });
+            }
+        });
+
+        SyncCopyActionDecoratorFileVisitor fileVisitor = new SyncCopyActionDecoratorFileVisitor(visited);
+
+        MinimalFileTree walker = new DirectoryFileTree(baseDestDir).postfix();
+        walker.visit(fileVisitor);
+        visited.clear();
+
+        return new SimpleWorkResult(didWork.getDidWork() || fileVisitor.didWork);
+    }
+
+    private static class SyncCopyActionDecoratorFileVisitor implements FileVisitor {
+        private final Set<RelativePath> visited;
+        private boolean didWork;
+
+        private SyncCopyActionDecoratorFileVisitor(Set<RelativePath> visited) {
+            this.visited = visited;
+        }
+
+        public void visitDir(FileVisitDetails dirDetails) {
+            maybeDelete(dirDetails, true);
+        }
+
+        public void visitFile(FileVisitDetails fileDetails) {
+            maybeDelete(fileDetails, false);
+        }
+
+        private void maybeDelete(FileVisitDetails fileDetails, boolean isDir) {
+            RelativePath path = fileDetails.getRelativePath();
+            if (!visited.contains(path)) {
+                if (isDir) {
+                    GFileUtils.deleteDirectory(fileDetails.getFile());
+                } else {
+                    GFileUtils.deleteQuietly(fileDetails.getFile());
+                }
+                didWork = true;
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
deleted file mode 100644
index 74783f3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
+++ /dev/null
@@ -1,90 +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.file.copy;
-
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.FileVisitor;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.collections.DirectoryFileTree;
-import org.gradle.api.internal.file.collections.MinimalFileTree;
-import org.gradle.util.GFileUtils;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-
-public class SyncCopySpecVisitor extends DelegatingCopySpecVisitor {
-    private final Set<RelativePath> visited = new HashSet<RelativePath>();
-    private File baseDestDir;
-    private boolean didWork;
-
-    public SyncCopySpecVisitor(CopySpecVisitor visitor) {
-        super(visitor);
-    }
-
-    public void startVisit(CopyAction action) {
-        baseDestDir = ((FileCopyAction) action).getDestinationDir();
-        getVisitor().startVisit(action);
-    }
-
-    @Override
-    public void visitDir(FileVisitDetails dirDetails) {
-        visited.add(dirDetails.getRelativePath());
-        getVisitor().visitDir(dirDetails);
-    }
-
-    @Override
-    public void visitFile(FileVisitDetails fileDetails) {
-        visited.add(fileDetails.getRelativePath());
-        getVisitor().visitFile(fileDetails);
-    }
-
-    @Override
-    public void endVisit() {
-        FileVisitor visitor = new FileVisitor() {
-            public void visitDir(FileVisitDetails dirDetails) {
-                maybeDelete(dirDetails, true);
-            }
-
-            public void visitFile(FileVisitDetails fileDetails) {
-                maybeDelete(fileDetails, false);
-            }
-
-            private void maybeDelete(FileVisitDetails fileDetails, boolean isDir) {
-                RelativePath path = fileDetails.getRelativePath();
-                if (!visited.contains(path)) {
-                    if (isDir) {
-                        GFileUtils.deleteDirectory(fileDetails.getFile());
-                    } else {
-                        GFileUtils.deleteQuietly(fileDetails.getFile());
-                    }
-                    didWork = true;
-                }
-            }
-        };
-
-        MinimalFileTree walker = new DirectoryFileTree(baseDestDir).postfix();
-        walker.visit(visitor);
-        visited.clear();
-
-        getVisitor().endVisit();
-    }
-
-    @Override
-    public boolean getDidWork() {
-        return didWork || getVisitor().getDidWork();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java
index c99e67b..8315f25 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java
@@ -15,12 +15,13 @@
  */
 package org.gradle.api.internal.file.copy;
 
-import java.io.File;
-
 import org.apache.tools.zip.ZipOutputStream;
 import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
 
+import java.io.File;
+
 public interface ZipCompressor extends ArchiveOutputStreamFactory {
 
     ZipOutputStream createArchiveOutputStream(File destination);
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipDeflatedCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipDeflatedCompressor.java
deleted file mode 100644
index 65bcbde..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipDeflatedCompressor.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.file.copy;
-
-import org.apache.tools.zip.ZipOutputStream;
-
-public class ZipDeflatedCompressor extends AbstractZipCompressor {
-
-    public static final ZipCompressor INSTANCE = new ZipDeflatedCompressor();
-
-    public ZipDeflatedCompressor() {
-    }
-
-    @Override
-    public int getCompressedMethod() {
-        return ZipOutputStream.DEFLATED;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipStoredCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipStoredCompressor.java
deleted file mode 100644
index fa71d59..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipStoredCompressor.java
+++ /dev/null
@@ -1,28 +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.file.copy;
-
-import org.apache.tools.zip.ZipOutputStream;
-
-public class ZipStoredCompressor extends AbstractZipCompressor{
-
-    public static final ZipCompressor INSTANCE = new ZipStoredCompressor();
-
-    @Override
-    public int getCompressedMethod() {
-        return ZipOutputStream.STORED;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/AnyWildcardPatternStep.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/AnyWildcardPatternStep.java
new file mode 100644
index 0000000..05cc770
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/AnyWildcardPatternStep.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.pattern;
+
+public class AnyWildcardPatternStep implements PatternStep{
+    public boolean matches(String candidate) {
+        return true;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcher.java
deleted file mode 100644
index 53dd653..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcher.java
+++ /dev/null
@@ -1,159 +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.file.pattern;
-
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.specs.Spec;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.util.ListIterator;
-
-/**
- * @author Steve Appling
- */
-public class DefaultPatternMatcher implements Spec<RelativePath> {
-    private List<PatternStep> steps;
-    private boolean partialMatchDirs;
-
-    public DefaultPatternMatcher(boolean partialMatchDirs, boolean caseSensitive, String... patternParts) {
-        this.partialMatchDirs = partialMatchDirs;
-        steps = new ArrayList<PatternStep>();
-        compile(caseSensitive, patternParts);
-    }
-
-    private void compile(boolean caseSensitive, String[] parts) {
-        if (parts.length > 0) {
-            for (int i = 0; i < parts.length; i++) {
-                steps.add(PatternStepFactory.getStep(parts[i], i == parts.length - 1, caseSensitive));
-            }
-        }
-    }
-
-    // segment -> path to test
-    // step -> pattern
-
-    public boolean isSatisfiedBy(RelativePath pathToTest) {
-        ListIterator<PatternStep> patternIt = steps.listIterator();
-        ListIterator<String> testIt = pathToTest.segmentIterator();
-        boolean seenGreedy = false;
-
-        PatternStep patternStep;
-
-        while (testIt.hasNext()) {
-            String nextToTest = testIt.next();
-
-            if (!patternIt.hasNext()) {
-                return false;
-            }
-            patternStep = patternIt.next();
-
-            if (patternStep.isGreedy()) {
-                seenGreedy = true;
-                advancePatternStepToNextNonGreedy(patternIt);
-                if (!patternIt.hasNext()) {
-                    return true;
-                }    // pattern ends in greedy
-                patternStep = patternIt.next();
-
-                // advance test until match
-                while (!(patternStep.matches(nextToTest, !testIt.hasNext() && pathToTest.isFile()) && (
-                        (patternIt.hasNext() == testIt.hasNext()) || nextPatternIsGreedy(patternIt)))) {
-                    if (!testIt.hasNext()) {
-                        return partialMatchDirs && !pathToTest
-                                .isFile(); //isTerminatingMatch(pathToTest, patternIt);  // didn't match, but no more segments to test
-                    }
-                    nextToTest = testIt.next();
-                }
-
-                // should have match at this point, can continue on around the loop
-            } else {
-                // not a greedy patternStep
-                if (!patternStep.matches(nextToTest, !testIt.hasNext() && pathToTest.isFile())) {
-                    // didn't match, check if we are after another greedy
-                    if (seenGreedy) {
-                        rewindPatternStepToPreviousGreedy(patternIt);  // rewind pattern to greedy
-                        testIt.previous(); // back up test by one
-                    } else {
-                        return false;  // haven't seen greedy, no match
-                    }
-                }
-            }
-        }
-        // ran out of stuff to test
-
-        if (!patternIt.hasNext()) {
-            return true;    // if out of pattern too, then it's a match
-        }
-
-        return isTerminatingMatch(pathToTest, patternIt);
-    }
-
-    private boolean nextPatternIsGreedy(ListIterator<PatternStep> patternIt) {
-        boolean result = false;
-        if (patternIt.hasNext()) {
-            PatternStep next = patternIt.next();
-            if (next.isGreedy() && !patternIt.hasNext()) {
-                result = true;
-            }
-            patternIt.previous();
-        }
-        return result;
-    }
-
-    private boolean isTerminatingMatch(RelativePath pathToTest, ListIterator<PatternStep> patternIt) {
-        PatternStep patternStep;
-
-        if (patternIt.hasNext()) {
-            patternStep = patternIt.next();
-            if (patternStep.isGreedy() && !patternIt.hasNext()) {
-                return true;    // if only a trailing greedy is left, then it matches
-            }
-        }
-
-        return !pathToTest.isFile() && partialMatchDirs;
-    }
-
-    private void advancePatternStepToNextNonGreedy(ListIterator<PatternStep> patternIt) {
-        PatternStep next = null;
-        while (patternIt.hasNext()) {
-            next = patternIt.next();
-            if (!next.isGreedy()) {
-                break;
-            }
-        }
-        // back up one
-        if (next != null && !next.isGreedy()) {
-            patternIt.previous();
-        }
-    }
-
-    private void rewindPatternStepToPreviousGreedy(ListIterator<PatternStep> patternIt) {
-        PatternStep result = null;
-        while (patternIt.hasPrevious()) {
-            result = patternIt.previous();
-            if (result.isGreedy()) {
-                //patternIt.next();
-                return;
-            }
-        }
-        throw new IllegalStateException("PatternStep list iterator in non-greedy state when rewindToLastGreedy");
-    }
-
-    List<PatternStep> getStepsForTest() {
-        return steps;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/EndOfPathMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/EndOfPathMatcher.java
new file mode 100644
index 0000000..1c969d5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/EndOfPathMatcher.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern;
+
+public class EndOfPathMatcher implements PathMatcher {
+    public int getMaxSegments() {
+        return 0;
+    }
+
+    public int getMinSegments() {
+        return 0;
+    }
+
+    public boolean matches(String[] segments, int startIndex) {
+        return startIndex == segments.length;
+    }
+
+    public boolean isPrefix(String[] segments, int startIndex) {
+        return false;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/FixedPatternStep.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/FixedPatternStep.java
new file mode 100644
index 0000000..a0435d5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/FixedPatternStep.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern;
+
+/**
+ * A pattern step for a fixed pattern segment that does not contain any wildcards.
+ */
+public class FixedPatternStep implements PatternStep {
+    private final String value;
+    private final boolean caseSensitive;
+
+    public FixedPatternStep(String value, boolean caseSensitive) {
+        this.value = value;
+        this.caseSensitive = caseSensitive;
+    }
+
+    public boolean matches(String candidate) {
+        return caseSensitive ? candidate.equals(value) : candidate.equalsIgnoreCase(value);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/FixedStepsPathMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/FixedStepsPathMatcher.java
new file mode 100644
index 0000000..85df562
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/FixedStepsPathMatcher.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern;
+
+import java.util.List;
+
+public class FixedStepsPathMatcher implements PathMatcher {
+    private final List<PatternStep> steps;
+    private final PathMatcher next;
+    private final int minSegments;
+    private final int maxSegments;
+
+    public FixedStepsPathMatcher(List<PatternStep> steps, PathMatcher next) {
+        this.steps = steps;
+        this.next = next;
+        minSegments = steps.size() + next.getMinSegments();
+        maxSegments = next.getMaxSegments() == Integer.MAX_VALUE ? Integer.MAX_VALUE : next.getMaxSegments() + steps.size();
+    }
+
+    public int getMinSegments() {
+        return minSegments;
+    }
+
+    public int getMaxSegments() {
+        return maxSegments;
+    }
+
+    public boolean matches(String[] segments, int startIndex) {
+        int remaining = segments.length - startIndex;
+        if (remaining < minSegments || remaining > maxSegments) {
+            return false;
+        }
+        int pos = startIndex;
+        for (int i = 0; i < steps.size(); i++, pos++) {
+            PatternStep step = steps.get(i);
+            if (!step.matches(segments[pos])) {
+                return false;
+            }
+        }
+        return next.matches(segments, pos);
+    }
+
+    public boolean isPrefix(String[] segments, int startIndex) {
+        int pos = startIndex;
+        for (int i = 0; pos < segments.length && i < steps.size(); i++, pos++) {
+            PatternStep step = steps.get(i);
+            if (!step.matches(segments[pos])) {
+                return false;
+            }
+        }
+        if (pos == segments.length) {
+            return true;
+        }
+        return next.isPrefix(segments, pos);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPathMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPathMatcher.java
new file mode 100644
index 0000000..f4d4e86
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPathMatcher.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern;
+
+public class GreedyPathMatcher implements PathMatcher {
+    private final PathMatcher next;
+
+    public GreedyPathMatcher(PathMatcher next) {
+        this.next = next;
+    }
+
+    public int getMaxSegments() {
+        return Integer.MAX_VALUE;
+    }
+
+    public int getMinSegments() {
+        return next.getMinSegments();
+    }
+
+    public boolean matches(String[] segments, int startIndex) {
+        int pos = segments.length - next.getMinSegments();
+        int minPos = Math.max(startIndex, segments.length - next.getMaxSegments());
+        for (; pos >= minPos; pos--) {
+            if (next.matches(segments, pos)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isPrefix(String[] segments, int startIndex) {
+        return true;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPatternStep.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPatternStep.java
deleted file mode 100644
index 7903443..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPatternStep.java
+++ /dev/null
@@ -1,29 +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.file.pattern;
-
-/**
- * @author Steve Appling
- */
-public class GreedyPatternStep implements PatternStep{
-    public boolean matches(String candidate, boolean isFile) {
-        return true;
-    }
-
-    public boolean isGreedy() {
-        return true;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcher.java
deleted file mode 100644
index 5092754..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcher.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.pattern;
-
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.specs.Spec;
-
-/**
- * PatternMatcher for handling the very common '**\name' pattern
- * more efficiently than the DefaultPatternMatcher.
- *
- * This will only match against the last part of a relative path and this only if
- * the RelativePath is a File.
- * @author Steve Appling
- */
-public class NameOnlyPatternMatcher implements Spec<RelativePath> {
-    private boolean partialMatchDirs;
-    private PatternStep nameStep;
-
-    /**
-     * CTOR
-     *
-     * Note that pattern must be a single pattern step - i.e. can't contain
-     * embedded '\' or '/'.  This is intended to match a file name.  It
-     * can contain '*', '?' as wildcards.
-     * @param partialMatchDirs
-     * @param caseSensitive
-     * @param pattern
-     */
-    public NameOnlyPatternMatcher(boolean partialMatchDirs, boolean caseSensitive, String pattern) {
-        this.partialMatchDirs = partialMatchDirs;
-        nameStep = PatternStepFactory.getStep(pattern, true, caseSensitive);
-    }
-
-    public boolean isSatisfiedBy(RelativePath path) {
-        if (!path.isFile()) {
-            return partialMatchDirs;
-        }
-        String lastName = path.getLastName();
-        if (lastName == null) {
-            return false;
-        }
-        return nameStep.matches(lastName, true);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PathMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PathMatcher.java
new file mode 100644
index 0000000..9da13b7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PathMatcher.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern;
+
+public interface PathMatcher {
+    /**
+     * Returns the minimum number of segments a path must have to satisfy this matcher.
+     */
+    int getMinSegments();
+
+    /**
+     * Returns the maximum number of segments a path must have to satisfy this matcher.
+     */
+    int getMaxSegments();
+
+    /**
+     * Returns true if the path starting at the given offset satisfies this pattern.
+     */
+    boolean matches(String[] segments, int startIndex);
+
+    /**
+     * Returns true if the path starting at the given offset could be satisfy this pattern if it contained additional segments at the end.
+     */
+    boolean isPrefix(String[] segments, int startIndex);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java
index 9a8670f..5a3cdfc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java
@@ -18,32 +18,65 @@ package org.gradle.api.internal.file.pattern;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.specs.Spec;
 
-/**
- * @author Steve Appling
- */
+import java.util.ArrayList;
+import java.util.List;
+
 public class PatternMatcherFactory {
+
+    public static final EndOfPathMatcher END_OF_PATH_MATCHER = new EndOfPathMatcher();
+
     public static Spec<RelativePath> getPatternMatcher(boolean partialMatchDirs, boolean caseSensitive, String pattern) {
+        PathMatcher pathMatcher = compile(caseSensitive, pattern);
+        return new PathMatcherBackedSpec(partialMatchDirs, pathMatcher);
+    }
+
+    private static PathMatcher compile(boolean caseSensitive, String pattern) {
+        if (pattern.length() == 0) {
+            return END_OF_PATH_MATCHER;
+        }
+
         // trailing / or \ assumes **
         if (pattern.endsWith("/") || pattern.endsWith("\\")) {
             pattern = pattern + "**";
         }
+        String[] parts = pattern.split("\\\\|/");
+        return compile(parts, 0, caseSensitive);
+    }
+
+    private static PathMatcher compile(String[] parts, int startIndex, boolean caseSensitive) {
+        if (startIndex >= parts.length) {
+            return END_OF_PATH_MATCHER;
+        }
+        int pos = startIndex;
+        while (pos < parts.length && parts[pos].equals("**")) {
+            pos++;
+        }
+        if (pos > startIndex) {
+            return new GreedyPathMatcher(compile(parts, pos, caseSensitive));
+        }
+        List<PatternStep> steps = new ArrayList<PatternStep>(parts.length - startIndex);
+        while (pos < parts.length && !parts[pos].equals("**")) {
+            steps.add(PatternStepFactory.getStep(parts[pos], caseSensitive));
+            pos++;
+        }
+        return new FixedStepsPathMatcher(steps, compile(parts, pos, caseSensitive));
+    }
 
-        if (pattern.length() == 0) {
-            return new DefaultPatternMatcher(partialMatchDirs, true);
-        } else {
-            String[] parts = pattern.split("\\\\|/");
-            if (parts.length == 2) {
-                if ("**".equals(parts[0])) {
-                    if ("**".equals(parts[1])) {
-                        // don't need second **
-                        return new DefaultPatternMatcher(partialMatchDirs, caseSensitive, "**");
-                    } else {
-                        // common name only case
-                        return new NameOnlyPatternMatcher(partialMatchDirs, caseSensitive, parts[1]);
-                    }
-                }
+    private static class PathMatcherBackedSpec implements Spec<RelativePath> {
+        private final boolean partialMatchDirs;
+        private final PathMatcher pathMatcher;
+
+        public PathMatcherBackedSpec(boolean partialMatchDirs, PathMatcher pathMatcher) {
+            this.partialMatchDirs = partialMatchDirs;
+            this.pathMatcher = pathMatcher;
+        }
+
+        public boolean isSatisfiedBy(RelativePath element) {
+            if (element.isFile() || !partialMatchDirs) {
+                return pathMatcher.matches(element.getSegments(), 0);
+            } else {
+                return pathMatcher.isPrefix(element.getSegments(), 0);
             }
-            return new DefaultPatternMatcher(partialMatchDirs, caseSensitive, parts);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java
index ddc46af..413fd14 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java
@@ -15,10 +15,6 @@
  */
 package org.gradle.api.internal.file.pattern;
 
-/**
- * @author Steve Appling
- */
 public interface PatternStep {
-    public boolean matches(String candidate, boolean isFile);
-    public boolean isGreedy();
+    public boolean matches(String candidate);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java
index 87d02fc..152038c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java
@@ -16,11 +16,47 @@
 package org.gradle.api.internal.file.pattern;
 
 public class PatternStepFactory {
-    public static PatternStep getStep(String source, boolean isLast, boolean caseSensitive) {
-        if (source.equals("**")) {
-            return new GreedyPatternStep();
-        } else {
-            return new RegExpPatternStep(source, caseSensitive);
+    private static final AnyWildcardPatternStep ANY_WILDCARD_PATTERN_STEP = new AnyWildcardPatternStep();
+
+    public static PatternStep getStep(String source, boolean caseSensitive) {
+        if (source.length() == 0) {
+            return new FixedPatternStep(source, caseSensitive);
+        }
+
+        // Here, we try to avoid using the reg exp backed pattern step, as it is expensive in terms of performance and heap usage.
+        // There are 3 special cases we handle here:
+        // 1. '*'
+        // 2. '*' <literal>
+        // 3. <literal>
+        // Everything else uses a reg exp.
+
+        // Handle '**' and '*some-pattern' special cases
+        char ch = source.charAt(0);
+        if (ch == '*') {
+            int pos = 1;
+            while (pos < source.length() && source.charAt(pos) == '*') {
+                pos++;
+            }
+            if (pos == source.length()) {
+                return ANY_WILDCARD_PATTERN_STEP;
+            }
+            for (int i = pos; i < source.length(); i++) {
+                ch = source.charAt(i);
+                if (ch == '?' || ch == '*') {
+                    // Too complicated - fall back to regexp
+                    return new RegExpPatternStep(source, caseSensitive);
+                }
+            }
+            return new WildcardPrefixPatternStep(source.substring(pos), caseSensitive);
+        }
+
+        for (int i = 0; i < source.length(); i++) {
+            ch = source.charAt(i);
+            if (ch == '?' || ch == '*') {
+                // Too complicated - fall back to regexp
+                return new RegExpPatternStep(source, caseSensitive);
+            }
         }
+        return new FixedPatternStep(source, caseSensitive);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java
index ae80553..b74581c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java
@@ -18,12 +18,8 @@ package org.gradle.api.internal.file.pattern;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * @author Steve Appling
- */
 public class RegExpPatternStep implements PatternStep {
     private static final String ESCAPE_CHARS = "\\[]^-&.{}()$+|<=!";
-    private static final String PATTERN_CHARS = "*?";
 
     private final Pattern pattern;
 
@@ -35,22 +31,23 @@ public class RegExpPatternStep implements PatternStep {
         StringBuilder result = new StringBuilder();
         for (int i=0; i<pattern.length(); i++) {
             char next = pattern.charAt(i);
-            if (ESCAPE_CHARS.indexOf(next) >= 0) {
+            if (next == '*') {
+                result.append(".*");
+            } else if (next == '?') {
+                result.append(".");
+            } else if (ESCAPE_CHARS.indexOf(next) >= 0) {
                 result.append('\\');
-            } else if (PATTERN_CHARS.indexOf(next) >= 0) {
-                result.append('.');
+                result.append(next);
+            } else {
+                result.append(next);
             }
-            result.append(next);
         }
         return result.toString();
     }
 
-    public boolean matches(String testString, boolean isFile) {
+    public boolean matches(String testString) {
         Matcher matcher = pattern.matcher(testString);
         return matcher.matches();
     }
 
-    public boolean isGreedy() {
-        return false;
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/WildcardPrefixPatternStep.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/WildcardPrefixPatternStep.java
new file mode 100644
index 0000000..660ceb8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/pattern/WildcardPrefixPatternStep.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern;
+
+/**
+ * A pattern step for a pattern segment a the common case with a '*' prefix on the pattern. e.g. '*.java'
+ */
+public class WildcardPrefixPatternStep implements PatternStep {
+    private final String suffix;
+    private final boolean caseSensitive;
+    private final int suffixLength;
+
+    public WildcardPrefixPatternStep(String suffix, boolean caseSensitive) {
+        this.suffix = suffix;
+        suffixLength = suffix.length();
+        this.caseSensitive = caseSensitive;
+    }
+
+    public boolean matches(String candidate) {
+        return candidate.regionMatches(!caseSensitive, candidate.length() - suffixLength, suffix, 0, suffixLength);
+    }
+}
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
deleted file mode 100644
index 00bf480..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/AbstractFileStoreEntry.java
+++ /dev/null
@@ -1,28 +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;
-
-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
deleted file mode 100644
index bdf2339..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.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/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
deleted file mode 100644
index 76298a5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
+++ /dev/null
@@ -1,29 +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.HashValue;
-
-import java.io.File;
-
-public interface FileStoreEntry {
-
-    File getFile();
-
-    HashValue getSha1();
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java
deleted file mode 100644
index b23bdfd..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.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.api.internal.filestore;
-
-import java.util.Set;
-
-public interface FileStoreSearcher<S> {
-
-    Set<? extends FileStoreEntry> search(S key);
-
-}
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
index 8127a95..b83a92b 100644
--- 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
@@ -18,7 +18,10 @@ 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 org.gradle.internal.filestore.FileStore;
+import org.gradle.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+import org.gradle.internal.hash.HashUtil;
 
 import java.io.File;
 import java.util.Set;
@@ -38,15 +41,15 @@ public class GroupedAndNamedUniqueFileStore<K> implements FileStore<K>, FileStor
         this.namer = namer;
     }
 
-    public FileStoreEntry move(K key, File source) {
+    public LocallyAvailableResource move(K key, File source) {
         return delegate.move(toPath(key, getChecksum(source)), source);
     }
 
-    public FileStoreEntry copy(K key, File source) {
+    public LocallyAvailableResource copy(K key, File source) {
         return delegate.copy(toPath(key, getChecksum(source)), source);
     }
 
-    public Set<? extends FileStoreEntry> search(K key) {
+    public Set<? extends LocallyAvailableResource> search(K key) {
         return delegate.search(toPath(key, "*"));
     }
 
@@ -69,7 +72,7 @@ public class GroupedAndNamedUniqueFileStore<K> implements FileStore<K>, FileStor
         delegate.moveFilestore(destination);
     }
 
-    public FileStoreEntry add(K key, Action<File> addAction) {
+    public LocallyAvailableResource 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();
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
index e8f0be9..8a2923e 100644
--- 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
@@ -18,12 +18,17 @@ package org.gradle.api.internal.filestore;
 
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
+import org.gradle.api.file.DeleteAction;
 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.MinimalFileTree;
 import org.gradle.api.internal.file.collections.SingleIncludePatternFileTree;
 import org.gradle.api.internal.file.copy.DeleteActionImpl;
+import org.gradle.internal.filestore.FileStore;
+import org.gradle.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.resource.local.AbstractLocallyAvailableResource;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
@@ -54,7 +59,7 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
     public static final String IN_PROGRESS_MARKER_FILE_SUFFIX = ".fslck";
 
     private File baseDir;
-    private final DeleteActionImpl deleteAction = new DeleteActionImpl(new IdentityFileResolver());
+    private final DeleteAction deleteAction = new DeleteActionImpl(new IdentityFileResolver());
 
     public PathKeyFileStore(File baseDir) {
         this.baseDir = baseDir;
@@ -64,11 +69,11 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
         return baseDir;
     }
 
-    public FileStoreEntry move(String path, File source) {
+    public LocallyAvailableResource move(String path, File source) {
         return saveIntoFileStore(source, getFile(path), true);
     }
 
-    public FileStoreEntry copy(String path, File source) {
+    public LocallyAvailableResource copy(String path, File source) {
         return saveIntoFileStore(source, getFile(path), false);
     }
 
@@ -93,12 +98,12 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
         baseDir = destination;
     }
 
-    public FileStoreEntry add(String path, Action<File> addAction) {
+    public LocallyAvailableResource 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) {
+    protected LocallyAvailableResource saveIntoFileStore(final File source, final File destination, final boolean isMove) {
         String verb = isMove ? "move" : "copy";
 
         if (!source.exists()) {
@@ -118,7 +123,7 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
         });
     }
 
-    protected FileStoreEntry doAdd(File destination, String failureDescription, Action<File> action) {
+    protected LocallyAvailableResource doAdd(File destination, String failureDescription, Action<File> action) {
         try {
             GFileUtils.parentMkdirs(destination);
             File inProgressMarkerFile = getInProgressMarkerFile(destination);
@@ -138,12 +143,12 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
         return entryAt(destination);
     }
 
-    public Set<? extends FileStoreEntry> search(String pattern) {
+    public Set<? extends LocallyAvailableResource> search(String pattern) {
         if (!getBaseDir().exists()) {
             return Collections.emptySet();
         }
 
-        final Set<FileStoreEntry> entries = new HashSet<FileStoreEntry>();
+        final Set<LocallyAvailableResource> entries = new HashSet<LocallyAvailableResource>();
         findFiles(pattern).visit(new EmptyFileVisitor() {
             public void visitFile(FileVisitDetails fileDetails) {
                 final File file = fileDetails.getFile();
@@ -174,19 +179,19 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
         return new SingleIncludePatternFileTree(baseDir, pattern);
     }
 
-    protected FileStoreEntry entryAt(File file) {
+    protected LocallyAvailableResource entryAt(File file) {
         return entryAt(GFileUtils.relativePath(baseDir, file));
     }
 
-    protected FileStoreEntry entryAt(final String path) {
-        return new AbstractFileStoreEntry() {
+    protected LocallyAvailableResource entryAt(final String path) {
+        return new AbstractLocallyAvailableResource() {
             public File getFile() {
                 return new File(baseDir, path);
             }
         };
     }
 
-    public FileStoreEntry get(String key) {
+    public LocallyAvailableResource get(String key) {
         final File file = getFileWhileCleaningInProgress(key);
         if (file.exists()) {
             return entryAt(file);
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
index ab03a5a..be47d6e 100644
--- 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
@@ -17,6 +17,9 @@
 package org.gradle.api.internal.filestore;
 
 import org.gradle.api.Action;
+import org.gradle.internal.filestore.FileStore;
+import org.gradle.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 
 import java.io.File;
 import java.util.Set;
@@ -33,11 +36,11 @@ public class PathNormalisingKeyFileStore implements FileStore<String>, FileStore
         this.delegate = delegate;
     }
 
-    public FileStoreEntry move(String key, File source) {
+    public LocallyAvailableResource move(String key, File source) {
         return delegate.move(normalizePath(key), source);
     }
 
-    public FileStoreEntry copy(String key, File source) {
+    public LocallyAvailableResource copy(String key, File source) {
         return delegate.copy(key, source);
     }
 
@@ -53,11 +56,11 @@ public class PathNormalisingKeyFileStore implements FileStore<String>, FileStore
         delegate.moveFilestore(destination);
     }
 
-    public FileStoreEntry add(String key, Action<File> addAction) {
+    public LocallyAvailableResource add(String key, Action<File> addAction) {
         return delegate.add(normalizePath(key), addAction);
     }
 
-    public Set<? extends FileStoreEntry> search(String key) {
+    public Set<? extends LocallyAvailableResource> 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
index 21c759c..4ba34b0 100644
--- 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
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.filestore;
 
 import org.gradle.api.Action;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
@@ -33,8 +34,8 @@ public class UniquePathKeyFileStore extends PathKeyFileStore {
     }
 
     @Override
-    public FileStoreEntry move(String path, File source) {
-        FileStoreEntry entry = super.move(path, source);
+    public LocallyAvailableResource move(String path, File source) {
+        LocallyAvailableResource entry = super.move(path, source);
         if (source.exists()) {
             GFileUtils.deleteQuietly(source);
         }
@@ -42,7 +43,7 @@ public class UniquePathKeyFileStore extends PathKeyFileStore {
     }
 
     @Override
-    protected FileStoreEntry doAdd(File destination, String failureDescription, Action<File> action) {
+    protected LocallyAvailableResource doAdd(File destination, String failureDescription, Action<File> action) {
         if (destination.exists()) {
             return entryAt(destination);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/hash/DefaultHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/hash/DefaultHasher.java
new file mode 100644
index 0000000..173b35f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/hash/DefaultHasher.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.hash;
+
+import org.gradle.internal.hash.HashUtil;
+
+import java.io.File;
+
+public class DefaultHasher implements Hasher {
+    public byte[] hash(File file) {
+        return HashUtil.createHash(file, "MD5").asByteArray();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/hash/Hasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/hash/Hasher.java
new file mode 100644
index 0000000..a44d7b2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/hash/Hasher.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.hash;
+
+import java.io.File;
+
+public interface Hasher {
+    byte[] hash(File file);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
index 27c19f6..b67a303 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
@@ -20,30 +20,28 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.util.ConfigureUtil;
-import org.gradle.util.MutableURLClassLoader;
 
 import java.io.File;
 import java.net.URI;
 
-public abstract class AbstractScriptHandler implements ScriptHandlerInternal {
+public abstract class AbstractScriptHandler implements ScriptHandler {
     private final ScriptSource scriptSource;
     private final RepositoryHandler repositoryHandler;
     private final DependencyHandler dependencyHandler;
     private final ConfigurationContainer configContainer;
-    private final MutableURLClassLoader classLoader;
     private final Configuration classpathConfiguration;
 
-    public AbstractScriptHandler(MutableURLClassLoader classLoader, RepositoryHandler repositoryHandler,
+    public AbstractScriptHandler(RepositoryHandler repositoryHandler,
                                  DependencyHandler dependencyHandler, ScriptSource scriptSource,
                                  ConfigurationContainer configContainer) {
-        this.classLoader = classLoader;
         this.repositoryHandler = repositoryHandler;
         this.dependencyHandler = dependencyHandler;
         this.scriptSource = scriptSource;
         this.configContainer = configContainer;
-        classpathConfiguration = configContainer.add(CLASSPATH_CONFIGURATION);
+        classpathConfiguration = configContainer.create(CLASSPATH_CONFIGURATION);
     }
 
     public void dependencies(Closure configureClosure) {
@@ -70,10 +68,6 @@ public abstract class AbstractScriptHandler implements ScriptHandlerInternal {
         return configContainer;
     }
 
-    public MutableURLClassLoader getClassLoader() {
-        return classLoader;
-    }
-
     public File getSourceFile() {
         return scriptSource.getResource().getFile();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ClassLoaderCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ClassLoaderCache.java
new file mode 100644
index 0000000..c7bf7e6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ClassLoaderCache.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.classloader.FilteringClassLoader;
+import org.gradle.internal.classpath.ClassPath;
+
+public interface ClassLoaderCache {
+
+    ClassLoader get(ClassLoader parent, ClassPath classPath, @Nullable FilteringClassLoader.Spec filterSpec);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ClassLoaderScope.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ClassLoaderScope.java
new file mode 100644
index 0000000..8cceee6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ClassLoaderScope.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import org.gradle.internal.classpath.ClassPath;
+
+/**
+ * Represents a particular node in the ClassLoader graph.
+ *
+ * Certain domain objects (e.g. Gradle, Settings, Project) have an associated class loader scope.
+ * This is used for evaluating associated scripts and script plugins.
+ *
+ * Use of this class allows class loader creation to be lazy, and potentially optimised.
+ * It also provides a central location for class loader reuse.
+ */
+public interface ClassLoaderScope {
+
+    /**
+     * The effective class loader for this scope.
+     *
+     * It is strongly preferable to only call this after {@link #lock()}ing the scope as it allows the structure to be optimized.
+     */
+    ClassLoader getScopeClassLoader();
+
+    /**
+     * The class loader that children inherit.
+     */
+    ClassLoader getChildClassLoader();
+
+    /**
+     * The base scope defines the parent for local additions.
+     */
+    ClassLoaderScope getBase();
+
+    /**
+     * Adds a class path to this scope, but not to children.
+     *
+     * Can not be called after being locked.
+     */
+    ClassLoader addLocal(ClassPath classpath);
+
+    /**
+     * Adds a class path to this scope, but not to children.
+     *
+     * Can not be called after being locked.
+     */
+    ClassLoader export(ClassPath classpath);
+
+    /**
+     * Creates a scope with the same parent as this scope.
+     */
+    ClassLoaderScope createSibling();
+
+    /**
+     * Creates a scope with the same parent as this scope.
+     *
+     * Local class paths added to the return child will NOT inherit from the exported classpath of this and parents (though exported class paths will)
+     */
+    ClassLoaderScope createChild();
+
+    /**
+     * Creates a scope with the same parent as this scope.
+     *
+     * Local class paths added to the return child WILL inherit from the exported classpath of this and parents (exported class paths also will)
+     */
+    ClassLoaderScope createRebasedChild();
+
+    /**
+     * Signal that no more modifications are to come, allowing the structure to be optimised if possible.
+     */
+    ClassLoaderScope lock();
+
+    boolean isLocked();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderCache.java
new file mode 100644
index 0000000..a2e2d84
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderCache.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.gradle.api.Nullable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.classloader.FilteringClassLoader;
+import org.gradle.internal.classpath.ClassPath;
+
+import java.net.URLClassLoader;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+public class DefaultClassLoaderCache implements ClassLoaderCache {
+
+    public static class Key {
+        private final ClassLoader parent;
+        private final ClassPath classPath;
+        private final FilteringClassLoader.Spec filterSpec;
+
+        private Key(ClassLoader parent, ClassPath classPath, FilteringClassLoader.Spec filterSpec) {
+            this.parent = parent;
+            this.classPath = classPath;
+            this.filterSpec = filterSpec;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Key key = (Key) o;
+
+            if (!classPath.equals(key.classPath)) {
+                return false;
+            }
+            if (filterSpec != null ? !filterSpec.equals(key.filterSpec) : key.filterSpec != null) {
+                return false;
+            }
+            if (!parent.equals(key.parent)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = parent.hashCode();
+            result = 31 * result + classPath.hashCode();
+            result = 31 * result + (filterSpec != null ? filterSpec.hashCode() : 0);
+            return result;
+        }
+    }
+
+    private final Cache<Key, ClassLoader> cache;
+
+    public DefaultClassLoaderCache() {
+        this(CacheBuilder.newBuilder().<Key, ClassLoader>build());
+    }
+
+    public DefaultClassLoaderCache(Cache<Key, ClassLoader> cache) {
+        this.cache = cache;
+    }
+
+    public ClassLoader get(final ClassLoader parent, final ClassPath classPath, @Nullable final FilteringClassLoader.Spec filterSpec) {
+        try {
+            return cache.get(new Key(parent, classPath, filterSpec), new Callable<ClassLoader>() {
+                public ClassLoader call() throws Exception {
+                    if (filterSpec == null) {
+                        return new URLClassLoader(classPath.getAsURLArray(), parent);
+                    } else {
+                        return new FilteringClassLoader(get(parent, classPath, null), filterSpec);
+                    }
+                }
+            });
+        } catch (ExecutionException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderScope.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderScope.java
new file mode 100644
index 0000000..502bfb9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderScope.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import org.gradle.internal.classloader.CachingClassLoader;
+import org.gradle.internal.classloader.MultiParentClassLoader;
+import org.gradle.internal.classpath.ClassPath;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultClassLoaderScope implements ClassLoaderScope {
+
+    public static final String STRICT_MODE_PROPERTY = "org.gradle.classloaderscope.strict";
+
+    private final ClassLoaderScope parent;
+    private final ClassLoaderScope base;
+    private final ClassLoaderCache classLoaderCache;
+
+    private boolean locked;
+
+    private List<ClassLoader> local;
+
+    private ClassLoader scopeClassLoader;
+
+    private MultiParentClassLoader exportingClassLoader;
+    private MultiParentClassLoader localClassLoader;
+    private ClassLoader childrenParent;
+
+    public DefaultClassLoaderScope(ClassLoaderScope parent, ClassLoaderScope base, ClassLoaderCache classLoaderCache) {
+        this.parent = parent;
+        this.base = base;
+        this.classLoaderCache = classLoaderCache;
+    }
+
+    public ClassLoader getChildClassLoader() {
+        getScopeClassLoader(); // trigger calculation
+        return childrenParent;
+    }
+
+    public ClassLoaderScope getBase() {
+        return base;
+    }
+
+    public ClassLoader getScopeClassLoader() {
+        if (scopeClassLoader == null) {
+            if (locked) {
+                if (local == null && exportingClassLoader == null) { // best case, no additions
+                    scopeClassLoader = parent.getChildClassLoader();
+                    childrenParent = scopeClassLoader;
+                } else if (exportingClassLoader == null) { // no impact on children
+                    localClassLoader = new MultiParentClassLoader(parent.getChildClassLoader());
+                    scopeClassLoader = new CachingClassLoader(localClassLoader);
+                    childrenParent = parent.getChildClassLoader();
+                } else if (local == null) {
+                    scopeClassLoader = exportingClassLoader;
+                    childrenParent = scopeClassLoader;
+                } else {
+                    createFlexibleLoaderStructure();
+                }
+            } else { // creating before locking, have to create the most flexible setup
+                if (Boolean.getBoolean(STRICT_MODE_PROPERTY)) {
+                    throw new IllegalStateException("Attempt to define scope class loader before scope is locked");
+                }
+
+                createFlexibleLoaderStructure();
+            }
+
+            if (local != null) {
+                for (ClassLoader localClassLoader : local) {
+                    addLocal(localClassLoader);
+                }
+            }
+        }
+
+        return scopeClassLoader;
+    }
+
+    private void addLocal(ClassLoader newClassLoader) {
+        assert localClassLoader != null;
+        localClassLoader.addParent(newClassLoader);
+    }
+
+    private void createFlexibleLoaderStructure() {
+        if (exportingClassLoader == null) {
+            exportingClassLoader = new MultiParentClassLoader(parent.getChildClassLoader());
+        }
+
+        localClassLoader = new MultiParentClassLoader(exportingClassLoader);
+        scopeClassLoader = new CachingClassLoader(localClassLoader);
+        childrenParent = exportingClassLoader;
+    }
+
+    public ClassLoader addLocal(ClassPath classpath) {
+        if (locked) {
+            throw new IllegalStateException("class loader scope is locked");
+        }
+        if (!classpath.isEmpty()) {
+            if (local == null) {
+                local = new ArrayList<ClassLoader>(1);
+            }
+
+            ClassLoader newClassLoader = classLoaderCache.get(base.getChildClassLoader(), classpath, null);
+            local.add(newClassLoader);
+
+            if (localClassLoader != null) { // classloader was eagerly created, have to add
+                addLocal(newClassLoader);
+            }
+
+            return newClassLoader;
+        } else {
+            return base.getChildClassLoader();
+        }
+    }
+
+    public ClassLoader export(ClassPath classpath) {
+        if (locked) {
+            throw new IllegalStateException("class loader scope is locked");
+        }
+        if (classpath.isEmpty()) {
+            return parent.getChildClassLoader();
+        } else {
+            if (exportingClassLoader == null) {
+                exportingClassLoader = new MultiParentClassLoader();
+            }
+
+            ClassLoader classLoader = classLoaderCache.get(parent.getChildClassLoader(), classpath, null);
+            exportingClassLoader.addParent(classLoader);
+            return classLoader;
+        }
+    }
+
+    public ClassLoaderScope createSibling() {
+        return new DefaultClassLoaderScope(parent, base, classLoaderCache);
+    }
+
+    public ClassLoaderScope createChild() {
+        return new DefaultClassLoaderScope(this, base, classLoaderCache);
+    }
+
+    public ClassLoaderScope createRebasedChild() {
+        return new DefaultClassLoaderScope(this, this, classLoaderCache);
+    }
+
+    public ClassLoaderScope lock() {
+        locked = true;
+        return this;
+    }
+
+    public boolean isLocked() {
+        return locked;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
index a801482..378d252 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
@@ -15,30 +15,75 @@
  */
 package org.gradle.api.internal.initialization;
 
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.MutableURLClassLoader;
+import org.gradle.internal.Factory;
+import org.gradle.util.ConfigureUtil;
 
 import java.io.File;
-import java.net.MalformedURLException;
+import java.net.URI;
 
-public class DefaultScriptHandler extends AbstractScriptHandler {
+public class DefaultScriptHandler implements ScriptHandler {
 
-    public DefaultScriptHandler(ScriptSource scriptSource, RepositoryHandler repositoryHandler,
-                                DependencyHandler dependencyHandler, ConfigurationContainer configContainer,
-                                MutableURLClassLoader classLoader) {
-        super(classLoader, repositoryHandler, dependencyHandler, scriptSource, configContainer);
+    private final ScriptSource scriptSource;
+    private final RepositoryHandler repositoryHandler;
+    private final DependencyHandler dependencyHandler;
+    private final ConfigurationContainer configContainer;
+    private final Factory<ClassLoader> classLoaderFactory;
+    private final Configuration classpathConfiguration;
+
+    public DefaultScriptHandler(
+            ScriptSource scriptSource, RepositoryHandler repositoryHandler,
+            DependencyHandler dependencyHandler, ConfigurationContainer configContainer,
+            Factory<ClassLoader> classLoaderFactory
+    ) {
+        this.repositoryHandler = repositoryHandler;
+        this.dependencyHandler = dependencyHandler;
+        this.scriptSource = scriptSource;
+        this.configContainer = configContainer;
+        this.classLoaderFactory = classLoaderFactory;
+        classpathConfiguration = configContainer.create(CLASSPATH_CONFIGURATION);
+    }
+
+    public void dependencies(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, dependencyHandler);
+    }
+
+    protected Configuration getClasspathConfiguration() {
+        return classpathConfiguration;
+    }
+
+    public DependencyHandler getDependencies() {
+        return dependencyHandler;
+    }
+
+    public RepositoryHandler getRepositories() {
+        return repositoryHandler;
     }
 
-    public void updateClassPath() {
-        for (File file : getClasspathConfiguration().getFiles()) {
-            try {
-                getClassLoader().addURL(file.toURI().toURL());
-            } catch (MalformedURLException e) {
-                throw new RuntimeException(e);
-            }
-        }
+    public void repositories(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, repositoryHandler);
     }
+
+    public ConfigurationContainer getConfigurations() {
+        return configContainer;
+    }
+
+    public File getSourceFile() {
+        return scriptSource.getResource().getFile();
+    }
+
+    public URI getSourceURI() {
+        return scriptSource.getResource().getURI();
+    }
+
+    public ClassLoader getClassLoader() {
+        return classLoaderFactory.create();
+    }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
index da94f98..05e64a3 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
@@ -20,6 +20,7 @@ import org.gradle.api.UnknownProjectException;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.api.internal.artifacts.DependencyManagementServices;
 import org.gradle.api.internal.artifacts.DependencyResolutionServices;
@@ -28,17 +29,10 @@ import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.MutableURLClassLoader;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 
 public class DefaultScriptHandlerFactory implements ScriptHandlerFactory {
     private final DependencyManagementServices dependencyManagementServices;
     private final DependencyMetaDataProvider dependencyMetaDataProvider;
-    private final Map<Collection<Object>, MutableURLClassLoader> classLoaderCache = new HashMap<Collection<Object>, MutableURLClassLoader>();
     private final FileResolver fileResolver;
     private final ProjectFinder projectFinder = new ProjectFinder() {
         public ProjectInternal getProject(String path) {
@@ -54,24 +48,16 @@ public class DefaultScriptHandlerFactory implements ScriptHandlerFactory {
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
     }
 
-    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader) {
-        return create(scriptSource, parentClassLoader, new BasicDomainObjectContext());
+    public ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope) {
+        return create(scriptSource, classLoaderScope, new BasicDomainObjectContext());
     }
 
-    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader, DomainObjectContext context) {
+    public ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope, DomainObjectContext context) {
         DependencyResolutionServices services = dependencyManagementServices.create(fileResolver, dependencyMetaDataProvider, projectFinder, context);
         RepositoryHandler repositoryHandler = services.getResolveRepositoryHandler();
         ConfigurationContainer configurationContainer = services.getConfigurationContainer();
         DependencyHandler dependencyHandler = services.getDependencyHandler();
-        Collection<Object> key = Arrays.asList(scriptSource.getClassName(), parentClassLoader);
-        MutableURLClassLoader classLoader = classLoaderCache.get(key);
-        if (classLoader == null) {
-            classLoader = new MutableURLClassLoader(parentClassLoader);
-            classLoaderCache.put(key, classLoader);
-            return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoader);
-        }
-
-        return new NoClassLoaderUpdateScriptHandler(classLoader, repositoryHandler, dependencyHandler, scriptSource, configurationContainer);
+        return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, new ScriptHandlerClassLoaderFactory(scriptSource, classLoaderScope));
     }
 
     private static class BasicDomainObjectContext implements DomainObjectContext {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
deleted file mode 100644
index 5a596f6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.initialization;
-
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.MutableURLClassLoader;
-
-public class NoClassLoaderUpdateScriptHandler extends AbstractScriptHandler {
-    public NoClassLoaderUpdateScriptHandler(MutableURLClassLoader classLoader, RepositoryHandler repositoryHandler,
-                                            DependencyHandler dependencyHandler, ScriptSource scriptSource,
-                                            ConfigurationContainer configContainer) {
-        super(classLoader, repositoryHandler, dependencyHandler, scriptSource, configContainer);
-    }
-
-    public void updateClassPath() {
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/RootClassLoaderScope.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/RootClassLoaderScope.java
new file mode 100644
index 0000000..3fe14b9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/RootClassLoaderScope.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import org.gradle.internal.classpath.ClassPath;
+
+public class RootClassLoaderScope implements ClassLoaderScope {
+
+    private final ClassLoader classLoader;
+    private final ClassLoaderCache classLoaderCache;
+
+    public RootClassLoaderScope(ClassLoader classLoader, ClassLoaderCache classLoaderCache) {
+        this.classLoader = classLoader;
+        this.classLoaderCache = classLoaderCache;
+    }
+
+    public ClassLoader getScopeClassLoader() {
+        return classLoader;
+    }
+
+    public ClassLoader getChildClassLoader() {
+        return classLoader;
+    }
+
+    public ClassLoaderScope getBase() {
+        return this;
+    }
+
+    public ClassLoader addLocal(ClassPath classpath) {
+        throw new UnsupportedOperationException("root class loader scope is immutable");
+    }
+
+    public ClassLoader export(ClassPath classpath) {
+        throw new UnsupportedOperationException("root class loader scope is immutable");
+    }
+
+    public ClassLoaderScope createSibling() {
+        return createChild();
+    }
+
+    public ClassLoaderScope createChild() {
+        return new DefaultClassLoaderScope(this, this, classLoaderCache);
+    }
+
+    public ClassLoaderScope createRebasedChild() {
+        return createChild();
+    }
+
+    public ClassLoaderScope lock() {
+        return this;
+    }
+
+    public boolean isLocked() {
+        return true;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoader.java
new file mode 100644
index 0000000..110f376
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoader.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import org.gradle.internal.classloader.MultiParentClassLoader;
+import org.gradle.internal.classloader.MutableURLClassLoader;
+
+public class ScriptClassLoader extends MultiParentClassLoader {
+
+    private final MutableURLClassLoader mutableClassLoader;
+    private final ClassLoader parent;
+
+    public ScriptClassLoader(ClassLoader parent) {
+        super(parent);
+        this.parent = parent;
+        this.mutableClassLoader = new MutableURLClassLoader(parent);
+        addParent(mutableClassLoader);
+    }
+
+    public MutableURLClassLoader getMutableClassLoader() {
+        return mutableClassLoader;
+    }
+
+    public ClassLoader getParentLoader() {
+        return parent;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoaderProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoaderProvider.java
deleted file mode 100644
index 94c9bab..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoaderProvider.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.initialization;
-
-public interface ScriptClassLoaderProvider {
-    ClassLoader getClassLoader();
-
-    void updateClassPath();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerClassLoaderFactory.java
new file mode 100644
index 0000000..0dc4fc7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerClassLoaderFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.Factory;
+
+public class ScriptHandlerClassLoaderFactory implements Factory<ClassLoader> {
+
+    private static final Logger LOGGER = Logging.getLogger(ScriptHandlerClassLoaderFactory.class);
+
+    private final ScriptSource scriptSource;
+    private final ClassLoaderScope classLoaderScope;
+
+    public ScriptHandlerClassLoaderFactory(ScriptSource scriptSource, ClassLoaderScope classLoaderScope) {
+        this.scriptSource = scriptSource;
+        this.classLoaderScope = classLoaderScope;
+    }
+
+    public ClassLoader create() {
+        if (!classLoaderScope.isLocked()) {
+            LOGGER.debug("Eager creation of script class loader for {}. This may result in performance issues.", scriptSource);
+        }
+        return classLoaderScope.getScopeClassLoader();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java
index 46f5009..3d5cb97 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java
@@ -16,11 +16,12 @@
 
 package org.gradle.api.internal.initialization;
 
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.groovy.scripts.ScriptSource;
 
 public interface ScriptHandlerFactory {
-    ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader);
+    ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope);
 
-    ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader, DomainObjectContext context);
+    ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope, DomainObjectContext context);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java
deleted file mode 100755
index 6851bc6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java
+++ /dev/null
@@ -1,21 +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.initialization;
-
-import org.gradle.api.initialization.dsl.ScriptHandler;
-
-public interface ScriptHandlerInternal extends ScriptHandler, ScriptClassLoaderProvider {
-}
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
deleted file mode 100644
index 94906af..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/NotationParserBuilder.java
+++ /dev/null
@@ -1,84 +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.notations;
-
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.parsers.CompositeNotationParser;
-import org.gradle.api.internal.notations.parsers.ErrorHandlingNotationParser;
-import org.gradle.api.internal.notations.parsers.FlatteningNotationParser;
-import org.gradle.api.internal.notations.parsers.JustReturningParser;
-import org.gradle.util.GUtil;
-
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * by Szczepan Faber, created at: 11/8/11
- */
-public class NotationParserBuilder<T> {
-    private 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;
-    }
-
-    public NotationParserBuilder<T> parser(NotationParser<? extends T> parser) {
-        this.notationParsers.add(parser);
-        return this;
-    }
-
-    public NotationParserBuilder<T> invalidNotationMessage(String invalidNotationMessage) {
-        this.invalidNotationMessage = invalidNotationMessage;
-        return this;
-    }
-
-    public NotationParserBuilder<T> parsers(Iterable<? extends NotationParser<? extends T>> notationParsers) {
-        GUtil.addToCollection(this.notationParsers, notationParsers);
-        return this;
-    }
-
-    public NotationParser<Set<T>> toFlatteningComposite() {
-        return wrapInErrorHandling(new FlatteningNotationParser<T>(create()));
-    }
-
-    public NotationParser<T> toComposite() {
-        return wrapInErrorHandling(create());
-    }
-
-    private <S> NotationParser<S> wrapInErrorHandling(NotationParser<S> parser) {
-        return new ErrorHandlingNotationParser<S>(resultingType.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.getTargetType()));
-        composites.addAll(this.notationParsers);
-
-        return new CompositeNotationParser<T>(composites);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/TypeInfo.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/TypeInfo.java
deleted file mode 100644
index 89993b5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/TypeInfo.java
+++ /dev/null
@@ -1,35 +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.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
deleted file mode 100644
index e93ab7a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/NotationParser.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.api.internal.notations.api;
-
-import java.util.Collection;
-
-/**
- * by Szczepan Faber, created at: 11/8/11
- */
-public interface NotationParser<T> {
-    T parseNotation(Object notation) throws UnsupportedNotationException;
-
-    void describe(Collection<String> candidateFormats);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/UnsupportedNotationException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/UnsupportedNotationException.java
deleted file mode 100644
index 5fcd33a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/UnsupportedNotationException.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.api.internal.notations.api;
-
-public class UnsupportedNotationException extends RuntimeException {
-    private final Object notation;
-
-    public UnsupportedNotationException(Object notation) {
-        this.notation = notation;
-    }
-
-    public Object getNotation() {
-        return notation;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParser.java
deleted file mode 100644
index c8e74e2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParser.java
+++ /dev/null
@@ -1,28 +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.notations.parsers;
-
-public class CharSequenceNotationParser extends TypedNotationParser<CharSequence, String> {
-    public CharSequenceNotationParser() {
-        super(CharSequence.class);
-    }
-
-    @Override
-    protected String parseType(CharSequence notation) {
-        return notation.toString();
-    }
-}
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
deleted file mode 100644
index 6f9cefd..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParser.java
+++ /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.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/notations/parsers/CompositeNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
deleted file mode 100644
index db00741..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers;
-
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-
-import java.util.Collection;
-
-/**
- * by Szczepan Faber, created at: 11/10/11
- */
-public class CompositeNotationParser<T> implements NotationParser<T> {
-
-    private final Collection<NotationParser<? extends T>> delegates;
-
-    public CompositeNotationParser(Collection<NotationParser<? extends T>> delegates) {
-        assert delegates != null : "delegates cannot be null!";
-        this.delegates = delegates;
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        for (NotationParser<? extends T> delegate : delegates) {
-            delegate.describe(candidateFormats);
-        }
-    }
-
-    public T parseNotation(Object notation) {
-        for (NotationParser<? extends T> delegate : delegates) {
-            try {
-                return delegate.parseNotation(notation);
-            } catch (UnsupportedNotationException e) {
-                // Ignore
-            }
-        }
-
-        throw new UnsupportedNotationException(notation);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
deleted file mode 100644
index 527c1d4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
+++ /dev/null
@@ -1,71 +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.notations.parsers;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-import org.gradle.util.GUtil;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Formatter;
-import java.util.List;
-
-/**
- * by Szczepan Faber, created at: 11/8/11
- */
-public class ErrorHandlingNotationParser<T> implements NotationParser<T> {
-    private final String targetTypeDisplayName;
-    private final String invalidNotationMessage;
-    private final NotationParser<T> delegate;
-
-    public ErrorHandlingNotationParser(String targetTypeDisplayName, String invalidNotationMessage, NotationParser<T> delegate) {
-        this.targetTypeDisplayName = targetTypeDisplayName;
-        this.invalidNotationMessage = invalidNotationMessage;
-        this.delegate = delegate;
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        delegate.describe(candidateFormats);
-    }
-
-    public T parseNotation(Object notation) {
-        Formatter message = new Formatter();
-        if (notation == null) {
-            //we don't support null input at the moment. If you need this please implement it.
-            message.format("Cannot convert a null value to an object of type %s.%n", targetTypeDisplayName);
-        } else {
-            try {
-                return delegate.parseNotation(notation);
-            } catch (UnsupportedNotationException e) {
-                message.format("Cannot convert the provided notation to an object of type %s: %s.%n", targetTypeDisplayName, e.getNotation());
-            }
-        }
-
-        message.format("The following types/formats are supported:");
-        List<String> formats = new ArrayList<String>();
-        describe(formats);
-        for (String format : formats) {
-            message.format("%n  - %s", format);
-        }
-        if (GUtil.isTrue(invalidNotationMessage)) {
-            message.format("%n%s", invalidNotationMessage);
-        }
-        throw new InvalidUserDataException(message.toString());
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/FlatteningNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/FlatteningNotationParser.java
deleted file mode 100644
index c962462..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/FlatteningNotationParser.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers;
-
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.util.GUtil;
-
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * Flattens or collectionizes input and passes the input notations to the delegates. Returns a set.
- */
-public class FlatteningNotationParser<T> implements NotationParser<Set<T>> {
-
-    private final NotationParser<T> delegate;
-
-    public FlatteningNotationParser(NotationParser<T> delegate) {
-        assert delegate != null : "delegate cannot be null";
-        this.delegate = delegate;
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        delegate.describe(candidateFormats);
-        candidateFormats.add("Collections or arrays of any other supported format. Nested collections/arrays will be flattened.");
-    }
-
-    public Set<T> parseNotation(Object notation) {
-        Set<T> out = new LinkedHashSet<T>();
-        Collection notations = GUtil.collectionize(notation);
-        for (Object n : notations) {
-            out.add(delegate.parseNotation(n));
-        }
-        return out;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/JustReturningParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/JustReturningParser.java
deleted file mode 100644
index 7b7efc6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/JustReturningParser.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.api.internal.notations.parsers;
-
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-
-import java.util.Collection;
-
-/**
- * by Szczepan Faber, created at: 11/8/11
- */
-public class JustReturningParser<T> implements NotationParser<T> {
-
-    private final Class<T> passThroughType;
-
-    public JustReturningParser(Class<T> passThroughType) {
-        this.passThroughType = passThroughType;
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        candidateFormats.add(String.format("Instances of %s.", passThroughType.getSimpleName()));
-    }
-
-    public T parseNotation(Object notation) {
-        if (!passThroughType.isInstance(notation)) {
-            throw new UnsupportedNotationException(notation);
-        }
-        return passThroughType.cast(notation);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapKey.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapKey.java
deleted file mode 100644
index d23326d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapKey.java
+++ /dev/null
@@ -1,28 +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.notations.parsers;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.PARAMETER)
-public @interface MapKey {
-    String value();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
deleted file mode 100644
index 834cbeb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
+++ /dev/null
@@ -1,133 +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.notations.parsers;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-import org.gradle.api.tasks.Optional;
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.ConfigureUtil;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.*;
-
-/**
- * Converts a {@code Map<String, Object>} to the target type. Subclasses should define a {@code T parseMap()} method which takes a parameter 
- * for each key value required from the source map. Each parameter should be annotated with a {@code @MapKey} annotation, and can also
- * be annotated with a {@code @optional} annotation.
- */
-public abstract class MapNotationParser<T> extends TypedNotationParser<Map, T> implements NotationParser<T> {
-    private final Method convertMethod;
-    private final String[] keyNames;
-    private final boolean[] optional;
-
-    public MapNotationParser() {
-        super(Map.class);
-        convertMethod = findConvertMethod();
-        keyNames = new String[convertMethod.getParameterAnnotations().length];
-        optional = new boolean[convertMethod.getParameterAnnotations().length];
-        for (int i = 0; i < convertMethod.getParameterAnnotations().length; i++) {
-            Annotation[] annotations = convertMethod.getParameterAnnotations()[i];
-            keyNames[i] = keyName(annotations);
-            optional[i] = optional(annotations);
-        }
-    }
-
-    private Method findConvertMethod() {
-        for (Method method : getClass().getDeclaredMethods()) {
-            if (method.getName().equals("parseMap")) {
-                method.setAccessible(true);
-                return method;
-            }
-        }
-        throw new UnsupportedOperationException(String.format("No parseMap() method found on class %s.", getClass().getSimpleName()));
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        candidateFormats.add("Maps");
-    }
-
-    public T parseType(Map values) throws UnsupportedNotationException {
-        Map<String, Object> mutableValues = new HashMap<String, Object>(values);
-        Set<String> missing = new TreeSet<String>();
-
-        Object[] params = new Object[convertMethod.getParameterTypes().length];
-        for (int i = 0; i < params.length; i++) {
-            String keyName = keyNames[i];
-            boolean optional = this.optional[i];
-            Class<?> type = convertMethod.getParameterTypes()[i];
-            Object value;
-            if (type.equals(String.class)) {
-                value = get(mutableValues, keyName);
-            } else {
-                value = type.cast(mutableValues.get(keyName));
-            }
-            if (!optional && value == null) {
-                missing.add(keyName);
-            }
-            mutableValues.remove(keyName);
-            params[i] = value;
-        }
-        if (!missing.isEmpty()) {
-            //TODO SF below could be better.
-            //Throwing InvalidUserDataException here means that useful context information (including candidate formats, etc.) is not presented to the user
-            throw new InvalidUserDataException(String.format("Required keys %s are missing from map %s.", missing, values));
-        }
-
-        T result;
-        try {
-            result = (T) convertMethod.invoke(this, params);
-        } catch (IllegalAccessException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.unwrapAndRethrow(e);
-        }
-
-        ConfigureUtil.configureByMap(mutableValues, result);
-        return result;
-    }
-
-    private boolean optional(Annotation[] annotations) {
-        for (Annotation annotation : annotations) {
-            if (annotation instanceof Optional) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private String keyName(Annotation[] annotations) {
-        for (Annotation annotation : annotations) {
-            if (annotation instanceof MapKey) {
-                return ((MapKey) annotation).value();
-            }
-        }
-        throw new UnsupportedOperationException("No @Key annotation on parameter of parseMap() method");
-    }
-
-    protected String get(Map<String, Object> args, String key) {
-        Object value = args.get(key);
-        String str = value != null ? value.toString() : null;
-        if (str != null && str.length() == 0) {
-            return null;
-        }
-        return str;
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/NormalizedTimeUnit.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/NormalizedTimeUnit.java
deleted file mode 100644
index 0cf3327..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/NormalizedTimeUnit.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * by Szczepan Faber, created at: 2/18/13
- */
-public class NormalizedTimeUnit {
-
-    private int value;
-    private TimeUnit timeUnit;
-
-    public NormalizedTimeUnit(int value, TimeUnit timeUnit) {
-        this.value = value;
-        this.timeUnit = timeUnit;
-    }
-
-    public static NormalizedTimeUnit millis(int value) {
-        return new NormalizedTimeUnit(value, TimeUnit.MILLISECONDS);
-    }
-
-    public int getValue() {
-        return value;
-    }
-
-    public TimeUnit getTimeUnit() {
-        return timeUnit;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParser.java
deleted file mode 100644
index e37616f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParser.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers;
-
-import org.gradle.api.InvalidUserDataException;
-
-import java.util.concurrent.TimeUnit;
-
-import static org.gradle.api.internal.notations.parsers.NormalizedTimeUnit.millis;
-
-/**
- * by Szczepan Faber, created at: 2/12/13
- */
-public class TimeUnitsParser {
-
-    public NormalizedTimeUnit parseNotation(CharSequence notation, int value) {
-        String candidate = notation.toString().toUpperCase();
-        //jdk5 does not have days, hours or minutes, normalizing to millis
-        if (candidate.equals("DAYS")) {
-            return millis(value * 24 * 60 * 60 * 1000);
-        } else if (candidate.equals("HOURS")) {
-            return millis(value * 60 * 60 * 1000);
-        } else if (candidate.equals("MINUTES")) {
-            return millis(value * 60 * 1000);
-        }
-        try {
-            return new NormalizedTimeUnit(value, TimeUnit.valueOf(candidate));
-        } catch (Exception e) {
-            throw new InvalidUserDataException("Unable to parse provided TimeUnit: " + notation, e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParser.java
deleted file mode 100644
index 627c6a5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParser.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.notations.parsers;
-
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-
-import java.util.Collection;
-
-/**
- * by Szczepan Faber, created at: 11/9/11
- */
-public abstract class TypedNotationParser<N, T> implements NotationParser<T> {
-
-    private final Class<N> typeToken;
-
-    public TypedNotationParser(Class<N> typeToken) {
-        assert typeToken != null : "typeToken cannot be null";
-        this.typeToken = typeToken;
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        candidateFormats.add(String.format("Instances of %s.", typeToken.getSimpleName()));
-    }
-
-    public T parseNotation(Object notation) {
-        if (!typeToken.isInstance(notation)) {
-            throw new UnsupportedNotationException(notation);
-        }
-        return parseType(typeToken.cast(notation));
-    }
-
-    abstract protected T parseType(N notation);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ClassloaderBackedPluginDescriptorLocator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ClassloaderBackedPluginDescriptorLocator.java
new file mode 100644
index 0000000..81544f3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ClassloaderBackedPluginDescriptorLocator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.net.URL;
+
+public class ClassloaderBackedPluginDescriptorLocator implements PluginDescriptorLocator {
+
+    private final ClassLoader classLoader;
+
+    public ClassloaderBackedPluginDescriptorLocator(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public PluginDescriptor findPluginDescriptor(String pluginId) {
+        URL resource = classLoader.getResource(String.format("META-INF/gradle-plugins/%s.properties", pluginId));
+        if (resource == null) {
+            return null;
+        } else {
+            return new PluginDescriptor(resource);
+        }
+    }
+
+}
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 5584d4c..420749d 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
@@ -22,17 +22,14 @@ import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.BeanDynamicObject;
 import org.gradle.api.internal.DynamicObject;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtraPropertiesExtension;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.DeprecationLogger;
 
 import java.util.*;
 
-/**
- * @author Hans Dockter
- */
-public class DefaultConvention implements Convention {
+public class DefaultConvention implements Convention, ExtensionContainerInternal {
 
     private final Map<String, Object> plugins = new LinkedHashMap<String, Object>();
     private final DefaultConvention.ExtensionsDynamicObject extensionsDynamicObject = new ExtensionsDynamicObject();
@@ -41,7 +38,7 @@ public class DefaultConvention implements Convention {
     private final Instantiator instantiator;
 
     /**
-     * This method should be used in runtime code proper as means that the convention cannot create
+     * This method should not be used in runtime code proper as means that the convention cannot create
      * dynamic extensions.
      *
      * It's here for backwards compatibility with our tests and for convenience.
@@ -141,6 +138,10 @@ public class DefaultConvention implements Convention {
         extensionsStorage.configureExtension(type, action);
     }
 
+    public Map<String, Object> getAsMap() {
+        return extensionsStorage.getAsMap();
+    }
+
     public Object propertyMissing(String name) {
         return getByName(name);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
index 31f7524..a703b0e 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
@@ -17,11 +17,15 @@
 package org.gradle.api.internal.plugins;
 
 import org.gradle.api.Plugin;
-import org.gradle.api.Project;
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.plugins.ObjectConfigurationAction;
+import org.gradle.api.plugins.PluginAware;
 import org.gradle.configuration.ScriptPlugin;
 import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.groovy.scripts.DefaultScript;
 import org.gradle.groovy.scripts.UriScriptSource;
 import org.gradle.util.GUtil;
 
@@ -32,14 +36,23 @@ import java.util.Set;
 public class DefaultObjectConfigurationAction implements ObjectConfigurationAction {
     private final FileResolver resolver;
     private final ScriptPluginFactory configurerFactory;
+    private final ScriptHandlerFactory scriptHandlerFactory;
     private final Set<Object> targets = new LinkedHashSet<Object>();
     private final Set<Runnable> actions = new LinkedHashSet<Runnable>();
+    private final ClassLoaderScope classLoaderScope;
     private final Object[] defaultTargets;
 
-    public DefaultObjectConfigurationAction(FileResolver resolver, ScriptPluginFactory configurerFactory,
-                                            Object... defaultTargets) {
+    public DefaultObjectConfigurationAction(
+            FileResolver resolver,
+            ScriptPluginFactory configurerFactory,
+            ScriptHandlerFactory scriptHandlerFactory,
+            ClassLoaderScope classLoaderScope,
+            Object... defaultTargets
+    ) {
         this.resolver = resolver;
         this.configurerFactory = configurerFactory;
+        this.scriptHandlerFactory = scriptHandlerFactory;
+        this.classLoaderScope = classLoaderScope;
         this.defaultTargets = defaultTargets;
     }
 
@@ -77,7 +90,9 @@ public class DefaultObjectConfigurationAction implements ObjectConfigurationActi
 
     private void applyScript(Object script) {
         URI scriptUri = resolver.resolveUri(script);
-        ScriptPlugin configurer = configurerFactory.create(new UriScriptSource("script", scriptUri));
+        UriScriptSource scriptSource = new UriScriptSource("script", scriptUri);
+        ScriptHandler scriptHandler = scriptHandlerFactory.create(scriptSource, classLoaderScope);
+        ScriptPlugin configurer = configurerFactory.create(scriptSource, scriptHandler, classLoaderScope, "buildscript", DefaultScript.class);
         for (Object target : targets) {
             configurer.apply(target);
         }
@@ -85,22 +100,22 @@ public class DefaultObjectConfigurationAction implements ObjectConfigurationActi
 
     private void applyPlugin(Class<? extends Plugin> pluginClass) {
         for (Object target : targets) {
-            if (target instanceof Project) {
-                Project project = (Project) target;
-                project.getPlugins().apply(pluginClass);
+            if (target instanceof PluginAware) {
+                PluginAware pluginAware = (PluginAware) target;
+                pluginAware.getPlugins().apply(pluginClass);
             } else {
-                throw new UnsupportedOperationException(String.format("Cannot apply plugin of class '%s' to '%s' (class: %s) as it is not a Project", pluginClass.getName(), target.toString(), target.getClass().getName()));
+                throw new UnsupportedOperationException(String.format("Cannot apply plugin of class '%s' to '%s' (class: %s) as it does not implement PluginAware", pluginClass.getName(), target.toString(), target.getClass().getName()));
             }
         }
     }
 
     private void applyPlugin(String pluginId) {
         for (Object target : targets) {
-            if (target instanceof Project) {
-                Project project = (Project) target;
-                project.getPlugins().apply(pluginId);
+            if (target instanceof PluginAware) {
+                PluginAware pluginAware = (PluginAware) target;
+                pluginAware.getPlugins().apply(pluginId);
             } else {
-                throw new UnsupportedOperationException(String.format("Cannot apply plugin with id '%s' to '%s' (class: %s) as it is not a Project", pluginId, target.toString(), target.getClass().getName()));
+                throw new UnsupportedOperationException(String.format("Cannot apply plugin with id '%s' to '%s' (class: %s) as it does not implement PluginAware", pluginId, target.toString(), target.getClass().getName()));
             }
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginContainer.java
new file mode 100644
index 0000000..1e88e9c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginContainer.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.plugins.PluginAware;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.plugins.UnknownPluginException;
+
+public class DefaultPluginContainer<T extends PluginAware> extends DefaultPluginCollection<Plugin> implements PluginContainer {
+    private PluginRegistry pluginRegistry;
+    private final T pluginAware;
+
+    public DefaultPluginContainer(PluginRegistry pluginRegistry, T pluginAware) {
+        super(Plugin.class);
+        this.pluginRegistry = pluginRegistry;
+        this.pluginAware = pluginAware;
+    }
+
+    public Plugin apply(String id) {
+        return addPluginInternal(getTypeForId(id));
+    }
+
+    public <T extends Plugin> T apply(Class<T> type) {
+        return addPluginInternal(type);
+    }
+
+    public boolean hasPlugin(String id) {
+        return findPlugin(id) != null;
+    }
+
+    public boolean hasPlugin(Class<? extends Plugin> type) {
+        return findPlugin(type) != null;
+    }
+
+    public Plugin findPlugin(String id) {
+        try {
+            return findPlugin(getTypeForId(id));
+        } catch (UnknownPluginException e) {
+            return null;
+        }
+    }
+
+    public <T extends Plugin> T findPlugin(Class<T> type) {
+        for (Plugin plugin : this) {
+            if (plugin.getClass().equals(type)) {
+                return type.cast(plugin);
+            }
+        }
+        return null;
+    }
+
+    private <T extends Plugin> T addPluginInternal(Class<T> type) {
+        if (findPlugin(type) == null) {
+            Plugin plugin = providePlugin(type);
+            add(plugin);
+        }
+        return type.cast(findPlugin(type));
+    }
+
+    public Plugin getPlugin(String id) {
+        Plugin plugin = findPlugin(id);
+        if (plugin == null) {
+            throw new UnknownPluginException("Plugin with id " + id + " has not been used.");
+        }
+        return plugin;
+    }
+
+    public Plugin getAt(String id) throws UnknownPluginException {
+        return getPlugin(id);
+    }
+
+    public <T extends Plugin> T getAt(Class<T> type) throws UnknownPluginException {
+        return getPlugin(type);
+    }
+
+    public <T extends Plugin> T getPlugin(Class<T> type) throws UnknownPluginException {
+        Plugin plugin = findPlugin(type);
+        if (plugin == null) {
+            throw new UnknownPluginException("Plugin with type " + type + " has not been used.");
+        }
+        return type.cast(plugin);
+    }
+
+    protected Class<? extends Plugin> getTypeForId(String id) {
+        return pluginRegistry.getTypeForId(id);
+    }
+
+    private Plugin<T> providePlugin(Class<? extends Plugin> type) {
+        Plugin<T> plugin = pluginRegistry.loadPlugin(type);
+        plugin.apply(pluginAware);
+        return plugin;
+    }
+}
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 fc0c279..4e1559d 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
@@ -18,39 +18,41 @@ package org.gradle.api.internal.plugins;
 
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.api.plugins.PluginInstantiationException;
 import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.internal.Factories;
+import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.gradle.util.GUtil;
 
-import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Properties;
-
-/**
- * @author Hans Dockter
- */
 
 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 Factory<ClassLoader> classLoaderFactory;
     private final Instantiator instantiator;
 
-    public DefaultPluginRegistry(ClassLoader classLoader, Instantiator instantiator) {
-        this(null, classLoader, instantiator);
+    public DefaultPluginRegistry(ClassLoader classLoaderFactory, Instantiator instantiator) {
+        this(null, Factories.constant(classLoaderFactory), instantiator);
     }
 
-    private DefaultPluginRegistry(DefaultPluginRegistry parent, ClassLoader classLoader, Instantiator instantiator) {
+    private DefaultPluginRegistry(DefaultPluginRegistry parent, Factory<ClassLoader> classLoaderFactory, Instantiator instantiator) {
         this.parent = parent;
-        this.classLoader = classLoader;
+        this.classLoaderFactory = classLoaderFactory;
         this.instantiator = instantiator;
     }
 
-    public PluginRegistry createChild(ClassLoader childClassPath, Instantiator instantiator) {
-        return new DefaultPluginRegistry(this, childClassPath, instantiator);
+    public PluginRegistry createChild(final ClassLoaderScope lookupScope, Instantiator instantiator) {
+        Factory<ClassLoader> classLoaderFactory = new Factory<ClassLoader>() {
+            public ClassLoader create() {
+                return lookupScope.getScopeClassLoader();
+            }
+        };
+        return new DefaultPluginRegistry(this, classLoaderFactory, instantiator);
     }
 
     public <T extends Plugin> T loadPlugin(Class<T> pluginClass) {
@@ -81,29 +83,31 @@ public class DefaultPluginRegistry implements PluginRegistry {
             return implClass;
         }
 
-        URL resource = classLoader.getResource(String.format("META-INF/gradle-plugins/%s.properties", pluginId));
-        if (resource == null) {
+        ClassLoader classLoader = this.classLoaderFactory.create();
+
+        PluginDescriptorLocator pluginDescriptorLocator = new ClassloaderBackedPluginDescriptorLocator(classLoader);
+        PluginDescriptor pluginDescriptor = pluginDescriptorLocator.findPluginDescriptor(pluginId);
+        if (pluginDescriptor == null) {
             throw new UnknownPluginException("Plugin with id '" + pluginId + "' not found.");
         }
 
-        Properties properties = GUtil.loadProperties(resource);
-        String implClassName = properties.getProperty("implementation-class");
+        String implClassName = pluginDescriptor.getImplementationClassName();
         if (!GUtil.isTrue(implClassName)) {
             throw new PluginInstantiationException(String.format(
-                    "No implementation class specified for plugin '%s' in %s.", pluginId, resource));
+                    "No implementation class specified for plugin '%s' in %s.", pluginId, pluginDescriptor));
         }
 
         try {
             Class<?> rawClass = classLoader.loadClass(implClassName);
             if (!Plugin.class.isAssignableFrom(rawClass)) {
                 throw new PluginInstantiationException(String.format("Implementation class '%s' specified for plugin '%s' does not implement the Plugin interface. Specified in %s.",
-                        implClassName, pluginId, resource));
+                        implClassName, pluginId, pluginDescriptor));
             }
             implClass = rawClass.asSubclass(Plugin.class);
         } catch (ClassNotFoundException e) {
             throw new PluginInstantiationException(String.format(
                     "Could not find implementation class '%s' for plugin '%s' specified in %s.", implClassName, pluginId,
-                    resource), e);
+                    pluginDescriptor), e);
         }
 
         idMappings.put(pluginId, implClass);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
deleted file mode 100644
index a6fe69a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
+++ /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.internal.plugins;
-
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.plugins.PluginContainer;
-import org.gradle.api.plugins.UnknownPluginException;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectsPluginContainer extends DefaultPluginCollection<Plugin> implements PluginContainer {
-    private PluginRegistry pluginRegistry;
-    private final Project project;
-
-    public DefaultProjectsPluginContainer(PluginRegistry pluginRegistry, Project project) {
-        super(Plugin.class);
-        this.pluginRegistry = pluginRegistry;
-        this.project = project;
-    }
-
-    public Plugin apply(String id) {
-        return addPluginInternal(getTypeForId(id));
-    }
-
-    public <T extends Plugin> T apply(Class<T> type) {
-        return addPluginInternal(type);
-    }
-
-    public boolean hasPlugin(String id) {
-        return findPlugin(id) != null;
-    }
-
-    public boolean hasPlugin(Class<? extends Plugin> type) {
-        return findPlugin(type) != null;
-    }
-
-    public Plugin findPlugin(String id) {
-        try {
-            return findPlugin(getTypeForId(id));
-        } catch (UnknownPluginException e) {
-            return null;
-        }
-    }
-
-    public <T extends Plugin> T findPlugin(Class<T> type) {
-        for (Plugin plugin : this) {
-            if (plugin.getClass().equals(type)) {
-                return type.cast(plugin);
-            }
-        }
-        return null;
-    }
-
-    private <T extends Plugin> T addPluginInternal(Class<T> type) {
-        if (findPlugin(type) == null) {
-            Plugin plugin = providePlugin(type);
-            add(plugin);
-        }
-        return type.cast(findPlugin(type));
-    }
-
-    public Plugin getPlugin(String id) {
-        Plugin plugin = findPlugin(id);
-        if (plugin == null) {
-            throw new UnknownPluginException("Plugin with id " + id + " has not been used.");
-        }
-        return plugin;
-    }
-
-    public Plugin getAt(String id) throws UnknownPluginException {
-        return getPlugin(id);
-    }
-
-    public <T extends Plugin> T getAt(Class<T> type) throws UnknownPluginException {
-        return getPlugin(type);
-    }
-
-    public <T extends Plugin> T getPlugin(Class<T> type) throws UnknownPluginException {
-        Plugin plugin = findPlugin(type);
-        if (plugin == null) {
-            throw new UnknownPluginException("Plugin with type " + type + " has not been used.");
-        }
-        return type.cast(plugin);
-    }
-
-    protected Class<? extends Plugin> getTypeForId(String id) {
-        return pluginRegistry.getTypeForId(id);
-    }
-
-    private Plugin<Project> providePlugin(Class<? extends Plugin> type) {
-        Plugin<Project> plugin = pluginRegistry.loadPlugin(type);
-        plugin.apply(project);
-        return plugin;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java
index f1f58ec..3500ecc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java
@@ -71,6 +71,10 @@ public class DslObject implements DynamicObjectAware, ExtensionAware, IConventio
         return conventionMapping;
     }
 
+    public Class getDeclaredType(){
+        return object.getClass().getSuperclass();
+    }
+
     private static <T> T toType(Object delegate, Class<T> type) {
         if (type.isInstance(delegate)) {
             return type.cast(delegate);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionContainerInternal.java
new file mode 100644
index 0000000..1236d4d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionContainerInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.plugins;
+
+import org.gradle.api.plugins.ExtensionContainer;
+
+import java.util.Map;
+
+public interface ExtensionContainerInternal extends ExtensionContainer {
+    /**
+     * Provides access to all known extensions.
+     * @return A map of extensions, keyed by name.
+     */
+    Map<String, Object> getAsMap();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
index 00372ba..de862d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
@@ -22,17 +22,14 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.UnknownDomainObjectException;
 import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.plugins.DeferredConfigurable;
+import org.gradle.internal.UncheckedException;
 import org.gradle.listener.ActionBroadcast;
-import org.gradle.listener.ListenerNotificationException;
 
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
-/**
- * @author Szczepan Faber, created at: 6/24/11
- */
 public class ExtensionsStorage {
     private final Map<String, ExtensionHolder> extensions = new LinkedHashMap<String, ExtensionHolder>();
 
@@ -40,7 +37,7 @@ public class ExtensionsStorage {
         if (extensions.containsKey(name)) {
             throw new IllegalArgumentException(String.format("Cannot add extension with name '%s', as there is an extension already registered with that name.", name));
         }
-        extensions.put(name, wrap(extension));
+        extensions.put(name, wrap(name, extension));
     }
 
     public boolean hasExtension(String name) {
@@ -81,11 +78,13 @@ public class ExtensionsStorage {
     }
 
     public <T> T findByType(Class<T> type) {
+        ExtensionHolder<T> holder;
         try {
-            return getHolderByType(type).get();
+            holder = getHolderByType(type);
         } catch (UnknownDomainObjectException e) {
             return null;
         }
+        return holder.get();
     }
 
     private <T> ExtensionHolder<T> getHolderByType(Class<T> type) {
@@ -112,9 +111,9 @@ public class ExtensionsStorage {
         return extensionHolder == null ? null : extensionHolder.get();
     }
 
-    private <T> ExtensionHolder<T> wrap(T extension) {
+    private <T> ExtensionHolder<T> wrap(String name, T extension) {
         if (isDeferredConfigurable(extension)) {
-            return new DeferredConfigurableExtensionHolder<T>(extension);
+            return new DeferredConfigurableExtensionHolder<T>(name, extension);
         }
         return new ExtensionHolder<T>(extension);
     }
@@ -149,11 +148,14 @@ public class ExtensionsStorage {
     }
 
     private static class DeferredConfigurableExtensionHolder<T> extends ExtensionHolder<T> {
+        private final String name;
         private ActionBroadcast<T> actions = new ActionBroadcast<T>();
         private boolean configured;
+        private Throwable configureFailure;
 
-        private DeferredConfigurableExtensionHolder(T extension) {
+        public DeferredConfigurableExtensionHolder(String name, T extension) {
             super(extension);
+            this.name = name;
         }
 
         public T get() {
@@ -169,7 +171,7 @@ public class ExtensionsStorage {
 
         private void configureLater(Action<? super T> action) {
             if (configured) {
-                throw new IllegalStateException("The 'publishing' extension is already configured");
+                throw new InvalidUserDataException(String.format("Cannot configure the '%s' extension after it has been accessed.", name));
             }
             actions.add(action);
         }
@@ -179,9 +181,14 @@ public class ExtensionsStorage {
                 configured = true;
                 try {
                     actions.execute(extension);
-                } catch (ListenerNotificationException e) {
-                    throw new InvalidUserDataException("A problem occurred configuring the 'publishing' extension", e.getCause());
+                } catch (Throwable t) {
+                    configureFailure = t;
                 }
+                actions = null;
+            }
+
+            if (configureFailure != null) {
+                throw UncheckedException.throwAsUncheckedException(configureFailure);
             }
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginDescriptor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginDescriptor.java
new file mode 100644
index 0000000..a29d059
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginDescriptor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.GUtil;
+
+import java.net.URL;
+import java.util.Properties;
+
+public class PluginDescriptor {
+
+    private final URL propertiesFileUrl;
+
+    public PluginDescriptor(URL propertiesFileUrl) {
+        this.propertiesFileUrl = propertiesFileUrl;
+    }
+
+    public String getImplementationClassName() {
+        Properties properties = GUtil.loadProperties(propertiesFileUrl);
+        return properties.getProperty("implementation-class");
+    }
+
+    @Override
+    public String toString() {
+        return propertiesFileUrl.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginDescriptorLocator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginDescriptorLocator.java
new file mode 100644
index 0000000..5a2f889
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginDescriptorLocator.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+public interface PluginDescriptorLocator {
+
+    PluginDescriptor findPluginDescriptor(String pluginId);
+
+}
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 b14bd5f..a4e1690 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
@@ -17,17 +17,18 @@
 package org.gradle.api.internal.plugins;
 
 import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.api.plugins.PluginInstantiationException;
 import org.gradle.api.plugins.UnknownPluginException;
 import org.gradle.internal.reflect.Instantiator;
 
-/**
- * @author Hans Dockter
- */
 public interface PluginRegistry {
     <T extends Plugin> T loadPlugin(Class<T> pluginClass) throws PluginInstantiationException;
 
     Class<? extends Plugin> getTypeForId(String pluginId) throws UnknownPluginException, PluginInstantiationException;
 
-    PluginRegistry createChild(ClassLoader childClassPath, Instantiator instantiator);
+    /**
+     * Creates a child registry which uses the plugins declared in the given script scope.
+     */
+    PluginRegistry createChild(ClassLoaderScope lookupScope, Instantiator instantiator);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractPluginAware.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractPluginAware.java
new file mode 100644
index 0000000..baf09da
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractPluginAware.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 groovy.lang.Closure;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction;
+import org.gradle.api.plugins.PluginAware;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.Map;
+
+abstract public class AbstractPluginAware implements PluginAware {
+
+    public void apply(Closure closure) {
+        DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(getFileResolver(), getScriptPluginFactory(), getScriptHandlerFactory(), getClassLoaderScope().getBase().createChild(), this);
+        ConfigureUtil.configure(closure, action);
+        action.execute();
+    }
+
+    public void apply(Map<String, ?> options) {
+        DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(getFileResolver(), getScriptPluginFactory(), getScriptHandlerFactory(), getClassLoaderScope().getBase().createChild(), this);
+        ConfigureUtil.configureByMap(options, action);
+        action.execute();
+    }
+
+    protected abstract FileResolver getFileResolver();
+
+    protected abstract ScriptPluginFactory getScriptPluginFactory();
+
+    protected abstract ScriptHandlerFactory getScriptHandlerFactory();
+
+    protected abstract ClassLoaderScope getClassLoaderScope();
+
+}
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 4162959..196926e 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
@@ -18,9 +18,7 @@ package org.gradle.api.internal.project;
 
 import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
-import groovy.lang.Script;
 import org.gradle.api.*;
-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;
@@ -31,32 +29,40 @@ import org.gradle.api.file.CopySpec;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.*;
+import org.gradle.api.internal.artifacts.ModuleInternal;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileOperations;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
-import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.plugins.ExtensionContainerInternal;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.Convention;
-import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.plugins.PluginContainer;
 import org.gradle.api.resources.ResourceHandler;
 import org.gradle.api.tasks.Directory;
+import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.tasks.WorkResult;
-import org.gradle.configuration.ProjectEvaluator;
-import org.gradle.configuration.ScriptPlugin;
 import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.configuration.project.ProjectConfigurationActionContainer;
+import org.gradle.configuration.project.ProjectEvaluator;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
+import org.gradle.model.ModelPath;
+import org.gradle.model.ModelRules;
+import org.gradle.model.dsl.internal.GroovyModelDsl;
+import org.gradle.model.internal.ModelRegistry;
 import org.gradle.process.ExecResult;
 import org.gradle.util.Configurable;
 import org.gradle.util.ConfigureUtil;
@@ -71,12 +77,10 @@ import static java.util.Collections.singletonMap;
 import static org.gradle.util.GUtil.addMaps;
 import static org.gradle.util.GUtil.isTrue;
 
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractProject implements ProjectInternal, DynamicObjectAware {
+public abstract class AbstractProject extends AbstractPluginAware implements ProjectInternal, DynamicObjectAware {
     private static Logger buildLogger = Logging.getLogger(Project.class);
-    private ServiceRegistryFactory services;
+    private final ClassLoaderScope classLoaderScope;
+    private ServiceRegistry services;
 
     private final ProjectInternal rootProject;
 
@@ -124,7 +128,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private TaskContainerInternal implicitTasksContainer;
 
-    private IProjectRegistry<ProjectInternal> projectRegistry;
+    private ProjectRegistry<ProjectInternal> projectRegistry;
 
     private DependencyHandler dependencyHandler;
 
@@ -136,8 +140,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private ScriptHandler scriptHandler;
 
-    private ScriptClassLoaderProvider scriptClassLoaderProvider;
-
     private ListenerBroadcast<ProjectEvaluationListener> evaluationListener = new ListenerBroadcast<ProjectEvaluationListener>(ProjectEvaluationListener.class);
 
     private LoggingManagerInternal loggingManager;
@@ -146,16 +148,26 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private ExtensibleDynamicObject extensibleDynamicObject;
 
+    private ProjectConfigurationActionContainer configurationActions;
+
+    private final ModelRegistry modelRegistry;
+    private final ModelRules modelRules;
+
     private String description;
 
     private final Path path;
+    private final ScriptPluginFactory scriptPluginFactory;
+    private final ScriptHandlerFactory scriptHandlerFactory;
 
     public AbstractProject(String name,
                            ProjectInternal parent,
                            File projectDir,
                            ScriptSource buildScriptSource,
                            GradleInternal gradle,
-                           ServiceRegistryFactory serviceRegistryFactory) {
+                           ServiceRegistryFactory serviceRegistryFactory,
+                           ClassLoaderScope classLoaderScope
+    ) {
+        this.classLoaderScope = classLoaderScope;
         assert name != null;
         this.rootProject = parent != null ? parent.getRootProject() : this;
         this.projectDir = projectDir;
@@ -188,10 +200,14 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         artifactHandler = services.get(ArtifactHandler.class);
         dependencyHandler = services.get(DependencyHandler.class);
         scriptHandler = services.get(ScriptHandler.class);
-        scriptClassLoaderProvider = services.get(ScriptClassLoaderProvider.class);
-        projectRegistry = services.get(IProjectRegistry.class);
+        projectRegistry = services.get(ProjectRegistry.class);
         loggingManager = services.get(LoggingManagerInternal.class);
         softwareComponentContainer = services.get(SoftwareComponentContainer.class);
+        scriptPluginFactory = services.get(ScriptPluginFactory.class);
+        scriptHandlerFactory = services.get(ScriptHandlerFactory.class);
+        configurationActions = services.get(ProjectConfigurationActionContainer.class);
+        modelRegistry = services.get(ModelRegistry.class);
+        modelRules = services.get(ModelRules.class);
 
         extensibleDynamicObject = new ExtensibleDynamicObject(this, services.get(Instantiator.class));
         if (parent != null) {
@@ -200,6 +216,34 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         extensibleDynamicObject.addObject(taskContainer.getTasksAsDynamicObject(), ExtensibleDynamicObject.Location.AfterConvention);
 
         evaluationListener.add(gradle.getProjectEvaluationBroadcaster());
+
+        final ModelPath tasksModelPath = ModelPath.path(TaskContainerInternal.MODEL_PATH);
+        modelRules.register(tasksModelPath.toString(), taskContainer);
+        taskContainer.all(new Action<Task>() {
+            public void execute(Task task) {
+                String name = task.getName();
+                modelRules.register(tasksModelPath.child(name).toString(), Task.class, new TaskFactory(taskContainer, name));
+            }
+        });
+        taskContainer.whenObjectRemoved(new Action<Task>() {
+            public void execute(Task task) {
+                modelRules.remove(tasksModelPath.child(task.getName()).toString());
+            }
+        });
+    }
+
+    private static class TaskFactory implements Factory<Task> {
+        private final TaskContainer tasks;
+        private final String name;
+
+        private TaskFactory(TaskContainer tasks, String name) {
+            this.tasks = tasks;
+            this.name = name;
+        }
+
+        public Task create() {
+            return tasks.getByName(name);
+        }
     }
 
     public ProjectInternal getRootProject() {
@@ -226,26 +270,11 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return scriptHandler;
     }
 
-    public void beforeCompile(ScriptPlugin configurer) {
-        if (configurer.getSource() != buildScriptSource) {
-            return;
-        }
-        configurer.setScriptBaseClass(ProjectScript.class);
-        configurer.setClassLoaderProvider(scriptClassLoaderProvider);
-    }
-
-    public void afterCompile(ScriptPlugin configurer, org.gradle.groovy.scripts.Script script) {
-        if (configurer.getSource() != buildScriptSource) {
-            return;
-        }
-        setScript(script);
-    }
-
     public File getBuildFile() {
         return getBuildscript().getSourceFile();
     }
 
-    public void setScript(Script buildScript) {
+    public void setScript(groovy.lang.Script buildScript) {
         extensibleDynamicObject.addObject(new BeanDynamicObject(buildScript).withNoProperties().withNotImplementsMissing(),
                 ExtensibleDynamicObject.Location.BeforeConvention);
     }
@@ -379,7 +408,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return depth;
     }
 
-    public IProjectRegistry<ProjectInternal> getProjectRegistry() {
+    public ProjectRegistry<ProjectInternal> getProjectRegistry() {
         return projectRegistry;
     }
 
@@ -492,7 +521,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         Map<String, Object> allArgs = new HashMap<String, Object>(args);
         allArgs.put(Task.TASK_NAME, name);
         allArgs.put(Task.TASK_ACTION, action);
-        return taskContainer.add(allArgs);
+        return taskContainer.create(allArgs);
     }
 
     public Task createTask(Map<String, ?> args, String name, Action<? super Task> action) {
@@ -502,7 +531,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         if (action != null) {
             allArgs.put(Task.TASK_ACTION, action);
         }
-        return taskContainer.add(allArgs);
+        return taskContainer.create(allArgs);
     }
 
     private void warnCreateTaskDeprecated() {
@@ -586,7 +615,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         DeprecationLogger.nagUserOfDiscontinuedMethod("Project.dependsOnChildren()");
         return DeprecationLogger.whileDisabled(new Factory<Project>() {
             public Project create() {
-               return dependsOnChildren(false);
+                return dependsOnChildren(false);
             }
         });
     }
@@ -726,7 +755,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
             } else if (task != null) {
                 throw new InvalidUserDataException(String.format("Cannot add directory task '%s' as a non-directory task with this name already exists.", name));
             } else {
-                dirTask = taskContainer.add(name, Directory.class);
+                dirTask = taskContainer.create(name, Directory.class);
             }
         }
         return dirTask;
@@ -812,10 +841,18 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return fileOperations.copy(closure);
     }
 
+    public WorkResult sync(Action<? super CopySpec> action) {
+        return fileOperations.sync(action);
+    }
+
     public CopySpec copySpec(Closure closure) {
         return fileOperations.copySpec(closure);
     }
 
+    public CopySpec copySpec(Action<? super CopySpec> action) {
+        return fileOperations.copySpec(action);
+    }
+
     public ExecResult javaexec(Closure closure) {
         return processOperations.javaexec(closure);
     }
@@ -824,26 +861,16 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return processOperations.exec(closure);
     }
 
-    public ServiceRegistryFactory getServices() {
+    public ServiceRegistry getServices() {
         return services;
     }
 
-    public Module getModule() {
-        return getServices().get(DependencyMetaDataProvider.class).getModule();
-    }
-
-    public void apply(Closure closure) {
-        DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(
-                ScriptPluginFactory.class), this);
-        configure(action, closure);
-        action.execute();
+    public ServiceRegistryFactory getServiceRegistryFactory() {
+        return services.get(ServiceRegistryFactory.class);
     }
 
-    public void apply(Map<String, ?> options) {
-        DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(
-                ScriptPluginFactory.class), this);
-        ConfigureUtil.configureByMap(options, action);
-        action.execute();
+    public ModuleInternal getModule() {
+        return services.get(DependencyMetaDataProvider.class).getModule();
     }
 
     public AntBuilder ant(Closure configureClosure) {
@@ -894,15 +921,15 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public Task task(String task) {
-        return taskContainer.add(task);
+        return taskContainer.create(task);
     }
 
     public Task task(Object task) {
-        return taskContainer.add(task.toString());
+        return taskContainer.create(task.toString());
     }
 
     public Task task(String task, Closure configureClosure) {
-        return taskContainer.add(task).configure(configureClosure);
+        return taskContainer.create(task).configure(configureClosure);
     }
 
     public Task task(Object task, Closure configureClosure) {
@@ -910,7 +937,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public Task task(Map options, String task) {
-        return taskContainer.add(addMaps(options, singletonMap(Task.TASK_NAME, task)));
+        return taskContainer.create(addMaps(options, singletonMap(Task.TASK_NAME, task)));
     }
 
     public Task task(Map options, Object task) {
@@ -918,13 +945,36 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public Task task(Map options, String task, Closure configureClosure) {
-        return taskContainer.add(addMaps(options, singletonMap(Task.TASK_NAME, task))).configure(configureClosure);
+        return taskContainer.create(addMaps(options, singletonMap(Task.TASK_NAME, task))).configure(configureClosure);
     }
 
     public Task task(Map options, Object task, Closure configureClosure) {
         return task(options, task.toString(), configureClosure);
     }
 
+    public ProjectConfigurationActionContainer getConfigurationActions() {
+        return configurationActions;
+    }
+
+    public ModelRegistry getModelRegistry() {
+        return modelRegistry;
+    }
+
+    @Override
+    protected ScriptPluginFactory getScriptPluginFactory() {
+        return scriptPluginFactory;
+    }
+
+    @Override
+    protected ScriptHandlerFactory getScriptHandlerFactory() {
+        return scriptHandlerFactory;
+    }
+
+    @Override
+    public ClassLoaderScope getClassLoaderScope() {
+        return classLoaderScope;
+    }
+
     /**
      * This is called by the task creation DSL. Need to find a cleaner way to do this...
      */
@@ -947,7 +997,15 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return instantiator.newInstance(FactoryNamedDomainObjectContainer.class, type, instantiator, new DynamicPropertyNamer(), factoryClosure);
     }
 
-    public ExtensionContainer getExtensions() {
-        return getConvention();
+    public ExtensionContainerInternal getExtensions() {
+        return (ExtensionContainerInternal) getConvention();
+    }
+
+    // This is here temporarily as a quick way to expose it in the build script
+    // Longer term it will not be available via Project, but be only available in a build script
+    public void model(Closure action) {
+        new GroovyModelDsl(modelRules).configure(action);
     }
+
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.groovy
deleted file mode 100644
index 9cd1aa6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.groovy
+++ /dev/null
@@ -1,96 +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.apache.tools.ant.MagicNames
-import org.apache.tools.ant.ProjectHelper
-import org.apache.tools.ant.Target
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-import org.gradle.api.internal.project.ant.BasicAntBuilder
-import org.gradle.api.tasks.ant.AntTarget
-import java.beans.PropertyChangeListener
-import java.beans.PropertyChangeEvent
-import org.apache.tools.ant.PropertyHelper
-
-public class DefaultAntBuilder extends BasicAntBuilder {
-    private final Project gradleProject
-
-    def DefaultAntBuilder(Project gradleProject) {
-        this.gradleProject = gradleProject;
-    }
-
-    def Object invokeMethod(String methodName, Object args) {
-        super.invokeMethod(methodName, args)
-    }
-
-    def propertyMissing(String property, Object newValue) {
-        doSetProperty(property, newValue)
-    }
-
-    private def doSetProperty(String property, newValue) {
-        PropertyHelper.getPropertyHelper(project).setUserProperty(null, property, newValue)
-    }
-
-    def propertyMissing(String name) {
-        if (project.properties.containsKey(name)) {
-            return project.properties[name]
-        }
-        throw new MissingPropertyException(name, getClass())
-    }
-
-    public Map getProperties() {
-        ObservableMap map = new ObservableMap(project.properties)
-        map.addPropertyChangeListener({PropertyChangeEvent event -> doSetProperty(event.propertyName, event.newValue) } as PropertyChangeListener)
-        map
-    }
-
-    public Map getReferences() {
-        ObservableMap map = new ObservableMap(project.references)
-        map.addPropertyChangeListener({PropertyChangeEvent event -> project.addReference(event.propertyName, event.newValue) } as PropertyChangeListener)
-        map
-    }
-
-    public void importBuild(Object antBuildFile) {
-        File file = gradleProject.file(antBuildFile)
-        File baseDir = file.parentFile
-
-        Set existingAntTargets = new HashSet(antProject.targets.keySet())
-        File oldBaseDir = antProject.baseDir
-        antProject.baseDir = baseDir
-        try {
-            antProject.setUserProperty(MagicNames.ANT_FILE, file.getAbsolutePath())
-            ProjectHelper.configureProject(antProject, file)
-        } catch(Exception e) {
-            throw new GradleException("Could not import Ant build file '$file'.", e)
-        } finally {
-            antProject.baseDir = oldBaseDir
-        }
-
-        // Chuck away the implicit target. It has already been executed
-        antProject.targets.remove('')
-
-        // Add an adapter for each newly added target
-        Set newAntTargets = new HashSet(antProject.targets.keySet())
-        newAntTargets.removeAll(existingAntTargets)
-        newAntTargets.each {name ->
-            Target target = antProject.targets[name]
-            AntTarget task = gradleProject.tasks.add(target.name, AntTarget)
-            task.target = target
-            task.baseDir = baseDir
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.java
new file mode 100644
index 0000000..e7e691c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 groovy.lang.GroovyObject;
+import groovy.lang.MissingPropertyException;
+import groovy.util.ObservableMap;
+import org.apache.tools.ant.MagicNames;
+import org.apache.tools.ant.ProjectHelper;
+import org.apache.tools.ant.PropertyHelper;
+import org.apache.tools.ant.Target;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.project.ant.BasicAntBuilder;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.ant.AntTarget;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.util.*;
+
+public class DefaultAntBuilder extends BasicAntBuilder implements GroovyObject {
+
+    private final Project gradleProject;
+
+    public DefaultAntBuilder(Project gradleProject) {
+        this.gradleProject = gradleProject;
+    }
+
+    public void propertyMissing(String property, Object newValue) {
+        doSetProperty(property, newValue);
+    }
+
+    @SuppressWarnings("deprecation")
+    private void doSetProperty(String property, Object newValue) {
+        PropertyHelper.getPropertyHelper(getProject()).setUserProperty(null, property, newValue);
+    }
+
+    public Object propertyMissing(String name) {
+        if (getProject().getProperties().containsKey(name)) {
+            return getProject().getProperties().get(name);
+        }
+
+        throw new MissingPropertyException(name, getClass());
+    }
+
+    public Map<String, Object> getProperties() {
+        ObservableMap map = new ObservableMap(getProject().getProperties());
+        map.addPropertyChangeListener(new PropertyChangeListener() {
+            public void propertyChange(PropertyChangeEvent event) {
+                doSetProperty(event.getPropertyName(), event.getNewValue());
+            }
+        });
+
+        @SuppressWarnings("unchecked") Map<String, Object> castMap = (Map<String, Object>) map;
+        return castMap;
+    }
+
+    public Map<String, Object> getReferences() {
+        ObservableMap map = new ObservableMap(getProject().getReferences());
+        map.addPropertyChangeListener(new PropertyChangeListener() {
+            public void propertyChange(PropertyChangeEvent event) {
+                getProject().addReference(event.getPropertyName(), event.getNewValue());
+            }
+        });
+
+        @SuppressWarnings("unchecked") Map<String, Object> castMap = (Map<String, Object>) map;
+        return castMap;
+    }
+
+    public void importBuild(Object antBuildFile) {
+        File file = gradleProject.file(antBuildFile);
+        final File baseDir = file.getParentFile();
+
+        Set<String> existingAntTargets = new HashSet<String>(getAntProject().getTargets().keySet());
+        File oldBaseDir = getAntProject().getBaseDir();
+        getAntProject().setBaseDir(baseDir);
+        try {
+            getAntProject().setUserProperty(MagicNames.ANT_FILE, file.getAbsolutePath());
+            ProjectHelper.configureProject(getAntProject(), file);
+        } catch (Exception e) {
+            throw new GradleException("Could not import Ant build file '" + String.valueOf(file) + "'.", e);
+        } finally {
+            getAntProject().setBaseDir(oldBaseDir);
+        }
+
+        // Chuck away the implicit target. It has already been executed
+        getAntProject().getTargets().remove("");
+
+        // Add an adapter for each newly added target
+        Set<String> newAntTargets = new HashSet<String>(getAntProject().getTargets().keySet());
+        newAntTargets.removeAll(existingAntTargets);
+        for (String name : newAntTargets) {
+            Target target = getAntProject().getTargets().get(name);
+            AntTarget task = gradleProject.getTasks().create(target.getName(), AntTarget.class);
+            task.setTarget(target);
+            task.setBaseDir(baseDir);
+            addDependencyOrdering(target.getDependencies());
+        }
+    }
+
+    private void addDependencyOrdering(Enumeration<String> dependencies) {
+        TaskContainer tasks = gradleProject.getTasks();
+        String previous = null;
+        for (final String dependency : Collections.list(dependencies)) {
+            if (previous != null) {
+                final String finalPrevious = previous;
+                tasks.all(new Action<Task>() {
+                    public void execute(Task task) {
+                        if (task.getName().equals(dependency)) {
+                            task.shouldRunAfter(finalPrevious);
+                        }
+                    }
+                });
+            }
+
+            previous = dependency;
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
index 6b39102..d879255 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
@@ -19,13 +19,12 @@ import org.apache.tools.ant.BuildListener;
 import org.gradle.api.AntBuilder;
 import org.gradle.api.Project;
 import org.gradle.internal.Factory;
+import org.gradle.internal.concurrent.CompositeStoppable;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultAntBuilderFactory implements Factory<AntBuilder> {
     private final BuildListener buildListener;
     private final Project project;
+    private final CompositeStoppable stoppable = new CompositeStoppable();
 
     public DefaultAntBuilderFactory(BuildListener buildListener, Project project) {
         this.buildListener = buildListener;
@@ -35,8 +34,13 @@ public class DefaultAntBuilderFactory implements Factory<AntBuilder> {
     public DefaultAntBuilder create() {
         DefaultAntBuilder antBuilder = new DefaultAntBuilder(project);
         antBuilder.getProject().setBaseDir(project.getProjectDir());
-        antBuilder.getProject().removeBuildListener((BuildListener) antBuilder.getProject().getBuildListeners().get(0));
+        antBuilder.getProject().removeBuildListener(antBuilder.getProject().getBuildListeners().get(0));
         antBuilder.getProject().addBuildListener(buildListener);
+        stoppable.add(antBuilder);
         return antBuilder;
     }
+
+    public void close() {
+        stoppable.stop();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
index 9782958..ce7ebd0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
@@ -18,6 +18,10 @@ package org.gradle.api.internal.project
 import org.gradle.api.internal.ClassPathRegistry
 import org.gradle.api.internal.project.ant.AntLoggingAdapter
 import org.gradle.api.internal.project.ant.BasicAntBuilder
+import org.gradle.internal.classloader.ClassLoaderFactory
+import org.gradle.internal.classloader.FilteringClassLoader
+import org.gradle.internal.classloader.MultiParentClassLoader
+import org.gradle.internal.classloader.MutableURLClassLoader
 import org.gradle.util.*
 import org.gradle.internal.jvm.Jvm
 import org.gradle.internal.classpath.DefaultClassPath
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java
index 9981c87..5cc774f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java
@@ -18,14 +18,16 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.NoConventionMapping;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 
 import java.io.File;
 
 @NoConventionMapping
 public class DefaultProject extends AbstractProject {
     public DefaultProject(String name, ProjectInternal parent, File projectDir, ScriptSource buildScriptSource,
-                           GradleInternal gradle, ServiceRegistryFactory serviceRegistryFactory) {
-        super(name, parent, projectDir, buildScriptSource, gradle, serviceRegistryFactory);
+                          GradleInternal gradle, ServiceRegistryFactory serviceRegistryFactory, ClassLoaderScope classLoaderScope) {
+        super(name, parent, projectDir, buildScriptSource, gradle, serviceRegistryFactory, classLoaderScope);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java
index ab2c723..7583d3c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.project;
 
 import org.gradle.initialization.ProjectAccessListener;
 
-/**
-* by Szczepan Faber, created at: 2/11/13
-*/
 public class DefaultProjectAccessListener implements ProjectAccessListener {
 
     public void beforeRequestingTaskByPath(ProjectInternal targetProject) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java
index 38e6870..d4c0918 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java
@@ -20,15 +20,12 @@ import org.gradle.api.specs.Spec;
 import org.gradle.util.GUtil;
 
 import java.io.File;
-import java.util.Map;
 import java.util.HashMap;
-import java.util.Set;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectRegistry<T extends ProjectIdentifier> implements IProjectRegistry<T> {
+public class DefaultProjectRegistry<T extends ProjectIdentifier> implements ProjectRegistry<T> {
     private Map<String, T> projects = new HashMap<String, T>();
     private Map<String, Set<T>> subProjects = new HashMap<String, Set<T>>();
 
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
deleted file mode 100755
index 1f4c673..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project;
-
-import org.gradle.StartParameter;
-import org.gradle.api.internal.*;
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.api.internal.classpath.DefaultPluginModuleRegistry;
-import org.gradle.api.internal.classpath.ModuleRegistry;
-import org.gradle.api.internal.classpath.PluginModuleRegistry;
-import org.gradle.cache.internal.*;
-import org.gradle.cli.CommandLineConverter;
-import org.gradle.initialization.ClassLoaderRegistry;
-import org.gradle.initialization.DefaultClassLoaderRegistry;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.internal.Factory;
-import org.gradle.internal.nativeplatform.ProcessEnvironment;
-import org.gradle.internal.nativeplatform.services.NativeServices;
-import org.gradle.internal.reflect.DirectInstantiator;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.LoggingServiceRegistry;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.internal.MessagingServices;
-import org.gradle.util.ClassLoaderFactory;
-import org.gradle.util.DefaultClassLoaderFactory;
-
-/**
- * Contains the services shared by all builds in a given process.
- */
-public class GlobalServicesRegistry extends DefaultServiceRegistry {
-    public GlobalServicesRegistry() {
-        this(LoggingServiceRegistry.newProcessLogging());
-    }
-
-    public GlobalServicesRegistry(ServiceRegistry loggingServices) {
-        super(loggingServices);
-        add(NativeServices.getInstance());
-    }
-
-    protected CommandLineConverter<StartParameter> createCommandLine2StartParameterConverter() {
-        return new DefaultCommandLineConverter();
-    }
-
-    protected ClassPathRegistry createClassPathRegistry() {
-        return new DefaultClassPathRegistry(new DefaultClassPathProvider(get(ModuleRegistry.class)), new DynamicModulesClassPathProvider(get(ModuleRegistry.class), get(PluginModuleRegistry.class)));
-    }
-
-    protected DefaultModuleRegistry createModuleRegistry() {
-        return new DefaultModuleRegistry();
-    }
-
-    protected DocumentationRegistry createDocumentationRegistry() {
-        return new DocumentationRegistry(get(GradleDistributionLocator.class));
-    }
-
-    protected PluginModuleRegistry createPluginModuleRegistry() {
-        return new DefaultPluginModuleRegistry(get(ModuleRegistry.class));
-    }
-
-    protected Factory<CacheFactory> createCacheFactory() {
-        return new DefaultCacheFactory(get(FileLockManager.class));
-    }
-
-    protected ClassLoaderRegistry createClassLoaderRegistry() {
-        return new DefaultClassLoaderRegistry(get(ClassPathRegistry.class), get(ClassLoaderFactory.class));
-    }
-
-    protected ListenerManager createListenerManager() {
-        return new DefaultListenerManager();
-    }
-   
-    protected ClassLoaderFactory createClassLoaderFactory() {
-        return new DefaultClassLoaderFactory();
-    }
-
-    protected MessagingServices createMessagingServices() {
-        return new MessagingServices(get(ClassLoaderRegistry.class).getPluginsClassLoader());
-    }
-
-    protected MessagingServer createMessagingServer() {
-        return get(MessagingServices.class).get(MessagingServer.class);
-    }
-
-    protected ClassGenerator createClassGenerator() {
-        return new AsmBackedClassGenerator();
-    }
-
-    protected Instantiator createInstantiator() {
-        return new ClassGeneratorBackedInstantiator(get(ClassGenerator.class), new DirectInstantiator());
-    }
-
-    protected FileLockManager createFileLockManager() {
-        return new DefaultFileLockManager(new DefaultProcessMetaDataProvider(get(ProcessEnvironment.class)));
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
deleted file mode 100644
index 996720f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.project;
-
-import org.gradle.StartParameter;
-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.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;
-
-import java.util.LinkedList;
-import java.util.List;
-
-import static java.util.Arrays.asList;
-
-/**
- * Contains the services for a given {@link GradleInternal} instance.
- */
-public class GradleInternalServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
-    private final GradleInternal gradle;
-
-    public GradleInternalServiceRegistry(ServiceRegistry parent, final GradleInternal gradle) {
-        super(parent);
-        this.gradle = gradle;
-        add(new TaskExecutionServices(parent, gradle));
-    }
-
-    protected BuildExecuter createBuildExecuter() {
-        List<BuildConfigurationAction> configs = new LinkedList<BuildConfigurationAction>();
-        if (get(StartParameter.class).isConfigureOnDemand()) {
-            configs.add(new ProjectEvaluatingAction());
-        }
-        configs.add(new DefaultTasksBuildExecutionAction());
-        configs.add(new ExcludedTaskFilteringBuildConfigurationAction());
-        configs.add(new TaskNameResolvingBuildConfigurationAction());
-
-        return new DefaultBuildExecuter(
-                configs,
-                asList(new DryRunBuildExecutionAction(),
-                        new TaskCacheLockHandlingBuildExecuter(get(TaskArtifactStateCacheAccess.class)),
-                        new SelectedTaskExecutionAction()));
-    }
-
-    protected ProjectFinder createProjectFinder() {
-        return new ProjectFinder() {
-            public ProjectInternal getProject(String path) {
-                return gradle.getRootProject().project(path);
-            }
-        };
-    }
-
-    protected IProjectRegistry createIProjectRegistry() {
-        return new DefaultProjectRegistry<ProjectInternal>();
-    }
-
-    protected TaskGraphExecuter createTaskGraphExecuter() {
-        return new DefaultTaskGraphExecuter(get(ListenerManager.class), get(TaskPlanExecutor.class));
-    }
-
-    protected PluginRegistry createPluginRegistry() {
-        return new DefaultPluginRegistry(gradle.getScriptClassLoader(), new DependencyInjectingInstantiator(this));
-    }
-
-    public ServiceRegistryFactory createFor(Object domainObject) {
-        if (domainObject instanceof ProjectInternal) {
-            return new ProjectInternalServiceRegistry(this, (ProjectInternal) domainObject);
-        }
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java
index aaa8a91..93e782c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java
@@ -17,12 +17,11 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 
 /**
  * Creates a {@link ProjectInternal} implementation.
- *
- * @author Hans Dockter
  */
 public interface IProjectFactory {
-    ProjectInternal createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle);
+    ProjectInternal createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle, ClassLoaderScope classLoaderScope);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectRegistry.java
deleted file mode 100644
index 7136df8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/IProjectRegistry.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.project;
-
-import org.gradle.api.specs.Spec;
-
-import java.util.Set;
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public interface IProjectRegistry<T extends ProjectIdentifier> {
-    void addProject(T project);
-
-    T getProject(String path);
-
-    T getProject(File projectDir);
-
-    Set<T> getAllProjects();
-    
-    Set<T> getAllProjects(String path);
-
-    Set<T> getSubProjects(String path);
-
-    Set<T> findAll(Spec<? super T> constraint);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
index 3fe76da..4e6c7f7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
@@ -18,24 +18,24 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.internal.reflect.Instantiator;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.io.File;
 
-/**
- * @author Hans Dockter
- */
 public class ProjectFactory implements IProjectFactory {
     private final Instantiator instantiator;
+    private final ProjectRegistry<ProjectInternal> projectRegistry;
 
-    public ProjectFactory(Instantiator instantiator) {
+    public ProjectFactory(Instantiator instantiator, ProjectRegistry<ProjectInternal> projectRegistry) {
         this.instantiator = instantiator;
+        this.projectRegistry = projectRegistry;
     }
 
-    public DefaultProject createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle) {
+    public DefaultProject createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle, ClassLoaderScope classLoaderScope) {
         File buildFile = projectDescriptor.getBuildFile();
         ScriptSource source;
         if (!buildFile.exists()) {
@@ -50,12 +50,14 @@ public class ProjectFactory implements IProjectFactory {
                 projectDescriptor.getProjectDir(),
                 source,
                 gradle,
-                gradle.getServices());
+                gradle.getServiceRegistryFactory(),
+                classLoaderScope
+                );
 
         if (parent != null) {
             parent.addChildProject(project);
         }
-        gradle.getProjectRegistry().addProject(project);
+        projectRegistry.addProject(project);
 
         return project;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
index b5a0fbd..0a08323 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
@@ -27,10 +27,16 @@ import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerIn
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileOperations;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.plugins.ExtensionContainerInternal;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
+import org.gradle.configuration.project.ProjectConfigurationActionContainer;
 import org.gradle.groovy.scripts.ScriptAware;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.logging.StandardOutputCapture;
+import org.gradle.model.internal.ModelRegistry;
 
 public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware, FileOperations, ProcessOperations, DomainObjectContext, DependencyMetaDataProvider {
     ProjectInternal getParent();
@@ -53,7 +59,7 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
 
     ProjectInternal findProject(String path);
 
-    IProjectRegistry<ProjectInternal> getProjectRegistry();
+    ProjectRegistry<ProjectInternal> getProjectRegistry();
 
     DynamicObject getInheritedScope();
 
@@ -63,9 +69,20 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
 
     FileResolver getFileResolver();
 
-    ServiceRegistryFactory getServices();
+    ServiceRegistry getServices();
+
+    ServiceRegistryFactory getServiceRegistryFactory();
 
     StandardOutputCapture getStandardOutputCapture();
 
     ProjectStateInternal getState();
+
+    ExtensionContainerInternal getExtensions();
+
+    ProjectConfigurationActionContainer getConfigurationActions();
+
+    ModelRegistry getModelRegistry();
+
+    ClassLoaderScope getClassLoaderScope();
+
 }
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
deleted file mode 100644
index 712e621..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project;
-
-import org.gradle.api.AntBuilder;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.dsl.ArtifactHandler;
-import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.component.SoftwareComponentContainer;
-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.DependencyManagementServices;
-import org.gradle.api.internal.artifacts.DependencyResolutionServices;
-import org.gradle.api.internal.artifacts.ProjectBackedModule;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.api.internal.component.DefaultSoftwareComponentContainer;
-import org.gradle.api.internal.file.*;
-import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
-import org.gradle.api.internal.initialization.ScriptHandlerFactory;
-import org.gradle.api.internal.initialization.ScriptHandlerInternal;
-import org.gradle.api.internal.plugins.DefaultProjectsPluginContainer;
-import org.gradle.api.internal.plugins.PluginRegistry;
-import org.gradle.api.internal.project.ant.AntLoggingAdapter;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
-import org.gradle.api.internal.tasks.TaskContainerInternal;
-import org.gradle.api.plugins.PluginContainer;
-import org.gradle.initialization.ProjectAccessListener;
-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;
-
-import java.io.File;
-
-/**
- * Contains the services for a given project.
- */
-public class ProjectInternalServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
-    private final ProjectInternal project;
-
-    public ProjectInternalServiceRegistry(ServiceRegistry parent, final ProjectInternal project) {
-        super(parent);
-        this.project = project;
-    }
-
-    protected PluginRegistry createPluginRegistry(PluginRegistry parentRegistry) {
-        return parentRegistry.createChild(get(ScriptClassLoaderProvider.class).getClassLoader(), new DependencyInjectingInstantiator(this));
-    }
-
-    protected FileResolver createFileResolver() {
-        return new BaseDirFileResolver(get(FileSystem.class), project.getProjectDir());
-    }
-
-    protected LoggingManagerInternal createLoggingManager() {
-        return getFactory(LoggingManagerInternal.class).create();
-    }
-
-    protected DefaultFileOperations createFileOperations() {
-        return new DefaultFileOperations(get(FileResolver.class), project.getTasks(), get(TemporaryFileProvider.class));
-    }
-
-    protected TemporaryFileProvider createTemporaryFileProvider() {
-        return new DefaultTemporaryFileProvider(new Factory<File>() {
-            public File create() {
-                return new File(project.getBuildDir(), "tmp");
-            }
-        });
-    }
-
-    protected Factory<AntBuilder> createAntBuilderFactory() {
-        return new DefaultAntBuilderFactory(new AntLoggingAdapter(), project);
-    }
-
-    protected PluginContainer createPluginContainer() {
-        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, get(ProjectAccessListener.class));
-    }
-
-    protected ArtifactPublicationServices createArtifactPublicationServices() {
-        return get(DependencyResolutionServices.class).createArtifactPublicationServices();
-    }
-
-    protected RepositoryHandler createRepositoryHandler() {
-        return get(DependencyResolutionServices.class).getResolveRepositoryHandler();
-    }
-
-    protected ConfigurationContainerInternal createConfigurationContainer() {
-        return get(DependencyResolutionServices.class).getConfigurationContainer();
-    }
-
-    protected SoftwareComponentContainer createSoftwareComponentContainer() {
-        Instantiator instantiator = get(Instantiator.class);
-        return instantiator.newInstance(DefaultSoftwareComponentContainer.class, instantiator);
-    }
-
-    protected DependencyResolutionServices createDependencyResolutionServices() {
-        return newDependencyResolutionServices();
-    }
-
-    private DependencyResolutionServices newDependencyResolutionServices() {
-        return get(DependencyManagementServices.class).create(get(FileResolver.class), get(DependencyMetaDataProvider.class), get(ProjectFinder.class), project);
-    }
-
-    protected ArtifactHandler createArtifactHandler() {
-        return get(DependencyResolutionServices.class).getArtifactHandler();
-    }
-
-    protected ProjectFinder createProjectFinder() {
-        return new ProjectFinder() {
-            public ProjectInternal getProject(String path) {
-                return project.project(path);
-            }
-        };
-    }
-
-    protected DependencyHandler createDependencyHandler() {
-        return get(DependencyResolutionServices.class).getDependencyHandler();
-    }
-
-    protected ScriptHandlerInternal createScriptHandler() {
-        ScriptHandlerFactory factory = new DefaultScriptHandlerFactory(
-                get(DependencyManagementServices.class),
-                get(FileResolver.class),
-                get(DependencyMetaDataProvider.class));
-        ClassLoader parentClassLoader;
-        if (project.getParent() != null) {
-            parentClassLoader = project.getParent().getBuildscript().getClassLoader();
-        } else {
-            parentClassLoader = project.getGradle().getScriptClassLoader();
-        }
-        return factory.create(project.getBuildScriptSource(), parentClassLoader, project);
-    }
-
-    protected DependencyMetaDataProvider createDependencyMetaDataProvider() {
-        return new DependencyMetaDataProvider() {
-            public Module getModule() {
-                return new ProjectBackedModule(project);
-            }
-        };
-    }
-
-    public ServiceRegistryFactory createFor(Object domainObject) {
-        if (domainObject instanceof TaskInternal) {
-            return new TaskInternalServiceRegistry(this, project, (TaskInternal)domainObject);
-        }
-        throw new UnsupportedOperationException();
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectRegistry.java
new file mode 100644
index 0000000..f0daee3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectRegistry.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.project;
+
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.Set;
+
+public interface ProjectRegistry<T extends ProjectIdentifier> {
+    void addProject(T project);
+
+    T getProject(String path);
+
+    T getProject(File projectDir);
+
+    Set<T> getAllProjects();
+    
+    Set<T> getAllProjects(String path);
+
+    Set<T> getSubProjects(String path);
+
+    Set<T> findAll(Spec<? super T> constraint);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
index b4d14ad..5b132e8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
@@ -34,7 +34,7 @@ abstract class ProjectScript extends DefaultScript {
         scriptTarget.apply(options)
     }
 
-    def ScriptHandler getBuildscript() {
+    ScriptHandler getBuildscript() {
         scriptTarget.buildscript
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
index badaac5..14aa925 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
@@ -46,6 +46,10 @@ public class ProjectStateInternal implements ProjectState {
         this.executing = executing;
     }
 
+    public boolean hasFailure() {
+        return failure != null;
+    }
+
     public Throwable getFailure() {
         return failure;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
deleted file mode 100644
index 1b3e36f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.project;
-
-import org.gradle.internal.service.ServiceRegistry;
-
-/**
- * A hierarchical service registry.
- */
-public interface ServiceRegistryFactory extends ServiceRegistry {
-    /**
-     * Creates the services for the given domain object.
-     *
-     * @param domainObject The domain object.
-     * @return The registry containing the services for the domain object.
-     */
-    ServiceRegistryFactory createFor(Object domainObject);
-}
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
deleted file mode 100644
index 146f248..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
+++ /dev/null
@@ -1,86 +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.project;
-
-import org.gradle.StartParameter;
-import org.gradle.api.execution.TaskActionListener;
-import org.gradle.api.internal.changedetection.*;
-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;
-
-public class TaskExecutionServices extends DefaultServiceRegistry {
-    private final Gradle gradle;
-
-    public TaskExecutionServices(ServiceRegistry parent, Gradle gradle) {
-        super(parent);
-        this.gradle = gradle;
-    }
-
-    protected TaskExecuter createTaskExecuter() {
-        return new ExecuteAtMostOnceTaskExecuter(
-                new SkipOnlyIfTaskExecuter(
-                        new SkipTaskWithNoActionsExecuter(
-                                new SkipEmptySourceFilesTaskExecuter(
-                                        new ValidatingTaskExecuter(
-                                                new SkipUpToDateTaskExecuter(
-                                                        new CacheLockHandlingTaskExecuter(
-                                                                new PostExecutionAnalysisTaskExecuter(
-                                                                        new ExecuteActionsTaskExecuter(
-                                                                                get(ListenerManager.class).getBroadcaster(TaskActionListener.class))),
-                                                                get(TaskArtifactStateCacheAccess.class)),
-                                                        get(TaskArtifactStateRepository.class)))))));
-    }
-
-    protected TaskArtifactStateCacheAccess createCacheAccess() {
-        return new DefaultTaskArtifactStateCacheAccess(gradle, get(CacheRepository.class));
-    }
-
-    protected TaskArtifactStateRepository createTaskArtifactStateRepository() {
-        TaskArtifactStateCacheAccess cacheAccess = get(TaskArtifactStateCacheAccess.class);
-
-        FileSnapshotter fileSnapshotter = new DefaultFileSnapshotter(
-                new CachingHasher(
-                        new DefaultHasher(),
-                        cacheAccess));
-
-        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(fileSnapshotter, new RandomLongIdGenerator(), cacheAccess);
-
-        TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess, new CacheBackedFileSnapshotRepository(cacheAccess));
-
-        return new FileCacheBroadcastTaskArtifactStateRepository(
-                new ShortCircuitTaskArtifactStateRepository(
-                        get(StartParameter.class),
-                        new DefaultTaskArtifactStateRepository(
-                                taskHistoryRepository,
-                                fileSnapshotter,
-                                outputFilesSnapshotter)),
-                new DefaultFileCacheListener());
-    }
-
-    protected TaskPlanExecutor createTaskExecutorFactory() {
-        StartParameter startParameter = gradle.getStartParameter();
-        TaskArtifactStateCacheAccess cacheAccess = get(TaskArtifactStateCacheAccess.class);
-        return new TaskPlanExecutorFactory(cacheAccess, startParameter.getParallelThreadCount()).create();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
deleted file mode 100644
index 95bbca0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project;
-
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.internal.tasks.DefaultTaskInputs;
-import org.gradle.api.internal.tasks.DefaultTaskOutputs;
-import org.gradle.api.internal.tasks.TaskStatusNagger;
-import org.gradle.api.tasks.TaskInputs;
-import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.logging.LoggingManagerInternal;
-
-/**
- * Contains the services for a given task.
- */
-public class TaskInternalServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
-    private final ProjectInternal project;
-    private final TaskInternal taskInternal;
-
-    public TaskInternalServiceRegistry(ServiceRegistry parent, final ProjectInternal project, TaskInternal taskInternal) {
-        super(parent);
-        this.project = project;
-        this.taskInternal = taskInternal;
-    }
-
-    protected TaskInputs createTaskInputs() {
-        return new DefaultTaskInputs(project.getFileResolver(), taskInternal, get(TaskStatusNagger.class));
-    }
-
-    protected TaskOutputsInternal createTaskOutputs() {
-        return new DefaultTaskOutputs(project.getFileResolver(), taskInternal, get(TaskStatusNagger.class));
-    }
-
-    protected TaskStatusNagger createTaskStatusNagger() {
-        return new TaskStatusNagger(taskInternal);
-    }
-
-    protected LoggingManagerInternal createLoggingManager() {
-        return getFactory(LoggingManagerInternal.class).create();
-    }
-
-    public ServiceRegistryFactory createFor(Object domainObject) {
-        throw new UnsupportedOperationException();
-    }
-}
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
deleted file mode 100644
index d5b18f4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Project;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.*;
-import org.gradle.api.internal.artifacts.DefaultModule;
-import org.gradle.api.internal.artifacts.DependencyManagementServices;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.classpath.ModuleRegistry;
-import org.gradle.api.internal.classpath.PluginModuleRegistry;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
-import org.gradle.api.internal.initialization.ScriptHandlerFactory;
-import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory;
-import org.gradle.api.internal.project.taskfactory.DependencyAutoWireTaskFactory;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.internal.project.taskfactory.TaskFactory;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.CacheValidator;
-import org.gradle.cache.internal.CacheFactory;
-import org.gradle.cache.internal.DefaultCacheRepository;
-import org.gradle.configuration.*;
-import org.gradle.groovy.scripts.DefaultScriptCompilerFactory;
-import org.gradle.groovy.scripts.ScriptCompilerFactory;
-import org.gradle.groovy.scripts.ScriptExecutionListener;
-import org.gradle.groovy.scripts.internal.*;
-import org.gradle.initialization.*;
-import org.gradle.internal.Factory;
-import org.gradle.internal.TimeProvider;
-import org.gradle.internal.TrueTimeProvider;
-import org.gradle.internal.concurrent.DefaultExecutorFactory;
-import org.gradle.internal.concurrent.ExecutorFactory;
-import org.gradle.internal.id.LongIdGenerator;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.internal.service.ServiceLocator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.messaging.actor.ActorFactory;
-import org.gradle.messaging.actor.internal.DefaultActorFactory;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.process.internal.DefaultWorkerProcessFactory;
-import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
-import org.gradle.profile.ProfileEventAdapter;
-import org.gradle.profile.ProfileListener;
-import org.gradle.util.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.
- */
-public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
-    private final StartParameter startParameter;
-
-    public TopLevelBuildServiceRegistry(final ServiceRegistry parent, final StartParameter startParameter) {
-        super(parent);
-        this.startParameter = startParameter;
-        add(StartParameter.class, startParameter);
-    }
-
-    protected ImportsReader createImportsReader() {
-        return new ImportsReader();
-    }
-
-    protected TimeProvider createTimeProvider() {
-        return new TrueTimeProvider();
-    }
-
-    protected ExecutorFactory createExecutorFactory() {
-        return new DefaultExecutorFactory();
-    }
-
-    protected IProjectFactory createProjectFactory() {
-        return new ProjectFactory(get(Instantiator.class));
-    }
-
-    protected ListenerManager createListenerManager(ListenerManager listenerManager) {
-        return listenerManager.createChild();
-    }
-
-    protected ClassPathRegistry createClassPathRegistry() {
-        return new DefaultClassPathRegistry(
-                new DefaultClassPathProvider(get(ModuleRegistry.class)),
-                new DependencyClassPathProvider(get(ModuleRegistry.class), get(PluginModuleRegistry.class)),
-                new WorkerProcessClassPathProvider(get(CacheRepository.class), get(ModuleRegistry.class)));
-    }
-
-    protected IsolatedAntBuilder createIsolatedAntBuilder() {
-        return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class), get(ClassLoaderFactory.class));
-    }
-
-    protected ActorFactory createActorFactory() {
-        return new DefaultActorFactory(get(ExecutorFactory.class));
-    }
-
-    protected IGradlePropertiesLoader createGradlePropertiesLoader() {
-        return new DefaultGradlePropertiesLoader(startParameter);
-    }
-
-    protected BuildLoader createBuildLoader() {
-        return new ProjectPropertySettingBuildLoader(
-                get(IGradlePropertiesLoader.class),
-                new InstantiatingBuildLoader(get(IProjectFactory.class)));
-    }
-
-    protected CacheFactory createCacheFactory() {
-        return getFactory(CacheFactory.class).create();
-    }
-
-    protected CacheRepository createCacheRepository() {
-        CacheFactory factory = get(CacheFactory.class);
-        return new DefaultCacheRepository(startParameter.getGradleUserHomeDir(), startParameter.getProjectCacheDir(),
-                startParameter.getCacheUsage(), factory);
-    }
-
-    protected ProjectEvaluator createProjectEvaluator() {
-        return new LifecycleProjectEvaluator(
-                new BuildScriptProcessor(
-                        get(ScriptPluginFactory.class)));
-    }
-
-    protected ITaskFactory createITaskFactory() {
-        return new DependencyAutoWireTaskFactory(
-                new AnnotationProcessingTaskFactory(
-                        new TaskFactory(
-                                get(ClassGenerator.class))));
-    }
-
-    protected ScriptCompilerFactory createScriptCompileFactory() {
-        ScriptExecutionListener scriptExecutionListener = get(ListenerManager.class).getBroadcaster(ScriptExecutionListener.class);
-        EmptyScriptGenerator emptyScriptGenerator = new AsmBackedEmptyScriptGenerator();
-        CacheValidator scriptCacheInvalidator = new CacheValidator() {
-            public boolean isValid() {
-                return !get(StartParameter.class).isRecompileScripts();
-            }
-        };
-        return new DefaultScriptCompilerFactory(
-                new CachingScriptClassCompiler(
-                        new ShortCircuitEmptyScriptCompiler(
-                                new FileCacheBackedScriptClassCompiler(
-                                        get(CacheRepository.class),
-                                        scriptCacheInvalidator,
-                                        new DefaultScriptCompilationHandler(
-                                                emptyScriptGenerator)),
-                                emptyScriptGenerator)),
-                new DefaultScriptRunnerFactory(scriptExecutionListener));
-    }
-
-    protected ScriptPluginFactory createScriptObjectConfigurerFactory() {
-        return new DefaultScriptPluginFactory(
-                get(ScriptCompilerFactory.class),
-                get(ImportsReader.class),
-                get(ScriptHandlerFactory.class),
-                get(ClassLoader.class),
-                getFactory(LoggingManagerInternal.class));
-    }
-
-    protected MultiParentClassLoader createRootClassLoader() {
-        return get(ClassLoaderRegistry.class).createScriptClassLoader();
-    }
-
-    protected InitScriptHandler createInitScriptHandler() {
-        return new InitScriptHandler(
-                new DefaultInitScriptProcessor(get(ScriptPluginFactory.class))
-        );
-    }
-
-    protected SettingsProcessor createSettingsProcessor() {
-        return new PropertiesLoadingSettingsProcessor(
-                new ScriptEvaluatingSettingsProcessor(
-                        get(ScriptPluginFactory.class),
-                        new SettingsFactory(
-                                new DefaultProjectDescriptorRegistry(),
-                                get(Instantiator.class)
-                        ),
-                        get(IGradlePropertiesLoader.class)),
-                get(IGradlePropertiesLoader.class));
-    }
-
-    protected ExceptionAnalyser createExceptionAnalyser() {
-        return new MultipleBuildFailuresExceptionAnalyser(new DefaultExceptionAnalyser(get(ListenerManager.class)));
-    }
-
-    protected ScriptHandlerFactory createScriptHandlerFactory() {
-        return new DefaultScriptHandlerFactory(
-                get(DependencyManagementServices.class),
-                get(FileResolver.class),
-                new DependencyMetaDataProviderImpl());
-    }
-
-    protected FileResolver createFileResolver() {
-        return new IdentityFileResolver();
-    }
-
-    protected Factory<WorkerProcessBuilder> createWorkerProcessFactory() {
-        ClassPathRegistry classPathRegistry = get(ClassPathRegistry.class);
-        return new DefaultWorkerProcessFactory(startParameter.getLogLevel(), get(MessagingServer.class), classPathRegistry,
-                new IdentityFileResolver(), new LongIdGenerator());
-    }
-
-    protected BuildConfigurer createBuildConfigurer() {
-        return new DefaultBuildConfigurer();
-    }
-
-    protected ProjectAccessListener createProjectAccessListener() {
-        return new DefaultProjectAccessListener();
-    }
-
-    protected ProfileEventAdapter createProfileEventAdapter() {
-        return new ProfileEventAdapter(get(BuildRequestMetaData.class), get(TimeProvider.class), get(ListenerManager.class).getBroadcaster(ProfileListener.class));
-    }
-
-    protected DependencyManagementServices createDependencyManagementServices() {
-        ClassLoader coreImplClassLoader = get(ClassLoaderRegistry.class).getCoreImplClassLoader();
-        ServiceLocator serviceLocator = new ServiceLocator(coreImplClassLoader);
-        return serviceLocator.getFactory(DependencyManagementServices.class).newInstance(this);
-    }
-
-    public ServiceRegistryFactory createFor(Object domainObject) {
-        if (domainObject instanceof GradleInternal) {
-            return new GradleInternalServiceRegistry(this, (GradleInternal) domainObject);
-        }
-        throw new IllegalArgumentException(String.format("Cannot create services for unknown domain object of type %s.",
-                domainObject.getClass().getSimpleName()));
-    }
-
-    private class DependencyMetaDataProviderImpl implements DependencyMetaDataProvider {
-
-        public Module getModule() {
-            return new DefaultModule("unspecified", "unspecified", Project.DEFAULT_VERSION, Project.DEFAULT_STATUS);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java
index ada6e6d..fe274da 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java
@@ -23,9 +23,6 @@ import org.gradle.api.logging.Logging;
 
 import java.io.PrintStream;
 
-/**
- * @author Hans Dockter
- */
 public class AntLoggingAdapter implements BuildLogger {
     private final Logger logger = Logging.getLogger(AntLoggingAdapter.class);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
index 3786dbf..e1d9b98 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
@@ -94,4 +94,9 @@ public class BasicAntBuilder extends org.gradle.api.AntBuilder {
         }
         return value;
     }
+
+    public void close() {
+        getProject().fireBuildFinished(null);
+    }
+
 }
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 eedcb7d..28059ef 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
@@ -16,16 +16,22 @@
 package org.gradle.api.internal.project.taskfactory;
 
 import org.apache.commons.lang.StringUtils;
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.Task;
-import org.gradle.api.Transformer;
+import org.gradle.api.*;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.TaskArtifactState;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.ContextAwareTaskAction;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
+import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
 import java.lang.annotation.Annotation;
@@ -41,8 +47,8 @@ import java.util.concurrent.Callable;
  */
 public class AnnotationProcessingTaskFactory implements ITaskFactory {
     private final ITaskFactory taskFactory;
-    private final Map<Class, List<Action<Task>>> actionsForType;
-    
+    private final Map<Class, TaskClassInfo> classInfos;
+
     private final Transformer<Iterable<File>, Object> filePropertyTransformer = new Transformer<Iterable<File>, Object>() {
         public Iterable<File> transform(Object original) {
             File file = (File) original;
@@ -56,7 +62,7 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
             return original != null ? (Iterable<File>) original : Collections.<File>emptyList();
         }
     };
-    
+
     private final List<? extends PropertyAnnotationHandler> handlers = Arrays.asList(
             new InputFilePropertyAnnotationHandler(),
             new InputDirectoryPropertyAnnotationHandler(),
@@ -77,66 +83,71 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
 
     public AnnotationProcessingTaskFactory(ITaskFactory taskFactory) {
         this.taskFactory = taskFactory;
-        this.actionsForType = new HashMap<Class, List<Action<Task>>>();
+        this.classInfos = new HashMap<Class, TaskClassInfo>();
     }
 
-    private AnnotationProcessingTaskFactory(Map<Class, List<Action<Task>>> actionsForType, ITaskFactory taskFactory) {
-        this.actionsForType = actionsForType;
+    private AnnotationProcessingTaskFactory(Map<Class, TaskClassInfo> classInfos, ITaskFactory taskFactory) {
+        this.classInfos = classInfos;
         this.taskFactory = taskFactory;
     }
 
     public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator) {
-        return new AnnotationProcessingTaskFactory(actionsForType, taskFactory.createChild(project, instantiator));
+        return new AnnotationProcessingTaskFactory(classInfos, taskFactory.createChild(project, instantiator));
     }
 
     public TaskInternal createTask(Map<String, ?> args) {
         TaskInternal task = taskFactory.createTask(args);
+        TaskClassInfo taskClassInfo = getTaskClassInfo(task.getClass());
+
+        // TODO:DAZ Make this more general purpose, and support IncrementalTaskActions added via another mechanism.
+        if (taskClassInfo.incremental) {
+            // Add a dummy upToDateWhen spec: this will for TaskOutputs.hasOutputs() to be true.
+            task.getOutputs().upToDateWhen(new Spec<Task>() {
+                public boolean isSatisfiedBy(Task element) {
+                    return true;
+                }
+            });
+        }
 
-        Class<? extends Task> type = task.getClass();
-        List<Action<Task>> actions = actionsForType.get(type);
-        if (actions == null) {
-            actions = createActionsForType(type);
-            actionsForType.put(type, actions);
+        for (Factory<Action<Task>> actionFactory : taskClassInfo.taskActions) {
+            task.doFirst(actionFactory.create());
         }
 
-        for (Action<Task> action : actions) {
-            task.doFirst(action);
-            if (action instanceof Validator) {
-                Validator validator = (Validator) action;
-                validator.addInputsAndOutputs(task);
-            }
+        if (taskClassInfo.validator != null) {
+            task.doFirst(taskClassInfo.validator);
+            taskClassInfo.validator.addInputsAndOutputs(task);
         }
 
         return task;
     }
 
-    private List<Action<Task>> createActionsForType(Class<? extends Task> type) {
-        List<Action<Task>> actions = new ArrayList<Action<Task>>();
-        findTaskActions(type, actions);
-        findProperties(type, actions);
-        return actions;
-    }
-
-    private void findProperties(Class<? extends Task> type, List<Action<Task>> actions) {
-        Validator validator = new Validator();
+    private TaskClassInfo getTaskClassInfo(Class<? extends Task> type) {
+        TaskClassInfo taskClassInfo = classInfos.get(type);
+        if (taskClassInfo == null) {
+            taskClassInfo = new TaskClassInfo();
+            findTaskActions(type, taskClassInfo);
 
-        validator.attachActions(null, type);
+            Validator validator = new Validator();
+            validator.attachActions(null, type);
 
-        if (!validator.properties.isEmpty()) {
-            actions.add(validator);
+            if (!validator.properties.isEmpty()) {
+                taskClassInfo.validator = validator;
+            }
+            classInfos.put(type, taskClassInfo);
         }
+        return taskClassInfo;
     }
 
-    private void findTaskActions(Class<? extends Task> type, List<Action<Task>> actions) {
+    private void findTaskActions(Class<? extends Task> type, TaskClassInfo taskClassInfo) {
         Set<String> methods = new HashSet<String>();
         for (Class current = type; current != null; current = current.getSuperclass()) {
             for (Method method : current.getDeclaredMethods()) {
-                attachTaskAction(method, actions, methods);
+                attachTaskAction(method, taskClassInfo, methods);
             }
         }
     }
 
-    private void attachTaskAction(final Method method, Collection<Action<Task>> actions, Collection<String> methods) {
+    private void attachTaskAction(final Method method, TaskClassInfo taskClassInfo, Collection<String> processedMethods) {
         if (method.getAnnotation(TaskAction.class) == null) {
             return;
         }
@@ -144,33 +155,95 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
             throw new GradleException(String.format("Cannot use @TaskAction annotation on static method %s.%s().",
                     method.getDeclaringClass().getSimpleName(), method.getName()));
         }
-        if (method.getParameterTypes().length > 0) {
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+        if (parameterTypes.length > 1) {
             throw new GradleException(String.format(
-                    "Cannot use @TaskAction annotation on method %s.%s() as this method takes parameters.",
+                    "Cannot use @TaskAction annotation on method %s.%s() as this method takes multiple parameters.",
                     method.getDeclaringClass().getSimpleName(), method.getName()));
         }
-        if (methods.contains(method.getName())) {
+
+        if (parameterTypes.length == 1) {
+            if (!parameterTypes[0].equals(IncrementalTaskInputs.class)) {
+                throw new GradleException(String.format(
+                        "Cannot use @TaskAction annotation on method %s.%s() because %s is not a valid parameter to an action method.",
+                        method.getDeclaringClass().getSimpleName(), method.getName(), parameterTypes[0]));
+            }
+            if (taskClassInfo.incremental) {
+                throw new GradleException(String.format("Cannot have multiple @TaskAction methods accepting an %s parameter.", IncrementalTaskInputs.class.getSimpleName()));
+            }
+            taskClassInfo.incremental = true;
+        }
+        if (processedMethods.contains(method.getName())) {
             return;
         }
-        methods.add(method.getName());
-        actions.add(new Action<Task>() {
-            public void execute(Task task) {
-                ClassLoader original = Thread.currentThread().getContextClassLoader();
-                Thread.currentThread().setContextClassLoader(method.getDeclaringClass().getClassLoader());
-                try {
-                    ReflectionUtil.invoke(task, method.getName());
-                } finally {
-                    Thread.currentThread().setContextClassLoader(original);
+        taskClassInfo.taskActions.add(createActionFactory(method, parameterTypes));
+        processedMethods.add(method.getName());
+    }
+
+    private Factory<Action<Task>> createActionFactory(final Method method, final Class<?>[] parameterTypes) {
+        return new Factory<Action<Task>>() {
+            public Action<Task> create() {
+                if (parameterTypes.length == 1) {
+                    return new IncrementalTaskAction(method);
+                } else {
+                    return new StandardTaskAction(method);
                 }
             }
-        });
+        };
     }
 
     private static boolean isGetter(Method method) {
-        return method.getName().startsWith("get") && method.getReturnType() != Void.TYPE
+        return ((method.getName().startsWith("get") && method.getReturnType() != Void.TYPE)
+                || (method.getName().startsWith("is") && method.getReturnType().equals(boolean.class)))
                 && method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers());
     }
 
+    private static class StandardTaskAction implements Action<Task> {
+        private final Method method;
+
+        public StandardTaskAction(Method method) {
+            this.method = method;
+        }
+
+        public void execute(Task task) {
+            ClassLoader original = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(method.getDeclaringClass().getClassLoader());
+            try {
+                doExecute(task, method.getName());
+            } finally {
+                Thread.currentThread().setContextClassLoader(original);
+            }
+        }
+
+        protected void doExecute(Task task, String methodName) {
+            JavaReflectionUtil.method(task, Object.class, methodName).invoke(task);
+        }
+    }
+
+    public static class IncrementalTaskAction extends StandardTaskAction implements ContextAwareTaskAction {
+
+        private TaskArtifactState taskArtifactState;
+
+        public IncrementalTaskAction(Method method) {
+            super(method);
+        }
+
+        public void contextualise(TaskExecutionContext context) {
+            this.taskArtifactState = context == null ? null : context.getTaskArtifactState();
+        }
+
+        protected void doExecute(Task task, String methodName) {
+            JavaReflectionUtil.method(task, Object.class, methodName, IncrementalTaskInputs.class).invoke(task, taskArtifactState.getInputChanges());
+            taskArtifactState = null;
+        }
+    }
+
+    private static class TaskClassInfo {
+        public Validator validator;
+        public List<Factory<Action<Task>>> taskActions = new ArrayList<Factory<Action<Task>>>();
+        public boolean incremental;
+    }
+
     private class Validator implements Action<Task>, TaskValidator {
         private Set<PropertyInfo> properties = new LinkedHashSet<PropertyInfo>();
 
@@ -204,15 +277,23 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
         }
 
         public void attachActions(PropertyInfo parent, Class<?> type) {
-            if (type.getSuperclass() != null) {
-                attachActions(parent, type.getSuperclass());
+            Class<?> superclass = type.getSuperclass();
+            if (!(superclass == null
+                    // Avoid reflecting on classes we know we don't need to look at
+                    || superclass.equals(ConventionTask.class) || superclass.equals(DefaultTask.class)
+                    || superclass.equals(AbstractTask.class) || superclass.equals(Object.class)
+            )) {
+                attachActions(parent, superclass);
             }
+
             for (Method method : type.getDeclaredMethods()) {
                 if (!isGetter(method)) {
                     continue;
                 }
 
-                String fieldName = StringUtils.uncapitalize(method.getName().substring(3));
+                String name = method.getName();
+                int prefixLength = name.startsWith("is") ? 2 : 3; // it's 'get' if not 'is'.
+                String fieldName = StringUtils.uncapitalize(name.substring(prefixLength));
                 String propertyName = fieldName;
                 if (parent != null) {
                     propertyName = parent.getName() + '.' + propertyName;
@@ -307,7 +388,7 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
             this.validator = validator;
             this.parent = parent;
             this.propertyName = propertyName;
-            this.method = method;   
+            this.method = method;
         }
 
         @Override
@@ -366,7 +447,12 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
                 bean = parentValue.getValue();
             }
 
-            final Object value = ReflectionUtil.invoke(bean, method.getName());
+            final Object finalBean = bean;
+            final Object value = DeprecationLogger.whileDisabled(new Factory<Object>() {
+                public Object create() {
+                    return JavaReflectionUtil.method(finalBean, Object.class, method).invoke(finalBean);
+                }
+            });
 
             return new PropertyValue() {
                 public Object getValue() {
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 f2dc175..c18d79a 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
@@ -21,9 +21,6 @@ import org.gradle.internal.reflect.Instantiator;
 
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public interface ITaskFactory {
     public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator);
 
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 16874d6..e3fef78 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
@@ -30,17 +30,17 @@ import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.gradle.util.GUtil;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 
-/**
- * @author Hans Dockter
- */
 public class TaskFactory implements ITaskFactory {
     public static final String GENERATE_SUBCLASS = "generateSubclass";
     private final ClassGenerator generator;
     private final ProjectInternal project;
     private final Instantiator instantiator;
+    private final Set<String> validTaskArguments;
 
     public TaskFactory(ClassGenerator generator) {
         this(generator, null, null);
@@ -50,6 +50,17 @@ public class TaskFactory implements ITaskFactory {
         this.generator = generator;
         this.project = project;
         this.instantiator = instantiator;
+
+
+        validTaskArguments = new HashSet<String>();
+        validTaskArguments.add(Task.TASK_ACTION);
+        validTaskArguments.add(Task.TASK_DEPENDS_ON);
+        validTaskArguments.add(Task.TASK_DESCRIPTION);
+        validTaskArguments.add(Task.TASK_GROUP);
+        validTaskArguments.add(Task.TASK_NAME);
+        validTaskArguments.add(Task.TASK_OVERWRITE);
+        validTaskArguments.add(Task.TASK_TYPE);
+
     }
 
     public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator) {
@@ -120,6 +131,7 @@ public class TaskFactory implements ITaskFactory {
     }
 
     private void checkTaskArgsAndCreateDefaultValues(Map<String, Object> args) {
+        validateArgs(args);
         setIfNull(args, Task.TASK_NAME, "");
         setIfNull(args, Task.TASK_TYPE, DefaultTask.class);
         if (((Class) args.get(Task.TASK_TYPE)).isAssignableFrom(DefaultTask.class)) {
@@ -128,6 +140,15 @@ public class TaskFactory implements ITaskFactory {
         setIfNull(args, GENERATE_SUBCLASS, "true");
     }
 
+    private void validateArgs(Map<String, Object> args) {
+        if (!validTaskArguments.containsAll(args.keySet())) {
+            Map unknownArguments = new HashMap<String, Object>(args);
+            unknownArguments.keySet().removeAll(validTaskArguments);
+            throw new InvalidUserDataException(String.format("Could not create task '%s': Unknown argument(s) in task definition: %s",
+                        args.get(Task.TASK_NAME), unknownArguments.keySet()));
+        }
+    }
+
     private void setIfNull(Map<String, Object> map, String key, Object defaultValue) {
         if (map.get(key) == null) {
             map.put(key, defaultValue);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
index 9ad8dc6..9b21136 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.resource;
 
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 @Contextual
 public class ResourceException extends GradleException {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java
index 453c454..450cf92 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java
@@ -23,9 +23,6 @@ import org.gradle.api.internal.file.archive.compression.GzipArchiver;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.api.resources.ResourceHandler;
 
-/**
- * by Szczepan Faber, created at: 11/24/11
- */
 public class DefaultResourceHandler implements ResourceHandler {
     private final FileResolver resolver;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java
index 288ca63..2059269 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java
@@ -20,9 +20,6 @@ import org.gradle.util.GUtil;
 
 import java.net.URI;
 
-/**
- * by Szczepan Faber, created at: 12/13/11
- */
 public class URIBuilder {
     private final URI uri;
     private String schemePrefix = "";
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java
index 8747618..e4a03d0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java
@@ -16,9 +16,6 @@
 
 package org.gradle.api.internal.specs;
 
-/**
- * by Szczepan Faber, created at: 5/14/12
- */
 public class ExplainingSpecs {
 
     private static final ExplainingSpec<Object> SATISFIES_ALL = new ExplainingSpec<Object>() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java
index e235a3a..c993c4f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java
@@ -19,8 +19,8 @@ package org.gradle.api.internal.tasks;
 import org.gradle.api.Buildable;
 import org.gradle.api.GradleException;
 import org.gradle.api.Task;
-import org.gradle.api.internal.CachingDirectedGraphWalker;
-import org.gradle.api.internal.DirectedGraph;
+import org.gradle.internal.graph.CachingDirectedGraphWalker;
+import org.gradle.internal.graph.DirectedGraph;
 import org.gradle.api.tasks.TaskDependency;
 
 import java.util.*;
@@ -79,7 +79,7 @@ public class CachingTaskDependencyResolveContext implements TaskDependencyResolv
     }
 
     private class TaskGraphImpl implements DirectedGraph<Object, Task> {
-        public void getNodeValues(Object node, Collection<Task> values, Collection<Object> connectedNodes) {
+        public void getNodeValues(Object node, Collection<? super Task> values, Collection<? super Object> connectedNodes) {
             if (node instanceof TaskDependencyInternal) {
                 TaskDependencyInternal taskDependency = (TaskDependencyInternal) node;
                 queue.clear();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CommandLineOption.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CommandLineOption.java
deleted file mode 100644
index c68b1d5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/CommandLineOption.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import java.lang.annotation.*;
-
-/**
- * Marks a property as available from the command-line.
- */
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.METHOD)
- at Inherited
-public @interface CommandLineOption {
-    /**
-     * The command-line options to map to this property.
-     *
-     * @return The command-line options.
-     */
-    String[] options();
-
-    /**
-     * The description of this command-line option.
-     *
-     * @return The description.
-     */
-    String description();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/ContextAwareTaskAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/ContextAwareTaskAction.java
new file mode 100644
index 0000000..1b77f0c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/ContextAwareTaskAction.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+
+public interface ContextAwareTaskAction extends Action<Task> {
+    void contextualise(TaskExecutionContext context);
+}
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 eb4cbde..675bb29 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
@@ -17,29 +17,26 @@ package org.gradle.api.internal.tasks;
 
 import groovy.lang.Closure;
 import org.apache.commons.lang.StringUtils;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.UnknownTaskException;
-import org.gradle.api.internal.CachingDirectedGraphWalker;
-import org.gradle.api.internal.DirectedGraph;
+import org.gradle.api.*;
 import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.internal.NamedDomainObjectContainerConfigureDelegate;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.initialization.ProjectAccessListener;
+import org.gradle.internal.Transformers;
+import org.gradle.internal.graph.CachingDirectedGraphWalker;
+import org.gradle.internal.graph.DirectedGraph;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GUtil;
 
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements TaskContainerInternal {
     private final ITaskFactory taskFactory;
     private final ProjectAccessListener projectAccessListener;
+    private Map<String, Runnable> placeholders = new HashMap<String, Runnable>();
 
     public DefaultTaskContainer(ProjectInternal project, Instantiator instantiator, ITaskFactory taskFactory, ProjectAccessListener projectAccessListener) {
         super(Task.class, instantiator, project);
@@ -47,7 +44,7 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         this.projectAccessListener = projectAccessListener;
     }
 
-    public Task add(Map<String, ?> options) {
+    public Task create(Map<String, ?> options) {
         Map<String, Object> mutableOptions = new HashMap<String, Object>(options);
 
         Object replaceStr = mutableOptions.remove(Task.TASK_OVERWRITE);
@@ -71,16 +68,45 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         return task;
     }
 
+    public <U extends Task> U maybeCreate(String name, Class<U> type) throws InvalidUserDataException {
+        Task existing = findByName(name);
+        if (existing != null) {
+            return Transformers.cast(type).transform(existing);
+        }
+        return create(name, type);
+    }
+
+    public Task add(Map<String, ?> options) {
+        DeprecationLogger.nagUserOfReplacedMethod("TaskContainer.add()", "create()");
+        return create(options);
+    }
+
+    public Task create(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException {
+        return create(options).configure(configureClosure);
+    }
+
     public Task add(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException {
-        return add(options).configure(configureClosure);
+        DeprecationLogger.nagUserOfReplacedMethod("TaskContainer.add()", "create()");
+        return create(options, configureClosure);
+    }
+
+    public <T extends Task> T create(String name, Class<T> type) {
+        return type.cast(create(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type)));
     }
 
     public <T extends Task> T add(String name, Class<T> type) {
-        return type.cast(add(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type)));
+        DeprecationLogger.nagUserOfReplacedMethod("TaskContainer.add()", "create()");
+        return create(name, type);
     }
 
     public Task create(String name) {
-        return add(name);
+        return create(GUtil.map(Task.TASK_NAME, name));
+    }
+
+    public Task create(String name, Action<? super Task> configureAction) throws InvalidUserDataException {
+        Task task = create(name);
+        configureAction.execute(task);
+        return task;
     }
 
     public Task maybeCreate(String name) {
@@ -92,23 +118,31 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
     }
 
     public Task add(String name) {
-        return add(GUtil.map(Task.TASK_NAME, name));
+        DeprecationLogger.nagUserOfReplacedMethod("TaskContainer.add()", "create()");
+        return create(name);
     }
 
     public Task replace(String name) {
-        return add(GUtil.map(Task.TASK_NAME, name, Task.TASK_OVERWRITE, true));
+        return create(GUtil.map(Task.TASK_NAME, name, Task.TASK_OVERWRITE, true));
     }
 
     public Task create(String name, Closure configureClosure) {
-        return add(name, configureClosure);
+        return create(name).configure(configureClosure);
     }
 
     public Task add(String name, Closure configureClosure) {
-        return add(GUtil.map(Task.TASK_NAME, name)).configure(configureClosure);
+        DeprecationLogger.nagUserOfReplacedMethod("TaskContainer.add()", "create()");
+        return create(name, configureClosure);
+    }
+
+    public <T extends Task> T create(String name, Class<T> type, Action<? super T> configuration) throws InvalidUserDataException {
+        T task = create(name, type);
+        configuration.execute(task);
+        return task;
     }
 
     public <T extends Task> T replace(String name, Class<T> type) {
-        return type.cast(add(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type, Task.TASK_OVERWRITE, true)));
+        return type.cast(create(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type, Task.TASK_OVERWRITE, true)));
     }
 
     public Task findByPath(String path) {
@@ -133,7 +167,7 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         if (!GUtil.isTrue(path)) {
             throw new InvalidUserDataException("A path must be specified!");
         }
-        if(!(path instanceof CharSequence)) {
+        if (!(path instanceof CharSequence)) {
             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"
@@ -150,12 +184,8 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         return task;
     }
 
-    protected Object createConfigureDelegate(Closure configureClosure) {
-        return new NamedDomainObjectContainerConfigureDelegate(configureClosure.getOwner(), this);
-    }
-
     public TaskContainerInternal configure(Closure configureClosure) {
-        ConfigureUtil.configure(configureClosure, createConfigureDelegate(configureClosure));
+        ConfigureUtil.configure(configureClosure, new NamedDomainObjectContainerConfigureDelegate(configureClosure.getOwner(), this));
         return this;
     }
 
@@ -163,11 +193,55 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         return getElementsAsDynamicObject();
     }
 
+    public SortedSet<String> getNames() {
+        SortedSet<String> set = new TreeSet<String>();
+        for (Task o : getStore()) {
+            set.add(o.getName());
+        }
+        for (String placeHolderName : placeholders.keySet()) {
+            set.add(placeHolderName);
+        }
+        return set;
+    }
+
     public void actualize() {
         new CachingDirectedGraphWalker<Task, Void>(new DirectedGraph<Task, Void>() {
-            public void getNodeValues(Task node, Collection<Void> values, Collection<Task> connectedNodes) {
+            public void getNodeValues(Task node, Collection<? super Void> values, Collection<? super Task> connectedNodes) {
                 connectedNodes.addAll(node.getTaskDependencies().getDependencies(node));
             }
         }).add(this).findValues();
+
+
+        final HashSet<String> placeholderNames = new HashSet<String>(placeholders.keySet());
+        for (String placeholder : placeholderNames) {
+            maybeMaterializePlaceholder(placeholder);
+        }
+
+    }
+
+    public Map<String, Runnable> getPlaceholderActions() {
+        return placeholders;
+    }
+
+    public Task findByName(String name) {
+        Task task = super.findByName(name);
+        if (task != null) {
+            return task;
+        }
+        maybeMaterializePlaceholder(name);
+        return super.findByName(name);
+    }
+
+    private void maybeMaterializePlaceholder(String name) {
+        if (placeholders.containsKey(name)) {
+            if (super.findByName(name) == null) {
+                final Runnable placeholderAction = placeholders.remove(name);
+                placeholderAction.run();
+            }
+        }
+    }
+
+    public void addPlaceholderAction(String placeholderName, Runnable runnable) {
+        placeholders.put(placeholderName, runnable);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/SimpleWorkResult.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/SimpleWorkResult.java
new file mode 100644
index 0000000..5d52b22
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/SimpleWorkResult.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.tasks.WorkResult;
+
+public class SimpleWorkResult implements WorkResult {
+    private final boolean didWork;
+
+    public SimpleWorkResult(boolean didWork) {
+        this.didWork = didWork;
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
index 5a4e57c..b75a114 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
@@ -18,15 +18,35 @@ package org.gradle.api.internal.tasks;
 import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.tasks.TaskContainer;
 
+import java.util.Map;
+
 public interface TaskContainerInternal extends TaskContainer, TaskResolver {
+
+    // The path to the project's task container in the model registry
+    public String MODEL_PATH = "tasks";
+
     DynamicObject getTasksAsDynamicObject();
 
     /**
+     * <p>Add placeholder action if task is referenced by name that does not (yet) exist.
+     * If a task is referenced by name and not listed as task, the provided action is executed and the task name is looked up again before proceeding
+     * This allows lazy application of plugins if task is referenced but not yet part of the taskcontainer.</p>
+     *
+     * @param placeholderName the placeholderName that references the placeholder action.
+     * @param runnable the Runnable executed when referencing a task that does not exist, but a placeholder with the given name is defined.
+     */
+    void addPlaceholderAction(String placeholderName, Runnable runnable);
+
+    /**
      * Force the entire graph to come into existence.
      *
-     * Tasks may have dependencies that are abstract (e.g. a dependency on a task _name_). Calling this method
-     * will force all task dependencies to be actualised, which may mean new tasks are created because of things
-     * like task rules etc.
+     * Tasks may have dependencies that are abstract (e.g. a dependency on a task _name_).
+     * Calling this method will force all task dependencies to be actualised, which may mean new tasks are
+     * created because of things like task rules etc.
+     *
+     * As part of this, all placeholder actions are materialized to show up in 'tasks' and 'tasks --all' overview.
      */
     void actualize();
+
+    Map<String, Runnable> getPlaceholderActions();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java
index 08aa32a..56a0310 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java
@@ -22,5 +22,5 @@ public interface TaskExecuter {
      * Executes the given task. If the task fails with an exception, the exception is packaged in the provided task
      * state.
      */
-    void execute(TaskInternal task, TaskStateInternal state);
+    void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecutionContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecutionContext.java
new file mode 100644
index 0000000..972c919
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecutionContext.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+
+public interface TaskExecutionContext {
+    TaskArtifactState getTaskArtifactState();
+    void setTaskArtifactState(TaskArtifactState taskArtifactState);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java
index d9602f1..74533c0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java
@@ -17,7 +17,6 @@
 package org.gradle.api.internal.tasks;
 
 import groovy.util.ObservableList;
-import org.gradle.api.Action;
 import org.gradle.api.Task;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.util.DeprecationLogger;
@@ -70,8 +69,8 @@ public class TaskStatusNagger {
         }
     }
 
-    public Action<Task> leftShift(final Action<? super Task> action) {
-        return new Action<Task>() {
+    public ContextAwareTaskAction leftShift(final ContextAwareTaskAction action) {
+        return new ContextAwareTaskAction() {
             public void execute(Task task) {
                 executingleftShiftAction = true;
                 try {
@@ -80,6 +79,10 @@ public class TaskStatusNagger {
                     executingleftShiftAction = false;
                 }
             }
+
+            public void contextualise(TaskExecutionContext context) {
+                action.contextualise(context);
+            }
         };
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java
new file mode 100644
index 0000000..8727b82
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.execution;
+
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
+
+public class DefaultTaskExecutionContext implements TaskExecutionContext {
+    private TaskArtifactState taskArtifactState;
+
+    public TaskArtifactState getTaskArtifactState() {
+        return taskArtifactState;
+    }
+
+    public void setTaskArtifactState(TaskArtifactState taskArtifactState) {
+        this.taskArtifactState = taskArtifactState;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
index d3b4dc5..7bad7bf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
@@ -15,13 +15,10 @@
  */
 package org.gradle.api.internal.tasks.execution;
 
-import org.gradle.api.Action;
 import org.gradle.api.GradleException;
-import org.gradle.api.Task;
 import org.gradle.api.execution.TaskActionListener;
 import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.api.internal.tasks.*;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.tasks.StopActionException;
@@ -42,11 +39,11 @@ public class ExecuteActionsTaskExecuter implements TaskExecuter {
         this.listener = listener;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         listener.beforeActions(task);
         state.setExecuting(true);
         try {
-            GradleException failure = executeActions(task, state);
+            GradleException failure = executeActions(task, state, context);
             state.executed(failure);
         } finally {
             state.setExecuting(false);
@@ -54,14 +51,14 @@ public class ExecuteActionsTaskExecuter implements TaskExecuter {
         }
     }
 
-    private GradleException executeActions(TaskInternal task, TaskStateInternal state) {
+    private GradleException executeActions(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         logger.debug("Executing actions for {}.", task);
-        final List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>(task.getActions());
-        for (Action<? super Task> action : actions) {
+        final List<ContextAwareTaskAction> actions = new ArrayList<ContextAwareTaskAction>(task.getTaskActions());
+        for (ContextAwareTaskAction action : actions) {
             state.setDidWork(true);
             task.getStandardOutputCapture().start();
             try {
-                action.execute(task);
+                executeAction(task, action, context);
             } catch (StopActionException e) {
                 // Ignore
                 logger.debug("Action stopped by some action with message: {}", e.getMessage());
@@ -76,4 +73,13 @@ public class ExecuteActionsTaskExecuter implements TaskExecuter {
         }
         return null;
     }
+
+    private void executeAction(TaskInternal task, ContextAwareTaskAction action, TaskExecutionContext context) {
+        action.contextualise(context);
+        try {
+            action.execute(task);
+        } finally {
+            action.contextualise(null);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuter.java
index 9744ee5..a51fa5a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuter.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.execution;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -33,13 +34,13 @@ public class ExecuteAtMostOnceTaskExecuter implements TaskExecuter {
         this.executer = executer;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         if (state.getExecuted()) {
             return;
         }
         LOGGER.debug("Starting to execute {}", task);
         try {
-            executer.execute(task, state);
+            executer.execute(task, state, context);
         } finally {
             state.executed();
             LOGGER.debug("Finished executing {}", task);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuter.java
index e866788..022448f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuter.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.execution;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 
 /**
@@ -30,8 +31,8 @@ public class PostExecutionAnalysisTaskExecuter implements TaskExecuter {
         this.executer = executer;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
-        executer.execute(task, state);
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
+        executer.execute(task, state, context);
         if (!state.getDidWork()) {
             state.upToDate();
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java
index e847392..b6ecff7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.tasks.execution;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -32,12 +33,12 @@ public class SkipEmptySourceFilesTaskExecuter implements TaskExecuter {
         this.executer = executer;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         if (task.getInputs().getHasSourceFiles() && task.getInputs().getSourceFiles().isEmpty()) {
             LOGGER.info("Skipping {} as it has no source files.", task);
             state.upToDate();
             return;
         }
-        executer.execute(task, state);
+        executer.execute(task, state, context);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java
index e56584d..8549818 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java
@@ -19,6 +19,7 @@ package org.gradle.api.internal.tasks.execution;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -34,7 +35,7 @@ public class SkipOnlyIfTaskExecuter implements TaskExecuter {
         this.executer = executer;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         boolean skip;
         try {
             skip = !task.getOnlyIf().isSatisfiedBy(task);
@@ -49,6 +50,6 @@ public class SkipOnlyIfTaskExecuter implements TaskExecuter {
             return;
         }
 
-        executer.execute(task, state);
+        executer.execute(task, state, context);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java
index 088b2b6..5cf62a3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.execution;
 import org.gradle.api.Task;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -33,7 +34,7 @@ public class SkipTaskWithNoActionsExecuter implements TaskExecuter {
         this.executer = executer;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         if (task.getActions().isEmpty()) {
             LOGGER.info("Skipping {} as it has no actions.", task);
             boolean upToDate = true;
@@ -48,6 +49,6 @@ public class SkipTaskWithNoActionsExecuter implements TaskExecuter {
             }
             return;
         }
-        executer.execute(task, state);
+        executer.execute(task, state, context);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuter.java
index e478e61..297eaac 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuter.java
@@ -20,10 +20,16 @@ import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.changedetection.TaskArtifactState;
 import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.util.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+
 /**
  * A {@link TaskExecuter} which skips tasks whose outputs are up-to-date.
  */
@@ -32,35 +38,52 @@ public class SkipUpToDateTaskExecuter implements TaskExecuter {
     private final TaskExecuter executer;
     private final TaskArtifactStateRepository repository;
 
-    public SkipUpToDateTaskExecuter(TaskExecuter executer, TaskArtifactStateRepository repository) {
+    public SkipUpToDateTaskExecuter(TaskArtifactStateRepository repository, TaskExecuter executer) {
         this.executer = executer;
         this.repository = repository;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         LOGGER.debug("Determining if {} is up-to-date", task);
+        Clock clock = new Clock();
         TaskArtifactState taskArtifactState = repository.getStateFor(task);
         try {
-            if (taskArtifactState.isUpToDate()) {
-                LOGGER.info("Skipping {} as it is up-to-date", task);
+            List<String> messages = new ArrayList<String>();
+            if (taskArtifactState.isUpToDate(messages)) {
+                LOGGER.info("Skipping {} as it is up-to-date (took {}).", task, clock.getTime());
                 state.upToDate();
                 return;
-
             }
-            LOGGER.debug("{} is not up-to-date", task);
+            logOutOfDateMessages(messages, task, clock.getTime());
 
-            taskArtifactState.beforeTask();
             task.getOutputs().setHistory(taskArtifactState.getExecutionHistory());
+            context.setTaskArtifactState(taskArtifactState);
+
+            taskArtifactState.beforeTask();
             try {
-                executer.execute(task, state);
+                executer.execute(task, state, context);
                 if (state.getFailure() == null) {
                     taskArtifactState.afterTask();
                 }
             } finally {
                 task.getOutputs().setHistory(null);
+                context.setTaskArtifactState(null);
             }
         } finally {
             taskArtifactState.finished();
         }
     }
+
+
+    private void logOutOfDateMessages(List<String> messages, TaskInternal task, String took) {
+        if (LOGGER.isInfoEnabled()) {
+            Formatter formatter = new Formatter();
+            formatter.format("Executing %s (up-to-date check took %s) due to:", task, took);
+            for (String message : messages) {
+                formatter.format("%n  %s", message);
+            }
+            LOGGER.info(formatter.toString());
+        }
+    }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java
index 67927c2..63ca339 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.execution;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.tasks.TaskValidationException;
 
@@ -34,7 +35,7 @@ public class ValidatingTaskExecuter implements TaskExecuter {
         this.executer = executer;
     }
 
-    public void execute(TaskInternal task, TaskStateInternal state) {
+    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
         List<String> messages = new ArrayList<String>();
         for (TaskValidator validator : task.getValidators()) {
             validator.validate(task, messages);
@@ -54,6 +55,6 @@ public class ValidatingTaskExecuter implements TaskExecuter {
             state.executed(new TaskValidationException(errorMessage, causes));
             return;
         }
-        executer.execute(task, state);
+        executer.execute(task, state, context);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/AbstractOptionElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/AbstractOptionElement.java
new file mode 100644
index 0000000..f6b08a6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/AbstractOptionElement.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.internal.reflect.JavaMethod;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.ValueAwareNotationParser;
+
+import java.lang.annotation.IncompleteAnnotationException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class AbstractOptionElement implements OptionElement {
+    private final String optionName;
+    private final String description;
+    private final Class<?> optionType;
+    private final ValueAwareNotationParser<?> notationParser;
+
+    public AbstractOptionElement(String optionName, Option option, Class<?> optionType, Class<?> declaringClass, ValueAwareNotationParser<?> notationParser) {
+        this.description = readDescription(option, optionName, declaringClass);
+        this.optionName = optionName;
+        this.optionType = optionType;
+        this.notationParser = notationParser;
+    }
+
+    public List<String> getAvailableValues() {
+        List<String> describes = new ArrayList<String>();
+        notationParser.describeValues(describes);
+        return describes;
+    }
+
+    public Class<?> getOptionType() {
+        return optionType;
+    }
+
+    private String readDescription(Option option, String optionName, Class<?> declaringClass) {
+        try {
+            return option.description();
+        } catch (IncompleteAnnotationException ex) {
+            throw new OptionValidationException(String.format("No description set on option '%s' at for class '%s'.", optionName, declaringClass.getName()));
+        }
+    }
+
+    protected Object invokeMethod(Object object, Method method, Object... parameterValues) {
+        final JavaMethod<Object, Object> javaMethod = JavaReflectionUtil.method(Object.class, Object.class, method);
+        return javaMethod.invoke(object, parameterValues);
+    }
+
+    public String getOptionName() {
+        return optionName;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    protected NotationParser<CharSequence, ?> getNotationParser() {
+        return notationParser;
+    }
+
+    protected static ValueAwareNotationParser<Object> createNotationParserOrFail(OptionNotationParserFactory optionNotationParserFactory, String optionName, Class<?> optionType, Class<?> declaringClass) {
+        try {
+            return optionNotationParserFactory.toComposite(optionType);
+        } catch (OptionValidationException ex) {
+            throw new OptionValidationException(String.format("Option '%s' cannot be casted to type '%s' in class '%s'.",
+                    optionName, optionType.getName(), declaringClass.getName()));
+        }
+    }
+
+    protected static Class<?> calculateOptionType(Class<?> type) {
+        //we don't want to support "--flag true" syntax
+        if (type == Boolean.class || type == Boolean.TYPE) {
+            return Void.TYPE;
+        } else {
+            return type;
+        }
+    }
+
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/FieldOptionElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/FieldOptionElement.java
new file mode 100644
index 0000000..937f655
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/FieldOptionElement.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.internal.typeconversion.ValueAwareNotationParser;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class FieldOptionElement extends AbstractOptionElement {
+
+    public static FieldOptionElement create(Option option, Field field, OptionNotationParserFactory optionNotationParserFactory){
+        String optionName = calOptionName(option, field);
+        Class<?> optionType = calculateOptionType(field.getType());
+        ValueAwareNotationParser<?> notationParser = createNotationParserOrFail(optionNotationParserFactory, optionName, optionType, field.getDeclaringClass());
+        return new FieldOptionElement(field, optionName, option, optionType, notationParser);
+    }
+
+    private final Field field;
+
+    public FieldOptionElement(Field field, String optionName, Option option, Class<?> optionType, ValueAwareNotationParser<?> notationParser) {
+        super(optionName, option, optionType, field.getDeclaringClass(), notationParser);
+        this.field = field;
+        getSetter();
+    }
+
+    private static String calOptionName(Option option, Field field) {
+        if (option.option().length() == 0) {
+            return field.getName();
+        } else {
+            return option.option();
+        }
+    }
+
+    private Method getSetter() {
+        try{
+            String setterName = "set" + StringUtils.capitalize(field.getName());
+            return field.getDeclaringClass().getMethod(setterName, field.getType());
+        } catch (NoSuchMethodException e) {
+            throw new OptionValidationException(String.format("No setter for Option annotated field '%s' in class '%s'.",
+                    getElementName(), getDeclaredClass()));
+        }
+    }
+
+    public String getElementName() {
+        return field.getName();
+    }
+
+    public Class<?> getDeclaredClass() {
+        return field.getDeclaringClass();
+    }
+
+    public void apply(Object object, List<String> parameterValues) {
+        if (getOptionType() == Void.TYPE && parameterValues.size() == 0) {
+            setFieldValue(object, true);
+        } else if (parameterValues.size() > 1) {
+            throw new IllegalArgumentException(String.format("Lists not supported for option"));
+        } else {
+            Object arg = getNotationParser().parseNotation(parameterValues.get(0));
+            setFieldValue(object, arg);
+        }
+    }
+
+    private void setFieldValue(Object object, Object value) {
+            Method setter = getSetter();
+            invokeMethod(object, setter, value);
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/InstanceOptionDescriptor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/InstanceOptionDescriptor.java
new file mode 100644
index 0000000..5a7e261
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/InstanceOptionDescriptor.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.internal.reflect.JavaMethod;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class InstanceOptionDescriptor implements OptionDescriptor {
+
+    private final Object object;
+    private final OptionElement optionElement;
+    private final JavaMethod<Object, Collection> optionValueMethod;
+
+    InstanceOptionDescriptor(Object object, OptionElement optionElement) {
+        this(object, optionElement, null);
+    }
+
+    public InstanceOptionDescriptor(Object object, OptionElement optionElement, JavaMethod<Object, Collection> optionValueMethod) {
+        this.object = object;
+        this.optionElement = optionElement;
+        this.optionValueMethod = optionValueMethod;
+    }
+
+    public OptionElement getOptionElement() {
+        return optionElement;
+    }
+
+    public String getName() {
+        return optionElement.getOptionName();
+    }
+
+    public List<String> getAvailableValues() {
+        final List<String> values = optionElement.getAvailableValues();
+
+        if (getArgumentType().isAssignableFrom(String.class)) {
+            values.addAll(readDynamicAvailableValues());
+        }
+        return values;
+    }
+
+    public Class<?> getArgumentType() {
+        return optionElement.getOptionType();
+    }
+
+    private List<String> readDynamicAvailableValues() {
+        if (optionValueMethod != null) {
+            Collection values = optionValueMethod.invoke(object);
+            return CollectionUtils.toStringList(values);
+        }
+        return Collections.emptyList();
+    }
+
+    public String getDescription() {
+        return optionElement.getDescription();
+    }
+
+    public void apply(Object objectParam, List<String> parameterValues) {
+        if (objectParam != object) {
+            throw new AssertionError(String.format("Object %s not applyable. Expecting %s", objectParam, object));
+        }
+        optionElement.apply(objectParam, parameterValues);
+    }
+
+    public int compareTo(OptionDescriptor o) {
+        return getName().compareTo(o.getName());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/MethodOptionElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/MethodOptionElement.java
new file mode 100644
index 0000000..10ff223
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/MethodOptionElement.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.internal.typeconversion.ValueAwareNotationParser;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class MethodOptionElement extends AbstractOptionElement {
+
+    private final Method method;
+
+    MethodOptionElement(Option option, Method method, Class<?> optionType, ValueAwareNotationParser<?> notationParser) {
+        super(option.option(), option, optionType, method.getDeclaringClass(), notationParser);
+        this.method = method;
+        assertMethodTypeSupported(getOptionName(), method);
+        assertValidOptionName();
+    }
+
+    private void assertValidOptionName() {
+        if (getOptionName()== null || getOptionName().length() == 0) {
+            throw new OptionValidationException(String.format("No option name set on '%s' in class '%s'.", getElementName(), getDeclaredClass().getName()));
+        }
+    }
+
+    public Class<?> getDeclaredClass() {
+        return method.getDeclaringClass();
+    }
+
+    public String getElementName() {
+        return method.getName();
+    }
+
+    public void apply(Object object, List<String> parameterValues) {
+        if (parameterValues.size() == 0) {
+            invokeMethod(object, method, true);
+        } else if (parameterValues.size() > 1) {
+            throw new IllegalArgumentException(String.format("Lists not supported for option."));
+        } else {
+            invokeMethod(object, method, getNotationParser().parseNotation(parameterValues.get(0)));
+        }
+    }
+
+    public static MethodOptionElement create(Option option, Method method, OptionNotationParserFactory optionNotationParserFactory){
+        Class<?> optionType = calculateOptionType(method);
+        ValueAwareNotationParser<?> notationParser = createNotationParserOrFail(optionNotationParserFactory, option.option(), optionType, method.getDeclaringClass());
+        return new MethodOptionElement(option, method, optionType, notationParser);
+    }
+
+
+    private static Class<?> calculateOptionType(Method optionMethod) {
+        if (optionMethod.getParameterTypes().length == 0) {
+            return Void.TYPE;
+        } else {
+            return calculateOptionType(optionMethod.getParameterTypes()[0]);
+        }
+    }
+
+    private static void assertMethodTypeSupported(String optionName, Method method) {
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+        if (parameterTypes.length > 1) {
+            throw new OptionValidationException(String.format("Option '%s' cannot be linked to methods with multiple parameters in class '%s#%s'.",
+                    optionName, method.getDeclaringClass().getName(), method.getName()));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/Option.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/Option.java
new file mode 100644
index 0000000..cce0e7d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/Option.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.options;
+
+import java.lang.annotation.*;
+
+/**
+ * Marks a property as available from the command-line.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+ at Inherited
+public @interface Option {
+    /**
+     * The option to map to this property.
+     *
+     * @return The option.
+     */
+    String option() default "";
+
+    /**
+     * The description of this option.
+     *
+     * @return The description.
+     */
+    String description();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionDescriptor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionDescriptor.java
new file mode 100644
index 0000000..6ec301c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionDescriptor.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.internal.typeconversion.TypeConversionException;
+
+import java.util.List;
+
+public interface OptionDescriptor extends Comparable<OptionDescriptor> {
+
+    String getName();
+
+    Class<?> getArgumentType();
+
+    List<String> getAvailableValues();
+
+    String getDescription();
+
+    /**
+     * @throws TypeConversionException On failure to convert the given values to the required types.
+     */
+    void apply(Object object, List<String> values) throws TypeConversionException;
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionElement.java
new file mode 100644
index 0000000..1f9a179
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionElement.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.internal.typeconversion.TypeConversionException;
+
+import java.util.List;
+
+public interface OptionElement {
+    Class<?> getDeclaredClass();
+
+    List<String> getAvailableValues();
+
+    Class<?> getOptionType();
+
+    String getElementName();
+
+    String getOptionName();
+
+    /**
+     * @throws TypeConversionException On failure to convert the supplied values to the appropriate target types.
+     */
+    void apply(Object object, List<String> parameterValues) throws TypeConversionException;
+
+    String getDescription();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionNotationParserFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionNotationParserFactory.java
new file mode 100644
index 0000000..535bd40
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionNotationParserFactory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.internal.typeconversion.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class OptionNotationParserFactory {
+    public ValueAwareNotationParser<Object> toComposite(Class<?> targetType) throws OptionValidationException {
+        assert targetType != null : "resultingType cannot be null";
+        List<ValueAwareNotationParser<?>> parsers = new ArrayList<ValueAwareNotationParser<?>>();
+
+        if (targetType == Void.TYPE) {
+            parsers.add(new UnsupportedNotationParser());
+        }
+        if (targetType.isAssignableFrom(String.class)) {
+            parsers.add(new NoDescriptionValuesJustReturningParser<Object>(targetType));
+        }
+        if (targetType.isEnum()) {
+            parsers.add(new NoDescriptionValuesJustReturningParser<Object>(targetType));
+            parsers.add(new EnumFromCharSequenceNotationParser<Enum>(targetType.asSubclass(Enum.class)));
+        }
+        if (parsers.isEmpty()) {
+            throw new OptionValidationException(String.format("Don't know how to convert strings to type '%s'.", targetType.getName()));
+        }
+        return new ValueAwareCompositeNotationParser<Object>(parsers);
+    }
+
+    private class UnsupportedNotationParser implements ValueAwareNotationParser<Object> {
+
+        public Object parseNotation(CharSequence notation) throws UnsupportedNotationException, TypeConversionException {
+            throw new UnsupportedOperationException();
+        }
+
+        public void describe(Collection<String> candidateFormats) {
+        }
+
+        public void describeValues(Collection<String> collector) {
+        }
+    }
+
+    private class NoDescriptionValuesJustReturningParser<T> extends JustReturningParser<CharSequence, T> implements ValueAwareNotationParser<T> {
+        public NoDescriptionValuesJustReturningParser(Class<? extends T> targetType) {
+            super(targetType);
+        }
+
+        public void describeValues(Collection<String> collector) {
+
+        }
+    }
+
+    private class ValueAwareCompositeNotationParser<T> extends CompositeNotationParser<CharSequence, T> implements ValueAwareNotationParser<T> {
+        private final Collection<ValueAwareNotationParser<? extends T>> delegates;
+
+        public ValueAwareCompositeNotationParser(Collection<ValueAwareNotationParser<? extends T>> delegates) {
+            super(delegates);
+            this.delegates = delegates;
+        }
+
+        public void describeValues(Collection<String> collector) {
+            for (ValueAwareNotationParser<? extends T> delegate : delegates) {
+                delegate.describeValues(collector);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionReader.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionReader.java
new file mode 100644
index 0000000..69c71fd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionReader.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import org.gradle.internal.reflect.JavaMethod;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.util.CollectionUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+public class OptionReader {
+    private final ListMultimap<Class<?>, OptionElement> cachedOptionElements = ArrayListMultimap.create();
+    private final Map<OptionElement, JavaMethod<Object, Collection>> cachedOptionValueMethods = new HashMap<OptionElement, JavaMethod<Object, Collection>>();
+    private final OptionNotationParserFactory optionNotationParserFactory = new OptionNotationParserFactory();
+
+    public List<OptionDescriptor> getOptions(Object target) {
+        final Class<?> targetClass = target.getClass();
+        Map<String, OptionDescriptor> options = new HashMap<String, OptionDescriptor>();
+        if (!cachedOptionElements.containsKey(targetClass)) {
+            loadClassDescriptorInCache(target);
+        }
+        for (OptionElement optionElement : cachedOptionElements.get(targetClass)) {
+            JavaMethod<Object, Collection> optionValueMethod = cachedOptionValueMethods.get(optionElement);
+            options.put(optionElement.getOptionName(), new InstanceOptionDescriptor(target, optionElement, optionValueMethod));
+        }
+        return CollectionUtils.sort(options.values());
+    }
+
+    private void loadClassDescriptorInCache(Object target) {
+        final Collection<OptionElement> optionElements = getOptionElements(target);
+        List<JavaMethod<Object, Collection>> optionValueMethods = loadValueMethodForOption(target.getClass());
+        Set<String> processedOptionElements = new HashSet<String>();
+        for (OptionElement optionElement : optionElements) {
+            if (processedOptionElements.contains(optionElement.getOptionName())) {
+                throw new OptionValidationException(String.format("@Option '%s' linked to multiple elements in class '%s'.",
+                        optionElement.getOptionName(), target.getClass().getName()));
+            }
+            processedOptionElements.add(optionElement.getOptionName());
+            JavaMethod<Object, Collection> optionValueMethodForOption = getOptionValueMethodForOption(optionValueMethods, optionElement);
+
+            cachedOptionElements.put(target.getClass(), optionElement);
+            cachedOptionValueMethods.put(optionElement, optionValueMethodForOption);
+        }
+    }
+
+    private static JavaMethod<Object, Collection> getOptionValueMethodForOption(List<JavaMethod<Object, Collection>> optionValueMethods, OptionElement optionElement) {
+        JavaMethod<Object, Collection> valueMethod = null;
+        for (JavaMethod<Object, Collection> optionValueMethod : optionValueMethods) {
+            OptionValues optionValues = optionValueMethod.getMethod().getAnnotation(OptionValues.class);
+            if (CollectionUtils.toList(optionValues.value()).contains(optionElement.getOptionName())) {
+                            if (valueMethod == null) {
+                                valueMethod = optionValueMethod;
+                            } else {
+                                throw new OptionValidationException(
+                                        String.format("@OptionValues for '%s' cannot be attached to multiple methods in class '%s'.",
+                                                optionElement.getOptionName(),
+                                                optionValueMethod.getMethod().getDeclaringClass().getName()));
+                            }
+                        }
+        }
+        return valueMethod;
+    }
+
+    private Collection<OptionElement> getOptionElements(Object target) {
+        List<OptionElement> allOptionElements = new ArrayList<OptionElement>();
+        for (Class<?> type = target.getClass(); type != Object.class && type != null; type = type.getSuperclass()) {
+            allOptionElements.addAll(getMethodAnnotations(type));
+            allOptionElements.addAll(getFieldAnnotations(type));
+        }
+
+        return allOptionElements;
+    }
+
+    private List<OptionElement> getFieldAnnotations(Class<?> type) {
+        List<OptionElement> fieldOptionElements = new ArrayList<OptionElement>();
+        for (Field field : type.getDeclaredFields()) {
+            Option option = field.getAnnotation(Option.class);
+            if (option != null) {
+                if (Modifier.isStatic(field.getModifiers())) {
+                    throw new OptionValidationException(String.format("@Option on static field '%s' not supported in class '%s'.",
+                            field.getName(), field.getDeclaringClass().getName()));
+                }
+
+                fieldOptionElements.add(FieldOptionElement.create(option, field, optionNotationParserFactory));
+            }
+        }
+        return fieldOptionElements;
+    }
+
+    private List<OptionElement> getMethodAnnotations(Class<?> type) {
+        List<OptionElement> methodOptionElements = new ArrayList<OptionElement>();
+        for (Method method : type.getDeclaredMethods()) {
+            Option option = method.getAnnotation(Option.class);
+            if (option != null) {
+                if (Modifier.isStatic(method.getModifiers())) {
+                    throw new OptionValidationException(String.format("@Option on static method '%s' not supported in class '%s'.",
+                            method.getName(), method.getDeclaringClass().getName()));
+                }
+                final OptionElement methodOptionDescriptor = MethodOptionElement.create(option, method, optionNotationParserFactory);
+                methodOptionElements.add(methodOptionDescriptor);
+            }
+        }
+        return methodOptionElements;
+    }
+
+    private static List<JavaMethod<Object, Collection>> loadValueMethodForOption(Class<?> declaredClass) {
+        List<JavaMethod<Object, Collection>> methods = new ArrayList<JavaMethod<Object, Collection>>();
+        for (Class<?> type = declaredClass; type != Object.class && type != null; type = type.getSuperclass()) {
+            for (Method method : type.getDeclaredMethods()) {
+                OptionValues optionValues = method.getAnnotation(OptionValues.class);
+                if (optionValues != null) {
+                    if (Collection.class.isAssignableFrom(method.getReturnType())
+                            && method.getParameterTypes().length == 0
+                            && !Modifier.isStatic(method.getModifiers())) {
+
+                        methods.add(JavaReflectionUtil.method(Object.class, Collection.class, method));
+                    } else {
+                        throw new OptionValidationException(
+                                String.format("@OptionValues annotation not supported on method '%s' in class '%s'. Supported method must be non-static, return a Collection<String> and take no parameters.",
+                                        method.getName(),
+                                        type.getName()));
+                    }
+                }
+            }
+        }
+        return methods;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionValidationException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionValidationException.java
new file mode 100644
index 0000000..e073627
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionValidationException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options;
+
+import org.gradle.api.GradleException;
+
+/**
+ * Thrown when there is some problem with an option definition (but not when there is some problem with an option value).
+ */
+public class OptionValidationException extends GradleException {
+    public OptionValidationException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionValues.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionValues.groovy
new file mode 100644
index 0000000..05a1417
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/options/OptionValues.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Inherited
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+
+/**
+ * Marks a method providing as available values for a given option.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.METHOD)
+ at Inherited
+public @interface OptionValues {
+    String[] value();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java
index b8eb52c..44c2a86 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java
@@ -331,7 +331,9 @@ public class SimpleMarkupWriter extends Writer {
     }
 
     private boolean isLegalCharacter(final char c) {
-        if (c == 0) {
+        if (c == 0x9 || c == 0xA || c == 0xD) {
+            return true;
+        } else if (c < 0x20) {
             return false;
         } else if (c <= 0xD7FF) {
             return true;
@@ -339,6 +341,10 @@ public class SimpleMarkupWriter extends Writer {
             return false;
         } else if (c <= 0xFFFD) {
             return true;
+        } else if (c < 0x10000) {
+            return false;
+        } else if (c <= 0x10FFFF) {
+            return true;
         }
         return false;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
index 73bf959..d6b6f0f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
@@ -23,8 +23,6 @@ import java.io.Writer;
 
 /**
  * <p>A streaming XML writer.</p>
- *
- * by Szczepan Faber, created at: 12/3/12
  */
 public class SimpleXmlWriter extends SimpleMarkupWriter {
 
@@ -42,6 +40,6 @@ public class SimpleXmlWriter extends SimpleMarkupWriter {
     }
 
     private void writeXmlDeclaration(String encoding) throws IOException {
-        writeRaw(String.format("<?xml version=\"1.1\" encoding=\"%s\"?>", encoding));
+        writeRaw(String.format("<?xml version=\"1.0\" encoding=\"%s\"?>", encoding));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
index 9d5b767..63c7b4d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
@@ -26,7 +26,7 @@ import org.gradle.api.Transformer;
 import org.gradle.api.XmlProvider;
 import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.DomNode;
-import org.gradle.api.internal.IoActions;
+import org.gradle.internal.IoActions;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.UncheckedException;
 import org.gradle.util.GUtil;
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 2588fad..394306d 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,7 +22,8 @@ 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 org.gradle.internal.HasInternalProtocol;
+import org.gradle.api.plugins.PluginAware;
 
 import java.io.File;
 
@@ -32,7 +33,7 @@ import java.io.File;
  * <p>You can obtain a {@code Gradle} instance by calling {@link Project#getGradle()}.</p>
  */
 @HasInternalProtocol
-public interface Gradle {
+public interface Gradle extends PluginAware {
     /**
      * Returns the current Gradle version.
      *
@@ -43,7 +44,7 @@ public interface Gradle {
     /**
      * Returns the Gradle user home directory.
      *
-     * This directory is used to cache downloaded resources.
+     * This directory is used to cache downloaded resources, compiled build scripts and so on.
      *
      * @return The user home directory. Never returns null.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java b/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
index 4107c32..1935167 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
@@ -17,8 +17,6 @@ package org.gradle.api.logging;
 
 /**
  * The log levels supported by Gradle.
- *
- * @author Hans Dockter
  */
 public enum LogLevel {
     DEBUG {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/logging/Logging.java b/subprojects/core/src/main/groovy/org/gradle/api/logging/Logging.java
index 0d60dc2..7d2b455 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/logging/Logging.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/logging/Logging.java
@@ -27,8 +27,6 @@ import java.util.Map;
 /**
  * <p>The main entry point for Gradle's logging system. Gradle routes all logging via SLF4J. You can use either an SLF4J
  * {@link org.slf4j.Logger} or a Gradle {@link Logger} to perform logging.</p>
- *
- * @author Hans Dockter
  */
 public class Logging {
     public static final Marker LIFECYCLE = MarkerFactory.getDetachedMarker("LIFECYCLE");
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java
index b38dad3..7256711 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java
@@ -24,8 +24,6 @@ import java.util.Map;
  * Convention}, and the properties and methods of the convention object become available as properties and methods of
  * the object which the convention is associated to. A convention object is simply a POJO or POGO. Usually, a {@code
  * Convention} is used by plugins to extend a {@link org.gradle.api.Project} or a {@link org.gradle.api.Task}.</p>
- *
- * @author Hans Dockter
  */
 public interface Convention extends ExtensionContainer {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
index 0c8edbb..7e0fd94 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
@@ -19,10 +19,12 @@ package org.gradle.api.plugins;
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
 import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * Allows adding 'namespaced' DSL extensions to a target object.
  */
+ at HasInternalProtocol
 public interface ExtensionContainer {
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginAware.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginAware.java
new file mode 100644
index 0000000..2dd774b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginAware.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins;
+
+import groovy.lang.Closure;
+
+import java.util.Map;
+
+/**
+ * Objects a {@link org.gradle.api.Plugin} can be applied to.
+ *
+ * <p>
+ * For more on writing and applying plugins, see {@link org.gradle.api.Plugin}.
+ * </p>
+ */
+public interface PluginAware {
+    /**
+     * Returns the plugins container for this object. The returned container can be used to manage the plugins which
+     * are used by this object.
+     *
+     * @return the plugin container. Never returns null.
+     */
+    PluginContainer getPlugins();
+
+    /**
+     * <p>Configures this object using plugins or scripts. The given closure is used to configure an {@link
+     * ObjectConfigurationAction} which is then used to configure this object.</p>
+     *
+     * @param closure The closure to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Closure closure);
+
+    /**
+     * <p>Configures this Object using plugins or scripts. The following options are available:</p>
+     *
+     * <ul><li>{@code from}: A script to apply to the object. Accepts any path supported by {@link org.gradle.api.Project#uri(Object)}.</li>
+     *
+     * <li>{@code plugin}: The id or implementation class of the plugin to apply to the object.</li>
+     *
+     * <li>{@code to}: The target delegate object or objects. Use this to configure objects other than this
+     * object.</li></ul>
+     *
+     * <p>For more detail, see {@link ObjectConfigurationAction}.</p>
+     *
+     * @param options The options to use to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Map<String, ?> options);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
index 1430c64..99e5b24 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
@@ -15,17 +15,15 @@
  */
 package org.gradle.api.plugins;
 
-import org.gradle.api.DomainObjectSet;
-import org.gradle.api.specs.Spec;
+import groovy.lang.Closure;
 import org.gradle.api.Action;
+import org.gradle.api.DomainObjectSet;
 import org.gradle.api.Plugin;
-
-import groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
 
 /**
  * <p>A {@code PluginCollection} represents a collection of {@link org.gradle.api.Plugin} instances.</p>
  * 
- * @author Hans Dockter
  * @param <T> The type of plugins which this collection contains.
  */
 public interface PluginCollection<T extends Plugin> extends DomainObjectSet<T> {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java
index c90d288..ef8025f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java
@@ -24,8 +24,6 @@ import org.gradle.api.Plugin;
  *
  * <p>Plugins can be specified using either an id or type. The id of a plugin is specified using a
  * META-INF/gradle-plugins/${id}.properties classpath resource.</p>
- *
- * @author Hans Dockter
  */
 public interface PluginContainer extends PluginCollection<Plugin> {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java
index 914d3fd..9d5cc54 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java
@@ -19,8 +19,6 @@ import org.gradle.api.GradleException;
 
 /**
  * A {@code PluginInstantiationException} is thrown when a plugin cannot be instantiated.
- *
- * @author Hans Dockter
  */
 public class PluginInstantiationException extends GradleException {
     public PluginInstantiationException(String message) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java
index fce0920..a0c1c19 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java
@@ -19,8 +19,6 @@ import org.gradle.api.InvalidUserDataException;
 
 /**
  * A {@code UnknownPluginException} is thrown when an unknown plugin id is provided. 
- *
- * @author Hans Dockter
  */
 public class UnknownPluginException extends InvalidUserDataException {
     public UnknownPluginException(String message) {
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 bf6c8d8..0430238 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
@@ -25,8 +25,6 @@ import java.util.Set;
 
 /**
  * Provides a number of {@link org.gradle.api.specs.Spec} implementations.
- *
- * @author Hans Dockter
  */
 public class Specs {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
index 6b610b8..cb3630f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
@@ -19,11 +19,12 @@ import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.file.*;
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.file.copy.CopyActionImpl;
-import org.gradle.api.internal.file.copy.CopySpecSource;
-import org.gradle.api.internal.file.copy.ReadableCopySpec;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.copy.*;
 import org.gradle.api.specs.Spec;
 import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.DeprecationLogger;
 
 import java.io.FilterReader;
@@ -36,15 +37,37 @@ import java.util.regex.Pattern;
  */
 public abstract class AbstractCopyTask extends ConventionTask implements CopySpec, CopySpecSource {
 
+    private final CopySpecInternal rootSpec;
+    private final CopySpecInternal mainSpec;
+
+    protected AbstractCopyTask() {
+        this.rootSpec = createRootSpec();
+        this.mainSpec = rootSpec.addChild();
+    }
+
+    protected CopySpecInternal createRootSpec() {
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        FileResolver fileResolver = getServices().get(FileResolver.class);
+        return instantiator.newInstance(DefaultCopySpec.class, fileResolver, instantiator);
+    }
+
+    protected abstract CopyAction createCopyAction();
+
     @TaskAction
     protected void copy() {
         configureRootSpec();
-        getCopyAction().execute();
-        setDidWork(getCopyAction().getDidWork());
+
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        FileSystem fileSystem = getServices().get(FileSystem.class);
+
+        CopyActionExecuter copyActionExecuter = new CopyActionExecuter(instantiator, fileSystem);
+        CopyAction copyAction = createCopyAction();
+        WorkResult didWork = copyActionExecuter.execute(rootSpec, copyAction);
+        setDidWork(didWork.getDidWork());
     }
 
     protected void configureRootSpec() {
-        if (!getCopyAction().hasSource()) {
+        if (!rootSpec.hasSource()) {
             Object srcDirs = getDefaultSource();
             if (srcDirs != null) {
                 from(srcDirs);
@@ -68,8 +91,8 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
      */
     @InputFiles @SkipWhenEmpty @Optional
     public FileCollection getSource() {
-        if(getCopyAction().hasSource()){
-            return getCopyAction().getAllSource();
+        if (rootSpec.hasSource()){
+            return rootSpec.getAllSource();
         }else{
             return DeprecationLogger.whileDisabled(new Factory<FileCollection>() {
                 public FileCollection create() {
@@ -78,19 +101,18 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
             });
         }
     }
-    
-    protected abstract CopyActionImpl getCopyAction();
 
-    public ReadableCopySpec getRootSpec() {
-        return getCopyAction().getRootSpec();
+
+    public CopySpecInternal getRootSpec() {
+        return rootSpec;
     }
 
     // -----------------------------------------------
     // ---- Delegate CopySpec methods to rootSpec ----
     // -----------------------------------------------
 
-    protected CopySpec getMainSpec() {
-        return getCopyAction();
+    protected CopySpecInternal getMainSpec() {
+        return mainSpec;
     }
 
     /**
@@ -124,6 +146,20 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
     /**
      * {@inheritDoc}
      */
+    public void setDuplicatesStrategy(DuplicatesStrategy strategy) {
+        getRootSpec().setDuplicatesStrategy(strategy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public DuplicatesStrategy getDuplicatesStrategy() {
+        return getRootSpec().getDuplicatesStrategy();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public AbstractCopyTask from(Object... sourcePaths) {
         getMainSpec().from(sourcePaths);
         return this;
@@ -132,6 +168,22 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
     /**
      * {@inheritDoc}
      */
+    public AbstractCopyTask filesMatching(String pattern, Action<? super FileCopyDetails> action) {
+        getMainSpec().filesMatching(pattern, action);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask filesNotMatching(String pattern, Action<? super FileCopyDetails> action) {
+        getMainSpec().filesNotMatching(pattern, action);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public AbstractCopyTask from(Object sourcePath, Closure c) {
         getMainSpec().from(sourcePath, c);
         return this;
@@ -149,7 +201,7 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
      * {@inheritDoc}
      */
     public AbstractCopyTask into(Object destDir) {
-        getMainSpec().into(destDir);
+        getRootSpec().into(destDir);
         return this;
     }
 
@@ -356,4 +408,5 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
         getMainSpec().eachFile(closure);
         return this;
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy
index d1a9b5b..9031780 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy
@@ -19,8 +19,6 @@ package org.gradle.api.tasks
 /**
  * An {@code AntBuilderAware} represents an object which can add itself to Ant tasks, using an
  * {@link org.gradle.api.AntBuilder}.
- *
- * @author Hans Dockter
  */
 interface AntBuilderAware {
     def addToAntBuilder(node, String childNodeName)
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
index 4db9eb5..7cd2625 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
@@ -23,7 +23,6 @@ import org.gradle.api.plugins.Convention;
  * A ConventionValue can be assigned to a {@link org.gradle.api.internal.IConventionAware} task. If a property of such an object is not set internally, a ConventionValue is used to calculate the value
  * for the property.
  *
- * @author Hans Dockter
  * @deprecated Use {@link groovy.lang.Closure} or {@link java.util.concurrent.Callable} instead.
  */
 @Deprecated
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java
index 1cdf48b..053be4d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java
@@ -16,71 +16,72 @@
 
 package org.gradle.api.tasks;
 
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.file.FileLookup;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.copy.FileCopyActionImpl;
-import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
+import org.gradle.api.internal.file.copy.CopyAction;
+import org.gradle.api.internal.file.copy.CopySpecInternal;
+import org.gradle.api.internal.file.copy.DestinationRootCopySpec;
+import org.gradle.api.internal.file.copy.FileCopyAction;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.io.File;
 
 /**
- * Copies files into a destination directory.  This task can also rename and filter files as it copies. The task
+ * Copies files into a destination directory. This task can also rename and filter files as it copies. The task
  * implements {@link org.gradle.api.file.CopySpec CopySpec} for specifying what to copy.
  *
  * <p> Examples:
  * <pre autoTested=''>
- * task mydoc(type:Copy) {
- *    from 'src/main/doc'
- *    into 'build/target/doc'
+ * task copyDocs(type: Copy) {
+ *     from 'src/main/doc'
+ *     into 'build/target/doc'
  * }
  *
- * //for ant filter
+ * //for Ant filter
  * import org.apache.tools.ant.filters.ReplaceTokens
  *
- * task initconfig(type:Copy) {
- *    from('src/main/config') {
- *       include '**/*.properties'
- *       include '**/*.xml'
- *       filter(ReplaceTokens, tokens:[version:'2.3.1'])
- *    }
- *    from('src/main/config') {
- *       exclude '**/*.properties', '**/*.xml'
- *    }
- *    from('src/main/languages') {
- *       rename 'EN_US_(.*)', '$1'
- *    }
- *    into 'build/target/config'
- *    exclude '**/*.bak'
+ * task initConfig(type: Copy) {
+ *     from('src/main/config') {
+ *         include '**/*.properties'
+ *         include '**/*.xml'
+ *         filter(ReplaceTokens, tokens: [version: '2.3.1'])
+ *     }
+ *     from('src/main/config') {
+ *         exclude '**/*.properties', '**/*.xml'
+ *     }
+ *     from('src/main/languages') {
+ *         rename 'EN_US_(.*)', '$1'
+ *     }
+ *     into 'build/target/config'
+ *     exclude '**/*.bak'
  *
- *    includeEmptyDirs = false
+ *     includeEmptyDirs = false
  * }
  * </pre>
- *
- * @author Steve Appling
  */
 public class Copy extends AbstractCopyTask {
-    private FileCopyActionImpl copyAction;
-
-    public Copy() {
-        FileResolver fileResolver = getServices().get(FileResolver.class);
-        copyAction = new FileCopyActionImpl(fileResolver, new FileCopySpecVisitor());
-    }
 
-    protected void configureRootSpec() {
-        super.configureRootSpec();
-        if (getCopyAction().getDestinationDir() == null) {
-            File destDir = getDestinationDir();
-            if (destDir != null) {
-                into(destDir);
-            }
+    @Override
+    protected CopyAction createCopyAction() {
+        File destinationDir = getDestinationDir();
+        if (destinationDir == null) {
+            throw new InvalidUserDataException("No copy destination directory has been specified, use 'into' to specify a target directory.");
         }
+        return new FileCopyAction(getServices().get(FileLookup.class).getFileResolver(destinationDir));
     }
 
-    public FileCopyActionImpl getCopyAction() {
-        return copyAction;
+    @Override
+    protected CopySpecInternal createRootSpec() {
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        FileResolver fileResolver = getServices().get(FileResolver.class);
+
+        return instantiator.newInstance(DestinationRootCopySpec.class, fileResolver, super.createRootSpec());
     }
 
-    public void setCopyAction(FileCopyActionImpl copyAction) {
-        this.copyAction = copyAction;
+    @Override
+    public DestinationRootCopySpec getRootSpec() {
+        return (DestinationRootCopySpec) super.getRootSpec();
     }
 
     /**
@@ -90,7 +91,7 @@ public class Copy extends AbstractCopyTask {
      */
     @OutputDirectory
     public File getDestinationDir() {
-        return getCopyAction().getDestinationDir();
+        return getRootSpec().getDestinationDir();
     }
 
     /**
@@ -101,4 +102,5 @@ public class Copy extends AbstractCopyTask {
     public void setDestinationDir(File destinationDir) {
         into(destinationDir);
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java
index 531ec76..f09ef9d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java
@@ -29,8 +29,6 @@ import java.util.Set;
  *   delete 'uglyFolder', 'uglyFile'
  * }
  * </pre>
- *
- * @author Hans Dockter
  */
 public class Delete extends ConventionTask {
     private Set<Object> delete = new LinkedHashSet<Object>();
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 13b2e11..22f0603 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
@@ -16,12 +16,11 @@
 package org.gradle.api.tasks;
 
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.file.FileResolver;
 import org.gradle.process.ExecResult;
 import org.gradle.process.ExecSpec;
 import org.gradle.process.ProcessForkOptions;
-import org.gradle.process.internal.DefaultExecAction;
 import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecActionFactory;
 
 import java.io.File;
 import java.io.InputStream;
@@ -50,15 +49,13 @@ import java.util.Map;
  *   }
  * }
  * </pre>
- * 
- * @author Hans Dockter
  */
 public class Exec extends ConventionTask implements ExecSpec {
     private ExecAction execAction;
     private ExecResult execResult;
 
     public Exec() {
-        execAction = new DefaultExecAction(getServices().get(FileResolver.class));
+        execAction = getServices().get(ExecActionFactory.class).newExecAction();
     }
 
     @TaskAction
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java
index ecde020..06fc04c 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java
@@ -15,10 +15,11 @@
  */
 package org.gradle.api.tasks;
 
-import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.initialization.GradleLauncherFactory;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.util.Collection;
 import java.util.List;
@@ -27,10 +28,13 @@ import java.util.List;
  * Executes a Gradle build.
  */
 public class GradleBuild extends ConventionTask {
+    private final GradleLauncherFactory gradleLauncherFactory;
     private StartParameter startParameter;
 
-    public GradleBuild() {
-        this.startParameter = getProject().getGradle().getStartParameter().newBuild();
+    @Inject
+    public GradleBuild(StartParameter currentBuild, GradleLauncherFactory gradleLauncherFactory) {
+        this.gradleLauncherFactory = gradleLauncherFactory;
+        this.startParameter = currentBuild.newBuild();
         startParameter.setCurrentDir(getProject().getProjectDir());
     }
 
@@ -109,6 +113,6 @@ public class GradleBuild extends ConventionTask {
 
     @TaskAction
     void build() {
-        GradleLauncher.newInstance(getStartParameter()).run().rethrowFailure();
+        gradleLauncherFactory.newInstance(getStartParameter()).run().rethrowFailure();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
index 541830d..5b180e1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
@@ -19,6 +19,7 @@ package org.gradle.api.tasks;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.options.Option;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.process.JavaForkOptions;
 import org.gradle.process.ProcessForkOptions;
@@ -33,8 +34,11 @@ import java.util.Map;
 
 /**
  * Executes a Java application in a child process.
- *
- * @author Hans Dockter
+ * <p>
+ * The process can be started in debug mode (see {@link #getDebug()}) in an ad-hoc manner by supplying the `--debug-jvm` switch when invoking the build.
+ * <pre>
+ * gradle someJavaExecTask --debug-jvm
+ * </pre>
  */
 public class JavaExec extends ConventionTask implements JavaExecSpec {
     private JavaExecAction javaExecHandleBuilder;
@@ -46,7 +50,8 @@ public class JavaExec extends ConventionTask implements JavaExecSpec {
 
     @TaskAction
     public void exec() {
-        setMain(getMain()); // make convention mapping work (at least for 'main')
+        setMain(getMain()); // make convention mapping work (at least for 'main'...
+        setJvmArgs(getJvmArgs()); // ...and for 'jvmArgs')
         javaExecHandleBuilder.execute();
     }
 
@@ -213,6 +218,7 @@ public class JavaExec extends ConventionTask implements JavaExecSpec {
     /**
      * {@inheritDoc}
      */
+    @Option(option = "debug-jvm", description = "Enable debugging for the process. The process is started suspended and listening on port 5005. [INCUBATING]")
     public void setDebug(boolean enabled) {
         javaExecHandleBuilder.setDebug(enabled);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
index 3203072..df7c01d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
@@ -34,7 +34,7 @@ import java.util.Set;
  * A {@code SourceTask} performs some operation on source files.
  */
 public class SourceTask extends ConventionTask implements PatternFilterable {
-    private final List<Object> source = new ArrayList<Object>();
+    protected final List<Object> source = new ArrayList<Object>();
     private final PatternFilterable patternSet = new PatternSet();
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopActionException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopActionException.java
index 3def548..7eb97ea 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopActionException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopActionException.java
@@ -32,8 +32,6 @@ import org.gradle.api.GradleException;
  * need the if statement.</p>
  *
  * <p>Note that throwing this exception does not fail the execution of the task or the build.</p>
- *
- * @author Hans Dockter
  */
 public class StopActionException extends GradleException {
     public StopActionException() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.java
index 233e3f1..9900845 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.java
@@ -22,8 +22,6 @@ package org.gradle.api.tasks;
  * actions to be added to a task which abort execution of the task if the preconditions are not met.</p>
  *
  * <p>Note that throwing this exception does not fail the execution of the task or the build.</p>
- *
- * @author Hans Dockter
  */
 public class StopExecutionException extends RuntimeException {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Sync.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Sync.java
index 7f4e22a..42974df 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Sync.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Sync.java
@@ -16,10 +16,11 @@
 
 package org.gradle.api.tasks;
 
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.file.FileLookup;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.copy.FileCopyActionImpl;
-import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
-import org.gradle.api.internal.file.copy.SyncCopySpecVisitor;
+import org.gradle.api.internal.file.copy.*;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.io.File;
 
@@ -27,16 +28,27 @@ import java.io.File;
  * Synchronises the contents of a destination directory with some source directories and files.
  */
 public class Sync extends AbstractCopyTask {
-    private FileCopyActionImpl action;
 
-    public Sync() {
+    @Override
+    protected CopyAction createCopyAction() {
+        File destinationDir = getDestinationDir();
+        if (destinationDir == null) {
+            throw new InvalidUserDataException("No copy destination directory has been specified, use 'into' to specify a target directory.");
+        }
+        return new SyncCopyActionDecorator(destinationDir, new FileCopyAction(getServices().get(FileLookup.class).getFileResolver(destinationDir)));
+    }
+
+    @Override
+    protected CopySpecInternal createRootSpec() {
+        Instantiator instantiator = getServices().get(Instantiator.class);
         FileResolver fileResolver = getServices().get(FileResolver.class);
-        action = new FileCopyActionImpl(fileResolver, new SyncCopySpecVisitor(new FileCopySpecVisitor()));
+
+        return instantiator.newInstance(DestinationRootCopySpec.class, fileResolver, super.createRootSpec());
     }
 
     @Override
-    protected FileCopyActionImpl getCopyAction() {
-        return action;
+    public DestinationRootCopySpec getRootSpec() {
+        return (DestinationRootCopySpec) super.getRootSpec();
     }
 
     /**
@@ -46,7 +58,7 @@ public class Sync extends AbstractCopyTask {
      */
     @OutputDirectory
     public File getDestinationDir() {
-        return getCopyAction().getDestinationDir();
+        return getRootSpec().getDestinationDir();
     }
 
     /**
@@ -57,4 +69,5 @@ public class Sync extends AbstractCopyTask {
     public void setDestinationDir(File destinationDir) {
         into(destinationDir);
     }
+
 }
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 266042e..f7168aa 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
@@ -26,7 +26,7 @@ import java.util.Map;
  * <p>You can obtain a {@code TaskContainer} instance by calling {@link org.gradle.api.Project#getTasks()}, or using the
  * {@code tasks} property in your build script.</p>
  */
-public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectContainer<Task> {
+public interface TaskContainer extends TaskCollection<Task>, PolymorphicDomainObjectContainer<Task> {
     /**
      * <p>Locates a task by path. You can supply a task name, a relative path, or an absolute path. Relative paths are
      * interpreted relative to the project for this container. This method returns null if no task with the given path
@@ -62,7 +62,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * <tr><td><code>{@value org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to
      * create.</td><td>{@link org.gradle.api.DefaultTask}</td></tr>
      *
-     * <tr><td><code>{@value org.gradle.api.Task#TASK_ACTION}</code></td><td>The closure or {@link TaskAction} to
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_ACTION}</code></td><td>The closure or {@link Action} to
      * execute when the task executes. See {@link Task#doFirst(Action)}.</td><td><code>null</code></td></tr>
      *
      * <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace an existing
@@ -76,13 +76,52 @@ 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>overwrite</code> option is not set
-     * to true, an exception is thrown.</p>
+     * <p>If a task with the given name already exists in this container and the <code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code>
+     * option is not set to true, an exception is thrown.</p>
      *
      * @param options The task creation options.
      * @return The newly created task object
      * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
+    Task create(Map<String, ?> options) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} and adds it to this container. A map of creation options can be passed to this method
+     * to control how the task is created. The following options are available:</p>
+     *
+     * <table>
+     *
+     * <tr><th>Option</th><th>Description</th><th>Default Value</th></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_NAME}</code></td><td>The name of the task to create.</td><td>None.
+     * Must be specified.</td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to
+     * create.</td><td>{@link org.gradle.api.DefaultTask}</td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_ACTION}</code></td><td>The closure or {@link Action} to
+     * execute when the task executes. See {@link Task#doFirst(Action)}.</td><td><code>null</code></td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace an existing
+     * task?</td><td><code>false</code></td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_DEPENDS_ON}</code></td><td>The dependencies of the task. See <a
+     * href="../Task.html#dependencies">here</a> for more details.</td><td><code>[]</code></td></tr>
+     *
+     * </table>
+     *
+     * <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>{@value org.gradle.api.Task#TASK_OVERWRITE}</code>
+     * option is not set to true, an exception is thrown.</p>
+     *
+     * @param options The task creation options.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated use {@link #create(java.util.Map)} instead
+     */
+    @Deprecated
     Task add(Map<String, ?> options) throws InvalidUserDataException;
 
     /**
@@ -98,6 +137,23 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @return The newly created task object
      * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
+    Task create(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} adds it to this container. A map of creation options can be passed to this method to
+     * control how the task is created. See {@link #add(java.util.Map)} for the list of options available. The given
+     * closure is used to configure the task before it is returned by this method.</p>
+     *
+     * <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>
+     *
+     * @param options The task creation options.
+     * @param configureClosure The closure to use to configure the task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated use {@link #create(java.util.Map, groovy.lang.Closure)} instead
+     */
+    @Deprecated
     Task add(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
 
     /**
@@ -112,6 +168,22 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @return The newly created task object
      * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
+    Task create(String name, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name adds it to this container. The given closure is used to configure
+     * the task before it is returned by this method.</p>
+     *
+     * <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>
+     *
+     * @param name The name of the task to be created
+     * @param configureClosure The closure to use to configure the task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated use {@link #create(String, groovy.lang.Closure)} instead
+     */
+    @Deprecated
     Task add(String name, Closure configureClosure) throws InvalidUserDataException;
 
     /**
@@ -124,6 +196,20 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @return The newly created task object
      * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
+    Task create(String name) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this container.</p>
+     *
+     * <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>
+     *
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated use {@link #create(String)} instead
+     */
+    @Deprecated
     Task add(String name) throws InvalidUserDataException;
 
     /**
@@ -137,9 +223,38 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @return The newly created task object
      * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
+    <T extends Task> T create(String name, Class<T> type) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and type, and adds it to this container.</p>
+     *
+     * <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>
+     *
+     * @param name The name of the task to be created.
+     * @param type The type of task to create.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated use {@link #create(String, Class)} instead
+     */
+    @Deprecated
     <T extends Task> T add(String name, Class<T> type) throws InvalidUserDataException;
 
     /**
+     * <p>Creates a {@link Task} with the given name and type, configures it with the given action, and adds it to this container.</p>
+     *
+     * <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>
+     *
+     * @param name The name of the task to be created.
+     * @param type The type of task to create.
+     * @param configuration The action to configure the task with.
+     * @return The newly created task object.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     */
+    <T extends Task> T create(String name, Class<T> type, Action<? super T> configuration) throws InvalidUserDataException;
+
+    /**
      * <p>Creates a {@link Task} with the given name and adds it to this container, replacing any existing task with the
      * same name.</p>
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java
index 74c7f66..4e39c62 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java
@@ -17,7 +17,7 @@ package org.gradle.api.tasks;
 
 import org.gradle.api.GradleException;
 import org.gradle.api.Task;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * <p>A {@code TaskExecutionException} is thrown when a task fails to execute successfully.</p>
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskValidationException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskValidationException.java
index 442c2ec..c93ddb8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskValidationException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskValidationException.java
@@ -16,8 +16,8 @@
 package org.gradle.api.tasks;
 
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.AbstractMultiCauseException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.AbstractMultiCauseException;
+import org.gradle.internal.exceptions.Contextual;
 
 import java.util.List;
 
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
deleted file mode 100644
index 926975c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
+++ /dev/null
@@ -1,147 +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;
-
-import groovy.lang.Closure;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.Transformers;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
-import org.gradle.util.ConfigureUtil;
-
-import javax.inject.Inject;
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-import static org.gradle.util.CollectionUtils.collect;
-
-/**
- * Uploads the artifacts of a {@link Configuration} to a set of repositories.
- *
- * @author Hans Dockter
- */
-public class Upload extends ConventionTask {
-
-    private Configuration configuration;
-    private boolean uploadDescriptor;
-    private File descriptorDestination;
-    private RepositoryHandler repositories;
-
-    private final ArtifactPublicationServices publicationServices;
-
-    @Inject
-    public Upload(ArtifactPublicationServices publicationServices) {
-        this.publicationServices = publicationServices;
-        repositories = publicationServices.createRepositoryHandler();
-    }
-
-    @TaskAction
-    protected void upload() {
-        getLogger().info("Publishing configuration: " + configuration);
-        Module module = ((ConfigurationInternal) configuration).getModule();
-        Set<Configuration> configurationsToPublish = configuration.getHierarchy();
-
-        ArtifactPublisher artifactPublisher = publicationServices.createArtifactPublisher();
-
-        try {
-            File descriptorDestination = isUploadDescriptor() ? getDescriptorDestination() : null;
-            if (descriptorDestination != null) {
-                Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
-                ModuleDescriptorConverter moduleDescriptorConverter = publicationServices.getDescriptorFileModuleConverter();
-                ModuleDescriptor moduleDescriptor = moduleDescriptorConverter.convert(allConfigurations, module);
-                IvyModuleDescriptorWriter ivyModuleDescriptorWriter = publicationServices.getIvyModuleDescriptorWriter();
-                ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorDestination);
-            }
-
-            List<PublicationAwareRepository> publishRepositories = collect(repositories, Transformers.cast(PublicationAwareRepository.class));
-            artifactPublisher.publish(publishRepositories,  module, configurationsToPublish, descriptorDestination);
-        } catch (Exception e) {
-            throw new PublishException(String.format("Could not publish configuration '%s'", configuration.getName()), e);
-        }
-    }
-
-    /**
-     * Specifies whether the dependency descriptor should be uploaded.
-     */
-    public boolean isUploadDescriptor() {
-        return uploadDescriptor;
-    }
-
-    public void setUploadDescriptor(boolean uploadDescriptor) {
-        this.uploadDescriptor = uploadDescriptor;
-    }
-
-    /**
-     * Returns the path to generate the dependency descriptor to.
-     */
-    public File getDescriptorDestination() {
-        return descriptorDestination;
-    }
-
-    @SuppressWarnings("UnusedDeclaration")
-    public void setDescriptorDestination(File descriptorDestination) {
-        this.descriptorDestination = descriptorDestination;
-    }
-
-    /**
-     * Returns the repositories to upload to.
-     */
-    public RepositoryHandler getRepositories() {
-        return repositories;
-    }
-
-    /**
-     * Returns the configuration to upload.
-     */
-    public Configuration getConfiguration() {
-        return configuration;
-    }
-
-    public void setConfiguration(Configuration configuration) {
-        this.configuration = configuration;
-    }
-
-    /**
-     * Configures the set of repositories to upload to.
-     */
-    public RepositoryHandler repositories(Closure configureClosure) {
-        return ConfigureUtil.configure(configureClosure, repositories);
-    }
-
-    /**
-     * Returns the artifacts which will be uploaded.
-     *
-     * @return the artifacts.
-     */
-    @InputFiles
-    public FileCollection getArtifacts() {
-        Configuration configuration = getConfiguration();
-        return configuration == null ? null : configuration.getAllArtifacts().getFiles();
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java
index af4665e..7d44707 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java
@@ -17,6 +17,7 @@ package org.gradle.api.tasks.ant;
 
 import org.apache.tools.ant.Target;
 import org.gradle.api.Task;
+import org.gradle.api.UnknownTaskException;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.TaskDependency;
@@ -46,7 +47,11 @@ public class AntTarget extends ConventionTask {
         Enumeration dependencies = target.getDependencies();
         while (dependencies.hasMoreElements()) {
             String name = (String) dependencies.nextElement();
-            tasks.add(getProject().getTasks().getByName(name));
+            Task dependency = getProject().getTasks().findByName(name);
+            if (dependency == null) {
+                throw new UnknownTaskException(String.format("Imported Ant target '%s' depends on target or task '%s' which does not exist", getName(), name));
+            }
+            tasks.add(dependency);
         }
         return tasks;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
index fd88ab9..3058b41 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
@@ -24,8 +24,6 @@ import java.io.File;
 
 /**
  * {@code AbstractArchiveTask} is the base class for all archive tasks.
- *
- * @author Hans Dockter
  */
 public abstract class AbstractArchiveTask extends AbstractCopyTask {
     private File destinationDir;
@@ -185,4 +183,5 @@ public abstract class AbstractArchiveTask extends AbstractCopyTask {
         super.into(destPath, configureClosure);
         return this;
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
index f8b04db..863fe28 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
@@ -23,8 +23,6 @@ import java.util.List;
 
 /**
  * Specifies the compression which should be applied to a TAR archive.
- * 
- * @author Hans Dockter
  */
 public enum Compression {
     NONE("tar"),
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
index f8af439..396cbef 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
@@ -16,29 +16,22 @@
 
 package org.gradle.api.tasks.bundling;
 
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.archive.TarCopySpecVisitor;
-import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.TarCopyAction;
 import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
 import org.gradle.api.internal.file.archive.compression.GzipArchiver;
 import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
-import org.gradle.api.internal.file.copy.CopyActionImpl;
+import org.gradle.api.internal.file.copy.CopyAction;
 
-import java.io.File;
 import java.util.concurrent.Callable;
 
 /**
  * Assembles a TAR archive.
- *
- * @author Hans Dockter
  */
 public class Tar extends AbstractArchiveTask {
-    private final CopyActionImpl action;
     private Compression compression = Compression.NONE;
 
     public Tar() {
-        action = new TarCopyActionImpl(getServices().get(FileResolver.class));
         getConventionMapping().map("extension", new Callable<Object>(){
             public Object call() throws Exception {
                 return getCompression().getDefaultExtension();
@@ -46,10 +39,18 @@ public class Tar extends AbstractArchiveTask {
         });
     }
 
-    protected CopyActionImpl getCopyAction() {
-        return action;
+    @Override
+    protected CopyAction createCopyAction() {
+        return new TarCopyAction(getArchivePath(), getCompressor());
     }
 
+    private ArchiveOutputStreamFactory getCompressor() {
+        switch(compression) {
+            case BZIP2: return Bzip2Archiver.getCompressor();
+            case GZIP:  return GzipArchiver.getCompressor();
+            default:    return new SimpleCompressor();
+        }
+    }
     /**
      * Returns the compression that is used for this archive.
      *
@@ -68,21 +69,4 @@ public class Tar extends AbstractArchiveTask {
         this.compression = compression;
     }
 
-    private class TarCopyActionImpl extends CopyActionImpl implements ArchiveCopyAction  {
-        public TarCopyActionImpl(FileResolver fileResolver) {
-            super(fileResolver, new TarCopySpecVisitor());
-        }
-
-        public File getArchivePath() {
-            return Tar.this.getArchivePath();
-        }
-
-        public ArchiveOutputStreamFactory getCompressor() {
-            switch(compression) {
-                case BZIP2: return Bzip2Archiver.getCompressor();
-                case GZIP:  return GzipArchiver.getCompressor();
-                default:    return new SimpleCompressor();
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
index e7c515f..df1d284 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
@@ -15,31 +15,42 @@
  */
 package org.gradle.api.tasks.bundling;
 
-import org.gradle.api.internal.file.FileResolver;
+import org.apache.tools.zip.ZipOutputStream;
+import org.gradle.api.Incubating;
+import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.api.internal.file.archive.ZipCopyAction;
-import org.gradle.api.internal.file.archive.ZipCopySpecVisitor;
-import org.gradle.api.internal.file.copy.CopyActionImpl;
-import org.gradle.api.internal.file.copy.ZipDeflatedCompressor;
-import org.gradle.api.internal.file.copy.ZipCompressor;
-import org.gradle.api.internal.file.copy.ZipStoredCompressor;
-
-import java.io.File;
+import org.gradle.api.internal.file.copy.*;
 
 /**
  * Assembles a ZIP archive.
- * 
+ *
  * The default is to compress the contents of the zip.
- * 
- * @author Hans Dockter
  */
 public class Zip extends AbstractArchiveTask {
     public static final String ZIP_EXTENSION = "zip";
-    private final ZipCopyActionImpl action;
     private ZipEntryCompression entryCompression = ZipEntryCompression.DEFLATED;
+    private boolean allowZip64;
 
     public Zip() {
         setExtension(ZIP_EXTENSION);
-        action = new ZipCopyActionImpl(getServices().get(FileResolver.class));
+        allowZip64 = false;
+    }
+
+    protected ZipCompressor getCompressor() {
+        switch (entryCompression) {
+            case DEFLATED:
+                return new DefaultZipCompressor(allowZip64, ZipOutputStream.DEFLATED);
+            case STORED:
+                return new DefaultZipCompressor(allowZip64, ZipOutputStream.STORED);
+            default:
+                throw new IllegalArgumentException(String.format("Unknown Compression type %s", entryCompression));
+        }
+    }
+
+    @Override
+    protected CopyAction createCopyAction() {
+        DocumentationRegistry documentationRegistry = getServices().get(DocumentationRegistry.class);
+        return new ZipCopyAction(getArchivePath(), getCompressor(), documentationRegistry);
     }
 
     /**
@@ -51,7 +62,7 @@ public class Zip extends AbstractArchiveTask {
     public ZipEntryCompression getEntryCompression() {
         return entryCompression;
     }
-    
+
     /**
      * Sets the compression level of the entries of the archive. If set to {@link ZipEntryCompression#DEFLATED} (the default), each entry is
      * compressed using the DEFLATE algorithm. If set to {@link ZipEntryCompression#STORED} the entries of the archive are left uncompressed.
@@ -62,31 +73,39 @@ public class Zip extends AbstractArchiveTask {
         this.entryCompression = entryCompression;
     }
 
-    protected ZipCopyActionImpl getCopyAction() {
-        return action;
+    /**
+     * Enables building zips with more than 65535 files or bigger than 4GB.
+     *
+     * @see #isZip64()
+     */
+    @Incubating
+    public void setZip64(boolean allowZip64) {
+        this.allowZip64 = allowZip64;
     }
 
     /**
-     * Zip compress action implementation.
+     * Whether the zip can contain more than 65535 files and/or support files greater than 4GB in size.
+     * <p>
+     * The standard zip format has hard limits on file size and count.
+     * The <a href="http://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64">Zip64 format extension</a>
+     * practically removes these limits and is therefore required for building large zips.
+     * <p>
+     * However, not all Zip readers support the Zip64 extensions.
+     * Notably, the {@link java.util.zip.ZipInputStream} JDK class does not support Zip64 for versions earlier than Java 7.
+     * This means you should not enable this property if you are building JARs to be used with Java 6 and earlier runtimes.
      */
-    protected class ZipCopyActionImpl extends CopyActionImpl implements ZipCopyAction {
-        public ZipCopyActionImpl(FileResolver fileResolver) {
-            super(fileResolver, new ZipCopySpecVisitor());
-        }
-
-        public File getArchivePath() {
-            return Zip.this.getArchivePath();
-        }
+    @Incubating
+    public boolean isZip64() {
+        return allowZip64;
+    }
 
-        public ZipCompressor getCompressor() {
-            switch(entryCompression) {
-                case DEFLATED:
-                    return ZipDeflatedCompressor.INSTANCE;
-                case STORED:
-                    return ZipStoredCompressor.INSTANCE;
-                default:
-                    throw new IllegalArgumentException(String.format("Unknown Compression type %s", entryCompression));
-            }
-        }
+    /**
+     * DO NOT REMOVE.
+     *
+     * Do not use - kept for binary compatibility.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    @Deprecated
+    protected class ZipCopyActionImpl {
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/internal/Zip64RequiredException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/internal/Zip64RequiredException.java
new file mode 100644
index 0000000..38a9682
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/internal/Zip64RequiredException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.bundling.internal;
+
+import org.gradle.api.GradleException;
+
+public class Zip64RequiredException extends GradleException {
+
+    public Zip64RequiredException(String message) {
+        super(message);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java
new file mode 100644
index 0000000..fc97522
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.incremental;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.NonExtensible;
+
+/**
+ * Provides access to any input files that need to be processed by an incremental task.
+ * <p>
+ * An incremental task action is one that accepts a single {@link IncrementalTaskInputs} parameter.
+ * The task can then provide an action to execute for all input files that are out of date with respect to the previous execution of the task,
+ * and a separate action for all input files that have been removed since the previous execution.
+ *
+ * <pre autoTested="true">
+ * class IncrementalReverseTask extends DefaultTask {
+ *      @InputDirectory
+ *      def File inputDir
+ *
+ *      @OutputDirectory
+ *      def File outputDir
+ *
+ *      @TaskAction
+ *      void execute(IncrementalTaskInputs inputs) {
+ *          inputs.outOfDate { change ->
+ *              def targetFile = project.file("$outputDir/${change.file.name}")
+ *              targetFile.text = change.file.text.reverse()
+ *          }
+ *
+ *          inputs.removed { change ->
+ *              def targetFile = project.file("$outputDir/${change.file.name}")
+ *              if (targetFile.exists()) {
+ *                  targetFile.delete()
+ *              }
+ *          }
+ *      }
+ *  }
+ * </pre>
+ *
+ * <p>
+ * In the case where Gradle is unable to determine which input files need to be reprocessed, then all of the input files will be reported as {@link #outOfDate}.
+ * Cases where this occurs include:
+ * <ul>
+ *     <li>There is no history available from a previous execution.</li>
+ *     <li>An {@link org.gradle.api.tasks.TaskOutputs#upToDateWhen(groovy.lang.Closure)} criteria added to the task returns <code>false</code>.</li>
+ *     <li>An {@link org.gradle.api.tasks.Input} property has changed since the previous execution.</li>
+ *     <li>One or more output files have changed since the previous execution.</li>
+ * </ul>
+ *
+ * Note that this is a stateful API:
+ * <ul>
+ *     <li>{@link #outOfDate} and {@link #removed} can each only be executed a single time per {@link IncrementalTaskInputs} instance.</li>
+ *     <li>{@link #outOfDate} must be executed before {@link #removed} is called.</li>
+ * </ul>
+ */
+ at Incubating
+ at NonExtensible
+public interface IncrementalTaskInputs {
+    /**
+     * Indicates if it was possible for Gradle to determine which exactly which input files were out of date compared to a previous execution.
+     * This is <em>not</em> possible in the case of no previous execution, changed Input Properties, Output Files, etc.
+     * <p>
+     * When <code>true</code>:
+     * <ul>
+     *     <li>Any input file that has been added or modified since previous execution will be considered 'out-of-date' and reported to {@link #outOfDate}.</li>
+     *     <li>Any input files that has been removed since previous execution will be reported to {@link #removed}.</li>
+     * </ul>
+     * </p>
+     * <p>
+     * When <code>false</code>:
+     * <ul>
+     *     <li>Every input file will be considered to be 'out-of-date' and will be reported to {@link #outOfDate}.</li>
+     *     <li>No input files will be reported to {@link #removed}.</li>
+     * </ul>
+     * </p>
+     */
+    boolean isIncremental();
+
+    /**
+     * Executes the action for all of the input files that are out-of-date since the previous task execution. The action may also be supplied as a {@link groovy.lang.Closure}.
+     * <ul>
+     *     <li>When {@link #isIncremental()} == <code>true</code>, the action will be executed for any added or modified input file.</li>
+     *     <li>When {@link #isIncremental()} == <code>false</code>, the action will be executed for every input file for the task.</li>
+     * </ul>
+     * <p>
+     * This method may only be called a single time for a single {@link IncrementalTaskInputs} instance.
+     * </p>
+     * @throws IllegalStateException on second and subsequent invocations.
+     */
+    void outOfDate(Action<? super InputFileDetails> outOfDateAction);
+
+    /**
+     * Executes the action for all of the input files that were removed since the previous task execution. The action may also be supplied as a {@link groovy.lang.Closure}.
+     * <ul>
+     *     <li>When {@link #isIncremental()} == <code>true</code>, the action will be executed for any removed input file.</li>
+     *     <li>When {@link #isIncremental()} == <code>false</code>, the action will not be executed.</li>
+     * </ul>
+     * <p>
+     * This method may only be called a single time for a single {@link IncrementalTaskInputs} instance.
+     * </p><p>
+     * This method may only be called after {@link #outOfDate} has been called.
+     * </p>
+     * @throws IllegalStateException if invoked prior to {@link #outOfDate}, or if invoked more than once.
+     */
+    void removed(Action<? super InputFileDetails> removedAction);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/InputFileDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/InputFileDetails.java
new file mode 100644
index 0000000..bb59cd3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/InputFileDetails.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.incremental;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+
+/**
+ * A change to an input file.
+ */
+ at Incubating
+public interface InputFileDetails {
+    /**
+     * Was the file added?
+     * @return true if the file was added since the last execution
+     */
+    boolean isAdded();
+
+    /**
+     * Was the file modified?
+     * @return if the file was modified
+     */
+    boolean isModified();
+
+    /**
+     * Was the file removed?
+     * @return true if the file was removed since the last execution
+     */
+    boolean isRemoved();
+
+    /**
+     * The input file, which may no longer exist.
+     * @return the input file
+     */
+    File getFile();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/package-info.java
new file mode 100644
index 0000000..59d40ba
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/incremental/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * API classes for implementing incremental tasks.
+ */
+package org.gradle.api.tasks.incremental;
\ No newline at end of file
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
index 5379eff..fe84ee8 100644
--- 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
@@ -25,7 +25,9 @@ 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.internal.notations.parsers.CharSequenceNotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.CharSequenceNotationParser;
 import org.gradle.api.specs.*;
 import org.gradle.api.tasks.AntBuilderAware;
 import org.gradle.api.tasks.util.internal.PatternSetAntBuilderDelegate;
@@ -162,7 +164,10 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
     }
 
     public PatternSet include(Iterable includes) {
-        CharSequenceNotationParser parser = new CharSequenceNotationParser();
+        NotationParser<Object, String> parser = new NotationParserBuilder<String>()
+                .resultingType(String.class)
+                .parser(new CharSequenceNotationParser())
+                .toComposite();
         for (Object include : includes) {
             this.includes.add(parser.parseNotation(include));
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java
index d484ac8..8dd6497 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java
@@ -16,9 +16,12 @@
 
 package org.gradle.cache;
 
+import org.gradle.api.Action;
+import org.gradle.cache.internal.filelock.LockOptions;
+
 import java.util.Map;
 
-public interface CacheBuilder<T> {
+public interface CacheBuilder {
     enum VersionStrategy {
         /**
          * A separate cache instance for each Gradle version. This is the default.
@@ -28,11 +31,7 @@ public interface CacheBuilder<T> {
          * A single cache instance shared by all Gradle versions. It is the caller's responsibility to make sure that this is shared only with
          * those versions of Gradle that are compatible with the cache implementation and contents.
          */
-        SharedCache,
-        /**
-         * A single cache instance, which is invalidated when the Gradle version changes.
-         */
-        SharedCacheInvalidateOnVersionChange,
+        SharedCache
     }
 
     /**
@@ -42,36 +41,44 @@ public interface CacheBuilder<T> {
      * @param properties additional properties for the cache.
      * @return this
      */
-    CacheBuilder<T> withProperties(Map<String, ?> properties);
+    CacheBuilder withProperties(Map<String, ?> properties);
 
     /**
-     * Specifies the target domain object.  This might be a task, project, or similar. The cache is scoped for the given target object. The default is to use a globally-scoped cache.
-     *
-     * @param target The target domain object which the cache is for.
+     * Specifies that the cache should be shared by all versions of Gradle. The default is to use a Gradle version specific cache.
      * @return this
      */
-    CacheBuilder<T> forObject(Object target);
+    CacheBuilder withCrossVersionCache();
 
     /**
-     * Specifies the versioning strategy for this cache. The default is {@link VersionStrategy#CachePerVersion}.
+     * Specifies a cache validator for this cache. If {@link CacheValidator#isValid()} results in false, the Cache is considered as invalid.
      *
-     * @param strategy The strategy
+     * @param validator The validator
      * @return this
      */
-    CacheBuilder<T> withVersionStrategy(VersionStrategy strategy);
+    CacheBuilder withValidator(CacheValidator validator);
 
     /**
-     * Specifies a cache validator for this cache. If {@link CacheValidator#isValid()} results in false, the Cache is considered as invalid.
+     * Specifies the display name for this cache. This display name is used in logging and error messages.
+     */
+    CacheBuilder withDisplayName(String displayName);
+
+    /**
+     * Specifies the <em>initial</em> lock options to use. See {@link PersistentCache} for details.
      *
-     * @param validator The validator
-     * @return this
+     * <p>Note that not every combination of cache type and lock options is supported.
+     */
+    CacheBuilder withLockOptions(LockOptions lockOptions);
+
+    /**
+     * Specifies an action to execute to initialize the cache contents, if the cache does not exist or is invalid. An exclusive lock is held while the initializer is executing, to prevent
+     * cross-process access.
      */
-    CacheBuilder<T> withValidator(CacheValidator validator);
+    CacheBuilder withInitializer(Action<? super PersistentCache> initializer);
 
     /**
-     * Opens the cache.
+     * Opens the cache. It is the caller's responsibility to close the cache when finished with it.
      *
      * @return The cache.
      */
-    T open() throws CacheOpenException;
+    PersistentCache open() throws CacheOpenException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java
index 16014b1..e4bb861 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java
@@ -16,7 +16,7 @@
 package org.gradle.cache;
 
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 
 @Contextual
 public class CacheOpenException extends GradleException {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java
index a9977a4..c2f0017 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java
@@ -15,71 +15,50 @@
  */
 package org.gradle.cache;
 
+import java.io.File;
+
 /**
  * A repository of persistent caches and stores. A <em>store</em> is a store for persistent data. A <em>cache</em> is a store for persistent
  * cache data. The only real difference between the two is that a store cannot be invalidated, whereas a cache can be invalidated when things
  * change. For example, running with {@code --cache rebuild} will invalidate the contents of all caches, but not the contents of any stores.
- *
- * <p>There are 3 types of caches and stores:
- *
- * <ul>
- *
- * <li>A directory backed store, represented by {@link PersistentCache}. The caller is responsible for managing the contents of this directory.</li>
- *
- * <li>An indexed store, essentially a persistent {@link java.util.Map}, represented by {@link PersistentIndexedCache}.</li>
- *
- * <li>A state store, essentially a persistent {@link java.util.concurrent.atomic.AtomicReference}, represented by {@link PersistentStateCache}.</li>
- *
- * </ul>
  */
 public interface CacheRepository {
     /**
-     * Returns a builder for the store with the given key. Default is a Gradle version-specific store shared by all builds, though this can be
-     * changed using the given builder.
+     * Returns a builder for the store with the given key and global scope. Default is a Gradle version-specific store shared by all builds, though this
+     * can be changed using the provided builder.
      *
      * <p>A store is always opened with a shared lock, so that it can be accessed by multiple processes. It is the caller's responsibility to
      * coordinate access to the cache.</p>
      *
-     * @param key The cache key.
+     * @param key The cache key. This is a unique identifier within the cache scope.
      * @return The builder.
      */
-    DirectoryCacheBuilder store(String key);
+    CacheBuilder store(String key);
+
+    /**
+     * Returns a builder for the store with the given key and scope. Scope might be a Gradle, Project or Task.
+     */
+    CacheBuilder store(Object scope, String key);
 
     /**
-     * Returns a builder for the cache with the given key. Default is a Gradle version-specific cache shared by all builds, though this can be
-     * changed using the given builder.
+     * Returns a builder for the cache with the given key and global scope. Default is a Gradle version-specific cache shared by all builds, though this
+     * can be changed using the provided builder.
      *
      * <p>A state cache is always opened with a shared lock, so that it can be accessed by multiple processes. It is the caller's responsibility
      * to coordinate access to the cache.</p>
      *
-     * @param key The cache key.
+     * @param key The cache key. This is a unique identifier within the cache scope.
      * @return The builder.
      */
-    DirectoryCacheBuilder cache(String key);
+    CacheBuilder cache(String key);
 
     /**
-     * Returns a builder for the state cache with the given key. Default is a Gradle version-specific cache shared by all builds, though this
-     * can be changed using the given
-     * builder.
-     *
-     * <p>A state cache is always opened with an exclusive lock, so that it can be accessed only by this process.</p>
-     *
-     * @param key The cache key.
-     * @param elementType The type of element kept in the cache.
-     * @return The builder.
+     * Returns a builder for the cache with the given base directory. You should prefer one of the other methods over using this method.
      */
-    <E> ObjectCacheBuilder<E, PersistentStateCache<E>> stateCache(Class<E> elementType, String key);
+    CacheBuilder cache(File baseDir);
 
     /**
-     * Returns a builder for the indexed cache with the given key. Default is a Gradle version-specific cache shared by all builds, though this
-     * can be changed using the given builder.
-     *
-     * <p>An indexed cache is always opened with an exclusive lock, so that it can be accessed only by this process.</p>
-     *
-     * @param key The cache key.
-     * @param keyType The type of key kept in the cache.
-     * @param elementType The type of element kept in the cache.
-     * @return The builder.
+     * Returns a builder for the cache with the given key and scope. Scope might be a Gradle, Project or Task.
      */
-    <K, V> ObjectCacheBuilder<V, PersistentIndexedCache<K, V>> indexedCache(Class<K> keyType, Class<V> elementType, String key);
+    CacheBuilder cache(Object scope, String key);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java
index af53db8..d73aa95 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java
@@ -18,8 +18,15 @@ package org.gradle.cache;
 
 /**
  * CacheValidator interface can be used for specify a particular cache validation logic.
- * */
+  */
 public interface CacheValidator {
+    /**
+     * <p>Determines whether a cache is valid. A shared or exclusive lock is held on the cache while this method is executed, so the action is free to
+     * perform read-only operations on the cache to determine its validity.
+     *
+     * <p>If this method returns false, then the contents of the cache are discarded and a new empty cache is created.
+     *
+     * @return true if value, false if not.
+     */
     boolean isValid();
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DirectoryCacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/DirectoryCacheBuilder.java
deleted file mode 100644
index 7f859a7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DirectoryCacheBuilder.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.cache;
-
-import org.gradle.api.Action;
-import org.gradle.cache.internal.FileLockManager;
-
-import java.util.Map;
-
-public interface DirectoryCacheBuilder extends CacheBuilder<PersistentCache> {
-    DirectoryCacheBuilder withVersionStrategy(VersionStrategy strategy);
-
-    DirectoryCacheBuilder withProperties(Map<String, ?> properties);
-
-    DirectoryCacheBuilder forObject(Object target);
-
-    /**
-     * Specifies the display name for this cache. This display name is used in logging and error messages.
-     */
-    DirectoryCacheBuilder withDisplayName(String displayName);
-
-    /**
-     * Specifies the <em>initial</em> lock mode to use. See {@link PersistentCache} for details.
-     *
-     * <p>Note that not every combination of cache type and lock mode is supported.
-     */
-    DirectoryCacheBuilder withLockMode(FileLockManager.LockMode lockMode);
-
-    /**
-     * Specifies an action to execute to initialize the cache contents, if the cache does not exist or is invalid. An exclusive lock is held while the initializer is executing, to prevent
-     * cross-process access.
-     */
-    DirectoryCacheBuilder withInitializer(Action<? super PersistentCache> initializer);
-
-    DirectoryCacheBuilder withValidator(CacheValidator validator);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
deleted file mode 100644
index 031096f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.cache;
-
-import org.gradle.messaging.serialize.Serializer;
-
-import java.util.Map;
-
-public interface ObjectCacheBuilder<E, T> extends CacheBuilder<T> {
-    /**
-     * {@inheritDoc}
-     */
-    ObjectCacheBuilder<E, T> forObject(Object target);
-
-    /**
-     * {@inheritDoc}
-     */
-    ObjectCacheBuilder<E, T> withProperties(Map<String, ?> properties);
-
-    /**
-     * {@inheritDoc}
-     */
-    ObjectCacheBuilder<E, T> withVersionStrategy(VersionStrategy strategy);
-
-    ObjectCacheBuilder<E, T> withSerializer(Serializer<E> serializer);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
index 88cc810..4742336 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
@@ -15,24 +15,23 @@
  */
 package org.gradle.cache;
 
-import org.gradle.messaging.serialize.Serializer;
-
+import java.io.Closeable;
 import java.io.File;
 
 /**
  * Represents a directory that can be used for caching.
  *
  * <p>By default, a shared lock is held on this cache by this process, to prevent it being removed or rebuilt by another process
- * while it is in use. You can change this use {@link DirectoryCacheBuilder#withLockMode(org.gradle.cache.internal.FileLockManager.LockMode)}.
+ * while it is in use. You can change this use {@link CacheBuilder#withLockOptions(org.gradle.cache.internal.filelock.LockOptions)}.
  *
- * <p>You can use {@link DirectoryCacheBuilder#withInitializer(org.gradle.api.Action)} to provide an action to initialize the contents
+ * <p>You can use {@link CacheBuilder#withInitializer(org.gradle.api.Action)} to provide an action to initialize the contents
  * of the cache, for building a read-only cache. An exclusive lock is held by this process while the initializer is running.</p>
  *
  * <p>You can also use {@link #useCache(String, org.gradle.internal.Factory)} to perform some action on the cache while holding an exclusive
  * lock on the cache.
  * </p>
  */
-public interface PersistentCache extends CacheAccess {
+public interface PersistentCache extends PersistentStore, CacheAccess, Closeable {
     /**
      * Returns the base directory for this cache.
      */
@@ -46,25 +45,10 @@ public interface PersistentCache extends CacheAccess {
      *
      * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
      */
-    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType);
-
-    /**
-     * Creates an indexed cache implementation that is contained within this cache. This method may be used at any time.
-     *
-     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
-     * In this instance, an exclusive lock will be held on the cache.
-     *
-     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
-     */
-    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer);
+    <K, V> PersistentIndexedCache<K, V> createCache(PersistentIndexedCacheParameters<K, V> parameters);
 
     /**
-     * Creates an indexed cache implementation that is contained within this cache. This method may be used at any time.
-     *
-     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
-     * In this instance, an exclusive lock will be held on the cache.
-     *
-     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
+     * Closes this cache, blocking until all operations are complete.
      */
-    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer);
+    void close();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
index d78cc24..dfaf540 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.cache;
 
+import org.gradle.api.Nullable;
+
 /**
  * A persistent store of objects of type V indexed by a key of type K.
  */
@@ -24,6 +26,7 @@ public interface PersistentIndexedCache<K, V> {
      *
      * @return The value, or null if no value associated with the key.
      */
+    @Nullable
     V get(K key);
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCacheParameters.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCacheParameters.java
new file mode 100644
index 0000000..9384225
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCacheParameters.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache;
+
+import org.gradle.api.Nullable;
+import org.gradle.cache.internal.CacheDecorator;
+import org.gradle.messaging.serialize.BaseSerializerFactory;
+import org.gradle.messaging.serialize.Serializer;
+
+public class PersistentIndexedCacheParameters<K, V> {
+    private static final BaseSerializerFactory SERIALIZER_FACTORY = new BaseSerializerFactory();
+    private final String cacheName;
+    private final Serializer<K> keySerializer;
+    private final Serializer<V> valueSerializer;
+    private CacheDecorator cacheDecorator;
+
+    public PersistentIndexedCacheParameters(String cacheName, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+        this.cacheName = cacheName;
+        this.keySerializer = keySerializer;
+        this.valueSerializer = valueSerializer;
+    }
+
+    public PersistentIndexedCacheParameters(String cacheName, Class<K> keyType, Serializer<V> valueSerializer) {
+        this(cacheName, SERIALIZER_FACTORY.getSerializerFor(keyType), valueSerializer);
+    }
+
+    public PersistentIndexedCacheParameters(String cacheName, Class<K> keyType, Class<V> valueType) {
+        this(cacheName, keyType, SERIALIZER_FACTORY.getSerializerFor(valueType));
+    }
+
+    public String getCacheName() {
+        return cacheName;
+    }
+
+    public Serializer<K> getKeySerializer() {
+        return keySerializer;
+    }
+
+    public Serializer<V> getValueSerializer() {
+        return valueSerializer;
+    }
+
+    @Nullable
+    public CacheDecorator getCacheDecorator() {
+        return cacheDecorator;
+    }
+
+    public PersistentIndexedCacheParameters<K, V> cacheDecorator(CacheDecorator cacheDecorator) {
+        assert cacheDecorator != null;
+        this.cacheDecorator = cacheDecorator;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStore.java
new file mode 100644
index 0000000..d275b36
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStore.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache;
+
+import org.gradle.messaging.serialize.Serializer;
+
+/**
+ * Represents some persistent store.
+ *
+ * <p>Use can use</p>
+ *
+ * <p>You can use {@link #useCache(String, org.gradle.internal.Factory)} to perform some action on the store while holding an exclusive
+ * lock on the store.</p>
+ */
+public interface PersistentStore extends CacheAccess {
+    /**
+     * Creates an indexed cache implementation that is contained within this store. This method may be used at any time.
+     *
+     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
+     * In this instance, an exclusive lock will be held on the cache.
+     *
+     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
+     */
+    <K, V> PersistentIndexedCache<K, V> createCache(String name, Class<K> keyType, Serializer<V> valueSerializer);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheCoordinator.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheCoordinator.java
new file mode 100644
index 0000000..a030438
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheCoordinator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal;
+
+import org.gradle.cache.CacheAccess;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentIndexedCacheParameters;
+import org.gradle.cache.internal.filelock.LockOptions;
+
+import java.io.Closeable;
+
+public interface CacheCoordinator extends CacheAccess, Closeable {
+    void open(LockOptions lockOptions);
+
+    /**
+     * Closes the cache, blocking until all operations have completed.
+     */
+    void close();
+
+    <K, V> PersistentIndexedCache<K, V> newCache(PersistentIndexedCacheParameters<K, V> parameters);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheDecorator.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheDecorator.java
new file mode 100644
index 0000000..6365951
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheDecorator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal;
+
+public interface CacheDecorator {
+    /**
+     * @param cacheId Unique id for this cache instance.
+     * @param cacheName Name for the type of contents stored in this cache instance.
+     */
+    <K, V> MultiProcessSafePersistentIndexedCache<K, V> decorate(String cacheId, String cacheName, MultiProcessSafePersistentIndexedCache<K, V> original);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
index 2290aac..8eb4f7b 100755
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
@@ -17,19 +17,16 @@ package org.gradle.cache.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.cache.*;
-import org.gradle.cache.internal.FileLockManager.LockMode;
-import org.gradle.messaging.serialize.Serializer;
+import org.gradle.cache.CacheOpenException;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.internal.filelock.LockOptions;
 
 import java.io.File;
 import java.util.Map;
 
 public interface CacheFactory {
-    PersistentCache openStore(File storeDir, String displayName, LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException;
+    PersistentCache openStore(File storeDir, String displayName, LockOptions lockOptions, Action<? super PersistentCache> initializer) throws CacheOpenException;
 
-    PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException;
-
-    <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Serializer<E> serializer) throws CacheOpenException;
-
-    <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Serializer<V> serializer) throws CacheOpenException;
+    PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockOptions lockOptions, Action<? super PersistentCache> initializer) throws CacheOpenException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheInitializationAction.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheInitializationAction.java
new file mode 100644
index 0000000..64e3c69
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheInitializationAction.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal;
+
+public interface CacheInitializationAction {
+    /**
+     * Determines if this action should run. Called when the cache is opened, holding either a shared or exclusive lock. May be called multiple times.
+     */
+    boolean requiresInitialization(FileLock fileLock);
+
+    /**
+     * Executes the action to initialize the cache. Called only if {@link #requiresInitialization(FileLock)} returns true, holding an exclusive lock.
+     * The lock is not released between calling {@link #requiresInitialization(FileLock)} and this method.
+     */
+    void initialize(FileLock fileLock);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheScopeMapping.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheScopeMapping.java
new file mode 100644
index 0000000..770dd92
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheScopeMapping.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.cache.CacheBuilder;
+
+import java.io.File;
+
+public interface CacheScopeMapping {
+    File getBaseDirectory(@Nullable Object scope, String key, CacheBuilder.VersionStrategy versionStrategy);
+}
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 9ce31a2..b03633a 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,119 +16,188 @@
 package org.gradle.cache.internal;
 
 import net.jcip.annotations.ThreadSafe;
-import org.gradle.cache.CacheAccess;
-import org.gradle.messaging.serialize.DefaultSerializer;
-import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.cache.CacheOpenException;
+import org.gradle.cache.PersistentIndexedCacheParameters;
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.cache.internal.cacheops.CacheAccessOperationsStack;
+import org.gradle.cache.internal.filelock.LockOptions;
 import org.gradle.internal.Factories;
 import org.gradle.internal.Factory;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
-import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive;
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared;
 
 @ThreadSafe
-public class DefaultCacheAccess implements CacheAccess {
-    private final String cacheDiplayName;
-    private final File lockFile;
+public class DefaultCacheAccess implements CacheCoordinator {
+
+    private final static Logger LOG = Logging.getLogger(DefaultCacheAccess.class);
+
+    private final String cacheDisplayName;
+    private final File lockTarget;
+    private final File baseDir;
     private final FileLockManager lockManager;
+    private final CacheInitializationAction initializationAction;
     private final FileAccess fileAccess = new UnitOfWorkFileAccess();
-    private final Set<MultiProcessSafePersistentIndexedCache<?, ?>> caches = new HashSet<MultiProcessSafePersistentIndexedCache<?, ?>>();
+    private final Set<MultiProcessSafePersistentIndexedCache> caches = new HashSet<MultiProcessSafePersistentIndexedCache>();
     private final Lock lock = new ReentrantLock();
     private final Condition condition = lock.newCondition();
     private Thread owner;
-    private FileLockManager.LockMode lockMode;
+    private LockOptions lockOptions;
     private FileLock fileLock;
-    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;
-        this.lockFile = lockFile;
+    private FileLock.State stateAtOpen;
+    private boolean contended;
+    private final CacheAccessOperationsStack operations;
+    private int cacheClosedCount;
+
+    public DefaultCacheAccess(String cacheDisplayName, File lockTarget, File baseDir, FileLockManager lockManager, CacheInitializationAction initializationAction) {
+        this.cacheDisplayName = cacheDisplayName;
+        this.lockTarget = lockTarget;
+        this.baseDir = baseDir;
         this.lockManager = lockManager;
+        this.initializationAction = initializationAction;
+        this.operations = new CacheAccessOperationsStack();
     }
 
-    /**
-     * Opens this cache access with the given lock mode. Calling this with {@link org.gradle.cache.internal.FileLockManager.LockMode#Exclusive} will
-     * lock the cache for exclusive access from all other threads (including those in this process and all other processes), until
-     * {@link #close()} is called.
-     */
-    public void open(FileLockManager.LockMode lockMode) {
+    public void open(LockOptions lockOptions) {
         lock.lock();
         try {
-            if (owner != null) {
-                throw new IllegalStateException(String.format("Cannot open the %s, as it is already in use.", cacheDiplayName));
+            if (this.lockOptions != null) {
+                throw new IllegalStateException(String.format("Cannot open the %s, as it has already been opened.", cacheDisplayName));
             }
-            this.lockMode = lockMode;
-            if (lockMode == FileLockManager.LockMode.None) {
+            this.lockOptions = lockOptions;
+            if (lockOptions.getMode() == FileLockManager.LockMode.None) {
                 return;
             }
-            fileLock = lockManager.lock(lockFile, lockMode, cacheDiplayName);
-            takeOwnership(String.format("Access %s", cacheDiplayName));
+            if (fileLock != null) {
+                throw new IllegalStateException("File lock " + lockTarget + " is already open.");
+            }
+            fileLock = lockManager.lock(lockTarget, lockOptions, cacheDisplayName);
+
+            boolean rebuild = initializationAction.requiresInitialization(fileLock);
+            if (rebuild) {
+                if (lockOptions.getMode() == Exclusive) {
+                    fileLock.writeFile(new Runnable() {
+                        public void run() {
+                            initializationAction.initialize(fileLock);
+                        }
+                    });
+                } else {
+                    for (int tries = 0; rebuild && tries < 3; tries++) {
+                        fileLock.close();
+                        fileLock = lockManager.lock(lockTarget, lockOptions.withMode(Exclusive), cacheDisplayName, "Initialize cache");
+                        rebuild = initializationAction.requiresInitialization(fileLock);
+                        if (rebuild) {
+                            fileLock.writeFile(new Runnable() {
+                                public void run() {
+                                    initializationAction.initialize(fileLock);
+                                }
+                            });
+                        }
+                        fileLock.close();
+                        fileLock = lockManager.lock(lockTarget, lockOptions, cacheDisplayName);
+                        rebuild = initializationAction.requiresInitialization(fileLock);
+                    }
+                    if (rebuild) {
+                        throw new CacheOpenException(String.format("Failed to initialize %s", cacheDisplayName));
+                    }
+                }
+            }
+
+            stateAtOpen = fileLock.getState();
+            takeOwnership(String.format("Access %s", cacheDisplayName));
+        } catch (Throwable throwable) {
+            if (fileLock != null) {
+                fileLock.close();
+                fileLock = null;
+            }
+            throw UncheckedException.throwAsUncheckedException(throwable);
         } finally {
             lock.unlock();
         }
     }
 
+    private void closeFileLock() {
+        try {
+            cacheClosedCount++;
+            try {
+                // Close the caches and then notify them of the final state, in case the caches do work on close
+                new CompositeStoppable().add(caches).stop();
+                FileLock.State state = fileLock.getState();
+                for (MultiProcessSafePersistentIndexedCache cache : caches) {
+                    cache.onEndWork(state);
+                }
+            } finally {
+                fileLock.close();
+            }
+        } finally {
+            fileLock = null;
+            stateAtOpen = null;
+            contended = false;
+        }
+    }
+
     public void close() {
         lock.lock();
         try {
-            for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
-                cache.close();
+            // Take ownership
+            if (owner == null) {
+                owner = Thread.currentThread();
+            } else if (lockOptions.getMode() != Shared && owner != Thread.currentThread()) {
+                // TODO:ADAM - The check for shared mode is a work around. Owner should release the lock
+                throw new IllegalStateException(String.format("Cannot close %s as it is currently being used by another thread.", cacheDisplayName));
             }
-            operationStack.remove();
-            lockMode = null;
-            owner = null;
             if (fileLock != null) {
-                try {
-                    fileLock.close();
-                } finally {
-                    fileLock = null;
-                }
+                closeFileLock();
+            }
+            if (cacheClosedCount != 1) {
+                LOG.debug("Cache {} was closed {} times.", cacheDisplayName, cacheClosedCount);
             }
         } finally {
+            lockOptions = null;
+            owner = null;
             lock.unlock();
         }
     }
 
-    public FileLock getFileLock() {
-        return fileLock;
-    }
-
     public void useCache(String operationDisplayName, Runnable action) {
         useCache(operationDisplayName, Factories.toFactory(action));
     }
 
     public <T> T useCache(String operationDisplayName, Factory<? extends T> factory) {
-        if (lockMode == FileLockManager.LockMode.Shared) {
+        if (lockOptions != null && lockOptions.getMode() == FileLockManager.LockMode.Shared) {
             throw new UnsupportedOperationException("Not implemented yet.");
         }
 
         takeOwnership(operationDisplayName);
+        boolean wasStarted = false;
         try {
-            boolean wasStarted = onStartWork();
+            wasStarted = onStartWork();
+            return factory.create();
+        } finally {
+            lock.lock();
             try {
-                return factory.create();
-            } finally {
-                if (wasStarted) {
-                    onEndWork();
+                try {
+                    if (wasStarted) {
+                        onEndWork();
+                    }
+                } finally {
+                    releaseOwnership();
                 }
+            } finally {
+                lock.unlock();
             }
-        } finally {
-            releaseOwnership(operationDisplayName);
         }
     }
 
@@ -143,17 +212,17 @@ public class DefaultCacheAccess implements CacheAccess {
                 }
             }
             owner = Thread.currentThread();
-            operationStack.get().pushCacheAction(operationDisplayName);
+            operations.pushCacheAction(operationDisplayName);
         } finally {
             lock.unlock();
         }
     }
 
-    private void releaseOwnership(String operationDisplayName) {
+    private void releaseOwnership() {
         lock.lock();
         try {
-            operationStack.get().popCacheAction(operationDisplayName);
-            if (!operationStack.get().isInCacheAction()) {
+            operations.popCacheAction();
+            if (!operations.isInCacheAction()) {
                 owner = null;
                 condition.signalAll();
             }
@@ -163,55 +232,63 @@ public class DefaultCacheAccess implements CacheAccess {
     }
 
     public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
-        if (operationStack.get().isInLongRunningOperation()) {
-            operationStack.get().pushLongRunningOperation(operationDisplayName);
-            try {
-                return action.create();
-            } finally {
-                operationStack.get().popLongRunningOperation(operationDisplayName);
-            }
-        }
-
-        checkThreadIsOwner();
-        boolean wasEnded = onEndWork();
-        parkOwner(operationDisplayName);
+        boolean wasEnded = startLongRunningOperation(operationDisplayName);
         try {
             return action.create();
         } finally {
-            restoreOwner(operationDisplayName);
-            if (wasEnded) {
-                onStartWork();
+            finishLongRunningOperation(wasEnded);
+        }
+    }
+
+    private boolean startLongRunningOperation(String operationDisplayName) {
+        boolean wasEnded;
+        lock.lock();
+        try {
+            if (lockOptions == null || lockOptions.getMode() == Shared) {
+                throw new UnsupportedOperationException("Not supported for this lock mode.");
+            }
+            if (operations.isInCacheAction()) {
+                checkThreadIsOwner();
+                wasEnded = onEndWork();
+                owner = null;
+                condition.signalAll();
+            } else {
+                wasEnded = false;
             }
+            operations.pushLongRunningOperation(operationDisplayName);
+        } finally {
+            lock.unlock();
         }
+        return wasEnded;
     }
 
-    private void checkThreadIsOwner() {
+    private void finishLongRunningOperation(boolean wasEnded) {
         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));
+            operations.popLongRunningOperation();
+            if (operations.isInCacheAction()) {
+                restoreOwner();
+                if (wasEnded) {
+                    onStartWork();
+                }
             }
         } finally {
             lock.unlock();
         }
     }
 
-    private void parkOwner(String operationDisplayName) {
+    private void checkThreadIsOwner() {
         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));
+                throw new IllegalStateException(String.format("Cannot start long running operation, as the %s has not been locked.", cacheDisplayName));
             }
-            owner = null;
-            condition.signalAll();
-
-            operationStack.get().pushLongRunningOperation(operationDisplayName);
         } finally {
             lock.unlock();
         }
     }
 
-    private void restoreOwner(String description) {
+    private void restoreOwner() {
         lock.lock();
         try {
             while (owner != null) {
@@ -222,7 +299,6 @@ public class DefaultCacheAccess implements CacheAccess {
                 }
             }
             owner = Thread.currentThread();
-            operationStack.get().popLongRunningOperation(description);
         } finally {
             lock.unlock();
         }
@@ -232,26 +308,24 @@ public class DefaultCacheAccess implements CacheAccess {
         longRunningOperation(operationDisplayName, Factories.toFactory(action));
     }
 
-    public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Class<K> keyType, final Class<V> valueType) {
-        return newCache(cacheFile, keyType, new DefaultSerializer<V>(valueType.getClassLoader()));
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Class<K> keyType, final Serializer<V> valueSerializer) {
-        return newCache(cacheFile, new DefaultSerializer<K>(keyType.getClassLoader()), valueSerializer);
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Serializer<K> keySerializer, final Serializer<V> valueSerializer) {
+    public <K, V> MultiProcessSafePersistentIndexedCache<K, V> newCache(final PersistentIndexedCacheParameters<K, V> parameters) {
+        final File cacheFile = new File(baseDir, parameters.getCacheName() + ".bin");
         Factory<BTreePersistentIndexedCache<K, V>> indexedCacheFactory = new Factory<BTreePersistentIndexedCache<K, V>>() {
             public BTreePersistentIndexedCache<K, V> create() {
-                return doCreateCache(cacheFile, keySerializer, valueSerializer);
+                return doCreateCache(cacheFile, parameters.getKeySerializer(), parameters.getValueSerializer());
             }
         };
-        MultiProcessSafePersistentIndexedCache<K, V> indexedCache = new MultiProcessSafePersistentIndexedCache<K, V>(indexedCacheFactory, fileAccess);
+
+        MultiProcessSafePersistentIndexedCache<K, V> indexedCache = new DefaultMultiProcessSafePersistentIndexedCache<K, V>(indexedCacheFactory, fileAccess);
+        CacheDecorator decorator = parameters.getCacheDecorator();
+        indexedCache = decorator == null ? indexedCache : decorator.decorate(cacheFile.getAbsolutePath(), parameters.getCacheName(), indexedCache);
+
         lock.lock();
         try {
             caches.add(indexedCache);
             if (fileLock != null) {
-                indexedCache.onStartWork(operationStack.get().getDescription());
+                String description = operations.isInCacheAction() ? operations.getDescription() : "cache creation";
+                indexedCache.onStartWork(description, stateAtOpen);
             }
         } finally {
             lock.unlock();
@@ -259,7 +333,7 @@ public class DefaultCacheAccess implements CacheAccess {
         return indexedCache;
     }
 
-    <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(final File cacheFile, final Serializer<K> keySerializer, final Serializer<V> valueSerializer) {
+    <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
         return new BTreePersistentIndexedCache<K, V>(cacheFile, keySerializer, valueSerializer);
     }
 
@@ -267,11 +341,21 @@ public class DefaultCacheAccess implements CacheAccess {
         if (fileLock != null) {
             return false;
         }
-
-        fileLock = lockManager.lock(lockFile, Exclusive, cacheDiplayName, operationStack.get().getDescription());
-        for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
-            cache.onStartWork(operationStack.get().getDescription());
+        fileLock = lockManager.lock(lockTarget, lockOptions.withMode(Exclusive), cacheDisplayName, operations.getDescription());
+        if (initializationAction.requiresInitialization(fileLock)) {
+            fileLock.writeFile(new Runnable() {
+                public void run() {
+                    initializationAction.initialize(fileLock);
+                }
+            });
         }
+        stateAtOpen = fileLock.getState();
+        for (UnitOfWorkParticipant cache : caches) {
+            cache.onStartWork(operations.getDescription(), stateAtOpen);
+        }
+
+        lockManager.allowContention(fileLock, whenContended());
+
         return true;
     }
 
@@ -279,14 +363,8 @@ public class DefaultCacheAccess implements CacheAccess {
         if (fileLock == null) {
             return false;
         }
-
-        try {
-            for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
-                cache.onEndWork();
-            }
-            fileLock.close();
-        } finally {
-            fileLock = null;
+        if (contended || fileLock.getMode() == Shared) {
+            closeFileLock();
         }
         return true;
     }
@@ -294,8 +372,8 @@ public class DefaultCacheAccess implements CacheAccess {
     private FileLock getLock() {
         lock.lock();
         try {
-            if (Thread.currentThread() != owner || fileLock == null) {
-                throw new IllegalStateException(String.format("The %s has not been locked.", cacheDiplayName));
+            if (Thread.currentThread() != owner) {
+                throw new IllegalStateException(String.format("The %s has not been locked for this thread. File lock: %s, owner: %s", cacheDisplayName, fileLock != null, owner));
             }
         } finally {
             lock.unlock();
@@ -304,6 +382,11 @@ public class DefaultCacheAccess implements CacheAccess {
     }
 
     private class UnitOfWorkFileAccess extends AbstractFileAccess {
+        @Override
+        public String toString() {
+            return cacheDisplayName;
+        }
+
         public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException {
             return getLock().readFile(action);
         }
@@ -317,61 +400,39 @@ 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();
+    Runnable whenContended() {
+        return new Runnable() {
+            public void run() {
+                lock.lock();
+                try {
+                    LOG.debug("Detected file lock contention of {} (fileLock={}, contended={}, owner={})", cacheDisplayName, fileLock != null, contended, owner);
+                    if (fileLock == null) {
+                        //the lock may have been closed
+                        return;
+                    }
+                    if (owner != null) {
+                        contended = true;
+                        return;
+                    }
+
+                    takeOwnership("Other process requested access to " + cacheDisplayName);
+                    try {
+                        closeFileLock();
+                    } finally {
+                        releaseOwnership();
+                    }
+                } finally {
+                    lock.unlock();
+                }
             }
-        }
+        };
     }
 
-    private class CacheOperation {
-        final String description;
-        final boolean longRunningOperation;
+    Thread getOwner() {
+        return owner;
+    }
 
-        private CacheOperation(String description, boolean longRunningOperation) {
-            this.description = description;
-            this.longRunningOperation = longRunningOperation;
-        }
+    FileAccess getFileAccess() {
+        return fileAccess;
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
index e668d55..54f486d 100755
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
@@ -17,234 +17,180 @@ package org.gradle.cache.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.internal.Factory;
 import org.gradle.cache.*;
-import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
-import org.gradle.messaging.serialize.DefaultSerializer;
+import org.gradle.cache.internal.filelock.LockOptions;
+import org.gradle.internal.Factory;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GFileUtils;
 
+import java.io.Closeable;
 import java.io.File;
 import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
-import static org.gradle.cache.internal.FileLockManager.LockMode;
-
-public class DefaultCacheFactory implements Factory<CacheFactory> {
+public class DefaultCacheFactory implements CacheFactory {
     private final Map<File, DirCacheReference> dirCaches = new HashMap<File, DirCacheReference>();
     private final FileLockManager lockManager;
+    private final Lock lock = new ReentrantLock();
 
     public DefaultCacheFactory(FileLockManager fileLockManager) {
         this.lockManager = fileLockManager;
     }
 
-    public CacheFactory create() {
-        return new CacheFactoryImpl();
-    }
-
     void onOpen(Object cache) {
     }
 
     void onClose(Object cache) {
     }
 
-    public void close() {
-        for (DirCacheReference dirCacheReference : dirCaches.values()) {
-            dirCacheReference.close();
+    public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockOptions lockOptions, Action<? super PersistentCache> initializer) throws CacheOpenException {
+        lock.lock();
+        try {
+            return doOpen(cacheDir, displayName, usage, cacheValidator, properties, lockOptions, initializer);
+        } finally {
+            lock.unlock();
         }
     }
 
-    private class CacheFactoryImpl implements CacheFactory {
-        private final Set<BasicCacheReference<?>> caches = new LinkedHashSet<BasicCacheReference<?>>();
-
-        private DirCacheReference doOpenDir(File cacheDir, String displayName, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Action<? super PersistentCache> action) {
-            File canonicalDir = GFileUtils.canonicalise(cacheDir);
-            DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
-            if (dirCacheReference == null) {
-                if (lockMode.equals(LockMode.None)) {
-                    // Create nested cache with LockMode#Exclusive (tb discussed) that is opened and closed on Demand in the DelegateOnDemandPersistentDirectoryCache.
-                    DefaultPersistentDirectoryCache nestedCache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, LockMode.Exclusive, action, lockManager);
-                    DelegateOnDemandPersistentDirectoryCache onDemandDache = new DelegateOnDemandPersistentDirectoryCache(nestedCache);
-                    onDemandDache.open();
-                    dirCacheReference = new DirCacheReference(onDemandDache, properties, lockMode);
-                    dirCaches.put(canonicalDir, dirCacheReference);
-                } else {
-                    ReferencablePersistentCache cache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, lockMode, action, lockManager);
-                    cache.open();
-                    dirCacheReference = new DirCacheReference(cache, properties, lockMode);
-                    dirCaches.put(canonicalDir, dirCacheReference);
-                }
-            } else {
-                if (usage == CacheUsage.REBUILD && dirCacheReference.rebuiltBy != this) {
-                    throw new IllegalStateException(String.format("Cannot rebuild cache '%s' as it is already open.", cacheDir));
-                }
-                if (lockMode != dirCacheReference.lockMode) {
-                    throw new IllegalStateException(String.format("Cannot open cache '%s' with %s lock mode as it is already open with %s lock mode.", cacheDir, lockMode.toString().toLowerCase(), dirCacheReference.lockMode.toString().toLowerCase()));
-                }
-                if (!properties.equals(dirCacheReference.properties)) {
-                    throw new IllegalStateException(String.format("Cache '%s' is already open with different state.", cacheDir));
-                }
-            }
-            if (usage == CacheUsage.REBUILD) {
-                dirCacheReference.rebuiltBy = this;
-            }
-            dirCacheReference.addReference(this);
-            return dirCacheReference;
-        }
-
-        public PersistentCache openStore(File storeDir, String displayName, LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException {
-            if (initializer != null) {
-                throw new UnsupportedOperationException("Initializer actions are not currently supported by the directory store implementation.");
-            }
-            File canonicalDir = GFileUtils.canonicalise(storeDir);
-            DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
-            if (dirCacheReference == null) {
-                ReferencablePersistentCache cache = new DefaultPersistentDirectoryStore(canonicalDir, displayName, lockMode, lockManager);
-                cache.open();
-                dirCacheReference = new DirCacheReference(cache, Collections.<String, Object>emptyMap(), lockMode);
-                dirCaches.put(canonicalDir, dirCacheReference);
-            }
-            dirCacheReference.addReference(this);
-            return dirCacheReference.getCache();
-        }
-
-        public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initializer) {
-            DirCacheReference dirCacheReference = doOpenDir(cacheDir, displayName, usage, cacheValidator, properties, lockMode, initializer);
-            return dirCacheReference.getCache();
+    public PersistentCache openStore(File storeDir, String displayName, LockOptions lockOptions, Action<? super PersistentCache> initializer) throws CacheOpenException {
+        lock.lock();
+        try {
+            return doOpenStore(storeDir, displayName, lockOptions, initializer);
+        } finally {
+            lock.unlock();
         }
+    }
 
-        public <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Serializer<E> serializer) {
-            StateCacheReference<E> cacheReference = doOpenDir(cacheDir, null, usage, validator, properties, lockMode, null).getStateCache(serializer);
-            cacheReference.addReference(this);
-            return cacheReference.getCache();
+    public void close() {
+        lock.lock();
+        try {
+            CompositeStoppable.stoppable(dirCaches.values()).stop();
+        } finally {
+            dirCaches.clear();
+            lock.unlock();
         }
+    }
 
-        public <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Serializer<V> serializer) {
-            if (lockMode != LockMode.Exclusive) {
-                throw new UnsupportedOperationException(String.format("No %s mode indexed cache implementation is available.", lockMode));
+    private PersistentCache doOpen(File cacheDir, String displayName, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, LockOptions lockOptions, Action<? super PersistentCache> action) {
+        File canonicalDir = GFileUtils.canonicalise(cacheDir);
+        DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
+        if (dirCacheReference == null) {
+            ReferencablePersistentCache cache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, lockOptions, action, lockManager);
+            cache.open();
+            dirCacheReference = new DirCacheReference(cache, properties, lockOptions, usage == CacheUsage.REBUILD);
+            dirCaches.put(canonicalDir, dirCacheReference);
+        } else {
+            if (usage == CacheUsage.REBUILD && !dirCacheReference.rebuild) {
+                throw new IllegalStateException(String.format("Cannot rebuild cache '%s' as it is already open.", cacheDir));
+            }
+            if (!lockOptions.equals(dirCacheReference.lockOptions)) {
+                throw new IllegalStateException(String.format("Cache '%s' is already open with different options.", cacheDir));
+            }
+            if (!properties.equals(dirCacheReference.properties)) {
+                throw new IllegalStateException(String.format("Cache '%s' is already open with different state.", cacheDir));
             }
-            IndexedCacheReference<K, V> cacheReference = doOpenDir(cacheDir, null, usage, validator, properties, LockMode.Exclusive, null).getIndexedCache(serializer);
-            cacheReference.addReference(this);
-            return cacheReference.getCache();
         }
+        return new ReferenceTrackingCache(dirCacheReference);
+    }
 
-        public void close() {
-            try {
-                List<BasicCacheReference<?>> caches = new ArrayList<BasicCacheReference<?>>(this.caches);
-                Collections.reverse(caches);
-                for (BasicCacheReference cache : caches) {
-                    cache.release(this);
-                }
-            } finally {
-                caches.clear();
-            }
+    private PersistentCache doOpenStore(File storeDir, String displayName, LockOptions lockOptions, Action<? super PersistentCache> initializer) throws CacheOpenException {
+        if (initializer != null) {
+            throw new UnsupportedOperationException("Initializer actions are not currently supported by the directory store implementation.");
         }
+        File canonicalDir = GFileUtils.canonicalise(storeDir);
+        DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
+        if (dirCacheReference == null) {
+            ReferencablePersistentCache cache = new DefaultPersistentDirectoryStore(canonicalDir, displayName, lockOptions, lockManager);
+            cache.open();
+            dirCacheReference = new DirCacheReference(cache, Collections.<String, Object>emptyMap(), lockOptions, false);
+            dirCaches.put(canonicalDir, dirCacheReference);
+        }
+        return new ReferenceTrackingCache(dirCacheReference);
     }
 
-    private abstract class BasicCacheReference<T> {
-        private Set<CacheFactoryImpl> references = new HashSet<CacheFactoryImpl>();
-        private final T cache;
+    private class DirCacheReference implements Closeable {
+        private final Map<String, ?> properties;
+        private final LockOptions lockOptions;
+        private final ReferencablePersistentCache cache;
+        private final Set<ReferenceTrackingCache> references = new HashSet<ReferenceTrackingCache>();
+        private final boolean rebuild;
 
-        protected BasicCacheReference(T cache) {
+        public DirCacheReference(ReferencablePersistentCache cache, Map<String, ?> properties, LockOptions lockOptions, boolean rebuild) {
             this.cache = cache;
+            this.properties = properties;
+            this.lockOptions = lockOptions;
+            this.rebuild = rebuild;
             onOpen(cache);
         }
 
-        public T getCache() {
-            return cache;
+        public void addReference(ReferenceTrackingCache cache) {
+            references.add(cache);
         }
 
-        public void release(CacheFactoryImpl owner) {
-            boolean removed = references.remove(owner);
-            assert removed;
-            if (references.isEmpty()) {
-                onClose(cache);
-                close();
+        public void release(ReferenceTrackingCache cache) {
+            lock.lock();
+            try {
+                if (references.remove(cache) && references.isEmpty()) {
+                    close();
+                }
+            } finally {
+                lock.unlock();
             }
         }
 
-        public void addReference(CacheFactoryImpl owner) {
-            references.add(owner);
-            owner.caches.add(this);
-        }
-
         public void close() {
+            onClose(cache);
+            dirCaches.values().remove(this);
+            references.clear();
+            cache.close();
         }
     }
 
-    private class DirCacheReference extends BasicCacheReference<ReferencablePersistentCache> {
-        private final Map<String, ?> properties;
-        private final FileLockManager.LockMode lockMode;
-        IndexedCacheReference indexedCache;
-        StateCacheReference stateCache;
-        CacheFactoryImpl rebuiltBy;
+    private static class ReferenceTrackingCache implements PersistentCache {
+        private final DirCacheReference reference;
 
-        public DirCacheReference(ReferencablePersistentCache cache, Map<String, ?> properties, FileLockManager.LockMode lockMode) {
-            super(cache);
-            this.properties = properties;
-            this.lockMode = lockMode;
+        private ReferenceTrackingCache(DirCacheReference reference) {
+            this.reference = reference;
+            reference.addReference(this);
         }
 
-        public <E> StateCacheReference<E> getStateCache(Serializer<E> serializer) {
-            if (stateCache == null) {
-                SimpleStateCache<E> stateCache = new SimpleStateCache<E>(new File(getCache().getBaseDir(), "state.bin"), getCache().getLock(), serializer);
-                this.stateCache = new StateCacheReference<E>(stateCache, this);
-            }
-            return stateCache;
-        }
-
-        public <K, V> IndexedCacheReference<K, V> getIndexedCache(Serializer<V> serializer) {
-            if (indexedCache == null) {
-                final BTreePersistentIndexedCache<K, V> indexedCache = new BTreePersistentIndexedCache<K, V>(new File(getCache().getBaseDir(), "cache.bin"),
-                        new DefaultSerializer<K>(),
-                        serializer);
-                Factory<BTreePersistentIndexedCache<K, V>> cacheFactory = new Factory<BTreePersistentIndexedCache<K, V>>() {
-                    public BTreePersistentIndexedCache<K, V> create() {
-                        return indexedCache;
-                    }
-                };
-                MultiProcessSafePersistentIndexedCache<K, V> safeCache = new MultiProcessSafePersistentIndexedCache<K, V>(cacheFactory, getCache().getLock());
-                this.indexedCache = new IndexedCacheReference<K, V>(safeCache, this);
-            }
-            return indexedCache;
+        @Override
+        public String toString() {
+            return reference.cache.toString();
         }
 
         public void close() {
-            dirCaches.values().remove(this);
-            getCache().close();
+            reference.release(this);
         }
-    }
 
-    private abstract class NestedCacheReference<T> extends BasicCacheReference<T> {
-        protected final DefaultCacheFactory.DirCacheReference backingCache;
+        public File getBaseDir() {
+            return reference.cache.getBaseDir();
+        }
 
-        protected NestedCacheReference(T cache, DirCacheReference backingCache) {
-            super(cache);
-            this.backingCache = backingCache;
+        public <K, V> PersistentIndexedCache<K, V> createCache(PersistentIndexedCacheParameters<K, V> parameters) {
+            return reference.cache.createCache(parameters);
         }
-    }
 
-    private class IndexedCacheReference<K, V> extends NestedCacheReference<MultiProcessSafePersistentIndexedCache<K, V>> {
-        private IndexedCacheReference(MultiProcessSafePersistentIndexedCache<K, V> cache, DirCacheReference backingCache) {
-            super(cache, backingCache);
+        public <K, V> PersistentIndexedCache<K, V> createCache(String name, Class<K> keyType, Serializer<V> valueSerializer) {
+            return reference.cache.createCache(name, keyType, valueSerializer);
         }
 
-        @Override
-        public void close() {
-            backingCache.indexedCache = null;
-            getCache().close();
-            super.close();
+        public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
+            return reference.cache.longRunningOperation(operationDisplayName, action);
         }
-    }
 
-    private class StateCacheReference<E> extends NestedCacheReference<SimpleStateCache<E>> {
-        private StateCacheReference(SimpleStateCache<E> cache, DirCacheReference backingCache) {
-            super(cache, backingCache);
+        public void longRunningOperation(String operationDisplayName, Runnable action) {
+            reference.cache.longRunningOperation(operationDisplayName, action);
         }
 
-        @Override
-        public void close() {
-            backingCache.stateCache = null;
-            super.close();
+        public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+            return reference.cache.useCache(operationDisplayName, action);
+        }
+
+        public void useCache(String operationDisplayName, Runnable action) {
+            reference.cache.useCache(operationDisplayName, action);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
index 545d862..a99d2f8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
@@ -17,177 +17,134 @@ package org.gradle.cache.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.cache.*;
-import org.gradle.messaging.serialize.DefaultSerializer;
-import org.gradle.messaging.serialize.Serializer;
-import org.gradle.util.GradleVersion;
+import org.gradle.cache.CacheBuilder;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.internal.filelock.LockOptions;
 
 import java.io.File;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 
 import static org.gradle.cache.internal.FileLockManager.LockMode;
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
 
 public class DefaultCacheRepository implements CacheRepository {
-    private final GradleVersion version = GradleVersion.current();
-    private final File globalCacheDir;
     private final CacheUsage cacheUsage;
-    private final File projectCacheDir;
+    private final CacheScopeMapping cacheScopeMapping;
     private final CacheFactory factory;
 
-    public DefaultCacheRepository(File userHomeDir, File projectCacheDir, CacheUsage cacheUsage, CacheFactory factory) {
-        this.projectCacheDir = projectCacheDir;
+    public DefaultCacheRepository(CacheScopeMapping cacheScopeMapping, CacheUsage cacheUsage, CacheFactory factory) {
+        this.cacheScopeMapping = cacheScopeMapping;
         this.factory = factory;
-        this.globalCacheDir = new File(userHomeDir, "caches");
         this.cacheUsage = cacheUsage;
     }
 
-    public DirectoryCacheBuilder store(String key) {
-        return new PersistentStoreBuilder(key);
+    public CacheBuilder store(String key) {
+        return new PersistentStoreBuilder(null, key);
     }
 
-    public DirectoryCacheBuilder cache(String key) {
-        return new PersistentCacheBuilder(key);
+    public CacheBuilder store(Object scope, String key) {
+        return new PersistentStoreBuilder(scope, key);
     }
 
-    public <E> ObjectCacheBuilder<E, PersistentStateCache<E>> stateCache(Class<E> elementType, String key) {
-        return new StateCacheBuilder<E>(key);
+    public CacheBuilder cache(String key) {
+        return new PersistentCacheBuilder(null, key);
     }
 
-    public <K, V> ObjectCacheBuilder<V, PersistentIndexedCache<K, V>> indexedCache(Class<K> keyType, Class<V> elementType, String key) {
-        return new IndexedCacheBuilder<K, V>(key);
+    public CacheBuilder cache(File baseDir) {
+        return new PersistentCacheBuilder(baseDir);
     }
 
-    private abstract class AbstractCacheBuilder<T> implements CacheBuilder<T> {
-        private final String key;
-        private Map<String, ?> properties = Collections.emptyMap();
-        private Object target;
-        private VersionStrategy versionStrategy = VersionStrategy.CachePerVersion;
-        private CacheValidator validator;
+    public CacheBuilder cache(Object scope, String key) {
+        return new PersistentCacheBuilder(scope, key);
+    }
 
-        protected AbstractCacheBuilder(String key) {
+    private abstract class AbstractCacheBuilder implements CacheBuilder {
+        final Object scope;
+        final String key;
+        final File baseDir;
+        Map<String, ?> properties = Collections.emptyMap();
+        CacheValidator validator;
+        Action<? super PersistentCache> initializer;
+        LockOptions lockOptions = mode(LockMode.Shared);
+        String displayName;
+        VersionStrategy versionStrategy = VersionStrategy.CachePerVersion;
+
+        protected AbstractCacheBuilder(Object scope, String key) {
+            this.scope = scope;
             this.key = key;
+            this.baseDir = null;
         }
 
-        public CacheBuilder<T> withProperties(Map<String, ?> properties) {
-            this.properties = properties;
-            return this;
+        protected AbstractCacheBuilder(File baseDir) {
+            this.scope = null;
+            this.key = null;
+            this.baseDir = baseDir;
         }
 
-        public CacheBuilder<T> withVersionStrategy(VersionStrategy strategy) {
-            this.versionStrategy = strategy;
+        public CacheBuilder withProperties(Map<String, ?> properties) {
+            this.properties = properties;
             return this;
         }
 
-        public CacheBuilder<T> forObject(Object target) {
-            this.target = target;
+        public CacheBuilder withCrossVersionCache() {
+            this.versionStrategy = VersionStrategy.SharedCache;
             return this;
         }
 
-        public CacheBuilder<T> withValidator(CacheValidator validator) {
+        public CacheBuilder withValidator(CacheValidator validator) {
             this.validator = validator;
             return this;
         }
 
-        public T open() {
-            File cacheBaseDir;
-            Map<String, Object> properties = new HashMap<String, Object>(this.properties);
-            if (target == null) {
-                cacheBaseDir = globalCacheDir;
-            } else if (target instanceof Gradle) {
-                Gradle gradle = (Gradle) target;
-                File rootProjectDir = gradle.getRootProject().getProjectDir();
-                cacheBaseDir = maybeProjectCacheDir(rootProjectDir);
-            } else if (target instanceof File) {
-                cacheBaseDir = new File((File) target, ".gradle");
-            } else {
-                throw new IllegalArgumentException(String.format("Cannot create cache for unrecognised domain object %s.", target));
-            }
-            switch (versionStrategy) {
-                case SharedCache:
-                    // Use the root directory
-                    break;
-                case CachePerVersion:
-                    cacheBaseDir = new File(cacheBaseDir, version.getVersion());
-                    break;
-                case SharedCacheInvalidateOnVersionChange:
-                    // Include the 'noVersion' suffix for backwards compatibility
-                    cacheBaseDir = new File(cacheBaseDir, "noVersion");
-                    properties.put("gradle.version", version.getVersion());
-                    break;
-            }
-            return doOpen(new File(cacheBaseDir, key), properties, validator);
-        }
-
-        protected abstract T doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator);
-
-        private File maybeProjectCacheDir(File potentialParentDir) {
-            if (projectCacheDir != null) {
-                return projectCacheDir;
-            }
-            return new File(potentialParentDir, ".gradle");
-        }
-    }
-
-    private class PersistentCacheBuilder extends AbstractCacheBuilder<PersistentCache> implements DirectoryCacheBuilder {
-        Action<? super PersistentCache> initializer;
-        LockMode lockMode = LockMode.Shared;
-        String displayName;
-
-        protected PersistentCacheBuilder(String key) {
-            super(key);
-        }
-
-        @Override
-        public DirectoryCacheBuilder forObject(Object target) {
-            super.forObject(target);
+        public CacheBuilder withDisplayName(String displayName) {
+            this.displayName = displayName;
             return this;
         }
 
-        @Override
-        public DirectoryCacheBuilder withProperties(Map<String, ?> properties) {
-            super.withProperties(properties);
+        public CacheBuilder withLockOptions(LockOptions lockOptions) {
+            this.lockOptions = lockOptions;
             return this;
         }
 
-        @Override
-        public DirectoryCacheBuilder withVersionStrategy(VersionStrategy strategy) {
-            super.withVersionStrategy(strategy);
+        public CacheBuilder withInitializer(Action<? super PersistentCache> initializer) {
+            this.initializer = initializer;
             return this;
         }
 
-        @Override
-        public DirectoryCacheBuilder withValidator(CacheValidator validator) {
-            super.withValidator(validator);
-            return this;
+        public PersistentCache open() {
+            File cacheBaseDir;
+            if (baseDir != null) {
+                cacheBaseDir = baseDir;
+            } else {
+                cacheBaseDir = cacheScopeMapping.getBaseDirectory(scope, key, versionStrategy);
+            }
+            return doOpen(cacheBaseDir, properties, validator);
         }
 
-        public DirectoryCacheBuilder withInitializer(Action<? super PersistentCache> initializer) {
-            this.initializer = initializer;
-            return this;
-        }
+        protected abstract PersistentCache doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator);
+    }
 
-        public DirectoryCacheBuilder withDisplayName(String displayName) {
-            this.displayName = displayName;
-            return this;
+    private class PersistentCacheBuilder extends AbstractCacheBuilder {
+        private PersistentCacheBuilder(Object scope, String key) {
+            super(scope, key);
         }
 
-        public DirectoryCacheBuilder withLockMode(LockMode lockMode) {
-            this.lockMode = lockMode;
-            return this;
+        private PersistentCacheBuilder(File baseDir) {
+            super(baseDir);
         }
 
         @Override
         protected PersistentCache doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
-            return factory.open(cacheDir, displayName, cacheUsage, validator, properties, lockMode, initializer);
+            return factory.open(cacheDir, displayName, cacheUsage, validator, properties, lockOptions, initializer);
         }
     }
 
-    private class PersistentStoreBuilder extends PersistentCacheBuilder {
-        private PersistentStoreBuilder(String key) {
-            super(key);
+    private class PersistentStoreBuilder extends AbstractCacheBuilder {
+        private PersistentStoreBuilder(Object scope, String key) {
+            super(scope, key);
         }
 
         @Override
@@ -195,60 +152,7 @@ public class DefaultCacheRepository implements CacheRepository {
             if (!properties.isEmpty()) {
                 throw new UnsupportedOperationException("Properties are not supported for stores.");
             }
-            return factory.openStore(cacheDir, displayName, lockMode, initializer);
-        }
-    }
-
-    private abstract class AbstractObjectCacheBuilder<E, T> extends AbstractCacheBuilder<T> implements ObjectCacheBuilder<E, T> {
-        protected Serializer<E> serializer = new DefaultSerializer<E>();
-
-        protected AbstractObjectCacheBuilder(String key) {
-            super(key);
-        }
-
-        @Override
-        public ObjectCacheBuilder<E, T> forObject(Object target) {
-            super.forObject(target);
-            return this;
-        }
-
-        @Override
-        public ObjectCacheBuilder<E, T> withProperties(Map<String, ?> properties) {
-            super.withProperties(properties);
-            return this;
-        }
-
-        @Override
-        public ObjectCacheBuilder<E, T> withVersionStrategy(VersionStrategy strategy) {
-            super.withVersionStrategy(strategy);
-            return this;
-        }
-
-        public ObjectCacheBuilder<E, T> withSerializer(Serializer<E> serializer) {
-            this.serializer = serializer;
-            return this;
-        }
-    }
-
-    private class StateCacheBuilder<E> extends AbstractObjectCacheBuilder<E, PersistentStateCache<E>>  {
-        protected StateCacheBuilder(String key) {
-            super(key);
-        }
-
-        @Override
-        protected PersistentStateCache<E> doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
-            return factory.openStateCache(cacheDir, cacheUsage, validator, properties, LockMode.Exclusive, serializer);
-        }
-    }
-
-    private class IndexedCacheBuilder<K, V> extends AbstractObjectCacheBuilder<V, PersistentIndexedCache<K, V>> {
-        private IndexedCacheBuilder(String key) {
-            super(key);
-        }
-
-        @Override
-        protected PersistentIndexedCache<K, V> doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
-            return factory.openIndexedCache(cacheDir, cacheUsage, validator, properties, LockMode.Exclusive, serializer);
+            return factory.openStore(cacheDir, displayName, lockOptions, initializer);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheScopeMapping.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheScopeMapping.java
new file mode 100644
index 0000000..4ac9791
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheScopeMapping.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheBuilder;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+
+public class DefaultCacheScopeMapping implements CacheScopeMapping {
+    private final File globalCacheDir;
+    private final File buildCacheDir;
+    private final GradleVersion version;
+
+    public DefaultCacheScopeMapping(File userHomeDir, File buildCacheDir, GradleVersion version) {
+        this.version = version;
+        this.globalCacheDir = new File(userHomeDir, "caches");
+        this.buildCacheDir = buildCacheDir;
+    }
+
+    public File getBaseDirectory(Object scope, String key, CacheBuilder.VersionStrategy versionStrategy) {
+        if (key.equalsIgnoreCase("projects") || key.equalsIgnoreCase("tasks") || !key.matches("\\p{Alpha}+[-//.\\w]*")) {
+            throw new IllegalArgumentException(String.format("Unsupported cache key '%s'.", key));
+        }
+        if (scope == null) {
+            return getCacheDir(globalCacheDir, versionStrategy, key);
+        }
+        if (scope instanceof Gradle) {
+            Gradle gradle = (Gradle) scope;
+            return getCacheDir(getBuildCacheDir(gradle.getRootProject()), versionStrategy, key);
+        }
+        if (scope instanceof Project) {
+            Project project = (Project) scope;
+            return getCacheDir(getBuildCacheDir(project.getRootProject()), versionStrategy, String.format("projects/%s/%s", project.getPath().replace(':', '_'), key));
+        }
+        if (scope instanceof Task) {
+            Task task = (Task) scope;
+            return getCacheDir(getBuildCacheDir(task.getProject().getRootProject()), versionStrategy, String.format("tasks/%s/%s", task.getPath().replace(':', '_'), key));
+        }
+        throw new IllegalArgumentException(String.format("Don't know how to determine the cache directory for scope of type %s.", scope.getClass().getSimpleName()));
+    }
+
+    private File getCacheDir(File rootDir, CacheBuilder.VersionStrategy versionStrategy, String subDir) {
+        switch (versionStrategy) {
+            case CachePerVersion:
+                return new File(rootDir, version.getVersion() + "/" + subDir);
+            case SharedCache:
+                return new File(rootDir, subDir);
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    private File getBuildCacheDir(Project rootProject) {
+        if (buildCacheDir != null) {
+            return buildCacheDir;
+        }
+        return new File(rootProject.getProjectDir(), ".gradle");
+    }
+}
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 a328e8a..a5991e6 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
@@ -15,68 +15,80 @@
  */
 package org.gradle.cache.internal;
 
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.cache.internal.filelock.*;
+import org.gradle.cache.internal.locklistener.FileLockContentionHandler;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.Factory;
-import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.Stoppable;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.RandomLongIdGenerator;
 import org.gradle.util.GFileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import java.io.EOFException;
 import java.io.File;
 import java.io.IOException;
-import java.io.RandomAccessFile;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
 
+import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
+
 /**
- * Uses file system locks on a lock file per target file. Each lock file is made up of 2 regions:
- *
- * <ul> <li>State region: 1 byte version field, 1 byte clean flag.</li> <li>Owner information region: 1 byte version field, utf-8 encoded owner process id, utf-8 encoded owner operation display
- * name.</li> </ul>
+ * Uses file system locks on a lock file per target file.
  */
 public class DefaultFileLockManager implements FileLockManager {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileLockManager.class);
-    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;
-    private static final byte INFORMATION_REGION_PROTOCOL = 2;
-    private static final int INFORMATION_REGION_POS = STATE_REGION_POS + STATE_REGION_SIZE;
-    public static final int INFORMATION_REGION_SIZE = 2048;
-    public static final int INFORMATION_REGION_DESCR_CHUNK_LIMIT = 340;
+    private static final Logger LOGGER = Logging.getLogger(DefaultFileLockManager.class);
+    public static final int DEFAULT_LOCK_TIMEOUT = 60000;
+
     private final Set<File> lockedFiles = new CopyOnWriteArraySet<File>();
     private final ProcessMetaDataProvider metaDataProvider;
     private final int lockTimeoutMs;
+    private final IdGenerator<Long> generator;
+    private final FileLockContentionHandler fileLockContentionHandler;
+    private final long shortTimeoutMs = 10000;
+
+    public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider, FileLockContentionHandler fileLockContentionHandler) {
+        this(metaDataProvider, DEFAULT_LOCK_TIMEOUT, fileLockContentionHandler);
+    }
 
-    public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider) {
-        this(metaDataProvider, DEFAULT_LOCK_TIMEOUT);
+    public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider, int lockTimeoutMs, FileLockContentionHandler fileLockContentionHandler) {
+        this(metaDataProvider, lockTimeoutMs, fileLockContentionHandler, new RandomLongIdGenerator());
     }
 
-    public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider, int lockTimeoutMs) {
+    DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider, int lockTimeoutMs, FileLockContentionHandler fileLockContentionHandler,
+                           IdGenerator<Long> generator) {
         this.metaDataProvider = metaDataProvider;
         this.lockTimeoutMs = lockTimeoutMs;
+        this.fileLockContentionHandler = fileLockContentionHandler;
+        this.generator = generator;
     }
 
-    public FileLock lock(File target, LockMode mode, String targetDisplayName) throws LockTimeoutException {
-        return lock(target, mode, targetDisplayName, "");
+    public FileLock lock(File target, LockOptions options, String targetDisplayName) throws LockTimeoutException {
+        return lock(target, options, targetDisplayName, "");
     }
 
-    public FileLock lock(File target, LockMode mode, String targetDisplayName, String operationDisplayName) {
-        if (mode == LockMode.None) {
-            throw new UnsupportedOperationException(String.format("No %s mode lock implementation available.", mode));
+    public FileLock lock(File target, LockOptions options, String targetDisplayName, String operationDisplayName) {
+        if (options.getMode() == LockMode.None) {
+            throw new UnsupportedOperationException(String.format("No %s mode lock implementation available.", options));
         }
         File canonicalTarget = GFileUtils.canonicalise(target);
         if (!lockedFiles.add(canonicalTarget)) {
             throw new IllegalStateException(String.format("Cannot lock %s as it has already been locked by this process.", targetDisplayName));
         }
         try {
-            return new DefaultFileLock(canonicalTarget, mode, targetDisplayName, operationDisplayName);
+            int port = fileLockContentionHandler.reservePort();
+            return new DefaultFileLock(canonicalTarget, options, targetDisplayName, operationDisplayName, port);
         } catch (Throwable t) {
             lockedFiles.remove(canonicalTarget);
-            throw UncheckedException.throwAsUncheckedException(t);
+            throw throwAsUncheckedException(t);
         }
     }
 
+    public void allowContention(FileLock fileLock, Runnable whenContended) {
+        DefaultFileLock internalLock = (DefaultFileLock) fileLock;
+        fileLockContentionHandler.start(internalLock.lockId, whenContended);
+    }
+
     private class DefaultFileLock extends AbstractFileAccess implements FileLock {
         private final File lockFile;
         private final File target;
@@ -84,11 +96,15 @@ public class DefaultFileLockManager implements FileLockManager {
         private final String displayName;
         private final String operationDisplayName;
         private java.nio.channels.FileLock lock;
-        private RandomAccessFile lockFileAccess;
-        private boolean integrityViolated;
-
-        public DefaultFileLock(File target, LockMode mode, String displayName, String operationDisplayName) throws Throwable {
-            if (mode == LockMode.None) {
+        private LockFileAccess lockFileAccess;
+        private LockState lockState;
+        private int port;
+        private final long lockId;
+
+        public DefaultFileLock(File target, LockOptions options, String displayName, String operationDisplayName, int port) throws Throwable {
+            this.port = port;
+            this.lockId = generator.generateId();
+            if (options.getMode() == LockMode.None) {
                 throw new UnsupportedOperationException("Locking mode None is not supported.");
             }
 
@@ -104,10 +120,11 @@ public class DefaultFileLockManager implements FileLockManager {
 
             GFileUtils.mkdirs(lockFile.getParentFile());
             lockFile.createNewFile();
-            lockFileAccess = new RandomAccessFile(lockFile, "rw");
+
+            LockStateSerializer stateProtocol = options.isUseCrossVersionImplementation() ? new Version1LockStateSerializer() : new DefaultLockStateSerializer();
+            lockFileAccess = new LockFileAccess(lockFile, new LockStateAccess(stateProtocol));
             try {
-                lock = lock(mode);
-                integrityViolated = !getUnlockedCleanly();
+                lockState = lock(options.getMode());
             } catch (Throwable t) {
                 // Also releases any locks
                 lockFileAccess.close();
@@ -123,20 +140,12 @@ public class DefaultFileLockManager implements FileLockManager {
 
         public boolean getUnlockedCleanly() {
             assertOpen();
-            try {
-                lockFileAccess.seek(STATE_REGION_POS + 1);
-                if (!lockFileAccess.readBoolean()) {
-                    // Process has crashed while updating target file
-                    return false;
-                }
-            } catch (EOFException e) {
-                // Process has crashed writing to lock file
-                return false;
-            } catch (Exception e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
+            return !lockState.isDirty();
+        }
 
-            return true;
+        public State getState() {
+            assertOpen();
+            return lockState;
         }
 
         public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException {
@@ -160,13 +169,11 @@ public class DefaultFileLockManager implements FileLockManager {
             }
 
             try {
-                integrityViolated = true;
-                markDirty();
+                lockState = lockFileAccess.markDirty(lockState);
                 action.run();
-                markClean();
-                integrityViolated = false;
+                lockState = lockFileAccess.markClean(lockState);
             } catch (Throwable t) {
-                throw UncheckedException.throwAsUncheckedException(t);
+                throw throwAsUncheckedException(t);
             }
         }
 
@@ -178,153 +185,159 @@ public class DefaultFileLockManager implements FileLockManager {
 
         private void assertOpenAndIntegral() {
             assertOpen();
-            if (integrityViolated) {
+            if (lockState.isDirty()) {
                 throw new FileIntegrityViolationException(String.format("The file '%s' was not unlocked cleanly", target));
             }
         }
 
-        private void markClean() throws IOException {
-            lockFileAccess.seek(STATE_REGION_POS);
-            lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
-            lockFileAccess.writeBoolean(true);
-            assert lockFileAccess.getFilePointer() == STATE_REGION_SIZE + STATE_REGION_POS;
-        }
-
-        private void markDirty() throws IOException {
-            lockFileAccess.seek(STATE_REGION_POS);
-            lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
-            lockFileAccess.writeBoolean(false);
-            assert lockFileAccess.getFilePointer() == STATE_REGION_SIZE + STATE_REGION_POS;
-        }
-
         public void close() {
-            if (lockFileAccess == null) {
-                return;
-            }
-            try {
-                LOGGER.debug("Releasing lock on {}.", displayName);
-                lockedFiles.remove(target);
-                // Also releases any locks
-                try {
-                    if (lock != null && !lock.isShared()) {
-                        // Discard information region
-                        lockFileAccess.setLength(INFORMATION_REGION_POS);
+            CompositeStoppable stoppable = new CompositeStoppable();
+            stoppable.add(new Stoppable() {
+                public void stop() {
+                    try {
+                        fileLockContentionHandler.stop(lockId);
+                    } catch (Exception e) {
+                        throw new RuntimeException("Unable to stop listening for file lock requests for " + displayName, e);
                     }
-                } finally {
-                    lockFileAccess.close();
                 }
-            } catch (IOException e) {
-                LOGGER.warn("Error releasing lock on {}: {}", displayName, e);
-            } finally {
-                lock = null;
-                lockFileAccess = null;
-            }
+            });
+            stoppable.add(new Stoppable() {
+                public void stop() {
+                    if (lockFileAccess == null) {
+                        return;
+                    }
+                    try {
+                        LOGGER.debug("Releasing lock on {}.", displayName);
+                        try {
+                            if (lock != null && !lock.isShared()) {
+                                // Discard information region
+                                java.nio.channels.FileLock info;
+                                try {
+                                    info = lockInformationRegion(LockMode.Exclusive, System.currentTimeMillis() + shortTimeoutMs);
+                                } catch (InterruptedException e) {
+                                    throw throwAsUncheckedException(e);
+                                }
+                                if (info != null) {
+                                    try {
+                                        lockFileAccess.clearLockInfo();
+                                    } finally {
+                                        info.release();
+                                    }
+                                }
+                            }
+                        } finally {
+                            lockFileAccess.close();
+                        }
+                    } catch (Exception e) {
+                        throw new RuntimeException("Failed to release lock on " + displayName, e);
+                    }
+                }
+            });
+            stoppable.add(new Stoppable() {
+                public void stop() {
+                    lock = null;
+                    lockFileAccess = null;
+                    lockedFiles.remove(target);
+                }
+            });
+            stoppable.stop();
         }
 
         public LockMode getMode() {
             return mode;
         }
 
-        private java.nio.channels.FileLock lock(FileLockManager.LockMode lockMode) throws Throwable {
+        private LockState lock(FileLockManager.LockMode lockMode) throws Throwable {
             LOGGER.debug("Waiting to acquire {} lock on {}.", lockMode.toString().toLowerCase(), displayName);
-            long timeout = System.currentTimeMillis() + lockTimeoutMs;
+            long waitUntil = System.currentTimeMillis() + lockTimeoutMs;
 
             // Lock the state region, with the requested mode
-            java.nio.channels.FileLock stateRegionLock = lockStateRegion(lockMode, timeout);
+            java.nio.channels.FileLock stateRegionLock = lockStateRegion(lockMode, waitUntil);
             if (stateRegionLock == null) {
-                // Can't acquire lock, get details of owner to include in the error message
-                String ownerPid = "unknown";
-                String ownerOperation = "unknown";
-                java.nio.channels.FileLock informationRegionLock = lockInformationRegion(LockMode.Shared, timeout);
-                if (informationRegionLock == null) {
-                    LOGGER.debug("Could not lock information region for {}. Ignoring.", displayName);
-                } else {
-                    try {
-                        if (lockFileAccess.length() <= INFORMATION_REGION_POS) {
-                            LOGGER.debug("Lock file for {} is too short to contain information region. Ignoring.", displayName);
-                        } else {
-                            lockFileAccess.seek(INFORMATION_REGION_POS);
-                            if (lockFileAccess.readByte() != INFORMATION_REGION_PROTOCOL) {
-                                throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file '%s' for %s.", lockFile, displayName));
-                            }
-                            ownerPid = lockFileAccess.readUTF();
-                            ownerOperation = lockFileAccess.readUTF();
-                        }
-                    } finally {
-                        informationRegionLock.release();
-                    }
-                }
-
+                LockInfo lockInfo = readInformationRegion(System.currentTimeMillis() + shortTimeoutMs);
                 throw new LockTimeoutException(String.format("Timeout waiting to lock %s. It is currently in use by another Gradle instance.%nOwner PID: %s%nOur PID: %s%nOwner Operation: %s%nOur operation: %s%nLock file: %s",
-                        displayName, ownerPid, metaDataProvider.getProcessIdentifier(), ownerOperation, operationDisplayName, lockFile));
+                        displayName, lockInfo.pid, metaDataProvider.getProcessIdentifier(), lockInfo.operation, operationDisplayName, lockFile));
             }
 
             try {
-                if (lockFileAccess.length() > 0) {
-                    lockFileAccess.seek(STATE_REGION_POS);
-                    if (lockFileAccess.readByte() != STATE_REGION_PROTOCOL) {
-                        throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file '%s' for %s.", lockFile, displayName));
-                    }
-                }
-
+                LockState lockState;
                 if (!stateRegionLock.isShared()) {
                     // We have an exclusive lock (whether we asked for it or not).
+
                     // Update the state region
-                    if (lockFileAccess.length() < STATE_REGION_SIZE) {
-                        // File did not exist before locking
-                        lockFileAccess.seek(STATE_REGION_POS);
-                        lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
-                        lockFileAccess.writeBoolean(false);
-                    }
+                    lockState = lockFileAccess.ensureLockState();
+
                     // Acquire an exclusive lock on the information region and write our details there
-                    java.nio.channels.FileLock informationRegionLock = lockInformationRegion(LockMode.Exclusive, timeout);
+                    java.nio.channels.FileLock informationRegionLock = lockInformationRegion(LockMode.Exclusive, System.currentTimeMillis() + shortTimeoutMs);
                     if (informationRegionLock == null) {
-                        throw new IllegalStateException(String.format("Timeout waiting to lock the information region for lock %s", displayName));
+                        throw new IllegalStateException(String.format("Unable to lock the information region for %s", displayName));
                     }
                     // check that the length of the reserved region is enough for storing our content
                     try {
-                        lockFileAccess.seek(INFORMATION_REGION_POS);
-                        lockFileAccess.writeByte(INFORMATION_REGION_PROTOCOL);
-                        lockFileAccess.writeUTF(trimIfNecessary(metaDataProvider.getProcessIdentifier()));
-                        lockFileAccess.writeUTF(trimIfNecessary(operationDisplayName));
-                        lockFileAccess.setLength(lockFileAccess.getFilePointer());
+                        lockFileAccess.writeLockInfo(port, lockId, metaDataProvider.getProcessIdentifier(), operationDisplayName);
                     } finally {
                         informationRegionLock.release();
                     }
+                } else {
+                    // Just read the state region
+                    lockState = lockFileAccess.readLockState();
                 }
+                LOGGER.debug("Lock acquired.");
+                lock = stateRegionLock;
+                return lockState;
             } catch (Throwable t) {
                 stateRegionLock.release();
                 throw t;
             }
-
-            LOGGER.debug("Lock acquired.");
-            return stateRegionLock;
         }
 
-        private String trimIfNecessary(String inputString) {
-            if(inputString.length() > INFORMATION_REGION_DESCR_CHUNK_LIMIT){
-                return inputString.substring(0, INFORMATION_REGION_DESCR_CHUNK_LIMIT);
-            }else{
-                return inputString;
+        private LockInfo readInformationRegion(long waitUntil) throws IOException, InterruptedException {
+            // Can't acquire lock, get details of owner to include in the error message
+            LockInfo out = new LockInfo();
+            java.nio.channels.FileLock informationRegionLock = lockInformationRegion(LockMode.Shared, waitUntil);
+            if (informationRegionLock == null) {
+                LOGGER.debug("Could not lock information region for {}. Ignoring.", displayName);
+            } else {
+                try {
+                    out = lockFileAccess.readLockInfo();
+                } finally {
+                    informationRegionLock.release();
+                }
             }
+            return out;
         }
 
-        private java.nio.channels.FileLock lockStateRegion(LockMode lockMode, long timeout) throws IOException, InterruptedException {
-            return lockRegion(lockMode, timeout, STATE_REGION_POS, STATE_REGION_SIZE);
-        }
-
-        private java.nio.channels.FileLock lockInformationRegion(LockMode lockMode, long timeout) throws IOException, InterruptedException {
-            return lockRegion(lockMode, timeout, INFORMATION_REGION_POS, INFORMATION_REGION_SIZE - INFORMATION_REGION_POS);
+        private java.nio.channels.FileLock lockStateRegion(LockMode lockMode, final long waitUntil) throws IOException, InterruptedException {
+            do {
+                java.nio.channels.FileLock fileLock = lockFileAccess.tryLockState(lockMode == LockMode.Shared);
+                if (fileLock != null) {
+                    return fileLock;
+                }
+                if (port != -1) { //we don't like the assumption about the port very much
+                    LockInfo lockInfo = readInformationRegion(System.currentTimeMillis()); //no need for timeout here, as we're already looping with timeout
+                    if (lockInfo.port != -1) {
+                        LOGGER.debug("The file lock is held by a different Gradle process (pid: {}, operation: {}). Will attempt to ping owner at port {}", lockInfo.pid, lockInfo.operation, lockInfo.port);
+                        fileLockContentionHandler.pingOwner(lockInfo.port, lockInfo.lockId, displayName);
+                    } else {
+                        LOGGER.debug("The file lock is held by a different Gradle process. I was unable to read on which port the owner listens for lock access requests.");
+                    }
+                }
+                //TODO SF it would really nice to print some message to the user after say 2 seconds of waiting
+                //saying what gradle is doing, and why we're waiting.
+                Thread.sleep(200L);
+            } while (System.currentTimeMillis() < waitUntil);
+            return null;
         }
 
-        private java.nio.channels.FileLock lockRegion(FileLockManager.LockMode lockMode, long timeout, long start, long size) throws IOException, InterruptedException {
+        private java.nio.channels.FileLock lockInformationRegion(LockMode lockMode, long waitUntil) throws IOException, InterruptedException {
             do {
-                java.nio.channels.FileLock fileLock = lockFileAccess.getChannel().tryLock(start, size, lockMode == LockMode.Shared);
+                java.nio.channels.FileLock fileLock = lockFileAccess.tryLockInfo(lockMode == LockMode.Shared);
                 if (fileLock != null) {
                     return fileLock;
                 }
                 Thread.sleep(200L);
-            } while (System.currentTimeMillis() < timeout);
+            }
+            while (System.currentTimeMillis() < waitUntil);
             return null;
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultMultiProcessSafePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultMultiProcessSafePersistentIndexedCache.java
new file mode 100644
index 0000000..483a2ea
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultMultiProcessSafePersistentIndexedCache.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal;
+
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.internal.Factory;
+
+public class DefaultMultiProcessSafePersistentIndexedCache<K, V> implements MultiProcessSafePersistentIndexedCache<K, V> {
+    private final FileAccess fileAccess;
+    private final Factory<BTreePersistentIndexedCache<K, V>> factory;
+    private BTreePersistentIndexedCache<K, V> cache;
+
+    public DefaultMultiProcessSafePersistentIndexedCache(Factory<BTreePersistentIndexedCache<K, V>> factory, FileAccess fileAccess) {
+        this.factory = factory;
+        this.fileAccess = fileAccess;
+    }
+
+    public V get(final K key) {
+        final PersistentIndexedCache<K, V> cache = getCache();
+        try {
+            return fileAccess.readFile(new Factory<V>() {
+                public V create() {
+                    return cache.get(key);
+                }
+            });
+        } catch (FileIntegrityViolationException e) {
+            return null;
+        }
+    }
+
+    public void put(final K key, final V value) {
+        final PersistentIndexedCache<K, V> cache = getCache();
+        // Use writeFile because the cache can internally recover from datafile
+        // corruption, so we don't care at this level if it's corrupt
+        fileAccess.writeFile(new Runnable() {
+            public void run() {
+                cache.put(key, value);
+            }
+        });
+    }
+
+    public void remove(final K key) {
+        final PersistentIndexedCache<K, V> cache = getCache();
+        // Use writeFile because the cache can internally recover from datafile
+        // corruption, so we don't care at this level if it's corrupt
+        fileAccess.writeFile(new Runnable() {
+            public void run() {
+                cache.remove(key);
+            }
+        });
+    }
+
+    public void onStartWork(String operationDisplayName, FileLock.State currentCacheState) {
+    }
+
+    public void onEndWork(FileLock.State currentCacheState) {
+    }
+
+    public void close() {
+        if (cache != null) {
+            try {
+                fileAccess.writeFile(new Runnable() {
+                    public void run() {
+                        cache.close();
+                    }
+                });
+            } finally {
+                cache = null;
+            }
+        }
+    }
+
+    private PersistentIndexedCache<K, V> getCache() {
+        if (cache == null) {
+            // Use writeFile because the cache can internally recover from datafile
+            // corruption, so we don't care at this level if it's corrupt
+            fileAccess.writeFile(new Runnable() {
+                public void run() {
+                    cache = factory.create();
+                }
+            });
+        }
+        return cache;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
index d814342..4ba8658 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
@@ -17,22 +17,18 @@ package org.gradle.cache.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.cache.CacheOpenException;
 import org.gradle.cache.CacheValidator;
 import org.gradle.cache.PersistentCache;
+import org.gradle.cache.internal.filelock.LockOptions;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.GUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.Map;
 import java.util.Properties;
 
-import static org.gradle.cache.internal.FileLockManager.LockMode;
-
 public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryStore implements ReferencablePersistentCache {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPersistentDirectoryCache.class);
     private final File propertiesFile;
@@ -42,11 +38,8 @@ public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryS
     private final CacheValidator validator;
     private boolean didRebuild;
 
-    public DefaultPersistentDirectoryCache(File dir, String displayName, CacheUsage cacheUsage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initAction, FileLockManager lockManager) {
-        super(dir, displayName, lockMode, lockManager);
-        if (lockMode == LockMode.None) {
-            throw new UnsupportedOperationException("Locking mode None is not supported.");
-        }
+    public DefaultPersistentDirectoryCache(File dir, String displayName, CacheUsage cacheUsage, CacheValidator validator, Map<String, ?> properties, LockOptions lockOptions, Action<? super PersistentCache> initAction, FileLockManager lockManager) {
+        super(dir, displayName, lockOptions, lockManager);
         this.validator = validator;
         this.cacheUsage = cacheUsage;
         this.initAction = initAction;
@@ -60,77 +53,55 @@ public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryS
         return propertiesFile;
     }
 
-    protected void init() throws IOException {
-        boolean valid = determineIfCacheIsValid(getLock());
-        int tries = 3;
-        while (!valid) {
-            if (--tries < 0) {
-                throw new CacheOpenException(String.format("Failed to init valid cache for %s", this));
-            }
-            withExclusiveLock(new Action<FileLock>() {
-                public void execute(final FileLock fileLock) {
-                    boolean exclusiveLockValid;
-                    try {
-                        exclusiveLockValid = determineIfCacheIsValid(fileLock);
-                    } catch (IOException e) {
-                        throw new UncheckedIOException(e);
-                    }
-
-                    if (!exclusiveLockValid) {
-                        fileLock.writeFile(new Runnable() {
-                            public void run() {
-                                buildCacheDir(initAction, fileLock);
-                            }
-                        });
-                    }
-                }
-            });
-            valid = determineIfCacheIsValid(getLock());
-        }
+    @Override
+    protected CacheInitializationAction getInitAction() {
+        return new Initializer();
     }
 
-    private void buildCacheDir(Action<? super PersistentCache> initAction, FileLock fileLock) {
-        for (File file : getBaseDir().listFiles()) {
-            if (fileLock.isLockFile(file) || file.equals(propertiesFile)) {
-                continue;
-            }
-            GFileUtils.forceDelete(file);
-        }
-        if (initAction != null) {
-            initAction.execute(this);
-        }
-        GUtil.saveProperties(properties, propertiesFile);
-        didRebuild = true;
+    public Properties getProperties() {
+        return properties;
     }
 
-    // made protected for DefaultPersistentDirectoryCacheTest.exceptionThrownIfValidCacheCannotBeInitd
-    protected boolean determineIfCacheIsValid(FileLock lock) throws IOException {
-        if (!didRebuild) {
-            if (cacheUsage != CacheUsage.ON) {
-                LOGGER.debug("Invalidating {} as cache usage is set to rebuild.", this);
-                return false;
+    private class Initializer implements CacheInitializationAction {
+        public boolean requiresInitialization(FileLock lock) {
+            if (!didRebuild) {
+                if (cacheUsage == CacheUsage.REBUILD) {
+                    LOGGER.debug("Invalidating {} as cache usage is set to rebuild.", this);
+                    return true;
+                }
+                if (validator!=null && !validator.isValid()) {
+                    LOGGER.debug("Invalidating {} as cache validator return false.", this);
+                    return true;
+                }
             }
-            if (validator!=null && !validator.isValid()) {
-                LOGGER.debug("Invalidating {} as cache validator return false.", this);
-                return false;
+
+            if (!lock.getUnlockedCleanly()) {
+                LOGGER.debug("Invalidating {} as it was not closed cleanly.", this);
+                return true;
             }
-        }
 
-        if (!lock.getUnlockedCleanly()) {
-            LOGGER.debug("Invalidating {} as it was not closed cleanly.", this);
+            Properties currentProperties = GUtil.loadProperties(propertiesFile);
+            for (Map.Entry<?, ?> entry : properties.entrySet()) {
+                if (!entry.getValue().toString().equals(currentProperties.getProperty(entry.getKey().toString()))) {
+                    LOGGER.debug("Invalidating {} as cache property {} has changed value.", this, entry.getKey());
+                    return true;
+                }
+            }
             return false;
         }
-        Properties currentProperties = GUtil.loadProperties(propertiesFile);
-        for (Map.Entry<?, ?> entry : properties.entrySet()) {
-            if (!entry.getValue().toString().equals(currentProperties.getProperty(entry.getKey().toString()))) {
-                LOGGER.debug("Invalidating {} as cache property {} has changed value.", this, entry.getKey());
-                return false;
+
+        public void initialize(FileLock fileLock) {
+            for (File file : getBaseDir().listFiles()) {
+                if (fileLock.isLockFile(file) || file.equals(propertiesFile)) {
+                    continue;
+                }
+                GFileUtils.forceDelete(file);
+            }
+            if (initAction != null) {
+                initAction.execute(DefaultPersistentDirectoryCache.this);
             }
+            GUtil.saveProperties(properties, propertiesFile);
+            didRebuild = true;
         }
-        return true;
-    }
-
-    public Properties getProperties() {
-        return properties;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
index 74d7736..58bf79a 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
@@ -15,25 +15,26 @@
  */
 package org.gradle.cache.internal;
 
-import org.gradle.api.Action;
-import org.gradle.cache.*;
+import org.gradle.cache.CacheOpenException;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentIndexedCacheParameters;
+import org.gradle.cache.internal.filelock.LockOptions;
 import org.gradle.internal.Factory;
 import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
-import java.io.IOException;
 
 public class DefaultPersistentDirectoryStore implements ReferencablePersistentCache {
     private final File dir;
-    private final FileLockManager.LockMode lockMode;
+    private final LockOptions lockOptions;
     private final FileLockManager lockManager;
     private final String displayName;
-    private DefaultCacheAccess cacheAccess;
+    private CacheCoordinator cacheAccess;
 
-    public DefaultPersistentDirectoryStore(File dir, String displayName, FileLockManager.LockMode lockMode, FileLockManager fileLockManager) {
+    public DefaultPersistentDirectoryStore(File dir, String displayName, LockOptions lockOptions, FileLockManager fileLockManager) {
         this.dir = dir;
-        this.lockMode = lockMode;
+        this.lockOptions = lockOptions;
         this.lockManager = fileLockManager;
         this.displayName = displayName != null ? String.format("%s (%s)", displayName, dir) : String.format("cache directory %s (%s)", dir.getName(), dir);
     }
@@ -42,15 +43,7 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
         GFileUtils.mkdirs(dir);
         cacheAccess = createCacheAccess();
         try {
-            cacheAccess.open(lockMode);
-            try {
-                init();
-            } catch (Throwable throwable) {
-                if (cacheAccess != null) {
-                    cacheAccess.close();
-                }
-                throw throwable;
-            }
+            cacheAccess.open(lockOptions);
         } catch (Throwable e) {
             throw new CacheOpenException(String.format("Could not open %s.", this), e);
         }
@@ -58,36 +51,24 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
         return this;
     }
 
-    private DefaultCacheAccess createCacheAccess() {
-        return new DefaultCacheAccess(displayName, getLockTarget(), lockManager);
-    }
-
-    protected void withExclusiveLock(Action<FileLock> action) {
-        if (cacheAccess != null && (cacheAccess.getFileLock().getMode() == FileLockManager.LockMode.Exclusive)) {
-            action.execute(getLock());
-        } else {
-            boolean reopen = cacheAccess != null;
-            close();
-            DefaultCacheAccess exclusiveAccess = createCacheAccess();
-            exclusiveAccess.open(FileLockManager.LockMode.Exclusive);
-            try {
-                action.execute(exclusiveAccess.getFileLock());
-            } finally {
-                exclusiveAccess.close();
-            }
-            if (reopen) {
-                cacheAccess = createCacheAccess();
-                cacheAccess.open(lockMode);
-            }
-        }
+    private CacheCoordinator createCacheAccess() {
+        return new DefaultCacheAccess(displayName, getLockTarget(), dir, lockManager, getInitAction());
     }
 
-
     protected File getLockTarget() {
         return dir;
     }
 
-    protected void init() throws IOException {
+    protected CacheInitializationAction getInitAction() {
+        return new CacheInitializationAction() {
+            public boolean requiresInitialization(FileLock fileLock) {
+                return false;
+            }
+
+            public void initialize(FileLock fileLock) {
+                throw new UnsupportedOperationException();
+            }
+        };
     }
 
     public void close() {
@@ -98,11 +79,6 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
                 cacheAccess = null;
             }
         }
-
-    }
-
-    public FileLock getLock() {
-        return cacheAccess.getFileLock();
     }
 
     public File getBaseDir() {
@@ -114,16 +90,12 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
         return displayName;
     }
 
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
-        return cacheAccess.newCache(cacheFile, keyType, valueType);
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
-        return cacheAccess.newCache(cacheFile, keyType, valueSerializer);
+    public <K, V> PersistentIndexedCache<K, V> createCache(PersistentIndexedCacheParameters<K, V> parameters) {
+        return cacheAccess.newCache(parameters);
     }
 
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
-        return cacheAccess.newCache(cacheFile, keySerializer, valueSerializer);
+    public <K, V> PersistentIndexedCache<K, V> createCache(String name, Class<K> keyType, Serializer<V> valueSerializer) {
+        return cacheAccess.newCache(new PersistentIndexedCacheParameters<K, V>(name, keyType, valueSerializer));
     }
 
     public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
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
deleted file mode 100644
index 72a523f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
+++ /dev/null
@@ -1,114 +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.cache.internal;
-
-import org.gradle.cache.CacheOpenException;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.internal.Factory;
-import org.gradle.messaging.serialize.Serializer;
-
-import java.io.File;
-
-public class DelegateOnDemandPersistentDirectoryCache implements ReferencablePersistentCache {
-    private DefaultPersistentDirectoryCache delegateCache;
-    private boolean isOpen;
-
-    public DelegateOnDemandPersistentDirectoryCache(DefaultPersistentDirectoryCache cache) {
-        this.delegateCache = cache;
-    }
-
-    public DelegateOnDemandPersistentDirectoryCache open() {
-        this.isOpen = true;
-        return this;
-    }
-
-    public void close() {
-        this.isOpen = false;
-        delegateCache.close();
-    }
-
-    public FileLock getLock() {
-        return delegateCache.getLock();
-    }
-
-    public <T> T useCache(final String operationDisplayName, final Factory<? extends T> action) {
-        return runWithOpenedCache(new Factory<T>() {
-            public T create() {
-                return delegateCache.useCache(operationDisplayName, action);
-            }
-        });
-    }
-
-    public void useCache(final String operationDisplayName, final Runnable action) {
-        runWithOpenedCache(new Factory<Void>() {
-            public Void create() {
-                delegateCache.useCache(operationDisplayName, action);
-                return null;
-            }
-        });
-    }
-
-    public <T> T longRunningOperation(final String operationDisplayName, final Factory<? extends T> action) {
-        return runWithOpenedCache(new Factory<T>() {
-            public T create() {
-                return delegateCache.longRunningOperation(operationDisplayName, action);
-            }
-        });
-    }
-
-    public void longRunningOperation(final String operationDisplayName, final Runnable action) {
-        runWithOpenedCache(new Factory<Void>() {
-            public Void create() {
-                delegateCache.longRunningOperation(operationDisplayName, action);
-                return null;
-            }
-        });
-    }
-
-    private <T> T runWithOpenedCache(Factory<T> factory) {
-        if (isOpen) {
-            delegateCache.open();
-            try {
-                return factory.create();
-            } finally {
-                delegateCache.close();
-            }
-        } else {
-            throw new CacheOpenException("Cannot run operation on cache that has not been opened.");
-        }
-    }
-
-    public File getBaseDir() {
-        return delegateCache.getBaseDir();
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
-        throw new UnsupportedOperationException();
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
-        throw new UnsupportedOperationException();
-    }
-
-    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
-        throw new UnsupportedOperationException();
-    }
-
-    public String toString(){
-        return String.format("On Demand Cache for %s", delegateCache.toString());
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
index 892941b..cbfb4e2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
@@ -54,7 +54,7 @@ public interface FileAccess {
     void updateFile(Runnable action) throws LockTimeoutException, FileIntegrityViolationException, InsufficientLockModeException;
 
     /**
-     * Runs the given action under an exclusive lock on the target file, without checking it's integrity. If the given action fails, the lock is marked as uncleanly unlocked.
+     * Runs the given action under an exclusive lock on the target file, without checking its integrity. If the given action fails, the lock is marked as uncleanly unlocked.
      *
      * <p>This method should be used when it is of no consequence if the target was not previously unlocked, e.g. the content is being replaced.
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
index 04896e6..3936323 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
@@ -23,7 +23,7 @@ public interface FileLock extends Closeable, FileAccess {
      * Returns true if the most recent mutation method ({@link #updateFile(Runnable)} or {@link #writeFile(Runnable)} attempted by any process succeeded
      * (ie a process did not crash while updating the target file).
      *
-     * Returns false if no mutation method has been called for the target file.
+     * Returns false if no mutation method has ever been called for the target file.
      */
     boolean getUnlockedCleanly();
 
@@ -38,7 +38,19 @@ public interface FileLock extends Closeable, FileAccess {
     void close();
 
     /**
+     * Returns some memento of the current state of this target file.
+     */
+    State getState();
+
+    /**
      * The actual mode of the lock. May be different to what was requested.
      */
     FileLockManager.LockMode getMode();
+
+    /**
+     * An immutable snapshot of the state of a lock.
+     */
+    interface State {
+        boolean hasBeenUpdatedSince(State state);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockCommunicator.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockCommunicator.java
new file mode 100644
index 0000000..2544d7a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockCommunicator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal;
+
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
+
+import java.io.*;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+
+import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
+
+public class FileLockCommunicator {
+    private static final byte PROTOCOL_VERSION = 1;
+    private final DatagramSocket socket;
+    private final InetAddressFactory addressFactory;
+    private boolean stopped;
+
+    public FileLockCommunicator(InetAddressFactory addressFactory) {
+        this.addressFactory = addressFactory;
+        try {
+            socket = new DatagramSocket();
+        } catch (SocketException e) {
+            throw throwAsUncheckedException(e);
+        }
+    }
+
+    public void pingOwner(int ownerPort, long lockId, String displayName) {
+        try {
+            byte[] bytesToSend = encode(lockId);
+            // Ping the owner via all available local addresses
+            for (InetAddress address : addressFactory.findLocalAddresses()) {
+                socket.send(new DatagramPacket(bytesToSend, bytesToSend.length, address, ownerPort));
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Failed to ping owner of lock for %s (lock id: %s, port: %s)", displayName, lockId, ownerPort), e);
+        }
+    }
+
+    public long receive() throws GracefullyStoppedException {
+        try {
+            byte[] bytes = new byte[9];
+            DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
+            socket.receive(packet);
+            return decode(bytes);
+        } catch (IOException e) {
+            if (!stopped) {
+                throw new RuntimeException(e);
+            }
+            throw new GracefullyStoppedException();
+        }
+    }
+
+    public void stop() {
+        stopped = true;
+        socket.close();
+    }
+
+    private static byte[] encode(long lockId) throws IOException {
+        ByteArrayOutputStream packet = new ByteArrayOutputStream();
+        DataOutputStream dataOutput = new DataOutputStream(packet);
+        dataOutput.writeByte(PROTOCOL_VERSION);
+        dataOutput.writeLong(lockId);
+        dataOutput.flush();
+        return packet.toByteArray();
+    }
+
+    private static long decode(byte[] bytes) throws IOException {
+        DataInputStream dataInput = new DataInputStream(new ByteArrayInputStream(bytes));
+        byte version = dataInput.readByte();
+        if (version != PROTOCOL_VERSION) {
+            throw new IllegalArgumentException(String.format("Unexpected protocol version %s received in lock contention notification message", version));
+        }
+        return dataInput.readLong();
+    }
+
+    public int getPort() {
+        return socket.getLocalPort();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java
index fab7db9..7df1ac0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.cache.internal;
 
+import org.gradle.cache.internal.filelock.LockOptions;
+
 import java.io.File;
 
 public interface FileLockManager {
@@ -23,21 +25,31 @@ public interface FileLockManager {
      * released by calling {@link org.gradle.cache.internal.FileLock#close()}. This method blocks until the lock can be acquired.
      *
      * @param target The file to be locked.
-     * @param mode The lock mode.
+     * @param options The lock options.
      * @param targetDisplayName A display name for the target file. This is used in log and error messages.
      */
-    FileLock lock(File target, LockMode mode, String targetDisplayName) throws LockTimeoutException;
+    FileLock lock(File target, LockOptions options, String targetDisplayName) throws LockTimeoutException;
 
     /**
      * Creates a locks for the given file with the given mode. Acquires a lock with the given mode, which is held until the lock is
      * released by calling {@link org.gradle.cache.internal.FileLock#close()}. This method blocks until the lock can be acquired.
      *
      * @param target The file to be locked.
-     * @param mode The lock mode.
+     * @param options The lock options.
      * @param targetDisplayName A display name for the target file. This is used in log and error messages.
      * @param operationDisplayName A display name for the operation being performed on the target file. This is used in log and error messages.
      */
-    FileLock lock(File target, LockMode mode, String targetDisplayName, String operationDisplayName) throws LockTimeoutException;
+    FileLock lock(File target, LockOptions options, String targetDisplayName, String operationDisplayName) throws LockTimeoutException;
+
+    /**
+     * Enables other processes to request access to the provided lock. Provided action runs when the lock access request is received
+     * (it means that the lock is contended).
+     *
+     * @param fileLock the lock
+     * @param whenContended will be called asynchronously by the thread that listens for cache access requests, when such request is received.
+     * Note: currently, implementations are permitted to invoke the action <em>after</em> the lock as been closed.
+     */
+    void allowContention(FileLock fileLock, Runnable whenContended);
 
     enum LockMode {
         /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/GracefullyStoppedException.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/GracefullyStoppedException.java
new file mode 100644
index 0000000..789e001
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/GracefullyStoppedException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal;
+
+public class GracefullyStoppedException extends RuntimeException {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
index 8006d0e..f1424ca 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,88 +15,14 @@
  */
 package org.gradle.cache.internal;
 
-import org.gradle.internal.Factory;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
 
 import java.io.Closeable;
 
-public class MultiProcessSafePersistentIndexedCache<K, V> implements PersistentIndexedCache<K, V>, UnitOfWorkParticipant, Closeable {
-    private final FileAccess fileAccess;
-    private final Factory<BTreePersistentIndexedCache<K, V>> factory;
-    private BTreePersistentIndexedCache<K, V> cache;
-
-    public MultiProcessSafePersistentIndexedCache(Factory<BTreePersistentIndexedCache<K, V>> factory, FileAccess fileAccess) {
-        this.factory = factory;
-        this.fileAccess = fileAccess;
-    }
-
-    public V get(final K key) {
-        final PersistentIndexedCache<K, V> cache = getCache();
-        try {
-            return fileAccess.readFile(new Factory<V>() {
-                public V create() {
-                    return cache.get(key);
-                }
-            });
-        } catch (FileIntegrityViolationException e) {
-            return null;
-        }
-    }
-
-    public void put(final K key, final V value) {
-        final PersistentIndexedCache<K, V> cache = getCache();
-        // Use writeFile because the cache can internally recover from datafile
-        // corruption, so we don't care at this level if it's corrupt
-        fileAccess.writeFile(new Runnable() {
-            public void run() {
-                cache.put(key, value);
-            }
-        });
-    }
-
-    public void remove(final K key) {
-        final PersistentIndexedCache<K, V> cache = getCache();
-        // Use writeFile because the cache can internally recover from datafile
-        // corruption, so we don't care at this level if it's corrupt
-        fileAccess.writeFile(new Runnable() {
-            public void run() {
-                cache.remove(key);
-            }
-        });
-    }
-
-    public void onStartWork(String operationDisplayName) {
-    }
-
-    public void onEndWork() {
-        close();
-    }
-
-    public void close() {
-        if (cache != null) {
-            try {
-                fileAccess.writeFile(new Runnable() {
-                    public void run() {
-                        cache.close();
-                    }
-                });
-            } finally {
-                cache = null;
-            }
-        }
-    }
-
-    private PersistentIndexedCache<K, V> getCache() {
-        if (cache == null) {
-            // Use writeFile because the cache can internally recover from datafile
-            // corruption, so we don't care at this level if it's corrupt
-            fileAccess.writeFile(new Runnable() {
-                public void run() {
-                    cache = factory.create();
-                }
-            });
-        }
-        return cache;
-    }
-}
+public interface MultiProcessSafePersistentIndexedCache<K, V> extends
+        PersistentIndexedCache<K, V>, UnitOfWorkParticipant, Closeable {
+    /**
+     * Note: this method is called before {@link UnitOfWorkParticipant#onEndWork(org.gradle.cache.internal.FileLock.State)}.
+     */
+    void close(); //so that we don't have to handle IOException (do we need this?)
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
index 1e70c09..2a8f139 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
@@ -19,6 +19,8 @@ import org.gradle.internal.Factory;
 
 import java.io.File;
 
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
+
 public class OnDemandFileAccess extends AbstractFileAccess {
     private final String displayName;
     private final FileLockManager manager;
@@ -31,7 +33,7 @@ public class OnDemandFileAccess extends AbstractFileAccess {
     }
 
     public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException {
-        FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Shared, displayName);
+        FileLock lock = manager.lock(targetFile, mode(FileLockManager.LockMode.Shared), displayName);
         try {
             return lock.readFile(action);
         } finally {
@@ -40,7 +42,7 @@ public class OnDemandFileAccess extends AbstractFileAccess {
     }
 
     public void updateFile(Runnable action) throws LockTimeoutException, FileIntegrityViolationException {
-        FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Exclusive, displayName);
+        FileLock lock = manager.lock(targetFile, mode(FileLockManager.LockMode.Exclusive), displayName);
         try {
             lock.updateFile(action);
         } finally {
@@ -49,7 +51,7 @@ public class OnDemandFileAccess extends AbstractFileAccess {
     }
 
     public void writeFile(Runnable action) throws LockTimeoutException {
-        FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Exclusive, displayName);
+        FileLock lock = manager.lock(targetFile, mode(FileLockManager.LockMode.Exclusive), displayName);
         try {
             lock.writeFile(action);
         } finally {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java
index 7acb3f6..b3a5c33 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java
@@ -18,9 +18,11 @@ package org.gradle.cache.internal;
 
 import org.gradle.cache.PersistentCache;
 
-public interface ReferencablePersistentCache extends PersistentCache{
+import java.io.Closeable;
+
+public interface ReferencablePersistentCache extends PersistentCache, Closeable {
 
     void close();
-    FileLock getLock();
+
     ReferencablePersistentCache open();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
index fcd415d..370b61e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
@@ -19,6 +19,8 @@ package org.gradle.cache.internal;
 import org.gradle.api.GradleException;
 import org.gradle.internal.Factory;
 import org.gradle.cache.PersistentStateCache;
+import org.gradle.messaging.serialize.InputStreamBackedDecoder;
+import org.gradle.messaging.serialize.OutputStreamBackedEncoder;
 import org.gradle.messaging.serialize.Serializer;
 
 import java.io.*;
@@ -62,11 +64,11 @@ public class SimpleStateCache<T> implements PersistentStateCache<T> {
 
     private void serialize(T newValue) {
         try {
-            OutputStream outStr = new BufferedOutputStream(new FileOutputStream(cacheFile));
+            OutputStreamBackedEncoder encoder = new OutputStreamBackedEncoder(new BufferedOutputStream(new FileOutputStream(cacheFile)));
             try {
-                serializer.write(outStr, newValue);
+                serializer.write(encoder, newValue);
             } finally {
-                outStr.close();
+                encoder.close();
             }
         } catch (Exception e) {
             throw new GradleException(String.format("Could not write cache value to '%s'.", cacheFile), e);
@@ -78,11 +80,11 @@ public class SimpleStateCache<T> implements PersistentStateCache<T> {
             return null;
         }
         try {
-            InputStream inStr = new BufferedInputStream(new FileInputStream(cacheFile));
+            InputStreamBackedDecoder decoder = new InputStreamBackedDecoder(new BufferedInputStream(new FileInputStream(cacheFile)));
             try {
-                return serializer.read(inStr);
+                return serializer.read(decoder);
             } finally {
-                inStr.close();
+                decoder.close();
             }
         } catch (Exception e) {
             throw new GradleException(String.format("Could not read cache value from '%s'.", cacheFile), e);
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java
index 7b155c9..7e63a3f 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
@@ -21,11 +21,14 @@ package org.gradle.cache.internal;
 public interface UnitOfWorkParticipant {
     /**
      * Called just after the cache is locked. Called before any work has been performed.
+     *
+     * @param operationDisplayName operation
+     * @param currentCacheState the current cache state.
      */
-    void onStartWork(String operationDisplayName);
+    void onStartWork(String operationDisplayName, FileLock.State currentCacheState);
 
     /**
      * Called just before the cache is to be unlocked. Called after all work has been completed.
      */
-    void onEndWork();
+    void onEndWork(FileLock.State currentCacheState);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
index 061f283..aef8f06 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
@@ -18,6 +18,8 @@ package org.gradle.cache.internal.btree;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.messaging.serialize.Serializer;
+import org.gradle.messaging.serialize.kryo.KryoBackedDecoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedEncoder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -131,7 +133,9 @@ public class BTreePersistentIndexedCache<K, V> implements PersistentIndexedCache
     public void put(K key, V value) {
         try {
             MessageDigestStream digestStream = new MessageDigestStream();
-            keySerializer.write(digestStream, key);
+            KryoBackedEncoder encoder = new KryoBackedEncoder(digestStream);
+            keySerializer.write(encoder, key);
+            encoder.flush();
             long hashCode = digestStream.getChecksum();
             Lookup lookup = header.getRoot().find(hashCode);
             boolean needNewBlock = true;
@@ -447,7 +451,9 @@ public class BTreePersistentIndexedCache<K, V> implements PersistentIndexedCache
 
         public Lookup find(K key) throws Exception {
             MessageDigestStream digestStream = new MessageDigestStream();
-            keySerializer.write(digestStream, key);
+            KryoBackedEncoder encoder = new KryoBackedEncoder(digestStream);
+            keySerializer.write(encoder, key);
+            encoder.flush();
             long checksum = digestStream.getChecksum();
             return find(checksum);
         }
@@ -645,13 +651,15 @@ public class BTreePersistentIndexedCache<K, V> implements PersistentIndexedCache
 
         public void setValue(V value) throws Exception {
             ByteArrayOutputStream outStr = new ByteArrayOutputStream();
-            serializer.write(outStr, value);
+            KryoBackedEncoder encoder = new KryoBackedEncoder(outStr);
+            serializer.write(encoder, value);
+            encoder.flush();
             this.serialisedValue = outStr.toByteArray();
         }
 
         public V getValue() throws Exception {
             if (value == null) {
-                value = serializer.read(new ByteArrayInputStream(serialisedValue));
+                value = serializer.read(new KryoBackedDecoder(new ByteArrayInputStream(serialisedValue)));
             }
             return value;
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java
index 8781b35..e09d280 100755
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java
@@ -16,6 +16,8 @@
 package org.gradle.cache.internal.btree;
 
 import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.io.RandomAccessFileInputStream;
+import org.gradle.internal.io.RandomAccessFileOutputStream;
 
 import java.io.*;
 import java.util.zip.CRC32;
@@ -38,6 +40,7 @@ public class FileBackedBlockStore implements BlockStore {
     public void open(Runnable runnable, Factory factory) {
         this.factory = factory;
         try {
+            cacheFile.getParentFile().mkdirs();
             file = new RandomAccessFile(cacheFile, "rw");
             nextBlock = file.length();
             if (file.length() == 0) {
@@ -241,52 +244,6 @@ public class FileBackedBlockStore implements BlockStore {
         }
     }
 
-    private static class RandomAccessFileInputStream extends InputStream {
-        private final RandomAccessFile file;
-
-        private RandomAccessFileInputStream(RandomAccessFile file) {
-            this.file = file;
-        }
-
-        @Override
-        public int read(byte[] bytes) throws IOException {
-            return file.read(bytes);
-        }
-
-        @Override
-        public int read() throws IOException {
-            return file.read();
-        }
-
-        @Override
-        public int read(byte[] bytes, int offset, int length) throws IOException {
-            return file.read(bytes, offset, length);
-        }
-    }
-
-    private static class RandomAccessFileOutputStream extends OutputStream {
-        private final RandomAccessFile file;
-
-        private RandomAccessFileOutputStream(RandomAccessFile file) {
-            this.file = file;
-        }
-
-        @Override
-        public void write(int i) throws IOException {
-            file.write(i);
-        }
-
-        @Override
-        public void write(byte[] bytes) throws IOException {
-            file.write(bytes);
-        }
-
-        @Override
-        public void write(byte[] bytes, int offset, int length) throws IOException {
-            file.write(bytes, offset, length);
-        }
-    }
-
     private static class Crc32InputStream extends FilterInputStream {
         private final CRC32 checksum;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheAccessOperationsStack.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheAccessOperationsStack.java
new file mode 100644
index 0000000..947378e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheAccessOperationsStack.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.cacheops;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.Thread.currentThread;
+
+public class CacheAccessOperationsStack {
+    private final Map<Thread, CacheOperationStack> perThreadStacks = new HashMap<Thread, CacheOperationStack>();
+
+    public void pushCacheAction(String operationDisplayName) {
+        getStackForCurrentThread().pushCacheAction(operationDisplayName);
+    }
+
+    public void popCacheAction() {
+        CacheOperationStack stack = getStackForCurrentThread();
+        stack.popCacheAction();
+        if (stack.isEmpty()) {
+            perThreadStacks.remove(currentThread());
+        }
+    }
+
+    public boolean isInCacheAction() {
+        CacheOperationStack stack = perThreadStacks.get(currentThread());
+        return stack != null && stack.isInCacheAction();
+    }
+
+    public void pushLongRunningOperation(String operationDisplayName) {
+        getStackForCurrentThread().pushLongRunningOperation(operationDisplayName);
+    }
+
+    public void popLongRunningOperation() {
+        CacheOperationStack stack = getStackForCurrentThread();
+        stack.popLongRunningOperation();
+        if (stack.isEmpty()) {
+            perThreadStacks.remove(currentThread());
+        }
+    }
+
+    public String getDescription() {
+        return getStackForCurrentThread().getDescription();
+    }
+
+    public CacheOperationStack getStackForCurrentThread() {
+        CacheOperationStack stack = perThreadStacks.get(currentThread());
+        if (stack == null) {
+            stack = new CacheOperationStack();
+            perThreadStacks.put(currentThread(), stack);
+        }
+        return stack;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheOperation.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheOperation.java
new file mode 100644
index 0000000..4df99a9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheOperation.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.cacheops;
+
+class CacheOperation {
+    final String description;
+    final boolean longRunningOperation;
+
+    CacheOperation(String description, boolean longRunningOperation) {
+        this.description = description;
+        this.longRunningOperation = longRunningOperation;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheOperationStack.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheOperationStack.java
new file mode 100644
index 0000000..d0716f5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/cacheops/CacheOperationStack.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.cache.internal.cacheops;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class CacheOperationStack {
+    private final List<CacheOperation> operations = new ArrayList<CacheOperation>();
+
+    public String getDescription() {
+        checkNotEmpty();
+        return operations.get(0).description;
+    }
+
+    public CacheOperationStack pushLongRunningOperation(String description) {
+        operations.add(0, new CacheOperation(description, true));
+        return this;
+    }
+
+    public void popLongRunningOperation() {
+        pop(true);
+    }
+
+    public boolean isInCacheAction() {
+        return !operations.isEmpty() && !operations.get(0).longRunningOperation;
+    }
+
+    public boolean isInLongRunningOperation() {
+        return !operations.isEmpty() && !isInCacheAction();
+    }
+
+    public CacheOperationStack pushCacheAction(String description) {
+        operations.add(0, new CacheOperation(description, false));
+        return this;
+    }
+
+    public CacheOperation popCacheAction() {
+        return pop(false);
+    }
+
+    private CacheOperation pop(boolean longRunningOperation) {
+        checkNotEmpty();
+        CacheOperation operation = operations.remove(0);
+        if (operation.longRunningOperation == longRunningOperation) {
+            return operation;
+        }
+        throw new IllegalStateException(String.format("Unexpected operation %s at the top of the stack.", operation));
+    }
+
+    private void checkNotEmpty() {
+        if (operations.isEmpty()) {
+            throw new IllegalStateException("Operation stack is empty.");
+        }
+    }
+
+    public boolean isEmpty() {
+        return operations.isEmpty();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/DefaultLockStateSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/DefaultLockStateSerializer.java
new file mode 100644
index 0000000..e43b941
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/DefaultLockStateSerializer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.cache.internal.FileLock;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Random;
+
+public class DefaultLockStateSerializer implements LockStateSerializer {
+
+    public int getSize() {
+        return 16;
+    }
+
+    public byte getVersion() {
+        return 3;
+    }
+
+    public LockState createInitialState() {
+        long creationNumber = new Random().nextLong();
+        return new SequenceNumberLockState(creationNumber, -1, 0);
+    }
+
+    public void write(DataOutput dataOutput, LockState lockState) throws IOException {
+        SequenceNumberLockState state = (SequenceNumberLockState) lockState;
+        dataOutput.writeLong(state.creationNumber);
+        dataOutput.writeLong(state.sequenceNumber);
+    }
+
+    public LockState read(DataInput dataInput) throws IOException {
+        long creationNumber = dataInput.readLong();
+        long sequenceNumber = dataInput.readLong();
+        return new SequenceNumberLockState(creationNumber, sequenceNumber, sequenceNumber);
+    }
+
+    private static class SequenceNumberLockState implements LockState {
+        private final long creationNumber;
+        private final long originalSequenceNumber;
+        private final long sequenceNumber;
+
+        private SequenceNumberLockState(long creationNumber, long originalSequenceNumber, long sequenceNumber) {
+            this.creationNumber = creationNumber;
+            this.originalSequenceNumber = originalSequenceNumber;
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("[%s,%s,%s]", creationNumber, sequenceNumber, isDirty());
+        }
+
+        public LockState beforeUpdate() {
+            return new SequenceNumberLockState(creationNumber, originalSequenceNumber, 0);
+        }
+
+        public LockState completeUpdate() {
+            long newSequenceNumber;
+            if (originalSequenceNumber <= 0) {
+                newSequenceNumber = 1;
+            } else {
+                newSequenceNumber = originalSequenceNumber + 1;
+            }
+            return new SequenceNumberLockState(creationNumber, newSequenceNumber, newSequenceNumber);
+        }
+
+        public boolean isDirty() {
+            return sequenceNumber == 0 || sequenceNumber != originalSequenceNumber;
+        }
+
+        public boolean hasBeenUpdatedSince(FileLock.State state) {
+            SequenceNumberLockState other = (SequenceNumberLockState) state;
+            return sequenceNumber != other.sequenceNumber || creationNumber != other.creationNumber;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockFileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockFileAccess.java
new file mode 100644
index 0000000..35ea5bf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockFileAccess.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.api.Nullable;
+
+import java.io.*;
+import java.nio.channels.FileLock;
+
+public class LockFileAccess {
+
+    private final RandomAccessFile lockFileAccess;
+
+    private final LockStateAccess lockStateAccess;
+    private final LockInfoAccess lockInfoAccess;
+
+    public LockFileAccess(File lockFile, LockStateAccess lockStateAccess) throws FileNotFoundException {
+        this.lockFileAccess = new RandomAccessFile(lockFile, "rw");
+        this.lockStateAccess = lockStateAccess;
+        lockInfoAccess = new LockInfoAccess(this.lockStateAccess.getRegionEnd());
+    }
+
+    public void close() throws IOException {
+        lockFileAccess.close();
+    }
+
+    public void writeLockInfo(int port, long lockId, String pid, String operation) throws IOException {
+        LockInfo lockInfo = new LockInfo();
+        lockInfo.port = port;
+        lockInfo.lockId = lockId;
+        lockInfo.pid = pid;
+        lockInfo.operation = operation;
+        lockInfoAccess.writeLockInfo(lockFileAccess, lockInfo);
+    }
+
+    public LockInfo readLockInfo() throws IOException {
+        return lockInfoAccess.readLockInfo(lockFileAccess);
+    }
+
+    /**
+     * Reads the lock state from the lock file, possibly writing out a new lock file if not present or empty.
+     */
+    public LockState ensureLockState() throws IOException {
+        return lockStateAccess.ensureLockState(lockFileAccess);
+    }
+
+    public LockState markClean(LockState lockState) throws IOException {
+        LockState newState = lockState.completeUpdate();
+        lockStateAccess.writeState(lockFileAccess, newState);
+        return newState;
+    }
+
+    public LockState markDirty(LockState lockState) throws IOException {
+        LockState newState = lockState.beforeUpdate();
+        lockStateAccess.writeState(lockFileAccess, newState);
+        return newState;
+    }
+
+    public void clearLockInfo() throws IOException {
+        lockInfoAccess.clearLockInfo(lockFileAccess);
+    }
+
+    @Nullable
+    public FileLock tryLockInfo(boolean shared) throws IOException {
+        return lockInfoAccess.tryLock(lockFileAccess, shared);
+    }
+
+    @Nullable
+    public FileLock tryLockState(boolean shared) throws IOException {
+        return lockStateAccess.tryLock(lockFileAccess, shared);
+    }
+
+    /**
+     * Reads the lock state from the lock file.
+     */
+    public LockState readLockState() throws IOException {
+        return lockStateAccess.readState(lockFileAccess);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfo.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfo.java
new file mode 100644
index 0000000..bd515ec
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+public class LockInfo {
+    public int port = -1;
+    public long lockId;
+    public String pid = "unknown";
+    public String operation = "unknown";
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfoAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfoAccess.java
new file mode 100644
index 0000000..74d9338
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfoAccess.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.io.RandomAccessFileInputStream;
+import org.gradle.internal.io.RandomAccessFileOutputStream;
+
+import java.io.*;
+import java.nio.channels.FileLock;
+
+public class LockInfoAccess {
+    public static final int INFORMATION_REGION_SIZE = 2052;
+    private final LockInfoSerializer lockInfoSerializer = new LockInfoSerializer();
+    private final long infoRegionPos;
+
+    public LockInfoAccess(long infoRegionPos) {
+        this.infoRegionPos = infoRegionPos;
+    }
+
+    public LockInfo readLockInfo(RandomAccessFile lockFileAccess) throws IOException {
+        if (lockFileAccess.length() <= infoRegionPos) {
+            return new LockInfo();
+        } else {
+            lockFileAccess.seek(infoRegionPos);
+
+            DataInputStream inputStream = new DataInputStream(new BufferedInputStream(new RandomAccessFileInputStream(lockFileAccess)));
+            byte protocolVersion = inputStream.readByte();
+            if (protocolVersion != lockInfoSerializer.getVersion()) {
+                throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file. Expected %s, found %s.", lockInfoSerializer.getVersion(), protocolVersion));
+            }
+
+            return lockInfoSerializer.read(inputStream);
+        }
+    }
+
+    public void writeLockInfo(RandomAccessFile lockFileAccess, LockInfo lockInfo) throws IOException {
+        lockFileAccess.seek(infoRegionPos);
+
+        DataOutputStream outstr = new DataOutputStream(new BufferedOutputStream(new RandomAccessFileOutputStream(lockFileAccess)));
+        outstr.writeByte(lockInfoSerializer.getVersion());
+        lockInfoSerializer.write(outstr, lockInfo);
+        outstr.flush();
+
+        lockFileAccess.setLength(lockFileAccess.getFilePointer());
+    }
+
+    public void clearLockInfo(RandomAccessFile lockFileAccess) throws IOException {
+        lockFileAccess.setLength(Math.min(lockFileAccess.length(), infoRegionPos));
+    }
+
+    @Nullable
+    public FileLock tryLock(RandomAccessFile lockFileAccess, boolean shared) throws IOException {
+        return lockFileAccess.getChannel().tryLock(infoRegionPos, INFORMATION_REGION_SIZE - infoRegionPos, shared);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfoSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfoSerializer.java
new file mode 100644
index 0000000..1d69a86
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockInfoSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.filelock;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class LockInfoSerializer {
+    public static final int INFORMATION_REGION_DESCR_CHUNK_LIMIT = 340;
+
+    public byte getVersion() {
+        return 3;
+    }
+
+    public void write(DataOutput dataOutput, LockInfo lockInfo) throws IOException {
+        dataOutput.writeInt(lockInfo.port);
+        dataOutput.writeLong(lockInfo.lockId);
+        dataOutput.writeUTF(trimIfNecessary(lockInfo.pid));
+        dataOutput.writeUTF(trimIfNecessary(lockInfo.operation));
+    }
+
+    public LockInfo read(DataInput dataInput) throws IOException {
+        LockInfo out = new LockInfo();
+        out.port = dataInput.readInt();
+        out.lockId = dataInput.readLong();
+        out.pid = dataInput.readUTF();
+        out.operation = dataInput.readUTF();
+        return out;
+    }
+
+    private String trimIfNecessary(String inputString) {
+        if (inputString.length() > INFORMATION_REGION_DESCR_CHUNK_LIMIT) {
+            return inputString.substring(0, INFORMATION_REGION_DESCR_CHUNK_LIMIT);
+        } else {
+            return inputString;
+        }
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockOptions.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockOptions.java
new file mode 100644
index 0000000..3199028
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockOptions.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.cache.internal.FileLockManager;
+
+public interface LockOptions {
+
+    FileLockManager.LockMode getMode();
+
+    boolean isUseCrossVersionImplementation();
+
+    /**
+     * Creates a copy of these options with the given mode.
+     */
+    LockOptions withMode(FileLockManager.LockMode mode);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockOptionsBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockOptionsBuilder.java
new file mode 100644
index 0000000..8ea374c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockOptionsBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.cache.internal.FileLockManager;
+
+public class LockOptionsBuilder implements LockOptions {
+
+    private FileLockManager.LockMode mode;
+    private boolean crossVersion;
+
+    private LockOptionsBuilder(FileLockManager.LockMode mode, boolean crossVersion) {
+        this.mode = mode;
+        this.crossVersion = crossVersion;
+    }
+
+    public static LockOptionsBuilder mode(FileLockManager.LockMode lockMode) {
+        return new LockOptionsBuilder(lockMode, false);
+    }
+
+    public LockOptionsBuilder useCrossVersionImplementation() {
+        crossVersion = true;
+        return this;
+    }
+
+    public FileLockManager.LockMode getMode() {
+        return mode;
+    }
+
+    public boolean isUseCrossVersionImplementation() {
+        return crossVersion;
+    }
+
+    public LockOptions withMode(FileLockManager.LockMode mode) {
+        return new LockOptionsBuilder(mode, crossVersion);
+    }
+
+    @Override
+    public String toString() {
+        return mode + " (simple=" + crossVersion + ")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof LockOptionsBuilder)) {
+            return false;
+        }
+
+        LockOptionsBuilder that = (LockOptionsBuilder) o;
+
+        if (crossVersion != that.crossVersion) {
+            return false;
+        }
+        if (mode != that.mode) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mode.hashCode();
+        result = 31 * result + (crossVersion ? 1 : 0);
+        return result;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockState.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockState.java
new file mode 100644
index 0000000..a157777
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockState.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.cache.internal.FileLock;
+
+/**
+ * An immutable snapshot of the state of a lock.
+ */
+public interface LockState extends FileLock.State {
+    boolean isDirty();
+
+    /**
+     * Called after an update is complete, returns a new clean state based on this state.
+     */
+    LockState completeUpdate();
+
+    /**
+     * Called before an update is complete, returns a new dirty state based on this state.
+     */
+    LockState beforeUpdate();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockStateAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockStateAccess.java
new file mode 100644
index 0000000..19457c8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockStateAccess.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import java.io.*;
+import java.nio.channels.FileLock;
+
+public class LockStateAccess {
+    private final LockStateSerializer protocol;
+    private static final int REGION_START = 0;
+    private static final int STATE_CONTENT_START = 1;
+    private final int stateRegionSize;
+
+    public LockStateAccess(LockStateSerializer protocol) {
+        this.protocol = protocol;
+        stateRegionSize = STATE_CONTENT_START + protocol.getSize();
+    }
+
+    public LockState ensureLockState(RandomAccessFile lockFileAccess) throws IOException {
+        if (lockFileAccess.length() == 0) {
+            // File did not exist before locking, use some initial state
+            LockState state = protocol.createInitialState();
+            writeState(lockFileAccess, state);
+            return state;
+        } else {
+            return readState(lockFileAccess);
+        }
+    }
+
+    public void writeState(RandomAccessFile lockFileAccess, LockState lockState) throws IOException {
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+        DataOutputStream dataOutput = new DataOutputStream(outstr);
+        dataOutput.writeByte(protocol.getVersion());
+        protocol.write(dataOutput, lockState);
+        dataOutput.flush();
+
+        lockFileAccess.seek(REGION_START);
+        lockFileAccess.write(outstr.toByteArray());
+        assert lockFileAccess.getFilePointer() == stateRegionSize;
+    }
+
+    public LockState readState(RandomAccessFile lockFileAccess) throws IOException {
+        try {
+            byte[] buffer = new byte[stateRegionSize];
+            lockFileAccess.seek(REGION_START);
+
+            int readPos = 0;
+            while (readPos < buffer.length) {
+                int nread = lockFileAccess.read(buffer, readPos, buffer.length - readPos);
+                if (nread < 0) {
+                    break;
+                }
+                readPos += nread;
+            }
+
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, 0, readPos);
+            DataInputStream dataInput = new DataInputStream(inputStream);
+
+            byte protocolVersion = dataInput.readByte();
+            if (protocolVersion != protocol.getVersion()) {
+                throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file. Expected %s, found %s.", protocol.getVersion(), protocolVersion));
+            }
+            return protocol.read(dataInput);
+        } catch (EOFException e) {
+            return protocol.createInitialState();
+        }
+    }
+
+    public FileLock tryLock(RandomAccessFile lockFileAccess, boolean shared) throws IOException {
+        return lockFileAccess.getChannel().tryLock(REGION_START, stateRegionSize, shared);
+    }
+
+    public int getRegionEnd() {
+        return stateRegionSize;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockStateSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockStateSerializer.java
new file mode 100644
index 0000000..6bcc735
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/LockStateSerializer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public interface LockStateSerializer {
+
+    /**
+     * size (bytes) of the data of this protocol.
+     */
+    int getSize();
+
+    /**
+     * single byte that describes the version.
+     * an implementation protocol should increment the value when protocol changes in an incompatible way
+     */
+    byte getVersion();
+
+    /**
+     * Returns the initial state for a lock file with this format.
+     */
+    LockState createInitialState();
+
+    /**
+     * writes the state data
+     */
+    void write(DataOutput lockFileAccess, LockState lockState) throws IOException;
+
+    /**
+     * reads the state data
+     */
+    LockState read(DataInput lockFileAccess) throws IOException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/Version1LockStateSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/Version1LockStateSerializer.java
new file mode 100644
index 0000000..13e6d2d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/filelock/Version1LockStateSerializer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.cache.internal.filelock;
+
+import org.gradle.cache.internal.FileLock;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * An older, cross-version state info format.
+ */
+public class Version1LockStateSerializer implements LockStateSerializer {
+    public int getSize() {
+        return 1;
+    }
+
+    public byte getVersion() {
+        return 1;
+    }
+
+    public LockState createInitialState() {
+        return new DirtyFlagLockState(true);
+    }
+
+    public void write(DataOutput dataOutput, LockState lockState) throws IOException {
+        DirtyFlagLockState state = (DirtyFlagLockState) lockState;
+        dataOutput.writeBoolean(!state.dirty);
+    }
+
+    public LockState read(DataInput dataInput) throws IOException {
+        return new DirtyFlagLockState(!dataInput.readBoolean());
+    }
+
+    private static class DirtyFlagLockState implements LockState {
+        private final boolean dirty;
+
+        private DirtyFlagLockState(boolean dirty) {
+            this.dirty = dirty;
+        }
+
+        public boolean isDirty() {
+            return dirty;
+        }
+
+        public LockState beforeUpdate() {
+            return new DirtyFlagLockState(true);
+        }
+
+        public LockState completeUpdate() {
+            return new DirtyFlagLockState(false);
+        }
+
+        public boolean hasBeenUpdatedSince(FileLock.State state) {
+            throw new UnsupportedOperationException("This protocol version does not support detecting changes by other processes.");
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/locklistener/DefaultFileLockContentionHandler.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/locklistener/DefaultFileLockContentionHandler.java
new file mode 100644
index 0000000..fb612db
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/locklistener/DefaultFileLockContentionHandler.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.locklistener;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.cache.internal.FileLockCommunicator;
+import org.gradle.cache.internal.GracefullyStoppedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultFileLockContentionHandler implements FileLockContentionHandler {
+    private static final Logger LOGGER = Logging.getLogger(DefaultFileLockContentionHandler.class);
+    private final Lock lock = new ReentrantLock();
+    private final Map<Long, Runnable> contendedActions = new HashMap<Long, Runnable>();
+    private final ExecutorFactory executorFactory;
+    private final InetAddressFactory addressFactory;
+
+    private FileLockCommunicator communicator;
+    private StoppableExecutor executor;
+    private boolean stopped;
+
+    public DefaultFileLockContentionHandler(ExecutorFactory executorFactory, InetAddressFactory addressFactory) {
+        this.executorFactory = executorFactory;
+        this.addressFactory = addressFactory;
+    }
+
+    private Runnable listener() {
+        return new Runnable() {
+            public void run() {
+                try {
+                    LOGGER.debug("Starting file lock listener thread.");
+                    doRun();
+                } catch (Throwable t) {
+                    //Logging exception here is only needed because by default Gradle does not show the stack trace
+                    LOGGER.error("Problems handling incoming cache access requests.", t);
+                } finally {
+                    LOGGER.debug("File lock listener thread completed.");
+                }
+            }
+
+            private void doRun() {
+                while (true) {
+                    long lockId;
+                    try {
+                        lockId = communicator.receive();
+                    } catch (GracefullyStoppedException e) {
+                        return;
+                    }
+                    lock.lock();
+                    Runnable action;
+                    try {
+                        action = contendedActions.get(lockId);
+                        if (action == null) {
+                            //received access request for lock that is already closed
+                            continue;
+                        }
+                    } finally {
+                        lock.unlock();
+                    }
+                    action.run();
+                }
+            }
+        };
+    }
+
+    public void start(long lockId, Runnable whenContended) {
+        lock.lock();
+        try {
+            assertNotStopped();
+            if (communicator == null) {
+                throw new IllegalStateException("Must initialize the handler by reserving the port first.");
+            }
+            if (executor == null) {
+                executor = executorFactory.create("File lock request listener");
+                executor.execute(listener());
+            }
+            if (contendedActions.containsKey(lockId)) {
+                throw new UnsupportedOperationException("Multiple contention actions for a given lock are currently not supported.");
+            }
+            contendedActions.put(lockId, whenContended);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void pingOwner(int port, long lockId, String displayName) {
+        getCommunicator().pingOwner(port, lockId, displayName);
+    }
+
+    private void assertNotStopped() {
+        if (stopped) {
+            throw new IllegalStateException(
+                    "Cannot start managing file contention because this handler has been closed.");
+        }
+    }
+
+    public void stop(long lockId) {
+        lock.lock();
+        try {
+            contendedActions.remove(lockId);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void stop() {
+        //Down the road this method should be used to clean up,
+        //when the Gradle process is about to complete (not gradle build).
+        //Ideally in future, this is happens during the clean-up/stopping of the global services
+        // (at the moment we never stop the global services)
+        lock.lock();
+        try {
+            stopped = true;
+            contendedActions.clear();
+            if (communicator != null) {
+                communicator.stop();
+            }
+        } finally {
+            lock.unlock();
+        }
+        if (executor != null) {
+            executor.stop();
+        }
+    }
+
+    public int reservePort() {
+        return getCommunicator().getPort();
+    }
+
+    private FileLockCommunicator getCommunicator() {
+        lock.lock();
+        try {
+            assertNotStopped();
+            if (communicator == null) {
+                communicator = new FileLockCommunicator(addressFactory);
+            }
+            return communicator;
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/locklistener/FileLockContentionHandler.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/locklistener/FileLockContentionHandler.java
new file mode 100644
index 0000000..28a37a0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/locklistener/FileLockContentionHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.locklistener;
+
+public interface FileLockContentionHandler {
+    void start(long lockId, Runnable whenContended);
+
+    void stop(long lockId);
+
+    int reservePort();
+
+    void pingOwner(int port, long lockId, String displayName);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/BuildScriptProcessor.java b/subprojects/core/src/main/groovy/org/gradle/configuration/BuildScriptProcessor.java
deleted file mode 100644
index 9d7ffcc..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/BuildScriptProcessor.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-import org.gradle.util.Clock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class BuildScriptProcessor implements ProjectEvaluator {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BuildScriptProcessor.class);
-    private final ScriptPluginFactory configurerFactory;
-
-    public BuildScriptProcessor(ScriptPluginFactory configurerFactory) {
-        this.configurerFactory = configurerFactory;
-    }
-
-    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
-        LOGGER.info(String.format("Evaluating %s using %s.", project, project.getBuildScriptSource().getDisplayName()));
-        Clock clock = new Clock();
-
-        try {
-            ScriptPlugin configurer = configurerFactory.create(project.getBuildScriptSource());
-            configurer.apply(project);
-        } catch (Exception e) {
-            state.executed(e);
-        }
-
-        LOGGER.debug("Timing: Running the build script took " + clock.getTime());
-    }
-}
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 a6e02fa..d14c7eb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
@@ -15,27 +15,31 @@
  */
 package org.gradle.configuration;
 
-import org.gradle.api.Action;
+import org.gradle.StartParameter;
+import org.gradle.api.Project;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.util.SingleMessageLogger;
 
 public class DefaultBuildConfigurer implements BuildConfigurer {
-
     public void configure(GradleInternal gradle) {
-        gradle.addProjectEvaluationListener(new ImplicitTasksConfigurer());
-        gradle.addProjectEvaluationListener(new ProjectDependencies2TaskResolver());
+        maybeInformAboutIncubatingMode(gradle.getStartParameter());
         if (gradle.getStartParameter().isConfigureOnDemand()) {
-            SingleMessageLogger.informAboutIncubating("Configuration on demand");
             gradle.getRootProject().evaluate();
         } else {
-            gradle.getRootProject().allprojects((Action) new ConfigureProject());
+            for (Project project : gradle.getRootProject().getAllprojects()) {
+                ((ProjectInternal) project).evaluate();
+            }
         }
     }
 
-    static class ConfigureProject implements Action<ProjectInternal> {
-        public void execute(ProjectInternal projectInternal) {
-            projectInternal.evaluate();
+    private void maybeInformAboutIncubatingMode(StartParameter startParameter) {
+        if (startParameter.getParallelThreadCount() != 0 && startParameter.isConfigureOnDemand()) {
+            SingleMessageLogger.incubatingFeatureUsed("Parallel execution with configuration on demand");
+        } else if (startParameter.getParallelThreadCount() != 0) {
+            SingleMessageLogger.incubatingFeatureUsed("Parallel execution");
+        } else if (startParameter.isConfigureOnDemand()) {
+            SingleMessageLogger.incubatingFeatureUsed("Configuration on demand");
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java
index 5fa1fce..88bd935 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java
@@ -15,7 +15,10 @@
  */
 package org.gradle.configuration;
 
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.initialization.InitScript;
 
@@ -25,15 +28,17 @@ import org.gradle.initialization.InitScript;
  */
 public class DefaultInitScriptProcessor implements InitScriptProcessor {
     private final ScriptPluginFactory configurerFactory;
+    private final ScriptHandlerFactory scriptHandlerFactory;
 
-    public DefaultInitScriptProcessor(ScriptPluginFactory configurerFactory) {
+    public DefaultInitScriptProcessor(ScriptPluginFactory configurerFactory, ScriptHandlerFactory scriptHandlerFactory) {
         this.configurerFactory = configurerFactory;
+        this.scriptHandlerFactory = scriptHandlerFactory;
     }
 
-    public void process(ScriptSource initScript, GradleInternal gradle) {
-        ScriptPlugin configurer = configurerFactory.create(initScript);
-        configurer.setClasspathClosureName("initscript");
-        configurer.setScriptBaseClass(InitScript.class);
+    public void process(final ScriptSource initScript, GradleInternal gradle) {
+        ClassLoaderScope classLoaderScope = gradle.getClassLoaderScope().createSibling();
+        ScriptHandler scriptHandler = scriptHandlerFactory.create(initScript, classLoaderScope);
+        ScriptPlugin configurer = configurerFactory.create(initScript, scriptHandler, classLoaderScope, "initscript", InitScript.class);
         configurer.apply(gradle);
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
index 8ee6e7c..6fe42ac 100755
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
@@ -16,115 +16,141 @@
 
 package org.gradle.configuration;
 
-import org.gradle.internal.Factory;
-import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.groovy.scripts.internal.BuildScriptClasspathScriptTransformer;
-import org.gradle.groovy.scripts.internal.BuildScriptTransformer;
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.api.internal.initialization.ScriptHandlerFactory;
-import org.gradle.api.internal.initialization.ScriptHandlerInternal;
+import org.gradle.api.plugins.PluginAware;
 import org.gradle.groovy.scripts.*;
+import org.gradle.groovy.scripts.internal.BuildScriptTransformer;
+import org.gradle.groovy.scripts.internal.PluginsAndBuildscriptTransformer;
+import org.gradle.groovy.scripts.internal.StatementExtractingScriptTransformer;
+import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.plugin.PluginHandler;
+import org.gradle.plugin.internal.*;
+import org.gradle.plugin.resolve.internal.PluginRequest;
+import org.gradle.plugin.resolve.internal.PluginResolver;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
 
 public class DefaultScriptPluginFactory implements ScriptPluginFactory {
     private final ScriptCompilerFactory scriptCompilerFactory;
     private final ImportsReader importsReader;
-    private final ScriptHandlerFactory scriptHandlerFactory;
-    private final ClassLoader defaultClassLoader;
     private final Factory<LoggingManagerInternal> loggingManagerFactory;
+    private final Instantiator instantiator;
+    private final ScriptHandlerFactory scriptHandlerFactory;
+    private final PluginResolverFactory pluginResolverFactory;
+    private final FileLookup fileLookup;
 
     public DefaultScriptPluginFactory(ScriptCompilerFactory scriptCompilerFactory,
-                                                ImportsReader importsReader,
-                                                ScriptHandlerFactory scriptHandlerFactory,
-                                                ClassLoader defaultClassLoader,
-                                                Factory<LoggingManagerInternal> loggingManagerFactory) {
+                                      ImportsReader importsReader,
+                                      Factory<LoggingManagerInternal> loggingManagerFactory,
+                                      Instantiator instantiator,
+                                      ScriptHandlerFactory scriptHandlerFactory,
+                                      PluginResolverFactory pluginResolverFactory,
+                                      FileLookup fileLookup) {
         this.scriptCompilerFactory = scriptCompilerFactory;
         this.importsReader = importsReader;
-        this.scriptHandlerFactory = scriptHandlerFactory;
-        this.defaultClassLoader = defaultClassLoader;
         this.loggingManagerFactory = loggingManagerFactory;
+        this.instantiator = instantiator;
+        this.scriptHandlerFactory = scriptHandlerFactory;
+        this.pluginResolverFactory = pluginResolverFactory;
+        this.fileLookup = fileLookup;
     }
 
-    public ScriptPlugin create(ScriptSource scriptSource) {
-        return new ScriptPluginImpl(scriptSource);
+    public ScriptPlugin create(ScriptSource scriptSource, ScriptHandler scriptHandler, ClassLoaderScope classLoaderScope, String classpathClosureName, Class<? extends BasicScript> scriptClass) {
+        return new ScriptPluginImpl(scriptSource, scriptHandler, classLoaderScope, classpathClosureName, scriptClass);
     }
 
     private class ScriptPluginImpl implements ScriptPlugin {
         private final ScriptSource scriptSource;
-        private String classpathClosureName = "buildscript";
-        private Class<? extends BasicScript> scriptType = DefaultScript.class;
-        private ScriptClassLoaderProvider classLoaderProvider;
-        private ClassLoader classLoader = defaultClassLoader;
+        private final ClassLoaderScope classLoaderScope;
+        private final String classpathClosureName;
+        private final Class<? extends BasicScript> scriptType;
+        private final ScriptHandler scriptHandler;
 
-        public ScriptPluginImpl(ScriptSource scriptSource) {
+        public ScriptPluginImpl(ScriptSource scriptSource, ScriptHandler scriptHandler, ClassLoaderScope classLoaderScope, String classpathClosureName, Class<? extends BasicScript> scriptType) {
             this.scriptSource = scriptSource;
+            this.classLoaderScope = classLoaderScope;
+            this.classpathClosureName = classpathClosureName;
+            this.scriptHandler = scriptHandler;
+            this.scriptType = scriptType;
         }
 
+
         public ScriptSource getSource() {
             return scriptSource;
         }
 
-        public ScriptPlugin setClasspathClosureName(String name) {
-            this.classpathClosureName = name;
-            return this;
-        }
-
-        public ScriptPlugin setClassLoader(ClassLoader classLoader) {
-            this.classLoader = classLoader;
-            return this;
-        }
-
-        public ScriptPlugin setClassLoaderProvider(ScriptClassLoaderProvider classLoaderProvider) {
-            this.classLoaderProvider = classLoaderProvider;
-            return this;
-        }
-
-        public ScriptPlugin setScriptBaseClass(Class<? extends BasicScript> type) {
-            scriptType = type;
-            return this;
-        }
-
-        public void apply(Object target) {
+        public void apply(final Object target) {
             DefaultServiceRegistry services = new DefaultServiceRegistry();
             services.add(ScriptPluginFactory.class, DefaultScriptPluginFactory.this);
+            services.add(ScriptHandlerFactory.class, scriptHandlerFactory);
+            services.add(ClassLoaderScope.class, classLoaderScope);
             services.add(LoggingManagerInternal.class, loggingManagerFactory.create());
+            services.add(Instantiator.class, instantiator);
+            services.add(ScriptHandler.class, scriptHandler);
+            services.add(FileLookup.class, fileLookup);
 
-            ScriptAware scriptAware = null;
-            if (target instanceof ScriptAware) {
-                scriptAware = (ScriptAware) target;
-                scriptAware.beforeCompile(this);
-            }
-            ScriptClassLoaderProvider classLoaderProvider = this.classLoaderProvider;
             ScriptSource withImports = importsReader.withImports(scriptSource);
 
-            if (classLoaderProvider == null) {
-                ScriptHandlerInternal defaultScriptHandler = scriptHandlerFactory.create(withImports, classLoader);
-                services.add(ScriptHandlerInternal.class, defaultScriptHandler);
-                classLoaderProvider = defaultScriptHandler;
+            List<PluginRequest> pluginRequests = new LinkedList<PluginRequest>();
+            if (target instanceof PluginAware) {
+                services.add(PluginHandler.class, new DefaultPluginHandler(pluginRequests));
+            } else {
+                services.add(PluginHandler.class, new NonPluggableTargetPluginHandler(target));
             }
-            
+
             ScriptCompiler compiler = scriptCompilerFactory.createCompiler(withImports);
+            compiler.setClassloader(classLoaderScope.getBase().getChildClassLoader());
+
+            PluginsAndBuildscriptTransformer scriptBlockTransformer = new PluginsAndBuildscriptTransformer(classpathClosureName);
 
-            compiler.setClassloader(classLoaderProvider.getClassLoader());
+            StatementExtractingScriptTransformer classpathScriptTransformer = new StatementExtractingScriptTransformer(classpathClosureName, scriptBlockTransformer);
 
-            BuildScriptClasspathScriptTransformer classpathScriptTransformer
-                    = new BuildScriptClasspathScriptTransformer(classpathClosureName);
             compiler.setTransformer(classpathScriptTransformer);
 
             ScriptRunner<? extends BasicScript> classPathScriptRunner = compiler.compile(scriptType);
             classPathScriptRunner.getScript().init(target, services);
             classPathScriptRunner.run();
 
-            classLoaderProvider.updateClassPath();
+            Configuration classpathConfiguration = scriptHandler.getConfigurations().getByName(ScriptHandler.CLASSPATH_CONFIGURATION);
+            Set<File> files = classpathConfiguration.getFiles();
+            ClassPath classPath = new DefaultClassPath(files);
 
-            compiler.setTransformer(new BuildScriptTransformer(classpathScriptTransformer));
+            ClassLoader exportedClassLoader = classLoaderScope.export(classPath);
+
+            if (!pluginRequests.isEmpty()) {
+                PluginResolver pluginResolver = pluginResolverFactory.createPluginResolver(exportedClassLoader);
+                @SuppressWarnings("ConstantConditions")
+                PluginResolutionApplicator resolutionApplicator = new PluginResolutionApplicator((PluginAware) target, classLoaderScope);
+                PluginRequestApplicator requestApplicator = new PluginRequestApplicator(pluginResolver, resolutionApplicator);
+                requestApplicator.applyPlugin(pluginRequests);
+            }
+
+            classLoaderScope.lock();
+
+            compiler.setClassloader(classLoaderScope.getScopeClassLoader());
+
+            compiler.setTransformer(new BuildScriptTransformer("no_" + classpathScriptTransformer.getId(), classpathScriptTransformer.invert()));
             ScriptRunner<? extends BasicScript> runner = compiler.compile(scriptType);
 
-            runner.getScript().init(target, services);
-            if (scriptAware != null) {
-                scriptAware.afterCompile(this, runner.getScript());
+            BasicScript script = runner.getScript();
+            script.init(target, services);
+            if (target instanceof ScriptAware) {
+                ((ScriptAware) target).setScript(script);
             }
             runner.run();
         }
+
     }
 }
\ No newline at end of file
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 cab2353..600d78f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
@@ -15,12 +15,7 @@
  */
 package org.gradle.configuration;
 
-import org.gradle.api.Project;
-import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.ProjectState;
-
-//This one should go away once we complete the auto-apply plugins
-public class ImplicitTasksConfigurer implements ProjectEvaluationListener {
+public class ImplicitTasksConfigurer {
     public static final String HELP_GROUP = "help";
     public static final String HELP_TASK = "help";
     public static final String PROJECTS_TASK = "projects";
@@ -28,10 +23,4 @@ public class ImplicitTasksConfigurer implements ProjectEvaluationListener {
     public static final String PROPERTIES_TASK = "properties";
     public static final String DEPENDENCIES_TASK = "dependencies";
     public static final String DEPENDENCY_INSIGHT_TASK = "dependencyInsight";
-
-    public void beforeEvaluate(Project project) {}
-
-    public void afterEvaluate(Project project, ProjectState state) {
-        project.getPlugins().apply("help-tasks");
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java
index 2ed96f4..b01f074 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java
@@ -19,13 +19,10 @@ package org.gradle.configuration;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.internal.UncheckedException;
 
-import java.net.URL;
-import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
 
-/**
- * @author Hans Dockter
- */
 public class ImportsReader {
 
     private String importsText;
@@ -33,20 +30,23 @@ public class ImportsReader {
     public String getImports() {
         if (importsText == null) {
             try {
-                URL url = getClass().getResource("default-imports.txt");
+                URL url = getClass().getResource("/default-imports.txt");
                 InputStreamReader reader = new InputStreamReader(url.openStream(), "UTF8");
-
-                int bufferSize = 2048; // at time of writing, the file was about 1k so this should cover in one read
-                StringBuilder imports = new StringBuilder(bufferSize);
-                char[] chars = new char[bufferSize];
-
-                int numRead = reader.read(chars, 0, bufferSize);
-                while (numRead != -1) {
-                    imports.append(chars, 0, numRead);
-                    numRead = reader.read(chars, 0, bufferSize);
+                try {
+                    int bufferSize = 2048; // at time of writing, the file was about 1k so this should cover in one read
+                    StringBuilder imports = new StringBuilder(bufferSize);
+                    char[] chars = new char[bufferSize];
+
+                    int numRead = reader.read(chars, 0, bufferSize);
+                    while (numRead != -1) {
+                        imports.append(chars, 0, numRead);
+                        numRead = reader.read(chars, 0, bufferSize);
+                    }
+
+                    importsText = imports.toString();
+                } finally {
+                    reader.close();
                 }
-
-                importsText = imports.toString();
             } catch (IOException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
deleted file mode 100644
index 5d0ee20..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-
-/**
- * Manages lifecycle concerns while delegating actual evaluation to another evaluator
- * 
- * @see org.gradle.api.internal.project.TopLevelBuildServiceRegistry#createProjectEvaluator()
- */
-public class LifecycleProjectEvaluator implements ProjectEvaluator {
-    private final ProjectEvaluator evaluator;
-
-    public LifecycleProjectEvaluator(ProjectEvaluator evaluator) {
-        this.evaluator = evaluator;
-    }
-
-    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
-        //TODO this is one of the places to look into thread safety when we implement parallel configuration
-        if (state.getExecuted() || state.getExecuting()) {
-            return;
-        }
-
-        ProjectEvaluationListener listener = project.getProjectEvaluationBroadcaster();
-        listener.beforeEvaluate(project);
-        state.setExecuting(true);
-        try {
-            evaluator.evaluate(project, state);
-        } finally {
-            state.setExecuting(false);
-            state.executed();
-            listener.afterEvaluate(project, state);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
deleted file mode 100644
index 9ab5cd5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
+++ /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.configuration;
-
-import org.gradle.api.Project;
-import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.ProjectState;
-import org.gradle.api.Task;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependencies2TaskResolver implements ProjectEvaluationListener {
-    private static Logger logger = LoggerFactory.getLogger(ProjectDependencies2TaskResolver.class);
-
-    public void beforeEvaluate(Project project) {}
-
-    public void afterEvaluate(Project project, ProjectState state) {
-        for (Project dependsOnProject : project.getDependsOnProjects()) {
-            logger.debug("Checking task dependencies for project: {} dependsOn: {}", project, dependsOnProject);
-            for (Task task : project.getTasks()) {
-                String taskName = task.getName();
-                Task dependentTask = dependsOnProject.getTasks().findByName(taskName);
-                if (dependentTask != null) {
-                    logger.debug("Setting task dependencies for task: {}", taskName);
-                    task.dependsOn(dependentTask);
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectEvaluator.java
deleted file mode 100644
index 677cc61..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectEvaluator.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-
-public interface ProjectEvaluator {
-    void evaluate(ProjectInternal project, ProjectStateInternal state);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java
index fc79f09..d6d8d48 100755
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java
@@ -16,27 +16,12 @@
 package org.gradle.configuration;
 
 import org.gradle.api.Plugin;
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
-import org.gradle.groovy.scripts.BasicScript;
 import org.gradle.groovy.scripts.ScriptSource;
 
 public interface ScriptPlugin extends Plugin<Object> {
-    ScriptSource getSource();
-
-    ScriptPlugin setClasspathClosureName(String name);
-
-    ScriptPlugin setScriptBaseClass(Class<? extends BasicScript> type);
 
-    ScriptPlugin setClassLoader(ClassLoader classLoader);
-
-    ScriptPlugin setClassLoaderProvider(ScriptClassLoaderProvider classLoaderProvider);
+    ScriptSource getSource();
 
-    /**
-     * Applies the script to the given object. If target object implements {@link org.gradle.groovy.scripts.ScriptAware},
-     * will call {@link org.gradle.groovy.scripts.ScriptAware#beforeCompile(ScriptPlugin)} and {@link
-     * org.gradle.groovy.scripts.ScriptAware#afterCompile(ScriptPlugin , org.gradle.groovy.scripts.Script)}.
-     *
-     * @param target The target object to configure.
-     */
     void apply(Object target);
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java
index 0ad9f4f..febb169 100755
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java
@@ -15,8 +15,11 @@
  */
 package org.gradle.configuration;
 
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.groovy.scripts.BasicScript;
 import org.gradle.groovy.scripts.ScriptSource;
 
 public interface ScriptPluginFactory {
-    ScriptPlugin create(ScriptSource scriptSource);
+    ScriptPlugin create(ScriptSource scriptSource, ScriptHandler scriptHandler, ClassLoaderScope classLoaderScope, String classpathClosureName, Class<? extends BasicScript> scriptClass);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/BuildScriptProcessor.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/BuildScriptProcessor.java
new file mode 100644
index 0000000..d6ab218
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/BuildScriptProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.configuration.project;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectScript;
+import org.gradle.configuration.ScriptPlugin;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BuildScriptProcessor implements ProjectConfigureAction {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BuildScriptProcessor.class);
+    private final ScriptPluginFactory configurerFactory;
+
+    public BuildScriptProcessor(ScriptPluginFactory configurerFactory) {
+        this.configurerFactory = configurerFactory;
+    }
+
+    public void execute(ProjectInternal project) {
+        LOGGER.info(String.format("Evaluating %s using %s.", project, project.getBuildScriptSource().getDisplayName()));
+        Clock clock = new Clock();
+        try {
+            ScriptPlugin configurer = configurerFactory.create(project.getBuildScriptSource(), project.getBuildscript(), project.getClassLoaderScope(), "buildscript", ProjectScript.class);
+
+            configurer.apply(project);
+        } finally {
+            LOGGER.debug("Timing: Running the build script took " + clock.getTime());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/ConfigureActionsProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ConfigureActionsProjectEvaluator.java
new file mode 100644
index 0000000..e4d4c3e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ConfigureActionsProjectEvaluator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ConfigureActionsProjectEvaluator implements ProjectEvaluator {
+    private final List<ProjectConfigureAction> configureActions;
+
+    public ConfigureActionsProjectEvaluator(ProjectConfigureAction... configureActions) {
+        this.configureActions = Arrays.asList(configureActions);
+    }
+
+    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
+        for (ProjectConfigureAction configureAction : configureActions) {
+            configureAction.execute(project);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/DefaultProjectConfigurationActionContainer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/DefaultProjectConfigurationActionContainer.java
new file mode 100644
index 0000000..03e7879
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/DefaultProjectConfigurationActionContainer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultProjectConfigurationActionContainer implements ProjectConfigurationActionContainer {
+    private final List<Action<? super ProjectInternal>> actions = new ArrayList<Action<? super ProjectInternal>>();
+
+    public void finished() {
+        actions.clear();
+    }
+
+    public List<Action<? super ProjectInternal>> getActions() {
+        return actions;
+    }
+
+    public void add(Action<? super ProjectInternal> action) {
+        actions.add(action);
+    }
+
+    public void add(Closure action) {
+        add(new ClosureBackedAction<ProjectInternal>(action));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/DelayedConfigurationActions.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/DelayedConfigurationActions.java
new file mode 100644
index 0000000..9af016c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/DelayedConfigurationActions.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ProjectInternal;
+
+public class DelayedConfigurationActions implements ProjectConfigureAction {
+    public void execute(ProjectInternal projectInternal) {
+        ProjectConfigurationActionContainer actions = projectInternal.getConfigurationActions();
+        try {
+            for (Action<? super ProjectInternal> action : actions.getActions()) {
+                action.execute(projectInternal);
+            }
+        } finally {
+            actions.finished();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/LifecycleProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/LifecycleProjectEvaluator.java
new file mode 100644
index 0000000..1a1547a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/LifecycleProjectEvaluator.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.configuration.project;
+
+import org.gradle.api.ProjectConfigurationException;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages lifecycle concerns while delegating actual evaluation to another evaluator
+ * 
+ * @see org.gradle.internal.service.scopes.BuildScopeServices#createProjectEvaluator()
+ */
+public class LifecycleProjectEvaluator implements ProjectEvaluator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleProjectEvaluator.class);
+
+    private final ProjectEvaluator delegate;
+
+    public LifecycleProjectEvaluator(ProjectEvaluator delegate) {
+        this.delegate = delegate;
+    }
+
+    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
+        //TODO this is one of the places to look into thread safety when we implement parallel configuration
+        if (state.getExecuted() || state.getExecuting()) {
+            return;
+        }
+
+        ProjectEvaluationListener listener = project.getProjectEvaluationBroadcaster();
+        try {
+            listener.beforeEvaluate(project);
+        } catch (Exception e) {
+            addConfigurationFailure(project, state, e);
+            return;
+        }
+
+        state.setExecuting(true);
+        try {
+            delegate.evaluate(project, state);
+        } catch (Exception e) {
+            addConfigurationFailure(project, state, e);
+        } finally {
+            state.setExecuting(false);
+            state.executed();
+            notifyAfterEvaluate(listener, project, state);
+        }
+    }
+
+    private void notifyAfterEvaluate(ProjectEvaluationListener listener, ProjectInternal project, ProjectStateInternal state) {
+        try {
+            listener.afterEvaluate(project, state);
+        } catch (Exception e) {
+            if (state.hasFailure()) {
+                // Just log this failure, and pass the existing failure out in the project state
+                LOGGER.error("Failed to notify ProjectEvaluationListener.afterEvaluate(), but primary configuration failure takes precedence.", e);
+                return;
+            }
+            addConfigurationFailure(project, state, e);
+        }
+    }
+
+    private void addConfigurationFailure(ProjectInternal project, ProjectStateInternal state, Exception e) {
+        ProjectConfigurationException failure = new ProjectConfigurationException(String.format("A problem occurred configuring %s.", project), e);
+        state.executed(failure);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/PluginsProjectConfigureActions.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/PluginsProjectConfigureActions.java
new file mode 100644
index 0000000..834f60b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/PluginsProjectConfigureActions.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.configuration.project;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.service.ServiceLocator;
+
+public class PluginsProjectConfigureActions implements ProjectConfigureAction {
+    private final ServiceLocator serviceLocator;
+
+    public PluginsProjectConfigureActions(ClassLoader pluginsClassLoader) {
+        this.serviceLocator = new ServiceLocator(pluginsClassLoader);
+    }
+
+    public void execute(ProjectInternal project) {
+        for (ProjectConfigureAction configureAction : serviceLocator.getAll(ProjectConfigureAction.class)) {
+            configureAction.execute(project);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectConfigurationActionContainer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectConfigurationActionContainer.java
new file mode 100644
index 0000000..1cbf77c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectConfigurationActionContainer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * A container responsible for managing the configuration of a project.
+ */
+public interface ProjectConfigurationActionContainer {
+    void finished();
+
+    Iterable<Action<? super ProjectInternal>> getActions();
+
+    /**
+     * Registers an action to execute to configure the project. Actions are executed in an arbitrary order.
+     */
+    void add(Action<? super ProjectInternal> action);
+
+    /**
+     * Registers an action to execute to configure the project. Actions are executed in an arbitrary order.
+     */
+    void add(Closure action);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectConfigureAction.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectConfigureAction.java
new file mode 100644
index 0000000..416d1ce
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectConfigureAction.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.configuration.project;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * Can be implemented by plugins to auto-configure each project.
+ *
+ * <p>Implementations are discovered using the JAR service locator mechanism (see {@link org.gradle.internal.service.ServiceLocator}).
+ * Each action is invoked for each project that is to be configured, before the project has been configured. Actions are executed
+ * in an arbitrary order.
+ */
+public interface ProjectConfigureAction extends Action<ProjectInternal> {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectDependencies2TaskResolver.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectDependencies2TaskResolver.java
new file mode 100644
index 0000000..688b698
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectDependencies2TaskResolver.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.configuration.project;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProjectDependencies2TaskResolver implements ProjectConfigureAction {
+    private static Logger logger = LoggerFactory.getLogger(ProjectDependencies2TaskResolver.class);
+
+    public void execute(ProjectInternal project) {
+        for (Project dependsOnProject : project.getDependsOnProjects()) {
+            logger.debug("Checking task dependencies for project: {} dependsOn: {}", project, dependsOnProject);
+            for (Task task : project.getTasks()) {
+                String taskName = task.getName();
+                Task dependentTask = dependsOnProject.getTasks().findByName(taskName);
+                if (dependentTask != null) {
+                    logger.debug("Setting task dependencies for task: {}", taskName);
+                    task.dependsOn(dependentTask);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectEvaluator.java
new file mode 100644
index 0000000..79c1c4a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/ProjectEvaluator.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.configuration.project;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+
+public interface ProjectEvaluator {
+    void evaluate(ProjectInternal project, ProjectStateInternal state);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/project/TaskModelRealizingConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/configuration/project/TaskModelRealizingConfigurationAction.java
new file mode 100644
index 0000000..5164736
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/project/TaskModelRealizingConfigurationAction.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
+
+/**
+ * Realizes the project tasks by getting the closed task container from the model registry.
+ */
+public class TaskModelRealizingConfigurationAction implements ProjectConfigureAction {
+
+    public void execute(ProjectInternal projectInternal) {
+        projectInternal.getModelRegistry().get(TaskContainerInternal.MODEL_PATH, Object.class);
+    }
+
+}
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 fbfb721..f9e800a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
@@ -31,7 +31,7 @@ public class ExcludedTaskFilteringBuildConfigurationAction implements BuildConfi
         GradleInternal gradle = context.getGradle();
         Set<String> excludedTaskNames = gradle.getStartParameter().getExcludedTaskNames();
         if (!excludedTaskNames.isEmpty()) {
-            TaskSelector selector = createSelector(gradle);
+            TaskSelector selector = gradle.getServices().get(TaskSelector.class);
             final Set<Task> excludedTasks = new HashSet<Task>();
             for (String taskName : excludedTaskNames) {
                 excludedTasks.addAll(selector.getSelection(taskName).getTasks());
@@ -45,8 +45,4 @@ 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
index 9b0ddb5..e202727 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/MultipleBuildFailures.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/MultipleBuildFailures.java
@@ -16,7 +16,7 @@
 
 package org.gradle.execution;
 
-import org.gradle.api.internal.AbstractMultiCauseException;
+import org.gradle.internal.exceptions.AbstractMultiCauseException;
 
 import java.util.List;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
index d036dec..81b82e8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
@@ -23,8 +23,6 @@ import java.util.List;
 
 /**
  * Ensures that projects resolved from the command line task names are evaluated.
- *
- * by Szczepan Faber, created at: 11/22/12
  */
 public class ProjectEvaluatingAction implements BuildConfigurationAction {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolver.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolver.java
index 424ae41..30d5c37 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolver.java
@@ -20,54 +20,71 @@ import com.google.common.collect.SetMultimap;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.TaskContainer;
 
 import java.util.Collections;
 
 public class TaskNameResolver {
-    
-    public SetMultimap<String, Task> select(String name, Project project) {
+
+    public SetMultimap<String, TaskSelectionResult> select(String name, Project project) {
         return select(name, (ProjectInternal) project, Collections.<Project>emptySet());
     }
 
-    public SetMultimap<String, Task> selectAll(String name, Project project) {
+    public SetMultimap<String, TaskSelectionResult> selectAll(String name, Project project) {
         return select(name, (ProjectInternal) project, project.getSubprojects());
     }
 
-    private SetMultimap<String, Task> select(String name, ProjectInternal project, Iterable<Project> additionalProjects) {
-        SetMultimap<String, Task> selected = LinkedHashMultimap.create();
+    private SetMultimap<String, TaskSelectionResult> select(String name, ProjectInternal project, Iterable<Project> additionalProjects) {
+        SetMultimap<String, TaskSelectionResult> selected = LinkedHashMultimap.create();
         Task task = project.getTasks().findByName(name);
         if (task != null) {
-            selected.put(task.getName(), task);
+            selected.put(task.getName(), new LazyTaskSelectionResult(task.getName(), project.getTasks()));
         } else {
             task = project.getImplicitTasks().findByName(name);
             if (task != null) {
-                selected.put(task.getName(), task);
+                selected.put(task.getName(), new LazyTaskSelectionResult(task.getName(), project.getImplicitTasks()));
             }
         }
         for (Project additionalProject : additionalProjects) {
             task = additionalProject.getTasks().findByName(name);
             if (task != null) {
-                selected.put(task.getName(), task);
+                selected.put(task.getName(), new LazyTaskSelectionResult(task.getName(), additionalProject.getTasks()));
             }
         }
         if (!selected.isEmpty()) {
             return selected;
         }
 
-        for (Task t : project.getTasks()) {
-            selected.put(t.getName(), t);
+        for (String taskName : project.getTasks().getNames()) {
+            selected.put(taskName, new LazyTaskSelectionResult(taskName, project.getTasks()));
         }
-        for (Task t : project.getImplicitTasks()) {
-            if (!selected.containsKey(t.getName())) {
-                selected.put(t.getName(), t);
+        for (String taskName : project.getImplicitTasks().getNames()) {
+            if (!selected.containsKey(taskName)) {
+                selected.put(taskName, new LazyTaskSelectionResult(taskName, project.getImplicitTasks()));
             }
         }
+
         for (Project additionalProject : additionalProjects) {
-            for (Task t : additionalProject.getTasks()) {
-                selected.put(t.getName(), t);
+            for (String taskName : additionalProject.getTasks().getNames()) {
+                selected.put(taskName, new LazyTaskSelectionResult(taskName, additionalProject.getTasks()));
             }
         }
 
         return selected;
     }
+
+
+    public static class LazyTaskSelectionResult implements TaskSelectionResult {
+        private final TaskContainer taskContainer;
+        private final String taskName;
+
+        public LazyTaskSelectionResult(String taskName, TaskContainer tasksContainer) {
+            this.taskContainer = tasksContainer;
+            this.taskName = taskName;
+        }
+
+        public Task getTask() {
+            return taskContainer.getByName(taskName);
+        }
+    }
 }
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 fb501e2..87263c1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
@@ -31,20 +31,18 @@ import java.util.List;
  */
 public class TaskNameResolvingBuildConfigurationAction implements BuildConfigurationAction {
     private static final Logger LOGGER = LoggerFactory.getLogger(TaskNameResolvingBuildConfigurationAction.class);
-    private final TaskNameResolver taskNameResolver;
+    private final CommandLineTaskParser commandLineTaskParser;
+    private final TaskSelector selector;
 
-    public TaskNameResolvingBuildConfigurationAction() {
-        this(new TaskNameResolver());
-    }
-
-    TaskNameResolvingBuildConfigurationAction(TaskNameResolver taskNameResolver) {
-        this.taskNameResolver = taskNameResolver;
+    public TaskNameResolvingBuildConfigurationAction(CommandLineTaskParser commandLineTaskParser, TaskSelector selector) {
+        this.commandLineTaskParser = commandLineTaskParser;
+        this.selector = selector;
     }
 
     public void configure(BuildExecutionContext context) {
         GradleInternal gradle = context.getGradle();
         List<String> taskNames = gradle.getStartParameter().getTaskNames();
-        Multimap<String, Task> selectedTasks = doSelect(gradle, taskNames, taskNameResolver);
+        Multimap<String, Task> selectedTasks = commandLineTaskParser.parseTasks(taskNames, selector);
 
         TaskGraphExecuter executer = gradle.getTaskGraph();
         for (String name : selectedTasks.keySet()) {
@@ -60,8 +58,4 @@ public class TaskNameResolvingBuildConfigurationAction implements BuildConfigura
         context.proceed();
     }
 
-    private Multimap<String, Task> doSelect(GradleInternal gradle, List<String> paths, TaskNameResolver taskNameResolver) {
-        TaskSelector selector = new TaskSelector(gradle, taskNameResolver);
-        return new CommandLineTaskParser().parseTasks(paths, selector);
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
index 45139f3..d1bc839 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
@@ -21,9 +21,6 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.execution.taskpath.ResolvedTaskPath;
 import org.gradle.execution.taskpath.TaskPathResolver;
 
-/**
- * by Szczepan Faber, created at: 1/8/13
- */
 public class TaskPathProjectEvaluator {
 
     private final TaskPathResolver taskPathResolver;
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionException.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionException.java
index f6fdfc4..1b7c041 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionException.java
@@ -15,14 +15,26 @@
  */
 package org.gradle.execution;
 
+import org.gradle.internal.exceptions.FailureResolutionAware;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.configuration.ImplicitTasksConfigurer;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.logging.StyledTextOutput;
+
+import static org.gradle.logging.StyledTextOutput.Style.UserInput;
 
 /**
  * A {@code TaskSelectionException} is thrown when the tasks to execute cannot be selected due to some user input
  * problem.
  */
-public class TaskSelectionException extends InvalidUserDataException {
+public class TaskSelectionException extends InvalidUserDataException implements FailureResolutionAware{
     public TaskSelectionException(String message) {
         super(message);
     }
+
+    public void appendResolution(StyledTextOutput output, BuildClientMetaData clientMetaData) {
+        output.text("Run ");
+        clientMetaData.describeCommand(output.withStyle(UserInput), ImplicitTasksConfigurer.TASKS_TASK);
+        output.text(" to get a list of available tasks.");
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionResult.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionResult.java
new file mode 100644
index 0000000..4946a48
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelectionResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution;
+
+import org.gradle.api.Task;
+
+public interface TaskSelectionResult {
+    Task getTask();
+}
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 56a186b..b781b97 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
@@ -17,12 +17,16 @@ package org.gradle.execution;
 
 import com.google.common.collect.SetMultimap;
 import org.gradle.api.Task;
+import org.gradle.api.Transformer;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.execution.taskpath.ResolvedTaskPath;
 import org.gradle.execution.taskpath.TaskPathResolver;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.NameMatcher;
 
+import java.util.Collection;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 public class TaskSelector {
@@ -40,17 +44,17 @@ public class TaskSelector {
     }
 
     public TaskSelection getSelection(String path) {
-        SetMultimap<String, Task> tasksByName;
+        SetMultimap<String, TaskSelectionResult> tasksByName;
         ProjectInternal project = gradle.getDefaultProject();
         ResolvedTaskPath taskPath = taskPathResolver.resolvePath(path, project);
 
         if (taskPath.isQualified()) {
             tasksByName = taskNameResolver.select(taskPath.getTaskName(), taskPath.getProject());
         } else {
-            tasksByName = taskNameResolver.selectAll(path, project);
+            tasksByName = taskNameResolver.selectAll(taskPath.getTaskName(), taskPath.getProject());
         }
 
-        Set<Task> tasks = tasksByName.get(taskPath.getTaskName());
+        Set<TaskSelectionResult> tasks = tasksByName.get(taskPath.getTaskName());
         if (!tasks.isEmpty()) {
             // An exact match
             return new TaskSelection(path, tasks);
@@ -58,22 +62,19 @@ public class TaskSelector {
 
         NameMatcher matcher = new NameMatcher();
         String actualName = matcher.find(taskPath.getTaskName(), tasksByName.keySet());
-
         if (actualName != null) {
-            // A partial match
             return new TaskSelection(taskPath.getPrefix() + actualName, tasksByName.get(actualName));
         }
 
-        throw new TaskSelectionException(matcher.formatErrorMessage("task", project));
+        throw new TaskSelectionException(matcher.formatErrorMessage("task", taskPath.getProject()));
     }
 
     public static class TaskSelection {
         private String taskName;
-        private Set<Task> tasks;
-
-        public TaskSelection(String taskName, Set<Task> tasks) {
+        private Collection<TaskSelectionResult> taskSelectionResult;
+        public TaskSelection(String taskName, Set<TaskSelectionResult> tasks) {
             this.taskName = taskName;
-            this.tasks = tasks;
+            taskSelectionResult = tasks;
         }
 
         public String getTaskName() {
@@ -81,7 +82,11 @@ public class TaskSelector {
         }
 
         public Set<Task> getTasks() {
-            return tasks;
+            return CollectionUtils.collect(taskSelectionResult, new LinkedHashSet<Task>(), new Transformer<Task, TaskSelectionResult>() {
+                public Task transform(TaskSelectionResult original) {
+                    return original.getTask();
+                }
+            });
         }
     }
 }
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
index e7139cb..18da44c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
@@ -16,26 +16,26 @@
 
 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.api.internal.tasks.options.OptionDescriptor;
+import org.gradle.api.internal.tasks.options.OptionReader;
 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 org.gradle.internal.typeconversion.TypeConversionException;
 
-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 {
 
+    private OptionReader optionReader;
+
+    public CommandLineTaskConfigurer(OptionReader optionReader) {
+        this.optionReader = optionReader;
+    }
+
     public List<String> configureTasks(Collection<Task> tasks, List<String> arguments) {
         assert !tasks.isEmpty();
         if (arguments.isEmpty()) {
@@ -47,21 +47,13 @@ public class CommandLineTaskConfigurer {
     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));
-                    }
-                }
+            final List<OptionDescriptor> commandLineOptions = optionReader.getOptions(task);
+            for (OptionDescriptor optionDescriptor : commandLineOptions) {
+                String optionName = optionDescriptor.getName();
+                org.gradle.cli.CommandLineOption option = parser.option(optionName);
+                option.hasDescription(optionDescriptor.getDescription());
+                option.hasArgument(optionDescriptor.getArgumentType());
             }
 
             ParsedCommandLine parsed;
@@ -69,31 +61,25 @@ public class CommandLineTaskConfigurer {
                 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);
+                throw new TaskConfigurationException(task.getPath(), "Problem configuring task " + task.getPath() + " from command line.", 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);
+
+            for (OptionDescriptor commandLineOptionDescriptor : commandLineOptions) {
+                final String name = commandLineOptionDescriptor.getName();
+                if (parsed.hasOption(name)) {
+                    ParsedCommandLineOption o = parsed.option(name);
+                    try {
+                        commandLineOptionDescriptor.apply(task, o.getValues());
+                    } catch (TypeConversionException ex) {
+                        throw new TaskConfigurationException(task.getPath(),
+                                String.format("Problem configuring option '%s' on task '%s' from command line.", name, task.getPath()), ex);
                     }
                 }
             }
-            //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";
+                    : "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;
-    }
 }
\ 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
index 291b5d7..305658c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java
@@ -26,12 +26,12 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
-/**
- * by Szczepan Faber, created at: 10/8/12
- */
 public class CommandLineTaskParser {
+    private final CommandLineTaskConfigurer taskConfigurer;
 
-    CommandLineTaskConfigurer taskConfigurer =  new CommandLineTaskConfigurer();
+    public CommandLineTaskParser(CommandLineTaskConfigurer commandLineTaskConfigurer) {
+        this.taskConfigurer = commandLineTaskConfigurer;
+    }
 
     public Multimap<String, Task> parseTasks(List<String> taskPaths, TaskSelector taskSelector) {
         SetMultimap<String, Task> out = LinkedHashMultimap.create();
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/TaskConfigurationException.java b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/TaskConfigurationException.java
new file mode 100644
index 0000000..f2c001c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/TaskConfigurationException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.configuration.ImplicitTasksConfigurer;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.exceptions.Contextual;
+import org.gradle.internal.exceptions.FailureResolutionAware;
+import org.gradle.logging.StyledTextOutput;
+
+import static org.gradle.logging.StyledTextOutput.Style.UserInput;
+
+ at Contextual
+public class TaskConfigurationException extends GradleException implements FailureResolutionAware {
+
+    private final String taskPath;
+
+    public TaskConfigurationException(String taskPath, String message, Exception cause) {
+        super(message, cause);
+        this.taskPath = taskPath;
+    }
+
+    public void appendResolution(StyledTextOutput output, BuildClientMetaData clientMetaData) {
+        output.text("Run ");
+        clientMetaData.describeCommand(output.withStyle(UserInput), ImplicitTasksConfigurer.HELP_TASK);
+        output.withStyle(UserInput).format(" --task %s", taskPath);
+        output.text(" to get task usage details.");
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/AbstractTaskPlanExecutor.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/AbstractTaskPlanExecutor.java
new file mode 100644
index 0000000..fbc86c3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/AbstractTaskPlanExecutor.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import static org.gradle.util.Clock.prettyTime;
+
+abstract class AbstractTaskPlanExecutor implements TaskPlanExecutor {
+    private static final Logger LOGGER = Logging.getLogger(AbstractTaskPlanExecutor.class);
+    private final Object lock = new Object();
+
+    protected Runnable taskWorker(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+        return new TaskExecutorWorker(taskExecutionPlan, taskListener);
+    }
+
+    private class TaskExecutorWorker implements Runnable {
+        private final TaskExecutionPlan taskExecutionPlan;
+        private final TaskExecutionListener taskListener;
+
+        private TaskExecutorWorker(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+            this.taskExecutionPlan = taskExecutionPlan;
+            this.taskListener = taskListener;
+        }
+
+        public void run() {
+            long busy = 0;
+            long start = System.currentTimeMillis();
+            TaskInfo task;
+            while ((task = taskExecutionPlan.getTaskToExecute()) != null) {
+                final String taskPath = task.getTask().getPath();
+                LOGGER.info("{} ({}) started.", taskPath, Thread.currentThread());
+                long startTask = System.currentTimeMillis();
+                processTask(task);
+                long taskDuration = System.currentTimeMillis() - startTask;
+                busy += taskDuration;
+                LOGGER.info("{} ({}) completed. Took {}.", taskPath, Thread.currentThread(), prettyTime(taskDuration));
+            }
+            long total = System.currentTimeMillis() - start;
+            //TODO SF it would be nice to print one-line statement that concludes the utilisation of the worker threads
+            LOGGER.debug("Task worker [{}] finished, busy: {}, idle: {}", Thread.currentThread(), prettyTime(busy), prettyTime(total - busy));
+        }
+
+        protected void processTask(TaskInfo taskInfo) {
+            try {
+                executeTask(taskInfo);
+            } 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) {
+            TaskInternal task = taskInfo.getTask();
+            synchronized (lock) {
+                taskListener.beforeExecute(task);
+            }
+            try {
+                task.executeWithoutThrowingTaskFailure();
+            } finally {
+                synchronized (lock) {
+                    taskListener.afterExecute(task, task.getState());
+                }
+            }
+        }
+    }
+}
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
index 912c5da..557b800 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
@@ -18,6 +18,7 @@ package org.gradle.execution.taskgraph;
 
 import org.gradle.api.CircularReferenceException;
 import org.gradle.api.Task;
+import org.gradle.api.Transformer;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.tasks.CachingTaskDependencyResolveContext;
 import org.gradle.api.specs.Spec;
@@ -25,7 +26,14 @@ import org.gradle.api.specs.Specs;
 import org.gradle.execution.MultipleBuildFailures;
 import org.gradle.execution.TaskFailureHandler;
 import org.gradle.internal.UncheckedException;
-
+import org.gradle.internal.graph.CachingDirectedGraphWalker;
+import org.gradle.internal.graph.DirectedGraph;
+import org.gradle.internal.graph.DirectedGraphRenderer;
+import org.gradle.internal.graph.GraphNodeRenderer;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.util.CollectionUtils;
+
+import java.io.StringWriter;
 import java.util.*;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
@@ -38,6 +46,9 @@ import java.util.concurrent.locks.ReentrantLock;
 class DefaultTaskExecutionPlan implements TaskExecutionPlan {
     private final Lock lock = new ReentrantLock();
     private final Condition condition = lock.newCondition();
+    private final Set<TaskInfo> tasksInUnknownState = new LinkedHashSet<TaskInfo>();
+    private final Set<TaskInfo> entryTasks = new LinkedHashSet<TaskInfo>();
+    private final TaskDependencyGraph graph = new TaskDependencyGraph();
     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();
@@ -46,57 +57,307 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
     private final List<String> runningProjects = new ArrayList<String>();
 
     public void addToTaskGraph(Collection<? extends Task> tasks) {
-        List<Task> queue = new ArrayList<Task>(tasks);
-        Collections.sort(queue);
+        List<TaskInfo> queue = new ArrayList<TaskInfo>();
+
+        List<Task> sortedTasks = new ArrayList<Task>(tasks);
+        Collections.sort(sortedTasks);
+        for (Task task : sortedTasks) {
+            TaskInfo node = graph.addNode(task);
+            if (node.getMustNotRun()) {
+                requireWithDependencies(node);
+            } else if (filter.isSatisfiedBy(task)) {
+                node.require();
+            }
+            entryTasks.add(node);
+            queue.add(node);
+        }
 
-        Set<Task> visiting = new HashSet<Task>();
+        Set<TaskInfo> visiting = new HashSet<TaskInfo>();
         CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext();
 
         while (!queue.isEmpty()) {
-            Task task = queue.get(0);
-            if (!filter.isSatisfiedBy(task)) {
-                // Filtered - skip
+            TaskInfo node = queue.get(0);
+            if (node.getDependenciesProcessed()) {
+                // Have already visited this task - skip it
                 queue.remove(0);
                 continue;
             }
-            if (executionPlan.containsKey(task)) {
-                // Already in plan - skip
+
+            TaskInternal task = node.getTask();
+            boolean filtered = !filter.isSatisfiedBy(task);
+            if (filtered) {
+                // Task is not required - skip it
                 queue.remove(0);
+                node.dependenciesProcessed();
+                node.doNotRequire();
                 continue;
             }
 
-            if (visiting.add(task)) {
+            if (visiting.add(node)) {
                 // 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));
+                Set<? extends Task> dependsOnTasks = 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));
+                    TaskInfo targetNode = graph.addNode(dependsOnTask);
+                    node.addDependencySuccessor(targetNode);
+                    if (!visiting.contains(targetNode)) {
+                        queue.add(0, targetNode);
+                    }
+                }
+                for (Task finalizerTask : task.getFinalizedBy().getDependencies(task)) {
+                    TaskInfo targetNode = graph.addNode(finalizerTask);
+                    addFinalizerNode(node, targetNode);
+                    if (!visiting.contains(targetNode)) {
+                        queue.add(0, targetNode);
+                    }
+                }
+                for (Task mustRunAfter : task.getMustRunAfter().getDependencies(task)) {
+                    TaskInfo targetNode = graph.addNode(mustRunAfter);
+                    node.addMustSuccessor(targetNode);
+                }
+                for (Task shouldRunAfter : task.getShouldRunAfter().getDependencies(task)) {
+                    TaskInfo targetNode = graph.addNode(shouldRunAfter);
+                    node.addShouldSuccessor(targetNode);
+                }
+                if (node.isRequired()) {
+                    for (TaskInfo successor : node.getDependencySuccessors()) {
+                        if (filter.isSatisfiedBy(successor.getTask())) {
+                            successor.require();
+                        }
+                    }
+                } else {
+                    tasksInUnknownState.add(node);
+                }
+            } else {
+                // Have visited this task's dependencies - add it to the graph
+                queue.remove(0);
+                visiting.remove(node);
+                node.dependenciesProcessed();
+            }
+        }
+        resolveTasksInUnknownState();
+    }
+
+    private void resolveTasksInUnknownState() {
+        List<TaskInfo> queue = new ArrayList<TaskInfo>(tasksInUnknownState);
+        Set<TaskInfo> visiting = new HashSet<TaskInfo>();
+
+        while (!queue.isEmpty()) {
+            TaskInfo task = queue.get(0);
+            if (task.isInKnownState()) {
+                queue.remove(0);
+                continue;
+            }
+
+            if (visiting.add(task)) {
+                for (TaskInfo hardPredecessor : task.getDependencyPredecessors()) {
+                    if (!visiting.contains(hardPredecessor)) {
+                        queue.add(0, hardPredecessor);
                     }
-                    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);
+                task.mustNotRun();
+                for (TaskInfo predecessor : task.getDependencyPredecessors()) {
+                    assert predecessor.isRequired() || predecessor.getMustNotRun();
+                    if (predecessor.isRequired()) {
+                        task.require();
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private void addFinalizerNode(TaskInfo node, TaskInfo finalizerNode) {
+        if (filter.isSatisfiedBy(finalizerNode.getTask())) {
+            node.addFinalizer(finalizerNode);
+            if (!finalizerNode.isInKnownState()) {
+                finalizerNode.mustNotRun();
+            }
+            finalizerNode.addMustSuccessor(node);
+        }
+    }
+
+    private <T> void addAllReversed(List<T> list, TreeSet<T> set) {
+        List<T> elements = CollectionUtils.toList(set);
+        Collections.reverse(elements);
+        list.addAll(elements);
+    }
+
+    private void requireWithDependencies(TaskInfo taskInfo) {
+        if (taskInfo.getMustNotRun() && filter.isSatisfiedBy(taskInfo.getTask())) {
+            taskInfo.require();
+            for (TaskInfo dependency : taskInfo.getDependencySuccessors()) {
+                requireWithDependencies(dependency);
+            }
+        }
+    }
+
+    public void determineExecutionPlan() {
+        List<TaskInfo> nodeQueue = new ArrayList<TaskInfo>(entryTasks);
+        Set<TaskInfo> visitingNodes = new HashSet<TaskInfo>();
+        Stack<TaskDependencyGraphEdge> walkedShouldRunAfterEdges = new Stack<TaskDependencyGraphEdge>();
+        Stack<TaskInfo> path = new Stack<TaskInfo>();
+        HashMap<TaskInfo, Integer> planBeforeVisiting = new HashMap<TaskInfo, Integer>();
+        while (!nodeQueue.isEmpty()) {
+            TaskInfo taskNode = nodeQueue.get(0);
+
+            if (taskNode.isIncludeInGraph() || executionPlan.containsKey(taskNode.getTask())) {
+                nodeQueue.remove(0);
+                continue;
+            }
+
+            if (visitingNodes.add(taskNode)) {
+                // Have not seen this task before - add its dependencies to the head of the queue and leave this
+                // task in the queue
+                recordEdgeIfArrivedViaShouldRunAfter(walkedShouldRunAfterEdges, path, taskNode);
+                removeShouldRunAfterSuccessorsIfTheyImposeACycle(visitingNodes, taskNode);
+                takePlanSnapshotIfCanBeRestoredToCurrentTask(planBeforeVisiting, taskNode);
+                ArrayList<TaskInfo> successors = new ArrayList<TaskInfo>();
+                addAllSuccessorsInReverseOrder(taskNode, successors);
+                for (TaskInfo successor : successors) {
+                    if (visitingNodes.contains(successor)) {
+                        if (!walkedShouldRunAfterEdges.empty()) {
+                            //remove the last walked should run after edge and restore state from before walking it
+                            TaskDependencyGraphEdge toBeRemoved = walkedShouldRunAfterEdges.pop();
+                            toBeRemoved.getFrom().removeShouldRunAfterSuccessor(toBeRemoved.getTo());
+                            restorePath(path, toBeRemoved);
+                            restoreQueue(nodeQueue, visitingNodes, toBeRemoved);
+                            restoreExecutionPlan(planBeforeVisiting, toBeRemoved);
+                            break;
+                        } else {
+                            onOrderingCycle();
+                        }
+                    }
+                    nodeQueue.add(0, successor);
+                }
+                path.push(taskNode);
+            } else {
+                // Have visited this task's dependencies - add it to the end of the plan
+                nodeQueue.remove(0);
+                visitingNodes.remove(taskNode);
+                path.pop();
+                executionPlan.put(taskNode.getTask(), taskNode);
+                // Add any finalizers to the queue
+                ArrayList<TaskInfo> finalizerTasks = new ArrayList<TaskInfo>();
+                addAllReversed(finalizerTasks, taskNode.getFinalizers());
+                for (TaskInfo finalizer : finalizerTasks) {
+                    if (!visitingNodes.contains(finalizer) && !nodeQueue.contains(finalizer)) {
+                        nodeQueue.add(finalizerTaskPosition(finalizer, nodeQueue), finalizer);
                     }
-                    // else - the dependency has been filtered, so ignore it
                 }
-                executionPlan.put(task, new TaskInfo((TaskInternal) task, dependencies));
             }
         }
     }
 
+    private void restoreExecutionPlan(HashMap<TaskInfo, Integer> planBeforeVisiting, TaskDependencyGraphEdge toBeRemoved) {
+        Iterator<Map.Entry<Task, TaskInfo>> executionPlanIterator = executionPlan.entrySet().iterator();
+        for (int i = 0; i < planBeforeVisiting.get(toBeRemoved.getFrom()); i++) {
+            executionPlanIterator.next();
+        }
+        while (executionPlanIterator.hasNext()) {
+            executionPlanIterator.next();
+            executionPlanIterator.remove();
+        }
+    }
+
+    private void restoreQueue(List<TaskInfo> nodeQueue, Set<TaskInfo> visitingNodes, TaskDependencyGraphEdge toBeRemoved) {
+        TaskInfo nextInQueue = null;
+        while (!toBeRemoved.getFrom().equals(nextInQueue)) {
+            nextInQueue = nodeQueue.get(0);
+            visitingNodes.remove(nextInQueue);
+            if (!toBeRemoved.getFrom().equals(nextInQueue)) {
+                nodeQueue.remove(0);
+            }
+        }
+    }
+
+    private void restorePath(Stack<TaskInfo> path, TaskDependencyGraphEdge toBeRemoved) {
+        TaskInfo removedFromPath = null;
+        while (!toBeRemoved.getFrom().equals(removedFromPath)) {
+            removedFromPath = path.pop();
+        }
+    }
+
+    private void addAllSuccessorsInReverseOrder(TaskInfo taskNode, ArrayList<TaskInfo> dependsOnTasks) {
+        addAllReversed(dependsOnTasks, taskNode.getDependencySuccessors());
+        addAllReversed(dependsOnTasks, taskNode.getMustSuccessors());
+        addAllReversed(dependsOnTasks, taskNode.getShouldSuccessors());
+    }
+
+    private void removeShouldRunAfterSuccessorsIfTheyImposeACycle(Set<TaskInfo> visitingNodes, TaskInfo taskNode) {
+        for (TaskInfo shouldRunAfterSuccessor : taskNode.getShouldSuccessors()) {
+            if (visitingNodes.contains(shouldRunAfterSuccessor)) {
+                taskNode.removeShouldRunAfterSuccessor(shouldRunAfterSuccessor);
+            }
+        }
+    }
+
+    private void takePlanSnapshotIfCanBeRestoredToCurrentTask(HashMap<TaskInfo, Integer> planBeforeVisiting, TaskInfo taskNode) {
+        if (taskNode.getShouldSuccessors().size() > 0) {
+            planBeforeVisiting.put(taskNode, executionPlan.size());
+        }
+    }
+
+    private void recordEdgeIfArrivedViaShouldRunAfter(Stack<TaskDependencyGraphEdge> walkedShouldRunAfterEdges, Stack<TaskInfo> path, TaskInfo taskNode) {
+        if (!path.empty() && path.peek().getShouldSuccessors().contains(taskNode)) {
+            walkedShouldRunAfterEdges.push(new TaskDependencyGraphEdge(path.peek(), taskNode));
+        }
+    }
+
+    private int finalizerTaskPosition(TaskInfo finalizer, final List<TaskInfo> nodeQueue) {
+        if (nodeQueue.size() == 0) {
+            return 0;
+        }
+
+        ArrayList<TaskInfo> dependsOnTasks = new ArrayList<TaskInfo>();
+        dependsOnTasks.addAll(finalizer.getDependencySuccessors());
+        dependsOnTasks.addAll(finalizer.getMustSuccessors());
+        dependsOnTasks.addAll(finalizer.getShouldSuccessors());
+        List<Integer> dependsOnTaskIndexes = CollectionUtils.collect(dependsOnTasks, new Transformer<Integer, TaskInfo>() {
+            public Integer transform(TaskInfo dependsOnTask) {
+                return nodeQueue.indexOf(dependsOnTask);
+            }
+        });
+        return Collections.max(dependsOnTaskIndexes) + 1;
+    }
+
+    private void onOrderingCycle() {
+        CachingDirectedGraphWalker<TaskInfo, Void> graphWalker = new CachingDirectedGraphWalker<TaskInfo, Void>(new DirectedGraph<TaskInfo, Void>() {
+            public void getNodeValues(TaskInfo node, Collection<? super Void> values, Collection<? super TaskInfo> connectedNodes) {
+                connectedNodes.addAll(node.getDependencySuccessors());
+                connectedNodes.addAll(node.getMustSuccessors());
+            }
+        });
+        graphWalker.add(entryTasks);
+        final List<TaskInfo> firstCycle = new ArrayList<TaskInfo>(graphWalker.findCycles().get(0));
+        Collections.sort(firstCycle);
+
+        DirectedGraphRenderer<TaskInfo> graphRenderer = new DirectedGraphRenderer<TaskInfo>(new GraphNodeRenderer<TaskInfo>() {
+            public void renderTo(TaskInfo node, StyledTextOutput output) {
+                output.withStyle(StyledTextOutput.Style.Identifier).text(node.getTask().getPath());
+            }
+        }, new DirectedGraph<TaskInfo, Object>() {
+            public void getNodeValues(TaskInfo node, Collection<? super Object> values, Collection<? super TaskInfo> connectedNodes) {
+                for (TaskInfo dependency : firstCycle) {
+                    if (node.getDependencySuccessors().contains(dependency) || node.getMustSuccessors().contains(dependency)) {
+                        connectedNodes.add(dependency);
+                    }
+                }
+            }
+        });
+        StringWriter writer = new StringWriter();
+        graphRenderer.renderTo(firstCycle.get(0), writer);
+        throw new CircularReferenceException(String.format("Circular dependency between the following tasks:%n%s", writer.toString()));
+    }
+
     public void clear() {
         lock.lock();
         try {
+            graph.clear();
+            entryTasks.clear();
             executionPlan.clear();
             failures.clear();
             runningProjects.clear();
@@ -117,45 +378,10 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
         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();
-        }
-    }
-
     public TaskInfo getTaskToExecute() {
         lock.lock();
         try {
-            while(true) {
+            while (true) {
                 TaskInfo nextMatching = null;
                 boolean allTasksComplete = true;
                 for (TaskInfo taskInfo : executionPlan.values()) {
@@ -190,18 +416,10 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
         }
     }
 
-    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 {
+            enforceFinalizerTasks(taskInfo);
             if (taskInfo.isFailed()) {
                 handleFailure(taskInfo);
             }
@@ -214,6 +432,23 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
         }
     }
 
+    private void enforceFinalizerTasks(TaskInfo taskInfo) {
+        for (TaskInfo finalizerNode : taskInfo.getFinalizers()) {
+            if (finalizerNode.isRequired() || finalizerNode.getMustNotRun()) {
+                enforceWithDependencies(finalizerNode);
+            }
+        }
+    }
+
+    private void enforceWithDependencies(TaskInfo node) {
+        for (TaskInfo dependencyNode : node.getDependencySuccessors()) {
+            enforceWithDependencies(dependencyNode);
+        }
+        if (node.getMustNotRun() || node.isRequired()) {
+            node.enforceRun();
+        }
+    }
+
     private void handleFailure(TaskInfo taskInfo) {
         Throwable executionFailure = taskInfo.getExecutionFailure();
         if (executionFailure != null) {
@@ -235,9 +470,9 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
     }
 
     private void abortExecution() {
-        // Allow currently executing tasks to complete, but skip everything else.
+        // Allow currently executing and enforced tasks to complete, but skip everything else.
         for (TaskInfo taskInfo : executionPlan.values()) {
-            if (taskInfo.isReady()) {
+            if (taskInfo.isRequired()) {
                 taskInfo.skipExecution();
             }
         }
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
index dddbae7..5b319e8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
@@ -34,17 +34,18 @@ 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 enum TaskGraphState {
+        EMPTY, DIRTY, POPULATED
+    }
+
     private final TaskPlanExecutor taskPlanExecutor;
     private final ListenerBroadcast<TaskExecutionGraphListener> graphListeners;
     private final ListenerBroadcast<TaskExecutionListener> taskListeners;
     private final DefaultTaskExecutionPlan taskExecutionPlan = new DefaultTaskExecutionPlan();
-    private boolean populated;
+    private TaskGraphState taskGraphState = TaskGraphState.EMPTY;
 
     public DefaultTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor) {
         this.taskPlanExecutor = taskPlanExecutor;
@@ -52,14 +53,15 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
         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 useFilter(Spec<? super Task> filter) {
+        taskExecutionPlan.useFilter(filter);
+        taskGraphState = TaskGraphState.DIRTY;
+    }
+
     public void addTasks(Iterable<? extends Task> tasks) {
         assert tasks != null;
 
@@ -70,14 +72,14 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
             taskSet.add(task);
         }
         taskExecutionPlan.addToTaskGraph(taskSet);
-        populated = true;
+        taskGraphState = TaskGraphState.DIRTY;
 
         logger.debug("Timing: Creating the DAG took " + clock.getTime());
     }
 
     public void execute() {
-        assertPopulated();
         Clock clock = new Clock();
+        ensurePopulated();
 
         graphListeners.getSource().graphPopulated(this);
         try {
@@ -117,12 +119,12 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
     }
 
     public boolean hasTask(Task task) {
-        assertPopulated();
+        ensurePopulated();
         return taskExecutionPlan.getTasks().contains(task);
     }
 
     public boolean hasTask(String path) {
-        assertPopulated();
+        ensurePopulated();
         assert path != null && path.length() > 0;
         for (Task task : taskExecutionPlan.getTasks()) {
             if (task.getPath().equals(path)) {
@@ -133,14 +135,20 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
     }
 
     public List<Task> getAllTasks() {
-        assertPopulated();
+        ensurePopulated();
         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.");
+    private void ensurePopulated() {
+        switch (taskGraphState) {
+            case EMPTY:
+                throw new IllegalStateException(
+                        "Task information is not available, as this task execution graph has not been populated.");
+            case DIRTY:
+                taskExecutionPlan.determineExecutionPlan();
+                taskGraphState = TaskGraphState.POPULATED;
+                return;
+            case 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
index b353a6d..6bf6e12 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutor.java
@@ -17,41 +17,10 @@
 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);
-        }
+class DefaultTaskPlanExecutor extends AbstractTaskPlanExecutor {
+    public void process(final TaskExecutionPlan taskExecutionPlan, final TaskExecutionListener taskListener) {
+        taskWorker(taskExecutionPlan, taskListener).run();
         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
deleted file mode 100644
index caa6512..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ExecutionOptions.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.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
index 29af40c..f49b834 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ParallelTaskPlanExecutor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ParallelTaskPlanExecutor.java
@@ -19,58 +19,51 @@ 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.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
 
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
-import static org.gradle.util.Clock.prettyTime;
-
-class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
+class ParallelTaskPlanExecutor extends AbstractTaskPlanExecutor {
     private static final Logger LOGGER = Logging.getLogger(ParallelTaskPlanExecutor.class);
-
-    private final List<Thread> executorThreads = new ArrayList<Thread>();
-    private final TaskArtifactStateCacheAccess stateCacheAccess;
     private final int executorCount;
+    private final ExecutorFactory executorFactory;
 
-    public ParallelTaskPlanExecutor(TaskArtifactStateCacheAccess cacheAccess, int numberOfParallelExecutors) {
+    public ParallelTaskPlanExecutor(int numberOfParallelExecutors, ExecutorFactory executorFactory) {
+        this.executorFactory = executorFactory;
         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();
-            }
-        });
+        StoppableExecutor executor = executorFactory.create("Task worker");
+        try {
+            startAdditionalWorkers(taskExecutionPlan, taskListener, executor);
+            taskWorker(taskExecutionPlan, taskListener).run();
+            taskExecutionPlan.awaitCompletion();
+        } finally {
+            executor.stop();
+        }
     }
 
-    private void doProcess(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+    private void startAdditionalWorkers(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener, Executor executor) {
         List<Project> projects = getAllProjects(taskExecutionPlan);
         int numExecutors = Math.min(executorCount, projects.size());
-        numExecutors = Math.min(numExecutors, 4);
 
-        for (int i = 0; i < numExecutors; i++) {
-            TaskExecutorWorker worker = new TaskExecutorWorker(taskExecutionPlan, taskListener);
-            executorThreads.add(new Thread(worker));
-        }
+        LOGGER.info("Using {} parallel executor threads", numExecutors);
 
-        for (Thread executorThread : executorThreads) {
-            // TODO A bunch more stuff to contextualise the thread
-            executorThread.start();
+        for (int i = 1; i < numExecutors; i++) {
+            Runnable worker = taskWorker(taskExecutionPlan, taskListener);
+            executor.execute(worker);
         }
     }
 
@@ -81,41 +74,4 @@ class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
         }
         return new ArrayList<Project>(uniqueProjects);
     }
-
-    private class TaskExecutorWorker implements Runnable {
-        private final TaskExecutionPlan taskExecutionPlan;
-        private final TaskExecutionListener taskListener;
-        private long busyMs;
-        private long waitedForCacheMs;
-
-        private TaskExecutorWorker(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
-            this.taskExecutionPlan = taskExecutionPlan;
-            this.taskListener = taskListener;
-        }
-
-        public void run() {
-            long start = System.currentTimeMillis();
-            TaskInfo task;
-            while((task = taskExecutionPlan.getTaskToExecute()) != null) {
-                executeTaskWithCacheLock(task);
-            }
-            long total = System.currentTimeMillis() - start;
-            LOGGER.info("Parallel worker [{}] stopped, busy: {}, idle: {}, waited for cache: {}", Thread.currentThread(), prettyTime(busyMs), prettyTime(total - busyMs), prettyTime(waitedForCacheMs));
-        }
-
-        private void executeTaskWithCacheLock(final TaskInfo taskInfo) {
-            final String taskPath = taskInfo.getTask().getPath();
-            LOGGER.info(taskPath + " (" + Thread.currentThread() + " - start");
-            final long start = System.currentTimeMillis();
-            stateCacheAccess.useCache("Executing " + taskPath, new Runnable() {
-                public void run() {
-                    waitedForCacheMs += System.currentTimeMillis() - start;
-                    processTask(taskInfo, taskExecutionPlan, taskListener);
-                }
-            });
-            busyMs += System.currentTimeMillis() - start;
-
-            LOGGER.info(taskPath + " (" + Thread.currentThread() + ") - complete");
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskDependencyGraph.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskDependencyGraph.java
new file mode 100644
index 0000000..2bf5e7c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskDependencyGraph.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Nullable;
+import org.gradle.api.Task;
+import org.gradle.api.internal.TaskInternal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class TaskDependencyGraph {
+    private final Map<Task, TaskInfo> nodes = new HashMap<Task, TaskInfo>();
+
+    public Set<Task> getTasks() {
+        return nodes.keySet();
+    }
+
+    @Nullable
+    public TaskInfo getNode(Task task) {
+        return nodes.get(task);
+    }
+
+    public TaskInfo addNode(Task task) {
+        TaskInfo node = nodes.get(task);
+        if (node == null) {
+            node = new TaskInfo((TaskInternal) task);
+            nodes.put(task, node);
+        }
+        return node;
+    }
+
+    public void clear() {
+        nodes.clear();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskDependencyGraphEdge.groovy b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskDependencyGraphEdge.groovy
new file mode 100644
index 0000000..9a84e4c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskDependencyGraphEdge.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 TaskDependencyGraphEdge {
+
+    TaskInfo from
+    TaskInfo to
+
+    TaskDependencyGraphEdge(TaskInfo from, TaskInfo to) {
+        this.from = from
+        this.to = to
+    }
+
+    TaskInfo getFrom() {
+        return from
+    }
+
+    TaskInfo getTo() {
+        return to
+    }
+}
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
index 7020ba2..3a2b9a4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java
@@ -17,7 +17,6 @@
 package org.gradle.execution.taskgraph;
 
 import org.gradle.api.Task;
-import org.gradle.api.specs.Spec;
 
 import java.util.List;
 
@@ -26,15 +25,6 @@ import java.util.List;
  */
 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.
      */
@@ -50,6 +40,11 @@ public interface TaskExecutionPlan {
      */
     List<Task> getTasks();
 
-    //TODO SF this should replace completely getTaskToExecute(), inherit and expand existing unit test coverage
+    /**
+     * Provides a ready-to-execute task. A task is ready-to-execute if all of its dependencies have been completed successfully.
+     * This method blocks until the at least one task is ready-to-execute.
+     * If no tasks remain, null will be returned.
+     * @return The task, or null if no matching tasks remain.
+     */
     TaskInfo getTaskToExecute();
 }
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
index ea7b9fb..a9d448e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskInfo.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskInfo.java
@@ -16,45 +16,68 @@
 
 package org.gradle.execution.taskgraph;
 
+import com.google.common.collect.Iterables;
 import org.gradle.api.internal.TaskInternal;
 
-import java.util.Set;
+import java.util.TreeSet;
 
-class TaskInfo {
+public class TaskInfo implements Comparable<TaskInfo> {
 
     private enum TaskExecutionState {
-        READY, EXECUTING, EXECUTED, SKIPPED
+        UNKNOWN, NOT_REQUIRED, SHOULD_RUN, MUST_RUN, MUST_NOT_RUN, 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) {
+    private boolean dependenciesProcessed;
+    private final TreeSet<TaskInfo> dependencyPredecessors = new TreeSet<TaskInfo>();
+    private final TreeSet<TaskInfo> dependencySuccessors = new TreeSet<TaskInfo>();
+    private final TreeSet<TaskInfo> mustSuccessors = new TreeSet<TaskInfo>();
+    private final TreeSet<TaskInfo> shouldSuccessors = new TreeSet<TaskInfo>();
+    private final TreeSet<TaskInfo> finalizers = new TreeSet<TaskInfo>();
+
+    public TaskInfo(TaskInternal task) {
         this.task = task;
-        this.dependencies = dependencies;
-        this.state = TaskExecutionState.READY;
+        this.state = TaskExecutionState.UNKNOWN;
     }
 
     public TaskInternal getTask() {
         return task;
     }
 
-    public Set<TaskInfo> getDependencies() {
-        return dependencies;
+    public boolean isRequired() {
+        return state == TaskExecutionState.SHOULD_RUN;
+    }
+
+    public boolean getMustNotRun() {
+        return state == TaskExecutionState.MUST_NOT_RUN;
+    }
+
+    public boolean isIncludeInGraph() {
+        return state == TaskExecutionState.NOT_REQUIRED || state == TaskExecutionState.UNKNOWN;
     }
 
     public boolean isReady() {
-        return state == TaskExecutionState.READY;
+        return state == TaskExecutionState.SHOULD_RUN || state == TaskExecutionState.MUST_RUN;
+    }
+
+    public boolean isInKnownState() {
+        return state != TaskExecutionState.UNKNOWN;
     }
 
     public boolean isComplete() {
-        return state == TaskExecutionState.EXECUTED || state == TaskExecutionState.SKIPPED;
+        return state == TaskExecutionState.EXECUTED
+                || state == TaskExecutionState.SKIPPED
+                || state == TaskExecutionState.UNKNOWN
+                || state == TaskExecutionState.NOT_REQUIRED
+                || state == TaskExecutionState.MUST_NOT_RUN;
     }
 
     public boolean isSuccessful() {
-        return state == TaskExecutionState.EXECUTED && !isFailed();
+        return (state == TaskExecutionState.EXECUTED && !isFailed())
+                || state == TaskExecutionState.NOT_REQUIRED
+                || state == TaskExecutionState.MUST_NOT_RUN;
     }
 
     public boolean isFailed() {
@@ -62,7 +85,7 @@ class TaskInfo {
     }
 
     public void startExecution() {
-        assert state == TaskExecutionState.READY;
+        assert isReady();
         state = TaskExecutionState.EXECUTING;
     }
 
@@ -72,10 +95,27 @@ class TaskInfo {
     }
 
     public void skipExecution() {
-        assert state == TaskExecutionState.READY;
+        assert state == TaskExecutionState.SHOULD_RUN;
         state = TaskExecutionState.SKIPPED;
     }
 
+    public void require() {
+        state = TaskExecutionState.SHOULD_RUN;
+    }
+
+    public void doNotRequire() {
+        state = TaskExecutionState.NOT_REQUIRED;
+    }
+
+    public void mustNotRun() {
+        state = TaskExecutionState.MUST_NOT_RUN;
+    }
+
+    public void enforceRun() {
+        assert state == TaskExecutionState.SHOULD_RUN || state == TaskExecutionState.MUST_NOT_RUN || state == TaskExecutionState.MUST_RUN;
+        state = TaskExecutionState.MUST_RUN;
+    }
+
     public void setExecutionFailure(Throwable failure) {
         assert state == TaskExecutionState.EXECUTING;
         this.executionFailure = failure;
@@ -90,7 +130,7 @@ class TaskInfo {
     }
 
     public boolean allDependenciesComplete() {
-        for (TaskInfo dependency : getDependencies()) {
+        for (TaskInfo dependency : Iterables.concat(mustSuccessors, dependencySuccessors)) {
             if (!dependency.isComplete()) {
                 return false;
             }
@@ -99,11 +139,68 @@ class TaskInfo {
     }
 
     public boolean allDependenciesSuccessful() {
-        for (TaskInfo dependency : getDependencies()) {
+        for (TaskInfo dependency : dependencySuccessors) {
             if (!dependency.isSuccessful()) {
                 return false;
             }
         }
         return true;
     }
+
+    public TreeSet<TaskInfo> getDependencyPredecessors() {
+        return dependencyPredecessors;
+    }
+
+    public TreeSet<TaskInfo> getDependencySuccessors() {
+        return dependencySuccessors;
+    }
+
+    public TreeSet<TaskInfo> getMustSuccessors() {
+        return mustSuccessors;
+    }
+
+    public TreeSet<TaskInfo> getFinalizers() {
+        return finalizers;
+    }
+
+    public TreeSet<TaskInfo> getShouldSuccessors() {
+        return shouldSuccessors;
+    }
+
+    public boolean getDependenciesProcessed() {
+        return dependenciesProcessed;
+    }
+
+    public void dependenciesProcessed() {
+        dependenciesProcessed = true;
+    }
+
+    public void addDependencySuccessor(TaskInfo toNode) {
+        dependencySuccessors.add(toNode);
+        toNode.dependencyPredecessors.add(this);
+    }
+
+    public void addMustSuccessor(TaskInfo toNode) {
+        mustSuccessors.add(toNode);
+    }
+
+    public void addFinalizer(TaskInfo finalizerNode) {
+        finalizers.add(finalizerNode);
+    }
+
+    public void addShouldSuccessor(TaskInfo toNode) {
+        shouldSuccessors.add(toNode);
+    }
+
+    public void removeShouldRunAfterSuccessor(TaskInfo toNode) {
+        shouldSuccessors.remove(toNode);
+    }
+
+    public int compareTo(TaskInfo otherInfo) {
+        return task.compareTo(otherInfo.getTask());
+    }
+
+    public String toString() {
+        return task.getPath();
+    }
 }
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
index e988f2c..450a205 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java
@@ -16,27 +16,33 @@
 
 package org.gradle.execution.taskgraph;
 
-import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
 import org.gradle.internal.Factory;
-import org.gradle.util.SingleMessageLogger;
+import org.gradle.internal.concurrent.ExecutorFactory;
 
 public class TaskPlanExecutorFactory implements Factory<TaskPlanExecutor> {
-
-    private final TaskArtifactStateCacheAccess taskArtifactStateCacheAccess;
     private final int parallelThreads;
+    private final ExecutorFactory executorFactory;
 
-    public TaskPlanExecutorFactory(TaskArtifactStateCacheAccess taskArtifactStateCacheAccess, int parallelThreads) {
-        this.taskArtifactStateCacheAccess = taskArtifactStateCacheAccess;
+    public TaskPlanExecutorFactory(int parallelThreads, ExecutorFactory executorFactory) {
         this.parallelThreads = parallelThreads;
+        this.executorFactory = executorFactory;
     }
 
     public TaskPlanExecutor create() {
-        ExecutionOptions options = new ExecutionOptions(parallelThreads);
-        if (options.executeProjectsInParallel()) {
-            SingleMessageLogger.informAboutIncubating("Parallel project execution");
-            return new ParallelTaskPlanExecutor(taskArtifactStateCacheAccess, options.numberOfParallelThreads());
+        if (executeProjectsInParallel()) {
+            return new ParallelTaskPlanExecutor(numberOfParallelThreads(), executorFactory);
         }
         return new DefaultTaskPlanExecutor();
+    }
 
+    private boolean executeProjectsInParallel() {
+        return parallelThreads != 0;
+    }
+
+    private int numberOfParallelThreads() {
+        if (parallelThreads == -1) {
+            return Runtime.getRuntime().availableProcessors();
+        }
+        return parallelThreads;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java
index d6e43ff..50372ff 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java
@@ -23,9 +23,6 @@ import org.gradle.util.NameMatcher;
 
 import java.util.Map;
 
-/**
- * by Szczepan Faber, created at: 1/3/13
- */
 public class ProjectFinderByTaskPath {
 
     public ProjectInternal findProject(String projectPath, ProjectInternal startFrom) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java
index 031ddef..f074bec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java
@@ -18,9 +18,6 @@ package org.gradle.execution.taskpath;
 
 import org.gradle.api.internal.project.ProjectInternal;
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 public class ResolvedTaskPath {
     private final String prefix;
     private final String taskName;
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java
index a419d55..5d87ad5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java
@@ -19,9 +19,6 @@ package org.gradle.execution.taskpath;
 import org.gradle.api.Project;
 import org.gradle.api.internal.project.ProjectInternal;
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 public class TaskPathResolver {
 
     private final ProjectFinderByTaskPath projectFinder;
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java
index 4db2849..19d99ff 100755
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java
@@ -19,17 +19,13 @@ package org.gradle.groovy.scripts;
 import groovy.lang.MetaClass;
 import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.internal.DynamicObjectUtil;
-import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.api.internal.ProcessOperations;
 import org.gradle.api.internal.file.FileOperations;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.StandardOutputCapture;
-import org.gradle.api.internal.ProcessOperations;
 
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- *
- */
 public abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations {
     private StandardOutputCapture standardOutputCapture;
     private Object target;
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 f911362..0bbaec6 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
@@ -17,6 +17,7 @@
 package org.gradle.groovy.scripts;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.PathValidation;
 import org.gradle.api.Script;
 import org.gradle.api.file.ConfigurableFileCollection;
@@ -25,7 +26,12 @@ import org.gradle.api.file.CopySpec;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.ProcessOperations;
-import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.file.DefaultFileOperations;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -33,7 +39,7 @@ import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.resources.ResourceHandler;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.configuration.ScriptPluginFactory;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.process.ExecResult;
 import org.gradle.util.ConfigureUtil;
@@ -45,23 +51,26 @@ import java.util.Map;
 
 public abstract class DefaultScript extends BasicScript {
     private static final Logger LOGGER = Logging.getLogger(Script.class);
-    private ServiceRegistry services;
+
     private FileOperations fileOperations;
     private ProcessOperations processOperations;
     private LoggingManager loggingManager;
 
-    public void init(Object target, ServiceRegistry services) {
+    public static final String SCRIPT_SERVICES_PROPERTY = "__scriptServices";
+    public ServiceRegistry __scriptServices;
+
+    public void init(final Object target, ServiceRegistry services) {
         super.init(target, services);
-        this.services = services;
+        this.__scriptServices = services;
         loggingManager = services.get(LoggingManager.class);
+        Instantiator instantiator = services.get(Instantiator.class);
+        FileLookup fileLookup = services.get(FileLookup.class);
         if (target instanceof FileOperations) {
             fileOperations = (FileOperations) target;
         } else if (getScriptSource().getResource().getFile() != null) {
-            fileOperations = new DefaultFileOperations(
-                    new BaseDirFileResolver(FileSystems.getDefault(), getScriptSource().getResource().getFile().getParentFile()), null, null
-            );
+            fileOperations = new DefaultFileOperations(fileLookup.getFileResolver(getScriptSource().getResource().getFile().getParentFile()), null, null, instantiator, fileLookup);
         } else {
-            fileOperations = new DefaultFileOperations(new IdentityFileResolver(), null, null);
+            fileOperations = new DefaultFileOperations(fileLookup.getFileResolver(), null, null, instantiator, fileLookup);
         }
 
         processOperations = (ProcessOperations) fileOperations;
@@ -72,7 +81,10 @@ public abstract class DefaultScript extends BasicScript {
     }
 
     private DefaultObjectConfigurationAction createObjectConfigurationAction() {
-        return new DefaultObjectConfigurationAction(getFileResolver(), services.get(ScriptPluginFactory.class), getScriptTarget());
+        ClassLoaderScope classLoaderScope = __scriptServices.get(ClassLoaderScope.class).createChild();
+        return new DefaultObjectConfigurationAction(
+                getFileResolver(), __scriptServices.get(ScriptPluginFactory.class), __scriptServices.get(ScriptHandlerFactory.class), classLoaderScope, getScriptTarget()
+        );
     }
 
     public void apply(Closure closure) {
@@ -88,7 +100,7 @@ public abstract class DefaultScript extends BasicScript {
     }
 
     public ScriptHandler getBuildscript() {
-        return services.get(ScriptHandler.class);
+        return __scriptServices.get(ScriptHandler.class);
     }
 
     public void buildscript(Closure configureClosure) {
@@ -153,10 +165,18 @@ public abstract class DefaultScript extends BasicScript {
         return fileOperations.copy(closure);
     }
 
+    public WorkResult sync(Action<? super CopySpec> action) {
+        return fileOperations.sync(action);
+    }
+
     public CopySpec copySpec(Closure closure) {
         return fileOperations.copySpec(closure);
     }
 
+    public CopySpec copySpec(Action<? super CopySpec> action) {
+        return fileOperations.copySpec(action);
+    }
+
     public File mkdir(Object path) {
         return fileOperations.mkdir(path);
     }
@@ -184,4 +204,6 @@ public abstract class DefaultScript extends BasicScript {
     public String toString() {
         return "script";
     }
+
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
index 62de9c6..fa34188 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
@@ -15,14 +15,11 @@
  */
 package org.gradle.groovy.scripts;
 
-import org.gradle.internal.reflect.DirectInstantiator;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.groovy.scripts.internal.ScriptClassCompiler;
 import org.gradle.groovy.scripts.internal.ScriptRunnerFactory;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultScriptCompilerFactory implements ScriptCompilerFactory {
     private final ScriptRunnerFactory scriptRunnerFactory;
     private final ScriptClassCompiler scriptClassCompiler;
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java
index a3db4a0..4e43808 100755
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java
@@ -15,10 +15,6 @@
  */
 package org.gradle.groovy.scripts;
 
-import org.gradle.configuration.ScriptPlugin;
-
 public interface ScriptAware {
-    void beforeCompile(ScriptPlugin configurer);
-
-    void afterCompile(ScriptPlugin configurer, Script script);
+    void setScript(groovy.lang.Script script);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java
index ea4fb9c..1c5e396 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java
@@ -17,8 +17,6 @@ package org.gradle.groovy.scripts;
 
 /**
  * A factory for script compilers.
- *
- * @author Hans Dockter
  */
 public interface ScriptCompilerFactory {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
index a4c68fc..ebed4b9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
@@ -17,7 +17,7 @@ package org.gradle.groovy.scripts;
 
 import org.gradle.api.internal.resource.Resource;
 import org.gradle.api.internal.resource.StringResource;
-import org.gradle.util.hash.HashUtil;
+import org.gradle.internal.hash.HashUtil;
 
 public class StringScriptSource implements ScriptSource {
     private final Resource resource;
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
index 5dba9b1..1a4b6b0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
@@ -18,7 +18,7 @@ package org.gradle.groovy.scripts;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.internal.resource.Resource;
 import org.gradle.api.internal.resource.UriResource;
-import org.gradle.util.hash.HashUtil;
+import org.gradle.internal.hash.HashUtil;
 
 import java.io.File;
 import java.net.URI;
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java
index 99d94c8..8e643a6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java
@@ -15,12 +15,7 @@
  */
 package org.gradle.groovy.scripts.internal;
 
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.GroovyCodeVisitor;
-import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.control.CompilationUnit;
-import org.codehaus.groovy.ast.expr.*;
-import org.codehaus.groovy.control.SourceUnit;
 import org.gradle.groovy.scripts.Transformer;
 
 public abstract class AbstractScriptTransformer extends CompilationUnit.SourceUnitOperation implements Transformer {
@@ -30,35 +25,4 @@ public abstract class AbstractScriptTransformer extends CompilationUnit.SourceUn
 
     protected abstract int getPhase();
 
-    protected boolean isMethodOnThis(MethodCallExpression call, String name) {
-        boolean isTaskMethod = call.getMethod() instanceof ConstantExpression && call.getMethod().getText().equals(
-                name);
-        return isTaskMethod && targetIsThis(call);
-    }
-
-    protected boolean targetIsThis(MethodCallExpression call) {
-        Expression target = call.getObjectExpression();
-        return target instanceof VariableExpression && target.getText().equals("this");
-    }
-
-    protected void visitScriptCode(SourceUnit source, GroovyCodeVisitor transformer) {
-        source.getAST().getStatementBlock().visit(transformer);
-        for (Object method : source.getAST().getMethods()) {
-            MethodNode methodNode = (MethodNode) method;
-            methodNode.getCode().visit(transformer);
-        }
-    }
-
-    protected ClassNode getScriptClass(SourceUnit source) {
-        if (source.getAST().getStatementBlock().getStatements().isEmpty() && source.getAST().getMethods().isEmpty()) {
-            // There is no script class when there are no statements or methods declared in the script
-            return null;
-        }
-        return source.getAST().getClasses().get(0);
-    }
-
-    protected void removeMethod(ClassNode declaringClass, MethodNode methodNode) {
-        declaringClass.getMethods().remove(methodNode);
-        declaringClass.getDeclaredMethods(methodNode.getName()).clear();
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java
index b6c2fbb..8df45a8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java
@@ -16,7 +16,8 @@
 package org.gradle.groovy.scripts.internal;
 
 import groovy.lang.Script;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.reflect.JavaMethod;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -77,8 +78,9 @@ public class AsmBackedEmptyScriptGenerator implements EmptyScriptGenerator {
         visitor.visitEnd();
 
         byte[] bytecode = visitor.toByteArray();
-        return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
-                typeName, bytecode, 0, bytecode.length
-        });
+        JavaMethod<ClassLoader, Class> method = JavaReflectionUtil.method(ClassLoader.class, Class.class, "defineClass", String.class, byte[].class, int.class, int.class);
+        @SuppressWarnings("unchecked")
+        Class<T> clazz = method.invoke(type.getClassLoader(), typeName, bytecode, 0, bytecode.length);
+        return clazz;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AstUtils.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AstUtils.java
new file mode 100644
index 0000000..4ab1a6e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AstUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+
+import java.util.ListIterator;
+
+/**
+ * Self contained utility functions for dealing with AST.
+ */
+public abstract class AstUtils {
+
+    private AstUtils() {
+    }
+
+    public static boolean isMethodOnThis(MethodCallExpression call, String name) {
+        boolean hasName = call.getMethod() instanceof ConstantExpression && call.getMethod().getText().equals(name);
+        return hasName && targetIsThis(call);
+    }
+
+    public static boolean targetIsThis(MethodCallExpression call) {
+        Expression target = call.getObjectExpression();
+        return target instanceof VariableExpression && target.getText().equals("this");
+    }
+
+    public static void visitScriptCode(SourceUnit source, GroovyCodeVisitor transformer) {
+        source.getAST().getStatementBlock().visit(transformer);
+        for (Object method : source.getAST().getMethods()) {
+            MethodNode methodNode = (MethodNode) method;
+            methodNode.getCode().visit(transformer);
+        }
+    }
+
+    public static ClassNode getScriptClass(SourceUnit source) {
+        if (source.getAST().getStatementBlock().getStatements().isEmpty() && source.getAST().getMethods().isEmpty()) {
+            // There is no script class when there are no statements or methods declared in the script
+            return null;
+        }
+        return source.getAST().getClasses().get(0);
+    }
+
+    public static void removeMethod(ClassNode declaringClass, MethodNode methodNode) {
+        declaringClass.getMethods().remove(methodNode);
+        declaringClass.getDeclaredMethods(methodNode.getName()).clear();
+    }
+
+    public static void filterAndTransformStatements(SourceUnit source, StatementTransformer transformer) {
+        ListIterator<Statement> statementIterator = source.getAST().getStatementBlock().getStatements().listIterator();
+        while (statementIterator.hasNext()) {
+            Statement originalStatement = statementIterator.next();
+            Statement transformedStatement = transformer.transform(source, originalStatement);
+            if (transformedStatement == null) {
+                statementIterator.remove();
+            } else if (transformedStatement != originalStatement) {
+                statementIterator.set(transformedStatement);
+            }
+        }
+    }
+
+    public static boolean isVisible(SourceUnit source, String className) {
+        try {
+            source.getClassLoader().loadClass(className);
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptClasspathScriptTransformer.java
deleted file mode 100644
index 09197d1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptClasspathScriptTransformer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.groovy.scripts.internal;
-
-/**
- * An implementation of ClasspathScriptTransformer for use in build scripts.  This subclass defines the script method
- * name to be buildscript {}.
- */
-public class BuildScriptClasspathScriptTransformer extends ClasspathScriptTransformer {
-    private final String classpathClosureName;
-
-    public BuildScriptClasspathScriptTransformer(String classpathClosureName) {
-        this.classpathClosureName = classpathClosureName;
-    }
-
-    public String getId() {
-        return classpathClosureName;
-    }
-
-    protected String getScriptMethodName() {
-        return classpathClosureName;
-    }
-}
-
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java
index f504f25..d2ebe05 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java
@@ -19,18 +19,21 @@ import org.codehaus.groovy.control.CompilationUnit;
 import org.gradle.groovy.scripts.Transformer;
 
 public class BuildScriptTransformer implements Transformer {
-    private final BuildScriptClasspathScriptTransformer classpathScriptTransformer;
 
-    public BuildScriptTransformer(BuildScriptClasspathScriptTransformer transformer) {
-        classpathScriptTransformer = transformer;
+    private final String id;
+    private final Transformer extractionTransformer;
+
+    public BuildScriptTransformer(String id, Transformer extractionTransformer) {
+        this.id = id;
+        this.extractionTransformer = extractionTransformer;
     }
 
     public String getId() {
-        return "no_" + classpathScriptTransformer.getId();
+        return id;
     }
 
     public void register(CompilationUnit compilationUnit) {
-        classpathScriptTransformer.invert().register(compilationUnit);
+        extractionTransformer.register(compilationUnit);
         new TaskDefinitionScriptTransformer().register(compilationUnit);
         new FixMainScriptTransformer().register(compilationUnit); // TODO - remove this
         new StatementLabelsScriptTransformer().register(compilationUnit);
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ClasspathScriptTransformer.java
deleted file mode 100644
index efe2952..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ClasspathScriptTransformer.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.groovy.scripts.internal;
-
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.ImportNode;
-import org.codehaus.groovy.ast.MethodNode;
-import org.codehaus.groovy.ast.ModuleNode;
-import org.codehaus.groovy.ast.expr.ArgumentListExpression;
-import org.codehaus.groovy.ast.expr.ClosureExpression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.ast.stmt.ExpressionStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.codehaus.groovy.control.Phases;
-import org.codehaus.groovy.control.SourceUnit;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.groovy.scripts.Transformer;
-import org.gradle.internal.UncheckedException;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * The classpath script transformer uses Groovy's AST support to implement a two-phase
- * compilation of a script into a "class path script" and an "everything else script".
- * The classpath script can then be executed and it's results taken into account (in
- * particular, to update the classpath) before the remainder of the script is executed.
- */
-public abstract class ClasspathScriptTransformer extends AbstractScriptTransformer {
-    protected abstract String getScriptMethodName();
-
-    protected int getPhase() {
-        return Phases.CONVERSION;
-    }
-
-    public void call(SourceUnit source) throws CompilationFailedException {
-        Spec<Statement> spec = isScriptBlock();
-        filterStatements(source, spec);
-
-        // Filter imported classes which are not available yet
-
-        Iterator<ImportNode> iter = source.getAST().getImports().iterator();
-        while (iter.hasNext()) {
-            ImportNode importedClass = iter.next();
-            if (!isVisible(source, importedClass.getClassName())) {
-                try {
-                    Field field = ModuleNode.class.getDeclaredField("imports");
-                    field.setAccessible(true);
-                    Map value = (Map) field.get(source.getAST());
-                    value.remove(importedClass.getAlias());
-                } catch (Exception e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-            }
-        }
-
-        iter = source.getAST().getStaticImports().values().iterator();
-        while (iter.hasNext()) {
-            ImportNode importedClass = iter.next();
-            if (!isVisible(source, importedClass.getClassName())) {
-                iter.remove();
-            }
-        }
-
-        iter = source.getAST().getStaticStarImports().values().iterator();
-        while (iter.hasNext()) {
-            ImportNode importedClass = iter.next();
-            if (!isVisible(source, importedClass.getClassName())) {
-                iter.remove();
-            }
-        }
-
-        ClassNode scriptClass = getScriptClass(source);
-
-        // Remove all the classes other than the main class
-        Iterator<ClassNode> classes = source.getAST().getClasses().iterator();
-        while (classes.hasNext()) {
-            ClassNode classNode = classes.next();
-            if (classNode != scriptClass) {
-                classes.remove();
-            }
-        }
-
-        // Remove all the methods from the main class
-        if (scriptClass != null) {
-            for (MethodNode methodNode : new ArrayList<MethodNode>(scriptClass.getMethods())) {
-                if (!methodNode.getName().equals("run")) {
-                    removeMethod(scriptClass, methodNode);
-                }
-            }
-        }
-
-        source.getAST().getMethods().clear();
-    }
-
-    private boolean isVisible(SourceUnit source, String className) {
-        try {
-            source.getClassLoader().loadClass(className);
-            return true;
-        } catch (ClassNotFoundException e) {
-            return false;
-        }
-    }
-
-    private void filterStatements(SourceUnit source, Spec<Statement> spec) {
-        Iterator statementIterator = source.getAST().getStatementBlock().getStatements().iterator();
-        while (statementIterator.hasNext()) {
-            Statement statement = (Statement) statementIterator.next();
-            if (!spec.isSatisfiedBy(statement)) {
-                statementIterator.remove();
-            }
-        }
-    }
-
-    public Transformer invert() {
-        return new AbstractScriptTransformer() {
-            protected int getPhase() {
-                return Phases.CANONICALIZATION;
-            }
-
-            public String getId() {
-                return "no_" + ClasspathScriptTransformer.this.getId();
-            }
-
-            @Override
-            public void call(SourceUnit source) throws CompilationFailedException {
-                Spec<Statement> spec = Specs.not(isScriptBlock());
-                filterStatements(source, spec);
-            }
-        };
-    }
-
-    public Spec<Statement> isScriptBlock() {
-        return new Spec<Statement>() {
-            public boolean isSatisfiedBy(Statement statement) {
-                if (!(statement instanceof ExpressionStatement)) {
-                    return false;
-                }
-
-                ExpressionStatement expressionStatement = (ExpressionStatement) statement;
-                if (!(expressionStatement.getExpression() instanceof MethodCallExpression)) {
-                    return false;
-                }
-
-                MethodCallExpression methodCall = (MethodCallExpression) expressionStatement.getExpression();
-                if (!isMethodOnThis(methodCall, getScriptMethodName())) {
-                    return false;
-                }
-
-                if (!(methodCall.getArguments() instanceof ArgumentListExpression)) {
-                    return false;
-                }
-
-                ArgumentListExpression args = (ArgumentListExpression) methodCall.getArguments();
-                return args.getExpressions().size() == 1 && args.getExpression(0) instanceof ClosureExpression;
-            }
-        };
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
index 3182a27..3b143e6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
@@ -44,9 +44,6 @@ import java.net.URLClassLoader;
 import java.security.CodeSource;
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultScriptCompilationHandler implements ScriptCompilationHandler {
     private Logger logger = LoggerFactory.getLogger(DefaultScriptCompilationHandler.class);
     private static final String EMPTY_SCRIPT_MARKER_FILE_NAME = "emptyScript.txt";
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java
index cfe8340..2d31bc9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java
@@ -22,7 +22,10 @@ import org.gradle.cache.CacheValidator;
 import org.gradle.cache.PersistentCache;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.Transformer;
-import org.gradle.util.hash.HashUtil;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
 
 import java.io.File;
 import java.util.HashMap;
@@ -33,13 +36,16 @@ import java.util.Map;
  */
 public class FileCacheBackedScriptClassCompiler implements ScriptClassCompiler {
     private final ScriptCompilationHandler scriptCompilationHandler;
+    private ProgressLoggerFactory progressLoggerFactory;
     private final CacheRepository cacheRepository;
     private final CacheValidator validator;
+    private final CompositeStoppable caches = new CompositeStoppable();
 
-    public FileCacheBackedScriptClassCompiler(CacheRepository cacheRepository, CacheValidator validator, ScriptCompilationHandler scriptCompilationHandler) {
+    public FileCacheBackedScriptClassCompiler(CacheRepository cacheRepository, CacheValidator validator, ScriptCompilationHandler scriptCompilationHandler, ProgressLoggerFactory progressLoggerFactory) {
         this.cacheRepository = cacheRepository;
         this.validator = validator;
         this.scriptCompilationHandler = scriptCompilationHandler;
+        this.progressLoggerFactory = progressLoggerFactory;
     }
 
     public <T extends Script> Class<? extends T> compile(ScriptSource source, ClassLoader classLoader, Transformer transformer, Class<T> scriptBaseClass) {
@@ -52,12 +58,21 @@ public class FileCacheBackedScriptClassCompiler implements ScriptClassCompiler {
                 .withProperties(properties)
                 .withValidator(validator)
                 .withDisplayName(String.format("%s class cache for %s", transformer.getId(), source.getDisplayName()))
-                .withInitializer(new CacheInitializer(source, classLoader, transformer, scriptBaseClass)).open();
+                .withInitializer(new ProgressReportingInitializer(progressLoggerFactory, new CacheInitializer(source, classLoader, transformer, scriptBaseClass)))
+                .open();
+
+        // This isn't quite right. The cache will be closed at the end of the build, releasing the shared lock on the classes. Instead, the cache for a script should be
+        // closed once we no longer require the script classes. This may be earlier than the end of the current build, or it may used across multiple builds
+        caches.add(cache);
 
         File classesDir = classesDir(cache);
         return scriptCompilationHandler.loadFromDir(source, classLoader, classesDir, scriptBaseClass);
     }
 
+    public void close() {
+        caches.stop();
+    }
+
     private File classesDir(PersistentCache cache) {
         return new File(cache.getBaseDir(), "classes");
     }
@@ -80,4 +95,24 @@ public class FileCacheBackedScriptClassCompiler implements ScriptClassCompiler {
             scriptCompilationHandler.compileToDir(source, classLoader, classesDir, transformer, scriptBaseClass);
         }
     }
+
+    static class ProgressReportingInitializer implements Action<PersistentCache> {
+        private ProgressLoggerFactory progressLoggerFactory;
+        private Action<? super PersistentCache> delegate;
+
+        public ProgressReportingInitializer(ProgressLoggerFactory progressLoggerFactory, Action<PersistentCache> delegate) {
+            this.progressLoggerFactory = progressLoggerFactory;
+            this.delegate = delegate;
+        }
+
+        public void execute(PersistentCache cache) {
+            ProgressLogger op = progressLoggerFactory.newOperation(FileCacheBackedScriptClassCompiler.class)
+                    .start("Compile script into cache", "Compiling script into cache");
+            try {
+                delegate.execute(cache);
+            } finally {
+                op.completed();
+            }
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FilteredTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FilteredTransformer.java
new file mode 100644
index 0000000..fe07df9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FilteredTransformer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.specs.Spec;
+
+public interface FilteredTransformer<R, T> {
+
+    Spec<T> getSpec();
+
+    Transformer<R, T> getTransformer();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FilteringStatementTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FilteringStatementTransformer.java
new file mode 100644
index 0000000..615a445
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FilteringStatementTransformer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.gradle.api.specs.Spec;
+
+public class FilteringStatementTransformer implements StatementTransformer {
+
+    private final Spec<? super Statement> spec;
+
+    public FilteringStatementTransformer(Spec<? super Statement> spec) {
+        this.spec = spec;
+    }
+
+    public Statement transform(SourceUnit sourceUnit, Statement statement) {
+        if (spec.isSatisfiedBy(statement)) {
+            return statement;
+        } else {
+            return null;
+        }
+    }
+
+    public Spec<? super Statement> getSpec() {
+        return spec;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java
index 84afa71..d058272 100755
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java
@@ -37,13 +37,13 @@ public class FixMainScriptTransformer extends AbstractScriptTransformer {
 
     @Override
     public void call(SourceUnit source) throws CompilationFailedException {
-        ClassNode scriptClass = getScriptClass(source);
+        ClassNode scriptClass = AstUtils.getScriptClass(source);
         if (scriptClass == null) {
             return;
         }
         for (MethodNode methodNode : scriptClass.getMethods()) {
             if (methodNode.getName().equals("main")) {
-                removeMethod(scriptClass, methodNode);
+                AstUtils.removeMethod(scriptClass, methodNode);
                 break;
             }
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/PluginsAndBuildscriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/PluginsAndBuildscriptTransformer.java
new file mode 100644
index 0000000..6b6d733
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/PluginsAndBuildscriptTransformer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.gradle.api.specs.Spec;
+import org.gradle.groovy.scripts.DefaultScript;
+import org.gradle.plugin.PluginHandler;
+
+public class PluginsAndBuildscriptTransformer implements StatementTransformer {
+
+    private static final String PLUGINS = "plugins";
+    private static final ScriptBlockToServiceConfigurationTransformer PLUGIN_BLOCK_TRANSFORMER = new ScriptBlockToServiceConfigurationTransformer(DefaultScript.SCRIPT_SERVICES_PROPERTY, PluginHandler.class);
+
+    private final String classpathBlockName;
+    private boolean seenNonClasspathStatement;
+    private boolean seenPluginsBlock;
+
+    public PluginsAndBuildscriptTransformer(String classpathBlockName) {
+        this.classpathBlockName = classpathBlockName;
+    }
+
+    public Statement transform(SourceUnit sourceUnit, Statement statement) {
+        ScriptBlock scriptBlock = detectScriptBlock(statement);
+        if (scriptBlock == null) {
+            seenNonClasspathStatement = true;
+            return null;
+        } else {
+            if (scriptBlock.getName().equals(PLUGINS)) {
+                seenPluginsBlock = true;
+                if (seenNonClasspathStatement) {
+                    String message = String.format(
+                            "only %s {} and and other %s {} script blocks are allowed before %s {} blocks, no other statements are allowed",
+                            classpathBlockName, PLUGINS, PLUGINS
+                    );
+                    sourceUnit.getErrorCollector().addError(
+                            new SyntaxException(message, statement.getLineNumber(), statement.getColumnNumber()),
+                            sourceUnit
+                    );
+                    return statement;
+                } else {
+                    return PLUGIN_BLOCK_TRANSFORMER.transform(scriptBlock);
+                }
+            } else {
+                if (seenPluginsBlock) {
+                    String message = String.format(
+                            "all %s {} blocks must appear before any %s {} blocks in the script",
+                            classpathBlockName, PLUGINS
+                    );
+                    sourceUnit.getErrorCollector().addError(
+                            new SyntaxException(message, statement.getLineNumber(), statement.getColumnNumber()),
+                            sourceUnit
+                    );
+                }
+                return statement; // don't transform classpathBlockName
+            }
+        }
+    }
+
+    public Spec<Statement> getSpec() {
+        return new Spec<Statement>() {
+            public boolean isSatisfiedBy(Statement statement) {
+                return detectScriptBlock(statement) != null;
+            }
+        };
+    }
+
+    // returns null if the statement is not a script block
+    private ScriptBlock detectScriptBlock(Statement statement) {
+        if (!(statement instanceof ExpressionStatement)) {
+            return null;
+        }
+
+        ExpressionStatement expressionStatement = (ExpressionStatement) statement;
+        if (!(expressionStatement.getExpression() instanceof MethodCallExpression)) {
+            return null;
+        }
+
+        MethodCallExpression methodCall = (MethodCallExpression) expressionStatement.getExpression();
+        if (!AstUtils.targetIsThis(methodCall)) {
+            return null;
+        }
+
+        if (!(methodCall.getMethod() instanceof ConstantExpression)) {
+            return null;
+        }
+
+        String methodName = methodCall.getMethod().getText();
+
+        if (methodName.equals(PLUGINS) || methodName.equals(classpathBlockName)) {
+            if (!(methodCall.getArguments() instanceof ArgumentListExpression)) {
+                return null;
+            }
+
+            ArgumentListExpression args = (ArgumentListExpression) methodCall.getArguments();
+            if (args.getExpressions().size() == 1 && args.getExpression(0) instanceof ClosureExpression) {
+                return new ScriptBlock(methodName, (ClosureExpression) args.getExpression(0));
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptBlock.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptBlock.java
new file mode 100644
index 0000000..3ac1f4d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptBlock.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+
+class ScriptBlock {
+    private final String name;
+    private final ClosureExpression closureExpression;
+
+    ScriptBlock(String name, ClosureExpression closureExpression) {
+        this.name = name;
+        this.closureExpression = closureExpression;
+    }
+
+    String getName() {
+        return name;
+    }
+
+    ClosureExpression getClosureExpression() {
+        return closureExpression;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptBlockToServiceConfigurationTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptBlockToServiceConfigurationTransformer.java
new file mode 100644
index 0000000..9be89dd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptBlockToServiceConfigurationTransformer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.gradle.api.Transformer;
+
+public class ScriptBlockToServiceConfigurationTransformer implements Transformer<Statement, ScriptBlock> {
+
+    private final String servicesFieldName;
+    private final Class<?> serviceClass;
+
+    public ScriptBlockToServiceConfigurationTransformer(String servicesFieldName, Class<?> serviceClass) {
+        this.servicesFieldName = servicesFieldName;
+        this.serviceClass = serviceClass;
+    }
+
+    public Statement transform(ScriptBlock scriptBlock) {
+        Expression closureArg = scriptBlock.getClosureExpression();
+
+        PropertyExpression servicesProperty = new PropertyExpression(VariableExpression.THIS_EXPRESSION, servicesFieldName);
+        MethodCallExpression getServiceMethodCall = new MethodCallExpression(servicesProperty, "get",
+                new ArgumentListExpression(
+                        new ClassExpression(new ClassNode(serviceClass))
+                )
+        );
+
+        // Remove access to any surrounding context
+        Expression hydrateMethodCall = new MethodCallExpression(closureArg, "rehydrate", new ArgumentListExpression(
+                ConstantExpression.NULL, getServiceMethodCall, getServiceMethodCall
+        ));
+
+        Expression closureCall = new MethodCallExpression(hydrateMethodCall, "call", ArgumentListExpression.EMPTY_ARGUMENTS);
+
+        return new ExpressionStatement(closureCall);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java
index afd9584..3642cfc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java
@@ -21,9 +21,6 @@ import org.gradle.groovy.scripts.Transformer;
 
 import java.io.File;
 
-/**
- * @author Hans Dockter
- */
 public interface ScriptCompilationHandler {
     void compileToDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir, Transformer transformer,
                       Class<? extends Script> scriptBaseClass);
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementExtractingScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementExtractingScriptTransformer.java
new file mode 100644
index 0000000..36a75c3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementExtractingScriptTransformer.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ImportNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.control.SourceUnit;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.groovy.scripts.Transformer;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Excludes everything from a script except statements that are satisfied by a given predicate, and imports for accessible classes. <p> *All* other kinds of constructs are filtered, including:
+ * classes, methods etc.
+ */
+public class StatementExtractingScriptTransformer extends AbstractScriptTransformer {
+
+    private final String id;
+    private final StatementTransformer transformer;
+
+    public StatementExtractingScriptTransformer(String id, StatementTransformer transformer) {
+        this.id = id;
+        this.transformer = transformer;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    protected int getPhase() {
+        return Phases.CONVERSION;
+    }
+
+    public void call(SourceUnit source) throws CompilationFailedException {
+        AstUtils.filterAndTransformStatements(source, transformer);
+
+        // Filter imported classes which are not available yet
+
+        Iterator<ImportNode> iter = source.getAST().getImports().iterator();
+        while (iter.hasNext()) {
+            ImportNode importedClass = iter.next();
+            if (!AstUtils.isVisible(source, importedClass.getClassName())) {
+                try {
+                    Field field = ModuleNode.class.getDeclaredField("imports");
+                    field.setAccessible(true);
+                    Map value = (Map) field.get(source.getAST());
+                    value.remove(importedClass.getAlias());
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        }
+
+        iter = source.getAST().getStaticImports().values().iterator();
+        while (iter.hasNext()) {
+            ImportNode importedClass = iter.next();
+            if (!AstUtils.isVisible(source, importedClass.getClassName())) {
+                iter.remove();
+            }
+        }
+
+        iter = source.getAST().getStaticStarImports().values().iterator();
+        while (iter.hasNext()) {
+            ImportNode importedClass = iter.next();
+            if (!AstUtils.isVisible(source, importedClass.getClassName())) {
+                iter.remove();
+            }
+        }
+
+        ClassNode scriptClass = AstUtils.getScriptClass(source);
+
+        // Remove all the classes other than the main class
+        Iterator<ClassNode> classes = source.getAST().getClasses().iterator();
+        while (classes.hasNext()) {
+            ClassNode classNode = classes.next();
+            if (classNode != scriptClass) {
+                classes.remove();
+            }
+        }
+
+        // Remove all the methods from the main class
+        if (scriptClass != null) {
+            for (MethodNode methodNode : new ArrayList<MethodNode>(scriptClass.getMethods())) {
+                if (!methodNode.getName().equals("run")) {
+                    AstUtils.removeMethod(scriptClass, methodNode);
+                }
+            }
+        }
+
+        source.getAST().getMethods().clear();
+    }
+
+    public Transformer invert() {
+        return new Inverse("no_" + StatementExtractingScriptTransformer.this.getId(), Specs.not(transformer.getSpec()));
+    }
+
+    private static class Inverse extends AbstractScriptTransformer {
+        private final String id;
+        private final Spec<? super Statement> spec;
+
+        private Inverse(String id, Spec<? super Statement> spec) {
+            this.id = id;
+            this.spec = spec;
+        }
+
+        protected int getPhase() {
+            return Phases.CANONICALIZATION;
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        @Override
+        public void call(SourceUnit source) throws CompilationFailedException {
+            AstUtils.filterAndTransformStatements(source, new FilteringStatementTransformer(spec));
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java
index 58bf036..c6631f4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java
@@ -43,7 +43,7 @@ public class StatementLabelsScriptTransformer extends AbstractScriptTransformer
         final List<Statement> logStats = Lists.newArrayList();
 
         // currently we only look in script code; could extend this to build script classes
-        visitScriptCode(source, new ClassCodeVisitorSupport() {
+        AstUtils.visitScriptCode(source, new ClassCodeVisitorSupport() {
             @Override
             protected SourceUnit getSourceUnit() {
                 return source;
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementTransformer.java
new file mode 100644
index 0000000..854aef6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementTransformer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.groovy.scripts.internal;
+
+
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.gradle.api.specs.Spec;
+
+public interface StatementTransformer {
+
+    // Return null to remove the statement
+    Statement transform(SourceUnit sourceUnit, Statement statement);
+
+    Spec<? super Statement> getSpec();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java
index 9644361..ff72f40 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java
@@ -36,7 +36,7 @@ public class TaskDefinitionScriptTransformer extends AbstractScriptTransformer {
     }
 
     public void call(SourceUnit source) throws CompilationFailedException {
-        visitScriptCode(source, new TaskDefinitionTransformer());
+        AstUtils.visitScriptCode(source, new TaskDefinitionTransformer());
     }
 
     private class TaskDefinitionTransformer extends CodeVisitorSupport {
@@ -129,7 +129,7 @@ public class TaskDefinitionScriptTransformer extends AbstractScriptTransformer {
         }
 
         private boolean maybeTransformNestedMethodCall(MethodCallExpression nestedMethod, MethodCallExpression target) {
-            if (!(isTaskIdentifier(nestedMethod.getMethod()) && targetIsThis(nestedMethod))) {
+            if (!(isTaskIdentifier(nestedMethod.getMethod()) && AstUtils.targetIsThis(nestedMethod))) {
                 return false;
             }
 
@@ -171,7 +171,7 @@ public class TaskDefinitionScriptTransformer extends AbstractScriptTransformer {
         }
 
         private boolean isInstanceMethod(MethodCallExpression call, String name) {
-            boolean isTaskMethod = isMethodOnThis(call, name);
+            boolean isTaskMethod = AstUtils.isMethodOnThis(call, name);
             if (!isTaskMethod) {
                 return false;
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.java
index ba20320..adf92f9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.api.InvalidUserDataException;
 
@@ -23,7 +23,7 @@ import java.util.List;
 import java.util.ArrayList;
 
 public abstract class AbstractProjectSpec implements ProjectSpec {
-    public boolean containsProject(IProjectRegistry<?> registry) {
+    public boolean containsProject(ProjectRegistry<?> registry) {
         checkPreconditions(registry);
         for (ProjectIdentifier project : registry.getAllProjects()) {
             if (select(project)) {
@@ -33,7 +33,7 @@ public abstract class AbstractProjectSpec implements ProjectSpec {
         return false;
     }
 
-    public <T extends ProjectIdentifier> T selectProject(IProjectRegistry<? extends T> registry) {
+    public <T extends ProjectIdentifier> T selectProject(ProjectRegistry<? extends T> registry) {
         checkPreconditions(registry);
         List<T> matches = new ArrayList<T>();
         for (T project : registry.getAllProjects()) {
@@ -50,7 +50,7 @@ public abstract class AbstractProjectSpec implements ProjectSpec {
         return matches.get(0);
     }
 
-    protected void checkPreconditions(IProjectRegistry<?> registry) {
+    protected void checkPreconditions(ProjectRegistry<?> registry) {
     }
 
     protected abstract String formatMultipleMatchesMessage(Iterable<? extends ProjectIdentifier> matches);
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 1aa27da..e62fd90 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
@@ -21,44 +21,56 @@ import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.initialization.Settings;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.project.AbstractPluginAware;
+import org.gradle.api.internal.project.ProjectRegistry;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.configuration.ScriptPluginFactory;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 
 import java.io.File;
-import java.net.URLClassLoader;
 
-/**
- * @author Hans Dockter
- */
-public class BaseSettings implements SettingsInternal {
+public class BaseSettings extends AbstractPluginAware implements SettingsInternal {
     public static final String DEFAULT_BUILD_SRC_DIR = "buildSrc";
 
     private ScriptSource settingsScript;
 
     private StartParameter startParameter;
 
-    private URLClassLoader classloader;
-
     private File settingsDir;
 
     private DefaultProjectDescriptor rootProjectDescriptor;
 
     private GradleInternal gradle;
-    private IProjectDescriptorRegistry projectDescriptorRegistry;
 
-    protected BaseSettings() {
-    }
+    private ProjectDescriptorRegistry projectDescriptorRegistry;
+
+    private PluginContainer plugins;
+
+    private FileResolver fileResolver;
+
+    private final ScriptPluginFactory scriptPluginFactory;
+    private final ScriptHandlerFactory scriptHandlerFactory;
+    private final ClassLoaderScope classLoaderScope;
 
-    public BaseSettings(GradleInternal gradle,
-                        IProjectDescriptorRegistry projectDescriptorRegistry,
-                        URLClassLoader classloader, File settingsDir, ScriptSource settingsScript,
+    public BaseSettings(ServiceRegistryFactory serviceRegistryFactory, GradleInternal gradle,
+                        ClassLoaderScope classLoaderScope, File settingsDir, ScriptSource settingsScript,
                         StartParameter startParameter) {
         this.gradle = gradle;
-        this.projectDescriptorRegistry = projectDescriptorRegistry;
         this.settingsDir = settingsDir;
         this.settingsScript = settingsScript;
         this.startParameter = startParameter;
-        this.classloader = classloader;
+        this.classLoaderScope = classLoaderScope;
+        ServiceRegistry services = serviceRegistryFactory.createFor(this);
+        this.plugins = services.get(PluginContainer.class);
+        this.fileResolver = services.get(FileResolver.class);
+        this.scriptPluginFactory = services.get(ScriptPluginFactory.class);
+        this.scriptHandlerFactory = services.get(ScriptHandlerFactory.class);
+        this.projectDescriptorRegistry = services.get(ProjectDescriptorRegistry.class);
         rootProjectDescriptor = createProjectDescriptor(null, settingsDir.getName(), settingsDir);
     }
 
@@ -76,7 +88,7 @@ public class BaseSettings implements SettingsInternal {
     }
 
     public DefaultProjectDescriptor createProjectDescriptor(DefaultProjectDescriptor parent, String name, File dir) {
-        return new DefaultProjectDescriptor(parent, name, dir, projectDescriptorRegistry);
+        return new DefaultProjectDescriptor(parent, name, dir, projectDescriptorRegistry, fileResolver);
     }
 
     public DefaultProjectDescriptor findProject(String path) {
@@ -134,10 +146,6 @@ public class BaseSettings implements SettingsInternal {
         return projectPath;
     }
 
-    public URLClassLoader getClassLoader() {
-        return classloader;
-    }
-
     public ProjectDescriptor getRootProject() {
         return rootProjectDescriptor;
     }
@@ -174,15 +182,40 @@ public class BaseSettings implements SettingsInternal {
         this.settingsScript = settingsScript;
     }
 
-    public IProjectDescriptorRegistry getProjectDescriptorRegistry() {
+    public ProjectDescriptorRegistry getProjectDescriptorRegistry() {
         return projectDescriptorRegistry;
     }
 
-    public void setProjectDescriptorRegistry(IProjectDescriptorRegistry projectDescriptorRegistry) {
+    public void setProjectDescriptorRegistry(ProjectDescriptorRegistry projectDescriptorRegistry) {
         this.projectDescriptorRegistry = projectDescriptorRegistry;
     }
 
-    public IProjectRegistry<DefaultProjectDescriptor> getProjectRegistry() {
+    public ProjectRegistry<DefaultProjectDescriptor> getProjectRegistry() {
         return projectDescriptorRegistry;
     }
+
+    public PluginContainer getPlugins() {
+        return plugins;
+    }
+
+
+    @Override
+    protected FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    @Override
+    protected ScriptPluginFactory getScriptPluginFactory() {
+        return scriptPluginFactory;
+    }
+
+    @Override
+    protected ScriptHandlerFactory getScriptHandlerFactory() {
+        return scriptHandlerFactory;
+    }
+
+    @Override
+    public ClassLoaderScope getClassLoaderScope() {
+        return classLoaderScope;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildAction.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildAction.java
new file mode 100644
index 0000000..258e0ee
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildAction.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.initialization;
+
+/**
+ * An object that performs some action with a {@link BuildController} and produces a “result” object (e.g. the output).
+ * <p>
+ * Implementations of this are typically composed to bootstrap a build in a certain environment.
+ * <p>
+ */
+public interface BuildAction<T> {
+    /**
+     * Executes the action with the given controller.
+     * <p>
+     * The state of the build is not defined as part of this contract, it is highly context specific.
+     */
+    T run(BuildController buildController);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildController.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildController.java
new file mode 100644
index 0000000..f74f6ea
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildController.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.initialization;
+
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+
+/**
+ * This is intended to eventually replace {@link GradleLauncher} internally. It's pretty rough at the moment.
+ */
+public interface BuildController {
+    /**
+     * Specifies the start parameter to use to run the build. Cannot be used after the launcher has been created.
+     */
+    void setStartParameter(StartParameter startParameter);
+
+    /**
+     * Returns the launcher to use to run the build.
+     */
+    GradleLauncher getLauncher();
+
+    /**
+     * Runs the build.
+     */
+    void run();
+
+    /**
+     * Configures the build but does not run any tasks.
+     */
+    void configure();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
index e291545..3058929 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
@@ -18,7 +18,7 @@ package org.gradle.initialization;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
 import org.gradle.api.internal.project.ProjectIdentifier;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.api.InvalidUserDataException;
 
 import java.io.File;
@@ -48,7 +48,7 @@ public class BuildFileProjectSpec extends AbstractProjectSpec implements Seriali
     }
 
     @Override
-    protected void checkPreconditions(IProjectRegistry<?> registry) {
+    protected void checkPreconditions(ProjectRegistry<?> registry) {
         if (!buildFile.exists()) {
             throw new InvalidUserDataException(String.format("Build file '%s' does not exist.", buildFile));
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java
index 9f32a3c..a8d0d81 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java
@@ -16,17 +16,30 @@
 
 package org.gradle.initialization;
 
+import org.gradle.StartParameter;
+import org.gradle.internal.SystemProperties;
+
 import java.io.File;
 
-/**
- * by Szczepan Faber, created at: 2/18/13
- */
+import static org.gradle.util.GFileUtils.canonicalise;
+
 public class BuildLayoutParameters {
 
-    private Boolean searchUpwards;
-    private File projectDir;
+    private boolean searchUpwards = true;
+    private File projectDir = canonicalise(SystemProperties.getCurrentDir());
     private File gradleUserHomeDir;
 
+    public BuildLayoutParameters() {
+        String gradleUserHome = System.getProperty(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY);
+        if (gradleUserHome == null) {
+            gradleUserHome = System.getenv("GRADLE_USER_HOME");
+            if (gradleUserHome == null) {
+                gradleUserHome = StartParameter.DEFAULT_GRADLE_USER_HOME.getAbsolutePath();
+            }
+        }
+        gradleUserHomeDir = canonicalise(new File(gradleUserHome));
+    }
+
     public BuildLayoutParameters setSearchUpwards(boolean searchUpwards) {
         this.searchUpwards = searchUpwards;
         return this;
@@ -50,7 +63,7 @@ public class BuildLayoutParameters {
         return gradleUserHomeDir;
     }
 
-    public Boolean getSearchUpwards() {
+    public boolean getSearchUpwards() {
         return searchUpwards;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java
index 08e3719..94195b4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java
@@ -17,11 +17,12 @@ package org.gradle.initialization;
 
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 
 public interface BuildLoader {
     /**
      * Creates the {@link org.gradle.api.internal.GradleInternal} and {@link org.gradle.api.internal.project.ProjectInternal} instances for the given root project,
      * ready for the projects to be evaluated.
      */
-    void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle);
+    void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle, ClassLoaderScope classLoaderScope);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildProgressLogger.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildProgressLogger.java
deleted file mode 100644
index c619fb1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildProgressLogger.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.initialization;
-
-import org.gradle.BuildAdapter;
-import org.gradle.BuildResult;
-import org.gradle.api.execution.TaskExecutionGraph;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.logging.ProgressLogger;
-import org.gradle.logging.ProgressLoggerFactory;
-
-class BuildProgressLogger extends BuildAdapter implements TaskExecutionGraphListener {
-    private ProgressLogger progressLogger;
-    private final ProgressLoggerFactory progressLoggerFactory;
-    private Gradle gradle;
-
-    public BuildProgressLogger(ProgressLoggerFactory progressLoggerFactory) {
-        this.progressLoggerFactory = progressLoggerFactory;
-    }
-
-    @Override
-    public void buildStarted(Gradle gradle) {
-        if (gradle.getParent() == null) {
-            progressLogger = progressLoggerFactory.newOperation(BuildProgressLogger.class);
-            progressLogger.setDescription("Configure projects");
-            progressLogger.setShortDescription("Loading");
-            progressLogger.started();
-            this.gradle = gradle;
-        }
-    }
-
-    public void graphPopulated(TaskExecutionGraph graph) {
-        if (graph == gradle.getTaskGraph()) {
-            progressLogger.completed();
-            progressLogger = progressLoggerFactory.newOperation(BuildProgressLogger.class);
-            progressLogger.setDescription("Execute tasks");
-            progressLogger.setShortDescription("Building");
-            progressLogger.started();
-        }
-    }
-
-    @Override
-    public void buildFinished(BuildResult result) {
-        if (result.getGradle() == gradle) {
-            progressLogger.completed();
-            progressLogger = null;
-            gradle = null;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
deleted file mode 100644
index d118ae6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.BuildAdapter;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.plugins.EmbeddableJavaProject;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.cache.CacheBuilder;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.PersistentCache;
-import org.gradle.cache.internal.FileLockManager;
-import org.gradle.internal.Factory;
-import org.gradle.internal.classpath.ClassPath;
-import org.gradle.internal.classpath.DefaultClassPath;
-import org.gradle.util.WrapUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class BuildSourceBuilder {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BuildSourceBuilder.class);
-
-    private final GradleLauncherFactory gradleLauncherFactory;
-    private final ClassLoaderRegistry classLoaderRegistry;
-    private final CacheRepository cacheRepository;
-
-    private static final String DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE = "defaultBuildSourceScript.txt";
-
-    public BuildSourceBuilder(GradleLauncherFactory gradleLauncherFactory, ClassLoaderRegistry classLoaderRegistry, CacheRepository cacheRepository) {
-        this.gradleLauncherFactory = gradleLauncherFactory;
-        this.classLoaderRegistry = classLoaderRegistry;
-        this.cacheRepository = cacheRepository;
-    }
-
-    public URLClassLoader buildAndCreateClassLoader(StartParameter startParameter) {
-        ClassPath classpath = createBuildSourceClasspath(startParameter);
-        return new URLClassLoader(classpath.getAsURLArray(), classLoaderRegistry.getRootClassLoader());
-    }
-
-    private ClassPath createBuildSourceClasspath(StartParameter startParameter) {
-        assert startParameter.getCurrentDir() != null && startParameter.getBuildFile() == null;
-
-        LOGGER.debug("Starting to build the build sources.");
-        if (!startParameter.getCurrentDir().isDirectory()) {
-            LOGGER.debug("Gradle source dir does not exist. We leave.");
-            return new DefaultClassPath();
-        }
-        LOGGER.info("================================================" + " Start building buildSrc");
-
-        // If we were not the most recent version of Gradle to build the buildSrc dir, then do a clean build
-        // Otherwise, just to a regular build
-        final PersistentCache buildSrcCache = cacheRepository.
-                cache("buildSrc").
-                withLockMode(FileLockManager.LockMode.None).
-                forObject(startParameter.getCurrentDir()).
-                withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange).
-                open();
-
-        GradleLauncher gradleLauncher = buildGradleLauncher(startParameter);
-        return buildSrcCache.useCache("rebuild buildSrc", new BuildSrcUpdateFactory(buildSrcCache, gradleLauncher));
-    }
-
-    private GradleLauncher buildGradleLauncher(StartParameter startParameter) {
-        final StartParameter startParameterArg = startParameter.newInstance();
-        startParameterArg.setProjectProperties(startParameter.getProjectProperties());
-        startParameterArg.setSearchUpwards(false);
-        startParameterArg.setProfile(startParameter.isProfile());
-        return gradleLauncherFactory.newInstance(startParameterArg);
-    }
-
-    static URL getDefaultScript() {
-        return BuildSourceBuilder.class.getResource(DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE);
-    }
-
-    private static class BuildSrcBuildListener extends BuildAdapter implements ModelConfigurationListener {
-        private EmbeddableJavaProject projectInfo;
-        private Set<File> classpath;
-        private final boolean rebuild;
-
-        public BuildSrcBuildListener(boolean rebuild) {
-            this.rebuild = rebuild;
-        }
-
-        @Override
-        public void projectsLoaded(Gradle gradle) {
-            gradle.getRootProject().apply(WrapUtil.toMap("from", getDefaultScript()));
-        }
-
-        public Collection<File> getRuntimeClasspath() {
-            return classpath;
-        }
-
-        public void onConfigure(GradleInternal gradle) {
-            projectInfo = gradle.getRootProject().getConvention().getPlugin(EmbeddableJavaProject.class);
-            gradle.getStartParameter().setTaskNames(rebuild ? projectInfo.getRebuildTasks() : projectInfo.getBuildTasks());
-            classpath = projectInfo.getRuntimeClasspath().getFiles();
-        }
-    }
-
-    private static class BuildSrcUpdateFactory implements Factory<DefaultClassPath> {
-        private final PersistentCache cache;
-        private final GradleLauncher gradleLauncher;
-
-        public BuildSrcUpdateFactory(PersistentCache cache, GradleLauncher gradleLauncher) {
-            this.cache = cache;
-            this.gradleLauncher = gradleLauncher;
-        }
-
-        public DefaultClassPath create() {
-            File markerFile = new File(cache.getBaseDir(), "built.bin");
-            final boolean rebuild = !markerFile.exists();
-
-            BuildSrcBuildListener listener = new BuildSrcBuildListener(rebuild);
-            gradleLauncher.addListener(listener);
-            gradleLauncher.run().rethrowFailure();
-
-            Collection<File> classpath = listener.getRuntimeClasspath();
-            LOGGER.debug("Gradle source classpath is: {}", classpath);
-            LOGGER.info("================================================" + " Finished building buildSrc");
-            try {
-                markerFile.createNewFile();
-            } catch (IOException e) {
-                throw new UncheckedIOException(e);
-            }
-            return new DefaultClassPath(classpath);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java
index bf1ca5a..e638ee6 100755
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java
@@ -15,13 +15,16 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.util.MultiParentClassLoader;
-
 public interface ClassLoaderRegistry {
     /**
-     * Returns the root class loader shared by all builds.
+     * Returns the root class loader shared by all builds. This class loader exposes the Gradle API and built-in plugins.
+     */
+    ClassLoader getGradleApiClassLoader();
+
+    /**
+     * Returns the class loader for the Gradle runtime.
      */
-    ClassLoader getRootClassLoader();
+    ClassLoader getRuntimeClassLoader();
 
     /**
      * Returns the class loader for the coreImpl project.
@@ -32,9 +35,4 @@ public interface ClassLoaderRegistry {
      * Returns the class loader for the plugins.
      */
     ClassLoader getPluginsClassLoader();
-
-    /**
-     * Creates the script class loader for a build.
-     */
-    MultiParentClassLoader createScriptClassLoader();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
index eac333d..93a2fcd 100755
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
@@ -17,27 +17,20 @@
 package org.gradle.initialization;
 
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.internal.classloader.*;
 import org.gradle.internal.classpath.ClassPath;
 import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.internal.jvm.Jvm;
-import org.gradle.util.*;
 
 import java.io.File;
 import java.net.URLClassLoader;
 
-public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
-    private final FilteringClassLoader rootClassLoader;
+public class DefaultClassLoaderRegistry implements ClassLoaderRegistry, JdkToolsInitializer {
+    private final ClassLoader rootClassLoader;
     private final ClassLoader coreImplClassLoader;
     private final ClassLoader pluginsClassLoader;
 
     public DefaultClassLoaderRegistry(ClassPathRegistry classPathRegistry, ClassLoaderFactory classLoaderFactory) {
-        // Add in tools.jar to the systemClassloader parent
-        File toolsJar = Jvm.current().getToolsJar();
-        if (toolsJar != null) {
-            final ClassLoader systemClassLoaderParent = ClassLoader.getSystemClassLoader().getParent();
-            ClasspathUtil.addUrl((URLClassLoader) systemClassLoaderParent, new DefaultClassPath(toolsJar).getAsURLs());
-        }
-
         ClassLoader runtimeClassLoader = getClass().getClassLoader();
 
         // Core impl
@@ -46,10 +39,10 @@ public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
 
         // Add in libs for plugins
         ClassPath pluginsClassPath = classPathRegistry.getClassPath("GRADLE_PLUGINS");
-        MultiParentClassLoader pluginsImports = new MultiParentClassLoader(runtimeClassLoader, coreImplClassLoader);
+        ClassLoader pluginsImports = new CachingClassLoader(new MultiParentClassLoader(runtimeClassLoader, coreImplClassLoader));
         pluginsClassLoader = new MutableURLClassLoader(pluginsImports, pluginsClassPath);
 
-        rootClassLoader = classLoaderFactory.createFilteringClassLoader(pluginsClassLoader);
+        FilteringClassLoader rootClassLoader = classLoaderFactory.createFilteringClassLoader(pluginsClassLoader);
         rootClassLoader.allowPackage("org.gradle");
         rootClassLoader.allowResources("META-INF/gradle-plugins");
         rootClassLoader.allowPackage("org.apache.tools.ant");
@@ -61,9 +54,24 @@ public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
         rootClassLoader.allowPackage("org.apache.commons.logging");
         rootClassLoader.allowPackage("org.apache.log4j");
         rootClassLoader.allowPackage("javax.inject");
+
+        this.rootClassLoader = new CachingClassLoader(rootClassLoader);
+    }
+
+    public void initializeJdkTools() {
+        // Add in tools.jar to the systemClassloader parent
+        File toolsJar = Jvm.current().getToolsJar();
+        if (toolsJar != null) {
+            final ClassLoader systemClassLoaderParent = ClassLoader.getSystemClassLoader().getParent();
+            ClasspathUtil.addUrl((URLClassLoader) systemClassLoaderParent, new DefaultClassPath(toolsJar).getAsURLs());
+        }
+    }
+
+    public ClassLoader getRuntimeClassLoader() {
+        return getClass().getClassLoader();
     }
 
-    public ClassLoader getRootClassLoader() {
+    public ClassLoader getGradleApiClassLoader() {
         return rootClassLoader;
     }
 
@@ -74,8 +82,4 @@ public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
     public ClassLoader getPluginsClassLoader() {
         return pluginsClassLoader;
     }
-
-    public MultiParentClassLoader createScriptClassLoader() {
-        return new MultiParentClassLoader(rootClassLoader);
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
index 0be7a9a..0b2b666 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -19,18 +19,18 @@ import org.gradle.CacheUsage;
 import org.gradle.RefreshOptions;
 import org.gradle.StartParameter;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.file.BaseDirFileResolver;
+import org.gradle.api.internal.file.DefaultFileLookup;
+import org.gradle.api.internal.file.FileLookup;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.cli.*;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.services.FileSystems;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
 
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
+import static org.gradle.StartParameter.GRADLE_USER_HOME_PROPERTY_KEY;
+
 public class DefaultCommandLineConverter extends AbstractCommandLineConverter<StartParameter> {
     private static final String NO_PROJECT_DEPENDENCY_REBUILD = "a";
     private static final String BUILD_FILE = "b";
@@ -57,7 +57,13 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
     private final CommandLineConverter<LoggingConfiguration> loggingConfigurationCommandLineConverter = new LoggingCommandLineConverter();
     private final SystemPropertiesCommandLineConverter systemPropertiesCommandLineConverter = new SystemPropertiesCommandLineConverter();
     private final ProjectPropertiesCommandLineConverter projectPropertiesCommandLineConverter = new ProjectPropertiesCommandLineConverter();
-    private final LayoutCommandLineConverter layoutCommandLineConverter = new LayoutCommandLineConverter();
+    private final LayoutCommandLineConverter layoutCommandLineConverter;
+    private final FileLookup fileLookup;
+
+    public DefaultCommandLineConverter() {
+        this.fileLookup = new DefaultFileLookup(FileSystems.getDefault());
+        layoutCommandLineConverter = new LayoutCommandLineConverter(fileLookup);
+    }
 
     public void configure(CommandLineParser parser) {
         loggingConfigurationCommandLineConverter.configure(parser);
@@ -95,7 +101,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
 
     public StartParameter convert(final ParsedCommandLine options, final StartParameter startParameter) throws CommandLineArgumentException {
         loggingConfigurationCommandLineConverter.convert(options, startParameter);
-        FileResolver resolver = new BaseDirFileResolver(FileSystems.getDefault(), startParameter.getCurrentDir());
+        FileResolver resolver = fileLookup.getFileResolver(startParameter.getCurrentDir());
 
         Map<String, String> systemProperties = systemPropertiesCommandLineConverter.convert(options);
         convertCommandLineSystemProperties(systemProperties, startParameter, resolver);
@@ -103,17 +109,13 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         Map<String, String> projectProperties = projectPropertiesCommandLineConverter.convert(options);
         startParameter.getProjectProperties().putAll(projectProperties);
 
-        BuildLayoutParameters layout = new BuildLayoutParameters().setProjectDir(startParameter.getCurrentDir());
+        BuildLayoutParameters layout = new BuildLayoutParameters()
+                .setGradleUserHomeDir(startParameter.getGradleUserHomeDir())
+                .setProjectDir(startParameter.getCurrentDir());
         layoutCommandLineConverter.convert(options, layout);
-        if (layout.getGradleUserHomeDir() != null) {
-            startParameter.setGradleUserHomeDir(layout.getGradleUserHomeDir());
-        }
-        if (layout.getProjectDir() != null) {
-            startParameter.setProjectDir(layout.getProjectDir());
-        }
-        if (layout.getSearchUpwards() != null) {
-            startParameter.setSearchUpwards(layout.getSearchUpwards());
-        }
+        startParameter.setGradleUserHomeDir(layout.getGradleUserHomeDir());
+        startParameter.setProjectDir(layout.getProjectDir());
+        startParameter.setSearchUpwards(layout.getSearchUpwards());
 
         if (options.hasOption(BUILD_FILE)) {
             startParameter.setBuildFile(resolver.resolve(options.option(BUILD_FILE).getValue()));
@@ -208,9 +210,16 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
 
     void convertCommandLineSystemProperties(Map<String, String> systemProperties, StartParameter startParameter, FileResolver resolver) {
         startParameter.getSystemPropertiesArgs().putAll(systemProperties);
-        String gradleUserHomeProp = "gradle.user.home";
-        if (systemProperties.containsKey(gradleUserHomeProp)) {
-            startParameter.setGradleUserHomeDir(resolver.resolve(systemProperties.get(gradleUserHomeProp)));
+        if (systemProperties.containsKey(GRADLE_USER_HOME_PROPERTY_KEY)) {
+            startParameter.setGradleUserHomeDir(resolver.resolve(systemProperties.get(GRADLE_USER_HOME_PROPERTY_KEY)));
         }
     }
+
+    public LayoutCommandLineConverter getLayoutConverter() {
+        return layoutCommandLineConverter;
+    }
+
+    public SystemPropertiesCommandLineConverter getSystemPropertiesConverter() {
+        return systemPropertiesCommandLineConverter;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
index 77eb6c2..7795d46 100755
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
@@ -16,16 +16,15 @@
 package org.gradle.initialization;
 
 import org.gradle.api.GradleScriptException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.Contextual;
 import org.gradle.api.internal.ExceptionAnalyser;
-import org.gradle.api.internal.LocationAwareException;
+import org.gradle.internal.exceptions.LocationAwareException;
 import org.gradle.api.tasks.TaskExecutionException;
 import org.gradle.groovy.scripts.Script;
 import org.gradle.groovy.scripts.ScriptCompilationException;
 import org.gradle.groovy.scripts.ScriptExecutionListener;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.listener.ListenerManager;
-import org.gradle.listener.ListenerNotificationException;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -47,26 +46,19 @@ public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecut
 
     public Throwable transform(Throwable exception) {
         Throwable actualException = findDeepestRootException(exception);
-        if (actualException == null) {
-            return exception;
-        }
         if (actualException instanceof LocationAwareException) {
             return actualException;
         }
 
         ScriptSource source = null;
         Integer lineNumber = null;
-        Throwable target = actualException;
 
-        // todo - remove these special cases
+        // TODO: remove these special cases
         if (actualException instanceof ScriptCompilationException) {
             ScriptCompilationException scriptCompilationException = (ScriptCompilationException) actualException;
             source = scriptCompilationException.getScriptSource();
             lineNumber = scriptCompilationException.getLineNumber();
         }
-        if (actualException instanceof ListenerNotificationException && actualException.getCause() != null) {
-            target = actualException.getCause();
-        }
 
         if (source == null) {
             for (
@@ -82,7 +74,7 @@ public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecut
             }
         }
 
-        return new LocationAwareException(actualException, target, source, lineNumber);
+        return new LocationAwareException(actualException, source, lineNumber);
     }
 
     private Throwable findDeepestRootException(Throwable exception) {
@@ -103,8 +95,10 @@ public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecut
             return locationAware;
         } else if (result != null) {
             return result;
-        } else {
+        } else if (contextMatch != null) {
             return contextMatch;
+        } else {
+            return exception;
         }
     }
 }
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 c853773..9f394c7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -46,7 +46,7 @@ public class DefaultGradleLauncher extends GradleLauncher {
 
     /**
      * Creates a new instance.  Don't call this directly, use {@link #newInstance(org.gradle.StartParameter)} or {@link
-     * #newInstance(String...)} instead.  Note that this method is package-protected to discourage it's direct use.
+     * #newInstance(String...)} instead.
      */
     public DefaultGradleLauncher(GradleInternal gradle, InitScriptHandler initScriptHandler, SettingsHandler settingsHandler,
                                  BuildLoader buildLoader, BuildConfigurer buildConfigurer, BuildListener buildListener,
@@ -135,7 +135,7 @@ public class DefaultGradleLauncher extends GradleLauncher {
         buildListener.settingsEvaluated(settings);
 
         // Load build
-        buildLoader.load(settings.getRootProject(), gradle);
+        buildLoader.load(settings.getRootProject(), gradle, settings.getClassLoaderScope().createSibling());
         buildListener.projectsLoaded(gradle);
 
         // Configure 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 088fb61..eec61d9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -19,17 +19,23 @@ package org.gradle.initialization;
 import org.gradle.*;
 import org.gradle.api.internal.ExceptionAnalyser;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.GlobalServicesRegistry;
-import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.internal.progress.BuildProgressFilter;
+import org.gradle.internal.progress.BuildProgressLogger;
+import org.gradle.internal.progress.LoggerProvider;
+import org.gradle.internal.service.scopes.BuildScopeServices;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.configuration.BuildConfigurer;
 import org.gradle.execution.BuildExecuter;
+import org.gradle.initialization.buildsrc.BuildSourceBuilder;
 import org.gradle.initialization.layout.BuildLayoutFactory;
+import org.gradle.internal.featurelifecycle.ScriptUsageLocationReporter;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.invocation.DefaultGradle;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.LoggingManagerInternal;
@@ -37,32 +43,24 @@ import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.profile.ProfileEventAdapter;
 import org.gradle.profile.ReportGeneratingProfileListener;
+import org.gradle.util.DeprecationLogger;
 
 import java.util.Arrays;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
     private final ServiceRegistry sharedServices;
     private final NestedBuildTracker tracker;
+    private final BuildProgressLogger buildProgressLogger;
     private CommandLineConverter<StartParameter> commandLineConverter;
 
-    public DefaultGradleLauncherFactory(ServiceRegistry loggingServices) {
-        this(new GlobalServicesRegistry(loggingServices));
-    }
-    
-    public DefaultGradleLauncherFactory() {
-        this(new GlobalServicesRegistry());
-    }
-
-    private DefaultGradleLauncherFactory(GlobalServicesRegistry globalServices) {
+    public DefaultGradleLauncherFactory(ServiceRegistry globalServices) {
         sharedServices = globalServices;
         tracker = new NestedBuildTracker();
 
         // Register default loggers 
         ListenerManager listenerManager = sharedServices.get(ListenerManager.class);
-        listenerManager.addListener(new BuildProgressLogger(sharedServices.get(ProgressLoggerFactory.class)));
+        buildProgressLogger = new BuildProgressLogger(sharedServices.get(ProgressLoggerFactory.class));
+        listenerManager.addListener(new BuildProgressFilter(buildProgressLogger));
         listenerManager.useLogger(new DependencyResolutionLogger(sharedServices.get(ProgressLoggerFactory.class)));
 
         GradleLauncher.injectCustomFactory(this);
@@ -100,7 +98,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
     }
 
     private DefaultGradleLauncher doNewInstance(StartParameter startParameter, BuildRequestMetaData requestMetaData) {
-        final TopLevelBuildServiceRegistry serviceRegistry = new TopLevelBuildServiceRegistry(sharedServices, startParameter);
+        final BuildScopeServices serviceRegistry = new BuildScopeServices(sharedServices, startParameter);
         serviceRegistry.add(BuildRequestMetaData.class, requestMetaData);
         serviceRegistry.add(BuildClientMetaData.class, requestMetaData.getClient());
         ListenerManager listenerManager = serviceRegistry.get(ListenerManager.class);
@@ -111,7 +109,8 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         loggingManager.addStandardOutputListener(listenerManager.getBroadcaster(StandardOutputListener.class));
         loggingManager.addStandardErrorListener(listenerManager.getBroadcaster(StandardOutputListener.class));
 
-        listenerManager.useLogger(new TaskExecutionLogger(serviceRegistry.get(ProgressLoggerFactory.class)));
+        LoggerProvider loggerProvider = (tracker.getCurrentBuild() == null)? buildProgressLogger: LoggerProvider.NO_OP;
+        listenerManager.useLogger(new TaskExecutionLogger(serviceRegistry.get(ProgressLoggerFactory.class), loggerProvider));
         if (tracker.getCurrentBuild() == null) {
             listenerManager.useLogger(new BuildLogger(Logging.getLogger(BuildLogger.class), serviceRegistry.get(StyledTextOutputFactory.class), startParameter, requestMetaData));
         }
@@ -122,8 +121,11 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         if (startParameter.isProfile()) {
             listenerManager.addListener(new ReportGeneratingProfileListener());
         }
+        ScriptUsageLocationReporter usageLocationReporter = new ScriptUsageLocationReporter();
+        listenerManager.addListener(usageLocationReporter);
+        DeprecationLogger.useLocationReporter(usageLocationReporter);
 
-        GradleInternal gradle = serviceRegistry.get(Instantiator.class).newInstance(DefaultGradle.class, tracker.getCurrentBuild(), startParameter, serviceRegistry);
+        GradleInternal gradle = serviceRegistry.get(Instantiator.class).newInstance(DefaultGradle.class, tracker.getCurrentBuild(), startParameter, serviceRegistry.get(ServiceRegistryFactory.class));
         return new DefaultGradleLauncher(
                 gradle,
                 serviceRegistry.get(InitScriptHandler.class),
@@ -133,7 +135,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
                         serviceRegistry.get(SettingsProcessor.class),
                         new BuildSourceBuilder(
                                 this,
-                                serviceRegistry.get(ClassLoaderRegistry.class),
+                                serviceRegistry.get(ClassLoaderScope.class),
                                 serviceRegistry.get(CacheRepository.class))),
                 serviceRegistry.get(BuildLoader.class),
                 serviceRegistry.get(BuildConfigurer.class),
@@ -151,9 +153,9 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
     }
 
     private static class BuildCleanupListener extends BuildAdapter {
-        private final TopLevelBuildServiceRegistry services;
+        private final BuildScopeServices services;
 
-        private BuildCleanupListener(TopLevelBuildServiceRegistry services) {
+        private BuildCleanupListener(BuildScopeServices services) {
             this.services = services;
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
index bf360d8..62298a2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
@@ -26,9 +26,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultGradlePropertiesLoader implements IGradlePropertiesLoader {
     private static Logger logger = LoggerFactory.getLogger(DefaultGradlePropertiesLoader.class);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java
index 1a33d47..549f555 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java
@@ -17,6 +17,7 @@ package org.gradle.initialization;
 
 import org.gradle.api.Project;
 import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.Path;
@@ -25,22 +26,21 @@ import java.io.File;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultProjectDescriptor implements ProjectDescriptor, ProjectIdentifier {
     private String name;
+    private final FileResolver fileResolver;
     private File dir;
     private DefaultProjectDescriptor parent;
     private Set<ProjectDescriptor> children = new LinkedHashSet<ProjectDescriptor>();
-    private IProjectDescriptorRegistry projectDescriptorRegistry;
+    private ProjectDescriptorRegistry projectDescriptorRegistry;
     private Path path;
     private String buildFileName = Project.DEFAULT_BUILD_FILE;
 
     public DefaultProjectDescriptor(DefaultProjectDescriptor parent, String name, File dir,
-                                    IProjectDescriptorRegistry projectDescriptorRegistry) {
+                                    ProjectDescriptorRegistry projectDescriptorRegistry, FileResolver fileResolver) {
         this.parent = parent;
         this.name = name;
+        this.fileResolver = fileResolver;
         this.dir = GFileUtils.canonicalise(dir);
         this.projectDescriptorRegistry = projectDescriptorRegistry;
         this.path = path(name);
@@ -80,7 +80,7 @@ public class DefaultProjectDescriptor implements ProjectDescriptor, ProjectIdent
     }
 
     public void setProjectDir(File dir) {
-        this.dir = GFileUtils.canonicalise(dir);
+        this.dir = fileResolver.resolve(dir);
     }
 
     public DefaultProjectDescriptor getParent() {
@@ -115,11 +115,11 @@ public class DefaultProjectDescriptor implements ProjectDescriptor, ProjectIdent
         return GFileUtils.canonicalise(new File(dir, buildFileName));
     }
 
-    public IProjectDescriptorRegistry getProjectDescriptorRegistry() {
+    public ProjectDescriptorRegistry getProjectDescriptorRegistry() {
         return projectDescriptorRegistry;
     }
 
-    public void setProjectDescriptorRegistry(IProjectDescriptorRegistry projectDescriptorRegistry) {
+    public void setProjectDescriptorRegistry(ProjectDescriptorRegistry projectDescriptorRegistry) {
         this.projectDescriptorRegistry = projectDescriptorRegistry;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java
index 148e100..cd3f64a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java
@@ -18,10 +18,7 @@ package org.gradle.initialization;
 import org.gradle.api.internal.project.DefaultProjectRegistry;
 import org.gradle.util.Path;
 
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectDescriptorRegistry extends DefaultProjectRegistry<DefaultProjectDescriptor> implements IProjectDescriptorRegistry {
+public class DefaultProjectDescriptorRegistry extends DefaultProjectRegistry<DefaultProjectDescriptor> implements ProjectDescriptorRegistry {
 
     public void changeDescriptorPath(Path oldPath, Path newPath) {
         DefaultProjectDescriptor projectDescriptor = removeProject(oldPath.toString());
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java
index 1996eb0..56c77de 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 
 import java.io.File;
 
@@ -25,7 +25,7 @@ public class DefaultProjectSpec extends ProjectDirectoryProjectSpec {
     }
 
     @Override
-    protected void checkPreconditions(IProjectRegistry<?> registry) {
+    protected void checkPreconditions(ProjectRegistry<?> registry) {
         // Ignore
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
deleted file mode 100644
index b5aedfe..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.initialization
-
-import org.gradle.StartParameter
-import org.gradle.api.internal.GradleInternal
-import org.gradle.groovy.scripts.ScriptSource
-
-/**
- * @author Hans Dockter
- */
-public class DefaultSettings extends BaseSettings {
-    public DefaultSettings() {}
-
-    DefaultSettings(GradleInternal gradle,
-                    IProjectDescriptorRegistry projectDescriptorRegistry,
-                    URLClassLoader classloader, File settingsDir,
-                    ScriptSource settingsScript, StartParameter startParameter) {
-      super(gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter)
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.java
new file mode 100644
index 0000000..df0b7bd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
+
+import java.io.File;
+
+public class DefaultSettings extends BaseSettings {
+
+    public DefaultSettings(ServiceRegistryFactory serviceRegistryFactory,
+                    GradleInternal gradle,
+                    ClassLoaderScope classLoaderScope, File settingsDir,
+                    ScriptSource settingsScript, StartParameter startParameter) {
+        super(serviceRegistryFactory, gradle, classLoaderScope, settingsDir, settingsScript, startParameter);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
index 6889452..c0c26c0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
@@ -20,9 +20,6 @@ import org.gradle.initialization.layout.BuildLayout;
 import org.gradle.initialization.layout.BuildLayoutConfiguration;
 import org.gradle.initialization.layout.BuildLayoutFactory;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultSettingsFinder implements ISettingsFinder {
     private final BuildLayoutFactory layoutFactory;
 
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 7987192..f028c56 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
@@ -22,7 +22,6 @@ import org.gradle.logging.ProgressLoggerFactory;
 
 import java.util.LinkedList;
 
-// TODO:DAZ Think about a better way to do thread-safety here, maybe
 public class DependencyResolutionLogger implements DependencyResolutionListener {
     private final ThreadLocal<LinkedList<ProgressLogger>> progressLoggers = new ThreadLocal<LinkedList<ProgressLogger>>() {
         protected LinkedList<ProgressLogger> initialValue() {
@@ -40,9 +39,7 @@ public class DependencyResolutionLogger implements DependencyResolutionListener
         LinkedList<ProgressLogger> loggers = progressLoggers.get();
         progressLoggers.set(loggers);
         ProgressLogger logger = loggerFactory.newOperation(DependencyResolutionLogger.class);
-        logger.setDescription(String.format("Resolve %s", dependencies));
-        logger.setShortDescription(String.format("Resolving %s", dependencies));
-        logger.started();
+        logger.start(String.format("Resolve %s", dependencies), String.format("Resolving %s", dependencies));
         loggers.add(logger);
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java
deleted file mode 100644
index 2437d93..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.initialization;
-
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-
-/**
- * An object that performs some action with a {@link GradleLauncher}, and optionally produces a “result” object (e.g. the output).
- * <p>
- * Implementations of this are typically composed to bootstrap a build in a certain environment.
- * <p>
- * @see org.gradle.launcher.cli.ExecuteBuildAction
- * @see org.gradle.tooling.internal.provider.BuildModelAction
- */
-public interface GradleLauncherAction<T> {
-    
-    /**
-     * Something produced by the action, the meaning of which is entirely up to the implementation to define.
-     */
-    T getResult();
-
-    /**
-     * Executes the action with the given launcher.
-     * <p>
-     * The state of the launcher is not defined as part of this contract, it is highly context specific. For example,
-     * it is not guaranteed that the start parameter for the launcher has been configured.
-     */
-    BuildResult run(GradleLauncher launcher);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
index a271e0f..d749341 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
@@ -21,8 +21,6 @@ import org.gradle.StartParameter;
 /**
  * <p>A {@code GradleLauncherFactory} is responsible for creating a {@link org.gradle.GradleLauncher} instance for a build, from a {@link
  * org.gradle.StartParameter}.</p>
- *
- * @author Hans Dockter
  */
 public interface GradleLauncherFactory {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
index 27db653..730e5c6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
@@ -18,9 +18,6 @@ package org.gradle.initialization;
 import java.io.File;
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public interface IGradlePropertiesLoader {
     public static final String SYSTEM_PROJECT_PROPERTIES_PREFIX = "org.gradle.project.";
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/IProjectDescriptorRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/IProjectDescriptorRegistry.java
deleted file mode 100644
index 8dde639..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/IProjectDescriptorRegistry.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.initialization;
-
-import org.gradle.api.internal.project.IProjectRegistry;
-import org.gradle.util.Path;
-
-/**
- * @author Hans Dockter
- */
-public interface IProjectDescriptorRegistry extends IProjectRegistry<DefaultProjectDescriptor> {
-    void changeDescriptorPath(Path oldPath, Path newPath);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java
index 62074f6..ee1c1c0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java
@@ -17,9 +17,6 @@ package org.gradle.initialization;
 
 import org.gradle.StartParameter;
 
-/**
- * @author Hans Dockter
- */
 public interface ISettingsFinder {
     SettingsLocation find(StartParameter startParameter);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java
index 3b13750..dd678c1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java
@@ -20,14 +20,10 @@ import org.gradle.api.GradleException;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.api.internal.project.IProjectFactory;
 import org.gradle.api.internal.project.ProjectInternal;
 
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
 public class InstantiatingBuildLoader implements BuildLoader {
     private final IProjectFactory projectFactory;
 
@@ -39,17 +35,13 @@ public class InstantiatingBuildLoader implements BuildLoader {
      * Creates the {@link org.gradle.api.internal.GradleInternal} and {@link ProjectInternal} instances for the given root project,
      * ready for the projects to be evaluated.
      */
-    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle) {
-        createProjects(rootProjectDescriptor, gradle);
+    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle, ClassLoaderScope classLoaderScope) {
+        createProjects(rootProjectDescriptor, gradle, classLoaderScope);
         attachDefaultProject(gradle);
     }
 
     private void attachDefaultProject(GradleInternal gradle) {
-        File explicitProjectDir = gradle.getStartParameter().getProjectDir();
-        File explicitBuildFile = gradle.getStartParameter().getBuildFile();
-        ProjectSpec spec = explicitBuildFile != null
-                ? new BuildFileProjectSpec(explicitBuildFile)
-                : explicitProjectDir == null ? new DefaultProjectSpec(gradle.getStartParameter().getCurrentDir()) : new ProjectDirectoryProjectSpec(explicitProjectDir);
+        ProjectSpec spec = ProjectSpecs.forStartParameter(gradle.getStartParameter());
         try {
             gradle.setDefaultProject(spec.selectProject(gradle.getRootProject().getProjectRegistry()));
         } catch (InvalidUserDataException e) {
@@ -58,15 +50,15 @@ public class InstantiatingBuildLoader implements BuildLoader {
         }
     }
 
-    private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle) {
-        ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle);
+    private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle, ClassLoaderScope classLoaderScope) {
+        ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle, classLoaderScope);
         gradle.setRootProject(rootProject);
         addProjects(rootProject, rootProjectDescriptor, gradle);
     }
 
     private void addProjects(ProjectInternal parent, ProjectDescriptor parentProjectDescriptor, GradleInternal gradle) {
         for (ProjectDescriptor childProjectDescriptor : parentProjectDescriptor.getChildren()) {
-            ProjectInternal childProject = projectFactory.createProject(childProjectDescriptor, parent, gradle);
+            ProjectInternal childProject = projectFactory.createProject(childProjectDescriptor, parent, gradle, parent.getClassLoaderScope().createChild());
             addProjects(childProject, childProjectDescriptor, gradle);
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/JdkToolsInitializer.java b/subprojects/core/src/main/groovy/org/gradle/initialization/JdkToolsInitializer.java
new file mode 100644
index 0000000..564a871
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/JdkToolsInitializer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public interface JdkToolsInitializer {
+    /**
+     * Ensures that the JDK tools are visible on the system ClassLoader. Not really a great idea, but here for backwards
+     * compatibility.
+     */
+    void initializeJdkTools();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java
index db5ba26..96e34b4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java
@@ -17,31 +17,30 @@
 package org.gradle.initialization;
 
 import org.gradle.api.initialization.Settings;
-import org.gradle.api.internal.file.BaseDirFileResolver;
+import org.gradle.api.internal.file.FileLookup;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.cli.AbstractCommandLineConverter;
 import org.gradle.cli.CommandLineArgumentException;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
-import org.gradle.internal.SystemProperties;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
-import org.gradle.util.GFileUtils;
 
-/**
- * by Szczepan Faber, created at: 2/18/13
- */
 public class LayoutCommandLineConverter extends AbstractCommandLineConverter<BuildLayoutParameters> {
 
     public static final String GRADLE_USER_HOME = "g";
     private static final String NO_SEARCH_UPWARDS = "u";
     private static final String PROJECT_DIR = "p";
+    private final FileLookup fileLookup;
+
+    public LayoutCommandLineConverter(FileLookup fileLookup) {
+        this.fileLookup = fileLookup;
+    }
 
     protected BuildLayoutParameters newInstance() {
-        return new BuildLayoutParameters().setProjectDir(GFileUtils.canonicalise(SystemProperties.getCurrentDir()));
+        return new BuildLayoutParameters();
     }
 
     public BuildLayoutParameters convert(ParsedCommandLine options, BuildLayoutParameters target) throws CommandLineArgumentException {
-        FileResolver resolver = new BaseDirFileResolver(FileSystems.getDefault(), target.getProjectDir());
+        FileResolver resolver = fileLookup.getFileResolver(target.getProjectDir());
         if (options.hasOption(NO_SEARCH_UPWARDS)) {
             target.setSearchUpwards(false);
         }
@@ -57,6 +56,6 @@ public class LayoutCommandLineConverter extends AbstractCommandLineConverter<Bui
     public void configure(CommandLineParser parser) {
         parser.option(NO_SEARCH_UPWARDS, "no-search-upward").hasDescription(String.format("Don't search in parent folders for a %s file.", Settings.DEFAULT_SETTINGS_FILE));
         parser.option(PROJECT_DIR, "project-dir").hasArgument().hasDescription("Specifies the start directory for Gradle. Defaults to current directory.");
-        parser.option(LayoutCommandLineConverter.GRADLE_USER_HOME, "gradle-user-home").hasArgument().hasDescription("Specifies the gradle user home directory.");
+        parser.option(GRADLE_USER_HOME, "gradle-user-home").hasArgument().hasDescription("Specifies the gradle user home directory.");
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java
index b4fa573..3a38789 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java
@@ -20,8 +20,6 @@ import org.gradle.api.internal.project.ProjectInternal;
 
 /**
  * Internal interface, used by our configuration on demand mode.
- *
- * by Szczepan Faber, created at: 2/5/13
  */
 public interface ProjectAccessListener {
     void beforeRequestingTaskByPath(ProjectInternal targetProject);
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDescriptorRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDescriptorRegistry.java
new file mode 100644
index 0000000..4df3a38
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDescriptorRegistry.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization;
+
+import org.gradle.api.internal.project.ProjectRegistry;
+import org.gradle.util.Path;
+
+public interface ProjectDescriptorRegistry extends ProjectRegistry<DefaultProjectDescriptor> {
+    void changeDescriptorPath(Path oldPath, Path newPath);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
index 320603a..72991d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
@@ -18,7 +18,7 @@ package org.gradle.initialization;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
 import org.gradle.api.internal.project.ProjectIdentifier;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.api.InvalidUserDataException;
 
 import java.io.File;
@@ -48,7 +48,7 @@ public class ProjectDirectoryProjectSpec extends AbstractProjectSpec implements
     }
 
     @Override
-    protected void checkPreconditions(IProjectRegistry<?> registry) {
+    protected void checkPreconditions(ProjectRegistry<?> registry) {
         if (!dir.exists()) {
             throw new InvalidUserDataException(String.format("Project directory '%s' does not exist.", dir));
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPathProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPathProjectSpec.java
new file mode 100644
index 0000000..4acf378
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPathProjectSpec.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.base.Strings;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.internal.project.ProjectRegistry;
+
+import java.io.Serializable;
+
+public class ProjectPathProjectSpec extends AbstractProjectSpec implements Serializable {
+    private final String projectPath;
+
+    public ProjectPathProjectSpec(String projectPath) {
+        this.projectPath = projectPath;
+    }
+
+    public String getDisplayName() {
+        return String.format("project has path '%s'", projectPath);
+    }
+
+    protected String formatNoMatchesMessage() {
+        return String.format("No projects in this build have path '%s'.", projectPath);
+    }
+
+    protected String formatMultipleMatchesMessage(Iterable<? extends ProjectIdentifier> matches) {
+        return String.format("Multiple projects in this build have path '%s': %s", projectPath, matches);
+    }
+
+    protected boolean select(ProjectIdentifier project) {
+        return projectPath.equals(project.getPath());
+    }
+
+    @Override
+    protected void checkPreconditions(ProjectRegistry<?> registry) {
+        // TODO(radimk): pattern for path?
+        if (Strings.isNullOrEmpty(projectPath)) {
+            throw new InvalidUserDataException("Project path must not be empty.");
+        }
+    }
+
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java
index 6e6b30c..7af724a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java
@@ -19,6 +19,7 @@ package org.gradle.initialization;
 import org.gradle.api.Project;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.plugins.ExtraPropertiesExtension;
 import org.gradle.util.GUtil;
@@ -30,9 +31,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
-/**
- * @author Hans Dockter
- */
 public class ProjectPropertySettingBuildLoader implements BuildLoader {
     private static final Logger LOGGER = LoggerFactory.getLogger(ProjectPropertySettingBuildLoader.class);
 
@@ -44,8 +42,8 @@ public class ProjectPropertySettingBuildLoader implements BuildLoader {
         this.propertiesLoader = propertiesLoader;
     }
 
-    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle) {
-        buildLoader.load(rootProjectDescriptor, gradle);
+    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle, ClassLoaderScope classLoaderScope) {
+        buildLoader.load(rootProjectDescriptor, gradle, classLoaderScope);
         setProjectProperties(gradle.getRootProject());
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpec.java
index b8a73a6..2b2133e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpec.java
@@ -16,21 +16,21 @@
 package org.gradle.initialization;
 
 import org.gradle.api.internal.project.ProjectIdentifier;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.api.InvalidUserDataException;
 
 public interface ProjectSpec {
     /**
      * Determines whether the given registry contains at least 1 project which meets this spec.
      */
-    boolean containsProject(IProjectRegistry<?> registry);
+    boolean containsProject(ProjectRegistry<?> registry);
 
     /**
      * Returns the single project in the given registry which meets this spec.
      * @return the project. Never returns null.
      * @throws InvalidUserDataException When project cannot be selected due to some user input mismatch.
      */
-    <T extends ProjectIdentifier> T selectProject(IProjectRegistry<? extends T> registry) throws
+    <T extends ProjectIdentifier> T selectProject(ProjectRegistry<? extends T> registry) throws
             InvalidUserDataException;
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpecs.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpecs.java
new file mode 100644
index 0000000..d230000
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectSpecs.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization;
+
+import org.gradle.StartParameter;
+
+import java.io.File;
+
+class ProjectSpecs {
+
+    static ProjectSpec forStartParameter(StartParameter startParameter) {
+        String explicitProjectPath = startParameter.getProjectPath();
+        File explicitProjectDir = startParameter.getProjectDir();
+        File explicitBuildFile = startParameter.getBuildFile();
+        ProjectSpec spec = explicitProjectPath != null
+                ? new ProjectPathProjectSpec(explicitProjectPath)
+                : explicitBuildFile != null
+                ? new BuildFileProjectSpec(explicitBuildFile)
+                : explicitProjectDir == null ? new DefaultProjectSpec(startParameter.getCurrentDir()) : new ProjectDirectoryProjectSpec(explicitProjectDir);
+        return spec;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
index 2b3cb50..dd0d359 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
@@ -15,11 +15,10 @@
  */
 package org.gradle.initialization;
 
+import org.gradle.StartParameter;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
-import org.gradle.StartParameter;
-
-import java.net.URLClassLoader;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 
 public class PropertiesLoadingSettingsProcessor implements SettingsProcessor {
     private final SettingsProcessor processor;
@@ -32,9 +31,9 @@ public class PropertiesLoadingSettingsProcessor implements SettingsProcessor {
 
     public SettingsInternal process(GradleInternal gradle,
                                     SettingsLocation settingsLocation,
-                                    URLClassLoader buildSourceClassLoader,
+                                    ClassLoaderScope classLoaderScope,
                                     StartParameter startParameter) {
         propertiesLoader.loadProperties(settingsLocation.getSettingsDir());
-        return processor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter);
+        return processor.process(gradle, settingsLocation, classLoaderScope, startParameter);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
index 6656eb5..795b02c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
@@ -17,54 +17,59 @@
 package org.gradle.initialization;
 
 import org.gradle.StartParameter;
+import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.configuration.ScriptPlugin;
 import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.util.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.URLClassLoader;
 import java.util.Collections;
 import java.util.Map;
 
 
-/**
- * @author Hans Dockter
- */
 public class ScriptEvaluatingSettingsProcessor implements SettingsProcessor {
     private static Logger logger = LoggerFactory.getLogger(ScriptEvaluatingSettingsProcessor.class);
 
+    private final ScriptHandlerFactory scriptHandlerFactory;
     private final SettingsFactory settingsFactory;
     private final IGradlePropertiesLoader propertiesLoader;
     private final ScriptPluginFactory configurerFactory;
 
     public ScriptEvaluatingSettingsProcessor(ScriptPluginFactory configurerFactory,
+                                             ScriptHandlerFactory scriptHandlerFactory,
                                              SettingsFactory settingsFactory,
                                              IGradlePropertiesLoader propertiesLoader) {
         this.configurerFactory = configurerFactory;
+        this.scriptHandlerFactory = scriptHandlerFactory;
         this.settingsFactory = settingsFactory;
         this.propertiesLoader = propertiesLoader;
     }
 
     public SettingsInternal process(GradleInternal gradle,
                                     SettingsLocation settingsLocation,
-                                    URLClassLoader buildSourceClassLoader,
+                                    ClassLoaderScope classLoaderScope,
                                     StartParameter startParameter) {
         Clock settingsProcessingClock = new Clock();
         Map<String, String> properties = propertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
         SettingsInternal settings = settingsFactory.createSettings(gradle, settingsLocation.getSettingsDir(),
-                settingsLocation.getSettingsScriptSource(), properties, startParameter, buildSourceClassLoader);
-        applySettingsScript(settingsLocation, buildSourceClassLoader, settings);
+                settingsLocation.getSettingsScriptSource(), properties, startParameter, classLoaderScope);
+        applySettingsScript(settingsLocation, settings);
         logger.debug("Timing: Processing settings took: {}", settingsProcessingClock.getTime());
         return settings;
     }
 
-    private void applySettingsScript(SettingsLocation settingsLocation, ClassLoader buildSourceClassLoader, SettingsInternal settings) {
-        ScriptPlugin configurer = configurerFactory.create(settingsLocation.getSettingsScriptSource());
-        configurer.setClassLoader(buildSourceClassLoader);
-        configurer.setScriptBaseClass(SettingsScript.class);
+    private void applySettingsScript(SettingsLocation settingsLocation, final SettingsInternal settings) {
+        ScriptSource settingsScriptSource = settingsLocation.getSettingsScriptSource();
+        ClassLoaderScope classLoaderScope = settings.getClassLoaderScope();
+        ScriptHandler scriptHandler = scriptHandlerFactory.create(settingsScriptSource, classLoaderScope);
+        ScriptPlugin configurer = configurerFactory.create(settingsScriptSource, scriptHandler, classLoaderScope, "buildscript", SettingsScript.class);
         configurer.apply(settings);
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
index ca633e8..fe319ec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
@@ -18,31 +18,29 @@ package org.gradle.initialization;
 
 import org.gradle.StartParameter;
 import org.gradle.api.internal.*;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.internal.reflect.Instantiator;
 
 import java.io.File;
-import java.net.URLClassLoader;
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public class SettingsFactory {
-    private final IProjectDescriptorRegistry projectDescriptorRegistry;
     private final Instantiator instantiator;
+    private final ServiceRegistryFactory serviceRegistryFactory;
 
-    public SettingsFactory(IProjectDescriptorRegistry projectDescriptorRegistry, Instantiator instantiator) {
-        this.projectDescriptorRegistry = projectDescriptorRegistry;
+    public SettingsFactory(Instantiator instantiator, ServiceRegistryFactory serviceRegistryFactory) {
         this.instantiator = instantiator;
+        this.serviceRegistryFactory = serviceRegistryFactory;
     }
 
     public SettingsInternal createSettings(GradleInternal gradle, File settingsDir, ScriptSource settingsScript,
                                            Map<String, String> gradleProperties, StartParameter startParameter,
-                                           URLClassLoader classloader) {
+                                           ClassLoaderScope classLoaderScope) {
 
         DefaultSettings settings = instantiator.newInstance(DefaultSettings.class,
-                gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter
+                serviceRegistryFactory, gradle, classLoaderScope, settingsDir, settingsScript, startParameter
         );
 
         DynamicObject dynamicObject = ((DynamicObjectAware) settings).getAsDynamicObject();
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
index fd88f4d..4e09148 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
@@ -20,9 +20,10 @@ import org.gradle.StartParameter;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.initialization.buildsrc.BuildSourceBuilder;
 
 import java.io.File;
-import java.net.URLClassLoader;
 
 /**
  * Handles locating and processing setting.gradle files.  Also deals with the buildSrc module, since that modules is
@@ -44,11 +45,7 @@ public class SettingsHandler {
         StartParameter startParameter = gradle.getStartParameter();
         SettingsInternal settings = findSettingsAndLoadIfAppropriate(gradle, startParameter);
 
-        File explicitProjectDir = startParameter.getProjectDir();
-        File explicitBuildFile = startParameter.getBuildFile();
-        ProjectSpec spec = explicitBuildFile != null
-                ? new BuildFileProjectSpec(explicitBuildFile)
-                : explicitProjectDir == null ? new DefaultProjectSpec(startParameter.getCurrentDir()) : new ProjectDirectoryProjectSpec(explicitProjectDir);
+        ProjectSpec spec = ProjectSpecs.forStartParameter(startParameter);
 
         if (!spec.containsProject(settings.getProjectRegistry())) {
             // The settings we found did not include the desired default project. Try again with an empty settings file.
@@ -69,7 +66,6 @@ public class SettingsHandler {
             }
         }
 
-        gradle.getScriptClassLoader().addParent(settings.getClassLoader());
         return settings;
     }
 
@@ -87,9 +83,9 @@ public class SettingsHandler {
         StartParameter buildSrcStartParameter = startParameter.newBuild();
         buildSrcStartParameter.setCurrentDir(new File(settingsLocation.getSettingsDir(),
                 BaseSettings.DEFAULT_BUILD_SRC_DIR));
-        URLClassLoader buildSourceClassLoader = buildSourceBuilder.buildAndCreateClassLoader(buildSrcStartParameter);
+        ClassLoaderScope buildSourceClassLoader = buildSourceBuilder.buildAndCreateClassLoader(buildSrcStartParameter);
 
-        return loadSettings(gradle, settingsLocation, buildSourceClassLoader, startParameter);
+        return loadSettings(gradle, settingsLocation, buildSourceClassLoader.createRebasedChild(), startParameter);
     }
 
     private SettingsLocation findSettings(StartParameter startParameter) {
@@ -97,8 +93,8 @@ public class SettingsHandler {
     }
 
     private SettingsInternal loadSettings(GradleInternal gradle, SettingsLocation settingsLocation,
-                                          URLClassLoader buildSourceClassLoader, StartParameter startParameter) {
-        return settingsProcessor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter);
+                                          ClassLoaderScope classLoaderScope, StartParameter startParameter) {
+        return settingsProcessor.process(gradle, settingsLocation, classLoaderScope, startParameter);
     }
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
index 7ea4f02..0db94ef 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
@@ -15,11 +15,10 @@
  */
 package org.gradle.initialization;
 
+import org.gradle.StartParameter;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
-import org.gradle.StartParameter;
-
-import java.net.URLClassLoader;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 
 /**
  * Responsible for locating, constructing, and evaluating the {@link SettingsInternal} for a build.
@@ -27,6 +26,6 @@ import java.net.URLClassLoader;
 public interface SettingsProcessor {
     SettingsInternal process(GradleInternal gradle,
                              SettingsLocation settingsLocation,
-                             URLClassLoader buildSourceClassLoader,
+                             ClassLoaderScope classLoaderScope,
                              StartParameter startParameter);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSourceBuilder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSourceBuilder.java
new file mode 100644
index 0000000..cfd286a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSourceBuilder.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.buildsrc;
+
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
+import org.gradle.util.GradleVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Collections;
+
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
+
+public class BuildSourceBuilder {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BuildSourceBuilder.class);
+
+    private final GradleLauncherFactory gradleLauncherFactory;
+    private final ClassLoaderScope classLoaderScope;
+    private final CacheRepository cacheRepository;
+
+    public BuildSourceBuilder(GradleLauncherFactory gradleLauncherFactory, ClassLoaderScope classLoaderScope, CacheRepository cacheRepository) {
+        this.gradleLauncherFactory = gradleLauncherFactory;
+        this.classLoaderScope = classLoaderScope;
+        this.cacheRepository = cacheRepository;
+    }
+
+    public ClassLoaderScope buildAndCreateClassLoader(StartParameter startParameter) {
+        ClassPath classpath = createBuildSourceClasspath(startParameter);
+        if (classpath.isEmpty()) {
+            return classLoaderScope;
+        } else {
+            ClassLoaderScope childScope = classLoaderScope.createChild();
+            childScope.export(classpath);
+            childScope.lock();
+            return childScope;
+        }
+    }
+
+    ClassPath createBuildSourceClasspath(StartParameter startParameter) {
+        assert startParameter.getCurrentDir() != null && startParameter.getBuildFile() == null;
+
+        LOGGER.debug("Starting to build the build sources.");
+        if (!startParameter.getCurrentDir().isDirectory()) {
+            LOGGER.debug("Gradle source dir does not exist. We leave.");
+            return new DefaultClassPath();
+        }
+        LOGGER.info("================================================" + " Start building buildSrc");
+
+        // If we were not the most recent version of Gradle to build the buildSrc dir, then do a clean build
+        // Otherwise, just to a regular build
+        final PersistentCache buildSrcCache = createCache(startParameter);
+        try {
+            GradleLauncher gradleLauncher = buildGradleLauncher(startParameter);
+            return buildSrcCache.useCache("rebuild buildSrc", new BuildSrcUpdateFactory(buildSrcCache, gradleLauncher, new BuildSrcBuildListenerFactory()));
+        } finally {
+            // This isn't quite right. We should not unlock the classes until we're finished with them, and the classes may be used across multiple builds
+            buildSrcCache.close();
+        }
+    }
+
+    PersistentCache createCache(StartParameter startParameter) {
+        return cacheRepository
+                .cache(new File(startParameter.getCurrentDir(), ".gradle/noVersion/buildSrc"))
+                .withCrossVersionCache()
+                .withDisplayName("buildSrc state cache")
+                .withLockOptions(mode(FileLockManager.LockMode.None).useCrossVersionImplementation())
+                .withProperties(Collections.singletonMap("gradle.version", GradleVersion.current().getVersion()))
+                .open();
+    }
+
+    private GradleLauncher buildGradleLauncher(StartParameter startParameter) {
+        final StartParameter startParameterArg = startParameter.newInstance();
+        startParameterArg.setProjectProperties(startParameter.getProjectProperties());
+        startParameterArg.setSearchUpwards(false);
+        startParameterArg.setProfile(startParameter.isProfile());
+        return gradleLauncherFactory.newInstance(startParameterArg);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcBuildListenerFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcBuildListenerFactory.java
new file mode 100644
index 0000000..e52586d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcBuildListenerFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.buildsrc;
+
+import org.gradle.BuildAdapter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.plugins.EmbeddableJavaProject;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.initialization.ModelConfigurationListener;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Set;
+
+public class BuildSrcBuildListenerFactory {
+
+    private static final String DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE = "defaultBuildSourceScript.txt";
+
+    Listener create(boolean rebuild) {
+        return new Listener(rebuild);
+    }
+
+    public static class Listener extends BuildAdapter implements ModelConfigurationListener {
+        private Set<File> classpath;
+        private final boolean rebuild;
+
+        public Listener(boolean rebuild) {
+            this.rebuild = rebuild;
+        }
+
+        @Override
+        public void projectsLoaded(Gradle gradle) {
+            gradle.getRootProject().apply(WrapUtil.toMap("from", BuildSrcBuildListenerFactory.class.getResource(DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE)));
+        }
+
+        public Collection<File> getRuntimeClasspath() {
+            return classpath;
+        }
+
+        public void onConfigure(GradleInternal gradle) {
+            EmbeddableJavaProject projectInfo = gradle.getRootProject().getConvention().getPlugin(EmbeddableJavaProject.class);
+            gradle.getStartParameter().setTaskNames(rebuild ? projectInfo.getRebuildTasks() : projectInfo.getBuildTasks());
+            classpath = projectInfo.getRuntimeClasspath().getFiles();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java
new file mode 100644
index 0000000..31fe50b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.buildsrc;
+
+import org.gradle.GradleLauncher;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.cache.PersistentCache;
+import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.DefaultClassPath;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+public class BuildSrcUpdateFactory implements Factory<DefaultClassPath> {
+    private final PersistentCache cache;
+    private final GradleLauncher gradleLauncher;
+    private BuildSrcBuildListenerFactory listenerFactory;
+    private static final Logger LOGGER = Logging.getLogger(BuildSrcUpdateFactory.class);
+
+    public BuildSrcUpdateFactory(PersistentCache cache, GradleLauncher gradleLauncher, BuildSrcBuildListenerFactory listenerFactory) {
+        this.cache = cache;
+        this.gradleLauncher = gradleLauncher;
+        this.listenerFactory = listenerFactory;
+    }
+
+    public DefaultClassPath create() {
+        File markerFile = new File(cache.getBaseDir(), "built.bin");
+        final boolean rebuild = !markerFile.exists();
+
+        BuildSrcBuildListenerFactory.Listener listener = listenerFactory.create(rebuild);
+        gradleLauncher.addListener(listener);
+        gradleLauncher.run().rethrowFailure();
+
+        Collection<File> classpath = listener.getRuntimeClasspath();
+        LOGGER.debug("Gradle source classpath is: {}", classpath);
+        LOGGER.info("================================================" + " Finished building buildSrc");
+        try {
+            markerFile.createNewFile();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return new DefaultClassPath(classpath);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/environment/GradleBuildEnvironment.java b/subprojects/core/src/main/groovy/org/gradle/internal/environment/GradleBuildEnvironment.java
new file mode 100644
index 0000000..2f84e86
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/environment/GradleBuildEnvironment.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.environment;
+
+public interface GradleBuildEnvironment {
+
+    boolean isLongLivingProcess();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/AbstractMultiCauseException.java b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/AbstractMultiCauseException.java
new file mode 100644
index 0000000..ed70abc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/AbstractMultiCauseException.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.exceptions;
+
+import org.gradle.api.GradleException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class AbstractMultiCauseException extends GradleException implements MultiCauseException {
+    private final List<Throwable> causes = new CopyOnWriteArrayList<Throwable>();
+    private transient ThreadLocal<Boolean> hideCause = threadLocal();
+
+    public AbstractMultiCauseException(String message) {
+        super(message);
+    }
+
+    public AbstractMultiCauseException(String message, Throwable... causes) {
+        super(message);
+        this.causes.addAll(Arrays.asList(causes));
+    }
+
+    public AbstractMultiCauseException(String message, Iterable<? extends Throwable> causes) {
+        super(message);
+        initCauses(causes);
+    }
+
+    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+        inputStream.defaultReadObject();
+        hideCause = threadLocal();
+    }
+
+    private ThreadLocal<Boolean> threadLocal() {
+        return new ThreadLocal<Boolean>() {
+            @Override
+            protected Boolean initialValue() {
+                return false;
+            }
+        };
+    }
+
+    public List<? extends Throwable> getCauses() {
+        return causes;
+    }
+
+    @Override
+    public Throwable initCause(Throwable throwable) {
+        causes.clear();
+        causes.add(throwable);
+        return null;
+    }
+
+    public void initCauses(Iterable<? extends Throwable> causes) {
+        this.causes.clear();
+        for (Throwable cause : causes) {
+            this.causes.add(cause);
+        }
+    }
+
+    @Override
+    public Throwable getCause() {
+        if (hideCause.get()) {
+            return null;
+        }
+        return causes.isEmpty() ? null : causes.get(0);
+    }
+
+    @Override
+    public void printStackTrace(PrintStream printStream) {
+        PrintWriter writer = new PrintWriter(printStream);
+        printStackTrace(writer);
+        writer.flush();
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter printWriter) {
+        if (causes.size() <= 1) {
+            super.printStackTrace(printWriter);
+            return;
+        }
+
+        hideCause.set(true);
+        try {
+            super.printStackTrace(printWriter);
+            for (int i = 0; i < causes.size(); i++) {
+                Throwable cause = causes.get(i);
+                printWriter.format("Cause %s: ", i + 1);
+                cause.printStackTrace(printWriter);
+            }
+        } finally {
+            hideCause.set(false);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/Contextual.java b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/Contextual.java
new file mode 100644
index 0000000..fcc1a6a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/Contextual.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.exceptions;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation is attached to an exception class to indicate that it provides contextual information about the
+ * exception which might help the user determine what the failed operation was, or where it took place. Generally, this
+ * annotation is only attached to exceptions which chain lower-level exceptions.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface Contextual {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/FailureResolutionAware.java b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/FailureResolutionAware.java
new file mode 100644
index 0000000..2a91dba
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/FailureResolutionAware.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.exceptions;
+
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.logging.StyledTextOutput;
+
+/**
+ * Enhancement interface that exceptions can implement to provide additional information on how to resolve the failure.
+ */
+public interface FailureResolutionAware {
+
+    public void appendResolution(StyledTextOutput output, BuildClientMetaData clientMetaData);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/LocationAwareException.java b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/LocationAwareException.java
new file mode 100755
index 0000000..a1d17e4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/LocationAwareException.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.exceptions;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.GradleException;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.util.TreeVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@code LocationAwareException} is an exception which can be annotated with a location in a script.
+ */
+public class LocationAwareException extends GradleException implements FailureResolutionAware {
+    private final ScriptSource source;
+    private final Integer lineNumber;
+
+    public LocationAwareException(Throwable cause, ScriptSource source, Integer lineNumber) {
+        this.source = source;
+        this.lineNumber = lineNumber;
+        initCause(cause);
+    }
+
+    /**
+     * <p>Returns the source of the script where this exception occurred.</p>
+     *
+     * @return The source. May return null.
+     */
+    public ScriptSource getScriptSource() {
+        return source;
+    }
+
+    /**
+     * <p>Returns a description of the location of where this exception occurred.</p>
+     *
+     * @return The location description. May return null.
+     */
+    public String getLocation() {
+        if (source == null) {
+            return null;
+        }
+        String sourceMsg = StringUtils.capitalize(source.getDisplayName());
+        if (lineNumber == null) {
+            return sourceMsg;
+        }
+        return String.format("%s line: %d", sourceMsg, lineNumber);
+    }
+
+    /**
+     * Returns the line in the script where this exception occurred, if known.
+     *
+     * @return The line number, or null if not known.
+     */
+    public Integer getLineNumber() {
+        return lineNumber;
+    }
+
+    /**
+     * Returns the fully formatted error message, including the location.
+     *
+     * @return the message. May return null.
+     */
+    public String getMessage() {
+        String location = getLocation();
+        String message = getCause().getMessage();
+        if (location == null && message == null) {
+            return null;
+        }
+        if (location == null) {
+            return message;
+        }
+        if (message == null) {
+            return location;
+        }
+        return String.format("%s%n%s", location, message);
+    }
+
+    public void appendResolution(StyledTextOutput output, BuildClientMetaData clientMetaData) {
+        if (getCause() instanceof FailureResolutionAware) {
+            FailureResolutionAware resolutionAware = (FailureResolutionAware) getCause();
+            resolutionAware.appendResolution(output, clientMetaData);
+        }
+    }
+
+    /**
+     * Returns the reportable causes for this failure.
+     *
+     * @return The causes. Never returns null, returns an empty list if this exception has no reportable causes.
+     */
+    public List<Throwable> getReportableCauses() {
+        final List<Throwable> causes = new ArrayList<Throwable>();
+        visitCauses(getCause(), new TreeVisitor<Throwable>(){
+            @Override
+            public void node(Throwable node) {
+                causes.add(node);
+            }
+        });
+        return causes;
+    }
+
+    /**
+     * Visits the reportable causes for this failure.
+     */
+    public void visitReportableCauses(TreeVisitor<? super Throwable> visitor) {
+        visitor.node(this);
+        visitCauses(getCause(), visitor);
+    }
+
+    private void visitCauses(Throwable t, TreeVisitor<? super Throwable> visitor) {
+        if (t instanceof MultiCauseException) {
+            MultiCauseException multiCauseException = (MultiCauseException) t;
+            List<? extends Throwable> causes = multiCauseException.getCauses();
+            if (!causes.isEmpty()) {
+                visitor.startChildren();
+                for (Throwable cause : causes) {
+                    visitor.node(cause);
+                    if (cause.getClass().getAnnotation(Contextual.class) != null) {
+                        visitCauses(cause, visitor);
+                    }
+                }
+                visitor.endChildren();
+            }
+            return;
+        }
+
+        if (t.getCause() != null) {
+            visitor.startChildren();
+            Throwable next = findNearestContextualCause(t);
+            if (next != null) {
+                // Show any contextual cause recursively
+                visitor.node(next);
+                visitCauses(next, visitor);
+            } else {
+                // Show the direct cause of the last contextual cause.
+                visitor.node(t.getCause());
+            }
+            visitor.endChildren();
+        }
+    }
+
+    private Throwable findNearestContextualCause(Throwable t) {
+        if (t.getCause() == null) {
+            return null;
+        }
+        Throwable cause = t.getCause();
+        if (cause.getClass().getAnnotation(Contextual.class) != null) {
+            return cause;
+        }
+        return findNearestContextualCause(cause);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/MultiCauseException.java b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/MultiCauseException.java
new file mode 100644
index 0000000..efb3828
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/exceptions/MultiCauseException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.exceptions;
+
+import java.util.List;
+
+public interface MultiCauseException {
+    List<? extends Throwable> getCauses();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureHandler.java b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureHandler.java
new file mode 100644
index 0000000..16f3cad
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureHandler.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle;
+
+/**
+ * Notified when a deprecated feature is used.
+ *
+ * <p>Implementations need not be thread-safe.
+ */
+public interface DeprecatedFeatureHandler {
+    void deprecatedFeatureUsed(DeprecatedFeatureUsage usage);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureUsage.java b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureUsage.java
new file mode 100644
index 0000000..5971aa2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureUsage.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle;
+
+import org.codehaus.groovy.runtime.StackTraceUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An immutable description of the usage of a deprecated feature.
+ */
+public class DeprecatedFeatureUsage {
+    private final String message;
+    private final Class<?> calledFrom;
+    private final List<StackTraceElement> stack;
+
+    public DeprecatedFeatureUsage(String message, Class<?> calledFrom) {
+        this.message = message;
+        this.calledFrom = calledFrom;
+        this.stack = Collections.emptyList();
+    }
+
+    private DeprecatedFeatureUsage(DeprecatedFeatureUsage usage, List<StackTraceElement> stack) {
+        this.message = usage.message;
+        this.calledFrom = usage.calledFrom;
+        this.stack = stack;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public List<StackTraceElement> getStack() {
+        return stack;
+    }
+
+    /**
+     * Creates a copy of this usage with the stack trace populated. Implementation is a bit limited in that it assumes that
+     * this method is called from the same thread that triggered the usage.
+     */
+    public DeprecatedFeatureUsage withStackTrace() {
+        if (!stack.isEmpty()) {
+            return this;
+        }
+
+        StackTraceElement[] originalStack = StackTraceUtils.sanitize(new Exception()).getStackTrace();
+        int caller = 0;
+        while (caller < originalStack.length && !originalStack[caller].getClassName().startsWith(calledFrom.getName())) {
+            caller++;
+        }
+        while (caller < originalStack.length && originalStack[caller].getClassName().startsWith(calledFrom.getName())) {
+            caller++;
+        }
+        caller++;
+        List<StackTraceElement> stack = new ArrayList<StackTraceElement>();
+        for (; caller < originalStack.length; caller++) {
+            stack.add(originalStack[caller]);
+        }
+        return new DeprecatedFeatureUsage(this, stack);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/LoggingDeprecatedFeatureHandler.java b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/LoggingDeprecatedFeatureHandler.java
new file mode 100644
index 0000000..4b10d5d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/LoggingDeprecatedFeatureHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.SystemProperties;
+import org.gradle.util.SingleMessageLogger;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class LoggingDeprecatedFeatureHandler implements DeprecatedFeatureHandler {
+    private static final Logger LOGGER = Logging.getLogger(LoggingDeprecatedFeatureHandler.class);
+    private final Set<String> messages = new HashSet<String>();
+    private UsageLocationReporter locationReporter;
+
+    public LoggingDeprecatedFeatureHandler() {
+        this(new UsageLocationReporter() {
+            public void reportLocation(DeprecatedFeatureUsage usage, StringBuilder target) {
+            }
+        });
+    }
+
+    public LoggingDeprecatedFeatureHandler(UsageLocationReporter locationReporter) {
+        this.locationReporter = locationReporter;
+    }
+
+    public void setLocationReporter(UsageLocationReporter locationReporter) {
+        this.locationReporter = locationReporter;
+    }
+
+    public void deprecatedFeatureUsed(DeprecatedFeatureUsage usage) {
+        if (messages.add(usage.getMessage())) {
+            usage = usage.withStackTrace();
+            StringBuilder message = new StringBuilder();
+            locationReporter.reportLocation(usage, message);
+            if (message.length() > 0) {
+                message.append(SystemProperties.getLineSeparator());
+            }
+            message.append(usage.getMessage());
+            logTraceIfNecessary(usage.getStack(), message);
+            LOGGER.warn(message.toString());
+        }
+    }
+
+    private void logTraceIfNecessary(List<StackTraceElement> stack, StringBuilder message) {
+        if (isTraceLoggingEnabled()) {
+            for (StackTraceElement frame : stack) {
+                message.append(SystemProperties.getLineSeparator());
+                message.append("    ");
+                message.append(frame.toString());
+            }
+        }
+    }
+
+    private static boolean isTraceLoggingEnabled() {
+        return Boolean.getBoolean(SingleMessageLogger.ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java
new file mode 100644
index 0000000..8e1b375
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle;
+
+import net.jcip.annotations.ThreadSafe;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+ at ThreadSafe
+public class ScriptUsageLocationReporter implements ScriptExecutionListener, UsageLocationReporter {
+    private final Lock lock = new ReentrantLock();
+    private final Map<String, ScriptSource> scripts = new HashMap<String, ScriptSource>();
+
+    public void beforeScript(Script script) {
+        lock.lock();
+        try {
+            ScriptSource scriptSource = script.getScriptSource();
+            scripts.put(scriptSource.getFileName(), scriptSource);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void afterScript(Script script, Throwable result) {
+    }
+
+    public void reportLocation(DeprecatedFeatureUsage usage, StringBuilder target) {
+        lock.lock();
+        try {
+            doReportLocation(usage, target);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doReportLocation(DeprecatedFeatureUsage usage, StringBuilder target) {
+        List<StackTraceElement> stack = usage.getStack();
+        if (stack.isEmpty()) {
+            return;
+        }
+
+        StackTraceElement directCaller = stack.get(0);
+        if (scripts.containsKey(directCaller.getFileName())) {
+            reportStackTraceElement(directCaller, target);
+            return;
+        }
+
+        int caller = 1;
+        while (caller < stack.size() && stack.get(caller).getClassName().equals(directCaller.getClassName())) {
+            caller++;
+        }
+        if (caller == stack.size()) {
+            return;
+        }
+        StackTraceElement indirectCaller = stack.get(caller);
+        if (scripts.containsKey(indirectCaller.getFileName())) {
+            reportStackTraceElement(indirectCaller, target);
+        }
+    }
+
+    private void reportStackTraceElement(StackTraceElement stackTraceElement, StringBuilder target) {
+        ScriptSource scriptSource = scripts.get(stackTraceElement.getFileName());
+        target.append(StringUtils.capitalize(scriptSource.getDisplayName()));
+        if (stackTraceElement.getLineNumber() > 0) {
+            target.append(": line ");
+            target.append(stackTraceElement.getLineNumber());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/UsageLocationReporter.java b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/UsageLocationReporter.java
new file mode 100644
index 0000000..2891692
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/featurelifecycle/UsageLocationReporter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle;
+
+public interface UsageLocationReporter {
+    void reportLocation(DeprecatedFeatureUsage usage, StringBuilder target);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/CachingDirectedGraphWalker.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/CachingDirectedGraphWalker.java
new file mode 100644
index 0000000..a2eeb73
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/CachingDirectedGraphWalker.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graph;
+
+import org.gradle.util.GUtil;
+
+import java.util.*;
+
+/**
+ * A graph walker which collects the values reachable from a given set of start nodes. Handles cycles in the graph. Can
+ * be reused to perform multiple searches, and reuses the results of previous searches.
+ *
+ * Uses a variation of Tarjan's algorithm: http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+ */
+public class CachingDirectedGraphWalker<N, T> {
+    private final DirectedGraphWithEdgeValues<N, T> graph;
+    private List<N> startNodes = new LinkedList<N>();
+    private Set<NodeDetails<N, T>> strongComponents = new LinkedHashSet<NodeDetails<N, T>>();
+    private final Map<N, Set<T>> cachedNodeValues = new HashMap<N, Set<T>>();
+
+    public CachingDirectedGraphWalker(DirectedGraph<N, T> graph) {
+        this.graph = new GraphWithEmpyEdges<N, T>(graph);
+    }
+
+    public CachingDirectedGraphWalker(DirectedGraphWithEdgeValues<N, T> graph) {
+        this.graph = graph;
+    }
+
+    /**
+     * Adds some start nodes.
+     */
+    public CachingDirectedGraphWalker<N, T> add(N... values) {
+        add(Arrays.asList(values));
+        return this;
+    }
+
+    /**
+     * Adds some start nodes.
+     */
+    public CachingDirectedGraphWalker add(Iterable<? extends N> values) {
+        GUtil.addToCollection(startNodes, values);
+        return this;
+    }
+
+    /**
+     * Calculates the set of values of nodes reachable from the start nodes.
+     */
+    public Set<T> findValues() {
+        try {
+            return doSearch();
+        } finally {
+            startNodes.clear();
+        }
+    }
+
+    /**
+     * Returns the set of cycles seen in the graph.
+     */
+    public List<Set<N>> findCycles() {
+        findValues();
+        List<Set<N>> result = new ArrayList<Set<N>>();
+        for (NodeDetails<N, T> nodeDetails : strongComponents) {
+            Set<N> componentMembers = new LinkedHashSet<N>();
+            for (NodeDetails<N, T> componentMember : nodeDetails.componentMembers) {
+                componentMembers.add(componentMember.node);
+            }
+            result.add(componentMembers);
+        }
+        return result;
+    }
+
+    private Set<T> doSearch() {
+        int componentCount = 0;
+        Map<N, NodeDetails<N, T>> seenNodes = new HashMap<N, NodeDetails<N, T>>();
+        Map<Integer, NodeDetails<N, T>> components = new HashMap<Integer, NodeDetails<N, T>>();
+        LinkedList<N> queue = new LinkedList<N>(startNodes);
+
+        while (!queue.isEmpty()) {
+            N node = queue.getFirst();
+            NodeDetails<N, T> details = seenNodes.get(node);
+            if (details == null) {
+                // Have not visited this node yet. Push its successors onto the queue in front of this node and visit
+                // them
+
+                details = new NodeDetails<N, T>(node, componentCount++);
+                seenNodes.put(node, details);
+                components.put(details.component, details);
+
+                Set<T> cacheValues = cachedNodeValues.get(node);
+                if (cacheValues != null) {
+                    // Already visited this node
+                    details.values = cacheValues;
+                    details.finished = true;
+                    queue.removeFirst();
+                    continue;
+                }
+
+                graph.getNodeValues(node, details.values, details.successors);
+                for (N connectedNode : details.successors) {
+                    NodeDetails<N, T> connectedNodeDetails = seenNodes.get(connectedNode);
+                    if (connectedNodeDetails == null) {
+                        // Have not visited the successor node, so add to the queue for visiting
+                        queue.add(0, connectedNode);
+                    } else if (!connectedNodeDetails.finished) {
+                        // Currently visiting the successor node - we're in a cycle
+                        details.stronglyConnected = true;
+                    }
+                    // Else, already visited
+                }
+            } else {
+                // Have visited all of this node's successors
+                queue.removeFirst();
+
+                if (cachedNodeValues.containsKey(node)) {
+                    continue;
+                }
+
+                for (N connectedNode : details.successors) {
+                    NodeDetails<N, T> connectedNodeDetails = seenNodes.get(connectedNode);
+                    if (!connectedNodeDetails.finished) {
+                        // part of a cycle
+                        details.minSeen = Math.min(details.minSeen, connectedNodeDetails.minSeen);
+                        details.stronglyConnected = true;
+                    }
+                    details.values.addAll(connectedNodeDetails.values);
+                    graph.getEdgeValues(node, connectedNode, details.values);
+                }
+
+                if (details.minSeen != details.component) {
+                    // Part of a strongly connected component (ie cycle) - move values to root of the component
+                    // The root is the first node of the component we encountered
+                    NodeDetails<N, T> rootDetails = components.get(details.minSeen);
+                    rootDetails.values.addAll(details.values);
+                    details.values.clear();
+                    rootDetails.componentMembers.addAll(details.componentMembers);
+                } else {
+                    // Not part of a strongly connected component or the root of a strongly connected component
+                    for (NodeDetails<N, T> componentMember : details.componentMembers) {
+                        cachedNodeValues.put(componentMember.node, details.values);
+                        componentMember.finished = true;
+                        components.remove(componentMember.component);
+                    }
+                    if (details.stronglyConnected) {
+                        strongComponents.add(details);
+                    }
+                }
+            }
+        }
+
+        Set<T> values = new LinkedHashSet<T>();
+        for (N startNode : startNodes) {
+            values.addAll(cachedNodeValues.get(startNode));
+        }
+        return values;
+    }
+
+    private static class NodeDetails<N, T> {
+        private final int component;
+        private final N node;
+        private Set<T> values = new LinkedHashSet<T>();
+        private List<N> successors = new ArrayList<N>();
+        private Set<NodeDetails<N, T>> componentMembers = new LinkedHashSet<NodeDetails<N, T>>();
+        private int minSeen;
+        private boolean stronglyConnected;
+        private boolean finished;
+
+        public NodeDetails(N node, int component) {
+            this.node = node;
+            this.component = component;
+            minSeen = component;
+            componentMembers.add(this);
+        }
+    }
+
+    private static class GraphWithEmpyEdges<N, T> implements DirectedGraphWithEdgeValues<N, T> {
+        private final DirectedGraph<N, T> graph;
+
+        public GraphWithEmpyEdges(DirectedGraph<N, T> graph) {
+            this.graph = graph;
+        }
+
+        public void getEdgeValues(N from, N to, Collection<T> values) {
+        }
+
+        public void getNodeValues(N node, Collection<? super T> values, Collection<? super N> connectedNodes) {
+            graph.getNodeValues(node, values, connectedNodes);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraph.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraph.java
new file mode 100644
index 0000000..a1d8483
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraph.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.graph;
+
+import java.util.Collection;
+
+/**
+ * A directed graph with nodes of type N. Each node has a collection of values of type V.
+ */
+public interface DirectedGraph<N, V> {
+    void getNodeValues(N node, Collection<? super V> values, Collection<? super N> connectedNodes);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraphRenderer.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraphRenderer.java
new file mode 100644
index 0000000..9376900
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraphRenderer.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graph;
+
+import org.gradle.api.Action;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.logging.internal.StreamingStyledTextOutput;
+
+import java.util.*;
+
+import static org.gradle.logging.StyledTextOutput.Style.Info;
+
+public class DirectedGraphRenderer<N> {
+    private final GraphNodeRenderer<N> nodeRenderer;
+    private final DirectedGraph<N, ?> graph;
+    private boolean omittedDetails;
+
+    public DirectedGraphRenderer(GraphNodeRenderer<N> nodeRenderer, DirectedGraph<N, ?> graph) {
+        this.nodeRenderer = nodeRenderer;
+        this.graph = graph;
+    }
+
+    public void renderTo(N root, Appendable output) {
+        renderTo(root, new StreamingStyledTextOutput(output));
+    }
+
+    public void renderTo(N root, StyledTextOutput output) {
+        GraphRenderer renderer = new GraphRenderer(output);
+        Set<N> rendered = new HashSet<N>();
+        omittedDetails = false;
+        renderTo(root, renderer, rendered, false);
+        if (omittedDetails) {
+            output.println();
+            output.withStyle(Info).println("(*) - details omitted (listed previously)");
+        }
+    }
+
+    private void renderTo(final N node, GraphRenderer graphRenderer, Collection<N> rendered, boolean lastChild) {
+        final boolean alreadySeen = !rendered.add(node);
+
+        graphRenderer.visit(new Action<StyledTextOutput>() {
+            public void execute(StyledTextOutput output) {
+                nodeRenderer.renderTo(node, output);
+                if (alreadySeen) {
+                    output.text(" (*)");
+                }
+            }
+        }, lastChild);
+
+        if (alreadySeen) {
+            omittedDetails = true;
+            return;
+        }
+
+        List<N> children = new ArrayList<N>();
+        graph.getNodeValues(node, new HashSet<Object>(), children);
+        if (children.isEmpty()) {
+            return;
+        }
+        graphRenderer.startChildren();
+        for (int i = 0; i < children.size(); i++) {
+            N child = children.get(i);
+            renderTo(child, graphRenderer, rendered, i == children.size() - 1);
+        }
+        graphRenderer.completeChildren();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraphWithEdgeValues.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraphWithEdgeValues.java
new file mode 100644
index 0000000..5dcf7cb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/DirectedGraphWithEdgeValues.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.graph;
+
+import java.util.Collection;
+
+/**
+ * A directed graph with nodes of type N. Each edge has a collection of values of type V
+ */
+public interface DirectedGraphWithEdgeValues<N, V> extends DirectedGraph<N, V> {
+    void getEdgeValues(N from, N to, Collection<V> values);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphAggregator.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphAggregator.java
new file mode 100644
index 0000000..ae97929
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphAggregator.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.internal.graph;
+
+import java.util.*;
+
+/**
+ * Groups the nodes of a graph based on their reachability from a set of starting nodes.
+ */
+public class GraphAggregator<N> {
+    private final CachingDirectedGraphWalker<N, N> graphWalker;
+
+    public GraphAggregator(DirectedGraph<N, ?> graph) {
+        graphWalker = new CachingDirectedGraphWalker<N, N>(new ConnectedNodesAsValuesDirectedGraph<N>(graph));
+    }
+
+    public Result<N> group(Collection<? extends N> startNodes, Collection<? extends N> allNodes) {
+        Map<N, Set<N>> reachableByNode = new HashMap<N, Set<N>>();
+        Set<N> topLevelNodes = new LinkedHashSet<N>(allNodes);
+        for (N node : allNodes) {
+            Set<N> reachableNodes = graphWalker.add(node).findValues();
+            reachableByNode.put(node, reachableNodes);
+            topLevelNodes.removeAll(reachableNodes);
+        }
+        topLevelNodes.addAll(startNodes);
+        Map<N, Set<N>> nodes = new HashMap<N, Set<N>>();
+        for (N node : topLevelNodes) {
+            nodes.put(node, calculateReachableNodes(reachableByNode, node, topLevelNodes));
+        }
+        return new Result<N>(nodes, topLevelNodes);
+    }
+
+    private Set<N> calculateReachableNodes(Map<N, Set<N>> nodes, N node, Set<N> topLevelNodes) {
+        Set<N> reachableNodes = nodes.get(node);
+        reachableNodes.add(node);
+        Set<N> reachableStartNodes = new LinkedHashSet<N>(topLevelNodes);
+        reachableStartNodes.retainAll(reachableNodes);
+        reachableStartNodes.remove(node);
+        for (N startNode : reachableStartNodes) {
+            reachableNodes.removeAll(calculateReachableNodes(nodes, startNode, topLevelNodes));
+        }
+        return reachableNodes;
+    }
+
+    public static class Result<N> {
+        private final Map<N, Set<N>> nodes;
+        private final Set<N> topLevelNodes;
+
+        public Result(Map<N, Set<N>> nodes, Set<N> topLevelNodes) {
+            this.nodes = nodes;
+            this.topLevelNodes = topLevelNodes;
+        }
+
+        public Set<N> getNodes(N startNode) {
+            return nodes.get(startNode);
+        }
+
+        public Set<N> getTopLevelNodes() {
+            return topLevelNodes;
+        }
+    }
+
+    private static class ConnectedNodesAsValuesDirectedGraph<N> implements DirectedGraph<N, N> {
+        private final DirectedGraph<N, ?> graph;
+
+        private ConnectedNodesAsValuesDirectedGraph(DirectedGraph<N, ?> graph) {
+            this.graph = graph;
+        }
+
+        public void getNodeValues(N node, Collection<? super N> values, Collection<? super N> connectedNodes) {
+            Set<N> edges = new LinkedHashSet<N>();
+            graph.getNodeValues(node, new ArrayList(), edges);
+            values.addAll(edges);
+            connectedNodes.addAll(edges);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphNodeRenderer.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphNodeRenderer.java
new file mode 100644
index 0000000..e595847
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphNodeRenderer.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graph;
+
+import org.gradle.logging.StyledTextOutput;
+
+public interface GraphNodeRenderer<N> {
+    void renderTo(N node, StyledTextOutput output);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphRenderer.java b/subprojects/core/src/main/groovy/org/gradle/internal/graph/GraphRenderer.java
new file mode 100644
index 0000000..09494dd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/graph/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.internal.graph;
+
+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/internal/progress/BuildProgressFilter.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildProgressFilter.java
new file mode 100644
index 0000000..a2f4e69
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildProgressFilter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.api.Project;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.ProjectState;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.tasks.TaskState;
+
+//Filters out nested projects
+public class BuildProgressFilter implements BuildListener, TaskExecutionGraphListener, TaskExecutionListener, ProjectEvaluationListener {
+
+    private Gradle gradle;
+    private BuildProgressLogger logger;
+
+    public BuildProgressFilter(BuildProgressLogger logger) {
+        this.logger = logger;
+    }
+
+    public void buildStarted(Gradle gradle) {
+        if (gradle.getParent() == null) {
+            this.gradle = gradle;
+            logger.buildStarted();
+        }
+    }
+
+    public void settingsEvaluated(Settings settings) {
+        if (settings.getGradle() == gradle) {
+            logger.settingsEvaluated();
+        }
+    }
+
+    public void projectsLoaded(Gradle gradle) {
+        if (gradle == this.gradle) {
+            logger.projectsLoaded(gradle.getRootProject().getAllprojects().size());
+        }
+    }
+
+    public void graphPopulated(TaskExecutionGraph graph) {
+        if (gradle != null && graph == gradle.getTaskGraph()) {
+            logger.graphPopulated(graph.getAllTasks().size());
+        }
+    }
+
+    public void beforeEvaluate(Project project) {
+        if (project.getGradle() == gradle) {
+            logger.beforeEvaluate(project.getPath());
+        }
+    }
+
+    public void afterEvaluate(Project project, ProjectState state) {
+        if (project.getGradle() == gradle) {
+            logger.afterEvaluate(project.getPath());
+        }
+    }
+
+    public void projectsEvaluated(Gradle gradle) {}
+
+    public void beforeExecute(Task task) {}
+
+    public void afterExecute(Task task, TaskState state) {
+        if (task.getProject().getGradle() == gradle) {
+            logger.afterExecute();
+        }
+    }
+
+    public void buildFinished(BuildResult result) {
+        if (result.getGradle() == gradle) {
+            gradle = null;
+            logger.buildFinished();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildProgressLogger.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildProgressLogger.java
new file mode 100644
index 0000000..fe2a017
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildProgressLogger.java
@@ -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.internal.progress;
+
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class BuildProgressLogger implements LoggerProvider {
+
+    private final ProgressLoggerProvider loggerProvider;
+
+    private ProgressLogger buildProgress;
+    private ProgressLogger configurationProgress;
+    private Map<String, ProgressLogger> projectConfigurationProgress = new HashMap<String, ProgressLogger>();
+
+    private ProgressFormatter buildProgressFormatter;
+    private ProgressFormatter configurationProgressFormatter;
+
+    public BuildProgressLogger(ProgressLoggerFactory progressLoggerFactory) {
+        this(new ProgressLoggerProvider(progressLoggerFactory, BuildProgressLogger.class));
+    }
+
+    BuildProgressLogger(ProgressLoggerProvider loggerProvider) {
+        this.loggerProvider = loggerProvider;
+    }
+
+    public void buildStarted() {
+        buildProgress = loggerProvider.start("Initialize build", "Loading");
+    }
+
+    public void projectsLoaded(int totalProjects) {
+        configurationProgressFormatter = new SimpleProgressFormatter(totalProjects, "projects");
+        configurationProgress = loggerProvider.start("Configure projects", configurationProgressFormatter.getProgress());
+    }
+
+    public void graphPopulated(int totalTasks) {
+        configurationProgress.completed();
+        configurationProgress = null;
+
+        buildProgress.completed("Task graph ready");
+
+        buildProgressFormatter = new PercentageProgressFormatter("Building", totalTasks);
+        buildProgress = loggerProvider.start("Execute tasks", buildProgressFormatter.getProgress());
+    }
+
+    public void buildFinished() {
+        for (ProgressLogger l : projectConfigurationProgress.values()) {
+            l.completed();
+        }
+        if (configurationProgress != null) {
+            configurationProgress.completed();
+        }
+        buildProgress.completed();
+        buildProgress = null;
+        buildProgressFormatter = null;
+        configurationProgress = null;
+    }
+
+    public void afterExecute() {
+        buildProgress.progress(buildProgressFormatter.incrementAndGetProgress());
+    }
+
+    public void settingsEvaluated() {
+        buildProgress.progress("Configuring");
+    }
+
+    public void beforeEvaluate(String projectPath) {
+        if (configurationProgress != null) {
+            ProgressLogger logger = loggerProvider.start("Configure project " + projectPath, projectPath.equals(":") ? "root project" : projectPath);
+            projectConfigurationProgress.put(projectPath, logger);
+        }
+    }
+
+    public void afterEvaluate(String projectPath) {
+        if (configurationProgress != null) {
+            ProgressLogger logger = projectConfigurationProgress.remove(projectPath);
+            if (logger == null) {
+                throw new IllegalStateException("Unexpected afterEvaluate event received without beforeEvaluate");
+            }
+            logger.completed();
+            configurationProgress.progress(configurationProgressFormatter.incrementAndGetProgress());
+        }
+    }
+
+    public ProgressLogger getLogger() {
+        if (buildProgress == null) {
+            throw new IllegalStateException("Build logger is unavailable (it hasn't started or is already completed).");
+        }
+        return buildProgress;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/LoggerProvider.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/LoggerProvider.java
new file mode 100644
index 0000000..050b4da
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/LoggerProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import org.gradle.logging.ProgressLogger;
+
+public interface LoggerProvider {
+
+    ProgressLogger getLogger();
+
+    public static LoggerProvider NO_OP = new LoggerProvider() {
+        public ProgressLogger getLogger() {
+            return null;
+        }
+    };
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdentifier.java
new file mode 100644
index 0000000..0d796a2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdentifier.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+public class OperationIdentifier {
+    private final long id;
+    private final Long parentId;
+
+    public OperationIdentifier(long id, Long parentId) {
+        this.id = id;
+        this.parentId = parentId;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public Long getParentId() {
+        return parentId;
+    }
+
+    @Override
+    public String toString() {
+        return id + ":" + parentId;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchy.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchy.java
new file mode 100644
index 0000000..c18d2d9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchy.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class OperationsHierarchy {
+    final AtomicLong sharedCounter;
+    final LinkedList<Long> hierarchy;
+    private OperationIdentifier id;
+
+    public OperationsHierarchy(AtomicLong sharedCounter, LinkedList<Long> hierarchy) {
+        assert sharedCounter != null;
+        assert hierarchy != null;
+
+        this.sharedCounter = sharedCounter;
+        this.hierarchy = hierarchy;
+    }
+
+    public OperationIdentifier start() {
+        if (id == null) {
+            Long parent = hierarchy.isEmpty()? null: hierarchy.getLast();
+            long operationId = sharedCounter.incrementAndGet();
+            hierarchy.addLast(operationId);
+            id = new OperationIdentifier(operationId, parent);
+        }
+
+        return id;
+    }
+
+    public long currentOperationId() {
+        assertOperationStarted();
+        return id.getId();
+    }
+
+    public long completeCurrentOperation() {
+        assertOperationStarted();
+        assertHierarchyNotEmpty();
+        Long last = hierarchy.getLast();
+        if (id.getId() == last) {
+            //typical scenario
+            hierarchy.removeLast();
+        } else {
+            //this means that we're removing an operation id that has incomplete children
+            //this is not strictly correct, we might fail fast here
+            //however, this needs some discussion and making sure child operations are always completed before the parent
+            //(even in internal error conditions)
+            hierarchy.remove(id.getId());
+        }
+        long out = id.getId();
+        id = null;
+        return out;
+    }
+
+    private void assertOperationStarted() {
+        if (id == null) {
+            throw new NoActiveOperationFound("Cannot provide current operation id because the operation was not started or it was already completed.");
+        }
+    }
+
+    private void assertHierarchyNotEmpty() {
+        if (hierarchy.isEmpty()) {
+            throw new HierarchyEmptyException("Unable to provide operation id because there are no active operations in the hierarchy. Was the hierarchy list tinkered with?");
+        }
+    }
+
+    static class HierarchyEmptyException extends IllegalStateException {
+        public HierarchyEmptyException(String message) {
+            super(message);
+        }
+    }
+    static class NoActiveOperationFound extends IllegalStateException {
+        public NoActiveOperationFound(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchyKeeper.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchyKeeper.java
new file mode 100644
index 0000000..f9b6c69
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchyKeeper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import org.gradle.logging.ProgressLogger;
+
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class OperationsHierarchyKeeper {
+
+    private final AtomicLong sharedCounter = new AtomicLong();
+    private final ThreadLocal<LinkedList<Long>> hierarchy = new ThreadLocal<LinkedList<Long>>();
+
+    public OperationsHierarchy currentHierarchy(ProgressLogger parentHint) {
+        LinkedList<Long> h = hierarchy.get();
+        if (h == null) {
+            h = new LinkedList<Long>();
+            if (parentHint != null) {
+                h.add(parentHint.currentOperationId());
+            }
+            hierarchy.set(h);
+        }
+        return new OperationsHierarchy(sharedCounter, h);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/PercentageProgressFormatter.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/PercentageProgressFormatter.java
new file mode 100644
index 0000000..7fe6158
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/PercentageProgressFormatter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+class PercentageProgressFormatter implements ProgressFormatter {
+    private int current;
+    private int total;
+    private String prefix;
+
+    public PercentageProgressFormatter(String prefix, int total) {
+        this.total = total;
+        this.prefix = prefix;
+    }
+
+    public String incrementAndGetProgress() {
+        if (current == total) {
+            throw new IllegalStateException("Cannot increment beyond the total of: " + total);
+        }
+        current++;
+        return getProgress();
+    }
+
+    public String getProgress() {
+        return prefix + " " + (int) (current * 100.0 / total) + "%";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/ProgressFormatter.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/ProgressFormatter.java
new file mode 100644
index 0000000..380ef45
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/ProgressFormatter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+interface ProgressFormatter {
+    String incrementAndGetProgress();
+    String getProgress();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/ProgressLoggerProvider.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/ProgressLoggerProvider.java
new file mode 100644
index 0000000..5c66d00
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/ProgressLoggerProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+
+public class ProgressLoggerProvider {
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private final Class loggerClazz;
+
+    public ProgressLoggerProvider(ProgressLoggerFactory progressLoggerFactory, Class loggerClazz) {
+        this.progressLoggerFactory = progressLoggerFactory;
+        this.loggerClazz = loggerClazz;
+    }
+
+    public ProgressLogger start(String description, String shortDescription) {
+        return progressLoggerFactory.newOperation(loggerClazz).start(description, shortDescription);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/SimpleProgressFormatter.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/SimpleProgressFormatter.java
new file mode 100644
index 0000000..6f9973a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/SimpleProgressFormatter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+class SimpleProgressFormatter implements ProgressFormatter {
+    private final int total;
+    private int current;
+    private String postfix;
+
+    public SimpleProgressFormatter(int total, String postfix) {
+        this.total = total;
+        this.postfix = postfix;
+    }
+
+    public String incrementAndGetProgress() {
+        if (current == total) {
+            throw new IllegalStateException("Cannot increment beyond the total of: " + total);
+        }
+        current++;
+        return getProgress();
+    }
+
+    public String getProgress() {
+        return current + "/" + total + " " + postfix;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/BuildScopeServiceRegistryFactory.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/BuildScopeServiceRegistryFactory.java
new file mode 100644
index 0000000..90a826e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/BuildScopeServiceRegistryFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.service.ServiceRegistry;
+
+class BuildScopeServiceRegistryFactory implements ServiceRegistryFactory {
+    private final ServiceRegistry services;
+    private final CompositeStoppable registries = new CompositeStoppable();
+
+    public BuildScopeServiceRegistryFactory(ServiceRegistry services) {
+        this.services = services;
+    }
+
+    public ServiceRegistry createFor(Object domainObject) {
+        if (domainObject instanceof GradleInternal) {
+            GradleScopeServices gradleServices = new GradleScopeServices(services, (GradleInternal) domainObject);
+            registries.add(gradleServices);
+            return gradleServices;
+        }
+        if (domainObject instanceof SettingsInternal) {
+            SettingsScopeServices settingsServices = new SettingsScopeServices(services, (SettingsInternal) domainObject);
+            registries.add(settingsServices);
+            return settingsServices;
+        }
+        throw new IllegalArgumentException(String.format("Cannot create services for unknown domain object of type %s.",
+                domainObject.getClass().getSimpleName()));
+    }
+
+    public void close() {
+        registries.stop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/BuildScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/BuildScopeServices.java
new file mode 100644
index 0000000..494934f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/BuildScopeServices.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.service.scopes;
+
+import com.google.common.cache.CacheBuilder;
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.artifacts.DefaultModule;
+import org.gradle.api.internal.artifacts.DependencyManagementServices;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.*;
+import org.gradle.api.internal.plugins.DefaultPluginRegistry;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.*;
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory;
+import org.gradle.api.internal.project.taskfactory.DependencyAutoWireTaskFactory;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.project.taskfactory.TaskFactory;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.internal.CacheFactory;
+import org.gradle.cache.internal.DefaultCacheRepository;
+import org.gradle.cache.internal.DefaultCacheScopeMapping;
+import org.gradle.configuration.*;
+import org.gradle.configuration.project.*;
+import org.gradle.groovy.scripts.DefaultScriptCompilerFactory;
+import org.gradle.groovy.scripts.ScriptCompilerFactory;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.internal.*;
+import org.gradle.initialization.*;
+import org.gradle.internal.Factory;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.classloader.ClassLoaderFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.LongIdGenerator;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.messaging.actor.ActorFactory;
+import org.gradle.messaging.actor.internal.DefaultActorFactory;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.plugin.internal.PluginResolverFactory;
+import org.gradle.process.internal.DefaultWorkerProcessFactory;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
+import org.gradle.profile.ProfileEventAdapter;
+import org.gradle.profile.ProfileListener;
+import org.gradle.util.GradleVersion;
+
+/**
+ * Contains the singleton services for a single build invocation.
+ */
+public class BuildScopeServices extends DefaultServiceRegistry {
+    public BuildScopeServices(final ServiceRegistry parent, final StartParameter startParameter) {
+        super(parent);
+        register(new Action<ServiceRegistration>() {
+            public void execute(ServiceRegistration registration) {
+                add(StartParameter.class, startParameter);
+                for (PluginServiceRegistry pluginServiceRegistry : parent.getAll(PluginServiceRegistry.class)) {
+                    pluginServiceRegistry.registerBuildServices(registration);
+                }
+            }
+        });
+    }
+
+    protected ImportsReader createImportsReader() {
+        return new ImportsReader();
+    }
+
+    protected TimeProvider createTimeProvider() {
+        return new TrueTimeProvider();
+    }
+
+    protected ProjectRegistry<ProjectInternal> createProjectRegistry() {
+        return new DefaultProjectRegistry<ProjectInternal>();
+    }
+
+    protected IProjectFactory createProjectFactory(Instantiator instantiator, ProjectRegistry<ProjectInternal> projectRegistry) {
+        return new ProjectFactory(instantiator, projectRegistry);
+    }
+
+    protected ListenerManager createListenerManager(ListenerManager listenerManager) {
+        return listenerManager.createChild();
+    }
+
+    protected ClassPathRegistry createClassPathRegistry() {
+        return new DefaultClassPathRegistry(
+                new DefaultClassPathProvider(get(ModuleRegistry.class)),
+                new DependencyClassPathProvider(get(ModuleRegistry.class),
+                        get(PluginModuleRegistry.class)),
+                get(WorkerProcessClassPathProvider.class));
+    }
+
+    protected WorkerProcessClassPathProvider createWorkerProcessClassPathProvider(CacheRepository cacheRepository, ModuleRegistry moduleRegistry) {
+        return new WorkerProcessClassPathProvider(cacheRepository, moduleRegistry);
+    }
+
+    protected IsolatedAntBuilder createIsolatedAntBuilder() {
+        return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class), get(ClassLoaderFactory.class));
+    }
+
+    protected ActorFactory createActorFactory() {
+        return new DefaultActorFactory(get(ExecutorFactory.class));
+    }
+
+    protected IGradlePropertiesLoader createGradlePropertiesLoader() {
+        return new DefaultGradlePropertiesLoader(get(StartParameter.class));
+    }
+
+    protected BuildLoader createBuildLoader() {
+        return new ProjectPropertySettingBuildLoader(
+                get(IGradlePropertiesLoader.class),
+                new InstantiatingBuildLoader(get(IProjectFactory.class)));
+    }
+
+    protected CacheRepository createCacheRepository() {
+        CacheFactory factory = get(CacheFactory.class);
+        StartParameter startParameter = get(StartParameter.class);
+        DefaultCacheScopeMapping scopeMapping = new DefaultCacheScopeMapping(startParameter.getGradleUserHomeDir(), startParameter.getProjectCacheDir(), GradleVersion.current());
+        return new DefaultCacheRepository(
+                scopeMapping,
+                startParameter.getCacheUsage(),
+                factory);
+    }
+
+    protected ProjectEvaluator createProjectEvaluator() {
+        ConfigureActionsProjectEvaluator withActionsEvaluator = new ConfigureActionsProjectEvaluator(
+                new PluginsProjectConfigureActions(get(ClassLoaderRegistry.class).getPluginsClassLoader()),
+                new BuildScriptProcessor(get(ScriptPluginFactory.class)),
+                new DelayedConfigurationActions(),
+                new TaskModelRealizingConfigurationAction(),
+                new ProjectDependencies2TaskResolver()
+        );
+        return new LifecycleProjectEvaluator(withActionsEvaluator);
+    }
+
+    protected ITaskFactory createITaskFactory() {
+        return new DependencyAutoWireTaskFactory(
+                new AnnotationProcessingTaskFactory(
+                        new TaskFactory(
+                                get(ClassGenerator.class))));
+    }
+
+    protected ScriptCompilerFactory createScriptCompileFactory(ListenerManager listenerManager, EmptyScriptGenerator emptyScriptGenerator, FileCacheBackedScriptClassCompiler scriptCompiler) {
+        ScriptExecutionListener scriptExecutionListener = listenerManager.getBroadcaster(ScriptExecutionListener.class);
+        return new DefaultScriptCompilerFactory(
+                new CachingScriptClassCompiler(
+                        new ShortCircuitEmptyScriptCompiler(
+                                scriptCompiler,
+                                emptyScriptGenerator)),
+                new DefaultScriptRunnerFactory(
+                        scriptExecutionListener));
+    }
+
+    protected EmptyScriptGenerator createEmptyScriptGenerator() {
+        return new AsmBackedEmptyScriptGenerator();
+    }
+
+    protected FileCacheBackedScriptClassCompiler createFileCacheBackedScriptClassCompiler(CacheRepository cacheRepository, EmptyScriptGenerator emptyScriptGenerator, final StartParameter startParameter, ProgressLoggerFactory progressLoggerFactory) {
+        CacheValidator scriptCacheInvalidator = new CacheValidator() {
+            public boolean isValid() {
+                return !startParameter.isRecompileScripts();
+            }
+        };
+        return new FileCacheBackedScriptClassCompiler(
+                cacheRepository,
+                scriptCacheInvalidator,
+                new DefaultScriptCompilationHandler(
+                        emptyScriptGenerator),
+                progressLoggerFactory);
+    }
+
+    protected ScriptPluginFactory createScriptObjectConfigurerFactory() {
+        return new DefaultScriptPluginFactory(
+                get(ScriptCompilerFactory.class),
+                get(ImportsReader.class),
+                getFactory(LoggingManagerInternal.class),
+                get(Instantiator.class),
+                get(ScriptHandlerFactory.class),
+                get(PluginResolverFactory.class),
+                get(FileLookup.class)
+        );
+    }
+
+    protected InitScriptHandler createInitScriptHandler() {
+        return new InitScriptHandler(
+                new DefaultInitScriptProcessor(
+                        get(ScriptPluginFactory.class),
+                        get(ScriptHandlerFactory.class)
+                )
+        );
+    }
+
+    protected SettingsProcessor createSettingsProcessor() {
+        return new PropertiesLoadingSettingsProcessor(
+                new ScriptEvaluatingSettingsProcessor(
+                        get(ScriptPluginFactory.class),
+                        get(ScriptHandlerFactory.class),
+                        new SettingsFactory(
+                                get(Instantiator.class),
+                                get(ServiceRegistryFactory.class)
+                        ),
+                        get(IGradlePropertiesLoader.class)),
+                get(IGradlePropertiesLoader.class));
+    }
+
+    protected ExceptionAnalyser createExceptionAnalyser() {
+        return new MultipleBuildFailuresExceptionAnalyser(new DefaultExceptionAnalyser(get(ListenerManager.class)));
+    }
+
+    protected ScriptHandlerFactory createScriptHandlerFactory() {
+        return new DefaultScriptHandlerFactory(
+                get(DependencyManagementServices.class),
+                get(FileResolver.class),
+                new DependencyMetaDataProviderImpl());
+    }
+
+    protected PluginResolverFactory createPluginResolverFactory() {
+        return new PluginResolverFactory(
+                get(PluginRegistry.class),
+                get(Instantiator.class),
+                get(DependencyManagementServices.class),
+                get(FileResolver.class),
+                new DependencyMetaDataProviderImpl(),
+                get(DocumentationRegistry.class),
+                get(CacheRepository.class)
+        );
+    }
+
+    protected Factory<WorkerProcessBuilder> createWorkerProcessFactory(StartParameter startParameter, MessagingServer messagingServer, ClassPathRegistry classPathRegistry,
+                                                                       FileResolver fileResolver) {
+        return new DefaultWorkerProcessFactory(
+                startParameter.getLogLevel(),
+                messagingServer,
+                classPathRegistry,
+                fileResolver,
+                new LongIdGenerator());
+    }
+
+    protected BuildConfigurer createBuildConfigurer() {
+        return new DefaultBuildConfigurer();
+    }
+
+    protected ProjectAccessListener createProjectAccessListener() {
+        return new DefaultProjectAccessListener();
+    }
+
+    protected ProfileEventAdapter createProfileEventAdapter() {
+        return new ProfileEventAdapter(get(BuildRequestMetaData.class), get(TimeProvider.class), get(ListenerManager.class).getBroadcaster(ProfileListener.class));
+    }
+
+    protected PluginRegistry createPluginRegistry() {
+        return new DefaultPluginRegistry(get(ClassLoaderRegistry.class).getPluginsClassLoader(), new DependencyInjectingInstantiator(this));
+    }
+
+    protected ServiceRegistryFactory createServiceRegistryFactory(final ServiceRegistry services) {
+        return new BuildScopeServiceRegistryFactory(services);
+    }
+
+    protected ClassLoaderCache createClassLoaderCache() {
+        return new DefaultClassLoaderCache(CacheBuilder.newBuilder().<DefaultClassLoaderCache.Key, ClassLoader>build());
+    }
+
+    protected ClassLoaderScope createClassLoaderScope(ClassLoaderRegistry classLoaderRegistry, ClassLoaderCache classLoaderCache) {
+        return new RootClassLoaderScope(classLoaderRegistry.getGradleApiClassLoader(), classLoaderCache);
+    }
+
+    private class DependencyMetaDataProviderImpl implements DependencyMetaDataProvider {
+        public ModuleInternal getModule() {
+            return new DefaultModule("unspecified", "unspecified", Project.DEFAULT_VERSION, Project.DEFAULT_STATUS);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/GlobalScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/GlobalScopeServices.java
new file mode 100755
index 0000000..4f6eb0f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/GlobalScopeServices.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.service.scopes;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.changedetection.state.InMemoryTaskArtifactCache;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.DefaultPluginModuleRegistry;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.api.internal.file.*;
+import org.gradle.cache.internal.*;
+import org.gradle.cache.internal.locklistener.DefaultFileLockContentionHandler;
+import org.gradle.cache.internal.locklistener.FileLockContentionHandler;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.initialization.*;
+import org.gradle.internal.classloader.ClassLoaderFactory;
+import org.gradle.internal.classloader.DefaultClassLoaderFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.environment.GradleBuildEnvironment;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceLocator;
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.MessagingServices;
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
+
+import java.util.List;
+
+/**
+ * Defines the services shared by all builds in a given process.
+ */
+public class GlobalScopeServices {
+
+    private GradleBuildEnvironment environment;
+
+    public GlobalScopeServices(final boolean longLiving) {
+        this.environment = new GradleBuildEnvironment() {
+            public boolean isLongLivingProcess() {
+                return longLiving;
+            }
+        };
+    }
+
+    void configure(ServiceRegistration registration, ClassLoaderRegistry classLoaderRegistry) {
+        final List<PluginServiceRegistry> pluginServiceFactories = new ServiceLocator(classLoaderRegistry.getRuntimeClassLoader(), classLoaderRegistry.getCoreImplClassLoader(), classLoaderRegistry.getPluginsClassLoader()).getAll(PluginServiceRegistry.class);
+        for (PluginServiceRegistry pluginServiceRegistry : pluginServiceFactories) {
+            registration.add(PluginServiceRegistry.class, pluginServiceRegistry);
+            pluginServiceRegistry.registerGlobalServices(registration);
+        }
+    }
+
+    GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry services) {
+        return new DefaultGradleLauncherFactory(services);
+    }
+
+    TemporaryFileProvider createTemporaryFileProvider() {
+        return new TmpDirTemporaryFileProvider();
+    }
+
+    GradleBuildEnvironment createGradleBuildEnvironment() {
+        return environment;
+    }
+
+    CommandLineConverter<StartParameter> createCommandLine2StartParameterConverter() {
+        return new DefaultCommandLineConverter();
+    }
+
+    ClassPathRegistry createClassPathRegistry(ModuleRegistry moduleRegistry, PluginModuleRegistry pluginModuleRegistry) {
+        return new DefaultClassPathRegistry(
+                new DefaultClassPathProvider(moduleRegistry),
+                new DynamicModulesClassPathProvider(moduleRegistry,
+                        pluginModuleRegistry));
+    }
+
+    DefaultModuleRegistry createModuleRegistry() {
+        return new DefaultModuleRegistry();
+    }
+
+    DocumentationRegistry createDocumentationRegistry() {
+        return new DocumentationRegistry();
+    }
+
+    PluginModuleRegistry createPluginModuleRegistry(ModuleRegistry moduleRegistry) {
+        return new DefaultPluginModuleRegistry(moduleRegistry);
+    }
+
+    protected CacheFactory createCacheFactory(FileLockManager fileLockManager) {
+        return new DefaultCacheFactory(fileLockManager);
+    }
+
+    DefaultClassLoaderRegistry createClassLoaderRegistry(ClassPathRegistry classPathRegistry, ClassLoaderFactory classLoaderFactory) {
+        return new DefaultClassLoaderRegistry(classPathRegistry, classLoaderFactory);
+    }
+
+    ListenerManager createListenerManager() {
+        return new DefaultListenerManager();
+    }
+   
+    ClassLoaderFactory createClassLoaderFactory() {
+        return new DefaultClassLoaderFactory();
+    }
+
+    MessagingServices createMessagingServices(ClassLoaderRegistry classLoaderRegistry) {
+        return new MessagingServices(classLoaderRegistry.getPluginsClassLoader());
+    }
+
+    MessagingServer createMessagingServer(MessagingServices messagingServices) {
+        return messagingServices.get(MessagingServer.class);
+    }
+
+    ClassGenerator createClassGenerator() {
+        return new AsmBackedClassGenerator();
+    }
+
+    Instantiator createInstantiator(ClassGenerator classGenerator) {
+        return new ClassGeneratorBackedInstantiator(classGenerator, new DirectInstantiator());
+    }
+
+    ExecutorFactory createExecutorFactory() {
+        return new DefaultExecutorFactory();
+    }
+
+    FileLockManager createFileLockManager(ProcessEnvironment processEnvironment, FileLockContentionHandler fileLockContentionHandler) {
+        return new DefaultFileLockManager(
+                new DefaultProcessMetaDataProvider(
+                        processEnvironment),
+                fileLockContentionHandler);
+    }
+
+    InMemoryTaskArtifactCache createInMemoryTaskArtifactCache() {
+        return new InMemoryTaskArtifactCache();
+    }
+
+    DefaultFileLockContentionHandler createFileLockContentionHandler(ExecutorFactory executorFactory, MessagingServices messagingServices) {
+        return new DefaultFileLockContentionHandler(
+                executorFactory,
+                messagingServices.get(InetAddressFactory.class)
+        );
+    }
+
+    FileResolver createFileResolver(FileSystem fileSystem) {
+        return new IdentityFileResolver(fileSystem);
+    }
+
+    FileLookup createFileLookup(FileSystem fileSystem) {
+        return new DefaultFileLookup(fileSystem);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/GradleScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/GradleScopeServices.java
new file mode 100644
index 0000000..7f30327
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/GradleScopeServices.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes;
+
+import org.gradle.StartParameter;
+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.plugins.DefaultPluginContainer;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.options.OptionReader;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.execution.*;
+import org.gradle.execution.commandline.CommandLineTaskConfigurer;
+import org.gradle.execution.commandline.CommandLineTaskParser;
+import org.gradle.execution.taskgraph.DefaultTaskGraphExecuter;
+import org.gradle.execution.taskgraph.TaskPlanExecutor;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.ListenerManager;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Contains the services for a given {@link GradleInternal} instance.
+ */
+public class GradleScopeServices extends DefaultServiceRegistry {
+
+    private final CompositeStoppable registries = new CompositeStoppable();
+
+    public GradleScopeServices(ServiceRegistry parent, GradleInternal gradle) {
+        super(parent);
+        add(GradleInternal.class, gradle);
+        addProvider(new TaskExecutionServices());
+    }
+
+    TaskSelector createTaskSelector(GradleInternal gradle) {
+        return new TaskSelector(gradle);
+    }
+
+    OptionReader createOptionReader() {
+        return new OptionReader();
+    }
+
+    CommandLineTaskParser createCommandLineTaskParser(OptionReader optionReader) {
+        return new CommandLineTaskParser(new CommandLineTaskConfigurer(optionReader));
+    }
+
+    BuildExecuter createBuildExecuter(CommandLineTaskParser commandLineTaskParser, TaskSelector taskSelector) {
+        List<BuildConfigurationAction> configs = new LinkedList<BuildConfigurationAction>();
+        if (get(StartParameter.class).isConfigureOnDemand()) {
+            configs.add(new ProjectEvaluatingAction());
+        }
+        configs.add(new DefaultTasksBuildExecutionAction());
+        configs.add(new ExcludedTaskFilteringBuildConfigurationAction());
+        configs.add(new TaskNameResolvingBuildConfigurationAction(commandLineTaskParser, taskSelector));
+
+        return new DefaultBuildExecuter(
+                configs,
+                asList(new DryRunBuildExecutionAction(),
+                        new SelectedTaskExecutionAction()));
+    }
+
+    ProjectFinder createProjectFinder(final GradleInternal gradle) {
+        return new ProjectFinder() {
+            public ProjectInternal getProject(String path) {
+                return gradle.getRootProject().project(path);
+            }
+        };
+    }
+
+    TaskGraphExecuter createTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor) {
+        return new DefaultTaskGraphExecuter(listenerManager, taskPlanExecutor);
+    }
+
+    ServiceRegistryFactory createServiceRegistryFactory(final ServiceRegistry services) {
+        return new ServiceRegistryFactory() {
+            public ServiceRegistry createFor(Object domainObject) {
+                if (domainObject instanceof ProjectInternal) {
+                    ProjectScopeServices projectScopeServices = new ProjectScopeServices(services, (ProjectInternal) domainObject);
+                    registries.add(projectScopeServices);
+                    return projectScopeServices;
+                }
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    PluginRegistry createPluginRegistry(PluginRegistry parentRegistry) {
+        return parentRegistry.createChild(get(GradleInternal.class).getClassLoaderScope(), new DependencyInjectingInstantiator(this));
+    }
+
+    PluginContainer createPluginContainer(GradleInternal gradle, PluginRegistry pluginRegistry) {
+        return new DefaultPluginContainer<GradleInternal>(pluginRegistry, gradle);
+    }
+
+    @Override
+    public void close() {
+        registries.stop();
+        super.close();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/PluginServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/PluginServiceRegistry.java
new file mode 100644
index 0000000..caf5a3d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/PluginServiceRegistry.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes;
+
+import org.gradle.internal.service.ServiceRegistration;
+
+/**
+ * Can be implemented by plugins to provide services in various scopes.
+ *
+ * <p>Implementations are discovered using the JAR service locator mechanism (see {@link org.gradle.internal.service.ServiceLocator}).
+ */
+public interface PluginServiceRegistry {
+    /**
+     * Called once per process, to register any globally scoped services. These services are reused across builds in the same process.
+     */
+    void registerGlobalServices(ServiceRegistration registration);
+
+    /**
+     * Called once per build, to registry any build scoped services. These services are discarded at the end of the current build.
+     * All global scoped services are visible to the build scope services, but not vice versa.
+     */
+    void registerBuildServices(ServiceRegistration registration);
+
+    /**
+     * Called once per project per build, to registry any project scoped services. These services are discarded at the end of the current build.
+     * All global and build scoped services are visible to the project scope services, but not vice versa.
+     */
+    void registerProjectServices(ServiceRegistration registration);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/ProjectScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/ProjectScopeServices.java
new file mode 100644
index 0000000..c9ac691
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/ProjectScopeServices.java
@@ -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.internal.service.scopes;
+
+import org.gradle.api.Action;
+import org.gradle.api.AntBuilder;
+import org.gradle.api.component.SoftwareComponentContainer;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.artifacts.DependencyManagementServices;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.ProjectBackedModule;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.component.DefaultSoftwareComponentContainer;
+import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.plugins.DefaultPluginContainer;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.DefaultAntBuilderFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ant.AntLoggingAdapter;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.configuration.project.DefaultProjectConfigurationActionContainer;
+import org.gradle.configuration.project.ProjectConfigurationActionContainer;
+import org.gradle.initialization.ProjectAccessListener;
+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.ServiceRegistration;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.model.ModelRules;
+import org.gradle.model.internal.DefaultModelRegistry;
+import org.gradle.model.internal.ModelRegistry;
+import org.gradle.model.internal.ModelRegistryBackedModelRules;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+import org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry;
+
+import java.io.File;
+
+/**
+ * Contains the services for a given project.
+ */
+public class ProjectScopeServices extends DefaultServiceRegistry {
+    private final ProjectInternal project;
+
+    public ProjectScopeServices(final ServiceRegistry parent, final ProjectInternal project) {
+        super(parent);
+        this.project = project;
+        register(new Action<ServiceRegistration>() {
+            public void execute(ServiceRegistration registration) {
+                registration.add(DomainObjectContext.class, project);
+                parent.get(DependencyManagementServices.class).addDslServices(registration);
+                for (PluginServiceRegistry pluginServiceRegistry : parent.getAll(PluginServiceRegistry.class)) {
+                    pluginServiceRegistry.registerProjectServices(registration);
+                }
+            }
+        });
+    }
+
+    protected PluginRegistry createPluginRegistry(PluginRegistry parentRegistry) {
+        return parentRegistry.createChild(project.getClassLoaderScope().createChild().lock(), new DependencyInjectingInstantiator(this));
+    }
+
+    protected FileResolver createFileResolver() {
+        return new BaseDirFileResolver(get(FileSystem.class), project.getProjectDir());
+    }
+
+    protected LoggingManagerInternal createLoggingManager() {
+        return getFactory(LoggingManagerInternal.class).create();
+    }
+
+    protected ProjectConfigurationActionContainer createProjectConfigurationActionContainer() {
+        return new DefaultProjectConfigurationActionContainer();
+    }
+
+    protected DefaultFileOperations createFileOperations() {
+        return new DefaultFileOperations(get(FileResolver.class), project.getTasks(), get(TemporaryFileProvider.class), get(Instantiator.class), get(FileLookup.class));
+    }
+
+    protected TemporaryFileProvider createTemporaryFileProvider() {
+        return new DefaultTemporaryFileProvider(new Factory<File>() {
+            public File create() {
+                return new File(project.getBuildDir(), "tmp");
+            }
+        });
+    }
+
+    protected Factory<AntBuilder> createAntBuilderFactory() {
+        return new DefaultAntBuilderFactory(new AntLoggingAdapter(), project);
+    }
+
+    protected ToolingModelBuilderRegistry createToolingModelRegistry() {
+        return new DefaultToolingModelBuilderRegistry();
+    }
+
+    protected PluginContainer createPluginContainer() {
+        return new DefaultPluginContainer(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, get(ProjectAccessListener.class));
+    }
+
+    protected SoftwareComponentContainer createSoftwareComponentContainer() {
+        Instantiator instantiator = get(Instantiator.class);
+        return instantiator.newInstance(DefaultSoftwareComponentContainer.class, instantiator);
+    }
+
+    protected ProjectFinder createProjectFinder() {
+        return new ProjectFinder() {
+            public ProjectInternal getProject(String path) {
+                return project.project(path);
+            }
+        };
+    }
+
+    protected ModelRegistry createModelRegistry() {
+        return new DefaultModelRegistry();
+    }
+
+    protected ModelRules createModelRules() {
+        return get(Instantiator.class).newInstance(ModelRegistryBackedModelRules.class, get(ModelRegistry.class));
+    }
+
+    protected ScriptHandler createScriptHandler() {
+        ScriptHandlerFactory factory = new DefaultScriptHandlerFactory(
+                get(DependencyManagementServices.class),
+                get(FileResolver.class),
+                get(DependencyMetaDataProvider.class));
+        return factory.create(project.getBuildScriptSource(), project.getClassLoaderScope(), project);
+    }
+
+    protected DependencyMetaDataProvider createDependencyMetaDataProvider() {
+        return new DependencyMetaDataProvider() {
+            public ModuleInternal getModule() {
+                return new ProjectBackedModule(project);
+            }
+        };
+    }
+
+    protected ServiceRegistryFactory createServiceRegistryFactory(final ServiceRegistry services) {
+        return new ServiceRegistryFactory() {
+            public ServiceRegistry createFor(Object domainObject) {
+                if (domainObject instanceof TaskInternal) {
+                    return new TaskScopeServices(services, project, (TaskInternal) domainObject);
+                }
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/ServiceRegistryFactory.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/ServiceRegistryFactory.java
new file mode 100644
index 0000000..9077d3a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/ServiceRegistryFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.service.scopes;
+
+import org.gradle.internal.service.ServiceRegistry;
+
+/**
+ * A hierarchical service registry.
+ */
+public interface ServiceRegistryFactory {
+    /**
+     * Creates the services for the given domain object.
+     *
+     * @param domainObject The domain object.
+     * @return The registry containing the services for the domain object.
+     */
+    ServiceRegistry createFor(Object domainObject);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/SettingsScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/SettingsScopeServices.java
new file mode 100644
index 0000000..67e8019
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/SettingsScopeServices.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes;
+
+import org.gradle.api.internal.DependencyInjectingInstantiator;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.file.BaseDirFileResolver;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.plugins.DefaultPluginContainer;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.initialization.DefaultProjectDescriptorRegistry;
+import org.gradle.initialization.ProjectDescriptorRegistry;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+
+public class SettingsScopeServices extends DefaultServiceRegistry {
+    private final SettingsInternal settings;
+
+    public SettingsScopeServices(ServiceRegistry parent, final SettingsInternal settings) {
+        super(parent);
+        this.settings = settings;
+    }
+
+    protected FileResolver createFileResolver() {
+        return new BaseDirFileResolver(get(FileSystem.class), settings.getSettingsDir());
+    }
+
+    protected PluginRegistry createPluginRegistry(PluginRegistry parentRegistry) {
+        return parentRegistry.createChild(settings.getClassLoaderScope(), new DependencyInjectingInstantiator(this));
+    }
+
+    protected PluginContainer createPluginContainer() {
+        return new DefaultPluginContainer(get(PluginRegistry.class), settings);
+    }
+
+    protected ProjectDescriptorRegistry createProjectDescriptorRegistry() {
+        return new DefaultProjectDescriptorRegistry();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/TaskExecutionServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/TaskExecutionServices.java
new file mode 100644
index 0000000..67cb586
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/TaskExecutionServices.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.service.scopes;
+
+import org.gradle.StartParameter;
+import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
+import org.gradle.api.internal.changedetection.changes.DefaultTaskArtifactStateRepository;
+import org.gradle.api.internal.changedetection.changes.ShortCircuitTaskArtifactStateRepository;
+import org.gradle.api.internal.changedetection.state.*;
+import org.gradle.api.internal.hash.DefaultHasher;
+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.cache.internal.CacheDecorator;
+import org.gradle.execution.taskgraph.TaskPlanExecutor;
+import org.gradle.execution.taskgraph.TaskPlanExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.environment.GradleBuildEnvironment;
+import org.gradle.internal.id.RandomLongIdGenerator;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.listener.ListenerManager;
+import org.gradle.messaging.serialize.DefaultSerializerRegistry;
+import org.gradle.messaging.serialize.SerializerRegistry;
+
+public class TaskExecutionServices {
+    TaskExecuter createTaskExecuter(TaskArtifactStateRepository repository, ListenerManager listenerManager) {
+        return new ExecuteAtMostOnceTaskExecuter(
+                new SkipOnlyIfTaskExecuter(
+                        new SkipTaskWithNoActionsExecuter(
+                                new SkipEmptySourceFilesTaskExecuter(
+                                        new ValidatingTaskExecuter(
+                                                new SkipUpToDateTaskExecuter(repository,
+                                                        new PostExecutionAnalysisTaskExecuter(
+                                                                new ExecuteActionsTaskExecuter(
+                                                                        listenerManager.getBroadcaster(TaskActionListener.class)
+                                                                ))))))));
+    }
+
+    TaskArtifactStateCacheAccess createCacheAccess(Gradle gradle, CacheRepository cacheRepository, InMemoryTaskArtifactCache inMemoryTaskArtifactCache, GradleBuildEnvironment environment) {
+        CacheDecorator decorator;
+        if (environment.isLongLivingProcess()) {
+            decorator = inMemoryTaskArtifactCache;
+        } else {
+            decorator = new NoOpDecorator();
+        }
+        return new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository, decorator);
+    }
+
+    FileSnapshotter createFileSnapshotter(TaskArtifactStateCacheAccess cacheAccess) {
+        return new CachingFileSnapshotter(new DefaultHasher(), cacheAccess);
+    }
+
+    TaskArtifactStateRepository createTaskArtifactStateRepository(Instantiator instantiator, TaskArtifactStateCacheAccess cacheAccess, StartParameter startParameter, FileSnapshotter fileSnapshotter) {
+        FileCollectionSnapshotter fileCollectionSnapshotter = new DefaultFileCollectionSnapshotter(fileSnapshotter, cacheAccess);
+
+        FileCollectionSnapshotter outputFilesSnapshotter = new OutputFilesCollectionSnapshotter(fileCollectionSnapshotter, new RandomLongIdGenerator(), cacheAccess);
+
+        SerializerRegistry<FileCollectionSnapshot> serializerRegistry = new DefaultSerializerRegistry<FileCollectionSnapshot>();
+        fileCollectionSnapshotter.registerSerializers(serializerRegistry);
+        outputFilesSnapshotter.registerSerializers(serializerRegistry);
+
+        TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess,
+                new CacheBackedFileSnapshotRepository(cacheAccess,
+                        serializerRegistry.build(),
+                        new RandomLongIdGenerator()));
+
+        return new ShortCircuitTaskArtifactStateRepository(
+                        startParameter,
+                        instantiator,
+                        new DefaultTaskArtifactStateRepository(
+                                taskHistoryRepository,
+                                instantiator,
+                                outputFilesSnapshotter,
+                                fileCollectionSnapshotter
+                        )
+        );
+    }
+
+    TaskPlanExecutor createTaskExecutorFactory(StartParameter startParameter, ExecutorFactory executorFactory) {
+        return new TaskPlanExecutorFactory(startParameter.getParallelThreadCount(), executorFactory).create();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/TaskScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/TaskScopeServices.java
new file mode 100644
index 0000000..6a862e3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/service/scopes/TaskScopeServices.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.service.scopes;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.DefaultTaskInputs;
+import org.gradle.api.internal.tasks.DefaultTaskOutputs;
+import org.gradle.api.internal.tasks.TaskStatusNagger;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.logging.LoggingManagerInternal;
+
+/**
+ * Contains the services for a given task.
+ */
+public class TaskScopeServices extends DefaultServiceRegistry {
+    private final ProjectInternal project;
+    private final TaskInternal taskInternal;
+
+    public TaskScopeServices(ServiceRegistry parent, final ProjectInternal project, TaskInternal taskInternal) {
+        super(parent);
+        this.project = project;
+        this.taskInternal = taskInternal;
+    }
+
+    protected TaskInputs createTaskInputs() {
+        return new DefaultTaskInputs(project.getFileResolver(), taskInternal, get(TaskStatusNagger.class));
+    }
+
+    protected TaskOutputsInternal createTaskOutputs() {
+        return new DefaultTaskOutputs(project.getFileResolver(), taskInternal, get(TaskStatusNagger.class));
+    }
+
+    protected TaskStatusNagger createTaskStatusNagger() {
+        return new TaskStatusNagger(taskInternal);
+    }
+
+    protected LoggingManagerInternal createLoggingManager() {
+        return getFactory(LoggingManagerInternal.class).create();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/text/TreeFormatter.java b/subprojects/core/src/main/groovy/org/gradle/internal/text/TreeFormatter.java
new file mode 100644
index 0000000..a79b703
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/text/TreeFormatter.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.text;
+
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.logging.internal.AbstractStyledTextOutput;
+import org.gradle.logging.internal.LinePrefixingStyledTextOutput;
+import org.gradle.util.TreeVisitor;
+
+public class TreeFormatter extends TreeVisitor<String> {
+    private final StringBuilder buffer = new StringBuilder();
+    private final AbstractStyledTextOutput original;
+    private Node current;
+
+    public TreeFormatter() {
+        original = new AbstractStyledTextOutput() {
+            @Override
+            protected void doAppend(String text) {
+                buffer.append(text);
+            }
+        };
+        current = new Node();
+    }
+
+    @Override
+    public String toString() {
+        return buffer.toString();
+    }
+
+    @Override
+    public void node(String node) {
+        if (current.traversing) {
+            // First child node
+            current = new Node(current, node);
+            if (current.isRoot()) {
+                original.append(node);
+                current.valueWritten = true;
+            }
+        } else {
+            // A sibling node
+            current = new Node(current.parent, node);
+        }
+    }
+
+    @Override
+    public void startChildren() {
+        current.traversing = true;
+    }
+
+    @Override
+    public void endChildren() {
+        if (current.parent == null) {
+            throw new IllegalStateException("Not visiting any node.");
+        }
+        if (!current.traversing) {
+            current = current.parent;
+        }
+        if (current.isRoot()) {
+            writeNode(current);
+        }
+        current = current.parent;
+    }
+
+    private void writeNode(Node node) {
+        if (node.prefix == null) {
+            node.prefix = node.isRoot() ? "" : node.parent.prefix + "    ";
+        }
+
+        StyledTextOutput output = new LinePrefixingStyledTextOutput(original, node.prefix);
+        if (!node.valueWritten) {
+            output.append(node.parent.prefix);
+            output.append("  - ");
+            output.append(node.value);
+        }
+
+        if (node.canCollapseFirstChild()) {
+            output.append(": ");
+            Node firstChild = node.firstChild;
+            output.append(firstChild.value);
+            firstChild.valueWritten = true;
+            firstChild.prefix = node.prefix;
+            writeNode(firstChild);
+        } else if (node.firstChild != null) {
+            original.format(":%n");
+            writeNode(node.firstChild);
+        }
+        if (node.nextSibling != null) {
+            original.format("%n");
+            writeNode(node.nextSibling);
+        }
+    }
+
+    private static class Node {
+        final Node parent;
+        final String value;
+        boolean written;
+        boolean traversing;
+        Node firstChild;
+        Node lastChild;
+        Node nextSibling;
+        String prefix;
+        public boolean valueWritten;
+
+        private Node() {
+            this.parent = null;
+            this.value = null;
+            traversing = true;
+            written = true;
+            prefix = "";
+        }
+
+        private Node(Node parent, String value) {
+            this.parent = parent;
+            this.value = value;
+            if (parent.firstChild == null) {
+                parent.firstChild = this;
+                parent.lastChild = this;
+            } else {
+                parent.lastChild.nextSibling = this;
+                parent.lastChild = this;
+            }
+        }
+
+        boolean canCollapseFirstChild() {
+            return firstChild != null && firstChild.nextSibling == null && !firstChild.canCollapseFirstChild();
+        }
+
+        boolean isRoot() {
+            return parent.parent == null;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/CharSequenceNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/CharSequenceNotationParser.java
new file mode 100644
index 0000000..eeb8089
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/CharSequenceNotationParser.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.internal.typeconversion;
+
+public class CharSequenceNotationParser extends TypedNotationParser<CharSequence, String> {
+    public CharSequenceNotationParser() {
+        super(CharSequence.class);
+    }
+
+    @Override
+    protected String parseType(CharSequence notation) {
+        return notation.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ClosureToSpecNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ClosureToSpecNotationParser.java
new file mode 100644
index 0000000..9d6f5e9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ClosureToSpecNotationParser.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.typeconversion;
+
+import groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.Collection;
+
+public class ClosureToSpecNotationParser<T> implements NotationParser<Object, 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/internal/typeconversion/CompositeNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/CompositeNotationParser.java
new file mode 100644
index 0000000..6095e26
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/CompositeNotationParser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import java.util.Collection;
+
+public class CompositeNotationParser<N, T> implements NotationParser<N, T> {
+    private final Collection<? extends NotationParser<? super N, ? extends T>> delegates;
+
+    public CompositeNotationParser(Collection<? extends NotationParser<? super N, ? extends T>> delegates) {
+        assert delegates != null : "delegates cannot be null!";
+        this.delegates = delegates;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        for (NotationParser<?, ?> delegate : delegates) {
+            delegate.describe(candidateFormats);
+        }
+    }
+
+    public T parseNotation(N notation) {
+        for (NotationParser<? super N, ? extends T> delegate : delegates) {
+            try {
+                return delegate.parseNotation(notation);
+            } catch (UnsupportedNotationException e) {
+                // Ignore
+            }
+        }
+
+        throw new UnsupportedNotationException(notation);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/EnumFromCharSequenceNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/EnumFromCharSequenceNotationParser.java
new file mode 100644
index 0000000..644eedd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/EnumFromCharSequenceNotationParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class EnumFromCharSequenceNotationParser<T extends Enum> implements ValueAwareNotationParser<T> {
+    private final Class<? extends T> type;
+
+    public EnumFromCharSequenceNotationParser(Class<? extends T> enumType) {
+        assert enumType.isEnum() : "resultingType must be enum";
+        this.type = enumType;
+    }
+
+    public T parseNotation(CharSequence notation) throws UnsupportedNotationException, TypeConversionException {
+        final String enumString = notation.toString();
+        List<T> enumConstants = Arrays.asList(type.getEnumConstants());
+        T match = CollectionUtils.findFirst(enumConstants, new Spec<T>() {
+            public boolean isSatisfiedBy(T enumValue) {
+                return enumValue.name().equalsIgnoreCase(enumString);
+            }
+        });
+        if (match == null) {
+            throw new TypeConversionException(
+                    String.format("Cannot coerce string value '%s' to an enum value of type '%s' (valid case insensitive values: %s)",
+                            enumString, type.getName(), CollectionUtils.toStringList(Arrays.asList(type.getEnumConstants()))
+                    )
+            );
+        } else {
+            return match;
+        }
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add(String.format("A String representing an Enum instance of type %s", type.getName()));
+    }
+
+    public void describeValues(Collection<String> collector) {
+        final Enum[] enumConstants = type.getEnumConstants();
+        for (Enum enumConstant : enumConstants) {
+            collector.add(enumConstant.name());
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ErrorHandlingNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ErrorHandlingNotationParser.java
new file mode 100644
index 0000000..f90c969
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ErrorHandlingNotationParser.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.util.GUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Formatter;
+import java.util.List;
+
+public class ErrorHandlingNotationParser<N, T> implements NotationParser<N, T> {
+    private final String targetTypeDisplayName;
+    private final String invalidNotationMessage;
+    private final NotationParser<N, T> delegate;
+
+    public ErrorHandlingNotationParser(String targetTypeDisplayName, String invalidNotationMessage, NotationParser<N, T> delegate) {
+        this.targetTypeDisplayName = targetTypeDisplayName;
+        this.invalidNotationMessage = invalidNotationMessage;
+        this.delegate = delegate;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    public T parseNotation(N notation) {
+        Formatter message = new Formatter();
+        if (notation == null) {
+            //we don't support null input at the moment. If you need this please implement it.
+            message.format("Cannot convert a null value to an object of type %s.%n", targetTypeDisplayName);
+        } else {
+            try {
+                return delegate.parseNotation(notation);
+            } catch (UnsupportedNotationException e) {
+                message.format("Cannot convert the provided notation to an object of type %s: %s.%n", targetTypeDisplayName, e.getNotation());
+            }
+        }
+
+        message.format("The following types/formats are supported:");
+        List<String> formats = new ArrayList<String>();
+        describe(formats);
+        for (String format : formats) {
+            message.format("%n  - %s", format);
+        }
+        if (GUtil.isTrue(invalidNotationMessage)) {
+            message.format("%n%s", invalidNotationMessage);
+        }
+        throw new InvalidUserDataException(message.toString());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/FlatteningNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/FlatteningNotationParser.java
new file mode 100644
index 0000000..bc7926a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/FlatteningNotationParser.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import org.gradle.util.GUtil;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Flattens or collectionizes input and passes the input notations to the delegates. Returns a set.
+ */
+public class FlatteningNotationParser<T> implements NotationParser<Object, Set<T>> {
+
+    private final NotationParser<Object, T> delegate;
+
+    public FlatteningNotationParser(NotationParser<Object, T> delegate) {
+        assert delegate != null : "delegate cannot be null";
+        this.delegate = delegate;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+        candidateFormats.add("Collections or arrays of any other supported format. Nested collections/arrays will be flattened.");
+    }
+
+    public Set<T> parseNotation(Object notation) {
+        Set<T> out = new LinkedHashSet<T>();
+        Collection notations = GUtil.collectionize(notation);
+        for (Object n : notations) {
+            out.add(delegate.parseNotation(n));
+        }
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/JustReturningParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/JustReturningParser.java
new file mode 100644
index 0000000..46c83d3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/JustReturningParser.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.typeconversion;
+
+import java.util.Collection;
+
+public class JustReturningParser<N, T> implements NotationParser<N, T> {
+
+    private final Class<? extends T> passThroughType;
+
+    public JustReturningParser(Class<? extends T> passThroughType) {
+        this.passThroughType = passThroughType;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add(String.format("Instances of %s.", passThroughType.getSimpleName()));
+    }
+
+    public T parseNotation(N notation) {
+        if (!passThroughType.isInstance(notation)) {
+            throw new UnsupportedNotationException(notation);
+        }
+        return passThroughType.cast(notation);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/MapKey.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/MapKey.java
new file mode 100644
index 0000000..f2f589e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/MapKey.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface MapKey {
+    String value();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/MapNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/MapNotationParser.java
new file mode 100644
index 0000000..1a4692d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/MapNotationParser.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.typeconversion;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.tasks.Optional;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.ConfigureUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * Converts a {@code Map<String, Object>} to the target type. Subclasses should define a {@code T parseMap()} method which takes a parameter 
+ * for each key value required from the source map. Each parameter should be annotated with a {@code @MapKey} annotation, and can also
+ * be annotated with a {@code @optional} annotation.
+ */
+public abstract class MapNotationParser<T> extends TypedNotationParser<Map, T> {
+    private final Method convertMethod;
+    private final String[] keyNames;
+    private final boolean[] optional;
+
+    public MapNotationParser() {
+        super(Map.class);
+        convertMethod = findConvertMethod();
+        keyNames = new String[convertMethod.getParameterAnnotations().length];
+        optional = new boolean[convertMethod.getParameterAnnotations().length];
+        for (int i = 0; i < convertMethod.getParameterAnnotations().length; i++) {
+            Annotation[] annotations = convertMethod.getParameterAnnotations()[i];
+            keyNames[i] = keyName(annotations);
+            optional[i] = optional(annotations);
+        }
+    }
+
+    private Method findConvertMethod() {
+        for (Method method : getClass().getDeclaredMethods()) {
+            if (method.getName().equals("parseMap")) {
+                method.setAccessible(true);
+                return method;
+            }
+        }
+        throw new UnsupportedOperationException(String.format("No parseMap() method found on class %s.", getClass().getSimpleName()));
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Maps");
+    }
+
+    public T parseType(Map values) throws UnsupportedNotationException {
+        Map<String, Object> mutableValues = new HashMap<String, Object>(values);
+        Set<String> missing = new TreeSet<String>();
+
+        Object[] params = new Object[convertMethod.getParameterTypes().length];
+        for (int i = 0; i < params.length; i++) {
+            String keyName = keyNames[i];
+            boolean optional = this.optional[i];
+            Class<?> type = convertMethod.getParameterTypes()[i];
+            Object value;
+            if (type.equals(String.class)) {
+                value = get(mutableValues, keyName);
+            } else {
+                value = type.cast(mutableValues.get(keyName));
+            }
+            if (!optional && value == null) {
+                missing.add(keyName);
+            }
+            mutableValues.remove(keyName);
+            params[i] = value;
+        }
+        if (!missing.isEmpty()) {
+            //below could be better.
+            //Throwing InvalidUserDataException here means that useful context information (including candidate formats, etc.) is not presented to the user
+            throw new InvalidUserDataException(String.format("Required keys %s are missing from map %s.", missing, values));
+        }
+
+        T result;
+        try {
+            result = (T) convertMethod.invoke(this, params);
+        } catch (IllegalAccessException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.unwrapAndRethrow(e);
+        }
+
+        ConfigureUtil.configureByMap(mutableValues, result);
+        return result;
+    }
+
+    private boolean optional(Annotation[] annotations) {
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof Optional) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String keyName(Annotation[] annotations) {
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof MapKey) {
+                return ((MapKey) annotation).value();
+            }
+        }
+        throw new UnsupportedOperationException("No @Key annotation on parameter of parseMap() method");
+    }
+
+    protected String get(Map<String, Object> args, String key) {
+        Object value = args.get(key);
+        String str = value != null ? value.toString() : null;
+        if (str != null && str.length() == 0) {
+            return null;
+        }
+        return str;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NormalizedTimeUnit.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NormalizedTimeUnit.java
new file mode 100644
index 0000000..107fe2a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NormalizedTimeUnit.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion;
+
+import java.util.concurrent.TimeUnit;
+
+public class NormalizedTimeUnit {
+
+    private int value;
+    private TimeUnit timeUnit;
+
+    public NormalizedTimeUnit(int value, TimeUnit timeUnit) {
+        this.value = value;
+        this.timeUnit = timeUnit;
+    }
+
+    public static NormalizedTimeUnit millis(int value) {
+        return new NormalizedTimeUnit(value, TimeUnit.MILLISECONDS);
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public TimeUnit getTimeUnit() {
+        return timeUnit;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NotationParser.java
new file mode 100644
index 0000000..b2d4b6a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NotationParser.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import java.util.Collection;
+
+public interface NotationParser<N, T> {
+    /**
+     * @throws UnsupportedNotationException When the supplied notation is not handled by this parser.
+     * @throws TypeConversionException When the supplied notation is recognised by this parser but is badly formed and cannot be converted to the target type.
+     */
+    T parseNotation(N notation) throws UnsupportedNotationException, TypeConversionException;
+
+    /**
+     * Describes the formats that the parser accepts.
+     * */
+    void describe(Collection<String> candidateFormats);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NotationParserBuilder.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NotationParserBuilder.java
new file mode 100644
index 0000000..78e937a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/NotationParserBuilder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import org.gradle.util.GUtil;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class NotationParserBuilder<T> {
+    private TypeInfo<T> resultingType;
+    private String invalidNotationMessage;
+    private Collection<NotationParser<Object, ? extends T>> notationParsers = new LinkedList<NotationParser<Object, ? extends T>>();
+    private boolean withJustReturningParser = true;
+
+    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;
+    }
+
+    public NotationParserBuilder<T> withDefaultJustReturnParser(boolean withJustReturningParser) {
+        this.withJustReturningParser = withJustReturningParser;
+        return this;
+    }
+
+    public NotationParserBuilder<T> parser(NotationParser<Object, ? extends T> parser) {
+        this.notationParsers.add(parser);
+        return this;
+    }
+
+    public NotationParserBuilder<T> invalidNotationMessage(String invalidNotationMessage) {
+        this.invalidNotationMessage = invalidNotationMessage;
+        return this;
+    }
+
+    public NotationParserBuilder<T> parsers(Iterable<? extends NotationParser<Object, ? extends T>> notationParsers) {
+        GUtil.addToCollection(this.notationParsers, notationParsers);
+        return this;
+    }
+
+    public NotationParser<Object, Set<T>> toFlatteningComposite() {
+        return wrapInErrorHandling(new FlatteningNotationParser<T>(create()));
+    }
+
+    public NotationParser<Object, T> toComposite() {
+        return wrapInErrorHandling(create());
+    }
+
+    private <S> NotationParser<Object, S> wrapInErrorHandling(NotationParser<Object, S> parser) {
+        return new ErrorHandlingNotationParser<Object, S>(resultingType.getTargetType().getSimpleName(), invalidNotationMessage, parser);
+    }
+
+    private CompositeNotationParser<Object, T> create() {
+        assert resultingType != null : "resultingType cannot be null";
+
+        List<NotationParser<Object, ? extends T>> composites = new LinkedList<NotationParser<Object, ? extends T>>();
+        if(withJustReturningParser){
+            composites.add(new JustReturningParser<Object, T>(resultingType.getTargetType()));
+        }
+        composites.addAll(this.notationParsers);
+
+        return new CompositeNotationParser<Object, T>(composites);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TimeUnitsParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TimeUnitsParser.java
new file mode 100644
index 0000000..5b94650
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TimeUnitsParser.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion;
+
+import org.gradle.api.InvalidUserDataException;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.gradle.internal.typeconversion.NormalizedTimeUnit.millis;
+
+public class TimeUnitsParser {
+
+    public NormalizedTimeUnit parseNotation(CharSequence notation, int value) {
+        String candidate = notation.toString().toUpperCase();
+        //jdk5 does not have days, hours or minutes, normalizing to millis
+        if (candidate.equals("DAYS")) {
+            return millis(value * 24 * 60 * 60 * 1000);
+        } else if (candidate.equals("HOURS")) {
+            return millis(value * 60 * 60 * 1000);
+        } else if (candidate.equals("MINUTES")) {
+            return millis(value * 60 * 1000);
+        }
+        try {
+            return new NormalizedTimeUnit(value, TimeUnit.valueOf(candidate));
+        } catch (Exception e) {
+            throw new InvalidUserDataException("Unable to parse provided TimeUnit: " + notation, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypeConversionException.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypeConversionException.java
new file mode 100644
index 0000000..6eb5fa9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypeConversionException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion;
+
+/**
+ * Thrown when a given value cannot be converted to the target type.
+ */
+public class TypeConversionException extends RuntimeException {
+    public TypeConversionException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypeInfo.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypeInfo.java
new file mode 100644
index 0000000..98435a5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypeInfo.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.internal.typeconversion;
+
+/**
+ * Type literal, useful for nested Generics.
+ */
+public class TypeInfo<T> {
+    private final Class<T> targetType;
+
+    public TypeInfo(Class targetType) {
+        assert targetType != null;
+        this.targetType = targetType;
+    }
+
+    public Class<T> getTargetType() {
+        return targetType;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypedNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypedNotationParser.java
new file mode 100644
index 0000000..640f5ed
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/TypedNotationParser.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+import java.util.Collection;
+
+public abstract class TypedNotationParser<N, T> implements NotationParser<Object, T> {
+
+    private final Class<N> typeToken;
+
+    public TypedNotationParser(Class<N> typeToken) {
+        assert typeToken != null : "typeToken cannot be null";
+        this.typeToken = typeToken;
+    }
+
+    public TypedNotationParser(TypeInfo<N> typeToken) {
+        assert typeToken != null : "typeToken cannot be null";
+        this.typeToken = typeToken.getTargetType();
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add(String.format("Instances of %s.", typeToken.getSimpleName()));
+    }
+
+    public T parseNotation(Object notation) {
+        if (!typeToken.isInstance(notation)) {
+            throw new UnsupportedNotationException(notation);
+        }
+        return parseType(typeToken.cast(notation));
+    }
+
+    abstract protected T parseType(N notation);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/UnsupportedNotationException.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/UnsupportedNotationException.java
new file mode 100644
index 0000000..9f9c2ca
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/UnsupportedNotationException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.typeconversion;
+
+public class UnsupportedNotationException extends RuntimeException {
+    private final Object notation;
+
+    public UnsupportedNotationException(Object notation) {
+        this.notation = notation;
+    }
+
+    public Object getNotation() {
+        return notation;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ValueAwareNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ValueAwareNotationParser.java
new file mode 100644
index 0000000..72ceb56
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/typeconversion/ValueAwareNotationParser.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion;
+
+import java.util.Collection;
+
+public interface ValueAwareNotationParser<T> extends NotationParser<CharSequence, T> {
+    void describeValues(Collection<String> collector);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java b/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
index 9b21679..bea78db 100644
--- a/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
@@ -25,47 +25,60 @@ import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.internal.GradleDistributionLocator;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.project.AbstractPluginAware;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.api.invocation.Gradle;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.configuration.ScriptPluginFactory;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.listener.ActionBroadcast;
 import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.GradleVersion;
-import org.gradle.util.MultiParentClassLoader;
 
 import java.io.File;
 
-public class DefaultGradle implements GradleInternal {
+public class DefaultGradle extends AbstractPluginAware implements GradleInternal {
     private ProjectInternal rootProject;
     private ProjectInternal defaultProject;
-    private TaskGraphExecuter taskGraph;
+    private final TaskGraphExecuter taskGraph;
     private final Gradle parent;
-    private StartParameter startParameter;
-    private MultiParentClassLoader scriptClassLoader;
-    private IProjectRegistry<ProjectInternal> projectRegistry;
+    private final StartParameter startParameter;
     private final ListenerManager listenerManager;
-    private final ServiceRegistryFactory services;
+    private final ServiceRegistry services;
     private final GradleDistributionLocator distributionLocator;
     private final ListenerBroadcast<BuildListener> buildListenerBroadcast;
     private final ListenerBroadcast<ProjectEvaluationListener> projectEvaluationListenerBroadcast;
     private ActionBroadcast<Project> rootProjectActions = new ActionBroadcast<Project>();
 
+    private PluginContainer pluginContainer;
+    private FileResolver fileResolver;
+
+    private final ScriptPluginFactory scriptPluginFactory;
+    private final ClassLoaderScope classLoaderScope;
+    private final ScriptHandlerFactory scriptHandlerFactory;
+
     public DefaultGradle(Gradle parent, StartParameter startParameter, ServiceRegistryFactory parentRegistry) {
         this.parent = parent;
         this.startParameter = startParameter;
         this.services = parentRegistry.createFor(this);
         this.listenerManager = services.get(ListenerManager.class);
-        projectRegistry = services.get(IProjectRegistry.class);
         taskGraph = services.get(TaskGraphExecuter.class);
-        scriptClassLoader = services.get(MultiParentClassLoader.class);
         distributionLocator = services.get(GradleDistributionLocator.class);
+        classLoaderScope = services.get(ClassLoaderScope.class);
+        pluginContainer = services.get(PluginContainer.class);
+        fileResolver = services.get(FileResolver.class);
+        scriptPluginFactory = services.get(ScriptPluginFactory.class);
+        scriptHandlerFactory = services.get(ScriptHandlerFactory.class);
         buildListenerBroadcast = listenerManager.createAnonymousBroadcaster(BuildListener.class);
         projectEvaluationListenerBroadcast = listenerManager.createAnonymousBroadcaster(ProjectEvaluationListener.class);
-        buildListenerBroadcast.add(new BuildAdapter(){
+        buildListenerBroadcast.add(new BuildAdapter() {
             @Override
             public void projectsLoaded(Gradle gradle) {
                 rootProjectActions.execute(rootProject);
@@ -139,18 +152,6 @@ public class DefaultGradle implements GradleInternal {
         return taskGraph;
     }
 
-    public void setTaskGraph(TaskGraphExecuter taskGraph) {
-        this.taskGraph = taskGraph;
-    }
-
-    public IProjectRegistry<ProjectInternal> getProjectRegistry() {
-        return projectRegistry;
-    }
-
-    public MultiParentClassLoader getScriptClassLoader() {
-        return scriptClassLoader;
-    }
-
     public ProjectEvaluationListener addProjectEvaluationListener(ProjectEvaluationListener listener) {
         addListener(listener);
         return listener;
@@ -216,7 +217,35 @@ public class DefaultGradle implements GradleInternal {
         return this;
     }
 
-    public ServiceRegistryFactory getServices() {
+    public ServiceRegistry getServices() {
         return services;
     }
+
+    public ServiceRegistryFactory getServiceRegistryFactory() {
+        return services.get(ServiceRegistryFactory.class);
+    }
+
+    public PluginContainer getPlugins() {
+        return pluginContainer;
+    }
+
+    @Override
+    protected FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    @Override
+    protected ScriptPluginFactory getScriptPluginFactory() {
+        return scriptPluginFactory;
+    }
+
+    @Override
+    protected ScriptHandlerFactory getScriptHandlerFactory() {
+        return scriptHandlerFactory;
+    }
+
+    @Override
+    public ClassLoaderScope getClassLoaderScope() {
+        return classLoaderScope;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
index c76f2d1..9f349a2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
@@ -16,22 +16,27 @@
 package org.gradle.listener;
 
 import org.gradle.api.Action;
+import org.gradle.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class ActionBroadcast<T> implements Action<T> {
-    private final ListenerBroadcast<Action> broadcast = new ListenerBroadcast<Action>(Action.class);
+    private final List<Action<? super T>> actions = new ArrayList<Action<? super T>>();
 
     public ActionBroadcast() {}
 
-    public ActionBroadcast(Iterable<Action> actions) {
-        broadcast.addAll(actions);
-    }
-
-    public void execute(T t) {
-        broadcast.getSource().execute(t);
+    public ActionBroadcast(Iterable<Action<? super T>> actions) {
+        CollectionUtils.addAll(this.actions, actions);
     }
 
     public void add(Action<? super T> action) {
-        broadcast.add(action);
+        actions.add(action);
     }
 
+    public void execute(T t) {
+        for (Action<? super T> action : new ArrayList<Action<? super T>>(actions)) {
+            action.execute(t);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java b/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
index b5b5bd1..dac4c5a 100755
--- a/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
@@ -18,20 +18,19 @@ package org.gradle.listener;
 
 import org.gradle.api.Action;
 import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.dispatch.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class BroadcastDispatch<T> implements Dispatch<MethodInvocation> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastDispatch.class);
     private final Class<T> type;
-    private final Map<Object, Dispatch<MethodInvocation>> handlers
-            = new LinkedHashMap<Object, Dispatch<MethodInvocation>>();
+    private final Map<Object, Dispatch<MethodInvocation>> handlers = new LinkedHashMap<Object, Dispatch<MethodInvocation>>();
 
     public BroadcastDispatch(Class<T> type) {
         this.type = type;
@@ -68,26 +67,31 @@ public class BroadcastDispatch<T> implements Dispatch<MethodInvocation> {
         handlers.remove(listener);
     }
 
+    public void removeAll() {
+        handlers.clear();
+    }
+
     private String getErrorMessage() {
         String typeDescription = type.getSimpleName().replaceAll("(\\p{Upper})", " $1").trim().toLowerCase();
         return String.format("Failed to notify %s.", typeDescription);
     }
 
     public void dispatch(MethodInvocation invocation) {
-        try {
-            ExceptionTrackingFailureHandler tracker = new ExceptionTrackingFailureHandler(LOGGER);
-            for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
-                try {
-                    handler.dispatch(invocation);
-                } catch (UncheckedException e) {
-                    tracker.dispatchFailed(invocation, e.getCause());
-                } catch (Throwable t) {
-                    tracker.dispatchFailed(invocation, t);
-                }
+        List<Throwable> failures = new ArrayList<Throwable>();
+        for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
+            try {
+                handler.dispatch(invocation);
+            } catch (UncheckedException e) {
+                failures.add(e.getCause());
+            } catch (Throwable t) {
+                failures.add(t);
             }
-            tracker.stop();
-        } catch (DispatchException t) {
-            throw new ListenerNotificationException(getErrorMessage(), t.getCause());
+        }
+        if (failures.size() == 1 && failures.get(0) instanceof RuntimeException) {
+            throw (RuntimeException) failures.get(0);
+        }
+        if (!failures.isEmpty()) {
+            throw new ListenerNotificationException(getErrorMessage(), failures);
         }
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
index 3af54de..d84b4bd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
@@ -78,17 +78,6 @@ public class ListenerBroadcast<T> implements Dispatch<MethodInvocation> {
             broadcast.add(listener);
         }
     }
-    
-    /**
-     * Adds the given listener if it is an instance of the listener type.
-     *
-     * @param listener The listener
-     */
-    public void maybeAdd(Object listener) {
-        if (type.isInstance(listener)) {
-            add(type.cast(listener));
-        }
-    }
 
     /**
      * Adds a {@link org.gradle.messaging.dispatch.Dispatch} to receive events from this broadcast.
@@ -125,6 +114,13 @@ public class ListenerBroadcast<T> implements Dispatch<MethodInvocation> {
     }
 
     /**
+     * Removes all listeners.
+     */
+    public void removeAll() {
+        broadcast.removeAll();
+    }
+
+    /**
      * Broadcasts the given event to all listeners.
      *
      * @param event The event
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java
index e9921a3..4f151f9 100755
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java
@@ -15,15 +15,15 @@
  */
 package org.gradle.listener;
 
-import org.gradle.api.GradleException;
-import org.gradle.api.internal.Contextual;
+import org.gradle.internal.exceptions.AbstractMultiCauseException;
+import org.gradle.internal.exceptions.Contextual;
 
 /**
  * A {@code ListenerNotificationException} is thrown when a listener cannot be notified of an event.
  */
 @Contextual
-public class ListenerNotificationException extends GradleException {
-    public ListenerNotificationException(String message, Throwable cause) {
-        super(message, cause);
+public class ListenerNotificationException extends AbstractMultiCauseException {
+    public ListenerNotificationException(String message, Iterable<? extends Throwable> causes) {
+        super(message, causes);
     }
 }
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 9d4248a..ad10d02 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
@@ -16,7 +16,7 @@
 
 package org.gradle.logging;
 
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.internal.Factory;
 import org.gradle.internal.TimeProvider;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLogger.java b/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLogger.java
index c2665b9..bfe6679 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLogger.java
@@ -87,6 +87,13 @@ public interface ProgressLogger {
     void setLoggingHeader(String header);
 
     /**
+     * Convenience method that sets descriptions and logs started() event.
+     *
+     * @return this logger instance
+     */
+    ProgressLogger start(String description, String shortDescription);
+
+    /**
      * Logs the start of the operation, with no initial status.
      */
     void started();
@@ -116,4 +123,6 @@ public interface ProgressLogger {
      * @param status The final status message. Can be null or empty.
      */
     void completed(String status);
+
+    long currentOperationId();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java
index 516563c..34c7dfe 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java
@@ -16,6 +16,8 @@
 
 package org.gradle.logging;
 
+import org.gradle.TaskExecutionLogger;
+
 public interface ProgressLoggerFactory {
     /**
      * Creates a new long-running operation which has not been started.
@@ -32,4 +34,6 @@ public interface ProgressLoggerFactory {
      * @return The progress logger for the operation.
      */
     ProgressLogger newOperation(Class loggerCategory);
+
+    ProgressLogger newOperation(Class<TaskExecutionLogger> taskExecutionLoggerClass, ProgressLogger parent);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/StandardOutputCapture.java b/subprojects/core/src/main/groovy/org/gradle/logging/StandardOutputCapture.java
index 8392d98..316b47a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/StandardOutputCapture.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/StandardOutputCapture.java
@@ -15,9 +15,6 @@
  */
 package org.gradle.logging;
 
-/**
- * @author Hans Dockter
- */
 public interface StandardOutputCapture {
     /**
      * Starts redirection of System.out and System.err to the Gradle logging system.
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java
index 1642023..36d47b8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java
@@ -70,7 +70,7 @@ public abstract class AbstractLineChoppingStyledTextOutput extends AbstractStyle
 
     /**
      * Called when text is to be appended.
-     * @param text The text.
+     * @param text The text, includes any end-of-line terminator
      * @param terminatesLine true if the given text terminates a line (including the end-of-line string).
      */
     protected abstract void doLineText(CharSequence text, boolean terminatesLine);
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleBackedProgressRenderer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleBackedProgressRenderer.java
index 3204c12..b8d0849 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
@@ -15,64 +15,47 @@
  */
 package org.gradle.logging.internal;
 
-import org.gradle.util.GUtil;
-
-import java.util.LinkedList;
+import org.gradle.logging.internal.progress.ProgressOperation;
+import org.gradle.logging.internal.progress.ProgressOperations;
 
 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 final ProgressOperations operations = new ProgressOperations();
+    private final DefaultStatusBarFormatter statusBarFormatter;
     private Label statusBar;
 
-    public ConsoleBackedProgressRenderer(OutputEventListener listener, Console console, StatusBarFormatter statusBarFormatter) {
+    public ConsoleBackedProgressRenderer(OutputEventListener listener, Console console, DefaultStatusBarFormatter statusBarFormatter) {
         this.listener = listener;
         this.console = console;
         this.statusBarFormatter = statusBarFormatter;
     }
 
     public void onOutput(OutputEvent event) {
-        if (event instanceof ProgressStartEvent) {
-            ProgressStartEvent startEvent = (ProgressStartEvent) event;
-            operations.addLast(new Operation(startEvent.getShortDescription(), startEvent.getStatus()));
-            updateText();
-        } else if (event instanceof ProgressCompleteEvent) {
-            operations.removeLast();
-            updateText();
-        } else if (event instanceof ProgressEvent) {
-            ProgressEvent progressEvent = (ProgressEvent) event;
-            operations.getLast().status = progressEvent.getStatus();
-            updateText();
+        try {
+            if (event instanceof ProgressStartEvent) {
+                ProgressStartEvent startEvent = (ProgressStartEvent) event;
+                ProgressOperation op = operations.start(startEvent.getShortDescription(), startEvent.getStatus(), startEvent.getOperationId(), startEvent.getParentOperationId());
+                updateText(op);
+            } else if (event instanceof ProgressCompleteEvent) {
+                ProgressOperation op = operations.complete(((ProgressCompleteEvent) event).getOperationId());
+                updateText(op.getParent());
+            } else if (event instanceof ProgressEvent) {
+                ProgressEvent progressEvent = (ProgressEvent) event;
+                ProgressOperation op = operations.progress(progressEvent.getStatus(), progressEvent.getOperationId());
+                updateText(op);
+            }
+            listener.onOutput(event);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to process incoming event '" + event
+                    + "' (" + event.getClass().getSimpleName() + ")", e);
         }
-        listener.onOutput(event);
     }
 
-    private void updateText() {
+    private void updateText(ProgressOperation op) {
         if (statusBar == null) {
             statusBar = console.getStatusBar();
         }
-        statusBar.setText(statusBarFormatter.format(operations));
+        statusBar.setText(statusBarFormatter.format(op));
     }
-
-    static class Operation {
-        private final String shortDescription;
-        private String status;
-
-        private Operation(String shortDescription, String status) {
-            this.shortDescription = shortDescription;
-            this.status = status;
-        }
-
-        String getMessage() {
-            if (GUtil.isTrue(status)) {
-                return status;
-            }
-            if (GUtil.isTrue(shortDescription)) {
-                return shortDescription;
-            }
-            return null;
-        }
-    }
-
 }
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 82c22e2..f06d237 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingConfigurer.java
@@ -17,15 +17,12 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 public class DefaultLoggingConfigurer implements LoggingConfigurer {
-    private final static Logger LOGGER = Logging.getLogger(DefaultLoggingConfigurer.class);
     private final List<LoggingConfigurer> configurers = new ArrayList<LoggingConfigurer>();
 
     public DefaultLoggingConfigurer(LoggingConfigurer... configurers) {
@@ -36,6 +33,5 @@ public class DefaultLoggingConfigurer implements LoggingConfigurer {
         for (LoggingConfigurer configurer : configurers) {
             configurer.configure(logLevel);
         }
-        LOGGER.debug("Finished configuring with level: {}, configurers: {}", logLevel, configurers);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java
index 3745344..3f07a49 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java
@@ -18,16 +18,13 @@ package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.logging.LoggingManagerInternal;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultLoggingManager implements LoggingManagerInternal {
     private boolean started;
     private final StartableLoggingSystem loggingSystem;
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 cb62bf3..b11f638 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
@@ -17,6 +17,9 @@
 package org.gradle.logging.internal;
 
 import org.gradle.internal.TimeProvider;
+import org.gradle.internal.progress.OperationIdentifier;
+import org.gradle.internal.progress.OperationsHierarchy;
+import org.gradle.internal.progress.OperationsHierarchyKeeper;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.util.GUtil;
@@ -24,6 +27,7 @@ import org.gradle.util.GUtil;
 public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
     private final ProgressListener progressListener;
     private final TimeProvider timeProvider;
+    private final OperationsHierarchyKeeper hierarchyKeeper = new OperationsHierarchyKeeper();
 
     public DefaultProgressLoggerFactory(ProgressListener progressListener, TimeProvider timeProvider) {
         this.progressListener = progressListener;
@@ -35,11 +39,21 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
     }
 
     public ProgressLogger newOperation(String loggerCategory) {
-        return new ProgressLoggerImpl(loggerCategory, progressListener, timeProvider);
+        return init(loggerCategory, null);
+    }
+
+    public ProgressLogger newOperation(Class loggerCategory, ProgressLogger parent) {
+        return init(loggerCategory.toString(), parent);
+    }
+
+    private ProgressLogger init(String loggerCategory, ProgressLogger parentHint) {
+        return new ProgressLoggerImpl(hierarchyKeeper.currentHierarchy(parentHint), loggerCategory, progressListener, timeProvider);
     }
 
     private static class ProgressLoggerImpl implements ProgressLogger {
         private enum State { idle, started, completed }
+
+        private final OperationsHierarchy hierarchy;
         private final String category;
         private final ProgressListener listener;
         private final TimeProvider timeProvider;
@@ -48,7 +62,8 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
         private String loggingHeader;
         private State state = State.idle;
 
-        public ProgressLoggerImpl(String category, ProgressListener listener, TimeProvider timeProvider) {
+        public ProgressLoggerImpl(OperationsHierarchy hierarchy, String category, ProgressListener listener, TimeProvider timeProvider) {
+            this.hierarchy = hierarchy;
             this.category = category;
             this.listener = listener;
             this.timeProvider = timeProvider;
@@ -81,6 +96,13 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
             this.loggingHeader = loggingHeader;
         }
 
+        public ProgressLogger start(String description, String shortDescription) {
+            setDescription(description);
+            setShortDescription(shortDescription);
+            started();
+            return this;
+        }
+
         public void started() {
             started(null);
         }
@@ -94,13 +116,14 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
             }
             assertNotCompleted();
             state = State.started;
-            listener.started(new ProgressStartEvent(timeProvider.getCurrentTime(), category, description, shortDescription, loggingHeader, toStatus(status)));
+            OperationIdentifier id = hierarchy.start();
+            listener.started(new ProgressStartEvent(id.getId(), id.getParentId(), timeProvider.getCurrentTime(), category, description, shortDescription, loggingHeader, toStatus(status)));
         }
 
         public void progress(String status) {
             assertStarted();
             assertNotCompleted();
-            listener.progress(new ProgressEvent(timeProvider.getCurrentTime(), category, toStatus(status)));
+            listener.progress(new ProgressEvent(hierarchy.currentOperationId(), timeProvider.getCurrentTime(), category, toStatus(status)));
         }
 
         public void completed() {
@@ -111,7 +134,12 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
             assertStarted();
             assertNotCompleted();
             state = State.completed;
-            listener.completed(new ProgressCompleteEvent(timeProvider.getCurrentTime(), category, description, toStatus(status)));
+            listener.completed(new ProgressCompleteEvent(hierarchy.completeCurrentOperation(),
+                    timeProvider.getCurrentTime(), category, description, toStatus(status)));
+        }
+
+        public long currentOperationId() {
+            return hierarchy.currentOperationId();
         }
 
         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 a3f205a..13b942b 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
@@ -16,8 +16,9 @@
 
 package org.gradle.logging.internal;
 
-import org.gradle.api.Action;
+import org.gradle.api.Nullable;
 import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.internal.io.TextStream;
 import org.gradle.logging.StandardOutputCapture;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.util.LinePerThreadBufferingOutputStream;
@@ -76,13 +77,16 @@ public class DefaultStandardOutputRedirector implements StandardOutputRedirector
         }
     }
 
-    private static class WriteAction implements Action<String> {
+    private static class WriteAction implements TextStream {
         private StandardOutputListener destination;
 
-        public void execute(String message) {
+        public void text(String message) {
             destination.onOutput(message);
         }
 
+        public void endOfStream(@Nullable Throwable failure) {
+        }
+
         public void setDestination(StandardOutputListener destination) {
             this.destination = destination;
         }
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
index 5a64f9c..d73c31e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStatusBarFormatter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStatusBarFormatter.java
@@ -17,28 +17,30 @@
 package org.gradle.logging.internal;
 
 import org.gradle.internal.nativeplatform.console.ConsoleMetaData;
+import org.gradle.logging.internal.progress.ProgressOperation;
 
-import java.util.List;
-
-public class DefaultStatusBarFormatter implements StatusBarFormatter {
+public class DefaultStatusBarFormatter {
     private final ConsoleMetaData consoleMetaData;
 
     public DefaultStatusBarFormatter(ConsoleMetaData consoleMetaData) {
         this.consoleMetaData = consoleMetaData;
     }
 
-    public String format(List<ConsoleBackedProgressRenderer.Operation> operations) {
+    public String format(ProgressOperation op) {
         StringBuilder builder = new StringBuilder();
-        for (ConsoleBackedProgressRenderer.Operation operation : operations) {
-            String message = operation.getMessage();
+        ProgressOperation current = op;
+        while(current != null) {
+            String message = current.getMessage();
+            current = current.getParent();
+
             if (message == null) {
                 continue;
             }
-            if (builder.length() > 0) {
-                builder.append(' ');
-            }
-            builder.append("> ");
-            builder.append(message);
+
+            builder.insert(0, " > ").insert(3, message);
+        }
+        if (builder.length() > 0) {
+            builder.delete(0, 1);
         }
         return trim(builder);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java
index e791f36..f98be66 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java
@@ -19,9 +19,6 @@ package org.gradle.logging.internal;
 import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
-/**
- * by Szczepan Faber, created at: 1/23/12
- */
 public interface EmbeddedLoggingServices {
 
     Factory<LoggingManagerInternal> getLoggingManagerFactory();
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 5f0fd2c..55bf5b6 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
@@ -15,85 +15,48 @@
  */
 package org.gradle.logging.internal;
 
-import org.gradle.api.Action;
-import org.gradle.api.UncheckedIOException;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.TimeProvider;
-import org.gradle.util.LineBufferingOutputStream;
 
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
  * A {@link org.gradle.logging.StyledTextOutput} implementation which generates events of type {@link
  * org.gradle.logging.internal.StyledTextOutputEvent}. This implementation is not thread-safe.
  */
-public class LoggingBackedStyledTextOutput extends AbstractStyledTextOutput {
-    private final LineBufferingOutputStream outstr;
+public class LoggingBackedStyledTextOutput extends AbstractLineChoppingStyledTextOutput {
+    private final OutputEventListener listener;
+    private final String category;
+    private final LogLevel logLevel;
+    private final TimeProvider timeProvider;
+    private final StringBuilder buffer = new StringBuilder();
+    private List<StyledTextOutputEvent.Span> spans = new ArrayList<StyledTextOutputEvent.Span>();
     private Style style = Style.Normal;
-    private boolean styleChange;
 
     public LoggingBackedStyledTextOutput(OutputEventListener listener, String category, LogLevel logLevel, TimeProvider timeProvider) {
-        outstr = new LineBufferingOutputStream(new LogAction(listener, category, logLevel, timeProvider));
+        this.listener = listener;
+        this.category = category;
+        this.logLevel = logLevel;
+        this.timeProvider = timeProvider;
     }
 
     protected void doStyleChange(Style style) {
-        styleChange = true;
-        try {
-            outstr.flush();
-        } finally {
-            styleChange = false;
+        if (buffer.length() > 0) {
+            spans.add(new StyledTextOutputEvent.Span(this.style, buffer.toString()));
+            buffer.setLength(0);
         }
         this.style = style;
     }
 
     @Override
-    protected void doAppend(String text) {
-        try {
-            outstr.write(text.getBytes());
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    private class LogAction implements Action<String> {
-        private final OutputEventListener listener;
-        private final String category;
-        private final LogLevel logLevel;
-        private final TimeProvider timeProvider;
-        private List<StyledTextOutputEvent.Span> spans;
-
-        public LogAction(OutputEventListener listener, String category, LogLevel logLevel, TimeProvider timeProvider) {
-            this.listener = listener;
-            this.category = category;
-            this.logLevel = logLevel;
-            this.timeProvider = timeProvider;
-        }
-
-        public void execute(String text) {
-            if (text.length() == 0) {
-                return;
-            }
-
-            StyledTextOutputEvent.Span span = new StyledTextOutputEvent.Span(style, text);
-            if (styleChange) {
-                if (spans == null) {
-                    spans = new ArrayList<StyledTextOutputEvent.Span>();
-                }
-                spans.add(span);
-                return;
-            } else if (spans != null) {
-                spans.add(span);
-            } else {
-                spans = Collections.singletonList(span);
-            }
-
-            StyledTextOutputEvent event = new StyledTextOutputEvent(timeProvider.getCurrentTime(), category, logLevel, spans);
-            spans = null;
-            
-            listener.onOutput(event);
+    protected void doLineText(CharSequence text, boolean terminatesLine) {
+        buffer.append(text);
+        if (terminatesLine) {
+            spans.add(new StyledTextOutputEvent.Span(this.style, buffer.toString()));
+            buffer.setLength(0);
+            listener.onOutput(new StyledTextOutputEvent(timeProvider.getCurrentTime(), category, logLevel, spans));
+            spans = new ArrayList<StyledTextOutputEvent.Span>();
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
index 3848666..9ba8e82 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
@@ -25,9 +25,9 @@ import org.gradle.cli.ParsedCommandLine;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.ShowStacktrace;
 
-import java.util.Collection;
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 public class LoggingCommandLineConverter extends AbstractCommandLineConverter<LoggingConfiguration> {
@@ -49,7 +49,6 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
         logLevelMap.put(QUIET, LogLevel.QUIET);
         logLevelMap.put(INFO, LogLevel.INFO);
         logLevelMap.put(DEBUG, LogLevel.DEBUG);
-        logLevelMap.put("", LogLevel.LIFECYCLE);
         showStacktraceMap.put(FULL_STACKTRACE, ShowStacktrace.ALWAYS_FULL);
         showStacktraceMap.put(STACKTRACE, ShowStacktrace.ALWAYS);
     }
@@ -60,45 +59,36 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
     }
 
     public LoggingConfiguration convert(ParsedCommandLine commandLine, LoggingConfiguration loggingConfiguration) throws CommandLineArgumentException {
-        loggingConfiguration.setLogLevel(getLogLevel(commandLine));
-        if (commandLine.hasOption(NO_COLOR)) {
-            loggingConfiguration.setColorOutput(false);
+        for (Map.Entry<String, LogLevel> entry : logLevelMap.entrySet()) {
+            if (commandLine.hasOption(entry.getKey())) {
+                loggingConfiguration.setLogLevel(entry.getValue());
+            }
         }
-        loggingConfiguration.setShowStacktrace(getShowStacktrace(commandLine));
-        return loggingConfiguration;
-    }
 
-    private ShowStacktrace getShowStacktrace(ParsedCommandLine options) {
-        if (options.hasOption(FULL_STACKTRACE)) {
-            return ShowStacktrace.ALWAYS_FULL;
+        for (Map.Entry<String, ShowStacktrace> entry : showStacktraceMap.entrySet()) {
+            if (commandLine.hasOption(entry.getKey())) {
+                loggingConfiguration.setShowStacktrace(entry.getValue());
+            }
         }
-        if (options.hasOption(STACKTRACE)) {
-            return ShowStacktrace.ALWAYS;
-        }
-        return ShowStacktrace.INTERNAL_EXCEPTIONS;
-    }
 
-    private LogLevel getLogLevel(ParsedCommandLine options) {
-        LogLevel logLevel = LogLevel.LIFECYCLE;
-        if (options.hasOption(QUIET)) {
-            logLevel = LogLevel.QUIET;
-        }
-        if (options.hasOption(INFO)) {
-            logLevel = LogLevel.INFO;
-        }
-        if (options.hasOption(DEBUG)) {
-            logLevel = LogLevel.DEBUG;
+        if (commandLine.hasOption(NO_COLOR)) {
+            loggingConfiguration.setColorOutput(false);
         }
-        return logLevel;
+
+        return loggingConfiguration;
     }
 
     public void configure(CommandLineParser parser) {
         parser.option(DEBUG, DEBUG_LONG).hasDescription("Log in debug mode (includes normal stacktrace).");
         parser.option(QUIET, QUIET_LONG).hasDescription("Log errors only.");
         parser.option(INFO, INFO_LONG).hasDescription("Set log level to info.");
+        parser.allowOneOf(DEBUG, QUIET, INFO);
+
         parser.option(NO_COLOR).hasDescription("Do not use color in the console output.");
+
         parser.option(STACKTRACE, STACKTRACE_LONG).hasDescription("Print out the stacktrace for all exceptions.");
         parser.option(FULL_STACKTRACE, FULL_STACKTRACE_LONG).hasDescription("Print out the full (very verbose) stacktrace for all exceptions.");
+        parser.allowOneOf(STACKTRACE, FULL_STACKTRACE_LONG);
     }
 
     /**
@@ -106,7 +96,6 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
      *
      * @param commandLineArgument a single command line argument (with no '-')
      * @return the corresponding log level or null if it doesn't match any.
-     * @author mhunsicker
      */
     public LogLevel getLogLevel(String commandLineArgument) {
         LogLevel logLevel = logLevelMap.get(commandLineArgument);
@@ -122,7 +111,6 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
      *
      * @param logLevel the log level.
      * @return the command line argument or null if this level cannot be represented on the command line.
-     * @author mhunsicker
      */
     public String getLogLevelCommandLine(LogLevel logLevel) {
         String commandLine = logLevelMap.inverse().get(logLevel);
@@ -137,19 +125,16 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
      * This returns the log levels that are supported on the command line.
      *
      * @return a collection of available log levels
-     * @author mhunsicker
      */
-    public Collection<LogLevel> getLogLevels() {
-        return Collections.unmodifiableCollection(logLevelMap.values());
+    public Set<LogLevel> getLogLevels() {
+        return new HashSet<LogLevel>(Arrays.asList(LogLevel.DEBUG, LogLevel.INFO, LogLevel.LIFECYCLE, LogLevel.QUIET));
     }
 
     /**
      * @return the set of short option strings that are used to configure log levels.
      */
     public Set<String> getLogLevelOptions() {
-        Set<String> options = new HashSet<String>(logLevelMap.keySet());
-        options.remove("");
-        return options;
+        return logLevelMap.keySet();
     }
 
     /**
@@ -157,7 +142,6 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
      *
      * @param commandLineArgument a single command line argument (with no '-')
      * @return the corresponding stack trace level or null if it doesn't match any.
-     * @author mhunsicker
      */
     public ShowStacktrace getShowStacktrace(String commandLineArgument) {
         ShowStacktrace showStacktrace = showStacktraceMap.get(commandLineArgument);
@@ -173,7 +157,6 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
      *
      * @param showStacktrace the stack trace level.
      * @return the command line argument or null if this level cannot be represented on the command line.
-     * @author mhunsicker
      */
     public String getShowStacktraceCommandLine(ShowStacktrace showStacktrace) {
         String commandLine = showStacktraceMap.inverse().get(showStacktrace);
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingConfigurer.java
index 6553da8..ceec51e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingConfigurer.java
@@ -17,9 +17,6 @@ package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
 
-/**
- * @author Hans Dockter
- */
 public interface LoggingConfigurer {
     void configure(LogLevel logLevel);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java
index 373cb99..983548d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java
@@ -18,9 +18,6 @@ package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
 
-/**
- * by Szczepan Faber, created at: 11/21/11
- */
 public class NoOpLoggingSystem implements StdOutLoggingSystem, StdErrLoggingSystem, LoggingSystem {
     public Snapshot snapshot() {
         return dummy();
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 6a7e15e..00986b9 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
@@ -15,10 +15,11 @@
  */
 package org.gradle.logging.internal;
 
-import org.gradle.api.Action;
+import org.gradle.api.Nullable;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.internal.TimeProvider;
+import org.gradle.internal.io.TextStream;
 import org.gradle.util.LinePerThreadBufferingOutputStream;
 
 import java.io.PrintStream;
@@ -31,10 +32,13 @@ import java.util.concurrent.atomic.AtomicReference;
  */
 abstract class PrintStreamLoggingSystem implements LoggingSystem {
     private final AtomicReference<StandardOutputListener> destination = new AtomicReference<StandardOutputListener>();
-    private final PrintStream outstr = new LinePerThreadBufferingOutputStream(new Action<String>() {
-        public void execute(String output) {
+    private final PrintStream outstr = new LinePerThreadBufferingOutputStream(new TextStream() {
+        public void text(String output) {
             destination.get().onOutput(output);
         }
+
+        public void endOfStream(@Nullable Throwable failure) {
+        }
     });
     private StandardOutputListener original;
     private LogLevel logLevel;
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 c2d2bd0..6830ea9 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
@@ -20,9 +20,11 @@ import org.gradle.api.logging.LogLevel;
 public class ProgressCompleteEvent extends CategorisedOutputEvent {
     private final String status;
     private final String description;
+    private long operationId;
 
-    public ProgressCompleteEvent(long timestamp, String category, String description, String status) {
+    public ProgressCompleteEvent(long operationId, long timestamp, String category, String description, String status) {
         super(timestamp, category, LogLevel.LIFECYCLE);
+        this.operationId = operationId;
         this.status = status;
         this.description = description;
     }
@@ -39,4 +41,8 @@ public class ProgressCompleteEvent extends CategorisedOutputEvent {
     public String toString() {
         return String.format("ProgressComplete %s", status);
     }
+
+    public long getOperationId() {
+        return operationId;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressEvent.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressEvent.java
index db04582..62f5795 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressEvent.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressEvent.java
@@ -19,9 +19,11 @@ import org.gradle.api.logging.LogLevel;
 
 public class ProgressEvent extends CategorisedOutputEvent {
     private final String status;
+    private long operationId;
 
-    public ProgressEvent(long timestamp, String category, String status) {
+    public ProgressEvent(long operationId, long timestamp, String category, String status) {
         super(timestamp, category, LogLevel.LIFECYCLE);
+        this.operationId = operationId;
         this.status = status;
     }
 
@@ -33,4 +35,8 @@ public class ProgressEvent extends CategorisedOutputEvent {
     public String toString() {
         return String.format("Progress %s", status);
     }
+
+    public long getOperationId() {
+        return operationId;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressStartEvent.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressStartEvent.java
index 0e836f1..718eca8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressStartEvent.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressStartEvent.java
@@ -18,13 +18,17 @@ package org.gradle.logging.internal;
 import org.gradle.api.logging.LogLevel;
 
 public class ProgressStartEvent extends CategorisedOutputEvent {
+    private long operationId;
+    private Long parentOperationId;
     private final String description;
     private final String shortDescription;
     private final String loggingHeader;
     private final String status;
 
-    public ProgressStartEvent(long timestamp, String category, String description, String shortDescription, String loggingHeader, String status) {
+    public ProgressStartEvent(long operationId, Long parentOperationId, long timestamp, String category, String description, String shortDescription, String loggingHeader, String status) {
         super(timestamp, category, LogLevel.LIFECYCLE);
+        this.operationId = operationId;
+        this.parentOperationId = parentOperationId;
         this.description = description;
         this.shortDescription = shortDescription;
         this.loggingHeader = loggingHeader;
@@ -51,4 +55,12 @@ public class ProgressStartEvent extends CategorisedOutputEvent {
     public String toString() {
         return String.format("ProgressStart %s", description);
     }
+
+    public long getOperationId() {
+        return operationId;
+    }
+
+    public Long getParentOperationId() {
+        return parentOperationId;
+    }
 }
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
deleted file mode 100644
index 312dde1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StatusBarFormatter.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.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/StdErrLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java
index ed19c67..b7ede43 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java
@@ -16,8 +16,5 @@
 
 package org.gradle.logging.internal;
 
-/**
- * by Szczepan Faber, created at: 11/21/11
- */
 public interface StdErrLoggingSystem extends LoggingSystem {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java
index 7592ea3..f4eecf4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java
@@ -16,8 +16,5 @@
 
 package org.gradle.logging.internal;
 
-/**
- * by Szczepan Faber, created at: 11/21/11
- */
 public interface StdOutLoggingSystem extends LoggingSystem {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java
index c7c3a99..41db276 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java
@@ -16,7 +16,6 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
-import org.gradle.internal.SystemProperties;
 import org.gradle.logging.StyledTextOutput;
 
 import java.text.SimpleDateFormat;
@@ -26,8 +25,6 @@ import static org.gradle.logging.StyledTextOutput.Style.Error;
 import static org.gradle.logging.StyledTextOutput.Style.Normal;
 
 public class StyledTextOutputBackedRenderer implements OutputEventListener {
-    private static final String EOL = SystemProperties.getLineSeparator();
-
     private final OutputEventTextOutputImpl textOutput;
     private boolean debugOutput;
     private RenderableOutputEvent lastEvent;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java
index cea30a6..4eba18f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java
@@ -28,7 +28,9 @@ import ch.qos.logback.core.ConsoleAppender;
 import ch.qos.logback.core.spi.FilterReply;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.UncheckedException;
-import org.gradle.logging.internal.*;
+import org.gradle.logging.internal.LogEvent;
+import org.gradle.logging.internal.LoggingConfigurer;
+import org.gradle.logging.internal.OutputEventListener;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Marker;
 
@@ -37,8 +39,6 @@ import java.io.PrintStream;
 /**
  * A {@link org.gradle.logging.internal.LoggingConfigurer} implementation which configures Logback
  * to route logging events to a {@link org.gradle.logging.internal.OutputEventListener}.
- *
- * @author Hans Dockter
  */
 public class LogbackLoggingConfigurer implements LoggingConfigurer {
     private final OutputEventListener outputEventListener;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/progress/ProgressOperation.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/progress/ProgressOperation.java
new file mode 100644
index 0000000..9d6b496
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/progress/ProgressOperation.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import org.gradle.util.GUtil;
+
+public class ProgressOperation {
+
+    private final String shortDescription;
+    private String status;
+    private ProgressOperation parent;
+
+    public ProgressOperation(String shortDescription, String status, ProgressOperation parent) {
+        this.shortDescription = shortDescription;
+        this.status = status;
+        this.parent = parent;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getMessage() {
+        if (GUtil.isTrue(status)) {
+            return status;
+        }
+        if (GUtil.isTrue(shortDescription)) {
+            return shortDescription;
+        }
+        return null;
+    }
+
+    public ProgressOperation getParent() {
+        return parent;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/progress/ProgressOperations.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/progress/ProgressOperations.java
new file mode 100644
index 0000000..7eb4c3a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/progress/ProgressOperations.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ProgressOperations {
+
+    private final Map<Long, ProgressOperation> operationsById = new HashMap<Long, ProgressOperation>();
+
+    public ProgressOperation start(String description, String status, long operationId, Long parentOperationId) {
+        ProgressOperation parent = null;
+        if (parentOperationId != null) {
+            parent = operationsById.get(parentOperationId);
+        }
+        ProgressOperation operation = new ProgressOperation(description, status, parent);
+        operationsById.put(operationId, operation);
+        return operation;
+    }
+
+    public ProgressOperation progress(String description, long operationId) {
+        ProgressOperation op = operationsById.get(operationId);
+        if (op == null) {
+            throw new IllegalStateException("Received progress event for an unknown operation (id: " + operationId + ")");
+        }
+        op.setStatus(description);
+        return op;
+    }
+
+    public ProgressOperation complete(long operationId) {
+        ProgressOperation op = operationsById.remove(operationId);
+        if (op == null) {
+            throw new IllegalStateException("Received complete event for an unknown operation (id: " + operationId + ")");
+        }
+        return op;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/ModelFinalizer.java b/subprojects/core/src/main/groovy/org/gradle/model/ModelFinalizer.java
new file mode 100644
index 0000000..8e1a0cc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/ModelFinalizer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An model object mutation rule, provided to {@link ModelRules#rule(ModelRule)}.
+ *
+ * <p>Rules that are an instance of this class are executed after those rules that are not. This allows some
+ * basic control over rule ordering.
+ */
+ at Incubating
+public abstract class ModelFinalizer extends ModelRule {
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/ModelPath.java b/subprojects/core/src/main/groovy/org/gradle/model/ModelPath.java
new file mode 100644
index 0000000..030eda3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/ModelPath.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model;
+
+import org.gradle.api.Incubating;
+
+ at Incubating
+public class ModelPath {
+
+    public static final String SEPARATOR = ".";
+
+    private final String path;
+
+    public ModelPath(String path) {
+        this.path = path;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ModelPath modelPath = (ModelPath) o;
+
+        if (!path.equals(modelPath.path)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return path.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return path;
+    }
+
+    public static ModelPath path(String path) {
+        return new ModelPath(path);
+    }
+
+    public ModelPath child(String child) {
+        return path(path + SEPARATOR + child);
+    }
+
+    public ModelPath getParent() {
+        int lastIndex = path.lastIndexOf(SEPARATOR);
+        if (lastIndex == -1) {
+            return null;
+        } else {
+            return path(path.substring(0, lastIndex));
+        }
+    }
+
+    public String getName() {
+        int lastIndex = path.lastIndexOf(SEPARATOR);
+        if (lastIndex == -1) {
+            return path;
+        } else {
+            return path.substring(lastIndex + 1);
+        }
+    }
+
+    public boolean isDirectChild(ModelPath other) {
+        ModelPath otherParent = other.getParent();
+        return otherParent == null ? false : otherParent.equals(this);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/ModelRule.java b/subprojects/core/src/main/groovy/org/gradle/model/ModelRule.java
new file mode 100644
index 0000000..9ccd8b5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/ModelRule.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An model object mutation rule, provided to {@link ModelRules#rule(ModelRule)}.
+ *
+ * <p>Subclasses should provide one and only one public method, with any name and any signature.
+ * This method will be inspected for bindings.
+ *
+ * <p>The first parameter of this method is considered the target or 'output' of the rule. The rule is free to modify the
+ * object as appropriate.
+ *
+ * <p>The subsequent parameters of this method are considered the parameters or 'inputs' of the rule. The rule should not
+ * modify these objects. Rules are ordered so that the input to a rule is completely configured before the rule is invoked.
+ *
+ * <p>The ordering of rules with the same target object is currently undefined and rules are executed in some arbitrary but
+ * fixed order. There is some basic support for controlling the ordering, where all rules that extend {@link ModelFinalizer}
+ * are executed after all rules that extend {@link ModelRule}.
+ */
+ at Incubating
+public abstract class ModelRule {
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/ModelRules.java b/subprojects/core/src/main/groovy/org/gradle/model/ModelRules.java
new file mode 100644
index 0000000..836b79f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/ModelRules.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.internal.Factory;
+
+/**
+ * A service for registering model rules.
+ *
+ * Plugins can inject an instance of this.
+ */
+ at Incubating
+public interface ModelRules {
+    /**
+     * Registers a model object under the given path.
+     */
+    <T> void register(String path, T model);
+
+    /**
+     * Registers a model object under the given path. The provided factory will be used to create the model object when it is required.
+     */
+    <T> void register(String path, Class<T> type, Factory<? extends T> model);
+
+    /**
+     * Registers an action that configures the model object at the given path. The provided action will be executed against the model object when
+     * the model object is required.
+     */
+    <T> void config(String path, Action<T> action);
+
+    /**
+     * Registers a rule. The rule is inspected to determine its inputs and outputs.
+     *
+     * @see ModelRule For details.
+     */
+    void rule(ModelRule rule);
+
+    /**
+     * Removes the model object from the given path.
+     */
+    void remove(String path);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/Path.java b/subprojects/core/src/main/groovy/org/gradle/model/Path.java
new file mode 100644
index 0000000..9ec495d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/Path.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model;
+
+import org.gradle.api.Incubating;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a model path on a parameter
+ */
+ at Target({ElementType.PARAMETER})
+ at Retention(RetentionPolicy.RUNTIME)
+ at Incubating
+public @interface Path {
+    String value();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/dsl/ModelDsl.java b/subprojects/core/src/main/groovy/org/gradle/model/dsl/ModelDsl.java
new file mode 100644
index 0000000..5404e94
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/dsl/ModelDsl.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.dsl;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Exposes the Groovy level DSL for configuring model elements in a build script.
+ */
+ at Incubating
+public interface ModelDsl {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/dsl/internal/GroovyModelDsl.java b/subprojects/core/src/main/groovy/org/gradle/model/dsl/internal/GroovyModelDsl.java
new file mode 100644
index 0000000..818ebfa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/dsl/internal/GroovyModelDsl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.dsl.internal;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.model.ModelPath;
+import org.gradle.model.ModelRules;
+import org.gradle.model.dsl.ModelDsl;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class GroovyModelDsl extends GroovyObjectSupport implements ModelDsl {
+    private final ModelPath modelPath;
+    private final ModelRules modelRules;
+    private AtomicBoolean executingDsl;
+
+    public GroovyModelDsl(ModelRules modelRules) {
+        this(new AtomicBoolean(), null, modelRules);
+    }
+
+    private GroovyModelDsl(AtomicBoolean executingDsl, ModelPath modelPath, ModelRules modelRules) {
+        this.executingDsl = executingDsl;
+        this.modelPath = modelPath;
+        this.modelRules = modelRules;
+    }
+
+    private GroovyModelDsl getChildPath(String name) {
+        ModelPath path = modelPath == null ? ModelPath.path(name) : modelPath.child(name);
+        return new GroovyModelDsl(executingDsl, path, modelRules);
+    }
+
+    private void registerConfigurationAction(Closure<?> action) {
+        modelRules.config(modelPath.toString(), new ClosureBackedAction<Object>(action));
+    }
+
+    public void configure(Closure<?> action) {
+        executingDsl.set(true);
+        try {
+            new ClosureBackedAction<Object>(action).execute(this);
+        } finally {
+            executingDsl.set(false);
+        }
+    }
+
+    public GroovyModelDsl propertyMissing(String name) {
+        if (!executingDsl.get()) {
+            throw new MissingPropertyException(name, getClass());
+        }
+        return getChildPath(name);
+    }
+
+    public Void methodMissing(String name, Object argsObj) {
+        Object[] args = (Object[]) argsObj;
+
+        if (!executingDsl.get() || args.length != 1 || !(args[0] instanceof Closure)) {
+            throw new MissingMethodException(name, getClass(), args);
+        }
+
+        Closure closure = (Closure) args[0];
+
+        getChildPath(name).registerConfigurationAction(closure);
+
+        return null;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/DefaultInputs.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/DefaultInputs.java
new file mode 100644
index 0000000..dfc8041
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/DefaultInputs.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+import com.google.common.collect.ImmutableList;
+
+public class DefaultInputs implements Inputs {
+
+    private final ImmutableList<?> inputs;
+
+    public DefaultInputs(ImmutableList<?> inputs) {
+        this.inputs = inputs;
+    }
+
+    public <T> T get(int i, Class<T> type) {
+        Object input = inputs.get(i);
+        if (type.isInstance(input)) {
+            return type.cast(input);
+        } else {
+            throw new RuntimeException("Can't convert input '" + i + "' with type '" + input.getClass() + "' to type '" + type + "'");
+        }
+    }
+
+    public int size() {
+        return inputs.size();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/DefaultModelRegistry.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/DefaultModelRegistry.java
new file mode 100644
index 0000000..e8ec27d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/DefaultModelRegistry.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import org.gradle.api.Transformer;
+import org.gradle.internal.Transformers;
+import org.gradle.model.ModelPath;
+
+import java.util.*;
+
+import static org.gradle.util.CollectionUtils.collect;
+
+public class DefaultModelRegistry implements ModelRegistry {
+
+    private final Map<ModelPath, Object> store = new HashMap<ModelPath, Object>();
+
+    private final Map<ModelPath, ModelCreation> creations = new HashMap<ModelPath, ModelCreation>();
+    private final Multimap<ModelPath, ModelMutation<?>> mutators = ArrayListMultimap.create();
+    private final Multimap<ModelPath, ImmutableList<ModelPath>> usedMutators = ArrayListMultimap.create();
+    private final Multimap<ModelPath, ModelMutation<?>> finalizers = ArrayListMultimap.create();
+    private final Multimap<ModelPath, ImmutableList<ModelPath>> usedFinalizers = ArrayListMultimap.create();
+
+    private final List<ModelCreationListener> modelCreationListeners = new LinkedList<ModelCreationListener>();
+
+    public <T> void create(String path, List<String> inputPaths, ModelCreator<T> creator) {
+        ModelPath creationModelPath = ModelPath.path(path);
+        if (creations.containsKey(creationModelPath)) {
+            throw new IllegalStateException("creator already registered for '" + creationModelPath + "'");
+        }
+        if (store.containsKey(creationModelPath)) {
+            throw new IllegalStateException("model already created for '" + creationModelPath + "'");
+        }
+
+        notifyCreationListeners(creationModelPath, creator);
+        creations.put(creationModelPath, new ModelCreation(creator, toModelPaths(inputPaths)));
+    }
+
+    private static ImmutableList<ModelPath> toModelPaths(List<String> inputPaths) {
+        return ImmutableList.copyOf(collect(inputPaths, new ToModelPath()));
+    }
+
+    public <T> void mutate(String path, List<String> inputPaths, ModelMutator<T> mutator) {
+        mutate(path, new ModelMutation<T>(mutator, toModelPaths(inputPaths)));
+    }
+
+    public <T> void mutate(String path, ModelMutation<T> mutation) {
+        ModelPath mutationModelPath = assertNotFinalized(path);
+        mutators.put(mutationModelPath, mutation);
+    }
+
+    public <T> void finalize(String path, List<String> inputPaths, ModelMutator<T> mutator) {
+        finalize(path, new ModelMutation<T>(mutator, toModelPaths(inputPaths)));
+    }
+
+    public <T> void finalize(String path, ModelMutation<T> mutation) {
+        ModelPath mutationModelPath = assertNotFinalized(path);
+        finalizers.put(mutationModelPath, mutation);
+    }
+
+    private ModelPath assertNotFinalized(String path) {
+        ModelPath mutationModelPath = ModelPath.path(path);
+        if (store.containsKey(mutationModelPath)) {
+            throw new IllegalStateException("model '" + mutationModelPath + "' is finalized");
+        }
+        return mutationModelPath;
+    }
+
+    public <T> T get(String path, Class<T> type) {
+        ModelPath modelPath = ModelPath.path(path);
+        Object model = getClosedModel(modelPath);
+
+        if (type.isInstance(model)) {
+            return type.cast(model);
+        } else {
+            throw new RuntimeException("Can't convert model at path '" + path + "' with type '" + model.getClass() + "' to target type '" + type + "'");
+        }
+    }
+
+    public void registerListener(ModelCreationListener listener) {
+        boolean remove;
+
+        for (Map.Entry<ModelPath, ModelCreation> entry : creations.entrySet()) {
+            remove = listener.onCreate(entry.getKey(), entry.getValue().getCreator().getType());
+            if (remove) {
+                return;
+            }
+        }
+
+        modelCreationListeners.add(listener);
+    }
+
+    public void remove(String path) {
+        ModelPath modelPath = ModelPath.path(path);
+        if (creations.remove(modelPath) == null) {
+            if (store.remove(modelPath) == null) {
+                throw new RuntimeException("Tried to remove model " + path + " but it is not registered");
+            } else {
+                if (isDependedOn(modelPath)) {
+                    throw new RuntimeException("Tried to remove model " + path + " but it is depended on by other model elements");
+                }
+            }
+        }
+    }
+
+    private boolean isDependedOn(ModelPath candidate) {
+        Transformer<Iterable<ModelPath>, ModelMutation<?>> extractInputPaths = new Transformer<Iterable<ModelPath>, ModelMutation<?>>() {
+            public Iterable<ModelPath> transform(ModelMutation<?> original) {
+                return original.getInputPaths();
+            }
+        };
+
+        Transformer<ImmutableList<ModelPath>, ImmutableList<ModelPath>> passThrough = Transformers.noOpTransformer();
+
+        return hasModelPath(candidate, mutators.values(), extractInputPaths)
+                || hasModelPath(candidate, usedMutators.values(), passThrough)
+                || hasModelPath(candidate, finalizers.values(), extractInputPaths)
+                || hasModelPath(candidate, usedFinalizers.values(), passThrough);
+    }
+
+    private <T> boolean hasModelPath(ModelPath candidate, Iterable<T> things, Transformer<? extends Iterable<ModelPath>, T> transformer) {
+        for (T thing : things) {
+            for (ModelPath path : transformer.transform(thing)) {
+                if (path.equals(candidate)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private Set<ModelPath> getPromisedPaths() {
+        return ImmutableSet.<ModelPath>builder().addAll(creations.keySet()).build();
+    }
+
+    private Object getClosedModel(ModelPath path) {
+        if (store.containsKey(path)) {
+            return store.get(path);
+        }
+
+        Object model = createModel(path);
+        Collection<ModelMutation<?>> modelMutations = mutators.removeAll(path);
+        for (ModelMutation<?> modelMutation : modelMutations) {
+            fireMutation(model, modelMutation);
+            @SuppressWarnings("unchecked") ImmutableList<ModelPath> inputPaths = modelMutation.getInputPaths();
+            usedMutators.put(path, inputPaths);
+        }
+
+        modelMutations = finalizers.removeAll(path);
+        for (ModelMutation modelMutation : modelMutations) {
+            fireMutation(model, modelMutation);
+            @SuppressWarnings("unchecked") ImmutableList<ModelPath> inputPaths = modelMutation.getInputPaths();
+            usedFinalizers.put(path, inputPaths);
+        }
+
+        // close all the child objects
+        Set<ModelPath> promisedPaths = getPromisedPaths();
+        for (ModelPath modelPath : promisedPaths) {
+            if (path.isDirectChild(modelPath)) {
+                getClosedModel(modelPath);
+            }
+        }
+
+        store.put(path, model);
+
+        return model;
+    }
+
+    private Object createModel(final ModelPath path) {
+        ModelCreation creation = creations.remove(path);
+        if (creation == null) {
+            throw new IllegalStateException("No creator for '" + path + "'");
+        }
+
+        Inputs inputs = toInputs(creation.getInputPaths());
+        Object created = creation.getCreator().create(inputs);
+        store.put(path, created);
+        return created;
+    }
+
+    private <T> void fireMutation(Object model, ModelMutation<T> modelMutation) {
+        ModelMutator<T> mutator = modelMutation.getMutator();
+        Inputs inputs = toInputs(modelMutation.getInputPaths());
+        T cast = mutator.getType().cast(model);
+        mutator.mutate(cast, inputs);
+    }
+
+    private Inputs toInputs(Iterable<ModelPath> inputPaths) {
+        ImmutableList.Builder<Object> builder = ImmutableList.builder();
+        for (ModelPath inputPath : inputPaths) {
+            builder.add(getClosedModel(inputPath));
+        }
+        return new DefaultInputs(builder.build());
+    }
+
+    private <T> void notifyCreationListeners(ModelPath path, ModelCreator<T> creator) {
+        ListIterator<ModelCreationListener> modelCreationListenerListIterator = modelCreationListeners.listIterator();
+        while (modelCreationListenerListIterator.hasNext()) {
+            ModelCreationListener next = modelCreationListenerListIterator.next();
+            boolean remove = next.onCreate(path, creator.getType());
+            if (remove) {
+                modelCreationListenerListIterator.remove();
+            }
+        }
+    }
+
+    private static class ModelCreation {
+
+        private final ModelCreator<?> creator;
+        private final ImmutableList<ModelPath> inputPaths;
+
+        public ModelCreation(ModelCreator<?> creator, ImmutableList<ModelPath> inputPaths) {
+            this.creator = creator;
+            this.inputPaths = inputPaths;
+        }
+
+        public ModelCreator<?> getCreator() {
+            return creator;
+        }
+
+        public ImmutableList<ModelPath> getInputPaths() {
+            return inputPaths;
+        }
+
+    }
+
+    private static class ToModelPath implements Transformer<ModelPath, String> {
+        public ModelPath transform(String string) {
+            return ModelPath.path(string);
+        }
+    }
+
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/Inputs.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/Inputs.java
new file mode 100644
index 0000000..0f233f0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/Inputs.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+public interface Inputs {
+
+    <T> T get(int i, Class<T> type);
+
+    int size();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelCreationListener.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelCreationListener.java
new file mode 100644
index 0000000..6318cd0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelCreationListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+import org.gradle.model.ModelPath;
+
+public interface ModelCreationListener {
+
+    boolean onCreate(ModelPath path, Class<?> type);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelCreator.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelCreator.java
new file mode 100644
index 0000000..e750a0e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelCreator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+public interface ModelCreator<T> {
+
+    Class<T> getType();
+
+    T create(Inputs inputs);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelMutation.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelMutation.java
new file mode 100644
index 0000000..5a89ab6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelMutation.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.model.ModelPath;
+
+class ModelMutation<T> {
+
+    private final ModelMutator<T> mutator;
+    private final ImmutableList<ModelPath> inputPaths;
+
+    public ModelMutation(ModelMutator<T> mutator, ImmutableList<ModelPath> inputPaths) {
+        this.mutator = mutator;
+        this.inputPaths = inputPaths;
+    }
+
+    public ModelMutator<T> getMutator() {
+        return mutator;
+    }
+
+    public ImmutableList<ModelPath> getInputPaths() {
+        return inputPaths;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelMutator.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelMutator.java
new file mode 100644
index 0000000..2675b5f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelMutator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+public interface ModelMutator<T> {
+
+    Class<T> getType();
+
+    void mutate(T object, Inputs inputs);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelRegistry.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelRegistry.java
new file mode 100644
index 0000000..96e0e07
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelRegistry.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+import java.util.List;
+
+public interface ModelRegistry {
+
+    public <T> void create(String path, List<String> inputPaths, ModelCreator<T> creator);
+
+    public <T> void mutate(String path, List<String> inputPaths, ModelMutator<T> mutator);
+
+    <T> void mutate(String path, ModelMutation<T> mutation);
+
+    public <T> void finalize(String path, List<String> inputPaths, ModelMutator<T> mutator);
+
+    <T> void finalize(String path, ModelMutation<T> mutation);
+
+    public <T> T get(String path, Class<T> type);
+
+    public void registerListener(ModelCreationListener listener);
+
+    void remove(String path);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelRegistryBackedModelRules.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelRegistryBackedModelRules.java
new file mode 100644
index 0000000..8195189
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/ModelRegistryBackedModelRules.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.api.Action;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factories;
+import org.gradle.internal.Factory;
+import org.gradle.model.ModelRule;
+import org.gradle.model.ModelRules;
+import org.gradle.model.internal.rules.ReflectiveRule;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Collections;
+
+import static org.gradle.util.CollectionUtils.findFirst;
+
+public class ModelRegistryBackedModelRules implements ModelRules {
+
+    private final ModelRegistry modelRegistry;
+
+    public ModelRegistryBackedModelRules(ModelRegistry modelRegistry) {
+        this.modelRegistry = modelRegistry;
+    }
+
+    public <T> void register(String path, final T model) {
+        @SuppressWarnings("unchecked") Class<T> aClass = (Class<T>) model.getClass();
+        register(path, aClass, Factories.constant(model));
+    }
+
+    public <T> void register(String path, final Class<T> type, final Factory<? extends T> model) {
+        modelRegistry.create(path, ImmutableList.<String>of(), new ModelCreator<T>() {
+            public Class<T> getType() {
+                return type;
+            }
+
+            public T create(Inputs inputs) {
+                return model.create();
+            }
+        });
+    }
+
+    public void remove(String path) {
+        modelRegistry.remove(path);
+    }
+
+    public void rule(ModelRule rule) {
+        ReflectiveRule.rule(modelRegistry, rule);
+    }
+
+    public <T> void config(String path, Action<T> action) {
+        final Class<T> modelType = getActionObjectType(action);
+        modelRegistry.mutate(path, Collections.<String>emptyList(), ActionBackedModelMutator.<T>create(modelType, action));
+    }
+
+    private <T> Class<T> getActionObjectType(Action<T> action) {
+        Class<? extends Action> aClass = action.getClass();
+        Type[] genericInterfaces = aClass.getGenericInterfaces();
+        Type actionType = findFirst(genericInterfaces, new Spec<Type>() {
+            public boolean isSatisfiedBy(Type element) {
+                return element instanceof ParameterizedType && ((ParameterizedType) element).getRawType().equals(Action.class);
+            }
+        });
+
+        final Class<?> modelType;
+
+        if (actionType == null) {
+            modelType = Object.class;
+        } else {
+            ParameterizedType actionParamaterizedType = (ParameterizedType) actionType;
+            Type tType = actionParamaterizedType.getActualTypeArguments()[0];
+
+            if (tType instanceof Class) {
+                modelType = (Class) tType;
+            } else if (tType instanceof ParameterizedType) {
+                modelType = (Class) ((ParameterizedType) tType).getRawType();
+            } else if (tType instanceof TypeVariable) {
+                TypeVariable  typeVariable = (TypeVariable) tType;
+                Type[] bounds = typeVariable.getBounds();
+                return (Class<T>) bounds[0];
+            } else {
+                throw new RuntimeException("Don't know how to handle type: " + tType.getClass());
+            }
+        }
+
+        @SuppressWarnings("unchecked") Class<T> castModelType = (Class<T>) modelType;
+        return castModelType;
+    }
+
+    private static class ActionBackedModelMutator<T> implements ModelMutator<T> {
+        private final Class<T> modelType;
+        private final Action<T> action;
+
+        public static <T> ModelMutator<T> create(Class<T> type, Action<T> action) {
+            return new ActionBackedModelMutator<T>(type, action);
+        }
+
+        public ActionBackedModelMutator(Class<T> modelType, Action<T> action) {
+            this.modelType = modelType;
+            this.action = action;
+        }
+
+        public Class<T> getType() {
+            return modelType;
+        }
+
+        public void mutate(T object, Inputs inputs) {
+            action.execute(object);
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/internal/rules/ReflectiveRule.java b/subprojects/core/src/main/groovy/org/gradle/model/internal/rules/ReflectiveRule.java
new file mode 100644
index 0000000..504578e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/internal/rules/ReflectiveRule.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal.rules;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.api.Nullable;
+import org.gradle.api.Transformer;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.UncheckedException;
+import org.gradle.model.ModelFinalizer;
+import org.gradle.model.ModelPath;
+import org.gradle.model.ModelRule;
+import org.gradle.model.Path;
+import org.gradle.model.internal.Inputs;
+import org.gradle.model.internal.ModelCreationListener;
+import org.gradle.model.internal.ModelMutator;
+import org.gradle.model.internal.ModelRegistry;
+import org.gradle.util.CollectionUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.lang.reflect.Modifier.isPrivate;
+import static java.lang.reflect.Modifier.isStatic;
+import static org.gradle.util.CollectionUtils.*;
+
+public abstract class ReflectiveRule {
+
+    public static void rule(final ModelRegistry modelRegistry, final ModelRule modelRule) {
+        final Method bindingMethod = findBindingMethod(modelRule);
+        final List<BindableParameter<?>> initialBindings = bindings(bindingMethod);
+
+        boolean unsatisfied = CollectionUtils.any(initialBindings, new Spec<BindableParameter<?>>() {
+            public boolean isSatisfiedBy(BindableParameter<?> element) {
+                return element.getPath() == null;
+            }
+        });
+
+        if (unsatisfied) {
+            modelRegistry.registerListener(new ModelCreationListener() {
+
+                private List<BindableParameter<?>> bindings = initialBindings;
+
+                public boolean onCreate(ModelPath path, Class<?> type) {
+                    ImmutableList.Builder<BindableParameter<?>> bindingsBuilder = ImmutableList.builder();
+
+                    boolean unsatisfied = false;
+
+                    for (BindableParameter<?> binding : bindings) {
+                        if (binding.getPath() == null) {
+                            if (binding.getType().isAssignableFrom(type)) {
+                                bindingsBuilder.add(copyBindingWithPath(path, binding));
+                                continue;
+                            } else {
+                                unsatisfied = true;
+                            }
+                        }
+
+                        bindingsBuilder.add(binding);
+                    }
+
+                    bindings = bindingsBuilder.build();
+
+                    if (unsatisfied) {
+                        return false;
+                    } else {
+                        registerMutator(modelRegistry, modelRule, bindingMethod, bindings);
+                        return true;
+                    }
+                }
+            });
+        } else {
+            registerMutator(modelRegistry, modelRule, bindingMethod, initialBindings);
+        }
+    }
+
+    private static void registerMutator(ModelRegistry modelRegistry, ModelRule modelRule, final Method bindingMethod, final List<BindableParameter<?>> bindings) {
+        BindableParameter<?> first = bindings.get(0);
+        List<BindableParameter<?>> tail = bindings.subList(1, bindings.size());
+        ModelMutator<?> modelMutator = toMutator(modelRule, bindingMethod, first, tail);
+
+        String path = first.getPath().toString();
+        List<String> bindingPaths = CollectionUtils.collect(tail, new Transformer<String, BindableParameter<?>>() {
+            public String transform(BindableParameter<?> bindableParameter) {
+                return bindableParameter.getPath().toString();
+            }
+        });
+
+        if (modelRule instanceof ModelFinalizer) {
+            modelRegistry.finalize(path, bindingPaths, modelMutator);
+        } else {
+            modelRegistry.mutate(path, bindingPaths, modelMutator);
+        }
+    }
+
+    private static <T> ModelMutator<T> toMutator(final ModelRule modelRule, final Method bindingMethod, final BindableParameter<T> first, final List<BindableParameter<?>> tail) {
+        return new ModelMutator<T>() {
+            public Class<T> getType() {
+                return first.getType();
+            }
+
+            public void mutate(T object, Inputs inputs) {
+                Object[] args = new Object[1 + tail.size()];
+                args[0] = object;
+                for (int i = 0; i < inputs.size(); ++i) {
+                    args[i + 1] = inputs.get(i, tail.get(i).getType());
+                }
+
+                bindingMethod.setAccessible(true);
+
+                try {
+                    bindingMethod.invoke(modelRule, args);
+                } catch (Exception e) {
+                    Throwable t = e;
+                    if (t instanceof InvocationTargetException) {
+                        t = e.getCause();
+                    }
+
+                    UncheckedException.throwAsUncheckedException(t);
+                }
+            }
+        };
+    }
+
+    private static <T> BindableParameter<T> copyBindingWithPath(ModelPath path, BindableParameter<T> binding) {
+        return new BindableParameter<T>(path, binding.getType());
+    }
+
+    public static Method findBindingMethod(Object object) {
+        Class<?> objectClass = object.getClass();
+        List<Method> declaredMethods = filter(Arrays.asList(objectClass.getDeclaredMethods()), new Spec<Method>() {
+            public boolean isSatisfiedBy(Method element) {
+                int modifiers = element.getModifiers();
+                return !isPrivate(modifiers) && !isStatic(modifiers) && !element.isSynthetic();
+            }
+        });
+        if (declaredMethods.size() != 1) {
+            throw new IllegalArgumentException(objectClass + " rule must have exactly 1 public method, has: " + join(", ", toStringList(declaredMethods)));
+        }
+
+        return declaredMethods.get(0);
+    }
+
+    private static List<BindableParameter<?>> bindings(Method method) {
+        return bindings(method.getParameterTypes(), method.getParameterAnnotations());
+    }
+
+    static List<BindableParameter<?>> bindings(Class[] types, Annotation[][] annotations) {
+        ImmutableList.Builder<BindableParameter<?>> inputBindingBuilder = ImmutableList.builder();
+
+        for (int i = 0; i < types.length; i++) {
+            Class<?> paramType = types[i];
+            Annotation[] paramAnnotations = annotations[i];
+
+            inputBindingBuilder.add(binding(paramType, paramAnnotations));
+        }
+
+        return inputBindingBuilder.build();
+    }
+
+    private static <T> BindableParameter binding(Class<T> type, Annotation[] annotations) {
+        Path pathAnnotation = (Path) findFirst(annotations, new Spec<Annotation>() {
+            public boolean isSatisfiedBy(Annotation element) {
+                return element.annotationType().equals(Path.class);
+            }
+        });
+        String path = pathAnnotation == null ? null : pathAnnotation.value();
+        return new BindableParameter<T>(path == null ? null : ModelPath.path(path), type);
+    }
+
+
+    public static class BindableParameter<T> {
+
+        private final ModelPath path;
+        private final Class<T> type;
+
+        public BindableParameter(@Nullable ModelPath path, Class<T> type) {
+            this.path = path;
+            this.type = type;
+        }
+
+        public ModelPath getPath() {
+            return path;
+        }
+
+        public Class<T> getType() {
+            return type;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/PluginHandler.java b/subprojects/core/src/main/groovy/org/gradle/plugin/PluginHandler.java
new file mode 100644
index 0000000..19b9aaf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/PluginHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin;
+
+import org.gradle.api.Incubating;
+
+import java.util.Map;
+
+/**
+ * A manager of plugins.
+ */
+ at Incubating
+public interface PluginHandler {
+
+    void apply(Map<String, ?> attributes);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/internal/DefaultPluginHandler.java b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/DefaultPluginHandler.java
new file mode 100644
index 0000000..45e5801
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/DefaultPluginHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal;
+
+import org.gradle.api.tasks.Optional;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
+import org.gradle.plugin.PluginHandler;
+import org.gradle.plugin.resolve.internal.DefaultPluginRequest;
+import org.gradle.plugin.resolve.internal.PluginRequest;
+
+import java.util.List;
+import java.util.Map;
+
+public class DefaultPluginHandler implements PluginHandler {
+
+    private final List<PluginRequest> pluginRequests;
+
+    public DefaultPluginHandler(List<PluginRequest> pluginRequests) {
+        this.pluginRequests = pluginRequests;
+    }
+
+    private static class PluginRequestNotationParser extends MapNotationParser<PluginRequest> {
+        protected PluginRequest parseMap(@MapKey("plugin") String id, @MapKey("version") @Optional String version) {
+            return version == null ? new DefaultPluginRequest(id) : new DefaultPluginRequest(id, version);
+        }
+    }
+
+    public void apply(Map<String, ?> attributes) {
+        PluginRequest pluginRequest = new PluginRequestNotationParser().parseType(attributes);
+        pluginRequests.add(pluginRequest);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/internal/NonPluggableTargetPluginHandler.java b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/NonPluggableTargetPluginHandler.java
new file mode 100644
index 0000000..068023f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/NonPluggableTargetPluginHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal;
+
+import org.gradle.plugin.PluginHandler;
+
+import java.util.Map;
+
+public class NonPluggableTargetPluginHandler implements PluginHandler {
+
+    private final Object target;
+
+    public NonPluggableTargetPluginHandler(Object target) {
+        this.target = target;
+    }
+
+    public void apply(Map<String, ?> attributes) {
+        throw fail();
+    }
+
+    private RuntimeException fail() {
+        return new UnsupportedOperationException("Script target " + target + " cannot have plugins applied to it");
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginRequestApplicator.java b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginRequestApplicator.java
new file mode 100644
index 0000000..9c8a2a6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginRequestApplicator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.plugin.resolve.internal.PluginRequest;
+import org.gradle.plugin.resolve.internal.PluginResolution;
+import org.gradle.plugin.resolve.internal.PluginResolver;
+
+public class PluginRequestApplicator {
+
+    private final PluginResolver pluginResolver;
+    private final Action<? super PluginResolution> pluginResolutionHandler;
+
+    public PluginRequestApplicator(PluginResolver pluginResolver, Action<? super PluginResolution> pluginResolutionHandler) {
+        this.pluginResolver = pluginResolver;
+        this.pluginResolutionHandler = pluginResolutionHandler;
+    }
+
+    public void applyPlugin(Iterable<? extends PluginRequest> requests) {
+        for (PluginRequest request : requests) {
+            applyPlugin(request);
+        }
+    }
+
+    public void applyPlugin(PluginRequest request) {
+        PluginResolution resolution = pluginResolver.resolve(request);
+        if (resolution == null) {
+            throw new UnknownPluginException("Cannot resolve plugin request " + request + " from " + pluginResolver.getDescriptionForNotFoundMessage());
+        }
+
+        pluginResolutionHandler.execute(resolution);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolutionApplicator.java b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolutionApplicator.java
new file mode 100644
index 0000000..6d3bae7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolutionApplicator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.plugins.PluginAware;
+import org.gradle.plugin.resolve.internal.PluginResolution;
+
+public class PluginResolutionApplicator implements Action<PluginResolution> {
+
+    private final PluginAware target;
+    private final ClassLoaderScope classLoaderScope;
+
+    public PluginResolutionApplicator(PluginAware target, ClassLoaderScope classLoaderScope) {
+        this.target = target;
+        this.classLoaderScope = classLoaderScope;
+    }
+
+    public void execute(PluginResolution pluginResolution) {
+        Class<? extends Plugin> pluginClass = pluginResolution.resolve(classLoaderScope);
+        target.getPlugins().apply(pluginClass);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolverFactory.java b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolverFactory.java
new file mode 100644
index 0000000..989b74c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolverFactory.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal;
+
+import org.gradle.api.UnknownProjectException;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.DependencyManagementServices;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.plugins.ClassloaderBackedPluginDescriptorLocator;
+import org.gradle.api.internal.plugins.PluginDescriptorLocator;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.cache.CacheRepository;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.plugin.resolve.internal.CompositePluginResolver;
+import org.gradle.plugin.resolve.internal.NotInPluginRegistryPluginResolverCheck;
+import org.gradle.plugin.resolve.internal.PluginRegistryPluginResolver;
+import org.gradle.plugin.resolve.internal.PluginResolver;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.gradle.plugin.internal.PluginResolvers.jcenterGradleOfficial;
+
+public class PluginResolverFactory {
+
+    private final PluginRegistry pluginRegistry;
+    private final Instantiator instantiator;
+    private final DependencyManagementServices dependencyManagementServices;
+    private final FileResolver fileResolver;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
+    private final DocumentationRegistry documentationRegistry;
+    private final CacheRepository cacheRepository;
+
+    private final ProjectFinder projectFinder = new ProjectFinder() {
+        public ProjectInternal getProject(String path) {
+            throw new UnknownProjectException("Cannot use project dependencies in a plugin resolution definition.");
+        }
+    };
+
+    public PluginResolverFactory(PluginRegistry pluginRegistry, Instantiator instantiator, DependencyManagementServices dependencyManagementServices, FileResolver fileResolver, DependencyMetaDataProvider dependencyMetaDataProvider, DocumentationRegistry documentationRegistry, CacheRepository cacheRepository) {
+        this.pluginRegistry = pluginRegistry;
+        this.instantiator = instantiator;
+        this.dependencyManagementServices = dependencyManagementServices;
+        this.fileResolver = fileResolver;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+        this.documentationRegistry = documentationRegistry;
+        this.cacheRepository = cacheRepository;
+    }
+
+    public PluginResolver createPluginResolver(ClassLoader scriptClassLoader) {
+        List<PluginResolver> resolvers = new LinkedList<PluginResolver>();
+        addDefaultResolvers(resolvers);
+        CompositePluginResolver compositePluginResolver = new CompositePluginResolver(resolvers);
+
+        PluginDescriptorLocator scriptClasspathPluginDescriptorLocator = new ClassloaderBackedPluginDescriptorLocator(scriptClassLoader);
+        PluginResolver notAlreadyOnClasspathCheck = new NotInPluginRegistryPluginResolverCheck(compositePluginResolver, pluginRegistry, scriptClasspathPluginDescriptorLocator);
+
+        return notAlreadyOnClasspathCheck;
+    }
+
+    private void addDefaultResolvers(List<PluginResolver> resolvers) {
+        resolvers.add(new PluginRegistryPluginResolver(documentationRegistry, pluginRegistry));
+        resolvers.add(jcenterGradleOfficial(instantiator, createDependencyResolutionServices(), cacheRepository));
+    }
+
+    private DependencyResolutionServices createDependencyResolutionServices() {
+        return dependencyManagementServices.create(fileResolver, dependencyMetaDataProvider, projectFinder, new BasicDomainObjectContext());
+    }
+
+    private static class BasicDomainObjectContext implements DomainObjectContext {
+        public String absoluteProjectPath(String name) {
+            return name;
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolvers.java b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolvers.java
new file mode 100644
index 0000000..ae57eb6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/internal/PluginResolvers.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentIndexedCacheParameters;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.cache.internal.filelock.LockOptionsBuilder;
+import org.gradle.internal.Factory;
+import org.gradle.internal.Supplier;
+import org.gradle.internal.Suppliers;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.messaging.serialize.BaseSerializerFactory;
+import org.gradle.plugin.resolve.internal.*;
+
+public abstract class PluginResolvers {
+
+    public static PluginResolver jcenterGradleOfficial(Instantiator instantiator, DependencyResolutionServices dependencyResolutionServices, final CacheRepository cacheRepository) {
+        Supplier<PersistentIndexedCache<PluginRequest, String>> cacheSupplier = Suppliers.wrap(
+                Suppliers.ofQuietlyClosed(new Factory<PersistentCache>() {
+                    public PersistentCache create() {
+                        return cacheRepository.cache("plugins").withLockOptions(LockOptionsBuilder.mode(FileLockManager.LockMode.Exclusive)).open();
+                    }
+                }),
+                new Transformer<PersistentIndexedCache<PluginRequest, String>, PersistentCache>() {
+                    public PersistentIndexedCache<PluginRequest, String> transform(PersistentCache original) {
+                        PersistentIndexedCacheParameters<PluginRequest, String> cacheParams = new PersistentIndexedCacheParameters<PluginRequest, String>("jcenter", new PluginRequestSerializer(), BaseSerializerFactory.STRING_SERIALIZER);
+                        return original.createCache(cacheParams);
+                    }
+                });
+
+        final JCenterPluginMapper mapper = new JCenterPluginMapper(cacheSupplier);
+
+        return new ModuleMappingPluginResolver("jcenter plugin resolver", dependencyResolutionServices, instantiator, mapper, new JCenterRepositoryConfigurer()) {
+            @Override
+            public String getDescriptionForNotFoundMessage() {
+                return String.format("Gradle Bintray Plugin Repository (listing: %s)", mapper.getBintrayRepoUrl());
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/ClassPathPluginResolution.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/ClassPathPluginResolution.java
new file mode 100644
index 0000000..dd16b46
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/ClassPathPluginResolution.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.plugins.DefaultPluginRegistry;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.reflect.Instantiator;
+
+class ClassPathPluginResolution implements PluginResolution {
+
+    private final String pluginId;
+    private final Instantiator instantiator;
+    private final Factory<? extends ClassPath> classPathFactory;
+
+    public ClassPathPluginResolution(Instantiator instantiator, String pluginId, Factory<? extends ClassPath> classPathFactory) {
+        this.pluginId = pluginId;
+        this.instantiator = instantiator;
+        this.classPathFactory = classPathFactory;
+    }
+
+    public Class<? extends Plugin> resolve(ClassLoaderScope classLoaderScope) {
+        ClassPath classPath = classPathFactory.create();
+        ClassLoader classLoader = classLoaderScope.addLocal(classPath);
+        PluginRegistry pluginRegistry = new DefaultPluginRegistry(classLoader, instantiator);
+        Class<? extends Plugin> typeForId = pluginRegistry.getTypeForId(pluginId);
+        return typeForId;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/CompositePluginResolver.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/CompositePluginResolver.java
new file mode 100644
index 0000000..f488fcb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/CompositePluginResolver.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import java.util.List;
+
+public class CompositePluginResolver implements PluginResolver {
+
+    private final List<PluginResolver> repositories;
+
+    public CompositePluginResolver(List<PluginResolver> repositories) {
+        this.repositories = repositories;
+    }
+
+    public PluginResolution resolve(PluginRequest pluginRequest) {
+        PluginResolution resolution = null;
+        for (PluginResolver repository : repositories) {
+            resolution = repository.resolve(pluginRequest);
+            if (resolution != null) {
+                break;
+            }
+        }
+
+        return resolution;
+    }
+
+    public String getDescriptionForNotFoundMessage() {
+        StringBuilder sb = new StringBuilder("plugin repositories:");
+        for (PluginResolver repository : repositories) {
+            sb.append("\n - ").append(repository.getDescriptionForNotFoundMessage());
+        }
+        return sb.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/DefaultPluginRequest.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/DefaultPluginRequest.java
new file mode 100644
index 0000000..db0c2bd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/DefaultPluginRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+public class DefaultPluginRequest implements PluginRequest {
+
+    private final String id;
+    private final String version;
+
+    public DefaultPluginRequest(String id) {
+        this.id = id;
+        this.version = null;
+    }
+
+    public DefaultPluginRequest(String id, String version) {
+        this.id = id;
+        this.version = version;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        if (version == null) {
+            return String.format("[plugin: '%s']", id);
+        } else {
+            return String.format("[plugin: '%s', version: '%s']", id, version);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultPluginRequest that = (DefaultPluginRequest) o;
+
+        if (!id.equals(that.id)) {
+            return false;
+        }
+        if (version != null ? !version.equals(that.version) : that.version != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id.hashCode();
+        result = 31 * result + (version != null ? version.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/DependencyResolvingClasspathProvider.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/DependencyResolvingClasspathProvider.java
new file mode 100644
index 0000000..51c26ee
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/DependencyResolvingClasspathProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
+
+public class DependencyResolvingClasspathProvider implements Factory<ClassPath> {
+
+    private final DependencyResolutionServices dependencyResolutionServices;
+    private final Dependency dependency;
+    private final Action<? super RepositoryHandler> repositoriesConfigurer;
+
+    public DependencyResolvingClasspathProvider(DependencyResolutionServices dependencyResolutionServices, Dependency dependency, Action<? super RepositoryHandler> repositoriesConfigurer) {
+        this.dependencyResolutionServices = dependencyResolutionServices;
+        this.dependency = dependency;
+        this.repositoriesConfigurer = repositoriesConfigurer;
+    }
+
+    public ClassPath create() {
+        Configuration configuration = dependencyResolutionServices.getConfigurationContainer().detachedConfiguration(dependency);
+        repositoriesConfigurer.execute(dependencyResolutionServices.getResolveRepositoryHandler());
+        return new DefaultClassPath(configuration.resolve());
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/InvalidPluginRequestException.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/InvalidPluginRequestException.java
new file mode 100644
index 0000000..2c82e5b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/InvalidPluginRequestException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.GradleException;
+
+public class InvalidPluginRequestException extends GradleException {
+    public InvalidPluginRequestException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/JCenterPluginMapper.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/JCenterPluginMapper.java
new file mode 100644
index 0000000..f8719c3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/JCenterPluginMapper.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import com.jfrog.bintray.client.api.handle.Bintray;
+import com.jfrog.bintray.client.api.model.Pkg;
+import com.jfrog.bintray.client.impl.BintrayClient;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.Supplier;
+
+import java.util.List;
+
+public class JCenterPluginMapper implements ModuleMappingPluginResolver.Mapper {
+
+    public static final String BINTRAY_API_OVERRIDE_URL_PROPERTY = JCenterPluginMapper.class.getName() + ".bintray.override";
+
+    public static final String GRADLE_PLUGINS_ORG = "gradle-plugins-development";
+    public static final String GRADLE_PLUGINS_REPO = "gradle-plugins";
+    public static final String PLUGIN_ID_ATTRIBUTE_NAME = "gradle-plugin-id";
+
+    private static final String NOT_FOUND = "";
+
+    private final Supplier<PersistentIndexedCache<PluginRequest, String>> cacheSupplier;
+
+    public JCenterPluginMapper(Supplier<PersistentIndexedCache<PluginRequest, String>> cacheSupplier) {
+        this.cacheSupplier = cacheSupplier;
+    }
+
+    public Dependency map(final PluginRequest request, DependencyHandler dependencyHandler) {
+        final String pluginId = request.getId();
+
+        String systemId = cacheSupplier.supplyTo(new Transformer<String, PersistentIndexedCache<PluginRequest, String>>() {
+            public String transform(PersistentIndexedCache<PluginRequest, String> cache) {
+                return doCacheAwareSearch(request, pluginId, cache);
+            }
+        });
+
+        if (systemId.equals(NOT_FOUND)) {
+            return null;
+        } else {
+            return dependencyHandler.create(systemId + ":" + request.getVersion());
+        }
+    }
+
+    private String doCacheAwareSearch(PluginRequest request, String pluginId, PersistentIndexedCache<PluginRequest, String> indexedCache) {
+        String cached = indexedCache.get(request);
+        if (cached != null) {
+            return cached;
+        }
+
+        Bintray bintrayClient = createBintrayClient();
+        List<Pkg> results = bintrayClient.
+                subject(GRADLE_PLUGINS_ORG).
+                repository(GRADLE_PLUGINS_REPO).
+                searchForPackage().
+                byAttributeName(PLUGIN_ID_ATTRIBUTE_NAME).
+                equals(pluginId).
+                search();
+
+        String systemId;
+
+        if (results.isEmpty()) {
+            systemId = NOT_FOUND;
+        } else if (request.getVersion() == null) {
+            throw new InvalidPluginRequestException(String.format("No version number supplied for plugin '%s'. A version number must be supplied for plugins resolved from '%s'.", pluginId, getBintrayRepoUrl()));
+        } else if (results.size() > 1) {
+            throw new InvalidPluginRequestException("Found more than one plugin for plugin id " + pluginId);
+        } else {
+            Pkg pluginPackage = results.get(0);
+            List<String> systemIds = pluginPackage.systemIds();
+            if (systemIds.isEmpty()) {
+                throw new InvalidPluginRequestException("No artifacts in maven layout found for plugin id" + pluginId);
+            }
+
+            systemId = systemIds.get(0);
+        }
+
+        indexedCache.put(request, systemId);
+        return systemId;
+    }
+
+    private Bintray createBintrayClient() {
+        String override = System.getProperty(BINTRAY_API_OVERRIDE_URL_PROPERTY);
+        if (override == null) {
+            return BintrayClient.create();
+        } else {
+            return BintrayClient.create(override, null, null);
+        }
+    }
+
+    public String getBintrayRepoUrl() {
+        return String.format("https://bintray.com/%s/%s", GRADLE_PLUGINS_ORG, GRADLE_PLUGINS_REPO);
+    }
+
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/JCenterRepositoryConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/JCenterRepositoryConfigurer.java
new file mode 100644
index 0000000..c0b1691
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/JCenterRepositoryConfigurer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+
+public class JCenterRepositoryConfigurer implements Action<RepositoryHandler> {
+    public void execute(RepositoryHandler repositories) {
+        repositories.jcenter();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/ModuleMappingPluginResolver.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/ModuleMappingPluginResolver.java
new file mode 100644
index 0000000..8e3063b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/ModuleMappingPluginResolver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.reflect.Instantiator;
+
+public abstract class ModuleMappingPluginResolver implements PluginResolver {
+
+    private final String name;
+    private final DependencyResolutionServices dependencyResolutionServices;
+    private final Instantiator instantiator;
+    private final Mapper mapper;
+    private Action<? super RepositoryHandler> repositoriesConfigurer;
+
+    public interface Mapper {
+        @Nullable
+        Dependency map(PluginRequest request, DependencyHandler dependencyHandler);
+    }
+
+    public ModuleMappingPluginResolver(String name, DependencyResolutionServices dependencyResolutionServices, Instantiator instantiator, Mapper mapper, Action<? super RepositoryHandler> repositoriesConfigurer) {
+        this.name = name;
+        this.dependencyResolutionServices = dependencyResolutionServices;
+        this.instantiator = instantiator;
+        this.mapper = mapper;
+        this.repositoriesConfigurer = repositoriesConfigurer;
+    }
+
+    public PluginResolution resolve(final PluginRequest pluginRequest) {
+        final Dependency dependency = mapper.map(pluginRequest, dependencyResolutionServices.getDependencyHandler());
+        if (dependency == null) {
+            return null;
+        } else {
+            // TODO the dependency resolution config of this guy needs to be externalized
+            Factory<ClassPath> classPathFactory = new DependencyResolvingClasspathProvider(dependencyResolutionServices, dependency, repositoriesConfigurer);
+
+            return new ClassPathPluginResolution(instantiator, pluginRequest.getId(), classPathFactory);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getName() + "[" + name + "]";
+    }
+
+    public abstract String getDescriptionForNotFoundMessage();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/NotInPluginRegistryPluginResolverCheck.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/NotInPluginRegistryPluginResolverCheck.java
new file mode 100644
index 0000000..d48b484
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/NotInPluginRegistryPluginResolverCheck.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.internal.plugins.PluginDescriptor;
+import org.gradle.api.internal.plugins.PluginDescriptorLocator;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.plugins.UnknownPluginException;
+
+public class NotInPluginRegistryPluginResolverCheck implements PluginResolver {
+
+    private final PluginResolver delegate;
+    private final PluginRegistry corePluginRegistry;
+    private final PluginDescriptorLocator pluginDescriptorLocator;
+
+    public NotInPluginRegistryPluginResolverCheck(PluginResolver delegate, PluginRegistry corePluginRegistry, PluginDescriptorLocator pluginDescriptorLocator) {
+        this.delegate = delegate;
+        this.corePluginRegistry = corePluginRegistry;
+        this.pluginDescriptorLocator = pluginDescriptorLocator;
+    }
+
+    public PluginResolution resolve(PluginRequest pluginRequest) {
+        String pluginId = pluginRequest.getId();
+        PluginDescriptor pluginDescriptor = pluginDescriptorLocator.findPluginDescriptor(pluginId);
+        if (pluginDescriptor == null || isCorePlugin(pluginId)) {
+            return delegate.resolve(pluginRequest);
+        } else {
+            throw new InvalidPluginRequestException(
+                    String.format("Plugin '%s' is already on the script classpath (plugins on the script classpath cannot be used in a plugins {} block; move \"apply plugin: '%s'\" outside of the plugins {} block)", pluginId, pluginId)
+            );
+        }
+    }
+
+    private boolean isCorePlugin(String pluginId) {
+        try {
+            corePluginRegistry.getTypeForId(pluginId);
+            return true;
+        } catch (UnknownPluginException ignore) {
+            return false;
+        }
+    }
+
+    public String getDescriptionForNotFoundMessage() {
+        return delegate.getDescriptionForNotFoundMessage();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRegistryPluginResolver.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRegistryPluginResolver.java
new file mode 100644
index 0000000..0465bb4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRegistryPluginResolver.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.plugins.UnknownPluginException;
+
+public class PluginRegistryPluginResolver implements PluginResolver {
+
+    private final DocumentationRegistry documentationRegistry;
+    private final PluginRegistry pluginRegistry;
+
+    public PluginRegistryPluginResolver(DocumentationRegistry documentationRegistry, PluginRegistry pluginRegistry) {
+        this.documentationRegistry = documentationRegistry;
+        this.pluginRegistry = pluginRegistry;
+    }
+
+    public PluginResolution resolve(PluginRequest pluginRequest) {
+        try {
+            Class<? extends Plugin> typeForId = pluginRegistry.getTypeForId(pluginRequest.getId());
+            if (pluginRequest.getVersion() != null) {
+                throw new InvalidPluginRequestException(
+                        "Plugin '" + pluginRequest.getId() + "' is a core Gradle plugin, which cannot be specified with a version number. "
+                        + "Such plugins are versioned as part of Gradle. Please remove the version number from the declaration.");
+            }
+            return new SimplePluginResolution(typeForId);
+        } catch (UnknownPluginException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "PluginRegistryPluginResolver[" + pluginRegistry + "]";
+    }
+
+    public String getDescriptionForNotFoundMessage() {
+        return String.format("Gradle Distribution Plugins (listing: %s)", documentationRegistry.getDocumentationFor("standard_plugins"));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRequest.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRequest.java
new file mode 100644
index 0000000..97ff8dc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRequest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+/**
+ * Represents a request for a plugin.
+ */
+ at Incubating
+public interface PluginRequest {
+
+    String getId();
+
+    @Nullable
+    String getVersion();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRequestSerializer.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRequestSerializer.java
new file mode 100644
index 0000000..6921d4d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginRequestSerializer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.Serializer;
+
+public class PluginRequestSerializer implements Serializer<PluginRequest> {
+
+    public PluginRequest read(Decoder decoder) throws Exception {
+        return new DefaultPluginRequest(decoder.readString(), decoder.readNullableString());
+    }
+
+    public void write(Encoder encoder, PluginRequest value) throws Exception {
+        encoder.writeString(value.getId());
+        encoder.writeNullableString(value.getVersion());
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginResolution.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginResolution.java
new file mode 100644
index 0000000..2e3314f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginResolution.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+
+/**
+ * The result of attempting to resolve a plugin to a classpath.
+ */
+ at Incubating
+public interface PluginResolution {
+
+    Class<? extends Plugin> resolve(ClassLoaderScope classLoaderScope);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginResolver.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginResolver.java
new file mode 100644
index 0000000..71a674d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/PluginResolver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+/**
+ * A repository of plugins.
+ */
+ at Incubating
+public interface PluginResolver {
+
+    @Nullable
+    PluginResolution resolve(PluginRequest pluginRequest);
+
+    String getDescriptionForNotFoundMessage();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/SimplePluginResolution.java b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/SimplePluginResolution.java
new file mode 100644
index 0000000..90980ed
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/resolve/internal/SimplePluginResolution.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.resolve.internal;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+
+public class SimplePluginResolution implements PluginResolution {
+
+    private final Class<? extends Plugin> pluginClass;
+
+    public SimplePluginResolution(Class<? extends Plugin> pluginClass) {
+        this.pluginClass = pluginClass;
+    }
+
+    public Class<? extends Plugin> resolve(ClassLoaderScope classLoaderScope) {
+        return pluginClass;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/BaseExecSpec.java b/subprojects/core/src/main/groovy/org/gradle/process/BaseExecSpec.java
index 734401a..85ecb0e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/BaseExecSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/BaseExecSpec.java
@@ -21,8 +21,6 @@ import java.util.List;
 
 /**
  * Specifies options for launching a child process.
- *
- * @author Hans Dockter
  */
 public interface BaseExecSpec extends ProcessForkOptions {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/ExecResult.java b/subprojects/core/src/main/groovy/org/gradle/process/ExecResult.java
index df3ef6a..0109805 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/ExecResult.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/ExecResult.java
@@ -20,8 +20,6 @@ import org.gradle.process.internal.ExecException;
 
 /**
  * Represents the result of running an external process.
- *
- * @author Hans Dockter
  */
 public interface ExecResult {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/ExecSpec.java b/subprojects/core/src/main/groovy/org/gradle/process/ExecSpec.java
index 83ca73a..7cb1a9e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/ExecSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/ExecSpec.java
@@ -19,8 +19,6 @@ import java.util.List;
 
 /**
  * Specified the options for executing some command.
- *
- * @author Hans Dockter
  */
 public interface ExecSpec extends BaseExecSpec {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/JavaExecSpec.java b/subprojects/core/src/main/groovy/org/gradle/process/JavaExecSpec.java
index 8c5222c..f92d5bd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/JavaExecSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/JavaExecSpec.java
@@ -21,8 +21,6 @@ import java.util.List;
 
 /**
  * Specifies the options for executing a Java application.
- *
- * @author Hans Dockter
  */
 public interface JavaExecSpec extends JavaForkOptions, BaseExecSpec {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
index 9672afa..f5064af 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
@@ -27,9 +27,6 @@ import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractExecHandleBuilder extends DefaultProcessForkOptions implements BaseExecSpec {
     private OutputStream standardOutput;
     private OutputStream errorOutput;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java
index 2a2f17b..edbf5d1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java
@@ -15,9 +15,6 @@
  */
 package org.gradle.process.internal;
 
-/**
- * @author Tom Eyckmans
- */
 public class BadExitCodeException extends Exception {
     public BadExitCodeException(String message) {
         super(message);
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.java
index 86ef6fc..2f48c8e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.java
@@ -17,21 +17,13 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.process.ExecResult;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExecAction extends ExecHandleBuilder implements ExecAction {
     public DefaultExecAction(FileResolver fileResolver) {
         super(fileResolver);
     }
 
-    public DefaultExecAction() {
-        super(new IdentityFileResolver());
-    }
-
     public ExecResult execute() {
         ExecHandle execHandle = build();
         ExecResult execResult = execHandle.start().waitForFinish();
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
index 20a330f..378098a 100755
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
@@ -17,11 +17,12 @@
 package org.gradle.process.internal;
 
 import com.google.common.base.Joiner;
-
+import net.rubygrapefruit.platform.ProcessLauncher;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.shutdown.ShutdownHookActionRegister;
@@ -52,8 +53,6 @@ import java.util.concurrent.locks.ReentrantLock;
  * <li>{@link #start()} allowed when state is INIT</li>
  * <li>{@link #abort()} allowed when state is STARTED or DETACHED</li>
  * </ul>
- *
- * @author Tom Eyckmans
  */
 public class DefaultExecHandle implements ExecHandle, ProcessSettings {
     private static final Logger LOGGER = Logging.getLogger(DefaultExecHandle.class);
@@ -78,6 +77,7 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
     private final Map<String, String> environment;
     private final StreamsHandler streamsHandler;
     private final boolean redirectErrorStream;
+    private final ProcessLauncher processLauncher;
     private int timeoutMillis;
     private boolean daemon;
 
@@ -122,6 +122,7 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
         this.condition = lock.newCondition();
         this.state = ExecHandleState.INIT;
         executor = new DefaultExecutorFactory().create(String.format("Run %s", displayName));
+        processLauncher = NativeServices.getInstance().get(ProcessLauncher.class);
         shutdownHookAction = new ExecHandleShutdownHookAction(this);
         broadcast = new ListenerBroadcast<ExecHandleListener>(ExecHandleListener.class);
         broadcast.addAll(listeners);
@@ -200,13 +201,13 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
                 }
             }
             setState(newState);
-            execResult = new ExecResultImpl(exitValue, wrappedException);
+            execResult = new ExecResultImpl(exitValue, wrappedException, displayName);
             result = execResult;
         } finally {
             lock.unlock();
         }
 
-        LOGGER.info("Process '{}' finished with exit value {} (state: {})", displayName, exitValue, newState);
+        LOGGER.debug("Process '{}' finished with exit value {} (state: {})", displayName, exitValue, newState);
 
         if (currentState != ExecHandleState.DETACHED && newState != ExecHandleState.DETACHED) {
             broadcast.getSource().executionFinished(this, result);
@@ -227,7 +228,7 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
             }
             setState(ExecHandleState.STARTING);
 
-            execHandleRunner = new ExecHandleRunner(this, streamsHandler);
+            execHandleRunner = new ExecHandleRunner(this, streamsHandler, processLauncher);
             executor.execute(execHandleRunner);
 
             while(stateIn(ExecHandleState.STARTING)) {
@@ -344,13 +345,15 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
         return timeoutMillis;
     }
 
-    private class ExecResultImpl implements ExecResult {
+    private static class ExecResultImpl implements ExecResult {
         private final int exitValue;
         private final ExecException failure;
+        private final String displayName;
 
-        public ExecResultImpl(int exitValue, ExecException failure) {
+        public ExecResultImpl(int exitValue, ExecException failure, String displayName) {
             this.exitValue = exitValue;
             this.failure = failure;
+            this.displayName = displayName;
         }
 
         public int getExitValue() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java
index 0dcae9a..1adcfd5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java
@@ -19,9 +19,6 @@ package org.gradle.process.internal;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.process.ExecResult;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultJavaExecAction extends JavaExecHandleBuilder implements JavaExecAction {
     public DefaultJavaExecAction(FileResolver fileResolver) {
         super(fileResolver);
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 b2e2ece..1d067c5 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
@@ -18,8 +18,8 @@ package org.gradle.process.internal;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.messaging.remote.ConnectionAcceptor;
 import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.ExecResult;
@@ -30,6 +30,8 @@ import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+import static java.lang.String.format;
+
 public class DefaultWorkerProcess implements WorkerProcess {
     private final static Logger LOGGER = Logging.getLogger(DefaultWorkerProcess.class);
     private final Lock lock = new ReentrantLock();
@@ -134,7 +136,10 @@ 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 (%s, running: %s) to connect.", ((double) connectTimeout) / 1000, execHandle, execHandle.getState(), running));
+                        throw new ExecException(format("Unable to connect to the child process '%s'.\n"
+                                + "It is likely that the child process have crashed - please find the stack trace in the build log.\n"
+                                + "This exception might occur when the build machine is extremely loaded.\n"
+                                + "The connection attempt hit a timeout after %.1f seconds (last known process state: %s, running: %s).", execHandle, ((double) connectTimeout) / 1000, execHandle.getState(), running));
                     }
                 } catch (InterruptedException e) {
                     throw UncheckedException.throwAsUncheckedException(e);
@@ -144,7 +149,7 @@ public class DefaultWorkerProcess implements WorkerProcess {
                 throw UncheckedException.throwAsUncheckedException(processFailure);
             }
             if (connection == null) {
-                throw new ExecException(String.format("Never received a connection from %s.", execHandle));
+                throw new ExecException(format("Never received a connection from %s.", execHandle));
             }
         } finally {
             lock.unlock();
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
index b4c7c3c..a2cb5f7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
@@ -21,13 +21,16 @@ import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.Factory;
+import org.gradle.internal.classloader.ClasspathUtil;
 import org.gradle.internal.id.IdGenerator;
-import org.gradle.messaging.remote.*;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectionAcceptor;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.internal.child.ApplicationClassesInIsolatedClassLoaderWorkerFactory;
 import org.gradle.process.internal.child.ApplicationClassesInSystemClassLoaderWorkerFactory;
 import org.gradle.process.internal.child.EncodedStream;
 import org.gradle.process.internal.child.WorkerFactory;
-import org.gradle.util.ClasspathUtil;
 import org.gradle.util.GUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -74,9 +77,9 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
             }
 
             final DefaultWorkerProcess workerProcess = new DefaultWorkerProcess(120, TimeUnit.SECONDS);
-            ConnectionAcceptor acceptor = server.accept(new Action<ConnectEvent<ObjectConnection>>() {
-                public void execute(ConnectEvent<ObjectConnection> event) {
-                    workerProcess.onConnect(event.getConnection());
+            ConnectionAcceptor acceptor = server.accept(new Action<ObjectConnection>() {
+                public void execute(ObjectConnection connection) {
+                    workerProcess.onConnect(connection);
                 }
             });
             workerProcess.startAccepting(acceptor);
@@ -85,7 +88,7 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
             // Build configuration for GradleWorkerMain
             List<URL> implementationClassPath = ClasspathUtil.getClasspath(getWorker().getClass().getClassLoader());
             Object id = idGenerator.generateId();
-            String displayName = String.format("Gradle Worker %s", id);
+            String displayName = getBaseName() + " " + id;
 
             WorkerFactory workerFactory;
             if (isLoadApplicationInSystemClassLoader()) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecAction.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecAction.java
index ca68e64..854ce4f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecAction.java
@@ -15,12 +15,11 @@
  */
 package org.gradle.process.internal;
 
+import org.gradle.api.NonExtensible;
 import org.gradle.process.ExecResult;
 import org.gradle.process.ExecSpec;
 
-/**
- * @author Hans Dockter
- */
+ at NonExtensible
 public interface ExecAction extends ExecSpec {
     ExecResult execute() throws ExecException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecActionFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecActionFactory.java
new file mode 100644
index 0000000..11069a0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecActionFactory.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.process.internal;
+
+public interface ExecActionFactory {
+    ExecAction newExecAction();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecException.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecException.java
index 6999d85..2e964de 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecException.java
@@ -18,9 +18,6 @@ package org.gradle.process.internal;
 
 import org.gradle.api.GradleException;
 
-/**
- * @author Hans Dockter
- */
 public class ExecException extends GradleException {
     public ExecException(String message) {
         super(message);
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
index 9a999a1..5c58856 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
@@ -22,9 +22,6 @@ import java.io.File;
 import java.util.List;
 import java.util.Map;
 
-/**
- * @author Tom Eyckmans
- */
 public interface ExecHandle {
 
     File getDirectory();
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
index 175d150..9ad91a5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
@@ -29,9 +29,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-/**
- * @author Tom Eyckmans
- */
 public class ExecHandleBuilder extends AbstractExecHandleBuilder implements ExecSpec {
     private final List<Object> arguments = new ArrayList<Object>();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java
index a3f8b58..6ef4a82 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java
@@ -18,9 +18,6 @@ package org.gradle.process.internal;
 
 import org.gradle.process.ExecResult;
 
-/**
- * @author Tom Eyckmans
- */
 public interface ExecHandleListener {
     void executionStarted(ExecHandle execHandle);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
index 7b86a21..10024f0 100755
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
@@ -16,6 +16,7 @@
 
 package org.gradle.process.internal;
 
+import net.rubygrapefruit.platform.ProcessLauncher;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.process.internal.streams.StreamsHandler;
@@ -23,22 +24,20 @@ import org.gradle.process.internal.streams.StreamsHandler;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-/**
- * @author Tom Eyckmans
- */
 public class ExecHandleRunner implements Runnable {
-    private static final Object START_LOCK = new Object();
     private static final Logger LOGGER = Logging.getLogger(ExecHandleRunner.class);
 
     private final ProcessBuilderFactory processBuilderFactory;
     private final DefaultExecHandle execHandle;
     private final Lock lock = new ReentrantLock();
+    private final ProcessLauncher processLauncher;
 
     private Process process;
     private boolean aborted;
     private final StreamsHandler streamsHandler;
 
-    public ExecHandleRunner(DefaultExecHandle execHandle, StreamsHandler streamsHandler) {
+    public ExecHandleRunner(DefaultExecHandle execHandle, StreamsHandler streamsHandler, ProcessLauncher processLauncher) {
+        this.processLauncher = processLauncher;
         if (execHandle == null) {
             throw new IllegalArgumentException("execHandle == null!");
         }
@@ -61,17 +60,10 @@ public class ExecHandleRunner implements Runnable {
     }
 
     public void run() {
-        ProcessParentingInitializer.intitialize();
-        ProcessBuilder processBuilder = processBuilderFactory.createProcessBuilder(execHandle);
         try {
-            Process process;
-
-            // This big fat static lock is here for windows. When starting multiple processes concurrently, the stdout
-            // and stderr streams for some of the processes get stuck
-            synchronized (START_LOCK) {
-                process = processBuilder.start();
-                streamsHandler.connectStreams(process, execHandle.getDisplayName());
-            }
+            ProcessBuilder processBuilder = processBuilderFactory.createProcessBuilder(execHandle);
+            Process process = processLauncher.start(processBuilder);
+            streamsHandler.connectStreams(process, execHandle.getDisplayName());
             setProcess(process);
 
             execHandle.started();
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java
index f2cbddf..48a54c6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java
@@ -20,8 +20,6 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Terminates the external running 'sub' process when the Gradle process is being cancelled.
- *
- * @author Tom Eyckmans
  */
 public class ExecHandleShutdownHookAction implements Runnable {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
index 783c337..b3e227b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
@@ -16,9 +16,6 @@
 
 package org.gradle.process.internal;
 
-/**
- * @author Tom Eyckmans
- */
 public enum ExecHandleState {
     INIT,
     STARTING,
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java
index 81f5b49..7d9e654 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java
@@ -15,12 +15,11 @@
  */
 package org.gradle.process.internal;
 
+import org.gradle.api.NonExtensible;
 import org.gradle.process.ExecResult;
 import org.gradle.process.JavaExecSpec;
 
-/**
- * @author Hans Dockter
- */
+ at NonExtensible
 public interface JavaExecAction extends JavaExecSpec {
     ExecResult execute();
 }
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 50367b6..69c0946 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
@@ -166,17 +166,17 @@ public class JavaExecHandleBuilder extends AbstractExecHandleBuilder implements
 
     public JavaExecHandleBuilder setArgs(Iterable<?> applicationArgs) {
         this.applicationArgs.clear();
-        GUtil.addToCollection(this.applicationArgs, applicationArgs);
+        args(applicationArgs);
         return this;
     }
 
     public JavaExecHandleBuilder args(Object... args) {
-        applicationArgs.addAll(Arrays.asList(args));
+        args(Arrays.asList(args));
         return this;
     }
 
     public JavaExecSpec args(Iterable<?> args) {
-        GUtil.addToCollection(applicationArgs, args);
+        GUtil.addToCollection(applicationArgs, true, args);
         return this;
     }
 
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 91ae121..ab9f04d 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
@@ -30,7 +30,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class JvmOptions {
-    private static final Pattern SYS_PROP_PATTERN = Pattern.compile("-D(.+?)=(.*)");
+    private static final Pattern SYS_PROP_PATTERN = Pattern.compile("(?s)-D(.+?)=(.*)");
     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(.+)");
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
index 59e4aa4..0957409 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
@@ -22,8 +22,6 @@ import java.util.Map;
 
 /**
  * Creates a {@link java.lang.ProcessBuilder} based on a {@link ExecHandle}.
- *
- * @author Tom Eyckmans
  */
 public class ProcessBuilderFactory {
     public ProcessBuilder createProcessBuilder(ProcessSettings processSettings) {
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
deleted file mode 100644
index aba64ee..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
+++ /dev/null
@@ -1,87 +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.process.internal;
-
-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;
-
-/**
- * Initializes for a well behaved parent process.
- * <p>
- * by Szczepan Faber, created at: 3/2/12
- */
-public class ProcessParentingInitializer {
-
-    private static boolean initialized;
-    private static Synchronizer synchronizer = new Synchronizer();
-    private static final Logger LOGGER = Logging.getLogger(ProcessParentingInitializer.class);
-
-    /**
-     * Initializes the current process so that it can be a well behaving parent.
-     * <p>
-     * If the process has been already initialized then the method does nothing.
-     */
-    public static void intitialize() {
-        ProcessParentingInitializer.intitialize(new Factory<Object>() {
-            public Object create() { return null; } //no op
-        });
-    }
-    
-    /**
-     * Initializes the current process so that it can be a well behaving parent.
-     * <p>
-     * Intended to solve an intermittent windows problem that made some child processes not quite detached in concurrent scenario.
-     * This method is useful when the child process is intended to live longer than the parent process (this process)
-     * *and* you would like to support thread safety. Use it if spawning the child processes may occur concurrently.
-     * <p>
-     * The operation parameter is intended to spawn the well behaving daemon process that closes its inputs/outputs.
-     * The operation should not wait for the daemon to finish - it should rather monitor the daemon for a little bit
-     * to make sure it started properly and then return or throw. For example, the operation can consume daemon outputs
-     * until the daemon closes them.
-     * <p>
-     * operation parameter is a Factory so that your spawning operation can return value should you need it.
-     * <p>
-     * If the parent process has been already initialized then the method simply executes the operation.
-     */
-    public static <T> T intitialize(final Factory<T> operation) {
-        if (initialized) {
-            return operation.create();
-        }
-        return synchronizer.synchronize(new Factory<T>() {
-            public T create() {
-                if (initialized) {
-                    return operation.create();
-                }
-                try {
-                    //make sure the the children will be fully detached on windows:
-                    if (OperatingSystem.current().isWindows()) {
-                        new WindowsHandlesManipulator().uninheritStandardStreams();
-                    }
-                    return operation.create();
-                } finally {
-                    //even if initialization fails we don't want it to re-run
-                    initialized = true;
-                    LOGGER.info("An attempt to initialize for well behaving parent process finished.");
-                }
-            }
-        });
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java
index f571230..b6dbe5a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java
@@ -20,9 +20,6 @@ import java.io.File;
 import java.util.List;
 import java.util.Map;
 
-/**
- * by Szczepan Faber, created at: 4/20/12
- */
 public interface ProcessSettings {
 
     File getDirectory();
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcess.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcess.java
index f839ffc..058687a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcess.java
@@ -20,13 +20,16 @@ import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.ExecResult;
 
 /**
- * A Java process which performs some worker action. You can send and receive messages to/from the worker process
+ * A Java child process that performs some worker action. You can send and receive messages to/from the worker action
  * using a supplied {@link org.gradle.messaging.remote.ObjectConnection}.
  */
 public interface WorkerProcess {
-    ObjectConnection getConnection();
-
     void start();
 
+    /**
+     * The connection to the worker. Call {@link org.gradle.messaging.remote.ObjectConnection#connect()} to complete the connection.
+     */
+    ObjectConnection getConnection();
+
     ExecResult waitForStop();
 }
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 358e50a..dc8c7ce 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
@@ -42,11 +42,21 @@ public abstract class WorkerProcessBuilder {
     private Action<? super WorkerProcessContext> action;
     private LogLevel logLevel = LogLevel.LIFECYCLE;
     private boolean loadApplicationInSystemClassLoader;
+    private String baseName = "Gradle Worker";
 
     public WorkerProcessBuilder(FileResolver fileResolver) {
         javaCommand = new JavaExecHandleBuilder(fileResolver);
     }
 
+    public WorkerProcessBuilder setBaseName(String baseName) {
+        this.baseName = baseName;
+        return this;
+    }
+
+    public String getBaseName() {
+        return baseName;
+    }
+
     public WorkerProcessBuilder applicationClasspath(Iterable<File> files) {
         GUtil.addToCollection(applicationClasspath, files);
         return this;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java
index 52383ae..b4e9799 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java
@@ -30,7 +30,7 @@ public interface WorkerProcessContext {
     String getDisplayName();
 
     /**
-     * Returns the connection which can be used to send/receive messages to/from the server process.
+     * Returns the connection which can be used to send/receive messages to/from the server process. Call {@link ObjectConnection#connect()} to complete the connection.
      */
     ObjectConnection getServerConnection();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
index c1f8fcc..f7613eb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
@@ -49,8 +49,8 @@ import java.util.concurrent.Callable;
  *                |                               |
  *             filter                          filter
  *        (shared packages)                  (logging)
- *                |                              |
- *                +---------------+--------------+
+ *                |                               |
+ *                +---------------+---------------+
  *                                |
  *                          implementation
  *         (ActionExecutionWorker + worker action implementation)
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java
index b516b8c..e7a3159 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java
@@ -23,8 +23,6 @@ import java.io.OutputStream;
 /**
  * Provides Input/OutputStream implementations that are able to encode/decode using a simple algorithm (byte<->2 digit hex string(2 bytes)).
  * Useful when streams are interpreted a text streams as it happens on IBM java for standard input.
- * <p>
- * by Szczepan Faber, created at: 5/25/12
  */
 public abstract class EncodedStream {
     private final static char[] HEX_DIGIT = new char[] {
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 a482c8c..144fbe3 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
@@ -19,6 +19,10 @@ package org.gradle.process.internal.child;
 import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.classloader.CachingClassLoader;
+import org.gradle.internal.classloader.FilteringClassLoader;
+import org.gradle.internal.classloader.MultiParentClassLoader;
+import org.gradle.internal.classloader.MutableURLClassLoader;
 import org.gradle.internal.io.ClassLoaderObjectInputStream;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
@@ -86,6 +90,6 @@ public class ImplementationClassLoaderWorker implements Action<WorkerContext>, S
     }
 
     MutableURLClassLoader createImplementationClassLoader(ClassLoader system, ClassLoader application) {
-        return new MutableURLClassLoader(new MultiParentClassLoader(application, system));
+        return new MutableURLClassLoader(new CachingClassLoader(new MultiParentClassLoader(application, system)));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
index a069bb7..b8511c9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
@@ -17,7 +17,7 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
-import org.gradle.util.DefaultClassLoaderFactory;
+import org.gradle.internal.classloader.DefaultClassLoaderFactory;
 
 import java.io.Serializable;
 import java.net.URI;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
index ae6448f..dec4ff6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
@@ -25,7 +25,6 @@ import java.util.concurrent.Callable;
 /**
  * <p>Stage 3 of the start-up for a worker process with the application classes loaded in the system ClassLoader. Takes
  * care of adding the application classes to the system ClassLoader and then invoking the next stage of start-up.</p>
- * TODO:DAZ No longer adds application classes to system ClassLoader: is this required at all?
  *
  * <p> Instantiated in the worker bootstrap ClassLoader and invoked from {@link org.gradle.process.internal.launcher.BootstrapClassLoaderWorker}.
  * See {@link ApplicationClassesInSystemClassLoaderWorkerFactory} for details.</p>
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
index 4cfe4e9..be3eed7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
@@ -46,6 +46,7 @@ public class WorkerProcessClassPathProvider implements ClassPathProvider {
     private final ModuleRegistry moduleRegistry;
     private final Object lock = new Object();
     private ClassPath workerClassPath;
+    private PersistentCache workerClassPathCache;
 
     public WorkerProcessClassPathProvider(CacheRepository cacheRepository, ModuleRegistry moduleRegistry) {
         this.cacheRepository = cacheRepository;
@@ -65,13 +66,17 @@ public class WorkerProcessClassPathProvider implements ClassPathProvider {
             classpath = classpath.plus(moduleRegistry.getExternalModule("logback-classic").getClasspath());
             classpath = classpath.plus(moduleRegistry.getExternalModule("logback-core").getClasspath());
             classpath = classpath.plus(moduleRegistry.getExternalModule("jul-to-slf4j").getClasspath());
+            classpath = classpath.plus(moduleRegistry.getExternalModule("guava-jdk5").getClasspath());
             return classpath;
         }
         if (name.equals("WORKER_MAIN")) {
             synchronized (lock) {
                 if (workerClassPath == null) {
-                    PersistentCache cache = cacheRepository.cache("workerMain").withInitializer(new CacheInitializer()).open();
-                    workerClassPath = new DefaultClassPath(jarFile(cache));
+                    workerClassPathCache = cacheRepository
+                            .cache("workerMain")
+                            .withInitializer(new CacheInitializer())
+                            .open();
+                    workerClassPath = new DefaultClassPath(jarFile(workerClassPathCache));
                 }
                 LOGGER.debug("Using worker process classpath: {}", workerClassPath);
                 return workerClassPath;
@@ -81,6 +86,21 @@ public class WorkerProcessClassPathProvider implements ClassPathProvider {
         return null;
     }
 
+    public void close() {
+        // This isn't quite right. Should close the worker classpath cache once we're finished with the worker processes. This may be before the end of this build
+        // or they may be used across multiple builds
+        synchronized (lock) {
+            try {
+                if (workerClassPathCache != null) {
+                    workerClassPathCache.close();
+                }
+            } finally {
+                workerClassPathCache = null;
+                workerClassPath = null;
+            }
+        }
+    }
+
     private static File jarFile(PersistentCache cache) {
         return new File(cache.getBaseDir(), "gradle-worker.jar");
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java
index 24eca1f..90281e8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java
@@ -15,16 +15,9 @@
  */
 package org.gradle.process.internal.shutdown;
 
-import org.gradle.api.UncheckedIOException;
-
-import java.io.Closeable;
-import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-/**
- * @author Tom Eyckmans
- */
 public class ShutdownHookActionRegister {
     private static final ShutdownHookActionRegister INSTANCE = new ShutdownHookActionRegister();
     private final List<Runnable> shutdownHookActions = new CopyOnWriteArrayList<Runnable>();
@@ -41,18 +34,6 @@ public class ShutdownHookActionRegister {
         INSTANCE.shutdownHookActions.remove(shutdownHookAction);
     }
 
-    public static void closeOnExit(final Closeable closeable) {
-        addAction(new Runnable() {
-            public void run() {
-                try {
-                    closeable.close();
-                } catch (IOException e) {
-                    throw new UncheckedIOException(e);
-                }
-            }
-        });
-    }
-
     private class GradleShutdownHook implements Runnable {
         public void run() {
             for (final Runnable shutdownHookAction : shutdownHookActions) {
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 e1fb0ec..b4c7be8 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
@@ -18,16 +18,13 @@ package org.gradle.process.internal.streams;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.concurrent.Executor;
 
-/**
- * @author Tom Eyckmans
- */
 public class ExecOutputHandleRunner implements Runnable {
     private final static Logger LOGGER = Logging.getLogger(ExecOutputHandleRunner.class);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java
index d751b39..990e4f8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java
@@ -22,9 +22,6 @@ import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-/**
- * by Szczepan Faber, created at: 4/17/12
- */
 public class SafeStreams {
 
     public static OutputStream systemErr() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java
index 14fd1b2..35efc00 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java
@@ -24,9 +24,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-/**
- * by Szczepan Faber, created at: 4/17/12
- */
 public class StreamsForwarder implements StreamsHandler {
 
     private final OutputStream standardOutput;
@@ -47,6 +44,11 @@ public class StreamsForwarder implements StreamsHandler {
     }
 
     public void connectStreams(Process process, String processName) {
+        /*
+            There's a potential problem here in that DisconnectableInputStream reads from input in the background.
+            This won't automatically stop when the process is over. Therefore, if input is not closed then this thread
+            will run forever. It would be better to ensure that this thread stops when the process does.
+         */
         InputStream instr = new DisconnectableInputStream(input);
 
         standardOutputRunner = new ExecOutputHandleRunner("read standard output of: " + processName,
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java
index f13d605..204fa25 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java
@@ -16,11 +16,8 @@
 
 package org.gradle.process.internal.streams;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 
-/**
- * by Szczepan Faber, created at: 4/27/12
- */
 public interface StreamsHandler extends Stoppable {
 
     void start();
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java
index d574371..7727436 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java
@@ -15,10 +15,11 @@
  */
 package org.gradle.profile;
 
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.ResolvableDependencies;
-import org.gradle.api.invocation.Gradle;
+import org.gradle.StartParameter;
+import org.gradle.util.CollectionUtils;
 
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -35,28 +36,26 @@ import java.util.Map;
  * <li>setBuildStarted</li>
  * <li>setSettingsEvaluated</li>
  * <li>setProjectsLoaded</li>
- * <li>setProjectsEvaluated</li>
+ * <li>setProjectsConfigured</li>
  * <li>setBuildFinished</li>
  * </ul>
  */
 public class BuildProfile {
-    private final Gradle gradle;
-    private final Map<Project, ProjectProfile> projects = new LinkedHashMap<Project, ProjectProfile>();
-    private final Map<String, DependencyResolveProfile> dependencySets = new LinkedHashMap<String, DependencyResolveProfile>();
+
+    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd - HH:mm:ss");
+
+    private final Map<String, ProjectProfile> projects = new LinkedHashMap<String, ProjectProfile>();
+    private final Map<String, ContinuousOperation> dependencySets = new LinkedHashMap<String, ContinuousOperation>();
     private long profilingStarted;
     private long buildStarted;
     private long settingsEvaluated;
     private long projectsLoaded;
-    private long projectsEvaluated;
     private long buildFinished;
+    private StartParameter startParameter;
     private boolean successful;
 
-    public BuildProfile(Gradle gradle) {
-        this.gradle = gradle;
-    }
-
-    public Gradle getGradle() {
-        return gradle;
+    public BuildProfile(StartParameter startParameter) {
+        this.startParameter = startParameter;
     }
 
     public long getBuildStarted() {
@@ -64,33 +63,43 @@ public class BuildProfile {
     }
 
     /**
-     * Get a description of the tasks passed to gradle as targets from the command line
-     * @return
+     * Get a description of this profiled build. It contains info about tasks passed to gradle as targets from the command line.
      */
-    public String getTaskDescription() {
-        StringBuilder result = new StringBuilder();
-        for (String name : gradle.getStartParameter().getExcludedTaskNames()) {
-            result.append("-x");
-            result.append(name);
-            result.append(" ");
+    public String getBuildDescription() {
+        StringBuilder sb = new StringBuilder();
+        for (String name : startParameter.getExcludedTaskNames()) {
+            sb.append("-x ");
+            sb.append(name);
+            sb.append(" ");
         }
-        for (String name : gradle.getStartParameter().getTaskNames()) {
-            result.append(name);
-            result.append(" ");
+        for (String name : startParameter.getTaskNames()) {
+            sb.append(name);
+            sb.append(" ");
         }
-        return result.toString();
+        String tasks = sb.toString();
+        if (tasks.length() == 0) {
+            tasks = "(no tasks specified)";
+        }
+        return String.format("Profiled build: %s", tasks);
+    }
+
+    public boolean isSuccessful() {
+        return successful;
+    }
+
+    public void setSuccessful(boolean successful) {
+        this.successful = successful;
     }
 
     /**
      * Get the profiling container for the specified project
-     * @param project to look up
-     * @return
+     * @param projectPath to look up
      */
-    public ProjectProfile getProjectProfile(Project project) {
-        ProjectProfile result = projects.get(project);
+    public ProjectProfile getProjectProfile(String projectPath) {
+        ProjectProfile result = projects.get(projectPath);
         if (result == null) {
-            result = new ProjectProfile(project);
-            projects.put(project, result);
+            result = new ProjectProfile(projectPath);
+            projects.put(projectPath, result);
         }
         return result;
     }
@@ -100,28 +109,30 @@ public class BuildProfile {
      * @return list
      */
     public List<ProjectProfile> getProjects() {
-        return new ArrayList<ProjectProfile>(projects.values());
+        return CollectionUtils.sort(projects.values(), Operation.slowestFirst());
     }
 
     public CompositeOperation<Operation> getProjectConfiguration() {
         List<Operation> operations = new ArrayList<Operation>();
         for (ProjectProfile projectProfile : projects.values()) {
-            operations.add(projectProfile.getEvaluation());
+            operations.add(projectProfile.getConfigurationOperation());
         }
+        operations = CollectionUtils.sort(operations, Operation.slowestFirst());
         return new CompositeOperation<Operation>(operations);
     }
 
-    public DependencyResolveProfile getDependencySetProfile(ResolvableDependencies dependencySet) {
-        DependencyResolveProfile profile = dependencySets.get(dependencySet.getPath());
+    public ContinuousOperation getDependencySetProfile(String dependencySetDescription) {
+        ContinuousOperation profile = dependencySets.get(dependencySetDescription);
         if (profile == null) {
-            profile = new DependencyResolveProfile(dependencySet);
-            dependencySets.put(dependencySet.getPath(), profile);
+            profile = new ContinuousOperation(dependencySetDescription);
+            dependencySets.put(dependencySetDescription, profile);
         }
         return profile;
     }
 
-    public CompositeOperation<DependencyResolveProfile> getDependencySets() {
-        return new CompositeOperation<DependencyResolveProfile>(dependencySets.values());
+    public CompositeOperation<ContinuousOperation> getDependencySets() {
+        final List<ContinuousOperation> profiles = CollectionUtils.sort(dependencySets.values(), Operation.slowestFirst());
+        return new CompositeOperation<ContinuousOperation>(profiles);
     }
 
     /**
@@ -160,15 +171,6 @@ public class BuildProfile {
     }
 
     /**
-     * Should be set with a timestamp from a {@link org.gradle.BuildListener#projectsEvaluated}
-     * callback.
-     * @param projectsEvaluated
-     */
-    public void setProjectsEvaluated(long projectsEvaluated) {
-        this.projectsEvaluated = projectsEvaluated;
-    }
-
-    /**
      * Should be set with a timestamp from a {@link org.gradle.BuildListener#buildFinished}
      * callback.
      * @param buildFinished
@@ -211,38 +213,22 @@ public class BuildProfile {
     }
 
     /**
-     * Get the elapsed time (in mSec) between the projectsEvaluated event and the projectsLoaded event.
-     * @return
-     */
-    public long getElapsedProjectsEvaluated() {
-        return projectsEvaluated - projectsLoaded;
-    }
-
-    /**
-     * Get the elapsed time (in mSec) between the buildFinished event and the projectsEvaluated event.
-     * @return
-     */
-    public long getElapsedAfterProjectsEvaluated() {
-        return buildFinished - projectsEvaluated;
-    }
-
-    /**
      * Get the total task execution time from all projects.
      * @return
      */
     public long getElapsedTotalExecutionTime() {
         long result = 0;
         for (ProjectProfile projectProfile : projects.values()) {
-            result += projectProfile.getTasks().getElapsedTime();
+            result += projectProfile.getElapsedTime();
         }
         return result;
     }
 
-    public boolean isSuccessful() {
-        return successful;
+    public String getBuildStartedDescription() {
+        return String.format("Started on: %s", DATE_FORMAT.format(buildStarted));
     }
 
-    public void setSuccessful(boolean successful) {
-        this.successful = successful;
+    public StartParameter getStartParameter() {
+        return startParameter;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java b/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java
index 6965bd4..4fe6b75 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java
@@ -47,4 +47,8 @@ public class CompositeOperation<T extends Operation> extends Operation implement
         }
         return sum;
     }
+
+    public String getDescription() {
+        return "<composite operation>";
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java b/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java
index c0b342c..2e647e1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java
@@ -21,13 +21,25 @@ package org.gradle.profile;
 public class ContinuousOperation extends Operation {
     private long start;
     private long finish;
+    private String description;
 
-    public void setStart(long start) {
+    public ContinuousOperation(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return description;
+    }
+
+    public ContinuousOperation setStart(long start) {
         this.start = start;
+        return this;
     }
 
-    public void setFinish(long finish) {
+    public ContinuousOperation setFinish(long finish) {
         this.finish = finish;
+        return this;
     }
 
     public long getStartTime() {
@@ -37,4 +49,8 @@ public class ContinuousOperation extends Operation {
     public long getElapsedTime() {
         return finish - start;
     }
+
+    public String getDescription() {
+        return description;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/DependencyResolveProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/DependencyResolveProfile.java
deleted file mode 100644
index 8c703a5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/profile/DependencyResolveProfile.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.profile;
-
-import org.gradle.api.artifacts.ResolvableDependencies;
-
-public class DependencyResolveProfile extends ContinuousOperation {
-    private final ResolvableDependencies dependencySet;
-
-    public DependencyResolveProfile(ResolvableDependencies dependencySet) {
-        this.dependencySet = dependencySet;
-    }
-
-    public String getPath() {
-        return dependencySet.getPath();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/EvalutationOperation.java b/subprojects/core/src/main/groovy/org/gradle/profile/EvalutationOperation.java
deleted file mode 100644
index b886e07..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/profile/EvalutationOperation.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.profile;
-
-import org.gradle.api.Project;
-
-public class EvalutationOperation extends ContinuousOperation {
-    private final Project project;
-
-    public EvalutationOperation(Project project) {
-        this.project = project;
-    }
-
-    public String getPath(){
-        return project.getPath();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java b/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java
index 591b71e..453b924 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.profile;
 
+import java.util.Comparator;
+
 /**
  * A general operation.
  */
@@ -23,4 +25,23 @@ public abstract class Operation {
      * Returns the total elapsed execution time of this operation in millis.
      */
     abstract long getElapsedTime();
+
+    abstract String getDescription();
+
+    /**
+     * @return comparator that compares operations, slowest first, then alphabetically
+     */
+    public static Comparator<? super Operation> slowestFirst() {
+        return new Comparator<Operation>() {
+            public int compare(Operation o1, Operation o2) {
+                long byElapsedTime = o2.getElapsedTime() - o1.getElapsedTime();
+                if (byElapsedTime > 0) {
+                    return 1;
+                } else if (byElapsedTime < 0) {
+                    return -1;
+                }
+                return o1.getDescription().compareTo(o2.getDescription());
+            }
+        };
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
index 81228bd..f28693c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
@@ -47,8 +47,9 @@ public class ProfileEventAdapter implements BuildListener, ProjectEvaluationList
 
     // BuildListener
     public void buildStarted(Gradle gradle) {
-        buildProfile = new BuildProfile(gradle);
-        buildProfile.setBuildStarted(timeProvider.getCurrentTime());
+        long now = timeProvider.getCurrentTime();
+        buildProfile = new BuildProfile(gradle.getStartParameter());
+        buildProfile.setBuildStarted(now);
         buildProfile.setProfilingStarted(buildMetaData.getBuildTimeClock().getStartTime());
     }
 
@@ -60,9 +61,7 @@ public class ProfileEventAdapter implements BuildListener, ProjectEvaluationList
         buildProfile.setProjectsLoaded(timeProvider.getCurrentTime());
     }
 
-    public void projectsEvaluated(Gradle gradle) {
-        buildProfile.setProjectsEvaluated(timeProvider.getCurrentTime());
-    }
+    public void projectsEvaluated(Gradle gradle) {}
 
     public void buildFinished(BuildResult result) {
         buildProfile.setBuildFinished(timeProvider.getCurrentTime());
@@ -76,39 +75,42 @@ public class ProfileEventAdapter implements BuildListener, ProjectEvaluationList
 
     // ProjectEvaluationListener
     public void beforeEvaluate(Project project) {
-        buildProfile.getProjectProfile(project).getEvaluation().setStart(System.currentTimeMillis());
+        long now = timeProvider.getCurrentTime();
+        buildProfile.getProjectProfile(project.getPath()).getConfigurationOperation().setStart(now);
     }
 
     public void afterEvaluate(Project project, ProjectState state) {
-        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
-        projectProfile.getEvaluation().setFinish(timeProvider.getCurrentTime());
-        projectProfile.setState(state);
+        long now = timeProvider.getCurrentTime();
+        ProjectProfile projectProfile = buildProfile.getProjectProfile(project.getPath());
+        projectProfile.getConfigurationOperation().setFinish(now);
     }
 
     // TaskExecutionListener
     public void beforeExecute(Task task) {
+        long now = timeProvider.getCurrentTime();
         Project project = task.getProject();
-        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
-        projectProfile.getTaskProfile(task).setStart(timeProvider.getCurrentTime());
+        ProjectProfile projectProfile = buildProfile.getProjectProfile(project.getPath());
+        projectProfile.getTaskProfile(task.getPath()).setStart(now);
     }
 
     public void afterExecute(Task task, TaskState state) {
+        long now = timeProvider.getCurrentTime();
         Project project = task.getProject();
-        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
-        TaskExecution taskExecution = projectProfile.getTaskProfile(task);
-        taskExecution.setFinish(timeProvider.getCurrentTime());
-        taskExecution.setState(state);
+        ProjectProfile projectProfile = buildProfile.getProjectProfile(project.getPath());
+        TaskExecution taskExecution = projectProfile.getTaskProfile(task.getPath());
+        taskExecution.setFinish(now);
+        taskExecution.completed(state);
     }
 
     // DependencyResolutionListener
     public void beforeResolve(ResolvableDependencies dependencies) {
-        DependencyResolveProfile profile = buildProfile.getDependencySetProfile(dependencies);
-        profile.setStart(timeProvider.getCurrentTime());
+        long now = timeProvider.getCurrentTime();
+        buildProfile.getDependencySetProfile(dependencies.getPath()).setStart(now);
     }
 
     public void afterResolve(ResolvableDependencies dependencies) {
-        DependencyResolveProfile profile = buildProfile.getDependencySetProfile(dependencies);
-        profile.setFinish(timeProvider.getCurrentTime());
+        long now = timeProvider.getCurrentTime();
+        buildProfile.getDependencySetProfile(dependencies.getPath()).setFinish(now);
     }
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
index 9f5f6e4..3db502b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
@@ -20,16 +20,10 @@ import org.gradle.reporting.DurationFormatter;
 import org.gradle.reporting.HtmlReportRenderer;
 import org.gradle.reporting.ReportRenderer;
 import org.gradle.reporting.TabbedPageRenderer;
-import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.io.IOException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Comparator;
-import java.util.List;
 
-//TODO SF add coverage
 public class ProfileReportRenderer {
     public void writeTo(BuildProfile buildProfile, File file) {
         HtmlReportRenderer renderer = new HtmlReportRenderer();
@@ -42,8 +36,6 @@ public class ProfileReportRenderer {
     private static final DurationFormatter DURATION_FORMAT = new DurationFormatter();
 
     private static class ProfilePageRenderer extends TabbedPageRenderer<BuildProfile> {
-        static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd - HH:mm:ss");
-
         @Override
         protected String getTitle() {
             return "Profile report";
@@ -55,8 +47,8 @@ public class ProfileReportRenderer {
                 @Override
                 public void render(BuildProfile model, SimpleHtmlWriter htmlWriter) throws IOException {
                     htmlWriter.startElement("div").attribute("id", "header")
-                        .startElement("p").characters(String.format("Profiled with tasks: %s", model.getTaskDescription())).endElement()
-                        .startElement("p").characters(String.format("Run on: %s", DATE_FORMAT.format(model.getBuildStarted()))).endElement()
+                        .startElement("p").characters(model.getBuildDescription()).endElement()
+                        .startElement("p").characters(model.getBuildStartedDescription()).endElement()
                     .endElement();
                 }
             };
@@ -67,6 +59,8 @@ public class ProfileReportRenderer {
             return new ReportRenderer<BuildProfile, SimpleHtmlWriter>() {
                 @Override
                 public void render(BuildProfile model, SimpleHtmlWriter htmlWriter) throws IOException {
+                    CompositeOperation<Operation> profiledProjectConfiguration = model.getProjectConfiguration();
+
                     htmlWriter.startElement("div").attribute("id", "tabs")
                         .startElement("ul").attribute("class", "tabLinks")
                             .startElement("li").startElement("a").attribute("href", "#tab0").characters("Summary").endElement().endElement()
@@ -101,7 +95,7 @@ public class ProfileReportRenderer {
                                 htmlWriter.endElement();
                                 htmlWriter.startElement("tr");
                                     htmlWriter.startElement("td").characters("Configuring Projects").endElement();
-                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedAfterProjectsEvaluated())).endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(profiledProjectConfiguration.getElapsedTime())).endElement();
                                 htmlWriter.endElement();
                                 htmlWriter.startElement("tr");
                                     htmlWriter.startElement("td").characters("Task Execution").endElement();
@@ -120,18 +114,12 @@ public class ProfileReportRenderer {
                                 htmlWriter.endElement();
                                 htmlWriter.startElement("tr");
                                     htmlWriter.startElement("td").characters("All projects").endElement();
-                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getProjectConfiguration().getElapsedTime())).endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(profiledProjectConfiguration.getElapsedTime())).endElement();
                                 htmlWriter.endElement();
-                                final List<Operation> operations = CollectionUtils.sort(model.getProjectConfiguration().getOperations(), new Comparator<Operation>() {
-                                    public int compare(Operation o1, Operation o2) {
-                                        return Long.valueOf(o2.getElapsedTime()).compareTo(Long.valueOf(o1.getElapsedTime()));
-                                    }
-                                });
-                                for (Operation operation : operations) {
-                                    EvalutationOperation evalOperation = (EvalutationOperation)operation;
+                                for (Operation operation : profiledProjectConfiguration) {
                                     htmlWriter.startElement("tr");
-                                        htmlWriter.startElement("td").characters(evalOperation.getPath()).endElement();
-                                        htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(evalOperation.getElapsedTime())).endElement();
+                                        htmlWriter.startElement("td").characters(operation.getDescription()).endElement();
+                                        htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(operation.getElapsedTime())).endElement();
                                     htmlWriter.endElement();
                                 }
                             htmlWriter.endElement()
@@ -150,15 +138,10 @@ public class ProfileReportRenderer {
                                     htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getDependencySets().getElapsedTime())).endElement();
                                 htmlWriter.endElement();
 
-                                final List<DependencyResolveProfile> dependencyResolveProfiles = CollectionUtils.sort(model.getDependencySets().getOperations(), new Comparator<DependencyResolveProfile>() {
-                                        public int compare(DependencyResolveProfile p1, DependencyResolveProfile p2) {
-                                        return Long.valueOf(p2.getElapsedTime()).compareTo(Long.valueOf(p1.getElapsedTime()));
-                                    }
-                                    });
-                                for (DependencyResolveProfile profile : dependencyResolveProfiles) {
+                                for (Operation operation : model.getDependencySets()) {
                                     htmlWriter.startElement("tr");
-                                        htmlWriter.startElement("td").characters(profile.getPath()).endElement();
-                                        htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(profile.getElapsedTime())).endElement();
+                                        htmlWriter.startElement("td").characters(operation.getDescription()).endElement();
+                                        htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(operation.getElapsedTime())).endElement();
                                     htmlWriter.endElement();
                                 }
                             htmlWriter.endElement()
@@ -173,27 +156,17 @@ public class ProfileReportRenderer {
                                         .startElement("th").characters("Result").endElement()
                                     .endElement()
                                 .endElement();
-                                final List<ProjectProfile> projects = CollectionUtils.sort(model.getProjects(), new Comparator<ProjectProfile>() {
-                                        public int compare(ProjectProfile p1, ProjectProfile p2) {
-                                        return Long.valueOf(p2.getTasks().getElapsedTime()).compareTo(p1.getTasks().getElapsedTime());
-                                    }
-                                });
-                                for (ProjectProfile project : projects) {
+                                for (ProjectProfile project : model.getProjects()) {
                                    htmlWriter.startElement("tr")
                                         .startElement("td").characters(project.getPath()).endElement()
-                                        .startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(project.getTasks().getElapsedTime())).endElement()
+                                        .startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(project.getElapsedTime())).endElement()
                                         .startElement("td").characters("(total)").endElement()
                                     .endElement();
-                                    final List<TaskExecution> taskExecutions = CollectionUtils.sort(project.getTasks().getOperations(), new Comparator<TaskExecution>() {
-                                        public int compare(TaskExecution p1, TaskExecution p2) {
-                                            return Long.valueOf(p2.getElapsedTime()).compareTo(Long.valueOf(p1.getElapsedTime()));
-                                        }
-                                    });
-                                    for (TaskExecution taskExecution : taskExecutions) {
+                                    for (TaskExecution taskExecution : project.getTasks()) {
                                         htmlWriter.startElement("tr")
                                             .startElement("td").attribute("class", "indentPath").characters(taskExecution.getPath()).endElement()
                                             .startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(taskExecution.getElapsedTime())).endElement()
-                                            .startElement("td").characters(taskExecution.getState().getSkipped() ? taskExecution.getState().getSkipMessage() : (taskExecution.getState().getDidWork()) ? "" : "Did No Work").endElement()
+                                            .startElement("td").characters(taskExecution.getStatus()).endElement()
                                         .endElement();
                                     }
                                 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
index e7abbe5..d0d2852 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
@@ -15,31 +15,29 @@
  */
 package org.gradle.profile;
 
-import org.gradle.api.Project;
-import org.gradle.api.ProjectState;
-import org.gradle.api.Task;
+import org.gradle.util.CollectionUtils;
 
 import java.util.HashMap;
+import java.util.List;
 
-public class ProjectProfile {
-    private final Project project;
-    private ProjectState state;
-    private HashMap<Task, TaskExecution> tasks = new HashMap<Task, TaskExecution>();
-    private final ContinuousOperation evaluation;
+public class ProjectProfile extends Operation {
+    private HashMap<String, TaskExecution> tasks = new HashMap<String, TaskExecution>();
+    private final ContinuousOperation configurationOperation;
+    private String projectPath;
 
-    public ProjectProfile(Project project) {
-        this.project = project;
-        this.evaluation = new EvalutationOperation(project);
+    public ProjectProfile(String projectPath) {
+        this.projectPath = projectPath;
+        this.configurationOperation = new ContinuousOperation(projectPath);
     }
 
     /**
      * Gets the task profiling container for the specified task.
      */
-    public TaskExecution getTaskProfile(Task task) {
-        TaskExecution result = tasks.get(task);
+    public TaskExecution getTaskProfile(String taskPath) {
+        TaskExecution result = tasks.get(taskPath);
         if (result == null) {
-            result = new TaskExecution(task);
-            tasks.put(task, result);
+            result = new TaskExecution(taskPath);
+            tasks.put(taskPath, result);
         }
         return result;
     }
@@ -48,31 +46,33 @@ public class ProjectProfile {
      * Returns the task executions for this project.
      */
     public CompositeOperation<TaskExecution> getTasks() {
-        return new CompositeOperation<TaskExecution>(tasks.values());
+        List<TaskExecution> taskExecutions = CollectionUtils.sort(tasks.values(), Operation.slowestFirst());
+        return new CompositeOperation<TaskExecution>(taskExecutions);
     }
 
     /**
      * Get the String project path.
      */
     public String getPath() {
-        return project.getPath();
+        return projectPath;
     }
 
     /**
-     * Returns the evaluation time of this project.
+     * Returns the configuration time of this project.
      */
-    public ContinuousOperation getEvaluation() {
-        return evaluation;
+    public ContinuousOperation getConfigurationOperation() {
+        return configurationOperation;
     }
 
-    /**
-     * Gets the state of the project after evaluation finishes.
-     */
-    public ProjectState getState() {
-        return state;
+    public String toString() {
+        return projectPath;
+    }
+
+    public String getDescription() {
+        return projectPath;
     }
 
-    public void setState(ProjectState state) {
-        this.state = state;
+    long getElapsedTime() {
+        return getTasks().getElapsedTime();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java b/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java
index e322a82..2596879 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java
@@ -15,34 +15,41 @@
  */
 package org.gradle.profile;
 
-import org.gradle.api.Task;
 import org.gradle.api.tasks.TaskState;
 
 /**
  * Container for task profiling information.
- * This includes timestamps around task execution and the resulting TaskState.
+ * This includes timestamps around task execution and the resulting task status.
  */
 public class TaskExecution extends ContinuousOperation {
-    private final Task task;
+
+    final static String NO_WORK_MESSAGE = "Did No Work";
+
+    private final String path;
     private TaskState state;
 
-    public TaskExecution(Task task) {
-        this.task = task;
+    public TaskExecution(String taskPath) {
+        super(taskPath);
+        this.path = taskPath;
     }
 
     /**
      * Gets the string task path.
-     * @return
      */
     public String getPath() {
-        return task.getPath();
+        return path;
+    }
+
+    public String getStatus() {
+        return state.getSkipped() ? state.getSkipMessage() : (state.getDidWork()) ? "" : NO_WORK_MESSAGE;
     }
 
     public TaskState getState() {
         return state;
     }
 
-    public void setState(TaskState state) {
+    public TaskExecution completed(TaskState state) {
         this.state = state;
+        return this;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
index d55c587..ef3624f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
@@ -56,8 +56,10 @@ public class HtmlReportRenderer {
                 super.writeTo(model, file);
                 for (URL resource : resources) {
                     String name = StringUtils.substringAfterLast(resource.getPath(), "/");
-                    File destFile = new File(file.getParentFile(), name);
+                    String type = StringUtils.substringAfterLast(resource.getPath(), ".");
+                    File destFile = new File(file.getParentFile(), String.format("%s/%s", type, name));
                     if (!destFile.exists()) {
+                        destFile.getParentFile().mkdirs();
                         GFileUtils.copyURLToFile(resource, destFile);
                     }
                 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java
index 328acff..d3142c8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java
@@ -20,7 +20,7 @@ import java.io.IOException;
 
 public abstract class ReportRenderer<T, E> {
     /**
-     * Renders the report for the given model as children of the given DOM element.
+     * Renders the report for the given model to the given output.
      */
-    public abstract void render(T model, E parent) throws IOException;
+    public abstract void render(T model, E output) throws IOException;
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
index 43c638c..2260eb8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
@@ -31,9 +31,9 @@ public abstract class TabbedPageRenderer<T> extends ReportRenderer<T, SimpleHtml
 
     protected abstract String getTitle();
 
-    protected abstract  ReportRenderer<T, SimpleHtmlWriter> getHeaderRenderer();
+    protected abstract ReportRenderer<T, SimpleHtmlWriter> getHeaderRenderer();
 
-    protected abstract  ReportRenderer<T, SimpleHtmlWriter> getContentRenderer();
+    protected abstract ReportRenderer<T, SimpleHtmlWriter> getContentRenderer();
 
     protected String getPageTitle() {
         return getTitle();
@@ -43,11 +43,11 @@ public abstract class TabbedPageRenderer<T> extends ReportRenderer<T, SimpleHtml
     public void render(final T model, SimpleHtmlWriter htmlWriter) throws IOException {
         this.model = model;
         htmlWriter.startElement("head")
-            .startElement("meta").attribute("httpEquiv", "Content-Type").attribute("content", "text/html; charset=utf-8").endElement()
+            .startElement("meta").attribute("http-equiv", "Content-Type").attribute("content", "text/html; charset=utf-8").endElement()
             .startElement("title").characters(getPageTitle()).endElement()
-            .startElement("link").attribute("href", "base-style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
-            .startElement("link").attribute("href", "style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
-            .startElement("script").attribute("src", "report.js").attribute("type", "text/javascript").characters("").endElement() //html does not like <a name="..."/>
+            .startElement("link").attribute("href", "css/base-style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
+            .startElement("link").attribute("href", "css/style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
+            .startElement("script").attribute("src", "js/report.js").attribute("type", "text/javascript").characters("").endElement() //html does not like <a name="..."/>
         .endElement();
 
         htmlWriter.startElement("body")
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 6832c1a..ffbe215 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.reporting;
 
-import org.gradle.api.internal.ErroringAction;
-import org.gradle.api.internal.IoActions;
+import org.gradle.internal.ErroringAction;
+import org.gradle.internal.IoActions;
 
 import java.io.File;
 import java.io.Writer;
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
deleted file mode 100644
index 168bc85..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
+++ /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.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.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.logging.internal.DefaultProgressLoggerFactory;
-import org.gradle.logging.internal.DefaultStyledTextOutputFactory;
-import org.gradle.logging.internal.OutputEventListener;
-import org.gradle.logging.internal.ProgressListener;
-
-public class GlobalTestServices extends GlobalServicesRegistry {
-    public GlobalTestServices() {
-        super(new TestLoggingServices());
-    }
-
-    private static class TestLoggingServices extends DefaultServiceRegistry {
-        final ListenerManager listenerManager = new DefaultListenerManager();
-
-        protected ProgressLoggerFactory createProgressLoggerFactory() {
-            return new DefaultProgressLoggerFactory(listenerManager.getBroadcaster(ProgressListener.class), new TrueTimeProvider());
-        }
-
-        protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
-            return new Factory<LoggingManagerInternal>() {
-                public LoggingManagerInternal create() {
-                    return new NoOpLoggingManager();
-                }
-            };
-        }
-
-        protected StyledTextOutputFactory createStyledTextOutputFactory() {
-            return new DefaultStyledTextOutputFactory(listenerManager.getBroadcaster(OutputEventListener.class), new TrueTimeProvider());
-        }
-
-        protected TestOutputEventListener createStubOutputEventListener() {
-            return new TestOutputEventListener();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
index 3f802b2..da0d2ee 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
@@ -17,11 +17,10 @@ package org.gradle.testfixtures.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.api.internal.changedetection.InMemoryIndexedCache;
 import org.gradle.cache.*;
-import org.gradle.cache.internal.*;
+import org.gradle.cache.internal.CacheFactory;
+import org.gradle.cache.internal.filelock.LockOptions;
 import org.gradle.internal.Factory;
-import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GFileUtils;
 
@@ -30,11 +29,11 @@ import java.util.Collections;
 import java.util.Map;
 
 public class InMemoryCacheFactory implements CacheFactory {
-    public PersistentCache openStore(File storeDir, String displayName, FileLockManager.LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException {
-        return open(storeDir, displayName, CacheUsage.ON, null, Collections.<String, Object>emptyMap(), lockMode, initializer);
+    public PersistentCache openStore(File storeDir, String displayName, LockOptions lockOptions, Action<? super PersistentCache> initializer) throws CacheOpenException {
+        return open(storeDir, displayName, CacheUsage.ON, null, Collections.<String, Object>emptyMap(), lockOptions, initializer);
     }
 
-    public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Action<? super PersistentCache> initializer) {
+    public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockOptions lockOptions, Action<? super PersistentCache> initializer) {
         GFileUtils.mkdirs(cacheDir);
         InMemoryCache cache = new InMemoryCache(cacheDir);
         if (initializer != null) {
@@ -43,32 +42,10 @@ public class InMemoryCacheFactory implements CacheFactory {
         return cache;
     }
 
-    public <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Serializer<V> serializer) {
+    public <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, LockOptions lockOptions, Serializer<V> serializer) {
         return new InMemoryIndexedCache<K, V>(serializer);
     }
 
-    public <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Serializer<E> serializer) {
-        GFileUtils.mkdirs(cacheDir);
-        return new SimpleStateCache<E>(new File(cacheDir, "state.bin"), new NoOpFileLock(), new DefaultSerializer<E>());
-    }
-
-    private static class NoOpFileLock extends AbstractFileAccess {
-        public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException {
-            return action.create();
-        }
-
-        public void updateFile(Runnable action) throws LockTimeoutException {
-            action.run();
-        }
-
-        public void writeFile(Runnable action) throws LockTimeoutException {
-            action.run();
-        }
-
-        public void close() {
-        }
-    }
-
     private static class InMemoryCache implements PersistentCache {
         private final File cacheDir;
 
@@ -76,20 +53,19 @@ public class InMemoryCacheFactory implements CacheFactory {
             this.cacheDir = cacheDir;
         }
 
-        public File getBaseDir() {
-            return cacheDir;
+        public void close() {
         }
 
-        public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
-            return new InMemoryIndexedCache<K, V>(new DefaultSerializer<V>());
+        public File getBaseDir() {
+            return cacheDir;
         }
 
-        public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
+        public <K, V> PersistentIndexedCache<K, V> createCache(String name, Class<K> keyType, Serializer<V> valueSerializer) {
             return new InMemoryIndexedCache<K, V>(valueSerializer);
         }
 
-        public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
-            return new InMemoryIndexedCache<K, V>(valueSerializer);
+        public <K, V> PersistentIndexedCache<K, V> createCache(PersistentIndexedCacheParameters<K, V> parameters) {
+            return new InMemoryIndexedCache<K, V>(parameters.getValueSerializer());
         }
 
         public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryIndexedCache.java
new file mode 100644
index 0000000..cdea768
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryIndexedCache.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.testfixtures.internal;
+
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.serialize.InputStreamBackedDecoder;
+import org.gradle.messaging.serialize.OutputStreamBackedEncoder;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple in-memory cache, used by the testing fixtures.
+ */
+public class InMemoryIndexedCache<K, V> implements PersistentIndexedCache<K, V> {
+    private final Map<Object, byte[]> entries = new HashMap<Object, byte[]>();
+    private final Serializer<V> valueSerializer;
+
+    public InMemoryIndexedCache(Serializer<V> valueSerializer) {
+        this.valueSerializer = valueSerializer;
+    }
+
+    public V get(K key) {
+        byte[] serialised = entries.get(key);
+        if (serialised == null) {
+            return null;
+        }
+        try {
+            ByteArrayInputStream instr = new ByteArrayInputStream(serialised);
+            InputStreamBackedDecoder decoder = new InputStreamBackedDecoder(instr);
+            return valueSerializer.read(decoder);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void put(K key, V value) {
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+        OutputStreamBackedEncoder encoder = new OutputStreamBackedEncoder(outstr);
+        try {
+            valueSerializer.write(encoder, value);
+            encoder.flush();
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        entries.put(key, outstr.toByteArray());
+    }
+
+    public void remove(K key) {
+        entries.remove(key);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
index 51a8bb2..047c0af 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
@@ -20,25 +20,32 @@ import org.gradle.StartParameter;
 import org.gradle.api.Project;
 import org.gradle.api.internal.AsmBackedClassGenerator;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.internal.project.IProjectFactory;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.initialization.DefaultProjectDescriptor;
 import org.gradle.initialization.DefaultProjectDescriptorRegistry;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistryBuilder;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.invocation.DefaultGradle;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
 
-/**
- * by Szczepan Faber, created at: 10/1/11
- */
 public class ProjectBuilderImpl {
-    private static final GlobalTestServices GLOBAL_SERVICES = new GlobalTestServices();
+    private static final ServiceRegistry GLOBAL_SERVICES = ServiceRegistryBuilder
+            .builder()
+            .displayName("global services")
+            .parent(new TestGlobalScopeServices.TestLoggingServices())
+            .parent(NativeServices.getInstance())
+            .provider(new TestGlobalScopeServices())
+            .build();
     private static final AsmBackedClassGenerator CLASS_GENERATOR = new AsmBackedClassGenerator();
 
     public Project createChildProject(String name, Project parent, File projectDir) {
@@ -50,7 +57,8 @@ public class ProjectBuilderImpl {
                 (projectDir != null) ? projectDir.getAbsoluteFile() : new File(parentProject.getProjectDir(), name),
                 new StringScriptSource("test build file", null),
                 parentProject.getGradle(),
-                parentProject.getGradle().getServices()
+                parentProject.getGradle().getServiceRegistryFactory(),
+                parentProject.getClassLoaderScope().createChild()
         );
         parentProject.addChildProject(project);
         parentProject.getProjectRegistry().addProject(project);
@@ -65,17 +73,16 @@ public class ProjectBuilderImpl {
         StartParameter startParameter = new StartParameter();
         startParameter.setGradleUserHomeDir(new File(projectDir, "userHome"));
 
-        ServiceRegistryFactory topLevelRegistry = new TestTopLevelBuildServiceRegistry(GLOBAL_SERVICES, startParameter, homeDir);
-        GradleInternal gradle = new DefaultGradle(null, startParameter, topLevelRegistry);
+        ServiceRegistry topLevelRegistry = new TestBuildScopeServices(GLOBAL_SERVICES, startParameter, homeDir);
+        GradleInternal gradle = new DefaultGradle(null, startParameter, topLevelRegistry.get(ServiceRegistryFactory.class));
 
-        DefaultProjectDescriptor projectDescriptor = new DefaultProjectDescriptor(null, name, projectDir, new DefaultProjectDescriptorRegistry());
-        ProjectInternal project = topLevelRegistry.get(IProjectFactory.class).createProject(projectDescriptor, null, gradle);
+        DefaultProjectDescriptor projectDescriptor = new DefaultProjectDescriptor(null, name, projectDir, new DefaultProjectDescriptorRegistry(),
+                topLevelRegistry.get(FileResolver.class));
+        ProjectInternal project = topLevelRegistry.get(IProjectFactory.class).createProject(projectDescriptor, null, gradle, gradle.getClassLoaderScope().createSibling());
 
         gradle.setRootProject(project);
         gradle.setDefaultProject(project);
 
-        gradle.getScriptClassLoader().addParent(getClass().getClassLoader());
-
         return project;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestBuildScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestBuildScopeServices.java
new file mode 100644
index 0000000..f66de1f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestBuildScopeServices.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.testfixtures.internal;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.BuildScopeServices;
+
+import java.io.File;
+
+public class TestBuildScopeServices extends BuildScopeServices {
+    private final File homeDir;
+
+    public TestBuildScopeServices(ServiceRegistry parent, StartParameter startParameter, File homeDir) {
+        super(parent, startParameter);
+        this.homeDir = homeDir;
+    }
+
+    protected BuildClientMetaData createClientMetaData() {
+        return new GradleLauncherMetaData();
+    }
+
+    protected GradleDistributionLocator createGradleDistributionLocator() {
+        return new GradleDistributionLocator() {
+            public File getGradleHome() {
+                return homeDir;
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestGlobalScopeServices.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestGlobalScopeServices.java
new file mode 100644
index 0000000..bb7f288
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestGlobalScopeServices.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.testfixtures.internal;
+
+import org.gradle.cache.internal.CacheFactory;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.internal.Factory;
+import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.scopes.GlobalScopeServices;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.logging.internal.DefaultProgressLoggerFactory;
+import org.gradle.logging.internal.DefaultStyledTextOutputFactory;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.logging.internal.ProgressListener;
+
+public class TestGlobalScopeServices extends GlobalScopeServices {
+    public TestGlobalScopeServices() {
+        super(false);
+    }
+
+    @Override
+    protected CacheFactory createCacheFactory(FileLockManager fileLockManager) {
+        return new InMemoryCacheFactory();
+    }
+
+    public static class TestLoggingServices extends DefaultServiceRegistry {
+        final ListenerManager listenerManager = new DefaultListenerManager();
+
+        protected ProgressLoggerFactory createProgressLoggerFactory() {
+            return new DefaultProgressLoggerFactory(listenerManager.getBroadcaster(ProgressListener.class), new TrueTimeProvider());
+        }
+
+        protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
+            return new Factory<LoggingManagerInternal>() {
+                public LoggingManagerInternal create() {
+                    return new NoOpLoggingManager();
+                }
+            };
+        }
+
+        protected StyledTextOutputFactory createStyledTextOutputFactory() {
+            return new DefaultStyledTextOutputFactory(listenerManager.getBroadcaster(OutputEventListener.class), new TrueTimeProvider());
+        }
+
+        protected TestOutputEventListener createStubOutputEventListener() {
+            return new TestOutputEventListener();
+        }
+    }
+}
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
deleted file mode 100644
index 09bc272..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestTopLevelBuildServiceRegistry.java
+++ /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.testfixtures.internal;
-
-import org.gradle.StartParameter;
-import org.gradle.api.internal.GradleDistributionLocator;
-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;
-
-public class TestTopLevelBuildServiceRegistry extends TopLevelBuildServiceRegistry {
-    private final File homeDir;
-
-    public TestTopLevelBuildServiceRegistry(ServiceRegistry parent, StartParameter startParameter, File homeDir) {
-        super(parent, startParameter);
-        this.homeDir = homeDir;
-    }
-
-    protected BuildClientMetaData createClientMetaData() {
-        return new GradleLauncherMetaData();
-    }
-
-    @Override
-    protected CacheFactory createCacheFactory() {
-        return new InMemoryCacheFactory();
-    }
-
-    protected GradleDistributionLocator createGradleDistributionLocator() {
-        return new GradleDistributionLocator() {
-            public File getGradleHome() {
-                return homeDir;
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/ToolingModelBuilder.java b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/ToolingModelBuilder.java
new file mode 100644
index 0000000..2b394b5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/ToolingModelBuilder.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.provider.model;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Project;
+
+/**
+ * Responsible for building tooling models.
+ */
+ at Incubating
+public interface ToolingModelBuilder {
+    boolean canBuild(String modelName);
+    Object buildAll(String modelName, Project project);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/ToolingModelBuilderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/ToolingModelBuilderRegistry.java
new file mode 100644
index 0000000..5757d23
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/ToolingModelBuilderRegistry.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.provider.model;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A registry of tooling model builders. Adding a builder to this registry makes a model (or models) available via the tooling API.
+ */
+ at Incubating
+public interface ToolingModelBuilderRegistry {
+    void register(ToolingModelBuilder builder);
+
+    ToolingModelBuilder getBuilder(String modelName) throws UnknownModelException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/UnknownModelException.java b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/UnknownModelException.java
new file mode 100644
index 0000000..f66d460
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/UnknownModelException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.provider.model;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Incubating;
+
+/**
+ * Thrown when an unknown tooling model is requested.
+ */
+ at Incubating
+public class UnknownModelException extends GradleException {
+    public UnknownModelException(String message) {
+        super(message);
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java
new file mode 100644
index 0000000..2837773
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.provider.model.internal;
+
+import org.gradle.api.Project;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+import org.gradle.tooling.provider.model.UnknownModelException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultToolingModelBuilderRegistry implements ToolingModelBuilderRegistry {
+    private final List<ToolingModelBuilder> builders = new ArrayList<ToolingModelBuilder>();
+
+    public DefaultToolingModelBuilderRegistry() {
+        register(new VoidToolingModelBuilder());
+    }
+
+    public void register(ToolingModelBuilder builder) {
+        builders.add(builder);
+    }
+
+    public ToolingModelBuilder getBuilder(String modelName) throws UnsupportedOperationException {
+        ToolingModelBuilder match = null;
+        for (ToolingModelBuilder builder : builders) {
+            if (builder.canBuild(modelName)) {
+                if (match != null) {
+                    throw new UnsupportedOperationException(String.format("Multiple builders are available to build a model of type '%s'.", modelName));
+                }
+                match = builder;
+            }
+        }
+        if (match != null) {
+            return match;
+        }
+
+        throw new UnknownModelException(String.format("No builders are available to build a model of type '%s'.", modelName));
+    }
+
+    private static class VoidToolingModelBuilder implements ToolingModelBuilder {
+        public boolean canBuild(String modelName) {
+            return modelName.equals(Void.class.getName());
+        }
+
+        public Object buildAll(String modelName, Project project) {
+            return null;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/internal/LegacyConsumerInterface.java b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/internal/LegacyConsumerInterface.java
new file mode 100644
index 0000000..9aeb05e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/internal/LegacyConsumerInterface.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.provider.model.internal;
+
+import java.lang.annotation.*;
+
+/**
+ * Indicates that a given marker interface should be mixed in to instances of the annotated type before they
+ * are passed to the tooling API client.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+public @interface LegacyConsumerInterface {
+    String value();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/package-info.java b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/package-info.java
new file mode 100644
index 0000000..ae44366
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/tooling/provider/model/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Interfaces and classes that allow tooling models to be made available to the tooling API client.
+ */
+package org.gradle.tooling.provider.model;
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/AntUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/AntUtil.java
index fc621f3..34fa854 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/AntUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/AntUtil.java
@@ -15,12 +15,11 @@
  */
 package org.gradle.util;
 
-import org.apache.tools.ant.*;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectHelper;
+import org.apache.tools.ant.Task;
 import org.gradle.api.internal.project.ant.AntLoggingAdapter;
 
-/**
- * @author Hans Dockter
- */
 public class AntUtil {
     /**
      * @return Factory method to create new Project instances
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 fd7efdd..677f86f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
@@ -31,8 +31,6 @@ import java.util.concurrent.locks.ReentrantLock;
  *
  * <em>Note:</em> If possible, it's preferable to let the party creating the server socket select the port (e.g. with <tt>new ServerSocket(0)</tt>) and then query it for the port chosen. With this
  * class, there is always a risk that someone else grabs the port between the time it is returned from <tt>getNextAvailable()</tt> and the time the socket is created.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  * @see <a href="http://www.iana.org/assignments/port-numbers">IANA.org</a>
  */
 @ThreadSafe
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java b/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
index dc6cc76..75bca93 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
@@ -15,9 +15,7 @@
  */
 package org.gradle.util;
 
-import org.gradle.internal.TimeProvider;
-
-public class BuildCommencedTimeProvider implements TimeProvider {
+public class BuildCommencedTimeProvider {
     private final long fixedTime = System.currentTimeMillis();
 
     public long getCurrentTime() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderBackedClasspathSource.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderBackedClasspathSource.java
deleted file mode 100644
index a83a233..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderBackedClasspathSource.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.util;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Arrays;
-import java.util.Collection;
-
-public class ClassLoaderBackedClasspathSource implements ClasspathSource {
-    private final ClassLoader classLoader;
-
-    public ClassLoaderBackedClasspathSource(ClassLoader classLoader) {
-        this.classLoader = classLoader;
-    }
-
-    public void collectClasspath(Collection<? super URL> classpath) {
-        ClassLoader stopAt = ClassLoader.getSystemClassLoader() == null ? null : ClassLoader.getSystemClassLoader().getParent();
-        for (ClassLoader cl = classLoader; cl != null && cl != stopAt; cl = cl.getParent()) {
-            if (cl instanceof ClasspathSource) {
-                ClasspathSource classpathSource = (ClasspathSource) cl;
-                classpathSource.collectClasspath(classpath);
-                break;
-            }
-            if (cl instanceof URLClassLoader) {
-                classpath.addAll(Arrays.asList(((URLClassLoader) cl).getURLs()));
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java
deleted file mode 100644
index 084dc82..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.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.util;
-
-import org.gradle.internal.classpath.ClassPath;
-
-import java.net.URI;
-
-public interface ClassLoaderFactory {
-    /**
-     * Creates a ClassLoader implementation which has only the classes from the specified URIs and the Java API visible.
-     */
-    ClassLoader createIsolatedClassLoader(ClassPath classPath);
-
-    /**
-     * Creates a ClassLoader implementation which has only the classes from the specified URIs and the Java API visible.
-     */
-    ClassLoader createIsolatedClassLoader(Iterable<URI> uris);
-
-    /**
-     * Creates a ClassLoader implementation which has, by default, only the classes from the Java API visible, but which can allow access
-     * to selected classes from the given parent ClassLoader.
-     *
-     * @param parent the parent ClassLoader
-     * @return The ClassLoader
-     */
-    FilteringClassLoader createFilteringClassLoader(ClassLoader parent);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathSource.java b/subprojects/core/src/main/groovy/org/gradle/util/ClasspathSource.java
deleted file mode 100644
index c47980f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathSource.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.util;
-
-import java.net.URL;
-import java.util.Collection;
-
-public interface ClasspathSource {
-    void collectClasspath(Collection<? super URL> classpath);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java
deleted file mode 100644
index a80bec4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.GradleException;
-import org.gradle.internal.UncheckedException;
-
-import java.io.File;
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class ClasspathUtil {
-    public static void addUrl(URLClassLoader classLoader, Iterable<URL> classpathElements) {
-        try {
-            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
-            method.setAccessible(true);
-            for (URL classpathElement : classpathElements) {
-                method.invoke(classLoader, classpathElement);
-            }
-        } catch (Throwable t) {
-            throw new RuntimeException("Error, could not add URL to classloader", t);
-        }
-    }
-
-    public static List<URL> getClasspath(ClassLoader classLoader) {
-        List<URL> implementationClassPath = new ArrayList<URL>();
-        new ClassLoaderBackedClasspathSource(classLoader).collectClasspath(implementationClassPath);
-        return implementationClassPath;
-    }
-
-    public static File getClasspathForClass(Class<?> targetClass) {
-        URI location;
-        try {
-            location = targetClass.getProtectionDomain().getCodeSource().getLocation().toURI();
-        } catch (URISyntaxException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-        if (!location.getScheme().equals("file")) {
-            throw new GradleException(String.format("Cannot determine classpath for %s from codebase '%s'.", targetClass.getName(), location));
-        }
-        return new File(location.getPath());
-    }
-
-    public static File getClasspathForResource(ClassLoader classLoader, String name) {
-        if (classLoader == null) {
-            return getClasspathForResource(ClassLoader.getSystemResource(name), name);
-        } else {
-            return getClasspathForResource(classLoader.getResource(name), name);
-        }
-    }
-
-    public static File getClasspathForResource(URL resource, String name) {
-        URI location;
-        try {
-            location = resource.toURI();
-            String path = location.getPath();
-            if (location.getScheme().equals("file")) {
-                assert path.endsWith("/" + name);
-                return new File(path.substring(0, path.length() - (name.length() + 1)));
-            } else if (location.getScheme().equals("jar")) {
-                String schemeSpecificPart = location.getRawSchemeSpecificPart();
-                int pos = schemeSpecificPart.indexOf("!");
-                if (pos > 0) {
-                    assert schemeSpecificPart.substring(pos + 1).equals("/" + name);
-                    URI jarFile = new URI(schemeSpecificPart.substring(0, pos));
-                    if (jarFile.getScheme().equals("file")) {
-                        return new File(jarFile.getPath());
-                    }
-                }
-            }
-        } catch (URISyntaxException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-        throw new GradleException(String.format("Cannot determine classpath for resource '%s' from location '%s'.", name, location));
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Clock.java b/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
index 317cff3..adf17dd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
@@ -19,9 +19,6 @@ package org.gradle.util;
 import org.gradle.internal.TimeProvider;
 import org.gradle.internal.TrueTimeProvider;
 
-/**
- * @author Hans Dockter
- */
 public class Clock {
     private long start;
     private TimeProvider timeProvider;
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 f70c538..482fd06 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
@@ -27,9 +27,6 @@ import java.util.Map;
 
 import static org.gradle.util.CollectionUtils.toStringList;
 
-/**
- * @author Hans Dockter
- */
 public class ConfigureUtil {
 
     public static <T> T configureByMap(Map<?, ?> properties, T delegate) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
deleted file mode 100644
index 684e616..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
+++ /dev/null
@@ -1,86 +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.internal.UncheckedException;
-import org.gradle.internal.classpath.ClassPath;
-import org.gradle.internal.service.ServiceLocator;
-
-import javax.xml.datatype.DatatypeFactory;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.SAXParserFactory;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Collection;
-
-public class DefaultClassLoaderFactory implements ClassLoaderFactory {
-    public ClassLoader createIsolatedClassLoader(Iterable<URI> uris) {
-        return doCreateIsolatedClassLoader(GFileUtils.urisToUrls(uris));
-    }
-
-    public ClassLoader createIsolatedClassLoader(ClassPath classPath) {
-        return doCreateIsolatedClassLoader(classPath.getAsURLs());
-    }
-
-    private ClassLoader doCreateIsolatedClassLoader(Collection<URL> classpath) {
-        // This piece of ugliness copies the JAXP (ie XML API) provider, if any, from the system ClassLoader. Here's why:
-        //
-        // 1. When looking for a provider, JAXP looks for a service resource in the context ClassLoader, which is our isolated ClassLoader. If our classpath above does not contain a
-        //    provider, this returns null. If it does contain a provider, JAXP extracts the classname from the service resource.
-        // 2. If not found, JAXP looks for a service resource in the system ClassLoader. This happens to include all the application classes specified on the classpath. If the application
-        //    classpath does not contain a provider, this returns null. If it does contain a provider, JAXP extracts the implementation classname from the service resource.
-        // 3. If not found, JAXP uses a default classname
-        // 4. JAXP attempts to load the provider using the context ClassLoader. which is our isolated ClassLoader. This is fine if the classname came from step 1 or 3. It blows up if the
-        //    classname came from step 2.
-        //
-        // So, as a workaround, locate and include the JAXP provider jar in the classpath for our isolated ClassLoader.
-        //
-        // Note that in practise, this is only triggered when running in our tests
-
-        if (needJaxpImpl()) {
-            try {
-                classpath.add(ClasspathUtil.getClasspathForResource(ClassLoader.getSystemClassLoader(), "META-INF/services/javax.xml.parsers.SAXParserFactory").toURI().toURL());
-            } catch (MalformedURLException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-        }
-
-        return new URLClassLoader(classpath.toArray(new URL[classpath.size()]), ClassLoader.getSystemClassLoader().getParent());
-    }
-
-    public FilteringClassLoader createFilteringClassLoader(ClassLoader parent) {
-        // See the comment for {@link #createIsolatedClassLoader} above
-        FilteringClassLoader classLoader = new FilteringClassLoader(parent);
-        if (needJaxpImpl()) {
-            ServiceLocator locator = new ServiceLocator(ClassLoader.getSystemClassLoader());
-            makeServiceVisible(locator, classLoader, SAXParserFactory.class);
-            makeServiceVisible(locator, classLoader, DocumentBuilderFactory.class);
-            makeServiceVisible(locator, classLoader, DatatypeFactory.class);
-        }
-        return classLoader;
-    }
-
-    private void makeServiceVisible(ServiceLocator locator, FilteringClassLoader classLoader, Class<?> serviceType) {
-        classLoader.allowClass(locator.getFactory(serviceType).getImplementationClass());
-        classLoader.allowResource("META-INF/services/" + serviceType.getName());
-    }
-
-    private boolean needJaxpImpl() {
-        return ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory") != null;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DeleteOnExit.java b/subprojects/core/src/main/groovy/org/gradle/util/DeleteOnExit.java
deleted file mode 100644
index a16ac23..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/DeleteOnExit.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util;
-
-import org.apache.commons.io.FileUtils;
-
-import java.util.ArrayList;
-import java.io.File;
-
-/**
- * Provides a mechanism to delete files or whole directories on shutdown.
- * File.deleteOnExit won't work on subdirectories that are not empty.
- * There are some temporary files which are not currently well managed
- * but we want to make sure that they are eventually removed
- * @author Steve Appling
- */
-public class DeleteOnExit {
-    private static final ArrayList<File> FILES = new ArrayList<File>();
-
-    static {
-        Runtime.getRuntime().addShutdownHook(new DeleteOnExitThread());
-    }
-
-    public static void addFile(File file) {
-        synchronized (FILES) {
-            FILES.add(file);
-        }
-    }
-
-    private static class DeleteOnExitThread extends Thread {
-        public void run() {
-            synchronized (FILES) {
-                for (File file : FILES) {
-                    FileUtils.deleteQuietly(file);
-                }
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java b/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java
index 140195a..8cf57ad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java
@@ -21,8 +21,8 @@ import java.net.URI;
 import java.net.URISyntaxException;
 
 public class DistributionLocator {
-    private static final String RELEASE_REPOSITORY = "http://services.gradle.org/distributions";
-    private static final String SNAPSHOT_REPOSITORY = "http://services.gradle.org/distributions-snapshots";
+    private static final String RELEASE_REPOSITORY = "https://services.gradle.org/distributions";
+    private static final String SNAPSHOT_REPOSITORY = "https://services.gradle.org/distributions-snapshots";
 
     public URI getDistributionFor(GradleVersion version) {
         return getDistribution(getDistributionRepository(version), version, "gradle", "bin");
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
deleted file mode 100644
index ab2b12a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
+++ /dev/null
@@ -1,212 +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 java.io.IOException;
-import java.net.URL;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.*;
-
-/**
- * A ClassLoader which hides all non-system classes, packages and resources. Allows certain non-system packages and classes to be declared as visible. By default, only the Java system classes,
- * packages and resources are visible.
- */
-public class FilteringClassLoader extends ClassLoader {
-    private static final Set<ClassLoader> SYSTEM_CLASS_LOADERS = new HashSet<ClassLoader>();
-    private static final ClassLoader EXT_CLASS_LOADER;
-    private static final Set<String> SYSTEM_PACKAGES = new HashSet<String>();
-    private final Set<String> packageNames = new HashSet<String>();
-    private final Set<String> packagePrefixes = new HashSet<String>();
-    private final Set<String> resourcePrefixes = new HashSet<String>();
-    private final Set<String> resourceNames = new HashSet<String>();
-    private final Set<String> classNames = new HashSet<String>();
-    private final Set<String> disallowedClassNames = new HashSet<String>();
-
-    static {
-        EXT_CLASS_LOADER = ClassLoader.getSystemClassLoader().getParent();
-        for (ClassLoader cl = EXT_CLASS_LOADER; cl != null; cl = cl.getParent()) {
-            SYSTEM_CLASS_LOADERS.add(cl);
-        }
-        JavaMethod<ClassLoader, Package[]> method = JavaMethod.create(ClassLoader.class, Package[].class, "getPackages");
-        Package[] systemPackages = method.invoke(EXT_CLASS_LOADER);
-        for (Package p : systemPackages) {
-            SYSTEM_PACKAGES.add(p.getName());
-        }
-    }
-
-    public FilteringClassLoader(ClassLoader parent) {
-        super(parent);
-    }
-
-    @Override
-    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-        Class<?> cl;
-        try {
-            cl = super.loadClass(name, false);
-        } catch (NoClassDefFoundError e) {
-            if (classAllowed(name)) {
-                throw e;
-            }
-            // The class isn't visible
-            throw new ClassNotFoundException(String.format("%s not found.", name));
-        }
-
-        if (!allowed(cl)) {
-            throw new ClassNotFoundException(String.format("%s not found.", cl.getName()));
-        }
-        if (resolve) {
-            resolveClass(cl);
-        }
-
-        return cl;
-    }
-
-    @Override
-    protected Package getPackage(String name) {
-        Package p = super.getPackage(name);
-        if (p == null || !allowed(p)) {
-            return null;
-        }
-        return p;
-    }
-
-    @Override
-    protected Package[] getPackages() {
-        List<Package> packages = new ArrayList<Package>();
-        for (Package p : super.getPackages()) {
-            if (allowed(p)) {
-                packages.add(p);
-            }
-        }
-        return packages.toArray(new Package[packages.size()]);
-    }
-
-    @Override
-    public URL getResource(String name) {
-        if (allowed(name)) {
-            return super.getResource(name);
-        }
-        return EXT_CLASS_LOADER.getResource(name);
-    }
-
-    @Override
-    public Enumeration<URL> getResources(String name) throws IOException {
-        if (allowed(name)) {
-            return super.getResources(name);
-        }
-        return EXT_CLASS_LOADER.getResources(name);
-    }
-
-    private boolean allowed(String resourceName) {
-        if (resourceNames.contains(resourceName)) {
-            return true;
-        }
-        for (String resourcePrefix : resourcePrefixes) {
-            if (resourceName.startsWith(resourcePrefix)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean allowed(Package pkg) {
-        if (SYSTEM_PACKAGES.contains(pkg.getName())) {
-            return true;
-        }
-        if (packageNames.contains(pkg.getName())) {
-            return true;
-        }
-        for (String packagePrefix : packagePrefixes) {
-            if (pkg.getName().startsWith(packagePrefix)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean allowed(final Class<?> clazz) {
-        boolean systemClass = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
-            public Boolean run() {
-                return clazz.getClassLoader() == null || SYSTEM_CLASS_LOADERS.contains(clazz.getClassLoader());
-            }
-        });
-        return systemClass || classAllowed(clazz.getName());
-    }
-
-    private boolean classAllowed(String className) {
-        if (disallowedClassNames.contains(className)) {
-            return false;
-        }
-        if (classNames.contains(className)) {
-            return true;
-        }
-        for (String packagePrefix : packagePrefixes) {
-            if (className.startsWith(packagePrefix)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Marks a package and all its sub-packages as visible. Also makes resources in those packages visible.
-     *
-     * @param packageName the package name
-     */
-    public void allowPackage(String packageName) {
-        packageNames.add(packageName);
-        packagePrefixes.add(packageName + ".");
-        resourcePrefixes.add(packageName.replace('.', '/') + '/');
-    }
-
-    /**
-     * Marks a single class as visible.
-     *
-     * @param clazz the class
-     */
-    public void allowClass(Class<?> clazz) {
-        classNames.add(clazz.getName());
-    }
-
-    /**
-     * Marks a single class as not visible.
-     *
-     * @param className the class name
-     */
-    public void disallowClass(String className) {
-        disallowedClassNames.add(className);
-    }
-
-    /**
-     * Marks all resources with the given prefix as visible.
-     *
-     * @param resourcePrefix the resource prefix
-     */
-    public void allowResources(String resourcePrefix) {
-        resourcePrefixes.add(resourcePrefix + "/");
-    }
-
-    /**
-     * Marks a single resource as visible.
-     *
-     * @param resourceName the resource name
-     */
-    public void allowResource(String resourceName) {
-        resourceNames.add(resourceName);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
index a08f5d9..1e13ab1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
@@ -23,16 +23,11 @@ import org.gradle.internal.UncheckedException;
 import org.gradle.util.internal.LimitedDescription;
 
 import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.*;
 import java.util.zip.Checksum;
 
-/**
- * @author Hans Dockter
- */
 public class GFileUtils {
 
     public static FileInputStream openInputStream(File file) {
@@ -74,6 +69,7 @@ public class GFileUtils {
             throw new UncheckedIOException(e);
         }
     }
+
     public static String readFile(File file) {
         return readFile(file, Charset.defaultCharset().name());
     }
@@ -114,18 +110,6 @@ public class GFileUtils {
         return paths;
     }
 
-    public static List<URL> urisToUrls(Iterable<URI> uris) {
-        List<URL> urls = new ArrayList<URL>();
-        for (URI uri : uris) {
-            try {
-                urls.add(uri.toURL());
-            } catch (MalformedURLException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-        return urls;
-    }
-
     public static void copyURLToFile(URL source, File destination) {
         try {
             FileUtils.copyURLToFile(source, destination);
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 e1be0fa..b91082d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
@@ -29,9 +29,6 @@ import java.util.regex.Pattern;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 
-/**
- * @author Hans Dockter
- */
 public class GUtil {
     private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+");
     private static final Pattern UPPER_LOWER = Pattern.compile("(\\p{Upper}*)(\\p{Lower}*)");
@@ -133,15 +130,22 @@ public class GUtil {
         return isTrue(object) ? object : defaultValue;
     }
 
-    public static <V, T extends Collection<? super V>> T addToCollection(T dest, Iterable<? extends V>... srcs) {
+    public static <V, T extends Collection<? super V>> T addToCollection(T dest, boolean failOnNull, Iterable<? extends V>... srcs) {
         for (Iterable<? extends V> src : srcs) {
             for (V v : src) {
+                if (failOnNull && v == null) {
+                    throw new IllegalArgumentException("Illegal null value provided in this collection: " + src);
+                }
                 dest.add(v);
             }
         }
         return dest;
     }
 
+    public static <V, T extends Collection<? super V>> T addToCollection(T dest, Iterable<? extends V>... srcs) {
+        return addToCollection(dest, false, srcs);
+    }
+
     public static Comparator<String> caseInsensitive() {
         return new Comparator<String>() {
             public int compare(String o1, String o2) {
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 674eb14..c87eb2c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
@@ -20,7 +20,6 @@ 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;
@@ -39,16 +38,16 @@ import java.util.TimeZone;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * @author Hans Dockter
- * @author Russel Winder
- */
 public class GradleVersion implements Comparable<GradleVersion> {
-    public final static String URL = "http://www.gradle.org";
-    private final static Pattern VERSION_PATTERN = Pattern.compile("(\\d+(\\.\\d+)+)(-(\\p{Alpha}+)-(\\d+[a-z]?))?(-(\\d{14}([-+]\\d{4})?))?");
+    public static final String URL = "http://www.gradle.org";
+    private static final Pattern VERSION_PATTERN = Pattern.compile("((\\d+)(\\.\\d+)+)(-(\\p{Alpha}+)-(\\d+[a-z]?))?(-(\\d{14}([-+]\\d{4})?))?");
+    private static final int STAGE_MILESTONE = 0;
 
     private final String version;
+    private final int majorPart;
     private final String buildTime;
+    private final String commitId;
+    private final String buildNumber;
     private final Long snapshot;
     private final String versionPart;
     private final Stage stage;
@@ -69,9 +68,11 @@ public class GradleVersion implements Comparable<GradleVersion> {
 
             String version = properties.get("versionNumber").toString();
             String buildTimestamp = properties.get("buildTimestamp").toString();
+            String buildNumber = properties.get("buildNumber").toString();
+            String commitId = properties.get("commitId").toString();
             Date buildTime = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(buildTimestamp);
 
-            CURRENT = new GradleVersion(version, buildTime);
+            CURRENT = new GradleVersion(version, buildTime, buildNumber, commitId);
         } catch (Exception e) {
             throw new GradleException(String.format("Could not load version details from resource '%s'.", resource), e);
         } finally {
@@ -85,51 +86,58 @@ public class GradleVersion implements Comparable<GradleVersion> {
         }
     }
 
+
     public static GradleVersion current() {
         return CURRENT;
     }
 
-    public static GradleVersion version(String version) {
-        return new GradleVersion(version, null);
+    /**
+     * Parses the given string into a GradleVersion.
+     *
+     * @throws IllegalArgumentException On unrecognized version string.
+     */
+    public static GradleVersion version(String version) throws IllegalArgumentException {
+        return new GradleVersion(version, null, null, null);
     }
 
-    private GradleVersion(String version, Date buildTime) {
+    private GradleVersion(String version, Date buildTime, String buildNumber, String commitId) {
         this.version = version;
+        this.buildNumber = buildNumber;
+        this.commitId = commitId;
         this.buildTime = buildTime == null ? null : formatBuildTime(buildTime);
         Matcher matcher = VERSION_PATTERN.matcher(version);
         if (!matcher.matches()) {
-            // Unrecognized version
-            versionPart = null;
-            snapshot = null;
-            stage = null;
-            return;
+            throw new IllegalArgumentException(String.format("'%s' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')", version));
         }
 
         versionPart = matcher.group(1);
+        majorPart = Integer.parseInt(matcher.group(2), 10);
 
-        if (matcher.group(3) != null) {
-            int stageNumber = 0;
-            if (matcher.group(4).equals("milestone")) {
-                stageNumber = 1;
-            } else if (matcher.group(4).equals("preview")) {
+        if (matcher.group(4) != null) {
+            int stageNumber;
+            if (matcher.group(5).equals("milestone")) {
+                stageNumber = STAGE_MILESTONE;
+            } else if (matcher.group(5).equals("preview")) {
                 stageNumber = 2;
-            } else if (matcher.group(4).equals("rc")) {
+            } else if (matcher.group(5).equals("rc")) {
                 stageNumber = 3;
+            } else {
+                stageNumber = 1;
             }
-            String stageString = matcher.group(5);
+            String stageString = matcher.group(6);
             stage = new Stage(stageNumber, stageString);
         } else {
             stage = null;
         }
 
-        if (matcher.group(7) != null) {
+        if (matcher.group(8) != null) {
             try {
-                if (matcher.group(8) != null) {
-                    snapshot = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(matcher.group(7)).getTime();
+                if (matcher.group(9) != null) {
+                    snapshot = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(matcher.group(8)).getTime();
                 } else {
                     SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
                     format.setTimeZone(TimeZone.getTimeZone("UTC"));
-                    snapshot = format.parse(matcher.group(7)).getTime();
+                    snapshot = format.parse(matcher.group(8)).getTime();
                 }
             } catch (ParseException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
@@ -140,7 +148,7 @@ public class GradleVersion implements Comparable<GradleVersion> {
     }
 
     private String formatBuildTime(Date buildTime) {
-        DateFormat format = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
         format.setTimeZone(TimeZone.getTimeZone("UTC"));
         return format.format(buildTime);
     }
@@ -159,37 +167,37 @@ public class GradleVersion implements Comparable<GradleVersion> {
     }
 
     public boolean isSnapshot() {
-        return versionPart == null || snapshot != null;
+        return snapshot != null;
     }
 
     /**
-     * The base version number of the overall version.
+     * The base version of this version. For pre-release versions, this is the target 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.
+     * @return The version base
      */
-    @Nullable
-    public String getVersionBase() {
-        return versionPart;
+    public GradleVersion getBaseVersion() {
+        if (stage == null && snapshot == null) {
+            return this;
+        }
+        if (stage != null && stage.stage == STAGE_MILESTONE) {
+            return version(versionPart + "-milestone-" + stage.number);
+        }
+        return version(versionPart);
     }
 
-    public int getMajor() {
-        if (isValid()) {
-            return Integer.valueOf(versionPart.split("\\.", 2)[0], 10);
-        } else {
-            return -1;
+    public GradleVersion getNextMajor() {
+        if (stage != null && stage.stage == STAGE_MILESTONE) {
+            return version(majorPart + ".0");
         }
+        return version((majorPart + 1) + ".0");
     }
 
     public int compareTo(GradleVersion gradleVersion) {
-        assertCanQueryParts();
-        gradleVersion.assertCanQueryParts();
-
         String[] majorVersionParts = versionPart.split("\\.");
         String[] otherMajorVersionParts = gradleVersion.versionPart.split("\\.");
 
-
         for (int i = 0; i < majorVersionParts.length && i < otherMajorVersionParts.length; i++) {
             int part = Integer.parseInt(majorVersionParts[i]);
             int otherPart = Integer.parseInt(otherMajorVersionParts[i]);
@@ -234,12 +242,6 @@ public class GradleVersion implements Comparable<GradleVersion> {
         return 0;
     }
 
-    private void assertCanQueryParts() {
-        if (versionPart == null) {
-            throw new IllegalArgumentException(String.format("Cannot compare unrecognized Gradle version '%s'.", version));
-        }
-    }
-
     @Override
     public boolean equals(Object o) {
         if (o == this) {
@@ -261,17 +263,21 @@ public class GradleVersion implements Comparable<GradleVersion> {
         final StringBuilder sb = new StringBuilder();
         sb.append("\n------------------------------------------------------------\nGradle ");
         sb.append(getVersion());
-        sb.append("\n------------------------------------------------------------\n\nGradle build time: ");
+        sb.append("\n------------------------------------------------------------\n\nBuild time:   ");
         sb.append(getBuildTime());
-        sb.append("\nGroovy: ");
+        sb.append("\nBuild number: ");
+        sb.append(buildNumber);
+        sb.append("\nRevision:     ");
+        sb.append(commitId);
+        sb.append("\n\nGroovy:       ");
         sb.append(GroovySystem.getVersion());
-        sb.append("\nAnt: ");
+        sb.append("\nAnt:          ");
         sb.append(Main.getAntVersion());
-        sb.append("\nIvy: ");
+        sb.append("\nIvy:          ");
         sb.append(Ivy.getIvyVersion());
-        sb.append("\nJVM: ");
+        sb.append("\nJVM:          ");
         sb.append(Jvm.current());
-        sb.append("\nOS: ");
+        sb.append("\nOS:           ");
         sb.append(OperatingSystem.current());
         sb.append("\n");
         return sb.toString();
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/JarUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/JarUtil.java
index 998c81a..51f92a6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/JarUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/JarUtil.java
@@ -19,12 +19,9 @@ package org.gradle.util;
 import org.apache.commons.io.IOUtils;
 
 import java.io.*;
-import java.util.zip.ZipInputStream;
 import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
 
-/**
- * @author Tom Eyckmans
- */
 public class JarUtil {
     public static boolean extractZipEntry(File jarFile, String entryName, File extractToFile) throws IOException {
         boolean entryExtracted = false;
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java b/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
deleted file mode 100644
index bbe0d9e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
+++ /dev/null
@@ -1,75 +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.GradleException;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-
-public class JavaMethod<T, R> {
-    private final Method method;
-    private final Class<R> returnType;
-
-    private JavaMethod(Class<T> target, Class<R> returnType, String name, Class<?>... paramTypes) {
-        this.returnType = returnType;
-        method = findMethod(target, name, paramTypes);
-        method.setAccessible(true);
-    }
-
-    private JavaMethod(Class<T> target, Class<R> returnType, Method method) {
-        this.returnType = returnType;
-        this.method = method;
-        method.setAccessible(true);
-    }
-
-    private Method findMethod(Class target, String name, Class<?>[] paramTypes) {
-        for (Method method : target.getDeclaredMethods()) {
-            if (Modifier.isStatic(method.getModifiers())) {
-                continue;
-            }
-            if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), paramTypes)) {
-                return method;
-            }
-        }
-        throw new GradleException(String.format("Could not find method %s(%s) on %s", name, Arrays.toString(paramTypes),
-                target));
-    }
-
-    public R invoke(T target, Object... args) {
-        try {
-            return returnType.cast(method.invoke(target, args));
-        } catch (InvocationTargetException e) {
-            Throwable cause = e.getCause();
-            if (cause instanceof RuntimeException) {
-                throw (RuntimeException) cause;
-            }
-            throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), cause);
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), e);
-        }
-    }
-
-    public static <T, R> JavaMethod<T, R> create(Class<T> target, Class<R> returnType, String name, Class<?>... paramTypes) {
-        return new JavaMethod<T, R>(target, returnType, name, paramTypes);
-    }
-
-    public static <T, R> JavaMethod<T, R> create(Class<T> target, Class<R> returnType, Method method) {
-        return new JavaMethod<T, R>(target, returnType, method);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java b/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
index 2811d0c..5a1b47e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
@@ -15,31 +15,29 @@
  */
 package org.gradle.util;
 
-import org.gradle.api.Action;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.io.TextStream;
 
 import java.io.IOException;
 import java.io.OutputStream;
 
 /**
- * An OutputStream which separates bytes written into lines. Uses the platform default encoding. Is not thread safe.
- *
- * @author Hans Dockter
+ * An OutputStream which separates bytes written into lines of text. Uses the platform default encoding. Is not thread safe.
  */
 public class LineBufferingOutputStream extends OutputStream {
     private boolean hasBeenClosed;
     private final byte[] lineSeparator;
     private final int bufferIncrement;
-    private final Action<String> action;
+    private final TextStream handler;
     private byte[] buf;
     private int count;
 
-    public LineBufferingOutputStream(Action<String> action) {
-        this(action, 2048);
+    public LineBufferingOutputStream(TextStream handler) {
+        this(handler, 2048);
     }
 
-    public LineBufferingOutputStream(Action<String> action, int bufferLength) {
-        this.action = action;
+    public LineBufferingOutputStream(TextStream handler, int bufferLength) {
+        this.handler = handler;
         bufferIncrement = bufferLength;
         buf = new byte[bufferLength];
         count = 0;
@@ -52,8 +50,9 @@ public class LineBufferingOutputStream extends OutputStream {
      * cannot be reopened.
      */
     public void close() throws IOException {
-        flush();
         hasBeenClosed = true;
+        flush();
+        handler.endOfStream(null);
     }
 
     /**
@@ -98,15 +97,9 @@ public class LineBufferingOutputStream extends OutputStream {
         return true;
     }
 
-    /**
-     * Flushes this output stream and forces any buffered output bytes to be written out. The general contract of
-     * <code>flush</code> is that calling it is an indication that, if any bytes previously written have been buffered
-     * by the implementation of the output stream, such bytes should immediately be written to their intended
-     * destination.
-     */
     public void flush() {
         if (count != 0) {
-            action.execute(new String(buf, 0, count));
+            handler.text(new String(buf, 0, count));
         }
         reset();
     }
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 f848cd7..2ae8d07 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
@@ -16,7 +16,7 @@
 
 package org.gradle.util;
 
-import org.gradle.api.Action;
+import org.gradle.internal.io.TextStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -26,21 +26,21 @@ import java.security.PrivilegedAction;
 import java.util.Locale;
 
 public class LinePerThreadBufferingOutputStream extends PrintStream {
-    private final Action<String> listener;
+    private final TextStream handler;
     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));
+                    return new PrintStream(new LineBufferingOutputStream(handler));
                 }
             });
         }
     };
 
-    public LinePerThreadBufferingOutputStream(Action<String> listener) {
+    public LinePerThreadBufferingOutputStream(TextStream handler) {
         super(new ByteArrayOutputStream(), true);
-        this.listener = listener;
+        this.handler = handler;
     }
 
     private PrintStream getStream() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java
deleted file mode 100644
index d86da52..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util;
-
-import java.util.*;
-import java.net.URL;
-import java.io.IOException;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * A {@code ClassLoader} which delegates to multiple parent ClassLoaders.
- */
-public class MultiParentClassLoader extends ClassLoader implements ClasspathSource {
-    private final List<ClassLoader> parents;
-    private final JavaMethod<ClassLoader, Package[]> getPackagesMethod;
-    private final JavaMethod<ClassLoader, Package> getPackageMethod;
-
-    public MultiParentClassLoader(ClassLoader... parents) {
-        super(null);
-        this.parents = new CopyOnWriteArrayList<ClassLoader>(Arrays.asList(parents));
-        getPackagesMethod = JavaMethod.create(ClassLoader.class, Package[].class, "getPackages");
-        getPackageMethod = JavaMethod.create(ClassLoader.class, Package.class, "getPackage", String.class);
-    }
-
-    public void addParent(ClassLoader parent) {
-        parents.add(parent);
-    }
-
-    public void collectClasspath(Collection<? super URL> classpath) {
-        for (ClassLoader parent : parents) {
-            new ClassLoaderBackedClasspathSource(parent).collectClasspath(classpath);
-        }
-    }
-
-    @Override
-    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-        for (ClassLoader parent : parents) {
-            try {
-                return parent.loadClass(name);
-            } catch (ClassNotFoundException e) {
-                // Expected
-            }
-        }
-        throw new ClassNotFoundException(String.format("%s not found.", name));
-    }
-
-    @Override
-    protected Package getPackage(String name) {
-        for (ClassLoader parent : parents) {
-            Package p = getPackageMethod.invoke(parent, name);
-            if (p != null) {
-                return p;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    protected Package[] getPackages() {
-        Set<Package> packages = new LinkedHashSet<Package>();
-        for (ClassLoader parent : parents) {
-            Package[] parentPackages = getPackagesMethod.invoke(parent);
-            packages.addAll(Arrays.asList(parentPackages));
-        }
-        return packages.toArray(new Package[packages.size()]);
-    }
-
-    @Override
-    public URL getResource(String name) {
-        for (ClassLoader parent : parents) {
-            URL resource = parent.getResource(name);
-            if (resource != null) {
-                return resource;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Enumeration<URL> getResources(String name) throws IOException {
-        Set<URL> resources = new LinkedHashSet<URL>();
-        for (ClassLoader parent : parents) {
-            Enumeration<URL> parentResources = parent.getResources(name);
-            while (parentResources.hasMoreElements()) {
-                resources.add(parentResources.nextElement());
-            }
-        }
-        return Collections.enumeration(resources);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java
deleted file mode 100755
index c8a50ce..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.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.util;
-
-import org.gradle.internal.classpath.ClassPath;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Collection;
-
-public class MutableURLClassLoader extends URLClassLoader {
-    public MutableURLClassLoader(ClassLoader parent, URL... urls) {
-        super(urls, parent);
-    }
-
-    public MutableURLClassLoader(ClassLoader parent, Collection<URL> urls) {
-        super(urls.toArray(new URL[urls.size()]), parent);
-    }
-
-    public MutableURLClassLoader(ClassLoader parent, ClassPath classPath) {
-        super(classPath.getAsURLArray(), parent);
-    }
-
-    @Override
-    public void addURL(URL url) {
-        super.addURL(url);
-    }
-
-    public void addURLs(Iterable<URL> urls) {
-        for (URL url : urls) {
-            addURL(url);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Path.java b/subprojects/core/src/main/groovy/org/gradle/util/Path.java
index f433fbf..6c70294 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Path.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Path.java
@@ -23,9 +23,6 @@ import org.gradle.api.Project;
 import java.util.Arrays;
 import java.util.Comparator;
 
-/**
- * @author Hans Dockter
- */
 public class Path implements Comparable<Path> {
     private static final Comparator<String> STRING_COMPARATOR = GUtil.caseInsensitive();
     private final String prefix;
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy b/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
deleted file mode 100644
index 80a1e05..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util
-
-/**
- * @author Hans Dockter
- */
-class ReflectionUtil {
-    static Object invoke(Object object, String method, Object... params) {
-        object.invokeMethod(method, params)
-    }
-
-    static Object getProperty(Object object, String property) {
-        object."$property"
-    }
-
-    static void setProperty(Object object, String property, Object value) {
-        object."$property" = value
-    }
-
-    static boolean hasProperty(Object object, String property) {
-        object.metaClass.hasProperty(object, property) != null
-    }
-
-    static boolean isClassAvailable(String className) {
-        try {
-            ReflectionUtil.classLoader.loadClass(className)
-            return true
-        } catch (ClassNotFoundException e) {
-            return false
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java b/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java
index 647345f..b58e0ec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java
@@ -16,12 +16,14 @@
 
 package org.gradle.util;
 
+import net.jcip.annotations.ThreadSafe;
 import org.apache.commons.lang.StringUtils;
-import org.codehaus.groovy.runtime.StackTraceUtils;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.Factory;
+import org.gradle.internal.featurelifecycle.DeprecatedFeatureUsage;
+import org.gradle.internal.featurelifecycle.LoggingDeprecatedFeatureHandler;
+import org.gradle.internal.featurelifecycle.UsageLocationReporter;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -29,15 +31,11 @@ import java.util.Set;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+ at ThreadSafe
 public class SingleMessageLogger {
-
     private static final Logger LOGGER = Logging.getLogger(DeprecationLogger.class);
-    private static final Set<String> PLUGINS = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> TASKS = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> METHODS = Collections.synchronizedSet(new HashSet<String>());
     private static final Set<String> DYNAMIC_PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> NAMED_PARAMETERS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> FEATURES = Collections.synchronizedSet(new HashSet<String>());
 
     private static final ThreadLocal<Boolean> ENABLED = new ThreadLocal<Boolean>() {
         @Override
@@ -46,128 +44,106 @@ public class SingleMessageLogger {
         }
     };
 
-    private static final ThreadLocal<Boolean> LOG_TRACE = new ThreadLocal<Boolean>() {
-        @Override
-        protected Boolean initialValue() {
-            return false;
-        }
-    };
-
     public static final String ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME = "org.gradle.deprecation.trace";
+    public static final String INCUBATION_MESSAGE = "%s is an incubating feature.";
 
+    private static final Lock LOCK = new ReentrantLock();
+    private static LoggingDeprecatedFeatureHandler handler = new LoggingDeprecatedFeatureHandler();
     private static String deprecationMessage;
-    private static Lock deprecationMessageLock = new ReentrantLock();
-    public static final String INCUBATION_MESSAGE = "%s is an incubating feature. Enjoy it and let us know how it works for you.";
 
-    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();
+    public static String getDeprecationMessage() {
+        LOCK.lock();
+        try {
+            if (deprecationMessage == null) {
+                String messageBase = "has been deprecated and is scheduled to be removed in";
+
+                GradleVersion currentVersion = GradleVersion.current();
+                String when = String.format("Gradle %s", currentVersion.getNextMajor().getVersion());
+
+                deprecationMessage = String.format("%s %s", messageBase, when);
             }
+            return deprecationMessage;
+        } finally {
+            LOCK.unlock();
         }
-
-        return deprecationMessage;
     }
 
     public static void reset() {
-        PLUGINS.clear();
-        METHODS.clear();
-        PROPERTIES.clear();
-        NAMED_PARAMETERS.clear();
         DYNAMIC_PROPERTIES.clear();
+        FEATURES.clear();
+        LOCK.lock();
+        try {
+            handler = new LoggingDeprecatedFeatureHandler();
+        } finally {
+            LOCK.unlock();
+        }
     }
 
-    public static void nagUserOfReplacedPlugin(String pluginName, String replacement) {
-        if (isEnabled() && PLUGINS.add(pluginName)) {
-            LOGGER.warn(String.format(
-                    "The %s plugin %S. Please use the %s plugin instead.",
-                    pluginName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
+    public static void useLocationReporter(UsageLocationReporter reporter) {
+        LOCK.lock();
+        try {
+            handler.setLocationReporter(reporter);
+        } finally {
+            LOCK.unlock();
         }
     }
 
+    public static void nagUserOfReplacedPlugin(String pluginName, String replacement) {
+        nagUserWith(String.format(
+                "The %s plugin %s. Please use the %s plugin instead.",
+                pluginName, getDeprecationMessage(), replacement));
+    }
+
     public static void nagUserOfReplacedTaskType(String taskName, String replacement) {
-        if (isEnabled() && TASKS.add(taskName)) {
-            LOGGER.warn(String.format(
-                    "The %s task type %s. Please use the %s instead.",
-                    taskName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
+        nagUserWith(String.format(
+                "The %s task type %s. Please use the %s instead.",
+                taskName, getDeprecationMessage(), replacement));
     }
 
     public static void nagUserOfReplacedMethod(String methodName, String replacement) {
-        if (isEnabled() && METHODS.add(methodName)) {
-            LOGGER.warn(String.format(
-                    "The %s method %s. Please use the %s method instead.",
-                    methodName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
+        nagUserWith(String.format(
+                "The %s method %s. Please use the %s method instead.",
+                methodName, getDeprecationMessage(), replacement));
     }
 
     public static void nagUserOfReplacedProperty(String propertyName, String replacement) {
-        if (isEnabled() && PROPERTIES.add(propertyName)) {
-            LOGGER.warn(String.format(
-                    "The %s property %s. Please use the %s property instead.",
-                    propertyName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
+        nagUserWith(String.format(
+                "The %s property %s. Please use the %s property instead.",
+                propertyName, getDeprecationMessage(), replacement));
     }
 
     public static void nagUserOfDiscontinuedMethod(String methodName) {
-        if (isEnabled() && METHODS.add(methodName)) {
-            LOGGER.warn(String.format("The %s method %s.",
-                    methodName, getDeprecationMessage()));
-            logTraceIfNecessary();
-        }
+        nagUserWith(String.format("The %s method %s.",
+                methodName, getDeprecationMessage()));
     }
 
     public static void nagUserOfDiscontinuedProperty(String propertyName, String advice) {
-        if (isEnabled() && PROPERTIES.add(propertyName)) {
-            LOGGER.warn(String.format("The %s property %s. %s",
-                    propertyName, getDeprecationMessage(), advice));
-            logTraceIfNecessary();
-        }
+        nagUserWith(String.format("The %s property %s. %s",
+                propertyName, getDeprecationMessage(), advice));
     }
 
-    public static void nagUserOfReplacedNamedParameter(String parameterName, String replacement) {
-        if (isEnabled() && NAMED_PARAMETERS.add(parameterName)) {
-            LOGGER.warn(String.format(
-                    "The %s named parameter %s. Please use the %s named parameter instead.",
-                    parameterName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
+    public static void nagUserOfDiscontinuedConfiguration(String configurationName, String advice) {
+        nagUserWith(String.format("The %s configuration %s. %s",
+                configurationName, getDeprecationMessage(), advice));
     }
 
-    /**
-     * 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) {
-        inform(LogLevel.WARN, message);
-        logTraceIfNecessary();
+    public static void nagUserOfReplacedNamedParameter(String parameterName, String replacement) {
+        nagUserWith(String.format(
+                "The %s named parameter %s. Please use the %s named parameter instead.",
+                parameterName, getDeprecationMessage(), replacement));
     }
 
     /**
      * Try to avoid using this nagging method. The other methods use a consistent wording for when things will be removed.
      */
-    public static void inform(LogLevel level, String message) {
-        if (isEnabled() && METHODS.add(message)) {
-            LOGGER.log(level, message);
+    public static void nagUserWith(String message) {
+        if (isEnabled()) {
+            LOCK.lock();
+            try {
+                handler.deprecatedFeatureUsed(new DeprecatedFeatureUsage(message, SingleMessageLogger.class));
+            } finally {
+                LOCK.unlock();
+            }
         }
     }
 
@@ -204,29 +180,10 @@ public class SingleMessageLogger {
         }
     }
 
-    private static boolean isTraceLoggingEnabled() {
-        return Boolean.getBoolean(ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME) || LOG_TRACE.get();
-    }
-
-    private static void logTraceIfNecessary() {
-        if (isTraceLoggingEnabled()) {
-            StackTraceElement[] stack = StackTraceUtils.sanitize(new Exception()).getStackTrace();
-            for (StackTraceElement frame : stack) {
-                if (!frame.getClassName().startsWith(DeprecationLogger.class.getName())) {
-                    LOGGER.warn("    {}", frame.toString());
-                }
-            }
-        }
-    }
-
     private static boolean isEnabled() {
         return ENABLED.get();
     }
 
-    public static void setLogTrace(boolean flag) {
-        LOG_TRACE.set(flag);
-    }
-
     public static void nagUserAboutDynamicProperty(String propertyName, Object target, Object value) {
         if (!isEnabled()) {
             return;
@@ -243,7 +200,9 @@ public class SingleMessageLogger {
         }
     }
 
-    public static void informAboutIncubating(String incubatingFeature) {
-        inform(LogLevel.LIFECYCLE, String.format(INCUBATION_MESSAGE, incubatingFeature));
+    public static void incubatingFeatureUsed(String incubatingFeature) {
+        if (FEATURES.add(incubatingFeature)) {
+            LOGGER.lifecycle(String.format(INCUBATION_MESSAGE, incubatingFeature));
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/StdoutSwapper.java b/subprojects/core/src/main/groovy/org/gradle/util/StdoutSwapper.java
deleted file mode 100644
index 5118100..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/StdoutSwapper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util;
-
-import org.gradle.api.Action;
-import java.util.concurrent.Callable;
-
-import java.io.PrintStream;
-
-public class StdoutSwapper extends Swapper<PrintStream> {
-
-    public StdoutSwapper() {
-        super(
-            new Callable<PrintStream>() {
-                public PrintStream call() {
-                    return System.out;
-                }
-            },
-            new Action<PrintStream>() {
-                public void execute(PrintStream newValue) {
-                    System.setOut(newValue);
-                }
-            }
-        );
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
index c7f6c9e..90e3186 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
@@ -61,6 +61,13 @@ public class TextUtil {
     }
 
     /**
+     * Converts all line separators in the specified string to a single new line character.
+     */
+    public static String normaliseLineSeparators(String str) {
+        return str == null ? null : convertLineSeparators(str, "\n");
+    }
+
+    /**
      * Converts all native file separators in the specified string to '/'.
      */
     public static String normaliseFileSeparators(String path) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java b/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
index 3ad64b6..10255ff 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
@@ -26,8 +26,13 @@ import java.util.regex.Pattern;
 
 /**
  * Represents, parses, and compares version numbers following a major.minor.micro-qualifier format.
- * Note that this class considers "2.10.0" less than "2.10.0-something", presumably to make it easier to
- * test for "less than any 2.10 version" and "greater than any 2.10 version".
+ * The {@link #parse} method handles missing parts and allows "." to be used instead of "-".
+ *
+ * <p>Note that this class considers "1.2.3-something" less than "1.2.3". Qualifiers are compared
+ * lexicographically ("1.2.3-alpha" < "1.2.3-beta") and case-insensitive ("1.2.3-alpha" < "1.2.3.RELEASE").
+ *
+ * <p>To check if a version number is at least "1.2.3", disregarding a potential qualifier like "beta", use
+ * {@code version.getBaseVersion().compareTo(VersionNumber.parse("1.2.3")) >= 0}.
  */
 public class VersionNumber implements Comparable<VersionNumber> {
     public static final VersionNumber UNKNOWN = new VersionNumber(0, 0, 0, null);
@@ -63,11 +68,15 @@ public class VersionNumber implements Comparable<VersionNumber> {
         return qualifier;
     }
 
+    public VersionNumber getBaseVersion() {
+        return new VersionNumber(major, minor, micro, null);
+    }
+
     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);
+        return Ordering.natural().nullsLast().compare(toLowerCase(qualifier), toLowerCase(other.qualifier));
     }
 
     public boolean equals(Object other) {
@@ -100,5 +109,9 @@ public class VersionNumber implements Comparable<VersionNumber> {
 
         return new VersionNumber(major, minor, micro, qualifier);
     }
+
+    private String toLowerCase(@Nullable String string) {
+        return string == null ? null : string.toLowerCase();
+    }
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashUtil.java
deleted file mode 100644
index ea6db61..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashUtil.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util.hash;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.UncheckedException;
-
-import java.io.*;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-public class HashUtil {
-    public static HashValue createHash(String scriptText, String algorithm) {
-        MessageDigest messageDigest = createMessageDigest(algorithm);
-        messageDigest.update(scriptText.getBytes());
-        return new HashValue(messageDigest.digest());
-    }
-
-    public static HashValue createHash(File file, String algorithm) {
-        try {
-            return createHash(new FileInputStream(file), algorithm);
-        } catch (FileNotFoundException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static HashValue createHash(InputStream instr, String algorithm) {
-        MessageDigest messageDigest = createMessageDigest(algorithm);
-        try {
-            byte[] buffer = new byte[4096];
-            try {
-                while (true) {
-                    int nread = instr.read(buffer);
-                    if (nread < 0) {
-                        break;
-                    }
-                    messageDigest.update(buffer, 0, nread);
-                }
-            } finally {
-                instr.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        return new HashValue(messageDigest.digest());
-    }
-
-    private static MessageDigest createMessageDigest(String algorithm) {
-        try {
-            return MessageDigest.getInstance(algorithm);
-        } catch (NoSuchAlgorithmException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public static String createCompactMD5(String scriptText) {
-        return createHash(scriptText, "MD5").asCompactString();
-    }
-
-    public static HashValue sha1(byte[] bytes) {
-        return createHash(new ByteArrayInputStream(bytes), "SHA1");
-    }
-
-    public static HashValue sha1(InputStream inputStream) {
-        return createHash(inputStream, "SHA1");
-    }
-
-    public static HashValue sha1(File file) {
-        return createHash(file, "SHA1");
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
deleted file mode 100644
index 155f01b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util.hash;
-
-import java.math.BigInteger;
-
-public class HashValue {
-    private final BigInteger digest;
-
-    public HashValue(byte[] digest) {
-        this.digest = new BigInteger(1, digest);
-    }
-
-    public HashValue(String hexString) {
-        this.digest = new BigInteger(hexString, 16);
-    }
-
-    public static HashValue parse(String inputString) {
-        if (inputString == null || inputString.length() == 0) {
-            return null;
-        }
-        return new HashValue(parseInput(inputString));
-    }
-
-    private static String parseInput(String inputString) {
-        if (inputString == null) {
-            return null;
-        }
-        String cleaned = inputString.trim().toLowerCase();
-        int spaceIndex = cleaned.indexOf(' ');
-        if (spaceIndex != -1) {
-            String firstPart = cleaned.substring(0, spaceIndex);
-            if (firstPart.startsWith("md") || firstPart.startsWith("sha")) {
-                cleaned = cleaned.substring(cleaned.lastIndexOf(' ') + 1);
-            } else if (firstPart.endsWith(":")) {
-                cleaned = cleaned.substring(spaceIndex + 1).replace(" ", "");
-            } else {
-                cleaned = cleaned.substring(0, spaceIndex);
-            }
-        }
-        return cleaned;
-    }
-
-    public String asCompactString() {
-        return digest.toString(32);
-    }
-
-    public String asHexString() {
-        return digest.toString(16);
-    }
-
-    public byte[] asByteArray() {
-        return digest.toByteArray();
-    }
-
-    public BigInteger asBigInteger() {
-        return digest;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-        if (!(other instanceof HashValue)) {
-            return false;
-        }
-
-        HashValue otherHashValue = (HashValue) other;
-        return digest.equals(otherHashValue.digest);
-    }
-
-    @Override
-    public int hashCode() {
-        return digest.hashCode();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java b/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java
index 06a7a8a..e629ec0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java
@@ -23,8 +23,6 @@ import java.util.List;
 
 /**
  * Discards old entries when current count is over the limit.
- * <p>
- * by Szczepan Faber, created at: 2/28/12
  */
 public class LimitedDescription {
 
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
deleted file mode 100644
index 1d22590..0000000
--- a/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-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.publish.maven.*
-import org.gradle.api.publish.maven.tasks.*
-import org.gradle.api.execution.*
-import org.gradle.api.file.*
-import org.gradle.api.resources.*
-import org.gradle.api.initialization.*
-import org.gradle.api.invocation.*
-import org.gradle.api.java.archives.*
-import org.gradle.api.logging.*
-import org.gradle.api.plugins.*
-import org.gradle.plugins.ide.eclipse.*
-import org.gradle.plugins.ide.idea.*
-import org.gradle.plugins.jetty.*
-import org.gradle.api.plugins.quality.*
-import org.gradle.api.plugins.announce.*
-import org.gradle.api.plugins.buildcomparison.gradle.*
-import org.gradle.api.specs.*
-import org.gradle.api.tasks.*
-import org.gradle.api.tasks.bundling.*
-import org.gradle.api.tasks.diagnostics.*
-import org.gradle.api.tasks.compile.*
-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/main/resources/org/gradle/initialization/buildsrc/defaultBuildSourceScript.txt b/subprojects/core/src/main/resources/org/gradle/initialization/buildsrc/defaultBuildSourceScript.txt
new file mode 100644
index 0000000..86b3740
--- /dev/null
+++ b/subprojects/core/src/main/resources/org/gradle/initialization/buildsrc/defaultBuildSourceScript.txt
@@ -0,0 +1,6 @@
+apply plugin: 'groovy'
+dependencies {
+    compile gradleApi()
+    compile localGroovy()
+}
+
diff --git a/subprojects/core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt b/subprojects/core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt
deleted file mode 100644
index a47ee21..0000000
--- a/subprojects/core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-apply plugin: 'groovy'
-dependencies {
-    compile gradleApi()
-    groovy localGroovy()
-}
diff --git a/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css b/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css
index e09a387..8ddb790 100644
--- a/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css
+++ b/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css
@@ -68,7 +68,7 @@ ul.tabLinks li {
     margin-right: 25px;
     border: solid 1px #d4d4d4;
     background-color: #f0f0f0;
-    behavior: url(css3-pie-1.0beta3.htc);
+    behavior: url(htc/css3-pie-1.0beta3.htc);
 }
 
 ul.tabLinks li:hover {
diff --git a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
index faf0658..d1e2ff7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
@@ -16,19 +16,17 @@
 package org.gradle
 
 import org.gradle.api.GradleException
-import org.gradle.api.internal.LocationAwareException
+import org.gradle.internal.exceptions.AbstractMultiCauseException
+import org.gradle.internal.exceptions.LocationAwareException
 import org.gradle.api.logging.LogLevel
-import org.gradle.api.internal.AbstractMultiCauseException
-import org.gradle.execution.TaskSelectionException
+import org.gradle.execution.MultipleBuildFailures
 import org.gradle.initialization.BuildClientMetaData
 import org.gradle.logging.LoggingConfiguration
 import org.gradle.logging.ShowStacktrace
 import org.gradle.logging.StyledTextOutputFactory
 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()
@@ -42,34 +40,30 @@ class BuildExceptionReporterTest extends Specification {
         clientMetaData.describeCommand(!null, !null) >> { args -> args[0].append("[gradle ${args[1].join(' ')}]")}
     }
 
-    def doesNothingWheBuildIsSuccessful() {
+    def doesNothingWhenBuildIsSuccessful() {
         expect:
         reporter.buildFinished(result(null))
         output.value == ''
     }
 
-    def reportsInternalFailure() {
-        final RuntimeException exception = new RuntimeException("<message>");
+    def reportsBuildFailure() {
+        GradleException exception = new GradleException("<message>");
 
         expect:
         reporter.buildFinished(result(exception))
         output.value == '''
-{failure}FAILURE: {normal}{failure}Build aborted because of an internal error.{normal}
+{failure}FAILURE: {normal}{failure}Build failed with an exception.{normal}
 
 * What went wrong:
-Build aborted because of an unexpected internal error. Please file an issue at: http://forums.gradle.org.
+<message>
 
 * Try:
-Run with {userinput}--debug{normal} option to get additional debug info.
-
-* Exception is:
-java.lang.RuntimeException: <message>
-{stacktrace}
+Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
 '''
     }
 
-    def reportsBuildFailure() {
-        GradleException exception = new GradleException("<message>");
+    def reportsBuildFailureWhenFailureHasNoMessage() {
+        GradleException exception = new GradleException();
 
         expect:
         reporter.buildFinished(result(exception))
@@ -77,31 +71,35 @@ java.lang.RuntimeException: <message>
 {failure}FAILURE: {normal}{failure}Build failed with an exception.{normal}
 
 * What went wrong:
-<message>
+org.gradle.api.GradleException (no error message)
 
 * Try:
 Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
 '''
     }
 
-    def reportsBuildFailureWhenFailureHasNoMessage() {
-        GradleException exception = new GradleException();
+    def reportsLocationAwareException() {
+        Throwable exception = exception("<location>", new RuntimeException("<message>"), new RuntimeException("<cause>"));
 
         expect:
         reporter.buildFinished(result(exception))
         output.value == '''
 {failure}FAILURE: {normal}{failure}Build failed with an exception.{normal}
 
+* Where:
+<location>
+
 * What went wrong:
-org.gradle.api.GradleException (no error message)
+<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.
 '''
     }
 
-    def reportsLocationAwareException() {
-        Throwable exception = exception("<location>", "<message>", new RuntimeException("<cause>"));
+    def reportsLocationAwareExceptionWithNoMessage() {
+        Throwable exception = exception("<location>", new RuntimeException(), new IOException());
 
         expect:
         reporter.buildFinished(result(exception))
@@ -112,8 +110,8 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 <location>
 
 * What went wrong:
-<message>
-{info}> {normal}<cause>
+java.lang.RuntimeException (no error message)
+{info}> {normal}java.io.IOException (no error message)
 
 * Try:
 Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
@@ -121,7 +119,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
     }
 
     def reportsLocationAwareExceptionWithMultipleCauses() {
-        Throwable exception = exception("<location>", "<message>", new RuntimeException("<cause1>"), new RuntimeException("<cause2>"));
+        Throwable exception = exception("<location>", new RuntimeException("<message>"), new RuntimeException("<cause1>"), new RuntimeException("<cause2>"));
 
         expect:
         reporter.buildFinished(result(exception))
@@ -144,7 +142,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
     def reportsLocationAwareExceptionWithMultipleNestedCauses() {
         def cause1 = nested("<cause1>", new RuntimeException("<cause1.1>"), new RuntimeException("<cause1.2>"))
         def cause2 = nested("<cause2>", new RuntimeException("<cause2.1>"))
-        Throwable exception = exception("<location>", "<message>", cause1, cause2);
+        Throwable exception = exception("<location>", new RuntimeException("<message>"), cause1, cause2);
 
         expect:
         reporter.buildFinished(result(exception))
@@ -168,7 +166,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
     }
 
     def reportsLocationAwareExceptionWhenCauseHasNoMessage() {
-        Throwable exception = exception("<location>", "<message>", new RuntimeException());
+        Throwable exception = exception("<location>", new RuntimeException("<message>"), new RuntimeException());
 
         expect:
         reporter.buildFinished(result(exception))
@@ -190,7 +188,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
     def showsStacktraceOfCauseOfLocationAwareException() {
         configuration.showStacktrace = ShowStacktrace.ALWAYS
 
-        Throwable exception = exception("<location>", "<message>", new GradleException('<failure>'))
+        Throwable exception = exception("<location>", new GradleException("<message>"), new GradleException('<failure>'))
 
         expect:
         reporter.buildFinished(result(exception))
@@ -208,29 +206,13 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
 
 * Exception is:
-org.gradle.api.GradleException: <failure>
+org.gradle.api.GradleException: <message>
 {stacktrace}
 '''
     }
 
-    def reportsTaskSelectionException() {
-        Throwable exception = new TaskSelectionException("<message>");
-
-        expect:
-        reporter.buildFinished(result(exception))
-        output.value == '''
-{failure}FAILURE: {normal}{failure}Could not determine which tasks to execute.{normal}
-
-* What went wrong:
-<message>
-
-* Try:
-Run {userinput}[gradle tasks]{normal} to get a list of available tasks.
-'''
-    }
-
     def reportsMultipleBuildFailures() {
-        def failure1 = exception("<location>", "<message>", new RuntimeException("<cause>"))
+        def failure1 = exception("<location>", new RuntimeException("<message>"), new RuntimeException("<cause>"))
         def failure2 = new GradleException("<failure>")
         def failure3 = new RuntimeException("<error>")
         Throwable exception = new MultipleBuildFailures([failure1, failure2, failure3])
@@ -262,17 +244,13 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 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}
+{failure}3: {normal}{failure}Task failed with an exception.{normal}
 -----------
 * What went wrong:
-Build aborted because of an unexpected internal error. Please file an issue at: http://forums.gradle.org.
+<error>
 
 * Try:
-Run with {userinput}--debug{normal} option to get additional debug info.
-
-* Exception is:
-java.lang.RuntimeException: <error>
-{stacktrace}
+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.
 ==============================================================================
 ''';
     }
@@ -350,11 +328,10 @@ org.gradle.api.GradleException: <message>
         return new TestException(message, causes)
     }
     
-    def exception(String location, String message, Throwable... causes) {
+    def exception(String location, RuntimeException target, Throwable... causes) {
         LocationAwareException exception = Mock()
         exception.location >> location
-        exception.originalMessage >> message
-        exception.cause >> causes[0]
+        exception.cause >> target
         exception.visitReportableCauses(!null) >> { TreeVisitor visitor ->
             visitor.node(exception)
             visitor.startChildren()
diff --git a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
index fccd6e5..2b4ade3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -25,9 +25,6 @@ import spock.lang.Specification
 import static org.gradle.util.Matchers.isSerializable
 import static org.junit.Assert.assertThat
 
-/**
- * @author Hans Dockter
- */
 class StartParameterTest extends Specification {
     @Rule private TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     @Rule private SetSystemProperties systemProperties = new SetSystemProperties()
@@ -286,19 +283,6 @@ class StartParameterTest extends Specification {
         assertThat(newParameter, isSerializable())
     }
     
-    void "system properties are merged"() {
-        def parameter = new StartParameter()
-
-        System.properties.clear()
-        System.properties.a = "sys a"
-        System.properties.c = "sys c"
-
-        parameter.systemPropertiesArgs= [a: 'a', b: 'b']
-
-        expect:
-        parameter.mergedSystemProperties.sort() == [a: 'a', b: 'b', c: 'sys c']
-    }
-
     void "gets all init scripts"() {
         def gradleUserHomeDir = tmpDir.testDirectory.createDir("gradleUserHomeDie")
         def gradleHomeDir = tmpDir.testDirectory.createDir("gradleHomeDir")
@@ -330,16 +314,4 @@ class StartParameterTest extends Specification {
         then:
         parameter.allInitScripts == [userMainInit, userInit1, userInit2, distroInit1, distroInit2]
     }
-
-    def "knows if parallel feature was configured"() {
-        def parameter = new StartParameter()
-        assert !parameter.parallelThreadCountConfigured
-
-        when:
-        parameter.setParallelThreadCount(15)
-
-        then:
-        parameter.parallelThreadCount == 15
-        parameter.parallelThreadCountConfigured
-    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy
index b0c3a9a..b210a9c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy
@@ -20,6 +20,7 @@ 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.internal.progress.LoggerProvider
 import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
 import spock.lang.Specification
@@ -31,7 +32,8 @@ public class TaskExecutionLoggerTest extends Specification {
     def state = Mock(TaskState)
     def progressLogger = Mock(ProgressLogger)
     def gradle = Mock(Gradle)
-    def executionLogger = new TaskExecutionLogger(progressLoggerFactory);
+    def loggerProvider = Stub(LoggerProvider) { getLogger() >> Stub(ProgressLogger) }
+    def executionLogger = new TaskExecutionLogger(progressLoggerFactory, loggerProvider);
     def project = Mock(Project)
 
     def setup() {
@@ -99,7 +101,7 @@ public class TaskExecutionLoggerTest extends Specification {
     }
 
     def startLogTaskExecution(def path) {
-        1 * progressLoggerFactory.newOperation(TaskExecutionLogger) >> progressLogger
+        1 * progressLoggerFactory.newOperation(TaskExecutionLogger, _ as ProgressLogger) >> progressLogger
         1 * progressLogger.setDescription("Execute " + path)
         1 * progressLogger.setShortDescription("$path")
         1 * progressLogger.setLoggingHeader("$path")
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy
index 4523490..47a1f9c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy
@@ -17,11 +17,9 @@
 package org.gradle.api
 
 import org.junit.Test
-import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.assertEquals;
+
 class AllGradleExceptionsTest {
     static final List EXCEPTION_CLASSES = [UnknownTaskException, UnknownProjectException, InvalidUserDataException, GradleException, CircularReferenceException]
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/file/ProjectCopySpecTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/file/ProjectCopySpecTest.groovy
new file mode 100644
index 0000000..ec124a5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/file/ProjectCopySpecTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.file
+
+import org.gradle.api.Project
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectCopySpecTest extends Specification {
+
+    Project project
+
+    @Rule TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+
+    def setup() {
+        project = ProjectBuilder.builder().withProjectDir(testDirectoryProvider.testDirectory).build()
+    }
+
+    TestFile getCopySource() {
+        testDirectoryProvider.testDirectory.createDir("source")
+    }
+
+    TestFile getCopyDest() {
+        testDirectoryProvider.testDirectory.createDir("dest")
+    }
+
+
+    def "copy spec is enhanced"() {
+        given:
+        def copySpecRootCalled = false
+        def copySpecEachFileCalled = false
+        def copySpecNestedEachFileCalled = false
+        def copyRootCalled = false
+        def copyEachFileCalled = false
+        def copyNestedEachFileCalled = false
+
+        copySource.createFile("file")
+        def copySpec = project.copySpec {
+            copySpecRootCalled = true
+            delegate.duplicatesStrategy "include"
+            from copySource
+
+            from copySource, {
+                delegate.duplicatesStrategy "include"
+                delegate.eachFile {
+                    copySpecNestedEachFileCalled = true
+                    delegate.duplicatesStrategy "include"
+                }
+            }
+
+            eachFile {
+                copySpecEachFileCalled = true
+                delegate.duplicatesStrategy "include"
+            }
+        }
+
+        expect:
+        project.copy {
+            copyRootCalled = true
+            into copyDest
+            with copySpec
+            from copySource
+            from copySource, {
+                delegate.duplicatesStrategy "include"
+                eachFile {
+                    copyNestedEachFileCalled = true
+                    delegate.duplicatesStrategy "include"
+                }
+            }
+            delegate.duplicatesStrategy "include"
+
+            eachFile {
+                delegate.duplicatesStrategy "include"
+                copyEachFileCalled = true
+            }
+        }
+
+        and:
+        copyRootCalled
+        copyEachFileCalled
+        copyNestedEachFileCalled
+        copySpecRootCalled
+        copySpecEachFileCalled
+        copySpecNestedEachFileCalled
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy
deleted file mode 100644
index 368a354..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal
-
-import spock.lang.Specification
-
-class AbstractMultiCauseExceptionTest extends Specification {
-    def getCauseReturnsTheFirstCause() {
-        def cause1 = new RuntimeException()
-        def cause2 = new RuntimeException()
-        def failure = new TestMultiCauseException('message', [cause1, cause2])
-
-        expect:
-        failure.cause == cause1
-        failure.causes == [cause1, cause2]
-    }
-
-    def getCauseReturnsNullWhenThereAreNoCauses() {
-        def failure = new TestMultiCauseException('message', [])
-
-        expect:
-        failure.cause == null
-        failure.causes == []
-    }
-
-    def canUseInitCauseToProvideCause() {
-        def cause1 = new RuntimeException()
-        def failure = new TestMultiCauseException('message', [])
-        failure.initCause(cause1)
-
-        expect:
-        failure.cause == cause1
-        failure.causes == [cause1]
-    }
-
-    def canUseInitCausesToProvideMultipleCause() {
-        def cause1 = new RuntimeException()
-        def cause2 = new RuntimeException()
-        def failure = new TestMultiCauseException('message', [])
-        failure.initCauses([cause1, cause2])
-
-        expect:
-        failure.cause == cause1
-        failure.causes == [cause1, cause2]
-    }
-
-    def printStackTraceWithMultipleCauses() {
-        RuntimeException cause1 = new RuntimeException('cause1')
-        RuntimeException cause2 = new RuntimeException('cause2')
-        def failure = new TestMultiCauseException('message', [cause1, cause2])
-        def outstr = new StringWriter()
-
-        when:
-        outstr.withPrintWriter { writer ->
-            failure.printStackTrace(writer)
-        }
-
-        then:
-        outstr.toString().contains("${TestMultiCauseException.name}: message")
-        outstr.toString().contains("Cause 1: ${RuntimeException.name}: cause1")
-        outstr.toString().contains("Cause 2: ${RuntimeException.name}: cause2")
-    }
-    
-    def printStackTraceWithSingleCause() {
-        RuntimeException cause1 = new RuntimeException('cause1')
-        def failure = new TestMultiCauseException('message', [cause1])
-        def outstr = new StringWriter()
-
-        when:
-        outstr.withPrintWriter { writer ->
-            failure.printStackTrace(writer)
-        }
-
-        then:
-        outstr.toString().contains("${TestMultiCauseException.name}: message")
-        outstr.toString().contains("Caused by: ${RuntimeException.name}: cause1")
-    }
-}
-
-class TestMultiCauseException extends AbstractMultiCauseException {
-    TestMultiCauseException(String message, Iterable<? extends Throwable> causes) {
-        super(message, causes)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
index 75d6cf1..9640f27 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
@@ -15,126 +15,192 @@
  */
 package org.gradle.api.internal
 
-import org.junit.Ignore
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-import static org.junit.Assert.fail
-import org.gradle.internal.reflect.Instantiator
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
 import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
 
-class AbstractNamedDomainObjectContainerTest {
-    private final Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
-    private final AbstractNamedDomainObjectContainer container = instantiator.newInstance(TestContainer.class, instantiator)
+class AbstractNamedDomainObjectContainerTest extends Specification {
+    Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+    AbstractNamedDomainObjectContainer container = instantiator.newInstance(TestContainer.class, instantiator)
 
-    @Test
-    public void isDynamicObjectAware() {
-        assertThat(container, instanceOf(DynamicObjectAware));
+    def "is dynamic object aware"() {
+        expect:
+        container instanceof DynamicObjectAware
     }
 
-    @Test
-    public void canAddObjectWithName() {
-        container.create('obj')
-        assertThat(container.getByName('obj'), equalTo(['obj']))
+    def "can create object by name"() {
+        when:
+        def obj = container.create('obj')
+
+        then:
+        container.getByName('obj') == obj
+        container.findByName('obj') == obj
+        container.obj == obj
+        container['obj'] == obj
     }
 
-    @Test
-    public void canAddAndConfigureAnObjectWithName() {
+    def "can create and configure object using closure"() {
+        when:
         container.create('obj') {
-            add(1)
-            add('value')
+            prop = 'value'
         }
-        assertThat(container.getByName('obj'), equalTo(['obj', 1, 'value']))
+
+        then:
+        container.obj.prop == 'value'
     }
 
-    @Test
-    public void canUseMaybeCreateToFindOrCreateObjectWithName() {
-        def created = container.maybeCreate('obj')
-        assertThat(container.getByName('obj'), equalTo(['obj']))
+    def "can create and configure object using action"() {
+        def action = Mock(Action)
+
+        given:
+        action.execute(_) >> { TestObject obj ->
+            obj.prop = 'value'
+        }
+
+        when:
+        container.create('obj', action)
+
+        then:
+        container.obj.prop == 'value'
+    }
 
+    def "can use 'maybeCreate' to find or create object by name"() {
+        when:
+        def created = container.maybeCreate('obj')
         def fetched = container.maybeCreate('obj')
-        assertThat(fetched, sameInstance(created))
+
+        then:
+        fetched.is(created)
     }
 
-    @Test
-    public void failsToAddObjectWhenObjectWithSameNameAlreadyInContainer() {
+    def "creation fails if object with same name already exists"() {
         container.create('obj')
 
-        try {
-            container.create('obj')
-            fail()
-        } catch (org.gradle.api.InvalidUserDataException e) {
-            assertThat(e.message, equalTo('Cannot add a TestObject with name \'obj\' as a TestObject with that name already exists.'))
-        }
+        when:
+        container.create('obj')
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == 'Cannot add a TestObject with name \'obj\' as a TestObject with that name already exists.'
     }
 
-    @Test
-    public void canConfigureExistingObject() {
+    def "can configure existing object"() {
         container.create('list1')
+
+        when:
         container.configure {
-            list1 { add(1) }
+            someObj { prop = 'value' }
         }
-        assertThat(container.list1, equalTo(['list1', 1]))
+
+        then:
+        container.someObj.prop == 'value'
     }
 
-    @Test
-    public void propagatesNestedMissingMethodException() {
-        container.create('list1')
-        try {
-            container.configure {
-                list1 { unknown { anotherUnknown(2) } }
-            }
-        } catch (groovy.lang.MissingMethodException e) {
-            assertThat(e.method, equalTo('unknown'))
-            assertThat(e.type, equalTo(TestObject))
+    def "propagates nested MissingMethodException"() {
+        container.create('someObj')
+
+        when:
+        container.configure {
+            someObj { unknown { anotherUnknown(2) } }
         }
+
+        then:
+        groovy.lang.MissingMethodException e = thrown()
+        e.method == 'unknown'
+        e.type == TestObject
     }
 
-    @Test
-    public void propagatesMethodInvocationException() {
+    def "propagates method invocation exception"() {
         RuntimeException failure = new RuntimeException()
-        try {
-            container.configure {
-                list1 { throw failure }
-            }
-        } catch (RuntimeException e) {
-            assertThat(e, sameInstance(failure))
+
+        when:
+        container.configure {
+            someObj { throw failure }
+        }
+
+        then:
+        RuntimeException e = thrown()
+        e.is(failure)
+    }
+
+    def "implicitly creates an object when container is being configured"() {
+        when:
+        container.configure {
+            obj1
+            obj2 { prop = 'value' }
         }
+
+        then:
+        container.obj1.prop == null
+        container.obj2.prop == 'value'
     }
 
-    @Test
-    public void implicitlyAddsAnObjectWhenContainerIsBeingConfigured() {
+    def "does not implicitly create an object when container is not being configured"() {
+        when:
+        container.obj1
+
+        then:
+        MissingPropertyException missingProp = thrown()
+        missingProp.property == 'obj1'
+
+        when:
+        container.obj2 { }
+
+        then:
+        MissingMethodException missingMethod = thrown()
+        missingMethod.method == 'obj2'
+
+        when:
         container.configure {
-            list1
-            list2 { add(1) }
+            element {
+                nested
+            }
         }
-        assertThat(container.list1, equalTo(['list1']))
-        assertThat(container.list2, equalTo(['list2', 1]))
+
+        then:
+        missingProp = thrown()
+        missingProp.property == 'nested'
+
+        when:
+        container.configure {
+            element {
+                prop = nested
+            }
+        }
+
+        then:
+        missingProp = thrown()
+        missingProp.property == 'nested'
     }
 
-    @Test
-    public void canReferToPropertiesAndMethodsOfOwner() {
-        new DynamicOwner().configure(container)
-        assertThat(container.asMap.keySet(), equalTo(['list1', 'list2'] as Set))
-        assertThat(container.list1, equalTo(['list1', 'dynamicProp', 'ownerProp', 'ownerMethod', 'dynamicMethod', 'dynamicMethod', 1, 'prop', 'testObjectDynamicMethod']))
-        assertThat(container.list1.prop, equalTo('prop'))
-        assertThat(container.list2, equalTo(['list2', container.list1]))
-    }
-
-    @Test @Ignore
-    public void canUseAnItemCalledMainInAScript() {
-        Script script = new GroovyShell().parse("""import org.gradle.util.ConfigureUtil
-            c.configure {
-                run
-                main { add(1) }
+    def "can nest containers"() {
+        when:
+        container.configure {
+            someObj {
+                children {
+                    child1 { prop = 'child1' }
+                    child2
+                }
             }
+        }
 
-""")
-        script.getBinding().setProperty("c", container)
-        script.run()
+        then:
+        container.names == ['someObj'] as SortedSet
+        container.someObj.prop == null
+        container.someObj.children.names == ['child1', 'child2'] as SortedSet
+        container.someObj.children.child1.prop == 'child1'
+        container.someObj.children.child2.prop == null
+    }
 
-        assertThat(container.run, equalTo(['run']))
-        assertThat(container.main, equalTo(['main', 1]))
+    def "can refer to properties and methods of owner"() {
+        new DynamicOwner().configure(container)
+
+        expect:
+        container.asMap.keySet() == ['list1', 'list2'] as Set
+        container.list1.prop == 'list1'
+        container.list2.prop == 'list2'
     }
 }
 
@@ -176,30 +242,37 @@ class DynamicOwner {
             list1 {
                 // owner properties and methods - owner is a DynamicOwner
                 dynamicProp = 'dynamicProp'
-                add dynamicProp
-                add ownerProp
-                add ownerMethod('ownerMethod')
-                add dynamicMethod('a', 'b', 'c')
-                add dynamicMethod { doesntGetEvaluated }
+                assert dynamicProp == 'dynamicProp'
+                assert ownerProp == 'ownerProp'
+                assert ownerMethod('ownerMethod') == 'ownerMethod'
+                assert dynamicMethod('a', 'b', 'c') == 'dynamicMethod'
+                assert dynamicMethod { doesntGetEvaluated } == 'dynamicMethod'
                 // delegate properties and methods - delegate is a TestObject
-                add owner.size()
-                prop = 'prop'
-                add prop
-                testObjectDynamicMethod { doesntGetEvaluated }
+                prop = 'list1'
+                assert testObjectDynamicMethod { doesntGetEvaluated } == 'testObjectDynamicMethod'
             }
             list2 {
-                add list1
+                prop = 'list2'
             }
         }
     }
 }
 
-class TestObject extends ArrayList<String> {
-    def String prop
+class TestObject {
+    String prop
     String name
+    final children
+
+    TestObject(Instantiator instantiator) {
+        children = instantiator.newInstance(TestContainer, instantiator)
+    }
+
+    def children(Closure cl) {
+        children.configure(cl)
+    }
+
     def methodMissing(String name, Object params) {
         if (name == 'testObjectDynamicMethod') {
-            add(name)
             return name
         }
         throw new groovy.lang.MissingMethodException(name, getClass(), params)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy
index d7607f0..c8d5ae1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy
@@ -25,7 +25,7 @@ import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.DefaultServiceRegistry
 import org.gradle.util.GUtil
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Test
 import spock.lang.Specification
 
@@ -42,7 +42,7 @@ class AbstractTaskSpec extends Specification {
     }
 
     public Task createTask(String name) {
-        AbstractProject project = HelperUtil.createRootProject();
+        AbstractProject project = TestUtil.createRootProject();
         DefaultServiceRegistry registry = new DefaultServiceRegistry();
         registry.add(Instantiator.class, new DirectInstantiator());
         Task task = rootFactory.createChild(project, instantiator).createTask(GUtil.map(Task.TASK_TYPE, TestTask.class, Task.TASK_NAME, name));
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
index 5523da1..02f5d4c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorGroovyTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorGroovyTest.groovy
@@ -16,11 +16,14 @@
 
 package org.gradle.api.internal
 
+import org.gradle.api.Action
+import org.gradle.api.NonExtensible
+import org.gradle.api.plugins.ExtensionAware
 import org.gradle.internal.reflect.DirectInstantiator
-import spock.lang.Specification
-import spock.lang.Issue
+import org.gradle.internal.typeconversion.TypeConversionException
 import org.gradle.util.ConfigureUtil
-import org.gradle.api.Action
+import spock.lang.Issue
+import spock.lang.Specification
 
 class AsmBackedClassGeneratorGroovyTest extends Specification {
 
@@ -138,9 +141,164 @@ class AsmBackedClassGeneratorGroovyTest extends Specification {
         tester.lastArgs.last().is(closure)
     }
 
+    def "can coerce enum values"() {
+        given:
+        def i = create(EnumCoerceTestSubject)
+
+        when:
+        i.enumProperty = "abc"
+
+        then:
+        i.enumProperty == TestEnum.ABC
+
+        when:
+        i.someEnumMethod("DEF")
+
+        then:
+        i.enumProperty == TestEnum.DEF
+
+        when:
+        i.enumProperty "abc"
+
+        then:
+        i.enumProperty == TestEnum.ABC
+
+        when:
+        i.enumProperty "foo"
+
+        then:
+        thrown TypeConversionException
+
+        when:
+        i.enumMethodWithStringOverload("foo")
+
+        then:
+        i.stringValue == "foo"
+
+        when:
+        i.enumMethodWithStringOverload(TestEnum.DEF)
+
+        then:
+        i.enumProperty == TestEnum.DEF
+    }
+
+    def "can call methods during construction"() {
+        /*
+            We route all methods through invokeMethod, which requires fields
+            added in the subclass. We have special handling for the case where
+            methods are called before this field has been initialised; this tests that.
+         */
+        when:
+        def i = create(CallsMethodDuringConstruction)
+
+        then:
+        i.setDuringConstructor == i.class
+        i.setAtFieldInit == i.class
+    }
+
+    def "can call private methods internally"() {
+        /*
+            We have to specially handle private methods in our dynamic protocol.
+         */
+        given:
+        def i = create(CallsPrivateMethods)
+
+        when:
+        i.flagCalled("a")
+
+        then:
+        i.calledWith == String
+
+        when:
+        i.flagCalled(1.2)
+
+        then:
+        i.calledWith == Number
+
+        when:
+        i.flagCalled([])
+
+        then:
+        i.calledWith == Object
+
+        when:
+        i.flagCalled(1)
+
+        then:
+        i.calledWith == Integer
+    }
+
+
+    def "can use non extensible objects"() {
+        def i = create(NonExtensibleObject)
+
+        when:
+        i.testEnum "ABC"
+
+        then:
+        i.testEnum == TestEnum.ABC
+
+        !(TestEnum instanceof ExtensionAware)
+        !(TestEnum instanceof IConventionAware)
+        !(TestEnum instanceof HasConvention)
+
+        when:
+        i.ext.foo = "bar"
+
+        then:
+        def e = thrown(MissingPropertyException)
+        e.property == "ext"
+    }
+
     def conf(o, c) {
         ConfigureUtil.configure(c, o)
     }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2863")
+    def "checked exceptions from private methods are thrown"() {
+            when:
+        create(CallsPrivateMethods).callsPrivateThatThrowsCheckedException("1")
+
+        then:
+        thrown IOException
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2863")
+    def "private methods are called with Groovy semantics"() {
+        when:
+        def foo = "bar"
+        def obj = create(CallsPrivateMethods)
+
+        then:
+        obj.callsPrivateStringMethodWithGString("$foo") == "BAR"
+    }
+}
+
+enum TestEnum {
+    ABC, DEF
+}
+
+class EnumCoerceTestSubject {
+    TestEnum enumProperty
+
+    String stringValue
+
+    void someEnumMethod(TestEnum testEnum) {
+        this.enumProperty = testEnum
+    }
+
+    void enumMethodWithStringOverload(TestEnum testEnum) {
+        enumProperty = testEnum
+    }
+
+    void enumMethodWithStringOverload(String stringValue) {
+        this.stringValue = stringValue
+    }
+}
+
+ at NonExtensible
+class NonExtensibleObject {
+    TestEnum testEnum
 }
 
 class DynamicThing {
@@ -209,7 +367,62 @@ class ActionsTester {
         lastMethod = "hasClosure"
         lastArgs = [s, closure]
     }
+}
+
+class CallsMethodDuringConstruction {
 
+    Class setAtFieldInit = getClass()
+    Class setDuringConstructor
 
+    CallsMethodDuringConstruction() {
+        setDuringConstructor = getClass()
+    }
+}
 
-}
\ No newline at end of file
+class CallsPrivateMethods {
+
+    Class calledWith
+
+    void flagCalled(arg) {
+        doFlagCalled(arg)
+    }
+
+    private doFlagCalled(String s) {
+        calledWith = String
+    }
+
+    private doFlagCalled(Number s) {
+        calledWith = Number
+    }
+
+    private doFlagCalled(Integer s) {
+        calledWith = Integer
+    }
+
+    private doFlagCalled(Object s) {
+        calledWith = Object
+    }
+
+    // It's important here that we take an untyped arg, and call a method that types a typed arg
+    // See http://issues.gradle.org/browse/GRADLE-2863
+    def callsPrivateThatThrowsCheckedException(s) {
+        try {
+            throwsCheckedException(s)
+        } catch (Exception e) {
+            assert e instanceof IOException
+            throw e
+        }
+    }
+
+    private throwsCheckedException(String a) {
+        throw new IOException("!")
+    }
+
+    def callsPrivateStringMethodWithGString(GString gString) {
+        upperCaser(gString)
+    }
+
+    private upperCaser(String str) {
+        str.toUpperCase()
+    }
+}
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 7e86531..844b607 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
@@ -17,6 +17,7 @@ package org.gradle.api.internal;
 
 import groovy.lang.Closure;
 import groovy.lang.GroovyObject;
+import groovy.lang.MissingMethodException;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.JavaVersion;
@@ -35,8 +36,8 @@ import java.lang.reflect.*;
 import java.util.*;
 import java.util.concurrent.Callable;
 
-import static org.gradle.util.HelperUtil.TEST_CLOSURE;
-import static org.gradle.util.HelperUtil.call;
+import static org.gradle.util.TestUtil.TEST_CLOSURE;
+import static org.gradle.util.TestUtil.call;
 import static org.gradle.util.Matchers.isEmpty;
 import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.*;
@@ -645,6 +646,21 @@ public class AsmBackedClassGeneratorTest {
     }
 
     @Test
+    public void addsInsteadOfOverridesSetValueMethodIfOnlyMultiArgMethods() throws Exception {
+        BeanWithMultiArgDslMethods bean = generator.generate(BeanWithMultiArgDslMethods.class).newInstance();
+        // this method should have been added to the class
+        call("{ it.prop 'value'}", bean);
+        assertThat(bean.getProp(), equalTo("value"));
+    }
+
+    @Test
+    public void doesNotOverrideSetValueMethodForPropertyThatIsNotConventionMappingAware() throws Exception {
+        BeanWithMultiArgDslMethodsAndNoConventionMapping bean = generator.generate(BeanWithMultiArgDslMethodsAndNoConventionMapping.class).newInstance();
+        call("{ it.prop 'value'}", bean);
+        assertThat(bean.getProp(), equalTo("(value)"));
+    }
+
+    @Test
     public void mixesInClosureOverloadForActionMethod() throws Exception {
         Bean bean = generator.generate(Bean.class).newInstance();
         bean.prop = "value";
@@ -788,6 +804,53 @@ public class AsmBackedClassGeneratorTest {
         }
     }
 
+    public static class BeanWithMultiArgDslMethods extends Bean {
+        private String prop;
+
+        public String getProp() {
+            return prop;
+        }
+
+        public void setProp(String prop) {
+            this.prop = prop;
+        }
+
+        public BeanWithMultiArgDslMethods prop(String part1, String part2) {
+            this.prop = String.format("<%s%s>", part1, part2);
+            return this;
+        }
+
+        public BeanWithMultiArgDslMethods prop(String part1, String part2, String part3) {
+            this.prop = String.format("[%s%s%s]", part1, part2, part3);
+            return this;
+        }
+    }
+
+    @NoConventionMapping
+    public static class BeanWithMultiArgDslMethodsAndNoConventionMapping extends Bean {
+        private String prop;
+
+        public String getProp() {
+            return prop;
+        }
+
+        public void setProp(String prop) {
+            this.prop = prop;
+        }
+
+        public void prop(String value) {
+            this.prop = String.format("(%s)", value);
+        }
+
+        public void prop(String part1, String part2) {
+            this.prop = String.format("<%s%s>", part1, part2);
+        }
+
+        public void prop(String part1, String part2, String part3) {
+            this.prop = String.format("[%s%s%s]", part1, part2, part3);
+        }
+    }
+
     public static class ConventionAwareBean extends Bean implements IConventionAware, ConventionMapping {
         public Convention getConvention() {
             throw new UnsupportedOperationException();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/CachingDirectedGraphWalkerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/CachingDirectedGraphWalkerTest.groovy
deleted file mode 100644
index 26da809..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/CachingDirectedGraphWalkerTest.groovy
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal
-
-import spock.lang.Specification
-
-class CachingDirectedGraphWalkerTest extends Specification {
-    private final DirectedGraphWithEdgeValues<Integer, String> graph = Mock()
-    private final CachingDirectedGraphWalker walker = new CachingDirectedGraphWalker(graph)
-
-    def collectsValuesForASingleNode() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1' }
-        0 * _._
-        values == ['1'] as Set
-    }
-
-    def collectsValuesForEachSuccessorNode() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2; args[2] << 3 }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
-        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
-        3 * graph.getEdgeValues(_, _, _)
-        0 * _._
-        values == ['1', '2', '3'] as Set
-    }
-
-    def collectsValuesForEachEdgeTraversed() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 2; args[2] << 3 }
-        1 * graph.getEdgeValues(1, 2, _) >> { args -> args[2] << '1->2' }
-        1 * graph.getEdgeValues(1, 3, _) >> { args -> args[2] << '1->3' }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 3 }
-        1 * graph.getEdgeValues(2, 3, _) >> { args -> args[2] << '2->3' }
-        1 * graph.getNodeValues(3, _, _)
-        0 * _._
-        values == ['1->2', '1->3', '2->3'] as Set
-    }
-
-    def collectsValuesForAllStartNodes() {
-        when:
-        walker.add(1, 2)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 3 }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
-        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
-        2 * graph.getEdgeValues(_, _, _)
-        0 * _._
-        values == ['1', '2', '3'] as Set
-    }
-
-    def collectsValuesWhenCycleIsPresentInGraph() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2 }
-        1 * graph.getEdgeValues(1, 2, _) >> { args -> args[2] << '1->2' }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
-        1 * graph.getEdgeValues(2, 3, _) >> { args -> args[2] << '2->3' }
-        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 1 }
-        1 * graph.getEdgeValues(3, 1, _) >> { args -> args[2] << '3->1' }
-        0 * _._
-        values == ['1', '1->2', '2', '2->3', '3', '3->1'] as Set
-    }
-
-    def collectsValuesWhenNodeConnectedToItself() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 1 }
-        1 * graph.getEdgeValues(1, 1, _) >> { args -> args[2] << '1->1' }
-        0 * _._
-        values == ['1', '1->1'] as Set
-    }
-
-    def collectsValuesWhenMultipleCyclesInGraph() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 1; args[2] << 2 }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3; args[2] << 4 }
-        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 2 }
-        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4' }
-        5 * graph.getEdgeValues(_, _, _) >> { args -> args[2] << "${args[0]}->${args[1]}".toString() }
-        0 * _._
-        values == ['1', '1->1', '1->2', '2', '2->3', '2->4', '3', '3->2', '4'] as Set
-    }
-
-    def canReuseWalkerForMultipleSearches() {
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2; args[2] << 3 }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
-        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
-        3 * graph.getEdgeValues(_, _, _)
-        0 * _._
-        values == ['1', '2', '3'] as Set
-
-        // Cached node (1) is reachable via 2 separate new paths (4->5->1 and 4->6->1)
-        when:
-        walker.add(4)
-        values = walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4'; args[2] << 5; args[2] << 6 }
-        1 * graph.getNodeValues(5, _, _) >> { args -> args[1] << '5'; args[2] << 1 }
-        1 * graph.getNodeValues(6, _, _) >> { args -> args[1] << '6'; args[2] << 1 }
-        4 * graph.getEdgeValues(_, _, _) >> { args -> args[2] << "${args[0]}->${args[1]}".toString() }
-        0 * _._
-        values == ['4', '4->5', '4->6', '5', '5->1', '6', '6->1', '1', '2', '3'] as Set
-
-        when:
-        walker.add(2)
-        values = walker.findValues()
-
-        then:
-        values == ['2', '3'] as Set
-    }
-
-    def canReuseWalkerWhenGraphContainsACycle() {
-        when:
-        walker.add(1)
-        walker.findValues()
-
-        then:
-        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2 }
-        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
-        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 1; args[2] << 4 }
-        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4'; args[2] << 2}
-        5 * graph.getEdgeValues(_, _, _)
-        0 * _._
-
-        when:
-        walker.add(1)
-        def values = walker.findValues()
-
-        then:
-        values == ['1', '2', '3', '4'] as Set
-
-        when:
-        walker.add(2)
-        values = walker.findValues()
-
-        then:
-        values == ['1', '2', '3', '4'] as Set
-
-        when:
-        walker.add(3)
-        values = walker.findValues()
-
-        then:
-        values == ['1', '2', '3', '4'] as Set
-
-        when:
-        walker.add(4)
-        values = walker.findValues()
-
-        then:
-        values == ['1', '2', '3', '4'] as Set
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
index a9c4c5a..1982ce8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal;
 
+import org.gradle.util.TestUtil;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.Expectations;
@@ -22,7 +23,6 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.gradle.util.HelperUtil;
 import org.gradle.api.Transformer;
 import groovy.lang.Closure;
 
@@ -59,7 +59,7 @@ public class ChainingTransformerTest {
 
     @Test
     public void canUseAClosureAsATransformer() {
-        Closure closure = HelperUtil.toClosure("{ it + ' transformed' }");
+        Closure closure = TestUtil.toClosure("{ it + ' transformed' }");
 
         transformer.add(closure);
 
@@ -68,7 +68,7 @@ public class ChainingTransformerTest {
 
     @Test
     public void usesOriginalObjectWhenClosureReturnsNull() {
-        Closure closure = HelperUtil.toClosure("{ null }");
+        Closure closure = TestUtil.toClosure("{ null }");
 
         transformer.add(closure);
 
@@ -77,7 +77,7 @@ public class ChainingTransformerTest {
 
     @Test
     public void usesOriginalObjectWhenClosureReturnsObjectOfUnexpectedType() {
-        Closure closure = HelperUtil.toClosure("{ 9 }");
+        Closure closure = TestUtil.toClosure("{ 9 }");
 
         transformer.add(closure);
 
@@ -86,7 +86,7 @@ public class ChainingTransformerTest {
 
     @Test
     public void originalObjectIsSetAsDelegateForClosure() {
-        Closure closure = HelperUtil.toClosure("{ substring(1, 3) }");
+        Closure closure = TestUtil.toClosure("{ substring(1, 3) }");
 
         transformer.add(closure);
 
@@ -95,7 +95,7 @@ public class ChainingTransformerTest {
     
     @Test
     public void closureCanTransformAStringIntoAGString() {
-        Closure closure = HelperUtil.toClosure("{ \"[$it]\" }");
+        Closure closure = TestUtil.toClosure("{ \"[$it]\" }");
 
         transformer.add(closure);
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy
index 379a394..1a8a981 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy
@@ -345,13 +345,12 @@ class CompositeDomainObjectSetTest extends Specification {
 
         and:
         component1 << "a" << "c"
-        component2 << "a" << "d"
+        component2 << "a" << "d" << "a"
 
         then:
-        calledFor == ["c", "d"]
+        calledFor == ["a", "c", "d"]
     }
 
-    @IgnoreRest
     def "all notifications are only fired once for each in composite"() {
         given:
         def component1 = collection("a")
@@ -400,4 +399,28 @@ class CompositeDomainObjectSetTest extends Specification {
         thrown UnsupportedOperationException
     }
 
+    def "behaves when the same collection added"() {
+        def same = collection("a", "b")
+        def composite = composite(same, same, same)
+
+        expect:
+        composite.toList() == ['a', 'b']
+
+        when:
+        same << 'c'
+
+        then:
+        composite.toList() == ['a', 'b', 'c']
+    }
+
+    def "removing collection removes all instances"() {
+        def instance = collection("a", "b")
+        def composite = composite(instance, instance)
+
+        when:
+        composite.removeCollection(instance)
+
+        then:
+        composite.toList() == []
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
index 4cd4991..3be760a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal;
 
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.TestTask;
 import org.junit.Before;
 import org.junit.Test;
@@ -33,28 +33,25 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 public class ConventionAwareHelperTest {
     ConventionAwareHelper conventionAware;
 
     TestTask testTask;
 
     @Before public void setUp() {
-        testTask = HelperUtil.createTask(TestTask.class);
+        testTask = TestUtil.createTask(TestTask.class);
         conventionAware = new ConventionAwareHelper(testTask);
     }
 
     @Test
     public void canMapPropertiesUsingClosure() {
-        conventionAware.map("list1", HelperUtil.toClosure("{ ['a'] }"));
+        conventionAware.map("list1", TestUtil.toClosure("{ ['a'] }"));
         assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList("a")));
 
-        conventionAware.map("list1", HelperUtil.toClosure("{ convention -> [convention] }"));
+        conventionAware.map("list1", TestUtil.toClosure("{ convention -> [convention] }"));
         assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList(conventionAware.getConvention())));
 
-        conventionAware.map("list1", HelperUtil.toClosure("{ convention, object -> [convention, object] }"));
+        conventionAware.map("list1", TestUtil.toClosure("{ convention, object -> [convention, object] }"));
         assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList(conventionAware.getConvention(), testTask)));
     }
 
@@ -72,7 +69,7 @@ public class ConventionAwareHelperTest {
     
     @Test
     public void canSetMappingUsingDynamicProperty() {
-        HelperUtil.call("{ it.list1 = { ['a'] } }", conventionAware);
+        TestUtil.call("{ it.list1 = { ['a'] } }", conventionAware);
         assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList("a")));
     }
     
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java
index 5408f9c..95101d1 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal;
 import org.gradle.api.Action;
 import org.gradle.api.DomainObjectCollection;
 import org.gradle.api.specs.Spec;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.TestClosure;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
@@ -121,7 +121,7 @@ public class DefaultDomainObjectCollectionTest {
         container.add("c");
 
         assertThat(toList(container.matching(spec)), equalTo(toList((CharSequence) "a", "c")));
-        assertThat(toList(container.matching(HelperUtil.toClosure(testClosure))), equalTo(toList((CharSequence) "a", "c")));
+        assertThat(toList(container.matching(TestUtil.toClosure(testClosure))), equalTo(toList((CharSequence) "a", "c")));
     }
 
     @Test
@@ -162,7 +162,7 @@ public class DefaultDomainObjectCollectionTest {
             one(closure).call("a");
         }});
         
-        container.withType(String.class, HelperUtil.toClosure(closure));
+        container.withType(String.class, TestUtil.toClosure(closure));
         container.add("a");
     }
 
@@ -220,7 +220,7 @@ public class DefaultDomainObjectCollectionTest {
             }
         };
 
-        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
+        container.matching(spec).whenObjectAdded(TestUtil.toClosure(closure));
 
         container.add("a");
         container.add("b");
@@ -254,7 +254,7 @@ public class DefaultDomainObjectCollectionTest {
         container.add("b");
         container.add("c");
 
-        Collection<CharSequence> collection = container.findAll(HelperUtil.toClosure("{ it != 'b' }"));
+        Collection<CharSequence> collection = container.findAll(TestUtil.toClosure("{ it != 'b' }"));
         assertThat(collection, instanceOf(List.class));
         assertThat(collection, equalTo((Collection) toList("a", "c")));
     }
@@ -265,7 +265,7 @@ public class DefaultDomainObjectCollectionTest {
         container.add("b");
         container.add("c");
 
-        Collection<CharSequence> collection = container.findAll(HelperUtil.toClosure("{ it != 'b' }"));
+        Collection<CharSequence> collection = container.findAll(TestUtil.toClosure("{ it != 'b' }"));
 
         container.add("d");
         assertThat(collection, equalTo((Collection) toList("a", "c")));
@@ -292,7 +292,7 @@ public class DefaultDomainObjectCollectionTest {
             one(closure).call("a");
         }});
 
-        container.whenObjectAdded(HelperUtil.toClosure(closure));
+        container.whenObjectAdded(TestUtil.toClosure(closure));
         container.add("a");
     }
 
@@ -306,13 +306,13 @@ public class DefaultDomainObjectCollectionTest {
             one(closure).call("a");
         }});
 
-        container.whenObjectRemoved(HelperUtil.toClosure(closure));
+        container.whenObjectRemoved(TestUtil.toClosure(closure));
         container.remove("a");
     }
 
     @Test
     public void callsClosureWithNewObjectAsDelegateWhenObjectAdded() {
-        container.whenObjectAdded(HelperUtil.toClosure("{ assert delegate == 'a' }"));
+        container.whenObjectAdded(TestUtil.toClosure("{ assert delegate == 'a' }"));
         container.add("a");
     }
 
@@ -373,7 +373,7 @@ public class DefaultDomainObjectCollectionTest {
         }});
 
         container.add("a");
-        container.all(HelperUtil.toClosure(closure));
+        container.all(TestUtil.toClosure(closure));
     }
 
     @Test
@@ -397,13 +397,13 @@ public class DefaultDomainObjectCollectionTest {
             one(closure).call("a");
         }});
 
-        container.all(HelperUtil.toClosure(closure));
+        container.all(TestUtil.toClosure(closure));
         container.add("a");
     }
 
     @Test
     public void allCallsClosureWithObjectAsDelegate() {
-        container.all(HelperUtil.toClosure(" { assert delegate == 'a' } "));
+        container.all(TestUtil.toClosure(" { assert delegate == 'a' } "));
         container.add("a");
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollectionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollectionTest.groovy
new file mode 100644
index 0000000..7565454
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollectionTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal
+
+import org.gradle.api.Namer
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+class DefaultNamedDomainObjectCollectionTest extends Specification {
+
+    private final Namer<Bean> namer = new Namer<Bean>() {
+        public String determineName(Bean bean) { return bean.name; }
+    };
+
+    Instantiator instantiator = Mock(Instantiator)
+
+    private final DefaultNamedDomainObjectCollection<Bean> container = new DefaultNamedDomainObjectCollection<CharSequence>(Bean, new HashSet<>(), instantiator, namer);
+    Set<Bean> store
+
+    def setup() {
+        container.clear()
+    }
+
+    def "getNames"() {
+        expect:
+        container.getNames().isEmpty()
+
+        when:
+        container.add(new Bean("bean1"))
+        container.add(new Bean("bean2"))
+        container.add(new Bean("bean3"))
+        then:
+        container.names == ["bean1", "bean2", "bean3"] as SortedSet
+    }
+
+    private class Bean {
+        public final String name;
+
+        public Bean(String name) {
+            this.name = name;
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
index 514c216..3ea532d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
@@ -22,7 +22,7 @@ import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.GUtil;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.TestClosure;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -32,8 +32,8 @@ import org.junit.runner.RunWith;
 
 import java.util.Iterator;
 
-import static org.gradle.util.HelperUtil.call;
-import static org.gradle.util.HelperUtil.toClosure;
+import static org.gradle.util.TestUtil.call;
+import static org.gradle.util.TestUtil.toClosure;
 import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
@@ -167,7 +167,7 @@ public class DefaultNamedDomainObjectSetTest {
         container.add(bean3);
 
         assertThat(toList(container.matching(spec)), equalTo(toList(bean2, bean3)));
-        assertThat(toList(container.matching(HelperUtil.toClosure(testClosure))), equalTo(toList(bean2, bean3)));
+        assertThat(toList(container.matching(TestUtil.toClosure(testClosure))), equalTo(toList(bean2, bean3)));
         assertThat(container.matching(spec).findByName("a"), nullValue());
         assertThat(container.matching(spec).findByName("b"), sameInstance(bean2));
     }
@@ -234,7 +234,7 @@ public class DefaultNamedDomainObjectSetTest {
             one(closure).call(bean2);
         }});
 
-        container.withType(OtherBean.class, HelperUtil.toClosure(closure));
+        container.withType(OtherBean.class, TestUtil.toClosure(closure));
     }
 
     @Test
@@ -306,7 +306,7 @@ public class DefaultNamedDomainObjectSetTest {
             }
         };
 
-        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
+        container.matching(spec).whenObjectAdded(TestUtil.toClosure(closure));
 
         container.add(bean);
         container.add(new Bean());
@@ -461,7 +461,7 @@ public class DefaultNamedDomainObjectSetTest {
             one(closure).call(bean);
         }});
 
-        container.whenObjectAdded(HelperUtil.toClosure(closure));
+        container.whenObjectAdded(TestUtil.toClosure(closure));
         container.add(bean);
     }
 
@@ -552,7 +552,7 @@ public class DefaultNamedDomainObjectSetTest {
         }});
 
         container.add(bean);
-        container.all(HelperUtil.toClosure(closure));
+        container.all(TestUtil.toClosure(closure));
     }
 
     @Test
@@ -577,7 +577,7 @@ public class DefaultNamedDomainObjectSetTest {
             one(closure).call(bean);
         }});
 
-        container.all(HelperUtil.toClosure(closure));
+        container.all(TestUtil.toClosure(closure));
         container.add(bean);
     }
 
@@ -704,7 +704,7 @@ public class DefaultNamedDomainObjectSetTest {
     public void addRuleByClosure() {
         String testPropertyKey = "org.gradle.test.addRuleByClosure";
         String expectedTaskName = "someTaskName";
-        Closure ruleClosure = HelperUtil.toClosure(String.format("{ taskName -> System.setProperty('%s', '%s') }",
+        Closure ruleClosure = TestUtil.toClosure(String.format("{ taskName -> System.setProperty('%s', '%s') }",
                 testPropertyKey, expectedTaskName));
         container.addRule("description", ruleClosure);
         container.getRules().get(0).apply(expectedTaskName);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerBaseTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerBaseTest.groovy
new file mode 100644
index 0000000..cc0f716
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerBaseTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal
+
+import org.gradle.internal.reflect.Instantiator
+
+class DefaultPolymorphicDomainObjectContainerBaseTest extends AbstractNamedDomainObjectContainerTest {
+    def setup() {
+        container = instantiator.newInstance(PolymorphicTestContainer.class, instantiator)
+    }
+}
+
+class PolymorphicTestContainer extends AbstractPolymorphicDomainObjectContainer<TestObject> {
+    PolymorphicTestContainer(Instantiator instantiator) {
+        super(TestObject, instantiator, new DynamicPropertyNamer())
+    }
+
+    @Override
+    protected TestObject doCreate(String name) {
+        def testObject = new TestObject(instantiator)
+        testObject.name = name
+        testObject
+    }
+
+    @Override
+    protected <U extends TestObject> U doCreate(String name, Class<U> type) {
+        throw new UnsupportedOperationException("doCreate")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy
index 5c3b2a5..f097a0d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal
 import org.gradle.api.Named
 import org.gradle.api.NamedDomainObjectFactory
 import org.gradle.internal.reflect.Instantiator
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 
 import spock.lang.Specification
 
@@ -28,7 +28,7 @@ class DefaultPolymorphicDomainObjectContainerDslTest extends Specification {
     def agedFred = new DefaultAgeAwarePerson(name: "Fred", age: 42)
     def agedBarney = new DefaultAgeAwarePerson(name: "Barney", age: 42)
 
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
     def instantiator = project.services.get(Instantiator)
     def container = instantiator.newInstance(DefaultPolymorphicDomainObjectContainer, Person, instantiator)
 
@@ -85,17 +85,13 @@ class DefaultPolymorphicDomainObjectContainerDslTest extends Specification {
         container.asDynamicObject.getProperty("Barney") == barney
     }
 
-    def "configure elements with default type"() {
-
-    }
-
     def "create elements with specified type"() {
         container.registerFactory(Person, { new DefaultPerson(name: it) } as NamedDomainObjectFactory)
         container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
 
         when:
         project.container {
-            Fred(Person) {}
+            Fred(Person)
             Barney(AgeAwarePerson) {}
         }
 
@@ -105,6 +101,84 @@ class DefaultPolymorphicDomainObjectContainerDslTest extends Specification {
         container.findByName("Barney") == agedBarney
         container.asDynamicObject.getProperty("Fred") == fred
         container.asDynamicObject.getProperty("Barney") == agedBarney
+    }
+
+    def "configure elements with default type"() {
+        container.registerDefaultFactory({ new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
+
+        when:
+        project.container {
+            Fred {
+                age = 11
+            }
+            Barney {
+                age = 22
+            }
+        }
 
+        then:
+        container.size() == 2
+        container.findByName("Fred").age == 11
+        container.findByName("Barney").age == 22
+    }
+
+    def "configure elements with specified type"() {
+        container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
+
+        when:
+        project.container {
+            Fred(AgeAwarePerson) {
+                age = 11
+            }
+            Barney(AgeAwarePerson) {
+                age = 22
+            }
+        }
+
+        then:
+        container.size() == 2
+        container.findByName("Fred").age == 11
+        container.findByName("Barney").age == 22
+    }
+
+    def "configure same element multiple times"() {
+        container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
+
+        when:
+        project.container {
+            Fred(AgeAwarePerson) {
+                age = 11
+            }
+            Barney(AgeAwarePerson) {
+                age = 22
+            }
+            Fred(AgeAwarePerson) {
+                age = 33
+            }
+            Barney(AgeAwarePerson) {
+                age = 44
+            }
+        }
+
+        then:
+        container.size() == 2
+        container.findByName("Fred").age == 33
+        container.findByName("Barney").age == 44
+    }
+
+    def "create elements without configuration"() {
+        container.registerDefaultFactory({ new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
+        container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 43) } as NamedDomainObjectFactory)
+
+        when:
+        project.container {
+            Fred
+            Barney(AgeAwarePerson)
+        }
+
+        then:
+        container.size() == 2
+        container.findByName("Fred").age == 42
+        container.findByName("Barney").age == 43
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy
index 1f1ad01..cc42e6f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy
@@ -63,6 +63,18 @@ class DefaultPolymorphicDomainObjectContainerTest extends Specification {
         }
     }
 
+    interface UnnamedPerson {} // doesn't implement Named
+
+    static class DefaultUnnamedPerson {}
+
+    interface CtorNamedPerson extends Person {}
+
+    static class DefaultCtorNamedPerson extends DefaultPerson implements CtorNamedPerson {
+        DefaultCtorNamedPerson(String name) {
+            this.name = name
+        }
+    }
+
     def "add elements"() {
         when:
         container.add(fred)
@@ -87,6 +99,19 @@ class DefaultPolymorphicDomainObjectContainerTest extends Specification {
         container.asDynamicObject.getProperty("barney") == barney
     }
 
+    def "maybe create elements without specifying type"() {
+        container.registerDefaultFactory({ new DefaultPerson(name: it) } as NamedDomainObjectFactory )
+
+        when:
+        def first = container.maybeCreate("fred")
+        def second = container.maybeCreate("fred")
+
+        then:
+        container.size() == 1
+        container.findByName("fred") == fred
+        first == second
+    }
+
     def "throws meaningful exception if it doesn't support creating domain objects without specifying a type"() {
         container = new DefaultPolymorphicDomainObjectContainer<Person>(Person, new DirectInstantiator())
 
@@ -95,10 +120,11 @@ class DefaultPolymorphicDomainObjectContainerTest extends Specification {
 
         then:
         InvalidUserDataException e = thrown()
-        e.message == "This container does not support creating domain objects without specifying a type."
+        e.message == "Cannot create a Person named 'fred' because this container does not support creating " +
+                "elements by name alone. Please specify which subtype of Person to create. Known subtypes are: (None)"
     }
 
-    def "create elements with specified type"() {
+    def "create elements with specified type based on NamedDomainObjectFactory"() {
         container.registerFactory(Person, { new DefaultPerson(name: it) } as NamedDomainObjectFactory)
         container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
 
@@ -114,6 +140,70 @@ class DefaultPolymorphicDomainObjectContainerTest extends Specification {
         container.asDynamicObject.getProperty("barney") == agedBarney
     }
 
+    def "create elements with specified type based on closure-based factory"() {
+        container.registerFactory(Person, { new DefaultPerson(name: it) })
+        container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) })
+
+        when:
+        container.create("fred", Person)
+        container.create("barney", AgeAwarePerson)
+
+        then:
+        container.size() == 2
+        container.findByName("fred") == fred
+        container.findByName("barney") == agedBarney
+        container.asDynamicObject.getProperty("fred") == fred
+        container.asDynamicObject.getProperty("barney") == agedBarney
+    }
+
+    def "create elements with specified type based on type binding"() {
+        container = new DefaultPolymorphicDomainObjectContainer<?>(Object, new DirectInstantiator(),
+                { it instanceof Named ? it.name : "unknown" } as Named.Namer)
+
+        container.registerBinding(UnnamedPerson, DefaultUnnamedPerson)
+        container.registerBinding(CtorNamedPerson, DefaultCtorNamedPerson)
+
+        when:
+        container.create("fred", UnnamedPerson)
+        container.create("barney", CtorNamedPerson)
+
+        then:
+        container.size() == 2
+        !container.findByName("fred")
+        with(container.findByName("unknown")) {
+            it.getClass() == DefaultUnnamedPerson
+        }
+        with(container.findByName("barney")) {
+            it.getClass() == DefaultCtorNamedPerson
+            name == "barney"
+        }
+    }
+
+    def "maybe create elements with specified type"() {
+        container.registerFactory(Person, { new DefaultPerson(name: it) } as NamedDomainObjectFactory)
+
+        when:
+        def first = container.maybeCreate("fred", Person)
+        def second = container.maybeCreate("fred", Person)
+
+        then:
+        container.size() == 1
+        container.findByName("fred") == fred
+        first == second
+    }
+
+    def "throws meaningful exception if element with same name exists with incompatible type"() {
+        container.registerFactory(Person, { new DefaultPerson(name: it) } as NamedDomainObjectFactory)
+        container.create("fred", Person)
+
+        when:
+        container.maybeCreate("fred", AgeAwarePerson)
+
+        then:
+        ClassCastException e = thrown()
+        e.message == "Failed to cast object fred of type ${DefaultPerson.class.name} to target type ${AgeAwarePerson.class.name}"
+    }
+
     def "throws meaningful exception if it doesn't support creating domain objects with the specified type"() {
         container = new DefaultPolymorphicDomainObjectContainer<Person>(Person, new DirectInstantiator())
 
@@ -122,8 +212,7 @@ class DefaultPolymorphicDomainObjectContainerTest extends Specification {
 
         then:
         InvalidUserDataException e = thrown()
-        e.message == "This container does not support creating domain objects of type " +
-                "'org.gradle.api.internal.DefaultPolymorphicDomainObjectContainerTest\$Person'."
+        e.message == "Cannot create a Person because this type is not known to this container. Known types are: (None)"
     }
 
     def "throws meaningful exception if factory element type is not a subtype of container element type"() {
@@ -132,8 +221,8 @@ class DefaultPolymorphicDomainObjectContainerTest extends Specification {
 
         then:
         IllegalArgumentException e = thrown()
-        e.message == "Factory element type 'java.lang.String' is not a subtype of container element type " +
-                "'org.gradle.api.internal.DefaultPolymorphicDomainObjectContainerTest\$Person'"
+        e.message == "Cannot register a factory for type String because it is not a subtype of " +
+                "container element type Person."
     }
 
     def "fires events when elements are added"() {
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 11be2ae..bb57cf7 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,45 +16,45 @@
 
 package org.gradle.api.internal
 
-import org.gradle.api.Action
-import org.gradle.api.DefaultTask
-import org.gradle.api.Project
-import org.gradle.api.Task
+import com.google.common.collect.Lists
+import org.gradle.api.*
 import org.gradle.api.tasks.AbstractTaskTest
 import org.gradle.api.tasks.TaskDependency
 import org.gradle.api.tasks.TaskExecutionException
 import org.gradle.api.tasks.TaskInstantiationException
+import org.gradle.internal.Actions
 import org.gradle.listener.ListenerManager
 import org.gradle.util.WrapUtil
 import org.jmock.Expectations
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.ExpectedException
 import spock.lang.Issue
 
 import java.util.concurrent.Callable
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class DefaultTaskTest extends AbstractTaskTest {
     ClassLoader cl
     DefaultTask defaultTask
 
     Object testCustomPropValue;
 
-    @Before public void setUp() {
+    @Before
+    public void setUp() {
         testCustomPropValue = new Object()
         defaultTask = createTask(DefaultTask.class)
         cl = Thread.currentThread().contextClassLoader
     }
 
-    @After public void cleanup() {
+    @After
+    public void cleanup() {
         Thread.currentThread().contextClassLoader = cl
     }
 
@@ -62,17 +62,20 @@ class DefaultTaskTest extends AbstractTaskTest {
         defaultTask
     }
 
-    @Test public void testDefaultTask() {
+    @Test
+    public void testDefaultTask() {
         DefaultTask task = AbstractTask.injectIntoNewInstance(project, TEST_TASK_NAME, { new DefaultTask() } as Callable)
         assertThat(task.dependsOn, isEmpty())
         assertEquals([], task.actions)
     }
 
-    @Test public void testHasUsefulToString() {
-        assertEquals('task \':taskname\'', task.toString())
+    @Test
+    public void testHasUsefulToString() {
+        assertEquals('task \':testTask\'', task.toString())
     }
 
-    @Test public void testCanInjectValuesIntoTaskWhenUsingNoArgsConstructor() {
+    @Test
+    public void testCanInjectValuesIntoTaskWhenUsingNoArgsConstructor() {
         DefaultTask task = AbstractTask.injectIntoNewInstance(project, TEST_TASK_NAME, { new DefaultTask() } as Callable)
         assertThat(task.project, sameInstance(project))
         assertThat(task.name, equalTo(TEST_TASK_NAME))
@@ -92,6 +95,56 @@ class DefaultTaskTest extends AbstractTaskTest {
     }
 
     @Test
+    public void testMustRunAfter() {
+        Task mustRunAfterTask = createTask(project, "mustRunAfter")
+        Task mustRunAfterTaskUsingPath = project.getTasks().create("path")
+        Task task = createTask(project, TEST_TASK_NAME)
+
+        task.mustRunAfter(mustRunAfterTask, "path")
+        assert task.mustRunAfter.getDependencies(task) == [mustRunAfterTask, mustRunAfterTaskUsingPath] as Set
+    }
+
+    @Test
+    public void testFinalizedBy() {
+        Task finalizer = createTask(project, "finalizer")
+        Task finalizerFromPath = project.getTasks().create("path")
+        Task finalized = createTask(project, TEST_TASK_NAME)
+
+        finalized.finalizedBy(finalizer, "path")
+        assert finalized.finalizedBy.getDependencies(finalized) == [finalizer, finalizerFromPath] as Set
+    }
+
+    @Test
+    public void testSetFinalizedBy() {
+        Task finalizer = createTask(project, "finalizer")
+        Task finalizerFromPath = project.getTasks().create("path")
+        Task finalized = createTask(project, TEST_TASK_NAME)
+
+        finalized.finalizedBy = [finalizer, "path"]
+        assert finalized.finalizedBy.getDependencies(finalized) == [finalizer, finalizerFromPath] as Set
+    }
+
+    @Test
+    void testShouldRunAfter() {
+        Task shouldRunAfterTask = createTask(project, "shouldRunAfter")
+        Task shouldRunAfterFromPath = project.getTasks().create("path")
+        Task task = createTask(project, TEST_TASK_NAME)
+
+        task.shouldRunAfter(shouldRunAfterTask, shouldRunAfterFromPath)
+        assert task.shouldRunAfter.getDependencies(task) == [shouldRunAfterTask, shouldRunAfterFromPath] as Set
+    }
+
+    @Test
+    void testSetShouldRunAfter() {
+        Task shouldRunAfterTask = createTask(project, "shouldRunAfter")
+        Task shouldRunAfterFromPath = project.getTasks().create("path")
+        Task task = createTask(project, TEST_TASK_NAME)
+
+        task.shouldRunAfter = [shouldRunAfterTask, shouldRunAfterFromPath]
+        assert task.shouldRunAfter.getDependencies(task) == [shouldRunAfterTask, shouldRunAfterFromPath] as Set
+    }
+
+    @Test
     public void testConfigure() {
         Closure action1 = { Task t -> }
         assertSame(task, task.configure {
@@ -130,7 +183,8 @@ class DefaultTaskTest extends AbstractTaskTest {
         assertSame(action2, defaultTask.actions[1].action)
     }
 
-    @Test public void testSetsContextClassLoaderWhenExecutingAction() {
+    @Test
+    public void testSetsContextClassLoaderWhenExecutingAction() {
         Action<Task> testAction = context.mock(Action)
         context.checking {
             one(testAction).execute(defaultTask)
@@ -145,7 +199,8 @@ class DefaultTaskTest extends AbstractTaskTest {
         defaultTask.actions[0].execute(defaultTask)
     }
 
-    @Test public void testClosureActionDelegatesToTask() {
+    @Test
+    public void testClosureActionDelegatesToTask() {
         Closure testAction = {
             assert delegate == defaultTask
             assert resolveStrategy == Closure.DELEGATE_FIRST
@@ -154,7 +209,8 @@ class DefaultTaskTest extends AbstractTaskTest {
         defaultTask.actions[0].execute(defaultTask)
     }
 
-    @Test public void testSetsContextClassLoaderWhenRunningClosureAction() {
+    @Test
+    public void testSetsContextClassLoaderWhenRunningClosureAction() {
         Closure testAction = {
             assert Thread.currentThread().contextClassLoader == getClass().classLoader
         }
@@ -165,10 +221,11 @@ class DefaultTaskTest extends AbstractTaskTest {
         defaultTask.actions[0].execute(defaultTask)
     }
 
-    @Test public void testDoFirstWithClosureAddsActionToTheStartOfActionsList() {
-        Closure testAction1 = { }
-        Closure testAction2 = { }
-        Closure testAction3 = { }
+    @Test
+    public void testDoFirstWithClosureAddsActionToTheStartOfActionsList() {
+        Closure testAction1 = {}
+        Closure testAction2 = {}
+        Closure testAction3 = {}
         defaultTask.doLast(testAction1)
         defaultTask.doLast(testAction2)
         defaultTask.doLast(testAction3)
@@ -178,10 +235,11 @@ class DefaultTaskTest extends AbstractTaskTest {
         assertSame(defaultTask.actions[2].closure, testAction3)
     }
 
-    @Test public void testDoLastWithClosureAddsActionToTheEndOfActionsList() {
-        Closure testAction1 = { }
-        Closure testAction2 = { }
-        Closure testAction3 = { }
+    @Test
+    public void testDoLastWithClosureAddsActionToTheEndOfActionsList() {
+        Closure testAction1 = {}
+        Closure testAction2 = {}
+        Closure testAction3 = {}
         defaultTask.doFirst(testAction1)
         defaultTask.doFirst(testAction2)
         defaultTask.doFirst(testAction3)
@@ -191,7 +249,92 @@ class DefaultTaskTest extends AbstractTaskTest {
         assertSame(defaultTask.actions[2].closure, testAction1)
     }
 
-    @Test public void testExecuteThrowsExecutionFailure() {
+    @Issue("GRADLE-2774")
+    @Test
+    public void testActionToActionsAndExecute() {
+        def actionExecuted = false
+        def closureAction = { t -> actionExecuted = true } as Action
+        defaultTask.actions.add(closureAction)
+        defaultTask.execute()
+        assertTrue(actionExecuted)
+
+    }
+
+    @Issue("GRADLE-2774")
+    @Test
+    public void testAddAllActionToActionsAndExecute() {
+        def actionExecuted = false
+        def closureAction = { t -> actionExecuted = true } as Action
+        defaultTask.actions.addAll(Lists.newArrayList(closureAction))
+        defaultTask.execute()
+
+        assertTrue(actionExecuted)
+    }
+
+    @Issue("GRADLE-2774")
+    @Test
+    public void testAddAllActionToActionsWithIndexAndExecute() {
+        def actionExecuted = false
+        def closureAction = { t -> actionExecuted = true } as Action
+        defaultTask.actions.addAll(0, Lists.newArrayList(closureAction))
+        defaultTask.execute()
+        assertTrue(actionExecuted)
+
+    }
+
+    @Issue("GRADLE-2774")
+    @Test
+    public void testAddActionToActionsWithIteratorAndExecute() {
+        def actionExecuted = false
+        def closureAction = { t -> actionExecuted = true } as Action
+        defaultTask.actions.listIterator().add(closureAction)
+        defaultTask.execute()
+        assertTrue(actionExecuted)
+    }
+
+    @Test
+    public void testAddedActionCanBeRemoved() {
+        def closureAction = { t -> } as Action
+
+        defaultTask.actions.add(closureAction)
+        defaultTask.actions.remove(closureAction)
+        assertTrue(defaultTask.actions.isEmpty())
+
+        defaultTask.actions.add(closureAction)
+        defaultTask.actions.removeAll([closureAction])
+        assertTrue(defaultTask.actions.isEmpty())
+    }
+
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none()
+
+    @Test
+    public void testAddNullToActionsAndExecute() {
+        thrown.expect(InvalidUserDataException.class)
+        defaultTask.actions.add(null);
+    }
+
+    @Test
+    public void testAddNullToActionsWithIndexAndExecute() {
+        thrown.expect(InvalidUserDataException.class)
+        defaultTask.actions.add(0, null);
+    }
+
+    @Test
+    public void testAddAllNullToActionsAndExecute() {
+        thrown.expect(InvalidUserDataException.class)
+        defaultTask.actions.addAll(null);
+    }
+
+    @Test
+    public void testAddAllNullToActionsWithIndexAndExecute() {
+        thrown.expect(InvalidUserDataException.class)
+        defaultTask.actions.addAll(0, null);
+    }
+
+    @Test
+    public void testExecuteThrowsExecutionFailure() {
         def failure = new RuntimeException()
         defaultTask.doFirst { throw failure }
 
@@ -206,7 +349,8 @@ class DefaultTaskTest extends AbstractTaskTest {
         assertThat(defaultTask.state.failure.cause, sameInstance(failure))
     }
 
-    @Test public void testExecuteWithoutThrowingTaskFailureThrowsExecutionFailure() {
+    @Test
+    public void testExecuteWithoutThrowingTaskFailureThrowsExecutionFailure() {
         def failure = new RuntimeException()
         defaultTask.doFirst { throw failure }
 
@@ -239,7 +383,7 @@ class DefaultTaskTest extends AbstractTaskTest {
 
     @Test
     void canGetTemporaryDirectory() {
-        File tmpDir = new File(project.buildDir, "tmp/taskname")
+        File tmpDir = new File(project.buildDir, "tmp/testTask")
         assertFalse(tmpDir.exists())
 
         assertThat(defaultTask.temporaryDir, equalTo(tmpDir))
@@ -257,16 +401,18 @@ class DefaultTaskTest extends AbstractTaskTest {
         final Task task2 = context.mock(Task.class, "task2");
         final TaskDependency dependencyMock = context.mock(TaskDependency.class);
         getTask().dependsOn(dependencyMock);
-        context.checking(new Expectations() {{
-            allowing(dependencyMock).getDependencies(getTask());
-            will(returnValue(WrapUtil.toSet(task1, task2)));
+        context.checking(new Expectations() {
+            {
+                allowing(dependencyMock).getDependencies(getTask());
+                will(returnValue(WrapUtil.toSet(task1, task2)));
 
-            exactly(2).of(task1).getDidWork();
-            will(returnValue(false));
+                exactly(2).of(task1).getDidWork();
+                will(returnValue(false));
 
-            exactly(2).of(task2).getDidWork();
-            will(onConsecutiveCalls(returnValue(false), returnValue(true)));
-        }});
+                exactly(2).of(task2).getDidWork();
+                will(onConsecutiveCalls(returnValue(false), returnValue(true)));
+            }
+        });
 
         assertFalse(getTask().dependsOnTaskDidWork());
 
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 e5b6e70..bb69e62 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
@@ -23,53 +23,10 @@ import spock.lang.Specification
 
 class DocumentationRegistryTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmpDir
-    final GradleDistributionLocator locator = Mock()
     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")
-        distDir.createFile("docs/userguide/userguide.html")
-        def daemonPage = distDir.createFile("docs/userguide/gradle_daemon.html")
-
-        given:
-        _ * locator.gradleHome >> distDir
-
-        expect:
-        registry.getDocumentationFor('gradle_daemon') == daemonPage.absolutePath
-    }
-
-    def "fails when local user guide is present in distribution but target page not found"() {
-        def distDir = tmpDir.createDir("home")
-        distDir.createFile("docs/userguide/userguide.html")
-        def expectedPage = distDir.file("docs/userguide/gradle_daemon.html")
-
-        given:
-        _ * locator.gradleHome >> distDir
-
-        when:
-        registry.getDocumentationFor('gradle_daemon')
-
-        then:
-        IllegalArgumentException e = thrown()
-        e.message == "User guide page '${expectedPage}' not found."
-    }
-
-    def "points users at the remote user guide when user guide not present in distribution"() {
-        def distDir = tmpDir.createDir("home")
-
-        given:
-        _ * locator.gradleHome >> distDir
-
-        expect:
-        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/${gradleVersion.version}/userguide/gradle_daemon.html"
-    }
-
-    def "points users at the remote user guide when no distribution"() {
-        given:
-        _ * locator.gradleHome >> null
+    final DocumentationRegistry registry = new DocumentationRegistry()
 
+    def "points users at the gradle docs web site"() {
         expect:
         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 af3f768..85a7135 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
@@ -20,7 +20,7 @@ import groovy.lang.MissingMethodException;
 import org.gradle.api.internal.project.AbstractProject;
 import org.gradle.api.plugins.Convention;
 import org.gradle.testfixtures.ProjectBuilder;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.junit.Test;
 
 import java.util.Map;
@@ -413,7 +413,7 @@ public class ExtensibleDynamicObjectTest {
     @Test
     public void canInvokeMethodDefinedByScriptObject() {
         Bean bean = new Bean();
-        Script script = HelperUtil.createScript("def scriptMethod(a, b) { \"script:$a.$b\" } ");
+        Script script = TestUtil.createScript("def scriptMethod(a, b) { \"script:$a.$b\" } ");
         bean.extensibleDynamicObject.addObject(new BeanDynamicObject(script), ExtensibleDynamicObject.Location.BeforeConvention);
 
         assertTrue(bean.hasMethod("scriptMethod", "a", "b"));
@@ -484,7 +484,7 @@ public class ExtensibleDynamicObjectTest {
     @Test
     public void canInvokeClosurePropertyAsAMethod() {
         Bean bean = new Bean();
-        bean.setProperty("someMethod", HelperUtil.toClosure("{ param -> param.toLowerCase() }"));
+        bean.setProperty("someMethod", TestUtil.toClosure("{ param -> param.toLowerCase() }"));
         assertThat(bean.invokeMethod("someMethod", "Param"), equalTo((Object) "param"));
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/GraphAggregatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/GraphAggregatorTest.groovy
deleted file mode 100644
index b956b28..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/GraphAggregatorTest.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal
-
-import spock.lang.Specification
-
-class GraphAggregatorTest extends Specification {
-    private final DirectedGraph<String, String> graph = Mock()
-    private final GraphAggregator aggregator = new GraphAggregator(graph)
-
-    def groupsNodeWithTheEntryNodeItIsReachableFrom() {
-        when:
-        def result = aggregator.group(['a'], ['a', 'b'])
-
-        then:
-        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
-        result.getNodes('a') == ['a', 'b'] as Set
-    }
-
-    def groupsNodeWithTheClosesEntryNodeItIsReachableFrom() {
-        when:
-        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c'])
-
-        then:
-        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
-        1 * graph.getNodeValues('b', !null, !null) >> { args -> args[2].add('c') }
-        result.getNodes('a') == ['a'] as Set
-        result.getNodes('b') == ['b', 'c'] as Set
-    }
-
-    def groupsNodeWithMultipleEntryNodesWhenTheNodeHasMultipleClosesNodes() {
-        when:
-        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c'])
-
-        then:
-        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('c') }
-        1 * graph.getNodeValues('b', !null, !null) >> { args -> args[2].add('c') }
-        result.getNodes('a') == ['a', 'c'] as Set
-        result.getNodes('b') == ['b', 'c'] as Set
-    }
-
-    def groupsNodesWhichAreNotReachableFromStartNodes() {
-        when:
-        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c', 'd'])
-
-        then:
-        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
-        1 * graph.getNodeValues('c', !null, !null) >> { args -> args[2].add('d') }
-        result.topLevelNodes == ['a', 'b', 'c'] as Set
-        result.getNodes('c') == ['c', 'd'] as Set
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
deleted file mode 100644
index 696ccdc..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
+++ /dev/null
@@ -1,184 +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
-
-import spock.lang.Specification
-import org.gradle.util.TreeVisitor
-
-class LocationAwareExceptionTest extends Specification {
-    def "visit reportable causes does not visit direct cause"() {
-        TreeVisitor visitor = Mock()
-        def cause = new RuntimeException()
-        def e = new LocationAwareException(cause, cause, null, 100)
-
-        when:
-        e.visitReportableCauses(visitor)
-
-        then:
-        1 * visitor.node(e)
-        0 * visitor._
-        
-        and:
-        e.reportableCauses == []
-    }
-
-    def "visit reportable causes visits indirect cause"() {
-        TreeVisitor visitor = Mock()
-        def childCause = new RuntimeException()
-        def cause = new RuntimeException(childCause)
-        def e = new LocationAwareException(cause, cause, null, 100)
-
-        when:
-        e.visitReportableCauses(visitor)
-
-        then:
-        1 * visitor.node(e)
-
-        and:
-        1 * visitor.startChildren()
-
-        and:
-        1 * visitor.node(childCause)
-
-        and:
-        1 * visitor.endChildren()
-        0 * visitor._
-        
-        and:
-        e.reportableCauses == [childCause]
-    }
-
-    def "visit reportable causes visits causes of contextual exception"() {
-        TreeVisitor visitor = Mock()
-        def childCause = new RuntimeException()
-        def cause = new TestContextualException(childCause)
-        def e = new LocationAwareException(cause, cause, null, 100)
-
-        when:
-        e.visitReportableCauses(visitor)
-
-        then:
-        1 * visitor.node(e)
-
-        and:
-        1 * visitor.startChildren()
-
-        and:
-        1 * visitor.node(childCause)
-
-        and:
-        1 * visitor.endChildren()
-        0 * visitor._
-
-        and:
-        e.reportableCauses == [childCause]
-    }
-
-    def "visit reportable causes visits all contextual exceptions and direct cause of last contextual exception"() {
-        TreeVisitor visitor = Mock()
-        def unreportedCause = new RuntimeException()
-        def reportedCause = new RuntimeException(unreportedCause)
-        def lastContextual = new TestContextualException(reportedCause)
-        def interveningUnreported = new RuntimeException(lastContextual)
-        def contextual = new TestContextualException(interveningUnreported)
-        def interveningUnreported2 = new RuntimeException(contextual)
-        def cause = new TestContextualException(interveningUnreported2)
-        def e = new LocationAwareException(cause, cause, null, 100)
-
-        when:
-        e.visitReportableCauses(visitor)
-
-        then:
-        1 * visitor.node(e)
-        1 * visitor.node(contextual)
-        1 * visitor.node(lastContextual)
-        1 * visitor.node(reportedCause)
-
-        and:
-        _ * visitor.startChildren()
-        _ * visitor.endChildren()
-        0 * visitor._
-
-        and:
-        e.reportableCauses == [contextual, lastContextual, reportedCause]
-    }
-
-    def "visit reportable causes visits causes of multi-cause exception"() {
-        TreeVisitor visitor = Mock()
-        def childCause1 = new RuntimeException()
-        def childCause2 = new RuntimeException()
-        def cause = new AbstractMultiCauseException("broken", childCause1, childCause2)
-        def e = new LocationAwareException(cause, cause, null, 100)
-
-        when:
-        e.visitReportableCauses(visitor)
-
-        then:
-        1 * visitor.node(e)
-
-        and:
-        1 * visitor.startChildren()
-
-        and:
-        1 * visitor.node(childCause1)
-
-        and:
-        1 * visitor.node(childCause2)
-
-        and:
-        1 * visitor.endChildren()
-        0 * visitor._
-
-        and:
-        e.reportableCauses == [childCause1, childCause2]
-    }
-
-    def "visit reportable causes visits causes recursively"() {
-        TreeVisitor visitor = Mock()
-        def ignored = new RuntimeException()
-        def childCause1 = new RuntimeException(ignored)
-        def childCause2 = new RuntimeException()
-        def childCause3 = new TestContextualException(childCause2)
-        def childCause4 = new TestContextualException(childCause3)
-        def cause = new AbstractMultiCauseException("broken", childCause1, childCause4)
-        def e = new LocationAwareException(cause, cause, null, 100)
-
-        when:
-        e.visitReportableCauses(visitor)
-
-        then:
-        1 * visitor.node(e)
-
-        and:
-        3 * visitor.startChildren()
-        1 * visitor.node(childCause1)
-        1 * visitor.node(childCause4)
-        1 * visitor.node(childCause3)
-        1 * visitor.node(childCause2)
-        3 * visitor.endChildren()
-        0 * visitor._
-
-        and:
-        e.reportableCauses == [childCause1, childCause4, childCause3, childCause2]
-    }
-
-    @Contextual
-    class TestContextualException extends RuntimeException {
-        TestContextualException(Throwable throwable) {
-            super(throwable)
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
index 43fa12b..b62a752 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api.internal;
 
+import org.gradle.internal.reflect.Instantiator;
+
 public class TestContainer extends AbstractNamedDomainObjectContainer<TestObject> {
 
     public TestContainer(org.gradle.internal.reflect.Instantiator instantiator) {
@@ -22,9 +24,9 @@ public class TestContainer extends AbstractNamedDomainObjectContainer<TestObject
     }
 
     protected TestObject doCreate(String name) {
-        TestObject testObject = new TestObject();
+        Instantiator instantiator = getInstantiator();
+        TestObject testObject = new TestObject(instantiator);
         testObject.setName(name);
-        testObject.add(name);
         return testObject;
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java
index 48d2981..1ea7fd2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java
@@ -25,9 +25,6 @@ import java.util.*;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExcludeRuleContainerTest {
     @Test
     public void testInit() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy
index 89a8e3e..de3147f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy
@@ -16,14 +16,14 @@
 
 package org.gradle.api.internal.artifacts
 
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class ProjectBackedModuleTest extends Specification {
 
     def "module exposes project properties"() {
         given:
-        def project = HelperUtil.createRootProject()
+        def project = TestUtil.createRootProject()
         def module = new ProjectBackedModule(project)
 
         expect:
@@ -31,6 +31,7 @@ class ProjectBackedModuleTest extends Specification {
         module.group == project.group.toString()
         module.version == project.version.toString()
         module.status == project.status.toString()
+        module.projectPath == project.path
 
         when:
         project.group = "fo${1}o"
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy
index 653f467..e5a1bca 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy
@@ -19,7 +19,7 @@ import org.gradle.api.artifacts.Dependency
 import org.gradle.api.artifacts.DependencyArtifact
 import org.gradle.api.artifacts.ModuleDependency
 import org.gradle.api.internal.artifacts.DefaultExcludeRule
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.gradle.util.WrapUtil
 import spock.lang.Specification
 
@@ -83,7 +83,7 @@ class AbstractModuleDependencySpec extends Specification {
     }
 
     private DependencyArtifact createAnonymousArtifact() {
-        return new DefaultDependencyArtifact(HelperUtil.createUniqueId(), "type", "org", "classifier", "url")
+        return new DefaultDependencyArtifact(TestUtil.createUniqueId(), "type", "org", "classifier", "url")
     }
 
     public static void assertDeepCopy(ModuleDependency dependency, ModuleDependency copiedDependency) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
index 58d2472..118ea7b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.DependencyArtifact;
 import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.WrapUtil;
 import org.jmock.integration.junit4.JMock;
@@ -35,9 +35,6 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 abstract public class AbstractModuleDependencyTest {
     //TODO SF rework the remaining coverage of this hierarchy in the spirit of AbstractModuleDependencySpec and DefaultProjectDependencyTest
@@ -85,7 +82,7 @@ abstract public class AbstractModuleDependencyTest {
     }
 
     private DependencyArtifact createAnonymousArtifact() {
-        return new DefaultDependencyArtifact(HelperUtil.createUniqueId(), "type", "org", "classifier", "url");
+        return new DefaultDependencyArtifact(TestUtil.createUniqueId(), "type", "org", "classifier", "url");
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
index 6a3e98a..e99073c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
@@ -23,9 +23,6 @@ import org.junit.Test;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultClientModuleTest extends AbstractModuleDependencyTest {
     private static final String TEST_GROUP = "org.gradle";
     private static final String TEST_NAME = "gradle-core";
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java
index e27779a..1194e84 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java
@@ -16,12 +16,10 @@
 package org.gradle.api.internal.artifacts.dependencies;
 
 import org.gradle.api.artifacts.DependencyArtifact;
-import static org.junit.Assert.assertEquals;
 import org.junit.Test;
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.assertEquals;
+
 public class DefaultDependencyArtifactTest {
     @Test
     public void testInit() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
index d1b657c..1019446 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
@@ -27,9 +27,6 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultExternalModuleDependencyTest extends AbstractModuleDependencyTest {
     private static final String TEST_GROUP = "org.gradle";
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.groovy
index 31bedb9..bc1b489 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import org.gradle.api.internal.artifacts.DependencyResolveContext
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext
 import org.gradle.initialization.ProjectAccessListener
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.dependencies.AbstractModuleDependencySpec.assertDeepCopy
@@ -31,7 +31,7 @@ import static org.junit.Assert.assertThat
 
 class DefaultProjectDependencyTest extends Specification {
 
-    ProjectInternal project = HelperUtil.createRootProject()
+    ProjectInternal project = TestUtil.createRootProject()
     ProjectAccessListener listener = Mock()
 
     private projectDependency = new DefaultProjectDependency(project, null, false)
@@ -62,8 +62,8 @@ class DefaultProjectDependencyTest extends Specification {
     void "transitive resolution resolves all dependencies"() {
         def context = Mock(DependencyResolveContext)
 
-        def superConf = project.configurations.add("superConf")
-        def conf = project.configurations.add("conf")
+        def superConf = project.configurations.create("superConf")
+        def conf = project.configurations.create("conf")
         conf.extendsFrom(superConf)
 
         def dep1 = Mock(ProjectDependency)
@@ -110,7 +110,7 @@ class DefaultProjectDependencyTest extends Specification {
     void "is Buildable"() {
         def context = Mock(TaskDependencyResolveContext)
 
-        def conf = project.configurations.add('conf')
+        def conf = project.configurations.create('conf')
         def listener = Mock(ProjectAccessListener)
         projectDependency = new DefaultProjectDependency(project, 'conf', listener, true)
 
@@ -126,7 +126,7 @@ class DefaultProjectDependencyTest extends Specification {
 
     void "does not build project dependencies if configured so"() {
         def context = Mock(TaskDependencyResolveContext)
-        project.configurations.add('conf')
+        project.configurations.create('conf')
         projectDependency = new DefaultProjectDependency(project, 'conf', listener, false)
 
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
index c58f082..3875a21 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
@@ -15,12 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.dsl.dependencies
 
-import spock.lang.Specification
 import org.gradle.api.artifacts.*
+import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
+import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class DefaultDependencyHandlerTest extends Specification {
     private static final String TEST_CONF_NAME = "someConf"
     private ConfigurationContainer configurationContainer = Mock()
@@ -29,7 +27,8 @@ class DefaultDependencyHandlerTest extends Specification {
     private ProjectFinder projectFinder = Mock()
     private DependencySet dependencySet = Mock()
 
-    private DefaultDependencyHandler dependencyHandler = new DefaultDependencyHandler(configurationContainer, dependencyFactory, projectFinder)
+    private DefaultDependencyHandler dependencyHandler = new DefaultDependencyHandler(
+            configurationContainer, dependencyFactory, projectFinder, Stub(ComponentMetadataHandler), Stub(ArtifactResolutionQueryFactory))
 
     void setup() {
         _ * configurationContainer.findByName(TEST_CONF_NAME) >> configuration
@@ -262,4 +261,12 @@ class DefaultDependencyHandlerTest extends Specification {
         then:
         thrown(MissingMethodException)
     }
+
+    void "reasonable error when supplying null as a dependency notation"() {
+        when:
+        dependencyHandler."$TEST_CONF_NAME"(null)
+
+        then:
+        1 * dependencyFactory.createDependency(null)
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java
index 27fe2f6..e6b4772 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.ClientModule;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.WrapUtil;
 import org.hamcrest.Matchers;
 import org.jmock.Expectations;
@@ -29,9 +29,6 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class ModuleFactoryDelegateTest {
     private JUnit4Mockery context = new JUnit4Mockery();
 
@@ -52,7 +49,7 @@ public class ModuleFactoryDelegateTest {
     @Test
     public void dependencyWithClosure() {
         final String dependencyNotation = "someNotation";
-        final Closure configureClosure = HelperUtil.toClosure("{}");
+        final Closure configureClosure = TestUtil.toClosure("{}");
         final ModuleDependency dependencyDummy = context.mock(ModuleDependency.class);
         letFactoryStubReturnDependency(dependencyNotation, dependencyDummy);
         moduleFactoryDelegate.dependency(dependencyNotation, configureClosure);
@@ -81,7 +78,7 @@ public class ModuleFactoryDelegateTest {
     @Test
     public void module() {
         final String clientModuleNotation = "someNotation";
-        final Closure configureClosure = HelperUtil.toClosure("{}");
+        final Closure configureClosure = TestUtil.toClosure("{}");
         final ClientModule clientModuleDummy = context.mock(ClientModule.class);
         context.checking(new Expectations() {{
             allowing(dependencyFactoryStub).createModule(clientModuleNotation, configureClosure);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java
index b29f542..2a91119 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java
@@ -21,14 +21,12 @@ import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertEquals;
 
 import java.io.File;
 import java.util.Date;
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.assertEquals;
+
 public abstract class AbstractPublishArtifactTest {
     private static final File TEST_FILE = new File("artifactFile");
     private static final String TEST_NAME = "myfile-1";
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy
index 4713781..9b9e91d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy
@@ -15,69 +15,51 @@
  */
 package org.gradle.api.internal.artifacts.publish
 
-
-import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.file.copy.CopyAction
 import org.gradle.api.tasks.bundling.AbstractArchiveTask
-import org.gradle.util.WrapUtil
-import org.jmock.Expectations
-import org.junit.Test
-
-import static org.hamcrest.Matchers.equalTo
-import static org.hamcrest.Matchers.sameInstance
-import static org.junit.Assert.assertThat
-
-/**
- * @author Hans Dockter
- */
-public class ArchivePublishArtifactTest extends AbstractPublishArtifactTest {
-    private AbstractArchiveTask archiveTask = context.mock(AbstractArchiveTask.class)
-
-    @Override
-    protected PublishArtifact createPublishArtifact(final String classifier) {
-        prepareMocks(classifier, "")
-        return new ArchivePublishArtifact(archiveTask)
-    }
-
-    private void prepareMocks(final String classifier, final String appendix) {
-        context.checking(new Expectations() {
-            {
-                allowing(archiveTask).getExtension()
-                will(returnValue(getTestExt()))
+import org.gradle.util.TestUtil
+import spock.lang.Specification
 
-                allowing(archiveTask).getBaseName()
-                will(returnValue(getTestName()))
+public class ArchivePublishArtifactTest extends Specification {
 
-                allowing(archiveTask).getAppendix()
-                will(returnValue(appendix))
+    def "provides sensible default values for quite empty archive tasks"() {
+        def quiteEmptyJar = TestUtil.createTask(DummyJar)
 
-                allowing(archiveTask).getArchivePath()
-                will(returnValue(getTestFile()))
+        when:
+        def a = new ArchivePublishArtifact(quiteEmptyJar)
 
-                allowing(archiveTask).getClassifier()
-                will(returnValue(classifier))
-            }
-        })
+        then:
+        a.archiveTask == quiteEmptyJar
+        a.classifier == ""
+        a.date.time == quiteEmptyJar.archivePath.lastModified()
+        a.extension == "jar"
+        a.file == quiteEmptyJar.archivePath
+        a.type == "jar"
     }
 
-    @Override
-    protected String getTestType() {
-        return getTestExt()
-    }
+    def "configures name correctly"() {
+        def noName = TestUtil.createTask(DummyJar)
+        def withArchiveName = TestUtil.createTask(DummyJar, [archiveName: "hey"])
+        def withBaseName = TestUtil.createTask(DummyJar, [baseName: "foo"])
+        def withAppendix = TestUtil.createTask(DummyJar, [baseName: "foo", appendix: "javadoc"])
+        def withAppendixOnly = TestUtil.createTask(DummyJar, [appendix: "javadoc"])
 
-    @Test
-    public void init() {
-        ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) createPublishArtifact(getTestClassifier())
-        assertThat((Set<AbstractArchiveTask>) publishArtifact.getBuildDependencies().getDependencies(null), equalTo(WrapUtil.toSet(archiveTask)))
-        assertCommonPropertiesAreSet(publishArtifact, true)
-        assertThat(publishArtifact.getArchiveTask(), sameInstance(archiveTask))
+        expect:
+        new ArchivePublishArtifact(noName).name == null
+        new ArchivePublishArtifact(withArchiveName).name == null
+        new ArchivePublishArtifact(withBaseName).name == "foo"
+        new ArchivePublishArtifact(withBaseName).setName("haha").name == "haha"
+        new ArchivePublishArtifact(withAppendix).name == "foo-javadoc"
+        new ArchivePublishArtifact(withAppendixOnly).name == "javadoc"
     }
 
-    @Test
-    public void nameWithAppendix() {
-        String testAppendix = "appendix"
-        prepareMocks(getTestClassifier(), testAppendix)
-        PublishArtifact publishArtifact = new ArchivePublishArtifact(archiveTask)
-        assertThat(publishArtifact.getName(), equalTo(getTestName() + "-" + testAppendix))
-    }
+    static class DummyJar extends AbstractArchiveTask {
+        DummyJar() {
+            extension = "jar"
+        }
 
+        protected CopyAction createCopyAction() {
+            return null
+        }
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
index e1a857f..681f65a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
@@ -28,9 +28,6 @@ import static org.gradle.util.WrapUtil.toSet;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultPublishArtifactTest extends AbstractPublishArtifactTest {
     protected PublishArtifact createPublishArtifact(String classifier) {
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
index 428c62e..1e09674 100644
--- 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
@@ -21,26 +21,23 @@ 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 org.gradle.util.TestUtil
+import org.junit.Rule
 import spock.lang.Specification
 import spock.lang.Unroll
 
 class ArtifactRepositoryChangingNameAfterContainerInclusionDeprecationTest extends Specification {
 
     TestAppender appender = new TestAppender()
-    ConfigureLogging logging
+    @Rule ConfigureLogging logging = new ConfigureLogging(appender)
     Project project
 
     def setup() {
-        project = HelperUtil.createRootProject()
+        project = TestUtil.createRootProject()
         DeprecationLogger.reset()
-        appender = new TestAppender()
-        logging = new ConfigureLogging(appender)
-        logging.attachAppender()
     }
 
     def cleanup() {
-        logging.detachAppender()
         DeprecationLogger.reset()
     }
 
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
deleted file mode 100644
index b363c9d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparatorSpec.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.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/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy
deleted file mode 100644
index 94a6498..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection
-
-import org.gradle.cache.PersistentIndexedCache
-import spock.lang.Specification
-
-class CacheBackedFileSnapshotRepositoryTest extends Specification {
-    final TaskArtifactStateCacheAccess cacheAccess = Mock()
-    final PersistentIndexedCache<Object, Object> indexedCache = Mock()
-    FileSnapshotRepository repository
-
-    def setup() {
-        1 * cacheAccess.createCache("fileSnapshots", Object, Object) >> indexedCache
-        repository = new CacheBackedFileSnapshotRepository(cacheAccess)
-    }
-
-    def "assigns an id when a snapshot is added"() {
-        FileCollectionSnapshot snapshot = Mock()
-
-        when:
-        def id = repository.add(snapshot)
-
-        then:
-        id == 4
-        1 * indexedCache.get("nextId") >> (4 as Long)
-        1 * indexedCache.put("nextId", 5)
-        1 * indexedCache.put(4, snapshot)
-        0 * _._
-    }
-
-    def "can fetch a snapshot by id"() {
-        FileCollectionSnapshot snapshot = Mock()
-
-        when:
-        def result = repository.get(4)
-
-        then:
-        result == snapshot
-        1 * indexedCache.get(4) >> snapshot
-        0 * _._
-    }
-
-    def "can delete a snapshot by id"() {
-        when:
-        repository.remove(4)
-
-        then:
-        1 * indexedCache.remove(4)
-        0 * _._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
deleted file mode 100644
index 4082439..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.messaging.serialize.Serializer;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-import static org.gradle.util.Matchers.reflectionEquals;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class CachingHasherTest {
-    @Rule
-    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Hasher delegate = context.mock(Hasher.class);
-    private final PersistentIndexedCache<File, CachingHasher.FileInfo> cache = context.mock(
-            PersistentIndexedCache.class);
-    private final TaskArtifactStateCacheAccess cacheAccess = context.mock(TaskArtifactStateCacheAccess.class);
-    private final byte[] hash = "hash".getBytes();
-    private final File file = tmpDir.createFile("testfile").write("content");
-    private CachingHasher hasher;
-
-    @Before
-    public void setup() {
-        context.checking(new Expectations(){{
-            one(cacheAccess).createCache(with(equalTo("fileHashes")), with(equalTo(File.class)), with(notNullValue(Class.class)), with(notNullValue(Serializer.class)));
-            will(returnValue(cache));
-        }});
-        hasher = new CachingHasher(delegate, cacheAccess);
-    }
-
-    @Test
-    public void hashesFileWhenHashNotCached() {
-        context.checking(new Expectations() {{
-            one(cache).get(file);
-            will(returnValue(null));
-            one(delegate).hash(file);
-            will(returnValue(hash));
-            one(cache).put(with(equalTo(file)), with(reflectionEquals(new CachingHasher.FileInfo(hash, file.length(),
-                    file.lastModified()))));
-        }});
-
-        assertThat(hasher.hash(file), sameInstance(hash));
-    }
-
-    @Test
-    public void hashesFileWhenLengthHasChanged() {
-        context.checking(new Expectations() {{
-            one(cache).get(file);
-            will(returnValue(new CachingHasher.FileInfo(hash, 1078, file.lastModified())));
-            one(delegate).hash(file);
-            will(returnValue(hash));
-            one(cache).put(with(equalTo(file)), with(reflectionEquals(new CachingHasher.FileInfo(hash, file.length(),
-                    file.lastModified()))));
-        }});
-
-        assertThat(hasher.hash(file), sameInstance(hash));
-    }
-
-    @Test
-    public void hashesFileWhenTimestampHasChanged() {
-        context.checking(new Expectations() {{
-            one(cache).get(file);
-            will(returnValue(new CachingHasher.FileInfo(hash, file.length(), 12)));
-            one(delegate).hash(file);
-            will(returnValue(hash));
-            one(cache).put(with(equalTo(file)), with(reflectionEquals(new CachingHasher.FileInfo(hash, file.length(),
-                    file.lastModified()))));
-        }});
-
-        assertThat(hasher.hash(file), sameInstance(hash));
-    }
-
-    @Test
-    public void doesNotHashFileWhenTimestampAndLengthHaveNotChanged() {
-        context.checking(new Expectations() {{
-            one(cache).get(file);
-            will(returnValue(new CachingHasher.FileInfo(hash, file.length(), file.lastModified())));
-        }});
-
-        assertThat(hasher.hash(file), sameInstance(hash));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CompositeUpToDateRuleTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CompositeUpToDateRuleTest.groovy
deleted file mode 100644
index ef36edf..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CompositeUpToDateRuleTest.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.changedetection
-
-import spock.lang.Specification
-import org.gradle.api.internal.TaskInternal
-
-class CompositeUpToDateRuleTest extends Specification {
-    final UpToDateRule rule1 = Mock()
-    final UpToDateRule rule2 = Mock()
-    final UpToDateRule.TaskUpToDateState state1 = Mock()
-    final UpToDateRule.TaskUpToDateState state2 = Mock()
-    final TaskInternal task = Mock()
-    final TaskExecution previous = Mock()
-    final TaskExecution current = Mock()
-    final CompositeUpToDateRule rule = new CompositeUpToDateRule(rule1, rule2)
-
-    def delegatesToEachRuleInOrder() {
-        when:
-        def state = rule.create(task, previous, current)
-
-        then:
-        1 * rule1.create(task, previous, current) >> state1
-        1 * rule2.create(task, previous, current) >> state2
-
-        when:
-        state.checkUpToDate([])
-
-        then:
-        1 * state1.checkUpToDate([])
-        1 * state2.checkUpToDate([])
-
-        when:
-        state.snapshotAfterTask()
-
-        then:
-        1 * state1.snapshotAfterTask()
-        1 * state2.snapshotAfterTask()
-    }
-
-    def checkUpToDateStopsAtFirstRuleWhichMarksTaskOutOfDate() {
-        when:
-        def state = rule.create(task, previous, current)
-
-        then:
-        1 * rule1.create(task, previous, current) >> state1
-        1 * rule2.create(task, previous, current) >> state2
-
-        when:
-        state.checkUpToDate([])
-
-        then:
-        1 * state1.checkUpToDate([]) >> { args -> args[0] << 'out-of-date' }
-        0 * state2.checkUpToDate(_)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
deleted file mode 100755
index 961a356..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection
-
-import org.gradle.api.file.FileCollection
-import org.gradle.api.file.FileTree
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.ChangeListener
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import static org.hamcrest.Matchers.equalTo
-import static org.hamcrest.Matchers.notNullValue
-import static org.junit.Assert.assertThat
-
- at RunWith(JMock.class)
-public class DefaultFileSnapshotterTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Hasher hasher = new DefaultHasher()
-    private int counter
-    private ChangeListener listener = context.mock(ChangeListener.class)
-    private final DefaultFileSnapshotter snapshotter = new DefaultFileSnapshotter(hasher)
-    @Rule
-    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-
-    @Test
-    public void getFilesReturnsOnlyTheFilesWhichExisted() {
-        TestFile file = tmpDir.createFile('file1')
-        TestFile dir = tmpDir.createDir('file2')
-        TestFile noExist = tmpDir.file('file3')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file, dir, noExist))
-
-        assertThat(snapshot.files.files as List, equalTo([file]))
-    }
-    
-    @Test
-    public void notifiesListenerWhenFileAdded() {
-        TestFile file1 = tmpDir.createFile('file1')
-        TestFile file2 = tmpDir.createFile('file2')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1))
-
-        context.checking {
-            one(listener).added(file2)
-        }
-        snapshotter.snapshot(files(file1, file2)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void notifiesListenerWhenFileRemoved() {
-        TestFile file1 = tmpDir.createFile('file1')
-        TestFile file2 = tmpDir.createFile('file2')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1, file2))
-
-        context.checking {
-            one(listener).removed(file2)
-        }
-        snapshotter.snapshot(files(file1)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void fileHasNotChangedWhenTypeAndHashHaveNotChanged() {
-        TestFile file = tmpDir.createFile('file')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
-        assertThat(snapshot, notNullValue())
-
-        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void fileHasChangedWhenTypeHasChanged() {
-        TestFile file = tmpDir.createFile('file')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
-
-        file.delete()
-        file.createDir()
-
-        context.checking {
-            one(listener).changed(file)
-        }
-        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void fileHasChangedWhenHashHasChanged() {
-        TestFile file = tmpDir.createFile('file')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
-
-        file.write('new content')
-
-        context.checking {
-            one(listener).changed(file)
-        }
-        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void directoryHasNotChangedWhenTypeHasNotChanged() {
-        TestFile dir = tmpDir.createDir('dir')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(dir))
-
-        snapshotter.snapshot(files(dir)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void directoryHasChangedWhenTypeHasChanged() {
-        TestFile dir = tmpDir.createDir('dir')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(dir))
-
-        dir.deleteDir()
-        dir.createFile()
-
-        context.checking {
-            one(listener).changed(dir)
-        }
-        snapshotter.snapshot(files(dir)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void nonExistentFileUnchangedWhenTypeHasNotChanged() {
-        TestFile file = tmpDir.file('unknown')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
-
-        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void nonExistentFileIsChangedWhenTypeHasChanged() {
-        TestFile file = tmpDir.file('unknown')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
-
-        file.createFile()
-
-        context.checking {
-            one(listener).changed(file)
-        }
-        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void ignoresDuplicatesInFileCollection() {
-        TestFile file1 = tmpDir.createFile('file')
-        TestFile file2 = tmpDir.createFile('file')
-
-        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1, file2))
-
-        snapshotter.snapshot(files(file1)).changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void canCreateEmptySnapshot() {
-        TestFile file = tmpDir.createFile('file')
-        FileCollectionSnapshot snapshot = snapshotter.emptySnapshot()
-
-        FileCollectionSnapshot newSnapshot = snapshotter.snapshot(files(file))
-
-        context.checking {
-            one(listener).added(file)
-        }
-
-        newSnapshot.changesSince(snapshot, listener)
-    }
-
-    @Test
-    public void diffAddsAddedFilesToSnapshot() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.emptySnapshot()
-        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
-
-        context.checking {
-            one(mergeListener).added(withParam(notNullValue()))
-        }
-
-        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
-
-        context.checking {
-            one(listener).added(file)
-        }
-        target.changesSince(original, listener)
-    }
-
-    @Test
-    public void canIgnoreAddedFileInDiff() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.emptySnapshot()
-        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
-
-        context.checking {
-            one(mergeListener).added(withParam(notNullValue()))
-            will {merge -> merge.ignore()}
-        }
-
-        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
-
-        target.changesSince(original, listener)
-    }
-
-    @Test
-    public void diffAddsChangedFilesToSnapshot() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
-        file.write('new content')
-        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
-
-        context.checking {
-            one(mergeListener).changed(withParam(notNullValue()))
-        }
-
-        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
-
-        context.checking {
-            one(listener).changed(file)
-        }
-        target.changesSince(original, listener)
-    }
-
-    @Test
-    public void canIgnoreChangedFileInDiff() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
-        FileCollectionSnapshot target = snapshotter.snapshot(files(file))
-        file.write('new content')
-        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
-
-        context.checking {
-            one(mergeListener).changed(withParam(notNullValue()))
-            will {merge -> merge.ignore()}
-        }
-
-        target = modified.changesSince(original).applyTo(target, mergeListener)
-
-        target.changesSince(original, listener)
-    }
-
-    @Test
-    public void diffRemovesDeletedFilesFromSnapshot() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
-        FileCollectionSnapshot modified = snapshotter.emptySnapshot()
-
-        context.checking {
-            one(mergeListener).removed(withParam(notNullValue()))
-        }
-
-        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(files(file)), mergeListener)
-
-        context.checking {
-            one(listener).removed(file)
-        }
-        target.changesSince(original, listener)
-    }
-
-    @Test
-    public void canIgnoreRemovedFileInDiff() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
-        FileCollectionSnapshot modified = snapshotter.emptySnapshot()
-
-        context.checking {
-            one(mergeListener).removed(withParam(notNullValue()))
-            will{merge -> merge.ignore()}
-        }
-
-        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(files(file)), mergeListener)
-
-        target.changesSince(original, listener)
-    }
-
-    @Test
-    public void diffIgnoresUnchangedFilesInSnapshot() {
-        TestFile file = tmpDir.createFile('file')
-        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
-
-        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
-        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
-        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
-
-        target.changesSince(snapshotter.emptySnapshot(), listener)
-    }
-
-    private FileCollection files(File... files) {
-        FileTree collection = context.mock(FileTree.class)
-        context.checking {
-            allowing(collection).getAsFileTree()
-            will(returnValue(collection))
-            allowing(collection).iterator()
-            will(returnIterator(files as List))
-        }
-        return collection
-    }
-    
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccessTest.groovy
deleted file mode 100644
index afbbad1..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccessTest.groovy
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection
-
-import org.gradle.cache.CacheRepository
-import spock.lang.Specification
-import org.gradle.api.internal.GradleInternal
-import org.gradle.cache.DirectoryCacheBuilder
-import org.gradle.cache.PersistentCache
-import org.gradle.cache.PersistentIndexedCache
-
-class DefaultTaskArtifactStateCacheAccessTest extends Specification {
-    final GradleInternal gradle = Mock()
-    final CacheRepository cacheRepository = Mock()
-    final DefaultTaskArtifactStateCacheAccess cacheAccess = new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository)
-    
-    def "opens backing cache on first use"() {
-        DirectoryCacheBuilder cacheBuilder = Mock()
-        PersistentCache backingCache = Mock()
-        PersistentIndexedCache<String, Integer> backingIndexedCache = Mock()
-
-        when:
-        def indexedCache = cacheAccess.createCache("some-cache", String, Integer)
-
-        then:
-        0 * _._
-
-        when:
-        indexedCache.get("key")
-
-        then:
-        1 * cacheRepository.cache("taskArtifacts") >> cacheBuilder
-        1 * cacheBuilder.open() >> backingCache
-        _ * cacheBuilder._ >> cacheBuilder
-        _ * backingCache.baseDir >> new File("baseDir")
-        1 * backingCache.createCache(new File("baseDir/some-cache.bin"), String, Integer) >> backingIndexedCache
-        1 * backingIndexedCache.get("key")
-        0 * _._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
deleted file mode 100644
index c25ebbc..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.CacheUsage;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.internal.DefaultCacheRepository;
-import org.gradle.internal.id.RandomLongIdGenerator;
-import org.gradle.test.fixtures.file.TestFile;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.testfixtures.internal.InMemoryCacheFactory;
-import org.gradle.util.HelperUtil;
-import org.hamcrest.Matcher;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-import java.util.*;
-
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-public class DefaultTaskArtifactStateRepositoryTest {
-    @Rule
-    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final ProjectInternal project = HelperUtil.createRootProject();
-    private final Gradle gradle = project.getGradle();
-    private final TestFile outputFile = tmpDir.file("output-file");
-    private final TestFile outputDir = tmpDir.file("output-dir");
-    private final TestFile outputDirFile = outputDir.file("some-file");
-    private final TestFile outputDirFile2 = outputDir.file("some-file-2");
-    private final TestFile emptyOutputDir = tmpDir.file("empty-output-dir");
-    private final TestFile missingOutputFile = tmpDir.file("missing-output-file");
-    private final TestFile inputFile = tmpDir.createFile("input-file");
-    private final TestFile inputDir = tmpDir.createDir("input-dir");
-    private final TestFile inputDirFile = inputDir.file("input-file2").createFile();
-    private final TestFile missingInputFile = tmpDir.file("missing-input-file");
-    private final Set<TestFile> inputFiles = toSet(inputFile, inputDir, missingInputFile);
-    private final Set<TestFile> outputFiles = toSet(outputFile, outputDir, emptyOutputDir, missingOutputFile);
-    private final Set<TestFile> createFiles = toSet(outputFile, outputDirFile, outputDirFile2);
-    private DefaultTaskArtifactStateRepository repository;
-
-    @Before
-    public void setup() {
-        CacheRepository cacheRepository = new DefaultCacheRepository(tmpDir.createDir("user-home"), null, CacheUsage.ON, new InMemoryCacheFactory());
-        TaskArtifactStateCacheAccess cacheAccess = new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository);
-        FileSnapshotter inputFilesSnapshotter = new DefaultFileSnapshotter(new DefaultHasher());
-        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(inputFilesSnapshotter, new RandomLongIdGenerator(), cacheAccess);
-        TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess, new CacheBackedFileSnapshotRepository(cacheAccess));
-        repository = new DefaultTaskArtifactStateRepository(taskHistoryRepository, inputFilesSnapshotter, outputFilesSnapshotter);
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenCacheIsEmpty() {
-        TaskArtifactState state = repository.getStateFor(task());
-        assertNotNull(state);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyOutputFileNoLongerExists() {
-        execute();
-
-        outputFile.delete();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileInOutputDirNoLongerExists() {
-        execute();
-
-        outputDirFile.delete();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyOutputFileHasChangedType() {
-        execute();
-
-        outputFile.delete();
-        outputFile.createDir();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileInOutputDirHasChangedType() {
-        execute();
-
-        outputDirFile.delete();
-        outputDirFile.createDir();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyOutputFileHasChangedHash() {
-        execute();
-
-        outputFile.write("new content");
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileInOutputDirHasChangedHash() {
-        execute();
-
-        outputDirFile.write("new content");
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyOutputFilesAddedToSet() {
-        execute();
-
-        TaskInternal task = builder().withOutputFiles(outputFile, outputDir, tmpDir.createFile("output-file-2"), emptyOutputDir, missingOutputFile).task();
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyOutputFilesRemovedFromSet() {
-        execute();
-
-        TaskInternal task = builder().withOutputFiles(outputFile, emptyOutputDir, missingOutputFile).task();
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenTaskWithDifferentTypeGeneratedAnyOutputFiles() {
-        TaskInternal task1 = builder().withOutputFiles(outputFile).task();
-        TaskInternal task2 = builder().withType(TaskSubType.class).withOutputFiles(outputFile).task();
-
-        execute(task1, task2);
-
-        TaskArtifactState state = repository.getStateFor(task1);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputFilesAddedToSet() {
-        execute();
-
-        TaskInternal task = builder().withInputFiles(inputFile, inputDir, tmpDir.createFile("other-input"), missingInputFile).task();
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputFilesRemovedFromSet() {
-        execute();
-
-        TaskInternal task = builder().withInputFiles(inputFile).task();
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputFileHasChangedHash() {
-        execute();
-
-        inputFile.write("some new content");
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputFileHasChangedType() {
-        execute();
-
-        inputFile.delete();
-        inputFile.createDir();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputFileNoLongerExists() {
-        execute();
-
-        inputFile.delete();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileCreatedInInputDir() {
-        execute();
-
-        inputDir.file("other-file").createFile();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileDeletedFromInputDir() {
-        execute();
-
-        inputDirFile.delete();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileInInputDirChangesHash() {
-        execute();
-
-        inputDirFile.writelns("new content");
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyFileInInputDirChangesType() {
-        execute();
-
-        inputDirFile.delete();
-        inputDirFile.mkdir();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputPropertyValueChanged() {
-        execute();
-
-        TaskArtifactState state = repository.getStateFor(builder().withProperty("prop", "new value").task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void inputPropertyValueCanBeNull() {
-        TaskInternal task = builder().withProperty("prop", null).task();
-        execute(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputPropertyAdded() {
-        execute();
-
-        TaskArtifactState state = repository.getStateFor(builder().withProperty("prop2", "value").task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenAnyInputPropertyRemoved() {
-        execute(builder().withProperty("prop2", "value").task());
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenStateHasNotBeenUpdated() {
-        repository.getStateFor(task());
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreNotUpToDateWhenOutputDirWhichUsedToExistHasBeenDeleted() {
-        // Output dir already exists before first execution of task
-        outputDirFile.createFile();
-
-        TaskInternal task1 = builder().withOutputFiles(outputDir).createsFiles(outputDirFile).task();
-        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).createsFiles(outputDirFile2).task();
-
-        TaskArtifactState state = repository.getStateFor(task1);
-        assertFalse(state.isUpToDate());
-        state.afterTask();
-
-        outputDir.deleteDir();
-
-        // Another task creates dir
-        state = repository.getStateFor(task2);
-        assertFalse(state.isUpToDate());
-        task2.execute();
-        state.afterTask();
-
-        // Task should be out-of-date
-        state = repository.getStateFor(task1);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreUpToDateWhenNothingHasChangedSinceOutputFilesWereGenerated() {
-        execute();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertTrue(state.isUpToDate());
-
-        state = repository.getStateFor(task());
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreUpToDateWhenOutputFileWhichDidNotExistNowExists() {
-        execute();
-
-        missingOutputFile.touch();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreUpToDateWhenOutputDirWhichWasEmptyIsNoLongerEmpty() {
-        execute();
-
-        emptyOutputDir.file("some-file").touch();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void hasEmptyTaskHistoryWhenTaskHasNeverBeenExecuted() {
-        TaskArtifactState state = repository.getStateFor(task());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), isEmpty());
-    }
-
-    @Test
-    public void hasTaskHistoryFromPreviousExecution() {
-        execute();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), equalTo(toLinkedSet((File) outputFile, outputDirFile, outputDirFile2)));
-    }
-
-    @Test
-    public void multipleTasksCanProduceFilesIntoTheSameOutputDirectory() {
-        TaskInternal task1 = task();
-        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).createsFiles(outputDir.file("output2")).task();
-        execute(task1, task2);
-
-        TaskArtifactState state = repository.getStateFor(task1);
-        assertTrue(state.isUpToDate());
-
-        state = repository.getStateFor(task2);
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void multipleTasksCanProduceTheSameFileWithTheSameContents() {
-        TaskInternal task1 = builder().withOutputFiles(outputFile).task();
-        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputFile).task();
-        execute(task1, task2);
-
-        TaskArtifactState state = repository.getStateFor(task1);
-        assertTrue(state.isUpToDate());
-
-        state = repository.getStateFor(task2);
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void multipleTasksCanProduceTheSameEmptyDir() {
-        TaskInternal task1 = task();
-        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).task();
-        execute(task1, task2);
-
-        TaskArtifactState state = repository.getStateFor(task1);
-        assertTrue(state.isUpToDate());
-
-        state = repository.getStateFor(task2);
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void doesNotConsiderExistingFilesInOutputDirectoryAsProducedByTask() {
-        TestFile otherFile = outputDir.file("other").createFile();
-
-        execute();
-
-        otherFile.delete();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertTrue(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), (Matcher) not(hasItem(otherFile)));
-    }
-
-    @Test
-    public void considersExistingFileInOutputDirectoryWhichIsUpdatedByTheTaskAsProducedByTask() {
-        TestFile otherFile = outputDir.file("other").createFile();
-
-        TaskInternal task = task();
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-
-        task.execute();
-        otherFile.write("new content");
-
-        state.afterTask();
-
-        otherFile.delete();
-
-        state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), hasItem((File) otherFile));
-    }
-
-    @Test
-    public void fileIsNoLongerConsideredProducedByTaskOnceItIsDeleted() {
-        execute();
-
-        outputDirFile.delete();
-
-        TaskArtifactState state = repository.getStateFor(task());
-        assertFalse(state.isUpToDate());
-        state.afterTask();
-
-        outputDirFile.write("ignore me");
-
-        state = repository.getStateFor(task());
-        assertTrue(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), not(hasItem((File) outputDirFile)));
-        state.afterTask();
-    }
-
-    @Test
-    public void artifactsAreUpToDateWhenTaskDoesNotAcceptAnyInputs() {
-        TaskInternal task = builder().doesNotAcceptInput().task();
-        execute(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertTrue(state.isUpToDate());
-
-        outputDirFile.delete();
-
-        state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreUpToDateWhenTaskHasNoInputFiles() {
-        TaskInternal task = builder().withInputFiles().task();
-        execute(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void artifactsAreUpToDateWhenTaskHasNoOutputs() {
-        TaskInternal task = builder().withOutputFiles().task();
-        execute(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertTrue(state.isUpToDate());
-    }
-
-    @Test
-    public void taskCanProduceIntoDifferentSetsOfOutputFiles() {
-        TestFile outputDir2 = tmpDir.createDir("output-dir-2");
-        TestFile outputDirFile2 = outputDir2.file("output-file-2");
-        TaskInternal instance1 = builder().withOutputFiles(outputDir).createsFiles(outputDirFile).task();
-        TaskInternal instance2 = builder().withOutputFiles(outputDir2).createsFiles(outputDirFile2).task();
-
-        execute(instance1, instance2);
-
-        TaskArtifactState state = repository.getStateFor(instance1);
-        assertTrue(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), equalTo(toLinkedSet((File) outputDirFile)));
-
-        state = repository.getStateFor(instance2);
-        assertTrue(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), equalTo(toLinkedSet((File) outputDirFile2)));
-    }
-
-    private void execute() {
-        execute(task());
-    }
-
-    private void execute(TaskInternal... tasks) {
-        for (TaskInternal task : tasks) {
-            TaskArtifactState state = repository.getStateFor(task);
-            state.isUpToDate();
-            task.execute();
-            state.afterTask();
-        }
-    }
-
-    private TaskInternal task() {
-        return builder().task();
-    }
-
-    private TaskBuilder builder() {
-        return new TaskBuilder();
-    }
-
-    private class TaskBuilder {
-        private String path = "task";
-        private Collection<? extends File> inputs = inputFiles;
-        private Collection<? extends File> outputs = outputFiles;
-        private Collection<? extends TestFile> create = createFiles;
-        private Class<? extends TaskInternal> type = TaskInternal.class;
-        private Map<String, Object> inputProperties = new HashMap<String, Object>(toMap("prop", "value"));
-
-        TaskBuilder withInputFiles(File... inputFiles) {
-            inputs = Arrays.asList(inputFiles);
-            return this;
-        }
-
-        TaskBuilder withOutputFiles(File... outputFiles) {
-            outputs = Arrays.asList(outputFiles);
-            return this;
-        }
-
-        TaskBuilder createsFiles(TestFile... outputFiles) {
-            create = Arrays.asList(outputFiles);
-            return this;
-        }
-
-        TaskBuilder withPath(String path) {
-            this.path = path;
-            return this;
-        }
-
-        TaskBuilder withType(Class<? extends TaskInternal> type) {
-            this.type = type;
-            return this;
-        }
-
-        TaskBuilder doesNotAcceptInput() {
-            inputs = null;
-            inputProperties = null;
-            return this;
-        }
-
-        public TaskBuilder withProperty(String name, Object value) {
-            inputProperties.put(name, value);
-            return this;
-        }
-
-        TaskInternal task() {
-            final TaskInternal task = HelperUtil.createTask(type, project, path);
-            if (inputs != null) {
-                task.getInputs().files(inputs);
-            }
-            if (inputProperties != null) {
-                task.getInputs().properties(inputProperties);
-            }
-            if (outputs != null) {
-                task.getOutputs().files(outputs);
-            }
-            task.doLast(new org.gradle.api.Action<Object>() {
-                public void execute(Object o) {
-                    for (TestFile file : create) {
-                        file.createFile();
-                    }
-                }
-            });
-
-            return task;
-        }
-    }
-
-    public static class TaskSubType extends DefaultTask {
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/FileCacheBroadcastTaskArtifactStateRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/FileCacheBroadcastTaskArtifactStateRepositoryTest.groovy
deleted file mode 100644
index b7f1794..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/FileCacheBroadcastTaskArtifactStateRepositoryTest.groovy
+++ /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.api.internal.changedetection
-
-import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.TaskInternal
-import org.gradle.api.internal.TaskOutputsInternal
-import org.gradle.api.tasks.TaskInputs
-import spock.lang.Specification
-
-class FileCacheBroadcastTaskArtifactStateRepositoryTest extends Specification {
-    final TaskArtifactStateRepository target = Mock()
-    final TaskArtifactState targetState = Mock()
-    final TaskInternal task = Mock()
-    final TaskInputs taskInputs = Mock()
-    final TaskOutputsInternal taskOutputs = Mock()
-    final FileCollection outputs = Mock()
-    final FileCollection inputs = Mock()
-    final FileCacheListener listener = Mock()
-    final FileCacheBroadcastTaskArtifactStateRepository repository = new FileCacheBroadcastTaskArtifactStateRepository(target, listener)
-
-    def setup() {
-        _ * task.inputs >> taskInputs
-        _ * taskInputs.files >> inputs
-        _ * task.outputs >> taskOutputs
-        _ * taskOutputs.files >> outputs
-    }
-    
-    def marksTaskInputsAndOutputsAsCacheableWhenCheckingUpToDate() {
-        when:
-        def state = repository.getStateFor(task)
-        state.isUpToDate()
-
-        then:
-        1 * listener.cacheable(inputs)
-        1 * listener.cacheable(outputs)
-        1 * target.getStateFor(task) >> targetState
-        1 * targetState.isUpToDate()
-        0 * listener._
-    }
-
-    def invalidatesTaskOutputsWhenTaskIsToBeExecuted() {
-        given:
-        taskOutputs.hasOutput >> true
-
-        when:
-        def state = repository.getStateFor(task)
-        state.beforeTask()
-
-        then:
-        1 * listener.invalidate(outputs)
-        1 * target.getStateFor(task) >> targetState
-        1 * targetState.beforeTask()
-        0 * listener._
-    }
-
-    def invalidatesEverythingWhenTaskWhichDoesNotDeclareAnyOutputsIsToBeExecuted() {
-        given:
-        taskOutputs.hasOutput >> false
-        
-        when:
-        def state = repository.getStateFor(task)
-        state.beforeTask()
-
-        then:
-        1 * listener.invalidateAll()
-        1 * target.getStateFor(task) >> targetState
-        1 * targetState.beforeTask()
-        0 * listener._
-    }
-
-    def marksTaskOutputsAsCacheableAfterTaskHasExecuted() {
-        when:
-        def state = repository.getStateFor(task)
-        state.afterTask()
-
-        then:
-        1 * listener.cacheable(outputs)
-        1 * target.getStateFor(task) >> targetState
-        1 * targetState.afterTask()
-        0 * listener._
-    }
-
-    def delegatesToBackingStateForOtherMethods() {
-        when:
-        def state = repository.getStateFor(task)
-        state.finished()
-
-        then:
-        1 * target.getStateFor(task) >> targetState
-        1 * targetState.finished()
-        0 * listener._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
deleted file mode 100755
index ecec7f5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.changedetection;
-
-import org.gradle.StartParameter;
-import org.gradle.api.Task;
-import org.gradle.api.internal.TaskExecutionHistory;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.specs.Spec;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class ShortCircuitTaskArtifactStateRepositoryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final StartParameter startParameter = new StartParameter();
-    private final TaskArtifactStateRepository delegate = context.mock(TaskArtifactStateRepository.class);
-    private final TaskArtifactState taskArtifactState = context.mock(TaskArtifactState.class);
-    private final TaskOutputsInternal taskOutputsInternal = context.mock(TaskOutputsInternal.class);
-    private final Spec<Task> upToDateSpec = context.mock(Spec.class);
-    private final ShortCircuitTaskArtifactStateRepository repository = new ShortCircuitTaskArtifactStateRepository(startParameter, delegate);
-
-    @Test
-    public void doesNotCreateStateObjectWhenTaskHasNotDeclaredAnyOutputs() {
-        TaskInternal task = taskWithNoOutputs();
-        TaskArtifactState state = repository.getStateFor(task);
-        assertNotNull(state);
-
-        assertFalse(state.isUpToDate());
-        state.beforeTask();
-        state.afterTask();
-        state.finished();
-    }
-    
-    @Test
-    public void delegatesToBackingRepositoryToCreateStateObjectForTaskThatHasDeclaredSomeOutputs() {
-        TaskInternal task = taskWithOutputs();
-        expectTaskStateCreated(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertNotNull(state);
-
-        final TaskExecutionHistory executionHistory = context.mock(TaskExecutionHistory.class);
-
-        context.checking(new Expectations() {{
-            one(taskArtifactState).getExecutionHistory();
-            will(returnValue(executionHistory));
-            one(taskArtifactState).beforeTask();
-            one(taskArtifactState).afterTask();
-            one(taskArtifactState).finished();
-        }});
-
-        assertThat(state.getExecutionHistory(), sameInstance(executionHistory));
-        state.beforeTask();
-        state.afterTask();
-        state.finished();
-    }
-
-    @Test
-    public void taskArtifactsAreOutOfDateWhenStartParameterOverrideNoOptIsSet() {
-        TaskInternal task = taskWithOutputs();
-        expectTaskStateCreated(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-
-        startParameter.setRerunTasks(true);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void taskArtifactsAreOutOfDateWhenStartParameterOverrideRerunTasksIsSet() {
-        TaskInternal task = taskWithOutputs();
-        expectTaskStateCreated(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-
-        startParameter.setRerunTasks(true);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void taskArtifactsAreOutOfDateWhenUpToDateSpecIsFalse() {
-        final TaskInternal task = taskWithOutputs();
-        expectTaskStateCreated(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-
-        context.checking(new Expectations() {{
-            one(upToDateSpec).isSatisfiedBy(task);
-            will(returnValue(false));
-        }});
-
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void determinesWhetherTaskArtifactsAreUpToDateUsingBackingRepository() {
-        final TaskInternal task = taskWithOutputs();
-        expectTaskStateCreated(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-
-        context.checking(new Expectations() {{
-            one(upToDateSpec).isSatisfiedBy(task);
-            will(returnValue(true));
-            one(taskArtifactState).isUpToDate();
-            will(returnValue(true));
-        }});
-
-        assertTrue(state.isUpToDate());
-    }
-
-    private void expectTaskStateCreated(final TaskInternal task) {
-        context.checking(new Expectations() {{
-            one(delegate).getStateFor(task);
-            will(returnValue(taskArtifactState));
-        }});
-    }
-
-    private TaskInternal taskWithOutputs() {
-        final TaskInternal task = context.mock(TaskInternal.class);
-        context.checking(new Expectations() {{
-            allowing(task).getOutputs();
-            will(returnValue(taskOutputsInternal));
-            allowing(taskOutputsInternal).getHasOutput();
-            will(returnValue(true));
-            allowing(taskOutputsInternal).getUpToDateSpec();
-            will(returnValue(upToDateSpec));
-        }});
-
-        return task;
-    }
-
-    private TaskInternal taskWithNoOutputs() {
-        final TaskInternal task = context.mock(TaskInternal.class);
-        context.checking(new Expectations() {{
-            allowing(task).getOutputs();
-            will(returnValue(taskOutputsInternal));
-            allowing(taskOutputsInternal).getHasOutput();
-            will(returnValue(false));
-        }});
-
-        return task;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/changes/DefaultTaskArtifactStateRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/changes/DefaultTaskArtifactStateRepositoryTest.groovy
new file mode 100644
index 0000000..8284894
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/changes/DefaultTaskArtifactStateRepositoryTest.groovy
@@ -0,0 +1,695 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.changes
+
+import org.gradle.CacheUsage
+import org.gradle.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.changedetection.TaskArtifactState
+import org.gradle.api.internal.changedetection.state.*
+import org.gradle.api.internal.hash.DefaultHasher
+import org.gradle.api.tasks.incremental.InputFileDetails
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.internal.CacheScopeMapping
+import org.gradle.cache.internal.DefaultCacheRepository
+import org.gradle.internal.id.RandomLongIdGenerator
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.messaging.serialize.DefaultSerializerRegistry
+import org.gradle.messaging.serialize.SerializerRegistry
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.internal.InMemoryCacheFactory
+import org.gradle.util.TestUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.util.WrapUtil.toMap
+import static org.gradle.util.WrapUtil.toSet
+
+public class DefaultTaskArtifactStateRepositoryTest extends Specification {
+    
+    @Rule
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    final project = TestUtil.createRootProject()
+    final gradle = project.getGradle()
+    final outputFile = tmpDir.file("output-file")
+    final outputDir = tmpDir.file("output-dir")
+    final outputDirFile = outputDir.file("some-file")
+    final outputDirFile2 = outputDir.file("some-file-2")
+    final emptyOutputDir = tmpDir.file("empty-output-dir")
+    final missingOutputFile = tmpDir.file("missing-output-file")
+    final inputFile = tmpDir.createFile("input-file")
+    final inputDir = tmpDir.createDir("input-dir")
+    final inputDirFile = inputDir.file("input-file2").createFile()
+    final missingInputFile = tmpDir.file("missing-input-file")
+    final inputFiles = toSet(inputFile, inputDir, missingInputFile)
+    final outputFiles = toSet(outputFile, outputDir, emptyOutputDir, missingOutputFile)
+    final createFiles = toSet(outputFile, outputDirFile, outputDirFile2)
+    TaskInternal task = builder.task()
+    def mapping = Stub(CacheScopeMapping) {
+        getBaseDirectory(_, _, _) >> {
+            return tmpDir.createDir("history-cache")
+        }
+    }
+    DefaultTaskArtifactStateRepository repository
+
+    def setup() {
+        CacheRepository cacheRepository = new DefaultCacheRepository(mapping, CacheUsage.ON, new InMemoryCacheFactory())
+        TaskArtifactStateCacheAccess cacheAccess = new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository, new NoOpDecorator())
+        FileCollectionSnapshotter inputFilesSnapshotter = new DefaultFileCollectionSnapshotter(new CachingFileSnapshotter(new DefaultHasher(), cacheAccess), cacheAccess)
+        FileCollectionSnapshotter outputFilesSnapshotter = new OutputFilesCollectionSnapshotter(inputFilesSnapshotter, new RandomLongIdGenerator(), cacheAccess)
+        SerializerRegistry<FileCollectionSnapshot> serializerRegistry = new DefaultSerializerRegistry<FileCollectionSnapshot>();
+        inputFilesSnapshotter.registerSerializers(serializerRegistry);
+        outputFilesSnapshotter.registerSerializers(serializerRegistry);
+        TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess, new CacheBackedFileSnapshotRepository(cacheAccess, serializerRegistry.build(), new RandomLongIdGenerator()))
+        repository = new DefaultTaskArtifactStateRepository(taskHistoryRepository, new DirectInstantiator(), outputFilesSnapshotter, inputFilesSnapshotter)
+    }
+
+    def artifactsAreNotUpToDateWhenCacheIsEmpty() {
+        expect:
+        outOfDate(task)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyOutputFileNoLongerExists() {
+        given:
+        execute(task)
+
+        when:
+        outputFile.delete()
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileInOutputDirNoLongerExists() {
+        given:
+        execute(task)
+
+        when:
+        outputDirFile.delete()
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyOutputFileHasChangedType() {
+        given:
+        execute(task)
+
+        when:
+        outputFile.delete()
+        outputFile.createDir()
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileInOutputDirHasChangedType() {
+        given:
+        execute(task)
+
+        when:
+        outputDirFile.delete()
+        outputDirFile.createDir()
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyOutputFileHasChangedHash() {
+        given:
+        execute(task)
+        
+        when:
+        outputFile.write("new content")
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileInOutputDirHasChangedHash() {
+        given:
+        execute(task)
+        
+        when:
+        outputDirFile.write("new content")
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyOutputFilesAddedToSet() {
+        when:
+        execute(task)
+        TaskInternal outputFilesAddedTask = builder.withOutputFiles(outputFile, outputDir, tmpDir.createFile("output-file-2"), emptyOutputDir, missingOutputFile).task()
+
+        then:
+        outOfDate outputFilesAddedTask
+    }
+
+    def artifactsAreNotUpToDateWhenAnyOutputFilesRemovedFromSet() {
+        when:
+        execute(task)
+        TaskInternal outputFilesRemovedTask = builder.withOutputFiles(outputFile, emptyOutputDir, missingOutputFile).task()
+
+        then:
+        outOfDate outputFilesRemovedTask
+    }
+
+    def artifactsAreNotUpToDateWhenTaskWithDifferentTypeGeneratedAnyOutputFiles() {
+        when:
+        TaskInternal task1 = builder.withOutputFiles(outputFile).task()
+        TaskInternal task2 = builder.withType(TaskSubType.class).withOutputFiles(outputFile).task()
+        execute(task1, task2)
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputFilesAddedToSet() {
+        final addedFile = tmpDir.createFile("other-input")
+
+        when:
+        execute(task)
+        TaskInternal inputFilesAdded = builder.withInputFiles(inputFile, inputDir, addedFile, missingInputFile).task()
+
+        then:
+        inputsOutOfDate(inputFilesAdded).withAddedFile(addedFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputFilesRemovedFromSet() {
+        when:
+        execute(task)
+        TaskInternal inputFilesRemoved = builder.withInputFiles(inputFile).task()
+
+        then:
+        inputsOutOfDate(inputFilesRemoved).withRemovedFile(inputDirFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputFileHasChangedHash() {
+        given:
+        execute(task)
+
+        when:
+        inputFile.write("some new content")
+
+        then:
+        inputsOutOfDate(task).withModifiedFile(inputFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputFileHasChangedType() {
+        given:
+        execute(task)
+
+        when:
+        inputFile.delete()
+        inputFile.createDir()
+
+        then:
+        inputsOutOfDate(task).withRemovedFile(inputFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputFileNoLongerExists() {
+        given:
+        execute(task)
+
+        when:
+        inputFile.delete()
+
+        then:
+        inputsOutOfDate(task).withRemovedFile(inputFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileCreatedInInputDir() {
+        given:
+        execute(task)
+
+        when:
+        def file = inputDir.file("other-file").createFile()
+
+        then:
+        inputsOutOfDate(task).withAddedFile(file)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileDeletedFromInputDir() {
+        given:
+        execute(task)
+
+        when:
+        inputDirFile.delete()
+
+        then:
+        inputsOutOfDate(task).withRemovedFile(inputDirFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileInInputDirChangesHash() {
+        given:
+        execute(task)
+
+        when:
+        inputDirFile.writelns("new content")
+
+        then:
+        inputsOutOfDate(task).withModifiedFile(inputDirFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyFileInInputDirChangesType() {
+        given:
+        execute(task)
+
+        when:
+        inputDirFile.delete()
+        inputDirFile.mkdir()
+
+        then:
+        inputsOutOfDate(task).withRemovedFile(inputDirFile)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputPropertyValueChanged() {
+        when:
+        execute(builder.withProperty("prop", "original value").task())
+        final inputPropertiesTask = builder.withProperty("prop", "new value").task()
+
+        then:
+        outOfDate inputPropertiesTask
+    }
+
+    def inputPropertyValueCanBeNull() {
+        when:
+        TaskInternal task = builder.withProperty("prop", null).task()
+        execute(task)
+
+        then:
+        upToDate(task)
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputPropertyAdded() {
+        when:
+        execute(task)
+        final addedPropertyTask = builder.withProperty("prop2", "value").task()
+
+        then:
+        outOfDate addedPropertyTask
+    }
+
+    def artifactsAreNotUpToDateWhenAnyInputPropertyRemoved() {
+        given:
+        execute(builder.withProperty("prop2", "value").task())
+
+        expect:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenStateHasNotBeenUpdated() {
+        when:
+        repository.getStateFor(task)
+
+        then:
+        outOfDate task
+    }
+
+    def artifactsAreNotUpToDateWhenOutputDirWhichUsedToExistHasBeenDeleted() {
+        given:
+        // Output dir already exists before first execution of task
+        outputDirFile.createFile()
+
+        TaskInternal task1 = builder.withOutputFiles(outputDir).createsFiles(outputDirFile).task()
+        TaskInternal task2 = builder.withPath("other").withOutputFiles(outputDir).createsFiles(outputDirFile2).task()
+        
+        when:
+        TaskArtifactState state = repository.getStateFor(task1)
+        state.afterTask()
+        
+        then:
+        !state.upToDate
+        
+        when:
+        outputDir.deleteDir()
+
+        and:
+        // Another task creates dir
+        state = repository.getStateFor(task2)
+
+        then:
+        !state.isUpToDate([])
+        
+        when:
+        task2.execute()
+        state.afterTask()
+        
+        then:
+        // Task should be out-of-date
+        outOfDate task1
+    }
+
+    def artifactsAreUpToDateWhenNothingHasChangedSinceOutputFilesWereGenerated() {
+        given:
+        execute(task)
+
+        expect:
+        repository.getStateFor(task).isUpToDate([])
+        repository.getStateFor(task).isUpToDate([])
+    }
+
+    def artifactsAreUpToDateWhenOutputFileWhichDidNotExistNowExists() {
+        given:
+        execute(task)
+
+        when:
+        missingOutputFile.touch()
+
+        then:
+        upToDate task
+    }
+
+    def artifactsAreUpToDateWhenOutputDirWhichWasEmptyIsNoLongerEmpty() {
+        given:
+        execute(task)
+
+        when:
+        emptyOutputDir.file("some-file").touch()
+
+        then:
+        upToDate task
+    }
+
+    def hasEmptyTaskHistoryWhenTaskHasNeverBeenExecuted() {
+        when:
+        TaskArtifactState state = repository.getStateFor(task)
+        
+        then:
+        state.getExecutionHistory().getOutputFiles().getFiles().isEmpty()
+    }
+
+    def hasTaskHistoryFromPreviousExecution() {
+        given:
+        execute(task)
+
+        when:
+        TaskArtifactState state = repository.getStateFor(task)
+        
+        then:
+        state.getExecutionHistory().getOutputFiles().getFiles() == [outputFile, outputDirFile, outputDirFile2] as Set
+    }
+
+    def multipleTasksCanProduceFilesIntoTheSameOutputDirectory() {
+        when:
+        TaskInternal task1 = task
+        TaskInternal task2 = builder.withPath("other").withOutputFiles(outputDir).createsFiles(outputDir.file("output2")).task()
+        execute(task1, task2)
+
+        then:
+        upToDate task1
+        upToDate task2
+    }
+
+    def multipleTasksCanProduceTheSameFileWithTheSameContents() {
+        when:
+        TaskInternal task1 = builder.withOutputFiles(outputFile).task()
+        TaskInternal task2 = builder.withPath("other").withOutputFiles(outputFile).task()
+        execute(task1, task2)
+
+        then:
+        upToDate task1
+        upToDate task2
+    }
+
+    def multipleTasksCanProduceTheSameEmptyDir() {
+        when:
+        TaskInternal task1 = task
+        TaskInternal task2 = builder.withPath("other").withOutputFiles(outputDir).task()
+        execute(task1, task2)
+
+        then:
+        upToDate task1
+        upToDate task2
+    }
+
+    def doesNotConsiderExistingFilesInOutputDirectoryAsProducedByTask() {
+        when:
+        TestFile otherFile = outputDir.file("other").createFile()
+        execute(task)
+        otherFile.delete()
+
+        then:
+        TaskArtifactState state = repository.getStateFor(task)
+        state.isUpToDate([])
+        !state.getExecutionHistory().getOutputFiles().getFiles().contains(otherFile)
+    }
+
+    def considersExistingFileInOutputDirectoryWhichIsUpdatedByTheTaskAsProducedByTask() {
+        when:
+        TestFile otherFile = outputDir.file("other").createFile()
+        TaskArtifactState state = repository.getStateFor(task)
+
+        then:
+        !state.isUpToDate([])
+
+        when:
+        task.execute()
+        otherFile.write("new content")
+        state.afterTask()
+        otherFile.delete()
+
+        then:
+        def stateAfter = repository.getStateFor(task)
+        !stateAfter.upToDate
+        stateAfter.executionHistory.outputFiles.files.contains(otherFile)
+    }
+
+    def fileIsNoLongerConsideredProducedByTaskOnceItIsDeleted() {
+        given:
+        execute(task)
+
+        outputDirFile.delete()
+        TaskArtifactState state = repository.getStateFor(task)
+        state.afterTask()
+
+        when:
+        outputDirFile.write("ignore me")
+
+        then:
+        def stateAfter = repository.getStateFor(task)
+        stateAfter.isUpToDate([])
+        !stateAfter.executionHistory.outputFiles.files.contains(outputDirFile)
+    }
+
+    def artifactsAreUpToDateWhenTaskDoesNotAcceptAnyInputs() {
+        when:
+        TaskInternal noInputsTask = builder.doesNotAcceptInput().task()
+        execute(noInputsTask)
+
+        then:
+        upToDate noInputsTask
+
+        when:
+        outputDirFile.delete()
+
+        then:
+        outOfDate noInputsTask
+    }
+
+    def artifactsAreUpToDateWhenTaskHasNoInputFiles() {
+        when:
+        TaskInternal noInputFilesTask = builder.withInputFiles().task()
+        execute(noInputFilesTask)
+
+        then:
+        upToDate noInputFilesTask
+    }
+
+    def artifactsAreUpToDateWhenTaskHasNoOutputFiles() {
+        when:
+        TaskInternal noOutputsTask = builder.withOutputFiles().task()
+        execute(noOutputsTask)
+
+        then:
+        upToDate noOutputsTask
+    }
+
+    def taskCanProduceIntoDifferentSetsOfOutputFiles() {
+        when:
+        TestFile outputDir2 = tmpDir.createDir("output-dir-2")
+        TestFile outputDirFile2 = outputDir2.file("output-file-2")
+        TaskInternal task1 = builder.withOutputFiles(outputDir).createsFiles(outputDirFile).task()
+        TaskInternal task2 = builder.withOutputFiles(outputDir2).createsFiles(outputDirFile2).task()
+
+        execute(task1, task2)
+
+        then:
+        def state1 = repository.getStateFor(task1)
+        state1.isUpToDate([])
+        state1.executionHistory.outputFiles.files == [outputDirFile] as Set
+
+        and:
+        def state2 = repository.getStateFor(task2)
+        state2.isUpToDate([])
+        state2.executionHistory.outputFiles.files == [outputDirFile2] as Set
+    }
+
+    private void outOfDate(TaskInternal task) {
+        final state = repository.getStateFor(task)
+        assert !state.upToDate
+        assert !state.inputChanges.incremental
+    }
+
+    def inputsOutOfDate(TaskInternal task) {
+        final state = repository.getStateFor(task)
+        assert !state.upToDate
+
+        final inputChanges = state.inputChanges
+        assert inputChanges.incremental
+
+        final changedFiles = new ChangedFiles()
+        inputChanges.outOfDate(new Action<InputFileDetails>() {
+            void execute(InputFileDetails t) {
+                if (t.added) {
+                    println "Added: " + t.file
+                    changedFiles.added << t.file
+                } else if (t.modified) {
+                    println "Modified: " + t.file
+                    changedFiles.modified << t.file
+                } else {
+                    assert false : "Not a valid change"
+                }
+            }
+        })
+        inputChanges.removed(new Action<InputFileDetails>() {
+            void execute(InputFileDetails t) {
+                println "Removed: " + t.file
+                assert t.removed
+                changedFiles.removed << t.file
+            }
+        })
+
+        return changedFiles
+    }
+
+    private void upToDate(TaskInternal task) {
+        final state = repository.getStateFor(task)
+        assert state.isUpToDate([])
+    }
+
+    private void execute(TaskInternal... tasks) {
+        for (TaskInternal task : tasks) {
+            TaskArtifactState state = repository.getStateFor(task)
+            state.isUpToDate([])
+            task.execute()
+            state.afterTask()
+        }
+    }
+
+    private static class ChangedFiles {
+        def added = []
+        def modified = []
+        def removed = []
+
+        void withAddedFile(File file) {
+            assert added == [file]
+            assert modified == []
+            assert removed == []
+        }
+
+        void withModifiedFile(File file) {
+            assert added == []
+            assert modified == [file]
+            assert removed == []
+        }
+
+        void withRemovedFile(File file) {
+            assert added == []
+            assert modified == []
+            assert removed == [file]
+        }
+    }
+
+    private TaskBuilder getBuilder() {
+        return new TaskBuilder()
+    }
+
+    private class TaskBuilder {
+        private String path = "task"
+        private Collection<? extends File> inputs = inputFiles
+        private Collection<? extends File> outputs = outputFiles
+        private Collection<? extends TestFile> create = createFiles
+        private Class<? extends TaskInternal> type = TaskInternal.class
+        private Map<String, Object> inputProperties = new HashMap<String, Object>(toMap("prop", "value"))
+
+        TaskBuilder withInputFiles(File... inputFiles) {
+            inputs = Arrays.asList(inputFiles)
+            return this
+        }
+
+        TaskBuilder withOutputFiles(File... outputFiles) {
+            outputs = Arrays.asList(outputFiles)
+            return this
+        }
+
+        TaskBuilder createsFiles(TestFile... outputFiles) {
+            create = Arrays.asList(outputFiles)
+            return this
+        }
+
+        TaskBuilder withPath(String path) {
+            this.path = path
+            return this
+        }
+
+        TaskBuilder withType(Class<? extends TaskInternal> type) {
+            this.type = type
+            return this
+        }
+
+        TaskBuilder doesNotAcceptInput() {
+            inputs = null
+            inputProperties = null
+            return this
+        }
+
+        public TaskBuilder withProperty(String name, Object value) {
+            inputProperties.put(name, value)
+            return this
+        }
+
+        TaskInternal task() {
+            final TaskInternal task = TestUtil.createTask(type, project, path)
+            if (inputs != null) {
+                task.getInputs().files(inputs)
+            }
+            if (inputProperties != null) {
+                task.getInputs().properties(inputProperties)
+            }
+            if (outputs != null) {
+                task.getOutputs().files(outputs)
+            }
+            task.doLast(new org.gradle.api.Action<Object>() {
+                public void execute(Object o) {
+                    for (TestFile file : create) {
+                        file.createFile()
+                    }
+                }
+            })
+
+            return task
+        }
+    }
+
+    public static class TaskSubType extends DefaultTask {
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/changes/ShortCircuitTaskArtifactStateRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/changes/ShortCircuitTaskArtifactStateRepositoryTest.groovy
new file mode 100755
index 0000000..86723cb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/changes/ShortCircuitTaskArtifactStateRepositoryTest.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.changes
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.api.internal.changedetection.TaskArtifactState
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository
+import org.gradle.api.specs.Spec
+import org.gradle.internal.reflect.DirectInstantiator
+import spock.lang.Specification
+
+public class ShortCircuitTaskArtifactStateRepositoryTest extends Specification {
+    StartParameter startParameter = new StartParameter()
+    TaskArtifactStateRepository delegate = Mock(TaskArtifactStateRepository)
+    ShortCircuitTaskArtifactStateRepository repository = new ShortCircuitTaskArtifactStateRepository(startParameter, new DirectInstantiator(), delegate)
+    TaskArtifactState taskArtifactState = Mock(TaskArtifactState)
+    TaskInternal task = Mock(TaskInternal)
+    TaskOutputsInternal outputs = Mock(TaskOutputsInternal)
+    Spec upToDateSpec = Mock(Spec)
+
+    def doesNotLoadHistoryWhenTaskHasNoDeclaredOutputs() {
+        def messages = []
+
+        when:
+        TaskArtifactState state = repository.getStateFor(task);
+
+        then:
+        1 * task.getOutputs() >> outputs
+        1 * outputs.getHasOutput() >> false
+        0 * _
+
+        and:
+        state instanceof NoHistoryArtifactState
+        !state.isUpToDate(messages)
+        !messages.empty
+    }
+
+    def delegatesDirectToBackingRepositoryWithoutRerunTasks() {
+        when:
+        TaskArtifactState state = repository.getStateFor(task);
+
+        then:
+        2 * task.getOutputs() >> outputs
+        1 * outputs.getHasOutput() >> true
+        1 * outputs.getUpToDateSpec() >> upToDateSpec
+        1 * upToDateSpec.isSatisfiedBy(task) >> true
+
+        and:
+        1 * delegate.getStateFor(task) >> taskArtifactState
+        state == taskArtifactState
+    }
+
+    def taskArtifactsAreAlwaysOutOfDateWithRerunTasks() {
+        def messages = []
+
+        when:
+        startParameter.setRerunTasks(true);
+        def state = repository.getStateFor(task)
+
+        then:
+        1 * task.getOutputs() >> outputs
+        1 * outputs.getHasOutput() >> true
+        1 * delegate.getStateFor(task) >> taskArtifactState
+        0 * taskArtifactState._
+
+        and:
+        !state.isUpToDate(messages)
+        !messages.empty
+
+        and:
+        !state.inputChanges.incremental
+    }
+
+    def taskArtifactsAreAlwaysOutOfDateWhenUpToDateSpecReturnsFalse() {
+        def messages = []
+
+        when:
+        def state = repository.getStateFor(task)
+
+        then:
+        2 * task.getOutputs() >> outputs
+        1 * outputs.getHasOutput() >> true
+        1 * outputs.getUpToDateSpec() >> upToDateSpec
+        1 * upToDateSpec.isSatisfiedBy(task) >> false
+
+        and:
+        1 * delegate.getStateFor(task) >> taskArtifactState
+        0 * taskArtifactState._
+
+        and:
+        !state.isUpToDate(messages)
+        !messages.empty
+
+        and:
+        !state.inputChanges.incremental
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/CachingTaskStateChangesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/CachingTaskStateChangesTest.groovy
new file mode 100644
index 0000000..3acfff7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/CachingTaskStateChangesTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules;
+
+import spock.lang.Specification;
+
+public class CachingTaskStateChangesTest extends Specification {
+    def delegate = Mock(TaskStateChanges)
+    def change1 = Mock(TaskStateChange)
+    def change2 = Mock(TaskStateChange)
+    def change3 = Mock(TaskStateChange)
+
+    def cachingChanges = new CachingTaskStateChanges(2, delegate)
+
+    def "delegates to underlying instance"() {
+        when:
+        def reported = cachingChanges.iterator().collect()
+
+        then:
+        delegate.iterator() >> [change1, change2, change3].iterator()
+
+        and:
+        reported == [change1, change2, change3]
+
+        when:
+        cachingChanges.snapshotAfterTask()
+
+        then:
+        delegate.snapshotAfterTask()
+    }
+
+    def "caches all reported changes under cache size"() {
+        when:
+        cachingChanges.iterator().collect()
+
+        then:
+        1 * delegate.iterator() >> [change1, change2].iterator()
+        0 * _
+
+        when:
+        cachingChanges.iterator().collect()
+        def reported = cachingChanges.iterator().collect()
+
+        then:
+        0 * _
+
+        and:
+        reported == [change1, change2]
+    }
+
+    def "does not cache once reported changes exceed cache size"() {
+        when:
+        cachingChanges.iterator().collect()
+
+        then:
+        1 * delegate.iterator() >> [change1, change2, change3].iterator()
+        0 * _
+
+        when:
+        cachingChanges.iterator().collect()
+        def reported = cachingChanges.iterator().collect()
+
+        then:
+        2 * delegate.iterator() >> [change1, change2, change3].iterator() >> [change3, change2, change1].iterator()
+        0 * _
+
+        and:
+        reported == [change3, change2, change1]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/InputFilesStateChangeRuleTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/InputFilesStateChangeRuleTest.groovy
new file mode 100644
index 0000000..16740e3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/InputFilesStateChangeRuleTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshot
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshotter
+import org.gradle.api.internal.changedetection.state.TaskExecution
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.tasks.TaskInputs
+import org.gradle.util.ChangeListener
+import spock.lang.Specification
+
+public class InputFilesStateChangeRuleTest extends Specification {
+    def inputSnapshot = Mock(FileCollectionSnapshot)
+    def previousInputSnapshot = Mock(FileCollectionSnapshot)
+    FileCollectionSnapshot.ChangeIterator<String> changeIterator = Mock()
+
+    TaskStateChanges createStateChanges() {
+        def taskInputs = Stub(TaskInputs) {
+            getFiles() >> new SimpleFileCollection()
+        }
+        def task = Stub(TaskInternal) {
+            getInputs() >> taskInputs
+        }
+        def snapshotter = Stub(FileCollectionSnapshotter) {
+            snapshot(_) >> inputSnapshot
+        }
+
+        def previousExecution = Stub(TaskExecution) {
+            getInputFilesSnapshot() >> previousInputSnapshot
+        }
+        return InputFilesStateChangeRule.create(task, previousExecution, Mock(TaskExecution), snapshotter)
+    }
+
+    def "emits change for no previous input snapshot"() {
+        when:
+        previousInputSnapshot = null
+        def messages = createStateChanges().iterator().collect {it.message}
+
+        then:
+        messages == ["Input file history is not available."]
+    }
+
+    def "emits change for file changes since previous input snapshot"() {
+        when:
+        def messages = createStateChanges().iterator().collect {it.message}
+
+        then:
+        1 * inputSnapshot.iterateChangesSince(previousInputSnapshot) >> changeIterator
+        4 * changeIterator.next(_ as ChangeListener) >> { ChangeListener listener ->
+            listener.added("one")
+            true
+        } >> { ChangeListener listener ->
+            listener.removed("two")
+            true
+        } >> { ChangeListener listener ->
+            listener.changed("three")
+            true
+        } >> false
+
+        and:
+        messages == ["Input file one has been added.", "Input file two has been removed.", "Input file three has changed."]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/OutputFilesStateChangeRuleTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/OutputFilesStateChangeRuleTest.groovy
new file mode 100644
index 0000000..24d7c97
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/OutputFilesStateChangeRuleTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.rules
+
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshot
+import org.gradle.api.internal.changedetection.state.FileCollectionSnapshotter
+import org.gradle.api.internal.changedetection.state.TaskExecution
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.util.ChangeListener
+import spock.lang.Specification;
+
+public class OutputFilesStateChangeRuleTest extends Specification {
+    def outputSnapshot = Mock(FileCollectionSnapshot)
+    def previousOutputSnapshot = Mock(FileCollectionSnapshot)
+
+    TaskStateChanges createStateChanges() {
+        def taskOutputs = Stub(TaskOutputsInternal) {
+            getFiles() >> new SimpleFileCollection()
+        }
+        def task = Stub(TaskInternal) {
+            getOutputs() >> taskOutputs
+        }
+        def snapshotter = Stub(FileCollectionSnapshotter) {
+            snapshot(_) >> outputSnapshot
+        }
+
+        def previousExecution = Stub(TaskExecution) {
+            getOutputFilesSnapshot() >> previousOutputSnapshot
+        }
+        return OutputFilesStateChangeRule.create(task, previousExecution, Mock(TaskExecution), snapshotter)
+    }
+
+    def "emits change for no previous output snapshot"() {
+        when:
+        previousOutputSnapshot = null
+        def it = createStateChanges().iterator()
+
+        then:
+        it.hasNext()
+        it.next().message == "Output file history is not available."
+        !it.hasNext()
+    }
+
+    def "emits change for file changes since previous output snapshot"() {
+        FileCollectionSnapshot.ChangeIterator<String> changeIterator = Mock()
+        when:
+        def it = createStateChanges().iterator()
+        def messages = it.collect {it.message}
+
+        then:
+        1 * outputSnapshot.iterateChangesSince(previousOutputSnapshot) >> changeIterator
+        4 * changeIterator.next(_ as ChangeListener<String>) >> { ChangeListener listener ->
+            listener.added("one")
+            true
+        } >> { ChangeListener listener ->
+            listener.removed("two")
+            true
+        } >> { ChangeListener listener ->
+            listener.changed("three")
+            true
+        } >> false
+
+        and:
+        messages == ["Output file one has been added.", "Output file two has been removed.", "Output file three has changed."]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/SimpleTaskStateChangesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/SimpleTaskStateChangesTest.groovy
new file mode 100644
index 0000000..5c89d6f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/SimpleTaskStateChangesTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.internal.changedetection.rules
+
+import spock.lang.Specification;
+
+public class SimpleTaskStateChangesTest extends Specification {
+    def simpleTaskStateChanges = new TestSimpleTaskStateChanges()
+    def change1 = Mock(TaskStateChange)
+    def change2 = Mock(TaskStateChange)
+
+    def "fires all changes"() {
+        when:
+        final iterator = simpleTaskStateChanges.iterator()
+
+        then:
+        iterator.hasNext()
+        iterator.next() == change1
+        iterator.hasNext()
+        iterator.next() == change2
+    }
+
+    def "caches all changes"() {
+        when:
+        simpleTaskStateChanges.iterator().next()
+        simpleTaskStateChanges.iterator().next()
+
+        then:
+        simpleTaskStateChanges.addAllCount == 1
+    }
+
+    private class TestSimpleTaskStateChanges extends SimpleTaskStateChanges {
+        int addAllCount;
+        @Override
+        protected void addAllChanges(List<TaskStateChange> changes) {
+            changes.addAll([change1, change2])
+            addAllCount++
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/SummaryTaskStateChangesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/SummaryTaskStateChangesTest.groovy
new file mode 100644
index 0000000..1fb1171
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/rules/SummaryTaskStateChangesTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.internal.changedetection.rules
+
+import spock.lang.Specification
+
+class SummaryTaskStateChangesTest extends Specification {
+
+    def state1 = Mock(TaskStateChanges)
+    def state2 = Mock(TaskStateChanges)
+    def state = new SummaryTaskStateChanges(2, state1, state2)
+    def change = Mock(TaskStateChange)
+
+    def looksForChangesInAllDelegateChangeSets() {
+        when:
+        def hasNext = state.iterator().hasNext()
+
+        then:
+        1 * state1.iterator() >> [].iterator()
+        1 * state2.iterator() >> [].iterator()
+        0 * _
+
+        and:
+        !hasNext
+    }
+
+    def delegatesSnapshotToAllDelegateChangeSets() {
+        when:
+        state.snapshotAfterTask()
+
+        then:
+        1 * state1.snapshotAfterTask()
+        1 * state2.snapshotAfterTask()
+        0 * _
+    }
+
+    def onlyReturnsChangesFromASingleDelegate() {
+        def change1 = Mock(TaskStateChange)
+
+        when:
+        def it = state.iterator()
+        it.hasNext()
+
+        then:
+        1 * state1.iterator() >> [change1].iterator()
+        0 * _
+
+        and:
+        it.hasNext()
+        it.next() == change1
+        !it.hasNext()
+    }
+
+    def willNotEmitMoreChangesThanSpecified() {
+        def change1 = Mock(TaskStateChange)
+        def change2 = Mock(TaskStateChange)
+        def change3 = Mock(TaskStateChange)
+
+        when:
+        def it = state.iterator()
+        it.hasNext()
+
+        then:
+        1 * state1.iterator() >> [].iterator()
+        1 * state2.iterator() >> [change1, change2, change3].iterator()
+
+        and:
+        it.hasNext()
+        it.next() == change1
+        it.hasNext()
+        it.next() == change2
+        !it.hasNext()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/CacheBackedFileSnapshotRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/CacheBackedFileSnapshotRepositoryTest.groovy
new file mode 100644
index 0000000..7e81421
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/CacheBackedFileSnapshotRepositoryTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.cache.PersistentIndexedCache
+import org.gradle.internal.id.IdGenerator
+import org.gradle.messaging.serialize.Serializer
+import spock.lang.Specification
+
+class CacheBackedFileSnapshotRepositoryTest extends Specification {
+    final TaskArtifactStateCacheAccess cacheAccess = Mock()
+    final PersistentIndexedCache<Object, Object> indexedCache = Mock()
+    final IdGenerator<Long> idGenerator = Mock()
+    final Serializer<FileCollectionSnapshot> serializer = Mock()
+    FileSnapshotRepository repository
+
+    def setup() {
+        1 * cacheAccess.createCache("fileSnapshots", _, _) >> indexedCache
+        repository = new CacheBackedFileSnapshotRepository(cacheAccess, serializer, idGenerator)
+    }
+
+    def "assigns an id when a snapshot is added"() {
+        FileCollectionSnapshot snapshot = Mock()
+
+        when:
+        def id = repository.add(snapshot)
+
+        then:
+        id == 15
+        1 * idGenerator.generateId() >> 15L
+        1 * indexedCache.put(15, snapshot)
+        0 * _._
+    }
+
+    def "can fetch a snapshot by id"() {
+        FileCollectionSnapshot snapshot = Mock()
+
+        when:
+        def result = repository.get(4)
+
+        then:
+        result == snapshot
+        1 * indexedCache.get(4) >> snapshot
+        0 * _._
+    }
+
+    def "can delete a snapshot by id"() {
+        when:
+        repository.remove(4)
+
+        then:
+        1 * indexedCache.remove(4)
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/CachingFileSnapshotterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/CachingFileSnapshotterTest.groovy
new file mode 100644
index 0000000..c1f5acc
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/CachingFileSnapshotterTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.api.internal.hash.Hasher
+import org.gradle.cache.PersistentIndexedCache
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class CachingFileSnapshotterTest extends Specification {
+    @Rule
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    def target = Mock(Hasher)
+    def cache = Mock(PersistentIndexedCache)
+    def cacheAccess = Mock(TaskArtifactStateCacheAccess)
+    def byte[] hash = "hash".bytes
+    def file = tmpDir.createFile("testfile")
+    CachingFileSnapshotter hasher
+
+    def setup() {
+        file.write("some-content")
+        1 * cacheAccess.createCache("fileHashes", _, _) >> cache
+        hasher = new CachingFileSnapshotter(target, cacheAccess);
+    }
+
+    def hashesFileWhenHashNotCached() {
+        when:
+        def result = hasher.snapshot(file)
+
+        then:
+        result.hash == hash
+
+        and:
+        1 * cache.get(file) >> null
+        1 * target.hash(file) >> hash
+        1 * cache.put(file, _) >> { File key, CachingFileSnapshotter.FileInfo fileInfo ->
+            fileInfo.hash == hash
+            fileInfo.length == file.length()
+            fileInfo.timestamp == file.lastModified()
+        }
+        0 * _._
+    }
+
+    def hashesFileWhenLengthHasChanged() {
+        when:
+        def result = hasher.snapshot(file)
+
+        then:
+        result.hash == hash
+
+        and:
+        1 * cache.get(file) >> new CachingFileSnapshotter.FileInfo(hash, 1024, file.lastModified())
+        1 * target.hash(file) >> hash
+        1 * cache.put(file, _) >> { File key, CachingFileSnapshotter.FileInfo fileInfo ->
+            fileInfo.hash == hash
+            fileInfo.length == file.length()
+            fileInfo.timestamp == file.lastModified()
+        }
+        0 * _._
+    }
+
+    def hashesFileWhenTimestampHasChanged() {
+        when:
+        def result = hasher.snapshot(file)
+
+        then:
+        result.hash == hash
+
+        and:
+        1 * cache.get(file) >> new CachingFileSnapshotter.FileInfo(hash, file.length(), 124)
+        1 * target.hash(file) >> hash
+        1 * cache.put(file, _) >> { File key, CachingFileSnapshotter.FileInfo fileInfo ->
+            fileInfo.hash == hash
+            fileInfo.length == file.length()
+            fileInfo.timestamp == file.lastModified()
+        }
+        0 * _._
+    }
+
+    def doesNotHashFileWhenTimestampAndLengthHaveNotChanged() {
+        when:
+        def result = hasher.snapshot(file)
+
+        then:
+        result.hash == hash
+
+        and:
+        1 * cache.get(file) >> new CachingFileSnapshotter.FileInfo(hash, file.length(), file.lastModified())
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultFileCollectionSnapshotterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultFileCollectionSnapshotterTest.groovy
new file mode 100755
index 0000000..b66dcbd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultFileCollectionSnapshotterTest.groovy
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTree
+import org.gradle.internal.hash.HashUtil
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.ChangeListener
+import org.junit.Rule
+import spock.lang.Specification
+
+public class DefaultFileCollectionSnapshotterTest extends Specification {
+    def fileSnapshotter = Stub(FileSnapshotter)
+    def cacheAccess = Stub(TaskArtifactStateCacheAccess)
+    def snapshotter = new DefaultFileCollectionSnapshotter(fileSnapshotter, cacheAccess)
+
+    def listener = Mock(ChangeListener)
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    def setup() {
+        fileSnapshotter.snapshot(_) >> { File file ->
+            return Stub(FileSnapshotter.FileSnapshot) {
+                getHash() >> HashUtil.sha1(file).asByteArray()
+            }
+        }
+        cacheAccess.useCache(_, _) >> { String name, Runnable action ->
+            action.run()
+        }
+    }
+
+    def getFilesReturnsOnlyTheFilesWhichExisted() {
+        given:
+        TestFile file = tmpDir.createFile('file1')
+        TestFile dir = tmpDir.createDir('file2')
+        TestFile noExist = tmpDir.file('file3')
+
+        when:
+        def snapshot = snapshotter.snapshot(files(file, dir, noExist))
+
+        then:
+        snapshot.files.files as List == [file]
+    }
+    
+    def notifiesListenerWhenFileAdded() {
+        given:
+        TestFile file1 = tmpDir.createFile('file1')
+        TestFile file2 = tmpDir.createFile('file2')
+
+        when:
+        def snapshot = snapshotter.snapshot(files(file1))
+        snapshotter.snapshot(files(file1, file2)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.added(file2.path)
+        0 * _
+    }
+
+    def notifiesListenerWhenFileRemoved() {
+        given:
+        TestFile file1 = tmpDir.createFile('file1')
+        TestFile file2 = tmpDir.createFile('file2')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1, file2))
+        snapshotter.snapshot(files(file1)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.removed(file2.path)
+        0 * _
+    }
+
+    def fileHasNotChangedWhenTypeAndHashHaveNotChanged() {
+        given:
+        TestFile file = tmpDir.createFile('file')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+        snapshotter.snapshot(files(file)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * listener._
+    }
+
+    def fileHasChangedWhenTypeHasChanged() {
+        given:
+        TestFile file = tmpDir.createFile('file')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+        file.delete()
+        file.createDir()
+        snapshotter.snapshot(files(file)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.changed(file.path)
+
+        and:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * _
+    }
+
+    def fileHasChangedWhenHashHasChanged() {
+        TestFile file = tmpDir.createFile('file')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+        file.write('new content')
+        snapshotter.snapshot(files(file)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.changed(file.path)
+
+        and:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * _
+    }
+
+    def directoryHasNotChangedWhenTypeHasNotChanged() {
+        TestFile dir = tmpDir.createDir('dir')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(dir))
+
+        snapshotter.snapshot(files(dir)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * _
+    }
+
+    def directoryHasChangedWhenTypeHasChanged() {
+        TestFile dir = tmpDir.createDir('dir')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(dir))
+        dir.deleteDir()
+        dir.createFile()
+        snapshotter.snapshot(files(dir)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.changed(dir.path)
+    }
+
+    def nonExistentFileUnchangedWhenTypeHasNotChanged() {
+        TestFile file = tmpDir.file('unknown')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+        snapshotter.snapshot(files(file)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * _
+    }
+
+    def nonExistentFileIsChangedWhenTypeHasChanged() {
+        TestFile file = tmpDir.file('unknown')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+        file.createFile()
+        snapshotter.snapshot(files(file)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.changed(file.path)
+    }
+
+    def ignoresDuplicatesInFileCollection() {
+        TestFile file1 = tmpDir.createFile('file')
+        TestFile file2 = tmpDir.createFile('file')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1, file2))
+        snapshotter.snapshot(files(file1)).iterateChangesSince(snapshot).next(listener)
+
+        then:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * _
+    }
+
+    def canCreateEmptySnapshot() {
+        TestFile file = tmpDir.createFile('file')
+
+        when:
+        FileCollectionSnapshot snapshot = snapshotter.emptySnapshot()
+        FileCollectionSnapshot newSnapshot = snapshotter.snapshot(files(file))
+        newSnapshot.iterateChangesSince(snapshot).next(listener)
+
+        then:
+        1 * listener.added(file.path)
+    }
+
+    def diffAddsAddedFilesToSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+
+        given:
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.emptySnapshot()
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        when:
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
+
+        then:
+        1 * mergeListener.added(_)
+
+        when:
+        target.iterateChangesSince(original).next(listener)
+
+        then:
+        1 * listener.added(file.path)
+    }
+
+    def canIgnoreAddedFileInDiff() {
+        TestFile file = tmpDir.createFile('file')
+
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+        FileCollectionSnapshot original = snapshotter.emptySnapshot()
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        when:
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
+        target.iterateChangesSince(original).next(listener)
+
+        then:
+        mergeListener.added(!null) >> { FileCollectionSnapshot.Merge merge -> merge.ignore() }
+    }
+
+    def diffAddsChangedFilesToSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        file.write('new content')
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        when:
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
+
+        then:
+        1 * mergeListener.changed(!null)
+
+        when:
+        target.iterateChangesSince(original).next(listener)
+
+        then:
+        1 * listener.changed(file.path)
+    }
+
+    def canIgnoreChangedFileInDiff() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+
+        when:
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot target = snapshotter.snapshot(files(file))
+        file.write('new content')
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        and:
+        target = modified.changesSince(original).applyTo(target, mergeListener)
+        target.iterateChangesSince(original).next(listener)
+
+        then:
+        1 * mergeListener.changed(!null) >> { FileCollectionSnapshot.Merge merge -> merge.ignore() }
+    }
+
+    def diffRemovesDeletedFilesFromSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+
+        when:
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot modified = snapshotter.emptySnapshot()
+
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(files(file)), mergeListener)
+
+        then:
+        1 * mergeListener.removed(!null)
+
+        when:
+        target.iterateChangesSince(original).next(listener)
+
+        then:
+        1 * listener.removed(file.path)
+    }
+
+    def canIgnoreRemovedFileInDiff() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+
+        when:
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot modified = snapshotter.emptySnapshot()
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(files(file)), mergeListener)
+
+        target.iterateChangesSince(original).next(listener)
+
+        then:
+        mergeListener.removed(!null) >> { FileCollectionSnapshot.Merge merge -> merge.ignore() }
+    }
+
+    def diffIgnoresUnchangedFilesInSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = Mock(ChangeListener.class)
+
+        when:
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.emptySnapshot(), mergeListener)
+
+        target.iterateChangesSince(snapshotter.emptySnapshot()).next(listener)
+
+        then:
+        _ * listener.stopped >> false
+        _ * listener.resumeAfter >> null
+        0 * _
+    }
+
+    private FileCollection files(File... files) {
+        FileTree collection = Mock(FileTree.class)
+        _ * collection.asFileTree >> collection
+        _ * collection.getFiles() >> files
+        return collection
+    }
+    
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultFileSnapshotterSerializerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultFileSnapshotterSerializerTest.groovy
new file mode 100644
index 0000000..607e47d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultFileSnapshotterSerializerTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.messaging.serialize.SerializerSpec
+
+class DefaultFileSnapshotterSerializerTest extends SerializerSpec {
+
+    def serializer = new DefaultFileSnapshotterSerializer()
+
+    def "reads and writes the snapshot"() {
+        when:
+        DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl out = serialize(new DefaultFileCollectionSnapshotter.FileCollectionSnapshotImpl([
+                "1": new DefaultFileCollectionSnapshotter.DirSnapshot(),
+                "2": new DefaultFileCollectionSnapshotter.MissingFileSnapshot(),
+                "3": new DefaultFileCollectionSnapshotter.FileHashSnapshot("foo".bytes)]), serializer)
+
+        then:
+        out.snapshots.size() == 3
+        out.snapshots['1'] instanceof DefaultFileCollectionSnapshotter.DirSnapshot
+        out.snapshots['2'] instanceof DefaultFileCollectionSnapshotter.MissingFileSnapshot
+        ((DefaultFileCollectionSnapshotter.FileHashSnapshot) out.snapshots['3']).hash == "foo".bytes
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultTaskArtifactStateCacheAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultTaskArtifactStateCacheAccessTest.groovy
new file mode 100644
index 0000000..f3dc2a9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/DefaultTaskArtifactStateCacheAccessTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.api.internal.GradleInternal
+import org.gradle.cache.CacheBuilder
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.FileLockManager
+import org.gradle.cache.internal.filelock.LockOptionsBuilder
+import spock.lang.Specification
+
+class DefaultTaskArtifactStateCacheAccessTest extends Specification {
+    final GradleInternal gradle = Mock()
+    final CacheRepository cacheRepository = Mock()
+
+    def "opens backing cache on construction"() {
+        CacheBuilder cacheBuilder = Mock()
+        PersistentCache backingCache = Mock()
+
+        when:
+        new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository, new NoOpDecorator())
+
+        then:
+        1 * cacheRepository.cache(gradle, "taskArtifacts") >> cacheBuilder
+        1 * cacheBuilder.withDisplayName(_) >> cacheBuilder
+        1 * cacheBuilder.withLockOptions(LockOptionsBuilder.mode(FileLockManager.LockMode.None)) >> cacheBuilder
+        1 * cacheBuilder.open() >> backingCache
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/InMemoryTaskArtifactCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/InMemoryTaskArtifactCacheTest.groovy
new file mode 100644
index 0000000..8c107ca
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/InMemoryTaskArtifactCacheTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.cache.internal.MultiProcessSafePersistentIndexedCache
+import spock.lang.Specification
+
+class InMemoryTaskArtifactCacheTest extends Specification {
+    def cacheFactory = new InMemoryTaskArtifactCache()
+    def target = Mock(MultiProcessSafePersistentIndexedCache)
+
+    def "caches result from backing cache"() {
+        given:
+        def cache = cacheFactory.decorate("path/fileSnapshots.bin", "fileSnapshots", target)
+
+        when:
+        def result = cache.get("key")
+
+        then:
+        result == "result"
+
+        and:
+        1 * target.get("key") >> "result"
+        0 * target._
+
+        when:
+        result = cache.get("key")
+
+        then:
+        result == "result"
+
+        and:
+        0 * target._
+    }
+
+    def "caches null result from backing cache"() {
+        given:
+        def cache = cacheFactory.decorate("path/fileSnapshots.bin", "fileSnapshots", target)
+
+        when:
+        def result = cache.get("key")
+
+        then:
+        result == null
+
+        and:
+        1 * target.get("key") >> null
+        0 * target._
+
+        when:
+        result = cache.get("key")
+
+        then:
+        result == null
+
+        and:
+        0 * target._
+    }
+
+    def "caches result of putting item"() {
+        given:
+        def cache = cacheFactory.decorate("path/fileSnapshots.bin", "fileSnapshots", target)
+
+        when:
+        def result = cache.get("key")
+
+        then:
+        result == "result"
+
+        and:
+        1 * target.get("key") >> "result"
+        0 * target._
+
+        when:
+        cache.put("key", "new value")
+
+        then:
+        1 * target.put("key", "new value")
+        0 * target._
+
+        when:
+        result = cache.get("key")
+
+        then:
+        result == "new value"
+
+        and:
+        0 * target._
+    }
+
+    def "caches result of removing item"() {
+        given:
+        def cache = cacheFactory.decorate("path/fileSnapshots.bin", "fileSnapshots", target)
+
+        when:
+        def result = cache.get("key")
+
+        then:
+        result == "result"
+
+        and:
+        1 * target.get("key") >> "result"
+        0 * target._
+
+        when:
+        cache.remove("key")
+
+        then:
+        1 * target.remove("key")
+        0 * target._
+
+        when:
+        result = cache.get("key")
+
+        then:
+        result == null
+
+        and:
+        0 * target._
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/OutputFilesSnapshotSerializerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/OutputFilesSnapshotSerializerTest.groovy
new file mode 100644
index 0000000..c569af8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/state/OutputFilesSnapshotSerializerTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.changedetection.state
+
+import org.gradle.messaging.serialize.Serializer
+import org.gradle.messaging.serialize.SerializerSpec
+
+class OutputFilesSnapshotSerializerTest extends SerializerSpec {
+    def targetSerializer = Mock(Serializer)
+    def serializer = new OutputFilesSnapshotSerializer(targetSerializer)
+
+    def "reads and writes the snapshot"() {
+        def snapshot = Stub(FileCollectionSnapshot)
+        def outputSnapshot = new OutputFilesCollectionSnapshotter.OutputFilesSnapshot(["x": 14L], snapshot)
+
+        given:
+        1 * targetSerializer.write(_, snapshot)
+        1 * targetSerializer.read(_) >> snapshot
+
+        when:
+        OutputFilesCollectionSnapshotter.OutputFilesSnapshot out = serialize(outputSnapshot, serializer)
+
+        then:
+        out.rootFileIds == ['x': 14L]
+        out.filesSnapshot == snapshot
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/coerce/TypeCoercingMethodArgumentsTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/coerce/TypeCoercingMethodArgumentsTransformerTest.groovy
new file mode 100644
index 0000000..2110ce7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/coerce/TypeCoercingMethodArgumentsTransformerTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.coerce
+
+import org.gradle.internal.typeconversion.TypeConversionException
+import spock.lang.Specification
+import spock.lang.Unroll
+
+ at SuppressWarnings("GroovyUnusedDeclaration")
+class TypeCoercingMethodArgumentsTransformerTest extends Specification {
+
+    def transformer = new TypeCoercingMethodArgumentsTransformer()
+
+    static enum TestEnum {
+        ABC, DEF
+    }
+
+    static class EnumTester {
+        TestEnum enumProperty
+
+        void oneArgEnumMethod(TestEnum testEnum) {}
+
+        void twoArgEnumMethod(TestEnum testEnum, String other) {}
+
+        void oneArgNonEnumMethod(String other) {}
+    }
+
+    @Unroll
+    def "can enum transform correctly - #desc"() {
+        def i = new EnumTester()
+
+        expect:
+        transformer.transform(i, name, args.toArray()).toList() == transformed
+
+        where:
+        name                  | args    | transformed    | desc
+        "setEnumProperty"     | ["abc"] | [TestEnum.ABC] | "for property"
+        "oneArgEnumMethod"    | ["dEf"] | [TestEnum.DEF] | "one arg method"
+        "twoArgEnumMethod"    | ["abc"] | ["abc"]        | "two arg method"
+        "oneArgNonEnumMethod" | ["abc"] | ["abc"]        | "one arg method non enum method"
+    }
+
+    def "exception thrown when coercing invalid string to enum"() {
+        def i = new EnumTester()
+
+        when:
+        transformer.transform(i, "oneArgEnumMethod", "invalid")
+
+        then:
+        def e = thrown TypeConversionException
+        e.message.contains TestEnum.values().toString() // error message shows valid values
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
index 564d959..b0ca056 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
@@ -25,7 +25,7 @@ import org.gradle.api.tasks.TaskDependency;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GUtil;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -275,7 +275,7 @@ public class AbstractFileCollectionTest {
         File file2 = new File("f2");
 
         TestFileCollection collection = new TestFileCollection(file1, file2);
-        FileCollection filtered = collection.filter(HelperUtil.toClosure("{f -> f.name == 'f1'}"));
+        FileCollection filtered = collection.filter(TestUtil.toClosure("{f -> f.name == 'f1'}"));
         assertThat(filtered.getFiles(), equalTo(toSet(file1)));
     }
 
@@ -286,7 +286,7 @@ public class AbstractFileCollectionTest {
         File file3 = new File("dir/f1");
 
         TestFileCollection collection = new TestFileCollection(file1, file2);
-        FileCollection filtered = collection.filter(HelperUtil.toClosure("{f -> f.name == 'f1'}"));
+        FileCollection filtered = collection.filter(TestUtil.toClosure("{f -> f.name == 'f1'}"));
         assertThat(filtered.getFiles(), equalTo(toSet(file1)));
 
         collection.files.add(file3);
@@ -304,14 +304,14 @@ public class AbstractFileCollectionTest {
         collection.files.add(new File("f1"));
 
         assertHasSameDependencies(collection.getAsFileTree());
-        assertHasSameDependencies(collection.getAsFileTree().matching(HelperUtil.TEST_CLOSURE));
+        assertHasSameDependencies(collection.getAsFileTree().matching(TestUtil.TEST_CLOSURE));
     }
 
     @Test
     public void filteredCollectionHasSameDependenciesAsThis() {
         TestFileCollectionWithDependency collection = new TestFileCollectionWithDependency();
 
-        assertHasSameDependencies(collection.filter(HelperUtil.toClosure("{true}")));
+        assertHasSameDependencies(collection.filter(TestUtil.toClosure("{true}")));
     }
 
     private void assertHasSameDependencies(FileCollection tree) {
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 5c881ce..0f39bac 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
@@ -103,6 +103,7 @@ public class AbstractFileTreeElementTest {
         }
 
         public TestFileTreeElement(TestFile file, Integer mode) {
+            super(chmod);
             this.file = file;
             this.mode = mode;
         }
@@ -127,11 +128,6 @@ public class AbstractFileTreeElementTest {
             return file.length();
         }
 
-        @Override
-        protected Chmod getChmod() {
-            return chmod;
-        }
-
         public RelativePath getRelativePath() {
             throw new UnsupportedOperationException();
         }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
index f2d3317..9bea03d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.file
 
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
@@ -174,11 +174,11 @@ class BaseDirFileResolverSpec extends Specification {
     }
 
     def createLink(File link, File target) {
-        FileSystems.default.createSymbolicLink(link, target)
+        createLink(link, target.absolutePath)
     }
 
     def createLink(File link, String target) {
-        createLink(link, new File(target))
+        new TestFile(link).createLink(target)
     }
 
     def createFile(File file) {
@@ -188,7 +188,7 @@ class BaseDirFileResolverSpec extends Specification {
     }
 
     def normalize(Object path, File baseDir = tmpDir.testDirectory) {
-        new BaseDirFileResolver(FileSystems.default, baseDir).resolve(path)
+        new BaseDirFileResolver(TestFiles.fileSystem(), baseDir).resolve(path)
     }
 
     private File[] getFsRoots() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
index 10115b1..ddd2826 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
@@ -19,7 +19,6 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.PathValidation
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.PreconditionVerifier
 import org.gradle.util.Requires
@@ -33,9 +32,6 @@ import java.util.concurrent.Callable
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class BaseDirFileResolverTest {
     static final String TEST_PATH = 'testpath'
 
@@ -49,7 +45,7 @@ class BaseDirFileResolverTest {
 
     @Before public void setUp() {
         baseDir = rootDir.testDirectory
-        baseDirConverter = new BaseDirFileResolver(FileSystems.default, baseDir)
+        baseDirConverter = new BaseDirFileResolver(TestFiles.fileSystem(), baseDir)
         testFile = new File(baseDir, 'testfile')
         testDir = new File(baseDir, 'testdir')
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
index e0382ca..1ce86c3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
@@ -21,7 +21,7 @@ import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.*;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -266,12 +266,12 @@ public class CompositeFileCollectionTest {
     @Test
     public void fileTreeDependsOnUnionOfAllDependencies() {
         assertDependsOnUnionOfSourceCollections(collection.getAsFileTree());
-        assertDependsOnUnionOfSourceCollections(collection.getAsFileTree().matching(HelperUtil.TEST_CLOSURE));
+        assertDependsOnUnionOfSourceCollections(collection.getAsFileTree().matching(TestUtil.TEST_CLOSURE));
     }
 
     @Test
     public void filteredCollectionDependsOnUnionOfAllDependencies() {
-        assertDependsOnUnionOfSourceCollections(collection.filter(HelperUtil.TEST_CLOSURE));
+        assertDependsOnUnionOfSourceCollections(collection.filter(TestUtil.TEST_CLOSURE));
     }
 
     private void assertDependsOnUnionOfSourceCollections(FileCollection collection) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java
index 5bf5d8c..057e82b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java
@@ -20,7 +20,7 @@ import org.gradle.api.file.FileTree;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
 import org.gradle.api.tasks.util.PatternSet;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import static org.gradle.util.WrapUtil.*;
 import static org.hamcrest.Matchers.*;
 
@@ -52,7 +52,7 @@ public class CompositeFileTreeTest {
 
     @Test
     public void matchingWithClosureReturnsUnionOfFilteredSets() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = TestUtil.TEST_CLOSURE;
         final FileTree filtered1 = context.mock(FileTree.class);
         final FileTree filtered2 = context.mock(FileTree.class);
 
@@ -114,7 +114,7 @@ public class CompositeFileTreeTest {
 
     @Test
     public void visitsEachTreeWithClosure() {
-        final Closure visitor = HelperUtil.TEST_CLOSURE;
+        final Closure visitor = TestUtil.TEST_CLOSURE;
 
         context.checking(new Expectations() {{
             one(source1).visit(visitor);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultCompositeFileTreeTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultCompositeFileTreeTest.groovy
new file mode 100644
index 0000000..a4878d8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultCompositeFileTreeTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.test.fixtures.file.WorkspaceTest
+
+class DefaultCompositeFileTreeTest extends WorkspaceTest {
+
+    def "can be empty"() {
+        when:
+        def ft = new DefaultCompositeFileTree(Collections.emptyList())
+
+        then:
+        ft.files.isEmpty()
+    }
+
+    def "contains all files"() {
+        given:
+        def a1 = file("a/1.txt") << "a/1"
+        def b1 = file("b/1.txt") << "b/1"
+        def fileResolver = TestFiles.resolver(testDirectory)
+
+        when:
+        def a = fileResolver.resolveFilesAsTree("a")
+        def b = fileResolver.resolveFilesAsTree("b")
+        def composite = new DefaultCompositeFileTree(Arrays.asList(a, b))
+
+        then:
+        composite.files == [a1, b1].toSet()
+    }
+
+    def "can visit all files"() {
+        given:
+        def a1 = file("a/1.txt") << "a/1"
+        def b1 = file("b/1.txt") << "b/1"
+        def fileResolver = TestFiles.resolver(testDirectory)
+
+        when:
+        def a = fileResolver.resolveFilesAsTree("a")
+        def b = fileResolver.resolveFilesAsTree("b")
+        def composite = new DefaultCompositeFileTree(Arrays.asList(a, b))
+
+        and:
+        def visited = []
+        composite.visit {
+            visited << it.file
+        }
+
+        then:
+        visited.toSet() == [a1, b1].toSet()
+    }
+
+}
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 43c5019..bb96f53 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
@@ -26,15 +26,18 @@ import org.gradle.api.internal.file.archive.TarFileTree
 import org.gradle.api.internal.file.archive.ZipFileTree
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
 import org.gradle.api.internal.file.collections.FileTreeAdapter
-import org.gradle.api.internal.file.copy.CopyActionImpl
-import org.gradle.api.internal.file.copy.CopySpecImpl
+import org.gradle.api.internal.file.copy.DefaultCopySpec
 import org.gradle.api.internal.tasks.TaskResolver
-import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.classloader.ClasspathUtil
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.process.ExecResult
+import org.gradle.process.internal.DefaultExecAction
 import org.gradle.process.internal.ExecException
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.ClasspathUtil
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import org.junit.Test
 import spock.lang.Specification
@@ -43,7 +46,9 @@ public class DefaultFileOperationsTest extends Specification {
     private final FileResolver resolver = Mock()
     private final TaskResolver taskResolver = Mock()
     private final TemporaryFileProvider temporaryFileProvider = Mock()
-    private DefaultFileOperations fileOperations = new DefaultFileOperations(resolver, taskResolver, temporaryFileProvider)
+    private final Instantiator instantiator = new DirectInstantiator()
+    private final FileLookup fileLookup = Mock()
+    private DefaultFileOperations fileOperations = new DefaultFileOperations(resolver, taskResolver, temporaryFileProvider, instantiator, fileLookup)
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
@@ -171,7 +176,6 @@ public class DefaultFileOperationsTest extends Specification {
         def result = fileOperations.copy { from 'file'; into 'dir' }
 
         then:
-        result instanceof CopyActionImpl
         !result.didWork
     }
 
@@ -216,7 +220,7 @@ public class DefaultFileOperationsTest extends Specification {
         def spec = fileOperations.copySpec { include 'pattern'}
 
         then:
-        spec instanceof CopySpecImpl
+        spec instanceof DefaultCopySpec
         spec.includes == ['pattern'] as Set
     }
 
@@ -240,7 +244,7 @@ public class DefaultFileOperationsTest extends Specification {
 
     def javaexec() {
         File testFile = tmpDir.file("someFile")
-        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider)
+        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider, instantiator, fileLookup)
         List files = ClasspathUtil.getClasspath(getClass().classLoader)
 
         when:
@@ -256,7 +260,7 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def javaexecWithNonZeroExitValueShouldThrowException() {
-        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider)
+        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider, instantiator, fileLookup)
 
         when:
         fileOperations.javaexec {
@@ -268,7 +272,7 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def javaexecWithNonZeroExitValueAndIgnoreExitValueShouldNotThrowException() {
-        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider)
+        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider, instantiator, fileLookup)
 
         when:
         ExecResult result = fileOperations.javaexec {
@@ -280,12 +284,9 @@ public class DefaultFileOperationsTest extends Specification {
         result.exitValue != 0
     }
 
+    @Requires(TestPrecondition.NOT_WINDOWS)
     def exec() {
-        if (OperatingSystem.current().isWindows()) {
-            return
-        }
-
-        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider)
+        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider, instantiator, fileLookup)
         File testFile = tmpDir.file("someFile")
 
         when:
@@ -300,11 +301,9 @@ public class DefaultFileOperationsTest extends Specification {
         result.exitValue == 0
     }
 
+    @Requires(TestPrecondition.NOT_WINDOWS)
     def execWithNonZeroExitValueShouldThrowException() {
-        if (OperatingSystem.current().isWindows()) {
-            return
-        }
-        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider)
+        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider, instantiator, fileLookup)
 
         when:
         fileOperations.exec {
@@ -317,11 +316,9 @@ public class DefaultFileOperationsTest extends Specification {
         thrown(ExecException)
     }
 
+    @Requires(TestPrecondition.NOT_WINDOWS)
     def execWithNonZeroExitValueAndIgnoreExitValueShouldNotThrowException() {
-        if (OperatingSystem.current().isWindows()) {
-            return
-        }
-        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider)
+        fileOperations = new DefaultFileOperations(resolver(), taskResolver, temporaryFileProvider, instantiator, fileLookup)
 
         when:
         ExecResult result = fileOperations.exec {
@@ -335,6 +332,11 @@ public class DefaultFileOperationsTest extends Specification {
         result.exitValue != 0
     }
 
+    def createsExecAction() {
+        expect:
+        fileOperations.newExecAction() instanceof DefaultExecAction
+    }
+
     def resolver() {
         return TestFiles.resolver(tmpDir.testDirectory)
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
index 6907615..af4a1d9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
@@ -16,25 +16,24 @@
 package org.gradle.api.internal.file
 
 import org.gradle.api.file.FileTreeElement
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.internal.nativeplatform.filesystem.Chmod
+import org.gradle.internal.nativeplatform.filesystem.Stat
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 class DefaultFileTreeElementTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
-    @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "permissions on file can be read"() {
+        def stat = Mock(Stat)
         def f = tmpDir.createFile("f")
-        FileTreeElement e = new DefaultFileTreeElement(f, null)
+        FileTreeElement e = new DefaultFileTreeElement(f, null, Stub(Chmod), stat)
 
-        when:
-        FileSystems.default.chmod(f, 0644)
+        given:
+        stat.getUnixMode(f) >> 0644
 
-        then:
+        expect:
         e.mode == 0644
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DelegatingFileCollectionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DelegatingFileCollectionTest.groovy
new file mode 100644
index 0000000..073e9fe
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DelegatingFileCollectionTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.collections.DelegatingFileCollection
+import org.gradle.api.internal.file.collections.MinimalFileSet
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+
+import spock.lang.Specification
+
+class DelegatingFileCollectionTest extends Specification {
+    FileCollection delegatedTo = Mock()
+    DelegatingFileCollection fileCollection = new DelegatingFileCollection() {
+        @Override
+        FileCollection getDelegate() {
+            delegatedTo
+        }
+    }
+
+    File aFile = new File("foo")
+    FileCollection aCollection = Stub()
+    Object anObject = new Object()
+
+    def "delegates all method calls"() {
+        when:
+        fileCollection.with {
+            getSingleFile()
+            getFiles()
+            contains(aFile)
+            getAsPath()
+            plus(aCollection)
+            minus(aCollection)
+            filter({ true })
+            filter(Specs.satisfyAll())
+            delegate.asType(List) // avoid collision with DGM method
+            add(aCollection)
+            isEmpty()
+            stopExecutionIfEmpty()
+            getAsFileTree()
+            addToAntBuilder(anObject, "nodeName", FileCollection.AntType.MatchingTask)
+            addToAntBuilder(anObject, "nodeName")
+            getBuildDependencies()
+            delegate.iterator() // avoid collision with DGM method
+        }
+
+        then:
+        with(delegatedTo) {
+            1 * getSingleFile()
+            1 * getFiles()
+            1 * contains({ it.is(aFile) })
+            1 * getAsPath()
+            1 * plus({ it.is(aCollection) })
+            1 * minus({ it.is(aCollection) })
+            1 * filter(_ as Closure)
+            1 * filter(_ as Spec)
+            1 * asType(List)
+            1 * add({ it.is(aCollection) })
+            1 * isEmpty()
+            1 * stopExecutionIfEmpty()
+            1 * getAsFileTree()
+            1 * addToAntBuilder(anObject, "nodeName", FileCollection.AntType.MatchingTask)
+            1 * addToAntBuilder(anObject, "nodeName")
+            1 * getBuildDependencies()
+            1 * iterator()
+            0 * _
+        }
+    }
+
+    interface MyFileCollection extends FileCollection, MinimalFileSet {}
+
+    def "delegates getDisplayName() to toString() if delegate is not a MinimalFileSet"() {
+        when:
+        fileCollection.getDisplayName()
+
+        then:
+        1 * delegatedTo.toString()
+
+    }
+
+    def "delegates getDisplayName() to getDisplayName() if delegate is a MinimalFileSet"() {
+        delegatedTo = Mock(MyFileCollection)
+
+        when:
+        fileCollection.getDisplayName()
+
+        then:
+        1 * delegatedTo.getDisplayName()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
index 651ce84..f2edccf 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.api.internal.file
 
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
@@ -25,7 +24,7 @@ class FileOrUriNotationParserTest extends Specification {
 
     @Rule public TestNameTestDirectoryProvider folder = new TestNameTestDirectoryProvider();
 
-    final FileOrUriNotationParser<Serializable> parser = new FileOrUriNotationParser<Serializable>(FileSystems.default)
+    final FileOrUriNotationParser<Serializable> parser = new FileOrUriNotationParser<Serializable>(TestFiles.fileSystem())
 
     def "with File returns this File"() {
         setup:
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/LazilyInitializedFileCollectionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/LazilyInitializedFileCollectionTest.groovy
new file mode 100644
index 0000000..c5734a2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/LazilyInitializedFileCollectionTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+
+import spock.lang.Specification
+
+class LazilyInitializedFileCollectionTest extends Specification {
+    def createCount = 0
+    def fileCollection = new LazilyInitializedFileCollection() {
+        @Override
+        FileCollection createDelegate() {
+            createCount++
+            new SimpleFileCollection([new File("foo")])
+        }
+    }
+
+    def "creates delegate on first access"() {
+        expect:
+        createCount == 0
+
+        when:
+        def files = fileCollection.files
+
+        then:
+        createCount == 1
+        files == [new File("foo")] as Set
+
+        when:
+        fileCollection.files
+
+        then:
+        createCount == 1
+        files == [new File("foo")] as Set
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy
index c07f889..d3a886b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.api.internal.file.archive.compression.Bzip2Archiver
 import org.gradle.api.internal.file.archive.compression.GzipArchiver
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 11/24/11
- */
 public class MaybeCompressedFileResourceTest extends Specification {
 
     def "understands file extensions"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopyActionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopyActionTest.java
new file mode 100644
index 0000000..adc7861
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopyActionTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.archive;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.file.FileResource;
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.compression.GzipArchiver;
+import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
+import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
+import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+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.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
+import static org.gradle.api.internal.file.TestFiles.fileSystem;
+import static org.gradle.api.internal.file.copy.CopyActionExecuterUtil.visit;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+ at RunWith(JMock.class)
+public class TarCopyActionTest {
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private TarCopyAction action;
+
+
+    @Test
+    public void createsTarFile() {
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
+                new SimpleCompressor());
+        tarAndUntarAndCheckFileContents(tarFile);
+    }
+
+    private void tarAndUntarAndCheckFileContents(TestFile tarFile) {
+        tar(file("dir/file1"), file("file2"));
+
+        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
+        tarFile.untarTo(expandDir);
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
+        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+    }
+
+    @Test
+    public void createsGzipCompressedTarFile() {
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tgz"),
+                GzipArchiver.getCompressor());
+        tarAndUntarAndCheckFileContents(tarFile);
+    }
+
+    @Test
+    public void createsBzip2CompressedTarFile() {
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tbz2"),
+                Bzip2Archiver.getCompressor());
+        tarAndUntarAndCheckFileContents(tarFile);
+    }
+
+    @Test
+    public void tarFileContainsExpectedPermissions() {
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
+                new SimpleCompressor());
+
+        tar(dir("dir"), file("file"));
+
+        Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("dir", 2);
+        expected.put("file", 1);
+
+        assertVisitsPermissions(new TarFileTree(new FileResource(tarFile), null, fileSystem()),
+                expected);
+    }
+
+    @Test
+    public void wrapsFailureToOpenOutputFile() {
+        final TestFile tarFile = initializeTarFile(tmpDir.createDir("test.tar"),
+                new SimpleCompressor());
+
+        try {
+            action.execute(new CopyActionProcessingStream() {
+                public void process(CopyActionProcessingStreamAction action) {
+                    // nothing
+                }
+            });
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(String.format("Could not create TAR '%s'.", tarFile)));
+        }
+    }
+
+    @Test
+    public void wrapsFailureToAddElement() {
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
+                new SimpleCompressor());
+
+        Throwable failure = new RuntimeException("broken");
+        try {
+            visit(action, brokenFile("dir/file1", failure));
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(String.format("Could not add [dir/file1] to TAR '%s'.", tarFile)));
+            assertThat(e.getCause(), sameInstance(failure));
+        }
+    }
+
+    private TestFile initializeTarFile(final TestFile tarFile, final ArchiveOutputStreamFactory compressor) {
+        action = new TarCopyAction(tarFile, compressor);
+        return tarFile;
+    }
+
+    private void tar(final FileCopyDetailsInternal... files) {
+        action.execute(new CopyActionProcessingStream() {
+            public void process(CopyActionProcessingStreamAction action) {
+                for (FileCopyDetailsInternal f : files) {
+                    if (f.isDirectory()) {
+                        action.processFile(f);
+                    } else {
+                        action.processFile(f);
+                    }
+                }
+            }
+        });
+    }
+
+    private FileCopyDetailsInternal file(final String path) {
+        final FileCopyDetailsInternal details = context.mock(FileCopyDetailsInternal.class, path);
+        final String content = String.format("contents of %s", path);
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(true, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).getSize();
+            will(returnValue((long) content.getBytes().length));
+
+            allowing(details).isDirectory();
+            will(returnValue(false));
+
+            allowing(details).getMode();
+            will(returnValue(1));
+
+            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
+            will(new org.jmock.api.Action() {
+                public void describeTo(Description description) {
+                    description.appendText("write content");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    IOUtils.write(content, (OutputStream) invocation.getParameter(0));
+                    return null;
+                }
+            });
+        }});
+
+        return details;
+    }
+
+    private FileCopyDetailsInternal dir(final String path) {
+        final FileCopyDetailsInternal details = context.mock(FileCopyDetailsInternal.class, path);
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(false, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).isDirectory();
+            will(returnValue(true));
+
+            allowing(details).getMode();
+            will(returnValue(2));
+        }});
+
+        return details;
+    }
+
+    private FileCopyDetailsInternal brokenFile(final String path, final Throwable failure) {
+        final FileCopyDetailsInternal details = context.mock(FileCopyDetailsInternal.class, String.format("[%s]", path));
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(true, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).getSize();
+            will(returnValue(1000L));
+
+            allowing(details).isDirectory();
+            will(returnValue(false));
+
+            allowing(details).getMode();
+            will(returnValue(1));
+
+            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
+            will(new org.jmock.api.Action() {
+                public void describeTo(Description description) {
+                    description.appendText("write content");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    failure.fillInStackTrace();
+                    throw failure;
+                }
+            });
+        }});
+
+        return details;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
deleted file mode 100644
index 6f4d75f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
+++ /dev/null
@@ -1,250 +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.file.archive;
-
-import org.apache.commons.io.IOUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.FileResource;
-import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
-import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
-import org.gradle.api.internal.file.archive.compression.GzipArchiver;
-import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
-import org.gradle.api.internal.file.copy.ReadableCopySpec;
-import org.gradle.test.fixtures.file.TestFile;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-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.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith(JMock.class)
-public class TarCopySpecVisitorTest {
-    @Rule
-    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ArchiveCopyAction copyAction = context.mock(ArchiveCopyAction.class);
-    private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
-    private final TarCopySpecVisitor visitor = new TarCopySpecVisitor();
-
-    @Test
-    public void createsTarFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
-            new SimpleCompressor());
-        tarAndUntarAndCheckFileContents(tarFile);
-    }
-
-    private void tarAndUntarAndCheckFileContents(TestFile tarFile) {
-        tar(file("dir/file1"), file("file2"));
-
-        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
-        tarFile.untarTo(expandDir);
-        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
-        expandDir.file("file2").assertContents(equalTo("contents of file2"));
-    }
-
-    @Test
-    public void createsGzipCompressedTarFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tgz"),
-            GzipArchiver.getCompressor());
-        tarAndUntarAndCheckFileContents(tarFile);
-    }
-
-    @Test
-    public void createsBzip2CompressedTarFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tbz2"),
-            Bzip2Archiver.getCompressor());
-        tarAndUntarAndCheckFileContents(tarFile);
-    }
-
-    @Test
-    public void tarFileContainsExpectedPermissions() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
-            new SimpleCompressor());
-
-        tar(dir("dir"), file("file"));
-
-        Map<String, Integer> expected = new HashMap<String, Integer>();
-        expected.put("dir", 2);
-        expected.put("file", 1);
-
-        assertVisitsPermissions(new TarFileTree(new FileResource(tarFile), null),
-            expected);
-    }
-
-    @Test
-    public void wrapsFailureToOpenOutputFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.createDir("test.tar"),
-            new SimpleCompressor());
-
-        try {
-            visitor.startVisit(copyAction);
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo(String.format("Could not create TAR '%s'.", tarFile)));
-        }
-    }
-
-    @Test
-    public void wrapsFailureToAddElement() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
-            new SimpleCompressor());
-
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        Throwable failure = new RuntimeException("broken");
-        try {
-            visitor.visitFile(brokenFile("dir/file1", failure));
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo(String.format("Could not add [dir/file1] to TAR '%s'.", tarFile)));
-            assertThat(e.getCause(), sameInstance(failure));
-        }
-    }
-
-    private TestFile initializeTarFile(final TestFile tarFile, final ArchiveOutputStreamFactory compressor) {
-        context.checking(new Expectations() {{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(tarFile));
-            allowing(copyAction).getCompressor();
-            will(returnValue(compressor));
-        }});
-        return tarFile;
-    }
-
-    private void tar(FileVisitDetails... files) {
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        for (FileVisitDetails f : files) {
-            if (f.isDirectory()) {
-                visitor.visitDir(f);
-            } else {
-                visitor.visitFile(f);
-            }
-        }
-
-        visitor.endVisit();
-    }
-
-    private FileVisitDetails file(final String path) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
-        final String content = String.format("contents of %s", path);
-
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(true, path)));
-
-            allowing(details).getLastModified();
-            will(returnValue(1000L));
-
-            allowing(details).getSize();
-            will(returnValue((long)content.getBytes().length));
-
-            allowing(details).isDirectory();
-            will(returnValue(false));
-
-            allowing(details).getMode();
-            will(returnValue(1));
-
-            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
-            will(new Action() {
-                public void describeTo(Description description) {
-                    description.appendText("write content");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    IOUtils.write(content, (OutputStream) invocation.getParameter(0));
-                    return null;
-                }
-            });
-        }});
-
-        return details;
-    }
-
-    private FileVisitDetails dir(final String path) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
-
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(false, path)));
-
-            allowing(details).getLastModified();
-            will(returnValue(1000L));
-
-            allowing(details).isDirectory();
-            will(returnValue(true));
-
-            allowing(details).getMode();
-            will(returnValue(2));
-        }});
-
-        return details;
-    }
-
-    private FileVisitDetails brokenFile(final String path, final Throwable failure) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, String.format("[%s]", path));
-
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(true, path)));
-
-            allowing(details).getLastModified();
-            will(returnValue(1000L));
-
-            allowing(details).getSize();
-            will(returnValue(1000L));
-
-            allowing(details).isDirectory();
-            will(returnValue(false));
-
-            allowing(details).getMode();
-            will(returnValue(1));
-
-            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
-            will(new Action() {
-                public void describeTo(Description description) {
-                    description.appendText("write content");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    failure.fillInStackTrace();
-                    throw failure;
-                }
-            });
-        }});
-
-        return details;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
index ac45a8b..8c09b19 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
@@ -30,6 +30,7 @@ import java.util.Map;
 
 import static java.util.Collections.EMPTY_LIST;
 import static org.gradle.api.file.FileVisitorUtil.*;
+import static org.gradle.api.internal.file.TestFiles.fileSystem;
 import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
 import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.containsString;
@@ -43,7 +44,7 @@ public class TarFileTreeTest {
     private final TestFile tarFile = tmpDir.getTestDirectory().file("test.tar");
     private final TestFile rootDir = tmpDir.getTestDirectory().file("root");
     private final TestFile expandDir = tmpDir.getTestDirectory().file("tmp");
-    private final TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tarFile)), expandDir);
+    private final TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tarFile)), expandDir, fileSystem());
 
     @Test
     public void displayName() {
@@ -68,7 +69,7 @@ public class TarFileTreeTest {
         rootDir.file("subdir2/file2.txt").write("content");
         rootDir.tgzTo(tgz);
 
-        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tgz)), expandDir);
+        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tgz)), expandDir, fileSystem());
 
         assertVisits(tree, toList("subdir/file1.txt", "subdir2/file2.txt"), toList("subdir", "subdir2"));
         assertSetContainsForAllTypes(tree, toList("subdir/file1.txt", "subdir2/file2.txt"));
@@ -82,7 +83,7 @@ public class TarFileTreeTest {
         rootDir.file("subdir2/file2.txt").write("content");
         rootDir.tbzTo(tbz2);
 
-        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tbz2)), expandDir);
+        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tbz2)), expandDir, fileSystem());
 
         assertVisits(tree, toList("subdir/file1.txt", "subdir2/file2.txt"), toList("subdir", "subdir2"));
         assertSetContainsForAllTypes(tree, toList("subdir/file1.txt", "subdir2/file2.txt"));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopyActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopyActionTest.groovy
new file mode 100644
index 0000000..970cca5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopyActionTest.groovy
@@ -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.api.internal.file.archive
+
+import org.apache.tools.zip.Zip64RequiredException
+import org.apache.tools.zip.ZipOutputStream
+import org.gradle.api.file.RelativePath
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.api.internal.file.copy.CopyActionProcessingStream
+import org.gradle.api.internal.file.copy.DefaultZipCompressor
+import org.gradle.api.internal.file.copy.FileCopyDetailsInternal
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Specification
+
+import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions
+import static org.gradle.api.internal.file.copy.CopyActionExecuterUtil.visit
+import static org.hamcrest.Matchers.equalTo
+
+class ZipCopyActionTest extends Specification {
+
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    ZipCopyAction visitor
+    TestFile zipFile
+
+    def setup() {
+        zipFile = tmpDir.getTestDirectory().file("test.zip")
+        visitor = new ZipCopyAction(zipFile, new DefaultZipCompressor(false, ZipOutputStream.STORED), new DocumentationRegistry())
+    }
+
+    void createsZipFile() {
+        given:
+        zip(dir("dir"), file("dir/file1"), file("file2"))
+
+        when:
+        TestFile expandDir = tmpDir.getTestDirectory().file("expanded")
+        zipFile.unzipTo(expandDir)
+
+        then:
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"))
+        expandDir.file("file2").assertContents(equalTo("contents of file2"))
+    }
+
+    void createsDeflatedZipFile() {
+        given:
+        zip(dir("dir"), file("dir/file1"), file("file2"))
+
+        when:
+        TestFile expandDir = tmpDir.getTestDirectory().file("expanded")
+        zipFile.unzipTo(expandDir)
+
+        then:
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"))
+        expandDir.file("file2").assertContents(equalTo("contents of file2"))
+    }
+
+    void zipFileContainsExpectedPermissions() {
+        given:
+        zip(dir("dir"), file("file"))
+
+        when:
+        Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("dir", 2);
+        expected.put("file", 1);
+
+        then:
+        assertVisitsPermissions(new ZipFileTree(zipFile, null, TestFiles.fileSystem()), expected)
+    }
+
+    void wrapsFailureToOpenOutputFile() {
+        given:
+        def invalidZipFile = tmpDir.createDir("test.zip")
+        visitor = new ZipCopyAction(invalidZipFile, new DefaultZipCompressor(false, ZipOutputStream.STORED), new DocumentationRegistry())
+
+        when:
+        visitor.execute(new CopyActionProcessingStream() {
+            public void process(CopyActionProcessingStreamAction action) {
+                // nothing
+            }
+        })
+
+        then:
+        def e = thrown(Exception)
+        e.message == String.format("Could not create ZIP '%s'.", zipFile)
+    }
+
+    void wrapsZip64Failure() {
+        given:
+        def zipOutputStream = Mock(ZipOutputStream)
+        zipOutputStream.close() >> {
+            throw new Zip64RequiredException("xyz")
+        }
+
+        def compressor = new DefaultZipCompressor(false, ZipOutputStream.STORED) {
+            @Override
+            ZipOutputStream createArchiveOutputStream(File destination) {
+                zipOutputStream
+            }
+        }
+
+        def docRegistry = Mock(DocumentationRegistry)
+        1 * docRegistry.getDslRefForProperty(Zip, "zip64") >> "doc url"
+        0 * docRegistry._
+
+        visitor = new ZipCopyAction(zipFile, compressor, docRegistry)
+
+        when:
+        zip(file("file2"))
+
+        then:
+        def e = thrown(org.gradle.api.tasks.bundling.internal.Zip64RequiredException)
+        e.message == "xyz\n\nTo build this archive, please enable the zip64 extension.\nSee: doc url"
+    }
+
+    @Test
+    public void wrapsFailureToAddElement() {
+        given:
+        Throwable failure = new RuntimeException("broken")
+
+        def brokenFile = brokenFile("dir/file1", failure)
+        when:
+        visit(visitor, brokenFile)
+
+        then:
+        def e = thrown(Exception)
+        e.message == String.format("Could not add $brokenFile to ZIP '%s'.", zipFile)
+        e.cause.is(failure)
+    }
+
+    private void zip(final FileCopyDetailsInternal... files) {
+        visitor.execute(new CopyActionProcessingStream() {
+            public void process(CopyActionProcessingStreamAction action) {
+                for (FileCopyDetailsInternal f : files) {
+                    action.processFile(f);
+                }
+            }
+        });
+    }
+
+    private FileCopyDetailsInternal file(final String path) {
+        def mock = Mock(FileCopyDetailsInternal)
+        mock.getRelativePath() >> RelativePath.parse(false, path)
+        mock.getLastModified() >> 1000L
+        mock.isDirectory() >> false
+        mock.getMode() >> 1
+        mock.copyTo(_ as OutputStream) >> { OutputStream out ->
+            out << "contents of $path"
+        }
+        mock
+    }
+
+    private FileCopyDetailsInternal dir(final String path) {
+        def mock = Mock(FileCopyDetailsInternal)
+        mock.getRelativePath() >> RelativePath.parse(false, path)
+        mock.getLastModified() >> 1000L
+        mock.isDirectory() >> true
+        mock.getMode() >> 2
+        mock
+    }
+
+    private FileCopyDetailsInternal brokenFile(final String path, final Throwable failure) {
+        def mock = Mock(FileCopyDetailsInternal)
+        mock.getRelativePath() >> RelativePath.parse(false, path)
+        mock.getLastModified() >> 1000L
+        mock.isDirectory() >> false
+        mock.getMode() >> 1
+        mock.copyTo(_ as OutputStream) >> { OutputStream out ->
+            failure.fillInStackTrace()
+            throw failure
+        }
+        mock
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
deleted file mode 100644
index bbc30b5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.archive;
-
-import org.apache.commons.io.IOUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
-import org.gradle.api.internal.file.copy.ZipDeflatedCompressor;
-import org.gradle.api.internal.file.copy.ReadableCopySpec;
-import org.gradle.api.internal.file.copy.ZipStoredCompressor;
-import org.gradle.test.fixtures.file.TestFile;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-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.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith(JMock.class)
-public class ZipCopySpecVisitorTest {
-    @Rule
-    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ArchiveCopyAction copyAction = context.mock(ZipCopyAction.class);
-    private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
-    private final ZipCopySpecVisitor visitor = new ZipCopySpecVisitor();
-    private TestFile zipFile;
-
-    @Before
-    public void setup() {
-        zipFile = tmpDir.getTestDirectory().file("test.zip");
-        context.checking(new Expectations() {{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(zipFile));
-        }});
-        context.checking(new Expectations() {{
-            allowing(copyAction).getCompressor();
-            will(returnValue(ZipStoredCompressor.INSTANCE));
-        }});
-    }
-
-    private TestFile initializeZipFile(final TestFile testFile, final ArchiveOutputStreamFactory compressor) {
-        context.checking(new Expectations() {{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(zipFile));
-            allowing(copyAction).getCompressor();
-            will(returnValue(compressor));
-        }});
-        return testFile;
-    }
-
-    @Test
-    public void createsZipFile() {
-        initializeZipFile(zipFile, ZipStoredCompressor.INSTANCE);
-        zip(dir("dir"), file("dir/file1"), file("file2"));
-
-        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
-        zipFile.unzipTo(expandDir);
-        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
-        expandDir.file("file2").assertContents(equalTo("contents of file2"));
-    }
-
-    @Test
-    public void createsDeflatedZipFile() {
-        initializeZipFile(zipFile, ZipDeflatedCompressor.INSTANCE);
-        zip(dir("dir"), file("dir/file1"), file("file2"));
-
-        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
-        zipFile.unzipTo(expandDir);
-        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
-        expandDir.file("file2").assertContents(equalTo("contents of file2"));
-    }
-
-    @Test
-    public void zipFileContainsExpectedPermissions() {
-        zip(dir("dir"), file("file"));
-
-        Map<String, Integer> expected = new HashMap<String, Integer>();
-        expected.put("dir", 2);
-        expected.put("file", 1);
-
-        assertVisitsPermissions(new ZipFileTree(zipFile, null), expected);
-    }
-
-    @Test
-    public void wrapsFailureToOpenOutputFile() {
-        final TestFile invalidZipFile = tmpDir.createDir("test.zip");
-
-        context.checking(new Expectations() {{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(invalidZipFile));
-        }});
-
-        try {
-            visitor.startVisit(copyAction);
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo(String.format("Could not create ZIP '%s'.", zipFile)));
-        }
-    }
-
-    @Test
-    public void wrapsFailureToAddElement() {
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        Throwable failure = new RuntimeException("broken");
-        try {
-            visitor.visitFile(brokenFile("dir/file1", failure));
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo(String.format("Could not add [dir/file1] to ZIP '%s'.", zipFile)));
-            assertThat(e.getCause(), sameInstance(failure));
-        }
-    }
-
-    private void zip(FileVisitDetails... files) {
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        for (FileVisitDetails f : files) {
-            if (f.isDirectory()) {
-                visitor.visitDir(f);
-            } else {
-                visitor.visitFile(f);
-            }
-        }
-
-        visitor.endVisit();
-    }
-
-    private FileVisitDetails file(final String path) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
-
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(true, path)));
-
-            allowing(details).getLastModified();
-            will(returnValue(1000L));
-
-            allowing(details).isDirectory();
-            will(returnValue(false));
-
-            allowing(details).getMode();
-            will(returnValue(1));
-
-            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
-            will(new Action() {
-                public void describeTo(Description description) {
-                    description.appendText("write content");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    IOUtils.write(String.format("contents of %s", path), (OutputStream) invocation.getParameter(0));
-                    return null;
-                }
-            });
-        }});
-
-        return details;
-    }
-
-    private FileVisitDetails dir(final String path) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
-
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(false, path)));
-
-            allowing(details).getLastModified();
-            will(returnValue(1000L));
-
-            allowing(details).isDirectory();
-            will(returnValue(true));
-
-            allowing(details).getMode();
-            will(returnValue(2));
-        }});
-
-        return details;
-    }
-
-    private FileVisitDetails brokenFile(final String path, final Throwable failure) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, String.format("[%s]", path));
-
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(true, path)));
-
-            allowing(details).getLastModified();
-            will(returnValue(1000L));
-
-            allowing(details).isDirectory();
-            will(returnValue(false));
-
-            allowing(details).getMode();
-            will(returnValue(1));
-
-            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
-            will(new Action() {
-                public void describeTo(Description description) {
-                    description.appendText("write content");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    failure.fillInStackTrace();
-                    throw failure;
-                }
-            });
-        }});
-
-        return details;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
index fb324e6..106f621 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
@@ -28,6 +28,7 @@ import java.util.Map;
 
 import static java.util.Collections.EMPTY_LIST;
 import static org.gradle.api.file.FileVisitorUtil.*;
+import static org.gradle.api.internal.file.TestFiles.fileSystem;
 import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
 import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.equalTo;
@@ -40,7 +41,7 @@ public class ZipFileTreeTest {
     private final TestFile zipFile = tmpDir.getTestDirectory().file("test.zip");
     private final TestFile rootDir = tmpDir.getTestDirectory().file("root");
     private final TestFile expandDir = tmpDir.getTestDirectory().file("tmp");
-    private final ZipFileTree tree = new ZipFileTree(zipFile, expandDir);
+    private final ZipFileTree tree = new ZipFileTree(zipFile, expandDir, fileSystem());
 
     @Test
     public void displayName() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy
index 7dd9b99..935df22 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy
@@ -20,9 +20,6 @@ package org.gradle.api.internal.file.archive.compression;
 import org.gradle.api.internal.file.FileResource
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 12/13/11
- */
 public class ArchiversTest extends Specification {
 
     def "archivers have unqique URIs"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java
index ecaa42b..ad4e016 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java
@@ -22,7 +22,7 @@ import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.tasks.TaskResolver;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -118,7 +118,7 @@ public class DefaultConfigurableFileCollectionTest {
         }});
 
         List<Character> files = toList('a');
-        Closure closure = HelperUtil.returns(files);
+        Closure closure = TestUtil.returns(files);
         collection.from(closure);
 
         assertThat(collection.getFiles(), equalTo(toLinkedSet(file1)));
@@ -130,7 +130,7 @@ public class DefaultConfigurableFileCollectionTest {
 
     @Test
     public void canUseAClosureToSpecifyASingleFile() {
-        Closure closure = HelperUtil.returns('a');
+        Closure closure = TestUtil.returns('a');
         final File file = new File("1");
 
         collection.from(closure);
@@ -145,7 +145,7 @@ public class DefaultConfigurableFileCollectionTest {
 
     @Test
     public void closureCanReturnNull() {
-        Closure closure = HelperUtil.returns(null);
+        Closure closure = TestUtil.returns(null);
 
         collection.from(closure);
 
@@ -202,7 +202,7 @@ public class DefaultConfigurableFileCollectionTest {
             will(returnValue(file2));
         }});
 
-        collection.from(HelperUtil.toClosure("{[{['src1', { ['src2'] as String[] }]}]}"));
+        collection.from(TestUtil.toClosure("{[{['src1', { ['src2'] as String[] }]}]}"));
         assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
     }
 
@@ -362,7 +362,7 @@ public class DefaultConfigurableFileCollectionTest {
 
         assertThat(collection.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
         assertThat(collection.getAsFileTree().getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
-        assertThat(collection.getAsFileTree().matching(HelperUtil.TEST_CLOSURE).getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
+        assertThat(collection.getAsFileTree().matching(TestUtil.TEST_CLOSURE).getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
     }
     
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy
index 0dcb1ea..01c7006 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Task
 import org.gradle.api.file.FileTree
 import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.copy.FileCopier
 import org.gradle.api.internal.tasks.TaskResolver
 import org.gradle.api.tasks.StopExecutionException
 import org.gradle.api.tasks.TaskDependency
@@ -41,48 +42,41 @@ import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class DefaultConfigurableFileTreeTest extends AbstractTestForPatternSet {
     JUnit4Mockery context = new JUnit4GroovyMockery();
-    TaskResolver taskResolverStub = context.mock(TaskResolver.class);
+    TaskResolver taskResolverStub = context.mock(TaskResolver)
+    FileCopier fileCopier = context.mock(FileCopier)
     DefaultConfigurableFileTree fileSet
     FileResolver fileResolverStub = [resolve: {it as File}] as FileResolver
-    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     File testDir = tmpDir.testDirectory
 
     PatternFilterable getPatternSet() {
         return fileSet
     }
 
-    Class getPatternSetType() {
-        DefaultConfigurableFileTree
-    }
-
     @Before public void setUp() {
         super.setUp()
-        fileSet = patternSetType.newInstance(testDir, fileResolverStub, taskResolverStub)
+        fileSet = new DefaultConfigurableFileTree(testDir, fileResolverStub, taskResolverStub, fileCopier)
     }
 
     @Test public void testFileSetConstructionWithBaseDir() {
-        fileSet = new DefaultConfigurableFileTree(testDir, fileResolverStub, taskResolverStub)
         assertEquals(testDir, fileSet.dir)
     }
 
     @Test public void testFileSetConstructionFromMap() {
-        fileSet = new DefaultConfigurableFileTree(fileResolverStub, taskResolverStub, dir: testDir, includes: ['include'])
+        fileSet = new DefaultConfigurableFileTree(fileResolverStub, taskResolverStub, dir: testDir, includes: ['include'], fileCopier)
         assertEquals(testDir, fileSet.dir)
         assertEquals(['include'] as Set, fileSet.includes)
     }
 
     @Test(expected = InvalidUserDataException) public void testFileSetConstructionWithNoBaseDirSpecified() {
-        DefaultConfigurableFileTree fileSet = new DefaultConfigurableFileTree([:], fileResolverStub, taskResolverStub)
+        DefaultConfigurableFileTree fileSet = new DefaultConfigurableFileTree([:], fileResolverStub, taskResolverStub, fileCopier)
         fileSet.contains(new File('unknown'))
     }
 
     @Test public void testFileSetConstructionWithBaseDirAsString() {
-        DefaultConfigurableFileTree fileSet = new DefaultConfigurableFileTree(fileResolverStub, taskResolverStub, dir: 'dirname')
+        DefaultConfigurableFileTree fileSet = new DefaultConfigurableFileTree(fileResolverStub, taskResolverStub, dir: 'dirname', fileCopier)
         assertEquals(new File('dirname'), fileSet.dir);
     }
 
@@ -299,7 +293,7 @@ class DefaultConfigurableFileTreeTest extends AbstractTestForPatternSet {
     @Test
     public void canGetAndSetTaskDependencies() {
         FileResolver fileResolverStub = context.mock(FileResolver.class);
-        fileSet = patternSetType.newInstance(testDir, fileResolverStub, taskResolverStub)
+        fileSet = new DefaultConfigurableFileTree(testDir, fileResolverStub, taskResolverStub, fileCopier)
 
         assertThat(fileSet.getBuiltBy(), isEmpty());
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java
index 9cb0f03..11a4694 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java
@@ -16,8 +16,8 @@
 package org.gradle.api.internal.file.collections;
 
 import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.copy.CopySpecVisitor;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.test.fixtures.file.TestFile;
@@ -51,11 +51,11 @@ public class DirectoryFileTreeTest {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private JUnit4Mockery context = new JUnit4GroovyMockery();
-    private CopySpecVisitor visitor;
+    private FileVisitor visitor;
 
     @Before
     public void setUp() {
-        visitor = context.mock(CopySpecVisitor.class);
+        visitor = context.mock(FileVisitor.class);
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java
index 9eb4a54..a11c29a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java
@@ -16,9 +16,10 @@
 package org.gradle.api.internal.file.collections;
 
 import groovy.lang.Closure;
+import org.gradle.api.internal.file.TestFiles;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -34,7 +35,7 @@ public class MapFileTreeTest {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private TestFile rootDir = tmpDir.getTestDirectory();
-    private final MapFileTree tree = new MapFileTree(rootDir);
+    private final MapFileTree tree = new MapFileTree(rootDir, TestFiles.fileSystem());
 
     @Test
     public void isEmptyByDefault() {
@@ -45,7 +46,7 @@ public class MapFileTreeTest {
     
     @Test
     public void canAddAnElementUsingAClosureToGeneratedContent() {
-        Closure closure = HelperUtil.toClosure("{it.write('content'.getBytes())}");
+        Closure closure = TestUtil.toClosure("{it.write('content'.getBytes())}");
         tree.add("path/file.txt", closure);
 
         assertVisits(tree, toList("path/file.txt"), toList("path"));
@@ -57,7 +58,7 @@ public class MapFileTreeTest {
 
     @Test
     public void canAddMultipleElementsInDifferentDirs() {
-        Closure closure = HelperUtil.toClosure("{it.write('content'.getBytes())}");
+        Closure closure = TestUtil.toClosure("{it.write('content'.getBytes())}");
         tree.add("path/file.txt", closure);
         tree.add("file.txt", closure);
         tree.add("path/subdir/file.txt", closure);
@@ -68,7 +69,7 @@ public class MapFileTreeTest {
 
     @Test
     public void canStopVisitingElements() {
-        Closure closure = HelperUtil.toClosure("{it.write('content'.getBytes())}");
+        Closure closure = TestUtil.toClosure("{it.write('content'.getBytes())}");
         tree.add("path/file.txt", closure);
         tree.add("file.txt", closure);
         assertCanStopVisiting(tree);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionExecuterTest.groovy
new file mode 100644
index 0000000..57416ea
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionExecuterTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy
+
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.api.internal.tasks.SimpleWorkResult
+import org.gradle.api.tasks.WorkResult
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.test.fixtures.file.WorkspaceTest
+
+class CopyActionExecuterTest extends WorkspaceTest {
+
+    def "correctly executes copy actions, normalising and handling excludes"() {
+        given:
+        file("a").with {
+            createFile("a")
+        }
+        file("b").with {
+            createFile("b")
+            createDir("b1").createFile("b1")
+        }
+
+        def resolver = TestFiles.resolver(testDirectory)
+        def copySpec = new DestinationRootCopySpec(resolver, new DefaultCopySpec(resolver, new DirectInstantiator()))
+        copySpec.with {
+            into "out"
+            from "a", {
+                from "b/b1", {
+                    it.eachFile {
+                        FileCopyDetails fcd -> fcd.exclude()
+                    }
+                }
+            }
+        }
+
+        def action = Mock(CopyActionProcessingStreamAction)
+        def workResult = true
+        def copyAction = new CopyAction() {
+            WorkResult execute(CopyActionProcessingStream stream) {
+                stream.process(action)
+                new SimpleWorkResult(workResult)
+            }
+        }
+        def executer = new CopyActionExecuter(new DirectInstantiator(), TestFiles.fileSystem())
+
+        when:
+        executer.execute(copySpec, copyAction)
+
+        then:
+        1 * action.processFile({ it.relativePath.pathString == "a" })
+        0 * action.processFile(_)
+    }
+
+    Closure path(path) {
+        return { println it.relativePath.pathString; it.relativePath.pathString == path }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionImplTest.groovy
deleted file mode 100644
index d2024f5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionImplTest.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.api.internal.file.copy
-
-import org.gradle.api.file.FileTree
-import org.gradle.api.internal.file.FileResolver
-import spock.lang.Specification
-
-public class CopyActionImplTest extends Specification {
-    FileCopySpecVisitor visitor = Mock()
-    FileResolver resolver = Mock()
-    FileTree sourceFileTree = Mock()
-    CopyActionImpl copyAction = new CopyActionImpl(resolver, visitor)
-
-    def delegatesToMainSpecRootSpec() {
-        when:
-        copyAction.include 'a'
-
-        then:
-        copyAction.includes == ['a'] as Set
-        copyAction.mainSpec.includes == ['a'] as Set
-        copyAction.rootSpec.includes == [] as Set
-        copyAction.rootSpec.childSpecs.contains(copyAction.mainSpec)
-    }
-
-    def didWorkDelegatesToVisitor() {
-        when:
-        def didWork = copyAction.didWork
-
-        then:
-        1 * visitor.didWork >> true
-        didWork
-    }
-
-    def visitsAndCopiesEachSpec() {
-        FileTree source = Mock()
-        _ * source.matching(_) >> source
-
-        copyAction.from('source1')
-        def child = copyAction.from('source2') { }
-
-        when:
-        copyAction.execute()
-
-        then:
-        1 * visitor.startVisit(copyAction)
-        1 * visitor.visitSpec(copyAction.rootSpec)
-        1 * resolver.resolveFilesAsTree([[] as Set] as Object[]) >> source
-        1 * visitor.visitSpec(copyAction.mainSpec)
-        1 * resolver.resolveFilesAsTree([['source1'] as Set] as Object[]) >> source
-        1 * visitor.visitSpec(child)
-        1 * resolver.resolveFilesAsTree([['source2'] as Set] as Object[]) >> source
-        1 * visitor.endVisit()
-        0 * resolver._
-        0 * visitor._
-    }
-   
-    def allSourceIncludesSourceFromAllSpecs() {
-        FileTree mainSource = Mock()
-        _ * mainSource.matching(_) >> mainSource
-        FileTree rootSource = Mock()
-        _ * rootSource.matching(_) >> rootSource
-        FileTree childSource = Mock()
-        _ * childSource.matching(_) >> childSource
-        FileTree allSource = Mock()
-
-        copyAction.from('source1')
-        copyAction.from('source2') { }
-
-        when:
-        def source = copyAction.allSource
-
-        then:
-        source == allSource
-        1 * resolver.resolveFilesAsTree([[] as Set] as Object[]) >> rootSource
-        1 * resolver.resolveFilesAsTree([['source1'] as Set] as Object[]) >> mainSource
-        1 * resolver.resolveFilesAsTree([['source2'] as Set] as Object[]) >> childSource
-        1 * resolver.resolveFilesAsTree([[rootSource, mainSource, childSource]] as Object[]) >> allSource
-        0 * resolver._
-        0 * visitor._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyFileVisitorImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyFileVisitorImplTest.groovy
new file mode 100644
index 0000000..83c6f3f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopyFileVisitorImplTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy
+
+import org.gradle.api.Action
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.file.FileTree
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.api.file.FileVisitor
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction
+import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+class CopyFileVisitorImplTest extends Specification {
+    CopySpecInternal spec = Mock()
+    CopyActionProcessingStreamAction action = Mock()
+    Instantiator instantiator = Mock()
+    FileSystem fileSystem = Mock()
+    FileTree source = Mock()
+    FileVisitor copyFileVisitorImpl
+
+    def setup() {
+        copyFileVisitorImpl = new CopyFileVisitorImpl(spec, action, instantiator, fileSystem)
+    }
+
+    def "visit directory"() {
+        given:
+        FileVisitDetails dirDetails = Mock()
+        DefaultFileCopyDetails defaultFileCopyDetails = new DefaultFileCopyDetails(dirDetails, spec, fileSystem)
+
+        when:
+        copyFileVisitorImpl.visitDir(dirDetails)
+
+        then:
+        1 * instantiator.newInstance(DefaultFileCopyDetails.class, dirDetails, spec, fileSystem) >> defaultFileCopyDetails
+        1 * action.processFile(defaultFileCopyDetails)
+        0 * defaultFileCopyDetails.excluded
+    }
+
+    def "visit file if no action are defined in copy spec"() {
+        given:
+        FileVisitDetails dirDetails = Mock()
+        DefaultFileCopyDetails defaultFileCopyDetails = new DefaultFileCopyDetails(dirDetails, spec, fileSystem)
+
+        when:
+        copyFileVisitorImpl.visitFile(dirDetails)
+
+        then:
+        1 * instantiator.newInstance(DefaultFileCopyDetails.class, dirDetails, spec, fileSystem) >> defaultFileCopyDetails
+        1 * spec.getAllCopyActions() >> []
+        1 * action.processFile(defaultFileCopyDetails)
+        0 * defaultFileCopyDetails.excluded
+    }
+
+    def "visit file for actions defined in copy spec"() {
+        given:
+        FileVisitDetails dirDetails = Mock()
+        DefaultFileCopyDetails defaultFileCopyDetails = Mock()
+        Action<? super FileCopyDetails> fileCopyAction1 = Mock()
+        Action<? super FileCopyDetails> fileCopyAction2 = Mock()
+
+        when:
+        copyFileVisitorImpl.visitFile(dirDetails)
+
+        then:
+        1 * instantiator.newInstance(DefaultFileCopyDetails.class, dirDetails, spec, fileSystem) >> defaultFileCopyDetails
+        1 * spec.getAllCopyActions() >> [fileCopyAction1, fileCopyAction2]
+        1 * action.processFile(defaultFileCopyDetails)
+        1 * fileCopyAction1.execute(defaultFileCopyDetails)
+        1 * fileCopyAction2.execute(defaultFileCopyDetails)
+        2 * defaultFileCopyDetails.isExcluded() >> false
+    }
+
+    def "visit file for excluded file copy details"() {
+        given:
+        FileVisitDetails dirDetails = Mock()
+        DefaultFileCopyDetails defaultFileCopyDetails = Mock()
+        Action<? super FileCopyDetails> fileCopyAction1 = Mock()
+        Action<? super FileCopyDetails> fileCopyAction2 = Mock()
+
+        when:
+        copyFileVisitorImpl.visitFile(dirDetails)
+
+        then:
+        1 * instantiator.newInstance(DefaultFileCopyDetails.class, dirDetails, spec, fileSystem) >> defaultFileCopyDetails
+        1 * spec.getAllCopyActions() >> [fileCopyAction1, fileCopyAction2]
+        0 * action.processFile(defaultFileCopyDetails)
+        1 * fileCopyAction1.execute(defaultFileCopyDetails)
+        1 * defaultFileCopyDetails.excluded >> true
+        0 * fileCopyAction2.execute(defaultFileCopyDetails)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecActionImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecActionImplTest.groovy
new file mode 100644
index 0000000..19f6855
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecActionImplTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy
+
+import org.gradle.api.Action
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction
+import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+class CopySpecActionImplTest extends Specification {
+    CopyActionProcessingStreamAction action = Mock()
+    Instantiator instantiator = Mock()
+    FileSystem fileSystem = Mock()
+    CopySpecInternal copySpecInternal = Mock()
+    FileTree source = Mock()
+    Action<CopySpecInternal> copySpecInternalAction
+
+    def setup() {
+        copySpecInternalAction = new CopySpecActionImpl(action, instantiator, fileSystem)
+    }
+
+    def "can visit spec source"() {
+        when:
+        copySpecInternalAction.execute(copySpecInternal)
+
+        then:
+        1 * copySpecInternal.getSource() >> source
+        1 * source.visit(_ as CopyFileVisitorImpl)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecBackedCopyActionProcessingStreamTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecBackedCopyActionProcessingStreamTest.groovy
new file mode 100644
index 0000000..467ee64
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecBackedCopyActionProcessingStreamTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy
+
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction
+import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+class CopySpecBackedCopyActionProcessingStreamTest extends Specification {
+    CopySpecInternal copySpecInternal = Mock()
+    Instantiator instantiator = Mock()
+    FileSystem fileSystem = Mock()
+    CopyActionProcessingStreamAction action = Mock()
+    CopyActionProcessingStream copyActionProcessingStream
+
+    def setup() {
+        copyActionProcessingStream = new CopySpecBackedCopyActionProcessingStream(copySpecInternal, instantiator, fileSystem)
+    }
+
+    def "walks spec"() {
+        when:
+        copyActionProcessingStream.process(action)
+
+        then:
+        1 * copySpecInternal.walk(_ as CopySpecActionImpl)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
deleted file mode 100644
index 089d334..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy
-
-import org.apache.tools.ant.filters.HeadFilter
-import org.apache.tools.ant.filters.StripJavaComments
-import org.gradle.api.Action
-import org.gradle.api.file.FileTree
-import org.gradle.api.file.RelativePath
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.specs.Spec
-import org.gradle.api.tasks.util.PatternSet
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
- at RunWith(JMock)
-public class CopySpecImplTest {
-
-    @Rule public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
-    private TestFile baseFile = testDir.testDirectory
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
-    private final FileResolver fileResolver = context.mock(FileResolver);
-    private final CopySpecImpl spec = new CopySpecImpl(fileResolver)
-
-    private List<String> getTestSourceFileNames() {
-        ['first', 'second']
-    }
-
-    private List<File> getAbsoluteTestSources() {
-        testSourceFileNames.collect { new File(baseFile, it) }
-    }
-
-    @Test public void testAbsoluteFromList() {
-        List<File> sources = getAbsoluteTestSources();
-        spec.from(sources);
-        assertEquals([sources], spec.sourcePaths as List);
-    }
-
-    @Test public void testFromArray() {
-        List<File> sources = getAbsoluteTestSources();
-        spec.from(sources as File[]);
-        assertEquals(sources, spec.sourcePaths as List);
-    }
-
-    @Test public void testSourceWithClosure() {
-        CopySpecImpl child = spec.from('source') {
-        }
-
-        assertThat(child, not(sameInstance(spec)))
-        assertEquals(['source'], child.sourcePaths as List);
-    }
-
-    @Test public void testMultipleSourcesWithClosure() {
-        CopySpecImpl child = spec.from(['source1', 'source2']) {
-        }
-
-        assertThat(child, not(sameInstance(spec)))
-        assertEquals(['source1', 'source2'], child.sourcePaths.flatten() as List);
-    }
-
-    @Test public void testDefaultDestinationPathForRootSpec() {
-        assertThat(spec.destPath, equalTo(new RelativePath(false)))
-    }
-
-    @Test public void testInto() {
-        spec.into 'spec'
-        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
-        spec.into '/'
-        assertThat(spec.destPath, equalTo(new RelativePath(false)))
-    }
-
-    @Test public void testIntoWithAClosure() {
-        spec.into { 'spec' }
-        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
-        spec.into { return { 'spec' } }
-        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
-    }
-
-    @Test public void testWithSpec() {
-        CopySpecImpl other1 = new CopySpecImpl(fileResolver)
-        CopySpecImpl other2 = new CopySpecImpl(fileResolver)
-
-        spec.with other1, other2
-        assertTrue(spec.sourcePaths.empty)
-        assertThat(spec.childSpecs.size(), equalTo(2))
-    }
-    
-    @Test public void testWithSpecSource() {
-        CopyActionImpl source = new CopyActionImpl(fileResolver, null)
-
-        spec.with source
-        assertTrue(spec.sourcePaths.empty)
-        assertThat(spec.childSpecs.size(), equalTo(1))
-    }
-
-    @Test public void testWithSpecInheritsDestinationPathFromParent() {
-        CopySpecImpl other = new CopySpecImpl(fileResolver)
-        other.into 'other'
-
-        spec.into 'spec'
-        spec.with other
-
-        ReadableCopySpec child = spec.childSpecs[0]
-        assertThat(child.destPath, equalTo(new RelativePath(false, 'spec', 'other')))
-    }
-
-    @Test public void testDestinationWithClosure() {
-        CopySpecImpl child = spec.into('target') {
-        }
-
-        assertThat(child, not(sameInstance(spec)))
-        assertThat(child.destPath, equalTo(new RelativePath(false, 'target')))
-    }
-
-    @Test public void testGetAllSpecsReturnsBreadthwiseTraverseOfSpecs() {
-        CopySpecImpl child = spec.into('somedir') { }
-        CopySpecImpl grandchild = child.into('somedir') { }
-        CopySpecImpl child2 = spec.into('somedir') { }
-
-        assertThat(spec.allSpecs, equalTo([spec, child, grandchild, child2]))
-    }
-
-    @Test public void testRootSpecHasRootPathAsDestination() {
-        assertThat(spec.destPath, equalTo(new RelativePath(false)))
-    }
-
-    @Test public void testChildSpecResolvesIntoArgRelativeToParentDestinationDir() {
-        CopySpecImpl child = spec.from('somedir') { into 'child' }
-        assertThat(child.destPath, equalTo(new RelativePath(false, 'child')))
-
-        CopySpecImpl grandchild = child.from('somedir') { into 'grandchild'}
-        assertThat(grandchild.destPath, equalTo(new RelativePath(false, 'child', 'grandchild')))
-
-        grandchild.into '/grandchild'
-        assertThat(grandchild.destPath, equalTo(new RelativePath(false, 'grandchild')))
-    }
-
-    @Test public void testChildSpecUsesParentDestinationPathAsDefault() {
-        CopySpecImpl child = spec.from('somedir') { }
-        assertThat(child.destPath, equalTo(spec.destPath))
-
-        child.into 'child'
-
-        CopySpecImpl grandchild = child.from('somedir') { }
-        assertThat(grandchild.destPath, equalTo(child.destPath))
-    }
-
-    @Test public void testSourceIsFilteredTreeOfSources() {
-        spec.from 'a'
-        spec.from 'b'
-
-        def filteredTree = context.mock(FileTree, 'filtered')
-
-        context.checking {
-            one(fileResolver).resolveFilesAsTree(['a', 'b'] as Set)
-            def tree = context.mock(FileTree, 'source')
-            will(returnValue(tree))
-            one(tree).matching(withParam(equalTo(spec.patternSet)))
-            will(returnValue(filteredTree))
-        }
-
-        assertThat(spec.source, sameInstance(filteredTree))
-    }
-
-    @Test public void testChildUsesPatternsFromParent() {
-        CopySpecImpl child = spec.from('dir') {}
-        Spec specInclude = [:] as Spec
-        Spec specExclude = [:] as Spec
-        Spec childInclude = [:] as Spec
-        Spec childExclude = [:] as Spec
-
-        spec.include('parent-include')
-        spec.exclude('parent-exclude')
-        spec.include(specInclude)
-        spec.exclude(specExclude)
-        child.include('child-include')
-        child.exclude('child-exclude')
-        child.include(childInclude)
-        child.exclude(childExclude)
-
-        PatternSet patterns = child.patternSet
-        assertThat(patterns.includes, equalTo(['parent-include', 'child-include'] as Set))
-        assertThat(patterns.excludes, equalTo(['parent-exclude', 'child-exclude'] as Set))
-        assertThat(patterns.includeSpecs, equalTo([specInclude, childInclude] as Set))
-        assertThat(patterns.excludeSpecs, equalTo([specExclude, childExclude] as Set))
-    }
-
-    @Test public void testChildUsesParentPatternsAsDefault() {
-        CopySpecImpl child = spec.from('dir') {}
-        Spec specInclude = [:] as Spec
-        Spec specExclude = [:] as Spec
-
-        spec.include('parent-include')
-        spec.exclude('parent-exclude')
-        spec.include(specInclude)
-        spec.exclude(specExclude)
-
-        PatternSet patterns = child.patternSet
-        assertThat(patterns.includes, equalTo(['parent-include'] as Set))
-        assertThat(patterns.excludes, equalTo(['parent-exclude'] as Set))
-        assertThat(patterns.includeSpecs, equalTo([specInclude] as Set))
-        assertThat(patterns.excludeSpecs, equalTo([specExclude] as Set))
-    }
-
-    @Test public void testChildUsesCaseSensitiveFlagFromParentAsDefault() {
-        CopySpecImpl child = spec.from('dir') {}
-        assertTrue(child.caseSensitive)
-        assertTrue(child.patternSet.caseSensitive)
-
-        spec.caseSensitive = false
-        assertFalse(child.caseSensitive)
-        assertFalse(child.patternSet.caseSensitive)
-
-        child.caseSensitive = true
-        assertTrue(child.caseSensitive)
-        assertTrue(child.patternSet.caseSensitive)
-    }
-
-    @Test public void testChildUsesIncludeEmptyDirsFlagFromParentAsDefault() {
-        def child = spec.from('dir') {}
-        assert child.includeEmptyDirs
-
-        spec.includeEmptyDirs = false
-        assert !child.includeEmptyDirs
-
-        child.includeEmptyDirs = true
-        assert child.includeEmptyDirs
-    }
-
-    @Test public void testNoArgFilter() {
-        spec.filter(StripJavaComments)
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-    }
-
-    @Test public void testArgFilter() {
-        spec.filter(HeadFilter, lines: 15, skip: 2)
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-    }
-
-    @Test public void testExpand() {
-        spec.expand(version: '1.2', skip: 2)
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-    }
-
-    @Test public void testTwoFilters() {
-        spec.filter(StripJavaComments)
-        spec.filter(HeadFilter, lines: 15, skip: 2)
-
-        assertThat(spec.allCopyActions.size(), equalTo(2))
-    }
-
-    @Test public void testAddsStringNameTransformerToActions() {
-        spec.rename("regexp", "replacement")
-
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
-        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
-    }
-
-    @Test public void testAddsPatternNameTransformerToActions() {
-        spec.rename(/regexp/, "replacement")
-
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
-        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
-    }
-
-    @Test public void testAddsClosureToActions() {
-        spec.rename {}
-
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
-    }
-
-    @Test public void testAddAction() {
-        def action = context.mock(Action)
-        spec.eachFile(action)
-
-        assertThat(spec.allCopyActions, equalTo([action]))
-    }
-
-    @Test public void testAddActionAsClosure() {
-        def action = {}
-        spec.eachFile(action)
-
-        assertThat(spec.allCopyActions.size(), equalTo(1))
-    }
-
-    @Test public void testSpecInheritsActionsFromParent() {
-        Action parentAction = context.mock(Action, 'parent')
-        Action childAction = context.mock(Action, 'child')
-
-        spec.eachFile parentAction
-        CopySpecImpl childSpec = spec.from('src') {
-            eachFile childAction
-        }
-
-        assertThat(childSpec.allCopyActions, equalTo([parentAction, childAction]))
-    }
-
-    @Test public void testHasNoPermissionsByDefault() {
-        assert spec.fileMode == null
-        assert spec.dirMode == null
-    }
-
-    @Test public void testInheritsPermissionsFromParent() {
-        spec.fileMode = 0x1
-        spec.dirMode = 0x2
-
-        CopySpecImpl child = spec.from('src') { }
-        org.junit.Assert.assertEquals(0x1, child.fileMode)
-        org.junit.Assert.assertEquals(0x2, child.dirMode)
-    }
-
-    @Test public void testHasNoSourceByDefault() {
-        assertFalse(spec.hasSource())
-    }
-
-    @Test public void testHasSourceWhenSpecHasSource() {
-        spec.from 'source'
-        assertTrue(spec.hasSource())
-    }
-
-    @Test public void testHasSourceWhenChildSpecHasSource() {
-        spec.from('source') {}
-        assertTrue(spec.hasSource())
-    }
-}
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecMatchingTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecMatchingTest.groovy
new file mode 100644
index 0000000..f2f006c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecMatchingTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy
+
+import org.gradle.api.Action
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.file.FileTree
+import org.gradle.api.file.RelativePath
+import org.gradle.internal.Actions
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.internal.reflect.DirectInstantiator
+import spock.lang.Specification
+
+class CopySpecMatchingTest extends Specification {
+
+    DefaultCopySpec copySpec = new DefaultCopySpec(TestFiles.resolver(), new DirectInstantiator(), null)
+
+    FileTree fileTree = Mock()
+
+    def canMatchFiles() {
+        given:
+
+        FileCopyDetails details1 = Mock()
+        FileCopyDetails details2 = Mock()
+
+        details1.relativePath >>  RelativePath.parse(true, 'path/abc.txt')
+        details2.relativePath >> RelativePath.parse(true, 'path/bcd.txt')
+
+        Action matchingAction = Mock()
+
+        when:
+        copySpec.filesMatching("**/a*", matchingAction)
+        copySpec.allCopyActions.each { copyAction ->
+            copyAction.execute(details1)
+            copyAction.execute(details2)
+        }
+
+        then:
+        1 * matchingAction.execute(details1)
+    }
+
+
+    def canNotMatchFiles() {
+        given:
+
+        FileCopyDetails details1 = Mock()
+        FileCopyDetails details2 = Mock()
+
+        details1.relativePath >>  RelativePath.parse(true, 'path/abc.txt')
+        details2.relativePath >> RelativePath.parse(true, 'path/bcd.txt')
+
+        Action matchingAction = Mock()
+
+        when:
+        copySpec.filesNotMatching("**/a*", matchingAction)
+        copySpec.allCopyActions.each { copyAction ->
+            copyAction.execute(details1)
+            copyAction.execute(details2)
+        }
+
+        then:
+        1 * matchingAction.execute(details2)
+    }
+
+    def matchingSpecInherited() {
+        given:
+        DefaultCopySpec childSpec = copySpec.addChild()
+        when:
+        copySpec.filesMatching("**/*.java", Actions.doNothing())
+        then:
+        1 == childSpec.allCopyActions.size()
+        childSpec.allCopyActions[0] instanceof MatchingCopyAction
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecTest.groovy
new file mode 100644
index 0000000..b956679
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecTest.groovy
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy
+
+import org.apache.tools.ant.filters.HeadFilter
+import org.apache.tools.ant.filters.StripJavaComments
+import org.gradle.api.Action
+import org.gradle.api.file.CopySpec
+import org.gradle.api.file.DuplicatesStrategy
+import org.gradle.api.file.FileTree
+import org.gradle.api.file.RelativePath
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.util.PatternSet
+import org.gradle.internal.Actions
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(JMock)
+public class DefaultCopySpecTest {
+
+    @Rule
+    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
+    private TestFile baseFile = testDir.testDirectory
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final FileResolver fileResolver = context.mock(FileResolver);
+    private final Instantiator instantiator = new DirectInstantiator()
+    private final DefaultCopySpec spec = new DefaultCopySpec(fileResolver, instantiator)
+
+    private List<String> getTestSourceFileNames() {
+        ['first', 'second']
+    }
+
+    private List<File> getAbsoluteTestSources() {
+        testSourceFileNames.collect { new File(baseFile, it) }
+    }
+
+    @Test
+    public void testAbsoluteFromList() {
+        List<File> sources = getAbsoluteTestSources();
+        spec.from(sources);
+        assertEquals([sources], spec.sourcePaths as List);
+    }
+
+    @Test
+    public void testFromArray() {
+        List<File> sources = getAbsoluteTestSources();
+        spec.from(sources as File[]);
+        assertEquals(sources, spec.sourcePaths as List);
+    }
+
+    @Test
+    public void testSourceWithClosure() {
+        CopySpec child = spec.from('source') {
+        }
+
+        assertThat(child, not(sameInstance(spec as CopySpec)))
+        assertEquals(['source'], unpackWrapper(child).sourcePaths as List);
+    }
+
+    @Test
+    public void testMultipleSourcesWithClosure() {
+        CopySpec child = spec.from(['source1', 'source2']) {
+        }
+
+        assertThat(child, not(sameInstance(spec as CopySpec)))
+        assertEquals(['source1', 'source2'], unpackWrapper(child).sourcePaths.flatten() as List);
+    }
+
+    @Test
+    public void testDefaultDestinationPathForRootSpec() {
+        assertThat(spec.destPath, equalTo(new RelativePath(false)))
+    }
+
+    @Test
+    public void testInto() {
+        spec.into 'spec'
+        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
+        spec.into '/'
+        assertThat(spec.destPath, equalTo(new RelativePath(false)))
+    }
+
+    @Test
+    public void testIntoWithAClosure() {
+        spec.into { 'spec' }
+        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
+        spec.into { return { 'spec' } }
+        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
+    }
+
+    @Test
+    public void testWithSpec() {
+        DefaultCopySpec other1 = new DefaultCopySpec(fileResolver, instantiator)
+        DefaultCopySpec other2 = new DefaultCopySpec(fileResolver, instantiator)
+
+        spec.with other1, other2
+        assertTrue(spec.sourcePaths.empty)
+        assertThat(spec.childSpecs.size(), equalTo(2))
+    }
+
+    @Test
+    public void testWithSpecInheritsDestinationPathFromParent() {
+        DefaultCopySpec other = new DefaultCopySpec(fileResolver, instantiator)
+        other.into 'other'
+
+        spec.into 'spec'
+        spec.with other
+
+        CopySpecInternal child = spec.childSpecs[0]
+        assertThat(child.destPath, equalTo(new RelativePath(false, 'spec', 'other')))
+    }
+
+    @Test
+    public void testDestinationWithClosure() {
+        CopySpec child = spec.into('target') {
+        }
+
+        assertThat(child, not(sameInstance(spec as CopySpec)))
+        assertThat(unpackWrapper(child).destPath, equalTo(new RelativePath(false, 'target')))
+    }
+
+    @Test
+    public void testRootSpecHasRootPathAsDestination() {
+        assertThat(spec.destPath, equalTo(new RelativePath(false)))
+    }
+
+    @Test
+    public void testChildSpecResolvesIntoArgRelativeToParentDestinationDir() {
+        CopySpec child = spec.from('somedir') { into 'child' }
+        assertThat(unpackWrapper(child).destPath, equalTo(new RelativePath(false, 'child')))
+
+        CopySpec grandchild = child.from('somedir') { into 'grandchild' }
+        assertThat(unpackWrapper(grandchild).destPath, equalTo(new RelativePath(false, 'child', 'grandchild')))
+
+        grandchild.into '/grandchild'
+        assertThat(unpackWrapper(grandchild).destPath, equalTo(new RelativePath(false, 'grandchild')))
+    }
+
+    @Test
+    public void testChildSpecUsesParentDestinationPathAsDefault() {
+        CopySpec child = spec.from('somedir') {}
+        assertThat(unpackWrapper(child).destPath, equalTo(spec.destPath))
+
+        child.into 'child'
+
+        CopySpec grandchild = child.from('somedir') {}
+        assertThat(unpackWrapper(grandchild).destPath, equalTo(unpackWrapper(child).destPath))
+    }
+
+    @Test
+    public void testSourceIsFilteredTreeOfSources() {
+        spec.from 'a'
+        spec.from 'b'
+
+        def filteredTree = context.mock(FileTree, 'filtered')
+
+        context.checking {
+            one(fileResolver).resolveFilesAsTree(['a', 'b'] as Set)
+            def tree = context.mock(FileTree, 'source')
+            will(returnValue(tree))
+            one(tree).matching(withParam(equalTo(spec.patternSet)))
+            will(returnValue(filteredTree))
+        }
+
+        assertThat(spec.source, sameInstance(filteredTree))
+    }
+
+    @Test
+    public void testChildUsesPatternsFromParent() {
+        CopySpec child = spec.from('dir') {}
+        Spec specInclude = [:] as Spec
+        Spec specExclude = [:] as Spec
+        Spec childInclude = [:] as Spec
+        Spec childExclude = [:] as Spec
+
+        spec.include('parent-include')
+        spec.exclude('parent-exclude')
+        spec.include(specInclude)
+        spec.exclude(specExclude)
+        child.include('child-include')
+        child.exclude('child-exclude')
+        child.include(childInclude)
+        child.exclude(childExclude)
+
+        PatternSet patterns = unpackWrapper(child).patternSet
+        assertThat(patterns.includes, equalTo(['parent-include', 'child-include'] as Set))
+        assertThat(patterns.excludes, equalTo(['parent-exclude', 'child-exclude'] as Set))
+        assertThat(patterns.includeSpecs, equalTo([specInclude, childInclude] as Set))
+        assertThat(patterns.excludeSpecs, equalTo([specExclude, childExclude] as Set))
+    }
+
+    @Test
+    public void testChildUsesParentPatternsAsDefault() {
+        CopySpec child = spec.from('dir') {}
+        Spec specInclude = [:] as Spec
+        Spec specExclude = [:] as Spec
+
+        spec.include('parent-include')
+        spec.exclude('parent-exclude')
+        spec.include(specInclude)
+        spec.exclude(specExclude)
+
+        PatternSet patterns = unpackWrapper(child).patternSet
+        assertThat(patterns.includes, equalTo(['parent-include'] as Set))
+        assertThat(patterns.excludes, equalTo(['parent-exclude'] as Set))
+        assertThat(patterns.includeSpecs, equalTo([specInclude] as Set))
+        assertThat(patterns.excludeSpecs, equalTo([specExclude] as Set))
+    }
+
+    @Test
+    public void caseSensitiveFlagDefaultsToTrue() {
+        assert spec.caseSensitive
+        assert spec.patternSet.caseSensitive
+    }
+
+    @Test
+    public void childUsesCaseSensitiveFlagFromParentAsDefault() {
+        def child = spec.from('dir') {}
+        def realChild = (child as CopySpecWrapper).delegate as DefaultCopySpec
+
+        assert child.caseSensitive
+        assert realChild.patternSet.caseSensitive
+
+        spec.caseSensitive = false
+        assert !child.caseSensitive
+        assert !realChild.patternSet.caseSensitive
+
+        child.caseSensitive = true
+        assert child.caseSensitive
+        assert realChild.patternSet.caseSensitive
+    }
+
+    @Test
+    public void includeEmptyDirsFlagDefaultsToTrue() {
+        assert spec.includeEmptyDirs
+    }
+
+    @Test
+    public void childUsesIncludeEmptyDirsFlagFromParentAsDefault() {
+        def child = spec.from('dir') {}
+
+        assert child.includeEmptyDirs
+
+        spec.includeEmptyDirs = false
+        assert !child.includeEmptyDirs
+
+        child.includeEmptyDirs = true
+        assert child.includeEmptyDirs
+    }
+
+    @Test
+    public void testNoArgFilter() {
+        spec.filter(StripJavaComments)
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test
+    public void testArgFilter() {
+        spec.filter(HeadFilter, lines: 15, skip: 2)
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test
+    public void testExpand() {
+        spec.expand(version: '1.2', skip: 2)
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test
+    public void testTwoFilters() {
+        spec.filter(StripJavaComments)
+        spec.filter(HeadFilter, lines: 15, skip: 2)
+
+        assertThat(spec.allCopyActions.size(), equalTo(2))
+    }
+
+    @Test
+    public void testAddsStringNameTransformerToActions() {
+        spec.rename("regexp", "replacement")
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
+        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
+    }
+
+    @Test
+    public void testAddsPatternNameTransformerToActions() {
+        spec.rename(/regexp/, "replacement")
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
+        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
+    }
+
+    @Test
+    public void testAddsClosureToActions() {
+        spec.rename {}
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
+    }
+
+    @Test
+    public void testAddAction() {
+        def action = context.mock(Action)
+        spec.eachFile(action)
+
+        assertThat(spec.allCopyActions, equalTo([action]))
+    }
+
+    @Test
+    public void testAddActionAsClosure() {
+        def action = {}
+        spec.eachFile(action)
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test
+    public void testSpecInheritsActionsFromParent() {
+        Action parentAction = context.mock(Action, 'parent')
+        Action childAction = context.mock(Action, 'child')
+
+        spec.eachFile parentAction
+        CopySpec childSpec = spec.from('src') {
+            eachFile childAction
+        }
+
+        assertThat(unpackWrapper(childSpec).allCopyActions, equalTo([parentAction, childAction]))
+    }
+
+    @Test
+    public void testHasNoPermissionsByDefault() {
+        assert spec.fileMode == null
+        assert spec.dirMode == null
+    }
+
+    @Test
+    public void testInheritsPermissionsFromParent() {
+        spec.fileMode = 0x1
+        spec.dirMode = 0x2
+
+        CopySpec child = spec.from('src') {}
+        Assert.assertEquals(0x1, child.fileMode)
+        Assert.assertEquals(0x2, child.dirMode)
+    }
+
+    @Test
+    public void testHasNoSourceByDefault() {
+        assertFalse(spec.hasSource())
+    }
+
+    @Test
+    public void testHasSourceWhenSpecHasSource() {
+        spec.from 'source'
+        assertTrue(spec.hasSource())
+    }
+
+    @Test
+    public void testHasSourceWhenChildSpecHasSource() {
+        spec.from('source') {}
+        assertTrue(spec.hasSource())
+    }
+
+    @Test
+    public void duplicatesStrategyDefaultsToInclude() {
+        assert spec.duplicatesStrategy == DuplicatesStrategy.INCLUDE
+    }
+
+    @Test
+    public void childInheritsDuplicatesStrategyFromParent() {
+        def child = spec.from('dir') {}
+
+        assert child.duplicatesStrategy == DuplicatesStrategy.INCLUDE
+
+        spec.duplicatesStrategy = 'EXCLUDE'
+        assert child.duplicatesStrategy == DuplicatesStrategy.EXCLUDE
+
+        child.duplicatesStrategy = 'INCLUDE'
+        assert child.duplicatesStrategy == DuplicatesStrategy.INCLUDE
+    }
+
+    @Test
+    public void testMatchingCreatesAppropriateAction() {
+        spec.filesMatching "root/**/a*", Actions.doNothing()
+        assertEquals(1, spec.allCopyActions.size())
+        assertThat(spec.allCopyActions[0], instanceOf(MatchingCopyAction))
+
+        Spec<RelativePath> matchSpec = spec.allCopyActions[0].matchSpec
+        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/root/folder/abc')))
+        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/root/abc')))
+        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/notRoot/abc')))
+        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/root/bbc')))
+        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/notRoot/bbc')))
+    }
+
+    @Test
+    public void testNotMatchingCreatesAppropriateAction() {
+        // no path component starting with an a
+        spec.filesNotMatching("**/a*/**", Actions.doNothing())
+        assertEquals(1, spec.allCopyActions.size())
+        assertThat(spec.allCopyActions[0], instanceOf(MatchingCopyAction))
+
+        Spec<RelativePath> matchSpec = spec.allCopyActions[0].matchSpec
+        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'root/folder1/folder2')))
+        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'modules/project1')))
+        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'archives/folder/file')))
+        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'root/archives/file')))
+        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'root/folder/abc')))
+    }
+
+    @Test
+    void "Add spec as first child"() {
+        DefaultCopySpec child1 = spec.addFirst()
+        assert child1 != null
+        assert spec.childSpecs.size() == 1
+        assert spec.childSpecs[0] == child1
+        DefaultCopySpec child2 = spec.addFirst()
+        assert child2 != null
+        assert spec.childSpecs.size() == 2
+        assert spec.childSpecs[0] == child2
+        assert spec.childSpecs[1] == child1
+    }
+
+    @Test
+    void "Add spec in between two child specs if given child exists"() {
+        DefaultCopySpec child1 = spec.addChild()
+        DefaultCopySpec child2 = spec.addChild()
+        assert child1 != null
+        assert child2 != null
+        assert spec.childSpecs.size() == 2
+        assert spec.childSpecs[0] == child1
+        assert spec.childSpecs[1] == child2
+        DefaultCopySpec child3 = spec.addChildBeforeSpec(child2)
+        assert child3 != null
+        assert spec.childSpecs.size() == 3
+        assert spec.childSpecs[0] == child1
+        assert spec.childSpecs[1] == child3
+        assert spec.childSpecs[2] == child2
+    }
+
+    @Test
+    void "Add spec in between two child specs if given child does not exist"() {
+        DefaultCopySpec child1 = spec.addChild()
+        DefaultCopySpec child2 = spec.addChild()
+        assert child1 != null
+        assert child2 != null
+        assert spec.childSpecs.size() == 2
+        assert spec.childSpecs[0] == child1
+        assert spec.childSpecs[1] == child2
+        DefaultCopySpec unknownChild = new DefaultCopySpec(fileResolver, instantiator)
+        DefaultCopySpec child3 = spec.addChildBeforeSpec(unknownChild)
+        assert child3 != null
+        assert spec.childSpecs.size() == 3
+        assert spec.childSpecs[0] == child1
+        assert spec.childSpecs[1] == child2
+        assert spec.childSpecs[2] == child3
+    }
+
+    @Test
+    void "Add spec in between two child specs if given child is null"() {
+        DefaultCopySpec child1 = spec.addChild()
+        DefaultCopySpec child2 = spec.addChild()
+        assert child1 != null
+        assert child2 != null
+        assert spec.childSpecs.size() == 2
+        assert spec.childSpecs[0] == child1
+        assert spec.childSpecs[1] == child2
+        DefaultCopySpec child3 = spec.addChildBeforeSpec(null)
+        assert child3 != null
+        assert spec.childSpecs.size() == 3
+        assert spec.childSpecs[0] == child1
+        assert spec.childSpecs[1] == child2
+        assert spec.childSpecs[2] == child3
+    }
+
+    DefaultCopySpec unpackWrapper(CopySpec copySpec) {
+        (copySpec as CopySpecWrapper).delegate as DefaultCopySpec
+    }
+}
+
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 b5f5439..bb13cca 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
@@ -21,9 +21,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class DeleteActionImplTest extends Specification {
     @Rule
     TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecoratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecoratorTest.groovy
new file mode 100644
index 0000000..7fbb8c7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecoratorTest.groovy
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy
+
+import org.gradle.api.Action
+import org.gradle.api.file.*
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction
+import org.gradle.api.internal.tasks.SimpleWorkResult
+import org.gradle.api.tasks.WorkResult
+import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
+import org.junit.Rule
+import spock.lang.Shared
+import spock.lang.Specification
+
+class DuplicateHandlingCopyActionDecoratorTest extends Specification {
+
+    private static interface MyCopySpec extends CopySpec, CopySpecInternal {}
+
+    def fileSystem = Mock(FileSystem)
+    def delegateAction = Mock(CopyActionProcessingStreamAction)
+    def delegate = new CopyAction() {
+        WorkResult execute(CopyActionProcessingStream stream) {
+            stream.process(delegateAction)
+            return new SimpleWorkResult(true)
+        }
+    }
+
+    def appender = new TestAppender()
+    @Rule ConfigureLogging logging = new ConfigureLogging(appender)
+
+    @Shared Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
+    def driver = new CopyActionExecuter(instantiator, fileSystem)
+    def copySpec = Mock(MyCopySpec) {
+        getChildren() >> []
+    }
+
+    def duplicatesIncludedByDefault() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions {}
+
+        when:
+        visit()
+
+        then:
+        2 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+    }
+
+    def duplicatesExcludedByPerFileConfiguration() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions { it.duplicatesStrategy = 'exclude' }
+
+        when:
+        visit()
+
+        then:
+        1 * delegateAction.processFile({ FileCopyDetails it ->
+            it.relativePath.pathString == '/root/path/file1.txt'
+        })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+    }
+
+
+    def duplicatesExcludedEvenWhenRenamed() {
+        given:
+        files 'module1/path/file1.txt', 'module1/path/file2.txt', 'module2/path/file1.txt'
+
+        actions({ it.name = it.name.replaceAll('module[0-9]+/', '') }, { it.duplicatesStrategy = 'exclude' })
+
+        when:
+        visit()
+
+        then:
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+    }
+
+    def duplicatesExcludedByDefaultConfiguration() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions {}
+        copySpec.duplicatesStrategy >> DuplicatesStrategy.EXCLUDE
+
+        when:
+        visit()
+
+        then:
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+    }
+
+    def duplicatesFailByDefaultConfiguration() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions {}
+        copySpec.duplicatesStrategy >> DuplicatesStrategy.FAIL
+
+        when:
+        visit()
+
+        then:
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+        thrown(DuplicateFileCopyingException)
+    }
+
+    def duplicatesWarnByDefaultConfiguration() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions {}
+        copySpec.duplicatesStrategy >> DuplicatesStrategy.WARN
+
+        when:
+        visit()
+
+        then:
+        2 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+        appender.toString().contains('WARN Encountered duplicate path "/root/path/file1.txt"')
+    }
+
+
+    def duplicatesWarnByPerFileConfiguration() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions { it.duplicatesStrategy = 'warn' }
+
+        when:
+        visit()
+
+        then:
+        2 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+        appender.toString().contains('WARN Encountered duplicate path "/root/path/file1.txt"')
+    }
+
+    def duplicatesFailByPerFileConfiguration() {
+        given:
+        files 'path/file1.txt', 'path/file2.txt', 'path/file1.txt'
+        actions { it.duplicatesStrategy = 'fail' }
+
+        when:
+        visit()
+
+        then:
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file1.txt' })
+        1 * delegateAction.processFile({ it.relativePath.pathString == '/root/path/file2.txt' })
+        thrown(DuplicateFileCopyingException)
+    }
+
+
+    void files(String... fileNames) {
+        copySpec.destPath >> new RelativePath(false, '/root')
+        def fileTree = Mock(FileTree)
+        copySpec.getSource() >> fileTree
+        fileTree.visit(_ as FileVisitor) >> { FileVisitor visitor ->
+            fileNames.each { filename ->
+                def fvd = Mock(FileVisitDetails) {
+                    getRelativePath() >> new RelativePath(true, filename)
+                }
+                visitor.visitFile(fvd)
+            }
+            fileTree
+        }
+        copySpec.walk(_) >> { Action it -> it.execute(copySpec) }
+    }
+
+    void actions(Closure... actions) {
+        copySpec.allCopyActions >> actions.collect { new ClosureBackedAction<>(it) }
+    }
+
+    void visit() {
+        driver.execute(copySpec, delegate)
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionImplTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionImplTest.java
deleted file mode 100644
index afca533..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionImplTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.internal.file.FileResolver;
-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 static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class FileCopyActionImplTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final FileResolver fileResolver = context.mock(FileResolver.class);
-    private final FileCopyActionImpl spec = new FileCopyActionImpl(fileResolver, context.mock(CopySpecVisitor.class));
-
-    @Test public void testRootSpecResolvesItsIntoArgAsDestinationDir() {
-        final File file = new File("base dir");
-
-        spec.into("somedir");
-
-        context.checking(new Expectations() {{
-            allowing(fileResolver).resolve("somedir");
-            will(returnValue(file));
-        }});
-
-        assertThat(spec.getDestinationDir(), equalTo(file));
-    }
-
-    @Test public void testRootSpecHasNoDefaultDestinationDir() {
-        assertThat(spec.getDestinationDir(), nullValue());
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionTest.java
new file mode 100644
index 0000000..6678702
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.TestFiles;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.gradle.api.internal.file.copy.CopyActionExecuterUtil.visit;
+
+ at RunWith(JMock.class)
+public class FileCopyActionTest {
+    private File destDir;
+    private final JUnit4Mockery context = new JUnit4Mockery();
+
+    @Rule
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+
+    @Before
+    public void setUp() throws IOException {
+        destDir = tmpDir.getTestDirectory().file("dest");
+    }
+
+    @Test
+    public void plainCopy() {
+        FileCopyAction visitor = new FileCopyAction(TestFiles.resolver(destDir));
+        visit(visitor,
+                file(new RelativePath(true, "rootfile.txt"), new File(destDir, "rootfile.txt")),
+                file(new RelativePath(true, "subdir", "anotherfile.txt"), new File(destDir, "subdir/anotherfile.txt"))
+        );
+    }
+
+    private FileCopyDetailsInternal file(final RelativePath relativePath, final File targetFile) {
+        final FileCopyDetailsInternal details = context.mock(FileCopyDetailsInternal.class, relativePath.getPathString());
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(relativePath));
+            one(details).copyTo(targetFile);
+        }});
+        return details;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
deleted file mode 100644
index 06345eb..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith(JMock.class)
-public class FileCopySpecVisitorTest {
-    private File destDir;
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final FileCopySpecVisitor visitor = new FileCopySpecVisitor();
-    @Rule
-    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-
-    @Before
-    public void setUp() throws IOException {
-        destDir = tmpDir.getTestDirectory().file("dest");
-    }
-
-    @Test
-    public void plainCopy() {
-        visitor.startVisit(action(destDir));
-
-        visitor.visitDir(file(new RelativePath(false), destDir));
-
-        visitor.visitFile(file(new RelativePath(true, "rootfile.txt"), new File(destDir, "rootfile.txt")));
-
-        visitor.visitDir(file(new RelativePath(false, "subdir"), new File(destDir, "subdir")));
-
-        visitor.visitFile(file(new RelativePath(true, "subdir", "anotherfile.txt"), new File(destDir, "subdir/anotherfile.txt")));
-    }
-
-    @Test
-    public void testThrowsExceptionWhenNoDestinationSet() {
-        try {
-            visitor.startVisit(action(null));
-            fail();
-        } catch (InvalidUserDataException e) {
-            assertThat(e.getMessage(), equalTo("No copy destination directory has been specified, use 'into' to specify a target directory."));
-        }
-    }
-
-    private FileCopyAction action(final File destDir) {
-        final FileCopyAction action = context.mock(FileCopyAction.class);
-        context.checking(new Expectations(){{
-            allowing(action).getDestinationDir();
-            will(returnValue(destDir));
-        }});
-        return action;
-    }
-
-    private FileVisitDetails file(final RelativePath relativePath, final File targetFile) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, relativePath.getPathString());
-        context.checking(new Expectations(){{
-            allowing(details).getRelativePath();
-            will(returnValue(relativePath));
-            one(details).copyTo(targetFile);
-        }});
-        return details;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java
index 019ec32..8dc07b3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.file.copy;
 
 import org.apache.commons.io.IOUtils;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.WrapUtil;
 import org.junit.Test;
 
@@ -59,7 +59,7 @@ public class FilterChainTest {
 
     @Test
     public void canAddLineFilterReaderToEndOfChain() {
-        filterChain.add(HelperUtil.TEST_CLOSURE);
+        filterChain.add(TestUtil.TEST_CLOSURE);
         Reader transformedReader = filterChain.transform(originalReader);
         assertThat(transformedReader, instanceOf(LineFilter.class));
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
deleted file mode 100644
index 6ddd161..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
+++ /dev/null
@@ -1,400 +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.file.copy;
-
-import org.gradle.api.Action;
-import org.gradle.api.file.FileCopyDetails;
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.gradle.internal.nativeplatform.filesystem.FileSystem;
-import org.gradle.test.fixtures.file.TestFile;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.HelperUtil;
-import org.hamcrest.Description;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-
-import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.toList;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class MappingCopySpecVisitorTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-
-    @Rule
-    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
-    private final ReadableCopySpec spec = context.mock(ReadableCopySpec.class);
-    private final FileVisitDetails details = context.mock(FileVisitDetails.class);
-    private final FileSystem fileSystem = context.mock(FileSystem.class);
-    private final MappingCopySpecVisitor visitor = new MappingCopySpecVisitor(delegate, fileSystem);
-
-    @Test
-    public void delegatesStartAndEndVisitMethods() {
-        final CopyAction action = context.mock(CopyAction.class);
-
-        context.checking(new Expectations() {{
-            one(delegate).startVisit(action);
-            one(delegate).endVisit();
-        }});
-
-        visitor.startVisit(action);
-        visitor.endVisit();
-    }
-
-    @Test
-    public void delegatesDidWork() {
-        context.checking(new Expectations() {{
-            allowing(delegate).getDidWork();
-            will(onConsecutiveCalls(returnValue(true), returnValue(false)));
-        }});
-
-        assertTrue(visitor.getDidWork());
-        assertFalse(visitor.getDidWork());
-    }
-
-    @Test
-    public void visitFileInvokesEachCopyAction() {
-        @SuppressWarnings("unchecked")
-        final Action<FileCopyDetails> action1 = context.mock(Action.class, "action1");
-        @SuppressWarnings("unchecked")
-        final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
-        final Collector<FileCopyDetails> collectDetails1 = collector();
-        final Collector<Object> collectDetails2 = collector();
-        final Collector<Object> collectDetails3 = collector();
-
-        context.checking(new Expectations() {{
-            Sequence seq = context.sequence("seq");
-            one(delegate).visitSpec(spec);
-            inSequence(seq);
-
-            allowing(spec).getAllCopyActions();
-            will(returnValue(toList(action1, action2)));
-
-            one(action1).execute(with(notNullValue(FileCopyDetails.class)));
-            inSequence(seq);
-            will(collectTo(collectDetails1));
-
-            one(action2).execute(with(notNullValue(FileCopyDetails.class)));
-            inSequence(seq);
-            will(collectTo(collectDetails2));
-
-            one(delegate).visitFile(with(not(sameInstance(details))));
-            inSequence(seq);
-            will(collectTo(collectDetails3));
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitFile(details);
-
-        assertThat(collectDetails1.get(), sameInstance(collectDetails2.get()));
-        assertThat(collectDetails1.get(), sameInstance(collectDetails3.get()));
-    }
-
-    @Test
-    public void initialRelativePathForFileIsSpecPathPlusFilePath() {
-        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
-
-        context.checking(new Expectations() {{
-            allowing(spec).getDestPath();
-            will(returnValue(new RelativePath(false, "spec")));
-            allowing(details).getRelativePath();
-            will(returnValue(new RelativePath(true, "file")));
-        }});
-
-        assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "spec", "file")));
-    }
-
-    @Test
-    public void relativePathForDirIsSpecPathPlusFilePath() {
-        FileVisitDetails visitDetails = expectSpecAndDirVisited();
-
-        context.checking(new Expectations() {{
-            allowing(spec).getDestPath();
-            will(returnValue(new RelativePath(false, "spec")));
-            allowing(details).getRelativePath();
-            will(returnValue(new RelativePath(false, "dir")));
-        }});
-
-        assertThat(visitDetails.getRelativePath(), equalTo(new RelativePath(false, "spec", "dir")));
-    }
-
-    @Test
-    public void copyActionCanChangeFileDestinationPath() {
-        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
-
-        RelativePath newPath = new RelativePath(true, "new");
-        copyDetails.setRelativePath(newPath);
-        assertThat(copyDetails.getRelativePath(), equalTo(newPath));
-
-        copyDetails.setPath("/a/b");
-        assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "a", "b")));
-
-        copyDetails.setName("new name");
-        assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "a", "new name")));
-    }
-
-    @Test
-    public void copyActionCanExcludeFile() {
-        @SuppressWarnings("unchecked")
-        final Action<FileCopyDetails> action1 = context.mock(Action.class, "action1");
-        @SuppressWarnings("unchecked")
-        final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
-
-        context.checking(new Expectations() {{
-            Sequence seq = context.sequence("seq");
-            one(delegate).visitSpec(spec);
-            inSequence(seq);
-
-            allowing(spec).getAllCopyActions();
-            will(returnValue(toList(action1, action2)));
-
-            one(action1).execute(with(notNullValue(FileCopyDetails.class)));
-            inSequence(seq);
-            will(excludeFile());
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitFile(details);
-    }
-
-    @Test
-    public void copyActionCanFilterContentWhenFileIsCopiedToStream() {
-        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
-
-        context.checking(new Expectations() {{
-            one(details).open();
-            will(returnValue(new ByteArrayInputStream("content".getBytes())));
-        }});
-
-        mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
-
-        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
-        mappedDetails.copyTo(outstr);
-        assertThat(new String(outstr.toByteArray()), equalTo("PREFIX: content"));
-    }
-
-    @Test
-    public void copyActionCanFilterContentWhenFileIsCopiedToFile() {
-        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
-
-        // shortcut the permission logic by explicitly setting permissions
-        mappedDetails.setMode(0644);
-
-        context.checking(new Expectations() {{
-
-            one(details).open();
-            will(returnValue(new ByteArrayInputStream("content".getBytes())));
-            one(details).isDirectory();
-            will(returnValue(false));
-            one(details).getLastModified();
-            will(returnValue(90L));
-        }});
-
-        mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
-
-        TestFile destDir = tmpDir.getTestDirectory().file("test.txt");
-        mappedDetails.copyTo(destDir);
-        destDir.assertContents(equalTo("PREFIX: content"));
-    }
-
-    @Test
-    public void explicitFileModeDefinitionIsAppliedToTarget() throws IOException {
-        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
-        final TestFile destFile = tmpDir.getTestDirectory().file("test.txt").createFile();
-
-        // set file permissions explicitly
-        mappedDetails.setMode(0645);
-        context.checking(new Expectations() {{
-            one(details).copyTo(destFile);
-            will(returnValue(true));
-            one(fileSystem).chmod(destFile, 0645);
-        }});
-        mappedDetails.copyTo(destFile);
-    }
-
-    @Test
-    public void getSizeReturnsSizeOfFilteredContent() {
-        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
-
-        context.checking(new Expectations() {{
-            one(details).open();
-            will(returnValue(new ByteArrayInputStream("content".getBytes())));
-        }});
-
-        mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
-
-        assertThat(mappedDetails.getSize(), equalTo(15L));
-    }
-
-    @Test
-    public void wrappedFileElementDelegatesToSourceForRemainingMethods() {
-        final FileVisitDetails mappedDetails = expectSpecAndFileVisited();
-        final File file = new File("file");
-
-        context.checking(new Expectations() {{
-            one(details).getFile();
-            will(returnValue(file));
-        }});
-
-        assertThat(mappedDetails.getFile(), sameInstance(file));
-    }
-
-    @Test
-    public void permissionsArePreservedByDefault() {
-        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
-
-        context.checking(new Expectations() {{
-            one(details).isDirectory();
-            will(returnValue(true));
-
-            one(spec).getDirMode();
-            will(returnValue(null));
-
-            one(details).getMode();
-            will(returnValue(123));
-        }});
-
-        assertThat(copyDetails.getMode(), equalTo(123));
-    }
-
-    @Test
-    public void filePermissionsCanBeOverriddenBySpec() {
-        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
-
-        context.checking(new Expectations() {{
-            one(details).isDirectory();
-            will(returnValue(false));
-
-            one(spec).getFileMode();
-            will(returnValue(234));
-        }});
-
-        assertThat(copyDetails.getMode(), equalTo(234));
-    }
-
-
-    @Test
-    public void directoryPermissionsCanBeOverriddenBySpec() {
-        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
-
-        context.checking(new Expectations() {{
-            one(details).isDirectory();
-            will(returnValue(true));
-
-            one(spec).getDirMode();
-            will(returnValue(345));
-        }});
-
-        assertThat(copyDetails.getMode(), equalTo(345));
-    }
-
-    @Test
-    public void permissionsCanBeOverriddenByCopyAction() {
-        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
-
-        copyDetails.setMode(456);
-        assertThat(copyDetails.getMode(), equalTo(456));
-    }
-
-    private FileVisitDetails expectSpecAndFileVisited() {
-        final Collector<FileVisitDetails> collector = collector();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-
-            one(spec).getAllCopyActions();
-            will(returnValue(toList()));
-
-            one(delegate).visitFile(with(not(sameInstance(details))));
-            will(collectTo(collector));
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitFile(details);
-        return collector.get();
-    }
-
-    private FileCopyDetails expectActionExecutedWhenFileVisited() {
-        final Collector<FileCopyDetails> collectDetails = collector();
-        @SuppressWarnings("unchecked")
-        final Action<FileCopyDetails> action = context.mock(Action.class, "action1");
-
-        context.checking(new Expectations() {{
-            Sequence seq = context.sequence("seq");
-            one(delegate).visitSpec(spec);
-            inSequence(seq);
-
-            allowing(spec).getAllCopyActions();
-            will(returnValue(toList(action)));
-
-            one(action).execute(with(notNullValue(FileCopyDetails.class)));
-            inSequence(seq);
-            will(collectTo(collectDetails));
-
-            one(delegate).visitFile(with(not(sameInstance(details))));
-            inSequence(seq);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitFile(details);
-
-        return collectDetails.get();
-    }
-
-    private FileVisitDetails expectSpecAndDirVisited() {
-        final Collector<FileVisitDetails> collector = collector();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-            one(delegate).visitDir(with(not(sameInstance(details))));
-
-            will(collectTo(collector));
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(details);
-
-        return collector.get();
-    }
-
-    private org.jmock.api.Action excludeFile() {
-        return new org.jmock.api.Action() {
-            public void describeTo(Description description) {
-                description.appendText("exclude file");
-            }
-
-            public Object invoke(Invocation invocation) throws Throwable {
-                FileCopyDetails details = (FileCopyDetails) invocation.getParameter(0);
-                details.exclude();
-                return null;
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopyActionDecoratorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopyActionDecoratorTest.java
new file mode 100644
index 0000000..1127208
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopyActionDecoratorTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.gradle.api.internal.file.TestFiles.fileSystem;
+import static org.gradle.api.internal.file.copy.CopyActionExecuterUtil.visit;
+
+ at RunWith(JMock.class)
+public class NormalizingCopyActionDecoratorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final CopyActionProcessingStreamAction delegateAction = context.mock(CopyActionProcessingStreamAction.class);
+    private final CopyAction delegate = new CopyAction() {
+        public WorkResult execute(CopyActionProcessingStream stream) {
+            stream.process(delegateAction);
+            return new SimpleWorkResult(true);
+        }
+    };
+    private final NormalizingCopyActionDecorator decorator = new NormalizingCopyActionDecorator(delegate, fileSystem());
+
+    @Test
+    public void doesNotVisitADirectoryWhichHasBeenVisitedBefore() {
+        final FileCopyDetailsInternal details = file("dir", true, true);
+        final FileCopyDetailsInternal file = file("dir/file", false, true);
+
+        context.checking(new Expectations() {{
+            one(delegateAction).processFile(details);
+            one(delegateAction).processFile(file);
+        }});
+
+        visit(decorator, details, file, details);
+    }
+
+    @Test
+    public void visitsDirectoryAncestorsWhichHaveNotBeenVisited() {
+        final FileCopyDetailsInternal dir1 = file("a/b/c", true, true);
+        final FileCopyDetailsInternal file1 = file("a/b/c/file", false, true);
+
+        decorator.execute(new CopyActionProcessingStream() {
+            public void process(CopyActionProcessingStreamAction action) {
+                context.checking(new Expectations() {{
+                    one(delegateAction).processFile(with(hasPath("a")));
+                    one(delegateAction).processFile(with(hasPath("a/b")));
+                    one(delegateAction).processFile(dir1);
+                    one(delegateAction).processFile(file1);
+                }});
+
+                action.processFile(dir1);
+                action.processFile(file1);
+
+                final FileCopyDetailsInternal dir2 = file("a/b/d/e", true, true);
+                final FileCopyDetailsInternal file2 = file("a/b/d/e/file", false, true);
+
+                context.checking(new Expectations() {{
+                    one(delegateAction).processFile(with(hasPath("a/b/d")));
+                    one(delegateAction).processFile(dir2);
+                    one(delegateAction).processFile(file2);
+                }});
+
+                action.processFile(dir2);
+                action.processFile(file2);
+            }
+        });
+    }
+
+    @Test
+    public void visitsFileAncestorsWhichHaveNotBeenVisited() {
+        final FileCopyDetailsInternal details = file("a/b/c", false, true);
+
+        context.checking(new Expectations() {{
+            one(delegateAction).processFile(with(hasPath("a")));
+            one(delegateAction).processFile(with(hasPath("a/b")));
+            one(delegateAction).processFile(details);
+        }});
+
+        visit(decorator, details);
+    }
+
+    @Test
+    public void visitsAnEmptyDirectoryIfCorrespondingOptionIsOn() {
+        final FileCopyDetailsInternal dir = file("dir", true, true);
+
+        context.checking(new Expectations() {{
+            one(delegateAction).processFile(dir);
+        }});
+
+        visit(decorator, dir);
+    }
+
+    @Test
+    public void doesNotVisitAnEmptyDirectoryIfCorrespondingOptionIsOff() {
+        final FileCopyDetailsInternal dir = file("dir", true, false);
+
+        context.checking(new Expectations() {{
+            exactly(0).of(delegateAction).processFile(dir);
+        }});
+
+        visit(decorator, dir);
+    }
+
+    private FileCopyDetailsInternal file(final String path, final boolean isDir, final boolean includeEmptyDirs) {
+        final FileCopyDetailsInternal details = context.mock(FileCopyDetailsInternal.class, path);
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(false, path)));
+            allowing(details).isDirectory();
+            will(returnValue(isDir));
+            allowing(details).isIncludeEmptyDirs();
+            will(returnValue(includeEmptyDirs));
+        }});
+        return details;
+    }
+
+    private Matcher<FileCopyDetailsInternal> hasPath(final String path) {
+        return new BaseMatcher<FileCopyDetailsInternal>() {
+            public void describeTo(Description description) {
+                description.appendText("has path ").appendValue(path);
+            }
+
+            public boolean matches(Object o) {
+                FileCopyDetails details = (FileCopyDetails) o;
+                return details.getRelativePath().getPathString().equals(path);
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitorTest.java
deleted file mode 100644
index 53e3889..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitorTest.java
+++ /dev/null
@@ -1,226 +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.file.copy;
-
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.RelativePath;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.integration.junit4.JMock;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class NormalizingCopySpecVisitorTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
-    private final NormalizingCopySpecVisitor visitor = new NormalizingCopySpecVisitor(delegate);
-    private final ReadableCopySpec spec = context.mock(ReadableCopySpec.class);
-
-    private void allowGetIncludeEmptyDirs() {
-        context.checking(new Expectations() {{
-            allowing(spec).getIncludeEmptyDirs();
-            will(returnValue(true));
-        }});
-    }
-
-    @Test
-    public void doesNotVisitADirectoryWhichHasBeenVisitedBefore() {
-        final FileVisitDetails details = file("dir");
-        final FileVisitDetails file = file("dir/file");
-
-        allowGetIncludeEmptyDirs();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-            one(delegate).visitDir(details);
-            one(delegate).visitFile(file);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(details);
-        visitor.visitFile(file);
-        visitor.visitDir(details);
-    }
-
-    @Test
-    public void doesNotVisitADirectoryUntilAChildFileIsVisited() {
-        final FileVisitDetails dir = file("dir");
-        final FileVisitDetails file = file("dir/file");
-
-        allowGetIncludeEmptyDirs();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(dir);
-
-        context.checking(new Expectations() {{
-            one(delegate).visitDir(dir);
-            one(delegate).visitFile(file);
-        }});
-
-        visitor.visitFile(file);
-    }
-
-    @Test
-    public void doesNotVisitADirectoryUntilAChildDirIsVisited() {
-        final FileVisitDetails dir = file("dir");
-        final FileVisitDetails subdir = file("dir/sub");
-        final FileVisitDetails file = file("dir/sub/file");
-
-        allowGetIncludeEmptyDirs();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(dir);
-        visitor.visitDir(subdir);
-
-        context.checking(new Expectations() {{
-            one(delegate).visitDir(dir);
-            one(delegate).visitDir(subdir);
-            one(delegate).visitFile(file);
-        }});
-
-        visitor.visitFile(file);
-    }
-
-    @Test
-    public void visitsDirectoryAncestorsWhichHaveNotBeenVisited() {
-        final FileVisitDetails dir1 = file("a/b/c");
-        final FileVisitDetails file1 = file("a/b/c/file");
-
-        allowGetIncludeEmptyDirs();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-            one(delegate).visitDir(with(hasPath("a")));
-            one(delegate).visitDir(with(hasPath("a/b")));
-            one(delegate).visitDir(dir1);
-            one(delegate).visitFile(file1);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(dir1);
-        visitor.visitFile(file1);
-
-        final FileVisitDetails dir2 = file("a/b/d/e");
-        final FileVisitDetails file2 = file("a/b/d/e/file");
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-            one(delegate).visitDir(with(hasPath("a/b/d")));
-            one(delegate).visitDir(dir2);
-            one(delegate).visitFile(file2);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(dir2);
-        visitor.visitFile(file2);
-    }
-
-    @Test
-    public void visitsFileAncestorsWhichHaveNotBeenVisited() {
-        final FileVisitDetails details = file("a/b/c");
-
-        allowGetIncludeEmptyDirs();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-            one(delegate).visitDir(with(hasPath("a")));
-            one(delegate).visitDir(with(hasPath("a/b")));
-            one(delegate).visitFile(details);
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitFile(details);
-    }
-
-    @Test
-    public void visitSpecDelegatesToVisitor() {
-        allowGetIncludeEmptyDirs();
-
-        context.checking(new Expectations() {{
-            one(delegate).visitSpec(spec);
-        }});
-
-        visitor.visitSpec(spec);
-    }
-
-    @Test
-    public void visitsAnEmptyDirectoryIfCorrespondingOptionIsOn() {
-        final FileVisitDetails dir = file("dir");
-
-        context.checking(new Expectations() {{
-            one(spec).getIncludeEmptyDirs();
-            will(returnValue(true));
-            one(delegate).visitSpec(spec);
-            one(delegate).visitDir(dir);
-            one(delegate).endVisit();
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(dir);
-        visitor.endVisit();
-    }
-
-    @Test
-    public void doesNotVisitAnEmptyDirectoryIfCorrespondingOptionIsOff() {
-        FileVisitDetails dir = file("dir");
-
-        context.checking(new Expectations() {{
-            one(spec).getIncludeEmptyDirs();
-            will(returnValue(false));
-            one(delegate).visitSpec(spec);
-            one(delegate).endVisit();
-        }});
-
-        visitor.visitSpec(spec);
-        visitor.visitDir(dir);
-        visitor.endVisit();
-    }
-
-    private FileVisitDetails file(final String path) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
-        context.checking(new Expectations() {{
-            allowing(details).getRelativePath();
-            will(returnValue(RelativePath.parse(false, path)));
-        }});
-        return details;
-    }
-
-    private Matcher<FileVisitDetails> hasPath(final String path) {
-        return new BaseMatcher<FileVisitDetails>() {
-            public void describeTo(Description description) {
-                description.appendText("has path ").appendValue(path);
-            }
-
-            public boolean matches(Object o) {
-                FileVisitDetails details = (FileVisitDetails) o;
-                return details.getRelativePath().getPathString().equals(path);
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopyActionDecoratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopyActionDecoratorTest.groovy
new file mode 100644
index 0000000..7844dbb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopyActionDecoratorTest.groovy
@@ -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.internal.file.copy
+
+import org.gradle.api.Action
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.test.fixtures.file.WorkspaceTest
+
+class SyncCopyActionDecoratorTest extends WorkspaceTest {
+
+    FileCopier copier
+
+    def setup() {
+        copier = new FileCopier(new DirectInstantiator(), TestFiles.resolver(testDirectory), TestFiles.fileLookup())
+    }
+
+    void deletesExtraFilesFromDestinationDirectoryAtTheEndOfVisit() {
+        given:
+        file("src").with {
+            createFile("subdir/included.txt")
+            createFile("included.txt")
+        }
+
+        file("dest").with {
+            createFile("subdir/included.txt")
+            createFile("subdir/extra.txt")
+            createFile("included.txt")
+            createFile("extra.txt")
+            createDir("extra")
+        }
+
+        when:
+        def result = copier.sync({
+            it.from "src"
+            it.into "dest"
+        } as Action)
+
+        then:
+        result.didWork
+        file("dest").assertHasDescendants("subdir/included.txt", "included.txt");
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
deleted file mode 100644
index 68e806b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.copy;
-
-import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.file.FileVisitor;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.internal.file.collections.DirectoryFileTree;
-import org.gradle.test.fixtures.file.TestFile;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertTrue;
-
- at RunWith(JMock.class)
-public class SyncCopySpecVisitorTest {
-    @Rule
-    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
-    private final SyncCopySpecVisitor visitor = new SyncCopySpecVisitor(delegate);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations(){{
-            allowing(delegate).startVisit(with(notNullValue(CopyAction.class)));
-            allowing(delegate).visitFile(with(notNullValue(FileVisitDetails.class)));
-            allowing(delegate).visitDir(with(notNullValue(FileVisitDetails.class)));
-            allowing(delegate).endVisit();
-        }});
-    }
-    
-    @Test
-    public void deletesExtraFilesFromDestinationDirectoryAtTheEndOfVisit() {
-        TestFile destDir = tmpDir.createDir("dest");
-        destDir.createFile("subdir/included.txt");
-        destDir.createFile("subdir/extra.txt");
-        destDir.createFile("included.txt");
-        destDir.createFile("extra.txt");
-
-        visitor.startVisit(action(destDir));
-        visitor.visitDir(dir("subdir"));
-        visitor.visitFile(file("subdir/included.txt"));
-        visitor.visitFile(file("included.txt"));
-        visitor.endVisit();
-
-        destDir.assertHasDescendants("subdir/included.txt", "included.txt");
-    }
-
-    @Test
-    public void deletesExtraDirectoriesFromDestinationDirectoryAtTheEndOfVisit() throws Exception {
-        TestFile destDir = tmpDir.createDir("dest");
-        destDir.createFile("included.txt");
-        destDir.createFile("extra/extra.txt");
-
-        visitor.startVisit(action(destDir));
-        visitor.visitFile(file("included.txt"));
-
-        // TODO - delete these
-        Field field = SyncCopySpecVisitor.class.getDeclaredField("visited");
-        field.setAccessible(true);
-        Set visited = (Set) field.get(visitor);
-        assert visited.contains(new RelativePath(true, "included.txt"));
-        assert !visited.contains(new RelativePath(true, "extra", "extra.txt"));
-        final Set<RelativePath> actual = new HashSet<RelativePath>();
-        new DirectoryFileTree(destDir).postfix().visit(new FileVisitor() {
-            public void visitDir(FileVisitDetails dirDetails) {
-            }
-
-            public void visitFile(FileVisitDetails fileDetails) {
-                actual.add(fileDetails.getRelativePath());
-            }
-        });
-        assert actual.contains(new RelativePath(true, "included.txt"));
-        assert actual.contains(new RelativePath(true, "extra", "extra.txt"));
-
-        visitor.endVisit();
-
-        destDir.assertHasDescendants("included.txt");
-    }
-
-    @Test
-    public void doesNotDeleteDestDirectoryWhenNothingCopied() {
-        TestFile destDir = tmpDir.createDir("dest");
-        destDir.createFile("extra.txt");
-        destDir.createFile("extra/extra.txt");
-
-        visitor.startVisit(action(destDir));
-        visitor.endVisit();
-
-        destDir.assertHasDescendants();
-    }
-
-    @Test
-    public void didWorkWhenDelegateDidWork() {
-        context.checking(new Expectations() {{
-            allowing(delegate).getDidWork();
-            will(returnValue(true));
-        }});
-
-        assertTrue(visitor.getDidWork());
-    }
-
-    @Test
-    public void didWorkWhenFilesDeleted() {
-        TestFile destDir = tmpDir.createDir("dest");
-        destDir.createFile("extra.txt");
-
-        visitor.startVisit(action(destDir));
-        visitor.endVisit();
-
-        assertTrue(visitor.getDidWork());
-    }
-
-    private FileCopyAction action(final File destDir) {
-        final FileCopyAction action = context.mock(FileCopyAction.class);
-
-        context.checking(new Expectations() {{
-            allowing(action).getDestinationDir();
-            will(returnValue(destDir));
-        }});
-
-        return action;
-    }
-
-    private FileVisitDetails file(final String path) {
-        return file(RelativePath.parse(true, path));
-    }
-
-    private FileVisitDetails dir(final String path) {
-        return file(RelativePath.parse(false, path));
-    }
-
-    private FileVisitDetails file(final RelativePath relativePath) {
-        final FileVisitDetails details = context.mock(FileVisitDetails.class, relativePath.toString());
-
-        context.checking(new Expectations(){{
-            allowing(details).getRelativePath();
-            will(returnValue(relativePath));
-        }});
-
-        return details;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcherTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcherTest.java
deleted file mode 100644
index 697e4d7..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcherTest.java
+++ /dev/null
@@ -1,218 +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.file.pattern;
-
-import org.junit.Test;
-import static org.junit.Assert.*;
-import org.gradle.api.file.RelativePath;
-
-import java.util.List;
-
-public class DefaultPatternMatcherTest {
-    private DefaultPatternMatcher matcher;
-    private RelativePath path;
-
-
-    @Test public void testParsing() {
-        List<PatternStep> steps;
-        PatternStep step;
-
-        // parse forward slash pattern
-        matcher = new DefaultPatternMatcher(true, true, "a", "b", "c");
-        steps = matcher.getStepsForTest();
-        assertEquals(3, steps.size());
-        step = steps.get(2);
-        assertTrue(step.matches("c", true));
-        //assertFalse(step.matches("c", false));
-
-        // try matching a wrong literal string
-        assertFalse(step.matches("somethingelse", true));
-
-        // check greedy
-        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c");
-        steps = matcher.getStepsForTest();
-        step = steps.get(1);
-        assertTrue(step.isGreedy());
-    }
-
-    @Test public void testEmpty() {
-        DefaultPatternMatcher matcher = new DefaultPatternMatcher(true, true);
-        List<PatternStep> steps = matcher.getStepsForTest();
-        assertEquals(0, steps.size());
-
-        // both empty
-        RelativePath path = new RelativePath(true);
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        // empty matcher, non-empty path
-        path = new RelativePath(true, "a");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        // non-empty matcher, empty path
-        matcher = new DefaultPatternMatcher(true, true, "a");
-        path = new RelativePath(true);
-        assertFalse(matcher.isSatisfiedBy(path));
-
-    }
-
-    @Test public void testLiterals() {
-        matcher = new DefaultPatternMatcher(true, true, "a");
-        path = new RelativePath(true, "a");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "b");
-        assertFalse(matcher.isSatisfiedBy(path));
-        
-        matcher = new DefaultPatternMatcher(true, true, "a", "b");
-        path = new RelativePath(true, "a", "b");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "c");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "b", "c");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        // short path
-        path = new RelativePath(true, "a");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        // long path
-        path = new RelativePath(true, "a", "b", "c");
-        assertFalse(matcher.isSatisfiedBy(path));
-    }
-
-    @Test public void testPartials() {
-        matcher = new DefaultPatternMatcher(true, true, "a", "b", "c");
-        path = new RelativePath(false, "a", "b");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "b");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        matcher = new DefaultPatternMatcher(false, true, "a", "b", "c");
-        path = new RelativePath(false, "a", "b");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-    }
-
-    @Test public void testWildCards() {
-        matcher = new DefaultPatternMatcher(true, true, "*");
-        path = new RelativePath(true, "anything");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "anything", "b");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        matcher = new DefaultPatternMatcher(true, true, "any??ing");
-        path = new RelativePath(true, "anything");
-        assertTrue(matcher.isSatisfiedBy(path));
-    }
-
-    @Test public void testGreedy() {
-        matcher = new DefaultPatternMatcher(true, true, "a", "**");
-        path = new RelativePath(true, "a", "b", "c");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        //leading greedy
-        matcher = new DefaultPatternMatcher(true, true, "**", "c");
-        path = new RelativePath(true, "a", "b", "c");
-        assertTrue(matcher.isSatisfiedBy(path));
-        
-        path = new RelativePath(true, "a", "b", "d");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        // inner greedy
-        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c");
-        path = new RelativePath(true, "a", "b", "c");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "aa", "bb", "c");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(false, "a", "aa", "bb", "d");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "aa", "bb", "d");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        // fake trail
-        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c", "d");
-        path = new RelativePath(true, "a", "b", "c", "e", "c", "d");
-        assertTrue(matcher.isSatisfiedBy(path));
-        
-        // multiple greedies
-        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c", "**", "e");
-        path = new RelativePath(true, "a", "b", "c", "d", "e");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "b", "bb", "c", "d", "e");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "q", "bb", "c", "d", "c", "d", "e");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        // Missing greedy
-        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c");
-        path = new RelativePath(true, "a", "c");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "a", "d");
-        assertFalse(matcher.isSatisfiedBy(path));
-    }
-
-    @Test public void testTypical() {
-        matcher = new DefaultPatternMatcher(true, true, "**", "CVS", "*");
-        path = new RelativePath(true, "CVS", "Repository");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "org", "gradle", "CVS", "Entries");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "org", "gradle", "CVS", "foo", "bar", "Entries");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        matcher = new DefaultPatternMatcher(true, true, "src", "main", "**");
-        path = new RelativePath(true, "src", "main", "groovy", "org");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "src", "test", "groovy", "org");
-        assertFalse(matcher.isSatisfiedBy(path));
-
-        matcher = new DefaultPatternMatcher(true, true, "**", "test", "**");
-        // below fails, trailing ** not ignored
-        path = new RelativePath(true, "src", "main", "test");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "src", "test", "main");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "src", "main", "fred");
-        assertFalse(matcher.isSatisfiedBy(path));
-    }
-
-    @Test public void testCase() {
-        matcher = new DefaultPatternMatcher(true, true, "a", "b");
-        assertTrue(matcher.isSatisfiedBy(new RelativePath(true, "a", "b")));
-        assertFalse(matcher.isSatisfiedBy(new RelativePath(true, "A", "B")));
-
-        matcher = new DefaultPatternMatcher(true, false, "a", "b");
-        assertTrue(matcher.isSatisfiedBy(new RelativePath(true, "a", "b")));
-        assertTrue(matcher.isSatisfiedBy(new RelativePath(true, "A", "B")));
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/FixedPatternStepTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/FixedPatternStepTest.groovy
new file mode 100644
index 0000000..74e0949
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/FixedPatternStepTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern
+
+import spock.lang.Specification
+
+class FixedPatternStepTest extends Specification {
+    def "matches name case sensitive"() {
+        def step = new FixedPatternStep("name", true)
+
+        expect:
+        step.matches("name")
+        !step.matches("Name")
+        !step.matches("")
+        !step.matches("something else")
+    }
+
+    def "matches name case insensitive"() {
+        def step = new FixedPatternStep("name", false)
+
+        expect:
+        step.matches("name")
+        step.matches("Name")
+        step.matches("NAME")
+        !step.matches("")
+        !step.matches("something else")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/FixedStepsPathMatcherTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/FixedStepsPathMatcherTest.groovy
new file mode 100644
index 0000000..53d740e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/FixedStepsPathMatcherTest.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern
+
+import spock.lang.Specification
+
+class FixedStepsPathMatcherTest extends Specification {
+    def "calculates min and max number of segments"() {
+        def next = Stub(PathMatcher) {
+            getMinSegments() >> 2
+            getMaxSegments() >> 3
+        }
+        def matcher = new FixedStepsPathMatcher([Stub(PatternStep), Stub(PatternStep)], next)
+
+        expect:
+        matcher.minSegments == 4
+        matcher.maxSegments == 5
+    }
+
+    def "calculates min and max number of segments when next pattern is unbounded"() {
+        def next = Stub(PathMatcher) {
+            getMinSegments() >> 2
+            getMaxSegments() >> Integer.MAX_VALUE
+        }
+        def matcher = new FixedStepsPathMatcher([Stub(PatternStep), Stub(PatternStep)], next)
+
+        expect:
+        matcher.minSegments == 4
+        matcher.maxSegments == Integer.MAX_VALUE
+    }
+
+    def "matches path that is appropriate length and matches every step and the next pattern"() {
+        def matcher = new FixedStepsPathMatcher([step("a"), step("b")], matchesLastOrSecondLast("c"))
+
+        expect:
+        !matcher.matches(["a"] as String[], 0)
+        !matcher.matches(["a", "b"] as String[], 0)
+        !matcher.matches(["a", "b", "c"] as String[], 1)
+        !matcher.matches(["a", "b", "c", "d", "e"] as String[], 0)
+        !matcher.matches(["a", "b", "c", "d", "e", "f"] as String[], 1)
+
+        and:
+        matcher.matches(["a", "b", "c"] as String[], 0)
+        matcher.matches(["a", "b", "c", "d"] as String[], 0)
+        matcher.matches(["prefix", "a", "b", "c"] as String[], 1)
+
+        and:
+        !matcher.matches(["other", "b", "c"] as String[], 0)
+        !matcher.matches(["a", "other", "c"] as String[], 0)
+        !matcher.matches(["a", "b", "other"] as String[], 0)
+        !matcher.matches(["prefix", "a", "b", "other"] as String[], 1)
+    }
+
+    def "path is a prefix when it matches the steps and is shorter than or equal to the number of steps"() {
+        def matcher = new FixedStepsPathMatcher([step("a"), step("b")], matchesLastOrSecondLast("c"))
+
+        expect:
+        matcher.isPrefix(["a"] as String[], 0)
+        matcher.isPrefix(["a", "b"] as String[], 0)
+        matcher.isPrefix(["prefix", "a"] as String[], 1)
+        matcher.isPrefix(["prefix", "a", "b"] as String[], 1)
+        !matcher.isPrefix(["a", "other"] as String[], 0)
+        !matcher.isPrefix(["other", "b"] as String[], 0)
+        !matcher.isPrefix(["other"] as String[], 0)
+        !matcher.isPrefix(["prefix", "a", "other"] as String[], 1)
+    }
+
+    def "path is a prefix when it matches the steps and is a prefix of next pattern"() {
+        def matcher = new FixedStepsPathMatcher([step("a"), step("b")], matchesLastOrSecondLast("c"))
+
+        expect:
+        matcher.isPrefix(["a", "b", "c"] as String[], 0)
+        matcher.isPrefix(["a", "b", "c", "d"] as String[], 0)
+        matcher.isPrefix(["prefix", "a", "b", "c", "d"] as String[], 1)
+        !matcher.isPrefix(["other", "b", "c"] as String[], 0)
+        !matcher.isPrefix(["a", "other", "c"] as String[], 0)
+        !matcher.isPrefix(["a", "b", "other"] as String[], 0)
+    }
+
+    def matchesLastOrSecondLast(String value) {
+        return Stub(PathMatcher) {
+            getMinSegments() >> 1
+            getMaxSegments() >> 2
+            matches(_, _) >> { String[] segments, int index ->
+                return segments[index] == value && segments.length - index <= 2
+            }
+            isPrefix(_, _) >> { String[] segments, int index ->
+                return segments[index] == value;
+            }
+        }
+    }
+
+    def step(String value) {
+        return Stub(PatternStep) {
+            matches(value) >> true
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/GreedyPathMatcherTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/GreedyPathMatcherTest.groovy
new file mode 100644
index 0000000..6a600b0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/GreedyPathMatcherTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern
+
+import spock.lang.Specification
+
+class GreedyPathMatcherTest extends Specification {
+    def "calculates min and max number of segments"() {
+        def next = Stub(PathMatcher) {
+            getMinSegments() >> 2
+            getMaxSegments() >> 3
+        }
+        def matcher = new GreedyPathMatcher(next)
+
+        expect:
+        matcher.minSegments == 2
+        matcher.maxSegments == Integer.MAX_VALUE
+    }
+
+    def "matches a path whose suffix matches the next pattern"() {
+        def matcher = new GreedyPathMatcher(matchesLastOrSecondLast("c"))
+
+        expect:
+        !matcher.matches(["a"] as String[], 0)
+        !matcher.matches(["a", "c", "d", "b"] as String[], 0)
+
+        matcher.matches(["c"] as String[], 0)
+        matcher.matches(["prefix", "c"] as String[], 1)
+        matcher.matches(["a", "c"] as String[], 0)
+        matcher.matches(["a", "c", "d"] as String[], 0)
+        matcher.matches(["a", "b", "c", "d"] as String[], 0)
+        matcher.matches(["a", "b", "c", "d"] as String[], 1)
+    }
+
+    def "every path is a prefix"() {
+        def matcher = new GreedyPathMatcher(matchesLastOrSecondLast("c"))
+
+        expect:
+        matcher.isPrefix(["a"] as String[], 0)
+        matcher.isPrefix(["a", "b"] as String[], 1)
+    }
+
+    def matchesLastOrSecondLast(String value) {
+        return Stub(PathMatcher) {
+            getMinSegments() >> 1
+            getMaxSegments() >> 2
+            matches(_, _) >> { String[] segments, int index ->
+                return segments[index] == value && segments.length - index <= 2
+            }
+            isPrefix(_, _) >> { String[] segments, int index ->
+                return segments[index] == value;
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcherTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcherTest.java
deleted file mode 100644
index 3d4af16..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcherTest.java
+++ /dev/null
@@ -1,70 +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.file.pattern;
-
-import org.junit.Test;
-import static org.junit.Assert.*;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.specs.Spec;
-
-public class NameOnlyPatternMatcherTest {
-
-    @Test public void testLiteralName() {
-        Spec<RelativePath> matcher;
-        RelativePath path;
-
-        matcher = new NameOnlyPatternMatcher(true, true, "fred.txt");
-
-        path = new RelativePath(true, "fred.txt");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "something.else");
-        assertFalse(matcher.isSatisfiedBy(path));
-    }
-
-    @Test public void testPartialMatch() {
-        Spec<RelativePath> matcher;
-        RelativePath path;
-
-        matcher = new NameOnlyPatternMatcher(true, true, "fred");
-        path = new RelativePath(true, "fred");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(false, "subdir");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-
-        matcher = new NameOnlyPatternMatcher(false, true, "fred");
-        path = new RelativePath(true, "fred");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(false, "subdir");
-        assertFalse(matcher.isSatisfiedBy(path));
-    }
-
-    @Test public void testWildcardInName() {
-        Spec<RelativePath> matcher;
-        RelativePath path;
-
-        matcher = new NameOnlyPatternMatcher(true, true, "*.jsp");
-        path = new RelativePath(true, "fred.jsp");
-        assertTrue(matcher.isSatisfiedBy(path));
-
-        path = new RelativePath(true, "fred.java");
-        assertFalse(matcher.isSatisfiedBy(path));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java
index 09e78e2..e2ba500 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java
@@ -16,74 +16,136 @@
 
 package org.gradle.api.internal.file.pattern;
 
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import org.gradle.api.file.RelativePath;
-import org.gradle.api.specs.Spec;
-
-import java.util.List;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
 
 public class PatternMatcherFactoryTest {
     private Spec<RelativePath> matcher;
-    private RelativePath path;
-    List<PatternStep> steps;
-    PatternStep step;
+
+    @Test public void testEmpty() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "");
+        assertThat(matcher, matchesFile());
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b")));
+    }
 
     @Test public void testSlashDirection() {
         matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/c");
-        assertTrue(matcher instanceof DefaultPatternMatcher);
-        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
-        assertEquals(3, steps.size());
-        step = steps.get(2);
-        assertTrue(step.matches("c", true));
-
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b")));
+        assertThat(matcher, not(matchesFile("a", "b", "c", "d")));
+        assertThat(matcher, not(matchesFile("a", "other", "c")));
 
         matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a\\b\\c");
-        assertTrue(matcher instanceof DefaultPatternMatcher);
-        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
-        assertEquals(3, steps.size());
-        step = steps.get(2);
-        assertTrue(step.matches("c", true));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b")));
+        assertThat(matcher, not(matchesFile("a", "b", "c", "d")));
+        assertThat(matcher, not(matchesFile("a", "other", "c")));
+    }
+
+    @Test public void testCaseSensitive() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/c");
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile("a", "b", "C")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, false, "a\\b\\c");
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, matchesFile("a", "b", "C"));
     }
 
-    /**
-     * Test that trailing slash gets ** added automatically
-     */
-    @Test public void testAddGreedy() {
+    @Test public void testTrailingSlashIsReplacedWithTrailingGreedy() {
         matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/");
-        assertTrue(matcher instanceof DefaultPatternMatcher);
-        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
-        assertEquals(3, steps.size());
-        step = steps.get(2);
-        assertTrue(step.isGreedy());
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "c")));
+        assertThat(matcher, not(matchesFile("c", "b")));
 
         matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a\\b\\");
-        assertTrue(matcher instanceof DefaultPatternMatcher);
-        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
-        assertEquals(3, steps.size());
-        step = steps.get(2);
-        assertTrue(step.isGreedy());
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "c")));
+        assertThat(matcher, not(matchesFile("c", "b")));
     }
 
-    @Test public void testNameOnly() {
+    @Test public void testGreedyWithTrailingName() {
         matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/*.jsp");
-        assertTrue(matcher instanceof NameOnlyPatternMatcher);
-        path = new RelativePath(true, "fred.jsp");
-        assertTrue(matcher.isSatisfiedBy(path));
+        assertThat(matcher, matchesFile("fred.jsp"));
+        assertThat(matcher, matchesFile("a", "fred.jsp"));
+        assertThat(matcher, matchesFile("a", "b", "fred.jsp"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("fred.txt")));
+        assertThat(matcher, not(matchesFile("src", "fred.txt")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/**/*.jsp");
+        assertThat(matcher, matchesFile("fred.jsp"));
+        assertThat(matcher, matchesFile("a", "fred.jsp"));
+        assertThat(matcher, matchesFile("a", "b", "fred.jsp"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("fred.txt")));
+        assertThat(matcher, not(matchesFile("src", "fred.txt")));
+    }
+
+    @Test public void testWildcards() {
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "a/*");
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b", "c")));
+        assertThat(matcher, not(matchesFile("other", "b")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "?");
+        assertThat(matcher, matchesFile("?"));
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("C"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("abc")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "?b??e*");
+        assertThat(matcher, matchesFile("?b??e*"));
+        assertThat(matcher, matchesFile("abcde"));
+        assertThat(matcher, matchesFile("abcdefgh"));
+        assertThat(matcher, not(matchesFile("aaaae")));
+        assertThat(matcher, not(matchesFile("abcdfe")));
+        assertThat(matcher, not(matchesFile("abc")));
+    }
+
+    @Test public void testLiteralsPartialMatchingDirs() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, not(matchesDir("other")));
+        assertThat(matcher, not(matchesDir("other", "b")));
+        assertThat(matcher, not(matchesDir("b", "other")));
+        assertThat(matcher, not(matchesDir("a", "b", "c")));
     }
 
-    @Test public void testShortenedGreedy() {
-        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/");
-        assertTrue(matcher instanceof DefaultPatternMatcher);
-        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
-        assertEquals(1, steps.size());
-        step = steps.get(0);
-        assertTrue(step.isGreedy());
+    @Test public void testGreedy() {
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**");
+        assertThat(matcher, matchesFile());
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**/");
+        assertThat(matcher, matchesFile());
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**/**/**");
+        assertThat(matcher, matchesFile());
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
     }
 
     @Test public void testGreedyPatternsMatchingFiles() {
@@ -151,6 +213,26 @@ public class PatternMatcherFactoryTest {
         assertThat(matcher, not(matchesFile("a")));
         assertThat(matcher, not(matchesFile("a", "b")));
         assertThat(matcher, not(matchesFile("d", "a", "b")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**/*");
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile()));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "*/**");
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile()));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "a/**/*");
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, matchesFile("a", "b", "c", "d"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("b", "a")));
     }
     
     @Test public void testGreedyPatternsPartialMatchingDirs() {
@@ -197,6 +279,29 @@ public class PatternMatcherFactoryTest {
         assertThat(matcher, matchesDir("a", "b", "c", "d"));
         assertThat(matcher, not(matchesDir("a", "c", "b", "c")));
         assertThat(matcher, not(matchesDir("d", "a", "b")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/*");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, matchesDir("a", "b", "c"));
+        assertThat(matcher, matchesDir("a", "b", "d", "c"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "*/**");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, matchesDir("a", "b", "c"));
+        assertThat(matcher, matchesDir("a", "b", "d", "c"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/**/*");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, matchesDir("a", "b", "c"));
+        assertThat(matcher, matchesDir("a", "b", "d", "c"));
+        assertThat(matcher, not(matchesDir("b")));
+        assertThat(matcher, not(matchesDir("b", "a")));
     }
 
     private Matcher<Spec<RelativePath>> matchesFile(String... paths) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.groovy
new file mode 100644
index 0000000..f364cd2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern
+
+import spock.lang.Specification
+
+class PatternStepFactoryTest extends Specification {
+    def "creates step for ** wildcard"() {
+        expect:
+        def step = PatternStepFactory.getStep("**", true);
+        step instanceof AnyWildcardPatternStep
+        step.matches("anything")
+        step.matches("")
+    }
+
+    def "creates step for * wildcard"() {
+        expect:
+        def step = PatternStepFactory.getStep("*", true);
+        step instanceof AnyWildcardPatternStep
+        step.matches("anything")
+        step.matches("")
+    }
+
+    def "creates step for * prefix wildcard"() {
+        expect:
+        def step1 = PatternStepFactory.getStep("*abc", true);
+        step1 instanceof WildcardPrefixPatternStep
+        step1.matches("abc")
+        step1.matches("thing.abc")
+        !step1.matches("thing.java")
+
+        and:
+        def step2 = PatternStepFactory.getStep("**abc", true);
+        step2 instanceof WildcardPrefixPatternStep
+        step2.matches("abc")
+        step2.matches("thing.abc")
+        !step2.matches("thing.java")
+    }
+
+    def "creates step for wildcard segment"() {
+        expect:
+        def step1 = PatternStepFactory.getStep("a?c", true);
+        step1 instanceof RegExpPatternStep
+        step1.matches("abc")
+        !step1.matches("ABC")
+        !step1.matches("other")
+
+        and:
+        def step2 = PatternStepFactory.getStep("a*c", true);
+        step2 instanceof RegExpPatternStep
+        step2.matches("ac")
+        step2.matches("abc")
+        !step2.matches("ABC")
+        !step2.matches("other")
+
+        and:
+        def step3 = PatternStepFactory.getStep("?bc", true);
+        step3 instanceof RegExpPatternStep
+        step3.matches("abc")
+        step3.matches("Abc")
+        !step3.matches("bc")
+        !step3.matches("ABC")
+        !step3.matches("other")
+
+        and:
+        def step4 = PatternStepFactory.getStep("*?bc", true);
+        step4 instanceof RegExpPatternStep
+        step4.matches("abc")
+        step4.matches("123abc")
+        !step4.matches("bc")
+        !step4.matches("ABC")
+        !step4.matches("other")
+
+        and:
+        def step5 = PatternStepFactory.getStep("*bc*", true);
+        step5 instanceof RegExpPatternStep
+        step5.matches("bc")
+        step5.matches("abc")
+        step5.matches("bcd")
+        step5.matches("abcd")
+        step5.matches("123abc")
+        !step5.matches("BC")
+        !step5.matches("ABC")
+        !step5.matches("other")
+
+        and:
+        def step6 = PatternStepFactory.getStep("?", true);
+        step6 instanceof RegExpPatternStep
+        step6.matches("a")
+        !step6.matches("")
+        !step6.matches("abc")
+    }
+
+    def "creates step for non-wildcard segment"() {
+        expect:
+        def step = PatternStepFactory.getStep("abc", true);
+        step instanceof FixedPatternStep
+        step.matches("abc")
+        !step.matches("ABC")
+        !step.matches("other")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.java
deleted file mode 100644
index 89d2878..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.file.pattern;
-
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-public class PatternStepFactoryTest {
-    @Test public void testGreedy() {
-        PatternStep step = PatternStepFactory.getStep("**", true, true);
-        assertTrue(step.isGreedy());
-        assertTrue(step.matches("anything", true));
-        assertTrue(step.matches("anything", false));
-    }
-
-    @Test public void testNormal() {
-        PatternStep step = PatternStepFactory.getStep("*.jsp", true, true);
-        assertFalse(step.isGreedy());
-        assertTrue(step.matches("fred.jsp", true));
-
-        // check case sensitivity param
-        assertFalse(step.matches("fred.JSP", true));
-        step = PatternStepFactory.getStep("*.jsp", true, false);
-        assertTrue(step.matches("fred.JSP", true));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java
index fffdd9f..db7a26b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java
@@ -16,21 +16,18 @@
 package org.gradle.api.internal.file.pattern;
 
 import org.junit.Test;
+
 import static org.junit.Assert.*;
 
-/**
- * @author Steve Appling
- */
 public class RegExpPatternStepTest {
 
     private void testPatternEscape(String expected, String pattern) {
         assertEquals(expected, RegExpPatternStep.getRegExPattern(pattern));
     }
 
-
     @Test public void testGetRegExpPattern() {
         testPatternEscape("literal", "literal");
-        testPatternEscape("dotq.?", "dotq?");
+        testPatternEscape("dotq.", "dotq?");
         testPatternEscape("star.*stuff", "star*stuff");
         testPatternEscape("\\\\\\[\\]\\^\\-\\&\\.\\{\\}\\(\\)\\$\\+\\|\\<\\=\\!", "\\[]^-&.{}()$+|<=!");
         testPatternEscape("\\$\\&time", "$&time");
@@ -39,38 +36,57 @@ public class RegExpPatternStepTest {
     @Test public void testEscapeSet() {
         String testChars = "`~!@#$%^&*()-_=+[]{}\\|;:'\"<>,/";
         RegExpPatternStep step = new RegExpPatternStep(testChars, true);
-        assertTrue(step.matches(testChars, true));
+        assertTrue(step.matches(testChars));
     }
 
-    @Test public void testMatches() {
+    @Test public void testLiteralMatches() {
         RegExpPatternStep step = new RegExpPatternStep("literal", true);
-        assertTrue(step.matches("literal", true));
-        assertFalse(step.matches("Literal", true));
-        assertFalse(step.matches("literally", true));
-        assertFalse(step.matches("aliteral", true));
+        assertTrue(step.matches("literal"));
+        assertFalse(step.matches("Literal"));
+        assertFalse(step.matches("literally"));
+        assertFalse(step.matches("aliteral"));
+    }
 
-        step = new RegExpPatternStep("a?c", true);
-        assertTrue(step.matches("abc", true));
-        assertFalse(step.matches("abcd", true));
-        assertTrue(step.matches("a$c", true));
+    @Test public void testSingleCharWildcard() {
+        RegExpPatternStep step = new RegExpPatternStep("a?c", true);
+        assertTrue(step.matches("abc"));
+        assertTrue(step.matches("a$c"));
+        assertTrue(step.matches("a?c"));
 
-        step = new RegExpPatternStep("a*c", true);
-        assertTrue(step.matches("abc", true));
-        assertTrue(step.matches("abrac", true));
-        assertFalse(step.matches("abcd", true));
+        assertFalse(step.matches("ac"));
+        assertFalse(step.matches("abcd"));
+        assertFalse(step.matches("abd"));
+        assertFalse(step.matches("a"));
+    }
+
+    @Test public void testMultiCharWildcard() {
+        RegExpPatternStep step = new RegExpPatternStep("a*c", true);
+        assertTrue(step.matches("abc"));
+        assertTrue(step.matches("abrac"));
+        assertFalse(step.matches("abcd"));
+        assertFalse(step.matches("ab"));
+        assertFalse(step.matches("a"));
 
         step = new RegExpPatternStep("*", true);
-        assertTrue(step.matches("asd;flkj", true));
+        assertTrue(step.matches("asd;flkj"));
+        assertTrue(step.matches(""));
     }
 
-
     @Test public void testCase() {
         RegExpPatternStep step = new RegExpPatternStep("MiXeD", true);
-        assertTrue(step.matches("MiXeD", true));
-        assertFalse(step.matches("mixed", true));
+        assertTrue(step.matches("MiXeD"));
+        assertFalse(step.matches("mixed"));
 
         step = new RegExpPatternStep("MiXeD", false);
-        assertTrue(step.matches("MiXeD", true));
-        assertTrue(step.matches("mixed", true));
+        assertTrue(step.matches("MiXeD"));
+        assertTrue(step.matches("mixed"));
+
+        step = new RegExpPatternStep("MiXeD?", true);
+        assertTrue(step.matches("MiXeD1"));
+        assertFalse(step.matches("mixed1"));
+
+        step = new RegExpPatternStep("MiXeD?", false);
+        assertTrue(step.matches("MiXeD1"));
+        assertTrue(step.matches("mixed1"));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/WildcardPrefixPatternStepTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/WildcardPrefixPatternStepTest.groovy
new file mode 100644
index 0000000..c2b8b81
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/pattern/WildcardPrefixPatternStepTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.pattern
+
+import spock.lang.Specification
+
+class WildcardPrefixPatternStepTest extends Specification {
+    def "matches name case sensitive"() {
+        def step = new WildcardPrefixPatternStep(".java", true)
+
+        expect:
+        step.matches("thing.java")
+        step.matches(".java")
+        !step.matches("thing.JAVA")
+        !step.matches("thing.jav")
+        !step.matches("thing.c")
+        !step.matches("")
+        !step.matches("something else")
+    }
+
+    def "matches name case insensitive"() {
+        def step = new WildcardPrefixPatternStep(".java", false)
+
+        expect:
+        step.matches("thing.java")
+        step.matches(".java")
+        step.matches("thing.JAVA")
+        !step.matches("thing.jav")
+        !step.matches("thing.c")
+        !step.matches("")
+        !step.matches("something else")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderCacheTest.groovy
new file mode 100644
index 0000000..5df9838
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderCacheTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization
+
+import com.google.common.cache.CacheBuilder
+import org.gradle.internal.classloader.FilteringClassLoader
+import org.gradle.internal.classpath.ClassPath
+import org.gradle.internal.classpath.DefaultClassPath
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultClassLoaderCacheTest extends Specification {
+
+    def backingCache = CacheBuilder.newBuilder().build()
+    def cache = new DefaultClassLoaderCache(backingCache)
+
+    @Rule TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+
+    TestFile file(String path) {
+        testDirectoryProvider.testDirectory.file(path)
+    }
+
+    ClassPath classPath(String... paths) {
+        new DefaultClassPath(paths.collect { file(it) } as Iterable<File>)
+    }
+
+    ClassLoader classLoader(ClassPath classPath) {
+        new URLClassLoader(classPath.asURLArray)
+    }
+
+    def "class loaders are reused"() {
+        expect:
+        def root = classLoader(classPath("root"))
+        cache.get(root, classPath("c1"), null).is cache.get(root, classPath("c1"), null)
+    }
+
+    def "parents are respected"() {
+        expect:
+        def root1 = classLoader(classPath("root1"))
+        def root2 = classLoader(classPath("root2"))
+        cache.get(root1, classPath("c1"), null) != cache.get(root2, classPath("c1"), null)
+    }
+
+    def "filters are respected"() {
+        expect:
+        def root = classLoader(classPath("root"))
+        def f1 = new FilteringClassLoader.Spec(["1"], [], [], [], [], [])
+        def f2 = new FilteringClassLoader.Spec(["2"], [], [], [], [], [])
+        cache.get(root, classPath("c1"), f1).is(cache.get(root, classPath("c1"), f1))
+        !cache.get(root, classPath("c1"), f1).is(cache.get(root, classPath("c1"), f2))
+        backingCache.size() == 3
+    }
+
+    def "non filtered classloaders are reused"() {
+        expect:
+        def root = classLoader(classPath("root"))
+        def f1 = new FilteringClassLoader.Spec(["1"], [], [], [], [], [])
+        cache.get(root, classPath("c1"), f1)
+        backingCache.size() == 2
+        cache.get(root, classPath("c1"), null)
+        backingCache.size() == 2
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderScopeTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderScopeTest.groovy
new file mode 100644
index 0000000..2ec75a8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultClassLoaderScopeTest.groovy
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.initialization
+
+import com.google.common.cache.Cache
+import com.google.common.cache.CacheBuilder
+import org.gradle.internal.classloader.CachingClassLoader
+import org.gradle.internal.classpath.ClassPath
+import org.gradle.internal.classpath.DefaultClassPath
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultClassLoaderScopeTest extends Specification {
+
+    ClassLoader parentClassLoader
+    ClassLoader baseClassLoader
+
+    ClassLoaderScope parent
+    ClassLoaderScope base
+    ClassLoaderScope scope
+
+    Cache<DefaultClassLoaderCache.Key, ClassLoader> backingCache = CacheBuilder.newBuilder().build();
+    ClassLoaderCache classLoaderCache = new DefaultClassLoaderCache(backingCache);
+
+    @Rule TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+
+    def setup() {
+        parentClassLoader = new URLClassLoader(classPath("root").asURLArray)
+        baseClassLoader = new URLClassLoader(classPath("base").asURLArray)
+        parent = new RootClassLoaderScope(parentClassLoader, classLoaderCache)
+        base = new RootClassLoaderScope(baseClassLoader, classLoaderCache)
+        scope = new DefaultClassLoaderScope(parent, base, classLoaderCache)
+    }
+
+    TestFile file(String path) {
+        testDirectoryProvider.testDirectory.file(path)
+    }
+
+    ClassPath classPath(String... paths) {
+        new DefaultClassPath(paths.collect { file(it).createDir() } as Iterable<File>)
+    }
+
+    def "locked scope with no modifications exports parent"() {
+        when:
+        scope.lock()
+
+        then:
+        scope.childClassLoader.is parentClassLoader
+        scope.scopeClassLoader.is parentClassLoader
+    }
+
+    def "locked scope with only exports only exports exports"() {
+        when:
+        file("export/foo") << "bar"
+        def exportClassLoader = scope.export(classPath("export"))
+        scope.lock()
+
+        then:
+        exportClassLoader.getResource("foo").text == "bar"
+        scope.childClassLoader.is scope.scopeClassLoader
+        scope.scopeClassLoader.getResource("foo").text == "bar"
+    }
+
+    def "locked scope with only local exports parent loader to children"() {
+        when:
+        file("local/foo") << "bar"
+        def localClassLoader = scope.addLocal(classPath("local"))
+        scope.lock()
+
+        then:
+        localClassLoader.getResource("foo").text == "bar"
+        scope.childClassLoader.is parentClassLoader
+        scope.scopeClassLoader.getResource("foo").text == "bar"
+    }
+
+    def "locked scope with local and exports exports custom classloader to children"() {
+        when:
+        file("local/local") << "bar"
+        file("export/export") << "bar"
+        def localClassLoader = scope.addLocal(classPath("local"))
+        def exportClassLoader = scope.export(classPath("export"))
+        scope.lock()
+
+        then:
+        localClassLoader.getResource("local").text == "bar"
+        exportClassLoader.getResource("export").text == "bar"
+        scope.childClassLoader.getResource("export").text == "bar"
+        scope.childClassLoader.getResource("local") == null
+        scope.scopeClassLoader instanceof CachingClassLoader
+        scope.scopeClassLoader.getResource("export").text == "bar"
+        scope.scopeClassLoader.getResource("local").text == "bar"
+    }
+
+    def "requesting loaders before locking creates pessimistic setup"() {
+        given:
+        scope.scopeClassLoader // trigger
+
+        when:
+        file("local/local") << "bar"
+        file("export/export") << "bar"
+        def localClassLoader = scope.addLocal(classPath("local"))
+        def exportClassLoader = scope.export(classPath("export"))
+
+        then:
+        localClassLoader.getResource("local").text == "bar"
+        exportClassLoader.getResource("export").text == "bar"
+        scope.childClassLoader.getResource("export").text == "bar"
+        scope.childClassLoader.getResource("local") == null
+        scope.scopeClassLoader instanceof CachingClassLoader
+        scope.scopeClassLoader.getResource("export").text == "bar"
+        scope.scopeClassLoader.getResource("local").text == "bar"
+    }
+
+    def "cannot modify after locking"() {
+        given:
+        scope.lock()
+
+        when:
+        scope.addLocal(classPath("local"))
+
+        then:
+        thrown IllegalStateException
+
+        when:
+        scope.export(classPath("local"))
+
+        then:
+        thrown IllegalStateException
+    }
+
+    def "child scopes can access exported but not local"() {
+        when:
+        file("local/local") << "bar"
+        file("export/export") << "bar"
+        scope.addLocal(classPath("local"))
+        scope.export(classPath("export"))
+        scope.lock()
+        def child = scope.createChild()
+        child.lock()
+
+        then:
+        child.scopeClassLoader.getResource("export").text == "bar"
+        child.scopeClassLoader.getResource("local") == null
+        child.childClassLoader.getResource("export").text == "bar"
+        child.childClassLoader.getResource("local") == null
+    }
+
+    def "sibling scopes can not access exported or local"() {
+        when:
+        file("local/local") << "bar"
+        file("export/export") << "bar"
+        scope.addLocal(classPath("local"))
+        scope.export(classPath("export"))
+        scope.lock()
+        def sibling = scope.createSibling()
+        sibling.lock()
+
+        then:
+        sibling.scopeClassLoader.getResource("export") == null
+        sibling.scopeClassLoader.getResource("local") == null
+        sibling.childClassLoader.getResource("export") == null
+        sibling.childClassLoader.getResource("local") == null
+    }
+
+    def "rebased children local additions can access parent exported"() {
+        when:
+        file("local/local") << "bar"
+        file("export/export") << "bar"
+        scope.addLocal(classPath("local"))
+        scope.export(classPath("export"))
+        scope.lock()
+        def child = scope.createRebasedChild()
+        file("childLocal/childLocal") << "bar"
+        def childLocalClassLoader = child.addLocal(classPath("childLocal"))
+        child.lock()
+
+        then:
+        childLocalClassLoader.getResource("local") == null
+        childLocalClassLoader.getResource("export").text == "bar"
+        childLocalClassLoader.getResource("childLocal").text == "bar"
+    }
+
+    def "class loaders are reused"() {
+        expect:
+        backingCache.size() == 0
+
+        when:
+        file("c1/c1") << "bar"
+        file("c2/c2") << "bar"
+        def c1ExportLoader = scope.export(classPath("c1"))
+        def c2Local = scope.addLocal(classPath("c2"))
+        scope.lock()
+
+        def sibling = scope.createSibling()
+        def child = scope.createChild()
+
+        then:
+        backingCache.size() == 2
+        sibling.export(classPath("c1")).is c1ExportLoader
+        backingCache.size() == 2
+
+        !child.export(classPath("c1")).is(c1ExportLoader) // classpath is the same, but parent is different
+        backingCache.size() == 3
+
+        sibling.addLocal(classPath("c2")).is c2Local
+        child.addLocal(classPath("c2")).is c2Local
+        backingCache.size() == 3
+    }
+
+    def "pessimistic structure has parent visibility"() {
+        when:
+        file("root/root") << "foo"
+
+        then:
+        scope.scopeClassLoader.getResource("root").text == "foo"
+    }
+
+    def "optimised structure has parent visibility"() {
+        when:
+        file("root/root") << "foo"
+
+        then:
+        scope.lock().scopeClassLoader.getResource("root").text == "foo"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
index 36e91a4..ee7748a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
@@ -19,48 +19,50 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler
 import org.gradle.api.internal.artifacts.DependencyManagementServices
 import org.gradle.api.internal.artifacts.DependencyResolutionServices
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.groovy.scripts.ScriptSource
-
 import spock.lang.Specification
-import org.gradle.util.MutableURLClassLoader
 
 class DefaultScriptHandlerFactoryTest extends Specification {
     private final DependencyMetaDataProvider metaDataProvider = Mock()
-    private final ClassLoader parentClassLoader = new ClassLoader() {}
+    private final ClassLoaderScope parentScope = Stub() {
+        getScopeClassLoader() >> Stub(ClassLoader)
+    }
     private final RepositoryHandler repositoryHandler = Mock()
     private final ConfigurationContainerInternal configurationContainer = Mock()
     private final FileResolver fileResolver = Mock()
     private final DependencyManagementServices dependencyManagementServices = Mock()
-    private final DefaultScriptHandlerFactory factory = new DefaultScriptHandlerFactory(dependencyManagementServices, fileResolver, metaDataProvider)
+    private final DefaultScriptHandlerFactory scriptHandlerFactory = new DefaultScriptHandlerFactory(dependencyManagementServices, fileResolver, metaDataProvider)
 
     def createsScriptHandler() {
         ScriptSource script = scriptSource()
         expectConfigContainerCreated()
 
         when:
-        def handler = factory.create(script, parentClassLoader)
+        def handler = scriptHandlerFactory.create(script, parentScope)
 
         then:
         handler instanceof DefaultScriptHandler
-        handler.classLoader instanceof MutableURLClassLoader
-        handler.classLoader.parent == parentClassLoader
     }
 
-    def reusesClassLoaderForGivenScriptClassAndParentClassLoader() {
-        ScriptSource script = scriptSource('script')
-        ScriptSource other = scriptSource('script')
-        expectConfigContainerCreated()
-
-        when:
-        def handler1 = factory.create(script, parentClassLoader)
-        def handler2 = factory.create(other, parentClassLoader)
-
-        then:
-        handler1.classLoader == handler2.classLoader
-        handler2 instanceof NoClassLoaderUpdateScriptHandler
-    }
+    // TODO - reenable LD 6/2/2014
+//    def reusesClassLoaderForGivenScriptClassAndParentScope() {
+//        ScriptSource script = scriptSource('script')
+//        ScriptSource other = scriptSource('script')
+//        expectConfigContainerCreated()
+//
+//        when:
+//        def handler1 = scriptHandlerFactory.create(script, parentScope)
+//        handler1.updateClassPath()
+//        def handler2 = scriptHandlerFactory.create(other, parentScope)
+//
+//        then:
+//        handler2 instanceof NoClassLoaderUpdateScriptHandler
+//        handler1.baseCompilationClassLoader == handler2.baseCompilationClassLoader
+//        handler1.scopeClassLoader == handler2.scopeClassLoader
+//    }
 
     def doesNotReuseClassLoaderForDifferentScriptClass() {
         ScriptSource script = scriptSource('script')
@@ -68,11 +70,10 @@ class DefaultScriptHandlerFactoryTest extends Specification {
         expectConfigContainerCreated()
 
         when:
-        def handler1 = factory.create(script, parentClassLoader)
-        def handler2 = factory.create(other, parentClassLoader)
+        scriptHandlerFactory.create(script, parentScope)
+        def handler2 = scriptHandlerFactory.create(other, parentScope)
 
         then:
-        handler1.classLoader != handler2.classLoader
         handler2 instanceof DefaultScriptHandler
     }
 
@@ -81,6 +82,7 @@ class DefaultScriptHandlerFactoryTest extends Specification {
         _ * dependencyManagementServices.create(fileResolver, metaDataProvider, _, _) >> dependencyResolutionServices
         _ * dependencyResolutionServices.resolveRepositoryHandler >> repositoryHandler
         _ * dependencyResolutionServices.configurationContainer >> configurationContainer
+        _ * configurationContainer.create(_) >> Stub(ConfigurationInternal)
     }
 
     private def scriptSource(String className = 'script') {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
index e295350..5334414 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
@@ -19,87 +19,57 @@ import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.api.artifacts.dsl.RepositoryHandler
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.WrapUtil
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
 import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.util.MutableURLClassLoader
 import org.gradle.util.ConfigureUtil
-
- at RunWith(JMock)
-public class DefaultScriptHandlerTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class)
-    private final DependencyHandler dependencyHandler = context.mock(DependencyHandler.class)
-    private final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class)
-    private final Configuration configuration = context.mock(Configuration.class)
-    private final ScriptSource scriptSource = context.mock(ScriptSource.class)
-    private final MutableURLClassLoader classLoader = context.mock(MutableURLClassLoader.class)
-
-    @Test void addsClasspathConfiguration() {
-        context.checking {
-            one(configurationContainer).add('classpath')
-        }
-
-        new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoader)
+import spock.lang.Specification
+
+class DefaultScriptHandlerTest extends Specification {
+    def repositoryHandler = Mock(RepositoryHandler)
+    def dependencyHandler = Mock(DependencyHandler)
+    def configurationContainer = Mock(ConfigurationContainer)
+    def configuration = Stub(Configuration)
+    def scriptSource = Stub(ScriptSource)
+    def baseClassLoader = new ClassLoader() {}
+    def classLoaderScope = Stub(ClassLoaderScope) {
+        getScopeClassLoader() >> baseClassLoader
     }
 
-    @Test void createsAClassLoaderAndAddsContentsOfClassPathConfiguration() {
-        DefaultScriptHandler handler = handler()
-
-        ClassLoader classLoader = handler.classLoader
-        assertThat(classLoader, sameInstance(this.classLoader))
-
-        File file1 = new File('a')
-        File file2 = new File('b')
-        context.checking {
-            one(configuration).getFiles()
-            will(returnValue(WrapUtil.toSet(file1, file2)))
-            one(classLoader).addURL(file1.toURI().toURL())
-            one(classLoader).addURL(file2.toURI().toURL())
-        }
+    def "adds classpath configuration"() {
+        when:
+        new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, new ScriptHandlerClassLoaderFactory(scriptSource, classLoaderScope))
 
-        handler.updateClassPath()
+        then:
+        1 * configurationContainer.create('classpath')
     }
 
-    @Test void canConfigureRepositories() {
-        DefaultScriptHandler handler = handler()
-
+    def "can configure repositories"() {
+        def handler = handler()
         def configure = {
             mavenCentral()
         }
 
-        context.checking {
-            one(repositoryHandler).configure(configure)
-            will { ConfigureUtil.configure(configure, repositoryHandler, false) }
-            one(repositoryHandler).mavenCentral()
-        }
-
+        when:
         handler.repositories(configure)
-    }
 
-    @Test void canConfigureDependencies() {
-        DefaultScriptHandler handler = handler()
+        then:
+        1 * repositoryHandler.configure(configure) >> { ConfigureUtil.configure(configure, repositoryHandler, false) }
+        1 * repositoryHandler.mavenCentral()
+    }
 
-        context.checking {
-            one(dependencyHandler).add('config', 'dep')
-        }
+    def "can configure dependencies"() {
+        def handler = handler()
 
+        when:
         handler.dependencies {
             add('config', 'dep')
         }
+
+        then:
+        1 * dependencyHandler.add('config', 'dep')
     }
 
     private DefaultScriptHandler handler() {
-        context.checking {
-            one(configurationContainer).add('classpath')
-            will(returnValue(configuration))
-        }
-        return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoader)
+        1 * configurationContainer.create('classpath') >> configuration
+        return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, new ScriptHandlerClassLoaderFactory(scriptSource, classLoaderScope))
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParserTest.groovy
deleted file mode 100644
index c60fee4..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParserTest.groovy
+++ /dev/null
@@ -1,47 +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.notations.parsers
-
-import spock.lang.Specification
-
-class CharSequenceNotationParserTest extends Specification {
-    def parser = new CharSequenceNotationParser()
-
-    def "handles Strings"() {
-        expect:
-        converts("abc", "abc")
-    }
-
-    def "handles GStrings"() {
-        expect:
-        def foo = "abc"
-        converts("$foo", "abc")
-    }
-
-    def "handles StringBuilders"() {
-        def builder = new StringBuilder()
-        builder.append("abc")
-
-        expect:
-        converts(builder, "abc")
-    }
-
-    void converts(from, to) {
-        assert parser.parseNotation(from).getClass() == String
-        assert parser.parseNotation(from) == to
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserTest.groovy
deleted file mode 100644
index fbd7659..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserTest.groovy
+++ /dev/null
@@ -1,40 +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.notations.parsers
-
-import org.gradle.api.internal.notations.api.UnsupportedNotationException
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 10/12/12
- */
-class ClosureToSpecNotationParserTest 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/notations/parsers/ErrorHandlingNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
deleted file mode 100644
index 6185901..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.notations.parsers
-
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.internal.notations.api.NotationParser
-import org.gradle.api.internal.notations.api.UnsupportedNotationException
-import spock.lang.Specification
-
-import static org.gradle.util.TextUtil.toPlatformLineSeparators
-
-class ErrorHandlingNotationParserTest extends Specification {
-    def NotationParser<String> target = Mock()
-    def parser = new ErrorHandlingNotationParser<String>("String", "<broken>", target)
-
-    def "reports unable to parse null"() {
-        when:
-        parser.parseNotation(null)
-
-        then:
-        InvalidUserDataException e = thrown()
-        e.message == toPlatformLineSeparators('''Cannot convert a null value to an object of type String.
-The following types/formats are supported:
-  - format 1
-  - format 2
-<broken>''')
-
-        1 * target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
-        0 * target._  //no parsing
-    }
-
-    def "reports unable to parse non-null"() {
-        given:
-        target.parseNotation("bad") >> { throw new UnsupportedNotationException("broken-part") }
-        target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
-
-        when:
-        parser.parseNotation("bad")
-
-        then:
-        InvalidUserDataException e = thrown()
-        e.message == toPlatformLineSeparators('''Cannot convert the provided notation to an object of type String: broken-part.
-The following types/formats are supported:
-  - format 1
-  - format 2
-<broken>''')
-
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/MapNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/MapNotationParserTest.groovy
deleted file mode 100644
index 98a12f0..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/MapNotationParserTest.groovy
+++ /dev/null
@@ -1,110 +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.notations.parsers
-
-import spock.lang.Specification
-import org.gradle.api.internal.notations.api.UnsupportedNotationException
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.tasks.Optional
-
-class MapNotationParserTest extends Specification {
-    final DummyParser parser = new DummyParser()
-    
-    def "parses map with required keys"() {
-        expect:
-        def object = parser.parseNotation([name: 'name', version: 'version'])
-        object.key1 == 'name'
-        object.key2 == 'version'
-        object.prop1 == null
-    }
-
-    def "parses map with required and optional keys"() {
-        expect:
-        def object = parser.parseNotation([name: 'name', version: 'version', optional: '1.2'])
-        object.key1 == 'name'
-        object.key2 == 'version'
-        object.optional == '1.2'
-        object.prop1 == null
-    }
-
-    def "configures properties of converted object using extra properties"() {
-        expect:
-        def object = parser.parseNotation([name: 'name', version: 'version', prop1: 'prop1', optional: '1.2'])
-        object.key1 == 'name'
-        object.key2 == 'version'
-        object.prop1 == 'prop1'
-    }
-
-    def "does not mutate original map"() {
-        def source = [name: 'name', version: 'version', prop1: 'prop1', optional: '1.2']
-        def copy = new HashMap<String, Object>(source)
-        
-        when:
-        parser.parseNotation(source)
-        
-        then:
-        source == copy
-    }
-
-    def "does not parse map with missing keys"() {
-        when:
-        parser.parseNotation([name: 'name'])
-
-        then:
-        InvalidUserDataException e = thrown()
-        e.message == 'Required keys [version] are missing from map {name=name}.'
-    }
-
-    def "treats empty strings and null values as missing"() {
-        when:
-        parser.parseNotation([name: null, version: ''])
-
-        then:
-        InvalidUserDataException e = thrown()
-        e.message.startsWith 'Required keys [name, version] are missing from map '
-    }
-
-    def "does not parse map with unknown extra properties"() {
-        when:
-        parser.parseNotation([name: 'name', version: 1.2, unknown: 'unknown'])
-
-        then:
-        MissingFieldException e = thrown()
-    }
-
-    def "does not parse notation that is not a map"() {
-        when:
-        parser.parseNotation('string')
-
-        then:
-        thrown(UnsupportedNotationException)
-    }
-    
-    static class DummyParser extends MapNotationParser<TargetObject> {
-        protected TargetObject parseMap(@MapKey('name') String name,
-                                        @MapKey('version') String version,
-                                        @Optional @MapKey('optional') optional) {
-            return new TargetObject(key1:  name, key2:  version, optional:  optional)
-        }
-    }
-
-    static class TargetObject {
-        String key1;
-        String key2;
-        String optional;
-        String prop1;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParserTest.groovy
deleted file mode 100644
index 8f70474..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParserTest.groovy
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers
-
-import org.gradle.api.InvalidUserDataException
-import spock.lang.Specification
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS
-import static java.util.concurrent.TimeUnit.NANOSECONDS
-
-/**
- * by Szczepan Faber, created at: 2/12/13
- */
-class TimeUnitsParserTest extends Specification {
-
-    def parser = new TimeUnitsParser()
-
-    def "parses time units"() {
-        expect:
-        def unit = parser.parseNotation(input, value)
-        unit.value == normalizedValue
-        unit.timeUnit == parsed
-
-        where:
-        value           |input          |parsed         | normalizedValue
-        10              |'nanoseconds'  |NANOSECONDS    |10
-        20              |'mILLISECONds' |MILLISECONDS   |20
-        1               |'days'         |MILLISECONDS   |1 * 24 * 60 * 60 * 1000
-        2               |'hours'        |MILLISECONDS   |2 * 60 * 60 * 1000
-        5               |'MINUTES'      |MILLISECONDS   |5 * 60 * 1000
-    }
-
-    def "fails gracefully for invalid input"() {
-        when:
-        parser.parseNotation("foobar", 133)
-        then:
-        def ex = thrown(InvalidUserDataException)
-        ex.message.contains("foobar")
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParserTest.groovy
deleted file mode 100644
index 85881bd..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParserTest.groovy
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers;
-
-
-import org.gradle.api.internal.notations.api.UnsupportedNotationException
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 11/10/11
- */
-public class TypedNotationParserTest extends Specification {
-
-    def parser = new DummyParser();
-
-    def "parses object of source type"(){
-        expect:
-        parser.parseNotation("100") == 100
-    }
-
-    def "throws meaningful exception on parse attempt"(){
-        when:
-        parser.parseNotation(new Object())
-
-        then:
-        thrown(UnsupportedNotationException)
-    }
-
-    class DummyParser extends TypedNotationParser<String, Integer> {
-
-        DummyParser() {
-            super(String.class)
-        }
-
-        Integer parseType(String notation) {
-            return Integer.valueOf(notation);
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
index 99c255a..87d0f77 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
@@ -16,19 +16,17 @@
 
 package org.gradle.api.internal.plugins
 
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.plugins.Convention
 import org.gradle.api.plugins.TestPluginConvention1
 import org.gradle.api.plugins.TestPluginConvention2
+import org.gradle.internal.reflect.Instantiator
 import org.junit.Before
 import org.junit.Test
+
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class DefaultConventionTest {
     Convention convention
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
index 45a8f30..c2e9129 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
@@ -15,88 +15,83 @@
  */
 package org.gradle.api.internal.plugins
 
-import static org.hamcrest.Matchers.*
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.runner.RunWith
-import org.junit.Test
+import org.gradle.api.initialization.dsl.ScriptHandler
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.initialization.ScriptHandlerFactory
+
 import org.gradle.configuration.ScriptPlugin
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.DefaultScript
+import org.junit.Test
+import spock.lang.Specification
 
- at RunWith(JMock.class)
-public class DefaultObjectConfigurationActionTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Object target = new Object()
-    private final URI file = new URI('script:something')
-    private final FileResolver resolver = context.mock(FileResolver.class)
-    private final ScriptPluginFactory factory = context.mock(ScriptPluginFactory.class)
-    private final ScriptPlugin configurer = context.mock(ScriptPlugin.class)
-    private final DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(resolver, factory, target)
+class DefaultObjectConfigurationActionTest extends Specification {
+    Object target = new Object()
+    URI file = new URI('script:something')
 
-    @Test
-    public void doesNothingWhenNothingSpecified() {
+    def resolver = Mock(FileResolver)
+    def scriptPluginFactory = Mock(ScriptPluginFactory)
+    def scriptHandlerFactory = Mock(ScriptHandlerFactory)
+    def scriptHandler = Mock(ScriptHandler)
+    def scriptCompileScope = Mock(ClassLoaderScope)
+    def configurer = Mock(ScriptPlugin)
+
+    DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(resolver, scriptPluginFactory, scriptHandlerFactory, scriptCompileScope, target)
+
+    void doesNothingWhenNothingSpecified() {
+        expect:
         action.execute()
     }
 
     @Test
     public void appliesScriptsToDefaultTargetObject() {
-        context.checking {
-            one(resolver).resolveUri('script')
-            will(returnValue(file))
-
-            one(factory).create(withParam(notNullValue()))
-            will(returnValue(configurer))
-
-            one(configurer).apply(target)
-        }
+        given:
+        1 * resolver.resolveUri('script') >> file
+        1 * scriptHandlerFactory.create(_, scriptCompileScope) >> scriptHandler
+        1 * scriptPluginFactory.create(_, scriptHandler, scriptCompileScope, "buildscript", DefaultScript) >> configurer
 
+        when:
         action.from('script')
+
+        then:
         action.execute()
     }
 
     @Test
     public void appliesScriptsToTargetObjects() {
+        when:
         Object target1 = new Object()
         Object target2 = new Object()
+        1 * resolver.resolveUri('script') >> file
+        1 * scriptHandlerFactory.create(_, scriptCompileScope) >> scriptHandler
+        1 * scriptPluginFactory.create(_, scriptHandler, scriptCompileScope, "buildscript", DefaultScript) >> configurer
+        1 * configurer.apply(target1)
+        1 * configurer.apply(target2)
 
-        context.checking {
-            one(resolver).resolveUri('script')
-            will(returnValue(file))
-
-            one(factory).create(withParam(notNullValue()))
-            will(returnValue(configurer))
-
-            one(configurer).apply(target1)
-            one(configurer).apply(target2)
-        }
-
+        then:
         action.from('script')
         action.to(target1)
         action.to(target2)
         action.execute()
     }
-    
+
     @Test
     public void flattensCollections() {
+        when:
         Object target1 = new Object()
         Object target2 = new Object()
+        1 * resolver.resolveUri('script') >> file
+        1 * scriptHandlerFactory.create(_, scriptCompileScope) >> scriptHandler
+        1 * scriptPluginFactory.create(_, scriptHandler, scriptCompileScope, "buildscript", DefaultScript) >> configurer
+        1 * configurer.apply(target1)
+        1 * configurer.apply(target2)
 
-        context.checking {
-            one(resolver).resolveUri('script')
-            will(returnValue(file))
-
-            one(factory).create(withParam(notNullValue()))
-            will(returnValue(configurer))
-
-            one(configurer).apply(target1)
-            one(configurer).apply(target2)
-        }
-
+        then:
         action.from('script')
         action.to([[target1], target2])
         action.execute()
     }
+
 }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginContainerTest.java
new file mode 100644
index 0000000..5f2e756
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginContainerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Plugin;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.internal.project.TestPlugin1;
+import org.gradle.api.internal.project.TestPlugin2;
+import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.util.TestUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+public class DefaultPluginContainerTest {
+    protected String pluginId = "somePluginId";
+    protected JUnit4Mockery context = new JUnit4Mockery();
+    private final DefaultProject project = TestUtil.createRootProject();
+
+    private PluginRegistry pluginRegistryStub = context.mock(PluginRegistry.class);
+    private DefaultPluginContainer container = new DefaultPluginContainer(pluginRegistryStub, project);
+
+    private TestPlugin1 pluginWithIdMock = new TestPlugin1();
+    private TestPlugin2 pluginWithoutIdMock = new TestPlugin2();
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(pluginRegistryStub).getTypeForId(pluginId); will(returnValue(TestPlugin1.class));
+            allowing(pluginRegistryStub).loadPlugin(TestPlugin1.class); will(returnValue(pluginWithIdMock));
+            allowing(pluginRegistryStub).loadPlugin(TestPlugin2.class); will(returnValue(pluginWithoutIdMock));
+        }});
+    }
+
+    @Test
+    public void usePluginById() {
+        Plugin addedPlugin = container.apply(pluginId);
+        assertThat(pluginWithIdMock, sameInstance(addedPlugin));
+        assertThat(container.apply(pluginId), sameInstance(addedPlugin));
+
+        assertThat(container.findPlugin(addedPlugin.getClass()), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(pluginId), sameInstance(addedPlugin));
+    }
+
+    @Test
+    public void usePluginWithIdByType() {
+        Class<? extends Plugin> type = pluginWithIdMock.getClass();
+
+        Plugin addedPlugin = container.apply(type);
+        assertThat(pluginWithIdMock, sameInstance(addedPlugin));
+        assertThat(container.apply(type), sameInstance(addedPlugin));
+        assertThat(container.apply(pluginId), sameInstance(addedPlugin));
+
+        assertThat(container.findPlugin(type), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(pluginId), sameInstance(addedPlugin));
+    }
+
+    @Test
+    public void usePluginWithoutId() {
+        Class<? extends Plugin> type = pluginWithoutIdMock.getClass();
+        Plugin addedPlugin = container.apply(type);
+        assertThat(pluginWithoutIdMock, sameInstance(addedPlugin));
+        assertThat(container.apply(type), sameInstance(addedPlugin));
+
+        assertThat(container.findPlugin(type), sameInstance(addedPlugin));
+    }
+
+    @Test
+    public void hasAndFindForPluginWithId() {
+        container.apply(pluginId);
+        assertThat(container.hasPlugin(pluginId), equalTo(true));
+        assertThat(container.hasPlugin(pluginWithIdMock.getClass()), equalTo(true));
+        assertThat(container.findPlugin(pluginId), sameInstance((Plugin) pluginWithIdMock));
+        assertThat(container.findPlugin(pluginWithIdMock.getClass()), sameInstance((Plugin) pluginWithIdMock));
+    }
+
+    @Test
+    public void hasAndFindForUnknownPluginId() {
+        context.checking(new Expectations() {{
+            allowing(pluginRegistryStub).getTypeForId("unknown"); will(throwException(new UnknownPluginException("unknown")));
+        }});
+
+        assertThat(container.hasPlugin("unknown"), equalTo(false));
+        assertThat(container.findPlugin("unknown"), nullValue());
+    }
+
+    @Test
+    public void hasAndFindForPluginWithoutId() {
+        Plugin plugin = pluginWithoutIdMock;
+        Class<? extends Plugin> pluginType = plugin.getClass();
+        container.apply(pluginType);
+        assertThat(container.hasPlugin(pluginType), equalTo(true));
+        assertThat(container.findPlugin(pluginType), sameInstance(plugin));
+    }
+
+    @Test
+    public void hasAndFindPluginByTypeWithUnknownPlugin() {
+        assertThat(container.hasPlugin(TestPlugin2.class), equalTo(false));
+        assertThat(container.findPlugin(TestPlugin2.class), nullValue());
+    }
+
+    @Test(expected = UnknownPluginException.class)
+    public void getNonUsedPluginById() {
+        container.getPlugin(pluginId);
+    }
+
+    @Test(expected = UnknownPluginException.class)
+    public void getNonUsedPluginByType() {
+        container.getPlugin(TestPlugin1.class);
+    }
+}
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
index 9d5f7f4..0117655 100644
--- 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
@@ -18,6 +18,7 @@ package org.gradle.api.internal.plugins
 
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Plugin
+import org.gradle.api.internal.initialization.ClassLoaderScope
 import org.gradle.api.internal.project.TestPlugin1
 import org.gradle.api.internal.project.TestPlugin2
 import org.gradle.api.plugins.PluginInstantiationException
@@ -146,12 +147,12 @@ class DefaultPluginRegistryTest extends Specification {
     }
 
     public void childUsesItsOwnInstantiatorToCreatePlugin() {
-        ClassLoader childClassLoader = Mock()
-        Instantiator childInstantiator = Mock()
+        def lookupScope = Mock(ClassLoaderScope)
+        def childInstantiator = Mock(Instantiator)
         def plugin = new TestPlugin1()
 
         given:
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader, childInstantiator)
+        PluginRegistry child = pluginRegistry.createChild(lookupScope, childInstantiator)
 
         when:
         def result = child.loadPlugin(TestPlugin1)
@@ -165,12 +166,12 @@ class DefaultPluginRegistryTest extends Specification {
     }
 
     public void childDelegatesToParentRegistryToLookupPluginType() throws Exception {
-        ClassLoader childClassLoader = Mock()
-        Instantiator childInstantiator = Mock()
+        def lookupScope = Mock(ClassLoaderScope)
+        def childInstantiator = Mock(Instantiator)
         def url = writePluginProperties("somePlugin", TestPlugin1)
 
         given:
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader, childInstantiator)
+        PluginRegistry child = pluginRegistry.createChild(lookupScope, childInstantiator)
         _ * classLoader.getResource("META-INF/gradle-plugins/somePlugin.properties") >> url
         _ * classLoader.loadClass(TestPlugin1.name) >> TestPlugin1
 
@@ -181,16 +182,18 @@ class DefaultPluginRegistryTest extends Specification {
         type == TestPlugin1
 
         and:
-        0 * childClassLoader._
+        0 * lookupScope._
     }
 
     public void childClasspathCanContainAdditionalMappingsForPlugins() throws Exception {
-        ClassLoader childClassLoader = Mock()
-        Instantiator childInstantiator = Mock()
+        def childClassLoader = Mock(ClassLoader)
+        def lookupScope = Mock(ClassLoaderScope)
+        def childInstantiator = Mock(Instantiator)
         def url = writePluginProperties("somePlugin", TestPlugin1)
 
         given:
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader, childInstantiator)
+        PluginRegistry child = pluginRegistry.createChild(lookupScope, childInstantiator)
+        _ * lookupScope.scopeClassLoader >> childClassLoader
         _ * childClassLoader.getResource("META-INF/gradle-plugins/somePlugin.properties") >> url
         _ * childClassLoader.loadClass(TestPlugin1.name) >> TestPlugin1
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
deleted file mode 100644
index 5c27473..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Plugin;
-import org.gradle.api.internal.project.DefaultProject;
-import org.gradle.api.internal.project.TestPlugin1;
-import org.gradle.api.internal.project.TestPlugin2;
-import org.gradle.api.plugins.UnknownPluginException;
-import org.gradle.util.HelperUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectsPluginContainerTest {
-    protected String pluginId = "somePluginId";
-    protected JUnit4Mockery context = new JUnit4Mockery();
-    private final DefaultProject project = HelperUtil.createRootProject();
-
-    private PluginRegistry pluginRegistryStub = context.mock(PluginRegistry.class);
-    private DefaultProjectsPluginContainer container = new DefaultProjectsPluginContainer(pluginRegistryStub, project);
-
-    private TestPlugin1 pluginWithIdMock = new TestPlugin1();
-    private TestPlugin2 pluginWithoutIdMock = new TestPlugin2();
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(pluginRegistryStub).getTypeForId(pluginId); will(returnValue(TestPlugin1.class));
-            allowing(pluginRegistryStub).loadPlugin(TestPlugin1.class); will(returnValue(pluginWithIdMock));
-            allowing(pluginRegistryStub).loadPlugin(TestPlugin2.class); will(returnValue(pluginWithoutIdMock));
-        }});
-    }
-
-    @Test
-    public void usePluginById() {
-        Plugin addedPlugin = container.apply(pluginId);
-        assertThat(pluginWithIdMock, sameInstance(addedPlugin));
-        assertThat(container.apply(pluginId), sameInstance(addedPlugin));
-
-        assertThat(container.findPlugin(addedPlugin.getClass()), sameInstance(addedPlugin));
-        assertThat(container.findPlugin(pluginId), sameInstance(addedPlugin));
-    }
-
-    @Test
-    public void usePluginWithIdByType() {
-        Class<? extends Plugin> type = pluginWithIdMock.getClass();
-
-        Plugin addedPlugin = container.apply(type);
-        assertThat(pluginWithIdMock, sameInstance(addedPlugin));
-        assertThat(container.apply(type), sameInstance(addedPlugin));
-        assertThat(container.apply(pluginId), sameInstance(addedPlugin));
-
-        assertThat(container.findPlugin(type), sameInstance(addedPlugin));
-        assertThat(container.findPlugin(pluginId), sameInstance(addedPlugin));
-    }
-
-    @Test
-    public void usePluginWithoutId() {
-        Class<? extends Plugin> type = pluginWithoutIdMock.getClass();
-        Plugin addedPlugin = container.apply(type);
-        assertThat(pluginWithoutIdMock, sameInstance(addedPlugin));
-        assertThat(container.apply(type), sameInstance(addedPlugin));
-
-        assertThat(container.findPlugin(type), sameInstance(addedPlugin));
-    }
-
-    @Test
-    public void hasAndFindForPluginWithId() {
-        container.apply(pluginId);
-        assertThat(container.hasPlugin(pluginId), equalTo(true));
-        assertThat(container.hasPlugin(pluginWithIdMock.getClass()), equalTo(true));
-        assertThat(container.findPlugin(pluginId), sameInstance((Plugin) pluginWithIdMock));
-        assertThat(container.findPlugin(pluginWithIdMock.getClass()), sameInstance((Plugin) pluginWithIdMock));
-    }
-
-    @Test
-    public void hasAndFindForUnknownPluginId() {
-        context.checking(new Expectations() {{
-            allowing(pluginRegistryStub).getTypeForId("unknown"); will(throwException(new UnknownPluginException("unknown")));
-        }});
-
-        assertThat(container.hasPlugin("unknown"), equalTo(false));
-        assertThat(container.findPlugin("unknown"), nullValue());
-    }
-
-    @Test
-    public void hasAndFindForPluginWithoutId() {
-        Plugin plugin = pluginWithoutIdMock;
-        Class<? extends Plugin> pluginType = plugin.getClass();
-        container.apply(pluginType);
-        assertThat(container.hasPlugin(pluginType), equalTo(true));
-        assertThat(container.findPlugin(pluginType), sameInstance(plugin));
-    }
-
-    @Test
-    public void hasAndFindPluginByTypeWithUnknownPlugin() {
-        assertThat(container.hasPlugin(TestPlugin2.class), equalTo(false));
-        assertThat(container.findPlugin(TestPlugin2.class), nullValue());
-    }
-
-    @Test(expected = UnknownPluginException.class)
-    public void getNonUsedPluginById() {
-        container.getPlugin(pluginId);
-    }
-
-    @Test(expected = UnknownPluginException.class)
-    public void getNonUsedPluginByType() {
-        container.getPlugin(TestPlugin1.class);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy
index 2c78e79..8a4a1b0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy
@@ -23,9 +23,6 @@ import org.gradle.api.plugins.ExtensionAware
 import org.gradle.api.plugins.ExtraPropertiesExtension
 import spock.lang.Specification
 
-/**
- * @author: Szczepan Faber, created at: 6/24/11
- */
 public class ExtensionContainerTest extends Specification {
 
     def container = new DefaultConvention(ThreadGlobalInstantiator.getOrCreate())
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy
index 9e7049f..9056abc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.plugins
 
 import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
 import org.gradle.api.UnknownDomainObjectException
 import org.gradle.api.plugins.DeferredConfigurable
 import spock.lang.Specification
@@ -132,6 +133,81 @@ class ExtensionsStorageTest extends Specification {
         1 * delegate.call(2)
     }
 
+    def "propagates configure exception on each attempt to access deferred configurable exception"() {
+
+        TestDeferredExtension extension = new TestDeferredExtension()
+        def delegate = Mock(TestExtension)
+        extension.delegate = delegate
+
+        given:
+        storage.add("ext", extension)
+        storage.configureExtension("ext", {
+            throw new RuntimeException("bad")
+        })
+
+        when:
+        storage.getByName("ext")
+
+        then:
+        def first = thrown RuntimeException
+        first.message == "bad"
+
+        when:
+        storage.getByName("ext")
+
+        then:
+        def second = thrown RuntimeException
+        second == first
+    }
+
+    def "rethrows unknown domain object exception thrown by deferred configurable extension config"() {
+
+        TestDeferredExtension extension = new TestDeferredExtension()
+        def delegate = Mock(TestExtension)
+        extension.delegate = delegate
+
+        when:
+        storage.add("ext", extension)
+        storage.configureExtension("ext", {
+            throw new UnknownDomainObjectException("ORIGINAL")
+        })
+
+        then:
+        0 * _
+
+        when:
+        storage.findByType(TestDeferredExtension)
+
+        then:
+        def t = thrown UnknownDomainObjectException
+        t.message == "ORIGINAL"
+    }
+
+    def "cannot configure deferred configurable extension after access"() {
+
+        TestDeferredExtension extension = new TestDeferredExtension()
+        def delegate = Mock(TestExtension)
+        extension.delegate = delegate
+
+        given:
+        storage.add("ext", extension)
+        storage.configureExtension("ext", {
+            it.call(1)
+        })
+
+        and:
+        storage.getByName("ext")
+
+        when:
+        storage.configureExtension("ext", {
+            it.call(2)
+        })
+
+        then:
+        def t = thrown InvalidUserDataException
+        t.message == "Cannot configure the 'ext' extension after it has been accessed."
+    }
+
     public static interface TestExtension {
         void call(def value);
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy
index 87a851b..b6e240a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.api.internal.project
 
 import org.apache.tools.ant.BuildListener
 import org.gradle.api.Project
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
 import org.junit.Before
@@ -30,7 +30,7 @@ import static org.junit.Assert.*
 public class DefaultAntBuilderFactoryTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
     private final BuildListener listener = context.mock(BuildListener)
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final DefaultAntBuilderFactory factory = new DefaultAntBuilderFactory(listener, project)
 
     @Before
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
index f3c5ad4..37025bc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.project
 import groovy.xml.MarkupBuilder
 import org.junit.Test
 import org.gradle.api.Project
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import static org.junit.Assert.*
 import static org.hamcrest.Matchers.*
 import static org.gradle.util.Matchers.*
@@ -28,7 +28,7 @@ import org.apache.tools.ant.Target
 import org.apache.tools.ant.Task
 
 class DefaultAntBuilderTest {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final def ant = new DefaultAntBuilder(project)
 
     @Test
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 ebf5826..c4082c8 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
@@ -28,7 +28,7 @@ import org.gradle.api.internal.classpath.ModuleRegistry
 import org.gradle.api.internal.project.ant.BasicAntBuilder
 import org.gradle.logging.ConfigureLogging
 import org.gradle.logging.TestAppender
-import org.gradle.util.DefaultClassLoaderFactory
+import org.gradle.internal.classloader.DefaultClassLoaderFactory
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -36,7 +36,7 @@ import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 import static org.junit.Assert.fail
 import org.apache.tools.ant.Task
-import org.gradle.util.ClasspathUtil
+import org.gradle.internal.classloader.ClasspathUtil
 
 class DefaultIsolatedAntBuilderTest {
     private final ModuleRegistry moduleRegistry = new DefaultModuleRegistry()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java
index 7b9c0d1..24bb1c7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.project;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Project;
 import org.gradle.api.specs.Spec;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -26,18 +26,11 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 
 import static junit.framework.Assert.assertSame;
-import static org.gradle.util.WrapUtil.*;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.gradle.util.WrapUtil.toSortedSet;
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.*;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultProjectRegistryTest {
     public static final String CHILD_NAME = "child";
     public static final String CHILD_CHILD_NAME = "childchild";
@@ -50,9 +43,9 @@ public class DefaultProjectRegistryTest {
     @Before
     public void setUp() {
         projectRegistry = new DefaultProjectRegistry<ProjectInternal>();
-        rootMock = HelperUtil.createRootProject();
-        childMock = HelperUtil.createChildProject(rootMock, CHILD_NAME);
-        childChildMock = HelperUtil.createChildProject(childMock, CHILD_CHILD_NAME);
+        rootMock = TestUtil.createRootProject();
+        childMock = TestUtil.createChildProject(rootMock, CHILD_NAME);
+        childChildMock = TestUtil.createChildProject(childMock, CHILD_CHILD_NAME);
         projectRegistry.addProject(rootMock);
         projectRegistry.addProject(childMock);
         projectRegistry.addProject(childChildMock);
@@ -77,7 +70,7 @@ public class DefaultProjectRegistryTest {
 
     @Test
     public void cannotLocateProjectsWithAmbiguousProjectDir() {
-        DefaultProject duplicateProjectDirProject = HelperUtil.createChildProject(childMock, "childchild2", childMock.getProjectDir());
+        DefaultProject duplicateProjectDirProject = TestUtil.createChildProject(childMock, "childchild2", childMock.getProjectDir());
         projectRegistry.addProject(duplicateProjectDirProject);
 
         try {
@@ -91,7 +84,7 @@ public class DefaultProjectRegistryTest {
     @Test
     public void accessMethodsForNonExistingsPaths() {
         projectRegistry = new DefaultProjectRegistry<ProjectInternal>();
-        Project otherRoot = HelperUtil.createRootProject();
+        Project otherRoot = TestUtil.createRootProject();
         assertNull(projectRegistry.getProject(otherRoot.getPath()));
         assertEquals(new TreeSet<ProjectInternal>(), projectRegistry.getAllProjects(otherRoot.getPath()));
         assertEquals(new TreeSet<ProjectInternal>(), projectRegistry.getSubProjects(otherRoot.getPath()));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
index 32c4cee..66e15b1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
@@ -17,68 +17,69 @@
 package org.gradle.api.internal.project
 
 import org.apache.tools.ant.types.FileSet
-import org.gradle.api.artifacts.Module
+import org.gradle.api.*
 import org.gradle.api.artifacts.dsl.ArtifactHandler
+import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.api.artifacts.dsl.RepositoryHandler
 import org.gradle.api.component.SoftwareComponentContainer
 import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.*
+import org.gradle.api.internal.artifacts.ModuleInternal
+import org.gradle.api.internal.artifacts.ProjectBackedModule
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
-import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
 import org.gradle.api.internal.file.FileOperations
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.initialization.DefaultClassLoaderCache
+import org.gradle.api.internal.initialization.RootClassLoaderScope
+import org.gradle.api.internal.initialization.ScriptHandlerFactory
 import org.gradle.api.internal.tasks.TaskContainerInternal
 import org.gradle.api.invocation.Gradle
 import org.gradle.api.plugins.PluginContainer
 import org.gradle.api.tasks.Directory
-import org.gradle.configuration.ProjectEvaluator
 import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.configuration.project.ProjectConfigurationActionContainer
+import org.gradle.configuration.project.ProjectEvaluator
 import org.gradle.groovy.scripts.EmptyScript
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.internal.Factory
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.ServiceRegistry
+import org.gradle.internal.service.scopes.ServiceRegistryFactory
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.StandardOutputCapture
-import org.gradle.util.HelperUtil
+import org.gradle.model.ModelRules
+import org.gradle.model.internal.ModelRegistry
+import org.gradle.model.internal.ModelRegistryBackedModelRules
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TestClosure
+import org.gradle.util.TestUtil
 import org.jmock.integration.junit4.JMock
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
-import java.awt.Point
+import java.awt.*
 import java.text.FieldPosition
 
-import org.gradle.api.*
-import org.gradle.api.internal.*
-
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
- at RunWith (JMock.class)
+ at RunWith(JMock.class)
 class DefaultProjectTest {
     JUnit4GroovyMockery context = new JUnit4GroovyMockery()
 
-    static final String TEST_PROJECT_NAME = 'testproject'
-
     static final String TEST_BUILD_FILE_NAME = 'build.gradle'
 
-    static final String TEST_TASK_NAME = 'testtask'
-
     Task testTask;
 
     DefaultProject project, child1, child2, childchild
 
     ProjectEvaluator projectEvaluator = context.mock(ProjectEvaluator.class)
 
-    IProjectRegistry projectRegistry
+    ProjectRegistry projectRegistry
 
     File rootDir
 
@@ -94,8 +95,8 @@ class DefaultProjectTest {
 
     ConfigurationContainerInternal configurationContainerMock = context.mock(ConfigurationContainerInternal.class)
     RepositoryHandler repositoryHandlerMock = context.mock(RepositoryHandler.class)
-    DependencyFactory dependencyFactoryMock = context.mock(DependencyFactory.class)
     DependencyHandler dependencyHandlerMock = context.mock(DependencyHandler)
+    ComponentMetadataHandler moduleHandlerMock = context.mock(ComponentMetadataHandler)
     PluginContainer pluginContainerMock = context.mock(PluginContainer)
     ScriptHandler scriptHandlerMock = context.mock(ScriptHandler)
     DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider)
@@ -105,6 +106,9 @@ class DefaultProjectTest {
     LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class)
     Instantiator instantiatorMock = context.mock(Instantiator)
     SoftwareComponentContainer softwareComponentsMock = context.mock(SoftwareComponentContainer.class)
+    ProjectConfigurationActionContainer configureActions = context.mock(ProjectConfigurationActionContainer.class)
+
+    ClassLoaderScope rootProjectClassLoaderScope = new RootClassLoaderScope(getClass().classLoader, new DefaultClassLoaderCache()).createChild()
 
     @Before
     void setUp() {
@@ -120,36 +124,44 @@ class DefaultProjectTest {
 
         testScript = new EmptyScript()
 
-        testTask = HelperUtil.createTask(DefaultTask)
+        testTask = TestUtil.createTask(DefaultTask)
 
         projectRegistry = new DefaultProjectRegistry()
 
         projectServiceRegistryFactoryMock = context.mock(ServiceRegistryFactory.class, 'parent')
-        serviceRegistryMock = context.mock(ServiceRegistryFactory.class, 'project')
+        serviceRegistryMock = context.mock(ServiceRegistry.class, 'project')
 
         context.checking {
             allowing(projectServiceRegistryFactoryMock).createFor(withParam(notNullValue())); will(returnValue(serviceRegistryMock))
             allowing(serviceRegistryMock).newInstance(TaskContainerInternal); will(returnValue(taskContainerMock))
             allowing(taskContainerMock).getTasksAsDynamicObject(); will(returnValue(new BeanDynamicObject(new TaskContainerDynamicObject(someTask: testTask))))
+            allowing(taskContainerMock).all(withParam(notNullValue()))
+            allowing(taskContainerMock).whenObjectRemoved(withParam(notNullValue()))
             allowing(serviceRegistryMock).get(RepositoryHandler); will(returnValue(repositoryHandlerMock))
             allowing(serviceRegistryMock).get(ConfigurationContainerInternal); will(returnValue(configurationContainerMock))
             allowing(serviceRegistryMock).get(ArtifactHandler); will(returnValue(context.mock(ArtifactHandler)))
             allowing(serviceRegistryMock).get(DependencyHandler); will(returnValue(dependencyHandlerMock))
+            allowing(serviceRegistryMock).get(ComponentMetadataHandler); will(returnValue(moduleHandlerMock))
             allowing(serviceRegistryMock).get(SoftwareComponentContainer); will(returnValue(softwareComponentsMock))
             allowing(serviceRegistryMock).get(ProjectEvaluator); will(returnValue(projectEvaluator))
             allowing(serviceRegistryMock).getFactory(AntBuilder); will(returnValue(antBuilderFactoryMock))
             allowing(serviceRegistryMock).get(PluginContainer); will(returnValue(pluginContainerMock))
             allowing(serviceRegistryMock).get(ScriptHandler); will(returnValue(scriptHandlerMock))
-            allowing(serviceRegistryMock).get(ScriptClassLoaderProvider); will(returnValue(context.mock(ScriptClassLoaderProvider)))
             allowing(serviceRegistryMock).get(LoggingManagerInternal); will(returnValue(loggingManagerMock))
             allowing(serviceRegistryMock).get(StandardOutputCapture); will(returnValue(context.mock(StandardOutputCapture)))
-            allowing(serviceRegistryMock).get(IProjectRegistry); will(returnValue(projectRegistry))
+            allowing(serviceRegistryMock).get(ProjectRegistry); will(returnValue(projectRegistry))
             allowing(serviceRegistryMock).get(DependencyMetaDataProvider); will(returnValue(dependencyMetaDataProviderMock))
-            allowing(serviceRegistryMock).get(FileResolver); will(returnValue([toString: { -> "file resolver" }] as FileResolver))
+            allowing(serviceRegistryMock).get(FileResolver); will(returnValue([toString: {-> "file resolver" }] as FileResolver))
             allowing(serviceRegistryMock).get(Instantiator); will(returnValue(instantiatorMock))
             allowing(serviceRegistryMock).get(FileOperations); will(returnValue(fileOperationsMock))
             allowing(serviceRegistryMock).get(ProcessOperations); will(returnValue(processOperationsMock))
-            allowing(serviceRegistryMock).get(ScriptPluginFactory); will(returnValue([toString: { -> "script plugin factory" }] as ScriptPluginFactory))
+            allowing(serviceRegistryMock).get(ScriptPluginFactory); will(returnValue([toString: {-> "script plugin factory" }] as ScriptPluginFactory))
+            allowing(serviceRegistryMock).get(ScriptHandlerFactory); will(returnValue([toString: {-> "script plugin factory" }] as ScriptHandlerFactory))
+            allowing(serviceRegistryMock).get(ProjectConfigurationActionContainer); will(returnValue(configureActions))
+            ModelRegistry modelRegistry = context.mock(ModelRegistry)
+            ignoring(modelRegistry)
+            allowing(serviceRegistryMock).get(ModelRegistry); will(returnValue(modelRegistry))
+            allowing(serviceRegistryMock).get(ModelRules); will(returnValue(new ModelRegistryBackedModelRules(modelRegistry)))
             Object listener = context.mock(ProjectEvaluationListener)
             ignoring(listener)
             allowing(build).getProjectEvaluationBroadcaster();
@@ -158,12 +170,13 @@ class DefaultProjectTest {
 
         // TODO - don't decorate the project objects
         AsmBackedClassGenerator classGenerator = new AsmBackedClassGenerator()
-        project = classGenerator.newInstance(DefaultProject.class, 'root', null, rootDir, script, build, projectServiceRegistryFactoryMock);
-        child1 = classGenerator.newInstance(DefaultProject.class, "child1", project, new File("child1"), script, build, projectServiceRegistryFactoryMock)
+        project = classGenerator.newInstance(DefaultProject.class, 'root', null, rootDir, script, build, projectServiceRegistryFactoryMock, rootProjectClassLoaderScope);
+        def child1ClassLoaderScope = rootProjectClassLoaderScope.createChild()
+        child1 = classGenerator.newInstance(DefaultProject.class, "child1", project, new File("child1"), script, build, projectServiceRegistryFactoryMock, child1ClassLoaderScope)
         project.addChildProject(child1)
-        childchild = classGenerator.newInstance(DefaultProject.class, "childchild", child1, new File("childchild"), script, build, projectServiceRegistryFactoryMock)
+        childchild = classGenerator.newInstance(DefaultProject.class, "childchild", child1, new File("childchild"), script, build, projectServiceRegistryFactoryMock, child1ClassLoaderScope.createChild())
         child1.addChildProject(childchild)
-        child2 = classGenerator.newInstance(DefaultProject.class, "child2", project, new File("child2"), script, build, projectServiceRegistryFactoryMock)
+        child2 = classGenerator.newInstance(DefaultProject.class, "child2", project, new File("child2"), script, build, projectServiceRegistryFactoryMock, rootProjectClassLoaderScope.createChild())
         project.addChildProject(child2)
         [project, child1, childchild, child2].each {
             projectRegistry.addProject(it)
@@ -172,7 +185,8 @@ class DefaultProjectTest {
 
     //TODO please move more coverage to NewDefaultProjectTest
 
-  @Test void testScriptClasspath() {
+    @Test
+    void testScriptClasspath() {
         context.checking {
             one(scriptHandlerMock).getRepositories()
         }
@@ -181,7 +195,8 @@ class DefaultProjectTest {
         }
     }
 
-    @Test void testProject() {
+    @Test
+    void testProject() {
         assertSame project, child1.parent
         assertSame project, child1.rootProject
         checkProject(project, null, 'root', rootDir)
@@ -209,7 +224,8 @@ class DefaultProjectTest {
         assert project.components.is(softwareComponentsMock)
     }
 
-    @Test public void testNullVersionAndStatus() {
+    @Test
+    public void testNullVersionAndStatus() {
         project.version = 'version'
         project.status = 'status'
         assertEquals('version', project.version)
@@ -220,7 +236,8 @@ class DefaultProjectTest {
         assertEquals(Project.DEFAULT_STATUS, project.status)
     }
 
-    @Test void testGetGroup() {
+    @Test
+    void testGetGroup() {
         assertThat(project.getGroup(), equalTo(''))
         assertThat(childchild.getGroup(), equalTo('root.child1'))
 
@@ -231,7 +248,8 @@ class DefaultProjectTest {
         assertThat(child1.getGroup(), equalTo('root'))
     }
 
-    @Test public void testExecutesActionBeforeEvaluation() {
+    @Test
+    public void testExecutesActionBeforeEvaluation() {
         Action<Project> listener = context.mock(Action)
         context.checking {
             one(listener).execute(project)
@@ -240,7 +258,8 @@ class DefaultProjectTest {
         project.projectEvaluationBroadcaster.beforeEvaluate(project)
     }
 
-    @Test public void testExecutesActionAfterEvaluation() {
+    @Test
+    public void testExecutesActionAfterEvaluation() {
         Action<Project> listener = context.mock(Action)
         context.checking {
             one(listener).execute(project)
@@ -249,58 +268,64 @@ class DefaultProjectTest {
         project.projectEvaluationBroadcaster.afterEvaluate(project, null)
     }
 
-    @Test public void testExecutesClosureBeforeEvaluation() {
+    @Test
+    public void testExecutesClosureBeforeEvaluation() {
         TestClosure listener = context.mock(TestClosure)
         context.checking {
             one(listener).call(project)
         }
 
-        project.beforeEvaluate(HelperUtil.toClosure(listener))
+        project.beforeEvaluate(TestUtil.toClosure(listener))
         project.projectEvaluationBroadcaster.beforeEvaluate(project)
     }
 
-    @Test public void testExecutesClosureAfterEvaluation() {
+    @Test
+    public void testExecutesClosureAfterEvaluation() {
         TestClosure listener = context.mock(TestClosure)
         context.checking {
             one(listener).call(project)
         }
 
-        project.afterEvaluate(HelperUtil.toClosure(listener))
+        project.afterEvaluate(TestUtil.toClosure(listener))
         project.projectEvaluationBroadcaster.afterEvaluate(project, null)
     }
 
-    @Test void testEvaluate() {
+    @Test
+    void testEvaluate() {
         context.checking {
             one(projectEvaluator).evaluate(project, project.state)
         }
         assertSame(project, project.evaluate())
     }
 
-    @Test void testUsePluginWithString() {
+    @Test
+    void testUsePluginWithString() {
         context.checking {
             one(pluginContainerMock).apply('someplugin'); will(returnValue([:] as Plugin))
         }
         project.apply(plugin: 'someplugin')
     }
 
-    @Test void testUsePluginWithClass() {
+    @Test
+    void testUsePluginWithClass() {
         context.checking {
             one(pluginContainerMock).apply(Plugin); will(returnValue([:] as Plugin))
         }
         project.apply(plugin: Plugin)
     }
 
-    @Test void testEvaluationDependsOn() {
+    @Test
+    void testEvaluationDependsOn() {
         boolean mockReader2Finished = false
         boolean mockReader1Called = false
-        final ProjectEvaluator mockReader1 = [evaluate: {DefaultProject project, state ->
+        final ProjectEvaluator mockReader1 = [evaluate: { DefaultProject project, state ->
             project.evaluationDependsOn(child1.path)
             assertTrue(mockReader2Finished)
             mockReader1Called = true
             testScript
         }] as ProjectEvaluator
         final ProjectEvaluator mockReader2 = [
-                evaluate: {DefaultProject project, state ->
+                evaluate: { DefaultProject project, state ->
                     mockReader2Finished = true
                     testScript
                 }] as ProjectEvaluator
@@ -316,7 +341,7 @@ class DefaultProjectTest {
         boolean child1MockReaderFinished = false
         boolean child2MockReaderFinished = false
         boolean mockReader1Called = false
-        final ProjectEvaluator mockReader1 = [evaluate: {DefaultProject project, state ->
+        final ProjectEvaluator mockReader1 = [evaluate: { DefaultProject project, state ->
             project.evaluationDependsOnChildren()
             assertTrue(child1MockReaderFinished)
             assertTrue(child2MockReaderFinished)
@@ -324,12 +349,12 @@ class DefaultProjectTest {
             testScript
         }] as ProjectEvaluator
         final ProjectEvaluator mockReader2 = [
-                evaluate: {DefaultProject project, state ->
+                evaluate: { DefaultProject project, state ->
                     child1MockReaderFinished = true
                     testScript
                 }] as ProjectEvaluator
         final ProjectEvaluator mockReader3 = [
-                evaluate: {DefaultProject project, state ->
+                evaluate: { DefaultProject project, state ->
                     child2MockReaderFinished = true
                     testScript
                 }] as ProjectEvaluator
@@ -340,21 +365,24 @@ class DefaultProjectTest {
         assertTrue mockReader1Called
     }
 
-    @Test (expected = InvalidUserDataException) void testEvaluationDependsOnWithNullArgument() {
+    @Test(expected = InvalidUserDataException)
+    void testEvaluationDependsOnWithNullArgument() {
         project.evaluationDependsOn(null)
     }
 
-    @Test (expected = InvalidUserDataException) void testEvaluationDependsOnWithEmptyArgument() {
+    @Test(expected = InvalidUserDataException)
+    void testEvaluationDependsOnWithEmptyArgument() {
         project.evaluationDependsOn('')
     }
 
-    @Test (expected = CircularReferenceException) void testEvaluationDependsOnWithCircularDependency() {
-        final ProjectEvaluator mockReader1 = [evaluate: {DefaultProject project, ProjectState state ->
+    @Test(expected = CircularReferenceException)
+    void testEvaluationDependsOnWithCircularDependency() {
+        final ProjectEvaluator mockReader1 = [evaluate: { DefaultProject project, ProjectState state ->
             state.executing = true
             project.evaluationDependsOn(child1.path)
             testScript
         }] as ProjectEvaluator
-        final ProjectEvaluator mockReader2 = [evaluate: {DefaultProject project, ProjectState state ->
+        final ProjectEvaluator mockReader2 = [evaluate: { DefaultProject project, ProjectState state ->
             state.executing = true
             project.evaluationDependsOn(project.path)
             testScript
@@ -364,9 +392,10 @@ class DefaultProjectTest {
         project.evaluate()
     }
 
-    @Test void testDependsOnWithNoEvaluation() {
+    @Test
+    void testDependsOnWithNoEvaluation() {
         boolean mockReaderCalled = false
-        final ProjectEvaluator mockReader = [evaluateProject: {DefaultProject project ->
+        final ProjectEvaluator mockReader = [evaluateProject: { DefaultProject project ->
             mockReaderCalled = true
             testScript
         }] as ProjectEvaluator
@@ -378,9 +407,10 @@ class DefaultProjectTest {
         assertEquals([child1, child2] as Set, project.dependsOnProjects)
     }
 
-    @Test void testDependsOn() {
+    @Test
+    void testDependsOn() {
         boolean mockReaderCalled = false
-        final ProjectEvaluator mockReader = [evaluate: {DefaultProject project, state ->
+        final ProjectEvaluator mockReader = [evaluate: { DefaultProject project, state ->
             mockReaderCalled = true
             testScript
         }] as ProjectEvaluator
@@ -391,7 +421,8 @@ class DefaultProjectTest {
 
     }
 
-    @Test void testChildrenDependsOnMe() {
+    @Test
+    void testChildrenDependsOnMe() {
         project.childrenDependOnMe()
         assertTrue(child1.dependsOnProjects.contains(project))
         assertTrue(child2.dependsOnProjects.contains(project))
@@ -399,7 +430,8 @@ class DefaultProjectTest {
         assertEquals(1, child2.dependsOnProjects.size())
     }
 
-    @Test void testDependsOnChildren() {
+    @Test
+    void testDependsOnChildren() {
         context.checking {
             never(projectEvaluator).evaluate(child1, child1.state)
         }
@@ -411,7 +443,8 @@ class DefaultProjectTest {
         assertEquals(2, project.dependsOnProjects.size())
     }
 
-    @Test void testDependsOnChildrenIncludingEvaluate() {
+    @Test
+    void testDependsOnChildrenIncludingEvaluate() {
         context.checking {
             one(projectEvaluator).evaluate(child1, child1.state)
             one(projectEvaluator).evaluate(child2, child2.state)
@@ -422,25 +455,30 @@ class DefaultProjectTest {
         assertEquals(2, project.dependsOnProjects.size())
     }
 
-    @Test (expected = InvalidUserDataException) void testDependsOnWithNullPath() {
+    @Test(expected = InvalidUserDataException)
+    void testDependsOnWithNullPath() {
         project.dependsOn(null)
     }
 
-    @Test (expected = InvalidUserDataException) void testDependsOnWithEmptyPath() {
+    @Test(expected = InvalidUserDataException)
+    void testDependsOnWithEmptyPath() {
         project.dependsOn('')
     }
 
-    @Test (expected = UnknownProjectException) void testDependsOnWithUnknownParentPath() {
+    @Test(expected = UnknownProjectException)
+    void testDependsOnWithUnknownParentPath() {
         project.dependsOn(child1.path + 'XXX')
     }
 
-    @Test (expected = UnknownProjectException) void testDependsOnWithUnknownProjectPath() {
+    @Test(expected = UnknownProjectException)
+    void testDependsOnWithUnknownProjectPath() {
         project.dependsOn(child1.name + 'XXX')
     }
 
-    @Test void testAddAndGetChildProject() {
-        ProjectInternal child1 = ['getName': {-> 'child1'}] as ProjectInternal
-        ProjectInternal child2 = ['getName': {-> 'child2'}] as ProjectInternal
+    @Test
+    void testAddAndGetChildProject() {
+        ProjectInternal child1 = ['getName': {-> 'child1' }] as ProjectInternal
+        ProjectInternal child2 = ['getName': {-> 'child2' }] as ProjectInternal
 
         project.addChildProject(child1)
         assertEquals(2, project.childProjects.size())
@@ -451,30 +489,36 @@ class DefaultProjectTest {
         assertSame(child2, project.childProjects.child2)
     }
 
-    @Test public void testDefaultTasks() {
+    @Test
+    public void testDefaultTasks() {
         project.defaultTasks("a", "b");
         assertEquals(["a", "b"], project.getDefaultTasks())
         project.defaultTasks("c");
         assertEquals(["c"], project.getDefaultTasks())
     }
 
-    @Test (expected = InvalidUserDataException) public void testDefaultTasksWithNull() {
+    @Test(expected = InvalidUserDataException)
+    public void testDefaultTasksWithNull() {
         project.defaultTasks(null);
     }
 
-    @Test (expected = InvalidUserDataException) public void testDefaultTasksWithSingleNullValue() {
+    @Test(expected = InvalidUserDataException)
+    public void testDefaultTasksWithSingleNullValue() {
         project.defaultTasks("a", null);
     }
 
-    @Test void testCanAccessTaskAsAProjectProperty() {
+    @Test
+    void testCanAccessTaskAsAProjectProperty() {
         assertThat(project.someTask, sameInstance(testTask))
     }
 
-    @Test (expected = MissingPropertyException) void testPropertyShortCutForTaskCallWithNonExistingTask() {
+    @Test(expected = MissingPropertyException)
+    void testPropertyShortCutForTaskCallWithNonExistingTask() {
         project.unknownTask
     }
 
-    @Test (expected = MissingMethodException) void testMethodShortCutForTaskCallWithNonExistingTask() {
+    @Test(expected = MissingMethodException)
+    void testMethodShortCutForTaskCallWithNonExistingTask() {
         project.unknownTask([dependsOn: '/task2'])
     }
 
@@ -487,12 +531,14 @@ class DefaultProjectTest {
 
     }
 
-    @Test void testPath() {
+    @Test
+    void testPath() {
         assertEquals(Project.PATH_SEPARATOR + "child1", child1.path)
         assertEquals(Project.PATH_SEPARATOR, project.path)
     }
 
-    @Test void testGetProject() {
+    @Test
+    void testGetProject() {
         assertSame(project, project.project(Project.PATH_SEPARATOR))
         assertSame(child1, project.project(Project.PATH_SEPARATOR + "child1"))
         assertSame(child1, project.project("child1"))
@@ -500,7 +546,8 @@ class DefaultProjectTest {
         assertSame(child1, childchild.project(Project.PATH_SEPARATOR + "child1"))
     }
 
-    @Test void testGetProjectWithUnknownAbsolutePath() {
+    @Test
+    void testGetProjectWithUnknownAbsolutePath() {
         try {
             project.project(Project.PATH_SEPARATOR + "unknownchild")
             fail()
@@ -509,7 +556,8 @@ class DefaultProjectTest {
         }
     }
 
-    @Test void testGetProjectWithUnknownRelativePath() {
+    @Test
+    void testGetProjectWithUnknownRelativePath() {
         try {
             project.project("unknownchild")
             fail()
@@ -518,15 +566,18 @@ class DefaultProjectTest {
         }
     }
 
-    @Test (expected = InvalidUserDataException) void testGetProjectWithEmptyPath() {
+    @Test(expected = InvalidUserDataException)
+    void testGetProjectWithEmptyPath() {
         project.project("")
     }
 
-    @Test (expected = InvalidUserDataException) void testGetProjectWithNullPath() {
+    @Test(expected = InvalidUserDataException)
+    void testGetProjectWithNullPath() {
         project.project(null)
     }
 
-    @Test void testFindProject() {
+    @Test
+    void testFindProject() {
         assertSame(project, project.findProject(Project.PATH_SEPARATOR))
         assertSame(child1, project.findProject(Project.PATH_SEPARATOR + "child1"))
         assertSame(child1, project.findProject("child1"))
@@ -534,15 +585,18 @@ class DefaultProjectTest {
         assertSame(child1, childchild.findProject(Project.PATH_SEPARATOR + "child1"))
     }
 
-    @Test void testFindProjectWithUnknownAbsolutePath() {
+    @Test
+    void testFindProjectWithUnknownAbsolutePath() {
         assertNull(project.findProject(Project.PATH_SEPARATOR + "unknownchild"))
     }
 
-    @Test void testFindProjectWithUnknownRelativePath() {
+    @Test
+    void testFindProjectWithUnknownRelativePath() {
         assertNull(project.findProject("unknownChild"))
     }
 
-    @Test void testGetProjectWithClosure() {
+    @Test
+    void testGetProjectWithClosure() {
         String newPropValue = 'someValue'
         assert child1.is(project.project("child1") {
             newProp = newPropValue
@@ -550,10 +604,11 @@ class DefaultProjectTest {
         assertEquals(child1.newProp, newPropValue)
     }
 
-    @Test void testGetAllTasksRecursive() {
-        Task projectTask = HelperUtil.createTask(DefaultTask.class)
-        Task child1Task = HelperUtil.createTask(DefaultTask.class)
-        Task child2Task = HelperUtil.createTask(DefaultTask.class)
+    @Test
+    void testGetAllTasksRecursive() {
+        Task projectTask = TestUtil.createTask(DefaultTask.class)
+        Task child1Task = TestUtil.createTask(DefaultTask.class)
+        Task child2Task = TestUtil.createTask(DefaultTask.class)
 
         Map expectedMap = new TreeMap()
         expectedMap[project] = [projectTask] as TreeSet
@@ -575,8 +630,9 @@ class DefaultProjectTest {
         assertEquals(expectedMap, project.getAllTasks(true))
     }
 
-    @Test void testGetAllTasksNonRecursive() {
-        Task projectTask = HelperUtil.createTask(DefaultTask.class)
+    @Test
+    void testGetAllTasksNonRecursive() {
+        Task projectTask = TestUtil.createTask(DefaultTask.class)
 
         Map expectedMap = new TreeMap()
         expectedMap[project] = [projectTask] as TreeSet
@@ -589,9 +645,10 @@ class DefaultProjectTest {
         assertEquals(expectedMap, project.getAllTasks(false))
     }
 
-    @Test void testGetTasksByNameRecursive() {
-        Task projectTask = HelperUtil.createTask(DefaultTask.class)
-        Task child1Task = HelperUtil.createTask(DefaultTask.class)
+    @Test
+    void testGetTasksByNameRecursive() {
+        Task projectTask = TestUtil.createTask(DefaultTask.class)
+        Task child1Task = TestUtil.createTask(DefaultTask.class)
 
         context.checking {
             one(taskContainerMock).findByName('task'); will(returnValue(projectTask))
@@ -603,8 +660,9 @@ class DefaultProjectTest {
         assertEquals([projectTask, child1Task] as Set, project.getTasksByName('task', true))
     }
 
-    @Test void testGetTasksByNameNonRecursive() {
-        Task projectTask = HelperUtil.createTask(DefaultTask.class)
+    @Test
+    void testGetTasksByNameNonRecursive() {
+        Task projectTask = TestUtil.createTask(DefaultTask.class)
 
         context.checking {
             one(taskContainerMock).findByName('task'); will(returnValue(projectTask))
@@ -613,15 +671,18 @@ class DefaultProjectTest {
         assertEquals([projectTask] as Set, project.getTasksByName('task', false))
     }
 
-    @Test (expected = InvalidUserDataException) void testGetTasksWithEmptyName() {
+    @Test(expected = InvalidUserDataException)
+    void testGetTasksWithEmptyName() {
         project.getTasksByName('', true)
     }
 
-    @Test (expected = InvalidUserDataException) void testGetTasksWithNullName() {
+    @Test(expected = InvalidUserDataException)
+    void testGetTasksWithNullName() {
         project.getTasksByName(null, true)
     }
 
-    @Test void testGetTasksWithUnknownName() {
+    @Test
+    void testGetTasksWithUnknownName() {
         context.checking {
             allowing(taskContainerMock).findByName('task'); will(returnValue(null))
         }
@@ -632,7 +693,7 @@ class DefaultProjectTest {
 
     private List addTestTaskToAllProjects(String name) {
         List tasks = []
-        project.allprojects.each {Project project ->
+        project.allprojects.each { Project project ->
             tasks << addTestTask(project, name)
         }
         tasks
@@ -642,9 +703,10 @@ class DefaultProjectTest {
         new DefaultTask(project, name)
     }
 
-    @Test void testMethodMissing() {
+    @Test
+    void testMethodMissing() {
         boolean closureCalled = false
-        Closure testConfigureClosure = {closureCalled = true}
+        Closure testConfigureClosure = { closureCalled = true }
         project.someTask(testConfigureClosure)
         assert closureCalled
 
@@ -661,10 +723,11 @@ def scriptMethod(Closure closure) {
     "$returnValue"
 }
 """
-        HelperUtil.createScript(code)
+        TestUtil.createScript(code)
     }
 
-    @Test void testSetPropertyAndPropertyMissingWithProjectProperty() {
+    @Test
+    void testSetPropertyAndPropertyMissingWithProjectProperty() {
         String propertyName = 'propName'
         String expectedValue = 'somevalue'
 
@@ -673,7 +736,8 @@ def scriptMethod(Closure closure) {
         assertEquals(expectedValue, child1."$propertyName")
     }
 
-    @Test void testPropertyMissingWithExistingConventionProperty() {
+    @Test
+    void testPropertyMissingWithExistingConventionProperty() {
         String propertyName = 'conv'
         String expectedValue = 'somevalue'
         project.convention.plugins.test = new TestConvention()
@@ -683,7 +747,8 @@ def scriptMethod(Closure closure) {
         assertEquals(expectedValue, child1."$propertyName")
     }
 
-    @Test void testSetPropertyAndPropertyMissingWithConventionProperty() {
+    @Test
+    void testSetPropertyAndPropertyMissingWithConventionProperty() {
         String expectedValue = 'somevalue'
         project.convention.plugins.test = new TestConvention()
         project.conv = expectedValue
@@ -692,7 +757,8 @@ def scriptMethod(Closure closure) {
         assertEquals(expectedValue, child1.conv)
     }
 
-    @Test void testSetPropertyAndPropertyMissingWithProjectAndConventionProperty() {
+    @Test
+    void testSetPropertyAndPropertyMissingWithProjectAndConventionProperty() {
         String propertyName = 'archivesBaseName'
         String expectedValue = 'somename'
 
@@ -704,18 +770,20 @@ def scriptMethod(Closure closure) {
         assertEquals('someothername', project.convention."$propertyName")
     }
 
-    @Test void testPropertyMissingWithNullProperty() {
+    @Test
+    void testPropertyMissingWithNullProperty() {
         project.nullProp = null
         assertNull(project.nullProp)
         assert project.hasProperty('nullProp')
     }
 
-    @Test (expected = MissingPropertyException)
+    @Test(expected = MissingPropertyException)
     public void testPropertyMissingWithUnknownProperty() {
         project.unknownProperty
     }
 
-    @Test void testHasProperty() {
+    @Test
+    void testHasProperty() {
         assertTrue(project.hasProperty('name'))
         String propertyName = 'beginIndex'
         assertFalse(project.hasProperty(propertyName))
@@ -727,11 +795,13 @@ def scriptMethod(Closure closure) {
         assertTrue(child1.hasProperty(propertyName))
     }
 
-    @Test void testProperties() {
+    @Test
+    void testProperties() {
         context.checking {
-            allowing(dependencyMetaDataProviderMock).getModule(); will(returnValue({} as Module))
+            allowing(dependencyMetaDataProviderMock).getModule(); will(returnValue({} as ModuleInternal))
             ignoring(fileOperationsMock)
             ignoring(taskContainerMock)
+            allowing(serviceRegistryMock).get(ServiceRegistryFactory); will(returnValue({} as ServiceRegistryFactory))
         }
         project.additional = 'additional'
 
@@ -741,38 +811,45 @@ def scriptMethod(Closure closure) {
         assertSame(properties['someTask'], testTask)
     }
 
-    @Test void testAdditionalPropertiesAreInheritable() {
+    @Test
+    void testAdditionalPropertiesAreInheritable() {
         project.somename = 'somevalue'
         assertTrue(project.inheritedScope.hasProperty('somename'))
         assertEquals(project.inheritedScope.getProperty('somename'), 'somevalue')
     }
 
-    @Test void testConventionPropertiesAreInheritable() {
+    @Test
+    void testConventionPropertiesAreInheritable() {
         project.convention.plugins.test = new TestConvention()
         project.convention.plugins.test.conv = 'somevalue'
         assertTrue(project.inheritedScope.hasProperty('conv'))
         assertEquals(project.inheritedScope.getProperty('conv'), 'somevalue')
     }
 
-    @Test void testInheritedPropertiesAreInheritable() {
+    @Test
+    void testInheritedPropertiesAreInheritable() {
         project.somename = 'somevalue'
         assertTrue(child1.inheritedScope.hasProperty('somename'))
         assertEquals(child1.inheritedScope.getProperty('somename'), 'somevalue')
     }
 
-    @Test void testGetProjectProperty() {
+    @Test
+    void testGetProjectProperty() {
         assert project.is(project.getProject())
     }
 
-    @Test void testAllprojectsField() {
+    @Test
+    void testAllprojectsField() {
         assertEquals(getListWithAllProjects(), project.allprojects)
     }
 
-    @Test void testChildren() {
+    @Test
+    void testChildren() {
         assertEquals(getListWithAllChildProjects(), project.subprojects)
     }
 
-    @Test void testBuildDir() {
+    @Test
+    void testBuildDir() {
         File dir = new File(rootDir, 'dir')
         context.checking {
             one(fileOperationsMock).file(Project.DEFAULT_BUILD_DIR_NAME)
@@ -788,45 +865,48 @@ def scriptMethod(Closure closure) {
         assertEquals(dir, child1.buildDir)
     }
 
-    @Test public void testDir() {
-        Task dirTask1 = HelperUtil.createTask(Directory.class)
-        Task dirTask12 = HelperUtil.createTask(Directory.class)
-        Task dirTask123 = HelperUtil.createTask(Directory.class)
+    @Test
+    public void testDir() {
+        Task dirTask1 = TestUtil.createTask(Directory.class)
+        Task dirTask12 = TestUtil.createTask(Directory.class)
+        Task dirTask123 = TestUtil.createTask(Directory.class)
         context.checking {
             one(taskContainerMock).findByName('dir1'); will(returnValue(null))
-            one(taskContainerMock).add('dir1', Directory); will(returnValue(dirTask1))
+            one(taskContainerMock).create('dir1', Directory); will(returnValue(dirTask1))
             one(taskContainerMock).findByName('dir1/dir2'); will(returnValue(null))
-            one(taskContainerMock).add('dir1/dir2', Directory); will(returnValue(dirTask12))
+            one(taskContainerMock).create('dir1/dir2', Directory); will(returnValue(dirTask12))
             one(taskContainerMock).findByName('dir1/dir2/dir3'); will(returnValue(null))
-            one(taskContainerMock).add('dir1/dir2/dir3', Directory); will(returnValue(dirTask123))
+            one(taskContainerMock).create('dir1/dir2/dir3', Directory); will(returnValue(dirTask123))
         }
         assertSame(dirTask123, project.dir('dir1/dir2/dir3'));
     }
 
-    @Test public void testDirWithExistingParentDirTask() {
-        Task dirTask1 = HelperUtil.createTask(Directory.class)
+    @Test
+    public void testDirWithExistingParentDirTask() {
+        Task dirTask1 = TestUtil.createTask(Directory.class)
         context.checking {
             one(taskContainerMock).findByName('dir1'); will(returnValue(null))
-            one(taskContainerMock).add('dir1', Directory); will(returnValue(dirTask1))
+            one(taskContainerMock).create('dir1', Directory); will(returnValue(dirTask1))
         }
         project.dir('dir1')
 
-        Task dirTask14 = HelperUtil.createTask(Directory.class)
+        Task dirTask14 = TestUtil.createTask(Directory.class)
         context.checking {
             one(taskContainerMock).findByName('dir1'); will(returnValue(dirTask1))
             one(taskContainerMock).findByName('dir1/dir4'); will(returnValue(null))
-            one(taskContainerMock).add('dir1/dir4', Directory); will(returnValue(dirTask14))
+            one(taskContainerMock).create('dir1/dir4', Directory); will(returnValue(dirTask14))
         }
         assertSame(dirTask14, project.dir('dir1/dir4'))
     }
 
-    @Test public void testDirWithConflictingNonDirTask() {
-        Task dirTask14 = HelperUtil.createTask(DefaultTask.class)
+    @Test
+    public void testDirWithConflictingNonDirTask() {
+        Task dirTask14 = TestUtil.createTask(DefaultTask.class)
 
-        Task dirTask1 = HelperUtil.createTask(Directory.class)
+        Task dirTask1 = TestUtil.createTask(Directory.class)
         context.checking {
             one(taskContainerMock).findByName('dir1'); will(returnValue(null))
-            one(taskContainerMock).add('dir1', Directory); will(returnValue(dirTask1))
+            one(taskContainerMock).create('dir1', Directory); will(returnValue(dirTask1))
             one(taskContainerMock).findByName('dir1/dir4'); will(returnValue(dirTask14))
         }
 
@@ -838,54 +918,64 @@ def scriptMethod(Closure closure) {
         }
     }
 
-    @Test void testCachingOfAnt() {
+    @Test
+    void testCachingOfAnt() {
         assertSame(testAntBuilder, project.ant)
         assert project.ant.is(project.ant)
     }
 
-    @Test void testAnt() {
-        Closure configureClosure = {fileset(dir: 'dir', id: 'fileset')}
+    @Test
+    void testAnt() {
+        Closure configureClosure = { fileset(dir: 'dir', id: 'fileset') }
         project.ant(configureClosure)
         assertThat(project.ant.project.getReference('fileset'), instanceOf(FileSet))
     }
 
-    @Test void testCreateAntBuilder() {
+    @Test
+    void testCreateAntBuilder() {
         assertSame testAntBuilder, project.createAntBuilder()
     }
 
-    @Test void testCompareTo() {
+    @Test
+    void testCompareTo() {
         assertThat(project, lessThan(child1))
         assertThat(child1, lessThan(child2))
         assertThat(child1, lessThan(childchild))
         assertThat(child2, lessThan(childchild))
     }
 
-    @Test void testDepthCompare() {
+    @Test
+    void testDepthCompare() {
         assertTrue(project.depthCompare(child1) < 0)
         assertTrue(child1.depthCompare(project) > 0)
         assertTrue(child1.depthCompare(child2) == 0)
     }
 
-    @Test void testDepth() {
+    @Test
+    void testDepth() {
         assertTrue(project.depth == 0)
         assertTrue(child1.depth == 1)
         assertTrue(child2.depth == 1)
         assertTrue(childchild.depth == 2)
     }
 
-    @Test void testSubprojects() {
+    @Test
+    void testSubprojects() {
         checkConfigureProject('subprojects', listWithAllChildProjects)
     }
 
-    @Test void testAllprojects() {
+    @Test
+    void testAllprojects() {
         checkConfigureProject('allprojects', listWithAllProjects)
     }
 
-    @Test void testConfigureProjects() {
+    @Test
+    void testConfigureProjects() {
         checkConfigureProject('configure', [project, child1] as Set)
     }
 
-    @Test void testHasUsefulToString() {
+    @Test
+    void testHasUsefulToString() {
         assertEquals('root project \'root\'', project.toString())
         assertEquals('project \':child1\'', child1.toString())
         assertEquals('project \':child1:childchild\'', childchild.toString())
@@ -894,15 +984,15 @@ def scriptMethod(Closure closure) {
     private void checkConfigureProject(String configureMethod, Set projectsToCheck) {
         String propValue = 'someValue'
         if (configureMethod == 'configure') {
-            project."$configureMethod" projectsToCheck as List,
+            project."$configureMethod" projectsToCheck as java.util.List,
                     {
                         testSubProp = propValue
                     }
         } else {
             project."$configureMethod"(
-            {
-                testSubProp = propValue
-            })
+                    {
+                        testSubProp = propValue
+                    })
         }
 
         projectsToCheck.each {
@@ -919,38 +1009,44 @@ def scriptMethod(Closure closure) {
         assertEquals(expectedPoint, actualPoint)
     }
 
-    @Test(expected = ReadOnlyPropertyException) void setName() {
+    @Test(expected = ReadOnlyPropertyException)
+    void setName() {
         project.name = "someNewName"
     }
 
-    @Test void testGetModule() {
-        Module moduleDummyResolve = [:] as Module
+    @Test
+    void testGetModule() {
+        ModuleInternal moduleDummyResolve = new ProjectBackedModule(project)
         context.checking {
             allowing(dependencyMetaDataProviderMock).getModule(); will(returnValue(moduleDummyResolve))
         }
         assertThat(project.getModule(), equalTo(moduleDummyResolve))
     }
 
-    @Test void convertsAbsolutePathToAbsolutePath() {
+    @Test
+    void convertsAbsolutePathToAbsolutePath() {
         assertThat(project.absoluteProjectPath(':'), equalTo(':'))
         assertThat(project.absoluteProjectPath(':other'), equalTo(':other'))
         assertThat(child1.absoluteProjectPath(':'), equalTo(':'))
         assertThat(child1.absoluteProjectPath(':other'), equalTo(':other'))
     }
 
-    @Test void convertsRelativePathToAbsolutePath() {
+    @Test
+    void convertsRelativePathToAbsolutePath() {
         assertThat(project.absoluteProjectPath('task'), equalTo(':task'))
         assertThat(project.absoluteProjectPath('sub:other'), equalTo(':sub:other'))
         assertThat(child1.absoluteProjectPath('task'), equalTo(':child1:task'))
         assertThat(child1.absoluteProjectPath('sub:other'), equalTo(':child1:sub:other'))
     }
 
-    @Test void convertsRelativePathToRelativePath() {
+    @Test
+    void convertsRelativePathToRelativePath() {
         assertThat(project.relativeProjectPath('task'), equalTo('task'))
         assertThat(project.relativeProjectPath('sub:other'), equalTo('sub:other'))
     }
 
-    @Test void convertsAbsolutePathToRelativePath() {
+    @Test
+    void convertsAbsolutePathToRelativePath() {
         assertThat(project.relativeProjectPath(':'), equalTo(':'))
         assertThat(project.relativeProjectPath(':task'), equalTo('task'))
         assertThat(project.relativeProjectPath(':sub:other'), equalTo('sub:other'))
@@ -960,7 +1056,8 @@ def scriptMethod(Closure closure) {
         assertThat(child1.relativeProjectPath(':sub:other'), equalTo(':sub:other'))
     }
 
-    @Test void createsADomainObjectContainer() {
+    @Test
+    void createsADomainObjectContainer() {
         def container = context.mock(FactoryNamedDomainObjectContainer)
         context.checking {
             allowing(instantiatorMock).newInstance(withParam(equalTo(FactoryNamedDomainObjectContainer)), withParam(notNullValue()))
@@ -968,14 +1065,14 @@ def scriptMethod(Closure closure) {
         }
         assertThat(project.container(String.class), sameInstance(container))
         assertThat(project.container(String.class, context.mock(NamedDomainObjectFactory.class)), sameInstance(container))
-        assertThat(project.container(String.class, { }), sameInstance(container))
+        assertThat(project.container(String.class, {}), sameInstance(container))
     }
 
 }
 
 class TaskContainerDynamicObject {
     Task someTask
-    
+
     def someTask(Closure closure) {
         closure.call()
     }
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
deleted file mode 100755
index 664b628..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.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.api.internal.project;
-
-import org.gradle.api.internal.*;
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.api.internal.classpath.DefaultPluginModuleRegistry;
-import org.gradle.api.internal.classpath.ModuleRegistry;
-import org.gradle.api.internal.classpath.PluginModuleRegistry;
-import org.gradle.cache.internal.CacheFactory;
-import org.gradle.cache.internal.DefaultCacheFactory;
-import org.gradle.cache.internal.DefaultFileLockManager;
-import org.gradle.cache.internal.FileLockManager;
-import org.gradle.initialization.ClassLoaderRegistry;
-import org.gradle.cli.CommandLineConverter;
-import org.gradle.initialization.DefaultClassLoaderRegistry;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.internal.nativeplatform.*;
-import org.gradle.internal.nativeplatform.filesystem.FileSystem;
-import org.gradle.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.logging.internal.DefaultLoggingManagerFactory;
-import org.gradle.logging.internal.DefaultProgressLoggerFactory;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.util.ClassLoaderFactory;
-import org.gradle.util.DefaultClassLoaderFactory;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertThat;
-
-public class GlobalServicesRegistryTest {
-    private final GlobalServicesRegistry registry = new GlobalServicesRegistry();
-
-    @Test
-    public void providesCommandLineArgsConverter() {
-        assertThat(registry.get(CommandLineConverter.class), instanceOf(
-                DefaultCommandLineConverter.class));
-    }
-
-    @Test
-    public void providesACacheFactoryFactory() {
-        assertThat(registry.getFactory(CacheFactory.class), instanceOf(DefaultCacheFactory.class));
-    }
-
-    @Test
-    public void providesAModuleRegistry() {
-        assertThat(registry.get(ModuleRegistry.class), instanceOf(DefaultModuleRegistry.class));
-    }
-
-    @Test
-    public void providesAPluginModuleRegistry() {
-        assertThat(registry.get(PluginModuleRegistry.class), instanceOf(DefaultPluginModuleRegistry.class));
-    }
-
-    @Test
-    public void providesAClassPathRegistry() {
-        assertThat(registry.get(ClassPathRegistry.class), instanceOf(DefaultClassPathRegistry.class));
-    }
-
-    @Test
-    public void providesAClassLoaderRegistry() {
-        assertThat(registry.get(ClassLoaderRegistry.class), instanceOf(DefaultClassLoaderRegistry.class));
-    }
-
-    @Test
-    public void providesALoggingManagerFactory() {
-        assertThat(registry.getFactory(LoggingManagerInternal.class), instanceOf(DefaultLoggingManagerFactory.class));
-    }
-    
-    @Test
-    public void providesAListenerManager() {
-        assertThat(registry.get(ListenerManager.class), instanceOf(DefaultListenerManager.class));
-    }
-    
-    @Test
-    public void providesAProgressLoggerFactory() {
-        assertThat(registry.get(ProgressLoggerFactory.class), instanceOf(DefaultProgressLoggerFactory.class));
-    }
-    
-    @Test
-    public void providesAGradleDistributionLocator() {
-        assertThat(registry.get(GradleDistributionLocator.class), instanceOf(DefaultModuleRegistry.class));
-    }
-    
-    @Test
-    public void providesAClassLoaderFactory() {
-        assertThat(registry.get(ClassLoaderFactory.class), instanceOf(DefaultClassLoaderFactory.class));
-    }
-
-    @Test
-    public void providesAMessagingServer() {
-        assertThat(registry.get(MessagingServer.class), instanceOf(MessagingServer.class));
-    }
-
-    @Test
-    public void providesAClassGenerator() {
-        assertThat(registry.get(ClassGenerator.class), instanceOf(AsmBackedClassGenerator.class));
-    }
-    
-    @Test
-    public void providesAnInstantiator() {
-        assertThat(registry.get(org.gradle.internal.reflect.Instantiator.class), instanceOf(ClassGeneratorBackedInstantiator.class));
-    }
-
-    @Test
-    public void providesAFileLockManager() {
-        assertThat(registry.get(FileLockManager.class), instanceOf(DefaultFileLockManager.class));
-    }
-
-    @Test
-    public void providesAProcessEnvironment() {
-        assertThat(registry.get(ProcessEnvironment.class), notNullValue());
-    }
-
-    @Test
-    public void providesAFileSystem() {
-        assertThat(registry.get(FileSystem.class), notNullValue());
-    }
-
-    @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
deleted file mode 100644
index 9902974..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.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(StartParameter) >> Mock(StartParameter)
-        parent.get(ListenerManager) >> listenerManager
-        parent.get(CacheRepository) >> 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/NewDefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/NewDefaultProjectTest.groovy
index 7584cac..3c9a0bb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/NewDefaultProjectTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/NewDefaultProjectTest.groovy
@@ -19,15 +19,12 @@ package org.gradle.api.internal.project
 import org.gradle.api.artifacts.dsl.ArtifactHandler
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber
- */
 class NewDefaultProjectTest extends Specification {
 
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
 
     void "delegates to artifacts handler"() {
         def handler = Mock(ArtifactHandler)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.groovy
new file mode 100644
index 0000000..60e5e07
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.project
+
+import org.gradle.api.initialization.ProjectDescriptor
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.groovy.scripts.StringScriptSource
+import org.gradle.groovy.scripts.UriScriptSource
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.scopes.ServiceRegistryFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectFactoryTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    def instantiator = Mock(Instantiator)
+    def projectDescriptor = Stub(ProjectDescriptor)
+    def gradle = Stub(GradleInternal)
+    def serviceRegistryFactory = Stub(ServiceRegistryFactory)
+    def projectRegistry = Mock(ProjectRegistry)
+    def project = Stub(DefaultProject)
+    def factory = new ProjectFactory(instantiator, projectRegistry)
+    def classLoaderScope = Mock(ClassLoaderScope)
+
+    def setup() {
+        gradle.serviceRegistryFactory >> serviceRegistryFactory
+    }
+
+    def "creates a project with build script"() {
+        def buildFile = tmpDir.createFile("build.gradle")
+        def projectDir = tmpDir.createFile("project")
+
+        given:
+        projectDescriptor.name >> "name"
+        projectDescriptor.projectDir >> projectDir
+        projectDescriptor.buildFile >> buildFile
+
+        when:
+        def result = factory.createProject(projectDescriptor, null, gradle, classLoaderScope)
+
+        then:
+        result == project
+        1 * instantiator.newInstance(DefaultProject, "name", null, projectDir, { it instanceof UriScriptSource }, gradle, serviceRegistryFactory, classLoaderScope) >> project
+        1 * projectRegistry.addProject(project)
+    }
+
+    def "creates a project with missing build script"() {
+        def buildFile = tmpDir.file("build.gradle")
+        def projectDir = tmpDir.createFile("project")
+
+        given:
+        projectDescriptor.name >> "name"
+        projectDescriptor.projectDir >> projectDir
+        projectDescriptor.buildFile >> buildFile
+
+        when:
+        def result = factory.createProject(projectDescriptor, null, gradle, classLoaderScope)
+
+        then:
+        result == project
+        1 * instantiator.newInstance(DefaultProject, "name", null, projectDir, { it instanceof StringScriptSource }, gradle, serviceRegistryFactory, classLoaderScope) >> project
+        1 * projectRegistry.addProject(project)
+    }
+
+    def "creates a child project"() {
+        def parent = Mock(ProjectInternal)
+        def buildFile = tmpDir.file("build.gradle")
+        def projectDir = tmpDir.createFile("project")
+
+        given:
+        projectDescriptor.name >> "name"
+        projectDescriptor.projectDir >> projectDir
+        projectDescriptor.buildFile >> buildFile
+
+        when:
+        def result = factory.createProject(projectDescriptor, parent, gradle, classLoaderScope)
+
+        then:
+        result == project
+        1 * instantiator.newInstance(DefaultProject, "name", parent, projectDir, _, gradle, serviceRegistryFactory, classLoaderScope) >> project
+        1 * parent.addChildProject(project)
+        1 * projectRegistry.addProject(project)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
deleted file mode 100644
index 077e2b9..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project;
-
-import org.apache.commons.io.FileUtils;
-import org.gradle.StartParameter;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.initialization.ProjectDescriptor;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.StringScriptSource;
-import org.gradle.groovy.scripts.UriScriptSource;
-import org.gradle.internal.Factory;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.testfixtures.internal.GlobalTestServices;
-import org.gradle.testfixtures.internal.TestTopLevelBuildServiceRegistry;
-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.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class ProjectFactoryTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final MultiParentClassLoader buildScriptClassLoader = new MultiParentClassLoader(getClass().getClassLoader());
-    @Rule
-    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
-    private final File rootDir = testDir.getTestDirectory();
-    private final File projectDir = new File(rootDir, "project");
-    private Factory<RepositoryHandler> repositoryHandlerFactory = context.mock(Factory.class);
-    private RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
-    private StartParameter startParameterStub = new StartParameter();
-    private ServiceRegistryFactory serviceRegistryFactory = new TestTopLevelBuildServiceRegistry(new GlobalTestServices(), startParameterStub, rootDir);
-    private org.gradle.internal.reflect.Instantiator instantiatorMock = serviceRegistryFactory.get(org.gradle.internal.reflect.Instantiator.class);
-    private GradleInternal gradle = context.mock(GradleInternal.class);
-
-    private ProjectFactory projectFactory;
-
-    @Before
-    public void setUp() throws Exception {
-        startParameterStub.setGradleUserHomeDir(testDir.createDir("home"));
-        context.checking(new Expectations() {{
-            allowing(repositoryHandlerFactory).create();
-            will(returnValue(repositoryHandler));
-        }});
-        final ServiceRegistryFactory gradleServices = serviceRegistryFactory.createFor(gradle);
-        context.checking(new Expectations() {{
-            allowing(gradle).getServices();
-            will(returnValue(gradleServices));
-            allowing(gradle).getStartParameter();
-            will(returnValue(startParameterStub));
-            allowing(gradle).getProjectRegistry();
-            will(returnValue(gradleServices.get(IProjectRegistry.class)));
-            allowing(gradle).getScriptClassLoader();
-            will(returnValue(buildScriptClassLoader));
-            allowing(gradle).getGradleUserHomeDir();
-            will(returnValue(new File("gradleUserHomeDir")));
-            ignoring(gradle).getProjectEvaluationBroadcaster();
-        }});
-
-        projectFactory = new ProjectFactory(instantiatorMock);
-    }
-
-    @Test
-    public void testConstructsRootProjectWithBuildFile() throws IOException {
-        File buildFile = new File(rootDir, "build.gradle");
-        FileUtils.writeStringToFile(buildFile, "build");
-        ProjectDescriptor descriptor = descriptor("somename", rootDir, buildFile, "build.gradle");
-
-        DefaultProject project = projectFactory.createProject(descriptor, null, gradle);
-
-        assertEquals("somename", project.getName());
-        assertEquals(buildFile, project.getBuildFile());
-        assertNull(project.getParent());
-        assertSame(rootDir, project.getRootDir());
-        assertSame(rootDir, project.getProjectDir());
-        assertSame(project, project.getRootProject());
-        checkProjectResources(project);
-
-        assertThat(project.getBuildScriptSource(), instanceOf(UriScriptSource.class));
-        assertThat(project.getBuildScriptSource().getDisplayName(), startsWith("build file "));
-        assertThat(project.getBuildScriptSource().getResource().getFile(), equalTo(buildFile));
-    }
-
-    @Test
-    public void testConstructsChildProjectWithBuildFile() throws IOException {
-        File buildFile = new File(projectDir, "build.gradle");
-        FileUtils.writeStringToFile(buildFile, "build");
-
-        ProjectDescriptor rootDescriptor = descriptor("root");
-        ProjectDescriptor parentDescriptor = descriptor("parent");
-        ProjectDescriptor projectDescriptor = descriptor("somename", projectDir, buildFile, "build.gradle");
-
-        DefaultProject rootProject = projectFactory.createProject(rootDescriptor, null, gradle);
-        DefaultProject parentProject = projectFactory.createProject(parentDescriptor, rootProject, gradle);
-
-        DefaultProject project = projectFactory.createProject(projectDescriptor, parentProject, gradle);
-
-        assertEquals("somename", project.getName());
-        assertEquals(buildFile, project.getBuildFile());
-        assertSame(parentProject, project.getParent());
-        assertSame(rootDir, project.getRootDir());
-        assertSame(projectDir, project.getProjectDir());
-        assertSame(rootProject, project.getRootProject());
-        assertSame(project, parentProject.getChildProjects().get("somename"));
-        checkProjectResources(project);
-
-        assertThat(project.getBuildScriptSource(), instanceOf(UriScriptSource.class));
-        assertThat(project.getBuildScriptSource().getDisplayName(), startsWith("build file "));
-        assertThat(project.getBuildScriptSource().getResource().getFile(), equalTo(buildFile));
-    }
-
-    @Test
-    public void testAddsProjectToProjectRegistry() throws IOException {
-        ProjectDescriptor rootDescriptor = descriptor("root");
-        ProjectDescriptor parentDescriptor = descriptor("somename");
-
-        DefaultProject rootProject = projectFactory.createProject(rootDescriptor, null, gradle);
-        DefaultProject project = projectFactory.createProject(parentDescriptor, rootProject, gradle);
-
-        assertThat(gradle.getProjectRegistry().getProject(":somename"), sameInstance((ProjectIdentifier) project));
-    }
-
-    @Test
-    public void testUsesEmptyBuildFileWhenBuildFileIsMissing() {
-
-        DefaultProject rootProject = projectFactory.createProject(descriptor("root"), null, gradle);
-        DefaultProject project = projectFactory.createProject(descriptor("somename", projectDir), rootProject, gradle);
-
-        assertThat(project.getBuildScriptSource(), instanceOf(StringScriptSource.class));
-        assertThat(project.getBuildScriptSource().getDisplayName(), equalTo("empty build file"));
-        assertThat(project.getBuildScriptSource().getResource().getText(), equalTo(""));
-    }
-
-    @Test
-    public void testConstructsRootProjectWithEmbeddedBuildScript() {
-
-
-        ProjectFactory projectFactory = new ProjectFactory(instantiatorMock);
-
-        DefaultProject project = projectFactory.createProject(descriptor("somename"), null, gradle);
-
-        assertEquals("somename", project.getName());
-        assertSame(rootDir, project.getRootDir());
-        assertSame(rootDir, project.getProjectDir());
-        assertNull(project.getParent());
-        assertSame(project, project.getRootProject());
-        assertNotNull(project.getConvention());
-        checkProjectResources(project);
-
-    }
-
-    private ProjectDescriptor descriptor(String name) {
-        return descriptor(name, rootDir);
-    }
-
-    private ProjectDescriptor descriptor(String name, File projectDir) {
-        return descriptor(name, projectDir, new File(projectDir, "build.gradle"), "build.gradle");
-    }
-
-    private ProjectDescriptor descriptor(final String name, final File projectDir, final File buildFile,
-                                         final String buildFileName) {
-        final ProjectDescriptor descriptor = context.mock(ProjectDescriptor.class, name);
-
-        context.checking(new Expectations() {{
-            allowing(descriptor).getName();
-            will(returnValue(name));
-            allowing(descriptor).getProjectDir();
-            will(returnValue(projectDir));
-            allowing(descriptor).getBuildFile();
-            will(returnValue(buildFile));
-            allowing(descriptor).getBuildFileName();
-            will(returnValue(buildFileName));
-        }});
-        return descriptor;
-    }
-
-    private void checkProjectResources(DefaultProject project) {
-        assertSame(gradle, project.getGradle());
-    }
-}
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
deleted file mode 100644
index 2894370..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project;
-
-import org.gradle.api.AntBuilder;
-import org.gradle.api.artifacts.dsl.ArtifactHandler;
-import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.initialization.dsl.ScriptHandler;
-import org.gradle.api.internal.*;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.internal.artifacts.DependencyManagementServices;
-import org.gradle.api.internal.artifacts.DependencyResolutionServices;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
-import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.api.internal.file.*;
-import org.gradle.api.internal.initialization.DefaultScriptHandler;
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
-import org.gradle.api.internal.plugins.DefaultProjectsPluginContainer;
-import org.gradle.api.internal.plugins.PluginRegistry;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
-import org.gradle.api.internal.tasks.TaskContainerInternal;
-import org.gradle.api.logging.LoggingManager;
-import org.gradle.api.plugins.PluginContainer;
-import org.gradle.initialization.ProjectAccessListener;
-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;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class ProjectInternalServiceRegistryTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final ConfigurationContainerInternal configurationContainer = context.mock(ConfigurationContainerInternal.class);
-    private final GradleInternal gradle = context.mock(GradleInternal.class);
-    private final DependencyManagementServices dependencyManagementServices = context.mock(DependencyManagementServices.class);
-    private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
-    private final DependencyFactory dependencyFactory = context.mock(DependencyFactory.class);
-    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-    private final ProjectInternalServiceRegistry registry = new ProjectInternalServiceRegistry(parent, project);
-    private final PluginRegistry pluginRegistry = context.mock(PluginRegistry.class);
-    private final DependencyResolutionServices dependencyResolutionServices = context.mock(DependencyResolutionServices.class);
-    private final RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
-    private final ArtifactPublicationServices publicationServices = context.mock(ArtifactPublicationServices.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() {
-        context.checking(new Expectations() {{
-            allowing(project).getGradle();
-            will(returnValue(gradle));
-            allowing(project).getProjectDir();
-            will(returnValue(new File("project-dir").getAbsoluteFile()));
-            allowing(project).getBuildScriptSource();
-            allowing(parent).get(ITaskFactory.class);
-            will(returnValue(taskFactory));
-            allowing(parent).get(DependencyFactory.class);
-            will(returnValue(dependencyFactory));
-            allowing(parent).get(PluginRegistry.class);
-            will(returnValue(pluginRegistry));
-            allowing(parent).get(DependencyManagementServices.class);
-            will(returnValue(dependencyManagementServices));
-            allowing(parent).get(org.gradle.internal.reflect.Instantiator.class);
-            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)));
-            allowing(parent).get(ProjectAccessListener.class);
-            will(returnValue(context.mock(ProjectAccessListener.class)));
-        }});
-    }
-
-    @Test
-    public void createsARegistryForATask() {
-        ServiceRegistryFactory taskRegistry = registry.createFor(context.mock(TaskInternal.class));
-        assertThat(taskRegistry, instanceOf(TaskInternalServiceRegistry.class));
-    }
-
-    @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));
-    }
-
-    @Test
-    public void providesAPluginContainer() {
-        expectScriptClassLoaderProviderCreated();
-        context.checking(new Expectations() {{
-            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));
-        assertThat(registry.get(PluginContainer.class), sameInstance(registry.get(PluginContainer.class)));
-    }
-
-    @Test
-    public void providesAnArtifactPublicationServicesFactory() {
-        expectDependencyResolutionServicesCreated();
-
-        assertThat(registry.get(ArtifactPublicationServices.class), sameInstance(publicationServices));
-    }
-
-    @Test
-    public void providesARepositoryHandler() {
-        expectDependencyResolutionServicesCreated();
-
-        assertThat(registry.get(RepositoryHandler.class), sameInstance(repositoryHandler));
-        assertThat(registry.get(RepositoryHandler.class), sameInstance(registry.get(RepositoryHandler.class)));
-    }
-
-    @Test
-    public void providesAConfigurationContainer() {
-        expectDependencyResolutionServicesCreated();
-
-        assertThat(registry.get(ConfigurationContainerInternal.class), sameInstance(configurationContainer));
-        assertThat(registry.get(ConfigurationContainerInternal.class), sameInstance(registry.get(ConfigurationContainerInternal.class)));
-    }
-
-    @Test
-    public void providesAnArtifactHandler() {
-        expectDependencyResolutionServicesCreated();
-
-        assertThat(registry.get(ArtifactHandler.class), sameInstance(artifactHandler));
-        assertThat(registry.get(ArtifactHandler.class), sameInstance(registry.get(ArtifactHandler.class)));
-    }
-
-    @Test
-    public void providesADependencyHandler() {
-        expectDependencyResolutionServicesCreated();
-
-        assertThat(registry.get(DependencyHandler.class), sameInstance(dependencyHandler));
-        assertThat(registry.get(DependencyHandler.class), sameInstance(registry.get(DependencyHandler.class)));
-    }
-
-    @Test
-    public void providesAnAntBuilderFactory() {
-        assertThat(registry.getFactory(AntBuilder.class), instanceOf(DefaultAntBuilderFactory.class));
-        assertThat(registry.getFactory(AntBuilder.class), sameInstance((Object) registry.getFactory(AntBuilder.class)));
-    }
-
-    @Test
-    public void providesAScriptHandlerAndScriptClassLoaderProvider() {
-        expectScriptClassLoaderProviderCreated();
-
-        assertThat(registry.get(ScriptHandler.class), instanceOf(DefaultScriptHandler.class));
-        assertThat(registry.get(ScriptHandler.class), sameInstance(registry.get(ScriptHandler.class)));
-        assertThat(registry.get(ScriptClassLoaderProvider.class), sameInstance((Object) registry.get(
-                ScriptHandler.class)));
-    }
-
-    @Test
-    public void providesAFileResolver() {
-        assertThat(registry.get(FileResolver.class), instanceOf(BaseDirFileResolver.class));
-        assertThat(registry.get(FileResolver.class), sameInstance(registry.get(FileResolver.class)));
-    }
-
-    @Test
-    public void providesAFileOperationsInstance() {
-        context.checking(new Expectations(){{
-            one(project).getTasks();
-        }});
-
-        assertThat(registry.get(FileOperations.class), instanceOf(DefaultFileOperations.class));
-        assertThat(registry.get(FileOperations.class), sameInstance(registry.get(FileOperations.class)));
-    }
-    
-    @Test
-    public void providesATemporaryFileProvider() {
-        assertThat(registry.get(TemporaryFileProvider.class), instanceOf(DefaultTemporaryFileProvider.class));
-        assertThat(registry.get(TemporaryFileProvider.class), sameInstance(registry.get(TemporaryFileProvider.class)));
-    }
-    
-    @Test
-    public void providesALoggingManager() {
-        final Factory<LoggingManagerInternal> loggingManagerFactory = context.mock(Factory.class);
-        final LoggingManager loggingManager = context.mock(LoggingManagerInternal.class);
-
-        context.checking(new Expectations(){{
-            allowing(parent).getFactory(LoggingManagerInternal.class);
-            will(returnValue(loggingManagerFactory));
-            one(loggingManagerFactory).create();
-            will(returnValue(loggingManager));
-        }});
-
-        assertThat(registry.get(LoggingManager.class), sameInstance(loggingManager));
-        assertThat(registry.get(LoggingManager.class), sameInstance(registry.get(LoggingManager.class)));
-    }
-
-    private void expectScriptClassLoaderProviderCreated() {
-        context.checking(new Expectations() {{
-            one(dependencyManagementServices).create(with(notNullValue(FileResolver.class)),
-                    with(notNullValue(DependencyMetaDataProvider.class)),
-                    with(notNullValue(ProjectFinder.class)),
-                    with(notNullValue(DomainObjectContext.class)));
-            will(returnValue(dependencyResolutionServices));
-
-            ignoring(dependencyResolutionServices);
-
-            allowing(project).getParent();
-            will(returnValue(null));
-
-            allowing(gradle).getScriptClassLoader();
-            will(returnValue(null));
-        }});
-    }
-
-    private void expectDependencyResolutionServicesCreated() {
-        context.checking(new Expectations(){{
-            one(dependencyManagementServices).create(with(notNullValue(FileResolver.class)),
-                    with(notNullValue(DependencyMetaDataProvider.class)),
-                    with(notNullValue(ProjectFinder.class)),
-                    with(notNullValue(DomainObjectContext.class)));
-            will(returnValue(dependencyResolutionServices));
-
-            allowing(dependencyResolutionServices).getResolveRepositoryHandler();
-            will(returnValue(repositoryHandler));
-
-            allowing(dependencyResolutionServices).createArtifactPublicationServices();
-            will(returnValue(publicationServices));
-
-            allowing(dependencyResolutionServices).getConfigurationContainer();
-            will(returnValue(configurationContainer));
-
-            allowing(dependencyResolutionServices).getDependencyHandler();
-            will(returnValue(dependencyHandler));
-
-            allowing(dependencyResolutionServices).getArtifactHandler();
-            will(returnValue(artifactHandler));
-        }});
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskExecutionServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskExecutionServicesTest.groovy
deleted file mode 100644
index a73b72b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskExecutionServicesTest.groovy
+++ /dev/null
@@ -1,54 +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.project
-
-import spock.lang.Specification
-import org.gradle.api.internal.tasks.TaskExecuter
-import org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter
-import org.gradle.listener.ListenerManager
-import org.gradle.cache.CacheRepository
-import org.gradle.StartParameter
-import org.gradle.api.invocation.Gradle
-import org.gradle.cache.DirectoryCacheBuilder
-import org.gradle.cache.PersistentCache
-import org.gradle.internal.service.ServiceRegistry
-
-class TaskExecutionServicesTest extends Specification {
-    final ServiceRegistry parent = Mock()
-    final Gradle gradle = Mock()
-    final TaskExecutionServices services = new TaskExecutionServices(parent, gradle)
-
-    def "makes a TaskExecutor available"() {
-        given:
-        ListenerManager listenerManager = Mock()
-        StartParameter startParameter = Mock()
-        CacheRepository cacheRepository = Mock()
-        DirectoryCacheBuilder cacheBuilder = Mock()
-        PersistentCache cache = Mock()
-        _ * parent.get(ListenerManager) >> listenerManager
-        _ * parent.get(StartParameter) >> startParameter
-        _ * parent.get(CacheRepository) >> cacheRepository
-        _ * cacheRepository.cache(!null) >> cacheBuilder
-        _ * cacheBuilder.forObject(gradle) >> cacheBuilder
-        _ * cacheBuilder.withDisplayName(!null) >> cacheBuilder
-        _ * cacheBuilder.withLockMode(!null) >> cacheBuilder
-        _ * cacheBuilder.open() >> cache
-
-        expect:
-        services.get(TaskExecuter) instanceof ExecuteAtMostOnceTaskExecuter
-        services.get(TaskExecuter).is(services.get(TaskExecuter))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
deleted file mode 100644
index 1844816..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project;
-
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.tasks.DefaultTaskInputs;
-import org.gradle.api.internal.tasks.DefaultTaskOutputs;
-import org.gradle.api.internal.tasks.TaskStatusNagger;
-import org.gradle.api.logging.LoggingManager;
-import org.gradle.api.tasks.TaskInputs;
-import org.gradle.internal.Factory;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.logging.LoggingManagerInternal;
-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 junit.framework.Assert.assertSame;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class TaskInternalServiceRegistryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final TaskInternal task = context.mock(TaskInternal.class);
-    private final TaskInternalServiceRegistry registry = new TaskInternalServiceRegistry(parent, project, task);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(project).getFileResolver();
-            will(returnValue(context.mock(FileResolver.class)));
-        }});
-    }
-
-    @Test
-    public void createsATaskInputsInstance() {
-        TaskInputs inputs = registry.get(TaskInputs.class);
-        assertThat(inputs, instanceOf(DefaultTaskInputs.class));
-    }
-
-    @Test
-    public void createsATaskOutputsInternalInstance() {
-        TaskOutputsInternal outputs = registry.get(TaskOutputsInternal.class);
-        assertThat(outputs, instanceOf(DefaultTaskOutputs.class));
-    }
-
-    @Test
-    public void createsATaskStatusNaggerInstance() {
-        TaskStatusNagger nagger = registry.get(TaskStatusNagger.class);
-        assertSame(nagger, registry.get(TaskStatusNagger.class));
-    }
-
-    @Test
-    public void createsALoggingManagerAndStdOutputCapture() {
-        final Factory<LoggingManagerInternal> loggingManagerFactory = context.mock(Factory.class);
-        final LoggingManager loggingManager = context.mock(LoggingManagerInternal.class);
-
-        context.checking(new Expectations() {{
-            allowing(parent).getFactory(LoggingManagerInternal.class);
-            will(returnValue(loggingManagerFactory));
-            one(loggingManagerFactory).create();
-            will(returnValue(loggingManager));
-        }});
-
-        assertThat(registry.get(LoggingManager.class), sameInstance(loggingManager));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy
index 3dc70b5..6dc5e3d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy
@@ -19,9 +19,6 @@ package org.gradle.api.internal.project
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 
-/**
-* @author Hans Dockter
-*/
 class TestPlugin1 implements Plugin<Project> {
 
     int applyCounter = 0
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy
index b68f716..87bde21 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy
@@ -19,9 +19,6 @@ package org.gradle.api.internal.project
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 
-/**
-* @author Hans Dockter
-*/
 class TestPlugin2 implements Plugin<Project> {
     void apply(Project project) {
     }
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
deleted file mode 100644
index 6cc5a13..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.project
-
-import org.gradle.StartParameter
-import org.gradle.api.internal.*
-import org.gradle.api.internal.classpath.DefaultModuleRegistry
-import org.gradle.api.internal.classpath.ModuleRegistry
-import org.gradle.api.internal.classpath.PluginModuleRegistry
-import org.gradle.cache.CacheRepository
-import org.gradle.cache.internal.CacheFactory
-import org.gradle.cache.internal.DefaultCacheRepository
-import org.gradle.configuration.BuildConfigurer
-import org.gradle.configuration.DefaultBuildConfigurer
-import org.gradle.configuration.DefaultScriptPluginFactory
-import org.gradle.configuration.ScriptPluginFactory
-import org.gradle.groovy.scripts.DefaultScriptCompilerFactory
-import org.gradle.groovy.scripts.ScriptCompilerFactory
-import org.gradle.initialization.*
-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.messaging.remote.MessagingServer
-import org.gradle.process.internal.DefaultWorkerProcessFactory
-import org.gradle.process.internal.WorkerProcessBuilder
-import org.gradle.profile.ProfileEventAdapter
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.ClassLoaderFactory
-import org.gradle.util.MultiParentClassLoader
-import org.junit.Rule
-import spock.lang.Specification
-import spock.lang.Timeout
-
-import static org.hamcrest.Matchers.instanceOf
-import static org.hamcrest.Matchers.sameInstance
-import static org.junit.Assert.assertThat
-
-public class TopLevelBuildServiceRegistryTest extends Specification {
-    @Rule
-    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    StartParameter startParameter = new StartParameter()
-    ServiceRegistry parent = Mock()
-    Factory<CacheFactory> cacheFactoryFactory = Mock()
-    ClosableCacheFactory cacheFactory = Mock()
-    ClassLoaderRegistry classLoaderRegistry = Mock()
-
-    TopLevelBuildServiceRegistry registry = new TopLevelBuildServiceRegistry(parent, startParameter)
-
-    def setup() {
-        startParameter.gradleUserHomeDir = tmpDir.testDirectory
-        parent.getFactory(CacheFactory) >> cacheFactoryFactory
-        cacheFactoryFactory.create() >> cacheFactory
-        parent.get(ClassLoaderRegistry) >> classLoaderRegistry
-        parent.getFactory(LoggingManagerInternal) >> Mock(Factory)
-        parent.get(ModuleRegistry) >> new DefaultModuleRegistry()
-        parent.get(PluginModuleRegistry) >> Mock(PluginModuleRegistry)
-        parent.get(Instantiator) >> ThreadGlobalInstantiator.getOrCreate()
-    }
-
-    def delegatesToParentForUnknownService() {
-        setup:
-        parent.get(String) >> "value"
-
-        expect:
-        registry.get(String) == "value"
-    }
-
-    def throwsExceptionForUnknownDomainObject() {
-        when:
-        registry.createFor("string")
-        then:
-        def e = thrown(IllegalArgumentException)
-        e.message == "Cannot create services for unknown domain object of type String."
-    }
-
-    def canCreateServicesForAGradleInstance() {
-        setup:
-        GradleInternal gradle = Mock()
-        ServiceRegistryFactory registry = this.registry.createFor(gradle)
-        expect:
-        registry instanceof GradleInternalServiceRegistry
-    }
-
-    def providesAListenerManager() {
-        setup:
-        ListenerManager listenerManager = expectListenerManagerCreated()
-        expect:
-        assertThat(registry.get(ListenerManager), sameInstance(listenerManager))
-    }
-
-    @Timeout(5)
-    def providesAScriptCompilerFactory() {
-        setup:
-        expectListenerManagerCreated()
-
-        expect:
-        registry.get(ScriptCompilerFactory) instanceof DefaultScriptCompilerFactory
-        registry.get(ScriptCompilerFactory) == registry.get(ScriptCompilerFactory)
-    }
-
-    def providesACacheRepositoryAndCleansUpOnClose() {
-        setup:
-        1 * cacheFactory.close()
-
-        expect:
-        registry.get(CacheRepository) instanceof DefaultCacheRepository
-        registry.get(CacheRepository) == registry.get(CacheRepository)
-        registry.close()
-    }
-
-    def providesAnInitScriptHandler() {
-        setup:
-        allowGetCoreImplClassLoader()
-        expectScriptClassLoaderCreated()
-        expectListenerManagerCreated()
-        allowGetGradleDistributionLocator()
-
-        expect:
-        registry.get(InitScriptHandler) instanceof InitScriptHandler
-        registry.get(InitScriptHandler) == registry.get(InitScriptHandler)
-    }
-
-    def providesAScriptObjectConfigurerFactory() {
-        setup:
-        allowGetCoreImplClassLoader()
-        expectListenerManagerCreated()
-        expectScriptClassLoaderCreated()
-        expect:
-        assertThat(registry.get(ScriptPluginFactory), instanceOf(DefaultScriptPluginFactory))
-        assertThat(registry.get(ScriptPluginFactory), sameInstance(registry.get(ScriptPluginFactory)))
-    }
-
-    def providesASettingsProcessor() {
-        setup:
-        allowGetCoreImplClassLoader()
-        expectListenerManagerCreated()
-        expectScriptClassLoaderCreated()
-        expect:
-        assertThat(registry.get(SettingsProcessor), instanceOf(PropertiesLoadingSettingsProcessor))
-        assertThat(registry.get(SettingsProcessor), sameInstance(registry.get(SettingsProcessor)))
-    }
-
-    def providesAnExceptionAnalyser() {
-        setup:
-        expectListenerManagerCreated()
-        expect:
-        assertThat(registry.get(ExceptionAnalyser), instanceOf(MultipleBuildFailuresExceptionAnalyser))
-        assertThat(registry.get(ExceptionAnalyser).delegate, instanceOf(DefaultExceptionAnalyser))
-        assertThat(registry.get(ExceptionAnalyser), sameInstance(registry.get(ExceptionAnalyser)))
-    }
-
-    def providesAWorkerProcessFactory() {
-        setup:
-        expectParentServiceLocated(MessagingServer)
-        allowGetCoreImplClassLoader()
-
-        expect:
-        assertThat(registry.getFactory(WorkerProcessBuilder), instanceOf(DefaultWorkerProcessFactory))
-    }
-
-    def providesAnIsolatedAntBuilder() {
-        setup:
-        expectParentServiceLocated(ClassLoaderFactory)
-        allowGetCoreImplClassLoader()
-        expect:
-
-        assertThat(registry.get(IsolatedAntBuilder), instanceOf(DefaultIsolatedAntBuilder))
-        assertThat(registry.get(IsolatedAntBuilder), sameInstance(registry.get(IsolatedAntBuilder)))
-    }
-
-    def providesAProjectFactory() {
-        setup:
-        expectParentServiceLocated(Instantiator)
-        expectParentServiceLocated(ClassGenerator)
-        expect:
-        assertThat(registry.get(IProjectFactory), instanceOf(ProjectFactory))
-        assertThat(registry.get(IProjectFactory), sameInstance(registry.get(IProjectFactory)))
-    }
-
-    def providesAnExecutorFactory() {
-        expect:
-        assertThat(registry.get(ExecutorFactory), instanceOf(DefaultExecutorFactory))
-        assertThat(registry.get(ExecutorFactory), sameInstance(registry.get(ExecutorFactory)))
-    }
-
-    def providesABuildConfigurer() {
-        expect:
-        assertThat(registry.get(BuildConfigurer), instanceOf(DefaultBuildConfigurer))
-        assertThat(registry.get(BuildConfigurer), sameInstance(registry.get(BuildConfigurer)))
-    }
-
-    def providesAPropertiesLoader() {
-        expect:
-        assertThat(registry.get(IGradlePropertiesLoader), instanceOf(DefaultGradlePropertiesLoader))
-        assertThat(registry.get(IGradlePropertiesLoader), sameInstance(registry.get(IGradlePropertiesLoader)))
-    }
-
-    def providesABuildLoader() {
-        setup:
-        expectParentServiceLocated(Instantiator)
-        expect:
-        assertThat(registry.get(BuildLoader), instanceOf(ProjectPropertySettingBuildLoader))
-        assertThat(registry.get(BuildLoader), sameInstance(registry.get(BuildLoader)))
-    }
-
-    def providesAProfileEventAdapter() {
-        setup:
-        expectParentServiceLocated(BuildRequestMetaData)
-        expectListenerManagerCreated()
-
-        expect:
-        assertThat(registry.get(ProfileEventAdapter), instanceOf(ProfileEventAdapter))
-        assertThat(registry.get(ProfileEventAdapter), sameInstance(registry.get(ProfileEventAdapter)))
-    }
-
-    private <T> T expectParentServiceLocated(Class<T> type) {
-        T t = Mock(type)
-        parent.get(type) >> t
-        t
-    }
-
-    private ListenerManager expectListenerManagerCreated() {
-        final ListenerManager listenerManager = new DefaultListenerManager()
-        final ListenerManager listenerManagerParent = Mock()
-        parent.get(ListenerManager) >> listenerManagerParent
-        1 * listenerManagerParent.createChild() >> listenerManager
-        listenerManager
-    }
-
-    private void allowGetCoreImplClassLoader() {
-        classLoaderRegistry.getCoreImplClassLoader() >> new ClassLoader() {}
-    }
-
-    private void expectScriptClassLoaderCreated() {
-        1 * classLoaderRegistry.createScriptClassLoader() >> new MultiParentClassLoader()
-    }
-
-    private void allowGetGradleDistributionLocator() {
-        parent.get(GradleDistributionLocator) >> Mock(GradleDistributionLocator)
-    }
-
-    public interface ClosableCacheFactory extends CacheFactory {
-        void close()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
index 032e97d..f5f6fe0 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.project.taskfactory;
 
+import org.gradle.api.Action;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.Task;
@@ -24,11 +25,12 @@ import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.internal.UncheckedException;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.util.TestUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -36,8 +38,10 @@ import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import spock.lang.Issue;
 
 import java.io.File;
+import java.lang.reflect.Field;
 import java.util.*;
 import java.util.concurrent.Callable;
 
@@ -77,7 +81,7 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     private <T extends Task> T expectTaskCreated(final Class<T> type, final Object... params) {
-        DefaultProject project = HelperUtil.createRootProject();
+        DefaultProject project = TestUtil.createRootProject();
         T task = AbstractTask.injectIntoNewInstance(project, "task", new Callable<T>() {
             public T call() throws Exception {
                 if (params.length > 0) {
@@ -139,11 +143,29 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void createsContextualActionFoIncrementalTaskAction() {
+        final Action<IncrementalTaskInputs> action = context.mock(Action.class);
+        TaskWithIncrementalAction task = expectTaskCreated(TaskWithIncrementalAction.class, action);
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(IncrementalTaskInputs.class)));
+        }});
+
+        task.execute();
+    }
+
+    @Test
+    public void failsWhenMultipleActionsAreIncremental() {
+        assertTaskCreationFails(TaskWithMultipleIncrementalActions.class,
+                "Cannot have multiple @TaskAction methods accepting an IncrementalTaskInputs parameter.");
+    }
+
+    @Test
     public void cachesClassMetaInfo() {
         TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingFile);
         TaskWithInputFile task2 = expectTaskCreated(TaskWithInputFile.class, missingFile);
 
-        assertThat(ReflectionUtil.getProperty(task.getActions().get(0), "action"), sameInstance(ReflectionUtil.getProperty(task2.getActions().get(0), "action")));
+        assertThat(readField(task.getActions().get(0), Action.class, "action"), sameInstance(readField(task2.getActions().get(0), Action.class, "action")));
     }
     
     @Test
@@ -154,8 +176,14 @@ public class AnnotationProcessingTaskFactoryTest {
 
     @Test
     public void failsWhenMethodWithParametersHasTaskActionAnnotation() {
-        assertTaskCreationFails(TaskWithParamMethod.class,
-                "Cannot use @TaskAction annotation on method TaskWithParamMethod.doStuff() as this method takes parameters.");
+        assertTaskCreationFails(TaskWithMultiParamAction.class,
+                "Cannot use @TaskAction annotation on method TaskWithMultiParamAction.doStuff() as this method takes multiple parameters.");
+    }
+
+    @Test
+    public void failsWhenMethodWithInvalidParameterHasTaskActionAnnotation() {
+        assertTaskCreationFails(TaskWithSingleParamAction.class,
+                "Cannot use @TaskAction annotation on method TaskWithSingleParamAction.doStuff() because int is not a valid parameter to an action method.");
     }
 
     private void assertTaskCreationFails(Class<? extends Task> type, String message) {
@@ -565,6 +593,13 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    @Issue("http://issues.gradle.org/browse/GRADLE-2815")
+    public void registersSpecifiedBooleanInputValue() {
+        TaskWithBooleanInput task = expectTaskCreated(TaskWithBooleanInput.class, true);
+        assertThat(task.getInputs().getProperties().get("inputValue"), equalTo((Object) true));
+    }
+
+    @Test
     public void validationActionSucceedsWhenPropertyMarkedWithOptionalAnnotationNotSpecified() {
         TaskWithOptionalInputFile task = expectTaskCreated(TaskWithOptionalInputFile.class);
         task.execute();
@@ -664,6 +699,26 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static <T> T readField(Object target, Class<T> type, String name) {
+        Class<?> objectType = target.getClass();
+        while (objectType != null) {
+            try {
+                Field field = objectType.getDeclaredField(name);
+                field.setAccessible(true);
+                return (T) field.get(target);
+            } catch (NoSuchFieldException ignore) {
+                // ignore
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+
+            objectType = objectType.getSuperclass();
+        }
+
+        throw new RuntimeException("Could not find field '" + name + "' with type '" + type.getClass() + "' on class '" + target.getClass() + "'");
+    }
+
+
     public static class TestTask extends DefaultTask {
         final Runnable action;
 
@@ -732,9 +787,39 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
-    public static class TaskWithParamMethod extends DefaultTask {
+    public static class TaskWithIncrementalAction extends DefaultTask {
+        private final Action<IncrementalTaskInputs> action;
+
+        public TaskWithIncrementalAction(Action<IncrementalTaskInputs> action) {
+            this.action = action;
+        }
+
+        @TaskAction
+        public void doStuff(IncrementalTaskInputs changes) {
+            action.execute(changes);
+        }
+    }
+
+    public static class TaskWithMultipleIncrementalActions extends DefaultTask {
+
+        @TaskAction
+        public void doStuff(IncrementalTaskInputs changes) {
+        }
+
+        @TaskAction
+        public void doStuff2(IncrementalTaskInputs changes) {
+        }
+    }
+
+    public static class TaskWithSingleParamAction extends DefaultTask {
         @TaskAction
-        public void doStuff(int value) {
+        public void doStuff(int value1) {
+        }
+    }
+
+    public static class TaskWithMultiParamAction extends DefaultTask {
+        @TaskAction
+        public void doStuff(int value1, int value2) {
         }
     }
 
@@ -777,6 +862,19 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static class TaskWithBooleanInput extends DefaultTask {
+        boolean inputValue;
+
+        public TaskWithBooleanInput(boolean inputValue) {
+            this.inputValue = inputValue;
+        }
+
+        @Input
+        public boolean isInputValue() {
+            return inputValue;
+        }
+    }
+
     public static class BrokenTaskWithInputDir extends TaskWithInputDir {
         public BrokenTaskWithInputDir(File inputDir) {
             super(inputDir);
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
index eb83288..f5517b7 100644
--- 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
@@ -25,13 +25,13 @@ 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 org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class TaskFactoryTest extends Specification {
     final ClassGenerator generator = Mock()
     final Instantiator instantiator = Mock()
-    final ProjectInternal project = HelperUtil.createRootProject()
+    final ProjectInternal project = TestUtil.createRootProject()
     final ITaskFactory taskFactory = new TaskFactory(generator).createChild(project, instantiator)
 
     def setup() {
@@ -94,6 +94,23 @@ class TaskFactoryTest extends Specification {
         task.dependsOn == ["/path1"] as Set
     }
 
+    public void taskCreationFailsWithUnknownArguments() {
+        when:
+        taskFactory.createTask([name: 'task', dependson: 'anotherTask'])
+
+        then:
+        InvalidUserDataException exception = thrown()
+        exception.message == "Could not create task 'task': Unknown argument(s) in task definition: [dependson]"
+
+        when:
+        taskFactory.createTask([name: 'task', Type: NotATask])
+
+        then:
+        exception = thrown()
+        exception.message == "Could not create task 'task': Unknown argument(s) in task definition: [Type]"
+
+    }
+
     public void testCreateTaskWithAction() {
         Action<Task> action = Mock()
 
@@ -118,7 +135,7 @@ class TaskFactoryTest extends Specification {
 
     public void testCreateTaskForTypeWhichDoesNotImplementTask() {
         when:
-        taskFactory.createTask([name: 'task', type:  NotATask])
+        taskFactory.createTask([name: 'task', type: NotATask])
 
         then:
         InvalidUserDataException e = thrown()
@@ -129,7 +146,7 @@ class TaskFactoryTest extends Specification {
         def failure = new RuntimeException()
 
         when:
-        taskFactory.createTask([name: 'task', type:  TestDefaultTask])
+        taskFactory.createTask([name: 'task', type: TestDefaultTask])
 
         then:
         TaskInstantiationException e = thrown()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
index dc2fb84..beb8119 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
@@ -19,7 +19,8 @@ package org.gradle.api.internal.resource
 
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.testing.internal.util.Network
+import org.gradle.util.TestPrecondition
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -150,7 +151,7 @@ class UriResourceTest {
 
     @Test
     public void hasNoContentWhenUsingHttpUriAndFileDoesNotExist() {
-        if (Network.offline) { return } // when this test moves to spock, ignore this test instead of just passing.
+        Assume.assumeTrue(TestPrecondition.ONLINE.fulfilled) // when this test moves to spock, ignore this test instead of just passing.
 
         UriResource resource = new UriResource('<display-name>', new URI("http://www.gradle.org/unknown.txt"));
         assertFalse(resource.exists)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy
index cb11589..1cdb045 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.api.internal.resources;
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 12/13/11
- */
 public class URIBuilderTest extends Specification {
 
     def "builds URIs"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy
index d1ca77b..7d08c1b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-package org.gradle.api.internal.tasks;
+package org.gradle.api.internal.tasks
 
-
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.Rule
-import org.gradle.api.Task
-import org.gradle.api.UnknownTaskException
+import org.gradle.api.*
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.project.taskfactory.ITaskFactory
@@ -28,7 +24,6 @@ import org.gradle.api.tasks.TaskDependency
 import org.gradle.initialization.ProjectAccessListener
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.util.GUtil
-import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 import static java.util.Collections.singletonMap
@@ -76,7 +71,7 @@ public class DefaultTaskContainerTest extends Specification {
 
     void "adds by name and closure"() {
         given:
-        final Closure action = HelperUtil.toClosure("{ description = 'description' }")
+        final Closure action = {}
         def options = singletonMap(Task.TASK_NAME, "task")
         def task = task("task")
 
@@ -90,6 +85,38 @@ public class DefaultTaskContainerTest extends Specification {
         1 * task.configure(action) >> task
     }
 
+    void "creates by name and closure"() {
+        given:
+        final Closure action = {}
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+
+        taskFactory.createTask(options) >> task
+
+        when:
+        def added = container.create("task", action)
+
+        then:
+        added == task
+        1 * task.configure(action) >> task
+    }
+
+    void "creates by name and action"() {
+        given:
+        def action = Mock(Action)
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+
+        taskFactory.createTask(options) >> task
+
+        when:
+        def added = container.create("task", action)
+
+        then:
+        added == task
+        1 * action.execute(task)
+    }
+
     void "replaces task by name"() {
         given:
         def options = singletonMap(Task.TASK_NAME, "task")
@@ -272,13 +299,87 @@ public class DefaultTaskContainerTest extends Specification {
         aTaskDependency.getDependencies(task) >> { container.add("b"); Collections.singleton(b) }
         task.dependsOn("b")
 
+        addPlaceholderTask("c")
+
         assert container.size() == 1
 
         when:
         container.actualize()
 
         then:
-        container.size() == 2
+        container.size() == 3
+    }
+
+    void "can add task via placeholder action"() {
+        when:
+        addPlaceholderTask("task")
+        then:
+        container.getByName("task") != null
+    }
+
+    void "task priotized over placeholders"() {
+        given:
+        Task task = addTask("task")
+        Runnable placeholderAction = addPlaceholderTask("task")
+
+        when:
+        container.getByName("task") == task
+
+        then:
+        0 * placeholderAction.run()
+    }
+
+    void "getNames contains task and placeholder action names"() {
+        when:
+        addTask("task1")
+        Runnable placeholderAction = addPlaceholderTask("task2")
+        0 * placeholderAction.run()
+        then:
+        container.names ==  ['task1', 'task2'] as SortedSet
+    }
+
+    void "maybeCreate creates new task"() {
+        given:
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+
+        taskFactory.createTask(options) >> task
+
+        when:
+        def added = container.maybeCreate("task")
+
+        then:
+        added == task
+    }
+
+    void "maybeCreate returns existing task"() {
+        when:
+        def task = addTask("task")
+
+        then:
+        container.maybeCreate("task") == task
+    }
+
+    void "maybeCreate creates new task with type"() {
+        given:
+        def options = [name: "task", type: CustomTask]
+        def task = task("task", CustomTask)
+
+        taskFactory.createTask(options) >> task
+
+        when:
+        def added = container.maybeCreate("task", CustomTask)
+
+        then:
+        added == task
+    }
+
+    void "maybeCreate returns existing task with type"() {
+        when:
+        def task = addTask("task", CustomTask)
+
+        then:
+        container.maybeCreate("task", CustomTask) == task
     }
 
     private ProjectInternal expectTaskLookupInOtherProject(final String projectPath, final String taskName, def task) {
@@ -294,16 +395,37 @@ public class DefaultTaskContainerTest extends Specification {
     }
 
     private TaskInternal task(final String name) {
-        Mock(TaskInternal, name: "[task" + ++taskCount + "]") {
+        task(name, TaskInternal)
+    }
+
+    private <U extends TaskInternal> U task(final String name, Class<U> type) {
+        Mock(type, name: "[task" + ++taskCount + "]") {
             getName() >> name
         }
     }
 
+    private Runnable addPlaceholderTask(String placeholderName) {
+        Runnable runnable = Mock(Runnable)
+        runnable.run() >> { addTask(placeholderName) }
+        container.addPlaceholderAction(placeholderName, runnable)
+        runnable
+    }
+
     private Task addTask(String name) {
         def task = task(name)
         def options = singletonMap(Task.TASK_NAME, name)
         taskFactory.createTask(options) >> task
-        container.add(name)
+        container.create(name)
         return task;
     }
+
+    private <U extends Task> U addTask(String name, Class<U> type) {
+        def task = task(name, type)
+        def options = [name: name, type: type]
+        taskFactory.createTask(options) >> task
+        container.create(name, type)
+        return task;
+    }
+
+    interface CustomTask extends TaskInternal {}
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
index f7bf87e..725b307 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,11 +15,12 @@
  */
 package org.gradle.api.internal.tasks.execution;
 
-import org.gradle.api.Action;
 import org.gradle.api.Task;
 import org.gradle.api.execution.TaskActionListener;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.ContextAwareTaskAction;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.tasks.StopActionException;
 import org.gradle.api.tasks.StopExecutionException;
@@ -47,11 +48,10 @@ import static org.junit.Assert.assertThat;
 public class ExecuteActionsTaskExecuterTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final TaskInternal task = context.mock(TaskInternal.class, "<task>");
-    @SuppressWarnings("unchecked")
-    private final Action<Task> action1 = context.mock(Action.class, "action1");
-    @SuppressWarnings("unchecked")
-    private final Action<Task> action2 = context.mock(Action.class, "action2");
+    private final ContextAwareTaskAction action1 = context.mock(ContextAwareTaskAction.class, "action1");
+    private final ContextAwareTaskAction action2 = context.mock(ContextAwareTaskAction.class, "action2");
     private final TaskStateInternal state = context.mock(TaskStateInternal.class);
+    private final TaskExecutionContext executionContext = context.mock(TaskExecutionContext.class);
     private final ScriptSource scriptSource = context.mock(ScriptSource.class);
     private final StandardOutputCapture standardOutputCapture = context.mock(StandardOutputCapture.class);
     private final Sequence sequence = context.sequence("seq");
@@ -79,7 +79,7 @@ public class ExecuteActionsTaskExecuterTest {
     @Test
     public void doesNothingWhenTaskHasNoActions() {
         context.checking(new Expectations() {{
-            allowing(task).getActions();
+            allowing(task).getTaskActions();
             will(returnValue(emptyList()));
 
             one(listener).beforeActions(task);
@@ -98,13 +98,13 @@ public class ExecuteActionsTaskExecuterTest {
             inSequence(sequence);
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 
     @Test
     public void executesEachActionInOrder() {
         context.checking(new Expectations() {{
-            allowing(task).getActions();
+            allowing(task).getTaskActions();
             will(returnValue(toList(action1, action2)));
 
             one(listener).beforeActions(task);
@@ -119,9 +119,15 @@ public class ExecuteActionsTaskExecuterTest {
             one(standardOutputCapture).start();
             inSequence(sequence);
 
+            one(action1).contextualise(executionContext);
+            inSequence(sequence);
+
             one(action1).execute(task);
             inSequence(sequence);
 
+            one(action1).contextualise(null);
+            inSequence(sequence);
+
             one(standardOutputCapture).stop();
             inSequence(sequence);
 
@@ -131,9 +137,15 @@ public class ExecuteActionsTaskExecuterTest {
             one(standardOutputCapture).start();
             inSequence(sequence);
 
+            one(action2).contextualise(executionContext);
+            inSequence(sequence);
+
             one(action2).execute(task);
             inSequence(sequence);
 
+            one(action2).contextualise(null);
+            inSequence(sequence);
+
             one(standardOutputCapture).stop();
             inSequence(sequence);
 
@@ -147,7 +159,7 @@ public class ExecuteActionsTaskExecuterTest {
             inSequence(sequence);
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 
     @Test
@@ -157,6 +169,9 @@ public class ExecuteActionsTaskExecuterTest {
                 allowing(task).getActions();
                 will(returnValue(toList(action1)));
 
+                allowing(task).getTaskActions();
+                will(returnValue(toList(action1)));
+
                 one(listener).beforeActions(task);
                 inSequence(sequence);
 
@@ -169,6 +184,9 @@ public class ExecuteActionsTaskExecuterTest {
                 one(standardOutputCapture).start();
                 inSequence(sequence);
 
+                one(action1).contextualise(executionContext);
+                inSequence(sequence);
+
                 one(action1).execute(task);
                 will(new CustomAction("Add action to actions list") {
                     public Object invoke(Invocation invocation) throws Throwable {
@@ -179,6 +197,9 @@ public class ExecuteActionsTaskExecuterTest {
 
                 inSequence(sequence);
 
+                one(action1).contextualise(null);
+                inSequence(sequence);
+
                 one(standardOutputCapture).stop();
                 one(state).executed(null);
                 inSequence(sequence);
@@ -190,7 +211,7 @@ public class ExecuteActionsTaskExecuterTest {
                 inSequence(sequence);
             }
         });
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 
 
@@ -199,7 +220,7 @@ public class ExecuteActionsTaskExecuterTest {
         final Throwable failure = new RuntimeException("failure");
         final Collector<Throwable> wrappedFailure = collector();
         context.checking(new Expectations() {{
-            allowing(task).getActions();
+            allowing(task).getTaskActions();
             will(returnValue(toList(action1, action2)));
 
             one(listener).beforeActions(task);
@@ -214,10 +235,16 @@ public class ExecuteActionsTaskExecuterTest {
             one(standardOutputCapture).start();
             inSequence(sequence);
 
+            one(action1).contextualise(executionContext);
+            inSequence(sequence);
+
             one(action1).execute(task);
             will(throwException(failure));
             inSequence(sequence);
 
+            one(action1).contextualise(null);
+            inSequence(sequence);
+
             one(standardOutputCapture).stop();
             inSequence(sequence);
 
@@ -232,7 +259,7 @@ public class ExecuteActionsTaskExecuterTest {
             inSequence(sequence);
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
 
         assertThat(wrappedFailure.get(), instanceOf(TaskExecutionException.class));
         TaskExecutionException exception = (TaskExecutionException) wrappedFailure.get();
@@ -244,7 +271,7 @@ public class ExecuteActionsTaskExecuterTest {
     @Test
     public void stopsAtFirstActionWhichThrowsStopExecutionException() {
         context.checking(new Expectations() {{
-            allowing(task).getActions();
+            allowing(task).getTaskActions();
             will(returnValue(toList(action1, action2)));
 
             one(listener).beforeActions(task);
@@ -259,10 +286,16 @@ public class ExecuteActionsTaskExecuterTest {
             one(standardOutputCapture).start();
             inSequence(sequence);
 
+            one(action1).contextualise(executionContext);
+            inSequence(sequence);
+
             one(action1).execute(task);
             will(throwException(new StopExecutionException("stop")));
             inSequence(sequence);
 
+            one(action1).contextualise(null);
+            inSequence(sequence);
+
             one(standardOutputCapture).stop();
             inSequence(sequence);
 
@@ -276,13 +309,13 @@ public class ExecuteActionsTaskExecuterTest {
             inSequence(sequence);
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 
     @Test
     public void skipsActionWhichThrowsStopActionException() {
         context.checking(new Expectations() {{
-            allowing(task).getActions();
+            allowing(task).getTaskActions();
             will(returnValue(toList(action1, action2)));
 
             one(listener).beforeActions(task);
@@ -297,10 +330,16 @@ public class ExecuteActionsTaskExecuterTest {
             one(standardOutputCapture).start();
             inSequence(sequence);
 
+            one(action1).contextualise(executionContext);
+            inSequence(sequence);
+
             one(action1).execute(task);
             will(throwException(new StopActionException("stop")));
             inSequence(sequence);
 
+            one(action1).contextualise(null);
+            inSequence(sequence);
+
             one(standardOutputCapture).stop();
             inSequence(sequence);
 
@@ -310,9 +349,15 @@ public class ExecuteActionsTaskExecuterTest {
             one(standardOutputCapture).start();
             inSequence(sequence);
 
+            one(action2).contextualise(executionContext);
+            inSequence(sequence);
+
             one(action2).execute(task);
             inSequence(sequence);
 
+            one(action2).contextualise(null);
+            inSequence(sequence);
+
             one(standardOutputCapture).stop();
             inSequence(sequence);
 
@@ -326,6 +371,6 @@ public class ExecuteActionsTaskExecuterTest {
             inSequence(sequence);
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuterTest.groovy
index ecb7cb2..d751c10 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteAtMostOnceTaskExecuterTest.groovy
@@ -16,14 +16,17 @@
 package org.gradle.api.internal.tasks.execution
 
 import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskExecutionContext
+import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.gradle.api.internal.tasks.TaskExecuter
-import org.gradle.api.internal.tasks.TaskStateInternal
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
+
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
 
 @RunWith(JMock.class)
 class ExecuteAtMostOnceTaskExecuterTest {
@@ -31,6 +34,7 @@ class ExecuteAtMostOnceTaskExecuterTest {
     private final TaskExecuter target = context.mock(TaskExecuter.class)
     private final TaskInternal task = context.mock(TaskInternal.class)
     private final TaskStateInternal state = context.mock(TaskStateInternal.class)
+    private final TaskExecutionContext executionContext = context.mock(TaskExecutionContext)
     private final ExecuteAtMostOnceTaskExecuter executer = new ExecuteAtMostOnceTaskExecuter(target)
 
     @Test
@@ -40,7 +44,7 @@ class ExecuteAtMostOnceTaskExecuterTest {
             will(returnValue(true))
         }
 
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
     }
 
     @Test
@@ -48,11 +52,11 @@ class ExecuteAtMostOnceTaskExecuterTest {
         context.checking {
             allowing(state).getExecuted()
             will(returnValue(false))
-            one(target).execute(task, state)
+            one(target).execute(task, state, executionContext)
             one(state).executed()
         }
 
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
     }
 
     @Test
@@ -62,13 +66,13 @@ class ExecuteAtMostOnceTaskExecuterTest {
         context.checking {
             allowing(state).getExecuted()
             will(returnValue(false))
-            one(target).execute(task, state)
+            one(target).execute(task, state, executionContext)
             will(throwException(failure))
             one(state).executed()
         }
 
         try {
-            executer.execute(task, state)
+            executer.execute(task, state, executionContext)
             fail()
         } catch (RuntimeException e) {
             assertThat(e, sameInstance(failure))
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuterTest.groovy
index 9054502..aa17273 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/PostExecutionAnalysisTaskExecuterTest.groovy
@@ -14,52 +14,38 @@
  * limitations under the License.
  */
 
-
-
-
 package org.gradle.api.internal.tasks.execution
-
-import org.gradle.api.Action
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskExecutionContext
 import org.gradle.api.internal.tasks.TaskStateInternal
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-
- at RunWith(JMock.class)
-class PostExecutionAnalysisTaskExecuterTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final TaskExecuter target = context.mock(TaskExecuter.class)
-    private final TaskInternal task = context.mock(TaskInternal.class)
-    private final TaskStateInternal state = context.mock(TaskStateInternal.class)
-    private final PostExecutionAnalysisTaskExecuter executer = new PostExecutionAnalysisTaskExecuter(target)
-
-    @Test
-    public void marksTaskUpToDateWhenItHasActionsAndItDidNotDoWork() {
-        context.checking {
-            one(target).execute(task, state)
-            allowing(task).getActions();
-            will(returnValue([{} as Action]))
-            allowing(state).getDidWork()
-            will(returnValue(false))
-            one(state).upToDate()
-        }
-
-        executer.execute(task, state)
+import spock.lang.Specification
+
+class PostExecutionAnalysisTaskExecuterTest extends Specification {
+    def target = Mock(TaskExecuter)
+    def task = Mock(TaskInternal)
+    def state = Mock(TaskStateInternal)
+    def context = Mock(TaskExecutionContext)
+    final PostExecutionAnalysisTaskExecuter executer = new PostExecutionAnalysisTaskExecuter(target)
+
+    def marksTaskUpToDateWhenItHasActionsAndItDidNotDoWork() {
+        when:
+        executer.execute(task, state, context)
+
+        then:
+        1 * target.execute(task, state, context)
+        1 * state.didWork >> false
+        1 * state.upToDate()
+        0 * _
     }
 
-    @Test
-    public void doesNotMarkTaskUpToDateWhenItHasActionsAndDidWork() {
-        context.checking {
-            one(target).execute(task, state)
-            allowing(task).getActions();
-            will(returnValue([{} as Action]))
-            allowing(state).getDidWork()
-            will(returnValue(true))
-        }
+    def doesNotMarkTaskUpToDateWhenItHasActionsAndDidWork() {
+        when:
+        executer.execute(task, state, context)
 
-        executer.execute(task, state)
+        then:
+        1 * target.execute(task, state, context)
+        1 * state.didWork >> true
+        0 * _
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy
index c6746c0..8a638b8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.execution
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskExecutionContext
 import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.api.tasks.TaskInputs
 import spock.lang.Specification
@@ -26,6 +27,7 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
     final TaskExecuter target = Mock()
     final TaskInternal task = Mock()
     final TaskStateInternal state = Mock()
+    final TaskExecutionContext executionContext = Mock()
     final TaskInputs taskInputs = Mock()
     final FileCollection sourceFiles = Mock()
     final SkipEmptySourceFilesTaskExecuter executer = new SkipEmptySourceFilesTaskExecuter(target)
@@ -41,7 +43,7 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
         sourceFiles.empty >> true
 
         when:
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
 
         then:
         1 * state.upToDate()
@@ -55,10 +57,10 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
         sourceFiles.empty >> false
 
         when:
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
 
         then:
-        1 * target.execute(task, state)
+        1 * target.execute(task, state, executionContext)
         0 * target._
         0 * state._
     }
@@ -68,10 +70,10 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
         taskInputs.hasSourceFiles >> false
 
         when:
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
 
         then:
-        1 * target.execute(task, state)
+        1 * target.execute(task, state, executionContext)
         0 * target._
         0 * state._
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.java
index 80ab5cf..21453f0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.java
@@ -21,6 +21,7 @@ import org.gradle.api.Task;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.specs.Spec;
 import org.gradle.groovy.scripts.ScriptSource;
@@ -42,6 +43,8 @@ public class SkipOnlyIfTaskExecuterTest {
     private final TaskInternal task = context.mock(TaskInternal.class, "<task>");
     private final Spec<Task> spec = context.mock(Spec.class);
     private final TaskStateInternal state = context.mock(TaskStateInternal.class);
+    private final TaskExecutionContext executionContext = context.mock(TaskExecutionContext.class);
+
     private final ScriptSource scriptSource = context.mock(ScriptSource.class);
     private final TaskExecuter delegate = context.mock(TaskExecuter.class);
     private final SkipOnlyIfTaskExecuter executer = new SkipOnlyIfTaskExecuter(delegate);
@@ -73,10 +76,10 @@ public class SkipOnlyIfTaskExecuterTest {
             allowing(spec).isSatisfiedBy(task);
             will(returnValue(true));
 
-            one(delegate).execute(task, state);
+            one(delegate).execute(task, state, executionContext);
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 
     @Test
@@ -89,7 +92,7 @@ public class SkipOnlyIfTaskExecuterTest {
             one(state).skipped("SKIPPED");
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
     }
 
     @Test
@@ -105,7 +108,7 @@ public class SkipOnlyIfTaskExecuterTest {
             will(collectTo(wrappedFailure));
         }});
 
-        executer.execute(task, state);
+        executer.execute(task, state, executionContext);
 
         GradleException exception = (GradleException) wrappedFailure.get();
         assertThat(exception.getMessage(), equalTo("Could not evaluate onlyIf predicate for <task>."));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuterTest.groovy
index 429f7ea..e3dd720 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuterTest.groovy
@@ -16,15 +16,17 @@
 package org.gradle.api.internal.tasks.execution
 
 import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskExecutionContext
+import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.api.tasks.TaskAction
 import org.gradle.api.tasks.TaskDependency
 import spock.lang.Specification
-import org.gradle.api.internal.tasks.TaskStateInternal
-import org.gradle.api.internal.tasks.TaskExecuter
 
 class SkipTaskWithNoActionsExecuterTest extends Specification {
     final TaskInternal task = Mock()
     final TaskStateInternal state = Mock()
+    final TaskExecutionContext executionContext = Mock()
     final TaskExecuter target = Mock()
     final TaskInternal dependency = Mock()
     final TaskStateInternal dependencyState = Mock()
@@ -43,7 +45,7 @@ class SkipTaskWithNoActionsExecuterTest extends Specification {
         dependencyState.skipped >> true
 
         when:
-        executor.execute(task, state)
+        executor.execute(task, state, executionContext)
 
         then:
         1 * state.upToDate()
@@ -57,7 +59,7 @@ class SkipTaskWithNoActionsExecuterTest extends Specification {
         dependencyState.skipped >> false
 
         when:
-        executor.execute(task, state)
+        executor.execute(task, state, executionContext)
 
         then:
         0 * target._
@@ -69,10 +71,10 @@ class SkipTaskWithNoActionsExecuterTest extends Specification {
         task.actions >> [{} as TaskAction]
 
         when:
-        executor.execute(task, state)
+        executor.execute(task, state, executionContext)
 
         then:
-        1 * target.execute(task, state)
+        1 * target.execute(task, state, executionContext)
         0 * target._
         0 * state._
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuterTest.groovy
new file mode 100644
index 0000000..1d474dd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuterTest.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.internal.tasks.execution
+
+import org.gradle.api.Action
+import org.gradle.api.Task
+import org.gradle.api.internal.TaskExecutionHistory
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.api.internal.changedetection.TaskArtifactState
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskExecutionContext
+import org.gradle.api.internal.tasks.TaskStateInternal
+import spock.lang.Specification
+
+public class SkipUpToDateTaskExecuterTest extends Specification {
+    def delegate = Mock(TaskExecuter)
+    def outputs = Mock(TaskOutputsInternal)
+    def task = Mock(TaskInternal)
+    def taskState = Mock(TaskStateInternal)
+    def taskContext = Mock(TaskExecutionContext)
+    def repository = Mock(TaskArtifactStateRepository)
+    def taskArtifactState = Mock(TaskArtifactState)
+    def executionHistory = Mock(TaskExecutionHistory)
+    Action<Task> action = Mock(Action)
+
+    def executer = new SkipUpToDateTaskExecuter(repository, delegate)
+
+    def skipsTaskWhenOutputsAreUpToDate() {
+        when:
+        executer.execute(task, taskState, taskContext);
+
+        then:
+        1 * repository.getStateFor(task) >> taskArtifactState
+        1 * taskArtifactState.isUpToDate([]) >> true
+        1 * taskState.upToDate()
+        1 * taskArtifactState.finished()
+        0 * _
+    }
+    
+    def executesTaskWhenOutputsAreNotUpToDate() {
+        when:
+        executer.execute(task, taskState, taskContext);
+
+        then:
+        1 * repository.getStateFor(task) >> taskArtifactState
+        1 * taskArtifactState.isUpToDate([]) >> false
+
+        then:
+        1 * taskArtifactState.beforeTask()
+        1 * taskArtifactState.getExecutionHistory() >> executionHistory
+        1 * task.outputs >> outputs
+        1 * outputs.setHistory(executionHistory)
+        1 * taskContext.setTaskArtifactState(taskArtifactState)
+
+        then:
+        1 * delegate.execute(task, taskState, taskContext)
+        _ * taskState.getFailure() >> null
+
+        then:
+        1 * taskArtifactState.afterTask()
+        1 * task.outputs >> outputs
+        1 * outputs.setHistory(null)
+        1 * taskContext.setTaskArtifactState(null)
+        1 * taskArtifactState.finished()
+        0 * _
+    }
+
+    def doesNotUpdateStateWhenTaskFails() {
+        when:
+        executer.execute(task, taskState, taskContext)
+
+        then:
+        1 * repository.getStateFor(task) >> taskArtifactState
+        1 * taskArtifactState.isUpToDate([]) >> false
+
+        then:
+        1 * taskArtifactState.beforeTask()
+        1 * taskArtifactState.getExecutionHistory() >> executionHistory
+        1 * task.outputs >> outputs
+        1 * outputs.setHistory(executionHistory)
+        1 * taskContext.setTaskArtifactState(taskArtifactState)
+
+        then:
+        1 * delegate.execute(task, taskState, taskContext)
+        1 * taskState.getFailure() >> new RuntimeException()
+
+        then:
+        1 * task.outputs >> outputs
+        1 * outputs.setHistory(null)
+        1 * taskContext.setTaskArtifactState(null)
+        1 * taskArtifactState.finished()
+        0 * _
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuterTest.java
deleted file mode 100644
index 61a5847..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipUpToDateTaskExecuterTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.execution;
-
-import org.gradle.api.internal.TaskExecutionHistory;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.internal.changedetection.TaskArtifactState;
-import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.api.internal.tasks.TaskStateInternal;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class SkipUpToDateTaskExecuterTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final TaskExecuter delegate = context.mock(TaskExecuter.class);
-    private final TaskOutputsInternal outputs = context.mock(TaskOutputsInternal.class);
-    private final TaskInternal task = context.mock(TaskInternal.class);
-    private final TaskStateInternal taskState = context.mock(TaskStateInternal.class);
-    private final TaskArtifactStateRepository repository = context.mock(TaskArtifactStateRepository.class);
-    private final TaskArtifactState taskArtifactState = context.mock(TaskArtifactState.class);
-    private final TaskExecutionHistory executionHistory = context.mock(TaskExecutionHistory.class);
-    private final SkipUpToDateTaskExecuter executer = new SkipUpToDateTaskExecuter(delegate, repository);
-
-    @Before
-    public void setup() {
-
-        context.checking(new Expectations(){{
-            allowing(task).getOutputs();
-            will(returnValue(outputs));
-        }});
-    }
-    @Test
-    public void skipsTaskWhenOutputsAreUpToDate() {
-        context.checking(new Expectations() {{
-            one(repository).getStateFor(task);
-            will(returnValue(taskArtifactState));
-
-            one(taskArtifactState).isUpToDate();
-            will(returnValue(true));
-
-            one(taskState).upToDate();
-
-            one(taskArtifactState).finished();
-        }});
-
-        executer.execute(task, taskState);
-    }
-    
-    @Test
-    public void executesTaskWhenOutputsAreNotUpToDate() {
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("seq");
-
-            one(repository).getStateFor(task);
-            will(returnValue(taskArtifactState));
-            inSequence(sequence);
-
-            one(taskArtifactState).isUpToDate();
-            will(returnValue(false));
-            inSequence(sequence);
-
-            one(taskArtifactState).beforeTask();
-            inSequence(sequence);
-
-            one(taskArtifactState).getExecutionHistory();
-            will(returnValue(executionHistory));
-
-            one(outputs).setHistory(executionHistory);
-            inSequence(sequence);
-
-            one(delegate).execute(task, taskState);
-            inSequence(sequence);
-
-            allowing(taskState).getFailure();
-            will(returnValue(null));
-
-            one(taskArtifactState).afterTask();
-            inSequence(sequence);
-
-            one(outputs).setHistory(null);
-            inSequence(sequence);
-
-            one(taskArtifactState).finished();
-            inSequence(sequence);
-        }});
-
-        executer.execute(task, taskState);
-    }
-
-    @Test
-    public void doesNotUpdateStateWhenTaskFails() {
-        context.checking(new Expectations() {{
-            one(repository).getStateFor(task);
-            will(returnValue(taskArtifactState));
-
-            one(taskArtifactState).isUpToDate();
-            will(returnValue(false));
-
-            one(taskArtifactState).beforeTask();
-
-            one(taskArtifactState).getExecutionHistory();
-            will(returnValue(executionHistory));
-
-            one(outputs).setHistory(executionHistory);
-
-            one(delegate).execute(task, taskState);
-
-            allowing(taskState).getFailure();
-            will(returnValue(new RuntimeException()));
-
-            one(outputs).setHistory(null);
-
-            one(taskArtifactState).finished();
-        }});
-
-        executer.execute(task, taskState);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy
index 74e5113..07c4a05 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy
@@ -15,34 +15,36 @@
  */
 package org.gradle.api.internal.tasks.execution
 
-import spock.lang.Specification
-import org.gradle.api.internal.tasks.TaskStateInternal
+import org.gradle.api.InvalidUserDataException
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.tasks.TaskExecuter
-import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.tasks.TaskExecutionContext
+import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.api.tasks.TaskValidationException
+import spock.lang.Specification
 
 class ValidatingTaskExecuterTest extends Specification {
     final TaskExecuter target = Mock()
     final TaskInternal task = Mock()
     final TaskStateInternal state = Mock()
+    final TaskExecutionContext executionContext = Mock()
     final TaskValidator validator = Mock()
     final ValidatingTaskExecuter executer = new ValidatingTaskExecuter(target)
 
     def executesTaskWhenThereAreNoViolations() {
         when:
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
 
         then:
         _ * task.validators >> [validator]
         1 * validator.validate(task, !null)
-        1 * target.execute(task, state)
+        1 * target.execute(task, state, executionContext)
         0 * _._
     }
 
     def failsTaskWhenThereIsAViolation() {
         when:
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
 
         then:
         _ * task.validators >> [validator]
@@ -59,7 +61,7 @@ class ValidatingTaskExecuterTest extends Specification {
 
     def failsTaskWhenThereAreMultipleViolations() {
         when:
-        executer.execute(task, state)
+        executer.execute(task, state, executionContext)
 
         then:
         _ * task.validators >> [validator]
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/InstanceOptionDescriptorSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/InstanceOptionDescriptorSpec.groovy
new file mode 100644
index 0000000..2e91c65
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/InstanceOptionDescriptorSpec.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options
+
+import org.gradle.internal.reflect.JavaMethod
+import spock.lang.Specification
+
+class InstanceOptionDescriptorSpec extends Specification{
+
+    OptionElement delegate = Mock(OptionElement)
+
+    def setup(){
+        _ * delegate.getOptionType() >> String.class
+        _ * delegate.getAvailableValues() >> new ArrayList<String>()
+        _ * delegate.getOptionName() >> "someOption"
+    }
+
+    def testGetAvailableValuesWithNoDefaults() {
+        when:
+        InstanceOptionDescriptor descriptor = new InstanceOptionDescriptor(new SomeClass(), delegate)
+        then:
+        descriptor.getAvailableValues() == []
+    }
+
+    def getAvailableValuesCallsWhenOptionValueMethodAvailable() {
+        setup:
+        JavaMethod<Object, Collection> optionValueMethod = Mock(JavaMethod)
+        when:
+        InstanceOptionDescriptor descriptor = new InstanceOptionDescriptor(new SomeClass(), delegate, optionValueMethod)
+        List<String> values = descriptor.getAvailableValues()
+        then:
+        values == ["dynValue1", "dynValue2"]
+        1 * optionValueMethod.invoke(_,_) >> ["dynValue1", "dynValue2"]
+    }
+
+    public class SomeClass {
+    }
+}
+
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/OptionNotationParserFactorySpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/OptionNotationParserFactorySpec.groovy
new file mode 100644
index 0000000..63622a6
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/OptionNotationParserFactorySpec.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options
+
+import org.gradle.api.GradleException
+import spock.lang.Specification
+
+class OptionNotationParserFactorySpec extends Specification {
+
+    def "creates notationparser for handling strings"(){
+        given:
+        OptionNotationParserFactory factory = new OptionNotationParserFactory()
+        when:
+        def parser = factory.toComposite(String.class);
+        then:
+        parser.parseNotation("somestring") == "somestring"
+    }
+
+    def "creates notationparser for handling handles enums"(){
+        given:
+        OptionNotationParserFactory factory = new OptionNotationParserFactory()
+        when:
+        def parser = factory.toComposite(TestEnum.class);
+        then:
+        parser.parseNotation(TestEnum.ABC) == TestEnum.ABC
+        parser.parseNotation("ABC") == TestEnum.ABC
+    }
+
+    def "fails on creating parser for unsupported"(){
+        setup:
+        OptionNotationParserFactory factory = new OptionNotationParserFactory()
+        when:
+        factory.toComposite(File.class);
+        then:
+        def e = thrown(GradleException);
+        e.message == "Don't know how to convert strings to type 'java.io.File'."
+    }
+
+    enum TestEnum {
+        ABC, DEF
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/OptionReaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/OptionReaderTest.groovy
new file mode 100644
index 0000000..b3634b5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/options/OptionReaderTest.groovy
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.options
+
+import org.gradle.api.Project
+import spock.lang.Specification
+
+class OptionReaderTest extends Specification {
+
+    OptionReader reader
+    Project project
+
+    def setup() {
+        reader = new OptionReader()
+    }
+
+    def "can read options linked to setter methods of a task"() {
+        when:
+        List<InstanceOptionDescriptor> options = reader.getOptions(new TestClass1())
+        then:
+        options[0].name == "aFlag"
+        options[0].description == "simple flag"
+        options[0].argumentType == Void.TYPE
+        options[0].optionElement.elementName == "setActive"
+        options[0].availableValues == []
+
+        options[1].name == "booleanValue"
+        options[1].description == "boolean value"
+        options[1].argumentType == Void.TYPE
+        options[1].optionElement.elementName == "setBooleanValue"
+        options[1].availableValues == []
+
+        options[2].name == "enumValue"
+        options[2].description == "enum value"
+        options[2].argumentType == TestEnum
+        options[2].optionElement.elementName == "setEnumValue"
+        options[2].availableValues == ["ABC", "DEF"]
+
+        options[3].name == "objectValue"
+        options[3].description == "object value"
+        options[3].argumentType == Object
+        options[3].optionElement.elementName == "setObjectValue"
+        options[3].availableValues == []
+
+        options[4].name == "stringValue"
+        options[4].description == "string value"
+        options[4].argumentType == String
+        options[4].optionElement.elementName == "setStringValue"
+        options[4].availableValues == ["dynValue1", "dynValue2"]
+
+    }
+
+    def "fail when multiple methods define same option"() {
+        when:
+        reader.getOptions(new TestClass2())
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "@Option 'stringValue' linked to multiple elements in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass2'."
+    }
+
+    def "fails on static methods"() {
+        when:
+        reader.getOptions(new TestClass31())
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "@Option on static method 'setStaticString' not supported in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass31'."
+    }
+
+    def "fails on static fields"() {
+        when:
+        reader.getOptions(new TestClass32())
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "@Option on static field 'staticField' not supported in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass32'."
+    }
+
+    def "fail when parameter cannot be converted from the command-line"() {
+        when:
+        reader.getOptions(new TestClass5())
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "Option 'fileValue' cannot be casted to type 'java.io.File' in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass5'."
+    }
+
+    def "fails when method has > 1 parameter"() {
+        when:
+        reader.getOptions(new TestClass4());
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "Option 'stringValue' cannot be linked to methods with multiple parameters in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass4#setStrings'."
+    }
+
+    def "handles field options"() {
+        when:
+        List<InstanceOptionDescriptor> options = reader.getOptions(new TestClass6())
+        then:
+        options[0].name == "customOptionName"
+        options[0].description == "custom description"
+        options[0].argumentType == String
+
+        options[1].name == "field2"
+        options[1].description == "Descr Field2"
+        options[1].argumentType == String
+        options[1].availableValues == ["dynValue1", "dynValue2"]
+
+        options[2].name == "field3"
+        options[2].description == "Descr Field3"
+        options[2].argumentType == TestEnum
+        options[2].availableValues as Set == ["ABC", "DEF"] as Set
+
+        options[3].name == "field4"
+        options[3].description == "Descr Field4"
+        options[3].argumentType == Void.TYPE
+        options[3].availableValues.isEmpty()
+    }
+
+
+    def "throws decent error when description not set"() {
+        when:
+        reader.getOptions(new TestClass7());
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "No description set on option 'aValue' at for class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass7'."
+
+        when:
+        reader.getOptions(new TestClass8());
+        then:
+        e = thrown(OptionValidationException)
+        e.message == "No description set on option 'field' at for class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass8'."
+    }
+
+    def "throws decent error when method annotated without option name set"() {
+        when:
+        reader.getOptions(new TestClass9());
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "No option name set on 'setStrings' in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass9'."
+    }
+
+    def "throws decent error when private field is annotated as option and no setter declared"() {
+        when:
+        reader.getOptions(new TestClass10())
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "No setter for Option annotated field 'field' in class 'class org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass10'."
+    }
+
+    def "throws decent error for invalid OptionValues annotated methods"() {
+        when:
+        reader.getOptions(new WithInvalidSomeOptionMethod());
+        then:
+        def e = thrown(OptionValidationException)
+        e.message == "@OptionValues annotation not supported on method 'getValues' in class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$WithInvalidSomeOptionMethod'. Supported method must be non-static, return a Collection<String> and take no parameters.";
+
+        when:
+        reader.getOptions(new TestClass8());
+        then:
+        e = thrown(OptionValidationException)
+        e.message == "No description set on option 'field' at for class 'org.gradle.api.internal.tasks.options.OptionReaderTest\$TestClass8'."
+    }
+
+    public static class TestClass1{
+        @Option(option = "stringValue", description = "string value")
+        public void setStringValue(String value) {
+        }
+
+        @Option(option = "objectValue", description = "object value")
+        public void setObjectValue(Object value) {
+        }
+
+        @Option(option = "booleanValue", description = "boolean value")
+        public void setBooleanValue(boolean value) {
+        }
+
+        @Option(option = "enumValue", description = "enum value")
+        public void setEnumValue(TestEnum value) {
+        }
+
+        @Option(option = "aFlag", description = "simple flag")
+        public void setActive() {
+        }
+
+        @OptionValues("stringValue")
+        public Collection<CustomClass> getAvailableValues() {
+            return Arrays.asList(new CustomClass(value: "dynValue1"), new CustomClass(value: "dynValue2"))
+        }
+    }
+
+    public static class CustomClass {
+        String value
+
+        public String toString() {
+            value
+        }
+    }
+
+    public static class TestClass2{
+        @Option(option = "stringValue", description = "string value")
+        public void setStringValue(String value) {
+        }
+
+        @Option(option = "stringValue", description = "string value")
+        public void setStringValue2(String value) {
+        }
+    }
+
+    public static class TestClass31{
+        @Option(option = "staticString", description = "string value")
+        public static void setStaticString(String value) {
+        }
+    }
+
+    public static class TestClass32{
+        @Option(description = "staticOption")
+        static String staticField
+    }
+
+    public static class TestClass4{
+        @Option(option = 'stringValue', description = "string value")
+        public void setStrings(String value1, String value2) {
+        }
+    }
+
+    public static class TestClass5{
+        @Option(option = 'fileValue', description = "file value")
+        public void setStrings(File file) {
+        }
+    }
+
+    public static class TestClass6{
+        @Option(option = 'customOptionName', description = "custom description")
+        String field1
+
+        @Option(description = "Descr Field2")
+        String field2
+
+        @Option(description = "Descr Field3")
+        TestEnum field3
+
+        @Option(description = "Descr Field4")
+        boolean field4
+
+
+        @OptionValues("field2")
+        List<String> getField2Options() {
+            return Arrays.asList("dynValue1", "dynValue2")
+        }
+    }
+
+    public static class TestClass7{
+        @Option(option = 'aValue')
+        public void setStrings(String value) {
+        }
+    }
+
+    public static class TestClass8{
+        @Option
+        String field
+    }
+
+    public static class TestClass9 {
+        @Option(description = "some description")
+        public void setStrings(String value) {
+        }
+    }
+
+    public static class TestClass10{
+        @Option(description = "some description")
+        private String field
+    }
+
+    public static class WithInvalidSomeOptionMethod {
+        @OptionValues("someOption")
+        List<String> getValues(String someParam) { return Arrays.asList("something")}
+    }
+
+    public static class WithDuplicateSomeOptions {
+        @OptionValues("someOption")
+        List<String> getValues() { return Arrays.asList("something")}
+
+        @OptionValues("someOption")
+        List<String> getValues2() { return Arrays.asList("somethingElse")}
+    }
+
+    public static class WithAnnotatedStaticMethod {
+        @OptionValues("someOption")
+        static List<String> getValues(String someParam) { return Arrays.asList("something")}
+    }
+
+
+    public class SomeOptionValues{
+        @OptionValues("someOption")
+        List<String> getValues() { return Arrays.asList("something")}
+    }
+
+    enum TestEnum {
+        ABC, DEF
+    }
+}
+
+
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 abce164..9141221 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
@@ -17,10 +17,11 @@
 
 package org.gradle.api.internal.tasks.util
 
+import org.gradle.api.internal.file.TestFiles
+
 import java.nio.charset.Charset
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.file.IdentityFileResolver
 import org.gradle.process.JavaForkOptions
 import org.gradle.process.internal.DefaultJavaForkOptions
 import org.gradle.util.JUnit4GroovyMockery
@@ -235,7 +236,7 @@ public class DefaultJavaForkOptionsTest {
     @Test
     public void canAddToBootstrapClasspath() {
         def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
-        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options = new DefaultJavaForkOptions(TestFiles.resolver());
         options.bootstrapClasspath(files[0])
         options.bootstrapClasspath(files[1])
 
@@ -245,7 +246,7 @@ public class DefaultJavaForkOptionsTest {
     @Test
     public void allJvmArgsIncludeBootstrapClasspath() {
         def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
-        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options = new DefaultJavaForkOptions(TestFiles.resolver());
         options.bootstrapClasspath(files)
 
         context.checking {
@@ -259,7 +260,7 @@ public class DefaultJavaForkOptionsTest {
     @Test
     public void canSetBootstrapClasspathViaAllJvmArgs() {
         def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
-        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options = new DefaultJavaForkOptions(TestFiles.resolver());
         options.bootstrapClasspath(files[0])
 
         options.allJvmArgs = ['-Xbootclasspath:' + files[1]]
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy
index 38a67b4..b98f65e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy
@@ -16,14 +16,11 @@
 
 package org.gradle.api.internal.xml
 
+import org.gradle.util.TextUtil
 import spock.lang.Specification
 
 import javax.xml.parsers.DocumentBuilderFactory
-import org.gradle.util.TextUtil
 
-/**
- * by Szczepan Faber, created at: 12/3/12
- */
 class SimpleXmlWriterSpec extends Specification {
 
     private sw = new ByteArrayOutputStream()
@@ -51,7 +48,7 @@ class SimpleXmlWriterSpec extends Specification {
         writer.endElement()
 
         then:
-        xml == '<?xml version="1.1" encoding="UTF-8"?><root items="9"><item/><item size="10m">some chars and some other chars.<foo> </foo></item></root>'
+        xml == '<?xml version="1.0" encoding="UTF-8"?><root items="9"><item/><item size="10m">some chars and some other chars.<foo> </foo></item></root>'
     }
 
     def "escapes reserved characters in text content"() {
@@ -152,13 +149,13 @@ class SimpleXmlWriterSpec extends Specification {
     def "escapes restricted characters in text content"() {
         when:
         writer.startElement("root")
-        writer.attribute("name", "\u0084\u0002")
-        writer.characters("\u0084\u0002\u009f")
-        writer.startCDATA().characters("\u0084\u0002").endCDATA()
+        writer.attribute("name", "\u0084\u009f")
+        writer.characters("\u0084\u009f")
+        writer.startCDATA().characters("\u0084\u009f").endCDATA()
         writer.endElement()
 
         then:
-        xml.contains('<root name="&#x84;&#x2;">&#x84;&#x2;&#x9f;<![CDATA[]]>&#x84;<![CDATA[]]>&#x2;<![CDATA[]]></root>')
+        xml.contains('<root name="&#x84;&#x9f;">&#x84;&#x9f;<![CDATA[]]>&#x84;<![CDATA[]]>&#x9f;<![CDATA[]]></root>')
     }
 
     def "replaces illegal characters in text content"() {
@@ -346,7 +343,7 @@ class SimpleXmlWriterSpec extends Specification {
         writer.endElement()
 
         then:
-        xml == TextUtil.toPlatformLineSeparators('''<?xml version="1.1" encoding="UTF-8"?>
+        xml == TextUtil.toPlatformLineSeparators('''<?xml version="1.0" encoding="UTF-8"?>
 <root items="9">
     <item/>
     <item>some text</item>
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy b/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy
index c38c20f..0af5662 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy
@@ -16,9 +16,6 @@
  
 package org.gradle.api.plugins
 
-/**
- * @author Hans Dockter
- */
 class TestPluginConvention1 {
     String a = 'a1'
     String b = 'b'
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy b/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy
index a7e1b4d..0aab5b1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy
@@ -16,9 +16,6 @@
  
 package org.gradle.api.plugins
 
-/**
- * @author Hans Dockter
- */
 class TestPluginConvention2 {
     String a = 'a2'
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.groovy
new file mode 100644
index 0000000..ad7f4a8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.file.copy.CopyAction
+import org.gradle.test.fixtures.file.WorkspaceTest
+import org.gradle.util.TestUtil
+import org.junit.Test
+
+class AbstractCopyTaskTest extends WorkspaceTest {
+
+    TestCopyTask task
+
+    def setup() {
+        task = TestUtil.createTask(TestCopyTask)
+    }
+
+    void usesDefaultSourceWhenNoSourceHasBeenSpecified() {
+        given:
+        def defaultSource = Mock(FileTree)
+
+        when:
+        task.defaultSource = defaultSource
+
+        then:
+        task.source.is(defaultSource)
+    }
+
+    public void doesNotUseDefaultSourceWhenSourceHasBeenSpecifiedOnSpec() {
+        when:
+        FileTree source = Mock(FileTree)
+        task.defaultSource = source
+        task.from "foo"
+
+        then:
+        !task.source.is(source)
+    }
+
+    @Test
+    public void copySpecMethodsDelegateToMainSpecOfCopyAction() {
+        given:
+        file("include") << "bar"
+
+        expect:
+        task.rootSpec.source.isEmpty()
+
+        when:
+        task.from testDirectory.absolutePath
+        task.include "include"
+
+        then:
+        task.mainSpec.getIncludes() == ["include"].toSet()
+        task.mainSpec.source.files == task.project.fileTree(testDirectory).files
+    }
+
+    static class TestCopyTask extends AbstractCopyTask {
+        CopyAction copyAction
+        FileCollection defaultSource
+
+        protected CopyAction createCopyAction() {
+            copyAction
+        }
+
+    }
+}
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
deleted file mode 100644
index 61a7581..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.AbstractTask;
-import org.gradle.api.internal.file.copy.CopyActionImpl;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class AbstractCopyTaskTest extends AbstractTaskTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private TestCopyTask task;
-
-    @Before
-    public void setUp() {
-        task = createTask(TestCopyTask.class);
-        task.action = context.mock(CopyActionImpl.class);
-        task.defaultSource = context.mock(FileCollection.class);
-    }
-
-    @Override
-    public AbstractTask getTask() {
-        return task;
-    }
-
-    @Test
-    public void usesDefaultSourceWhenNoSourceHasBeenSpecified() {
-        context.checking(new Expectations() {{
-            one(task.action).hasSource();
-            will(returnValue(false));
-
-        }});
-        assertThat(task.getSource(), sameInstance(task.defaultSource));
-    }
-
-    @Test
-    public void doesNotUseDefaultSourceWhenSourceHasBeenSpecifiedOnSpec() {
-        final FileTree source = context.mock(FileTree.class, "source");
-        context.checking(new Expectations() {{
-            one(task.action).hasSource();
-            will(returnValue(true));
-            one(task.action).getAllSource();
-            will(returnValue(source));
-        }});
-        assertThat(task.getSource(), sameInstance((FileCollection) source));
-    }
-
-
-    @Test
-    public void copySpecMethodsDelegateToMainSpecOfCopyAction() {
-        context.checking(new Expectations() {{
-            one(task.action).include("include");
-            one(task.action).from("source");
-        }});
-
-        assertThat(task.include("include"), sameInstance((AbstractCopyTask) task));
-        assertThat(task.from("source"), sameInstance((AbstractCopyTask) task));
-    }
-
-    public static class TestCopyTask extends AbstractCopyTask {
-        CopyActionImpl action;
-        FileCollection defaultSource;
-
-        @Override
-        protected CopyActionImpl getCopyAction() {
-            return action;
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public FileCollection getDefaultSource() {
-            return defaultSource;
-        }
-    }
-}
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 9ac42c6..6c0f48c 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,69 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.tasks
-
-import org.gradle.api.internal.AbstractTask
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.lib.legacy.ClassImposteriser
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.api.internal.file.copy.FileCopyActionImpl
-
- at RunWith (org.jmock.integration.junit4.JMock)
-public class CopyTest extends AbstractTaskTest {
-    Copy copyTask;
-    FileCopyActionImpl action;
 
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery();
-
-    @Before
-    public void setUp() {
-        context.setImposteriser(ClassImposteriser.INSTANCE)
-        action = context.mock(FileCopyActionImpl.class)
-        copyTask = createTask(Copy.class)
-        copyTask.copyAction = action
-    }
-
-    public AbstractTask getTask() {
-        return copyTask;
-    }
-
-    @Test public void executesActionOnExecute() {
-        context.checking {
-            one(action).hasSource(); will(returnValue(true))
-            one(action).getDestinationDir(); will(returnValue(new File('dest')))
-            one(action).execute()
-            one(action).getDidWork()
-        }
-
-        copyTask.copy()
-    }
-    
-    @Test public void usesConventionValuesForDestDirWhenNotSpecified() {
-        copyTask.conventionMapping.destinationDir = { new File('convention') }
-
-        context.checking {
-            exactly(2).of(action).getDestinationDir()
-            will(returnValue(null))
-            one(action).into(new File('convention'))
-            one(action).hasSource(); will(returnValue(true))
-        }
-
-        copyTask.configureRootSpec()
-    }
+package org.gradle.api.tasks
 
-    @Test public void doesNotUseConventionValueForDestDirWhenSpecified() {
-        copyTask.conventionMapping.destinationDir = { new File('convention') }
+class CopyTest extends AbstractCopyTaskContractTest {
 
-        context.checking {
-            one(action).getDestinationDir()
-            will(returnValue(new File('dest')))
-            one(action).hasSource(); will(returnValue(true))
-        }
+    private Copy task = project.tasks.create(TEST_TASK_NAME, Copy)
 
-        copyTask.configureRootSpec()
+    AbstractCopyTask getTask() {
+        return task
     }
 }
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 45cc0d1..46952df 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
@@ -16,15 +16,9 @@
 
 package org.gradle.api.tasks;
 
-import org.gradle.api.file.DeleteAction;
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.file.DefaultFileOperations;
-import org.gradle.api.internal.file.FileOperations;
-import org.gradle.api.internal.project.DefaultProject;
-import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,21 +29,13 @@ import java.io.IOException;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class DeleteTest extends AbstractConventionTaskTest {
-    private Mockery context = new JUnit4GroovyMockery();
-    private DeleteAction deleteAction = context.mock(DeleteAction.class);
     private Delete delete;
 
     @Before
     public void setUp() {
         delete = createTask(Delete.class);
-        DefaultFileOperations fileOperations = (DefaultFileOperations) ((DefaultProject)
-                delete.getProject()).getServices().get(FileOperations.class);
-        fileOperations.setDeleteAction(deleteAction);
     }
 
     public ConventionTask getTask() {
@@ -63,25 +49,18 @@ public class DeleteTest extends AbstractConventionTaskTest {
 
     @Test
     public void didWorkIsTrueWhenSomethingGetsDeleted() throws IOException {
-        context.checking(new Expectations() {{
-            one(deleteAction).delete(WrapUtil.toSet("someFile"));
-            returnValue(true);
-        }});
+        TestFile file = tmpDir.createFile("someFile");
 
-        delete.delete("someFile");
+        delete.delete(file);
         delete.execute();
 
-        assertFalse(delete.getDidWork());
+        assertTrue(delete.getDidWork());
+        assertFalse(file.exists());
     }
 
     @Test
     public void didWorkIsFalseWhenNothingDeleted() throws IOException {
-        context.checking(new Expectations() {{
-            one(deleteAction).delete(WrapUtil.toSet("someFile"));
-            returnValue(false);
-        }});
-
-        delete.delete("someFile");
+        delete.delete("does-not-exist");
         delete.execute();
 
         assertFalse(delete.getDidWork());
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 9633666..f8e3842 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
@@ -16,18 +16,16 @@
 
 package org.gradle.api.tasks
 
+import org.gradle.api.GradleException
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.UncheckedIOException
 import org.gradle.api.internal.AbstractTask
 import org.junit.Before
 import org.junit.Test
+
+import static org.hamcrest.Matchers.instanceOf
 import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import org.gradle.api.GradleException
 
-/**
- * @author Hans Dockter
- */
 class DirectoryTest extends AbstractTaskTest {
     static final String TASK_DIR_NAME = 'parent/child'
     Directory directoryForAbstractTest
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 2e3f6e6..676313a 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
@@ -18,54 +18,39 @@ package org.gradle.api.tasks
 import org.gradle.BuildResult
 import org.gradle.GradleLauncher
 import org.gradle.initialization.GradleLauncherFactory
-import org.gradle.api.internal.AbstractTask
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
+import org.gradle.util.TestUtil
+import spock.lang.Specification
 
-public class GradleBuildTest extends AbstractTaskTest {
-    GradleLauncherFactory launcherFactoryMock = context.mock(GradleLauncherFactory.class)
-    GradleBuild task
+public class GradleBuildTest extends Specification {
+    GradleLauncherFactory launcherFactory = Mock()
+    GradleBuild task = TestUtil.createTask(GradleBuild, [gradleLauncherFactory: launcherFactory])
 
-    AbstractTask getTask() {
-        return task
-    }
-
-    @Before
-    void setUp() {
-        task = createTask(GradleBuild.class)
-        GradleLauncher.injectCustomFactory(launcherFactoryMock)
-    }
+    void usesCopyOfCurrentBuildsStartParams() {
+        def expectedStartParameter = task.project.gradle.startParameter.newBuild()
+        expectedStartParameter.currentDir = task.project.projectDir
 
-    @After
-    void tearDown() {
-        GradleLauncher.injectCustomFactory(null)
-    }
+        expect:
+        task.startParameter == expectedStartParameter
 
-    @Test
-    void usesCopyOfCurrentBuildsStartParams() {
-        def expectedStartParameter = project.gradle.startParameter.newBuild()
-        expectedStartParameter.currentDir = project.projectDir
-        assertThat(task.startParameter, equalTo(expectedStartParameter))
+        when:
         task.tasks = ['a', 'b']
-        assertThat(task.tasks, equalTo(['a', 'b']))
-        assertThat(task.startParameter.taskNames, equalTo(['a', 'b']))
+
+        then:
+        task.tasks == ['a', 'b']
+        task.startParameter.taskNames == ['a', 'b']
     }
 
-    @Test
     void executesBuild() {
-        GradleLauncher launcherMock = context.mock(GradleLauncher.class)
-        BuildResult resultMock = context.mock(BuildResult.class)
+        GradleLauncher launcher = Mock()
+        BuildResult resultMock = Mock()
 
-        context.checking {
-            one(launcherFactoryMock).newInstance(task.startParameter)
-            will(returnValue(launcherMock))
-            one(launcherMock).run()
-            will(returnValue(resultMock))
-            one(resultMock).rethrowFailure()
-        }
+        when:
         task.build()
+
+        then:
+        1 * launcherFactory.newInstance(task.startParameter) >> launcher
+        1 * launcher.run() >> resultMock
+        1 * resultMock.rethrowFailure()
+        0 * _._
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/SyncTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/SyncTest.groovy
new file mode 100644
index 0000000..818c133
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/SyncTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+class SyncTest extends AbstractCopyTaskContractTest {
+
+    private Sync task = project.tasks.create(TEST_TASK_NAME, Sync)
+
+    @Override
+    AbstractCopyTask getTask() {
+        task
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy
deleted file mode 100644
index 8b70973..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy
+++ /dev/null
@@ -1,32 +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.tasks
-
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class UploadTest extends Specification {
-
-    def "can create task"() {
-        when:
-        HelperUtil.createTask(Upload)
-
-        then:
-        noExceptionThrown()
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
index 124125a..685ccfa 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
@@ -21,7 +21,7 @@ import org.apache.tools.ant.Target;
 import org.gradle.api.Task;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,8 +36,8 @@ import static org.junit.Assert.assertTrue;
 
 public class AntTargetTest {
     private final Target antTarget = new Target();
-    private final DefaultProject project = HelperUtil.createRootProject();
-    private final AntTarget task = HelperUtil.createTask(AntTarget.class, project);
+    private final DefaultProject project = TestUtil.createRootProject();
+    private final AntTarget task = TestUtil.createTask(AntTarget.class, project);
     @Rule
     public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
     private final File baseDir = testDir.getTestDirectory();
@@ -62,8 +62,8 @@ public class AntTargetTest {
 
     @Test
     public void dependsOnTargetDependencies() {
-        Task a = project.getTasks().add("a");
-        Task b = project.getTasks().add("b");
+        Task a = project.getTasks().create("a");
+        Task b = project.getTasks().create("b");
         antTarget.setDepends("a, b");
 
         task.setTarget(antTarget);
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 c65aef8..ee7459b 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
@@ -16,21 +16,18 @@
 
 package org.gradle.api.tasks.bundling
 
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
 import org.junit.Before
-import org.junit.Test;
+import org.junit.Test
+
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 class TarTest extends AbstractArchiveTaskTest {
     Tar tar
 
     @Before public void setUp()  {
         tar = createTask(Tar)
         configure(tar)
-        tar.from tmpDir.createFile('file.txt')
     }
 
     AbstractArchiveTask getArchiveTask() {
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 9ea31f0..3506f75 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
@@ -18,18 +18,15 @@ package org.gradle.api.tasks.bundling
 
 import org.junit.Before
 import org.junit.Test
-import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.assertEquals
+
 class ZipTest extends AbstractArchiveTaskTest {
     Zip zip
 
     @Before public void setUp()  {
         zip = createTask(Zip)
         configure(zip)
-        zip.from tmpDir.createFile('file.txt')
     }
 
     AbstractArchiveTask getArchiveTask() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy
index e2480a6..6165667 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.api.tasks.util
 
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import static org.gradle.util.Matchers.*
 import org.junit.Before
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
+import static org.gradle.util.Matchers.isEmpty
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertThat
+
 abstract class AbstractTestForPatternSet {
     static final String TEST_PATTERN_1 = 'pattern1'
     static final String TEST_PATTERN_2 = 'pattern2'
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/AbstractFileLockManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/AbstractFileLockManagerTest.groovy
new file mode 100644
index 0000000..364042a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/AbstractFileLockManagerTest.groovy
@@ -0,0 +1,532 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal
+
+import org.apache.commons.lang.RandomStringUtils
+import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.cache.internal.filelock.LockInfoSerializer
+import org.gradle.cache.internal.filelock.LockOptionsBuilder
+import org.gradle.cache.internal.locklistener.FileLockContentionHandler
+import org.gradle.internal.Factory
+import org.gradle.internal.id.IdGenerator
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
+
+abstract class AbstractFileLockManagerTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    def metaDataProvider = Mock(ProcessMetaDataProvider)
+    def generator = Stub(IdGenerator)
+    def contentionHandler = Stub(FileLockContentionHandler)
+
+    FileLockManager manager = new DefaultFileLockManager(metaDataProvider, 5000, contentionHandler, generator)
+
+    TestFile testFile
+    TestFile testFileLock
+    TestFile testDir
+    TestFile testDirLock
+
+    def setup() {
+        testFile = tmpDir.createFile("state.bin")
+        testFileLock = tmpDir.file(testFile.name + ".lock")
+        testDir = tmpDir.createDir("lockable-dir")
+        testDirLock = tmpDir.file("${testDir.name}/${testDir.name}.lock")
+
+        metaDataProvider.processIdentifier >> '123'
+        metaDataProvider.processDisplayName >> 'process'
+        contentionHandler.reservePort() >> 34
+        generator.generateId() >> 678L
+    }
+
+    def "readFile throws integrity exception when not cleanly unlocked file"() {
+        given:
+        unlockUncleanly()
+
+        and:
+        def lock = createLock(lockMode)
+
+        when:
+        lock.readFile {}
+
+        then:
+        thrown FileIntegrityViolationException
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "updateFile throws integrity exception when not cleanly unlocked file"() {
+        given:
+        unlockUncleanly()
+
+        and:
+        def lock = createLock(Exclusive)
+
+        when:
+        lock.updateFile {}
+
+        then:
+        thrown FileIntegrityViolationException
+    }
+
+    def "writeFile does not throw integrity exception when not cleanly unlocked file"() {
+        given:
+        unlockUncleanly()
+
+        when:
+        createLock(Exclusive).writeFile { }
+
+        then:
+        notThrown FileIntegrityViolationException
+    }
+
+    def "can lock a file"() {
+        when:
+        def lock = createLock(lockMode)
+
+        then:
+        lock.isLockFile(tmpDir.createFile(testFile.name + ".lock"))
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "can lock a directory"() {
+        when:
+        def lock = createLock(lockMode, testDir)
+
+        then:
+        lock.isLockFile(testDirLock)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "can lock a file after it has been closed"() {
+        given:
+        def fileLock = createLock(Exclusive)
+        fileLock.close()
+
+        when:
+        createLock(lockMode)
+
+        then:
+        notThrown(RuntimeException)
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "lock on new file is not unlocked cleanly"() {
+        when:
+        def lock = createLock(mode)
+
+        then:
+        !lock.unlockedCleanly
+
+        cleanup:
+        lock?.close()
+
+        where:
+        mode << [Shared, Exclusive]
+    }
+
+    def "file is 'invalid' unless a valid lock file exists"() {
+        given:
+        def lock = createLock(Exclusive)
+
+        when:
+        lock.readFile({})
+
+        then:
+        thrown FileIntegrityViolationException
+
+        when:
+        lock.updateFile({})
+
+        then:
+        thrown FileIntegrityViolationException
+
+        when:
+        lock.writeFile({})
+
+        and:
+        lock.readFile({})
+        lock.updateFile({})
+
+        then:
+        notThrown FileIntegrityViolationException
+    }
+
+    def "integrity violation exception is thrown after a failed write"() {
+        given:
+        def e = new RuntimeException()
+        def lock = createLock(Exclusive)
+
+        when:
+        lock.writeFile {}
+        lock.updateFile { throw e }
+
+        then:
+        thrown RuntimeException
+
+        when:
+        lock.readFile({})
+
+        then:
+        thrown FileIntegrityViolationException
+    }
+
+    def "existing lock is unlocked cleanly after writeToFile() has been called"() {
+        when:
+        def lock = this.createLock(Exclusive)
+        lock.writeFile({})
+        lock.updateFile({} as Runnable)
+
+        then:
+        lock.unlockedCleanly
+
+        when:
+        lock.close()
+        lock = createLock(Exclusive)
+
+        then:
+        lock.unlockedCleanly
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "existing lock is unlocked cleanly after writeToFile() throws exception"() {
+        def failure = new RuntimeException()
+
+        when:
+        def lock = createLock(Exclusive)
+        lock.writeFile({ })
+        lock.updateFile({throw failure} as Runnable)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        !lock.unlockedCleanly
+
+        when:
+        lock.close()
+        lock = createLock(Exclusive)
+
+        then:
+        !lock.unlockedCleanly
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "cannot lock a file twice in single process"() {
+        given:
+        createLock(Exclusive);
+
+        when:
+        createLock(Exclusive);
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot lock twice in single process for mixed modes"() {
+        given:
+        createLock(Exclusive);
+
+        when:
+        createLock(Shared);
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot lock twice in single process for shared mode"() {
+        given:
+        createLock(Shared);
+
+        when:
+        createLock(Shared);
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "can close a lock multiple times"() {
+        given:
+        def lock = createLock(Exclusive)
+        lock.close()
+
+        expect:
+        lock.close()
+    }
+
+    def "cannot read from file after lock has been closed"() {
+        given:
+        def lock = createLock(Exclusive)
+        lock.close()
+
+        when:
+        lock.readFile({} as Factory)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot write to file after lock has been closed"() {
+        given:
+        def lock = createLock(Exclusive)
+        lock.close()
+
+        when:
+        lock.updateFile({} as Runnable)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "can acquire lock on partially written lock file"() {
+        given:
+        partiallyWritten()
+
+        when:
+        def lock = createLock(mode)
+
+        then:
+        !lock.unlockedCleanly
+        lock.isLockFile(testFileLock)
+        lock.close()
+
+        where:
+        mode << [Shared, Exclusive]
+    }
+
+    def "fails to acquire lock on lock file with unknown version"() {
+        testFileLock.withDataOutputStream {
+            it.writeByte(125)
+        }
+
+        when:
+        createLock(mode)
+
+        then:
+        thrown(IllegalStateException)
+
+        where:
+        mode << [Shared, Exclusive]
+    }
+
+    def "leaves lock file after exclusive lock on new file closed"() {
+        when:
+        def lock = createLock(Exclusive)
+        lock.close()
+
+        then:
+        lock.isLockFile(testFileLock)
+
+        and:
+        isVersionLockFile(testFileLock, true)
+    }
+
+    def "leaves empty lock file after shared lock on new file closed"() {
+        when:
+        def lock = createLock(Shared)
+        lock.close()
+
+        then:
+        lock.isLockFile(testFileLock)
+
+        and:
+        isEmptyLockFile(testFileLock)
+
+        when:
+        lock = createLock(Shared)
+        lock.close()
+
+        then:
+        lock.isLockFile(testFileLock)
+
+        and:
+        isEmptyLockFile(testFileLock)
+    }
+
+    def "leaves lock file after lock on existing file is closed"() {
+        given:
+        writeFile()
+
+        when:
+        def lock = createLock(mode)
+        lock.close()
+
+        then:
+        lock.isLockFile(testFileLock)
+
+        and:
+        isVersionLockFile(testFileLock, false)
+
+        where:
+        mode << [Shared, Exclusive]
+    }
+
+    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
+    def "writes lock file with info region while exclusive lock is open"() {
+        expect:
+        def lock = createLock(Exclusive)
+        isVersionLockFileWithInfoRegion(testFileLock, true)
+        lock.writeFile {}
+        isVersionLockFileWithInfoRegion(testFileLock, false)
+
+        cleanup:
+        lock?.close()
+    }
+
+    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
+    def "writes dirty lock file with info region while updating file"() {
+        given:
+        writeFile()
+
+        when:
+        def lock = createLock(Exclusive)
+
+        then:
+        lock.isLockFile(testFileLock)
+
+        expect:
+        isVersionLockFileWithInfoRegion(testFileLock, false)
+        lock.writeFile {
+            isVersionLockFileWithInfoRegion(testFileLock, true)
+        }
+        isVersionLockFileWithInfoRegion(testFileLock, false)
+
+        cleanup:
+        lock?.close()
+    }
+
+    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
+    def "long descriptor strings are trimmed when written to information region"() {
+        setup:
+        def customMetaDataProvider = Mock(ProcessMetaDataProvider)
+        def processIdentifier = RandomStringUtils.randomAlphanumeric(1000)
+        1 * customMetaDataProvider.processIdentifier >> processIdentifier
+        def customManager = new DefaultFileLockManager(customMetaDataProvider, 5000, contentionHandler, generator)
+        def operationalDisplayName = RandomStringUtils.randomAlphanumeric(1000)
+
+        when:
+        def lock = customManager.lock(testFile, options().withMode(Exclusive), "targetDisplayName", operationalDisplayName)
+
+        then:
+        isVersionLockFileWithInfoRegion(testFileLock, true, processIdentifier.substring(0, LockInfoSerializer.INFORMATION_REGION_DESCR_CHUNK_LIMIT), operationalDisplayName.substring(0, LockInfoSerializer.INFORMATION_REGION_DESCR_CHUNK_LIMIT))
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "require exclusive lock for writing"() {
+        given:
+        def lock = createLock(Shared)
+
+        when:
+        lock.writeFile {}
+
+        then:
+        thrown InsufficientLockModeException
+    }
+
+    def "require exclusive lock for updating"() {
+        given:
+        writeFile()
+        def lock = createLock(Shared)
+
+        when:
+        lock.updateFile {}
+
+        then:
+        thrown InsufficientLockModeException
+
+        cleanup:
+        lock?.close()
+    }
+
+    void isEmptyLockFile(TestFile lockFile) {
+        assert lockFile.isFile()
+        assert lockFile.length() == 0
+    }
+
+    abstract void isVersionLockFile(TestFile lockFile, boolean dirty)
+
+    void isVersionLockFileWithInfoRegion(TestFile lockFile, boolean dirty) {
+        isVersionLockFileWithInfoRegion(lockFile, dirty, "123", "operation")
+    }
+
+    abstract void isVersionLockFileWithInfoRegion(TestFile lockFile, boolean dirty, String processIdentifier, String operationalName)
+
+    FileLock createLock(LockMode lockMode, File file = testFile, FileLockManager lockManager = manager) {
+        lockManager.lock(file, options().withMode(lockMode), "foo", "operation")
+    }
+
+    protected abstract LockOptionsBuilder options();
+
+    protected void writeFile(FileLockManager lockManager = manager) {
+        def lock = lockManager.lock(testFile, options().withMode(Exclusive), "foo", "operation")
+        try {
+            lock.writeFile { }
+        } finally {
+            lock.close()
+        }
+    }
+
+    protected void unlockUncleanly(FileLockManager lockManager = manager) {
+        def lock = createLock(Exclusive, testFile, lockManager)
+        def failure = new RuntimeException()
+        try {
+            lock.writeFile {
+                throw failure
+            }
+        } catch(RuntimeException e) {
+            if (e != failure) {
+                throw e
+            }
+        } finally {
+            lock.close()
+        }
+    }
+
+    protected void partiallyWritten(FileLockManager lockManager = manager) {
+        createLock(Exclusive, testFile, lockManager).close()
+        assert testFileLock.length() > 1
+        def file = new RandomAccessFile(testFileLock, "rw")
+        try {
+            file.setLength(1)
+        } finally {
+            file.close()
+        }
+    }
+}
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 fa4ffc9..1ca4464 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,428 +15,541 @@
  */
 package org.gradle.cache.internal
 
+import org.gradle.cache.PersistentIndexedCacheParameters
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache
 import org.gradle.internal.Factory
 import org.gradle.messaging.serialize.Serializer
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import spock.lang.Specification
+import spock.lang.Unroll
 
 import static org.gradle.cache.internal.FileLockManager.LockMode.*
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
 
-class DefaultCacheAccessTest extends Specification {
+class DefaultCacheAccessTest extends ConcurrentSpec {
     @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final FileLockManager lockManager = Mock()
+    final CacheInitializationAction initializationAction = Mock()
     final File lockFile = tmpDir.file('lock.bin')
-    final File targetFile = tmpDir.file('cache.bin')
+    final File cacheDir = tmpDir.file('caches')
     final FileLock lock = Mock()
     final BTreePersistentIndexedCache<String, Integer> backingCache = Mock()
-    final DefaultCacheAccess manager = new DefaultCacheAccess("<display-name>", lockFile, lockManager) {
-        @Override
-        def <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
-            return backingCache
+    DefaultCacheAccess access = newAccess()
+
+    private DefaultCacheAccess newAccess() {
+        new DefaultCacheAccess("<display-name>", lockFile, cacheDir, lockManager, initializationAction) {
+            @Override
+            def <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+                return backingCache
+            }
         }
     }
 
-    def "acquires lock on open and releases on close when initial lock mode is not none"() {
+    def "acquires lock on open and releases on close when lock mode is shared"() {
         when:
-        manager.open(Shared)
+        access.open(mode(Shared))
 
         then:
-        1 * lockManager.lock(lockFile, Shared, "<display-name>") >> lock
+        1 * lockManager.lock(lockFile, mode(Shared), "<display-name>") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> false
+        _ * lock.state
         0 * _._
 
+        and:
+        access.owner == Thread.currentThread()
+
         when:
-        manager.close()
+        access.close()
 
         then:
+        _ * lock.state
         1 * lock.close()
         0 * _._
+
+        and:
+        !access.owner
     }
 
-    def "does not acquires lock on open when initial lock mode is none"() {
+    def "acquires lock on open and releases on close when lock mode is exclusive"() {
         when:
-        manager.open(None)
+        access.open(mode(Exclusive))
 
         then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> false
+        _ * lock.state
         0 * _._
 
+        and:
+        access.owner == Thread.currentThread()
+
         when:
-        manager.close()
+        access.close()
 
         then:
+        _ * lock.state
+        1 * lock.close()
         0 * _._
-    }
 
-    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()
+        and:
+        !access.owner
+    }
 
-        given:
-        manager.open(None)
+    def "initializes cache on open when lock mode is shared by upgrading lock"() {
+        def exclusiveLock = Mock(FileLock)
+        def sharedLock = Mock(FileLock)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(Shared))
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Shared), "<display-name>") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> true
+        1 * lock.close()
 
         then:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", _) >> exclusiveLock
+        1 * initializationAction.requiresInitialization(exclusiveLock) >> true
+        1 * exclusiveLock.writeFile(_) >> { Runnable r -> r.run() }
+        1 * initializationAction.initialize(exclusiveLock)
+        1 * exclusiveLock.close()
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Shared), "<display-name>") >> sharedLock
+        1 * initializationAction.requiresInitialization(sharedLock) >> false
+        _ * sharedLock.state
+        0 * _._
 
         and:
-        1 * action.create()
+        access.owner == Thread.currentThread()
+    }
+
+    def "initializes cache on open when lock mode is exclusive"() {
+        when:
+        access.open(mode(Exclusive))
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> true
+        1 * lock.writeFile(_) >> { Runnable r -> r.run() }
+        1 * initializationAction.initialize(lock)
+        _ * lock.state
+        0 * _._
 
         and:
+        access.owner == Thread.currentThread()
+    }
+
+    def "cleans up when cache validation fails"() {
+        def failure = new RuntimeException()
+
+        when:
+        access.open(mode(Exclusive))
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> { throw failure }
         1 * lock.close()
         0 * _._
-    }
 
-    def "does not acquire lock at start of cache action when initial lock mode is exclusive"() {
-        Factory<String> action = Mock()
+        and:
+        RuntimeException e = thrown()
+        e == failure
+    }
 
-        given:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
-        manager.open(Exclusive)
-        def cache = manager.newCache(targetFile, String, Integer)
+    def "cleans up when initialization fails"() {
+        def failure = new RuntimeException()
+        def exclusiveLock = Mock(FileLock)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(Shared))
 
         then:
-        1 * action.create() >> {
-            canAccess cache
-        }
-        _ * lock.readFile(_)
-        _ * lock.writeFile(_)
+        1 * lockManager.lock(lockFile, mode(Shared), "<display-name>") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> true
+        1 * lock.close()
 
-        and:
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", _) >> exclusiveLock
+        1 * initializationAction.requiresInitialization(exclusiveLock) >> true
+        1 * exclusiveLock.writeFile(_) >> { Runnable r -> r.run() }
+        1 * initializationAction.initialize(exclusiveLock) >> { throw failure }
+        1 * exclusiveLock.close()
         0 * _._
+
+        and:
+        RuntimeException e = thrown()
+        e == failure
     }
 
-    def "can create cache instance outside of cache action"() {
-        given:
-        manager.open(None)
+    def "initializes cache on open when lock mode is none"() {
+        def action = Mock(Runnable)
+        def contentionAction
 
         when:
-        def cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
+        access.open(mode(None))
 
         then:
-        cache instanceof MultiProcessSafePersistentIndexedCache
+        0 * _._
+
+        when:
+        access.useCache("some action", action)
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "some action") >> lock
+        1 * lockManager.allowContention(lock, _ as Runnable) >> { FileLock l, Runnable r -> contentionAction = r }
+        1 * initializationAction.requiresInitialization(lock) >> true
+        1 * lock.writeFile(_) >> { Runnable r -> r.run() }
+        1 * initializationAction.initialize(lock)
+        1 * action.run()
+        _ * lock.mode >> Exclusive
+        _ * lock.state
+        0 * _._
+
+        when:
+        contentionAction.run()
+
+        then:
+        1 * lock.close()
+
+        when:
+        access.useCache("some action", action)
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "some action") >> lock
+        1 * lockManager.allowContention(lock, _ as Runnable) >> { FileLock l, Runnable r -> contentionAction = r }
+        1 * initializationAction.requiresInitialization(lock) >> true
+        1 * lock.writeFile(_) >> { Runnable r -> r.run() }
+        1 * initializationAction.initialize(lock)
+        1 * action.run()
+        _ * lock.mode >> Exclusive
+        _ * lock.state
         0 * _._
     }
 
-    def "can create cache instance inside of cache action"() {
-        def cache
+    def "does not acquire lock on open when initial lock mode is none"() {
+        when:
+        access.open(mode(None))
 
-        given:
-        manager.open(None)
+        then:
+        0 * _._
 
         when:
-        manager.useCache("init", {
-            cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
-        } as Factory)
+        access.close()
 
         then:
-        cache instanceof MultiProcessSafePersistentIndexedCache
+        0 * _._
 
         and:
-        1 * lockManager.lock(lockFile, Exclusive, _, _) >> lock
+        !access.owner
     }
 
-    def "can use cache instance during cache action"() {
-        Factory<String> action = Mock()
+    @Unroll
+    def "cannot be opened more than once for mode #lockMode"() {
+        lockManager.lock(lockFile, lockMode, "<display-name>") >> lock
 
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
+        when:
+        access.open(lockMode)
+        access.open(lockMode)
+
+        then:
+        thrown(IllegalStateException)
+
+        where:
+        lockMode << [mode(Shared), mode(Exclusive), mode(None)]
+    }
+
+    def "using cache pushes an operation and acquires lock but does not release it at the end of the operation"() {
+        Factory<String> action = Mock()
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(None))
+        access.useCache("some operation", action)
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "some operation") >> lock
+        1 * initializationAction.requiresInitialization(lock) >> false
+        _ * lock.state
+        1 * lockManager.allowContention(lock, _ as Runnable)
 
         then:
         1 * action.create() >> {
-            canAccess cache
+            assert access.owner == Thread.currentThread()
         }
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
 
-        and:
-        _ * lock.writeFile(_)
-        1 * lock.close()
+        then:
+        1 * lock.getMode() >> Exclusive
         0 * _._
+
+        and:
+        !access.owner
     }
 
-    def "releases lock before long running operation and reacquires after"() {
+    def "nested use cache operation does not release the lock"() {
         Factory<String> action = Mock()
-        Factory<String> longRunningAction = Mock()
-
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(None))
+        access.useCache("some operation", action)
 
         then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", _) >> lock
         1 * action.create() >> {
-            canAccess cache
-            manager.longRunningOperation("nested", longRunningAction)
-            canAccess cache
+            access.useCache("nested operation") {
+                assert access.owner == Thread.currentThread()
+            }
         }
-        1 * longRunningAction.create()
-        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
-        _ * lock.writeFile(_)
-        2 * lock.close()
-        0 * _._
+
+        then:
+        !access.owner
     }
 
-    def "releases lock before nested long running operation and reacquires after"() {
+    def "use cache operation reuses existing file lock"() {
         Factory<String> action = Mock()
-        Factory<String> lockInsideLongRunningOperation = Mock()
-        Factory<String> nestedLongRunningAction = Mock()
-        Factory<String> deeplyNestedAction = Mock()
-
-        FileLock anotherLock = Mock()
-
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(None))
+        access.useCache("some operation", action)
 
         then:
-        1 * action.create() >> {
-            canAccess cache
-            manager.longRunningOperation("nested", lockInsideLongRunningOperation)
-            canAccess cache
-        }
-        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
-        }
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "some operation") >> lock
+        1 * action.create() >> { assert access.owner == Thread.currentThread() }
 
-        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
-        _ * lock.writeFile(_)
-        2 * lock.close()
+        when:
+        access.useCache("some other operation", action)
 
-        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested operation") >> anotherLock
-        _ * anotherLock.readFile(_)
-        _ * anotherLock.writeFile(_)
-        2 * anotherLock.close()
+        then:
+        0 * lockManager._
+        1 * action.create() >> { assert access.owner == Thread.currentThread() }
         0 * _._
+
+        and:
+        !access.owner
     }
 
-    def "cannot run long running operation from outside cache action"() {
+    def "use cache operation does not allow shared locks"() {
         given:
-        manager.open(None)
+        1 * lockManager.lock(lockFile, mode(Shared), "<display-name>") >> lock
+        access.open(mode(Shared))
 
         when:
-        manager.longRunningOperation("operation", Mock(Factory))
+        access.useCache("some operation", Mock(Factory))
 
         then:
-        IllegalStateException e = thrown()
-        e.message == 'Cannot start long running operation, as the <display-name> has not been locked.'
+        thrown(UnsupportedOperationException)
     }
 
-    def "cannot use cache from within long running operation"() {
+    def "long running operation pushes an operation and releases ownership but not lock"() {
         Factory<String> action = Mock()
-        Factory<String> longRunningAction = Mock()
 
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
+        when:
+        access.open(mode(Exclusive))
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
+        access.owner == Thread.currentThread()
 
         when:
-        manager.useCache("some operation", action)
+        access.longRunningOperation("some operation", action)
+
+        then:
+        _ * lock.mode >> Exclusive
+        0 * lock._
 
         then:
-        _ * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         1 * action.create() >> {
-            manager.longRunningOperation("nested", longRunningAction)
-        }
-        1 * longRunningAction.create() >> {
-            cannotAccess cache
+            assert !access.owner
         }
+
+        then:
+        0 * _._
+
+        then:
+        access.owner == Thread.currentThread()
     }
 
-    def "can execute cache action from within long running operation"() {
+    def "long running operation closes the lock if contended during action"() {
         Factory<String> action = Mock()
-        Factory<String> longRunningAction = Mock()
-        Factory<String> nestedAction = Mock()
-
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(Exclusive))
 
         then:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
 
-        and:
+        when:
+        access.longRunningOperation("some operation", action)
+
+        then:
         1 * action.create() >> {
-            canAccess cache
-            manager.longRunningOperation("nested", longRunningAction)
-            canAccess cache
+            access.whenContended().run()
         }
+        1 * lock.close()
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", _) >> lock
+    }
 
-        and:
+    def "long running operation closes the lock if contended before action"() {
+        Factory<String> action = Mock()
+
+        when:
+        access.open(mode(Exclusive))
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
+
+        when:
+        access.whenContended().run()
+        access.longRunningOperation("some operation", action)
+
+        then:
+        1 * action.create()
         1 * lock.close()
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", _) >> lock
+    }
 
-        and:
-        1 * longRunningAction.create() >> {
-            cannotAccess cache
-            manager.useCache("nested 2", nestedAction)
-            cannotAccess cache
-        }
+    def "top-level long running operation does not lock file"() {
+        Factory<String> action = Mock()
 
-        and:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested 2") >> lock
+        when:
+        access.open(mode(None))
+        access.longRunningOperation("some operation", action)
 
-        and:
-        1 * nestedAction.create() >> {
-            canAccess cache
+        then:
+        1 * action.create() >> {
+            assert !access.owner
         }
 
-        and:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
-        _ * lock.writeFile(_)
-        2 * lock.close()
-        0 * _._
+        then:
+        0 * lock._
+        0 * lockManager._
     }
 
-    def "can execute long running operation from within long running operation"() {
+    def "re-entrant long running operation does not lock file"() {
         Factory<String> action = Mock()
-        Factory<String> longRunningAction = Mock()
-        Factory<String> nestedAction = Mock()
-
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(None))
+        access.longRunningOperation("some operation", action)
 
         then:
         1 * action.create() >> {
-            canAccess cache
-            manager.longRunningOperation("nested", longRunningAction)
-            canAccess cache
-        }
-        1 * longRunningAction.create() >> {
-            cannotAccess cache
-            manager.longRunningOperation("nested 2", nestedAction)
-            cannotAccess cache
+            access.longRunningOperation("other operation") {
+                assert !access.owner
+            }
         }
-        1 * nestedAction.create() >> {
-            cannotAccess cache
-        }
-        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
-        _ * lock.writeFile(_)
-        2 * lock.close()
+
+        then:
+        0 * lock._
+        0 * lockManager._
+    }
+
+    def "can create new cache"() {
+        when:
+        def cache = access.newCache(new PersistentIndexedCacheParameters('cache', String.class, Integer.class))
+
+        then:
+        cache instanceof MultiProcessSafePersistentIndexedCache
         0 * _._
     }
 
-    def "can execute cache action from within cache action"() {
+    def "contended action does nothing when no lock"() {
+        when:
+        access.whenContended().run()
+
+        then:
+        0 * _._
+    }
+
+    def "contended action safely closes the lock when cache is not busy"() {
         Factory<String> action = Mock()
-        Factory<String> nestedAction = Mock()
 
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
+        when:
+        access.open(mode(None))
+        access.useCache("some operation", action)
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", _) >> lock
 
         when:
-        manager.useCache("some operation", action)
+        access.whenContended().run()
 
         then:
-        1 * action.create() >> {
-            canAccess cache
-            manager.useCache("nested", nestedAction)
-            canAccess cache
-        }
-        1 * nestedAction.create() >> {
-            canAccess cache
-        }
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
-        _ * lock.writeFile(_)
         1 * lock.close()
-        0 * _._
     }
 
-    def "closes caches at the end of the cache action when initial lock mode is none"() {
-        Factory<String> action = Mock()
+    def "file access requires acquired lock"() {
+        def runnable = Mock(Runnable)
 
-        given:
-        manager.open(None)
-        def cache = manager.newCache(targetFile, String, Integer)
+        when:
+        access.open(mode(None))
+        access.fileAccess.updateFile(runnable)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "file access is available when lock is acquired"() {
+        def runnable = Mock(Runnable)
 
         when:
-        manager.useCache("some operation", action)
+        access.open(mode(Exclusive))
+        access.fileAccess.updateFile(runnable)
 
         then:
-        1 * action.create() >> {
-            canAccess cache
-        }
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFile(_)
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>") >> lock
+        1 * lock.updateFile(runnable)
+    }
 
-        and:
-        _ * lock.writeFile(_) >> {Runnable runnable -> runnable.run()}
-        1 * backingCache.close()
-        1 * lock.close()
-        0 * _._
+    def "file access is available when there is an owner"() {
+        def runnable = Mock(Runnable)
+
+        when:
+        access.open(mode(None))
+        access.useCache("use cache", { access.fileAccess.updateFile(runnable)})
+
+        then:
+        1 * lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "use cache") >> lock
+        1 * lock.updateFile(runnable)
     }
 
-    def "closes caches on close when initial lock mode is not none"() {
-        given:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
-        _ * lock.readFile(_) >> {Factory factory -> factory.create()}
-        _ * lock.writeFile(_) >> {Runnable runnable -> runnable.run()}
+    def "file access can not be accessed when there is no owner"() {
+        def runnable = Mock(Runnable)
 
-        and:
-        manager.open(Exclusive)
-        def cache = manager.newCache(targetFile, String, Integer)
-        cache.get("key")
+        given:
+        lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "use cache") >> lock
+        access.open(mode(None))
+        access.useCache("use cache", runnable)
 
         when:
-        manager.close()
+        access.fileAccess.updateFile(runnable)
 
         then:
-        _ * lock.readFile(_) >> {Factory factory -> factory.create()}
-        _ * lock.writeFile(_) >> {Runnable runnable -> runnable.run()}
-        1 * backingCache.close()
-        1 * lock.close()
-        0 * _._
+        thrown(IllegalStateException)
     }
 
-    def canAccess(def cache) {
-        try {
-            cache.get("key")
-        } catch (IllegalStateException e) {
-            assert false: "Should be able to access cache here"
-        }
+    def "can close cache when the cache has not been used"() {
+        when:
+        access.open(mode(None))
+        access.close()
+
+        then:
+        0 * _
     }
 
-    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.'
-        }
+    def "can close cache when there is no owner"() {
+        given:
+        lockManager.lock(lockFile, mode(Exclusive), "<display-name>", "use cache") >> lock
+        lock.writeFile(_) >> { Runnable r -> r.run() }
+        access.open(mode(None))
+        def cache = access.newCache(new PersistentIndexedCacheParameters('cache', String.class, Integer.class))
+        access.useCache("use cache", { cache.get("key") })
+
+        when:
+        access.close()
+
+        then:
+        1 * lock.close()
     }
 
-}
+}
\ No newline at end of file
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 3de6d72..6006cf8 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
@@ -18,18 +18,22 @@ package org.gradle.cache.internal
 import org.gradle.CacheUsage
 import org.gradle.api.Action
 import org.gradle.cache.CacheValidator
-import org.gradle.messaging.serialize.DefaultSerializer
+import org.gradle.cache.internal.locklistener.NoOpFileLockContentionHandler
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
+import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
+
 class DefaultCacheFactoryTest extends Specification {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final Action<?> opened = Mock()
     final Action<?> closed = Mock()
     final ProcessMetaDataProvider metaDataProvider = Mock()
-    private final DefaultCacheFactory factoryFactory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider)) {
+    private final DefaultCacheFactory factory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider, new NoOpFileLockContentionHandler())) {
         @Override
         void onOpen(Object cache) {
             opened.execute(cache)
@@ -47,289 +51,142 @@ class DefaultCacheFactoryTest extends Specification {
     }
 
     def cleanup() {
-        factoryFactory.close()
+        factory.close()
     }
 
     public void "creates directory backed store instance"() {
         when:
-        def factory = factoryFactory.create()
-        def cache = factory.openStore(tmpDir.testDirectory, "<display>", FileLockManager.LockMode.Shared, null)
+        def cache = factory.openStore(tmpDir.testDirectory, "<display>", mode(Shared), null)
 
         then:
-        cache instanceof DefaultPersistentDirectoryStore
+        cache.reference.cache instanceof DefaultPersistentDirectoryStore
         cache.baseDir == tmpDir.testDirectory
         cache.toString().startsWith "<display>"
-
-        when:
-        factory.close()
-
-        then:
-        1 * closed.execute(cache)
     }
 
     public void "creates directory backed cache instance"() {
         when:
-        def factory = factoryFactory.create()
-        def cache = factory.open(tmpDir.testDirectory, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+        def cache = factory.open(tmpDir.testDirectory, "<display>", CacheUsage.ON, null, [prop: 'value'], mode(Shared), null)
 
         then:
-        cache instanceof DefaultPersistentDirectoryCache
+        cache.reference.cache instanceof DefaultPersistentDirectoryCache
         cache.baseDir == tmpDir.testDirectory
         cache.toString().startsWith "<display>"
-
-        when:
-        factory.close()
-
-        then:
-        1 * closed.execute(cache)
-    }
-
-    public void "creates DelegateOnDemandPersistentDirectoryCache cache instance for LockMode.NONE"() {
-            when:
-            def factory = factoryFactory.create()
-            def cache = factory.open(tmpDir.testDirectory, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.None, null)
-
-            then:
-            cache instanceof DelegateOnDemandPersistentDirectoryCache
-            cache.baseDir == tmpDir.testDirectory
-            cache.toString().startsWith "On Demand Cache for <display>"
-
-            when:
-            factory.close()
-
-            then:
-            1 * closed.execute(cache)
-        }
-
-    public void "creates indexed cache instance"() {
-        when:
-        def factory = factoryFactory.create()
-        def cache = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
-
-        then:
-        cache instanceof MultiProcessSafePersistentIndexedCache
-    }
-
-    public void "creates state cache instance"() {
-        when:
-        def factory = factoryFactory.create()
-        def cache = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
-
-        then:
-        cache instanceof SimpleStateCache
     }
 
     public void "reuses directory backed cache instances"() {
         when:
-        def factory = factoryFactory.create()
-        def ref1 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
+        def ref2 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
-        ref1.is(ref2)
-        0 * closed._
-    }
+        ref1.reference.cache.is(ref2.reference.cache)
 
-    public void "reuses directory backed cache instances across multiple sessions"() {
-        when:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        def ref1 = factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-
-        then:
-        ref1.is(ref2)
-        0 * closed._
+        and:
+        1 * opened.execute(_)
+        0 * opened._
     }
 
     public void "reuses directory backed store instances"() {
         when:
-        def factory = factoryFactory.create()
-        def ref1 = factory.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory.openStore(tmpDir.testDirectory, null, mode(Exclusive), null)
+        def ref2 = factory.openStore(tmpDir.testDirectory, null, mode(Exclusive), null)
 
         then:
-        ref1.is(ref2)
-        0 * closed._
-    }
-
-    public void "reuses directory backed store instances across multiple sessions"() {
-        when:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        def ref1 = factory1.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
+        ref1.reference.cache.is(ref2.reference.cache)
 
-        then:
-        ref1.is(ref2)
-        0 * closed._
+        and:
+        1 * opened.execute(_)
+        0 * opened._
     }
 
-    public void "reuses indexed cache instances"() {
-        when:
-        def factory = factoryFactory.create()
-        def ref1 = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+    public void "closes cache instance when factory is closed"() {
+        def implementation
 
-        then:
-        ref1.is(ref2)
-        0 * closed._
-    }
-
-    public void "reuses indexed cache instances across multiple sessions"() {
-        when:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        def ref1 = factory1.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-
-        then:
-        ref1.is(ref2)
-        0 * closed._
-    }
-
-    public void "reuses state cache instances"() {
         when:
-        def factory = factoryFactory.create()
-        def ref1 = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
-        ref1.is(ref2)
-        0 * closed._
-    }
+        1 * opened.execute(_) >> { DefaultPersistentDirectoryStore s -> implementation = s }
+        0 * opened._
 
-    public void "reuses state cache instances across multiple sessions"() {
         when:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        def ref1 = factory1.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.close()
 
         then:
-        ref1.is(ref2)
-        0 * closed._
+        1 * closed.execute(implementation)
+        0 * _
     }
 
-    public void "releases directory cache instance when last reference released"() {
-        given:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def oldCache = factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+    public void "loses cache instance when reference is closed"() {
+        def implementation
 
         when:
-        factory1.close()
-        factory2.close()
+        def cache1 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
+        def cache2 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
-        1 * closed.execute(!null)
+        1 * opened.execute(_) >> { DefaultPersistentDirectoryStore s -> implementation = s }
+        0 * opened._
 
         when:
-        def factory = factoryFactory.create()
-        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        cache1.close()
 
         then:
-        !cache.is(oldCache)
-        1 * opened.execute(!null)
-        0 * closed._
-    }
-
-    public void "releases index cache instance and backing directory instance when last reference released"() {
-        given:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        factory1.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def oldCache = factory2.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        0 * _
 
         when:
-        factory1.close()
-        factory2.close()
+        cache2.close()
 
         then:
-        2 * closed.execute(!null)
-
-        when:
-        def factory = factoryFactory.create()
-        def cache = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-
-        then:
-        !cache.is(oldCache)
-        2 * opened.execute(!null)
-        0 * closed._
+        1 * closed.execute(implementation)
+        0 * _
     }
 
-    public void "releases state cache instance and backing directory instance when last reference released"() {
-        given:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        factory1.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def oldCache = factory2.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+    public void "can close cache multiple times"() {
+        def implementation
 
         when:
-        factory1.close()
-        factory2.close()
+        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
-        2 * closed.execute(!null)
+        1 * opened.execute(_) >> { DefaultPersistentDirectoryStore s -> implementation = s }
+        0 * opened._
 
         when:
-        def factory = factoryFactory.create()
-        def cache = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        cache.close()
+        cache.close()
 
         then:
-        !cache.is(oldCache)
-        2 * opened.execute(!null)
-        0 * closed._
+        1 * closed.execute(implementation)
+        0 * _
     }
 
-    public void "can open and release cache as directory and indexed and state cache"() {
-        given:
-        def factory1 = factoryFactory.create()
-        def factory2 = factoryFactory.create()
-        def oldCache = factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory2.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory2.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+    public void "can close factory after closing cache"() {
+        def implementation
 
         when:
-        factory1.close()
-        factory2.close()
+        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
-        3 * closed.execute(!null)
+        1 * opened.execute(_) >> { DefaultPersistentDirectoryStore s -> implementation = s }
+        0 * opened._
 
         when:
-        def factory = factoryFactory.create()
-        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        cache.close()
+        factory.close()
 
         then:
-        !oldCache.is(cache)
-        1 * opened.execute(!null)
-        0 * closed._
+        1 * closed.execute(implementation)
+        0 * _
     }
 
     public void "fails when directory cache is already open with different properties"() {
         given:
-        def factory = factoryFactory.create()
-        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         when:
-        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
-
-        then:
-        IllegalStateException e = thrown()
-        e.message == "Cache '${tmpDir.testDirectory}' is already open with different state."
-    }
-
-    public void "fails when directory cache is already open with different properties in different session"() {
-        given:
-        def factory1 = factoryFactory.create()
-        factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-
-        when:
-        def factory2 = factoryFactory.create()
-        factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], mode(Exclusive), null)
 
         then:
         IllegalStateException e = thrown()
@@ -338,25 +195,10 @@ class DefaultCacheFactoryTest extends Specification {
 
     public void "fails when directory cache is already open when rebuild is requested"() {
         given:
-        def factory = factoryFactory.create()
-        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-
-        when:
-        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-
-        then:
-        IllegalStateException e = thrown()
-        e.message == "Cannot rebuild cache '${tmpDir.testDirectory}' as it is already open."
-    }
-
-    public void "fails when directory cache is already open in different session when rebuild is requested"() {
-        given:
-        def factory1 = factoryFactory.create()
-        factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Exclusive), null)
 
         when:
-        def factory2 = factoryFactory.create()
-        factory2.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
         IllegalStateException e = thrown()
@@ -365,11 +207,10 @@ class DefaultCacheFactoryTest extends Specification {
 
     public void "can open directory cache when rebuild is requested and cache was rebuilt in same session"() {
         given:
-        def factory = factoryFactory.create()
-        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], mode(Exclusive), null)
 
         when:
-        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
         notThrown(RuntimeException)
@@ -377,13 +218,11 @@ class DefaultCacheFactoryTest extends Specification {
 
     public void "can open directory cache when rebuild is requested and has been closed"() {
         given:
-        def factory1 = factoryFactory.create()
-        factory1.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory1.close()
+        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], mode(Exclusive), null)
+        cache.close()
 
         when:
-        def factory2 = factoryFactory.create()
-        factory2.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], mode(Exclusive), null)
 
         then:
         notThrown(RuntimeException)
@@ -391,24 +230,22 @@ class DefaultCacheFactoryTest extends Specification {
 
     public void "fails when directory cache when cache is already open with different lock mode"() {
         given:
-        def factory = factoryFactory.create()
-        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], mode(Shared), null)
 
         when:
-        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], mode(Exclusive), null)
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cannot open cache '${tmpDir.testDirectory}' with exclusive lock mode as it is already open with shared lock mode."
+        e.message == "Cache '${tmpDir.testDirectory}' is already open with different options."
     }
 
     public void "can pass CacheValidator to Cache"() {
         given:
-        def factory1 = factoryFactory.create()
         CacheValidator validator = Mock()
 
         when:
-        def cache = factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, validator, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, validator, [prop: 'value'], mode(Shared), null)
 
         then:
         validator.isValid() >>> [false, true]
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
index 80931a3..7cbd017 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
@@ -17,39 +17,30 @@ package org.gradle.cache.internal
 
 import org.gradle.CacheUsage
 import org.gradle.api.Action
-import org.gradle.api.Project
-import org.gradle.api.invocation.Gradle
-import org.gradle.cache.CacheBuilder.VersionStrategy
+import org.gradle.cache.CacheBuilder
 import org.gradle.cache.CacheValidator
 import org.gradle.cache.PersistentCache
-import org.gradle.cache.PersistentIndexedCache
-import org.gradle.cache.PersistentStateCache
-import org.gradle.messaging.serialize.DefaultSerializer
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GradleVersion
 import org.junit.Rule
 import spock.lang.Specification
 
+import static org.gradle.cache.internal.FileLockManager.LockMode.None
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
+
 class DefaultCacheRepositoryTest extends Specification {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     private final TestFile homeDir = tmpDir.createDir("home")
-    private final TestFile buildRootDir = tmpDir.createDir("build")
     private final TestFile sharedCacheDir = homeDir.file("caches")
     private final String version = GradleVersion.current().version
     private final Map<String, ?> properties = [a: "value", b: "value2"]
     private final CacheFactory cacheFactory = Mock()
     private final PersistentCache cache = Mock()
-    private final Gradle gradle = Mock()
-    private final DefaultCacheRepository repository = new DefaultCacheRepository(homeDir, null, CacheUsage.ON, cacheFactory)
-
-    public void setup() {
-        Project project = Mock()
-        _ * cache.baseDir >> tmpDir.testDirectory
-        _ * gradle.rootProject >> project
-        _ * project.projectDir >> buildRootDir
-    }
+    private final CacheScopeMapping scopeMapping = Mock()
+    private final DefaultCacheRepository repository = new DefaultCacheRepository(scopeMapping, CacheUsage.ON, cacheFactory)
 
     public void createsGlobalDirectoryBackedStore() {
         when:
@@ -57,7 +48,8 @@ class DefaultCacheRepositoryTest extends Specification {
 
         then:
         result == cache
-        1 * cacheFactory.openStore(sharedCacheDir.file(version, "a/b/c"), null, FileLockManager.LockMode.Shared, null) >> cache
+        1 * scopeMapping.getBaseDirectory(null, "a/b/c", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.openStore(sharedCacheDir, null, mode(Shared), null) >> cache
         0 * cacheFactory._
     }
 
@@ -67,33 +59,8 @@ class DefaultCacheRepositoryTest extends Specification {
 
         then:
         result == cache
-        1 * cacheFactory.open(sharedCacheDir.file(version, "a/b/c"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
-        0 * cacheFactory._
-    }
-
-    public void createsGlobalIndexedCache() {
-        given:
-        PersistentIndexedCache<String, Integer> indexedCache = Mock()
-
-        when:
-        def result = repository.indexedCache(String.class, Integer.class, "key").open()
-
-        then:
-        result == indexedCache
-        1 * cacheFactory.openIndexedCache(sharedCacheDir.file(version, "key"), CacheUsage.ON, null, [:], FileLockManager.LockMode.Exclusive, {it instanceof DefaultSerializer}) >> indexedCache
-        0 * cacheFactory._
-    }
-
-    public void createsGlobalStateCache() {
-        given:
-        PersistentStateCache<String> stateCache = Mock()
-
-        when:
-        def result = repository.stateCache(String.class, "key").open()
-
-        then:
-        result == stateCache
-        1 * cacheFactory.openStateCache(sharedCacheDir.file(version, "key"), CacheUsage.ON, null, [:], FileLockManager.LockMode.Exclusive, {it instanceof DefaultSerializer}) >> stateCache
+        1 * scopeMapping.getBaseDirectory(null, "a/b/c", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, [:], mode(Shared), null) >> cache
         0 * cacheFactory._
     }
 
@@ -102,41 +69,34 @@ class DefaultCacheRepositoryTest extends Specification {
         repository.cache("a/b/c").withProperties(properties).open()
 
         then:
-        1 * cacheFactory.open(sharedCacheDir.file(version, "a/b/c"), null, CacheUsage.ON, null, properties, FileLockManager.LockMode.Shared, null) >> cache
+        1 * scopeMapping.getBaseDirectory(null, "a/b/c", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, properties, mode(Shared), null) >> cache
     }
 
-    public void createsCacheForAGradleInstance() {
-        when:
-        repository.cache("a/b/c").forObject(gradle).open()
-
-        then:
-        1 * cacheFactory.open(buildRootDir.file(".gradle", version, "a/b/c"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
-    }
-
-    public void createsCacheForAFile() {
-        final TestFile dir = tmpDir.createDir("otherDir");
-
+    public void createsScopedCache() {
         when:
-        repository.cache("a/b/c").forObject(dir).open()
+        repository.cache("scope", "a/b/c").open()
 
         then:
-        1 * cacheFactory.open(dir.file(".gradle", version, "a/b/c"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
+        1 * scopeMapping.getBaseDirectory("scope", "a/b/c", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, [:], mode(Shared), null) >> cache
     }
 
-    public void createsCrossVersionCacheThatIsInvalidatedOnVersionChange() {
+    public void createsCacheWithBaseDirectory() {
         when:
-        repository.cache("a/b/c").withVersionStrategy(VersionStrategy.SharedCacheInvalidateOnVersionChange).open()
+        repository.cache(sharedCacheDir).open()
 
         then:
-        1 * cacheFactory.open(sharedCacheDir.file("noVersion", "a/b/c"), null, CacheUsage.ON, null, ["gradle.version": version], FileLockManager.LockMode.Shared, null) >> cache
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, [:], mode(Shared), null) >> cache
     }
 
-    public void createsCrossVersionCacheForAGradleInstanceThatIsInvalidatedOnVersionChange() {
+    public void createsCrossVersionCache() {
         when:
-        repository.cache("a/b/c").withVersionStrategy(VersionStrategy.SharedCacheInvalidateOnVersionChange).forObject(gradle).open()
+        repository.cache("scope", "a/b/c").withCrossVersionCache().open()
 
         then:
-        1 * cacheFactory.open(buildRootDir.file(".gradle", "noVersion", "a/b/c"), null, CacheUsage.ON, null, ["gradle.version": version], FileLockManager.LockMode.Shared, null) >> cache
+        1 * scopeMapping.getBaseDirectory("scope", "a/b/c", CacheBuilder.VersionStrategy.SharedCache) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, [:], mode(Shared), null) >> cache
     }
 
     public void canSpecifyInitializerActionForDirectoryCache() {
@@ -146,15 +106,17 @@ class DefaultCacheRepositoryTest extends Specification {
         repository.cache("a").withInitializer(action).open()
 
         then:
-        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, action) >> cache
+        1 * scopeMapping.getBaseDirectory(null, "a", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, [:], mode(Shared), action) >> cache
     }
 
     public void canSpecifyLockModeForDirectoryCache() {
         when:
-        repository.cache("a").withLockMode(FileLockManager.LockMode.None).open()
+        repository.cache("a").withLockOptions(mode(None)).open()
 
         then:
-        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.None, null) >> cache
+        1 * scopeMapping.getBaseDirectory(null, "a", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, null, [:], mode(None), null) >> cache
     }
 
     public void canSpecifyDisplayNameForDirectoryCache() {
@@ -162,7 +124,8 @@ class DefaultCacheRepositoryTest extends Specification {
         repository.cache("a").withDisplayName("<cache>").open()
 
         then:
-        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), "<cache>", CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
+        1 * scopeMapping.getBaseDirectory(null, "a", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, "<cache>", CacheUsage.ON, null, [:], mode(Shared), null) >> cache
     }
 
     public void canSpecifyCacheValidatorForDirectoryCache() {
@@ -171,6 +134,7 @@ class DefaultCacheRepositoryTest extends Specification {
         repository.cache("a").withValidator(validator).open()
 
         then:
-        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), null, CacheUsage.ON, validator, [:], FileLockManager.LockMode.Shared, null) >> cache
+        1 * scopeMapping.getBaseDirectory(null, "a", CacheBuilder.VersionStrategy.CachePerVersion) >> sharedCacheDir
+        1 * cacheFactory.open(sharedCacheDir, null, CacheUsage.ON, validator, [:], mode(Shared), null) >> cache
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheScopeMappingTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheScopeMappingTest.groovy
new file mode 100644
index 0000000..0e87499
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheScopeMappingTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.invocation.Gradle
+import org.gradle.cache.CacheBuilder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.GradleVersion
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DefaultCacheScopeMappingTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    def userHome = tmpDir.createDir("user-home")
+    def gradleVersion = Stub(GradleVersion) {
+        getVersion() >> "version"
+    }
+    def mapping = new DefaultCacheScopeMapping(userHome, null, gradleVersion)
+
+    def "null scope maps to user home directory"() {
+        expect:
+        mapping.getBaseDirectory(null, "key", CacheBuilder.VersionStrategy.CachePerVersion) == userHome.file("caches/version/key")
+        mapping.getBaseDirectory(null, "key", CacheBuilder.VersionStrategy.SharedCache) == userHome.file("caches/key")
+    }
+
+    def "Gradle scope maps to root project directory"() {
+        def rootDir = tmpDir.createDir("root")
+        def rootProject = Stub(Project) {
+            getProjectDir() >> rootDir
+        }
+        def gradle = Stub(Gradle) {
+            getRootProject() >> rootProject
+        }
+
+        expect:
+        mapping.getBaseDirectory(gradle, "key", CacheBuilder.VersionStrategy.CachePerVersion) == rootDir.file(".gradle/version/key")
+        mapping.getBaseDirectory(gradle, "key", CacheBuilder.VersionStrategy.SharedCache) == rootDir.file(".gradle/key")
+    }
+
+    def "Project scope maps to child of root project directory"() {
+        def rootDir = tmpDir.createDir("root")
+        def rootProject = Stub(Project) {
+            getProjectDir() >> rootDir
+            getPath() >> ":"
+        }
+        rootProject.rootProject >> rootProject
+        def childProject = Stub(Project) {
+            getRootProject() >> rootProject
+            getPath() >> ":child1:child2"
+        }
+
+        expect:
+        mapping.getBaseDirectory(rootProject, "key", CacheBuilder.VersionStrategy.CachePerVersion) == rootDir.file(".gradle/version/projects/_/key")
+        mapping.getBaseDirectory(rootProject, "key", CacheBuilder.VersionStrategy.SharedCache) == rootDir.file(".gradle/projects/_/key")
+        mapping.getBaseDirectory(childProject, "key", CacheBuilder.VersionStrategy.CachePerVersion) == rootDir.file(".gradle/version/projects/_child1_child2/key")
+    }
+
+    def "Task scope maps to child of root project directory"() {
+        def rootDir = tmpDir.createDir("root")
+        def rootProject = Stub(Project) {
+            getProjectDir() >> rootDir
+        }
+        def childProject = Stub(Project) {
+            getRootProject() >> rootProject
+        }
+        def task = Stub(Task) {
+            getProject() >> childProject
+            getPath() >> ":project:task"
+        }
+
+        expect:
+        mapping.getBaseDirectory(task, "key", CacheBuilder.VersionStrategy.CachePerVersion) == rootDir.file(".gradle/version/tasks/_project_task/key")
+        mapping.getBaseDirectory(task, "key", CacheBuilder.VersionStrategy.SharedCache) == rootDir.file(".gradle/tasks/_project_task/key")
+    }
+
+    def "Can override the build specific cache directory"() {
+        def customCacheDir = tmpDir.createDir("other")
+        def rootDir = tmpDir.createDir("root")
+        def rootProject = Stub(Project) {
+            getProjectDir() >> rootDir
+        }
+        def gradle = Stub(Gradle) {
+            getRootProject() >> rootProject
+        }
+        def childProject = Stub(Project) {
+            getRootProject() >> rootProject
+            getPath() >> ":child1:child2"
+        }
+        def task = Stub(Task) {
+            getProject() >> childProject
+            getPath() >> ":project:task"
+        }
+        def mapping = new DefaultCacheScopeMapping(userHome, customCacheDir, gradleVersion)
+
+        expect:
+        mapping.getBaseDirectory(null, "key", CacheBuilder.VersionStrategy.CachePerVersion) == userHome.file("caches/version/key")
+        mapping.getBaseDirectory(gradle, "key", CacheBuilder.VersionStrategy.CachePerVersion) == customCacheDir.file("version/key")
+        mapping.getBaseDirectory(childProject, "key", CacheBuilder.VersionStrategy.SharedCache) == customCacheDir.file("projects/_child1_child2/key")
+        mapping.getBaseDirectory(task, "key", CacheBuilder.VersionStrategy.CachePerVersion) == customCacheDir.file("version/tasks/_project_task/key")
+    }
+
+    @Unroll
+    def "can't use badly-formed key '#key'"() {
+        when:
+        mapping.getBaseDirectory(null, key, CacheBuilder.VersionStrategy.CachePerVersion)
+
+        then:
+        thrown(IllegalArgumentException)
+
+        where:
+        key << ["tasks", "projects", "1.11", "1.2.3.4", "", "/", "..", "c:/some-dir", "\n", "a\\b", " no white space "]
+    }
+
+    @Unroll
+    def "can use well-formed key '#key'"() {
+        when:
+        mapping.getBaseDirectory(null, key, CacheBuilder.VersionStrategy.CachePerVersion)
+
+        then:
+        noExceptionThrown()
+
+        where:
+        key << ["abc", "a/b/c", "module-1.2"]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
deleted file mode 100644
index f43bc06..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
+++ /dev/null
@@ -1,474 +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.cache.internal
-
-import org.apache.commons.lang.RandomStringUtils
-import org.gradle.cache.internal.FileLockManager.LockMode
-import org.gradle.internal.Factory
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import spock.lang.Specification
-import spock.lang.Unroll
-
-import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive
-import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
-
-/**
- * @author: Szczepan Faber, created at: 8/30/11
- */
-class DefaultFileLockManagerTest extends Specification {
-    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    def metaDataProvider = Mock(ProcessMetaDataProvider)
-    FileLockManager manager = new DefaultFileLockManager(metaDataProvider)
-
-    TestFile testFile
-    TestFile testFileLock
-    TestFile testDir
-    TestFile testDirLock
-
-    def setup() {
-        testFile = tmpDir.createFile("state.bin")
-        testFileLock = tmpDir.file(testFile.name + ".lock")
-        testDir = tmpDir.createDir("lockable-dir")
-        testDirLock = tmpDir.file("${testDir.name}/${testDir.name}.lock")
-
-        metaDataProvider.processIdentifier >> '123'
-        metaDataProvider.processDisplayName >> 'process'
-    }
-
-    @Unroll
-    "#operation throws integrity exception when not cleanly unlocked file"() {
-        given:
-        unlockUncleanly()
-
-        and:
-        def lock = createLock()
-
-        when:
-        lock."$operation"(arg)
-
-        then:
-        thrown FileIntegrityViolationException
-
-        where:
-        operation    | arg
-        "readFile"   | {} as Factory
-        "updateFile" | {} as Runnable
-    }
-
-    def "writeFile does not throw integrity exception when not cleanly unlocked file"() {
-        given:
-        unlockUncleanly()
-
-        when:
-        createLock(Exclusive).writeFile { }
-
-        then:
-        notThrown FileIntegrityViolationException
-    }
-
-    def "can lock a file"() {
-        when:
-        def lock = createLock()
-
-        then:
-        lock.isLockFile(tmpDir.createFile(testFile.name + ".lock"))
-
-        cleanup:
-        lock?.close()
-    }
-
-    def "can lock a directory"() {
-        when:
-        def lock = createLock(testDir)
-
-        then:
-        lock.isLockFile(testDirLock)
-
-        cleanup:
-        lock?.close()
-    }
-
-    def "can lock a file once it has been closed"() {
-        given:
-        def fileLock = createLock(Exclusive)
-        fileLock.close()
-
-        when:
-        createLock(Exclusive)
-
-        then:
-        notThrown(RuntimeException)
-    }
-
-    def "lock on new file is not unlocked cleanly"() {
-        when:
-        def lock = createLock(mode)
-
-        then:
-        !lock.unlockedCleanly
-
-        cleanup:
-        lock?.close()
-
-        where:
-        mode << [Shared, Exclusive]
-    }
-
-    def "file is 'invalid' unless a valid lock file exists"() {
-        given:
-        def lock = createLock(Exclusive)
-
-        when:
-        lock.readFile({})
-
-        then:
-        thrown FileIntegrityViolationException
-
-        when:
-        lock.updateFile({})
-
-        then:
-        thrown FileIntegrityViolationException
-
-        when:
-        lock.writeFile({})
-
-        and:
-        lock.readFile({})
-        lock.updateFile({})
-
-        then:
-        notThrown FileIntegrityViolationException
-    }
-
-    def "integrity violation exception is thrown after a failed write"() {
-        given:
-        def e = new RuntimeException()
-        def lock = createLock()
-
-        when:
-        lock.writeFile {}
-        lock.updateFile { throw e }
-
-        then:
-        thrown RuntimeException
-
-        when:
-        lock.readFile({})
-
-        then:
-        thrown FileIntegrityViolationException
-    }
-
-    def "existing lock is unlocked cleanly after writeToFile() has been called"() {
-        when:
-        def lock = this.createLock(Exclusive)
-        lock.writeFile({})
-        lock.updateFile({} as Runnable)
-
-        then:
-        lock.unlockedCleanly
-
-        when:
-        lock.close()
-        lock = createLock(Exclusive)
-
-        then:
-        lock.unlockedCleanly
-
-        cleanup:
-        lock?.close()
-    }
-
-    def "existing lock is unlocked cleanly after writeToFile() throws exception"() {
-        def failure = new RuntimeException()
-
-        when:
-        def lock = createLock(Exclusive)
-        lock.writeFile({ })
-        lock.updateFile({throw failure} as Runnable)
-
-        then:
-        RuntimeException e = thrown()
-        e == failure
-        !lock.unlockedCleanly
-
-        when:
-        lock.close()
-        lock = createLock(Exclusive)
-
-        then:
-        !lock.unlockedCleanly
-
-        cleanup:
-        lock?.close()
-    }
-
-    def "cannot lock a file twice in single process"() {
-        given:
-        createLock(Exclusive);
-
-        when:
-        createLock(Exclusive);
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot lock twice in single process for mixed modes"() {
-        given:
-        createLock(Exclusive);
-
-        when:
-        createLock(Shared);
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot lock twice in single process for shared mode"() {
-        given:
-        createLock(Shared);
-
-        when:
-        createLock(Shared);
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "can close a lock multiple times"() {
-        given:
-        def lock = createLock(Exclusive)
-        lock.close()
-
-        expect:
-        lock.close()
-    }
-
-    def "cannot read from file after lock has been closed"() {
-        given:
-        def lock = createLock(Exclusive)
-        lock.close()
-
-        when:
-        lock.readFile({} as Factory)
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot write to file after lock has been closed"() {
-        given:
-        def lock = createLock(Exclusive)
-        lock.close()
-
-        when:
-        lock.updateFile({} as Runnable)
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "leaves version 1 lock file after exclusive lock on new file closed"() {
-        when:
-        def lock = createLock(Exclusive)
-        lock.close()
-
-        then:
-        lock.isLockFile(testFileLock)
-
-        and:
-        isVersion1LockFile(testFileLock)
-    }
-
-    def "leaves empty lock file after shared lock on new file closed"() {
-        when:
-        def lock = createLock()
-        lock.close()
-
-        then:
-        lock.isLockFile(testFileLock)
-
-        and:
-        isEmptyLockFile(testFileLock)
-    }
-
-    def "leaves version 1 lock file after lock on existing file is closed"() {
-        testFileLock.withDataOutputStream {
-            it.writeByte(1)
-            it.writeBoolean(false)
-        }
-
-        when:
-        def lock = createLock(mode)
-        lock.close()
-
-        then:
-        lock.isLockFile(testFileLock)
-
-        and:
-        isVersion1LockFile(testFileLock)
-
-        where:
-        mode << [Shared, Exclusive]
-    }
-
-    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
-    def "writes version 2 lock file while exclusive lock is open"() {
-        when:
-        def lock = createLock(Exclusive)
-
-        then:
-        lock.isLockFile(testFileLock)
-
-        and:
-        isVersion2LockFile(testFileLock)
-
-        cleanup:
-        lock?.close()
-    }
-
-    def "can acquire lock on partially written lock file"() {
-        when:
-        testFileLock.withDataOutputStream {
-            it.writeByte(1)
-        }
-        def lock = createLock(mode)
-
-        then:
-        lock.isLockFile(testFileLock)
-        lock.close()
-
-        when:
-        testFileLock.withDataOutputStream {
-            it.writeByte(1)
-            it.writeBoolean(true)
-            it.writeByte(2)
-            it.writeByte(12)
-        }
-        lock = createLock(mode)
-
-        then:
-        lock.isLockFile(testFileLock)
-        lock.close()
-
-        where:
-        mode << [Shared, Exclusive]
-    }
-
-    def "fails to acquire lock on lock file with unknown version"() {
-        testFileLock.withDataOutputStream {
-            it.writeByte(125)
-        }
-
-        when:
-        createLock(mode)
-
-        then:
-        thrown(IllegalStateException)
-
-        where:
-        mode << [Shared, Exclusive]
-    }
-
-    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
-    def "long descriptor strings are trimmed when written to information region"() {
-        setup:
-        def customMetaDataProvider = Mock(ProcessMetaDataProvider)
-        def processIdentifier = RandomStringUtils.randomAlphanumeric(1000)
-        1 * customMetaDataProvider.processIdentifier >> processIdentifier
-        def customManager = new DefaultFileLockManager(customMetaDataProvider)
-        def operationalDisplayName = RandomStringUtils.randomAlphanumeric(1000)
-
-        when:
-        customManager.lock(testFile, Exclusive, "targetDisplayName", operationalDisplayName)
-
-        then:
-        isVersion2LockFile(testFileLock, processIdentifier.substring(0, DefaultFileLockManager.INFORMATION_REGION_DESCR_CHUNK_LIMIT), operationalDisplayName.substring(0, DefaultFileLockManager.INFORMATION_REGION_DESCR_CHUNK_LIMIT))
-    }
-
-    def "require exclusive lock for writing"() {
-        given:
-        def lock = createLock(Shared)
-
-        when:
-        lock.writeFile {}
-
-        then:
-        thrown InsufficientLockModeException
-    }
-
-    def "require exclusive lock for updating"() {
-        given:
-        def writeLock = createLock(Exclusive)
-        writeLock.writeFile {}
-        writeLock.close()
-
-        def lock = createLock(Shared)
-
-        when:
-        lock.updateFile {}
-
-        then:
-        thrown InsufficientLockModeException
-    }
-
-    private void isEmptyLockFile(TestFile lockFile) {
-        assert lockFile.isFile()
-        assert lockFile.length() == 0
-    }
-
-    private void isVersion1LockFile(TestFile lockFile) {
-        assert lockFile.isFile()
-        assert lockFile.length() == 2
-        lockFile.withDataInputStream { str ->
-            assert str.readByte() == 1
-            assert !str.readBoolean()
-        }
-    }
-
-    private void isVersion2LockFile(TestFile lockFile, String processIdentifier = "123", String operationalName = 'operation') {
-        assert lockFile.isFile()
-        assert lockFile.length() > 3
-        assert lockFile.length() <= 2048
-        lockFile.withDataInputStream { str ->
-            assert str.readByte() == 1
-            assert !str.readBoolean()
-            assert str.readByte() == 2
-            assert str.readUTF() == processIdentifier
-            assert str.readUTF() == operationalName
-            assert str.read() < 0
-        }
-    }
-
-    private FileLock createLock(File testFile) {
-        createLock(Shared, testFile)
-    }
-
-    private FileLock createLock(LockMode lockMode = Shared, File file = testFile) {
-        manager.lock(file, lockMode, "foo", "operation")
-    }
-
-    private File unlockUncleanly(LockMode lockMode = Shared, File file = testFile) {
-        DefaultFileLockManagerTestHelper.unlockUncleanly(file)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerWithCrossVersionProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerWithCrossVersionProtocolTest.groovy
new file mode 100644
index 0000000..e9eb279
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerWithCrossVersionProtocolTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal
+
+import org.gradle.cache.internal.filelock.LockOptionsBuilder
+import org.gradle.test.fixtures.file.TestFile
+
+class DefaultFileLockManagerWithCrossVersionProtocolTest extends AbstractFileLockManagerTest {
+    @Override
+    protected LockOptionsBuilder options() {
+        return LockOptionsBuilder.mode(FileLockManager.LockMode.None).useCrossVersionImplementation()
+    }
+
+    void isVersionLockFile(TestFile lockFile, boolean dirty) {
+        assert lockFile.isFile()
+        assert lockFile.length() == 2
+        lockFile.withDataInputStream { str ->
+            assert str.readByte() == 1
+            assert str.readBoolean() != dirty
+        }
+    }
+
+    @Override
+    void isVersionLockFileWithInfoRegion(TestFile lockFile, boolean dirty, String processIdentifier, String operationalName) {
+        assert lockFile.isFile()
+        lockFile.withDataInputStream { str ->
+            // state version + dirty flag
+            assert str.readByte() == 1
+            assert str.readBoolean() != dirty
+            // info version + port, lock-id, pid, operation-name
+            assert str.readByte() == 3
+            assert str.readInt() == 34
+            assert str.readLong() == 678L
+            assert str.readUTF() == processIdentifier
+            assert str.readUTF() == operationalName
+            assert str.read() < 0
+        }
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerWithNewProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerWithNewProtocolTest.groovy
new file mode 100644
index 0000000..924b6b0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerWithNewProtocolTest.groovy
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal
+
+import org.gradle.cache.internal.filelock.LockOptionsBuilder
+import org.gradle.test.fixtures.file.TestFile
+
+import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
+
+class DefaultFileLockManagerWithNewProtocolTest extends AbstractFileLockManagerTest {
+    @Override
+    protected LockOptionsBuilder options() {
+        return LockOptionsBuilder.mode(FileLockManager.LockMode.None)
+    }
+
+    def "a lock has been updated when never written to"() {
+        given:
+        def lock = createLock(Shared)
+        def state = lock.state
+        lock.close()
+
+        when:
+        lock = createLock(lockMode)
+
+        then:
+        lock.state.hasBeenUpdatedSince(state)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "a lock has not been updated when locking an existing file that has not been accessed since last open"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(lockMode)
+        def state = lock.state
+        lock.close()
+
+        when:
+        lock = createLock(lockMode)
+
+        then:
+        !lock.state.hasBeenUpdatedSince(state)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "a lock has not been updated when locking an existing file that has been read by another process since last open"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(lockMode)
+        def beforeAccess = lock.state
+        lock.close()
+
+        and:
+        lock = createLock(lockMode, testFile, lockManager)
+        lock.readFile {}
+        lock.close()
+
+        when:
+        lock = createLock(lockMode)
+
+        then:
+        !lock.state.hasBeenUpdatedSince(beforeAccess)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "a lock has been updated when written to by another process since last open"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(lockMode)
+        def beforeUpdate = lock.state
+        lock.close()
+
+        and:
+        writeFile(lockManager)
+        writeFile(lockManager)
+
+        when:
+        lock = createLock(lockMode)
+        def afterUpdate = lock.state
+
+        then:
+        lock.state.hasBeenUpdatedSince(beforeUpdate)
+
+        when:
+        lock.close()
+        lock = createLock(lockMode)
+
+        then:
+        lock.state.hasBeenUpdatedSince(beforeUpdate)
+        !lock.state.hasBeenUpdatedSince(afterUpdate)
+
+        when:
+        lock.close()
+        writeFile(lockManager)
+        lock = createLock(lockMode)
+
+        then:
+        lock.state.hasBeenUpdatedSince(beforeUpdate)
+        lock.state.hasBeenUpdatedSince(afterUpdate)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "a lock has been updated when written to while open"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(Exclusive)
+        def beforeUpdate = lock.state
+
+        when:
+        lock.writeFile { }
+
+        then:
+        lock.state.hasBeenUpdatedSince(beforeUpdate)
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "a lock has been updated when lock is dirty"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(lockMode)
+        def state = lock.state
+        lock.close()
+
+        and:
+        unlockUncleanly(lockManager)
+
+        when:
+        lock = createLock(lockMode)
+
+        then:
+        !lock.unlockedCleanly
+        lock.state.hasBeenUpdatedSince(state)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "a lock has been updated when lock is partially written"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(lockMode)
+        def state = lock.state
+        lock.close()
+
+        and:
+        partiallyWritten(lockManager)
+
+        when:
+        lock = createLock(lockMode)
+
+        then:
+        !lock.unlockedCleanly
+        lock.state.hasBeenUpdatedSince(state)
+
+        cleanup:
+        lock?.close()
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    def "a lock has been updated when lock file has been recreated by another process"() {
+        given:
+        def lockManager = new DefaultFileLockManager(metaDataProvider, contentionHandler)
+        writeFile(lockManager)
+
+        and:
+        def lock = createLock(lockMode)
+        def state = lock.state
+        lock.close()
+
+        and:
+        testFileLock.delete()
+        writeFile(lockManager)
+
+        when:
+        lock = createLock(lockMode)
+
+        then:
+        lock.unlockedCleanly
+        lock.state.hasBeenUpdatedSince(state)
+
+        where:
+        lockMode << [Exclusive, Shared]
+    }
+
+    void isVersionLockFile(TestFile lockFile, boolean dirty) {
+        assert lockFile.isFile()
+        assert lockFile.length() <= 2048
+        lockFile.withDataInputStream { str ->
+            // state version + creation number + sequence number
+            assert str.readByte() == 3
+            str.readLong()
+            if (dirty) {
+                assert str.readLong() == 0
+            } else {
+                assert str.readLong() != 0
+            }
+            assert str.read() < 0
+        }
+    }
+
+    void isVersionLockFileWithInfoRegion(TestFile lockFile, boolean dirty, String processIdentifier, String operationalName) {
+        assert lockFile.isFile()
+        assert lockFile.length() <= 2048
+        lockFile.withDataInputStream { str ->
+            // state version + creation number + sequence number
+            assert str.readByte() == 3
+            str.readLong()
+            if (dirty) {
+                assert str.readLong() == 0
+            } else {
+                assert str.readLong() != 0
+            }
+            // info version + port, lock-id, pid, operation-name
+            assert str.readByte() == 3
+            assert str.readInt() == 34
+            assert str.readLong() == 678L
+            assert str.readUTF() == processIdentifier
+            assert str.readUTF() == operationalName
+            assert str.read() < 0
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
index d31e055..b4c1a66 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
@@ -25,6 +25,7 @@ import spock.lang.Specification
 
 import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.createDefaultFileLockManager
 import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.unlockUncleanly
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
 
 class DefaultPersistentDirectoryCacheSpec extends Specification {
 
@@ -37,7 +38,7 @@ class DefaultPersistentDirectoryCacheSpec extends Specification {
         def init = { initd = true } as Action
         unlockUncleanly(new File(dir, "cache.properties"))
         def cache = new DefaultPersistentDirectoryCache(
-                dir, "test", CacheUsage.ON, { true } as CacheValidator, [:], FileLockManager.LockMode.Exclusive, init, createDefaultFileLockManager()
+                dir, "test", CacheUsage.ON, { true } as CacheValidator, [:], mode(FileLockManager.LockMode.Exclusive), init, createDefaultFileLockManager()
         )
         
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
index fff1a16..b5b09dd 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
@@ -20,6 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.cache.CacheOpenException;
 import org.gradle.cache.CacheValidator;
 import org.gradle.cache.PersistentCache;
+import org.gradle.cache.internal.locklistener.NoOpFileLockContentionHandler;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GUtil;
@@ -32,14 +33,15 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
 import static org.gradle.cache.internal.FileLockManager.LockMode;
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 @RunWith(JMock.class)
 public class DefaultPersistentDirectoryCacheTest {
@@ -47,7 +49,7 @@ public class DefaultPersistentDirectoryCacheTest {
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final ProcessMetaDataProvider metaDataProvider = context.mock(ProcessMetaDataProvider.class);
-    private final FileLockManager lockManager = new DefaultFileLockManager(metaDataProvider);
+    private final FileLockManager lockManager = new DefaultFileLockManager(metaDataProvider, new NoOpFileLockContentionHandler());
     private final Action<PersistentCache> action = context.mock(Action.class);
     private final CacheValidator validator = context.mock(CacheValidator.class);
     private final Map<String, String> properties = GUtil.map("prop", "value", "prop2", "other-value");
@@ -72,7 +74,7 @@ public class DefaultPersistentDirectoryCacheTest {
             one(action).execute(with(notNullValue(PersistentCache.class)));
         }});
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(emptyDir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(emptyDir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         assertThat(loadProperties(emptyDir.file("cache.properties")), equalTo(properties));
     }
@@ -85,7 +87,7 @@ public class DefaultPersistentDirectoryCacheTest {
             one(action).execute(with(notNullValue(PersistentCache.class)));
         }});
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
     }
@@ -98,7 +100,7 @@ public class DefaultPersistentDirectoryCacheTest {
             one(action).execute(with(notNullValue(PersistentCache.class)));
         }});
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
     }
@@ -111,7 +113,7 @@ public class DefaultPersistentDirectoryCacheTest {
             one(action).execute(with(notNullValue(PersistentCache.class)));
         }});
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.REBUILD, validator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.REBUILD, validator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
     }
@@ -130,35 +132,12 @@ public class DefaultPersistentDirectoryCacheTest {
 
         }});
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, invalidator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, invalidator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
     }
 
     @Test
-    public void exceptionThrownIfValidCacheCannotBeInitd() {
-        TestFile dir = createCacheDir();
-
-        context.checking(new Expectations() {{
-            allowing(action).execute(with(notNullValue(PersistentCache.class)));
-        }});
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, null, properties, LockMode.Shared, action, lockManager) {
-            @Override
-            protected boolean determineIfCacheIsValid(FileLock lock) throws IOException {
-                return false;
-            }
-        };
-
-        try {
-            cache.open();
-            fail("expected exception");
-        } catch (CacheOpenException e) {
-            assertNotNull(e); // to make block not empty
-        }
-    }
-
-    @Test
     public void rebuildsCacheWhenInitialiserFailedOnPreviousOpen() {
         TestFile dir = tmpDir.getTestDirectory().file("dir").createDir();
         final RuntimeException failure = new RuntimeException();
@@ -169,7 +148,7 @@ public class DefaultPersistentDirectoryCacheTest {
         }});
 
         try {
-            new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager).open();
+            new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), action, lockManager).open();
             fail();
         } catch (CacheOpenException e) {
             assertThat(e.getCause(), sameInstance((Throwable) failure));
@@ -179,7 +158,7 @@ public class DefaultPersistentDirectoryCacheTest {
             one(action).execute(with(notNullValue(PersistentCache.class)));
         }});
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
     }
@@ -188,7 +167,7 @@ public class DefaultPersistentDirectoryCacheTest {
     public void doesNotInitializeCacheWhenCacheDirExistsAndIsNotInvalid() {
         TestFile dir = createCacheDir();
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), action, lockManager);
         cache.open();
         dir.file("cache.properties").assertIsFile();
         dir.file("some-file").assertIsFile();
@@ -210,7 +189,7 @@ public class DefaultPersistentDirectoryCacheTest {
         properties.putAll(this.properties);
         properties.putAll(GUtil.map((Object[]) extraProps));
 
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, null, lockManager);
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, mode(LockMode.Shared), null, lockManager);
         cache.open();
         dir.file("some-file").touch();
         cache.close();
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
index d583841..7eca90a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
@@ -21,6 +21,7 @@ import spock.lang.Specification
 
 import static org.gradle.cache.internal.FileLockManager.LockMode.None
 import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
 
 class DefaultPersistentDirectoryStoreTest extends Specification {
     @Rule
@@ -29,7 +30,7 @@ class DefaultPersistentDirectoryStoreTest extends Specification {
     final FileLock lock = Mock()
     final cacheDir = tmpDir.file("dir")
     final cacheFile = cacheDir.file("some-content.bin")
-    final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", None, lockManager)
+    final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", mode(None), lockManager)
 
     def "has useful toString() implementation"() {
         expect:
@@ -59,24 +60,25 @@ class DefaultPersistentDirectoryStoreTest extends Specification {
     }
 
     def "open locks cache directory with requested mode"() {
-        final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", Shared, lockManager)
+        final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", mode(Shared), lockManager)
 
         when:
         store.open()
 
         then:
-        1 * lockManager.lock(cacheDir, Shared, "<display> ($cacheDir)") >> lock
+        1 * lockManager.lock(cacheDir, mode(Shared), "<display> ($cacheDir)") >> lock
 
         when:
         store.close()
 
         then:
+        _ * lock.state
         1 * lock.close()
         0 * _._
     }
 
     def "open does not lock cache directory when None mode requested"() {
-        final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", None, lockManager)
+        final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", mode(None), lockManager)
 
         when:
         store.open()
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
deleted file mode 100644
index 710c33e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
+++ /dev/null
@@ -1,103 +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.cache.internal
-
-import org.gradle.cache.CacheOpenException
-import org.gradle.internal.Factory
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-import spock.lang.Unroll
-
-class DelegateOnDemandPersistentDirectoryCacheSpec extends Specification {
-
-    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    DefaultPersistentDirectoryCache delegateCache = Mock()
-    File dir = tmpDir.createDir("cacheDir")
-    String displayName = "test display name"
-    FileLockManager.LockMode lockMode = FileLockManager.LockMode.None
-    FileLockManager lockManager = Mock()
-
-
-    @Unroll
-    def "#methodName opens delegate, executes #methodName on delegate and closes delegate"() {
-        given:
-        Runnable runnable = {} as Runnable
-        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
-        when:
-        cut.open()
-        and:
-        cut."${methodName}"("operation name", runnable)
-        then:
-        1 * delegateCache.open()
-        1 * delegateCache."${methodName}"("operation name", runnable) //checking against *params fails here. TODO: ping Peter for details
-        1 * delegateCache.close()
-        where:
-        methodName << ["useCache", "longRunningOperation"]
-    }
-
-    @Unroll
-    def "#methodName opens delegate, executes #methodName on delegate, closes delegate and returns delegate return value of delegates #methodName"() {
-        given:
-        org.gradle.internal.Factory factory = {-> "testResult" } as Factory
-        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
-
-        when:
-        cut.open()
-        and:
-        cut."${methodName}"("operation name", factory)
-
-        then:
-        1 * delegateCache.open()
-        1 * delegateCache."${methodName}"("operation name", factory) >> "testResult"
-        1 * delegateCache.close()
-
-        where:
-        methodName << ["useCache", "longRunningOperation"]
-    }
-
-    @Unroll
-    def "#methodName with #actionType only allowed on explicit opened cache"() {
-        given:
-        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
-        when:
-        cut."${methodName}"(* methodParameters)
-        then:
-        thrown(CacheOpenException)
-        0 * delegateCache.open()
-        0 * delegateCache."${methodName}"(* _)
-        0 * delegateCache.close()
-        where:
-        methodName             | methodParameters                                                                       | actionType // for test naming only
-        "useCache"             | ["operation name", {-> "factory return "} as org.gradle.internal.Factory]              | "Factory"
-        "useCache"             | ["operation name", {} as Runnable]                                                     | "Runnable"
-        "longRunningOperation" | ["long running operation name", {-> "factory return "} as org.gradle.internal.Factory] | "Factory"
-        "longRunningOperation" | ["long running operation name", {} as Runnable]                                        | "Runnable"
-    }
-
-    @Unroll
-    def "#methodName calls delegated close"() {
-        given:
-        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
-        when:
-        cut."${methodName}"()
-        then:
-        1 * delegateCache."${methodName}"()
-        where:
-        methodName << ['close', 'getLock']
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/FileLockCommunicatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/FileLockCommunicatorTest.groovy
new file mode 100644
index 0000000..4cc108c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/FileLockCommunicatorTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.cache.internal
+
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory
+import org.gradle.util.ConcurrentSpecification
+
+import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
+
+class FileLockCommunicatorTest extends ConcurrentSpecification {
+
+    def communicator = new FileLockCommunicator(new InetAddressFactory())
+    Long receivedId
+
+    def cleanup() {
+        communicator.stop()
+    }
+
+    def "knows port"() {
+        expect:
+        communicator.getPort() != -1
+    }
+
+    def "knows port after stopping"() {
+        when:
+        communicator.stop()
+
+        then:
+        communicator.getPort() == -1
+    }
+
+    def "can receive lock id"() {
+        start {
+            receivedId = communicator.receive()
+        }
+
+        poll {
+            assert communicator.getPort() != -1 && receivedId == null
+        }
+
+        when:
+        communicator.pingOwner(communicator.getPort(), 155, "lock")
+
+        then:
+        poll {
+            assert receivedId == 155
+        }
+    }
+
+    def "may not receive after the stop"() {
+        communicator.stop()
+        when:
+        communicator.receive()
+        then:
+        thrown(GracefullyStoppedException)
+    }
+
+    def "pinging on a port that nobody listens is safe"() {
+        when:
+        communicator.pingOwner(6666, 166, "lock")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can be stopped"() {
+        expect:
+        communicator.stop()
+    }
+
+    def "can be stopped during receive"() {
+        start {
+            try {
+                communicator.receive()
+            } catch (GracefullyStoppedException e) {}
+        }
+
+        when:
+        communicator.stop()
+
+        then:
+        finished()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
index f69d3d0..33c33d5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
@@ -22,7 +22,7 @@ import spock.lang.Specification
 class MultiProcessSafePersistentIndexedCacheTest extends Specification {
     final FileAccess fileAccess = Mock()
     final Factory<BTreePersistentIndexedCache<String, String>> factory = Mock()
-    final MultiProcessSafePersistentIndexedCache<String, String> cache = new MultiProcessSafePersistentIndexedCache<String, String>(factory, fileAccess)
+    final cache = new DefaultMultiProcessSafePersistentIndexedCache<String, String>(factory, fileAccess)
     final BTreePersistentIndexedCache<String, String> backingCache = Mock()
     
     def "opens cache on first access"() {
@@ -89,12 +89,12 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         0 * _._
     }
 
-    def "closes cache at end of unit of work"() {
+    def "closes cache when closed"() {
         given:
         cacheOpened()
 
         when:
-        cache.onEndWork()
+        cache.close()
 
         then:
         1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
@@ -102,14 +102,6 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         0 * _._
     }
 
-    def "does nothing at end of unit of work when cache is not open"() {
-        when:
-        cache.onEndWork()
-
-        then:
-        0 * _._
-    }
-
     def "does nothing on close when cache is not open"() {
         when:
         cache.close()
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 e5cd076..5ca9bc4 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
@@ -21,6 +21,8 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
+
 class OnDemandFileAccessTest extends Specification {
     final FileLockManager manager = Mock()
     final FileLock targetLock = Mock()
@@ -42,7 +44,7 @@ class OnDemandFileAccessTest extends Specification {
 
         then:
         !file.exists()
-        1 * manager.lock(file, LockMode.Shared, "some-lock") >> targetLock
+        1 * manager.lock(file, mode(LockMode.Shared), "some-lock") >> targetLock
         1 * targetLock.readFile(action)
         1 * targetLock.close()
         0 * targetLock._
@@ -56,7 +58,7 @@ class OnDemandFileAccessTest extends Specification {
 
         then:
         !file.exists()
-        1 * manager.lock(file, LockMode.Exclusive, "some-lock") >> targetLock
+        1 * manager.lock(file, mode(LockMode.Exclusive), "some-lock") >> targetLock
         1 * targetLock.updateFile(action)
         1 * targetLock.close()
         0 * targetLock._
@@ -70,7 +72,7 @@ class OnDemandFileAccessTest extends Specification {
 
         then:
         !file.exists()
-        1 * manager.lock(file, LockMode.Exclusive, "some-lock") >> targetLock
+        1 * manager.lock(file, mode(LockMode.Exclusive), "some-lock") >> targetLock
         1 * targetLock.writeFile(action)
         1 * targetLock.close()
         0 * targetLock._
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/cacheops/CacheAccessOperationsStackTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/cacheops/CacheAccessOperationsStackTest.groovy
new file mode 100644
index 0000000..667871c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/cacheops/CacheAccessOperationsStackTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.cacheops
+
+import org.gradle.util.ConcurrentSpecification
+
+class CacheAccessOperationsStackTest extends ConcurrentSpecification {
+
+    def stack = new CacheAccessOperationsStack()
+
+    def "maintains operations per thread"() {
+        expect:
+        start {
+            assert !stack.inCacheAction
+            stack.pushCacheAction("foo1")
+            stack.pushCacheAction("foo2")
+            assert stack.inCacheAction
+            assert stack.description == "foo2"
+        }
+        start {
+            assert !stack.inCacheAction
+            stack.pushCacheAction("bar1")
+            stack.pushCacheAction("bar2")
+            assert stack.inCacheAction
+            assert stack.description == "bar2"
+        }
+        finished()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/cacheops/CacheOperationStackTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/cacheops/CacheOperationStackTest.groovy
new file mode 100644
index 0000000..0e15437
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/cacheops/CacheOperationStackTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.cacheops
+
+import spock.lang.Specification
+
+class CacheOperationStackTest extends Specification {
+
+    def stack = new CacheOperationStack()
+
+    def "provides no description initially"() {
+        when:
+        stack.description
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "manages long running operations"() {
+        when:
+        stack.pushLongRunningOperation("long")
+        then:
+        stack.description == "long"
+
+        when:
+        stack.pushLongRunningOperation("long2")
+        then:
+        stack.description == "long2"
+
+        when:
+        stack.popLongRunningOperation()
+        then:
+        stack.description == "long"
+
+        when:
+        stack.popLongRunningOperation()
+        and:
+        stack.description
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "manages cache actions"() {
+        when:
+        stack.pushCacheAction("foo")
+        then:
+        stack.description == "foo"
+
+        when:
+        stack.pushCacheAction("foo2")
+        then:
+        stack.description == "foo2"
+
+        when:
+        stack.popCacheAction()
+        then:
+        stack.description == "foo"
+
+        when:
+        stack.popCacheAction()
+        and:
+        stack.description
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "prevents popping latest operation if the kind does not match"() {
+        stack.pushCacheAction("foo")
+
+        when:
+        stack.popLongRunningOperation()
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "knows the kind of current cache operation"() {
+        assert !stack.isInCacheAction()
+
+        when:
+        stack.pushLongRunningOperation("long")
+        then:
+        !stack.inCacheAction
+        stack.inLongRunningOperation
+
+        when:
+        stack.pushCacheAction("cache")
+        then:
+        stack.inCacheAction
+        !stack.inLongRunningOperation
+
+        when:
+        stack.pushCacheAction("cache2")
+        then:
+        stack.inCacheAction
+        !stack.inLongRunningOperation
+
+        when:
+        stack.popCacheAction()
+        stack.popCacheAction()
+        then:
+        !stack.inCacheAction
+        stack.inLongRunningOperation
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/filelock/LockOptionsBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/filelock/LockOptionsBuilderTest.groovy
new file mode 100644
index 0000000..0ce2a05
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/filelock/LockOptionsBuilderTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.filelock
+
+import spock.lang.Specification
+
+import static org.gradle.cache.internal.FileLockManager.LockMode.*
+
+class LockOptionsBuilderTest extends Specification {
+    def "can make copy of options"() {
+        def builder = LockOptionsBuilder.mode(Exclusive).useCrossVersionImplementation()
+
+        when:
+        def copy = builder.withMode(Shared)
+
+        then:
+        !copy.is(builder)
+        copy.mode == Shared
+        copy.useCrossVersionImplementation
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/locklistener/DefaultFileLockContentionHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/locklistener/DefaultFileLockContentionHandlerTest.groovy
new file mode 100644
index 0000000..3440b77
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/locklistener/DefaultFileLockContentionHandlerTest.groovy
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.locklistener
+
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory
+import org.gradle.util.ConcurrentSpecification
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
+
+class DefaultFileLockContentionHandlerTest extends ConcurrentSpecification {
+    def addressFactory = new InetAddressFactory()
+    def handler = new DefaultFileLockContentionHandler(executorFactory, addressFactory)
+    def client = new DefaultFileLockContentionHandler(executorFactory, addressFactory)
+
+    def cleanup() {
+        handler?.stop()
+        client?.stop()
+    }
+
+    def "manages contention for multiple locks"() {
+        def action1 = new AtomicBoolean()
+        def action2 = new AtomicBoolean()
+
+        when:
+        int port = handler.reservePort()
+        handler.start(10, { action1.set(true) })
+        handler.start(11, { action2.set(true) })
+
+        client.pingOwner(port, 10, "lock 1")
+        client.pingOwner(port, 11, "lock 2")
+
+        then:
+        poll {
+            assert action1.get() && action2.get()
+        }
+    }
+
+    def "there is only one executor thread"() {
+        def factory = Mock(ExecutorFactory)
+        handler = new DefaultFileLockContentionHandler(factory, addressFactory)
+
+        when:
+        handler.reservePort()
+        handler.start(10, {} as Runnable)
+        handler.start(11, {} as Runnable)
+
+        then:
+        1 * factory.create(_ as String) >> Mock(StoppableExecutor)
+        0 * factory._
+    }
+
+    def "cannot start contention handling when the handler was stopped"() {
+        handler.stop()
+
+        when:
+        handler.start(10, {} as Runnable)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot start contention handling when the handler was not initialized"() {
+        when:
+        handler.start(10, {} as Runnable)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "handler can be closed and contended action does not run"() {
+        when:
+        int port = handler.reservePort();
+        handler.start(10, { throw new RuntimeException("Boo!") } as Runnable)
+        handler.stop()
+
+        client.pingOwner(port, 10, "lock 1")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can receive request for lock that is already closed"() {
+        when:
+        int port = handler.reservePort()
+        handler.start(10, { assert false } as Runnable)
+        sleep(300) //so that it starts receiving
+
+        //close the lock
+        handler.stop(10)
+
+        //receive request for lock that is already closed
+        client.pingOwner(port, 10, "lock 1")
+
+        then:
+        canHandleMoreRequests()
+    }
+
+    private void canHandleMoreRequests() {
+        def executed = new AtomicBoolean()
+        int port = handler.reservePort();
+        handler.start(15, { executed.set(true) } as Runnable)
+        client.pingOwner(port, 15, "lock")
+        poll { assert executed.get() }
+    }
+
+    def "reserving port is safely reentrant"() {
+        when:
+        int port = handler.reservePort()
+
+        then:
+        handler.reservePort() == port
+    }
+
+    def "cannot reserve port when the handler was stopped"() {
+        handler.stop()
+
+        when:
+        handler.reservePort()
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "reserving port does not start the thread"() {
+        def factory = Mock(ExecutorFactory)
+        handler = new DefaultFileLockContentionHandler(factory, addressFactory)
+
+        when:
+        handler.reservePort()
+
+        then:
+        0 * factory._
+    }
+
+    def "stopping the handler stops the executor"() {
+        def factory = Mock(ExecutorFactory)
+        def executor = Mock(StoppableExecutor)
+        handler = new DefaultFileLockContentionHandler(factory, addressFactory)
+
+        when:
+        handler.reservePort()
+        handler.start(10, {} as Runnable)
+        handler.stop()
+
+        then:
+        1 * factory.create(_ as String) >> executor
+        1 * executor.stop()
+    }
+
+    def "stopping is safe even if the handler was not initialized"() {
+        when:
+        handler.stop()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "stopping is safe even if the executor was not initialized"() {
+        handler.reservePort()
+
+        when:
+        handler.stop()
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/BuildScriptProcessorTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/BuildScriptProcessorTest.java
deleted file mode 100644
index 60ae726..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/BuildScriptProcessorTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class BuildScriptProcessorTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final ScriptSource scriptSource = context.mock(ScriptSource.class);
-    private final ScriptPluginFactory configurerFactory = context.mock(ScriptPluginFactory.class);
-    private final ScriptPlugin scriptPlugin = context.mock(ScriptPlugin.class);
-    private final ProjectStateInternal state = context.mock(ProjectStateInternal.class);
-    private final BuildScriptProcessor evaluator = new BuildScriptProcessor(configurerFactory);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(project).getBuildScriptSource();
-            will(returnValue(scriptSource));
-            ignoring(scriptSource);
-        }});
-    }
-
-    @Test
-    public void configuresProjectUsingBuildScript() {
-        context.checking(new Expectations() {{
-            one(configurerFactory).create(scriptSource);
-            will(returnValue(scriptPlugin));
-
-            one(scriptPlugin).apply(project);
-        }});
-
-        evaluator.evaluate(project, state);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
index 347c903..7a7b531 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
@@ -32,16 +32,19 @@ class DefaultBuildConfigurerTest extends Specification {
     }
 
     def "configures build for standard mode"() {
+        def child1 = Mock(ProjectInternal)
+        def child2 = Mock(ProjectInternal)
+
+        given:
+        _ * rootProject.allprojects >> [rootProject, child1, child2]
+
         when:
         configurer.configure(gradle)
 
         then:
-        1 * gradle.addProjectEvaluationListener(_ as ImplicitTasksConfigurer);
-        1 * gradle.addProjectEvaluationListener(_ as ProjectDependencies2TaskResolver);
-        1 * rootProject.allprojects(!null) >> { args ->
-            assert args[0] instanceof DefaultBuildConfigurer.ConfigureProject
-        }
-        0 * rootProject._
+        1 * rootProject.evaluate()
+        1 * child1.evaluate()
+        1 * child2.evaluate()
     }
 
     def "configures build for on demand mode"() {
@@ -49,12 +52,8 @@ class DefaultBuildConfigurerTest extends Specification {
         configurer.configure(gradle)
 
         then:
-        1 * startParameter.isConfigureOnDemand() >> true
+        startParameter.isConfigureOnDemand() >> true
         1 * rootProject.evaluate()
         0 * rootProject._
-
-        and:
-        1 * gradle.addProjectEvaluationListener(_ as ImplicitTasksConfigurer);
-        1 * gradle.addProjectEvaluationListener(_ as ProjectDependencies2TaskResolver);
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy
new file mode 100644
index 0000000..85d7059
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.configuration
+
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.initialization.ScriptHandlerFactory
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.initialization.InitScript
+import spock.lang.Specification
+
+class DefaultInitScriptProcessorTest extends Specification {
+
+    void "can execute init script"() {
+        when:
+        def scriptPluginFactory = Mock(ScriptPluginFactory)
+        def scriptHandlerFactory = Mock(ScriptHandlerFactory)
+        def compileScope = Mock(ClassLoaderScope)
+        def initScriptMock = Mock(ScriptSource)
+        def gradleMock = Mock(GradleInternal)
+        def scriptHandler = Mock(ScriptHandler)
+        def scriptPlugin = Mock(ScriptPlugin)
+
+        1 * gradleMock.getClassLoaderScope() >> Mock(ClassLoaderScope) {
+            createSibling() >> compileScope
+        }
+
+        1 * scriptHandlerFactory.create(initScriptMock, compileScope) >> scriptHandler
+        1 * scriptPluginFactory.create(initScriptMock, scriptHandler, compileScope, "initscript", InitScript) >> scriptPlugin
+        1 * scriptPlugin.apply(gradleMock)
+
+        DefaultInitScriptProcessor processor = new DefaultInitScriptProcessor(scriptPluginFactory, scriptHandlerFactory)
+
+        then:
+        processor.process(initScriptMock, gradleMock)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.java
deleted file mode 100644
index da6730f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.initialization.InitScript;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class DefaultInitScriptProcessorTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void testProcess() {
-        final ScriptPluginFactory scriptPluginFactory = context.mock(ScriptPluginFactory.class);
-        final ScriptPlugin configurer = context.mock(ScriptPlugin.class);
-        final ScriptSource initScriptMock = context.mock(ScriptSource.class);
-        final GradleInternal gradleMock = context.mock(GradleInternal.class);
-
-        context.checking(new Expectations() {{
-            one(scriptPluginFactory).create(initScriptMock);
-            will(returnValue(configurer));
-
-            one(configurer).setClasspathClosureName("initscript");
-            one(configurer).setScriptBaseClass(InitScript.class);
-
-            one(configurer).apply(gradleMock);
-        }});
-
-        DefaultInitScriptProcessor processor = new DefaultInitScriptProcessor(scriptPluginFactory);
-        processor.process(initScriptMock, gradleMock);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy
new file mode 100755
index 0000000..57aabeb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.file.FileLookup
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.initialization.ScriptHandlerFactory
+import org.gradle.groovy.scripts.*
+import org.gradle.groovy.scripts.internal.StatementExtractingScriptTransformer
+import org.gradle.internal.Factory
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.plugin.internal.PluginResolverFactory
+import spock.lang.Specification
+
+public class DefaultScriptPluginFactoryTest extends Specification {
+
+    def scriptCompilerFactory = Mock(ScriptCompilerFactory)
+    def importsReader = Mock(ImportsReader)
+    def scriptCompiler = Mock(ScriptCompiler)
+    def scriptSource = Mock(ScriptSource)
+    def scriptRunner = Mock(ScriptRunner)
+    def script = Mock(BasicScript)
+    def instantiator = Mock(Instantiator)
+    def classLoaderScope = Mock(ClassLoaderScope)
+    def baseClassLoaderScope = Mock(ClassLoaderScope)
+    def scopeClassLoader = Mock(ClassLoader)
+    def baseChildClassLoader = Mock(ClassLoader)
+    def scriptHandlerFactory = Mock(ScriptHandlerFactory)
+    def pluginResolverFactory = Mock(PluginResolverFactory)
+    def scriptHandler = Mock(ScriptHandler)
+    def classPathScriptRunner = Mock(ScriptRunner)
+    def classPathScript = Mock(BasicScript)
+    def loggingManagerFactory = Mock(Factory) as Factory<LoggingManagerInternal>
+    def sourceWithImports = Mock(ScriptSource)
+    def loggingManager = Mock(LoggingManagerInternal)
+    def fileLookup = Mock(FileLookup)
+
+    def factory = new DefaultScriptPluginFactory(scriptCompilerFactory, importsReader, loggingManagerFactory, instantiator, scriptHandlerFactory, pluginResolverFactory, fileLookup)
+
+    def setup() {
+        def configurations = Mock(ConfigurationContainer)
+        scriptHandler.configurations >> configurations
+        def configuration = Mock(Configuration)
+        configurations.getByName(ScriptHandler.CLASSPATH_CONFIGURATION) >> configuration
+        configuration.getFiles() >> Collections.emptySet()
+        classLoaderScope.getBase() >> baseClassLoaderScope
+        baseClassLoaderScope.getChildClassLoader() >> baseChildClassLoader
+
+        1 * classLoaderScope.getScopeClassLoader() >> scopeClassLoader
+    }
+
+    void configuresATargetObjectUsingScript() {
+        when:
+        final Object target = new Object()
+
+        1 * loggingManagerFactory.create() >> loggingManager
+        1 * importsReader.withImports(scriptSource) >> sourceWithImports
+        1 * scriptCompilerFactory.createCompiler(sourceWithImports) >> scriptCompiler
+        1 * scriptCompiler.setClassloader(baseChildClassLoader)
+        1 * scriptCompiler.setTransformer(_ as StatementExtractingScriptTransformer)
+        1 * scriptCompiler.compile(DefaultScript) >> classPathScriptRunner
+        1 * classPathScriptRunner.getScript() >> classPathScript
+        1 * classPathScript.init(target, _ as ServiceRegistry)
+        1 * classPathScriptRunner.run()
+        1 * scriptCompiler.setClassloader(scopeClassLoader)
+        1 * scriptCompiler.setTransformer(!null)
+        1 * scriptCompiler.compile(DefaultScript) >> scriptRunner
+        1 * scriptRunner.getScript() >> script
+        1 * script.init(target, _ as ServiceRegistry)
+        1 * scriptRunner.run()
+
+        then:
+        ScriptPlugin configurer = factory.create(scriptSource, scriptHandler, classLoaderScope, "buildscript", DefaultScript)
+        configurer.apply(target)
+    }
+
+    void configuresAScriptAwareObjectUsingScript() {
+        when:
+        def target = Mock(ScriptAware)
+
+        1 * loggingManagerFactory.create() >> loggingManager
+        1 * importsReader.withImports(scriptSource) >> sourceWithImports
+        1 * scriptCompilerFactory.createCompiler(sourceWithImports) >> scriptCompiler
+        1 * scriptCompiler.setClassloader(baseChildClassLoader)
+        1 * scriptCompiler.setTransformer(_ as StatementExtractingScriptTransformer)
+        1 * scriptCompiler.compile(DefaultScript) >> classPathScriptRunner
+        1 * classPathScriptRunner.getScript() >> classPathScript
+        1 * classPathScript.init(target, _ as ServiceRegistry)
+        1 * classPathScriptRunner.run()
+        1 * scriptCompiler.setClassloader(scopeClassLoader)
+        1 * scriptCompiler.setTransformer(!null)
+        1 * scriptCompiler.compile(DefaultScript) >> scriptRunner
+        1 * scriptRunner.getScript() >> script
+        1 * script.init(target, _ as ServiceRegistry)
+        1 * scriptRunner.run()
+
+        then:
+        ScriptPlugin configurer = factory.create(scriptSource, scriptHandler, classLoaderScope, "buildscript", DefaultScript)
+        configurer.apply(target)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
deleted file mode 100755
index 556878e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.internal.Factory;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.groovy.scripts.internal.ClasspathScriptTransformer;
-import org.gradle.api.internal.initialization.ScriptHandlerFactory;
-import org.gradle.api.internal.initialization.ScriptHandlerInternal;
-import org.gradle.groovy.scripts.*;
-import org.gradle.logging.LoggingManagerInternal;
-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.Test;
-import org.junit.runner.RunWith;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-
-import static org.hamcrest.Matchers.*;
-
- at RunWith(JMock.class)
-public class DefaultScriptPluginFactoryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    private final ScriptCompilerFactory scriptCompilerFactoryMock = context.mock(ScriptCompilerFactory.class);
-    private final ImportsReader importsReaderMock = context.mock(ImportsReader.class);
-    private final ScriptCompiler scriptCompilerMock = context.mock(ScriptCompiler.class);
-    private final ScriptSource scriptSourceMock = context.mock(ScriptSource.class);
-    private final ScriptRunner scriptRunnerMock = context.mock(ScriptRunner.class, "scriptRunner");
-    private final BasicScript scriptMock = context.mock(BasicScript.class);
-    private final URLClassLoader parentClassLoader = new URLClassLoader(new URL[0]);
-    private final URLClassLoader scriptClassLoader = new URLClassLoader(new URL[0]);
-    private final ScriptHandlerFactory scriptHandlerFactoryMock = context.mock(ScriptHandlerFactory.class);
-    private final ScriptHandlerInternal scriptHandlerMock = context.mock(ScriptHandlerInternal.class);
-    private final ScriptRunner classPathScriptRunnerMock = context.mock(ScriptRunner.class, "classpathScriptRunner");
-    private final BasicScript classPathScriptMock = context.mock(BasicScript.class, "classpathScript");
-    private final Factory<LoggingManagerInternal> loggingManagerFactoryMock = context.mock(Factory.class);
-    private final DefaultScriptPluginFactory factory = new DefaultScriptPluginFactory(scriptCompilerFactoryMock, importsReaderMock, scriptHandlerFactoryMock, parentClassLoader, loggingManagerFactoryMock);
-
-    @Test
-    public void configuresATargetObjectUsingScript() {
-        final Object target = new Object();
-
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("seq");
-            ScriptSource sourceWithImportsMock = context.mock(ScriptSource.class, "imports");
-            LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
-
-            one(loggingManagerFactoryMock).create();
-            will(returnValue(loggingManagerMock));
-
-            one(importsReaderMock).withImports(scriptSourceMock);
-            will(returnValue(sourceWithImportsMock));
-
-            one(scriptCompilerFactoryMock).createCompiler(sourceWithImportsMock);
-            will(returnValue(scriptCompilerMock));
-
-            one(scriptHandlerFactoryMock).create(sourceWithImportsMock, parentClassLoader);
-            will(returnValue(scriptHandlerMock));
-
-            allowing(scriptHandlerMock).getClassLoader();
-            will(returnValue(scriptClassLoader));
-
-            one(scriptCompilerMock).setClassloader(scriptClassLoader);
-            inSequence(sequence);
-
-            one(scriptCompilerMock).setTransformer(with(any(ClasspathScriptTransformer.class)));
-            inSequence(sequence);
-
-            one(scriptCompilerMock).compile(DefaultScript.class);
-            will(returnValue(classPathScriptRunnerMock));
-
-            allowing(classPathScriptRunnerMock).getScript();
-            will(returnValue(classPathScriptMock));
-
-            one(classPathScriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
-            inSequence(sequence);
-
-            one(classPathScriptRunnerMock).run();
-            inSequence(sequence);
-
-            one(scriptHandlerMock).updateClassPath();
-            inSequence(sequence);
-            
-            one(scriptCompilerMock).setTransformer(with(notNullValue(Transformer.class)));
-            inSequence(sequence);
-            
-            one(scriptCompilerMock).compile(DefaultScript.class);
-            will(returnValue(scriptRunnerMock));
-            inSequence(sequence);
-
-            allowing(scriptRunnerMock).getScript();
-            will(returnValue(scriptMock));
-
-            one(scriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
-            inSequence(sequence);
-
-            one(scriptRunnerMock).run();
-            inSequence(sequence);
-        }});
-
-        ScriptPlugin configurer = factory.create(scriptSourceMock);
-        configurer.apply(target);
-    }
-
-    @Test
-    public void configuresAScriptAwareObjectUsingScript() {
-        final ScriptAware target = context.mock(ScriptAware.class);
-
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("seq");
-            ScriptSource sourceWithImportsMock = context.mock(ScriptSource.class, "imports");
-            LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
-
-            one(loggingManagerFactoryMock).create();
-            will(returnValue(loggingManagerMock));
-
-            one(importsReaderMock).withImports(scriptSourceMock);
-            will(returnValue(sourceWithImportsMock));
-
-            one(scriptCompilerFactoryMock).createCompiler(sourceWithImportsMock);
-            will(returnValue(scriptCompilerMock));
-
-            allowing(target).beforeCompile(with(notNullValue(ScriptPlugin.class)));
-
-            one(scriptHandlerFactoryMock).create(sourceWithImportsMock, parentClassLoader);
-            will(returnValue(scriptHandlerMock));
-            
-            allowing(scriptHandlerMock).getClassLoader();
-            will(returnValue(scriptClassLoader));
-
-            one(scriptCompilerMock).setClassloader(scriptClassLoader);
-            inSequence(sequence);
-
-            one(scriptCompilerMock).setTransformer(with(any(ClasspathScriptTransformer.class)));
-            inSequence(sequence);
-
-            one(scriptCompilerMock).compile(DefaultScript.class);
-            will(returnValue(classPathScriptRunnerMock));
-
-            allowing(classPathScriptRunnerMock).getScript();
-            will(returnValue(classPathScriptMock));
-
-            one(classPathScriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
-            inSequence(sequence);
-
-            one(classPathScriptRunnerMock).run();
-            inSequence(sequence);
-
-            one(scriptHandlerMock).updateClassPath();
-            inSequence(sequence);
-
-            one(scriptCompilerMock).setTransformer(with(notNullValue(Transformer.class)));
-            inSequence(sequence);
-
-            one(scriptCompilerMock).compile(DefaultScript.class);
-            will(returnValue(scriptRunnerMock));
-            inSequence(sequence);
-
-            allowing(scriptRunnerMock).getScript();
-            will(returnValue(scriptMock));
-
-            one(scriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
-            inSequence(sequence);
-
-            one(target).afterCompile(with(notNullValue(ScriptPlugin.class)), with(sameInstance(scriptMock)));
-            inSequence(sequence);
-
-            one(scriptRunnerMock).run();
-            inSequence(sequence);
-        }});
-
-        ScriptPlugin configurer = factory.create(scriptSourceMock);
-        configurer.apply(target);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
deleted file mode 100644
index ecbeebe..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration
-
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class ImplicitTasksConfigurerTest extends Specification {
-    private final ImplicitTasksConfigurer configurer = new ImplicitTasksConfigurer()
-    private final ProjectInternal project = HelperUtil.createRootProject()
-
-    def "applies help tasks after evaluate"() {
-        when:
-        configurer.afterEvaluate(project, null)
-
-        then:
-        project.plugins.hasPlugin('help-tasks')
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy
index 7ca7732..e75959a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy
@@ -19,27 +19,26 @@ package org.gradle.configuration
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.util.Resources
 import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
+import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
-class ImportsReaderTest {
+class ImportsReaderTest extends Specification {
     @Rule public Resources resources = new Resources()
-    ImportsReader testObj = new ImportsReader()
+    ImportsReader reader = new ImportsReader()
 
-    @Test public void testReadImportsFromResource() {
-        String result = testObj.getImports()
-        assertEquals(resources.getResource('default-imports.txt').text, result)
+    public void testReadImportsFromResource() {
+        expect:
+        reader.imports.contains('import org.gradle.api.*')
     }
 
-    @Test public void testCreatesScriptSource() {
-        ScriptSource source = [:] as ScriptSource
-        ScriptSource importsSource = testObj.withImports(source)
-        assertThat(importsSource, instanceOf(ImportsScriptSource.class))
-        assertThat(importsSource.source, sameInstance(source))
-        assertThat(importsSource.importsReader, sameInstance(testObj))
+    public void testCreatesScriptSource() {
+        def source = [:] as ScriptSource
+
+        when:
+        def importsSource = reader.withImports(source)
+
+        then:
+        importsSource instanceof ImportsScriptSource
+        importsSource.source == source
+        importsSource.importsReader == reader
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.groovy
deleted file mode 100644
index fbc6c84..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.configuration
-
-import org.gradle.api.ProjectEvaluationListener
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.project.ProjectStateInternal
-import spock.lang.Specification
-
-public class LifecycleProjectEvaluatorTest extends Specification {
-    private project = Mock(ProjectInternal)
-    private listener = Mock(ProjectEvaluationListener)
-    private delegate = Mock(ProjectEvaluator)
-    private state = Mock(ProjectStateInternal)
-    private evaluator = new LifecycleProjectEvaluator(delegate)
-
-    void setup() {
-        project.getProjectEvaluationBroadcaster() >> listener
-    }
-
-    void "nothing happens if project was already configured"() {
-        state.executed >> true
-
-        when:
-        evaluator.evaluate(project, state)
-
-        then:
-        0 * delegate._
-    }
-
-    void "nothing happens if project is being configured now"() {
-        state.executing >> true
-
-        when:
-        evaluator.evaluate(project, state)
-
-        then:
-        0 * delegate._
-    }
-    
-    void "evaluates the project firing all necessary listeners and updating the state"() {
-        when:
-        evaluator.evaluate(project, state)
-
-        then:
-        1 * listener.beforeEvaluate(project)
-        1 * state.setExecuting(true)
-
-        then:
-        1 * delegate.evaluate(project, state)
-
-        then:
-        1 * state.setExecuting(false)
-        1 * state.executed()
-        1 * listener.afterEvaluate(project, state)
-    }
-
-    void "notifies listeners and updates states even if there was evaluation failure"() {
-        def failure = new RuntimeException()
-
-        when:
-        evaluator.evaluate(project, state)
-
-        then:
-        def ex = thrown(RuntimeException)
-        ex == failure
-
-        and:
-        delegate.evaluate(project, state) >> { throw failure }
-
-        and:
-        1 * state.setExecuting(false)
-        1 * state.executed()
-        1 * listener.afterEvaluate(project, state)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
deleted file mode 100644
index b11e6a8..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
+++ /dev/null
@@ -1,40 +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.configuration
-
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter
- */
-class ProjectDependencies2TaskResolverTest extends Specification {
-    private root = HelperUtil.createRootProject()
-    private child = HelperUtil.createChildProject(root, "child")
-    private rootTask = root.tasks.add('compile')
-    private childTask = child.tasks.add('compile')
-
-    private resolver = new ProjectDependencies2TaskResolver()
-
-    void "resolves task dependencies"() {
-        child.dependsOn(root.path, false)
-        when:
-        resolver.afterEvaluate(child, null)
-        then:
-        childTask.taskDependencies.getDependencies(childTask) == [rootTask] as Set
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/BuildScriptProcessorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/BuildScriptProcessorTest.groovy
new file mode 100644
index 0000000..40044b3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/BuildScriptProcessorTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.configuration.project
+
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.ProjectScript
+import org.gradle.configuration.ScriptPlugin
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.ScriptSource
+import spock.lang.Specification
+
+public class BuildScriptProcessorTest extends Specification {
+    def project = Mock(ProjectInternal)
+    def scriptSource = Mock(ScriptSource)
+    def configurerFactory = Mock(ScriptPluginFactory)
+    def scriptPlugin = Mock(ScriptPlugin)
+    def classLoaderScope = Mock(ClassLoaderScope)
+    def BuildScriptProcessor buildScriptProcessor = new BuildScriptProcessor(configurerFactory)
+    private ScriptHandler scriptHandler;
+
+    def "setup"() {
+        _ * project.buildScriptSource >> scriptSource
+        scriptHandler = Mock(ScriptHandler)
+        project.getBuildscript() >> scriptHandler
+        project.getClassLoaderScope() >> classLoaderScope
+    }
+
+    def configuresProjectUsingBuildScript() {
+        when:
+        buildScriptProcessor.execute(project)
+
+        then:
+        1 * configurerFactory.create(scriptSource, scriptHandler, classLoaderScope, "buildscript", ProjectScript) >> scriptPlugin
+        1 * scriptPlugin.apply(project)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/ConfigureActionsProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/ConfigureActionsProjectEvaluatorTest.groovy
new file mode 100644
index 0000000..ebde2fe
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/ConfigureActionsProjectEvaluatorTest.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.configuration.project
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.ProjectStateInternal
+import spock.lang.Specification
+
+class ConfigureActionsProjectEvaluatorTest extends Specification {
+    final def project = Mock(ProjectInternal)
+    final def state = Mock(ProjectStateInternal)
+    final def action1 = Mock(ProjectConfigureAction)
+    final def action2 = Mock(ProjectConfigureAction)
+    final def evaluator = new ConfigureActionsProjectEvaluator(action1, action2)
+
+    def "executes all configuration actions"() {
+        def project = Mock(ProjectInternal)
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        1 * action1.execute(project)
+        1 * action2.execute(project)
+        0 * _._
+    }
+
+    def "does not continue executing actions when action fails"() {
+        def project = Mock(ProjectInternal)
+        def failure = new RuntimeException("Configure action failed")
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        1 * action1.execute(project) >> {
+            throw failure
+        }
+        0 * _._
+
+        and:
+        def t = thrown(RuntimeException)
+        t == failure
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/DefaultProjectConfigurationActionContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/DefaultProjectConfigurationActionContainerTest.groovy
new file mode 100644
index 0000000..30be9e5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/DefaultProjectConfigurationActionContainerTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project
+
+import org.gradle.api.Action
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+class DefaultProjectConfigurationActionContainerTest extends Specification {
+    def container = new DefaultProjectConfigurationActionContainer()
+
+    def "can add action to container"() {
+        def action = Mock(Action)
+
+        when:
+        container.add(action)
+
+        then:
+        container.actions == [action]
+    }
+
+    def "can add action as closure"() {
+        def run = false
+        def action = { run = true }
+
+        when:
+        container.add(action)
+
+        then:
+        container.actions.size() == 1
+
+        when:
+        container.actions[0].execute(Stub(ProjectInternal))
+
+        then:
+        run
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/DelayedConfigurationActionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/DelayedConfigurationActionsTest.groovy
new file mode 100644
index 0000000..4670551
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/DelayedConfigurationActionsTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.project
+
+import org.gradle.api.Action
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+class DelayedConfigurationActionsTest extends Specification {
+    final container = Mock(ProjectConfigurationActionContainer)
+    final project = Stub(ProjectInternal) {
+        getConfigurationActions() >> container
+    }
+    final action = new DelayedConfigurationActions()
+
+    def "runs actions and discards actions when finished"() {
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+
+        given:
+        container.actions >> [action1, action2]
+
+        when:
+        action.execute(project)
+
+        then:
+        1 * action1.execute(project)
+        1 * action2.execute(project)
+
+        and:
+        1 * container.finished()
+    }
+
+    def "discards actions on failure"() {
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+        def failure = new RuntimeException()
+
+        given:
+        container.actions >> [action1, action2]
+
+        when:
+        action.execute(project)
+
+        then:
+        def e = thrown(RuntimeException)
+        e == failure
+
+        and:
+        1 * action1.execute(project) >> { throw failure }
+        0 * action2._
+
+        and:
+        1 * container.finished()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/LifecycleProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/LifecycleProjectEvaluatorTest.groovy
new file mode 100644
index 0000000..ffa970c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/LifecycleProjectEvaluatorTest.groovy
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.configuration.project
+
+import org.gradle.api.ProjectConfigurationException
+import org.gradle.api.ProjectEvaluationListener
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.ProjectStateInternal
+import spock.lang.Specification
+
+public class LifecycleProjectEvaluatorTest extends Specification {
+    private project = Mock(ProjectInternal)
+    private listener = Mock(ProjectEvaluationListener)
+    private delegate = Mock(ProjectEvaluator)
+    private state = Mock(ProjectStateInternal)
+    private evaluator = new LifecycleProjectEvaluator(delegate)
+
+    void setup() {
+        project.getProjectEvaluationBroadcaster() >> listener
+        project.toString() >> "project1"
+    }
+
+    void "nothing happens if project was already configured"() {
+        state.executed >> true
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        0 * delegate._
+    }
+
+    void "nothing happens if project is being configured now"() {
+        state.executing >> true
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        0 * delegate._
+    }
+
+    void "evaluates the project firing all necessary listeners and updating the state"() {
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        1 * listener.beforeEvaluate(project)
+        1 * state.setExecuting(true)
+
+        then:
+        1 * delegate.evaluate(project, state)
+
+        then:
+        1 * state.setExecuting(false)
+        1 * state.executed()
+        1 * listener.afterEvaluate(project, state)
+    }
+
+    void "notifies listeners and updates state on evaluation failure"() {
+        def failure = new RuntimeException()
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        delegate.evaluate(project, state) >> { throw failure }
+
+        and:
+        1 * state.executed({
+            assertIsConfigurationFailure(it, failure)
+        })
+        1 * state.setExecuting(false)
+        1 * listener.afterEvaluate(project, state)
+    }
+
+    void "updates state and does not delegate when beforeEvaluate action fails"() {
+        def failure = new RuntimeException()
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        1 * listener.beforeEvaluate(project) >> { throw failure }
+        1 * state.executed({
+            assertIsConfigurationFailure(it, failure)
+        })
+        0 * delegate._
+        0 * listener._
+    }
+
+    void "updates state when afterEvaluate action fails"() {
+        def failure = new RuntimeException()
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        delegate.evaluate(project, state)
+
+        and:
+        1 * state.setExecuting(false)
+        1 * state.executed()
+
+        then:
+        1 * listener.afterEvaluate(project, state) >> { throw failure }
+        1 * state.executed({
+            assertIsConfigurationFailure(it, failure)
+        })
+    }
+
+    def assertIsConfigurationFailure(def it, def cause) {
+        assert it instanceof ProjectConfigurationException
+        assert it.message == "A problem occurred configuring project1."
+        assert it.cause == cause
+        true
+    }
+
+    void "notifies listeners and updates state on evaluation failure even if afterEvaluate fails"() {
+        def failure = new RuntimeException()
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        delegate.evaluate(project, state) >> { throw failure }
+
+        and:
+        _ * project.toString() >> "project1"
+        1 * state.executed(_)
+        1 * state.setExecuting(false)
+
+        then:
+        1 * listener.afterEvaluate(project, state) >> { throw new RuntimeException("afterEvaluate") }
+        1 * state.hasFailure() >> true
+        0 * state.executed(_)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/PluginsProjectConfigureActionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/PluginsProjectConfigureActionsTest.groovy
new file mode 100644
index 0000000..d4ec1aa
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/PluginsProjectConfigureActionsTest.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.configuration.project
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+class PluginsProjectConfigureActionsTest extends Specification {
+    final def pluginsClassLoader = Mock(ClassLoader)
+    final def evaluator = new PluginsProjectConfigureActions(pluginsClassLoader)
+
+    def "executes all implicit configuration actions"() {
+        def project = Mock(ProjectInternal)
+
+        when:
+        evaluator.execute(project)
+
+        then:
+        1 * pluginsClassLoader.getResources('META-INF/services/org.gradle.configuration.project.ProjectConfigureAction') >> resources()
+        1 * pluginsClassLoader.loadClass('ConfigureActionClass') >> TestConfigureAction
+        1 * project.setVersion(12)
+        0 * _._
+    }
+
+    def resources() {
+        URLStreamHandler handler = Mock()
+        URLConnection connection = Mock()
+        URL url = new URL("custom", "host", 12, "file", handler)
+        _ * handler.openConnection(url) >> connection
+        _ * connection.getInputStream() >> new ByteArrayInputStream("ConfigureActionClass".bytes)
+        return Collections.enumeration([url])
+    }
+
+    static class TestConfigureAction implements ProjectConfigureAction {
+        void execute(ProjectInternal project) {
+            project.version = 12
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/project/ProjectDependencies2TaskResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/project/ProjectDependencies2TaskResolverTest.groovy
new file mode 100644
index 0000000..9be41ed
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/project/ProjectDependencies2TaskResolverTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.configuration.project
+
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class ProjectDependencies2TaskResolverTest extends Specification {
+    private root = TestUtil.createRootProject()
+    private child = TestUtil.createChildProject(root, "child")
+    private rootTask = root.tasks.create('compile')
+    private childTask = child.tasks.create('compile')
+
+    private resolver = new ProjectDependencies2TaskResolver()
+
+    void "resolves task dependencies"() {
+        child.dependsOn(root.path, false)
+        when:
+        resolver.execute(child)
+        then:
+        childTask.taskDependencies.getDependencies(childTask) == [rootTask] as Set
+    }
+}
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 9ada63e..a0a38b5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.execution
 
 import org.gradle.StartParameter
 import org.gradle.api.internal.GradleInternal
+import org.gradle.internal.service.ServiceRegistry
 import spock.lang.Specification
 
 import static java.util.Collections.emptySet
@@ -27,13 +28,15 @@ class ExcludedTaskFilteringBuildConfigurationActionTest extends Specification {
     final TaskGraphExecuter taskGraph = Mock()
     final TaskSelector selector = Mock()
     final GradleInternal gradle = Mock()
-    final action = Spy(ExcludedTaskFilteringBuildConfigurationAction)
+    final ServiceRegistry services = Mock()
+    final action = new ExcludedTaskFilteringBuildConfigurationAction()
 
     def setup() {
         _ * context.gradle >> gradle
         _ * gradle.startParameter >> startParameter
         _ * gradle.taskGraph >> taskGraph
-        _ * action.createSelector(gradle) >> selector
+        _ * gradle.services >> services
+        _ * services.get(TaskSelector) >> selector
     }
 
     def "calls proceed when there are no excluded tasks defined"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
index 7ffe1f2..abd3159 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.project.DefaultProject
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/8/13
- */
 class ProjectEvaluatingActionTest extends Specification {
 
     private evaluator = Mock(TaskPathProjectEvaluator)
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolverTest.groovy
index 1f56ba4..f9dda3a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolverTest.groovy
@@ -27,15 +27,15 @@ class TaskNameResolverTest extends Specification {
         ProjectInternal project = Mock()
         TaskContainerInternal tasks = Mock()
         _ * project.tasks >> tasks
-
         Task task = task('task')
+        1 * tasks.getByName('task') >> task
 
         when:
         def candidates = resolver.select('task', project)
 
         then:
         1 * tasks.findByName('task') >> task
-        candidates.get('task') == [task] as Set
+        asTasks(candidates.get("task")) == [task] as Set
     }
 
     def selectsImplicitTaskForSingleProjectWhenThereIsAnExactMatchOnName() {
@@ -46,6 +46,7 @@ class TaskNameResolverTest extends Specification {
         _ * project.implicitTasks >> implicitTasks
 
         Task task = task('task')
+        1 * implicitTasks.getByName('task') >> task
 
         when:
         def candidates = resolver.select('task', project)
@@ -53,7 +54,7 @@ class TaskNameResolverTest extends Specification {
         then:
         1 * tasks.findByName('task') >> null
         1 * implicitTasks.findByName('task') >> task
-        candidates.get('task') == [task] as Set
+        asTasks(candidates.get('task')) == [task] as Set
     }
 
     def selectsAllTasksForSingleProjectWhenThereIsNoExactMatchOnName() {
@@ -65,7 +66,8 @@ class TaskNameResolverTest extends Specification {
 
         Task task1 = task('task1')
         Task task2 = task('task2')
-        Task hidden = task('task1')
+        1 * tasks.getByName('task1') >> task1
+        1 * implicitTasks.getByName('task2') >> task2
 
         when:
         def candidates = resolver.select('task', project)
@@ -73,12 +75,13 @@ class TaskNameResolverTest extends Specification {
         then:
         1 * tasks.findByName('task') >> null
         1 * implicitTasks.findByName('task') >> null
-        1 * tasks.iterator() >> [task1].iterator()
-        1 * implicitTasks.iterator() >> [task2, hidden].iterator()
-        candidates.get('task1') == [task1] as Set
-        candidates.get('task2') == [task2] as Set
+        1 * tasks.names >> (['task1'] as SortedSet)
+        1 * implicitTasks.names >> (['task2', 'task1'] as SortedSet)
+        asTasks(candidates.get('task1')) == [task1] as Set
+        asTasks(candidates.get('task2')) == [task2] as Set
     }
 
+
     def selectsTasksForMultipleProjectsWhenThereIsAnExactMatchOnName() {
         ProjectInternal project = Mock()
         TaskContainerInternal tasks = Mock()
@@ -89,7 +92,9 @@ class TaskNameResolverTest extends Specification {
         _ * childProject.tasks >> childProjectTasks
 
         Task task1 = task('task')
+        _ * tasks.getByName('task') >> task1
         Task task2 = task('task')
+        _ * childProjectTasks.getByName('task') >> task2
 
         when:
         def candidates = resolver.selectAll('task', project)
@@ -97,7 +102,7 @@ class TaskNameResolverTest extends Specification {
         then:
         1 * tasks.findByName('task') >> task1
         1 * childProjectTasks.findByName('task') >> task2
-        candidates.get('task') == [task1, task2] as Set
+        asTasks(candidates.get('task')) == [task1, task2] as Set
     }
 
     def selectsImplicitTaskForMultipleProjectsWhenThereIsAnExactMatchOnName() {
@@ -112,7 +117,9 @@ class TaskNameResolverTest extends Specification {
         _ * childProject.tasks >> childProjectTasks
 
         Task task1 = task('task')
+        _ * implicitTasks.getByName('task') >> task1
         Task task2 = task('task')
+        _ * childProjectTasks.getByName('task') >> task2
 
         when:
         def candidates = resolver.selectAll('task', project)
@@ -121,7 +128,7 @@ class TaskNameResolverTest extends Specification {
         1 * tasks.findByName('task') >> null
         1 * implicitTasks.findByName('task') >> task1
         1 * childProjectTasks.findByName('task') >> task2
-        candidates.get('task') == [task1, task2] as Set
+        asTasks(candidates.get('task')) == [task1, task2] as Set
     }
 
     def selectsAllTasksForMultipleProjectsWhenThereIsNoExactMatchOnName() {
@@ -139,6 +146,13 @@ class TaskNameResolverTest extends Specification {
         Task task2 = task('name2')
         Task task3 = task('name1')
         Task task4 = task('name2')
+        Task task5 = task('name3')
+
+        _ * childProjectTasks.getByName(task3.name) >> task3
+        _ * childProjectTasks.getByName(task4.name) >> task4
+        _ * childProjectTasks.getByName(task5.name) >> task5
+        _ * tasks.getByName(task1.name) >> task1
+        _ * implicitTasks.getByName(task2.name) >> task2
 
         when:
         def candidates = resolver.selectAll('task', project)
@@ -147,11 +161,13 @@ class TaskNameResolverTest extends Specification {
         1 * tasks.findByName('task') >> null
         1 * implicitTasks.findByName('task') >> null
         1 * childProjectTasks.findByName('task') >> null
-        1 * tasks.iterator() >> [task1].iterator()
-        1 * implicitTasks.iterator() >> [task2].iterator()
-        1 * childProjectTasks.iterator() >> [task3, task4].iterator()
-        candidates.get('name1') == [task1, task3] as Set
-        candidates.get('name2') == [task2, task4] as Set
+        1 * tasks.names >> ([task1.name] as SortedSet)
+        1 * implicitTasks.names >> ([task2.name] as SortedSet)
+        1 * childProjectTasks.names >> ([task3.name, task4.name, task5.name] as SortedSet)
+
+        asTasks(candidates.get('name1')) == [task1, task3] as Set
+        asTasks(candidates.get('name2')) == [task2, task4] as Set
+        asTasks(candidates.get('name3')) == [task5] as Set
     }
 
     def task(String name) {
@@ -159,4 +175,8 @@ class TaskNameResolverTest extends Specification {
         _ * task.name >> name
         return task
     }
+
+    Set<Task> asTasks(Set<TaskSelectionResult> taskSelectionResults) {
+        taskSelectionResults.collect { it.getTask() }.toSet()
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
index 7314ea3..ee0abe1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
@@ -23,7 +23,10 @@ import org.gradle.api.Task;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.AbstractProject;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.api.internal.tasks.options.Option;
+import org.gradle.api.internal.tasks.options.OptionReader;
+import org.gradle.execution.commandline.CommandLineTaskConfigurer;
+import org.gradle.execution.commandline.CommandLineTaskParser;
 import org.gradle.util.GUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
@@ -42,7 +45,7 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
- at RunWith (org.jmock.integration.junit4.JMock.class)
+ at RunWith(org.jmock.integration.junit4.JMock.class)
 public class TaskNameResolvingBuildConfigurationActionTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final ProjectInternal project = context.mock(AbstractProject.class, "[project]");
@@ -53,11 +56,14 @@ public class TaskNameResolvingBuildConfigurationActionTest {
     private final TaskNameResolver resolver = context.mock(TaskNameResolver.class);
     private final BuildExecutionContext executionContext = context.mock(BuildExecutionContext.class);
     private final StartParameter startParameter = context.mock(StartParameter.class);
-    private final TaskNameResolvingBuildConfigurationAction action = new TaskNameResolvingBuildConfigurationAction(resolver);
+    private final OptionReader optionReader = new OptionReader();
+    private final CommandLineTaskParser parser = new CommandLineTaskParser(new CommandLineTaskConfigurer(optionReader));
+    private final TaskSelector selector = new TaskSelector(gradle, resolver);
+    private final TaskNameResolvingBuildConfigurationAction action = new TaskNameResolvingBuildConfigurationAction(parser, selector);
 
     @Before
     public void setUp() {
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(executionContext).getGradle();
             will(returnValue(gradle));
             allowing(gradle).getDefaultProject();
@@ -66,12 +72,6 @@ public class TaskNameResolvingBuildConfigurationActionTest {
             will(returnValue(taskExecuter));
             allowing(gradle).getStartParameter();
             will(returnValue(startParameter));
-            allowing(project).getAllprojects();
-            will(returnValue(toSet(project, otherProject)));
-            allowing(otherProject).getPath();
-            will(returnValue(":anotherProject"));
-            allowing(rootProject).getPath();
-            will(returnValue(":"));
         }});
     }
 
@@ -132,13 +132,13 @@ public class TaskNameResolvingBuildConfigurationActionTest {
 
         action.configure(executionContext);
     }
-    
+
     @Test
     public void selectsTaskWithMatchingRelativePath() {
         final Task task1 = task("b");
         final Task task2 = task("a");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(startParameter).getTaskNames();
             will(returnValue(toList("a:b")));
 
@@ -158,7 +158,7 @@ public class TaskNameResolvingBuildConfigurationActionTest {
         final Task task1 = task("b");
         final Task task2 = task("a");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(startParameter).getTaskNames();
             will(returnValue(toList(":b")));
 
@@ -178,7 +178,7 @@ public class TaskNameResolvingBuildConfigurationActionTest {
         final Task task1 = task("b");
         final Task task2 = task("a");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(startParameter).getTaskNames();
             will(returnValue(toList(":a:b")));
 
@@ -200,7 +200,7 @@ public class TaskNameResolvingBuildConfigurationActionTest {
         final Task task1 = task("someTask");
         final Task task2 = task("other");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(startParameter).getTaskNames();
             will(returnValue(toList("anotherProject:soTa")));
 
@@ -220,7 +220,7 @@ public class TaskNameResolvingBuildConfigurationActionTest {
         final Task task1 = task("someTask");
         final Task task2 = task("other");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(startParameter).getTaskNames();
             will(returnValue(toList("anPr:soTa")));
 
@@ -384,27 +384,40 @@ public class TaskNameResolvingBuildConfigurationActionTest {
 
     private <T extends Task> T task(final String name, Class<T> taskType) {
         final T task = context.mock(taskType);
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(task).getName();
             will(returnValue(name));
         }});
         return task;
     }
 
-    private Multimap<String, Task> tasks(Task... tasks) {
+    private Multimap<String, TaskSelectionResult> tasks(Task... tasks) {
         return tasks(Arrays.asList(tasks));
     }
 
-    private Multimap<String, Task> tasks(Iterable<Task> tasks) {
-        Multimap<String, Task> map = LinkedHashMultimap.create();
-        for (Task task : tasks) {
-            map.put(task.getName(), task);
+    private Multimap<String, TaskSelectionResult> tasks(Iterable<Task> tasks) {
+        Multimap<String, TaskSelectionResult> map = LinkedHashMultimap.create();
+        for (final Task task : tasks) {
+            map.put(task.getName(), new SimpleTaskSelectionResult(task));
         }
         return map;
     }
 
+    private static class SimpleTaskSelectionResult implements TaskSelectionResult {
+        private final Task task;
+
+        public SimpleTaskSelectionResult(Task task) {
+            this.task = task;
+        }
+
+        public Task getTask() {
+            return task;
+        }
+    }
+
     public abstract class TaskWithBooleanProperty implements Task {
-        @CommandLineOption(options = "all", description = "Some boolean flag")
-        public void setSomeFlag(boolean flag) { }
+        @Option(option = "all", description = "Some boolean flag")
+        public void setSomeFlag(boolean flag) {
+        }
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
index 4a8306c..214d9bb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.execution.taskpath.ResolvedTaskPath
 import org.gradle.execution.taskpath.TaskPathResolver
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/8/13
- */
 class TaskPathProjectEvaluatorTest extends Specification {
 
     private resolver = Mock(TaskPathResolver)
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
index 8742df0..b485483 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
@@ -13,27 +13,22 @@
  * 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.internal.tasks.options.Option
+import org.gradle.api.internal.tasks.options.OptionReader
 import org.gradle.api.tasks.TaskAction
 import org.gradle.execution.TaskSelector
+import org.gradle.internal.typeconversion.TypeConversionException
 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()
+    CommandLineTaskConfigurer configurer = new CommandLineTaskConfigurer(new OptionReader());
 
     TaskSelector selector = Mock()
     SomeTask task = project.task('someTask', type: SomeTask)
@@ -54,7 +49,7 @@ class CommandLineTaskConfigurerSpec extends Specification {
     }
 
     def "does not attempt configure if no options"() {
-        configurer = Spy(CommandLineTaskConfigurer)
+        configurer = Spy(CommandLineTaskConfigurer, constructorArgs: [new OptionReader()])
 
         when:
         def out = configurer.configureTasks([task, task2], ['foo'])
@@ -79,6 +74,22 @@ class CommandLineTaskConfigurerSpec extends Specification {
         task.someFlag
     }
 
+    def "configures enum option"() {
+        when:
+        configurer.configureTasks([task], ['--someEnum', "value1"])
+        then:
+        task.anEnum == TestEnum.value1
+
+        when:
+        configurer.configureTasks([task], ['--someEnum', "unsupportedEnumValue"])
+        then:
+        def e = thrown(TaskConfigurationException)
+        e.message == "Problem configuring option 'someEnum' on task ':someTask' from command line."
+        e.cause instanceof TypeConversionException
+        e.cause.message == "Cannot coerce string value 'unsupportedEnumValue' to an enum value of type 'org.gradle.execution.commandline.CommandLineTaskConfigurerSpec\$TestEnum' (valid case insensitive values: [value1, value2])"
+
+    }
+
     def "configures options on all types that can accommodate the setting"() {
         when:
         configurer.configureTasks([task, otherTask], ['--someFlag'])
@@ -90,17 +101,19 @@ class CommandLineTaskConfigurerSpec extends Specification {
     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 ex = thrown(TaskConfigurationException)
+        ex.cause.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')
+        def ex = thrown(TaskConfigurationException)
+        ex.cause.message.contains('someFlag2')
 
         where:
         input << [['--someFlag', '--someFlag2'], ['--someFlag2', '--someFlag']]
@@ -137,8 +150,8 @@ class CommandLineTaskConfigurerSpec extends Specification {
         configurer.configureTasks([task, task2], args)
 
         then:
-        def ex = thrown(GradleException)
-        ex.message.contains('xxx')
+        def ex = thrown(TaskConfigurationException)
+        ex.cause.message.contains('xxx')
     }
 
     def "fails neatly when short option used"() {
@@ -147,41 +160,64 @@ class CommandLineTaskConfigurerSpec extends Specification {
         configurer.configureTasks([task], args)
 
         then:
-        def ex = thrown(GradleException)
-        ex.message.contains("Unknown command-line option '-c'")
+        def ex = thrown(TaskConfigurationException)
+        ex.cause.message.contains("Unknown command-line option '-c'")
     }
 
     public static class SomeTask extends DefaultTask {
         String content = 'default content'
-        @CommandLineOption(options="content", description="Some content.") public void setContent(String content) {
+
+        @Option(option = "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) {
+
+        @Option(option = "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) {
+
+        @Option(option = "someFlag2", description = "Some 2nd flag.")
+        public void setSomeFlag2(Boolean someFlag2) {
             this.someFlag2 = someFlag2
         }
 
-        @CommandLineOption(options="notUsed", description="Not used.") public void setNotUsed(boolean notUsed) {
+        @Option(option = "notUsed", description = "Not used.")
+        public void setNotUsed(boolean notUsed) {
             throw new RuntimeException("Not used");
         }
 
-        @TaskAction public void dummy() {}
+        TestEnum anEnum
+
+        @Option(option = "someEnum", description = "some enum value.")
+        public void setEnum(TestEnum anEnum) {
+            this.anEnum = anEnum;
+        }
+
+        @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) {
+
+        @Option(option = "someFlag", description = "Some flag.")
+        public void setSomeFlag(boolean someFlag) {
             this.someFlag = someFlag
         }
-        @CommandLineOption(options="stuff", description="Some stuff.") public void setStuff(String stuff) {
+
+        @Option(option = "stuff", description = "Some stuff.")
+        public void setStuff(String stuff) {
             this.stuff = stuff;
         }
     }
+
+    enum TestEnum {
+        value1, value2
+    }
 }
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
index 269f3ad..a93383d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
@@ -19,6 +19,7 @@ package org.gradle.execution.commandline
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.tasks.TaskAction
+import org.gradle.execution.TaskSelectionResult
 import org.gradle.execution.TaskSelector
 import org.gradle.testfixtures.ProjectBuilder
 import spock.lang.Specification
@@ -26,21 +27,20 @@ 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)
 
+    CommandLineTaskParser parser
+
     def setup() {
-        parser.taskConfigurer = Mock(CommandLineTaskConfigurer)
-        parser.taskConfigurer.configureTasks(_, _) >> { args -> args[1] }
+        CommandLineTaskConfigurer taskConfigurer = Mock(CommandLineTaskConfigurer)
+        taskConfigurer.configureTasks(_, _) >> { args -> args[1] }
+        parser = new CommandLineTaskParser(taskConfigurer)
     }
 
     def "deals with empty input"() {
@@ -50,7 +50,7 @@ class CommandLineTaskParserSpec extends Specification {
 
     def "parses a single task"() {
         given:
-        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task] as Set)
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', asTaskSelectionResults(task))
 
         when:
         def out = parser.parseTasks(['foo'], selector)
@@ -60,9 +60,17 @@ class CommandLineTaskParserSpec extends Specification {
         out.get('foo task') == [task] as Set
     }
 
+    Set<TaskSelectionResult> asTaskSelectionResults(SomeTask... someTasks) {
+        return someTasks.collect {task ->
+            TaskSelectionResult mock = Mock(TaskSelectionResult)
+            _ * mock.task >> task
+            mock
+        }
+    }
+
     def "parses single task with multiple matches"() {
         given:
-        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task, task2] as Set)
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', asTaskSelectionResults(task, task2))
 
         when:
         def out = parser.parseTasks(['foo'], selector)
@@ -74,8 +82,8 @@ class CommandLineTaskParserSpec extends Specification {
 
     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)
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', asTaskSelectionResults(task, task2))
+        selector.getSelection('bar') >> new TaskSelector.TaskSelection('bar task', asTaskSelectionResults(task3))
 
         when:
         def out = parser.parseTasks(['foo', 'bar'], selector)
@@ -88,9 +96,9 @@ class CommandLineTaskParserSpec extends Specification {
 
     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)
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', asTaskSelectionResults(task, task2))
+        selector.getSelection('bar') >> new TaskSelector.TaskSelection('bar task', asTaskSelectionResults(task3))
+        selector.getSelection('lastTask') >> new TaskSelector.TaskSelection('last task', asTaskSelectionResults(task3))
 
         when:
         def out = parser.parseTasks(['foo', '--all', 'bar', '--include', 'stuff', 'lastTask'], selector)
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
index fc8f67e..b2fd9ba 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
@@ -14,83 +14,88 @@
  * limitations under the License.
  */
 
-package org.gradle.execution.taskgraph;
-
+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.internal.project.DefaultProject
 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 org.gradle.util.TextUtil
+import spock.lang.Issue
 import spock.lang.Specification
+import spock.lang.Unroll
 
-import static org.gradle.util.HelperUtil.createRootProject
+import static org.gradle.util.TestUtil.createChildProject
+import static org.gradle.util.TestUtil.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();
+    DefaultProject root;
 
     def setup() {
         root = createRootProject();
         executionPlan = new DefaultTaskExecutionPlan()
     }
 
-    def "returns tasks in dependency order"() {
+    private void addToGraphAndPopulate(List tasks) {
+        executionPlan.addToTaskGraph(tasks)
+        executionPlan.determineExecutionPlan()
+    }
+
+    private TaskFailureHandler createIgnoreTaskFailureHandler(Task task) {
+        Mock(TaskFailureHandler) {
+            onTaskFailure(task) >> {}
+        }
+    }
+
+    def "schedules 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);
+        Task b = task("b", dependsOn: [a]);
+        Task c = task("c", dependsOn: [b, a]);
+        Task d = task("d", dependsOn: [c]);
 
         when:
-        executionPlan.addToTaskGraph(toList(d))
+        addToGraphAndPopulate([d])
 
         then:
-        executedTasks == [a, b, c, d]
+        executes(a, b, c, d)
     }
 
-    def "returns task dependencies in name order"() {
+    def "schedules task dependencies in name order when there are no dependencies between them"() {
         given:
         Task a = task("a");
         Task b = task("b");
         Task c = task("c");
-        Task d = task("d", b, a, c);
+        Task d = task("d", dependsOn: [b, a, c]);
 
         when:
-        executionPlan.addToTaskGraph(toList(d));
+        addToGraphAndPopulate([d])
 
         then:
-        executedTasks == [a, b, c, d]
+        executes(a, b, c, d)
     }
 
-    def "returns a single batch of tasks in name order"() {
+    def "schedules 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));
+        addToGraphAndPopulate(toList(b, c, a));
 
         then:
-        executedTasks == [a, b, c]
+        executes(a, b, c)
     }
 
-    def "returns separately added tasks in order added"() {
+    def "schedules separately added tasks in order added"() {
         given:
         Task a = task("a");
         Task b = task("b");
@@ -99,73 +104,415 @@ public class DefaultTaskExecutionPlanTest extends Specification {
 
         when:
         executionPlan.addToTaskGraph(toList(c, b));
-        executionPlan.addToTaskGraph(toList(d, a));
+        executionPlan.addToTaskGraph(toList(d, a))
+        executionPlan.determineExecutionPlan()
+
+        then:
+        executes(b, c, a, d)
+    }
+
+    @Unroll
+    def "schedules #orderingRule task dependencies in name order"() {
+        given:
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c", (orderingRule): [b, a]);
+        Task d = task("d", dependsOn: [b, a]);
+
+        when:
+        addToGraphAndPopulate([c, d]);
 
         then:
-        executedTasks == [b, c, a, d];
+        executes(a, b, c, d)
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
     }
 
-    def "common tasks in separate batches are returned only once"() {
+    def "common tasks in separate batches are schedules only once"() {
         Task a = task("a");
         Task b = task("b");
-        Task c = task("c", a, b);
+        Task c = task("c", dependsOn: [a, b]);
         Task d = task("d");
-        Task e = task("e", b, d);
+        Task e = task("e", dependsOn: [b, d]);
 
         when:
         executionPlan.addToTaskGraph(toList(c));
         executionPlan.addToTaskGraph(toList(e));
+        executionPlan.determineExecutionPlan();
 
         then:
-        executedTasks == [a, b, c, d, e];
+        executes(a, b, c, d, e)
     }
 
-    def "all dependencies added when adding tasks"() {
+    def "all dependencies scheduled when adding tasks"() {
         Task a = task("a");
-        Task b = task("b", a);
-        Task c = task("c", b, a);
-        Task d = task("d", c);
+        Task b = task("b", dependsOn: [a]);
+        Task c = task("c", dependsOn: [b, a]);
+        Task d = task("d", dependsOn: [c]);
 
         when:
-        executionPlan.addToTaskGraph(toList(d));
+        addToGraphAndPopulate(toList(d));
 
         then:
-        executionPlan.getTasks() == [a, b, c, d];
-        executedTasks == [a, b, c, d]
+        executes(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);
+    @Unroll
+    def "#orderingRule ordering is honoured for tasks added separately to graph"() {
+        Task a = task("a")
+        Task b = task("b", dependsOn: [a])
+        Task c = task("c", (orderingRule): [b])
+
+        when:
+        executionPlan.addToTaskGraph([c])
+        executionPlan.addToTaskGraph([b])
+        executionPlan.determineExecutionPlan()
+
+        then:
+        executes(a, b, c)
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
+    }
+
+    @Unroll
+    def "#orderingRule ordering is honoured for dependencies"() {
+        Task b = task("b")
+        Task a = task("a", (orderingRule): [b])
+        Task c = task("c", dependsOn: [a, b])
+
+        when:
+        addToGraphAndPopulate([c])
+
+        then:
+        executes(b, a, c)
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
+    }
+
+    def "mustRunAfter dependencies are scheduled before regular dependencies"() {
+        Task a = task("a")
+        Task b = task("b")
+        Task c = task("c", dependsOn: [a], mustRunAfter: [b])
+        Task d = task("d", dependsOn: [b])
+
+        when:
+        addToGraphAndPopulate([c, d])
+
+        then:
+        executes(b, a, c, d)
+    }
+
+    def "shouldRunAfter dependencies are scheduled before mustRunAfter dependencies"() {
+        Task a = task("a")
+        Task b = task("b")
+        Task c = task("c", mustRunAfter: [a], shouldRunAfter: [b])
+        Task d = task("d", dependsOn: [a, b])
+
+        when:
+        addToGraphAndPopulate([c, d])
+
+        then:
+        executes(b, a, c, d)
+    }
+
+    @Unroll
+    def "#orderingRule does not pull in tasks that are not in the graph"() {
+        Task a = task("a")
+        Task b = task("b", (orderingRule): [a])
+
+        when:
+        addToGraphAndPopulate([b])
+
+        then:
+        executes(b)
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
+    }
+
+    def "finalizer tasks are executed if a finalized task is added to the graph"() {
+        Task finalizer = task("a")
+        Task finalized = task("b", finalizedBy: [finalizer])
+
+        when:
+        addToGraphAndPopulate([finalized])
+
+        then:
+        executes(finalized, finalizer)
+    }
+
+    def "finalizer tasks and their dependencies are executed even in case of a task failure"() {
+        Task finalizerDependency = task("finalizerDependency")
+        Task finalizer1 = task("finalizer1", dependsOn: [finalizerDependency])
+        Task finalized1 = task("finalized1", finalizedBy: [finalizer1])
+        Task finalizer2 = task("finalizer2")
+        Task finalized2 = task("finalized2", finalizedBy: [finalizer2], failure: new RuntimeException("failure"))
+
+        when:
+        addToGraphAndPopulate([finalized1, finalized2])
+
+        then:
+        executes(finalized1, finalizerDependency, finalizer1, finalized2, finalizer2)
+    }
+
+    def "finalizer task is not added to the graph if it is filtered"() {
+        given:
+        Task finalizer = filteredTask("finalizer")
+        Task finalized = task("finalized", finalizedBy: [finalizer])
+        Spec<Task> filter = Mock() {
+            isSatisfiedBy(_) >> { Task t -> t != finalizer }
+        }
+
+        when:
+        executionPlan.useFilter(filter);
+        addToGraphAndPopulate([finalized])
+
+        then:
+        executes(finalized)
+    }
+
+    def "finalizer tasks and their dependencies are not executed if finalized task did not run"() {
+        Task finalizerDependency = task("finalizerDependency")
+        Task finalizer = task("finalizer", dependsOn: [finalizerDependency])
+        Task finalizedDependency = task("finalizedDependency", failure: new RuntimeException("failure"))
+        Task finalized = task("finalized", dependsOn: [finalizedDependency], finalizedBy: [finalizer])
+
+        when:
+        addToGraphAndPopulate([finalized])
+
+        then:
+        executionPlan.tasks == [finalizedDependency, finalized, finalizerDependency, finalizer]
+        executedTasks == [finalizedDependency]
+    }
+
+    def "finalizer tasks and their dependencies are executed if they are previously required even if the finalized task did not run"() {
+        Task finalizerDependency = task("finalizerDependency")
+        Task finalizer = task("finalizer", dependsOn: [finalizerDependency])
+        Task finalizedDependency = task("finalizedDependency", failure: new RuntimeException("failure"))
+        Task finalized = task("finalized", dependsOn: [finalizedDependency], finalizedBy: [finalizer])
+        executionPlan.useFailureHandler(createIgnoreTaskFailureHandler(finalizedDependency));
+
+        when:
+        addToGraphAndPopulate([finalizer, finalized])
+
+        then:
+        executionPlan.tasks == [finalizedDependency, finalized, finalizerDependency, finalizer]
+        executedTasks == [finalizedDependency, finalizerDependency, finalizer]
+    }
+
+    def "finalizer tasks and their dependencies are executed if they are later required via dependency even if the finalized task did not do any work"() {
+        Task finalizerDependency = task("finalizerDependency")
+        Task finalizer = task("finalizer", dependsOn: [finalizerDependency])
+        Task dependsOnFinalizer = task("dependsOnFinalizer", dependsOn: [finalizer])
+        Task finalized = task("finalized", finalizedBy: [finalizer], didWork: false)
+
+        when:
+        executionPlan.addToTaskGraph([finalized])
+        executionPlan.addToTaskGraph([dependsOnFinalizer])
+        executionPlan.determineExecutionPlan()
+
+        then:
+        executes(finalized, finalizerDependency, finalizer, dependsOnFinalizer)
+    }
+
+    def "finalizer tasks run as soon as possible for tasks that depend on finalized tasks"() {
+        Task finalizer = task("finalizer")
+        Task finalized = task("finalized", finalizedBy: [finalizer])
+        Task dependsOnFinalized = task("dependsOnFinalized", dependsOn: [finalized])
+
+        when:
+        addToGraphAndPopulate([dependsOnFinalized])
+
+        then:
+        executes(finalized, finalizer, dependsOnFinalized)
+    }
+
+    def "multiple finalizer tasks may have relationships between each other"() {
+        Task f2 = task("f2")
+        Task f1 = task("f1", dependsOn: [f2])
+        Task finalized = task("finalized", finalizedBy: [f1, f2])
 
         when:
-        executionPlan.addToTaskGraph(toList(a));
+        addToGraphAndPopulate([finalized])
 
         then:
-        executionPlan.getTasks() == [c, d, b, a]
-        executedTasks == [c, d, b, a]
+        executes(finalized, f2, f1)
+    }
+
+    def "multiple finalizer tasks may have relationships between each other via some other task"() {
+        Task f2 = task("f2")
+        Task d = task("d", dependsOn:[f2] )
+        Task f1 = task("f1", dependsOn: [d])
+        Task finalized = task("finalized", finalizedBy: [f1, f2])
+
+        when:
+        addToGraphAndPopulate([finalized])
+
+        then:
+        executes(finalized, f2, d, f1)
+    }
+
+    @Issue("GRADLE-2983")
+    def "multiple finalizer tasks with relationships via other tasks scheduled from multiple tasks"() {
+        //finalizers with a relationship via a dependency
+        Task f1 = task("f1")
+        Task dep = task("dep", dependsOn:[f1] )
+        Task f2 = task("f2", dependsOn: [dep])
+
+        //2 finalized tasks
+        Task finalized1 = task("finalized1", finalizedBy: [f1, f2])
+        Task finalized2 = task("finalized2", finalizedBy: [f1, f2])
+
+        //tasks that depends on finalized, we will execute them
+        Task df1 = task("df1", dependsOn: [finalized1])
+        Task df2 = task("df1", dependsOn: [finalized2])
+
+        when:
+        addToGraphAndPopulate([df1, df2])
+
+        then:
+        executes(finalized1, finalized2, f1, dep, f2, df1, df2)
+    }
+
+    @Unroll
+    def "finalizer tasks run as soon as possible for tasks that #orderingRule finalized tasks"() {
+        Task finalizer = task("finalizer")
+        Task finalized = task("finalized", finalizedBy: [finalizer])
+        Task runsAfterFinalized = task("runsAfterFinalized", (orderingRule): [finalized])
+
+        when:
+        addToGraphAndPopulate([runsAfterFinalized, finalized])
+
+        then:
+        executes(finalized, finalizer, runsAfterFinalized)
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
+    }
+
+    @Unroll
+    def "finalizer tasks run as soon as possible but after its #orderingRule tasks"() {
+        Task finalizer = createTask("finalizer")
+        Task finalized = task("finalized", finalizedBy: [finalizer])
+        Task dependsOnFinalized = task("dependsOnFinalized", dependsOn: [finalized])
+        relationships(finalizer, (orderingRule): [dependsOnFinalized])
+
+        when:
+        addToGraphAndPopulate([dependsOnFinalized])
+
+        then:
+        executes(finalized, dependsOnFinalized, finalizer)
+
+        where:
+        orderingRule << ['dependsOn', 'mustRunAfter' , 'shouldRunAfter']
     }
 
     def "cannot add task with circular reference"() {
-        Task a = createTask("a");
-        Task b = task("b", a);
-        Task c = task("c", b);
-        dependsOn(a, c);
+        Task a = createTask("a")
+        Task b = task("b", dependsOn: [a])
+        Task c = task("c", dependsOn: [b])
+        Task d = task("d")
+        relationships(a, dependsOn: [c, d])
 
         when:
-        executionPlan.addToTaskGraph([c])
+        addToGraphAndPopulate([c])
+
+        then:
+        def e = thrown CircularReferenceException
+        e.message == TextUtil.toPlatformLineSeparators("""Circular dependency between the following tasks:
+:a
+\\--- :c
+     \\--- :b
+          \\--- :a (*)
+
+(*) - details omitted (listed previously)
+""")
+    }
+
+    def "cannot add a task with must run after induced circular reference"() {
+        Task a = createTask("a")
+        Task b = task("b", mustRunAfter: [a])
+        Task c = task("c", dependsOn: [b])
+        relationships(a, dependsOn: [c])
+
+        when:
+        addToGraphAndPopulate([a])
 
         then:
-        thrown CircularReferenceException
+        def e = thrown CircularReferenceException
+        e.message == TextUtil.toPlatformLineSeparators("""Circular dependency between the following tasks:
+:a
+\\--- :c
+     \\--- :b
+          \\--- :a (*)
+
+(*) - details omitted (listed previously)
+""")
+    }
+
+    def "cannot add a task with must run after induced circular reference that was previously in graph but not required"() {
+        Task a = createTask("a")
+        Task b = task("b", mustRunAfter: [a])
+        Task c = task("c", dependsOn: [b])
+        Task d = task("d", dependsOn: [c])
+        relationships(a, mustRunAfter: [c])
+        executionPlan.addToTaskGraph([d])
+
+        when:
+        executionPlan.addToTaskGraph([a])
+        executionPlan.determineExecutionPlan()
+
+        then:
+        def e = thrown CircularReferenceException
+        e.message == TextUtil.toPlatformLineSeparators("""Circular dependency between the following tasks:
+:a
+\\--- :c
+     \\--- :b
+          \\--- :a (*)
+
+(*) - details omitted (listed previously)
+""")
+    }
+
+    def "should run after ordering is ignored if it is in a middle of a circular reference"() {
+        Task a = task("a")
+        Task b = task("b")
+        Task c = task("c")
+        Task d = createTask("d")
+        Task e = task("e", dependsOn: [a, d])
+        Task f = task("f", dependsOn: [e])
+        Task g = task("g", dependsOn: [c, f])
+        Task h = task("h", dependsOn: [b, g])
+        relationships(d, shouldRunAfter: [g])
+
+        when:
+        addToGraphAndPopulate([e, h])
+
+        then:
+        executedTasks == [a, d, e, b, c, f, g, h]
+    }
+
+    def "should run after ordering is ignored if it is at the end of a circular reference"() {
+        Task a = createTask("a")
+        Task b = task("b", dependsOn: [a])
+        Task c = task("c", dependsOn: [b])
+        relationships(a, shouldRunAfter: [c])
+
+        when:
+        addToGraphAndPopulate([c])
+
+        then:
+        executedTasks == [a, b, c]
     }
 
     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])
+        addToGraphAndPopulate([a, b])
 
         when:
         def taskInfoA = taskToExecute
@@ -184,16 +531,16 @@ public class DefaultTaskExecutionPlanTest extends Specification {
     }
 
     protected TaskInfo getTaskToExecute() {
-        executionPlan.getTaskToExecute(anyTask)
+        executionPlan.getTaskToExecute()
     }
 
     def "stops returning tasks on first task failure when no failure handler provided"() {
         RuntimeException failure = new RuntimeException("failure");
-        Task a = brokenTask("a", failure);
+        Task a = task("a", failure: failure);
         Task b = task("b");
 
         when:
-        executionPlan.addToTaskGraph([a, b])
+        addToGraphAndPopulate([a, b])
 
         then:
         executedTasks == [a]
@@ -208,10 +555,10 @@ public class DefaultTaskExecutionPlanTest extends Specification {
 
     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 a = task("a", failure: failure);
         Task b = task("b");
 
-        executionPlan.addToTaskGraph([a, b])
+        addToGraphAndPopulate([a, b])
 
         TaskFailureHandler handler = Mock()
         RuntimeException wrappedFailure = new RuntimeException("wrapped");
@@ -235,16 +582,33 @@ public class DefaultTaskExecutionPlanTest extends Specification {
 
     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 a = task("a", failure: failure);
         Task b = task("b");
-        executionPlan.addToTaskGraph([a, b])
+        addToGraphAndPopulate([a, b])
 
-        TaskFailureHandler handler = Mock()
-        handler.onTaskFailure(a) >> {
-        }
+        when:
+        executionPlan.useFailureHandler(createIgnoreTaskFailureHandler(a));
+
+        then:
+        executedTasks == [a, b]
 
         when:
-        executionPlan.useFailureHandler(handler);
+        executionPlan.awaitCompletion()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    @Unroll
+    def "continues to return tasks when failure handler does not abort execution and tasks are #orderingRule dependent"() {
+        RuntimeException failure = new RuntimeException();
+        Task a = task("a", failure: failure);
+        Task b = task("b", (orderingRule): [a]);
+        addToGraphAndPopulate([a, b])
+
+        when:
+        executionPlan.useFailureHandler(createIgnoreTaskFailureHandler(a));
 
         then:
         executedTasks == [a, b]
@@ -255,22 +619,20 @@ public class DefaultTaskExecutionPlanTest extends Specification {
         then:
         RuntimeException e = thrown()
         e == failure
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
     }
 
     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 a = task("a", failure: failure)
+        final Task b = task("b", dependsOn: [a])
         final Task c = task("c")
-        executionPlan.addToTaskGraph([b, c])
-
-        TaskFailureHandler handler = Mock()
-        handler.onTaskFailure(a) >> {
-            // Ignore failure
-        }
+        addToGraphAndPopulate([b, c])
 
         when:
-        executionPlan.useFailureHandler(handler)
+        executionPlan.useFailureHandler(createIgnoreTaskFailureHandler(a))
 
         then:
         executedTasks == [a, c]
@@ -288,108 +650,216 @@ public class DefaultTaskExecutionPlanTest extends Specification {
         Task a = task("a");
 
         when:
-        executionPlan.addToTaskGraph(toList(a));
+        addToGraphAndPopulate(toList(a));
         executionPlan.clear()
 
         then:
-        executionPlan.getTasks() == []
+        executionPlan.tasks == []
         executedTasks == []
     }
 
-    def getExecutedTasks() {
-        def tasks = []
-        def taskInfo
-        while ((taskInfo = taskToExecute) != 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])
+        addToGraphAndPopulate([a])
 
         then:
-        executedTasks == [a]
+        executes(a)
 
         when:
         executionPlan.clear()
-        executionPlan.addToTaskGraph([b])
+        addToGraphAndPopulate([b])
 
         then:
-        executedTasks == [b]
+        executes(b)
     }
 
-    def "does not execute filtered tasks"() {
+    def "does not build graph for or execute filtered tasks"() {
         given:
-        Task a = task("a", task("a-dep"))
+        Task a = filteredTask("a")
         Task b = task("b")
+        Spec<Task> filter = Mock()
+
+        and:
+        filter.isSatisfiedBy(_) >> { Task t -> t != a }
 
         when:
-        executionPlan.useFilter({ it != a } as Spec<Task>);
-        executionPlan.addToTaskGraph([a, b])
+        executionPlan.useFilter(filter);
+        addToGraphAndPopulate([a, b])
 
         then:
-        executionPlan.getTasks() == [b]
-        executedTasks == [b]
+        executes(b)
     }
 
-    def "does not execute filtered dependencies"() {
+    def "does not build graph for or execute filtered dependencies"() {
         given:
-        Task a = task("a", task("a-dep"))
+        Task a = filteredTask("a")
         Task b = task("b")
-        Task c = task("c", a, b)
+        Task c = task("c", dependsOn: [a, b])
+        Spec<Task> filter = Mock()
+
+        and:
+        filter.isSatisfiedBy(_) >> { Task t -> t != a }
 
         when:
+        executionPlan.useFilter(filter)
+        addToGraphAndPopulate([c])
 
-        executionPlan.useFilter({ it != a } as Spec<Task>)
-        executionPlan.addToTaskGraph([c])
+        then:
+        executes(b, c)
+    }
+
+    @Unroll
+    def "does not build graph for or execute filtered tasks reachable via #orderingRule task ordering"() {
+        given:
+        Task a = filteredTask("a")
+        Task b = task("b", (orderingRule): [a])
+        Task c = task("c", dependsOn: [a])
+        Spec<Task> filter = Mock()
+
+        and:
+        filter.isSatisfiedBy(_) >> { Task t -> t != a }
+
+        when:
+        executionPlan.useFilter(filter)
+        addToGraphAndPopulate([b, c])
 
         then:
-        executionPlan.tasks == [b, c]
-        executedTasks == [b, c]
+        executes(b, c)
+
+        where:
+        orderingRule << ['mustRunAfter', 'shouldRunAfter']
     }
 
     def "will execute a task whose dependencies have been filtered"() {
         given:
-        Task b = task("b")
-        Task c = task("c", b)
+        Task b = filteredTask("b")
+        Task c = task("c", dependsOn: [b])
+        Spec<Task> filter = Mock()
+
+        and:
+        filter.isSatisfiedBy(_) >> { Task t -> t != b }
+
+        when:
+        executionPlan.useFilter(filter)
+        addToGraphAndPopulate([c]);
+
+        then:
+        executes(c)
+    }
+
+    def "one parallel task per project is allowed"() {
+        given:
+        //2 projects, 2 tasks each
+        def projectA = createChildProject(root, "a")
+        def projectB = createChildProject(root, "b")
+
+        def fooA = projectA.task("foo")
+        def barA = projectA.task("bar")
+
+        def fooB = projectB.task("foo")
+        def barB = projectB.task("bar")
+
+        addToGraphAndPopulate([fooA, barA, fooB, barB])
 
         when:
-        executionPlan.useFilter({ it != b } as Spec<Task>)
-        executionPlan.addToTaskGraph([c]);
+        def t1 = executionPlan.getTaskToExecute()
+        def t2 = executionPlan.getTaskToExecute()
 
         then:
-        executedTasks == [c]
+        t1.task.project != t2.task.project
+
+        when:
+        executionPlan.taskComplete(t1)
+        executionPlan.taskComplete(t2)
+        def t3 = executionPlan.getTaskToExecute()
+        def t4 = executionPlan.getTaskToExecute()
+
+        then:
+        t3.task.project != t4.task.project
+    }
+
+    void executes(Task... expectedTasks) {
+        assert executionPlan.tasks == expectedTasks as List
+        assert expectedTasks == expectedTasks as List
+    }
+
+    def getExecutedTasks() {
+        def tasks = []
+        def taskInfo
+        while ((taskInfo = taskToExecute) != null) {
+            tasks << taskInfo.task
+            executionPlan.taskComplete(taskInfo)
+        }
+        return tasks
+    }
+
+    private TaskDependency taskDependencyResolvingTo(TaskInternal task, List<Task> tasks) {
+        Mock(TaskDependency) {
+            getDependencies(task) >> tasks
+        }
+    }
+
+    private TaskDependency brokenDependencies() {
+        Mock(TaskDependency) {
+            0 * getDependencies(_)
+        }
+    }
+
+    private void dependsOn(TaskInternal task, List<Task> dependsOnTasks) {
+        task.getTaskDependencies() >> taskDependencyResolvingTo(task, dependsOnTasks)
     }
 
-    private void dependsOn(TaskInternal task, final Task... dependsOnTasks) {
-        TaskDependency taskDependency = Mock()
-        task.getTaskDependencies() >> taskDependency
-        taskDependency.getDependencies(task) >> toSet(dependsOnTasks)
+    private void mustRunAfter(TaskInternal task, List<Task> mustRunAfterTasks) {
+        task.getMustRunAfter() >> taskDependencyResolvingTo(task, mustRunAfterTasks)
     }
-    
-    private Task brokenTask(String name, final RuntimeException failure, final Task... dependsOnTasks) {
-        final TaskInternal task = createTask(name);
-        dependsOn(task, dependsOnTasks);
 
+    private void finalizedBy(TaskInternal task, List<Task> finalizedByTasks) {
+        task.getFinalizedBy() >> taskDependencyResolvingTo(task, finalizedByTasks)
+    }
+
+    private void shouldRunAfter(TaskInternal task, List<Task> shouldRunAfterTasks) {
+        task.getShouldRunAfter() >> taskDependencyResolvingTo(task, shouldRunAfterTasks)
+    }
+
+    private void failure(TaskInternal task, final RuntimeException failure) {
         task.state.getFailure() >> failure
         task.state.rethrowFailure() >> { throw failure }
-        return task;
     }
-    
-    private TaskInternal task(final String name, final Task... dependsOnTasks) {
+
+    private TaskInternal task(final String name) {
+        task([:], name)
+    }
+
+    private TaskInternal task(Map options, final String name) {
+        def task = createTask(name)
+        relationships(options, task)
+        if (options.failure) {
+            failure(task, options.failure)
+        }
+        task.getDidWork() >> (options.containsKey('didWork') ? options.didWork : true)
+        return task
+    }
+
+    private void relationships(Map options, TaskInternal task) {
+        dependsOn(task, options.dependsOn ?: [])
+        mustRunAfter(task, options.mustRunAfter ?: [])
+        shouldRunAfter(task, options.shouldRunAfter ?: [])
+        finalizedBy(task, options.finalizedBy ?: [])
+    }
+
+    private TaskInternal filteredTask(final String name) {
         def task = createTask(name);
-        dependsOn(task, dependsOnTasks);
-        task.state.getFailure() >> null
-        return task;
+        task.getTaskDependencies() >> brokenDependencies()
+        task.getMustRunAfter() >> brokenDependencies()
+        task.getShouldRunAfter() >> brokenDependencies()
+        task.getFinalizedBy() >> taskDependencyResolvingTo(task, [])
+        return task
     }
-    
+
     private TaskInternal createTask(final String name) {
         TaskInternal task = Mock()
         TaskState state = Mock()
@@ -403,44 +873,5 @@ public class DefaultTaskExecutionPlanTest extends Specification {
         }
         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
index 291bf8d..9f1b67a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
@@ -22,18 +22,15 @@ 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.DefaultTaskDependency;
 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;
@@ -48,21 +45,17 @@ 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.TestUtil.createRootProject;
+import static org.gradle.util.TestUtil.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
- */
 @RunWith(JMock.class)
 public class DefaultTaskGraphExecuterTest {
-
-    JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+    final JUnit4Mockery context = new JUnit4GroovyMockery();
+    final ListenerManager listenerManager = context.mock(ListenerManager.class);
     DefaultTaskGraphExecuter taskExecuter;
     ProjectInternal root;
     List<Task> executedTasks = new ArrayList<Task>();
@@ -76,7 +69,7 @@ public class DefaultTaskGraphExecuterTest {
             one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
             will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
         }});
-        taskExecuter = new org.gradle.execution.taskgraph.DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor());
+        taskExecuter = new DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor());
     }
 
     @Test
@@ -244,8 +237,9 @@ public class DefaultTaskGraphExecuterTest {
         Task c = task("c", b);
         dependsOn(a, c);
 
+        taskExecuter.addTasks(toList(c));
         try {
-            taskExecuter.addTasks(toList(c));
+            taskExecuter.execute();
             fail();
         } catch (CircularReferenceException e) {
             // Expected
@@ -524,6 +518,14 @@ public class DefaultTaskGraphExecuterTest {
             will(returnValue(":" + name));
             allowing(task).getState();
             will(returnValue(state));
+            allowing(task).getMustRunAfter();
+            will(returnValue(new DefaultTaskDependency()));
+            allowing(task).getFinalizedBy();
+            will(returnValue(new DefaultTaskDependency()));
+            allowing(task).getShouldRunAfter();
+            will(returnValue(new DefaultTaskDependency()));
+            allowing(task).getDidWork();
+            will(returnValue(true));
             allowing(task).compareTo(with(notNullValue(TaskInternal.class)));
             will(new org.jmock.api.Action() {
                 public Object invoke(Invocation invocation) throws Throwable {
@@ -556,25 +558,4 @@ public class DefaultTaskGraphExecuterTest {
         }
     }
 
-    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/DefaultTaskPlanExecutorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutorTest.groovy
new file mode 100644
index 0000000..5961136
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutorTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Specification
+
+class DefaultTaskPlanExecutorTest extends Specification {
+    def taskPlan = Mock(TaskExecutionPlan)
+    def executionListener = Mock(TaskExecutionListener)
+    def executor = new DefaultTaskPlanExecutor()
+
+    def "executes tasks until no further tasks remain"() {
+        def task = Mock(TaskInternal)
+        def taskInfo = new TaskInfo(task)
+
+        when:
+        executor.process(taskPlan, executionListener)
+
+        then:
+        1 * taskPlan.taskToExecute >> taskInfo
+        1 * executionListener.beforeExecute(task)
+        1 * task.executeWithoutThrowingTaskFailure()
+        1 * executionListener.afterExecute(task, _)
+        1 * taskPlan.taskComplete(taskInfo)
+        1 * taskPlan.taskToExecute >> null
+        1 * taskPlan.awaitCompletion()
+    }
+
+    def "rethrows task execution failure"() {
+        def failure = new RuntimeException()
+
+        given:
+        _ * taskPlan.awaitCompletion() >> { throw failure }
+
+        when:
+        executor.process(taskPlan, executionListener)
+
+        then:
+        def e = thrown(RuntimeException)
+        e == failure
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/ParallelTaskExecutionPlanTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/ParallelTaskExecutionPlanTest.groovy
deleted file mode 100644
index afcb0ee..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/ParallelTaskExecutionPlanTest.groovy
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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;
-
-
-public class ParallelTaskExecutionPlanTest extends DefaultTaskExecutionPlanTest {
-
-    protected TaskInfo getTaskToExecute() {
-        executionPlan.getTaskToExecute()
-    }
-}
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskDependencyGraphTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskDependencyGraphTest.groovy
new file mode 100644
index 0000000..7be312a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskDependencyGraphTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Specification
+
+class TaskDependencyGraphTest extends Specification {
+    def graph = new TaskDependencyGraph()
+    def a = task('a')
+    def b = task('b')
+    def c = task('c')
+    def d = task('d')
+    def e = task('e')
+
+    private TaskInternal task(String name) {
+        Mock(TaskInternal) {
+            getName() >> name
+            compareTo(_) >> { args -> name.compareTo(args[0].name)}
+        }
+    }
+
+    void 'can create a node for a task'() {
+        when:
+        def node = graph.addNode(a)
+
+        then:
+        !node.inKnownState
+        node.dependencyPredecessors.empty
+        node.mustSuccessors.empty
+        node.dependencySuccessors.empty
+        node.shouldSuccessors.empty
+        node.finalizers.empty
+    }
+
+    void 'caches node for a given task'() {
+        when:
+        def node = graph.addNode(a)
+
+        then:
+        graph.getNode(a).is(node)
+        graph.addNode(a).is(node)
+    }
+
+    void 'can add multiple nodes'() {
+        when:
+        graph.addNode(a)
+        graph.addNode(b)
+
+        then:
+        graph.tasks == [a, b] as Set
+    }
+
+    void 'clear'() {
+        when:
+        graph.addNode(a)
+        graph.addNode(b)
+        graph.addNode(c)
+        graph.clear()
+
+        then:
+        !graph.tasks
+    }
+}
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
index f7a921a..83fbbc5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactoryTest.groovy
@@ -17,15 +17,17 @@
 package org.gradle.execution.taskgraph;
 
 
-import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess
+import org.gradle.internal.concurrent.ExecutorFactory
 import spock.lang.Specification
 
 public class TaskPlanExecutorFactoryTest extends Specification {
     final TaskArtifactStateCacheAccess cache = Mock()
+    final ExecutorFactory executorFactory = Mock()
 
     def "creates a default executor"() {
         when:
-        def factory = new TaskPlanExecutorFactory(cache, 0)
+        def factory = new TaskPlanExecutorFactory(0, executorFactory)
 
         then:
         factory.create().class == DefaultTaskPlanExecutor
@@ -33,7 +35,7 @@ public class TaskPlanExecutorFactoryTest extends Specification {
 
     def "creates a parallel executor"() {
         when:
-        def factory = new TaskPlanExecutorFactory(cache, parallelExecuterCount)
+        def factory = new TaskPlanExecutorFactory(parallelExecuterCount, executorFactory)
 
         then:
         factory.create().class == ParallelTaskPlanExecutor
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy
index 2120e88..eef22f6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy
@@ -17,20 +17,17 @@
 package org.gradle.execution.taskpath
 
 import org.gradle.api.internal.project.DefaultProject
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/8/13
- */
 class ProjectFinderByTaskPathTest extends Specification {
 
     def finder = new ProjectFinderByTaskPath()
 
     //root->foo->bar
-    DefaultProject root = HelperUtil.createRootProject()
-    DefaultProject foo = HelperUtil.createChildProject(root, "foo")
-    DefaultProject bar = HelperUtil.createChildProject(foo, "bar")
+    DefaultProject root = TestUtil.createRootProject()
+    DefaultProject foo = TestUtil.createChildProject(root, "foo")
+    DefaultProject bar = TestUtil.createChildProject(foo, "bar")
 
     def "finds root"() {
         expect:
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy
index 6b65fa9..af61b10 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.execution.taskpath
 import org.gradle.api.internal.project.ProjectInternal
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 class ResolvedTaskPathTest extends Specification {
 
     def "knows if path is qualified"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy
index c2acb52..5628a0c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.execution.taskpath
 import org.gradle.api.internal.project.ProjectInternal
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/29/13
- */
 class TaskPathResolverTest extends Specification {
 
     private finder = Mock(ProjectFinderByTaskPath)
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
index 0a32cd1..fc1fc83 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
@@ -20,20 +20,20 @@ package org.gradle.groovy.scripts
 
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.file.FileLookup
 import org.gradle.api.internal.project.DefaultProject
-import org.gradle.internal.service.ServiceRegistry
 import org.gradle.api.logging.LoggingManager
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.logging.StandardOutputCapture
-import org.gradle.util.HelperUtil
 import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TestUtil
 import org.jmock.integration.junit4.JMock
 import org.junit.Test
 import org.junit.runner.RunWith
-import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.assertEquals
+
 @RunWith(JMock)
 class DefaultScriptTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
@@ -47,10 +47,14 @@ class DefaultScriptTest {
             will(returnValue(context.mock(StandardOutputCapture.class)))
             allowing(serviceRegistryMock).get(LoggingManager.class)
             will(returnValue(context.mock(LoggingManager.class)))
+            allowing(serviceRegistryMock).get(Instantiator)
+            will(returnValue(context.mock(Instantiator)))
+            allowing(serviceRegistryMock).get(FileLookup)
+            will(returnValue(context.mock(FileLookup)))
         }
 
         DefaultScript script = new GroovyShell(createBaseCompilerConfiguration()).parse(testScriptText)
-        DefaultProject testProject = HelperUtil.createRootProject()
+        DefaultProject testProject = TestUtil.createRootProject()
         testProject.custom = 'true'
         script.setScriptSource(new StringScriptSource('script', '//'))
         script.init(testProject, serviceRegistryMock)
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java
index 17a7b8e..d314f2d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java
@@ -15,9 +15,6 @@
  */
 package org.gradle.groovy.scripts;
 
-/**
- * @author Hans Dockter
- */
 public class EmptyScript extends groovy.lang.Script {
     public Object run() {
         return null;
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
index d960ced..3ced662 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
@@ -50,9 +50,6 @@ import static org.gradle.util.Matchers.isA;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultScriptCompilationHandlerTest {
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy
index 17d81fc..d533a3e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy
@@ -15,27 +15,30 @@
  */
 package org.gradle.groovy.scripts.internal
 
-import spock.lang.Specification
-import org.gradle.cache.CacheRepository
+import org.gradle.api.Action
 import org.gradle.api.internal.resource.Resource
-import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.cache.CacheBuilder
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.CacheValidator
 import org.gradle.cache.PersistentCache
+import org.gradle.groovy.scripts.Script
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.groovy.scripts.Transformer
-import org.gradle.groovy.scripts.Script
-import org.gradle.cache.CacheValidator
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import spock.lang.Specification
 
 class FileCacheBackedScriptClassCompilerTest extends Specification {
     final ScriptCompilationHandler scriptCompilationHandler = Mock()
     final CacheRepository cacheRepository = Mock()
-    final DirectoryCacheBuilder cacheBuilder = Mock()
+    final CacheBuilder cacheBuilder = Mock()
     final CacheValidator validator = Mock()
     final PersistentCache cache = Mock()
     final ScriptSource source = Mock()
     final ClassLoader classLoader = Mock()
     final Transformer transformer = Mock()
     final File cacheDir = new File("base-dir")
-    final FileCacheBackedScriptClassCompiler compiler = new FileCacheBackedScriptClassCompiler(cacheRepository, validator, scriptCompilationHandler)
+    final FileCacheBackedScriptClassCompiler compiler = new FileCacheBackedScriptClassCompiler(cacheRepository, validator, scriptCompilationHandler, Stub(ProgressLoggerFactory))
 
     def setup() {
         Resource resource = Mock()
@@ -104,4 +107,29 @@ class FileCacheBackedScriptClassCompilerTest extends Specification {
         1 * scriptCompilationHandler.loadFromDir(source, classLoader, new File(cacheDir, "classes"), Script) >> Script
         0 * scriptCompilationHandler._
     }
+
+    def "reports compilation progress even in case of a failure"() {
+        def factory = Mock(ProgressLoggerFactory)
+        def delegate = Mock(Action)
+        def cache = Mock(PersistentCache)
+        def logger = Mock(ProgressLogger)
+
+        def initializer = new FileCacheBackedScriptClassCompiler.ProgressReportingInitializer(factory, delegate)
+
+        when:
+        initializer.execute(cache)
+
+        then:
+        def ex = thrown(RuntimeException)
+        ex.message == "Boo!"
+
+        1 * factory.newOperation(FileCacheBackedScriptClassCompiler) >> logger
+        1 * logger.start("Compile script into cache", "Compiling script into cache") >> logger
+
+        then:
+        1 * delegate.execute(cache) >> { throw new RuntimeException("Boo!")} //stress it a bit with a failure
+
+        then:
+        1 * logger.completed()
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
index 69ce214..3ce9934 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
@@ -16,7 +16,7 @@
 package org.gradle.initialization;
 
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
@@ -100,8 +100,8 @@ public class BuildFileProjectSpecTest {
         }
     }
 
-    private IProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
-        final IProjectRegistry<ProjectIdentifier> registry = context.mock(IProjectRegistry.class, String.valueOf(counter++));
+    private ProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
+        final ProjectRegistry<ProjectIdentifier> registry = context.mock(ProjectRegistry.class, String.valueOf(counter++));
         context.checking(new Expectations(){{
             allowing(registry).getAllProjects();
             will(returnValue(toSet(projects)));
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildLayoutParametersTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildLayoutParametersTest.groovy
new file mode 100644
index 0000000..4954dc8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildLayoutParametersTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization
+
+import org.gradle.StartParameter
+import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.util.GFileUtils.canonicalise
+
+class BuildLayoutParametersTest extends Specification {
+
+    @Rule SetSystemProperties props = new SetSystemProperties()
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def "has reasonable defaults"() {
+        expect:
+        def params = new BuildLayoutParameters()
+        params.searchUpwards
+        params.gradleUserHomeDir == canonicalise(StartParameter.DEFAULT_GRADLE_USER_HOME)
+        params.projectDir == canonicalise(SystemProperties.getCurrentDir())
+    }
+
+    def "reads gradle user home dir from system property"() {
+        def dir = temp.createDir("someGradleUserHomePath")
+        System.setProperty(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY, dir.absolutePath)
+
+        when:
+        def params = new BuildLayoutParameters()
+
+        then:
+        params.gradleUserHomeDir == dir
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildProgressLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildProgressLoggerTest.groovy
deleted file mode 100644
index 22e769e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildProgressLoggerTest.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.initialization
-
-import spock.lang.Specification
-import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.logging.ProgressLogger
-import org.gradle.api.invocation.Gradle
-import org.gradle.api.execution.TaskExecutionGraph
-import org.gradle.BuildResult
-
-class BuildProgressLoggerTest extends Specification {
-    private final ProgressLoggerFactory progressLoggerFactory = Mock()
-    private final ProgressLogger progressLogger = Mock()
-    private final Gradle gradle = Mock()
-    private final TaskExecutionGraph graph = Mock()
-    private final BuildResult result = Mock()
-    private final BuildProgressLogger logger = new BuildProgressLogger(progressLoggerFactory)
-
-    def logsBuildStages() {
-        given:
-        gradle.getTaskGraph() >> graph
-        result.getGradle() >> gradle
-
-        when:
-        logger.buildStarted(gradle)
-
-        then:
-        1 * progressLoggerFactory.newOperation(BuildProgressLogger) >> progressLogger
-        1 * progressLogger.setDescription('Configure projects')
-        1 * progressLogger.setShortDescription('Loading')
-        1 * progressLogger.started()
-        0 * progressLogger._
-
-        when:
-        logger.graphPopulated(graph)
-
-        then:
-        1 * progressLogger.completed()
-        1 * progressLoggerFactory.newOperation(BuildProgressLogger) >> progressLogger
-        1 * progressLogger.setDescription('Execute tasks')
-        1 * progressLogger.setShortDescription('Building')
-        1 * progressLogger.started()
-        0 * progressLogger._
-
-        when:
-        logger.buildFinished(result)
-
-        then:
-        1 * progressLogger.completed()
-        0 * progressLogger._
-    }
-
-    def ignoresNestedBuilds() {
-        given:
-        gradle.getParent() >> Mock(Gradle)
-        
-        when:
-        logger.buildStarted(gradle)
-
-        then:
-        0 * progressLoggerFactory._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
deleted file mode 100644
index 8f86e87..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.BuildResult
-import org.gradle.GradleLauncher
-import org.gradle.StartParameter
-import org.gradle.api.Project
-import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.plugins.EmbeddableJavaProject
-import org.gradle.api.invocation.Gradle
-import org.gradle.api.plugins.Convention
-import org.gradle.cache.CacheBuilder
-import org.gradle.cache.CacheRepository
-import org.gradle.cache.DirectoryCacheBuilder
-import org.gradle.cache.PersistentCache
-import org.gradle.cache.internal.FileLockManager
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.api.Action
-import org.junit.runner.RunWith
-import org.junit.*
-
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertEquals
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock)
- at Ignore //TODO SF spockify
-class BuildSourceBuilderTest {
-    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    BuildSourceBuilder buildSourceBuilder
-    GradleLauncherFactory gradleFactoryMock = context.mock(GradleLauncherFactory.class)
-    GradleLauncher gradleMock = context.mock(GradleLauncher.class)
-    Project rootProjectMock = context.mock(Project.class)
-    FileCollection configurationMock = context.mock(FileCollection.class)
-    TestFile rootDir = tmpDir.createDir('root')
-    TestFile testBuildSrcDir = rootDir.file('buildSrc').createDir()
-    TestFile buildSrcCache = testBuildSrcDir.createDir(".gradle/noVersion/buildSrc");
-    List testDependencies
-    StartParameter expectedStartParameter
-    CacheRepository cacheRepository = context.mock(CacheRepository.class)
-    PersistentCache persistentCache = context.mock(PersistentCache.class)
-    BuildResult expectedBuildResult
-    Gradle build = context.mock(Gradle.class)
-    EmbeddableJavaProject projectMetaInfo = context.mock(EmbeddableJavaProject.class)
-
-    @Before public void setUp() {
-        buildSourceBuilder = new BuildSourceBuilder(gradleFactoryMock, context.mock(ClassLoaderRegistry.class), cacheRepository)
-        expectedStartParameter = new StartParameter(currentDir: testBuildSrcDir)
-        testDependencies = ['dep1' as File, 'dep2' as File]
-
-        Convention convention = context.mock(Convention)
-        context.checking {
-            allowing(build).getRootProject(); will(returnValue(rootProjectMock))
-            allowing(build).getStartParameter(); will(returnValue(expectedStartParameter))
-            allowing(rootProjectMock).getConvention(); will(returnValue(convention))
-            allowing(convention).getPlugin(EmbeddableJavaProject);
-            will(returnValue(projectMetaInfo))
-            allowing(projectMetaInfo).getRuntimeClasspath(); will(returnValue(configurationMock))
-            allowing(configurationMock).getFiles(); will(returnValue(testDependencies as Set))
-        }
-        expectedBuildResult = new BuildResult(build, null)
-    }
-
-    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasNotBeenBuiltBefore() {
-        expectMarkerFileFetchedFromCache(false)
-        context.checking {
-            one(projectMetaInfo).getRebuildTasks(); will(returnValue(['clean', 'build']))
-            one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()));
-            will(returnValue(gradleMock))
-            one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
-            one(gradleMock).run(); will(returnValue(expectedBuildResult))
-        }
-        expectMarkerFileInCache()
-
-        createBuildFile()
-        def actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter).asFiles
-        assertEquals(testDependencies, actualClasspath)
-    }
-
-    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasBeenBuiltBefore() {
-        expectMarkerFileFetchedFromCache(true)
-        context.checking {
-            one(projectMetaInfo).getBuildTasks(); will(returnValue(['build']))
-            one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()))
-            will(returnValue(gradleMock))
-            one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
-            one(gradleMock).run(); will(returnValue(expectedBuildResult))
-        }
-        expectMarkerFileInCache()
-
-        def actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter).asFiles
-        assertEquals(testDependencies, actualClasspath)
-    }
-
-    @Test public void testCreateClasspathWhenBuildSrcDirDoesNotExist() {
-        expectedStartParameter = expectedStartParameter.newInstance()
-        expectedStartParameter.setCurrentDir(new File('nonexisting'));
-        assertEquals([], buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter).asFiles)
-    }
-
-    private expectMarkerFileFetchedFromCache(boolean markerFileExists) {
-        if (markerFileExists) {
-            buildSrcCache.createFile("built.bin");
-        } else {
-            new File(buildSrcCache, "built.bin").delete()
-        }
-        context.checking {
-            DirectoryCacheBuilder builder = context.mock(DirectoryCacheBuilder.class)
-            one(cacheRepository).cache('buildSrc')
-            will(returnValue(builder))
-
-            one(builder).forObject(testBuildSrcDir)
-            will(returnValue(builder))
-
-            one(builder).withLockMode(FileLockManager.LockMode.None)
-            will(returnValue(builder))
-
-            one(builder).withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange)
-            will(returnValue(builder))
-
-            one(builder).open()
-            will(returnValue(persistentCache))
-
-            one(persistentCache).getBaseDir()
-            will(returnValue(buildSrcCache))
-            one(persistentCache).useCache(withParam(equalTo("rebuild buildSrc")), (org.gradle.internal.Factory) withParam(any(org.gradle.internal.Factory.class)))
-            will(executeBuildSrcBuild())
-        }
-    }
-
-    private expectMarkerFileInCache() {
-        buildSrcCache.file("buildSrc.lock").exists()
-    }
-
-    private createBuildFile() {
-        new File(testBuildSrcDir, Project.DEFAULT_BUILD_FILE).createNewFile()
-    }
-
-    private Action executeBuildSrcBuild() {
-        return [invoke: {invocation -> invocation.getParameter(1).create()},
-                describeTo: {description -> }] as Action
-    }
-
-    private Action notifyProjectsEvaluated() {
-        return [invoke: {invocation -> invocation.getParameter(0).projectsEvaluated(build)},
-                describeTo: {description -> }] as Action
-    }
-
-    @After public void cleanup() {
-        buildSrcCache.file("buildSrc.lock").delete()
-    }
-}
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 553cc21..231e265 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -37,9 +37,6 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultCommandLineConverterTest {
     @Rule
     public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
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 f50ef15..c14dc08 100755
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
@@ -16,14 +16,13 @@
 package org.gradle.initialization;
 
 import org.gradle.api.GradleScriptException;
-import org.gradle.api.internal.Contextual;
-import org.gradle.api.internal.LocationAwareException;
-import org.gradle.api.internal.MultiCauseException;
+import org.gradle.internal.exceptions.Contextual;
+import org.gradle.internal.exceptions.LocationAwareException;
+import org.gradle.internal.exceptions.MultiCauseException;
 import org.gradle.api.tasks.TaskExecutionException;
 import org.gradle.groovy.scripts.Script;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.listener.ListenerManager;
-import org.gradle.listener.ListenerNotificationException;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -59,11 +58,16 @@ public class DefaultExceptionAnalyserTest {
     }
 
     @Test
-    public void usesOriginalExceptionWhenItIsNotAContextualException() {
+    public void wrapsOriginalExceptionWhenItIsNotAContextualException() {
         Throwable failure = new RuntimeException();
 
         DefaultExceptionAnalyser analyser = analyser();
-        assertThat(analyser.transform(failure), sameInstance(failure));
+        Throwable transformed = analyser.transform(failure);
+        assertThat(transformed, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformed;
+        assertThat(gse.getCause(), sameInstance(failure));
+        assertThat(gse.getReportableCauses(), isEmpty());
     }
 
     @Test
@@ -76,7 +80,6 @@ public class DefaultExceptionAnalyserTest {
         assertThat(transformedFailure, instanceOf(LocationAwareException.class));
 
         LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(failure));
         assertThat(gse.getCause(), sameInstance(failure));
         assertThat(gse.getReportableCauses(), isEmpty());
     }
@@ -92,7 +95,6 @@ public class DefaultExceptionAnalyserTest {
         assertThat(transformedFailure, instanceOf(LocationAwareException.class));
 
         LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(failure));
         assertThat(gse.getCause(), sameInstance(failure));
         assertThat(gse.getReportableCauses(), equalTo(toList(cause)));
     }
@@ -152,26 +154,11 @@ public class DefaultExceptionAnalyserTest {
         assertThat(transformedFailure, instanceOf(LocationAwareException.class));
 
         LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(failure));
         assertThat(gse.getCause(), sameInstance(failure));
         assertThat(gse.getReportableCauses(), equalTo(toList(cause1, cause2)));
     }
 
     @Test
-    public void unpacksListenerNotificationException() {
-        Throwable cause = new RuntimeException();
-        Throwable failure = new ListenerNotificationException("broken", cause);
-
-        Throwable transformedFailure = analyser().transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
-        assertThat(gse.getCause(), sameInstance(failure));
-        assertThat(gse.getReportableCauses(), isEmpty());
-    }
-
-    @Test
     public void usesOriginalExceptionWhenItIsAlreadyLocationAware() {
         Throwable failure = locationAwareException(null);
 
@@ -190,7 +177,6 @@ public class DefaultExceptionAnalyserTest {
         assertThat(transformedFailure, instanceOf(LocationAwareException.class));
 
         LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
         assertThat(gse.getCause(), sameInstance(cause));
     }
 
@@ -213,7 +199,6 @@ public class DefaultExceptionAnalyserTest {
         assertThat(transformedFailure, instanceOf(LocationAwareException.class));
 
         LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
         assertThat(gse.getCause(), sameInstance(cause));
     }
 
@@ -227,6 +212,23 @@ public class DefaultExceptionAnalyserTest {
         assertThat(analyser.transform(failure), sameInstance(cause));
     }
 
+    @Test
+    public void wrapsArbitraryFailureWithLocationInformation() {
+        Throwable failure = new RuntimeException();
+        failure.setStackTrace(toArray(element, otherElement, callerElement));
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), sameInstance(source));
+        assertThat(gse.getLineNumber(), equalTo(7));
+        assertThat(gse.getCause(), sameInstance(failure));
+    }
+
     private Throwable locationAwareException(final Throwable cause) {
         final Throwable failure = context.mock(TestException.class);
         context.checking(new Expectations() {{
@@ -276,19 +278,12 @@ 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
     public abstract static class TestException extends LocationAwareException {
         protected TestException(Throwable cause, ScriptSource source, Integer lineNumber) {
-            super(cause, cause, source, lineNumber);
+            super(cause, source, lineNumber);
         }
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
index 0a4f694..933fae0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
@@ -18,11 +18,17 @@ package org.gradle.initialization
 import org.gradle.GradleLauncher
 import org.gradle.StartParameter
 import org.gradle.cli.CommandLineConverter
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.internal.service.scopes.GlobalScopeServices
+import org.gradle.logging.LoggingServiceRegistry
 import spock.lang.Specification
 
 class DefaultGradleLauncherFactoryTest extends Specification {
     final CommandLineConverter<StartParameter> parameterConverter = Mock()
-    final DefaultGradleLauncherFactory factory = new DefaultGradleLauncherFactory();
+    final ServiceRegistry sharedServices = new DefaultServiceRegistry(LoggingServiceRegistry.newEmbeddableLogging(), NativeServices.getInstance()).addProvider(new GlobalScopeServices(false))
+    final DefaultGradleLauncherFactory factory = new DefaultGradleLauncherFactory(sharedServices)
 
     def setup() {
         factory.setCommandLineConverter(parameterConverter);
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 3e42d2b..57138da 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
@@ -24,14 +24,16 @@ import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.ExceptionAnalyser;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.file.TestFiles;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.configuration.BuildConfigurer;
 import org.gradle.execution.BuildExecuter;
 import org.gradle.execution.TaskGraphExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.HelperUtil;
 import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.TestUtil;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -47,9 +49,6 @@ import java.io.File;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class DefaultGradleLauncherTest {
     private BuildLoader buildLoaderMock;
@@ -72,6 +71,8 @@ public class DefaultGradleLauncherTest {
 
     private JUnit4Mockery context = new JUnit4GroovyMockery();
 
+    private ClassLoaderScope settingsClassLoaderScope = context.mock(ClassLoaderScope.class);
+    private ClassLoaderScope rootProjectClassLoaderScope = context.mock(ClassLoaderScope.class);
     private ExceptionAnalyser exceptionAnalyserMock = context.mock(ExceptionAnalyser.class);
     private LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
     private ModelConfigurationListener modelListenerMock = context.mock(ModelConfigurationListener.class);
@@ -96,9 +97,10 @@ public class DefaultGradleLauncherTest {
         File expectedRootDir = tmpDir.file("rootDir");
         File expectedCurrentDir = new File(expectedRootDir, "currentDir");
 
-        expectedRootProjectDescriptor = new DefaultProjectDescriptor(null, "someName", new File("somedir"), new DefaultProjectDescriptorRegistry());
-        expectedRootProject = HelperUtil.createRootProject(expectedRootDir);
-        expectedCurrentProject = HelperUtil.createRootProject(expectedCurrentDir);
+        expectedRootProjectDescriptor = new DefaultProjectDescriptor(null, "someName", new File("somedir"), new DefaultProjectDescriptorRegistry(),
+                TestFiles.resolver(expectedRootDir));
+        expectedRootProject = TestUtil.createRootProject(expectedRootDir);
+        expectedCurrentProject = TestUtil.createRootProject(expectedCurrentDir);
 
         expectedStartParams = new StartParameter();
         expectedStartParams.setCurrentDir(expectedCurrentDir);
@@ -113,6 +115,10 @@ public class DefaultGradleLauncherTest {
             {
                 allowing(settingsMock).getRootProject();
                 will(returnValue(expectedRootProjectDescriptor));
+                allowing(settingsMock).getClassLoaderScope();
+                will(returnValue(settingsClassLoaderScope));
+                allowing(settingsClassLoaderScope).createSibling();
+                will(returnValue(rootProjectClassLoaderScope));
                 allowing(gradleMock).getRootProject();
                 will(returnValue(expectedRootProject));
                 allowing(gradleMock).getDefaultProject();
@@ -157,7 +163,7 @@ public class DefaultGradleLauncherTest {
         expectSettingsBuilt();
         expectBuildListenerCallbacks();
         context.checking(new Expectations() {{
-            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, rootProjectClassLoaderScope);
             one(buildConfigurerMock).configure(gradleMock);
         }});
         BuildResult buildResult = gradleLauncher.getBuildAnalysis();
@@ -174,7 +180,7 @@ public class DefaultGradleLauncherTest {
         expectSettingsBuilt();
         context.checking(new Expectations() {{
             one(buildBroadcaster).buildStarted(gradleMock);
-            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, rootProjectClassLoaderScope);
             will(throwException(exception));
             one(exceptionAnalyserMock).transform(exception);
             will(returnValue(transformedException));
@@ -192,7 +198,7 @@ public class DefaultGradleLauncherTest {
         expectSettingsBuilt();
         expectBuildListenerCallbacks();
         context.checking(new Expectations() {{
-            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, rootProjectClassLoaderScope);
             one(buildConfigurerMock).configure(gradleMock);
         }});
 
@@ -245,7 +251,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))));
         }});
 
@@ -254,7 +260,7 @@ public class DefaultGradleLauncherTest {
     }
 
     private void expectLoggingStartedAndStoped() {
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(loggingManagerMock).start();
             one(loggingManagerMock).stop();
         }});
@@ -289,7 +295,7 @@ public class DefaultGradleLauncherTest {
     private void expectDagBuilt() {
         context.checking(new Expectations() {
             {
-                one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
+                one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, rootProjectClassLoaderScope);
                 one(buildConfigurerMock).configure(gradleMock);
                 one(buildExecuter).select(gradleMock);
             }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
index bc19764..0401418 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
@@ -32,9 +32,6 @@ import java.util.Properties;
 
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultGradlePropertiesLoaderTest {
     private DefaultGradlePropertiesLoader gradlePropertiesLoader;
     private File gradleUserHomeDir;
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java
index c5f6a32..8466aca 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java
@@ -15,25 +15,27 @@
  */
 package org.gradle.initialization;
 
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.TestFiles;
 import org.gradle.util.Path;
 import org.junit.Test;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
 
 import java.io.File;
 
-/**
- * @author Hans Dockter
- */
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
 public class DefaultProjectDescriptorRegistryTest {
     private static final File TEST_DIR = new File("testDir");
 
+    private static final FileResolver FILE_RESOLVER = TestFiles.resolver(TEST_DIR.getAbsoluteFile());
     private final DefaultProjectDescriptorRegistry registry = new DefaultProjectDescriptorRegistry();
 
     @Test
     public void addProjectDescriptor() {
-        DefaultProjectDescriptor rootProject = new DefaultProjectDescriptor(null, "testName", TEST_DIR, registry);
+        DefaultProjectDescriptor rootProject = new DefaultProjectDescriptor(null, "testName", TEST_DIR, registry, FILE_RESOLVER);
 
         registry.addProject(rootProject);
         assertSame(rootProject, registry.getProject(rootProject.getPath()));
@@ -42,7 +44,7 @@ public class DefaultProjectDescriptorRegistryTest {
 
     @Test
     public void changeProjectDescriptorPath() {
-        DefaultProjectDescriptor project = new DefaultProjectDescriptor(null, "name", TEST_DIR, registry);
+        DefaultProjectDescriptor project = new DefaultProjectDescriptor(null, "name", TEST_DIR, registry, FILE_RESOLVER);
         registry.addProject(project);
 
         registry.changeDescriptorPath(Path.path(":"), Path.path(":newPath"));
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java
index 54c121d..091f058 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java
@@ -15,29 +15,28 @@
  */
 package org.gradle.initialization;
 
+import org.gradle.api.Project;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.TestFiles;
 import org.gradle.util.Path;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import org.gradle.api.Project;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.Expectations;
 
 import java.io.File;
 import java.io.IOException;
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.*;
+
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class DefaultProjectDescriptorTest {
     private DefaultProjectDescriptor projectDescriptor;
     private DefaultProjectDescriptor parentProjectDescriptor;
     private static final String TEST_NAME = "testName";
     private static final File TEST_DIR = new File("testDir");
+    private static final FileResolver FILE_RESOLVER = TestFiles.resolver(TEST_DIR.getAbsoluteFile());
     private DefaultProjectDescriptorRegistry testProjectDescriptorRegistry;
     private JUnit4Mockery context = new JUnit4Mockery();
 
@@ -45,9 +44,9 @@ public class DefaultProjectDescriptorTest {
     public void setUp() {
         testProjectDescriptorRegistry = new DefaultProjectDescriptorRegistry();
         parentProjectDescriptor = new DefaultProjectDescriptor(null, "somename", new File("somefile"),
-                testProjectDescriptorRegistry);
+                testProjectDescriptorRegistry, FILE_RESOLVER);
         projectDescriptor = new DefaultProjectDescriptor(parentProjectDescriptor, TEST_NAME, TEST_DIR,
-                testProjectDescriptorRegistry);
+                testProjectDescriptorRegistry, FILE_RESOLVER);
     }
 
     @Test
@@ -70,7 +69,7 @@ public class DefaultProjectDescriptorTest {
     @Test
     public void setName() {
         final String newName = "newName";
-        final IProjectDescriptorRegistry projectDescriptorRegistryMock = context.mock(IProjectDescriptorRegistry.class);
+        final ProjectDescriptorRegistry projectDescriptorRegistryMock = context.mock(ProjectDescriptorRegistry.class);
         projectDescriptor.setProjectDescriptorRegistry(projectDescriptorRegistryMock);
         context.checking(new Expectations() {{
             one(projectDescriptorRegistryMock).changeDescriptorPath(Path.path(TEST_NAME), Path.path(Project.PATH_SEPARATOR + newName));
@@ -80,6 +79,24 @@ public class DefaultProjectDescriptorTest {
     }
 
     @Test
+    public void setProjectDirRelative() {
+        final ProjectDescriptorRegistry projectDescriptorRegistryMock = context.mock(ProjectDescriptorRegistry.class);
+        projectDescriptor.setProjectDescriptorRegistry(projectDescriptorRegistryMock);
+        projectDescriptor.setProjectDir(new File("relative/path"));
+        final String expectedPath = new File(TEST_DIR, "relative/path").getAbsolutePath();
+        assertEquals(expectedPath, projectDescriptor.getProjectDir().getAbsolutePath());
+    }
+
+    @Test
+    public void setProjectDirAbsolute() {
+        final ProjectDescriptorRegistry projectDescriptorRegistryMock = context.mock(ProjectDescriptorRegistry.class);
+        projectDescriptor.setProjectDescriptorRegistry(projectDescriptorRegistryMock);
+        String absolutePath = new File("absolute/path").getAbsolutePath();
+        projectDescriptor.setProjectDir(new File(absolutePath));
+        assertEquals(absolutePath, projectDescriptor.getProjectDir().getAbsolutePath());
+    }
+
+    @Test
     public void buildFileIsBuiltFromBuildFileNameAndProjectDir() throws IOException {
         projectDescriptor.setBuildFileName("project.gradle");
         assertEquals(new File(TEST_DIR, "project.gradle").getCanonicalFile(), projectDescriptor.getBuildFile());
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 ff540a4..aeef7f2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
@@ -20,49 +20,82 @@ import org.gradle.StartParameter
 import org.gradle.api.Project
 import org.gradle.api.UnknownProjectException
 import org.gradle.api.initialization.ProjectDescriptor
+import org.gradle.api.initialization.Settings
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.initialization.ScriptHandlerFactory
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.configuration.ScriptPluginFactory
 import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.internal.service.scopes.ServiceRegistryFactory
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
 import org.jmock.lib.legacy.ClassImposteriser
 import org.junit.Before
 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
- */
- at RunWith (JMock)
+ at RunWith(JMock)
 class DefaultSettingsTest {
     File settingsDir
     StartParameter startParameter
-    URLClassLoader expectedClassLoader
+    ClassLoaderScope classLoaderScope
     Map gradleProperties
     ScriptSource scriptSourceMock
     GradleInternal gradleMock
     DefaultSettings settings
     JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    DefaultProjectDescriptorRegistry projectDescriptorRegistry
-
-    @Before public void setUp() {
+    ProjectDescriptorRegistry projectDescriptorRegistry
+    ServiceRegistryFactory serviceRegistryFactory
+    PluginContainer pluginContainer
+    FileResolver fileResolver
+    ScriptPluginFactory scriptPluginFactory
+    ScriptHandlerFactory scriptHandlerFactory
+
+    @Before
+    public void setUp() {
         context.setImposteriser(ClassImposteriser.INSTANCE)
         settingsDir = new File('/somepath/root').absoluteFile
         gradleProperties = [someGradleProp: 'someValue']
         startParameter = new StartParameter(currentDir: new File(settingsDir, 'current'), gradleUserHomeDir: new File('gradleUserHomeDir'))
-        expectedClassLoader = new URLClassLoader(new URL[0])
+        classLoaderScope = context.mock(ClassLoaderScope)
 
         scriptSourceMock = context.mock(ScriptSource)
         gradleMock = context.mock(GradleInternal)
-
+        serviceRegistryFactory = context.mock(ServiceRegistryFactory.class)
+        pluginContainer = context.mock(PluginContainer.class)
+        scriptPluginFactory = context.mock(ScriptPluginFactory.class)
+        scriptHandlerFactory = context.mock(ScriptHandlerFactory.class)
+        fileResolver = context.mock(FileResolver.class)
         projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
-        settings = ThreadGlobalInstantiator.orCreate.newInstance(DefaultSettings,
-                gradleMock, projectDescriptorRegistry, expectedClassLoader, settingsDir, scriptSourceMock, startParameter
-        )
-    }
 
-    @Test public void testSettings() {
+        def settingsServices = context.mock(ServiceRegistry.class)
+        context.checking{
+                one(serviceRegistryFactory).createFor(with(any(Settings.class)));
+                will(returnValue(settingsServices));
+                one(settingsServices).get(PluginContainer.class);
+                will(returnValue(pluginContainer));
+                one(settingsServices).get(FileResolver.class);
+                will(returnValue(fileResolver));
+                one(settingsServices).get(ScriptPluginFactory.class);
+                will(returnValue(scriptPluginFactory));
+                one(settingsServices).get(ScriptHandlerFactory.class);
+                will(returnValue(scriptHandlerFactory));
+                one(settingsServices).get(ProjectDescriptorRegistry.class);
+                will(returnValue(projectDescriptorRegistry));
+        }
+        settings = ThreadGlobalInstantiator.orCreate.newInstance(DefaultSettings, serviceRegistryFactory,
+                    gradleMock, classLoaderScope, settingsDir, scriptSourceMock, startParameter);
+
+    }
+
+    @Test
+    public void testSettings() {
         assert settings.startParameter.is(startParameter)
         assertSame(settings, settings.getSettings())
         assertEquals(settingsDir, settings.getSettingsDir())
@@ -74,7 +107,8 @@ class DefaultSettingsTest {
         assertSame(gradleMock, settings.gradle)
     }
 
-    @Test public void testInclude() {
+    @Test
+    public void testInclude() {
         ProjectDescriptor rootProjectDescriptor = settings.getRootProject();
         String projectA = "a"
         String projectB = "b"
@@ -90,7 +124,8 @@ class DefaultSettingsTest {
         testDescriptor(settings.project(":$projectB:$projectC"), projectC, new File(settingsDir, "$projectB/$projectC"))
     }
 
-    @Test public void testIncludeFlat() {
+    @Test
+    public void testIncludeFlat() {
         ProjectDescriptor rootProjectDescriptor = settings.getRootProject();
         String projectA = "a"
         String projectB = "b"
@@ -106,7 +141,8 @@ class DefaultSettingsTest {
         assertEquals(projectDir, descriptor.getProjectDir())
     }
 
-    @Test public void testCreateProjectDescriptor() {
+    @Test
+    public void testCreateProjectDescriptor() {
         String testName = "testname"
         File testDir = new File("testDir")
         DefaultProjectDescriptor projectDescriptor = settings.createProjectDescriptor(settings.getRootProject(), testName, testDir)
@@ -116,19 +152,22 @@ class DefaultSettingsTest {
         assertEquals(testDir.canonicalFile, projectDescriptor.getProjectDir())
     }
 
-    @Test public void testFindDescriptorByPath() {
+    @Test
+    public void testFindDescriptorByPath() {
         DefaultProjectDescriptor projectDescriptor = createTestDescriptor();
         DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getPath())
         assertSame(foundProjectDescriptor, projectDescriptor)
     }
 
-    @Test public void testFindDescriptorByProjectDir() {
+    @Test
+    public void testFindDescriptorByProjectDir() {
         DefaultProjectDescriptor projectDescriptor = createTestDescriptor()
         DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getProjectDir())
         assertSame(foundProjectDescriptor, projectDescriptor)
     }
 
-    @Test (expected = UnknownProjectException) public void testDescriptorByPath() {
+    @Test(expected = UnknownProjectException)
+    public void testDescriptorByPath() {
         DefaultProjectDescriptor projectDescriptor = createTestDescriptor()
         DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getPath())
         assertSame(foundProjectDescriptor, projectDescriptor)
@@ -136,7 +175,8 @@ class DefaultSettingsTest {
     }
 
 
-    @Test (expected = UnknownProjectException) public void testDescriptorByProjectDir() {
+    @Test(expected = UnknownProjectException)
+    public void testDescriptorByProjectDir() {
         DefaultProjectDescriptor projectDescriptor = createTestDescriptor()
         DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getProjectDir())
         assertSame(foundProjectDescriptor, projectDescriptor)
@@ -153,27 +193,32 @@ class DefaultSettingsTest {
         return [name: 'someName']
     }
 
-    @Test public void testCreateClassLoader() {
+    @Test
+    public void testCreateClassLoader() {
         StartParameter expectedStartParameter = settings.startParameter.newInstance()
         expectedStartParameter.setCurrentDir(new File(settingsDir, DefaultSettings.DEFAULT_BUILD_SRC_DIR))
-        URLClassLoader createdClassLoader = settings.getClassLoader()
-        assertSame(createdClassLoader, expectedClassLoader)
+        def createdClassLoaderScope = settings.getClassLoaderScope()
+        assertSame(createdClassLoaderScope, classLoaderScope)
     }
 
-    @Test public void testCanGetAndSetDynamicProperties() {
+    @Test
+    public void testCanGetAndSetDynamicProperties() {
         settings.dynamicProp = 'value'
         assertEquals('value', settings.dynamicProp)
     }
 
-    @Test (expected = MissingPropertyException) public void testPropertyMissing() {
+    @Test(expected = MissingPropertyException)
+    public void testPropertyMissing() {
         settings.unknownProp
     }
 
-    @Test public void testGetRootDir() {
+    @Test
+    public void testGetRootDir() {
         assertEquals(settingsDir, settings.rootDir);
     }
 
-    @Test public void testHasUsefulToString() {
+    @Test
+    public void testHasUsefulToString() {
         assertEquals('settings \'root\'', settings.toString())
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
index 43dd49f..05f9287 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
@@ -34,9 +34,7 @@ class DependencyResolutionLoggerTest extends Specification {
 
         then:
         1 * progressLoggerFactory.newOperation(DependencyResolutionLogger) >> progressLogger
-        1 * progressLogger.setDescription("Resolve ${dependencies}")
-        1 * progressLogger.setShortDescription("Resolving ${dependencies}")
-        1 * progressLogger.started()
+        1 * progressLogger.start("Resolve ${dependencies}", "Resolving ${dependencies}") >> progressLogger
         0 * progressLogger._
 
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
index 7a20c8f..a012945 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
@@ -19,44 +19,37 @@ package org.gradle.initialization
 import org.gradle.StartParameter
 import org.gradle.api.initialization.ProjectDescriptor
 import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.api.internal.initialization.ClassLoaderScope
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.project.IProjectFactory
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Before
+import org.gradle.util.TestUtil
 import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
+import spock.lang.Specification
 
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-class InstantiatingBuildLoaderTest {
+class InstantiatingBuildLoaderTest extends Specification {
 
     InstantiatingBuildLoader buildLoader
     IProjectFactory projectFactory
     File testDir
     File rootProjectDir
     File childProjectDir
-    IProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
+    ProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
     StartParameter startParameter = new StartParameter()
     ProjectDescriptor rootDescriptor
     ProjectInternal rootProject
     ProjectDescriptor childDescriptor
     ProjectInternal childProject
     GradleInternal build
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    def rootProjectClassLoaderScope = Mock(ClassLoaderScope)
+
+    @Rule
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
-    @Before public void setUp()  {
-        projectFactory = context.mock(IProjectFactory)
+    def setup() {
+        projectFactory = Mock(IProjectFactory)
         buildLoader = new InstantiatingBuildLoader(projectFactory)
         testDir = tmpDir.testDirectory
         (rootProjectDir = new File(testDir, 'root')).mkdirs()
@@ -66,75 +59,66 @@ class InstantiatingBuildLoaderTest {
         rootProject = project(rootDescriptor, null)
         childDescriptor = descriptor('child', rootDescriptor, childProjectDir)
         childProject = project(childDescriptor, rootProject)
-        build = context.mock(GradleInternal)
-        context.checking {
-            allowing(build).getStartParameter()
-            will(returnValue(startParameter))
-        }
+        build = Mock(GradleInternal)
+        build.getStartParameter() >> startParameter
     }
 
-    @Test public void createsBuildWithRootProject() {
+    def createsBuildWithRootProject() {
+        when:
         ProjectDescriptor rootDescriptor = descriptor('root', null, rootProjectDir)
         ProjectInternal rootProject = project(rootDescriptor, null)
 
-        context.checking {
-            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
-                    withParam(nullValue()),
-                    withParam(notNullValue()))
-            will(returnValue(rootProject))
-            one(build).setRootProject(rootProject)
-            allowing(build).getRootProject()
-            will(returnValue(rootProject))
-            one(build).setDefaultProject(rootProject)
-        }
+        projectFactory.createProject(rootDescriptor, null, !null, rootProjectClassLoaderScope) >> rootProject
+        1 * build.setRootProject(rootProject)
+        build.getRootProject() >> rootProject
+        1 * build.setDefaultProject(rootProject)
 
-        buildLoader.load(rootDescriptor, build)
+        then:
+        buildLoader.load(rootDescriptor, build, rootProjectClassLoaderScope)
     }
 
-    @Test public void createsBuildWithMultipleProjects() {
+    def createsBuildWithMultipleProjects() {
+        when:
         expectProjectsCreated()
+        buildLoader.load(rootDescriptor, build, rootProjectClassLoaderScope)
 
-        buildLoader.load(rootDescriptor, build)
+        then:
+        rootProject.childProjects['child'].is childProject
+    }
+
+    def createsBuildWithMultipleProjectsAndSetsDefaultFromPath() {
+        when:
+        expectProjectsCreatedNoDefaultProject()
+        startParameter.projectPath = ':child'
+        buildLoader.load(rootDescriptor, build, rootProjectClassLoaderScope)
 
-        assertThat(rootProject.childProjects['child'], sameInstance(childProject))
+        then:
+        1 * build.setDefaultProject(childProject)
     }
 
-    private def expectProjectsCreatedNoDefaultProject() {
-        context.checking {
-            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
-                    withParam(nullValue()),
-                    withParam(notNullValue()))
-            will(returnValue(rootProject))
-
-            one(projectFactory).createProject(withParam(equalTo(childDescriptor)),
-                    withParam(equalTo(rootProject)),
-                    withParam(notNullValue()))
-            will(returnValue(childProject))
-
-            one(build).setRootProject(rootProject)
-            allowing(build).getRootProject()
-            will(returnValue(rootProject))
-        }
+    def expectProjectsCreatedNoDefaultProject() {
+        1 * projectFactory.createProject(rootDescriptor, null, !null, rootProjectClassLoaderScope) >> rootProject
+        1 * projectFactory.createProject(childDescriptor, rootProject, !null, _ as ClassLoaderScope) >> childProject
+        1 * build.setRootProject(rootProject)
+        build.getRootProject() >> rootProject
     }
 
-    private def expectProjectsCreated() {
+    def expectProjectsCreated() {
         expectProjectsCreatedNoDefaultProject()
 
-        context.checking {
-            one(build).setDefaultProject(rootProject)
-        }
+        1 * build.setDefaultProject(rootProject)
     }
 
-    private ProjectDescriptor descriptor(String name, ProjectDescriptor parent, File projectDir) {
-        new DefaultProjectDescriptor(parent, name, projectDir, projectDescriptorRegistry)
+    ProjectDescriptor descriptor(String name, ProjectDescriptor parent, File projectDir) {
+        new DefaultProjectDescriptor(parent, name, projectDir, projectDescriptorRegistry, TestFiles.resolver(rootProjectDir))
     }
 
-    private ProjectInternal project(ProjectDescriptor descriptor, ProjectInternal parent) {
+    ProjectInternal project(ProjectDescriptor descriptor, ProjectInternal parent) {
         DefaultProject project
         if (parent) {
-            project = HelperUtil.createChildProject(parent, descriptor.name, descriptor.projectDir)
+            project = TestUtil.createChildProject(parent, descriptor.name, descriptor.projectDir)
         } else {
-            project = HelperUtil.createRootProject(descriptor.projectDir)
+            project = TestUtil.createRootProject(descriptor.projectDir)
         }
         project
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy
index 8d307e3..3841fb1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy
@@ -16,14 +16,19 @@
 
 package org.gradle.initialization
 
+import org.gradle.StartParameter
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 2/19/13
- */
+import static org.gradle.util.GFileUtils.canonicalise
+
 class LayoutCommandLineConverterTest extends Specification {
 
-    def converter = new LayoutCommandLineConverter();
+    def converter = new LayoutCommandLineConverter(TestFiles.fileLookup())
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
 
     def convert(String... args) {
         converter.convert(Arrays.asList(args))
@@ -31,9 +36,9 @@ class LayoutCommandLineConverterTest extends Specification {
 
     def "has reasonable defaults"() {
         expect:
-        convert().projectDir
-        !convert().gradleUserHomeDir
-        convert().searchUpwards == null
+        convert().projectDir == canonicalise(SystemProperties.getCurrentDir())
+        convert().gradleUserHomeDir == canonicalise(StartParameter.DEFAULT_GRADLE_USER_HOME)
+        convert().searchUpwards
     }
 
     def "converts"() {
@@ -42,4 +47,31 @@ class LayoutCommandLineConverterTest extends Specification {
         convert("-g", "bar").gradleUserHomeDir.name == "bar"
         !convert("-u").searchUpwards
     }
+
+    def "converts relatively to the target dir"() {
+        given:
+        def root = temp.createDir('root')
+        def target = new BuildLayoutParameters().setProjectDir(root)
+
+        when:
+        converter.convert(['-p', 'projectDir', '-g', 'gradleDir'], target)
+
+        then:
+        target.gradleUserHomeDir == temp.file("root/gradleDir")
+        target.projectDir == temp.file("root/projectDir")
+    }
+
+    def "converts absolute paths"() {
+        given:
+        def root = temp.createDir('root')
+        def other = temp.createDir('other')
+        def target = new BuildLayoutParameters().setProjectDir(root)
+
+        when:
+        converter.convert(['-p', other.file('projectDir').absolutePath, '-g', other.file('gradleDir').absolutePath], target)
+
+        then:
+        target.gradleUserHomeDir == temp.file("other/gradleDir")
+        target.projectDir == temp.file("other/projectDir")
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
index d2aae08..d6f2b1e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
@@ -16,7 +16,7 @@
 package org.gradle.initialization;
 
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectRegistry;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GFileUtils;
@@ -101,8 +101,8 @@ public class ProjectDirectoryProjectSpecTest {
         }
     }
 
-    private IProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
-        final IProjectRegistry<ProjectIdentifier> registry = context.mock(IProjectRegistry.class, String.valueOf(counter++));
+    private ProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
+        final ProjectRegistry<ProjectIdentifier> registry = context.mock(ProjectRegistry.class, String.valueOf(counter++));
         context.checking(new Expectations(){{
             allowing(registry).getAllProjects();
             will(returnValue(toSet(projects)));
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPathProjectSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPathProjectSpecTest.java
new file mode 100644
index 0000000..784b9e7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPathProjectSpecTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.InvalidUserDataException;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.internal.project.ProjectRegistry;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+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.WrapUtil.toSet;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class ProjectPathProjectSpecTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    private final String path = ":subproject";
+    private final ProjectPathProjectSpec spec = new ProjectPathProjectSpec(path);
+    private int counter;
+
+    @Test
+    public void containsMatchWhenAtLeastOneProjectHasSpecifiedProjectDir() {
+        assertFalse(spec.containsProject(registry()));
+        assertFalse(spec.containsProject(registry(project(":other"))));
+
+        assertTrue(spec.containsProject(registry(project(path))));
+        assertTrue(spec.containsProject(registry(project(path), project(":"), project(":other"))));
+        assertTrue(spec.containsProject(registry(project(path), project(path))));
+    }
+
+    @Test
+    public void selectsSingleProjectWhichHasSpecifiedProjectDir() {
+        ProjectIdentifier project = project(path);
+        assertThat(spec.selectProject(registry(project, project(":other"))), sameInstance(project));
+    }
+
+    @Test
+    public void selectProjectFailsWhenNoProjectHasSpecifiedProjectDir() {
+        try {
+            spec.selectProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("No projects in this build have path '" + path + "'."));
+        }
+    }
+
+    @Test
+    public void selectProjectFailsWhenMultipleProjectsHaveSpecifiedProjectDir() {
+        // this is really artificial
+        ProjectIdentifier project1 = project(path);
+        ProjectIdentifier project2 = project(path);
+        try {
+            spec.selectProject(registry(project1, project2));
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), startsWith("Multiple projects in this build have path '" + path + "':"));
+        }
+    }
+
+    @Test
+    public void cannotSelectProjectWhenBuildFileIsNotAFile() {
+
+        try {
+            new ProjectPathProjectSpec(null).containsProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Project path must not be empty."));
+        }
+
+        try {
+            new ProjectPathProjectSpec("").containsProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Project path must not be empty."));
+        }
+    }
+
+    private ProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
+        final ProjectRegistry<ProjectIdentifier> registry = context.mock(ProjectRegistry.class, String.valueOf(counter++));
+        context.checking(new Expectations(){{
+            allowing(registry).getAllProjects();
+            will(returnValue(toSet(projects)));
+        }});
+        return registry;
+    }
+
+    private ProjectIdentifier project(final String path) {
+        final ProjectIdentifier projectIdentifier = context.mock(ProjectIdentifier.class, String.valueOf(counter++));
+        context.checking(new Expectations(){{
+            allowing(projectIdentifier).getPath();
+            will(returnValue(path));
+        }});
+        return projectIdentifier;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
index 76a3594..5f169bd 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 package org.gradle.initialization
-
 import org.gradle.api.Project
 import org.gradle.api.initialization.ProjectDescriptor
 import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.plugins.ExtensionContainerInternal
 import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.plugins.ExtensionContainer
 import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
@@ -37,10 +37,11 @@ class ProjectPropertySettingBuildLoaderTest extends Specification {
     final File rootProjectDir = tmpDir.createDir('root')
     final File childProjectDir = tmpDir.createDir('child')
     final ProjectPropertySettingBuildLoader loader = new ProjectPropertySettingBuildLoader(propertiesLoader, target)
-    final ExtensionContainer rootExtension = Mock()
+    final ExtensionContainerInternal rootExtension = Mock()
     final ExtraPropertiesExtension rootProperties = Mock()
-    final ExtensionContainer childExtension = Mock()
+    final ExtensionContainerInternal childExtension = Mock()
     final ExtraPropertiesExtension childProperties = Mock()
+    def classLoaderScope = Mock(ClassLoaderScope)
 
     def setup() {
         _ * gradle.rootProject >> rootProject
@@ -59,10 +60,10 @@ class ProjectPropertySettingBuildLoaderTest extends Specification {
         _ * propertiesLoader.mergeProperties(!null) >> [:]
         
         when:
-        loader.load(projectDescriptor, gradle)
+        loader.load(projectDescriptor, gradle, classLoaderScope)
 
         then:
-        1 * target.load(projectDescriptor, gradle)
+        1 * target.load(projectDescriptor, gradle, classLoaderScope)
         0 * target._
     }
 
@@ -71,7 +72,7 @@ class ProjectPropertySettingBuildLoaderTest extends Specification {
         2 * propertiesLoader.mergeProperties([:]) >> [prop: 'value']
 
         when:
-        loader.load(projectDescriptor, gradle)
+        loader.load(projectDescriptor, gradle, classLoaderScope)
 
         then:
         1 * rootProperties.set('prop', 'value')
@@ -84,7 +85,7 @@ class ProjectPropertySettingBuildLoaderTest extends Specification {
         GUtil.saveProperties(new Properties([prop: 'childValue']), new File(childProjectDir, Project.GRADLE_PROPERTIES))
 
         when:
-        loader.load(projectDescriptor, gradle)
+        loader.load(projectDescriptor, gradle, classLoaderScope)
 
         then:
         1 * propertiesLoader.mergeProperties([prop: 'rootValue']) >> [prop: 'rootValue']
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectSpecsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectSpecsTest.groovy
new file mode 100644
index 0000000..28bb87e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectSpecsTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization
+
+import org.gradle.StartParameter
+import spock.lang.Specification
+
+class ProjectSpecsTest extends Specification {
+    static File buildFile
+    static File projectDir
+    static File currentDir
+
+    def setupSpec() {
+        projectDir = Mock(File)
+        _ * projectDir.getCanonicalFile() >> projectDir
+        currentDir = Mock(File)
+        _ * currentDir.getCanonicalFile() >> currentDir
+        buildFile = Mock(File)
+        _ * buildFile.getCanonicalFile() >> buildFile
+        _ * buildFile.getParent() >> currentDir
+
+    }
+
+    def "path based spec"() {
+        given:
+        StartParameter parameter = new StartParameter()
+        parameter.setProjectPath(':a')
+        parameter.setBuildFile(buildFile)
+        parameter.setProjectDir(projectDir)
+        parameter.setCurrentDir(currentDir)
+
+        expect:
+        ProjectSpecs.forStartParameter(parameter).class == ProjectPathProjectSpec
+    }
+
+    def "build file based spec"() {
+        given:
+        StartParameter parameter = new StartParameter()
+        parameter.setBuildFile(buildFile)
+        parameter.setProjectDir(projectDir)
+        parameter.setCurrentDir(currentDir)
+
+        expect:
+        ProjectSpecs.forStartParameter(parameter).class == BuildFileProjectSpec
+    }
+
+    def "project dir based spec"() {
+        given:
+        StartParameter parameter = new StartParameter()
+        parameter.setProjectDir(projectDir)
+        parameter.setCurrentDir(currentDir)
+
+        expect:
+        ProjectSpecs.forStartParameter(parameter).class == ProjectDirectoryProjectSpec
+    }
+
+    def "current dir based spec"() {
+        given:
+        StartParameter parameter = new StartParameter()
+        parameter.setCurrentDir(currentDir)
+
+        expect:
+        ProjectSpecs.forStartParameter(parameter).class == DefaultProjectSpec
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
index 8b6440b..177da68 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
@@ -18,6 +18,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.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.DefaultClassLoaderCache;
+import org.gradle.api.internal.initialization.RootClassLoaderScope;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -48,12 +51,14 @@ public class PropertiesLoadingSettingsProcessorTest {
 
         PropertiesLoadingSettingsProcessor processor = new PropertiesLoadingSettingsProcessor(delegate, propertiesLoader);
 
+        final ClassLoaderScope classLoaderScope = new RootClassLoaderScope(urlClassLoader, new DefaultClassLoaderCache());
+
         context.checking(new Expectations() {{
             one(propertiesLoader).loadProperties(settingsDir);
-            one(delegate).process(gradle, settingsLocation, urlClassLoader, startParameter);
+            one(delegate).process(gradle, settingsLocation, classLoaderScope, startParameter);
             will(returnValue(settings));
         }});
 
-        assertThat(processor.process(gradle, settingsLocation, urlClassLoader, startParameter), sameInstance(settings));
+        assertThat(processor.process(gradle, settingsLocation, classLoaderScope, startParameter), sameInstance(settings));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
deleted file mode 100644
index 41c5c60..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.initialization
-
-import groovy.mock.interceptor.MockFor
-import org.gradle.StartParameter
-import org.gradle.api.internal.GradleInternal
-import org.gradle.configuration.ScriptPlugin
-import org.gradle.configuration.ScriptPluginFactory
-import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.util.JUnit4GroovyMockery
-import org.junit.Before
-import org.junit.Test
-import static org.junit.Assert.assertSame
-
-/**
- * @author Hans Dockter
- */
-class ScriptEvaluatingSettingsProcessorTest {
-    static final File TEST_ROOT_DIR = new File('rootDir')
-    static final File TEST_CURRENT_DIR = new File('currentDir')
-    ScriptEvaluatingSettingsProcessor settingsProcessor
-    DefaultSettingsFinder expectedSettingsFinder
-    SettingsFactory settingsFactory
-    StartParameter expectedStartParameter
-    DefaultSettings expectedSettings
-    MockFor settingsFactoryMocker
-    ScriptSource scriptSourceMock
-    IGradlePropertiesLoader propertiesLoaderMock
-    ScriptPluginFactory configurerFactoryMock
-    Map expectedGradleProperties
-    URLClassLoader urlClassLoader
-    GradleInternal gradleMock
-    SettingsLocation settingsLocation
-
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
-    @Before public void setUp() {
-        settingsLocation = context.mock(SettingsLocation)
-        configurerFactoryMock = context.mock(ScriptPluginFactory)
-        settingsFactory = context.mock(SettingsFactory)
-        propertiesLoaderMock = context.mock(IGradlePropertiesLoader)
-        settingsProcessor = new ScriptEvaluatingSettingsProcessor(configurerFactoryMock, settingsFactory, propertiesLoaderMock)
-        expectedSettingsFinder = new DefaultSettingsFinder()
-        scriptSourceMock = context.mock(ScriptSource)
-        gradleMock = context.mock(GradleInternal)
-        expectedStartParameter = new StartParameter()
-        expectedGradleProperties = [a: 'b']
-        urlClassLoader = new URLClassLoader(new URL[0]);
-        initExpectedSettings()
-    }
-
-    private void initExpectedSettings() {
-        expectedSettings = new DefaultSettings()
-        DefaultProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
-        expectedSettings.setRootProjectDescriptor(new DefaultProjectDescriptor(null, TEST_ROOT_DIR.name,
-                TEST_ROOT_DIR, projectDescriptorRegistry))
-        expectedSettings.setProjectDescriptorRegistry(projectDescriptorRegistry)
-        expectedSettings.setStartParameter(expectedStartParameter)
-        context.checking {
-            one(settingsFactory).createSettings(gradleMock, TEST_ROOT_DIR, scriptSourceMock, expectedGradleProperties, expectedStartParameter, urlClassLoader)
-            will(returnValue(expectedSettings))
-            
-            one(settingsLocation).getSettingsDir()
-            will(returnValue(TEST_ROOT_DIR))
-            allowing(settingsLocation).getSettingsScriptSource()
-            will(returnValue(scriptSourceMock))
-        }
-    }
-
-    @Test public void testProcessWithSettingsFile() {
-        expectedStartParameter.setCurrentDir(TEST_ROOT_DIR)
-        ScriptPlugin configurerMock = context.mock(ScriptPlugin)
-
-        context.checking {
-            one(configurerFactoryMock).create(scriptSourceMock)
-            will(returnValue(configurerMock))
-            one(propertiesLoaderMock).mergeProperties([:])
-            will(returnValue(expectedGradleProperties))
-            one(configurerMock).setClassLoader(urlClassLoader)
-            one(configurerMock).setScriptBaseClass(SettingsScript)
-            one(configurerMock).apply(expectedSettings)
-        }
-
-        assertSame(expectedSettings, settingsProcessor.process(gradleMock, settingsLocation, urlClassLoader, expectedStartParameter))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy
new file mode 100644
index 0000000..ee7a08c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization
+
+import org.gradle.StartParameter
+import org.gradle.api.initialization.Settings
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.initialization.DefaultClassLoaderCache
+import org.gradle.api.internal.initialization.RootClassLoaderScope
+import org.gradle.api.internal.initialization.ScriptHandlerFactory
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.internal.service.scopes.ServiceRegistryFactory
+import org.gradle.util.WrapUtil
+import spock.lang.Specification
+
+class SettingsFactoryTest extends Specification {
+
+    void createSettings() {
+        given:
+        def settingsDir = new File("settingsDir")
+        def scriptSource = Mock(ScriptSource)
+        def expectedGradleProperties = WrapUtil.toMap("key", "myvalue")
+        def startParameter = new StartParameter()
+        def serviceRegistryFactory = Mock(ServiceRegistryFactory)
+        def settingsServices = Mock(ServiceRegistry)
+        def pluginContainer = Mock(PluginContainer)
+        def fileResolver = Mock(FileResolver)
+        def scriptPluginFactory = Mock(ScriptPluginFactory)
+        def scriptHandlerFactory = Mock(ScriptHandlerFactory)
+        def projectDescriptorRegistry = Mock(ProjectDescriptorRegistry)
+
+        1 * serviceRegistryFactory.createFor(_ as Settings) >> settingsServices
+        1 * settingsServices.get(PluginContainer) >> pluginContainer
+        1 * settingsServices.get(FileResolver) >> fileResolver
+        1 * settingsServices.get(ScriptPluginFactory) >> scriptPluginFactory
+        1 * settingsServices.get(ScriptHandlerFactory) >> scriptHandlerFactory
+        1 * settingsServices.get(ProjectDescriptorRegistry) >> projectDescriptorRegistry
+        1 * projectDescriptorRegistry.addProject(_ as DefaultProjectDescriptor)
+
+        when:
+        SettingsFactory settingsFactory = new SettingsFactory(ThreadGlobalInstantiator.getOrCreate(), serviceRegistryFactory);
+        GradleInternal gradle = Mock(GradleInternal)
+
+        DefaultSettings settings = (DefaultSettings) settingsFactory.createSettings(gradle,
+                settingsDir, scriptSource, expectedGradleProperties, startParameter, new RootClassLoaderScope(getClass().classLoader, new DefaultClassLoaderCache()));
+
+        then:
+        gradle.is(settings.gradle)
+        projectDescriptorRegistry.is(settings.projectDescriptorRegistry)
+        expectedGradleProperties.each {
+            settings.properties[it.key] == it.value
+        }
+
+        settingsDir.is settings.getSettingsDir()
+        scriptSource.is settings.getSettingsScript()
+        startParameter.is settings.getStartParameter()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
deleted file mode 100644
index f9a12fe..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.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;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class SettingsFactoryTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    @Test
-    public void createSettings() {
-        final File expectedSettingsDir = new File("settingsDir");
-        ScriptSource expectedScriptSource = context.mock(ScriptSource.class);
-        Map<String, String> expectedGradleProperties = WrapUtil.toMap("key", "myvalue");
-        IProjectDescriptorRegistry expectedProjectDescriptorRegistry = new DefaultProjectDescriptorRegistry();
-        StartParameter expectedStartParameter = new StartParameter();
-        SettingsFactory settingsFactory = new SettingsFactory(expectedProjectDescriptorRegistry, ThreadGlobalInstantiator.getOrCreate());
-        final URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
-        GradleInternal gradle = context.mock(GradleInternal.class);
-
-        DefaultSettings settings = (DefaultSettings) settingsFactory.createSettings(gradle,
-                expectedSettingsDir, expectedScriptSource, expectedGradleProperties, expectedStartParameter, urlClassLoader);
-
-        assertSame(gradle, settings.getGradle());
-        assertSame(expectedProjectDescriptorRegistry, settings.getProjectDescriptorRegistry());
-        for (Map.Entry<String, String> entry : expectedGradleProperties.entrySet()) {
-            assertEquals(entry.getValue(), ((DynamicObjectAware)settings).getAsDynamicObject().getProperty(entry.getKey()));
-        }
-
-        assertSame(expectedSettingsDir, settings.getSettingsDir());
-        assertSame(expectedScriptSource, settings.getSettingsScript());
-        assertSame(expectedStartParameter, settings.getStartParameter());
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.groovy
new file mode 100644
index 0000000..6bc529f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.SettingsInternal
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.project.ProjectRegistry
+import org.gradle.initialization.buildsrc.BuildSourceBuilder
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.util.GFileUtils
+import org.gradle.util.WrapUtil
+import spock.lang.Specification
+
+class SettingsHandlerTest extends Specification {
+
+    def gradle = Mock(GradleInternal)
+    def settings = Mock(SettingsInternal)
+    def settingsLocation = new SettingsLocation(GFileUtils.canonicalise(new File("someDir")), null);
+    def startParameter = new StartParameter();
+    def classLoaderScope = Mock(ClassLoaderScope)
+    def settingsClassLoaderScope = Mock(ClassLoaderScope)
+    def settingsFinder = Mock(ISettingsFinder)
+    def settingsProcessor = Mock(SettingsProcessor)
+    def buildSourceBuilder = Mock(BuildSourceBuilder)
+    def settingsHandler = new SettingsHandler(settingsFinder, settingsProcessor, buildSourceBuilder);
+
+    public void findAndLoadSettingsWithExistingSettings() {
+        when:
+        def projectRegistry = Mock(ProjectRegistry)
+        def projectDescriptor = Mock(DefaultProjectDescriptor)
+        def services = Mock(ServiceRegistry)
+        startParameter.setCurrentDir(settingsLocation.getSettingsDir())
+
+        settings.getProjectRegistry() >> projectRegistry
+        projectRegistry.getAllProjects() >> WrapUtil.toSet(projectDescriptor)
+        projectDescriptor.getProjectDir() >> settingsLocation.settingsDir
+        projectDescriptor.getBuildFile() >> new File(settingsLocation.getSettingsDir(), "build.gradle")
+        gradle.getStartParameter() >> startParameter
+        gradle.getServices() >> services
+        settingsFinder.find(startParameter) >> settingsLocation
+        1 * buildSourceBuilder.buildAndCreateClassLoader({ StartParameter sp -> sp.currentDir == new File(settingsLocation.getSettingsDir(), BaseSettings.DEFAULT_BUILD_SRC_DIR) }) >> classLoaderScope
+        1 * classLoaderScope.createRebasedChild() >> settingsClassLoaderScope
+        1 * settingsProcessor.process(gradle, settingsLocation, settingsClassLoaderScope, startParameter) >> settings
+
+        then:
+        settingsHandler.findAndLoadSettings(gradle).is(settings)
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
deleted file mode 100644
index c50b8dc..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
+++ /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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
-import org.gradle.api.internal.project.IProjectRegistry;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.MultiParentClassLoader;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Description;
-import org.hamcrest.Factory;
-import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeMatcher;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public class SettingsHandlerTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    private GradleInternal gradle = context.mock(GradleInternal.class);
-    private SettingsInternal settings = context.mock(SettingsInternal.class);
-    private SettingsLocation settingsLocation = new SettingsLocation(GFileUtils.canonicalise(new File("someDir")), null);
-    private StartParameter startParameter = new StartParameter();
-    private URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
-    private ISettingsFinder settingsFinder = context.mock(ISettingsFinder.class);
-    private SettingsProcessor settingsProcessor = context.mock(SettingsProcessor.class);
-    private BuildSourceBuilder buildSourceBuilder = context.mock(BuildSourceBuilder.class);
-    private MultiParentClassLoader scriptClassLoader = context.mock(MultiParentClassLoader.class);
-    private SettingsHandler settingsHandler = new SettingsHandler(settingsFinder, settingsProcessor,
-            buildSourceBuilder);
-
-    @org.junit.Test
-    public void findAndLoadSettingsWithExistingSettings() {
-        prepareForExistingSettings();
-        context.checking(new Expectations() {{
-            allowing(buildSourceBuilder).buildAndCreateClassLoader(with(aBuildSrcStartParameter(new File(
-                    settingsLocation.getSettingsDir(), BaseSettings.DEFAULT_BUILD_SRC_DIR))));
-            will(returnValue(urlClassLoader));
-        }});
-        assertThat(settingsHandler.findAndLoadSettings(gradle), sameInstance(settings));
-    }
-
-    private void prepareForExistingSettings() {
-        final IProjectRegistry projectRegistry = context.mock(IProjectRegistry.class);
-        final DefaultProjectDescriptor projectDescriptor = context.mock(DefaultProjectDescriptor.class);
-        startParameter.setCurrentDir(settingsLocation.getSettingsDir());
-
-        context.checking(new Expectations() {{
-            allowing(settings).getProjectRegistry();
-            will(returnValue(projectRegistry));
-
-            allowing(projectRegistry).getAllProjects();
-            will(returnValue(WrapUtil.toSet(projectDescriptor)));
-
-            allowing(projectDescriptor).getProjectDir();
-            will(returnValue(settingsLocation.getSettingsDir()));
-
-            allowing(projectDescriptor).getBuildFile();
-            will(returnValue(new File(settingsLocation.getSettingsDir(), "build.gradle")));
-
-            allowing(settings).getClassLoader();
-            will(returnValue(urlClassLoader));
-
-            allowing(gradle).getScriptClassLoader();
-            will(returnValue(scriptClassLoader));
-
-            allowing(gradle).getStartParameter();
-            will(returnValue(startParameter));
-
-            allowing(settingsFinder).find(startParameter);
-            will(returnValue(settingsLocation));
-
-            one(settingsProcessor).process(gradle, settingsLocation, urlClassLoader, startParameter);
-            will(returnValue(settings));
-
-            one(scriptClassLoader).addParent(urlClassLoader);
-        }});
-    }
-
-    @Factory
-    public static Matcher<StartParameter> aBuildSrcStartParameter(File currentDir) {
-        return new BuildSrcParameterMatcher(currentDir);
-    }
-
-    public static class BuildSrcParameterMatcher extends TypeSafeMatcher<StartParameter> {
-        private File currentDir;
-
-        public BuildSrcParameterMatcher(File currentDir) {
-            this.currentDir = currentDir;
-        }
-
-        public boolean matchesSafely(StartParameter startParameter) {
-            try {
-                return startParameter.getCurrentDir().getCanonicalFile().equals(currentDir.getCanonicalFile());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void describeTo(Description description) {
-            description.appendText("a startparameter with ").appendValue(currentDir);
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSourceBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSourceBuilderTest.groovy
new file mode 100644
index 0000000..753ac9d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSourceBuilderTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization.buildsrc
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.PersistentCache
+import org.gradle.initialization.GradleLauncherFactory
+import org.gradle.internal.classpath.ClassPath
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class BuildSourceBuilderTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    GradleLauncherFactory launcherFactory = Mock()
+    ClassLoaderScope classLoaderScope = Mock()
+    CacheRepository cacheRepository = Mock()
+    BuildSourceBuilder buildSourceBuilder = Spy(BuildSourceBuilder, constructorArgs: [launcherFactory, classLoaderScope,  cacheRepository])
+
+    StartParameter parameter = new StartParameter()
+
+    void "creates classpath when build src does not exist"() {
+        when:
+        parameter.setCurrentDir(new File('nonexisting'));
+        then:
+        buildSourceBuilder.createBuildSourceClasspath(parameter).asFiles == []
+    }
+
+    void "creates classpath when build src exists"() {
+        def cache = Mock(PersistentCache)
+        def classpath = Mock(ClassPath)
+        buildSourceBuilder.createCache(parameter) >> cache
+        cache.useCache(_ as String, _ as BuildSrcUpdateFactory) >> classpath
+
+        when:
+        parameter.setCurrentDir(tmpDir.createDir("someDir"));
+
+        then:
+        buildSourceBuilder.createBuildSourceClasspath(parameter) == classpath
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSrcBuildListenerFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSrcBuildListenerFactoryTest.groovy
new file mode 100644
index 0000000..8e0c913
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSrcBuildListenerFactoryTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.buildsrc
+
+import spock.lang.Specification
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.Convention
+import org.gradle.api.internal.plugins.EmbeddableJavaProject
+import org.gradle.StartParameter
+
+class BuildSrcBuildListenerFactoryTest extends Specification {
+
+    def startParameter = Mock(StartParameter)
+    def plugin = Stub(EmbeddableJavaProject)
+    def convention = Mock(Convention) {
+        getPlugin(EmbeddableJavaProject) >> plugin
+    }
+    def project = Mock(ProjectInternal) {
+        getConvention() >> convention
+    }
+    def gradle = Mock(GradleInternal) {
+        getStartParameter() >> startParameter
+        getRootProject() >> project
+    }
+
+    def "configures task names when rebuild on"() {
+        def listener = new BuildSrcBuildListenerFactory().create(true)
+        plugin.getRebuildTasks() >> ['fooBuild']
+
+        when:
+        listener.onConfigure(gradle)
+
+        then:
+        1 * startParameter.setTaskNames(['fooBuild'])
+    }
+
+    def "configures task names when rebuild off"() {
+        def listener = new BuildSrcBuildListenerFactory().create(false)
+        plugin.getBuildTasks() >> ['barBuild']
+
+        when:
+        listener.onConfigure(gradle)
+
+        then:
+        1 * startParameter.setTaskNames(['barBuild'])
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactoryTest.groovy
new file mode 100644
index 0000000..ce52264
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactoryTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.buildsrc
+
+import org.gradle.GradleLauncher
+import org.gradle.cache.PersistentCache
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class BuildSrcUpdateFactoryTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def cache = Stub(PersistentCache)
+    def launcher = Stub(GradleLauncher)
+    def listener = Stub(BuildSrcBuildListenerFactory.Listener)
+    def listenerFactory = Mock(BuildSrcBuildListenerFactory)
+    def factory = new BuildSrcUpdateFactory(cache, launcher, listenerFactory)
+
+    def "creates classpath"() {
+        cache.getBaseDir() >> temp.testDirectory
+        listener.getRuntimeClasspath() >> [new File("dummy")]
+
+        when:
+        def classpath = factory.create()
+
+        then:
+        classpath.asFiles == [new File("dummy")]
+        1 * listenerFactory.create(_) >> listener
+    }
+
+    def "uses listener with rebuild off when marker file present"() {
+        temp.createFile("built.bin")
+        cache.getBaseDir() >> temp.testDirectory
+
+        when:
+        factory.create()
+
+        then:
+        1 * listenerFactory.create(false) >> listener
+    }
+
+    def "uses listener with rebuild on when marker file not present"() {
+        cache.getBaseDir() >> temp.createDir("empty")
+
+        when:
+        factory.create()
+
+        then:
+        1 * listenerFactory.create(true) >> listener
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/exceptions/AbstractMultiCauseExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/exceptions/AbstractMultiCauseExceptionTest.groovy
new file mode 100644
index 0000000..e1f7524
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/exceptions/AbstractMultiCauseExceptionTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.exceptions
+
+import org.gradle.util.GUtil
+import spock.lang.Specification
+
+
+class AbstractMultiCauseExceptionTest extends Specification {
+    def getCauseReturnsTheFirstCause() {
+        def cause1 = new RuntimeException()
+        def cause2 = new RuntimeException()
+        def failure = new TestMultiCauseException('message', [cause1, cause2])
+
+        expect:
+        failure.cause == cause1
+        failure.causes == [cause1, cause2]
+    }
+
+    def getCauseReturnsNullWhenThereAreNoCauses() {
+        def failure = new TestMultiCauseException('message', [])
+
+        expect:
+        failure.cause == null
+        failure.causes == []
+    }
+
+    def canUseInitCauseToProvideCause() {
+        def cause1 = new RuntimeException()
+        def failure = new TestMultiCauseException('message', [])
+        failure.initCause(cause1)
+
+        expect:
+        failure.cause == cause1
+        failure.causes == [cause1]
+    }
+
+    def canUseInitCausesToProvideMultipleCause() {
+        def cause1 = new RuntimeException()
+        def cause2 = new RuntimeException()
+        def failure = new TestMultiCauseException('message', [])
+        failure.initCauses([cause1, cause2])
+
+        expect:
+        failure.cause == cause1
+        failure.causes == [cause1, cause2]
+    }
+
+    def printStackTraceWithMultipleCauses() {
+        RuntimeException cause1 = new RuntimeException('cause1')
+        RuntimeException cause2 = new RuntimeException('cause2')
+        def failure = new TestMultiCauseException('message', [cause1, cause2])
+        def outstr = new StringWriter()
+
+        when:
+        outstr.withPrintWriter { writer ->
+            failure.printStackTrace(writer)
+        }
+
+        then:
+        outstr.toString().contains("${TestMultiCauseException.name}: message")
+        outstr.toString().contains("Cause 1: ${RuntimeException.name}: cause1")
+        outstr.toString().contains("Cause 2: ${RuntimeException.name}: cause2")
+    }
+
+    def printStackTraceWithSingleCause() {
+        RuntimeException cause1 = new RuntimeException('cause1')
+        def failure = new TestMultiCauseException('message', [cause1])
+        def outstr = new StringWriter()
+
+        when:
+        outstr.withPrintWriter { writer ->
+            failure.printStackTrace(writer)
+        }
+
+        then:
+        outstr.toString().contains("${TestMultiCauseException.name}: message")
+        outstr.toString().contains("Caused by: ${RuntimeException.name}: cause1")
+    }
+
+    def canSerializeAndDeserializeException() {
+        def cause1 = new RuntimeException("cause1")
+        def cause2 = new RuntimeException("cause2")
+        def failure = new TestMultiCauseException("message", [cause1, cause2])
+
+        when:
+        def bytes = GUtil.serialize(failure)
+        def result = new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject()
+
+        then:
+        result instanceof TestMultiCauseException
+        result.message == "message"
+        result.causes.size() == 2
+        result.causes*.message == ["cause1", "cause2"]
+
+        when:
+        def outstr = new StringWriter()
+        outstr.withPrintWriter { result.printStackTrace(it) }
+
+        then:
+        outstr.toString().contains("${TestMultiCauseException.name}: message")
+        outstr.toString().contains("Cause 1: ${RuntimeException.name}: cause1")
+        outstr.toString().contains("Cause 2: ${RuntimeException.name}: cause2")
+    }
+}
+
+class TestMultiCauseException extends AbstractMultiCauseException {
+    TestMultiCauseException(String message, Iterable<? extends Throwable> causes) {
+        super(message, causes)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/exceptions/LocationAwareExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/exceptions/LocationAwareExceptionTest.groovy
new file mode 100644
index 0000000..a8595fb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/exceptions/LocationAwareExceptionTest.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.internal.exceptions
+
+import org.gradle.util.TreeVisitor
+import spock.lang.Specification
+
+class LocationAwareExceptionTest extends Specification {
+    def "visit reportable causes does not visit direct cause"() {
+        TreeVisitor visitor = Mock()
+        def cause = new RuntimeException()
+        def e = new LocationAwareException(cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+        0 * visitor._
+
+        and:
+        e.reportableCauses == []
+    }
+
+    def "visit reportable causes visits indirect cause"() {
+        TreeVisitor visitor = Mock()
+        def childCause = new RuntimeException()
+        def cause = new RuntimeException(childCause)
+        def e = new LocationAwareException(cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        1 * visitor.startChildren()
+
+        and:
+        1 * visitor.node(childCause)
+
+        and:
+        1 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause]
+    }
+
+    def "visit reportable causes visits causes of contextual exception"() {
+        TreeVisitor visitor = Mock()
+        def childCause = new RuntimeException()
+        def cause = new TestContextualException(childCause)
+        def e = new LocationAwareException(cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        1 * visitor.startChildren()
+
+        and:
+        1 * visitor.node(childCause)
+
+        and:
+        1 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause]
+    }
+
+    def "visit reportable causes visits all contextual exceptions and direct cause of last contextual exception"() {
+        TreeVisitor visitor = Mock()
+        def unreportedCause = new RuntimeException()
+        def reportedCause = new RuntimeException(unreportedCause)
+        def lastContextual = new TestContextualException(reportedCause)
+        def interveningUnreported = new RuntimeException(lastContextual)
+        def contextual = new TestContextualException(interveningUnreported)
+        def interveningUnreported2 = new RuntimeException(contextual)
+        def cause = new TestContextualException(interveningUnreported2)
+        def e = new LocationAwareException(cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+        1 * visitor.node(contextual)
+        1 * visitor.node(lastContextual)
+        1 * visitor.node(reportedCause)
+
+        and:
+        _ * visitor.startChildren()
+        _ * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [contextual, lastContextual, reportedCause]
+    }
+
+    def "visit reportable causes visits causes of multi-cause exception"() {
+        TreeVisitor visitor = Mock()
+        def childCause1 = new RuntimeException()
+        def childCause2 = new RuntimeException()
+        def cause = new AbstractMultiCauseException("broken", childCause1, childCause2)
+        def e = new LocationAwareException(cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        1 * visitor.startChildren()
+
+        and:
+        1 * visitor.node(childCause1)
+
+        and:
+        1 * visitor.node(childCause2)
+
+        and:
+        1 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause1, childCause2]
+    }
+
+    def "visit reportable causes visits causes recursively"() {
+        TreeVisitor visitor = Mock()
+        def ignored = new RuntimeException()
+        def childCause1 = new RuntimeException(ignored)
+        def childCause2 = new RuntimeException()
+        def childCause3 = new TestContextualException(childCause2)
+        def childCause4 = new TestContextualException(childCause3)
+        def cause = new AbstractMultiCauseException("broken", childCause1, childCause4)
+        def e = new LocationAwareException(cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        3 * visitor.startChildren()
+        1 * visitor.node(childCause1)
+        1 * visitor.node(childCause4)
+        1 * visitor.node(childCause3)
+        1 * visitor.node(childCause2)
+        3 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause1, childCause4, childCause3, childCause2]
+    }
+
+    @Contextual
+    class TestContextualException extends RuntimeException {
+        TestContextualException(Throwable throwable) {
+            super(throwable)
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureUsageTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureUsageTest.groovy
new file mode 100644
index 0000000..d642ee7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/DeprecatedFeatureUsageTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle
+
+import spock.lang.Specification
+
+class DeprecatedFeatureUsageTest extends Specification {
+    def "can create copy with stack-trace filled in"() {
+        def usage = new DeprecatedFeatureUsage("message", DeprecatedFeatureUsageTest)
+
+        expect:
+        usage.stack.empty
+        def copy = usage.withStackTrace()
+        copy.message == usage.message
+        !copy.stack.empty
+    }
+
+    def "returns self when stack-trace already filled in"() {
+        def usage = new DeprecatedFeatureUsage("message", DeprecatedFeatureUsageTest).withStackTrace()
+
+        expect:
+        usage.withStackTrace() == usage
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/LoggingDeprecatedFeatureHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/LoggingDeprecatedFeatureHandlerTest.groovy
new file mode 100644
index 0000000..353eaa6
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/LoggingDeprecatedFeatureHandlerTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle
+
+import org.gradle.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TextUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+class LoggingDeprecatedFeatureHandlerTest extends Specification {
+    final appender = new TestAppender()
+    @Rule final ConfigureLogging logging = new ConfigureLogging(appender)
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+    final locationReporter = Mock(UsageLocationReporter)
+    final handler = new LoggingDeprecatedFeatureHandler(locationReporter)
+
+    def "logs each deprecation warning once only"() {
+        when:
+        handler.deprecatedFeatureUsed(new DeprecatedFeatureUsage("feature1", LoggingDeprecatedFeatureHandlerTest))
+        handler.deprecatedFeatureUsed(new DeprecatedFeatureUsage("feature2", LoggingDeprecatedFeatureHandlerTest))
+        handler.deprecatedFeatureUsed(new DeprecatedFeatureUsage("feature2", LoggingDeprecatedFeatureHandlerTest))
+
+        then:
+        appender.toString() == '[WARN feature1][WARN feature2]'
+    }
+
+    def "location reporter can prepend text"() {
+        def usage = new DeprecatedFeatureUsage("feature", LoggingDeprecatedFeatureHandlerTest)
+
+        when:
+        handler.deprecatedFeatureUsed(usage)
+
+        then:
+        1 * locationReporter.reportLocation(_, _) >> { DeprecatedFeatureUsage param1, StringBuilder message ->
+            message.append("location")
+        }
+
+        and:
+        appender.toString() == TextUtil.toPlatformLineSeparators('[WARN location\nfeature]')
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporterTest.groovy
new file mode 100644
index 0000000..8613b91
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporterTest.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurelifecycle
+
+import org.gradle.groovy.scripts.ScriptSource
+import spock.lang.Specification
+import org.gradle.groovy.scripts.Script
+
+class ScriptUsageLocationReporterTest extends Specification {
+    final reporter = new ScriptUsageLocationReporter()
+
+    def "does nothing when no caller is a script"() {
+        def stack = [
+                new StackTraceElement("SomeClass", "method", "SomeClass.java", 12),
+                new StackTraceElement("SomeClass", "method", "SomeClass.java", 77)]
+        def usage = Stub(DeprecatedFeatureUsage) {
+            getStack() >> stack
+        }
+        def result = new StringBuilder()
+
+        when:
+        reporter.reportLocation(usage, result)
+
+        then:
+        result.length() == 0
+    }
+
+    def "reports location when immediate caller is a script"() {
+        def scriptSource = Stub(ScriptSource) {
+            getFileName() >> "some-file.gradle"
+            getDisplayName() >> "build script 'some-file.gradle'"
+        }
+        def script = Stub(Script) {
+            getScriptSource() >> scriptSource
+        }
+        def stack = [
+                new StackTraceElement("SomeClass", "method", "some-file.gradle", 12),
+                new StackTraceElement("SomeClass", "method", "some-file.gradle", 77)]
+        def usage = Stub(DeprecatedFeatureUsage) {
+            getStack() >> stack
+        }
+        def result = new StringBuilder()
+
+        given:
+        reporter.beforeScript(script)
+        reporter.afterScript(script, null)
+
+        when:
+        reporter.reportLocation(usage, result)
+
+        then:
+        result.toString() == /Build script 'some-file.gradle': line 12/
+    }
+
+    def "reports location when second caller is a script"() {
+        def scriptSource = Stub(ScriptSource) {
+            getFileName() >> "some-file.gradle"
+            getDisplayName() >> "build script 'some-file.gradle'"
+        }
+        def script = Stub(Script) {
+            getScriptSource() >> scriptSource
+        }
+        def stack = [
+                new StackTraceElement("SomeLibrary", "method", "SomeLibrary.java", 103),
+                new StackTraceElement("SomeLibrary", "method", "SomeLibrary.java", 67),
+                new StackTraceElement("SomeClass", "method", "some-file.gradle", 12)
+                ]
+        def usage = Stub(DeprecatedFeatureUsage) {
+            getStack() >> stack
+        }
+        def result = new StringBuilder()
+
+        given:
+        reporter.beforeScript(script)
+        reporter.afterScript(script, null)
+
+        when:
+        reporter.reportLocation(usage, result)
+
+        then:
+        result.toString() == /Build script 'some-file.gradle': line 12/
+    }
+
+    def "does not report location when subsequent caller is a script"() {
+        def scriptSource = Stub(ScriptSource) {
+            getFileName() >> "some-file.gradle"
+            getDisplayName() >> "build script 'some-file.gradle'"
+        }
+        def script = Stub(Script) {
+            getScriptSource() >> scriptSource
+        }
+        def stack = [
+                new StackTraceElement("SomeLibrary", "method", "SomeLibrary.java", 103),
+                new StackTraceElement("OtherLibrary", "method", "OtherLibrary.java", 67),
+                new StackTraceElement("SomeClass", "method", "some-file.gradle", 12)
+                ]
+        def usage = Stub(DeprecatedFeatureUsage) {
+            getStack() >> stack
+        }
+        def result = new StringBuilder()
+
+        given:
+        reporter.beforeScript(script)
+        reporter.afterScript(script, null)
+
+        when:
+        reporter.reportLocation(usage, result)
+
+        then:
+        result.length() == 0
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/graph/CachingDirectedGraphWalkerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/graph/CachingDirectedGraphWalkerTest.groovy
new file mode 100644
index 0000000..654e59c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/graph/CachingDirectedGraphWalkerTest.groovy
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graph
+
+import spock.lang.Specification
+
+class CachingDirectedGraphWalkerTest extends Specification {
+    private final DirectedGraphWithEdgeValues<Integer, String> graph = Mock()
+    private final CachingDirectedGraphWalker walker = new CachingDirectedGraphWalker(graph)
+
+    def collectsValuesForASingleNode() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1' }
+        0 * _._
+        values == ['1'] as Set
+    }
+
+    def collectsValuesForEachSuccessorNode() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2; args[2] << 3 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
+        3 * graph.getEdgeValues(_, _, _)
+        0 * _._
+        values == ['1', '2', '3'] as Set
+    }
+
+    def collectsValuesForEachEdgeTraversed() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 2; args[2] << 3 }
+        1 * graph.getEdgeValues(1, 2, _) >> { args -> args[2] << '1->2' }
+        1 * graph.getEdgeValues(1, 3, _) >> { args -> args[2] << '1->3' }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 3 }
+        1 * graph.getEdgeValues(2, 3, _) >> { args -> args[2] << '2->3' }
+        1 * graph.getNodeValues(3, _, _)
+        0 * _._
+        values == ['1->2', '1->3', '2->3'] as Set
+    }
+
+    def collectsValuesForAllStartNodes() {
+        when:
+        walker.add(1, 2)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 3 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
+        2 * graph.getEdgeValues(_, _, _)
+        0 * _._
+        values == ['1', '2', '3'] as Set
+    }
+
+    def collectsValuesWhenCycleIsPresentInGraph() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2 }
+        1 * graph.getEdgeValues(1, 2, _) >> { args -> args[2] << '1->2' }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getEdgeValues(2, 3, _) >> { args -> args[2] << '2->3' }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 1 }
+        1 * graph.getEdgeValues(3, 1, _) >> { args -> args[2] << '3->1' }
+        0 * _._
+        values == ['1', '1->2', '2', '2->3', '3', '3->1'] as Set
+    }
+
+    def collectsValuesWhenNodeConnectedToItself() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 1 }
+        1 * graph.getEdgeValues(1, 1, _) >> { args -> args[2] << '1->1' }
+        0 * _._
+        values == ['1', '1->1'] as Set
+    }
+
+    def collectsValuesWhenMultipleCyclesInGraph() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 1; args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3; args[2] << 4 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 2 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4' }
+        5 * graph.getEdgeValues(_, _, _) >> { args -> args[2] << "${args[0]}->${args[1]}".toString() }
+        0 * _._
+        values == ['1', '1->1', '1->2', '2', '2->3', '2->4', '3', '3->2', '4'] as Set
+    }
+
+    def locatesCyclesWhenSingleCycleInGraph() {
+        when:
+        walker.add(1)
+        def values = walker.findCycles()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[2] << 1; args[2] << 4 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> }
+        _ * graph.getEdgeValues(_, _, _)
+        0 * _._
+
+        and:
+        values == [[1, 2, 3] as Set]
+    }
+
+    def locatesCyclesWhenCycleContainsASingleNode() {
+        when:
+        walker.add(1)
+        def values = walker.findCycles()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 2 }
+        _ * graph.getEdgeValues(_, _, _)
+        0 * _._
+
+        and:
+        values == [[2] as Set]
+    }
+
+    def locatesCyclesWhenCycleContainsCycles() {
+        when:
+        walker.add(1)
+        def values = walker.findCycles()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[2] << 4; args[2] << 1 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[2] << 3; args[2] << 5 }
+        1 * graph.getNodeValues(5, _, _) >> { args -> args[2] << 4; args[2] << 1 }
+        _ * graph.getEdgeValues(_, _, _)
+        0 * _._
+
+        and:
+        values == [[1, 2, 3, 4, 5] as Set]
+    }
+
+    def locatesCyclesWhenMultipleCyclesInGraph() {
+        when:
+        walker.add(1)
+        def values = walker.findCycles()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 1; args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[2] << 1; args[2] << 4 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[2] << 4; args[2] << 5; }
+        1 * graph.getNodeValues(5, _, _) >> { args -> args[2] << 6 }
+        1 * graph.getNodeValues(6, _, _) >> { args -> args[2] << 5 }
+        _ * graph.getEdgeValues(_, _, _)
+        0 * _._
+
+        and:
+        values == [[5, 6] as Set, [4] as Set, [1, 2, 3] as Set]
+    }
+
+    def canReuseWalkerForMultipleSearches() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2; args[2] << 3 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
+        3 * graph.getEdgeValues(_, _, _)
+        0 * _._
+        values == ['1', '2', '3'] as Set
+
+        // Cached node (1) is reachable via 2 separate new paths (4->5->1 and 4->6->1)
+        when:
+        walker.add(4)
+        values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4'; args[2] << 5; args[2] << 6 }
+        1 * graph.getNodeValues(5, _, _) >> { args -> args[1] << '5'; args[2] << 1 }
+        1 * graph.getNodeValues(6, _, _) >> { args -> args[1] << '6'; args[2] << 1 }
+        4 * graph.getEdgeValues(_, _, _) >> { args -> args[2] << "${args[0]}->${args[1]}".toString() }
+        0 * _._
+        values == ['4', '4->5', '4->6', '5', '5->1', '6', '6->1', '1', '2', '3'] as Set
+
+        when:
+        walker.add(2)
+        values = walker.findValues()
+
+        then:
+        values == ['2', '3'] as Set
+    }
+
+    def canReuseWalkerWhenGraphContainsACycle() {
+        when:
+        walker.add(1)
+        walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 1; args[2] << 4 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4'; args[2] << 2 }
+        5 * graph.getEdgeValues(_, _, _)
+        0 * _._
+
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+
+        when:
+        walker.add(2)
+        values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+
+        when:
+        walker.add(3)
+        values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+
+        when:
+        walker.add(4)
+        values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/graph/DirectedGraphRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/graph/DirectedGraphRendererTest.groovy
new file mode 100644
index 0000000..c11fb3c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/graph/DirectedGraphRendererTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graph
+
+import org.gradle.logging.StyledTextOutput
+import org.gradle.logging.TestStyledTextOutput
+import org.gradle.util.TextUtil
+import spock.lang.Specification
+
+class DirectedGraphRendererTest extends Specification {
+    final DirectedGraph<String, Void> graph = Mock(DirectedGraph)
+    final GraphNodeRenderer<String> nodeRenderer = Stub(GraphNodeRenderer) {
+        renderTo(_, _) >> { String node, StyledTextOutput output -> output.text("[$node]")}
+    }
+    final DirectedGraphRenderer<String> renderer = new DirectedGraphRenderer<String>(nodeRenderer, graph)
+    final TestStyledTextOutput output = new TestStyledTextOutput()
+
+    def "renders a graph with a single root node"() {
+        given:
+        1 * graph.getNodeValues("1", _, _) >> { args -> }
+
+        when:
+        renderer.renderTo("1", output)
+
+        then:
+        output.value == """[1]
+"""
+    }
+
+    def "renders a tree"() {
+        given:
+        1 * graph.getNodeValues("1", _, _) >> { args -> args[2] << "2"; args[2] << "3" }
+        1 * graph.getNodeValues("2", _, _) >> { args -> args[2] << "4" }
+        1 * graph.getNodeValues("3", _, _) >> { args -> }
+        1 * graph.getNodeValues("4", _, _) >> { args -> args[2] << "5" }
+        1 * graph.getNodeValues("5", _, _) >> { args -> }
+
+        when:
+        renderer.renderTo("1", output)
+
+        then:
+        output.value == """[1]
+{info}+--- {normal}[2]
+{info}|    \\--- {normal}[4]
+{info}|         \\--- {normal}[5]
+{info}\\--- {normal}[3]
+"""
+    }
+
+    def "renders a graph that contains nodes with multiple incoming edges"() {
+        given:
+        1 * graph.getNodeValues("1", _, _) >> { args -> args[2] << "2"; args[2] << "3" }
+        1 * graph.getNodeValues("2", _, _) >> { args -> args[2] << "4" }
+        1 * graph.getNodeValues("3", _, _) >> { args -> args[2] << "2" }
+        1 * graph.getNodeValues("4", _, _) >> { args -> }
+
+        when:
+        renderer.renderTo("1", output)
+
+        then:
+        output.value == """[1]
+{info}+--- {normal}[2]
+{info}|    \\--- {normal}[4]
+{info}\\--- {normal}[3]
+{info}     \\--- {normal}[2] (*)
+
+{info}(*) - details omitted (listed previously){normal}
+"""
+    }
+
+    def "renders a graph that contains cycles"() {
+        given:
+        1 * graph.getNodeValues("1", _, _) >> { args -> args[2] << "2"; args[2] << "3" }
+        1 * graph.getNodeValues("2", _, _) >> { args -> args[2] << "3" }
+        1 * graph.getNodeValues("3", _, _) >> { args -> args[2] << "2" }
+
+        when:
+        renderer.renderTo("1", output)
+
+        then:
+        output.value == """[1]
+{info}+--- {normal}[2]
+{info}|    \\--- {normal}[3]
+{info}|         \\--- {normal}[2] (*)
+{info}\\--- {normal}[3] (*)
+
+{info}(*) - details omitted (listed previously){normal}
+"""
+    }
+
+    def "renders a graph that contains a single node with an edge to itself"() {
+        given:
+        1 * graph.getNodeValues("1", _, _) >> { args -> args[2] << "1" }
+
+        when:
+        renderer.renderTo("1", output)
+
+        then:
+        output.value == """[1]
+{info}\\--- {normal}[1] (*)
+
+{info}(*) - details omitted (listed previously){normal}
+"""
+    }
+
+    def "renders to an appendable"() {
+        given:
+        1 * graph.getNodeValues("1", _, _) >> { args -> args[2] << "2"; args[2] << "3" }
+        1 * graph.getNodeValues("2", _, _) >> { args -> args[2] << "4" }
+        1 * graph.getNodeValues("3", _, _) >> { args -> }
+        1 * graph.getNodeValues("4", _, _) >> { args -> args[2] << "5" }
+        1 * graph.getNodeValues("5", _, _) >> { args -> }
+
+        when:
+        StringWriter writer = new StringWriter()
+        renderer.renderTo("1", writer)
+
+        then:
+        writer.toString() == TextUtil.toPlatformLineSeparators("""[1]
++--- [2]
+|    \\--- [4]
+|         \\--- [5]
+\\--- [3]
+""")
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/graph/GraphAggregatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/graph/GraphAggregatorTest.groovy
new file mode 100644
index 0000000..9faebe9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/graph/GraphAggregatorTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.graph
+
+import spock.lang.Specification
+
+class GraphAggregatorTest extends Specification {
+    private final DirectedGraph<String, String> graph = Mock()
+    private final GraphAggregator aggregator = new GraphAggregator(graph)
+
+    def groupsNodeWithTheEntryNodeItIsReachableFrom() {
+        when:
+        def result = aggregator.group(['a'], ['a', 'b'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
+        result.getNodes('a') == ['a', 'b'] as Set
+    }
+
+    def groupsNodeWithTheClosesEntryNodeItIsReachableFrom() {
+        when:
+        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
+        1 * graph.getNodeValues('b', !null, !null) >> { args -> args[2].add('c') }
+        result.getNodes('a') == ['a'] as Set
+        result.getNodes('b') == ['b', 'c'] as Set
+    }
+
+    def groupsNodeWithMultipleEntryNodesWhenTheNodeHasMultipleClosesNodes() {
+        when:
+        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('c') }
+        1 * graph.getNodeValues('b', !null, !null) >> { args -> args[2].add('c') }
+        result.getNodes('a') == ['a', 'c'] as Set
+        result.getNodes('b') == ['b', 'c'] as Set
+    }
+
+    def groupsNodesWhichAreNotReachableFromStartNodes() {
+        when:
+        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c', 'd'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
+        1 * graph.getNodeValues('c', !null, !null) >> { args -> args[2].add('d') }
+        result.topLevelNodes == ['a', 'b', 'c'] as Set
+        result.getNodes('c') == ['c', 'd'] as Set
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/BuildProgressFilterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/BuildProgressFilterTest.groovy
new file mode 100644
index 0000000..58b8013
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/BuildProgressFilterTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.progress
+
+import org.gradle.BuildResult
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.execution.TaskExecutionGraph
+import org.gradle.api.initialization.Settings
+import org.gradle.api.invocation.Gradle
+import spock.lang.Specification
+import spock.lang.Subject
+
+class BuildProgressFilterTest extends Specification {
+
+    BuildProgressLogger logger = Mock()
+    @Subject f = new BuildProgressFilter(logger)
+
+    def graph = Stub(TaskExecutionGraph) { getAllTasks() >> [Mock(Task), Mock(Task), Mock(Task)]} //3 tasks
+    def gradle = Stub(Gradle) {
+        getRootProject() >> Stub(Project) { getAllprojects() >> [Mock(Project), Mock(Project)] } //2 projects
+        getTaskGraph() >> graph
+    }
+    def settings = Stub(Settings) { getGradle() >> gradle }
+    def project = Stub(Project) {
+        getGradle() >> gradle
+        getPath() >> ":foo:bar"
+    }
+    def result = Stub(BuildResult) { getGradle() >> gradle }
+    def task = Stub(Task) { getProject() >> project }
+
+    def "delegates to logger when building root project"() {
+        gradle.getParent() >> null
+
+        when:
+        f.buildStarted(gradle)
+        f.settingsEvaluated(settings)
+        f.projectsLoaded(gradle)
+        f.beforeEvaluate(project)
+        f.afterEvaluate(project, null)
+        f.graphPopulated(graph)
+        f.afterExecute(task, null)
+        f.buildFinished(result)
+
+        then: 1 * logger.buildStarted()
+        then: 1 * logger.settingsEvaluated()
+        then: 1 * logger.projectsLoaded(2)
+        then: 1 * logger.beforeEvaluate(":foo:bar")
+        then: 1 * logger.afterEvaluate(":foo:bar")
+        then: 1 * logger.graphPopulated(3)
+        then: 1 * logger.afterExecute()
+        then: 1 * logger.buildFinished()
+        then: 0 * logger._
+    }
+
+    def "does not delegate when building nested projects"() {
+        gradle.getParent() >> Stub(Gradle)
+
+        when:
+        f.buildStarted(gradle)
+        f.settingsEvaluated(settings)
+        f.projectsLoaded(gradle)
+        f.beforeEvaluate(project)
+        f.afterEvaluate(project, null)
+        f.graphPopulated(graph)
+        f.afterExecute(task, null)
+        f.buildFinished(result)
+
+        then:
+        0 * logger._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/BuildProgressLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/BuildProgressLoggerTest.groovy
new file mode 100644
index 0000000..581860c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/BuildProgressLoggerTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.internal.progress
+
+import org.gradle.logging.ProgressLogger
+import spock.lang.Specification
+import spock.lang.Subject
+
+class BuildProgressLoggerTest extends Specification {
+    ProgressLoggerProvider provider = Mock()
+    ProgressLogger progress = Mock()
+    ProgressLogger confProgress = Mock()
+
+    @Subject logger = new BuildProgressLogger(provider)
+
+    def "logs initialisation stage"() {
+        when: logger.buildStarted()
+
+        then:
+        1 * provider.start('Initialize build', 'Loading') >> progress
+        0 * _
+
+        when: logger.settingsEvaluated()
+
+        then:
+        1 * progress.progress("Configuring")
+        0 * _
+
+        when: logger.projectsLoaded(6)
+
+        then:
+        1 * provider.start("Configure projects", '0/6 projects') >> confProgress
+        0 * _
+    }
+
+    def "logs configuration progress"() {
+        def progress1 = Mock(ProgressLogger)
+        def progress2 = Mock(ProgressLogger)
+
+        when:
+        logger.projectsLoaded(16)
+        logger.beforeEvaluate(":")
+        logger.beforeEvaluate(":foo:bar")
+
+        then:
+        1 * provider.start("Configure projects", '0/16 projects') >> confProgress
+        1 * provider.start("Configure project :", 'root project') >> progress1
+        1 * provider.start("Configure project :foo:bar", ':foo:bar') >> progress2
+        0 * _
+
+        when: logger.afterEvaluate(":foo:bar")
+
+        then:
+        1 * progress2.completed()
+        1 * confProgress.progress("1/16 projects")
+        0 * _
+
+        when: logger.afterEvaluate(":")
+
+        then:
+        1 * progress1.completed()
+        1 * confProgress.progress("2/16 projects")
+        0 * _
+    }
+
+    def "logs configuration completion"() {
+        when:
+        logger.buildStarted()
+        logger.projectsLoaded(16)
+        logger.graphPopulated(10)
+
+        then:
+        1 * provider.start("Configure projects", _) >> confProgress
+        1 * provider.start('Initialize build', _) >> progress
+        0 * _
+
+        then:
+        1 * confProgress.completed()
+        1 * progress.completed("Task graph ready")
+        1 * provider.start("Execute tasks", "Building 0%");
+        0 * _
+    }
+
+    def "logs execution progress"() {
+        def executeProgress = Mock(ProgressLogger)
+
+        when:
+        logger.buildStarted()
+        logger.projectsLoaded(16)
+        logger.graphPopulated(10)
+
+        then:
+        1 * provider.start("Configure projects", _) >> confProgress
+        1 * provider.start('Initialize build', _) >> progress
+        1 * provider.start("Execute tasks", _) >> executeProgress
+
+        when:
+        logger.afterExecute()
+        logger.afterExecute()
+
+        then:
+        1 * executeProgress.progress("Building 10%")
+        1 * executeProgress.progress("Building 20%")
+        0 * _
+
+        when: logger.buildFinished()
+
+        then:
+        1 * executeProgress.completed()
+        0 * _
+    }
+
+    def "don't log progress for projects configured after official configuration phase"() {
+        //currently this can happen, see the ConfigurationOnDemandIntegrationTest
+        when:
+        logger.buildStarted()
+        logger.projectsLoaded(16)
+        logger.graphPopulated(10)
+
+        then:
+        1 * provider.start("Configure projects", _) >> confProgress
+        1 * provider.start('Initialize build', _) >> progress
+
+        when:
+        logger.beforeEvaluate(":foo")
+        logger.afterEvaluate(":bar")
+
+        then:
+        0 * _
+    }
+
+    def "build finished cleans up configuration logger"() {
+        when:
+        logger.buildStarted()
+        logger.projectsLoaded(16)
+        logger.buildFinished()
+
+        then:
+        1 * provider.start('Initialize build', _) >> progress
+        1 * provider.start("Configure projects", _) >> confProgress
+        1 * confProgress.completed()
+    }
+
+    def "build finished cleans up any unfinished configuration loggers"() {
+        def progress1 = Mock(ProgressLogger)
+
+        when:
+        logger.buildStarted()
+        logger.projectsLoaded(16)
+        logger.beforeEvaluate(":")
+        logger.buildFinished()
+
+        then:
+        1 * provider.start('Initialize build', _) >> progress
+        1 * provider.start("Configure project :", 'root project') >> progress1
+        1 * provider.start("Configure projects", _) >> confProgress
+        1 * progress1.completed()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyKeeperTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyKeeperTest.groovy
new file mode 100644
index 0000000..ccb3a01
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyKeeperTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress
+
+import org.gradle.logging.ProgressLogger
+import org.gradle.util.ConcurrentSpecification
+import spock.lang.Subject
+
+class OperationsHierarchyKeeperTest extends ConcurrentSpecification {
+
+    @Subject manager = new OperationsHierarchyKeeper()
+
+    def "provides hierarchy"() {
+        def h1 = manager.currentHierarchy(null)
+        def h2 = manager.currentHierarchy(null)
+
+        expect:
+        h1.hierarchy.is(h2.hierarchy)
+        h1.sharedCounter.is(h2.sharedCounter)
+    }
+
+    def "provides hierarchy per thread"() {
+        def h1
+        def h2
+
+        when:
+        start { h1 = manager.currentHierarchy(null) }
+        start { h2 = manager.currentHierarchy(null) }
+        finished()
+
+        then:
+        !h1.hierarchy.is(h2.hierarchy)
+        h1.sharedCounter.is(h2.sharedCounter)
+    }
+
+    def "may feed the parent logger"() {
+        def parent1 = Stub(ProgressLogger) { currentOperationId() >> 1 }
+        def parent2 = Mock(ProgressLogger) { currentOperationId() >> 2 }
+
+        when:
+        def h1 = manager.currentHierarchy(parent1)
+        def h2 = manager.currentHierarchy(parent2)
+
+        then:
+        h1.hierarchy.is(h2.hierarchy)
+        h1.hierarchy == [1]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyTest.groovy
new file mode 100644
index 0000000..0b11636
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress
+
+import spock.lang.Specification
+import spock.lang.Subject
+
+import java.util.concurrent.atomic.AtomicLong
+
+class OperationsHierarchyTest extends Specification {
+
+    @Subject hierarchy = new OperationsHierarchy(new AtomicLong(), new LinkedList<Long>())
+
+    def "does not have any identifier initially"() {
+        when: hierarchy.currentOperationId()
+        then: thrown(OperationsHierarchy.NoActiveOperationFound)
+
+        when: hierarchy.completeCurrentOperation()
+        then: thrown(OperationsHierarchy.NoActiveOperationFound)
+    }
+
+    def "provides operation identifier"() {
+        def id1 = hierarchy.start()
+        def id2 = hierarchy.start()
+
+        expect:
+        id1.id == 1
+        id1.parentId == null
+
+        id2.id == 1
+        id2.parentId == null
+    }
+
+    def "provides hierarchical operation identifier"() {
+        hierarchy = new OperationsHierarchy(new AtomicLong(2), [1L,2L] as LinkedList)
+
+        when:
+        def id = hierarchy.start()
+
+        then:
+        id.id == 3
+        id.parentId == 2
+    }
+
+    def "provides current operation id"() {
+        hierarchy = new OperationsHierarchy(new AtomicLong(2), [1L,2L] as LinkedList)
+
+        when:
+        def id1 = hierarchy.start()
+
+        then:
+        hierarchy.currentOperationId() //can be called many times
+        id1.id == hierarchy.currentOperationId()
+        id1.id == 3
+    }
+
+    def "removes current operation id"() {
+        hierarchy = new OperationsHierarchy(new AtomicLong(12), [1L,2L] as LinkedList)
+
+        when: hierarchy.start()
+
+        then:
+        hierarchy.currentOperationId() == 13
+        hierarchy.completeCurrentOperation() == 13
+
+        when: hierarchy.currentOperationId()
+
+        then: thrown(OperationsHierarchy.NoActiveOperationFound)
+    }
+
+    def "new operations respect removed parents"() {
+        hierarchy = new OperationsHierarchy(new AtomicLong(12), [1L,2L] as LinkedList)
+
+        when:
+        def id1 = hierarchy.start()
+        def removed = hierarchy.completeCurrentOperation()
+        def id2 = hierarchy.start()
+
+        then:
+        id1.id == 13
+        id1.parentId == 2
+        removed == 13
+        id2.id == 14
+        id2.parentId == 2
+    }
+
+    def "incomplete child operations are tolerated"() {
+        def ids = [1L,2L] as LinkedList
+        hierarchy = new OperationsHierarchy(new AtomicLong(12), ids)
+
+        when: hierarchy.start()
+
+        then: hierarchy.currentOperationId() == 13
+
+        when: ids << 100L //some child operation is added
+
+        then:
+        hierarchy.currentOperationId() == 13 //current id is the same
+        hierarchy.completeCurrentOperation() == 13
+
+        when:
+        def id = hierarchy.start()
+
+        then:
+        id.id == 14
+        id.parentId == 100
+    }
+
+    def "reports if hierarchy is empty"() {
+        def ids = [] as LinkedList
+        hierarchy = new OperationsHierarchy(new AtomicLong(12), ids)
+        hierarchy.start()
+        ids.clear() //this should never happen
+
+        when: hierarchy.completeCurrentOperation()
+        then: thrown(OperationsHierarchy.HierarchyEmptyException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/PercentageProgressFormatterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/PercentageProgressFormatterTest.groovy
new file mode 100644
index 0000000..eb22fef
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/PercentageProgressFormatterTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress
+
+import spock.lang.Specification
+
+class PercentageProgressFormatterTest extends Specification {
+
+    def "knows progress"() {
+        def f = new PercentageProgressFormatter("Building", 3);
+
+        expect:
+        f.progress == "Building 0%"
+        f.incrementAndGetProgress() == "Building 33%"
+        f.incrementAndGetProgress() == "Building 66%"
+        f.progress == "Building 66%"
+        f.incrementAndGetProgress() == "Building 100%"
+
+        when:
+        f.incrementAndGetProgress()
+
+        then:
+        thrown(IllegalStateException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/SimpleProgressFormatterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/SimpleProgressFormatterTest.groovy
new file mode 100644
index 0000000..05adebe
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/SimpleProgressFormatterTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress
+
+import spock.lang.Specification
+
+class SimpleProgressFormatterTest extends Specification {
+
+    def "formats progress"() {
+        def f = new SimpleProgressFormatter(10, "things")
+
+        expect:
+        f.progress == "0/10 things"
+        f.incrementAndGetProgress() == "1/10 things"
+        f.incrementAndGetProgress() == "2/10 things"
+        f.progress == "2/10 things"
+    }
+
+    def "does not allow overflow"() {
+        def f = new SimpleProgressFormatter(2, "cats");
+        f.incrementAndGetProgress()
+        f.incrementAndGetProgress()
+
+        when:
+        f.incrementAndGetProgress()
+
+        then:
+        thrown(IllegalStateException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/BuildScopeServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/BuildScopeServicesTest.groovy
new file mode 100644
index 0000000..d4079c2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/BuildScopeServicesTest.groovy
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.*
+import org.gradle.api.internal.artifacts.DependencyManagementServices
+import org.gradle.api.internal.classpath.DefaultModuleRegistry
+import org.gradle.api.internal.classpath.ModuleRegistry
+import org.gradle.api.internal.classpath.PluginModuleRegistry
+import org.gradle.api.internal.file.FileLookup
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.project.*
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.internal.CacheFactory
+import org.gradle.cache.internal.DefaultCacheRepository
+import org.gradle.configuration.BuildConfigurer
+import org.gradle.configuration.DefaultBuildConfigurer
+import org.gradle.configuration.DefaultScriptPluginFactory
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.DefaultScriptCompilerFactory
+import org.gradle.groovy.scripts.ScriptCompilerFactory
+import org.gradle.initialization.*
+import org.gradle.internal.Factory
+import org.gradle.internal.classloader.ClassLoaderFactory
+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.logging.ProgressLoggerFactory
+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.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.hamcrest.Matchers.instanceOf
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+
+public class BuildScopeServicesTest extends Specification {
+    @Rule
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    StartParameter startParameter = new StartParameter()
+    ServiceRegistry parent = Stub()
+    Factory<CacheFactory> cacheFactoryFactory = Mock()
+    ClosableCacheFactory cacheFactory = Mock()
+    ClassLoaderRegistry classLoaderRegistry = Mock()
+
+    BuildScopeServices registry = new BuildScopeServices(parent, startParameter)
+
+    def setup() {
+        startParameter.gradleUserHomeDir = tmpDir.testDirectory
+        parent.getFactory(CacheFactory) >> cacheFactoryFactory
+        cacheFactoryFactory.create() >> cacheFactory
+        parent.get(ClassLoaderRegistry) >> classLoaderRegistry
+        parent.getFactory(LoggingManagerInternal) >> Stub(Factory)
+        parent.get(ModuleRegistry) >> new DefaultModuleRegistry()
+        parent.get(PluginModuleRegistry) >> Stub(PluginModuleRegistry)
+        parent.get(DependencyManagementServices) >> Stub(DependencyManagementServices)
+        parent.get(Instantiator) >> ThreadGlobalInstantiator.getOrCreate()
+        parent.get(FileResolver) >> Stub(FileResolver)
+        parent.get(ProgressLoggerFactory) >> Stub(ProgressLoggerFactory)
+        parent.get(CacheFactory) >> Stub(CacheFactory)
+        parent.get(DocumentationRegistry) >> new DocumentationRegistry()
+        parent.get(FileLookup) >> Stub(FileLookup)
+    }
+
+    def delegatesToParentForUnknownService() {
+        setup:
+        parent.get(String) >> "value"
+
+        expect:
+        registry.get(String) == "value"
+    }
+
+    def addsAllPluginBuildScopeServices() {
+        def plugin2 = Mock(PluginServiceRegistry)
+        def plugin1 = Mock(PluginServiceRegistry)
+
+        given:
+        parent.getAll(PluginServiceRegistry) >> [plugin1, plugin2]
+
+        when:
+        new BuildScopeServices(parent, startParameter)
+
+        then:
+        1 * plugin1.registerBuildServices(_)
+        1 * plugin2.registerBuildServices(_)
+    }
+
+    def throwsExceptionForUnknownDomainObject() {
+        when:
+        registry.get(ServiceRegistryFactory).createFor("string")
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == "Cannot create services for unknown domain object of type String."
+    }
+
+    def canCreateServicesForAGradleInstance() {
+        setup:
+        GradleInternal gradle = Mock()
+        def registry = registry.get(ServiceRegistryFactory).createFor(gradle)
+        expect:
+        registry instanceof GradleScopeServices
+    }
+
+    def "closing the registry closes gradle scoped services, closing project services"() {
+        given:
+        GradleInternal gradle = Mock()
+        def gradleRegistry = registry.get(ServiceRegistryFactory).createFor(gradle)
+        def project = Mock(ProjectInternal)
+        def projectRegistry = gradleRegistry.get(ServiceRegistryFactory).createFor(project)
+
+        expect:
+        !gradleRegistry.closed
+        !projectRegistry.closed
+
+        when:
+        registry.close()
+
+        then:
+        gradleRegistry.closed
+        projectRegistry.closed
+    }
+
+    def canCreateServicesForASettingsInstance() {
+        setup:
+        SettingsInternal settings = Mock()
+        def registry = registry.get(ServiceRegistryFactory).createFor(settings)
+        expect:
+        registry instanceof SettingsScopeServices
+    }
+
+    def providesAListenerManager() {
+        setup:
+        ListenerManager listenerManager = expectListenerManagerCreated()
+        expect:
+        assertThat(registry.get(ListenerManager), sameInstance(listenerManager))
+    }
+
+    def providesAScriptCompilerFactory() {
+        setup:
+        expectListenerManagerCreated()
+
+        expect:
+        registry.get(ScriptCompilerFactory) instanceof DefaultScriptCompilerFactory
+        registry.get(ScriptCompilerFactory) == registry.get(ScriptCompilerFactory)
+    }
+
+    def providesACacheRepositoryAndCleansUpOnClose() {
+        expect:
+        registry.get(CacheRepository) instanceof DefaultCacheRepository
+        registry.get(CacheRepository) == registry.get(CacheRepository)
+    }
+
+    def providesAnInitScriptHandler() {
+        setup:
+        allowGetCoreImplClassLoader()
+        expectListenerManagerCreated()
+        allowGetGradleDistributionLocator()
+
+        expect:
+        registry.get(InitScriptHandler) instanceof InitScriptHandler
+        registry.get(InitScriptHandler) == registry.get(InitScriptHandler)
+    }
+
+    def providesAScriptObjectConfigurerFactory() {
+        setup:
+        allowGetCoreImplClassLoader()
+        expectListenerManagerCreated()
+        expect:
+        assertThat(registry.get(ScriptPluginFactory), instanceOf(DefaultScriptPluginFactory))
+        assertThat(registry.get(ScriptPluginFactory), sameInstance(registry.get(ScriptPluginFactory)))
+    }
+
+    def providesASettingsProcessor() {
+        setup:
+        allowGetCoreImplClassLoader()
+        expectListenerManagerCreated()
+        expect:
+        assertThat(registry.get(SettingsProcessor), instanceOf(PropertiesLoadingSettingsProcessor))
+        assertThat(registry.get(SettingsProcessor), sameInstance(registry.get(SettingsProcessor)))
+    }
+
+    def providesAnExceptionAnalyser() {
+        setup:
+        expectListenerManagerCreated()
+        expect:
+        assertThat(registry.get(ExceptionAnalyser), instanceOf(MultipleBuildFailuresExceptionAnalyser))
+        assertThat(registry.get(ExceptionAnalyser).delegate, instanceOf(DefaultExceptionAnalyser))
+        assertThat(registry.get(ExceptionAnalyser), sameInstance(registry.get(ExceptionAnalyser)))
+    }
+
+    def providesAWorkerProcessFactory() {
+        setup:
+        expectParentServiceLocated(MessagingServer)
+        allowGetCoreImplClassLoader()
+
+        expect:
+        assertThat(registry.getFactory(WorkerProcessBuilder), instanceOf(DefaultWorkerProcessFactory))
+    }
+
+    def providesAnIsolatedAntBuilder() {
+        setup:
+        expectParentServiceLocated(ClassLoaderFactory)
+        allowGetCoreImplClassLoader()
+        expect:
+
+        assertThat(registry.get(IsolatedAntBuilder), instanceOf(DefaultIsolatedAntBuilder))
+        assertThat(registry.get(IsolatedAntBuilder), sameInstance(registry.get(IsolatedAntBuilder)))
+    }
+
+    def providesAProjectFactory() {
+        setup:
+        expectParentServiceLocated(Instantiator)
+        expectParentServiceLocated(ClassGenerator)
+        expect:
+        assertThat(registry.get(IProjectFactory), instanceOf(ProjectFactory))
+        assertThat(registry.get(IProjectFactory), sameInstance(registry.get(IProjectFactory)))
+    }
+
+    def providesABuildConfigurer() {
+        expect:
+        assertThat(registry.get(BuildConfigurer), instanceOf(DefaultBuildConfigurer))
+        assertThat(registry.get(BuildConfigurer), sameInstance(registry.get(BuildConfigurer)))
+    }
+
+    def providesAPropertiesLoader() {
+        expect:
+        assertThat(registry.get(IGradlePropertiesLoader), instanceOf(DefaultGradlePropertiesLoader))
+        assertThat(registry.get(IGradlePropertiesLoader), sameInstance(registry.get(IGradlePropertiesLoader)))
+    }
+
+    def providesABuildLoader() {
+        setup:
+        expectParentServiceLocated(Instantiator)
+        expect:
+        assertThat(registry.get(BuildLoader), instanceOf(ProjectPropertySettingBuildLoader))
+        assertThat(registry.get(BuildLoader), sameInstance(registry.get(BuildLoader)))
+    }
+
+    def providesAProfileEventAdapter() {
+        setup:
+        expectParentServiceLocated(BuildRequestMetaData)
+        expectListenerManagerCreated()
+
+        expect:
+        assertThat(registry.get(ProfileEventAdapter), instanceOf(ProfileEventAdapter))
+        assertThat(registry.get(ProfileEventAdapter), sameInstance(registry.get(ProfileEventAdapter)))
+    }
+
+    def "provides a project registry"() {
+        when:
+        def projectRegistry = registry.get(ProjectRegistry)
+        def secondRegistry = registry.get(ProjectRegistry)
+
+        then:
+        projectRegistry instanceof DefaultProjectRegistry
+        projectRegistry sameInstance(secondRegistry)
+    }
+
+    private <T> T expectParentServiceLocated(Class<T> type) {
+        T t = Mock(type)
+        parent.get(type) >> t
+        t
+    }
+
+    private ListenerManager expectListenerManagerCreated() {
+        final ListenerManager listenerManager = new DefaultListenerManager()
+        final ListenerManager listenerManagerParent = Mock()
+        parent.get(ListenerManager) >> listenerManagerParent
+        1 * listenerManagerParent.createChild() >> listenerManager
+        listenerManager
+    }
+
+    private void allowGetCoreImplClassLoader() {
+        classLoaderRegistry.getCoreImplClassLoader() >> new ClassLoader() {}
+    }
+
+    private void allowGetGradleDistributionLocator() {
+        parent.get(GradleDistributionLocator) >> Mock(GradleDistributionLocator)
+    }
+
+    public interface ClosableCacheFactory extends CacheFactory {
+        void close()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/GlobalScopeServicesTest.java b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/GlobalScopeServicesTest.java
new file mode 100755
index 0000000..8b02f93
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/GlobalScopeServicesTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.service.scopes;
+
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.DefaultPluginModuleRegistry;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.api.internal.file.DefaultFileLookup;
+import org.gradle.api.internal.file.FileLookup;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.cache.internal.CacheFactory;
+import org.gradle.cache.internal.DefaultCacheFactory;
+import org.gradle.cache.internal.DefaultFileLockManager;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.initialization.*;
+import org.gradle.internal.classloader.ClassLoaderFactory;
+import org.gradle.internal.classloader.DefaultClassLoaderFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.logging.internal.DefaultLoggingManagerFactory;
+import org.gradle.logging.internal.DefaultProgressLoggerFactory;
+import org.gradle.messaging.remote.MessagingServer;
+import org.junit.Test;
+import spock.lang.Shared;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+public class GlobalScopeServicesTest {
+    @Shared
+    private final ServiceRegistry registry = new DefaultServiceRegistry(LoggingServiceRegistry.newEmbeddableLogging(), NativeServices.getInstance()).addProvider(new GlobalScopeServices(false));
+
+    @Test
+    public void providesAGradleLauncherFactory() {
+        assertThat(registry.get(GradleLauncherFactory.class), instanceOf(DefaultGradleLauncherFactory.class));
+    }
+
+    @Test
+    public void providesCommandLineArgsConverter() {
+        assertThat(registry.get(CommandLineConverter.class), instanceOf(
+                DefaultCommandLineConverter.class));
+    }
+
+    @Test
+    public void providesACacheFactory() {
+        assertThat(registry.get(CacheFactory.class), instanceOf(DefaultCacheFactory.class));
+    }
+
+    @Test
+    public void providesAModuleRegistry() {
+        assertThat(registry.get(ModuleRegistry.class), instanceOf(DefaultModuleRegistry.class));
+    }
+
+    @Test
+    public void providesAPluginModuleRegistry() {
+        assertThat(registry.get(PluginModuleRegistry.class), instanceOf(DefaultPluginModuleRegistry.class));
+    }
+
+    @Test
+    public void providesAClassPathRegistry() {
+        assertThat(registry.get(ClassPathRegistry.class), instanceOf(DefaultClassPathRegistry.class));
+    }
+
+    @Test
+    public void providesAClassLoaderRegistry() {
+        assertThat(registry.get(ClassLoaderRegistry.class), instanceOf(DefaultClassLoaderRegistry.class));
+    }
+
+    @Test
+    public void providesALoggingManagerFactory() {
+        assertThat(registry.getFactory(LoggingManagerInternal.class), instanceOf(DefaultLoggingManagerFactory.class));
+    }
+    
+    @Test
+    public void providesAListenerManager() {
+        assertThat(registry.get(ListenerManager.class), instanceOf(DefaultListenerManager.class));
+    }
+    
+    @Test
+    public void providesAProgressLoggerFactory() {
+        assertThat(registry.get(ProgressLoggerFactory.class), instanceOf(DefaultProgressLoggerFactory.class));
+    }
+    
+    @Test
+    public void providesAGradleDistributionLocator() {
+        assertThat(registry.get(GradleDistributionLocator.class), instanceOf(DefaultModuleRegistry.class));
+    }
+    
+    @Test
+    public void providesAClassLoaderFactory() {
+        assertThat(registry.get(ClassLoaderFactory.class), instanceOf(DefaultClassLoaderFactory.class));
+    }
+
+    @Test
+    public void providesAMessagingServer() {
+        assertThat(registry.get(MessagingServer.class), instanceOf(MessagingServer.class));
+    }
+
+    @Test
+    public void providesAClassGenerator() {
+        assertThat(registry.get(ClassGenerator.class), instanceOf(AsmBackedClassGenerator.class));
+    }
+
+    @Test
+    public void providesAnInstantiator() {
+        assertThat(registry.get(org.gradle.internal.reflect.Instantiator.class), instanceOf(ClassGeneratorBackedInstantiator.class));
+    }
+
+    @Test
+    public void providesAnExecutorFactory() {
+        assertThat(registry.get(ExecutorFactory.class), instanceOf(DefaultExecutorFactory.class));
+    }
+
+    @Test
+    public void providesAFileLockManager() {
+        assertThat(registry.get(FileLockManager.class), instanceOf(DefaultFileLockManager.class));
+    }
+
+    @Test
+    public void providesAProcessEnvironment() {
+        assertThat(registry.get(ProcessEnvironment.class), notNullValue());
+    }
+
+    @Test
+    public void providesAFileSystem() {
+        assertThat(registry.get(FileSystem.class), notNullValue());
+    }
+
+    @Test
+    public void providesAFileResolver() {
+        assertThat(registry.get(FileResolver.class), instanceOf(IdentityFileResolver.class));
+    }
+
+    @Test
+    public void providesAFileLookup() {
+        assertThat(registry.get(FileLookup.class), instanceOf(DefaultFileLookup.class));
+    }
+
+    @Test
+    public void providesADocumentationRegistry() throws Exception {
+        assertThat(registry.get(DocumentationRegistry.class), instanceOf(DocumentationRegistry.class));
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/GradleScopeServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/GradleScopeServicesTest.groovy
new file mode 100644
index 0000000..d09ba90
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/GradleScopeServicesTest.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.artifacts.DependencyManagementServices
+import org.gradle.api.internal.changedetection.state.InMemoryTaskArtifactCache
+import org.gradle.api.internal.plugins.DefaultPluginContainer
+import org.gradle.api.internal.plugins.PluginRegistry
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.options.OptionReader
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.cache.CacheRepository
+import org.gradle.execution.BuildExecuter
+import org.gradle.execution.DefaultBuildExecuter
+import org.gradle.execution.TaskGraphExecuter
+import org.gradle.execution.TaskSelector
+import org.gradle.execution.taskgraph.DefaultTaskGraphExecuter
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.environment.GradleBuildEnvironment
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.listener.ListenerManager
+import spock.lang.Specification
+
+import static org.hamcrest.Matchers.sameInstance
+
+public class GradleScopeServicesTest extends Specification {
+    private GradleInternal gradle = Stub()
+    private ServiceRegistry parent = Stub()
+    private ListenerManager listenerManager = Stub()
+    private CacheRepository cacheRepository = Stub()
+    private GradleScopeServices registry = new GradleScopeServices(parent, gradle)
+    private StartParameter startParameter = new StartParameter()
+    private PluginRegistry pluginRegistryParent = Stub()
+    private PluginRegistry pluginRegistryChild = Stub()
+
+    public void setup() {
+        parent.get(StartParameter) >> Stub(StartParameter)
+        parent.get(GradleBuildEnvironment) >> Stub(GradleBuildEnvironment)
+        parent.get(InMemoryTaskArtifactCache) >> Stub(InMemoryTaskArtifactCache)
+        parent.get(ListenerManager) >> listenerManager
+        parent.get(CacheRepository) >> cacheRepository
+        parent.get(PluginRegistry) >> pluginRegistryParent
+        parent.get(DependencyManagementServices) >> Stub(DependencyManagementServices)
+        parent.get(ExecutorFactory) >> Stub(ExecutorFactory)
+        gradle.getStartParameter() >> startParameter
+        pluginRegistryParent.createChild(_, _) >> pluginRegistryChild
+    }
+
+    def "can create services for a project instance"() {
+        ProjectInternal project = Mock()
+
+        when:
+        def serviceRegistry = registry.get(ServiceRegistryFactory).createFor(project)
+
+        then:
+        serviceRegistry instanceof ProjectScopeServices
+    }
+
+    def "created project registries are closed on close"() {
+        ProjectInternal project1 = Mock()
+        ProjectInternal project2 = Mock()
+
+        when:
+        def serviceRegistry1 = registry.get(ServiceRegistryFactory).createFor(project1)
+        def serviceRegistry2 = registry.get(ServiceRegistryFactory).createFor(project2)
+
+        then:
+        !serviceRegistry1.closed
+        !serviceRegistry2.closed
+
+        when:
+        registry.close()
+
+        then:
+        serviceRegistry1.closed
+        serviceRegistry2.closed
+    }
+
+    def "provides a plugin registry"() {
+        when:
+        def pluginRegistry = registry.get(PluginRegistry)
+        def secondRegistry = registry.get(PluginRegistry)
+
+        then:
+        pluginRegistry == pluginRegistryChild
+        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 plugin container"() {
+        when:
+        def pluginContainer = registry.get(PluginContainer)
+        def secondPluginContainer = registry.get(PluginContainer)
+
+        then:
+        pluginContainer instanceof DefaultPluginContainer
+        secondPluginContainer sameInstance(pluginContainer)
+    }
+
+    def "provides a task graph executer"() {
+        when:
+        def graphExecuter = registry.get(TaskGraphExecuter)
+        def secondExecuter = registry.get(TaskGraphExecuter)
+
+        then:
+        graphExecuter instanceof DefaultTaskGraphExecuter
+        graphExecuter sameInstance(secondExecuter)
+    }
+
+    def "provides a task selector"() {
+        when:
+        def selector = registry.get(TaskSelector)
+        def secondSelector = registry.get(TaskSelector)
+
+        then:
+        selector instanceof TaskSelector
+        secondSelector sameInstance(selector)
+    }
+
+    def "provides an option reader"() {
+        when:
+        def optionReader = registry.get(OptionReader)
+        def secondOptionReader = registry.get(OptionReader)
+
+        then:
+        optionReader instanceof OptionReader
+        secondOptionReader sameInstance(optionReader)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/ProjectScopeServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/ProjectScopeServicesTest.groovy
new file mode 100644
index 0000000..6ffe1f7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/ProjectScopeServicesTest.groovy
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes
+
+import org.gradle.api.AntBuilder
+import org.gradle.api.RecordingAntBuildListener
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.*
+import org.gradle.api.internal.artifacts.DependencyManagementServices
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
+import org.gradle.api.internal.file.*
+import org.gradle.api.internal.initialization.ClassLoaderScope
+import org.gradle.api.internal.initialization.DefaultScriptHandler
+import org.gradle.api.internal.plugins.DefaultPluginContainer
+import org.gradle.api.internal.plugins.PluginRegistry
+import org.gradle.api.internal.project.DefaultAntBuilderFactory
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.api.internal.tasks.DefaultTaskContainerFactory
+import org.gradle.api.internal.tasks.TaskContainerInternal
+import org.gradle.api.logging.LoggingManager
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.configuration.project.DefaultProjectConfigurationActionContainer
+import org.gradle.configuration.project.ProjectConfigurationActionContainer
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.initialization.ProjectAccessListener
+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.ServiceRegistration
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+import org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectScopeServicesTest extends Specification {
+    ProjectInternal project = Mock()
+    ConfigurationContainerInternal configurationContainer = Mock()
+    GradleInternal gradle = Mock()
+    DependencyManagementServices dependencyManagementServices = Mock()
+    ITaskFactory taskFactory = Mock()
+    DependencyFactory dependencyFactory = Mock()
+    ServiceRegistry parent = Stub()
+    ProjectScopeServices registry
+    PluginRegistry pluginRegistry = Mock()
+    def classLoaderScope = Mock(ClassLoaderScope)
+    DependencyResolutionServices dependencyResolutionServices = Stub()
+
+    @Rule
+    TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+
+    def setup() {
+        project.gradle >> gradle
+        project.projectDir >> testDirectoryProvider.file("project-dir").createDir().absoluteFile
+        project.buildScriptSource >> Stub(ScriptSource)
+        project.getClassLoaderScope() >> classLoaderScope
+        project.getClassLoaderScope().createChild() >> classLoaderScope
+        project.getClassLoaderScope().lock() >> classLoaderScope
+        parent.get(ITaskFactory) >> taskFactory
+        parent.get(DependencyFactory) >> dependencyFactory
+        parent.get(PluginRegistry) >> pluginRegistry
+        parent.get(DependencyManagementServices) >> dependencyManagementServices
+        parent.get(Instantiator) >> new DirectInstantiator()
+        parent.get(FileSystem) >> Stub(FileSystem)
+        parent.get(ClassGenerator) >> Stub(ClassGenerator)
+        parent.get(ProjectAccessListener) >> Stub(ProjectAccessListener)
+        parent.get(FileLookup) >> Stub(FileLookup)
+        registry = new ProjectScopeServices(parent, project)
+    }
+
+    def "creates a registry for a task"() {
+        expect:
+        registry.get(ServiceRegistryFactory).createFor(Stub(TaskInternal)) instanceof TaskScopeServices
+    }
+
+    def "adds all project scoped plugin services"() {
+        def plugin2 = Mock(PluginServiceRegistry)
+        def plugin1 = Mock(PluginServiceRegistry)
+
+        given:
+        parent.getAll(PluginServiceRegistry) >> [plugin1, plugin2]
+
+        when:
+        new ProjectScopeServices(parent, project)
+
+        then:
+        1 * plugin1.registerProjectServices(_)
+        1 * plugin2.registerProjectServices(_)
+    }
+
+    def "provides a TaskContainerFactory"() {
+        1 * taskFactory.createChild({ it.is project }, { it instanceof ClassGeneratorBackedInstantiator }) >> Stub(ITaskFactory)
+
+        expect:
+        registry.getFactory(TaskContainerInternal) instanceof DefaultTaskContainerFactory
+    }
+
+    def "provides a PluginContainer"() {
+        1 * pluginRegistry.createChild(classLoaderScope, _ as DependencyInjectingInstantiator) >> Stub(PluginRegistry)
+
+        expect:
+        provides(PluginContainer, DefaultPluginContainer)
+    }
+
+    def "provides a ToolingModelBuilderRegistry"() {
+        expect:
+        provides(ToolingModelBuilderRegistry, DefaultToolingModelBuilderRegistry)
+    }
+
+    def "provides dependency management DSL services"() {
+        def testDslService = Stub(Runnable)
+
+        when:
+        def registry = new ProjectScopeServices(parent, project)
+        def service = registry.get(Runnable)
+
+        then:
+        service.is(testDslService)
+
+        and:
+        1 * dependencyManagementServices.addDslServices(_) >> { ServiceRegistration registration ->
+            registration.add(Runnable, testDslService)
+        }
+    }
+
+    def "provides an AntBuilder factory"() {
+        expect:
+        registry.getFactory(AntBuilder) instanceof DefaultAntBuilderFactory
+        registry.getFactory(AntBuilder).is registry.getFactory(AntBuilder)
+    }
+
+    def "provides a ScriptHandler"() {
+        expectScriptClassLoaderProviderCreated()
+
+        expect:
+        provides(ScriptHandler, DefaultScriptHandler)
+    }
+
+    def "provides a FileResolver"() {
+        expect:
+        provides(FileResolver, BaseDirFileResolver)
+    }
+
+    def "provides a FileOperations instance"() {
+        1 * project.tasks
+
+        expect:
+        provides(FileOperations, DefaultFileOperations)
+    }
+
+    def "provides a TemporaryFileProvider"() {
+        expect:
+        provides(TemporaryFileProvider, DefaultTemporaryFileProvider)
+    }
+
+    def "provides a ProjectConfigurationActionContainer"() {
+        expect:
+        provides(ProjectConfigurationActionContainer, DefaultProjectConfigurationActionContainer)
+    }
+
+    def "provides a LoggingManager"() {
+        Factory<LoggingManagerInternal> loggingManagerFactory = Mock()
+        LoggingManager loggingManager = Mock(LoggingManagerInternal)
+
+        parent.getFactory(LoggingManagerInternal) >> loggingManagerFactory
+        1 * loggingManagerFactory.create() >> loggingManager
+
+        expect:
+        registry.get(LoggingManager).is loggingManager
+        registry.get(LoggingManager).is registry.get(LoggingManager)
+    }
+
+    def "ant builder is closed when registry is closed"() {
+        given:
+        def antBuilder = registry.getFactory(AntBuilder).create()
+        def listener = new RecordingAntBuildListener()
+        antBuilder.project.addBuildListener(listener)
+
+        expect:
+        listener.buildFinished.empty
+
+        when:
+        registry.close()
+
+        then:
+        !listener.buildFinished.empty
+    }
+
+    void provides(Class<?> contractType, Class<?> implementationType) {
+        assert implementationType.isInstance(registry.get(contractType))
+        assert registry.get(contractType).is(registry.get(contractType))
+    }
+
+    private void expectScriptClassLoaderProviderCreated() {
+        1 * dependencyManagementServices.create(!null, !null, !null, !null) >> dependencyResolutionServices
+        // return mock rather than stub; workaround for fact that Spock doesn't substitute generic method return type as it should
+        dependencyResolutionServices.configurationContainer >> Mock(ConfigurationContainerInternal)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/SettingsScopeServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/SettingsScopeServicesTest.groovy
new file mode 100644
index 0000000..6a0d578
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/SettingsScopeServicesTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scopes
+
+import org.gradle.api.internal.SettingsInternal
+import org.gradle.api.internal.file.BaseDirFileResolver
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.plugins.DefaultPluginContainer
+import org.gradle.api.internal.plugins.PluginRegistry
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.internal.service.ServiceRegistry
+import spock.lang.Specification
+
+import static org.hamcrest.Matchers.sameInstance
+
+class SettingsScopeServicesTest extends Specification {
+    private SettingsInternal settings = Mock()
+    private ServiceRegistry parent = Stub()
+    private SettingsScopeServices registry = new SettingsScopeServices(parent, settings)
+    private PluginRegistry pluginRegistryParent = Mock()
+    private PluginRegistry pluginRegistryChild = Mock()
+
+    def setup() {
+        settings.getSettingsDir() >> new File("settings-dir").absoluteFile
+        parent.get(org.gradle.internal.nativeplatform.filesystem.FileSystem) >> Stub(org.gradle.internal.nativeplatform.filesystem.FileSystem)
+        parent.get(PluginRegistry) >> pluginRegistryParent
+        pluginRegistryParent.createChild(_, _) >> pluginRegistryChild
+    }
+
+
+    def "provides a plugin container"() {
+        when:
+        def pluginContainer = registry.get(PluginContainer)
+        def secondPluginContainer = registry.get(PluginContainer)
+
+        then:
+        pluginContainer instanceof DefaultPluginContainer
+        secondPluginContainer sameInstance(pluginContainer)
+    }
+
+    def "provides a file resolver"() {
+        when:
+        def fileResolver = registry.get(FileResolver)
+        def secondFileResolver = registry.get(FileResolver)
+
+        then:
+        fileResolver instanceof BaseDirFileResolver
+        secondFileResolver sameInstance(fileResolver)
+    }
+
+
+    def "provides a plugin registry"() {
+        when:
+        def pluginRegistry = registry.get(PluginRegistry)
+        def secondPluginRegistry = registry.get(PluginRegistry)
+
+        then:
+        pluginRegistry == pluginRegistryChild
+        secondPluginRegistry sameInstance(pluginRegistry)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/TaskExecutionServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/TaskExecutionServicesTest.groovy
new file mode 100644
index 0000000..6236df1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/TaskExecutionServicesTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.service.scopes
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.changedetection.state.InMemoryTaskArtifactCache
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter
+import org.gradle.api.invocation.Gradle
+import org.gradle.cache.CacheBuilder
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.PersistentCache
+import org.gradle.internal.environment.GradleBuildEnvironment
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.listener.ListenerManager
+import spock.lang.Specification
+
+class TaskExecutionServicesTest extends Specification {
+    final ServiceRegistry parent = Mock()
+    final Gradle gradle = Mock()
+    final def services = new DefaultServiceRegistry(parent).addProvider(new TaskExecutionServices())
+
+    def "makes a TaskExecutor available"() {
+        given:
+        CacheRepository cacheRepository = Mock()
+        CacheBuilder cacheBuilder = Mock()
+        _ * parent.get(Gradle) >> gradle
+        _ * parent.get(ListenerManager) >> Mock(ListenerManager)
+        _ * parent.get(StartParameter) >> Mock(StartParameter)
+        _ * parent.get(GradleBuildEnvironment) >> Stub(GradleBuildEnvironment)
+        _ * parent.get(CacheRepository) >> cacheRepository
+        _ * parent.get(Instantiator) >> Mock(Instantiator)
+        _ * parent.get(InMemoryTaskArtifactCache) >> Mock(InMemoryTaskArtifactCache)
+        _ * cacheRepository.cache(gradle, 'taskArtifacts') >> cacheBuilder
+        _ * cacheBuilder.withDisplayName(!null) >> cacheBuilder
+        _ * cacheBuilder.withLockOptions(!null) >> cacheBuilder
+        _ * cacheBuilder.open() >> Mock(PersistentCache)
+
+        expect:
+        services.get(TaskExecuter) instanceof ExecuteAtMostOnceTaskExecuter
+        services.get(TaskExecuter).is(services.get(TaskExecuter))
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/TaskScopeServicesTest.java b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/TaskScopeServicesTest.java
new file mode 100644
index 0000000..eda529d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/service/scopes/TaskScopeServicesTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.service.scopes;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.DefaultTaskInputs;
+import org.gradle.api.internal.tasks.DefaultTaskOutputs;
+import org.gradle.api.internal.tasks.TaskStatusNagger;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.logging.LoggingManagerInternal;
+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 junit.framework.Assert.assertSame;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class TaskScopeServicesTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final TaskInternal task = context.mock(TaskInternal.class);
+    private final TaskScopeServices registry = new TaskScopeServices(parent, project, task);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getFileResolver();
+            will(returnValue(context.mock(FileResolver.class)));
+        }});
+    }
+
+    @Test
+    public void createsATaskInputsInstance() {
+        TaskInputs inputs = registry.get(TaskInputs.class);
+        assertThat(inputs, instanceOf(DefaultTaskInputs.class));
+    }
+
+    @Test
+    public void createsATaskOutputsInternalInstance() {
+        TaskOutputsInternal outputs = registry.get(TaskOutputsInternal.class);
+        assertThat(outputs, instanceOf(DefaultTaskOutputs.class));
+    }
+
+    @Test
+    public void createsATaskStatusNaggerInstance() {
+        TaskStatusNagger nagger = registry.get(TaskStatusNagger.class);
+        assertSame(nagger, registry.get(TaskStatusNagger.class));
+    }
+
+    @Test
+    public void createsALoggingManagerAndStdOutputCapture() {
+        final Factory<LoggingManagerInternal> loggingManagerFactory = context.mock(Factory.class);
+        final LoggingManager loggingManager = context.mock(LoggingManagerInternal.class);
+
+        context.checking(new Expectations() {{
+            allowing(parent).getFactory(LoggingManagerInternal.class);
+            will(returnValue(loggingManagerFactory));
+            one(loggingManagerFactory).create();
+            will(returnValue(loggingManager));
+        }});
+
+        assertThat(registry.get(LoggingManager.class), sameInstance(loggingManager));
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/text/TreeFormatterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/text/TreeFormatterTest.groovy
new file mode 100644
index 0000000..d6d9914
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/text/TreeFormatterTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.text
+
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class TreeFormatterTest extends Specification {
+    def formatter = new TreeFormatter()
+
+    def "formats single node"() {
+        when:
+        formatter.node("Some thing.")
+
+        then:
+        formatter.toString() == "Some thing."
+    }
+
+    def "formats root with no children"() {
+        when:
+        formatter.node("Some thing.")
+        formatter.startChildren()
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == "Some thing."
+    }
+
+    def "formats root with single leaf child"() {
+        when:
+        formatter.node("Some things")
+        formatter.startChildren()
+        formatter.node("child 1")
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == 'Some things: child 1'
+    }
+
+    def "formats root with single nested leaf child"() {
+        when:
+        formatter.node("Some things")
+        formatter.startChildren()
+        formatter.node("child 1")
+        formatter.startChildren()
+        formatter.node("child 2")
+        formatter.endChildren()
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == toPlatformLineSeparators("""Some things:
+  - child 1: child 2""")
+    }
+
+    def "formats root with single child with multiple children"() {
+        when:
+        formatter.node("Some things")
+        formatter.startChildren()
+        formatter.node("child 1")
+        formatter.startChildren()
+        formatter.node("child 1.1")
+        formatter.node("child 1.2")
+        formatter.endChildren()
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == toPlatformLineSeparators("""Some things: child 1:
+  - child 1.1
+  - child 1.2""")
+    }
+
+    def "formats root with multiple children"() {
+        when:
+        formatter.node("Some things")
+        formatter.startChildren()
+        formatter.node("child 1")
+        formatter.node("child 2")
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == toPlatformLineSeparators("""Some things:
+  - child 1
+  - child 2""")
+    }
+
+    def "formats nested children"() {
+        when:
+        formatter.node("Some things")
+        formatter.startChildren()
+        formatter.node("child 1")
+        formatter.startChildren()
+        formatter.node("child 1.1")
+        formatter.node("child 1.2")
+        formatter.endChildren()
+        formatter.node("child 2")
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == toPlatformLineSeparators("""Some things:
+  - child 1:
+      - child 1.1
+      - child 1.2
+  - child 2""")
+    }
+
+    def "indents nested children that span multiple lines"() {
+        when:
+        formatter.node(toPlatformLineSeparators("Multiple\nlines"))
+        formatter.startChildren()
+        formatter.node("child 1")
+        formatter.startChildren()
+        formatter.node(toPlatformLineSeparators("multiple\nlines"))
+        formatter.node(toPlatformLineSeparators("another\nchild"))
+        formatter.endChildren()
+        formatter.node(toPlatformLineSeparators("one\ntwo"))
+        formatter.endChildren()
+
+        then:
+        formatter.toString() == toPlatformLineSeparators("""Multiple
+lines:
+  - child 1:
+      - multiple
+        lines
+      - another
+        child
+  - one
+    two""")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/CharSequenceNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/CharSequenceNotationParserTest.groovy
new file mode 100644
index 0000000..0d4c3dd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/CharSequenceNotationParserTest.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.internal.typeconversion
+
+import spock.lang.Specification
+
+class CharSequenceNotationParserTest extends Specification {
+    def parser = new CharSequenceNotationParser()
+
+    def "handles Strings"() {
+        expect:
+        converts("abc", "abc")
+    }
+
+    def "handles GStrings"() {
+        expect:
+        def foo = "abc"
+        converts("$foo", "abc")
+    }
+
+    def "handles StringBuilders"() {
+        def builder = new StringBuilder()
+        builder.append("abc")
+
+        expect:
+        converts(builder, "abc")
+    }
+
+    void converts(from, to) {
+        assert parser.parseNotation(from).getClass() == String
+        assert parser.parseNotation(from) == to
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/ClosureToSpecNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/ClosureToSpecNotationParserTest.groovy
new file mode 100644
index 0000000..f73bd91
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/ClosureToSpecNotationParserTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion
+
+import spock.lang.Specification
+
+class ClosureToSpecNotationParserTest 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/internal/typeconversion/EnumFromCharSequenceNotationParserSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/EnumFromCharSequenceNotationParserSpec.groovy
new file mode 100644
index 0000000..3a84eee
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/EnumFromCharSequenceNotationParserSpec.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion
+
+import spock.lang.Specification
+
+class EnumFromCharSequenceNotationParserSpec extends Specification{
+
+    NotationParser<Object, TestEnum> parser = new EnumFromCharSequenceNotationParser(TestEnum.class);
+
+    def "can convert strings to enums"(){
+        expect:
+        TestEnum.ENUM1 == parser.parseNotation("ENUM1")
+        TestEnum.ENUM1 == parser.parseNotation("enum1")
+        TestEnum.ENUM1 == parser.parseNotation("EnUm1")
+        TestEnum.ENUM2 == parser.parseNotation("enum2")
+    }
+
+    def "throws decent error for non convertable strings"(){
+        when:
+        parser.parseNotation("notKnown")
+        then:
+        def e = thrown(TypeConversionException)
+        e.message == "Cannot coerce string value 'notKnown' to an enum value of type 'org.gradle.internal.typeconversion.EnumFromCharSequenceNotationParserSpec\$TestEnum' (valid case insensitive values: [ENUM1, ENUM2, ENUM3])"
+    }
+
+    static enum TestEnum {
+        ENUM1, ENUM2, ENUM3
+    }
+
+}
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/ErrorHandlingNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/ErrorHandlingNotationParserTest.groovy
new file mode 100644
index 0000000..2ab5c5e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/ErrorHandlingNotationParserTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.typeconversion
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ErrorHandlingNotationParserTest extends Specification {
+    def NotationParser<String, String> target = Mock()
+    def parser = new ErrorHandlingNotationParser<String, String>("String", "<broken>", target)
+
+    def "reports unable to parse null"() {
+        when:
+        parser.parseNotation(null)
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == toPlatformLineSeparators('''Cannot convert a null value to an object of type String.
+The following types/formats are supported:
+  - format 1
+  - format 2
+<broken>''')
+
+        1 * target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
+        0 * target._  //no parsing
+    }
+
+    def "reports unable to parse non-null"() {
+        given:
+        target.parseNotation("bad") >> { throw new UnsupportedNotationException("broken-part") }
+        target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
+
+        when:
+        parser.parseNotation("bad")
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == toPlatformLineSeparators('''Cannot convert the provided notation to an object of type String: broken-part.
+The following types/formats are supported:
+  - format 1
+  - format 2
+<broken>''')
+
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/MapNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/MapNotationParserTest.groovy
new file mode 100644
index 0000000..474a474
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/MapNotationParserTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.typeconversion
+
+import spock.lang.Specification
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.tasks.Optional
+
+class MapNotationParserTest extends Specification {
+    final DummyParser parser = new DummyParser()
+    
+    def "parses map with required keys"() {
+        expect:
+        def object = parser.parseNotation([name: 'name', version: 'version'])
+        object.key1 == 'name'
+        object.key2 == 'version'
+        object.prop1 == null
+    }
+
+    def "parses map with required and optional keys"() {
+        expect:
+        def object = parser.parseNotation([name: 'name', version: 'version', optional: '1.2'])
+        object.key1 == 'name'
+        object.key2 == 'version'
+        object.optional == '1.2'
+        object.prop1 == null
+    }
+
+    def "configures properties of converted object using extra properties"() {
+        expect:
+        def object = parser.parseNotation([name: 'name', version: 'version', prop1: 'prop1', optional: '1.2'])
+        object.key1 == 'name'
+        object.key2 == 'version'
+        object.prop1 == 'prop1'
+    }
+
+    def "does not mutate original map"() {
+        def source = [name: 'name', version: 'version', prop1: 'prop1', optional: '1.2']
+        def copy = new HashMap<String, Object>(source)
+        
+        when:
+        parser.parseNotation(source)
+        
+        then:
+        source == copy
+    }
+
+    def "does not parse map with missing keys"() {
+        when:
+        parser.parseNotation([name: 'name'])
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == 'Required keys [version] are missing from map {name=name}.'
+    }
+
+    def "treats empty strings and null values as missing"() {
+        when:
+        parser.parseNotation([name: null, version: ''])
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message.startsWith 'Required keys [name, version] are missing from map '
+    }
+
+    def "does not parse map with unknown extra properties"() {
+        when:
+        parser.parseNotation([name: 'name', version: 1.2, unknown: 'unknown'])
+
+        then:
+        MissingFieldException e = thrown()
+    }
+
+    def "does not parse notation that is not a map"() {
+        when:
+        parser.parseNotation('string')
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+    
+    static class DummyParser extends MapNotationParser<TargetObject> {
+        protected TargetObject parseMap(@MapKey('name') String name,
+                                        @MapKey('version') String version,
+                                        @Optional @MapKey('optional') optional) {
+            return new TargetObject(key1:  name, key2:  version, optional:  optional)
+        }
+    }
+
+    static class TargetObject {
+        String key1;
+        String key2;
+        String optional;
+        String prop1;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/NotationParserBuilderSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/NotationParserBuilderSpec.groovy
new file mode 100644
index 0000000..727e6e0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/NotationParserBuilderSpec.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class NotationParserBuilderSpec extends Specification {
+
+    def "adds just return parser as default"(){
+        when:
+        def parser = new NotationParserBuilder<String>()
+                .resultingType(String.class).toComposite()
+        then:
+        "Some String" == parser.parseNotation("Some String")
+    }
+
+    def "can build with no just return parser"(){
+        setup:
+        def parser = new NotationParserBuilder<String>()
+                .resultingType(String.class)
+                .withDefaultJustReturnParser(false).toComposite()
+        when:
+        parser.parseNotation("Some String")
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message == toPlatformLineSeparators("""Cannot convert the provided notation to an object of type String: Some String.
+The following types/formats are supported:""")
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/TimeUnitsParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/TimeUnitsParserTest.groovy
new file mode 100644
index 0000000..c1d9512
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/TimeUnitsParserTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.typeconversion
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS
+import static java.util.concurrent.TimeUnit.NANOSECONDS
+
+class TimeUnitsParserTest extends Specification {
+
+    def parser = new TimeUnitsParser()
+
+    def "parses time units"() {
+        expect:
+        def unit = parser.parseNotation(input, value)
+        unit.value == normalizedValue
+        unit.timeUnit == parsed
+
+        where:
+        value           |input          |parsed         | normalizedValue
+        10              |'nanoseconds'  |NANOSECONDS    |10
+        20              |'mILLISECONds' |MILLISECONDS   |20
+        1               |'days'         |MILLISECONDS   |1 * 24 * 60 * 60 * 1000
+        2               |'hours'        |MILLISECONDS   |2 * 60 * 60 * 1000
+        5               |'MINUTES'      |MILLISECONDS   |5 * 60 * 1000
+    }
+
+    def "fails gracefully for invalid input"() {
+        when:
+        parser.parseNotation("foobar", 133)
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains("foobar")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/TypedNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/TypedNotationParserTest.groovy
new file mode 100644
index 0000000..beaebd9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/typeconversion/TypedNotationParserTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.typeconversion;
+
+
+import spock.lang.Specification
+
+public class TypedNotationParserTest extends Specification {
+
+    def parser = new DummyParser();
+
+    def "parses object of source type"(){
+        expect:
+        parser.parseNotation("100") == 100
+    }
+
+    def "throws meaningful exception on parse attempt"(){
+        when:
+        parser.parseNotation(new Object())
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+
+    class DummyParser extends TypedNotationParser<String, Integer> {
+
+        DummyParser() {
+            super(String.class)
+        }
+
+        Integer parseType(String notation) {
+            return Integer.valueOf(notation);
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java b/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
index f9c084e..54fd2dc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
@@ -24,20 +24,24 @@ import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.plugins.PluginRegistry;
-import org.gradle.api.internal.project.IProjectRegistry;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.api.invocation.Gradle;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.configuration.ScriptPluginFactory;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.internal.classloader.MultiParentClassLoader;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.ServiceRegistryFactory;
 import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.GradleVersion;
-import org.gradle.util.HelperUtil;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.MultiParentClassLoader;
+import org.gradle.util.TestUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -59,8 +63,7 @@ public class DefaultGradleTest {
     private final StartParameter parameter = new StartParameter();
     private final ScriptHandler scriptHandlerMock = context.mock(ScriptHandler.class);
     private final ServiceRegistryFactory serviceRegistryFactoryMock = context.mock(ServiceRegistryFactory.class, "parent");
-    private final ServiceRegistryFactory gradleServiceRegistryMock = context.mock(ServiceRegistryFactory.class, "gradle");
-    private final IProjectRegistry projectRegistry = context.mock(IProjectRegistry.class);
+    private final ServiceRegistry gradleServiceRegistryMock = context.mock(ServiceRegistry.class, "gradle");
     private final PluginRegistry pluginRegistry = context.mock(PluginRegistry.class);
     private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
     private final ListenerManager listenerManager = context.mock(ListenerManager.class);
@@ -69,6 +72,12 @@ public class DefaultGradleTest {
     private final GradleDistributionLocator gradleDistributionLocatorMock = context.mock(GradleDistributionLocator.class);
     private final ListenerBroadcast<BuildListener> buildListenerBroadcast = new ListenerBroadcast<BuildListener>(BuildListener.class);
     private final ListenerBroadcast<ProjectEvaluationListener> projectEvaluationListenerBroadcast = context.mock(ListenerBroadcast.class);
+    private final FileResolver fileResolverMock = context.mock(FileResolver.class);
+    private final PluginContainer pluginContainer = context.mock(PluginContainer.class);
+    private final ScriptPluginFactory scriptPluginFactory = context.mock(ScriptPluginFactory.class);
+    private final ScriptHandlerFactory scriptHandlerFactory = context.mock(ScriptHandlerFactory.class);
+    private final ClassLoaderScope classLoaderScope = context.mock(ClassLoaderScope.class);
+
     private DefaultGradle gradle;
 
     @Before
@@ -78,10 +87,8 @@ public class DefaultGradleTest {
             will(returnValue(gradleServiceRegistryMock));
             allowing(gradleServiceRegistryMock).get(ScriptHandler.class);
             will(returnValue(scriptHandlerMock));
-            allowing(gradleServiceRegistryMock).get(ScriptClassLoaderProvider.class);
-            will(returnValue(context.mock(ScriptClassLoaderProvider.class)));
-            allowing(gradleServiceRegistryMock).get(IProjectRegistry.class);
-            will(returnValue(projectRegistry));
+            allowing(gradleServiceRegistryMock).get(ClassLoaderScope.class);
+            will(returnValue(classLoaderScope));
             allowing(gradleServiceRegistryMock).get(PluginRegistry.class);
             will(returnValue(pluginRegistry));
             allowing(gradleServiceRegistryMock).get(TaskGraphExecuter.class);
@@ -92,6 +99,14 @@ public class DefaultGradleTest {
             will(returnValue(scriptClassLoaderMock));
             allowing(gradleServiceRegistryMock).get(GradleDistributionLocator.class);
             will(returnValue(gradleDistributionLocatorMock));
+            allowing(gradleServiceRegistryMock).get(PluginContainer.class);
+            will(returnValue(pluginContainer));
+            allowing(gradleServiceRegistryMock).get(FileResolver.class);
+            will(returnValue(fileResolverMock));
+            allowing(gradleServiceRegistryMock).get(ScriptPluginFactory.class);
+            will(returnValue(scriptPluginFactory));
+            allowing(gradleServiceRegistryMock).get(ScriptHandlerFactory.class);
+            will(returnValue(scriptHandlerFactory));
             allowing(listenerManager).createAnonymousBroadcaster(BuildListener.class);
             will(returnValue(buildListenerBroadcast));
             allowing(listenerManager).createAnonymousBroadcaster(ProjectEvaluationListener.class);
@@ -104,7 +119,6 @@ public class DefaultGradleTest {
     public void defaultValues() {
         assertThat(gradle.getParent(), sameInstance(parent));
         assertThat(gradle.getServices(), sameInstance(gradleServiceRegistryMock));
-        assertThat(gradle.getProjectRegistry(), sameInstance(projectRegistry));
         assertThat(gradle.getTaskGraph(), sameInstance(taskExecuter));
     }
 
@@ -145,7 +159,7 @@ public class DefaultGradleTest {
 
     @Test
     public void broadcastsBeforeProjectEvaluateEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = TestUtil.TEST_CLOSURE;
         context.checking(new Expectations() {{
             one(projectEvaluationListenerBroadcast).add(new ClosureBackedMethodInvocationDispatch("beforeEvaluate", closure));
         }});
@@ -155,7 +169,7 @@ public class DefaultGradleTest {
 
     @Test
     public void broadcastsAfterProjectEvaluateEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = TestUtil.TEST_CLOSURE;
         context.checking(new Expectations() {{
             one(projectEvaluationListenerBroadcast).add(new ClosureBackedMethodInvocationDispatch("afterEvaluate", closure));
         }});
@@ -240,10 +254,10 @@ public class DefaultGradleTest {
 
         ProjectInternal rootProject = context.mock(ProjectInternal.class);
         gradle.setRootProject(rootProject);
-        
+
         assertThat(gradle.getRootProject(), sameInstance(rootProject));
     }
-    
+
     @Test
     public void rootProjectActionIsExecutedWhenProjectsAreLoaded() {
         final Action<Project> action = context.mock(Action.class);
@@ -254,7 +268,7 @@ public class DefaultGradleTest {
         context.checking(new Expectations() {{
             one(action).execute(rootProject);
         }});
-        
+
         gradle.setRootProject(rootProject);
         gradle.getBuildListenerBroadcaster().projectsLoaded(gradle);
     }
@@ -279,7 +293,7 @@ public class DefaultGradleTest {
         assertThat(gradle.toString(), equalTo("build"));
 
         final ProjectInternal project = context.mock(ProjectInternal.class);
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(project).getName();
             will(returnValue("rootProject"));
         }});
@@ -289,7 +303,7 @@ public class DefaultGradleTest {
 
     private Closure closure() {
         final Closure mock = context.mock(Closure.class);
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(mock).getMaximumNumberOfParameters();
             will(returnValue(0));
         }});
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy b/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
index 1e4f2eb..44ba62b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
@@ -22,7 +22,7 @@ class ActionBroadcastTest extends Specification {
     final ActionBroadcast<String> broadcast = new ActionBroadcast<String>()
 
     def broadcastsEventsToAction() {
-        Action<String> action = Mock()
+        def action = Mock(Action)
         broadcast.add(action)
 
         when:
@@ -33,4 +33,55 @@ class ActionBroadcastTest extends Specification {
         0 * action._
     }
 
+    def broadcastsEventsToMultipleActions() {
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+        broadcast.add(action1)
+        broadcast.add(action2)
+
+        when:
+        broadcast.execute('value')
+
+        then:
+        1 * action1.execute('value')
+        1 * action2.execute('value')
+        0 * _._
+    }
+
+    def broadcastsEventsToMultipleActionsStopsOnFirstFailure() {
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+        def action3 = Mock(Action)
+        def failure = new RuntimeException()
+
+        broadcast.add(action1)
+        broadcast.add(action2)
+        broadcast.add(action3)
+
+        when:
+        broadcast.execute('value')
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
+        1 * action1.execute('value')
+        1 * action2.execute('value') >> { throw failure }
+        0 * _._
+    }
+
+    def actionCanAddOtherActions() {
+        def action1 = Mock(Action)
+        def action2 = Mock(Action)
+        broadcast.add(action1)
+
+        when:
+        broadcast.execute('value')
+
+        then:
+        1 * action1.execute('value') >> { broadcast.add(action2) }
+        0 * _._
+    }
+
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java b/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
index 90eb842..2332720 100644
--- a/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
@@ -185,40 +185,19 @@ public class ListenerBroadcastTest {
     }
 
     @Test
-    public void wrapsExceptionThrownByListener() {
+    public void wrapsCheckedExceptionThrownByListener() throws Exception {
         final TestListener listener = context.mock(TestListener.class);
-        final RuntimeException failure = new RuntimeException();
-
-        context.checking(new Expectations() {{
-            one(listener).event1("param");
-            will(throwException(failure));
-        }});
-
-        broadcast.add(listener);
-
-        try {
-            broadcast.getSource().event1("param");
-            fail();
-        } catch (ListenerNotificationException e) {
-            assertThat(e.getMessage(), equalTo("Failed to notify test listener."));
-            assertThat(e.getCause(), sameInstance((Throwable) failure));
-        }
-    }
-
-    @Test
-    public void dispatchWrapsExceptionThrownByListener() throws NoSuchMethodException {
-        final TestListener listener = context.mock(TestListener.class);
-        final RuntimeException failure = new RuntimeException();
+        final Exception failure = new Exception();
 
         context.checking(new Expectations() {{
-            one(listener).event1("param");
+            one(listener).event3();
             will(throwException(failure));
         }});
 
         broadcast.add(listener);
 
         try {
-            broadcast.dispatch(new MethodInvocation(TestListener.class.getMethod("event1", String.class), new Object[]{"param"}));
+            broadcast.getSource().event3();
             fail();
         } catch (ListenerNotificationException e) {
             assertThat(e.getMessage(), equalTo("Failed to notify test listener."));
@@ -244,8 +223,8 @@ public class ListenerBroadcastTest {
         try {
             broadcast.getSource().event1("param");
             fail();
-        } catch (ListenerNotificationException e) {
-            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
         }
     }
 
@@ -254,13 +233,14 @@ public class ListenerBroadcastTest {
         final TestListener listener1 = context.mock(TestListener.class);
         final TestListener listener2 = context.mock(TestListener.class);
         final TestListener listener3 = context.mock(TestListener.class);
-        final RuntimeException failure = new RuntimeException();
+        final RuntimeException failure1 = new RuntimeException();
+        final RuntimeException failure2 = new RuntimeException();
 
         context.checking(new Expectations() {{
             one(listener1).event1("param");
-            will(throwException(failure));
+            will(throwException(failure1));
             one(listener2).event1("param");
-            will(throwException(new RuntimeException()));
+            will(throwException(failure2));
             one(listener3).event1("param");
         }});
 
@@ -272,7 +252,9 @@ public class ListenerBroadcastTest {
             broadcast.getSource().event1("param");
             fail();
         } catch (ListenerNotificationException e) {
-            assertThat(e.getCause(), sameInstance((Throwable) failure));
+            assertThat(e.getCauses().size(), equalTo(2));
+            assertThat(e.getCauses().get(0), sameInstance((Throwable) failure1));
+            assertThat(e.getCauses().get(1), sameInstance((Throwable) failure2));
         }
     }
 
@@ -280,5 +262,7 @@ public class ListenerBroadcastTest {
         void event1(String param);
 
         void event2(int value, String other);
+
+        void event3() throws Exception;
     }
 }
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 6266ec0..4394931 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
@@ -16,13 +16,15 @@
 package org.gradle.logging.internal
 
 import org.gradle.internal.nativeplatform.console.ConsoleMetaData
+import spock.lang.Subject
 
 class ConsoleBackedProgressRendererTest extends OutputSpecification {
-    private final OutputEventListener listener = Mock()
-    private final Console console = Mock()
-    private final Label statusBar = Mock()
-    private final StatusBarFormatter statusBarFormatter = new DefaultStatusBarFormatter(Mock(ConsoleMetaData))
-    private final ConsoleBackedProgressRenderer renderer = new ConsoleBackedProgressRenderer(listener, console, statusBarFormatter)
+    def listener = Mock(OutputEventListener)
+    def console = Mock(Console)
+    def statusBar = Mock(Label)
+    def statusBarFormatter = new DefaultStatusBarFormatter(Mock(ConsoleMetaData))
+
+    @Subject renderer = new ConsoleBackedProgressRenderer(listener, console, statusBarFormatter)
 
     def setup() {
         (0..1) * console.getStatusBar() >> statusBar
@@ -206,4 +208,13 @@ class ConsoleBackedProgressRendererTest extends OutputSpecification {
         1 * statusBar.setText('')
         0 * statusBar._
     }
+
+    def "failure to process the event contains the context"() {
+        when:
+        renderer.onOutput(complete('unstarted operation'))
+
+        then:
+        def e = thrown(RuntimeException)
+        e.message.contains('unstarted operation')
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java
index ebc82e4..920e5c3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java
@@ -25,11 +25,9 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultLoggingManagerTest {
     @Rule
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy
index f932b8b..336ce8d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy
@@ -237,5 +237,15 @@ class DefaultProgressLoggerFactoryTest extends Specification {
         IllegalStateException e = thrown()
         e.message == 'This operation has completed.'
     }
+
+    def "can log start conveniently"() {
+        when:
+        def logger = factory.newOperation('logger').start("foo", "f")
+
+        then:
+        logger.description == "foo"
+        logger.shortDescription == "f"
+        1 * progressListener.started(!null)
+    }
 }
 
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
index 0a97f3d..09ca716 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStatusBarFormatterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStatusBarFormatterTest.groovy
@@ -16,29 +16,42 @@
 
 package org.gradle.logging.internal
 
-import spock.lang.Specification
 import org.gradle.internal.nativeplatform.console.ConsoleMetaData
+import org.gradle.logging.internal.progress.ProgressOperation
+import spock.lang.Specification
+import spock.lang.Subject
 
 class DefaultStatusBarFormatterTest extends Specification {
 
-    ConsoleMetaData consoleMetaData = Mock()
-    private final StatusBarFormatter statusBarFormatter = new DefaultStatusBarFormatter(consoleMetaData)
+    def consoleMetaData = Mock(ConsoleMetaData)
+    @Subject statusBarFormatter = new DefaultStatusBarFormatter(consoleMetaData)
+
+    def "formats operations"() {
+        def op1 = new ProgressOperation("shortDescr1", "status1", null)
+        def op2 = new ProgressOperation(null, null, op1)
+        def op3 = new ProgressOperation("shortDescr2", "status2", op2)
 
-    def "formats multiple operations"(){
         expect:
-        "> status1 > status2" == statusBarFormatter.format(Arrays.asList(new ConsoleBackedProgressRenderer.Operation("shortDescr1", "status1"), new ConsoleBackedProgressRenderer.Operation("shortDescr2", "status2")))
+        statusBarFormatter.format(op3) == "> status1 > status2"
+        statusBarFormatter.format(op2) == "> status1"
+        statusBarFormatter.format(op1) == "> status1"
     }
 
-    def "uses shortDescr if no status available"(){
+    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", '')))
+        statusBarFormatter.format(new ProgressOperation("shortDescr1", null, null)) == "> shortDescr1"
+        statusBarFormatter.format(new ProgressOperation("shortDescr2", '', null)) == "> shortDescr2"
     }
 
-    def "trims output to one less than the max console width"(){
+    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")))
+        statusBarFormatter.format(new ProgressOperation("shortDescr1", "these are more than 10 characters", null)) == "> these a"
+    }
+
+    def "empty message is supported"() {
+        expect:
+        statusBarFormatter.format(new ProgressOperation(null, null, null)) == ""
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy
index 413503b..d1581e2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy
@@ -51,10 +51,22 @@ class LoggingBackedStyledTextOutputTest extends OutputSpecification {
 
     def buffersTextUntilEndOfLineReached() {
         when:
-        output.text('message ').text(toNative('with more\nanother ')).text('line').println()
+        output.text('message ')
+
+        then:
+        0 * listener._
+
+        when:
+        output.text(toNative('with more\nanother '))
 
         then:
         1 * listener.onOutput({it.spans[0].text == toNative('message with more\n')})
+        0 * listener._
+
+        when:
+        output.text('line').println()
+
+        then:
         1 * listener.onOutput({it.spans[0].text == toNative('another line\n')})
         0 * listener._
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy
index 0ee9f1e..e77d3cf 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy
@@ -61,7 +61,7 @@ class LoggingCommandLineConverterTest extends Specification {
     }
 
     def convertsShowStacktrace() {
-        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS;
+        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS
 
         expect:
         checkConversion(['-s'])
@@ -69,17 +69,25 @@ class LoggingCommandLineConverterTest extends Specification {
     }
 
     def convertsShowFullStacktrace() {
-        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS_FULL;
+        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS_FULL
 
         expect:
         checkConversion(['-S'])
         checkConversion(['--full-stacktrace'])
     }
 
+    def usesLastLogLevelAndStacktraceOption() {
+        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS_FULL
+        expectedConfig.logLevel = LogLevel.QUIET
+
+        expect:
+        checkConversion(['-s', '--debug', '-q', '--full-stacktrace'])
+    }
+
     def providesLogLevelOptions() {
         expect:
-        converter.logLevelOptions.containsAll(["d", "q", "i"])
-        converter.logLevelOptions.size() == 3
+        converter.logLevelOptions == ["d", "q", "i"] as Set
+        converter.logLevels == [LogLevel.DEBUG, LogLevel.INFO, LogLevel.LIFECYCLE, LogLevel.QUIET] as Set
     }
 
     void checkConversion(List<String> args) {
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 77fa55e..7e01f7b 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
@@ -15,12 +15,16 @@
  */
 package org.gradle.logging.internal
 
-import spock.lang.Specification
 import org.gradle.api.logging.LogLevel
 import org.gradle.util.TextUtil
+import spock.lang.Specification
+
 import java.text.SimpleDateFormat
 
 class OutputSpecification extends Specification {
+
+    private Long counter
+
     protected String toNative(String value) {
         return TextUtil.toPlatformLineSeparators(value)
     }
@@ -58,18 +62,20 @@ class OutputSpecification extends Specification {
     }
 
     ProgressStartEvent start(String description) {
-        return new ProgressStartEvent(tenAm, 'category', description, null, null, null)
+        start(description: description)
     }
 
     ProgressStartEvent start(Map args) {
-        return new ProgressStartEvent(tenAm, 'category', args.description, args.shortDescription, args.loggingHeader, args.status)
+        Long parent = counter
+        long id = counter == null? counter = 1 : ++counter
+        return new ProgressStartEvent(id, parent, tenAm, 'category', args.description, args.shortDescription, args.loggingHeader, args.status)
     }
 
     ProgressEvent progress(String status) {
-        return new ProgressEvent(tenAm, 'category', status)
+        return new ProgressEvent(counter? counter:1, tenAm, 'category', status)
     }
 
     ProgressCompleteEvent complete(String status) {
-        return new ProgressCompleteEvent(tenAm, 'category', 'description', status)
+        return new ProgressCompleteEvent(counter? counter--:1, tenAm, 'category', 'description', status)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/progress/ProgressOperationsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/progress/ProgressOperationsTest.groovy
new file mode 100644
index 0000000..67b5bd8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/progress/ProgressOperationsTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.progress
+
+import spock.lang.Specification
+
+class ProgressOperationsTest extends Specification {
+
+    def ops = new ProgressOperations()
+
+    def "starts operation"() {
+        when:
+        def op = ops.start("compile", null, 1, null)
+
+        then:
+        op.parent == null
+        op.message == "compile"
+    }
+
+    def "starts operations"() {
+        when:
+        def op1 = ops.start("compile", null, 1, null)
+        def op2 = ops.start("resolve", null, 2, 1)
+
+        then:
+        op1.message == "compile"
+        op1.parent == null
+        op2.message == "resolve"
+        op2.parent == op1
+    }
+
+    def "operation can be started multiple times"() {
+        expect:
+        ops.start("compile", null, 1, null).message == "compile"
+        ops.progress("compiling...", 1).message == "compiling..."
+        ops.start("resolve", null, 1, null).message == "resolve"
+        ops.progress("resolving...", 1).message == "resolving..."
+        ops.complete(1).message == "resolving..."
+    }
+
+    def "starts operations from different hierarchies"() {
+        when:
+        def op1 = ops.start("compile", null, 1, null)
+        def op2 = ops.start("resolve", null, 2, null)
+
+        then:
+        op1.message == "compile"
+        op1.parent == null
+        op2.message == "resolve"
+        op2.parent == null
+    }
+
+    def "the operation uses status first"() {
+        expect:
+        ops.start("foo", "compiling now", 1, null).message == "compiling now"
+    }
+
+    def "tracks progress"() {
+        when:
+        ops.start("Building", "", 1, null)
+        def op2 = ops.start("Resolving", "", 2, 1)
+        def op3 = ops.progress("Download", 2)
+
+        then:
+        op2 == op3
+        op2.message == "Download"
+        op2.parent.message == "Building"
+    }
+
+    def "progress cannot be reported for unknown operation"() {
+        when:
+        ops.progress("Download", 2)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "completed events are no longer tracked"() {
+        when:
+        ops.start("Building", "", 1, null)
+        ops.start("Resolving", "", 2, 1)
+        def op3 = ops.progress("Download", 2)
+        def op4 = ops.complete(2)
+
+        then:
+        op3 == op4
+
+        when:
+        ops.progress("foo", 2)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "missing parents are tolerated"() {
+        when:
+        def op = ops.start("Building", "", 1, 112)
+
+        then:
+        op.parent == null
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/model/dsl/internal/GroovyModelDslTest.groovy b/subprojects/core/src/test/groovy/org/gradle/model/dsl/internal/GroovyModelDslTest.groovy
new file mode 100644
index 0000000..c286708
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/model/dsl/internal/GroovyModelDslTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.dsl.internal
+
+import org.gradle.model.internal.DefaultModelRegistry
+import org.gradle.model.internal.ModelRegistryBackedModelRules
+import spock.lang.Specification
+
+class GroovyModelDslTest extends Specification {
+
+    def modelRegistry = new DefaultModelRegistry()
+    def modelRules = new ModelRegistryBackedModelRules(modelRegistry)
+    def modelDsl = new GroovyModelDsl(modelRules)
+
+    def "can add rules via dsl"() {
+        given:
+        modelRules.register("foo", [])
+
+        when:
+        modelDsl.configure {
+            foo {
+                add 1
+            }
+        }
+
+        then:
+        modelRegistry.get("foo", List) == [1]
+    }
+
+    def "can use property accessors in DSL to build model object path"() {
+        given:
+        modelRules.register("foo.bar", [])
+
+        when:
+        modelDsl.configure {
+            foo.bar {
+                add 1
+            }
+        }
+
+        then:
+        modelRegistry.get("foo.bar", List) == [1]
+    }
+
+    def "does not add rules when not configuring"() {
+        given:
+        modelRules.register("foo", new TestObject())
+        modelRules.register("bah", new TestObject())
+
+        when:
+        modelDsl.configure {
+            foo {
+                defineSomeThing {
+                    unknown
+                }
+            }
+        }
+        modelRegistry.get("foo", Object)
+
+        then:
+        MissingPropertyException missingProp = thrown()
+        missingProp.property == 'unknown'
+
+        when:
+        modelDsl.configure {
+            bah {
+                defineSomeThing {
+                    unknown { }
+                }
+            }
+        }
+        modelRegistry.get("bah", Object)
+
+        then:
+        MissingMethodException missingMethod = thrown()
+        missingMethod.method == 'unknown'
+    }
+}
+
+class TestObject {
+    String prop
+
+    def defineSomeThing(Closure cl) {
+        cl.delegate = this
+        cl.call()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/model/internal/ModelRegistryBackedModelRulesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/model/internal/ModelRegistryBackedModelRulesTest.groovy
new file mode 100644
index 0000000..0ca5b31
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/model/internal/ModelRegistryBackedModelRulesTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.model.internal
+
+import org.gradle.api.Action
+import org.gradle.model.ModelFinalizer
+import org.gradle.model.ModelRule
+import spock.lang.Specification
+
+class ModelRegistryBackedModelRulesTest extends Specification {
+
+    static class ModelElement {
+        List<String> names = []
+    }
+
+    static class DerivedThing {
+        String name
+    }
+
+    def modelRegistry = new DefaultModelRegistry()
+    def rules = new ModelRegistryBackedModelRules(modelRegistry)
+
+    def "can configure by rules"() {
+        when:
+        rules.register("element", new ModelElement())
+        rules.register("things", [] as List<DerivedThing>)
+
+        3.times { int i ->
+            rules.rule(new ModelRule() {
+                void addName(ModelElement modelElement) {
+                    modelElement.names << "name$i"
+                }
+            })
+        }
+
+        rules.rule(new ModelRule() {
+            public void registerThings(List<DerivedThing> things, ModelElement element) {
+                element.names.each {
+                    things << new DerivedThing(name: it)
+                }
+            }
+        })
+
+        then:
+        List<DerivedThing> things = modelRegistry.get("things", List)
+        things*.name == ["name0", "name1", "name2"]
+    }
+
+    def "can configure by action"() {
+        when:
+        rules.register("element", new ModelElement())
+
+        3.times { int i ->
+            rules.config("element", { ModelElement it ->
+                    it.names << "name$i"
+            } as Action)
+        }
+
+
+        def element = modelRegistry.get("element", ModelElement)
+        then:
+        element
+        element.names == ["name0", "name1", "name2"]
+    }
+
+    def "can finalize"() {
+        when:
+        rules.register("element", new ModelElement())
+
+        rules.rule(new ModelFinalizer() {
+            void addFinal(ModelElement modelElement) {
+                modelElement.names << "final"
+            }
+        })
+
+        3.times { int i ->
+            rules.config("element", { ModelElement it ->
+                it.names << "name$i"
+            } as Action)
+        }
+
+
+        def element = modelRegistry.get("element", ModelElement)
+        then:
+        element
+        element.names == ["name0", "name1", "name2", "final"]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/plugin/bintray/JCenterPluginMapperSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/plugin/bintray/JCenterPluginMapperSpec.groovy
new file mode 100644
index 0000000..3ef797b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/plugin/bintray/JCenterPluginMapperSpec.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.bintray
+
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.cache.PersistentIndexedCache
+import org.gradle.internal.Factories
+import org.gradle.internal.Supplier
+import org.gradle.internal.Suppliers
+import org.gradle.messaging.serialize.BaseSerializerFactory
+import org.gradle.plugin.resolve.internal.DefaultPluginRequest
+import org.gradle.plugin.resolve.internal.InvalidPluginRequestException
+import org.gradle.plugin.resolve.internal.JCenterPluginMapper
+import org.gradle.plugin.resolve.internal.PluginRequest
+import org.gradle.testfixtures.internal.InMemoryIndexedCache
+import spock.lang.Specification
+
+class JCenterPluginMapperSpec extends Specification {
+
+    public static final String TEST_PLUGIN_MAVEN_GROUP_ID = 'com.bintray.gradle.test'
+    public static final String TEST_PLUGIN_MAVEN_ARTIFACT_ID = 'test-plugin'
+    public static final String TEST_PLUGIN_EXPLICIT_VERSION = '1.0'
+    public static final String TEST_PLUGIN_ID = 'gradle-test-plugin'
+
+    private DependencyHandler getMockForVersion(String version) {
+        Mock(DependencyHandler) {
+            create("$TEST_PLUGIN_MAVEN_GROUP_ID:$TEST_PLUGIN_MAVEN_ARTIFACT_ID:$version") >> Stub(Dependency) {
+                getGroup() >> TEST_PLUGIN_MAVEN_GROUP_ID
+                getName() >> TEST_PLUGIN_MAVEN_ARTIFACT_ID
+                getVersion() >> version
+            }
+            0 * _ //fail if create called with any other string
+        }
+    }
+
+    PersistentIndexedCache<PluginRequest, String> cache = new InMemoryIndexedCache<PluginRequest, String>(BaseSerializerFactory.STRING_SERIALIZER)
+    Supplier<PersistentIndexedCache<PluginRequest, String>> cacheSupplier = Suppliers.of(Factories.constant(cache))
+    JCenterPluginMapper mapper = new JCenterPluginMapper(cacheSupplier)
+
+    def 'Latest version of plugin maps correctly from Bintray'() {
+        when:
+        def dependencyHandler = Mock(DependencyHandler)
+        0 * dependencyHandler._
+        mapper.map(new DefaultPluginRequest(TEST_PLUGIN_ID), dependencyHandler)
+
+        then:
+        def e = thrown InvalidPluginRequestException
+        e.message.startsWith "No version number supplied for plugin '$TEST_PLUGIN_ID'"
+    }
+
+    def 'Explicit version of plugin maps correctly from Bintray'() {
+        when:
+        Dependency dependency = mapper.map(new DefaultPluginRequest(TEST_PLUGIN_ID, TEST_PLUGIN_EXPLICIT_VERSION), getMockForVersion(TEST_PLUGIN_EXPLICIT_VERSION))
+
+        then:
+        dependency.group == TEST_PLUGIN_MAVEN_GROUP_ID
+        dependency.name == TEST_PLUGIN_MAVEN_ARTIFACT_ID
+        dependency.version == TEST_PLUGIN_EXPLICIT_VERSION
+    }
+
+    def 'Query for non-existing plugin returns null'() {
+        expect:
+        mapper.map(new DefaultPluginRequest("not-exist"), getMockForVersion(TEST_PLUGIN_EXPLICIT_VERSION)) == null
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/plugin/internal/DefaultPluginHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/plugin/internal/DefaultPluginHandlerTest.groovy
new file mode 100644
index 0000000..9c8d86f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/plugin/internal/DefaultPluginHandlerTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugin.internal
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.plugin.resolve.internal.DefaultPluginRequest
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DefaultPluginHandlerTest extends Specification {
+
+    def requests = []
+    def handler = new DefaultPluginHandler(requests)
+
+    @Unroll
+    def "errors on invalid notation - #map"() {
+        when:
+        handler.apply(map)
+
+        then:
+        thrown InvalidUserDataException
+
+        where:
+        map << [
+                [:],
+                [foo: "bar"],
+                [version: "1.0"],
+                [version: "1"]
+        ]
+    }
+
+    @Unroll
+    def "accepts valid notation and applies when resolved - #map"() {
+        when:
+        handler.apply(map)
+
+        then:
+        requests.first() == request
+
+        where:
+        map                             | request
+        [plugin: "foo"]                 | new DefaultPluginRequest("foo")
+        [plugin: "foo", version: "bar"] | new DefaultPluginRequest("foo", "bar")
+        [plugin: "foo", version: 1]     | new DefaultPluginRequest("foo", "1")
+        [plugin: "foo", version: []]    | new DefaultPluginRequest("foo", "[]")
+    }
+
+}
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 8707311..93f9470 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
@@ -28,9 +28,6 @@ import spock.lang.Timeout
 
 import java.util.concurrent.Callable
 
-/**
- * @author Tom Eyckmans, Szczepan Faber
- */
 @Timeout(60)
 class DefaultExecHandleSpec extends Specification {
     @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
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 2265f3c..611d890 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
@@ -82,7 +82,10 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
                 workerProcess.start()
                 fail()
             } catch (ExecException e) {
-                assertThat(e.message, equalTo("Timeout after waiting 1.0 seconds for $execHandle (STARTED, running: true) to connect." as String))
+                assertThat(e.message, equalTo(String.format("Unable to connect to the child process 'ExecHandle'.\n"
+                        + "It is likely that the child process have crashed - please find the stack trace in the build log.\n"
+                        + "This exception might occur when the build machine is extremely loaded.\n"
+                        + "The connection attempt hit a timeout after %.1f seconds (last known process state: STARTED, running: true)." as String, 1d)))
             }
         }
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
index 53c3f7f..db05100 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
@@ -13,20 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.process.internal;
+package org.gradle.process.internal
 
-
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.api.internal.file.TestFiles
 import org.gradle.internal.jvm.Jvm
 import spock.lang.Specification
-import static java.util.Arrays.asList
-import java.nio.charset.Charset
 import spock.lang.Unroll
 
+import java.nio.charset.Charset
+
+import static java.util.Arrays.asList
+
 public class JavaExecHandleBuilderTest extends Specification {
-    FileResolver fileResolver = new IdentityFileResolver()
-    JavaExecHandleBuilder builder = new JavaExecHandleBuilder(fileResolver)
+    JavaExecHandleBuilder builder = new JavaExecHandleBuilder(TestFiles.resolver())
     
     public void cannotSetAllJvmArgs() {
         when:
@@ -70,6 +69,11 @@ public class JavaExecHandleBuilderTest extends Specification {
         "UTF-16"      | "UTF-16"
     }
 
+    def "detects null entries early"() {
+        when: builder.args(1, null)
+        then: thrown(IllegalArgumentException)
+    }
+
     private String fileEncodingProperty(String encoding = Charset.defaultCharset().name()) {
         return "-Dfile.encoding=$encoding"
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
index 9f2837f..016a3f0 100755
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
@@ -18,14 +18,12 @@
 
 package org.gradle.process.internal
 
-import org.gradle.api.internal.file.IdentityFileResolver
-import spock.lang.Specification
+import org.gradle.api.internal.file.TestFiles
 import org.gradle.process.JavaForkOptions
+import spock.lang.Specification
+
 import java.nio.charset.Charset
 
-/**
- * by Szczepan Faber, created at: 2/13/12
- */
 class JvmOptionsTest extends Specification {
     final String defaultCharset = Charset.defaultCharset().name()
 
@@ -185,8 +183,28 @@ class JvmOptionsTest extends Specification {
         opts.allJvmArgs.containsAll(['-Xmx1G', '-Xms1G', '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'])
     }
 
+    def "options with newlines are parsed correctly"() {
+        def opts = createOpts()
+        when:
+        opts.jvmArgs('-Dprops=a:1\nb:2\nc:3')
+
+        then:
+        opts.allJvmArgs.contains('-Dprops=a:1\nb:2\nc:3')
+        opts.systemProperties['props'] == 'a:1\nb:2\nc:3'
+    }
+
+    def "options with Win newlines are parsed correctly"() {
+        def opts = createOpts()
+        when:
+        opts.jvmArgs('-Dprops=a:1\r\nb:2\r\nc:3')
+
+        then:
+        opts.allJvmArgs.contains('-Dprops=a:1\r\nb:2\r\nc:3')
+        opts.systemProperties['props'] == 'a:1\r\nb:2\r\nc:3'
+    }
+
     private JvmOptions createOpts() {
-        return new JvmOptions(new IdentityFileResolver())
+        return new JvmOptions(TestFiles.resolver())
     }
 
     private JvmOptions parse(String optsString) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
index 5847570..42ebb24 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
@@ -20,7 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.MutableURLClassLoader;
+import org.gradle.internal.classloader.MutableURLClassLoader;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy
index 252a31a..5061b8c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy
@@ -20,17 +20,15 @@ package org.gradle.process.internal.child
 
 import groovyjarjarasm.asm.ClassVisitor
 import groovyjarjarasm.asm.ClassWriter
-import java.util.concurrent.atomic.AtomicInteger
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.control.CompilationUnit
 import org.codehaus.groovy.control.CompilationUnit.ClassgenCallback
 import org.codehaus.groovy.control.Phases
 import org.gradle.api.Action
 
-class SerializableMockHelper { /**
- * @author Hans Dockter
- */
-    static final Map ACTIONS = [:]
+import java.util.concurrent.atomic.AtomicInteger
+
+class SerializableMockHelper {     static final Map ACTIONS = [:]
     private final AtomicInteger counter = new AtomicInteger()
 
     /**
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
index b6bf08c..d9df8f0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
@@ -17,8 +17,8 @@
 package org.gradle.process.internal.child
 
 import org.gradle.api.internal.classpath.ModuleRegistry
+import org.gradle.cache.CacheBuilder
 import org.gradle.cache.CacheRepository
-import org.gradle.cache.DirectoryCacheBuilder
 import org.gradle.cache.PersistentCache
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
@@ -38,7 +38,7 @@ class WorkerProcessClassPathProviderTest extends Specification {
     def createsTheWorkerClasspathOnDemand() {
         def cacheDir = tmpDir.testDirectory
         def jarFile = cacheDir.file('gradle-worker.jar')
-        DirectoryCacheBuilder cacheBuilder = Mock()
+        CacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
         def initializer = null
 
@@ -58,7 +58,7 @@ class WorkerProcessClassPathProviderTest extends Specification {
     def reusesTheCachedClasspath() {
         def cacheDir = tmpDir.testDirectory
         def jarFile = cacheDir.file('gradle-worker.jar')
-        DirectoryCacheBuilder cacheBuilder = Mock()
+        CacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
 
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy b/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy
index 68a51da..2d6ac7c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy
@@ -15,52 +15,82 @@
  */
 package org.gradle.profile
 
+import org.gradle.StartParameter
+import org.gradle.api.tasks.TaskState
 import spock.lang.Specification
-import org.gradle.api.invocation.Gradle
-import org.gradle.api.artifacts.ResolvableDependencies
-import org.gradle.api.Project
 
 class BuildProfileTest extends Specification {
-    final Gradle gradle = Mock()
-    final BuildProfile profile = new BuildProfile(gradle)
+    private profile = new BuildProfile(new StartParameter())
 
     def "creates dependency set profile on first get"() {
+        expect:
+        def dependencyProfile = profile.getDependencySetProfile("path")
+        dependencyProfile != null
+        profile.getDependencySetProfile("path") == dependencyProfile
+    }
+
+    def "provides sorted dependency set profiles"() {
         given:
-        ResolvableDependencies deps = dependencySet("path")
+        def a = profile.getDependencySetProfile("a").setStart(100).setFinish(200)
+        def b = profile.getDependencySetProfile("b").setStart(200).setFinish(400)
+        def c = profile.getDependencySetProfile("c").setStart(400).setFinish(600)
+        def d = profile.getDependencySetProfile("d").setStart(600).setFinish(601)
 
         expect:
-        def dependencyProfile = profile.getDependencySetProfile(deps)
-        dependencyProfile != null
-        profile.getDependencySetProfile(deps) == dependencyProfile
+        profile.dependencySets.operations == [b, c, a, d]
     }
 
-    def "can get all dependency set profiles"() {
+    def "provides sorted configuration profiles"() {
         given:
-        def a = profile.getDependencySetProfile(dependencySet("a"))
-        def b = profile.getDependencySetProfile(dependencySet("b"))
+        def a = profile.getProjectProfile("a").configurationOperation.setStart(100).setFinish(200)
+        def b = profile.getProjectProfile("b").configurationOperation.setStart(200).setFinish(500)
+        def c = profile.getProjectProfile("c").configurationOperation.setStart(500).setFinish(800)
+        def d = profile.getProjectProfile("d").configurationOperation.setStart(800).setFinish(850)
 
         expect:
-        profile.dependencySets.operations == [a, b]
+        profile.projectConfiguration.operations == [b, c, a, d]
     }
 
-    def "can get all project configuration profiles"() {
+    def "provides sorted project profiles"() {
         given:
-        def a = profile.getProjectProfile(project("a"))
-        def b = profile.getProjectProfile(project("b"))
+        profile.getProjectProfile("a").getTaskProfile("a:x").completed(Stub(TaskState)).setStart(100).setFinish(300)
+        profile.getProjectProfile("b").getTaskProfile("b:x").completed(Stub(TaskState)).setStart(300).setFinish(300)
+        profile.getProjectProfile("c").getTaskProfile("c:x").completed(Stub(TaskState)).setStart(300).setFinish(300)
+        profile.getProjectProfile("d").getTaskProfile("d:x").completed(Stub(TaskState)).setStart(301).setFinish(302)
 
         expect:
-        profile.projectConfiguration.operations == [a.evaluation, b.evaluation]
+        profile.projects == [profile.getProjectProfile("a"), profile.getProjectProfile("d"), profile.getProjectProfile("b"), profile.getProjectProfile("c")]
+    }
+
+    def "contains build description"() {
+        given:
+        def param = new StartParameter()
+        param.setTaskNames(["foo", "bar"])
+        param.setExcludedTaskNames(["one", "two"])
+
+        when:
+        profile = new BuildProfile(param)
+
+        then:
+        profile.buildDescription.contains(" -x one -x two foo bar")
     }
 
-    def dependencySet(String path) {
-        ResolvableDependencies dependencies = Mock()
-        _ * dependencies.path >> path
-        return dependencies
+    def "build description looks nice even if no tasks specified"() {
+        given:
+        def param = new StartParameter()
+
+        when:
+        profile = new BuildProfile(param)
+
+        then:
+        profile.buildDescription.contains(" (no tasks specified)")
     }
 
-    def project(String path) {
-        Project project = Mock()
-        _ * project.path >> path
-        return project
+    def "provides start time description"() {
+        when:
+        profile.buildStarted = new GregorianCalendar(2010, 1, 1, 12, 25).getTimeInMillis()
+
+        then:
+        profile.buildStartedDescription == "Started on: 2010/02/01 - 12:25:00"
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/profile/ProfileReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/profile/ProfileReportRendererTest.groovy
new file mode 100644
index 0000000..8fa5b15
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/profile/ProfileReportRendererTest.groovy
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.profile
+
+import org.gradle.StartParameter
+import org.gradle.api.tasks.TaskState
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ProfileReportRendererTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def "renders report"() {
+        def model = new BuildProfile(new StartParameter())
+        def file = temp.file("report.html")
+
+        model.profilingStarted   = time(12, 20, 0)
+        model.buildStarted       = time(12, 20, 0, 700)
+        model.settingsEvaluated  = time(12, 20, 3)
+        model.projectsLoaded     = time(12, 20, 6)
+
+        model.buildFinished      = time(12, 35, 30)
+
+        model.getDependencySetProfile("compile").start = time(12, 22, 0)
+        model.getDependencySetProfile("compile").finish = time(12, 23, 30)
+
+        model.getDependencySetProfile("runtime").start = time(12, 24, 0)
+        model.getDependencySetProfile("runtime").finish = time(12, 24, 30)
+
+        model.getProjectProfile("a").configurationOperation.start = time(12, 20, 7)
+        model.getProjectProfile("a").configurationOperation.finish = time(12, 20, 10)
+        model.getProjectProfile("a").getTaskProfile("a:foo").completed(Stub(TaskState)).setStart(time(12, 25, 0)).setFinish(time(12, 26, 30))
+        model.getProjectProfile("a").getTaskProfile("a:bar").completed(Stub(TaskState)).setStart(time(12, 26, 30)).setFinish(time(12, 27, 0))
+
+        model.getProjectProfile("b").configurationOperation.start = time(12, 20, 10)
+        model.getProjectProfile("b").configurationOperation.finish = time(12, 20, 15)
+        //let's say they run in parallel, hence same start time
+        model.getProjectProfile("b").getTaskProfile("b:foo").completed(Stub(TaskState)).setStart(time(12, 27, 0)).setFinish(time(12, 29, 30))
+        model.getProjectProfile("b").getTaskProfile("b:bar").completed(Stub(TaskState)).setStart(time(12, 27, 0)).setFinish(time(12, 29, 0))
+
+        when:
+        new ProfileReportRenderer().writeTo(model, file)
+
+        then:
+        println file
+        println file.text
+        file.text.contains(toPlatformLineSeparators("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>Profile report</title>
+<link href="css/base-style.css" rel="stylesheet" type="text/css"/>
+<link href="css/style.css" rel="stylesheet" type="text/css"/>
+<script src="js/report.js" type="text/javascript"></script>
+</head>
+<body>
+<div id="content">
+<h1>Profile report</h1>
+<div id="header">
+<p>Profiled build: (no tasks specified)</p>
+<p>Started on: 2010/02/05 - 12:20:00</p>
+</div>
+<div id="tabs">
+<ul class="tabLinks">
+<li>
+<a href="#tab0">Summary</a>
+</li>
+<li>
+<a href="#tab1">Configuration</a>
+</li>
+<li>
+<a href="#tab2">Dependency Resolution</a>
+</li>
+<li>
+<a href="#tab3">Task Execution</a>
+</li>
+</ul>
+<div class="tab" id="tab0">
+<h2>Summary</h2>
+<table>
+<thead>
+<tr>
+<th>Description</th>
+<th class="numeric">Duration</th>
+</tr>
+</thead>
+<tr>
+<td>Total Build Time</td>
+<td class="numeric">15m30.00s</td>
+</tr>
+<tr>
+<td>Startup</td>
+<td class="numeric">0.700s</td>
+</tr>
+<tr>
+<td>Settings and BuildSrc</td>
+<td class="numeric">2.300s</td>
+</tr>
+<tr>
+<td>Loading Projects</td>
+<td class="numeric">3.000s</td>
+</tr>
+<tr>
+<td>Configuring Projects</td>
+<td class="numeric">8.000s</td>
+</tr>
+<tr>
+<td>Task Execution</td>
+<td class="numeric">6m30.00s</td>
+</tr>
+</table>
+</div>
+<div class="tab" id="tab1">
+<h2>Configuration</h2>
+<table>
+<thead>
+<tr>
+<th>Project</th>
+<th class="numeric">Duration</th>
+</tr>
+</thead>
+<tr>
+<td>All projects</td>
+<td class="numeric">8.000s</td>
+</tr>
+<tr>
+<td>b</td>
+<td class="numeric">5.000s</td>
+</tr>
+<tr>
+<td>a</td>
+<td class="numeric">3.000s</td>
+</tr>
+</table>
+</div>
+<div class="tab" id="tab2">
+<h2>Dependency Resolution</h2>
+<table>
+<thead>
+<tr>
+<th>Dependencies</th>
+<th class="numeric">Duration</th>
+</tr>
+</thead>
+<tr>
+<td>All dependencies</td>
+<td class="numeric">2m0.00s</td>
+</tr>
+<tr>
+<td>compile</td>
+<td class="numeric">1m30.00s</td>
+</tr>
+<tr>
+<td>runtime</td>
+<td class="numeric">30.000s</td>
+</tr>
+</table>
+</div>
+<div class="tab" id="tab3">
+<h2>Task Execution</h2>
+<table>
+<thead>
+<tr>
+<th>Task</th>
+<th class="numeric">Duration</th>
+<th>Result</th>
+</tr>
+</thead>
+<tr>
+<td>b</td>
+<td class="numeric">4m30.00s</td>
+<td>(total)</td>
+</tr>
+<tr>
+<td class="indentPath">b:foo</td>
+<td class="numeric">2m30.00s</td>
+<td>Did No Work</td>
+</tr>
+<tr>
+<td class="indentPath">b:bar</td>
+<td class="numeric">2m0.00s</td>
+<td>Did No Work</td>
+</tr>
+<tr>
+<td>a</td>
+<td class="numeric">2m0.00s</td>
+<td>(total)</td>
+</tr>
+<tr>
+<td class="indentPath">a:foo</td>
+<td class="numeric">1m30.00s</td>
+<td>Did No Work</td>
+</tr>
+<tr>
+<td class="indentPath">a:bar</td>
+<td class="numeric">30.000s</td>
+<td>Did No Work</td>
+</tr>
+</table>
+</div>
+</div>"""))
+    }
+
+    private long time(int hour, int mins, int secs, int ms = 0) {
+        def cal = new GregorianCalendar(2010, 1, 5, hour, mins, secs)
+        cal.add(Calendar.MILLISECOND, ms)
+        cal.getTimeInMillis()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/profile/ProjectProfileTest.groovy b/subprojects/core/src/test/groovy/org/gradle/profile/ProjectProfileTest.groovy
new file mode 100644
index 0000000..a737dfa
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/profile/ProjectProfileTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.profile
+
+import org.gradle.api.tasks.TaskState
+import spock.lang.Specification
+
+class ProjectProfileTest extends Specification {
+
+    def "provides sorted tasks"() {
+        def profile = new ProjectProfile(":foo")
+        def a = profile.getTaskProfile("foo:a").completed(Stub(TaskState)).setStart(100).setFinish(300)
+        def b = profile.getTaskProfile("foo:b").completed(Stub(TaskState)).setStart(300).setFinish(300)
+        def c = profile.getTaskProfile("foo:c").completed(Stub(TaskState)).setStart(300).setFinish(300)
+        def d = profile.getTaskProfile("foo:d").completed(Stub(TaskState)).setStart(301).setFinish(302)
+
+        expect:
+        profile.tasks.operations == [a, d, b, c]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/profile/TaskExecutionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/profile/TaskExecutionTest.groovy
new file mode 100644
index 0000000..ac227ea
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/profile/TaskExecutionTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.profile
+
+import org.gradle.api.tasks.TaskState
+import spock.lang.Specification
+
+class TaskExecutionTest extends Specification {
+
+    def "knows task status"() {
+        def skipped = Stub(TaskState) {
+            getSkipped() >> true
+            getSkipMessage() >> "Skipped for a good reason."
+        }
+        def busy = Stub(TaskState) {
+            getSkipped() >> false
+            getDidWork() >> true
+        }
+        def noWork = Stub(TaskState) {
+            getSkipped() >> false
+            getDidWork() >> false
+        }
+
+        expect:
+        new TaskExecution("a").completed(skipped).status == "Skipped for a good reason."
+        new TaskExecution("a").completed(busy).status == ""
+        new TaskExecution("a").completed(noWork).status == TaskExecution.NO_WORK_MESSAGE
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
index 7318bc1..5d058be 100644
--- a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
@@ -55,6 +55,6 @@ class HtmlReportRendererTest extends Specification {
         renderer.renderer(abstractHtmlReportRenderer).writeTo("test", destFile)
 
         then:
-        tmpDir.file("base-style.css").file
+        tmpDir.file("css/base-style.css").file
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.groovy
new file mode 100644
index 0000000..9fbb7d9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.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.tooling.provider.model.internal
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.tooling.provider.model.ToolingModelBuilder
+import org.gradle.tooling.provider.model.UnknownModelException
+import spock.lang.Specification
+
+class DefaultToolingModelBuilderRegistryTest extends Specification {
+    final def registy = new DefaultToolingModelBuilderRegistry()
+
+    def "finds model builder for requested model"() {
+        def builder1 = Mock(ToolingModelBuilder)
+        def builder2 = Mock(ToolingModelBuilder)
+
+        given:
+        registy.register(builder1)
+        registy.register(builder2)
+
+        and:
+        builder1.canBuild("model") >> false
+        builder2.canBuild("model") >> true
+
+        expect:
+        registy.getBuilder("model") == builder2
+    }
+
+    def "includes a simple implementation for the Void model"() {
+        expect:
+        registy.getBuilder(Void.class.name).buildAll(Void.class.name, Mock(ProjectInternal)) == null
+    }
+
+    def "fails when no builder is available for requested model"() {
+        when:
+        registy.getBuilder("model")
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == "No builders are available to build a model of type 'model'."
+    }
+
+    def "fails when multiple builders are available for requested model"() {
+        def builder1 = Mock(ToolingModelBuilder)
+        def builder2 = Mock(ToolingModelBuilder)
+
+        given:
+        registy.register(builder1)
+        registy.register(builder2)
+
+        and:
+        builder1.canBuild("model") >> true
+        builder2.canBuild("model") >> true
+
+        when:
+        registy.getBuilder("model")
+
+        then:
+        UnsupportedOperationException e = thrown()
+        e.message == "Multiple builders are available to build a model of type 'model'."
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTest.groovy
deleted file mode 100644
index 38737a6..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTest.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 spock.lang.Specification
-
-class DefaultClassLoaderFactoryTest extends Specification {
-    final DefaultClassLoaderFactory factory = new DefaultClassLoaderFactory()
-    ClassLoader original
-
-    def setup() {
-        original = Thread.currentThread().contextClassLoader
-    }
-
-    def cleanup() {
-        Thread.currentThread().contextClassLoader = original
-    }
-
-    def "classes from specified URLs are visible in isolated ClassLoader"() {
-        when:
-        def cl = factory.createIsolatedClassLoader(classpath)
-        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
-
-        then:
-        c.name == DefaultClassLoaderFactoryTestHelper.name
-        c != DefaultClassLoaderFactoryTestHelper
-    }
-
-    def "application classes are not visible in isolated ClassLoader"() {
-        when:
-        def cl = factory.createIsolatedClassLoader(classpath)
-        cl.loadClass(Closure.name)
-
-        then:
-        thrown(ClassNotFoundException)
-    }
-
-    def "can use XML APIs from isolated ClassLoader when application classes include an XML provider"() {
-        assert ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory")
-
-        when:
-        def cl = factory.createIsolatedClassLoader(classpath)
-        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
-
-        then:
-        c != DefaultClassLoaderFactoryTestHelper
-
-        when:
-        Thread.currentThread().contextClassLoader = cl
-        c.newInstance().doStuff()
-
-        then:
-        notThrown()
-    }
-
-    def "can use XML APIs from filtering ClassLoader when application classes include an XML provider"() {
-        assert ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory")
-
-        when:
-        def cl = new URLClassLoader(classpath.collect { it.toURL() } as URL[], factory.createFilteringClassLoader(getClass().classLoader))
-        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
-
-        then:
-        c != DefaultClassLoaderFactoryTestHelper
-
-        when:
-        Thread.currentThread().contextClassLoader = cl
-        c.newInstance().doStuff()
-
-        then:
-        notThrown()
-    }
-
-    def getClasspath() {
-        return [ClasspathUtil.getClasspathForClass(DefaultClassLoaderFactoryTestHelper)].collect { it.toURI() }
-    }
-}
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTestHelper.java b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTestHelper.java
deleted file mode 100644
index 0d52d2b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTestHelper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util;
-
-import javax.xml.XMLConstants;
-import javax.xml.datatype.DatatypeFactory;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.SAXParserFactory;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.xpath.XPathFactory;
-
-public class DefaultClassLoaderFactoryTestHelper {
-    public void doStuff() throws Exception {
-        SAXParserFactory.newInstance().newSAXParser();
-        DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        DatatypeFactory.newInstance().newXMLGregorianCalendar();
-        TransformerFactory.newInstance().newTransformer();
-        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-        XPathFactory.newInstance().newXPath();
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
deleted file mode 100644
index d4f778e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util
-
-import org.hamcrest.Matcher
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.junit.Before
-
- at RunWith(JMock.class)
-class FilteringClassLoaderTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final FilteringClassLoader classLoader = new FilteringClassLoader(FilteringClassLoaderTest.class.getClassLoader())
-
-    @Test
-    void passesThroughSystemClasses() {
-        assertThat(classLoader.loadClass(String.class.name), sameInstance(String.class))
-    }
-
-    @Test
-    void passesThroughSystemPackages() {
-        assertThat(classLoader.getPackage('java.lang'), notNullValue(Package.class))
-        assertThat(classLoader.getPackages(), hasPackage('java.lang'))
-    }
-
-    private Matcher<Package[]> hasPackage(String name) {
-        Matcher matcher = [matches: {Package p -> p.name == name }, describeTo: {description -> description.appendText("has package '$name'")}] as Matcher
-        return hasItemInArray(matcher)
-    }
-
-    @Test
-    void passesThroughSystemResources() {
-        assertThat(classLoader.getResource('com/sun/jndi/ldap/jndiprovider.properties'), notNullValue())
-        assertThat(classLoader.getResourceAsStream('com/sun/jndi/ldap/jndiprovider.properties'), notNullValue())
-        assertTrue(classLoader.getResources('com/sun/jndi/ldap/jndiprovider.properties').hasMoreElements())
-    }
-
-    @Test
-    void filtersClasses() {
-        classLoader.parent.loadClass(Test.class.name)
-
-        try {
-            classLoader.loadClass(Test.class.name, false)
-            fail()
-        } catch (ClassNotFoundException e) {
-            assertThat(e.message, equalTo("$Test.name not found.".toString()))
-        }
-        try {
-            classLoader.loadClass(Test.class.name)
-            fail()
-        } catch (ClassNotFoundException e) {
-            assertThat(e.message, equalTo("$Test.name not found.".toString()))
-        }
-    }
-
-    @Test
-    void filtersPackages() {
-        assertThat(classLoader.parent.getPackage('org.junit'), notNullValue())
-
-        assertThat(classLoader.getPackage('org.junit'), nullValue())
-        assertThat(classLoader.getPackages(), not(hasPackage('org.junit')))
-    }
-
-    @Test
-    void filtersResources() {
-        assertThat(classLoader.parent.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
-        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), nullValue())
-        assertFalse(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
-    }
-
-    @Test
-    void passesThroughClassesInSpecifiedPackages() {
-        classLoader.allowPackage('org.junit')
-        assertThat(classLoader.loadClass(Test.class.name), sameInstance(Test.class))
-        assertThat(classLoader.loadClass(Test.class.name, false), sameInstance(Test.class))
-        assertThat(classLoader.loadClass(BlockJUnit4ClassRunner.class.name), sameInstance(BlockJUnit4ClassRunner.class))
-    }
-
-    @Test
-    void passesThroughSpecifiedClasses() {
-        classLoader.allowClass(Test.class)
-        assertThat(classLoader.loadClass(Test.class.name), sameInstance(Test.class))
-        try {
-            classLoader.loadClass(Before.class.name)
-            fail()
-        } catch (ClassNotFoundException e) {
-            // expected
-        }
-    }
-
-    @Test
-    void filtersSpecifiedClasses() {
-        classLoader.allowPackage("org.junit")
-        classLoader.disallowClass("org.junit.Test")
-
-        canLoadClass(Before)
-        cannotLoadClass(Test)
-    }
-
-    @Test
-    void disallowClassWinsOverAllowClass() {
-        classLoader.allowClass(Test)
-        classLoader.disallowClass(Test.name)
-
-        cannotLoadClass(Test)
-    }
-
-    @Test
-    void passesThroughSpecifiedPackages() {
-        assertThat(classLoader.getPackage('org.junit'), nullValue())
-        assertThat(classLoader.getPackages(), not(hasPackage('org.junit')))
-
-        classLoader.allowPackage('org.junit')
-
-        assertThat(classLoader.getPackage('org.junit'), notNullValue())
-        assertThat(classLoader.getPackages(), hasPackage('org.junit'))
-        assertThat(classLoader.getPackage('org.junit.runner'), notNullValue())
-        assertThat(classLoader.getPackages(), hasPackage('org.junit.runner'))
-    }
-
-    @Test
-    void passesThroughResourcesInSpecifiedPackages() {
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
-
-        classLoader.allowPackage('org.gradle')
-
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
-    }
-
-    @Test
-    void passesThroughResourcesWithSpecifiedPrefix() {
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
-
-        classLoader.allowResources('org/gradle')
-
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
-    }
-
-    @Test
-    void passesThroughSpecifiedResources() {
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
-
-        classLoader.allowResource('org/gradle/util/ClassLoaderTest.txt')
-
-        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
-        assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
-    }
-
-    void canLoadClass(Class<?> clazz) {
-        assert classLoader.loadClass(clazz.name) == clazz
-    }
-
-    void cannotLoadClass(Class<?> clazz) {
-        try {
-            classLoader.loadClass(clazz.name)
-            fail()
-        } catch (ClassNotFoundException expected) {}
-    }
-}
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 e241b24..03a221c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
@@ -24,9 +24,6 @@ import spock.lang.Specification
 import static org.gradle.util.GFileUtils.mkdirs
 import static org.gradle.util.GFileUtils.parentMkdirs
 
-/**
- * by Szczepan Faber, created at: 2/28/12
- */
 class GFileUtilsTest extends Specification {
 
     @Rule TestNameTestDirectoryProvider temp
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 84fc2d3..b6bfade 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
@@ -159,4 +159,23 @@ public class GUtilTest extends spock.lang.Specification {
         expect:
         asPath(["lib1.jar", "lib2.jar", new File("lib3.jar")]) == "lib1.jar${sep}lib2.jar${sep}lib3.jar"
     }
+
+    def "adds to collection"() {
+        def list = [0]
+        when: addToCollection(list, [1, 2], [2, 3])
+        then: list == [0, 1, 2, 2, 3]
+    }
+
+    def "adds empty list to collection"() {
+        expect:
+        addToCollection([], [], []) == []
+        addToCollection([1], [], [2]) == [1, 2]
+    }
+
+    def "adds to collection preventing nulls"() {
+        when: addToCollection([], true, [1, 2], [null, 3])
+        then:
+        def ex = thrown(IllegalArgumentException)
+        ex.message.contains([null, 3].toString())
+    }
 }
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 c0de292..21b25af 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
@@ -24,22 +24,33 @@ import org.gradle.internal.os.OperatingSystem
 import spock.lang.Issue
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 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 "parsing fails for unrecognized version string"() {
+        when:
+        GradleVersion.version(versionString)
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "'$versionString' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')"
+
+        where:
+        versionString << [
+                "",
+                "something",
+                "1",
+                "1-beta",
+                "1.0-\n"
+        ]
     }
 
-    def currentVersionHasNonNullVersion() {
+    def "current version has non-null parts"() {
         expect:
         version.version
+        version.buildTime
+        version.nextMajor
+        version.baseVersion
     }
 
     @Issue("http://issues.gradle.org/browse/GRADLE-1892")
@@ -96,28 +107,6 @@ class GradleVersionTest extends Specification {
                 '1.2.1']
     }
 
-    def canOnlyQueryVersionStringForUnrecognizedVersion(String version) {
-        def gradleVersion = GradleVersion.version(version)
-
-        expect:
-        gradleVersion.version == version
-        gradleVersion.snapshot
-
-        when:
-        gradleVersion > GradleVersion.version('1.2')
-
-        then:
-        IllegalArgumentException e = thrown()
-        e.message == "Cannot compare unrecognized Gradle version '${version}'."
-
-        where:
-        version << [
-                'abc',
-                '3.0-status-5-master',
-                'user-master',
-        ]
-    }
-
     def canCompareMajorVersions() {
         expect:
         GradleVersion.version(a) > GradleVersion.version(b)
@@ -173,8 +162,9 @@ class GradleVersionTest extends Specification {
         a                 | b
         '1.0-milestone-2' | '1.0-milestone-1'
         '1.0-preview-2'   | '1.0-preview-1'
-        '1.0-rc-2'        | '1.0-rc-1'
         '1.0-preview-1'   | '1.0-milestone-7'
+        '1.0-rc-1'        | '1.0-milestone-7'
+        '1.0-rc-2'        | '1.0-rc-1'
         '1.0-rc-7'        | '1.0-rc-1'
         '1.0'             | '1.0-rc-7'
     }
@@ -215,28 +205,53 @@ class GradleVersionTest extends Specification {
 
     def "can get version base"() {
         expect:
-        GradleVersion.version(v).versionBase == base
+        GradleVersion.version(v).baseVersion == GradleVersion.version(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
+        v                                     | base
+        "1.0"                                 | "1.0"
+        "1.0-rc-1"                            | "1.0"
+        "1.2.3.4"                             | "1.2.3.4"
+        '0.9'                                 | "0.9"
+        '0.9.2'                               | "0.9.2"
+        '0.9-20101220100000+1000'             | "0.9"
+        '0.9-20101220100000'                  | "0.9"
+        '20.17-20101220100000+1000'           | "20.17"
     }
 
-    def "can get version major"() {
+    def "milestones are treated as base versions"() {
         expect:
-        GradleVersion.version(v).major == major
+        GradleVersion.version(v).baseVersion == GradleVersion.version(base)
 
         where:
-        v                         | major
-        "1.0"                     | 1
-        "1.0-rc-1"                | 1
-        '0.9-20101220100000+1000' | 0
-        '0.9-20101220100000'      | 0
-        "asdfasd"                 | -1
+        v                                     | base
+        '1.0-milestone-3'                     | "1.0-milestone-3"
+        '1.0-milestone-3-20121012100000+1000' | "1.0-milestone-3"
+        '2.0-milestone-3'                     | "2.0-milestone-3"
+    }
+
+    def "can get next major version"() {
+        expect:
+        GradleVersion.version(v).nextMajor == GradleVersion.version(major)
+
+        where:
+        v                                     | major
+        "1.0"                                 | "2.0"
+        "1.0-rc-1"                            | "2.0"
+        '0.9-20101220100000+1000'             | "1.0"
+        '0.9-20101220100000'                  | "1.0"
+        '20.17-20101220100000+1000'           | "21.0"
+    }
+
+    def "milestones are part of previous major version"() {
+        expect:
+        GradleVersion.version(v).nextMajor == GradleVersion.version(major)
+
+        where:
+        v                                     | major
+        '1.0-milestone-3'                     | "1.0"
+        '1.0-milestone-3-20121012100000+1000' | "1.0"
+        '2.0-milestone-3'                     | "2.0" // not that we're planning to do this
     }
 
     def prettyPrint() {
@@ -245,12 +260,15 @@ class GradleVersionTest extends Specification {
 Gradle $version.version
 ------------------------------------------------------------
 
-Gradle build time: $version.buildTime
-Groovy: $InvokerHelper.version
-Ant: $Main.antVersion
-Ivy: ${Ivy.ivyVersion}
-JVM: ${Jvm.current()}
-OS: ${OperatingSystem.current()}
+Build time:   $version.buildTime
+Build number: $version.buildNumber
+Revision:     $version.commitId
+
+Groovy:       $InvokerHelper.version
+Ant:          $Main.antVersion
+Ivy:          ${Ivy.ivyVersion}
+JVM:          ${Jvm.current()}
+OS:           ${OperatingSystem.current()}
 """
         expect:
         version.prettyPrint() == expectedText
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java b/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java
deleted file mode 100644
index 850778b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java
+++ /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.util;
-
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class JavaMethodTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void invokesMethodOnObject() {
-        JavaMethod<CharSequence, CharSequence> method = JavaMethod.create(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
-        assertThat(method.invoke("string", 0, 3), equalTo((CharSequence) "str"));
-    }
-    
-    @Test
-    public void propagatesExceptionThrownByMethod() {
-        final CharSequence mock = context.mock(CharSequence.class);
-        final RuntimeException failure = new RuntimeException();
-        context.checking(new Expectations() {{
-            one(mock).subSequence(0, 3);
-            will(throwException(failure));
-        }});
-
-        JavaMethod<CharSequence, CharSequence> method = JavaMethod.create(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
-        try {
-            method.invoke(mock, 0, 3);
-            fail();
-        } catch (RuntimeException e) {
-            assertThat(e, sameInstance(failure));
-        }
-    }
-
-    @Test
-    public void canAccessProtectedMethod() {
-        final Package[] packages = new Package[0];
-        ClassLoader classLoader = new ClassLoader() {
-            @Override
-            protected Package[] getPackages() {
-                return packages;
-            }
-        };
-
-        JavaMethod<ClassLoader, Package[]> method = JavaMethod.create(ClassLoader.class, Package[].class, "getPackages");
-        assertThat(method.invoke(classLoader), sameInstance(packages));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java b/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
index 307702e..7eaa4b7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.util;
 
-import org.gradle.api.Action;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.io.TextStream;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -30,8 +30,7 @@ import java.io.IOException;
 @RunWith(JMock.class)
 public class LineBufferingOutputStreamTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private Action<String> action = context.mock(Action.class);
-    private LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+    private TextStream action = context.mock(TextStream.class);
     private String eol;
 
     @Before
@@ -46,65 +45,94 @@ public class LineBufferingOutputStreamTest {
 
     @Test
     public void logsEachLineAsASeparateLogMessage() throws IOException {
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute(TextUtil.toPlatformLineSeparators("line 1\n"));
-            one(action).execute(TextUtil.toPlatformLineSeparators("line 2\n"));
+            one(action).text(TextUtil.toPlatformLineSeparators("line 1\n"));
+            one(action).text(TextUtil.toPlatformLineSeparators("line 2\n"));
         }});
 
         outputStream.write(TextUtil.toPlatformLineSeparators("line 1\nline 2\n").getBytes());
     }
 
     @Test
-    public void logsEmptyLines() throws IOException {
+    public void buffersTextUntilEndOfLineReached() throws IOException {
+        System.setProperty("line.separator", "-");
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute(TextUtil.getPlatformLineSeparator());
-            one(action).execute(TextUtil.getPlatformLineSeparator());
+            one(action).text("line 1-");
         }});
 
-        outputStream.write(TextUtil.toPlatformLineSeparators("\n\n").getBytes());
+        outputStream.write("line ".getBytes());
+        outputStream.write("1-line 2".getBytes());
+
+        context.checking(new Expectations() {{
+            one(action).text("line 2-");
+        }});
+
+        outputStream.write("-".getBytes());
     }
 
     @Test
-    public void handlesSingleCharacterLineSeparator() throws IOException {
+    public void logsEmptyLines() throws IOException {
+        System.setProperty("line.separator", "-");
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute("line 1-");
-            one(action).execute("line 2-");
+            one(action).text("-");
+            one(action).text("-");
         }});
 
+        outputStream.write("--".getBytes());
+    }
+
+    @Test
+    public void handlesSingleCharacterLineSeparator() throws IOException {
         System.setProperty("line.separator", "-");
-        outputStream = new LineBufferingOutputStream(action, 8);
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
+        context.checking(new Expectations() {{
+            one(action).text("line 1-");
+            one(action).text("line 2-");
+        }});
 
         outputStream.write(String.format("line 1-line 2-").getBytes());
     }
-    
+
     @Test
     public void handlesMultiCharacterLineSeparator() throws IOException {
+        System.setProperty("line.separator", "----");
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute("line 1----");
-            one(action).execute("line 2----");
+            one(action).text("line 1----");
+            one(action).text("line 2----");
         }});
 
-        System.setProperty("line.separator", "----");
-        outputStream = new LineBufferingOutputStream(action, 8);
-
         outputStream.write(String.format("line 1----line 2----").getBytes());
     }
 
     @Test
     public void logsLineWhichIsLongerThanInitialBufferLength() throws IOException {
+        System.setProperty("line.separator", "-");
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute(TextUtil.toPlatformLineSeparators("a line longer than 8 bytes long\n"));
-            one(action).execute("line 2");
+            one(action).text("a line longer than 8 bytes long-");
+            one(action).text("line 2");
         }});
-        outputStream.write(TextUtil.toPlatformLineSeparators("a line longer than 8 bytes long\n").getBytes());
+        outputStream.write("a line longer than 8 bytes long-".getBytes());
         outputStream.write("line 2".getBytes());
-        outputStream.close();
+        outputStream.flush();
     }
 
     @Test
     public void logsPartialLineOnFlush() throws IOException {
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute("line 1");
+            one(action).text("line 1");
         }});
 
         outputStream.write("line 1".getBytes());
@@ -112,16 +140,48 @@ public class LineBufferingOutputStreamTest {
     }
 
     @Test
+    public void logsNothingOnCloseWhenNothingHasBeenWrittenToStream() throws IOException {
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
+        context.checking(new Expectations() {{
+            one(action).endOfStream(null);
+        }});
+        outputStream.close();
+    }
+
+    @Test
+    public void logsNothingOnCloseWhenCompleteLineHasBeenWrittenToStream() throws IOException {
+        System.setProperty("line.separator", "-");
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
+        context.checking(new Expectations() {{
+            one(action).text("line 1-");
+            one(action).endOfStream(null);
+        }});
+
+        outputStream.write("line 1-".getBytes());
+        outputStream.close();
+    }
+
+    @Test
     public void logsPartialLineOnClose() throws IOException {
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
         context.checking(new Expectations() {{
-            one(action).execute("line 1");
+            one(action).text("line 1");
+            one(action).endOfStream(null);
         }});
         outputStream.write("line 1".getBytes());
         outputStream.close();
     }
-    
+
     @Test(expected = IOException.class)
     public void cannotWriteAfterClose() throws IOException {
+        LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
+
+        context.checking(new Expectations() {{
+            one(action).endOfStream(null);
+        }});
         outputStream.close();
         outputStream.write("ignore me".getBytes());
     }
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 5222980..41d3072 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.util
 
-import org.gradle.api.Action
+import org.gradle.internal.io.TextStream
 import org.junit.Test
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
@@ -25,7 +25,7 @@ class LinePerThreadBufferingOutputStreamTest extends MultithreadedTestCase {
     @Test
     public void interleavesLinesFromEachThread() {
         List<String> output = [].asSynchronized()
-        Action<String> action = { String line -> output << line.replace(TextUtil.platformLineSeparator, "<EOL>") } as Action
+        TextStream action = { String line -> output << line.replace(TextUtil.platformLineSeparator, "<EOL>") } as TextStream
         LinePerThreadBufferingOutputStream outstr = new LinePerThreadBufferingOutputStream(action)
         10.times {
             start {
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy
index 7c7dbbe..3cb9951 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy
@@ -22,9 +22,6 @@ import java.util.regex.Pattern
 
 import static org.gradle.util.Matchers.matchesRegexp
 
-/**
- * by Szczepan Faber, created at: 12/17/12
- */
 class MatchersTest extends Specification {
 
     def "matches regex"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
deleted file mode 100644
index 6efd696..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util
-
-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.*
-
- at RunWith(JMock.class)
-class MultiParentClassLoaderTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private ClassLoader parent1
-    private ClassLoader parent2
-    private MultiParentClassLoader loader
-
-    @Before
-    public void setup() {
-        parent1 = context.mock(ClassLoader)
-        parent2 = context.mock(ClassLoader)
-        loader = new MultiParentClassLoader(parent1, parent2)
-    }
-
-    @Test
-    public void parentsAreNotVisibleViaSuperClass() {
-        assertThat(loader.parent, nullValue())
-    }
-
-    @Test
-    public void loadsClassFromParentsInOrderSpecified() {
-        Class stringClass = String.class
-        Class integerClass = Integer.class
-
-        context.checking {
-            allowing(parent1).loadClass('string')
-            will(returnValue(stringClass))
-            allowing(parent1).loadClass('integer')
-            will(throwException(new ClassNotFoundException()))
-            allowing(parent2).loadClass('integer')
-            will(returnValue(integerClass))
-        }
-
-        assertThat(loader.loadClass('string'), equalTo(String.class))
-        assertThat(loader.loadClass('string', true), equalTo(String.class))
-        assertThat(loader.loadClass('integer'), equalTo(Integer.class))
-        assertThat(loader.loadClass('integer', true), equalTo(Integer.class))
-    }
-
-    @Test
-    public void throwsCNFExceptionWhenClassNotFound() {
-        context.checking {
-            allowing(parent1).loadClass('string')
-            will(throwException(new ClassNotFoundException()))
-            allowing(parent2).loadClass('string')
-            will(throwException(new ClassNotFoundException()))
-        }
-
-        try {
-            loader.loadClass('string')
-            fail()
-        } catch (ClassNotFoundException e) {
-            assertThat(e.message, equalTo('string not found.'))
-        }
-    }
-    
-    @Test
-    public void loadsPackageFromParentsInOrderSpecified() {
-        Package stringPackage = String.class.getPackage()
-        Package listPackage = List.class.getPackage()
-
-        context.checking {
-            allowing(parent1).getPackage('string')
-            will(returnValue(stringPackage))
-            allowing(parent1).getPackage('list')
-            will(returnValue(null))
-            allowing(parent2).getPackage('list')
-            will(returnValue(listPackage))
-        }
-
-        assertThat(loader.getPackage('string'), sameInstance(stringPackage))
-        assertThat(loader.getPackage('list'), sameInstance(listPackage))
-    }
-
-    @Test
-    public void containsUnionOfPackagesFromAllParents() {
-        Package package1 = context.mock(Package.class, 'p1')
-        Package package2 = context.mock(Package.class, 'p2')
-
-        context.checking {
-            allowing(parent1).getPackages()
-            will(returnValue([package1] as Package[]))
-            allowing(parent2).getPackages()
-            will(returnValue([package2] as Package[]))
-        }
-
-        assertThat(loader.getPackages(), hasItemInArray(package1))
-        assertThat(loader.getPackages(), hasItemInArray(package2))
-    }
-
-    @Test
-    public void loadsResourceFromParentsInOrderSpecified() {
-        URL resource1 = new File('res1').toURI().toURL()
-        URL resource2 = new File('res2').toURI().toURL()
-
-        context.checking {
-            allowing(parent1).getResource('resource1')
-            will(returnValue(resource1))
-            allowing(parent1).getResource('resource2')
-            will(returnValue(null))
-            allowing(parent2).getResource('resource2')
-            will(returnValue(resource2))
-        }
-
-        assertThat(loader.getResource('resource1'), equalTo(resource1))
-        assertThat(loader.getResource('resource2'), equalTo(resource2))
-    }
-    
-    @Test
-    public void containsUnionOfResourcesFromAllParents() {
-        URL resource1 = new File('res1').toURI().toURL()
-        URL resource2 = new File('res2').toURI().toURL()
-
-        context.checking {
-            allowing(parent1).getResources('resource1')
-            will(returnValue(Collections.enumeration([resource1])))
-            allowing(parent2).getResources('resource1')
-            will(returnValue(Collections.enumeration([resource2, resource1])))
-        }
-
-        Enumeration resources = loader.getResources('resource1')
-        assertTrue(resources.hasMoreElements())
-        assertThat(resources.nextElement(), sameInstance(resource1))
-        assertTrue(resources.hasMoreElements())
-        assertThat(resources.nextElement(), sameInstance(resource2))
-        assertFalse(resources.hasMoreElements())
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/PathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/PathTest.groovy
index 23c5052..5928d69 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/PathTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/PathTest.groovy
@@ -17,11 +17,9 @@
 package org.gradle.util
 
 import spock.lang.Specification
-import static org.gradle.util.Matchers.*
 
-/**
- * @author Hans Dockter
- */
+import static org.gradle.util.Matchers.strictlyEquals
+
 class PathTest extends Specification {
     def construction() {
         expect:
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy
index c8b198e..81da2f9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy
@@ -19,24 +19,31 @@ package org.gradle.util
 import org.gradle.internal.Factory
 import org.gradle.logging.ConfigureLogging
 import org.gradle.logging.TestAppender
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 import org.junit.Rule
-import spock.lang.Specification
 
-class SingleMessageLoggerTest extends Specification {
+class SingleMessageLoggerTest extends ConcurrentSpec {
     final TestAppender appender = new TestAppender()
     @Rule final ConfigureLogging logging = new ConfigureLogging(appender)
 
-    public void cleanup() {
+    def cleanup() {
         SingleMessageLogger.reset()
     }
 
-    def "logs deprecation warning once"() {
+    def "logs deprecation warning once until reset"() {
         when:
         SingleMessageLogger.nagUserWith("nag")
         SingleMessageLogger.nagUserWith("nag")
 
         then:
         appender.toString() == '[WARN nag]'
+
+        when:
+        SingleMessageLogger.reset()
+        SingleMessageLogger.nagUserWith("nag")
+
+        then:
+        appender.toString() == '[WARN nag][WARN nag]'
     }
 
     def "does not log warning while disabled with factory"() {
@@ -54,6 +61,9 @@ class SingleMessageLoggerTest extends Specification {
             return "result"
         }
         0 * _._
+
+        and:
+        appender.toString().length() == 0
     }
 
     def "does not log warning while disabled with action"() {
@@ -65,19 +75,40 @@ class SingleMessageLoggerTest extends Specification {
         then:
         1 * action.run()
         0 * _._
+
+        and:
+        appender.toString().length() == 0
+    }
+
+    def "warnings are disabled for the current thread only"() {
+        when:
+        async {
+            start {
+                thread.blockUntil.disabled
+                SingleMessageLogger.nagUserWith("nag")
+                instant.logged
+            }
+            start {
+                SingleMessageLogger.whileDisabled {
+                    instant.disabled
+                    SingleMessageLogger.nagUserWith("ignored")
+                    thread.blockUntil.logged
+                }
+            }
+        }
+
+        then:
+        appender.toString() == '[WARN nag]'
     }
 
     def "deprecation message has next major version"() {
         given:
-        def major = GradleVersion.current().major
-
-        expect:
-        major != -1
+        def major = GradleVersion.current().nextMajor
 
         when:
         SingleMessageLogger.nagUserOfDeprecated("foo", "bar")
 
         then:
-        appender.toString() == "[WARN foo has been deprecated and is scheduled to be removed in Gradle ${major + 1}.0. bar.]"
+        appender.toString() == "[WARN foo has been deprecated and is scheduled to be removed in Gradle ${major.version}. bar.]"
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/StageTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/StageTest.groovy
index e501cdb..ac5227f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/StageTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/StageTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.util
 import org.gradle.util.GradleVersion.Stage
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber
- */
 class StageTest extends Specification {
     def "builds simple stage"() {
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/StdoutSwapperTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/StdoutSwapperTest.groovy
deleted file mode 100644
index c860a6c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/StdoutSwapperTest.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.util
-
-import spock.lang.*
-import java.util.concurrent.Callable
-
-class StdoutSwapperTest extends Specification {
-
-    def "can swap stdout"() {
-        given:
-        def original = System.out
-        def bytes = new ByteArrayOutputStream()
-        def stream = new PrintStream(bytes)
-        def text = "abc"
-
-        when:
-        new StdoutSwapper().swap(stream, { 
-            assert System.out.is(stream)
-            print text 
-        } as Callable)
-        
-        then:
-        bytes.toString() == text
-        
-        and:
-        System.out.is(original)
-    }
-
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
index ac097c6..dffd28e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
@@ -29,6 +29,8 @@ class TextUtilTest extends Specification {
 
         where:
         original                          | converted
+        ""                                | ""
+        "none"                            | "none"
         "one\rtwo\nthree\r\nfour\n\rfive" | "one${sep}two${sep}three${sep}four${sep}${sep}five"
     }
 
@@ -38,10 +40,24 @@ class TextUtilTest extends Specification {
 
         where:
         original                          | converted
+        ""                                | ""
+        "none"                            | "none"
         "one\rtwo\nthree\r\nfour\n\rfive" | "one${platformSep}two${platformSep}three${platformSep}four${platformSep}${platformSep}five"
         "\n\n"                            | "${platformSep}${platformSep}"
     }
 
+    def normaliseLineSeparators() {
+        expect:
+        TextUtil.normaliseLineSeparators(original) == converted
+
+        where:
+        original                          | converted
+        ""                                | ""
+        "none"                            | "none"
+        "one\rtwo\nthree\r\nfour\n\rfive" | "one\ntwo\nthree\nfour\n\nfive"
+        "\r\n\n\r"                        | "\n\n\n"
+    }
+
     def containsWhitespace() {
         expect:
         TextUtil.containsWhitespace(str) == whitespace
@@ -62,11 +78,11 @@ class TextUtilTest extends Specification {
         TextUtil.indent(text, indent) == result
 
         where:
-        text            | indent | result
-        ""              | ""     | ""
-        "abc"           | "  "   | "  abc"
-        "abc"           | "def"  | "defabc"
-        "abc\ndef\nghi" | " "    | " abc\n def\n ghi"
+        text                | indent | result
+        ""                  | ""     | ""
+        "abc"               | "  "   | "  abc"
+        "abc"               | "def"  | "defabc"
+        "abc\ndef\nghi"     | " "    | " abc\n def\n ghi"
         "abc\n\t\n   \nghi" | "X"    | "Xabc\n\t\n   \nXghi"
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.groovy
index 96bc172..d3f4b3f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.groovy
@@ -73,7 +73,7 @@ class VersionNumberTest extends Specification {
         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, "rc") != new VersionNumber(1, 1, 1, null)
     }
 
     def "comparison"() {
@@ -83,14 +83,23 @@ class VersionNumberTest extends Specification {
         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, "rc") < new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 1, 1, "beta") > new VersionNumber(1, 1, 1, "alpha")
+        new VersionNumber(1, 1, 1, "RELEASE") > new VersionNumber(1, 1, 1, "beta")
+        new VersionNumber(1, 1, 1, "SNAPSHOT") < 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, "a") < new VersionNumber(1, 1, 1, "b")
+        new VersionNumber(1, 1, 1, null) > new VersionNumber(1, 1, 1, "rc")
+        new VersionNumber(1, 1, 1, "alpha") < new VersionNumber(1, 1, 1, "beta")
+        new VersionNumber(1, 1, 1, "beta") < new VersionNumber(1, 1, 1, "RELEASE")
+    }
+
+    def "base version"() {
+        expect:
+        new VersionNumber(1, 2, 3, null).baseVersion == new VersionNumber(1, 2, 3, null)
+        new VersionNumber(1, 2, 3, "beta").baseVersion == new VersionNumber(1, 2, 3, null)
     }
 }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/hash/HashValueTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/hash/HashValueTest.groovy
deleted file mode 100644
index 34d5826..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/hash/HashValueTest.groovy
+++ /dev/null
@@ -1,64 +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.hash
-
-import spock.lang.Specification
-
-class HashValueTest extends Specification {
-    def "parses hash value from input strings"() {
-        expect:
-        def digest = HashValue.parse(inputString)
-        digest.asHexString() == hexString
-
-        where:
-        hexString                                  | inputString
-        "1234"                                     | "1234"
-        "abc123"                                   | "ABC123"
-        "1"                                        | "000000000000001"
-        "123456"                                   | "md5 = 123456"
-        "123456"                                   | "sha1 = 123456"
-        "76be4c7459d7fb64bf638bac7accd9b6df728f2b" | "SHA1 (dummy.gz) = 76be4c7459d7fb64bf638bac7accd9b6df728f2b"
-        "687cab044c8f937b8957166272f1da3c"         | "fontbox-0.8.0-incubating.jar: 68 7C AB 04 4C 8F 93 7B  89 57 16 62 72 F1 DA 3C" // http://repo2.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
-        "f951934aa5ae5a88d7e6dfaa6d32307d834a88be" | "f951934aa5ae5a88d7e6dfaa6d32307d834a88be  /home/maven/repository-staging/to-ibiblio/maven2/commons-collections/commons-collections/3.2/commons-collections-3.2.jar"
-    }
-
-    def "creates compact string representation"() {
-        expect:
-        new HashValue(hexString).asCompactString() == compactString
-
-        where:
-        hexString                          | compactString
-        "1234"                             | "4hk"
-        "abc123"                           | "ang93"
-        "d41d8cd98f00b204e9800998ecf8427e" | "6k3m6dj3o0m82ej009j3mfggju"
-        "FFF"                              | "3vv"
-    }
-
-    def "can roundtrip compact sha1 representation"() {
-        given:
-        def hash = new HashValue("1234")
-        
-        expect:
-        hash.equals(new HashValue(hash.asHexString()))
-    }
-    
-    def "creates short MD5 for string input"() {
-        expect:
-        HashUtil.createCompactMD5("") == "6k3m6dj3o0m82ej009j3mfggju"
-        HashUtil.createCompactMD5("a") == "co5qrjg7hmqk33gsps9kne9j1"
-        HashUtil.createCompactMD5("i") == "46bg60milgs1hubil371u1l1q1"
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy
index 4d88fd7..aab8018 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy
@@ -17,11 +17,9 @@
 package org.gradle.util.internal
 
 import spock.lang.Specification
+
 import static org.gradle.util.internal.ArgumentsSplitter.split
 
-/**
- * by Szczepan Faber, created at: 2/29/12
- */
 class ArgumentsSplitterTest extends Specification {
 
     def breaksUpEmptyCommandLineIntoEmptyList() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy
index e31a6ce..c37064d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.util.internal
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 2/28/12
- */
 class LimitedDescriptionTest extends Specification {
 
     def desc = new LimitedDescription(2)
diff --git a/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/nomodeinfos.zip b/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/nomodeinfos.zip
new file mode 100644
index 0000000..abdc980
Binary files /dev/null and b/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/nomodeinfos.zip differ
diff --git a/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/permissions.zip b/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/permissions.zip
new file mode 100644
index 0000000..858c1a3
Binary files /dev/null and b/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/permissions.zip differ
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/RecordingAntBuildListener.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/RecordingAntBuildListener.groovy
new file mode 100644
index 0000000..dbd797c
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/RecordingAntBuildListener.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.apache.tools.ant.BuildEvent
+import org.apache.tools.ant.BuildListener
+
+class RecordingAntBuildListener implements BuildListener {
+
+    List<BuildEvent> buildStarted = []
+    List<BuildEvent> buildFinished = []
+    List<BuildEvent> targetStarted = []
+    List<BuildEvent> targetFinished = []
+    List<BuildEvent> taskStarted = []
+    List<BuildEvent> taskFinished = []
+    List<BuildEvent> messageLogged = []
+
+    void buildStarted(BuildEvent event) {
+        buildStarted << event
+    }
+
+    void buildFinished(BuildEvent event) {
+        buildFinished << event
+    }
+
+    void targetStarted(BuildEvent event) {
+        targetStarted << event
+    }
+
+    void targetFinished(BuildEvent event) {
+        targetFinished << event
+    }
+
+    void taskStarted(BuildEvent event) {
+        taskStarted << event
+    }
+
+    void taskFinished(BuildEvent event) {
+        taskFinished << event
+    }
+
+    void messageLogged(BuildEvent event) {
+        messageLogged << event
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/file/FileCollectionMatchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/file/FileCollectionMatchers.java
new file mode 100644
index 0000000..af2ecc3
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/file/FileCollectionMatchers.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.file;
+
+import org.gradle.api.internal.file.CompositeFileCollection;
+import org.gradle.api.internal.file.UnionFileCollection;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.internal.file.collections.DefaultFileCollectionResolveContext;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class FileCollectionMatchers {
+    @Factory
+    public static <T extends FileCollection> Matcher<T> sameCollection(final FileCollection expected) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                FileCollection actual = (FileCollection) o;
+                List<? extends FileCollection> actualCollections = unpack(actual);
+                List<? extends FileCollection> expectedCollections = unpack(expected);
+                boolean equals = actualCollections.equals(expectedCollections);
+                if (!equals) {
+                    System.out.println("expected: " + expectedCollections);
+                    System.out.println("actual: " + actualCollections);
+                }
+                return equals;
+            }
+
+            private List<? extends FileCollection> unpack(FileCollection expected) {
+                if (expected instanceof UnionFileCollection) {
+                    UnionFileCollection collection = (UnionFileCollection) expected;
+                    return new ArrayList<FileCollection>(collection.getSources());
+                }
+                if (expected instanceof DefaultConfigurableFileCollection) {
+                    DefaultConfigurableFileCollection collection = (DefaultConfigurableFileCollection) expected;
+                    return new ArrayList<FileCollection>((Set) collection.getFrom());
+                }
+                if (expected instanceof CompositeFileCollection) {
+                    CompositeFileCollection collection = (CompositeFileCollection) expected;
+                    DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext();
+                    collection.resolve(context);
+                    return context.resolveAsFileCollections();
+                }
+                throw new RuntimeException("Cannot get children of " + expected);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("same file collection as ").appendValue(expected);
+            }
+        };
+    }
+}
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
index 7517678..20ffb37 100644
--- 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
@@ -15,22 +15,34 @@
  */
 package org.gradle.api.internal.file;
 
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.services.NativeServices;
 
 import java.io.File;
 
 public class TestFiles {
+    private static final FileSystem FILE_SYSTEM = NativeServices.getInstance().get(FileSystem.class);
+    private static final DefaultFileLookup FILE_LOOKUP = new DefaultFileLookup(FILE_SYSTEM);
+
+    public static FileLookup fileLookup() {
+        return FILE_LOOKUP;
+    }
+
+    public static FileSystem fileSystem() {
+        return FILE_SYSTEM;
+    }
+
     /**
      * Returns a resolver with no base directory.
      */
     public static FileResolver resolver() {
-        return new IdentityFileResolver();
+        return FILE_LOOKUP.getFileResolver();
     }
 
     /**
      * Returns a resolver with the given base directory.
      */
     public static FileResolver resolver(File baseDir) {
-        return new BaseDirFileResolver(FileSystems.getDefault(), baseDir);
+        return FILE_LOOKUP.getFileResolver(baseDir);
     }
 }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/internal/file/copy/CopyActionExecuterUtil.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/internal/file/copy/CopyActionExecuterUtil.java
new file mode 100644
index 0000000..2bcc283
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/internal/file/copy/CopyActionExecuterUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.copy;
+
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.tasks.WorkResult;
+
+import java.util.Arrays;
+
+public class CopyActionExecuterUtil {
+
+    public static WorkResult visit(CopyAction visitor, final Iterable<FileCopyDetailsInternal> detailses) {
+        return visitor.execute(new CopyActionProcessingStream() {
+            public void process(CopyActionProcessingStreamAction action) {
+                for (FileCopyDetailsInternal detailsInternal : detailses) {
+                    action.processFile(detailsInternal);
+                }
+            }
+        });
+    }
+
+    public static WorkResult visit(CopyAction visitor, final FileCopyDetailsInternal... detailses) {
+        return visit(visitor, Arrays.asList(detailses));
+    }
+
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
index 3172e06..a2bf3d5 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
+
 package org.gradle.api.tasks;
 
+import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.ConventionAwareHelper;
 import org.gradle.api.internal.ConventionTask;
 import org.junit.Test;
@@ -24,16 +25,13 @@ import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractConventionTaskTest extends AbstractTaskTest {
 
-    public abstract ConventionTask getTask();
-    
+    public abstract AbstractTask getTask();
+
     @Test
     public void testConventionAwareness() {
-        ConventionTask task = getTask();
+        ConventionTask task = (ConventionTask) getTask();
         assertThat(task.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
         ConventionAwareHelper conventionMapping = (ConventionAwareHelper) task.getConventionMapping();
         assertThat(conventionMapping.getConvention(), sameInstance(getProject().getConvention()));
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractCopyTaskContractTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractCopyTaskContractTest.groovy
new file mode 100644
index 0000000..6c0cd9e
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractCopyTaskContractTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks
+
+import org.gradle.api.Action
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.internal.DynamicObject
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Test
+import spock.lang.Issue
+import spock.lang.Unroll
+
+import static junit.framework.TestCase.fail
+
+/**
+ * Tests that different types of copy tasks correctly expose DSL enhanced objects.
+ */
+abstract class AbstractCopyTaskContractTest extends AbstractConventionTaskTest {
+
+    abstract AbstractCopyTask getTask()
+
+    @Unroll
+    @Test
+    public void rootLevelFileCopyDetailsIsDslEnhanced() {
+        task.eachFile {
+            assert delegate instanceof DynamicObject
+        }
+        task.eachFile(new Action<FileCopyDetails>() {
+            void execute(FileCopyDetails fcd) {
+                assert fcd instanceof DynamicObject
+            }
+        })
+    }
+
+    @Test
+    @Issue("GRADLE-2906")
+    void "each file does not execute action for directories"() {
+        File fromSrcDir = createDir(project.projectDir, 'src')
+        File fromConfDir = createDir(fromSrcDir, 'conf')
+        File fromPropertiesFile = createFile(fromConfDir, 'file.properties')
+        fromPropertiesFile.text << 'foo'
+        File intoBuildDir = createDir(project.projectDir, 'build')
+        EachFileClosureInvocation closureInvocation = new EachFileClosureInvocation()
+
+        task.from fromSrcDir
+        task.into intoBuildDir
+        task.eachFile closureInvocation.closure
+        task.execute()
+
+        assert closureInvocation.wasCalled(1)
+        assert closureInvocation.files.containsAll(fromPropertiesFile)
+    }
+
+    @Test
+    @Issue("GRADLE-2900")
+    void "each file does not execute action for directories after filtering file tree"() {
+        File fromSrcDir = createDir(project.projectDir, 'src')
+        File fromConfDir = createDir(fromSrcDir, 'conf')
+        File fromPropertiesFile = createFile(fromConfDir, 'file.properties')
+        fromPropertiesFile.text << 'foo'
+        File intoBuildDir = createDir(project.projectDir, 'build')
+        EachFileClosureInvocation closureInvocation = new EachFileClosureInvocation()
+
+        task.from(project.fileTree(dir: fromSrcDir).matching { include 'conf/file.properties' }) { eachFile closureInvocation.closure }
+        task.into intoBuildDir
+        task.execute()
+
+        assert closureInvocation.wasCalled(1)
+        assert closureInvocation.files.contains(fromPropertiesFile)
+    }
+
+    private File createDir(File parentDir, String path) {
+        TestFile newDir = new TestFile(parentDir, path)
+        boolean success = newDir.mkdirs()
+
+        if(!success) {
+            fail "Failed to create directory $newDir"
+        }
+
+        newDir
+    }
+
+    private File createFile(File parent, String filename) {
+        TestFile file = new TestFile(parent, filename)
+        file.createNewFile()
+        file
+    }
+
+    private class EachFileClosureInvocation {
+        private int calls = 0
+        private final Closure closure
+        private final List<String> files = []
+
+        public EachFileClosureInvocation() {
+            closure = {
+                ++calls
+                files << it.file
+            }
+        }
+
+        Closure getClosure() {
+            closure
+        }
+
+        boolean wasCalled(int times) {
+            calls == times
+        }
+
+        List<String> getFiles() {
+            files
+        }
+    }
+}
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 d6f57ef..e1ac786 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
@@ -21,7 +21,7 @@ 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.internal.Actions
 import org.gradle.api.internal.AsmBackedClassGenerator
 import org.gradle.api.internal.project.AbstractProject
 import org.gradle.api.internal.project.DefaultProject
@@ -29,13 +29,13 @@ import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFacto
 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.TaskExecutionContext
 import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.api.specs.Spec
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
-import org.gradle.util.HelperUtil
-import org.gradle.util.Matchers
+import org.gradle.util.TestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -43,15 +43,12 @@ import java.util.concurrent.atomic.AtomicBoolean
 
 import static org.junit.Assert.assertFalse
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractSpockTaskTest extends Specification {
     public static final String TEST_TASK_NAME = "taskname"
     @Rule
     public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
-    private AbstractProject project = HelperUtil.createRootProject()
+    private AbstractProject project = TestUtil.createRootProject()
 
     private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()))
 
@@ -87,10 +84,10 @@ public abstract class AbstractSpockTaskTest extends Specification {
     }
 
     def testPath() {
-        DefaultProject rootProject = HelperUtil.createRootProject();
-        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
+        DefaultProject rootProject = TestUtil.createRootProject();
+        DefaultProject childProject = TestUtil.createChildProject(rootProject, "child");
         childProject.getProjectDir().mkdirs();
-        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
+        DefaultProject childchildProject = TestUtil.createChildProject(childProject, "childchild");
         childchildProject.getProjectDir().mkdirs();
 
         when:
@@ -122,13 +119,13 @@ public abstract class AbstractSpockTaskTest extends Specification {
         task.dependsOn(Project.PATH_SEPARATOR + "path1");
 
         then:
-        Matchers.dependsOn("path1").matches(task)
+        TaskDependencyMatchers.dependsOn("path1").matches(task)
 
         when:
         task.dependsOn("path2", dependsOnTask);
 
         then:
-        Matchers.dependsOn("path1", "path2", "somename").matches(task)
+        TaskDependencyMatchers.dependsOn("path1", "path2", "somename").matches(task)
     }
 
     def testToString() {
@@ -164,7 +161,7 @@ public abstract class AbstractSpockTaskTest extends Specification {
         task.execute()
 
         then:
-        1 * executer.execute(task, _ as TaskStateInternal)
+        1 * executer.execute(task, _ as TaskStateInternal, _ as TaskExecutionContext)
 
     }
 
@@ -192,7 +189,7 @@ public abstract class AbstractSpockTaskTest extends Specification {
         task.getOnlyIf().isSatisfiedBy(task)
 
         when:
-        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
+        task.onlyIf(TestUtil.toClosure("{ task -> false }"));
 
         then:
         assertFalse(task.getOnlyIf().isSatisfiedBy(task));
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 329f2de..0e6f261 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,20 +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.internal.Actions;
 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.ITaskFactory;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecutionContext;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.specs.Spec;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GUtil;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.lib.legacy.ClassImposteriser;
@@ -49,11 +50,8 @@ import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractTaskTest {
-    public static final String TEST_TASK_NAME = "taskname";
+    public static final String TEST_TASK_NAME = "testTask";
     @Rule
     public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
@@ -65,7 +63,7 @@ public abstract class AbstractTaskTest {
 
     protected Instantiator instantiator = new DependencyInjectingInstantiator(serviceRegistry);
 
-    private AbstractProject project = HelperUtil.createRootProject();
+    private AbstractProject project = TestUtil.createRootProject();
 
     public abstract AbstractTask getTask();
 
@@ -103,10 +101,10 @@ public abstract class AbstractTaskTest {
 
     @Test
     public void testPath() {
-        DefaultProject rootProject = HelperUtil.createRootProject();
-        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
+        DefaultProject rootProject = TestUtil.createRootProject();
+        DefaultProject childProject = TestUtil.createChildProject(rootProject, "child");
         childProject.getProjectDir().mkdirs();
-        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
+        DefaultProject childchildProject = TestUtil.createChildProject(childProject, "childchild");
         childchildProject.getProjectDir().mkdirs();
 
         Task task = createTask(rootProject, TEST_TASK_NAME);
@@ -145,7 +143,7 @@ public abstract class AbstractTaskTest {
         task.setExecuter(executer);
 
         context.checking(new Expectations() {{
-            one(executer).execute(with(sameInstance(task)), with(notNullValue(TaskStateInternal.class)));
+            one(executer).execute(with(sameInstance(task)), with(notNullValue(TaskStateInternal.class)), with(notNullValue(TaskExecutionContext.class)));
         }});
 
         task.execute();
@@ -171,7 +169,7 @@ public abstract class AbstractTaskTest {
         AbstractTask task = getTask();
         assertTrue(task.getOnlyIf().isSatisfiedBy(task));
 
-        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
+        task.onlyIf(TestUtil.toClosure("{ task -> false }"));
         assertFalse(task.getOnlyIf().isSatisfiedBy(task));
     }
 
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/TaskDependencyMatchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/TaskDependencyMatchers.java
new file mode 100644
index 0000000..b4d6216
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/TaskDependencyMatchers.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Task;
+import org.hamcrest.*;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class TaskDependencyMatchers {
+    @Factory
+    public static Matcher<Task> dependsOn(final String... tasks) {
+        return dependsOn(equalTo(new HashSet<String>(Arrays.asList(tasks))));
+    }
+
+    @Factory
+    public static Matcher<Task> dependsOn(Matcher<? extends Iterable<String>> matcher) {
+        return dependsOn(matcher, false);
+    }
+
+    @Factory
+    public static Matcher<Task> dependsOnPaths(Matcher<? extends Iterable<String>> matcher) {
+        return dependsOn(matcher, true);
+    }
+
+    private static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher, final boolean matchOnPaths) {
+        return new BaseMatcher<Task>() {
+            public boolean matches(Object o) {
+                Task task = (Task) o;
+                Set<String> names = new HashSet<String>();
+                Set<? extends Task> depTasks = task.getTaskDependencies().getDependencies(task);
+                for (Task depTask : depTasks) {
+                    names.add(matchOnPaths ? depTask.getPath() : depTask.getName());
+                }
+                boolean matches = matcher.matches(names);
+                if (!matches) {
+                    StringDescription description = new StringDescription();
+                    matcher.describeTo(description);
+                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
+                }
+                return matches;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a Task that depends on ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends Buildable> Matcher<T> builtBy(String... tasks) {
+        return builtBy(equalTo(new HashSet<String>(Arrays.asList(tasks))));
+    }
+
+    @Factory
+    public static <T extends Buildable> Matcher<T> builtBy(final Matcher<? extends Iterable<String>> matcher) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                Buildable task = (Buildable) o;
+                Set<String> names = new HashSet<String>();
+                Set<? extends Task> depTasks = task.getBuildDependencies().getDependencies(null);
+                for (Task depTask : depTasks) {
+                    names.add(depTask.getName());
+                }
+                boolean matches = matcher.matches(names);
+                if (!matches) {
+                    StringDescription description = new StringDescription();
+                    matcher.describeTo(description);
+                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
+                }
+                return matches;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a Buildable that is built by ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
index be42d17..7280fc5 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
@@ -16,24 +16,20 @@
 
 package org.gradle.api.tasks.bundling
 
-import org.gradle.api.internal.ConventionTask
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.tasks.AbstractConventionTaskTest
+import org.gradle.api.tasks.AbstractCopyTaskContractTest
 import org.junit.Test
 
 import static org.junit.Assert.assertEquals
 import static org.junit.Assert.assertTrue
 
-/**
- * @author Hans Dockter
- */
-abstract class AbstractArchiveTaskTest extends AbstractConventionTaskTest {
+abstract class AbstractArchiveTaskTest extends AbstractCopyTaskContractTest {
 
     FileResolver resolver = [resolve: {it as File}] as FileResolver
 
     abstract AbstractArchiveTask getArchiveTask()
 
-    ConventionTask getTask() {
+    AbstractArchiveTask getTask() {
         archiveTask
     }
 
@@ -50,6 +46,7 @@ abstract class AbstractArchiveTaskTest extends AbstractConventionTaskTest {
     }
 
     @Test public void testExecute() {
+        archiveTask.from tmpDir.createFile('file.txt')
         archiveTask.execute()
         assertTrue(archiveTask.destinationDir.isDirectory())
         assertTrue(archiveTask.archivePath.isFile())
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 125fd31..58359e6 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,6 +16,9 @@
 
 package org.gradle.cache.internal
 
+import org.gradle.cache.internal.filelock.LockOptionsBuilder
+import org.gradle.cache.internal.locklistener.NoOpFileLockContentionHandler
+
 abstract class DefaultFileLockManagerTestHelper {
 
     private static class AnException extends RuntimeException {}
@@ -51,13 +54,13 @@ abstract class DefaultFileLockManagerTestHelper {
             String getProcessDisplayName() {
                 return "process"
             }
-        })
+        }, new NoOpFileLockContentionHandler())
     }
     
     static FileLock createDefaultFileLock(File file, FileLockManager.LockMode mode = FileLockManager.LockMode.Exclusive, DefaultFileLockManager manager = createDefaultFileLockManager()) {
-        manager.lock(file, mode, "test lock")        
+        manager.lock(file, LockOptionsBuilder.mode(mode), "test lock")
     }
-    
+
     static File getLockFile(File target) {
         new File(target.absolutePath + ".lock")
     }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/locklistener/NoOpFileLockContentionHandler.java b/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/locklistener/NoOpFileLockContentionHandler.java
new file mode 100644
index 0000000..8d81bb7
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/locklistener/NoOpFileLockContentionHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.cache.internal.locklistener;
+
+public class NoOpFileLockContentionHandler implements FileLockContentionHandler {
+
+    public void start(long lockId, Runnable whenContended) {}
+
+    public void stop(long lockId) {}
+
+    public int reservePort() {
+        return -1;
+    }
+
+    public void pingOwner(int port, long lockId, String displayName) {
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
index 5e334f5..0ad11fb 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
@@ -19,9 +19,6 @@ import org.gradle.test.fixtures.ConcurrentTestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 12/8/11
- */
 class ConcurrentSpecification extends Specification {
     @Rule @Delegate ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
 }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
deleted file mode 100644
index 8b24c26..0000000
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util
-
-import org.apache.ivy.core.module.descriptor.Configuration
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.codehaus.groovy.control.CompilerConfiguration
-import org.gradle.api.Task
-import org.gradle.api.internal.project.DefaultProject
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.project.taskfactory.ITaskFactory
-import org.gradle.groovy.scripts.DefaultScript
-import org.gradle.groovy.scripts.Script
-import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.testfixtures.ProjectBuilder
-
-import java.rmi.server.UID
-
-/**
- * @author Hans Dockter
- */
-class HelperUtil {
-     public static final Closure TEST_CLOSURE = {}
-
-     static <T extends Task> T createTask(Class<T> type) {
-         return createTask(type, createRootProject())
-     }
-
-     static <T extends Task> T createTask(Class<T> type, Map taskFields) {
-         def task = createTask(type, createRootProject())
-         hackInTaskProperties(type, task, taskFields)
-         return task
-     }
-
-    private static void hackInTaskProperties(Class type, Task task, Map args) {
-        args.each { k, v ->
-            def field = type.getDeclaredField(k)
-            field.setAccessible(true)
-            field.set(task, v)
-        }
-    }
-
-    static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
-         return createTask(type, project, 'name')
-     }
-
-     static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
-         return project.services.get(ITaskFactory).createTask([name: name, type: type])
-     }
-
-    static ProjectBuilder builder() {
-        return ProjectBuilder.builder().withProjectDir(TestNameTestDirectoryProvider.newInstance().testDirectory)
-    }
-
-     static DefaultProject createRootProject() {
-         createRootProject(TestNameTestDirectoryProvider.newInstance().testDirectory)
-     }
-
-     static DefaultProject createRootProject(File rootDir) {
-         return ProjectBuilder
-                 .builder()
-                 .withProjectDir(rootDir)
-                 .build()
-     }
-
-     static DefaultProject createChildProject(DefaultProject parent, String name, File projectDir = null) {
-         return ProjectBuilder
-                 .builder()
-                 .withName(name)
-                 .withParent(parent)
-                 .withProjectDir(projectDir)
-                 .build();
-     }
-
-     static DefaultModuleDescriptor createModuleDescriptor(Set confs) {
-         DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), "status", null)
-         confs.each { moduleDescriptor.addConfiguration(new Configuration(it)) }
-         return moduleDescriptor;
-     }
-
-     static groovy.lang.Script createScript(String code) {
-         new GroovyShell().parse(code)
-     }
-
-     static Object call(String text, Object... params) {
-         toClosure(text).call(*params)
-     }
-
-     static Closure toClosure(String text) {
-         return new GroovyShell().evaluate("return " + text)
-     }
-
-     static Closure toClosure(ScriptSource source) {
-         CompilerConfiguration configuration = new CompilerConfiguration();
-         configuration.setScriptBaseClass(TestScript.getName());
-
-         GroovyShell shell = new GroovyShell(configuration)
-         Script script = shell.parse(source.resource.text)
-         script.setScriptSource(source)
-         return script.run()
-     }
-
-     static Closure toClosure(TestClosure closure) {
-         return { param -> closure.call(param) }
-     }
-
-     static Closure returns(Object value) {
-         return { value }
-     }
-
-     static Closure createSetterClosure(String name, String value) {
-         return {
-             "set$name"(value)
-         }
-     }
-
-     static String createUniqueId() {
-         return new UID().toString();
-     }
- }
-
-
-interface TestClosure {
-    Object call(Object param);
-}
-
-abstract class TestScript extends DefaultScript {
-}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
deleted file mode 100644
index 5ceada1..0000000
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.util;
-
-import org.gradle.api.Buildable;
-import org.gradle.api.Task;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.CompositeFileCollection;
-import org.gradle.api.internal.file.UnionFileCollection;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.gradle.api.internal.file.collections.DefaultFileCollectionResolveContext;
-import org.hamcrest.*;
-import org.jmock.api.Action;
-import org.jmock.api.Invocation;
-import org.jmock.internal.ReturnDefaultValueAction;
-
-import java.io.*;
-import java.util.*;
-import java.util.regex.Pattern;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertTrue;
-
-public class Matchers {
-    /**
-     * A reimplementation of hamcrest's isA() but without the broken generics.
-     */
-    @Factory
-    public static Matcher<Object> isA(final Class<?> type) {
-        return new BaseMatcher<Object>() {
-            public boolean matches(Object item) {
-                return type.isInstance(item);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("instanceof ").appendValue(type);
-            }
-        };
-    }
-
-    @Factory
-    public static <T> Matcher<T> reflectionEquals(T equalsTo) {
-        return new ReflectionEqualsMatcher<T>(equalsTo);
-    }
-
-    @Factory
-    public static <T, S extends Iterable<? extends T>> Matcher<S> hasSameItems(final S items) {
-        return new BaseMatcher<S>() {
-            public boolean matches(Object o) {
-                Iterable<? extends T> iterable = (Iterable<? extends T>) o;
-                List<T> actual = new ArrayList<T>();
-                for (T t : iterable) {
-                    actual.add(t);
-                }
-                List<T> expected = new ArrayList<T>();
-                for (T t : items) {
-                    expected.add(t);
-                }
-
-                return expected.equals(actual);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an Iterable that has same items as ").appendValue(items);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends CharSequence> Matcher<T> matchesRegexp(final String pattern) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                return Pattern.compile(pattern).matcher((CharSequence) o).matches();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends CharSequence> Matcher<T> matchesRegexp(final Pattern pattern) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                return pattern.matcher((CharSequence) o).matches();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
-            }
-        };
-    }
-
-    @Factory
-    public static <T 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) {
-                return strictlyEquals(o, other);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an Object that strictly equals ").appendValue(other);
-            }
-        };
-    }
-
-    public static boolean strictlyEquals(Object a, Object b) {
-        if (!a.equals(b)) {
-            return false;
-        }
-        if (!b.equals(a)) {
-            return false;
-        }
-        if (!a.equals(a)) {
-            return false;
-        }
-        if (b.equals(null)) {
-            return false;
-        }
-        if (b.equals(new Object())) {
-            return false;
-        }
-        if (a.hashCode() != b.hashCode()) {
-            return false;
-        }
-        return true;
-
-    }
-
-    @Factory
-    @Deprecated
-    /**
-     * Please avoid using as the hamcrest way of reporting error wraps a multi-line
-     * text into a single line and makes hard to understand the problem.
-     * Instead, please try to use the spock/groovy assert and {@link #containsLine(String, String)}
-     */
-    public static Matcher<String> containsLine(final String line) {
-        return new BaseMatcher<String>() {
-            public boolean matches(Object o) {
-                return containsLine(equalTo(line)).matches(o);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a String that contains line ").appendValue(line);
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<String> containsLine(final Matcher<? super String> matcher) {
-        return new BaseMatcher<String>() {
-            public boolean matches(Object o) {
-                String str = (String) o;
-                return containsLine(str, matcher);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a String that contains line that is ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    public static boolean containsLine(String action, String expected) {
-        return containsLine(action, equalTo(expected));
-    }
-
-    public static boolean containsLine(String actual, Matcher<? super String> matcher) {
-        BufferedReader reader = new BufferedReader(new StringReader(actual));
-        String line;
-        try {
-            while ((line = reader.readLine()) != null) {
-                if (matcher.matches(line)) {
-                    return true;
-                }
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        return false;
-    }
-
-    @Factory
-    public static Matcher<Iterable<?>> isEmpty() {
-        return new BaseMatcher<Iterable<?>>() {
-            public boolean matches(Object o) {
-                Iterable<?> iterable = (Iterable<?>) o;
-                return iterable != null && !iterable.iterator().hasNext();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an empty Iterable");
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Map<?, ?>> isEmptyMap() {
-        return new BaseMatcher<Map<?, ?>>() {
-            public boolean matches(Object o) {
-                Map<?, ?> map = (Map<?, ?>) o;
-                return map != null && map.isEmpty();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an empty map");
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Object> isSerializable() {
-        return new BaseMatcher<Object>() {
-            public boolean matches(Object o) {
-                try {
-                    new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(o);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-                return true;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("is serializable");
-            }
-        };
-    }
-    
-    @Factory
-    public static Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
-        return new BaseMatcher<Throwable>() {
-            public boolean matches(Object o) {
-                Throwable t = (Throwable) o;
-                return matcher.matches(t.getMessage());
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an exception with message that is ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Task> dependsOn(final String... tasks) {
-        return dependsOn(equalTo(new HashSet<String>(Arrays.asList(tasks))));
-    }
-
-    @Factory
-    public static Matcher<Task> dependsOn(Matcher<? extends Iterable<String>> matcher) {
-        return dependsOn(matcher, false);
-    }
-
-    @Factory
-    public static Matcher<Task> dependsOnPaths(Matcher<? extends Iterable<String>> matcher) {
-        return dependsOn(matcher, true);
-    }
-
-    private static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher, final boolean matchOnPaths) {
-        return new BaseMatcher<Task>() {
-            public boolean matches(Object o) {
-                Task task = (Task) o;
-                Set<String> names = new HashSet<String>();
-                Set<? extends Task> depTasks = task.getTaskDependencies().getDependencies(task);
-                for (Task depTask : depTasks) {
-                    names.add(matchOnPaths ? depTask.getPath() : depTask.getName());
-                }
-                boolean matches = matcher.matches(names);
-                if (!matches) {
-                    StringDescription description = new StringDescription();
-                    matcher.describeTo(description);
-                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
-                }
-                return matches;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a Task that depends on ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends Buildable> Matcher<T> builtBy(String... tasks) {
-        return builtBy(equalTo(new HashSet<String>(Arrays.asList(tasks))));
-    }
-
-    @Factory
-    public static <T extends Buildable> Matcher<T> builtBy(final Matcher<? extends Iterable<String>> matcher) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                Buildable task = (Buildable) o;
-                Set<String> names = new HashSet<String>();
-                Set<? extends Task> depTasks = task.getBuildDependencies().getDependencies(null);
-                for (Task depTask : depTasks) {
-                    names.add(depTask.getName());
-                }
-                boolean matches = matcher.matches(names);
-                if (!matches) {
-                    StringDescription description = new StringDescription();
-                    matcher.describeTo(description);
-                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
-                }
-                return matches;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a Buildable that is built by ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends FileCollection> Matcher<T> sameCollection(final FileCollection expected) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                FileCollection actual = (FileCollection) o;
-                List<? extends FileCollection> actualCollections = unpack(actual);
-                List<? extends FileCollection> expectedCollections = unpack(expected);
-                boolean equals = actualCollections.equals(expectedCollections);
-                if (!equals) {
-                    System.out.println("expected: " + expectedCollections);
-                    System.out.println("actual: " + actualCollections);
-                }
-                return equals;
-            }
-
-            private List<? extends FileCollection> unpack(FileCollection expected) {
-                if (expected instanceof UnionFileCollection) {
-                    UnionFileCollection collection = (UnionFileCollection) expected;
-                    return new ArrayList<FileCollection>(collection.getSources());
-                }
-                if (expected instanceof DefaultConfigurableFileCollection) {
-                    DefaultConfigurableFileCollection collection = (DefaultConfigurableFileCollection) expected;
-                    return new ArrayList<FileCollection>((Set) collection.getFrom());
-                }
-                if (expected instanceof CompositeFileCollection) {
-                    CompositeFileCollection collection = (CompositeFileCollection) expected;
-                    DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext();
-                    collection.resolve(context);
-                    return context.resolveAsFileCollections();
-                }
-                throw new RuntimeException("Cannot get children of " + expected);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("same file collection as ").appendValue(expected);
-            }
-        };
-    }
-
-    /**
-     * Returns a placeholder for a mock method parameter.
-     */
-    public static <T> Collector<T> collector() {
-        return new Collector<T>();
-    }
-
-    /**
-     * Returns an action which collects the first parameter into the given placeholder.
-     */
-    public static CollectAction collectTo(Collector<?> collector) {
-        return new CollectAction(collector);
-    }
-
-    public static class CollectAction implements Action {
-        private Action action = new ReturnDefaultValueAction();
-        private final Collector<?> collector;
-
-        public CollectAction(Collector<?> collector) {
-            this.collector = collector;
-        }
-
-        public Action then(Action action) {
-            this.action = action;
-            return this;
-        }
-
-        public void describeTo(Description description) {
-            description.appendText("collect parameter then ").appendDescriptionOf(action);
-        }
-
-        public Object invoke(Invocation invocation) throws Throwable {
-            collector.setValue(invocation.getParameter(0));
-            return action.invoke(invocation);
-        }
-    }
-
-    public static class Collector<T> {
-        private T value;
-        private boolean set;
-
-        public T get() {
-            assertTrue(set);
-            return value;
-        }
-
-        void setValue(Object parameter) {
-            value = (T) parameter;
-            set = true;
-        }
-    }
-}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestTask.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestTask.groovy
index eadda17..eb6d41a 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestTask.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestTask.groovy
@@ -18,9 +18,6 @@ package org.gradle.util
 
 import org.gradle.api.internal.ConventionTask
 
-/**
- * @author Hans Dockter
- */
 class TestTask extends ConventionTask  {
     TestTask self
     String customProp
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy
new file mode 100644
index 0000000..572f624
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.util
+
+import org.apache.ivy.core.module.descriptor.Configuration
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.gradle.api.Task
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.groovy.scripts.DefaultScript
+import org.gradle.groovy.scripts.Script
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.ProjectBuilder
+
+import java.rmi.server.UID
+
+class TestUtil {
+     public static final Closure TEST_CLOSURE = {}
+
+     static <T extends Task> T createTask(Class<T> type) {
+         return createTask(type, createRootProject())
+     }
+
+     static <T extends Task> T createTask(Class<T> type, Map taskFields) {
+         def task = createTask(type, createRootProject())
+         hackInTaskProperties(type, task, taskFields)
+         return task
+     }
+
+    private static void hackInTaskProperties(Class type, Task task, Map args) {
+        args.each { k, v ->
+            def field = type.getDeclaredFields().find { it.name == k }
+            if (field) {
+                field.setAccessible(true)
+                field.set(task, v)
+            } else {
+                //I'm feeling lucky
+                task."$k" = v
+            }
+        }
+    }
+
+    static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
+         return createTask(type, project, 'name')
+     }
+
+     static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
+         return project.services.get(ITaskFactory).createTask([name: name, type: type])
+     }
+
+    static ProjectBuilder builder() {
+        return ProjectBuilder.builder().withProjectDir(TestNameTestDirectoryProvider.newInstance().testDirectory)
+    }
+
+     static DefaultProject createRootProject() {
+         createRootProject(TestNameTestDirectoryProvider.newInstance().testDirectory)
+     }
+
+     static DefaultProject createRootProject(File rootDir) {
+         return ProjectBuilder
+                 .builder()
+                 .withProjectDir(rootDir)
+                 .build()
+     }
+
+     static DefaultProject createChildProject(DefaultProject parent, String name, File projectDir = null) {
+         return ProjectBuilder
+                 .builder()
+                 .withName(name)
+                 .withParent(parent)
+                 .withProjectDir(projectDir)
+                 .build();
+     }
+
+     static DefaultModuleDescriptor createModuleDescriptor(Set confs) {
+         DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(new ModuleRevisionId(new ModuleId('org', 'name'), 'rev'), "status", null)
+         confs.each { moduleDescriptor.addConfiguration(new Configuration(it)) }
+         return moduleDescriptor;
+     }
+
+     static groovy.lang.Script createScript(String code) {
+         new GroovyShell().parse(code)
+     }
+
+     static Object call(String text, Object... params) {
+         toClosure(text).call(*params)
+     }
+
+     static Closure toClosure(String text) {
+         return new GroovyShell().evaluate("return " + text)
+     }
+
+     static Closure toClosure(ScriptSource source) {
+         CompilerConfiguration configuration = new CompilerConfiguration();
+         configuration.setScriptBaseClass(TestScript.getName());
+
+         GroovyShell shell = new GroovyShell(configuration)
+         Script script = shell.parse(source.resource.text)
+         script.setScriptSource(source)
+         return script.run()
+     }
+
+     static Closure toClosure(TestClosure closure) {
+         return { param -> closure.call(param) }
+     }
+
+     static Closure returns(Object value) {
+         return { value }
+     }
+
+     static Closure createSetterClosure(String name, String value) {
+         return {
+             "set$name"(value)
+         }
+     }
+
+     static String createUniqueId() {
+         return new UID().toString();
+     }
+ }
+
+
+interface TestClosure {
+    Object call(Object param);
+}
+
+abstract class TestScript extends DefaultScript {
+}
diff --git a/subprojects/cpp/cpp.gradle b/subprojects/cpp/cpp.gradle
index 0bfc388..5881e21 100644
--- a/subprojects/cpp/cpp.gradle
+++ b/subprojects/cpp/cpp.gradle
@@ -15,18 +15,16 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
     compile project(':core')
     compile project(":plugins")
     compile project(":ide")
+    compile libraries.commons_io
     integTestRuntime project(":maven")
 }
 
-
-integTestTasks.all {
-    if (isWindows && systemProperties['org.gradle.integtest.executer'] == "embedded") {
-        systemProperties['org.gradle.integtest.executer'] =  "forking"
-    }
-}
-
 useTestFixtures()
+useTestFixtures(project: ":messaging")
+useTestFixtures(sourceSet: "testFixtures")
+
+//useClassycle()
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioFileCustomizationIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioFileCustomizationIntegrationTest.groovy
new file mode 100755
index 0000000..05af27d
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioFileCustomizationIntegrationTest.groovy
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio
+
+import org.gradle.ide.visualstudio.fixtures.FiltersFile
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+
+class VisualStudioFileCustomizationIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    def app = new CppHelloWorldApp()
+
+    def setup() {
+        app.writeSources(file("src/main"))
+        buildFile << """
+    apply plugin: 'cpp'
+    apply plugin: 'visual-studio'
+
+    model {
+        platforms {
+            win32 {
+                architecture "i386"
+            }
+        }
+        buildTypes {
+            debug
+            release
+        }
+    }
+    executables {
+        main {}
+    }
+"""
+    }
+
+    def "can specify location of generated files"() {
+        when:
+        file("gradlew.bat") << "dummy wrapper"
+        buildFile << '''
+    model {
+        visualStudio {
+            projects.all { project ->
+                projectFile.location = "very/deeply/nested/${project.name}.vcxproj"
+                filtersFile.location = "other/filters.vcxproj.filters"
+            }
+            solutions.all {
+                solutionFile.location = "vs/${it.name}.solution"
+            }
+        }
+    }
+'''
+        and:
+        run "mainVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainExeVisualStudio"
+
+        and:
+        final projectFile = projectFile("very/deeply/nested/mainExe.vcxproj")
+        assert projectFile.headerFiles == app.headerFiles*.withPath("../../../src/main").sort()
+        assert projectFile.sourceFiles == ['../../../build.gradle'] + app.sourceFiles*.withPath("../../../src/main").sort()
+        projectFile.projectConfigurations.values().each {
+            assert it.buildCommand == "../../../gradlew.bat -p \"../../..\" :install${it.name.capitalize()}MainExecutable"
+            assert it.outputFile == OperatingSystem.current().getExecutableName("../../../build/install/mainExecutable/${it.name}/lib/main")
+        }
+        def filtersFile = filtersFile("other/filters.vcxproj.filters")
+
+        and:
+        final mainSolution = solutionFile("vs/mainExe.solution")
+        mainSolution.assertHasProjects("mainExe")
+        mainSolution.assertReferencesProject(projectFile, ['debug', 'release'])
+
+        // Ensure that clean handles custom file locations
+        when:
+        run "cleanVisualStudio"
+
+        then:
+        projectFile.projectFile.assertDoesNotExist()
+        filtersFile.file.assertDoesNotExist()
+        mainSolution.file.assertDoesNotExist()
+    }
+
+    def "can add xml configuration to generated project files"() {
+        when:
+        buildFile << """
+    model {
+        visualStudio {
+            projects.all { project ->
+                projectFile.withXml { xml ->
+                    Node globals = xml.asNode().PropertyGroup.find({it.'@Label' == 'Globals'}) as Node
+                    globals.appendNode("ExtraInfo", "Some extra info")
+                    globals.appendNode("ProjectName", project.name)
+                }
+            }
+        }
+    }
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final projectFile = projectFile("mainExe.vcxproj")
+        projectFile.globals.ExtraInfo[0].text() == "Some extra info"
+        projectFile.globals.ProjectName[0].text() == "mainExe"
+    }
+
+    def "can add xml configuration to generated filter files"() {
+        when:
+        buildFile << '''
+    model {
+        visualStudio {
+            projects.all { project ->
+                filtersFile.withXml { xml ->
+                    xml.asNode().appendNode("ExtraContent", "Filter - ${project.name}")
+                }
+            }
+        }
+    }
+'''
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final filtersFile = filtersFile("mainExe.vcxproj.filters")
+        filtersFile.xml.ExtraContent[0].text() == "Filter - mainExe"
+    }
+
+    def "can add text content to generated solution files"() {
+        when:
+        buildFile << '''
+    model {
+        visualStudio {
+            solutions.all { solution ->
+                solution.solutionFile.withContent { content ->
+                    String projectList = solution.projects.collect({it.name}).join(',')
+                    int insertPos = text.lastIndexOf("EndGlobal")
+                    content.text = content.text.replace("EndGlobal", """
+    GlobalSection(MyGlobalSection)
+       Project-list: ${projectList}
+    EndGlobalSection
+EndGlobal
+""")
+                }
+            }
+        }
+    }
+'''
+
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final solutionFile = solutionFile("mainExe.sln")
+        solutionFile.content.contains "GlobalSection(MyGlobalSection)"
+        solutionFile.content.contains "Project-list: mainExe"
+    }
+
+    def "can configure gradle command line"() {
+        when:
+        buildFile << """
+    executables {
+        main {}
+    }
+    tasks.withType(GenerateProjectFileTask) {
+        it.gradleExe "myCustomGradleExe"
+        it.gradleArgs "--configure-on-demand --another"
+    }
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final projectFile = projectFile("mainExe.vcxproj")
+        projectFile.projectConfigurations.values().each {
+            assert it.buildCommand == "myCustomGradleExe --configure-on-demand --another :install${it.name.capitalize()}MainExecutable"
+        }
+    }
+
+    private SolutionFile solutionFile(String path) {
+        return new SolutionFile(file(path))
+    }
+
+    private ProjectFile projectFile(String path) {
+        return new ProjectFile(file(path))
+    }
+
+    private FiltersFile filtersFile(String path) {
+        return new FiltersFile(file(path))
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioMultiProjectIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioMultiProjectIntegrationTest.groovy
new file mode 100755
index 0000000..6cea134
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioMultiProjectIntegrationTest.groovy
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio
+
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
+
+class VisualStudioMultiProjectIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    private final Set<String> projectConfigurations = ['debug', 'release'] as Set
+
+    def app = new CppHelloWorldApp()
+
+    def setup() {
+        buildFile << """
+    subprojects {
+        apply plugin: 'cpp'
+        apply plugin: 'visual-studio'
+
+        model {
+            platforms {
+                win32 {
+                    architecture "i386"
+                }
+            }
+            buildTypes {
+                debug
+                release
+            }
+        }
+    }
+
+"""
+    }
+
+    def "create visual studio solution for executable that depends on static library in another project"() {
+        when:
+        app.executable.writeSources(file("exe/src/main"))
+        app.library.writeSources(file("lib/src/hello"))
+
+        settingsFile.text = "include ':exe', ':lib'"
+        file("exe", "build.gradle") << """
+    executables {
+        main {}
+    }
+    sources.main.cpp.lib project: ':lib', library: 'hello', linkage: 'static'
+"""
+        file("lib", "build.gradle") << """
+    libraries {
+        hello {}
+    }
+"""
+        and:
+        run ":exe:mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("exe/exe_mainExe.vcxproj")
+        exeProject.assertHasComponentSources(app.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == projectConfigurations
+        exeProject.projectConfigurations.values().each {
+            assert it.includePath == filePath("src/main/headers", "../lib/src/hello/headers")
+            assert it.buildCommand == "gradle -p \"..\" :exe:install${it.name.capitalize()}MainExecutable"
+        }
+
+        and:
+        final libProject = projectFile("lib/lib_helloLib.vcxproj")
+        libProject.assertHasComponentSources(app.library, "src/hello")
+        libProject.projectConfigurations.keySet() == projectConfigurations
+        libProject.projectConfigurations.values().each {
+            assert it.includePath == filePath("src/hello/headers")
+            assert it.buildCommand == "gradle -p \"..\" :lib:${it.name}HelloStaticLibrary"
+        }
+
+        and:
+        final mainSolution = solutionFile("exe/exe_mainExe.sln")
+        mainSolution.assertHasProjects("exe_mainExe", "lib_helloLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(libProject, projectConfigurations)
+    }
+
+    def "create visual studio solution for executable that transitively depends on multiple projects"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.writeSources(file("exe/src/main"), file("lib/src/hello"), file("greet/src/greetings"))
+
+        and:
+        settingsFile.text = "include ':exe', ':lib', ':greet'"
+        buildFile << """
+        project(":exe") {
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib project: ':lib', library: 'hello'
+        }
+        project(":lib") {
+            apply plugin: "cpp"
+            libraries {
+                hello {}
+            }
+            sources.hello.cpp.lib project: ':greet', library: 'greetings', linkage: 'static'
+        }
+        project(":greet") {
+            apply plugin: "cpp"
+            libraries {
+                greetings {}
+            }
+        }
+        """
+
+        when:
+        succeeds ":exe:mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("exe/exe_mainExe.vcxproj")
+        final helloProject = projectFile("lib/lib_helloDll.vcxproj")
+        final greetProject = projectFile("greet/greet_greetingsLib.vcxproj")
+        final mainSolution = solutionFile("exe/exe_mainExe.sln")
+
+        and:
+        mainSolution.assertHasProjects("exe_mainExe", "lib_helloDll", "greet_greetingsLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(helloProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetProject, projectConfigurations)
+
+        and:
+        exeProject.projectConfigurations['debug'].includePath == filePath("src/main/headers", "../lib/src/hello/headers")
+        helloProject.projectConfigurations['debug'].includePath == filePath("src/hello/headers", "../greet/src/greetings/headers")
+        greetProject.projectConfigurations['debug'].includePath == filePath("src/greetings/headers")
+    }
+
+    def "create visual studio solution where multiple components have same name"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.writeSources(file("exe/src/main"), file("lib/src/main"), file("greet/src/main"))
+
+        and:
+        settingsFile.text = "include ':exe', ':lib', ':greet'"
+        buildFile << """
+        project(":exe") {
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib project: ':lib', library: 'main'
+        }
+        project(":lib") {
+            apply plugin: "cpp"
+            libraries {
+                main {}
+            }
+            sources.main.cpp.lib project: ':greet', library: 'main', linkage: 'static'
+        }
+        project(":greet") {
+            apply plugin: "cpp"
+            libraries {
+                main {}
+            }
+        }
+        """
+
+        when:
+        succeeds ":exe:mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("exe/exe_mainExe.vcxproj")
+        final helloProject = projectFile("lib/lib_mainDll.vcxproj")
+        final greetProject = projectFile("greet/greet_mainLib.vcxproj")
+        final mainSolution = solutionFile("exe/exe_mainExe.sln")
+
+        and:
+        mainSolution.assertHasProjects("exe_mainExe", "lib_mainDll", "greet_mainLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(helloProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetProject, projectConfigurations)
+
+        and:
+        exeProject.projectConfigurations['debug'].includePath == filePath("src/main/headers", "../lib/src/main/headers")
+        helloProject.projectConfigurations['debug'].includePath == filePath("src/main/headers", "../greet/src/main/headers")
+        greetProject.projectConfigurations['debug'].includePath == filePath("src/main/headers")
+    }
+
+    def "create visual studio solution for executable with project dependency cycle"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.writeSources(file("exe/src/main"), file("lib/src/hello"), file("exe/src/greetings"))
+
+        and:
+        settingsFile.text = "include ':exe', ':lib'"
+        buildFile << """
+        project(":exe") {
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            libraries {
+                greetings {}
+            }
+            sources.main.cpp.lib project: ':lib', library: 'hello'
+        }
+        project(":lib") {
+            apply plugin: "cpp"
+            libraries {
+                hello {}
+            }
+            sources.hello.cpp.lib project: ':exe', library: 'greetings', linkage: 'static'
+        }
+        """
+
+        when:
+        succeeds ":exe:mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("exe/exe_mainExe.vcxproj")
+        final helloProject = projectFile("lib/lib_helloDll.vcxproj")
+        final greetProject = projectFile("exe/exe_greetingsLib.vcxproj")
+        final mainSolution = solutionFile("exe/exe_mainExe.sln")
+
+        and:
+        mainSolution.assertHasProjects("exe_mainExe", "lib_helloDll", "exe_greetingsLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(helloProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetProject, projectConfigurations)
+
+        and:
+        exeProject.projectConfigurations['debug'].includePath == filePath("src/main/headers", "../lib/src/hello/headers")
+        helloProject.projectConfigurations['debug'].includePath == filePath("src/hello/headers", "../exe/src/greetings/headers")
+        greetProject.projectConfigurations['debug'].includePath == filePath("src/greetings/headers")
+    }
+
+    def "detects gradle wrapper and uses in vs project"() {
+        when:
+        def gradlew = file("gradlew.bat") << "dummy wrapper"
+
+        settingsFile.text = "include ':exe'"
+        buildFile << """
+    project(':exe') {
+        executables {
+            main {}
+        }
+    }
+"""
+        and:
+        run ":exe:mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("exe/exe_mainExe.vcxproj")
+        exeProject.projectConfigurations.values().each {
+            assert it.buildCommand == "../gradlew.bat -p \"..\" :exe:install${it.name.capitalize()}MainExecutable"
+        }
+    }
+
+    def "cleanVisualStudio removes all generated visual studio files"() {
+        when:
+        settingsFile.text = "include ':exe', ':lib'"
+        buildFile << """
+    project(':exe') {
+        executables {
+            main {}
+        }
+        sources.main.cpp.lib project: ':lib', library: 'main', linkage: 'static'
+    }
+    project(':lib') {
+        libraries {
+            main {}
+        }
+    }
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        def generatedFiles = [
+                file("exe/exe_mainExe.sln"),
+                file("exe/exe_mainExe.vcxproj"),
+                file("exe/exe_mainExe.vcxproj.filters"),
+                file("lib/lib_mainDll.sln"),
+                file("lib/lib_mainDll.vcxproj"),
+                file("lib/lib_mainDll.vcxproj.filters")
+        ]
+        generatedFiles*.assertExists()
+
+        when:
+        run "cleanVisualStudio"
+
+        then:
+        generatedFiles*.assertDoesNotExist()
+    }
+
+    private SolutionFile solutionFile(String path) {
+        return new SolutionFile(file(path))
+    }
+
+    private ProjectFile projectFile(String path) {
+        return new ProjectFile(file(path))
+    }
+
+    private static String filePath(String... paths) {
+        return (paths as List).join(";")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy
new file mode 100755
index 0000000..fe9e651
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy
@@ -0,0 +1,840 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio
+
+import org.gradle.ide.visualstudio.fixtures.FiltersFile
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.*
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+
+class VisualStudioSingleProjectIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    private final Set<String> projectConfigurations = ['win32Debug', 'win32Release', 'x64Debug', 'x64Release'] as Set
+
+    def app = new CppHelloWorldApp()
+
+    def setup() {
+        buildFile << """
+    apply plugin: 'cpp'
+    apply plugin: 'visual-studio'
+
+    model {
+        platforms {
+            win32 {
+                architecture "i386"
+            }
+            x64 {
+                architecture "amd64"
+            }
+        }
+        buildTypes {
+            debug
+            release
+        }
+    }
+
+"""
+    }
+
+    def "create visual studio solution for single executable"() {
+        when:
+        app.writeSources(file("src/main"))
+        buildFile << """
+    executables {
+        main {}
+    }
+    binaries.all {
+        cppCompiler.define "TEST"
+        cppCompiler.define "foo", "bar"
+    }
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainExeVisualStudio"
+
+        and:
+        final projectFile = projectFile("mainExe.vcxproj")
+        projectFile.assertHasComponentSources(app, "src/main")
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        projectFile.projectConfigurations.values().each {
+            assert it.macros == "TEST;foo=bar"
+            assert it.includePath == filePath("src/main/headers")
+            assert it.buildCommand == "gradle :install${it.name.capitalize()}MainExecutable"
+            assert it.outputFile == OperatingSystem.current().getExecutableName("build/install/mainExecutable/${it.name}/lib/main")
+        }
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe")
+        mainSolution.assertReferencesProject(projectFile, projectConfigurations)
+    }
+
+    def "create visual studio solution for shared and library"() {
+        when:
+        app.library.writeSources(file("src/main"))
+        buildFile << """
+    libraries {
+        main {}
+    }
+"""
+        and:
+        run "mainDllVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainDllVisualStudio"
+
+        and:
+        final projectFile = projectFile("mainDll.vcxproj")
+        projectFile.assertHasComponentSources(app.library, "src/main")
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        projectFile.projectConfigurations.values().each {
+            assert it.includePath == filePath("src/main/headers")
+            assert it.buildCommand == "gradle :${it.name}MainSharedLibrary"
+            assert it.outputFile == OperatingSystem.current().getSharedLibraryName("build/binaries/mainSharedLibrary/${it.name}/main")
+        }
+
+        and:
+        final mainSolution = solutionFile("mainDll.sln")
+        mainSolution.assertHasProjects("mainDll")
+        mainSolution.assertReferencesProject(projectFile, projectConfigurations)
+    }
+
+    def "create visual studio solution for static library"() {
+        when:
+        app.library.writeSources(file("src/main"))
+        buildFile << """
+    libraries {
+        main {
+        }
+    }
+"""
+        and:
+        run "mainLibVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainLibVisualStudio"
+
+        and:
+        final projectFile = projectFile("mainLib.vcxproj")
+        projectFile.assertHasComponentSources(app.library, "src/main")
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        projectFile.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers")
+
+        and:
+        final mainSolution = solutionFile("mainLib.sln")
+        mainSolution.assertHasProjects("mainLib")
+        mainSolution.assertReferencesProject(projectFile, projectConfigurations)
+    }
+
+    def "lifecycle task creates visual studio solution for buildable static and shared libraries"() {
+        when:
+        app.library.writeSources(file("src/main"))
+        buildFile << """
+    libraries {
+        both {}
+        staticOnly {
+            binaries.withType(SharedLibraryBinary) {
+                buildable false
+            }
+        }
+    }
+"""
+        and:
+        run "bothVisualStudio", "staticOnlyVisualStudio"
+
+        then:
+        executedAndNotSkipped ":bothDllVisualStudio", ":bothLibVisualStudio", ":staticOnlyLibVisualStudio"
+
+        and:
+        file("staticOnlyLib.sln").assertExists()
+        file("staticOnlyDll.sln").assertDoesNotExist()
+    }
+
+    def "create visual studio solution for defined static library"() {
+        when:
+        app.library.writeSources(file("src/main"))
+        buildFile << """
+    libraries {
+        main {
+            binaries.withType(SharedLibraryBinary) {
+                buildable = false
+            }
+        }
+    }
+"""
+        and:
+        run "mainLibVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainLibVisualStudio"
+
+        and:
+        final projectFile = projectFile("mainLib.vcxproj")
+        projectFile.assertHasComponentSources(app.library, "src/main")
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        projectFile.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers")
+
+        and:
+        final mainSolution = solutionFile("mainLib.sln")
+        mainSolution.assertHasProjects("mainLib")
+        mainSolution.assertReferencesProject(projectFile, projectConfigurations)
+    }
+
+    def "create visual studio solution for executable that depends on static library"() {
+        when:
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        buildFile << """
+    libraries {
+        hello {}
+    }
+    executables {
+        main {}
+    }
+    sources.main.cpp.lib libraries.hello.static
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("mainExe.vcxproj")
+        exeProject.assertHasComponentSources(app.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == projectConfigurations
+        exeProject.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers", "src/hello/headers")
+
+        and:
+        final libProject = projectFile("helloLib.vcxproj")
+        libProject.assertHasComponentSources(app.library, "src/hello")
+        libProject.projectConfigurations.keySet() == projectConfigurations
+        libProject.projectConfigurations['win32Debug'].includePath == filePath("src/hello/headers")
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe", "helloLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(libProject, projectConfigurations)
+    }
+
+    def "create visual studio solution for executable that depends on shared library"() {
+        when:
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        buildFile << """
+    libraries {
+        hello {}
+    }
+    executables {
+        main {}
+    }
+    sources.main.cpp.lib libraries.hello
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("mainExe.vcxproj")
+        exeProject.assertHasComponentSources(app.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == projectConfigurations
+        exeProject.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers", "src/hello/headers")
+
+        and:
+        final dllProject = projectFile("helloDll.vcxproj")
+        dllProject.assertHasComponentSources(app.library, "src/hello")
+        dllProject.projectConfigurations.keySet() == projectConfigurations
+        dllProject.projectConfigurations['win32Debug'].includePath == filePath("src/hello/headers")
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe", "helloDll")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(dllProject, projectConfigurations)
+    }
+
+    def "create visual studio solution for executable that depends on library that depends on another library"() {
+        given:
+        def testApp = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        testApp.writeSources(file("src/main"), file("src/hello"), file("src/greetings"))
+
+        buildFile << """
+            apply plugin: "cpp"
+            libraries {
+                greetings {}
+                hello {}
+            }
+            executables {
+                main {}
+            }
+            sources {
+                hello.cpp.lib libraries.greetings.static
+                main.cpp.lib libraries.hello
+            }
+        """
+        when:
+        run "mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("mainExe.vcxproj")
+        exeProject.assertHasComponentSources(testApp.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == projectConfigurations
+        exeProject.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers", "src/hello/headers")
+
+        and:
+        final helloDllProject = projectFile("helloDll.vcxproj")
+        helloDllProject.assertHasComponentSources(testApp.library, "src/hello")
+        helloDllProject.projectConfigurations.keySet() == projectConfigurations
+        helloDllProject.projectConfigurations['win32Debug'].includePath == filePath("src/hello/headers", "src/greetings/headers")
+
+        and:
+        final greetingsLibProject = projectFile("greetingsLib.vcxproj")
+        greetingsLibProject.assertHasComponentSources(testApp.greetingsLibrary, "src/greetings")
+        greetingsLibProject.projectConfigurations.keySet() == projectConfigurations
+        greetingsLibProject.projectConfigurations['win32Debug'].includePath == filePath("src/greetings/headers")
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe", "helloDll", "greetingsLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(helloDllProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetingsLibProject, projectConfigurations)
+    }
+
+    def "create visual studio solutions for 2 executables that depend on different linkages of the same library"() {
+        when:
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        buildFile << """
+    libraries {
+        hello {}
+    }
+    executables {
+        main {}
+        mainStatic {}
+    }
+    sources.main.cpp.lib libraries.hello
+    sources.mainStatic.cpp.source.srcDirs "src/main/cpp"
+    sources.mainStatic.cpp.lib libraries.hello.static
+"""
+        and:
+        run "mainVisualStudio", "mainStaticVisualStudio"
+
+        then:
+        solutionFile("mainExe.sln").assertHasProjects("mainExe", "helloDll")
+        solutionFile("mainStaticExe.sln").assertHasProjects("mainStaticExe", "helloLib")
+
+        and:
+        final exeProject = projectFile("mainExe.vcxproj")
+        final staticExeProject = projectFile("mainStaticExe.vcxproj")
+        exeProject.sourceFiles == staticExeProject.sourceFiles
+        exeProject.headerFiles == []
+        staticExeProject.headerFiles == []
+
+        and:
+        final dllProject = projectFile("helloDll.vcxproj")
+        final libProject = projectFile("helloLib.vcxproj")
+        dllProject.sourceFiles == libProject.sourceFiles
+        dllProject.headerFiles == libProject.headerFiles
+    }
+
+    def "create visual studio solutions for 2 executables that depend on different build types of the same library"() {
+        when:
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        buildFile << """
+    libraries {
+        hello {}
+    }
+    executables {
+        main {}
+        mainRelease {
+            targetBuildTypes "release"
+        }
+    }
+    sources.main.cpp.lib libraries.hello
+
+    sources.mainRelease.cpp.source.srcDirs "src/main/cpp"
+    sources.mainRelease.cpp.lib libraries.hello
+"""
+        and:
+        run "mainVisualStudio", "mainReleaseVisualStudio"
+
+        then:
+        solutionFile("mainExe.sln").assertHasProjects("mainExe", "helloDll")
+        solutionFile("mainReleaseExe.sln").assertHasProjects("mainReleaseExe", "helloDll")
+
+        and:
+        final helloProjectFile = projectFile("helloDll.vcxproj")
+        helloProjectFile.projectConfigurations.keySet() == projectConfigurations
+        final mainProjectFile = projectFile("mainExe.vcxproj")
+        mainProjectFile.projectConfigurations.keySet() == projectConfigurations
+        final mainReleaseProjectFile = projectFile("mainReleaseExe.vcxproj")
+        mainReleaseProjectFile.projectConfigurations.keySet() == ['win32', 'x64'] as Set
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertReferencesProject(helloProjectFile, projectConfigurations)
+        final mainReleaseSolution = solutionFile("mainReleaseExe.sln")
+        mainReleaseSolution.assertReferencesProject(helloProjectFile, [win32: 'win32Release', x64: 'x64Release'])
+    }
+
+    def "create visual studio project for executable that targets multiple platforms with the same architecture"() {
+        when:
+        app.writeSources(file("src/main"))
+        buildFile << """
+    model {
+        platforms {
+            otherWin32 {
+                architecture "i386"
+            }
+        }
+    }
+    executables {
+        main {
+            targetPlatforms "win32", "otherWin32"
+        }
+    }
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final mainProjectFile = projectFile("mainExe.vcxproj")
+        mainProjectFile.projectConfigurations.keySet() == ['win32Debug', 'otherWin32Debug', 'win32Release', 'otherWin32Release'] as Set
+    }
+
+    def "create visual studio solution for executable that has diamond dependency"() {
+        def testApp = new ExeWithDiamondDependencyHelloWorldApp()
+        testApp.writeSources(file("src/main"), file("src/hello"), file("src/greetings"))
+
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                greetings {}
+            }
+            sources.main.cpp.lib libraries.hello.shared
+            sources.main.cpp.lib libraries.greetings.static
+            sources.hello.cpp.lib libraries.greetings.static
+        """
+
+        when:
+        succeeds "mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("mainExe.vcxproj")
+        final helloProject = projectFile("helloDll.vcxproj")
+        final greetProject = projectFile("greetingsLib.vcxproj")
+        final mainSolution = solutionFile("mainExe.sln")
+
+        and:
+        mainSolution.assertHasProjects("mainExe", "helloDll", "greetingsLib")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(helloProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetProject, projectConfigurations)
+
+        and:
+        exeProject.assertHasComponentSources(testApp.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == projectConfigurations
+        exeProject.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers", "src/hello/headers", "src/greetings/headers")
+    }
+
+    def "create visual studio solution for executable that depends on both static and shared linkage of library"() {
+        given:
+        def testApp = new ExeWithDiamondDependencyHelloWorldApp()
+        testApp.writeSources(file("src/main"), file("src/hello"), file("src/greetings"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                greetings {}
+            }
+            sources.main.cpp.lib libraries.hello.shared
+            sources.main.cpp.lib libraries.greetings.shared
+            sources.hello.cpp.lib libraries.greetings.static
+        """
+
+        when:
+        succeeds "mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("mainExe.vcxproj")
+        final helloProject = projectFile("helloDll.vcxproj")
+        final greetDllProject = projectFile("greetingsDll.vcxproj")
+        final greetLibProject = projectFile("greetingsLib.vcxproj")
+        final mainSolution = solutionFile("mainExe.sln")
+
+        and:
+        mainSolution.assertHasProjects("mainExe", "helloDll", "greetingsLib", "greetingsDll")
+        mainSolution.assertReferencesProject(exeProject, projectConfigurations)
+        mainSolution.assertReferencesProject(helloProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetDllProject, projectConfigurations)
+        mainSolution.assertReferencesProject(greetLibProject, projectConfigurations)
+
+        and:
+        exeProject.assertHasComponentSources(testApp.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == projectConfigurations
+        exeProject.projectConfigurations['win32Debug'].includePath == filePath("src/main/headers", "src/hello/headers", "src/greetings/headers")
+
+        and:
+        helloProject.assertHasComponentSources(testApp.library, "src/hello")
+        helloProject.projectConfigurations.keySet() == projectConfigurations
+        helloProject.projectConfigurations['win32Debug'].includePath == filePath("src/hello/headers", "src/greetings/headers")
+    }
+
+    def "generate visual studio solution for executable with mixed sources"() {
+        given:
+        def testApp = new MixedLanguageHelloWorldApp(toolChain)
+        testApp.writeSources(file("src/main"))
+
+        and:
+        buildFile << """
+            apply plugin: 'c'
+            apply plugin: 'assembler'
+            executables {
+                main {}
+            }
+        """
+
+        when:
+        run "mainVisualStudio"
+
+        then:
+        final projectFile = projectFile("mainExe.vcxproj")
+        projectFile.assertHasComponentSources(testApp, "src/main")
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        with (projectFile.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/main/headers")
+        }
+
+        and:
+        solutionFile("mainExe.sln").assertHasProjects("mainExe")
+    }
+
+    @RequiresInstalledToolChain(VisualCpp)
+    def "generate visual studio solution for executable with windows resource files"() {
+        given:
+        def resourceApp = new WindowsResourceHelloWorldApp()
+        resourceApp.writeSources(file("src/main"))
+
+        and:
+        buildFile << """
+            apply plugin: 'windows-resources'
+            executables {
+                main {}
+            }
+            binaries.all {
+                rcCompiler.define "TEST"
+                rcCompiler.define "foo", "bar"
+            }
+        """
+
+        when:
+        run "mainVisualStudio"
+
+        then:
+        final projectFile = projectFile("mainExe.vcxproj")
+        final List<SourceFile> resources = resourceApp.resourceSources
+        final List<SourceFile> sources = resourceApp.sourceFiles - resources
+        assert projectFile.headerFiles == resourceApp.headerFiles*.withPath("src/main").sort()
+        assert projectFile.sourceFiles == ['build.gradle'] + sources*.withPath("src/main").sort()
+        assert projectFile.resourceFiles == resources*.withPath("src/main").sort()
+
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        with (projectFile.projectConfigurations['win32Debug']) {
+            macros == "TEST;foo=bar"
+            includePath == filePath("src/main/headers")
+        }
+
+        and:
+        solutionFile("mainExe.sln").assertHasProjects("mainExe")
+    }
+
+    def "builds solution for component with no source"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        when:
+        run "mainVisualStudio"
+
+        then:
+        final projectFile = projectFile("mainExe.vcxproj")
+        projectFile.sourceFiles == ['build.gradle']
+        projectFile.headerFiles == []
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+        with (projectFile.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/main/headers")
+        }
+
+        and:
+        solutionFile("mainExe.sln").assertHasProjects("mainExe")
+    }
+
+    def "visual studio project includes headers co-located with sources"() {
+        when:
+        // Write headers so they sit with sources
+        app.allFiles.each {
+            it.writeToFile(file("src/main/cpp/${it.name}"))
+        }
+        buildFile << """
+    executables {
+        main {}
+    }
+    sources.main.cpp.source.include "**/*.cpp"
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainExeVisualStudio"
+
+        and:
+        final projectFile = projectFile("mainExe.vcxproj")
+        assert projectFile.sourceFiles == ['build.gradle'] + app.sourceFiles.collect({"src/main/cpp/${it.name}"}).sort()
+        assert projectFile.headerFiles == app.headerFiles.collect({"src/main/cpp/${it.name}"}).sort()
+    }
+
+    def "visual studio solution with header-only library"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+
+        app.library.headerFiles*.writeToDir(file("src/helloApi"))
+        app.library.sourceFiles*.writeToDir(file("src/hello"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                helloApi {}
+                hello {}
+            }
+            sources.main.cpp.lib library: 'helloApi', linkage: 'api' // TODO:DAZ This should not be needed
+            sources.main.cpp.lib library: 'hello'
+            sources.hello.cpp.lib library: 'helloApi', linkage: 'api'
+        """
+
+        when:
+        succeeds "mainVisualStudio"
+
+        then:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe", "helloDll", "helloApiDll")
+
+        and:
+        final mainExeProject = projectFile("mainExe.vcxproj")
+        with (mainExeProject.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/main/headers", "src/helloApi/headers", "src/hello/headers")
+        }
+
+        and:
+        final helloDllProject = projectFile("helloDll.vcxproj")
+        with (helloDllProject.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/hello/headers", "src/helloApi/headers")
+        }
+
+        and:
+        final helloApiDllProject = projectFile("helloApiDll.vcxproj")
+        with (helloApiDllProject.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/helloApi/headers")
+        }
+    }
+
+    def "create visual studio solution for executable with variant conditional sources"() {
+        when:
+        app.writeSources(file("src/win32"))
+        app.alternate.writeSources(file("src/x64"))
+        buildFile << """
+    sources {
+        win32 {}
+        x64 {}
+    }
+    executables {
+        main {}
+    }
+    binaries.all {
+        source sources[it.targetPlatform.name]
+    }
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final projectFile = projectFile("mainExe.vcxproj")
+        projectFile.assertHasComponentSources(app, "src/win32", app.alternate, "src/x64")
+        projectFile.projectConfigurations.keySet() == projectConfigurations
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe")
+        mainSolution.assertReferencesProject(projectFile, projectConfigurations)
+    }
+
+    def "visual studio solution with pre-built library"() {
+        given:
+        app.writeSources(file("src/main"))
+        buildFile << """
+    model {
+        repositories {
+            libs(PrebuiltLibraries) {
+                test {
+                    headers.srcDir "libs/test/include"
+                }
+            }
+        }
+    }
+    executables {
+        main {}
+    }
+    sources.main.cpp.lib library: 'test', linkage: 'api'
+"""
+
+        when:
+        run "mainVisualStudio"
+
+        then:
+        executedAndNotSkipped ":mainExeVisualStudio"
+        and:
+
+        then:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe")
+
+        and:
+        final mainExeProject = projectFile("mainExe.vcxproj")
+        with (mainExeProject.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/main/headers", "libs/test/include")
+        }
+    }
+
+    def "visual studio solution for component graph with library dependency cycle"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        app.greetingsHeader.writeToDir(file("src/hello"))
+        app.greetingsSources*.writeToDir(file("src/greetings"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                greetings {}
+            }
+            sources.main.cpp.lib library: 'hello'
+            sources.hello.cpp.lib library: 'greetings', linkage: 'static'
+            sources.greetings.cpp.lib library: 'hello', linkage: 'api'
+        """
+
+        when:
+        succeeds "mainVisualStudio"
+
+        then:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe", "helloDll", "greetingsLib")
+
+        and:
+        final mainExeProject = projectFile("mainExe.vcxproj")
+        with (mainExeProject.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/main/headers", "src/hello/headers")
+        }
+
+        and:
+        final helloDllProject = projectFile("helloDll.vcxproj")
+        with (helloDllProject.projectConfigurations['win32Debug']) {
+            includePath == filePath( "src/hello/headers", "src/greetings/headers")
+        }
+
+        and:
+        final greetingsLibProject = projectFile("greetingsLib.vcxproj")
+        with (greetingsLibProject.projectConfigurations['win32Debug']) {
+            includePath == filePath("src/greetings/headers", "src/hello/headers")
+        }
+    }
+
+    def "create visual studio solution where referenced projects have different configurations"() {
+        when:
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        buildFile << """
+    libraries {
+        hello {}
+    }
+    executables {
+        main {
+            targetPlatforms "win32"
+            targetBuildTypes "release"
+        }
+    }
+    sources.main.cpp.lib libraries.hello
+"""
+        and:
+        run "mainVisualStudio"
+
+        then:
+        final exeProject = projectFile("mainExe.vcxproj")
+        exeProject.assertHasComponentSources(app.executable, "src/main")
+        exeProject.projectConfigurations.keySet() == ['release'] as Set
+        exeProject.projectConfigurations['release'].includePath == filePath("src/main/headers", "src/hello/headers")
+
+        and:
+        final dllProject = projectFile("helloDll.vcxproj")
+        dllProject.assertHasComponentSources(app.library, "src/hello")
+        dllProject.projectConfigurations.keySet() == projectConfigurations
+        dllProject.projectConfigurations['win32Debug'].includePath == filePath("src/hello/headers")
+
+        and:
+        final mainSolution = solutionFile("mainExe.sln")
+        mainSolution.assertHasProjects("mainExe", "helloDll")
+        mainSolution.assertReferencesProject(exeProject, ['release'])
+        mainSolution.assertReferencesProject(dllProject, [release: 'win32Release'])
+    }
+
+    private SolutionFile solutionFile(String path) {
+        return new SolutionFile(file(path))
+    }
+
+    private ProjectFile projectFile(String path) {
+        return new ProjectFile(file(path))
+    }
+
+    private FiltersFile filtersFile(String path) {
+        return new FiltersFile(file(path))
+    }
+
+    private static String filePath(String... paths) {
+        return (paths as List).join(';')
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/plugins/VisualStudioPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/plugins/VisualStudioPluginIntegrationTest.groovy
new file mode 100644
index 0000000..e2b281d
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/ide/visualstudio/plugins/VisualStudioPluginIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class VisualStudioPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getPluginId() {
+        "visual-studio"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/CppAutoTestedSamplesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/CppAutoTestedSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..bc6edf9
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/CppAutoTestedSamplesIntegrationTest.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.nativebinaries.language
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+class CppAutoTestedSamplesIntegrationTest extends AbstractAutoTestedSamplesTest{
+
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/cpp/src/main")
+    }
+
+}
+
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerPluginIntegrationTest.groovy
new file mode 100644
index 0000000..cbffb4f
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerPluginIntegrationTest.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.assembler.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class AssemblerPluginIntegrationTest extends WellBehavedPluginTest {
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/c/plugins/CPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/c/plugins/CPluginIntegrationTest.groovy
new file mode 100644
index 0000000..4a28638
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/c/plugins/CPluginIntegrationTest.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class CPluginIntegrationTest extends WellBehavedPluginTest {
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIncrementalBuildIntegrationTest.groovy
new file mode 100755
index 0000000..e506efb
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.GUtil
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Assume
+import spock.lang.Ignore
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+import static org.gradle.util.TextUtil.escapeString
+
+abstract class AbstractLanguageIncrementalBuildIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    static boolean multiPlatformsAvailable = true
+
+    IncrementalHelloWorldApp app
+    String mainCompileTask
+    String libraryCompileTask
+    TestFile sourceFile
+    TestFile headerFile
+    List<TestFile> librarySourceFiles = []
+
+    abstract IncrementalHelloWorldApp getHelloWorldApp();
+
+    String getCompilerTool() {
+        "${app.sourceType}Compiler"
+    }
+
+    String getSourceType() {
+        GUtil.toCamelCase(app.sourceType)
+    }
+
+    def "setup"() {
+        app = getHelloWorldApp()
+        mainCompileTask = ":compileMainExecutableMain${sourceType}"
+        libraryCompileTask = ":compileHelloSharedLibraryHello${sourceType}"
+
+        buildFile << app.pluginScript
+        buildFile << app.extraConfiguration
+
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        lib libraries.hello
+                    }
+                }
+            }
+            libraries {
+                hello {
+                    binaries.withType(SharedLibraryBinary) {
+                        ${app.compilerDefine("DLL_EXPORT")}
+                    }
+                }
+            }
+        """
+        settingsFile << "rootProject.name = 'test'"
+        sourceFile = app.mainSource.writeToDir(file("src/main"))
+        headerFile = app.libraryHeader.writeToDir(file("src/hello"))
+        app.librarySources.each {
+            librarySourceFiles << it.writeToDir(file("src/hello"))
+        }
+    }
+
+    def "does not re-execute build with no change"() {
+        given:
+        run "installMainExecutable"
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        nonSkippedTasks.empty
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "rebuilds executable with source file change"() {
+        given:
+        run "installMainExecutable"
+
+        def install = installation("build/install/mainExecutable")
+
+        when:
+        sourceFile.text = app.alternateMainSource.content
+
+        and:
+        run "installMainExecutable"
+
+        then:
+        skipped libraryCompileTask
+        skipped ":linkHelloSharedLibrary"
+        skipped ":helloSharedLibrary"
+        executedAndNotSkipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+        executedAndNotSkipped ":installMainExecutable"
+
+        and:
+        install.assertInstalled()
+        install.exec().out == app.alternateOutput
+    }
+
+    def "recompiles but does not relink executable with source comment change"() {
+        given:
+        run "installMainExecutable"
+        maybeWait()
+
+        when:
+        sourceFile.text = sourceFile.text.replaceFirst("// Simple hello world app", "// Comment is changed")
+        run "mainExecutable"
+
+        then:
+        skipped libraryCompileTask
+        skipped ":linkHelloSharedLibrary"
+        skipped ":helloSharedLibrary"
+
+        executedAndNotSkipped mainCompileTask
+
+        // Visual C++ compiler embeds a timestamp in every object file, so relinking is always required after recompiling
+        if (toolChain.visualCpp) {
+            executedAndNotSkipped ":linkMainExecutable"
+            executedAndNotSkipped ":mainExecutable"
+        } else if(objectiveCWithAslr()){
+            executed ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            executed ":linkMainExecutable", ":mainExecutable"
+        } else {
+            skipped ":linkMainExecutable"
+            skipped ":mainExecutable"
+        }
+    }
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "recompiles library and relinks executable with library source file change"() {
+        given:
+        run "installMainExecutable"
+        maybeWait()
+        def install = installation("build/install/mainExecutable")
+
+        when:
+        for (int i = 0; i < librarySourceFiles.size(); i++) {
+            TestFile sourceFile = librarySourceFiles.get(i);
+            sourceFile.text = app.alternateLibrarySources[i].content
+        }
+
+        and:
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask
+        executedAndNotSkipped ":linkHelloSharedLibrary"
+        executedAndNotSkipped ":helloSharedLibrary"
+        skipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+        executedAndNotSkipped ":installMainExecutable"
+
+        and:
+        install.assertInstalled()
+        install.exec().out == app.alternateLibraryOutput
+    }
+
+    def "recompiles binary when header file changes"() {
+        given:
+        run "installMainExecutable"
+        maybeWait()
+
+        when:
+        headerFile << """
+            int unused();
+"""
+
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask
+        executedAndNotSkipped mainCompileTask
+
+        // Visual C++ compiler embeds a timestamp in every object file, so relinking is always required after recompiling
+        if (toolChain.visualCpp) {
+            executedAndNotSkipped ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            executedAndNotSkipped ":linkMainExecutable", ":mainExecutable"
+        } else if(objectiveCWithAslr()){
+            executed ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            executed ":linkMainExecutable", ":mainExecutable"
+        } else {
+            skipped ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            skipped ":linkMainExecutable", ":mainExecutable"
+        }
+    }
+
+    def "recompiles binary when header file changes in a way that does not affect the object files"() {
+        given:
+        run "installMainExecutable"
+        maybeWait()
+
+        when:
+        headerFile << """
+// Comment added to the end of the header file
+"""
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask
+        executedAndNotSkipped mainCompileTask
+
+        // Visual C++ compiler embeds a timestamp in every object file, so relinking is always required after recompiling
+        if (toolChain.visualCpp) {
+            executedAndNotSkipped ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            executedAndNotSkipped ":linkMainExecutable", ":mainExecutable"
+        } else if(objectiveCWithAslr()){
+             executed ":linkHelloSharedLibrary", ":helloSharedLibrary"
+             executed ":linkMainExecutable", ":mainExecutable"
+        } else {
+            skipped ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            skipped ":linkMainExecutable", ":mainExecutable"
+        }
+    }
+
+    // compiling Objective-C and Objective-Cpp with clang generates
+    // random different object files (related to ASLR settings) 
+    // We saw this behaviour only on linux so far. 
+    boolean objectiveCWithAslr() {
+        return (sourceType == "Objc" || sourceType == "Objcpp") &&
+                OperatingSystem.current().isLinux() && 
+                toolChain.displayName == "clang"
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "rebuilds binary with compiler option change"() {
+        given:
+        run "installMainExecutable"
+
+        def install = installation("build/install/mainExecutable")
+
+        when:
+        buildFile << """
+            libraries {
+                hello {
+                    binaries.all {
+                        ${helloWorldApp.compilerArgs("-DFRENCH")}
+                    }
+                }
+            }
+"""
+
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask
+        executedAndNotSkipped ":linkHelloSharedLibrary"
+        executedAndNotSkipped ":helloSharedLibrary"
+        skipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+        executedAndNotSkipped ":installMainExecutable"
+
+        and:
+        install.assertInstalled()
+        install.exec().out == app.frenchOutput
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "rebuilds binary with target platform change"() {
+        Assume.assumeTrue(multiPlatformsAvailable)
+        given:
+        buildFile << """
+    model {
+        platforms {
+            arch {
+                // Tool chain defaults
+            }
+        }
+    }
+"""
+        run "mainExecutable"
+
+        when:
+        buildFile.text = buildFile.text.replace("// Tool chain defaults", "architecture 'i386'")
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask, mainCompileTask
+        executedAndNotSkipped ":linkHelloSharedLibrary"
+        executedAndNotSkipped ":helloSharedLibrary", ":mainExecutable"
+    }
+
+    def "relinks binary when set of input libraries changes"() {
+        given:
+        run "mainExecutable", "helloStaticLibrary"
+
+        def executable = executable("build/binaries/mainExecutable/main")
+        def snapshot = executable.snapshot()
+
+        when:
+        buildFile.text = buildFile.text.replaceFirst("lib libraries.hello", "lib libraries.hello.static")
+        run "mainExecutable"
+
+        then:
+        skipped ":helloStaticLibrary"
+        skipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+
+        and:
+        executable.assertHasChangedSince(snapshot)
+    }
+
+    def "relinks binary but does not recompile when linker option changed"() {
+        given:
+        run "installMainExecutable"
+
+        when:
+        def executable = executable("build/binaries/mainExecutable/main")
+        def snapshot = executable.snapshot()
+
+        and:
+        def linkerArgs =
+            toolChain.isVisualCpp() ? "'/DEBUG'" : OperatingSystem.current().isMacOsX() ? "'-Xlinker', '-no_pie'" : "'-Xlinker', '-q'";
+        linkerArgs = escapeString(linkerArgs)
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        linker.args ${escapeString(linkerArgs)}
+                    }
+                }
+            }
+"""
+
+        run "installMainExecutable"
+
+        then:
+        skipped libraryCompileTask
+        skipped ":linkHelloSharedLibrary"
+        skipped ":helloSharedLibrary"
+        skipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+        executedAndNotSkipped ":installMainExecutable"
+
+        and:
+        executable.assertExists()
+        executable.assertHasChangedSince(snapshot)
+    }
+
+    def "cleans up stale object files when executable source file renamed"() {
+        given:
+        run "installMainExecutable"
+
+        def oldObjFile = objectFileFor(sourceFile)
+        def newObjFile = objectFileFor(sourceFile.getParentFile().file("changed_${sourceFile.name}"))
+        assert oldObjFile.file
+        assert !newObjFile.file
+
+        final source = sourceFile
+
+        when:
+        rename(source)
+        run "mainExecutable"
+
+        then:
+        skipped libraryCompileTask
+        skipped ":linkHelloSharedLibrary"
+        skipped ":helloSharedLibrary"
+        executedAndNotSkipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+
+        and:
+        !oldObjFile.file
+        newObjFile.file
+    }
+
+    def "cleans up stale object files when library source file renamed"() {
+        when:
+        run "helloStaticLibrary"
+
+        then:
+        String objectFilesPath = "build/objectFiles/helloStaticLibrary/hello${sourceType}"
+        def oldObjFile = objectFileFor(librarySourceFiles[0], objectFilesPath)
+        def newObjFile = objectFileFor( librarySourceFiles[0].getParentFile().file("changed_${librarySourceFiles[0].name}"), objectFilesPath)
+        assert oldObjFile.file
+        assert !newObjFile.file
+
+        assert staticLibrary("build/binaries/helloStaticLibrary/hello").listObjectFiles().contains(oldObjFile.name)
+
+        when:
+        librarySourceFiles.each { rename(it) }
+        run "helloStaticLibrary"
+
+        then:
+        executedAndNotSkipped libraryCompileTask.replace("Shared", "Static")
+        executedAndNotSkipped ":createHelloStaticLibrary"
+        executedAndNotSkipped ":helloStaticLibrary"
+
+        and:
+        !oldObjFile.file
+        newObjFile.file
+
+        and:
+        assert staticLibrary("build/binaries/helloStaticLibrary/hello").listObjectFiles().contains(newObjFile.name)
+        assert !staticLibrary("build/binaries/helloStaticLibrary/hello").listObjectFiles().contains(oldObjFile.name)
+    }
+
+    @RequiresInstalledToolChain(VisualCpp)
+    def "cleans up stale debug files when changing from debug to non-debug"() {
+
+        given:
+        buildFile << """
+            binaries.all { ${compilerTool}.args '/Zi'; linker.args '/DEBUG'; }
+        """
+        run "mainExecutable"
+
+        def executable = executable("build/binaries/mainExecutable/main")
+        executable.assertDebugFileExists()
+
+        when:
+        buildFile << """
+            binaries.all { ${compilerTool}.args.clear(); linker.args.clear(); }
+        """
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask
+        executedAndNotSkipped ":helloSharedLibrary"
+        executedAndNotSkipped mainCompileTask
+        executedAndNotSkipped ":linkMainExecutable"
+        executedAndNotSkipped ":mainExecutable"
+
+        and:
+        executable.assertDebugFileDoesNotExist()
+    }
+
+    @Ignore("Test demonstrates missing functionality in incremental build with C++")
+    def "recompiles binary when header file with relative path changes"() {
+        when:
+        buildFile << """
+            apply plugin: 'cpp'
+            executables {
+                main {}
+            }
+"""
+
+        file("src/main/cpp/main.cpp") << """
+            #include "../not_included/hello.h"
+
+            int main () {
+              sayHello();
+              return 0;
+            }
+"""
+
+        def headerFile = file("src/main/not_included/hello.h") << """
+            void sayHello();
+"""
+
+        file("src/main/cpp/hello.cpp") << """
+            #include <iostream>
+
+            void sayHello() {
+                std::cout << "HELLO" << std::endl;
+            }
+"""
+        then:
+        succeeds "mainExecutable"
+        executable("build/binaries/mainExecutable/main").exec().out == "HELLO\n"
+
+        when:
+        headerFile.text = """
+            NOT A VALID HEADER FILE
+"""
+        then:
+        fails "mainExecutable"
+        and:
+        executedAndNotSkipped "compileMainExecutableMainCpp"
+    }
+
+    private void maybeWait() {
+        if (toolChain.visualCpp) {
+            def now = System.currentTimeMillis()
+            def nextSecond = now % 1000
+            Thread.sleep(1200 - nextSecond)
+        }
+    }
+
+    private static boolean rename(TestFile sourceFile) {
+        final newFile = new File(sourceFile.getParentFile(), "changed_${sourceFile.name}")
+        newFile << sourceFile.text
+        sourceFile.delete()
+    }
+
+
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIncrementalCompileIntegrationTest.groovy
new file mode 100755
index 0000000..8c8f9d2
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIncrementalCompileIntegrationTest.groovy
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import groovy.io.FileType
+import org.apache.commons.io.FilenameUtils
+import org.gradle.internal.hash.HashUtil
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.GUtil
+import spock.lang.Unroll
+
+abstract class AbstractLanguageIncrementalCompileIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    IncrementalHelloWorldApp app
+    String compileTask
+    TestFile sourceFile
+    TestFile sharedHeaderFile
+    TestFile otherHeaderFile
+    List<TestFile> otherSourceFiles = []
+    TestFile objectFileDir
+
+    abstract IncrementalHelloWorldApp getHelloWorldApp();
+
+    String getSourceType() {
+        GUtil.toCamelCase(app.sourceType)
+    }
+
+    def "setup"() {
+        app = getHelloWorldApp()
+        compileTask = ":compileMainExecutableMain${sourceType}"
+
+        buildFile << app.pluginScript
+        buildFile << app.extraConfiguration
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        sourceFile = app.mainSource.writeToDir(file("src/main"))
+        sharedHeaderFile = app.libraryHeader.writeToDir(file("src/main"))
+        app.librarySources.each {
+            otherSourceFiles << it.writeToDir(file("src/main"))
+        }
+        otherHeaderFile = file("src/main/headers/other.h") << """
+            // Dummy header file
+"""
+        objectFileDir = file("build/objectFiles/mainExecutable")
+    }
+
+    def "recompiles changed source file only"() {
+        given:
+        initialCompile()
+
+        when:
+        sourceFile << """
+// Changed source file
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+    def "recompiles all source files that include changed header file"() {
+        given:
+        initialCompile()
+
+        when:
+        sharedHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled allSources
+    }
+
+    def "recompiles only source file that includes changed header file"() {
+        given:
+        sourceFile << """
+            #include "${otherHeaderFile.name}"
+"""
+        and:
+        initialCompile()
+
+        when:
+        otherHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+    def "source is always recompiled if it includes header via macro"() {
+        given:
+        sourceFile << """
+            #define MY_HEADER "${otherHeaderFile.name}"
+            #include MY_HEADER
+"""
+
+        and:
+        initialCompile()
+
+        when:
+        otherHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+
+        // TODO:DAZ Remove this behaviour
+        when: "Header that is NOT included is changed"
+        file("src/main/headers/notIncluded.h") << """
+            // Dummy header file
+"""
+        and:
+        run "mainExecutable"
+
+        then: "Source is still recompiled"
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+    def "recompiles source file when transitively included header file is changed"() {
+        given:
+        def transitiveHeaderFile = file("src/main/headers/transitive.h") << """
+           // Dummy header file
+"""
+        otherHeaderFile << """
+            #include "${transitiveHeaderFile.name}"
+"""
+        sourceFile << """
+            #include "${otherHeaderFile.name}"
+"""
+
+        and:
+        initialCompile()
+
+        when:
+        transitiveHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+    def "recompiles source file when an included header file is renamed"() {
+        given:
+        initialCompile()
+
+        and:
+        final newFile = file("src/main/headers/changed.h")
+        newFile << sharedHeaderFile.text
+        sharedHeaderFile.delete()
+
+        when:
+        fails "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+        failure.assertHasDescription("Execution failed for task '${compileTask}'.");
+    }
+
+    def "does not recompile any sources when unused header file is changed"() {
+        given:
+        initialCompile()
+
+        when:
+        otherHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        skipped compileTask
+        noneRecompiled()
+    }
+
+    @Unroll
+    def "does not recompile when include path has #testCase"() {
+        given:
+        initialCompile()
+
+        file("src/additional-headers/other.h") << """
+    // extra header file that is not included in source
+"""
+        file("src/replacement-headers/${sharedHeaderFile.name}") << """
+    // replacement header file that is included in source
+"""
+
+        when:
+        buildFile << """
+            sources.main.${app.sourceType} {
+                exportedHeaders {
+                    srcDirs ${headerDirs}
+                }
+            }
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        skipped compileTask
+        noneRecompiled()
+
+        where:
+        testCase                       | headerDirs
+        "extra header dir after"       | '"src/main/headers", "src/additional-headers"'
+        "extra header dir before"      | '"src/additional-headers", "src/main/headers"'
+        "replacement header dir after" | '"src/main/headers", "src/replacement-headers"'
+    }
+
+    def "recompiles when include path is changed so that replacement header file occurs before previous header"() {
+        given:
+        initialCompile()
+
+        file("src/replacement-headers/${sharedHeaderFile.name}") << sharedHeaderFile.text
+
+        when:
+        buildFile << """
+            sources.main.${app.sourceType}  {
+                exportedHeaders {
+                    srcDirs "src/replacement-headers", "src/main/headers"
+                }
+            }
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled allSources
+    }
+
+    def "recompiles when replacement header file is added before previous header to existing include path"() {
+        given:
+        buildFile << """
+            sources.main.${app.sourceType} {
+                exportedHeaders {
+                    srcDirs "src/replacement-headers", "src/main/headers"
+                }
+            }
+"""
+        initialCompile()
+
+        when:
+        file("src/replacement-headers/${sharedHeaderFile.name}") << sharedHeaderFile.text
+
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled allSources
+    }
+
+    def "recompiles when replacement header file is added to source directory"() {
+        given:
+        initialCompile()
+
+        when:
+        sourceFile.parentFile.file(sharedHeaderFile.name) << sharedHeaderFile.text
+
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled allSources
+    }
+
+    def "recompiles all source files and removes stale outputs when compiler arg changes"() {
+        given:
+        def extraSource = file("src/main/${app.sourceType}/extra.${app.sourceExtension}")
+        extraSource << sourceFile.text.replaceAll("main", "main2")
+
+        initialCompile()
+
+        outputFile(extraSource).assertExists()
+
+        when:
+        sourceFile << """
+            // Changed source file
+"""
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        ${helloWorldApp.compilerDefine("MY_DEF")}
+                    }
+                }
+            }
+"""
+        extraSource.delete()
+
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled allSources
+        outputFile(extraSource).assertDoesNotExist()
+    }
+
+    def "recompiles all source files when generated object files are removed"() {
+        given:
+        initialCompile()
+
+        when:
+        objectFileDir.deleteDir()
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled allSources
+    }
+
+    def "removes output file when source file is renamed"() {
+        given:
+        initialCompile()
+
+        when:
+        final newFile = file("src/main/${app.sourceType}/changed.${app.sourceExtension}")
+        newFile << sourceFile.text
+        sourceFile.delete()
+
+        and:
+        run "mainExecutable"
+
+        then:
+        recompiled newFile
+        outputFile(sourceFile).assertDoesNotExist()
+    }
+
+    def "removes output file when source file is removed"() {
+        given:
+        def extraSource = file("src/main/${app.sourceType}/extra.${app.sourceExtension}")
+        extraSource << sourceFile.text.replaceAll("main", "main2")
+
+        initialCompile()
+
+        and:
+        outputFile(extraSource).assertExists()
+
+        when:
+        extraSource.delete()
+
+        and:
+        run "mainExecutable"
+
+        then:
+        outputFile(extraSource).assertDoesNotExist()
+        noneRecompiled()
+    }
+
+    def "removes output files when all source files are removed"() {
+        given:
+        initialCompile()
+
+        def executable = executable("build/binaries/mainExecutable/main")
+        executable.assertExists()
+
+        when:
+        file("src/main").eachFileRecurse(FileType.FILES) {
+            println "deleting ${it}"
+            it.delete()
+        }
+
+        and:
+        run "mainExecutable"
+
+        then: "linker output file is removed"
+        executable.assertDoesNotExist()
+
+        // Stale object files are removed when a new file is added to the source set
+        when:
+        def newSource = file("src/main/${app.sourceType}/newfile.${app.sourceExtension}") << """
+            #include <stdio.h>
+
+            int main () {
+                printf("hello");
+                return 0;
+            }
+"""
+
+        and:
+        run "mainExecutable"
+
+        then:
+        executable.exec().out == "hello"
+        outputFile(newSource).assertExists()
+
+        and: "Previous object files are removed"
+        outputFile(sourceFile).assertDoesNotExist()
+        otherSourceFiles.each {
+            outputFile(it).assertDoesNotExist()
+        }
+    }
+
+    def "incremental compile is not effected by other compile tasks"() {
+        given:
+        buildFile << """
+            executables {
+                other
+            }
+"""
+        app.writeSources(file("src/other"))
+
+        and:
+        initialCompile()
+
+        and:
+        // Completely independent compile task (state should be independent)
+        run "otherExecutable"
+
+        when:
+        sourceFile << """
+// Changed source file
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+
+    def initialCompile() {
+        run "mainExecutable"
+
+        // Set the last modified timestamp to zero for all object files
+        objectFileDir.eachFileRecurse(FileType.FILES) { it.lastModified = 0 }
+    }
+
+    def getRecompiledFiles() {
+        // Get all of the object files that do not have a zero last modified timestamp
+        def recompiled = []
+        objectFileDir.eachFileRecurse(FileType.FILES) {
+            if (it.lastModified() > 0) {
+                recompiled << FilenameUtils.removeExtension(it.name)
+            }
+        }
+        return recompiled as Set
+    }
+
+    def getAllSources() {
+        return [sourceFile] + otherSourceFiles
+    }
+
+    def noneRecompiled() {
+        recompiled([])
+    }
+
+    def recompiled(TestFile file) {
+        recompiled([file])
+    }
+
+    def recompiled(List<TestFile> files) {
+        def expectedNames = files.collect({ FilenameUtils.removeExtension(it.name) }) as Set
+        assert getRecompiledFiles() == expectedNames
+        return true
+    }
+
+    def outputFile(TestFile sourceFile) {
+        final baseName = FilenameUtils.removeExtension(sourceFile.name)
+        String compactMD5 = HashUtil.createCompactMD5(sourceFile.getAbsolutePath());
+        return objectFile("build/objectFiles/mainExecutable/main${sourceType}/$compactMD5/${baseName}")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..94cb126
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AbstractLanguageIntegrationTest.groovy
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.apache.commons.lang.RandomStringUtils
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Ignore
+
+abstract class AbstractLanguageIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    abstract HelloWorldApp getHelloWorldApp()
+
+    def "setup"() {
+        buildFile << helloWorldApp.pluginScript
+        buildFile << helloWorldApp.extraConfiguration
+    }
+
+    def "compile and link executable"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+
+    def "build executable with custom compiler arg"() {
+        given:
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        ${helloWorldApp.compilerArgs("-DFRENCH")}
+                    }
+                }
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.frenchOutput
+    }
+
+    def "build executable with macro defined"() {
+        given:
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        ${helloWorldApp.compilerDefine("FRENCH")}
+                    }
+                }
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.frenchOutput
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "build shared library and link into executable"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.main.${helloWorldApp.sourceType}.lib libraries.hello
+        """
+
+        and:
+        helloWorldApp.executable.writeSources(file("src/main"))
+        helloWorldApp.library.writeSources(file("src/hello"))
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        sharedLibrary("build/binaries/helloSharedLibrary/hello").assertExists()
+        executable("build/binaries/mainExecutable/main").assertExists()
+
+        def install = installation("build/install/mainExecutable")
+        install.assertInstalled()
+        install.assertIncludesLibraries("hello")
+        install.exec().out == helloWorldApp.englishOutput
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "build static library and link into executable"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {
+                    binaries.withType(StaticLibraryBinary) {
+                        ${helloWorldApp.compilerDefine("FRENCH")}
+                    }
+                }
+            }
+            sources.main.${helloWorldApp.sourceType}.lib libraries.hello.static
+        """
+
+        and:
+        helloWorldApp.executable.writeSources(file("src/main"))
+        helloWorldApp.library.writeSources(file("src/hello"))
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        staticLibrary("build/binaries/helloStaticLibrary/hello").assertExists()
+        executable("build/binaries/mainExecutable/main").assertExists()
+
+        and:
+        def install = installation("build/install/mainExecutable")
+        install.assertInstalled()
+        install.exec().out == helloWorldApp.frenchOutput
+    }
+
+    @Ignore
+    def "can run project in extended nested file paths"() {
+        // windows can't handle a path up to 260 characters
+        // we create a path that ends up with build folder longer than is 260
+        def projectPathOffset = 180 - testDirectory.getAbsolutePath().length()
+        def nestedProjectPath = RandomStringUtils.randomAlphanumeric(projectPathOffset-10) + "/123456789"
+
+        setup:
+        def deepNestedProjectFolder = file(nestedProjectPath)
+        executer.usingProjectDirectory(deepNestedProjectFolder)
+        def TestFile buildFile = deepNestedProjectFolder.file("build.gradle")
+        buildFile << helloWorldApp.pluginScript
+        buildFile << helloWorldApp.extraConfiguration
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("$nestedProjectPath/src/main"));
+
+        expect:
+        succeeds "mainExecutable"
+        def mainExecutable = executable("$nestedProjectPath/build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AssemblyLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AssemblyLanguageIncrementalBuildIntegrationTest.groovy
new file mode 100755
index 0000000..cf0c21e
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AssemblyLanguageIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class AssemblyLanguageIncrementalBuildIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    HelloWorldApp app = new MixedLanguageHelloWorldApp(toolChain)
+    TestFile asmSourceFile
+    def install
+
+    def "setup"() {
+        buildFile << """
+            apply plugin: 'assembler'
+            apply plugin: 'c'
+            apply plugin: 'cpp'
+
+            $app.extraConfiguration
+
+            libraries {
+                hello {}
+            }
+            executables {
+                main {
+                    binaries.all {
+                        lib libraries.hello
+                    }
+                }
+            }
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        asmSourceFile = file("src/hello/asm/sum.s")
+
+        run "installMainExecutable"
+
+        install = installation("build/install/mainExecutable")
+    }
+
+    def "does not re-execute build with no change"() {
+        when:
+        run "mainExecutable"
+
+        then:
+        nonSkippedTasks.empty
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "reassembles binary with assembler option change"() {
+        when:
+        buildFile << """
+            libraries {
+                hello {
+                    binaries.all {
+                        if (toolChain in VisualCpp) {
+                            assembler.args '/Zf'
+                        } else {
+                            assembler.args '-W'
+                        }
+                    }
+                }
+            }
+"""
+
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":assembleHelloSharedLibraryHelloAsm"
+
+        and:
+        install.exec().out == app.englishOutput
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "reassembles binary with target platform change"() {
+        when:
+        buildFile.text = buildFile.text.replace("i386", "x86")
+
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":assembleHelloSharedLibraryHelloAsm"
+
+        and:
+        install.exec().out == app.englishOutput
+    }
+
+    def "cleans up stale object files when source file renamed"() {
+        def oldObjFile = objectFile("build/objectFiles/helloSharedLibrary/helloAsm/${hashFor(asmSourceFile)}/sum")
+        def newObjFile = objectFile("build/objectFiles/helloSharedLibrary/helloAsm/${hashFor(file('src/hello/asm/changed_sum.s'))}/changed_sum")
+        assert oldObjFile.file
+        assert !newObjFile.file
+
+        when:
+        asmSourceFile.renameTo(file("src/hello/asm/changed_sum.s"))
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped ":assembleHelloSharedLibraryHelloAsm"
+
+        and:
+        !oldObjFile.file
+        newObjFile.file
+    }
+
+    def "reassembles binary with source comment change"() {
+        when:
+        asmSourceFile << "# A comment at the end of the file\n"
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped ":assembleHelloSharedLibraryHelloAsm"
+    }
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AssemblyLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AssemblyLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..74b0adb
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/AssemblyLanguageIntegrationTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.SourceFile
+
+class AssemblyLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+
+    HelloWorldApp helloWorldApp = new AssemblerWithCHelloWorldApp(toolChain)
+
+    def "build fails when assemble fails"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        file("src/main/asm/broken.s") << """
+.section    __TEXT,__text,regular,pure_instructions
+.globl  _sum
+.align  4, 0x90
+_sum:
+pushl
+"""
+
+        expect:
+        fails "mainExecutable"
+        failure.assertHasDescription("Execution failed for task ':assembleMainExecutableMainAsm'.");
+        failure.assertHasCause("Assembler failed; see the error output for details.")
+    }
+
+    def "can manually define Assembler source sets"() {
+        given:
+        helloWorldApp.mainSource.writeToDir(file("src/main"))
+        helloWorldApp.getLibraryHeader().writeToDir(file("src/main"))
+        helloWorldApp.librarySources[0].writeToDir(file("src/main"))
+        file("src/main/sum-sources/sum.s") << helloWorldApp.librarySources[1].content
+
+        and:
+        buildFile << """
+            sources {
+                main {
+                    sumAsm(AssemblerSourceSet) {
+                        source {
+                            srcDir "src/main/sum-sources"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+"""
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+
+
+    static class AssemblerWithCHelloWorldApp extends MixedLanguageHelloWorldApp {
+        AssemblerWithCHelloWorldApp(AvailableToolChains.InstalledToolChain toolChain) {
+            super(toolChain)
+        }
+
+        @Override
+        List<String> getPluginList() {
+            return ['c', 'assembler']
+        }
+
+        @Override
+        SourceFile getMainSource() {
+            return new SourceFile("c", "main.c", """
+                #include <stdio.h>
+                #include "hello.h"
+
+                int main () {
+                    sayHello();
+                    printf("%d", sum(5, 7));
+                    return 0;
+                }
+            """);
+        }
+    }
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryBuildTypesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryBuildTypesIntegrationTest.groovy
new file mode 100755
index 0000000..1a9b153
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryBuildTypesIntegrationTest.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class BinaryBuildTypesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def helloWorldApp = new CppHelloWorldApp()
+
+    def "creates debug and release variants"() {
+        when:
+        helloWorldApp.writeSources(file("src/main"))
+        and:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                buildTypes {
+                    debug {
+                        ext.debug = true
+                    }
+                    integration {
+                        ext.debug = true
+                    }
+                    release {
+                        ext.debug = false
+                    }
+                }
+            }
+            binaries.all { binary ->
+                if (toolChain in Gcc && buildType.debug) {
+                    cppCompiler.args "-g"
+                }
+                if (toolChain in VisualCpp) {
+                    // Apply to all debug build types: 'debug' and 'integration'
+                    if (buildType.debug) {
+                        cppCompiler.args '/Zi'
+                        cppCompiler.define 'DEBUG'
+                        linker.args '/DEBUG'
+                    }
+                }
+                // Apply to 'integration' type binaries only
+                if (buildType == buildTypes['integration']) {
+                    cppCompiler.define "FRENCH"
+                }
+            }
+            executables {
+                main {}
+            }
+        """
+        and:
+        succeeds "debugMainExecutable", "integrationMainExecutable", "releaseMainExecutable"
+
+        then:
+        with(executable("build/binaries/mainExecutable/debug/main")) {
+            it.assertExists()
+            it.assertDebugFileExists()
+            it.exec().out == helloWorldApp.englishOutput
+        }
+        with (executable("build/binaries/mainExecutable/integration/main")) {
+            it.assertExists()
+            it.assertDebugFileExists()
+            it.exec().out == helloWorldApp.frenchOutput
+        }
+        with (executable("build/binaries/mainExecutable/release/main")) {
+            it.assertExists()
+            it.assertDebugFileDoesNotExist()
+            it.exec().out == helloWorldApp.englishOutput
+        }
+    }
+
+    def "configure component for a single build type"() {
+        when:
+        helloWorldApp.writeSources(file("src/main"))
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                buildTypes {
+                    debug
+                    release
+                }
+            }
+            executables {
+                main {
+                    targetBuildTypes "release"
+                }
+            }
+            binaries.all { binary ->
+                if (buildType == buildTypes.release) {
+                    cppCompiler.define "FRENCH"
+                }
+            }
+"""
+
+        and:
+        succeeds "mainExecutable"
+
+        then:
+        // Build type dimension is flattened since there is only one possible value
+        executedAndNotSkipped(":mainExecutable")
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.frenchOutput
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "executable with build type depends on library with matching build type"() {
+        when:
+        helloWorldApp.executable.writeSources(file("src/main"))
+        helloWorldApp.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                buildTypes {
+                    debug
+                    release
+                }
+            }
+            binaries.all {
+                if (buildType == buildTypes.debug) {
+                    cppCompiler.define "FRENCH" // Equate 'debug' to 'french' for this test
+                }
+            }
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.main.cpp.lib libraries.hello.static
+        """
+        and:
+        succeeds "installDebugMainExecutable", "installReleaseMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable/debug").exec().out == helloWorldApp.frenchOutput
+        installation("build/install/mainExecutable/release").exec().out == helloWorldApp.englishOutput
+    }
+
+    def "fails with reasonable error message when trying to target an unknown build type"() {
+        when:
+        settingsFile << "rootProject.name = 'bad-build-type'"
+        buildFile << """
+            model {
+                buildTypes {
+                    debug
+                }
+            }
+            executables {
+                main {
+                    targetBuildTypes "unknown"
+                }
+            }
+"""
+
+        and:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("A problem occurred configuring root project 'bad-build-type'.")
+        failure.assertHasCause("Invalid BuildType: 'unknown'")
+    }
+
+    def "fails with reasonable error message when depended on library has no variant with matching build type"() {
+        when:
+        settingsFile << "rootProject.name = 'no-matching-build-type'"
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                buildTypes {
+                    debug
+                    release
+                }
+            }
+            executables {
+                main {}
+            }
+            libraries {
+                hello {
+                    targetBuildTypes "debug"
+                }
+            }
+            sources.main.cpp.lib libraries.hello.static
+"""
+
+        and:
+        fails "releaseMainExecutable"
+
+        then:
+        failure.assertHasDescription("No static library binary available for library 'hello' with [flavor: 'default', platform: 'current', buildType: 'release']")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryFlavorsIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryFlavorsIntegrationTest.groovy
new file mode 100755
index 0000000..f5d3bc5
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryFlavorsIntegrationTest.groovy
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Ignore
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class BinaryFlavorsIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    static final DEFAULT = HelloWorldApp.HELLO_WORLD
+    static final FRENCH = HelloWorldApp.HELLO_WORLD_FRENCH
+
+    def helloWorldApp = new ExeWithLibraryUsingLibraryHelloWorldApp()
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+
+        buildFile << """
+            apply plugin: "cpp"
+            model {
+                flavors {
+                    english
+                    french
+                    german
+                }
+            }
+            libraries {
+                greetings {
+                    binaries.all {
+                        if (!org.gradle.internal.os.OperatingSystem.current().isWindows()) {
+                            cppCompiler.args("-fPIC");
+                        }
+                    }
+                }
+                hello {
+                    binaries.all {
+                        lib libraries.greetings.static
+                    }
+                }
+            }
+            executables {
+                main {
+                    binaries.all {
+                        lib libraries.hello
+                    }
+                }
+            }
+        """
+
+        helloWorldApp.writeSources(file("src/main"), file("src/hello"), file("src/greetings"))
+    }
+
+    def "can configure components for a single flavor"() {
+        given:
+        buildFile << """
+    binaries.all {
+        if (flavor == flavors.french) {
+            cppCompiler.define "FRENCH"
+        }
+    }
+    executables.main.targetFlavors "french"
+    libraries.hello.targetFlavors "french"
+    libraries.greetings.targetFlavors "french"
+"""
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == FRENCH + " " + FRENCH
+    }
+
+    def "builds executable for each defined flavor when not configured for component"() {
+        when:
+        succeeds "installEnglishMainExecutable", "installFrenchMainExecutable", "installGermanMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable/english").assertInstalled()
+        installation("build/install/mainExecutable/french").assertInstalled()
+        installation("build/install/mainExecutable/german").assertInstalled()
+    }
+
+    def "executable with flavors depends on library with matching flavors"() {
+        when:
+        buildFile << """
+            executables {
+                main {
+                    targetFlavors "english", "french"
+                    binaries.all {
+                        if (flavor == flavors.french) {
+                            cppCompiler.define "FRENCH"
+                        }
+                    }
+                }
+            }
+            libraries.all {
+                targetFlavors "english", "french"
+                binaries.all {
+                    if (flavor == flavors.french) {
+                        cppCompiler.define "FRENCH"
+                    }
+                }
+            }
+        """
+
+        and:
+        succeeds "installEnglishMainExecutable", "installFrenchMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable/english").exec().out == DEFAULT + " " + DEFAULT
+        installation("build/install/mainExecutable/french").exec().out == FRENCH + " " + FRENCH
+    }
+
+    // TODO:DAZ Un-ignore
+    @Ignore("Requires proper dependency resolution")
+    def "executable with flavors depends on library with no defined flavor"() {
+        when:
+        buildFile << """
+            executables {
+                main {
+                    targetFlavors "english", "french"
+                    binaries.all {
+                        if (flavor == flavors.french) {
+                            cppCompiler.define "FRENCH"
+                        }
+                    }
+                }
+            }
+        """
+
+        and:
+        succeeds "installEnglishMainExecutable", "installFrenchMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable/english").exec().out == DEFAULT + " " + DEFAULT
+        installation("build/install/mainExecutable/french").exec().out == FRENCH + " " + DEFAULT
+    }
+
+    // TODO:DAZ Un-ignore
+    @Ignore("Library resolution does not yet handle this case")
+    def "executable with flavors depends on a library with a single flavor which depends on a library with flavors"() {
+        when:
+        buildFile << """
+            executables {
+                main {
+                    targetFlavors "english", "french"
+                    binaries.all {
+                        if (flavor == flavors.french) {
+                            cppCompiler.define "FRENCH"
+                        }
+                    }
+                }
+            }
+            libraries {
+                greetings {
+                    targetFlavors "english", "french"
+                    binaries.all {
+                        if (flavor == flavors.french) {
+                            cppCompiler.define "FRENCH"
+                        }
+                    }
+                }
+            }
+        """
+
+        and:
+        succeeds "installEnglishMainExecutable", "installFrenchMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable/english").exec().out == DEFAULT + " " + DEFAULT
+        installation("build/install/mainExecutable/french").exec().out == FRENCH + " " + FRENCH
+    }
+
+    def "build fails when library has no matching flavour"() {
+        when:
+        buildFile << """
+            apply plugin: "cpp"
+            libraries {
+                hello {
+                    targetFlavors "english", "french"
+                }
+            }
+            executables {
+                main {
+                    targetFlavors "english", "german"
+                    binaries.all {
+                        lib libraries.hello
+                    }
+                }
+            }
+        """
+
+        then:
+        fails "germanMainExecutable"
+        failure.assertHasDescription("No shared library binary available for library 'hello' with [flavor: 'german', platform: 'current', buildType: 'debug']")
+    }
+
+    def "fails with reasonable error message when trying to target an unknown flavor"() {
+        when:
+        buildFile << """
+            executables.main.targetFlavors "unknown"
+"""
+
+        and:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("A problem occurred configuring root project 'test'.")
+        failure.assertHasCause("Invalid Flavor: 'unknown'")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryPlatformIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryPlatformIntegrationTest.groovy
new file mode 100755
index 0000000..f3c8189
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/BinaryPlatformIntegrationTest.groovy
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import net.rubygrapefruit.platform.Native
+import net.rubygrapefruit.platform.SystemInfo
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement
+import org.gradle.nativebinaries.language.cpp.fixtures.app.PlatformDetectingTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.DumpbinBinaryInfo
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.OtoolBinaryInfo
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.ReadelfBinaryInfo
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.TextUtil
+import spock.lang.Unroll
+
+ at Requires(TestPrecondition.NOT_UNKNOWN_OS)
+class BinaryPlatformIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def testApp = new PlatformDetectingTestApp()
+    def os = OperatingSystem.current()
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'cpp'
+
+            executables {
+                main {}
+            }
+        """
+
+        testApp.writeSources(file("src/main"))
+    }
+
+    def "build binary for a default target platform"() {
+        given:
+        def arch =  [name: "x86_64", altName: "amd64"]
+        // Tool chains on Windows currently build for i386 by default, even on amd64
+        if (OperatingSystem.current().windows || Native.get(SystemInfo).architecture == SystemInfo.Architecture.i386) {
+            arch = [name: "x86", altName: "i386"]
+        }
+
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executedAndNotSkipped(":mainExecutable")
+        executable("build/binaries/mainExecutable/main").binaryInfo.arch.name == arch.name
+        executable("build/binaries/mainExecutable/main").exec().out == "${arch.altName} ${os.familyName}" * 2
+        binaryInfo(objectFileFor(file("src/main/cpp/main.cpp"), "build/objectFiles/mainExecutable/mainCpp")).arch.name == arch.name
+    }
+
+    def "configure component for a single target platform"() {
+        when:
+        buildFile << """
+            model {
+                platforms {
+                    x86 {
+                        architecture "x86"
+                    }
+                    x86_64 {
+                        architecture "x86_64"
+                    }
+                }
+            }
+            task buildExecutables {
+                dependsOn binaries.withType(ExecutableBinary).matching {
+                    it.buildable
+                }
+            }
+            executables.main.targetPlatforms "x86"
+"""
+
+        and:
+        succeeds "buildExecutables"
+
+        then:
+        // Platform dimension is flattened since there is only one possible value
+        executedAndNotSkipped(":mainExecutable")
+        executable("build/binaries/mainExecutable/main").binaryInfo.arch.name == "x86"
+        executable("build/binaries/mainExecutable/main").exec().out == "i386 ${os.familyName}" * 2
+    }
+
+    def "library with matching platform is chosen by dependency resolution"() {
+        given:
+        testApp.executable.writeSources(file("src/exe"))
+        testApp.library.writeSources(file("src/hello"))
+        when:
+        buildFile << """
+            model {
+                platforms {
+                    x86 {
+                        architecture "x86"
+                    }
+                    x86_64 {
+                        architecture "x86_64"
+                    }
+                }
+            }
+            executables {
+                exe {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.exe.cpp.lib libraries.hello.static
+            executables.exe.targetPlatforms "x86"
+"""
+
+        and:
+        succeeds "exeExecutable"
+
+        then:
+        // Platform dimension is flattened since there is only one possible value
+        executedAndNotSkipped(":exeExecutable")
+        executable("build/binaries/exeExecutable/exe").binaryInfo.arch.name == "x86"
+        executable("build/binaries/exeExecutable/exe").exec().out == "i386 ${os.familyName}" * 2
+    }
+
+    def "build binary for multiple target architectures"() {
+        when:
+        buildFile << """
+            model {
+                platforms {
+                    x86 {
+                        architecture "x86"
+                    }
+                    x86_64 {
+                        architecture "x86_64"
+                    }
+                    itanium {
+                        architecture "ia-64"
+                    }
+                    arm {
+                        architecture "arm"
+                    }
+                }
+            }
+            task buildExecutables {
+                dependsOn binaries.withType(ExecutableBinary).matching {
+                    it.buildable
+                }
+            }
+"""
+
+        and:
+        succeeds "buildExecutables"
+
+        then:
+        executable("build/binaries/mainExecutable/x86/main").binaryInfo.arch.name == "x86"
+        executable("build/binaries/mainExecutable/x86/main").exec().out == "i386 ${os.familyName}" * 2
+        binaryInfo(objectFileFor(file("src/main/cpp/main.cpp"), "build/objectFiles/mainExecutable/x86/mainCpp")).arch.name == "x86"
+
+        // x86_64 binaries not supported on MinGW or cygwin
+        if (toolChain.id == "mingw" || toolChain.id == "gcccygwin") {
+            executable("build/binaries/mainExecutable/x86_64/main").assertDoesNotExist()
+        } else {
+            executable("build/binaries/mainExecutable/x86_64/main").binaryInfo.arch.name == "x86_64"
+            executable("build/binaries/mainExecutable/x86_64/main").exec().out == "amd64 ${os.familyName}" * 2
+            binaryInfo(objectFileFor(file("src/main/cpp/main.cpp"), "build/objectFiles/mainExecutable/x86_64/mainCpp")).arch.name == "x86_64"
+        }
+
+        // Itanium only supported on visualCpp
+        if (toolChain.visualCpp) {
+            executable("build/binaries/mainExecutable/itanium/main").binaryInfo.arch.name == "ia-64"
+            binaryInfo(objectFileFor(file("src/main/cpp/main.cpp"),"build/objectFiles/mainExecutable/itanium/mainCpp")).arch.name == "ia-64"
+        } else {
+            executable("build/binaries/mainExecutable/itanium/main").assertDoesNotExist()
+        }
+
+        // ARM only supported on visualCpp 2013
+        if (toolChain.meets(ToolChainRequirement.VisualCpp2013)) {
+            executable("build/binaries/mainExecutable/arm/main").binaryInfo.arch.name == "arm"
+            binaryInfo(objectFileFor(file("src/main/cpp/main.cpp"), "build/objectFiles/mainExecutable/arm/mainCpp")).arch.name == "arm"
+        } else {
+            executable("build/binaries/mainExecutable/arm/main").assertDoesNotExist()
+        }
+    }
+
+    def "can configure binary for multiple target operating systems"() {
+        when:
+        buildFile << """
+            model {
+                platforms {
+                    windows {
+                        operatingSystem "windows"
+                    }
+                    linux {
+                        operatingSystem "linux"
+                    }
+                    osx {
+                        operatingSystem "osx"
+                    }
+                }
+            }
+
+            binaries.matching({ it.targetPlatform.operatingSystem.windows }).all {
+                cppCompiler.define "FRENCH"
+            }
+            task buildExecutables {
+                dependsOn binaries.withType(ExecutableBinary).matching {
+                    it.buildable
+                }
+            }
+        """
+
+        and:
+        succeeds "buildExecutables"
+
+        then:
+        if (os.windows) {
+            executable("build/binaries/mainExecutable/windows/main").exec().out == "amd64 windows" * 2
+        } else if (os.linux) {
+            executable("build/binaries/mainExecutable/linux/main").exec().out == "amd64 linux" * 2
+        } else if (os.macOsX) {
+            executable("build/binaries/mainExecutable/osx/main").exec().out == "amd64 os x" * 2
+        } else {
+            throw new AssertionError("Unexpected operating system")
+        }
+    }
+
+    @Unroll
+    def "fails with reasonable error message when trying to build for an unavailable #type"() {
+        when:
+        buildFile << """
+            model {
+                platforms {
+                    unavailable {
+                        ${config}
+                    }
+                }
+            }
+"""
+
+        and:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainCpp'.")
+        failure.assertHasCause(TextUtil.toPlatformLineSeparators("""No tool chain is available to build for platform 'unavailable':
+  - ${toolChain.instanceDisplayName}: Don't know how to build for platform 'unavailable'."""))
+
+        where:
+        type               | config
+        "architecture"     | "architecture 'sparc'"
+        "operating system" | "operatingSystem 'solaris'"
+    }
+
+    @Unroll
+    def "fails with reasonable error message when trying to build for an unknown #type"() {
+        when:
+        settingsFile << """rootProject.name = 'bad'"""
+        buildFile << """
+            model {
+                platforms {
+                    bad {
+                        ${badConfig}
+                    }
+                }
+            }
+"""
+
+        and:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("A problem occurred configuring root project 'bad'.")
+        failure.assertHasCause("Cannot convert the provided notation to an object of type ${type}: bad.")
+
+        where:
+        type               | badConfig
+        "Architecture"     | "architecture 'bad'"
+        "OperatingSystem" | "operatingSystem 'bad'"
+    }
+
+    def "fails with reasonable error message when trying to target an unknown platform"() {
+        when:
+        settingsFile << "rootProject.name = 'bad-platform'"
+        buildFile << """
+            model {
+                platforms {
+                    main
+                }
+            }
+            executables.main.targetPlatforms "unknown"
+"""
+
+        and:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("A problem occurred configuring root project 'bad-platform'.")
+        failure.assertHasCause("Invalid Platform: 'unknown'")
+    }
+
+    def "fails with reasonable error message when depended on library has no variant with matching platform"() {
+        when:
+        settingsFile << "rootProject.name = 'no-matching-platform'"
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                platforms {
+                    one
+                    two
+                }
+            }
+            libraries {
+                hello {
+                    targetPlatforms "two"
+                }
+            }
+            sources.main.cpp.lib libraries.hello
+"""
+
+        and:
+        fails "oneMainExecutable"
+
+        then:
+        failure.assertHasDescription("No shared library binary available for library 'hello' with [flavor: 'default', platform: 'one', buildType: 'debug']")
+    }
+
+    def binaryInfo(TestFile file) {
+        file.assertIsFile()
+        if (os.macOsX) {
+            return new OtoolBinaryInfo(file)
+        }
+        if (os.windows) {
+            return new DumpbinBinaryInfo(file, toolChain)
+        }
+        return new ReadelfBinaryInfo(file)
+    }
+
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CCallingMixedCAndCppLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CCallingMixedCAndCppLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..07f8a1a
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CCallingMixedCAndCppLanguageIntegrationTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CCallingMixedCAndCppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+
+class CCallingMixedCAndCppLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+    HelloWorldApp helloWorldApp = new CCallingMixedCAndCppHelloWorldApp()
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIncrementalBuildIntegrationTest.groovy
new file mode 100755
index 0000000..d4d7677
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+
+class CLanguageIncrementalBuildIntegrationTest extends AbstractLanguageIncrementalBuildIntegrationTest {
+    @Override
+    IncrementalHelloWorldApp getHelloWorldApp() {
+        new CHelloWorldApp()
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIncrementalCompileIntegrationTest.groovy
new file mode 100644
index 0000000..db9c190
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIncrementalCompileIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+
+class CLanguageIncrementalCompileIntegrationTest extends AbstractLanguageIncrementalCompileIntegrationTest {
+     @Override
+     IncrementalHelloWorldApp getHelloWorldApp() {
+         return new CHelloWorldApp()
+     }
+ }
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..d8e5149
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CLanguageIntegrationTest.groovy
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CCompilerDetectingTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import spock.lang.Issue
+import spock.lang.Unroll
+
+// TODO:DAZ Some of these tests should apply to all single-language integration tests
+class CLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+
+    HelloWorldApp helloWorldApp = new CHelloWorldApp()
+
+    def "sources are compiled with C compiler"() {
+        def app = new CCompilerDetectingTestApp()
+
+        given:
+        app.writeSources(file('src/main'))
+
+        and:
+        buildFile << """
+             executables {
+                 main {}
+             }
+         """
+
+        expect:
+        succeeds "mainExecutable"
+        executable("build/binaries/mainExecutable/main").exec().out == app.expectedOutput(toolChain)
+    }
+
+    def "can manually define C source sets"() {
+        given:
+        helloWorldApp.getLibraryHeader().writeToDir(file("src/shared"))
+
+        file("src/main/c/main.c") << helloWorldApp.mainSource.content
+        file("src/main/c2/hello.c") << helloWorldApp.librarySources[0].content
+        file("src/main/sum-sources/sum.c") << helloWorldApp.librarySources[1].content
+
+
+        and:
+        buildFile << """
+            sources {
+                main {
+                    c {
+                        exportedHeaders {
+                            srcDirs "src/shared/headers"
+                        }
+                    }
+                    c2(CSourceSet) {
+                        exportedHeaders {
+                            srcDirs "src/shared/headers"
+                        }
+                    }
+                    c3(CSourceSet) {
+                        source {
+                            srcDir "src/main/sum-sources"
+                        }
+                        exportedHeaders {
+                            srcDirs "src/shared/headers"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+"""
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+
+    def "uses headers co-located with sources"() {
+        given:
+        // Write headers so they sit with sources
+        helloWorldApp.allFiles.each {
+            it.writeToFile(file("src/main/c/${it.name}"))
+        }
+        buildFile << """
+    executables {
+        main {}
+    }
+    sources.main.c.source.include "**/*.c"
+"""
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+
+    @Issue("GRADLE-2943")
+    @Unroll
+    def "can define macro #output"() {
+        given:
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        ${helloWorldApp.compilerDefine('CUSTOM', inString)}
+                    }
+                }
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.getCustomOutput(output)
+
+        where:
+        inString                           | output
+        '"quoted"'                         | 'quoted'
+        '"with space"'                     | 'with space'
+        '"with\\\\"quote\\\\"internal"'    | 'with"quote"internal'
+        '"with \\\\"quote\\\\" and space"' | 'with "quote" and space'
+    }
+
+    def "compiler and linker args can contain quotes and spaces"() {
+        given:
+        buildFile << '''
+            executables {
+                main {
+                    binaries.all {
+                        // These are just some dummy arguments to test we don't blow up. Their effects are not verified.
+                        if (toolChain in VisualCpp) {
+                            cCompiler.args '/DVERSION="The version is \\'1.0\\'"'
+                            linker.args '/MANIFESTUAC:level=\\'asInvoker\\' uiAccess=\\'false\\''
+                        } else if (toolChain in Clang) {
+                            cCompiler.args '-frandom-seed="here is the \\'random\\' seed"'
+                            // TODO:DAZ Find something that works here (for all our CI machines)
+                            // linker.args '-Wl,-client_name,"a \\'client\\' name"'
+                        } else {
+                            cCompiler.args '-frandom-seed="here is the \\'random\\' seed"'
+                            // TODO:DAZ Find something that works on linux
+                            // linker.args '-Wl,--auxiliary,"an \\'auxiliary\\' name"'
+                        }
+                    }
+                }
+            }
+        '''
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        expect:
+        succeeds "mainExecutable"
+    }
+
+    def "build fails when compilation fails"() {
+        given:
+        buildFile << """
+             executables {
+                 main {}
+             }
+         """
+
+        and:
+        file("src/main/c/broken.c") << """
+        #include <stdio.h>
+
+        'broken
+"""
+
+        expect:
+        fails "mainExecutable"
+        failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainC'.");
+        failure.assertHasCause("C compiler failed; see the error output for details.")
+    }
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest.groovy
new file mode 100755
index 0000000..dfd0eec
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest.groovy
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.nativebinaries.test.cunit.CUnitTestResults
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+
+import static org.gradle.util.TextUtil.normaliseLineSeparators
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class CUnitIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    @Rule TestResources resources = new TestResources(temporaryFolder)
+    def app = new CHelloWorldApp()
+
+    def setup() {
+        buildFile << """
+            apply plugin: "c"
+            apply plugin: "cunit"
+
+            model {
+                repositories {
+                    libs(PrebuiltLibraries) {
+                        cunit {
+                            headers.srcDir "libs/cunit/2.1-2/include"
+                            binaries.withType(StaticLibraryBinary) {
+                                staticLibraryFile = file("libs/cunit/2.1-2/lib/${cunitPlatform}/${cunitLibName}")
+                            }
+                        }
+                    }
+                }
+            }
+        """
+        settingsFile << "rootProject.name = 'test'"
+    }
+
+    private void useStandardConfig() {
+        buildFile << """
+            libraries {
+                hello {}
+            }
+            binaries.withType(TestSuiteExecutableBinary) {
+                lib library: "cunit", linkage: "static"
+            }
+"""
+    }
+
+    private def getCunitPlatform() {
+        if (OperatingSystem.current().isMacOsX()) {
+            return "osx"
+        }
+        if (OperatingSystem.current().isLinux()) {
+            return "linux"
+        }
+        if (toolChain.displayName == "mingw") {
+            return "mingw"
+        }
+        if (toolChain.displayName == "gcc cygwin") {
+            return "cygwin"
+        }
+        if (toolChain.visualCpp) {
+            def vcVersion = (toolChain as AvailableToolChains.InstalledVisualCpp).version
+            switch (vcVersion.major) {
+                case "12":
+                    return "vs2013"
+                case "10":
+                    return "vs2010"
+            }
+        }
+        throw new IllegalStateException("No cunit binary available for ${toolChain.displayName}")
+    }
+
+    private def getCunitLibName() {
+        return OperatingSystem.current().getStaticLibraryName("cunit")
+    }
+
+    def "can build and run cunit test suite"() {
+        given:
+        useConventionalSourceLocations()
+        useStandardConfig()
+
+        when:
+        run "runHelloTestCUnitExe"
+
+        then:
+        executedAndNotSkipped ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestCunit",
+                              ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+
+        def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
+        testResults.suiteNames == ['hello test']
+        testResults.suites['hello test'].passingTests == ['test_sum']
+        testResults.suites['hello test'].failingTests == []
+        testResults.checkTestCases(1, 1, 0)
+        testResults.checkAssertions(3, 3, 0)
+    }
+
+    def "can configure via testSuite component"() {
+        given:
+        useConventionalSourceLocations()
+
+        buildFile << """
+            libraries {
+                hello {}
+            }
+            testSuites {
+                helloTest {
+                    binaries.all {
+                        lib library: "cunit", linkage: "static"
+                    }
+                }
+            }
+"""
+
+        when:
+        run "runHelloTestCUnitExe"
+
+        then:
+        executedAndNotSkipped ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestCunit",
+                              ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+
+        def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
+        testResults.suiteNames == ['hello test']
+        testResults.suites['hello test'].passingTests == ['test_sum']
+        testResults.suites['hello test'].failingTests == []
+        testResults.checkTestCases(1, 1, 0)
+        testResults.checkAssertions(3, 3, 0)
+    }
+
+    def "can supply cCompiler macro to cunit sources"() {
+        given:
+        useConventionalSourceLocations()
+        useStandardConfig()
+
+        when:
+        buildFile << """
+            binaries.withType(TestSuiteExecutableBinary) {
+                cCompiler.define "ONE_TEST"
+            }
+"""
+        and:
+        run "runHelloTestCUnitExe"
+
+        then:
+        def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
+        testResults.checkAssertions(1, 1, 0)
+    }
+
+    def "can configure location of cunit test sources"() {
+        given:
+        useStandardConfig()
+        app.library.writeSources(file("src/hello"))
+        app.cunitTests.writeSources(file("src/alternateHelloTest"))
+
+        when:
+        buildFile << """
+            sources {
+                helloTest {
+                    cunit {
+                        source.srcDir "src/alternateHelloTest/cunit"
+                    }
+                }
+            }
+"""
+
+        then:
+        succeeds "runHelloTestCUnitExe"
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+    }
+
+    def "can configure location of cunit test sources before component is declared"() {
+        given:
+        app.library.writeSources(file("src/hello"))
+        app.cunitTests.writeSources(file("src/alternateHelloTest"))
+
+        when:
+        buildFile << """
+            sources {
+                helloTest {
+                    cunit(CSourceSet) {
+                        source.srcDir "src/alternateHelloTest/cunit"
+                    }
+                }
+            }
+"""
+        useStandardConfig()
+
+        then:
+        succeeds "runHelloTestCUnitExe"
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+    }
+
+    def "variant-dependent sources are included in test binary"() {
+        given:
+        useStandardConfig()
+        app.library.headerFiles*.writeToDir(file("src/hello"))
+        app.cunitTests.writeSources(file("src/helloTest"))
+        app.library.sourceFiles*.writeToDir(file("src/variant"))
+
+        when:
+        buildFile << """
+            sources {
+                variant {
+                    c {
+                        lib sources.hello.c
+                    }
+                }
+            }
+            binaries.withType(LibraryBinary) {
+                source sources.variant
+            }
+"""
+
+        then:
+        succeeds "runHelloTestCUnitExe"
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+    }
+
+    def "can configure variant-dependent test sources"() {
+        given:
+        useStandardConfig()
+        app.library.writeSources(file("src/hello"))
+        app.cunitTests.writeSources(file("src/variantTest"))
+
+        when:
+        buildFile << """
+            sources {
+                variantTest {
+                    cunit(CSourceSet) {
+                        lib sources.hello.c
+                        lib sources.helloTest.cunitLauncher
+                    }
+                }
+            }
+            binaries.withType(TestSuiteExecutableBinary) {
+                source sources.variantTest.cunit
+            }
+"""
+
+        then:
+        succeeds "runHelloTestCUnitExe"
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+    }
+
+    def "test suite skipped after successful run"() {
+        given:
+        useStandardConfig()
+        useConventionalSourceLocations()
+        run "runHelloTestCUnitExe"
+
+        when:
+        run "runHelloTestCUnitExe"
+
+        then:
+        skipped ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+    }
+
+    def "can build and run cunit failing test suite"() {
+        when:
+        useStandardConfig()
+        useFailingTestSources()
+        fails "runHelloTestCUnitExe"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':runHelloTestCUnitExe'.")
+        failure.assertHasCause("There were failing tests. See the results at: ")
+
+        and:
+        executedAndNotSkipped ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestCunit",
+                              ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+        output.contains """
+There were test failures:
+"""
+        and:
+        def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
+        testResults.suiteNames == ['hello test']
+        testResults.suites['hello test'].passingTests == []
+        testResults.suites['hello test'].failingTests == ['test_sum']
+        testResults.checkTestCases(1, 0, 1)
+        testResults.checkAssertions(3, 1, 2)
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+    }
+
+    def "build does not break for failing tests if ignoreFailures is true"() {
+        when:
+        useStandardConfig()
+        useFailingTestSources()
+        buildFile << """
+    tasks.withType(RunTestExecutable) {
+        it.ignoreFailures = true
+    }
+"""
+        succeeds "runHelloTestCUnitExe"
+
+        then:
+        output.contains """
+There were test failures:
+"""
+        output.contains "There were failing tests. See the results at: "
+
+        and:
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml").assertExists()
+        file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
+    }
+
+    def "test suite not skipped after failing run"() {
+        given:
+        useStandardConfig()
+        useFailingTestSources()
+        fails "runHelloTestCUnitExe"
+
+        when:
+        fails "runHelloTestCUnitExe"
+
+        then:
+        executedAndNotSkipped ":runHelloTestCUnitExe"
+    }
+
+    def "creates visual studio solution and project for cunit test suite"() {
+        given:
+        useStandardConfig()
+        useConventionalSourceLocations()
+        buildFile.text = "apply plugin: 'visual-studio'\n" + buildFile.text
+
+        when:
+        succeeds "helloTestVisualStudio"
+
+        then:
+        final mainSolution = new SolutionFile(file("helloTestExe.sln"))
+        mainSolution.assertHasProjects("helloTestExe")
+
+        and:
+        final projectFile = new ProjectFile(file("helloTestExe.vcxproj"))
+        projectFile.sourceFiles as Set == [
+                "build.gradle",
+                "build/src/helloTestCUnitLauncher/cunit/gradle_cunit_main.c",
+                "src/helloTest/cunit/test.c",
+                "src/hello/c/hello.c",
+                "src/hello/c/sum.c"
+        ] as Set
+        projectFile.headerFiles == [
+                "build/src/helloTestCUnitLauncher/headers/gradle_cunit_register.h",
+                "src/hello/headers/hello.h"
+        ]
+        projectFile.projectConfigurations.keySet() == ['debug'] as Set
+        with (projectFile.projectConfigurations['debug']) {
+            includePath == "build/src/helloTestCUnitLauncher/headers;src/helloTest/headers;src/hello/headers;libs/cunit/2.1-2/include"
+        }
+    }
+
+    private useConventionalSourceLocations() {
+        app.library.writeSources(file("src/hello"))
+        app.cunitTests.writeSources(file("src/helloTest"))
+    }
+
+    private useFailingTestSources() {
+        useConventionalSourceLocations()
+        file("src/hello/c/sum.c").text = file("src/hello/c/sum.c").text.replace("return a + b;", "return 2;")
+    }
+
+    @Override
+    String getOutput() {
+        return normaliseLineSeparators(super.getOutput())
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppBinariesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppBinariesIntegrationTest.groovy
new file mode 100755
index 0000000..ee59943
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppBinariesIntegrationTest.groovy
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.hamcrest.Matchers
+import spock.lang.IgnoreIf
+import spock.lang.Issue
+import spock.lang.Unroll
+
+class CppBinariesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def "can configure the binaries of a C++ application"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+
+            executables {
+                main {
+                    binaries.all {
+                        cppCompiler.define 'ENABLE_GREETING'
+                    }
+                }
+            }
+        """
+
+        and:
+        file("src/main/cpp/helloworld.cpp") << """
+            #include <iostream>
+
+            int main () {
+              #ifdef ENABLE_GREETING
+              std::cout << "Hello!";
+              #endif
+              return 0;
+            }
+        """
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def executable = executable("build/binaries/mainExecutable/main")
+        executable.exec().out == "Hello!"
+    }
+
+    def "can build debug binaries for a C++ executable"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+
+            executables {
+                main {
+                    binaries.all {
+                        if (toolChain in VisualCpp) {
+                            cppCompiler.args '/Zi'
+                            linker.args '/DEBUG'
+                        } else {
+                            cppCompiler.args '-g'
+                        }
+                    }
+                }
+            }
+        """
+
+        and:
+        file("src/main/cpp/helloworld.cpp") << """
+            #include <iostream>
+
+            int main () {
+              std::cout << "Hello!";
+              return 0;
+            }
+        """
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def executable = executable("build/binaries/mainExecutable/main")
+        executable.exec().out == "Hello!"
+        executable.assertDebugFileExists()
+        // TODO - need to verify that the debug info ended up in the binary
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "can configure the binaries of a C++ library"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+
+            libraries {
+                hello {
+                    binaries.all {
+                        cppCompiler.define 'ENABLE_GREETING'
+                    }
+                }
+            }
+            executables {
+                main {
+                    binaries.all {
+                        lib libraries.hello.static
+                    }
+                }
+            }
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src/hello/cpp/hello.cpp") << """
+            #include <iostream>
+
+            void hello(const char* str) {
+              #ifdef ENABLE_GREETING
+              std::cout << str;
+              #endif
+            }
+        """
+
+        and:
+        file("src/hello/headers/hello.h") << """
+            void hello(const char* str);
+        """
+
+        and:
+        file("src/main/cpp/main.cpp") << """
+            #include "hello.h"
+
+            int main () {
+              hello("Hello!");
+              return 0;
+            }
+        """
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        staticLibrary("build/binaries/helloStaticLibrary/hello").assertExists()
+        installation("build/install/mainExecutable").exec().out == "Hello!"
+    }
+
+    def "can configure a binary to use additional source sets"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+
+            sources {
+                main {
+                    cpp {
+                        exportedHeaders.srcDir "src/shared/headers"
+                    }
+                }
+                util {
+                    cpp {
+                        exportedHeaders.srcDir "src/shared/headers"
+                    }
+                }
+            }
+            executables {
+                main {
+                    binaries.all {
+                        source sources.util.cpp
+                    }
+                }
+            }
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src/shared/headers/greeting.h") << """
+            void greeting();
+"""
+
+        file("src/util/cpp/greeting.cpp") << """
+            #include <iostream>
+            #include "greeting.h"
+
+            void greeting() {
+                std::cout << "Hello!";
+            }
+        """
+
+        file("src/main/cpp/helloworld.cpp") << """
+            #include "greeting.h"
+
+            int main() {
+                greeting();
+                return 0;
+            }
+        """
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def executable = executable("build/binaries/mainExecutable/main")
+        executable.exec().out == "Hello!"
+    }
+
+    def "can customize binaries before and after linking"() {
+        def helloWorldApp = new CppHelloWorldApp()
+        given:
+        buildFile << """
+            apply plugin: 'cpp'
+            executables {
+                main {}
+            }
+
+            binaries.withType(ExecutableBinary) { binary ->
+                def preLink = task("\${binary.name}PreLink") {
+                    dependsOn binary.tasks.withType(CppCompile)
+
+                    doLast {
+                        println "Pre Link"
+                    }
+                }
+                binary.tasks.link.dependsOn preLink
+
+                def postLink = task("\${binary.name}PostLink") {
+                    dependsOn binary.tasks.link
+
+                    doLast {
+                        println "Post Link"
+                    }
+                }
+
+                binary.builtBy postLink
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executedTasks.tail() == [":compileMainExecutableMainCpp", ":mainExecutablePreLink", ":linkMainExecutable", ":mainExecutablePostLink", ":mainExecutable"]
+    }
+
+    @Issue("GRADLE-2973")
+    @IgnoreIf({ !GradleContextualExecuter.isParallel() })
+    def "releases cache lock when compilation fails with --parallel"() {
+        def helloWorldApp = new CppHelloWorldApp()
+        given:
+        settingsFile << "include ':a', ':b'"
+        buildFile << """
+            subprojects {
+                apply plugin: 'cpp'
+                executables {
+                    main {}
+                }
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("a/src/main"))
+        helloWorldApp.writeSources(file("b/src/main"))
+
+        file("b/src/main/cpp/broken.cpp") << """
+    A broken C++ file
+"""
+
+        expect:
+        fails "mainExecutable"
+        failure.assertThatCause(Matchers.not(Matchers.containsString("Could not stop")))
+    }
+
+    def "can configure output file for binaries"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            apply plugin: 'cpp'
+            executables {
+                main {
+                    binaries.all {
+                        executableFile = modPath(executableFile)
+                    }
+                }
+            }
+            libraries {
+                hello {
+                    binaries.withType(SharedLibraryBinary) {
+                        sharedLibraryFile = modPath(sharedLibraryFile)
+                        sharedLibraryLinkFile = modPath(sharedLibraryLinkFile)
+                    }
+                    binaries.withType(StaticLibraryBinary) {
+                        staticLibraryFile = modPath(staticLibraryFile)
+                    }
+                }
+            }
+            //sources.main.cpp.lib libraries.hello
+
+            def modPath(File file) {
+                new File("\${file.parentFile}/new_output/_\${file.name}")
+            }
+"""
+
+        when:
+        succeeds "mainExecutable", "helloSharedLibrary", "helloStaticLibrary"
+
+        then:
+        def modPath = {TestFile file -> new TestFile("${file.parentFile}/new_output/_${file.name}")}
+        modPath(executable("build/binaries/mainExecutable/main").file).assertExists()
+        modPath(sharedLibrary("build/binaries/helloSharedLibrary/hello").file).assertExists()
+        modPath(staticLibrary("build/binaries/helloStaticLibrary/hello").file).assertExists()
+    }
+
+    @Unroll
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "can link to #linkage library binary with custom output file"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            apply plugin: 'cpp'
+            executables {
+                main {}
+            }
+            libraries {
+                hello {
+                    binaries.withType(SharedLibraryBinary) {
+                        sharedLibraryFile = modPath(sharedLibraryFile)
+                        sharedLibraryLinkFile = modPath(sharedLibraryLinkFile)
+                    }
+                    binaries.withType(StaticLibraryBinary) {
+                        staticLibraryFile = modPath(staticLibraryFile)
+                    }
+                }
+            }
+            sources.main.cpp.lib libraries.hello.${linkage}
+
+            def modPath(File file) {
+                new File("\${file.parentFile}/new_output/_\${file.name}")
+            }
+"""
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+
+        where:
+        linkage << ["static", "shared"]
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppCallingCLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppCallingCLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..bdb89f5
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppCallingCLanguageIntegrationTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppCallingCHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+
+class CppCallingCLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+    HelloWorldApp helloWorldApp = new CppCallingCHelloWorldApp()
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy
new file mode 100755
index 0000000..74521e2
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+
+class CppLanguageIncrementalBuildIntegrationTest extends AbstractLanguageIncrementalBuildIntegrationTest {
+    IncrementalHelloWorldApp getHelloWorldApp() {
+        new CppHelloWorldApp()
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy
new file mode 100644
index 0000000..753d0db
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+
+class CppLanguageIncrementalCompileIntegrationTest extends AbstractLanguageIncrementalCompileIntegrationTest {
+     @Override
+     IncrementalHelloWorldApp getHelloWorldApp() {
+         return new CppHelloWorldApp()
+     }
+ }
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..cc8b508
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppLanguageIntegrationTest.groovy
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppCompilerDetectingTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+
+class CppLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+
+    HelloWorldApp helloWorldApp = new CppHelloWorldApp()
+
+    def "build fails when compilation fails"() {
+        given:
+        buildFile << """
+             executables {
+                 main {}
+             }
+         """
+
+        and:
+        file("src/main/cpp/broken.cpp") << """
+        #include <iostream>
+
+        'broken
+"""
+
+        expect:
+        fails "mainExecutable"
+        failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainCpp'.");
+        failure.assertHasCause("C++ compiler failed; see the error output for details.")
+    }
+
+    def "sources are compiled with C++ compiler"() {
+        def app = new CppCompilerDetectingTestApp()
+
+        given:
+        app.writeSources(file('src/main'))
+
+        and:
+        buildFile << """
+             executables {
+                 main {}
+             }
+         """
+
+        expect:
+        succeeds "mainExecutable"
+        executable("build/binaries/mainExecutable/main").exec().out == app.expectedOutput(toolChain)
+    }
+
+    def "can manually define C++ source sets"() {
+        given:
+        helloWorldApp.getLibraryHeader().writeToDir(file("src/shared"))
+
+        file("src/main/cpp/main.cpp") << helloWorldApp.mainSource.content
+        file("src/main/cpp2/hello.cpp") << helloWorldApp.librarySources[0].content
+        file("src/main/sum-sources/sum.cpp") << helloWorldApp.librarySources[1].content
+
+
+        and:
+        buildFile << """
+            sources {
+                main {
+                    cpp {
+                        exportedHeaders {
+                            srcDirs "src/shared/headers"
+                        }
+                    }
+                    cpp2(CppSourceSet) {
+                        exportedHeaders {
+                            srcDirs "src/shared/headers"
+                        }
+                    }
+                    cpp3(CppSourceSet) {
+                        source {
+                            srcDir "src/main/sum-sources"
+                        }
+                        exportedHeaders {
+                            srcDirs "src/shared/headers"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+"""
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppPluginGoodBehaviourTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..fe3c020
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/CppPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class CppPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    def String getPluginId() {
+        return "cpp"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/DuplicateBaseNamesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/DuplicateBaseNamesIntegrationTest.groovy
new file mode 100644
index 0000000..00cdbd7
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/DuplicateBaseNamesIntegrationTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateAssemblerBaseNamesTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateCBaseNamesTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateCppBaseNamesTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateMixedSameBaseNamesTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateObjectiveCBaseNamesTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateObjectiveCppBaseNamesTestApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.DuplicateWindowsResourcesBaseNamesTestApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+
+// TODO add coverage for mixed sources
+class DuplicateBaseNamesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    def "can have sourcefiles with same base name but different directories"() {
+        setup:
+        testApp.writeSources(file("src/main"))
+        buildFile.text = ""
+        testApp.plugins.each{ plugin ->
+            buildFile << "apply plugin: '$plugin'\n"
+        }
+
+        buildFile << """
+        binaries.all{
+            linker.args "-v"
+        }
+        """
+        buildFile << """
+            model {
+                platforms {
+                    x86 {
+                        architecture "i386"
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+
+            """
+        expect:
+        succeeds "mainExecutable"
+        executable("build/binaries/mainExecutable/main").exec().out == expectedOutput
+        where:
+        testApp                                              |   expectedOutput
+        new DuplicateCBaseNamesTestApp()                     |    "foo1foo2"
+        new DuplicateCppBaseNamesTestApp()                   |    "foo1foo2"
+        new DuplicateAssemblerBaseNamesTestApp(toolChain)    |    "foo1foo2"
+        new DuplicateMixedSameBaseNamesTestApp(toolChain)    |    "fooFromC\nfooFromCpp\nfooFromAsm\n"
+    }
+
+    //TODO Rene: inline with testcase above once we got coverage for objective-c and objective-cpp on windows
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "can have objectiveC and objectiveCpp source files with same name in different directories"(){
+        setup:
+        testApp.writeSources(file("src/main"))
+        buildFile.text = ""
+        testApp.plugins.each{ plugin ->
+            buildFile << "apply plugin: '$plugin'\n"
+        }
+        buildFile << testApp.extraConfiguration
+
+        buildFile << """
+            executables {
+                main {}
+            }
+
+            """
+        expect:
+        succeeds "mainExecutable"
+        executable("build/binaries/mainExecutable/main").exec().out == "foo1foo2"
+        where:
+        testApp << [ new DuplicateObjectiveCBaseNamesTestApp(), new DuplicateObjectiveCppBaseNamesTestApp() ]
+    }
+
+    @RequiresInstalledToolChain(VisualCpp)
+    def "windows-resources can have sourcefiles with same base name but different directories"() {
+        setup:
+        def testApp = new DuplicateWindowsResourcesBaseNamesTestApp();
+        testApp.writeSources(file("src/main"))
+        buildFile.text = ""
+        testApp.plugins.each{ plugin ->
+            buildFile << "apply plugin: '$plugin'\n"
+        }
+        buildFile <<"""
+            binaries.all {
+                linker.args "user32.lib"
+            }
+            executables {
+                main {}
+            }
+            """
+        expect:
+        succeeds "mainExecutable"
+        executable("build/binaries/mainExecutable/main").exec().out == "foo1foo2"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GccToolChainCustomisationIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GccToolChainCustomisationIntegrationTest.groovy
new file mode 100755
index 0000000..2e2bbe9
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GccToolChainCustomisationIntegrationTest.groovy
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.GccCompatible
+
+ at RequiresInstalledToolChain(GccCompatible)
+class GccToolChainCustomisationIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def helloWorldApp = new CHelloWorldApp()
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'c'
+
+            model {
+                toolChains {
+                    ${toolChain.buildScriptConfig}
+                }
+            }
+
+            executables {
+                main {
+                    binaries.all {
+                        lib libraries.hello.static
+                    }
+                }
+            }
+            libraries {
+                hello {}
+            }
+"""
+
+        helloWorldApp.executable.writeSources(file("src/main"))
+        helloWorldApp.library.writeSources(file("src/hello"))
+    }
+
+    def "can add binary configuration to target a platform"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        addPlatformConfiguration(new ArmArchitecture())
+                    }
+                }
+                platforms {
+                    arm {
+                        architecture "arm"
+                    }
+                    i386 {
+                        architecture "i386"
+                    }
+                }
+            }
+
+            class ArmArchitecture implements TargetPlatformConfiguration {
+                boolean supportsPlatform(Platform element) {
+                    return element.getArchitecture().name == "arm"
+                }
+
+                List<String> getCppCompilerArgs() {
+                    []
+                }
+
+                List<String> getCCompilerArgs() {
+                    ["-m32", "-DFRENCH"]
+                }
+
+                List<String> getObjectiveCCompilerArgs() {
+                    []
+                }
+
+                List<String> getObjectiveCppCompilerArgs() {
+                    []
+                }
+
+                List<String> getAssemblerArgs() {
+                    []
+                }
+
+                List<String> getLinkerArgs() {
+                    ["-m32"]
+                }
+
+                List<String> getStaticLibraryArchiverArgs() {
+                    []
+                }
+            }
+"""
+
+        and:
+        succeeds "armMainExecutable", "i386MainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/arm/main").binaryInfo.arch.name == "x86"
+        executable("build/binaries/mainExecutable/arm/main").exec().out == helloWorldApp.frenchOutput
+
+        executable("build/binaries/mainExecutable/i386/main").binaryInfo.arch.name == "x86"
+        executable("build/binaries/mainExecutable/i386/main").exec().out == helloWorldApp.englishOutput
+    }
+
+    def "can add action to tool chain that modifies tool arguments prior to execution"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        cCompiler.withArguments { args ->
+                            Collections.replaceAll(args, "CUSTOM", "-DFRENCH")
+                        }
+                        linker.withArguments { args ->
+                            args.remove "CUSTOM"
+                        }
+                        staticLibArchiver.withArguments { args ->
+                            args.remove "CUSTOM"
+                        }
+                    }
+                }
+            }
+            binaries.all {
+                cCompiler.args "CUSTOM"
+                linker.args "CUSTOM"
+            }
+            binaries.withType(StaticLibraryBinary) {
+                staticLibArchiver.args "CUSTOM"
+            }
+"""
+        then:
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.frenchOutput
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "can configure tool executables"() {
+        def binDir = testDirectory.createDir("bin")
+        wrapperTool(binDir, "c-compiler", toolChain.CCompiler, "-DFRENCH")
+        wrapperTool(binDir, "static-lib", toolChain.staticLibArchiver)
+        wrapperTool(binDir, "linker", toolChain.linker)
+
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        path file('${binDir.toURI()}')
+                        cCompiler.executable = 'c-compiler'
+                        staticLibArchiver.executable = 'static-lib'
+                        linker.executable = 'linker'
+                    }
+                }
+            }
+"""
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.frenchOutput
+    }
+
+    def wrapperTool(TestFile binDir, String wrapperName, String executable, String... additionalArgs) {
+        def script = binDir.file(OperatingSystem.current().getExecutableName(wrapperName))
+        if (OperatingSystem.current().windows) {
+            script.text = "${executable} ${additionalArgs.join(' ')} %*"
+        } else {
+            script.text = "${executable} ${additionalArgs.join(' ')} \"\$@\""
+            script.permissions = "rwxr--r--"
+        }
+        return script
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GccToolChainDiscoveryIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GccToolChainDiscoveryIntegrationTest.groovy
new file mode 100755
index 0000000..7354288
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GccToolChainDiscoveryIntegrationTest.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.hamcrest.Matchers
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.GccCompatible
+
+ at RequiresInstalledToolChain(GccCompatible)
+class GccToolChainDiscoveryIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def helloWorldApp = new CHelloWorldApp()
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'c'
+
+            model {
+                toolChains {
+                    ${toolChain.buildScriptConfig}
+                }
+            }
+
+            executables {
+                main {
+                    binaries.all {
+                        lib libraries.hello.static
+                    }
+                }
+            }
+            libraries {
+                hello {}
+            }
+"""
+
+        helloWorldApp.executable.writeSources(file("src/main"))
+        helloWorldApp.library.writeSources(file("src/hello"))
+    }
+
+    def "can build when language tools that are not required are not available"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        cppCompiler.executable = 'does-not-exist'
+                    }
+                }
+            }
+"""
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.englishOutput
+    }
+
+    def "does not break when compiler not available and not building"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        cCompiler.executable = 'does-not-exist'
+                    }
+                }
+            }
+"""
+
+        then:
+        succeeds "help"
+    }
+
+    def "fails when required language tool is not available"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        cCompiler.executable = 'does-not-exist'
+                    }
+                }
+            }
+"""
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainC'.")
+        failure.assertThatCause(Matchers.startsWith("Could not find C compiler 'does-not-exist'"))
+    }
+
+    def "fails when required linker tool is not available"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChain.id} {
+                        linker.executable = 'does-not-exist'
+                    }
+                }
+            }
+"""
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':linkMainExecutable'.")
+        failure.assertThatCause(Matchers.startsWith("Could not find Linker 'does-not-exist'"))
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GeneratedSourcesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GeneratedSourcesIntegrationTest.groovy
new file mode 100755
index 0000000..c0115a8
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/GeneratedSourcesIntegrationTest.groovy
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.apache.commons.io.FileUtils
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.WindowsResourceHelloWorldApp
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+// TODO:DAZ Test incremental
+class GeneratedSourcesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    def setup() {
+        settingsFile << "rootProject.name = 'test'"
+        buildFile << """
+    class GenerateSources extends DefaultTask {
+        @InputDirectory File inputDir
+        @OutputDirectory File sourceDir
+        @OutputDirectory @Optional File headerDir
+
+        @TaskAction
+        void processInputFiles() {
+            project.copy {
+                from inputDir
+                into sourceDir.parentFile
+                filter { String line ->
+                    line.replaceAll('REMOVE_ME', '')
+                }
+            }
+        }
+    }
+    task generateCSources(type: GenerateSources) {
+        inputDir project.file("src/input")
+        headerDir project.file("build/src/generated/headers")
+        sourceDir project.file("build/src/generated/c")
+    }
+"""
+    }
+
+    private void degenerateInputSources() {
+        FileUtils.listFiles(file("src/input"), null, true).each { File file ->
+            file.text = "REMOVE_ME\n" + file.text
+        }
+    }
+
+    def "generator task produces c sources and headers"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    executables {
+        main {}
+    }
+    sources.main.c.generatedBy tasks.generateCSources
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "generator task produces sources for dependent source set with headers only"() {
+        given:
+        // Write sources to src/main, headers to src/input
+        def app = new CHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.sourceFiles*.writeToDir(file("src/main"))
+        app.library.headerFiles*.writeToDir(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    executables {
+        main {}
+    }
+    sources {
+        generated {
+            cHeaders(CSourceSet) {
+                generatedBy tasks.generateCSources
+            }
+        }
+    }
+    sources.main.c.lib sources.generated.cHeaders
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "generator task produces sources for dependent source set"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    executables {
+        main {}
+    }
+    sources {
+        generated {
+            c {
+                generatedBy tasks.generateCSources
+            }
+        }
+    }
+    sources.main.c.lib sources.generated.c
+    executables.main.source sources.generated.c
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "can have library composed of generated sources"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    executables {
+        main {}
+    }
+    libraries {
+        hello {}
+    }
+    sources {
+        hello {
+            c {
+                generatedBy tasks.generateCSources
+            }
+        }
+    }
+    sources.main.c.lib library: 'hello', linkage: 'static'
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "generator task produces cpp sources"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'cpp'
+
+    task generateCppSources(type: GenerateSources) {
+        inputDir project.file("src/input")
+        headerDir project.file("build/src/generated/headers")
+        sourceDir project.file("build/src/generated/cpp")
+    }
+
+    executables {
+        main {}
+    }
+    sources.main.cpp.generatedBy tasks.generateCppSources
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "generator task produces assembler sources"() {
+        given:
+        def app = new MixedLanguageHelloWorldApp(toolChain)
+        def asmSources = app.sourceFiles.findAll {it.path == 'asm'}
+        def mainSources = app.headerFiles + app.sourceFiles - asmSources
+        mainSources*.writeToDir(file("src/main"))
+        asmSources*.writeToDir(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << app.pluginScript
+        buildFile << app.extraConfiguration
+        buildFile << """
+    task generateAsmSources(type: GenerateSources) {
+        inputDir project.file("src/input")
+        sourceDir project.file("build/src/generated/asm")
+    }
+
+    executables {
+        main {}
+    }
+    sources.main.asm.generatedBy tasks.generateAsmSources
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    @RequiresInstalledToolChain(VisualCpp)
+    def "generator task produces windows resources"() {
+        given:
+        def app = new WindowsResourceHelloWorldApp()
+        def rcSources = app.sourceFiles.findAll {it.path == 'rc'}
+        def mainSources = app.headerFiles + app.sourceFiles - rcSources
+        mainSources*.writeToDir(file("src/main"))
+        rcSources*.writeToDir(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << app.pluginScript
+        buildFile << app.extraConfiguration
+        buildFile << """
+    task generateRcSources(type: GenerateSources) {
+        inputDir project.file("src/input")
+        sourceDir project.file("build/src/generated/rc")
+    }
+
+    executables {
+        main {}
+    }
+    sources.main.rc.generatedBy tasks.generateRcSources
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "produces reasonable error message when generator task does not have sourceDir property"() {
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    task generateSources {
+    }
+
+    executables {
+        main {}
+    }
+    sources.main.c.generatedBy tasks.generateSources
+"""
+
+        and:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription "A problem occurred configuring root project 'test'."
+        failure.assertHasCause "Could not find property 'sourceDir' on task ':generateSources'."
+    }
+
+    def "can explicitly configure source and header directories from generator task"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    executables {
+        main {}
+    }
+    sources {
+        main {
+            c {
+                builtBy tasks.generateCSources
+                source {
+                    srcDirs tasks.generateCSources.sourceDir
+                }
+                exportedHeaders {
+                    srcDirs tasks.generateCSources.headerDir
+                }
+            }
+        }
+    }
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "can configure generator task properties after wiring"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        when:
+        buildFile << """
+    apply plugin: 'c'
+
+    task lateConfiguredGenerator(type: GenerateSources)
+
+    executables {
+        main {}
+    }
+    sources.main.c.generatedBy tasks.lateConfiguredGenerator
+
+    lateConfiguredGenerator {
+        inputDir project.file("src/input")
+        headerDir project.file("build/src/generated/headers")
+        sourceDir project.file("build/src/generated/c")
+    }
+"""
+
+        then:
+        executableBuilt(app)
+    }
+
+    def "creates visual studio project including generated sources"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.writeSources(file("src/input"))
+        degenerateInputSources()
+
+        and:
+        buildFile << """
+    apply plugin: 'visual-studio'
+    apply plugin: 'c'
+
+    executables {
+        main {}
+    }
+    sources.main.c.generatedBy tasks.generateCSources
+"""
+
+        when:
+        succeeds "mainVisualStudio"
+
+        then:
+        final mainSolution = new SolutionFile(file("mainExe.sln"))
+        mainSolution.assertHasProjects("mainExe")
+
+        and:
+        final projectFile = new ProjectFile(file("mainExe.vcxproj"))
+        projectFile.sourceFiles as Set == [
+                "build.gradle",
+                "build/src/generated/c/hello.c",
+                "build/src/generated/c/main.c",
+                "build/src/generated/c/sum.c"
+        ] as Set
+        projectFile.headerFiles == [ "build/src/generated/headers/hello.h" ]
+        projectFile.projectConfigurations.keySet() == ['debug'] as Set
+        with (projectFile.projectConfigurations['debug']) {
+            includePath == "build/src/generated/headers"
+        }
+    }
+
+    def executableBuilt(def app) {
+        succeeds "mainExecutable"
+        assert executable("build/binaries/mainExecutable/main").exec().out == app.englishOutput
+        true
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryApiDependenciesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryApiDependenciesIntegrationTest.groovy
new file mode 100755
index 0000000..e060551
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryApiDependenciesIntegrationTest.groovy
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Unroll
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class LibraryApiDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+        buildFile << """
+            apply plugin: "cpp"
+            // Allow static libraries to be linked into shared
+            binaries.withType(StaticLibraryBinary) {
+                if (toolChain in Gcc || toolChain in Clang) {
+                    cppCompiler.args '-fPIC'
+                }
+            }
+"""
+    }
+
+    @Unroll
+    def "can use api linkage via #notationName notation"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+
+        app.library.headerFiles*.writeToDir(file("src/helloApi"))
+        app.library.sourceFiles*.writeToDir(file("src/hello"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                helloApi {}
+                hello {}
+            }
+            sources.main.cpp.lib ${notation}
+            sources.main.cpp.lib library: 'hello'
+            sources.hello.cpp.lib ${notation}
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+
+        where:
+        notationName | notation
+        "direct"     | "libraries.helloApi.api"
+        "map"        | "library: 'helloApi', linkage: 'api'"
+    }
+
+    def "executable compiles using functions defined in header-only utility library"() {
+        given:
+        file("src/util/headers/util.h") << """
+            const char *message = "Hello from the utility library";
+"""
+        file("src/main/cpp/main.cpp") << """
+            #include "util.h"
+            #include <iostream>
+
+            int main () {
+                std::cout << message;
+                return 0;
+            }
+"""
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                util {}
+            }
+            sources.main.cpp.lib library: 'util'
+"""
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == "Hello from the utility library"
+    }
+
+    def "executable compiles using functions defined in utility library with build type variants"() {
+        given:
+        file("src/util/debug/util.h") << """
+            const char *message = "Hello from the debug library";
+"""
+        file("src/util/release/util.h") << """
+            const char *message = "Hello from the release library";
+"""
+        file("src/main/cpp/main.cpp") << """
+            #include "util.h"
+            #include <iostream>
+
+            int main () {
+                std::cout << message;
+                return 0;
+            }
+"""
+        buildFile << """
+            model {
+                buildTypes {
+                    debug
+                    release
+                }
+            }
+            executables {
+                main
+            }
+            libraries {
+                util {
+                    binaries.all { binary ->
+                        binary.source sources[binary.buildType.name]
+                    }
+                }
+            }
+            sources {
+                main.cpp.lib library: 'util'
+                debug.cpp {
+                    exportedHeaders.srcDir "src/util/debug"
+                }
+                release.cpp {
+                    exportedHeaders.srcDir "src/util/release"
+                }
+            }
+"""
+        when:
+        succeeds "installDebugMainExecutable", "installReleaseMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable/debug").exec().out == "Hello from the debug library"
+        installation("build/install/mainExecutable/release").exec().out == "Hello from the release library"
+    }
+
+    def "can choose alternative library implementation of api"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        app.alternateLibrarySources*.writeToDir(file("src/hello2"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                hello2 {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'api'
+            sources.main.cpp.lib library: 'hello2'
+            sources.hello2.cpp.lib library: 'hello', linkage: 'api'
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.alternateLibraryOutput
+    }
+
+    def "can use api linkage for component graph with library dependency cycle"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        app.greetingsHeader.writeToDir(file("src/hello"))
+        app.greetingsSources*.writeToDir(file("src/greetings"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                greetings {}
+            }
+            sources.main.cpp.lib library: 'hello'
+            sources.hello.cpp.lib library: 'greetings', linkage: 'static'
+            sources.greetings.cpp.lib library: 'hello', linkage: 'api'
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+    }
+
+    def "can compile but not link when executable depends on api of library required for linking"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'api'
+        """
+
+        when:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':linkMainExecutable'.")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryBinariesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryBinariesIntegrationTest.groovy
new file mode 100755
index 0000000..45bf094
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryBinariesIntegrationTest.groovy
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Issue
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class LibraryBinariesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+    }
+
+    def "executable can use a mix of static and shared libraries"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            libraries {
+                helloStatic {}
+                helloShared {}
+            }
+            sources.main.cpp.lib libraries.helloStatic.static
+            sources.main.cpp.lib libraries.helloShared
+        """
+
+        and:
+        file("src/helloStatic/cpp/hellostatic.cpp") << """
+            #include <iostream>
+
+            void helloStatic() {
+                std::cout << "Hello static";
+            }
+        """
+
+        and:
+        file("src/helloStatic/headers/hellostatic.h") << """
+            void helloStatic();
+        """
+
+        and:
+        file("src/helloShared/cpp/helloshared.cpp") << """
+            #include <iostream>
+            #include "helloshared.h"
+
+            void DLL_FUNC helloShared() {
+                std::cout << "Hello shared";
+            }
+        """
+
+        and:
+        file("src/helloShared/headers/helloshared.h") << """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC helloShared();
+        """
+
+        and:
+        file("src/main/cpp/main.cpp") << """
+            #include "hellostatic.h"
+            #include "helloshared.h"
+
+            int main () {
+                helloStatic();
+                helloShared();
+                return 0;
+            }
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        staticLibrary("build/binaries/helloStaticStaticLibrary/helloStatic").assertExistsAndDelete()
+        sharedLibrary("build/binaries/helloSharedSharedLibrary/helloShared").assertExistsAndDelete()
+        installation("build/install/mainExecutable")
+                .assertIncludesLibraries("helloShared")
+                .exec().out == "Hello staticHello shared"
+    }
+
+    def "executable can use a combination of libraries from the same and other projects"() {
+        given:
+        settingsFile << """
+include 'exe', 'lib'
+"""
+        buildFile << """
+            project('lib') {
+                apply plugin: "cpp"
+                libraries {
+                    helloLib {}
+                }
+            }
+            project('exe') {
+                evaluationDependsOn(":lib")
+                apply plugin: "cpp"
+                executables {
+                    main {}
+                }
+                libraries {
+                    helloMain {}
+                }
+                sources.main.cpp {
+                    lib libraries.helloMain
+                    lib project(":lib").libraries.helloLib
+                }
+            }
+        """
+
+        and:
+        file("lib/src/helloLib/cpp/hellolib.cpp") << """
+            #include <iostream>
+            #include "hellolib.h"
+
+            void DLL_FUNC helloLib() {
+                std::cout << "Hello lib";
+            }
+        """
+
+        and:
+        file("lib/src/helloLib/headers/hellolib.h") << """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC helloLib();
+        """
+
+        and:
+        file("exe/src/helloMain/cpp/hellomain.cpp") << """
+            #include <iostream>
+            #include "hellomain.h"
+
+            void DLL_FUNC helloMain() {
+                std::cout << "Hello main" << std::endl;
+            }
+        """
+
+        and:
+        file("exe/src/helloMain/headers/hellomain.h") << """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC helloMain();
+        """
+
+        and:
+        file("exe/src/main/cpp/main.cpp") << """
+            #include "hellolib.h"
+            #include "hellomain.h"
+
+            int main () {
+                helloMain();
+                helloLib();
+                return 0;
+            }
+        """
+
+        when:
+        succeeds "exe:installMainExecutable"
+
+        then:
+        sharedLibrary("lib/build/binaries/helloLibSharedLibrary/helloLib").assertExistsAndDelete()
+        sharedLibrary("exe/build/binaries/helloMainSharedLibrary/helloMain").assertExistsAndDelete()
+        installation("exe/build/install/mainExecutable")
+                .assertIncludesLibraries("helloLib", "helloMain")
+                .exec().out == "Hello main\nHello lib"
+    }
+
+    def "source set library dependencies are not shared with other source sets"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            apply plugin: "c"
+            executables {
+                main {}
+            }
+            libraries {
+                libCpp {}
+                libC {}
+            }
+            sources.main.cpp.lib libraries.libCpp.static
+            sources.main.c.lib libraries.libC.static
+        """
+
+        and:
+        file("src/main/headers/head.h") << """
+            void cppOut();
+
+            extern "C" {
+                void cOut();
+            }
+"""
+
+        file("src/main/cpp/main.cpp") << """
+            #include "head.h"
+
+            int main () {
+                cppOut();
+                cOut();
+                return 0;
+            }
+"""
+        and: "C and CPP sources sets depend on header file with same name"
+
+        file("src/main/cpp/test.cpp") << """
+            #include <iostream>
+            #include "output.h"
+
+            void cppOut() {
+                std::cout << OUTPUT << "_";
+            }
+"""
+
+        file("src/main/c/test.c") << """
+            #include <stdio.h>
+            #include "output.h"
+
+            void cOut() {
+                printf(OUTPUT);
+            }
+"""
+
+        and: "Library header files define different OUTPUT values"
+
+        file("src/libCpp/headers/output.h") << """
+            #define OUTPUT "CPP"
+"""
+
+        file("src/libC/headers/output.h") << """
+            #define OUTPUT "C"
+"""
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == "CPP_C"
+    }
+
+    @Issue("GRADLE-2925")
+    def "headers for source set added to library binary are available to consuming binary"() {
+        def app = new CppHelloWorldApp()
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            sources {
+                helloLib {}
+            }
+            executables {
+                main {}
+            }
+            libraries {
+                hello {
+                    binaries.all {
+                        source sources.helloLib.cpp
+                    }
+                }
+            }
+            sources.main.cpp.lib libraries.hello
+        """
+
+        and:
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/helloLib"))
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryDependenciesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryDependenciesIntegrationTest.groovy
new file mode 100755
index 0000000..740b7a9
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/LibraryDependenciesIntegrationTest.groovy
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ExeWithDiamondDependencyHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Unroll
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class LibraryDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+        buildFile << """
+            allprojects {
+                apply plugin: "cpp"
+                // Allow static libraries to be linked into shared
+                binaries.withType(StaticLibraryBinary) {
+                    if (toolChain in Gcc || toolChain in Clang) {
+                        cppCompiler.args '-fPIC'
+                    }
+                }
+            }
+"""
+    }
+
+    @Unroll
+    def "produces reasonable error message when referenced library #label"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("exe/src/main"))
+        app.library.writeSources(file("lib/src/hello"))
+
+        and:
+        settingsFile.text = "include ':exe', ':other'"
+        buildFile << """
+        project(":exe") {
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.main.cpp.lib ${dependencyNotation}
+        }
+        project(":other") {
+            libraries {
+                hello {}
+            }
+        }
+        """
+
+        when:
+        fails ":exe:mainExecutable"
+
+        then:
+        failure.assertHasDescription(description)
+        failure.assertHasCause(cause)
+
+        where:
+        label                                  | dependencyNotation                      | description                                                | cause
+        "does not exist"                       | "library: 'unknown'"                    | "Could not locate library 'unknown'."                      | "Library with name 'unknown' not found."
+        "project that does not exist"          | "project: ':unknown', library: 'hello'" | "Could not locate library 'hello' for project ':unknown'." | "Project with path ':unknown' could not be found in project ':exe'."
+        "does not exist in referenced project" | "project: ':other', library: 'unknown'" | "Could not locate library 'unknown' for project ':other'." | "Library with name 'unknown' not found."
+    }
+
+    @Unroll
+    def "can use #notationName notation to reference library in same project"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.main.cpp.lib ${notation}
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+
+        where:
+        notationName | notation
+        "direct"     | "libraries.hello"
+        "map"        | "library: 'hello'"
+    }
+
+    @Unroll
+    def "can use map #notationName notation to reference library dependency of binary"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            executables {
+                main {
+                    binaries.all { binary ->
+                        binary.lib ${notation}
+                    }
+                }
+            }
+            libraries {
+                hello {}
+            }
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+
+        where:
+        notationName | notation
+        "direct"     | "libraries.hello"
+        "map"        | "library: 'hello'"
+    }
+
+    def "can use map notation to reference static library in same project"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+
+        and:
+        buildFile << """
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'static'
+            libraries {
+                hello {}
+            }
+        """
+
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == app.englishOutput
+    }
+
+    @Unroll
+    def "can use map notation to reference library in different project#label"() {
+        given:
+        def app = new CppHelloWorldApp()
+        app.executable.writeSources(file("exe/src/main"))
+        app.library.writeSources(file("lib/src/hello"))
+
+        and:
+        settingsFile.text = "include ':lib', ':exe'"
+        buildFile << """
+        project(":exe") {
+            ${explicitEvaluation}
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib project: ':lib', library: 'hello'
+        }
+        project(":lib") {
+            libraries {
+                hello {}
+            }
+        }
+        """
+
+        when:
+        if (configureOnDemand) {
+            executer.withArgument('--configure-on-demand')
+        }
+        succeeds ":exe:installMainExecutable"
+
+        then:
+        installation("exe/build/install/mainExecutable").exec().out == app.englishOutput
+
+        where:
+        label                       | configureOnDemand | explicitEvaluation
+        ""                          | false             | ""
+        " with configure-on-demand" | true              | ""
+//        " with evaluationDependsOn" | false             | "evaluationDependsOn(':lib')"
+        " with afterEvaluate"       | false             | """
+project.afterEvaluate {
+    binaries*.libs*.linkFiles.files.each { println it }
+}
+"""
+    }
+
+    def "can use map notation to transitively reference libraries in different projects"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.writeSources(file("exe/src/main"), file("lib/src/hello"), file("greet/src/greetings"))
+
+        and:
+        settingsFile.text = "include ':exe', ':lib', ':greet'"
+        buildFile << """
+        project(":exe") {
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib project: ':lib', library: 'hello'
+        }
+        project(":lib") {
+            libraries {
+                hello {}
+            }
+            sources.hello.cpp.lib project: ':greet', library: 'greetings', linkage: 'static'
+        }
+        project(":greet") {
+            libraries {
+                greetings {}
+            }
+        }
+        """
+
+        when:
+        succeeds ":exe:installMainExecutable"
+
+        then:
+        installation("exe/build/install/mainExecutable").exec().out == app.englishOutput
+    }
+
+    def "can have component graph with project dependency cycle"() {
+        given:
+        def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
+        app.writeSources(file("exe/src/main"), file("lib/src/hello"), file("exe/src/greetings"))
+
+        and:
+        settingsFile.text = "include ':exe', ':lib'"
+        buildFile << """
+        project(":exe") {
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            libraries {
+                greetings {}
+            }
+            sources.main.cpp.lib project: ':lib', library: 'hello'
+        }
+        project(":lib") {
+            apply plugin: "cpp"
+            libraries {
+                hello {}
+            }
+            sources.hello.cpp.lib project: ':exe', library: 'greetings', linkage: 'static'
+        }
+        """
+
+        when:
+        succeeds ":exe:installMainExecutable"
+
+        then:
+        installation("exe/build/install/mainExecutable").exec().out == app.englishOutput
+    }
+
+    def "can have component graph with diamond dependency"() {
+        given:
+        def app = new ExeWithDiamondDependencyHelloWorldApp()
+        app.writeSources(file("src/main"), file("src/hello"), file("src/greetings"))
+
+        and:
+        buildFile << """
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                greetings {}
+            }
+            sources.main.cpp.lib libraries.hello.shared
+            sources.main.cpp.lib libraries.greetings.static
+            sources.hello.cpp.lib libraries.greetings.static
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+
+        and:
+        notExecuted ":greetingsSharedLibrary"
+        sharedLibrary("build/binaries/greetingsSharedLibrary/greetings").assertDoesNotExist()
+    }
+
+    def "can have component graph with both static and shared variants of same library"() {
+        given:
+        def app = new ExeWithDiamondDependencyHelloWorldApp()
+        app.writeSources(file("src/main"), file("src/hello"), file("src/greetings"))
+
+        and:
+        buildFile << """
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+                greetings {}
+            }
+            sources.main.cpp.lib libraries.hello.shared
+            sources.main.cpp.lib libraries.greetings.shared
+            sources.hello.cpp.lib libraries.greetings.static
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.englishOutput
+
+        and:
+        executedAndNotSkipped ":greetingsSharedLibrary", ":greetingsStaticLibrary"
+        sharedLibrary("build/binaries/greetingsSharedLibrary/greetings").assertExists()
+        staticLibrary("build/binaries/greetingsStaticLibrary/greetings").assertExists()
+
+        // TODO:DAZ Investigate this output and parse to ensure that greetings is dynamically linked into mainExe but not helloShared
+        and:
+        println executable("build/binaries/mainExecutable/main").binaryInfo.listLinkedLibraries()
+        println sharedLibrary("build/binaries/helloSharedLibrary/hello").binaryInfo.listLinkedLibraries()
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/MixedLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/MixedLanguageIntegrationTest.groovy
new file mode 100755
index 0000000..3a4bf5f
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/MixedLanguageIntegrationTest.groovy
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.SourceFile
+
+class MixedLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+
+    HelloWorldApp helloWorldApp = new MixedLanguageHelloWorldApp(toolChain)
+
+    def "can have all source files co-located in a common directory"() {
+        given:
+        buildFile << """
+            sources {
+                main {
+                    cpp {
+                        source {
+                            srcDirs "src/main/flat"
+                            include "**/*.cpp"
+                        }
+                    }
+                    c {
+                        source {
+                            srcDirs "src/main/flat"
+                            include "**/*.c"
+                        }
+                        exportedHeaders {
+                            srcDirs "src/main/flat"
+                        }
+                    }
+                    asm {
+                        source {
+                            srcDirs "src/main/flat"
+                            include "**/*.s"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.allFiles.each { SourceFile sourceFile ->
+            file("src/main/flat/${sourceFile.name}") << sourceFile.content
+        }
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.englishOutput
+    }
+
+    def "build and execute program with non-conventional source layout"() {
+        given:
+        buildFile << """
+            sources {
+                main {
+                    cpp {
+                        source {
+                            srcDirs "source"
+                            include "**/*.cpp"
+                        }
+                        exportedHeaders {
+                            srcDirs "source/hello", "include"
+                        }
+                    }
+                    c {
+                        source {
+                            srcDirs "source", "include"
+                            include "**/*.c"
+                        }
+                        exportedHeaders {
+                            srcDirs "source/hello", "include"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("source", "hello", "hello.cpp") << """
+            #include <iostream>
+
+            void hello () {
+              std::cout << "${HelloWorldApp.HELLO_WORLD}";
+            }
+        """
+
+        and:
+        file("source", "hello", "french", "bonjour.c") << """
+            #include <stdio.h>
+            #include "otherProject/bonjour.h"
+
+            void bonjour() {
+                printf("${HelloWorldApp.HELLO_WORLD_FRENCH}");
+            }
+        """
+
+        and:
+        file("source", "hello", "hello.h") << """
+            void hello();
+        """
+
+        and:
+        file("source", "app", "main", "main.cpp") << """
+            #include "hello.h"
+            extern "C" {
+                #include "otherProject/bonjour.h"
+            }
+
+            int main () {
+              hello();
+              bonjour();
+              return 0;
+            }
+        """
+
+        and:
+        file("include", "otherProject", "bonjour.h") << """
+            void bonjour();
+        """
+
+        and:
+        file("include", "otherProject", "bonjour.cpp") << """
+            THIS FILE WON'T BE COMPILED
+        """
+        file("src", "main", "cpp", "bad.cpp") << """
+            THIS FILE WON'T BE COMPILED
+        """
+        file("src", "main", "headers", "hello.h") << """
+            THIS FILE WON'T BE INCLUDED
+        """
+
+        when:
+        run "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == HelloWorldApp.HELLO_WORLD + HelloWorldApp.HELLO_WORLD_FRENCH
+    }
+
+
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/MultipleToolChainIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/MultipleToolChainIntegrationTest.groovy
new file mode 100755
index 0000000..b0abeb9
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/MultipleToolChainIntegrationTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppCompilerDetectingTestApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.TextUtil
+import spock.lang.Ignore
+
+ at RequiresInstalledToolChain
+class MultipleToolChainIntegrationTest extends AbstractIntegrationSpec {
+    def helloWorld = new CppCompilerDetectingTestApp()
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'cpp'
+
+            executables {
+                main {}
+            }
+            libraries {
+                hello {}
+            }
+            sources.main.cpp.lib libraries.hello
+        """
+
+        helloWorld.executable.writeSources(file("src/main"))
+        helloWorld.library.writeSources(file("src/hello"))
+    }
+
+    // TODO:DAZ Test building for 2 platforms that require different tool chains
+    @Ignore
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "can build with all multiple tool chains"() {
+        List<AvailableToolChains.InstalledToolChain> installedToolChains = []
+        for (AvailableToolChains.ToolChainCandidate toolChainCandidate : AvailableToolChains.getToolChains()) {
+            if (toolChainCandidate.isAvailable()) {
+                installedToolChains << toolChainCandidate
+            }
+        }
+
+        def toolChainConfig = installedToolChains.collect({it.buildScriptConfig}).join("\n")
+
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    ${toolChainConfig}
+                    unavailable(Gcc) {
+                        linker.executable = "does_not_exist"
+                    }
+                }
+            }
+
+"""
+
+        then:
+        def tasks = installedToolChains.collect { "install${it.id.capitalize()}MainExecutable" }
+        succeeds tasks as String[]
+
+        and:
+        installedToolChains.each { toolChain ->
+            checkInstall("build/install/mainExecutable/${toolChain.id}/main", toolChain)
+        }
+    }
+
+    def "exception when building with unavailable tool chain"() {
+        when:
+        buildFile << """
+            model {
+                toolChains {
+                    bad(Gcc) {
+                        cCompiler.executable = "does_not_exist"
+                        cppCompiler.executable = "does_not_exist"
+                    }
+                }
+            }
+"""
+
+        helloWorld.writeSources(file("src/main"))
+
+        then:
+        fails "mainExecutable"
+
+        and:
+        failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainCpp'.")
+        failure.assertHasCause(TextUtil.toPlatformLineSeparators("""No tool chain is available to build for platform 'current':
+  - Tool chain 'bad' (GNU GCC): """))
+    }
+
+    def checkInstall(String path, AvailableToolChains.InstalledToolChain toolChain) {
+        def executable = file(OperatingSystem.current().getScriptName(path))
+        executable.assertExists()
+        assert executable.execute([], toolChain.runtimeEnv).out == helloWorld.expectedOutput(toolChain)
+        return true
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/NativeBinariesPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/NativeBinariesPluginIntegrationTest.groovy
new file mode 100755
index 0000000..76da4f6
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/NativeBinariesPluginIntegrationTest.groovy
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppCallingCHelloWorldApp
+
+class NativeBinariesPluginIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def helloWorldApp = new CppCallingCHelloWorldApp()
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+    }
+
+    def "skips building executable binary with no source"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+        """
+
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").assertDoesNotExist()
+    }
+
+    def "assemble executable from component with multiple language source sets"() {
+        given:
+        useMixedSources()
+
+        when:
+        buildFile << """
+            apply plugin: "c"
+            apply plugin: "cpp"
+            sources {
+                test {}
+            }
+            executables {
+                main {
+                    source sources.test.cpp
+                    source sources.test.c
+                }
+            }
+        """
+
+        then:
+        succeeds "mainExecutable"
+
+        and:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.englishOutput
+    }
+
+    def "assemble executable binary directly from language source sets"() {
+        given:
+        useMixedSources()
+
+        when:
+        buildFile << """
+            apply plugin: "c"
+            apply plugin: "cpp"
+            sources {
+                test {}
+            }
+            executables {
+                main {}
+            }
+            binaries.all {
+                source sources.test.cpp
+                source sources.test.c
+            }
+        """
+
+        then:
+        succeeds "mainExecutable"
+
+        and:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.englishOutput
+    }
+
+    def "assemble executable binary directly from functional source set"() {
+        given:
+        useMixedSources()
+
+        when:
+        buildFile << """
+            apply plugin: "c"
+            apply plugin: "cpp"
+            sources {
+                test {}
+            }
+            executables {
+                main {}
+            }
+            binaries.all {
+                source sources.test
+            }
+        """
+        
+        then:
+        succeeds "mainExecutable"
+
+        and:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.englishOutput
+    }
+
+    def "ignores java sources added to binary"() {
+        given:
+        useMixedSources()
+        file("src/test/java/HelloWorld.java") << """
+    This would not compile
+"""
+
+        when:
+        buildFile << """
+            apply plugin: "c"
+            apply plugin: "cpp"
+            apply plugin: "java"
+
+            executables {
+                main {
+                    source sources.test.cpp
+                    source sources.test.c
+                    source sources.test.java
+                }
+            }
+         """
+
+        then:
+        succeeds "mainExecutable"
+
+        and:
+        executable("build/binaries/mainExecutable/main").exec().out == helloWorldApp.englishOutput
+    }
+
+    private def useMixedSources() {
+        helloWorldApp.writeSources(file("src/test"))
+    }
+
+    def "build fails when link executable fails"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        file("src", "main", "cpp", "helloworld.cpp") << """
+            int thing() { return 0; }
+        """
+
+        expect:
+        fails "mainExecutable"
+        failure.assertHasDescription("Execution failed for task ':linkMainExecutable'.");
+        failure.assertHasCause("Linker failed; see the error output for details.")
+    }
+
+    def "build fails when link library fails"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            libraries { main {} }
+        """
+
+        and:
+        file("src/main/cpp/hello1.cpp") << """
+            void hello() {
+            }
+"""
+
+        and:
+        file("src/main/cpp/hello2.cpp") << """
+            void hello() {
+            }
+"""
+
+        when:
+        fails "mainSharedLibrary"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':linkMainSharedLibrary'.");
+        failure.assertHasCause("Linker failed; see the error output for details.")
+    }
+
+    def "build fails when create static library fails"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp"
+            libraries { main {} }
+            binaries.withType(StaticLibraryBinary) {
+                staticLibArchiver.args "not_a_file"
+            }
+        """
+
+        and:
+        file("src/main/cpp/hello.cpp") << """
+            void hello() {
+            }
+"""
+
+        when:
+        fails "mainStaticLibrary"
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':createMainStaticLibrary'.");
+        failure.assertHasCause("Static library archiver failed; see the error output for details.")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/NativeSamplesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/NativeSamplesIntegrationTest.groovy
new file mode 100755
index 0000000..a716e3e
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/NativeSamplesIntegrationTest.groovy
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.test.cunit.CUnitTestResults
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class NativeSamplesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    @Rule public final Sample c = new Sample(temporaryFolder, 'native-binaries/c')
+    @Rule public final Sample assembler = new Sample(temporaryFolder, 'native-binaries/assembler')
+    @Rule public final Sample cpp = new Sample(temporaryFolder, 'native-binaries/cpp')
+    @Rule public final Sample cppLib = new Sample(temporaryFolder, 'native-binaries/cpp-lib')
+    @Rule public final Sample cppExe = new Sample(temporaryFolder, 'native-binaries/cpp-exe')
+    @Rule public final Sample objectiveC = new Sample(temporaryFolder, 'native-binaries/objective-c')
+    @Rule public final Sample objectiveCpp = new Sample(temporaryFolder, 'native-binaries/objective-cpp')
+    @Rule public final Sample customLayout = new Sample(temporaryFolder, 'native-binaries/custom-layout')
+    @Rule public final Sample multiProject = new Sample(temporaryFolder, 'native-binaries/multi-project')
+    @Rule public final Sample flavors = new Sample(temporaryFolder, 'native-binaries/flavors')
+    @Rule public final Sample variants = new Sample(temporaryFolder, 'native-binaries/variants')
+    @Rule public final Sample toolChains = new Sample(temporaryFolder, 'native-binaries/tool-chains')
+    @Rule public final Sample windowsResources = new Sample(temporaryFolder, 'native-binaries/windows-resources')
+    @Rule public final Sample visualStudio = new Sample(temporaryFolder, 'native-binaries/visual-studio')
+    @Rule public final Sample prebuilt = new Sample(temporaryFolder, 'native-binaries/prebuilt')
+    @Rule public final Sample idl = new Sample(temporaryFolder, 'native-binaries/idl')
+    @Rule public final Sample cunit = new Sample(temporaryFolder, 'native-binaries/cunit')
+
+    def "assembler"() {
+        given:
+        sample assembler
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        nonSkippedTasks.count { it.startsWith(":assembleMainExecutable") } == 1
+        executedAndNotSkipped ":compileMainExecutableMainC", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation("native-binaries/assembler/build/install/mainExecutable").exec().out == "5 + 7 = 12\n"
+    }
+
+    def "c"() {
+        given:
+        sample c
+        
+        when:
+        run "installMainExecutable"
+        
+        then:
+        executedAndNotSkipped ":compileHelloSharedLibraryHelloC", ":linkHelloSharedLibrary", ":helloSharedLibrary",
+                              ":compileMainExecutableMainC", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation("native-binaries/c/build/install/mainExecutable").exec().out == "Hello world!"
+    }
+
+    def "cpp"() {
+        given:
+        sample cpp
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileHelloSharedLibraryHelloCpp", ":linkHelloSharedLibrary", ":helloSharedLibrary",
+                              ":compileMainExecutableMainCpp", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation("native-binaries/cpp/build/install/mainExecutable").exec().out == "Hello world!\n"
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "objectiveC"() {
+        given:
+        sample objectiveC
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainObjc", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        executable("native-binaries/objective-c/build/binaries/mainExecutable/main").exec().out == "Hello world!\n"
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "objectiveCpp"() {
+        given:
+        sample objectiveCpp
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainObjcpp", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        executable("native-binaries/objective-cpp/build/binaries/mainExecutable/main").exec().out == "Hello world!\n"
+    }
+
+    def "exe"() {
+        given:
+        // Need to PATH to be set to find the 'strip' executable
+        toolChain.initialiseEnvironment()
+
+        and:
+        sample cppExe
+
+        when:
+        run "installMain"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainCpp", ":linkMainExecutable", ":stripMainExecutable", ":mainExecutable"
+
+        and:
+        executable("native-binaries/cpp-exe/build/binaries/mainExecutable/main").exec().out == "Hello, World!\n"
+        installation("native-binaries/cpp-exe/build/install/mainExecutable").exec().out == "Hello, World!\n"
+
+        cleanup:
+        toolChain.resetEnvironment()
+    }
+
+    def "lib"() {
+        given:
+        sample cppLib
+
+        when:
+        run "mainSharedLibrary"
+
+        then:
+        executedAndNotSkipped ":compileMainSharedLibraryMainCpp", ":linkMainSharedLibrary", ":mainSharedLibrary"
+
+        and:
+        sharedLibrary("native-binaries/cpp-lib/build/binaries/mainSharedLibrary/main").assertExists()
+
+        when:
+        sample cppLib
+        run "mainStaticLibrary"
+
+        then:
+        executedAndNotSkipped ":compileMainStaticLibraryMainCpp", ":createMainStaticLibrary", ":mainStaticLibrary"
+
+        and:
+        staticLibrary("native-binaries/cpp-lib/build/binaries/mainStaticLibrary/main").assertExists()
+    }
+
+
+    @RequiresInstalledToolChain(VisualCpp)
+    def "win rc"() {
+        given:
+        sample windowsResources
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileHelloSharedLibraryHelloCpp", ":compileHelloSharedLibraryHelloRc",
+                              ":linkHelloSharedLibrary", ":helloSharedLibrary",
+                              ":compileMainExecutableMainCpp", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation("native-binaries/windows-resources/build/install/mainExecutable").exec().out == "Hello world!\n"
+
+        when:
+        executer.usingBuildScript(windowsResources.dir.file('build-resource-only-dll.gradle'))
+        run "helloResSharedLibrary"
+
+        then:
+        file("native-binaries/windows-resources/build/binaries/helloResSharedLibrary/helloRes.dll").assertExists()
+    }
+
+    def "custom layout"() {
+        given:
+        sample customLayout
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileHelloStaticLibraryHelloC", ":createHelloStaticLibrary", ":helloStaticLibrary",
+                              ":compileMainExecutableMainCpp", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation("native-binaries/custom-layout/build/install/mainExecutable").exec().out == "Hello world!"
+    }
+
+    def flavors() {
+        when:
+        sample flavors
+        run "installEnglishMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileEnglishHelloSharedLibraryLibCpp", ":linkEnglishHelloSharedLibrary", ":englishHelloSharedLibrary"
+        executedAndNotSkipped ":compileEnglishMainExecutableExeCpp", ":linkEnglishMainExecutable", ":englishMainExecutable"
+
+        and:
+        executable("native-binaries/flavors/build/binaries/mainExecutable/english/main").assertExists()
+        sharedLibrary("native-binaries/flavors/build/binaries/helloSharedLibrary/english/hello").assertExists()
+
+        and:
+        installation("native-binaries/flavors/build/install/mainExecutable/english").exec().out == "Hello world!\n"
+
+        when:
+        sample flavors
+        run "installFrenchMainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileFrenchHelloSharedLibraryLibCpp", ":linkFrenchHelloSharedLibrary", ":frenchHelloSharedLibrary"
+        executedAndNotSkipped ":compileFrenchMainExecutableExeCpp", ":linkFrenchMainExecutable", ":frenchMainExecutable"
+
+        and:
+        executable("native-binaries/flavors/build/binaries/mainExecutable/french/main").assertExists()
+        sharedLibrary("native-binaries/flavors/build/binaries/helloSharedLibrary/french/hello").assertExists()
+
+        and:
+        installation("native-binaries/flavors/build/install/mainExecutable/french").exec().out == "Bonjour monde!\n"
+    }
+
+    def variants() {
+        when:
+        sample variants
+        run "buildExecutables"
+
+        then:
+        final debugX86 = executable("native-binaries/variants/build/binaries/mainExecutable/x86Debug/main")
+        final releaseX86 = executable("native-binaries/variants/build/binaries/mainExecutable/x86Release/main")
+        final debugX64 = executable("native-binaries/variants/build/binaries/mainExecutable/x64Debug/main")
+        final releaseX64 = executable("native-binaries/variants/build/binaries/mainExecutable/x64Release/main")
+        final debugIA64 = executable("native-binaries/variants/build/binaries/mainExecutable/itaniumDebug/main")
+        final releaseIA64 = executable("native-binaries/variants/build/binaries/mainExecutable/itaniumRelease/main")
+
+        debugX86.binaryInfo.arch.name == "x86"
+        debugX86.assertDebugFileExists()
+        debugX86.exec().out == "Hello world!\n"
+
+        releaseX86.binaryInfo.arch.name == "x86"
+        releaseX86.assertDebugFileDoesNotExist()
+        releaseX86.exec().out == "Hello world!\n"
+
+        // x86_64 binaries not supported on MinGW or cygwin
+        if (toolChain.id == "mingw" || toolChain.id == "gcccygwin") {
+            debugX64.assertDoesNotExist()
+            releaseX64.assertDoesNotExist()
+        } else {
+            debugX64.binaryInfo.arch.name == "x86_64"
+            releaseX64.binaryInfo.arch.name == "x86_64"
+        }
+
+        // Itanium not built
+        debugIA64.assertDoesNotExist()
+        releaseIA64.assertDoesNotExist()
+    }
+
+    def "tool chains"() {
+        given:
+        sample toolChains
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        executable("native-binaries/tool-chains/build/binaries/mainExecutable/main").exec().out == "Hello from ${toolChain.typeDisplayName}!\n"
+    }
+
+    def multiProject() {
+        given:
+        sample multiProject
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        ":exe:mainExecutable" in executedTasks
+
+        and:
+        sharedLibrary("native-binaries/multi-project/lib/build/binaries/mainSharedLibrary/main").assertExists()
+        executable("native-binaries/multi-project/exe/build/binaries/mainExecutable/main").assertExists()
+        installation("native-binaries/multi-project/exe/build/install/mainExecutable").exec().out == "Hello, World!\n"
+    }
+
+    def "visual studio"() {
+        given:
+        sample visualStudio
+
+        when:
+        run "mainVisualStudio"
+
+        then:
+        final solutionFile = new SolutionFile(visualStudio.dir.file("vs/mainExe.sln"))
+        solutionFile.assertHasProjects("mainExe", "helloDll")
+        solutionFile.content.contains "GlobalSection(SolutionNotes) = postSolution"
+        solutionFile.content.contains "Text2 = The projects in this solution are [mainExe, helloDll]."
+
+        final projectFile = new ProjectFile(visualStudio.dir.file("vs/helloDll.vcxproj"))
+        projectFile.projectXml.PropertyGroup.find({it.'@Label' == 'Custom'}).ProjectDetails[0].text() == "Project is named helloDll"
+    }
+
+    def prebuilt() {
+        given:
+        inDirectory(prebuilt.dir.file("3rd-party-lib/util"))
+        run "buildLibraries"
+
+        and:
+        sample prebuilt
+
+        when:
+        succeeds "buildExecutables"
+
+        then:
+
+        executable(prebuilt.dir.file("build/binaries/mainExecutable/debug/main")).exec().out ==
+"""Built with Boost version: 1_55
+Util build type: DEBUG
+"""
+        executable(prebuilt.dir.file("build/binaries/mainExecutable/release/main")).exec().out ==
+"""Built with Boost version: 1_55
+Util build type: RELEASE
+"""
+    }
+
+    def "idl"() {
+        given:
+        sample idl
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":idl", ":compileMainExecutableMainC", ":compileMainExecutableMainIdlOutput",
+                              ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation("native-binaries/idl/build/install/mainExecutable").exec().out == "Hello from generated source!!\n"
+    }
+
+    def "cunit"() {
+        given:
+        // CUnit prebuilt library only works for VS2010 on windows
+        if (OperatingSystem.current().windows && !isVisualCpp2010()) {
+            return
+        }
+
+        when:
+        sample cunit
+        succeeds "runPassing"
+
+        then:
+        executedAndNotSkipped ":operatorsTestCUnitLauncher",
+                              ":compilePassingOperatorsTestCUnitExeOperatorsTestCunit", ":compilePassingOperatorsTestCUnitExeOperatorsTestCunitLauncher",
+                              ":linkPassingOperatorsTestCUnitExe", ":passingOperatorsTestCUnitExe",
+                              ":installPassingOperatorsTestCUnitExe", ":runPassingOperatorsTestCUnitExe"
+
+        and:
+        def passingResults = new CUnitTestResults(file("native-binaries/cunit/build/test-results/operatorsTestCUnitExe/passing/CUnitAutomated-Results.xml"))
+        passingResults.suiteNames == ['operator tests']
+        passingResults.suites['operator tests'].passingTests == ['test_plus', 'test_minus']
+        passingResults.suites['operator tests'].failingTests == []
+        passingResults.checkTestCases(2, 2, 0)
+        passingResults.checkAssertions(6, 6, 0)
+
+        when:
+        sample cunit
+        fails "runFailing"
+
+        then:
+        skipped ":operatorsTestCUnitLauncher"
+        executedAndNotSkipped ":compileFailingOperatorsTestCUnitExeOperatorsTestCunit", ":compileFailingOperatorsTestCUnitExeOperatorsTestCunitLauncher",
+                              ":linkFailingOperatorsTestCUnitExe", ":failingOperatorsTestCUnitExe",
+                              ":installFailingOperatorsTestCUnitExe", ":runFailingOperatorsTestCUnitExe"
+
+        and:
+        def failingResults = new CUnitTestResults(file("native-binaries/cunit/build/test-results/operatorsTestCUnitExe/failing/CUnitAutomated-Results.xml"))
+        failingResults.suiteNames == ['operator tests']
+        failingResults.suites['operator tests'].passingTests == ['test_minus']
+        failingResults.suites['operator tests'].failingTests == ['test_plus']
+        failingResults.checkTestCases(2, 1, 1)
+        failingResults.checkAssertions(6, 4, 2)
+    }
+
+    private static boolean isVisualCpp2010() {
+        return (toolChain.visualCpp && (toolChain as AvailableToolChains.InstalledVisualCpp).version.major == "10")
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/PrebuiltLibrariesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/PrebuiltLibrariesIntegrationTest.groovy
new file mode 100755
index 0000000..11a3075
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/PrebuiltLibrariesIntegrationTest.groovy
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+class PrebuiltLibrariesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    final app = new CppHelloWorldApp()
+
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("libs/src/hello"))
+
+        file("libs/build.gradle") << """
+            apply plugin: 'cpp'
+            model {
+                flavors {
+                    english
+                    french
+                }
+            }
+            libraries {
+                hello {
+                    binaries.all {
+                        if (flavor == flavors.french) {
+                            cppCompiler.define "FRENCH"
+                        }
+                    }
+                }
+            }
+            task buildAll {
+                dependsOn binaries.matching {
+                    it.buildable
+                }
+            }
+"""
+    }
+
+    private void preBuildLibrary() {
+        executer.inDirectory(file("libs"))
+        run "buildAll"
+    }
+
+    def "can link to a prebuilt header-only library with api linkage"() {
+        given:
+        app.alternateLibrarySources*.writeToDir(file("src/main"))
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                repositories {
+                    libs(PrebuiltLibraries) {
+                        hello {
+                            headers.srcDir "libs/src/hello/headers"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'api'
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.alternateLibraryOutput
+    }
+
+    def "can link to a prebuilt library with static and shared linkage"() {
+        given:
+        preBuildLibrary()
+
+        and:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                repositories {
+                    libs(PrebuiltLibraries) {
+                        hello {
+                            headers.srcDir "libs/src/hello/headers"
+                            binaries.withType(StaticLibraryBinary) {
+                                def libName = targetPlatform.operatingSystem.windows ? 'hello.lib' : 'libhello.a'
+                                staticLibraryFile = file("libs/build/binaries/helloStaticLibrary/english/\${libName}")
+                            }
+                            binaries.withType(SharedLibraryBinary) {
+                                def os = targetPlatform.operatingSystem
+                                def baseDir = "libs/build/binaries/helloSharedLibrary/french"
+                                if (os.windows) {
+                                    // Windows uses a .dll file, and a different link file if it exists (not Cygwin or MinGW)
+                                    sharedLibraryFile = file("\${baseDir}/hello.dll")
+                                    if (file("\${baseDir}/hello.lib").exists()) {
+                                        sharedLibraryLinkFile = file("\${baseDir}/hello.lib")
+                                    }
+                                } else if (os.macOsX) {
+                                    sharedLibraryFile = file("\${baseDir}/libhello.dylib")
+                                } else {
+                                    sharedLibraryFile = file("\${baseDir}/libhello.so")
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+                mainStatic {}
+            }
+            sources {
+                main.cpp {
+                    lib library: 'hello'
+                }
+                mainStatic.cpp {
+                    source.srcDir "src/main/cpp"
+                    lib library: 'hello', linkage: 'static'
+                }
+            }
+        """
+
+        when:
+        succeeds "installMainExecutable", "installMainStaticExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.frenchOutput
+        installation("build/install/mainStaticExecutable").exec().out == app.englishOutput
+    }
+
+    def "searches all prebuilt library repositories"() {
+        given:
+        preBuildLibrary()
+
+        and:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                repositories {
+                    libs1(PrebuiltLibraries) {
+                        nope {
+                            headers.srcDir "not/here"
+                        }
+                    }
+                    libs2(PrebuiltLibraries) {
+                        hello {
+                            headers.srcDir "libs/src/hello/headers"
+                            binaries.withType(StaticLibraryBinary) {
+                                def libName = targetPlatform.operatingSystem.windows ? 'hello.lib' : 'libhello.a'
+                                staticLibraryFile = file("libs/build/binaries/helloStaticLibrary/french/\${libName}")
+                            }
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'static'
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("build/install/mainExecutable").exec().out == app.frenchOutput
+    }
+
+    def "locates prebuilt library in another project"() {
+        given:
+        app.executable.writeSources(file("projectA/src/main"))
+        app.librarySources*.writeToDir(file("projectA/src/main"))
+        app.libraryHeader.writeToDir(file("projectB/libs/src/hello"))
+
+        and:
+        settingsFile.text = "include ':projectA', ':projectB'"
+        buildFile << """
+            project(':projectA') {
+                apply plugin: 'cpp'
+                executables {
+                    main {}
+                }
+                sources.main.cpp.lib project: ':projectB', library: 'hello', linkage: 'api'
+            }
+            project(':projectB') {
+                apply plugin: 'cpp'
+                model {
+                    repositories {
+                        libs(PrebuiltLibraries) {
+                            hello {
+                                headers.srcDir "../libs/src/hello/headers"
+                            }
+                        }
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds "installMainExecutable"
+
+        then:
+        installation("projectA/build/install/mainExecutable").exec().out == app.englishOutput
+    }
+
+    def "produces reasonable error message when no output file is defined for binary"() {
+        given:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                repositories {
+                    libs(PrebuiltLibraries) {
+                        hello {
+                            headers.srcDir "libs/src/hello/headers"
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'static'
+        """
+
+        when:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Static library file not set for prebuilt library 'hello'.")
+    }
+
+    def "produces reasonable error message when prebuilt library output file does not exist"() {
+        given:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                repositories {
+                    libs(PrebuiltLibraries) {
+                        hello {
+                            headers.srcDir "libs/src/hello/headers"
+                            binaries.withType(StaticLibraryBinary) { binary ->
+                                staticLibraryFile = file("does_not_exist")
+                            }
+                        }
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib library: 'hello', linkage: 'static'
+        """
+
+        when:
+        succeeds "tasks"
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Static library file ${file("does_not_exist").absolutePath} does not exist for prebuilt library 'hello'.")
+    }
+
+    def "produces reasonable error message when prebuilt library does not exist"() {
+        given:
+        buildFile << """
+            apply plugin: 'cpp'
+            model {
+                repositories {
+                    libs(PrebuiltLibraries) {
+                        hello
+                    }
+                    libs2(PrebuiltLibraries) {
+                        hello2
+                    }
+                }
+            }
+            executables {
+                main {}
+            }
+            sources.main.cpp.lib library: 'other'
+        """
+
+        when:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Could not locate library 'other'.")
+        failure.assertHasCause("Library with name 'other' not found.")
+        failure.assertHasCause("Prebuilt library with name 'other' not found in repositories '[libs, libs2]'.")
+    }
+
+    def "produces reasonable error message when prebuilt library does not exist in a different project"() {
+        given:
+        settingsFile.text = "include ':projectA', ':projectB'"
+        buildFile << """
+            project(':projectA') {
+                apply plugin: 'cpp'
+                model {
+                    repositories {
+                        libs(PrebuiltLibraries) {
+                            hello {
+                                headers.srcDir "libs/src/hello/headers"
+                            }
+                        }
+                    }
+                }
+                executables {
+                    main {}
+                }
+                sources.main.cpp.lib project: ':projectB', library: 'hello', linkage: 'api'
+            }
+            project(':projectB') {
+                apply plugin: 'cpp'
+                model {
+                    repositories {
+                        libs(PrebuiltLibraries) {
+                            hello1
+                        }
+                        libs2(PrebuiltLibraries) {
+                            hello2
+                        }
+                    }
+                }
+            }
+        """
+
+        when:
+        fails "mainExecutable"
+
+        then:
+        failure.assertHasDescription("Could not locate library 'hello' for project ':projectB'.")
+        failure.assertHasCause("Library with name 'hello' not found.")
+        failure.assertHasCause("Prebuilt library with name 'hello' not found in repositories '[libs, libs2]'.")
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/SharedLibrarySoNameIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/SharedLibrarySoNameIntegrationTest.groovy
new file mode 100755
index 0000000..595374d
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/SharedLibrarySoNameIntegrationTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class SharedLibrarySoNameIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+    def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
+
+        def app = new CppHelloWorldApp()
+        app.library.writeSources(file("src/hello"))
+
+        buildFile << """
+            apply plugin: 'cpp'
+            libraries {
+                hello {}
+            }
+        """
+    }
+
+    def "library soname is file name when installName is not set"() {
+        when:
+        succeeds "helloSharedLibrary"
+
+        then:
+        final sharedLibrary = sharedLibrary("build/binaries/helloSharedLibrary/hello")
+        sharedLibrary.soName == sharedLibrary.file.name
+    }
+
+    def "library soname uses specified installName"() {
+        given:
+        buildFile << """
+            tasks.withType(LinkSharedLibrary) {
+                it.installName = 'hello-install-name'
+            }
+        """
+
+        when:
+        succeeds "helloSharedLibrary"
+
+        then:
+        sharedLibrary("build/binaries/helloSharedLibrary/hello").soName == "hello-install-name"
+    }
+
+    def "library soname defaults when installName is null"() {
+        given:
+        buildFile << """
+            tasks.withType(LinkSharedLibrary) {
+                it.installName = null
+            }
+        """
+
+        when:
+        succeeds "helloSharedLibrary"
+
+        then:
+        final library = sharedLibrary("build/binaries/helloSharedLibrary/hello")
+        def expectedSoName = OperatingSystem.current().macOsX ? library.file.absolutePath : null
+        library.soName == expectedSoName
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/SourceSetDependenciesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/SourceSetDependenciesIntegrationTest.groovy
new file mode 100755
index 0000000..534fcce
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/SourceSetDependenciesIntegrationTest.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppCallingCHelloWorldApp
+
+// TODO:DAZ Test incremental
+// TODO:DAZ Test dependency on functional source set
+// TODO:DAZ Test dependency on source set that is not HeaderExportingSourceSet
+// TODO:DAZ Sad day tests
+class SourceSetDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    def "source dependency on source set of same type"() {
+        def app = new CHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/library"))
+
+        buildFile << """
+    apply plugin: 'c'
+    sources {
+        main {}
+        library {}
+    }
+    sources.main.c.lib sources.library.c
+    executables {
+        main {
+            source sources.library
+        }
+    }
+"""
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == app.englishOutput
+    }
+
+    def "source dependency on source set of headers"() {
+        def app = new CHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.sourceFiles*.writeToDir(file("src/main"))
+        app.library.headerFiles*.writeToDir(file("src/library"))
+
+        buildFile << """
+    apply plugin: 'c'
+    // library not required in executable: only headers are used
+    executables {
+        main {}
+    }
+    sources {
+        library {}
+    }
+    sources.main.c.lib sources.library.c
+"""
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == app.englishOutput
+    }
+
+    def "source dependency on source set of different type"() {
+        def app = new CppCallingCHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/library"))
+
+        buildFile << """
+    apply plugin: 'cpp'
+    apply plugin: 'c'
+    sources {
+        main {}
+        library {}
+    }
+    sources.main.cpp.lib sources.library.c
+    executables {
+        main {
+            source sources.library
+        }
+    }
+"""
+        when:
+        succeeds "mainExecutable"
+
+        then:
+        executable("build/binaries/mainExecutable/main").exec().out == app.englishOutput
+    }
+
+    def "source files in depended-on source set are not included"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.writeSources(file("src/main"))
+
+        file("src/extra/cpp/bad.cpp") << """
+    FILE WILL BE IGNORED: source set dependency set only considers headers
+"""
+
+        buildFile << """
+    apply plugin: 'cpp'
+    executables {
+        main {}
+    }
+    sources {
+        extra {}
+    }
+    sources.main.cpp.lib sources.extra.cpp
+"""
+        expect:
+        succeeds "mainExecutable"
+    }
+
+    def "binary depending on source set has no effect"() {
+        given:
+        def app = new CHelloWorldApp()
+        app.writeSources(file("src/main"))
+
+        file("src/extra/headers/hello.h") << """
+    FILE WILL BE IGNORED: source set dependency added to binary
+"""
+
+        buildFile << """
+    apply plugin: 'cpp'
+    sources {
+        extra {}
+    }
+    executables {
+        main {
+            binaries.all {
+                lib sources.extra.cpp
+            }
+        }
+    }
+"""
+        expect:
+        succeeds "mainExecutable"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/ToolChainDiscoveryIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/ToolChainDiscoveryIntegrationTest.groovy
new file mode 100755
index 0000000..91bbf69
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/ToolChainDiscoveryIntegrationTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppCompilerDetectingTestApp
+
+/**
+ * Test that each available tool chain can be discovered and used without configuration, assuming it is in the path.
+ */
+class ToolChainDiscoveryIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    def helloWorldApp = new CppCompilerDetectingTestApp()
+
+    def setup() {
+        // Discard init script content generated by superclass
+        initScript.text = ""
+    }
+
+    def "can discover tool chain in environment"() {
+        given:
+        toolChain.initialiseEnvironment();
+
+        and:
+        // We explicitly apply a single tool chain plugin here, to avoid using an alternative tool chain
+        buildFile << """
+            apply plugin: CppNativeBinariesPlugin
+            apply plugin: ${toolChain.pluginClass}
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.expectedOutput(toolChain)
+
+        cleanup:
+        toolChain.resetEnvironment();
+    }
+
+    def "uses correct tool chain when explicitly configured"() {
+        given:
+        buildFile << """
+            apply plugin: 'cpp'
+
+            model {
+                toolChains {
+                    ${toolChain.buildScriptConfig}
+                }
+            }
+
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        run "mainExecutable"
+
+        then:
+        def mainExecutable = executable("build/binaries/mainExecutable/main")
+        mainExecutable.assertExists()
+        mainExecutable.exec().out == helloWorldApp.expectedOutput(toolChain)
+    }
+
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesIncrementalBuildIntegrationTest.groovy
new file mode 100644
index 0000000..09f6e0d
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.ExecutableFixture
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.WindowsResourceHelloWorldApp
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+
+ at RequiresInstalledToolChain(VisualCpp)
+class WindowsResourcesIncrementalBuildIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    HelloWorldApp helloWorldApp = new WindowsResourceHelloWorldApp()
+    ExecutableFixture mainExe
+    def mainResourceFile
+    def unusedHeaderFile
+
+    def "setup"() {
+        buildFile << helloWorldApp.pluginScript
+        buildFile << helloWorldApp.extraConfiguration
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        helloWorldApp.writeSources(file("src/main"))
+        unusedHeaderFile = file("src/main/headers/unused.h") << """
+    #define DUMMY_HEADER_FILE
+"""
+
+        run "mainExecutable"
+
+        mainExe = executable("build/binaries/mainExecutable/main")
+        mainResourceFile = file("src/main/rc/resources.rc")
+    }
+
+    def "does not re-compile sources with no change"() {
+        when:
+        run "mainExecutable"
+
+        then:
+        nonSkippedTasks.empty
+    }
+
+    def "compiles and links when resource source changes"() {
+        when:
+        file("src/main/rc/resources.rc").text = """
+#include "hello.h"
+
+STRINGTABLE
+{
+    IDS_HELLO, "Goodbye"
+}
+"""
+
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainRc", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        mainExe.exec().out == "Goodbye"
+    }
+
+    def "compiles and but does not link when resource source changes with comment only"() {
+        when:
+        file("src/main/rc/resources.rc") << """
+// Comment added to the end of the resource file
+"""
+
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainRc"
+        skipped ":linkMainExecutable", ":mainExecutable"
+    }
+
+    def "compiles and links when resource compiler arg changes"() {
+        when:
+        buildFile << """
+            executables {
+                main {
+                    binaries.all {
+                        // Use a compiler arg that will change the generated .res file
+                        rcCompiler.args "-DFRENCH"
+                    }
+                }
+            }
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainRc", ":linkMainExecutable", ":mainExecutable"
+    }
+
+    def "stale .res files are removed when a resource source file is renamed"() {
+        given:
+        def oldResFile = file("build/objectFiles/mainExecutable/mainRc/${hashFor(mainResourceFile)}/resources.res")
+        def newResFile = file("build/objectFiles/mainExecutable/mainRc/${hashFor(file('src/main/rc/changed_resources.rc'))}/changed_resources.res")
+        assert oldResFile.file
+        assert !newResFile.file
+
+        when:
+        mainResourceFile.renameTo(file("src/main/rc/changed_resources.rc"))
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped ":compileMainExecutableMainRc"
+
+        and:
+        !oldResFile.file
+        newResFile.file
+    }
+
+    def "recompiles resource when included header is changed"() {
+        given: "set the generated res file timestamp to zero"
+        def resourceFile = file("build/objectFiles/mainExecutable/mainRc/${hashFor(mainResourceFile)}/resources.res")
+        resourceFile.lastModified = 0
+        when: "Unused header is changed"
+        unusedHeaderFile << """
+    #define EXTRA_DEFINE
+"""
+        and:
+        run "mainExecutable"
+
+        then: "No resource compilation"
+        skipped ":compileMainExecutableMainRc"
+        resourceFile.lastModified() == 0
+
+        when:
+        file("src/main/headers/hello.h") << """
+    #define EXTRA_DEFINE
+"""
+        and:
+        run "mainExecutable"
+
+        then: "Resource is recompiled"
+        executedAndNotSkipped ":compileMainExecutableMainRc"
+        resourceFile.lastModified() > 0
+    }
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesIntegrationTest.groovy
new file mode 100755
index 0000000..df4bac6
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesIntegrationTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.apache.commons.lang.RandomStringUtils
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.WindowsResourceHelloWorldApp
+import org.gradle.test.fixtures.file.TestFile
+import spock.lang.Ignore
+
+import static org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement.VisualCpp
+
+ at RequiresInstalledToolChain(VisualCpp)
+class WindowsResourcesIntegrationTest extends AbstractLanguageIntegrationTest {
+
+    HelloWorldApp helloWorldApp = new WindowsResourceHelloWorldApp()
+
+    def "user receives a reasonable error message when resource compilation fails"() {
+        given:
+        buildFile << """
+             executables {
+                 main {}
+             }
+         """
+
+        and:
+        file("src/main/rc/broken.rc") << """
+        #include <stdio.h>
+
+        NOT A VALID RESOURCE
+"""
+
+        expect:
+        fails "mainExecutable"
+        failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainRc'.");
+        failure.assertHasCause("Windows resource compiler failed; see the error output for details.")
+    }
+
+    def "can create resources-only shared library"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+            libraries {
+                resources {
+                    binaries.all {
+                        linker.args "/noentry", "/machine:x86"
+                    }
+                }
+            }
+            sources.main.cpp.lib libraries.resources
+"""
+
+        and:
+        file("src/resources/rc/resources.rc") << """
+            #include "resources.h"
+
+            STRINGTABLE
+            {
+                IDS_HELLO, "Hello!"
+            }
+        """
+
+        and:
+        file("src/resources/headers/resources.h") << """
+            #define IDS_HELLO    111
+        """
+
+        and:
+        file("src/main/cpp/main.cpp") << """
+            #include <iostream>
+            #include <windows.h>
+            #include <string>
+            #include "resources.h"
+
+            std::string LoadStringFromResource(UINT stringID)
+            {
+                HMODULE instance = LoadLibraryEx("resources.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
+                WCHAR * pBuf = NULL;
+                int len = LoadStringW(instance, stringID, reinterpret_cast<LPWSTR>(&pBuf), 0);
+                std::wstring wide = std::wstring(pBuf, len);
+                return std::string(wide.begin(), wide.end());
+            }
+
+            int main() {
+                std::string hello = LoadStringFromResource(IDS_HELLO);
+                std::cout << hello;
+                return 0;
+            }
+        """
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        resourceOnlyLibrary("build/binaries/resourcesSharedLibrary/resources").assertExists()
+        installation("build/install/mainExecutable").exec().out == "Hello!"
+    }
+
+    @Ignore
+    def "windows resources compiler can use long file paths"() {
+        // windows can't handle a path up to 260 characters
+        // we create a project path that is ~180 characters to end up
+        // with a path for the compiled resources.res > 260 chars
+        def projectPathOffset = 180 - testDirectory.getAbsolutePath().length()
+        def nestedProjectPath = RandomStringUtils.randomAlphanumeric(projectPathOffset-10) + "/123456789"
+
+        setup:
+        def deepNestedProjectFolder = file(nestedProjectPath)
+        executer.usingProjectDirectory(deepNestedProjectFolder)
+        def TestFile buildFile = deepNestedProjectFolder.file("build.gradle")
+        buildFile << helloWorldApp.pluginScript
+        buildFile << helloWorldApp.extraConfiguration
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("$nestedProjectPath/src/main"));
+
+        expect:
+        // this test is just for verifying explicitly the behaviour of the windows resource compiler
+        // that's why we explicitly trigger this task instead of main.
+        succeeds "mainExecutable"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesUnsupportedIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesUnsupportedIntegrationTest.groovy
new file mode 100755
index 0000000..616daa7
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/WindowsResourcesUnsupportedIntegrationTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.app.CppHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class WindowsResourcesUnsupportedIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
+
+    HelloWorldApp helloWorldApp = new CppHelloWorldApp()
+
+    def "resource files are ignored on unsupported platforms"() {
+        given:
+        buildFile << """
+            apply plugin: 'cpp'
+            apply plugin: 'windows-resources'
+
+            executables {
+                main {}
+            }
+         """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+        file("src/main/rc/broken.rc") << """
+        #include <stdio.h>
+
+        NOT A VALID RESOURCE
+"""
+
+        when:
+        run "mainExecutable"
+
+        then:
+        !executedTasks.contains(":compileMainExecutableMainRc")
+    }
+}
+
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/fixtures/AbstractInstalledToolChainIntegrationSpec.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/fixtures/AbstractInstalledToolChainIntegrationSpec.groovy
new file mode 100755
index 0000000..34a1d33
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/fixtures/AbstractInstalledToolChainIntegrationSpec.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.nativebinaries.language.cpp.fixtures
+
+import org.apache.commons.io.FilenameUtils
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.internal.hash.HashUtil
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.runner.RunWith
+/**
+ * Runs a test separately for each installed tool chain.
+ */
+ at RunWith(SingleToolChainTestRunner.class)
+abstract class AbstractInstalledToolChainIntegrationSpec extends AbstractIntegrationSpec {
+    static AvailableToolChains.InstalledToolChain toolChain
+    File initScript
+
+    def setup() {
+        initScript = file("init.gradle") << """
+allprojects {
+    apply plugin: ${toolChain.pluginClass}
+    model {
+        toolChains {
+            ${toolChain.buildScriptConfig}
+        }
+    }
+}
+"""
+        executer.beforeExecute({
+            usingInitScript(initScript)
+        })
+    }
+
+    def NativeInstallationFixture installation(Object installDir) {
+        return new NativeInstallationFixture(file(installDir))
+    }
+
+    def ExecutableFixture executable(Object path) {
+        return toolChain.executable(file(path))
+    }
+
+    def TestFile objectFile(Object path) {
+        return toolChain.objectFile(file(path))
+    }
+
+    def SharedLibraryFixture sharedLibrary(Object path) {
+        return toolChain.sharedLibrary(file(path))
+    }
+
+    def StaticLibraryFixture staticLibrary(Object path) {
+        return toolChain.staticLibrary(file(path))
+    }
+
+    def NativeBinaryFixture resourceOnlyLibrary(Object path) {
+        return toolChain.resourceOnlyLibrary(file(path))
+    }
+
+    def objectFileFor(TestFile sourceFile, String rootObjectFilesDir = "build/objectFiles/mainExecutable/main${sourceType}") {
+        final baseName = FilenameUtils.removeExtension(sourceFile.name)
+        String compactMD5 = HashUtil.createCompactMD5(sourceFile.getAbsolutePath());
+        return objectFile("$rootObjectFilesDir/$compactMD5/${baseName}")
+    }
+
+    String hashFor(File inputFile){
+        HashUtil.createCompactMD5(inputFile.getAbsolutePath());
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/fixtures/SingleToolChainTestRunner.java b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/fixtures/SingleToolChainTestRunner.java
new file mode 100755
index 0000000..dd626d4
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/fixtures/SingleToolChainTestRunner.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.nativebinaries.language.cpp.fixtures;
+
+import org.gradle.integtests.fixtures.AbstractMultiTestRunner;
+
+import java.util.List;
+
+public class SingleToolChainTestRunner extends AbstractMultiTestRunner {
+    private static final String TOOLCHAINS_SYSPROP_NAME = "org.gradle.integtest.cpp.toolChains";
+
+    public SingleToolChainTestRunner(Class<? extends AbstractInstalledToolChainIntegrationSpec> target) {
+        super(target);
+    }
+
+    @Override
+    protected void createExecutions() {
+        boolean enableAllToolChains = "all".equals(System.getProperty(TOOLCHAINS_SYSPROP_NAME, "default"));
+        List<AvailableToolChains.ToolChainCandidate> toolChains = AvailableToolChains.getToolChains();
+        boolean someToolChainAvailable = false;
+        for (AvailableToolChains.ToolChainCandidate toolChain : toolChains) {
+            boolean enabled = enableAllToolChains || (toolChain.isAvailable() && !someToolChainAvailable);
+            someToolChainAvailable |= enabled;
+            add(new ToolChainExecution(toolChain, enabled));
+        }
+    }
+
+    private static class ToolChainExecution extends Execution {
+        private final AvailableToolChains.ToolChainCandidate toolChain;
+        private final boolean enabled;
+
+        public ToolChainExecution(AvailableToolChains.ToolChainCandidate toolChain, boolean enabled) {
+            this.toolChain = toolChain;
+            this.enabled = enabled;
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return toolChain.getDisplayName();
+        }
+
+        @Override
+        protected boolean isTestEnabled(TestDetails testDetails) {
+            if (enabled) {
+                RequiresInstalledToolChain toolChainRestriction = testDetails.getAnnotation(RequiresInstalledToolChain.class);
+                return toolChainRestriction == null
+                        || toolChain.meets(toolChainRestriction.value());
+            }
+            return false;
+        }
+
+        @Override
+        protected void assertCanExecute() {
+            assert toolChain.isAvailable() : String.format("Tool chain %s not available", toolChain.getDisplayName());
+        }
+
+        @Override
+        protected void before() {
+            System.out.println(String.format("Using tool chain %s", toolChain.getDisplayName()));
+            AbstractInstalledToolChainIntegrationSpec.setToolChain((AvailableToolChains.InstalledToolChain) toolChain);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppPluginIntegrationTest.groovy
new file mode 100644
index 0000000..3758071
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppPluginIntegrationTest.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class CppPluginIntegrationTest extends WellBehavedPluginTest {
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/MixedObjectiveCIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/MixedObjectiveCIntegrationTest.groovy
new file mode 100644
index 0000000..5d84e68
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/MixedObjectiveCIntegrationTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec
+
+import org.gradle.nativebinaries.language.cpp.AbstractLanguageIntegrationTest
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.MixedObjectiveCHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+//TODO find a better name
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class MixedObjectiveCIntegrationTest extends AbstractLanguageIntegrationTest{
+
+    @Override
+    HelloWorldApp getHelloWorldApp() {
+        return new MixedObjectiveCHelloWorldApp()
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy
new file mode 100644
index 0000000..6f986dd
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec
+
+import org.gradle.internal.hash.HashUtil
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.AbstractLanguageIncrementalBuildIntegrationTest
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Ignore
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class ObjectiveCLanguageIncrementalBuildIntegrationTest extends AbstractLanguageIncrementalBuildIntegrationTest{
+
+    def setupSpec(){
+        multiPlatformsAvailable = OperatingSystem.current().isMacOsX();
+    }
+
+    @Ignore("Demos a problem with clang on ubuntu creating randomly differerent object files")
+    def "generates always exactly same object file"() {
+        setup:
+        def recordings = []
+        def invocation =  10
+        when:
+        invocation.times{
+            run "cleanCompileHelloSharedLibraryHello$sourceType", "compileHelloSharedLibraryHello$sourceType"
+            def oldHash= HashUtil.sha1(objectFileFor(librarySourceFiles[0], "build/objectFiles/helloSharedLibrary/hello$sourceType")).asCompactString()
+
+            //to ensure it's not a timestamp issue
+            sleep(1000)
+            run "cleanCompileHelloSharedLibraryHello$sourceType", "compileHelloSharedLibraryHello$sourceType"
+            def newHash = HashUtil.sha1(objectFileFor(librarySourceFiles[0], "build/objectFiles/helloSharedLibrary/hello$sourceType")).asCompactString()
+            recordings << (oldHash == newHash)
+        }
+        then:
+        recordings.findAll{ it }.size() != 0 // not everytime the .o file differs -> not a timestamp issue
+        recordings.findAll{ it }.size() == invocation
+    }
+
+    def "recompiles binary when imported header file changes"() {
+        sourceFile.text = sourceFile.text.replaceFirst('#include "hello.h"', "#import \"hello.h\"")
+
+        given:
+        run "installMainExecutable"
+
+
+        when:
+        headerFile << """
+            int unused();
+"""
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped libraryCompileTask
+        executedAndNotSkipped mainCompileTask
+
+        if(objectiveCWithAslr()){
+            executed ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            executed ":linkMainExecutable", ":mainExecutable"
+        } else {
+            skipped ":linkHelloSharedLibrary", ":helloSharedLibrary"
+            skipped ":linkMainExecutable", ":mainExecutable"
+        }
+    }
+
+    @Override
+    IncrementalHelloWorldApp getHelloWorldApp() {
+        return new ObjectiveCHelloWorldApp()
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIncrementalCompileIntegrationTest.groovy
new file mode 100644
index 0000000..cea1480
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIncrementalCompileIntegrationTest.groovy
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec
+
+import org.gradle.nativebinaries.language.cpp.AbstractLanguageIncrementalCompileIntegrationTest
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class ObjectiveCLanguageIncrementalCompileIntegrationTest extends AbstractLanguageIncrementalCompileIntegrationTest {
+    @Override
+    IncrementalHelloWorldApp getHelloWorldApp() {
+        return new ObjectiveCHelloWorldApp()
+    }
+
+    def "recompiles only source file that imported changed header file"() {
+        given:
+        sourceFile << """
+            #import "${otherHeaderFile.name}"
+"""
+        and:
+        initialCompile()
+
+        when:
+        otherHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+    def "source is always recompiled if it imported header via macro"() {
+        given:
+        sourceFile << """
+            #define MY_HEADER "${otherHeaderFile.name}"
+            #import MY_HEADER
+"""
+
+        and:
+        initialCompile()
+
+        when:
+        otherHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+
+        // TODO:DAZ Remove this behaviour
+        when: "Header that is NOT included is changed"
+        file("src/main/headers/notIncluded.h") << """
+            // Dummy header file
+"""
+        and:
+        run "mainExecutable"
+
+        then: "Source is still recompiled"
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+
+    def "recompiles source file when transitively imported header file is changed"() {
+        given:
+        def transitiveHeaderFile = file("src/main/headers/transitive.h") << """
+           // Dummy header file
+"""
+        otherHeaderFile << """
+            #import "${transitiveHeaderFile.name}"
+"""
+        sourceFile << """
+            #import "${otherHeaderFile.name}"
+"""
+
+        and:
+        initialCompile()
+
+        when:
+        transitiveHeaderFile << """
+            // Some extra content
+"""
+        and:
+        run "mainExecutable"
+
+        then:
+        executedAndNotSkipped compileTask
+
+        and:
+        recompiled sourceFile
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIntegrationTest.groovy
new file mode 100644
index 0000000..ad4eb51
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCLanguageIntegrationTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.objectivec
+
+import org.gradle.nativebinaries.language.cpp.AbstractLanguageIntegrationTest
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class ObjectiveCLanguageIntegrationTest extends AbstractLanguageIntegrationTest{
+
+    @Override
+    HelloWorldApp getHelloWorldApp() {
+        return new ObjectiveCHelloWorldApp()
+    }
+
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy
new file mode 100644
index 0000000..108d6d6
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCHelloWorldApp
+
+import static org.hamcrest.CoreMatchers.containsString
+
+ at RequiresInstalledToolChain(ToolChainRequirement.VisualCpp)
+class ObjectiveCUnsupportedIntegrationTest extends AbstractInstalledToolChainIntegrationSpec{
+
+    def helloWorldApp = new ObjectiveCHelloWorldApp();
+
+    def "setup"() {
+        buildFile << helloWorldApp.pluginScript
+        buildFile << helloWorldApp.extraConfiguration
+    }
+
+    def "fails with decent error message with visual studio toolchain"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        fails "compileMainExecutableMainObjc"
+
+        then:
+        failure.assertThatCause(containsString("Objective-C is not available on the Visual C++ toolchain"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCPluginIntegrationTest.groovy
new file mode 100644
index 0000000..59c1c09
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCPluginIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class ObjectiveCPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getPluginId() {
+        "objective-c"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIncrementalBuildIntegrationTest.groovy
new file mode 100644
index 0000000..6fd838e
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCppHelloWorldApp
+import org.gradle.nativebinaries.language.objectivec.ObjectiveCLanguageIncrementalBuildIntegrationTest
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class ObjectiveCppLanguageIncrementalBuildIntegrationTest  extends ObjectiveCLanguageIncrementalBuildIntegrationTest{
+    @Override
+    IncrementalHelloWorldApp getHelloWorldApp() {
+        return new ObjectiveCppHelloWorldApp()
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIncrementalCompileIntegrationTest.groovy
new file mode 100644
index 0000000..53c2228
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIncrementalCompileIntegrationTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCppHelloWorldApp
+import org.gradle.nativebinaries.language.objectivec.ObjectiveCLanguageIncrementalCompileIntegrationTest
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class ObjectiveCppLanguageIncrementalCompileIntegrationTest extends ObjectiveCLanguageIncrementalCompileIntegrationTest {
+    @Override
+    IncrementalHelloWorldApp getHelloWorldApp() {
+        return new ObjectiveCppHelloWorldApp()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIntegrationTest.groovy
new file mode 100644
index 0000000..98fdf85
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppLanguageIntegrationTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp
+
+import org.gradle.nativebinaries.language.cpp.AbstractLanguageIntegrationTest
+import org.gradle.nativebinaries.language.cpp.fixtures.app.HelloWorldApp
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCppHelloWorldApp
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class ObjectiveCppLanguageIntegrationTest extends AbstractLanguageIntegrationTest {
+
+    @Override
+    HelloWorldApp getHelloWorldApp() {
+        return new ObjectiveCppHelloWorldApp()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy
new file mode 100644
index 0000000..440927c
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativebinaries.language.cpp.fixtures.RequiresInstalledToolChain
+import org.gradle.nativebinaries.language.cpp.fixtures.ToolChainRequirement
+import org.gradle.nativebinaries.language.cpp.fixtures.app.ObjectiveCppHelloWorldApp
+
+import static org.hamcrest.CoreMatchers.containsString
+
+
+ at RequiresInstalledToolChain(ToolChainRequirement.VisualCpp)
+class ObjectiveCppUnsupportedIntegrationTest extends AbstractInstalledToolChainIntegrationSpec{
+
+    def helloWorldApp = new ObjectiveCppHelloWorldApp();
+
+    def "setup"() {
+        buildFile << helloWorldApp.pluginScript
+        buildFile << helloWorldApp.extraConfiguration
+    }
+
+    def "fails with decent error message with visual studio toolchain"() {
+        given:
+        buildFile << """
+            executables {
+                main {}
+            }
+        """
+
+        and:
+        helloWorldApp.writeSources(file("src/main"))
+
+        when:
+        fails "compileMainExecutableMainObjcpp"
+
+        then:
+        failure.assertThatCause(containsString("Objective-C++ is not available on the Visual C++ toolchain"))
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppPluginIntegrationTest.groovy
new file mode 100644
index 0000000..cbbe5f8
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppPluginIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class ObjectiveCppPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getPluginId() {
+        "objective-cpp"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesPluginIntegrationTest.groovy
new file mode 100644
index 0000000..5bc3a03
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesPluginIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.rc.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class WindowsResourcesPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getPluginId() {
+        "windows-resources"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPluginIntegrationTest.groovy
new file mode 100644
index 0000000..58fd599
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPluginIntegrationTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class NativeBinariesPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getPluginId() {
+        "native-binaries"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/test/cunit/plugins/CUnitPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/test/cunit/plugins/CUnitPluginIntegrationTest.groovy
new file mode 100644
index 0000000..cc47aa7
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/nativebinaries/test/cunit/plugins/CUnitPluginIntegrationTest.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.test.cunit.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class CUnitPluginIntegrationTest extends WellBehavedPluginTest {
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
deleted file mode 100755
index 4df0c8d..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
+++ /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.plugins.cpp
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.test.fixtures.file.TestFile
-import org.junit.runner.RunWith
-
- at RunWith(CppIntegrationTestRunner)
-abstract class AbstractBinariesIntegrationSpec extends AbstractIntegrationSpec {
-    def TestFile executable(Object path) {
-        return file(OperatingSystem.current().getExecutableName(path.toString()))
-    }
-
-    def TestFile sharedLibrary(Object path) {
-        return file(OperatingSystem.current().getSharedLibraryName(path.toString()))
-    }
-}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
deleted file mode 100755
index bfe89e7..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
+++ /dev/null
@@ -1,171 +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.cpp;
-
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.plugins.cpp.gpp.internal.version.GppVersionDeterminer;
-
-import java.io.File;
-import java.util.*;
-
-public class AvailableCompilers {
-    static List<CompilerCandidate> getCompilers() {
-        List<CompilerCandidate> compilers = new ArrayList<CompilerCandidate>();
-        if (OperatingSystem.current().isWindows()) {
-            compilers.add(findVisualCpp());
-            compilers.add(findMinGW());
-        } else {
-            compilers.add(findGpp("3", "/opt/gcc/3.4.6/g++"));
-            compilers.add(findGpp("4", null));
-        }
-        return compilers;
-    }
-
-    static private CompilerCandidate findVisualCpp() {
-        // Search first in path, then in the standard installation locations
-        File compilerExe = OperatingSystem.current().findInPath("cl.exe");
-        if (compilerExe != null) {
-            return new InstalledCompiler("visual c++");
-        }
-
-        compilerExe = new File("C:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/bin/cl.exe");
-        if (compilerExe.isFile()) {
-            File binDir = compilerExe.getParentFile();
-            File vcDir = binDir.getParentFile();
-            File baseDir = vcDir.getParentFile();
-            File sdkDir = new File(baseDir.getParentFile(), "Microsoft SDKs/Windows/v7.0A");
-            return new InstalledCompiler("visual c++",
-                    new File(baseDir, "Common7/IDE"), 
-                    binDir, 
-                    new File(baseDir, "Common7/Tools"), 
-                    new File(vcDir, "VCPackages"),
-                    new File(sdkDir, "Bin"))
-                    .envVar("INCLUDE", new File(vcDir, "include").getAbsolutePath())
-                    .envVar("LIB", new File(vcDir, "lib").getAbsolutePath() + File.pathSeparator + new File(sdkDir, "lib").getAbsolutePath());
-        }
-        
-        return new UnavailableCompiler("visual c++");
-    }
-
-    static private CompilerCandidate findMinGW() {
-        // Search in the standard installation locations (doesn't yet work with cygwin g++ in path)
-        File compilerExe = new File("C:/MinGW/bin/g++.exe");
-        if (compilerExe.isFile()) {
-            return new InstalledCompiler("mingw", compilerExe.getParentFile());
-        }
-
-        return new UnavailableCompiler("mingw");
-    }
-
-    static private CompilerCandidate findGpp(String versionPrefix, String hardcodedFallback) {
-        String name = String.format("g++ (%s)", versionPrefix);
-        GppVersionDeterminer versionDeterminer = new GppVersionDeterminer();
-        for (File candidate : OperatingSystem.current().findAllInPath("g++")) {
-            if (versionDeterminer.transform(candidate).startsWith(versionPrefix)) {
-                return new InstalledCompiler(name, candidate.getParentFile());
-            }
-        }
-
-        if (hardcodedFallback != null) {
-            File fallback = new File(hardcodedFallback);
-            if (fallback.isFile()) {
-                return new InstalledCompiler(name, fallback.getParentFile());
-            }
-        }
-
-        return new UnavailableCompiler(name);
-    }
-
-    public static abstract class CompilerCandidate {
-        @Override
-        public String toString() {
-            return getDisplayName();
-        }
-
-        public abstract String getDisplayName();
-        
-        public abstract boolean isAvailable();
-
-        public abstract List<File> getPathEntries();
-
-        public abstract Map<String, String> getEnvironmentVars();
-    }
-    
-    public static class InstalledCompiler extends CompilerCandidate {
-        private final List<File> pathEntries;
-        private final Map<String, String> environmentVars = new HashMap<String, String>();
-        private final String name;
-
-        public InstalledCompiler(String name, File... pathEntries) {
-            this.name = name;
-            this.pathEntries = Arrays.asList(pathEntries);
-        }
-
-        InstalledCompiler envVar(String key, String value) {
-            environmentVars.put(key, value);
-            return this;
-        }
-
-        @Override
-        public String getDisplayName() {
-            return name;
-        }
-
-        @Override
-        public boolean isAvailable() {
-            return true;
-        }
-
-        @Override
-        public List<File> getPathEntries() {
-            return pathEntries;
-        }
-
-        @Override
-        public Map<String, String> getEnvironmentVars() {
-            return environmentVars;
-        }
-    }
-
-    public static class UnavailableCompiler extends CompilerCandidate {
-        private final String name;
-
-        public UnavailableCompiler(String name) {
-            this.name = name;
-        }
-
-        @Override
-        public String getDisplayName() {
-            return name;
-        }
-
-        @Override
-        public boolean isAvailable() {
-            return false;
-        }
-
-        @Override
-        public List<File> getPathEntries() {
-            throw new UnsupportedOperationException("This compiler is not installed.");
-        }
-
-        @Override
-        public Map<String, String> getEnvironmentVars() {
-            throw new UnsupportedOperationException("This compiler is not installed.");
-        }
-    }
-}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppExePluginGoodBehaviourTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppExePluginGoodBehaviourTest.groovy
deleted file mode 100644
index 1401efb..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppExePluginGoodBehaviourTest.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.plugins.cpp
-
-import org.gradle.integtests.fixtures.WellBehavedPluginTest
-
-class CppExePluginGoodBehaviourTest extends WellBehavedPluginTest {
-    @Override
-    def String getPluginId() {
-        return "cpp-exe"
-    }
-}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
deleted file mode 100755
index ee6733d..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
+++ /dev/null
@@ -1,96 +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.cpp;
-
-import com.google.common.base.Joiner;
-import org.gradle.integtests.fixtures.AbstractMultiTestRunner;
-import org.gradle.internal.nativeplatform.*;
-import org.gradle.internal.nativeplatform.services.NativeServices;
-import org.gradle.internal.os.OperatingSystem;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-public class CppIntegrationTestRunner extends AbstractMultiTestRunner {
-    public CppIntegrationTestRunner(Class<?> target) {
-        super(target);
-    }
-
-    @Override
-    protected void createExecutions() {
-        List<AvailableCompilers.CompilerCandidate> compilers = AvailableCompilers.getCompilers();
-        for (AvailableCompilers.CompilerCandidate compiler : compilers) {
-            add(new CompilerExecution(compiler));
-        }
-    }
-
-    private static class CompilerExecution extends Execution {
-        private static final ProcessEnvironment PROCESS_ENVIRONMENT = NativeServices.getInstance().get(ProcessEnvironment.class);
-        private final AvailableCompilers.CompilerCandidate compiler;
-        private String originalPath;
-        private final String pathVarName;
-
-
-        public CompilerExecution(AvailableCompilers.CompilerCandidate compiler) {
-            this.compiler = compiler;
-            this.pathVarName = !OperatingSystem.current().isWindows() ? "Path" : "PATH";
-        }
-
-        @Override
-        protected boolean isEnabled() {
-            return compiler.isAvailable() && canDoNecessaryEnvironmentManipulation();
-        }
-
-        private boolean canDoNecessaryEnvironmentManipulation() {
-            return (compiler.getEnvironmentVars().isEmpty() && compiler.getPathEntries().isEmpty())
-                    || PROCESS_ENVIRONMENT.maybeSetEnvironmentVariable(pathVarName, System.getenv(pathVarName));
-        }
-
-        @Override
-        protected String getDisplayName() {
-            return compiler.getDisplayName();
-        }
-
-        @Override
-        protected void before() {
-            System.out.println(String.format("Using compiler %s", compiler.getDisplayName()));
-
-            String compilerPath = Joiner.on(File.pathSeparator).join(compiler.getPathEntries());
-
-
-            if (compilerPath.length() > 0) {
-                originalPath = System.getenv(pathVarName);
-                String path = compilerPath + File.pathSeparator + originalPath;
-                System.out.println(String.format("Using path %s", path));
-                PROCESS_ENVIRONMENT.setEnvironmentVariable(pathVarName, path);
-            }
-
-            for (Map.Entry<String, String> entry : compiler.getEnvironmentVars().entrySet()) {
-                System.out.println(String.format("Using environment var %s -> %s", entry.getKey(), entry.getValue()));
-                PROCESS_ENVIRONMENT.setEnvironmentVariable(entry.getKey(), entry.getValue());
-            }
-        }
-
-        @Override
-        protected void after() {
-            if (originalPath != null) {
-                PROCESS_ENVIRONMENT.setEnvironmentVariable(pathVarName, originalPath);
-            }
-        }
-    }
-}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppLibPluginGoodBehaviourTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppLibPluginGoodBehaviourTest.groovy
deleted file mode 100644
index 134b5d5..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppLibPluginGoodBehaviourTest.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.plugins.cpp
-
-import org.gradle.integtests.fixtures.WellBehavedPluginTest
-
-class CppLibPluginGoodBehaviourTest extends WellBehavedPluginTest {
-    @Override
-    def String getPluginId() {
-        return "cpp-lib"
-    }
-}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppPluginIntegrationTest.groovy
deleted file mode 100755
index a4a3f22..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppPluginIntegrationTest.groovy
+++ /dev/null
@@ -1,220 +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.cpp
-
-import static org.gradle.util.TextUtil.escapeString
-
-class CppPluginIntegrationTest extends AbstractBinariesIntegrationSpec {
-
-    static final HELLO_WORLD = "Hello, World!"
-
-    def "build and execute simple cpp program"() {
-        given:
-        buildFile << """
-            apply plugin: "cpp-exe"
-        """
-        settingsFile << "rootProject.name = 'test'"
-
-        and:
-        file("src", "main", "cpp", "helloworld.cpp") << """
-            #include <iostream>
-
-            int main () {
-              std::cout << "${escapeString(HELLO_WORLD)}";
-              return 0;
-            }
-        """
-
-        when:
-        run "compileMain"
-
-        then:
-        def executable = executable("build/binaries/test")
-        executable.isFile()
-        executable.exec().out == HELLO_WORLD
-    }
-
-    def "build simple cpp library"() {
-        given:
-        buildFile << """
-            apply plugin: "cpp-lib"
-        """
-        settingsFile << "rootProject.name = 'test'"
-
-        and:
-        file("src", "main", "cpp", "helloworld.cpp") << """
-            #include <iostream>
-            #ifdef _WIN32
-            #define DLL_FUNC __declspec(dllexport)
-            #else
-            #define DLL_FUNC
-            #endif
-
-            int DLL_FUNC main () {
-              std::cout << "${escapeString(HELLO_WORLD)}";
-              return 0;
-            }
-        """
-
-        when:
-        run "compileMain"
-
-        then:
-        sharedLibrary("build/binaries/test").isFile()
-    }
-
-    def "build fails when compilation fails"() {
-        given:
-        buildFile << """
-            apply plugin: "cpp-exe"
-        """
-        settingsFile << "rootProject.name = 'test'"
-
-        and:
-        file("src", "main", "cpp", "helloworld.cpp") << """
-            #include <iostream>
-
-            'broken
-        """
-
-        expect:
-        fails "compileMain"
-    }
-
-    def "build fails when link fails"() {
-        given:
-        buildFile << """
-            apply plugin: "cpp-exe"
-        """
-        settingsFile << "rootProject.name = 'test'"
-
-        and:
-        file("src", "main", "cpp", "helloworld.cpp") << """
-            int thing() { return 0; }
-        """
-
-        expect:
-        fails "compileMain"
-    }
-
-    def "build and execute program from multiple source files"() {
-        given:
-        buildFile << """
-            apply plugin: "cpp-exe"
-        """
-        settingsFile << "rootProject.name = 'test'"
-
-        and:
-        file("src", "main", "cpp", "hello.cpp") << """
-            #include <iostream>
-
-            void hello () {
-              std::cout << "${escapeString(HELLO_WORLD)}";
-            }
-        """
-
-        and:
-        file("src", "main", "headers", "hello.h") << """
-            void hello();
-        """
-
-        and:
-        file("src", "main", "cpp", "main.cpp") << """
-            #include "hello.h"
-
-            int main () {
-              hello();
-              return 0;
-            }
-        """
-
-        when:
-        run "compileMain"
-
-        then:
-        executable("build/binaries/test").exec().out == HELLO_WORLD
-    }
-
-    def "build, install and execute program with shared library"() {
-        given:
-        buildFile << """
-            apply plugin: "cpp-exe"
-
-            cpp {
-                sourceSets {
-                    hello {}
-                }
-            }
-            libraries {
-                hello {
-                    sourceSets << cpp.sourceSets.hello
-                }
-            }
-            cpp.sourceSets.main.libs << libraries.hello
-        """
-        settingsFile << "rootProject.name = 'test'"
-
-        and:
-        file("src/hello/cpp/hello.cpp") << """
-            #include <iostream>
-            #ifdef _WIN32
-            #define DLL_FUNC __declspec(dllexport)
-            #else
-            #define DLL_FUNC
-            #endif
-
-            void DLL_FUNC hello(const char* str) {
-              std::cout << str;
-            }
-        """
-
-        and:
-        file("src/hello/headers/hello.h") << """
-            void hello(const char* str);
-        """
-
-        and:
-        file("src/main/cpp/main.cpp") << """
-            #include <iostream>
-            #include "hello.h"
-
-            int main (int argc, char** argv) {
-              hello("${escapeString(HELLO_WORLD)}");
-              for ( int i = 1; i < argc; i++ ) {
-                std::cout << "[" << argv[i] << "]";
-              }
-              return 0;
-            }
-        """
-
-        when:
-        run "installMain"
-
-        then:
-        sharedLibrary("build/binaries/hello").isFile()
-        executable("build/binaries/test").isFile()
-
-        executable("build/install/main/test").exec().out == HELLO_WORLD
-        executable("build/install/main/test").exec("a", "1 2 3").out.contains("[a][1 2 3]")
-
-        // Ensure installed binary is not dependent on the libraries in their original locations
-        when:
-        file("build/binaries").deleteDir()
-
-        then:
-        executable("build/install/main/test").exec().out == HELLO_WORLD
-    }
-}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
deleted file mode 100755
index 5531fe7..0000000
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
+++ /dev/null
@@ -1,85 +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.cpp
-
-import org.gradle.integtests.fixtures.Sample
-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(temporaryFolder, 'cpp/exewithlib')
-    @Rule public final Sample dependencies = new Sample(temporaryFolder, 'cpp/dependencies')
-    @Rule public final Sample exe = new Sample(temporaryFolder, 'cpp/exe')
-
-    def "exe with lib"() {
-        given:
-        sample exewithlib
-
-        when:
-        run "installMain"
-
-        then:
-        ":exe:compileMain" in executedTasks
-
-        and:
-        sharedLibrary("cpp/exewithlib/lib/build/binaries/lib").isFile()
-        executable("cpp/exewithlib/exe/build/binaries/exe").isFile()
-        executable("cpp/exewithlib/exe/build/install/main/exe").exec().out == toPlatformLineSeparators("Hello, World!\n")
-    }
-
-    // Does not work on windows, due to GRADLE-2118
-    @Requires(TestPrecondition.NOT_WINDOWS)
-    def "dependencies"() {
-        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:
-        sample dependencies
-        run ":exe:uploadArchives"
-        
-        then:
-        ":exe:mainExtractHeaders" in nonSkippedTasks
-        ":exe:compileMain" in nonSkippedTasks
-        
-        and:
-        executable("cpp/dependencies/exe/build/binaries/exe").isFile()
-        file("cpp/dependencies/exe/build/repo/dependencies/exe/1.0/exe-1.0.exe").exists()
-    }
-    
-    def "exe"() {
-        given:
-        sample exe
-        
-        when:
-        run "installMain"
-        
-        then:
-        ":compileMain" in nonSkippedTasks
-        
-        and:
-        executable("cpp/exe/build/binaries/exe").exec().out == toPlatformLineSeparators("Hello, World!\n")
-        executable("cpp/exe/build/install/main/exe").exec().out == toPlatformLineSeparators("Hello, World!\n")
-    }
-    
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Automated.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Automated.h
new file mode 100644
index 0000000..2acc694
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Automated.h
@@ -0,0 +1,90 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Automated Interface (generates HTML Report Files).
+ *
+ *  Feb 2002      Initial implementation (AK)
+ *
+ *  13-Feb-2002   Single interface to automated_run_tests. (AK)
+ *
+ *  20-Jul-2004   New interface, doxygen comments. (JDS)
+ */
+
+/** @file
+ * Automated testing interface with xml output (user interface).
+ */
+/** @addtogroup Automated
+ * @{
+ */
+                                       
+#ifndef CUNIT_AUTOMATED_H_SEEN
+#define CUNIT_AUTOMATED_H_SEEN
+
+#include "CUnit.h"
+#include "TestDB.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CU_EXPORT void CU_automated_run_tests(void);
+/**< 
+ *  Runs CUnit tests using the automated interface.
+ *  This function sets appropriate callback functions, initializes the 
+ *  test output files, and calls the appropriate functions to list the 
+ *  tests and run them.  If an output file name root has not been 
+ *  specified using CU_set_output_filename(), a generic root will be 
+ *  applied.  It is an error to call this function before the CUnit
+ *  test registry has been initialized (check by assertion).
+ */
+
+CU_EXPORT CU_ErrorCode CU_list_tests_to_file(void);
+/**< 
+ *  Generates an xml file containing a list of all tests in all suites 
+ *  in the active registry.  The output file will be named according to 
+ *  the most recent call to CU_set_output_filename(), or a default if 
+ *  not previously set.
+ *
+ *  @return An error code indicating the error status.
+ */
+
+CU_EXPORT void CU_set_output_filename(const char* szFilenameRoot);
+/**< 
+ *  Sets the root file name for automated test output files.
+ *  The strings "-Listing.xml" and "-Results.xml" are appended to the 
+ *  specified root to generate the filenames.  If szFilenameRoot is 
+ *  empty, the default root ("CUnitAutomated") is used.
+ *
+ *  @param szFilenameRoot String containing root to use for file names.
+ */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+/** Deprecated (version 1). @deprecated Use CU_automated_run_tests(). */
+#define automated_run_tests() CU_automated_run_tests()
+/** Deprecated (version 1). @deprecated Use CU_set_output_filename(). */
+#define set_output_filename(x) CU_set_output_filename((x))
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_AUTOMATED_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Basic.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Basic.h
new file mode 100644
index 0000000..d8a638d
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Basic.h
@@ -0,0 +1,113 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2004-2006  Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Interface for simple test runner.
+ *
+ *  11-Aug-2004   Initial implementation of basic test runner interface. (JDS)
+ */
+
+/** @file
+ * Basic interface with output to stdout.
+ */
+/** @addtogroup Basic
+ * @{
+ */
+
+#ifndef CUNIT_BASIC_H_SEEN
+#define CUNIT_BASIC_H_SEEN
+
+#include "CUnit.h"
+#include "TestDB.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Run modes for the basic interface. */
+typedef enum {
+  CU_BRM_NORMAL = 0,  /**< Normal mode - failures and run summary are printed [default]. */
+  CU_BRM_SILENT,      /**< Silent mode - no output is printed except framework error messages. */
+  CU_BRM_VERBOSE      /**< Verbose mode - maximum output of run details. */
+} CU_BasicRunMode;
+
+CU_EXPORT CU_ErrorCode CU_basic_run_tests(void);
+/**< 
+ *  Runs all registered CUnit tests using the basic interface.
+ *  The default CU_BasicRunMode is used unless it has been
+ *  previously changed using CU_basic_set_mode().  The CUnit test
+ *  registry must have been initialized before calling this function.
+ *
+ *  @return A CU_ErrorCode indicating the framework error condition, including
+ *          CUE_NOREGISTRY - Registry has not been initialized.
+ */
+
+CU_EXPORT CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite);
+/**< 
+ *  Runs all tests for a specific suite in the basic interface.
+ *  If pSuite is NULL, the function returns without taking any
+ *  action. The default CU_BasicRunMode is used unless it has
+ *  been changed using CU_basic_set_mode().
+ *
+ *  @param pSuite The CU_Suite to run.
+ *  @return A CU_ErrorCode indicating the framework error condition, including
+ *          CUE_NOSUITE - pSuite was NULL.
+ */
+
+CU_EXPORT CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest);
+/**<
+ *  Runs a single test in a specific suite in the basic interface.
+ *  If pSuite or pTest is NULL, the function returns without
+ *  taking any action.  The default CU_BasicRunMode is used unless
+ *  it has been changed using CU_basic_set_mode.
+ *
+ *  @param pSuite The CU_Suite holding the CU_Test to run.
+ *  @param pTest  The CU_Test to run.
+ *  @return A CU_ErrorCode indicating the framework error condition, including
+ *          CUE_NOSUITE - pSuite was NULL.
+ *          CUE_NOTEST  - pTest was NULL.
+ */
+
+CU_EXPORT void CU_basic_set_mode(CU_BasicRunMode mode);
+/**< Sets the run mode for the basic interface.
+ *  @param mode The new CU_BasicRunMode for subsequent test
+ *              runs using the basic interface.
+ */
+
+CU_EXPORT CU_BasicRunMode CU_basic_get_mode(void);
+/**< Retrieves the current run mode for the basic interface.
+ *  @return The current CU_BasicRunMode setting for test
+ *              runs using the basic interface.
+ */
+
+CU_EXPORT void CU_basic_show_failures(CU_pFailureRecord pFailure);
+/**<
+ *  Prints a summary of run failures to stdout.
+ *  This is provided for user convenience upon request, and does 
+ *  not take into account the current run mode.  The failures are 
+ *  printed to stdout independent of the most recent run mode.
+ *
+ *  @param pFailure List of CU_pFailureRecord's to output.
+ */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_BASIC_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUError.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUError.h
new file mode 100644
index 0000000..c9943c1
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUError.h
@@ -0,0 +1,199 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains CUnit error codes which can be used externally.
+ *
+ *  Aug 2001      Initial implementation.  (AK)
+ *
+ *  02/Oct/2001   Added proper Eror Codes. (AK)
+ *
+ *  13-Oct-2001   Added Error Codes for Duplicate TestGroup and Test. (AK)
+ *
+ *  03-Aug-2004   Converted error code macros to an enum, doxygen comments, moved
+ *                error handing code here, changed file name from Errno.h, added
+ *                error codes for file open errors, added error action selection. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ */
+
+/** @file
+ *  Error handling functions (user interface).
+ *  CUnit uses a simple (and conventional) error handling strategy.
+ *  Functions that can generate errors set (and usually return) an
+ *  error code to indicate the run status.  The error code can be
+ *  inspected using the CU_get_error() function.  A descriptive
+ *  error message can be retrieved using CU_get_error_msg().
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_CUERROR_H_SEEN
+#define CUNIT_CUERROR_H_SEEN
+
+#include <errno.h>
+
+/*------------------------------------------------------------------------*/
+/** CUnit error codes.
+ *  If codes are added or removed, be sure to make a change to the
+ *  error messages in CUError.c/get_error_desc().
+ *  @see CU_set_error()
+ *  @see CU_get_error()
+ *  @see CU_get_error_msg()
+ */
+typedef enum {
+  /* basic errors */
+  CUE_SUCCESS           = 0,  /**< No error condition. */
+  CUE_NOMEMORY          = 1,  /**< Memory allocation failed. */
+
+  /* Test Registry Level Errors */
+  CUE_NOREGISTRY        = 10,  /**< Test registry not initialized. */
+  CUE_REGISTRY_EXISTS   = 11,  /**< Attempt to CU_set_registry() without CU_cleanup_registry(). */
+
+  /* Test Suite Level Errors */
+  CUE_NOSUITE           = 20,  /**< A required CU_pSuite pointer was NULL. */
+  CUE_NO_SUITENAME      = 21,  /**< Required CU_Suite name not provided. */
+  CUE_SINIT_FAILED      = 22,  /**< Suite initialization failed. */
+  CUE_SCLEAN_FAILED     = 23,  /**< Suite cleanup failed. */
+  CUE_DUP_SUITE         = 24,  /**< Duplicate suite name not allowed. */
+  CUE_SUITE_INACTIVE    = 25,  /**< Test run initiated for an inactive suite. */
+
+  /* Test Case Level Errors */
+  CUE_NOTEST            = 30,  /**< A required CU_pTest or CU_TestFunc pointer was NULL. */
+  CUE_NO_TESTNAME       = 31,  /**< Required CU_Test name not provided. */
+  CUE_DUP_TEST          = 32,  /**< Duplicate test case name not allowed. */
+  CUE_TEST_NOT_IN_SUITE = 33,  /**< Test not registered in specified suite. */
+  CUE_TEST_INACTIVE     = 34,  /**< Test run initiated for an inactive test. */
+
+  /* File handling errors */
+  CUE_FOPEN_FAILED      = 40,  /**< An error occurred opening a file. */
+  CUE_FCLOSE_FAILED     = 41,  /**< An error occurred closing a file. */
+  CUE_BAD_FILENAME      = 42,  /**< A bad filename was requested (NULL, empty, nonexistent, etc.). */
+  CUE_WRITE_ERROR       = 43   /**< An error occurred during a write to a file. */
+} CU_ErrorCode;
+
+/*------------------------------------------------------------------------*/
+/** CUnit error action codes.
+ *  These are used to set the action desired when an error
+ *  condition is detected in the CUnit framework.
+ *  @see CU_set_error_action()
+ *  @see CU_get_error_action()
+ */
+typedef enum CU_ErrorAction {
+  CUEA_IGNORE,    /**< Runs should be continued when an error condition occurs (if possible). */
+  CUEA_FAIL,      /**< Runs should be stopped when an error condition occurs. */
+  CUEA_ABORT      /**< The application should exit() when an error conditions occurs. */
+} CU_ErrorAction;
+
+/* Error handling & reporting functions. */
+
+#include "CUnit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CU_EXPORT CU_ErrorCode   CU_get_error(void);
+/**<
+ *  Retrieves the current CUnit framework error code.
+ *  CUnit implementation functions set the error code to indicate the
+ *  status of the most recent operation.  In general, the CUnit functions
+ *  will clear the code to CUE_SUCCESS, then reset it to a specific error
+ *  code if an exception condition is encountered.  Some functions
+ *  return the code, others leave it to the user to inspect if desired.
+ *
+ *  @return The current error condition code.
+ *  @see CU_get_error_msg()
+ *  @see CU_ErrorCode
+ */
+
+CU_EXPORT const char*    CU_get_error_msg(void);
+/**<
+ *  Retrieves a message corresponding to the current framework error code.
+ *  CUnit implementation functions set the error code to indicate the
+ *  of the most recent operation.  In general, the CUnit functions will
+ *  clear the code to CUE_SUCCESS, then reset it to a specific error
+ *  code if an exception condition is encountered.  This function allows
+ *  the user to retrieve a descriptive error message corresponding to the
+ *  error code set by the last operation.
+ *
+ *  @return A message corresponding to the current error condition.
+ *  @see CU_get_error()
+ *  @see CU_ErrorCode
+ */
+
+CU_EXPORT void           CU_set_error_action(CU_ErrorAction action);
+/**<
+ *  Sets the action to take when a framework error condition occurs.
+ *  This function should be used to specify the action to take
+ *  when an error condition is encountered.  The default action is
+ *  CUEA_IGNORE, which results in errors being ignored and test runs
+ *  being continued (if possible).  A value of CUEA_FAIL causes test
+ *  runs to stop as soon as an error condition occurs, while
+ *  CU_ABORT causes the application to exit on any error.
+ *
+ *  @param action CU_ErrorAction indicating the new error action.
+ *  @see CU_get_error_action()
+ *  @see CU_set_error()
+ *  @see CU_ErrorAction
+ */
+
+CU_EXPORT CU_ErrorAction CU_get_error_action(void);
+/**<
+ *  Retrieves the current framework error action code.
+ *
+ *  @return The current error action code.
+ *  @see CU_set_error_action()
+ *  @see CU_set_error()
+ *  @see CU_ErrorAction
+ */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_CUError(void);
+#endif
+
+/* Internal function - users should not generally call this function */
+CU_EXPORT void CU_set_error(CU_ErrorCode error);
+/**<
+ *  Sets the CUnit framework error code.
+ *  This function is used internally by CUnit implementation functions
+ *  when an error condition occurs within the framework.  It should
+ *  not generally be called by user code.  NOTE that if the current
+ *  error action is CUEA_ABORT, then calling this function will
+ *  result in exit() being called for the current application.
+ *
+ *  @param error CU_ErrorCode indicating the current error condition.
+ *  @see CU_get_error()
+ *  @see CU_get_error_msg()
+ *  @see CU_ErrorCode
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+/** Deprecated (version 1). @deprecated Use CU_get_error_msg(). */
+#define get_error() CU_get_error_msg()
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#endif  /*  CUNIT_CUERROR_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUnit.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUnit.h
new file mode 100644
index 0000000..9592eda
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUnit.h
@@ -0,0 +1,383 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  ASSERT Macro definitions and general CUnit configuration definitions.
+ *
+ *  09/Aug/2001   ASSERT definitions. (AK)
+ *
+ *  12/Mar/2003   New Assert definitions. (AK)
+ *
+ *  27/Jul/2003   Modified ASSERT_XXX Macro definitions. (AK)
+ *
+ *  15-Jul-2004   New interface, changed action on assert failure to not
+ *                return, provided _FATAL versions of assertions to return
+ *                from test function on failure. (JDS)
+ *
+ *  01-Sep-2004   Modified assertions for setjmp/longjmp mechanism of 
+ *                aborting test runs, added CU_FAIL and CU_PASS macros. (JDS)
+ *
+ *  07-May-2005   Added CU_ prefix to remaining CUnit defines (BOOL, TRUE, 
+ *                FALSE, MAX_...).  Added CU_UNREFERENCED_PARAMETER() define. (JDS)
+ */
+
+/** @file
+ * Basic CUnit include file for user and system code.
+ * Defines macros for assertions for use in user test cases.
+ * Basic system macro definitions also appear here.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_CUNIT_H_SEEN
+#define CUNIT_CUNIT_H_SEEN
+
+#include <string.h>
+#include <math.h>
+
+/** CUnit version number. */
+#define CU_VERSION "2.1-2"
+
+/*  Max string lengths for names (includes terminating NULL. */
+/** Maximum length of a test name string. */
+#define CU_MAX_TEST_NAME_LENGTH 256
+/** Maximim length of a suite name string. */
+#define CU_MAX_SUITE_NAME_LENGTH 256
+
+/* Global type Definitions to be used for boolean operators. */
+#ifndef CU_BOOL
+  /** Boolean type for CUnit use. */
+  #define CU_BOOL int
+#endif
+
+#ifndef CU_TRUE
+  /** Boolean TRUE for CUnit use. */
+  #define CU_TRUE 1
+#endif
+
+#ifndef CU_FALSE
+  /** Boolean FALSE for CUnit use. */
+  #define CU_FALSE 0
+#endif
+
+#ifndef CU_UNREFERENCED_PARAMETER
+  /** Consistent approach to referencing unused parameters. */
+  #define CU_UNREFERENCED_PARAMETER(x) (void)x
+#endif
+
+#ifndef CU_MAX
+#  define CU_MAX(a,b) (((a) >= (b)) ? (a) : (b))
+#endif
+
+#ifndef CU_MIN
+#  define CU_MIN(a,b) (((a) >= (b)) ? (b) : (a))
+#endif
+
+#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__)
+#  ifdef CU_DLL
+#    ifdef CU_BUILD_DLL
+#      define CU_EXPORT __declspec(dllexport)
+#    else
+#      define CU_EXPORT __declspec(dllimport)
+#    endif
+#  else
+#    define CU_EXPORT
+#  endif
+#  ifdef _MSC_VER
+#    define snprintf _snprintf
+#  endif
+#else
+#  define CU_EXPORT
+#endif  /* WIN32 */
+
+#include "CUError.h"
+#include "TestDB.h"   /* not needed here - included for user convenience */
+#include "TestRun.h"  /* not needed here - include (after BOOL define) for user convenience */
+
+/** Record a pass condition without performing a logical test. */
+#define CU_PASS(msg) \
+  { CU_assertImplementation(CU_TRUE, __LINE__, ("CU_PASS(" #msg ")"), __FILE__, "", CU_FALSE); }
+
+/** Simple assertion.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_FALSE); }
+
+/** Simple assertion.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_FATAL(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_TRUE); }
+
+/** Simple assertion.
+ *  Reports failure with no other action.
+ */
+#define CU_TEST(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_FALSE); }
+
+/** Simple assertion.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_TEST_FATAL(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_TRUE); }
+
+/** Record a failure without performing a logical test. */
+#define CU_FAIL(msg) \
+  { CU_assertImplementation(CU_FALSE, __LINE__, ("CU_FAIL(" #msg ")"), __FILE__, "", CU_FALSE); }
+
+/** Record a failure without performing a logical test, and abort test. */
+#define CU_FAIL_FATAL(msg) \
+  { CU_assertImplementation(CU_FALSE, __LINE__, ("CU_FAIL_FATAL(" #msg ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that value is CU_TRUE.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_TRUE(value) \
+  { CU_assertImplementation((value), __LINE__, ("CU_ASSERT_TRUE(" #value ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that value is CU_TRUE.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_TRUE_FATAL(value) \
+  { CU_assertImplementation((value), __LINE__, ("CU_ASSERT_TRUE_FATAL(" #value ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that value is CU_FALSE.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_FALSE(value) \
+  { CU_assertImplementation(!(value), __LINE__, ("CU_ASSERT_FALSE(" #value ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that value is CU_FALSE.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_FALSE_FATAL(value) \
+  { CU_assertImplementation(!(value), __LINE__, ("CU_ASSERT_FALSE_FATAL(" #value ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that actual == expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_EQUAL(actual, expected) \
+  { CU_assertImplementation(((actual) == (expected)), __LINE__, ("CU_ASSERT_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that actual == expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((actual) == (expected)), __LINE__, ("CU_ASSERT_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that actual != expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_NOT_EQUAL(actual, expected) \
+  { CU_assertImplementation(((actual) != (expected)), __LINE__, ("CU_ASSERT_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that actual != expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_NOT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((actual) != (expected)), __LINE__, ("CU_ASSERT_NOT_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointers actual == expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_EQUAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) == (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointers actual == expected.
+ * Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) == (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointers actual != expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_NOT_EQUAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) != (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointers actual != expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_NOT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) != (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_NOT_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointer value is NULL.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_NULL(value) \
+  { CU_assertImplementation((NULL == (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NULL(" #value")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointer value is NULL.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_NULL_FATAL(value) \
+  { CU_assertImplementation((NULL == (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NULL_FATAL(" #value")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointer value is not NULL.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_NOT_NULL(value) \
+  { CU_assertImplementation((NULL != (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NOT_NULL(" #value")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointer value is not NULL.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_NOT_NULL_FATAL(value) \
+  { CU_assertImplementation((NULL != (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NOT_NULL_FATAL(" #value")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual == expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_STRING_EQUAL(actual, expected) \
+  { CU_assertImplementation(!(strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_EQUAL(" #actual ","  #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual == expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_STRING_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(!(strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_EQUAL_FATAL(" #actual ","  #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual != expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_STRING_NOT_EQUAL(actual, expected) \
+  { CU_assertImplementation((strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_NOT_EQUAL(" #actual ","  #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual != expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_STRING_NOT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation((strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_NOT_EQUAL_FATAL(" #actual ","  #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual == expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_NSTRING_EQUAL(actual, expected, count) \
+  { CU_assertImplementation(!(strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual == expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_NSTRING_EQUAL_FATAL(actual, expected, count) \
+  { CU_assertImplementation(!(strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_EQUAL_FATAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual != expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_NSTRING_NOT_EQUAL(actual, expected, count) \
+  { CU_assertImplementation((strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_NOT_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual != expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(actual, expected, count) \
+  { CU_assertImplementation((strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that double actual == expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion passes.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_DOUBLE_EQUAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) <= fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that double actual == expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion passes.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_DOUBLE_EQUAL_FATAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) <= fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_EQUAL_FATAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that double actual != expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion fails.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) > fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_NOT_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that double actual != expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion fails.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) > fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_TRUE); }
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+
+#ifndef BOOL
+  /** Deprecated (version 2.0-2). @deprecated Use CU_BOOL. */
+  #define BOOL int
+#endif
+
+#ifndef TRUE
+  /** Deprecated (version 2.0-2). @deprecated Use CU_TRUE. */
+  #define TRUE 1
+#endif
+
+#ifndef FALSE
+  /** Deprecated (version 2.0-2). @deprecated Use CU_FALSE. */
+  #define FALSE	0
+#endif
+
+/** Deprecated (version 2.0-2). @deprecated Use CU_MAX_TEST_NAME_LENGTH. */
+#define MAX_TEST_NAME_LENGTH	256
+/** Deprecated (version 2.0-2). @deprecated Use CU_MAX_SUITE_NAME_LENGTH. */
+#define MAX_SUITE_NAME_LENGTH	256
+
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_FATAL. */
+#define ASSERT(value) { if (FALSE == (int)(value)) { CU_assertImplementation((BOOL)value, __LINE__, #value, __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_TRUE_FATAL. */
+#define ASSERT_TRUE(value) { if (FALSE == (value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_TRUE(" #value ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_FALSE_FATAL. */
+#define ASSERT_FALSE(value) { if (FALSE != (value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_FALSE(" #value ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_EQUAL_FATAL. */
+#define ASSERT_EQUAL(actual, expected) { if ((actual) != (expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_NOT_EQUAL_FATAL. */
+#define ASSERT_NOT_EQUAL(actual, expected) { if ((void*)(actual) == (void*)(expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_EQUAL_FATAL. */
+#define ASSERT_PTR_EQUAL(actual, expected) { if ((void*)(actual) != (void*)(expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_NOT_EQUAL_FATAL. */
+#define ASSERT_PTR_NOT_EQUAL(actual, expected) { if ((void*)(actual) == (void*)(expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_NULL_FATAL. */
+#define ASSERT_PTR_NULL(value)  { if (NULL != (void*)(value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_NULL(" #value")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_NOT_NULL_FATAL. */
+#define ASSERT_PTR_NOT_NULL(value) { if (NULL == (void*)(value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_NOT_NULL(" #value")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_STRING_EQUAL_FATAL. */
+#define ASSERT_STRING_EQUAL(actual, expected) { if (strcmp((const char*)actual, (const char*)expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_STRING_EQUAL(" #actual ","  #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_STRING_NOT_EQUAL_FATAL. */
+#define ASSERT_STRING_NOT_EQUAL(actual, expected) { if (!strcmp((const char*)actual, (const char*)expected)) { CU_assertImplementation(TRUE, __LINE__, ("ASSERT_STRING_NOT_EQUAL(" #actual ","  #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_NSTRING_EQUAL_FATAL. */
+#define ASSERT_NSTRING_EQUAL(actual, expected, count) { if (strncmp((const char*)actual, (const char*)expected, (size_t)count)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_NSTRING_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_NSTRING_NOT_EQUAL_FATAL. */
+#define ASSERT_NSTRING_NOT_EQUAL(actual, expected, count) { if (!strncmp((const char*)actual, (const char*)expected, (size_t)count)) { CU_assertImplementation(TRUE, __LINE__, ("ASSERT_NSTRING_NOT_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_DOUBLE_EQUAL_FATAL. */
+#define ASSERT_DOUBLE_EQUAL(actual, expected, granularity) { if ((fabs((double)actual - expected) > fabs((double)granularity))) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_DOUBLE_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL. */
+#define ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity) { if ((fabs((double)actual - expected) <= fabs((double)granularity))) { CU_assertImplementation(TRUE, __LINE__, ("ASSERT_DOUBLE_NOT_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", FALSE); return; }}
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#endif  /*  CUNIT_CUNIT_H_SEEN  */
+
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUnit_intl.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUnit_intl.h
new file mode 100644
index 0000000..813917b
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/CUnit_intl.h
@@ -0,0 +1,62 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2006  Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Internationalization support
+ *
+ *  05-May-2006   Initial implementation.  (JDS)
+ */
+
+/** @file
+ *  Internal CUnit header supporting internationalization of
+ *  CUnit framework & interfaces.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_CUNIT_INTL_H_SEEN
+#define CUNIT_CUNIT_INTL_H_SEEN
+
+/* activate these when source preparation is complete
+#include <libintl.h>
+#ifndef _
+#  define _(String) gettext (String)
+#endif
+#ifndef gettext_noop
+#  define gettext_noop(String) String
+#endif
+#ifndef N_
+#  define N_(String) gettext_noop (String)
+#endif
+*/
+
+/* deactivate these when source preparation is complete */
+#undef _
+#define _(String) (String)
+#undef N_
+#define N_(String) String
+#undef textdomain
+#define textdomain(Domain)
+#undef bindtextdomain
+#define bindtextdomain(Package, Directory)
+
+#endif  /*  CUNIT_CUNIT_INTL_H_SEEN  */
+
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Console.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Console.h
new file mode 100644
index 0000000..f5b3769
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Console.h
@@ -0,0 +1,60 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Interface for console Run tests.
+ *
+ *  Aug 2001      Initial implementation. (AK)
+ *
+ *  09/Aug/2001   Single interface to Console_run_tests. (AK)
+ *
+ *  20-Jul-2004   New interface, doxygen comments. (JDS)
+ */
+
+/** @file
+ * Console interface with interactive output (user interface).
+ */
+/** @addtogroup Console
+ * @{
+ */
+
+#ifndef CUNIT_CONSOLE_H_SEEN
+#define CUNIT_CONSOLE_H_SEEN
+
+#include "CUnit.h"
+#include "TestDB.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CU_EXPORT void CU_console_run_tests(void);
+/**< Run registered CUnit tests using the console interface. */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+/** Deprecated (version 1). @deprecated Use CU_console_run_tests(). */
+#define console_run_tests() CU_console_run_tests()
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_CONSOLE_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/MyMem.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/MyMem.h
new file mode 100644
index 0000000..88a7e62
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/MyMem.h
@@ -0,0 +1,104 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Memory Related Defines to use internal routines to detect Memory Leak
+ *  in Debug Versions
+ *
+ *  18/Jun/2002   Memory Debug Functions. (AK)
+ *
+ *  17-Jul-2004   New interface for global function names. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ */
+
+/** @file
+ *  Memory management functions (user interface).
+ *  Two versions of memory allocation/deallocation are available.
+ *  If compiled with MEMTRACE defined, CUnit keeps track of all
+ *  system allocations & deallocations.  The memory record can
+ *  then be reported using CU_CREATE_MEMORY_REPORT.  Otherwise,
+ *  standard system memory allocation is used without tracing.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_MYMEM_H_SEEN
+#define CUNIT_MYMEM_H_SEEN
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef MEMTRACE
+  void* CU_calloc(size_t nmemb, size_t size, unsigned int uiLine, const char* szFileName);
+  void* CU_malloc(size_t size, unsigned int uiLine, const char* szFileName);
+  void  CU_free(void *ptr, unsigned int uiLine, const char* szFileName);
+  void* CU_realloc(void *ptr, size_t size, unsigned int uiLine, const char* szFileName);
+  CU_EXPORT void CU_dump_memory_usage(const char*);
+
+  /** c-allocate with memory tracking. */
+  #define CU_CALLOC(x, y)         CU_calloc((x), (y), __LINE__, __FILE__)
+  /** m-allocate with memory tracking. */
+  #define CU_MALLOC(x)            CU_malloc((x), __LINE__, __FILE__)
+  /** Free with memory tracking. */
+  #define CU_FREE(x)              CU_free((x), __LINE__, __FILE__)
+  /** Reallocate with memory tracking. */
+  #define CU_REALLOC(x, y)        CU_realloc((x), (y), __LINE__, __FILE__)
+  /** Generate report on tracked memory. */
+  #define CU_CREATE_MEMORY_REPORT(x) CU_dump_memory_usage((x))
+  /** Generate report on tracked memory (old macro). */
+  #define CU_DUMP_MEMORY_USAGE(x) CU_dump_memory_usage((x))
+#else   /* MEMTRACE */
+  /** Standard calloc() if MEMTRACE not defined. */
+  #define CU_CALLOC(x, y)         calloc((x), (y))
+  /** Standard malloc() if MEMTRACE not defined. */
+  #define CU_MALLOC(x)            malloc((x))
+  /** Standard free() if MEMTRACE not defined. */
+  #define CU_FREE(x)              free((x))
+  /** Standard realloc() if MEMTRACE not defined. */
+  #define CU_REALLOC(x, y)        realloc((x), (y))
+  /** No-op if MEMTRACE not defined. */
+  #define CU_CREATE_MEMORY_REPORT(x)
+  /** No-op if MEMTRACE not defined. */
+  #define CU_DUMP_MEMORY_USAGE(x)
+#endif  /* MEMTRACE */
+
+#ifdef CUNIT_BUILD_TESTS
+/** Disable memory allocation for testing purposes. */
+void test_cunit_deactivate_malloc(void);
+/** Enable memory allocation for testing purposes. */
+void test_cunit_activate_malloc(void);
+/** Retrieve number of memory events for a given pointer */
+unsigned int test_cunit_get_n_memevents(void* pLocation);
+/** Retrieve number of allocations for a given pointer */
+unsigned int test_cunit_get_n_allocations(void* pLocation);
+/** Retrieve number of deallocations for a given pointer */
+unsigned int test_cunit_get_n_deallocations(void* pLocation);
+
+void test_cunit_MyMem(void);
+#endif  /* CUNIT_BUILD_TESTS */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_MYMEM_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/TestDB.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/TestDB.h
new file mode 100644
index 0000000..ec07f92
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/TestDB.h
@@ -0,0 +1,914 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains all the Type Definitions and functions declarations
+ *  for the CUnit test database maintenance.
+ *
+ *  Aug 2001      Initial implementation. (AK)
+ *
+ *  09/Aug/2001   Added Preprocessor conditionals for the file. (AK)
+ *
+ *  24/aug/2001   Made the linked list from SLL to DLL(doubly linked list). (AK)
+ *
+ *  31-Aug-2004   Restructured to eliminate global variables error_number, 
+ *                g_pTestRegistry; new interface, support for deprecated 
+ *                version 1 interface, moved error handling code to 
+ *                CUError.[ch], moved test run counts and _TestResult out 
+ *                of TestRegistry to TestRun.h. (JDS)
+ *
+ *  01-Sep-2004   Added jmp_buf to CU_Test. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ *
+ *  15-Apr-2006   Removed constraint that suites/tests be uniquely named.
+ *                Added ability to turn individual tests/suites on or off.
+ *                Moved doxygen comments for public API here to header. (JDS)
+ */
+
+/** @file
+ *  Management functions for tests, suites, and the test registry.
+ *  Unit testing in CUnit follows the common structure of unit
+ *  tests aggregated in suites, which are themselves aggregated
+ *  in a test registry.  This module provides functions and
+ *  typedef's to support the creation, registration, and manipulation
+ *  of test cases, suites, and the registry.
+ */
+/** @addtogroup Framework
+ *  @{
+ */
+
+#ifndef CUNIT_TESTDB_H_SEEN
+#define CUNIT_TESTDB_H_SEEN
+
+#include <setjmp.h>   /* jmp_buf */
+
+#include "CUnit.h"
+#include "CUError.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*=================================================================
+ *  Typedefs and Data Structures
+ *=================================================================*/
+
+typedef int  (*CU_InitializeFunc)(void);  /**< Signature for suite initialization function. */
+typedef int  (*CU_CleanupFunc)(void);     /**< Signature for suite cleanup function. */
+typedef void (*CU_TestFunc)(void);        /**< Signature for a testing function in a test case. */
+
+/*-----------------------------------------------------------------
+ * CU_Test, CU_pTest
+ *-----------------------------------------------------------------*/
+/** CUnit test case data type.
+ *  CU_Test is a double linked list of unit tests.  Each test has
+ *  a name, a callable test function, and a flag for whether the 
+ *  test is active and thus executed during a  test run.  A test 
+ *  also holds links to the next and previous tests in the list, 
+ *  as well as a jmp_buf reference for use in implementing fatal 
+ *  assertions.<br /><br />
+ *
+ *  Generally, the linked list includes tests which are associated 
+ *  with each other in a CU_Suite.  As a result, tests are run in 
+ *  the order in which they are added to a suite (see CU_add_test()).
+ *  <br /><br />
+ *
+ *  It is recommended that the name of each CU_Test in a suite have
+ *  a unique name.  Otherwise, only the first-registered test having 
+ *  a given name will be accessible by that name.  There are no 
+ *  restrictions on the test function.  This means that the same 
+ *  function could, in principle, be called more than once from 
+ *  different tests.
+ *
+ *  @see CU_Suite
+ *  @see CU_TestRegistry
+ */
+typedef struct CU_Test
+{
+  char*           pName;      /**< Test name. */
+  CU_BOOL         fActive;    /**< Flag for whether test is executed during a run. */
+  CU_TestFunc     pTestFunc;  /**< Pointer to the test function. */
+  jmp_buf*        pJumpBuf;   /**< Jump buffer for setjmp/longjmp test abort mechanism. */
+
+  struct CU_Test* pNext;      /**< Pointer to the next test in linked list. */
+  struct CU_Test* pPrev;      /**< Pointer to the previous test in linked list. */
+
+} CU_Test;
+typedef CU_Test* CU_pTest;    /**< Pointer to a CUnit test case. */
+
+/*-----------------------------------------------------------------
+ *  CU_Suite, CU_pSuite
+ *-----------------------------------------------------------------*/
+/** CUnit suite data type.
+ *  CU_Suite is a linked list of CU_Test containers.  Each suite has 
+ *  a name, a count of registered unit tests, and a flag for whether 
+ *  the suite is active during test runs. It also holds pointers to 
+ *  optional initialization and cleanup functions.  If non-NULL, these 
+ *  are called before and after running the suite's tests, respectively.
+ *  In addition, the suite holds a pointer to the head of the linked 
+ *  list of associated CU_Test objects.  Finally, pointers to the next 
+ *  and previous suites in the linked list are maintained.<br /><br />
+ *
+ *  Generally, the linked list includes suites which are associated with 
+ *  each other in a CU_TestRegistry.  As a result, suites are run in the 
+ *  order in which they are registered (see CU_add_suite()).<br /><br />
+ *
+ *  It is recommended that name of each CU_Suite in a test registry have 
+ *  a unique name.  Otherwise, only the first-registered suite having a 
+ *  given name will be accessible by name.  There are no restrictions on 
+ *  the contained tests.  This means that the same CU_Test could, in 
+ *  principle, be run more than once fron different suites.
+ *
+ *  @see CU_Test
+ *  @see CU_TestRegistry
+ */
+typedef struct CU_Suite
+{
+  char*             pName;            /**< Suite name. */
+  CU_BOOL           fActive;          /**< Flag for whether suite is executed during a run. */
+  CU_pTest          pTest;            /**< Pointer to the 1st test in the suite. */
+  CU_InitializeFunc pInitializeFunc;  /**< Pointer to the suite initialization function. */
+  CU_CleanupFunc    pCleanupFunc;     /**< Pointer to the suite cleanup function. */
+
+  unsigned int      uiNumberOfTests;  /**< Number of tests in the suite. */
+  struct CU_Suite*  pNext;            /**< Pointer to the next suite in linked list. */
+  struct CU_Suite*  pPrev;            /**< Pointer to the previous suite in linked list. */
+
+} CU_Suite;
+typedef CU_Suite* CU_pSuite;          /**< Pointer to a CUnit suite. */
+
+/*-----------------------------------------------------------------
+ *  CU_TestRegistry, CU_pTestRegistry
+ *-----------------------------------------------------------------*/
+/** CUnit test registry data type.
+ *  CU_TestRegisty is the repository for suites containing unit tests.  
+ *  The test registry maintains a count of the number of CU_Suite 
+ *  objects contained in the registry, as well as a count of the total 
+ *  number of CU_Test objects associated with those suites.  It also 
+ *  holds a pointer to the head of the linked list of CU_Suite objects.
+ *  <br /><br />
+ *
+ *  With this structure, the user will normally add suites implictly to 
+ *  the internal test registry using CU_add_suite(), and then add tests 
+ *  to each suite using CU_add_test().  Test runs are then initiated 
+ *  using one of the appropriate functions in TestRun.c via one of the 
+ *  user interfaces.<br /><br />
+ *
+ *  Automatic creation and destruction of the internal registry and its 
+ *  objects is available using CU_initialize_registry() and 
+ *  CU_cleanup_registry(), respectively.  For internal and testing 
+ *  purposes, the internal registry can be retrieved and assigned.  
+ *  Functions are also provided for creating and destroying independent 
+ *  registries.<br /><br />
+ *
+ *  Note that earlier versions of CUnit also contained a pointer to a 
+ *  linked list of CU_FailureRecord objects (termed _TestResults).  
+ *  This has been removed from theregistry and relocated to TestRun.c.
+ *
+ *  @see CU_Test
+ *  @see CU_Suite
+ *  @see CU_initialize_registry()
+ *  @see CU_cleanup_registry()
+ *  @see CU_get_registry()
+ *  @see CU_set_registry()
+ *  @see CU_create_new_registry()
+ *  @see CU_destroy_existing_registry()
+ */
+typedef struct CU_TestRegistry
+{
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+  /** Union to support v1.1-1 member name. */
+  union {
+    unsigned int uiNumberOfSuites;  /**< Number of suites in the test registry. */
+    unsigned int uiNumberOfGroups;  /**< Deprecated (version 1). @deprecated Use uiNumberOfSuites. */
+  };
+  unsigned int uiNumberOfTests;     /**< Number of tests in the test registry. */
+  /** Union to support v1.1-1 member name. */
+  union {
+    CU_pSuite    pSuite;            /**< Pointer to the 1st suite in the test registry. */
+    CU_pSuite    pGroup;            /**< Deprecated (version 1). @deprecated Use pSuite. */
+  };
+#else
+  unsigned int uiNumberOfSuites;    /**< Number of registered suites in the registry. */
+  unsigned int uiNumberOfTests;     /**< Total number of registered tests in the registry. */
+  CU_pSuite    pSuite;              /**< Pointer to the 1st suite in the test registry. */
+#endif
+} CU_TestRegistry;
+typedef CU_TestRegistry* CU_pTestRegistry;  /**< Pointer to a CUnit test registry. */
+
+/*=================================================================
+ *  Public interface functions
+ *=================================================================*/
+
+CU_EXPORT 
+CU_ErrorCode CU_initialize_registry(void);
+/**<
+ *  Initializes the framework test registry.
+ *  Any existing registry is freed, including all stored suites 
+ *  and associated tests.  It is not necessary to explicitly call
+ *  CU_cleanup_registry() before reinitializing the framework.
+ *  The most recent stored test results are also cleared.<br /><br />
+ *
+ *  <B>This function must not be called during a test run (checked
+ *  by assertion)</B>
+ *
+ *  @return  CUE_NOMEMORY if memory for the new registry cannot 
+ *           be allocated, CUE_SUCCESS otherwise.
+ *  @see CU_cleanup_registry
+ *  @see CU_get_registry
+ *  @see CU_set_registry
+ *  @see CU_registry_initialized
+ */
+
+CU_EXPORT 
+void CU_cleanup_registry(void);
+/**<
+ *  Clears the test registry.
+ *  The active test registry is freed, including all stored suites 
+ *  and associated tests.  The most recent stored test results are 
+ *  also cleared.  After calling this function, CUnit suites cannot 
+ *  be added until CU_initialize_registry() or CU_set_registry() is 
+ *  called.  Further, any pointers to suites or test cases held by 
+ *  the user will be invalidated by calling this function.<br /><br />
+ *
+ *  This function may be called multiple times without generating 
+ *  an error condition.  However, <B>this function must not be 
+ *  called during a test run (checked by assertion)</B></P>.
+ *
+ *  @see CU_initialize_registry
+ *  @see CU_get_registry
+ *  @see CU_set_registry
+ */
+
+CU_EXPORT CU_BOOL CU_registry_initialized(void);
+/**<
+ *  Checks whether the test registry has been initialized.
+ *
+ *  @return  CU_TRUE if the registry has been initialized,
+ *           CU_FALSE otherwise.
+ *  @see CU_initialize_registry
+ *  @see CU_cleanup_registry
+ */
+
+CU_EXPORT 
+CU_pSuite CU_add_suite(const char *strName, 
+                       CU_InitializeFunc pInit, 
+                       CU_CleanupFunc pClean);
+/**<
+ *  Creates a new test suite and adds it to the test registry.
+ *  This function creates a new test suite having the specified
+ *  name and initialization/cleanup functions and adds it to the
+ *  test registry.  The new suite will be active and able to be
+ *  executed during a test run.  The test registry must be
+ *  initialized before calling this function (checked by assertion).
+ *  pInit and pClean may be NULL, in which case no corresponding
+ *  initialization of cleanup function will be called when the suite
+ *  is run.  strName may be empty ("") but may not be NULL.<br /><br />
+ *
+ *  The return value is a pointer to the newly-created suite, or 
+ *  NULL if there was a problem with the suite creation or addition.  
+ *  An error code is also set for the framework. Note that if the 
+ *  name specified for the new suite is a duplicate, the suite will 
+ *  be created and added but the error code will be set to CUE_DUP_SUITE.  
+ *  The duplicate suite will not be accessible by name.<br /><br />
+ *
+ *  NOTE - the CU_pSuite pointer returned should NOT BE FREED BY
+ *  THE USER.  The suite is freed by the CUnit system when
+ *  CU_cleanup_registry() is called.  <b>This function must not 
+ *  be called during a test run (checked by assertion)</b>. <br /><br />
+ *
+ *  CU_add_suite() sets the following error codes:
+ *  - CUE_SUCCESS if no errors occurred.
+ *  - CUE_NOREGISTRY if the registry has not been initialized.
+ *  - CUE_NO_SUITENAME if strName is NULL.
+ *  - CUE_DUP_SUITE if a suite having strName is already registered.
+ *  - CUE_NOMEMORY if a memory allocation failed.
+ *
+ *  @param strName Name for the new test suite (non-NULL).
+ *  @param pInit   Initialization function to call before running suite.
+ *  @param pClean  Cleanup function to call after running suite.
+ *  @return A pointer to the newly-created suite (NULL if creation failed)
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_active(CU_pSuite pSuite, CU_BOOL fNewActive);
+/**<
+ *  Activates or deactivates a suite.
+ *  Only activated suites can be executed during a test run.  
+ *  By default a suite is active upon creation, but can be deactivated
+ *  by passing it along with CU_FALSE to this function.  The suite
+ *  can be reactivated by passing it along with CU_TRUE.  The current
+ *  value of the active flag is available as pSuite->fActive.  If pSuite 
+ *  is NULL then error code CUE_NOSUITE is returned.
+ *
+ *  @param pSuite     Pointer to the suite to modify (non-NULL).
+ *  @param fNewActive If CU_TRUE then the suite will be activated; 
+ *                    if CU_FALSE it will be deactivated.
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, CUE_SUCCESS if all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_name(CU_pSuite pSuite, const char *strNewName);
+/**<
+ *  Modifies the name of a suite.
+ *  This function allows the name associated with a suite to
+ *  be changed.  It is not recommended that a suite name be changed,
+ *  nor should it be necessary under most circumstances.  However,
+ *  this function is provided for those clients who need to change
+ *  a suite's name.  The current value of the suite's name 
+ *  is available as pSuite->pName.  CUE_SUCCESS is returned if the
+ *  function succeeds in changing the name.  CUE_NOSUITE is returned if
+ *  pSuite is NULL, and CUE_NO_SUITENAME if strNewName is NULL.
+ *
+ *  @param pSuite     Pointer to the suite to modify (non-NULL).
+ *  @param strNewName Pointer to string containing new suite name (non-NULL).
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, CUE_NO_SUITENAME if
+ *          strNewName is NULL, and CUE_SUCCESS if all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_initfunc(CU_pSuite pSuite, CU_InitializeFunc pNewInit);
+/**<
+ *  Modifies the initialization function of a suite.
+ *  This function allows the initialization function associated with 
+ *  a suite to be changed.  This is neither recommended nor should it 
+ *  be necessary under most circumstances.  However, this function is 
+ *  provided for those clients who need to change the function.  The 
+ *  current value of the function is available as pSuite->pInitializeFunc.
+ *  CUE_SUCCESS is returned if the function succeeds, or CUE_NOSUITE if
+ *  pSuite is NULL.  pNewInit may be NULL, which indicates the suite has
+ *  no initialization function.
+ *
+ *  @param pSuite   Pointer to the suite to modify (non-NULL).
+ *  @param pNewInit Pointer to function to use to initialize suite.
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, and CUE_SUCCESS if 
+ *          all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_cleanupfunc(CU_pSuite pSuite, CU_CleanupFunc pNewClean);
+/**<
+ *  Modifies the cleanup function of a suite.
+ *  This function allows the cleanup function associated with a suite to 
+ *  be changed.  This is neither recommended nor should it be necessary 
+ *  under most circumstances.  However, this function is provided for those 
+ *  clients who need to change the function.  The current value of the 
+ *  function is available as pSuite->pCleanupFunc.  CUE_SUCCESS is returned 
+ *  if the function succeeds, or CUE_NOSUITE if pSuite is NULL.  pNewClean 
+ *  may be NULL, which indicates the suite has no cleanup function.
+ *
+ *  @param pSuite    Pointer to the suite to modify (non-NULL).
+ *  @param pNewClean Pointer to function to use to clean up suite.
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, and CUE_SUCCESS if 
+ *          all is well.
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite(const char* strName);
+/**<
+ *  Retrieves the suite having the specified name.  
+ *  Searches the active test registry and returns a pointer to the 1st 
+ *  suite found.  NULL is returned if no suite having the specified name 
+ *  is found.  In addition, the framework error state is set to CUE_NOREGISTRY 
+ *  if the registry is not initialized or to CUE_NO_SUITENAME if strName is NULL.  
+ *  If the return value is NULL and framework error state is CUE_SUCCESS, then 
+ *  the search simply failed to find the specified name.  
+ *  Use CU_get_suite_at_pos() to retrieve a suite by position rather than name.
+ *
+ *  @param strName The name of the suite to search for (non-NULL).
+ *  @return Returns a pointer to the suite, or NULL if not found or an error occurred.
+ *  @see CU_get_suite_at_pos()
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite_at_pos(unsigned int pos);
+/**<
+ *  Retrieves the suite at the specified position.
+ *  Iterates the active test registry and returns a pointer to the suite at 
+ *  position pos.  pos is a 1-based index having valid values 
+ *  [1 .. CU_get_registry()->uiNumberOfSuites] and corresponds to the order in
+ *  which suites were registered.  If pos is invalid or an error occurs, 0 is 
+ *  returned.  In addition, the framework error state is set to CUE_NOREGISTRY if 
+ *  the registry is not initialized, or CUE_SUCCESS if pos was invalid.  Use 
+ *  CU_get_suite() to retrieve a suite by name rather than position.
+ *
+ *  @param pos The 1-based position of the suite to fetch.
+ *  @return Returns a pointer to the suite, or 0 if not found or an error occurred.
+ *  @see CU_get_suite()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_suite_pos(CU_pSuite pSuite);
+/**<
+ *  Looks up the position of the specified suite.
+ *  The position is a 1-based index of suites in the active test registry which 
+ *  corresponds to the order in which suites were registered.  If pSuite is not 
+ *  found or an error occurs, 0 is returned.  In addition, the framework error 
+ *  state is set to CUE_NOREGISTRY if the registry is not initialized, or 
+ *  CUE_NOSUITE if pSuite is NULL.  The returned position may be used to retrieve 
+ *  the suite using CU_get_suite_by_pos().
+ *
+ *  @param pSuite Pointer to the suite to find (non-NULL).
+ *  @return Returns the 1-based position of pSuite in the registry, or NULL if
+ *         not found or an error occurred.
+ *  @see CU_get_suite_by_pos()
+ *  @see CU_get_suite_pos_by_name()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_suite_pos_by_name(const char* strName);
+/**<
+ *  Looks up the position of the suite having the specified name.
+ *  The position is a 1-based index of suites in the active test registry which 
+ *  corresponds to the order in which suites were registered.  If no suite has the
+ *  specified name or an error occurs, 0 is returned.  In addition, the framework error 
+ *  state is set to CUE_NOREGISTRY if the registry is not initialized, or 
+ *  CUE_NO_SUITENAME if strName is NULL.  The search ends at the 1st suite found having 
+ *  name strName.  The returned position may be used to retrieve the suite using 
+ *  CU_get_suite_by_pos().
+ *
+ *  @param strName Name of the suite to find (non-NULL).
+ *  @return Returns the 1-based position of pSuite in the registry, or NULL if
+ *          not found or an error occurred.
+ *  @see CU_get_suite_by_pos()
+ *  @see CU_get_suite_pos_by_name()
+ */
+
+CU_EXPORT 
+CU_pTest CU_add_test(CU_pSuite pSuite, const char* strName, CU_TestFunc pTestFunc);
+/**<
+ *  This function creates a new test having the specified name 
+ *  and function, and adds it to the specified suite.  The new test 
+ *  is active and able to be executed during a test run.  At present, 
+ *  there is no mechanism for creating a test case independent of a 
+ *  suite.  Neither pSuite, strName, nor pTestFunc may be NULL.
+ *
+ *  The return value is a pointer to the newly-created test, or 
+ *  NULL if there was a problem with the test creation or addition.  
+ *  An error code is also set for the framework. Note that if the 
+ *  name specified for the new test is a duplicate within pSuite, 
+ *  the test will be created and added but the error code will be 
+ *  set to CUE_DUP_TEST.  The duplicate test will not be accessible 
+ *  by name.<br /><br />
+ *
+ *  NOTE - the CU_pTest pointer returned should NOT BE FREED BY
+ *  THE USER.  The test is freed by the CUnit system when
+ *  CU_cleanup_registry() is called.  <b>This function must not 
+ *  be called during a test run (checked by assertion)</b>. <br /><br />
+
+ *  CU_add_test() sets the following error codes:
+ *  - CUE_SUCCESS if no errors occurred.
+ *  - CUE_NOREGISTRY if the registry has not been initialized.
+ *  - CUE_NOSUITE if pSuite is NULL.
+ *  - CUE_NO_TESTNAME if strName is NULL.
+ *  - CUE_NOTEST if pTestFunc is NULL.
+ *  - CUE_DUP_TEST if a test having strName is already registered to pSuite.
+ *  - CUE_NOMEMORY if a memory allocation failed.<br /><br />
+ *
+ *  @param pSuite  Test suite to which to add new test (non-NULL).
+ *  @param strName Name for the new test case (non-NULL).
+ *  @param pTest   Function to call when running the test (non-NULL).
+ *  @return A pointer to the newly-created test (NULL if creation failed)
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_test_active(CU_pTest pTest, CU_BOOL fNewActive);
+/**<
+ *  Activates or deactivates a specific test.
+ *  Only activated tests can be executed during a test run.  
+ *  By default a test is active upon creation, but can be deactvated
+ *  by passing it along with CU_FALSE to this function.  The test
+ *  can be reactivated by passing it along with CU_TRUE.  
+ *  The current value of the active flag is available as pTest->fActive.
+ *  If pTest is NULL then error code CUE_NOTEST is returned.  Otherwise
+ *  CUE_SUCCESS is returned.
+ *
+ *  @param pTest      Pointer to the test to modify (non-NULL).
+ *  @param fNewActive If CU_TRUE then test will be activated; 
+ *                    if CU_FALSE it will be deactivated.
+ *  @return Returns CUE_NOTEST if pTest is NULL, CUE_SUCCESS if all is well.
+*/
+
+CU_EXPORT 
+CU_ErrorCode CU_set_test_name(CU_pTest pTest, const char *strNewName);
+/**<
+ *  Modifies the name of a test.
+ *  This function allows the name associated with a test to
+ *  be changed.  It is not recommended that a test name be changed,
+ *  nor should it be necessary under most circumstances.  However,
+ *  this function is provided for those clients who need to change
+ *  a test's name.  The current value of the test's name is
+ *  available as pTest->pName.  CUE_SUCCESS is returned if the
+ *  function succeeds in changing the name.  CUE_NOTEST is returned if
+ *  pTest is NULL, and CUE_NO_TESTNAME if strNewName is NULL.
+ *
+ *  @param pTest      Pointer to the test to modify (non-NULL).
+ *  @param strNewName Pointer to string containing new test name (non-NULL).
+ *  @return Returns CUE_NOTEST if pTest is NULL, CUE_NO_TESTNAME if
+ *          strNewName is NULL, and CUE_SUCCESS if all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_test_func(CU_pTest pTest, CU_TestFunc pNewFunc);
+/**<
+ *  Modifies the test function of a test.
+ *  This function allows the test function associated with a test to be 
+ *  changed.  This is neither recommended nor should it be necessary under 
+ *  most circumstances.  However, this function is provided for those 
+ *  clients who need to change the test function.  The current value of 
+ *  the test function is available as pTest->pTestFunc.  CUE_SUCCESS is 
+ *  returned if the function succeeds, or CUE_NOTEST if either pTest or
+ *  pNewFunc is NULL.
+ *
+ *  @param pTest    Pointer to the test to modify (non-NULL).
+ *  @param pNewFunc Pointer to function to use for test function (non-NULL).
+ *  @return Returns CUE_NOTEST if pTest or pNewFunc is NULL, and CUE_SUCCESS 
+ *          if all is well.
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test(CU_pSuite pSuite, const char *strName);
+/**<
+ *  Retrieves the test having the specified name.  
+ *  Searches pSuite and returns a pointer to the 1st test found named strName.  
+ *  NULL is returned if no test having the specified name is found in pSuite.  
+ *  In addition, the framework error state is set as follows:
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *    - CUE_NO_TESTNAME if strName is NULL.
+ *  
+ *  If the return value is NULL and framework error state is CUE_SUCCESS, then 
+ *  the search simply failed to find the specified name.  Use CU_get_test_at_pos() 
+ *  to retrieve a test by position rather than name.
+ *
+ *  @param pSuite  Pointer to the suite to search (non-NULL).
+ *  @param strName The name of the test to search for (non-NULL).
+ *  @return Returns a pointer to the test, or NULL if not found or an error occurred.
+ *  @see CU_get_test_at_pos()
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test_at_pos(CU_pSuite pSuite, unsigned int pos);
+/**<
+ *  Retrieves the test at the specified position in pSuite.
+ *  Iterates the tests registered in pSuite and returns a pointer to the 
+ *  test at position pos.  pos is a 1-based index having valid values 
+ *  [1 .. pSuite->uiNumberOfTests] and corresponds to the order in
+ *  which tests were added to pSuite.  If pos is invalid or an error occurs, 0 is 
+ *  returned.  In addition, the framework error state is set as follows: 
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *  Use CU_get_test() to retrieve a test by name rather than position.
+ *
+ *  @param pSuite  Pointer to the suite to search (non-NULL).
+ *  @param pos     The 1-based position of the test to fetch.
+ *  @return Returns a pointer to the test, or 0 if not found or an error occurred.
+ *  @see CU_get_test()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_test_pos(CU_pSuite pSuite, CU_pTest pTest);
+/**<
+ *  Looks up the position of the specified test in pSuite.
+ *  The position is a 1-based index of tests in pSuite which corresponds to the 
+ *  order in which tests were added.  If pTest is not found or an error occurs, 
+ *  0 is returned.  In addition, the framework error state is set as follows:  
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *    - CUE_NOTEST if pTest is NULL
+ *
+ *  The returned position may be used to retrieve the test using CU_get_test_by_pos().
+ *
+ *  @param pSuite Pointer to the suite to search (non-NULL).
+ *  @param pTest  Pointer to the test to find (non-NULL).
+ *  @return Returns the 1-based position of pTest in pSuite, or NULL if
+ *         not found or an error occurred.
+ *  @see CU_get_test_by_pos()
+ *  @see CU_get_test_pos_by_name()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_test_pos_by_name(CU_pSuite pSuite, const char *strName);
+/**<
+ *  Looks up the position of the test having the specified name in pSuite.
+ *  The position is a 1-based index of tests in pSuite which corresponds to the order 
+ *  in which tests were added.  If no test has the specified name or an error occurs, 
+ *  0 is returned.  In addition, the framework error state is set as follows:
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *    - CUE_NO_TESTNAME if strName is NULL
+ *  The search ends at the 1st test found having name strName.  The returned position 
+ *  may be used to retrieve the suite using CU_get_test_by_pos().
+ *
+ *  @param pSuite  Pointer to the suite to search (non-NULL).
+ *  @param strName Name of the test to find (non-NULL).
+ *  @return Returns the 1-based position of pTest in pSuite, or NULL if
+ *          not found or an error occurred.
+ *  @see CU_get_test_by_pos()
+ *  @see CU_get_test_pos_by_name()
+ */
+
+#define CU_ADD_TEST(suite, test) (CU_add_test(suite, #test, (CU_TestFunc)test))
+/**< Shortcut macro for adding a test to a suite. */
+
+/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
+/*  This section is based conceptually on code
+ *  Copyright (C) 2004  Aurema Pty Ltd.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  Derived from code contributed by K. Cheung and Aurema Pty Ltd. (thanks!)
+ *    test_case_t, test_group_t, test_suite_t
+ */
+
+/** 
+ *  Test case parameters structure.
+ *  This data type is provided to assist CUnit users manage collections of 
+ *  tests and suites.  It is intended to be used to build arrays of test case 
+ *  parameters that can be then be referred to in a CU_suite_info_t variable.
+ */
+typedef struct CU_TestInfo {
+	char       *pName;      /**< Test name. */
+	CU_TestFunc pTestFunc;  /**< Test function. */
+} CU_TestInfo;
+typedef CU_TestInfo* CU_pTestInfo;  /**< Pointer to CU_TestInfo type. */
+
+/** 
+ *  Suite parameters.
+ *  This data type is provided to assist CUnit users manage collections of 
+ *  tests and suites.  It is intended to be used to build arrays of suite 
+ *  parameters that can be passed to a bulk registration function such as 
+ *  CU_register_suite() or CU_register_suites().
+ */
+typedef struct CU_SuiteInfo {
+	char             *pName;         /**< Suite name. */
+	CU_InitializeFunc pInitFunc;     /**< Suite initialization function. */
+	CU_CleanupFunc    pCleanupFunc;  /**< Suite cleanup function */
+	CU_TestInfo      *pTests;        /**< Test case array - must be NULL terminated. */
+} CU_SuiteInfo;
+typedef CU_SuiteInfo* CU_pSuiteInfo;  /**< Pointer to CU_SuiteInfo type. */
+
+#define CU_TEST_INFO_NULL { NULL, NULL }
+/**< NULL CU_test_info_t to terminate arrays of tests. */
+#define CU_SUITE_INFO_NULL { NULL, NULL, NULL, NULL }
+/**< NULL CU_suite_info_t to terminate arrays of suites. */
+
+
+CU_EXPORT CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[]);
+/**<
+ *  Registers the suites in a single CU_SuiteInfo array.
+ *  Multiple arrays can be registered using CU_register_nsuites().
+ *
+ *  @param	suite_info NULL-terminated array of CU_SuiteInfo items to register.
+ *  @return A CU_ErrorCode indicating the error status.
+ *  @see CU_register_suites()
+ */
+CU_EXPORT CU_ErrorCode CU_register_nsuites(int suite_count, ...);
+/**<
+ *  Registers multiple suite arrays in CU_SuiteInfo format.
+ *  The function accepts a variable number of suite arrays to be registered.  
+ *  The number of arrays is indicated by the value of the 1st argument, 
+ *  suite_count.  Each suite in each array is registered with the CUnit test 
+ *  registry, along with all of the associated tests.
+ *
+ *  @param	suite_count The number of CU_SuiteInfo* arguments to follow.
+ *  @param ...          suite_count number of CU_SuiteInfo* arguments.  NULLs are ignored.
+ *  @return A CU_ErrorCode indicating the error status.
+ *  @see CU_register_suites()
+ */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+typedef CU_TestInfo test_case_t;    /**< Deprecated (version 1). @deprecated Use CU_TestInfo. */
+typedef CU_SuiteInfo test_group_t;  /**< Deprecated (version 1). @deprecated Use CU_SuiteInfo. */
+
+/** Deprecated (version 1). @deprecated Use CU_SuiteInfo and CU_TestInfo. */
+typedef struct test_suite {
+	char *name;            /**< Suite name.  Currently not used. */
+	test_group_t *groups;  /**< Test groups.  This must be a NULL terminated array. */
+} test_suite_t;
+
+/** Deprecated (version 1). @deprecated Use CU_TEST_INFO_NULL. */
+#define TEST_CASE_NULL { NULL, NULL }
+/** Deprecated (version 1). @deprecated Use CU_TEST_GROUP_NULL. */
+#define TEST_GROUP_NULL { NULL, NULL, NULL, NULL }
+
+/** Deprecated (version 1). @deprecated Use CU_register_suites(). */
+#define test_group_register(tg) CU_register_suites(tg)
+
+/** Deprecated (version 1). @deprecated Use CU_SuiteInfo and CU_register_suites(). */
+CU_EXPORT int test_suite_register(test_suite_t *ts)
+{
+	test_group_t *tg;
+	int error;
+
+	for (tg = ts->groups; tg->pName; tg++)
+		if ((error = CU_register_suites(tg)) != CUE_SUCCESS)
+			return error;
+
+	return CUE_SUCCESS;
+}
+#endif    /* USE_DEPRECATED_CUNIT_NAMES */
+/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+typedef CU_InitializeFunc InitializeFunc; /**< Deprecated (version 1). @deprecated Use CU_InitializeFunc. */
+typedef CU_CleanupFunc CleanupFunc;       /**< Deprecated (version 1). @deprecated Use CU_CleanupFunc. */
+typedef CU_TestFunc TestFunc;             /**< Deprecated (version 1). @deprecated Use CU_TestFunc. */
+
+typedef CU_Test _TestCase;                /**< Deprecated (version 1). @deprecated Use CU_Test. */
+typedef CU_pTest PTestCase;               /**< Deprecated (version 1). @deprecated Use CU_pTest. */
+
+typedef CU_Suite  _TestGroup;             /**< Deprecated (version 1). @deprecated Use CU_Suite. */
+typedef CU_pSuite PTestGroup;             /**< Deprecated (version 1). @deprecated Use CU_pSuite. */
+
+typedef CU_TestRegistry  _TestRegistry;   /**< Deprecated (version 1). @deprecated Use CU_TestRegistry. */
+typedef CU_pTestRegistry PTestRegistry;   /**< Deprecated (version 1). @deprecated Use CU_pTestRegistry. */
+
+/* Public interface functions */
+/** Deprecated (version 1). @deprecated Use CU_initialize_registry(). */
+#define initialize_registry() CU_initialize_registry()
+/** Deprecated (version 1). @deprecated Use CU_cleanup_registry(). */
+#define cleanup_registry() CU_cleanup_registry()
+/** Deprecated (version 1). @deprecated Use CU_add_suite(). */
+#define add_test_group(name, init, clean) CU_add_suite(name, init, clean)
+/** Deprecated (version 1). @deprecated Use CU_add_test(). */
+#define add_test_case(group, name, test) CU_add_test(group, name, test)
+
+/* private internal CUnit testing functions */
+/** Deprecated (version 1). @deprecated Use CU_get_registry(). */
+#define get_registry() CU_get_registry()
+/** Deprecated (version 1). @deprecated Use CU_set_registry(). */
+#define set_registry(reg) CU_set_registry((reg))
+
+/** Deprecated (version 1). @deprecated Use CU_get_suite_by_name(). */
+#define get_group_by_name(group, reg) CU_get_suite_by_name(group, reg)
+/** Deprecated (version 1). @deprecated Use CU_get_test_by_name(). */
+#define get_test_by_name(test, group) CU_get_test_by_name(test, group)
+
+/** Deprecated (version 1). @deprecated Use ADD_TEST_TO_SUITE. */
+#define ADD_TEST_TO_GROUP(group, test) (CU_add_test(group, #test, (CU_TestFunc)test))
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+/*=================================================================
+ *  Internal CUnit system functions.  
+ *  Should not be routinely called by users.
+ *=================================================================*/
+
+CU_EXPORT CU_pTestRegistry CU_get_registry(void);
+/**<
+ *  Retrieves a pointer to the current test registry.
+ *  Returns NULL if the registry has not been initialized using
+ *  CU_initialize_registry().  Directly accessing the registry
+ *  should not be necessary for most users.  This function is
+ *  provided primarily for internal and testing purposes.
+ *
+ *  @return A pointer to the current registry (NULL if uninitialized).
+ *  @see CU_initialize_registry
+ *  @see CU_set_registry
+ */
+
+CU_EXPORT CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry);
+/**<
+ *  Sets the registry to an existing CU_pTestRegistry instance.
+ *  A pointer to the original registry is returned.  Note that the
+ *  original registry is not freed, and it becomes the caller's
+ *  responsibility to do so.  Directly accessing the registry
+ *  should not be necessary for most users.  This function is
+ *  provided primarily for internal and testing purposes.<br /><br />
+ *
+ *  <B>This function must not be called during a test run (checked
+ *  by assertion)</B>.
+ *
+ *  @return A pointer to the original registry that was replaced.
+ *  @see CU_initialize_registry
+ *  @see CU_cleanup_registry
+ *  @see CU_get_registry
+ */
+
+CU_EXPORT CU_pTestRegistry CU_create_new_registry(void);
+/**< 
+ *  Creates and initializes a new test registry.
+ *  Returns a pointer to a new, initialized registry (NULL if memory could 
+ *  not be allocated).  It is the caller's responsibility to destroy and free 
+ *  the new registry (unless it is made the active test registry using
+ *  CU_set_registry()).
+ */
+
+CU_EXPORT 
+void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry);
+/**< 
+ *  Destroys and frees all memory for an existing test registry.
+ *  The active test registry is destroyed by the CUnit system in 
+ *  CU_cleanup_registry(), so only call this function on registries created
+ *  or held independently of the internal CUnit system.<br /><br />
+ *
+ *  Once a registry is made the active test registry using CU_set_registry(), 
+ *  its destruction will be handled by the framework.  ppRegistry may not be 
+ *  NULL (checked by assertion), but *ppRegistry can be NULL (in which case the
+ *  function has no effect).  Note that *ppRegistry will be set to NULL on return.
+ *
+ *  @param ppRegistry Address of a pointer to the registry to destroy (non-NULL).
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite_by_name(const char *szSuiteName, CU_pTestRegistry pRegistry);
+/**<
+ *  Retrieves a pointer to the suite having the specified name.
+ *  Scans the pRegistry and returns a pointer to the first suite located 
+ *  having the specified name.  Neither szSuiteName nor pRegistry may be
+ *  NULL (checked by assertion).  Clients should normally use CU_get_suite()
+ *  instead, which automatically searches the active test registry.
+ *
+ *  @param szSuiteName The name of the suite to locate (non-NULL).
+ *  @param pRegistry   The registry to scan (non-NULL).
+ *  @return Pointer to the first suite having the specified name,
+ *          NULL if not found.
+ *  @see CU_get_suite()
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite_by_index(unsigned int index, CU_pTestRegistry pRegistry);
+/**<
+ *  Retrieves a pointer to the suite at the specified (1-based) index.
+ *  Iterates pRegistry and returns a pointer to the suite located at the 
+ *  specified index.  pRegistry may not be NULL (checked by assertion).  
+ *  Clients should normally use CU_get_suite_at_pos() instead, which 
+ *  automatically searches the active test registry.
+ *
+ *  @param index     The 1-based index of the suite to find.
+ *  @param pRegistry The registry to scan (non-NULL).
+ *  @return Pointer to the suite at the specified index, or
+ *          NULL if index is invalid.
+ *  @see CU_get_suite_at_pos()
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test_by_name(const char* szTestName, CU_pSuite pSuite);
+/**< 
+ *  Retrieves a pointer to the test case in pSuite having the specified name.
+ *  The first test case in pSuite having the specified name is returned, or
+ *  NULL if not found.  Neither szSuiteName nor pSuite may be NULL (checked 
+ *  by assertion).  Clients should normally use CU_get_test() instead.
+ *
+ *  @param szTestName The name of the test case to locate (non-NULL).
+ *  @param pSuite     The suite to scan (non-NULL).
+ *  @return Pointer to the first test case having the specified name,
+ *          NULL if not found.
+ *  @see CU_get_test()
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test_by_index(unsigned int index, CU_pSuite pSuite);
+/**<
+ *  Retrieves a pointer to the test at the specified (1-based) index.
+ *  Iterates pSuite and returns a pointer to the test located at the 
+ *  specified index.  pSuite may not be NULL (checked by assertion).  
+ *  Clients should normally use CU_get_test_at_pos() instead, which 
+ *  automatically searches the active test registry.
+ *
+ *  @param index     The 1-based index of the test to find.
+ *  @param pRegistry The registry to scan (non-NULL).
+ *  @return Pointer to the test at the specified index, or
+ *          NULL if index is invalid.
+ *  @see CU_get_test_at_pos()
+ */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_TestDB(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_TESTDB_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/TestRun.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/TestRun.h
new file mode 100644
index 0000000..51072f4
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/TestRun.h
@@ -0,0 +1,444 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Interface to Run tests.
+ *
+ *  Aug 2001      Initial implementation. (AK)
+ *
+ *  09/Aug/2001   Contains generic run tests interface which can be used
+ *                for any type of frontend interface framework. (AK)
+ *
+ *  24/Nov/2001   Added Handler for Group Initialization failure condition. (AK)
+ *
+ *  05-Aug-2004   New interface.  Since these should be internal functions,
+ *                no support for deprecated version 1 names provided now,
+ *                eliminated global variables for current test & suite,
+ *                moved (renamed) _TestResult here from TestDB.h. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ *
+ *  23-Apr-2006   Moved doxygen comments into header.
+ *                Added type marker to CU_FailureRecord.
+ *                Added support for tracking inactive suites/tests. (JDS)
+ *
+ *  08-May-2006   Moved CU_print_run_results() functionality from
+ *                console/basic test complete handler.  (JDS)
+ *
+ *  24-May-2006   Added callbacks for suite start and complete events.
+ *                Added tracking/reported of elapsed time.  (JDS)
+ */
+
+/** @file
+ *  Test run management functions (user interface).
+ *  The TestRun module implements functions supporting the running
+ *  of tests elements (suites and tests).  This includes functions for
+ *  running suites and tests, retrieving the number of tests/suites run,
+ *  and managing callbacks during the run process.<br /><br />
+ *
+ *  The callback mechanism works as follows.  The CUnit runtime system
+ *  supports the registering and calling of functions at the start and end
+ *  of each test, when all tests are complete, and when a suite
+ *  initialialization function returns an error.  This allows clients to
+ *  perform actions associated with these events such as output formatting
+ *  and reporting.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_TESTRUN_H_SEEN
+#define CUNIT_TESTRUN_H_SEEN
+
+#include "CUnit.h"
+#include "CUError.h"
+#include "TestDB.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Types of failures occurring during test runs. */
+typedef enum CU_FailureTypes
+{
+  CUF_SuiteInactive = 1,    /**< Inactive suite was run. */
+  CUF_SuiteInitFailed,      /**< Suite initialization function failed. */
+  CUF_SuiteCleanupFailed,   /**< Suite cleanup function failed. */
+  CUF_TestInactive,         /**< Inactive test was run. */
+  CUF_AssertFailed          /**< CUnit assertion failed during test run. */
+} CU_FailureType;           /**< Failure type. */
+
+/* CU_FailureRecord type definition. */
+/** Data type for holding assertion failure information (linked list). */
+typedef struct CU_FailureRecord
+{
+  CU_FailureType  type;           /**< Failure type. */
+  unsigned int    uiLineNumber;   /**< Line number of failure. */
+  char*           strFileName;    /**< Name of file where failure occurred. */
+  char*           strCondition;   /**< Test condition which failed. */
+  CU_pTest        pTest;          /**< Test containing failure. */
+  CU_pSuite       pSuite;         /**< Suite containing test having failure. */
+
+  struct CU_FailureRecord* pNext; /**< Pointer to next record in linked list. */
+  struct CU_FailureRecord* pPrev; /**< Pointer to previous record in linked list. */
+
+} CU_FailureRecord;
+typedef CU_FailureRecord* CU_pFailureRecord;  /**< Pointer to CU_FailureRecord. */
+
+/* CU_RunSummary type definition. */
+/** Data type for holding statistics and assertion failures for a test run. */
+typedef struct CU_RunSummary
+{
+  unsigned int nSuitesRun;        /**< Number of suites completed during run. */
+  unsigned int nSuitesFailed;     /**< Number of suites for which initialization failed. */
+  unsigned int nSuitesInactive;   /**< Number of suites which were inactive. */
+  unsigned int nTestsRun;         /**< Number of tests completed during run. */
+  unsigned int nTestsFailed;      /**< Number of tests containing failed assertions. */
+  unsigned int nTestsInactive;    /**< Number of tests which were inactive (in active suites). */
+  unsigned int nAsserts;          /**< Number of assertions tested during run. */
+  unsigned int nAssertsFailed;    /**< Number of failed assertions. */
+  unsigned int nFailureRecords;   /**< Number of failure records generated. */
+  double       ElapsedTime;       /**< Elapsed time for run in seconds. */
+} CU_RunSummary;
+typedef CU_RunSummary* CU_pRunSummary;  /**< Pointer to CU_RunSummary. */
+
+/*-------------------------------------------------------------------- 
+ * Type Definitions for Message Handlers.
+ *--------------------------------------------------------------------*/
+typedef void (*CU_SuiteStartMessageHandler)(const CU_pSuite pSuite);
+/**< Message handler called at the start of a suite. pSuite will not be null. */
+
+typedef void (*CU_TestStartMessageHandler)(const CU_pTest pTest, const CU_pSuite pSuite);
+/**< Message handler called at the start of a test.
+ *  The parameters are the test and suite being run.  The test run is 
+ *  considered in progress when the message handler is called.  
+ *  Neither pTest nor pSuite may be null.
+ */
+
+typedef void (*CU_TestCompleteMessageHandler)(const CU_pTest pTest, const CU_pSuite pSuite,
+                                              const CU_pFailureRecord pFailure);
+/**< Message handler called at the completion of a test.
+ *  The parameters are the test and suite being run, plus a pointer to 
+ *  the first failure record applicable to this test.  If the test did 
+ *  not have any assertion failures, pFailure will be NULL.  The test run 
+ *  is considered in progress when the message handler is called.
+ */
+
+typedef void (*CU_SuiteCompleteMessageHandler)(const CU_pSuite pSuite,
+                                               const CU_pFailureRecord pFailure);
+/**< Message handler called at the completion of a suite.
+ *  The parameters are suite being run, plus a pointer to the first failure 
+ *  record applicable to this suite.  If the suite and it's tests did not 
+ *  have any failures, pFailure will be NULL.  The test run is considered 
+ *  in progress when the message handler is called.
+ */
+
+typedef void (*CU_AllTestsCompleteMessageHandler)(const CU_pFailureRecord pFailure);
+/**< Message handler called at the completion of a test run.
+ *  The parameter is a pointer to the linked list holding the failure 
+ *  records for the test run.  The test run is considered completed 
+ *  when the message handler is called.
+ */
+
+typedef void (*CU_SuiteInitFailureMessageHandler)(const CU_pSuite pSuite);
+/**< Message handler called when a suite initializer fails.
+ *  The test run is considered in progress when the message handler is called.
+ */
+
+typedef void (*CU_SuiteCleanupFailureMessageHandler)(const CU_pSuite pSuite);
+/**< Message handler called when a suite cleanup function fails.
+ *  The test run is considered in progress when the message handler is called.
+ */
+
+/*-------------------------------------------------------------------- 
+ * Get/Set functions for Message Handlers
+ *--------------------------------------------------------------------*/
+CU_EXPORT void CU_set_suite_start_handler(CU_SuiteStartMessageHandler pSuiteStartMessage);
+/**< Sets the message handler to call before each suite is run. */
+CU_EXPORT void CU_set_test_start_handler(CU_TestStartMessageHandler pTestStartMessage);
+/**< Sets the message handler to call before each test is run. */
+CU_EXPORT void CU_set_test_complete_handler(CU_TestCompleteMessageHandler pTestCompleteMessage);
+/**< Sets the message handler to call after each test is run. */
+CU_EXPORT void CU_set_suite_complete_handler(CU_SuiteCompleteMessageHandler pSuiteCompleteMessage);
+/**< Sets the message handler to call after each suite is run. */
+CU_EXPORT void CU_set_all_test_complete_handler(CU_AllTestsCompleteMessageHandler pAllTestsCompleteMessage);
+/**< Sets the message handler to call after all tests have been run. */
+CU_EXPORT void CU_set_suite_init_failure_handler(CU_SuiteInitFailureMessageHandler pSuiteInitFailureMessage);
+/**< Sets the message handler to call when a suite initialization function returns an error. */
+CU_EXPORT void CU_set_suite_cleanup_failure_handler(CU_SuiteCleanupFailureMessageHandler pSuiteCleanupFailureMessage);
+/**< Sets the message handler to call when a suite cleanup function returns an error. */
+
+CU_EXPORT CU_SuiteStartMessageHandler          CU_get_suite_start_handler(void);
+/**< Retrieves the message handler called before each suite is run. */
+CU_EXPORT CU_TestStartMessageHandler           CU_get_test_start_handler(void);
+/**< Retrieves the message handler called before each test is run. */
+CU_EXPORT CU_TestCompleteMessageHandler        CU_get_test_complete_handler(void);
+/**< Retrieves the message handler called after each test is run. */
+CU_EXPORT CU_SuiteCompleteMessageHandler       CU_get_suite_complete_handler(void);
+/**< Retrieves the message handler called after each suite is run. */
+CU_EXPORT CU_AllTestsCompleteMessageHandler    CU_get_all_test_complete_handler(void);
+/**< Retrieves the message handler called after all tests are run. */
+CU_EXPORT CU_SuiteInitFailureMessageHandler    CU_get_suite_init_failure_handler(void);
+/**< Retrieves the message handler called when a suite initialization error occurs. */
+CU_EXPORT CU_SuiteCleanupFailureMessageHandler CU_get_suite_cleanup_failure_handler(void);
+/**< Retrieves the message handler called when a suite cleanup error occurs. */
+
+/*-------------------------------------------------------------------- 
+ * Functions for running registered tests and suites.
+ *--------------------------------------------------------------------*/
+CU_EXPORT CU_ErrorCode CU_run_all_tests(void);
+/**< 
+ *  Runs all tests in all suites registered in the test registry.
+ *  The suites are run in the order registered in the test registry.
+ *  For each suite, it is first checked to make sure it is active.
+ *  Any initialization function is then called, the suite is run 
+ *  using run_single_suite(), and finally any suite cleanup function 
+ *  is called.  If an error condition (other than CUE_NOREGISTRY) 
+ *  occurs during the run, the action depends on the current error 
+ *  action (see CU_set_error_action()).  An inactive suite is not
+ *  considered an error for this function.  Note that the run
+ *  statistics (counts of tests, successes, failures) are cleared 
+ *  each time this function is run, even if it is unsuccessful.
+ *
+ *  @return A CU_ErrorCode indicating the first error condition
+ *          encountered while running the tests.
+ *  @see CU_run_suite() to run the tests in a specific suite.
+ *  @see CU_run_test() for run a specific test only.
+ */
+
+CU_EXPORT CU_ErrorCode CU_run_suite(CU_pSuite pSuite);
+/**< 
+ *  Runs all tests in a specified suite.
+ *  The suite need not be registered in the test registry to be 
+ *  run.  It does, however, need to have its fActive flag set to 
+ *  CU_TRUE.<br /><br />
+ *
+ *  Any initialization function for the suite is first called,
+ *  then the suite is run using run_single_suite(), and any suite 
+ *  cleanup function is called.  Note that the run statistics 
+ *  (counts of tests, successes, failures) are initialized each 
+ *  time this function is called even if it is unsuccessful.  If 
+ *  an error condition occurs during the run, the action depends 
+ *  on the  current error action (see CU_set_error_action()).
+ *
+ *  @param pSuite The suite containing the test (non-NULL)
+ *  @return A CU_ErrorCode indicating the first error condition
+ *          encountered while running the suite.  CU_run_suite()
+ *          sets and returns CUE_NOSUITE if pSuite is NULL, or 
+ *          CUE_SUITE_INACTIVE if the requested suite is not 
+ *          activated.  Other error codes can be set during suite 
+ *          initialization or cleanup or during test runs.
+ *  @see CU_run_all_tests() to run all suites.
+ *  @see CU_run_test() to run a single test in a specific suite.
+ */
+
+CU_EXPORT CU_ErrorCode CU_run_test(CU_pSuite pSuite, CU_pTest pTest);
+/**< 
+ *  Runs a specific test in a specified suite.
+ *  The suite need not be registered in the test registry to be run,
+ *  although the test must be registered in the specified suite.
+ *  Any initialization function for the suite is first
+ *  called, then the test is run using run_single_test(), and
+ *  any suite cleanup function is called.  Note that the
+ *  run statistics (counts of tests, successes, failures)
+ *  will be initialized each time this function is called even
+ *  if it is not successful.  Both the suite and test specified
+ *  must be active for the test to be run.  The suite is not
+ *  considered to be run, although it may be counted as a failed
+ *  suite if the intialization or cleanup functions fail.
+ *
+ *  @param pSuite The suite containing the test (non-NULL)
+ *  @param pTest  The test to run (non-NULL)
+ *  @return A CU_ErrorCode indicating the first error condition
+ *          encountered while running the suite.  CU_run_test()
+ *          sets and returns CUE_NOSUITE if pSuite is NULL,
+ *          CUE_NOTEST if pTest is NULL, CUE_SUITE_INACTIVE if
+ *          pSuite is not active, CUE_TEST_NOT_IN_SUITE
+ *          if pTest is not registered in pSuite, and CU_TEST_INACTIVE
+ *          if pTest is not active.  Other error codes can be set during 
+ *          suite initialization or cleanup or during the test run.
+ *  @see CU_run_all_tests() to run all tests/suites.
+ *  @see CU_run_suite() to run all tests in a specific suite.
+ */
+
+/*-------------------------------------------------------------------- 
+ * Functions for setting runtime behavior.
+ *--------------------------------------------------------------------*/
+CU_EXPORT void CU_set_fail_on_inactive(CU_BOOL new_inactive);
+/**<
+ *  Sets whether an inactive suite or test is treated as a failure.
+ *  If CU_TRUE, then failure records will be generated for inactive 
+ *  suites or tests encountered during a test run.  The default is 
+ *  CU_TRUE so that the client is reminded that the framewrork 
+ *  contains inactive suites/tests.  Set to CU_FALSE to turn off 
+ *  this behavior.
+ *
+ *  @param new_inactive New setting for whether to treat inactive
+ *                      suites and tests as failures during a test 
+ *                      run (CU_TRUE) or not (CU_FALSE).
+ *  @see CU_get_fail_on_failure()
+ */
+ 
+CU_EXPORT CU_BOOL CU_get_fail_on_inactive(void);
+/**<
+ *  Retrieves the current setting for whether inactive suites/tests 
+ *  are treated as failures.  If CU_TRUE then failure records will 
+ *  be generated for inactive suites encountered during a test run.
+ *
+ *  @return CU_TRUE if inactive suites/tests are failures, CU_FALSE if not.
+ *  @see CU_set_fail_on_inactive()
+ */
+
+/*-------------------------------------------------------------------- 
+ * Functions for getting information about the previous test run. 
+ *--------------------------------------------------------------------*/
+CU_EXPORT unsigned int CU_get_number_of_suites_run(void);
+/**< Retrieves the number of suites completed during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_suites_failed(void);
+/**< Retrieves the number of suites which failed to initialize during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_suites_inactive(void);
+/**< Retrieves the number of inactive suites found during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_tests_run(void);
+/**< Retrieves the number of tests completed during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_tests_failed(void);
+/**< Retrieves the number of tests containing failed assertions during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_tests_inactive(void);
+/**< Retrieves the number of inactive tests found during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_asserts(void);
+/**< Retrieves the number of assertions processed during the last run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_successes(void);
+/**< Retrieves the number of successful assertions during the last run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_failures(void);
+/**< Retrieves the number of failed assertions during the last run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_failure_records(void);
+/**< 
+ *  Retrieves the number failure records created during the previous run (reset each run).  
+ *  Note that this may be more than the number of failed assertions, since failure 
+ *  records may also be created for failed suite initialization and cleanup.
+ */
+CU_EXPORT double CU_get_elapsed_time(void);
+/**< 
+ *  Retrieves the elapsed time for the last run in seconds (reset each run).
+ *  This function will calculate the current elapsed time if the test run has not
+ *  yet completed.  This is in contrast to the run summary returned by
+ *  CU_get_run_summary(), for which the elapsed time is not updated until the
+ *  end of the run.
+ */
+CU_EXPORT CU_pFailureRecord CU_get_failure_list(void);
+/**< 
+ *  Retrieves the head of the linked list of failures which occurred during the 
+ *  last run (reset each run).  Note that the pointer returned is invalidated 
+ *  when the client initiates a run using CU_run_all_tests(), CU_run_suite(), 
+ *  or CU_run_test().
+ */
+CU_EXPORT CU_pRunSummary CU_get_run_summary(void);
+/**< 
+ *  Retrieves the entire run summary for the last test run (reset each run).
+ *  The run counts and stats contained in the run summary are updated 
+ *  throughout a test run.  Note, however, that the elapsed time is not 
+ *  updated until after all suites/tests are run but before the "all tests 
+ *  complete"  message handler is called (if any).  To get the elapsed 
+ *  time during a test run, use CU_get_elapsed_time() instead.
+ */
+
+CU_EXPORT char * CU_get_run_results_string(void);
+/**<
+ *  Creates a string and fills it with a summary of the current run results.
+ *  The run summary presents data for the suites, tests, and assertions 
+ *  encountered during the run, as well as the elapsed time.  The data 
+ *  presented include the number of registered, run, passed, failed, and 
+ *  inactive entities for each, as well as the elapsed time.  This function 
+ *  can be called at any time, although the test registry must have been 
+ *  initialized (checked by assertion).  The returned string is owned by 
+ *  the caller and should be deallocated using CU_FREE().  NULL is returned 
+ *  if there is an error allocating the new string.
+ *
+ *  @return A new string containing the run summary (owned by caller).
+ */
+ 
+CU_EXPORT void CU_print_run_results(FILE *file);
+/**<
+ *  Prints a summary of the current run results to file.
+ *  The run summary is the same as returned by CU_get_run_results_string().
+ *  Note that no newlines are printed before or after the report, so any
+ *  positioning must be performed before/after calling this function.  The
+ *  report itself extends over several lines broken by '\n' characters.
+ *  file may not be NULL (checked by assertion).
+ *
+ *  @param file Pointer to stream to receive the printed summary (non-NULL).
+ */
+
+/*-------------------------------------------------------------------- 
+ * Functions for internal & testing use.
+ *--------------------------------------------------------------------*/
+CU_EXPORT CU_pSuite CU_get_current_suite(void);
+/**< Retrieves a pointer to the currently-running suite (NULL if none). */
+CU_EXPORT CU_pTest  CU_get_current_test(void);
+/**< Retrievea a pointer to the currently-running test (NULL if none). */
+CU_EXPORT CU_BOOL   CU_is_test_running(void);
+/**< Returns <CODE>CU_TRUE</CODE> if a test run is in progress,
+ *  <CODE>CU_TRUE</CODE> otherwise.
+ */
+
+CU_EXPORT void      CU_clear_previous_results(void);
+/**< 
+ *  Initializes the run summary information stored from the previous test run.  
+ *  Resets the run counts to zero, and frees any memory associated with 
+ *  failure records.  Calling this function multiple times, while inefficient, 
+ *  will not cause an error condition.
+ *  @see clear_previous_results()
+ */
+
+CU_EXPORT CU_BOOL CU_assertImplementation(CU_BOOL bValue,
+                                          unsigned int uiLine,
+                                          const char *strCondition,
+                                          const char *strFile,
+                                          const char *strFunction,
+                                          CU_BOOL bFatal);
+/**< 
+ *  Assertion implementation function.
+ *  All CUnit assertions reduce to a call to this function.  It should only be
+ *  called during an active test run (checked by assertion).  This means that CUnit
+ *  assertions should only be used in registered test functions during a test run.
+ *
+ *  @param bValue        Value of the assertion (CU_TRUE or CU_FALSE).
+ *  @param uiLine        Line number of failed test statement.
+ *  @param strCondition  String containing logical test that failed.
+ *  @param strFile       Source file where test statement failed.
+ *  @param strFunction   Function where test statement failed.
+ *  @param bFatal        CU_TRUE to abort test (via longjmp()), CU_FALSE to continue test.
+ *  @return As a convenience, returns the value of the assertion (i.e. bValue).
+ */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+typedef CU_FailureRecord  _TestResult;  /**< @deprecated Use CU_FailureRecord. */
+typedef CU_pFailureRecord PTestResult;  /**< @deprecated Use CU_pFailureRecord. */
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_TestRun(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_TESTRUN_H_SEEN  */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Util.h b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Util.h
new file mode 100644
index 0000000..c03085b
--- /dev/null
+++ b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/include/CUnit/Util.h
@@ -0,0 +1,158 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Type Definitions for some generic functions used across
+ *  CUnit project files.
+ *
+ *  13/Oct/2001   Moved some of the generic functions declarations from
+ *                other files to this one so as to use the functions 
+ *                consitently. This file is not included in the distribution 
+ *                headers because it is used internally by CUnit. (AK)
+ *
+ *  20-Jul-2004   New interface, support for deprecated version 1 names. (JDS)
+ *
+ *  5-Sep-2004    Added internal test interface. (JDS)
+ *
+ *  17-Apr-2006   Added CU_translated_strlen() and CU_number_width().
+ *                Removed CUNIT_MAX_STRING_LENGTH - dangerous since not enforced.
+ *                Fixed off-by-1 error in CU_translate_special_characters(), 
+ *                modifying implementation & results in some cases.  User can 
+ *                now tell if conversion failed. (JDS)
+ */
+
+/** @file
+ *  Utility functions (user interface).
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_UTIL_H_SEEN
+#define CUNIT_UTIL_H_SEEN
+
+#include "CUnit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CUNIT_MAX_ENTITY_LEN 5
+/**< Maximum number of characters in a translated xml entity. */
+
+CU_EXPORT size_t CU_translate_special_characters(const char *szSrc, char *szDest, size_t maxlen);
+/**< 
+ *  Converts special characters in szSrc to xml entity codes and stores 
+ *  result in szDest.  Currently conversion of '&', '<', and '>' is supported.
+ *  Note that conversion to entities increases the length of the converted 
+ *  string.  The greatest conversion size increase would be a string 
+ *  consisting entirely of entity characters of converted length 
+ *  CUNIT_MAX_ENTITY_LEN.  Neither szSrc nor szDest may be NULL 
+ *  (checked by assertion).<br /><br />
+ *
+ *  maxlen gives the maximum number of characters in the translated string.
+ *  If szDest does not have enough room to hold the converted string, the
+ *  return value will be zero and szDest will contain an empty string.
+ *  If this occurs, the remaining characters in szDest may be overwritten
+ *  in an unspecified manner.  It is the caller's responsibility to make 
+ *  sure there is sufficient room in szDest to hold the converted string.  
+ *  CU_translated_strlen() may be used to calculate the length of buffer 
+ *  required (remember to add 1 for the terminating \0).  
+ *
+ *  @param szSrc  Source string to convert (non-NULL).
+ *  @param szDest Location to hold the converted string (non-NULL).
+ *  @param maxlen Maximum number of characters szDest can hold.
+ *  @return   The number of special characters converted (always 0 if
+ *            szDest did not have enough room to hold converted string).
+ */
+
+CU_EXPORT size_t CU_translated_strlen(const char *szSrc);
+/**< 
+ *  Calculates the length of a translated string.
+ *  This function calculates the buffer length required to hold a string
+ *  after processing with CU_translate_special_characters().  The returned
+ *  length does not include space for the terminating '\0' character.  
+ *  szSrc may not be NULL (checked by assertion).
+ *
+ *  @param szSrc  Source string to analyze (non-NULL).
+ *  @return The number of characters szSrc will expand to when converted.
+ */
+
+CU_EXPORT int CU_compare_strings(const char *szSrc, const char *szDest);
+/**<
+ *  Case-insensitive string comparison.  Neither string pointer
+ *  can be NULL (checked by assertion).
+ *
+ *  @param szSrc  1st string to compare (non-NULL).
+ *  @param szDest 2nd string to compare (non-NULL).
+ *  @return  0 if the strings are equal, non-zero otherwise.
+ */
+
+CU_EXPORT void CU_trim_left(char *szString);
+/**<
+ *  Trims leading whitespace from the specified string.
+ *  @param szString  The string to trim.
+ */
+
+CU_EXPORT void CU_trim_right(char *szString);
+/**< 
+ *  Trims trailing whitespace from the specified string.
+ *  @param szString  The string to trim.
+ */
+
+CU_EXPORT void CU_trim(char *szString);
+/**< 
+ *  Trims leading and trailing whitespace from the specified string.
+ *  @param szString  The string to trim.
+ */
+
+CU_EXPORT size_t CU_number_width(int number);
+/**< 
+ *  Calulates the number of places required to display 
+ *  number in decimal.
+ */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_Util(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+#define CUNIT_MAX_STRING_LENGTH	1024
+/**< Maximum string length. */
+#define translate_special_characters(src, dest, len) CU_translate_special_characters(src, dest, len)
+/**< Deprecated (version 1). @deprecated Use CU_translate_special_characters(). */
+#define compare_strings(src, dest) CU_compare_strings(src, dest)
+/**< Deprecated (version 1). @deprecated Use CU_compare_strings(). */
+
+#define trim_left(str) CU_trim_left(str)
+/**< Deprecated (version 1). @deprecated Use CU_trim_left(). */
+#define trim_right(str) CU_trim_right(str)
+/**< Deprecated (version 1). @deprecated Use CU_trim_right(). */
+#define trim(str) CU_trim(str)
+/**< Deprecated (version 1). @deprecated Use CU_trim(). */
+
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#endif /* CUNIT_UTIL_H_SEEN */
+/** @} */
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/cygwin/cunit.lib b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/cygwin/cunit.lib
new file mode 100755
index 0000000..047d5d3
Binary files /dev/null and b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/cygwin/cunit.lib differ
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/linux/libcunit.a b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/linux/libcunit.a
new file mode 100644
index 0000000..8cc73ef
Binary files /dev/null and b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/linux/libcunit.a differ
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/mingw/cunit.lib b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/mingw/cunit.lib
new file mode 100755
index 0000000..411d432
Binary files /dev/null and b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/mingw/cunit.lib differ
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/osx/libcunit.a b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/osx/libcunit.a
new file mode 100644
index 0000000..16a84bb
Binary files /dev/null and b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/osx/libcunit.a differ
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/vs2010/cunit.lib b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/vs2010/cunit.lib
new file mode 100644
index 0000000..b4cda57
Binary files /dev/null and b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/vs2010/cunit.lib differ
diff --git a/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/vs2013/cunit.lib b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/vs2013/cunit.lib
new file mode 100755
index 0000000..a5c8b36
Binary files /dev/null and b/subprojects/cpp/src/integTest/resources/org/gradle/nativebinaries/language/cpp/CUnitIntegrationTest/shared/libs/cunit/2.1-2/lib/vs2013/cunit.lib differ
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/CdtIdePlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/CdtIdePlugin.groovy
new file mode 100644
index 0000000..cccb6ab
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/CdtIdePlugin.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt
+
+import org.gradle.api.Incubating
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.tasks.Delete
+
+import org.gradle.ide.cdt.model.ProjectSettings
+import org.gradle.ide.cdt.model.ProjectDescriptor
+import org.gradle.ide.cdt.model.CprojectSettings
+import org.gradle.ide.cdt.model.CprojectDescriptor
+
+import org.gradle.ide.cdt.tasks.GenerateMetadataFileTask
+
+ at Incubating
+class CdtIdePlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.apply(plugin: "native-binaries")
+        def metadataFileTasks = [addCreateProjectDescriptor(project), addCreateCprojectDescriptor(project)]
+
+        project.task("cleanCdt", type: Delete) {
+            delete metadataFileTasks*.outputs*.files
+        }
+
+        project.task("cdt", dependsOn: metadataFileTasks)
+    }
+
+    private addCreateProjectDescriptor(Project project) {
+        project.task("cdtProject", type: GenerateMetadataFileTask) {
+            inputFile = project.file(".project")
+            outputFile = project.file(".project")
+            factory { new ProjectDescriptor() }
+            onConfigure { new ProjectSettings(name: project.name).applyTo(it) }
+        }
+    }
+
+    private addCreateCprojectDescriptor(Project project) {
+        project.task("cdtCproject", type: GenerateMetadataFileTask) { task ->
+            
+            [project.executables, project.libraries]*.all { binary ->
+                if (binary.name == "main") {
+                    task.settings = new CprojectSettings(binary, project)
+                }
+            }
+            
+            doFirst {
+                if (task.settings == null) {
+                    throw new InvalidUserDataException("There is neither a main binary or library")
+                }
+            }
+            
+            inputs.files { task.settings.includeRoots }
+            inputFile = project.file(".cproject")
+            outputFile = project.file(".cproject")
+            factory { new CprojectDescriptor() }
+            onConfigure { descriptor ->
+                task.settings.applyTo(descriptor)
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/CprojectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/CprojectDescriptor.groovy
new file mode 100644
index 0000000..ce8903b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/CprojectDescriptor.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.model
+
+import org.gradle.api.Incubating
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+
+/**
+ * The actual .cproject descriptor file.
+ */
+ at Incubating
+class CprojectDescriptor extends XmlPersistableConfigurationObject {
+
+    private static final boolean LINUX_NOT_MACOS = true
+    
+    static public final String GNU_COMPILER_TOOL_ID_PREFIX = "cdt.managedbuild.tool.gnu.cpp.compiler"
+    static public final String GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX = "gnu.cpp.compiler.option.include.paths"
+
+    // linux
+    static public final String GNU_LINKER_TOOL_ID_PREFIX = LINUX_NOT_MACOS ? "cdt.managedbuild.tool.gnu.cpp.linker" : "cdt.managedbuild.tool.macosx.cpp.linker.macosx"
+    static public final String GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX = LINUX_NOT_MACOS ? "gnu.cpp.link.option.userobjs" : "macosx.cpp.link.option.userobjs"
+
+    CprojectDescriptor() {
+        super(new XmlTransformer())
+    }
+
+    protected String getDefaultResourceName() {
+        LINUX_NOT_MACOS ? 'defaultCproject-linux.xml' : 'defaultCproject-macos.xml'
+    }
+
+    NodeList getConfigurations() {
+        new NodeList(xml.storageModule.cconfiguration.storageModule.findAll { it. at moduleId == "cdtBuildSystem" }.collect { it.configuration[0] })
+    }
+
+    NodeList getRootToolChains() {
+        new NodeList(configurations.folderInfo.findAll { it. at resourcePath == "" }).toolChain
+    }
+
+    NodeList getRootCppCompilerTools() {
+        new NodeList(rootToolChains.tool.findAll { isGnuCompilerTool(it) })
+    }
+
+    NodeList getRootCppLinkerTools() {
+        new NodeList(rootToolChains.tool.findAll { isGnuLinkerTool(it) })
+    }
+
+    boolean isGnuCompilerTool(Node node) {
+        node.name() == "tool" && node. at id.startsWith(GNU_COMPILER_TOOL_ID_PREFIX)
+    }
+
+    boolean isGnuLinkerTool(Node node) {
+        node.name() == "tool" && node. at id.startsWith(GNU_LINKER_TOOL_ID_PREFIX)
+    }
+
+    Node getOrCreateIncludePathsOption(compilerToolNode) {
+        if (!isGnuCompilerTool(compilerToolNode)) {
+            throw new IllegalArgumentException("Arg must be a gnu compiler tool def, was $compilerToolNode")
+        }
+
+        def includePathsOption = compilerToolNode.option.find { it. at id.startsWith(GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX) }
+        if (!includePathsOption) {
+            includePathsOption = compilerToolNode.appendNode(
+                "option", [
+                    id: createId(GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX),
+                    superClass: GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX,
+                    valueType: "includePath"
+                ]
+            )
+        }
+
+        includePathsOption
+    }
+
+    Node getOrCreateLibsOption(linkerToolNode) {
+        if (!isGnuLinkerTool(linkerToolNode)) {
+            throw new IllegalArgumentException("Arg must be a gnu linker tool def, was $linkerToolNode")
+        }
+
+        def libsOption = linkerToolNode.option.find { it. at id.startsWith(GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX) }
+        if (!libsOption) {
+            libsOption = linkerToolNode.appendNode(
+                "option", [
+                    id: createId(GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX),
+                    superClass: GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX,
+                    valueType: "userObjs"
+                ]
+            )
+        }
+
+        libsOption
+    }
+
+    String createId(String prefix) {
+        prefix + "." + new java.text.SimpleDateFormat("yyMMddHHmmssS").format(new Date())
+    }
+
+    protected void store(Node xml) {
+        transformAction {
+            StringBuilder xmlString = it.asString()
+            xmlString.insert(xmlString.indexOf("\n") + 1, "<?fileVersion 4.0.0?>\n")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy
new file mode 100644
index 0000000..625a2fc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.model
+
+import org.gradle.api.Incubating
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.language.cpp.CppSourceSet
+import org.gradle.nativebinaries.*
+
+/**
+ * Exposes a more logical view of the actual .cproject descriptor file
+ */
+ at Incubating
+// TODO:DAZ I'm sure this is now broken
+class CprojectSettings {
+
+    ProjectNativeComponent binary
+    private final ConfigurableFileCollection includeRoots
+    private final ConfigurableFileCollection libs
+
+    CprojectSettings(ProjectNativeComponent binary, ProjectInternal project) {
+        this.binary = binary
+        includeRoots = project.files()
+        libs = project.files()
+
+        binary.source.withType(HeaderExportingSourceSet).all {
+            includeRoots.builtBy(it.exportedHeaders) // have to manually add because we use srcDirs in from, not the real collection
+            includeRoots.from(it.exportedHeaders.srcDirs)
+        }
+
+        binary.source.withType(CppSourceSet).all { sourceSet ->
+            sourceSet.libs.each { NativeDependencySet lib ->
+                this.libs.from(lib.linkFiles)
+                this.includeRoots.from(lib.includeRoots)
+            }
+        }
+    }
+
+    void applyTo(CprojectDescriptor descriptor) {
+        if (binary) {
+            applyBinaryTo(descriptor)
+        } else {
+            throw new IllegalStateException("no binary set")
+        }
+    }
+
+    private applyBinaryTo(CprojectDescriptor descriptor) {
+        descriptor.rootCppCompilerTools.each { compiler ->
+            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
+            new LinkedList(includePathsOption.children()).each { includePathsOption.remove(it) }
+            includeRoots.each { includeRoot ->
+                includePathsOption.appendNode("listOptionValue", [builtIn: "false", value: includeRoot.absolutePath])
+            }
+        }
+
+        descriptor.rootCppLinkerTools.each { linker ->
+            def libsOption = descriptor.getOrCreateLibsOption(linker)
+            new LinkedList(libsOption.children()).each { libsOption.remove(it) }
+            libs.each { lib ->
+                libsOption.appendNode("listOptionValue", [builtIn: "false", value: lib.absolutePath])
+            }
+        }
+
+        def extension = ""
+        def type 
+        if (binary instanceof Library) {
+            type = "org.eclipse.cdt.build.core.buildArtefactType.sharedLib"
+        } else if (binary instanceof Executable) {
+            type = "org.eclipse.cdt.build.core.buildArtefactType.exe"
+        } else {
+            throw new IllegalStateException("The binary $binary is of a type that we don't know about")
+        }
+        
+        descriptor.configurations.each { conf ->
+            conf. at buildArtefactType = type
+            conf. at artifactExtension = extension
+            def buildPropsPairs = conf. at buildProperties.split(",")
+            def buildProps = [:]
+            buildPropsPairs.each {
+                def parts = it.split("=", 2)
+                buildProps[parts[0]] = parts[1]
+            }
+            buildProps["org.eclipse.cdt.build.core.buildArtefactType"] = type
+            buildPropsPairs = buildProps.collect { k, v -> "$k=$v"}
+            conf. at buildProperties = buildPropsPairs.join(",")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/ProjectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/ProjectDescriptor.groovy
new file mode 100644
index 0000000..af499bc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/ProjectDescriptor.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.model
+
+import org.gradle.api.Incubating
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+
+/**
+ * The actual .project descriptor file.
+ */
+ at Incubating
+class ProjectDescriptor extends XmlPersistableConfigurationObject {
+
+    ProjectDescriptor() {
+        super(new XmlTransformer())
+    }
+
+    protected String getDefaultResourceName() {
+        'defaultProject.xml'
+    }
+
+    Node getOrCreate(String name) {
+        getOrCreate(xml, name)
+    }
+
+    Node findBuildCommand(Closure predicate) {
+        xml.buildSpec[0].buildCommand.find(predicate)
+    }
+
+    Node getOrCreate(Node parent, String name) {
+        def node = parent.get(name)
+        node ? node.first() : parent.appendNode(name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/ProjectSettings.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/ProjectSettings.groovy
new file mode 100644
index 0000000..922f2fc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/model/ProjectSettings.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.model
+
+import org.gradle.api.Incubating
+
+/**
+ * Gradle model element, the configurable parts of the .project file.
+ */
+ at Incubating
+class ProjectSettings {
+    String name
+
+    /**
+     * Apply this logical model to the physical descriptor
+     */
+    void applyTo(ProjectDescriptor descriptor) {
+        descriptor.getOrCreate("name").value = name
+
+        // not anywhere close to right at all, very hardcoded.
+        def genMakeBuilderBuildCommand = descriptor.findBuildCommand { it.name[0].text() == "org.eclipse.cdt.managedbuilder.core.genmakebuilder" }
+        if (genMakeBuilderBuildCommand) {
+            def dict = genMakeBuilderBuildCommand.arguments[0].dictionary.find { it.key[0].text() == "org.eclipse.cdt.make.core.buildLocation" }
+            if (dict) {
+                dict.value[0].value = "\${workspace_loc:/$name/Debug}"
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/tasks/GenerateMetadataFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/tasks/GenerateMetadataFileTask.groovy
new file mode 100644
index 0000000..06f051e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/cdt/tasks/GenerateMetadataFileTask.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.tasks
+
+import org.gradle.api.Incubating
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.internal.Factory
+import org.gradle.listener.ActionBroadcast
+import org.gradle.ide.cdt.model.CprojectSettings
+import org.gradle.plugins.ide.api.GeneratorTask
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator
+
+ at Incubating
+class GenerateMetadataFileTask<T extends PersistableConfigurationObject> extends GeneratorTask<T> {
+
+    Factory<T> factory
+    ActionBroadcast<T> configures = new ActionBroadcast<T>()
+    CprojectSettings settings
+
+    GenerateMetadataFileTask() {
+        generator = new PersistableConfigurationObjectGenerator() {
+            public create() {
+                GenerateMetadataFileTask.this.factory.create();
+            }
+
+            public void configure(object) {
+                GenerateMetadataFileTask.this.configures.execute(object);
+            }
+        }
+    }
+
+    void factory(Closure factory) {
+        this.factory = factory as Factory
+    }
+
+    void onConfigure(Closure configure) {
+        configures.add(new ClosureBackedAction(configure))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/ConfigFile.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/ConfigFile.java
new file mode 100644
index 0000000..8045354
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/ConfigFile.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+
+/**
+ * A configuration file generated for Visual Studio.
+ */
+ at Incubating
+public interface ConfigFile {
+    /**
+     * The location where this file will be generated.
+     */
+    File getLocation();
+
+    /**
+     * Specify the location where this file will be generated.
+     */
+    void setLocation(Object location);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/TextConfigFile.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/TextConfigFile.java
new file mode 100644
index 0000000..8599bb0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/TextConfigFile.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+
+/**
+ * A plain text config file generated for Visual Studio.
+ */
+ at Incubating
+public interface TextConfigFile extends ConfigFile {
+
+    /**
+     * Add an action that can manipulate the generated file content.
+     */
+    void withContent(Action<? super TextProvider> action);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/TextProvider.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/TextProvider.java
new file mode 100644
index 0000000..655198c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/TextProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Provides access to the content of a generated text file.
+ */
+ at Incubating
+public interface TextProvider {
+    /**
+     * The text content.
+     */
+    String getText();
+
+    /**
+     * Replace the content.
+     */
+    void setText(String value);
+
+    /**
+     * Get the content as a {@link StringBuilder}, permitting direct modification.
+     */
+    StringBuilder asBuilder();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioExtension.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioExtension.java
new file mode 100644
index 0000000..924869d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioExtension.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * The configuration for mapping a set of {@link org.gradle.nativebinaries.ProjectNativeComponent}s to a Visual Studio project.
+ */
+ at Incubating
+public interface VisualStudioExtension {
+    /**
+     * The {@link VisualStudioProject}s generated.
+     */
+    NamedDomainObjectSet<? extends VisualStudioProject> getProjects();
+
+    /**
+     * The {@link VisualStudioSolution}s generated.
+     */
+    NamedDomainObjectSet<? extends VisualStudioSolution> getSolutions();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioProject.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioProject.java
new file mode 100644
index 0000000..22c1726
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioProject.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.language.base.BuildableModelElement;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+
+/**
+ * A visual studio project, created from one or more {@link org.gradle.nativebinaries.NativeBinary} instances.
+ *
+ * <p/>
+ *
+ * The content and location of the generate project file can be modified by the supplied methods:
+ *
+ * <pre autoTested="true">
+ *  apply plugin: "visual-studio"
+ *  model {
+ *      visualStudio {
+ *          projects.all {
+ *              projectFile.location = "vs/${name}.vcxproj"
+ *              projectFile.withXml {
+ *                  asNode().appendNode('PropertyGroup', [Label: 'Custom'])
+ *                          .appendNode('ProjectDetails', "Project is named ${project.name}")
+ *              }
+ *          }
+ *      }
+ *  }
+ * </pre>
+ */
+ at Incubating
+public interface VisualStudioProject extends Named, BuildableModelElement {
+    /**
+     * The component that this project represents.
+     */
+    ProjectNativeComponent getComponent();
+
+    /**
+     * Configuration for the generated project file.
+     */
+    XmlConfigFile getProjectFile();
+
+    /**
+     * Configuration for the generated filters file.
+     */
+    XmlConfigFile getFiltersFile();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioSolution.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioSolution.java
new file mode 100644
index 0000000..9c62135
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/VisualStudioSolution.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.language.base.BuildableModelElement;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+
+import java.util.Set;
+
+/**
+ * A visual studio solution, representing one or more {@link org.gradle.nativebinaries.ProjectNativeBinary} instances
+ * from the same {@link ProjectNativeComponent}.
+ * <p/>
+ *
+ * The content and location of the generate solution file can be modified by the supplied methods:
+ *
+ * <pre autoTested="true">
+ *  apply plugin: "visual-studio"
+ *  model {
+ *      visualStudio {
+ *          solutions.all {
+ *              solutionFile.location = "vs/${name}.sln"
+ *              solutionFile.withContent { TextProvider content ->
+ *                  content.asBuilder().insert(0, "# GENERATED FILE: DO NOT EDIT\n")
+ *                  content.text = content.text.replaceAll("HideSolutionNode = FALSE", "HideSolutionNode = TRUE")
+ *              }
+ *          }
+ *      }
+ *  }
+ * </pre>
+ */
+ at Incubating
+public interface VisualStudioSolution extends Named, BuildableModelElement {
+    /**
+     * The set of projects included in this solution.
+     */
+    Set<VisualStudioProject> getProjects();
+
+    /**
+     * The component that this solution represents.
+     */
+    ProjectNativeComponent getComponent();
+
+    /**
+     * Configuration for the generated solution file.
+     */
+    TextConfigFile getSolutionFile();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/XmlConfigFile.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/XmlConfigFile.java
new file mode 100644
index 0000000..03c7305
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/XmlConfigFile.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.XmlProvider;
+
+/**
+ * An XML config file generated for Visual Studio.
+ */
+ at Incubating
+public interface XmlConfigFile extends ConfigFile {
+
+    /**
+     * Add an action that can manipulate the generated file content.
+     */
+    void withXml(Action<? super XmlProvider> action);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioExtension.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioExtension.java
new file mode 100644
index 0000000..3a99def
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioExtension.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.internal;
+
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.ide.visualstudio.VisualStudioExtension;
+import org.gradle.ide.visualstudio.VisualStudioProject;
+import org.gradle.ide.visualstudio.VisualStudioSolution;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.internal.resolve.ProjectLocator;
+
+public class DefaultVisualStudioExtension implements VisualStudioExtension {
+    private final VisualStudioProjectRegistry projectRegistry;
+    private final VisualStudioSolutionRegistry solutionRegistry;
+
+    public DefaultVisualStudioExtension(Instantiator instantiator, ProjectLocator projectLocator, FileResolver fileResolver) {
+        VisualStudioProjectMapper projectMapper = new VisualStudioProjectMapper();
+        projectRegistry = new VisualStudioProjectRegistry(fileResolver, projectMapper, instantiator);
+        VisualStudioProjectResolver projectResolver = new VisualStudioProjectResolver(projectLocator);
+        solutionRegistry = new VisualStudioSolutionRegistry(fileResolver, projectResolver, instantiator);
+    }
+
+    public NamedDomainObjectSet<? extends VisualStudioProject> getProjects() {
+        return projectRegistry;
+    }
+
+    public VisualStudioProjectRegistry getProjectRegistry() {
+        return projectRegistry;
+    }
+
+    public NamedDomainObjectSet<? extends VisualStudioSolution> getSolutions() {
+        return solutionRegistry;
+    }
+
+    public VisualStudioSolutionRegistry getSolutionRegistry() {
+        return solutionRegistry;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioProject.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioProject.groovy
new file mode 100644
index 0000000..929e256
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioProject.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+import org.gradle.api.Action
+import org.gradle.api.XmlProvider
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.ide.visualstudio.VisualStudioProject
+import org.gradle.ide.visualstudio.XmlConfigFile
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.base.internal.AbstractBuildableModelElement
+import org.gradle.language.rc.WindowsResourceSet
+import org.gradle.nativebinaries.NativeBinary
+import org.gradle.nativebinaries.ProjectNativeBinary
+import org.gradle.nativebinaries.ProjectNativeComponent
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal
+import org.gradle.util.CollectionUtils
+/**
+ * A VisualStudio project represents a set of binaries for a component that may vary in build type and target platform.
+ */
+class DefaultVisualStudioProject extends AbstractBuildableModelElement implements VisualStudioProject {
+    private final String name
+    private final DefaultConfigFile projectFile
+    private final DefaultConfigFile filtersFile
+    private final ProjectNativeComponent component
+    private final List<File> additionalFiles = []
+    final Set<LanguageSourceSet> sources = new LinkedHashSet<LanguageSourceSet>()
+    private final Map<NativeBinary, VisualStudioProjectConfiguration> configurations = [:]
+
+    DefaultVisualStudioProject(String name, ProjectNativeComponent component, FileResolver fileResolver, Instantiator instantiator) {
+        this.name = name
+        this.component = component
+        projectFile = instantiator.newInstance(DefaultConfigFile, fileResolver, "${name}.vcxproj" as String)
+        filtersFile = instantiator.newInstance(DefaultConfigFile, fileResolver, "${name}.vcxproj.filters" as String)
+    }
+
+    String getName() {
+        return name
+    }
+
+    DefaultConfigFile getProjectFile() {
+        return projectFile
+    }
+
+    DefaultConfigFile getFiltersFile() {
+        return filtersFile
+    }
+
+    ProjectNativeComponent getComponent() {
+        return component
+    }
+
+    void addSourceFile(File sourceFile) {
+        additionalFiles << sourceFile
+    }
+
+    String getUuid() {
+        String projectPath = (component as ProjectNativeComponentInternal).projectPath
+        String vsComponentPath = "${projectPath}:${name}"
+        return '{' + UUID.nameUUIDFromBytes(vsComponentPath.bytes).toString().toUpperCase() + '}'
+    }
+
+    void source(Collection<LanguageSourceSet> sources) {
+        this.sources.addAll(sources)
+        builtBy(sources)
+    }
+
+    List<File> getSourceFiles() {
+        def allSource = [] as Set
+        allSource.addAll additionalFiles
+        sources.each { LanguageSourceSet sourceSet ->
+            if (!(sourceSet instanceof WindowsResourceSet)) {
+                allSource.addAll sourceSet.source.files
+            }
+        }
+        return allSource as List
+    }
+
+    List<File> getResourceFiles() {
+        def allResources = [] as Set
+        sources.each { LanguageSourceSet sourceSet ->
+            if (sourceSet instanceof WindowsResourceSet) {
+                allResources.addAll sourceSet.source.files
+            }
+        }
+        return allResources as List
+    }
+
+    List<File> getHeaderFiles() {
+        def allHeaders = [] as Set
+        sources.each { LanguageSourceSet sourceSet ->
+            if (sourceSet instanceof HeaderExportingSourceSet) {
+                allHeaders.addAll sourceSet.exportedHeaders.files
+                allHeaders.addAll sourceSet.implicitHeaders.files
+            }
+        }
+        return allHeaders as List
+    }
+
+    List<VisualStudioProjectConfiguration> getConfigurations() {
+        return CollectionUtils.toList(configurations.values())
+    }
+
+    void addConfiguration(ProjectNativeBinary nativeBinary, VisualStudioProjectConfiguration configuration) {
+        configurations[nativeBinary] = configuration
+        source nativeBinary.source
+    }
+
+    VisualStudioProjectConfiguration getConfiguration(ProjectNativeBinary nativeBinary) {
+        return configurations[nativeBinary]
+    }
+
+    public static class DefaultConfigFile implements XmlConfigFile {
+        private final List<Action<? super XmlProvider>> actions = new ArrayList<Action<? super XmlProvider>>();
+        private final FileResolver fileResolver
+        private Object location
+
+        DefaultConfigFile(FileResolver fileResolver, String defaultLocation) {
+            this.fileResolver = fileResolver
+            this.location = defaultLocation
+        }
+
+        File getLocation() {
+            return fileResolver.resolve(location)
+        }
+
+        void setLocation(Object location) {
+            this.location = location
+        }
+
+        void withXml(Action<? super XmlProvider> action) {
+            actions.add(action)
+        }
+
+        List<Action<? super XmlProvider>> getXmlActions() {
+            return actions
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioSolution.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioSolution.groovy
new file mode 100644
index 0000000..634c497
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioSolution.groovy
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.ide.visualstudio.TextConfigFile
+import org.gradle.ide.visualstudio.TextProvider
+import org.gradle.ide.visualstudio.VisualStudioProject
+import org.gradle.ide.visualstudio.VisualStudioSolution
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.internal.AbstractBuildableModelElement
+import org.gradle.nativebinaries.LibraryBinary
+import org.gradle.nativebinaries.ProjectNativeComponent
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+
+class DefaultVisualStudioSolution extends AbstractBuildableModelElement implements VisualStudioSolution {
+    final DefaultVisualStudioProject rootProject
+    private final String name
+    private final SolutionFile solutionFile
+    private final VisualStudioProjectResolver vsProjectResolver
+
+    DefaultVisualStudioSolution(DefaultVisualStudioProject rootProject, FileResolver fileResolver,
+                                VisualStudioProjectResolver vsProjectResolver, Instantiator instantiator) {
+        this.rootProject = rootProject
+        this.name = rootProject.name
+        this.vsProjectResolver = vsProjectResolver
+        this.solutionFile = instantiator.newInstance(SolutionFile, fileResolver, "${name}.sln" as String)
+    }
+
+    String getName() {
+        return name
+    }
+
+    SolutionFile getSolutionFile() {
+        return solutionFile
+    }
+
+    ProjectNativeComponent getComponent() {
+        return rootProject.component
+    }
+
+    Set<VisualStudioProject> getProjects() {
+        def projects = [] as Set
+        solutionConfigurations.each { solutionConfig ->
+            getProjectConfigurations(solutionConfig).each { projectConfig ->
+                projects << projectConfig.project
+            }
+        }
+        return projects
+    }
+
+    List<VisualStudioProjectConfiguration> getSolutionConfigurations() {
+        return rootProject.configurations
+    }
+
+    List<VisualStudioProjectConfiguration> getProjectConfigurations(VisualStudioProjectConfiguration solutionConfiguration) {
+        def configurations = [] as Set
+        configurations << solutionConfiguration
+        addDependentConfigurations(configurations, solutionConfiguration)
+        return configurations as List
+    }
+
+    private void addDependentConfigurations(Set configurations, VisualStudioProjectConfiguration configuration) {
+        for (LibraryBinary library : configuration.binary.dependentBinaries) {
+            if (library instanceof ProjectNativeBinaryInternal) {
+                VisualStudioProjectConfiguration libraryConfiguration = vsProjectResolver.lookupProjectConfiguration(library);
+                if (configurations.add(libraryConfiguration)) {
+                    addDependentConfigurations(configurations, libraryConfiguration)
+                }
+            }
+        }
+    }
+
+    static class SolutionFile implements TextConfigFile {
+        private final List<Action<? super TextProvider>> actions = new ArrayList<Action<? super TextProvider>>();
+        private final FileResolver fileResolver
+        private Object location
+
+        SolutionFile(FileResolver fileResolver, String defaultLocation) {
+            this.fileResolver = fileResolver
+            this.location = defaultLocation
+        }
+
+        File getLocation() {
+            return fileResolver.resolve(location)
+        }
+
+        void setLocation(Object location) {
+            this.location = location
+        }
+
+        void withContent(Action<? super TextProvider> action) {
+            actions.add(action)
+        }
+
+        List<Action<? super TextProvider>> getTextActions() {
+            return actions
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/ExecutableVisualStudioProjectConfiguration.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/ExecutableVisualStudioProjectConfiguration.groovy
new file mode 100644
index 0000000..570a69c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/ExecutableVisualStudioProjectConfiguration.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+
+import org.gradle.nativebinaries.ProjectNativeBinary
+import org.gradle.nativebinaries.tasks.InstallExecutable
+
+class ExecutableVisualStudioProjectConfiguration extends VisualStudioProjectConfiguration {
+    ExecutableVisualStudioProjectConfiguration(DefaultVisualStudioProject vsProject, String configurationName, String platformName, ProjectNativeBinary binary) {
+        super(vsProject, configurationName, platformName, binary)
+    }
+
+    @Override
+    String getBuildTask() {
+        InstallExecutable installTask = getInstallTask()
+        return installTask == null ? super.getBuildTask() : installTask.path
+    }
+
+    @Override
+    File getOutputFile() {
+        InstallExecutable installTask = getInstallTask()
+        if (installTask == null) {
+            return super.getOutputFile()
+        } else {
+            return new File(installTask.destinationDir, "lib/" + installTask.executable.name)
+        }
+    }
+
+    private InstallExecutable getInstallTask() {
+        return binary.tasks.withType(InstallExecutable).iterator().next()
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectConfiguration.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectConfiguration.groovy
new file mode 100644
index 0000000..7cab44c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectConfiguration.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.nativebinaries.ProjectNativeBinary
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.language.PreprocessingTool
+import org.gradle.nativebinaries.toolchain.internal.MacroArgsConverter
+
+class VisualStudioProjectConfiguration {
+    private final DefaultVisualStudioProject vsProject
+    private final String configurationName
+    private final String platformName
+    final ProjectNativeBinaryInternal binary
+    final String type = "Makefile"
+
+    VisualStudioProjectConfiguration(DefaultVisualStudioProject vsProject, String configurationName, String platformName, ProjectNativeBinary binary) {
+        this.vsProject = vsProject
+        this.configurationName = configurationName
+        this.platformName = platformName
+        this.binary = binary as ProjectNativeBinaryInternal
+    }
+
+    String getName() {
+        return "${configurationName}|${platformName}"
+    }
+
+    String getConfigurationName() {
+        return configurationName
+    }
+
+    String getPlatformName() {
+        return platformName
+    }
+
+    String getBuildTask() {
+        return taskPath(binary.name)
+    }
+
+    String getCleanTask() {
+        return taskPath("clean")
+    }
+
+    // TODO:DAZ Make it easier to find the lifecycle task and clean task for a binary and use task.path.
+    private String taskPath(String taskName) {
+        String projectPath = binary.component.projectPath
+        if (projectPath == ":") {
+            return ":${taskName}"
+        }
+        return "${projectPath}:${taskName}"
+    }
+
+    File getOutputFile() {
+        return binary.primaryOutput
+    }
+
+    boolean isDebug() {
+        return binary.buildType.name != 'release'
+    }
+
+    List<String> getCompilerDefines() {
+        List<String> defines = []
+        defines.addAll getDefines('cCompiler')
+        defines.addAll getDefines('cppCompiler')
+        defines.addAll getDefines('rcCompiler')
+        return defines
+    }
+
+    private List<String> getDefines(String tool) {
+        PreprocessingTool rcCompiler = findCompiler(tool)
+        return rcCompiler == null ? [] : new MacroArgsConverter().transform(rcCompiler.macros)
+    }
+
+    private PreprocessingTool findCompiler(String tool) {
+        ExtensionAware extendedBinary = binary as ExtensionAware;
+        return extendedBinary.extensions.findByName(tool) as PreprocessingTool
+    }
+
+    List<File> getIncludePaths() {
+        def includes = [] as Set
+        binary.source.withType(HeaderExportingSourceSet).each { HeaderExportingSourceSet sourceSet ->
+            includes.addAll sourceSet.exportedHeaders.srcDirs
+        }
+        binary.libs*.includeRoots.each {
+            includes.addAll it.files
+        }
+        return includes as List
+    }
+
+    DefaultVisualStudioProject getProject() {
+        return vsProject
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectMapper.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectMapper.java
new file mode 100644
index 0000000..6cdd7e5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectMapper.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.nativebinaries.ExecutableBinary;
+import org.gradle.nativebinaries.LibraryBinary;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+import org.gradle.nativebinaries.SharedLibraryBinary;
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal;
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal;
+
+import java.util.List;
+
+public class VisualStudioProjectMapper {
+
+    public ProjectConfigurationNames mapToConfiguration(ProjectNativeBinary nativeBinary) {
+        String projectName = projectPrefix(nativeBinary) + componentName(nativeBinary) + projectSuffix(nativeBinary);
+        String configurationName = getConfigurationName(nativeBinary);
+        return new ProjectConfigurationNames(projectName, configurationName, "Win32");
+    }
+
+    private String getConfigurationName(ProjectNativeBinary nativeBinary) {
+        List<String> dimensions = ((ProjectNativeBinaryInternal) nativeBinary).getNamingScheme().getVariantDimensions();
+        if (dimensions.isEmpty()) {
+            return nativeBinary.getBuildType().getName();
+        }
+        return makeName(dimensions);
+    }
+
+    private String projectPrefix(ProjectNativeBinary nativeBinary) {
+        ProjectNativeComponentInternal component = (ProjectNativeComponentInternal) nativeBinary.getComponent();
+        String projectPath = component.getProjectPath();
+        if (":".equals(projectPath)) {
+            return "";
+        }
+        return projectPath.substring(1).replace(":", "_") + "_";
+    }
+
+    private String componentName(ProjectNativeBinary nativeBinary) {
+        return nativeBinary.getComponent().getName();
+    }
+
+    private String projectSuffix(ProjectNativeBinary nativeBinary) {
+        return nativeBinary instanceof SharedLibraryBinary ? "Dll"
+                : nativeBinary instanceof LibraryBinary ? "Lib"
+                : nativeBinary instanceof ExecutableBinary ? "Exe"
+                : "";
+    }
+
+    private static String makeName(Iterable<String> components) {
+        StringBuilder builder = new StringBuilder();
+        for (String component : components) {
+            if (component != null && component.length() > 0) {
+                if (builder.length() == 0) {
+                    builder.append(component);
+                } else {
+                    builder.append(StringUtils.capitalize(component));
+                }
+            }
+        }
+        return builder.toString();
+    }
+
+    static class ProjectConfigurationNames {
+        public final String project;
+        public final String configuration;
+        public final String platform;
+
+        ProjectConfigurationNames(String project, String configuration, String platform) {
+            this.project = project;
+            this.configuration = configuration;
+            this.platform = platform;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectRegistry.java
new file mode 100644
index 0000000..192fedf
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectRegistry.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal;
+
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.*;
+
+public class VisualStudioProjectRegistry extends DefaultNamedDomainObjectSet<DefaultVisualStudioProject> {
+    private final FileResolver fileResolver;
+    private final VisualStudioProjectMapper projectMapper;
+
+    public VisualStudioProjectRegistry(FileResolver fileResolver, VisualStudioProjectMapper projectMapper, Instantiator instantiator) {
+        super(DefaultVisualStudioProject.class, instantiator);
+        this.fileResolver = fileResolver;
+        this.projectMapper = projectMapper;
+    }
+
+    public VisualStudioProjectConfiguration getProjectConfiguration(ProjectNativeBinary nativeBinary) {
+        String projectName = projectName(nativeBinary);
+        return getByName(projectName).getConfiguration(nativeBinary);
+    }
+
+    public VisualStudioProjectConfiguration addProjectConfiguration(ProjectNativeBinary nativeBinary) {
+        VisualStudioProjectMapper.ProjectConfigurationNames names = projectMapper.mapToConfiguration(nativeBinary);
+        DefaultVisualStudioProject project = getOrCreateProject(nativeBinary.getComponent(), names.project);
+        VisualStudioProjectConfiguration configuration = createVisualStudioProjectConfiguration(nativeBinary, project, names.configuration, names.platform);
+        project.addConfiguration(nativeBinary, configuration);
+        return configuration;
+    }
+
+    private VisualStudioProjectConfiguration createVisualStudioProjectConfiguration(ProjectNativeBinary nativeBinary, DefaultVisualStudioProject project, String configuration, String platform) {
+        Class<? extends VisualStudioProjectConfiguration> type =
+                nativeBinary instanceof ExecutableBinary ? ExecutableVisualStudioProjectConfiguration.class : VisualStudioProjectConfiguration.class;
+        return getInstantiator().newInstance(type, project, configuration, platform, nativeBinary);
+    }
+
+    private DefaultVisualStudioProject getOrCreateProject(ProjectNativeComponent nativeComponent, String projectName) {
+        DefaultVisualStudioProject vsProject = findByName(projectName);
+        if (vsProject == null) {
+            vsProject = getInstantiator().newInstance(DefaultVisualStudioProject.class, projectName, nativeComponent, fileResolver, getInstantiator());
+            add(vsProject);
+        }
+        return vsProject;
+    }
+
+    private String projectName(ProjectNativeBinary nativeBinary) {
+        return projectMapper.mapToConfiguration(nativeBinary).project;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectResolver.java
new file mode 100644
index 0000000..c2d82d9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectResolver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal;
+import org.gradle.nativebinaries.internal.resolve.ProjectLocator;
+
+public class VisualStudioProjectResolver {
+    private final ProjectLocator projectLocator;
+
+    public VisualStudioProjectResolver(ProjectLocator projectLocator) {
+        this.projectLocator = projectLocator;
+    }
+
+    public VisualStudioProjectConfiguration lookupProjectConfiguration(ProjectNativeBinary nativeBinary) {
+        // Looks in the correct project registry for this binary
+        ProjectInternal componentProject = getComponentProject(nativeBinary);
+        DefaultVisualStudioExtension visualStudioExtension = componentProject.getModelRegistry().get("visualStudio", DefaultVisualStudioExtension.class);
+        VisualStudioProjectRegistry projectRegistry = visualStudioExtension.getProjectRegistry();
+        return projectRegistry.getProjectConfiguration(nativeBinary);
+    }
+
+    private ProjectInternal getComponentProject(ProjectNativeBinary nativeBinary) {
+        String projectPath = ((ProjectNativeComponentInternal) nativeBinary.getComponent()).getProjectPath();
+        return projectLocator.locateProject(projectPath);
+    }
+}
+
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioSolutionRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioSolutionRegistry.java
new file mode 100644
index 0000000..52d55b9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/VisualStudioSolutionRegistry.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.internal;
+
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+
+public class VisualStudioSolutionRegistry extends DefaultNamedDomainObjectSet<DefaultVisualStudioSolution> {
+    private final FileResolver fileResolver;
+    private final VisualStudioProjectResolver projectResolver;
+
+    public VisualStudioSolutionRegistry(FileResolver fileResolver, VisualStudioProjectResolver projectResolver, Instantiator instantiator) {
+        super(DefaultVisualStudioSolution.class, instantiator);
+        this.fileResolver = fileResolver;
+        this.projectResolver = projectResolver;
+    }
+
+    public void addSolution(DefaultVisualStudioProject visualStudioProject) {
+        for (DefaultVisualStudioSolution solution : this) {
+            if (solution.getRootProject() == visualStudioProject) {
+                return;
+            }
+        }
+
+        DefaultVisualStudioSolution solution = getInstantiator().newInstance(
+                DefaultVisualStudioSolution.class, visualStudioProject, fileResolver, projectResolver, getInstantiator());
+        add(solution);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/rules/CreateVisualStudioModel.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/rules/CreateVisualStudioModel.java
new file mode 100644
index 0000000..0a1fdfa
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/rules/CreateVisualStudioModel.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.internal.rules;
+
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioExtension;
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioProject;
+import org.gradle.ide.visualstudio.internal.VisualStudioProjectConfiguration;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.model.ModelRule;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+
+public class CreateVisualStudioModel extends ModelRule {
+    @SuppressWarnings("UnusedDeclaration")
+    public void createVisualStudioModelForBinaries(DefaultVisualStudioExtension visualStudioExtension, BinaryContainer binaryContainer) {
+        for (ProjectNativeBinary binary : binaryContainer.withType(ProjectNativeBinary.class)) {
+            VisualStudioProjectConfiguration configuration = visualStudioExtension.getProjectRegistry().addProjectConfiguration(binary);
+
+            // Only create a solution if one of the binaries is buildable
+            if (binary.isBuildable()) {
+                DefaultVisualStudioProject visualStudioProject = configuration.getProject();
+                visualStudioExtension.getSolutionRegistry().addSolution(visualStudioProject);
+            }
+        }
+    }
+}
+
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/rules/CreateVisualStudioTasks.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/rules/CreateVisualStudioTasks.java
new file mode 100644
index 0000000..2614100
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/internal/rules/CreateVisualStudioTasks.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.internal.rules;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Delete;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.ide.visualstudio.VisualStudioExtension;
+import org.gradle.ide.visualstudio.VisualStudioProject;
+import org.gradle.ide.visualstudio.VisualStudioSolution;
+import org.gradle.ide.visualstudio.tasks.GenerateFiltersFileTask;
+import org.gradle.ide.visualstudio.tasks.GenerateProjectFileTask;
+import org.gradle.ide.visualstudio.tasks.GenerateSolutionFileTask;
+import org.gradle.model.ModelRule;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+
+public class CreateVisualStudioTasks extends ModelRule {
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void createTasksForVisualStudio(TaskContainer tasks, VisualStudioExtension visualStudioExtension) {
+        for (VisualStudioProject vsProject : visualStudioExtension.getProjects()) {
+            vsProject.builtBy(createProjectsFileTask(tasks, vsProject));
+            vsProject.builtBy(createFiltersFileTask(tasks, vsProject));
+        }
+
+        for (VisualStudioSolution vsSolution : visualStudioExtension.getSolutions()) {
+            Task solutionTask = tasks.create(vsSolution.getName() + "VisualStudio");
+            solutionTask.setDescription(String.format("Generates the '%s' Visual Studio solution file.", vsSolution.getName()));
+            vsSolution.setLifecycleTask(solutionTask);
+            vsSolution.builtBy(createSolutionTask(tasks, vsSolution));
+
+            // Lifecycle task for component
+            ProjectNativeComponent component = vsSolution.getComponent();
+            Task lifecycleTask = tasks.maybeCreate(component.getName() + "VisualStudio");
+            lifecycleTask.dependsOn(vsSolution);
+            lifecycleTask.setGroup("IDE");
+            lifecycleTask.setDescription(String.format("Generates the Visual Studio solution for %s.", component));
+        }
+
+        addCleanTask(tasks);
+    }
+
+    private void addCleanTask(TaskContainer tasks) {
+        Delete cleanTask = tasks.create("cleanVisualStudio", Delete.class);
+        for (Task task : tasks.withType(GenerateSolutionFileTask.class)) {
+            cleanTask.delete(task.getOutputs().getFiles());
+        }
+        for (Task task : tasks.withType(GenerateFiltersFileTask.class)) {
+            cleanTask.delete(task.getOutputs().getFiles());
+        }
+        for (Task task : tasks.withType(GenerateProjectFileTask.class)) {
+            cleanTask.delete(task.getOutputs().getFiles());
+        }
+        cleanTask.setGroup("IDE");
+        cleanTask.setDescription("Removes all generated Visual Studio project and solution files");
+    }
+
+    private Task createSolutionTask(TaskContainer tasks, VisualStudioSolution solution) {
+        GenerateSolutionFileTask solutionFileTask = tasks.create(solution.getName() + "VisualStudioSolution", GenerateSolutionFileTask.class);
+        solutionFileTask.setVisualStudioSolution(solution);
+        return solutionFileTask;
+    }
+
+    private Task createProjectsFileTask(TaskContainer tasks, VisualStudioProject vsProject) {
+        GenerateProjectFileTask task = tasks.create(vsProject.getName() + "VisualStudioProject", GenerateProjectFileTask.class);
+        task.setVisualStudioProject(vsProject);
+        task.initGradleCommand();
+        return task;
+    }
+
+    private Task createFiltersFileTask(TaskContainer tasks, VisualStudioProject vsProject) {
+        GenerateFiltersFileTask task = tasks.create(vsProject.getName() + "VisualStudioFilters", GenerateFiltersFileTask.class);
+        task.setVisualStudioProject(vsProject);
+        return task;
+    }
+}
+
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/package-info.java
new file mode 100644
index 0000000..d21011d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for visual studio.
+ */
+package org.gradle.ide.visualstudio;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/plugins/VisualStudioPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/plugins/VisualStudioPlugin.groovy
new file mode 100644
index 0000000..5535b2a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/plugins/VisualStudioPlugin.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.ide.visualstudio.VisualStudioExtension
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioExtension
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioProject
+import org.gradle.ide.visualstudio.internal.rules.CreateVisualStudioModel
+import org.gradle.ide.visualstudio.internal.rules.CreateVisualStudioTasks
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.model.ModelRules
+import org.gradle.nativebinaries.internal.resolve.ProjectLocator
+import org.gradle.nativebinaries.plugins.NativeBinariesModelPlugin
+
+import javax.inject.Inject
+
+ at Incubating
+class VisualStudioPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator
+    private final ModelRules modelRules
+    private final ProjectLocator projectLocator
+    private final FileResolver fileResolver
+
+    @Inject
+    VisualStudioPlugin(Instantiator instantiator, ModelRules modelRules, ProjectLocator projectLocator, FileResolver fileResolver) {
+        this.instantiator = instantiator
+        this.modelRules = modelRules
+        this.projectLocator = projectLocator
+        this.fileResolver = fileResolver
+    }
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesModelPlugin)
+
+        modelRules.register("visualStudio", instantiator.newInstance(DefaultVisualStudioExtension, instantiator, projectLocator, fileResolver))
+        modelRules.config("visualStudio", new IncludeBuildFileInProject(project))
+        modelRules.rule(new CreateVisualStudioModel())
+        modelRules.rule(new CreateVisualStudioTasks())
+    }
+
+    private class IncludeBuildFileInProject implements Action<VisualStudioExtension> {
+        private final ProjectInternal project;
+
+        IncludeBuildFileInProject(ProjectInternal project) {
+            this.project = project
+        }
+
+        void execute(VisualStudioExtension extension) {
+            extension.projects.all {
+                if (project.getBuildFile() != null) {
+                    ((DefaultVisualStudioProject) it).addSourceFile(project.getBuildFile())
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateFiltersFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateFiltersFileTask.groovy
new file mode 100644
index 0000000..d1640eb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateFiltersFileTask.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.tasks
+import org.gradle.api.Incubating
+import org.gradle.ide.visualstudio.VisualStudioProject
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioProject
+import org.gradle.ide.visualstudio.tasks.internal.RelativeFileNameTransformer
+import org.gradle.ide.visualstudio.tasks.internal.VisualStudioFiltersFile
+import org.gradle.plugins.ide.api.XmlGeneratorTask
+
+ at Incubating
+class GenerateFiltersFileTask extends XmlGeneratorTask<VisualStudioFiltersFile> {
+    private DefaultVisualStudioProject visualStudioProject
+
+    void setVisualStudioProject(VisualStudioProject vsProject) {
+        this.visualStudioProject = vsProject as DefaultVisualStudioProject
+    }
+
+    VisualStudioProject getVisualStudioProject() {
+        return visualStudioProject
+    }
+
+    @Override
+    File getInputFile() {
+        return null
+    }
+
+    @Override
+    File getOutputFile() {
+        return visualStudioProject.filtersFile.location
+    }
+
+    @Override
+    protected void configure(VisualStudioFiltersFile filtersFile) {
+        DefaultVisualStudioProject vsProject = visualStudioProject
+        vsProject.sourceFiles.each {
+            filtersFile.addSource(it)
+        }
+        vsProject.headerFiles.each {
+            filtersFile.addHeader(it)
+        }
+
+        vsProject.filtersFile.xmlActions.each {
+            xmlTransformer.addAction(it)
+        }
+    }
+
+    @Override
+    protected VisualStudioFiltersFile create() {
+        return new VisualStudioFiltersFile(xmlTransformer, RelativeFileNameTransformer.forFile(project.rootDir, visualStudioProject.filtersFile.location))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateProjectFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateProjectFileTask.groovy
new file mode 100644
index 0000000..a7922c4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateProjectFileTask.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.tasks
+import org.gradle.api.Incubating
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+import org.gradle.ide.visualstudio.VisualStudioProject
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioProject
+import org.gradle.ide.visualstudio.tasks.internal.RelativeFileNameTransformer
+import org.gradle.ide.visualstudio.tasks.internal.VisualStudioProjectFile
+import org.gradle.plugins.ide.api.XmlGeneratorTask
+
+ at Incubating
+class GenerateProjectFileTask extends XmlGeneratorTask<VisualStudioProjectFile> {
+    private DefaultVisualStudioProject visualStudioProject
+
+    @Input
+    String gradleExe
+
+    @Input @Optional
+    String gradleArgs
+
+    void initGradleCommand() {
+        final File gradlew = project.getRootProject().file("gradlew.bat");
+        conventionMapping.map("gradleExe") {
+            def rootDir = transformer.transform(project.rootDir)
+            def args = ""
+            if (rootDir != ".") {
+                args = " -p \"${rootDir}\""
+            }
+            if (gradlew.isFile()) {
+                return transformer.transform(gradlew) + args
+            }
+            return "gradle" + args
+        }
+    }
+
+    def getTransformer() {
+        return RelativeFileNameTransformer.forFile(project.rootDir, visualStudioProject.projectFile.location)
+    }
+
+    void setVisualStudioProject(VisualStudioProject vsProject) {
+        this.visualStudioProject = vsProject as DefaultVisualStudioProject
+    }
+
+    VisualStudioProject getVisualStudioProject() {
+        return visualStudioProject
+    }
+
+    @Override
+    File getInputFile() {
+        return null
+    }
+
+    @Override
+    File getOutputFile() {
+        return visualStudioProject.projectFile.location
+    }
+
+    @Override
+    protected VisualStudioProjectFile create() {
+        return new VisualStudioProjectFile(xmlTransformer, transformer)
+    }
+
+    @Override
+    protected void configure(VisualStudioProjectFile projectFile) {
+        DefaultVisualStudioProject vsProject = visualStudioProject
+
+        projectFile.gradleCommand = buildGradleCommand()
+        projectFile.setProjectUuid(vsProject.uuid)
+        vsProject.sourceFiles.each {
+            projectFile.addSourceFile(it)
+        }
+        vsProject.resourceFiles.each {
+            projectFile.addResource(it)
+        }
+        vsProject.headerFiles.each {
+            projectFile.addHeaderFile(it)
+        }
+
+        vsProject.configurations.each {
+            projectFile.addConfiguration(it)
+        }
+
+        vsProject.projectFile.xmlActions.each {
+            xmlTransformer.addAction(it)
+        }
+    }
+
+    private buildGradleCommand() {
+        String exe = getGradleExe()
+        String args = getGradleArgs()
+        if (args == null || args.trim().length() == 0) {
+            return exe
+        } else {
+            return exe + " " + args.trim()
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateSolutionFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateSolutionFileTask.groovy
new file mode 100644
index 0000000..47b6670
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/GenerateSolutionFileTask.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.tasks
+import org.gradle.api.Incubating
+import org.gradle.ide.visualstudio.VisualStudioSolution
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioSolution
+import org.gradle.ide.visualstudio.tasks.internal.VisualStudioSolutionFile
+import org.gradle.plugins.ide.api.GeneratorTask
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator
+
+ at Incubating
+class GenerateSolutionFileTask extends GeneratorTask<VisualStudioSolutionFile> {
+    private DefaultVisualStudioSolution solution
+
+    GenerateSolutionFileTask() {
+        generator = new ConfigurationObjectGenerator();
+    }
+
+    void setVisualStudioSolution(VisualStudioSolution solution) {
+        this.solution = solution as DefaultVisualStudioSolution
+
+        dependsOn {
+            this.solution.projects
+        }
+    }
+
+    VisualStudioSolution getSolution() {
+        return solution
+    }
+
+    @Override
+    File getInputFile() {
+        return null
+    }
+
+    @Override
+    File getOutputFile() {
+        return this.solution.solutionFile.location
+    }
+
+    private class ConfigurationObjectGenerator extends PersistableConfigurationObjectGenerator<VisualStudioSolutionFile> {
+        public VisualStudioSolutionFile create() {
+            return new VisualStudioSolutionFile()
+        }
+
+        public void configure(VisualStudioSolutionFile solutionFile) {
+            DefaultVisualStudioSolution solution = getSolution() as DefaultVisualStudioSolution
+            solutionFile.setMainProject(solution.rootProject)
+            solution.solutionConfigurations.each { solutionConfig ->
+                solutionFile.addSolutionConfiguration(solutionConfig.name, solution.getProjectConfigurations(solutionConfig))
+            }
+
+            solution.solutionFile.textActions.each {
+                solutionFile.actions << it
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/AbsoluteFileNameTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/AbsoluteFileNameTransformer.java
new file mode 100644
index 0000000..e3edc00
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/AbsoluteFileNameTransformer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.tasks.internal;
+
+import org.gradle.api.Transformer;
+
+import java.io.File;
+
+public class AbsoluteFileNameTransformer implements Transformer<String, File> {
+    public String transform(File file) {
+        return file.getAbsolutePath();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/RelativeFileNameTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/RelativeFileNameTransformer.java
new file mode 100644
index 0000000..1530c4d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/RelativeFileNameTransformer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal;
+
+import com.google.common.base.Joiner;
+import org.gradle.api.Transformer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class RelativeFileNameTransformer implements Transformer<String, File> {
+    private final File rootDir;
+    private final File currentDir;
+
+    private RelativeFileNameTransformer(File rootDir, File currentDir) {
+        this.rootDir = rootDir;
+        this.currentDir = currentDir;
+    }
+
+    public static Transformer<String, File> forFile(File rootDir, File relativeFile) {
+        return new RelativeFileNameTransformer(rootDir, relativeFile.getParentFile());
+    }
+
+    public static Transformer<String, File> forDirectory(File rootDir, File currentDirectory) {
+        return new RelativeFileNameTransformer(rootDir, currentDirectory);
+    }
+
+    public String transform(File file) {
+        String canonicalRoot;
+        String canonicalFrom;
+        String canonicalTo;
+        try {
+            canonicalRoot = rootDir.getCanonicalPath();
+            canonicalFrom = currentDir.getCanonicalPath();
+            canonicalTo = file.getCanonicalPath();
+        } catch (IOException e) {
+            return file.getAbsolutePath();
+        }
+
+        return findRelativePathInRoot(canonicalRoot, canonicalFrom, canonicalTo);
+    }
+
+    private String findRelativePathInRoot(String root, String from, String to) {
+        if (!from.contains(root) || !to.contains(root)) {
+            return to;
+        }
+
+        String relativePath = findRelativePath(from, to);
+        return relativePath.length() == 0 ? "." : relativePath;
+    }
+
+    private String findRelativePath(String from, String to) {
+        List<String> fromPath = splitPath(from);
+        List<String> toPath = splitPath(to);
+        List<String> relativePath = new ArrayList<String>();
+
+        while (!fromPath.isEmpty() && !toPath.isEmpty() && fromPath.get(0).equals(toPath.get(0))) {
+            fromPath.remove(0);
+            toPath.remove(0);
+        }
+        for (String ignored : fromPath) {
+            relativePath.add("..");
+        }
+        for (String entry : toPath) {
+            relativePath.add(entry);
+        }
+        return Joiner.on(File.separatorChar).join(relativePath);
+    }
+
+    private List<String> splitPath(String path) {
+        File pathFile = new File(path);
+        List<String> split = new LinkedList<String>();
+        while (pathFile != null) {
+            split.add(0, pathFile.getName());
+            pathFile = pathFile.getParentFile();
+        }
+        return split;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioFiltersFile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioFiltersFile.groovy
new file mode 100644
index 0000000..188f4f0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioFiltersFile.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.visualstudio.tasks.internal
+
+import org.gradle.api.Transformer
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+
+class VisualStudioFiltersFile extends XmlPersistableConfigurationObject {
+    private final Transformer<String, File> fileLocationResolver
+
+    VisualStudioFiltersFile(XmlTransformer xmlTransformer, Transformer<String, File> fileLocationResolver) {
+        super(xmlTransformer)
+        this.fileLocationResolver = fileLocationResolver
+    }
+
+    protected String getDefaultResourceName() {
+        'default.vcxproj.filters'
+    }
+
+    def addSource(File sourceFile) {
+        sources.appendNode("ClCompile", [Include: toPath(sourceFile)]).appendNode('Filter', 'Source Files')
+    }
+
+    def addHeader(File headerFile) {
+        headers.appendNode("ClInclude", [Include: toPath(headerFile)]).appendNode('Filter', 'Header Files')
+    }
+
+    def getFilters() {
+        return xml.ItemGroup.findAll({ it.'@Label' == 'Filters' })[0]
+    }
+
+    private Node getSources() {
+        return xml.ItemGroup.find({ it.'@Label' == 'Sources' }) as Node
+    }
+
+    private Node getHeaders() {
+        return xml.ItemGroup.find({ it.'@Label' == 'Headers' }) as Node
+    }
+
+    private String toPath(File it) {
+        fileLocationResolver.transform(it)
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioProjectFile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioProjectFile.groovy
new file mode 100644
index 0000000..ca27bc5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioProjectFile.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal
+import org.gradle.api.Transformer
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.ide.visualstudio.internal.VisualStudioProjectConfiguration
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+
+class VisualStudioProjectFile extends XmlPersistableConfigurationObject {
+    private final Transformer<String, File> fileLocationResolver
+    String gradleCommand = 'gradle'
+
+    VisualStudioProjectFile(XmlTransformer xmlTransformer, Transformer<String, File> fileLocationResolver) {
+        super(xmlTransformer)
+        this.fileLocationResolver = fileLocationResolver
+    }
+
+    protected String getDefaultResourceName() {
+        'default.vcxproj'
+    }
+
+    def setProjectUuid(String uuid) {
+        Node globals = xml.PropertyGroup.find({it.'@Label' == 'Globals'}) as Node
+        globals.appendNode("ProjectGUID", uuid)
+    }
+
+    def addSourceFile(File it) {
+        def sources = xml.ItemGroup.find({ it.'@Label' == 'Sources' }) as Node
+        sources.appendNode("ClCompile", [Include: toPath(it)])
+    }
+
+    def addResource(File it) {
+        def resources = xml.ItemGroup.find({ it.'@Label' == 'References' }) as Node
+        resources.appendNode("ResourceCompile", [Include: toPath(it)])
+    }
+
+    def addHeaderFile(File it) {
+        def headers = xml.ItemGroup.find({ it.'@Label' == 'Headers' }) as Node
+        headers.appendNode("ClInclude", [Include: toPath(it)])
+    }
+
+    def addConfiguration(VisualStudioProjectConfiguration configuration) {
+        def configNode = configurations.appendNode("ProjectConfiguration", [Include: configuration.name])
+        configNode.appendNode("Configuration", configuration.configurationName)
+        configNode.appendNode("Platform", configuration.platformName)
+        final configCondition = "'\$(Configuration)|\$(Platform)'=='${configuration.name}'"
+
+        def vsOutputDir = ".vs\\${configuration.project.name}\\\$(Configuration)"
+        Node defaultProps = xml.Import.find({ it.'@Project' == '$(VCTargetsPath)\\Microsoft.Cpp.Default.props'}) as Node
+        defaultProps + {
+            PropertyGroup(Label: "Configuration", Condition: configCondition) {
+                ConfigurationType(configuration.type)
+                UseDebugLibraries(configuration.debug)
+                OutDir(vsOutputDir)
+                IntDir(vsOutputDir)
+            }
+        }
+
+        final includePath = toPath(configuration.includePaths).join(";")
+        Node userMacros = xml.PropertyGroup.find({ it.'@Label' == 'UserMacros'}) as Node
+        userMacros + {
+            PropertyGroup(Label: "NMakeConfiguration", Condition: configCondition) {
+                NMakeBuildCommandLine("${gradleCommand} ${configuration.buildTask}")
+                NMakeCleanCommandLine("${gradleCommand} ${configuration.cleanTask}")
+                NMakeReBuildCommandLine("${gradleCommand} ${configuration.cleanTask} ${configuration.buildTask}")
+                NMakePreprocessorDefinitions(configuration.compilerDefines.join(";"))
+                NMakeIncludeSearchPath(includePath)
+                NMakeOutput(toPath(configuration.outputFile))
+            }
+        }
+    }
+
+    private Node getConfigurations() {
+        return xml.ItemGroup.find({ it.'@Label' == 'ProjectConfigurations' }) as Node
+    }
+
+    private List<String> toPath(List<File> files) {
+        return files.collect({toPath(it)})
+    }
+
+    private String toPath(File it) {
+        fileLocationResolver.transform(it)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioSolutionFile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioSolutionFile.groovy
new file mode 100644
index 0000000..9c74eeb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioSolutionFile.groovy
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal
+
+import org.gradle.api.Action
+import org.gradle.ide.visualstudio.TextProvider
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioProject
+import org.gradle.ide.visualstudio.internal.VisualStudioProjectConfiguration
+import org.gradle.plugins.ide.internal.generator.AbstractPersistableConfigurationObject
+import org.gradle.util.TextUtil
+
+class VisualStudioSolutionFile extends AbstractPersistableConfigurationObject {
+    List<Action<? super TextProvider>> actions = new ArrayList<Action<? super TextProvider>>();
+    private final Map<String, List<VisualStudioProjectConfiguration>> solutionConfigurations = [:]
+    private final projects = [:]
+    private mainProject
+    private baseText
+
+    protected String getDefaultResourceName() {
+        'default.sln'
+    }
+
+    void setMainProject(DefaultVisualStudioProject mainProject) {
+        this.mainProject = mainProject
+    }
+
+    void addSolutionConfiguration(String name, List<VisualStudioProjectConfiguration> projectConfigurations) {
+        solutionConfigurations[name] = projectConfigurations
+        projectConfigurations.each {
+            projects[it.project.name] = it.project
+        }
+    }
+
+    @Override
+    void load(InputStream inputStream) throws Exception {
+        baseText = inputStream.text
+    }
+
+    @Override
+    void store(OutputStream outputStream) {
+        def provider = new SimpleTextProvider()
+        generateContent(provider.asBuilder())
+        actions.each {
+            it.execute(provider)
+        }
+        outputStream << TextUtil.convertLineSeparators(provider.getText(), TextUtil.getWindowsLineSeparator())
+    }
+
+    private void generateContent(StringBuilder builder) {
+        builder << baseText
+        projects.each { String name, DefaultVisualStudioProject vsProject ->
+            builder << """
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "${name}", "${vsProject.projectFile.location.absolutePath}", "${vsProject.getUuid()}"
+EndProject"""
+        }
+        builder << """
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution"""
+        solutionConfigurations.keySet().each { solutionConfiguration ->
+            builder << """
+		${solutionConfiguration}=${solutionConfiguration}"""
+        }
+        builder << """
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution"""
+        solutionConfigurations.each { solutionConfiguration, projectConfigurations ->
+            projectConfigurations.each { VisualStudioProjectConfiguration projectConfiguration ->
+                builder << """
+		${projectConfiguration.project.getUuid()}.${solutionConfiguration}.ActiveCfg = ${projectConfiguration.name}"""
+                if (mainProject == projectConfiguration.project) {
+                    builder << """
+		${projectConfiguration.project.getUuid()}.${solutionConfiguration}.Build.0 = ${projectConfiguration.name}"""
+                }
+            }
+        }
+
+        builder << """
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
+"""
+    }
+
+    static class SimpleTextProvider implements TextProvider {
+        private final StringBuilder builder = new StringBuilder();
+        StringBuilder asBuilder() {
+            return builder
+        }
+
+        String getText() {
+            return builder.toString()
+        }
+
+        void setText(String value) {
+            builder.replace(0, builder.length(), value)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/DependentSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/DependentSourceSet.java
new file mode 100644
index 0000000..168b2b1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/DependentSourceSet.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.base.LanguageSourceSet;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * A source set that depends on one or more {@link org.gradle.nativebinaries.NativeDependencySet}s to be built.
+ */
+ at Incubating
+public interface DependentSourceSet extends LanguageSourceSet {
+    /**
+     * The libraries that this source set requires.
+     */
+    Collection<?> getLibs();
+
+    /**
+     * Adds a library that this source set requires. This method accepts the following types:
+     *
+     * <ul>
+     *     <li>A {@link org.gradle.nativebinaries.Library}</li>
+     *     <li>A {@link org.gradle.nativebinaries.NativeDependencySet}</li>
+     *     <li>A {@link java.util.Map} containing the library selector.</li>
+     * </ul>
+     *
+     * The Map notation supports the following String attributes:
+     *
+     * <ul>
+     *     <li>project: the path to the project containing the library (optional, defaults to current project)</li>
+     *     <li>library: the name of the library (required)</li>
+     *     <li>linkage: the library linkage required ['shared'/'static'] (optional, defaults to 'shared')</li>
+     * </ul>
+     */
+    void lib(Object library);
+
+    /**
+     * Add a dependency to this source set.
+     */
+    void dependency(Map<?, ?> dep);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/HeaderExportingSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/HeaderExportingSourceSet.java
new file mode 100644
index 0000000..e27d234
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/HeaderExportingSourceSet.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.language;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * A source set that exposes headers
+ */
+ at Incubating
+public interface HeaderExportingSourceSet extends LanguageSourceSet {
+
+    /**
+     * Configure the exported header directories.
+     */
+    void exportedHeaders(Action<? super SourceDirectorySet> config);
+
+    /**
+     * The headers as a directory set.
+     */
+    SourceDirectorySet getExportedHeaders();
+
+    /**
+     * The headers that are private to this source set and implicitly available. These are not explicitly made available for compilation.
+     *
+     * TODO:DAZ This is temporary to get 'implicit' headers working in Visual Studio. The plan is to model these better soon.
+     */
+    SourceDirectorySet getImplicitHeaders();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/AssemblerSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/AssemblerSourceSet.java
new file mode 100644
index 0000000..48ea483
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/AssemblerSourceSet.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.assembler;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * A set of assembly language sources.
+ *
+ * <pre autoTested="true">
+ * apply plugin: "assembler"
+ *
+ * sources {
+ *     main {
+ *         // Configure an existing AssemblerSourceSet
+ *         asm {
+ *             source {
+ *                 srcDirs "src/main/i386", "src/shared/asm"
+ *                 include "**{@literal /}*.s"
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface AssemblerSourceSet extends LanguageSourceSet {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/internal/DefaultAssemblerSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/internal/DefaultAssemblerSourceSet.java
new file mode 100644
index 0000000..0f988da
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/internal/DefaultAssemblerSourceSet.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.assembler.internal;
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.assembler.AssemblerSourceSet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.internal.AbstractLanguageSourceSet;
+
+public class DefaultAssemblerSourceSet extends AbstractLanguageSourceSet implements AssemblerSourceSet {
+    public DefaultAssemblerSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project) {
+        super(name, parent, "assembler source", new DefaultSourceDirectorySet("source", project.getFileResolver()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/package-info.java
new file mode 100644
index 0000000..3494283
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for building from Assembler language sources.
+ */
+package org.gradle.language.assembler;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/plugins/AssemblerLangPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/plugins/AssemblerLangPlugin.groovy
new file mode 100644
index 0000000..b393bb8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/assembler/plugins/AssemblerLangPlugin.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.assembler.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.assembler.AssemblerSourceSet
+import org.gradle.language.assembler.internal.DefaultAssemblerSourceSet
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.plugins.LanguageBasePlugin
+
+import javax.inject.Inject
+/**
+ * Adds core Assembler language support.
+ *
+ * <ul>
+ *     <li>For any {@link FunctionalSourceSet}, adds a conventional {@link AssemblerSourceSet} called 'asm'.</li>
+ *     <li>Establishes a convention for all {@link AssemblerSourceSet}s so that sources are located in 'src/<name>/asm'.</li>
+ *     <li>
+ * </ul>
+ */
+ at Incubating
+class AssemblerLangPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public AssemblerLangPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    void apply(ProjectInternal project) {
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(AssemblerSourceSet) { name ->
+                    instantiator.newInstance(DefaultAssemblerSourceSet, name, functionalSourceSet, project)
+                }
+
+                // Create a single assembler source set
+                functionalSourceSet.create "asm", AssemblerSourceSet
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/c/CSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/c/CSourceSet.java
new file mode 100644
index 0000000..17269ab
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/c/CSourceSet.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.c;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.HeaderExportingSourceSet;
+
+/**
+ * A set of C source files.
+ *
+ * <p>A C source set contains a set of source files, together with an optional set of exported header files.</p>
+ *
+ * <pre autoTested="true">
+ * apply plugin: "c"
+ *
+ * sources {
+ *     main {
+ *         // Configure an existing CSourceSet
+ *         c {
+ *             source {
+ *                 srcDirs "src/main/cpp", "src/shared/c++"
+ *                 include "**{@literal /}*.c"
+ *             }
+ *             exportedHeaders {
+ *                 srcDirs "src/main/include"
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface CSourceSet extends HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSet {
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/c/internal/DefaultCSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/c/internal/DefaultCSourceSet.java
new file mode 100644
index 0000000..5035f2d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/c/internal/DefaultCSourceSet.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.c.internal;
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.c.CSourceSet;
+import org.gradle.language.internal.AbstractHeaderExportingDependentSourceSet;
+
+import javax.inject.Inject;
+
+public class DefaultCSourceSet extends AbstractHeaderExportingDependentSourceSet implements CSourceSet {
+    @Inject
+    public DefaultCSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project) {
+        super(name, parent, project, "C source", new DefaultSourceDirectorySet("source", project.getFileResolver()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/c/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/c/package-info.java
new file mode 100644
index 0000000..b16e10b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/c/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for building from C language sources.
+ */
+package org.gradle.language.c;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/c/plugins/CLangPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/c/plugins/CLangPlugin.groovy
new file mode 100644
index 0000000..d46a449
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/c/plugins/CLangPlugin.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.c.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.plugins.LanguageBasePlugin
+import org.gradle.language.c.CSourceSet
+import org.gradle.language.c.internal.DefaultCSourceSet
+
+import javax.inject.Inject
+/**
+ * Adds core C language support.
+ *
+ * <ul>
+ *     <li>For any {@link FunctionalSourceSet}, adds a conventional {@link CSourceSet} called 'cpp'.</li>
+ *     <li>Establishes a convention for all {@link CSourceSet}s so that sources are located in 'src/<name>/c' and
+ *         headers are located in 'src/<name>/headers'.</li>
+ *     <li>
+ * </ul>
+ */
+ at Incubating
+class CLangPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public CLangPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    void apply(ProjectInternal project) {
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(CSourceSet) { name ->
+                    instantiator.newInstance(DefaultCSourceSet, name, functionalSourceSet, project)
+                }
+
+                // Create a single C source set
+                functionalSourceSet.create "c", CSourceSet
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/CppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/CppSourceSet.java
new file mode 100644
index 0000000..20d2ea1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/CppSourceSet.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.cpp;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.HeaderExportingSourceSet;
+
+/**
+ * A set of C++ source files.
+ *
+ * <p>A C++ source set contains a set of source files, together with an optional set of exported header files.</p>
+ *
+ * <pre autoTested="true">
+ * apply plugin: "cpp"
+ *
+ * sources {
+ *     main {
+ *         // Configure an existing CppSourceSet
+ *         cpp {
+ *             source {
+ *                 srcDirs "src/main/cpp", "src/shared/c++"
+ *                 include "**{@literal /}*.cpp"
+ *             }
+ *             exportedHeaders {
+ *                 srcDirs "src/main/include", "src/shared/include"
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface CppSourceSet extends HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSet {
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/internal/DefaultCppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/internal/DefaultCppSourceSet.java
new file mode 100644
index 0000000..146049b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/internal/DefaultCppSourceSet.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.cpp.internal;
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.internal.AbstractHeaderExportingDependentSourceSet;
+import org.gradle.language.cpp.CppSourceSet;
+
+public class DefaultCppSourceSet extends AbstractHeaderExportingDependentSourceSet implements CppSourceSet {
+    public DefaultCppSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project) {
+        super(name, parent, project, "C++ source", new DefaultSourceDirectorySet("source", project.getFileResolver()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/package-info.java
new file mode 100644
index 0000000..943abe5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for building from C++ language sources.
+ */
+package org.gradle.language.cpp;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/plugins/CppLangPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/plugins/CppLangPlugin.groovy
new file mode 100644
index 0000000..036912d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/cpp/plugins/CppLangPlugin.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.cpp.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.plugins.LanguageBasePlugin
+import org.gradle.language.cpp.CppSourceSet
+import org.gradle.language.cpp.internal.DefaultCppSourceSet
+
+import javax.inject.Inject
+/**
+ * Adds core C++ language support.
+ *
+ * <ul>
+ *     <li>For any {@link FunctionalSourceSet}, adds a conventional {@link CppSourceSet} called 'cpp'.</li>
+ *     <li>Establishes a convention for all {@link CppSourceSet}s so that sources are located in 'src/<name>/cpp' and
+ *         headers are located in 'src/<name>/headers'.</li>
+ *     <li>
+ * </ul>
+ */
+ at Incubating
+class CppLangPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public CppLangPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    void apply(ProjectInternal project) {
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(CppSourceSet) { name ->
+                    instantiator.newInstance(DefaultCppSourceSet, name, functionalSourceSet, project)
+                }
+                // Add a single C++ source set
+                functionalSourceSet.create "cpp", CppSourceSet
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/internal/AbstractHeaderExportingDependentSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/internal/AbstractHeaderExportingDependentSourceSet.java
new file mode 100644
index 0000000..dcae0fb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/internal/AbstractHeaderExportingDependentSourceSet.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.language.internal;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A convenience base class for implementing language source sets with dependencies and exported headers.
+ */
+public abstract class AbstractHeaderExportingDependentSourceSet extends AbstractHeaderExportingSourceSet
+        implements HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSet {
+
+    private final List<Object> libs = new ArrayList<Object>();
+    private final ConfigurationBasedNativeDependencySet configurationDependencySet;
+
+    public AbstractHeaderExportingDependentSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project, String typeName, SourceDirectorySet source) {
+        super(name, parent, project, typeName, source);
+
+        this.configurationDependencySet = new ConfigurationBasedNativeDependencySet(project, getFullName());
+
+        libs.add(configurationDependencySet);
+    }
+
+    public Collection<?> getLibs() {
+        return libs;
+    }
+
+    public void lib(Object library) {
+        if (library instanceof Iterable<?>) {
+            Iterable<?> iterable = (Iterable) library;
+            CollectionUtils.addAll(libs, iterable);
+        } else {
+            libs.add(library);
+        }
+    }
+
+    public void dependency(Map<?, ?> dep) {
+        configurationDependencySet.add(dep);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/internal/AbstractHeaderExportingSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/internal/AbstractHeaderExportingSourceSet.java
new file mode 100644
index 0000000..982189f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/internal/AbstractHeaderExportingSourceSet.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.AbstractLanguageSourceSet;
+
+/**
+ * A convenience base class for implementing language source sets with dependencies and exported headers.
+ */
+public abstract class AbstractHeaderExportingSourceSet extends AbstractLanguageSourceSet
+        implements HeaderExportingSourceSet, LanguageSourceSet {
+
+    private final DefaultSourceDirectorySet exportedHeaders;
+    private final DefaultSourceDirectorySet implicitHeaders;
+
+    public AbstractHeaderExportingSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project, String typeName, SourceDirectorySet source) {
+        super(name, parent, typeName, source);
+        this.exportedHeaders = new DefaultSourceDirectorySet("exported headers", project.getFileResolver());
+        this.implicitHeaders = new DefaultSourceDirectorySet("implicit headers", project.getFileResolver());
+    }
+
+    public SourceDirectorySet getExportedHeaders() {
+        return exportedHeaders;
+    }
+
+    public void exportedHeaders(Action<? super SourceDirectorySet> config) {
+        config.execute(getExportedHeaders());
+    }
+
+    public SourceDirectorySet getImplicitHeaders() {
+        return implicitHeaders;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/internal/ConfigurationBasedNativeDependencySet.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/internal/ConfigurationBasedNativeDependencySet.groovy
new file mode 100644
index 0000000..2b9b0a9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/internal/ConfigurationBasedNativeDependencySet.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.internal
+
+import org.gradle.nativebinaries.NativeDependencySet
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.artifacts.Configuration
+
+class ConfigurationBasedNativeDependencySet implements NativeDependencySet {
+
+    private final String baseName
+    final String headersConfigurationName
+    final String filesConfigurationName // files is a bad name
+    final Project project
+    private Task headerExtractionTask
+
+    ConfigurationBasedNativeDependencySet(Project project, String baseName = "main") {
+        this.baseName = baseName
+        this.headersConfigurationName = baseName + "HeaderDependencies"
+        this.filesConfigurationName = baseName + "FileDependencies"
+        this.project = project
+
+        createConfigurations()
+        initHeaderExtractionTask()
+    }
+
+    private createConfigurations() {
+        project.configurations.with {
+            create(headersConfigurationName)
+            create(filesConfigurationName)
+        }
+    }
+
+    private initHeaderExtractionTask() {
+        def headersConfiguration = getHeadersConfiguration()
+        def dir = project.file("$project.buildDir/dependency-headers/$baseName")
+        headerExtractionTask = project.task(baseName + "ExtractHeaders") {
+            inputs.files headersConfiguration
+            outputs.files { dir.listFiles() }
+            doLast {
+                headersConfiguration.each { headerZip ->
+                    project.copy {
+                        from project.zipTree(headerZip)
+                        into "$dir/${headerZip.name - '.zip'}"
+                    }
+                }
+            }
+        }
+    }
+
+    Configuration getHeadersConfiguration() {
+        project.configurations[headersConfigurationName]
+    }
+
+    FileCollection getIncludeRoots() {
+        headerExtractionTask.outputs.files
+    }
+
+    FileCollection getLinkFiles() {
+        project.configurations[filesConfigurationName]
+    }
+
+    FileCollection getRuntimeFiles() {
+        return getLinkFiles()
+    }
+
+    void add(Map dep) {
+        // hackity hack hack
+        project.dependencies {
+            def m = { classifier, ext -> [classifier: classifier, ext: ext] }
+            delegate."$headersConfigurationName"(dep + m("headers", "zip"))
+            delegate."$filesConfigurationName"(dep + m("so", "so"))
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/ObjectiveCSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/ObjectiveCSourceSet.java
new file mode 100644
index 0000000..d86c297
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/ObjectiveCSourceSet.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.objectivec;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+
+
+/**
+ * A set of Objective-C source files.
+ *
+ * <p>An ObjectiveC source set contains a set of source files, together with an optional set of exported header files.</p>
+ *
+ * <pre autoTested="true">
+ * apply plugin: "objective-c"
+ *
+ * sources {
+ *     main {
+ *         // Configure an existing ObjectiveCSourceSet
+ *         objc {
+ *             source {
+ *                 srcDirs "src/main/objectiveC", "src/shared/objectiveC"
+ *                 include "**{@literal /}*.m"
+ *             }
+ *             exportedHeaders {
+ *                 srcDirs "src/main/include"
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface ObjectiveCSourceSet extends HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSet {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/internal/DefaultObjectiveCSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/internal/DefaultObjectiveCSourceSet.java
new file mode 100644
index 0000000..7e8b5f3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/internal/DefaultObjectiveCSourceSet.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.objectivec.internal;
+
+import javax.inject.Inject;
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.internal.AbstractHeaderExportingDependentSourceSet;
+import org.gradle.language.objectivec.ObjectiveCSourceSet;
+
+public class DefaultObjectiveCSourceSet extends AbstractHeaderExportingDependentSourceSet implements ObjectiveCSourceSet {
+    @Inject
+    public DefaultObjectiveCSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project) {
+        super(name, parent, project, "Objective-C source", new DefaultSourceDirectorySet("source", project.getFileResolver()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/package-info.java
new file mode 100644
index 0000000..9c889c8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for building from Objective-C language sources.
+ */
+package org.gradle.language.objectivec;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.groovy
new file mode 100644
index 0000000..e2d78d0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.objectivec.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.plugins.LanguageBasePlugin
+import org.gradle.language.objectivec.ObjectiveCSourceSet
+import org.gradle.language.objectivec.internal.DefaultObjectiveCSourceSet
+
+import javax.inject.Inject
+/**
+ * Adds core Objective-C language support.
+ *
+ * <ul>
+ *     <li>For any {@link FunctionalSourceSet}, adds a conventional {@link ObjectiveCSourceSet} called 'objectiveC'.</li>
+ *     <li>Establishes a convention for all {@link ObjectiveCSourceSet}s so that sources are located in 'src/<name>/objectiveC' and
+ *         headers are located in 'src/<name>/headers'.</li>
+ *     <li>
+ * </ul>
+ */
+ at Incubating
+class ObjectiveCLangPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public ObjectiveCLangPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    void apply(ProjectInternal project) {
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(ObjectiveCSourceSet) { name ->
+                    instantiator.newInstance(DefaultObjectiveCSourceSet, name, functionalSourceSet, project)
+                }
+                // Add a single Objective-C source set
+                functionalSourceSet.create "objc", ObjectiveCSourceSet
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/ObjectiveCppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/ObjectiveCppSourceSet.java
new file mode 100644
index 0000000..3db6a7c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/ObjectiveCppSourceSet.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.objectivecpp;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * A set of Objective-C++ source files.
+ *
+ * <p>An Objective-C++ source set contains a set of source files, together with an optional set of exported header files.</p>
+ *
+ * <pre autoTested="true">
+ * apply plugin: "objective-cpp"
+ *
+ * sources {
+ *     main {
+ *         // Configure an existing ObjectiveCppSourceSet
+ *         objcpp {
+ *             source {
+ *                 srcDirs "src/main/objectiveCpp", "src/shared/objectiveCpp"
+ *                 include "**{@literal /}*.mm"
+ *             }
+ *             exportedHeaders {
+ *                 srcDirs "src/main/include"
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface ObjectiveCppSourceSet extends HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSet {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/internal/DefaultObjectiveCppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/internal/DefaultObjectiveCppSourceSet.java
new file mode 100644
index 0000000..677450b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/internal/DefaultObjectiveCppSourceSet.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.objectivecpp.internal;
+
+import javax.inject.Inject;
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.internal.AbstractHeaderExportingDependentSourceSet;
+import org.gradle.language.objectivecpp.ObjectiveCppSourceSet;
+
+public class DefaultObjectiveCppSourceSet extends AbstractHeaderExportingDependentSourceSet implements ObjectiveCppSourceSet {
+    @Inject
+    public DefaultObjectiveCppSourceSet(String name, FunctionalSourceSet parent, ProjectInternal project) {
+        super(name, parent, project, "Objective-C++ source", new DefaultSourceDirectorySet("source", project.getFileResolver()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/package-info.java
new file mode 100644
index 0000000..c5dbba1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for building from Objective-C++ language sources.
+ */
+package org.gradle.language.objectivecpp;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.groovy
new file mode 100644
index 0000000..f538428
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.objectivecpp.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.plugins.LanguageBasePlugin
+import org.gradle.language.objectivecpp.ObjectiveCppSourceSet
+import org.gradle.language.objectivecpp.internal.DefaultObjectiveCppSourceSet
+
+import javax.inject.Inject
+/**
+ * Adds core Objective-C++ language support.
+ *
+ * <ul>
+ *     <li>For any {@link FunctionalSourceSet}, adds a conventional {@link ObjectiveCppSourceSet} called 'objcpp'.</li>
+ *     <li>Establishes a convention for all {@link ObjectiveCppSourceSet}s so that sources are located in 'src/<name>/objcpp' and
+ *         headers are located in 'src/<name>/headers'.</li>
+ *     <li>
+ * </ul>
+ */
+ at Incubating
+class ObjectiveCppLangPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public ObjectiveCppLangPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    void apply(ProjectInternal project) {
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(ObjectiveCppSourceSet) { name ->
+                    instantiator.newInstance(DefaultObjectiveCppSourceSet, name, functionalSourceSet, project)
+                }
+                // Add a single Objective-C++ source set
+                functionalSourceSet.create "objcpp", ObjectiveCppSourceSet
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/package-info.java
new file mode 100644
index 0000000..38a2af6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for managing language sources.
+ */
+package org.gradle.language;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/rc/WindowsResourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/WindowsResourceSet.java
new file mode 100644
index 0000000..09cce70
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/WindowsResourceSet.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.rc;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * A set of Windows Resource definition files.
+ *
+ * <p>A Windows Resource set contains a set of script files, together with an optional set of header files.</p>
+ *
+ * <pre autoTested="true">
+ * apply plugin: "windows-resources"
+ *
+ * sources {
+ *     main {
+ *         // Configure an existing WindowsResourceSet
+ *         rc {
+ *             source {
+ *                 srcDirs "src/main/rc"
+ *                 include "**{@literal /}*.rc"
+ *             }
+ *             exportedHeaders {
+ *                 srcDirs "src/main/include"
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface WindowsResourceSet extends LanguageSourceSet, HeaderExportingSourceSet {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/rc/internal/DefaultWindowsResourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/internal/DefaultWindowsResourceSet.java
new file mode 100644
index 0000000..90acc28
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/internal/DefaultWindowsResourceSet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.rc.internal;
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.internal.AbstractHeaderExportingSourceSet;
+import org.gradle.language.rc.WindowsResourceSet;
+
+public class DefaultWindowsResourceSet extends AbstractHeaderExportingSourceSet implements WindowsResourceSet {
+    public DefaultWindowsResourceSet(String name, FunctionalSourceSet parent, ProjectInternal project) {
+        super(name, parent, project, "windows resources", new DefaultSourceDirectorySet("source", project.getFileResolver()));
+     }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/rc/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/package-info.java
new file mode 100644
index 0000000..cde8cdf
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for building from Windows Resource scripts.
+ */
+package org.gradle.language.rc;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/language/rc/plugins/WindowsResourceScriptPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/plugins/WindowsResourceScriptPlugin.groovy
new file mode 100644
index 0000000..3bb029d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/language/rc/plugins/WindowsResourceScriptPlugin.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.rc.plugins
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.plugins.LanguageBasePlugin
+import org.gradle.language.rc.WindowsResourceSet
+import org.gradle.language.rc.internal.DefaultWindowsResourceSet
+
+import javax.inject.Inject
+/**
+ * Adds core language support for Windows resource script files.
+ *
+ * <ul>
+ *     <li>For any {@link FunctionalSourceSet}, adds a conventional {@link WindowsResourceSet} called 'rc'.</li>
+ *     <li>Establishes a convention for all {@link WindowsResourceSet}s so that sources are
+ *         located in 'src/<name>/rc' and headers are located in 'src/<name>/headers'.</li>
+ *     <li>
+ * </ul>
+ */
+ at Incubating
+class WindowsResourceScriptPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public WindowsResourceScriptPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    void apply(ProjectInternal project) {
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(WindowsResourceSet) { name ->
+                    instantiator.newInstance(DefaultWindowsResourceSet, name, functionalSourceSet, project)
+                }
+
+                // Create a single windows resource set
+                functionalSourceSet.create "rc", WindowsResourceSet
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/BuildType.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/BuildType.java
new file mode 100644
index 0000000..f70765f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/BuildType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * Specifies a build-type for a native binary. Common build types are 'debug' and 'release', but others may be defined.
+ */
+ at Incubating
+public interface BuildType extends Named {
+    /**
+     * Returns a human-consumable name for this build type.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/BuildTypeContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/BuildTypeContainer.java
new file mode 100644
index 0000000..a18f249
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/BuildTypeContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * A container of {@link BuildType}s.
+ */
+ at Incubating
+public interface BuildTypeContainer extends NamedDomainObjectContainer<BuildType> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Executable.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Executable.java
new file mode 100644
index 0000000..6bda7a2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Executable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+/**
+ * An executable native component that is built by Gradle.
+ */
+ at Incubating
+public interface Executable extends ProjectNativeComponent, TargetedNativeComponent {
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ExecutableBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ExecutableBinary.java
new file mode 100644
index 0000000..875f594
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ExecutableBinary.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+
+/**
+ * A binary artifact that is built from of a {@link Executable}, targeted at a particular platform with specific configuration.
+ */
+ at Incubating
+public interface ExecutableBinary extends NativeBinary {
+    /**
+     * The executable file.
+     */
+    File getExecutableFile();
+
+    /**
+     * The executable file.
+     */
+    void setExecutableFile(File executableFile);
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ExecutableContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ExecutableContainer.java
new file mode 100644
index 0000000..34bed7b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ExecutableContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * A container of native executables.
+ */
+ at Incubating
+public interface ExecutableContainer extends NamedDomainObjectSet<Executable> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Flavor.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Flavor.java
new file mode 100644
index 0000000..e147653
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Flavor.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * Defines a custom variant that differentiate a {@link NativeBinary}.
+ */
+ at Incubating
+public interface Flavor extends Named {
+    /**
+     * Returns a human-consumable display name for this flavor.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/FlavorContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/FlavorContainer.java
new file mode 100644
index 0000000..143fe6d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/FlavorContainer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * A container of {@link Flavor}s.
+ * <p/>
+ * If no flavor is explicitly configured, will contain a single {@link Flavor} named 'default'.
+ * Any flavors explicitly configured will overwrite the default flavor.
+ */
+ at Incubating
+public interface FlavorContainer extends NamedDomainObjectContainer<Flavor> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Library.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Library.java
new file mode 100644
index 0000000..a933b8f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Library.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A library component that is built by a gradle project.
+ */
+ at Incubating
+public interface Library extends ProjectNativeComponent, TargetedNativeComponent {
+    /**
+     * Converts this library to a native library requirement that uses the shared library variant. This is the default.
+     */
+    NativeLibraryRequirement getShared();
+
+    /**
+     * Converts this library to a native library requirement that uses the static library variant.
+     */
+    NativeLibraryRequirement getStatic();
+
+    /**
+     * Converts this library to a native library requirement that uses the api library linkage.
+     */
+    NativeLibraryRequirement getApi();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/LibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/LibraryBinary.java
new file mode 100644
index 0000000..2072ade
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/LibraryBinary.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A physical representation of a {@link Library} component.
+ */
+ at Incubating
+public interface LibraryBinary extends NativeBinary {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/LibraryContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/LibraryContainer.java
new file mode 100644
index 0000000..628f987
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/LibraryContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * A container of native libraries.
+ */
+ at Incubating
+public interface LibraryContainer extends NamedDomainObjectSet<Library> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeBinary.java
new file mode 100644
index 0000000..0c99b57
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeBinary.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.base.Binary;
+import org.gradle.nativebinaries.platform.Platform;
+
+/**
+ * Represents a particular binary artifact.
+ */
+ at Incubating
+public interface NativeBinary extends Binary {
+    /**
+     * The flavor that this binary was built with.
+     */
+    Flavor getFlavor();
+
+    /**
+     * Returns the {@link org.gradle.nativebinaries.platform.Platform} that this binary is targeted to run on.
+     */
+    Platform getTargetPlatform();
+
+    /**
+     * Returns the {@link BuildType} used to construct this binary.
+     */
+    BuildType getBuildType();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeBinaryTasks.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeBinaryTasks.java
new file mode 100644
index 0000000..d66bb95
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeBinaryTasks.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Incubating;
+import org.gradle.api.Task;
+import org.gradle.nativebinaries.tasks.BuildBinaryTask;
+
+/**
+ * Provides access to key tasks used for building the binary.
+ */
+ at Incubating
+public interface NativeBinaryTasks extends DomainObjectSet<Task> {
+    /**
+     * The link task, if one is present. Null otherwise.
+     */
+    Task getLink();
+
+    /**
+     * The create static library task if present. Null otherwise.
+     */
+    Task getCreateStaticLib();
+
+    /**
+     * The task that builds the binary out of object files: either the link task or create static library task as appropriate.
+     */
+    BuildBinaryTask getBuilder();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeDependencySet.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeDependencySet.java
new file mode 100644
index 0000000..b79f25c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeDependencySet.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * Models a collection of native type dependencies.
+ */
+ at Incubating
+public interface NativeDependencySet {
+    /**
+     * Returns the header file directories to use at compile time.
+     */
+    FileCollection getIncludeRoots();
+
+    /**
+     * Returns the files to use at link time.
+     */
+    FileCollection getLinkFiles();
+
+    /**
+     * Returns the files to use at runtime.
+     */
+    FileCollection getRuntimeFiles();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeLibraryRequirement.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeLibraryRequirement.java
new file mode 100644
index 0000000..2493b9a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/NativeLibraryRequirement.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A dependency on a native-binaries library within the build.
+ */
+ at Incubating
+public interface NativeLibraryRequirement {
+    /**
+     * The path to the project containing the library.
+     */
+    String getProjectPath();
+
+    /**
+     * The name of the required library.
+     */
+    String getLibraryName();
+
+    /**
+     * The required linkage.
+     */
+    // TODO:DAZ Type this
+    String getLinkage();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/PrebuiltLibraries.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/PrebuiltLibraries.java
new file mode 100644
index 0000000..9f3d605
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/PrebuiltLibraries.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+
+/**
+ * A container of {@link PrebuiltLibrary} instances.
+ */
+ at Incubating
+public interface PrebuiltLibraries extends ArtifactRepository, NamedDomainObjectSet<PrebuiltLibrary> {
+    PrebuiltLibrary resolveLibrary(String name);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/PrebuiltLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/PrebuiltLibrary.java
new file mode 100644
index 0000000..330f92b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/PrebuiltLibrary.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.api.file.SourceDirectorySet;
+
+/**
+ * A library component that is not built by gradle.
+ */
+ at Incubating
+public interface PrebuiltLibrary extends Named {
+    /**
+     * The binaries that are built for this component. You can use this to configure the binaries for this component.
+     */
+    DomainObjectSet<NativeBinary> getBinaries();
+
+    /**
+     * The headers exported by this library. These headers will be added to all binaries for this library.
+     */
+    SourceDirectorySet getHeaders();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ProjectNativeBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ProjectNativeBinary.java
new file mode 100644
index 0000000..332e29b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ProjectNativeBinary.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Incubating;
+import org.gradle.internal.HasInternalProtocol;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+
+import java.util.Collection;
+
+/**
+ * Represents a particular binary artifact that is the result of building a native component.
+ */
+ at Incubating @HasInternalProtocol
+public interface ProjectNativeBinary extends NativeBinary {
+    /**
+     * The component that this binary was built from.
+     */
+    ProjectNativeComponent getComponent();
+
+    /**
+     * Can this binary be built in the current environment?
+     */
+    boolean isBuildable();
+
+    /**
+     * The source sets used to compile this binary.
+     */
+    DomainObjectSet<LanguageSourceSet> getSource();
+
+    /**
+     * Adds one or more {@link org.gradle.language.base.LanguageSourceSet}s that are used to compile this binary.
+     * <p/>
+     * This method accepts the following types:
+     *
+     * <ul>
+     *     <li>A {@link org.gradle.language.base.FunctionalSourceSet}</li>
+     *     <li>A {@link org.gradle.language.base.LanguageSourceSet}</li>
+     *     <li>A Collection of {@link org.gradle.language.base.LanguageSourceSet}s</li>
+     * </ul>
+     */
+    void source(Object source);
+
+    /**
+     * The libraries that should be linked into this binary.
+     */
+    Collection<NativeDependencySet> getLibs();
+
+    /**
+     * Adds a library as input to this binary.
+     * <p/>
+     * This method accepts the following types:
+     *
+     * <ul>
+     *     <li>A {@link Library}</li>
+     *     <li>A {@link NativeDependencySet}</li>
+     *     <li>A {@link java.util.Map} containing the library selector.</li>
+     * </ul>
+     *
+     * The Map notation supports the following String attributes:
+     *
+     * <ul>
+     *     <li>project: the path to the project containing the library (optional, defaults to current project)</li>
+     *     <li>library: the name of the library (required)</li>
+     *     <li>linkage: the library linkage required ['shared'/'static'] (optional, defaults to 'shared')</li>
+     * </ul>
+     */
+    void lib(Object library);
+
+    /**
+     * Returns the {@link org.gradle.nativebinaries.toolchain.ToolChain} that will be used to build this binary.
+     */
+    ToolChain getToolChain();
+
+    // TODO:DAZ Add these tools as extensions: linker for ExecutableBinary and SharedLibraryBinary, staticLibArchiver for StaticLibraryBinary
+    /**
+     * The settings used for linking this binary.
+     */
+    Tool getLinker();
+
+    /**
+     * The static archiver settings used for creating this binary.
+     */
+    Tool getStaticLibArchiver();
+
+    /**
+     * The set of tasks associated with this binary.
+     */
+    NativeBinaryTasks getTasks();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ProjectNativeComponent.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ProjectNativeComponent.java
new file mode 100644
index 0000000..94774f4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/ProjectNativeComponent.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.internal.HasInternalProtocol;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * Represents a logical software component, which may be built in a number of variant binaries.
+ */
+ at Incubating @HasInternalProtocol
+public interface ProjectNativeComponent extends Named {
+    /**
+     * Returns a human-consumable display name for this component.
+     */
+    String getDisplayName();
+
+    /**
+     * The binaries that are built for this component. You can use this to configure the binaries for this component.
+     */
+    DomainObjectSet<NativeBinary> getBinaries();
+
+    /**
+     * The source sets that are used to build this component.
+     */
+    DomainObjectSet<LanguageSourceSet> getSource();
+
+    /**
+     * Adds one or more {@link org.gradle.language.base.LanguageSourceSet}s that are used to compile this binary.
+     * <p/>
+     * This method accepts the following types:
+     *
+     * <ul>
+     *     <li>A {@link org.gradle.language.base.FunctionalSourceSet}</li>
+     *     <li>A {@link org.gradle.language.base.LanguageSourceSet}</li>
+     *     <li>A Collection of {@link org.gradle.language.base.LanguageSourceSet}s</li>
+     * </ul>
+     */
+    void source(Object source);
+
+    /**
+     * The name that is used to construct the output file names when building this component.
+     */
+    String getBaseName();
+
+    /**
+     * Sets the name that is used to construct the output file names when building this component.
+     */
+    void setBaseName(String baseName);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Repositories.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Repositories.java
new file mode 100644
index 0000000..26b904e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Repositories.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.PolymorphicDomainObjectContainer;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+
+/**
+ * The repositories that Gradle will search for prebuilt libraries.
+ */
+ at Incubating
+public interface Repositories extends PolymorphicDomainObjectContainer<ArtifactRepository> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/SharedLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/SharedLibraryBinary.java
new file mode 100644
index 0000000..21e80f2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/SharedLibraryBinary.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+
+/**
+ * A {@link Library} that has been compiled and linked as a shared library.
+ */
+ at Incubating
+public interface SharedLibraryBinary extends LibraryBinary {
+
+    /**
+     * The shared library file.
+     */
+    File getSharedLibraryFile();
+
+    /**
+     * The shared library link file.
+     */
+    File getSharedLibraryLinkFile();
+
+    /**
+     * The shared library file.
+     */
+    void setSharedLibraryFile(File sharedLibraryFile);
+
+    /**
+     * The shared library file.
+     */
+    void setSharedLibraryLinkFile(File sharedLibraryLinkFile);
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/StaticLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/StaticLibraryBinary.java
new file mode 100644
index 0000000..d0306d9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/StaticLibraryBinary.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.internal.HasInternalProtocol;
+
+import java.io.File;
+
+/**
+ * A {@link Library} that has been compiled and archived into a static library.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface StaticLibraryBinary extends LibraryBinary {
+
+    /**
+     * The static library file.
+     */
+    File getStaticLibraryFile();
+
+    /**
+     * The static library binary file.
+     */
+    void setStaticLibraryFile(File staticLibraryFile);
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/TargetedNativeComponent.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/TargetedNativeComponent.java
new file mode 100644
index 0000000..3b4639b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/TargetedNativeComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A native component that can be configured to target certain variant dimensions.
+ */
+ at Incubating @HasInternalProtocol
+public interface TargetedNativeComponent {
+
+    /**
+     * Specifies the names of one or more {@link Flavor}s that this component should be built for.
+     */
+    void targetFlavors(String... flavorSelectors);
+
+    /**
+     * Specifies the names of one or more {@link org.gradle.nativebinaries.platform.Platform}s that this component should be built for.
+     */
+    void targetPlatforms(String... platformSelectors);
+
+    /**
+     * Specifies the names of one or more {@link BuildType}s that this component should be built for.
+     */
+    void targetBuildTypes(String... platformSelectors);
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Tool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Tool.java
new file mode 100644
index 0000000..a4d8810
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/Tool.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries;
+
+import org.gradle.api.Incubating;
+
+import java.util.List;
+
+// TODO:DAZ Possibly merge with org.gradle.nativecode.toolchain.GccTool
+// Need to work out if it makes sense to set the args when configuring a tool chain, or set the tool exe for a binary.
+// Seems like merging the 2 might work.
+/**
+ * Configuration of the arguments of a ToolChain executable.
+ */
+ at Incubating
+public interface Tool {
+    /**
+     * The arguments passed when executing this tool.
+     */
+    List<String> getArgs();
+
+    /**
+     * Adds a number of arguments to be passed to the tool.
+     */
+    void args(String... args);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractBinaryToolSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractBinaryToolSpec.java
new file mode 100644
index 0000000..b7dfcd5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractBinaryToolSpec.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AbstractBinaryToolSpec implements BinaryToolSpec {
+    private List<String> args = new ArrayList<String>();
+    private List<String> systemArgs = new ArrayList<String>();
+    private File tempDir;
+
+    public File getTempDir() {
+        return tempDir;
+    }
+
+    public void setTempDir(File tempDir) {
+        this.tempDir = tempDir;
+    }
+
+    public List<String> getArgs() {
+        return args;
+    }
+
+    public void args(List<String> args) {
+        this.args.addAll(args);
+    }
+
+    public List<String> getSystemArgs() {
+        return systemArgs;
+    }
+
+    public void systemArgs(List<String> args) {
+       if(!systemArgs.containsAll(args)){
+           systemArgs.addAll(args);
+       }
+    }
+
+    public List<String> getAllArgs() {
+        List<String> allArgs = new ArrayList<String>(systemArgs.size() + args.size());
+        allArgs.addAll(systemArgs);
+        allArgs.addAll(args);
+        return allArgs;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectLibraryBinary.java
new file mode 100644
index 0000000..ca29374
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectLibraryBinary.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.Library;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public abstract class AbstractProjectLibraryBinary extends AbstractProjectNativeBinary implements LibraryBinaryInternal {
+
+    protected AbstractProjectLibraryBinary(Library library, Flavor flavor, ToolChainInternal toolChain, Platform targetPlatform, BuildType buildType,
+                                           BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+        super(library, flavor, toolChain, targetPlatform, buildType, namingScheme, resolver);
+    }
+
+    protected boolean hasSources() {
+        for (LanguageSourceSet sourceSet : getSource()) {
+            if (!sourceSet.getSource().isEmpty()) {
+                return true;
+            }
+            if (sourceSet.hasBuildDependencies()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public FileCollection getHeaderDirs() {
+        return new AbstractFileCollection() {
+            public String getDisplayName() {
+                return String.format("Headers for %s", getName());
+            }
+
+            public Set<File> getFiles() {
+                Set<File> headerDirs = new LinkedHashSet<File>();
+                for (HeaderExportingSourceSet sourceSet : getSource().withType(HeaderExportingSourceSet.class)) {
+                    headerDirs.addAll(sourceSet.getExportedHeaders().getSrcDirs());
+                }
+                return headerDirs;
+            }
+
+            @Override
+            public TaskDependency getBuildDependencies() {
+                DefaultTaskDependency dependency = new DefaultTaskDependency();
+                for (HeaderExportingSourceSet sourceSet : getSource().withType(HeaderExportingSourceSet.class)) {
+                    dependency.add(sourceSet.getBuildDependencies());
+                }
+                return dependency;
+            }
+        };
+    }
+
+    protected abstract class LibraryOutputs extends AbstractFileCollection {
+        public final Set<File> getFiles() {
+            if (hasOutputs()) {
+                return getOutputs();
+            }
+            return Collections.emptySet();
+        }
+
+        public final String getDisplayName() {
+            return AbstractProjectLibraryBinary.this.toString();
+        }
+
+        public final TaskDependency getBuildDependencies() {
+            if (hasOutputs()) {
+                return AbstractProjectLibraryBinary.this.getBuildDependencies();
+            }
+            return new DefaultTaskDependency();
+        }
+
+        protected abstract boolean hasOutputs();
+
+        protected abstract Set<File> getOutputs();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectNativeBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectNativeBinary.java
new file mode 100644
index 0000000..0f0cc9a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectNativeBinary.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.AbstractBuildableModelElement;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.nativebinaries.*;
+import org.gradle.nativebinaries.internal.resolve.NativeBinaryResolveResult;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public abstract class AbstractProjectNativeBinary extends AbstractBuildableModelElement implements ProjectNativeBinaryInternal {
+    private final ProjectNativeComponent component;
+    private final NotationParser<Object, Set<LanguageSourceSet>> sourcesNotationParser = SourceSetNotationParser.parser();
+    private final Set<? super Object> libs = new LinkedHashSet<Object>();
+    private final DomainObjectSet<LanguageSourceSet> source = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
+    private final DefaultTool linker = new DefaultTool();
+    private final DefaultTool staticLibArchiver = new DefaultTool();
+    private final NativeBinaryTasks tasks = new DefaultNativeBinaryTasks();
+    private final BinaryNamingScheme namingScheme;
+    private final Flavor flavor;
+    private final ToolChainInternal toolChain;
+    private final Platform targetPlatform;
+    private final BuildType buildType;
+    private final NativeDependencyResolver resolver;
+    private boolean buildable;
+
+    protected AbstractProjectNativeBinary(ProjectNativeComponent owner, Flavor flavor, ToolChainInternal toolChain, Platform targetPlatform, BuildType buildType,
+                                          BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+        this.component = owner;
+        this.namingScheme = namingScheme;
+        this.flavor = flavor;
+        this.toolChain = toolChain;
+        this.targetPlatform = targetPlatform;
+        this.buildType = buildType;
+        this.buildable = true;
+        this.resolver = resolver;
+        owner.getSource().all(new Action<LanguageSourceSet>() {
+            public void execute(LanguageSourceSet sourceSet) {
+                source.add(sourceSet);
+            }
+        });
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getDisplayName() {
+        return namingScheme.getDescription();
+    }
+
+    public String getName() {
+        return namingScheme.getLifecycleTaskName();
+    }
+
+    public ProjectNativeComponent getComponent() {
+        return component;
+    }
+
+    public Flavor getFlavor() {
+        return flavor;
+    }
+
+    public ToolChainInternal getToolChain() {
+        return toolChain;
+    }
+
+    public Platform getTargetPlatform() {
+        return targetPlatform;
+    }
+
+    public BuildType getBuildType() {
+        return buildType;
+    }
+
+    public DomainObjectSet<LanguageSourceSet> getSource() {
+        return source;
+    }
+
+    public void source(Object sources) {
+        source.addAll(sourcesNotationParser.parseNotation(sources));
+    }
+
+    public Tool getLinker() {
+        return linker;
+    }
+
+    public Tool getStaticLibArchiver() {
+        return staticLibArchiver;
+    }
+
+    public NativeBinaryTasks getTasks() {
+        return tasks;
+    }
+
+    public BinaryNamingScheme getNamingScheme() {
+        return namingScheme;
+    }
+
+    public Collection<NativeDependencySet> getLibs() {
+        return resolve(source.withType(DependentSourceSet.class)).getAllResults();
+    }
+
+    public Collection<NativeDependencySet> getLibs(DependentSourceSet sourceSet) {
+        return resolve(Collections.singleton(sourceSet)).getAllResults();
+    }
+
+    public void lib(Object notation) {
+        libs.add(notation);
+    }
+
+    public Collection<LibraryBinary> getDependentBinaries() {
+        return resolve(source.withType(DependentSourceSet.class)).getAllLibraryBinaries();
+    }
+
+    private NativeBinaryResolveResult resolve(Collection<? extends DependentSourceSet> sourceSets) {
+        Set<? super Object> allLibs = new LinkedHashSet<Object>(libs);
+        for (DependentSourceSet dependentSourceSet : sourceSets) {
+            allLibs.addAll(dependentSourceSet.getLibs());
+        }
+        NativeBinaryResolveResult resolution = new NativeBinaryResolveResult(this, allLibs);
+        resolver.resolve(resolution);
+        return resolution;
+    }
+
+    public boolean isBuildable() {
+        return buildable;
+    }
+
+    public void setBuildable(boolean buildable) {
+        this.buildable = buildable;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectNativeComponent.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectNativeComponent.java
new file mode 100644
index 0000000..140b980
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractProjectNativeComponent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.nativebinaries.NativeBinary;
+import org.gradle.util.GUtil;
+
+import java.util.Set;
+
+public abstract class AbstractProjectNativeComponent implements ProjectNativeComponentInternal {
+    private final NotationParser<Object, Set<LanguageSourceSet>> sourcesNotationParser = SourceSetNotationParser.parser();
+    private final NativeProjectComponentIdentifier id;
+    private final DomainObjectSet<LanguageSourceSet> sourceSets;
+    private final DefaultDomainObjectSet<NativeBinary> binaries;
+
+    private String baseName;
+
+    public AbstractProjectNativeComponent(NativeProjectComponentIdentifier id) {
+        this.id = id;
+        this.sourceSets = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
+        binaries = new DefaultDomainObjectSet<NativeBinary>(NativeBinary.class);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getProjectPath() {
+        return id.getProjectPath();
+    }
+
+    public String getName() {
+        return id.getName();
+    }
+
+    public DomainObjectSet<LanguageSourceSet> getSource() {
+        return sourceSets;
+    }
+
+    public void source(Object sources) {
+        sourceSets.addAll(sourcesNotationParser.parseNotation(sources));
+    }
+
+    public DomainObjectSet<NativeBinary> getBinaries() {
+        return binaries;
+    }
+
+    public String getBaseName() {
+        return GUtil.elvis(baseName, getName());
+    }
+
+    public void setBaseName(String baseName) {
+        this.baseName = baseName;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractTargetedProjectNativeComponent.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractTargetedProjectNativeComponent.java
new file mode 100644
index 0000000..333db6f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/AbstractTargetedProjectNativeComponent.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Named;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public abstract class AbstractTargetedProjectNativeComponent extends AbstractProjectNativeComponent implements TargetedNativeComponentInternal {
+
+    private final Set<String> targetPlatforms = new HashSet<String>();
+    private final Set<String> buildTypes = new HashSet<String>();
+    private final Set<String> flavors = new HashSet<String>();
+
+    public AbstractTargetedProjectNativeComponent(NativeProjectComponentIdentifier id) {
+        super(id);
+    }
+
+    public void targetFlavors(String... flavorSelectors) {
+        Collections.addAll(flavors, flavorSelectors);
+    }
+
+    public void targetPlatforms(String... platformSelectors) {
+        Collections.addAll(targetPlatforms, platformSelectors);
+    }
+
+    public void targetBuildTypes(String... buildTypeSelectors) {
+        Collections.addAll(buildTypes, buildTypeSelectors);
+    }
+
+    public Set<Flavor> chooseFlavors(Set<? extends Flavor> candidates) {
+        return chooseElements(Flavor.class, candidates, flavors);
+    }
+
+    public Set<BuildType> chooseBuildTypes(Set<? extends BuildType> candidates) {
+        return chooseElements(BuildType.class, candidates, buildTypes);
+    }
+
+    public Set<Platform> choosePlatforms(Set<? extends Platform> candidates) {
+        return chooseElements(Platform.class, candidates, targetPlatforms);
+    }
+
+    private <T extends Named> Set<T> chooseElements(Class<T> type, Set<? extends T> candidates, final Set<String> names) {
+        if (names.isEmpty()) {
+            return new LinkedHashSet<T>(candidates);
+        }
+
+        Set<String> unusedNames = new HashSet<String>(names);
+        Set<T> chosen = new LinkedHashSet<T>();
+        for (T candidate : candidates) {
+            if (unusedNames.remove(candidate.getName())) {
+                chosen.add(candidate);
+            }
+        }
+
+        if (!unusedNames.isEmpty()) {
+            throw new InvalidUserDataException(String.format("Invalid %s: '%s'", type.getSimpleName(), unusedNames.iterator().next()));
+        }
+
+        return chosen;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/BinaryToolSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/BinaryToolSpec.java
new file mode 100644
index 0000000..50485c3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/BinaryToolSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+
+import java.io.File;
+import java.util.List;
+
+public interface BinaryToolSpec extends CompileSpec {
+    File getTempDir();
+
+    void setTempDir(File tempDir);
+
+    List<String> getArgs();
+
+    void args(List<String> args);
+
+    List<String> getSystemArgs();
+
+    void systemArgs(List<String> args);
+
+    List<String> getAllArgs();
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultBuildType.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultBuildType.java
new file mode 100644
index 0000000..279cdcc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultBuildType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.BuildType;
+
+public class DefaultBuildType implements BuildType {
+    private final String name;
+
+    public DefaultBuildType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getDisplayName() {
+        return String.format("build type '%s'", name);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultBuildTypeContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultBuildTypeContainer.java
new file mode 100644
index 0000000..c718868
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultBuildTypeContainer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.BuildTypeContainer;
+
+public class DefaultBuildTypeContainer extends AbstractNamedDomainObjectContainer<BuildType> implements BuildTypeContainer {
+    public DefaultBuildTypeContainer(Instantiator instantiator) {
+        super(BuildType.class, instantiator);
+    }
+
+    @Override
+    protected BuildType doCreate(String name) {
+        return getInstantiator().newInstance(DefaultBuildType.class, name);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultExecutable.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultExecutable.java
new file mode 100755
index 0000000..928fa14
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultExecutable.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.Executable;
+
+public class DefaultExecutable extends AbstractTargetedProjectNativeComponent implements Executable {
+    public DefaultExecutable(NativeProjectComponentIdentifier id) {
+        super(id);
+    }
+
+    public String getDisplayName() {
+        return String.format("executable '%s'", getName());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultExecutableContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultExecutableContainer.java
new file mode 100644
index 0000000..4adb2c4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultExecutableContainer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.Executable;
+import org.gradle.nativebinaries.ExecutableContainer;
+
+public class DefaultExecutableContainer extends AbstractNamedDomainObjectContainer<Executable> implements ExecutableContainer {
+    private final Project project;
+
+    public DefaultExecutableContainer(Instantiator instantiator, Project project) {
+        super(Executable.class, instantiator);
+        this.project = project;
+    }
+
+    @Override
+    protected Executable doCreate(String name) {
+        NativeProjectComponentIdentifier id = new NativeProjectComponentIdentifier(project.getPath(), name);
+        return getInstantiator().newInstance(DefaultExecutable.class, id);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultFlavor.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultFlavor.java
new file mode 100644
index 0000000..1117813
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultFlavor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.Flavor;
+
+public class DefaultFlavor implements Flavor {
+    public static final String DEFAULT = "default";
+    private final String name;
+
+    public DefaultFlavor(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getDisplayName() {
+        return String.format("flavor '%s'", name);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultFlavorContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultFlavorContainer.java
new file mode 100644
index 0000000..1e62fc9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultFlavorContainer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.FlavorContainer;
+
+public class DefaultFlavorContainer extends AbstractNamedDomainObjectContainer<Flavor> implements FlavorContainer {
+    public DefaultFlavorContainer(Instantiator instantiator) {
+        super(Flavor.class, instantiator);
+    }
+
+    @Override
+    protected Flavor doCreate(String name) {
+        return getInstantiator().newInstance(DefaultFlavor.class, name);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLibrary.java
new file mode 100755
index 0000000..2ee1fb3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLibrary.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.nativebinaries.internal;
+
+import org.gradle.nativebinaries.Library;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+public class DefaultLibrary extends AbstractTargetedProjectNativeComponent implements Library {
+    public DefaultLibrary(NativeProjectComponentIdentifier id) {
+        super(id);
+    }
+
+    public String getDisplayName() {
+        return String.format("library '%s'", getName());
+    }
+
+    public NativeLibraryRequirement getShared() {
+        return new ProjectNativeLibraryRequirement(getProjectPath(), this.getName(), "shared");
+    }
+
+    public NativeLibraryRequirement getStatic() {
+        return new ProjectNativeLibraryRequirement(getProjectPath(), this.getName(), "static");
+    }
+
+    public NativeLibraryRequirement getApi() {
+        return new ProjectNativeLibraryRequirement(getProjectPath(), this.getName(), "api");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLibraryContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLibraryContainer.java
new file mode 100644
index 0000000..90c573a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLibraryContainer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.Library;
+import org.gradle.nativebinaries.LibraryContainer;
+
+public class DefaultLibraryContainer extends AbstractNamedDomainObjectContainer<Library> implements LibraryContainer {
+    private final Project project;
+
+    public DefaultLibraryContainer(Instantiator instantiator, Project project) {
+        super(Library.class, instantiator);
+        this.project = project;
+    }
+
+    @Override
+    protected Library doCreate(String name) {
+        NativeProjectComponentIdentifier id = new NativeProjectComponentIdentifier(project.getPath(), name);
+        return getInstantiator().newInstance(DefaultLibrary.class, id);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLinkerSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLinkerSpec.java
new file mode 100644
index 0000000..990f025
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultLinkerSpec.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class DefaultLinkerSpec extends AbstractBinaryToolSpec implements LinkerSpec {
+
+    private final List<File> objectFiles = new ArrayList<File>();
+    private final List<File> libraries = new ArrayList<File>();
+    private final List<File> libraryPath = new ArrayList<File>();
+    private File outputFile;
+
+    public List<File> getObjectFiles() {
+        return objectFiles;
+    }
+
+    public void objectFiles(Iterable<File> objectFiles) {
+        addAll(this.objectFiles, objectFiles);
+    }
+
+    public List<File> getLibraries() {
+        return libraries;
+    }
+
+    public void libraries(Iterable<File> libraries) {
+        addAll(this.libraries, libraries);
+    }
+
+    public List<File> getLibraryPath() {
+        return libraryPath;
+    }
+
+    public void libraryPath(File... libraryPath) {
+        Collections.addAll(this.libraryPath, libraryPath);
+    }
+
+    public File getOutputFile() {
+        return outputFile;
+    }
+
+    public void setOutputFile(File outputFile) {
+        this.outputFile = outputFile;
+    }
+
+    private void addAll(List<File> list, Iterable<File> iterable) {
+        for (File file : iterable) {
+            list.add(file);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultNativeBinaryTasks.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultNativeBinaryTasks.java
new file mode 100644
index 0000000..8783210
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultNativeBinaryTasks.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.nativebinaries.NativeBinaryTasks;
+import org.gradle.nativebinaries.tasks.AbstractLinkTask;
+import org.gradle.nativebinaries.tasks.BuildBinaryTask;
+import org.gradle.nativebinaries.tasks.CreateStaticLibrary;
+
+public class DefaultNativeBinaryTasks extends DefaultDomainObjectSet<Task> implements NativeBinaryTasks {
+    public DefaultNativeBinaryTasks() {
+        super(Task.class);
+    }
+
+    public AbstractLinkTask getLink() {
+        return findOnlyWithType(AbstractLinkTask.class);
+    }
+
+    public CreateStaticLibrary getCreateStaticLib() {
+        return findOnlyWithType(CreateStaticLibrary.class);
+    }
+
+    public BuildBinaryTask getBuilder() {
+        BuildBinaryTask link = getLink();
+        return link == null ? getCreateStaticLib() : link;
+    }
+
+    private <T extends Task> T findOnlyWithType(Class<T> type) {
+        DomainObjectSet<T> tasks = withType(type);
+        if (tasks.size() == 0) {
+            return null;
+        }
+        if (tasks.size() > 1) {
+            throw new UnknownDomainObjectException(String.format("Multiple task with type '%s' found", type.getSimpleName()));
+        }
+        return tasks.iterator().next();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultStaticLibraryArchiverSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultStaticLibraryArchiverSpec.java
new file mode 100644
index 0000000..416c788
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultStaticLibraryArchiverSpec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultStaticLibraryArchiverSpec extends AbstractBinaryToolSpec implements StaticLibraryArchiverSpec {
+
+    private final List<File> objectFiles = new ArrayList<File>();
+    private File outputFile;
+
+    public List<File> getObjectFiles() {
+        return objectFiles;
+    }
+
+    public void objectFiles(Iterable<File> objectFiles) {
+        for (File objectFile : objectFiles) {
+            this.objectFiles.add(objectFile);
+        }
+    }
+
+    public File getOutputFile() {
+        return outputFile;
+    }
+
+    public void setOutputFile(File outputFile) {
+        this.outputFile = outputFile;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultTool.java
new file mode 100644
index 0000000..1669408
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/DefaultTool.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.Tool;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A tool that is part of a tool chain (compiler, linker, assembler, etc).
+ */
+public class DefaultTool implements Tool {
+    private final ArrayList<String> args = new ArrayList<String>();
+
+    public List<String> getArgs() {
+        return args;
+    }
+
+    public void args(String... args) {
+        Collections.addAll(this.args, args);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/LibraryBinaryInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/LibraryBinaryInternal.java
new file mode 100644
index 0000000..6f1f59c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/LibraryBinaryInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.nativebinaries.LibraryBinary;
+
+public interface LibraryBinaryInternal extends LibraryBinary {
+
+    FileCollection getHeaderDirs();
+
+    FileCollection getLinkFiles();
+
+    FileCollection getRuntimeFiles();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/LinkerSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/LinkerSpec.java
new file mode 100644
index 0000000..202800c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/LinkerSpec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A high level interface to the linker, specifying what is to be linked and how.
+ */
+public interface LinkerSpec extends BinaryToolSpec {
+
+    List<File> getObjectFiles();
+
+    void objectFiles(Iterable<File> objectFiles);
+
+    List<File> getLibraries();
+
+    void libraries(Iterable<File> libraries);
+
+    // TODO:DAZ Work out how to test this
+    List<File> getLibraryPath();
+
+    void libraryPath(File... libraryPath);
+
+    File getOutputFile();
+
+    void setOutputFile(File outputFile);
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/NativeBinaryServices.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/NativeBinaryServices.java
new file mode 100644
index 0000000..66150f5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/NativeBinaryServices.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.PluginServiceRegistry;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolverServices;
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.DefaultVisualStudioLocator;
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.DefaultWindowsSdkLocator;
+
+public class NativeBinaryServices implements PluginServiceRegistry {
+    public void registerGlobalServices(ServiceRegistration registration) {
+    }
+
+    public void registerBuildServices(ServiceRegistration registration) {
+        registration.add(DefaultVisualStudioLocator.class);
+        registration.add(DefaultWindowsSdkLocator.class);
+    }
+
+    public void registerProjectServices(ServiceRegistration registration) {
+        registration.addProvider(new NativeDependencyResolverServices());
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/NativeProjectComponentIdentifier.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/NativeProjectComponentIdentifier.java
new file mode 100644
index 0000000..5d9c294
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/NativeProjectComponentIdentifier.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+
+/**
+ * An identifier for a native component that is built as part of the current build.
+ */
+public class NativeProjectComponentIdentifier implements ProjectComponentIdentifier {
+    private final String projectPath;
+    private final String name;
+
+    public NativeProjectComponentIdentifier(String projectPath, String name) {
+        this.projectPath = projectPath;
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    public String getDisplayName() {
+        return String.format("Project native component: %s.%s", projectPath, name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof NativeProjectComponentIdentifier)) {
+            return false;
+        }
+
+        NativeProjectComponentIdentifier that = (NativeProjectComponentIdentifier) o;
+        return name.equals(that.name) && projectPath.equals(that.projectPath);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = projectPath.hashCode();
+        result = 31 * result + name.hashCode();
+        return result;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectExecutableBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectExecutableBinary.java
new file mode 100644
index 0000000..185697b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectExecutableBinary.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Executable;
+import org.gradle.nativebinaries.ExecutableBinary;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+
+public class ProjectExecutableBinary extends AbstractProjectNativeBinary implements ExecutableBinary {
+    private File executableFile;
+
+    public ProjectExecutableBinary(Executable executable, Flavor flavor, ToolChainInternal toolChain, Platform platform, BuildType buildType,
+                                   BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+        super(executable, flavor, toolChain, platform, buildType, namingScheme, resolver);
+    }
+
+    public File getExecutableFile() {
+        return executableFile;
+    }
+
+    public void setExecutableFile(File executableFile) {
+        this.executableFile = executableFile;
+    }
+
+    public File getPrimaryOutput() {
+        return getExecutableFile();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeBinaryInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeBinaryInternal.java
new file mode 100644
index 0000000..a9c976d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeBinaryInternal.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.language.DependentSourceSet;
+import org.gradle.language.base.internal.BinaryInternal;
+import org.gradle.nativebinaries.LibraryBinary;
+import org.gradle.nativebinaries.NativeDependencySet;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+
+import java.io.File;
+import java.util.Collection;
+
+public interface ProjectNativeBinaryInternal extends ProjectNativeBinary, BinaryInternal {
+    File getPrimaryOutput();
+
+    Collection<NativeDependencySet> getLibs(DependentSourceSet sourceSet);
+
+    void setBuildable(boolean buildable);
+
+    Collection<LibraryBinary> getDependentBinaries();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeComponentInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeComponentInternal.java
new file mode 100644
index 0000000..833e311
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeComponentInternal.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.ProjectNativeComponent;
+
+public interface ProjectNativeComponentInternal extends ProjectNativeComponent {
+
+    // TODO:DAZ Use BuildComponentIdentifier
+    String getProjectPath();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeLibraryRequirement.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeLibraryRequirement.java
new file mode 100644
index 0000000..fdfb92f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectNativeLibraryRequirement.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+public class ProjectNativeLibraryRequirement implements NativeLibraryRequirement {
+    private final String projectPath;
+    private final String libraryName;
+    private final String linkage;
+
+    public ProjectNativeLibraryRequirement(String libraryName, String linkage) {
+        this.projectPath = null;
+        this.libraryName = libraryName;
+        this.linkage = linkage;
+    }
+
+    public ProjectNativeLibraryRequirement(String projectPath, String libraryName, String linkage) {
+        this.projectPath = projectPath;
+        this.libraryName = libraryName;
+        this.linkage = linkage;
+    }
+
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    public String getLibraryName() {
+        return libraryName;
+    }
+
+    public String getLinkage() {
+        return linkage;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectSharedLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectSharedLibraryBinary.java
new file mode 100644
index 0000000..53b65b4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectSharedLibraryBinary.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.language.rc.WindowsResourceSet;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.Library;
+import org.gradle.nativebinaries.SharedLibraryBinary;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+public class ProjectSharedLibraryBinary extends AbstractProjectLibraryBinary implements SharedLibraryBinary {
+    private File sharedLibraryFile;
+    private File sharedLibraryLinkFile;
+
+    public ProjectSharedLibraryBinary(Library library, Flavor flavor, ToolChainInternal toolChain, Platform platform, BuildType buildType,
+                                      BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+        super(library, flavor, toolChain, platform, buildType, namingScheme, resolver);
+    }
+
+    public File getSharedLibraryFile() {
+        return sharedLibraryFile;
+    }
+
+    public void setSharedLibraryFile(File sharedLibraryFile) {
+        this.sharedLibraryFile = sharedLibraryFile;
+    }
+
+    public File getSharedLibraryLinkFile() {
+        return sharedLibraryLinkFile;
+    }
+
+    public void setSharedLibraryLinkFile(File sharedLibraryLinkFile) {
+        this.sharedLibraryLinkFile = sharedLibraryLinkFile;
+    }
+
+    public File getPrimaryOutput() {
+        return getSharedLibraryFile();
+    }
+
+    public FileCollection getLinkFiles() {
+        return new SharedLibraryLinkOutputs();
+    }
+
+    public FileCollection getRuntimeFiles() {
+        return new SharedLibraryRuntimeOutputs();
+    }
+
+    private class SharedLibraryLinkOutputs extends LibraryOutputs {
+        @Override
+        protected boolean hasOutputs() {
+            return hasSources() && !isResourceOnly();
+        }
+
+        @Override
+        protected Set<File> getOutputs() {
+            return Collections.singleton(getSharedLibraryLinkFile());
+        }
+
+        private boolean isResourceOnly() {
+            return hasResources() && !hasExportedSymbols();
+        }
+
+        private boolean hasResources() {
+            for (WindowsResourceSet windowsResourceSet : getSource().withType(WindowsResourceSet.class)) {
+                if (!windowsResourceSet.getSource().isEmpty()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean hasExportedSymbols() {
+            // TODO:DAZ This is a very rough approximation: actually inspect the binary to determine if there are exported symbols
+            for (LanguageSourceSet languageSourceSet : getSource()) {
+                if (!(languageSourceSet instanceof WindowsResourceSet)) {
+                    if (!languageSourceSet.getSource().isEmpty()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    private class SharedLibraryRuntimeOutputs extends LibraryOutputs {
+        @Override
+        protected boolean hasOutputs() {
+            return hasSources();
+        }
+
+        @Override
+        protected Set<File> getOutputs() {
+            return Collections.singleton(getSharedLibraryFile());
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectStaticLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectStaticLibraryBinary.java
new file mode 100644
index 0000000..9a08bc7
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/ProjectStaticLibraryBinary.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.Library;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ProjectStaticLibraryBinary extends AbstractProjectLibraryBinary implements StaticLibraryBinaryInternal {
+    private final List<FileCollection> additionalLinkFiles = new ArrayList<FileCollection>();
+    private File staticLibraryFile;
+
+    public ProjectStaticLibraryBinary(Library library, Flavor flavor, ToolChainInternal toolChain, Platform platform, BuildType buildType,
+                                      BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+        super(library, flavor, toolChain, platform, buildType, namingScheme, resolver);
+    }
+
+    public File getStaticLibraryFile() {
+        return staticLibraryFile;
+    }
+
+    public void setStaticLibraryFile(File staticLibraryFile) {
+        this.staticLibraryFile = staticLibraryFile;
+    }
+
+    public File getPrimaryOutput() {
+        return getStaticLibraryFile();
+    }
+
+    public void additionalLinkFiles(FileCollection files) {
+        this.additionalLinkFiles.add(files);
+    }
+
+    public FileCollection getLinkFiles() {
+        return new StaticLibraryLinkOutputs();
+    }
+
+    public FileCollection getRuntimeFiles() {
+        return new SimpleFileCollection();
+    }
+
+    private class StaticLibraryLinkOutputs extends LibraryOutputs {
+        @Override
+        protected boolean hasOutputs() {
+            return hasSources() || !additionalLinkFiles.isEmpty();
+        }
+
+        @Override
+        protected Set<File> getOutputs() {
+            Set<File> allFiles = new LinkedHashSet<File>();
+            if (hasSources()) {
+                allFiles.add(getStaticLibraryFile());
+            }
+            for (FileCollection resourceSet : additionalLinkFiles) {
+                allFiles.addAll(resourceSet.getFiles());
+            }
+            return allFiles;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/SharedLibraryLinkerSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/SharedLibraryLinkerSpec.java
new file mode 100644
index 0000000..09fb3f7
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/SharedLibraryLinkerSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+public interface SharedLibraryLinkerSpec extends LinkerSpec {
+    String getInstallName();
+
+    void setInstallName(String path);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/SourceSetNotationParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/SourceSetNotationParser.java
new file mode 100644
index 0000000..f01a520
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/SourceSetNotationParser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.TypeInfo;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class SourceSetNotationParser {
+    public static NotationParser<Object, Set<LanguageSourceSet>> parser() {
+        return new NotationParserBuilder<Set<LanguageSourceSet>>()
+                .resultingType(new TypeInfo<Set<LanguageSourceSet>>(Set.class))
+                .parser(new FunctionalSourceSetConverter())
+                .parser(new SingleLanguageSourceSetConverter())
+                .parser(new LanguageSourceSetCollectionConverter())
+                .toComposite();
+    }
+
+    private static class FunctionalSourceSetConverter extends TypedNotationParser<FunctionalSourceSet, Set<LanguageSourceSet>> {
+        private FunctionalSourceSetConverter() {
+            super(FunctionalSourceSet.class);
+        }
+
+        @Override
+        protected Set<LanguageSourceSet> parseType(FunctionalSourceSet notation) {
+            return notation;
+        }
+    }
+
+    private static class SingleLanguageSourceSetConverter extends TypedNotationParser<LanguageSourceSet, Set<LanguageSourceSet>> {
+        private SingleLanguageSourceSetConverter() {
+            super(LanguageSourceSet.class);
+        }
+
+        @Override
+        protected Set<LanguageSourceSet> parseType(LanguageSourceSet notation) {
+            return Collections.singleton(notation);
+        }
+    }
+
+    private static class LanguageSourceSetCollectionConverter extends TypedNotationParser<Collection<LanguageSourceSet>, Set<LanguageSourceSet>> {
+        private LanguageSourceSetCollectionConverter() {
+            super(new TypeInfo<Collection<LanguageSourceSet>>(Collection.class));
+        }
+
+        @Override
+        protected Set<LanguageSourceSet> parseType(Collection<LanguageSourceSet> notation) {
+            return new LinkedHashSet<LanguageSourceSet>(notation);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/StaticLibraryArchiverSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/StaticLibraryArchiverSpec.java
new file mode 100644
index 0000000..67983ae
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/StaticLibraryArchiverSpec.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal;
+
+import java.io.File;
+import java.util.List;
+
+public interface StaticLibraryArchiverSpec extends BinaryToolSpec {
+
+    File getOutputFile();
+
+    void setOutputFile(File outputFile);
+
+    List<File> getObjectFiles();
+
+    void objectFiles(Iterable<File> source);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/StaticLibraryBinaryInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/StaticLibraryBinaryInternal.java
new file mode 100644
index 0000000..dcaeac6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/StaticLibraryBinaryInternal.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.nativebinaries.StaticLibraryBinary;
+
+public interface StaticLibraryBinaryInternal extends StaticLibraryBinary {
+    /**
+     * Add some additional files required at link time.
+     */
+    void additionalLinkFiles(FileCollection files);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/TargetedNativeComponentInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/TargetedNativeComponentInternal.java
new file mode 100644
index 0000000..a6a6111
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/TargetedNativeComponentInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal;
+
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.TargetedNativeComponent;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.util.Set;
+
+public interface TargetedNativeComponentInternal extends TargetedNativeComponent {
+    Set<Flavor> chooseFlavors(Set<? extends Flavor> candidates);
+    Set<Platform> choosePlatforms(Set<? extends Platform> candidates);
+    Set<BuildType> chooseBuildTypes(Set<? extends BuildType> candidates);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ApplySourceSetConventions.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ApplySourceSetConventions.java
new file mode 100644
index 0000000..1448d97
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ApplySourceSetConventions.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+
+/**
+ * Applies source set conventions when no source directories are explicitly configured.
+ */
+public class ApplySourceSetConventions implements Action<ProjectInternal> {
+    public void execute(ProjectInternal project) {
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        for (FunctionalSourceSet functionalSourceSet : projectSourceSet) {
+            for (LanguageSourceSet languageSourceSet : functionalSourceSet) {
+                // Only apply default locations when none explicitly configured
+                if (languageSourceSet.getSource().getSrcDirs().isEmpty()) {
+                    languageSourceSet.getSource().srcDir(String.format("src/%s/%s", functionalSourceSet.getName(), languageSourceSet.getName()));
+                }
+            }
+            for (HeaderExportingSourceSet headerSourceSet : functionalSourceSet.withType(HeaderExportingSourceSet.class)) {
+                // Only apply default locations when none explicitly configured
+                if (headerSourceSet.getExportedHeaders().getSrcDirs().isEmpty()) {
+                    headerSourceSet.getExportedHeaders().srcDir(String.format("src/%s/headers", functionalSourceSet.getName()));
+                }
+
+                headerSourceSet.getImplicitHeaders().setSrcDirs(headerSourceSet.getSource().getSrcDirs());
+                headerSourceSet.getImplicitHeaders().include("**/*.h");
+            }
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ConfigureGeneratedSourceSets.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ConfigureGeneratedSourceSets.java
new file mode 100644
index 0000000..7bfa909
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ConfigureGeneratedSourceSets.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.language.base.internal.LanguageSourceSetInternal;
+
+/**
+ * Wires generator tasks into language source sets.
+ */
+public class ConfigureGeneratedSourceSets implements Action<ProjectInternal> {
+    public void execute(ProjectInternal project) {
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        for (FunctionalSourceSet functionalSourceSet : projectSourceSet) {
+            for (LanguageSourceSetInternal languageSourceSet : functionalSourceSet.withType(LanguageSourceSetInternal.class)) {
+                Task generatorTask = languageSourceSet.getGeneratorTask();
+                if (generatorTask != null) {
+                    languageSourceSet.builtBy(generatorTask);
+                    maybeSetSourceDir(languageSourceSet.getSource(), generatorTask, "sourceDir");
+                    if (languageSourceSet instanceof HeaderExportingSourceSet) {
+                        maybeSetSourceDir(((HeaderExportingSourceSet) languageSourceSet).getExportedHeaders(), generatorTask, "headerDir");
+                    }
+                }
+            }
+        }
+    }
+
+    private void maybeSetSourceDir(SourceDirectorySet sourceSet, Task task, String propertyName) {
+        // TODO:DAZ Handle multiple output directories
+        Object value = task.property(propertyName);
+        if (value != null) {
+            sourceSet.srcDir(value);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultBuildTypes.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultBuildTypes.java
new file mode 100644
index 0000000..50841f9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultBuildTypes.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.model.ModelFinalizer;
+import org.gradle.nativebinaries.BuildTypeContainer;
+
+public class CreateDefaultBuildTypes extends ModelFinalizer {
+
+    @SuppressWarnings("UnusedDeclaration")
+    void createDefaultPlatforms(BuildTypeContainer buildTypes) {
+        if (buildTypes.isEmpty()) {
+            buildTypes.create("debug");
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultFlavors.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultFlavors.java
new file mode 100644
index 0000000..0f3a319
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultFlavors.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.model.ModelFinalizer;
+import org.gradle.nativebinaries.FlavorContainer;
+import org.gradle.nativebinaries.internal.DefaultFlavor;
+
+public class CreateDefaultFlavors extends ModelFinalizer {
+
+    @SuppressWarnings("UnusedDeclaration")
+    void createDefaultFlavor(FlavorContainer flavors) {
+        if (flavors.isEmpty()) {
+            flavors.create(DefaultFlavor.DEFAULT);
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultPlatform.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultPlatform.java
new file mode 100644
index 0000000..cc05654
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultPlatform.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.model.ModelFinalizer;
+import org.gradle.nativebinaries.platform.PlatformContainer;
+
+public class CreateDefaultPlatform extends ModelFinalizer {
+    @SuppressWarnings("UnusedDeclaration")
+    void createDefaultPlatforms(PlatformContainer platforms) {
+        if (platforms.isEmpty()) {
+            platforms.create("current");
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateNativeBinaries.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateNativeBinaries.java
new file mode 100644
index 0000000..14b6379
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/CreateNativeBinaries.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.internal.BinaryNamingSchemeBuilder;
+import org.gradle.language.base.internal.DefaultBinaryNamingSchemeBuilder;
+import org.gradle.model.ModelRule;
+import org.gradle.nativebinaries.*;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.PlatformContainer;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class CreateNativeBinaries extends ModelRule {
+    private final Instantiator instantiator;
+    private final ProjectInternal project;
+    private final NativeDependencyResolver resolver;
+
+    public CreateNativeBinaries(Instantiator instantiator, ProjectInternal project, NativeDependencyResolver resolver) {
+        this.instantiator = instantiator;
+        this.project = project;
+        this.resolver = resolver;
+    }
+
+    public void create(BinaryContainer binaries, ToolChainRegistryInternal toolChains,
+                       PlatformContainer platforms, BuildTypeContainer buildTypes, FlavorContainer flavors) {
+        // TODO:DAZ Work out the right way to make these containers available to binaries.all
+        project.getExtensions().add("platforms", platforms);
+        project.getExtensions().add("buildTypes", buildTypes);
+        project.getExtensions().add("flavors", flavors);
+
+        Action<ProjectNativeBinary> configureBinaryAction = new ProjectNativeBinaryInitializer(project);
+        NativeBinariesFactory factory = new DefaultNativeBinariesFactory(instantiator, configureBinaryAction, resolver);
+        BinaryNamingSchemeBuilder namingSchemeBuilder = new DefaultBinaryNamingSchemeBuilder();
+        Action<ProjectNativeComponent> createBinariesAction =
+                new ProjectNativeComponentInitializer(factory, namingSchemeBuilder, toolChains, platforms, buildTypes, flavors);
+
+        for (ProjectNativeComponent component : allComponents()) {
+            createBinariesAction.execute(component);
+            binaries.addAll(component.getBinaries());
+        }
+    }
+
+    private Collection<ProjectNativeComponent> allComponents() {
+        ExecutableContainer executables = project.getExtensions().getByType(ExecutableContainer.class);
+        LibraryContainer libraries = project.getExtensions().getByType(LibraryContainer.class);
+
+        List<ProjectNativeComponent> components = new ArrayList<ProjectNativeComponent>();
+        for (Library library : libraries) {
+            components.add(library);
+        }
+        for (Executable executable : executables) {
+            components.add(executable);
+        }
+        return components;
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/DefaultNativeBinariesFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/DefaultNativeBinariesFactory.java
new file mode 100644
index 0000000..7d3282b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/DefaultNativeBinariesFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.language.base.internal.BinaryNamingSchemeBuilder;
+import org.gradle.nativebinaries.*;
+import org.gradle.nativebinaries.internal.ProjectExecutableBinary;
+import org.gradle.nativebinaries.internal.ProjectSharedLibraryBinary;
+import org.gradle.nativebinaries.internal.ProjectStaticLibraryBinary;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+
+class DefaultNativeBinariesFactory implements NativeBinariesFactory {
+    private final Instantiator instantiator;
+    private final Action<ProjectNativeBinary> configureAction;
+    private final NativeDependencyResolver resolver;
+
+    DefaultNativeBinariesFactory(Instantiator instantiator, Action<ProjectNativeBinary> configureAction, NativeDependencyResolver resolver) {
+        this.configureAction = configureAction;
+        this.instantiator = instantiator;
+        this.resolver = resolver;
+    }
+
+    public void createNativeBinaries(ProjectNativeComponent component, BinaryNamingSchemeBuilder namingScheme, ToolChain toolChain, Platform platform, BuildType buildType, Flavor flavor) {
+        if (component instanceof Library) {
+            createNativeBinary(ProjectSharedLibraryBinary.class, component, namingScheme.withTypeString("SharedLibrary").build(), toolChain, platform, buildType, flavor);
+            createNativeBinary(ProjectStaticLibraryBinary.class, component, namingScheme.withTypeString("StaticLibrary").build(), toolChain, platform, buildType, flavor);
+        } else {
+            createNativeBinary(ProjectExecutableBinary.class, component, namingScheme.withTypeString("Executable").build(), toolChain, platform, buildType, flavor);
+        }
+    }
+
+    private void createNativeBinary(Class<? extends ProjectNativeBinary> type, ProjectNativeComponent component, BinaryNamingScheme namingScheme,
+                            ToolChain toolChain, Platform platform, BuildType buildType, Flavor flavor) {
+        ProjectNativeBinary nativeBinary = instantiator.newInstance(type, component, flavor, toolChain, platform, buildType, namingScheme, resolver);
+        setupDefaults(nativeBinary);
+        component.getBinaries().add(nativeBinary);
+    }
+
+    private void setupDefaults(ProjectNativeBinary nativeBinary) {
+        configureAction.execute(nativeBinary);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/NativeBinariesFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/NativeBinariesFactory.java
new file mode 100644
index 0000000..c90d65d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/NativeBinariesFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.language.base.internal.BinaryNamingSchemeBuilder;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+
+public interface NativeBinariesFactory {
+    void createNativeBinaries(ProjectNativeComponent component, BinaryNamingSchemeBuilder namingScheme, ToolChain toolChain, Platform platform, BuildType buildType, Flavor flavor);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeBinaryInitializer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeBinaryInitializer.java
new file mode 100644
index 0000000..e0da6c2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeBinaryInitializer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.nativebinaries.ExecutableBinary;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+import org.gradle.nativebinaries.SharedLibraryBinary;
+import org.gradle.nativebinaries.StaticLibraryBinary;
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+
+class ProjectNativeBinaryInitializer implements Action<ProjectNativeBinary> {
+    private final File binariesOutputDir;
+
+    ProjectNativeBinaryInitializer(Project project) {
+        binariesOutputDir = new File(project.getBuildDir(), "binaries");
+    }
+
+    public void execute(ProjectNativeBinary nativeBinary) {
+        ToolChainInternal tc = (ToolChainInternal) nativeBinary.getToolChain();
+        BinaryNamingScheme namingScheme = ((ProjectNativeBinaryInternal) nativeBinary).getNamingScheme();
+        File binaryOutputDir = new File(binariesOutputDir, namingScheme.getOutputDirectoryBase());
+        String baseName = nativeBinary.getComponent().getBaseName();
+
+        if (nativeBinary instanceof ExecutableBinary) {
+            ((ExecutableBinary) nativeBinary).setExecutableFile(new File(binaryOutputDir, tc.getExecutableName(baseName)));
+        } else if (nativeBinary instanceof SharedLibraryBinary) {
+            ((SharedLibraryBinary) nativeBinary).setSharedLibraryFile(new File(binaryOutputDir, tc.getSharedLibraryName(baseName)));
+            ((SharedLibraryBinary) nativeBinary).setSharedLibraryLinkFile(new File(binaryOutputDir, tc.getSharedLibraryLinkFileName(baseName)));
+        } else if (nativeBinary instanceof StaticLibraryBinary) {
+            ((StaticLibraryBinary) nativeBinary).setStaticLibraryFile(new File(binaryOutputDir, tc.getStaticLibraryName(baseName)));
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeComponentInitializer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeComponentInitializer.java
new file mode 100644
index 0000000..92e1a17
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeComponentInitializer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.language.base.internal.BinaryNamingSchemeBuilder;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+import org.gradle.nativebinaries.internal.TargetedNativeComponentInternal;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class ProjectNativeComponentInitializer implements Action<ProjectNativeComponent> {
+    private final NativeBinariesFactory factory;
+    private final ToolChainRegistryInternal toolChainRegistry;
+    private final Set<Platform> allPlatforms = new LinkedHashSet<Platform>();
+    private final Set<BuildType> allBuildTypes = new LinkedHashSet<BuildType>();
+    private final Set<Flavor> allFlavors = new LinkedHashSet<Flavor>();
+    private final BinaryNamingSchemeBuilder namingSchemeBuilder;
+
+    public ProjectNativeComponentInitializer(NativeBinariesFactory factory, BinaryNamingSchemeBuilder namingSchemeBuilder, ToolChainRegistryInternal toolChainRegistry,
+                                             Collection<? extends Platform> allPlatforms, Collection<? extends BuildType> allBuildTypes, Collection<? extends Flavor> allFlavors) {
+        this.factory = factory;
+        this.namingSchemeBuilder = namingSchemeBuilder;
+        this.toolChainRegistry = toolChainRegistry;
+        this.allPlatforms.addAll(allPlatforms);
+        this.allBuildTypes.addAll(allBuildTypes);
+        this.allFlavors.addAll(allFlavors);
+    }
+
+    public void execute(ProjectNativeComponent projectNativeComponent) {
+        TargetedNativeComponentInternal targetedComponent = (TargetedNativeComponentInternal) projectNativeComponent;
+        for (Platform platform : targetedComponent.choosePlatforms(allPlatforms)) {
+            ToolChain toolChain = toolChainRegistry.getForPlatform(platform);
+            for (BuildType buildType : targetedComponent.chooseBuildTypes(allBuildTypes)) {
+                for (Flavor flavor : targetedComponent.chooseFlavors(allFlavors)) {
+                    BinaryNamingSchemeBuilder namingScheme = initializeNamingScheme(targetedComponent, projectNativeComponent.getName(), platform, buildType, flavor);
+                    factory.createNativeBinaries(projectNativeComponent, namingScheme, toolChain, platform, buildType, flavor);
+                }
+            }
+        }
+    }
+
+    private BinaryNamingSchemeBuilder initializeNamingScheme(TargetedNativeComponentInternal component, String name, Platform platform, BuildType buildType, Flavor flavor) {
+        BinaryNamingSchemeBuilder builder = namingSchemeBuilder.withComponentName(name);
+        if (usePlatformDimension(component)) {
+            builder = builder.withVariantDimension(platform.getName());
+        }
+        if (useBuildTypeDimension(component)) {
+            builder = builder.withVariantDimension(buildType.getName());
+        }
+        if (useFlavorDimension(component)) {
+            builder = builder.withVariantDimension(flavor.getName());
+        }
+        return builder;
+    }
+
+    private boolean usePlatformDimension(TargetedNativeComponentInternal component) {
+        return component.choosePlatforms(allPlatforms).size() > 1;
+    }
+
+    private boolean useBuildTypeDimension(TargetedNativeComponentInternal component) {
+        return component.chooseBuildTypes(allBuildTypes).size() > 1;
+    }
+
+    private boolean useFlavorDimension(TargetedNativeComponentInternal component) {
+        return component.chooseFlavors(allFlavors).size() > 1;
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/RepositoriesFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/RepositoriesFactory.java
new file mode 100644
index 0000000..496e0c6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/configure/RepositoriesFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.Namer;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.model.internal.Inputs;
+import org.gradle.model.internal.ModelCreator;
+import org.gradle.nativebinaries.*;
+import org.gradle.nativebinaries.internal.prebuilt.DefaultPrebuiltLibraries;
+import org.gradle.nativebinaries.internal.prebuilt.PrebuiltLibraryInitializer;
+import org.gradle.nativebinaries.platform.PlatformContainer;
+
+public class RepositoriesFactory implements ModelCreator<Repositories> {
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+
+    public RepositoriesFactory(Instantiator instantiator, FileResolver fileResolver) {
+        this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
+    }
+
+    public Repositories create(Inputs inputs) {
+        FlavorContainer flavors = inputs.get(0, FlavorContainer.class);
+        PlatformContainer platforms = inputs.get(1, PlatformContainer.class);
+        BuildTypeContainer buildTypes = inputs.get(2, BuildTypeContainer.class);
+        Action<PrebuiltLibrary> initializer = new PrebuiltLibraryInitializer(instantiator, platforms, buildTypes, flavors);
+        return new DefaultRepositories(instantiator, fileResolver, initializer);
+    }
+
+    public Class<Repositories> getType() {
+        return Repositories.class;
+    }
+
+    private static class DefaultRepositories extends DefaultPolymorphicDomainObjectContainer<ArtifactRepository> implements Repositories {
+        private DefaultRepositories(final Instantiator instantiator, final FileResolver fileResolver, final Action<PrebuiltLibrary> binaryFactory) {
+            super(ArtifactRepository.class, instantiator, new ArtifactRepositoryNamer());
+            registerFactory(PrebuiltLibraries.class, new NamedDomainObjectFactory<PrebuiltLibraries>() {
+                public PrebuiltLibraries create(String name) {
+                    return instantiator.newInstance(DefaultPrebuiltLibraries.class, name, instantiator, fileResolver, binaryFactory);
+                }
+            });
+        }
+    }
+
+    private static class ArtifactRepositoryNamer implements Namer<ArtifactRepository> {
+        public String determineName(ArtifactRepository object) {
+            return object.getName();
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/AbstractPrebuiltLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/AbstractPrebuiltLibraryBinary.java
new file mode 100644
index 0000000..4172551
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/AbstractPrebuiltLibraryBinary.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.FileCollectionAdapter;
+import org.gradle.api.internal.file.collections.MinimalFileSet;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.language.base.internal.AbstractBuildableModelElement;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.PrebuiltLibrary;
+import org.gradle.nativebinaries.internal.LibraryBinaryInternal;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+public abstract class AbstractPrebuiltLibraryBinary extends AbstractBuildableModelElement implements LibraryBinaryInternal {
+    private final String name;
+    private final PrebuiltLibrary library;
+    private final BuildType buildType;
+    private final Platform targetPlatform;
+    private final Flavor flavor;
+
+    public AbstractPrebuiltLibraryBinary(String name, PrebuiltLibrary library, BuildType buildType, Platform targetPlatform, Flavor flavor) {
+        this.name = name;
+        this.library = library;
+        this.buildType = buildType;
+        this.targetPlatform = targetPlatform;
+        this.flavor = flavor;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public PrebuiltLibrary getComponent() {
+        return library;
+    }
+
+    public BuildType getBuildType() {
+        return buildType;
+    }
+
+    public Flavor getFlavor() {
+        return flavor;
+    }
+
+    public Platform getTargetPlatform() {
+        return targetPlatform;
+    }
+
+    public FileCollection getHeaderDirs() {
+        return new SimpleFileCollection(library.getHeaders().getSrcDirs());
+    }
+
+    protected FileCollection createFileCollection(File file, String fileDescription) {
+        return new FileCollectionAdapter(new ValidatingFileSet(file, getComponent().getName(), fileDescription));
+    }
+
+    private static class ValidatingFileSet implements MinimalFileSet {
+        private final File file;
+        private final String libraryName;
+        private final String fileDescription;
+
+        private ValidatingFileSet(File file, String libraryName, String fileDescription) {
+            this.file = file;
+            this.libraryName = libraryName;
+            this.fileDescription = fileDescription;
+        }
+
+        public String getDisplayName() {
+            return String.format("%s for prebuilt library '%s'", fileDescription, libraryName);
+        }
+
+        public Set<File> getFiles() {
+            if (file == null) {
+                throw new PrebuiltLibraryResolveException(String.format("%s not set for prebuilt library '%s'.", fileDescription, libraryName));
+            }
+            if (!file.exists() || !file.isFile()) {
+                throw new PrebuiltLibraryResolveException(String.format("%s %s does not exist for prebuilt library '%s'.", fileDescription, file.getAbsolutePath(), libraryName));
+            }
+            return Collections.singleton(file);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltLibraries.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltLibraries.java
new file mode 100644
index 0000000..b1719c8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltLibraries.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.PrebuiltLibraries;
+import org.gradle.nativebinaries.PrebuiltLibrary;
+
+public class DefaultPrebuiltLibraries extends AbstractNamedDomainObjectContainer<PrebuiltLibrary> implements PrebuiltLibraries {
+    private final FileResolver fileResolver;
+    private final Action<PrebuiltLibrary> libraryInitializer;
+    private String name;
+
+    public DefaultPrebuiltLibraries(String name, Instantiator instantiator, FileResolver fileResolver, Action<PrebuiltLibrary> libraryInitializer) {
+        super(PrebuiltLibrary.class, instantiator);
+        this.name = name;
+        this.fileResolver = fileResolver;
+        this.libraryInitializer = libraryInitializer;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    protected PrebuiltLibrary doCreate(String name) {
+        return getInstantiator().newInstance(DefaultPrebuiltLibrary.class, name, fileResolver);
+    }
+
+    public PrebuiltLibrary resolveLibrary(String name) {
+        PrebuiltLibrary library = findByName(name);
+        if (library != null && library.getBinaries().isEmpty()) {
+            libraryInitializer.execute(library);
+        }
+        return library;
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltLibrary.java
new file mode 100644
index 0000000..1327e86
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltLibrary.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.nativebinaries.NativeBinary;
+import org.gradle.nativebinaries.PrebuiltLibrary;
+
+public class DefaultPrebuiltLibrary implements PrebuiltLibrary {
+
+    private final String name;
+    private final SourceDirectorySet headers;
+    private final DomainObjectSet<NativeBinary> binaries;
+
+    public DefaultPrebuiltLibrary(String name, FileResolver fileResolver) {
+        this.name = name;
+        headers = new DefaultSourceDirectorySet("headers", fileResolver);
+        binaries = new DefaultDomainObjectSet<NativeBinary>(NativeBinary.class);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public SourceDirectorySet getHeaders() {
+        return headers;
+    }
+
+    public DomainObjectSet<NativeBinary> getBinaries() {
+        return binaries;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltSharedLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltSharedLibraryBinary.java
new file mode 100644
index 0000000..8f43be9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltSharedLibraryBinary.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.PrebuiltLibrary;
+import org.gradle.nativebinaries.SharedLibraryBinary;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.io.File;
+
+public class DefaultPrebuiltSharedLibraryBinary extends AbstractPrebuiltLibraryBinary implements SharedLibraryBinary {
+    private File sharedLibraryFile;
+    private File sharedLibraryLinkFile;
+
+    public DefaultPrebuiltSharedLibraryBinary(String name, PrebuiltLibrary library, BuildType buildType, Platform targetPlatform, Flavor flavor) {
+        super(name, library, buildType, targetPlatform, flavor);
+    }
+
+    public String getDisplayName() {
+        return String.format("shared library '%s'", getName());
+    }
+
+    public void setSharedLibraryFile(File sharedLibraryFile) {
+        this.sharedLibraryFile = sharedLibraryFile;
+    }
+
+    public File getSharedLibraryFile() {
+        return sharedLibraryFile;
+    }
+
+    public void setSharedLibraryLinkFile(File sharedLibraryLinkFile) {
+        this.sharedLibraryLinkFile = sharedLibraryLinkFile;
+    }
+
+    public File getSharedLibraryLinkFile() {
+        if (sharedLibraryLinkFile != null) {
+            return sharedLibraryLinkFile;
+        }
+        return sharedLibraryFile;
+    }
+
+    public FileCollection getLinkFiles() {
+        return createFileCollection(getSharedLibraryLinkFile(), "Shared library link file");
+    }
+
+    public FileCollection getRuntimeFiles() {
+        return createFileCollection(getSharedLibraryFile(), "Shared library runtime file");
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltStaticLibraryBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltStaticLibraryBinary.java
new file mode 100644
index 0000000..434df2e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltStaticLibraryBinary.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.PrebuiltLibrary;
+import org.gradle.nativebinaries.StaticLibraryBinary;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.io.File;
+
+public class DefaultPrebuiltStaticLibraryBinary extends AbstractPrebuiltLibraryBinary implements StaticLibraryBinary {
+    private File staticLibraryFile;
+
+    public DefaultPrebuiltStaticLibraryBinary(String name, PrebuiltLibrary library, BuildType buildType, Platform targetPlatform, Flavor flavor) {
+        super(name, library, buildType, targetPlatform, flavor);
+    }
+
+    public String getDisplayName() {
+        return String.format("static library '%s'", getName());
+    }
+
+    public void setStaticLibraryFile(File staticLibraryFile) {
+        this.staticLibraryFile = staticLibraryFile;
+    }
+
+    public File getStaticLibraryFile() {
+        return staticLibraryFile;
+    }
+
+    public FileCollection getLinkFiles() {
+        return createFileCollection(getStaticLibraryFile(), "Static library file");
+    }
+
+    public FileCollection getRuntimeFiles() {
+        return new SimpleFileCollection();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryBinaryLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryBinaryLocator.java
new file mode 100644
index 0000000..6694b52
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryBinaryLocator.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.nativebinaries.*;
+import org.gradle.nativebinaries.internal.resolve.LibraryBinaryLocator;
+import org.gradle.nativebinaries.internal.resolve.ProjectLocator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PrebuiltLibraryBinaryLocator implements LibraryBinaryLocator {
+    private final ProjectLocator projectLocator;
+
+    public PrebuiltLibraryBinaryLocator(ProjectLocator projectLocator) {
+        this.projectLocator = projectLocator;
+    }
+
+    public DomainObjectSet<NativeBinary> getBinaries(NativeLibraryRequirement requirement) {
+        ProjectInternal project = projectLocator.locateProject(requirement.getProjectPath());
+        NamedDomainObjectSet<PrebuiltLibraries> repositories = project.getModelRegistry().get("repositories", Repositories.class).withType(PrebuiltLibraries.class);
+        if (repositories.isEmpty()) {
+            throw new PrebuiltLibraryResolveException("Project does not have any prebuilt library repositories.");
+        }
+        PrebuiltLibrary prebuiltLibrary = getPrebuiltLibrary(repositories, requirement.getLibraryName());
+        return prebuiltLibrary.getBinaries();
+    }
+
+    private PrebuiltLibrary getPrebuiltLibrary(NamedDomainObjectSet<PrebuiltLibraries> repositories, String libraryName) {
+        List<String> repositoryNames = new ArrayList<String>();
+        for (PrebuiltLibraries prebuiltLibraries : repositories) {
+            repositoryNames.add(prebuiltLibraries.getName());
+            PrebuiltLibrary prebuiltLibrary = prebuiltLibraries.resolveLibrary(libraryName);
+            if (prebuiltLibrary != null) {
+                return prebuiltLibrary;
+            }
+        }
+        throw new PrebuiltLibraryResolveException(
+                String.format("Prebuilt library with name '%s' not found in repositories '%s'.", libraryName, repositoryNames));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryInitializer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryInitializer.java
new file mode 100644
index 0000000..3c4eecc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryInitializer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.Action;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.internal.BinaryNamingSchemeBuilder;
+import org.gradle.language.base.internal.DefaultBinaryNamingSchemeBuilder;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.LibraryBinary;
+import org.gradle.nativebinaries.PrebuiltLibrary;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class PrebuiltLibraryInitializer implements Action<PrebuiltLibrary> {
+    private final Instantiator instantiator;
+    private final Set<Platform> allPlatforms = new LinkedHashSet<Platform>();
+    private final Set<BuildType> allBuildTypes = new LinkedHashSet<BuildType>();
+    private final Set<Flavor> allFlavors = new LinkedHashSet<Flavor>();
+
+    public PrebuiltLibraryInitializer(Instantiator instantiator,
+                                      Collection<? extends Platform> allPlatforms, Collection<? extends BuildType> allBuildTypes, Collection<? extends Flavor> allFlavors) {
+        this.instantiator = instantiator;
+        this.allPlatforms.addAll(allPlatforms);
+        this.allBuildTypes.addAll(allBuildTypes);
+        this.allFlavors.addAll(allFlavors);
+    }
+
+    public void execute(PrebuiltLibrary prebuiltLibrary) {
+        for (Platform platform : allPlatforms) {
+            for (BuildType buildType : allBuildTypes) {
+                for (Flavor flavor : allFlavors) {
+                    createNativeBinaries(prebuiltLibrary, platform, buildType, flavor);
+                }
+            }
+        }
+    }
+
+    public void createNativeBinaries(PrebuiltLibrary library, Platform platform, BuildType buildType, Flavor flavor) {
+        createNativeBinary(DefaultPrebuiltSharedLibraryBinary.class, library, platform, buildType, flavor);
+        createNativeBinary(DefaultPrebuiltStaticLibraryBinary.class, library, platform, buildType, flavor);
+    }
+
+    public <T extends LibraryBinary> void createNativeBinary(Class<T> type, PrebuiltLibrary library, Platform platform, BuildType buildType, Flavor flavor) {
+        String name = getName(type, library, platform, buildType, flavor);
+        T nativeBinary = instantiator.newInstance(type, name, library, buildType, platform, flavor);
+        library.getBinaries().add(nativeBinary);
+    }
+
+    private <T extends LibraryBinary> String getName(Class<T> type, PrebuiltLibrary library, Platform platform, BuildType buildType, Flavor flavor) {
+        BinaryNamingSchemeBuilder namingScheme = new DefaultBinaryNamingSchemeBuilder()
+                .withComponentName(library.getName())
+                .withTypeString(type.getSimpleName())
+                .withVariantDimension(platform.getName())
+                .withVariantDimension(buildType.getName())
+                .withVariantDimension(flavor.getName());
+        return namingScheme.build().getLifecycleTaskName();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryResolveException.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryResolveException.java
new file mode 100644
index 0000000..b08966e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/prebuilt/PrebuiltLibraryResolveException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt;
+
+import org.gradle.api.GradleException;
+import org.gradle.internal.exceptions.Contextual;
+
+ at Contextual
+public class PrebuiltLibraryResolveException extends GradleException {
+    public PrebuiltLibraryResolveException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ApiRequirementNativeDependencyResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ApiRequirementNativeDependencyResolver.java
new file mode 100644
index 0000000..b5e512b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ApiRequirementNativeDependencyResolver.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.nativebinaries.NativeDependencySet;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+/**
+ * Adapts an 'api' library requirement to a default linkage, and then wraps the result so that only headers are provided.
+ */
+public class ApiRequirementNativeDependencyResolver implements NativeDependencyResolver {
+    private final NativeDependencyResolver delegate;
+
+    public ApiRequirementNativeDependencyResolver(NativeDependencyResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    public void resolve(NativeBinaryResolveResult nativeBinaryResolveResult) {
+        for (NativeBinaryRequirementResolveResult resolution : nativeBinaryResolveResult.getAllResolutions()) {
+            String linkage = getLinkage(resolution);
+            if ("api".equals(linkage)) {
+                resolution.setRequirement(new ApiAdaptedNativeLibraryRequirement(resolution.getRequirement()));
+            }
+        }
+
+        delegate.resolve(nativeBinaryResolveResult);
+
+        for (NativeBinaryRequirementResolveResult resolution : nativeBinaryResolveResult.getAllResolutions()) {
+            if (resolution.getRequirement() instanceof ApiAdaptedNativeLibraryRequirement) {
+                ApiAdaptedNativeLibraryRequirement adaptedRequirement = (ApiAdaptedNativeLibraryRequirement) resolution.getRequirement();
+                resolution.setRequirement(adaptedRequirement.getOriginal());
+//                resolution.setLibraryBinary(null);
+                resolution.setNativeDependencySet(new ApiNativeDependencySet(resolution.getNativeDependencySet()));
+            }
+        }
+    }
+
+    private String getLinkage(NativeBinaryRequirementResolveResult resolution) {
+        if (resolution.getRequirement() == null) {
+            return null;
+        }
+        return resolution.getRequirement().getLinkage();
+    }
+
+    private static class ApiAdaptedNativeLibraryRequirement implements NativeLibraryRequirement {
+        private final NativeLibraryRequirement original;
+        public ApiAdaptedNativeLibraryRequirement(NativeLibraryRequirement original) {
+            this.original = original;
+        }
+
+        public NativeLibraryRequirement getOriginal() {
+            return original;
+        }
+
+        public String getProjectPath() {
+            return original.getProjectPath();
+        }
+
+        public String getLibraryName() {
+            return original.getLibraryName();
+        }
+
+        public String getLinkage() {
+            // Rely on the default linkage for providing the headers
+            return null;
+        }
+    }
+
+    private static class ApiNativeDependencySet implements NativeDependencySet {
+        private final NativeDependencySet delegate;
+
+        public ApiNativeDependencySet(NativeDependencySet delegate) {
+            this.delegate = delegate;
+        }
+
+        public FileCollection getIncludeRoots() {
+            return delegate.getIncludeRoots();
+        }
+
+        public FileCollection getLinkFiles() {
+            return new SimpleFileCollection();
+        }
+
+        public FileCollection getRuntimeFiles() {
+            return new SimpleFileCollection();
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ChainedLibraryBinaryLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ChainedLibraryBinaryLocator.java
new file mode 100644
index 0000000..890037f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ChainedLibraryBinaryLocator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.nativebinaries.NativeBinary;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChainedLibraryBinaryLocator implements LibraryBinaryLocator {
+    private final List<LibraryBinaryLocator> locators = new ArrayList<LibraryBinaryLocator>();
+
+    public ChainedLibraryBinaryLocator(List<? extends LibraryBinaryLocator> locators) {
+        this.locators.addAll(locators);
+    }
+
+    public DomainObjectSet<NativeBinary> getBinaries(NativeLibraryRequirement requirement) {
+        List<Exception> failures = new ArrayList<Exception>();
+        for (LibraryBinaryLocator locator : locators) {
+            try {
+                return locator.getBinaries(requirement);
+            } catch (Exception e) {
+                failures.add(e);
+            }
+        }
+        throw new LibraryResolveException(getFailureMessage(requirement), failures);
+    }
+
+    private String getFailureMessage(NativeLibraryRequirement requirement) {
+        return requirement.getProjectPath() == null
+                ? String.format("Could not locate library '%s'.", requirement.getLibraryName())
+                : String.format("Could not locate library '%s' for project '%s'.", requirement.getLibraryName(), requirement.getProjectPath());
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultLibraryResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultLibraryResolver.java
new file mode 100644
index 0000000..ee64467
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultLibraryResolver.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.nativebinaries.*;
+import org.gradle.nativebinaries.internal.LibraryBinaryInternal;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.util.GUtil;
+
+import java.util.Set;
+
+class DefaultLibraryResolver {
+    private final NativeLibraryRequirement requirement;
+    private final NativeBinary context;
+    private final LibraryBinaryLocator libraryBinaryLocator;
+
+    public DefaultLibraryResolver(LibraryBinaryLocator libraryBinaryLocator, NativeLibraryRequirement requirement, NativeBinary context) {
+        this.requirement = requirement;
+        this.context = context;
+        this.libraryBinaryLocator = libraryBinaryLocator;
+    }
+
+    public LibraryBinaryInternal resolveLibraryBinary() {
+        return new LibraryResolution()
+                .withFlavor(context.getFlavor())
+                .withPlatform(context.getTargetPlatform())
+                .withBuildType(context.getBuildType())
+                .resolveLibrary(libraryBinaryLocator.getBinaries(requirement));
+    }
+
+    private class LibraryResolution {
+        private Flavor flavor;
+        private Platform platform;
+        private BuildType buildType;
+
+        public LibraryResolution withFlavor(Flavor flavor) {
+            this.flavor = flavor;
+            return this;
+        }
+
+        public LibraryResolution withPlatform(Platform platform) {
+            this.platform = platform;
+            return this;
+        }
+
+        public LibraryResolution withBuildType(BuildType buildType) {
+            this.buildType = buildType;
+            return this;
+        }
+
+        public NativeDependencySet resolve(DomainObjectSet<NativeBinary> allBinaries) {
+            LibraryBinaryInternal resolve = resolveLibrary(allBinaries);
+            return new DefaultNativeDependencySet(resolve);
+        }
+
+        public LibraryBinaryInternal resolveLibrary(DomainObjectSet<NativeBinary> allBinaries) {
+            Class<? extends LibraryBinary> type = getTypeForLinkage(requirement.getLinkage());
+            DomainObjectSet<? extends LibraryBinary> candidateBinaries = allBinaries.withType(type);
+            return resolve(candidateBinaries);
+        }
+
+        private Class<? extends LibraryBinary> getTypeForLinkage(String linkage) {
+            if ("static".equals(linkage)) {
+                return StaticLibraryBinary.class;
+            }
+            if ("shared".equals(linkage) || linkage == null) {
+                return SharedLibraryBinary.class;
+            }
+            throw new InvalidUserDataException("Not a valid linkage: " + linkage);
+        }
+
+        private LibraryBinaryInternal resolve(Set<? extends LibraryBinary> candidates) {
+            for (LibraryBinary candidate : candidates) {
+                if (flavor != null && !flavor.getName().equals(candidate.getFlavor().getName())) {
+                    continue;
+                }
+                if (platform != null && !platform.getName().equals(candidate.getTargetPlatform().getName())) {
+                    continue;
+                }
+                if (buildType != null && !buildType.getName().equals(candidate.getBuildType().getName())) {
+                    continue;
+                }
+
+                return (LibraryBinaryInternal) candidate;
+            }
+
+            String typeName = GUtil.elvis(requirement.getLinkage(), "shared");
+            throw new LibraryResolveException(String.format("No %s library binary available for library '%s' with [flavor: '%s', platform: '%s', buildType: '%s']",
+                    typeName, requirement.getLibraryName(), flavor.getName(), platform.getName(), buildType.getName()));
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultNativeDependencySet.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultNativeDependencySet.java
new file mode 100644
index 0000000..9005f6d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultNativeDependencySet.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.nativebinaries.NativeDependencySet;
+import org.gradle.nativebinaries.internal.LibraryBinaryInternal;
+
+public class DefaultNativeDependencySet implements NativeDependencySet {
+    private final LibraryBinaryInternal binary;
+
+    public DefaultNativeDependencySet(LibraryBinaryInternal binary) {
+        this.binary = binary;
+    }
+
+    public FileCollection getIncludeRoots() {
+        return binary.getHeaderDirs();
+    }
+
+    public FileCollection getLinkFiles() {
+        return binary.getLinkFiles();
+    }
+
+    public FileCollection getRuntimeFiles() {
+        return binary.getRuntimeFiles();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultProjectLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultProjectLocator.java
new file mode 100644
index 0000000..1c47df9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/DefaultProjectLocator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.project.ProjectInternal;
+
+public class DefaultProjectLocator implements ProjectLocator {
+    private final String projectPath;
+    private final ProjectFinder delegate;
+
+    public DefaultProjectLocator(String projectPath, ProjectFinder delegate) {
+        this.projectPath = projectPath;
+        this.delegate = delegate;
+    }
+
+    public ProjectInternal locateProject(String path) {
+        if (path == null || path.length() == 0) {
+            return delegate.getProject(projectPath);
+        }
+
+        ProjectInternal referencedProject = delegate.getProject(path);
+        // TODO:DAZ This is a brain-dead way to ensure that the reference project's model is ready to access
+        referencedProject.evaluate();
+        return referencedProject;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/InputHandlingNativeDependencyResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/InputHandlingNativeDependencyResolver.java
new file mode 100644
index 0000000..6f96cbc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/InputHandlingNativeDependencyResolver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.nativebinaries.NativeDependencySet;
+
+public class InputHandlingNativeDependencyResolver implements NativeDependencyResolver {
+    private final NativeDependencyResolver delegate;
+
+    public InputHandlingNativeDependencyResolver(NativeDependencyResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    public void resolve(NativeBinaryResolveResult nativeBinaryResolveResult) {
+        for (NativeBinaryRequirementResolveResult resolution : nativeBinaryResolveResult.getPendingResolutions()) {
+            if (resolution.getInput() instanceof NativeDependencySet) {
+                resolution.setNativeDependencySet((NativeDependencySet) resolution.getInput());
+            }
+        }
+        delegate.resolve(nativeBinaryResolveResult);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryBinaryLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryBinaryLocator.java
new file mode 100644
index 0000000..a39c104
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryBinaryLocator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.nativebinaries.NativeBinary;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+public interface LibraryBinaryLocator {
+    DomainObjectSet<NativeBinary> getBinaries(NativeLibraryRequirement requirement);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryNativeDependencyResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryNativeDependencyResolver.java
new file mode 100644
index 0000000..aac1ed5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryNativeDependencyResolver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.nativebinaries.internal.LibraryBinaryInternal;
+
+public class LibraryNativeDependencyResolver implements NativeDependencyResolver {
+    private final LibraryBinaryLocator libraryBinaryLocator;
+
+    public LibraryNativeDependencyResolver(final LibraryBinaryLocator locator) {
+        libraryBinaryLocator = locator;
+    }
+
+    public void resolve(NativeBinaryResolveResult resolution) {
+        for (NativeBinaryRequirementResolveResult requirementResolution : resolution.getPendingResolutions()) {
+            DefaultLibraryResolver libraryResolver = new DefaultLibraryResolver(libraryBinaryLocator, requirementResolution.getRequirement(), resolution.getTarget());
+            LibraryBinaryInternal libraryBinary = libraryResolver.resolveLibraryBinary();
+            requirementResolution.setLibraryBinary(libraryBinary);
+            requirementResolution.setNativeDependencySet(new DefaultNativeDependencySet(libraryBinary));
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryResolveException.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryResolveException.java
new file mode 100644
index 0000000..8bb556b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/LibraryResolveException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.internal.exceptions.AbstractMultiCauseException;
+import org.gradle.internal.exceptions.Contextual;
+
+ at Contextual
+class LibraryResolveException extends AbstractMultiCauseException {
+
+    public LibraryResolveException(String message) {
+        super(message);
+    }
+
+    LibraryResolveException(String message, Iterable<? extends Throwable> causes) {
+        super(message, causes);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeBinaryRequirementResolveResult.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeBinaryRequirementResolveResult.java
new file mode 100644
index 0000000..9619178
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeBinaryRequirementResolveResult.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.nativebinaries.LibraryBinary;
+import org.gradle.nativebinaries.NativeDependencySet;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+import org.gradle.nativebinaries.internal.LibraryBinaryInternal;
+
+public class NativeBinaryRequirementResolveResult {
+    private Object input;
+    private NativeLibraryRequirement requirement;
+    private LibraryBinaryInternal libraryBinary;
+    private NativeDependencySet nativeDependencySet;
+
+    public NativeBinaryRequirementResolveResult(Object input) {
+        this.input = input;
+    }
+
+    public Object getInput() {
+        return input;
+    }
+
+    public void setRequirement(NativeLibraryRequirement requirement) {
+        this.requirement = requirement;
+    }
+
+    public NativeLibraryRequirement getRequirement() {
+        return requirement;
+    }
+
+    public LibraryBinary getLibraryBinary() {
+        return libraryBinary;
+    }
+
+    public void setLibraryBinary(LibraryBinaryInternal libraryBinary) {
+        this.libraryBinary = libraryBinary;
+    }
+
+    public NativeDependencySet getNativeDependencySet() {
+        return nativeDependencySet;
+    }
+
+    public void setNativeDependencySet(NativeDependencySet nativeDependencySet) {
+        this.nativeDependencySet = nativeDependencySet;
+    }
+
+    public boolean isComplete() {
+        return nativeDependencySet != null;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeBinaryResolveResult.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeBinaryResolveResult.java
new file mode 100644
index 0000000..df7a268
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeBinaryResolveResult.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.specs.Spec;
+import org.gradle.nativebinaries.LibraryBinary;
+import org.gradle.nativebinaries.NativeDependencySet;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+import org.gradle.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class NativeBinaryResolveResult {
+    private final ProjectNativeBinary target;
+    private final List<NativeBinaryRequirementResolveResult> resolutions = new ArrayList<NativeBinaryRequirementResolveResult>();
+
+    public NativeBinaryResolveResult(ProjectNativeBinary target, Collection<?> libs) {
+        this.target = target;
+        for (Object lib : libs) {
+            resolutions.add(new NativeBinaryRequirementResolveResult(lib));
+        }
+    }
+
+    public ProjectNativeBinary getTarget() {
+        return target;
+    }
+
+    public List<NativeBinaryRequirementResolveResult> getAllResolutions() {
+        return resolutions;
+    }
+
+    public List<NativeDependencySet> getAllResults() {
+        return CollectionUtils.collect(getAllResolutions(), new Transformer<NativeDependencySet, NativeBinaryRequirementResolveResult>() {
+            public NativeDependencySet transform(NativeBinaryRequirementResolveResult original) {
+                return original.getNativeDependencySet();
+            }
+        });
+    }
+
+    public List<LibraryBinary> getAllLibraryBinaries() {
+        List<LibraryBinary> result = new ArrayList<LibraryBinary>();
+        for (NativeBinaryRequirementResolveResult resolution : getAllResolutions()) {
+            if (resolution.getLibraryBinary() != null) {
+                result.add(resolution.getLibraryBinary());
+            }
+        }
+        return result;
+    }
+
+    public List<NativeBinaryRequirementResolveResult> getPendingResolutions() {
+        return CollectionUtils.filter(resolutions, new Spec<NativeBinaryRequirementResolveResult>() {
+            public boolean isSatisfiedBy(NativeBinaryRequirementResolveResult element) {
+                return !element.isComplete();
+            }
+        });
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyNotationParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyNotationParser.java
new file mode 100644
index 0000000..16ae8b1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyNotationParser.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.tasks.Optional;
+import org.gradle.internal.typeconversion.*;
+import org.gradle.nativebinaries.Library;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+import org.gradle.nativebinaries.internal.ProjectNativeLibraryRequirement;
+
+import java.util.Collection;
+
+class NativeDependencyNotationParser {
+    public static NotationParser<Object, NativeLibraryRequirement> parser() {
+        return new NotationParserBuilder<NativeLibraryRequirement>()
+                .resultingType(NativeLibraryRequirement.class)
+                .parser(new LibraryConverter())
+                .parser(new NativeLibraryRequirementMapNotationParser())
+                .toComposite();
+    }
+
+    private static class LibraryConverter extends TypedNotationParser<Library, NativeLibraryRequirement> {
+        private LibraryConverter() {
+            super(Library.class);
+        }
+
+        @Override
+        protected NativeLibraryRequirement parseType(Library notation) {
+            return notation.getShared();
+        }
+    }
+
+    private static class NativeLibraryRequirementMapNotationParser extends MapNotationParser<NativeLibraryRequirement> {
+
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Map with mandatory 'library' and optional 'project' and 'linkage' keys, e.g. [project: ':someProj', library: 'mylib', linkage: 'static']");
+        }
+
+        @SuppressWarnings("unused")
+        protected NativeLibraryRequirement parseMap(@MapKey("library") String libraryName, @Optional @MapKey("project") String projectPath, @Optional @MapKey("linkage") String linkage) {
+            return new ProjectNativeLibraryRequirement(projectPath, libraryName, linkage);
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyResolver.java
new file mode 100644
index 0000000..bc6a3da
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyResolver.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve;
+
+public interface NativeDependencyResolver {
+    void resolve(NativeBinaryResolveResult resolution);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyResolverServices.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyResolverServices.java
new file mode 100644
index 0000000..7786b5f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyResolverServices.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.nativebinaries.internal.prebuilt.PrebuiltLibraryBinaryLocator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NativeDependencyResolverServices {
+
+    public ProjectLocator createProjectLocator(ProjectFinder projectFinder, DependencyMetaDataProvider metaDataProvider) {
+        String currentProjectPath = metaDataProvider.getModule().getProjectPath();
+        return new DefaultProjectLocator(currentProjectPath, projectFinder);
+    }
+
+    public LibraryBinaryLocator createLibraryBinaryLocator(ProjectLocator projectLocator) {
+        List<LibraryBinaryLocator> locators = new ArrayList<LibraryBinaryLocator>();
+        locators.add(new ProjectLibraryBinaryLocator(projectLocator));
+        locators.add(new PrebuiltLibraryBinaryLocator(projectLocator));
+        return new ChainedLibraryBinaryLocator(locators);
+    }
+
+    public NativeDependencyResolver createResolver(LibraryBinaryLocator locator) {
+        NativeDependencyResolver resolver = new LibraryNativeDependencyResolver(locator);
+        resolver = new ApiRequirementNativeDependencyResolver(resolver);
+        resolver = new RequirementParsingNativeDependencyResolver(resolver);
+        resolver = new SourceSetNativeDependencyResolver(resolver);
+        return new InputHandlingNativeDependencyResolver(resolver);
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLibraryBinaryLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLibraryBinaryLocator.java
new file mode 100644
index 0000000..5763005
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLibraryBinaryLocator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Project;
+import org.gradle.nativebinaries.LibraryContainer;
+import org.gradle.nativebinaries.NativeBinary;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+public class ProjectLibraryBinaryLocator implements LibraryBinaryLocator {
+    private final ProjectLocator projectLocator;
+
+    public ProjectLibraryBinaryLocator(ProjectLocator projectLocator) {
+        this.projectLocator = projectLocator;
+    }
+
+    public DomainObjectSet<NativeBinary> getBinaries(NativeLibraryRequirement requirement) {
+        Project project = findProject(requirement);
+        LibraryContainer libraryContainer = (LibraryContainer) project.getExtensions().findByName("libraries");
+        if (libraryContainer == null) {
+            throw new LibraryResolveException(String.format("Project does not have a libraries container: '%s'", project.getPath()));
+        }
+        return libraryContainer.getByName(requirement.getLibraryName()).getBinaries();
+    }
+
+    private Project findProject(NativeLibraryRequirement requirement) {
+        return projectLocator.locateProject(requirement.getProjectPath());
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLocator.java
new file mode 100644
index 0000000..23b8a34
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLocator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.internal.project.ProjectInternal;
+
+public interface ProjectLocator {
+    ProjectInternal locateProject(String path);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/RequirementParsingNativeDependencyResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/RequirementParsingNativeDependencyResolver.java
new file mode 100644
index 0000000..a24010c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/RequirementParsingNativeDependencyResolver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.nativebinaries.NativeLibraryRequirement;
+
+public class RequirementParsingNativeDependencyResolver implements NativeDependencyResolver {
+    private final NotationParser<Object, NativeLibraryRequirement> parser = NativeDependencyNotationParser.parser();
+
+    private final NativeDependencyResolver delegate;
+
+    public RequirementParsingNativeDependencyResolver(NativeDependencyResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    public void resolve(NativeBinaryResolveResult nativeBinaryResolveResult) {
+        for (NativeBinaryRequirementResolveResult resolution : nativeBinaryResolveResult.getPendingResolutions()) {
+            NativeLibraryRequirement requirement = parser.parseNotation(resolution.getInput());
+            resolution.setRequirement(requirement);
+        }
+        delegate.resolve(nativeBinaryResolveResult);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/SourceSetNativeDependencyResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/SourceSetNativeDependencyResolver.java
new file mode 100644
index 0000000..8fcf6fc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/internal/resolve/SourceSetNativeDependencyResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.resolve;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.language.HeaderExportingSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.nativebinaries.NativeDependencySet;
+
+import java.io.File;
+import java.util.Set;
+
+public class SourceSetNativeDependencyResolver implements NativeDependencyResolver {
+    private final NativeDependencyResolver delegate;
+
+    public SourceSetNativeDependencyResolver(NativeDependencyResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    public void resolve(NativeBinaryResolveResult nativeBinaryResolveResult) {
+        for (NativeBinaryRequirementResolveResult resolution : nativeBinaryResolveResult.getPendingResolutions()) {
+            if (resolution.getInput() instanceof LanguageSourceSet) {
+                LanguageSourceSet input = (LanguageSourceSet) resolution.getInput();
+                resolution.setNativeDependencySet(createNativeDependencySet(input));
+            }
+        }
+        delegate.resolve(nativeBinaryResolveResult);
+    }
+
+    private NativeDependencySet createNativeDependencySet(LanguageSourceSet sourceSet) {
+        if (sourceSet instanceof HeaderExportingSourceSet) {
+            return new LanguageSourceSetNativeDependencySet((HeaderExportingSourceSet) sourceSet);
+        }
+        return new EmptyNativeDependencySet();
+    }
+
+    private static class EmptyNativeDependencySet implements NativeDependencySet {
+        public FileCollection getIncludeRoots() {
+            return empty();
+        }
+
+        public FileCollection getLinkFiles() {
+            return empty();
+        }
+
+        public FileCollection getRuntimeFiles() {
+            return empty();
+        }
+
+        private FileCollection empty() {
+            return new SimpleFileCollection();
+        }
+    }
+
+    private static class LanguageSourceSetNativeDependencySet extends EmptyNativeDependencySet {
+        private final HeaderExportingSourceSet sourceSet;
+
+        private LanguageSourceSetNativeDependencySet(HeaderExportingSourceSet sourceSet) {
+            this.sourceSet = sourceSet;
+        }
+
+        public FileCollection getIncludeRoots() {
+            return new AbstractFileCollection() {
+                @Override
+                public String getDisplayName() {
+                    return "Include roots of " + sourceSet.getName();
+                }
+
+                public Set<File> getFiles() {
+                    return sourceSet.getExportedHeaders().getSrcDirs();
+                }
+
+                @Override
+                public TaskDependency getBuildDependencies() {
+                    return sourceSet.getBuildDependencies();
+                }
+            };
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/PreprocessingTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/PreprocessingTool.java
new file mode 100644
index 0000000..4d3928c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/PreprocessingTool.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language;
+
+import org.gradle.api.Incubating;
+import org.gradle.nativebinaries.Tool;
+
+import java.util.Map;
+
+/**
+ * A tool that permits configuration of the C preprocessor.
+ */
+ at Incubating
+public interface PreprocessingTool extends Tool {
+    /**
+     * The set of preprocessor macros to define when compiling this binary.
+     */
+    Map<String, String> getMacros();
+
+    /**
+     * Defines a named preprocessor macros to use when compiling this binary.
+     * The macro will be supplied to the compiler as '-D name'.
+     */
+    void define(String name);
+
+    /**
+     * Defines a named preprocessor macro with a value, which will be used when compiling this binary.
+     * The macro will be supplied to the compiler as '-D name=definition'.
+     */
+    void define(String name, String definition);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/internal/AssembleSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/internal/AssembleSpec.java
new file mode 100644
index 0000000..8d667ac
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/internal/AssembleSpec.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.nativebinaries.language.assembler.internal;
+
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A compile spec that will be used to generate object files for combining into a native binary.
+ */
+public interface AssembleSpec extends BinaryToolSpec {
+    File getObjectFileDir();
+
+    void setObjectFileDir(File objectFileDir);
+
+    List<File> getSourceFiles();
+
+    void source(Iterable<File> sources);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/internal/DefaultAssembleSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/internal/DefaultAssembleSpec.java
new file mode 100644
index 0000000..8f97629
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/internal/DefaultAssembleSpec.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.assembler.internal;
+
+import org.gradle.nativebinaries.internal.AbstractBinaryToolSpec;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultAssembleSpec extends AbstractBinaryToolSpec implements AssembleSpec {
+
+    private List<File> source = new ArrayList<File>();
+    private File objectFileDir;
+
+    public List<File> getSourceFiles() {
+        return source;
+    }
+
+    public void source(Iterable<File> sources) {
+        for (File file : sources) {
+            this.source.add(file);
+        }
+    }
+
+    public File getObjectFileDir() {
+        return objectFileDir;
+    }
+
+    public void setObjectFileDir(File objectFileDir) {
+        this.objectFileDir = objectFileDir;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/package-info.java
new file mode 100644
index 0000000..036c922
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 building Assembler sources for a native runtime.
+ */
+package org.gradle.nativebinaries.language.assembler;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerNativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerNativeBinariesPlugin.groovy
new file mode 100644
index 0000000..7822937
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerNativeBinariesPlugin.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.assembler.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.assembler.AssemblerSourceSet
+import org.gradle.language.assembler.plugins.AssemblerLangPlugin
+import org.gradle.nativebinaries.Executable
+import org.gradle.nativebinaries.Library
+import org.gradle.nativebinaries.ProjectNativeBinary
+import org.gradle.nativebinaries.ProjectNativeComponent
+import org.gradle.nativebinaries.internal.DefaultTool
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.language.assembler.tasks.Assemble
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Assembly language sources.
+ *
+ * <p>Automatically includes the {@link AssemblerLangPlugin} for core Assembler support and the {@link NativeBinariesPlugin} for native binary support.</p>
+ *
+ * <li>Creates a {@link Assemble} task for each {@link AssemblerSourceSet} to assemble the sources.</li>
+ */
+ at Incubating
+class AssemblerNativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesPlugin)
+        project.plugins.apply(AssemblerLangPlugin)
+
+        // TODO:DAZ Clean this up (see CppNativeBinariesPlugin)
+        project.executables.all { Executable executable ->
+            addLanguageExtensionsToComponent(executable)
+        }
+        project.libraries.all { Library library ->
+            addLanguageExtensionsToComponent(library)
+        }
+
+        project.binaries.withType(ProjectNativeBinary) { ProjectNativeBinaryInternal binary ->
+            binary.source.withType(AssemblerSourceSet).all { AssemblerSourceSet sourceSet ->
+                if (sourceSet.mayHaveSources) {
+                    def assembleTask = createAssembleTask(project, binary, sourceSet)
+                    assembleTask.dependsOn sourceSet
+                    binary.tasks.add assembleTask
+                    binary.tasks.builder.source assembleTask.outputs.files.asFileTree.matching { include '**/*.obj', '**/*.o' }
+                }
+            }
+        }
+    }
+
+    private def addLanguageExtensionsToComponent(ProjectNativeComponent component) {
+        component.binaries.all { binary ->
+            binary.extensions.create("assembler", DefaultTool)
+        }
+    }
+
+    private def createAssembleTask(ProjectInternal project, ProjectNativeBinaryInternal binary, def sourceSet) {
+        def assembleTask = project.task(binary.namingScheme.getTaskName("assemble", sourceSet.fullName), type: Assemble) {
+            description = "Assembles the $sourceSet of $binary"
+        }
+
+        assembleTask.toolChain = binary.toolChain
+        assembleTask.targetPlatform = binary.targetPlatform
+
+        assembleTask.source sourceSet.source
+
+        assembleTask.objectFileDir = project.file("${project.buildDir}/objectFiles/${binary.namingScheme.outputDirectoryBase}/${sourceSet.fullName}")
+        assembleTask.assemblerArgs = binary.assembler.args
+
+        assembleTask
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerPlugin.groovy
new file mode 100644
index 0000000..9b38fd5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerPlugin.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.assembler.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.toolchain.internal.plugins.StandardToolChainsPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Assembly language sources.
+ *
+ * <p>Adds core tool chain support to the {@link AssemblerNativeBinariesPlugin}.</p>
+ */
+ at Incubating
+class AssemblerPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(StandardToolChainsPlugin)
+        project.plugins.apply(AssemblerNativeBinariesPlugin)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/tasks/Assemble.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/tasks/Assemble.groovy
new file mode 100644
index 0000000..2984112
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/assembler/tasks/Assemble.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.assembler.tasks
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.*
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.language.assembler.internal.DefaultAssembleSpec
+
+import javax.inject.Inject
+
+/**
+ * Translates Assembly language source files into object files.
+ */
+ at Incubating
+class Assemble extends DefaultTask {
+    private FileCollection source
+
+    ToolChain toolChain
+
+    /**
+     * The platform being targeted.
+     */
+    Platform targetPlatform
+
+    /**
+     * The directory where object files will be generated.
+     */
+    @OutputDirectory
+    File objectFileDir
+
+    @InputFiles @SkipWhenEmpty // Workaround for GRADLE-2026
+    FileCollection getSource() {
+        source
+    }
+
+    // Invalidate output when the tool chain output changes
+    @Input
+    def getOutputType() {
+        return "${toolChain.outputType}:${targetPlatform.compatibilityString}"
+    }
+
+    /**
+     * Additional arguments to provide to the assembler.
+     */
+    @Input
+    List<String> assemblerArgs
+
+    @Inject
+    Assemble() {
+        source = project.files()
+    }
+
+    @TaskAction
+    void assemble() {
+        def cleaner = new SimpleStaleClassCleaner(getOutputs())
+        cleaner.setDestinationDir(getObjectFileDir())
+        cleaner.execute()
+
+        def spec = new DefaultAssembleSpec()
+        spec.tempDir = getTemporaryDir()
+
+        spec.objectFileDir = getObjectFileDir()
+        spec.source getSource()
+        spec.args getAssemblerArgs()
+
+        def result = toolChain.target(targetPlatform).createAssembler().execute(spec)
+        didWork = result.didWork
+    }
+
+    /**
+     * Adds a set of assembler sources files to be translated.
+     * The provided sourceFiles object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void source(Object sourceFiles) {
+        source.from sourceFiles
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/CCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/CCompileSpec.java
new file mode 100644
index 0000000..4ddb79b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/CCompileSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal;
+
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+public interface CCompileSpec extends NativeCompileSpec {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/DefaultCCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/DefaultCCompileSpec.java
new file mode 100644
index 0000000..72866c7
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/DefaultCCompileSpec.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal;
+
+import org.gradle.nativebinaries.language.internal.AbstractNativeCompileSpec;
+
+public class DefaultCCompileSpec extends AbstractNativeCompileSpec implements CCompileSpec {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/AbstractIncrementalNativeCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/AbstractIncrementalNativeCompiler.java
new file mode 100644
index 0000000..cd43291
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/AbstractIncrementalNativeCompiler.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.PersistentStateCache;
+import org.gradle.internal.Factory;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+
+abstract class AbstractIncrementalNativeCompiler implements Compiler<NativeCompileSpec> {
+    private final TaskInternal task;
+    private final SourceIncludesParser sourceIncludesParser;
+    private final TaskArtifactStateCacheAccess cacheAccess;
+    private final FileSnapshotter fileSnapshotter;
+    private Iterable<File> includes;
+
+    protected AbstractIncrementalNativeCompiler(TaskInternal task, SourceIncludesParser sourceIncludesParser, Iterable<File> includes,
+                                                TaskArtifactStateCacheAccess cacheAccess, FileSnapshotter fileSnapshotter) {
+        this.task = task;
+        this.includes = includes;
+        this.sourceIncludesParser = sourceIncludesParser;
+        this.cacheAccess = cacheAccess;
+        this.fileSnapshotter = fileSnapshotter;
+    }
+
+    public WorkResult execute(final NativeCompileSpec spec) {
+        return cacheAccess.useCache("incremental compile", new Factory<WorkResult>() {
+            public WorkResult create() {
+                IncrementalCompileProcessor processor = createProcessor(includes);
+                return doIncrementalCompile(processor, spec);
+            }
+        });
+    }
+
+    protected abstract WorkResult doIncrementalCompile(IncrementalCompileProcessor processor, NativeCompileSpec spec);
+
+    protected TaskInternal getTask() {
+        return task;
+    }
+
+    private IncrementalCompileProcessor createProcessor(Iterable<File> includes) {
+        PersistentStateCache<CompilationState> compileStateCache = createCompileStateCache(task.getPath());
+
+        DefaultSourceIncludesResolver dependencyParser = new DefaultSourceIncludesResolver(CollectionUtils.toList(includes));
+
+        return new IncrementalCompileProcessor(compileStateCache, dependencyParser, sourceIncludesParser, fileSnapshotter);
+    }
+
+    private PersistentStateCache<CompilationState> createCompileStateCache(final String taskPath) {
+        final PersistentIndexedCache<String, CompilationState> stateIndexedCache = cacheAccess.createCache("compilationState", String.class, new CompilationStateSerializer());
+        return new PersistentStateCache<CompilationState>() {
+            public CompilationState get() {
+                return stateIndexedCache.get(taskPath);
+            }
+
+            public void set(CompilationState newValue) {
+                stateIndexedCache.put(taskPath, newValue);
+            }
+
+            public void update(UpdateAction<CompilationState> updateAction) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CleanCompilingNativeCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CleanCompilingNativeCompiler.java
new file mode 100644
index 0000000..9eef667
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CleanCompilingNativeCompiler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.io.File;
+
+public class CleanCompilingNativeCompiler extends AbstractIncrementalNativeCompiler {
+    private final Compiler<NativeCompileSpec> delegateCompiler;
+
+    public CleanCompilingNativeCompiler(TaskInternal task, SourceIncludesParser sourceIncludesParser, Iterable<File> includes,
+                                        TaskArtifactStateCacheAccess cacheAccess, FileSnapshotter fileSnapshotter,
+                                        Compiler<NativeCompileSpec> delegateCompiler) {
+        super(task, sourceIncludesParser, includes, cacheAccess, fileSnapshotter);
+        this.delegateCompiler = delegateCompiler;
+    }
+
+    @Override
+    protected WorkResult doIncrementalCompile(IncrementalCompileProcessor processor, NativeCompileSpec spec) {
+        processor.processSourceFiles(spec.getSourceFiles());
+        boolean deleted = cleanPreviousOutputs(spec);
+        WorkResult compileResult = delegateCompiler.execute(spec);
+        if (deleted && !compileResult.getDidWork()) {
+            return new SimpleWorkResult(deleted);
+        }
+        return compileResult;
+    }
+
+    private boolean cleanPreviousOutputs(NativeCompileSpec spec) {
+        SimpleStaleClassCleaner cleaner = new SimpleStaleClassCleaner(getTask().getOutputs());
+        cleaner.setDestinationDir(spec.getObjectFileDir());
+        cleaner.execute();
+        return cleaner.getDidWork();
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationFileState.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationFileState.java
new file mode 100644
index 0000000..a5295b7
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationFileState.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+public class CompilationFileState implements Serializable {
+    private byte[] hash;
+    private SourceIncludes sourceIncludes = new DefaultSourceIncludes();
+    private Set<ResolvedInclude> resolvedIncludes = new HashSet<ResolvedInclude>();
+
+    public CompilationFileState(byte[] hash) {
+        this.hash = hash;
+    }
+
+    public byte[] getHash() {
+        return hash;
+    }
+
+    public SourceIncludes getSourceIncludes() {
+        return sourceIncludes;
+    }
+
+    public void setSourceIncludes(SourceIncludes sourceIncludes) {
+        this.sourceIncludes = sourceIncludes;
+    }
+
+    public Set<ResolvedInclude> getResolvedIncludes() {
+        return resolvedIncludes;
+    }
+
+    public void setResolvedIncludes(Set<ResolvedInclude> resolvedIncludes) {
+        this.resolvedIncludes = resolvedIncludes;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationState.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationState.java
new file mode 100644
index 0000000..15ea4c5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationState.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.*;
+
+public class CompilationState implements Serializable {
+    List<File> sourceInputs = new ArrayList<File>();
+    Map<File, CompilationFileState> fileStates = new HashMap<File, CompilationFileState>();
+
+    public List<File> getSourceInputs() {
+        return sourceInputs;
+    }
+
+    public void addSourceInput(File file) {
+        sourceInputs.add(file);
+    }
+
+    public CompilationFileState getState(File file) {
+        return fileStates.get(file);
+    }
+
+    public void setState(File file, CompilationFileState compilationFileState) {
+        fileStates.put(file, compilationFileState);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationStateSerializer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationStateSerializer.java
new file mode 100644
index 0000000..121d66c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationStateSerializer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.messaging.serialize.*;
+
+import java.io.File;
+import java.util.Set;
+
+public class CompilationStateSerializer implements Serializer<CompilationState> {
+    
+    private static final int SERIAL_VERSION = 1;
+    private final BaseSerializerFactory serializerFactory = new BaseSerializerFactory();
+    private final Serializer<File> fileSerializer;
+    private final ListSerializer<File> fileListSerializer;
+    private final MapSerializer<File, CompilationFileState> stateMapSerializer;
+
+    public CompilationStateSerializer() {
+        fileSerializer = serializerFactory.getSerializerFor(File.class);
+        fileListSerializer = new ListSerializer<File>(fileSerializer);
+        stateMapSerializer = new MapSerializer<File, CompilationFileState>(fileSerializer, new CompilationFileStateSerializer());
+    }
+
+    public CompilationState read(Decoder decoder) throws Exception {
+        CompilationState compilationState = new CompilationState();
+        int version = decoder.readInt();
+        if (version != SERIAL_VERSION) {
+            return compilationState;
+        }
+
+        compilationState.sourceInputs.addAll(fileListSerializer.read(decoder));
+        compilationState.fileStates.putAll(stateMapSerializer.read(decoder));
+        return compilationState;
+    }
+
+    public void write(Encoder encoder, CompilationState value) throws Exception {
+        encoder.writeInt(SERIAL_VERSION);
+        fileListSerializer.write(encoder, value.sourceInputs);
+        stateMapSerializer.write(encoder, value.fileStates);
+    }
+
+    private class CompilationFileStateSerializer implements Serializer<CompilationFileState> {
+        private final Serializer<byte[]> hashSerializer = new HashSerializer();
+        private final Serializer<Set<ResolvedInclude>> resolveIncludesSerializer = new SetSerializer<ResolvedInclude>(new ResolvedIncludeSerializer());
+        private final Serializer<SourceIncludes> sourceIncludesSerializer = new SourceIncludesSerializer();
+
+        public CompilationFileState read(Decoder decoder) throws Exception {
+            CompilationFileState fileState = new CompilationFileState(hashSerializer.read(decoder));
+            fileState.setResolvedIncludes(resolveIncludesSerializer.read(decoder));
+            fileState.setSourceIncludes(sourceIncludesSerializer.read(decoder));
+            return fileState;
+        }
+
+        public void write(Encoder encoder, CompilationFileState value) throws Exception {
+            hashSerializer.write(encoder, value.getHash());
+            resolveIncludesSerializer.write(encoder, value.getResolvedIncludes());
+            sourceIncludesSerializer.write(encoder, value.getSourceIncludes());
+        }
+    }
+
+    private class HashSerializer implements Serializer<byte[]> {
+        public byte[] read(Decoder decoder) throws Exception {
+            int size = decoder.readSmallInt();
+            byte[] value = new byte[size];
+            decoder.readBytes(value);
+            return value;
+        }
+
+        public void write(Encoder encoder, byte[] value) throws Exception {
+            encoder.writeSmallInt(value.length);
+            encoder.writeBytes(value);
+        }
+    }
+
+    private class ResolvedIncludeSerializer implements Serializer<ResolvedInclude> {
+        public ResolvedInclude read(Decoder decoder) throws Exception {
+            String include = decoder.readString();
+            File included = null;
+            if (decoder.readBoolean()) {
+                included = fileSerializer.read(decoder);
+            }
+            return new ResolvedInclude(include, included);
+        }
+
+        public void write(Encoder encoder, ResolvedInclude value) throws Exception {
+            encoder.writeString(value.getInclude());
+            if (value.getFile() == null) {
+                encoder.writeBoolean(false);
+            } else {
+                encoder.writeBoolean(true);
+                fileSerializer.write(encoder, value.getFile());
+            }
+        }
+    }
+
+    private class SourceIncludesSerializer implements Serializer<SourceIncludes> {
+        private final Serializer<String> stringSerializer = serializerFactory.getSerializerFor(String.class);
+        private final ListSerializer<String> stringListSerializer = new ListSerializer<String>(stringSerializer);
+
+        public SourceIncludes read(Decoder decoder) throws Exception {
+            SourceIncludes sourceIncludes = new DefaultSourceIncludes();
+            sourceIncludes.getQuotedIncludes().addAll(stringListSerializer.read(decoder));
+            sourceIncludes.getSystemIncludes().addAll(stringListSerializer.read(decoder));
+            sourceIncludes.getMacroIncludes().addAll(stringListSerializer.read(decoder));
+            return sourceIncludes;
+        }
+
+        public void write(Encoder encoder, SourceIncludes value) throws Exception {
+            stringListSerializer.write(encoder, value.getQuotedIncludes());
+            stringListSerializer.write(encoder, value.getSystemIncludes());
+            stringListSerializer.write(encoder, value.getMacroIncludes());
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultIncrementalCompilation.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultIncrementalCompilation.java
new file mode 100644
index 0000000..cabd8ed
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultIncrementalCompilation.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.File;
+import java.util.List;
+
+public class DefaultIncrementalCompilation implements IncrementalCompilation {
+    private final List<File> recompile;
+    private final List<File> removed;
+
+    public DefaultIncrementalCompilation(List<File> recompile, List<File> removed) {
+        this.recompile = recompile;
+        this.removed = removed;
+    }
+
+    public List<File> getRecompile() {
+        return recompile;
+    }
+
+    public List<File> getRemoved() {
+        return removed;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludes.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludes.java
new file mode 100644
index 0000000..e4ff737
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludes.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+class DefaultSourceIncludes implements SourceIncludes, Serializable {
+    private final List<String> quotedIncludes = new ArrayList<String>();
+    private final List<String> systemIncludes = new ArrayList<String>();
+    private final List<String> macroIncludes = new ArrayList<String>();
+
+    public void addAll(List<String> includes) {
+        for (String value : includes) {
+            if (value.startsWith("<") && value.endsWith(">")) {
+                systemIncludes.add(strip(value));
+            } else if (value.startsWith("\"") && value.endsWith("\"")) {
+                quotedIncludes.add(strip(value));
+            } else {
+                macroIncludes.add(value);
+            }
+        }
+    }
+
+    private String strip(String include) {
+        return include.substring(1, include.length() - 1);
+    }
+
+    public List<String> getQuotedIncludes() {
+        return quotedIncludes;
+    }
+
+    public List<String> getSystemIncludes() {
+        return systemIncludes;
+    }
+
+    public List<String> getMacroIncludes() {
+        return macroIncludes;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultSourceIncludes)) {
+            return false;
+        }
+
+        DefaultSourceIncludes that = (DefaultSourceIncludes) o;
+
+        return macroIncludes.equals(that.macroIncludes)
+                && quotedIncludes.equals(that.quotedIncludes)
+                && systemIncludes.equals(that.systemIncludes);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = quotedIncludes.hashCode();
+        result = 31 * result + systemIncludes.hashCode();
+        result = 31 * result + macroIncludes.hashCode();
+        return result;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesParser.java
new file mode 100644
index 0000000..33a1c86
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesParser.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.nativebinaries.language.c.internal.incremental.sourceparser.CSourceParser;
+
+import java.io.File;
+
+public class DefaultSourceIncludesParser implements SourceIncludesParser {
+    private final CSourceParser sourceParser;
+    private final boolean importAware;
+
+    public DefaultSourceIncludesParser(CSourceParser sourceParser, boolean importAware) {
+        this.sourceParser = sourceParser;
+        this.importAware = importAware;
+    }
+
+    public SourceIncludes parseIncludes(File sourceFile) {
+        CSourceParser.SourceDetails sourceDetails = sourceParser.parseSource(sourceFile);
+        DefaultSourceIncludes defaultIncludes = new DefaultSourceIncludes();
+        defaultIncludes.addAll(sourceDetails.getIncludes());
+        if (importAware) {
+            defaultIncludes.addAll(sourceDetails.getImports());
+        }
+
+        return defaultIncludes;
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesResolver.java
new file mode 100644
index 0000000..6c93b35
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesResolver.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultSourceIncludesResolver implements SourceIncludesResolver {
+    private final List<File> includePaths;
+
+    public DefaultSourceIncludesResolver(List<File> includePaths) {
+        this.includePaths = includePaths;
+    }
+
+    public Set<ResolvedInclude> resolveIncludes(File sourceFile, SourceIncludes includes) {
+        Set<ResolvedInclude> dependencies = new LinkedHashSet<ResolvedInclude>();
+        searchForDependencies(dependencies, prependSourceDir(sourceFile, includePaths), includes.getQuotedIncludes());
+        searchForDependencies(dependencies, includePaths, includes.getSystemIncludes());
+        if (!includes.getMacroIncludes().isEmpty()) {
+            dependencies.add(new ResolvedInclude(includes.getMacroIncludes().get(0), null));
+        }
+
+        return dependencies;
+    }
+
+    private List<File> prependSourceDir(File sourceFile, List<File> includePaths) {
+        List<File> quotedSearchPath = new ArrayList<File>(includePaths.size() + 1);
+        quotedSearchPath.add(sourceFile.getParentFile());
+        quotedSearchPath.addAll(includePaths);
+        return quotedSearchPath;
+    }
+
+    private void searchForDependencies(Set<ResolvedInclude> dependencies, List<File> searchPath, List<String> includes) {
+        for (String include : includes) {
+            searchForDependency(dependencies, searchPath, include);
+        }
+    }
+
+    private void searchForDependency(Set<ResolvedInclude> dependencies, List<File> searchPath, String include) {
+        for (File searchDir : searchPath) {
+            File candidate = new File(searchDir, include);
+            // TODO:DAZ This means that we'll never detect changed files if they are not on the defined include path
+            if (candidate.isFile()) {
+                dependencies.add(new ResolvedInclude(include, GFileUtils.canonicalise(candidate)));
+                return;
+            }
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompilation.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompilation.java
new file mode 100644
index 0000000..4a22885
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompilation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.File;
+import java.util.List;
+
+public interface IncrementalCompilation {
+    List<File> getRecompile();
+
+    List<File> getRemoved();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompileProcessor.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompileProcessor.java
new file mode 100644
index 0000000..d65c606
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompileProcessor.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.api.internal.changedetection.state.FileSnapshotter;
+import org.gradle.cache.PersistentStateCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+public class IncrementalCompileProcessor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(IncrementalCompileProcessor.class);
+
+    private final PersistentStateCache<CompilationState> previousCompileStateCache;
+    private final SourceIncludesParser sourceIncludesParser;
+    private final SourceIncludesResolver sourceIncludesResolver;
+    private final FileSnapshotter snapshotter;
+
+    public IncrementalCompileProcessor(PersistentStateCache<CompilationState> previousCompileStateCache, SourceIncludesResolver sourceIncludesResolver, SourceIncludesParser sourceIncludesParser,
+                                       FileSnapshotter snapshotter) {
+        this.previousCompileStateCache = previousCompileStateCache;
+        this.sourceIncludesResolver = sourceIncludesResolver;
+        this.sourceIncludesParser = sourceIncludesParser;
+        this.snapshotter = snapshotter;
+    }
+
+    public IncrementalCompilation processSourceFiles(Collection<File> sourceFiles) {
+        CompilationState previousCompileState = previousCompileStateCache.get();
+        final IncrementalCompileFiles result = new IncrementalCompileFiles(previousCompileState);
+
+        for (File sourceFile : sourceFiles) {
+            result.processSource(sourceFile);
+        }
+
+        previousCompileStateCache.set(result.current);
+
+        return new DefaultIncrementalCompilation(result.getModifiedSources(), result.getRemovedSources());
+    }
+
+    private class IncrementalCompileFiles {
+
+        private final List<File> recompile = new ArrayList<File>();
+
+        private final CompilationState previous;
+        private final CompilationState current = new CompilationState();
+        private final Map<File, Boolean> processed = new HashMap<File, Boolean>();
+
+        public IncrementalCompileFiles(CompilationState previousCompileState) {
+            this.previous = previousCompileState == null ? new CompilationState() : previousCompileState;
+        }
+
+        public void processSource(File sourceFile) {
+            current.addSourceInput(sourceFile);
+            if (checkChangedAndUpdateState(sourceFile) || !previous.getSourceInputs().contains(sourceFile)) {
+                recompile.add(sourceFile);
+            }
+        }
+
+        public boolean checkChangedAndUpdateState(File file) {
+            boolean changed = false;
+
+            if (processed.containsKey(file)) {
+                return processed.get(file);
+            }
+
+            if (!file.exists()) {
+                return true;
+            }
+
+            // Assume unchanged if we recurse to the same file due to dependency cycle
+            processed.put(file, false);
+
+            CompilationFileState previousState = previous.getState(file);
+            CompilationFileState newState = new CompilationFileState(snapshotter.snapshot(file).getHash());
+
+            // TODO:DAZ Keep a separate, build-scoped cache of file -> parsed includes. This would prevent need for reparsing per-variant and per-component.
+            if (!sameHash(previousState, newState)) {
+                changed = true;
+                newState.setSourceIncludes(sourceIncludesParser.parseIncludes(file));
+            } else {
+                newState.setSourceIncludes(previousState.getSourceIncludes());
+            }
+
+            newState.setResolvedIncludes(resolveIncludes(file, newState.getSourceIncludes()));
+            // Compare the previous resolved includes with resolving now.
+            if (!sameResolved(previousState, newState)) {
+                changed = true;
+            }
+
+            current.setState(file, newState);
+
+            for (ResolvedInclude dep : newState.getResolvedIncludes()) {
+                if (dep.isUnknown()) {
+                    LOGGER.info(String.format("Cannot determine changed state of included '%s' in source file '%s'. Assuming changed.", dep.getInclude(), file.getName()));
+                    changed = true;
+                } else {
+                    boolean depChanged = checkChangedAndUpdateState(dep.getFile());
+                    changed = changed || depChanged;
+                }
+            }
+
+            processed.put(file, changed);
+
+            return changed;
+        }
+
+        private boolean sameHash(CompilationFileState previousState, CompilationFileState newState) {
+            return previousState != null && Arrays.equals(newState.getHash(), previousState.getHash());
+        }
+
+        private boolean sameResolved(CompilationFileState previousState, CompilationFileState newState) {
+            return previousState != null && newState.getResolvedIncludes().equals(previousState.getResolvedIncludes());
+        }
+
+        private Set<ResolvedInclude> resolveIncludes(File file, SourceIncludes sourceIncludes) {
+            return sourceIncludesResolver.resolveIncludes(file, sourceIncludes);
+        }
+
+        public List<File> getModifiedSources() {
+            return recompile;
+        }
+
+        public List<File> getRemovedSources() {
+            List<File> removed = new ArrayList<File>();
+            for (File previousSource : previous.getSourceInputs()) {
+                if (!current.getSourceInputs().contains(previousSource)) {
+                    removed.add(previousSource);
+                }
+            }
+            return removed;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompilerBuilder.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompilerBuilder.java
new file mode 100644
index 0000000..c08e454
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompilerBuilder.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.nativebinaries.language.c.internal.incremental.sourceparser.CSourceParser;
+import org.gradle.nativebinaries.language.c.internal.incremental.sourceparser.RegexBackedCSourceParser;
+import org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile;
+import org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.io.File;
+
+public class IncrementalCompilerBuilder {
+    private final TaskInternal task;
+    private final TaskArtifactStateCacheAccess cacheAccess;
+    private final SourceIncludesParser sourceIncludesParser;
+    private final FileSnapshotter fileSnapshotter;
+    private boolean cleanCompile;
+    private Iterable<File> includes;
+
+    public IncrementalCompilerBuilder(TaskArtifactStateCacheAccess cacheAccess, FileSnapshotter fileSnapshotter, TaskInternal task) {
+        this.task = task;
+        this.sourceIncludesParser = createIncludesParser(task);
+        this.cacheAccess = cacheAccess;
+        this.fileSnapshotter = fileSnapshotter;
+    }
+
+    private static SourceIncludesParser createIncludesParser(TaskInternal task) {
+        CSourceParser sourceParser = new RegexBackedCSourceParser();
+        boolean importsAreIncludes = ObjectiveCCompile.class.isAssignableFrom(task.getClass()) || ObjectiveCppCompile.class.isAssignableFrom(task.getClass());
+        return new DefaultSourceIncludesParser(sourceParser, importsAreIncludes);
+    }
+
+    public IncrementalCompilerBuilder withCleanCompile() {
+        this.cleanCompile = true;
+        return this;
+    }
+
+    public IncrementalCompilerBuilder withIncludes(Iterable<File> includes) {
+        this.includes = includes;
+        return this;
+    }
+
+    public Compiler<NativeCompileSpec> createIncrementalCompiler(Compiler<NativeCompileSpec> compiler) {
+        if (cleanCompile) {
+            return createCleaningCompiler(compiler, task, includes);
+        }
+        return createIncrementalCompiler(compiler, task, includes);
+    }
+
+    private Compiler<NativeCompileSpec> createIncrementalCompiler(Compiler<NativeCompileSpec> compiler, TaskInternal task, Iterable<File> includes) {
+        return new IncrementalNativeCompiler(task, sourceIncludesParser, includes, cacheAccess, fileSnapshotter, compiler);
+    }
+
+    private Compiler<NativeCompileSpec> createCleaningCompiler(Compiler<NativeCompileSpec> compiler, TaskInternal task, Iterable<File> includes) {
+        return new CleanCompilingNativeCompiler(task, sourceIncludesParser, includes, cacheAccess, fileSnapshotter, compiler);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalNativeCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalNativeCompiler.java
new file mode 100644
index 0000000..1c8fa09
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalNativeCompiler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.state.FileSnapshotter;
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.io.File;
+
+public class IncrementalNativeCompiler extends AbstractIncrementalNativeCompiler {
+    private final Compiler<NativeCompileSpec> delegateCompiler;
+
+    public IncrementalNativeCompiler(TaskInternal task, SourceIncludesParser sourceIncludesParser, Iterable<File> includes,
+                                     TaskArtifactStateCacheAccess cacheAccess, FileSnapshotter fileSnapshotter, Compiler<NativeCompileSpec> delegateCompiler) {
+        super(task, sourceIncludesParser, includes, cacheAccess, fileSnapshotter);
+        this.delegateCompiler = delegateCompiler;
+    }
+
+    @Override
+    protected WorkResult doIncrementalCompile(IncrementalCompileProcessor processor, NativeCompileSpec spec) {
+        IncrementalCompilation compilation = processor.processSourceFiles(spec.getSourceFiles());
+
+        // Determine the actual sources to clean/compile
+        spec.setSourceFiles(compilation.getRecompile());
+        spec.setRemovedSourceFiles(compilation.getRemoved());
+        return delegateCompiler.execute(spec);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/ResolvedInclude.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/ResolvedInclude.java
new file mode 100644
index 0000000..ae27164
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/ResolvedInclude.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class ResolvedInclude implements Serializable {
+    private final String include;
+    private final File dependencyFile;
+
+    public ResolvedInclude(String include, File dependencyFile) {
+        this.include = include;
+        this.dependencyFile = dependencyFile;
+    }
+
+    public boolean isUnknown() {
+        return dependencyFile == null;
+    }
+
+    public String getInclude() {
+        return include;
+    }
+
+    public File getFile() {
+        return dependencyFile;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Resolved include '%s'", include);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ResolvedInclude)) {
+            return false;
+        }
+
+        ResolvedInclude that = (ResolvedInclude) o;
+
+        return include.equals(that.include)
+                && (dependencyFile == null ? that.dependencyFile == null : dependencyFile.equals(that.dependencyFile));
+
+    }
+
+    @Override
+    public int hashCode() {
+        return include.hashCode();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludes.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludes.java
new file mode 100644
index 0000000..cb8e170
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludes.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.util.List;
+
+public interface SourceIncludes {
+    List<String> getQuotedIncludes();
+    List<String> getSystemIncludes();
+    List<String> getMacroIncludes();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludesParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludesParser.java
new file mode 100644
index 0000000..906fee9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludesParser.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.File;
+
+public interface SourceIncludesParser {
+
+    SourceIncludes parseIncludes(File sourceFile);
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludesResolver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludesResolver.java
new file mode 100644
index 0000000..30fc7f0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/SourceIncludesResolver.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental;
+
+import java.io.File;
+import java.util.Set;
+
+public interface SourceIncludesResolver {
+    Set<ResolvedInclude> resolveIncludes(File sourceFile, SourceIncludes includes);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/CSourceParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/CSourceParser.java
new file mode 100644
index 0000000..8098d45
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/CSourceParser.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal.incremental.sourceparser;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A parser to extract information from C-compatible source files.
+ */
+public interface CSourceParser {
+
+    SourceDetails parseSource(File sourceFile);
+
+    interface SourceDetails {
+        List<String> getIncludes();
+        List<String> getImports();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/PreprocessingReader.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/PreprocessingReader.java
new file mode 100644
index 0000000..6e38997
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/PreprocessingReader.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental.sourceparser;
+
+import org.apache.tools.ant.filters.BaseFilterReader;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Replaces c-style comments with a single space, and removes line-continuation characters.
+ * This code is largely adopted from org.apache.tools.ant.filters.StripJavaComments.
+ *
+ * This avoids the synchronisation overhead of PushbackReader and is _not_ threadsafe.
+ */
+public class PreprocessingReader extends BaseFilterReader {
+    /**
+     * The read-ahead characters, used for reading ahead up to 2 characters and pushing back into stream.
+     * A value of -1 indicates that no character is in the buffer.
+     */
+    private int[] readAheadChars = new int[2];
+
+    /**
+     * Whether or not the parser is currently in the middle of a string literal.
+     */
+    private boolean inString;
+
+    /**
+     * Whether or not the last char has been a backslash.
+     */
+    private boolean quoted;
+
+    public PreprocessingReader(Reader in) {
+        super(in);
+    }
+
+    /**
+     * Returns the next character in the filtered stream:
+     * <ul>
+     *     <li>Comments will be replaced by a single space</li>
+     *     <li>Line continuation (backslash-newline) will be removed</li>
+     * </ul>
+     */
+    public int read() throws IOException {
+        int ch = next();
+
+        if (ch == '\\') {
+            if (discardNewLine()) {
+                return read();
+            }
+        }
+
+        if (ch == '"' && !quoted) {
+            inString = !inString;
+            quoted = false;
+        } else if (ch == '\\') {
+            quoted = !quoted;
+        } else {
+            quoted = false;
+            if (!inString) {
+                if (ch == '/') {
+                    ch = next();
+                    if (ch == '/') {
+                        while (ch != '\n' && ch != -1 && ch != '\r') {
+                            ch = next();
+                        }
+                    } else if (ch == '*') {
+                        while (ch != -1) {
+                            ch = next();
+                            if (ch == '*') {
+                                ch = next();
+                                while (ch == '*') {
+                                    ch = next();
+                                }
+
+                                if (ch == '/') {
+                                    ch = ' ';
+                                    break;
+                                }
+                            }
+                        }
+                    } else {
+                        pushBack(ch);
+                        ch = '/';
+                    }
+                }
+            }
+        }
+
+        return ch;
+    }
+
+    private boolean discardNewLine() throws IOException {
+        int nextChar = next();
+        if (nextChar == '\n') {
+            return true; // '\\\n' discarded from stream
+        } else if (nextChar == '\r') {
+            int followingChar = next();
+            if (followingChar == '\n') {
+                return true; // '\\\r\n' discarded from stream
+            }
+            pushBack(nextChar);
+            pushBack(followingChar);
+            return false;
+        } else {
+            pushBack(nextChar);
+            return false;
+        }
+    }
+
+    private int next() throws IOException {
+        if (readAheadChars[0] != -1) {
+            int ch = readAheadChars[0];
+            readAheadChars[0] = readAheadChars[1];
+            readAheadChars[1] = -1;
+            return ch;
+        }
+
+        return in.read();
+    }
+
+    private void pushBack(int ch) {
+        if (readAheadChars[1] != -1) {
+            throw new IllegalStateException();
+        }
+        if (readAheadChars[0] != -1) {
+            readAheadChars[1] = ch;
+        } else {
+            readAheadChars[0] = ch;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/RegexBackedCSourceParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/RegexBackedCSourceParser.java
new file mode 100644
index 0000000..8d051c3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/RegexBackedCSourceParser.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal.incremental.sourceparser;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+// TODO:DAZ We might be better with a hand-crafter parser
+public class RegexBackedCSourceParser implements CSourceParser {
+    private static final String INCLUDE_IMPORT_PATTERN = "\\s*#\\s*(include|import)\\s+((<[^>]+>)|(\"[^\"]+\")|(\\w+))";
+    private final Pattern includePattern;
+
+    public RegexBackedCSourceParser() {
+        this.includePattern = Pattern.compile(INCLUDE_IMPORT_PATTERN, Pattern.CASE_INSENSITIVE);
+    }
+
+    public SourceDetails parseSource(File sourceFile) {
+        DefaultSourceDetails sourceDetails = new DefaultSourceDetails();
+        parseFile(sourceFile, sourceDetails);
+        return sourceDetails;
+    }
+
+    private void parseFile(File file, DefaultSourceDetails sourceDetails) {
+        try {
+            BufferedReader bf = new BufferedReader(new PreprocessingReader(new BufferedReader(new FileReader(file))));
+
+            try {
+                String line;
+                while ((line = bf.readLine()) != null) {
+                    Matcher m = includePattern.matcher(line);
+
+                    if (m.lookingAt()) {
+                        boolean isImport = "import".equals(m.group(1));
+                        String value = m.group(2);
+                        if (isImport) {
+                            sourceDetails.getImports().add(value);
+                        } else {
+                            sourceDetails.getIncludes().add(value);
+                        }
+                    }
+                }
+            } finally {
+                IOUtils.closeQuietly(bf);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private static class DefaultSourceDetails implements SourceDetails {
+        private final List<String> includes = new ArrayList<String>();
+        private final List<String> imports = new ArrayList<String>();
+
+        public List<String> getIncludes() {
+            return includes;
+        }
+
+        public List<String> getImports() {
+            return imports;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/package-info.java
new file mode 100644
index 0000000..74f4ebb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 compiling and linking C sources for a native runtime.
+ */
+package org.gradle.nativebinaries.language.c;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/plugins/CNativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/plugins/CNativeBinariesPlugin.groovy
new file mode 100644
index 0000000..53427c9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/plugins/CNativeBinariesPlugin.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.c.CSourceSet
+import org.gradle.language.c.plugins.CLangPlugin
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.language.c.tasks.CCompile
+import org.gradle.nativebinaries.language.internal.DefaultPreprocessingTool
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+/**
+ * A plugin for projects wishing to build native binary components from C sources.
+ *
+ * <p>Automatically includes the {@link CLangPlugin} for core C++ support and the {@link NativeBinariesPlugin} for native binary support.</p>
+ *
+ * <li>Creates a {@link CCompile} task for each {@link CSourceSet} to compile the C sources.</li>
+ */
+ at Incubating
+class CNativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesPlugin)
+        project.plugins.apply(CLangPlugin)
+
+        // TODO:DAZ Clean this up (see CppNativeBinariesPlugin)
+        project.executables.all { Executable executable ->
+            addLanguageExtensionsToComponent(executable)
+        }
+        project.libraries.all { Library library ->
+            addLanguageExtensionsToComponent(library)
+        }
+
+        project.binaries.withType(ProjectNativeBinary) { ProjectNativeBinaryInternal binary ->
+            binary.source.withType(CSourceSet).all { CSourceSet sourceSet ->
+                if (sourceSet.mayHaveSources) {
+                    def compileTask = createCompileTask(project, binary, sourceSet)
+                    compileTask.dependsOn sourceSet
+                    binary.tasks.add compileTask
+                    binary.tasks.builder.source compileTask.outputs.files.asFileTree.matching { include '**/*.obj', '**/*.o' }
+                }
+            }
+        }
+    }
+
+    private def addLanguageExtensionsToComponent(ProjectNativeComponent component) {
+        component.binaries.all { binary ->
+            binary.extensions.create("cCompiler", DefaultPreprocessingTool)
+        }
+    }
+
+    private def createCompileTask(ProjectInternal project, ProjectNativeBinaryInternal binary, CSourceSet sourceSet) {
+        def compileTask = project.task(binary.namingScheme.getTaskName("compile", sourceSet.fullName), type: CCompile) {
+            description = "Compiles the $sourceSet of $binary"
+        }
+
+        compileTask.toolChain = binary.toolChain
+        compileTask.targetPlatform = binary.targetPlatform
+        compileTask.positionIndependentCode = binary instanceof SharedLibraryBinary
+
+        compileTask.includes {
+            sourceSet.exportedHeaders.srcDirs
+        }
+        compileTask.includes {
+            binary.getLibs(sourceSet)*.includeRoots
+        }
+        compileTask.source sourceSet.source
+
+        compileTask.objectFileDir = project.file("${project.buildDir}/objectFiles/${binary.namingScheme.outputDirectoryBase}/${sourceSet.fullName}")
+        compileTask.macros = binary.cCompiler.macros
+        compileTask.compilerArgs = binary.cCompiler.args
+
+        compileTask
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/plugins/CPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/plugins/CPlugin.groovy
new file mode 100644
index 0000000..bcefa85
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/plugins/CPlugin.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.toolchain.internal.plugins.StandardToolChainsPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from C sources.
+ *
+ * <p>Adds core tool chain support to the {@link CNativeBinariesPlugin}.</p>
+ */
+ at Incubating
+class CPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(StandardToolChainsPlugin)
+        project.plugins.apply(CNativeBinariesPlugin)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/tasks/AbstractNativeCompileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/tasks/AbstractNativeCompileTask.groovy
new file mode 100644
index 0000000..0a09f66
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/tasks/AbstractNativeCompileTask.groovy
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.tasks
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.changedetection.state.FileSnapshotter
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs
+import org.gradle.nativebinaries.language.c.internal.incremental.IncrementalCompilerBuilder
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+
+import javax.inject.Inject
+/**
+ * Compiles native source files into object files.
+ */
+ at Incubating
+abstract class AbstractNativeCompileTask extends DefaultTask {
+    private final IncrementalCompilerBuilder incrementalCompilerBuilder
+
+    /**
+     * The tool chain used for compilation.
+     */
+    ToolChain toolChain
+
+    /**
+     * The platform being targeted.
+     */
+    Platform targetPlatform
+
+    /**
+     * Should the compiler generate position independent code?
+     */
+    @Input
+    boolean positionIndependentCode
+
+    /**
+     * The directory where object files will be generated.
+     */
+    @OutputDirectory
+    File objectFileDir
+
+    /**
+     * Returns the header directories to be used for compilation.
+     */
+    @InputFiles
+    FileCollection includes
+
+    /**
+     * Returns the source files to be compiled.
+     */
+    @InputFiles
+    FileCollection source
+
+    // Invalidate output when the tool chain output changes
+    @Input
+    def getOutputType() {
+        return "${toolChain.outputType}:${targetPlatform.compatibilityString}"
+    }
+
+    /**
+     * Macros that should be defined for the compiler.
+     */
+    @Input
+    Map<String, String> macros
+
+    /**
+     * Additional arguments to provide to the compiler.
+     */
+    @Input
+    List<String> compilerArgs
+
+    @Inject
+    AbstractNativeCompileTask() {
+        incrementalCompilerBuilder = new IncrementalCompilerBuilder(services.get(TaskArtifactStateCacheAccess),
+                                                                    services.get(FileSnapshotter), this)
+        includes = project.files()
+        source = project.files()
+    }
+
+    @TaskAction
+    void compile(IncrementalTaskInputs inputs) {
+
+        def spec = createCompileSpec()
+        spec.tempDir = getTemporaryDir()
+        spec.objectFileDir = getObjectFileDir()
+        spec.include getIncludes()
+        spec.source getSource()
+        spec.macros = getMacros()
+        spec.args getCompilerArgs()
+        spec.positionIndependentCode = isPositionIndependentCode()
+
+        PlatformToolChain platformToolChain = toolChain.target(targetPlatform)
+        if (!inputs.incremental) {
+            incrementalCompilerBuilder.withCleanCompile()
+        }
+        incrementalCompilerBuilder.withIncludes(includes)
+        final compiler = createCompiler(platformToolChain)
+        final incrementalCompiler = incrementalCompilerBuilder.createIncrementalCompiler(compiler)
+
+        def result = incrementalCompiler.execute(spec)
+        didWork = result.didWork
+    }
+
+    protected abstract NativeCompileSpec createCompileSpec();
+
+    protected abstract Compiler<NativeCompileSpec> createCompiler(PlatformToolChain toolChain)
+
+    /**
+     * Add directories where the compiler should search for header files.
+     */
+    void includes(Object includeRoots) {
+        includes.from(includeRoots)
+    }
+
+    /**
+     * Adds a set of source files to be compiled.
+     * The provided sourceFiles object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void source(Object sourceFiles) {
+        source.from sourceFiles
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/tasks/CCompile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/tasks/CCompile.groovy
new file mode 100644
index 0000000..e0f3f58
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/c/tasks/CCompile.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.tasks
+import org.gradle.api.Incubating
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.nativebinaries.language.c.internal.DefaultCCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+/**
+ * Compiles C source files into object files.
+ */
+ at Incubating
+class CCompile extends AbstractNativeCompileTask {
+    @Override
+    protected NativeCompileSpec createCompileSpec() {
+        new DefaultCCompileSpec()
+    }
+
+    protected Compiler<NativeCompileSpec> createCompiler(PlatformToolChain toolChain) {
+        toolChain.createCCompiler()
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/internal/CppCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/internal/CppCompileSpec.java
new file mode 100644
index 0000000..03d6217
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/internal/CppCompileSpec.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.nativebinaries.language.cpp.internal;
+
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+public interface CppCompileSpec extends NativeCompileSpec {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/internal/DefaultCppCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/internal/DefaultCppCompileSpec.java
new file mode 100644
index 0000000..2a7d4c9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/internal/DefaultCppCompileSpec.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.internal;
+
+import org.gradle.nativebinaries.language.internal.AbstractNativeCompileSpec;
+
+public class DefaultCppCompileSpec extends AbstractNativeCompileSpec implements CppCompileSpec {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/package-info.java
new file mode 100644
index 0000000..696f621
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 compiling and linking C++ sources for a native runtime.
+ */
+package org.gradle.nativebinaries.language.cpp;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppNativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppNativeBinariesPlugin.groovy
new file mode 100644
index 0000000..6d68b09
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppNativeBinariesPlugin.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.cpp.CppSourceSet
+import org.gradle.language.cpp.plugins.CppLangPlugin
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.language.cpp.tasks.CppCompile
+import org.gradle.nativebinaries.language.internal.DefaultPreprocessingTool
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+/**
+ * A plugin for projects wishing to build native binary components from C++ sources.
+ *
+ * <p>Automatically includes the {@link org.gradle.language.cpp.plugins.CppLangPlugin} for core C++ support and the {@link NativeBinariesPlugin} for native binary support.</p>
+ *
+ * <li>Creates a {@link CppCompile} task for each {@link CppSourceSet} to compile the C++ sources.</li>
+ */
+ at Incubating
+class CppNativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesPlugin)
+        project.plugins.apply(CppLangPlugin)
+
+        // TODO:DAZ It's ugly that we can't do this as project.binaries.all, but this is the way I could
+        // add the cppCompiler in time to allow it to be configured within the component.binaries.all block.
+        project.executables.all { Executable executable ->
+            executable.binaries.all { binary ->
+                binary.extensions.create("cppCompiler", DefaultPreprocessingTool)
+            }
+        }
+        project.libraries.all { Library library ->
+            library.binaries.all { binary ->
+                binary.extensions.create("cppCompiler", DefaultPreprocessingTool)
+            }
+        }
+
+        project.binaries.withType(ProjectNativeBinary) { ProjectNativeBinaryInternal binary ->
+            binary.source.withType(CppSourceSet).all { CppSourceSet sourceSet ->
+                if (sourceSet.mayHaveSources) {
+                    def compileTask = createCompileTask(project, binary, sourceSet)
+                    compileTask.dependsOn sourceSet
+                    binary.tasks.add compileTask
+                    binary.tasks.builder.source compileTask.outputs.files.asFileTree.matching { include '**/*.obj', '**/*.o' }
+                }
+            }
+        }
+    }
+
+    private def createCompileTask(ProjectInternal project, ProjectNativeBinaryInternal binary, CppSourceSet sourceSet) {
+        def compileTask = project.task(binary.namingScheme.getTaskName("compile", sourceSet.fullName), type: CppCompile) {
+            description = "Compiles the $sourceSet of $binary"
+        }
+
+        compileTask.toolChain = binary.toolChain
+        compileTask.targetPlatform = binary.targetPlatform
+        compileTask.positionIndependentCode = binary instanceof SharedLibraryBinary
+
+        compileTask.includes {
+            sourceSet.exportedHeaders.srcDirs
+        }
+        compileTask.includes {
+            binary.getLibs(sourceSet)*.includeRoots
+        }
+        compileTask.source sourceSet.source
+
+        compileTask.objectFileDir = project.file("${project.buildDir}/objectFiles/${binary.namingScheme.outputDirectoryBase}/${sourceSet.fullName}")
+        compileTask.macros = binary.cppCompiler.macros
+        compileTask.compilerArgs = binary.cppCompiler.args
+
+        compileTask
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppPlugin.groovy
new file mode 100644
index 0000000..7f2c322
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppPlugin.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.toolchain.internal.plugins.StandardToolChainsPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from C++ sources.
+ *
+ * <p>Adds core tool chain support to the {@link CppNativeBinariesPlugin}.</p>
+ */
+ at Incubating
+class CppPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(StandardToolChainsPlugin)
+        project.plugins.apply(CppNativeBinariesPlugin)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/package-info.java
new file mode 100644
index 0000000..4780b1d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Plugins for buildings C++ projects.
+ */
+package org.gradle.nativebinaries.language.cpp.plugins;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/tasks/CppCompile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/tasks/CppCompile.groovy
new file mode 100644
index 0000000..7aa2fe5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/tasks/CppCompile.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.nativebinaries.language.cpp.tasks
+import org.gradle.api.Incubating
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompileTask
+import org.gradle.nativebinaries.language.cpp.internal.DefaultCppCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+/**
+ * Compiles C++ source files into object files.
+ */
+ at Incubating
+class CppCompile extends AbstractNativeCompileTask {
+    @Override
+    protected NativeCompileSpec createCompileSpec() {
+        new DefaultCppCompileSpec()
+    }
+
+    @Override
+    protected Compiler<NativeCompileSpec> createCompiler(PlatformToolChain toolChain) {
+        return toolChain.createCppCompiler()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/tasks/package-info.java
new file mode 100644
index 0000000..55609c5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/cpp/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tasks for building C++ projects.
+ */
+package org.gradle.nativebinaries.language.cpp.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/internal/AbstractNativeCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/internal/AbstractNativeCompileSpec.java
new file mode 100644
index 0000000..74112f3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/internal/AbstractNativeCompileSpec.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.internal;
+
+import org.gradle.nativebinaries.internal.AbstractBinaryToolSpec;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.io.File;
+import java.util.*;
+
+public abstract class AbstractNativeCompileSpec extends AbstractBinaryToolSpec implements NativeCompileSpec {
+
+    private List<File> includeRoots = new ArrayList<File>();
+    private List<File> sourceFiles = new ArrayList<File>();
+    private List<File> removedSourceFiles = new ArrayList<File>();
+    private Map<String, String> macros = new LinkedHashMap<String, String>();
+    private File objectFileDir;
+    private boolean positionIndependentCode;
+
+    public List<File> getIncludeRoots() {
+        return includeRoots;
+    }
+
+    public void include(File... includeRoots) {
+        Collections.addAll(this.includeRoots, includeRoots);
+    }
+
+    public void include(Iterable<File> includeRoots) {
+        addAll(this.includeRoots, includeRoots);
+    }
+
+    public List<File> getSourceFiles() {
+        return sourceFiles;
+    }
+
+    public void source(Iterable<File> sources) {
+        addAll(sourceFiles, sources);
+    }
+
+    public void setSourceFiles(Collection<File> sources) {
+        sourceFiles.clear();
+        sourceFiles.addAll(sources);
+    }
+
+    public List<File> getRemovedSourceFiles() {
+        return removedSourceFiles;
+    }
+
+    public void removedSource(Iterable<File> sources) {
+        addAll(removedSourceFiles, sources);
+    }
+
+    public void setRemovedSourceFiles(Collection<File> sources) {
+        removedSourceFiles.clear();
+        removedSourceFiles.addAll(sources);
+    }
+
+    public File getObjectFileDir() {
+        return objectFileDir;
+    }
+
+    public void setObjectFileDir(File objectFileDir) {
+        this.objectFileDir = objectFileDir;
+    }
+
+    public Map<String, String> getMacros() {
+        return macros;
+    }
+
+    public void setMacros(Map<String, String> macros) {
+        this.macros = macros;
+    }
+
+    public void define(String name) {
+        macros.put(name, null);
+    }
+
+    public void define(String name, String value) {
+        macros.put(name, value);
+    }
+
+    public boolean isPositionIndependentCode() {
+        return positionIndependentCode;
+    }
+
+    public void setPositionIndependentCode(boolean positionIndependentCode) {
+        this.positionIndependentCode = positionIndependentCode;
+    }
+
+    private void addAll(List<File> list, Iterable<File> iterable) {
+        for (File file : iterable) {
+            list.add(file);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/internal/DefaultPreprocessingTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/internal/DefaultPreprocessingTool.java
new file mode 100644
index 0000000..74e34ee
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/internal/DefaultPreprocessingTool.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.internal;
+
+import org.gradle.nativebinaries.internal.DefaultTool;
+import org.gradle.nativebinaries.language.PreprocessingTool;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class DefaultPreprocessingTool extends DefaultTool implements PreprocessingTool {
+    private final Map<String, String> definitions = new LinkedHashMap<String, String>();
+
+    public Map<String, String> getMacros() {
+        return definitions;
+    }
+
+    public void define(String name) {
+        definitions.put(name, null);
+    }
+
+    public void define(String name, String definition) {
+        definitions.put(name, definition);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/internal/DefaultObjectiveCCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/internal/DefaultObjectiveCCompileSpec.java
new file mode 100644
index 0000000..68ef153
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/internal/DefaultObjectiveCCompileSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec.internal;
+
+import org.gradle.nativebinaries.language.internal.AbstractNativeCompileSpec;
+
+public class DefaultObjectiveCCompileSpec extends AbstractNativeCompileSpec implements ObjectiveCCompileSpec {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/internal/ObjectiveCCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/internal/ObjectiveCCompileSpec.java
new file mode 100644
index 0000000..88bd223
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/internal/ObjectiveCCompileSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec.internal;
+
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+public interface ObjectiveCCompileSpec extends NativeCompileSpec {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/package-info.java
new file mode 100644
index 0000000..16fda4a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 compiling and linking Objective-C sources for a native runtime.
+ */
+package org.gradle.nativebinaries.language.objectivec;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCNativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCNativeBinariesPlugin.groovy
new file mode 100644
index 0000000..ea340e8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCNativeBinariesPlugin.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.objectivec.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.objectivec.ObjectiveCSourceSet
+import org.gradle.language.objectivec.plugins.ObjectiveCLangPlugin
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile
+import org.gradle.nativebinaries.language.internal.DefaultPreprocessingTool
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+/**
+ * A plugin for projects wishing to build native binary components from Objective-C sources.
+ *
+ * <p>Automatically includes the {@link org.gradle.language.objectivec.plugins.ObjectiveCLangPlugin} for core Objective-C support and the {@link NativeBinariesPlugin} for native binary support.</p>
+ *
+ * <li>Creates a {@link ObjectiveCCompile} task for each {@link ObjectiveCSourceSet} to compile the Objective-C sources.</li>
+ */
+ at Incubating
+class ObjectiveCNativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesPlugin)
+        project.plugins.apply(ObjectiveCLangPlugin)
+
+        project.executables.all { Executable executable ->
+            executable.binaries.all { binary ->
+                binary.extensions.create("objcCompiler", DefaultPreprocessingTool)
+            }
+        }
+
+        project.libraries.all { Library library ->
+            library.binaries.all { binary ->
+                binary.extensions.create("objcCompiler", DefaultPreprocessingTool)
+            }
+        }
+
+        project.binaries.withType(ProjectNativeBinary) { ProjectNativeBinary binary ->
+            binary.source.withType(ObjectiveCSourceSet).all { ObjectiveCSourceSet sourceSet ->
+                if (sourceSet.mayHaveSources) {
+                    def compileTask = createCompileTask(project, binary, sourceSet)
+                    binary.tasks.add compileTask
+                    binary.tasks.builder.source compileTask.outputs.files.asFileTree.matching { include '**/*.obj', '**/*.o' }
+                }
+            }
+        }
+    }
+
+    private def createCompileTask(ProjectInternal project, ProjectNativeBinaryInternal binary, ObjectiveCSourceSet sourceSet) {
+        def compileTask = project.task(binary.namingScheme.getTaskName("compile", sourceSet.fullName), type: ObjectiveCCompile) {
+            description = "Compiles the $sourceSet of $binary"
+        }
+
+        compileTask.toolChain = binary.toolChain
+        compileTask.targetPlatform = binary.targetPlatform
+        compileTask.positionIndependentCode = binary instanceof SharedLibraryBinary
+
+        compileTask.includes {
+            sourceSet.exportedHeaders.srcDirs
+        }
+        compileTask.includes {
+            binary.getLibs(sourceSet)*.includeRoots
+        }
+
+        compileTask.source sourceSet.source
+
+        compileTask.objectFileDir = project.file("${project.buildDir}/objectFiles/${binary.namingScheme.outputDirectoryBase}/${sourceSet.fullName}")
+        compileTask.macros = binary.objcCompiler.macros
+        compileTask.compilerArgs = binary.objcCompiler.args
+
+        compileTask
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCPlugin.groovy
new file mode 100644
index 0000000..14feee5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCPlugin.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.objectivec.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.toolchain.internal.plugins.StandardToolChainsPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Objective-C sources.
+ *
+ * <p>Adds core tool chain support to the {@link ObjectiveCNativeBinariesPlugin}.</p>
+ */
+ at Incubating
+class ObjectiveCPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(StandardToolChainsPlugin)
+        project.plugins.apply(ObjectiveCNativeBinariesPlugin)
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/package-info.java
new file mode 100644
index 0000000..9760195
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 building Objective-C projects.
+ */
+package org.gradle.nativebinaries.language.objectivec.plugins;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/tasks/ObjectiveCCompile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/tasks/ObjectiveCCompile.groovy
new file mode 100644
index 0000000..0d7a046
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/tasks/ObjectiveCCompile.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec.tasks
+import org.gradle.api.Incubating
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompileTask
+import org.gradle.nativebinaries.language.objectivec.internal.DefaultObjectiveCCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+/**
+ * Compiles Objective-C source files into object files.
+ */
+ at Incubating
+class ObjectiveCCompile extends AbstractNativeCompileTask {
+    @Override
+    protected NativeCompileSpec createCompileSpec() {
+        new DefaultObjectiveCCompileSpec()
+    }
+
+    @Override
+    protected Compiler<NativeCompileSpec> createCompiler(PlatformToolChain toolChain) {
+        return toolChain.createObjectiveCCompiler()
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/tasks/package-info.java
new file mode 100644
index 0000000..555e538
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivec/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tasks for building Objective-C projects.
+ */
+package org.gradle.nativebinaries.language.objectivec.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/internal/DefaultObjectiveCppCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/internal/DefaultObjectiveCppCompileSpec.java
new file mode 100644
index 0000000..8125b84
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/internal/DefaultObjectiveCppCompileSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp.internal;
+
+import org.gradle.nativebinaries.language.internal.AbstractNativeCompileSpec;
+
+public class DefaultObjectiveCppCompileSpec extends AbstractNativeCompileSpec implements ObjectiveCppCompileSpec {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/internal/ObjectiveCppCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/internal/ObjectiveCppCompileSpec.java
new file mode 100644
index 0000000..4ad9a26
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/internal/ObjectiveCppCompileSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp.internal;
+
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+public interface ObjectiveCppCompileSpec extends NativeCompileSpec {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/package-info.java
new file mode 100644
index 0000000..082204c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 compiling and linking Objective-C++ sources for a native runtime.
+ */
+package org.gradle.nativebinaries.language.objectivecpp;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppNativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppNativeBinariesPlugin.groovy
new file mode 100644
index 0000000..11a3ab4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppNativeBinariesPlugin.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.objectivecpp.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.objectivecpp.ObjectiveCppSourceSet
+import org.gradle.language.objectivecpp.plugins.ObjectiveCppLangPlugin
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile
+import org.gradle.nativebinaries.language.internal.DefaultPreprocessingTool
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Objective-C++ sources.
+ *
+ * <p>Automatically includes the {@link org.gradle.language.objectivecpp.plugins.ObjectiveCppLangPlugin} for core Objective-C++ support and the {@link NativeBinariesPlugin} for native binary support.</p>
+ *
+ * <li>Creates a {@link ObjectiveCppCompile} task for each {@link ObjectiveCppSourceSet} to compile the Objective-C++ sources.</li>
+ */
+ at Incubating
+class ObjectiveCppNativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesPlugin)
+        project.plugins.apply(ObjectiveCppLangPlugin)
+
+        project.executables.all { Executable executable ->
+            executable.binaries.all { binary ->
+                binary.extensions.create("objcppCompiler", DefaultPreprocessingTool)
+            }
+        }
+
+        project.libraries.all { Library library ->
+            library.binaries.all { binary ->
+                binary.extensions.create("objcppCompiler", DefaultPreprocessingTool)
+            }
+        }
+
+        project.binaries.withType(NativeBinary) { ProjectNativeBinaryInternal binary ->
+            binary.source.withType(ObjectiveCppSourceSet).all { ObjectiveCppSourceSet sourceSet ->
+                if (sourceSet.mayHaveSources) {
+                    def compileTask = createCompileTask(project, binary, sourceSet)
+                    binary.tasks.add compileTask
+                    binary.tasks.builder.source compileTask.outputs.files.asFileTree.matching { include '**/*.obj', '**/*.o' }
+                }
+            }
+        }
+    }
+
+    private def createCompileTask(ProjectInternal project, ProjectNativeBinaryInternal binary, ObjectiveCppSourceSet sourceSet) {
+        def compileTask = project.task(binary.namingScheme.getTaskName("compile", sourceSet.fullName), type: ObjectiveCppCompile) {
+            description = "Compiles the $sourceSet of $binary"
+        }
+
+        compileTask.toolChain = binary.toolChain
+        compileTask.targetPlatform = binary.targetPlatform
+        compileTask.positionIndependentCode = binary instanceof SharedLibraryBinary
+
+        compileTask.includes {
+            sourceSet.exportedHeaders.srcDirs
+        }
+
+        compileTask.source sourceSet.source
+        binary.getLibs(sourceSet).each { deps ->
+            compileTask.includes deps.includeRoots
+        }
+
+        compileTask.objectFileDir = project.file("${project.buildDir}/objectFiles/${binary.namingScheme.outputDirectoryBase}/${sourceSet.fullName}")
+        compileTask.macros = binary.objcppCompiler.macros
+        compileTask.compilerArgs = binary.objcppCompiler.args
+
+        compileTask
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppPlugin.groovy
new file mode 100644
index 0000000..d871662
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppPlugin.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.objectivecpp.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.toolchain.internal.plugins.StandardToolChainsPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Objective-C++ sources.
+ *
+ * <p>Adds core tool chain support to the {@link ObjectiveCppNativeBinariesPlugin}.</p>
+ */
+ at Incubating
+class ObjectiveCppPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(StandardToolChainsPlugin)
+        project.plugins.apply(ObjectiveCppNativeBinariesPlugin)
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/package-info.java
new file mode 100644
index 0000000..70315a5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 building Objective-C++ projects.
+ */
+package org.gradle.nativebinaries.language.objectivecpp.plugins;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/tasks/ObjectiveCppCompile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/tasks/ObjectiveCppCompile.groovy
new file mode 100644
index 0000000..528c4ec
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/tasks/ObjectiveCppCompile.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp.tasks
+import org.gradle.api.Incubating
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompileTask
+import org.gradle.nativebinaries.language.objectivecpp.internal.DefaultObjectiveCppCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+/**
+ * Compiles Objective-C++ source files into object files.
+ */
+ at Incubating
+class ObjectiveCppCompile extends AbstractNativeCompileTask {
+    @Override
+    protected NativeCompileSpec createCompileSpec() {
+        new DefaultObjectiveCppCompileSpec()
+    }
+
+    @Override
+    protected Compiler<NativeCompileSpec> createCompiler(PlatformToolChain toolChain) {
+        return toolChain.createObjectiveCppCompiler()
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/tasks/package-info.java
new file mode 100644
index 0000000..cdd90f3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/objectivecpp/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tasks for building Objective-C++ projects.
+ */
+package org.gradle.nativebinaries.language.objectivecpp.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/package-info.java
new file mode 100644
index 0000000..6b6cb32
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 building sources for a native runtime.
+ */
+package org.gradle.nativebinaries.language;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/internal/DefaultWindowsResourceCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/internal/DefaultWindowsResourceCompileSpec.java
new file mode 100644
index 0000000..801e90f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/internal/DefaultWindowsResourceCompileSpec.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.rc.internal;
+
+import org.gradle.nativebinaries.language.internal.AbstractNativeCompileSpec;
+
+public class DefaultWindowsResourceCompileSpec extends AbstractNativeCompileSpec implements WindowsResourceCompileSpec {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/internal/WindowsResourceCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/internal/WindowsResourceCompileSpec.java
new file mode 100644
index 0000000..6c288a1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/internal/WindowsResourceCompileSpec.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.rc.internal;
+
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+public interface WindowsResourceCompileSpec extends NativeCompileSpec {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesNativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesNativeBinariesPlugin.groovy
new file mode 100644
index 0000000..b0dd256
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesNativeBinariesPlugin.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.rc.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.language.rc.WindowsResourceSet
+import org.gradle.language.rc.plugins.WindowsResourceScriptPlugin
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.internal.StaticLibraryBinaryInternal
+import org.gradle.nativebinaries.language.internal.DefaultPreprocessingTool
+import org.gradle.nativebinaries.language.rc.tasks.WindowsResourceCompile
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Windows Resource sources.
+ *
+ * <p>Automatically includes the {@link WindowsResourceScriptPlugin} for core Windows Resource source support
+ * and the {@link NativeBinariesPlugin} for native binary support.</p>
+ *
+ * <li>Creates a {@link WindowsResourceCompile} task for each {@link WindowsResourceSet} to compile the sources.</li>
+ */
+ at Incubating
+class WindowsResourcesNativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(NativeBinariesPlugin)
+        project.plugins.apply(WindowsResourceScriptPlugin)
+
+        // TODO:DAZ Clean this up (see CppNativeBinariesPlugin)
+        project.executables.all { Executable executable ->
+            addLanguageExtensionsToComponent(executable)
+        }
+        project.libraries.all { Library library ->
+            addLanguageExtensionsToComponent(library)
+        }
+
+        project.binaries.withType(ProjectNativeBinary) { ProjectNativeBinaryInternal binary ->
+            if (shouldProcessResources(binary)) {
+                binary.source.withType(WindowsResourceSet).all { WindowsResourceSet resources ->
+                    if (resources.mayHaveSources) {
+                        def resourceCompileTask = createResourceCompileTask(project, binary, resources)
+                        resourceCompileTask.dependsOn resources
+                        binary.tasks.add resourceCompileTask
+                        final resourceOutputs = resourceCompileTask.outputs.files.asFileTree.matching { include '**/*.res' }
+                        binary.tasks.builder.source resourceOutputs
+                        if (binary instanceof StaticLibraryBinaryInternal) {
+                            binary.additionalLinkFiles resourceOutputs
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private def addLanguageExtensionsToComponent(ProjectNativeComponent component) {
+        component.binaries.all { NativeBinary binary ->
+            if (shouldProcessResources(binary)) {
+                binary.extensions.create("rcCompiler", DefaultPreprocessingTool)
+            }
+        }
+    }
+
+    private boolean shouldProcessResources(NativeBinary binary) {
+        binary.targetPlatform.operatingSystem.windows
+    }
+
+    private def createResourceCompileTask(ProjectInternal project, ProjectNativeBinaryInternal binary, WindowsResourceSet sourceSet) {
+        WindowsResourceCompile compileTask = project.task(binary.namingScheme.getTaskName("compile", sourceSet.fullName), type: WindowsResourceCompile) {
+            description = "Compiles resources of the $sourceSet of $binary"
+        }
+
+        compileTask.toolChain = binary.toolChain
+        compileTask.targetPlatform = binary.targetPlatform
+
+        compileTask.includes {
+            sourceSet.exportedHeaders.srcDirs
+        }
+        compileTask.source sourceSet.source
+
+        compileTask.outputDir = project.file("${project.buildDir}/objectFiles/${binary.namingScheme.outputDirectoryBase}/${sourceSet.fullName}")
+        compileTask.macros = binary.rcCompiler.macros
+        compileTask.compilerArgs = binary.rcCompiler.args
+
+        compileTask
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesPlugin.groovy
new file mode 100644
index 0000000..2e0ac11
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/plugins/WindowsResourcesPlugin.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.rc.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.toolchain.internal.plugins.StandardToolChainsPlugin
+
+/**
+ * A plugin for projects wishing to build native binary components from Windows Resource sources.
+ *
+ * <p>Adds core tool chain support to the {@link WindowsResourcesNativeBinariesPlugin}.</p>
+ */
+ at Incubating
+class WindowsResourcesPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(StandardToolChainsPlugin)
+
+        project.plugins.apply(WindowsResourcesNativeBinariesPlugin)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/tasks/WindowsResourceCompile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/tasks/WindowsResourceCompile.groovy
new file mode 100644
index 0000000..4d0bd62
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/language/rc/tasks/WindowsResourceCompile.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.rc.tasks
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.changedetection.state.FileSnapshotter
+import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs
+import org.gradle.nativebinaries.language.c.internal.incremental.IncrementalCompilerBuilder
+import org.gradle.nativebinaries.language.rc.internal.DefaultWindowsResourceCompileSpec
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+
+import javax.inject.Inject
+/**
+ * Compiles Windows Resource scripts into .res files.
+ */
+ at Incubating
+class WindowsResourceCompile extends DefaultTask {
+    private final IncrementalCompilerBuilder incrementalCompilerBuilder
+
+    @Inject
+    WindowsResourceCompile() {
+        incrementalCompilerBuilder = new IncrementalCompilerBuilder(services.get(TaskArtifactStateCacheAccess),
+                                                                    services.get(FileSnapshotter), this)
+        includes = project.files()
+        source = project.files()
+    }
+
+     /**
+      * The tool chain used for compilation.
+      */
+     ToolChain toolChain
+
+     /**
+      * The platform being targeted.
+      */
+     Platform targetPlatform
+
+     /**
+      * The directory where object files will be generated.
+      */
+     @OutputDirectory
+     File outputDir
+
+     /**
+      * Returns the header directories to be used for compilation.
+      */
+     @InputFiles
+     FileCollection includes
+
+     /**
+      * Returns the source files to be compiled.
+      */
+     @InputFiles
+     FileCollection source
+
+     // Invalidate output when the tool chain output changes
+     @Input
+     def getOutputType() {
+         return "${toolChain.outputType}:${targetPlatform.compatibilityString}"
+     }
+
+     /**
+      * Macros that should be defined for the compiler.
+      */
+     @Input
+     Map<String, String> macros = [:]
+
+     /**
+      * Additional arguments to provide to the compiler.
+      */
+     @Input
+     List<String> compilerArgs = []
+
+     @TaskAction
+     void compile(IncrementalTaskInputs inputs) {
+         def spec = new DefaultWindowsResourceCompileSpec()
+         spec.tempDir = getTemporaryDir()
+         spec.objectFileDir = getOutputDir()
+         spec.include getIncludes()
+         spec.source getSource()
+         spec.macros = getMacros()
+         spec.args getCompilerArgs()
+
+         PlatformToolChain platformToolChain = toolChain.target(targetPlatform)
+         final compiler = platformToolChain.createWindowsResourceCompiler()
+         if (!inputs.incremental) {
+             incrementalCompilerBuilder.withCleanCompile()
+         }
+         incrementalCompilerBuilder.withIncludes(includes)
+         final incrementalCompiler = incrementalCompilerBuilder.createIncrementalCompiler(compiler)
+         def result = incrementalCompiler.execute(spec)
+         didWork = result.didWork
+     }
+
+    /**
+     * Add directories where the compiler should search for header files.
+     */
+    void includes(Object includeRoots) {
+        includes.from(includeRoots)
+    }
+
+     /**
+      * Adds a set of source files to be compiled.
+      * The provided sourceFiles object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+      */
+     void source(Object sourceFiles) {
+         source.from sourceFiles
+     }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/package-info.java
new file mode 100644
index 0000000..5911ef4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Classes that model aspects of native component projects.
+ */
+package org.gradle.nativebinaries;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/Architecture.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/Architecture.java
new file mode 100644
index 0000000..9f6de2b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/Architecture.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A cpu architecture.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface Architecture extends Named {
+    /**
+     * Returns a human-consumable display name for this architecture.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/OperatingSystem.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/OperatingSystem.java
new file mode 100644
index 0000000..b87b33d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/OperatingSystem.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * A machine operating system.
+ */
+ at Incubating
+public interface OperatingSystem extends Named {
+    /**
+     * Returns a human-consumable display name for this operating system.
+     */
+    String getDisplayName();
+
+    /**
+     * Is this the current OS?
+     */
+    boolean isCurrent();
+
+    /**
+     * Is it Windows?
+     */
+    boolean isWindows();
+
+    /**
+     * Is it Mac OS X?
+     */
+    boolean isMacOsX();
+
+    /**
+     * Is it Linux?
+     */
+    boolean isLinux();
+
+    /**
+     * Is it Solaris?
+     */
+    boolean isSolaris();
+
+    /**
+     * Is it FreeBSD?
+     */
+    boolean isFreeBSD();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/Platform.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/Platform.java
new file mode 100644
index 0000000..0d944c8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/Platform.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.platform;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A target platform for building native binaries. Each target platform is given a name, and may optionally be given
+ * a specific {@link Architecture} and/or {@link OperatingSystem} to target.
+ *
+ * <pre>
+ *     model {
+ *         platforms {
+ *             windows_x86 {
+ *                 architecture "i386"
+ *                 operatingSystem "windows"
+ *             }
+ *         }
+ *     }
+ * </pre>
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface Platform extends Named {
+    /**
+     * Returns a human-consumable display name for this platform.
+     */
+    String getDisplayName();
+
+    /**
+     * The cpu architecture being targeted. Defaults to the default architecture produced by the tool chain.
+     */
+    Architecture getArchitecture();
+
+    /**
+     * Sets the cpu architecture being targeted.
+     * The architecture is provided as a string name, which is translated into one of the supported architecture types.
+     *
+     * <table>
+     *     <tr>
+     *         <th>Instruction Set</th>
+     *         <th>32-bit names</th>
+     *         <th>64-bit names</th>
+     *     </tr>
+     *     <tr>
+     *         <td>Intel x86</td>
+     *         <td>"x86", "i386", "ia-32"</td>
+     *         <td>"x86_64", "amd64", "x64", "x86-64"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Intel Itanium</td>
+     *         <td></td>
+     *         <td>"ia-64"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Power PC</td>
+     *         <td>"ppc"</td>
+     *         <td>"ppc64"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Sparc</td>
+     *         <td>"sparc", "sparc32", "sparc-v7", "sparc-v8"</td>
+     *         <td>"sparc64", "ultrasparc", "sparc-v9"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>ARM</td>
+     *         <td>"arm"</td>
+     *         <td></td>
+     *     </tr>
+     * </table>
+     */
+    void architecture(Object notation);
+
+    /**
+     * The operating system being targeted.
+     * Defaults to the default operating system targeted by the tool chain (normally the current operating system).
+     */
+    OperatingSystem getOperatingSystem();
+
+    /**
+     * Sets the operating system being targeted.
+     * The operating system is provided as a string name, which is translated into one of the supported operating system types.
+     *
+     * <table>
+     *     <tr>
+     *         <th>Operating System</th>
+     *         <th>Aliases</th>
+     *     </tr>
+     *     <tr>
+     *         <td>Windows</td>
+     *         <td>"windows"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>GNU/Linux</td>
+     *         <td>"linux"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Mac OS X</td>
+     *         <td>"osx", "mac os x", "darwin"</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Solaris</td>
+     *         <td>"solaris", "sunos"</td>
+     *     </tr>
+     * </table>
+     */
+    void operatingSystem(Object notation);
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/PlatformContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/PlatformContainer.java
new file mode 100644
index 0000000..86718b3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/PlatformContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.platform;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * A container of {@link Platform}s.
+ */
+ at Incubating
+public interface PlatformContainer extends NamedDomainObjectContainer<Platform> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureInternal.java
new file mode 100644
index 0000000..d3fc1ca
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureInternal.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.nativebinaries.platform.Architecture;
+
+public interface ArchitectureInternal extends Architecture {
+    static final ArchitectureInternal TOOL_CHAIN_DEFAULT = new DefaultArchitecture("default", null, 0);
+
+    enum InstructionSet { X86, ITANIUM, PPC, SPARC, ARM }
+
+    InstructionSet getInstructionSet();
+
+    int getRegisterSize();
+
+    boolean isI386();
+
+    boolean isAmd64();
+
+    boolean isIa64();
+
+    boolean isArm();
+
+    boolean isArmv8();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureNotationParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureNotationParser.java
new file mode 100644
index 0000000..d71d730
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureNotationParser.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.TypedNotationParser;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.nativebinaries.platform.Architecture;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GUtil;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class ArchitectureNotationParser {
+
+    private static final List<String> X86_ALIASES = Arrays.asList("x86", "i386", "ia-32");
+    private static final List<String> X86_64_ALIASES = Arrays.asList("x86_64", "amd64", "x64", "x86-64");
+    private static final List<String> ITANIUM_ALIASES = Arrays.asList("ia-64");
+    private static final List<String> PPC_32_ALIASES = Arrays.asList("ppc");
+    private static final List<String> PPC_64_ALIASES = Arrays.asList("ppc64");
+    private static final List<String> SPARC_32_ALIASES = Arrays.asList("sparc", "sparc32", "sparc-v7", "sparc-v8");
+    private static final List<String> SPARC_64_ALIASES = Arrays.asList("sparc64", "ultrasparc", "sparc-v9");
+    private static final List<String> ARM_ALIASES = Arrays.asList("arm");
+
+    public static NotationParser<Object, Architecture> parser() {
+        return new NotationParserBuilder<Architecture>()
+                .resultingType(Architecture.class)
+                .parser(new Parser())
+                .toComposite();
+    }
+
+    private static final class Parser extends TypedNotationParser<String, Architecture> {
+        private Parser() {
+            super(String.class);
+        }
+
+        @Override
+        protected Architecture parseType(String notation) {
+            if (X86_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.X86, 32);
+            }
+            if (X86_64_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.X86, 64);
+            }
+            if (ITANIUM_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.ITANIUM, 64);
+            }
+            if (PPC_32_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.PPC, 32);
+            }
+            if (PPC_64_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.PPC, 64);
+            }
+            if (SPARC_32_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.SPARC, 32);
+            }
+            if (SPARC_64_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.SPARC, 64);
+            }
+            if (ARM_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultArchitecture(notation, ArchitectureInternal.InstructionSet.ARM, 32);
+            }
+            throw new UnsupportedNotationException(notation);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            List<String> validList = CollectionUtils.flattenCollections(String.class,
+                    X86_ALIASES, X86_64_ALIASES, ITANIUM_ALIASES, PPC_32_ALIASES, PPC_64_ALIASES, SPARC_32_ALIASES, SPARC_64_ALIASES, ARM_ALIASES
+            );
+            candidateFormats.add("One of the following values: " + GUtil.toString(validList));
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultArchitecture.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultArchitecture.java
new file mode 100644
index 0000000..27c28f0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultArchitecture.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal;
+
+public class DefaultArchitecture implements ArchitectureInternal {
+    private final String name;
+    private final InstructionSet instructionSet;
+    private final int registerSize;
+
+    public DefaultArchitecture(String name, InstructionSet instructionSet, int registerSize) {
+        this.name = name;
+        this.instructionSet = instructionSet;
+        this.registerSize = registerSize;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getDisplayName() {
+        return String.format("architecture '%s'", name);
+    }
+
+    public InstructionSet getInstructionSet() {
+        return instructionSet;
+    }
+
+    public int getRegisterSize() {
+        return registerSize;
+    }
+
+    public boolean isI386() {
+        return instructionSet == InstructionSet.X86 && registerSize == 32;
+    }
+
+    public boolean isAmd64() {
+        return instructionSet == InstructionSet.X86 && registerSize == 64;
+    }
+
+    public boolean isIa64() {
+        return instructionSet == InstructionSet.ITANIUM && registerSize == 64;
+    }
+
+    public boolean isArm() {
+        return instructionSet == InstructionSet.ARM && registerSize == 32;
+    }
+
+    public boolean isArmv8() {
+        return instructionSet == InstructionSet.ARM && registerSize == 64;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((instructionSet == null) ? 0 : instructionSet.hashCode());
+        result = prime * result + registerSize;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        DefaultArchitecture other = (DefaultArchitecture) obj;
+        if (instructionSet != other.instructionSet) {
+            return false;
+        }
+        if (registerSize != other.registerSize) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultOperatingSystem.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultOperatingSystem.java
new file mode 100644
index 0000000..26745de
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultOperatingSystem.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.nativebinaries.platform.OperatingSystem;
+
+public class DefaultOperatingSystem implements OperatingSystem {
+    private static final org.gradle.internal.os.OperatingSystem CURRENT_OS = org.gradle.internal.os.OperatingSystem.current();
+    public static final OperatingSystem TOOL_CHAIN_DEFAULT = new DefaultOperatingSystem("default", CURRENT_OS);
+
+    private final String name;
+    private final org.gradle.internal.os.OperatingSystem internalOs;
+
+    public DefaultOperatingSystem(String name, org.gradle.internal.os.OperatingSystem internalOs) {
+        this.name = name;
+        this.internalOs = internalOs;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDisplayName() {
+        return String.format("operating system '%s'", name);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public boolean isCurrent() {
+        return internalOs == CURRENT_OS;
+    }
+
+    public boolean isWindows() {
+        return internalOs.isWindows();
+    }
+
+    public boolean isLinux() {
+        return internalOs.isLinux();
+    }
+
+    public boolean isMacOsX() {
+        return internalOs.isMacOsX();
+    }
+
+    public boolean isSolaris() {
+        return internalOs == org.gradle.internal.os.OperatingSystem.SOLARIS;
+    }
+
+    public boolean isFreeBSD() {
+        return internalOs == org.gradle.internal.os.OperatingSystem.FREE_BSD;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatform.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatform.java
new file mode 100644
index 0000000..f8142ac
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatform.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.nativebinaries.platform.Architecture;
+import org.gradle.nativebinaries.platform.OperatingSystem;
+
+public class DefaultPlatform implements PlatformInternal {
+    private final NotationParser<Object, Architecture> archParser;
+    private final NotationParser<Object, OperatingSystem> osParser;
+    private final String name;
+    private Architecture architecture;
+    private OperatingSystem operatingSystem;
+
+    public DefaultPlatform(String name, NotationParser<Object, Architecture> archParser, NotationParser<Object, OperatingSystem> osParser) {
+        this.name = name;
+        this.architecture = ArchitectureInternal.TOOL_CHAIN_DEFAULT;
+        this.operatingSystem = DefaultOperatingSystem.TOOL_CHAIN_DEFAULT;
+        this.archParser = archParser;
+        this.osParser = osParser;
+    }
+
+    public DefaultPlatform(String name) {
+        this(name, ArchitectureNotationParser.parser(), OperatingSystemNotationParser.parser());
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getDisplayName() {
+        return String.format("platform '%s'", name);
+    }
+
+    public Architecture getArchitecture() {
+        return architecture;
+    }
+
+    public void architecture(Object notation) {
+        architecture = archParser.parseNotation(notation);
+    }
+
+    public OperatingSystem getOperatingSystem() {
+        return operatingSystem;
+    }
+
+    public void operatingSystem(Object notation) {
+        operatingSystem = osParser.parseNotation(notation);
+    }
+
+    public String getCompatibilityString() {
+        return String.format("%s:%s", architecture.getName(), operatingSystem.getName());
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatformContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatformContainer.java
new file mode 100644
index 0000000..5b9707e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatformContainer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.nativebinaries.platform.Architecture;
+import org.gradle.nativebinaries.platform.OperatingSystem;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.platform.PlatformContainer;
+
+public class DefaultPlatformContainer extends AbstractNamedDomainObjectContainer<Platform> implements PlatformContainer {
+    private final NotationParser<Object, Architecture> archParser = ArchitectureNotationParser.parser();
+    private final NotationParser<Object, OperatingSystem> osParser = OperatingSystemNotationParser.parser();
+
+    public DefaultPlatformContainer(Instantiator instantiator) {
+        super(Platform.class, instantiator);
+    }
+
+    @Override
+    protected Platform doCreate(String name) {
+        return getInstantiator().newInstance(DefaultPlatform.class, name, archParser, osParser);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/OperatingSystemNotationParser.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/OperatingSystemNotationParser.java
new file mode 100644
index 0000000..6ebf6ad
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/OperatingSystemNotationParser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.TypedNotationParser;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.nativebinaries.platform.OperatingSystem;
+import org.gradle.util.GUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class OperatingSystemNotationParser {
+
+    private static final List<String> WINDOWS_ALIASES = Arrays.asList("windows");
+    private static final List<String> OSX_ALIASES = Arrays.asList("osx", "mac os x");
+    private static final List<String> LINUX_ALIASES = Arrays.asList("linux");
+    private static final List<String> SOLARIS_ALIASES = Arrays.asList("solaris", "sunos");
+    private static final List<String> FREEBSD_ALIASES = Arrays.asList("freebsd");
+
+    public static NotationParser<Object, OperatingSystem> parser() {
+        return new NotationParserBuilder<OperatingSystem>()
+                .resultingType(OperatingSystem.class)
+                .parser(new Parser())
+                .toComposite();
+    }
+
+    private static final class Parser extends TypedNotationParser<String, OperatingSystem> {
+        private Parser() {
+            super(String.class);
+        }
+
+        @Override
+        protected OperatingSystem parseType(String notation) {
+            if (WINDOWS_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultOperatingSystem(notation, org.gradle.internal.os.OperatingSystem.WINDOWS);
+            }
+            if (OSX_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultOperatingSystem(notation, org.gradle.internal.os.OperatingSystem.MAC_OS);
+            }
+            if (LINUX_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultOperatingSystem(notation, org.gradle.internal.os.OperatingSystem.LINUX);
+            }
+            if (SOLARIS_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultOperatingSystem(notation, org.gradle.internal.os.OperatingSystem.SOLARIS);
+            }
+            if (FREEBSD_ALIASES.contains(notation.toLowerCase())) {
+                return new DefaultOperatingSystem(notation, org.gradle.internal.os.OperatingSystem.FREE_BSD);
+            }
+            throw new UnsupportedNotationException(notation);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            List<String> allValues = new ArrayList<String>();
+            allValues.addAll(WINDOWS_ALIASES);
+            allValues.addAll(OSX_ALIASES);
+            allValues.addAll(LINUX_ALIASES);
+            allValues.addAll(SOLARIS_ALIASES);
+            candidateFormats.add("One of the following values: " + GUtil.toString(allValues));
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/PlatformInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/PlatformInternal.java
new file mode 100644
index 0000000..74abf88
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/internal/PlatformInternal.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal;
+
+import org.gradle.nativebinaries.platform.Platform;
+
+public interface PlatformInternal extends Platform {
+    String getCompatibilityString();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/package-info.java
new file mode 100644
index 0000000..c5d0e06
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/platform/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 allow defining a native binary platform.
+ */
+package org.gradle.nativebinaries.platform;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/NativeBinariesModelPlugin.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/NativeBinariesModelPlugin.java
new file mode 100644
index 0000000..ac52682
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/NativeBinariesModelPlugin.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.plugins;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.configuration.project.ProjectConfigurationActionContainer;
+import org.gradle.internal.Actions;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.plugins.LanguageBasePlugin;
+import org.gradle.model.ModelFinalizer;
+import org.gradle.model.ModelRule;
+import org.gradle.model.ModelRules;
+import org.gradle.nativebinaries.BuildTypeContainer;
+import org.gradle.nativebinaries.FlavorContainer;
+import org.gradle.nativebinaries.internal.DefaultBuildTypeContainer;
+import org.gradle.nativebinaries.internal.DefaultExecutableContainer;
+import org.gradle.nativebinaries.internal.DefaultFlavorContainer;
+import org.gradle.nativebinaries.internal.DefaultLibraryContainer;
+import org.gradle.nativebinaries.internal.configure.*;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.PlatformContainer;
+import org.gradle.nativebinaries.platform.internal.DefaultPlatformContainer;
+import org.gradle.nativebinaries.toolchain.internal.DefaultToolChainRegistry;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal;
+
+import javax.inject.Inject;
+import java.util.Arrays;
+
+/**
+ * A plugin that sets up the infrastructure for defining native binaries.
+ */
+ at Incubating
+public class NativeBinariesModelPlugin implements Plugin<ProjectInternal> {
+
+    private final Instantiator instantiator;
+    private final ProjectConfigurationActionContainer configurationActions;
+    private final ModelRules modelRules;
+    private final NativeDependencyResolver resolver;
+    private final FileResolver fileResolver;
+
+    @Inject
+    public NativeBinariesModelPlugin(Instantiator instantiator, ProjectConfigurationActionContainer configurationActions, ModelRules modelRules,
+                                     NativeDependencyResolver resolver, FileResolver fileResolver) {
+        this.instantiator = instantiator;
+        this.configurationActions = configurationActions;
+        this.modelRules = modelRules;
+        this.resolver = resolver;
+        this.fileResolver = fileResolver;
+    }
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(BasePlugin.class);
+        project.getPlugins().apply(LanguageBasePlugin.class);
+
+        modelRules.register("toolChains", ToolChainRegistryInternal.class, factory(DefaultToolChainRegistry.class));
+        modelRules.register("platforms", PlatformContainer.class, factory(DefaultPlatformContainer.class));
+        modelRules.register("buildTypes", BuildTypeContainer.class, factory(DefaultBuildTypeContainer.class));
+        modelRules.register("flavors", FlavorContainer.class, factory(DefaultFlavorContainer.class));
+
+        project.getModelRegistry().create("repositories", Arrays.asList("flavors", "platforms", "buildTypes"), new RepositoriesFactory(instantiator, fileResolver));
+
+        modelRules.rule(new CreateDefaultPlatform());
+        modelRules.rule(new CreateDefaultBuildTypes());
+        modelRules.rule(new CreateDefaultFlavors());
+        modelRules.rule(new AddDefaultToolChainsIfRequired());
+        modelRules.rule(new CreateNativeBinaries(instantiator, project, resolver));
+        modelRules.rule(new CloseBinariesForTasks());
+
+        project.getExtensions().create(
+                "executables",
+                DefaultExecutableContainer.class,
+                instantiator,
+                project
+        );
+        project.getExtensions().create(
+                "libraries",
+                DefaultLibraryContainer.class,
+                instantiator,
+                project
+        );
+
+        // TODO:DAZ Lazy configuration actions: need a better way to accomplish these.
+        configurationActions.add(Actions.composite(
+                new ConfigureGeneratedSourceSets(),
+                new ApplySourceSetConventions()
+        ));
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    private static class CloseBinariesForTasks extends ModelRule {
+        void closeBinariesForTasks(TaskContainer tasks, BinaryContainer binaries) {
+            // nothing needed here
+        }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    private static class AddDefaultToolChainsIfRequired extends ModelFinalizer {
+        void createDefaultToolChain(ToolChainRegistryInternal toolChains) {
+            if (toolChains.isEmpty()) {
+                toolChains.addDefaultToolChains();
+            }
+        }
+    }
+
+    private <T> Factory<T> factory(Class<T> type) {
+        return new InstantiatingFactory<T>(type, instantiator);
+    }
+
+    private static class InstantiatingFactory<T> implements Factory<T> {
+        private final Class<T> type;
+        private final Instantiator instantiator;
+
+        public InstantiatingFactory(Class<T> type, Instantiator instantiator) {
+            this.type = type;
+            this.instantiator = instantiator;
+        }
+
+        public T create() {
+            return instantiator.newInstance(type, instantiator);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPlugin.groovy
new file mode 100644
index 0000000..cdbcfce
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPlugin.groovy
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.language.base.BinaryContainer
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.tasks.CreateStaticLibrary
+import org.gradle.nativebinaries.tasks.InstallExecutable
+import org.gradle.nativebinaries.tasks.LinkExecutable
+import org.gradle.nativebinaries.tasks.LinkSharedLibrary
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+/**
+ * A plugin that creates tasks used for constructing native binaries.
+ */
+ at Incubating
+public class NativeBinariesPlugin implements Plugin<ProjectInternal> {
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(NativeBinariesModelPlugin.class);
+
+        // Create a functionalSourceSet for each native component, with the same name
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        project.getExtensions().getByType(ExecutableContainer).all { Executable exe ->
+            exe.source projectSourceSet.maybeCreate(exe.name)
+        }
+        project.getExtensions().getByType(LibraryContainer).all { Library lib ->
+            lib.source projectSourceSet.maybeCreate(lib.name)
+        }
+
+        final BinaryContainer binaries = project.getExtensions().getByType(BinaryContainer.class);
+        binaries.withType(ProjectNativeBinary) { ProjectNativeBinaryInternal binary ->
+            binary.conventionMapping.buildable = { isBuildableBinary(binary) }
+            createTasks(project, binary)
+        }
+    }
+
+    static boolean isBuildableBinary(ProjectNativeBinaryInternal binary) {
+        final chain = binary.toolChain as ToolChainInternal
+        chain.target(binary.getTargetPlatform()).available
+    }
+
+    def createTasks(ProjectInternal project, ProjectNativeBinaryInternal binary) {
+        def builderTask
+        if (binary instanceof ExecutableBinary) {
+            builderTask = createLinkExecutableTask(project, binary as ExecutableBinary)
+            binary.tasks.add createInstallTask(project, binary as ExecutableBinary);
+        } else if (binary instanceof SharedLibraryBinary) {
+            builderTask = createLinkSharedLibraryTask(project, binary)
+        } else if (binary instanceof StaticLibraryBinary) {
+            builderTask = createStaticLibraryTask(project, binary)
+        } else {
+            throw new RuntimeException("Not a valid binary type for building: " + binary)
+        }
+        binary.tasks.add builderTask
+        binary.builtBy builderTask
+    }
+
+    private LinkExecutable createLinkExecutableTask(ProjectInternal project, ExecutableBinary executable) {
+        def binary = executable as ProjectNativeBinaryInternal
+        LinkExecutable linkTask = project.task(binary.namingScheme.getTaskName("link"), type: LinkExecutable) {
+             description = "Links ${executable}"
+         }
+
+        linkTask.toolChain = binary.toolChain
+        linkTask.targetPlatform = executable.targetPlatform
+
+        linkTask.lib { binary.libs*.linkFiles }
+
+        linkTask.conventionMapping.outputFile = { executable.executableFile }
+        linkTask.linkerArgs = binary.linker.args
+        return linkTask
+    }
+
+    private LinkSharedLibrary createLinkSharedLibraryTask(ProjectInternal project, SharedLibraryBinary sharedLibrary) {
+        def binary = sharedLibrary as ProjectNativeBinaryInternal
+        LinkSharedLibrary linkTask = project.task(binary.namingScheme.getTaskName("link"), type: LinkSharedLibrary) {
+             description = "Links ${sharedLibrary}"
+         }
+
+        linkTask.toolChain = binary.toolChain
+        linkTask.targetPlatform = binary.targetPlatform
+
+        linkTask.lib { binary.libs*.linkFiles }
+
+        linkTask.conventionMapping.outputFile = { sharedLibrary.sharedLibraryFile }
+        linkTask.conventionMapping.installName = { sharedLibrary.sharedLibraryFile.name }
+        linkTask.linkerArgs = binary.linker.args
+        return linkTask
+    }
+
+    private CreateStaticLibrary createStaticLibraryTask(ProjectInternal project, StaticLibraryBinary staticLibrary) {
+        def binary = staticLibrary as ProjectNativeBinaryInternal
+        CreateStaticLibrary task = project.task(binary.namingScheme.getTaskName("create"), type: CreateStaticLibrary) {
+             description = "Creates ${staticLibrary}"
+         }
+
+        task.toolChain = binary.toolChain
+        task.targetPlatform = staticLibrary.targetPlatform
+        task.conventionMapping.outputFile = { staticLibrary.staticLibraryFile }
+        task.staticLibArgs = binary.staticLibArchiver.args
+        return task
+    }
+
+    def createInstallTask(ProjectInternal project, ExecutableBinary executable) {
+        def binary = executable as ProjectNativeBinaryInternal
+        InstallExecutable installTask = project.task(binary.namingScheme.getTaskName("install"), type: InstallExecutable) {
+            description = "Installs a development image of $executable"
+            group = BasePlugin.BUILD_GROUP
+        }
+
+        installTask.toolChain = binary.toolChain
+        installTask.conventionMapping.destinationDir = { project.file("${project.buildDir}/install/${binary.namingScheme.outputDirectoryBase}") }
+
+        installTask.conventionMapping.executable = { executable.executableFile }
+        installTask.lib { binary.libs*.runtimeFiles }
+
+        installTask.dependsOn(executable)
+        return installTask
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/package-info.java
new file mode 100644
index 0000000..161301d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 building native component projects.
+ */
+package org.gradle.nativebinaries.plugins;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/AbstractLinkTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/AbstractLinkTask.groovy
new file mode 100644
index 0000000..d9a4064
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/AbstractLinkTask.groovy
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.*
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.internal.LinkerSpec
+
+import javax.inject.Inject
+
+ at Incubating
+abstract class AbstractLinkTask extends DefaultTask implements BuildBinaryTask {
+    @Inject
+    AbstractLinkTask() {
+        libs = project.files()
+        source = project.files()
+    }
+
+    /**
+     * The tool chain used for linking.
+     */
+    ToolChain toolChain
+    Platform targetPlatform
+
+    // Invalidate output when the tool chain output changes
+    @Input
+    def getOutputType() {
+        return "${toolChain.outputType}:${targetPlatform.compatibilityString}"
+    }
+
+    // To pick up auxiliary files produced alongside the main output file
+    @OutputDirectory
+    File getDestinationDir() {
+        return getOutputFile().parentFile
+    }
+
+    /**
+     * The file where the linked binary will be located.
+     */
+    @OutputFile
+    File outputFile
+
+    /**
+     * Additional arguments passed to the linker.
+     */
+    @Input
+    List<String> linkerArgs
+
+    /**
+     * The source object files to be passed to the linker.
+     */
+    @InputFiles
+    FileCollection source
+
+    /**
+     * The library files to be passed to the linker.
+     */
+    @InputFiles
+    FileCollection libs
+
+    /**
+     * Adds a set of object files to be linked.
+     * The provided source object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void source(Object source) {
+        this.source.from source
+    }
+
+    /**
+     * Adds a set of library files to be linked.
+     * The provided libs object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void lib(Object libs) {
+        this.libs.from libs
+    }
+
+    @TaskAction
+    void link() {
+        def cleaner = new SimpleStaleClassCleaner(getOutputs())
+        cleaner.setDestinationDir(getDestinationDir())
+        cleaner.execute()
+
+        if (source.empty) {
+            didWork = false
+            return
+        }
+
+        def spec = createLinkerSpec()
+        spec.tempDir = getTemporaryDir()
+        spec.outputFile = getOutputFile()
+
+        spec.objectFiles getSource()
+        spec.libraries getLibs()
+        spec.args getLinkerArgs()
+
+        def result = toolChain.target(targetPlatform).createLinker().execute(spec)
+        didWork = result.didWork
+    }
+
+    protected abstract LinkerSpec createLinkerSpec();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/BuildBinaryTask.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/BuildBinaryTask.java
new file mode 100644
index 0000000..c64a633
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/BuildBinaryTask.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.tasks;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A task that combines a set of object files into a single binary.
+ */
+ at Incubating
+public interface BuildBinaryTask {
+    /**
+     * Adds a set of object files to be combined into the file binary.
+     * The provided source object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void source(Object source);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/CreateStaticLibrary.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/CreateStaticLibrary.groovy
new file mode 100644
index 0000000..81b3784
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/CreateStaticLibrary.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.tasks
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.*
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.internal.DefaultStaticLibraryArchiverSpec
+
+import javax.inject.Inject
+/**
+ * Assembles a static library from object files.
+ */
+ at Incubating
+class CreateStaticLibrary extends DefaultTask implements BuildBinaryTask {
+    private FileCollection source
+
+    @Inject
+    CreateStaticLibrary() {
+        source = project.files()
+    }
+
+    /**
+     * The tool chain used for creating the static library.
+     */
+    ToolChain toolChain
+
+    /**
+     * The platform being targeted.
+     */
+    Platform targetPlatform
+
+    // Invalidate output when the tool chain output changes
+    @Input
+    def getOutputType() {
+        return "${toolChain.outputType}:${targetPlatform.compatibilityString}"
+    }
+
+    /**
+     * The file where the output binary will be located.
+     */
+    @OutputFile
+    File outputFile
+
+    /**
+     * The source object files to be passed to the archiver.
+     */
+    @InputFiles @SkipWhenEmpty // Can't use field due to GRADLE-2026
+    FileCollection getSource() {
+        source
+    }
+
+    /**
+     * Adds a set of object files to be linked.
+     * <p>
+     * The provided source object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void source(Object source) {
+        this.source.from source
+    }
+
+    /**
+     * Additional arguments passed to the archiver.
+     */
+    @Input
+    List<String> staticLibArgs
+
+    @TaskAction
+    void link() {
+        def spec = new DefaultStaticLibraryArchiverSpec()
+        spec.tempDir = getTemporaryDir()
+        spec.outputFile = getOutputFile()
+        spec.objectFiles getSource()
+        spec.args getStaticLibArgs()
+
+        def result = toolChain.target(targetPlatform).createStaticLibraryArchiver().execute(spec)
+        didWork = result.didWork
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/InstallExecutable.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/InstallExecutable.groovy
new file mode 100644
index 0000000..7efdb91
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/InstallExecutable.groovy
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.tasks
+import org.gradle.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.CopySpec
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.FileOperations
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.Gcc
+
+import javax.inject.Inject
+/**
+ * Installs an executable with it's dependent libraries so it can be easily executed.
+ */
+ at Incubating
+public class InstallExecutable extends DefaultTask {
+
+    private final Instantiator instantiator
+    private final FileOperations fileOperations
+
+    @Inject
+    InstallExecutable(Instantiator instantiator, FileOperations fileOperations) {
+        this.instantiator = instantiator
+        this.fileOperations = fileOperations
+        this.libs = project.files()
+    }
+
+    /**
+     * The tool chain used for linking.
+     */
+    ToolChain toolChain
+
+    /**
+     * The directory to install files into.
+     */
+    @OutputDirectory
+    File destinationDir
+
+    /**
+     * The executable file to install.
+     */
+    @InputFile
+    File executable
+
+    /**
+     * The library files that should be installed.
+     */
+    @InputFiles
+    FileCollection libs
+
+    /**
+     * Adds a set of library files to be installed.
+     * The provided libs object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
+     */
+    void lib(Object libs) {
+        this.libs.from libs
+    }
+
+    /**
+     * Returns the script file that can be used to run the install image.
+     */
+    File getRunScript() {
+        new File(getDestinationDir(), os.getScriptName(getExecutable().name))
+    }
+
+    // TODO:DAZ Allow this to be configured
+    private OperatingSystem os = OperatingSystem.current()
+
+    @TaskAction
+    void install() {
+        if (os.windows) {
+            installWindows()
+        } else {
+            installUnix()
+        }
+    }
+
+    private void installWindows() {
+        final destination = getDestinationDir()
+        final File executable = getExecutable()
+
+        installToDir(new File(destination, "lib"))
+
+        StringBuilder toolChainPath = new StringBuilder()
+        if (toolChain in Gcc) {
+            // Gcc on windows requires the path to be set
+            toolChainPath.append("SET PATH=")
+            for (File pathEntry : ((Gcc) toolChain).path) {
+                toolChainPath.append(pathEntry.absolutePath).append(";")
+            }
+            toolChainPath.append("%PATH%")
+        }
+
+        runScript.text = """
+ at echo off
+SETLOCAL
+$toolChainPath
+CALL "%~dp0lib\\${executable.name}"
+EXIT /B %ERRORLEVEL%
+
+ENDLOCAL
+"""
+    }
+
+    private void installUnix() {
+        final destination = getDestinationDir()
+        final executable = getExecutable()
+
+        installToDir(new File(destination, "lib"))
+
+        runScript.text = """
+#/bin/sh
+APP_BASE_NAME=`dirname "\$0"`
+export DYLD_LIBRARY_PATH="\$APP_BASE_NAME/lib"
+export LD_LIBRARY_PATH="\$APP_BASE_NAME/lib"
+exec "\$APP_BASE_NAME/lib/${executable.name}" \"\$@\"
+"""
+
+        FileSystem fileSystem = getServices().get(FileSystem.class);
+        fileSystem.chmod(runScript, 0755)
+    }
+
+    private void installToDir(File binaryDir) {
+        fileOperations.sync(new Action<CopySpec>() {
+            void execute(CopySpec copySpec) {
+                copySpec.into(binaryDir)
+                copySpec.from(getExecutable())
+                copySpec.from(getLibs())
+            }
+        })
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/LinkExecutable.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/LinkExecutable.groovy
new file mode 100644
index 0000000..bbe156e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/LinkExecutable.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.tasks
+import org.gradle.api.Incubating
+import org.gradle.nativebinaries.internal.DefaultLinkerSpec
+import org.gradle.nativebinaries.internal.LinkerSpec
+/**
+ * Links a binary executable from object files and libraries.
+ */
+ at Incubating
+class LinkExecutable extends AbstractLinkTask {
+
+    @Override
+    protected LinkerSpec createLinkerSpec() {
+        return new DefaultLinkerSpec()
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/LinkSharedLibrary.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/LinkSharedLibrary.groovy
new file mode 100644
index 0000000..219427d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/LinkSharedLibrary.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.tasks
+import org.gradle.api.Incubating
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+import org.gradle.nativebinaries.internal.DefaultLinkerSpec
+import org.gradle.nativebinaries.internal.LinkerSpec
+import org.gradle.nativebinaries.internal.SharedLibraryLinkerSpec
+
+/**
+ * Links a binary shared library from object files and imported libraries.
+ */
+ at Incubating
+class LinkSharedLibrary extends AbstractLinkTask {
+    @Input @Optional
+    String installName;
+
+    @Override
+    protected LinkerSpec createLinkerSpec() {
+        final spec = new Spec()
+        spec.installName = getInstallName()
+        return spec
+    }
+
+    private static class Spec extends DefaultLinkerSpec implements SharedLibraryLinkerSpec {
+        String installName;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/package-info.java
new file mode 100644
index 0000000..ec6daa2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tasks for building native component projects.
+ */
+package org.gradle.nativebinaries.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/ProjectComponentTestSuite.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/ProjectComponentTestSuite.java
new file mode 100644
index 0000000..b280fc5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/ProjectComponentTestSuite.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test;
+
+import org.gradle.api.Incubating;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+
+/**
+ * A suite of tests for a project component.
+ */
+ at Incubating
+public interface ProjectComponentTestSuite extends TestSuite {
+
+    /**
+     * The tested component.
+     */
+    ProjectNativeComponent getTestedComponent();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuite.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuite.java
new file mode 100644
index 0000000..687a584
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuite.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test;
+
+import org.gradle.api.Incubating;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+
+/**
+ * A component representing a suite of tests that will be executed together.
+ */
+ at Incubating
+public interface TestSuite extends ProjectNativeComponent {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuiteContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuiteContainer.java
new file mode 100644
index 0000000..d950f5d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuiteContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.test;
+
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
+import org.gradle.api.Incubating;
+
+/**
+ * A polymorphic container of {@link TestSuite} instances.
+ */
+ at Incubating
+public interface TestSuiteContainer extends ExtensiblePolymorphicDomainObjectContainer<TestSuite> {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuiteExecutableBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuiteExecutableBinary.java
new file mode 100644
index 0000000..fbcdd49
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/TestSuiteExecutableBinary.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test;
+
+import org.gradle.api.Incubating;
+import org.gradle.nativebinaries.ExecutableBinary;
+
+/**
+ * An executable which runs a suite of tests.
+ */
+ at Incubating
+public interface TestSuiteExecutableBinary extends ExecutableBinary {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/CUnitTestSuite.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/CUnitTestSuite.java
new file mode 100644
index 0000000..bd746a5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/CUnitTestSuite.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit;
+
+import org.gradle.api.Incubating;
+import org.gradle.nativebinaries.test.ProjectComponentTestSuite;
+
+/**
+ * Test suite of CUnit tests.
+ */
+ at Incubating
+public interface CUnitTestSuite extends ProjectComponentTestSuite {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/ConfigureCUnitTestSources.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/ConfigureCUnitTestSources.java
new file mode 100644
index 0000000..c47c38c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/ConfigureCUnitTestSources.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit.internal;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.language.c.CSourceSet;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+import org.gradle.nativebinaries.test.cunit.CUnitTestSuite;
+import org.gradle.nativebinaries.test.cunit.tasks.GenerateCUnitLauncher;
+
+import java.io.File;
+
+public class ConfigureCUnitTestSources {
+    private final ProjectInternal project;
+
+    public ConfigureCUnitTestSources(ProjectInternal project) {
+        this.project = project;
+    }
+
+    public void apply(CUnitTestSuite cUnitTestSuite) {
+        FunctionalSourceSet suiteSourceSet = createSuiteSources(cUnitTestSuite);
+
+        CSourceSet launcherSources = suiteSourceSet.maybeCreate("cunitLauncher", CSourceSet.class);
+        cUnitTestSuite.source(launcherSources);
+        createCUnitLauncherTask(cUnitTestSuite, launcherSources);
+
+        CSourceSet testSources = suiteSourceSet.maybeCreate("cunit", CSourceSet.class);
+        cUnitTestSuite.source(testSources);
+        testSources.lib(launcherSources);
+
+        ProjectNativeComponent testedComponent = cUnitTestSuite.getTestedComponent();
+        cUnitTestSuite.source(testedComponent.getSource());
+
+        testSources.lib(testedComponent.getSource().withType(CSourceSet.class));
+    }
+
+    private FunctionalSourceSet createSuiteSources(CUnitTestSuite cUnitTestSuite) {
+        ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+        return projectSourceSet.maybeCreate(cUnitTestSuite.getName());
+    }
+
+    private void createCUnitLauncherTask(CUnitTestSuite suite, CSourceSet cunitSourceSet) {
+        String taskName = suite.getName() + "CUnitLauncher";
+        GenerateCUnitLauncher skeletonTask = project.getTasks().create(taskName, GenerateCUnitLauncher.class);
+
+        File baseDir = new File(project.getBuildDir(), "src/" + taskName);
+        skeletonTask.setSourceDir(new File(baseDir, "cunit"));
+        skeletonTask.setHeaderDir(new File(baseDir, "headers"));
+        cunitSourceSet.generatedBy(skeletonTask);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/CreateCUnitBinaries.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/CreateCUnitBinaries.java
new file mode 100644
index 0000000..c9374c6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/CreateCUnitBinaries.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.language.base.internal.DefaultBinaryNamingSchemeBuilder;
+import org.gradle.nativebinaries.ProjectNativeBinary;
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.language.internal.DefaultPreprocessingTool;
+import org.gradle.nativebinaries.test.TestSuiteExecutableBinary;
+import org.gradle.nativebinaries.test.cunit.CUnitTestSuite;
+import org.gradle.nativebinaries.test.internal.DefaultTestSuiteExecutableBinary;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+
+public class CreateCUnitBinaries {
+    private final ProjectInternal project;
+    private final Instantiator instantiator;
+    private final NativeDependencyResolver resolver;
+
+    public CreateCUnitBinaries(ProjectInternal project, Instantiator instantiator, NativeDependencyResolver resolver) {
+        this.project = project;
+        this.instantiator = instantiator;
+        this.resolver = resolver;
+    }
+
+    public void apply(final CUnitTestSuite cUnitTestSuite, final BinaryContainer binaries) {
+        cUnitTestSuite.getTestedComponent().getBinaries().withType(ProjectNativeBinaryInternal.class).all(new Action<ProjectNativeBinaryInternal>() {
+            public void execute(ProjectNativeBinaryInternal testedBinary) {
+                final ProjectNativeBinary cunitExe = createTestBinary(cUnitTestSuite, testedBinary, project);
+                ((ExtensionAware) cunitExe).getExtensions().create("cCompiler", DefaultPreprocessingTool.class);
+
+                cUnitTestSuite.getBinaries().add(cunitExe);
+                binaries.add(cunitExe);
+
+                testedBinary.getSource().all(new Action<LanguageSourceSet>() {
+                    public void execute(LanguageSourceSet languageSourceSet) {
+                        cunitExe.source(languageSourceSet);
+                    }
+                });
+            }
+        });
+    }
+
+    public ProjectNativeBinary createTestBinary(CUnitTestSuite cUnitTestSuite, ProjectNativeBinaryInternal testedBinary, ProjectInternal project) {
+        BinaryNamingScheme namingScheme = new DefaultBinaryNamingSchemeBuilder(testedBinary.getNamingScheme())
+                .withComponentName(cUnitTestSuite.getBaseName())
+                .withTypeString("CUnitExe").build();
+
+        ProjectNativeBinary testBinary = instantiator.newInstance(DefaultTestSuiteExecutableBinary.class,
+                cUnitTestSuite, testedBinary.getFlavor(), testedBinary.getToolChain(),
+                testedBinary.getTargetPlatform(), testedBinary.getBuildType(), namingScheme, resolver);
+
+        setupDefaults(testBinary, project);
+        return testBinary;
+    }
+
+    private void setupDefaults(ProjectNativeBinary nativeBinary, ProjectInternal project) {
+        BinaryNamingScheme namingScheme = ((ProjectNativeBinaryInternal) nativeBinary).getNamingScheme();
+        File binaryOutputDir = new File(new File(project.getBuildDir(), "binaries"), namingScheme.getOutputDirectoryBase());
+        String baseName = nativeBinary.getComponent().getBaseName();
+
+        ToolChainInternal tc = (ToolChainInternal) nativeBinary.getToolChain();
+        ((TestSuiteExecutableBinary) nativeBinary).setExecutableFile(new File(binaryOutputDir, tc.getExecutableName(baseName)));
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/DefaultCUnitTestSuite.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/DefaultCUnitTestSuite.java
new file mode 100644
index 0000000..5b6d34d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/internal/DefaultCUnitTestSuite.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit.internal;
+
+import org.gradle.nativebinaries.ProjectNativeComponent;
+import org.gradle.nativebinaries.internal.AbstractProjectNativeComponent;
+import org.gradle.nativebinaries.internal.NativeProjectComponentIdentifier;
+import org.gradle.nativebinaries.test.ProjectComponentTestSuite;
+import org.gradle.nativebinaries.test.cunit.CUnitTestSuite;
+
+public class DefaultCUnitTestSuite extends AbstractProjectNativeComponent implements CUnitTestSuite, ProjectComponentTestSuite {
+    private final ProjectNativeComponent testedComponent;
+
+    public DefaultCUnitTestSuite(NativeProjectComponentIdentifier id, ProjectNativeComponent testedComponent) {
+        super(id);
+        this.testedComponent = testedComponent;
+    }
+
+    public String getDisplayName() {
+        return String.format("cunit tests '%s'", getName());
+    }
+
+    public ProjectNativeComponent getTestedComponent() {
+        return testedComponent;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/package-info.java
new file mode 100644
index 0000000..eb425bc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * API classes for cunit integration.
+ */
+package org.gradle.nativebinaries.test.cunit;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/plugins/CUnitPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/plugins/CUnitPlugin.groovy
new file mode 100644
index 0000000..4044e3f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/plugins/CUnitPlugin.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.BinaryContainer
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.NativeProjectComponentIdentifier
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.test.TestSuiteContainer
+import org.gradle.nativebinaries.test.cunit.CUnitTestSuite
+import org.gradle.nativebinaries.test.cunit.internal.ConfigureCUnitTestSources
+import org.gradle.nativebinaries.test.cunit.internal.CreateCUnitBinaries
+import org.gradle.nativebinaries.test.cunit.internal.DefaultCUnitTestSuite
+import org.gradle.nativebinaries.test.plugins.NativeBinariesTestPlugin
+
+import javax.inject.Inject
+/**
+ * A plugin that sets up the infrastructure for testing native binaries with CUnit.
+ */
+ at Incubating
+public class CUnitPlugin implements Plugin<ProjectInternal> {
+
+    private final Instantiator instantiator
+    private final NativeDependencyResolver resolver;
+
+    @Inject
+    public CUnitPlugin(Instantiator instantiator, NativeDependencyResolver resolver) {
+        this.instantiator = instantiator;
+        this.resolver = resolver;
+    }
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(NativeBinariesTestPlugin.class)
+
+        TestSuiteContainer testSuites = project.getExtensions().getByType(TestSuiteContainer)
+        BinaryContainer binaries = project.getExtensions().getByType(BinaryContainer)
+        project.getExtensions().getByType(ExecutableContainer).all { Executable executable ->
+            testSuites.add createCUnitTestSuite(executable, binaries, project)
+        }
+        project.getExtensions().getByType(LibraryContainer).all { Library library ->
+            testSuites.add createCUnitTestSuite(library, binaries, project)
+        }
+    }
+
+    private CUnitTestSuite createCUnitTestSuite(ProjectNativeComponent testedComponent, BinaryContainer binaries, ProjectInternal project) {
+        String suiteName = "${testedComponent.name}Test"
+        String path = (testedComponent as ProjectNativeComponentInternal).projectPath
+        NativeProjectComponentIdentifier id = new NativeProjectComponentIdentifier(path, suiteName);
+        CUnitTestSuite cUnitTestSuite = instantiator.newInstance(DefaultCUnitTestSuite, id, testedComponent);
+
+        new ConfigureCUnitTestSources(project).apply(cUnitTestSuite)
+        new CreateCUnitBinaries(project, instantiator, resolver).apply(cUnitTestSuite, binaries);
+        return cUnitTestSuite;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/plugins/package-info.java
new file mode 100644
index 0000000..1b9661b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 cunit testing.
+ */
+package org.gradle.nativebinaries.test.cunit.plugins;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/tasks/GenerateCUnitLauncher.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/tasks/GenerateCUnitLauncher.groovy
new file mode 100644
index 0000000..4820825
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/tasks/GenerateCUnitLauncher.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Generated the Gradle CUnit launcher: main method and header.
+ */
+class GenerateCUnitLauncher extends DefaultTask {
+    @OutputDirectory File sourceDir
+    @OutputDirectory File headerDir
+
+    @TaskAction
+    void generate() {
+        writeToFile(sourceDir, "gradle_cunit_main.c")
+        writeToFile(headerDir, "gradle_cunit_register.h")
+    }
+
+    private void writeToFile(File directory, String fileName) {
+        final File file = new File(directory, fileName)
+        file.text = ''
+        file << getClass().getResourceAsStream(fileName)
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/tasks/package-info.java
new file mode 100644
index 0000000..92abed7
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/cunit/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 cunit integration.
+ */
+package org.gradle.nativebinaries.test.cunit.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/internal/DefaultTestSuiteContainer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/internal/DefaultTestSuiteContainer.java
new file mode 100644
index 0000000..dcf907d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/internal/DefaultTestSuiteContainer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.test.internal;
+
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.test.TestSuite;
+import org.gradle.nativebinaries.test.TestSuiteContainer;
+
+// TODO:DAZ Add a 'components' container (polymorphic), and then a writable container filtered by type that looks like a non-polymorphic container
+// Then 'executables', 'libraries' and 'testSuites' would all be filtered containers, not separate.
+public class DefaultTestSuiteContainer extends DefaultPolymorphicDomainObjectContainer<TestSuite> implements TestSuiteContainer {
+    public DefaultTestSuiteContainer(Instantiator instantiator) {
+        super(TestSuite.class, instantiator);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/internal/DefaultTestSuiteExecutableBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/internal/DefaultTestSuiteExecutableBinary.java
new file mode 100644
index 0000000..1b0ba06
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/internal/DefaultTestSuiteExecutableBinary.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.internal;
+
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.nativebinaries.BuildType;
+import org.gradle.nativebinaries.Flavor;
+import org.gradle.nativebinaries.ProjectNativeComponent;
+import org.gradle.nativebinaries.test.TestSuiteExecutableBinary;
+import org.gradle.nativebinaries.internal.AbstractProjectNativeBinary;
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal;
+
+import java.io.File;
+
+public class DefaultTestSuiteExecutableBinary extends AbstractProjectNativeBinary implements TestSuiteExecutableBinary {
+    private File executableFile;
+
+    public DefaultTestSuiteExecutableBinary(ProjectNativeComponent owner, Flavor flavor, ToolChainInternal toolChain, Platform targetPlatform, BuildType buildType, BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+        super(owner, flavor, toolChain, targetPlatform, buildType, namingScheme, resolver);
+    }
+
+    public File getExecutableFile() {
+        return executableFile;
+    }
+
+    public void setExecutableFile(File executableFile) {
+        this.executableFile = executableFile;
+    }
+
+    public File getPrimaryOutput() {
+        return getExecutableFile();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/package-info.java
new file mode 100644
index 0000000..fa0a484
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * API classes for testing native binaries.
+ */
+package org.gradle.nativebinaries.test;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/plugins/NativeBinariesTestPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/plugins/NativeBinariesTestPlugin.groovy
new file mode 100644
index 0000000..fbacfc4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/plugins/NativeBinariesTestPlugin.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.BinaryContainer
+import org.gradle.model.ModelRules
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+import org.gradle.nativebinaries.tasks.InstallExecutable
+import org.gradle.nativebinaries.test.TestSuiteExecutableBinary
+import org.gradle.nativebinaries.test.internal.DefaultTestSuiteContainer
+import org.gradle.nativebinaries.test.tasks.RunTestExecutable
+
+import javax.inject.Inject
+
+/**
+ * A plugin that sets up the infrastructure for testing native binaries with CUnit.
+ */
+ at Incubating
+public class NativeBinariesTestPlugin implements Plugin<ProjectInternal> {
+
+    private final Instantiator instantiator
+    private final ModelRules modelRules
+    private final NativeDependencyResolver resolver
+
+    @Inject
+    public NativeBinariesTestPlugin(Instantiator instantiator, ModelRules modelRules, NativeDependencyResolver resolver) {
+        this.instantiator = instantiator
+        this.modelRules = modelRules
+        this.resolver = resolver
+    }
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(NativeBinariesPlugin.class)
+
+        project.getExtensions().create(
+                "testSuites",
+                DefaultTestSuiteContainer.class,
+                instantiator
+        );
+
+        final BinaryContainer binaries = project.getExtensions().getByType(BinaryContainer.class);
+        binaries.withType(TestSuiteExecutableBinary).all { testBinary ->
+            def binary = testBinary as ProjectNativeBinaryInternal
+            def namingScheme = binary.namingScheme
+
+            // TODO:DAZ Need a better model for accessing tasks related to binary
+            def installTask = binary.tasks.withType(InstallExecutable).find()
+
+            def runTask = project.tasks.create(namingScheme.getTaskName("run"), RunTestExecutable)
+            runTask.setDescription("Runs the " + binary.getDisplayName())
+            runTask.inputs.files(installTask.outputs.files)
+
+            runTask.conventionMapping.testExecutable = { installTask.runScript }
+            runTask.conventionMapping.outputDir = { project.file("${project.buildDir}/test-results/${namingScheme.outputDirectoryBase}") }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/plugins/package-info.java
new file mode 100644
index 0000000..34b66d1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Plugin classes for generic support for testing native binaries.
+ */
+package org.gradle.nativebinaries.test.plugins;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/tasks/RunTestExecutable.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/tasks/RunTestExecutable.groovy
new file mode 100644
index 0000000..698a55f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/tasks/RunTestExecutable.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.test.tasks
+import org.gradle.api.GradleException
+import org.gradle.api.Incubating
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.logging.ConsoleRenderer
+import org.gradle.process.ExecResult
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.ExecActionFactory
+/**
+ * Runs a compiled and installed test executable.
+ */
+ at Incubating
+public class RunTestExecutable extends ConventionTask {
+    /**
+     * The executable binary to run.
+     */
+    @InputFile File testExecutable
+
+    /**
+     * The directory where the results should be generated.
+     */
+    @OutputDirectory File outputDir
+
+    /**
+     * Should the build continue if a test fails, or should the build break?
+     */
+    @Input boolean ignoreFailures
+
+    private ExecAction execAction;
+    private ExecResult execResult;
+
+    public RunTestExecutable() {
+        execAction = getServices().get(ExecActionFactory.class).newExecAction();
+    }
+
+    @TaskAction
+    void exec() {
+        execAction.setExecutable(getTestExecutable())
+        execAction.setWorkingDir(getOutputDir())
+        try {
+            execResult = execAction.execute();
+        } catch (Exception e) {
+            handleTestFailures(e);
+        }
+    }
+
+    private void handleTestFailures(Exception e) {
+        String message = "There were failing tests";
+        String resultsUrl = new ConsoleRenderer().asClickableFileUrl(getOutputDir());
+        message = message.concat(". See the results at: " + resultsUrl);
+
+        if (isIgnoreFailures()) {
+            getLogger().warn(message);
+        } else {
+            throw new GradleException(message, e);
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/tasks/package-info.java
new file mode 100644
index 0000000..2601087
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/test/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 test execution.
+ */
+package org.gradle.nativebinaries.test.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/Clang.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/Clang.java
new file mode 100644
index 0000000..0601249
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/Clang.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * The <a href="http://clang.llvm.org">Clang</a> tool chain.
+ */
+ at Incubating
+public interface Clang extends PlatformConfigurableToolChain {
+    /**
+     * The paths setting required for executing the tool chain.
+     * These are used to locate tools for this tool chain, and are prepended to the system PATH when executing these tools.
+     */
+    List<File> getPath();
+
+    /**
+     * Append an entry or entries to the tool chain path.
+     *
+     * @param pathEntries The path values to append. These are evaluated as per {@link org.gradle.api.Project#files(Object...)}
+     */
+    void path(Object... pathEntries);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/Gcc.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/Gcc.java
new file mode 100644
index 0000000..1c65bf9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/Gcc.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * The <a href="http://gcc.gnu.org/">GNU GCC</a> tool chain.
+ */
+ at Incubating
+public interface Gcc extends PlatformConfigurableToolChain {
+    /**
+     * The path required for executing the tool chain.
+     * These are used to locate tools for this tool chain, and are prepended to the system PATH when executing these tools.
+     */
+    List<File> getPath();
+
+    /**
+     * Append an entry or entries to the tool chain path.
+     *
+     * @param pathEntries The path values to append. These are evaluated as per {@link org.gradle.api.Project#files(Object...)}
+     */
+    void path(Object... pathEntries);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/GccTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/GccTool.java
new file mode 100644
index 0000000..be4642f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/GccTool.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain;
+
+import groovy.lang.Closure;
+import org.gradle.api.Incubating;
+
+/**
+ * An executable tool that forms part of a tool chain.
+ */
+ at Incubating
+public interface GccTool {
+    /**
+     * The name of the executable file for this tool.
+     */
+    String getExecutable();
+
+    /**
+     * Set the name of the executable file for this tool.
+     * The executable will be located in the tool chain path.
+     */
+    void setExecutable(String file);
+
+    /**
+     * Adds an action that will be applied to the command-line arguments prior to execution.
+     */
+    void withArguments(Closure arguments);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/PlatformConfigurableToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/PlatformConfigurableToolChain.java
new file mode 100644
index 0000000..a74c869
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/PlatformConfigurableToolChain.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A ToolChain that can handle additional platforms simply by configuring the NativeBinary.
+ */
+ at Incubating
+public interface PlatformConfigurableToolChain extends ToolChain {
+
+    /**
+     * Add configuration for a target platform.
+     */
+    void addPlatformConfiguration(TargetPlatformConfiguration platformConfig);
+
+    /**
+     * The C++ compiler.
+     */
+    GccTool getCCompiler();
+
+    /**
+     * The C compiler.
+     */
+    GccTool getCppCompiler();
+
+    /**
+     * The assembler.
+     */
+    GccTool getAssembler();
+
+    /**
+     * The linker.
+     */
+    GccTool getLinker();
+
+    /**
+     * The static library archiver.
+     */
+    GccTool getStaticLibArchiver();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/TargetPlatformConfiguration.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/TargetPlatformConfiguration.java
new file mode 100644
index 0000000..eebc167
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/TargetPlatformConfiguration.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.Incubating;
+import org.gradle.nativebinaries.platform.Platform;
+
+import java.util.List;
+
+/**
+ * Configuration to add support for a target platform to a {@link PlatformConfigurableToolChain}.
+ */
+ at Incubating
+public interface TargetPlatformConfiguration {
+    /**
+     * Matches the platform that this configuration supports.
+     */
+    boolean supportsPlatform(Platform targetPlatform);
+
+    /**
+     * The additional args supplied to the C++ compiler to target the platform.
+     */
+    List<String> getCppCompilerArgs();
+
+    /**
+     * The additional args supplied to the C compiler to target the platform.
+     */
+    List<String> getCCompilerArgs();
+
+    /**
+     * The additional args supplied to the Objective-C++ compiler to target the platform.
+     */
+    List<String> getObjectiveCppCompilerArgs();
+
+    /**
+     * The additional args supplied to the Objective-C compiler to target the platform.
+     */
+    List<String> getObjectiveCCompilerArgs();
+
+    /**
+     * The additional args supplied to the Assembler to target the platform.
+     */
+    List<String> getAssemblerArgs();
+
+    /**
+     * The additional args supplied to the Static Library Archiver to target the platform.
+     */
+    List<String> getStaticLibraryArchiverArgs();
+
+    /**
+     * The additional args supplied to the Linker to target the platform.
+     */
+    List<String> getLinkerArgs();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/ToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/ToolChain.java
new file mode 100644
index 0000000..c5259fb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/ToolChain.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A set of compilers and linkers that are used together to construct a native binary.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface ToolChain extends Named {
+    /**
+     * Returns a human consumable name for this tool chain.
+     *
+     * @since 1.11
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/ToolChainRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/ToolChainRegistry.java
new file mode 100644
index 0000000..6679cdd
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/ToolChainRegistry.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
+import org.gradle.api.Incubating;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A container for {@link ToolChain}s.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface ToolChainRegistry extends ExtensiblePolymorphicDomainObjectContainer<ToolChain> {
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/VisualCpp.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/VisualCpp.java
new file mode 100644
index 0000000..d432620
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/VisualCpp.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+
+/**
+ * The Visual C++ tool chain.
+ */
+ at Incubating
+public interface VisualCpp extends ToolChain {
+    /**
+     * The directory where Visual Studio or Visual C++ is installed.
+     */
+    File getInstallDir();
+
+    /**
+     * The directory where Visual Studio or Visual C++ is installed.
+     */
+    void setInstallDir(Object installDir);
+
+    /**
+     * The directory where Windows SDK is installed.
+     */
+    File getWindowsSdkDir();
+
+    /**
+     * The directory where Windows SDK is installed.
+     */
+    void setWindowsSdkDir(Object installDir);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/AbstractToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/AbstractToolChain.java
new file mode 100644
index 0000000..0a78c31
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/AbstractToolChain.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.os.OperatingSystem;
+
+import java.io.File;
+
+public abstract class AbstractToolChain implements ToolChainInternal {
+    private final String name;
+    protected final OperatingSystem operatingSystem;
+    private final FileResolver fileResolver;
+
+    protected AbstractToolChain(String name, OperatingSystem operatingSystem, FileResolver fileResolver) {
+        this.name = name;
+        this.operatingSystem = operatingSystem;
+        this.fileResolver = fileResolver;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    protected abstract String getTypeName();
+
+    public String getDisplayName() {
+        return String.format("Tool chain '%s' (%s)", getName(), getTypeName());
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getOutputType() {
+        return String.format("%s-%s", getName(), operatingSystem.getName());
+    }
+
+    public String getExecutableName(String executablePath) {
+        return operatingSystem.getExecutableName(executablePath);
+    }
+
+    public String getSharedLibraryName(String libraryName) {
+        return operatingSystem.getSharedLibraryName(libraryName);
+    }
+
+    public String getSharedLibraryLinkFileName(String libraryName) {
+        return getSharedLibraryName(libraryName);
+    }
+
+    public String getStaticLibraryName(String libraryName) {
+        return operatingSystem.getStaticLibraryName(libraryName);
+    }
+
+    protected File resolve(Object path) {
+        return fileResolver.resolve(path);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ArgsTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ArgsTransformer.java
new file mode 100644
index 0000000..a7cf5fb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ArgsTransformer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+
+import java.util.List;
+
+public interface ArgsTransformer<T extends BinaryToolSpec> extends Transformer<List<String>, T> {
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/CommandLineTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/CommandLineTool.java
new file mode 100755
index 0000000..b06d42e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/CommandLineTool.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import com.google.common.base.Joiner;
+import org.gradle.api.GradleException;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecActionFactory;
+import org.gradle.process.internal.ExecException;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.*;
+
+public class CommandLineTool<T extends BinaryToolSpec> {
+    private final String action;
+    private final File executable;
+    private final ExecActionFactory execActionFactory;
+    private final Map<String, String> environment = new HashMap<String, String>();
+    private final List<File> path = new ArrayList<File>();
+    private ArgsTransformer<T> specToArgs;
+    private Transformer<T, T> specTransformer = new IdentityTransformer<T>();
+    private File workDir;
+
+    public CommandLineTool(String action, File executable, ExecActionFactory execActionFactory) {
+        this.action = action;
+        this.executable = executable;
+        this.execActionFactory = execActionFactory;
+    }
+
+    public CommandLineTool<T> inWorkDirectory(File workDir) {
+        GFileUtils.mkdirs(workDir);
+        this.workDir = workDir;
+        return this;
+    }
+
+    public CommandLineTool<T> withSpecTransformer(Transformer<T, T> specAction) {
+        this.specTransformer = specAction;
+        return this;
+    }
+
+    public CommandLineTool<T> withPath(List<File> pathEntries) {
+        path.addAll(pathEntries);
+        return this;
+    }
+
+    public CommandLineTool<T> withPath(File... pathEntries) {
+        Collections.addAll(path, pathEntries);
+        return this;
+    }
+
+    public CommandLineTool<T> withEnvironmentVar(String name, String value) {
+        environment.put(name, value);
+        return this;
+    }
+
+    public CommandLineTool<T> withArguments(ArgsTransformer<T> arguments) {
+        this.specToArgs = arguments;
+        return this;
+    }
+
+    public WorkResult execute(T spec) {
+        ExecAction compiler = execActionFactory.newExecAction();
+        compiler.executable(executable);
+        if (workDir != null) {
+            compiler.workingDir(workDir);
+        }
+
+        List<String> args = specToArgs.transform(specTransformer.transform(spec));
+        compiler.args(args);
+
+        if (!path.isEmpty()) {
+            String pathVar = OperatingSystem.current().getPathVar();
+            String compilerPath = Joiner.on(File.pathSeparator).join(path);
+            compilerPath = compilerPath + File.pathSeparator + System.getenv(pathVar);
+            compiler.environment(pathVar, compilerPath);
+        }
+
+        compiler.environment(environment);
+
+        try {
+            compiler.execute();
+        } catch (ExecException e) {
+            throw new GradleException(String.format("%s failed; see the error output for details.", action), e);
+        }
+        return new SimpleWorkResult(true);
+    }
+
+    static class IdentityTransformer<T> implements Transformer<T, T> {
+        public T transform(T original) {
+            return original;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/CompileSpecToArgsTransformerChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/CompileSpecToArgsTransformerChain.java
new file mode 100644
index 0000000..f107cfe
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/CompileSpecToArgsTransformerChain.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompileSpecToArgsTransformerChain<T extends BinaryToolSpec> implements Transformer<List<String>, T> {
+    private final Transformer<List<String>, T> delegate;
+    private final List<Transformer<List<String>, List<String>>> transformers = new ArrayList<Transformer<List<String>, List<String>>>();
+
+    public CompileSpecToArgsTransformerChain(Transformer<List<String>, T> delegate) {
+        this.delegate = delegate;
+    }
+
+    public List<String> transform(T spec) {
+        List<String> args = delegate.transform(spec);
+        for (Transformer<List<String>, List<String>> transformer : transformers) {
+            args = transformer.transform(args);
+        }
+        return args;
+    }
+
+    public CompileSpecToArgsTransformerChain<T> withTransformation(Transformer<List<String>, List<String>> transformer) {
+        transformers.add(transformer);
+        return this;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/DefaultToolChainRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/DefaultToolChainRegistry.java
new file mode 100755
index 0000000..3c57b70
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/DefaultToolChainRegistry.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+import org.gradle.util.TreeVisitor;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DefaultToolChainRegistry extends DefaultPolymorphicDomainObjectContainer<ToolChain> implements ToolChainRegistryInternal {
+    private final Map<String, Class<? extends ToolChain>> registeredDefaults = new LinkedHashMap<String, Class<? extends ToolChain>>();
+    private final List<ToolChainInternal> searchOrder = new ArrayList<ToolChainInternal>();
+
+    public DefaultToolChainRegistry(Instantiator instantiator) {
+        super(ToolChain.class, instantiator);
+        whenObjectAdded(new Action<ToolChain>() {
+            public void execute(ToolChain toolChain) {
+                searchOrder.add((ToolChainInternal) toolChain);
+            }
+        });
+        whenObjectRemoved(new Action<ToolChain>() {
+            public void execute(ToolChain toolChain) {
+                searchOrder.remove(toolChain);
+            }
+        });
+    }
+
+    @Override
+    protected void handleAttemptToAddItemWithNonUniqueName(ToolChain toolChain) {
+        throw new InvalidUserDataException(String.format("ToolChain with name '%s' added multiple times", toolChain.getName()));
+    }
+
+    public void registerDefaultToolChain(String name, Class<? extends ToolChain> type) {
+        registeredDefaults.put(name, type);
+    }
+
+    public void addDefaultToolChains() {
+        for (String name : registeredDefaults.keySet()) {
+            create(name, registeredDefaults.get(name));
+        }
+    }
+
+    public ToolChain getForPlatform(Platform targetPlatform) {
+        for (ToolChainInternal toolChain : searchOrder) {
+            if (toolChain.target(targetPlatform).isAvailable()) {
+                return toolChain;
+            }
+        }
+
+        // No tool chains can build for this platform. Assemble a description of why
+        Map<String, PlatformToolChain> candidates = new LinkedHashMap<String, PlatformToolChain>();
+        for (ToolChainInternal toolChain : searchOrder) {
+            candidates.put(toolChain.getDisplayName(), toolChain.target(targetPlatform));
+        }
+
+        return new UnavailableToolChain(new UnavailableToolChainDescription(targetPlatform, candidates));
+    }
+
+    private static class UnavailableToolChainDescription implements ToolSearchResult {
+        private final Platform targetPlatform;
+        private final Map<String, PlatformToolChain> candidates;
+
+        private UnavailableToolChainDescription(Platform targetPlatform, Map<String, PlatformToolChain> candidates) {
+            this.targetPlatform = targetPlatform;
+            this.candidates = candidates;
+        }
+
+        public boolean isAvailable() {
+            return false;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+            visitor.node(String.format("No tool chain is available to build for platform '%s'", targetPlatform.getName()));
+            visitor.startChildren();
+            for (Map.Entry<String, PlatformToolChain> entry : candidates.entrySet()) {
+                visitor.node(entry.getKey());
+                visitor.startChildren();
+                entry.getValue().explain(visitor);
+                visitor.endChildren();
+            }
+            if (candidates.isEmpty()) {
+                visitor.node("No tool chain plugin applied.");
+            }
+            visitor.endChildren();
+        }
+    }
+    private static class UnavailableToolChain implements ToolChainInternal {
+        private final OperatingSystem operatingSystem = OperatingSystem.current();
+        private final ToolSearchResult failure;
+
+        UnavailableToolChain(ToolSearchResult failure) {
+            this.failure = failure;
+        }
+
+        public String getDisplayName() {
+            return getName();
+        }
+
+        public String getName() {
+            return "unavailable";
+        }
+
+        public PlatformToolChain target(Platform targetPlatform) {
+            return new UnavailablePlatformToolChain(failure);
+        }
+
+        public String getExecutableName(String executablePath) {
+            return operatingSystem.getExecutableName(executablePath);
+        }
+
+        public String getSharedLibraryName(String libraryName) {
+            return operatingSystem.getSharedLibraryName(libraryName);
+        }
+
+        public String getSharedLibraryLinkFileName(String libraryName) {
+            return getSharedLibraryName(libraryName);
+        }
+
+        public String getStaticLibraryName(String libraryName) {
+            return operatingSystem.getStaticLibraryName(libraryName);
+        }
+
+        public String getOutputType() {
+            return "unavailable";
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/MacroArgsConverter.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/MacroArgsConverter.java
new file mode 100644
index 0000000..be90e2f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/MacroArgsConverter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.Transformer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class MacroArgsConverter implements Transformer<List<String>, Map<String, String>> {
+    public List<String> transform(Map<String, String> original) {
+        List<String> macroList = new ArrayList<String>(original.size());
+        for (String macroName : original.keySet()) {
+            String macroDef = original.get(macroName);
+            String arg = macroDef == null ? macroName : String.format("%s=%s", macroName, macroDef);
+            macroList.add(arg);
+        }
+        return macroList;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/NativeCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/NativeCompileSpec.java
new file mode 100644
index 0000000..4efbccc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/NativeCompileSpec.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.nativebinaries.toolchain.internal;
+
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A compile spec that will be used to generate object files for combining into a native binary.
+ */
+public interface NativeCompileSpec extends BinaryToolSpec {
+    File getObjectFileDir();
+
+    void setObjectFileDir(File objectFileDir);
+
+    List<File> getIncludeRoots();
+
+    void include(Iterable<File> includeRoots);
+
+    void include(File... includeRoots);
+
+    List<File> getSourceFiles();
+
+    void setSourceFiles(Collection<File> sources);
+
+    void source(Iterable<File> sources);
+
+    List<File> getRemovedSourceFiles();
+
+    void setRemovedSourceFiles(Collection<File> sources);
+
+    void removedSource(Iterable<File> sources);
+
+    Map<String, String> getMacros();
+
+    void setMacros(Map<String, String> macros);
+
+    void define(String name);
+
+    void define(String name, String value);
+
+    boolean isPositionIndependentCode();
+
+    void setPositionIndependentCode(boolean flag);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/OptionsFileArgsTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/OptionsFileArgsTransformer.java
new file mode 100644
index 0000000..88f4393
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/OptionsFileArgsTransformer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.tasks.compile.ArgWriter;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OptionsFileArgsTransformer<T extends BinaryToolSpec> implements ArgsTransformer<T> {
+    private final Transformer<ArgWriter, PrintWriter> argWriterFactory;
+    private final ArgsTransformer<T> delegate;
+
+    public OptionsFileArgsTransformer(Transformer<ArgWriter, PrintWriter> argWriterFactory, ArgsTransformer<T> delegate) {
+        this.argWriterFactory = argWriterFactory;
+        this.delegate = delegate;
+    }
+
+    public List<String> transform(T spec) {
+        List<String> output = new ArrayList<String>();
+        transformArgs(delegate.transform(spec), output, spec.getTempDir());
+        return output;
+    }
+
+    protected void transformArgs(List<String> input, List<String> output, File tempDir) {
+        GFileUtils.mkdirs(tempDir);
+        File optionsFile = new File(tempDir, "options.txt");
+        try {
+            PrintWriter writer = new PrintWriter(optionsFile);
+            try {
+                ArgWriter argWriter = argWriterFactory.transform(writer);
+                argWriter.args(input);
+            } finally {
+                IOUtils.closeQuietly(writer);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Could not write compiler options file '%s'.", optionsFile.getAbsolutePath()), e);
+        }
+
+        output.add(String.format("@%s", optionsFile.getAbsolutePath()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/OutputCleaningCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/OutputCleaningCompiler.java
new file mode 100755
index 0000000..5b51ce8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/OutputCleaningCompiler.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.nativebinaries.toolchain.internal;
+
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.hash.HashUtil;
+
+import java.io.File;
+
+public class OutputCleaningCompiler<T extends NativeCompileSpec> implements Compiler<T> {
+
+    private final Compiler<T> compiler;
+    private final String outputFileSuffix;
+
+    public OutputCleaningCompiler(Compiler<T> compiler, String outputFileSuffix) {
+        this.compiler = compiler;
+        this.outputFileSuffix = outputFileSuffix;
+    }
+
+    public WorkResult execute(T spec) {
+        boolean didRemove = deleteOutputsForRemovedSources(spec);
+        boolean didCompile = compileSources(spec);
+        return new SimpleWorkResult(didRemove || didCompile);
+    }
+
+    private boolean compileSources(T spec) {
+        if (spec.getSourceFiles().isEmpty()) {
+            return false;
+        }
+        return compiler.execute(spec).getDidWork();
+    }
+
+    private boolean deleteOutputsForRemovedSources(NativeCompileSpec spec) {
+        boolean didRemove = false;
+        for (File removedSource : spec.getRemovedSourceFiles()) {
+            File objectFile = getObjectFile(spec.getObjectFileDir(), removedSource);
+            if (objectFile.delete()) {
+                didRemove = true;
+                objectFile.getParentFile().delete();
+            }
+        }
+        return didRemove;
+    }
+
+    private File getObjectFile(File objectFileRoot, File sourceFile) {
+        String objectFileName = sourceFile.getName().replaceFirst("\\.[^\\.]+$", outputFileSuffix);
+        String compactMD5 = HashUtil.createCompactMD5(sourceFile.getAbsolutePath());
+        File objectFileDir = new File(objectFileRoot, compactMD5);
+        return new File(objectFileDir, objectFileName);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/PlatformToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/PlatformToolChain.java
new file mode 100644
index 0000000..a3c8fa0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/PlatformToolChain.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.nativebinaries.internal.LinkerSpec;
+import org.gradle.nativebinaries.internal.StaticLibraryArchiverSpec;
+
+public interface PlatformToolChain extends ToolSearchResult {
+    <T extends BinaryToolSpec> Compiler<T> createCppCompiler();
+
+    <T extends BinaryToolSpec> Compiler<T> createCCompiler();
+
+    <T extends BinaryToolSpec> Compiler<T> createObjectiveCppCompiler();
+
+    <T extends BinaryToolSpec> Compiler<T> createObjectiveCCompiler();
+
+    <T extends BinaryToolSpec> Compiler<T> createAssembler();
+
+    <T extends BinaryToolSpec> Compiler<T> createWindowsResourceCompiler();
+
+    <T extends LinkerSpec> Compiler<T> createLinker();
+
+    <T extends StaticLibraryArchiverSpec> Compiler<T> createStaticLibraryArchiver();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/SingleSourceCompileArgTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/SingleSourceCompileArgTransformer.java
new file mode 100644
index 0000000..44ed06d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/SingleSourceCompileArgTransformer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.internal.FileUtils;
+import org.gradle.internal.hash.HashUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class SingleSourceCompileArgTransformer<T extends NativeCompileSpec> implements ArgsTransformer<T> {
+    private final ArgsTransformer<T> delegate;
+    private final String objectFileName;
+    private final File sourceFile;
+    private final boolean usingVisualCToolChain;
+    private final boolean windowsPathLengthLimitation;
+
+    public SingleSourceCompileArgTransformer(File sourceFile, String objectFileName, ArgsTransformer<T> delegate, boolean windowsPathLengthLimitation, boolean usingVisualCToolChain) {
+        this.sourceFile = sourceFile;
+        this.delegate = delegate;
+        this.objectFileName = objectFileName;
+        this.usingVisualCToolChain = usingVisualCToolChain;
+        this.windowsPathLengthLimitation = windowsPathLengthLimitation;
+    }
+
+    public List<String> transform(T spec) {
+        List<String> args = new ArrayList<String>();
+        File outputFilePath = getOutputFileDir(sourceFile, spec.getObjectFileDir());
+
+        args.addAll(delegate.transform(spec));
+        args.add(sourceFile.getAbsolutePath());
+
+        if (usingVisualCToolChain) {
+            Collections.addAll(args, "/Fo" + outputFilePath.getAbsolutePath());
+        } else {
+            Collections.addAll(args, "-o", outputFilePath.getAbsolutePath());
+        }
+        return args;
+    }
+
+    protected File getOutputFileDir(File sourceFile, File objectFileDir) {
+        String compactMD5 = HashUtil.createCompactMD5(sourceFile.getAbsolutePath());
+        File outputFileDir = new File(objectFileDir, compactMD5);
+        if(!outputFileDir.exists()){
+            outputFileDir.mkdir();
+        }
+        File outputFile = new File(outputFileDir, objectFileName);
+        return windowsPathLengthLimitation ? FileUtils.assertInWindowsPathLengthLimitation(outputFile) : outputFile;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainAvailability.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainAvailability.java
new file mode 100644
index 0000000..81bc204
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainAvailability.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.internal.text.TreeFormatter;
+import org.gradle.util.TreeVisitor;
+
+public class ToolChainAvailability implements ToolSearchResult {
+    private ToolSearchResult reason;
+
+    public boolean isAvailable() {
+        return reason == null;
+    }
+
+    public String getUnavailableMessage() {
+        TreeFormatter formatter = new TreeFormatter();
+        this.explain(formatter);
+        return formatter.toString();
+    }
+
+    public void explain(TreeVisitor<? super String> visitor) {
+        reason.explain(visitor);
+    }
+
+    public ToolChainAvailability unavailable(String unavailableMessage) {
+        if (reason == null) {
+            reason = new FixedMessageToolSearchResult(unavailableMessage);
+        }
+        return this;
+    }
+
+    public ToolChainAvailability mustBeAvailable(ToolSearchResult tool) {
+        if (!tool.isAvailable() && reason == null) {
+            reason = tool;
+        }
+        return this;
+    }
+
+    private static class FixedMessageToolSearchResult implements ToolSearchResult {
+        private final String message;
+
+        private FixedMessageToolSearchResult(String message) {
+            this.message = message;
+        }
+
+        public boolean isAvailable() {
+            return false;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+            visitor.node(message);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainInternal.java
new file mode 100644
index 0000000..2e53bcf
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainInternal.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+
+public interface ToolChainInternal extends ToolChain {
+    /**
+     * Locates the tools that can target the given platform.
+     */
+    PlatformToolChain target(Platform targetPlatform);
+
+    // TODO:DAZ These are platform-specific
+    String getExecutableName(String executablePath);
+
+    String getSharedLibraryName(String libraryPath);
+
+    String getSharedLibraryLinkFileName(String libraryPath);
+
+    String getStaticLibraryName(String libraryPath);
+
+    /**
+     * Returns a unique identifier for the output produced by this toolchain on the current platform.
+     */
+    String getOutputType();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainRegistryInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainRegistryInternal.java
new file mode 100644
index 0000000..2db978a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainRegistryInternal.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.ToolChain;
+import org.gradle.nativebinaries.toolchain.ToolChainRegistry;
+
+public interface ToolChainRegistryInternal extends ToolChainRegistry {
+    /**
+     * Registers a default ToolChain, which may later be added to the registry via {@link #addDefaultToolChains()}.
+     */
+    void registerDefaultToolChain(String name, Class<? extends ToolChain> type);
+
+    /**
+     * Adds default tool chains to the registry.
+     */
+    void addDefaultToolChains();
+
+    ToolChain getForPlatform(Platform targetPlatform);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolSearchResult.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolSearchResult.java
new file mode 100644
index 0000000..134101f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolSearchResult.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.util.TreeVisitor;
+
+public interface ToolSearchResult {
+    boolean isAvailable();
+
+    /**
+     * Writes some diagnostics about why the tool is not available.
+     */
+    void explain(TreeVisitor<? super String> visitor);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolType.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolType.java
new file mode 100644
index 0000000..a197514
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/ToolType.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.util.GUtil;
+
+public enum ToolType {
+    CPP_COMPILER("C++ compiler"),
+    C_COMPILER("C compiler"),
+    OBJECTIVECPP_COMPILER("Objective-C++ compiler"),
+    OBJECTIVEC_COMPILER("Objective-C compiler"),
+    ASSEMBLER("Assembler"),
+    LINKER("Linker"),
+    STATIC_LIB_ARCHIVER("Static library archiver");
+
+    private final String toolName;
+
+    ToolType(String toolName) {
+        this.toolName = toolName;
+    }
+
+    public String getToolName() {
+        return toolName;
+    }
+
+    @Override
+    public String toString() {
+        return GUtil.toLowerCamelCase(name());
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/UnavailablePlatformToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/UnavailablePlatformToolChain.java
new file mode 100644
index 0000000..fe4d0f2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/UnavailablePlatformToolChain.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.text.TreeFormatter;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.nativebinaries.internal.LinkerSpec;
+import org.gradle.nativebinaries.internal.StaticLibraryArchiverSpec;
+import org.gradle.util.TreeVisitor;
+
+public class UnavailablePlatformToolChain implements PlatformToolChain {
+    private final ToolSearchResult failure;
+
+    public UnavailablePlatformToolChain(ToolSearchResult failure) {
+        this.failure = failure;
+    }
+
+    public boolean isAvailable() {
+        return false;
+    }
+
+    public void explain(TreeVisitor<? super String> visitor) {
+        failure.explain(visitor);
+    }
+
+    private RuntimeException failure() {
+        TreeFormatter formatter = new TreeFormatter();
+        this.explain(formatter);
+        return new GradleException(formatter.toString());
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createAssembler() {
+        throw failure();
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createCppCompiler() {
+        throw failure();
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createCCompiler() {
+        throw failure();
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createObjectiveCppCompiler() {
+        throw failure();
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createObjectiveCCompiler() {
+        throw failure();
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createWindowsResourceCompiler() {
+        throw failure();
+    }
+
+    public <T extends LinkerSpec> Compiler<T> createLinker() {
+        throw failure();
+    }
+
+    public <T extends StaticLibraryArchiverSpec> Compiler<T> createStaticLibraryArchiver() {
+        throw failure();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/clang/ClangToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/clang/ClangToolChain.java
new file mode 100644
index 0000000..0ef94a8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/clang/ClangToolChain.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.clang;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.toolchain.Clang;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+import org.gradle.nativebinaries.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
+import org.gradle.nativebinaries.toolchain.internal.tools.ToolSearchPath;
+import org.gradle.process.internal.ExecActionFactory;
+
+public class ClangToolChain extends AbstractGccCompatibleToolChain implements Clang {
+    public static final String DEFAULT_NAME = "clang";
+
+    public ClangToolChain(String name, OperatingSystem operatingSystem, FileResolver fileResolver, ExecActionFactory execActionFactory) {
+        super(name, operatingSystem, fileResolver, execActionFactory, new ToolSearchPath(operatingSystem));
+
+        registerTool(ToolType.CPP_COMPILER, "clang++");
+        registerTool(ToolType.C_COMPILER, "clang");
+        registerTool(ToolType.OBJECTIVECPP_COMPILER, "clang++");
+        registerTool(ToolType.OBJECTIVEC_COMPILER, "clang");
+        registerTool(ToolType.ASSEMBLER, "as");
+        registerTool(ToolType.LINKER, "clang++");
+        registerTool(ToolType.STATIC_LIB_ARCHIVER, "ar");
+    }
+
+    @Override
+    protected String getTypeName() {
+        return "Clang";
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java
new file mode 100644
index 0000000..5a7575b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal;
+import org.gradle.nativebinaries.toolchain.GccTool;
+import org.gradle.nativebinaries.toolchain.PlatformConfigurableToolChain;
+import org.gradle.nativebinaries.toolchain.TargetPlatformConfiguration;
+import org.gradle.nativebinaries.toolchain.internal.*;
+import org.gradle.nativebinaries.toolchain.internal.tools.*;
+import org.gradle.process.internal.ExecActionFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+
+/**
+ * A tool chain that has GCC semantics, where all platform variants are produced by varying the tool args.
+ */
+public abstract class AbstractGccCompatibleToolChain extends AbstractToolChain implements PlatformConfigurableToolChain {
+    private final ExecActionFactory execActionFactory;
+    private final ToolSearchPath toolSearchPath;
+    private final DefaultToolRegistry toolRegistry = new DefaultToolRegistry();
+
+    private final List<TargetPlatformConfiguration> platformConfigs = new ArrayList<TargetPlatformConfiguration>();
+    private int configInsertLocation;
+
+    public AbstractGccCompatibleToolChain(String name, OperatingSystem operatingSystem, FileResolver fileResolver, ExecActionFactory execActionFactory, ToolSearchPath toolSearchPath) {
+        super(name, operatingSystem, fileResolver);
+        this.execActionFactory = execActionFactory;
+        this.toolSearchPath = toolSearchPath;
+
+        addPlatformConfiguration(new ToolChainDefaultArchitecture());
+        addPlatformConfiguration(new Intel32Architecture());
+        addPlatformConfiguration(new Intel64Architecture());
+        configInsertLocation = 0;
+    }
+
+    protected GccTool getTool(ToolType toolType) {
+        return toolRegistry.getTool(toolType);
+    }
+
+    protected void registerTool(ToolType type, String defaultExecutable) {
+        toolRegistry.register(type, defaultExecutable);
+    }
+
+    protected CommandLineToolSearchResult locate(ToolType type) {
+        GccTool gccTool = getTool(type);
+        return toolSearchPath.locate(type, gccTool.getExecutable());
+    }
+
+    public List<File> getPath() {
+        return toolSearchPath.getPath();
+    }
+
+    public void path(Object... pathEntries) {
+        for (Object path : pathEntries) {
+            toolSearchPath.path(resolve(path));
+        }
+    }
+
+    protected void initTools(ToolChainAvailability availability) {
+        for (ToolType type : ToolType.values()) {
+            locate(type);
+        }
+        boolean found = false;
+        for (ToolType type : Arrays.asList(ToolType.C_COMPILER, ToolType.CPP_COMPILER, ToolType.OBJECTIVEC_COMPILER, ToolType.OBJECTIVECPP_COMPILER)) {
+            found |= locate(type).isAvailable();
+        }
+        if (!found) {
+            availability.mustBeAvailable(locate(ToolType.C_COMPILER));
+        }
+    }
+
+    public void addPlatformConfiguration(TargetPlatformConfiguration platformConfig) {
+        platformConfigs.add(configInsertLocation, platformConfig);
+        configInsertLocation++;
+    }
+
+    public PlatformToolChain target(Platform targetPlatform) {
+        TargetPlatformConfiguration platformConfiguration = getPlatformConfiguration(targetPlatform);
+        ToolChainAvailability result = new ToolChainAvailability();
+        if (platformConfiguration == null) {
+            result.unavailable(String.format("Don't know how to build for platform '%s'.", targetPlatform.getName()));
+            return new UnavailablePlatformToolChain(result);
+        }
+        initTools(result);
+        if (!result.isAvailable()) {
+            return new UnavailablePlatformToolChain(result);
+        }
+
+        // Target the tools for the platform
+        ToolRegistry platformTools = new PlatformToolRegistry(toolRegistry, platformConfiguration);
+        return new GccPlatformToolChain(toolSearchPath, platformTools, execActionFactory, canUseCommandFile());
+    }
+
+    protected TargetPlatformConfiguration getPlatformConfiguration(Platform targetPlatform) {
+        for (TargetPlatformConfiguration platformConfig : platformConfigs) {
+            if (platformConfig.supportsPlatform(targetPlatform)) {
+                return platformConfig;
+            }
+        }
+        return null;
+    }
+
+    protected boolean canUseCommandFile() {
+        return true;
+    }
+
+    public GccTool getCppCompiler() {
+        return getTool(ToolType.CPP_COMPILER);
+    }
+
+    public GccTool getCCompiler() {
+        return getTool(ToolType.C_COMPILER);
+    }
+
+    // This is here to allow using this property from Groovy as `cCompiler`
+    public GccTool getcCompiler() {
+        return getTool(ToolType.C_COMPILER);
+    }
+
+    public GccTool getAssembler() {
+        return getTool(ToolType.ASSEMBLER);
+    }
+
+    public GccTool getLinker() {
+        return getTool(ToolType.LINKER);
+    }
+
+    public GccTool getStaticLibArchiver() {
+        return getTool(ToolType.STATIC_LIB_ARCHIVER);
+    }
+    private static class ToolChainDefaultArchitecture implements TargetPlatformConfiguration {
+        public boolean supportsPlatform(Platform targetPlatform) {
+            return targetPlatform.getOperatingSystem().isCurrent()
+                && targetPlatform.getArchitecture() == ArchitectureInternal.TOOL_CHAIN_DEFAULT;
+        }
+
+        public List<String> getAssemblerArgs() {
+            return emptyList();
+        }
+
+        public List<String> getCppCompilerArgs() {
+            return emptyList();
+        }
+
+        public List<String> getCCompilerArgs() {
+            return emptyList();
+        }
+
+        public List<String> getObjectiveCppCompilerArgs() {
+            return emptyList();
+        }
+
+        public List<String> getObjectiveCCompilerArgs() {
+            return emptyList();
+        }
+
+        public List<String> getStaticLibraryArchiverArgs() {
+            return emptyList();
+        }
+
+        public List<String> getLinkerArgs() {
+            return emptyList();
+        }
+    }
+
+    private static class Intel32Architecture implements TargetPlatformConfiguration {
+        public boolean supportsPlatform(Platform targetPlatform) {
+            return targetPlatform.getOperatingSystem().isCurrent()
+                    && ((ArchitectureInternal) targetPlatform.getArchitecture()).isI386();
+        }
+
+        public List<String> getCppCompilerArgs() {
+            return asList("-m32");
+        }
+
+        public List<String> getCCompilerArgs() {
+            return asList("-m32");
+        }
+
+        public List<String> getObjectiveCppCompilerArgs() {
+            return asList("-m32");
+        }
+
+        public List<String> getObjectiveCCompilerArgs() {
+            return asList("-m32");
+        }
+
+        public List<String> getAssemblerArgs() {
+            if (OperatingSystem.current().isMacOsX()) {
+                return asList("-arch", "i386");
+            } else {
+                return asList("--32");
+            }
+        }
+
+        public List<String> getLinkerArgs() {
+            return asList("-m32");
+        }
+
+        public List<String> getStaticLibraryArchiverArgs() {
+            return emptyList();
+        }
+    }
+
+    private static class Intel64Architecture implements TargetPlatformConfiguration {
+        public boolean supportsPlatform(Platform targetPlatform) {
+            return targetPlatform.getOperatingSystem().isCurrent()
+                    && !OperatingSystem.current().isWindows() // Currently don't support building 64-bit binaries on GCC/Windows
+                    && ((ArchitectureInternal) targetPlatform.getArchitecture()).isAmd64();
+        }
+        public List<String> getCppCompilerArgs() {
+            return asList("-m64");
+        }
+
+        public List<String> getCCompilerArgs() {
+            return asList("-m64");
+        }
+
+        public List<String> getObjectiveCppCompilerArgs() {
+            return asList("-m64");
+        }
+
+        public List<String> getObjectiveCCompilerArgs() {
+            return asList("-m64");
+        }
+
+        public List<String> getAssemblerArgs() {
+            if (OperatingSystem.current().isMacOsX()) {
+                return asList("-arch", "x86_64");
+            } else {
+                return asList("--64");
+            }
+        }
+
+        public List<String> getLinkerArgs() {
+            return asList("-m64");
+        }
+
+        public List<String> getStaticLibraryArchiverArgs() {
+            return emptyList();
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ArStaticLibraryArchiver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ArStaticLibraryArchiver.java
new file mode 100755
index 0000000..b867e0d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ArStaticLibraryArchiver.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.nativebinaries.internal.StaticLibraryArchiverSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A static library archiver based on the GNU 'ar' utility
+ */
+class ArStaticLibraryArchiver implements Compiler<StaticLibraryArchiverSpec> {
+    private final CommandLineTool<StaticLibraryArchiverSpec> commandLineTool;
+
+    public ArStaticLibraryArchiver(CommandLineTool<StaticLibraryArchiverSpec> commandLineTool, Action<List<String>> argsAction) {
+        ArgsTransformer<StaticLibraryArchiverSpec> arguments = new ArchiverSpecToArguments();
+        arguments = new PostTransformActionArgsTransformer<StaticLibraryArchiverSpec>(arguments, argsAction);
+        this.commandLineTool = commandLineTool.withArguments(arguments);
+    }
+
+    public WorkResult execute(StaticLibraryArchiverSpec spec) {
+        deletePreviousOutput(spec);
+        return commandLineTool.execute(spec);
+    }
+
+    private void deletePreviousOutput(StaticLibraryArchiverSpec spec) {
+        // Need to delete the previous archive, otherwise stale object files will remain
+        if (!spec.getOutputFile().isFile()) {
+            return;
+        }
+        if (!(spec.getOutputFile().delete())) {
+            throw new GradleException("Create static archive failed: could not delete previous archive");
+        }
+    }
+
+    private static class ArchiverSpecToArguments implements ArgsTransformer<StaticLibraryArchiverSpec> {
+        public List<String> transform(StaticLibraryArchiverSpec spec) {
+            List<String> args = new ArrayList<String>();
+            // -r : Add files to static archive, creating if required
+            // -c : Don't write message to standard error when creating archive
+            // -s : Create an object file index (equivalent to running 'ranlib')
+            args.add("-rcs");
+            args.addAll(spec.getAllArgs());
+            args.add(spec.getOutputFile().getAbsolutePath());
+            for (File file : spec.getObjectFiles()) {
+                args.add(file.getAbsolutePath());
+            }
+            return args;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/Assembler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/Assembler.java
new file mode 100755
index 0000000..8799dbb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/Assembler.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.nativebinaries.language.assembler.internal.AssembleSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class Assembler implements Compiler<AssembleSpec> {
+
+    private final CommandLineTool<AssembleSpec> commandLineTool;
+    private final Action<List<String>> argsAction;
+    private String outputFileSuffix;
+
+    public Assembler(CommandLineTool<AssembleSpec> commandLineTool, Action<List<String>> argsAction, String outputFileSuffix) {
+        this.commandLineTool = commandLineTool;
+        this.argsAction = argsAction;
+        this.outputFileSuffix = outputFileSuffix;
+    }
+
+    public WorkResult execute(AssembleSpec spec) {
+        boolean didWork = false;
+        CommandLineTool<AssembleSpec> commandLineAssembler = commandLineTool.inWorkDirectory(spec.getObjectFileDir());
+        for (File sourceFile : spec.getSourceFiles()) {
+            ArgsTransformer<AssembleSpec> arguments = new AssembleSpecToArgsList(sourceFile, spec.getObjectFileDir(), outputFileSuffix);
+            arguments = new PostTransformActionArgsTransformer<AssembleSpec>(arguments, argsAction);
+            WorkResult result = commandLineAssembler.withArguments(arguments).execute(spec);
+            didWork = didWork || result.getDidWork();
+        }
+        return new SimpleWorkResult(didWork);
+    }
+
+    private static class AssembleSpecToArgsList implements ArgsTransformer<AssembleSpec> {
+        private final File inputFile;
+        private final File outputFile;
+
+        public AssembleSpecToArgsList(File inputFile, File objectFileRootDir, String outputFileSuffix) {
+            this.inputFile = inputFile;
+            String outputFileName = FilenameUtils.removeExtension(inputFile.getName())+ outputFileSuffix;
+            File currentObjectOutputDir = new File(objectFileRootDir, HashUtil.createCompactMD5(inputFile.getAbsolutePath()));
+            this.outputFile = new File(currentObjectOutputDir, outputFileName);
+        }
+
+        public List<String> transform(AssembleSpec spec) {
+            List<String> args = new ArrayList<String>();
+            args.addAll(spec.getAllArgs());
+            if(!outputFile.getParentFile().exists()){
+                outputFile.getParentFile().mkdir();
+            }
+            Collections.addAll(args, "-o", outputFile.getAbsolutePath());
+            args.add(inputFile.getAbsolutePath());
+            return args;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CCompiler.java
new file mode 100755
index 0000000..1c5fce8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CCompiler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.nativebinaries.language.c.internal.CCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.util.List;
+
+class CCompiler extends NativeCompiler<CCompileSpec> {
+
+    public CCompiler(CommandLineTool<CCompileSpec> commandLineTool, Action<List<String>> toolChainArgsAction, boolean useCommandFile) {
+        super(commandLineTool, toolChainArgsAction, new CCompileArgsTransformer(), useCommandFile);
+    }
+
+    private static class CCompileArgsTransformer extends GccCompilerArgsTransformer<CCompileSpec> {
+        protected String getLanguage() {
+            return "c";
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CommandLineToolSearchResult.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CommandLineToolSearchResult.java
new file mode 100644
index 0000000..694b96f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CommandLineToolSearchResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult;
+
+import java.io.File;
+
+public interface CommandLineToolSearchResult extends ToolSearchResult {
+    File getTool();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CppCompiler.java
new file mode 100755
index 0000000..5ff0885
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CppCompiler.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.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.nativebinaries.language.cpp.internal.CppCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.util.List;
+
+public class CppCompiler extends NativeCompiler<CppCompileSpec> {
+
+    public CppCompiler(CommandLineTool<CppCompileSpec> commandLineTool, Action<List<String>> toolChainArgsAction, boolean useCommandFile) {
+        super(commandLineTool, toolChainArgsAction, new CppCompileArgsTransformer(), useCommandFile);
+
+    }
+
+    private static class CppCompileArgsTransformer extends GccCompilerArgsTransformer<CppCompileSpec> {
+        protected String getLanguage() {
+            return "c++";
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccCompilerArgsTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccCompilerArgsTransformer.java
new file mode 100644
index 0000000..4fac79d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccCompilerArgsTransformer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.MacroArgsConverter;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Maps common options for C/C++ compiling with GCC
+ */
+abstract class GccCompilerArgsTransformer<T extends NativeCompileSpec> implements ArgsTransformer<T> {
+    public List<String> transform(T spec) {
+        List<String> args = new ArrayList<String>();
+        Collections.addAll(args, "-x", getLanguage());
+
+        for (String macroArg : new MacroArgsConverter().transform(spec.getMacros())) {
+            args.add("-D" + macroArg);
+        }
+
+        args.addAll(spec.getAllArgs());
+        args.add("-c");
+        if (spec.isPositionIndependentCode()) {
+            if (!OperatingSystem.current().isWindows()) {
+                args.add("-fPIC");
+            }
+        }
+
+        for (File file : spec.getIncludeRoots()) {
+            args.add("-I");
+            args.add(file.getAbsolutePath());
+        }
+        return args;
+    }
+
+    protected abstract String getLanguage();
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccLinker.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccLinker.java
new file mode 100755
index 0000000..7c84bed
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccLinker.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.internal.LinkerSpec;
+import org.gradle.nativebinaries.internal.SharedLibraryLinkerSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+class GccLinker implements Compiler<LinkerSpec> {
+
+    private final CommandLineTool<LinkerSpec> commandLineTool;
+
+    public GccLinker(CommandLineTool<LinkerSpec> commandLineTool, Action<List<String>> argsAction, boolean useCommandFile) {
+        ArgsTransformer<LinkerSpec> argsTransformer = new GccLinkerArgsTransformer();
+        argsTransformer = new PostTransformActionArgsTransformer<LinkerSpec>(argsTransformer, argsAction);
+        if (useCommandFile) {
+            argsTransformer = new GccOptionsFileArgTransformer<LinkerSpec>(argsTransformer);
+        }
+        this.commandLineTool = commandLineTool.withArguments(argsTransformer);
+    }
+
+    public WorkResult execute(LinkerSpec spec) {
+        return commandLineTool.execute(spec);
+    }
+
+    private static class GccLinkerArgsTransformer implements ArgsTransformer<LinkerSpec> {
+        public List<String> transform(LinkerSpec spec) {
+            List<String> args = new ArrayList<String>();
+            
+            args.addAll(spec.getSystemArgs());
+
+            if (spec instanceof SharedLibraryLinkerSpec) {
+                args.add("-shared");
+                maybeSetInstallName((SharedLibraryLinkerSpec) spec, args);
+            }
+            args.add("-o");
+            args.add(spec.getOutputFile().getAbsolutePath());
+            for (File file : spec.getObjectFiles()) {
+                args.add(file.getAbsolutePath());
+            }
+            for (File file : spec.getLibraries()) {
+                args.add(file.getAbsolutePath());
+            }
+            for (File pathEntry : spec.getLibraryPath()) {
+                // TODO:DAZ It's not clear to me what the correct meaning of this should be for GCC
+//                args.add("-L" + pathEntry.getAbsolutePath());
+//                args.add("-Wl,-L" + pathEntry.getAbsolutePath());
+//                args.add("-Wl,-rpath," + pathEntry.getAbsolutePath());
+                throw new UnsupportedOperationException("Library Path not yet supported on GCC");
+            }
+
+            for (String userArg : spec.getArgs()) {
+                args.add(userArg);
+            }
+
+            return args;
+        }
+
+        private void maybeSetInstallName(SharedLibraryLinkerSpec spec, List<String> args) {
+            String installName = spec.getInstallName();
+            if (installName == null || OperatingSystem.current().isWindows()) {
+                return;
+            }
+            if (OperatingSystem.current().isMacOsX()) {
+                args.add("-Wl,-install_name," + installName);
+            } else {
+                args.add("-Wl,-soname," + installName);
+            }
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccOptionsFileArgTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccOptionsFileArgTransformer.java
new file mode 100644
index 0000000..6065ab6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccOptionsFileArgTransformer.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.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.internal.tasks.compile.ArgWriter;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.OptionsFileArgsTransformer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Uses an option file for arguments passed to GCC if possible.
+ * Certain GCC options do not function correctly when included in an option file, so include these directly on the command line as well.
+ */
+class GccOptionsFileArgTransformer<T extends BinaryToolSpec> extends OptionsFileArgsTransformer<T> {
+    private static final List<String> CLI_ONLY_ARGS = Arrays.asList("-m32", "-m64");
+
+    public GccOptionsFileArgTransformer(ArgsTransformer<T> delegate) {
+        super(ArgWriter.unixStyleFactory(), delegate);
+    }
+
+    @Override
+    protected void transformArgs(List<String> input, List<String> output, File tempDir) {
+        List<String> commandLineOnlyArgs = getCommandLineOnlyArgs(input);
+        output.addAll(commandLineOnlyArgs);
+        super.transformArgs(input, output, tempDir);
+    }
+
+    private List<String> getCommandLineOnlyArgs(List<String> allArgs) {
+        List<String> commandLineOnlyArgs = new ArrayList<String>(allArgs);
+        commandLineOnlyArgs.retainAll(CLI_ONLY_ARGS);
+        return commandLineOnlyArgs;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccPlatformToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccPlatformToolChain.java
new file mode 100644
index 0000000..82be2d0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccPlatformToolChain.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.nativebinaries.internal.LinkerSpec;
+import org.gradle.nativebinaries.internal.StaticLibraryArchiverSpec;
+import org.gradle.nativebinaries.language.assembler.internal.AssembleSpec;
+import org.gradle.nativebinaries.language.c.internal.CCompileSpec;
+import org.gradle.nativebinaries.language.cpp.internal.CppCompileSpec;
+import org.gradle.nativebinaries.language.objectivec.internal.ObjectiveCCompileSpec;
+import org.gradle.nativebinaries.language.objectivecpp.internal.ObjectiveCppCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+import org.gradle.nativebinaries.toolchain.internal.OutputCleaningCompiler;
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+import org.gradle.nativebinaries.toolchain.internal.tools.ToolRegistry;
+import org.gradle.nativebinaries.toolchain.internal.tools.ToolSearchPath;
+import org.gradle.process.internal.ExecActionFactory;
+import org.gradle.util.TreeVisitor;
+
+class GccPlatformToolChain implements PlatformToolChain {
+    private final ToolSearchPath toolSearchPath;
+    private final ToolRegistry toolRegistry;
+    private final ExecActionFactory execActionFactory;
+    private final boolean useCommandFile;
+
+    GccPlatformToolChain(ToolSearchPath toolSearchPath, ToolRegistry toolRegistry, ExecActionFactory execActionFactory, boolean useCommandFile) {
+        this.toolRegistry = toolRegistry;
+        this.toolSearchPath = toolSearchPath;
+        this.execActionFactory = execActionFactory;
+        this.useCommandFile = useCommandFile;
+    }
+
+    public boolean isAvailable() {
+        return true;
+    }
+
+    public void explain(TreeVisitor<? super String> visitor) {
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createCppCompiler() {
+        CommandLineTool<CppCompileSpec> commandLineTool = commandLineTool(ToolType.CPP_COMPILER);
+        CppCompiler cppCompiler = new CppCompiler(commandLineTool, toolRegistry.getTool(ToolType.CPP_COMPILER).getArgAction(), useCommandFile);
+        return (Compiler<T>) new OutputCleaningCompiler<CppCompileSpec>(cppCompiler, getOutputFileSuffix());
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createCCompiler() {
+        CommandLineTool<CCompileSpec> commandLineTool = commandLineTool(ToolType.C_COMPILER);
+        CCompiler cCompiler = new CCompiler(commandLineTool, toolRegistry.getTool(ToolType.C_COMPILER).getArgAction(), useCommandFile);
+        return (Compiler<T>) new OutputCleaningCompiler<CCompileSpec>(cCompiler, getOutputFileSuffix());
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createObjectiveCppCompiler() {
+        CommandLineTool<ObjectiveCppCompileSpec> commandLineTool = commandLineTool(ToolType.OBJECTIVECPP_COMPILER);
+        ObjectiveCppCompiler objectiveCppCompiler = new ObjectiveCppCompiler(commandLineTool, toolRegistry.getTool(ToolType.OBJECTIVECPP_COMPILER).getArgAction(), useCommandFile);
+        return (Compiler<T>) new OutputCleaningCompiler<ObjectiveCppCompileSpec>(objectiveCppCompiler, getOutputFileSuffix());
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createObjectiveCCompiler() {
+        CommandLineTool<ObjectiveCCompileSpec> commandLineTool = commandLineTool(ToolType.OBJECTIVEC_COMPILER);
+        ObjectiveCCompiler objectiveCCompiler = new ObjectiveCCompiler(commandLineTool, toolRegistry.getTool(ToolType.OBJECTIVEC_COMPILER).getArgAction(), useCommandFile);
+        return (Compiler<T>) new OutputCleaningCompiler<ObjectiveCCompileSpec>(objectiveCCompiler, getOutputFileSuffix());
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createAssembler() {
+        CommandLineTool<AssembleSpec> commandLineTool = commandLineTool(ToolType.ASSEMBLER);
+        return (Compiler<T>) new Assembler(commandLineTool, toolRegistry.getTool(ToolType.ASSEMBLER).getArgAction(), getOutputFileSuffix());
+    }
+
+    public <T extends BinaryToolSpec> Compiler<T> createWindowsResourceCompiler() {
+        throw new UnsupportedOperationException();
+    }
+
+    public <T extends LinkerSpec> Compiler<T> createLinker() {
+        CommandLineTool<LinkerSpec> commandLineTool = commandLineTool(ToolType.LINKER);
+        return (Compiler<T>) new GccLinker(commandLineTool, toolRegistry.getTool(ToolType.LINKER).getArgAction(), useCommandFile);
+    }
+
+    public <T extends StaticLibraryArchiverSpec> Compiler<T> createStaticLibraryArchiver() {
+        CommandLineTool<StaticLibraryArchiverSpec> commandLineTool = commandLineTool(ToolType.STATIC_LIB_ARCHIVER);
+            return (Compiler<T>) new ArStaticLibraryArchiver(commandLineTool, toolRegistry.getTool(ToolType.STATIC_LIB_ARCHIVER).getArgAction());
+    }
+
+
+    private String getOutputFileSuffix() {
+        return OperatingSystem.current().isWindows() ? ".obj" : ".o";
+    }
+
+    private <T extends BinaryToolSpec> CommandLineTool<T> commandLineTool(ToolType key) {
+        String exeName = toolRegistry.getTool(key).getExecutable();
+        CommandLineTool<T> commandLineTool = new CommandLineTool<T>(key.getToolName(), toolSearchPath.locate(key, exeName).getTool(), execActionFactory);
+        // MinGW requires the path to be set
+        commandLineTool.withPath(toolSearchPath.getPath());
+        commandLineTool.withEnvironmentVar("CYGWIN", "nodosfilewarning");
+        return commandLineTool;
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolChain.java
new file mode 100755
index 0000000..bc8f4ba
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolChain.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.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.toolchain.Gcc;
+import org.gradle.nativebinaries.toolchain.internal.ToolChainAvailability;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+import org.gradle.nativebinaries.toolchain.internal.gcc.version.GccVersionDeterminer;
+import org.gradle.nativebinaries.toolchain.internal.gcc.version.GccVersionResult;
+import org.gradle.process.internal.ExecActionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * Compiler adapter for GCC.
+ */
+public class GccToolChain extends AbstractGccCompatibleToolChain implements Gcc {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GccToolChain.class);
+
+    public static final String DEFAULT_NAME = "gcc";
+
+    private final Transformer<GccVersionResult, File> versionDeterminer;
+
+    private GccVersionResult versionResult;
+
+    public GccToolChain(String name, OperatingSystem operatingSystem, FileResolver fileResolver, ExecActionFactory execActionFactory) {
+        super(name, operatingSystem, fileResolver, execActionFactory, new GccToolSearchPath(operatingSystem));
+        this.versionDeterminer = new GccVersionDeterminer(execActionFactory);
+
+        registerTool(ToolType.CPP_COMPILER, "g++");
+        registerTool(ToolType.C_COMPILER, "gcc");
+        registerTool(ToolType.OBJECTIVECPP_COMPILER, "g++");
+        registerTool(ToolType.OBJECTIVEC_COMPILER, "gcc");
+        registerTool(ToolType.ASSEMBLER, "as");
+        registerTool(ToolType.LINKER, "g++");
+        registerTool(ToolType.STATIC_LIB_ARCHIVER, "ar");
+    }
+
+    @Override
+    protected String getTypeName() {
+        return "GNU GCC";
+    }
+
+    @Override
+    protected void initTools(ToolChainAvailability availability) {
+        if (versionResult == null) {
+            CommandLineToolSearchResult compiler = locate(ToolType.C_COMPILER);
+            if (!compiler.isAvailable()) {
+                compiler = locate(ToolType.CPP_COMPILER);
+            }
+            availability.mustBeAvailable(compiler);
+            if (!compiler.isAvailable()) {
+                return;
+            }
+            versionResult = versionDeterminer.transform(compiler.getTool());
+            LOGGER.debug("Found {} with version {}", ToolType.C_COMPILER.getToolName(), versionResult);
+        }
+        availability.mustBeAvailable(versionResult);
+    }
+
+    protected boolean canUseCommandFile() {
+        String[] components = versionResult.getVersion().split("\\.");
+        int majorVersion;
+        try {
+            majorVersion = Integer.valueOf(components[0]);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(String.format("Unable to determine major g++ version from version number %s.", versionResult), e);
+        }
+        return majorVersion >= 4;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolSearchPath.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolSearchPath.java
new file mode 100644
index 0000000..91e506e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolSearchPath.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.toolchain.internal.tools.ToolSearchPath;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+class GccToolSearchPath extends ToolSearchPath {
+    public GccToolSearchPath(OperatingSystem operatingSystem) {
+        super(operatingSystem);
+    }
+
+    @Override
+    protected File findExecutable(OperatingSystem operatingSystem, String name) {
+        List<String> candidates;
+        if (operatingSystem.isWindows()) {
+            // Under Cygwin, g++/gcc is a Cygwin symlink to either g++-3 or g++-4. We can't run g++ directly
+            candidates = Arrays.asList(name + "-4", name + "-3", name);
+        } else {
+            candidates = Arrays.asList(name);
+        }
+        for (String candidate : candidates) {
+            File executable = super.findExecutable(operatingSystem, candidate);
+            if (executable != null) {
+                return executable;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/NativeCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/NativeCompiler.java
new file mode 100644
index 0000000..56f7c8d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/NativeCompiler.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.SingleSourceCompileArgTransformer;
+
+import java.io.File;
+import java.util.List;
+
+abstract public class NativeCompiler<T extends NativeCompileSpec> implements Compiler<T> {
+
+    private final CommandLineTool<T> commandLineTool;
+    private final ArgsTransformer<T> argsTransfomer;
+
+    public NativeCompiler(CommandLineTool<T> commandLineTool, Action<List<String>> toolChainArgsAction, ArgsTransformer<T> argsTransformer, boolean useCommandFile) {
+        argsTransformer = new PostTransformActionArgsTransformer<T>(argsTransformer, toolChainArgsAction);
+        if (useCommandFile) {
+            argsTransformer = new GccOptionsFileArgTransformer<T>(argsTransformer);
+        }
+        this.argsTransfomer = argsTransformer;
+        this.commandLineTool = commandLineTool;
+    }
+
+    public WorkResult execute(T spec) {
+        boolean didWork = false;
+        boolean windowsPathLimitation = OperatingSystem.current().isWindows();
+
+        String objectFileExtension = OperatingSystem.current().isWindows() ? ".obj" : ".o";
+        for (File sourceFile : spec.getSourceFiles()) {
+            String objectFileName = FilenameUtils.removeExtension(sourceFile.getName()) + objectFileExtension;
+            WorkResult result = commandLineTool.inWorkDirectory(spec.getObjectFileDir())
+                    .withArguments(new SingleSourceCompileArgTransformer<T>(sourceFile,
+                                            objectFileName,
+                                            new ShortCircuitArgsTransformer(argsTransfomer),
+                                            windowsPathLimitation,
+                                            false))
+                    .execute(spec);
+            didWork = didWork || result.getDidWork();
+        }
+        return new SimpleWorkResult(didWork);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ObjectiveCCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ObjectiveCCompiler.java
new file mode 100644
index 0000000..f13affe
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ObjectiveCCompiler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.nativebinaries.language.objectivec.internal.ObjectiveCCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.util.List;
+
+public class ObjectiveCCompiler extends NativeCompiler<ObjectiveCCompileSpec> {
+
+    public ObjectiveCCompiler(CommandLineTool<ObjectiveCCompileSpec> commandLineTool, Action<List<String>> toolChainArgsAction, boolean useCommandFile) {
+        super(commandLineTool, toolChainArgsAction, new ObjectiveCCompileArgsTransformer(), useCommandFile);
+    }
+
+    private static class ObjectiveCCompileArgsTransformer extends GccCompilerArgsTransformer<ObjectiveCCompileSpec> {
+        protected String getLanguage() {
+            return "objective-c";
+        }
+    }
+
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ObjectiveCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ObjectiveCppCompiler.java
new file mode 100644
index 0000000..eaba4dd
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ObjectiveCppCompiler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.nativebinaries.language.objectivecpp.internal.ObjectiveCppCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.util.List;
+
+public class ObjectiveCppCompiler extends NativeCompiler<ObjectiveCppCompileSpec> {
+
+    public ObjectiveCppCompiler(CommandLineTool<ObjectiveCppCompileSpec> commandLineTool, Action<List<String>> toolChainArgsAction, boolean useCommandFile) {
+        super(commandLineTool, toolChainArgsAction, new ObjectiveCppCompileArgsTransformer(), useCommandFile);
+    }
+
+    private static class ObjectiveCppCompileArgsTransformer extends GccCompilerArgsTransformer<ObjectiveCppCompileSpec> {
+        protected String getLanguage() {
+            return "objective-c++";
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/PostTransformActionArgsTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/PostTransformActionArgsTransformer.java
new file mode 100644
index 0000000..0e62a78
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/PostTransformActionArgsTransformer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.api.Action;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class PostTransformActionArgsTransformer<T extends BinaryToolSpec> implements ArgsTransformer<T> {
+    private final ArgsTransformer<T> delegate;
+    private final Action<List<String>> userTransformer;
+
+    PostTransformActionArgsTransformer(ArgsTransformer<T> delegate, Action<List<String>> userTransformer) {
+        this.delegate = delegate;
+        this.userTransformer = userTransformer;
+    }
+
+    public List<String> transform(T spec) {
+        List<String> args = new ArrayList<String>(delegate.transform(spec));
+        userTransformer.execute(args);
+        return args;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ShortCircuitArgsTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ShortCircuitArgsTransformer.java
new file mode 100644
index 0000000..41c14a3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ShortCircuitArgsTransformer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc;
+
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.util.List;
+
+public class ShortCircuitArgsTransformer<T extends NativeCompileSpec> implements ArgsTransformer<T>  {
+    private ArgsTransformer<T> delegate;
+    private List<String> cachedArguments;
+    private T cachedSpec;
+
+    public ShortCircuitArgsTransformer(ArgsTransformer<T> delegate) {
+        this.delegate = delegate;
+    }
+
+    public List<String> transform(T spec) {
+        if(spec.equals(cachedSpec)){
+            return cachedArguments;
+        }
+        cachedArguments = delegate.transform(spec);
+        this.cachedSpec = spec;
+        return cachedArguments;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionDeterminer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionDeterminer.java
new file mode 100644
index 0000000..b1bebbb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionDeterminer.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc.version;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecActionFactory;
+import org.gradle.util.TreeVisitor;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Given a File pointing to an (existing) g++ binary, extracts the version number by running with -v and scraping the output.
+ */
+public class GccVersionDeterminer implements Transformer<GccVersionResult, File> {
+    private static final Pattern DEFINE_PATTERN = Pattern.compile("\\s*#define\\s+(\\S+)\\s+(.*)");
+
+    private final Transformer<String, File> outputProducer;
+
+    public GccVersionDeterminer(ExecActionFactory execActionFactory) {
+        this.outputProducer = new GccVersionOutputProducer(execActionFactory);
+    }
+
+    public GccVersionResult transform(File gccBinary) {
+        String output = outputProducer.transform(gccBinary);
+        if (output == null) {
+            return new BrokenResult(String.format("Could not determine GCC version: failed to execute %s -v.", gccBinary.getName()));
+        }
+        return transform(output, gccBinary);
+    }
+
+    private GccVersionResult transform(String output, File gccBinary) {
+        BufferedReader reader = new BufferedReader(new StringReader(output));
+        String line;
+        Map<String, String> defines = new HashMap<String, String>();
+        try {
+            while ((line = reader.readLine()) != null) {
+                Matcher matcher = DEFINE_PATTERN.matcher(line);
+                if (!matcher.matches()) {
+                    return new BrokenResult(String.format("Could not determine GCC version: %s produced unexpected output.", gccBinary.getName()));
+                }
+                defines.put(matcher.group(1), matcher.group(2));
+            }
+        } catch (IOException e) {
+            // Should not happen reading from a StringReader
+            throw new UncheckedIOException(e);
+        }
+        if (!defines.containsKey("__GNUC__")) {
+            return new BrokenResult(String.format("Could not determine GCC version: %s produced unexpected output.", gccBinary.getName()));
+        }
+        if (defines.containsKey("__clang__")) {
+            return new BrokenResult(String.format("XCode %s is a wrapper around Clang. Treating it as Clang and not GCC.", gccBinary.getName()));
+        }
+        return new DefaultGccVersionResult(defines.get("__GNUC__"));
+    }
+
+    private static class DefaultGccVersionResult implements GccVersionResult {
+        private final String scrapedVersion;
+
+        public DefaultGccVersionResult(String scrapedVersion) {
+            this.scrapedVersion = scrapedVersion;
+        }
+
+        public String getVersion() {
+            return scrapedVersion;
+        }
+
+        public boolean isAvailable() {
+            return true;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+        }
+    }
+
+    static class GccVersionOutputProducer implements Transformer<String, File> {
+        
+        private final ExecActionFactory execActionFactory;
+
+        GccVersionOutputProducer(ExecActionFactory execActionFactory) {
+            this.execActionFactory = execActionFactory;
+        }
+
+        public String transform(File gccBinary) {
+            ExecAction exec = execActionFactory.newExecAction();
+            exec.executable(gccBinary.getAbsolutePath());
+            exec.setWorkingDir(gccBinary.getParentFile());
+            exec.args("-dM", "-E", "-");
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            exec.setStandardOutput(baos);
+            exec.setIgnoreExitValue(true);
+            ExecResult result = exec.execute();
+
+            int exitValue = result.getExitValue();
+            if (exitValue == 0) {
+                return new String(baos.toByteArray());
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private static class BrokenResult implements GccVersionResult {
+        private final String message;
+
+        private BrokenResult(String message) {
+            this.message = message;
+        }
+
+        public String getVersion() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isAvailable() {
+            return false;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+            visitor.node(message);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionResult.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionResult.java
new file mode 100644
index 0000000..e771c19
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc.version;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult;
+
+public interface GccVersionResult extends ToolSearchResult {
+    String getVersion();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/Assembler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/Assembler.java
new file mode 100755
index 0000000..b9587d9
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/Assembler.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.nativebinaries.language.assembler.internal.AssembleSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.nativebinaries.toolchain.internal.msvcpp.EscapeUserArgs.escapeUserArgs;
+
+class Assembler implements Compiler<AssembleSpec> {
+
+    private final CommandLineTool<AssembleSpec> commandLineTool;
+
+    public Assembler(CommandLineTool<AssembleSpec> commandLineTool) {
+        this.commandLineTool = commandLineTool;
+    }
+
+    public WorkResult execute(AssembleSpec spec) {
+        boolean didWork = false;
+        CommandLineTool<AssembleSpec> commandLineAssembler = commandLineTool.inWorkDirectory(spec.getObjectFileDir());
+        for (File sourceFile : spec.getSourceFiles()) {
+            WorkResult result = commandLineAssembler.withArguments(new AssemblerArgsTransformer(sourceFile)).execute(spec);
+            didWork = didWork || result.getDidWork();
+        }
+        return new SimpleWorkResult(didWork);
+    }
+
+
+    private static class AssemblerArgsTransformer implements ArgsTransformer<AssembleSpec> {
+        private final File inputFile;
+
+        public AssemblerArgsTransformer(File inputFile) {
+            this.inputFile = inputFile;
+        }
+
+        public List<String> transform(AssembleSpec spec) {
+            List<String> args = new ArrayList<String>();
+            args.addAll(escapeUserArgs(spec.getAllArgs()));
+            args.add("/nologo");
+            args.add("/c");
+            File outputFile = getOutputFilePath(spec);
+            if(!outputFile.getParentFile().exists()){
+                outputFile.getParentFile().mkdir();
+            }
+            args.add("/Fo"+ outputFile);
+            args.add(inputFile.getAbsolutePath());
+            return args;
+        }
+
+        public File getOutputFilePath(AssembleSpec spec) {
+            String compactMD5 = HashUtil.createCompactMD5(inputFile.getAbsolutePath());
+            File currentObjectOutputDir = new File(spec.getObjectFileDir(), compactMD5);
+            return new File(currentObjectOutputDir, FilenameUtils.removeExtension(inputFile.getName())+ ".obj");
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/CCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/CCompiler.java
new file mode 100755
index 0000000..7120bad
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/CCompiler.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.nativebinaries.language.c.internal.CCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+class CCompiler extends NativeCompiler<CCompileSpec> {
+
+    CCompiler(CommandLineTool<CCompileSpec> commandLineTool) {
+        super(commandLineTool, new CCompilerArgsTransformer());
+    }
+
+    private static class CCompilerArgsTransformer extends VisualCppCompilerArgsTransformer<CCompileSpec> {
+        protected String getLanguageOption() {
+            return "/TC";
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/CppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/CppCompiler.java
new file mode 100755
index 0000000..892c485
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/CppCompiler.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.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.nativebinaries.language.cpp.internal.CppCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+class CppCompiler extends NativeCompiler<CppCompileSpec> {
+
+    CppCompiler(CommandLineTool<CppCompileSpec> commandLineTool) {
+        super(commandLineTool, new CppCompilerArgsTransformer());
+    }
+
+    private static class CppCompilerArgsTransformer extends VisualCppCompilerArgsTransformer<CppCompileSpec> {
+        protected String getLanguageOption() {
+            return "/TP";
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultVisualStudioLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultVisualStudioLocator.java
new file mode 100644
index 0000000..0e088f5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultVisualStudioLocator.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import net.rubygrapefruit.platform.MissingRegistryEntryException;
+import net.rubygrapefruit.platform.SystemInfo;
+import net.rubygrapefruit.platform.WindowsRegistry;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.nativebinaries.platform.Architecture;
+import org.gradle.nativebinaries.platform.internal.ArchitectureNotationParser;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.TreeVisitor;
+import org.gradle.util.VersionNumber;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultVisualStudioLocator implements VisualStudioLocator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultVisualStudioLocator.class);
+    private static final String[] REGISTRY_BASEPATHS = {
+        "SOFTWARE\\",
+        "SOFTWARE\\Wow6432Node\\"
+    };
+    private static final String REGISTRY_ROOTPATH_VC = "Microsoft\\VisualStudio\\SxS\\VC7";
+    private static final String PATH_COMMON = "Common7/";
+    private static final String PATH_COMMONTOOLS = PATH_COMMON + "Tools/";
+    private static final String PATH_COMMONIDE = PATH_COMMON + "IDE/";
+    private static final String PATH_BIN = "bin/";
+    private static final String PATH_INCLUDE = "include/";
+    private static final String COMPILER_FILENAME = "cl.exe";
+
+    private static final String ARCHITECTURE_AMD64 = "amd64";
+    private static final String ARCHITECTURE_X86 = "x86";
+    private static final String ARCHITECTURE_ARM = "arm";
+    private static final String ARCHITECTURE_IA64 = "ia-64";
+    private static final String BINPATH_AMD64_AMD64 = "bin/amd64";
+    private static final String BINPATH_AMD64_ARM = "bin/amd64_arm";
+    private static final String BINPATH_AMD64_X86 = "bin/amd64_x86";
+    private static final String BINPATH_X86_AMD64 = "bin/x86_amd64";
+    private static final String BINPATH_X86_ARM = "bin/x86_arm";
+    private static final String BINPATH_X86_IA64 = "bin/x86_ia64";
+    private static final String BINPATH_X86_X86 = "bin";
+    private static final String LIBPATH_AMD64 = "lib/amd64";
+    private static final String LIBPATH_ARM = "lib/arm";
+    private static final String LIBPATH_IA64 = "lib/ia64";
+    private static final String LIBPATH_X86 = "lib";
+    private static final String ASSEMBLER_FILENAME_AMD64 = "ml64.exe";
+    private static final String ASSEMBLER_FILENAME_ARM = "armasm.exe";
+    private static final String ASSEMBLER_FILENAME_IA64 = "ias.exe";
+    private static final String ASSEMBLER_FILENAME_X86 = "ml.exe";
+    private static final String DEFINE_ARMPARTITIONAVAILABLE = "_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE";
+
+    private final Map<File, VisualStudioInstall> foundInstalls = new HashMap<File, VisualStudioInstall>();
+    private final OperatingSystem os;
+    private final WindowsRegistry windowsRegistry;
+    private final SystemInfo systemInfo;
+    private VisualStudioInstall pathInstall;
+    private boolean initialised;
+
+    public DefaultVisualStudioLocator(OperatingSystem os, WindowsRegistry windowsRegistry, SystemInfo systemInfo) {
+        this.os = os;
+        this.windowsRegistry = windowsRegistry;
+        this.systemInfo = systemInfo;
+    }
+
+    public SearchResult locateVisualStudioInstalls(File candidate) {
+        if (!initialised) {
+            locateInstallsInRegistry();
+            locateInstallInPath();
+            initialised = true;
+        }
+
+        if (candidate != null) {
+            return locateUserSpecifiedInstall(candidate);
+        }
+
+        return determineDefaultInstall();
+    }
+
+    private void locateInstallsInRegistry() {
+        for (String baseKey : REGISTRY_BASEPATHS) {
+            locateInstallsInRegistry(baseKey);
+        }
+    }
+
+    private void locateInstallsInRegistry(String baseKey) {
+        List<String> visualCppVersions;
+        try {
+            visualCppVersions = windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, baseKey + REGISTRY_ROOTPATH_VC);
+        } catch (MissingRegistryEntryException e) {
+            // No Visual Studio information available in the registry
+            return;
+        }
+
+        for (String valueName : visualCppVersions) {
+            if (!valueName.matches("\\d+\\.\\d+")) {
+                // Ignore the other values
+                continue;
+            }
+            File visualCppDir = new File(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, baseKey + REGISTRY_ROOTPATH_VC, valueName));
+            visualCppDir = GFileUtils.canonicalise(visualCppDir);
+            File visualStudioDir = visualCppDir.getParentFile();
+
+            if (isVisualCpp(visualCppDir) && isVisualStudio(visualStudioDir)) {
+                LOGGER.debug("Found Visual C++ {} at {}", valueName, visualCppDir);
+                VersionNumber version = VersionNumber.parse(valueName);
+                VisualCppInstall visualCpp = buildVisualCppInstall("Visual C++ " + valueName, visualStudioDir, visualCppDir, version);
+                VisualStudioInstall visualStudio = new VisualStudioInstall(visualStudioDir, visualCpp);
+                foundInstalls.put(visualStudioDir, visualStudio);
+            } else {
+                LOGGER.debug("Ignoring candidate Visual C++ directory {} as it does not look like a Visual C++ installation.", visualCppDir);
+            }
+        }
+    }
+
+    private void locateInstallInPath() {
+        File compilerInPath = os.findInPath(COMPILER_FILENAME);
+        if (compilerInPath == null) {
+            LOGGER.debug("No visual c++ compiler found in system path.");
+            return;
+        }
+
+        File visualCppDir = GFileUtils.canonicalise(compilerInPath.getParentFile().getParentFile());
+        if (!isVisualCpp(visualCppDir)) {
+            visualCppDir = visualCppDir.getParentFile();
+            if (!isVisualCpp(visualCppDir)) {
+                LOGGER.debug("Ignoring candidate Visual C++ install for {} as it does not look like a Visual C++ installation.", compilerInPath);
+                return;
+            }
+        }
+        LOGGER.debug("Found Visual C++ install {} using system path", visualCppDir);
+
+        File visualStudioDir = visualCppDir.getParentFile();
+        if (!foundInstalls.containsKey(visualStudioDir)) {
+            VisualCppInstall visualCpp = buildVisualCppInstall("Visual C++ from system path", visualStudioDir, visualCppDir, VersionNumber.UNKNOWN);
+            VisualStudioInstall visualStudio = new VisualStudioInstall(visualStudioDir, visualCpp);
+            foundInstalls.put(visualStudioDir, visualStudio);
+        }
+        pathInstall = foundInstalls.get(visualStudioDir);
+    }
+
+    private SearchResult locateUserSpecifiedInstall(File candidate) {
+        File visualStudioDir = GFileUtils.canonicalise(candidate);
+        File visualCppDir = new File(visualStudioDir, "VC");
+        if (!isVisualStudio(visualStudioDir) || !isVisualCpp(visualCppDir)) {
+            LOGGER.debug("Ignoring candidate Visual C++ install for {} as it does not look like a Visual C++ installation.", candidate);
+            return new InstallNotFound(String.format("The specified installation directory '%s' does not appear to contain a Visual Studio installation.", candidate));
+        }
+
+        if (!foundInstalls.containsKey(visualStudioDir)) {
+            VisualCppInstall visualCpp = buildVisualCppInstall("Visual C++ from user provided path", visualStudioDir, visualCppDir, VersionNumber.UNKNOWN);
+            VisualStudioInstall visualStudio = new VisualStudioInstall(visualStudioDir, visualCpp);
+            foundInstalls.put(visualStudioDir, visualStudio);
+        }
+        return new InstallFound(foundInstalls.get(visualStudioDir));
+    }
+
+    private VisualCppInstall buildVisualCppInstall(String name, File vsPath, File basePath, VersionNumber version) {
+        boolean isNativeAmd64 = systemInfo.getArchitecture() == SystemInfo.Architecture.amd64;
+        Map<Architecture, List<File>> paths = new HashMap<Architecture, List<File>>();
+        Map<Architecture, File> binaryPaths = new HashMap<Architecture, File>();
+        Map<Architecture, File> libraryPaths = new HashMap<Architecture, File>();
+        Map<Architecture, File> includePaths = new HashMap<Architecture, File>();
+        Map<Architecture, String> assemblerFilenames = new HashMap<Architecture, String>();
+        Map<Architecture, Map<String, String>> definitions = new HashMap<Architecture, Map<String, String>>();
+        NotationParser<Object, Architecture> architectureParser = ArchitectureNotationParser.parser();
+        Architecture amd64 = architectureParser.parseNotation(ARCHITECTURE_AMD64);
+        Architecture x86 = architectureParser.parseNotation(ARCHITECTURE_X86);
+        Architecture arm = architectureParser.parseNotation(ARCHITECTURE_ARM);
+        Architecture ia64 = architectureParser.parseNotation(ARCHITECTURE_IA64);
+        File includePath = new File(basePath, PATH_INCLUDE);
+        File commonTools = new File(vsPath, PATH_COMMONTOOLS);
+        File commonIde = new File(vsPath, PATH_COMMONIDE);
+
+        if (isNativeAmd64) {
+            Architecture[] architectures = {
+                amd64,
+                x86,
+                arm
+            };
+            String[] binPaths = {
+                BINPATH_AMD64_AMD64,
+                BINPATH_AMD64_X86,
+                BINPATH_AMD64_ARM
+            };
+            String[] libPaths = {
+                LIBPATH_AMD64,
+                LIBPATH_X86,
+                LIBPATH_ARM
+            };
+            String[] asmFilenames = {
+                ASSEMBLER_FILENAME_AMD64,
+                ASSEMBLER_FILENAME_X86,
+                ASSEMBLER_FILENAME_ARM
+            };
+
+            for (int i = 0; i != architectures.length; ++i) {
+                Architecture architecture = architectures[i];
+                File binPath = new File(basePath, binPaths[i]);
+                File libPath = new File(basePath, libPaths[i]);
+
+                if (binPath.isDirectory() && libPath.isDirectory()) {
+                    Map<String, String> definitionsList = new LinkedHashMap<String, String>();
+                    List<File> pathsList = new ArrayList<File>();
+
+                    pathsList.add(commonTools);
+                    pathsList.add(commonIde);
+
+                    // For cross-compilers, add the native compiler to the path as well
+                    if (architecture != amd64) {
+                        pathsList.add(new File(basePath, binPaths[0]));
+                    }
+
+                    if (architecture == arm) {
+                        definitionsList.put(DEFINE_ARMPARTITIONAVAILABLE, "1");
+                    }
+
+                    binaryPaths.put(architecture, binPath);
+                    libraryPaths.put(architecture, libPath);
+                    includePaths.put(architecture, includePath);
+                    assemblerFilenames.put(architecture, asmFilenames[i]);
+                    paths.put(architecture, pathsList);
+                    definitions.put(architecture, definitionsList);
+                }
+            }
+        }
+
+        Architecture[] architectures = {
+            x86,
+            amd64,
+            ia64,
+            arm
+        };
+        String[] binPaths = {
+            BINPATH_X86_X86,
+            BINPATH_X86_AMD64,
+            BINPATH_X86_IA64,
+            BINPATH_X86_ARM
+        };
+        String[] libPaths = {
+            LIBPATH_X86,
+            LIBPATH_AMD64,
+            LIBPATH_IA64,
+            LIBPATH_ARM
+        };
+        String[] asmFilenames = {
+            ASSEMBLER_FILENAME_X86,
+            ASSEMBLER_FILENAME_AMD64,
+            ASSEMBLER_FILENAME_IA64,
+            ASSEMBLER_FILENAME_ARM
+        };
+
+        for (int i = 0; i != architectures.length; ++i) {
+            Architecture architecture = architectures[i];
+
+            if (!binaryPaths.containsKey(architecture)) {
+                File binPath = new File(basePath, binPaths[i]);
+                File libPath = new File(basePath, libPaths[i]);
+    
+                if (binPath.isDirectory() && libPath.isDirectory()) {
+                    Map<String, String> definitionsList = new LinkedHashMap<String, String>();
+                    List<File> pathsList = new ArrayList<File>();
+
+                    pathsList.add(commonTools);
+                    pathsList.add(commonIde);
+
+                    // For cross-compilers, add the native compiler to the path as well
+                    if (architecture != x86) {
+                        pathsList.add(new File(basePath, binPaths[0]));
+                    }
+
+                    if (architecture == arm) {
+                        definitionsList.put(DEFINE_ARMPARTITIONAVAILABLE, "1");
+                    }
+
+                    binaryPaths.put(architecture, binPath);
+                    libraryPaths.put(architecture, libPath);
+                    includePaths.put(architecture, includePath);
+                    assemblerFilenames.put(architecture, asmFilenames[i]);
+                    paths.put(architecture, pathsList);
+                    definitions.put(architecture, definitionsList);
+                }
+            }
+        }
+
+        // TODO:MPUT - use x64 as the default architecture on x64 systems (same for winsdk)? (isNativeAmd64 && binaryPaths.containsKey(amd64)) ? amd64 : x86
+        return new VisualCppInstall(name, version, x86, paths, binaryPaths, libraryPaths, includePaths, assemblerFilenames, definitions);
+    }
+
+    private SearchResult determineDefaultInstall() {
+        if (pathInstall != null) {
+            return new InstallFound(pathInstall);
+        }
+
+        VisualStudioInstall candidate = null;
+
+        for (VisualStudioInstall visualStudio : foundInstalls.values()) {
+            if (candidate == null || visualStudio.getVersion().compareTo(candidate.getVersion()) > 0) {
+                candidate = visualStudio;
+            }
+        }
+
+        return candidate == null ? new InstallNotFound("Could not locate a Visual Studio installation, using the Windows registry and system path.") : new InstallFound(candidate);
+    }
+
+    private static boolean isVisualStudio(File candidate) {
+        return new File(candidate, PATH_COMMON).isDirectory() && isVisualCpp(new File(candidate, "VC"));
+    }
+
+    private static boolean isVisualCpp(File candidate) {
+        return new File(candidate, PATH_BIN + COMPILER_FILENAME).isFile();
+    }
+
+    private static class InstallFound implements SearchResult {
+        private final VisualStudioInstall install;
+
+        public InstallFound(VisualStudioInstall install) {
+            this.install = install;
+        }
+
+        public VisualStudioInstall getVisualStudio() {
+            return install;
+        }
+
+        public boolean isAvailable() {
+            return true;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+        }
+    }
+
+    private static class InstallNotFound implements SearchResult {
+        private final String message;
+
+        private InstallNotFound(String message) {
+            this.message = message;
+        }
+
+        public VisualStudioInstall getVisualStudio() {
+            return null;
+        }
+
+        public boolean isAvailable() {
+            return false;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+            visitor.node(message);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultWindowsSdkLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultWindowsSdkLocator.java
new file mode 100644
index 0000000..929bf49
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultWindowsSdkLocator.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import net.rubygrapefruit.platform.MissingRegistryEntryException;
+import net.rubygrapefruit.platform.WindowsRegistry;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.TreeVisitor;
+import org.gradle.util.VersionNumber;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DefaultWindowsSdkLocator implements WindowsSdkLocator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWindowsSdkLocator.class);
+    private static final String REGISTRY_BASEPATHS[] = {
+        "SOFTWARE\\",
+        "SOFTWARE\\Wow6432Node\\"
+    };
+    private static final String REGISTRY_ROOTPATH_SDK = "Microsoft\\Microsoft SDKs\\Windows";
+    private static final String REGISTRY_ROOTPATH_KIT = "Microsoft\\Windows Kits\\Installed Roots";
+    private static final String REGISTRY_FOLDER = "InstallationFolder";
+    private static final String REGISTRY_VERSION = "ProductVersion";
+    private static final String REGISTRY_NAME = "ProductName";
+    private static final String REGISTRY_KIT_8 = "KitsRoot";
+    private static final String REGISTRY_KIT_81 = "KitsRoot81";
+    private static final String VERSION_KIT_8 = "8.0";
+    private static final String VERSION_KIT_81 = "8.1";
+    private static final String VERSION_USER = "user";
+
+    private static final String NAME_USER = "User-provided Windows SDK";
+    private static final String NAME_KIT = "Windows Kit";
+
+    private static final String RESOURCE_PATHS[] = {
+        "bin/x86/",
+        "bin/"
+    };
+
+    private static final String KERNEL32_PATHS[] = {
+        "lib/winv6.3/um/x86/",
+        "lib/win8/um/x86/",
+        "lib/"
+    };
+
+    private static final String RESOURCE_FILENAME = "rc.exe";
+    private static final String KERNEL32_FILENAME = "kernel32.lib";
+
+    private final Map<File, WindowsSdk> foundSdks = new HashMap<File, WindowsSdk>();
+    private final OperatingSystem os;
+    private final WindowsRegistry windowsRegistry;
+    private WindowsSdk pathSdk;
+    private boolean initialised;
+
+    public DefaultWindowsSdkLocator(OperatingSystem os, WindowsRegistry windowsRegistry) {
+        this.os = os;
+        this.windowsRegistry = windowsRegistry;
+    }
+
+    public SearchResult locateWindowsSdks(File candidate) {
+        if (!initialised) {
+            locateSdksInRegistry();
+            locateKitsInRegistry();
+            locateSdkInPath();
+            initialised = true;
+        }
+
+        if (candidate != null) {
+            return locateUserSpecifiedSdk(candidate);
+        }
+
+        return locateDefaultSdk();
+    }
+
+    private void locateSdksInRegistry() {
+        for (String baseKey : REGISTRY_BASEPATHS) {
+            locateSdksInRegistry(baseKey);
+        }
+    }
+
+    private void locateSdksInRegistry(String baseKey) {
+        try {
+            List<String> subkeys = windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, baseKey + REGISTRY_ROOTPATH_SDK);
+            for (String subkey : subkeys) {
+                try {
+                    String basePath = baseKey + REGISTRY_ROOTPATH_SDK + "\\" + subkey;
+                    File sdkDir = GFileUtils.canonicalise(new File(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, basePath, REGISTRY_FOLDER)));
+                    String version = formatVersion(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, basePath, REGISTRY_VERSION));
+                    String name = windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, basePath, REGISTRY_NAME);
+
+                    if (isWindowsSdk(sdkDir)) {
+                        LOGGER.debug("Found Windows SDK {} at {}", version, sdkDir);
+                        addSdk(sdkDir, version, name);
+                    } else {
+                        LOGGER.debug("Ignoring candidate Windows SDK directory {} as it does not look like a Windows SDK installation.", sdkDir);
+                    }
+                } catch (MissingRegistryEntryException e) {
+                    // Ignore the subkey if it doesn't have a folder and version
+                }
+            }
+        } catch (MissingRegistryEntryException e) {
+            // No SDK information available in the registry
+        }
+    }
+
+    private void locateKitsInRegistry() {
+        for (String baseKey : REGISTRY_BASEPATHS) {
+            locateKitsInRegistry(baseKey);
+        }
+    }
+
+    private void locateKitsInRegistry(String baseKey) {
+        String[] versions = {
+                VERSION_KIT_8,
+                VERSION_KIT_81
+        };
+        String[] keys = {
+                REGISTRY_KIT_8,
+                REGISTRY_KIT_81
+        };
+
+        for (int i = 0; i != keys.length; ++i) {
+            try {
+                File kitDir = GFileUtils.canonicalise(new File(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, baseKey + REGISTRY_ROOTPATH_KIT, keys[i])));
+                if (isWindowsSdk(kitDir)) {
+                    LOGGER.debug("Found Windows Kit {} at {}", versions[i], kitDir);
+                    addSdk(kitDir, versions[i], NAME_KIT + " " + versions[i]);
+                } else {
+                    LOGGER.debug("Ignoring candidate Windows Kit directory {} as it does not look like a Windows Kit installation.", kitDir);
+                }
+            } catch (MissingRegistryEntryException e) {
+                // Ignore the version if the string cannot be read
+            }
+        }
+    }
+
+    private void locateSdkInPath() {
+        File resourceCompiler = os.findInPath(RESOURCE_FILENAME);
+        if (resourceCompiler == null) {
+            LOGGER.debug("Could not find Windows resource compiler in system path.");
+            return;
+        }
+        File sdkDir = GFileUtils.canonicalise(resourceCompiler.getParentFile().getParentFile());
+        if (!isWindowsSdk(sdkDir)) {
+            sdkDir = sdkDir.getParentFile();
+            if (!isWindowsSdk(sdkDir)) {
+                LOGGER.debug("Ignoring candidate Windows SDK for {} as it does not look like a Windows SDK installation.", resourceCompiler);
+            }
+        }
+        LOGGER.debug("Found Windows SDK {} using system path", sdkDir);
+
+        if (!foundSdks.containsKey(sdkDir)) {
+            addSdk(sdkDir, "path", "Path-resolved Windows SDK");
+        }
+        pathSdk = foundSdks.get(sdkDir);
+    }
+
+    private SearchResult locateUserSpecifiedSdk(File candidate) {
+        File sdkDir = GFileUtils.canonicalise(candidate);
+        if (!isWindowsSdk(sdkDir)) {
+            return new SdkNotFound(String.format("The specified installation directory '%s' does not appear to contain a Windows SDK installation.", candidate));
+        }
+
+        if (!foundSdks.containsKey(sdkDir)) {
+            addSdk(sdkDir, VERSION_USER, NAME_USER);
+        }
+        return new SdkFound(foundSdks.get(sdkDir));
+    }
+
+    private SearchResult locateDefaultSdk() {
+        if (pathSdk != null) {
+            return new SdkFound(pathSdk);
+        }
+
+        WindowsSdk candidate = null;
+        for (WindowsSdk windowsSdk : foundSdks.values()) {
+            if (candidate == null || windowsSdk.getVersion().compareTo(candidate.getVersion()) > 0) {
+                candidate = windowsSdk;
+            }
+        }
+        return candidate == null ? new SdkNotFound("Could not locate a Windows SDK installation, using the Windows registry and system path.") : new SdkFound(candidate);
+    }
+
+    private void addSdk(File path, String version, String name) {
+        foundSdks.put(path, new WindowsSdk(path, VersionNumber.parse(version), name));
+    }
+
+    private static boolean isWindowsSdk(File candidate) {
+        boolean hasResourceCompiler = false;
+        boolean hasKernel32Lib = false;
+
+        for (String path : RESOURCE_PATHS) {
+            if (new File(candidate, path + RESOURCE_FILENAME).isFile()) {
+                hasResourceCompiler = true;
+                break;
+            }
+        }
+
+        for (String path : KERNEL32_PATHS) {
+            if (new File(candidate, path + KERNEL32_FILENAME).isFile()) {
+                hasKernel32Lib = true;
+                break;
+            }
+        }
+
+        return hasResourceCompiler && hasKernel32Lib;
+    }
+
+    private static String formatVersion(String version) {
+        int index = StringUtils.ordinalIndexOf(version, ".", 2);
+
+        if (index != -1) {
+            version = version.substring(0, index);
+        }
+
+        return version;
+    }
+
+    private static class SdkFound implements SearchResult {
+        private final WindowsSdk sdk;
+
+        public SdkFound(WindowsSdk sdk) {
+            this.sdk = sdk;
+        }
+
+        public WindowsSdk getSdk() {
+            return sdk;
+        }
+
+        public boolean isAvailable() {
+            return true;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+        }
+    }
+
+    private static class SdkNotFound implements SearchResult {
+        private final String message;
+
+        private SdkNotFound(String message) {
+            this.message = message;
+        }
+
+        public WindowsSdk getSdk() {
+            return null;
+        }
+
+        public boolean isAvailable() {
+            return false;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+            visitor.node(message);
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/EscapeUserArgs.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/EscapeUserArgs.java
new file mode 100644
index 0000000..238d244
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/EscapeUserArgs.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.api.Transformer;
+import org.gradle.util.CollectionUtils;
+
+import java.util.List;
+
+class EscapeUserArgs implements Transformer<String, String> {
+    public static String escapeUserArg(String original) {
+        return new EscapeUserArgs().transform(original);
+    }
+
+    public static List<String> escapeUserArgs(List<String> original) {
+        return new EscapeUserArgs().transform(original);
+    }
+
+    public String transform(String original) {
+        return original.replace("\\", "\\\\").replace("\"", "\\\"");
+    }
+
+    public List<String> transform(List<String> args) {
+        return CollectionUtils.collect(args, this);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/InstallationSearchResult.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/InstallationSearchResult.java
new file mode 100644
index 0000000..3dfcae0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/InstallationSearchResult.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult;
+
+import java.io.File;
+
+public interface InstallationSearchResult extends ToolSearchResult {
+    String getVersion();
+
+    File getResult();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/LibExeStaticLibraryArchiver.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/LibExeStaticLibraryArchiver.java
new file mode 100755
index 0000000..2b6d38f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/LibExeStaticLibraryArchiver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.api.internal.tasks.compile.ArgWriter;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.nativebinaries.internal.StaticLibraryArchiverSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.OptionsFileArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.nativebinaries.toolchain.internal.msvcpp.EscapeUserArgs.escapeUserArgs;
+
+class LibExeStaticLibraryArchiver implements Compiler<StaticLibraryArchiverSpec> {
+    private final CommandLineTool<StaticLibraryArchiverSpec> commandLineTool;
+
+    public LibExeStaticLibraryArchiver(CommandLineTool<StaticLibraryArchiverSpec> commandLineTool) {
+        this.commandLineTool = commandLineTool
+                .withArguments(new OptionsFileArgsTransformer<StaticLibraryArchiverSpec>(ArgWriter.windowsStyleFactory(), new LibExeSpecToArguments()
+        ));
+    }
+
+    public WorkResult execute(StaticLibraryArchiverSpec spec) {
+        return commandLineTool.execute(spec);
+    }
+
+    private static class LibExeSpecToArguments implements ArgsTransformer<StaticLibraryArchiverSpec> {
+        public List<String> transform(StaticLibraryArchiverSpec spec) {
+            List<String> args = new ArrayList<String>();
+            args.add("/OUT:" + spec.getOutputFile().getAbsolutePath());
+            args.add("/NOLOGO");
+            args.addAll(escapeUserArgs(spec.getAllArgs()));
+            for (File file : spec.getObjectFiles()) {
+                args.add(file.getAbsolutePath());
+            }
+            return args;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/LinkExeLinker.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/LinkExeLinker.java
new file mode 100755
index 0000000..4f9bb4a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/LinkExeLinker.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.api.internal.tasks.compile.ArgWriter;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.nativebinaries.internal.LinkerSpec;
+import org.gradle.nativebinaries.internal.SharedLibraryLinkerSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.OptionsFileArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.nativebinaries.toolchain.internal.msvcpp.EscapeUserArgs.escapeUserArgs;
+
+class LinkExeLinker implements Compiler<LinkerSpec> {
+
+    private final CommandLineTool<LinkerSpec> commandLineTool;
+
+    public LinkExeLinker(CommandLineTool<LinkerSpec> commandLineTool) {
+        this.commandLineTool = commandLineTool
+                .withArguments(new OptionsFileArgsTransformer<LinkerSpec>(
+                ArgWriter.windowsStyleFactory(), new LinkerArgsTransformer()
+        ));
+    }
+
+    public WorkResult execute(LinkerSpec spec) {
+        return commandLineTool.execute(spec);
+    }
+
+    private static class LinkerArgsTransformer implements ArgsTransformer<LinkerSpec> {
+        public List<String> transform(LinkerSpec spec) {
+            List<String> args = new ArrayList<String>();
+            args.addAll(escapeUserArgs(spec.getAllArgs()));
+            args.add("/OUT:" + spec.getOutputFile().getAbsolutePath());
+            args.add("/NOLOGO");
+            if (spec instanceof SharedLibraryLinkerSpec) {
+                args.add("/DLL");
+            }
+            for (File pathEntry : spec.getLibraryPath()) {
+                args.add("/LIBPATH:" + pathEntry.getAbsolutePath());
+            }
+            for (File file : spec.getObjectFiles()) {
+                args.add(file.getAbsolutePath());
+            }
+            for (File file : spec.getLibraries()) {
+                args.add(file.getAbsolutePath());
+            }
+            return args;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/NativeCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/NativeCompiler.java
new file mode 100644
index 0000000..819a933
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/NativeCompiler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.ArgWriter;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.OptionsFileArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.SingleSourceCompileArgTransformer;
+import org.gradle.nativebinaries.toolchain.internal.gcc.ShortCircuitArgsTransformer;
+
+import java.io.File;
+
+abstract public class NativeCompiler<T extends NativeCompileSpec> implements org.gradle.api.internal.tasks.compile.Compiler<T> {
+
+    private final CommandLineTool<T> commandLineTool;
+    private final OptionsFileArgsTransformer<T> argsTransFormer;
+
+    NativeCompiler(CommandLineTool<T> commandLineTool, ArgsTransformer<T> argsTransFormer) {
+        this.argsTransFormer = new OptionsFileArgsTransformer<T>(
+                        ArgWriter.windowsStyleFactory(),
+                        argsTransFormer);
+        this.commandLineTool = commandLineTool;
+    }
+
+    public WorkResult execute(T spec) {
+        boolean didWork = false;
+        for (File sourceFile : spec.getSourceFiles()) {
+            String objectFileName = FilenameUtils.removeExtension(sourceFile.getName()) + ".obj";
+            WorkResult result = commandLineTool.inWorkDirectory(spec.getObjectFileDir())
+                    .withArguments(new SingleSourceCompileArgTransformer<T>(sourceFile,
+                                                                            objectFileName,
+                                                                            new ShortCircuitArgsTransformer<T>(argsTransFormer),
+                                                                            true,
+                                                                            true))
+                    .execute(spec);
+            didWork = didWork || result.getDidWork();
+        }
+        return new SimpleWorkResult(didWork);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppCompilerArgsTransformer.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppCompilerArgsTransformer.java
new file mode 100644
index 0000000..2871dc2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppCompilerArgsTransformer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.MacroArgsConverter;
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.nativebinaries.toolchain.internal.msvcpp.EscapeUserArgs.escapeUserArg;
+import static org.gradle.nativebinaries.toolchain.internal.msvcpp.EscapeUserArgs.escapeUserArgs;
+
+abstract class VisualCppCompilerArgsTransformer<T extends NativeCompileSpec> implements ArgsTransformer<T> {
+    public List<String> transform(T spec) {
+        List<String> args = new ArrayList<String>();
+        args.add(getLanguageOption());
+        args.add("/nologo");
+
+        for (String macroArg : new MacroArgsConverter().transform(spec.getMacros())) {
+            args.add(escapeUserArg("/D" + macroArg));
+        }
+        args.addAll(escapeUserArgs(spec.getAllArgs()));
+
+        args.add("/c");
+        for (File file : spec.getIncludeRoots()) {
+            args.add("/I" + file.getAbsolutePath());
+        }
+
+        return args;
+    }
+
+    protected abstract String getLanguageOption();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppInstall.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppInstall.java
new file mode 100644
index 0000000..d6c1e00
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppInstall.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import org.gradle.api.Named;
+import org.gradle.nativebinaries.platform.Architecture;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal;
+import org.gradle.util.VersionNumber;
+
+public class VisualCppInstall implements Named {
+    private static final String COMPILER_FILENAME = "cl.exe";
+    private static final String LINKER_FILENAME = "link.exe";
+    private static final String ARCHIVER_FILENAME = "lib.exe";
+
+    private final Map<Architecture, List<File>> paths;
+    private final Map<Architecture, File> binaryPaths;
+    private final Map<Architecture, File> libraryPaths;
+    private final Map<Architecture, File> includePaths;
+    private final Map<Architecture, String> assemblerFilenames;
+    private final Map<Architecture, Map<String, String>> definitions;
+    private final Architecture defaultArchitecture;
+    private final String name;
+    private final VersionNumber version;
+
+    public VisualCppInstall(String name, VersionNumber version, Architecture defaultArchitecture,
+            Map<Architecture, List<File>> paths, Map<Architecture, File> binaryPaths, Map<Architecture, File> libraryPaths,
+            Map<Architecture, File> includePaths, Map<Architecture, String> assemblerFilenames,
+            Map<Architecture, Map<String, String>> definitions) {
+        this.paths = paths;
+        this.name = name;
+        this.version = version;
+        this.defaultArchitecture = defaultArchitecture;
+        this.binaryPaths = binaryPaths;
+        this.libraryPaths = libraryPaths;
+        this.includePaths = includePaths;
+        this.assemblerFilenames = assemblerFilenames;
+        this.definitions = definitions;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public VersionNumber getVersion() {
+        return version;
+    }
+
+    public boolean isSupportedPlatform(Platform targetPlatform) {
+        // TODO:ADAM - ARM only if the target OS is Windows 8 or later
+        // TODO:MPUT - ARM also if the target OS is Windows RT or Windows Phone/Mobile/CE
+        // TODO:ADAM - IA64 only if the target OS is Windows 2008 or earlier
+        return targetPlatform.getOperatingSystem().isWindows()
+                && (binaryPaths.containsKey(getPlatformArchitecture(targetPlatform)));
+    }
+
+    public List<File> getPath(Platform targetPlatform) {
+        return paths.get(getPlatformArchitecture(targetPlatform));
+    }
+
+    public File getCompiler(Platform targetPlatform) {
+        return new File(binaryPaths.get(getPlatformArchitecture(targetPlatform)), COMPILER_FILENAME);
+    }
+
+    public File getLinker(Platform targetPlatform) {
+        return new File(binaryPaths.get(getPlatformArchitecture(targetPlatform)), LINKER_FILENAME);
+    }
+
+    public File getArchiver(Platform targetPlatform) {
+        return new File(binaryPaths.get(getPlatformArchitecture(targetPlatform)), ARCHIVER_FILENAME);
+    }
+
+    public File getAssembler(Platform targetPlatform) {
+        Architecture architecture = getPlatformArchitecture(targetPlatform);
+        return new File(binaryPaths.get(architecture), assemblerFilenames.get(architecture));
+    }
+
+    public File getBinaryPath(Platform targetPlatform) {
+        return binaryPaths.get(getPlatformArchitecture(targetPlatform));
+    }
+
+    public File getLibraryPath(Platform targetPlatform) {
+        return libraryPaths.get(getPlatformArchitecture(targetPlatform));
+    }
+
+    public Map<String, String> getDefinitions(Platform targetPlatform) {
+        return definitions.get(getPlatformArchitecture(targetPlatform));
+    }
+
+    public File getIncludePath(Platform targetPlatform) {
+        return includePaths.get(getPlatformArchitecture(targetPlatform));
+    }
+
+    private Architecture getPlatformArchitecture(Platform targetPlatform) {
+        ArchitectureInternal architecture = (ArchitectureInternal) targetPlatform.getArchitecture();
+        return (architecture == ArchitectureInternal.TOOL_CHAIN_DEFAULT) ? defaultArchitecture : architecture;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppToolChain.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppToolChain.java
new file mode 100755
index 0000000..fe70d8d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppToolChain.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.internal.BinaryToolSpec;
+import org.gradle.nativebinaries.internal.LinkerSpec;
+import org.gradle.nativebinaries.internal.StaticLibraryArchiverSpec;
+import org.gradle.nativebinaries.language.assembler.internal.AssembleSpec;
+import org.gradle.nativebinaries.language.c.internal.CCompileSpec;
+import org.gradle.nativebinaries.language.cpp.internal.CppCompileSpec;
+import org.gradle.nativebinaries.language.rc.internal.WindowsResourceCompileSpec;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.toolchain.VisualCpp;
+import org.gradle.nativebinaries.toolchain.internal.*;
+import org.gradle.process.internal.ExecActionFactory;
+import org.gradle.util.TreeVisitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class VisualCppToolChain extends AbstractToolChain implements VisualCpp {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(VisualCppToolChain.class);
+
+    public static final String DEFAULT_NAME = "visualCpp";
+
+    private final ExecActionFactory execActionFactory;
+    private final VisualStudioLocator visualStudioLocator;
+    private final WindowsSdkLocator windowsSdkLocator;
+    private File installDir;
+    private File windowsSdkDir;
+    private VisualCppInstall visualCpp;
+    private WindowsSdk windowsSdk;
+    private ToolChainAvailability availability;
+
+    public VisualCppToolChain(String name, OperatingSystem operatingSystem, FileResolver fileResolver, ExecActionFactory execActionFactory,
+                              VisualStudioLocator visualStudioLocator, WindowsSdkLocator windowsSdkLocator) {
+        super(name, operatingSystem, fileResolver);
+        this.execActionFactory = execActionFactory;
+        this.visualStudioLocator = visualStudioLocator;
+        this.windowsSdkLocator = windowsSdkLocator;
+    }
+
+    @Override
+    protected String getTypeName() {
+        return "Visual Studio";
+    }
+
+    private void checkAvailable(ToolChainAvailability availability) {
+        if (!operatingSystem.isWindows()) {
+            availability.unavailable("Visual Studio is not available on this operating system.");
+            return;
+        }
+        VisualStudioLocator.SearchResult visualStudioSearchResult = visualStudioLocator.locateVisualStudioInstalls(installDir);
+        availability.mustBeAvailable(visualStudioSearchResult);
+        if (visualStudioSearchResult.isAvailable()) {
+            visualCpp = visualStudioSearchResult.getVisualStudio().getVisualCpp();
+        }
+        WindowsSdkLocator.SearchResult windowsSdkSearchResult = windowsSdkLocator.locateWindowsSdks(windowsSdkDir);
+        availability.mustBeAvailable(windowsSdkSearchResult);
+        if (windowsSdkSearchResult.isAvailable()) {
+            windowsSdk = windowsSdkSearchResult.getSdk();
+        }
+    }
+
+    public File getInstallDir() {
+        return installDir;
+    }
+
+    public void setInstallDir(Object installDirPath) {
+        this.installDir = resolve(installDirPath);
+    }
+
+    public File getWindowsSdkDir() {
+        return windowsSdkDir;
+    }
+
+    public void setWindowsSdkDir(Object windowsSdkDirPath) {
+        this.windowsSdkDir = resolve(windowsSdkDirPath);
+    }
+
+    private ToolChainAvailability getAvailability() {
+        if (availability == null) {
+            availability = new ToolChainAvailability();
+            checkAvailable(availability);
+        }
+        return availability;
+    }
+
+    public PlatformToolChain target(Platform targetPlatform) {
+        ToolChainAvailability result = new ToolChainAvailability();
+        result.mustBeAvailable(getAvailability());
+        if (visualCpp != null && !visualCpp.isSupportedPlatform(targetPlatform)) {
+            result.unavailable(String.format("Don't know how to build for platform '%s'.", targetPlatform.getName()));
+        }
+        if (!result.isAvailable()) {
+            return new UnavailablePlatformToolChain(result);
+        }
+        return new VisualCppPlatformToolChain(visualCpp, windowsSdk, targetPlatform);
+    }
+
+    @Override
+    public String getSharedLibraryLinkFileName(String libraryName) {
+        return getSharedLibraryName(libraryName).replaceFirst("\\.dll$", ".lib");
+    }
+
+    private class VisualCppPlatformToolChain implements PlatformToolChain {
+        private final WindowsSdk sdk;
+        private final Platform targetPlatform;
+        private final VisualCppInstall visualCpp;
+
+        private VisualCppPlatformToolChain(VisualCppInstall visualCpp, WindowsSdk sdk, Platform targetPlatform) {
+            this.visualCpp = visualCpp;
+            this.sdk = sdk;
+            this.targetPlatform = targetPlatform;
+        }
+
+        public boolean isAvailable() {
+            return true;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+        }
+
+        public <T extends BinaryToolSpec> Compiler<T> createCppCompiler() {
+            CommandLineTool<CppCompileSpec> commandLineTool = commandLineTool("C++ compiler", visualCpp.getCompiler(targetPlatform));
+            Transformer<CppCompileSpec, CppCompileSpec> specTransformer = addIncludePathAndDefinitions();
+            commandLineTool.withSpecTransformer(specTransformer);
+            CppCompiler cppCompiler = new CppCompiler(commandLineTool);
+            return (Compiler<T>) new OutputCleaningCompiler<CppCompileSpec>(cppCompiler, ".obj");
+        }
+
+        public <T extends BinaryToolSpec> Compiler<T> createCCompiler() {
+            CommandLineTool<CCompileSpec> commandLineTool = commandLineTool("C compiler", visualCpp.getCompiler(targetPlatform));
+            Transformer<CCompileSpec, CCompileSpec> specTransformer = addIncludePathAndDefinitions();
+            commandLineTool.withSpecTransformer(specTransformer);
+            CCompiler cCompiler = new CCompiler(commandLineTool);
+            return (Compiler<T>) new OutputCleaningCompiler<CCompileSpec>(cCompiler, ".obj");
+        }
+
+        public <T extends BinaryToolSpec> Compiler<T> createObjectiveCppCompiler() {
+            throw new RuntimeException("Objective-C++ is not available on the Visual C++ toolchain");
+        }
+
+        public <T extends BinaryToolSpec> Compiler<T> createObjectiveCCompiler() {
+            throw new RuntimeException("Objective-C is not available on the Visual C++ toolchain");
+        }
+
+        public <T extends BinaryToolSpec> Compiler<T> createAssembler() {
+            CommandLineTool<AssembleSpec> commandLineTool = commandLineTool("Assembler", visualCpp.getAssembler(targetPlatform));
+            return (Compiler<T>) new Assembler(commandLineTool);
+        }
+
+        public <T extends BinaryToolSpec> Compiler<T> createWindowsResourceCompiler() {
+            CommandLineTool<WindowsResourceCompileSpec> commandLineTool = commandLineTool("Windows resource compiler", sdk.getResourceCompiler(targetPlatform));
+            Transformer<WindowsResourceCompileSpec, WindowsResourceCompileSpec> specTransformer = addIncludePathAndDefinitions();
+            commandLineTool.withSpecTransformer(specTransformer);
+            WindowsResourceCompiler windowsResourceCompiler = new WindowsResourceCompiler(commandLineTool);
+            return (Compiler<T>) new OutputCleaningCompiler<WindowsResourceCompileSpec>(windowsResourceCompiler, ".res");
+        }
+
+        public <T extends LinkerSpec> Compiler<T> createLinker() {
+            CommandLineTool<LinkerSpec> commandLineTool = commandLineTool("Linker", visualCpp.getLinker(targetPlatform));
+            commandLineTool.withSpecTransformer(addLibraryPath());
+            return (Compiler<T>) new LinkExeLinker(commandLineTool);
+        }
+
+        public <T extends StaticLibraryArchiverSpec> Compiler<T> createStaticLibraryArchiver() {
+            CommandLineTool<StaticLibraryArchiverSpec> commandLineTool = commandLineTool("Static library archiver", visualCpp.getArchiver(targetPlatform));
+            return (Compiler<T>) new LibExeStaticLibraryArchiver(commandLineTool);
+        }
+
+        private <T extends BinaryToolSpec> CommandLineTool<T> commandLineTool(String toolName, File exe) {
+            CommandLineTool<T> tool = new CommandLineTool<T>(toolName, exe, execActionFactory);
+
+            // The visual C++ tools use the path to find other executables
+            // TODO:ADAM - restrict this to the specific path for the target tool
+            tool.withPath(visualCpp.getPath(targetPlatform));
+            tool.withPath(sdk.getBinDir(targetPlatform));
+
+            // Clear environment variables that might effect cl.exe & link.exe
+            clearEnvironmentVars(tool, "INCLUDE", "CL", "LIBPATH", "LINK", "LIB");
+            return tool;
+        }
+
+        private <T extends BinaryToolSpec> void clearEnvironmentVars(CommandLineTool<T> tool, String... names) {
+            Map<String, ?> environmentVariables = Jvm.current().getInheritableEnvironmentVariables(System.getenv());
+            for (String name : names) {
+                Object value = environmentVariables.get(name);
+                if (value != null) {
+                    LOGGER.warn("Ignoring value '{}' set for environment variable '{}'.", value, name);
+                    tool.withEnvironmentVar(name, "");
+                }
+            }
+        }
+
+        public String getOutputType() {
+            return String.format("%s-%s", getName(), operatingSystem.getName());
+        }
+
+        private <T extends NativeCompileSpec> Transformer<T, T> addIncludePathAndDefinitions() {
+            return new Transformer<T, T>() {
+                public T transform(T original) {
+                    original.include(visualCpp.getIncludePath(targetPlatform));
+                    original.include(sdk.getIncludeDirs());
+                    for (Entry<String, String> definition : visualCpp.getDefinitions(targetPlatform).entrySet()) {
+                        original.define(definition.getKey(), definition.getValue());
+                    }
+                    return original;
+                }
+            };
+        }
+
+        private Transformer<LinkerSpec, LinkerSpec> addLibraryPath() {
+            return new Transformer<LinkerSpec, LinkerSpec>() {
+                public LinkerSpec transform(LinkerSpec original) {
+                    original.libraryPath(visualCpp.getLibraryPath(targetPlatform), sdk.getLibDir(targetPlatform));
+                    return original;
+                }
+            };
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualStudioInstall.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualStudioInstall.java
new file mode 100644
index 0000000..ac59ddd
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualStudioInstall.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.api.Named;
+import org.gradle.util.VersionNumber;
+
+import java.io.File;
+
+public class VisualStudioInstall implements Named {
+    private final VisualCppInstall visualCppInstall;
+    private final File baseDir;
+
+    public VisualStudioInstall(File baseDir,  VisualCppInstall visualCppInstall) {
+        this.baseDir = baseDir;
+        this.visualCppInstall = visualCppInstall;
+    }
+
+    public String getName() {
+        return visualCppInstall.getName();
+    }
+
+    public VersionNumber getVersion() {
+        return visualCppInstall.getVersion();
+    }
+
+    public File getVisualStudioDir() {
+        return baseDir;
+    }
+
+    public VisualCppInstall getVisualCpp() {
+        return visualCppInstall;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualStudioLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualStudioLocator.java
new file mode 100644
index 0000000..e7ad130
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualStudioLocator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult;
+
+import java.io.File;
+
+public interface VisualStudioLocator {
+
+    SearchResult locateVisualStudioInstalls(File candidate);
+
+    interface SearchResult extends ToolSearchResult {
+        VisualStudioInstall getVisualStudio();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsResourceCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsResourceCompiler.java
new file mode 100644
index 0000000..93adae2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsResourceCompiler.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.FileUtils;
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.language.rc.internal.WindowsResourceCompileSpec;
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer;
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool;
+import org.gradle.nativebinaries.toolchain.internal.MacroArgsConverter;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WindowsResourceCompiler implements Compiler<WindowsResourceCompileSpec> {
+
+    private final CommandLineTool<WindowsResourceCompileSpec> commandLineTool;
+
+    WindowsResourceCompiler(CommandLineTool<WindowsResourceCompileSpec> commandLineTool) {
+        this.commandLineTool = commandLineTool;
+    }
+
+    public WorkResult execute(WindowsResourceCompileSpec spec) {
+        boolean didWork = false;
+        boolean windowsPathLimitation = OperatingSystem.current().isWindows();
+        CommandLineTool<WindowsResourceCompileSpec> commandLineAssembler = commandLineTool.inWorkDirectory(spec.getObjectFileDir());
+        for (File sourceFile : spec.getSourceFiles()) {
+            WorkResult result = commandLineAssembler.withArguments(new RcCompilerArgsTransformer(sourceFile, windowsPathLimitation)).execute(spec);
+            didWork |= result.getDidWork();
+        }
+        return new SimpleWorkResult(didWork);
+    }
+
+    private static class RcCompilerArgsTransformer implements ArgsTransformer<WindowsResourceCompileSpec> {
+        private final File inputFile;
+        private boolean windowsPathLengthLimitation;
+
+        public RcCompilerArgsTransformer(File inputFile, boolean windowsPathLengthLimitation) {
+            this.inputFile = inputFile;
+            this.windowsPathLengthLimitation = windowsPathLengthLimitation;
+        }
+
+        public List<String> transform(WindowsResourceCompileSpec spec) {
+            List<String> args = new ArrayList<String>();
+            args.add("/nologo");
+            args.add("/fo");
+            args.add(getOutputFile(spec).getAbsolutePath());
+            for (String macroArg : new MacroArgsConverter().transform(spec.getMacros())) {
+                args.add("/D" + macroArg);
+            }
+            args.addAll(spec.getAllArgs());
+            for (File file : spec.getIncludeRoots()) {
+                args.add("/I" + file.getAbsolutePath());
+            }
+            args.add(inputFile.getAbsolutePath());
+
+            return args;
+        }
+
+        private File getOutputFile(WindowsResourceCompileSpec spec) {
+            String outputFileName = FilenameUtils.getBaseName(inputFile.getName()) + ".res";
+            String compactMD5 = HashUtil.createCompactMD5(inputFile.getAbsolutePath());
+            File outputDirectory = new File(spec.getObjectFileDir(), compactMD5);
+            if(!outputDirectory.exists()){
+                outputDirectory.mkdir();
+            }
+            File outputFile = new File(outputDirectory, outputFileName);
+            return windowsPathLengthLimitation ? FileUtils.assertInWindowsPathLengthLimitation(outputFile) : outputFile;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsSdk.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsSdk.java
new file mode 100644
index 0000000..b5ba454
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsSdk.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.api.Named;
+import org.gradle.nativebinaries.platform.Platform;
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal;
+import org.gradle.util.VersionNumber;
+
+import java.io.File;
+
+public class WindowsSdk implements Named {
+    private static final String[] BINPATHS_X86 = {
+        "bin/x86",
+        "Bin"
+    };
+    private static final String[] BINPATHS_AMD64 = {
+        "bin/x64"
+    };
+    private static final String[] BINPATHS_IA64 = {
+        "bin/IA64"
+    };
+    private static final String[] BINPATHS_ARM = {
+        "bin/arm"
+    };
+    private static final String LIBPATH_SDK8 = "Lib/win8/";
+    private static final String LIBPATH_SDK81 = "Lib/winv6.3/um/";
+    private static final String[] LIBPATHS_X86 = {
+        LIBPATH_SDK81 + "x86",
+        LIBPATH_SDK8 + "x86",
+        "lib"
+    };
+    private static final String[] LIBPATHS_AMD64 = {
+        LIBPATH_SDK81 + "x64",
+        LIBPATH_SDK8 + "x64",
+        "lib/x64"
+    };
+    private static final String[] LIBPATHS_IA64 = {
+        "lib/IA64"
+    };
+    private static final String[] LIBPATHS_ARM = {
+        LIBPATH_SDK81 + "arm",
+        LIBPATH_SDK8 + "arm"
+    };
+
+    private final File baseDir;
+    private final VersionNumber version;
+    private final String name;
+
+    public WindowsSdk(File baseDir, VersionNumber version, String name) {
+        this.baseDir = baseDir;
+        this.version = version;
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public VersionNumber getVersion() {
+        return version;
+    }
+
+    public File getResourceCompiler(Platform platform) {
+        return new File(getBinDir(platform), "rc.exe");
+    }
+
+    public File getBinDir(Platform platform) {
+        if (architecture(platform).isAmd64()) {
+            return getAvailableFile(BINPATHS_AMD64);
+        }
+        if (architecture(platform).isIa64()) {
+            return getAvailableFile(BINPATHS_IA64);
+        }
+        if (architecture(platform).isArm()) {
+            return getAvailableFile(BINPATHS_ARM);
+        }
+        return getAvailableFile(BINPATHS_X86);
+    }
+
+    public File[] getIncludeDirs() {
+        File[] includesSdk8 = new File[] {
+            new File(baseDir, "Include/shared"),
+            new File(baseDir, "Include/um")
+        };
+        for (File file : includesSdk8) {
+            if (!file.isDirectory()) {
+                return new File[] {
+                    new File(baseDir, "Include")
+                };
+            }
+        }
+        return includesSdk8;
+    }
+
+    public File getLibDir(Platform platform) {
+        if (architecture(platform).isAmd64()) {
+            return getAvailableFile(LIBPATHS_AMD64);
+        }
+        if (architecture(platform).isIa64()) {
+            return getAvailableFile(LIBPATHS_IA64);
+        }
+        if (architecture(platform).isArm()) {
+            return getAvailableFile(LIBPATHS_ARM);
+        }
+        return getAvailableFile(LIBPATHS_X86);
+    }
+
+    private ArchitectureInternal architecture(Platform platform) {
+        return (ArchitectureInternal) platform.getArchitecture();
+    }
+
+    private File getAvailableFile(String... candidates) {
+        for (String candidate : candidates) {
+            File file = new File(baseDir, candidate);
+            if (file.isDirectory()) {
+                return file;
+            }
+        }
+
+        return new File(baseDir, candidates[0]);
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsSdkLocator.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsSdkLocator.java
new file mode 100644
index 0000000..9a223aa
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/WindowsSdkLocator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.msvcpp;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult;
+
+import java.io.File;
+
+public interface WindowsSdkLocator {
+
+    SearchResult locateWindowsSdks(File candidate);
+
+    interface SearchResult extends ToolSearchResult {
+        WindowsSdk getSdk();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/plugins/StandardToolChainsPlugin.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/plugins/StandardToolChainsPlugin.java
new file mode 100644
index 0000000..157e2b3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/plugins/StandardToolChainsPlugin.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.nativebinaries.toolchain.plugins.ClangCompilerPlugin;
+import org.gradle.nativebinaries.toolchain.plugins.GccCompilerPlugin;
+import org.gradle.nativebinaries.toolchain.plugins.MicrosoftVisualCppPlugin;
+
+/**
+ * Registers the standard tool chains.
+ */
+public class StandardToolChainsPlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        project.getPlugins().apply(MicrosoftVisualCppPlugin.class);
+        project.getPlugins().apply(GccCompilerPlugin.class);
+        project.getPlugins().apply(ClangCompilerPlugin.class);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/DefaultTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/DefaultTool.java
new file mode 100644
index 0000000..e2feb02
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/DefaultTool.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.internal.Actions;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class DefaultTool implements GccToolInternal {
+    private final ToolType toolType;
+    private String executable;
+    private List<Action<? super List<String>>> argActions = new ArrayList<Action<? super List<String>>>();
+
+    public DefaultTool(ToolType toolType, String defaultExecutable) {
+        this.toolType = toolType;
+        this.executable = defaultExecutable;
+    }
+
+    public ToolType getToolType() {
+        return toolType;
+    }
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public void setExecutable(String file) {
+        executable = file;
+    }
+
+    // TODO:DAZ Decorate class and use an action parameter
+    public void withArguments(Closure arguments) {
+        Action<List<String>> action = new ClosureBackedAction<List<String>>(arguments);
+        argActions.add(action);
+    }
+
+    public Action<List<String>> getArgAction() {
+        return Actions.composite(argActions);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/DefaultToolRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/DefaultToolRegistry.java
new file mode 100644
index 0000000..7e2f7ad
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/DefaultToolRegistry.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultToolRegistry implements ToolRegistry {
+    private final Map<ToolType, GccToolInternal> gccTools = new HashMap<ToolType, GccToolInternal>();
+
+    // TODO:DAZ Should not need default executable here?
+    public void register(ToolType toolType, String defaultExecutable) {
+        DefaultTool tool = new DefaultTool(toolType, defaultExecutable);
+        gccTools.put(toolType, tool);
+    }
+
+    public GccToolInternal getTool(ToolType toolType) {
+        return gccTools.get(toolType);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/GccToolInternal.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/GccToolInternal.java
new file mode 100644
index 0000000..c803ddb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/GccToolInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import org.gradle.api.Action;
+import org.gradle.nativebinaries.toolchain.GccTool;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+
+import java.util.List;
+
+public interface GccToolInternal extends GccTool {
+
+    ToolType getToolType();
+
+    Action<List<String>> getArgAction();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/PlatformGccTool.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/PlatformGccTool.java
new file mode 100644
index 0000000..efee0bc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/PlatformGccTool.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.internal.Actions;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+
+import java.util.List;
+
+public class PlatformGccTool implements GccToolInternal {
+    private final GccToolInternal baseTool;
+    private final List<String> platformArgs;
+
+    public PlatformGccTool(GccToolInternal baseTool, List<String> platformArgs) {
+        this.baseTool = baseTool;
+        this.platformArgs = platformArgs;
+    }
+
+    public ToolType getToolType() {
+        return baseTool.getToolType();
+    }
+
+    public String getExecutable() {
+        return baseTool.getExecutable();
+    }
+
+    public void setExecutable(String file) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Action<List<String>> getArgAction() {
+        Action<? super List<String>> platformArgsAction = new Action<List<String>>() {
+            public void execute(List<String> strings) {
+                strings.addAll(0, platformArgs);
+            }
+        };
+        return Actions.composite(baseTool.getArgAction(), platformArgsAction);
+    }
+
+    public void withArguments(Closure arguments) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/PlatformToolRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/PlatformToolRegistry.java
new file mode 100644
index 0000000..ea32d95
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/PlatformToolRegistry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import org.gradle.nativebinaries.toolchain.GccTool;
+import org.gradle.nativebinaries.toolchain.TargetPlatformConfiguration;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PlatformToolRegistry implements ToolRegistry {
+    private final Map<ToolType, GccToolInternal> gccTools = new HashMap<ToolType, GccToolInternal>();
+
+    public PlatformToolRegistry(ToolRegistry defaultTools, TargetPlatformConfiguration configuration) {
+        // TODO:DAZ Replace TargetPlatformConfiguration with an action that applies to the entire registry.
+        // This will involve exposing the tools by their 'name' in a container, and applying the action to that container.
+        register(defaultTools.getTool(ToolType.CPP_COMPILER), configuration.getCppCompilerArgs());
+        register(defaultTools.getTool(ToolType.C_COMPILER), configuration.getCCompilerArgs());
+        register(defaultTools.getTool(ToolType.OBJECTIVECPP_COMPILER), configuration.getObjectiveCppCompilerArgs());
+        register(defaultTools.getTool(ToolType.OBJECTIVEC_COMPILER), configuration.getObjectiveCCompilerArgs());
+        register(defaultTools.getTool(ToolType.ASSEMBLER), configuration.getAssemblerArgs());
+        register(defaultTools.getTool(ToolType.LINKER), configuration.getLinkerArgs());
+        register(defaultTools.getTool(ToolType.STATIC_LIB_ARCHIVER), configuration.getStaticLibraryArchiverArgs());
+    }
+
+    private GccTool register(GccToolInternal baseTool, List<String> platformArgs) {
+        return gccTools.put(baseTool.getToolType(), new PlatformGccTool(baseTool, platformArgs));
+    }
+
+    public GccToolInternal getTool(ToolType toolType) {
+        return gccTools.get(toolType);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolRegistry.java
new file mode 100644
index 0000000..4edfa3a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolRegistry.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+
+public interface ToolRegistry {
+    GccToolInternal getTool(ToolType toolType);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolSearchPath.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolSearchPath.java
new file mode 100644
index 0000000..8a54fa1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolSearchPath.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.tools;
+
+import org.gradle.api.GradleException;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.text.TreeFormatter;
+import org.gradle.nativebinaries.toolchain.internal.ToolType;
+import org.gradle.nativebinaries.toolchain.internal.gcc.CommandLineToolSearchResult;
+import org.gradle.util.TreeVisitor;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ToolSearchPath {
+    private final Map<String, CommandLineToolSearchResult> executables = new HashMap<String, CommandLineToolSearchResult>();
+    private final List<File> pathEntries = new ArrayList<File>();
+
+    private final OperatingSystem operatingSystem;
+
+    public ToolSearchPath(OperatingSystem operatingSystem) {
+        this.operatingSystem = operatingSystem;
+    }
+
+    public List<File> getPath() {
+        return pathEntries;
+    }
+
+    public void setPath(List<File> pathEntries) {
+        this.pathEntries.clear();
+        this.pathEntries.addAll(pathEntries);
+        executables.clear();
+    }
+
+    public void path(File pathEntry) {
+        pathEntries.add(pathEntry);
+        executables.clear();
+    }
+
+    public CommandLineToolSearchResult locate(ToolType key, String exeName) {
+        CommandLineToolSearchResult result = executables.get(exeName);
+        if (result == null) {
+            File exe = findExecutable(operatingSystem, exeName);
+            result = exe == null || !exe.isFile() ? new MissingTool(key, exeName, pathEntries) : new FoundTool(exe);
+            executables.put(exeName, result);
+        }
+        return result;
+    }
+
+    protected File findExecutable(OperatingSystem operatingSystem, String name) {
+        if (!pathEntries.isEmpty()) {
+            String exeName = operatingSystem.getExecutableName(name);
+            for (File pathEntry : pathEntries) {
+                File candidate = new File(pathEntry, exeName);
+                if (candidate.isFile()) {
+                    return candidate;
+                }
+            }
+            return null;
+        } else {
+            return operatingSystem.findInPath(name);
+        }
+    }
+
+    private static class FoundTool implements CommandLineToolSearchResult {
+        private final File tool;
+
+        private FoundTool(File tool) {
+            this.tool = tool;
+        }
+
+        public boolean isAvailable() {
+            return true;
+        }
+
+        public File getTool() {
+            return tool;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+        }
+    }
+
+    private static class MissingTool implements CommandLineToolSearchResult {
+        private final ToolType type;
+        private final String exeName;
+        private final List<File> path;
+
+        private MissingTool(ToolType type, String exeName, List<File> path) {
+            this.type = type;
+            this.exeName = exeName;
+            this.path = path;
+        }
+
+        public void explain(TreeVisitor<? super String> visitor) {
+            if (path.isEmpty()) {
+                visitor.node(String.format("Could not find %s '%s' in system path.", type.getToolName(), exeName));
+            } else {
+                visitor.node(String.format("Could not find %s '%s'. Searched in", type.getToolName(), exeName));
+                visitor.startChildren();
+                for (File location : path) {
+                    visitor.node(location.toString());
+                }
+                visitor.endChildren();
+            }
+        }
+
+        public File getTool() {
+            TreeFormatter formatter = new TreeFormatter();
+            explain(formatter);
+            throw new GradleException(formatter.toString());
+        }
+
+        public boolean isAvailable() {
+            return false;
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/package-info.java
new file mode 100644
index 0000000..6e0a248
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 allow C++ tool chains to be configured.
+ */
+package org.gradle.nativebinaries.toolchain;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/ClangCompilerPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/ClangCompilerPlugin.groovy
new file mode 100644
index 0000000..36da872
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/ClangCompilerPlugin.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.model.ModelRule
+import org.gradle.model.ModelRules
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+import org.gradle.nativebinaries.toolchain.Clang
+import org.gradle.nativebinaries.toolchain.internal.clang.ClangToolChain
+import org.gradle.process.internal.ExecActionFactory
+
+import javax.inject.Inject
+/**
+ * A {@link Plugin} which makes the <a href="http://clang.llvm.org">Clang</a> compiler available for compiling C/C++ code.
+ */
+ at Incubating
+class ClangCompilerPlugin implements Plugin<Project> {
+    private final FileResolver fileResolver
+    private final ExecActionFactory execActionFactory
+    private final Instantiator instantiator
+    private final ModelRules modelRules
+
+    @Inject
+    ClangCompilerPlugin(FileResolver fileResolver, ExecActionFactory execActionFactory, ModelRules modelRules, Instantiator instantiator) {
+        this.execActionFactory = execActionFactory
+        this.fileResolver = fileResolver
+        this.instantiator = instantiator
+        this.modelRules = modelRules
+    }
+
+    void apply(Project project) {
+        project.plugins.apply(NativeBinariesPlugin)
+
+        modelRules.rule(new ModelRule() {
+            void addToolChain(ToolChainRegistryInternal toolChainRegistry) {
+                toolChainRegistry.registerFactory(Clang, { String name ->
+                    return instantiator.newInstance(ClangToolChain, name, OperatingSystem.current(), fileResolver, execActionFactory)
+                })
+                toolChainRegistry.registerDefaultToolChain(ClangToolChain.DEFAULT_NAME, Clang)
+            }
+        })
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/GccCompilerPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/GccCompilerPlugin.groovy
new file mode 100644
index 0000000..89a6ce1
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/GccCompilerPlugin.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.plugins
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.model.ModelRule
+import org.gradle.model.ModelRules
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+import org.gradle.nativebinaries.toolchain.Gcc
+import org.gradle.nativebinaries.toolchain.internal.gcc.GccToolChain
+import org.gradle.process.internal.ExecActionFactory
+
+import javax.inject.Inject
+/**
+ * A {@link Plugin} which makes the <a href="http://gcc.gnu.org/">GNU GCC/G++ compiler</a> available for compiling C/C++ code.
+ */
+ at Incubating
+class GccCompilerPlugin implements Plugin<Project> {
+    private final FileResolver fileResolver
+    private final ExecActionFactory execActionFactory
+    private final Instantiator instantiator
+    private final ModelRules modelRules;
+
+    @Inject
+    GccCompilerPlugin(FileResolver fileResolver, ExecActionFactory execActionFactory, ModelRules modelRules, Instantiator instantiator) {
+        this.execActionFactory = execActionFactory
+        this.fileResolver = fileResolver
+        this.modelRules = modelRules
+        this.instantiator = instantiator
+    }
+
+    void apply(Project project) {
+        project.plugins.apply(NativeBinariesPlugin)
+
+        modelRules.rule(new ModelRule() {
+            void addGccToolChain(ToolChainRegistryInternal toolChainRegistry) {
+                toolChainRegistry.registerFactory(Gcc, { String name ->
+                    return instantiator.newInstance(GccToolChain, name, OperatingSystem.current(), fileResolver, execActionFactory)
+                })
+                toolChainRegistry.registerDefaultToolChain(GccToolChain.DEFAULT_NAME, Gcc)
+            }
+        })
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/MicrosoftVisualCppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/MicrosoftVisualCppPlugin.groovy
new file mode 100755
index 0000000..f61abfb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/MicrosoftVisualCppPlugin.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.nativebinaries.toolchain.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.model.ModelRule
+import org.gradle.model.ModelRules
+import org.gradle.nativebinaries.plugins.NativeBinariesPlugin
+import org.gradle.nativebinaries.toolchain.VisualCpp
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.VisualCppToolChain
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.VisualStudioLocator
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.WindowsSdkLocator
+import org.gradle.process.internal.ExecActionFactory
+
+import javax.inject.Inject
+
+/**
+ * A {@link Plugin} which makes the Microsoft Visual C++ compiler available to compile C/C++ code.
+ */
+ at Incubating
+class MicrosoftVisualCppPlugin implements Plugin<Project> {
+    private final FileResolver fileResolver;
+    private final ExecActionFactory execActionFactory
+    private final Instantiator instantiator
+    private final ModelRules modelRules
+    private final OperatingSystem operatingSystem
+    private final VisualStudioLocator visualStudioLocator
+    private final WindowsSdkLocator windowsSdkLocator
+
+    @Inject
+    MicrosoftVisualCppPlugin(FileResolver fileResolver, ExecActionFactory execActionFactory, ModelRules modelRules, Instantiator instantiator, OperatingSystem operatingSystem,
+                             VisualStudioLocator visualStudioLocator, WindowsSdkLocator windowsSdkLocator) {
+        this.windowsSdkLocator = windowsSdkLocator
+        this.visualStudioLocator = visualStudioLocator
+        this.operatingSystem = operatingSystem
+        this.execActionFactory = execActionFactory
+        this.fileResolver = fileResolver
+        this.instantiator = instantiator
+        this.modelRules = modelRules
+    }
+
+    void apply(Project project) {
+        project.plugins.apply(NativeBinariesPlugin)
+
+        modelRules.rule(new ModelRule() {
+            void addToolChain(ToolChainRegistryInternal toolChainRegistry) {
+                toolChainRegistry.registerFactory(VisualCpp, { String name ->
+                    return instantiator.newInstance(VisualCppToolChain, name, operatingSystem, fileResolver, execActionFactory, visualStudioLocator, windowsSdkLocator)
+                })
+                toolChainRegistry.registerDefaultToolChain(VisualCppToolChain.DEFAULT_NAME, VisualCpp)
+            }
+        })
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/package-info.java
new file mode 100644
index 0000000..3b868d4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/nativebinaries/toolchain/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Built-in tool chain support.
+ */
+package org.gradle.nativebinaries.toolchain.plugins;
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
deleted file mode 100644
index 07aac93..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.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.plugins.binaries;
-
-import org.gradle.api.Plugin;
-import org.gradle.api.internal.FactoryNamedDomainObjectContainer;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.ReflectiveNamedDomainObjectFactory;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.plugins.BasePlugin;
-import org.gradle.plugins.binaries.model.Executable;
-import org.gradle.plugins.binaries.model.Library;
-import org.gradle.plugins.binaries.model.internal.DefaultCompilerRegistry;
-import org.gradle.plugins.binaries.model.internal.DefaultExecutable;
-import org.gradle.plugins.binaries.model.internal.DefaultLibrary;
-
-import javax.inject.Inject;
-
-/**
- * temp plugin, not sure what will provide the binaries container and model elements
- */
-public class BinariesPlugin implements Plugin<ProjectInternal> {
-    private final Instantiator instantiator;
-
-    @Inject
-    public BinariesPlugin(Instantiator instantiator) {
-        this.instantiator = instantiator;
-    }
-
-    public void apply(final ProjectInternal project) {
-        project.getPlugins().apply(BasePlugin.class);
-
-        project.getExtensions().create("compilers",
-                DefaultCompilerRegistry.class,
-                instantiator
-        );
-        DefaultCompilerRegistry registry = project.getExtensions().getByType(DefaultCompilerRegistry.class);
-
-        project.getExtensions().create("executables",
-                FactoryNamedDomainObjectContainer.class,
-                Executable.class,
-                instantiator,
-                new ReflectiveNamedDomainObjectFactory<Executable>(DefaultExecutable.class, project, registry)
-        );
-        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/Binary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Binary.java
deleted file mode 100644
index 1dd4a8e..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Binary.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.Buildable;
-import org.gradle.api.Named;
-import org.gradle.api.Project;
-import org.gradle.api.DomainObjectSet;
-
-/**
- * Something to be created.
- */
-public interface Binary extends Named, Buildable {
-
-    CompileSpec getSpec();
-
-    /**
-     * Returns the project that this binary is built by.
-     *
-     * @deprecated No replacement
-     */
-    @Deprecated
-    Project getProject();
-    
-    DomainObjectSet<SourceSet> getSourceSets();
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompileSpec.java
deleted file mode 100644
index f990edb..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompileSpec.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.Named;
-
-import java.io.File;
-
-/**
- * A high level interface to the compiler, specifying what is to be compiled and how.
- */
-public interface CompileSpec extends Named {
-
-    /**
-     * The ultimate output of the compilation.
-     */
-    File getOutputFile();
-    
-    /**
-     * Do the compile.
-     *
-     * @deprecated No replacement
-     */
-    @Deprecated
-    void compile();
-    
-    /**
-     * Configures the spec to include the source set 
-     */
-    // void from(SourceSet sourceSet);
-    /*
-        notes on from():
-        
-        The CompileSpec interface is likely to just have from(SourceSet) which the default impl of which would be to throw
-        unsupported operation exception, with implementations overriding this method to handle different kinds of source sets
-    */
-    
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Compiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Compiler.java
deleted file mode 100644
index fefdcfa..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Compiler.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.plugins.binaries.model;
-
-import org.gradle.api.Named;
-
-/**
- * A kind of compiler
- */
-public interface Compiler extends Named {
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompilerRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompilerRegistry.java
deleted file mode 100644
index 019563c..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompilerRegistry.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.NamedDomainObjectSet;
-
-/**
- * A container for compiler adapters
- */
-public interface CompilerRegistry extends NamedDomainObjectSet<Compiler> {
-
-    /**
-     * Somehow picks what the default compiler to use is.
-     *
-     * @return null when there is no default compiler available.
-     */
-    Compiler getDefaultCompiler();
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Executable.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Executable.java
deleted file mode 100644
index 1f64a13..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Executable.java
+++ /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.plugins.binaries.model;
-
-/**
- * An executable binary
- */
-public interface Executable extends Binary {
-    
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/HeaderExportingSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/HeaderExportingSourceSet.java
deleted file mode 100644
index 0725aa6..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/HeaderExportingSourceSet.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.file.SourceDirectorySet;
-
-/**
- * A source set that exposes headers
- */
-public interface HeaderExportingSourceSet extends SourceSet {
-
-    SourceDirectorySet getExportedHeaders();
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Library.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Library.java
deleted file mode 100644
index 73dacda..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Library.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.file.SourceDirectorySet;
-
-/**
- * A library
- */
-public interface Library extends Binary {
-    LibraryCompileSpec getSpec();
-
-    SourceDirectorySet getHeaders();
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/LibraryCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/LibraryCompileSpec.java
deleted file mode 100644
index 111e091..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/LibraryCompileSpec.java
+++ /dev/null
@@ -1,32 +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.binaries.model;
-
-/**
- * A high level interface to the compiler, specifying what is to be compiled and how.
- */
-public interface LibraryCompileSpec extends CompileSpec {
-    /**
-     * <p>Returns the <i>install name</i> for the library. This is the location where this library will be installed on the target
-     * system, and where clients of this library should look for the library.
-     *
-     * <p>On Linux systems, this corresponds to the <i>soname</i> for the library.</p>
-     */
-    String getInstallName();
-
-    void setInstallName(String path);
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencyCapableSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencyCapableSourceSet.java
deleted file mode 100644
index d834aba..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencyCapableSourceSet.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.DomainObjectSet;
-
-/**
- * Source set capability
- */
-public interface NativeDependencyCapableSourceSet extends SourceSet {
-    DomainObjectSet<NativeDependencySet> getNativeDependencySets();
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencySet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencySet.java
deleted file mode 100644
index 5db2b81..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencySet.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.plugins.binaries.model;
-
-import org.gradle.api.file.FileCollection;
-
-/**
- * Models a collection of native type dependencies.
- */
-public interface NativeDependencySet {
-
-    FileCollection getIncludeRoots();
-    FileCollection getFiles();
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/SourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/SourceSet.java
deleted file mode 100644
index 6bba25a..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/SourceSet.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model;
-
-import org.gradle.api.Named;
-
-/**
- * A generic model of a collection of source
- */
-public interface SourceSet extends Named {
-    
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpec.java
deleted file mode 100644
index 8d0734e..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpec.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.plugins.binaries.model.internal;
-
-import org.gradle.api.Buildable;
-import org.gradle.api.internal.tasks.compile.CompileSpec;
-
-public interface BinaryCompileSpec extends org.gradle.plugins.binaries.model.CompileSpec, CompileSpec, Buildable {
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpecFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpecFactory.java
deleted file mode 100644
index de9fe6d..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpecFactory.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.plugins.binaries.model.internal;
-
-import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.plugins.binaries.model.Binary;
-
-/**
- * This is ugly. Currently, the CompileSpec impls need access to a Compiler, so we need use this interface to provide one at construction time.
- */
-public interface BinaryCompileSpecFactory {
-    BinaryCompileSpec create(Binary binary, Compiler<?> compiler);
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileSpecFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileSpecFactory.java
deleted file mode 100644
index 2f010fb..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileSpecFactory.java
+++ /dev/null
@@ -1,29 +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.binaries.model.internal;
-
-import org.gradle.plugins.binaries.model.Binary;
-
-/**
- * Producer of compile specs
- */
-public interface CompileSpecFactory {
-
-    /**
-     * Create a new spec to compile this binary
-     */
-    BinaryCompileSpec create(Binary binary);
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.java
deleted file mode 100644
index 41760f1..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.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.plugins.binaries.model.internal;
-
-import org.gradle.plugins.cpp.CppCompile;
-
-public interface CompileTaskAware {
-    void configure(CppCompile compileTask);
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompilerAdapter.java
deleted file mode 100644
index b3726b8..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompilerAdapter.java
+++ /dev/null
@@ -1,32 +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.binaries.model.internal;
-
-import org.gradle.plugins.binaries.model.Binary;
-import org.gradle.plugins.binaries.model.Compiler;
-
-public interface CompilerAdapter<T extends BinaryCompileSpec> extends Compiler {
-    /**
-     * Creates a compiler which can compile the given binary. Should only be called if {@link #isAvailable()} has returned true.
-     */
-    org.gradle.api.internal.tasks.compile.Compiler<T> createCompiler(Binary binary);
-
-    /**
-     * Returns true if this compiler is available.
-     */
-    boolean isAvailable();
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/ConfigurationBasedNativeDependencySet.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/ConfigurationBasedNativeDependencySet.groovy
deleted file mode 100644
index d9d46ab..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/ConfigurationBasedNativeDependencySet.groovy
+++ /dev/null
@@ -1,86 +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.model.internal
-
-import org.gradle.plugins.binaries.model.NativeDependencySet
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.file.FileCollection
-import org.gradle.api.artifacts.Configuration
-
-class ConfigurationBasedNativeDependencySet implements NativeDependencySet {
-
-    private final String baseName
-    final String headersConfigurationName
-    final String filesConfigurationName // files is a bad name
-    final Project project
-    private Task headerExtractionTask
-
-    ConfigurationBasedNativeDependencySet(Project project, String baseName = "main") {
-        this.baseName = baseName
-        this.headersConfigurationName = baseName + "HeaderDependencies"
-        this.filesConfigurationName = baseName + "FileDependencies"
-        this.project = project
-
-        createConfigurations()
-        initHeaderExtractionTask()
-    }
-
-    private createConfigurations() {
-        project.configurations.with {
-            create(headersConfigurationName)
-            create(filesConfigurationName)
-        }
-    }
-
-    private initHeaderExtractionTask() {
-        def headersConfiguration = getHeadersConfiguration()
-        def dir = project.file("$project.buildDir/dependency-headers/$baseName")
-        headerExtractionTask = project.task(baseName + "ExtractHeaders") {
-            inputs.files headersConfiguration
-            outputs.files { dir.listFiles() }
-            doLast {
-                headersConfiguration.each { headerZip ->
-                    project.copy {
-                        from project.zipTree(headerZip)
-                        into "$dir/${headerZip.name - '.zip'}"
-                    }
-                }
-            }
-        }
-    }
-
-    Configuration getHeadersConfiguration() {
-        project.configurations[headersConfigurationName]
-    }
-
-    FileCollection getIncludeRoots() {
-        headerExtractionTask.outputs.files
-    }
-
-    FileCollection getFiles() {
-        project.configurations[filesConfigurationName]
-    }
-
-    void add(Map dep) {
-        // hackity hack hack
-        project.dependencies {
-            def m = { classifier, ext -> [classifier: classifier, ext: ext] }
-            delegate."$headersConfigurationName"(dep + m("headers", "zip"))
-            delegate."$filesConfigurationName"(dep + m("so", "so"))
-        }
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultBinary.java
deleted file mode 100644
index 64d04f0..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultBinary.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.plugins.binaries.model.internal;
-
-import groovy.lang.Closure;
-import org.gradle.api.DomainObjectSet;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.tasks.TaskDependency;
-import org.gradle.plugins.binaries.model.Binary;
-import org.gradle.plugins.binaries.model.CompileSpec;
-import org.gradle.plugins.binaries.model.SourceSet;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.DeprecationLogger;
-
-public class DefaultBinary implements Binary {
-    private final String name;
-    private final ProjectInternal project;
-    private final BinaryCompileSpec spec;
-    private final DomainObjectSet<SourceSet> sourceSets;
-
-    public DefaultBinary(String name, ProjectInternal project, CompileSpecFactory specFactory) {
-        this.name = name;
-        this.project = project;
-        this.sourceSets = new DefaultDomainObjectSet<SourceSet>(SourceSet.class);
-        this.spec = specFactory.create(this);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public TaskDependency getBuildDependencies() {
-        return spec.getBuildDependencies();
-    }
-
-    public ProjectInternal getProject() {
-        DeprecationLogger.nagUserOfDiscontinuedMethod("Binary.getProject()");
-        return project;
-    }
-
-    public CompileSpec getSpec() {
-        return spec;
-    }
-
-    public DomainObjectSet<SourceSet> getSourceSets() {
-        return sourceSets;
-    }
-    
-    public CompileSpec spec(Closure closure) {
-        return ConfigureUtil.configure(closure, spec);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
deleted file mode 100755
index 1052812..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model.internal;
-
-import com.google.common.base.Joiner;
-import org.gradle.api.Action;
-import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.tasks.WorkResult;
-import org.gradle.plugins.binaries.model.Binary;
-import org.gradle.plugins.binaries.model.Compiler;
-import org.gradle.plugins.binaries.model.CompilerRegistry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultCompilerRegistry extends DefaultNamedDomainObjectSet<Compiler> implements CompilerRegistry, CompileSpecFactory {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCompilerRegistry.class);
-    private BinaryCompileSpecFactory specFactory;
-    private final List<Compiler> searchOrder = new ArrayList<Compiler>();
-
-    public DefaultCompilerRegistry(Instantiator instantiator) {
-        super(Compiler.class, instantiator);
-        whenObjectAdded(new Action<Compiler>() {
-            public void execute(Compiler compiler) {
-                searchOrder.add(compiler);
-            }
-        });
-        whenObjectRemoved(new Action<Compiler>() {
-            public void execute(Compiler compiler) {
-                searchOrder.remove(compiler);
-            }
-        });
-    }
-
-    public List<Compiler> getSearchOrder() {
-        return searchOrder;
-    }
-
-    public void setSpecFactory(BinaryCompileSpecFactory specFactory) {
-        this.specFactory = specFactory;
-    }
-
-    public CompilerAdapter<BinaryCompileSpec> getDefaultCompiler() {
-        for (Compiler compiler : searchOrder) {
-            CompilerAdapter<BinaryCompileSpec> adapter = (CompilerAdapter<BinaryCompileSpec>) compiler;
-            if (adapter.isAvailable()) {
-                return adapter;
-            }
-        }
-        return null;
-    }
-
-    public BinaryCompileSpec create(final Binary binary) {
-        org.gradle.api.internal.tasks.compile.Compiler<BinaryCompileSpec> lazyCompiler = new LazyCompiler(binary);
-        return specFactory.create(binary, lazyCompiler);
-    }
-
-    private class LazyCompiler implements org.gradle.api.internal.tasks.compile.Compiler<BinaryCompileSpec> {
-        private final Binary binary;
-
-        public LazyCompiler(Binary binary) {
-            this.binary = binary;
-        }
-
-        public WorkResult execute(BinaryCompileSpec spec) {
-            CompilerAdapter<BinaryCompileSpec> compiler = getDefaultCompiler();
-            if (compiler == null) {
-                throw new IllegalStateException(String.format("No compiler is available to compile %s. Searched for %s.", binary, Joiner.on(", ").join(searchOrder)));
-            }
-            LOGGER.info("Using " + compiler + " to compile " + binary);
-            return compiler.createCompiler(binary).execute(spec);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultExecutable.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultExecutable.java
deleted file mode 100755
index e7a4f73..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultExecutable.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.binaries.model.internal;
-
-import org.gradle.plugins.binaries.model.Executable;
-
-import org.gradle.api.internal.project.ProjectInternal;
-
-public class DefaultExecutable extends DefaultBinary implements Executable {
-    public DefaultExecutable(String name, ProjectInternal project, CompileSpecFactory specFactory) {
-        super(name, project, specFactory);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("executable '%s'", getName());
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultLibrary.java
deleted file mode 100755
index fb15888..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultLibrary.java
+++ /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.plugins.binaries.model.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.DomainObjectSet;
-import org.gradle.api.file.SourceDirectorySet;
-import org.gradle.api.internal.file.DefaultSourceDirectorySet;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.plugins.binaries.model.HeaderExportingSourceSet;
-import org.gradle.plugins.binaries.model.Library;
-import org.gradle.plugins.binaries.model.LibraryCompileSpec;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultLibrary extends DefaultBinary implements Library {
-    private final DefaultSourceDirectorySet headers;
-
-    public DefaultLibrary(String name, ProjectInternal project, CompileSpecFactory specFactory) {
-        super(name, project, specFactory);
-        this.headers = new DefaultSourceDirectorySet("headers", String.format("Exported headers for native library '%s'", name), project.getFileResolver());
-
-        initExportedHeaderTracking();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("library '%s'", getName());
-    }
-
-    public LibraryCompileSpec getSpec() {
-        return (LibraryCompileSpec) super.getSpec();
-    }
-
-    public SourceDirectorySet getHeaders() {
-        return headers;
-    }
-
-    private void initExportedHeaderTracking() {
-        // TODO - headers.srcDirs() should allow a Callable<SourceDirectorySet> for lazy calculation
-        final DomainObjectSet<HeaderExportingSourceSet> headerExportingSourceSets = getSourceSets().withType(HeaderExportingSourceSet.class);
-        headerExportingSourceSets.all(new Action<HeaderExportingSourceSet>() {
-            public void execute(HeaderExportingSourceSet headerExportingSourceSet) {
-                updateHeaderDirs(headerExportingSourceSets, headers);
-            }
-        });
-        headerExportingSourceSets.whenObjectRemoved(new Action<HeaderExportingSourceSet>() {
-            public void execute(HeaderExportingSourceSet headerExportingSourceSet) {
-                updateHeaderDirs(headerExportingSourceSets, headers);
-            }
-        });
-    }
-
-    private void updateHeaderDirs(DomainObjectSet<HeaderExportingSourceSet> sourceSets, DefaultSourceDirectorySet headers) {
-        List<SourceDirectorySet> headerDirs = new ArrayList<SourceDirectorySet>();
-        for (HeaderExportingSourceSet sourceSet : sourceSets) {
-            headerDirs.add(sourceSet.getExportedHeaders());
-        }
-        headers.setSrcDirs(headerDirs);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/package-info.java
deleted file mode 100644
index 75ff69f..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-/**
- * Implementations of the native model classes.
- */
-package org.gradle.plugins.binaries.model.internal;
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/package-info.java
deleted file mode 100644
index 73d2a48..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/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 that model aspects of C++ projects.
- */
-package org.gradle.plugins.binaries.model;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/package-info.java
deleted file mode 100644
index a260db0..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
- 
-/**
- * Provides the binaries container and some generic model elements
- */
-package org.gradle.plugins.binaries;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/package-info.java
deleted file mode 100644
index 8a2cad0..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/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.
- */
-
-/**
- * Tasks for building native projects.
- */
-package org.gradle.plugins.binaries.tasks;
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
deleted file mode 100644
index b280684..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppCompile.groovy
+++ /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.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/CppExeConventionPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExeConventionPlugin.groovy
deleted file mode 100644
index 80320d8..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExeConventionPlugin.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.plugins.cpp
-
-import org.gradle.api.Project
-import org.gradle.api.Plugin
-
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
-
-class CppExeConventionPlugin implements Plugin<Project> {
-
-    void apply(Project project) {
-        project.plugins.apply(CppPlugin)
-        
-        project.with {
-            cpp {
-                sourceSets {
-                    main {}
-                }
-            }
-            executables {
-                main {
-                    spec.baseName = project.name
-                    sourceSets << project.cpp.sourceSets.main
-                }
-            }
-            
-            def exeArtifact = new DefaultPublishArtifact(
-                archivesBaseName, // name
-                "exe", // ext
-                "exe", // type
-                null, // classifier
-                null, // date
-
-                // needs to be more general and not peer into the spec
-                executables.main.spec.outputFile,
-                executables.main
-            )
-
-            extensions.getByType(DefaultArtifactPublicationSet).addCandidate(exeArtifact)
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
deleted file mode 100644
index e4b68e1..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp;
-
-import groovy.lang.Closure;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.internal.FactoryNamedDomainObjectContainer;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.ReflectiveNamedDomainObjectFactory;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.plugins.cpp.internal.DefaultCppSourceSet;
-
-/**
- * Adds a source set container.
- */
-public class CppExtension {
-
-    final private NamedDomainObjectContainer<CppSourceSet> sourceSets;
-
-    public CppExtension(final ProjectInternal project) {
-        Instantiator instantiator = project.getServices().get(Instantiator.class);
-        sourceSets = instantiator.newInstance(
-                FactoryNamedDomainObjectContainer.class,
-                CppSourceSet.class,
-                instantiator,
-                new ReflectiveNamedDomainObjectFactory<CppSourceSet>(DefaultCppSourceSet.class, project)
-        );
-    }
-
-    public NamedDomainObjectContainer<CppSourceSet> sourceSets(Closure closure) {
-        return sourceSets.configure(closure);
-    }
-    
-    public NamedDomainObjectContainer<CppSourceSet> getSourceSets() {
-        return sourceSets;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppLibConventionPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppLibConventionPlugin.groovy
deleted file mode 100644
index 9eacc50..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppLibConventionPlugin.groovy
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp
-
-import org.gradle.api.Project
-import org.gradle.api.Plugin
-import org.gradle.api.tasks.bundling.Zip
-
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
-import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
-
-class CppLibConventionPlugin implements Plugin<Project> {
-
-    void apply(Project project) {
-        project.plugins.apply(CppPlugin)
-
-        project.with {
-            cpp {
-                sourceSets {
-                    main {}
-                }
-            }
-            libraries {
-                main {
-                    sourceSets << project.cpp.sourceSets.main
-                    spec.baseName = project.name
-                }
-            }
-
-            def libArtifact = new DefaultPublishArtifact(
-                archivesBaseName, // name
-                "so", // ext
-                "so", // type
-                "so", // classifier
-                null, // date
-
-                // needs to be more general and not peer into the spec
-                libraries.main.spec.outputFile,
-                libraries.main
-            )
-
-            task("assembleHeaders", type: Zip) {
-                from libraries.main.headers
-                classifier = "headers"
-            }
-
-            def headerArtifact = new ArchivePublishArtifact(assembleHeaders)
-
-            extensions.getByType(DefaultArtifactPublicationSet).addCandidate(libArtifact)
-            extensions.getByType(DefaultArtifactPublicationSet).addCandidate(headerArtifact)
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy
deleted file mode 100644
index d5d4862..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp
-
-import org.gradle.api.Plugin
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.plugins.BasePlugin
-import org.gradle.api.tasks.Sync
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.plugins.binaries.BinariesPlugin
-import org.gradle.plugins.binaries.model.Binary
-import org.gradle.plugins.binaries.model.Executable
-import org.gradle.plugins.binaries.model.internal.DefaultCompilerRegistry
-import org.gradle.plugins.cpp.gpp.GppCompilerPlugin
-import org.gradle.plugins.cpp.gpp.internal.GppCompileSpecFactory
-import org.gradle.plugins.cpp.msvcpp.MicrosoftVisualCppPlugin
-import org.gradle.util.GUtil
-
-class CppPlugin implements Plugin<ProjectInternal> {
-
-    void apply(ProjectInternal project) {
-        project.plugins.apply(BinariesPlugin)
-        project.plugins.apply(MicrosoftVisualCppPlugin)
-        project.plugins.apply(GppCompilerPlugin)
-        project.extensions.create("cpp", CppExtension, project)
-
-        project.extensions.getByType(DefaultCompilerRegistry).specFactory = new GppCompileSpecFactory(project)
-
-        // Defaults for all cpp source sets
-        project.cpp.sourceSets.all { sourceSet ->
-            sourceSet.source.srcDir "src/${sourceSet.name}/cpp"
-            sourceSet.exportedHeaders.srcDir "src/${sourceSet.name}/headers"
-        }
-
-        // Defaults for all executables
-        project.executables.all { executable ->
-            configureExecutable(project, executable)
-        }
-
-        // Defaults for all libraries
-        project.libraries.all { library ->
-            configureBinary(project, library)
-        }
-    }
-
-    def configureExecutable(ProjectInternal project, Executable executable) {
-        configureBinary(project, executable)
-
-        def baseName = GUtil.toCamelCase(executable.name).capitalize()
-        project.task("install${baseName}", type: Sync) {
-            description = "Installs a development image of $executable"
-            into { project.file("${project.buildDir}/install/$executable.name") }
-            dependsOn executable
-            if (OperatingSystem.current().windows) {
-                from { executable.spec.outputFile }
-                from { executable.sourceSets*.libs*.spec*.outputFile }
-            } else {
-                into("lib") {
-                    from { executable.spec.outputFile }
-                    from { executable.sourceSets*.libs*.spec*.outputFile }
-                }
-                doLast {
-                    def script = new File(destinationDir, executable.spec.outputFile.name)
-                    script.text = """
-#/bin/sh
-APP_BASE_NAME=`dirname "\$0"`
-export DYLD_LIBRARY_PATH="\$APP_BASE_NAME/lib"
-export LD_LIBRARY_PATH="\$APP_BASE_NAME/lib"
-exec "\$APP_BASE_NAME/lib/${executable.spec.outputFile.name}" \"\$@\"
-                    """
-                    ant.chmod(perm: 'u+x', file: script)
-                }
-            }
-        }
-    }
-
-    def configureBinary(ProjectInternal project, Binary binary) {
-        def baseName = GUtil.toCamelCase(binary.name).capitalize()
-
-        def task = project.task("compile${baseName}", type: CppCompile) {
-            description = "Compiles and links $binary"
-            group = BasePlugin.BUILD_GROUP
-        }
-        binary.spec.configure(task)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppSourceSet.java
deleted file mode 100644
index 0d0d066..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppSourceSet.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp;
-
-import org.gradle.plugins.binaries.model.Library;
-import org.gradle.plugins.binaries.model.HeaderExportingSourceSet;
-import org.gradle.plugins.binaries.model.NativeDependencyCapableSourceSet;
-
-import org.gradle.api.Named;
-import org.gradle.api.DomainObjectSet;
-import org.gradle.api.file.SourceDirectorySet;
-
-import groovy.lang.Closure;
-
-import java.util.Map;
-
-/**
- * A representation of a unit of cpp source
- */
-public interface CppSourceSet extends HeaderExportingSourceSet, NativeDependencyCapableSourceSet, Named {
-
-    /**
-     * The headers.
-     */
-    SourceDirectorySet getExportedHeaders();
-
-    /**
-     * The headers.
-     */
-    CppSourceSet exportedHeaders(Closure closure);
-
-    /**
-     * The source.
-     */
-    SourceDirectorySet getSource();
-
-    /**
-     * The source.
-     */
-    CppSourceSet source(Closure closure);
-
-    /**
-     * Libs this source set requires
-     */
-    DomainObjectSet<Library> getLibs();
-    
-    /**
-     * Add a dependency to this source set
-     */
-    void dependency(Map<?, ?> dep);
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy
deleted file mode 100644
index d17dc4c..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.cdt
-
-import org.gradle.api.Project
-import org.gradle.api.Plugin
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.tasks.Delete
-
-import org.gradle.plugins.cpp.cdt.model.ProjectSettings
-import org.gradle.plugins.cpp.cdt.model.ProjectDescriptor
-import org.gradle.plugins.cpp.cdt.model.CprojectSettings
-import org.gradle.plugins.cpp.cdt.model.CprojectDescriptor
-
-import org.gradle.plugins.cpp.cdt.tasks.GenerateMetadataFileTask
-
-class CdtIdePlugin implements Plugin<Project> {
-
-    void apply(Project project) {
-        project.apply(plugin: "binaries")
-        def metadataFileTasks = [addCreateProjectDescriptor(project), addCreateCprojectDescriptor(project)]
-
-        project.task("cleanCdt", type: Delete) {
-            delete metadataFileTasks*.outputs*.files
-        }
-
-        project.task("cdt", dependsOn: metadataFileTasks)
-    }
-
-    private addCreateProjectDescriptor(Project project) {
-        project.task("cdtProject", type: GenerateMetadataFileTask) {
-            inputFile = project.file(".project")
-            outputFile = project.file(".project")
-            factory { new ProjectDescriptor() }
-            onConfigure { new ProjectSettings(name: project.name).applyTo(it) }
-        }
-    }
-
-    private addCreateCprojectDescriptor(Project project) {
-        project.task("cdtCproject", type: GenerateMetadataFileTask) { task ->
-            
-            [project.executables, project.libraries]*.all { binary ->
-                if (binary.name == "main") {
-                    task.settings = new CprojectSettings(binary, project)
-                }
-            }
-            
-            doFirst {
-                if (task.settings == null) {
-                    throw new InvalidUserDataException("There is neither a main binary or library")
-                }
-            }
-            
-            inputs.files { task.settings.includeRoots }
-            inputFile = project.file(".cproject")
-            outputFile = project.file(".cproject")
-            factory { new CprojectDescriptor() }
-            onConfigure { descriptor ->
-                task.settings.applyTo(descriptor)
-            }
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
deleted file mode 100644
index 860c149..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
+++ /dev/null
@@ -1,115 +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.cpp.cdt.model
-
-import org.gradle.api.internal.xml.XmlTransformer
-import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
-
-/**
- * The actual .cproject descriptor file.
- */
-class CprojectDescriptor extends XmlPersistableConfigurationObject {
-
-    private static final boolean LINUX_NOT_MACOS = true
-    
-    static public final String GNU_COMPILER_TOOL_ID_PREFIX = "cdt.managedbuild.tool.gnu.cpp.compiler"
-    static public final String GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX = "gnu.cpp.compiler.option.include.paths"
-
-    // linux
-    static public final String GNU_LINKER_TOOL_ID_PREFIX = LINUX_NOT_MACOS ? "cdt.managedbuild.tool.gnu.cpp.linker" : "cdt.managedbuild.tool.macosx.cpp.linker.macosx"
-    static public final String GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX = LINUX_NOT_MACOS ? "gnu.cpp.link.option.userobjs" : "macosx.cpp.link.option.userobjs"
-
-    CprojectDescriptor() {
-        super(new XmlTransformer())
-    }
-
-    protected String getDefaultResourceName() {
-        LINUX_NOT_MACOS ? 'defaultCproject-linux.xml' : 'defaultCproject-macos.xml'
-    }
-
-    NodeList getConfigurations() {
-        new NodeList(xml.storageModule.cconfiguration.storageModule.findAll { it. at moduleId == "cdtBuildSystem" }.collect { it.configuration[0] })
-    }
-
-    NodeList getRootToolChains() {
-        new NodeList(configurations.folderInfo.findAll { it. at resourcePath == "" }).toolChain
-    }
-
-    NodeList getRootCppCompilerTools() {
-        new NodeList(rootToolChains.tool.findAll { isGnuCompilerTool(it) })
-    }
-
-    NodeList getRootCppLinkerTools() {
-        new NodeList(rootToolChains.tool.findAll { isGnuLinkerTool(it) })
-    }
-
-    boolean isGnuCompilerTool(Node node) {
-        node.name() == "tool" && node. at id.startsWith(GNU_COMPILER_TOOL_ID_PREFIX)
-    }
-
-    boolean isGnuLinkerTool(Node node) {
-        node.name() == "tool" && node. at id.startsWith(GNU_LINKER_TOOL_ID_PREFIX)
-    }
-
-    Node getOrCreateIncludePathsOption(compilerToolNode) {
-        if (!isGnuCompilerTool(compilerToolNode)) {
-            throw new IllegalArgumentException("Arg must be a gnu compiler tool def, was $compilerToolNode")
-        }
-
-        def includePathsOption = compilerToolNode.option.find { it. at id.startsWith(GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX) }
-        if (!includePathsOption) {
-            includePathsOption = compilerToolNode.appendNode(
-                "option", [
-                    id: createId(GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX),
-                    superClass: GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX,
-                    valueType: "includePath"
-                ]
-            )
-        }
-
-        includePathsOption
-    }
-
-    Node getOrCreateLibsOption(linkerToolNode) {
-        if (!isGnuLinkerTool(linkerToolNode)) {
-            throw new IllegalArgumentException("Arg must be a gnu linker tool def, was $linkerToolNode")
-        }
-
-        def libsOption = linkerToolNode.option.find { it. at id.startsWith(GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX) }
-        if (!libsOption) {
-            libsOption = linkerToolNode.appendNode(
-                "option", [
-                    id: createId(GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX),
-                    superClass: GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX,
-                    valueType: "userObjs"
-                ]
-            )
-        }
-
-        libsOption
-    }
-
-    String createId(String prefix) {
-        prefix + "." + new java.text.SimpleDateFormat("yyMMddHHmmssS").format(new Date())
-    }
-
-    protected void store(Node xml) {
-        transformAction {
-            StringBuilder xmlString = it.asString()
-            xmlString.insert(xmlString.indexOf("\n") + 1, "<?fileVersion 4.0.0?>\n")
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettings.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettings.groovy
deleted file mode 100644
index c39a8bb..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettings.groovy
+++ /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.plugins.cpp.cdt.model
-
-import org.gradle.api.file.ConfigurableFileCollection
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.plugins.cpp.CppSourceSet
-import org.gradle.plugins.binaries.model.*
-
-/**
- * Exposes a more logical view of the actual .cproject descriptor file
- */
-class CprojectSettings {
-
-    Binary binary
-    private final ConfigurableFileCollection includeRoots
-    private final ConfigurableFileCollection libs
-
-    CprojectSettings(Binary binary, ProjectInternal project) {
-        this.binary = binary
-        includeRoots = project.files()
-        libs = project.files()
-
-        binary.sourceSets.withType(HeaderExportingSourceSet).all {
-            includeRoots.builtBy(it.exportedHeaders) // have to manually add because we use srcDirs in from, not the real collection
-            includeRoots.from(it.exportedHeaders.srcDirs)
-        }
-
-        binary.sourceSets.withType(NativeDependencyCapableSourceSet).all {
-            it.nativeDependencySets.all {
-                this.includeRoots.from(it.includeRoots)
-                this.libs.from(it.files)
-            }
-        }
-
-        binary.sourceSets.withType(CppSourceSet).all { sourceSet ->
-            sourceSet.libs.all { lib ->
-                this.libs.from(lib.spec.outputFile)
-                this.libs.builtBy(lib.spec.task)
-                this.includeRoots.from(lib.headers.srcDirs)
-            }
-        }
-    }
-
-    void applyTo(CprojectDescriptor descriptor) {
-        if (binary) {
-            applyBinaryTo(descriptor)
-        } else {
-            throw new IllegalStateException("no binary set")
-        }
-    }
-
-    private applyBinaryTo(CprojectDescriptor descriptor) {
-        descriptor.rootCppCompilerTools.each { compiler ->
-            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
-            new LinkedList(includePathsOption.children()).each { includePathsOption.remove(it) }
-            includeRoots.each { includeRoot ->
-                includePathsOption.appendNode("listOptionValue", [builtIn: "false", value: includeRoot.absolutePath])
-            }
-        }
-
-        descriptor.rootCppLinkerTools.each { linker ->
-            def libsOption = descriptor.getOrCreateLibsOption(linker)
-            new LinkedList(libsOption.children()).each { libsOption.remove(it) }
-            libs.each { lib ->
-                libsOption.appendNode("listOptionValue", [builtIn: "false", value: lib.absolutePath])
-            }
-        }
-
-        def extension = binary.spec.extension ?: ""
-        def type 
-        if (binary instanceof Library) {
-            type = "org.eclipse.cdt.build.core.buildArtefactType.sharedLib"
-        } else if (binary instanceof Executable) {
-            type = "org.eclipse.cdt.build.core.buildArtefactType.exe"
-        } else {
-            throw new IllegalStateException("The binary $binary is of a type that we don't know about")
-        }
-        
-        descriptor.configurations.each { conf ->
-            conf. at buildArtefactType = type
-            conf. at artifactExtension = extension
-            def buildPropsPairs = conf. at buildProperties.split(",")
-            def buildProps = [:]
-            buildPropsPairs.each {
-                def parts = it.split("=", 2)
-                buildProps[parts[0]] = parts[1]
-            }
-            buildProps["org.eclipse.cdt.build.core.buildArtefactType"] = type
-            buildPropsPairs = buildProps.collect { k, v -> "$k=$v"}
-            conf. at buildProperties = buildPropsPairs.join(",")
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
deleted file mode 100644
index 7f32bb4..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
+++ /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.plugins.cpp.cdt.model
-
-import org.gradle.api.internal.xml.XmlTransformer
-import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
-
-/**
- * The actual .project descriptor file.
- */
-class ProjectDescriptor extends XmlPersistableConfigurationObject {
-
-    ProjectDescriptor() {
-        super(new XmlTransformer())
-    }
-
-    protected String getDefaultResourceName() {
-        'defaultProject.xml'
-    }
-
-    Node getOrCreate(String name) {
-        getOrCreate(xml, name)
-    }
-
-    Node findBuildCommand(Closure predicate) {
-        xml.buildSpec[0].buildCommand.find(predicate)
-    }
-
-    Node getOrCreate(Node parent, String name) {
-        def node = parent.get(name)
-        node ? node.first() : parent.appendNode(name)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectSettings.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectSettings.groovy
deleted file mode 100644
index 6a8726a..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectSettings.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.cdt.model
-
-/**
- * Gradle model element, the configurable parts of the .project file.
- */
-class ProjectSettings {
-    String name
-
-    /**
-     * Apply this logical model to the physical descriptor
-     */
-    void applyTo(ProjectDescriptor descriptor) {
-        descriptor.getOrCreate("name").value = name
-
-        // not anywhere close to right at all, very hardcoded.
-        def genMakeBuilderBuildCommand = descriptor.findBuildCommand { it.name[0].text() == "org.eclipse.cdt.managedbuilder.core.genmakebuilder" }
-        if (genMakeBuilderBuildCommand) {
-            def dict = genMakeBuilderBuildCommand.arguments[0].dictionary.find { it.key[0].text() == "org.eclipse.cdt.make.core.buildLocation" }
-            if (dict) {
-                dict.value[0].value = "\${workspace_loc:/$name/Debug}"
-            }
-        }
-
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
deleted file mode 100644
index 8f25b5e..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.cdt.tasks
-
-import org.gradle.api.internal.ClosureBackedAction
-import org.gradle.internal.Factory
-import org.gradle.listener.ActionBroadcast
-import org.gradle.plugins.cpp.cdt.model.CprojectSettings
-import org.gradle.plugins.ide.api.GeneratorTask
-import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject
-import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator
-
-class GenerateMetadataFileTask<T extends PersistableConfigurationObject> extends GeneratorTask<T> {
-
-    Factory<T> factory
-    ActionBroadcast<T> configures = new ActionBroadcast<T>()
-    CprojectSettings settings
-
-    GenerateMetadataFileTask() {
-        generator = new PersistableConfigurationObjectGenerator() {
-            public create() {
-                GenerateMetadataFileTask.this.factory.create();
-            }
-
-            public void configure(object) {
-                GenerateMetadataFileTask.this.configures.execute(object);
-            }
-        }
-    }
-
-    void factory(Closure factory) {
-        this.factory = factory as Factory
-    }
-
-    void onConfigure(Closure configure) {
-        configures.add(new ClosureBackedAction(configure))
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/AgainstLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/AgainstLibrary.java
deleted file mode 100644
index aba90f3..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/AgainstLibrary.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.compiler.capability;
-
-import org.gradle.plugins.binaries.model.Library;
-
-/**
- * Can compile against libraries
- */
-public interface AgainstLibrary {
-    
-    /**
-     * Compile against the given libs (collection must be live, i.e. changes respected)
-     */
-    void libs(Iterable<Library> libs);
-    
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/CompilesCpp.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/CompilesCpp.java
deleted file mode 100644
index 1165fb8..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/CompilesCpp.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.compiler.capability;
-
-import org.gradle.plugins.cpp.CppSourceSet;
-
-/**
- * Capable of compiling cpp source from a cpp source set
- */
-public interface CompilesCpp {
-
-    /**
-     * Use the source set
-     */
-    void from(CppSourceSet sourceSet);
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/StandardCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/StandardCppCompiler.java
deleted file mode 100644
index eee8d78..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/StandardCppCompiler.java
+++ /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.plugins.cpp.compiler.capability;
-
-/**
- * Wraps up the standard capabilities you'd expect a cpp compiler to have
- */
-public interface StandardCppCompiler extends AgainstLibrary, CompilesCpp {
-    
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/package-info.java
deleted file mode 100644
index bfb0501..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-/**
- * Interfaces for compiler capabilities for cpp
- */
-package org.gradle.plugins.cpp.compiler.capability;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
deleted file mode 100755
index b6e4738..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
+++ /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.plugins.cpp.compiler.internal;
-
-import groovy.lang.Closure;
-import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
-import org.gradle.api.internal.tasks.compile.ExecSpecBackedArgCollector;
-import org.gradle.api.internal.tasks.compile.SimpleWorkResult;
-import org.gradle.api.tasks.WorkResult;
-import org.gradle.internal.Factory;
-import org.gradle.plugins.cpp.internal.CppCompileSpec;
-import org.gradle.process.internal.ExecAction;
-import org.gradle.util.GFileUtils;
-
-import java.io.File;
-
-public class CommandLineCppCompiler<T extends CppCompileSpec> implements CppCompiler<T> {
-    private final File executable;
-    private final Factory<ExecAction> execActionFactory;
-    private final CompileSpecToArguments<T> toArguments;
-
-    public CommandLineCppCompiler(File executable, Factory<ExecAction> execActionFactory, CompileSpecToArguments<T> toArguments) {
-        this.executable = executable;
-        this.execActionFactory = execActionFactory;
-        this.toArguments = toArguments;
-    }
-
-    public WorkResult execute(T spec) {
-        File workDir = spec.getWorkDir();
-
-        ensureDirsExist(workDir, spec.getOutputFile().getParentFile());
-
-        ExecAction compiler = execActionFactory.create();
-        compiler.executable(executable);
-        compiler.workingDir(workDir);
-
-        toArguments.collectArguments(spec, new ExecSpecBackedArgCollector(compiler));
-
-        // Apply all of the settings
-        for (Closure closure : spec.getSettings()) {
-            closure.call(compiler);
-        }
-
-        compiler.execute();
-        return new SimpleWorkResult(true);
-    }
-
-    private void ensureDirsExist(File... dirs) {
-        for (File dir : dirs) {
-            GFileUtils.mkdirs(dir);
-        }
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerAdapter.java
deleted file mode 100644
index 24e5dfa..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerAdapter.java
+++ /dev/null
@@ -1,59 +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.cpp.compiler.internal;
-
-import org.gradle.internal.Factory;
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.plugins.binaries.model.internal.CompilerAdapter;
-import org.gradle.plugins.cpp.internal.CppCompileSpec;
-import org.gradle.process.internal.ExecAction;
-
-import java.io.File;
-
-abstract public class CommandLineCppCompilerAdapter<T extends CppCompileSpec> implements CompilerAdapter<T> {
-
-    private final File executable;
-    private final Factory<ExecAction> execActionFactory;
-    private OperatingSystem operatingSystem;
-
-    protected CommandLineCppCompilerAdapter(String executableName, OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
-        this(operatingSystem.findInPath(executableName), operatingSystem, execActionFactory);
-    }
-
-    protected CommandLineCppCompilerAdapter(File executable, OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
-        this.executable = executable;
-        this.operatingSystem = operatingSystem;
-        this.execActionFactory = execActionFactory;
-    }
-
-    protected File getExecutable() {
-        return executable;
-    }
-
-    protected Factory<ExecAction> getExecActionFactory() {
-        return execActionFactory;
-    }
-
-    public OperatingSystem getOperatingSystem() {
-        return operatingSystem;
-    }
-
-    public boolean isAvailable() {
-        return executable != null && executable.exists();
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
deleted file mode 100644
index 1ae0174..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
+++ /dev/null
@@ -1,56 +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.cpp.compiler.internal;
-
-import org.gradle.api.Transformer;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.tasks.compile.ArgCollector;
-import org.gradle.api.internal.tasks.compile.ArgWriter;
-import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
-import org.gradle.plugins.cpp.internal.CppCompileSpec;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-public class CommandLineCppCompilerArgumentsToOptionFile<T extends CppCompileSpec> implements CompileSpecToArguments<T> {
-
-    private final Transformer<ArgWriter, PrintWriter> argWriterFactory;
-    private final CompileSpecToArguments<T> toArguments;
-
-    public CommandLineCppCompilerArgumentsToOptionFile(Transformer<ArgWriter, PrintWriter> argWriterFactory, CompileSpecToArguments<T> toArguments) {
-        this.argWriterFactory = argWriterFactory;
-        this.toArguments = toArguments;
-    }
-
-    public void collectArguments(T spec, ArgCollector collector) {
-        File optionsFile = new File(spec.getWorkDir(), "compiler-options.txt");
-        try {
-            PrintWriter writer = new PrintWriter(optionsFile);
-            ArgWriter argWriter = argWriterFactory.transform(writer);
-            try {
-                toArguments.collectArguments(spec, argWriter);
-            } finally {
-                writer.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(String.format("Could not write compiler options file '%s'.", optionsFile.getAbsolutePath()), e);
-        }
-
-        collector.args(String.format("@%s", optionsFile.getAbsolutePath()));
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CppCompiler.java
deleted file mode 100644
index b4347c2..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CppCompiler.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.plugins.cpp.compiler.internal;
-
-import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.plugins.cpp.internal.CppCompileSpec;
-
-public interface CppCompiler<T extends CppCompileSpec> extends Compiler<T> {
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
deleted file mode 100755
index 65efb5b..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
+++ /dev/null
@@ -1,218 +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.cpp.gpp
-
-import org.gradle.api.file.FileCollection
-import org.gradle.api.file.SourceDirectorySet
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.tasks.compile.Compiler
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.plugins.binaries.model.Binary
-import org.gradle.plugins.binaries.model.CompileSpec
-import org.gradle.plugins.binaries.model.Library
-import org.gradle.plugins.binaries.model.internal.CompileTaskAware
-import org.gradle.plugins.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.tasks.TaskDependency
-import org.gradle.api.internal.tasks.DefaultTaskDependency
-import org.gradle.plugins.cpp.CppCompile
-
-class GppCompileSpec implements CompileSpec, StandardCppCompiler, CompileTaskAware, CppCompileSpec {
-    Binary binary
-
-    private CppCompile task
-    List<Closure> settings = []
-
-    String outputFileName
-    String baseName
-    String extension
-    private final Compiler<? super GppCompileSpec> compiler
-    private final ProjectInternal project
-    private final ConfigurableFileCollection libs
-    private final ConfigurableFileCollection includes
-    private final ConfigurableFileCollection source
-
-    GppCompileSpec(Binary binary, Compiler<? super GppCompileSpec> compiler, ProjectInternal project) {
-        this.binary = binary
-        this.compiler = compiler
-        this.project = project
-        libs = project.files()
-        includes = project.files()
-        source = project.files()
-    }
-
-    void configure(CppCompile task) {
-        this.task = task
-        task.spec = this
-        task.compiler = compiler
-
-        task.onlyIf { !task.inputs.files.empty }
-        task.outputs.file { getOutputFile() }
-
-        // problem: will break if a source set is removed
-        binary.sourceSets.withType(CppSourceSet).all { from(it) }
-    }
-
-    String getName() {
-        binary.name
-    }
-
-    TaskDependency getBuildDependencies() {
-        return new DefaultTaskDependency().add(task)
-    }
-
-    File getWorkDir() {
-        project.file "$project.buildDir/compileWork/$name"
-    }
-
-    Iterable<File> getLibs() {
-        return libs
-    }
-
-    Iterable<File> getIncludeRoots() {
-        return includes
-    }
-
-    Iterable<File> getSource() {
-        return source
-    }
-
-    /**
-     * @deprecated No replacement
-     */
-    @Deprecated
-    String getExtension() {
-        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.getExtension()")
-        return extension
-    }
-
-    /**
-     * @deprecated No replacement
-     */
-    @Deprecated
-    void setExtension(String extension) {
-        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.setExtension()")
-        this.extension = extension
-    }
-
-    /**
-     * @deprecated No replacement
-     */
-    @Deprecated
-    void setBinary(Binary binary) {
-        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.setBinary()")
-        this.binary = binary
-    }
-
-    File getOutputFile() {
-        project.file "$project.buildDir/binaries/${getOutputFileName()}"
-    }
-
-    String getOutputFileName() {
-        if (outputFileName) {
-            return outputFileName
-        } else if (extension) {
-            return "${getBaseName()}.${extension}"
-        } else {
-            return getDefaultOutputFileName()
-        }
-    }
-
-    protected String getDefaultOutputFileName() {
-        return OperatingSystem.current().getExecutableName(getBaseName())
-    }
-
-    String getBaseName() {
-        baseName ?: name
-    }
-
-    void setting(Closure closure) {
-        settings << closure
-    }
-
-    void from(CppSourceSet sourceSet) {
-        includes sourceSet.exportedHeaders
-        source sourceSet.source
-        libs sourceSet.libs
-
-        sourceSet.nativeDependencySets.all { deps ->
-            includes deps.includeRoots
-            source deps.files
-        }
-    }
-
-    void includes(SourceDirectorySet dirs) {
-        task.inputs.files dirs
-        includes.from({dirs.srcDirs})
-    }
-
-    // special filecollection version because filecollection may be buildable
-    void includes(FileCollection includeRoots) {
-        task.inputs.files includeRoots
-        includes.from(includeRoots)
-    }
-
-    void includes(Iterable<File> includeRoots) {
-        for (File includeRoot in includeRoots) {
-            task.inputs.dir(includeRoot)
-        }
-        includes.from(includeRoots)
-    }
-
-    void source(Iterable<File> files) {
-        task.inputs.files files
-        source.from files
-    }
-
-    // special filecollection version because filecollection may be buildable
-    void source(FileCollection files) {
-        task.inputs.source files
-        source.from files
-    }
-
-    void libs(Iterable<Library> libs) {
-        task.dependsOn libs
-        this.libs.from({ libs*.spec*.outputFile })
-        includes(project.files { libs*.headers*.srcDirs })
-    }
-
-    void args(Object... args) {
-        setting {
-            it.args args
-        }
-    }
-
-    /**
-     * @deprecated No replacement
-     */
-    @Deprecated
-    void sharedLibrary() {
-        DeprecationLogger.nagUserOfDiscontinuedMethod("CompileSpec.sharedLibrary()")
-    }
-
-    /**
-     * @deprecated No replacement
-     */
-    @Deprecated
-    void compile() {
-        DeprecationLogger.nagUserOfDiscontinuedMethod("CompileSpec.compile()")
-        compiler.execute(this)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
deleted file mode 100644
index 433c2d4..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.gpp
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.internal.Factory
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.plugins.binaries.BinariesPlugin
-import org.gradle.plugins.binaries.model.CompilerRegistry
-import org.gradle.plugins.cpp.gpp.internal.GppCompilerAdapter
-import org.gradle.process.internal.DefaultExecAction
-import org.gradle.process.internal.ExecAction
-
-import javax.inject.Inject
-
-/**
- * A {@link Plugin} which makes the <a href="http://gcc.gnu.org/">GNU G++ compiler</a> available for compiling C/C++ code.
- */
-class GppCompilerPlugin implements Plugin<Project> {
-    private final FileResolver fileResolver
-
-    @Inject
-    GppCompilerPlugin(FileResolver fileResolver) {
-        this.fileResolver = fileResolver
-    }
-
-    void apply(Project project) {
-        project.plugins.apply(BinariesPlugin)
-        project.extensions.getByType(CompilerRegistry).add(new GppCompilerAdapter(
-                OperatingSystem.current(),
-                new Factory<ExecAction>() {
-                    ExecAction create() {
-                        new DefaultExecAction(fileResolver)
-                    }
-                }))
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpec.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpec.groovy
deleted file mode 100755
index e1ea17e..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpec.groovy
+++ /dev/null
@@ -1,40 +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.cpp.gpp
-
-import org.gradle.plugins.binaries.model.LibraryCompileSpec
-import org.gradle.plugins.binaries.model.Binary
-import org.gradle.api.internal.tasks.compile.Compiler
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.api.internal.project.ProjectInternal
-
-class GppLibraryCompileSpec extends GppCompileSpec implements LibraryCompileSpec {
-    String installName
-
-    GppLibraryCompileSpec(Binary binary, Compiler<? super GppCompileSpec> compiler, ProjectInternal project) {
-        super(binary, compiler, project)
-    }
-
-    @Override
-    protected String getDefaultOutputFileName() {
-        return OperatingSystem.current().getSharedLibraryName(getBaseName())
-    }
-
-    String getInstallName() {
-        return installName ?: getOutputFileName()
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecFactory.java
deleted file mode 100644
index 16a1ac6..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecFactory.java
+++ /dev/null
@@ -1,42 +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.cpp.gpp.internal;
-
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.plugins.binaries.model.Binary;
-import org.gradle.plugins.binaries.model.Library;
-import org.gradle.plugins.binaries.model.internal.BinaryCompileSpec;
-import org.gradle.plugins.binaries.model.internal.BinaryCompileSpecFactory;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-import org.gradle.plugins.cpp.gpp.GppLibraryCompileSpec;
-
-public class GppCompileSpecFactory implements BinaryCompileSpecFactory {
-    private ProjectInternal project;
-
-    public GppCompileSpecFactory(ProjectInternal project) {
-        this.project = project;
-    }
-
-    public BinaryCompileSpec create(Binary binary, org.gradle.api.internal.tasks.compile.Compiler<?> compiler) {
-        Compiler<? super GppCompileSpec> typed = (Compiler<? super GppCompileSpec>) compiler;
-        if (binary instanceof Library) {
-            return new GppLibraryCompileSpec(binary, typed, project);
-        }
-        return new GppCompileSpec(binary, typed, project);
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
deleted file mode 100644
index 1d44fff..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
+++ /dev/null
@@ -1,55 +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.cpp.gpp.internal;
-
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.plugins.binaries.model.LibraryCompileSpec;
-import org.gradle.api.internal.tasks.compile.ArgCollector;
-import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-
-import java.io.File;
-
-public class GppCompileSpecToArguments implements CompileSpecToArguments<GppCompileSpec> {
-
-    public void collectArguments(GppCompileSpec spec, ArgCollector collector) {
-        collector.args("-o", spec.getOutputFile().getAbsolutePath());
-        if (spec instanceof LibraryCompileSpec) {
-            LibraryCompileSpec librarySpec = (LibraryCompileSpec) spec;
-            collector.args("-shared");
-            if (!OperatingSystem.current().isWindows()) {
-                collector.args("-fPIC");
-                if (OperatingSystem.current().isMacOsX()) {
-                    collector.args("-Wl,-install_name," + librarySpec.getInstallName());
-                } else {
-                    collector.args("-Wl,-soname," + librarySpec.getInstallName());
-                }
-            }
-        }
-        for (File file : spec.getIncludeRoots()) {
-            collector.args("-I");
-            collector.args(file.getAbsolutePath());
-        }
-        for (File file : spec.getSource()) {
-            collector.args(file.getAbsolutePath());
-        }
-        for (File file : spec.getLibs()) {
-            collector.args(file.getAbsolutePath());
-        }
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
deleted file mode 100755
index 8b5dbf7..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
+++ /dev/null
@@ -1,44 +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.cpp.gpp.internal;
-
-import org.gradle.internal.Factory;
-import org.gradle.api.internal.tasks.compile.ArgWriter;
-import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompiler;
-import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerArgumentsToOptionFile;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-import org.gradle.process.internal.ExecAction;
-
-import java.io.File;
-
-public class GppCompiler extends CommandLineCppCompiler<GppCompileSpec> {
-
-    public GppCompiler(File executable, Factory<ExecAction> execActionFactory, boolean useCommandFile) {
-        super(executable, execActionFactory, useCommandFile ? viaCommandFile() : withoutCommandFile());
-    }
-
-    private static GppCompileSpecToArguments withoutCommandFile() {
-        return new GppCompileSpecToArguments();
-    }
-
-    private static CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec> viaCommandFile() {
-        return new CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec>(
-            ArgWriter.unixStyleFactory(), new GppCompileSpecToArguments()
-        );
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
deleted file mode 100755
index 4763183..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
+++ /dev/null
@@ -1,106 +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.cpp.gpp.internal;
-
-import org.gradle.api.Transformer;
-import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.internal.Factory;
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.plugins.binaries.model.Binary;
-import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerAdapter;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-import org.gradle.plugins.cpp.gpp.internal.version.GppVersionDeterminer;
-import org.gradle.process.internal.ExecAction;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-
-/**
- * Compiler adapter for g++
- */
-public class GppCompilerAdapter extends CommandLineCppCompilerAdapter<GppCompileSpec> {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(GppCompilerAdapter.class);
-
-    static final String EXECUTABLE = "g++";
-    
-    public static final String NAME = "gpp";
-
-    private boolean determinedVersion;
-    private String version;
-
-    private final Transformer<String, File> versionDeterminer;
-
-    public GppCompilerAdapter(OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
-        this(operatingSystem, execActionFactory, new GppVersionDeterminer());
-    }
-
-    GppCompilerAdapter(OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory, Transformer<String, File> versionDeterminer) {
-        super(EXECUTABLE, operatingSystem, execActionFactory);
-        this.versionDeterminer = versionDeterminer;
-    }
-
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("GNU G++ (%s)", getOperatingSystem().getExecutableName(EXECUTABLE));
-    }
-
-    public boolean isAvailable() {
-        String version = getVersion();
-        return version != null;
-    }
-
-    public Compiler<GppCompileSpec> createCompiler(Binary binary) {
-        String version = getVersion();
-        if (version == null) {
-            throw new IllegalStateException("Cannot create gpp compiler when it is not available");
-        }
-        
-        String[] components = version.split("\\.");
-
-        int majorVersion;
-        try {
-            majorVersion = Integer.valueOf(components[0]);
-        } catch (NumberFormatException e) {
-            throw new IllegalStateException(String.format("Unable to determine major g++ version from version number %s.", version), e);
-        }
-
-        return new GppCompiler(getExecutable(), getExecActionFactory(), majorVersion >= 4);
-    }
-
-    private String getVersion() {
-        if (!determinedVersion) {
-            determinedVersion = true;
-            version = determineVersion(getExecutable());
-            if (version == null) {
-                LOGGER.info("Did not find {} on system", EXECUTABLE);
-            } else {
-                LOGGER.info("Found {} with version {}", EXECUTABLE, version);
-            }
-        }
-        return version;
-    }
-
-    private String determineVersion(File executable) {
-        return executable == null ? null : versionDeterminer.transform(executable);
-    }
-    
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminer.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminer.java
deleted file mode 100644
index efc9863..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminer.java
+++ /dev/null
@@ -1,105 +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.cpp.gpp.internal.version;
-
-import org.gradle.api.Transformer;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.internal.Factory;
-import org.gradle.process.ExecResult;
-import org.gradle.process.internal.ExecHandle;
-import org.gradle.process.internal.ExecHandleBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Given a File pointing to an (existing) g++ binary, extracts the version number by running with -v and scraping the output.
- */
-public class GppVersionDeterminer implements Transformer<String, File> {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(GppVersionDeterminer.class);
-
-    private final Transformer<String, String> outputScraper;
-    private final Transformer<String, File> outputProducer;
-
-    public GppVersionDeterminer() {
-        this(new GppVersionOutputProducer(new Factory<ExecHandleBuilder>() {
-            public ExecHandleBuilder create() {
-                return new ExecHandleBuilder(new IdentityFileResolver());
-            }
-        }), new GppVersionOutputScraper());
-    }
-
-    GppVersionDeterminer(Transformer<String, File> outputProducer, Transformer<String, String> outputScraper) {
-        this.outputProducer = outputProducer;
-        this.outputScraper = outputScraper;
-    }
-
-    static class GppVersionOutputScraper implements Transformer<String, String> {
-        public String transform(String output) {
-            Pattern pattern = Pattern.compile(".*gcc version (\\S+).*", Pattern.DOTALL);
-            Matcher matcher = pattern.matcher(output);
-            if (matcher.matches()) {
-                String scrapedVersion = matcher.group(1);
-                LOGGER.debug("Extracted version {} from g++ -v output", scrapedVersion);
-                return scrapedVersion;
-            } else {
-                LOGGER.warn("Unable to extract g++ version number from \"{}\" with pattern \"{}\"", output, pattern);
-                return null;
-            }
-        }
-    }
-
-    static class GppVersionOutputProducer implements Transformer<String, File> {
-        
-        private final Factory<ExecHandleBuilder> execHandleBuilderFactory;
-
-        GppVersionOutputProducer(Factory<ExecHandleBuilder> execHandleBuilderFactory) {
-            this.execHandleBuilderFactory = execHandleBuilderFactory;
-        }
-
-        public String transform(File gppBinary) {
-            ExecHandleBuilder exec = execHandleBuilderFactory.create();
-            exec.executable(gppBinary.getAbsolutePath());
-            exec.setWorkingDir(gppBinary.getParentFile());
-            exec.args("-v");
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            exec.setErrorOutput(baos);
-            ExecHandle handle = exec.build();
-            ExecResult result = handle.start().waitForFinish();
-
-            int exitValue = result.getExitValue();
-            if (exitValue == 0) {
-                String output = new String(baos.toByteArray());
-                LOGGER.debug("Output from '{} -v {}", gppBinary.getPath(), output);    
-                return output;                
-            } else {
-                LOGGER.warn("Executing '{} -v' return exit code {}, cannot use", gppBinary.getPath(), exitValue);
-                return null;
-            }
-        }
-    }
-
-    public String transform(File gppBinary) {
-        String output = outputProducer.transform(gppBinary);
-        return output == null ? null : outputScraper.transform(output);
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/package-info.java
deleted file mode 100644
index 23db2ad..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-/**
- * Integration with the gpp (gcc frontend) compiler.
- */
-package org.gradle.plugins.cpp.gpp;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/CppCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/CppCompileSpec.java
deleted file mode 100644
index 1699745..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/CppCompileSpec.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.plugins.cpp.internal;
-
-import groovy.lang.Closure;
-import org.gradle.plugins.binaries.model.internal.BinaryCompileSpec;
-import org.gradle.plugins.cpp.compiler.capability.StandardCppCompiler;
-
-import java.io.File;
-import java.util.List;
-
-public interface CppCompileSpec extends StandardCppCompiler, BinaryCompileSpec {
-
-    File getWorkDir();
-
-    // This needs to go
-    List<Closure> getSettings();
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/DefaultCppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/DefaultCppSourceSet.java
deleted file mode 100644
index d7c1f32..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/DefaultCppSourceSet.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.internal;
-
-import org.gradle.plugins.cpp.CppSourceSet;
-
-import org.gradle.plugins.binaries.model.Library;
-import org.gradle.plugins.binaries.model.NativeDependencySet;
-import org.gradle.plugins.binaries.model.internal.ConfigurationBasedNativeDependencySet;
-
-import org.gradle.api.DomainObjectSet;
-import org.gradle.api.file.SourceDirectorySet;
-
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.file.DefaultSourceDirectorySet;
-import org.gradle.util.ConfigureUtil;
-
-import groovy.lang.Closure;
-import java.util.Map;
-
-public class DefaultCppSourceSet implements CppSourceSet {
-
-    private final String name;
-
-    private final DefaultSourceDirectorySet exportedHeaders;
-    private final DefaultSourceDirectorySet source;
-    private final DefaultDomainObjectSet<Library> libs;
-    private final DefaultDomainObjectSet<NativeDependencySet> nativeDependencySets;
-    private final ConfigurationBasedNativeDependencySet configurationDependencySet;
-
-    public DefaultCppSourceSet(String name, ProjectInternal project) {
-        this.name = name;
-
-        this.exportedHeaders = new DefaultSourceDirectorySet("exported headers", project.getFileResolver());
-        this.source = new DefaultSourceDirectorySet("source", project.getFileResolver());
-        this.libs = new DefaultDomainObjectSet<Library>(Library.class);
-        this.nativeDependencySets = new DefaultDomainObjectSet<NativeDependencySet>(NativeDependencySet.class);
-        this.configurationDependencySet = new ConfigurationBasedNativeDependencySet(project, name);
-        
-        nativeDependencySets.add(configurationDependencySet);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public SourceDirectorySet getExportedHeaders() {
-        return exportedHeaders;
-    }
-
-    public DefaultCppSourceSet exportedHeaders(Closure closure) {
-        ConfigureUtil.configure(closure, exportedHeaders);
-        return this;
-    }
-
-    public SourceDirectorySet getSource() {
-        return source;
-    }
-
-    public DefaultCppSourceSet source(Closure closure) {
-        ConfigureUtil.configure(closure, source);
-        return this;
-    }
-
-    public DomainObjectSet<Library> getLibs() {
-        return libs;
-    }
-
-    public DomainObjectSet<NativeDependencySet> getNativeDependencySets() {
-        return nativeDependencySets;
-    }
-
-    public void dependency(Map<?, ?> dep) {
-        configurationDependencySet.add(dep);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
deleted file mode 100755
index a64ea77..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
+++ /dev/null
@@ -1,61 +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.cpp.msvcpp
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.internal.Factory
-import org.gradle.plugins.binaries.BinariesPlugin
-import org.gradle.plugins.binaries.model.CompilerRegistry
-import org.gradle.plugins.cpp.msvcpp.internal.VisualCppCompilerAdapter
-import org.gradle.process.internal.ExecAction
-import org.gradle.process.internal.DefaultExecAction
-import org.gradle.internal.os.OperatingSystem
-
-import javax.inject.Inject
-
-/**
- * A {@link Plugin} which makes the Microsoft Visual C++ compiler available to compile C/C++ code.
- */
-class MicrosoftVisualCppPlugin implements Plugin<Project> {
-    private final FileResolver fileResolver;
-
-    @Inject
-    MicrosoftVisualCppPlugin(FileResolver fileResolver) {
-        this.fileResolver = fileResolver
-    }
-
-    void apply(Project project) {
-        project.plugins.apply(BinariesPlugin)
-
-        if (!OperatingSystem.current().windows) {
-            return
-        }
-
-        project.extensions.getByType(CompilerRegistry).add(new VisualCppCompilerAdapter(
-                OperatingSystem.current(),
-                new Factory<ExecAction>() {
-                    ExecAction create() {
-                        new DefaultExecAction(fileResolver)
-                    }
-                }
-        ))
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
deleted file mode 100644
index 91ba77f..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
+++ /dev/null
@@ -1,47 +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.cpp.msvcpp.internal;
-
-import org.gradle.plugins.binaries.model.LibraryCompileSpec;
-import org.gradle.api.internal.tasks.compile.ArgCollector;
-import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-
-import java.io.File;
-
-public class VisualCppCompileSpecToArguments implements CompileSpecToArguments<GppCompileSpec> {
-
-    public void collectArguments(GppCompileSpec spec, ArgCollector collector) {
-        collector.args("/nologo");
-        collector.args("/EHsc");
-        collector.args("/Fe" + spec.getOutputFile().getAbsolutePath());
-        if (spec instanceof LibraryCompileSpec) {
-            collector.args("/LD");
-        }
-        for (File file : spec.getIncludeRoots()) {
-            collector.args("/I", file.getAbsolutePath());
-        }
-        for (File file : spec.getSource()) {
-            collector.args(file);
-        }
-        // Link options need to be on one line in the options file
-        for (File file : spec.getLibs()) {
-            collector.args("/link", file.getAbsolutePath().replaceFirst("\\.dll$", ".lib"));
-        }
-
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java
deleted file mode 100755
index 25767dc..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.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.plugins.cpp.msvcpp.internal;
-
-import org.gradle.internal.Factory;
-import org.gradle.api.internal.tasks.compile.ArgWriter;
-import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompiler;
-import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerArgumentsToOptionFile;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-import org.gradle.process.internal.ExecAction;
-
-import java.io.File;
-
-class VisualCppCompiler extends CommandLineCppCompiler<GppCompileSpec> {
-
-    VisualCppCompiler(File executable, Factory<ExecAction> execActionFactory) {
-        super(executable, execActionFactory, new CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec>(
-                ArgWriter.windowsStyleFactory(), new VisualCppCompileSpecToArguments()
-        ));
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompilerAdapter.java
deleted file mode 100755
index 6fb7532..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompilerAdapter.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.plugins.cpp.msvcpp.internal;
-
-import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.internal.Factory;
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.plugins.binaries.model.Binary;
-import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerAdapter;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-import org.gradle.process.internal.ExecAction;
-
-public class VisualCppCompilerAdapter extends CommandLineCppCompilerAdapter<GppCompileSpec> {
-
-    static final String EXECUTABLE = "cl.exe";
-
-    public VisualCppCompilerAdapter(OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
-        super(EXECUTABLE, operatingSystem, execActionFactory);
-    }
-
-    public String getName() {
-        return "visualCpp";
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Visual C++ (%s)", getOperatingSystem().getExecutableName(EXECUTABLE));
-    }
-
-    public boolean isAvailable() {
-        return getOperatingSystem().isWindows() && super.isAvailable();
-    }
-
-    public Compiler<GppCompileSpec> createCompiler(Binary binary) {
-        return new VisualCppCompiler(getExecutable(), getExecActionFactory());
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/package-info.java
deleted file mode 100644
index 578bd12..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/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.
- */
-
-/**
- * A {@link org.gradle.api.Plugin} for building C++ projects with Gradle.
- */
-package org.gradle.plugins.cpp;
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/assembler.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/assembler.properties
new file mode 100644
index 0000000..4746f21
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/assembler.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.nativebinaries.language.assembler.plugins.AssemblerPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/binaries.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/binaries.properties
deleted file mode 100644
index fe8bfdc..0000000
--- a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/binaries.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=org.gradle.plugins.binaries.BinariesPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/c.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/c.properties
new file mode 100644
index 0000000..dc3fd3c
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/c.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.nativebinaries.language.c.plugins.CPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-exe.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-exe.properties
deleted file mode 100644
index 23e9515..0000000
--- a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-exe.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=org.gradle.plugins.cpp.CppExeConventionPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-lib.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-lib.properties
deleted file mode 100644
index 7ca7910..0000000
--- a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-lib.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=org.gradle.plugins.cpp.CppLibConventionPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties
index cad2a5f..9108bc0 100644
--- a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties
@@ -1 +1 @@
-implementation-class=org.gradle.plugins.cpp.CppPlugin
\ No newline at end of file
+implementation-class=org.gradle.nativebinaries.language.cpp.plugins.CppPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cunit.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cunit.properties
new file mode 100644
index 0000000..8b6f37e
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cunit.properties
@@ -0,0 +1,17 @@
+#
+# Copyright 2013 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+implementation-class=org.gradle.nativebinaries.test.cunit.plugins.CUnitPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties
index 7ec9e10..2bfae14 100644
--- a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties
@@ -1 +1 @@
-implementation-class=org.gradle.plugins.cpp.cdt.CdtIdePlugin
\ No newline at end of file
+implementation-class=org.gradle.ide.cdt.CdtIdePlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/gpp-compiler.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/gpp-compiler.properties
deleted file mode 100644
index a135aac..0000000
--- a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/gpp-compiler.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=org.gradle.plugins.cpp.gpp.GppCompilerPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/native-binaries.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/native-binaries.properties
new file mode 100644
index 0000000..838bbdd
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/native-binaries.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.nativebinaries.plugins.NativeBinariesPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/objective-c.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/objective-c.properties
new file mode 100644
index 0000000..a190690
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/objective-c.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.nativebinaries.language.objectivec.plugins.ObjectiveCPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/objective-cpp.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/objective-cpp.properties
new file mode 100644
index 0000000..9e9112d
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/objective-cpp.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.nativebinaries.language.objectivecpp.plugins.ObjectiveCppPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/visual-studio.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/visual-studio.properties
new file mode 100644
index 0000000..4bcff94
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/visual-studio.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.ide.visualstudio.plugins.VisualStudioPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/windows-resources.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/windows-resources.properties
new file mode 100644
index 0000000..b41a378
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/windows-resources.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.nativebinaries.language.rc.plugins.WindowsResourcesPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry b/subprojects/cpp/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
new file mode 100644
index 0000000..71acde7
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
@@ -0,0 +1 @@
+org.gradle.nativebinaries.internal.NativeBinaryServices
diff --git a/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-linux.xml b/subprojects/cpp/src/main/resources/org/gradle/ide/cdt/model/defaultCproject-linux.xml
similarity index 100%
rename from subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-linux.xml
rename to subprojects/cpp/src/main/resources/org/gradle/ide/cdt/model/defaultCproject-linux.xml
diff --git a/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-macos.xml b/subprojects/cpp/src/main/resources/org/gradle/ide/cdt/model/defaultCproject-macos.xml
similarity index 100%
rename from subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-macos.xml
rename to subprojects/cpp/src/main/resources/org/gradle/ide/cdt/model/defaultCproject-macos.xml
diff --git a/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultProject.xml b/subprojects/cpp/src/main/resources/org/gradle/ide/cdt/model/defaultProject.xml
similarity index 100%
rename from subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultProject.xml
rename to subprojects/cpp/src/main/resources/org/gradle/ide/cdt/model/defaultProject.xml
diff --git a/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.sln b/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.sln
new file mode 100644
index 0000000..bf0a1d8
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.sln
@@ -0,0 +1,2 @@
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
diff --git a/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.vcxproj b/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.vcxproj
new file mode 100644
index 0000000..ce7d8b1
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.vcxproj
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations"/>
+  <PropertyGroup Label="Globals">
+    <Keyword>MakeFileProj</Keyword>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <PropertyGroup Label="UserMacros" />
+  <ItemGroup Label="Sources">
+  </ItemGroup>
+  <ItemGroup Label="Headers">
+  </ItemGroup>
+  <ItemGroup Label="References">
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project>
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.vcxproj.filters b/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.vcxproj.filters
new file mode 100644
index 0000000..9f41a07
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/ide/visualstudio/tasks/internal/default.vcxproj.filters
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <ItemGroup Label="Filters">
+      <Filter Include="Source Files">
+        <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+      </Filter>
+      <Filter Include="Header Files">
+        <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+      </Filter>
+      <Filter Include="Resource Files">
+        <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
+      </Filter>
+    </ItemGroup>
+    <ItemGroup Label="Sources"/>
+    <ItemGroup Label="Headers"/>
+</Project>
diff --git a/subprojects/cpp/src/main/resources/org/gradle/nativebinaries/test/cunit/tasks/gradle_cunit_main.c b/subprojects/cpp/src/main/resources/org/gradle/nativebinaries/test/cunit/tasks/gradle_cunit_main.c
new file mode 100644
index 0000000..d50ee0d
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/nativebinaries/test/cunit/tasks/gradle_cunit_main.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <CUnit/Automated.h>
+#include <CUnit/Basic.h>
+#include <stdio.h>
+#include "gradle_cunit_register.h"
+
+/*
+ *  Generated launcher for CUnit tests. All tests and suites must be registered in a single method:
+ *      void gradle_cunit_register();
+ */
+int main() {
+    int failureCount;
+
+    CU_initialize_registry();
+
+    gradle_cunit_register();
+
+    CU_list_tests_to_file();
+    CU_automated_run_tests();
+    failureCount = CU_get_number_of_failures();
+
+    // Write test failures to the console
+    if (failureCount > 0) {
+        printf("\nThere were test failures:");
+        CU_basic_show_failures(CU_get_failure_list());
+        printf("\n\n");
+    }
+
+    CU_cleanup_registry();
+
+    // TODO Wire a test listener and use it to generate a test event stream and binary results (don't use Automated)
+
+    return failureCount == 0 ? 0 : -1;
+}
diff --git a/subprojects/cpp/src/main/resources/org/gradle/nativebinaries/test/cunit/tasks/gradle_cunit_register.h b/subprojects/cpp/src/main/resources/org/gradle/nativebinaries/test/cunit/tasks/gradle_cunit_register.h
new file mode 100644
index 0000000..9b69c51
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/nativebinaries/test/cunit/tasks/gradle_cunit_register.h
@@ -0,0 +1,4 @@
+/*
+ * Called by the Gradle CUnit launcher to register all CUnit tests.
+ */
+void gradle_cunit_register();
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/cdt/model/CprojectSettingsSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/cdt/model/CprojectSettingsSpec.groovy
new file mode 100644
index 0000000..a76d9fd
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/cdt/model/CprojectSettingsSpec.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.model
+
+import spock.lang.*
+
+import org.gradle.api.Project
+import org.gradle.util.TestUtil
+
+// very loose test, but I'm not expecting it to stay around
+ at Ignore
+class CprojectSettingsSpec extends Specification {
+
+    Project project = TestUtil.createRootProject()
+
+    def descriptor = new CprojectDescriptor()
+    def settings = new CprojectSettings()
+
+    def "wire in includes"() {
+        given:
+        project.apply plugin: 'cpp-exe'
+        settings.binary = project.executables.main
+        descriptor.loadDefaults()
+
+        expect:
+        descriptor.getRootCppCompilerTools().each { compiler ->
+            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
+            assert includePathsOption.listOptionValue.size() == 0
+        }
+
+        when:
+        settings.applyTo(descriptor)
+        def baos = new ByteArrayOutputStream()
+        descriptor.store(baos)
+        descriptor.load(new ByteArrayInputStream(baos.toByteArray()))
+
+        then:
+        descriptor.getRootCppCompilerTools().each { compiler ->
+            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
+            assert includePathsOption.listOptionValue.size() == 1
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/cdt/model/ProjectDescriptorSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/cdt/model/ProjectDescriptorSpec.groovy
new file mode 100644
index 0000000..32c3d13
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/cdt/model/ProjectDescriptorSpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.ide.cdt.model
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectDescriptorSpec extends Specification {
+
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    ProjectDescriptor descriptor = new ProjectDescriptor()
+
+    def "method"() {
+        given:
+        descriptor.loadDefaults()
+
+        when:
+        new ProjectSettings(name: "test").applyTo(descriptor)
+
+        then:
+        def dict = xml.buildSpec[0].buildCommand[0].arguments[0].dictionary.key.find { it.text() == "org.eclipse.cdt.make.core.buildLocation" }.parent()
+        dict.value[0].text() == "\${workspace_loc:/test/Debug}"
+    }
+
+    def getString() {
+        def baos = new ByteArrayOutputStream()
+        descriptor.store(baos)
+        baos.toString()
+    }
+    
+    def getXml() {
+        new XmlParser().parseText(getString())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioProjectTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioProjectTest.groovy
new file mode 100644
index 0000000..4927569
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/DefaultVisualStudioProjectTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal
+import spock.lang.Specification
+
+class DefaultVisualStudioProjectTest extends Specification {
+    private DirectInstantiator instantiator = new DirectInstantiator()
+    def component = Mock(ProjectNativeComponentInternal)
+    def fileResolver = Mock(FileResolver)
+    def vsProject = new DefaultVisualStudioProject("projectName", component, fileResolver, instantiator)
+
+    def "names"() {
+        final projectFile = new File("project")
+        final filtersFile = new File("filters")
+        when:
+        fileResolver.resolve("projectName.vcxproj") >> projectFile
+        fileResolver.resolve("projectName.vcxproj.filters") >> filtersFile
+
+        then:
+        vsProject.name == "projectName"
+        vsProject.projectFile.location == projectFile
+        vsProject.filtersFile.location == filtersFile
+    }
+
+    def "includes source files from all source sets"() {
+        when:
+        def file1 = Mock(File)
+        def file2 = Mock(File)
+        def file3 = Mock(File)
+        def sourceSet1 = sourceSet(file1, file2)
+        def sourceSet2 = sourceSet(file3)
+        vsProject.source([sourceSet1, sourceSet2])
+
+        then:
+        vsProject.sourceFiles == [file1, file2, file3]
+    }
+
+    def "includes header files from all source sets"() {
+        when:
+        def file1 = Mock(File)
+        def file2 = Mock(File)
+        def file3 = Mock(File)
+        def file4 = Mock(File)
+        def sourceSet1 = headerSourceSet([file1, file2])
+        def sourceSet2 = headerSourceSet([file3], [file4])
+        vsProject.source([sourceSet1, sourceSet2])
+
+        then:
+        vsProject.headerFiles == [file1, file2, file3, file4]
+    }
+
+    def "has consistent uuid for same mapped component"() {
+        when:
+        def sameComponent = Mock(ProjectNativeComponentInternal)
+        def otherComponent = Mock(ProjectNativeComponentInternal)
+
+        def sameProject = new DefaultVisualStudioProject("projectName", component, fileResolver, instantiator)
+        def samePath = new DefaultVisualStudioProject("projectName", sameComponent, fileResolver, instantiator)
+        def differentPath = new DefaultVisualStudioProject("projectName", otherComponent, fileResolver, instantiator)
+        def differentName = new DefaultVisualStudioProject("otherProject", component, fileResolver, instantiator)
+
+        and:
+        component.projectPath >> ":projectPath"
+        sameComponent.projectPath >> ":projectPath"
+        otherComponent.projectPath >> ":otherProjectPath"
+
+        then:
+        vsProject.uuid == sameProject.uuid
+        vsProject.uuid == samePath.uuid
+        vsProject.uuid != differentPath.uuid
+        vsProject.uuid != differentName.uuid
+    }
+
+    private LanguageSourceSet sourceSet(File... files) {
+        def allFiles = files as Set
+        def sourceSet = Mock(LanguageSourceSet)
+        def sourceDirs = Mock(SourceDirectorySet)
+        1 * sourceSet.source >> sourceDirs
+        1 * sourceDirs.files >> allFiles
+        return sourceSet
+    }
+
+    private HeaderExportingSourceSet headerSourceSet(List<File> exportedHeaders, List<File> implicitHeaders = []) {
+        def exportedHeaderFiles = exportedHeaders as Set
+        def implicitHeaderFiles = implicitHeaders as Set
+        def sourceSet = Mock(HeaderExportingSourceSet)
+        def sourceDirs = Mock(SourceDirectorySet)
+        1 * sourceSet.exportedHeaders >> sourceDirs
+        1 * sourceDirs.files >> exportedHeaderFiles
+        def implicitHeaderSet = Mock(SourceDirectorySet)
+        1 * sourceSet.implicitHeaders >> implicitHeaderSet
+        1 * implicitHeaderSet.files >> implicitHeaderFiles
+        return sourceSet
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectConfigurationTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectConfigurationTest.groovy
new file mode 100644
index 0000000..bcf2c85
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectConfigurationTest.groovy
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.plugins.ExtensionContainer
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.nativebinaries.Executable
+import org.gradle.nativebinaries.ExecutableBinary
+import org.gradle.nativebinaries.NativeDependencySet
+import org.gradle.nativebinaries.internal.DefaultFlavor
+import org.gradle.nativebinaries.internal.DefaultFlavorContainer
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal
+import org.gradle.nativebinaries.language.PreprocessingTool
+import org.gradle.nativebinaries.platform.Platform
+import spock.lang.Specification
+
+class VisualStudioProjectConfigurationTest extends Specification {
+    final flavor = new DefaultFlavor("flavor1")
+    def flavors = new DefaultFlavorContainer(new DirectInstantiator())
+    def exe = Mock(ExecutableInternal) {
+        getFlavors() >> flavors
+    }
+    def extensions = Mock(ExtensionContainer)
+    def platform = Mock(Platform)
+    def exeBinary = Mock(TestExecutableBinary) {
+        getExtensions() >> extensions
+        getFlavor() >> flavor
+        getComponent() >> exe
+        getTargetPlatform() >> platform
+    }
+    def configuration = new VisualStudioProjectConfiguration(null, "configName", "platformName", exeBinary)
+    def cppCompiler = Mock(PreprocessingTool)
+    def cCompiler = Mock(PreprocessingTool)
+    def rcCompiler = Mock(PreprocessingTool)
+
+    def "setup"() {
+        flavors.add(flavor)
+    }
+
+    def "configuration has supplied names"() {
+        expect:
+        configuration.configurationName == "configName"
+        configuration.platformName == "platformName"
+        configuration.name == "configName|platformName"
+    }
+
+    def "configuration tasks are binary tasks"() {
+        when:
+        exeBinary.name >> "exeBinary"
+        exeBinary.component >> exe
+        exe.projectPath >> ":project-path"
+
+        then:
+        configuration.buildTask == ":project-path:exeBinary"
+        configuration.cleanTask == ":project-path:clean"
+    }
+
+    def "compiler defines are taken from cpp compiler configuration"() {
+        when:
+        1 * extensions.findByName('cCompiler') >> null
+        1 * extensions.findByName('cppCompiler') >> cppCompiler
+        1 * extensions.findByName('rcCompiler') >> null
+        cppCompiler.macros >> [foo: "bar", empty: null]
+
+        then:
+        configuration.compilerDefines == ["foo=bar", "empty"]
+    }
+
+    def "compiler defines are taken from c compiler configuration"() {
+        when:
+        1 * extensions.findByName('cCompiler') >> cCompiler
+        1 * extensions.findByName('cppCompiler') >> null
+        1 * extensions.findByName('rcCompiler') >> null
+        cCompiler.macros >> [foo: "bar", another: null]
+
+        then:
+        configuration.compilerDefines == ["foo=bar", "another"]
+    }
+
+    def "resource defines are taken from rcCompiler config"() {
+        when:
+        1 * extensions.findByName('cCompiler') >> null
+        1 * extensions.findByName('cppCompiler') >> null
+        1 * extensions.findByName('rcCompiler') >> rcCompiler
+        rcCompiler.macros >> [foo: "bar", empty: null]
+
+        then:
+        configuration.compilerDefines == ["foo=bar", "empty"]
+    }
+
+    def "compiler defines are taken from cpp, c and rc compiler configurations combined"() {
+        when:
+        1 * extensions.findByName('cppCompiler') >> null
+        1 * extensions.findByName('cCompiler') >> null
+        1 * extensions.findByName('rcCompiler') >> null
+
+        then:
+        configuration.compilerDefines == []
+
+        when:
+        1 * extensions.findByName('cCompiler') >> cCompiler
+        1 * extensions.findByName('cppCompiler') >> cppCompiler
+        1 * extensions.findByName('rcCompiler') >> rcCompiler
+        cCompiler.macros >> [_c: null]
+        cppCompiler.macros >> [foo: "bar", _cpp: null]
+        rcCompiler.macros >> [rc: "defined", rc_empty: null]
+
+        then:
+        configuration.compilerDefines == ["_c", "foo=bar", "_cpp", "rc=defined", "rc_empty"]
+    }
+
+    def "include paths include component headers"() {
+        final sources = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet)
+
+        when:
+        exeBinary.source >> sources
+        exeBinary.libs >> []
+
+        then:
+        configuration.includePaths == []
+
+        when:
+        def file1 = Mock(File)
+        def file2 = Mock(File)
+        def file3 = Mock(File)
+        def sourceSet = Mock(LanguageSourceSet)
+        def sourceSet1 = headerSourceSet(file1, file2)
+        def sourceSet2 = headerSourceSet(file3)
+        sources.addAll(sourceSet, sourceSet1, sourceSet2)
+
+        and:
+        exeBinary.source >> sources
+        exeBinary.libs >> []
+
+        then:
+        configuration.includePaths == [file1, file2, file3]
+    }
+
+    def "include paths include library headers"() {
+        when:
+        def file1 = Mock(File)
+        def file2 = Mock(File)
+        def file3 = Mock(File)
+
+        def deps1 = dependencySet(file1, file2)
+        def deps2 = dependencySet(file3)
+
+        exeBinary.source >> new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet)
+        exeBinary.libs >> [deps1, deps2]
+
+        then:
+        configuration.includePaths == [file1, file2, file3]
+    }
+
+    private HeaderExportingSourceSet headerSourceSet(File... files) {
+        def allFiles = files as Set
+        def sourceSet = Mock(HeaderExportingSourceSet)
+        def sourceDirs = Mock(SourceDirectorySet)
+        1 * sourceSet.exportedHeaders >> sourceDirs
+        1 * sourceDirs.srcDirs >> allFiles
+        return sourceSet
+    }
+
+    private NativeDependencySet dependencySet(File... files) {
+        def deps = Mock(NativeDependencySet)
+        def fileCollection = Mock(FileCollection)
+        deps.includeRoots >> fileCollection
+        fileCollection.files >> (files as Set)
+        return deps
+    }
+
+    interface ExecutableInternal extends Executable, ProjectNativeComponentInternal {}
+    interface TestExecutableBinary extends ProjectNativeBinaryInternal, ExecutableBinary, ExtensionAware {}
+
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectMapperTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectMapperTest.groovy
new file mode 100644
index 0000000..90e07dd
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectMapperTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+import org.gradle.language.base.internal.BinaryNamingScheme
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal
+import org.gradle.nativebinaries.platform.Architecture
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.platform.internal.ArchitectureNotationParser
+import spock.lang.Specification
+
+class VisualStudioProjectMapperTest extends Specification {
+    def mapper = new VisualStudioProjectMapper()
+
+    def executable = Mock(ExecutableInternal)
+    ExecutableBinaryInternal executableBinary
+    def library = Mock(LibraryInternal)
+    def namingScheme = Mock(BinaryNamingScheme)
+
+    def flavorOne = Mock(Flavor)
+    def buildTypeOne = Mock(BuildType)
+    def buildTypeTwo = Mock(BuildType)
+    def platformOne = Mock(Platform)
+
+    def setup() {
+        executableBinary = createExecutableBinary("exeBinaryName", buildTypeOne, platformOne)
+        executableBinary.namingScheme >> namingScheme
+
+        executable.name >> "exeName"
+        library.name >> "libName"
+
+        flavorOne.name >> "flavorOne"
+        buildTypeOne.name >> "buildTypeOne"
+        buildTypeTwo.name >> "buildTypeTwo"
+        platformOne.name >> "platformOne"
+        platformOne.architecture >> arch("i386")
+    }
+
+    def "maps executable binary to visual studio project"() {
+        when:
+        executable.projectPath >> ":"
+        namingScheme.variantDimensions >> []
+
+        then:
+        checkNames executableBinary, "exeNameExe", 'buildTypeOne', 'Win32'
+    }
+
+    def "maps library binary types to visual studio projects"() {
+        when:
+        def sharedLibraryBinary = libraryBinary(SharedLibraryBinaryInternal)
+        def staticLibraryBinary = libraryBinary(StaticLibraryBinaryInternal)
+
+        library.projectPath >> ":"
+        namingScheme.variantDimensions >> []
+
+        then:
+        checkNames sharedLibraryBinary, "libNameDll", 'buildTypeOne', 'Win32'
+        checkNames staticLibraryBinary, "libNameLib", 'buildTypeOne', 'Win32'
+    }
+
+    def "includes project path in visual studio project name"() {
+        when:
+        executable.projectPath >> ":subproject:name"
+
+        and:
+        namingScheme.variantDimensions >> []
+
+        then:
+        checkNames executableBinary, "subproject_name_exeNameExe", 'buildTypeOne', 'Win32'
+    }
+
+    def "uses single variant dimension for configuration name where not empty"() {
+        when:
+        executable.projectPath >> ":"
+        namingScheme.variantDimensions >> ["flavorOne"]
+
+        then:
+        checkNames executableBinary, "exeNameExe", 'flavorOne', 'Win32'
+    }
+
+    def "includes variant dimensions in configuration where component has multiple dimensions"() {
+        when:
+        executable.projectPath >> ":"
+        namingScheme.variantDimensions >> ["platformOne", "buildTypeOne", "flavorOne"]
+
+        then:
+        checkNames executableBinary, "exeNameExe", 'platformOneBuildTypeOneFlavorOne', 'Win32'
+    }
+
+    private def createExecutableBinary(String binaryName, def buildType, def platform) {
+        def binary = Mock(ExecutableBinaryInternal)
+        binary.name >> binaryName
+        binary.component >> executable
+        binary.buildType >> buildType
+        binary.flavor >> flavorOne
+        binary.targetPlatform >> platform
+        return binary
+    }
+
+    private checkNames(def binary, def projectName, def configurationName, def platformName) {
+        def names = mapper.mapToConfiguration(binary)
+        assert names.project == projectName
+        assert names.configuration == configurationName
+        assert names.platform == platformName
+        true
+    }
+
+    private static Architecture arch(String name) {
+        return ArchitectureNotationParser.parser().parseNotation(name)
+    }
+
+    private libraryBinary(Class<? extends LibraryBinary> type) {
+        def binary = Mock(type)
+        binary.component >> library
+        binary.flavor >> flavorOne
+        binary.targetPlatform >> platformOne
+        binary.buildType >> buildTypeOne
+        binary.namingScheme >> namingScheme
+        return binary
+    }
+
+    interface ExecutableInternal extends Executable, ProjectNativeComponentInternal {}
+    interface LibraryInternal extends Library, ProjectNativeComponentInternal {}
+    interface ExecutableBinaryInternal extends ExecutableBinary, ProjectNativeBinaryInternal {}
+    interface SharedLibraryBinaryInternal extends SharedLibraryBinary, ProjectNativeBinaryInternal {}
+    interface StaticLibraryBinaryInternal extends StaticLibraryBinary, ProjectNativeBinaryInternal {}
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectRegistryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectRegistryTest.groovy
new file mode 100644
index 0000000..5444cdc
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/internal/VisualStudioProjectRegistryTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.internal
+
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import spock.lang.Specification
+
+class VisualStudioProjectRegistryTest extends Specification {
+    private DefaultDomainObjectSet<LanguageSourceSet> sources = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet)
+    def fileResolver = Mock(FileResolver)
+    def visualStudioProjectMapper = Mock(VisualStudioProjectMapper)
+    def registry = new VisualStudioProjectRegistry(fileResolver, visualStudioProjectMapper, new DirectInstantiator())
+
+    def executable = Mock(Executable)
+
+    def "creates a matching visual studio project configuration for NativeBinary"() {
+        def executableBinary = Mock(ExecutableInternal)
+        when:
+        visualStudioProjectMapper.mapToConfiguration(executableBinary) >> new VisualStudioProjectMapper.ProjectConfigurationNames("vsProject", "vsConfig", "vsPlatform")
+        executableBinary.component >> executable
+        executableBinary.source >> sources
+
+        and:
+        registry.addProjectConfiguration(executableBinary)
+
+        then:
+        def vsConfig = registry.getProjectConfiguration(executableBinary)
+        vsConfig.project.component == executable
+        vsConfig.type == "Makefile"
+        vsConfig.project.name == "vsProject"
+        vsConfig.configurationName == "vsConfig"
+        vsConfig.platformName == "vsPlatform"
+    }
+
+    def "returns same visual studio project configuration for native binaries that share project name"() {
+        def executableBinary1 = Mock(ExecutableInternal)
+        def executableBinary2 = Mock(ExecutableInternal)
+
+        when:
+        visualStudioProjectMapper.mapToConfiguration(executableBinary1) >> new VisualStudioProjectMapper.ProjectConfigurationNames("vsProject", "vsConfig1", "vsPlatform")
+        visualStudioProjectMapper.mapToConfiguration(executableBinary2) >> new VisualStudioProjectMapper.ProjectConfigurationNames("vsProject", "vsConfig2", "vsPlatform")
+        executableBinary1.source >> sources
+        executableBinary2.source >> sources
+
+        and:
+        registry.addProjectConfiguration(executableBinary1)
+        registry.addProjectConfiguration(executableBinary2)
+
+        then:
+        def vsConfig1 = registry.getProjectConfiguration(executableBinary1)
+        def vsConfig2 = registry.getProjectConfiguration(executableBinary2)
+        vsConfig1.project == vsConfig2.project
+
+        and:
+        vsConfig1.type == "Makefile"
+        vsConfig1.project.name == "vsProject"
+        vsConfig1.configurationName == "vsConfig1"
+        vsConfig1.platformName == "vsPlatform"
+
+        and:
+        vsConfig2.type == "Makefile"
+        vsConfig2.project.name == "vsProject"
+        vsConfig2.configurationName == "vsConfig2"
+        vsConfig2.platformName == "vsPlatform"
+    }
+
+    def "visual studio project contains sources for native binaries for all configurations"() {
+        def executableBinary1 = Mock(ExecutableInternal)
+        def executableBinary2 = Mock(ExecutableInternal)
+        def sourceCommon = Mock(LanguageSourceSet)
+        def source1 = Mock(LanguageSourceSet)
+        def source2 = Mock(LanguageSourceSet)
+
+        when:
+        visualStudioProjectMapper.mapToConfiguration(executableBinary1) >> new VisualStudioProjectMapper.ProjectConfigurationNames("vsProject", "vsConfig1", "vsPlatform")
+        visualStudioProjectMapper.mapToConfiguration(executableBinary2) >> new VisualStudioProjectMapper.ProjectConfigurationNames("vsProject", "vsConfig2", "vsPlatform")
+        executableBinary1.source >> new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet, [sourceCommon, source1])
+        executableBinary2.source >> new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet, [sourceCommon, source2])
+
+        and:
+        registry.addProjectConfiguration(executableBinary1)
+        registry.addProjectConfiguration(executableBinary2)
+
+        then:
+        def vsProject = registry.getProjectConfiguration(executableBinary1).project
+        vsProject.sources as List == [sourceCommon, source1, source2]
+    }
+
+    interface ExecutableInternal extends ExecutableBinary, ProjectNativeBinaryInternal {}
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/RelativeFileNameTransformerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/RelativeFileNameTransformerTest.groovy
new file mode 100644
index 0000000..7d970dc
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/RelativeFileNameTransformerTest.groovy
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal
+
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.normaliseFileSeparators
+
+class RelativeFileNameTransformerTest extends Specification {
+    static rootDir = new File("root")
+
+    def "returns canonical path where file outside of root"() {
+        expect:
+        transform(relative, file) == normaliseFileSeparators(file.canonicalPath)
+
+        where:
+        relative                                          | file
+        new File(rootDir, "current")                      | new File("file/outside")
+        new File(rootDir, "current")                      | new File(rootDir, "subdir/../../outside/of/root")
+        new File("current/outside")                       | new File(rootDir, "file/inside")
+        new File(rootDir, "subdir/../../current/outside") | new File(rootDir, "file/inside")
+    }
+
+    def "returns relative path where file inside of root"() {
+        when:
+        def file = new File(rootDir, filePath)
+        def current = new File(rootDir, "current/dir")
+
+        then:
+        transform(current, file) == relativePath
+
+        where:
+        filePath                   | relativePath
+        "child.txt"                | "../../child.txt"
+        "subdir"                   | "../../subdir"
+        "subdir/child.txt"         | "../../subdir/child.txt"
+        "subdir/another"           | "../../subdir/another"
+        "subdir/another/child.txt" | "../../subdir/another/child.txt"
+        "another/dir/child.txt"    | "../../another/dir/child.txt"
+    }
+
+    def "returns relative path where file shared some of current dir path"() {
+        when:
+        def file = new File(rootDir, filePath)
+        def current = new File(rootDir, "current/dir")
+
+
+        then:
+        transform(current, file) == relativePath
+
+        where:
+        filePath                   | relativePath
+        "current/child.txt"        | "../child.txt"
+        "current/dir/child.txt"    | "child.txt"
+        "current/subdir"           | "../subdir"
+        "current/subdir/child.txt" | "../subdir/child.txt"
+    }
+
+    def "handles mixed paths inside of root"() {
+        when:
+        def file = new File(rootDir, filePath)
+        def current = new File(rootDir, "current/dir")
+
+        then:
+        transform(current, file) == relativePath
+
+        where:
+        filePath                           | relativePath
+        "subdir/down/../another"           | "../../subdir/another"
+        "subdir/down/../another/child.txt" | "../../subdir/another/child.txt"
+    }
+
+    def "handles current is root"() {
+        when:
+        def file = new File(rootDir, filePath)
+        def current = new File(rootDir.absolutePath)
+
+        then:
+        transform(current, file) == relativePath
+
+        where:
+        filePath                   | relativePath
+        "child.txt"                | "child.txt"
+        "subdir"                   | "subdir"
+        "subdir/child.txt"         | "subdir/child.txt"
+        "subdir/another"           | "subdir/another"
+        "subdir/another/child.txt" | "subdir/another/child.txt"
+    }
+
+    def "handles file is root"() {
+        when:
+        def file = new File(rootDir.path)
+        def current = new File(rootDir, currentPath)
+
+        then:
+        transform(current, file) == relativePath
+
+        where:
+        currentPath      | relativePath
+        "."              | "."
+        "subdir"         | ".."
+        "subdir/another" | "../.."
+    }
+
+    String transform(File from, File to) {
+        return normaliseFileSeparators(RelativeFileNameTransformer.forDirectory(rootDir, from).transform(to))
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioFiltersFileTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioFiltersFileTest.groovy
new file mode 100644
index 0000000..3da512a
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioFiltersFileTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal
+import org.gradle.api.Transformer
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Specification
+
+class VisualStudioFiltersFileTest extends Specification {
+    TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    Transformer<String, File> fileNameTransformer = { it.name } as Transformer<String, File>
+    def filtersFile = new VisualStudioFiltersFile(new XmlTransformer(), fileNameTransformer)
+
+    def "empty filters file"() {
+        when:
+        filtersFile.loadDefaults()
+
+        then:
+        Node sourceFiles = itemGroup('Filters').Filter.find({it.'@Include' == 'Source Files'}) as Node
+        sourceFiles.Extensions[0].text() == 'cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx'
+
+        Node headerFiles = itemGroup('Filters').Filter.find({it.'@Include' == 'Header Files'}) as Node
+        headerFiles.Extensions[0].text() == 'h;hpp;hxx;hm;inl;inc;xsd'
+
+        Node resourceFiles = itemGroup('Filters').Filter.find({it.'@Include' == 'Resource Files'}) as Node
+        resourceFiles.Extensions[0].text() == 'rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav'
+
+        and:
+        itemGroup('Sources').children().isEmpty()
+        itemGroup('Headers').children().isEmpty()
+    }
+
+    def "adds sources and header files"() {
+        when:
+        filtersFile.loadDefaults()
+
+        and:
+        filtersFile.addSource(file("sourceOne"))
+        filtersFile.addSource(file("sourceTwo"))
+
+        filtersFile.addHeader(file("headerOne"))
+        filtersFile.addHeader(file("headerTwo"))
+
+        then:
+        assert sourceFile(0) == "sourceOne"
+        assert sourceFile(1) == "sourceTwo"
+
+        assert headerFile(0) == "headerOne"
+        assert headerFile(1) == "headerTwo"
+    }
+
+    private String sourceFile(int index) {
+        def source = itemGroup('Sources').ClCompile[index]
+        assert source.Filter[0].text() == 'Source Files'
+        return source.'@Include'
+    }
+
+    private String headerFile(int index) {
+        def header = itemGroup('Headers').ClInclude[index]
+        assert header.Filter[0].text() == 'Header Files'
+        return header.'@Include'
+    }
+
+    private Node itemGroup(String label) {
+        return filtersXml.ItemGroup.find({it.'@Label' == label}) as Node
+    }
+
+    private def getFiltersXml() {
+        return new XmlParser().parse(filtersFileContent)
+    }
+
+    private TestFile getFiltersFileContent() {
+        def file = testDirectoryProvider.testDirectory.file("filters.xml")
+        filtersFile.store(file)
+        return file
+    }
+
+    private TestFile file(String name) {
+        testDirectoryProvider.testDirectory.file(name)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioProjectFileTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioProjectFileTest.groovy
new file mode 100644
index 0000000..a864b6e
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioProjectFileTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal
+import org.gradle.api.Transformer
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.ide.visualstudio.fixtures.ProjectFile
+import org.gradle.ide.visualstudio.internal.VisualStudioProjectConfiguration
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Specification
+
+class VisualStudioProjectFileTest extends Specification {
+    TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    Transformer<String, File> fileNameTransformer = { it.name } as Transformer<String, File>
+    def generator = new VisualStudioProjectFile(new XmlTransformer(), fileNameTransformer)
+
+    def "setup"() {
+        generator.loadDefaults()
+    }
+
+    def "empty project file"() {
+        expect:
+        projectFile.projectConfigurations.isEmpty()
+        projectFile.sourceFiles == []
+        projectFile.headerFiles == []
+    }
+
+    def "set project uuid"() {
+        when:
+        generator.setProjectUuid("THE_PROJECT_UUID")
+
+        then:
+        projectFile.projectGuid == "THE_PROJECT_UUID"
+    }
+
+    def "add source and headers"() {
+        when:
+        generator.addSourceFile(file("sourceOne"))
+        generator.addSourceFile(file("sourceTwo"))
+
+        generator.addHeaderFile(file("headerOne"))
+        generator.addHeaderFile(file("headerTwo"))
+
+        then:
+        projectFile.sourceFiles == ["sourceOne", "sourceTwo"]
+        projectFile.headerFiles == ["headerOne", "headerTwo"]
+    }
+
+    def "add configurations"() {
+        when:
+        generator.gradleCommand = 'GRADLE'
+        generator.addConfiguration(configuration("debugWin32", "Win32", ["foo", "bar"], ["include1", "include2"]))
+        generator.addConfiguration(configuration("releaseWin32", "Win32", ["foo", "bar"], ["include1", "include2", "include3"]))
+        generator.addConfiguration(configuration("debugX64", "x64", ["foo", "bar"], ["include1", "include2"]))
+
+        then:
+        final configurations = projectFile.projectConfigurations
+        configurations.size() == 3
+        with (configurations['debugWin32']) {
+            name == 'debugWin32'
+            platformName == 'Win32'
+            macros == "foo;bar"
+            includePath == "include1;include2"
+            buildCommand == "GRADLE debugWin32Build"
+        }
+        with (configurations['releaseWin32']) {
+            name == 'releaseWin32'
+            platformName == 'Win32'
+            macros == "foo;bar"
+            includePath == "include1;include2;include3"
+            buildCommand == "GRADLE releaseWin32Build"
+        }
+        with (configurations['debugX64']) {
+            name == 'debugX64'
+            platformName == 'x64'
+            macros == "foo;bar"
+            includePath == "include1;include2"
+            buildCommand == "GRADLE debugX64Build"
+        }
+    }
+
+    private VisualStudioProjectConfiguration configuration(def configName, def platformName, def defines, def includes) {
+        return Stub(VisualStudioProjectConfiguration) {
+            getName() >> "${configName}|${platformName}"
+            getConfigurationName() >> configName
+            getPlatformName() >> platformName
+            getBuildTask() >> "${configName}Build"
+            getCleanTask() >> "${configName}Clean"
+            getCompilerDefines() >> defines
+            getIncludePaths() >> includes.collect { file(it) }
+        }
+    }
+
+    private ProjectFile getProjectFile() {
+        def file = testDirectoryProvider.testDirectory.file("project.xml")
+        generator.store(file)
+        return new ProjectFile(file)
+    }
+
+    private TestFile file(String name) {
+        testDirectoryProvider.testDirectory.file(name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioSolutionFileTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioSolutionFileTest.groovy
new file mode 100644
index 0000000..d3cbd28
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/ide/visualstudio/tasks/internal/VisualStudioSolutionFileTest.groovy
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.tasks.internal
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.ide.visualstudio.TextProvider
+import org.gradle.ide.visualstudio.fixtures.SolutionFile
+import org.gradle.ide.visualstudio.internal.DefaultVisualStudioProject
+import org.gradle.ide.visualstudio.internal.VisualStudioProjectConfiguration
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.nativebinaries.ProjectNativeBinary
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.internal.ProjectNativeComponentInternal
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Specification
+
+class VisualStudioSolutionFileTest extends Specification {
+    TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    def fileResolver = Mock(FileResolver)
+    def instantiator = new DirectInstantiator()
+    def solutionFile = new VisualStudioSolutionFile()
+    def binary1 = binary("one")
+
+    def "setup"() {
+        solutionFile.loadDefaults()
+    }
+
+    def "empty solution file"() {
+        expect:
+        generatedSolution.content ==
+"""Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
+"""
+    }
+
+    def "create for single project configuration"() {
+        when:
+        def project = createProject("project1")
+        def configuration1 = createProjectConfiguration(project, "projectConfig")
+        solutionFile.addSolutionConfiguration("solutionConfig", [configuration1])
+        solutionFile.mainProject = project
+
+        then:
+        generatedSolution.content ==
+"""Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "project1", "${project.projectFile.location.absolutePath}", "${project.uuid}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		solutionConfig=solutionConfig
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		${project.uuid}.solutionConfig.ActiveCfg = projectConfig|Win32
+		${project.uuid}.solutionConfig.Build.0 = projectConfig|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
+"""
+    }
+
+    def "create for multiple configurations"() {
+        when:
+        def project1 = createProject("project1")
+        def project2 = createProject("project2")
+        solutionFile.mainProject = project1
+        solutionFile.addSolutionConfiguration("solutionConfig1", [
+                createProjectConfiguration(project1, "config1"),
+                createProjectConfiguration(project1, "config2"),
+                createProjectConfiguration(project2, "configA")
+        ])
+        solutionFile.addSolutionConfiguration("solutionConfig2", [
+                createProjectConfiguration(project2, "configA")
+        ])
+
+        then:
+        generatedSolution.content ==
+"""Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "project1", "${project1.projectFile.location.absolutePath}", "${project1.uuid}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "project2", "${project2.projectFile.location.absolutePath}", "${project2.uuid}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		solutionConfig1=solutionConfig1
+		solutionConfig2=solutionConfig2
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		${project1.uuid}.solutionConfig1.ActiveCfg = config1|Win32
+		${project1.uuid}.solutionConfig1.Build.0 = config1|Win32
+		${project1.uuid}.solutionConfig1.ActiveCfg = config2|Win32
+		${project1.uuid}.solutionConfig1.Build.0 = config2|Win32
+		${project2.uuid}.solutionConfig1.ActiveCfg = configA|Win32
+		${project2.uuid}.solutionConfig2.ActiveCfg = configA|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
+"""
+    }
+
+    def "applies multiple text actions"() {
+        when:
+        solutionFile.actions << ({ TextProvider text ->
+            text.setText("foo")
+        } as Action)
+        solutionFile.actions << ({ TextProvider text ->
+            text.asBuilder().append("bar")
+        } as Action)
+
+        then:
+        generatedSolutionFile.text == "foobar"
+    }
+
+    def "can get and set text with actions"() {
+        when:
+        solutionFile.actions << ({ TextProvider text ->
+            text.text = "test"
+        } as Action)
+        solutionFile.actions << ({ TextProvider text ->
+            text.text = text.text.reverse()
+        } as Action)
+
+        then:
+        generatedSolutionFile.text == "tset"
+    }
+
+    private VisualStudioProjectConfiguration createProjectConfiguration(DefaultVisualStudioProject project1, String configName) {
+        return new VisualStudioProjectConfiguration(project1, configName, "Win32", binary1)
+    }
+
+    private DefaultVisualStudioProject createProject(String projectName) {
+        final project1File = new File(projectName)
+        fileResolver.resolve("${projectName}.vcxproj") >> project1File
+        return new DefaultVisualStudioProject(projectName, binary1.component, fileResolver, instantiator)
+    }
+
+    private ProjectNativeBinary binary(def name) {
+        def component = Mock(ProjectNativeComponentInternal)
+        def binary = Mock(ProjectNativeBinaryInternal)
+        component.name >> "${name}Component"
+        component.projectPath >> "project-path"
+        binary.name >> name
+        binary.component >> component
+        return binary
+    }
+
+    private SolutionFile getGeneratedSolution() {
+        TestFile file = getGeneratedSolutionFile()
+        return new SolutionFile(file)
+    }
+
+    private TestFile getGeneratedSolutionFile() {
+        def file = testDirectoryProvider.testDirectory.file("solution.txt")
+        solutionFile.store(file)
+        file
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerLangPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerLangPluginTest.groovy
new file mode 100644
index 0000000..3ea8783
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerLangPluginTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.assembler.plugins
+
+import org.gradle.language.assembler.AssemblerSourceSet
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class AssemblerLangPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def "adds support for custom AssemblerSourceSets"() {
+        when:
+        project.plugins.apply(AssemblerLangPlugin)
+        project.sources.create "test"
+
+        then:
+        project.sources.test.create("test_asm", AssemblerSourceSet) in AssemblerSourceSet
+    }
+
+    def "adds conventional AssemblerSourceSet"() {
+        when:
+        project.plugins.apply(AssemblerLangPlugin)
+        project.sources.create "test"
+
+        then:
+        project.sources.test.asm in AssemblerSourceSet
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/language/c/plugins/CLangPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/language/c/plugins/CLangPluginTest.groovy
new file mode 100644
index 0000000..eade031
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/language/c/plugins/CLangPluginTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.c.plugins
+
+import org.gradle.language.c.CSourceSet
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class CLangPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def "adds support for custom CSourceSets"() {
+        when:
+        project.plugins.apply(CLangPlugin)
+        project.sources.create "test"
+
+        then:
+        project.sources.test.create("test_c", CSourceSet) in CSourceSet
+    }
+
+    def "adds conventional CSourceSet"() {
+        when:
+        project.plugins.apply(CLangPlugin)
+        project.sources.create "test"
+
+        then:
+        project.sources.test.c in CSourceSet
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/language/cpp/plugins/CppLangPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/language/cpp/plugins/CppLangPluginTest.groovy
new file mode 100644
index 0000000..7369554
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/language/cpp/plugins/CppLangPluginTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.cpp.plugins
+
+import org.gradle.language.cpp.CppSourceSet
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class CppLangPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def "adds support for custom CppSourceSets"() {
+        when:
+        project.plugins.apply(CppLangPlugin)
+        project.sources.create "test"
+
+        then:
+        project.sources.test.create("test_cpp", CppSourceSet) in CppSourceSet
+    }
+
+    def "adds conventional CppSourceSet"() {
+        when:
+        project.plugins.apply(CppLangPlugin)
+        project.sources.create "test"
+
+        then:
+        project.sources.test.cpp in CppSourceSet
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultBuildTypeTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultBuildTypeTest.groovy
new file mode 100644
index 0000000..82699d0
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultBuildTypeTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+
+import spock.lang.Specification
+
+class DefaultBuildTypeTest extends Specification {
+    def "has useful string representation"() {
+        def buildType = new DefaultBuildType("release")
+
+        expect:
+        buildType.toString() == "build type 'release'"
+        buildType.displayName == "build type 'release'"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultExecutableBinaryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultExecutableBinaryTest.groovy
new file mode 100644
index 0000000..13ca2de
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultExecutableBinaryTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+import org.gradle.language.base.internal.DefaultBinaryNamingScheme
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Executable
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import spock.lang.Specification
+
+class DefaultExecutableBinaryTest extends Specification {
+    def namingScheme = new DefaultBinaryNamingScheme("bigOne", "executable", [])
+
+    def "has useful string representation"() {
+        given:
+        def executable = Stub(Executable)
+
+        when:
+        def binary = new ProjectExecutableBinary(executable, new DefaultFlavor("flavorOne"), Stub(ToolChainInternal), Stub(Platform), Stub(BuildType), namingScheme, Mock(NativeDependencyResolver))
+
+        then:
+        binary.toString() == "executable 'bigOne:executable'"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultExecutableTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultExecutableTest.groovy
new file mode 100644
index 0000000..4347607
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultExecutableTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+
+import spock.lang.Specification
+
+class DefaultExecutableTest extends Specification {
+    def executable = new DefaultExecutable(new NativeProjectComponentIdentifier("project-path", "someExe"))
+
+    def "has useful string representation"() {
+        expect:
+        executable.toString() == "executable 'someExe'"
+        executable.displayName == "executable 'someExe'"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultFlavorTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultFlavorTest.groovy
new file mode 100644
index 0000000..210b9ab
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultFlavorTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+
+import spock.lang.Specification
+
+class DefaultFlavorTest extends Specification {
+    def "has useful string representation"() {
+        def flavor = new DefaultFlavor("someFlavor")
+
+        expect:
+        flavor.toString() == "flavor 'someFlavor'"
+        flavor.displayName == "flavor 'someFlavor'"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultLibraryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultLibraryTest.groovy
new file mode 100644
index 0000000..9d1dcc6
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultLibraryTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+
+import spock.lang.Specification
+
+class DefaultLibraryTest extends Specification {
+    final library = new DefaultLibrary(new NativeProjectComponentIdentifier("project-path", "someLib"))
+
+    def "has useful string representation"() {
+        expect:
+        library.toString() == "library 'someLib'"
+        library.displayName == "library 'someLib'"
+    }
+
+    def "can use shared variant as requirement"() {
+        when:
+        def requirement = library.shared
+
+        then:
+        requirement.projectPath == 'project-path'
+        requirement.libraryName == 'someLib'
+        requirement.linkage == 'shared'
+    }
+
+    def "can use static variant as requirement"() {
+        when:
+        def requirement = library.static
+
+        then:
+        requirement.projectPath == 'project-path'
+        requirement.libraryName == 'someLib'
+        requirement.linkage == 'static'
+    }
+
+    def "can use api linkage as requirement"() {
+        when:
+        def requirement = library.api
+
+        then:
+        requirement.projectPath == 'project-path'
+        requirement.libraryName == 'someLib'
+        requirement.linkage == 'api'
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultNativeBinaryTasksTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultNativeBinaryTasksTest.groovy
new file mode 100644
index 0000000..9e2d31a
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultNativeBinaryTasksTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+
+import org.gradle.nativebinaries.tasks.CreateStaticLibrary
+import org.gradle.nativebinaries.tasks.LinkExecutable
+import org.gradle.util.TestUtil
+import spock.lang.Specification;
+
+class DefaultNativeBinaryTasksTest extends Specification {
+    def tasks = new DefaultNativeBinaryTasks()
+
+    def "returns null for link, createStaticLib and builder when none defined"() {
+        expect:
+        tasks.link == null
+        tasks.createStaticLib == null
+        tasks.builder == null
+    }
+
+    def "returns link task when defined"() {
+        when:
+        final linkTask = TestUtil.createTask(LinkExecutable)
+        tasks.add(linkTask)
+
+        then:
+        tasks.link == linkTask
+        tasks.createStaticLib == null
+        tasks.builder == linkTask
+    }
+
+    def "returns create task when defined"() {
+        when:
+        final createTask = TestUtil.createTask(CreateStaticLibrary)
+        tasks.add(createTask)
+
+        then:
+        tasks.link == null
+        tasks.createStaticLib == createTask
+        tasks.builder == createTask
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultNativeComponentTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultNativeComponentTest.groovy
new file mode 100644
index 0000000..d43d2df
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/DefaultNativeComponentTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.base.internal.DefaultFunctionalSourceSet
+import spock.lang.Specification
+
+class DefaultNativeComponentTest extends Specification {
+    def instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+    def id = new NativeProjectComponentIdentifier("project", "name")
+    def component = new TestProjectNativeComponent(id)
+
+    def "uses all source sets from a functional source set"() {
+        given:
+        def functionalSourceSet = new DefaultFunctionalSourceSet("func", instantiator)
+        def sourceSet1 = Stub(LanguageSourceSet) {
+            getName() >> "ss1"
+        }
+        def sourceSet2 = Stub(LanguageSourceSet) {
+            getName() >> "ss2"
+        }
+
+        when:
+        functionalSourceSet.add(sourceSet1)
+        functionalSourceSet.add(sourceSet2)
+
+        and:
+        component.source functionalSourceSet
+
+        then:
+        component.source.contains(sourceSet1)
+        component.source.contains(sourceSet2)
+    }
+
+    def "flavors can be chosen and will replace default flavor"() {
+        when:
+        component.targetFlavors "flavor1", "flavor2"
+
+        and:
+        component.targetFlavors("flavor3")
+
+        then:
+        component.chooseFlavors([flavor("flavor1"), flavor("flavor2"), flavor("flavor3"), flavor("flavor4")] as Set)*.name == ["flavor1", "flavor2", "flavor3"]
+    }
+
+    class TestProjectNativeComponent extends AbstractTargetedProjectNativeComponent {
+        TestProjectNativeComponent(NativeProjectComponentIdentifier id) {
+            super(id)
+        }
+
+        String getDisplayName() {
+            return "test component"
+        }
+    }
+
+    def flavor(String name) {
+        new DefaultFlavor(name)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectNativeBinaryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectNativeBinaryTest.groovy
new file mode 100644
index 0000000..955eab9
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectNativeBinaryTest.groovy
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.DependentSourceSet
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.base.internal.BinaryNamingScheme
+import org.gradle.language.base.internal.DefaultBinaryNamingScheme
+import org.gradle.language.base.internal.DefaultFunctionalSourceSet
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.resolve.NativeBinaryResolveResult
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal
+import org.gradle.nativebinaries.platform.internal.DefaultArchitecture
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import spock.lang.Specification
+
+class ProjectNativeBinaryTest extends Specification {
+    def flavor1 = new DefaultFlavor("flavor1")
+    def id = new NativeProjectComponentIdentifier("project", "name")
+    def component = new TestProjectNativeComponent(id)
+    def toolChain1 = Stub(ToolChainInternal) {
+        getName() >> "ToolChain1"
+    }
+    def platform1 = Stub(Platform) {
+        getArchitecture() >> new DefaultArchitecture("i386", ArchitectureInternal.InstructionSet.X86, 64)
+    }
+    def buildType1 = Stub(BuildType) {
+        getName() >> "BuildType1"
+    }
+    def resolver = Mock(NativeDependencyResolver)
+
+    def "binary uses source from its owner component"() {
+        given:
+        def binary = testBinary(component)
+        def sourceSet = Stub(LanguageSourceSet)
+
+        when:
+        component.source(sourceSet)
+
+        then:
+        binary.source.contains(sourceSet)
+    }
+
+    def "binary uses all source sets from a functional source set"() {
+        given:
+        def binary = testBinary(component)
+        def functionalSourceSet = new DefaultFunctionalSourceSet("func", new DirectInstantiator())
+        def sourceSet1 = Stub(LanguageSourceSet) {
+            getName() >> "ss1"
+        }
+        def sourceSet2 = Stub(LanguageSourceSet) {
+            getName() >> "ss2"
+        }
+
+        when:
+        functionalSourceSet.add(sourceSet1)
+        functionalSourceSet.add(sourceSet2)
+
+        and:
+        binary.source functionalSourceSet
+
+        then:
+        binary.source.contains(sourceSet1)
+        binary.source.contains(sourceSet2)
+    }
+
+    def "uses resolver to resolve lib to dependency"() {
+        def binary = testBinary(component, flavor1)
+        def lib = new Object()
+        def dependency = Stub(NativeDependencySet)
+
+        when:
+        binary.lib(lib)
+
+        and:
+        1 * resolver.resolve({NativeBinaryResolveResult result ->
+            result.allResolutions*.input == [lib]
+        }) >> { NativeBinaryResolveResult result ->
+            result.allResolutions[0].nativeDependencySet = dependency
+        }
+
+        then:
+        binary.libs// == [dependency]
+    }
+
+    def "binary libs include source set dependencies"() {
+        def binary = testBinary(component)
+        def lib = new Object()
+        def dependency = Stub(NativeDependencySet)
+
+        when:
+        def sourceSet = Stub(DependentSourceSet) {
+            getLibs() >> [lib]
+        }
+        binary.source sourceSet
+
+        1 * resolver.resolve({NativeBinaryResolveResult result ->
+            result.allResolutions*.input == [lib]
+        }) >> { NativeBinaryResolveResult result ->
+            result.allResolutions[0].nativeDependencySet = dependency
+        }
+
+        then:
+        binary.getLibs(sourceSet) == [dependency]
+    }
+
+    def "order of libraries is maintained"() {
+        def binary = testBinary(component)
+        def libraryBinary = Mock(LibraryBinary)
+        def dependency1 = Stub(NativeDependencySet)
+        def dependency2 = Stub(NativeDependencySet)
+        def dependency3 = Stub(NativeDependencySet)
+
+        when:
+        binary.lib(dependency1)
+        binary.lib(libraryBinary)
+        binary.lib(dependency3)
+
+        and:
+        1 * resolver.resolve({NativeBinaryResolveResult result ->
+            result.allResolutions*.input == [dependency1, libraryBinary, dependency3]
+        }) >> { NativeBinaryResolveResult result ->
+            result.allResolutions[0].nativeDependencySet = dependency1
+            result.allResolutions[1].nativeDependencySet = dependency2
+            result.allResolutions[2].nativeDependencySet = dependency3
+        }
+
+        then:
+        binary.libs as List == [dependency1, dependency2, dependency3]
+    }
+
+    def "library added to binary is ordered before library for source set"() {
+        def binary = testBinary(component)
+        def lib1 = new Object()
+        def dep1 = Stub(NativeDependencySet)
+        def lib2 = new Object()
+        def dep2 = Stub(NativeDependencySet)
+        def sourceLib = new Object()
+        def sourceDep = Stub(NativeDependencySet)
+
+        when:
+        binary.lib(lib1)
+        def sourceSet = Stub(DependentSourceSet) {
+            getLibs() >> [sourceLib]
+        }
+        binary.source sourceSet
+        binary.lib(lib2)
+
+        and:
+        1 * resolver.resolve({NativeBinaryResolveResult result ->
+            result.allResolutions*.input == [lib1, lib2, sourceLib]
+        }) >> { NativeBinaryResolveResult result ->
+            result.allResolutions[0].nativeDependencySet = dep1
+            result.allResolutions[1].nativeDependencySet = dep2
+            result.allResolutions[2].nativeDependencySet = sourceDep
+        }
+
+        then:
+        binary.libs as List == [dep1, dep2, sourceDep]
+    }
+
+    def testBinary(ProjectNativeComponent owner, Flavor flavor = new DefaultFlavor(DefaultFlavor.DEFAULT)) {
+        return new TestProjectNativeBinary(owner, flavor, toolChain1, platform1, buildType1, new DefaultBinaryNamingScheme("baseName", "", []), resolver)
+    }
+
+    class TestProjectNativeComponent extends AbstractProjectNativeComponent {
+        TestProjectNativeComponent(NativeProjectComponentIdentifier id) {
+            super(id)
+        }
+
+        String getDisplayName() {
+            return "test component"
+        }
+    }
+
+    class TestProjectNativeBinary extends AbstractProjectNativeBinary {
+        def owner
+
+        TestProjectNativeBinary(ProjectNativeComponent owner, Flavor flavor, ToolChainInternal toolChain, Platform targetPlatform, BuildType buildType,
+                   BinaryNamingScheme namingScheme, NativeDependencyResolver resolver) {
+            super(owner, flavor, toolChain, targetPlatform, buildType, namingScheme, resolver)
+            this.owner = owner
+        }
+
+        String getOutputFileName() {
+            return null
+        }
+
+        File getPrimaryOutput() {
+            File binaryOutputDir = getBinaryOutputDir();
+            return new File(binaryOutputDir, getOutputFileName());
+        }
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectSharedLibraryBinaryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectSharedLibraryBinaryTest.groovy
new file mode 100644
index 0000000..21f7527
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectSharedLibraryBinaryTest.groovy
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+import org.gradle.api.Task
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.language.base.internal.DefaultBinaryNamingScheme
+import org.gradle.language.rc.WindowsResourceSet
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Library
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectSharedLibraryBinaryTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir
+    def namingScheme = new DefaultBinaryNamingScheme("main", "sharedLibrary", [])
+    final toolChain = Stub(ToolChainInternal)
+    final platform = Stub(Platform)
+    final buildType = Stub(BuildType)
+    final library = Stub(Library)
+    final resolver = Stub(NativeDependencyResolver)
+    def sharedLibraryFile = Mock(File)
+    def sharedLibraryLinkFile = Mock(File)
+
+    def "has useful string representation"() {
+        expect:
+        sharedLibrary.toString() == "shared library 'main:sharedLibrary'"
+    }
+
+    def "can set output files"() {
+        given:
+        def binary = sharedLibrary
+
+        when:
+        binary.sharedLibraryFile = sharedLibraryFile
+        binary.sharedLibraryLinkFile = sharedLibraryLinkFile
+
+        then:
+        binary.sharedLibraryFile == sharedLibraryFile
+        binary.sharedLibraryLinkFile == sharedLibraryLinkFile
+    }
+
+    def "can convert binary to a native dependency"() {
+        given:
+        def binary = sharedLibrary
+        binary.sharedLibraryFile = sharedLibraryFile
+        binary.sharedLibraryLinkFile = sharedLibraryLinkFile
+        def lifecycleTask = Stub(Task)
+        binary.setLifecycleTask(lifecycleTask)
+        binary.builtBy(Stub(Task))
+
+        and: "has at least one header exporting source set"
+        final headerDir = tmpDir.createDir("headerDir")
+        def headerDirSet = Stub(SourceDirectorySet) {
+            getSrcDirs() >> [headerDir]
+        }
+        def sourceDirSet = Stub(SourceDirectorySet) {
+            getFiles() >> [tmpDir.createFile("input.src")]
+        }
+        def sourceSet = Stub(HeaderExportingSourceSet) {
+            getSource() >> sourceDirSet
+            getExportedHeaders() >> headerDirSet
+        }
+        binary.source sourceSet
+
+        expect:
+        binary.sharedLibraryFile == sharedLibraryFile
+        binary.sharedLibraryLinkFile == sharedLibraryLinkFile
+
+        binary.headerDirs.files == [headerDir] as Set
+
+        and:
+        binary.linkFiles.files == [binary.sharedLibraryLinkFile] as Set
+        binary.linkFiles.buildDependencies.getDependencies(Stub(Task)) == [lifecycleTask] as Set
+        binary.linkFiles.toString() == "shared library 'main:sharedLibrary'"
+
+        and:
+        binary.runtimeFiles.files == [binary.sharedLibraryFile] as Set
+        binary.runtimeFiles.buildDependencies.getDependencies(Stub(Task)) == [lifecycleTask] as Set
+        binary.runtimeFiles.toString() == "shared library 'main:sharedLibrary'"
+    }
+
+    def "has empty link files when has resources and no symbols are exported from library"() {
+        when:
+        def binary = sharedLibrary
+        def sourceDirSet = Stub(SourceDirectorySet) {
+            getFiles() >> [tmpDir.createFile("input.rc")]
+        }
+        def resourceSet = Stub(WindowsResourceSet) {
+            getSource() >> sourceDirSet
+        }
+        binary.source resourceSet
+
+        def binaryFile = tmpDir.createFile("binary.run")
+        def linkFile = tmpDir.createFile("binary.link")
+        toolChain.getSharedLibraryLinkFileName(binaryFile.path) >> linkFile.path
+
+        then:
+        binary.linkFiles.files == [] as Set
+    }
+
+    private ProjectSharedLibraryBinary getSharedLibrary() {
+        new ProjectSharedLibraryBinary(library, new DefaultFlavor("flavorOne"), toolChain, platform, buildType, namingScheme, resolver)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectStaticLibraryBinaryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectStaticLibraryBinaryTest.groovy
new file mode 100644
index 0000000..77a544b
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/ProjectStaticLibraryBinaryTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.language.HeaderExportingSourceSet
+import org.gradle.language.base.internal.DefaultBinaryNamingScheme
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Library
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectStaticLibraryBinaryTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir
+    def namingScheme = new DefaultBinaryNamingScheme("main", "staticLibrary", [])
+    def library = Stub(Library)
+    def toolChain = Stub(ToolChainInternal)
+    def platform = Stub(Platform)
+    def buildType = Stub(BuildType)
+    final resolver = Stub(NativeDependencyResolver)
+    final outputFile = Mock(File)
+
+    def "has useful string representation"() {  
+        expect:
+        staticLibrary.toString() == "static library 'main:staticLibrary'"
+    }
+
+    def getStaticLibrary() {
+        new ProjectStaticLibraryBinary(library, new DefaultFlavor("flavorOne"), toolChain, platform, buildType, namingScheme, resolver)
+    }
+
+    def "can set output file"() {
+        given:
+        final binary = staticLibrary
+        def outputFile = Mock(File)
+
+        when:
+        binary.staticLibraryFile = outputFile
+
+        then:
+        binary.staticLibraryFile == outputFile
+    }
+
+    def "can convert binary to a native dependency"() {
+        final binary = staticLibrary
+        given:
+        def lifecycleTask = Stub(Task)
+        binary.lifecycleTask = lifecycleTask
+        binary.builtBy(Stub(Task))
+
+        and:
+        binary.staticLibraryFile = outputFile
+
+        and:
+        final headerDir = tmpDir.createDir("headerDir")
+        addSources(binary, headerDir)
+
+        expect:
+        binary.headerDirs.files == [headerDir] as Set
+        binary.staticLibraryFile == outputFile
+
+        and:
+        binary.linkFiles.files == [binary.staticLibraryFile] as Set
+        binary.linkFiles.buildDependencies.getDependencies(Stub(Task)) == [lifecycleTask] as Set
+        binary.linkFiles.toString() == "static library 'main:staticLibrary'"
+
+        and:
+        binary.runtimeFiles.files.isEmpty()
+        binary.runtimeFiles.buildDependencies.getDependencies(Stub(Task)) == [] as Set
+    }
+
+    def "includes additional link files in native dependency"() {
+        final binary = staticLibrary
+        given:
+        binary.staticLibraryFile = outputFile
+        def linkFile1 = Mock(File)
+        def linkFile2 = Mock(File)
+        def additionalLinkFiles = Stub(FileCollection) {
+            getFiles() >> [linkFile1, linkFile2]
+        }
+        binary.additionalLinkFiles(additionalLinkFiles)
+
+        and:
+        addSources(binary, tmpDir.createDir("headerDir"))
+
+        expect:
+        binary.staticLibraryFile == outputFile
+        binary.linkFiles.files == [binary.staticLibraryFile, linkFile1, linkFile2] as Set
+    }
+
+    private TestFile addSources(ProjectStaticLibraryBinary binary, def headerDir) {
+        def headerDirSet = Stub(SourceDirectorySet) {
+            getSrcDirs() >> [headerDir]
+        }
+        def sourceDirSet = Stub(SourceDirectorySet) {
+            getFiles() >> [tmpDir.createFile("input.src")]
+        }
+        def sourceSet = Stub(HeaderExportingSourceSet) {
+            getSource() >> sourceDirSet
+            getExportedHeaders() >> headerDirSet
+        }
+        binary.source sourceSet
+        headerDir
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/SourceSetNotationParserTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/SourceSetNotationParserTest.groovy
new file mode 100644
index 0000000..a4ea850
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/SourceSetNotationParserTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.base.internal.DefaultFunctionalSourceSet
+import spock.lang.Specification
+
+class SourceSetNotationParserTest extends Specification {
+    def parser = SourceSetNotationParser.parser()
+    def languageSourceSet1 = languageSourceSet("lss1")
+    def languageSourceSet2 = languageSourceSet("lss2")
+
+    def "translates single LanguageSourceSet"() {
+        expect:
+        parser.parseNotation(languageSourceSet1) as List == [languageSourceSet1]
+    }
+
+    def "collects all LanguageSourceSets for a FunctionalSourceSet"() {
+        when:
+        def functionalSourceSet = new DefaultFunctionalSourceSet("func", new DirectInstantiator())
+        functionalSourceSet.add(languageSourceSet1)
+        functionalSourceSet.add(languageSourceSet2)
+
+        then:
+        parser.parseNotation(functionalSourceSet) as List == [languageSourceSet1, languageSourceSet2]
+    }
+
+    def "collects all LanguageSourceSets in a collection"() {
+        expect:
+        parser.parseNotation([languageSourceSet1, languageSourceSet2]) as List == [languageSourceSet1, languageSourceSet2]
+        parser.parseNotation([languageSourceSet2, languageSourceSet1]) as List == [languageSourceSet2, languageSourceSet1]
+    }
+
+    private LanguageSourceSet languageSourceSet(def name) {
+        Stub(LanguageSourceSet) {
+            getName() >> name
+        }
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultBuildTypesTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultBuildTypesTest.groovy
new file mode 100644
index 0000000..df9c45e
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultBuildTypesTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure
+
+import org.gradle.nativebinaries.BuildTypeContainer
+import spock.lang.Specification
+
+class CreateDefaultBuildTypesTest extends Specification {
+    def buildTypes = Mock(BuildTypeContainer)
+    def rule = new CreateDefaultBuildTypes()
+
+    def "adds a default build type when none configured"() {
+        when:
+        rule.createDefaultPlatforms(buildTypes)
+
+        then:
+        1 * buildTypes.empty >> true
+        1 * buildTypes.create("debug")
+        0 * buildTypes._
+    }
+
+    def "does not add default build type when some configured"() {
+        when:
+        rule.createDefaultPlatforms(buildTypes)
+
+        then:
+        1 * buildTypes.empty >> false
+        0 * buildTypes._
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultFlavorsTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultFlavorsTest.groovy
new file mode 100644
index 0000000..d11a912
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultFlavorsTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.nativebinaries.internal.DefaultFlavor
+import org.gradle.nativebinaries.internal.DefaultFlavorContainer
+import spock.lang.Specification
+
+class CreateDefaultFlavorsTest extends Specification {
+    def flavorContainer = new DefaultFlavorContainer(new DirectInstantiator())
+    def rule = new CreateDefaultFlavors()
+
+    def "has a single default flavor when not configured"() {
+        when:
+        rule.createDefaultFlavor(flavorContainer)
+
+        then:
+        flavorContainer.size() == 1
+        flavorNames == [DefaultFlavor.DEFAULT] as Set
+    }
+
+    def "configured flavors overwrite default flavor"() {
+        when:
+        flavorContainer.configure {
+            flavor1 {}
+            flavor2 {}
+        }
+        and:
+        rule.createDefaultFlavor(flavorContainer)
+
+        then:
+        flavorNames == ["flavor1", "flavor2"] as Set
+    }
+
+    def "can explicitly add flavor named 'default'"() {
+        when:
+        flavorContainer.configure {
+            flavor1 {}
+            it.'default' {}
+            flavor2 {}
+        }
+        and:
+        rule.createDefaultFlavor(flavorContainer)
+
+        then:
+        flavorNames == [DefaultFlavor.DEFAULT, "flavor1", "flavor2"] as Set
+
+    }
+
+    def getFlavorNames() {
+        return flavorContainer.collect { it.name } as Set
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultPlatformTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultPlatformTest.groovy
new file mode 100644
index 0000000..cfa14ae
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/CreateDefaultPlatformTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure
+
+import org.gradle.nativebinaries.platform.PlatformContainer
+import spock.lang.Specification
+
+class CreateDefaultPlatformTest extends Specification {
+    def platforms = Mock(PlatformContainer)
+    def action = new CreateDefaultPlatform()
+
+    def "adds a default platform when none configured"() {
+        when:
+        action.createDefaultPlatforms(platforms)
+
+        then:
+        1 * platforms.empty >> true
+        1 * platforms.create("current")
+        0 * platforms._
+    }
+
+    def "does not add default platform when some configured"() {
+        when:
+        action.createDefaultPlatforms(platforms)
+
+        then:
+        1 * platforms.empty >> false
+        0 * platforms._
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/DefaultNativeBinariesFactoryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/DefaultNativeBinariesFactoryTest.groovy
new file mode 100644
index 0000000..47803da
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/DefaultNativeBinariesFactoryTest.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure
+import org.gradle.api.Action
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.base.internal.DefaultBinaryNamingSchemeBuilder
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Flavor
+import org.gradle.nativebinaries.ProjectNativeBinary
+import org.gradle.nativebinaries.SharedLibraryBinary
+import org.gradle.nativebinaries.internal.DefaultExecutable
+import org.gradle.nativebinaries.internal.DefaultLibrary
+import org.gradle.nativebinaries.internal.NativeProjectComponentIdentifier
+import org.gradle.nativebinaries.internal.resolve.NativeDependencyResolver
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import spock.lang.Specification
+
+class DefaultNativeBinariesFactoryTest extends Specification {
+    def resolver = Mock(NativeDependencyResolver)
+    Action<ProjectNativeBinary> configAction = Mock(Action)
+
+    def toolChain = Mock(ToolChainInternal)
+    def platform = Mock(Platform)
+    def buildType = Mock(BuildType)
+    def flavor = Mock(Flavor)
+
+    def id = new NativeProjectComponentIdentifier("project", "name")
+
+    def namingSchemeBuilder = new DefaultBinaryNamingSchemeBuilder().withComponentName("test")
+    def factory = new DefaultNativeBinariesFactory(new DirectInstantiator(), configAction, resolver)
+
+    def "creates binaries for executable"() {
+        given:
+        def executable = new DefaultExecutable(id)
+
+        when:
+        1 * configAction.execute(_)
+
+        and:
+        factory.createNativeBinaries(executable, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+
+        then:
+        executable.binaries.size() == 1
+        def binary = (executable.binaries as List)[0] as ProjectNativeBinary
+        binary.name == "testExecutable"
+        binary.toolChain == toolChain
+        binary.targetPlatform == platform
+        binary.buildType == buildType
+        binary.flavor == flavor
+    }
+
+    def "creates binaries for library"() {
+        given:
+        def library = new DefaultLibrary(id)
+
+        when:
+        2 * configAction.execute(_)
+
+        and:
+        factory.createNativeBinaries(library, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+
+        then:
+        library.binaries.size() == 2
+        def sharedLibrary = (library.binaries.withType(SharedLibraryBinary) as List)[0] as ProjectNativeBinary
+        sharedLibrary.name == "testSharedLibrary"
+        sharedLibrary.toolChain == toolChain
+        sharedLibrary.targetPlatform == platform
+        sharedLibrary.buildType == buildType
+        sharedLibrary.flavor == flavor
+
+        def staticLibrary = (library.binaries.withType(SharedLibraryBinary) as List)[0] as ProjectNativeBinary
+        staticLibrary.name == "testSharedLibrary"
+        staticLibrary.toolChain == toolChain
+        staticLibrary.targetPlatform == platform
+        staticLibrary.buildType == buildType
+        staticLibrary.flavor == flavor
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeBinaryInitializerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeBinaryInitializerTest.groovy
new file mode 100644
index 0000000..9065895
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeBinaryInitializerTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure
+
+import org.gradle.api.Project
+import org.gradle.language.base.internal.BinaryNamingScheme
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.ProjectNativeBinaryInternal
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectNativeBinaryInitializerTest extends Specification {
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+
+    def component = Mock(ProjectNativeComponent)
+    def project = Mock(Project)
+    def configAction
+
+    def namingScheme = Mock(BinaryNamingScheme)
+    def toolChain = Mock(ToolChainInternal)
+
+    def setup() {
+        project.buildDir >> tmpDir.testDirectory
+        configAction = new ProjectNativeBinaryInitializer(project)
+    }
+
+    def "test executable"() {
+        def binary = initBinary(ExecutableBinaryInternal)
+
+        when:
+        toolChain.getExecutableName("base_name") >> "exe_name"
+
+        and:
+        configAction.execute(binary)
+
+        then:
+        1 * binary.setExecutableFile(tmpDir.testDirectory.file("binaries", "output_dir", "exe_name"))
+    }
+
+    def "test shared library"() {
+        def binary = initBinary(SharedLibraryBinaryInternal)
+
+        when:
+        toolChain.getSharedLibraryName("base_name") >> "shared_library_name"
+        toolChain.getSharedLibraryLinkFileName("base_name") >> "shared_library_link_name"
+
+        and:
+        configAction.execute(binary)
+
+        then:
+        1 * binary.setSharedLibraryFile(tmpDir.testDirectory.file("binaries", "output_dir", "shared_library_name"))
+        1 * binary.setSharedLibraryLinkFile(tmpDir.testDirectory.file("binaries", "output_dir", "shared_library_link_name"))
+    }
+
+    def "test static library"() {
+        def binary = initBinary(StaticLibraryBinaryInternal)
+
+        when:
+        toolChain.getStaticLibraryName("base_name") >> "static_library_name"
+
+        and:
+        configAction.execute(binary)
+
+        then:
+        1 * binary.setStaticLibraryFile(tmpDir.testDirectory.file("binaries", "output_dir", "static_library_name"))
+    }
+
+    private <T extends ProjectNativeBinaryInternal> T initBinary(Class<T> type) {
+        def binary = Mock(type)
+        binary.component >> component
+        binary.toolChain >> toolChain
+        binary.namingScheme >> namingScheme
+
+        namingScheme.outputDirectoryBase >> "output_dir"
+        component.baseName >> "base_name"
+        return binary
+    }
+
+    interface ExecutableBinaryInternal extends ExecutableBinary, ProjectNativeBinaryInternal {}
+    interface SharedLibraryBinaryInternal extends SharedLibraryBinary, ProjectNativeBinaryInternal {}
+    interface StaticLibraryBinaryInternal extends StaticLibraryBinary, ProjectNativeBinaryInternal {}
+
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeComponentInitializerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeComponentInitializerTest.groovy
new file mode 100644
index 0000000..689f9ae
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/configure/ProjectNativeComponentInitializerTest.groovy
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.configure
+
+import org.gradle.api.Named
+import org.gradle.language.base.internal.BinaryNamingSchemeBuilder
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Flavor
+import org.gradle.nativebinaries.internal.DefaultExecutable
+import org.gradle.nativebinaries.internal.NativeProjectComponentIdentifier
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.nativebinaries.toolchain.internal.ToolChainRegistryInternal
+import spock.lang.Specification
+
+class ProjectNativeComponentInitializerTest extends Specification {
+    def toolChains = Mock(ToolChainRegistryInternal)
+    def toolChain = Mock(ToolChainInternal)
+    def nativeBinariesFactory = Mock(NativeBinariesFactory)
+    def namingSchemeBuilder = Mock(BinaryNamingSchemeBuilder)
+
+    def platform = createStub(Platform, "platform1")
+    def buildType = createStub(BuildType, "buildType1")
+    def flavor = createStub(Flavor, "flavor1")
+
+    def id = new NativeProjectComponentIdentifier("project", "name")
+    def component = new DefaultExecutable(id)
+
+    def "does not use variant dimension names for single valued dimensions"() {
+        when:
+        def factory = new ProjectNativeComponentInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains, [platform], [buildType], [flavor])
+        factory.execute(component)
+
+        then:
+        1 * toolChains.getForPlatform(platform) >> toolChain
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+        0 * namingSchemeBuilder._
+    }
+
+    def "does not use variant dimension names when component targets a single point on dimension"() {
+        when:
+        def factory = new ProjectNativeComponentInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
+                [platform, Mock(Platform)], [buildType, Mock(BuildType)], [flavor, Mock(Flavor)])
+        component.targetPlatforms("platform1")
+        component.targetBuildTypes("buildType1")
+        component.targetFlavors("flavor1")
+        factory.execute(component)
+
+        then:
+        1 * toolChains.getForPlatform(platform) >> toolChain
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+        0 * namingSchemeBuilder._
+    }
+
+    def "includes platform in name for when multiple platforms"() {
+        final Platform platform2 = createStub(Platform, "platform2")
+        when:
+        def factory = new ProjectNativeComponentInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
+                [platform, platform2], [buildType], [flavor])
+        factory.execute(component)
+
+        then:
+        1 * toolChains.getForPlatform(platform) >> toolChain
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * namingSchemeBuilder.withVariantDimension("platform1") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+        0 * _
+
+        then:
+        1 * toolChains.getForPlatform(platform2) >> toolChain
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * namingSchemeBuilder.withVariantDimension("platform2") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform2, buildType, flavor)
+        0 * _
+    }
+
+    def "includes buildType in name for when multiple buildTypes"() {
+        final BuildType buildType2 = createStub(BuildType, "buildType2")
+        when:
+        def factory = new ProjectNativeComponentInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
+                [platform], [buildType, buildType2], [flavor])
+        factory.execute(component)
+
+        then:
+        1 * toolChains.getForPlatform(platform) >> toolChain
+
+        then:
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * namingSchemeBuilder.withVariantDimension("buildType1") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+        0 * _
+
+        then:
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * namingSchemeBuilder.withVariantDimension("buildType2") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType2, flavor)
+        0 * _
+    }
+
+    def "includes flavor in name for when multiple flavors"() {
+        final Flavor flavor2 = createStub(Flavor, "flavor2")
+        when:
+        def factory = new ProjectNativeComponentInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
+                [platform], [buildType], [flavor, flavor2])
+        factory.execute(component)
+
+        then:
+        1 * toolChains.getForPlatform(platform) >> toolChain
+
+        then:
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * namingSchemeBuilder.withVariantDimension("flavor1") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType, flavor)
+        0 * _
+
+        then:
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        1 * namingSchemeBuilder.withVariantDimension("flavor2") >> namingSchemeBuilder
+        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, platform, buildType, flavor2)
+        0 * _
+    }
+
+    private <T extends Named> T createStub(Class<T> type, def name) {
+        def stub = Stub(type) {
+            getName() >> name
+        }
+        return stub
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltSharedLibraryBinaryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltSharedLibraryBinaryTest.groovy
new file mode 100644
index 0000000..97c5ed2
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltSharedLibraryBinaryTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt
+
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Flavor
+import org.gradle.nativebinaries.PrebuiltLibrary
+import org.gradle.nativebinaries.platform.Platform
+import spock.lang.Specification
+
+class DefaultPrebuiltSharedLibraryBinaryTest extends Specification {
+    def binary = new DefaultPrebuiltSharedLibraryBinary("name", Stub(PrebuiltLibrary), Stub(BuildType), Stub(Platform), Stub(Flavor))
+
+    def "has useful string representation"() {
+        expect:
+        binary.toString() == "shared library 'name'"
+        binary.displayName == "shared library 'name'"
+    }
+
+    def "uses library file when link file not set"() {
+        given:
+        def sharedLibraryFile = Mock(File)
+        def sharedLibraryLinkFile = Mock(File)
+
+        when:
+        binary.sharedLibraryFile = sharedLibraryFile
+
+        then:
+        binary.sharedLibraryFile == sharedLibraryFile
+        binary.sharedLibraryLinkFile == sharedLibraryFile
+
+        when:
+        binary.sharedLibraryLinkFile = sharedLibraryLinkFile
+
+        then:
+        binary.sharedLibraryFile == sharedLibraryFile
+        binary.sharedLibraryLinkFile == sharedLibraryLinkFile
+    }
+
+    def "uses specified linke file and library file"() {
+        given:
+        def sharedLibraryFile = createFile()
+        def sharedLibraryLinkFile = createFile()
+
+        when:
+        binary.sharedLibraryFile = sharedLibraryFile
+        binary.sharedLibraryLinkFile = sharedLibraryLinkFile
+
+        then:
+        binary.linkFiles.files == [sharedLibraryLinkFile] as Set
+        binary.runtimeFiles.files == [sharedLibraryFile] as Set
+    }
+
+    def createFile() {
+        def file = Stub(File) {
+            exists() >> true
+            isFile() >> true
+        }
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltStaticLibraryBinaryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltStaticLibraryBinaryTest.groovy
new file mode 100644
index 0000000..d0e4ae6
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/prebuilt/DefaultPrebuiltStaticLibraryBinaryTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.internal.prebuilt
+
+import org.gradle.nativebinaries.BuildType
+import org.gradle.nativebinaries.Flavor
+import org.gradle.nativebinaries.PrebuiltLibrary
+import org.gradle.nativebinaries.platform.Platform
+import spock.lang.Specification
+
+class DefaultPrebuiltStaticLibraryBinaryTest extends Specification {
+    def binary = new DefaultPrebuiltStaticLibraryBinary("name", Stub(PrebuiltLibrary), Stub(BuildType), Stub(Platform), Stub(Flavor))
+
+    def "has useful string representation"() {
+        expect:
+        binary.toString() == "static library 'name'"
+        binary.displayName == "static library 'name'"
+    }
+
+    def "can set static library file"() {
+        given:
+        def file = createFile()
+
+        when:
+        binary.staticLibraryFile = file
+
+        then:
+        binary.staticLibraryFile == file
+        binary.linkFiles.files == [file] as Set
+
+        and:
+        binary.runtimeFiles.empty
+    }
+
+    def createFile() {
+        def file = Stub(File) {
+            exists() >> true
+            isFile() >> true
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyNotationParserTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyNotationParserTest.groovy
new file mode 100644
index 0000000..489be6f
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/resolve/NativeDependencyNotationParserTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.Library
+import org.gradle.nativebinaries.NativeLibraryRequirement
+import spock.lang.Specification
+
+class NativeDependencyNotationParserTest extends Specification {
+    def parser = NativeDependencyNotationParser.parser()
+    def requirement = Mock(NativeLibraryRequirement)
+    def library = Mock(Library)
+    def project = Mock(ProjectInternal)
+
+    def "uses shared variant of library"() {
+        when:
+        def input = library
+
+        and:
+        library.shared >> requirement
+
+        then:
+        parser.parseNotation(input) == requirement
+    }
+
+    def "parses map notation for library in same project"() {
+        when:
+        def input = [library: 'libName']
+        def dependency = parser.parseNotation(input)
+
+        then:
+        dependency.projectPath == null
+        dependency.libraryName == "libName"
+        dependency.linkage == null
+    }
+
+    def "parses map notation for library in other project"() {
+        when:
+        def input = [project: 'other', library: 'libName']
+        def dependency = parser.parseNotation(input)
+
+
+        then:
+        dependency.projectPath == "other"
+        dependency.libraryName == "libName"
+        dependency.linkage == null
+    }
+
+    def "parses map notation for library with defined linkage"() {
+        when:
+        def input = [project: 'other', library: 'libName', linkage: 'static']
+        def dependency = parser.parseNotation(input)
+
+        then:
+        dependency.projectPath == "other"
+        dependency.libraryName == "libName"
+        dependency.linkage == "static"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy
new file mode 100644
index 0000000..1ee846e
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.internal.resolve
+import org.gradle.api.DomainObjectSet
+import org.gradle.api.UnknownDomainObjectException
+import org.gradle.api.UnknownProjectException
+import org.gradle.api.internal.plugins.ExtensionContainerInternal
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.nativebinaries.Library
+import org.gradle.nativebinaries.LibraryContainer
+import org.gradle.nativebinaries.NativeLibraryRequirement
+import org.gradle.nativebinaries.internal.ProjectNativeLibraryRequirement
+import spock.lang.Specification
+
+class ProjectLibraryBinaryLocatorTest extends Specification {
+    def project = Mock(ProjectInternal)
+    def projectLocator = Mock(ProjectLocator)
+    def requirement = Mock(NativeLibraryRequirement)
+    def library = Mock(Library)
+    def binaries = Mock(DomainObjectSet)
+    def locator = new ProjectLibraryBinaryLocator(projectLocator)
+
+    def setup() {
+        library.binaries >> binaries
+    }
+
+    def "locates binaries for library in same project"() {
+        when:
+        requirement = new ProjectNativeLibraryRequirement("libName", null)
+
+        and:
+        projectLocator.locateProject(null) >> project
+        findLibraryInProject()
+
+        then:
+        locator.getBinaries(requirement) == binaries
+    }
+
+    def "locates binaries for library in other project"() {
+        when:
+        requirement = new ProjectNativeLibraryRequirement("other", "libName", null)
+
+        and:
+        projectLocator.locateProject("other") >> project
+        findLibraryInProject()
+
+        then:
+        locator.getBinaries(requirement) == binaries
+    }
+
+    def "parses map notation for library with static linkage"() {
+        when:
+        requirement = new ProjectNativeLibraryRequirement("other", "libName", "static")
+
+        and:
+        projectLocator.locateProject("other") >> project
+        findLibraryInProject()
+
+        then:
+        locator.getBinaries(requirement) == binaries
+    }
+
+    def "fails for unknown project"() {
+        when:
+        requirement = new ProjectNativeLibraryRequirement("unknown", "libName", "static")
+
+        and:
+        projectLocator.locateProject("unknown") >> { throw new UnknownProjectException("unknown")}
+
+        and:
+        locator.getBinaries(requirement)
+
+        then:
+        thrown(UnknownProjectException)
+    }
+
+    def "fails for unknown library"() {
+        when:
+        requirement = new ProjectNativeLibraryRequirement("other", "unknown", "static")
+
+        and:
+        projectLocator.locateProject("other") >> project
+        def libraries = findLibraryContainer(project)
+        libraries.getByName("unknown") >> { throw new UnknownDomainObjectException("unknown") }
+
+        and:
+        locator.getBinaries(requirement)
+
+        then:
+        thrown(UnknownDomainObjectException)
+    }
+
+    def "fails when project does not have libraries"() {
+        when:
+        requirement = new ProjectNativeLibraryRequirement("other", "libName", "static")
+
+        and:
+        projectLocator.locateProject("other") >> project
+        def extensions = Mock(ExtensionContainerInternal)
+        project.getExtensions() >> extensions
+        extensions.findByName("libraries") >> null
+        project.path >> "project-path"
+
+        and:
+        locator.getBinaries(requirement)
+
+        then:
+        def e = thrown(LibraryResolveException)
+        e.message == "Project does not have a libraries container: 'project-path'"
+    }
+
+    private void findLibraryInProject() {
+        def libraries = findLibraryContainer(project)
+        libraries.getByName("libName") >> library
+    }
+    private LibraryContainer findLibraryContainer(ProjectInternal project) {
+        def extensions = Mock(ExtensionContainerInternal)
+        def libraries = Mock(LibraryContainer)
+        project.getExtensions() >> extensions
+        extensions.findByName("libraries") >> libraries
+        return libraries
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/AbstractNativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/AbstractNativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..96c7c6b
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/AbstractNativeBinariesPluginTest.groovy
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language
+
+import org.apache.commons.lang.StringUtils
+import org.gradle.api.Plugin
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.nativebinaries.ExecutableBinary
+import org.gradle.nativebinaries.NativeBinary
+import org.gradle.util.GFileUtils
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+abstract class AbstractNativeBinariesPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    abstract Class<? extends Plugin> getPluginClass();
+    abstract Class<? extends LanguageSourceSet> getSourceSetClass();
+    abstract Class<? extends Plugin> getCompileTaskClass();
+    abstract String getPluginName();
+
+    def "creates source set with conventional locations for components"() {
+        when:
+        dsl {
+            apply plugin: pluginClass
+            executables {
+                exe {}
+            }
+            libraries {
+                lib {}
+            }
+        }
+
+        then:
+        def sourceSets = project.sources
+        sourceSets.size() == 2
+        sourceSets*.name == ["exe", "lib"]
+
+        and:
+        sourceSets.exe instanceof FunctionalSourceSet
+        sourceSetClass.isInstance(sourceSets.exe."$pluginName")
+        sourceSets.exe."$pluginName".source.srcDirs == [project.file("src/exe/$pluginName")] as Set
+        sourceSets.exe."$pluginName".exportedHeaders.srcDirs == [project.file("src/exe/headers")] as Set
+        project.executables.exe.source == [sourceSets.exe."$pluginName"] as Set
+
+        and:
+        sourceSets.lib instanceof FunctionalSourceSet
+        sourceSetClass.isInstance(sourceSets.lib."$pluginName")
+        sourceSets.lib."$pluginName".source.srcDirs == [project.file("src/lib/$pluginName")] as Set
+        sourceSets.lib."$pluginName".exportedHeaders.srcDirs == [project.file("src/lib/headers")] as Set
+        project.libraries.lib.source == [sourceSets.lib."$pluginName"] as Set
+    }
+
+    def "can configure source set locations"() {
+        given:
+        dsl {
+            apply plugin: pluginClass
+            sources {
+                exe {
+                    "$pluginName" {
+                        source {
+                            srcDirs "d1", "d2"
+                        }
+                        exportedHeaders {
+                            srcDirs "h1", "h2"
+                        }
+                    }
+                }
+                lib {
+                    "$pluginName" {
+                        source {
+                            srcDirs "d3"
+                        }
+                        exportedHeaders {
+                            srcDirs "h3"
+                        }
+                    }
+                }
+            }
+        }
+
+        expect:
+        def sourceSets = project.sources
+        with (sourceSets.exe."$pluginName") {
+            source.srcDirs*.name == ["d1", "d2"]
+            exportedHeaders.srcDirs*.name == ["h1", "h2"]
+        }
+
+        with (sourceSets.lib."$pluginName") {
+            source.srcDirs*.name == ["d3"]
+            exportedHeaders.srcDirs*.name == ["h3"]
+        }
+    }
+
+    def "creates compile tasks for each non-empty executable source set"() {
+        when:
+        touch("src/test/$pluginName/file.o")
+        touch("src/test/anotherOne/file.o")
+        dsl {
+            apply plugin: pluginClass
+            sources {
+                test {
+                    anotherOne(sourceSetClass) {}
+                    emptyOne(sourceSetClass) {}
+                }
+            }
+            executables {
+                test {
+                    binaries.all { NativeBinary binary ->
+                        binary."${pluginName}Compiler".define "NDEBUG"
+                        binary."${pluginName}Compiler".define "LEVEL", "1"
+                        binary."${pluginName}Compiler".args "ARG1", "ARG2"
+                    }
+                }
+            }
+        }
+
+        then:
+        ExecutableBinary binary = project.binaries.testExecutable
+        binary.tasks.withType(compileTaskClass)*.name == ["compileTestExecutableTestAnotherOne", "compileTestExecutableTest${StringUtils.capitalize(pluginName)}"]
+
+        and:
+        binary.tasks.withType(compileTaskClass).each { compile ->
+            compile.toolChain == binary.toolChain
+            compile.macros == [NDEBUG:null, LEVEL:"1"]
+            compile.compilerArgs == ["ARG1", "ARG2"]
+        }
+
+        and:
+        def linkTask = binary.tasks.link
+        linkTask TaskDependencyMatchers.dependsOn("compileTestExecutableTestAnotherOne", "compileTestExecutableTest${StringUtils.capitalize(pluginName)}")
+    }
+
+
+    def touch(String filePath) {
+        GFileUtils.touch(project.file(filePath))
+    }
+
+    def dsl(Closure closure) {
+        closure.delegate = project
+        closure()
+        project.evaluate()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerNativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerNativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..dbb720e
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/assembler/plugins/AssemblerNativeBinariesPluginTest.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.assembler.plugins
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.language.assembler.AssemblerSourceSet
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.nativebinaries.ExecutableBinary
+import org.gradle.nativebinaries.NativeBinary
+import org.gradle.nativebinaries.SharedLibraryBinary
+import org.gradle.nativebinaries.StaticLibraryBinary
+import org.gradle.nativebinaries.language.assembler.tasks.Assemble
+import org.gradle.util.GFileUtils
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class AssemblerNativeBinariesPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def "creates asm source set with conventional locations for components"() {
+        when:
+        dsl {
+            apply plugin: AssemblerNativeBinariesPlugin
+            executables {
+                exe {}
+            }
+            libraries {
+                lib {}
+            }
+        }
+
+        then:
+        def sourceSets = project.sources
+        sourceSets.size() == 2
+        sourceSets*.name == ["exe", "lib"]
+
+        and:
+        sourceSets.exe.asm instanceof AssemblerSourceSet
+        sourceSets.exe.asm.source.srcDirs == [project.file("src/exe/asm")] as Set
+        project.executables.exe.source == [sourceSets.exe.asm] as Set
+
+        and:
+        sourceSets.lib instanceof FunctionalSourceSet
+        sourceSets.lib.asm instanceof AssemblerSourceSet
+        sourceSets.lib.asm.source.srcDirs == [project.file("src/lib/asm")] as Set
+        project.libraries.lib.source == [sourceSets.lib.asm] as Set
+    }
+
+    def "can configure source set locations"() {
+        given:
+        dsl {
+            apply plugin: AssemblerNativeBinariesPlugin
+            sources {
+                exe {
+                    asm {
+                        source {
+                            srcDirs "d1", "d2"
+                        }
+                    }
+                }
+                lib {
+                    asm {
+                        source {
+                            srcDirs "d3"
+                        }
+                    }
+                }
+            }
+        }
+
+        expect:
+        project.sources.exe.asm.source.srcDirs*.name == ["d1", "d2"]
+        project.sources.lib.asm.source.srcDirs*.name == ["d3"]
+    }
+
+    def "creates assemble tasks for each non-empty executable source set "() {
+        when:
+        touch("src/test/asm/dummy.s")
+        touch("src/test/anotherOne/dummy.s")
+        dsl {
+            apply plugin: AssemblerNativeBinariesPlugin
+            sources {
+                test {
+                    anotherOne(AssemblerSourceSet) {}
+                    emptyOne(AssemblerSourceSet) {}
+                }
+            }
+            executables {
+                test {
+                    binaries.all { NativeBinary binary ->
+                        binary.assembler.args "ARG1", "ARG2"
+                    }
+                }
+            }
+        }
+
+        then:
+        ExecutableBinary binary = project.binaries.testExecutable
+        binary.tasks.withType(Assemble)*.name == ["assembleTestExecutableTestAnotherOne", "assembleTestExecutableTestAsm"]
+
+        and:
+        binary.tasks.withType(Assemble).each { compile ->
+            compile instanceof Assemble
+            compile.toolChain == binary.toolChain
+            compile.assemblerArgs == ["ARG1", "ARG2"]
+        }
+
+        and:
+        def linkTask = binary.tasks.link
+        linkTask TaskDependencyMatchers.dependsOn("assembleTestExecutableTestAnotherOne", "assembleTestExecutableTestAsm")
+    }
+
+    def "creates assemble tasks for each library source set"() {
+        when:
+        touch("src/test/asm/dummy.s")
+        touch("src/test/anotherOne/dummy.s")
+        dsl {
+            apply plugin: AssemblerNativeBinariesPlugin
+            sources {
+                test {
+                    anotherOne(AssemblerSourceSet) {}
+                    emptyOne(AssemblerSourceSet) {}
+                }
+            }
+            libraries {
+                test {
+                    binaries.all {
+                        assembler.args "ARG1", "ARG2"
+                    }
+                    binaries.withType(SharedLibraryBinary) {
+                        assembler.args "SHARED1", "SHARED2"
+                    }
+                    binaries.withType(StaticLibraryBinary) {
+                        assembler.args "STATIC1", "STATIC2"
+                    }
+                }
+            }
+        }
+
+        then:
+        SharedLibraryBinary sharedLib = project.binaries.testSharedLibrary
+        sharedLib.tasks.withType(Assemble)*.name == ["assembleTestSharedLibraryTestAnotherOne", "assembleTestSharedLibraryTestAsm"]
+        sharedLib.tasks.withType(Assemble).each { compile ->
+            compile.toolChain == sharedLib.toolChain
+            compile.assemblerArgs == ["ARG1", "ARG2", "SHARED1", "SHARED2"]
+        }
+        def sharedLinkTask = sharedLib.tasks.link
+        sharedLinkTask TaskDependencyMatchers.dependsOn("assembleTestSharedLibraryTestAnotherOne", "assembleTestSharedLibraryTestAsm")
+
+        and:
+        StaticLibraryBinary staticLib = project.binaries.testStaticLibrary
+        staticLib.tasks.withType(Assemble)*.name == ["assembleTestStaticLibraryTestAnotherOne", "assembleTestStaticLibraryTestAsm"]
+        staticLib.tasks.withType(Assemble).each { compile ->
+            compile.toolChain == sharedLib.toolChain
+            compile.assemblerArgs == ["ARG1", "ARG2", "STATIC1", "STATIC2"]
+        }
+        def staticLibTask = staticLib.tasks.createStaticLib
+        staticLibTask TaskDependencyMatchers.dependsOn("assembleTestStaticLibraryTestAnotherOne", "assembleTestStaticLibraryTestAsm")
+    }
+
+    def touch(String filePath) {
+        GFileUtils.touch(project.file(filePath))
+    }
+
+    def dsl(Closure closure) {
+        closure.delegate = project
+        closure()
+        project.evaluate()
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/assembler/tasks/AssemblerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/assembler/tasks/AssemblerTest.groovy
new file mode 100644
index 0000000..4c6b8da
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/assembler/tasks/AssemblerTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.assembler.tasks
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.tasks.WorkResult
+import org.gradle.nativebinaries.platform.internal.PlatformInternal
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.nativebinaries.language.assembler.internal.AssembleSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class AssemblerTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    Assemble assembleTask = TestUtil.createTask(Assemble)
+    def toolChain = Mock(ToolChainInternal)
+    def platform = Mock(PlatformInternal)
+    def platformToolChain = Mock(PlatformToolChain)
+    Compiler<AssembleSpec> assembler = Mock(Compiler)
+
+    def "executes using the Assembler"() {
+        def inputDir = testDir.file("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        assembleTask.toolChain = toolChain
+        assembleTask.targetPlatform = platform
+        assembleTask.assemblerArgs = ["arg"]
+        assembleTask.objectFileDir = testDir.file("outputFile")
+        assembleTask.source inputDir
+        assembleTask.execute()
+
+        then:
+        _ * toolChain.outputType >> "c"
+        _ * platform.compatibilityString >> "p"
+        1 * toolChain.target(platform) >> platformToolChain
+        1 * platformToolChain.createAssembler() >> assembler
+        1 * assembler.execute({ AssembleSpec spec ->
+            assert spec.sourceFiles*.name == ["sourceFile"]
+            assert spec.args == ['arg']
+            assert spec.allArgs == ['arg']
+            assert spec.objectFileDir.name == "outputFile"
+            true
+        }) >> result
+        1 * result.didWork >> true
+        0 * _._
+
+        and:
+        assembleTask.didWork
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CleanCompilingNativeCompilerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CleanCompilingNativeCompilerTest.groovy
new file mode 100644
index 0000000..269cc9d
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CleanCompilingNativeCompilerTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental
+
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.internal.tasks.SimpleWorkResult
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class CleanCompilingNativeCompilerTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    def delegateCompiler = Mock(org.gradle.api.internal.tasks.compile.Compiler)
+    def incrementalCompileProcessor = Mock(IncrementalCompileProcessor)
+    def task = Mock(TaskInternal)
+    def outputs = Mock(TaskOutputsInternal)
+    def includesParser = Mock(SourceIncludesParser);
+    def compiler = new CleanCompilingNativeCompiler(task, includesParser, null, null, null, delegateCompiler)
+
+    def "cleans outputs and delegates spec for compilation"() {
+        def spec = Mock(NativeCompileSpec)
+        def existingSource = temporaryFolder.file("existing")
+        def newSource = temporaryFolder.file("new")
+        def outputFile = temporaryFolder.createFile("output", "previous")
+
+        def sources = [existingSource, newSource]
+        def compilation = Mock(IncrementalCompilation)
+
+        when:
+        outputFile.assertExists()
+
+        and:
+        spec.getSourceFiles() >> sources
+        incrementalCompileProcessor.processSourceFiles(_) >> compilation
+        0 * compilation._
+
+        and:
+        def result = compiler.doIncrementalCompile(incrementalCompileProcessor, spec)
+
+        then:
+        1 * spec.getObjectFileDir() >> outputFile.parentFile
+        1 * task.getOutputs() >> outputs
+        1 * outputs.previousFiles >> new SimpleFileCollection(outputFile)
+        0 * spec._
+        1 * delegateCompiler.execute(spec) >> new SimpleWorkResult(false)
+
+        and:
+        result.didWork
+        outputFile.assertDoesNotExist()
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationStateSerializerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationStateSerializerTest.groovy
new file mode 100644
index 0000000..d5529a3
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/CompilationStateSerializerTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal.incremental
+
+import org.gradle.messaging.serialize.SerializerSpec
+
+class CompilationStateSerializerTest extends SerializerSpec {
+    def state = new CompilationState()
+    private CompilationStateSerializer serializer = new CompilationStateSerializer()
+
+    def "serializes empty state"() {
+        expect:
+        with (serialized) {
+            sourceInputs.empty
+            fileStates.isEmpty()
+        }
+    }
+
+    def "serializes source inputs"() {
+        when:
+        def fileOne = new File("one")
+        def fileTwo = new File("two")
+        state.sourceInputs << fileOne << fileTwo
+
+        then:
+        with (serialized) {
+            sourceInputs == [fileOne, fileTwo]
+            fileStates.isEmpty()
+        }
+    }
+
+    def "serializes file state"() {
+        when:
+        def fileEmpty = new File("empty")
+        state.fileStates.put(fileEmpty, new CompilationFileState(new byte[0]))
+
+        def fileTwo = new File("two")
+        def stateTwo = new CompilationFileState("FooBar".getBytes())
+        stateTwo.sourceIncludes = createSourceIncludes("<system>", '"quoted"', "MACRO")
+        stateTwo.resolvedIncludes = [resolvedInclude("ONE"), resolvedInclude("TWO")]
+        state.fileStates.put(fileTwo, stateTwo)
+
+        then:
+        def newState = serialized
+        newState.sourceInputs.empty
+        newState.fileStates.size() == 2
+
+        def emptyCompileState = newState.getState(fileEmpty)
+        emptyCompileState.hash.length == 0
+        emptyCompileState.sourceIncludes.macroIncludes.empty
+        emptyCompileState.sourceIncludes.quotedIncludes.empty
+        emptyCompileState.sourceIncludes.systemIncludes.empty
+        emptyCompileState.resolvedIncludes.empty
+
+        def otherCompileState = newState.getState(fileTwo)
+        new String(otherCompileState.hash) == "FooBar"
+        otherCompileState.sourceIncludes.systemIncludes == ["system"]
+        otherCompileState.sourceIncludes.quotedIncludes == ["quoted"]
+        otherCompileState.sourceIncludes.macroIncludes == ["MACRO"]
+        otherCompileState.resolvedIncludes == [resolvedInclude("ONE"), resolvedInclude("TWO")] as Set
+    }
+
+    private static DefaultSourceIncludes createSourceIncludes(String... strings) {
+        final DefaultSourceIncludes sourceIncludes = new DefaultSourceIncludes()
+        sourceIncludes.addAll(strings as List<String>)
+        sourceIncludes
+    }
+
+    private static ResolvedInclude resolvedInclude(String value) {
+        return new ResolvedInclude(value, new File(value))
+    }
+
+    private CompilationState getSerialized() {
+        serialize(state, serializer) as CompilationState
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesParserTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesParserTest.groovy
new file mode 100644
index 0000000..d5b8c82
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesParserTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal.incremental
+
+import org.gradle.nativebinaries.language.c.internal.incremental.sourceparser.CSourceParser
+import spock.lang.Specification
+
+class DefaultSourceIncludesParserTest extends Specification {
+    def sourceParser = Mock(CSourceParser)
+    def sourceDetails = Mock(CSourceParser.SourceDetails)
+
+    def "imports are not included in includes"() {
+        given:
+        def file = new File("test")
+
+        when:
+        def includesParser = new DefaultSourceIncludesParser(sourceParser, false)
+
+        1 * sourceParser.parseSource(file) >> sourceDetails
+        1 * sourceDetails.includes >> ['"quoted"', '<system>', 'DEFINED']
+        0 * sourceDetails._
+
+        and:
+        def includes = includesParser.parseIncludes(file)
+
+        then:
+        includes.quotedIncludes == ["quoted"]
+        includes.systemIncludes == ["system"]
+        includes.macroIncludes == ["DEFINED"]
+    }
+
+
+    def "imports are included in includes"() {
+        given:
+        def file = new File("test")
+
+        when:
+        def includesParser = new DefaultSourceIncludesParser(sourceParser, true)
+
+        1 * sourceParser.parseSource(file) >> sourceDetails
+        1 * sourceDetails.includes >> ['"quoted"', '<system>', 'DEFINED']
+        1 * sourceDetails.imports >> ['"quotedImport"', '<systemImport>', 'DEFINED_IMPORT']
+        0 * sourceDetails._
+
+        and:
+        def includes = includesParser.parseIncludes(file)
+
+        then:
+        includes.quotedIncludes == ["quoted", "quotedImport"]
+        includes.systemIncludes == ["system", "systemImport"]
+        includes.macroIncludes == ["DEFINED", "DEFINED_IMPORT"]
+    }
+
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesResolverTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesResolverTest.groovy
new file mode 100644
index 0000000..4bc6fd8
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/DefaultSourceIncludesResolverTest.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultSourceIncludesResolverTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    def testDirectory = temporaryFolder.testDirectory
+    def sourceDirectory = testDirectory.createDir("sources")
+    def quotedIncludes = []
+    def systemIncludes = []
+    def macroIncludes = []
+    def includesParser = Mock(SourceIncludesParser)
+    def includes
+    def includePaths = []
+
+    def setup() {
+        includes = Mock(SourceIncludes)
+        includesParser.parseIncludes(sourceFile) >> includes
+        includes.getQuotedIncludes() >> quotedIncludes
+        includes.getSystemIncludes() >> systemIncludes
+        includes.getMacroIncludes() >> macroIncludes
+    }
+
+    protected TestFile getSourceFile() {
+        sourceDirectory.file('source.c')
+    }
+
+    def getDependencies() {
+        return new DefaultSourceIncludesResolver(includePaths).resolveIncludes(sourceFile, includes) as List
+    }
+
+    def "handles source file with no includes"() {
+        expect:
+        dependencies == []
+    }
+
+    def "ignores include files that do not exist"() {
+        when:
+        quotedIncludes << "test.h"
+        systemIncludes << "system"
+
+        then:
+        dependencies == []
+    }
+
+    def "locates quoted includes in same directory"() {
+        when:
+        final header1 = sourceDirectory.createFile("test1.h")
+        final header2 = sourceDirectory.createFile("test2.h")
+
+        and:
+        quotedIncludes << "test1.h" << "test2.h"
+
+        then:
+        dependencies == deps(header1, header2)
+    }
+
+    def "locates quoted includes relative to source directory"() {
+        when:
+        final header1 = sourceDirectory.createFile("test1.h")
+        final header2 = sourceDirectory.file("nested", "test2.h").createFile()
+        final header3 = sourceDirectory.file("..", "sibling", "test3.h").createFile()
+
+        and:
+        quotedIncludes << "test1.h" << "nested/test2.h" << "../sibling/test3.h"
+
+        then:
+        dependencies.collect {it.file} == [header1, header2, header3]
+    }
+
+    def "does not locate system includes in same directory"() {
+        when:
+        sourceDirectory.file("system.h").createFile()
+
+        and:
+        systemIncludes << "system.h"
+
+        then:
+        dependencies == []
+    }
+
+    def "locates includes in path"() {
+        when:
+        def includeDir1 = testDirectory.file("include1")
+        final header11 = includeDir1.file("test11.h").createFile()
+        final header12 = includeDir1.file("test12.h").createFile()
+        def includeDir2 = testDirectory.file("include2")
+        final header21 = includeDir1.file("test21.h").createFile()
+        final header22 = includeDir1.file("test22.h").createFile()
+
+        and:
+        includePaths << includeDir1 << includeDir2
+        quotedIncludes << "test11.h" << "test21.h"
+        systemIncludes << "test12.h" << "test22.h"
+
+        then:
+        dependencies == deps(header11, header21, header12, header22)
+    }
+
+    def "searches relative before searching include path"() {
+        when:
+        final relativeHeader = sourceDirectory.createFile("test.h")
+        final includeDir = testDirectory.file("include")
+        includeDir.createFile("test.h")
+        final otherHeader = includeDir.createFile("other.h")
+
+        and:
+        includePaths << includeDir
+        quotedIncludes << "test.h" << "other.h"
+
+        then:
+        dependencies == deps(relativeHeader, otherHeader)
+    }
+
+    def "includes unknown source dependency for first macro include"() {
+        when:
+        macroIncludes << 'DEFINE_1' << 'DEFINE_2'
+
+        then:
+        dependencies.size() == 1
+        with (dependencies[0]) {
+            unknown
+            include == 'DEFINE_1'
+            file == null
+        }
+    }
+
+    def deps(File... files) {
+        return files.collect {dep(it)}
+    }
+
+    def dep(File dependencyFile) {
+        return new ResolvedInclude(dependencyFile.name, dependencyFile)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompileProcessorTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompileProcessorTest.groovy
new file mode 100644
index 0000000..58a44f9
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalCompileProcessorTest.groovy
@@ -0,0 +1,422 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental
+
+import org.gradle.api.internal.changedetection.state.FileSnapshotter
+import org.gradle.cache.PersistentStateCache
+import org.gradle.internal.hash.HashUtil
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class IncrementalCompileProcessorTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    def includesParser = Mock(SourceIncludesParser)
+    def dependencyParser = Mock(SourceIncludesResolver)
+    def fileSnapshotter = Stub(FileSnapshotter)
+    def stateCache = new DummyPersistentStateCache()
+    def incrementalCompileProcessor = new IncrementalCompileProcessor(stateCache, dependencyParser, includesParser, fileSnapshotter)
+
+    def source1 = sourceFile("source1")
+    def source2 = sourceFile("source2")
+    def dep1 = sourceFile("dep1")
+    def dep2 = sourceFile("dep2")
+    def dep3 = sourceFile("dep3")
+    def dep4 = sourceFile("dep4")
+    def sourceFiles
+
+    Map<TestFile, List<ResolvedInclude>> graph = [:]
+    List<TestFile> modified = []
+
+    def setup() {
+        fileSnapshotter.snapshot(_) >> { File file ->
+            return Stub(FileSnapshotter.FileSnapshot) {
+                getHash() >> HashUtil.sha1(file).asByteArray()
+            }
+        }
+
+        // S1 - D1 \
+        //    \ D2  \
+        //           D3
+        // S2 ------/
+        //    \ D4
+
+        graph[source1] = deps(dep1, dep2)
+        graph[source2] = deps(dep3, dep4)
+        graph[dep1] = deps(dep3)
+        graph[dep2] = []
+        graph[dep3] = []
+        graph[dep4] = []
+    }
+
+    def initialFiles() {
+
+        graph.keySet().each { TestFile sourceFile ->
+            parse(sourceFile)
+            resolve(sourceFile)
+        }
+
+        sourceFiles = [source1, source2]
+        with (state) {
+            assert recompile == [source1, source2]
+            assert removed == []
+        }
+    }
+
+    def parse(TestFile sourceFile) {
+        final Set<ResolvedInclude> deps = graph[sourceFile]
+        SourceIncludes includes = includes(deps)
+        1 * includesParser.parseIncludes(sourceFile) >> includes
+    }
+
+    def resolve(TestFile sourceFile) {
+        Set<ResolvedInclude> deps = graph[sourceFile]
+        SourceIncludes includes = includes(deps)
+        1 * dependencyParser.resolveIncludes(sourceFile, includes) >> deps
+    }
+
+    private static SourceIncludes includes(Set<ResolvedInclude> deps) {
+        def includes = new DefaultSourceIncludes()
+        includes.addAll(deps.collect { '<' + it.file.name + '>' })
+        return includes
+    }
+
+    def added(TestFile sourceFile) {
+        modified << sourceFile
+        graph[sourceFile] = []
+    }
+
+    def sourceAdded(TestFile sourceFile, def deps = []) {
+        sourceFiles << sourceFile
+        modified << sourceFile
+        graph[sourceFile] = deps
+    }
+
+    def modified(TestFile sourceFile, def deps = null) {
+        modified << sourceFile
+        sourceFile << "More text"
+        if (deps != null) {
+            graph[sourceFile] = deps
+        }
+    }
+
+    def sourceRemoved(TestFile sourceFile) {
+        sourceFiles.remove(sourceFile)
+        graph.remove(sourceFile)
+    }
+
+    def dependencyRemoved(TestFile sourceFile) {
+        graph.remove(sourceFile)
+        sourceFile.delete()
+    }
+
+    def "detects unchanged source files"() {
+        given:
+        initialFiles()
+
+        expect:
+        checkCompile recompiled: [], removed: []
+    }
+
+    def "detects new source files"() {
+        given:
+        initialFiles()
+
+        when:
+        def file3 = sourceFile("file3")
+        sourceAdded(file3)
+
+        then:
+        checkCompile recompiled: [file3], removed: []
+    }
+
+    def "detects removed source file"() {
+        given:
+        initialFiles()
+
+        when:
+        sourceRemoved(source2)
+        graph.remove(dep4)
+
+        then:
+        checkCompile recompiled: [], removed: [source2]
+    }
+
+    def "detects removed source file with multiple dependencies"() {
+        given:
+        initialFiles()
+
+        when:
+        sourceRemoved(source1)
+        graph.remove(dep1)
+        graph.remove(dep2)
+
+        then:
+        checkCompile recompiled: [], removed: [source1]
+    }
+
+    def "detects source file changed"() {
+        given:
+        initialFiles()
+
+        when:
+        modified(source2)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def "detects dependency file changed"() {
+        given:
+        initialFiles()
+
+        when:
+        modified(dep4)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def "detects dependency file removed"() {
+        given:
+        initialFiles()
+
+        when:
+        dependencyRemoved(dep4)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def "detects shared dependency file changed"() {
+        given:
+        initialFiles()
+
+        when:
+        modified(dep3)
+
+        then:
+        checkCompile recompiled: [source1, source2], removed: []
+    }
+
+    def "detects source file change with new dependencies"() {
+        given:
+        initialFiles()
+
+        when:
+        def dep5 = sourceFile("dep5")
+        added(dep5)
+        modified(source2, deps(dep3, dep4, dep5))
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+
+        when:
+        modified(dep5)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def "detects unchanged source file change with different resolved dependencies"() {
+        given:
+        initialFiles()
+
+        when:
+        def dep5 = sourceFile("dep5")
+        graph[dep5] = []
+
+        resolve(source1)
+        resolve(dep1)
+        resolve(dep2)
+        resolve(dep3)
+        parse(dep5)
+        resolve(dep5)
+
+        1 * dependencyParser.resolveIncludes(source2, includes(deps(dep3, dep4))) >> deps(dep3, dep5)
+
+        then:
+        with (state) {
+            recompile == [source2]
+            removed == []
+        }
+    }
+
+    def "detects dependency file change with new dependencies"() {
+        given:
+        initialFiles()
+
+        when:
+        def dep5 = sourceFile("dep5")
+        modified(dep4, deps(dep5))
+        added(dep5)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+
+        when:
+        modified(dep5)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def "detects dependency file change adding dependency cycle"() {
+        given:
+        initialFiles()
+
+        when:
+        modified(dep3, deps(dep1))
+
+        then:
+        checkCompile recompiled: [source1, source2], removed: []
+    }
+
+    def "detects shared dependency file changed with new dependency"() {
+        given:
+        initialFiles()
+
+        when:
+        def dep5 = sourceFile("dep5")
+        modified(dep3, deps(dep5))
+        added(dep5)
+
+        then:
+        checkCompile recompiled: [source1, source2], removed: []
+
+        when:
+        modified(dep5)
+
+        then:
+        checkCompile recompiled: [source1, source2], removed: []
+    }
+
+    def "detects changed dependency with new source file including that dependency"() {
+        given:
+        initialFiles()
+
+        when:
+        def file3 = sourceFile("file3")
+        sourceAdded(file3, deps(dep4))
+        modified(dep4)
+
+        then:
+        checkCompile recompiled: [source2, file3], removed: []
+    }
+
+    def "detects source file removed then readded"() {
+        given:
+        initialFiles()
+
+        when:
+        sourceRemoved(source2)
+        graph.remove(dep4)
+
+        then:
+        checkCompile recompiled: [], removed: [source2]
+
+        when:
+        sourceAdded(source2, [])
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def "handles source file that is also a dependency"() {
+        given:
+        initialFiles()
+
+        when:
+        modified(dep2, deps(source2))
+
+        then:
+        checkCompile recompiled: [source1], removed: []
+
+        when:
+        modified(dep4)
+
+        then:
+        checkCompile recompiled: [source1, source2], removed: []
+    }
+
+    def "reports source file changed to dependency as removed"() {
+        given:
+        initialFiles()
+
+        when:
+        modified(dep2, deps(source2))
+        sourceFiles.remove(source2)
+
+        then:
+        checkCompile recompiled: [source1], removed: [source2]
+
+        when:
+        sourceFiles.add(source2)
+
+        then:
+        checkCompile recompiled: [source2], removed: []
+    }
+
+    def checkCompile(Map<String, List<File>> args) {
+        parseAndResolve()
+        with (state) {
+            assert recompile == args['recompiled']
+            assert removed == args['removed']
+        }
+        return true
+    }
+
+    def parseAndResolve() {
+        modified.each {
+            parse(it)
+        }
+        modified.clear()
+        graph.keySet().each { TestFile sourceFile ->
+            resolve(sourceFile)
+        }
+        true
+    }
+
+    def getState() {
+        incrementalCompileProcessor.processSourceFiles(sourceFiles)
+    }
+
+    def sourceFile(def name) {
+        tmpDir.createFile(name) << "initial text"
+    }
+
+    Set<ResolvedInclude> deps(File... dep) {
+        dep.collect {new ResolvedInclude(it.name, it)} as Set
+    }
+
+    class DummyPersistentStateCache implements PersistentStateCache<CompilationState> {
+        private CompilationState compilationState
+
+        CompilationState get() {
+            return compilationState
+        }
+
+        void set(CompilationState newValue) {
+            this.compilationState = newValue
+        }
+
+        void update(PersistentStateCache.UpdateAction<CompilationState> updateAction) {
+
+        }
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalNativeCompilerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalNativeCompilerTest.groovy
new file mode 100644
index 0000000..8930690
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/IncrementalNativeCompilerTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental
+
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class IncrementalNativeCompilerTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    def delegateCompiler = Mock(org.gradle.api.internal.tasks.compile.Compiler)
+    def incrementalCompileProcessor = Mock(IncrementalCompileProcessor)
+    def includesParser = Mock(SourceIncludesParser);
+    def compiler = new IncrementalNativeCompiler(null, includesParser, null, null, null, delegateCompiler)
+
+    def "updates spec for incremental compilation"() {
+        def spec = Mock(NativeCompileSpec)
+        def existingSource = temporaryFolder.file("existing")
+        def newSource = temporaryFolder.file("new")
+        def removedSource = temporaryFolder.file("removed")
+
+        def sources = [existingSource, newSource]
+        def compilation = Mock(IncrementalCompilation)
+
+        when:
+        spec.getSourceFiles() >> sources
+        incrementalCompileProcessor.processSourceFiles(_) >> compilation
+        compilation.getRecompile() >> [newSource]
+        compilation.getRemoved() >> [removedSource]
+
+        and:
+        compiler.doIncrementalCompile(incrementalCompileProcessor, spec)
+
+        then:
+        1 * spec.setSourceFiles([newSource])
+        1 * spec.setRemovedSourceFiles([removedSource])
+        1 * delegateCompiler.execute(spec)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/PreprocessingReaderTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/PreprocessingReaderTest.groovy
new file mode 100644
index 0000000..7b03685
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/PreprocessingReaderTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.internal.incremental.sourceparser
+
+import spock.lang.Specification
+
+class PreprocessingReaderTest extends Specification {
+    private static final String BN = "\\" + System.getProperty("line.separator")
+    String input
+
+    def getOutput() {
+        def reader = new PreprocessingReader(new StringReader(input))
+        return reader.text.trim()
+    }
+
+    def "removes line continuation characters"() {
+        when:
+        input = """Here is a ${BN}single line ${BN}with continuations \\${BN}and \\ slashes\\\t\n too\\ \n."""
+
+        then:
+        output == "Here is a single line with continuations \\and \\ slashes\\\t\n too\\ \n."
+    }
+
+    def "replaces inline comments with space"() {
+        when:
+        input = """
+Here/* comment */is a string/*
+multiline
+comment
+here */that contains/**
+ comment /* containing ** / ${BN} characters */several inline comments.
+"""
+
+        then:
+        output == "Here is a string that contains several inline comments."
+    }
+
+    def "replaces line comments with space"() {
+        when:
+        input = """
+Here is a // comment
+string
+// Line comment
+with interspersed // comment /* with inline comment */ and // other comment */ chars
+line comments.
+"""
+
+        then:
+        output == "Here is a \nstring\n\nwith interspersed \nline comments."
+    }
+
+    def "can cope with multiple unescaped and escaped \\r characters"() {
+        when:
+        input =  "Here \r\r\\\r\\\r${BN}\\\r\\\r\\\r\\\r."
+        then:
+        output == "Here \r\r\\\r\\\r\\\r\\\r\\\r\\\r."
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy
new file mode 100644
index 0000000..c0a0a3d
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.c.internal.incremental.sourceparser
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class RegexBackedCSourceParserTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    CSourceParser parser = new RegexBackedCSourceParser()
+
+    protected TestFile getSourceFile() {
+        testDirectory.file('source.c')
+    }
+
+    TestFile getTestDirectory() {
+        temporaryFolder.testDirectory
+    }
+
+    def getParsedSource() {
+        parser.parseSource(sourceFile)
+    }
+
+    def getIncludes() {
+        return parsedSource.includes
+    }
+
+    def getImports() {
+        return parsedSource.imports
+    }
+
+    def getFound() {
+        return includes + imports
+    }
+    
+    def noIncludes() {
+        assert includes == []
+        true
+    }
+    
+    def noImports() {
+        assert imports == []
+        true
+    }
+
+    def useDirective(String directive) {
+        sourceFile.text = sourceFile.text.replace("include", directive)
+    }
+
+    def "parses file with no includes"() {
+        when:
+        sourceFile << ""
+
+        then:
+        noIncludes()
+        noImports()
+    }
+
+    def "finds quoted include"() {
+        when:
+        sourceFile << """
+    #include "test.h"
+"""
+
+        then:
+        includes == ['"test.h"']
+
+        and:
+        noImports()
+    }
+
+    def "finds system include"() {
+        when:
+        sourceFile << """
+    #include <test.h>
+"""
+
+        then:
+        includes == ['<test.h>']
+        
+        and:
+        noImports()
+    }
+
+    def "finds defined include"() {
+        when:
+        sourceFile << """
+    #include DEFINED
+"""
+
+        then:
+        includes == ['DEFINED']
+
+        and:
+        noImports()
+    }
+
+    def "finds multiple includes"() {
+        when:
+        sourceFile << """
+    #include "test1"
+    #include "test2"
+    #include <system1>
+    #include <system2>
+    #include DEFINED
+"""
+        then:
+        includes == ['"test1"', '"test2"', '<system1>', '<system2>', 'DEFINED']
+        
+        and:
+        noImports()
+    }
+
+    def "finds quoted import"() {
+        when:
+        sourceFile << """
+            #import "test.h"
+        """
+
+        then:
+        imports == ['"test.h"']
+
+        and:
+        noIncludes()
+    }
+
+    def "finds system import"() {
+        when:
+        sourceFile << """
+            #import <test.h>
+        """
+
+        then:
+        imports == ['<test.h>']
+        
+        and:
+        noIncludes()
+    }
+
+    def "finds defined import"() {
+        when:
+        sourceFile << """
+            #import DEFINED
+        """
+
+        then:
+        imports == ['DEFINED']
+
+        and:
+        noIncludes()
+    }
+
+    def "finds multiple imports"() {
+        when:
+        sourceFile << """
+    #import "test1"
+    #import "test2"
+    #import <system1>
+    #import <system2>
+    #import DEFINED
+"""
+        then:
+        imports == ['"test1"', '"test2"', '<system1>', '<system2>', 'DEFINED']
+
+        and:
+        noIncludes()
+    }
+
+    def "finds mixed import include statement imports"() {
+        when:
+        sourceFile << """
+    #import "test1"
+    #include "test2"
+    #import "test3"
+    #include "test4"
+    #import <system1>
+    #import <system2>
+    #include <system3>
+    #import <system4>
+    #include DEFINED1
+    #import DEFINED2
+"""
+        then:
+        includes == ['"test2"', '"test4"', '<system3>', 'DEFINED1']
+        imports == ['"test1"', '"test3"', '<system1>', '<system2>', '<system4>', 'DEFINED2']
+    }
+
+    @Unroll
+    def "finds #directive surrounded by different whitespace"() {
+        when:
+        sourceFile << """
+#include     "test1"
+#include\t"test2"\t
+\t#include\t"test3"
+#include     <system1>
+#include\t<system2>\t
+\t#include\t<system3>
+"""
+        and:
+        useDirective(directive)
+
+        then:
+        found == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', '<system3>']
+
+        where:
+        directive << ["include", "import"]
+    }
+
+    @Unroll
+    def "finds #directive where whitespace surrounds the # character"() {
+        when:
+        sourceFile << """
+  #  include   "test1"
+\t#\tinclude "test2"
+
+  #  include   <system1>
+\t#\tinclude <system2>
+"""
+        and:
+        useDirective(directive)
+
+        then:
+        found == ['"test1"', '"test2"', '<system1>', '<system2>']
+
+        where:
+        directive << ["include", "import"]
+    }
+
+    def "ignores comment after directive"() {
+        when:
+        sourceFile << """
+#include "test1"  // A comment here
+#include "test2" /* A comment here */
+#include "test3" /*
+   A comment here
+*/
+#include <system1>  // A comment here
+#include <system2> /* A comment here */
+#include <system3> /*
+   A comment here
+*/
+"""
+        then:
+        includes == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', '<system3>']
+    }
+
+    @Unroll
+    def "finds #directive where comment in place of whitespace"() {
+        when:
+        sourceFile << """
+#include/* a comment here*/"test1"
+#/* a
+    comment
+    here*/include "test2"
+/* a comment here*/#include/* a comment here*/"test3"
+#include/* a comment here*/<system1>
+#/* a comment here*/include <system2>
+/* a comment here*/#include/* a comment here*/DEFINED
+"""
+        useDirective(directive)
+
+        then:
+        found == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', 'DEFINED']
+
+        where:
+        directive << ["include", "import"]
+    }
+
+    def "find quoted include with special characters"() {
+        when:
+        sourceFile << """
+    #include "$included"
+    #import "$included"
+"""
+        then:
+        includes == ['"' + included + '"']
+        imports == ['"' + included + '"']
+
+        where:
+        included << ["test'file", "testfile'", "'testfile'", "test<>file", "test>file", "<testFile>", "test<file", "test file"]
+    }
+
+    def "find system include with special characters"() {
+        when:
+        sourceFile << """
+    #include <$included>
+    #import <$included>
+"""
+        then:
+        includes == ['<' + included + '>']
+        imports == ['<' + included + '>']
+
+        where:
+        included << ["test'file", "testfile'", "'testfile'", "test<file", "test\"file", "\"testFile\"", "test file"]
+    }
+
+    def "find various defined includes"() {
+        when:
+        sourceFile << """
+    #include $included
+    #import $included
+"""
+        then:
+        includes == [included]
+        imports == [included]
+
+        where:
+        included << ["DEFINED", "mixedDefined", "DEF_INED", "_DEFINED", "__DEFINED__"]
+    }
+
+    @Unroll
+    def "ignores #directive inside a quoted string"() {
+        when:
+        sourceFile << """
+    printf("use #include <stdio.h>");
+    printf("use #include \\"test1\\");
+"""
+        and:
+        useDirective(directive)
+
+        then:
+        noIncludes()
+        noImports()
+
+        where:
+        directive << ["include", "import"]
+    }
+
+    @Unroll
+    def "ignores #directive that is commented out"() {
+        when:
+        sourceFile << """
+/*
+    #include "test1"
+    #include <system1>
+*/
+/* #include "test2" */
+/* #include <system3> */
+
+//    #include "test3"
+//    #include <system3>
+"""
+        and:
+        useDirective(directive)
+
+        then:
+        noIncludes()
+        noImports()
+
+        where:
+        directive << ["include", "import"]
+    }
+
+    def "detects imports with line=continuation"() {
+        when:
+        sourceFile << """
+#include \\
+"test1"
+#\\
+include\\
+ "test2"
+#incl\\
+ude "te\\
+st3"
+"""
+
+        then:
+        includes == ['"test1"', '"test2"', '"test3"']
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/plugins/CNativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/plugins/CNativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..08e28c3
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/plugins/CNativeBinariesPluginTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.c.CSourceSet
+import org.gradle.nativebinaries.language.AbstractNativeBinariesPluginTest
+import org.gradle.nativebinaries.language.c.tasks.CCompile
+import org.gradle.util.TestUtil
+
+class CNativeBinariesPluginTest extends AbstractNativeBinariesPluginTest{
+    final def project = TestUtil.createRootProject()
+
+    @Override
+    Class<? extends Plugin> getPluginClass() {
+        return CNativeBinariesPlugin
+    }
+
+    @Override
+    Class<? extends LanguageSourceSet> getSourceSetClass() {
+        return CSourceSet
+    }
+
+    @Override
+    Class<? extends Plugin> getCompileTaskClass() {
+        return CCompile
+    }
+
+    @Override
+    String getPluginName() {
+        return "c"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/tasks/CCompileTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/tasks/CCompileTest.groovy
new file mode 100644
index 0000000..5524eb0
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/c/tasks/CCompileTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.c.tasks
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.tasks.WorkResult
+import org.gradle.nativebinaries.platform.internal.PlatformInternal
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.nativebinaries.language.c.internal.CCompileSpec
+import org.gradle.nativebinaries.language.cpp.internal.CppCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class CCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    CCompile cCompile = TestUtil.createTask(CCompile)
+    def toolChain = Mock(ToolChainInternal)
+    def platform = Mock(PlatformInternal)
+    def platformToolChain = Mock(PlatformToolChain)
+    Compiler<CppCompileSpec> cCompiler = Mock(Compiler)
+
+    def "executes using the C Compiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        cCompile.toolChain = toolChain
+        cCompile.targetPlatform = platform
+        cCompile.compilerArgs = ["arg"]
+        cCompile.macros = [def: "value"]
+        cCompile.objectFileDir = testDir.file("outputFile")
+        cCompile.source sourceFile
+        cCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "c"
+        _ * platform.compatibilityString >> "p"
+        1 * toolChain.target(platform) >> platformToolChain
+        1 * platformToolChain.createCCompiler() >> cCompiler
+        1 * cCompiler.execute({ CCompileSpec spec ->
+            assert spec.sourceFiles*.name== ["sourceFile"]
+            assert spec.args == ['arg']
+            assert spec.allArgs == ['arg']
+            assert spec.macros == [def: 'value']
+            assert spec.objectFileDir.name == "outputFile"
+            true
+        }) >> result
+        1 * result.didWork >> true
+        0 * _._
+
+        and:
+        cCompile.didWork
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/ReadelfBinaryInfoTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/ReadelfBinaryInfoTest.groovy
new file mode 100644
index 0000000..867471c
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/ReadelfBinaryInfoTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo
+
+import spock.lang.Specification
+
+class ReadelfBinaryInfoTest extends Specification {
+    def "reads soname value"() {
+        when:
+        def input = """
+Dynamic section at offset 0xdf8 contains 24 entries:
+  Tag        Type                         Name/Value
+ 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
+ 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
+ 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
+ 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
+ 0x000000000000000e (SONAME)             Library soname: [heythere]
+ 0x000000000000000c (INIT)               0x668
+"""
+        def inputLines = input.readLines()
+
+        then:
+        ReadelfBinaryInfo.readSoName(inputLines) == 'heythere'
+    }
+
+    def "returns null for no soname value"() {
+        when:
+        def input = """
+Dynamic section at offset 0xdf8 contains 24 entries:
+  Tag        Type                         Name/Value
+ 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
+ 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
+ 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
+ 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
+ 0x000000000000000c (INIT)               0x668
+"""
+        def inputLines = input.readLines()
+
+        then:
+        ReadelfBinaryInfo.readSoName(inputLines) == null
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/internal/DefaultCppSourceSetTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/internal/DefaultCppSourceSetTest.groovy
new file mode 100644
index 0000000..96f12fe
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/internal/DefaultCppSourceSetTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.internal
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.cpp.internal.DefaultCppSourceSet
+import org.gradle.nativebinaries.Library
+import org.gradle.nativebinaries.LibraryBinary
+import org.gradle.nativebinaries.NativeDependencySet
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class DefaultCppSourceSetTest extends Specification {
+    def parent = Stub(FunctionalSourceSet) {
+        getName() >> "main"
+    }
+    def sourceSet = new DefaultCppSourceSet("cpp", parent, TestUtil.createRootProject())
+
+    def "has useful string representation"() {
+        expect:
+        sourceSet.toString() == "C++ source 'main:cpp'"
+    }
+
+    def "can add a library as a dependency of the source set"() {
+        def library = Mock(Library)
+
+        when:
+        sourceSet.lib(library)
+
+        then:
+        sourceSet.libs.contains(library)
+    }
+
+    def "can add a library binary as a dependency of the binary"() {
+        def library = Mock(LibraryBinary)
+
+        when:
+        sourceSet.lib(library)
+
+        then:
+        sourceSet.libs.contains(library)
+    }
+
+    def "can add a native dependency as a dependency of the binary"() {
+        def dependency = Stub(NativeDependencySet)
+
+        when:
+        sourceSet.lib(dependency)
+
+        then:
+        sourceSet.libs.contains(dependency)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppNativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppNativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..4f010a7
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/plugins/CppNativeBinariesPluginTest.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.plugins
+
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.language.cpp.CppSourceSet
+import org.gradle.nativebinaries.ExecutableBinary
+import org.gradle.nativebinaries.NativeBinary
+import org.gradle.nativebinaries.SharedLibraryBinary
+import org.gradle.nativebinaries.StaticLibraryBinary
+import org.gradle.nativebinaries.language.cpp.tasks.CppCompile
+import org.gradle.util.GFileUtils
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class CppNativeBinariesPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def "creates cpp source set with conventional locations for components"() {
+        when:
+        dsl {
+            apply plugin: CppNativeBinariesPlugin
+            executables {
+                exe {}
+            }
+            libraries {
+                lib {}
+            }
+        }
+
+        then:
+        def sourceSets = project.sources
+        sourceSets.size() == 2
+        sourceSets*.name == ["exe", "lib"]
+
+        and:
+        sourceSets.exe instanceof FunctionalSourceSet
+        sourceSets.exe.cpp instanceof CppSourceSet
+        sourceSets.exe.cpp.source.srcDirs == [project.file("src/exe/cpp")] as Set
+        sourceSets.exe.cpp.exportedHeaders.srcDirs == [project.file("src/exe/headers")] as Set
+        project.executables.exe.source == [sourceSets.exe.cpp] as Set
+
+        and:
+        sourceSets.lib instanceof FunctionalSourceSet
+        sourceSets.lib.cpp instanceof CppSourceSet
+        sourceSets.lib.cpp.source.srcDirs == [project.file("src/lib/cpp")] as Set
+        sourceSets.lib.cpp.exportedHeaders.srcDirs == [project.file("src/lib/headers")] as Set
+        project.libraries.lib.source == [sourceSets.lib.cpp] as Set
+    }
+
+    def "can configure source set locations"() {
+        given:
+        dsl {
+            apply plugin: CppNativeBinariesPlugin
+            sources {
+                exe {
+                    cpp {
+                        source {
+                            srcDirs "d1", "d2"
+                        }
+                        exportedHeaders {
+                            srcDirs "h1", "h2"
+                        }
+                    }
+                }
+                lib {
+                    cpp {
+                        source {
+                            srcDirs "d3"
+                        }
+                        exportedHeaders {
+                            srcDirs "h3"
+                        }
+                    }
+                }
+            }
+        }
+
+        expect:
+        def sourceSets = project.sources
+        with (sourceSets.exe.cpp) {
+            source.srcDirs*.name == ["d1", "d2"]
+            exportedHeaders.srcDirs*.name == ["h1", "h2"]
+        }
+
+        with (sourceSets.lib.cpp) {
+            source.srcDirs*.name == ["d3"]
+            exportedHeaders.srcDirs*.name == ["h3"]
+        }
+    }
+
+    def "creates compile tasks for each non-empty executable source set"() {
+        when:
+        touch("src/test/cpp/file.cpp")
+        touch("src/test/anotherCpp/file.cpp")
+        dsl {
+            apply plugin: CppNativeBinariesPlugin
+            sources {
+                test {
+                    anotherCpp(CppSourceSet) {}
+                    emptyOne(CppSourceSet) {}
+                }
+            }
+            executables {
+                test {
+                    binaries.all { NativeBinary binary ->
+                        binary.cppCompiler.define "NDEBUG"
+                        binary.cppCompiler.define "LEVEL", "1"
+                        binary.cppCompiler.args "ARG1", "ARG2"
+                    }
+                }
+            }
+        }
+
+        then:
+        ExecutableBinary binary = project.binaries.testExecutable
+        binary.tasks.withType(CppCompile)*.name == ["compileTestExecutableTestAnotherCpp", "compileTestExecutableTestCpp"]
+
+        and:
+        binary.tasks.withType(CppCompile).each { compile ->
+            compile.toolChain == binary.toolChain
+            compile.macros == [NDEBUG:null, LEVEL:"1"]
+            compile.compilerArgs == ["ARG1", "ARG2"]
+        }
+
+        and:
+        def linkTask = binary.tasks.link
+        linkTask TaskDependencyMatchers.dependsOn("compileTestExecutableTestAnotherCpp", "compileTestExecutableTestCpp")
+    }
+
+    def "creates compile task for each library source set"() {
+        when:
+        touch("src/test/cpp/file.cpp")
+        touch("src/test/anotherCpp/file.cpp")
+        dsl {
+            apply plugin: CppNativeBinariesPlugin
+            sources {
+                test {
+                    anotherCpp(CppSourceSet) {}
+                }
+            }
+            libraries {
+                test {
+                    binaries.all {
+                        cppCompiler.define "NDEBUG"
+                        cppCompiler.define "LEVEL", "1"
+                        cppCompiler.args "ARG1", "ARG2"
+                    }
+                    binaries.withType(SharedLibraryBinary) {
+                        cppCompiler.args "SHARED1", "SHARED2"
+                    }
+                    binaries.withType(StaticLibraryBinary) {
+                        cppCompiler.args "STATIC1", "STATIC2"
+                    }
+                }
+            }
+        }
+
+        then:
+        SharedLibraryBinary sharedLib = project.binaries.testSharedLibrary
+        sharedLib.tasks.withType(CppCompile)*.name == ["compileTestSharedLibraryTestAnotherCpp", "compileTestSharedLibraryTestCpp"]
+        sharedLib.tasks.withType(CppCompile).each { compile ->
+            compile.toolChain == sharedLib.toolChain
+            compile.macros == [NDEBUG:null, LEVEL:"1"]
+            compile.compilerArgs == ["ARG1", "ARG2", "SHARED1", "SHARED2"]
+        }
+        def sharedLinkTask = sharedLib.tasks.link
+        sharedLinkTask TaskDependencyMatchers.dependsOn("compileTestSharedLibraryTestAnotherCpp", "compileTestSharedLibraryTestCpp")
+
+        and:
+        StaticLibraryBinary staticLib = project.binaries.testStaticLibrary
+        staticLib.tasks.withType(CppCompile)*.name == ["compileTestStaticLibraryTestAnotherCpp", "compileTestStaticLibraryTestCpp"]
+        staticLib.tasks.withType(CppCompile).each { compile ->
+            compile.toolChain == sharedLib.toolChain
+            compile.macros == [NDEBUG:null, LEVEL:"1"]
+            compile.compilerArgs == ["ARG1", "ARG2", "STATIC1", "STATIC2"]
+        }
+        def staticLibTask = staticLib.tasks.createStaticLib
+        staticLibTask TaskDependencyMatchers.dependsOn("compileTestStaticLibraryTestAnotherCpp", "compileTestStaticLibraryTestCpp")
+    }
+
+    def touch(String filePath) {
+        GFileUtils.touch(project.file(filePath))
+    }
+
+    def dsl(Closure closure) {
+        closure.delegate = project
+        closure()
+        project.evaluate()
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/tasks/CppCompileTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/tasks/CppCompileTest.groovy
new file mode 100644
index 0000000..ae50f01
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/cpp/tasks/CppCompileTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.tasks
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.tasks.WorkResult
+import org.gradle.nativebinaries.platform.internal.PlatformInternal
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.nativebinaries.language.cpp.internal.CppCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class CppCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    CppCompile cppCompile = TestUtil.createTask(CppCompile)
+    def toolChain = Mock(ToolChainInternal)
+    def platform = Mock(PlatformInternal)
+    def platformToolChain = Mock(PlatformToolChain)
+    Compiler<CppCompileSpec> cppCompiler = Mock(Compiler)
+
+    def "executes using the CppCompiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        cppCompile.toolChain = toolChain
+        cppCompile.targetPlatform = platform
+        cppCompile.compilerArgs = ["arg"]
+        cppCompile.macros = [def: "value"]
+        cppCompile.objectFileDir = testDir.file("outputFile")
+        cppCompile.source sourceFile
+        cppCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "cpp"
+        _ * platform.compatibilityString >> "p"
+        1 * toolChain.target(platform) >> platformToolChain
+        1 * platformToolChain.createCppCompiler() >> cppCompiler
+        1 * cppCompiler.execute({ CppCompileSpec spec ->
+            assert spec.sourceFiles*.name == ["sourceFile"]
+            assert spec.args == ['arg']
+            assert spec.allArgs == ['arg']
+            assert spec.macros == [def: 'value']
+            assert spec.objectFileDir.name == "outputFile"
+            true
+        }) >> result
+        1 * result.didWork >> true
+        0 * _._
+
+        and:
+        cppCompile.didWork
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCNativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCNativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..e3a5264
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/objectivec/plugins/ObjectiveCNativeBinariesPluginTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivec.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.objectivec.ObjectiveCSourceSet
+import org.gradle.nativebinaries.language.AbstractNativeBinariesPluginTest
+import org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile
+
+class ObjectiveCNativeBinariesPluginTest extends AbstractNativeBinariesPluginTest{
+    @Override
+    Class<? extends Plugin> getPluginClass() {
+        return ObjectiveCNativeBinariesPlugin
+    }
+
+    @Override
+    Class<? extends LanguageSourceSet> getSourceSetClass() {
+        return ObjectiveCSourceSet
+    }
+
+    @Override
+    Class<? extends Plugin> getCompileTaskClass() {
+        return ObjectiveCCompile
+    }
+
+    @Override
+    String getPluginName() {
+        return "objc"
+    }
+
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppNativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppNativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..148a65e
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/language/objectivecpp/plugins/ObjectiveCppNativeBinariesPluginTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.objectivecpp.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.language.objectivecpp.ObjectiveCppSourceSet
+import org.gradle.nativebinaries.language.AbstractNativeBinariesPluginTest
+import org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile
+
+class ObjectiveCppNativeBinariesPluginTest extends AbstractNativeBinariesPluginTest{
+    @Override
+    Class<? extends Plugin> getPluginClass() {
+        return ObjectiveCppNativeBinariesPlugin
+    }
+
+    @Override
+    Class<? extends LanguageSourceSet> getSourceSetClass() {
+        return ObjectiveCppSourceSet
+    }
+
+    @Override
+    Class<? extends Plugin> getCompileTaskClass() {
+        return ObjectiveCppCompile
+    }
+
+    @Override
+    String getPluginName() {
+        return "objcpp"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureNotationParserTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureNotationParserTest.groovy
new file mode 100644
index 0000000..3206e0d
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/ArchitectureNotationParserTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+class ArchitectureNotationParserTest extends Specification {
+    def parser = ArchitectureNotationParser.parser()
+
+    def "fails when parsing unknown architecture"() {
+        when:
+        parseNotation("bad")
+
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message.contains("One of the following values: 'x86', 'i386', 'ia-32', 'x86_64', 'amd64', 'x64', 'x86-64',")
+    }
+
+    def "parses intel architectures"() {
+        when:
+        def arch = parseNotation(archString)
+
+        then:
+        arch.instructionSet == ArchitectureInternal.InstructionSet.X86
+        arch.registerSize == registerSize
+        arch.name == archString
+
+        where:
+        archString | registerSize
+        "x86"      | 32
+        "X86"      | 32
+        "i386"     | 32
+        "ia-32"    | 32
+        "IA-32"    | 32
+        "x86_64"   | 64
+        "x86-64"   | 64
+        "amd64"    | 64
+        "AMD64"    | 64
+        "x64"      | 64
+    }
+
+    def "parses itanium architecture"() {
+        when:
+        def arch = parseNotation("ia-64")
+        then:
+        arch.instructionSet == ArchitectureInternal.InstructionSet.ITANIUM
+        arch.registerSize == 64
+        arch.name == "ia-64"
+    }
+
+    def "parses ppc architecture"() {
+        when:
+        def arch = parseNotation(archString)
+
+        then:
+        arch.instructionSet == ArchitectureInternal.InstructionSet.PPC
+        arch.registerSize == registerSize
+        arch.name == archString
+
+        where:
+        archString | registerSize
+        "ppc"      | 32
+        "PPC"      | 32
+        "ppc64"    | 64
+        "PPC64"    | 64
+    }
+
+    def "parses sparc architectures"() {
+        when:
+        def arch = parseNotation(archString)
+
+        then:
+        arch.instructionSet == ArchitectureInternal.InstructionSet.SPARC
+        arch.registerSize == registerSize
+        arch.name == archString
+
+        where:
+        archString   | registerSize
+        "sparc"      | 32
+        "SPARC"      | 32
+        "sparc32"    | 32
+        "sparc-v7"   | 32
+        "sparc-v8"   | 32
+        "sparc64"    | 64
+        "ultrasparc" | 64
+        "sparc-v9"   | 64
+    }
+
+    def "parses arm architecture"() {
+        when:
+        def arch = parseNotation("arm")
+        then:
+        arch.instructionSet == ArchitectureInternal.InstructionSet.ARM
+        arch.registerSize == 32
+        arch.name == "arm"
+    }
+
+    private ArchitectureInternal parseNotation(String archString) {
+        parser.parseNotation(archString) as ArchitectureInternal
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultArchitectureTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultArchitectureTest.groovy
new file mode 100644
index 0000000..37d75ca
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultArchitectureTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.platform.internal
+
+import spock.lang.Specification
+
+class DefaultArchitectureTest extends Specification {
+    def "has useful string representation"() {
+        def architecture = new DefaultArchitecture("arch", ArchitectureInternal.InstructionSet.ARM, 32)
+
+        expect:
+        architecture.toString() == "architecture 'arch'"
+        architecture.displayName == "architecture 'arch'"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultOperatingSystemTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultOperatingSystemTest.groovy
new file mode 100644
index 0000000..64b0ede
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultOperatingSystemTest.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.platform.internal
+
+import org.gradle.internal.os.OperatingSystem
+import spock.lang.Specification
+
+class DefaultOperatingSystemTest extends Specification {
+    def "has useful string representation"() {
+        def os = new DefaultOperatingSystem("windows", Stub(OperatingSystem))
+
+        expect:
+        os.toString() == "operating system 'windows'"
+        os.displayName == "operating system 'windows'"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatformTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatformTest.groovy
new file mode 100644
index 0000000..347dd91
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/DefaultPlatformTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal
+import org.gradle.internal.typeconversion.NotationParser
+import org.gradle.nativebinaries.platform.OperatingSystem
+import spock.lang.Specification
+
+class DefaultPlatformTest extends Specification {
+    def archParser = Mock(NotationParser)
+    def osParser = Mock(NotationParser)
+    def platform = new DefaultPlatform("platform", archParser, osParser)
+
+    def "has useful string representation"() {
+        expect:
+        platform.displayName == "platform 'platform'"
+        platform.toString() == "platform 'platform'"
+    }
+
+    def "has default architecture and operating system"() {
+        expect:
+        platform.architecture == ArchitectureInternal.TOOL_CHAIN_DEFAULT
+        platform.operatingSystem == DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+    }
+
+    def "can configure architecture"() {
+        def arch = Mock(ArchitectureInternal)
+        when:
+        platform.architecture "ppc64"
+
+        then:
+        1 * archParser.parseNotation("ppc64") >> arch
+
+        and:
+        platform.architecture == arch
+    }
+
+    def "can configure operating system"() {
+        def os = Mock(OperatingSystem)
+        when:
+        platform.operatingSystem "the-os"
+
+        then:
+        1 * osParser.parseNotation("the-os") >> os
+
+        and:
+        platform.operatingSystem == os
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/OperatingSystemNotationParserTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/OperatingSystemNotationParserTest.groovy
new file mode 100644
index 0000000..ab69961
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/platform/internal/OperatingSystemNotationParserTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.platform.internal
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+class OperatingSystemNotationParserTest extends Specification {
+    def parser = OperatingSystemNotationParser.parser()
+
+    def "fails when parsing unknown operating system"() {
+        when:
+        parser.parseNotation("bad")
+
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message.contains("One of the following values: 'windows', 'osx', 'mac os x', 'linux', 'solaris', 'sunos'")
+    }
+
+    def "parses windows"() {
+        when:
+        def os = parser.parseNotation("windows")
+        then:
+        os.windows
+        !os.macOsX
+        !os.linux
+        !os.solaris
+        !os.freeBSD
+    }
+
+    def "parses osx"() {
+        when:
+        def os = parser.parseNotation(osString)
+
+        then:
+        !os.windows
+        os.macOsX
+        !os.linux
+        !os.solaris
+        !os.freeBSD
+
+        where:
+        osString << ["osx", "mac os x"]
+    }
+
+    def "parses linux"() {
+        when:
+        def os = parser.parseNotation("linux")
+
+        then:
+        !os.windows
+        !os.macOsX
+        os.linux
+        !os.solaris
+        !os.freeBSD
+    }
+
+    def "parses solaris"() {
+        when:
+        def os = parser.parseNotation(osString)
+
+        then:
+        !os.windows
+        !os.macOsX
+        !os.linux
+        os.solaris
+        !os.freeBSD
+
+        where:
+        osString << ["solaris", "sunos"]
+    }
+
+    def "parses FreeBSD"() {
+        when:
+        def os = parser.parseNotation(osString)
+
+        then:
+        !os.windows
+        !os.macOsX
+        !os.linux
+        !os.solaris
+        os.freeBSD
+
+        where:
+        osString << ["FreeBSD", "freebsd"]
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/plugins/NativeBinariesModelPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/plugins/NativeBinariesModelPluginTest.groovy
new file mode 100644
index 0000000..ca33573
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/plugins/NativeBinariesModelPluginTest.groovy
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.plugins
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Task
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.nativebinaries.*
+import org.gradle.nativebinaries.internal.DefaultFlavor
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.platform.PlatformContainer
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal
+import org.gradle.nativebinaries.toolchain.ToolChainRegistry
+import org.gradle.nativebinaries.toolchain.internal.PlatformToolChain
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class NativeBinariesModelPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(NativeBinariesModelPlugin)
+    }
+
+    def "adds model extensions"() {
+        expect:
+        project.executables instanceof NamedDomainObjectContainer
+        project.libraries instanceof NamedDomainObjectContainer
+        project.modelRegistry.get("toolChains", ToolChainRegistry) != null
+        project.modelRegistry.get("platforms", PlatformContainer) != null
+        project.modelRegistry.get("buildTypes", BuildTypeContainer) != null
+        project.modelRegistry.get("flavors", FlavorContainer) != null
+    }
+
+    def "adds default target platform, build type and flavor"() {
+        expect:
+        with (one(project.modelRegistry.get("platforms", PlatformContainer))) {
+            name == 'current'
+            architecture == ArchitectureInternal.TOOL_CHAIN_DEFAULT
+        }
+        one(project.modelRegistry.get("buildTypes", BuildTypeContainer)).name == 'debug'
+        one(project.modelRegistry.get("flavors", FlavorContainer)).name == 'default'
+    }
+
+    def "does not provide a default tool chain"() {
+        expect:
+        project.modelRegistry.get("toolChains", ToolChainRegistry).isEmpty()
+    }
+
+    def "adds default flavor to every binary"() {
+        when:
+        project.executables.create "exe"
+        project.libraries.create "lib"
+        project.evaluate()
+
+        then:
+        one(project.binaries.withType(ExecutableBinary)).flavor.name == DefaultFlavor.DEFAULT
+        one(project.binaries.withType(SharedLibraryBinary)).flavor.name == DefaultFlavor.DEFAULT
+    }
+
+    def "does not add defaults when domain is explicitly configured"() {
+        when:
+        project.model {
+            toolChains {
+                add named(ToolChainInternal, "tc")
+            }
+            platforms {
+                add named(Platform, "platform")
+            }
+            buildTypes {
+                add named(BuildType, "bt")
+            }
+            flavors {
+                add named(Flavor, "flavor1")
+            }
+        }
+
+        and:
+        project.evaluate()
+
+        then:
+        one(project.modelRegistry.get("toolChains", ToolChainRegistry)).name == 'tc'
+        one(project.modelRegistry.get("platforms", PlatformContainer)).name == 'platform'
+        one(project.modelRegistry.get("buildTypes", BuildTypeContainer)).name == 'bt'
+        one(project.modelRegistry.get("flavors", FlavorContainer)).name == 'flavor1'
+    }
+
+    def "creates binaries for executable"() {
+        when:
+        project.plugins.apply(NativeBinariesModelPlugin)
+        project.model {
+            toolChains {
+                add toolChain("tc")
+            }
+            platforms {
+                add named(Platform, "platform")
+            }
+            buildTypes {
+                add named(BuildType, "bt")
+            }
+            flavors {
+                add named(Flavor, "flavor1")
+            }
+        }
+        def executable = project.executables.create "test"
+        project.evaluate()
+
+        then:
+        ExecutableBinary executableBinary = one(project.binaries) as ExecutableBinary
+        with (executableBinary) {
+            name == 'testExecutable'
+            component == executable
+            toolChain.name == "tc"
+            targetPlatform.name == "platform"
+            buildType.name == "bt"
+            flavor.name == "flavor1"
+        }
+
+        and:
+        executable.binaries == [executableBinary] as Set
+    }
+
+    def "creates binaries for library"() {
+        when:
+        project.plugins.apply(NativeBinariesModelPlugin)
+        project.model {
+            toolChains {
+                add toolChain("tc")
+            }
+            platforms {
+                add named(Platform, "platform")
+            }
+            buildTypes {
+                add named(BuildType, "bt")
+            }
+            flavors {
+                add named(Flavor, "flavor1")
+            }
+        }
+        def library = project.libraries.create "test"
+        project.evaluate()
+
+        then:
+        SharedLibraryBinary sharedLibraryBinary = project.binaries.testSharedLibrary as SharedLibraryBinary
+        with (sharedLibraryBinary) {
+            name == 'testSharedLibrary'
+            component == library
+
+            toolChain.name == "tc"
+            targetPlatform.name == "platform"
+            buildType.name == "bt"
+            flavor.name == "flavor1"
+        }
+
+        and:
+        StaticLibraryBinary staticLibraryBinary = project.binaries.testStaticLibrary as StaticLibraryBinary
+        with (staticLibraryBinary) {
+            name == 'testStaticLibrary'
+            component == library
+
+            toolChain.name == "tc"
+            targetPlatform.name == "platform"
+            buildType.name == "bt"
+            flavor.name == "flavor1"
+        }
+
+        and:
+        library.binaries.contains(sharedLibraryBinary)
+        library.binaries.contains(staticLibraryBinary)
+    }
+
+    def "creates lifecycle task for each binary"() {
+        when:
+        project.plugins.apply(NativeBinariesModelPlugin)
+        def executable = project.executables.create "exe"
+        def library = project.libraries.create "lib"
+        project.evaluate()
+
+        then:
+        ExecutableBinary executableBinary = project.binaries.exeExecutable as ExecutableBinary
+        with (oneTask(executableBinary.buildDependencies)) {
+            name == executableBinary.name
+            group == BasePlugin.BUILD_GROUP
+        }
+        SharedLibraryBinary sharedLibraryBinary = project.binaries.libSharedLibrary as SharedLibraryBinary
+        with (oneTask(sharedLibraryBinary.buildDependencies)) {
+            name == sharedLibraryBinary.name
+            group == BasePlugin.BUILD_GROUP
+        }
+        StaticLibraryBinary staticLibraryBinary = project.binaries.libStaticLibrary as StaticLibraryBinary
+        with (oneTask(staticLibraryBinary.buildDependencies)) {
+            name == staticLibraryBinary.name
+            group == BasePlugin.BUILD_GROUP
+        }
+    }
+
+    static <T> T one(Collection<T> collection) {
+        assert collection.size() == 1
+        return collection.iterator().next()
+    }
+
+    def named(Class type, def name) {
+        Stub(type) {
+            getName() >> name
+        }
+    }
+
+    def toolChain(def name) {
+        Stub(ToolChainInternal) {
+            getName() >> name
+            target(_) >> Stub(PlatformToolChain) {
+                isAvailable() >> true
+            }
+        }
+    }
+
+    Task oneTask(TaskDependency dependencies) {
+        def tasks = dependencies.getDependencies(Stub(Task))
+        assert tasks.size() == 1
+        return tasks.asList()[0]
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPluginTest.groovy
new file mode 100644
index 0000000..5a509b0
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/plugins/NativeBinariesPluginTest.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.plugins
+
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.language.base.LanguageSourceSet
+import org.gradle.nativebinaries.tasks.CreateStaticLibrary
+import org.gradle.nativebinaries.tasks.InstallExecutable
+import org.gradle.nativebinaries.tasks.LinkExecutable
+import org.gradle.nativebinaries.tasks.LinkSharedLibrary
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class NativeBinariesPluginTest extends Specification {
+    final def project = TestUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(NativeBinariesPlugin)
+    }
+
+    def "creates link and install task for executable"() {
+        when:
+        project.executables.create "test"
+        project.evaluate()
+
+        then:
+        def testExecutable = project.binaries.testExecutable
+        with (project.tasks.linkTestExecutable) {
+            it instanceof LinkExecutable
+            it == testExecutable.tasks.link
+            it.toolChain == testExecutable.toolChain
+            it.targetPlatform == testExecutable.targetPlatform
+            it.linkerArgs == testExecutable.linker.args
+        }
+        testExecutable.tasks.createStaticLib == null
+
+        and:
+        def lifecycleTask = project.tasks.testExecutable
+        lifecycleTask TaskDependencyMatchers.dependsOn("linkTestExecutable")
+
+        and:
+        project.tasks.installTestExecutable instanceof InstallExecutable
+    }
+
+    def "creates link task and static archive task for library"() {
+        when:
+        project.libraries.create "test"
+        project.evaluate()
+
+        then:
+        def sharedLibraryBinary = project.binaries.testSharedLibrary
+        with (project.tasks.linkTestSharedLibrary) {
+            it instanceof LinkSharedLibrary
+            it == sharedLibraryBinary.tasks.link
+            it.toolChain == sharedLibraryBinary.toolChain
+            it.targetPlatform == sharedLibraryBinary.targetPlatform
+            it.linkerArgs == sharedLibraryBinary.linker.args
+        }
+        sharedLibraryBinary.tasks.createStaticLib == null
+
+        and:
+        def sharedLibTask = project.tasks.testSharedLibrary
+        sharedLibTask TaskDependencyMatchers.dependsOn("linkTestSharedLibrary")
+
+        and:
+        def staticLibraryBinary = project.binaries.testStaticLibrary
+        with (project.tasks.createTestStaticLibrary) {
+            it instanceof  CreateStaticLibrary
+            it == staticLibraryBinary.tasks.createStaticLib
+            it.toolChain == staticLibraryBinary.toolChain
+            it.targetPlatform == staticLibraryBinary.targetPlatform
+            it.staticLibArgs == staticLibraryBinary.staticLibArchiver.args
+        }
+        staticLibraryBinary.tasks.link == null
+
+        and:
+        def staticLibTask = project.tasks.testStaticLibrary
+        staticLibTask TaskDependencyMatchers.dependsOn("createTestStaticLibrary")
+    }
+
+    def "attaches existing functional source set with same name to component"() {
+        def languageSourceSet = Stub(LanguageSourceSet) {
+            getName() >> "languageSourceSet"
+        }
+
+        when:
+        project.sources.create "testExe"
+        project.sources.testExe.add languageSourceSet
+
+        project.executables.create "testExe"
+
+        then:
+        project.executables.testExe.source == [languageSourceSet] as Set
+    }
+
+    def "creates and attaches functional source set with same name to component"() {
+        def languageSourceSet = Stub(LanguageSourceSet) {
+            getName() >> "languageSourceSet"
+        }
+
+        when:
+        // Ensure that any created functional source set has a language source set
+        project.sources.all { functionalSourceSet ->
+            functionalSourceSet.add languageSourceSet
+        }
+
+        project.executables.create "testExe"
+
+        then:
+        project.executables.testExe.source == [languageSourceSet] as Set
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/DefaultToolChainRegistryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/DefaultToolChainRegistryTest.groovy
new file mode 100644
index 0000000..9ffa628
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/DefaultToolChainRegistryTest.groovy
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal
+import org.gradle.api.GradleException
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.nativebinaries.platform.internal.DefaultPlatform
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class DefaultToolChainRegistryTest extends Specification {
+    def project = TestUtil.createRootProject()
+    def instantiator = project.services.get(Instantiator)
+    def registry = instantiator.newInstance(DefaultToolChainRegistry, instantiator)
+    def NamedDomainObjectFactory<TestToolChain> factory = Mock(NamedDomainObjectFactory)
+    def platform = new DefaultPlatform("platform")
+
+    def "setup"() {
+        project.extensions.add("toolChains", registry)
+        registry.registerFactory(TestToolChain, factory)
+    }
+
+    def "adds default tool chains"() {
+        def defaultToolChain1 = unavailableToolChain("test1")
+        def defaultToolChain2 = availableToolChain("test2")
+        def defaultToolChain3 = availableToolChain("test3")
+
+        when:
+        registry.registerDefaultToolChain("test1", TestToolChain)
+        registry.registerDefaultToolChain("test2", TestToolChain)
+        registry.registerDefaultToolChain("test3", TestToolChain)
+        registry.addDefaultToolChains()
+
+        then:
+        registry.asList() == [defaultToolChain1, defaultToolChain2, defaultToolChain3]
+    }
+
+    def "can add default tool chain when some configured"() {
+        def defaultToolChain = availableToolChain("default")
+        def configuredToolChain = unavailableToolChain("configured")
+
+        when:
+        registry.registerDefaultToolChain("default", TestToolChain)
+
+        and:
+        registry.create("configured", TestToolChain)
+
+        and:
+        registry.addDefaultToolChains()
+
+        then:
+        registry.asList() == [configuredToolChain, defaultToolChain]
+    }
+
+    def "provides unavailable tool chain when no tool chain available for requested  platform"() {
+        unavailableToolChain("test", "nope")
+        unavailableToolChain("test2", "not me")
+        unavailableToolChain("test3", "not me either")
+
+        given:
+        registry.registerDefaultToolChain("test", TestToolChain)
+        registry.registerDefaultToolChain("test2", TestToolChain)
+        registry.registerDefaultToolChain("test3", TestToolChain)
+        registry.addDefaultToolChains()
+
+        and:
+        def tc = registry.getForPlatform(platform)
+        def result = tc.target(platform)
+
+        when:
+        result.createCCompiler()
+
+        then:
+        GradleException e = thrown()
+        e.message == toPlatformLineSeparators("""No tool chain is available to build for platform 'platform':
+  - Tool chain 'test': nope
+  - Tool chain 'test2': not me
+  - Tool chain 'test3': not me either""")
+    }
+
+    def "can use DSL to configure toolchains"() {
+        def defaultToolChain = availableToolChain("test")
+        def anotherToolChain = unavailableToolChain("another")
+
+        when:
+        registry.registerDefaultToolChain("test", TestToolChain)
+        registry.addDefaultToolChains()
+
+        and:
+        project.toolChains {
+            test {
+                baseDir = "foo"
+            }
+            another(TestToolChain) {
+                baseDir = "bar"
+            }
+        }
+
+        then:
+        1 * defaultToolChain.setBaseDir("foo")
+        1 * anotherToolChain.setBaseDir("bar")
+
+        and:
+        registry.asList() == [anotherToolChain, defaultToolChain]
+    }
+
+    def "tool chains are returned in name order"() {
+        def test = unavailableToolChain("test")
+        def tc2 = availableToolChain("test2")
+        def tcFirst = availableToolChain("first")
+
+        when:
+        registry.create("test", TestToolChain)
+        registry.create("test2", TestToolChain)
+        registry.create("first", TestToolChain)
+
+        then:
+        registry.toList() == [tcFirst, test, tc2]
+    }
+
+    def "uses first available tool chain that can target platform"() {
+        def defaultToolChain1 = unavailableToolChain("test1")
+        def defaultToolChain2 = availableToolChain("test2")
+        def defaultToolChain3 = availableToolChain("test3")
+
+        given:
+        registry.add(defaultToolChain1)
+        registry.add(defaultToolChain2)
+        registry.add(defaultToolChain3)
+
+        expect:
+        registry.getForPlatform(platform) == defaultToolChain2
+    }
+
+    def availableToolChain(String name) {
+        PlatformToolChain platformToolChain = Stub(PlatformToolChain) {
+            _ * isAvailable() >> true
+        }
+        TestToolChain testToolChain = Mock(TestToolChain) {
+            _ * getName() >> name
+            _ * target(platform) >> platformToolChain
+        }
+        factory.create(name) >> testToolChain
+        return testToolChain
+    }
+
+    def unavailableToolChain(String name, String message = "Not available") {
+        PlatformToolChain platformToolChain = Stub(PlatformToolChain) {
+            _ * isAvailable() >> false
+            _ * explain(_) >> { it[0].node(message) }
+        }
+        TestToolChain testToolChain = Mock(TestToolChain) {
+            _ * getName() >> name
+            _ * getDisplayName() >> "Tool chain '$name'"
+            _ * target(platform) >> platformToolChain
+        }
+        factory.create(name) >> testToolChain
+        return testToolChain
+    }
+
+    interface TestToolChain extends ToolChainInternal
+    {
+        void setBaseDir(String value);
+    }
+
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/OutputCleaningCompilerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/OutputCleaningCompilerTest.groovy
new file mode 100644
index 0000000..c695676
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/OutputCleaningCompilerTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.internal.hash.HashUtil
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.compile.Compiler;
+
+class OutputCleaningCompilerTest extends Specification {
+
+    @Rule
+    final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+
+
+    NativeCompileSpec spec = Mock(NativeCompileSpec);
+    Compiler delegateCompiler = Mock(Compiler)
+    OutputCleaningCompiler cleanCompiler = new OutputCleaningCompiler<NativeCompileSpec>(delegateCompiler, ".o");
+
+    TestFile outputDir = tmpDirProvider.createDir("objectFiles")
+
+    WorkResult workResult = Mock(WorkResult)
+    List<TestFile> sourceFiles
+
+    def setup() {
+        _ * spec.objectFileDir >> outputDir
+        _ * delegateCompiler.execute(_) >> { NativeCompileSpec spec ->
+            List<File> sourceFiles = spec.getSourceFiles()
+            sourceFiles.each{ inputFile ->
+                createObjDummy(inputFile)
+            }
+            _ * workResult.getDidWork() >> !sourceFiles.isEmpty();
+            return workResult
+        }
+    }
+
+    def "deletes output files and according hash directory"() {
+        setup:
+        sourceFiles = Arrays.asList(tmpDirProvider.file("src/main/c/main.c"), tmpDirProvider.file("src/main/c/foo/main2.c"))
+        when:
+        compile(sourceFiles[0], sourceFiles[1])
+        then:
+        outputDir.listFiles().size() == 2
+
+        when:
+        compile(sourceFiles[0])
+        then:
+        objectFile(sourceFiles[0])
+        !objectFile(sourceFiles[1])
+
+
+        when:
+        compile(sourceFiles[1])
+        then:
+        !objectFile(sourceFiles[0])
+        objectFile(sourceFiles[1])
+    }
+
+    def "removes stale output when source file is moved"() {
+        setup:
+        def orgFile = tmpDirProvider.file("src/main/c/org/main.c")
+        def movedFile = tmpDirProvider.file("src/main/c/moved/main.c")
+        sourceFiles = [orgFile, movedFile]
+
+        when:
+        compile(orgFile)
+
+        then:
+        objectFile(orgFile)
+        !objectFile(movedFile)
+
+        when:
+        compile(movedFile)
+        then:
+        !objectFile(orgFile)
+        objectFile(movedFile)
+    }
+
+    def objectFile(TestFile testFile) {
+        assert outputDir.listFiles().size() == 1
+        assert outputDir.listFiles()[0].listFiles().size() == 1
+        outputDir.listFiles()[0].listFiles()[0].text == testFile.absolutePath
+    }
+
+    def compile(TestFile... sourceToCompile) {
+        List<TestFile> toCompile = Arrays.asList(sourceToCompile)
+        List<TestFile> toRemove = sourceFiles - toCompile
+        2 * spec.getSourceFiles() >> toCompile
+        1 * spec.getRemovedSourceFiles() >> toRemove
+        cleanCompiler.execute(spec)
+    }
+
+    def createObjDummy(File sourceFile) {
+        TestFile objectFile = outputDir.file("${HashUtil.createCompactMD5(sourceFile.absolutePath)}/${sourceFile.name - ".c" + ".o" }")
+        objectFile.touch()
+        objectFile.text = sourceFile.absolutePath
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainAvailabilityTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainAvailabilityTest.groovy
new file mode 100644
index 0000000..41e9306
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/ToolChainAvailabilityTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal
+
+import org.gradle.util.TreeVisitor
+import spock.lang.Specification
+
+class ToolChainAvailabilityTest extends Specification {
+    def "visits message"() {
+        def visitor = Mock(TreeVisitor)
+
+        given:
+        def availability = new ToolChainAvailability()
+        availability.unavailable("some reason")
+
+        when:
+        availability.explain(visitor)
+
+        then:
+        visitor.node("some reason")
+    }
+
+    def "ignores available tool"() {
+        def searchResult = Mock(ToolSearchResult)
+
+        given:
+        searchResult.available >> true
+
+        when:
+        def availability = new ToolChainAvailability()
+        availability.mustBeAvailable(searchResult)
+
+        then:
+        availability.available
+    }
+
+    def "visits missing tool"() {
+        def visitor = Mock(TreeVisitor)
+        def searchResult = Mock(ToolSearchResult)
+
+        given:
+        searchResult.available >> false
+
+        and:
+        def availability = new ToolChainAvailability()
+        availability.mustBeAvailable(searchResult)
+
+        when:
+        availability.explain(visitor)
+
+        then:
+        1 * searchResult.explain(visitor)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/UnavailablePlatformToolChainTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/UnavailablePlatformToolChainTest.groovy
new file mode 100644
index 0000000..f1da702
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/UnavailablePlatformToolChainTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal
+
+import org.gradle.api.GradleException
+import org.gradle.util.TreeVisitor
+import spock.lang.Specification
+
+class UnavailablePlatformToolChainTest extends Specification {
+    def reason = new ToolChainAvailability().unavailable("broken")
+    def toolChain = new UnavailablePlatformToolChain(reason)
+
+    def "is not available"() {
+        expect:
+        !toolChain.available
+
+        def visitor = Mock(TreeVisitor)
+
+        when:
+        toolChain.explain(visitor)
+
+        then:
+        1 * visitor.node("broken")
+    }
+
+    def "throws failure when attempting to create C compiler"() {
+        when:
+        toolChain.createCCompiler()
+
+        then:
+        GradleException e = thrown()
+        e.message == "broken"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy
new file mode 100644
index 0000000..2191f03
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.text.TreeFormatter
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal
+import org.gradle.nativebinaries.platform.internal.DefaultArchitecture
+import org.gradle.nativebinaries.platform.internal.DefaultOperatingSystem
+import org.gradle.nativebinaries.toolchain.TargetPlatformConfiguration
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult
+import org.gradle.nativebinaries.toolchain.internal.ToolType
+import org.gradle.nativebinaries.toolchain.internal.tools.ToolSearchPath
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.TreeVisitor
+import spock.lang.Specification
+
+import static ArchitectureInternal.InstructionSet.X86
+
+class AbstractGccCompatibleToolChainTest extends Specification {
+    def fileResolver = Mock(FileResolver)
+    def execActionFactory = Mock(ExecActionFactory)
+    def toolSearchPath = Stub(ToolSearchPath)
+    def tool = Stub(CommandLineToolSearchResult) {
+        isAvailable() >> true
+    }
+    def toolChain = new TestToolChain("test", fileResolver, execActionFactory, toolSearchPath)
+    def platform = Stub(Platform)
+
+    def "is unavailable when platform is not known and is not the default platform"() {
+        given:
+        platform.name >> 'unknown'
+
+        expect:
+        def platformToolChain = toolChain.target(platform)
+        !platformToolChain.available
+        getMessage(platformToolChain) == "Don't know how to build for platform 'unknown'."
+    }
+
+    def "is unavailable when no language tools can be found and building for default platform"() {
+        def missing = Stub(CommandLineToolSearchResult) {
+            isAvailable() >> false
+            explain(_) >> { TreeVisitor<String> visitor -> visitor.node("c compiler not found") }
+        }
+
+        given:
+        platform.operatingSystem >> DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+        platform.architecture >> ArchitectureInternal.TOOL_CHAIN_DEFAULT
+
+        and:
+        toolSearchPath.locate(ToolType.C_COMPILER, "gcc") >> missing
+        toolSearchPath.locate(ToolType.CPP_COMPILER, "g++") >> missing
+        toolSearchPath.locate(ToolType.OBJECTIVEC_COMPILER, "gcc") >> missing
+        toolSearchPath.locate(ToolType.OBJECTIVECPP_COMPILER, "g++") >> missing
+
+        expect:
+        def platformToolChain = toolChain.target(platform)
+        !platformToolChain.available
+        getMessage(platformToolChain) == "c compiler not found"
+    }
+
+    def "is available when any language tool can be found and building for default platform"() {
+        def missing = Stub(CommandLineToolSearchResult) {
+            isAvailable() >> false
+        }
+
+        given:
+        platform.operatingSystem >> DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+        platform.architecture >> ArchitectureInternal.TOOL_CHAIN_DEFAULT
+
+        and:
+        toolSearchPath.locate(ToolType.CPP_COMPILER, "g++") >> missing
+        toolSearchPath.locate(_, _) >> tool
+
+        expect:
+        toolChain.target(platform).available
+    }
+
+    def "is available when any language tool can be found and platform configuration registered for platform"() {
+        def platformConfig = Mock(TargetPlatformConfiguration)
+
+        given:
+        toolSearchPath.locate(_, _) >> tool
+        platformConfig.supportsPlatform(platform) >> true
+
+        and:
+        toolChain.addPlatformConfiguration(platformConfig)
+
+        expect:
+        toolChain.target(platform).available
+    }
+
+    def "supplies no additional arguments to target native binary for tool chain default"() {
+        when:
+        toolSearchPath.locate(_, _) >> tool
+        platform.getOperatingSystem() >> DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+        platform.getArchitecture() >> ArchitectureInternal.TOOL_CHAIN_DEFAULT
+
+        then:
+        with(toolChain.getPlatformConfiguration(platform)) {
+            linkerArgs == []
+            cppCompilerArgs == []
+            CCompilerArgs == []
+            assemblerArgs == []
+            staticLibraryArchiverArgs == []
+        }
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "supplies args for supported architecture"() {
+        when:
+        toolSearchPath.locate(_, _) >> tool
+        platform.operatingSystem >> DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+        platform.architecture >> new DefaultArchitecture(arch, instructionSet, registerSize)
+
+        then:
+        toolChain.target(platform).available
+
+        with(toolChain.getPlatformConfiguration(platform)) {
+            linkerArgs == [linkerArg]
+            cppCompilerArgs == [compilerArg]
+            CCompilerArgs == [compilerArg]
+            if (OperatingSystem.current().isMacOsX()) {
+                assemblerArgs == osxAssemblerArgs
+            } else {
+                assemblerArgs == [assemblerArg]
+            }
+            staticLibraryArchiverArgs == []
+        }
+
+        where:
+        arch     | instructionSet | registerSize | linkerArg | compilerArg | assemblerArg | osxAssemblerArgs
+        "i386"   | X86            | 32           | "-m32"    | "-m32"      | "--32"       | ["-arch", "i386"]
+        "x86_64" | X86            | 64           | "-m64"    | "-m64"      | "--64"       | ["-arch", "x86_64"]
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "supplies args for supported architecture for i386 architecture on windows"() {
+        when:
+        toolSearchPath.locate(_, _) >> tool
+        platform.operatingSystem >> DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+        platform.architecture >> new DefaultArchitecture("i386", X86, 32)
+
+        then:
+        toolChain.target(platform).available
+
+        with(toolChain.getPlatformConfiguration(platform)) {
+            linkerArgs == ["-m32"]
+            cppCompilerArgs == ["-m32"]
+            CCompilerArgs == ["-m32"]
+            assemblerArgs == ["--32"]
+            staticLibraryArchiverArgs == []
+        }
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "cannot target x86_64 architecture on windows"() {
+        given:
+        toolSearchPath.locate(_, _) >> tool
+
+        and:
+        platform.getName() >> "x64"
+        platform.operatingSystem >> DefaultOperatingSystem.TOOL_CHAIN_DEFAULT
+        platform.architecture >> new DefaultArchitecture("x64", X86, 64)
+
+        when:
+        def platformToolChain = toolChain.target(platform)
+
+        then:
+        !platformToolChain.available
+        getMessage(platformToolChain) == "Don't know how to build for platform 'x64'."
+    }
+
+    def "uses supplied platform configurations in order to target binary"() {
+        def platformConfig1 = Mock(TargetPlatformConfiguration)
+        def platformConfig2 = Mock(TargetPlatformConfiguration)
+        when:
+        toolSearchPath.locate(_, _) >> tool
+        platform.getOperatingSystem() >> new DefaultOperatingSystem("other", OperatingSystem.SOLARIS)
+
+        and:
+        toolChain.addPlatformConfiguration(platformConfig1)
+        toolChain.addPlatformConfiguration(platformConfig2)
+
+        and:
+        platformConfig1.supportsPlatform(platform) >> false
+        platformConfig2.supportsPlatform(platform) >> true
+
+        then:
+        toolChain.target(platform).available
+
+        and:
+        toolChain.getPlatformConfiguration(platform) == platformConfig2
+    }
+
+    def getMessage(ToolSearchResult result) {
+        def formatter = new TreeFormatter()
+        result.explain(formatter)
+        return formatter.toString()
+    }
+
+    static class TestToolChain extends AbstractGccCompatibleToolChain {
+        TestToolChain(String name, FileResolver fileResolver, ExecActionFactory execActionFactory, ToolSearchPath tools) {
+            super(name, OperatingSystem.current(), fileResolver, execActionFactory, tools)
+
+            registerTool(ToolType.CPP_COMPILER, "g++");
+            registerTool(ToolType.C_COMPILER, "gcc");
+            registerTool(ToolType.OBJECTIVECPP_COMPILER, "g++");
+            registerTool(ToolType.OBJECTIVEC_COMPILER, "gcc");
+            registerTool(ToolType.ASSEMBLER, "as");
+            registerTool(ToolType.LINKER, "ld");
+            registerTool(ToolType.STATIC_LIB_ARCHIVER, "ar");
+        }
+
+        @Override
+        protected String getTypeName() {
+            return "Test"
+        }
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AssemblerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AssemblerTest.groovy
new file mode 100644
index 0000000..41292f5
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/AssemblerTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.api.Action
+import org.gradle.internal.hash.HashUtil
+import org.gradle.nativebinaries.language.assembler.internal.AssembleSpec
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class AssemblerTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+
+    def executable = new File("executable")
+    def execActionFactory = Mock(ExecActionFactory)
+    Action<List<String>> argAction = Mock(Action)
+    CommandLineTool<AssembleSpec> commandLineTool = new CommandLineTool<AssembleSpec>("assembler", executable, execActionFactory)
+    Assembler assembler = new Assembler(commandLineTool, argAction, ".o");
+
+    def "assembles each source file independently"() {
+        given:
+        def testDir = tmpDirProvider.testDirectory
+        def objectFileDir = testDir.file("output/objects")
+
+        def execAction1 = Mock(ExecAction)
+        def execAction2 = Mock(ExecAction)
+
+        def sourceOne = testDir.file("one.s")
+        def sourceTwo = testDir.file("two.s")
+
+        when:
+        AssembleSpec assembleSpec = Mock(AssembleSpec)
+        assembleSpec.getObjectFileDir() >> objectFileDir
+        assembleSpec.getAllArgs() >> ["-firstArg", "-secondArg"]
+        assembleSpec.getSourceFiles() >> [sourceOne, sourceTwo]
+
+        and:
+        assembler.execute(assembleSpec)
+
+        then:
+        1 * argAction.execute(["-firstArg", "-secondArg", "-o", outputFilePathFor(objectFileDir, sourceOne), sourceOne.absolutePath])
+        1 * execActionFactory.newExecAction() >> execAction1
+        1 * execAction1.executable(executable)
+        1 * execAction1.workingDir(objectFileDir)
+        1 * execAction1.args(["-firstArg", "-secondArg", "-o", outputFilePathFor(objectFileDir, sourceOne), sourceOne.absolutePath])
+        1 * execAction1.environment([:])
+        1 * execAction1.execute()
+        0 * execAction1._
+
+        1 * argAction.execute(["-firstArg", "-secondArg", "-o", outputFilePathFor(objectFileDir, sourceTwo), sourceTwo.absolutePath])
+        1 * execActionFactory.newExecAction() >> execAction2
+        1 * execAction2.executable(executable)
+        1 * execAction2.workingDir(objectFileDir)
+        1 * execAction2.args(["-firstArg", "-secondArg", "-o", outputFilePathFor(objectFileDir, sourceTwo), sourceTwo.absolutePath])
+        1 * execAction2.environment([:])
+        1 * execAction2.execute()
+        0 * execAction2._
+    }
+
+    String outputFilePathFor(File objectFileRoot, File sourceFile) {
+        String relativeObjectFilePath = "${HashUtil.createCompactMD5(sourceFile.absolutePath)}/${sourceFile.name - ".s"}.o"
+        String outputFilePath = new File(objectFileRoot, relativeObjectFilePath).absolutePath;
+        outputFilePath
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CCompilerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CCompilerTest.groovy
new file mode 100644
index 0000000..7b21b75
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/CCompilerTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.api.Action
+import org.gradle.internal.hash.HashUtil
+import org.gradle.nativebinaries.language.c.internal.CCompileSpec
+import org.gradle.nativebinaries.language.c.internal.DefaultCCompileSpec
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import org.gradle.internal.os.OperatingSystem
+
+class CCompilerTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+
+    def executable = new File("executable")
+    def execActionFactory = Mock(ExecActionFactory)
+    Action<List<String>> argAction = Mock(Action)
+    CommandLineTool<CCompileSpec> commandLineTool = new CommandLineTool<CCompileSpec>("cCompiler", executable, execActionFactory)
+    CCompiler compiler = new CCompiler(commandLineTool, argAction, false);
+    String objectFileExtension = OperatingSystem.current().isWindows() ? ".obj" : ".o";
+
+    def "compiles all source files in separate executions"() {
+        given:
+        def testDir = tmpDirProvider.testDirectory
+        def objectFileDir = testDir.file("output/objects")
+
+        def execAction = Mock(ExecAction)
+
+        when:
+        CCompileSpec compileSpec = Spy(DefaultCCompileSpec)
+        compileSpec.getMacros() >> [foo: "bar", empty: null]
+        compileSpec.getObjectFileDir() >> objectFileDir
+        compileSpec.getAllArgs() >> ["-firstArg", "-secondArg"]
+        compileSpec.getIncludeRoots() >> [testDir.file("include.h")]
+        compileSpec.setSourceFiles([testDir.file("one.c"), testDir.file("two.c")])
+
+        and:
+        compiler.execute(compileSpec)
+
+        then:
+        2 * argAction.execute([
+                "-x", "c",
+                "-Dfoo=bar", "-Dempty",
+                "-firstArg", "-secondArg",
+                "-c",
+                "-I", testDir.file("include.h").absolutePath])
+
+        ["one.c", "two.c"].each{ sourceFileName ->
+
+            TestFile sourceFile = testDir.file(sourceFileName)
+            String objectOutputPath = outputFilePathFor(objectFileDir, sourceFile)
+
+            1 * execAction.args([
+                    "-x", "c",
+                    "-Dfoo=bar", "-Dempty",
+                    "-firstArg", "-secondArg",
+                    "-c",
+                    "-I", testDir.file("include.h").absolutePath,
+                    testDir.file(sourceFileName).absolutePath,
+                    "-o", objectOutputPath])
+
+        }
+        2 * execActionFactory.newExecAction() >> execAction
+        2 * execAction.executable(executable)
+        2 * execAction.workingDir(_)
+        2 * execAction.environment([:])
+        2 * execAction.execute()
+        0 * execAction._
+        0 * argAction._
+    }
+
+    String outputFilePathFor(File objectFileRoot, TestFile testFile) {
+        String relativeObjectFilePath = "${HashUtil.createCompactMD5(testFile.absolutePath)}/${testFile.name - ".c"}$objectFileExtension"
+        String outputFilePath = new File(objectFileRoot, relativeObjectFilePath).absolutePath;
+        outputFilePath
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ClangToolChainTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ClangToolChainTest.groovy
new file mode 100644
index 0000000..b2ef9a0
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ClangToolChainTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.toolchain.internal.clang.ClangToolChain
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ClangToolChainTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+    final FileResolver fileResolver = Mock(FileResolver)
+    final toolChain = new ClangToolChain("clang", OperatingSystem.current(), fileResolver, Stub(ExecActionFactory))
+
+    def "has default tool names"() {
+        expect:
+        toolChain.cppCompiler.executable == "clang++"
+        toolChain.cCompiler.executable == "clang"
+        toolChain.assembler.executable == "as"
+        toolChain.linker.executable == "clang++"
+        toolChain.staticLibArchiver.executable == "ar"
+    }
+
+    def "can update tool names"() {
+        when:
+        toolChain.assembler.executable = "foo"
+
+        then:
+        toolChain.assembler.executable == "foo"
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccLinkerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccLinkerTest.groovy
new file mode 100644
index 0000000..71a7bfd
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccLinkerTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.api.Action
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.internal.LinkerSpec
+import org.gradle.nativebinaries.internal.SharedLibraryLinkerSpec
+import org.gradle.nativebinaries.toolchain.internal.CommandLineTool
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class GccLinkerTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+
+    def executable = new File("executable")
+    def execActionFactory = Mock(ExecActionFactory)
+    Action<List<String>> argAction = Mock(Action)
+    CommandLineTool<LinkerSpec> commandLineTool = new CommandLineTool<LinkerSpec>("linker", executable, execActionFactory)
+    GccLinker linker = new GccLinker(commandLineTool, argAction, false);
+
+    def "compiles all source files in a single execution"() {
+        given:
+        def testDir = tmpDirProvider.testDirectory
+        def outputFile = testDir.file("output/lib")
+
+        def execAction = Mock(ExecAction)
+        final expectedArgs = [
+                "-sys1", "-sys2",
+                "-shared",
+                getSoNameProp("installName"),
+                "-o", outputFile.absolutePath,
+                testDir.file("one.o").absolutePath,
+                testDir.file("two.o").absolutePath,
+                "-arg1", "-arg2"].flatten()
+
+        when:
+        LinkerSpec spec = Mock(SharedLibraryLinkerSpec)
+        spec.getSystemArgs() >> ['-sys1', '-sys2']
+        spec.getArgs() >> ['-arg1', '-arg2']
+        spec.getOutputFile() >> outputFile
+        spec.getLibraries() >> []
+        spec.getLibraryPath() >> []
+        spec.getInstallName() >> "installName"
+        spec.getObjectFiles() >> [testDir.file("one.o"), testDir.file("two.o")]
+
+        and:
+        linker.execute(spec)
+
+        then:
+        1 * argAction.execute(expectedArgs)
+        1 * execActionFactory.newExecAction() >> execAction
+        1 * execAction.executable(executable)
+        1 * execAction.args(expectedArgs)
+        1 * execAction.environment([:])
+        1 * execAction.execute()
+        0 * execAction._
+    }
+
+    List<String> getSoNameProp(def value) {
+        if (OperatingSystem.current().isWindows()) {
+            return []
+        }
+        if (OperatingSystem.current().isMacOsX()) {
+            return ["-Wl,-install_name,${value}"]
+        }
+        return ["-Wl,-soname,${value}"]
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolChainTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolChainTest.groovy
new file mode 100644
index 0000000..e250b94
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/GccToolChainTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class GccToolChainTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+    final FileResolver fileResolver = Mock(FileResolver)
+    final toolChain = new GccToolChain("gcc", OperatingSystem.current(), fileResolver, Stub(ExecActionFactory))
+
+    def "uses shared library binary at link time"() {
+        expect:
+        toolChain.getSharedLibraryLinkFileName("test") == toolChain.getSharedLibraryName("test")
+    }
+
+    def "has default tool names"() {
+        expect:
+        toolChain.cppCompiler.executable == "g++"
+        toolChain.cCompiler.executable == "gcc"
+        toolChain.assembler.executable == "as"
+        toolChain.linker.executable == "g++"
+        toolChain.staticLibArchiver.executable == "ar"
+    }
+
+    def "can update tool names"() {
+        when:
+        toolChain.assembler.executable = "foo"
+
+        then:
+        toolChain.assembler.executable == "foo"
+    }
+
+    def "resolves path entries"() {
+        def testDir = tmpDirProvider.testDirectory
+
+        when:
+        toolChain.path "The Path"
+        toolChain.path "Path1", "Path2"
+
+        then:
+        fileResolver.resolve("The Path") >> testDir.file("one")
+        fileResolver.resolve("Path1") >> testDir.file("two")
+        fileResolver.resolve("Path2") >> testDir.file("three")
+
+        and:
+        toolChain.path == [testDir.file("one"), testDir.file("two"), testDir.file("three")]
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ShortCircuitArgsTransformerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ShortCircuitArgsTransformerTest.groovy
new file mode 100644
index 0000000..67a8607
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/ShortCircuitArgsTransformerTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.gcc
+
+import org.gradle.nativebinaries.toolchain.internal.ArgsTransformer
+import org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec
+import spock.lang.Specification
+
+class ShortCircuitArgsTransformerTest extends Specification {
+
+    def "can short circuit args transformation"(){
+        given:
+        def spec = Mock(NativeCompileSpec)
+        def expectedArgs = Arrays.asList("some", "options");
+        def delegateArgsTransformer = Mock(ArgsTransformer)
+        def ShortCircuitArgsTransformer transformer = new ShortCircuitArgsTransformer(delegateArgsTransformer)
+
+        when:
+        def transformedArgs = transformer.transform(spec)
+        then:
+        transformedArgs == expectedArgs
+        1 * delegateArgsTransformer.transform(spec) >> expectedArgs
+
+        when:
+        transformedArgs = transformer.transform(spec)
+        then:
+        transformedArgs == expectedArgs
+        0 * delegateArgsTransformer.transform(spec)
+
+    }
+
+    def "does not short circuit when spec has changed"(){
+        given:
+        def spec = Mock(NativeCompileSpec)
+        def changedSpec = Mock(NativeCompileSpec)
+        def delegateArgsTransformer = Mock(ArgsTransformer)
+        def ShortCircuitArgsTransformer transformer = new ShortCircuitArgsTransformer(delegateArgsTransformer)
+
+        when:
+        transformer.transform(spec)
+        then:
+        1 * delegateArgsTransformer.transform(spec)
+
+        when:
+        transformer.transform(changedSpec)
+        then:
+        1 * delegateArgsTransformer.transform(changedSpec)
+        0 * delegateArgsTransformer.transform(spec)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionDeterminerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionDeterminerTest.groovy
new file mode 100644
index 0000000..b1cb4d5
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/gcc/version/GccVersionDeterminerTest.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.nativebinaries.toolchain.internal.gcc.version
+
+import org.gradle.api.Transformer
+import org.gradle.process.ExecResult
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.util.TreeVisitor
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class GccVersionDeterminerTest extends Specification {
+    def execActionFactory = Mock(ExecActionFactory)
+
+    @Unroll
+    "can scrape ok output for #version"() {
+        expect:
+        def result = output(output)
+        result.available
+        result.version == version
+
+        where:
+        [version, output] << OUTPUTS.collect { [it.value, it.key] }
+    }
+
+    def "handles gcc output that cannot be parsed"() {
+        def visitor = Mock(TreeVisitor)
+
+        expect:
+        def result = output(output)
+        !result.available
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("Could not determine GCC version: g++ produced unexpected output.")
+
+        where:
+        output << [ "not sure about this", "" ]
+    }
+
+    def "g++ execution error ok"() {
+        given:
+        def visitor = Mock(TreeVisitor)
+        def action = Mock(ExecAction)
+        def execResult = Mock(ExecResult)
+
+        and:
+        def determiner = new GccVersionDeterminer(execActionFactory)
+        def binary = new File("g++")
+        
+        when:
+        def result = determiner.transform(binary)
+        
+        then:
+        1 * execActionFactory.newExecAction() >> action
+        1 * action.execute() >> execResult
+        1 * execResult.getExitValue() >> 1
+
+        and:
+        !result.available
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("Could not determine GCC version: failed to execute g++ -v.")
+    }
+
+    def "detects clang pretending to be gcc"() {
+        def visitor = Mock(TreeVisitor)
+
+        expect:
+        def result = output """#define __GNUC_MINOR__ 2
+#define __GNUC_PATCHLEVEL__ 1
+#define __GNUC__ 4
+#define __VERSION__ "4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)"
+#define __clang__ 1
+#define __clang_major__ 5
+#define __clang_minor__ 0
+#define __clang_patchlevel__ 0
+#define __clang_version__ "5.0 (clang-500.2.79)"
+"""
+        !result.available
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("XCode g++ is a wrapper around Clang. Treating it as Clang and not GCC.")
+    }
+
+    GccVersionResult output(String output) {
+        def action = Mock(ExecAction)
+        def result = Mock(ExecResult)
+        1 * execActionFactory.newExecAction() >> action
+        1 * action.setStandardOutput(_) >> { OutputStream outstr -> outstr << output; action }
+        1 * action.execute() >> result
+        new GccVersionDeterminer(execActionFactory).transform(new File("g++"))
+    }
+
+    Transformer transformer(constant) {
+        transformer { constant }
+    }
+
+    Transformer transformer(Closure closure) {
+        new Transformer() {
+            String transform(original) {
+                closure.call(original)
+            }
+        }
+    }
+
+    static final OUTPUTS = [
+        """#define __GNUC_MINOR__ 2
+#define __GNUC_PATCHLEVEL__ 1
+#define __GNUC__ 4
+#define __INTMAX_C(c) c ## LL
+#define __REGISTER_PREFIX__ """: "4",
+        """#define __gnu_linux__ 1
+#define __GNUC__ 3""": "3",
+    ]
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultVisualStudioLocatorTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultVisualStudioLocatorTest.groovy
new file mode 100644
index 0000000..3157ea9
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultVisualStudioLocatorTest.groovy
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp
+
+import net.rubygrapefruit.platform.MissingRegistryEntryException
+import net.rubygrapefruit.platform.SystemInfo
+import net.rubygrapefruit.platform.WindowsRegistry
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TreeVisitor
+import org.gradle.util.VersionNumber
+import org.junit.Rule
+
+import spock.lang.Specification
+
+class DefaultVisualStudioLocatorTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    final WindowsRegistry windowsRegistry =  Stub(WindowsRegistry)
+    final SystemInfo systemInfo =  Stub(SystemInfo)
+    final OperatingSystem operatingSystem = Stub(OperatingSystem) {
+        isWindows() >> true
+        getExecutableName(_ as String) >> { String exeName -> exeName }
+    }
+    final VisualStudioLocator visualStudioLocator = new DefaultVisualStudioLocator(operatingSystem, windowsRegistry, systemInfo)
+
+    def "use highest visual studio version found in the registry"() {
+        def dir1 = vsDir("vs1");
+        def dir2 = vsDir("vs2");
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/) >> ["", "11.0", "12.0", "ignore-me"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/, "11.0") >> dir1.absolutePath + "/VC"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/, "12.0") >> dir2.absolutePath + "/VC"
+
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(null)
+
+        then:
+        result.available
+        result.visualStudio.name == "Visual C++ 12.0"
+        result.visualStudio.version == VersionNumber.parse("12.0")
+        result.visualStudio.baseDir == dir2
+        result.visualStudio.visualCpp
+    }
+
+    def "visual studio not available when nothing in registry and executable not found in path"() {
+        def visitor = Mock(TreeVisitor)
+
+        given:
+        windowsRegistry.getValueNames(_, _) >> { throw new MissingRegistryEntryException("not found") }
+        operatingSystem.findInPath(_) >> null
+
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(null)
+
+        then:
+        !result.available
+        result.visualStudio == null
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("Could not locate a Visual Studio installation, using the Windows registry and system path.")
+    }
+
+    def "locates visual studio installation based on executables in path"() {
+        def vsDir = vsDir("vs")
+
+        given:
+        windowsRegistry.getValueNames(_, _) >> { throw new MissingRegistryEntryException("not found") }
+        operatingSystem.findInPath("cl.exe") >> vsDir.file("VC/bin/cl.exe")
+
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(null)
+
+        then:
+        result.available
+        result.visualStudio.name == "Visual C++ from system path"
+        result.visualStudio.version == VersionNumber.UNKNOWN
+        result.visualStudio.baseDir == vsDir
+    }
+
+    def "uses visual studio using specified install dir"() {
+        def vsDir1 = vsDir("vs")
+        def vsDir2 = vsDir("vs-2")
+        def ignored = vsDir("vs-3")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/) >> ["12.0"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/, "12.0") >> ignored.absolutePath + "/VC"
+        assert visualStudioLocator.locateVisualStudioInstalls(null).available
+
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(vsDir1)
+
+        then:
+        result.available
+        result.visualStudio.name == "Visual C++ from user provided path"
+        result.visualStudio.version == VersionNumber.UNKNOWN
+        result.visualStudio.baseDir == vsDir1
+
+        when:
+        result = visualStudioLocator.locateVisualStudioInstalls(vsDir2)
+
+        then:
+        result.available
+        result.visualStudio.name == "Visual C++ from user provided path"
+        result.visualStudio.version == VersionNumber.UNKNOWN
+        result.visualStudio.baseDir == vsDir2
+    }
+
+    def "visual studio not found when specified directory does not look like an install"() {
+        def visitor = Mock(TreeVisitor)
+        def providedDir = tmpDir.createDir("vs")
+        def ignoredDir = vsDir("vs-2")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/) >> ["12.0"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/, "12.0") >> ignoredDir.absolutePath + "/VC"
+        assert visualStudioLocator.locateVisualStudioInstalls(null).available
+
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(providedDir)
+
+        then:
+        !result.available
+        result.visualStudio == null
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("The specified installation directory '$providedDir' does not appear to contain a Visual Studio installation.")
+    }
+
+    def "fills in meta-data from registry for install discovered using the system path"() {
+        def vsDir = vsDir("vs")
+
+        given:
+        operatingSystem.findInPath("cl.exe") >> vsDir.file("VC/bin/cl.exe")
+
+        and:
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/) >> ["12.0"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/, "12.0") >> vsDir.absolutePath + "/VC"
+        
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(null)
+
+        then:
+        result.available
+        result.visualStudio.name == "Visual C++ 12.0"
+        result.visualStudio.version == VersionNumber.parse("12.0")
+        result.visualStudio.baseDir == vsDir
+    }
+
+    def "fills in meta-data from registry for user specified install"() {
+        def vsDir = vsDir("vs")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+
+        and:
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/) >> ["12.0"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\VisualStudio\SxS\VC7/, "12.0") >> vsDir.absolutePath + "/VC"
+
+        when:
+        def result = visualStudioLocator.locateVisualStudioInstalls(vsDir)
+
+        then:
+        result.available
+        result.visualStudio.name == "Visual C++ 12.0"
+        result.visualStudio.version == VersionNumber.parse("12.0")
+        result.visualStudio.baseDir == vsDir
+    }
+
+    def vsDir(String name) {
+        def dir = tmpDir.createDir(name)
+        dir.createDir("Common7")
+        dir.createFile("VC/bin/cl.exe")
+        dir.createDir("VC/lib")
+        return dir
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultWindowsSdkLocatorTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultWindowsSdkLocatorTest.groovy
new file mode 100644
index 0000000..55bc49c
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/DefaultWindowsSdkLocatorTest.groovy
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp
+
+import net.rubygrapefruit.platform.MissingRegistryEntryException
+import net.rubygrapefruit.platform.WindowsRegistry
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TreeVisitor
+import org.gradle.util.VersionNumber
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultWindowsSdkLocatorTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    final WindowsRegistry windowsRegistry = Stub(WindowsRegistry)
+    final OperatingSystem operatingSystem = Stub(OperatingSystem) {
+        isWindows() >> true
+        getExecutableName(_ as String) >> { String exeName -> exeName }
+    }
+    final WindowsSdkLocator windowsSdkLocator = new DefaultWindowsSdkLocator(operatingSystem, windowsRegistry)
+
+    def "uses highest version SDK found in registry"() {
+        def dir1 = sdkDir("sdk1")
+        def dir2 = sdkDir("sdk2")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows/) >> ["v1", "v2"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "InstallationFolder") >> dir1.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductVersion") >> "7.0"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductName") >> "sdk 1"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v2/, "InstallationFolder") >> dir2.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v2/, "ProductVersion") >> "7.1"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v2/, "ProductName") >> "sdk 2"
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(null)
+
+        then:
+        result.available
+        result.sdk.name == "sdk 2"
+        result.sdk.version == VersionNumber.parse("7.1")
+        result.sdk.baseDir == dir2
+    }
+
+    def "uses windows kit if version is higher than windows SDK"() {
+        def dir1 = sdkDir("sdk1")
+        def dir2 = kitDir("sdk2")
+        def dir3 = kitDir("sdk3")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\SDKs\Windows/) >> ["v1"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "InstallationFolder") >> dir1.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductVersion") >> "7.1"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductName") >> "sdk 1"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows Kits\Installed Roots/, "KitsRoot") >> dir2.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows Kits\Installed Roots/, "KitsRoot81") >> dir3.absolutePath
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(null)
+
+        then:
+        result.available
+        result.sdk.name == "Windows Kit 8.1"
+        result.sdk.version == VersionNumber.parse("8.1")
+        result.sdk.baseDir == dir3
+    }
+
+    def "handles missing SDKs and Kits"() {
+        def dir = sdkDir("sdk1")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\SDKs\Windows/) >> { throw new MissingRegistryEntryException("missing") }
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows Kits\Installed Roots/, "KitsRoot") >> { throw new MissingRegistryEntryException("missing") }
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows Kits\Installed Roots/, "KitsRoot81") >> dir.absolutePath
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(null)
+
+        then:
+        result.available
+        result.sdk.name == "Windows Kit 8.1"
+        result.sdk.version == VersionNumber.parse("8.1")
+        result.sdk.baseDir == dir
+    }
+
+    def "locates windows SDK based on executables in path"() {
+        def sdkDir = sdkDir("sdk")
+
+        given:
+        operatingSystem.findInPath("rc.exe") >> sdkDir.file("bin/rc.exe")
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(null)
+
+        then:
+        result.available
+        result.sdk.name == "Path-resolved Windows SDK"
+        result.sdk.version == VersionNumber.UNKNOWN
+        result.sdk.baseDir == sdkDir
+    }
+
+    def "SDK not available when not found in registry or system path"() {
+        def visitor = Mock(TreeVisitor)
+
+        given:
+        operatingSystem.findInPath(_) >> null
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(null)
+
+        then:
+        !result.available
+        result.sdk == null
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("Could not locate a Windows SDK installation, using the Windows registry and system path.")
+    }
+
+    def "uses windows SDK using specified install dir"() {
+        def sdkDir1 = this.sdkDir("sdk-1")
+        def sdkDir2 = this.sdkDir("sdk-2")
+        def ignoredDir = sdkDir("ignored")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows/) >> ["v1"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "InstallationFolder") >> ignoredDir.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductVersion") >> "7.0"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductName") >> "installed sdk"
+        assert windowsSdkLocator.locateWindowsSdks(null).available
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(sdkDir1)
+
+        then:
+        result.available
+        result.sdk.name == "User-provided Windows SDK"
+        result.sdk.version == VersionNumber.UNKNOWN
+        result.sdk.baseDir == sdkDir1
+
+        when:
+        result = windowsSdkLocator.locateWindowsSdks(sdkDir2)
+
+        then:
+        result.available
+        result.sdk.name == "User-provided Windows SDK"
+        result.sdk.version == VersionNumber.UNKNOWN
+        result.sdk.baseDir == sdkDir2
+    }
+
+    def "SDK not available when specified install dir does not look like an SDK"() {
+        def sdkDir1 = tmpDir.createDir("dir")
+        def ignoredDir = sdkDir("ignored")
+        def visitor = Mock(TreeVisitor)
+
+        given:
+        operatingSystem.findInPath(_) >> null
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows/) >> ["v1"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "InstallationFolder") >> ignoredDir.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductVersion") >> "7.0"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductName") >> "installed sdk"
+        assert windowsSdkLocator.locateWindowsSdks(null).available
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(sdkDir1)
+
+        then:
+        !result.available
+        result.sdk == null
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("The specified installation directory '$sdkDir1' does not appear to contain a Windows SDK installation.")
+    }
+
+    def "fills in meta-data from registry for SDK discovered using the path"() {
+        def sdkDir = sdkDir("sdk1")
+
+        given:
+        operatingSystem.findInPath("rc.exe") >> sdkDir.file("bin/rc.exe")
+
+        and:
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows/) >> ["v1"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "InstallationFolder") >> sdkDir.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductVersion") >> "7.0"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductName") >> "installed sdk"
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(null)
+
+        then:
+        result.available
+        result.sdk.name == "installed sdk"
+        result.sdk.version == VersionNumber.parse("7.0")
+        result.sdk.baseDir == sdkDir
+    }
+
+    def "fills in meta-data from registry for SDK specified by user"() {
+        def sdkDir = sdkDir("sdk1")
+
+        given:
+        operatingSystem.findInPath(_) >> null
+
+        and:
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows/) >> ["v1"]
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "InstallationFolder") >> sdkDir.absolutePath
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductVersion") >> "7.0"
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Microsoft SDKs\Windows\v1/, "ProductName") >> "installed sdk"
+
+        when:
+        def result = windowsSdkLocator.locateWindowsSdks(sdkDir)
+
+        then:
+        result.available
+        result.sdk.name == "installed sdk"
+        result.sdk.version == VersionNumber.parse("7.0")
+        result.sdk.baseDir == sdkDir
+    }
+
+    def sdkDir(String name) {
+        def dir = tmpDir.createDir(name)
+        dir.createFile("bin/rc.exe")
+        dir.createFile("lib/kernel32.lib")
+        return dir
+    }
+
+    def kitDir(String name) {
+        def dir = tmpDir.createDir(name)
+        dir.createFile("bin/x86/rc.exe")
+        dir.createFile("lib/win8/um/x86/kernel32.lib")
+        return dir
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppToolChainTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppToolChainTest.groovy
new file mode 100644
index 0000000..d926cf1
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/msvcpp/VisualCppToolChainTest.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.msvcpp
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.text.TreeFormatter
+import org.gradle.nativebinaries.platform.Platform
+import org.gradle.nativebinaries.toolchain.internal.ToolChainAvailability
+import org.gradle.nativebinaries.toolchain.internal.ToolSearchResult
+import org.gradle.process.internal.ExecActionFactory
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TreeVisitor
+import spock.lang.Specification
+
+class VisualCppToolChainTest extends Specification {
+    TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    final FileResolver fileResolver = Mock(FileResolver)
+    final ExecActionFactory execActionFactory = Mock(ExecActionFactory)
+    final VisualStudioLocator.SearchResult visualStudioLookup = Stub(VisualStudioLocator.SearchResult)
+    final WindowsSdkLocator.SearchResult windowsSdkLookup = Stub(WindowsSdkLocator.SearchResult)
+    final VisualStudioLocator visualStudioLocator = Stub(VisualStudioLocator) {
+        locateVisualStudioInstalls(_) >> visualStudioLookup
+    }
+    final WindowsSdkLocator windowsSdkLocator = Stub(WindowsSdkLocator) {
+        locateWindowsSdks(_) >> windowsSdkLookup
+    }
+    final OperatingSystem operatingSystem = Stub(OperatingSystem) {
+        isWindows() >> true
+    }
+    final toolChain = new VisualCppToolChain("visualCpp", operatingSystem, fileResolver, execActionFactory, visualStudioLocator, windowsSdkLocator)
+
+    def "uses .lib file for shared library at link time"() {
+        given:
+        operatingSystem.getSharedLibraryName("test") >> "test.dll"
+
+        expect:
+        toolChain.getSharedLibraryLinkFileName("test") == "test.lib"
+    }
+
+    def "uses .dll file for shared library at runtime time"() {
+        given:
+        operatingSystem.getSharedLibraryName("test") >> "test.dll"
+
+        expect:
+        toolChain.getSharedLibraryName("test") == "test.dll"
+    }
+
+    def "installs an unavailable tool chain when not windows"() {
+        given:
+        def operatingSystem = Stub(OperatingSystem)
+        operatingSystem.isWindows() >> false
+        def toolChain = new VisualCppToolChain("visualCpp", operatingSystem, fileResolver, execActionFactory, visualStudioLocator, windowsSdkLocator)
+
+        when:
+        def availability = new ToolChainAvailability()
+        toolChain.checkAvailable(availability)
+
+        then:
+        !availability.available
+        availability.unavailableMessage == 'Visual Studio is not available on this operating system.'
+    }
+
+    def "is not available when visual studio installation cannot be located"() {
+        when:
+        visualStudioLookup.available >> false
+        visualStudioLookup.explain(_) >> { TreeVisitor<String> visitor -> visitor.node("vs install not found anywhere") }
+        windowsSdkLookup.available >> false
+
+        and:
+        def result = toolChain.target(Stub(Platform))
+
+        then:
+        !result.available
+        getMessage(result) == "vs install not found anywhere"
+    }
+
+    def "is not available when windows SDK cannot be located"() {
+        when:
+        visualStudioLookup.available >> true
+
+        windowsSdkLookup.available >> false
+        windowsSdkLookup.explain(_) >> { TreeVisitor<String> visitor -> visitor.node("sdk not found anywhere") }
+
+        and:
+        def result = toolChain.target(Stub(Platform))
+
+        then:
+        !result.available
+        getMessage(result) == "sdk not found anywhere"
+    }
+
+    def "is not available when visual studio installation and windows SDK can be located and visual studio install does not support target platform"() {
+        when:
+        def visualStudio = Stub(VisualStudioInstall)
+        def visualCpp = Stub(VisualCppInstall)
+        def platform = Stub(Platform) { getName() >> 'platform' }
+        visualStudioLookup.available >> true
+        windowsSdkLookup.available >> true
+        visualStudioLookup.visualStudio >> visualStudio
+        visualStudioLookup.visualStudio >> Stub(VisualStudioInstall)
+        visualStudio.visualCpp >> visualCpp
+        visualCpp.isSupportedPlatform(platform) >> false
+
+        and:
+        def result = toolChain.target(platform)
+
+        then:
+        !result.available
+        getMessage(result) == "Don't know how to build for platform 'platform'."
+    }
+
+    def "is available when visual studio installation and windows SDK can be located and visual studio install supports target platform"() {
+        when:
+        def visualStudio = Stub(VisualStudioInstall)
+        def visualCpp = Stub(VisualCppInstall)
+        def platform = Stub(Platform)
+        visualStudioLookup.available >> true
+        windowsSdkLookup.available >> true
+        visualStudioLookup.visualStudio >> visualStudio
+        visualStudioLookup.visualStudio >> Stub(VisualStudioInstall)
+        visualStudio.visualCpp >> visualCpp
+        visualCpp.isSupportedPlatform(platform) >> true
+
+        and:
+        def platformToolChain = toolChain.target(platform)
+
+        then:
+        platformToolChain.available
+    }
+
+    def "uses provided installDir and windowsSdkDir for location"() {
+        when:
+        toolChain.installDir = "install-dir"
+        toolChain.windowsSdkDir = "windows-sdk-dir"
+
+        and:
+        fileResolver.resolve("install-dir") >> file("vs")
+        visualStudioLocator.locateVisualStudioInstalls(file("vs")) >> visualStudioLookup
+        visualStudioLookup.available >> true
+
+        and:
+        fileResolver.resolve("windows-sdk-dir") >> file("win-sdk")
+        windowsSdkLocator.locateWindowsSdks(file("win-sdk")) >> windowsSdkLookup
+        windowsSdkLookup.available >> true
+
+        and:
+        0 * _._
+
+        then:
+        def availability = new ToolChainAvailability()
+        toolChain.checkAvailable(availability);
+        availability.available
+    }
+
+    def "resolves install directory"() {
+        when:
+        toolChain.installDir = "The Path"
+
+        then:
+        fileResolver.resolve("The Path") >> file("one")
+
+        and:
+        toolChain.installDir == file("one")
+    }
+
+    def "resolves windows sdk directory"() {
+        when:
+        toolChain.windowsSdkDir = "The Path"
+
+        then:
+        fileResolver.resolve("The Path") >> file("one")
+
+        and:
+        toolChain.windowsSdkDir == file("one")
+    }
+
+    def file(String name) {
+        testDirectoryProvider.testDirectory.file(name)
+    }
+
+    def createFile(String name) {
+        file(name).createFile()
+    }
+
+    def getMessage(ToolSearchResult result) {
+        def formatter = new TreeFormatter()
+        result.explain(formatter)
+        return formatter.toString()
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolSearchPathTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolSearchPathTest.groovy
new file mode 100644
index 0000000..9e5cf82
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/internal/tools/ToolSearchPathTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.internal.tools
+
+import org.gradle.api.GradleException
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.toolchain.internal.ToolType
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TreeVisitor
+import org.junit.Rule
+import spock.lang.Specification
+
+class ToolSearchPathTest extends Specification {
+    @Rule def TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    def os = Stub(OperatingSystem)
+    def registry = new ToolSearchPath(os)
+
+    def "finds executable in system path"() {
+        def file = tmpDir.createFile("cc.bin")
+
+        given:
+        os.findInPath("cc") >> file
+
+        when:
+        def result = registry.locate(ToolType.C_COMPILER, "cc")
+
+        then:
+        result.available
+        result.tool == file
+    }
+
+    def "finds executable in provided path"() {
+        def file = tmpDir.createFile("cc.bin")
+
+        given:
+        os.getExecutableName("cc") >> "cc.bin"
+        registry.setPath([file.parentFile])
+
+        when:
+        def result = registry.locate(ToolType.C_COMPILER, "cc")
+
+        then:
+        result.available
+        result.tool == file
+    }
+
+    def "executable is unavailable when not found in path"() {
+        def visitor = Mock(TreeVisitor)
+        def dir1 = tmpDir.createDir("some-dir")
+        def dir2 = tmpDir.createDir("some-dir-2")
+
+        given:
+        os.getExecutableName("cc") >> "cc.bin"
+        registry.setPath([dir1, dir2])
+
+        when:
+        def result = registry.locate(ToolType.C_COMPILER, "cc")
+
+        then:
+        !result.available
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("Could not find C compiler 'cc'. Searched in")
+        1 * visitor.startChildren()
+        1 * visitor.node(dir1.toString())
+        1 * visitor.node(dir2.toString())
+        1 * visitor.endChildren()
+        0 * visitor._
+    }
+
+    def "executable is unavailable when not found in system path"() {
+        def visitor = Mock(TreeVisitor)
+
+        given:
+        os.findInPath("cc") >> null
+
+        when:
+        def result = registry.locate(ToolType.C_COMPILER, "cc")
+
+        then:
+        !result.available
+
+        when:
+        result.explain(visitor)
+
+        then:
+        1 * visitor.node("Could not find C compiler 'cc' in system path.")
+        0 * visitor._
+    }
+
+    def "cannot use an unavailable tool"() {
+        given:
+        os.findInPath("cc") >> null
+
+        when:
+        def result = registry.locate(ToolType.C_COMPILER, "cc")
+
+        then:
+        !result.available
+
+        when:
+        result.getTool()
+
+        then:
+        GradleException e = thrown()
+        e.message == "Could not find C compiler 'cc' in system path."
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/ClangCompilerPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/ClangCompilerPluginTest.groovy
new file mode 100644
index 0000000..5fbb6db
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/ClangCompilerPluginTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.Clang
+import org.gradle.nativebinaries.toolchain.internal.clang.ClangToolChain
+
+class ClangCompilerPluginTest extends ToolChainPluginTest {
+
+    @Override
+    Class<? extends Plugin> getPluginClass() {
+        ClangCompilerPlugin
+    }
+
+    @Override
+    Class<? extends ToolChain> getToolchainClass() {
+        Clang
+    }
+
+    @Override
+    String getToolchainName() {
+        "clang"
+    }
+
+    def "makes a Clang tool chain available"() {
+        when:
+        register()
+
+        then:
+        toolchain instanceof ClangToolChain
+        toolchain.displayName == "Tool chain 'clang' (Clang)"
+    }
+
+    def "registers default Clang tool chain"() {
+        when:
+        addDefaultToolchain()
+
+        then:
+        toolchain instanceof ClangToolChain
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/GccCompilerPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/GccCompilerPluginTest.groovy
new file mode 100644
index 0000000..d67cb54
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/GccCompilerPluginTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.Gcc
+import org.gradle.nativebinaries.toolchain.internal.gcc.GccToolChain
+import org.gradle.util.TestUtil
+
+class GccCompilerPluginTest extends ToolChainPluginTest {
+    def project = TestUtil.createRootProject()
+
+    @Override
+    Class<? extends Plugin> getPluginClass() {
+        GccCompilerPlugin
+    }
+
+    @Override
+    Class<? extends ToolChain> getToolchainClass() {
+        Gcc
+    }
+
+    @Override
+    String getToolchainName() {
+        "gcc"
+    }
+
+    def "makes a Gcc tool chain available"() {
+        when:
+        register()
+
+        then:
+        toolchain instanceof GccToolChain
+        toolchain.displayName == "Tool chain 'gcc' (GNU GCC)"
+    }
+
+    def "registers default Gcc tool chain"() {
+        when:
+        addDefaultToolchain()
+
+        then:
+        toolchain instanceof GccToolChain
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/MicrosoftVisualCppPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/MicrosoftVisualCppPluginTest.groovy
new file mode 100644
index 0000000..cfa6812
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/MicrosoftVisualCppPluginTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.VisualCpp
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.VisualCppToolChain
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+
+class MicrosoftVisualCppPluginTest extends ToolChainPluginTest {
+    @Rule
+    TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+
+    @Override
+    Class<? extends Plugin> getPluginClass() {
+        MicrosoftVisualCppPlugin
+    }
+
+    @Override
+    Class<? extends ToolChain> getToolchainClass() {
+        VisualCpp
+    }
+
+    @Override
+    String getToolchainName() {
+        VisualCppToolChain.DEFAULT_NAME
+    }
+
+    def "makes a VisualCpp tool chain available"() {
+        when:
+        register()
+
+        then:
+        toolchain instanceof VisualCppToolChain
+    }
+
+    def "registers default VisualCpp tool chain"() {
+        when:
+        addDefaultToolchain()
+
+        then:
+        toolchain instanceof VisualCppToolChain
+    }
+
+    def file(String name) {
+        testDirectoryProvider.testDirectory.file(name)
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/ToolchainPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/ToolchainPluginTest.groovy
new file mode 100644
index 0000000..89ba5d1
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/nativebinaries/toolchain/plugins/ToolchainPluginTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.toolchain.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.plugins.ExtraPropertiesExtension
+import org.gradle.nativebinaries.toolchain.ToolChain
+import org.gradle.nativebinaries.toolchain.ToolChainRegistry
+import org.gradle.nativebinaries.toolchain.internal.ToolChainInternal
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+abstract class ToolChainPluginTest extends Specification {
+
+    def project = TestUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(getPluginClass())
+    }
+
+    abstract Class<? extends Plugin> getPluginClass()
+
+    abstract Class<? extends ToolChain> getToolchainClass()
+
+    String getToolchainName() {
+        "toolchain"
+    }
+
+    ToolChainInternal getToolchain() {
+        project.modelRegistry.get("toolChains", ToolChainRegistry).getByName(getToolchainName()) as ToolChainInternal
+    }
+
+    void register() {
+        project.model {
+            toolChains {
+                create(getToolchainName(), getToolchainClass())
+            }
+        }
+    }
+
+    void addDefaultToolchain() {
+        project.model { toolChains { addDefaultToolChains() } }
+    }
+
+    def "tool chain is extended"() {
+        when:
+        register()
+
+        then:
+        with (toolchain) {
+            it instanceof ExtensionAware
+            it.ext instanceof ExtraPropertiesExtension
+        }
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
deleted file mode 100644
index 16d0a45..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
+++ /dev/null
@@ -1,130 +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.binaries.model.internal
-
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.api.internal.tasks.compile.Compiler
-import org.gradle.plugins.binaries.model.Binary
-import spock.lang.Specification
-
-class DefaultCompilerRegistryTest extends Specification {
-    final BinaryCompileSpecFactory specFactory = Mock()
-    final DefaultCompilerRegistry registry = new DefaultCompilerRegistry(new DirectInstantiator())
-
-    def setup() {
-        registry.specFactory = specFactory
-    }
-
-    def "search order defaults to the order that adapters are added"() {
-        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("z")
-        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("b")
-        CompilerAdapter<BinaryCompileSpec> compiler3 = compiler("a")
-
-        expect:
-        registry.searchOrder == []
-
-        when:
-        registry.add(compiler2)
-        registry.add(compiler1)
-        registry.add(compiler3)
-
-        then:
-        registry.searchOrder == [compiler2, compiler1, compiler3]
-
-        when:
-        registry.remove(compiler1)
-
-        then:
-        registry.searchOrder == [compiler2, compiler3]
-    }
-
-    def "compilation searches adapters in the order added and uses the first available"() {
-        Binary binary = Mock()
-        BinaryCompileSpec compileSpec = Mock()
-        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("z")
-        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("b")
-        CompilerAdapter<BinaryCompileSpec> compiler3 = compiler("a")
-        Compiler<BinaryCompileSpec> compiler = Mock()
-        Compiler<BinaryCompileSpec> lazyCompiler
-
-        given:
-        registry.add(compiler1)
-        registry.add(compiler2)
-        registry.add(compiler3)
-
-        and:
-        compiler2.available >> true
-
-        when:
-        registry.create(binary)
-
-        then:
-        1 * specFactory.create(binary, !null) >> { lazyCompiler = it[1]; return compileSpec }
-        
-        when:
-        lazyCompiler.execute(compileSpec)
-
-        then:
-        1 * compiler2.createCompiler(binary) >> compiler
-        1 * compiler.execute(compileSpec)
-    }
-
-    def "compilation fails when no adapter is available"() {
-        Binary binary = Mock()
-        BinaryCompileSpec compileSpec = Mock()
-        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("z")
-        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("b")
-        CompilerAdapter<BinaryCompileSpec> compiler3 = compiler("a")
-        Compiler<BinaryCompileSpec> lazyCompiler
-
-        given:
-        registry.add(compiler1)
-        registry.add(compiler2)
-        registry.add(compiler3)
-
-        when:
-        registry.create(binary)
-
-        then:
-        1 * specFactory.create(binary, !null) >> { lazyCompiler = it[1]; return compileSpec }
-        
-        when:
-        lazyCompiler.execute(compileSpec)
-
-        then:
-        IllegalStateException e = thrown()
-        e.message == "No compiler is available to compile $binary. Searched for $compiler1, $compiler2, $compiler3."
-    }
-
-    def "there is no default compiler when no adapters are available"() {
-        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("c1")
-        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("c2")
-
-        given:
-        registry.add(compiler1)
-        registry.add(compiler2)
-
-        expect:
-        registry.defaultCompiler == null
-    }
-
-    def compiler(String name) {
-        CompilerAdapter<BinaryCompileSpec> compiler = Mock()
-        _ * compiler.name >> name
-        return compiler
-    }
-}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppExeConventionPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppExeConventionPluginTest.groovy
deleted file mode 100644
index 9aee384..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppExeConventionPluginTest.groovy
+++ /dev/null
@@ -1,37 +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.cpp
-
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class CppExeConventionPluginTest extends Specification {
-    final def project = HelperUtil.createRootProject()
-
-    def "adds and configures main executable"() {
-        given:
-        project.plugins.apply(CppExeConventionPlugin)
-
-        expect:
-        def executable = project.executables.main
-        def sourceSet = project.cpp.sourceSets.main
-        executable.spec.baseName == project.name
-        executable.sourceSets as List == [sourceSet]
-    }
-}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppLibConventionPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppLibConventionPluginTest.groovy
deleted file mode 100644
index 76105de..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppLibConventionPluginTest.groovy
+++ /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.plugins.cpp
-
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class CppLibConventionPluginTest extends Specification {
-    final def project = HelperUtil.createRootProject()
-
-    def "adds and configures main library"() {
-        given:
-        project.plugins.apply(CppLibConventionPlugin)
-
-        expect:
-        def library = project.libraries.main
-        def sourceSet = project.cpp.sourceSets.main
-        library.spec.baseName == project.name
-        library.sourceSets as List == [sourceSet]
-    }
-}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy
deleted file mode 100644
index c2282e7..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy
+++ /dev/null
@@ -1,235 +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.cpp
-
-import spock.lang.Specification
-import org.gradle.util.HelperUtil
-import org.gradle.plugins.cpp.gpp.GppCompileSpec
-import org.gradle.plugins.cpp.gpp.GppLibraryCompileSpec
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import org.gradle.api.NamedDomainObjectContainer
-import org.gradle.api.tasks.Sync
-import org.gradle.util.Matchers
-
-class CppPluginTest extends Specification {
-    final def project = HelperUtil.createRootProject()
-
-    def "extensions are available"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        expect:
-        project.cpp instanceof CppExtension
-        project.executables instanceof NamedDomainObjectContainer
-        project.libraries instanceof NamedDomainObjectContainer
-    }
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "gcc and visual cpp adapters are available on windows"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        expect:
-        project.compilers.collect { it.name } == ['gpp', 'visualCpp']
-        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)
-
-        when:
-        project.cpp {
-            sourceSets {
-                s1 {}
-                s2 {}
-            }
-        }
-
-        then:
-        def sourceSets = project.cpp.sourceSets
-        sourceSets.size() == 2
-        sourceSets*.name == ["s1", "s2"]
-        sourceSets.s1 instanceof CppSourceSet
-    }
-
-    def "configure source sets"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.cpp {
-            sourceSets {
-                ss1 {
-                    source {
-                        srcDirs "d1", "d2"
-                    }
-                    exportedHeaders {
-                        srcDirs "h1", "h2"
-                    }
-                }
-                ss2 {
-                    source {
-                        srcDirs "d3"
-                    }
-                    exportedHeaders {
-                        srcDirs "h3"
-                    }
-                }
-            }
-        }
-
-        then:
-        def sourceSets = project.cpp.sourceSets
-        def ss1 = sourceSets.ss1
-        def ss2 = sourceSets.ss2
-
-        // cpp dir automatically added by convention
-        ss1.source.srcDirs*.name == ["cpp", "d1", "d2"]
-        ss2.source.srcDirs*.name == ["cpp", "d3"]
-
-        // headers dir automatically added by convention
-        ss1.exportedHeaders.srcDirs*.name == ["headers", "h1", "h2"]
-        ss2.exportedHeaders.srcDirs*.name == ["headers", "h3"]
-    }
-
-    @Requires(TestPrecondition.UNIX)
-    def "creates domain objects for executable on unix"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.executables {
-            test
-        }
-
-        then:
-        def executable = project.executables.test
-        executable.spec instanceof GppCompileSpec
-        executable.spec.outputFile == project.file("build/binaries/test")
-    }
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "creates domain objects for executable on windows"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.executables {
-            test
-        }
-
-        then:
-        def executable = project.executables.test
-        executable.spec instanceof GppCompileSpec
-        executable.spec.outputFile == project.file("build/binaries/test.exe")
-    }
-
-    def "creates tasks for each executable"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.executables {
-            test
-        }
-
-        then:
-        def compile = project.tasks['compileTest']
-        compile instanceof CppCompile
-        compile.spec == project.executables.test.spec
-
-        def install = project.tasks['installTest']
-        install instanceof Sync
-        install.destinationDir == project.file('build/install/test')
-        install Matchers.dependsOn("compileTest")
-    }
-
-    @Requires(TestPrecondition.MAC_OS_X)
-    def "creates domain objects for library on os x"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.libraries {
-            test
-        }
-
-        then:
-        def lib = project.libraries.test
-        lib.spec instanceof GppLibraryCompileSpec
-        lib.spec.outputFile == project.file("build/binaries/libtest.dylib")
-    }
-
-    @Requires(TestPrecondition.LINUX)
-    def "creates domain objects for library on linux"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.libraries {
-            test
-        }
-
-        then:
-        def lib = project.libraries.test
-        lib.spec instanceof GppLibraryCompileSpec
-        lib.spec.outputFile == project.file("build/binaries/libtest.so")
-    }
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "creates domain objects for library on windows"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.libraries {
-            test
-        }
-
-        then:
-        def lib = project.libraries.test
-        lib.spec instanceof GppLibraryCompileSpec
-        lib.spec.outputFile == project.file("build/binaries/test.dll")
-    }
-
-    def "creates tasks for each library"() {
-        given:
-        project.plugins.apply(CppPlugin)
-
-        when:
-        project.libraries {
-            test
-        }
-
-        then:
-        def compile = project.tasks['compileTest']
-        compile instanceof CppCompile
-        compile.spec == project.libraries.test.spec
-    }
-}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettingsSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettingsSpec.groovy
deleted file mode 100644
index 3811190..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettingsSpec.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.plugins.cpp.cdt.model
-
-import spock.lang.*
-
-import org.gradle.api.Project
-import org.gradle.util.HelperUtil
-
-// very loose test, but I'm not expecting it to stay around
- at Ignore
-class CprojectSettingsSpec extends Specification {
-
-    Project project = HelperUtil.createRootProject()
-
-    def descriptor = new CprojectDescriptor()
-    def settings = new CprojectSettings()
-
-    def "wire in includes"() {
-        given:
-        project.apply plugin: 'cpp-exe'
-        settings.binary = project.executables.main
-        descriptor.loadDefaults()
-
-        expect:
-        descriptor.getRootCppCompilerTools().each { compiler ->
-            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
-            assert includePathsOption.listOptionValue.size() == 0
-        }
-
-        when:
-        settings.applyTo(descriptor)
-        def baos = new ByteArrayOutputStream()
-        descriptor.store(baos)
-        descriptor.load(new ByteArrayInputStream(baos.toByteArray()))
-
-        then:
-        descriptor.getRootCppCompilerTools().each { compiler ->
-            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
-            assert includePathsOption.listOptionValue.size() == 1
-        }
-    }
-
-
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
deleted file mode 100644
index 7341abc..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.cpp.cdt.model
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-class ProjectDescriptorSpec extends Specification {
-
-    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    ProjectDescriptor descriptor = new ProjectDescriptor()
-
-    def "method"() {
-        given:
-        descriptor.loadDefaults()
-
-        when:
-        new ProjectSettings(name: "test").applyTo(descriptor)
-
-        then:
-        def dict = xml.buildSpec[0].buildCommand[0].arguments[0].dictionary.key.find { it.text() == "org.eclipse.cdt.make.core.buildLocation" }.parent()
-        dict.value[0].text() == "\${workspace_loc:/test/Debug}"
-    }
-
-    def getString() {
-        def baos = new ByteArrayOutputStream()
-        descriptor.store(baos)
-        baos.toString()
-    }
-    
-    def getXml() {
-        new XmlParser().parseText(getString())
-    }
-}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpecTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpecTest.groovy
deleted file mode 100644
index 1ed649c..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpecTest.groovy
+++ /dev/null
@@ -1,40 +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.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.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()
-    
-    def "is built by the compile task"() {
-        given:
-        def binary = new DefaultBinary("binary", project, Mock(CompileSpecFactory))
-        def spec = new GppCompileSpec(binary, Mock(Compiler), project)
-        def compileTask = project.tasks.add("compile", CppCompile)
-        spec.configure(compileTask)
-
-        expect:
-        spec.buildDependencies.getDependencies(null) == [compileTask] as Set
-    }
-}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpecTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpecTest.groovy
deleted file mode 100644
index 4c31e63..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpecTest.groovy
+++ /dev/null
@@ -1,37 +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.cpp.gpp
-
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.tasks.compile.Compiler
-import org.gradle.plugins.binaries.model.internal.CompileSpecFactory
-import org.gradle.plugins.binaries.model.internal.DefaultLibrary
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class GppLibraryCompileSpecTest extends Specification {
-    final ProjectInternal project = HelperUtil.createRootProject()
-
-    def "has default installPath"() {
-        given:
-        def library = new DefaultLibrary("binary", project, Mock(CompileSpecFactory))
-        def spec = new GppLibraryCompileSpec(library, Mock(Compiler), project)
-
-        expect:
-        spec.installName == spec.outputFileName
-    }
-}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminerTest.groovy
deleted file mode 100644
index 028c8b1..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminerTest.groovy
+++ /dev/null
@@ -1,142 +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.cpp.gpp.internal.version
-
-import org.gradle.api.Transformer
-import org.gradle.internal.Factory
-import org.gradle.process.internal.ExecHandleBuilder
-import spock.lang.Specification
-import spock.lang.Unroll
-import org.gradle.process.internal.ExecHandle
-import org.gradle.process.ExecResult
-
-class GppVersionDeterminerTest extends Specification {
-
-    @Unroll
-    "can scrape ok output"() {
-        expect:
-        version == output(output)
-
-        where:
-        [version, output] << OUTPUTS.collect { [it.value, it.key] }
-    }
-
-    def "null output (errored execution) ok"() {
-        expect:
-        output(null) == null
-    }
-
-    def "null scraped ok (can't parse output)"() {
-        expect:
-        scraped(null) == null
-    }
-
-    def "g++ -v execution error ok"() {
-        given:
-        def builder = Mock(ExecHandleBuilder)
-        def handle = Mock(ExecHandle)
-        def result = Mock(ExecResult)
-
-        and:
-        def determiner = new GppVersionDeterminer(producer(builder), new GppVersionDeterminer.GppVersionOutputScraper())
-        def binary = new File("g++")
-        
-        when:
-        String version = determiner.transform(binary)
-        
-        then:
-        1 * builder.build() >> handle
-        1 * handle.start() >> handle
-        1 * handle.waitForFinish() >> result
-        1 * result.getExitValue() >> 1
-
-        and:
-        version == null
-    }
-
-    def "happy day case"() {
-        given:
-        def builder = Mock(ExecHandleBuilder)
-        def handle = Mock(ExecHandle)
-        def result = Mock(ExecResult)
-        def output = """Reading specs from /opt/gcc/3.4.6/usr/local/bin/../lib/gcc/i686-pc-linux-gnu/3.4.6/specs
-Configured with: /home/ld/Downloads/gcc-3.4.6/configure
-Thread model: posix
-gcc version 3.4.6"""
-
-        and:
-        def determiner = new GppVersionDeterminer(producer(builder), new GppVersionDeterminer.GppVersionOutputScraper())
-        def binary = new File("g++")
-
-        when:
-        String version = determiner.transform(binary)
-
-        then:
-        1 * builder.build() >> handle
-        1 * builder.setErrorOutput(_) >> { OutputStream out -> out << output; builder }
-        1 * handle.start() >> handle
-        1 * handle.waitForFinish() >> result
-        1 * result.getExitValue() >> 0
-
-        and:
-        version == "3.4.6"
-    }
-
-    Transformer<String, File> producer(ExecHandleBuilder builder) {
-        new GppVersionDeterminer.GppVersionOutputProducer(new Factory() {
-            def create() {
-                builder
-            }
-        })
-    }
-
-    String output(String output) {
-        new GppVersionDeterminer(transformer(output), new GppVersionDeterminer.GppVersionOutputScraper()).transform(new File("."))
-    }
-
-    String scraped(String scraped) {
-        new GppVersionDeterminer(transformer("doesntmatter"), transformer(scraped)).transform(new File("."))
-    }
-
-    Transformer transformer(constant) {
-        transformer { constant }
-    }
-
-    Transformer transformer(Closure closure) {
-        new Transformer() {
-            String transform(original) {
-                closure.call(original)
-            }
-        }
-    }
-
-    static final OUTPUTS = [
-            """Using built-in specs.
-Target: i686-apple-darwin11
-Configured with: /private/var/tmp/llvmgcc42/llvmgcc42-2336.1~22/src/configure
-Thread model: posix
-gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)""": "4.2.1",
-            """Reading specs from /opt/gcc/3.4.6/usr/local/bin/../lib/gcc/i686-pc-linux-gnu/3.4.6/specs
-Configured with: /home/ld/Downloads/gcc-3.4.6/configure
-Thread model: posix
-gcc version 3.4.6""": "3.4.6",
-            """Reading specs from /opt/gcc/3.4.6/usr/local/bin/../lib/gcc/i686-pc-linux-gnu/3.4.6/specs
-Configured with: /home/ld/Downloads/gcc-3.4.6/configure
-Thread model: posix
-gcc version 3.4.6-sometag""": "3.4.6-sometag"
-    ]
-}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/FiltersFile.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/FiltersFile.groovy
new file mode 100644
index 0000000..f86aeae
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/FiltersFile.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.fixtures
+import org.gradle.test.fixtures.file.TestFile
+
+class FiltersFile {
+    TestFile file
+    Node xml
+
+    FiltersFile(TestFile filtersFile) {
+        assert filtersFile.exists()
+        this.file = filtersFile
+        this.xml = new XmlParser().parse(filtersFile)
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/ProjectFile.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/ProjectFile.groovy
new file mode 100644
index 0000000..86d36be
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/ProjectFile.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.fixtures
+
+import org.gradle.nativebinaries.language.cpp.fixtures.app.SourceFile
+import org.gradle.nativebinaries.language.cpp.fixtures.app.TestComponent
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.TextUtil
+
+class ProjectFile {
+    String name
+    TestFile projectFile
+    Node projectXml
+
+    ProjectFile(TestFile projectFile) {
+        assert projectFile.exists()
+        this.projectFile = projectFile
+        this.name = projectFile.name.replace(".vcxproj", "")
+        this.projectXml = new XmlParser().parse(projectFile)
+    }
+
+    public Map<String, Configuration> getProjectConfigurations() {
+        def configs = itemGroup("ProjectConfigurations").collect {
+            new Configuration(it.Configuration[0].text(), it.Platform[0].text())
+        }
+        return configs.collectEntries {
+            [it.name, it]
+        }
+    }
+
+    public String getProjectGuid() {
+        return globals.ProjectGUID[0].text()
+    }
+
+    public Node getGlobals() {
+        return projectXml.PropertyGroup.find({it.'@Label' == 'Globals'}) as Node
+    }
+
+    public List<String> getSourceFiles() {
+        def sources = itemGroup('Sources').ClCompile
+        return normalise(sources*.'@Include')
+    }
+
+    public List<String> getResourceFiles() {
+        def sources = itemGroup('References').ResourceCompile
+        return normalise(sources*.'@Include')
+    }
+
+    public List<String> getHeaderFiles() {
+        def sources = itemGroup('Headers').ClInclude
+        return normalise(sources*.'@Include')
+    }
+
+    private static List<String> normalise(List<String> files) {
+        return files.collect({ TextUtil.normaliseFileSeparators(it)}).sort()
+    }
+
+    private Node itemGroup(String label) {
+        return projectXml.ItemGroup.find({it.'@Label' == label}) as Node
+    }
+
+    class Configuration {
+        String name
+        String platformName
+
+        Configuration(String name, String platformName) {
+            this.name = name
+            this.platformName = platformName
+        }
+
+        ProjectFile getProject() {
+            return ProjectFile.this
+        }
+
+        String getMacros() {
+            buildConfiguration.NMakePreprocessorDefinitions[0].text()
+        }
+
+        String getIncludePath() {
+            TextUtil.normaliseFileSeparators(buildConfiguration.NMakeIncludeSearchPath[0].text())
+        }
+
+        String getBuildCommand() {
+            TextUtil.normaliseFileSeparators(buildConfiguration.NMakeBuildCommandLine[0].text())
+        }
+
+        String getOutputFile() {
+            TextUtil.normaliseFileSeparators(buildConfiguration.NMakeOutput[0].text())
+        }
+
+        private Node getBuildConfiguration() {
+            projectXml.PropertyGroup.find({ it.'@Label' == 'NMakeConfiguration' && it.'@Condition' == condition}) as Node
+        }
+
+        private String getCondition() {
+            "'\$(Configuration)|\$(Platform)'=='${name}|${platformName}'"
+        }
+    }
+
+    void assertHasComponentSources(TestComponent component, String basePath) {
+        assert sourceFiles == ['build.gradle'] + sourceFiles(component.sourceFiles, basePath)
+        assert headerFiles == sourceFiles(component.headerFiles, basePath)
+    }
+
+    void assertHasComponentSources(TestComponent component, String basePath, TestComponent component2, String basePath2) {
+        assert sourceFiles == ['build.gradle'] + sourceFiles(component.sourceFiles, basePath) + sourceFiles(component2.sourceFiles, basePath2)
+        assert headerFiles == sourceFiles(component.headerFiles, basePath) + sourceFiles(component2.headerFiles, basePath2)
+    }
+
+    private static List<String> sourceFiles(List<SourceFile> files, String path) {
+        return files*.withPath(path).sort()
+    }
+
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/SolutionFile.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/SolutionFile.groovy
new file mode 100644
index 0000000..2190c0a
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/ide/visualstudio/fixtures/SolutionFile.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.ide.visualstudio.fixtures
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.TextUtil
+
+class SolutionFile {
+    TestFile file
+    String content
+    Map<String, ProjectReference> projects = [:]
+
+    SolutionFile(TestFile solutionFile) {
+        assert solutionFile.exists()
+        this.file = solutionFile
+        assert TextUtil.convertLineSeparators(solutionFile.text, TextUtil.windowsLineSeparator) == solutionFile.text : "Solution file contains non-windows line separators"
+
+        content = TextUtil.normaliseLineSeparators(solutionFile.text)
+
+        content.findAll(~/(?m)^Project\(\"\{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942\}\"\) = \"(\w+)\", \"([^\"]*)\", \"\{([\w\-]+)\}\"$/, {
+            projects.put(it[1], new ProjectReference(it[1], it[2], it[3]))
+        })
+    }
+
+    def assertHasProjects(String... names) {
+        assert projects.keySet() == names as Set
+        return true
+    }
+
+    def assertReferencesProject(ProjectFile expectedProject, Collection<String> configurations) {
+        assertReferencesProject(expectedProject, configurations.collectEntries {[(it):it]})
+    }
+
+    def assertReferencesProject(ProjectFile expectedProject, Map<String, String> configurations) {
+        assertReferencesProject(expectedProject.name, expectedProject, configurations)
+    }
+
+    def assertReferencesProject(String projectName, ProjectFile expectedProject, Map<String, String> configurations) {
+        ProjectReference reference = projects.get(projectName)
+        assert reference.uuid == expectedProject.projectGuid
+        assert reference.file == expectedProject.projectFile.absolutePath
+        assert reference.configurations == configurations
+        return true
+    }
+
+    class ProjectReference {
+        final String name
+        final String file
+        final String rawUuid
+
+        ProjectReference(String name, String file, String rawUuid) {
+            this.name = name
+            this.file = file
+            this.rawUuid = rawUuid
+        }
+
+        String getUuid() {
+            return '{' + rawUuid + '}'
+        }
+
+        Map<String, String> getConfigurations() {
+            def configurations = [:]
+            content.eachMatch(~/\{${rawUuid}\}\.(\w+)\|\w+\.ActiveCfg = (\w+)\|\w+/, {
+                configurations[it[1]] = it[2]
+            })
+            return configurations
+        }
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/AvailableToolChains.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/AvailableToolChains.java
new file mode 100755
index 0000000..d7bec41
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/AvailableToolChains.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures;
+
+import com.google.common.base.Joiner;
+import net.rubygrapefruit.platform.SystemInfo;
+import net.rubygrapefruit.platform.WindowsRegistry;
+import org.gradle.api.internal.file.TestFiles;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.nativebinaries.platform.internal.DefaultPlatform;
+import org.gradle.nativebinaries.toolchain.Clang;
+import org.gradle.nativebinaries.toolchain.Gcc;
+import org.gradle.nativebinaries.toolchain.VisualCpp;
+import org.gradle.nativebinaries.toolchain.internal.gcc.version.GccVersionDeterminer;
+import org.gradle.nativebinaries.toolchain.internal.gcc.version.GccVersionResult;
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.DefaultVisualStudioLocator;
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.VisualStudioInstall;
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.VisualStudioLocator;
+import org.gradle.nativebinaries.toolchain.plugins.ClangCompilerPlugin;
+import org.gradle.nativebinaries.toolchain.plugins.GccCompilerPlugin;
+import org.gradle.nativebinaries.toolchain.plugins.MicrosoftVisualCppPlugin;
+import org.gradle.process.internal.DefaultExecAction;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecActionFactory;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.util.VersionNumber;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class AvailableToolChains {
+    /**
+     * @return A list of all tool chains installed on the system, with the default tool chain listed first (if installed).
+     */
+    public static List<InstalledToolChain> getAvailableToolChains() {
+        List<ToolChainCandidate> allToolChains = getToolChains();
+        List<InstalledToolChain> installedToolChains = new ArrayList<InstalledToolChain>();
+        for (ToolChainCandidate candidate : allToolChains) {
+            if (candidate.isAvailable()) {
+                installedToolChains.add((InstalledToolChain) candidate);
+            }
+        }
+        return installedToolChains;
+    }
+
+    /**
+     * @return The tool chain with the given name.
+     */
+    public static ToolChainCandidate getToolChain(ToolChainRequirement requirement) {
+        for (ToolChainCandidate toolChainCandidate : getToolChains()) {
+            if (toolChainCandidate.meets(requirement)) {
+                return toolChainCandidate;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return A list of all tool chains for this platform, with the default tool chain listed first.
+     */
+    public static List<ToolChainCandidate> getToolChains() {
+        List<ToolChainCandidate> compilers = new ArrayList<ToolChainCandidate>();
+        if (OperatingSystem.current().isWindows()) {
+            compilers.add(findVisualCpp());
+            compilers.add(findMinGW());
+            compilers.add(findCygwin());
+        } else {
+            // GCC4.x must be on the path
+            compilers.add(findGcc("4", null));
+
+            // Clang must be on the path
+            // TODO:ADAM Also check on windows
+            compilers.add(findClang());
+
+            // TODO:DAZ Make a GCC3 install available for testing
+        }
+        return compilers;
+    }
+
+    static private ToolChainCandidate findClang() {
+        File compilerExe = OperatingSystem.current().findInPath("clang");
+        if (compilerExe != null) {
+            return new InstalledClang();
+        }
+        return new UnavailableToolChain("clang");
+    }
+
+    static private ToolChainCandidate findVisualCpp() {
+        // Search in the standard installation locations
+        VisualStudioLocator vsLocator = new DefaultVisualStudioLocator(OperatingSystem.current(), NativeServices.getInstance().get(WindowsRegistry.class), NativeServices.getInstance().get(SystemInfo.class));
+        VisualStudioLocator.SearchResult searchResult = vsLocator.locateVisualStudioInstalls(null);
+        if (searchResult.isAvailable()) {
+            VisualStudioInstall install = searchResult.getVisualStudio();
+            return new InstalledVisualCpp("visual c++").withInstall(install);
+        }
+
+        return new UnavailableToolChain("visual c++");
+    }
+
+    static private ToolChainCandidate findMinGW() {
+        // Search in the standard installation locations
+        File compilerExe = new File("C:/MinGW/bin/g++.exe");
+        if (compilerExe.isFile()) {
+            return new InstalledWindowsGcc("mingw").inPath(compilerExe.getParentFile());
+        }
+
+        return new UnavailableToolChain("mingw");
+    }
+
+    static private ToolChainCandidate findCygwin() {
+        // Search in the standard installation locations
+        File compilerExe = new File("C:/cygwin/bin/g++.exe");
+        if (compilerExe.isFile()) {
+            return new InstalledWindowsGcc("gcc cygwin").inPath(compilerExe.getParentFile());
+        }
+
+        return new UnavailableToolChain("gcc cygwin");
+    }
+
+    static private ToolChainCandidate findGcc(String versionPrefix, String hardcodedFallback) {
+        String name = String.format("gcc %s", versionPrefix);
+        GccVersionDeterminer versionDeterminer = new GccVersionDeterminer(new ExecActionFactory() {
+            public ExecAction newExecAction() {
+                return new DefaultExecAction(TestFiles.resolver());
+            }
+        });
+
+        List<File> gppCandidates = OperatingSystem.current().findAllInPath("g++");
+        for (int i = 0; i < gppCandidates.size(); i++) {
+            File candidate = gppCandidates.get(i);
+            GccVersionResult version = versionDeterminer.transform(candidate);
+            if (version.isAvailable() && version.getVersion().startsWith(versionPrefix)) {
+                InstalledGcc gcc = new InstalledGcc(name);
+                if (i > 0) {
+                    // Not the first g++ in the path, needs the path variable updated
+                    gcc.inPath(candidate.getParentFile());
+                }
+                return gcc;
+            }
+        }
+
+        if (hardcodedFallback != null) {
+            File fallback = new File(hardcodedFallback);
+            if (fallback.isFile()) {
+                return new InstalledGcc(name).inPath(fallback.getParentFile());
+            }
+        }
+
+        return new UnavailableToolChain(name);
+    }
+
+    public static abstract class ToolChainCandidate {
+        @Override
+        public String toString() {
+            return getDisplayName();
+        }
+
+        public abstract String getDisplayName();
+
+        public abstract boolean isAvailable();
+
+        public abstract boolean meets(ToolChainRequirement requirement);
+
+        public abstract void initialiseEnvironment();
+
+        public abstract void resetEnvironment();
+
+   }
+    
+    public abstract static class InstalledToolChain extends ToolChainCandidate {
+        private static final ProcessEnvironment PROCESS_ENVIRONMENT = NativeServices.getInstance().get(ProcessEnvironment.class);
+        protected final List<File> pathEntries = new ArrayList<File>();
+        private final String displayName;
+        protected final String pathVarName;
+        private final String objectFileNameSuffix;
+
+        private String originalPath;
+
+        public InstalledToolChain(String displayName) {
+            this.displayName = displayName;
+            this.pathVarName = OperatingSystem.current().getPathVar();
+            this.objectFileNameSuffix = OperatingSystem.current().isWindows() ? ".obj" : ".o";
+        }
+
+        InstalledToolChain inPath(File... pathEntries) {
+            Collections.addAll(this.pathEntries, pathEntries);
+            return this;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return true;
+        }
+
+        public String getTypeDisplayName() {
+            return getDisplayName().replaceAll("\\s+\\d+(\\.\\d+)*$", "");
+        }
+
+        public abstract String getInstanceDisplayName();
+
+        public ExecutableFixture executable(Object path) {
+            return new ExecutableFixture(new TestFile(OperatingSystem.current().getExecutableName(path.toString())), this);
+        }
+
+        public TestFile objectFile(Object path) {
+            return new TestFile(path.toString() + objectFileNameSuffix);
+        }
+
+        public SharedLibraryFixture sharedLibrary(Object path) {
+            return new SharedLibraryFixture(new TestFile(OperatingSystem.current().getSharedLibraryName(path.toString())), this);
+        }
+
+        public StaticLibraryFixture staticLibrary(Object path) {
+            return new StaticLibraryFixture(new TestFile(OperatingSystem.current().getStaticLibraryName(path.toString())), this);
+        }
+
+        public NativeBinaryFixture resourceOnlyLibrary(Object path) {
+            return new NativeBinaryFixture(new TestFile(OperatingSystem.current().getSharedLibraryName(path.toString())), this);
+        }
+
+        /**
+         * Initialise the process environment so that this tool chain is visible to the default discovery mechanism that the
+         * plugin uses (eg add the compiler to the PATH).
+         */
+        public void initialiseEnvironment() {
+            String compilerPath = Joiner.on(File.pathSeparator).join(pathEntries);
+
+            if (compilerPath.length() > 0) {
+                originalPath = System.getenv(pathVarName);
+                String path = compilerPath + File.pathSeparator + originalPath;
+                System.out.println(String.format("Using path %s", path));
+                PROCESS_ENVIRONMENT.setEnvironmentVariable(pathVarName, path);
+            }
+        }
+
+        public void resetEnvironment() {
+            if (originalPath != null) {
+                PROCESS_ENVIRONMENT.setEnvironmentVariable(pathVarName, originalPath);
+            }
+        }
+
+        public abstract String getBuildScriptConfig();
+
+        public abstract String getImplementationClass();
+
+        public abstract String getPluginClass();
+
+        public boolean isVisualCpp() {
+            return false;
+        }
+
+        public List<File> getPathEntries() {
+            return pathEntries;
+        }
+
+        /**
+         * The environment required to execute a binary created by this toolchain.
+         */
+        public List<String> getRuntimeEnv() {
+            // Toolchains should be linking against stuff in the standard locations
+            return Collections.emptyList();
+        }
+
+        public String getId() {
+            return displayName.replaceAll("\\W", "");
+        }
+    }
+
+    public static abstract class GccCompatibleToolChain extends InstalledToolChain {
+        protected GccCompatibleToolChain(String displayName) {
+            super(displayName);
+        }
+
+        protected String find(String tool) {
+            if (getPathEntries().isEmpty()) {
+                return tool;
+            }
+            return new File(getPathEntries().get(0), tool).getAbsolutePath();
+        }
+
+        public String getLinker() {
+            return getCCompiler();
+        }
+
+        public String getStaticLibArchiver() {
+            return find("ar");
+        }
+
+        public abstract String getCCompiler();
+    }
+
+    public static class InstalledGcc extends GccCompatibleToolChain {
+        public InstalledGcc(String name) {
+            super(name);
+        }
+
+        @Override
+        public boolean meets(ToolChainRequirement requirement) {
+            return requirement == ToolChainRequirement.Gcc || requirement == ToolChainRequirement.GccCompatible || requirement == ToolChainRequirement.Available;
+        }
+
+        @Override
+        public String getBuildScriptConfig() {
+            String config = String.format("%s(%s)\n", getId(), getImplementationClass());
+            for (File pathEntry : getPathEntries()) {
+                config += String.format("%s.path file('%s')", getId(), pathEntry.toURI());
+            }
+            return config;
+        }
+
+        @Override
+        public String getCCompiler() {
+            return find("gcc");
+        }
+
+        public String getInstanceDisplayName() {
+            return String.format("Tool chain '%s' (GNU GCC)", getId());
+        }
+
+        public String getImplementationClass() {
+            return Gcc.class.getSimpleName();
+        }
+
+        @Override
+        public String getPluginClass() {
+            return GccCompilerPlugin.class.getSimpleName();
+        }
+    }
+
+    public static class InstalledWindowsGcc extends InstalledGcc {
+        public InstalledWindowsGcc(String name) {
+            super(name);
+        }
+
+        /**
+         * The environment required to execute a binary created by this toolchain.
+         */
+        public List<String> getRuntimeEnv() {
+            if (pathEntries.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            String path = Joiner.on(File.pathSeparator).join(pathEntries) + File.pathSeparator + System.getenv(pathVarName);
+            return Collections.singletonList(pathVarName + "=" + path);
+        }
+    }
+
+    public static class InstalledVisualCpp extends InstalledToolChain {
+        private VersionNumber version;
+        private File installDir;
+
+        public InstalledVisualCpp(String name) {
+            super(name);
+        }
+
+        public InstalledVisualCpp withInstall(VisualStudioInstall install) {
+            DefaultPlatform targetPlatform = new DefaultPlatform("default");
+            installDir = install.getVisualStudioDir();
+            version = install.getVersion();
+            pathEntries.addAll(install.getVisualCpp().getPath(targetPlatform));
+            return this;
+        }
+
+        @Override
+        public boolean meets(ToolChainRequirement requirement) {
+            switch (requirement) {
+                case Available:
+                case VisualCpp:
+                    return true;
+                case VisualCpp2013:
+                    return version.compareTo(VersionNumber.parse("12.0")) >= 0;
+                default:
+                    return false;
+            }
+        }
+
+        @Override
+        public String getBuildScriptConfig() {
+            String config = String.format("%s(%s)\n", getId(), getImplementationClass());
+            if (installDir != null) {
+                config += String.format("%s.installDir = file('%s')", getId(), installDir.toURI());
+            }
+            return config;
+        }
+
+        public String getImplementationClass() {
+            return VisualCpp.class.getSimpleName();
+        }
+
+        public String getInstanceDisplayName() {
+            return String.format("Tool chain '%s' (Visual Studio)", getId());
+        }
+
+        @Override
+        public String getPluginClass() {
+            return MicrosoftVisualCppPlugin.class.getSimpleName();
+        }
+
+        public boolean isVisualCpp() {
+            return true;
+        }
+
+        public VersionNumber getVersion() {
+            return version;
+        }
+
+        @Override
+        public TestFile objectFile(Object path) {
+            return new TestFile(path.toString() + ".obj");
+        }
+    }
+
+    public static class InstalledClang extends GccCompatibleToolChain {
+        public InstalledClang() {
+            super("clang");
+        }
+
+        @Override
+        public boolean meets(ToolChainRequirement requirement) {
+            return requirement == ToolChainRequirement.GccCompatible || requirement == ToolChainRequirement.Available;
+        }
+
+        @Override
+        public String getBuildScriptConfig() {
+            return "clang(Clang)";
+        }
+
+        @Override
+        public String getCCompiler() {
+            return find("clang");
+        }
+
+        public String getInstanceDisplayName() {
+            return String.format("Tool chain '%s' (Clang)", getId());
+        }
+
+        @Override
+        public String getImplementationClass() {
+            return Clang.class.getSimpleName();
+        }
+
+        @Override
+        public String getPluginClass() {
+            return ClangCompilerPlugin.class.getSimpleName();
+        }
+    }
+
+    public static class UnavailableToolChain extends ToolChainCandidate {
+        private final String name;
+
+        public UnavailableToolChain(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public boolean meets(ToolChainRequirement requirement) {
+            return false;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return name;
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return false;
+        }
+
+        @Override
+        public void initialiseEnvironment() {
+            throw new UnsupportedOperationException("Toolchain is not available");
+        }
+
+        @Override
+        public void resetEnvironment() {
+            throw new UnsupportedOperationException("Toolchain is not available");
+        }
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/ExecutableFixture.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/ExecutableFixture.groovy
new file mode 100644
index 0000000..530bb92
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/ExecutableFixture.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.gradle.test.fixtures.file.ExecOutput
+import org.gradle.test.fixtures.file.TestFile
+
+class ExecutableFixture extends NativeBinaryFixture {
+    ExecutableFixture(TestFile file, AvailableToolChains.InstalledToolChain toolChain) {
+        super(file, toolChain)
+    }
+
+    public ExecOutput exec(Object... args) {
+        assertExists()
+        return file.execute(args as List, toolChain.runtimeEnv)
+    }
+
+    public List<String> listLinkedLibraries() {
+        return binaryInfo.listLinkedLibraries()
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/NativeBinaryFixture.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/NativeBinaryFixture.groovy
new file mode 100644
index 0000000..a149496
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/NativeBinaryFixture.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.BinaryInfo
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.DumpbinBinaryInfo
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.OtoolBinaryInfo
+import org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo.ReadelfBinaryInfo
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestFile.Snapshot
+
+class NativeBinaryFixture {
+    final TestFile file
+    protected final AvailableToolChains.InstalledToolChain toolChain
+
+    NativeBinaryFixture(TestFile file, AvailableToolChains.InstalledToolChain toolChain) {
+        this.file = file
+        this.toolChain = toolChain
+    }
+
+    URI toURI() {
+        file.toURI()
+    }
+
+    Snapshot snapshot() {
+        file.snapshot()
+    }
+
+    void assertHasChangedSince(Snapshot snapshot) {
+        file.assertContentsHaveChangedSince(snapshot)
+    }
+
+    void assertExists() {
+        file.assertIsFile()
+    }
+
+    void assertDoesNotExist() {
+        file.assertDoesNotExist()
+    }
+
+    // Does nothing when tool chain does not generate a separate debug file
+    void assertDebugFileExists() {
+        if (toolChain.visualCpp) {
+            getDebugFile().assertIsFile()
+        }
+    }
+
+    // Does nothing when tool chain does not generate a separate debug file
+    void assertDebugFileDoesNotExist() {
+        if (toolChain.visualCpp) {
+            getDebugFile().assertDoesNotExist()
+        }
+    }
+
+    private TestFile getDebugFile() {
+        return file.withExtension("pdb")
+    }
+
+    boolean assertExistsAndDelete() {
+        assertExists()
+        file.delete()
+    }
+
+    BinaryInfo getBinaryInfo() {
+        file.assertExists()
+        if (OperatingSystem.current().isMacOsX()) {
+            return new OtoolBinaryInfo(file);
+        }
+        if (OperatingSystem.current().isWindows()) {
+            return new DumpbinBinaryInfo(file, toolChain);
+        }
+        return new ReadelfBinaryInfo(file);
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/NativeInstallationFixture.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/NativeInstallationFixture.groovy
new file mode 100644
index 0000000..315c4cb
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/NativeInstallationFixture.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.ExecOutput
+import org.gradle.test.fixtures.file.TestFile
+
+class NativeInstallationFixture {
+    private final TestFile installDir
+    private final OperatingSystem os = OperatingSystem.current()
+
+    NativeInstallationFixture(TestFile installDir) {
+        this.installDir = installDir
+    }
+
+    ExecOutput exec(Object... args) {
+        assertInstalled()
+        return scriptFile().exec(args)
+    }
+
+    private TestFile scriptFile() {
+        if (os.windows) {
+            return installDir.listFiles().find { it.file && it.name.endsWith(".bat") }
+        } else {
+            return installDir.listFiles().find { it.file }
+        }
+    }
+
+    NativeInstallationFixture assertInstalled() {
+        installDir.assertIsDir()
+        final script = scriptFile()
+        assert script
+
+        def libDir = installDir.file("lib")
+        libDir.assertIsDir()
+        libDir.file(os.getExecutableName(script.name)).assertIsFile()
+        this
+    }
+
+    NativeInstallationFixture assertIncludesLibraries(String... names) {
+        def expected = names.collect { os.getSharedLibraryName(it) } as Set
+        assert libraryFiles.collect { it.name } as Set == expected as Set
+        this
+    }
+
+    private ArrayList<TestFile> getLibraryFiles() {
+        installDir.assertIsDir()
+        def libDir = installDir.file("lib")
+        libDir.assertIsDir()
+        def libFiles
+        if (os.windows) {
+            libFiles = libDir.listFiles().findAll { it.file && !it.name.endsWith(".exe") }
+        } else {
+            libFiles = libDir.listFiles().findAll { it.file && it.name.contains(".") }
+        }
+        libFiles
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/RequiresInstalledToolChain.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/RequiresInstalledToolChain.groovy
new file mode 100644
index 0000000..ada654d
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/RequiresInstalledToolChain.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.spockframework.runtime.extension.ExtensionAnnotation
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target([ElementType.METHOD, ElementType.TYPE])
+ at ExtensionAnnotation(RequiresInstalledToolChainExtension.class)
+public @interface RequiresInstalledToolChain {
+    ToolChainRequirement value() default ToolChainRequirement.Available;
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/RequiresInstalledToolChainExtension.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/RequiresInstalledToolChainExtension.groovy
new file mode 100644
index 0000000..92567b2
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/RequiresInstalledToolChainExtension.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
+import org.spockframework.runtime.model.FeatureInfo
+import org.spockframework.runtime.model.SpecInfo
+
+class RequiresInstalledToolChainExtension extends AbstractAnnotationDrivenExtension<RequiresInstalledToolChain> {
+    @Override
+    void visitSpecAnnotation(RequiresInstalledToolChain annotation, SpecInfo spec) {
+        final available = isToolChainAvailable(annotation)
+        spec.skipped |= !available
+    }
+
+    @Override
+    void visitFeatureAnnotation(RequiresInstalledToolChain annotation, FeatureInfo feature) {
+        final available = isToolChainAvailable(annotation)
+        feature.skipped |= !available
+    }
+
+    private static boolean isToolChainAvailable(RequiresInstalledToolChain annotation) {
+        def requiredToolChain = AvailableToolChains.getToolChain(annotation.value())
+        return requiredToolChain != null
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/SharedLibraryFixture.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/SharedLibraryFixture.groovy
new file mode 100644
index 0000000..05a70c5
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/SharedLibraryFixture.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.gradle.test.fixtures.file.TestFile
+
+class SharedLibraryFixture extends NativeBinaryFixture {
+    SharedLibraryFixture(TestFile file, AvailableToolChains.InstalledToolChain toolChain) {
+        super(file, toolChain)
+    }
+
+    @Override
+    void assertExists() {
+        super.assertExists()
+        if (toolChain.visualCpp) {
+            file.withExtension("lib").assertIsFile()
+            file.withExtension("exp").assertIsFile()
+        }
+    }
+
+    @Override
+    void assertDoesNotExist() {
+        super.assertDoesNotExist()
+        if (toolChain.visualCpp) {
+            file.withExtension("lib").assertDoesNotExist()
+            file.withExtension("exp").assertDoesNotExist()
+        }
+    }
+
+    String getSoName() {
+        return binaryInfo.soName
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/StaticLibraryFixture.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/StaticLibraryFixture.groovy
new file mode 100644
index 0000000..63fffa6
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/StaticLibraryFixture.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures
+
+import org.gradle.test.fixtures.file.TestFile
+
+class StaticLibraryFixture extends NativeBinaryFixture {
+    StaticLibraryFixture(TestFile file, AvailableToolChains.InstalledToolChain toolChain) {
+        super(file, toolChain)
+    }
+
+    List<String> listObjectFiles() {
+        getBinaryInfo().listObjectFiles()
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/ToolChainRequirement.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/ToolChainRequirement.java
new file mode 100644
index 0000000..5250199
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/ToolChainRequirement.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures;
+
+public enum ToolChainRequirement {
+    // Any available tool chain
+    Available,
+    // Any available Visual Studio implementation
+    VisualCpp,
+    // Any available Visual Studio >= 2013
+    VisualCpp2013,
+    // Any available GCC implementation (including mingw, cygwin, but not clang)
+    Gcc,
+    // Any available GCC compatible implementation (including mingw, cygwin, and clang)
+    GccCompatible
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CCallingMixedCAndCppHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CCallingMixedCAndCppHelloWorldApp.groovy
new file mode 100644
index 0000000..12a7d21
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CCallingMixedCAndCppHelloWorldApp.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app;
+
+public class CCallingMixedCAndCppHelloWorldApp extends HelloWorldApp {
+    @Override
+    List<String> getPluginList() {
+        return ['c', 'cpp']
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        sourceFile("c", "main.c", """
+                #include <stdio.h>
+                #include "hello.h"
+
+                int main () {
+                    sayHello();
+                    printf("%d", sum(5, 7));
+                    return 0;
+                }
+        """)
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "hello.h", """
+                #ifdef _WIN32
+                #define DLL_FUNC __declspec(dllexport)
+                #else
+                #define DLL_FUNC
+                #endif
+
+                #ifdef __cplusplus
+                extern "C" {
+                #endif
+
+                void DLL_FUNC sayHello();
+                int DLL_FUNC sum(int a, int b);
+
+                #ifdef __cplusplus
+                }
+                #endif
+        """)
+    }
+
+
+    List<SourceFile> librarySources = [
+        sourceFile("cpp", "hello.cpp", """
+            #include <iostream>
+            #include "hello.h"
+
+            void DLL_FUNC sayHello() {
+                #ifdef FRENCH
+                std::cout << "${HELLO_WORLD_FRENCH}" << std::endl;
+                #else
+                std::cout << "${HELLO_WORLD}" << std::endl;
+                #endif
+            }
+"""),
+        sourceFile("c", "sum.c", """
+            #include "hello.h"
+
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+""")
+    ]
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CCompilerDetectingTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CCompilerDetectingTestApp.groovy
new file mode 100644
index 0000000..480d68a
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CCompilerDetectingTestApp.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains.InstalledToolChain
+
+class CCompilerDetectingTestApp extends TestApp {
+    String expectedOutput(InstalledToolChain toolChain) {
+        "C ${toolChain.typeDisplayName}"
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "c-detector.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC detectCCompiler();
+        """);
+    }
+
+    @Override
+    List<SourceFile> getLibrarySources() {
+        return [
+            sourceFile("c", "c-detector.c", """
+                #include <stdio.h>
+                #include "c-detector.h"
+
+                void detectCCompiler() {
+                #if !defined(__cplusplus)
+                    printf("C ");
+                #endif
+                #if defined(__clang__)
+                    printf("clang");
+                #elif defined(__GNUC__) && defined(__MINGW32__)
+                    printf("mingw");
+                #elif defined(__GNUC__) && defined(__CYGWIN__)
+                    printf("gcc cygwin");
+                #elif defined(__GNUC__)
+                    printf("gcc");
+                #elif defined(_MSC_VER)
+                    printf("visual c++");
+                #else
+                    printf("unknown");
+                #endif
+                }
+        """)
+        ]
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        return new SourceFile("c", "main.c", """
+#include <stdio.h>
+#include "c-detector.h"
+
+int main () {
+    detectCCompiler();
+    return 0;
+}
+""")
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CHelloWorldApp.groovy
new file mode 100644
index 0000000..69d1582
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CHelloWorldApp.groovy
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class CHelloWorldApp extends IncrementalHelloWorldApp {
+
+    @Override
+    SourceFile getMainSource() {
+        sourceFile("c", "main.c", """
+            // Simple hello world app
+            #include <stdio.h>
+            #include "hello.h"
+
+            int main () {
+                sayHello();
+                printf("%d", sum(5, 7));
+                return 0;
+            }
+        """);
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "hello.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC sayHello();
+            int DLL_FUNC sum(int a, int b);
+        """);
+    }
+
+    List<SourceFile> librarySources = [
+        sourceFile("c", "hello.c", """
+            #include <stdio.h>
+            #include "hello.h"
+
+            #ifdef FRENCH
+            char* greeting() {
+                return "${HELLO_WORLD_FRENCH}";
+            }
+            #endif
+            #ifdef CUSTOM
+            char* greeting() {
+                return CUSTOM;
+            }
+            #endif
+            void DLL_FUNC sayHello() {
+                #if defined(FRENCH) || defined(CUSTOM)
+                printf("%s\\n", greeting());
+                #else
+                printf("${HELLO_WORLD}\\n");
+                #endif
+                fflush(stdout);
+            }
+        """),
+        sourceFile("c", "sum.c","""
+            #include "hello.h"
+
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+        """)
+    ]
+
+    SourceFile getAlternateMainSource() {
+        sourceFile("c", "main.c", """
+            #include "hello.h"
+
+            int main () {
+              sayHello();
+              printf("goodbye");
+              return 0;
+            }
+        """)
+    }
+
+    String alternateOutput = "$HELLO_WORLD\ngoodbye"
+
+    List<SourceFile> alternateLibrarySources = [
+            sourceFile("c", "hello.c", """
+                #include <stdio.h>
+                #include "hello.h"
+
+                void DLL_FUNC sayHello() {
+                    printf("[${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}]\\n");
+                    fflush(stdout);
+                }
+
+                // Extra function to ensure library has different size
+                int anotherFunction() {
+                    return 1000;
+                }
+            """),
+            sourceFile("c", "sum.c","""
+                #include "hello.h"
+
+                int DLL_FUNC sum(int a, int b) {
+                    return a + b;
+                }
+            """)
+    ]
+
+    String alternateLibraryOutput = "[${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}]\n12"
+
+    TestComponent getCunitTests() {
+        return new TestComponent() {
+            List<SourceFile> sourceFiles = [
+                    sourceFile("cunit", "test.c", """
+#include <CUnit/Basic.h>
+#include "hello.h"
+#include "gradle_cunit_register.h"
+
+int init_test(void) {
+    return 0;
+}
+
+int clean_test(void) {
+    return 0;
+}
+
+void test_sum(void) {
+  CU_ASSERT(sum(0, 2) == 2);
+#ifndef ONE_TEST
+  CU_ASSERT(sum(0, -2) == -2);
+  CU_ASSERT(sum(2, 2) == 4);
+#endif
+}
+
+void gradle_cunit_register() {
+    CU_pSuite pSuiteMath = CU_add_suite("hello test", init_test, clean_test);
+    CU_add_test(pSuiteMath, "test_sum", test_sum);
+}
+                    """),
+            ]
+            List<SourceFile> headerFiles = [
+            ]
+
+            String testOutput = """
+Suite: hello test
+  Test: test of sum ...passed
+
+Run Summary:    Type  Total    Ran Passed Failed Inactive
+              suites      1      1    n/a      0        0
+               tests      1      1      1      0        0
+             asserts      3      3      3      0      n/a
+"""
+        };
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppCallingCHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppCallingCHelloWorldApp.groovy
new file mode 100644
index 0000000..41a1cd2
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppCallingCHelloWorldApp.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app;
+
+public class CppCallingCHelloWorldApp extends HelloWorldApp {
+    @Override
+    List<String> getPluginList() {
+        return ['c', 'cpp']
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        sourceFile("cpp", "main.cpp", """
+            #include <iostream>
+            extern "C" {
+                #include "hello.h"
+            }
+
+            int main () {
+              sayHello();
+              std::cout << sum(5, 7);
+              return 0;
+            }
+        """)
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "hello.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC sayHello();
+            int DLL_FUNC sum(int a, int b);
+        """);
+    }
+
+    List<SourceFile> librarySources = [
+        sourceFile("c", "hello.c", """
+            #include <stdio.h>
+            #include "hello.h"
+
+            void DLL_FUNC sayHello() {
+                #ifdef FRENCH
+                printf("${HELLO_WORLD_FRENCH}\\n");
+                #else
+                printf("${HELLO_WORLD}\\n");
+                #endif
+                fflush(stdout);
+            }
+
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+        """)
+    ]
+
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppCompilerDetectingTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppCompilerDetectingTestApp.groovy
new file mode 100644
index 0000000..01fd267
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppCompilerDetectingTestApp.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains.InstalledToolChain
+
+class CppCompilerDetectingTestApp extends TestApp {
+    String expectedOutput(InstalledToolChain toolChain) {
+        "C++ ${toolChain.typeDisplayName}"
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "cpp-detector.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC detectCppCompiler();
+        """);
+    }
+
+    @Override
+    List<SourceFile> getLibrarySources() {
+        return [
+            sourceFile("cpp", "cpp-detector.cpp", """
+                #include <stdio.h>
+                #include "cpp-detector.h"
+
+                void detectCppCompiler() {
+                #if defined(__cplusplus)
+                    printf("C++ ");
+                #endif
+                #if defined(__clang__)
+                    printf("clang");
+                #elif defined(__GNUC__) && defined(__MINGW32__)
+                    printf("mingw");
+                #elif defined(__GNUC__) && defined(__CYGWIN__)
+                    printf("gcc cygwin");
+                #elif defined(__GNUC__)
+                    printf("gcc");
+                #elif defined(_MSC_VER)
+                    printf("visual c++");
+                #else
+                    printf("unknown");
+                #endif
+                }
+        """)
+        ]
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        return new SourceFile("cpp", "main.cpp", """
+#include <stdio.h>
+#include "cpp-detector.h"
+
+int main () {
+    detectCppCompiler();
+    return 0;
+}
+""")
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppHelloWorldApp.groovy
new file mode 100644
index 0000000..c0398a4
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/CppHelloWorldApp.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class CppHelloWorldApp extends IncrementalHelloWorldApp {
+    @Override
+    SourceFile getMainSource() {
+        return sourceFile("cpp", "main.cpp", """
+            // Simple hello world app
+            #include <iostream>
+            #include "hello.h"
+
+            int main () {
+              sayHello();
+              std::cout << sum(5, 7);
+              return 0;
+            }
+        """);
+    }
+
+    SourceFile getAlternateMainSource() {
+        sourceFile("cpp", "main.cpp", """
+            #include <iostream>
+            #include "hello.h"
+
+            int main () {
+              sayHello();
+              return 0;
+            }
+        """)
+    }
+
+    String alternateOutput = "$HELLO_WORLD\n"
+
+    @Override
+    SourceFile getLibraryHeader() {
+        return sourceFile("headers", "hello.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC sayHello();
+            int DLL_FUNC sum(int a, int b);
+        """);
+    }
+
+    List<SourceFile> librarySources = [
+        sourceFile("cpp", "hello.cpp", """
+            #include <iostream>
+            #include "hello.h"
+
+            #ifdef FRENCH
+            const char* greeting() {
+                return "${HELLO_WORLD_FRENCH}";
+            }
+            #endif
+
+            void DLL_FUNC sayHello() {
+                #ifdef FRENCH
+                std::cout << greeting() << std::endl;
+                #else
+                std::cout << "${HELLO_WORLD}" << std::endl;
+                #endif
+            }
+        """),
+        sourceFile("cpp", "sum.cpp", """
+            #include "hello.h"
+
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+        """)
+    ]
+
+    List<SourceFile> alternateLibrarySources = [
+        sourceFile("cpp", "hello.cpp", """
+            #include <iostream>
+            #include "hello.h"
+
+            void DLL_FUNC sayHello() {
+                std::cout << "[${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}]" << std::endl;
+            }
+
+            // Extra function to ensure library has different size
+            int anotherFunction() {
+                return 1000;
+            }
+        """),
+        sourceFile("cpp", "sum.cpp", """
+            #include "hello.h"
+
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+        """)
+    ]
+
+    String alternateLibraryOutput = "[${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}]\n12"
+
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateAssemblerBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateAssemblerBaseNamesTestApp.groovy
new file mode 100644
index 0000000..b3a816b
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateAssemblerBaseNamesTestApp.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains
+
+class DuplicateAssemblerBaseNamesTestApp extends TestComponent{
+
+    AvailableToolChains.InstalledToolChain toolChain
+
+    DuplicateAssemblerBaseNamesTestApp(AvailableToolChains.InstalledToolChain toolChain) {
+        this.toolChain = toolChain
+    }
+
+    def plugins = ["c", "assembler"]
+
+    @Override
+    List<SourceFile> getSourceFiles() {
+        return  [
+            sourceFile("c", "main.c", """
+            #include <stdio.h>
+
+            // Ensure consistent asm name mapping on all platforms
+            #if !defined(_MSC_VER)
+            extern int sum1(int a, int b) asm("_sum1");
+            extern int sum2(int a, int b) asm("_sum2");
+            #endif
+
+            int main () {
+                printf("foo%dfoo%d", sum1(1, 0), sum2(1, 1));
+                fflush(stdout);
+                return 0;
+            }
+
+            """),
+            sourceFile("asm", "foo1/sum.s", getAsmSource("sum1")),
+            sourceFile("asm", "foo2/sum.s", getAsmSource("sum2"))
+        ]
+    }
+
+    @Override
+    List<SourceFile> getHeaderFiles() {
+        []
+    }
+
+    protected def getAsmSource(String methodName) {
+        if (toolChain.isVisualCpp()) {
+            return """
+.386
+.model    flat
+
+PUBLIC    _${methodName}
+_TEXT     SEGMENT
+_${methodName}    PROC
+mov    eax, DWORD PTR 4[esp]
+add    eax, DWORD PTR 8[esp]
+ret    0
+_${methodName}    ENDP
+_TEXT   ENDS
+END
+"""
+        }else{
+            return """
+.text
+.globl  _${methodName}
+_${methodName}:
+movl    8(%esp), %eax
+addl    4(%esp), %eax
+ret
+"""
+
+        }
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateCBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateCBaseNamesTestApp.groovy
new file mode 100644
index 0000000..871f8fa
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateCBaseNamesTestApp.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class DuplicateCBaseNamesTestApp extends TestComponent {
+
+    def plugins = ["c"]
+
+    @Override
+    List<SourceFile> getSourceFiles() {
+        [sourceFile("c", "main.c", """
+            #include <stdio.h>
+            #include "foo.h"
+            int main () {
+               foo1();
+               foo2();
+               return 0;
+            }
+        """),
+
+        sourceFile("c/foo1", "foo.c", """
+            #include <stdio.h>
+            #include "foo.h"
+
+            void foo1() {
+                printf("foo1");
+            }
+        """),
+
+        sourceFile("c/foo2", "foo.c", """
+            #include <stdio.h>
+            #include "foo.h"
+
+            void foo2() {
+                printf("foo2");
+            }
+        """)]
+    }
+
+    @Override
+    List<SourceFile> getHeaderFiles() {
+        [sourceFile("headers", "foo.h", """
+           void foo1();
+           void foo2();
+           """)]
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateCppBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateCppBaseNamesTestApp.groovy
new file mode 100644
index 0000000..061b547
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateCppBaseNamesTestApp.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class DuplicateCppBaseNamesTestApp extends TestComponent {
+
+    def plugins = ["cpp"]
+
+    @Override
+    List<SourceFile> getSourceFiles() {
+        [sourceFile("cpp", "main.cpp", """
+            #include <iostream>
+            #include "foo.h"
+            using namespace std;
+            int main () {
+               foo1();
+               foo2();
+               return 0;
+            }
+        """),
+         sourceFile("cpp/foo1", "foo.cpp", """
+            #include <iostream>
+            #include "foo.h"
+            using namespace std;
+
+            void foo1() {
+                cout << "foo1";
+            }
+        """),
+
+         sourceFile("cpp/foo2", "foo.cpp", """
+            #include <iostream>
+            #include "foo.h"
+            using namespace std;
+
+            void foo2() {
+                cout << "foo2";
+            }
+        """)]
+    }
+
+    @Override
+    List<SourceFile> getHeaderFiles() {
+        [sourceFile("headers", "foo.h", """
+           void foo1();
+           void foo2();
+           """)
+         ]
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateMixedSameBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateMixedSameBaseNamesTestApp.groovy
new file mode 100644
index 0000000..3d8c6f3
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateMixedSameBaseNamesTestApp.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains;
+
+
+// TODO integrate objective-c/cpp we have coverage on windows
+public class DuplicateMixedSameBaseNamesTestApp extends TestComponent {
+
+    AvailableToolChains.InstalledToolChain toolChain
+
+    public DuplicateMixedSameBaseNamesTestApp(AvailableToolChains.InstalledToolChain toolChain) {
+        this.toolChain = toolChain
+    }
+
+    def plugins = ["assembler", "c", "cpp"]
+
+    @Override
+    public List<SourceFile> getSourceFiles() {
+        [sourceFile("c", "main.c", """
+            #include <stdio.h>
+            #include "hello.h"
+
+            // Ensure consistent asm name mapping on all platforms
+            #if !defined(_MSC_VER)
+            extern void sayFooFromAsm() asm("_sayFooFromAsm");
+            #endif
+
+            int main () {
+                sayFooFromC();
+                sayFooFromCpp();
+                //sayFooFromObjectiveC //TODO RG
+                //sayFooFromObjectiveCpp //TODO RG
+                sayFooFromAsm();
+                return 0;
+            }"""),
+
+                sourceFile("c", "foo.c", """
+            #include <stdio.h>
+            #include "hello.h"
+
+            void sayFooFromC() {
+                printf("fooFromC\\n");
+            }
+        """),
+           sourceFile("cpp", "foo.cpp", """
+            #include "hello.h"
+            #include <iostream>
+            using namespace std;
+
+            void sayFooFromCpp() {
+                cout << "fooFromCpp" << std::endl;;
+            }
+
+        """),
+                sourceFile("asm", "foo.s", asmSource())
+        ];
+    }
+
+    String asmSource() {
+        if (toolChain.isVisualCpp()) {
+            return """
+.386
+;.model flat
+.model small,c
+.data
+msg db "fooFromAsm", 10, 0
+.code
+includelib MSVCRT
+extrn printf:near
+extrn exit:NEAR
+public sayFooFromAsm
+sayFooFromAsm proc
+push    offset msg
+call    printf
+mov eax,0
+push eax
+call exit
+sayFooFromAsm endp
+end
+"""
+        } else {
+            return """
+.text
+
+LC0:
+.ascii "fooFromAsm\\12\\0"
+.globl _sayFooFromAsm
+
+_sayFooFromAsm:
+        pushl   %ebp
+        movl    %esp, %ebp
+        subl    \$8, %esp
+        andl    \$-16, %esp
+        movl    \$0, %eax
+        movl    %eax, -4(%ebp)
+        movl    -4(%ebp), %eax
+        movl    \$LC0, (%esp)
+        call    ${(OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) ? '_printf' : 'printf'}
+        movl    \$0, %eax
+        leave
+        ret
+        """
+        }
+    }
+
+    @Override
+    public List<SourceFile> getHeaderFiles() {
+        [sourceFile("headers", "hello.h", """
+            #ifdef __cplusplus
+            extern "C" {
+            #endif
+            void sayFooFromCpp();
+            #ifdef __cplusplus
+            }
+            #endif
+            void sayFooFromC();
+            void sayFooFromAsm();
+            //void sayFooFromObjectiveC();
+            //void sayFooFromObjectiveCpp();
+           """)
+        ]
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateObjectiveCBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateObjectiveCBaseNamesTestApp.groovy
new file mode 100644
index 0000000..5fc2372
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateObjectiveCBaseNamesTestApp.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.internal.os.OperatingSystem
+
+class DuplicateObjectiveCBaseNamesTestApp extends TestComponent{
+
+    def plugins = ["objective-c"]
+    @Override
+    List<SourceFile> getSourceFiles() {
+        [sourceFile("objc", "main.m", """
+            #import <Foundation/Foundation.h>
+            #import "foo.h"
+
+            int main (int argc, const char * argv[])
+            {
+                sayFoo1();
+                sayFoo2();
+                return 0;
+            }
+        """),
+          fooSource(1),
+          fooSource(2)]
+    }
+
+    public String getExtraConfiguration() {
+        def linkerArgs = OperatingSystem.current().isMacOsX() ? '"-framework", "Foundation"' : '"-lgnustep-base", "-lobjc"'
+        return """
+            binaries.all {
+                if (toolChain in Gcc) {
+                    objcCompiler.args "-I/usr/include/GNUstep", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+                }
+
+                if (toolChain in Clang) {
+                    objcCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+                }
+
+                linker.args $linkerArgs
+            }
+        """
+    }
+
+    @Override
+    List<SourceFile> getHeaderFiles() {
+        [sourceFile("headers", "foo.h", """
+            void sayFoo1();
+            void sayFoo2();
+           """)
+        ]
+    }
+
+    SourceFile fooSource(int index) {
+        sourceFile("objc/foo$index", "foo.m", """
+            #import <Foundation/Foundation.h>
+            #import "foo.h"
+
+            void sayFoo$index()
+            {
+                NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+                NSData *strData = [@"foo$index" dataUsingEncoding: NSASCIIStringEncoding];
+                [stdout writeData: strData];
+            }
+        """)
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateObjectiveCppBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateObjectiveCppBaseNamesTestApp.groovy
new file mode 100644
index 0000000..d8be9c2
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateObjectiveCppBaseNamesTestApp.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.internal.os.OperatingSystem
+
+class DuplicateObjectiveCppBaseNamesTestApp extends TestComponent{
+    def plugins = ["objective-cpp"]
+    @Override
+    List<SourceFile> getSourceFiles() {
+        [sourceFile("objcpp", "main.mm", """
+            #define __STDC_LIMIT_MACROS
+            #include <stdint.h>
+            #import <Foundation/Foundation.h>
+            #import "foo.h"
+
+            int main (int argc, const char * argv[])
+            {
+                sayFoo1();
+                sayFoo2();
+                return 0;
+            }
+        """),
+            sourceFile("objcpp/foo1", "foo.mm", """
+            #define __STDC_LIMIT_MACROS
+            #include <stdint.h>
+            #import <Foundation/Foundation.h>
+            #import "foo.h"
+
+            void sayFoo1()
+            {
+                NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+                NSData *strData = [@"foo1" dataUsingEncoding: NSASCIIStringEncoding];
+                [stdout writeData: strData];
+            }
+        """),
+                sourceFile("objcpp/foo2", "foo.mm", """
+            #import <iostream>
+            #import "foo.h"
+
+            void sayFoo2()
+            {
+                std::cout << "foo2";
+            }
+        """)]
+    }
+
+    @Override
+    List<SourceFile> getHeaderFiles() {
+        [sourceFile("headers", "foo.h", """
+            void sayFoo1();
+            void sayFoo2();
+           """)
+        ]
+    }
+
+    public String getExtraConfiguration() {
+        def linkerArgs = OperatingSystem.current().isMacOsX() ? '"-framework", "Foundation"' : '"-lgnustep-base", "-lobjc"'
+        return """
+            binaries.all {
+                objcppCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+                linker.args $linkerArgs
+            }
+        """
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateWindowsResourcesBaseNamesTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateWindowsResourcesBaseNamesTestApp.groovy
new file mode 100644
index 0000000..8d7d3b4
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/DuplicateWindowsResourcesBaseNamesTestApp.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class DuplicateWindowsResourcesBaseNamesTestApp extends TestComponent {
+
+    def plugins = ["cpp","windows-resources"]
+
+    @Override
+    List<SourceFile> getSourceFiles() {
+        [sourceFile("cpp", "main.cpp", """
+#include "hello.h"
+
+int main () {
+    hello();
+    return 0;
+}
+"""),
+         sourceFile("cpp", "hello.cpp", """
+#include <iostream>
+#include <windows.h>
+#include <string>
+#include "hello.h"
+
+std::string LoadStringFromResource(UINT stringID)
+{
+    HINSTANCE instance = GetModuleHandle("hello");
+    WCHAR * pBuf = NULL;
+    int len = LoadStringW(instance, stringID, reinterpret_cast<LPWSTR>(&pBuf), 0);
+    std::wstring wide = std::wstring(pBuf, len);
+    return std::string(wide.begin(), wide.end());
+}
+
+void DLL_FUNC hello() {
+    std::string foo1 = LoadStringFromResource(IDS_FOO1);
+    std::string foo2 = LoadStringFromResource(IDS_FOO2);
+    std::cout << foo1;
+    std::cout << foo2;
+}
+"""),
+        sourceFile("rc/dir1", "resources.rc", """
+#include "hello.h"
+
+STRINGTABLE
+{
+    IDS_FOO1, "foo1"
+}
+"""),
+        sourceFile("rc/dir2", "resources.rc", """
+#include "hello.h"
+
+STRINGTABLE
+{
+    IDS_FOO2, "foo2"
+}
+""")]
+
+    }
+
+    @Override
+    List<SourceFile> getHeaderFiles() {
+        return [sourceFile("headers", "hello.h", """
+#define IDS_FOO1    111
+#define IDS_FOO2    1000
+
+#ifdef DLL_EXPORT
+#define DLL_FUNC __declspec(dllexport)
+#define MODULE_HANDLE GetModuleHandle("hello")
+#else
+#define DLL_FUNC
+#define MODULE_HANDLE null
+#endif
+
+void DLL_FUNC hello();
+""")]
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ExeWithDiamondDependencyHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ExeWithDiamondDependencyHelloWorldApp.groovy
new file mode 100644
index 0000000..76beee6
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ExeWithDiamondDependencyHelloWorldApp.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+public class ExeWithDiamondDependencyHelloWorldApp extends ExeWithLibraryUsingLibraryHelloWorldApp {
+
+    @Override
+    String getEnglishOutput() {
+        return HELLO_WORLD + " " + HELLO_WORLD + " " + HELLO_WORLD
+    }
+
+    @Override
+    String getFrenchOutput() {
+        return HELLO_WORLD_FRENCH + "\n"
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        sourceFile("cpp", "main.cpp", """
+            #include <iostream>
+            #include "hello.h"
+            #include "greetings.h"
+
+            const char* getExeHello() {
+                #ifdef FRENCH
+                return "${HELLO_WORLD_FRENCH}";
+                #else
+                return "${HELLO_WORLD}";
+                #endif
+            }
+
+            int main () {
+                std::cout << getExeHello() << " ";
+                std::cout << getHello() << " ";
+                sayHello();
+                return 0;
+            }
+        """)
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ExeWithLibraryUsingLibraryHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ExeWithLibraryUsingLibraryHelloWorldApp.groovy
new file mode 100644
index 0000000..10efa62
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ExeWithLibraryUsingLibraryHelloWorldApp.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.test.fixtures.file.TestFile;
+
+public class ExeWithLibraryUsingLibraryHelloWorldApp extends HelloWorldApp {
+
+    void writeSources(TestFile mainSourceDir, TestFile librarySourceDir, TestFile greetingsLibrarySourceDir) {
+        getExecutable().writeSources(mainSourceDir)
+        getLibrary().writeSources(librarySourceDir)
+        getGreetingsHeader().writeToDir(greetingsLibrarySourceDir);
+        for (SourceFile sourceFile : greetingsSources) {
+            sourceFile.writeToDir(greetingsLibrarySourceDir);
+        }
+    }
+
+
+    public TestComponent getGreetingsLibrary() {
+        return new TestComponent() {
+            @Override
+            public List<SourceFile> getHeaderFiles() {
+                return Arrays.asList(getGreetingsHeader())
+            }
+
+            @Override
+            public List<SourceFile> getSourceFiles() {
+                return greetingsSources
+            }
+        };
+    }
+
+    @Override
+    String getEnglishOutput() {
+        return HELLO_WORLD + " " + HELLO_WORLD
+    }
+
+    @Override
+    String getFrenchOutput() {
+        return HELLO_WORLD_FRENCH + "\n"
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        sourceFile("cpp", "main.cpp", """
+            #include <iostream>
+            #include "hello.h"
+
+            const char* getExeHello() {
+                #ifdef FRENCH
+                return "${HELLO_WORLD_FRENCH}";
+                #else
+                return "${HELLO_WORLD}";
+                #endif
+            }
+
+            int main () {
+                std::cout << getExeHello() << " ";
+                sayHello();
+                return 0;
+            }
+        """)
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "hello.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC sayHello();
+        """);
+    }
+
+    List<SourceFile> librarySources = [
+        sourceFile("cpp", "hello.cpp", """
+            #include <iostream>
+            #include "hello.h"
+            #include "greetings.h"
+
+            void DLL_FUNC sayHello() {
+                std::cout << getHello();
+            }
+        """)
+    ]
+
+    SourceFile getGreetingsHeader() {
+        sourceFile("headers", "greetings.h", """
+            #include <string>
+
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            std::string DLL_FUNC getHello();
+        """);
+    }
+
+    List<SourceFile> greetingsSources = [
+        sourceFile("cpp", "greetings.cpp", """
+            #include "greetings.h"
+
+            std::string DLL_FUNC getHello() {
+                #ifdef FRENCH
+                return "${HELLO_WORLD_FRENCH}";
+                #else
+                return "${HELLO_WORLD}";
+                #endif
+            }
+        """)
+    ]
+
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/HelloWorldApp.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/HelloWorldApp.java
new file mode 100644
index 0000000..ba7f9fc
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/HelloWorldApp.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.Transformer;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+public abstract class HelloWorldApp extends TestApp {
+    public static final String HELLO_WORLD = "Hello, World!";
+    public static final String HELLO_WORLD_FRENCH = "Bonjour, Monde!";
+
+    public String getEnglishOutput() {
+        return HELLO_WORLD + "\n12";
+    }
+
+    public String getFrenchOutput() {
+        return HELLO_WORLD_FRENCH + "\n12";
+    }
+
+    public String getCustomOutput(String value) {
+        return value + "\n12";
+    }
+
+    public String getExtraConfiguration() {
+        return "";
+    }
+
+    public String getSourceType() {
+        return getMainSource().getPath();
+    }
+
+    public String getSourceExtension() {
+        return FilenameUtils.getExtension(getMainSource().getName());
+    }
+
+    public List<String> getPluginList() {
+        return Collections.singletonList(getNormalizedPluginName());
+    }
+
+    private String getNormalizedPluginName() {
+        return getSourceType().replaceAll("([a-z])([A-Z])", "$1-$2").toLowerCase();
+    }
+
+    public String getPluginScript() {
+        StringBuilder builder = new StringBuilder();
+        for (String plugin : getPluginList()) {
+            builder.append("apply plugin: '").append(plugin).append("'\n");
+        }
+        return builder.toString();
+    }
+
+    public String compilerArgs(String arg) {
+        return compilerConfig("args", arg);
+    }
+
+    public String compilerDefine(String define) {
+        return compilerConfig("define", define);
+    }
+
+    public String compilerDefine(String define, String value) {
+        return compilerConfig("define", define, value);
+    }
+
+    private String compilerConfig(String action, String... args) {
+        String quotedArgs = CollectionUtils.join(",", CollectionUtils.collect(args, new SingleQuotingTransformer()));
+        StringBuilder builder = new StringBuilder();
+        for (String plugin : getPluginList()) {
+            String compilerPrefix = getCompilerPrefix(plugin);
+            if (compilerPrefix == null) {
+                continue;
+            }
+            builder.append(compilerPrefix).append("Compiler.").append(action).append(" ").append(quotedArgs).append("\n");
+        }
+
+        return builder.toString();
+    }
+
+    private String getCompilerPrefix(String plugin) {
+        if (plugin.equals("c")) {
+            return "c";
+        }
+        if (plugin.equals("cpp")) {
+            return "cpp";
+        }
+        if (plugin.equals("objective-c")) {
+            return "objc";
+        }
+        if (plugin.equals("objective-cpp")) {
+            return "objcpp";
+        }
+        return null;
+    }
+
+    private static class SingleQuotingTransformer implements Transformer<Object, String> {
+        public Object transform(String original) {
+            return "'" + original + "'";
+        }
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/IncrementalHelloWorldApp.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/IncrementalHelloWorldApp.java
new file mode 100644
index 0000000..6783006
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/IncrementalHelloWorldApp.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app;
+
+import java.util.List;
+
+public abstract class IncrementalHelloWorldApp extends HelloWorldApp {
+    public TestApp getAlternate() {
+        return new TestApp() {
+            @Override
+            public SourceFile getMainSource() {
+                return getAlternateMainSource();
+            }
+
+            @Override
+            public SourceFile getLibraryHeader() {
+                return getAlternateLibraryHeader();
+            }
+
+            @Override
+            public List<SourceFile> getLibrarySources() {
+                return getAlternateLibrarySources();
+            }
+        };
+    }
+    private SourceFile getAlternateLibraryHeader() {
+        return getLibraryHeader();
+    }
+
+    public abstract SourceFile getAlternateMainSource();
+    public abstract String getAlternateOutput();
+
+    public abstract List<SourceFile> getAlternateLibrarySources();
+    public abstract String getAlternateLibraryOutput();
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/MixedLanguageHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/MixedLanguageHelloWorldApp.groovy
new file mode 100644
index 0000000..914ca88
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/MixedLanguageHelloWorldApp.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains
+
+public class MixedLanguageHelloWorldApp extends HelloWorldApp {
+    private final AvailableToolChains.InstalledToolChain toolChain
+
+    MixedLanguageHelloWorldApp(AvailableToolChains.InstalledToolChain toolChain) {
+        this.toolChain = toolChain
+    }
+
+    @Override
+    List<String> getPluginList() {
+        return ['c', 'cpp', 'assembler']
+    }
+
+    String getExtraConfiguration() {
+        return """
+            model {
+                platforms {
+                    x86 {
+                        architecture "i386"
+                    }
+                }
+            }
+"""
+    }
+
+    SourceFile getMainSource() {
+        return new SourceFile("cpp", "main.cpp", """
+            #include <iostream>
+            extern "C" {
+                #include "hello.h"
+            }
+
+            int main () {
+              sayHello();
+              std::cout << sum(5, 7);
+              return 0;
+            }
+""")
+    }
+
+    SourceFile getLibraryHeader() {
+        return new SourceFile("headers", "hello.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC sayHello();
+            int DLL_FUNC sum(int a, int b);
+""")
+    }
+
+    List<SourceFile> getLibrarySources() {
+        return  [
+            new SourceFile("c", "hello.c", """
+            #include <stdio.h>
+            #include "hello.h"
+
+            void DLL_FUNC sayHello() {
+                #ifdef FRENCH
+                printf("${HELLO_WORLD_FRENCH}\\n");
+                #else
+                printf("${HELLO_WORLD}\\n");
+                #endif
+
+                fflush(stdout);
+            }
+
+            // Ensure consistent asm name mapping on all platforms
+            #if !defined(_MSC_VER)
+            extern int sumx(int a, int b) asm("_sumx");
+            #endif
+
+            int DLL_FUNC sum(int a, int b) {
+                return sumx(a, b);
+            }
+"""),
+            new SourceFile("asm", "sum.s", getAsmSource())
+        ]
+    }
+
+    protected def getAsmSource() {
+        if (toolChain.isVisualCpp()) {
+            return windowsMasmSource
+        }
+        return i386GnuAsmSource
+    }
+
+    private static String windowsMasmSource = '''
+.386
+.model    flat
+
+PUBLIC    _sumx
+_TEXT     SEGMENT
+_sumx    PROC
+mov    eax, DWORD PTR 4[esp]
+add    eax, DWORD PTR 8[esp]
+ret    0
+_sumx    ENDP
+_TEXT   ENDS
+END
+'''
+
+    private static String i386GnuAsmSource = '''
+    .text
+    .globl  _sumx
+_sumx:
+    movl    8(%esp), %eax
+    addl    4(%esp), %eax
+    ret
+'''
+
+    // TODO:DAZ test 64 bit assembler generation (on all tool chains)
+    private static String x64GnuAsmSource = '''
+    .text
+    .p2align 4,,15
+.globl sumx
+    .type   sumx, @function
+sumx:
+    leal    (%rsi,%rdi), %eax
+    ret
+'''
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/MixedObjectiveCHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/MixedObjectiveCHelloWorldApp.groovy
new file mode 100644
index 0000000..865d737
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/MixedObjectiveCHelloWorldApp.groovy
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.internal.os.OperatingSystem
+
+class MixedObjectiveCHelloWorldApp extends HelloWorldApp {
+
+    List pluginList = ["objective-c", "objective-cpp", "c", "cpp"]
+
+    public String getExtraConfiguration() {
+        def linkerArgs = OperatingSystem.current().isMacOsX() ? '"-framework", "Foundation"' : '"-lgnustep-base", "-lobjc"'
+        return """
+            binaries.all {
+                [objcCompiler, objcppCompiler].each{compiler ->
+                    compiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+                }
+                linker.args $linkerArgs
+            }
+        """
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        return sourceFile("objc", "main.m", """
+            // Simple hello world app
+            #import "hello.h"
+
+            int main (int argc, const char * argv[]) {
+                doGreeting();
+                printf("%d", sum(7, 5));
+                return 0;
+            }
+
+        """);
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        return sourceFile("headers", "hello.h", """
+            #ifdef __cplusplus
+            extern "C" {
+            #endif
+            int sum(int a, int b);
+            void world();
+            #ifdef __cplusplus
+            }
+            #endif
+            void doGreeting();
+            void hello();
+            void bonjour();
+           """)
+    }
+
+    @Override
+    List<SourceFile> getLibrarySources() {
+        return [
+                sourceFile("objc", "objclib.c", """
+            #import <Foundation/Foundation.h>
+            #include <stdio.h>
+            #include "hello.h"
+
+            void doGreeting()
+            {
+                #ifdef FRENCH
+                bonjour();
+                #else
+                hello();
+                world();
+                #endif
+            }
+
+            void hello()
+            {
+                NSString *hello = @"Hello";
+                NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+                NSData *strData = [hello dataUsingEncoding: NSASCIIStringEncoding];
+                [stdout writeData: strData];
+            }"""),
+
+                sourceFile("c", "clib.c", """
+            #include <stdio.h>
+            #include "hello.h"
+
+            void bonjour() {
+                printf("Bonjour, Monde!\\n");
+            }
+        """),
+                sourceFile("cpp", "cpplib.cpp", """
+            #include "hello.h"
+            #include <iostream>
+            using namespace std;
+
+            void world() {
+                cout << ", World!" << std::endl;
+            }
+        """),
+                sourceFile("objcpp", "sum.mm", """
+            #import "hello.h"
+            int sum(int a, int b) {
+                return a + b;
+            }
+            """)
+        ]
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ObjectiveCHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ObjectiveCHelloWorldApp.groovy
new file mode 100644
index 0000000..a1a8cf2
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ObjectiveCHelloWorldApp.groovy
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.internal.os.OperatingSystem
+
+class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
+
+    @Override
+    SourceFile getMainSource() {
+        return sourceFile("objc", "main.m", """
+            // Simple hello world app
+            #import <Foundation/Foundation.h>
+            #include "hello.h"
+
+            int main (int argc, const char * argv[])
+            {
+                sayHello();
+                printf("%d", sum(7, 5));
+                return 0;
+            }
+        """);
+    }
+
+    @Override
+    SourceFile getAlternateMainSource() {
+        return sourceFile("objc", "main.m", """
+            #import <Foundation/Foundation.h>
+            #import "hello.h"
+
+            int main (int argc, const char * argv[])
+            {
+                sayHello();
+                return 0;
+            }
+        """);
+    }
+
+    String alternateOutput = "$HELLO_WORLD\n"
+
+
+    @Override
+    SourceFile getLibraryHeader() {
+        return sourceFile("headers", "hello.h", """
+            int main (int argc, const char * argv[]);
+            int sum(int a, int b);
+            void sayHello();
+        """);
+    }
+
+    @Override
+    List<SourceFile> getLibrarySources() {
+        return [
+                sourceFile("objc", "hello.m", """
+            #import <Foundation/Foundation.h>
+            #import "hello.h"
+
+            void sayHello()
+            {
+                NSString *helloWorld = @"${HELLO_WORLD}\\n";
+                #ifdef FRENCH
+                helloWorld = @"${HELLO_WORLD_FRENCH}\\n";
+                #endif
+                NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+                NSData *strData = [helloWorld dataUsingEncoding: NSASCIIStringEncoding];
+                [stdout writeData: strData];
+            }
+        """),
+                sourceFile("objc", "sum.m", """
+            #import "hello.h"
+
+            int sum (int a, int b)
+            {
+                return a + b;
+            }
+        """)]
+    }
+
+    @Override
+    List<SourceFile> getAlternateLibrarySources() {
+        return [
+                sourceFile("objc", "hello.m", """
+            #import <Foundation/Foundation.h>
+            #import "hello.h"
+
+            void sayHello()
+            {
+                NSString *helloWorld = @"${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}\\n";
+                NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+                NSData *strData = [helloWorld dataUsingEncoding: NSASCIIStringEncoding];
+                [stdout writeData: strData];
+            }
+
+            // Extra function to ensure library has different size
+            int anotherFunction() {
+                return 1000;
+            }
+        """),
+                sourceFile("objc", "sum.m", """
+            #import "hello.h"
+
+            int sum (int a, int b)
+            {
+                return a + b;
+            }
+        """)]
+    }
+
+    String alternateLibraryOutput = "${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}\n12"
+
+    public String getExtraConfiguration() {
+        def linkerArgs = OperatingSystem.current().isMacOsX() ? '"-framework", "Foundation"' : '"-lgnustep-base", "-lobjc"'
+        return """
+            binaries.all {
+                objcCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+                linker.args $linkerArgs
+            }
+        """
+    }
+
+    @Override
+    List<String> getPluginList() {
+        ['objective-c']
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ObjectiveCppHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ObjectiveCppHelloWorldApp.groovy
new file mode 100644
index 0000000..2ceb795
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/ObjectiveCppHelloWorldApp.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.internal.os.OperatingSystem
+
+
+class ObjectiveCppHelloWorldApp extends IncrementalHelloWorldApp {
+    @Override
+    SourceFile getMainSource() {
+        return sourceFile("objcpp", "main.mm", """
+            // Simple hello world app
+            #define __STDC_LIMIT_MACROS
+            #include <stdint.h>
+            #import <Foundation/Foundation.h>
+            #include "hello.h"
+
+            int main (int argc, const char * argv[])
+            {
+                sayHello();
+                printf("%d", sum(7, 5));
+                return 0;
+            }
+        """);
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        return sourceFile("headers", "hello.h", """
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC sayHello();
+            int DLL_FUNC sum(int a, int b);
+        """);
+    }
+
+    List<SourceFile> librarySources = [
+            sourceFile("objcpp", "hello.mm", """
+            #define __STDC_LIMIT_MACROS
+            #include <stdint.h>
+            #include "hello.h"
+            #import <Foundation/Foundation.h>
+
+            #include <iostream>
+
+            #ifdef FRENCH
+            const char* greeting() {
+                return "${HELLO_WORLD_FRENCH}";
+            }
+            #endif
+
+            void DLL_FUNC sayHello() {
+                #ifdef FRENCH
+                std::cout << greeting() << std::endl;
+                #else
+                NSString *helloWorld = @"${HELLO_WORLD}\\n";
+                NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+                NSData *strData = [helloWorld dataUsingEncoding: NSASCIIStringEncoding];
+                [stdout writeData: strData];
+                #endif
+            }
+        """),
+            sourceFile("objcpp", "sum.mm", """
+            #include "hello.h"
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+        """)
+    ]
+
+    @Override
+    SourceFile getAlternateMainSource() {
+        return sourceFile("objcpp", "main.mm", """
+            // Simple hello world app
+            #define __STDC_LIMIT_MACROS
+            #include <stdint.h>
+            #import <Foundation/Foundation.h>
+            #import <iostream>
+            #import "hello.h"
+
+            int main (int argc, const char * argv[])
+            {
+                std::cout << "${HELLO_WORLD} ${HELLO_WORLD}" << std::endl;
+                return 0;
+            }
+        """);
+    }
+
+    String alternateOutput = "${HELLO_WORLD} ${HELLO_WORLD}\n"
+
+
+    @Override
+    List<SourceFile> getAlternateLibrarySources() {
+        return [
+            sourceFile("objcpp", "hello.mm", """
+            #define __STDC_LIMIT_MACROS
+            #include <stdint.h>
+            #include <iostream>
+            #include "hello.h"
+
+            void DLL_FUNC sayHello() {
+                std::cout << "${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}" << std::endl;
+            }
+        """),
+        sourceFile("objcpp", "sum.mm", """
+            #include "hello.h"
+            int DLL_FUNC sum(int a, int b) {
+                return a + b;
+            }
+        """)]
+    }
+
+    String alternateLibraryOutput = "${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}\n12"
+
+    public String getExtraConfiguration() {
+        def linkerArgs = OperatingSystem.current().isMacOsX() ? '"-framework", "Foundation"' : '"-lgnustep-base", "-lobjc"'
+        return """
+            binaries.all {
+                objcppCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+                linker.args $linkerArgs
+            }
+        """
+    }
+    @Override
+    List<String> getPluginList() {
+        ['objective-cpp']
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/PlatformDetectingTestApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/PlatformDetectingTestApp.groovy
new file mode 100644
index 0000000..f86522c
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/PlatformDetectingTestApp.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class PlatformDetectingTestApp extends TestApp {
+    @Override
+    SourceFile getMainSource() {
+        sourceFile("cpp", "main.cpp", """
+#include <iostream>
+using namespace std;
+#include "hello.h"
+
+int main () {
+    ${outputPlatform()}
+
+    outputLibraryPlatform();
+}
+""")
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        sourceFile("headers", "hello.h", """
+#ifdef _WIN32
+#define DLL_FUNC __declspec(dllexport)
+#else
+#define DLL_FUNC
+#endif
+
+void DLL_FUNC outputLibraryPlatform();
+        """);
+    }
+
+
+    List<SourceFile> librarySources = [
+        sourceFile("cpp", "hello.cpp", """
+#include <iostream>
+using namespace std;
+#include "hello.h"
+
+void DLL_FUNC outputLibraryPlatform() {
+    ${outputPlatform()}
+}
+        """)
+    ]
+
+    def outputPlatform() {
+        return """
+    #if defined(__x86_64__) || defined(_M_X64)
+    cout << "amd64";
+    #elif defined(__i386) || defined(_M_IX86)
+    cout << "i386";
+    #elif defined(_M_IA64)
+    cout << "itanium";
+    #else
+    cout << "unknown";
+    #endif
+    cout << " ";
+
+    #if defined(__linux__)
+    cout << "linux";
+    #elif defined(__APPLE__) && defined(__MACH__)
+    cout << "os x";
+    #elif defined(_WIN32) || defined (_WIN64) || defined (__CYGWIN__)
+    cout << "windows";
+    #else
+    cout << "unknown";
+    #endif
+"""
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/SourceFile.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/SourceFile.java
new file mode 100644
index 0000000..eaf42a9
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/SourceFile.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app;
+
+import com.google.common.base.Joiner;
+import org.gradle.test.fixtures.file.TestFile;
+
+public class SourceFile {
+    private final String path;
+    private final String name;
+    private final String content;
+
+    public SourceFile(String path, String name, String content) {
+        this.content = content;
+        this.path = path;
+        this.name = name;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public TestFile writeToDir(TestFile base) {
+        TestFile file = base.file(path, name);
+        writeToFile(file);
+        return file;
+    }
+
+    public void writeToFile(TestFile file) {
+        if (file.exists()) {
+            file.write("");
+        }
+        file.write(content);
+    }
+
+    public String withPath(String basePath) {
+        return Joiner.on('/').join(basePath, path, name);
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/TestApp.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/TestApp.java
new file mode 100644
index 0000000..8984ae3
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/TestApp.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class TestApp extends TestComponent {
+    public abstract SourceFile getMainSource();
+    public abstract SourceFile getLibraryHeader();
+    public abstract List<SourceFile> getLibrarySources();
+
+    public TestComponent getLibrary() {
+        return new TestComponent() {
+            @Override
+            public List<SourceFile> getSourceFiles() {
+                return getLibrarySources();
+            }
+
+            @Override
+            public List<SourceFile> getHeaderFiles() {
+                return Arrays.asList(getLibraryHeader());
+            }
+        };
+    }
+
+    public TestComponent getExecutable() {
+        return new TestComponent() {
+            @Override
+            public List<SourceFile> getHeaderFiles() {
+                return Collections.emptyList();
+            }
+
+            @Override
+            public List<SourceFile> getSourceFiles() {
+                return Arrays.asList(getMainSource());
+            }
+        };
+    }
+
+    @Override
+    public List<SourceFile> getHeaderFiles() {
+        ArrayList<SourceFile> headerFiles = new ArrayList<SourceFile>();
+        headerFiles.addAll(getExecutable().getHeaderFiles());
+        headerFiles.addAll(getLibrary().getHeaderFiles());
+        return headerFiles;
+    }
+
+    @Override
+    public List<SourceFile> getSourceFiles() {
+        ArrayList<SourceFile> sourceFiles = new ArrayList<SourceFile>();
+        sourceFiles.addAll(getExecutable().getSourceFiles());
+        sourceFiles.addAll(getLibrary().getSourceFiles());
+        return sourceFiles;
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/TestComponent.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/TestComponent.groovy
new file mode 100644
index 0000000..a8579bb
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/TestComponent.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+import org.gradle.test.fixtures.file.TestFile
+
+abstract class TestComponent {
+    List<SourceFile> getAllFiles() {
+        return sourceFiles + headerFiles
+    }
+
+    abstract List<SourceFile> getSourceFiles()
+
+    abstract List<SourceFile> getHeaderFiles()
+
+    protected SourceFile sourceFile(String path, String name, String content) {
+        return new SourceFile(path, name, content);
+    }
+
+    public void writeSources(TestFile sourceDir) {
+        for (SourceFile srcFile : allFiles) {
+            srcFile.writeToDir(sourceDir)
+        }
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/WindowsResourceHelloWorldApp.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/WindowsResourceHelloWorldApp.groovy
new file mode 100644
index 0000000..2c9b0ab
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/app/WindowsResourceHelloWorldApp.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.app
+
+class WindowsResourceHelloWorldApp extends HelloWorldApp {
+    @Override
+    String getEnglishOutput() {
+        return HELLO_WORLD
+    }
+
+    @Override
+    String getFrenchOutput() {
+        return HELLO_WORLD_FRENCH
+    }
+
+    @Override
+    List<String> getPluginList() {
+        ['cpp', 'windows-resources']
+    }
+
+    @Override
+    String getExtraConfiguration() {
+        return """
+            binaries.all {
+                linker.args "user32.lib"
+            }
+            binaries.withType(SharedLibraryBinary) {
+                cppCompiler.define "DLL_EXPORT"
+            }
+"""
+    }
+
+    @Override
+    String compilerArgs(String arg) {
+        "rcCompiler.args '${arg}'"
+    }
+
+    @Override
+    String compilerDefine(String define) {
+        "rcCompiler.define '${define}'"
+    }
+
+    @Override
+    String compilerDefine(String define, String value) {
+        "rcCompiler.define '${define}', '${value}'"
+    }
+
+    @Override
+    SourceFile getMainSource() {
+        return sourceFile("cpp", "main.cpp", """
+#include "hello.h"
+
+int main () {
+    hello();
+    return 0;
+}
+""");
+    }
+
+    @Override
+    SourceFile getLibraryHeader() {
+        return sourceFile("headers", "hello.h", """
+#define IDS_HELLO    111
+
+#ifdef DLL_EXPORT
+#define DLL_FUNC __declspec(dllexport)
+#define MODULE_HANDLE GetModuleHandle("hello")
+#else
+#define DLL_FUNC
+#define MODULE_HANDLE null
+#endif
+
+void DLL_FUNC hello();
+""");
+    }
+
+    List<SourceFile> librarySources = [
+        sourceFile("cpp", "hello.cpp", """
+#include <iostream>
+#include <windows.h>
+#include <string>
+#include "hello.h"
+
+std::string LoadStringFromResource(UINT stringID)
+{
+    HINSTANCE instance = GetModuleHandle("hello");
+    WCHAR * pBuf = NULL;
+    int len = LoadStringW(instance, stringID, reinterpret_cast<LPWSTR>(&pBuf), 0);
+    std::wstring wide = std::wstring(pBuf, len);
+    return std::string(wide.begin(), wide.end());
+}
+
+void DLL_FUNC hello() {
+    std::string hello = LoadStringFromResource(IDS_HELLO);
+    std::cout << hello;
+}
+"""),
+        sourceFile("rc", "resources.rc", """
+#include "hello.h"
+
+STRINGTABLE
+{
+    #ifdef FRENCH
+    IDS_HELLO, "${HELLO_WORLD_FRENCH}"
+    #else
+    IDS_HELLO, "${HELLO_WORLD}"
+    #endif
+}
+""")
+    ]
+
+    List<SourceFile> getResourceSources() {
+        getLibrarySources().subList(1, 2)
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/BinaryInfo.java b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/BinaryInfo.java
new file mode 100644
index 0000000..8959726
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/BinaryInfo.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo;
+
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal;
+
+import java.util.List;
+
+public interface BinaryInfo {
+    ArchitectureInternal getArch();
+    List<String> listObjectFiles();
+    List<String> listLinkedLibraries();
+    String getSoName();
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/DumpbinBinaryInfo.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/DumpbinBinaryInfo.groovy
new file mode 100644
index 0000000..0405181
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/DumpbinBinaryInfo.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo
+
+import net.rubygrapefruit.platform.SystemInfo
+import net.rubygrapefruit.platform.WindowsRegistry
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativebinaries.language.cpp.fixtures.AvailableToolChains.InstalledToolChain
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal
+import org.gradle.nativebinaries.platform.internal.DefaultArchitecture
+import org.gradle.nativebinaries.platform.internal.DefaultPlatform
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.DefaultVisualStudioLocator
+import org.gradle.nativebinaries.toolchain.internal.msvcpp.VisualStudioInstall
+
+class DumpbinBinaryInfo implements BinaryInfo {
+    final File binaryFile
+    final File vcBin
+    final String vcPath
+
+    DumpbinBinaryInfo(File binaryFile, InstalledToolChain tc) {
+        this.binaryFile = binaryFile
+
+        VisualStudioInstall vsInstall = findVisualStudio()
+        DefaultPlatform targetPlatform = new DefaultPlatform("default");
+        vcBin = vsInstall.getVisualCpp().getBinaryPath(targetPlatform)
+        vcPath = vsInstall.getVisualCpp().getPath(targetPlatform).join(';')
+    }
+
+    static VisualStudioInstall findVisualStudio() {
+        def vsLocator = new DefaultVisualStudioLocator(OperatingSystem.current(), NativeServices.instance.get(WindowsRegistry), NativeServices.instance.get(SystemInfo))
+        return vsLocator.locateVisualStudioInstalls(null).visualStudio
+    }
+
+    private findExe(String exe) {
+        final candidate = new File(vcBin, exe)
+        if (candidate.exists()) {
+            return candidate
+        }
+        throw new RuntimeException("dumpbin.exe not found")
+    }
+
+    private String getDumpbinHeaders() {
+        def dumpbin = findExe("dumpbin.exe")
+        def process = [dumpbin.absolutePath, '/HEADERS', binaryFile.absolutePath].execute(["PATH=$vcPath"], null)
+        return process.inputStream.text
+    }
+
+    def static readArch(def input) {
+        def pattern = /(?m)^.* machine \((.*)\).*$/
+        return (input =~ pattern).findResult { line, group ->
+            group.trim()
+        }
+    }
+
+    ArchitectureInternal getArch() {
+        def archString = readArch(dumpbinHeaders)
+        switch (archString) {
+            case "x86":
+                return new DefaultArchitecture("x86", ArchitectureInternal.InstructionSet.X86, 32)
+            case "x64":
+                return new DefaultArchitecture("x86_64", ArchitectureInternal.InstructionSet.X86, 64)
+            case "IA64":
+                return new DefaultArchitecture("ia-64", ArchitectureInternal.InstructionSet.ITANIUM, 64)
+            default:
+                throw new RuntimeException("Cannot determine architecture for ${archString}")
+        }
+    }
+
+    List<String> listObjectFiles() {
+        def dumpbin = findExe("lib.exe")
+        def process = [dumpbin.absolutePath, '/LIST', binaryFile.absolutePath].execute(["PATH=$vcPath"], null)
+        return process.inputStream.readLines().drop(3).collect { new File(it).name }
+    }
+
+    List<String> listLinkedLibraries() {
+        def dumpbin = findExe("dumpbin.exe")
+        def process = [dumpbin.absolutePath, '/IMPORTS', binaryFile.absolutePath].execute(["PATH=$vcPath"], null)
+        return process.inputStream.readLines()
+    }
+
+    String getSoName() {
+        throw new UnsupportedOperationException("soname is not relevant on windows")
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/OtoolBinaryInfo.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/OtoolBinaryInfo.groovy
new file mode 100644
index 0000000..6152c2d
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/OtoolBinaryInfo.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo
+
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal
+import org.gradle.nativebinaries.platform.internal.DefaultArchitecture
+
+class OtoolBinaryInfo implements BinaryInfo {
+    def binaryFile
+
+    OtoolBinaryInfo(File binaryFile) {
+        this.binaryFile = binaryFile    }
+
+    ArchitectureInternal getArch() {
+        def process = ['otool', '-hv', binaryFile.absolutePath].execute()
+        def lines = process.inputStream.readLines()
+        def archString = lines[3].split()[1]
+
+        switch (archString) {
+            case "I386":
+                return new DefaultArchitecture("x86", ArchitectureInternal.InstructionSet.X86, 32)
+            case "X86_64":
+                return new DefaultArchitecture("x86_64", ArchitectureInternal.InstructionSet.X86, 64)
+            default:
+                throw new RuntimeException("Cannot determine architecture for ${archString}")
+        }
+    }
+
+    List<String> listObjectFiles() {
+        def process = ['ar', '-t', binaryFile.getAbsolutePath()].execute()
+        return process.inputStream.readLines().drop(1)
+    }
+
+    List<String> listLinkedLibraries() {
+        def process = ['otool', '-L', binaryFile.absolutePath].execute()
+        def lines = process.inputStream.readLines()
+        return lines
+    }
+
+    String getSoName() {
+        def process = ['otool', '-D', binaryFile.absolutePath].execute()
+        def lines = process.inputStream.readLines()
+        return lines[1]
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/ReadelfBinaryInfo.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/ReadelfBinaryInfo.groovy
new file mode 100644
index 0000000..20b9505
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/language/cpp/fixtures/binaryinfo/ReadelfBinaryInfo.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativebinaries.language.cpp.fixtures.binaryinfo
+import org.gradle.nativebinaries.platform.internal.ArchitectureInternal
+import org.gradle.nativebinaries.platform.internal.DefaultArchitecture
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+class ReadelfBinaryInfo implements BinaryInfo {
+
+    private final File binaryFile
+
+    ReadelfBinaryInfo(File binaryFile) {
+        this.binaryFile = binaryFile
+    }
+
+    public static String readHeaderValue(List<String> lines, String header) {
+        String matchingLine = lines.find {
+            it.trim().startsWith(header)
+        }
+        return matchingLine != null ? matchingLine.replaceFirst(header, "").trim() : null
+    }
+
+    ArchitectureInternal getArch() {
+        def process = ['readelf', '-h', binaryFile.absolutePath].execute()
+        List<String> lines = process.inputStream.readLines()
+        def archString = readHeaderValue(lines, "Machine:")
+        switch (archString) {
+            case "Intel 80386":
+                return new DefaultArchitecture("x86", ArchitectureInternal.InstructionSet.X86, 32)
+            case "Advanced Micro Devices X86-64":
+                return new DefaultArchitecture("x86_64", ArchitectureInternal.InstructionSet.X86, 64)
+            default:
+                throw new RuntimeException("Cannot determine architecture for ${archString}")
+        }
+    }
+
+    List<String> listObjectFiles() {
+        def process = ['ar', '-t', binaryFile.getAbsolutePath()].execute()
+        return process.inputStream.readLines()
+    }
+
+    List<String> listLinkedLibraries() {
+        def process = ['readelf', '-d', binaryFile.absolutePath].execute()
+        def lines = process.inputStream.readLines()
+        return lines
+    }
+
+    String getSoName() {
+        def process = ['readelf', '-d', binaryFile.absolutePath].execute()
+        List<String> lines = process.inputStream.readLines()
+        return readSoName(lines)
+    }
+
+    static String readSoName(List<String> lines) {
+        final Pattern pattern = ~/^.*\(SONAME\)\s+Library soname\: \[(.*)\]$/
+        String matchingLine = lines.find {
+            pattern.matcher(it).matches()
+        }
+        if (matchingLine == null) {
+            return null;
+        }
+        final Matcher matcher = pattern.matcher(matchingLine)
+        assert matcher.matches()
+        return matcher.group(1)
+    }
+}
diff --git a/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/test/cunit/CUnitTestResults.groovy b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/test/cunit/CUnitTestResults.groovy
new file mode 100644
index 0000000..ce4f3a3
--- /dev/null
+++ b/subprojects/cpp/src/testFixtures/groovy/org/gradle/nativebinaries/test/cunit/CUnitTestResults.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.nativebinaries.test.cunit
+
+import org.gradle.test.fixtures.file.TestFile
+
+class CUnitTestResults {
+    TestFile testResultsFile
+    Node resultsNode
+    Map<String, Suite> suites = [:]
+    Map<String, SummaryRecord> summaryRecords = [:]
+
+    CUnitTestResults(TestFile testResultsFile) {
+        assert testResultsFile.exists()
+        this.testResultsFile = testResultsFile
+        final XmlParser parser = new XmlParser(false, false)
+        parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        parser.setFeature("http://xml.org/sax/features/namespaces", false)
+        this.resultsNode = parser.parse(testResultsFile)
+
+        suiteNames.each { String name ->
+            Node suiteNode = resultsNode.getAt('CUNIT_RESULT_LISTING').CUNIT_RUN_SUITE.CUNIT_RUN_SUITE_SUCCESS.find({it.SUITE_NAME.text() == name}) as Node
+            suites.put(name, new Suite(suiteNode))
+        }
+
+        summaryRecords['Suites'] = new SummaryRecord(resultsNode.CUNIT_RUN_SUMMARY.CUNIT_RUN_SUMMARY_RECORD.find({it.TYPE.text() == 'Suites'}) as Node)
+        summaryRecords['Test Cases'] = new SummaryRecord(resultsNode.CUNIT_RUN_SUMMARY.CUNIT_RUN_SUMMARY_RECORD.find({it.TYPE.text() == 'Test Cases'}) as Node)
+        summaryRecords['Assertions'] = new SummaryRecord(resultsNode.CUNIT_RUN_SUMMARY.CUNIT_RUN_SUMMARY_RECORD.find({it.TYPE.text() == 'Assertions'}) as Node)
+    }
+
+    List<String> getSuiteNames() {
+        resultsNode.getAt('CUNIT_RESULT_LISTING').CUNIT_RUN_SUITE.CUNIT_RUN_SUITE_SUCCESS.SUITE_NAME*.text()
+    }
+
+    def checkTestCases(int total, int succeeded, int failed) {
+        checkSummaryRecord('Test Cases', total, succeeded, failed)
+    }
+
+    def checkAssertions(int total, int succeeded, int failed) {
+        checkSummaryRecord('Assertions', total, succeeded, failed)
+    }
+
+    private def checkSummaryRecord(String name, int total, int succeeded, int failed) {
+        def recordNode = resultsNode.CUNIT_RUN_SUMMARY.CUNIT_RUN_SUMMARY_RECORD.find({it.TYPE.text() == name}) as Node
+        assert recordNode.RUN.text() as int == total
+        assert recordNode.SUCCEEDED.text() as int == succeeded
+        assert recordNode.FAILED.text() as int == failed
+        return true
+    }
+
+    class Suite {
+        final Node suiteNode
+
+        Suite(Node suiteNode) {
+            this.suiteNode = suiteNode
+        }
+
+        String getName() {
+            return suiteNode.SUITE_NAME.text()
+        }
+
+        List<String> getPassingTests() {
+            suiteNode.CUNIT_RUN_TEST_RECORD.CUNIT_RUN_TEST_SUCCESS.TEST_NAME*.text()
+        }
+
+        List<String> getFailingTests() {
+            suiteNode.CUNIT_RUN_TEST_RECORD.CUNIT_RUN_TEST_FAILURE.TEST_NAME*.text().unique()
+        }
+    }
+
+    class SummaryRecord {
+        final Node recordNode
+
+        SummaryRecord(Node recordNode) {
+            this.recordNode = recordNode
+        }
+
+        int getRun() {
+            recordNode.RUN.text() as int
+        }
+
+        int getSucceeded() {
+            recordNode.SUCCEEDED.text() as int
+        }
+
+        int getFailed() {
+            recordNode.FAILED.text() as int
+        }
+
+    }
+}
diff --git a/subprojects/diagnostics/diagnostics.gradle b/subprojects/diagnostics/diagnostics.gradle
index cbbf7b1..875e578 100644
--- a/subprojects/diagnostics/diagnostics.gradle
+++ b/subprojects/diagnostics/diagnostics.gradle
@@ -15,8 +15,9 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
     compile project(':core')
+    compile project(':reporting')
     compile project(':plugins')
     compile project(':coreImpl')
 }
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/dependencies/HtmlDependencyReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/dependencies/HtmlDependencyReportTaskIntegrationTest.groovy
new file mode 100644
index 0000000..d4d4ca0
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/dependencies/HtmlDependencyReportTaskIntegrationTest.groovy
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies
+
+import groovy.json.JsonSlurper
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
+import spock.lang.Issue
+
+/**
+ * Integration tests for the HTML dependency report generation task
+ */
+class HtmlDependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        executer.requireOwnGradleUserHomeDir()
+    }
+
+    def "renders graph"() {
+        given:
+        mavenRepo.module("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "qix", "1.0").publish()
+        mavenRepo.module("foo", "baz", "1.0").dependsOn("foo", "bar", "1.0").dependsOn("foo", "qix", "1.0").publish()
+
+        file("settings.gradle") << """
+            rootProject.name = 'fooProject'
+        """
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            description = 'dummy description'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { compile }
+            configurations { testCompile }
+            dependencies {
+              compile 'foo:baz:1.0'
+              testCompile 'foo:bar:1.0'
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+
+        then:
+        json.generationDate != null
+        json.gradleVersion != null
+        json.project.name == "fooProject"
+        json.project.description == "dummy description"
+        json.project.configurations.size() == 2
+        json.project.configurations[0].name == "compile"
+        json.project.configurations[0].dependencies.size() == 1
+        json.project.configurations[0].dependencies[0].module == "foo:baz"
+        json.project.configurations[0].dependencies[0].name == "foo:baz:1.0"
+        json.project.configurations[0].dependencies[0].resolvable == true
+        json.project.configurations[0].dependencies[0].alreadyRendered == false
+        json.project.configurations[0].dependencies[0].hasConflict == false
+        json.project.configurations[0].dependencies[0].children.size() == 2
+
+        json.project.configurations[0].dependencies[0].children[0].module == "foo:bar"
+        json.project.configurations[0].dependencies[0].children[0].name == "foo:bar:1.0"
+        json.project.configurations[0].dependencies[0].children[0].resolvable == true
+        json.project.configurations[0].dependencies[0].children[0].alreadyRendered == false
+        json.project.configurations[0].dependencies[0].children[0].hasConflict == false
+        json.project.configurations[0].dependencies[0].children[0].children.empty
+
+        json.project.configurations[0].dependencies[0].children[1].module == "foo:qix"
+        json.project.configurations[0].dependencies[0].children[1].name == "foo:qix:1.0"
+        json.project.configurations[0].dependencies[0].children[1].resolvable == true
+        json.project.configurations[0].dependencies[0].children[1].alreadyRendered == false
+        json.project.configurations[0].dependencies[0].children[1].hasConflict == false
+        json.project.configurations[0].dependencies[0].children[1].children.empty
+
+        json.project.configurations[1].name == "testCompile"
+        json.project.configurations[1].dependencies.size() == 1
+        json.project.configurations[1].dependencies[0].module == "foo:bar"
+        json.project.configurations[1].dependencies[0].name == "foo:bar:1.0"
+        json.project.configurations[1].dependencies[0].resolvable == true
+        json.project.configurations[1].dependencies[0].alreadyRendered == false
+        json.project.configurations[1].dependencies[0].hasConflict == false
+        json.project.configurations[1].dependencies[0].children.empty
+    }
+
+    def "already rendered dependencies are marked as such"() {
+        given:
+        mavenRepo.module("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "qix", "1.0").dependsOn("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "baz", "1.0").dependsOn("foo", "qix", "1.0").publish()
+
+        file("settings.gradle") << """
+            rootProject.name = 'fooProject'
+        """
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+              compile 'foo:qix:1.0'
+              compile 'foo:baz:1.0'
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+
+        then:
+        json.project.configurations[0].dependencies.size() == 2
+        json.project.configurations[0].dependencies[0].name == "foo:qix:1.0"
+        json.project.configurations[0].dependencies[0].children[0].name == "foo:bar:1.0"
+
+        json.project.configurations[0].dependencies[1].name == "foo:baz:1.0"
+        json.project.configurations[0].dependencies[1].children[0].name == "foo:qix:1.0"
+        json.project.configurations[0].dependencies[1].children[0].alreadyRendered == true
+        json.project.configurations[0].dependencies[1].children[0].children.empty
+    }
+
+    def "non-resolved dependencies are marked as such"() {
+        given:
+        mavenRepo.module("foo", "bar", "1.0").dependsOn("foo", "qix", "1.0").publish()
+
+        file("settings.gradle") << """
+            rootProject.name = 'fooProject'
+        """
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+              compile 'foo:bar:1.0'
+              compile 'grr:bzz:1.0'
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+
+        then:
+        json.project.configurations[0].dependencies.size() == 2
+        json.project.configurations[0].dependencies[0].name == "foo:bar:1.0"
+        json.project.configurations[0].dependencies[0].children[0].name == "foo:qix:1.0"
+        json.project.configurations[0].dependencies[0].children[0].resolvable == false
+        json.project.configurations[0].dependencies[1].name == "grr:bzz:1.0"
+        json.project.configurations[0].dependencies[1].resolvable == false
+    }
+
+    def "conflictual dependencies are marked as such"() {
+        given:
+        mavenRepo.module("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "bar", "2.0").publish()
+        mavenRepo.module("foo", "baz", "1.0").dependsOn("foo", "bar", "1.0").publish()
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+              compile 'foo:baz:1.0'
+              compile 'foo:bar:2.0'
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+
+        then:
+        json.project.configurations[0].dependencies.size() == 2
+        json.project.configurations[0].dependencies[0].name == "foo:baz:1.0"
+        json.project.configurations[0].dependencies[0].children[0].name == "foo:bar:1.0 \u27A1 2.0"
+        json.project.configurations[0].dependencies[0].children[0].hasConflict == true
+        json.project.configurations[0].dependencies[1].name == "foo:bar:2.0"
+    }
+
+    def "generates report for multiple projects"() {
+        given:
+        file("settings.gradle") << """
+            rootProject.name = 'fooProject'
+            include 'a', 'b'
+        """
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+
+            htmlDependencyReport {
+                projects = [project(':a'), project(':b')]
+            }
+        """
+        ["a", "b"].each { module ->
+            file(module, "build.gradle") << """
+                apply plugin: 'project-report'
+            """
+        }
+
+        when:
+        run "htmlDependencyReport"
+        def jsonA = readGeneratedJson("root.a")
+        def jsonB = readGeneratedJson("root.b")
+
+        then:
+        jsonA.project.name == "a"
+        jsonB.project.name == "b"
+    }
+
+    def "copies necessary css, images and js files"() {
+        given:
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            configurations { compile }
+        """
+
+        when:
+        run "htmlDependencyReport"
+
+        then:
+        file("build/reports/project/dependencies/d.gif").assertExists()
+        file("build/reports/project/dependencies/d.png").assertExists()
+        file("build/reports/project/dependencies/jquery.jstree.js").assertExists()
+        file("build/reports/project/dependencies/jquery-1.10.1.min.js").assertExists()
+        file("build/reports/project/dependencies/script.js").assertExists()
+        file("build/reports/project/dependencies/style.css").assertExists()
+        file("build/reports/project/dependencies/throbber.gif").assertExists()
+        file("build/reports/project/dependencies/tree.css").assertExists()
+
+        file("build/reports/project/dependencies/root.html").getText().contains('<script src="root.js" charset="utf-8"></script>');
+        file("build/reports/project/dependencies/root.js").assertExists();
+        file("build/reports/project/dependencies/index.html").getText().contains('<script src="index.js" charset="utf-8"></script>');
+    }
+
+    def "generates index.js file"() {
+        given:
+        file("settings.gradle") << """
+            rootProject.name = 'fooProject'
+            include 'a', 'b'
+        """
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+
+            description = 'dummy description'
+
+            htmlDependencyReport {
+                projects = project.allprojects
+            }
+        """
+        ["a", "b"].each { module ->
+            file(module, "build.gradle") << """
+                apply plugin: 'project-report'
+            """
+        }
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("index")
+
+        then:
+        json.generationDate != null
+        json.gradleVersion != null
+
+        json.projects.size() == 3
+        json.projects[0].path == 'root:'
+        json.projects[0].name == 'fooProject'
+        json.projects[0].description == 'dummy description'
+        json.projects[0].file == 'root.html'
+
+        json.projects[1].path == 'root:a'
+        json.projects[1].name == 'a'
+        json.projects[1].description == null
+        json.projects[1].file == 'root.a.html'
+
+        json.projects[2].path == 'root:b'
+        json.projects[2].name == 'b'
+        json.projects[2].description == null
+        json.projects[2].file == 'root.b.html'
+    }
+
+    def "renders insights graphs"() {
+        given:
+        mavenRepo.module("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "bar", "2.0").publish()
+        mavenRepo.module("foo", "baz", "1.0").dependsOn("foo", "bar", "1.0").publish()
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+              compile 'foo:baz:1.0'
+              compile 'foo:bar:2.0'
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+
+        then:
+        json.project.configurations[0].moduleInsights.size() == 2
+        json.project.configurations[0].moduleInsights.any { it.module == 'foo:bar' }
+        json.project.configurations[0].moduleInsights.any { it.module == 'foo:baz' }
+        def barInsight = json.project.configurations[0].moduleInsights.find({ it.module == 'foo:bar' }).insight
+        barInsight.size() == 2
+        barInsight[0].name == 'foo:bar:2.0'
+        barInsight[0].resolvable == true
+        barInsight[0].hasConflict == false
+        barInsight[0].description == 'conflict resolution'
+        barInsight[0].children.size() == 1
+        barInsight[0].children[0].name == 'compile'
+        barInsight[0].children[0].resolvable == true
+        barInsight[0].children[0].hasConflict == false
+        barInsight[0].children[0].isLeaf == true
+        barInsight[0].children[0].alreadyRendered == false
+        barInsight[0].children[0].children.size() == 0
+
+        barInsight[1].name == "foo:bar:1.0 \u27A1 2.0"
+        barInsight[1].resolvable == true
+        barInsight[1].hasConflict == true
+        barInsight[1].children.size() == 1
+        barInsight[1].children[0].name == 'foo:baz:1.0'
+        barInsight[1].children[0].resolvable == true
+        barInsight[1].children[0].isLeaf == false
+        barInsight[1].children[0].alreadyRendered == false
+        barInsight[1].children[0].children.size() == 1
+        barInsight[1].children[0].children[0].name == 'compile'
+        barInsight[1].children[0].children[0].resolvable == true
+        barInsight[1].children[0].children[0].hasConflict == false
+        barInsight[1].children[0].children[0].isLeaf == true
+        barInsight[1].children[0].children[0].alreadyRendered == false
+        barInsight[1].children[0].children[0].children.size() == 0
+
+        def bazInsight = json.project.configurations[0].moduleInsights.find({ it.module == 'foo:baz' }).insight
+        bazInsight.size() == 1
+        bazInsight[0].name == 'foo:baz:1.0'
+        bazInsight[0].resolvable == true
+        bazInsight[0].hasConflict == false
+        bazInsight[0].children.size() == 1
+        bazInsight[0].children[0].name == 'compile'
+        bazInsight[0].children[0].resolvable == true
+        bazInsight[0].children[0].hasConflict == false
+        bazInsight[0].children[0].isLeaf == true
+        bazInsight[0].children[0].alreadyRendered == false
+        bazInsight[0].children[0].children.empty
+    }
+
+    def "doesn't add insight for dependency with same prefix"() {
+        given:
+        mavenRepo.module("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "barImpl", "1.0").publish()
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+              compile 'foo:bar:1.0'
+              compile 'foo:barImpl:1.0'
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+
+        then:
+        def barInsight = json.project.configurations[0].moduleInsights.find({ it.module == 'foo:bar' }).insight
+        barInsight.size() == 1
+        barInsight[0].name == 'foo:bar:1.0'
+    }
+
+    @Issue("GRADLE-2979")
+    def "renders a mix of project and external dependencies"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).publish()
+        mavenRepo.module("foo", "bar", 2.0).publish()
+
+        file("settings.gradle") << """include 'a', 'b', 'a:c', 'd', 'e'
+rootProject.name = 'root'
+"""
+
+        file("build.gradle") << """
+            apply plugin : 'project-report'
+
+            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(":a:c") {
+               dependencies {
+                    compile 'foo:bar:2.0'
+               }
+            }
+
+            project(":d") {
+               dependencies {
+                    compile project(":e")
+                }
+            }
+
+            project(":e") {
+               dependencies {
+                    compile 'foo:bar:2.0'
+                }
+            }
+
+            dependencies {
+                compile project(":a"), project(":b"), project(":a:c"), project(":d")
+            }
+        """
+
+        when:
+        run "htmlDependencyReport"
+        def json = readGeneratedJson("root")
+        def compileConfiguration = json.project.configurations.find { it.name == "compile" }
+
+        then:
+        compileConfiguration
+        compileConfiguration.dependencies.size() == 4
+        compileConfiguration.dependencies[0].module == null
+        compileConfiguration.dependencies[0].name == "project :a"
+        compileConfiguration.dependencies[0].resolvable == true
+        compileConfiguration.dependencies[0].alreadyRendered == false
+        compileConfiguration.dependencies[0].hasConflict == false
+        compileConfiguration.dependencies[0].children.size() == 1
+        compileConfiguration.dependencies[0].children[0].module == "foo:bar"
+        compileConfiguration.dependencies[0].children[0].name == "foo:bar:1.0 \u27A1 2.0"
+        compileConfiguration.dependencies[0].children[0].resolvable == true
+        compileConfiguration.dependencies[0].children[0].alreadyRendered == false
+        compileConfiguration.dependencies[0].children[0].hasConflict == true
+        compileConfiguration.dependencies[0].children[0].children.empty
+
+        compileConfiguration.dependencies[1].module == null
+        compileConfiguration.dependencies[1].name == "project :b"
+        compileConfiguration.dependencies[1].resolvable == true
+        compileConfiguration.dependencies[1].alreadyRendered == false
+        compileConfiguration.dependencies[1].hasConflict == false
+        compileConfiguration.dependencies[1].children.size() == 1
+        compileConfiguration.dependencies[1].children[0].module == "foo:bar"
+        compileConfiguration.dependencies[1].children[0].name == "foo:bar:0.5.dont.exist \u27A1 2.0"
+        compileConfiguration.dependencies[1].children[0].resolvable == true
+        compileConfiguration.dependencies[1].children[0].alreadyRendered == false
+        compileConfiguration.dependencies[1].children[0].hasConflict == true
+        compileConfiguration.dependencies[1].children[0].children.empty
+
+        compileConfiguration.dependencies[2].module == null
+        compileConfiguration.dependencies[2].name == "project :a:c"
+        compileConfiguration.dependencies[2].resolvable == true
+        compileConfiguration.dependencies[2].alreadyRendered == false
+        compileConfiguration.dependencies[2].hasConflict == false
+        compileConfiguration.dependencies[2].children.size() == 1
+        compileConfiguration.dependencies[2].children[0].module == "foo:bar"
+        compileConfiguration.dependencies[2].children[0].name == "foo:bar:2.0"
+        compileConfiguration.dependencies[2].children[0].resolvable == true
+        compileConfiguration.dependencies[2].children[0].alreadyRendered == false
+        compileConfiguration.dependencies[2].children[0].hasConflict == false
+        compileConfiguration.dependencies[2].children[0].children.empty
+
+        compileConfiguration.dependencies[3].module == null
+        compileConfiguration.dependencies[3].name == "project :d"
+        compileConfiguration.dependencies[3].resolvable == true
+        compileConfiguration.dependencies[3].alreadyRendered == false
+        compileConfiguration.dependencies[3].hasConflict == false
+        compileConfiguration.dependencies[3].children.size() == 1
+        compileConfiguration.dependencies[3].children[0].module == null
+        compileConfiguration.dependencies[3].children[0].name == "project :e"
+        compileConfiguration.dependencies[3].children[0].resolvable == true
+        compileConfiguration.dependencies[3].children[0].alreadyRendered == false
+        compileConfiguration.dependencies[3].children[0].hasConflict == false
+        compileConfiguration.dependencies[3].children[0].children.size() == 1
+        compileConfiguration.dependencies[3].children[0].children[0].module == "foo:bar"
+        compileConfiguration.dependencies[3].children[0].children[0].name == "foo:bar:2.0"
+        compileConfiguration.dependencies[3].children[0].children[0].resolvable == true
+        compileConfiguration.dependencies[3].children[0].children[0].alreadyRendered == false
+        compileConfiguration.dependencies[3].children[0].children[0].hasConflict == false
+        compileConfiguration.dependencies[3].children[0].children[0].children.empty
+    }
+
+    private def readGeneratedJson(fileNameWithoutExtension) {
+        TestFile htmlReport = file("build/reports/project/dependencies/" + fileNameWithoutExtension + ".js")
+        String content = htmlReport.getText("utf-8");
+        // remove the variable declaration
+        int equalSignIndex = content.indexOf('=');
+        String jsonAsString = content.substring(equalSignIndex + 1);
+        // remove the trailing ;
+        jsonAsString = jsonAsString.substring(0, jsonAsString.length() - 1);
+        JsonSlurper json = new JsonSlurper()
+        return json.parseText(jsonAsString)
+    }
+}
\ No newline at end of file
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
index dee91ad..6624d9b 100644
--- 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
@@ -17,6 +17,7 @@
 package org.gradle.api.tasks.diagnostics
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Ignore
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
@@ -25,6 +26,87 @@ class DependencyInsightReportTaskIntegrationTest extends AbstractIntegrationSpec
         executer.requireOwnGradleUserHomeDir()
     }
 
+    def "requires use of configuration flag if Java plugin isn't applied"() {
+        given:
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+        """
+
+        when:
+        def failure = runAndFail("dependencyInsight", "--dependency", "unknown")
+
+        then:
+        failure.assertHasCause("Dependency insight report cannot be generated because the input configuration was not specified.")
+    }
+
+    def "indicates that requested dependency cannot be found for default configuration"() {
+        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") << """
+            apply plugin: 'java'
+
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+        """
+
+        when:
+        run "dependencyInsight", "--dependency", "unknown"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+No dependencies matching given input were found in configuration ':compile'
+"""))
+    }
+
+    def "indicates that requested dependency cannot be found for custom configuration"() {
+        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") << """
+            apply plugin: 'java'
+
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+        """
+
+        when:
+        run "dependencyInsight", "--dependency", "unknown", "--configuration", "conf"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+No dependencies matching given input were found in configuration ':conf'
+"""))
+    }
+
     def "shows basic single tree with repeated dependency"() {
         given:
         mavenRepo.module("org", "leaf1").publish()
@@ -45,7 +127,7 @@ class DependencyInsightReportTaskIntegrationTest extends AbstractIntegrationSpec
                 conf 'org:top:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'leaf2' }
+                setDependencySpec { it.requested.module == 'leaf2' }
                 configuration = configurations.conf
             }
         """
@@ -142,7 +224,7 @@ org:leaf2:1.5 -> 2.5
             }
             task insight(type: DependencyInsightReportTask) {
                 configuration = configurations.conf
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
             }
         """
 
@@ -163,13 +245,13 @@ org:leaf:2.0 -> 1.0
 
     def "shows multiple outgoing dependencies"() {
         given:
-        mavenRepo.module("org", "leaf", "1.0").publish()
-        mavenRepo.module("org", "middle", "1.0")
+        ivyRepo.module("org", "leaf", "1.0").publish()
+        ivyRepo.module("org", "middle", "1.0")
                 .dependsOn("org", "leaf", "1.0")
                 .dependsOn("org", "leaf", "[1.0,2.0]")
                 .dependsOn("org", "leaf", "latest.integration")
                 .publish()
-        mavenRepo.module("org", "top", "1.0")
+        ivyRepo.module("org", "top", "1.0")
                 .dependsOn("org", "middle", "1.0")
                 .dependsOn("org", "middle", "[1.0,2.0]")
                 .dependsOn("org", "middle", "latest.integration")
@@ -177,7 +259,7 @@ org:leaf:2.0 -> 1.0
 
         file("build.gradle") << """
             repositories {
-                maven { url "${mavenRepo.uri}" }
+                ivy { url "${ivyRepo.uri}" }
             }
             configurations {
                 conf
@@ -188,7 +270,7 @@ org:leaf:2.0 -> 1.0
                 conf 'org:top:latest.integration'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
                 configuration = configurations.conf
             }
         """
@@ -197,21 +279,18 @@ org:leaf:2.0 -> 1.0
         run "insight"
 
         then:
-        // TODO - need to use a fixed ordering for dynamic requested versions
         output.contains(toPlatformLineSeparators("""
 org:leaf:1.0
 \\--- org:middle:1.0
      \\--- org:top:1.0
           \\--- conf
-"""))
-        output.contains(toPlatformLineSeparators("""
-org:leaf:latest.integration -> 1.0
+
+org:leaf:[1.0,2.0] -> 1.0
 \\--- org:middle:1.0
      \\--- org:top:1.0
           \\--- conf
-"""))
-        output.contains(toPlatformLineSeparators("""
-org:leaf:[1.0,2.0] -> 1.0
+
+org:leaf:latest.integration -> 1.0
 \\--- org:middle:1.0
      \\--- org:top:1.0
           \\--- conf
@@ -240,7 +319,7 @@ org:leaf:[1.0,2.0] -> 1.0
             }
             task insight(type: DependencyInsightReportTask) {
                 configuration = configurations.conf
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
             }
         """
 
@@ -259,6 +338,94 @@ org:leaf:2.0 -> 1.0
 """))
     }
 
+    def "shows substituted modules"() {
+        given:
+        mavenRepo.module("org", "new-leaf", 77).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 {
+                    resolutionStrategy.eachDependency { if (it.requested.name == 'leaf') { it.useTarget('org:new-leaf:77') } }
+                }
+            }
+            dependencies {
+                conf 'org:foo:1.0', 'org:bar:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                configuration = configurations.conf
+                setDependencySpec { it.requested.module == 'leaf' }
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:new-leaf:77 (selected by rule)
+
+org:leaf:1.0 -> org:new-leaf:77
+\\--- org:foo:1.0
+     \\--- conf
+
+org:leaf:2.0 -> org:new-leaf:77
+\\--- org:bar:1.0
+     \\--- conf
+"""))
+    }
+
+    def "shows version resolved from dynamic selectors"() {
+        given:
+        ivyRepo.module("org", "leaf", "1.6").publish()
+        ivyRepo.module("org", "top", "1.0")
+                .dependsOn("org", "leaf", "[1.5,1.9]")
+                .dependsOn("org", "leaf", "latest.integration")
+                .dependsOn("org", "leaf", "1.+")
+                .publish()
+
+        file("build.gradle") << """
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.module == 'leaf' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.6
+
+org:leaf:1.+ -> 1.6
+\\--- org:top:1.0
+     \\--- conf
+
+org:leaf:[1.5,1.9] -> 1.6
+\\--- org:top:1.0
+     \\--- conf
+
+org:leaf:latest.integration -> 1.6
+\\--- org:top:1.0
+     \\--- conf
+"""))
+    }
+
     def "forced version matches the conflict resolution"() {
         given:
         mavenRepo.module("org", "leaf", 1.0).publish()
@@ -280,7 +447,7 @@ org:leaf:2.0 -> 1.0
             }
             task insight(type: DependencyInsightReportTask) {
                 configuration = configurations.conf
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
             }
         """
 
@@ -321,7 +488,7 @@ org:leaf:1.0 -> 2.0
             }
             task insight(type: DependencyInsightReportTask) {
                 configuration = configurations.conf
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
             }
         """
 
@@ -365,7 +532,7 @@ org:leaf:2.0 -> 1.5
             }
             task insight(type: DependencyInsightReportTask) {
                 configuration = configurations.conf
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
             }
         """
 
@@ -389,7 +556,7 @@ org:leaf:2.0 -> 1.0
         given:
         file("build.gradle") << """
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'leaf2' }
+                setDependencySpec { it.requested.module == 'leaf2' }
             }
         """
 
@@ -407,7 +574,7 @@ org:leaf:2.0 -> 1.0
                 conf
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'whatever' }
+                setDependencySpec { it.requested.module == 'whatever' }
                 configuration = configurations.conf
             }
         """
@@ -434,7 +601,7 @@ org:leaf:2.0 -> 1.0
                 conf 'org:top:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'foo.unknown' }
+                setDependencySpec { it.requested.module == 'foo.unknown' }
                 configuration = configurations.conf
             }
         """
@@ -461,7 +628,7 @@ org:leaf:2.0 -> 1.0
                 conf 'org:top:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'middle' }
+                setDependencySpec { it.requested.module == 'middle' }
                 configuration = configurations.conf
             }
         """
@@ -497,7 +664,7 @@ org:middle:1.0 FAILED
                 conf 'org:top:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'middle' }
+                setDependencySpec { it.requested.module == 'middle' }
                 configuration = configurations.conf
             }
         """
@@ -532,7 +699,7 @@ org:middle:1.0 -> 2.0 FAILED
                 conf 'org:middle:2.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'middle' }
+                setDependencySpec { it.requested.module == 'middle' }
                 configuration = configurations.conf
             }
         """
@@ -569,7 +736,7 @@ org:middle:1.0 -> 2.0 FAILED
                 conf 'org:top:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'middle' }
+                setDependencySpec { it.requested.module == 'middle' }
                 configuration = configurations.conf
             }
         """
@@ -587,13 +754,14 @@ org:middle:1.0 -> 2.0+ FAILED
 """))
     }
 
-    def "shows multiple failed outgoing dependencies"() {
+    @Ignore
+    def "shows version resolved from a range where some selectors did not match anything"() {
         given:
-        mavenRepo.module("org", "leaf", "1.0").publish()
+        mavenRepo.module("org", "leaf", "1.5").publish()
         mavenRepo.module("org", "top", "1.0")
                 .dependsOn("org", "leaf", "1.0")
-                .dependsOn("org", "leaf", "[1.5,2.0]")
-                .dependsOn("org", "leaf", "1.6+")
+                .dependsOn("org", "leaf", "[1.5,1.9]")
+                .dependsOn("org", "leaf", "0.8+")
                 .publish()
 
         file("build.gradle") << """
@@ -607,7 +775,7 @@ org:middle:1.0 -> 2.0+ FAILED
                 conf 'org:top:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'leaf' }
+                setDependencySpec { it.requested.module == 'leaf' }
                 configuration = configurations.conf
             }
         """
@@ -616,18 +784,60 @@ org:middle:1.0 -> 2.0+ FAILED
         run "insight"
 
         then:
-        // TODO - need to use a fixed ordering for dynamic requested versions
         output.contains(toPlatformLineSeparators("""
-org:leaf:1.0
+org:leaf:1.5 (conflict resolution)
+
+org:leaf:1.0 -> 1.5
+\\--- org:top:1.0
+     \\--- conf
+
+org:leaf:0.8+ -> 1.5
+\\--- org:top:1.0
+     \\--- conf
+
+org:leaf:[1.5,1.9] -> 1.5
 \\--- org:top:1.0
      \\--- conf
 """))
+    }
+
+    def "shows multiple failed outgoing dependencies"() {
+        given:
+        ivyRepo.module("org", "top", "1.0")
+                .dependsOn("org", "leaf", "1.0")
+                .dependsOn("org", "leaf", "[1.5,2.0]")
+                .dependsOn("org", "leaf", "1.6+")
+                .publish()
+
+        file("build.gradle") << """
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.module == 'leaf' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
         output.contains(toPlatformLineSeparators("""
+org:leaf:1.0 FAILED
+\\--- org:top:1.0
+     \\--- conf
+
 org:leaf:1.6+ FAILED
 \\--- org:top:1.0
      \\--- conf
-"""))
-        output.contains(toPlatformLineSeparators("""
+
 org:leaf:[1.5,2.0] FAILED
 \\--- org:top:1.0
      \\--- conf
@@ -650,7 +860,7 @@ org:leaf:[1.5,2.0] FAILED
                 conf 'org:leaf1:1.0'
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'leaf2' }
+                setDependencySpec { it.requested.module == 'leaf2' }
                 configuration = configurations.conf
             }
         """
@@ -697,12 +907,12 @@ org:leaf2:1.0
 
         then:
         output.contains(toPlatformLineSeparators("""
-org.foo:root:1.0
-\\--- org.foo:impl:1.0
-     \\--- org.foo:root:1.0 (*)"""))
+project :
+\\--- project :impl
+     \\--- project : (*)"""))
     }
 
-    def "shows project dependencies"() {
+    def "selects a module component dependency with a given name"() {
         given:
         mavenRepo.module("org", "leaf1").dependsOn("leaf2").publish()
         mavenRepo.module("org", "leaf2").dependsOn("leaf3").publish()
@@ -728,7 +938,7 @@ org.foo:root:1.0
                 }
             }
             task insight(type: DependencyInsightReportTask) {
-                setDependencySpec { it.requested.name == 'leaf2' }
+                setDependencySpec { it.requested instanceof ModuleComponentSelector && it.requested.module == 'leaf2' }
                 configuration = configurations.compile
             }
         """
@@ -740,8 +950,139 @@ org.foo:root:1.0
         output.contains(toPlatformLineSeparators("""
 org:leaf2:1.0
 \\--- org:leaf1:1.0
-     \\--- org.foo:impl:1.0-SNAPSHOT
+     \\--- project :impl
           \\--- compile
 """))
     }
+
+    def "selects a project component dependency with a given project path"() {
+        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 instanceof ProjectComponentSelector && it.requested.projectPath == ':impl' }
+                configuration = configurations.compile
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+project :impl
+\\--- compile
+"""))
+    }
+
+    def "selects a module component dependency with a given name with dependency command line option"() {
+        given:
+        mavenRepo.module("org", "leaf1").dependsOn("leaf2").publish()
+        mavenRepo.module("org", "leaf2").dependsOn("leaf3").publish()
+        mavenRepo.module("org", "leaf3").publish()
+        mavenRepo.module("org", "leaf4").publish()
+
+        file("settings.gradle") << "include 'api', '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(':api') {
+                dependencies {
+                    compile 'org:leaf1:1.0'
+                }
+            }
+            project(':impl') {
+                dependencies {
+                    compile project(':api')
+                    compile 'org:leaf4:1.0'
+                }
+            }
+        """
+
+        when:
+        run "dependencyInsight", "--dependency", "leaf4"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf4:1.0
+\\--- project :impl
+     \\--- compile
+"""))
+    }
+
+    def "selects a project component dependency with a given name with dependency command line option"() {
+        given:
+        mavenRepo.module("org", "leaf1").dependsOn("leaf2").publish()
+        mavenRepo.module("org", "leaf2").dependsOn("leaf3").publish()
+        mavenRepo.module("org", "leaf3").publish()
+        mavenRepo.module("org", "leaf4").publish()
+
+        file("settings.gradle") << "include 'api', '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(':api') {
+                dependencies {
+                    compile 'org:leaf1:1.0'
+                }
+            }
+            project(':impl') {
+                dependencies {
+                    compile project(':api')
+                    compile 'org:leaf4:1.0'
+                }
+            }
+        """
+
+        when:
+        run "dependencyInsight", "--dependency", ":api"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+project :api
+\\--- project :impl
+     \\--- 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
index 94ecc8e..48d38c4 100644
--- 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
@@ -240,33 +240,33 @@ rootProject.name = 'root'
             }
 
             project(":a") {
-               dependencies {
+                dependencies {
                     compile 'foo:bar:1.0'
                 }
             }
 
             project(":b") {
-               dependencies {
+                dependencies {
                     compile 'foo:bar:0.5.dont.exist'
                 }
             }
 
             project(":c") {
-               dependencies {
+                dependencies {
                     compile 'foo:bar:3.0'
-               }
+                }
             }
 
             project(":d") {
-               dependencies {
+                dependencies {
                     compile 'foo:bar:2.0'
-               }
+                }
             }
 
             project(":e") {
-               dependencies {
+                dependencies {
                     compile 'foo:bar:3.0'
-               }
+                }
             }
 
             dependencies {
@@ -278,19 +278,19 @@ rootProject.name = 'root'
         run ":dependencies"
 
         then:
-        output.contains 'compile - Classpath for compiling the main sources.'
+        output.contains "compile - Compile classpath for source set 'main'."
 
         output.contains(toPlatformLineSeparators("""
-+--- root:a:1.0
++--- project :a
 |    \\--- foo:bar:1.0 -> 3.0
 |         \\--- foo:baz:5.0
-+--- root:b:1.0
++--- project :b
 |    \\--- foo:bar:0.5.dont.exist -> 3.0 (*)
-+--- root:c:1.0
++--- project :c
 |    \\--- foo:bar:3.0 (*)
-+--- root:d:1.0
++--- project :d
 |    \\--- foo:bar:2.0 -> 3.0 (*)
-\\--- root:e:1.0
+\\--- project :e
      \\--- foo:bar:3.0 (*)
 """))
     }
@@ -411,13 +411,13 @@ rootProject.name = 'root'
     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.configuration('first', extendsFrom: ['second'])
+        module.configuration('second')
         module.publish()
 
         module = ivyRepo.module("org", "parent").dependsOn('child')
-        module.configurations['first'] = [extendsFrom: ['second'], transitive: true]
-        module.configurations['second'] = [extendsFrom: [], transitive: true]
+        module.configuration('first', extendsFrom: ['second'])
+        module.configuration('second')
         module.publish()
 
         file("build.gradle") << """
@@ -589,4 +589,76 @@ conf2
 \\--- org.utils:impl:1.3 FAILED
 """))
     }
+
+    def "renders a mix of project and external dependencies"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).publish()
+        mavenRepo.module("foo", "bar", 2.0).publish()
+
+        file("settings.gradle") << """include 'a', 'b', 'a: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(":a:c") {
+               dependencies {
+                    compile 'foo:bar:2.0'
+               }
+            }
+
+            project(":d") {
+               dependencies {
+                    compile project(":e")
+                }
+            }
+
+            project(":e") {
+               dependencies {
+                    compile 'foo:bar:2.0'
+                }
+            }
+
+            dependencies {
+                compile project(":a"), project(":b"), project(":a:c"), project(":d")
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains "compile - Compile classpath for source set 'main'."
+
+        output.contains(toPlatformLineSeparators("""
++--- project :a
+|    \\--- foo:bar:1.0 -> 2.0
++--- project :b
+|    \\--- foo:bar:0.5.dont.exist -> 2.0
++--- project :a:c
+|    \\--- foo:bar:2.0
+\\--- project :d
+     \\--- project :e
+          \\--- foo:bar:2.0
+"""))
+    }
 }
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy
new file mode 100644
index 0000000..693fcf3
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class HelpTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final TestResources resources = new TestResources(temporaryFolder)
+
+    def "can print help for implicit tasks"() {
+        when:
+        run "help", "--task", "dependencies"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for dependencies
+
+Path
+     :dependencies
+
+Type
+     DependencyReportTask (org.gradle.api.tasks.diagnostics.DependencyReportTask)
+
+Options
+     --configuration     The configuration to generate the report for.
+
+Description
+     Displays all dependencies declared in root project '${testDirectory.getName()}'.
+
+
+BUILD SUCCESSFUL"""))
+    }
+
+
+    def "can print help for placeholder added tasks"() {
+        when:
+        run "help", "--task", "help"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for help
+
+Path
+     :help
+
+Type
+     Help (org.gradle.configuration.Help)
+
+Options
+     --task     The task, detailed help is requested for.
+
+Description
+     Displays a help message
+
+
+BUILD SUCCESSFUL"""))
+    }
+
+
+    def "help for tasks same type different descriptions"() {
+        setup:
+        settingsFile.text = """
+include ":someproj"
+"""
+        buildFile.text = """
+        task hello {
+            description = "hello task from root"
+        }
+        project(":someproj"){
+            task hello {
+                description = "hello task from someproj"
+            }
+        }
+"""
+        when:
+        run "help", "--task", "hello"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for hello
+
+Paths
+     :hello
+     :someproj:hello
+
+Type
+     Task (org.gradle.api.Task)
+
+Descriptions
+     (:hello) hello task from root
+     (:someproj:hello) hello task from someproj"""))
+    }
+
+
+    def "matchingTasksOfSameType"() {
+        setup:
+        settingsFile << "include ':subproj1'"
+        buildFile << "allprojects{ apply plugin:'java'}"
+        when:
+        run "help", "--task", ":jar"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for :jar
+
+Path
+     :jar
+
+Type
+     Jar (org.gradle.api.tasks.bundling.Jar)
+
+Description
+     Assembles a jar archive containing the main classes.
+
+
+BUILD SUCCESSFUL"""))
+
+        when:
+        run "help", "--task", "jar"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for jar
+
+Paths
+     :jar
+     :subproj1:jar
+
+Type
+     Jar (org.gradle.api.tasks.bundling.Jar)
+
+Description
+     Assembles a jar archive containing the main classes.
+
+
+BUILD SUCCESSFUL"""))
+
+    }
+
+    def "multipleMatchingTasksOfDifferentType"() {
+        setup:
+        settingsFile << "include ':subproj1'"
+        buildFile << """task someTask(type:Jar){
+            description = "an archiving operation"
+        }
+
+        project(":subproj1"){
+            task someTask(type:Copy){
+                description = "a copy operation"
+            }
+        }"""
+
+        when:
+        run "help", "--task", "someTask"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for someTask
+
+Path
+     :subproj1:someTask
+
+Type
+     Copy (org.gradle.api.tasks.Copy)
+
+Description
+     a copy operation
+
+----------------------
+
+Path
+     :someTask
+
+Type
+     Jar (org.gradle.api.tasks.bundling.Jar)
+
+Description
+     an archiving operation
+
+----------------------
+
+BUILD SUCCESSFUL"""))
+    }
+
+    def "error message contains possible candidates"() {
+        buildFile.text = """
+        task aTask
+"""
+        when:
+        fails "help", "--task", "bTask"
+        then:
+        errorOutput.contains(" Task 'bTask' not found in root project '${testDirectory.getName()}'. Some candidates are: 'aTask', 'tasks'")
+    }
+
+    def "tasks can be defined by camelCase matching"() {
+        buildFile.text = """
+        task someCamelCaseTask{
+            description = "a description"
+        }"""
+        when:
+        run "help", "--task", "sCC"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for sCC
+
+Path
+     :someCamelCaseTask
+
+Type
+     Task (org.gradle.api.Task)
+
+Description
+     a description"""))
+
+    }
+
+    def "prints hint when using invalid commandlineoptions"() {
+        when:
+        fails "help", "--tasssk", "help"
+
+        then:
+        failure.assertHasDescription("Problem configuring task :help from command line.")
+        failure.assertHasCause("Unknown command-line option '--tasssk'.")
+        failure.assertHasResolution("Run gradle help --task :help to get task usage details. Run with --info or --debug option to get more log output.")
+    }
+
+    def "listsEnumAndBooleanCmdOptionValues"() {
+        when:
+        run "help", "--task", "hello"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for hello
+
+Paths
+     :hello
+     :proj1:hello
+     :proj2:hello
+
+Type
+     CustomTask (CustomTask)
+
+Options
+     --booleanValue     Configures a boolean flag in CustomTask.
+
+     --enumValue     Configures an enum value in CustomTask.
+                     Available values are:
+                          ABC
+                          DEF
+                          GHIJKL
+
+Description
+     -"""))
+    }
+
+    def "listsCommonDynamicAvailableValues"() {
+        when:
+        run "help", "--task", "hello"
+        then:
+        output.contains(toPlatformLineSeparators("""Detailed task information for hello
+
+Paths
+     :sub1:hello
+     :sub2:hello
+
+Type
+     CustomTask (CustomTask)
+
+Options
+     --stringValue     Configures a string value in CustomTask.
+                       Available values are:
+                            optionA
+                            optionB
+
+Description
+     -"""))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/ResolutionResultApiIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/ResolutionResultApiIntegrationTest.groovy
deleted file mode 100644
index 1aaf46d..0000000
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/ResolutionResultApiIntegrationTest.groovy
+++ /dev/null
@@ -1,79 +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.tasks.diagnostics
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-import static org.gradle.util.TextUtil.toPlatformLineSeparators
-
-class ResolutionResultApiIntegrationTest extends AbstractIntegrationSpec {
-
-    def setup() {
-        executer.requireOwnGradleUserHomeDir()
-    }
-
-    /*
-    The ResolutionResult API is also covered by the dependency report integration tests.
-     */
-
-    def "selection reasons are described"() {
-        given:
-        mavenRepo.module("org", "leaf", 1.0).publish()
-        mavenRepo.module("org", "leaf", 2.0).publish()
-        mavenRepo.module("org", "foo", 0.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()
-        mavenRepo.module("org", "baz", 1.0).dependsOn('org', 'foo',  '1.0').publish()
-
-        file("settings.gradle") << "rootProject.name = 'cool-project'"
-
-        file("build.gradle") << """
-            version = '5.0'
-            repositories {
-                maven { url "${mavenRepo.uri}" }
-            }
-            configurations {
-                conf
-            }
-            configurations.conf.resolutionStrategy.force 'org:leaf:2.0'
-            dependencies {
-                conf 'org:foo:0.5', 'org:bar:1.0', 'org:baz:1.0'
-            }
-            task resolutionResult << {
-                def result = configurations.conf.incoming.resolutionResult
-                result.allModuleVersions {
-                    println it.id.name + ":" + it.id.version + " " + it.selectionReason.description
-                }
-            }
-        """
-
-        when:
-        run "resolutionResult"
-
-        then:
-        output.contains(toPlatformLineSeparators("""
-cool-project:5.0 root
-foo:1.0 conflict resolution
-leaf:2.0 forced
-bar:1.0 requested
-baz:1.0 requested
-"""))
-    }
-}
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy
index b262c65..4d5acba 100644
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy
@@ -48,7 +48,7 @@ class TaskReportTaskIntegrationTest extends AbstractIntegrationSpec {
                     def name = it - "autoCreate"
                     name = name[0].toLowerCase() + name[1..-1]
                     if (tasks.findByName(name)) {
-                        project.tasks.add(it)
+                        project.tasks.create(it)
                     }
                 }
             }
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/configuration/HelpTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/configuration/HelpTest.groovy
new file mode 100644
index 0000000..3e6d843
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/configuration/HelpTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Project
+import org.gradle.execution.TaskSelectionException
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class HelpTest extends Specification {
+    Project project = TestUtil.createRootProject()
+    Help helpTask
+
+    def setup() {
+        helpTask = project.tasks.create("somehelp", Help.class)
+    }
+
+    def "gives decent error message for unknown tasks"() {
+        when:
+        helpTask.setTaskPath("notexisting")
+        helpTask.displayHelp()
+        then:
+        def e = thrown(TaskSelectionException)
+        e.message == "Task 'notexisting' not found in root project '${project.name}'."
+    }
+}
diff --git a/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsCommonDynamicAvailableValues/build.gradle b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsCommonDynamicAvailableValues/build.gradle
new file mode 100644
index 0000000..4176d84
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsCommonDynamicAvailableValues/build.gradle
@@ -0,0 +1,21 @@
+import org.gradle.api.internal.tasks.options.Option
+import org.gradle.api.internal.tasks.options.OptionValues
+
+subprojects{
+    task hello(type: CustomTask)
+}
+
+class CustomTask extends DefaultTask {
+    @TaskAction
+    void doSomething() {
+    }
+
+    @Option(option = "stringValue", description = "Configures a string value in CustomTask.")
+    public void setStringValue(String value) {
+    }
+
+    @OptionValues("stringValue")
+    public List<String> possibleValues(){
+        return Arrays.asList("optionA", "optionB", "$path")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsCommonDynamicAvailableValues/settings.gradle b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsCommonDynamicAvailableValues/settings.gradle
new file mode 100644
index 0000000..0e3b93f
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsCommonDynamicAvailableValues/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 'sub1'
+include 'sub2'
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsEnumAndBooleanCmdOptionValues/build.gradle b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsEnumAndBooleanCmdOptionValues/build.gradle
new file mode 100644
index 0000000..10857c8
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsEnumAndBooleanCmdOptionValues/build.gradle
@@ -0,0 +1,23 @@
+import org.gradle.api.internal.tasks.options.Option
+
+allprojects{
+    task hello(type: CustomTask)
+}
+
+class CustomTask extends DefaultTask {
+    @TaskAction
+    void doSomething() {
+    }
+
+    @Option(option = "enumValue", description = "Configures an enum value in CustomTask.")
+    public void setEnumValue(TestEnum value) {
+    }
+
+    @Option(option = "booleanValue", description = "Configures a boolean flag in CustomTask.")
+    public void setBooleanValue(boolean value) {
+    }
+}
+
+enum TestEnum {
+    ABC, DEF, GHIJKL
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsEnumAndBooleanCmdOptionValues/settings.gradle b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsEnumAndBooleanCmdOptionValues/settings.gradle
new file mode 100644
index 0000000..c913b39
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/resources/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest/listsEnumAndBooleanCmdOptionValues/settings.gradle
@@ -0,0 +1,2 @@
+include ':proj1'
+include ':proj2'
\ 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
index 1ef988e..c7e5587 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/HelpTasksPlugin.groovy
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/HelpTasksPlugin.groovy
@@ -19,39 +19,40 @@ 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 org.gradle.configuration.Help
 
 import static org.gradle.configuration.ImplicitTasksConfigurer.*
 
-/**
- * by Szczepan Faber, created at: 9/5/12
- */
 @Incubating
 class HelpTasksPlugin implements Plugin<ProjectInternal> {
 
     void apply(ProjectInternal project) {
-        project.implicitTasks.add(name: HELP_TASK, type: Help) {
+        project.implicitTasks.create(name: HELP_TASK, type: Help) {
             description = "Displays a help message"
             group = HELP_GROUP
         }
 
-        project.implicitTasks.add(name: PROJECTS_TASK, type: ProjectReportTask) {
+        project.implicitTasks.create(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)."
+        project.implicitTasks.create(name: TASKS_TASK, type: TaskReportTask) {
+            if (project.subprojects.empty) {
+                description = "Displays the tasks runnable from $project."
+            } else {
+                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) {
+        project.implicitTasks.create(name: PROPERTIES_TASK, type: PropertyReportTask) {
             description = "Displays the properties of $project."
             group = HELP_GROUP
         }
 
-        project.implicitTasks.add(name: DEPENDENCY_INSIGHT_TASK, type: DependencyInsightReportTask) { task ->
+        project.implicitTasks.create(name: DEPENDENCY_INSIGHT_TASK, type: DependencyInsightReportTask) { task ->
             description = "Displays the insight into a specific dependency in $project."
             group = HELP_GROUP
             project.plugins.withType(JavaPlugin) {
@@ -59,7 +60,7 @@ class HelpTasksPlugin implements Plugin<ProjectInternal> {
             }
         }
 
-        project.implicitTasks.add(name: DEPENDENCIES_TASK, type: DependencyReportTask) {
+        project.implicitTasks.create(name: DEPENDENCIES_TASK, type: DependencyReportTask) {
             description = "Displays all dependencies declared in $project."
             group = HELP_GROUP
         }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
index 245f237..3d244e4 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
@@ -18,7 +18,9 @@ package org.gradle.api.plugins;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.tasks.diagnostics.DependencyReportTask;
+import org.gradle.api.reporting.dependencies.HtmlDependencyReportTask;
 import org.gradle.api.tasks.diagnostics.PropertyReportTask;
 import org.gradle.api.tasks.diagnostics.TaskReportTask;
 
@@ -32,6 +34,7 @@ public class ProjectReportsPlugin implements Plugin<Project> {
     public static final String TASK_REPORT = "taskReport";
     public static final String PROPERTY_REPORT = "propertyReport";
     public static final String DEPENDENCY_REPORT = "dependencyReport";
+    public static final String HTML_DEPENDENCY_REPORT = "htmlDependencyReport";
     public static final String PROJECT_REPORT = "projectReport";
 
     public void apply(Project project) {
@@ -39,7 +42,7 @@ public class ProjectReportsPlugin implements Plugin<Project> {
         final ProjectReportsPluginConvention convention = new ProjectReportsPluginConvention(project);
         project.getConvention().getPlugins().put("projectReports", convention);
 
-        TaskReportTask taskReportTask = project.getTasks().add(TASK_REPORT, TaskReportTask.class);
+        TaskReportTask taskReportTask = project.getTasks().create(TASK_REPORT, TaskReportTask.class);
         taskReportTask.setDescription("Generates a report about your tasks.");
         taskReportTask.conventionMapping("outputFile", new Callable<Object>() {
             public Object call() throws Exception {
@@ -52,7 +55,7 @@ public class ProjectReportsPlugin implements Plugin<Project> {
             }
         });
 
-        PropertyReportTask propertyReportTask = project.getTasks().add(PROPERTY_REPORT, PropertyReportTask.class);
+        PropertyReportTask propertyReportTask = project.getTasks().create(PROPERTY_REPORT, PropertyReportTask.class);
         propertyReportTask.setDescription("Generates a report about your properties.");
         propertyReportTask.conventionMapping("outputFile", new Callable<Object>() {
             public Object call() throws Exception {
@@ -65,7 +68,7 @@ public class ProjectReportsPlugin implements Plugin<Project> {
             }
         });
 
-        DependencyReportTask dependencyReportTask = project.getTasks().add(DEPENDENCY_REPORT,
+        DependencyReportTask dependencyReportTask = project.getTasks().create(DEPENDENCY_REPORT,
                 DependencyReportTask.class);
         dependencyReportTask.setDescription("Generates a report about your library dependencies.");
         dependencyReportTask.conventionMapping("outputFile", new Callable<Object>() {
@@ -79,8 +82,22 @@ public class ProjectReportsPlugin implements Plugin<Project> {
             }
         });
 
-        Task projectReportTask = project.getTasks().add(PROJECT_REPORT);
-        projectReportTask.dependsOn(TASK_REPORT, PROPERTY_REPORT, DEPENDENCY_REPORT);
+        HtmlDependencyReportTask htmlDependencyReportTask = project.getTasks().create(HTML_DEPENDENCY_REPORT,
+                HtmlDependencyReportTask.class);
+        htmlDependencyReportTask.setDescription("Generates an HTML report about your library dependencies.");
+        new DslObject(htmlDependencyReportTask.getReports().getHtml()).getConventionMapping().map("destination", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return new File(convention.getProjectReportDir(), "dependencies");
+                    }
+                });
+        htmlDependencyReportTask.conventionMapping("projects", new Callable<Object>() {
+            public Object call() throws Exception {
+                return convention.getProjects();
+            }
+        });
+
+        Task projectReportTask = project.getTasks().create(PROJECT_REPORT);
+        projectReportTask.dependsOn(TASK_REPORT, PROPERTY_REPORT, DEPENDENCY_REPORT, HTML_DEPENDENCY_REPORT);
         projectReportTask.setDescription("Generates a report about your project.");
         projectReportTask.setGroup("reporting");
     }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/internal/HelpTasksAutoApplyAction.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/internal/HelpTasksAutoApplyAction.java
new file mode 100644
index 0000000..8852340
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/internal/HelpTasksAutoApplyAction.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.plugins.internal;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.configuration.project.ProjectConfigureAction;
+
+//This one should go away once we complete the auto-apply plugins
+public class HelpTasksAutoApplyAction implements ProjectConfigureAction {
+    public void execute(ProjectInternal project) {
+        project.getPlugins().apply("help-tasks");
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/DependencyReportContainer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/DependencyReportContainer.java
new file mode 100644
index 0000000..132795b
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/DependencyReportContainer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies;
+
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.reporting.ReportContainer;
+
+/**
+ * The set of reports that can be generated by the {@link HtmlDependencyReportTask} task type.
+ */
+public interface DependencyReportContainer extends ReportContainer<Report> {
+    /**
+     * The dependency HTML report
+     *
+     * @return The dependency HTML report
+     */
+    DirectoryReport getHtml();
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/HtmlDependencyReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/HtmlDependencyReportTask.java
new file mode 100644
index 0000000..864654c
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/HtmlDependencyReportTask.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies;
+
+import groovy.lang.Closure;
+import org.gradle.api.Incubating;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
+import org.gradle.api.reporting.Reporting;
+import org.gradle.api.reporting.dependencies.internal.DefaultDependencyReportContainer;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.reporting.dependencies.internal.HtmlDependencyReporter;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Generates an HTML dependency report. This report
+ * combines the features of the ASCII dependency report and those of the ASCII
+ * dependency insight report. For a given project, it generates a tree of the dependencies
+ * of every configuration, and each dependency can be clicked to show the insight of
+ * this dependency.
+ * <p>
+ * This task generates a report for the task's containing project by default. But it can also generate
+ * a report for multiple projects, by setting the value of the
+ * <code>projects</code> property. Here's how to generate an HTML
+ * dependency report for all the projects of a multi-project build, for example:
+ * <pre>
+ * htmlDependencyReport {
+ *     projects = project.allprojects
+ * }
+ * </pre>
+ * <p>
+ * The report is generated in the <code>build/reports/project/dependencies</code> directory by default.
+ * This can also be changed by setting the <code>outputDirectory</code>
+ * property.
+ */
+ at Incubating
+public class HtmlDependencyReportTask extends ConventionTask implements Reporting<DependencyReportContainer> {
+    private Set<Project> projects;
+    private final DefaultDependencyReportContainer reports;
+
+    public HtmlDependencyReportTask() {
+        reports = getServices().get(Instantiator.class).newInstance(DefaultDependencyReportContainer.class, this);
+        reports.getHtml().setEnabled(true);
+        getOutputs().upToDateWhen(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return false;
+            }
+        });
+    }
+
+    public DependencyReportContainer getReports() {
+        return reports;
+    }
+
+    public DependencyReportContainer reports(Closure closure) {
+        reports.configure(closure);
+        return reports;
+    }
+
+    @TaskAction
+    public void generate() {
+        if (!reports.getHtml().isEnabled()) {
+            setDidWork(false);
+            return;
+        }
+
+        try {
+            HtmlDependencyReporter reporter = new HtmlDependencyReporter(getServices().get(VersionMatcher.class));
+            reporter.setOutputDirectory(reports.getHtml().getDestination());
+            reporter.generate(getProjects());
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Returns the set of projects to generate a report for. By default, the report is generated for the task's
+     * containing project.
+     *
+     * @return The set of files.
+     */
+    public Set<Project> getProjects() {
+        return projects;
+    }
+
+    /**
+     * Specifies the set of projects to generate this report for.
+     *
+     * @param projects The set of projects. Must not be null.
+     */
+    public void setProjects(Set<Project> projects) {
+        this.projects = projects;
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/DefaultDependencyReportContainer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/DefaultDependencyReportContainer.java
new file mode 100644
index 0000000..280728a
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/DefaultDependencyReportContainer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.reporting.dependencies.DependencyReportContainer;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleDirectoryReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+
+public class DefaultDependencyReportContainer extends TaskReportContainer<Report> implements DependencyReportContainer {
+    public DefaultDependencyReportContainer(Task task) {
+        super(Report.class, task);
+        add(TaskGeneratedSingleDirectoryReport.class, "html", task, "index.html");
+    }
+
+    public DirectoryReport getHtml() {
+        return (DirectoryReport) getByName("html");
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/HtmlDependencyReporter.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/HtmlDependencyReporter.groovy
new file mode 100644
index 0000000..76ef2e4
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/HtmlDependencyReporter.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies.internal
+
+import org.gradle.api.Project
+import org.gradle.api.Transformer
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
+import org.gradle.util.GFileUtils
+
+/**
+ * Class responsible for the generation of an HTML dependency report.
+ * <p>
+ * The strategy is the following. The reporter uses an HTML template file containing a
+ * placeholder <code>@js@</code>. For every project, it generates a JSON structure containing
+ * all the data that must be displayed by the report. A JS file declaring a single variable, containing
+ * this JSON structure, is then generated for the project. An HTML file is then generated from the template,
+ * by replacing a placeholder @js@ by the name of the generated JS file.
+ * The HTML file uses a JavaScript script to generate an interactive page from the data contained in
+ * the JSON structure.
+ * <p>
+ * The same technique is also used to generate the index report, listing all the projects for which
+ * a dependency report has been generated.
+ *
+ * @see JsonDependencyReportIndexRenderer
+ * @see JsonProjectDependencyRenderer
+ */
+class HtmlDependencyReporter {
+
+    File outputDirectory;
+    JsonProjectDependencyRenderer renderer
+    JsonDependencyReportIndexRenderer indexRenderer = new JsonDependencyReportIndexRenderer()
+
+    HtmlDependencyReporter(VersionMatcher versionMatcher) {
+        renderer = new JsonProjectDependencyRenderer(versionMatcher)
+    }
+
+    /**
+     * Sets the output directory of the report. This directory contains the generated HTML file,
+     * but also JS and CSS files. This method must be called before generating the report.
+     */
+    void setOutputDirectory(File outputDirectory) {
+        this.outputDirectory = outputDirectory
+    }
+
+    /**
+     * Generates a report for each of the given projects, and generates the index report
+     */
+    void generate(Set<Project> projects) throws IOException {
+        GFileUtils.copyURLToFile(getClass().getResource("/org/gradle/reporting/base-style.css"), new File(outputDirectory, "base-style.css"))
+        copyReportFile("d.gif")
+        copyReportFile("d.png")
+        copyReportFile("jquery.jstree.js")
+        copyReportFile("jquery-1.10.1.min.js")
+        copyReportFile("script.js")
+        copyReportFile("style.css")
+        copyReportFile("throbber.gif")
+        copyReportFile("tree.css")
+        copyReportFile("index.html")
+
+        String template = readHtmlTemplate();
+        for (Project project : projects) {
+            String jsFileName = toFileName(project, '.js')
+            generateJsFile(project, jsFileName)
+            String htmlFileName = toFileName(project, '.html')
+            generateHtmlFile(template, htmlFileName, jsFileName)
+        }
+
+        generateIndexJsFile(projects, 'index.js')
+    }
+
+    private void generateJsFile(Project project, String fileName) {
+        String json = renderer.render(project)
+        String content = "var projectDependencyReport = " + json + ";";
+        GFileUtils.writeFile(content, new File(outputDirectory, fileName), "utf-8")
+    }
+
+    private void generateIndexJsFile(Set<Project> projects, String fileName) {
+        String json = indexRenderer.render(projects, new Transformer<String, Project>() {
+            String transform(Project project) {
+                toFileName(project, ".html")
+            }
+        })
+
+        String content = "var mainDependencyReport = " + json.toString() + ";";
+        GFileUtils.writeFile(content, new File(outputDirectory, fileName), "utf-8")
+    }
+
+    private void generateHtmlFile(String template, String fileName, String jsFileName) {
+        String content = template.replace('@js@', jsFileName);
+        GFileUtils.writeFile(content, new File(outputDirectory, fileName), "utf-8")
+    }
+
+    private copyReportFile(String fileName) {
+        GFileUtils.copyURLToFile(getClass().getResource(getReportResourcePath(fileName)),
+                                 new File(outputDirectory, fileName))
+
+    }
+
+    private String readHtmlTemplate() {
+        getClass().getResourceAsStream(getReportResourcePath("template.html")).getText("UTF8")
+    }
+
+    private String getReportResourcePath(String fileName) {
+        "/org/gradle/api/tasks/diagnostics/htmldependencyreport/" + fileName
+    }
+
+    private String toFileName(Project project, String extension) {
+        String name = project.path
+        if (name.equals(':')) {
+            return "root" + extension
+        }
+        return "root" + name.replace(':', '.') + extension;
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/JsonDependencyReportIndexRenderer.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/JsonDependencyReportIndexRenderer.groovy
new file mode 100644
index 0000000..8749b64
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/JsonDependencyReportIndexRenderer.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies.internal
+import groovy.json.JsonBuilder
+import org.gradle.api.Project
+import org.gradle.api.Transformer
+import org.gradle.util.GradleVersion
+/**
+ * Renderer that emits a JSON tree containing the structure of the data displayed in the index page
+ * of an HTML dependency report (list of projects).
+ * The structure is the following:
+ * <pre>
+ *     {
+ *         "gradleVersion" : "...",
+ *         "generationDate" : "...",
+ *         "projects" : [
+ *             {
+ *                 "path" : "...",
+ *                 "name" : "...",
+ *                 "description" : "...",
+ *                 "file" : "..."
+ *             },
+ *             ...
+ *         ]
+ *     }
+ * </pre>
+ */
+class JsonDependencyReportIndexRenderer {
+
+    /**
+     * Generates the project dependency report structure
+     * @param project the project for which the report must be generated
+     * @return the generated JSON, as a String
+     */
+    String render(Set<Project> projects, Transformer<String, Project> projectToFileName) {
+        JsonBuilder json = new JsonBuilder();
+        renderProjects(projects, projectToFileName, json);
+        return json.toString();
+    }
+
+    private void renderProjects(Set<Project> projects,
+                                Transformer<String, Project> projectToFileName,
+                                JsonBuilder json) {
+        List<Project> sortedProjects = sortProjects(projects)
+        List jsonProjectList = sortedProjects.collect { project ->
+            [
+                path: "root" + project.path,
+                name: project.name,
+                description: project.description,
+                file: projectToFileName.transform(project)
+            ]
+        }
+        json gradleVersion : GradleVersion.current().toString(),
+             generationDate : new Date().toString(),
+             projects : jsonProjectList
+    }
+
+    private List<Project> sortProjects(Set<Project> projects) {
+        List<Project> sortedProjects = new ArrayList<Project>(projects);
+        sortedProjects.sort {
+            it.path
+        }
+        sortedProjects
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/JsonProjectDependencyRenderer.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/JsonProjectDependencyRenderer.groovy
new file mode 100644
index 0000000..dae08e2
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/JsonProjectDependencyRenderer.groovy
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies.internal
+
+import groovy.json.JsonBuilder
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ModuleIdentifier
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.api.artifacts.result.DependencyResult
+import org.gradle.api.artifacts.result.ResolutionResult
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableModuleResult
+import org.gradle.api.tasks.diagnostics.internal.insight.DependencyInsightReporter
+import org.gradle.util.GradleVersion
+
+/**
+ * Renderer that emits a JSON tree containing the HTML dependency report structure for a given project.
+ * The structure is the following:
+ *
+ * <pre>
+ *     {
+ *          "gradleVersion" : "...",
+ *          "generationDate" : "...",
+ *          "project" : {
+ *               "name" : "...",
+ *               "description : "...", (optional)
+ *               "configurations" : [
+ *                   "name" : "...",
+ *                   "description" : "...", (optional)
+ *                   "dependencies" : [
+ *                       {
+ *                           "module" : "group:name"
+ *                           "name" : "...",
+ *                           "resolvable" : true|false,
+ *                           "alreadyRendered" : true|false
+ *                           "hasConflict" : true|false
+ *                           "children" : [
+ *                               same array as configurations.dependencies.children
+ *                           ]
+ *                       },
+ *                       ...
+ *                   ],
+ *                   "moduleInsights : [
+ *                       {
+ *                           "module" : "group:name"
+ *                           "insight" : [
+ *                               {
+ *                                   "name" : "...",
+ *                                   "description" : "...",
+ *                                   "resolvable" : true|false,
+ *                                   "hasConflict" : true|false,
+ *                                   "children": [
+ *                                       {
+ *                                           "name" : "...",
+ *                                           "resolvable" : "...",
+ *                                           "hasConflict" : true|false,
+ *                                           "alreadyRendered" : true|false
+ *                                           "isLeaf" : true|false
+ *                                           "children" : [
+ *                                               same array as configurations.moduleInsights.insight.children
+ *                                           ]
+ *                                       },
+ *                                       ...
+ *                                   ]
+ *                               },
+ *                               ...
+ *                           ]
+ *                       }
+ *                       ,
+ *                       ...
+ *                   ]
+ *               ]
+ *          }
+ *      }
+ * </pre>
+ */
+class JsonProjectDependencyRenderer {
+    private final VersionMatcher versionMatcher
+
+    JsonProjectDependencyRenderer(VersionMatcher versionMatcher) {
+        this.versionMatcher = versionMatcher
+    }
+
+    /**
+     * Generates the project dependency report structure
+     * @param project the project for which the report must be generated
+     * @return the generated JSON, as a String
+     */
+    String render(Project project) {
+        JsonBuilder json = new JsonBuilder();
+        renderProject(project, json);
+        return json.toString();
+    }
+
+    private void renderProject(Project project, json) {
+        json gradleVersion : GradleVersion.current().toString(),
+             generationDate : new Date().toString(),
+             project : [
+                 name : project.name,
+                 description : project.description,
+                 configurations : createConfigurations(project)
+             ]
+    }
+
+    private List createConfigurations(Project project) {
+        return project.configurations.collect { configuration -> [
+                name : configuration.name,
+                description : configuration.description,
+                dependencies : createDependencies(configuration),
+                moduleInsights : createModuleInsights(configuration)
+            ]
+        }
+    }
+
+    private List createDependencies(Configuration configuration) {
+        ResolutionResult result = configuration.incoming.resolutionResult
+        RenderableDependency root = new RenderableModuleResult(result.getRoot())
+        Set<ComponentIdentifier> visited = new HashSet<>()
+        return createDependencyChildren(root, visited);
+    }
+
+    private List createDependencyChildren(RenderableDependency dependency, Set<ComponentIdentifier> visited) {
+        dependency.children.collect { childDependency ->
+            boolean alreadyVisited = !visited.add(childDependency.id);
+            boolean alreadyRendered = alreadyVisited && !childDependency.children.empty
+            String name = replaceArrow(childDependency.name)
+            boolean hasConflict = name != childDependency.name
+            def result = [
+                module : getModuleIdentifier(childDependency)?.toString(),
+                name : name,
+                resolvable : childDependency.resolvable,
+                hasConflict : hasConflict,
+                alreadyRendered : alreadyRendered,
+                children : Collections.emptyList()
+            ]
+            if (!alreadyRendered) {
+                result.children = createDependencyChildren(childDependency, visited)
+            }
+            return result
+        }
+    }
+
+    private ModuleIdentifier getModuleIdentifier(RenderableDependency renderableDependency) {
+        if(renderableDependency.id instanceof ModuleComponentIdentifier) {
+            return new DefaultModuleIdentifier(renderableDependency.id.group, renderableDependency.id.module)
+        }
+    }
+
+    private List createModuleInsights(Configuration configuration) {
+        Set<ModuleIdentifier> modules = collectModules(configuration)
+        return modules.collect { module ->
+            createModuleInsight(module, configuration)
+        }
+    }
+
+    private Set<ModuleIdentifier> collectModules(Configuration configuration) {
+        ResolutionResult result = configuration.incoming.resolutionResult
+        RenderableDependency root = new RenderableModuleResult(result.getRoot())
+        Set<ModuleIdentifier> modules = new HashSet<>()
+        Set<ComponentIdentifier> visited = new HashSet<>()
+        populateModulesWithChildDependencies(root, visited, modules)
+        return modules
+    }
+
+    private void populateModulesWithChildDependencies(RenderableDependency dependency, Set<ComponentIdentifier> visited, Set<ModuleIdentifier> modules) {
+        for (RenderableDependency childDependency : dependency.children) {
+            def moduleId = getModuleIdentifier(childDependency)
+            modules.add(moduleId)
+            boolean alreadyVisited = !visited.add(childDependency.id);
+            if (!alreadyVisited) {
+                populateModulesWithChildDependencies(childDependency, visited, modules)
+            }
+        }
+    }
+
+    private Map createModuleInsight(ModuleIdentifier module, Configuration configuration) {
+        [
+            module : module?.toString(),
+            insight : createInsight(module, configuration)
+        ]
+    }
+
+    private List createInsight(ModuleIdentifier module, Configuration configuration) {
+        Spec<DependencyResult> dependencySpec = new StrictDependencyResultSpec(module)
+
+        ResolutionResult result = configuration.incoming.resolutionResult;
+        Set<DependencyResult> selectedDependencies = new LinkedHashSet<DependencyResult>()
+
+        result.allDependencies { DependencyResult it ->
+            if (dependencySpec.isSatisfiedBy(it)) {
+                selectedDependencies << it
+            }
+        }
+
+        Collection<RenderableDependency> sortedDeps = new DependencyInsightReporter().prepare(selectedDependencies, versionMatcher)
+        return sortedDeps.collect { dependency ->
+            String name = replaceArrow(dependency.name);
+            [
+                name : replaceArrow(dependency.name),
+                description : dependency.description,
+                resolvable : dependency.resolvable,
+                hasConflict : name != dependency.name,
+                children : createInsightDependencyChildren(dependency, new HashSet<ModuleVersionIdentifier>(), configuration)
+            ]
+        }
+    }
+
+    private List createInsightDependencyChildren(RenderableDependency dependency, Set<ModuleVersionIdentifier> visited, Configuration configuration) {
+        dependency.children.collect { childDependency ->
+            boolean alreadyVisited = !visited.add(childDependency.id);
+            boolean leaf = childDependency.children.empty
+            boolean alreadyRendered = alreadyVisited && !leaf
+            String childName = replaceArrow(childDependency.name);
+            boolean hasConflict = childName != childDependency.name;
+            String name = leaf ? configuration.name : childName
+            def result = [
+                    name : name,
+                    resolvable : childDependency.resolvable,
+                    hasConflict : hasConflict,
+                    alreadyRendered : alreadyRendered,
+                    isLeaf: leaf,
+                    children : Collections.emptyList()
+            ]
+            if (!alreadyRendered) {
+                result.children = createInsightDependencyChildren(childDependency, visited, configuration)
+            }
+            return result
+        }
+    }
+
+    private String replaceArrow(String name) {
+        return name.replace(' -> ', ' \u27A1 ')
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/StrictDependencyResultSpec.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/StrictDependencyResultSpec.java
new file mode 100644
index 0000000..fe95744
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/internal/StrictDependencyResultSpec.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies.internal;
+
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.specs.Spec;
+
+/**
+ * A DependencyResultSpec that matches requested and selected modules if their
+ * group and name match strictly with the given group and name
+ */
+public class StrictDependencyResultSpec implements Spec<DependencyResult> {
+    private final ModuleIdentifier moduleIdentifier;
+
+    public StrictDependencyResultSpec(ModuleIdentifier moduleIdentifier) {
+        this.moduleIdentifier = moduleIdentifier;
+    }
+
+    public boolean isSatisfiedBy(DependencyResult candidate) {
+        if (candidate instanceof ResolvedDependencyResult) {
+            return matchesRequested(candidate) || matchesSelected((ResolvedDependencyResult) candidate);
+        } else {
+            return matchesRequested(candidate);
+        }
+    }
+
+    private boolean matchesRequested(DependencyResult candidate) {
+        ComponentSelector requested = candidate.getRequested();
+
+        if (moduleIdentifier != null && requested instanceof ModuleComponentSelector) {
+            ModuleComponentSelector requestedSelector = (ModuleComponentSelector) requested;
+            return requestedSelector.getGroup().equals(moduleIdentifier.getGroup())
+                    && requestedSelector.getModule().equals(moduleIdentifier.getName());
+        }
+
+        return false;
+    }
+
+    private boolean matchesSelected(ResolvedDependencyResult candidate) {
+        ComponentIdentifier selected = candidate.getSelected().getId();
+
+        if (moduleIdentifier != null && selected instanceof ModuleComponentIdentifier) {
+            ModuleComponentIdentifier selectedModule = (ModuleComponentIdentifier) selected;
+            return selectedModule.getGroup().equals(moduleIdentifier.getGroup())
+                    && selectedModule.getModule().equals(moduleIdentifier.getName());
+        }
+
+        return false;
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/package-info.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/package-info.java
new file mode 100644
index 0000000..16bfaae
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/dependencies/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 responsible for generating dependency reports.
+ */
+package org.gradle.api.reporting.dependencies;
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
index 82b1751..9f4926b 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
@@ -44,7 +44,7 @@ public abstract class AbstractReportTask extends ConventionTask {
     protected AbstractReportTask() {
         getOutputs().upToDateWhen(new Spec<Task>() {
             public boolean isSatisfiedBy(Task element) {
-                return getOutputFile() != null;
+                return false;
             }
         });
         projects = new HashSet<Project>();
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
index 30657fa..ea366d6 100644
--- 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
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-package org.gradle.api.tasks.diagnostics;
-
+package org.gradle.api.tasks.diagnostics
 
 import org.gradle.api.Action
 import org.gradle.api.DefaultTask
@@ -24,24 +23,22 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.result.DependencyResult
 import org.gradle.api.artifacts.result.ResolutionResult
-import org.gradle.api.internal.tasks.CommandLineOption
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
+import org.gradle.api.internal.tasks.options.Option
 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.internal.graph.GraphRenderer
 import org.gradle.logging.StyledTextOutput
 import org.gradle.logging.StyledTextOutputFactory
 
 import javax.inject.Inject
 
-import static org.gradle.logging.StyledTextOutput.Style.Info
-import static org.gradle.logging.StyledTextOutput.Style.Failure
-import static org.gradle.logging.StyledTextOutput.Style.Identifier
-import static org.gradle.logging.StyledTextOutput.Style.Description
+import static org.gradle.logging.StyledTextOutput.Style.*
 
 /**
  * Generates a report that attempts to answer questions like:
@@ -85,11 +82,13 @@ public class DependencyInsightReportTask extends DefaultTask {
 
     private final StyledTextOutput output;
     private final GraphRenderer renderer;
+    private final VersionMatcher versionMatcher;
 
     @Inject
-    DependencyInsightReportTask(StyledTextOutputFactory outputFactory) {
+    DependencyInsightReportTask(StyledTextOutputFactory outputFactory, VersionMatcher versionMatcher) {
         output = outputFactory.create(getClass());
-        renderer = new GraphRenderer(output);
+        renderer = new GraphRenderer(output)
+        this.versionMatcher = versionMatcher
     }
 
     /**
@@ -115,7 +114,7 @@ public class DependencyInsightReportTask extends DefaultTask {
      *
      * @param dependencyInsightNotation
      */
-    @CommandLineOption(options = "dependency", description = "Shows the details of given dependency.")
+    @Option(option = "dependency", description = "Shows the details of given dependency.")
     public void setDependencySpec(Object dependencyInsightNotation) {
         def parser = DependencyResultSpecNotationParser.create()
         this.dependencySpec = parser.parseNotation(dependencyInsightNotation)
@@ -138,7 +137,7 @@ public class DependencyInsightReportTask extends DefaultTask {
      *
      * @param configurationName
      */
-    @CommandLineOption(options = "configuration", description = "Looks for the dependency in given configuration.")
+    @Option(option = "configuration", description = "Looks for the dependency in given configuration.")
     public void setConfiguration(String configurationName) {
         this.configuration = project.configurations.getByName(configurationName)
     }
@@ -168,7 +167,7 @@ public class DependencyInsightReportTask extends DefaultTask {
             return
         }
 
-        def sortedDeps = new DependencyInsightReporter().prepare(selectedDependencies)
+        def sortedDeps = new DependencyInsightReporter().prepare(selectedDependencies, versionMatcher)
 
         def nodeRenderer = new NodeRenderer() {
             void renderNode(StyledTextOutput output, RenderableDependency node, boolean alreadyRendered) {
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
index 2a03f4c..ae706e9 100644
--- 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
@@ -17,7 +17,7 @@ package org.gradle.api.tasks.diagnostics;
 
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.api.internal.tasks.options.Option;
 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;
@@ -28,8 +28,6 @@ import java.util.*;
 /**
  * Displays the dependency tree for a project. 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 {
 
@@ -90,7 +88,7 @@ public class DependencyReportTask extends AbstractReportTask {
      *
      * @param configurationName name of the configuration to generate the report for
      */
-    @CommandLineOption(options = "configuration", description = "The configuration to generate the report for.")
+    @Option(option = "configuration", description = "The configuration to generate the report for.")
     public void setConfiguration(String configurationName) {
         this.configurations = Collections.singleton(getProject().getConfigurations().getByName(configurationName));
     }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
index d4a1dbd..5c0ca6e 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
@@ -18,16 +18,15 @@ package org.gradle.api.tasks.diagnostics;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.Action;
 import org.gradle.api.Project;
-import org.gradle.api.tasks.diagnostics.internal.GraphRenderer;
 import org.gradle.api.tasks.diagnostics.internal.TextReportRenderer;
 import org.gradle.configuration.ImplicitTasksConfigurer;
 import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.graph.GraphRenderer;
 import org.gradle.logging.StyledTextOutput;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GUtil;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 import static org.gradle.logging.StyledTextOutput.Style.*;
@@ -96,8 +95,6 @@ public class ProjectReportTask extends AbstractReportTask {
     }
 
     private List<Project> getChildren(Project project) {
-        List<Project> children = new ArrayList<Project>(project.getChildProjects().values());
-        Collections.sort(children);
-        return children;
+        return CollectionUtils.sort(project.getChildProjects().values());
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
index e93040e..ae2fef1 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
@@ -19,7 +19,7 @@ import com.google.common.collect.Sets;
 import org.gradle.api.Project;
 import org.gradle.api.Rule;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.api.internal.tasks.options.Option;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.tasks.diagnostics.internal.*;
 
@@ -42,7 +42,7 @@ public class TaskReportTask extends AbstractReportTask {
         this.renderer = renderer;
     }
 
-    @CommandLineOption(options = "all", description = "Show additional tasks and detail.")
+    @Option(option = "all", description = "Show additional tasks and detail.")
     public void setShowDetail(boolean detail) {
         this.detail = detail;
     }
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
index d26be81..9a7e945 100644
--- 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
@@ -21,8 +21,6 @@ import java.io.IOException;
 
 /**
  * Renders the model of a project dependency report.
- *
- * @author Phil Messenger
  */
 public interface DependencyReportRenderer extends ReportRenderer {
     /**
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
deleted file mode 100644
index 03eef17..0000000
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.java
+++ /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.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/diagnostics/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
index 5bc8491..24e045c 100644
--- a/subprojects/diagnostics/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
@@ -18,8 +18,8 @@ package org.gradle.api.tasks.diagnostics.internal;
 import com.google.common.collect.SetMultimap;
 import com.google.common.collect.TreeMultimap;
 import org.gradle.api.Task;
-import org.gradle.api.internal.DirectedGraph;
-import org.gradle.api.internal.GraphAggregator;
+import org.gradle.internal.graph.DirectedGraph;
+import org.gradle.internal.graph.GraphAggregator;
 import org.gradle.util.GUtil;
 import org.gradle.util.Path;
 
@@ -49,7 +49,7 @@ public class SingleProjectTaskReportModel implements TaskReportModel {
             }
         }
         GraphAggregator<Task> aggregator = new GraphAggregator<Task>(new DirectedGraph<Task, Object>() {
-            public void getNodeValues(Task node, Collection<Object> values, Collection<Task> connectedNodes) {
+            public void getNodeValues(Task node, Collection<? super Object> values, Collection<? super Task> connectedNodes) {
                 for (Task dep : node.getTaskDependencies().getDependencies(node)) {
                     if (containsTaskWithPath(tasks, dep.getPath())) {
                         connectedNodes.add(dep);
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
index 7d560d1..19257ac 100644
--- 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
@@ -32,8 +32,6 @@ 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;
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
index 11a44e9..e18db8e 100644
--- 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
@@ -20,13 +20,13 @@ import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 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.internal.graph.GraphRenderer;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.util.GUtil;
 
@@ -36,8 +36,6 @@ 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;
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
index 5bcdf10..013a79d 100644
--- 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
@@ -17,13 +17,12 @@
 package org.gradle.api.tasks.diagnostics.internal.dsl;
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
 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;
 
@@ -42,13 +41,20 @@ class DependencyResultSpec implements Spec<DependencyResult> {
     }
 
     private boolean matchesRequested(DependencyResult candidate) {
-        String requestedCandidate = candidate.getRequested().getGroup() + ":" + candidate.getRequested().getName() + ":" + candidate.getRequested().getVersion();
-        return requestedCandidate.contains(stringNotation);
+        ComponentSelector requested = candidate.getRequested();
+
+        if(requested instanceof ModuleComponentSelector) {
+            ModuleComponentSelector requestedModule = (ModuleComponentSelector)requested;
+            String requestedCandidate = requestedModule.getGroup() + ":" + requestedModule.getModule() + ":" + requestedModule.getVersion();
+            return requestedCandidate.contains(stringNotation);
+        }
+
+        return false;
     }
 
     private boolean matchesSelected(ResolvedDependencyResult candidate) {
-        ModuleVersionIdentifier selected = candidate.getSelected().getId();
-        String selectedCandidate = selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
+        ModuleVersionIdentifier selected = candidate.getSelected().getModuleVersion();
+        String selectedCandidate = selected.getGroup() + ":" + selected.getModule() + ":" + 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
index 1b8f16b..6199ab5 100644
--- 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
@@ -17,19 +17,16 @@
 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.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.TypeInfo;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.internal.typeconversion.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 class DependencyResultSpecNotationParser implements NotationParser<Object, Spec<DependencyResult>> {
 
     public Spec<DependencyResult> parseNotation(final Object notation) throws UnsupportedNotationException {
         if (notation instanceof CharSequence) {
@@ -46,7 +43,7 @@ public class DependencyResultSpecNotationParser implements NotationParser<Spec<D
         candidateFormats.add("Closure that returns boolean and takes a single DependencyResult as parameter.");
     }
 
-    public static NotationParser<Spec<DependencyResult>> create() {
+    public static NotationParser<Object, 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.")
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
index caf2438..28b70f2 100644
--- 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
@@ -17,16 +17,13 @@
 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.artifacts.component.ComponentIdentifier
 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
+import org.gradle.internal.graph.GraphRenderer
 import org.gradle.logging.StyledTextOutput
 
 import static org.gradle.logging.StyledTextOutput.Style.Info
 
-/**
- * by Szczepan Faber, created at: 9/20/12
- */
 class DependencyGraphRenderer {
     private final GraphRenderer renderer
     private final NodeRenderer nodeRenderer
@@ -38,12 +35,12 @@ class DependencyGraphRenderer {
     }
 
     void render(RenderableDependency root) {
-        def visited = new HashSet<ModuleVersionIdentifier>()
+        def visited = new HashSet<ComponentIdentifier>()
         visited.add(root.getId())
         renderChildren(root.getChildren(), visited)
     }
 
-    private void renderChildren(Set<? extends RenderableDependency> children, Set<ModuleVersionIdentifier> visited) {
+    private void renderChildren(Set<? extends RenderableDependency> children, Set<ComponentIdentifier> visited) {
         renderer.startChildren()
         def i = 0
         for (RenderableDependency child : children) {
@@ -53,7 +50,7 @@ class DependencyGraphRenderer {
         renderer.completeChildren()
     }
 
-    private void render(final RenderableDependency node, boolean last, Set<ModuleVersionIdentifier> visited) {
+    private void render(final RenderableDependency node, boolean last, Set<ComponentIdentifier> visited) {
         def children = node.getChildren()
         def alreadyRendered = !visited.add(node.getId())
         if (alreadyRendered) {
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
index eb4ddc1..ed21b83 100644
--- 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
@@ -19,9 +19,6 @@ 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, 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
index 2e8b5b3..08f1222 100644
--- 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
@@ -22,9 +22,6 @@ import org.gradle.logging.StyledTextOutput;
 import static org.gradle.logging.StyledTextOutput.Style.Failure;
 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, boolean alreadyRendered) {
         output.text(node.getName());
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
index 499fca1..5524569 100644
--- 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
@@ -16,50 +16,62 @@
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
 import org.gradle.api.Nullable;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
 
 public abstract class AbstractRenderableDependencyResult implements RenderableDependency {
-    public ModuleVersionIdentifier getId() {
+    public ComponentIdentifier getId() {
         return getActual();
     }
 
     public String getName() {
-        if (requestedEqualsSelected()) {
+        ComponentSelector requested = getRequested();
+        ComponentIdentifier selected = getActual();
+
+        if(requested.matchesStrictly(selected)) {
             return getSimpleName();
         }
-        return getVerboseName();
-    }
 
-    public abstract boolean isResolvable();
+        if(requested instanceof ModuleComponentSelector && selected instanceof ModuleComponentIdentifier) {
+            ModuleComponentSelector requestedModuleComponentSelector = (ModuleComponentSelector)requested;
+            ModuleComponentIdentifier selectedModuleComponentedIdentifier = (ModuleComponentIdentifier)selected;
+
+            if(isSameGroupAndModuleButDifferentVersion(requestedModuleComponentSelector, selectedModuleComponentedIdentifier)) {
+                return getSimpleName() + " -> " + selectedModuleComponentedIdentifier.getVersion();
+            }
+        }
 
-    private boolean requestedEqualsSelected() {
-        return getRequested().matchesStrictly(getActual());
+        return getSimpleName() + " -> " + selected.getDisplayName();
     }
 
-    @Nullable
-    public String getDescription() {
-        return null;
+    /**
+     * Checks if requested and selected module component differ by version.
+     *
+     * @param requested Requested module component selector
+     * @param selected Selected module component identifier
+     * @return Indicates whether version differs
+     */
+    private boolean isSameGroupAndModuleButDifferentVersion(ModuleComponentSelector requested, ModuleComponentIdentifier selected) {
+        return requested.getGroup().equals(selected.getGroup()) && requested.getModule().equals(selected.getModule()) && !requested.getVersion().equals(selected.getVersion());
     }
 
+    /**
+     * Gets simple name of requested component selector.
+     *
+     * @return Display name of requested component selector
+     */
     private String getSimpleName() {
-        ModuleVersionSelector requested = getRequested();
-        return requested.getGroup() + ":" + requested.getName() + ":" + requested.getVersion();
+        return getRequested().getDisplayName();
     }
 
-    private String getVerboseName() {
-        ModuleVersionSelector requested = getRequested();
-        ModuleVersionIdentifier selected = getActual();
-        if(!selected.getGroup().equals(requested.getGroup()) || !selected.getName().equals(requested.getName())) {
-            return getSimpleName() + " -> " + selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
-        }
-        if (!selected.getVersion().equals(requested.getVersion())) {
-            return getSimpleName() + " -> " + selected.getVersion();
-        }
-        return getSimpleName();
+    @Nullable
+    public String getDescription() {
+        return null;
     }
 
-    protected abstract ModuleVersionSelector getRequested();
+    protected abstract ComponentSelector getRequested();
 
-    protected abstract ModuleVersionIdentifier getActual();
+    protected abstract ComponentIdentifier getActual();
 }
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
index 95a3606..8003fad 100644
--- 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
@@ -16,28 +16,25 @@
 
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 
 import java.util.Set;
 
-/**
- * by Szczepan Faber, created at: 8/10/12
- */
 public abstract class AbstractRenderableModuleResult implements RenderableDependency {
 
-    protected final ResolvedModuleVersionResult module;
+    protected final ResolvedComponentResult module;
 
-    public AbstractRenderableModuleResult(ResolvedModuleVersionResult module) {
+    public AbstractRenderableModuleResult(ResolvedComponentResult module) {
         this.module = module;
     }
 
-    public ModuleVersionIdentifier getId() {
+    public ComponentIdentifier getId() {
         return module.getId();
     }
 
     public String getName() {
-        return module.getId().getGroup() + ":" + module.getId().getName() + ":" + module.getId().getVersion();
+        return getId().getDisplayName();
     }
 
     public String getDescription() {
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java
index 98669a2..295bf1d 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java
@@ -16,22 +16,22 @@
 
 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.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
 import java.util.Set;
 
 public interface DependencyEdge {
     boolean isResolvable();
 
-    ModuleVersionSelector getRequested();
+    ComponentSelector getRequested();
 
-    ModuleVersionIdentifier getActual();
+    ComponentIdentifier getActual();
 
-    ModuleVersionIdentifier getFrom();
+    ComponentIdentifier getFrom();
 
-    ModuleVersionSelectionReason getReason();
+    ComponentSelectionReason getReason();
 
     Set<? extends RenderableDependency> getChildren();
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyReportHeader.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyReportHeader.java
new file mode 100644
index 0000000..5da9896
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyReportHeader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class DependencyReportHeader implements RenderableDependency {
+    private final DependencyEdge dependency;
+
+    public DependencyReportHeader(DependencyEdge dependency) {
+        this.dependency = dependency;
+    }
+
+    public ComponentIdentifier getId() {
+        return dependency.getActual();
+    }
+
+    public String getName() {
+        return getId().getDisplayName();
+    }
+
+    public String getDescription() {
+        return getReasonDescription(dependency.getReason());
+    }
+
+    public boolean isResolvable() {
+        return dependency.isResolvable();
+    }
+
+    public Set<? extends RenderableDependency> getChildren() {
+        return Collections.emptySet();
+    }
+
+    private String getReasonDescription(ComponentSelectionReason reason) {
+        return !reason.isExpected() ? reason.getDescription() : null;
+    }
+}
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
index 4f3bc33..384cd2d 100644
--- 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
@@ -16,9 +16,9 @@
 
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
 
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -27,17 +27,15 @@ 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 {
 
-    public InvertedRenderableModuleResult(ResolvedModuleVersionResult module) {
+    public InvertedRenderableModuleResult(ResolvedComponentResult module) {
         super(module);
     }
 
     public Set<RenderableDependency> getChildren() {
-        Map<ModuleVersionIdentifier, RenderableDependency> children = new LinkedHashMap<ModuleVersionIdentifier, RenderableDependency>();
+        Map<ComponentIdentifier, RenderableDependency> children = new LinkedHashMap<ComponentIdentifier, RenderableDependency>();
         for (ResolvedDependencyResult dependent : module.getDependents()) {
             InvertedRenderableModuleResult child = new InvertedRenderableModuleResult(dependent.getFrom());
             if (!children.containsKey(child.getId())) {
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
index d5695b4..4df63af 100644
--- 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
@@ -16,15 +16,10 @@
 
 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();
+    Object getId();
     String getName();
     String getDescription();
     boolean isResolvable();
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
index e5f697a..01ff387 100644
--- 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
@@ -16,8 +16,8 @@
 
 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.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
 import org.gradle.api.artifacts.result.DependencyResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
@@ -25,9 +25,6 @@ import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-/**
- * by Szczepan Faber, created at: 7/27/12
- */
 public class RenderableDependencyResult extends AbstractRenderableDependencyResult {
     private final ResolvedDependencyResult dependency;
 
@@ -35,18 +32,17 @@ public class RenderableDependencyResult extends AbstractRenderableDependencyResu
         this.dependency = dependency;
     }
 
-    @Override
     public boolean isResolvable() {
         return true;
     }
 
     @Override
-    protected ModuleVersionIdentifier getActual() {
+    protected ComponentIdentifier getActual() {
         return dependency.getSelected().getId();
     }
 
     @Override
-    protected ModuleVersionSelector getRequested() {
+    protected ComponentSelector getRequested() {
         return dependency.getRequested();
     }
 
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
index 20c2a4b..ec5ae6e 100644
--- 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
@@ -17,19 +17,16 @@
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
 import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-/**
- * by Szczepan Faber, created at: 8/10/12
- */
 public class RenderableModuleResult extends AbstractRenderableModuleResult {
 
-    public RenderableModuleResult(ResolvedModuleVersionResult module) {
+    public RenderableModuleResult(ResolvedComponentResult module) {
         super(module);
     }
 
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java
index 555e930..97ba154 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java
@@ -16,40 +16,55 @@
 
 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.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 
 import java.util.Collections;
 import java.util.Set;
 
-public class RenderableUnresolvedDependencyResult extends AbstractRenderableDependencyResult {
-    private final ModuleVersionIdentifier actual;
+public class RenderableUnresolvedDependencyResult implements RenderableDependency {
     private final UnresolvedDependencyResult dependency;
 
     public RenderableUnresolvedDependencyResult(UnresolvedDependencyResult dependency) {
         this.dependency = dependency;
-        ModuleVersionSelector attempted = dependency.getAttempted();
-        this.actual = DefaultModuleVersionIdentifier.newId(attempted.getGroup(), attempted.getName(), attempted.getVersion());
     }
 
-    @Override
     public boolean isResolvable() {
         return false;
     }
 
-    @Override
-    protected ModuleVersionSelector getRequested() {
-        return dependency.getRequested();
+    public Set<RenderableDependency> getChildren() {
+        return Collections.emptySet();
     }
 
-    @Override
-    protected ModuleVersionIdentifier getActual() {
-        return actual;
+    public Object getId() {
+        return dependency.getAttempted();
     }
 
-    public Set<RenderableDependency> getChildren() {
-        return Collections.emptySet();
+    public String getDescription() {
+        return null;
+    }
+
+    public String getName() {
+        ComponentSelector requested = dependency.getRequested();
+        ComponentSelector attempted = dependency.getAttempted();
+
+        if(requested.equals(attempted)) {
+            return requested.getDisplayName();
+        }
+
+        if(requested instanceof ModuleComponentSelector && attempted instanceof ModuleComponentSelector) {
+            ModuleComponentSelector requestedSelector = (ModuleComponentSelector)requested;
+            ModuleComponentSelector attemptedSelector = (ModuleComponentSelector)attempted;
+
+            if(requestedSelector.getGroup().equals(attemptedSelector.getGroup())
+                    && requestedSelector.getModule().equals(attemptedSelector.getModule())
+                    && !requestedSelector.getVersion().equals(attemptedSelector.getVersion())) {
+                return requested.getDisplayName() + " -> " + ((ModuleComponentSelector) attempted).getVersion();
+            }
+        }
+
+        return requested.getDisplayName() + " -> " + attempted.getDisplayName();
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java
index 7d3365e..45ce668 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java
@@ -16,25 +16,20 @@
 
 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.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
 
 public class RequestedVersion extends AbstractRenderableDependencyResult {
-    private final ModuleVersionSelector requested;
-    private final ModuleVersionIdentifier actual;
+    private final ComponentSelector requested;
+    private final ComponentIdentifier actual;
     private final boolean resolvable;
     private final String description;
     private final Set<RenderableDependency> children = new LinkedHashSet<RenderableDependency>();
 
-    public RequestedVersion(ModuleVersionIdentifier actual, boolean resolvable, String description) {
-        this(DefaultModuleVersionSelector.newSelector(actual.getGroup(), actual.getName(), actual.getVersion()), actual, resolvable, description);
-    }
-
-    public RequestedVersion(ModuleVersionSelector requested, ModuleVersionIdentifier actual, boolean resolvable, String description) {
+    public RequestedVersion(ComponentSelector requested, ComponentIdentifier actual, boolean resolvable, String description) {
         this.requested = requested;
         this.actual = actual;
         this.resolvable = resolvable;
@@ -51,16 +46,15 @@ public class RequestedVersion extends AbstractRenderableDependencyResult {
     }
 
     @Override
-    protected ModuleVersionIdentifier getActual() {
+    protected ComponentIdentifier getActual() {
         return actual;
     }
 
     @Override
-    protected ModuleVersionSelector getRequested() {
+    protected ComponentSelector getRequested() {
         return requested;
     }
 
-    @Override
     public boolean isResolvable() {
         return resolvable;
     }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java
index d24c548..ae4444b 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java
@@ -16,9 +16,9 @@
 
 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.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
 
 import java.util.Collections;
@@ -35,19 +35,19 @@ public class ResolvedDependencyEdge implements DependencyEdge {
         return true;
     }
 
-    public ModuleVersionSelector getRequested() {
+    public ComponentSelector getRequested() {
         return dependency.getRequested();
     }
 
-    public ModuleVersionSelectionReason getReason() {
+    public ComponentSelectionReason getReason() {
         return dependency.getSelected().getSelectionReason();
     }
 
-    public ModuleVersionIdentifier getActual() {
+    public ComponentIdentifier getActual() {
         return dependency.getSelected().getId();
     }
 
-    public ModuleVersionIdentifier getFrom() {
+    public ComponentIdentifier getFrom() {
         return dependency.getFrom().getId();
     }
 
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
deleted file mode 100644
index 5517bac..0000000
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
+++ /dev/null
@@ -1,64 +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.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 boolean resolvable;
-    private final String description;
-    private final Set<RenderableDependency> children = new LinkedHashSet<RenderableDependency>();
-
-    public SimpleDependency(String name) {
-        this(name, true, null);
-    }
-
-    public SimpleDependency(String name, boolean resolvable, String description) {
-        this.name = name;
-        this.resolvable = resolvable;
-        this.description = description;
-        this.id = newId(name, name, "1.0");
-    }
-
-    public ModuleVersionIdentifier getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public boolean isResolvable() {
-        return resolvable;
-    }
-
-    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/graph/nodes/UnresolvedDependencyEdge.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/UnresolvedDependencyEdge.java
index 41ffe55..faa137e 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/UnresolvedDependencyEdge.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/UnresolvedDependencyEdge.java
@@ -16,43 +16,44 @@
 
 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.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier;
 
 import java.util.Collections;
 import java.util.Set;
 
 public class UnresolvedDependencyEdge implements DependencyEdge {
     private final UnresolvedDependencyResult dependency;
-    private final ModuleVersionIdentifier actual;
+    private final ModuleComponentIdentifier actual;
 
     public UnresolvedDependencyEdge(UnresolvedDependencyResult dependency) {
         this.dependency = dependency;
-        ModuleVersionSelector attempted = dependency.getAttempted();
-        actual = DefaultModuleVersionIdentifier.newId(attempted.getGroup(), attempted.getName(), attempted.getVersion());
+        ModuleComponentSelector attempted = (ModuleComponentSelector)dependency.getAttempted();
+        actual = DefaultModuleComponentIdentifier.newId(attempted.getGroup(), attempted.getModule(), attempted.getVersion());
     }
 
     public boolean isResolvable() {
         return false;
     }
 
-    public ModuleVersionSelector getRequested() {
+    public ComponentSelector getRequested() {
         return dependency.getRequested();
     }
 
-    public ModuleVersionIdentifier getActual() {
+    public ModuleComponentIdentifier getActual() {
         return actual;
     }
 
-    public ModuleVersionSelectionReason getReason() {
+    public ComponentSelectionReason getReason() {
         return dependency.getAttemptedReason();
     }
 
-    public ModuleVersionIdentifier getFrom() {
-        return dependency.getFrom().getId();
+    public ModuleComponentIdentifier getFrom() {
+        return (ModuleComponentIdentifier)dependency.getFrom().getId();
     }
 
     public Set<? extends RenderableDependency> getChildren() {
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
index f9258de..71ab21e 100644
--- 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
@@ -14,23 +14,21 @@
  * limitations under the License.
  */
 
-package org.gradle.api.tasks.diagnostics.internal.insight;
+package org.gradle.api.tasks.diagnostics.internal.insight
 
-
-import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.artifacts.result.ComponentSelectionReason
 import org.gradle.api.artifacts.result.DependencyResult
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.*
 
 /**
  * Created: 23/08/2012
- *
- * @author Szczepan Faber
  */
 public class DependencyInsightReporter {
 
-    Collection<RenderableDependency> prepare(Collection<DependencyResult> input) {
+    Collection<RenderableDependency> prepare(Collection<DependencyResult> input, VersionMatcher versionMatcher) {
         def out = new LinkedList<RenderableDependency>()
         def dependencies = input.collect {
             if (it instanceof UnresolvedDependencyResult) {
@@ -40,10 +38,10 @@ public class DependencyInsightReporter {
             }
         }
 
-        def sorted = DependencyResultSorter.sort(dependencies)
+        def sorted = DependencyResultSorter.sort(dependencies, versionMatcher)
 
         //remember if module id was annotated
-        def annotated = new HashSet<ModuleVersionIdentifier>()
+        def annotated = new HashSet<ComponentIdentifier>()
         def current = null
 
         for (DependencyEdge dependency: sorted) {
@@ -51,11 +49,11 @@ public class DependencyInsightReporter {
             if (annotated.add(dependency.actual)) {
                 //add a heading dependency with the annotation if the dependency does not exist in the graph
                 if (!dependency.requested.matchesStrictly(dependency.actual)) {
-                    out << new RequestedVersion(dependency.actual, dependency.resolvable, describeReason(dependency.reason))
+                    out << new DependencyReportHeader(dependency)
                     current = new RequestedVersion(dependency.requested, dependency.actual, dependency.resolvable, null)
                     out << current
                 } else {
-                    current = new RequestedVersion(dependency.requested, dependency.actual, dependency.resolvable, describeReason(dependency.reason))
+                    current = new RequestedVersion(dependency.requested, dependency.actual, dependency.resolvable, getReasonDescription(dependency.reason))
                     out << current
                 }
             } else if (current.requested != dependency.requested) {
@@ -68,11 +66,7 @@ public class DependencyInsightReporter {
         out
     }
 
-    private String describeReason(ModuleVersionSelectionReason reason) {
-        if (reason.conflictResolution || reason.forced || reason.selectedByRule) {
-            return reason.description
-        } else {
-            return null
-        }
+    private String getReasonDescription(ComponentSelectionReason reason) {
+        !reason.expected ? reason.description : null
     }
 }
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java
index ba99512..5e9fc59 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java
@@ -16,17 +16,16 @@
 
 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.version.LatestVersionSemanticComparator;
+import org.gradle.api.artifacts.component.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher;
 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.DependencyEdge;
+import org.gradle.util.CollectionUtils;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.Comparator;
 
 /**
  * Created: 17/08/2012
- *
- * @author Szczepan Faber
  */
 public class DependencyResultSorter {
 
@@ -35,25 +34,81 @@ public class DependencyResultSorter {
      * If requested matches selected then it will override the version comparison
      * so that the dependency that was selected is more prominent.
      */
-    public static Collection<DependencyEdge> sort(Collection<DependencyEdge> input) {
-        List<DependencyEdge> out = new ArrayList<DependencyEdge>(input);
-        Collections.sort(out, new DependencyComparator());
-        return out;
+    public static Collection<DependencyEdge> sort(Collection<DependencyEdge> input, VersionMatcher versionMatcher) {
+        return CollectionUtils.sort(input, new DependencyComparator(versionMatcher));
     }
 
     private static class DependencyComparator implements Comparator<DependencyEdge> {
+        private final VersionMatcher matcher;
 
-        private final LatestVersionSemanticComparator versionComparator = new LatestVersionSemanticComparator();
+        private DependencyComparator(VersionMatcher matcher) {
+            this.matcher = matcher;
+        }
 
         public int compare(DependencyEdge left, DependencyEdge right) {
-            ModuleVersionSelector leftRequested = left.getRequested();
-            ModuleVersionSelector rightRequested = right.getRequested();
+            checkRequestedComponentSelectorType(left);
+            checkRequestedComponentSelectorType(right);
+
+            if(isLeftProjectButRightIsModuleComponentSelector(left, right)) {
+                return -1;
+            }
+
+            if(isLeftModuleButRightIsProjectComponentSelector(left, right)) {
+                return 1;
+            }
+
+            if(isLeftAndRightProjectComponentSelector(left, right)) {
+                return compareRequestedProjectComponentSelectors(left, right);
+            }
+
+            if(isLeftAndRightModuleComponentSelector(left, right)) {
+                return compareModuleComponentSelectors(left, right);
+            }
+
+            return 0;
+        }
+
+        private void checkRequestedComponentSelectorType(DependencyEdge dependencyEdge) {
+            if(dependencyEdge == null || dependencyEdge.getRequested() == null) {
+                throw new IllegalArgumentException("Dependency edge or the requested component selector may not be null");
+            }
+
+            ComponentSelector requested = dependencyEdge.getRequested();
+
+            if(!isExpectedComponentSelector(requested)) {
+                throw new IllegalArgumentException("Unexpected component selector type for dependency edge: " + requested.getClass());
+            }
+        }
+
+        private boolean isExpectedComponentSelector(ComponentSelector componentSelector) {
+            return componentSelector instanceof ProjectComponentSelector || componentSelector instanceof ModuleComponentSelector;
+        }
+
+        private boolean isLeftProjectButRightIsModuleComponentSelector(DependencyEdge left, DependencyEdge right) {
+            return left.getRequested() instanceof ProjectComponentSelector && right.getRequested() instanceof ModuleComponentSelector;
+        }
+
+        private boolean isLeftModuleButRightIsProjectComponentSelector(DependencyEdge left, DependencyEdge right) {
+            return left.getRequested() instanceof ModuleComponentSelector && right.getRequested() instanceof ProjectComponentSelector;
+        }
+
+        private boolean isLeftAndRightProjectComponentSelector(DependencyEdge left, DependencyEdge right) {
+            return left.getRequested() instanceof ProjectComponentSelector && right.getRequested() instanceof ProjectComponentSelector;
+        }
+
+        private boolean isLeftAndRightModuleComponentSelector(DependencyEdge left, DependencyEdge right) {
+            return left.getRequested() instanceof ModuleComponentSelector && right.getRequested() instanceof ModuleComponentSelector;
+        }
+
+        private int compareModuleComponentSelectors(DependencyEdge left, DependencyEdge right) {
+            ModuleComponentSelector leftRequested = (ModuleComponentSelector)left.getRequested();
+            ModuleComponentSelector rightRequested = (ModuleComponentSelector)right.getRequested();
             int byGroup = leftRequested.getGroup().compareTo(rightRequested.getGroup());
             if (byGroup != 0) {
                 return byGroup;
             }
 
-            int byModule = leftRequested.getName().compareTo(rightRequested.getName());
+            int byModule = leftRequested.getModule().compareTo(rightRequested.getModule());
             if (byModule != 0) {
                 return byModule;
             }
@@ -67,24 +122,80 @@ public class DependencyResultSorter {
                 return 1;
             }
 
-            int byVersion = versionComparator.compare(leftRequested.getVersion(), rightRequested.getVersion());
+            //order dynamic selectors after static selectors
+            boolean leftDynamic = matcher.isDynamic(leftRequested.getVersion());
+            boolean rightDynamic = matcher.isDynamic(rightRequested.getVersion());
+            if (leftDynamic && !rightDynamic) {
+                return 1;
+            } else if (!leftDynamic && rightDynamic) {
+                return -1;
+            }
+
+            int byVersion;
+            if (leftDynamic && rightDynamic) {
+                // order dynamic selectors lexicographically
+                byVersion = leftRequested.getVersion().compareTo(rightRequested.getVersion());
+            } else {
+                // order static selectors semantically
+                byVersion = matcher.compare(leftRequested.getVersion(), rightRequested.getVersion());
+            }
             if (byVersion != 0) {
                 return byVersion;
             }
 
-            ModuleVersionIdentifier leftFrom = left.getFrom();
-            ModuleVersionIdentifier rightFrom = right.getFrom();
-            byGroup = leftFrom.getGroup().compareTo(rightFrom.getGroup());
+            return compareFromComponentIdentifiers(left.getFrom(), right.getFrom());
+        }
+
+        private int compareRequestedProjectComponentSelectors(DependencyEdge left, DependencyEdge right) {
+            ProjectComponentSelector leftRequested = (ProjectComponentSelector)left.getRequested();
+            ProjectComponentSelector rightRequested = (ProjectComponentSelector)right.getRequested();
+            return leftRequested.getProjectPath().compareTo(rightRequested.getProjectPath());
+        }
+
+        public int compareFromComponentIdentifiers(ComponentIdentifier left, ComponentIdentifier right) {
+            if(isLeftAndRightFromProjectComponentIdentifier(left, right)) {
+                return compareFromProjectComponentIdentifiers(left, right);
+            }
+
+            if(isLeftAndRightFromModuleComponentIdentifier(left, right)) {
+                return compareFromModuleComponentIdentifiers(left, right);
+            }
+
+            return isLeftFromProjectButRightIsModuleComponentIdentifier(left, right) ? -1 : 1;
+        }
+
+        private int compareFromProjectComponentIdentifiers(ComponentIdentifier left, ComponentIdentifier right) {
+            ProjectComponentIdentifier leftFrom = (ProjectComponentIdentifier)left;
+            ProjectComponentIdentifier rightFrom = (ProjectComponentIdentifier)right;
+            return leftFrom.getProjectPath().compareTo(rightFrom.getProjectPath());
+        }
+
+        private int compareFromModuleComponentIdentifiers(ComponentIdentifier left, ComponentIdentifier right) {
+            ModuleComponentIdentifier leftFrom = (ModuleComponentIdentifier)left;
+            ModuleComponentIdentifier rightFrom = (ModuleComponentIdentifier)right;
+            int byGroup = leftFrom.getGroup().compareTo(rightFrom.getGroup());
             if (byGroup != 0) {
                 return byGroup;
             }
 
-            byModule = leftFrom.getName().compareTo(rightFrom.getName());
+            int byModule = leftFrom.getModule().compareTo(rightFrom.getModule());
             if (byModule != 0) {
                 return byModule;
             }
 
-            return versionComparator.compare(leftFrom.getVersion(), rightFrom.getVersion());
+            return matcher.compare(leftFrom.getVersion(), rightFrom.getVersion());
+        }
+
+        private boolean isLeftAndRightFromProjectComponentIdentifier(ComponentIdentifier left, ComponentIdentifier right) {
+            return left instanceof ProjectComponentIdentifier && right instanceof ProjectComponentIdentifier;
+        }
+
+        private boolean isLeftAndRightFromModuleComponentIdentifier(ComponentIdentifier left, ComponentIdentifier right) {
+            return left instanceof ModuleComponentIdentifier && right instanceof ModuleComponentIdentifier;
+        }
+
+        private boolean isLeftFromProjectButRightIsModuleComponentIdentifier(ComponentIdentifier left, ComponentIdentifier right) {
+            return left instanceof ProjectComponentIdentifier && right instanceof ModuleComponentIdentifier;
         }
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java
index 2621929..60b875b 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java
@@ -16,7 +16,10 @@
 package org.gradle.configuration;
 
 import org.gradle.api.DefaultTask;
+import org.gradle.api.internal.tasks.options.Option;
+import org.gradle.api.internal.tasks.options.OptionReader;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.execution.TaskSelector;
 import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
@@ -25,11 +28,28 @@ import org.gradle.util.GradleVersion;
 import static org.gradle.logging.StyledTextOutput.Style.UserInput;
 
 public class Help extends DefaultTask {
+    private String taskPath;
+
     @TaskAction
     void displayHelp() {
         StyledTextOutput output = getServices().get(StyledTextOutputFactory.class).create(Help.class);
         BuildClientMetaData metaData = getServices().get(BuildClientMetaData.class);
+        if (taskPath != null) {
+            printTaskHelp(output);
+        } else {
+            printDefaultHelp(output, metaData);
+        }
+    }
+
+    private void printTaskHelp(StyledTextOutput output) {
+        TaskSelector selector = getServices().get(TaskSelector.class);
+        final TaskSelector.TaskSelection selection = selector.getSelection(taskPath);
+        final OptionReader optionReader = getServices().get(OptionReader.class);
+        TaskDetailPrinter taskDetailPrinter = new TaskDetailPrinter(taskPath, selection, optionReader);
+        taskDetailPrinter.print(output);
+    }
 
+    private void printDefaultHelp(StyledTextOutput output, BuildClientMetaData metaData) {
         output.println();
         output.formatln("Welcome to Gradle %s.", GradleVersion.current().getVersion());
         output.println();
@@ -45,4 +65,9 @@ public class Help extends DefaultTask {
         metaData.describeCommand(output.withStyle(UserInput), "--help");
         output.println();
     }
+
+    @Option(option = "task", description = "The task, detailed help is requested for.")
+    public void setTaskPath(String taskPath) {
+        this.taskPath = taskPath;
+    }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java
new file mode 100644
index 0000000..f7b0338
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.internal.tasks.options.OptionDescriptor;
+import org.gradle.api.internal.tasks.options.OptionReader;
+import org.gradle.api.specs.Spec;
+import org.gradle.execution.TaskSelector;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.logging.internal.LinePrefixingStyledTextOutput;
+
+import java.util.*;
+
+import static org.gradle.logging.StyledTextOutput.Style.UserInput;
+import static org.gradle.util.CollectionUtils.*;
+
+public class TaskDetailPrinter {
+    private final String taskPath;
+    private final TaskSelector.TaskSelection selection;
+    private static final String INDENT = "     ";
+    private final OptionReader optionReader;
+
+    public TaskDetailPrinter(String taskPath, TaskSelector.TaskSelection selection, OptionReader optionReader) {
+        this.taskPath = taskPath;
+        this.selection = selection;
+        this.optionReader = optionReader;
+    }
+
+    public void print(StyledTextOutput output) {
+        final List<Task> tasks = sort(selection.getTasks());
+
+        output.text("Detailed task information for ").withStyle(UserInput).println(taskPath);
+        final ListMultimap<Class, Task> classListMap = groupTasksByType(tasks);
+
+        final Set<Class> classes = classListMap.keySet();
+        boolean multipleClasses = classes.size() > 1;
+        final List<Class> sortedClasses = sort(classes, new Comparator<Class>() {
+            public int compare(Class o1, Class o2) {
+                return o1.getSimpleName().compareTo(o2.getSimpleName());
+            }
+        });
+        for (Class clazz : sortedClasses) {
+            output.println();
+            final List<Task> tasksByType = classListMap.get(clazz);
+            final LinePrefixingStyledTextOutput pathOutput = createIndentedOutput(output, INDENT);
+            pathOutput.println(tasksByType.size() > 1 ? "Paths" : "Path");
+            for (Task task : tasksByType) {
+                pathOutput.withStyle(UserInput).println(task.getPath());
+            }
+            output.println();
+            final LinePrefixingStyledTextOutput typeOutput = createIndentedOutput(output, INDENT);
+            typeOutput.println("Type");
+            typeOutput.withStyle(UserInput).text(clazz.getSimpleName());
+            typeOutput.println(String.format(" (%s)", clazz.getName()));
+
+            printlnCommandlineOptions(output, tasksByType);
+
+            output.println();
+            printTaskDescription(output, tasksByType);
+            output.println();
+            if (multipleClasses) {
+                output.println("----------------------");
+            }
+        }
+    }
+
+    private ListMultimap<Class, Task> groupTasksByType(List<Task> tasks) {
+        final Set<Class> taskTypes = new TreeSet<Class>(new Comparator<Class>() {
+            public int compare(Class o1, Class o2) {
+                return o1.getSimpleName().compareTo(o2.getSimpleName());
+            }
+        });
+        taskTypes.addAll(collect(tasks, new Transformer<Class, Task>() {
+            public Class transform(Task original) {
+                return getDeclaredTaskType(original);
+            }
+        }));
+
+        ListMultimap<Class, Task> tasksGroupedByType = ArrayListMultimap.create();
+        for (final Class taskType : taskTypes) {
+            tasksGroupedByType.putAll(taskType, filter(tasks, new Spec<Task>() {
+                public boolean isSatisfiedBy(Task element) {
+                    return getDeclaredTaskType(element).equals(taskType);
+                }
+            }));
+        }
+        return tasksGroupedByType;
+    }
+
+    private Class getDeclaredTaskType(Task original) {
+        Class clazz = new DslObject(original).getDeclaredType();
+        if (clazz.equals(DefaultTask.class)) {
+            return org.gradle.api.Task.class;
+        } else {
+            return clazz;
+        }
+    }
+
+    private void printTaskDescription(StyledTextOutput output, List<Task> tasks) {
+        int differentDescriptionsCount = differentDescriptions(tasks);
+        final LinePrefixingStyledTextOutput descriptorOutput = createIndentedOutput(output, INDENT);
+        descriptorOutput.println(differentDescriptionsCount > 1 ? "Descriptions" : "Description");
+        if (differentDescriptionsCount == 1) {
+            // all tasks have the same description
+            final Task task = tasks.iterator().next();
+            descriptorOutput.println(task.getDescription() == null ? "-" : task.getDescription());
+        } else {
+            for (Task task : tasks) {
+                descriptorOutput.withStyle(UserInput).text(String.format("(%s) ", task.getPath()));
+                descriptorOutput.println(task.getDescription() == null ? "-" : task.getDescription());
+            }
+        }
+    }
+
+    private void printlnCommandlineOptions(StyledTextOutput output, List<Task> tasks) {
+        List<OptionDescriptor> allOptions = new ArrayList<OptionDescriptor>();
+        for (Task task : tasks) {
+            allOptions.addAll(optionReader.getOptions(task));
+        }
+        if (!allOptions.isEmpty()) {
+            output.println();
+            output.text("Options").println();
+        }
+        final ListMultimap<String, OptionDescriptor> optionsByName = groupDescriptorsByName(allOptions);
+        Iterator<String> optionNames = sort(optionsByName.asMap().keySet()).iterator();
+        while (optionNames.hasNext()) {
+            final String currentOption = optionNames.next();
+            final List<OptionDescriptor> descriptorsForCurrentName = optionsByName.get(currentOption);
+
+            final String optionString = String.format("--%s", currentOption);
+            output.text(INDENT).withStyle(UserInput).text(optionString);
+
+            List<List<String>> availableValuesByDescriptor = collect(descriptorsForCurrentName, new Transformer<List<String>, OptionDescriptor>() {
+                public List<String> transform(OptionDescriptor original) {
+                    return original.getAvailableValues();
+                }
+            });
+
+            List<String> commonAvailableValues = intersection(availableValuesByDescriptor);
+            Set<String> availableValues = new TreeSet<String>(commonAvailableValues);
+            //description does not differ between task objects, grab first one
+            output.text(INDENT).text(descriptorsForCurrentName.iterator().next().getDescription());
+            if (!availableValues.isEmpty()) {
+                final int optionDescriptionOffset = 2 * INDENT.length() + optionString.length();
+                final LinePrefixingStyledTextOutput prefixedOutput = createIndentedOutput(output, optionDescriptionOffset);
+                prefixedOutput.println();
+                prefixedOutput.println("Available values are:");
+                for (String value : availableValues) {
+                    prefixedOutput.text(INDENT);
+                    prefixedOutput.withStyle(UserInput).println(value);
+                }
+            } else {
+                output.println();
+            }
+            if (optionNames.hasNext()) {
+                output.println();
+            }
+        }
+    }
+
+    private ListMultimap<String, OptionDescriptor> groupDescriptorsByName(List<OptionDescriptor> allOptions) {
+        ListMultimap<String, OptionDescriptor> optionsGroupedByName = ArrayListMultimap.create();
+        for (final OptionDescriptor option : allOptions) {
+            optionsGroupedByName.put(option.getName(), option);
+        }
+        return optionsGroupedByName;
+    }
+
+    private LinePrefixingStyledTextOutput createIndentedOutput(StyledTextOutput output, int offset) {
+        return createIndentedOutput(output, StringUtils.leftPad("", offset, ' '));
+    }
+
+    private LinePrefixingStyledTextOutput createIndentedOutput(StyledTextOutput output, String prefix) {
+        return new LinePrefixingStyledTextOutput(output, prefix);
+    }
+
+    private int differentDescriptions(List<Task> tasks) {
+        return toSet(
+                collect(tasks, new Transformer<String, Task>() {
+                    public String transform(Task original) {
+                        return original.getDescription();
+                    }
+                })
+        ).size();
+    }
+}
diff --git a/subprojects/diagnostics/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction b/subprojects/diagnostics/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
new file mode 100644
index 0000000..9d15f53
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
@@ -0,0 +1 @@
+org.gradle.api.plugins.internal.HelpTasksAutoApplyAction
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/d.gif b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/d.gif
new file mode 100644
index 0000000..0e958d3
Binary files /dev/null and b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/d.gif differ
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/d.png b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/d.png
new file mode 100644
index 0000000..8540175
Binary files /dev/null and b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/d.png differ
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/index.html b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/index.html
new file mode 100644
index 0000000..928c0ee
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/index.html
@@ -0,0 +1,48 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Dependency Reports</title>
+        <link href="base-style.css" rel="stylesheet" type="text/css"/>
+        <link href="style.css" rel="stylesheet" type="text/css"/>
+        <script src="jquery-1.10.1.min.js" charset="utf-8"></script>
+        <script src="index.js" charset="utf-8"></script>
+        <script src="script.js" charset="utf-8"></script>
+    </head>
+    <body>
+        <div id="content">
+            <h1>Dependency Reports</h1>
+            <div id="tab0" class="tab selected">
+                <table id="projects">
+                    <thead>
+                        <tr>
+                            <th>Project path</th>
+                            <th>Name</th>
+                            <th>Description</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                </table>
+            </div>
+            <div id="footer">
+                <p>Generated by <a id="gradleVersion" href="http://www.gradle.org"></a> at <span id="generationDate"></span></p>
+            </div>
+        </div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/jquery-1.10.1.min.js b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/jquery-1.10.1.min.js
new file mode 100644
index 0000000..e407e76
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/jquery-1.10.1.min.js
@@ -0,0 +1,6 @@
+/*! jQuery v1.10.1 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery-1.10.1.min.map
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.1",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/ [...]
+}),n=s=l=u=r=o=null,t}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"== [...]
+u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.first [...]
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/jquery.jstree.js b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/jquery.jstree.js
new file mode 100644
index 0000000..c92c2ac
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/jquery.jstree.js
@@ -0,0 +1,4564 @@
+/*
+ * jsTree 1.0-rc3
+ * http://jstree.com/
+ *
+ * Copyright (c) 2010 Ivan Bozhanov (vakata.com)
+ *
+ * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $
+ * $Revision: 236 $
+ */
+
+/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */
+/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false, ActiveXObject: false */
+
+"use strict";
+
+// top wrapper to prevent multiple inclusion (is this OK?)
+(function () { if(jQuery && jQuery.jstree) { return; }
+	var is_ie6 = false, is_ie7 = false, is_ff2 = false;
+
+/* 
+ * jsTree core
+ */
+(function ($) {
+	// Common functions not related to jsTree 
+	// decided to move them to a `vakata` "namespace"
+	$.vakata = {};
+	// CSS related functions
+	$.vakata.css = {
+		get_css : function(rule_name, delete_flag, sheet) {
+			rule_name = rule_name.toLowerCase();
+			var css_rules = sheet.cssRules || sheet.rules,
+				j = 0;
+			do {
+				if(css_rules.length && j > css_rules.length + 5) { return false; }
+				if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) {
+					if(delete_flag === true) {
+						if(sheet.removeRule) { sheet.removeRule(j); }
+						if(sheet.deleteRule) { sheet.deleteRule(j); }
+						return true;
+					}
+					else { return css_rules[j]; }
+				}
+			}
+			while (css_rules[++j]);
+			return false;
+		},
+		add_css : function(rule_name, sheet) {
+			if($.jstree.css.get_css(rule_name, false, sheet)) { return false; }
+			if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); }
+			return $.vakata.css.get_css(rule_name);
+		},
+		remove_css : function(rule_name, sheet) { 
+			return $.vakata.css.get_css(rule_name, true, sheet); 
+		},
+		add_sheet : function(opts) {
+			var tmp = false, is_new = true;
+			if(opts.str) {
+				if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; }
+				if(tmp) { is_new = false; }
+				else {
+					tmp = document.createElement("style");
+					tmp.setAttribute('type',"text/css");
+					if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); }
+				}
+				if(tmp.styleSheet) {
+					if(is_new) { 
+						document.getElementsByTagName("head")[0].appendChild(tmp); 
+						tmp.styleSheet.cssText = opts.str; 
+					}
+					else {
+						tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; 
+					}
+				}
+				else {
+					tmp.appendChild(document.createTextNode(opts.str));
+					document.getElementsByTagName("head")[0].appendChild(tmp);
+				}
+				return tmp.sheet || tmp.styleSheet;
+			}
+			if(opts.url) {
+				if(document.createStyleSheet) {
+					try { tmp = document.createStyleSheet(opts.url); } catch (e) { }
+				}
+				else {
+					tmp			= document.createElement('link');
+					tmp.rel		= 'stylesheet';
+					tmp.type	= 'text/css';
+					tmp.media	= "all";
+					tmp.href	= opts.url;
+					document.getElementsByTagName("head")[0].appendChild(tmp);
+					return tmp.styleSheet;
+				}
+			}
+		}
+	};
+
+	// private variables 
+	var instances = [],			// instance array (used by $.jstree.reference/create/focused)
+		focused_instance = -1,	// the index in the instance array of the currently focused instance
+		plugins = {},			// list of included plugins
+		prepared_move = {};		// for the move_node function
+
+	// jQuery plugin wrapper (thanks to jquery UI widget function)
+	$.fn.jstree = function (settings) {
+		var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node")
+			args = Array.prototype.slice.call(arguments, 1), 
+			returnValue = this;
+
+		// if a method call execute the method on all selected instances
+		if(isMethodCall) {
+			if(settings.substring(0, 1) == '_') { return returnValue; }
+			this.each(function() {
+				var instance = instances[$.data(this, "jstree_instance_id")],
+					methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance;
+					if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; }
+			});
+		}
+		else {
+			this.each(function() {
+				// extend settings and allow for multiple hashes and $.data
+				var instance_id = $.data(this, "jstree_instance_id"),
+					a = [],
+					b = settings ? $.extend({}, true, settings) : {},
+					c = $(this), 
+					s = false, 
+					t = [];
+				a = a.concat(args);
+				if(c.data("jstree")) { a.push(c.data("jstree")); }
+				b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b;
+
+				// if an instance already exists, destroy it first
+				if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); }
+				// push a new empty object to the instances array
+				instance_id = parseInt(instances.push({}),10) - 1;
+				// store the jstree instance id to the container element
+				$.data(this, "jstree_instance_id", instance_id);
+				// clean up all plugins
+				b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice();
+				b.plugins.unshift("core");
+				// only unique plugins
+				b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");
+
+				// extend defaults with passed data
+				s = $.extend(true, {}, $.jstree.defaults, b);
+				s.plugins = b.plugins;
+				$.each(plugins, function (i, val) { 
+					if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } 
+					else { t.push(i); }
+				});
+				s.plugins = t;
+
+				// push the new object to the instances array (at the same time set the default classes to the container) and init
+				instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); 
+				// init all activated plugins for this instance
+				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; });
+				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } });
+				// initialize the instance
+				setTimeout(function() { if(instances[instance_id]) { instances[instance_id].init(); } }, 0);
+			});
+		}
+		// return the jquery selection (or if it was a method call that returned a value - the returned value)
+		return returnValue;
+	};
+	// object to store exposed functions and objects
+	$.jstree = {
+		defaults : {
+			plugins : []
+		},
+		_focused : function () { return instances[focused_instance] || null; },
+		_reference : function (needle) { 
+			// get by instance id
+			if(instances[needle]) { return instances[needle]; }
+			// get by DOM (if still no luck - return null
+			var o = $(needle); 
+			if(!o.length && typeof needle === "string") { o = $("#" + needle); }
+			if(!o.length) { return null; }
+			return instances[o.closest(".jstree").data("jstree_instance_id")] || null; 
+		},
+		_instance : function (index, container, settings) { 
+			// for plugins to store data in
+			this.data = { core : {} };
+			this.get_settings	= function () { return $.extend(true, {}, settings); };
+			this._get_settings	= function () { return settings; };
+			this.get_index		= function () { return index; };
+			this.get_container	= function () { return container; };
+			this.get_container_ul = function () { return container.children("ul:eq(0)"); };
+			this._set_settings	= function (s) { 
+				settings = $.extend(true, {}, settings, s);
+			};
+		},
+		_fn : { },
+		plugin : function (pname, pdata) {
+			pdata = $.extend({}, {
+				__init		: $.noop, 
+				__destroy	: $.noop,
+				_fn			: {},
+				defaults	: false
+			}, pdata);
+			plugins[pname] = pdata;
+
+			$.jstree.defaults[pname] = pdata.defaults;
+			$.each(pdata._fn, function (i, val) {
+				val.plugin		= pname;
+				val.old			= $.jstree._fn[i];
+				$.jstree._fn[i] = function () {
+					var rslt,
+						func = val,
+						args = Array.prototype.slice.call(arguments),
+						evnt = new $.Event("before.jstree"),
+						rlbk = false;
+
+					if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; }
+
+					// Check if function belongs to the included plugins of this instance
+					do {
+						if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; }
+						func = func.old;
+					} while(func);
+					if(!func) { return; }
+
+					// context and function to trigger events, then finally call the function
+					if(i.indexOf("_") === 0) {
+						rslt = func.apply(this, args);
+					}
+					else {
+						rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin });
+						if(rslt === false) { return; }
+						if(typeof rslt !== "undefined") { args = rslt; }
+
+						rslt = func.apply(
+							$.extend({}, this, { 
+								__callback : function (data) { 
+									this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk });
+								},
+								__rollback : function () { 
+									rlbk = this.get_rollback();
+									return rlbk;
+								},
+								__call_old : function (replace_arguments) {
+									return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) );
+								}
+							}), args);
+					}
+
+					// return the result
+					return rslt;
+				};
+				$.jstree._fn[i].old = val.old;
+				$.jstree._fn[i].plugin = pname;
+			});
+		},
+		rollback : function (rb) {
+			if(rb) {
+				if(!$.isArray(rb)) { rb = [ rb ]; }
+				$.each(rb, function (i, val) {
+					instances[val.i].set_rollback(val.h, val.d);
+				});
+			}
+		}
+	};
+	// set the prototype for all instances
+	$.jstree._fn = $.jstree._instance.prototype = {};
+
+	// load the css when DOM is ready
+	$(function() {
+		// code is copied from jQuery ($.browser is deprecated + there is a bug in IE)
+		var u = navigator.userAgent.toLowerCase(),
+			v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
+			css_string = '' + 
+				'.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + 
+				'.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + 
+				'.jstree-rtl li { margin-left:0; margin-right:18px; } ' + 
+				'.jstree > ul > li { margin-left:0px; } ' + 
+				'.jstree-rtl > ul > li { margin-right:0px; } ' + 
+				'.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + 
+				'.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + 
+				'.jstree a:focus { outline: none; } ' + 
+				'.jstree a > ins { height:16px; width:16px; } ' + 
+				'.jstree a > .jstree-icon { margin-right:3px; } ' + 
+				'.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + 
+				'li.jstree-open > ul { display:block; } ' + 
+				'li.jstree-closed > ul { display:none; } ';
+		// Correct IE 6 (does not support the > CSS selector)
+		if(/msie/.test(u) && parseInt(v, 10) == 6) { 
+			is_ie6 = true;
+
+			// fix image flicker and lack of caching
+			try {
+				document.execCommand("BackgroundImageCache", false, true);
+			} catch (err) { }
+
+			css_string += '' + 
+				'.jstree li { height:18px; margin-left:0; margin-right:0; } ' + 
+				'.jstree li li { margin-left:18px; } ' + 
+				'.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + 
+				'li.jstree-open ul { display:block; } ' + 
+				'li.jstree-closed ul { display:none !important; } ' + 
+				'.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + 
+				'.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + 
+				'.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } ';
+		}
+		// Correct IE 7 (shifts anchor nodes onhover)
+		if(/msie/.test(u) && parseInt(v, 10) == 7) { 
+			is_ie7 = true;
+			css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } ';
+		}
+		// correct ff2 lack of display:inline-block
+		if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) {
+			is_ff2 = true;
+			css_string += '' + 
+				'.jstree ins { display:-moz-inline-box; } ' + 
+				'.jstree li { line-height:12px; } ' + // WHY??
+				'.jstree a { display:-moz-inline-box; } ' + 
+				'.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } ';
+				/* this shouldn't be here as it is theme specific */
+		}
+		// the default stylesheet
+		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
+	});
+
+	// core functions (open, close, create, update, delete)
+	$.jstree.plugin("core", {
+		__init : function () {
+			this.data.core.locked = false;
+			this.data.core.to_open = this.get_settings().core.initially_open;
+			this.data.core.to_load = this.get_settings().core.initially_load;
+		},
+		defaults : { 
+			html_titles	: false,
+			animation	: 500,
+			initially_open : [],
+			initially_load : [],
+			open_parents : true,
+			notify_plugins : true,
+			rtl			: false,
+			load_open	: false,
+			strings		: {
+				loading		: "Loading ...",
+				new_node	: "New node",
+				multiple_selection : "Multiple selection"
+			}
+		},
+		_fn : { 
+			init	: function () { 
+				this.set_focus(); 
+				if(this._get_settings().core.rtl) {
+					this.get_container().addClass("jstree-rtl").css("direction", "rtl");
+				}
+				this.get_container().html("<ul><li class='jstree-last jstree-leaf'><ins> </ins><a class='jstree-loading' href='#'><ins class='jstree-icon'> </ins>" + this._get_string("loading") + "</a></li></ul>");
+				this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18;
+
+				this.get_container()
+					.delegate("li > ins", "click.jstree", $.proxy(function (event) {
+							var trgt = $(event.target);
+							// if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); }
+							this.toggle_node(trgt);
+						}, this))
+					.bind("mousedown.jstree", $.proxy(function () { 
+							this.set_focus(); // This used to be setTimeout(set_focus,0) - why?
+						}, this))
+					.bind("dblclick.jstree", function (event) { 
+						var sel;
+						if(document.selection && document.selection.empty) { document.selection.empty(); }
+						else {
+							if(window.getSelection) {
+								sel = window.getSelection();
+								try { 
+									sel.removeAllRanges();
+									sel.collapse();
+								} catch (err) { }
+							}
+						}
+					});
+				if(this._get_settings().core.notify_plugins) {
+					this.get_container()
+						.bind("load_node.jstree", $.proxy(function (e, data) { 
+								var o = this._get_node(data.rslt.obj),
+									t = this;
+								if(o === -1) { o = this.get_container_ul(); }
+								if(!o.length) { return; }
+								o.find("li").each(function () {
+									var th = $(this);
+									if(th.data("jstree")) {
+										$.each(th.data("jstree"), function (plugin, values) {
+											if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) {
+												t["_" + plugin + "_notify"].call(t, th, values);
+											}
+										});
+									}
+								});
+							}, this));
+				}
+				if(this._get_settings().core.load_open) {
+					this.get_container()
+						.bind("load_node.jstree", $.proxy(function (e, data) { 
+								var o = this._get_node(data.rslt.obj),
+									t = this;
+								if(o === -1) { o = this.get_container_ul(); }
+								if(!o.length) { return; }
+								o.find("li.jstree-open:not(:has(ul))").each(function () {
+									t.load_node(this, $.noop, $.noop);
+								});
+							}, this));
+				}
+				this.__callback();
+				this.load_node(-1, function () { this.loaded(); this.reload_nodes(); });
+			},
+			destroy	: function () { 
+				var i,
+					n = this.get_index(),
+					s = this._get_settings(),
+					_this = this;
+
+				$.each(s.plugins, function (i, val) {
+					try { plugins[val].__destroy.apply(_this); } catch(err) { }
+				});
+				this.__callback();
+				// set focus to another instance if this one is focused
+				if(this.is_focused()) { 
+					for(i in instances) { 
+						if(instances.hasOwnProperty(i) && i != n) { 
+							instances[i].set_focus(); 
+							break; 
+						} 
+					}
+				}
+				// if no other instance found
+				if(n === focused_instance) { focused_instance = -1; }
+				// remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events
+				this.get_container()
+					.unbind(".jstree")
+					.undelegate(".jstree")
+					.removeData("jstree_instance_id")
+					.find("[class^='jstree']")
+						.addBack()
+						.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
+				$(document)
+					.unbind(".jstree-" + n)
+					.undelegate(".jstree-" + n);
+				// remove the actual data
+				instances[n] = null;
+				delete instances[n];
+			},
+
+			_core_notify : function (n, data) {
+				if(data.opened) {
+					this.open_node(n, false, true);
+				}
+			},
+
+			lock : function () {
+				this.data.core.locked = true;
+				this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7");
+				this.__callback({});
+			},
+			unlock : function () {
+				this.data.core.locked = false;
+				this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1");
+				this.__callback({});
+			},
+			is_locked : function () { return this.data.core.locked; },
+			save_opened : function () {
+				var _this = this;
+				this.data.core.to_open = [];
+				this.get_container_ul().find("li.jstree-open").each(function () { 
+					if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); }
+				});
+				this.__callback(_this.data.core.to_open);
+			},
+			save_loaded : function () { },
+			reload_nodes : function (is_callback) {
+				var _this = this,
+					done = true,
+					current = [],
+					remaining = [];
+				if(!is_callback) { 
+					this.data.core.reopen = false; 
+					this.data.core.refreshing = true; 
+					this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
+					this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
+					if(this.data.core.to_open.length) {
+						this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open);
+					}
+				}
+				if(this.data.core.to_load.length) {
+					$.each(this.data.core.to_load, function (i, val) {
+						if(val == "#") { return true; }
+						if($(val).length) { current.push(val); }
+						else { remaining.push(val); }
+					});
+					if(current.length) {
+						this.data.core.to_load = remaining;
+						$.each(current, function (i, val) { 
+							if(!_this._is_loaded(val)) {
+								_this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); });
+								done = false;
+							}
+						});
+					}
+				}
+				if(this.data.core.to_open.length) {
+					$.each(this.data.core.to_open, function (i, val) {
+						_this.open_node(val, false, true); 
+					});
+				}
+				if(done) { 
+					// TODO: find a more elegant approach to syncronizing returning requests
+					if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
+					this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50);
+					this.data.core.refreshing = false;
+					this.reopen();
+				}
+			},
+			reopen : function () {
+				var _this = this;
+				if(this.data.core.to_open.length) {
+					$.each(this.data.core.to_open, function (i, val) {
+						_this.open_node(val, false, true); 
+					});
+				}
+				this.__callback({});
+			},
+			refresh : function (obj) {
+				var _this = this;
+				this.save_opened();
+				if(!obj) { obj = -1; }
+				obj = this._get_node(obj);
+				if(!obj) { obj = -1; }
+				if(obj !== -1) { obj.children("UL").remove(); }
+				else { this.get_container_ul().empty(); }
+				this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); });
+			},
+			// Dummy function to fire after the first load (so that there is a jstree.loaded event)
+			loaded	: function () { 
+				this.__callback(); 
+			},
+			// deal with focus
+			set_focus	: function () { 
+				if(this.is_focused()) { return; }
+				var f = $.jstree._focused();
+				if(f) { f.unset_focus(); }
+
+				this.get_container().addClass("jstree-focused"); 
+				focused_instance = this.get_index(); 
+				this.__callback();
+			},
+			is_focused	: function () { 
+				return focused_instance == this.get_index(); 
+			},
+			unset_focus	: function () {
+				if(this.is_focused()) {
+					this.get_container().removeClass("jstree-focused"); 
+					focused_instance = -1; 
+				}
+				this.__callback();
+			},
+
+			// traverse
+			_get_node		: function (obj) { 
+				var $obj = $(obj, this.get_container()); 
+				if($obj.is(".jstree") || obj == -1) { return -1; } 
+				$obj = $obj.closest("li", this.get_container()); 
+				return $obj.length ? $obj : false; 
+			},
+			_get_next		: function (obj, strict) {
+				obj = this._get_node(obj);
+				if(obj === -1) { return this.get_container().find("> ul > li:first-child"); }
+				if(!obj.length) { return false; }
+				if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; }
+
+				if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); }
+				else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); }
+				else { return obj.parentsUntil(".jstree","li").next("li").eq(0); }
+			},
+			_get_prev		: function (obj, strict) {
+				obj = this._get_node(obj);
+				if(obj === -1) { return this.get_container().find("> ul > li:last-child"); }
+				if(!obj.length) { return false; }
+				if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; }
+
+				if(obj.prev("li").length) {
+					obj = obj.prev("li").eq(0);
+					while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); }
+					return obj;
+				}
+				else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; }
+			},
+			_get_parent		: function (obj) {
+				obj = this._get_node(obj);
+				if(obj == -1 || !obj.length) { return false; }
+				var o = obj.parentsUntil(".jstree", "li:eq(0)");
+				return o.length ? o : -1;
+			},
+			_get_children	: function (obj) {
+				obj = this._get_node(obj);
+				if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); }
+				if(!obj.length) { return false; }
+				return obj.children("ul:eq(0)").children("li");
+			},
+			get_path		: function (obj, id_mode) {
+				var p = [],
+					_this = this;
+				obj = this._get_node(obj);
+				if(obj === -1 || !obj || !obj.length) { return false; }
+				obj.parentsUntil(".jstree", "li").each(function () {
+					p.push( id_mode ? this.id : _this.get_text(this) );
+				});
+				p.reverse();
+				p.push( id_mode ? obj.attr("id") : this.get_text(obj) );
+				return p;
+			},
+
+			// string functions
+			_get_string : function (key) {
+				return this._get_settings().core.strings[key] || key;
+			},
+
+			is_open		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); },
+			is_closed	: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); },
+			is_leaf		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); },
+			correct_state	: function (obj) {
+				obj = this._get_node(obj);
+				if(!obj || obj === -1) { return false; }
+				obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove();
+				this.__callback({ "obj" : obj });
+			},
+			// open/close
+			open_node	: function (obj, callback, skip_animation) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; }
+				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
+					t = this;
+				if(!this._is_loaded(obj)) {
+					obj.children("a").addClass("jstree-loading");
+					this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback);
+				}
+				else {
+					if(this._get_settings().core.open_parents) {
+						obj.parentsUntil(".jstree",".jstree-closed").each(function () {
+							t.open_node(this, false, true);
+						});
+					}
+					if(s) { obj.children("ul").css("display","none"); }
+					obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading");
+					if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); }
+					else { t.after_open(obj); }
+					this.__callback({ "obj" : obj });
+					if(callback) { callback.call(); }
+				}
+			},
+			after_open	: function (obj) { this.__callback({ "obj" : obj }); },
+			close_node	: function (obj, skip_animation) {
+				obj = this._get_node(obj);
+				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
+					t = this;
+				if(!obj.length || !obj.hasClass("jstree-open")) { return false; }
+				if(s) { obj.children("ul").attr("style","display:block !important"); }
+				obj.removeClass("jstree-open").addClass("jstree-closed");
+				if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); }
+				else { t.after_close(obj); }
+				this.__callback({ "obj" : obj });
+			},
+			after_close	: function (obj) { this.__callback({ "obj" : obj }); },
+			toggle_node	: function (obj) {
+				obj = this._get_node(obj);
+				if(obj.hasClass("jstree-closed")) { return this.open_node(obj); }
+				if(obj.hasClass("jstree-open")) { return this.close_node(obj); }
+			},
+			open_all	: function (obj, do_animation, original_obj) {
+				obj = obj ? this._get_node(obj) : -1;
+				if(!obj || obj === -1) { obj = this.get_container_ul(); }
+				if(original_obj) { 
+					obj = obj.find("li.jstree-closed");
+				}
+				else {
+					original_obj = obj;
+					if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").addBack(); }
+					else { obj = obj.find("li.jstree-closed"); }
+				}
+				var _this = this;
+				obj.each(function () { 
+					var __this = this; 
+					if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); }
+					else { _this.open_node(this, false, !do_animation); }
+				});
+				// so that callback is fired AFTER all nodes are open
+				if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); }
+			},
+			close_all	: function (obj, do_animation) {
+				var _this = this;
+				obj = obj ? this._get_node(obj) : this.get_container();
+				if(!obj || obj === -1) { obj = this.get_container_ul(); }
+				obj.find("li.jstree-open").addBack().each(function () { _this.close_node(this, !do_animation); });
+				this.__callback({ "obj" : obj });
+			},
+			clean_node	: function (obj) {
+				obj = obj && obj != -1 ? $(obj) : this.get_container_ul();
+				obj = obj.is("li") ? obj.find("li").addBack() : obj.find("li");
+				obj.removeClass("jstree-last")
+					.filter("li:last-child").addClass("jstree-last").end()
+					.filter(":has(li)")
+						.not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed");
+				obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove();
+				this.__callback({ "obj" : obj });
+			},
+			// rollback
+			get_rollback : function () { 
+				this.__callback();
+				return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; 
+			},
+			set_rollback : function (html, data) {
+				this.get_container().empty().append(html);
+				this.data = data;
+				this.__callback();
+			},
+			// Dummy functions to be overwritten by any datastore plugin included
+			load_node	: function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); },
+			_is_loaded	: function (obj) { return true; },
+
+			// Basic operations: create
+			create_node	: function (obj, position, js, callback, is_loaded) {
+				obj = this._get_node(obj);
+				position = typeof position === "undefined" ? "last" : position;
+				var d = $("<li />"),
+					s = this._get_settings().core,
+					tmp;
+
+				if(obj !== -1 && !obj.length) { return false; }
+				if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; }
+
+				this.__rollback();
+
+				if(typeof js === "string") { js = { "data" : js }; }
+				if(!js) { js = {}; }
+				if(js.attr) { d.attr(js.attr); }
+				if(js.metadata) { d.data(js.metadata); }
+				if(js.state) { d.addClass("jstree-" + js.state); }
+				if(!js.data) { js.data = this._get_string("new_node"); }
+				if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
+				$.each(js.data, function (i, m) {
+					tmp = $("<a />");
+					if($.isFunction(m)) { m = m.call(this, js); }
+					if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); }
+					else {
+						if(!m.attr) { m.attr = {}; }
+						if(!m.attr.href) { m.attr.href = '#'; }
+						tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title);
+						if(m.language) { tmp.addClass(m.language); }
+					}
+					tmp.prepend("<ins class='jstree-icon'> </ins>");
+					if(!m.icon && js.icon) { m.icon = js.icon; }
+					if(m.icon) { 
+						if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
+						else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
+					}
+					d.append(tmp);
+				});
+				d.prepend("<ins class='jstree-icon'> </ins>");
+				if(obj === -1) {
+					obj = this.get_container();
+					if(position === "before") { position = "first"; }
+					if(position === "after") { position = "last"; }
+				}
+				switch(position) {
+					case "before": obj.before(d); tmp = this._get_parent(obj); break;
+					case "after" : obj.after(d);  tmp = this._get_parent(obj); break;
+					case "inside":
+					case "first" :
+						if(!obj.children("ul").length) { obj.append("<ul />"); }
+						obj.children("ul").prepend(d);
+						tmp = obj;
+						break;
+					case "last":
+						if(!obj.children("ul").length) { obj.append("<ul />"); }
+						obj.children("ul").append(d);
+						tmp = obj;
+						break;
+					default:
+						if(!obj.children("ul").length) { obj.append("<ul />"); }
+						if(!position) { position = 0; }
+						tmp = obj.children("ul").children("li").eq(position);
+						if(tmp.length) { tmp.before(d); }
+						else { obj.children("ul").append(d); }
+						tmp = obj;
+						break;
+				}
+				if(tmp === -1 || tmp.get(0) === this.get_container().get(0)) { tmp = -1; }
+				this.clean_node(tmp);
+				this.__callback({ "obj" : d, "parent" : tmp });
+				if(callback) { callback.call(this, d); }
+				return d;
+			},
+			// Basic operations: rename (deal with text)
+			get_text	: function (obj) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				var s = this._get_settings().core.html_titles;
+				obj = obj.children("a:eq(0)");
+				if(s) {
+					obj = obj.clone();
+					obj.children("INS").remove();
+					return obj.html();
+				}
+				else {
+					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
+					return obj.nodeValue;
+				}
+			},
+			set_text	: function (obj, val) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				obj = obj.children("a:eq(0)");
+				if(this._get_settings().core.html_titles) {
+					var tmp = obj.children("INS").clone();
+					obj.html(val).prepend(tmp);
+					this.__callback({ "obj" : obj, "name" : val });
+					return true;
+				}
+				else {
+					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
+					this.__callback({ "obj" : obj, "name" : val });
+					return (obj.nodeValue = val);
+				}
+			},
+			rename_node : function (obj, val) {
+				obj = this._get_node(obj);
+				this.__rollback();
+				if(obj && obj.length && this.set_text.apply(this, Array.prototype.slice.call(arguments))) { this.__callback({ "obj" : obj, "name" : val }); }
+			},
+			// Basic operations: deleting nodes
+			delete_node : function (obj) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				this.__rollback();
+				var p = this._get_parent(obj), prev = $([]), t = this;
+				obj.each(function () {
+					prev = prev.add(t._get_prev(this));
+				});
+				obj = obj.detach();
+				if(p !== -1 && p.find("> ul > li").length === 0) {
+					p.removeClass("jstree-open jstree-closed").addClass("jstree-leaf");
+				}
+				this.clean_node(p);
+				this.__callback({ "obj" : obj, "prev" : prev, "parent" : p });
+				return obj;
+			},
+			prepare_move : function (o, r, pos, cb, is_cb) {
+				var p = {};
+
+				p.ot = $.jstree._reference(o) || this;
+				p.o = p.ot._get_node(o);
+				p.r = r === - 1 ? -1 : this._get_node(r);
+				p.p = (typeof pos === "undefined" || pos === false) ? "last" : pos; // TODO: move to a setting
+				if(!is_cb && prepared_move.o && prepared_move.o[0] === p.o[0] && prepared_move.r[0] === p.r[0] && prepared_move.p === p.p) {
+					this.__callback(prepared_move);
+					if(cb) { cb.call(this, prepared_move); }
+					return;
+				}
+				p.ot = $.jstree._reference(p.o) || this;
+				p.rt = $.jstree._reference(p.r) || this; // r === -1 ? p.ot : $.jstree._reference(p.r) || this
+				if(p.r === -1 || !p.r) {
+					p.cr = -1;
+					switch(p.p) {
+						case "first":
+						case "before":
+						case "inside":
+							p.cp = 0; 
+							break;
+						case "after":
+						case "last":
+							p.cp = p.rt.get_container().find(" > ul > li").length; 
+							break;
+						default:
+							p.cp = p.p;
+							break;
+					}
+				}
+				else {
+					if(!/^(before|after)$/.test(p.p) && !this._is_loaded(p.r)) {
+						return this.load_node(p.r, function () { this.prepare_move(o, r, pos, cb, true); });
+					}
+					switch(p.p) {
+						case "before":
+							p.cp = p.r.index();
+							p.cr = p.rt._get_parent(p.r);
+							break;
+						case "after":
+							p.cp = p.r.index() + 1;
+							p.cr = p.rt._get_parent(p.r);
+							break;
+						case "inside":
+						case "first":
+							p.cp = 0;
+							p.cr = p.r;
+							break;
+						case "last":
+							p.cp = p.r.find(" > ul > li").length; 
+							p.cr = p.r;
+							break;
+						default: 
+							p.cp = p.p;
+							p.cr = p.r;
+							break;
+					}
+				}
+				p.np = p.cr == -1 ? p.rt.get_container() : p.cr;
+				p.op = p.ot._get_parent(p.o);
+				p.cop = p.o.index();
+				if(p.op === -1) { p.op = p.ot ? p.ot.get_container() : this.get_container(); }
+				if(!/^(before|after)$/.test(p.p) && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp++; }
+				//if(p.p === "before" && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp--; }
+				p.or = p.np.find(" > ul > li:nth-child(" + (p.cp + 1) + ")");
+				prepared_move = p;
+				this.__callback(prepared_move);
+				if(cb) { cb.call(this, prepared_move); }
+			},
+			check_move : function () {
+				var obj = prepared_move, ret = true, r = obj.r === -1 ? this.get_container() : obj.r;
+				if(!obj || !obj.o || obj.or[0] === obj.o[0]) { return false; }
+				if(!obj.cy) {
+					if(obj.op && obj.np && obj.op[0] === obj.np[0] && obj.cp - 1 === obj.o.index()) { return false; }
+					obj.o.each(function () { 
+						if(r.parentsUntil(".jstree", "li").addBack().index(this) !== -1) { ret = false; return false; }
+					});
+				}
+				return ret;
+			},
+			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
+				if(!is_prepared) { 
+					return this.prepare_move(obj, ref, position, function (p) {
+						this.move_node(p, false, false, is_copy, true, skip_check);
+					});
+				}
+				if(is_copy) { 
+					prepared_move.cy = true;
+				}
+				if(!skip_check && !this.check_move()) { return false; }
+
+				this.__rollback();
+				var o = false;
+				if(is_copy) {
+					o = obj.o.clone(true);
+					o.find("*[id]").addBack().each(function () {
+						if(this.id) { this.id = "copy_" + this.id; }
+					});
+				}
+				else { o = obj.o; }
+
+				if(obj.or.length) { obj.or.before(o); }
+				else { 
+					if(!obj.np.children("ul").length) { $("<ul />").appendTo(obj.np); }
+					obj.np.children("ul:eq(0)").append(o); 
+				}
+
+				try { 
+					obj.ot.clean_node(obj.op);
+					obj.rt.clean_node(obj.np);
+					if(!obj.op.find("> ul > li").length) {
+						obj.op.removeClass("jstree-open jstree-closed").addClass("jstree-leaf").children("ul").remove();
+					}
+				} catch (e) { }
+
+				if(is_copy) { 
+					prepared_move.cy = true;
+					prepared_move.oc = o; 
+				}
+				this.__callback(prepared_move);
+				return prepared_move;
+			},
+			_get_move : function () { return prepared_move; }
+		}
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree ui plugin
+ * This plugins handles selecting/deselecting/hovering/dehovering nodes
+ */
+(function ($) {
+	var scrollbar_width, e1, e2;
+	$(function() {
+		if (/msie/.test(navigator.userAgent.toLowerCase())) {
+			e1 = $('<textarea cols="10" rows="2"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');
+			e2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');
+			scrollbar_width = e1.width() - e2.width();
+			e1.add(e2).remove();
+		} 
+		else {
+			e1 = $('<div />').css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: 0 })
+					.prependTo('body').append('<div />').find('div').css({ width: '100%', height: 200 });
+			scrollbar_width = 100 - e1.width();
+			e1.parent().remove();
+		}
+	});
+	$.jstree.plugin("ui", {
+		__init : function () { 
+			this.data.ui.selected = $(); 
+			this.data.ui.last_selected = false; 
+			this.data.ui.hovered = null;
+			this.data.ui.to_select = this.get_settings().ui.initially_select;
+
+			this.get_container()
+				.delegate("a", "click.jstree", $.proxy(function (event) {
+						event.preventDefault();
+						event.currentTarget.blur();
+						if(!$(event.currentTarget).hasClass("jstree-loading")) {
+							this.select_node(event.currentTarget, true, event);
+						}
+					}, this))
+				.delegate("a", "mouseenter.jstree", $.proxy(function (event) {
+						if(!$(event.currentTarget).hasClass("jstree-loading")) {
+							this.hover_node(event.target);
+						}
+					}, this))
+				.delegate("a", "mouseleave.jstree", $.proxy(function (event) {
+						if(!$(event.currentTarget).hasClass("jstree-loading")) {
+							this.dehover_node(event.target);
+						}
+					}, this))
+				.bind("reopen.jstree", $.proxy(function () { 
+						this.reselect();
+					}, this))
+				.bind("get_rollback.jstree", $.proxy(function () { 
+						this.dehover_node();
+						this.save_selected();
+					}, this))
+				.bind("set_rollback.jstree", $.proxy(function () { 
+						this.reselect();
+					}, this))
+				.bind("close_node.jstree", $.proxy(function (event, data) { 
+						var s = this._get_settings().ui,
+							obj = this._get_node(data.rslt.obj),
+							clk = (obj && obj.length) ? obj.children("ul").find("a.jstree-clicked") : $(),
+							_this = this;
+						if(s.selected_parent_close === false || !clk.length) { return; }
+						clk.each(function () { 
+							_this.deselect_node(this);
+							if(s.selected_parent_close === "select_parent") { _this.select_node(obj); }
+						});
+					}, this))
+				.bind("delete_node.jstree", $.proxy(function (event, data) { 
+						var s = this._get_settings().ui.select_prev_on_delete,
+							obj = this._get_node(data.rslt.obj),
+							clk = (obj && obj.length) ? obj.find("a.jstree-clicked") : [],
+							_this = this;
+						clk.each(function () { _this.deselect_node(this); });
+						if(s && clk.length) { 
+							data.rslt.prev.each(function () { 
+								if(this.parentNode) { _this.select_node(this); return false; /* if return false is removed all prev nodes will be selected */}
+							});
+						}
+					}, this))
+				.bind("move_node.jstree", $.proxy(function (event, data) { 
+						if(data.rslt.cy) { 
+							data.rslt.oc.find("a.jstree-clicked").removeClass("jstree-clicked");
+						}
+					}, this));
+		},
+		defaults : {
+			select_limit : -1, // 0, 1, 2 ... or -1 for unlimited
+			select_multiple_modifier : "ctrl", // on, or ctrl, shift, alt
+			select_range_modifier : "shift",
+			selected_parent_close : "select_parent", // false, "deselect", "select_parent"
+			selected_parent_open : true,
+			select_prev_on_delete : true,
+			disable_selecting_children : false,
+			initially_select : []
+		},
+		_fn : { 
+			_get_node : function (obj, allow_multiple) {
+				if(typeof obj === "undefined" || obj === null) { return allow_multiple ? this.data.ui.selected : this.data.ui.last_selected; }
+				var $obj = $(obj, this.get_container()); 
+				if($obj.is(".jstree") || obj == -1) { return -1; } 
+				$obj = $obj.closest("li", this.get_container()); 
+				return $obj.length ? $obj : false; 
+			},
+			_ui_notify : function (n, data) {
+				if(data.selected) {
+					this.select_node(n, false);
+				}
+			},
+			save_selected : function () {
+				var _this = this;
+				this.data.ui.to_select = [];
+				this.data.ui.selected.each(function () { if(this.id) { _this.data.ui.to_select.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } });
+				this.__callback(this.data.ui.to_select);
+			},
+			reselect : function () {
+				var _this = this,
+					s = this.data.ui.to_select;
+				s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
+				// this.deselect_all(); WHY deselect, breaks plugin state notifier?
+				$.each(s, function (i, val) { if(val && val !== "#") { _this.select_node(val); } });
+				this.data.ui.selected = this.data.ui.selected.filter(function () { return this.parentNode; });
+				this.__callback();
+			},
+			refresh : function (obj) {
+				this.save_selected();
+				return this.__call_old();
+			},
+			hover_node : function (obj) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				//if(this.data.ui.hovered && obj.get(0) === this.data.ui.hovered.get(0)) { return; }
+				if(!obj.hasClass("jstree-hovered")) { this.dehover_node(); }
+				this.data.ui.hovered = obj.children("a").addClass("jstree-hovered").parent();
+				this._fix_scroll(obj);
+				this.__callback({ "obj" : obj });
+			},
+			dehover_node : function () {
+				var obj = this.data.ui.hovered, p;
+				if(!obj || !obj.length) { return false; }
+				p = obj.children("a").removeClass("jstree-hovered").parent();
+				if(this.data.ui.hovered[0] === p[0]) { this.data.ui.hovered = null; }
+				this.__callback({ "obj" : obj });
+			},
+			select_node : function (obj, check, e) {
+				obj = this._get_node(obj);
+				if(obj == -1 || !obj || !obj.length) { return false; }
+				var s = this._get_settings().ui,
+					is_multiple = (s.select_multiple_modifier == "on" || (s.select_multiple_modifier !== false && e && e[s.select_multiple_modifier + "Key"])),
+					is_range = (s.select_range_modifier !== false && e && e[s.select_range_modifier + "Key"] && this.data.ui.last_selected && this.data.ui.last_selected[0] !== obj[0] && this.data.ui.last_selected.parent()[0] === obj.parent()[0]),
+					is_selected = this.is_selected(obj),
+					proceed = true,
+					t = this;
+				if(check) {
+					if(s.disable_selecting_children && is_multiple && 
+						(
+							(obj.parentsUntil(".jstree","li").children("a.jstree-clicked").length) ||
+							(obj.children("ul").find("a.jstree-clicked:eq(0)").length)
+						)
+					) {
+						return false;
+					}
+					proceed = false;
+					switch(!0) {
+						case (is_range):
+							this.data.ui.last_selected.addClass("jstree-last-selected");
+							obj = obj[ obj.index() < this.data.ui.last_selected.index() ? "nextUntil" : "prevUntil" ](".jstree-last-selected").addBack();
+							if(s.select_limit == -1 || obj.length < s.select_limit) {
+								this.data.ui.last_selected.removeClass("jstree-last-selected");
+								this.data.ui.selected.each(function () {
+									if(this !== t.data.ui.last_selected[0]) { t.deselect_node(this); }
+								});
+								is_selected = false;
+								proceed = true;
+							}
+							else {
+								proceed = false;
+							}
+							break;
+						case (is_selected && !is_multiple): 
+							this.deselect_all();
+							is_selected = false;
+							proceed = true;
+							break;
+						case (!is_selected && !is_multiple): 
+							if(s.select_limit == -1 || s.select_limit > 0) {
+								this.deselect_all();
+								proceed = true;
+							}
+							break;
+						case (is_selected && is_multiple): 
+							this.deselect_node(obj);
+							break;
+						case (!is_selected && is_multiple): 
+							if(s.select_limit == -1 || this.data.ui.selected.length + 1 <= s.select_limit) { 
+								proceed = true;
+							}
+							break;
+					}
+				}
+				if(proceed && !is_selected) {
+					if(!is_range) { this.data.ui.last_selected = obj; }
+					obj.children("a").addClass("jstree-clicked");
+					if(s.selected_parent_open) {
+						obj.parents(".jstree-closed").each(function () { t.open_node(this, false, true); });
+					}
+					this.data.ui.selected = this.data.ui.selected.add(obj);
+					this._fix_scroll(obj.eq(0));
+					this.__callback({ "obj" : obj, "e" : e });
+				}
+			},
+			_fix_scroll : function (obj) {
+				var c = this.get_container()[0], t;
+				if(c.scrollHeight > c.offsetHeight) {
+					obj = this._get_node(obj);
+					if(!obj || obj === -1 || !obj.length || !obj.is(":visible")) { return; }
+					t = obj.offset().top - this.get_container().offset().top;
+					if(t < 0) { 
+						c.scrollTop = c.scrollTop + t - 1; 
+					}
+					if(t + this.data.core.li_height + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0) > c.offsetHeight) { 
+						c.scrollTop = c.scrollTop + (t - c.offsetHeight + this.data.core.li_height + 1 + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0)); 
+					}
+				}
+			},
+			deselect_node : function (obj) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				if(this.is_selected(obj)) {
+					obj.children("a").removeClass("jstree-clicked");
+					this.data.ui.selected = this.data.ui.selected.not(obj);
+					if(this.data.ui.last_selected.get(0) === obj.get(0)) { this.data.ui.last_selected = this.data.ui.selected.eq(0); }
+					this.__callback({ "obj" : obj });
+				}
+			},
+			toggle_select : function (obj) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return false; }
+				if(this.is_selected(obj)) { this.deselect_node(obj); }
+				else { this.select_node(obj); }
+			},
+			is_selected : function (obj) { return this.data.ui.selected.index(this._get_node(obj)) >= 0; },
+			get_selected : function (context) { 
+				return context ? $(context).find("a.jstree-clicked").parent() : this.data.ui.selected; 
+			},
+			deselect_all : function (context) {
+				var ret = context ? $(context).find("a.jstree-clicked").parent() : this.get_container().find("a.jstree-clicked").parent();
+				ret.children("a.jstree-clicked").removeClass("jstree-clicked");
+				this.data.ui.selected = $([]);
+				this.data.ui.last_selected = false;
+				this.__callback({ "obj" : ret });
+			}
+		}
+	});
+	// include the selection plugin by default
+	$.jstree.defaults.plugins.push("ui");
+})(jQuery);
+//*/
+
+/* 
+ * jsTree CRRM plugin
+ * Handles creating/renaming/removing/moving nodes by user interaction.
+ */
+(function ($) {
+	$.jstree.plugin("crrm", { 
+		__init : function () {
+			this.get_container()
+				.bind("move_node.jstree", $.proxy(function (e, data) {
+					if(this._get_settings().crrm.move.open_onmove) {
+						var t = this;
+						data.rslt.np.parentsUntil(".jstree").addBack().filter(".jstree-closed").each(function () {
+							t.open_node(this, false, true);
+						});
+					}
+				}, this));
+		},
+		defaults : {
+			input_width_limit : 200,
+			move : {
+				always_copy			: false, // false, true or "multitree"
+				open_onmove			: true,
+				default_position	: "last",
+				check_move			: function (m) { return true; }
+			}
+		},
+		_fn : {
+			_show_input : function (obj, callback) {
+				obj = this._get_node(obj);
+				var rtl = this._get_settings().core.rtl,
+					w = this._get_settings().crrm.input_width_limit,
+					w1 = obj.children("ins").width(),
+					w2 = obj.find("> a:visible > ins").width() * obj.find("> a:visible > ins").length,
+					t = this.get_text(obj),
+					h1 = $("<div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
+					h2 = obj.css("position","relative").append(
+					$("<input />", { 
+						"value" : t,
+						"class" : "jstree-rename-input",
+						// "size" : t.length,
+						"css" : {
+							"padding" : "0",
+							"border" : "1px solid silver",
+							"position" : "absolute",
+							"left"  : (rtl ? "auto" : (w1 + w2 + 4) + "px"),
+							"right" : (rtl ? (w1 + w2 + 4) + "px" : "auto"),
+							"top" : "0px",
+							"height" : (this.data.core.li_height - 2) + "px",
+							"lineHeight" : (this.data.core.li_height - 2) + "px",
+							"width" : "150px" // will be set a bit further down
+						},
+						"blur" : $.proxy(function () {
+							var i = obj.children(".jstree-rename-input"),
+								v = i.val();
+							if(v === "") { v = t; }
+							h1.remove();
+							i.remove(); // rollback purposes
+							this.set_text(obj,t); // rollback purposes
+							this.rename_node(obj, v);
+							callback.call(this, obj, v, t);
+							obj.css("position","");
+						}, this),
+						"keyup" : function (event) {
+							var key = event.keyCode || event.which;
+							if(key == 27) { this.value = t; this.blur(); return; }
+							else if(key == 13) { this.blur(); return; }
+							else {
+								h2.width(Math.min(h1.text("pW" + this.value).width(),w));
+							}
+						},
+						"keypress" : function(event) {
+							var key = event.keyCode || event.which;
+							if(key == 13) { return false; }
+						}
+					})
+				).children(".jstree-rename-input"); 
+				this.set_text(obj, "");
+				h1.css({
+						fontFamily		: h2.css('fontFamily')		|| '',
+						fontSize		: h2.css('fontSize')		|| '',
+						fontWeight		: h2.css('fontWeight')		|| '',
+						fontStyle		: h2.css('fontStyle')		|| '',
+						fontStretch		: h2.css('fontStretch')		|| '',
+						fontVariant		: h2.css('fontVariant')		|| '',
+						letterSpacing	: h2.css('letterSpacing')	|| '',
+						wordSpacing		: h2.css('wordSpacing')		|| ''
+				});
+				h2.width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
+			},
+			rename : function (obj) {
+				obj = this._get_node(obj);
+				this.__rollback();
+				var f = this.__callback;
+				this._show_input(obj, function (obj, new_name, old_name) { 
+					f.call(this, { "obj" : obj, "new_name" : new_name, "old_name" : old_name });
+				});
+			},
+			create : function (obj, position, js, callback, skip_rename) {
+				var t, _this = this;
+				obj = this._get_node(obj);
+				if(!obj) { obj = -1; }
+				this.__rollback();
+				t = this.create_node(obj, position, js, function (t) {
+					var p = this._get_parent(t),
+						pos = $(t).index();
+					if(callback) { callback.call(this, t); }
+					if(p.length && p.hasClass("jstree-closed")) { this.open_node(p, false, true); }
+					if(!skip_rename) { 
+						this._show_input(t, function (obj, new_name, old_name) { 
+							_this.__callback({ "obj" : obj, "name" : new_name, "parent" : p, "position" : pos });
+						});
+					}
+					else { _this.__callback({ "obj" : t, "name" : this.get_text(t), "parent" : p, "position" : pos }); }
+				});
+				return t;
+			},
+			remove : function (obj) {
+				obj = this._get_node(obj, true);
+				var p = this._get_parent(obj), prev = this._get_prev(obj);
+				this.__rollback();
+				obj = this.delete_node(obj);
+				if(obj !== false) { this.__callback({ "obj" : obj, "prev" : prev, "parent" : p }); }
+			},
+			check_move : function () {
+				if(!this.__call_old()) { return false; }
+				var s = this._get_settings().crrm.move;
+				if(!s.check_move.call(this, this._get_move())) { return false; }
+				return true;
+			},
+			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
+				var s = this._get_settings().crrm.move;
+				if(!is_prepared) { 
+					if(typeof position === "undefined") { position = s.default_position; }
+					if(position === "inside" && !s.default_position.match(/^(before|after)$/)) { position = s.default_position; }
+					return this.__call_old(true, obj, ref, position, is_copy, false, skip_check);
+				}
+				// if the move is already prepared
+				if(s.always_copy === true || (s.always_copy === "multitree" && obj.rt.get_index() !== obj.ot.get_index() )) {
+					is_copy = true;
+				}
+				this.__call_old(true, obj, ref, position, is_copy, true, skip_check);
+			},
+
+			cut : function (obj) {
+				obj = this._get_node(obj, true);
+				if(!obj || !obj.length) { return false; }
+				this.data.crrm.cp_nodes = false;
+				this.data.crrm.ct_nodes = obj;
+				this.__callback({ "obj" : obj });
+			},
+			copy : function (obj) {
+				obj = this._get_node(obj, true);
+				if(!obj || !obj.length) { return false; }
+				this.data.crrm.ct_nodes = false;
+				this.data.crrm.cp_nodes = obj;
+				this.__callback({ "obj" : obj });
+			},
+			paste : function (obj) { 
+				obj = this._get_node(obj);
+				if(!obj || !obj.length) { return false; }
+				var nodes = this.data.crrm.ct_nodes ? this.data.crrm.ct_nodes : this.data.crrm.cp_nodes;
+				if(!this.data.crrm.ct_nodes && !this.data.crrm.cp_nodes) { return false; }
+				if(this.data.crrm.ct_nodes) { this.move_node(this.data.crrm.ct_nodes, obj); this.data.crrm.ct_nodes = false; }
+				if(this.data.crrm.cp_nodes) { this.move_node(this.data.crrm.cp_nodes, obj, false, true); }
+				this.__callback({ "obj" : obj, "nodes" : nodes });
+			}
+		}
+	});
+	// include the crr plugin by default
+	// $.jstree.defaults.plugins.push("crrm");
+})(jQuery);
+//*/
+
+/* 
+ * jsTree themes plugin
+ * Handles loading and setting themes, as well as detecting path to themes, etc.
+ */
+(function ($) {
+	var themes_loaded = [];
+	// this variable stores the path to the themes folder - if left as false - it will be autodetected
+	$.jstree._themes = false;
+	$.jstree.plugin("themes", {
+		__init : function () { 
+			this.get_container()
+				.bind("init.jstree", $.proxy(function () {
+						var s = this._get_settings().themes;
+						this.data.themes.dots = s.dots; 
+						this.data.themes.icons = s.icons; 
+						this.set_theme(s.theme, s.url);
+					}, this))
+				.bind("loaded.jstree", $.proxy(function () {
+						// bound here too, as simple HTML tree's won't honor dots & icons otherwise
+						if(!this.data.themes.dots) { this.hide_dots(); }
+						else { this.show_dots(); }
+						if(!this.data.themes.icons) { this.hide_icons(); }
+						else { this.show_icons(); }
+					}, this));
+		},
+		defaults : { 
+			theme : "default", 
+			url : false,
+			dots : true,
+			icons : true
+		},
+		_fn : {
+			set_theme : function (theme_name, theme_url) {
+				if(!theme_name) { return false; }
+				if(!theme_url) { theme_url = $.jstree._themes + theme_name + '/style.css'; }
+				if($.inArray(theme_url, themes_loaded) == -1) {
+					$.vakata.css.add_sheet({ "url" : theme_url });
+					themes_loaded.push(theme_url);
+				}
+				if(this.data.themes.theme != theme_name) {
+					this.get_container().removeClass('jstree-' + this.data.themes.theme);
+					this.data.themes.theme = theme_name;
+				}
+				this.get_container().addClass('jstree-' + theme_name);
+				if(!this.data.themes.dots) { this.hide_dots(); }
+				else { this.show_dots(); }
+				if(!this.data.themes.icons) { this.hide_icons(); }
+				else { this.show_icons(); }
+				this.__callback();
+			},
+			get_theme	: function () { return this.data.themes.theme; },
+
+			show_dots	: function () { this.data.themes.dots = true; this.get_container().children("ul").removeClass("jstree-no-dots"); },
+			hide_dots	: function () { this.data.themes.dots = false; this.get_container().children("ul").addClass("jstree-no-dots"); },
+			toggle_dots	: function () { if(this.data.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
+
+			show_icons	: function () { this.data.themes.icons = true; this.get_container().children("ul").removeClass("jstree-no-icons"); },
+			hide_icons	: function () { this.data.themes.icons = false; this.get_container().children("ul").addClass("jstree-no-icons"); },
+			toggle_icons: function () { if(this.data.themes.icons) { this.hide_icons(); } else { this.show_icons(); } }
+		}
+	});
+	// autodetect themes path
+	$(function () {
+		if($.jstree._themes === false) {
+			$("script").each(function () { 
+				if(this.src.toString().match(/jquery\.jstree[^\/]*?\.js(\?.*)?$/)) { 
+					$.jstree._themes = this.src.toString().replace(/jquery\.jstree[^\/]*?\.js(\?.*)?$/, "") + 'themes/'; 
+					return false; 
+				}
+			});
+		}
+		if($.jstree._themes === false) { $.jstree._themes = "themes/"; }
+	});
+	// include the themes plugin by default
+	$.jstree.defaults.plugins.push("themes");
+})(jQuery);
+//*/
+
+/*
+ * jsTree hotkeys plugin
+ * Enables keyboard navigation for all tree instances
+ * Depends on the jstree ui & jquery hotkeys plugins
+ */
+(function ($) {
+	var bound = [];
+	function exec(i, event) {
+		var f = $.jstree._focused(), tmp;
+		if(f && f.data && f.data.hotkeys && f.data.hotkeys.enabled) { 
+			tmp = f._get_settings().hotkeys[i];
+			if(tmp) { return tmp.call(f, event); }
+		}
+	}
+	$.jstree.plugin("hotkeys", {
+		__init : function () {
+			if(typeof $.hotkeys === "undefined") { throw "jsTree hotkeys: jQuery hotkeys plugin not included."; }
+			if(!this.data.ui) { throw "jsTree hotkeys: jsTree UI plugin not included."; }
+			$.each(this._get_settings().hotkeys, function (i, v) {
+				if(v !== false && $.inArray(i, bound) == -1) {
+					$(document).bind("keydown", i, function (event) { return exec(i, event); });
+					bound.push(i);
+				}
+			});
+			this.get_container()
+				.bind("lock.jstree", $.proxy(function () {
+						if(this.data.hotkeys.enabled) { this.data.hotkeys.enabled = false; this.data.hotkeys.revert = true; }
+					}, this))
+				.bind("unlock.jstree", $.proxy(function () {
+						if(this.data.hotkeys.revert) { this.data.hotkeys.enabled = true; }
+					}, this));
+			this.enable_hotkeys();
+		},
+		defaults : {
+			"up" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
+				this.hover_node(this._get_prev(o));
+				return false; 
+			},
+			"ctrl+up" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
+				this.hover_node(this._get_prev(o));
+				return false; 
+			},
+			"shift+up" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
+				this.hover_node(this._get_prev(o));
+				return false; 
+			},
+			"down" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
+				this.hover_node(this._get_next(o));
+				return false;
+			},
+			"ctrl+down" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
+				this.hover_node(this._get_next(o));
+				return false;
+			},
+			"shift+down" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
+				this.hover_node(this._get_next(o));
+				return false;
+			},
+			"left" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected;
+				if(o) {
+					if(o.hasClass("jstree-open")) { this.close_node(o); }
+					else { this.hover_node(this._get_prev(o)); }
+				}
+				return false;
+			},
+			"ctrl+left" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected;
+				if(o) {
+					if(o.hasClass("jstree-open")) { this.close_node(o); }
+					else { this.hover_node(this._get_prev(o)); }
+				}
+				return false;
+			},
+			"shift+left" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected;
+				if(o) {
+					if(o.hasClass("jstree-open")) { this.close_node(o); }
+					else { this.hover_node(this._get_prev(o)); }
+				}
+				return false;
+			},
+			"right" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected;
+				if(o && o.length) {
+					if(o.hasClass("jstree-closed")) { this.open_node(o); }
+					else { this.hover_node(this._get_next(o)); }
+				}
+				return false;
+			},
+			"ctrl+right" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected;
+				if(o && o.length) {
+					if(o.hasClass("jstree-closed")) { this.open_node(o); }
+					else { this.hover_node(this._get_next(o)); }
+				}
+				return false;
+			},
+			"shift+right" : function () { 
+				var o = this.data.ui.hovered || this.data.ui.last_selected;
+				if(o && o.length) {
+					if(o.hasClass("jstree-closed")) { this.open_node(o); }
+					else { this.hover_node(this._get_next(o)); }
+				}
+				return false;
+			},
+			"space" : function () { 
+				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").click(); } 
+				return false; 
+			},
+			"ctrl+space" : function (event) { 
+				event.type = "click";
+				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
+				return false; 
+			},
+			"shift+space" : function (event) { 
+				event.type = "click";
+				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
+				return false; 
+			},
+			"f2" : function () { this.rename(this.data.ui.hovered || this.data.ui.last_selected); },
+			"del" : function () { this.remove(this.data.ui.hovered || this._get_node(null)); }
+		},
+		_fn : {
+			enable_hotkeys : function () {
+				this.data.hotkeys.enabled = true;
+			},
+			disable_hotkeys : function () {
+				this.data.hotkeys.enabled = false;
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree JSON plugin
+ * The JSON data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
+ */
+(function ($) {
+	$.jstree.plugin("json_data", {
+		__init : function() {
+			var s = this._get_settings().json_data;
+			if(s.progressive_unload) {
+				this.get_container().bind("after_close.jstree", function (e, data) {
+					data.rslt.obj.children("ul").remove();
+				});
+			}
+		},
+		defaults : { 
+			// `data` can be a function:
+			//  * accepts two arguments - node being loaded and a callback to pass the result to
+			//  * will be executed in the current tree's scope & ajax won't be supported
+			data : false, 
+			ajax : false,
+			correct_state : true,
+			progressive_render : false,
+			progressive_unload : false
+		},
+		_fn : {
+			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_json(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
+			_is_loaded : function (obj) { 
+				var s = this._get_settings().json_data;
+				obj = this._get_node(obj); 
+				return obj == -1 || !obj || (!s.ajax && !s.progressive_render && !$.isFunction(s.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").length > 0;
+			},
+			refresh : function (obj) {
+				obj = this._get_node(obj);
+				var s = this._get_settings().json_data;
+				if(obj && obj !== -1 && s.progressive_unload && ($.isFunction(s.data) || !!s.ajax)) {
+					obj.removeData("jstree_children");
+				}
+				return this.__call_old();
+			},
+			load_node_json : function (obj, s_call, e_call) {
+				var s = this.get_settings().json_data, d,
+					error_func = function () {},
+					success_func = function () {};
+				obj = this._get_node(obj);
+
+				if(obj && obj !== -1 && (s.progressive_render || s.progressive_unload) && !obj.is(".jstree-open, .jstree-leaf") && obj.children("ul").children("li").length === 0 && obj.data("jstree_children")) {
+					d = this._parse_json(obj.data("jstree_children"), obj);
+					if(d) {
+						obj.append(d);
+						if(!s.progressive_unload) { obj.removeData("jstree_children"); }
+					}
+					this.clean_node(obj);
+					if(s_call) { s_call.call(this); }
+					return;
+				}
+
+				if(obj && obj !== -1) {
+					if(obj.data("jstree_is_loading")) { return; }
+					else { obj.data("jstree_is_loading",true); }
+				}
+				switch(!0) {
+					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
+					// function option added here for easier model integration (also supporting async - see callback)
+					case ($.isFunction(s.data)):
+						s.data.call(this, obj, $.proxy(function (d) {
+							d = this._parse_json(d, obj);
+							if(!d) { 
+								if(obj === -1 || !obj) {
+									if(s.correct_state) { this.get_container().children("ul").empty(); }
+								}
+								else {
+									obj.children("a.jstree-loading").removeClass("jstree-loading");
+									obj.removeData("jstree_is_loading");
+									if(s.correct_state) { this.correct_state(obj); }
+								}
+								if(e_call) { e_call.call(this); }
+							}
+							else {
+								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
+								else { obj.append(d).children("a.jstree-loading").removeClass("jstree-loading"); obj.removeData("jstree_is_loading"); }
+								this.clean_node(obj);
+								if(s_call) { s_call.call(this); }
+							}
+						}, this));
+						break;
+					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
+						if(!obj || obj == -1) {
+							d = this._parse_json(s.data, obj);
+							if(d) {
+								this.get_container().children("ul").empty().append(d.children());
+								this.clean_node();
+							}
+							else { 
+								if(s.correct_state) { this.get_container().children("ul").empty(); }
+							}
+						}
+						if(s_call) { s_call.call(this); }
+						break;
+					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
+						error_func = function (x, t, e) {
+							var ef = this.get_settings().json_data.ajax.error; 
+							if(ef) { ef.call(this, x, t, e); }
+							if(obj != -1 && obj.length) {
+								obj.children("a.jstree-loading").removeClass("jstree-loading");
+								obj.removeData("jstree_is_loading");
+								if(t === "success" && s.correct_state) { this.correct_state(obj); }
+							}
+							else {
+								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
+							}
+							if(e_call) { e_call.call(this); }
+						};
+						success_func = function (d, t, x) {
+							var sf = this.get_settings().json_data.ajax.success; 
+							if(sf) { d = sf.call(this,d,t,x) || d; }
+							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "") || (!$.isArray(d) && !$.isPlainObject(d))) {
+								return error_func.call(this, x, t, "");
+							}
+							d = this._parse_json(d, obj);
+							if(d) {
+								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
+								else { obj.append(d).children("a.jstree-loading").removeClass("jstree-loading"); obj.removeData("jstree_is_loading"); }
+								this.clean_node(obj);
+								if(s_call) { s_call.call(this); }
+							}
+							else {
+								if(obj === -1 || !obj) {
+									if(s.correct_state) { 
+										this.get_container().children("ul").empty(); 
+										if(s_call) { s_call.call(this); }
+									}
+								}
+								else {
+									obj.children("a.jstree-loading").removeClass("jstree-loading");
+									obj.removeData("jstree_is_loading");
+									if(s.correct_state) { 
+										this.correct_state(obj);
+										if(s_call) { s_call.call(this); } 
+									}
+								}
+							}
+						};
+						s.ajax.context = this;
+						s.ajax.error = error_func;
+						s.ajax.success = success_func;
+						if(!s.ajax.dataType) { s.ajax.dataType = "json"; }
+						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
+						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
+						$.ajax(s.ajax);
+						break;
+				}
+			},
+			_parse_json : function (js, obj, is_callback) {
+				var d = false, 
+					p = this._get_settings(),
+					s = p.json_data,
+					t = p.core.html_titles,
+					tmp, i, j, ul1, ul2;
+
+				if(!js) { return d; }
+				if(s.progressive_unload && obj && obj !== -1) { 
+					obj.data("jstree_children", d);
+				}
+				if($.isArray(js)) {
+					d = $('<ul>');
+					if(!js.length) { return false; }
+					for(i = 0, j = js.length; i < j; i++) {
+						tmp = this._parse_json(js[i], obj, true);
+						if(tmp.length) {
+							d = d.append(tmp);
+						}
+					}
+					d = d.children();
+				}
+				else {
+					if(typeof js == "string") { js = { data : js }; }
+					if(!js.data && js.data !== "") { return d; }
+					d = $("<li />");
+					if(js.attr) { d.attr(js.attr); }
+					if(js.metadata) { d.data(js.metadata); }
+					if(js.state) { d.addClass("jstree-" + js.state); }
+					if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
+					$.each(js.data, function (i, m) {
+						tmp = $("<a />");
+						if($.isFunction(m)) { m = m.call(this, js); }
+						if(typeof m == "string") { tmp.attr('href','#')[ t ? "html" : "text" ](m); }
+						else {
+							if(!m.attr) { m.attr = {}; }
+							if(!m.attr.href) { m.attr.href = '#'; }
+							tmp.attr(m.attr)[ t ? "html" : "text" ](m.title);
+							if(m.language) { tmp.addClass(m.language); }
+						}
+						tmp.prepend("<ins class='jstree-icon'> </ins>");
+						if(!m.icon && js.icon) { m.icon = js.icon; }
+						if(m.icon) { 
+							if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
+							else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
+						}
+						d.append(tmp);
+					});
+					d.prepend("<ins class='jstree-icon'> </ins>");
+					if(js.children) { 
+						if(s.progressive_render && js.state !== "open") {
+							d.addClass("jstree-closed").data("jstree_children", js.children);
+						}
+						else {
+							if(s.progressive_unload) { d.data("jstree_children", js.children); }
+							if($.isArray(js.children) && js.children.length) {
+								tmp = this._parse_json(js.children, obj, true);
+								if(tmp.length) {
+									ul2 = $("<ul />");
+									ul2.append(tmp);
+									d.append(ul2);
+								}
+							}
+						}
+					}
+				}
+				if(!is_callback) {
+					ul1 = $("<ul />");
+					ul1.append(d);
+					d = ul1;
+				}
+				return d;
+			},
+			get_json : function (obj, li_attr, a_attr, is_callback) {
+				var result = [], 
+					s = this._get_settings(), 
+					_this = this,
+					tmp1, tmp2, li, a, t, lang;
+				obj = this._get_node(obj);
+				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
+				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
+				if(!is_callback && this.data.types) { li_attr.push(s.types.type_attr); }
+				a_attr = $.isArray(a_attr) ? a_attr : [ ];
+
+				obj.each(function () {
+					li = $(this);
+					tmp1 = { data : [] };
+					if(li_attr.length) { tmp1.attr = { }; }
+					$.each(li_attr, function (i, v) { 
+						tmp2 = li.attr(v); 
+						if(tmp2 && tmp2.length && tmp2.replace(/jstree[^ ]*/ig,'').length) {
+							tmp1.attr[v] = (" " + tmp2).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,""); 
+						}
+					});
+					if(li.hasClass("jstree-open")) { tmp1.state = "open"; }
+					if(li.hasClass("jstree-closed")) { tmp1.state = "closed"; }
+					if(li.data()) { tmp1.metadata = li.data(); }
+					a = li.children("a");
+					a.each(function () {
+						t = $(this);
+						if(
+							a_attr.length || 
+							$.inArray("languages", s.plugins) !== -1 || 
+							t.children("ins").get(0).style.backgroundImage.length || 
+							(t.children("ins").get(0).className && t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').length)
+						) { 
+							lang = false;
+							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
+								$.each(s.languages, function (l, lv) {
+									if(t.hasClass(lv)) {
+										lang = lv;
+										return false;
+									}
+								});
+							}
+							tmp2 = { attr : { }, title : _this.get_text(t, lang) }; 
+							$.each(a_attr, function (k, z) {
+								tmp2.attr[z] = (" " + (t.attr(z) || "")).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
+							});
+							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
+								$.each(s.languages, function (k, z) {
+									if(t.hasClass(z)) { tmp2.language = z; return true; }
+								});
+							}
+							if(t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
+								tmp2.icon = t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
+							}
+							if(t.children("ins").get(0).style.backgroundImage.length) {
+								tmp2.icon = t.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","");
+							}
+						}
+						else {
+							tmp2 = _this.get_text(t);
+						}
+						if(a.length > 1) { tmp1.data.push(tmp2); }
+						else { tmp1.data = tmp2; }
+					});
+					li = li.find("> ul > li");
+					if(li.length) { tmp1.children = _this.get_json(li, li_attr, a_attr, true); }
+					result.push(tmp1);
+				});
+				return result;
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree languages plugin
+ * Adds support for multiple language versions in one tree
+ * This basically allows for many titles coexisting in one node, but only one of them being visible at any given time
+ * This is useful for maintaining the same structure in many languages (hence the name of the plugin)
+ */
+(function ($) {
+	var sh = false;
+	$.jstree.plugin("languages", {
+		__init : function () { this._load_css();  },
+		defaults : [],
+		_fn : {
+			set_lang : function (i) { 
+				var langs = this._get_settings().languages,
+					st = false,
+					selector = ".jstree-" + this.get_index() + ' a';
+				if(!$.isArray(langs) || langs.length === 0) { return false; }
+				if($.inArray(i,langs) == -1) {
+					if(!!langs[i]) { i = langs[i]; }
+					else { return false; }
+				}
+				if(i == this.data.languages.current_language) { return true; }
+				st = $.vakata.css.get_css(selector + "." + this.data.languages.current_language, false, sh);
+				if(st !== false) { st.style.display = "none"; }
+				st = $.vakata.css.get_css(selector + "." + i, false, sh);
+				if(st !== false) { st.style.display = ""; }
+				this.data.languages.current_language = i;
+				this.__callback(i);
+				return true;
+			},
+			get_lang : function () {
+				return this.data.languages.current_language;
+			},
+			_get_string : function (key, lang) {
+				var langs = this._get_settings().languages,
+					s = this._get_settings().core.strings;
+				if($.isArray(langs) && langs.length) {
+					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
+				}
+				if(s[lang] && s[lang][key]) { return s[lang][key]; }
+				if(s[key]) { return s[key]; }
+				return key;
+			},
+			get_text : function (obj, lang) {
+				obj = this._get_node(obj) || this.data.ui.last_selected;
+				if(!obj.size()) { return false; }
+				var langs = this._get_settings().languages,
+					s = this._get_settings().core.html_titles;
+				if($.isArray(langs) && langs.length) {
+					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
+					obj = obj.children("a." + lang);
+				}
+				else { obj = obj.children("a:eq(0)"); }
+				if(s) {
+					obj = obj.clone();
+					obj.children("INS").remove();
+					return obj.html();
+				}
+				else {
+					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
+					return obj.nodeValue;
+				}
+			},
+			set_text : function (obj, val, lang) {
+				obj = this._get_node(obj) || this.data.ui.last_selected;
+				if(!obj.size()) { return false; }
+				var langs = this._get_settings().languages,
+					s = this._get_settings().core.html_titles,
+					tmp;
+				if($.isArray(langs) && langs.length) {
+					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
+					obj = obj.children("a." + lang);
+				}
+				else { obj = obj.children("a:eq(0)"); }
+				if(s) {
+					tmp = obj.children("INS").clone();
+					obj.html(val).prepend(tmp);
+					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
+					return true;
+				}
+				else {
+					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
+					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
+					return (obj.nodeValue = val);
+				}
+			},
+			_load_css : function () {
+				var langs = this._get_settings().languages,
+					str = "/* languages css */",
+					selector = ".jstree-" + this.get_index() + ' a',
+					ln;
+				if($.isArray(langs) && langs.length) {
+					this.data.languages.current_language = langs[0];
+					for(ln = 0; ln < langs.length; ln++) {
+						str += selector + "." + langs[ln] + " {";
+						if(langs[ln] != this.data.languages.current_language) { str += " display:none; "; }
+						str += " } ";
+					}
+					sh = $.vakata.css.add_sheet({ 'str' : str, 'title' : "jstree-languages" });
+				}
+			},
+			create_node : function (obj, position, js, callback) {
+				var t = this.__call_old(true, obj, position, js, function (t) {
+					var langs = this._get_settings().languages,
+						a = t.children("a"),
+						ln;
+					if($.isArray(langs) && langs.length) {
+						for(ln = 0; ln < langs.length; ln++) {
+							if(!a.is("." + langs[ln])) {
+								t.append(a.eq(0).clone().removeClass(langs.join(" ")).addClass(langs[ln]));
+							}
+						}
+						a.not("." + langs.join(", .")).remove();
+					}
+					if(callback) { callback.call(this, t); }
+				});
+				return t;
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/*
+ * jsTree cookies plugin
+ * Stores the currently opened/selected nodes in a cookie and then restores them
+ * Depends on the jquery.cookie plugin
+ */
+(function ($) {
+	$.jstree.plugin("cookies", {
+		__init : function () {
+			if(typeof $.cookie === "undefined") { throw "jsTree cookie: jQuery cookie plugin not included."; }
+
+			var s = this._get_settings().cookies,
+				tmp;
+			if(!!s.save_loaded) {
+				tmp = $.cookie(s.save_loaded);
+				if(tmp && tmp.length) { this.data.core.to_load = tmp.split(","); }
+			}
+			if(!!s.save_opened) {
+				tmp = $.cookie(s.save_opened);
+				if(tmp && tmp.length) { this.data.core.to_open = tmp.split(","); }
+			}
+			if(!!s.save_selected) {
+				tmp = $.cookie(s.save_selected);
+				if(tmp && tmp.length && this.data.ui) { this.data.ui.to_select = tmp.split(","); }
+			}
+			this.get_container()
+				.one( ( this.data.ui ? "reselect" : "reopen" ) + ".jstree", $.proxy(function () {
+					this.get_container()
+						.bind("open_node.jstree close_node.jstree select_node.jstree deselect_node.jstree", $.proxy(function (e) { 
+								if(this._get_settings().cookies.auto_save) { this.save_cookie((e.handleObj.namespace + e.handleObj.type).replace("jstree","")); }
+							}, this));
+				}, this));
+		},
+		defaults : {
+			save_loaded		: "jstree_load",
+			save_opened		: "jstree_open",
+			save_selected	: "jstree_select",
+			auto_save		: true,
+			cookie_options	: {}
+		},
+		_fn : {
+			save_cookie : function (c) {
+				if(this.data.core.refreshing) { return; }
+				var s = this._get_settings().cookies;
+				if(!c) { // if called manually and not by event
+					if(s.save_loaded) {
+						this.save_loaded();
+						$.cookie(s.save_loaded, this.data.core.to_load.join(","), s.cookie_options);
+					}
+					if(s.save_opened) {
+						this.save_opened();
+						$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options);
+					}
+					if(s.save_selected && this.data.ui) {
+						this.save_selected();
+						$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options);
+					}
+					return;
+				}
+				switch(c) {
+					case "open_node":
+					case "close_node":
+						if(!!s.save_opened) { 
+							this.save_opened(); 
+							$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options); 
+						}
+						if(!!s.save_loaded) { 
+							this.save_loaded(); 
+							$.cookie(s.save_loaded, this.data.core.to_load.join(","), s.cookie_options); 
+						}
+						break;
+					case "select_node":
+					case "deselect_node":
+						if(!!s.save_selected && this.data.ui) { 
+							this.save_selected(); 
+							$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options); 
+						}
+						break;
+				}
+			}
+		}
+	});
+	// include cookies by default
+	// $.jstree.defaults.plugins.push("cookies");
+})(jQuery);
+//*/
+
+/*
+ * jsTree sort plugin
+ * Sorts items alphabetically (or using any other function)
+ */
+(function ($) {
+	$.jstree.plugin("sort", {
+		__init : function () {
+			this.get_container()
+				.bind("load_node.jstree", $.proxy(function (e, data) {
+						var obj = this._get_node(data.rslt.obj);
+						obj = obj === -1 ? this.get_container().children("ul") : obj.children("ul");
+						this.sort(obj);
+					}, this))
+				.bind("rename_node.jstree create_node.jstree create.jstree", $.proxy(function (e, data) {
+						this.sort(data.rslt.obj.parent());
+					}, this))
+				.bind("move_node.jstree", $.proxy(function (e, data) {
+						var m = data.rslt.np == -1 ? this.get_container() : data.rslt.np;
+						this.sort(m.children("ul"));
+					}, this));
+		},
+		defaults : function (a, b) { return this.get_text(a) > this.get_text(b) ? 1 : -1; },
+		_fn : {
+			sort : function (obj) {
+				var s = this._get_settings().sort,
+					t = this;
+				obj.append($.makeArray(obj.children("li")).sort($.proxy(s, t)));
+				obj.find("> li > ul").each(function() { t.sort($(this)); });
+				this.clean_node(obj);
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/*
+ * jsTree DND plugin
+ * Drag and drop plugin for moving/copying nodes
+ */
+(function ($) {
+	var o = false,
+		r = false,
+		m = false,
+		ml = false,
+		sli = false,
+		sti = false,
+		dir1 = false,
+		dir2 = false,
+		last_pos = false;
+	$.vakata.dnd = {
+		is_down : false,
+		is_drag : false,
+		helper : false,
+		scroll_spd : 10,
+		init_x : 0,
+		init_y : 0,
+		threshold : 5,
+		helper_left : 5,
+		helper_top : 10,
+		user_data : {},
+
+		drag_start : function (e, data, html) { 
+			if($.vakata.dnd.is_drag) { $.vakata.drag_stop({}); }
+			try {
+				e.currentTarget.unselectable = "on";
+				e.currentTarget.onselectstart = function() { return false; };
+				if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
+			} catch(err) { }
+			$.vakata.dnd.init_x = e.pageX;
+			$.vakata.dnd.init_y = e.pageY;
+			$.vakata.dnd.user_data = data;
+			$.vakata.dnd.is_down = true;
+			$.vakata.dnd.helper = $("<div id='vakata-dragged' />").html(html); //.fadeTo(10,0.25);
+			$(document).bind("mousemove", $.vakata.dnd.drag);
+			$(document).bind("mouseup", $.vakata.dnd.drag_stop);
+			return false;
+		},
+		drag : function (e) { 
+			if(!$.vakata.dnd.is_down) { return; }
+			if(!$.vakata.dnd.is_drag) {
+				if(Math.abs(e.pageX - $.vakata.dnd.init_x) > 5 || Math.abs(e.pageY - $.vakata.dnd.init_y) > 5) { 
+					$.vakata.dnd.helper.appendTo("body");
+					$.vakata.dnd.is_drag = true;
+					$(document).triggerHandler("drag_start.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
+				}
+				else { return; }
+			}
+
+			// maybe use a scrolling parent element instead of document?
+			if(e.type === "mousemove") { // thought of adding scroll in order to move the helper, but mouse poisition is n/a
+				var d = $(document), t = d.scrollTop(), l = d.scrollLeft();
+				if(e.pageY - t < 20) { 
+					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
+					if(!sti) { dir1 = "up"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() - $.vakata.dnd.scroll_spd); }, 150); }
+				}
+				else { 
+					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
+				}
+				if($(window).height() - (e.pageY - t) < 20) {
+					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
+					if(!sti) { dir1 = "down"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() + $.vakata.dnd.scroll_spd); }, 150); }
+				}
+				else { 
+					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
+				}
+
+				if(e.pageX - l < 20) {
+					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
+					if(!sli) { dir2 = "left"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() - $.vakata.dnd.scroll_spd); }, 150); }
+				}
+				else { 
+					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
+				}
+				if($(window).width() - (e.pageX - l) < 20) {
+					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
+					if(!sli) { dir2 = "right"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() + $.vakata.dnd.scroll_spd); }, 150); }
+				}
+				else { 
+					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
+				}
+			}
+
+			$.vakata.dnd.helper.css({ left : (e.pageX + $.vakata.dnd.helper_left) + "px", top : (e.pageY + $.vakata.dnd.helper_top) + "px" });
+			$(document).triggerHandler("drag.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
+		},
+		drag_stop : function (e) {
+			if(sli) { clearInterval(sli); }
+			if(sti) { clearInterval(sti); }
+			$(document).unbind("mousemove", $.vakata.dnd.drag);
+			$(document).unbind("mouseup", $.vakata.dnd.drag_stop);
+			$(document).triggerHandler("drag_stop.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
+			$.vakata.dnd.helper.remove();
+			$.vakata.dnd.init_x = 0;
+			$.vakata.dnd.init_y = 0;
+			$.vakata.dnd.user_data = {};
+			$.vakata.dnd.is_down = false;
+			$.vakata.dnd.is_drag = false;
+		}
+	};
+	$(function() {
+		var css_string = '#vakata-dragged { display:block; margin:0 0 0 0; padding:4px 4px 4px 24px; position:absolute; top:-2000px; line-height:16px; z-index:10000; } ';
+		$.vakata.css.add_sheet({ str : css_string, title : "vakata" });
+	});
+
+	$.jstree.plugin("dnd", {
+		__init : function () {
+			this.data.dnd = {
+				active : false,
+				after : false,
+				inside : false,
+				before : false,
+				off : false,
+				prepared : false,
+				w : 0,
+				to1 : false,
+				to2 : false,
+				cof : false,
+				cw : false,
+				ch : false,
+				i1 : false,
+				i2 : false,
+				mto : false
+			};
+			this.get_container()
+				.bind("mouseenter.jstree", $.proxy(function (e) {
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							if(this.data.themes) {
+								m.attr("class", "jstree-" + this.data.themes.theme); 
+								if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
+								$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme);
+							}
+							//if($(e.currentTarget).find("> ul > li").length === 0) {
+							if(e.currentTarget === e.target && $.vakata.dnd.user_data.obj && $($.vakata.dnd.user_data.obj).length && $($.vakata.dnd.user_data.obj).parents(".jstree:eq(0)")[0] !== e.target) { // node should not be from the same tree
+								var tr = $.jstree._reference(e.target), dc;
+								if(tr.data.dnd.foreign) {
+									dc = tr._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
+									if(dc === true || dc.inside === true || dc.before === true || dc.after === true) {
+										$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
+									}
+								}
+								else {
+									tr.prepare_move(o, tr.get_container(), "last");
+									if(tr.check_move()) {
+										$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
+									}
+								}
+							}
+						}
+					}, this))
+				.bind("mouseup.jstree", $.proxy(function (e) {
+						//if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && $(e.currentTarget).find("> ul > li").length === 0) {
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && e.currentTarget === e.target && $.vakata.dnd.user_data.obj && $($.vakata.dnd.user_data.obj).length && $($.vakata.dnd.user_data.obj).parents(".jstree:eq(0)")[0] !== e.target) { // node should not be from the same tree
+							var tr = $.jstree._reference(e.currentTarget), dc;
+							if(tr.data.dnd.foreign) {
+								dc = tr._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
+								if(dc === true || dc.inside === true || dc.before === true || dc.after === true) {
+									tr._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
+								}
+							}
+							else {
+								tr.move_node(o, tr.get_container(), "last", e[tr._get_settings().dnd.copy_modifier + "Key"]);
+							}
+						}
+					}, this))
+				.bind("mouseleave.jstree", $.proxy(function (e) {
+						if(e.relatedTarget && e.relatedTarget.id && e.relatedTarget.id === "jstree-marker-line") {
+							return false; 
+						}
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
+							if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
+							if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
+							if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
+							if($.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
+								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
+							}
+						}
+					}, this))
+				.bind("mousemove.jstree", $.proxy(function (e) {
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							var cnt = this.get_container()[0];
+
+							// Horizontal scroll
+							if(e.pageX + 24 > this.data.dnd.cof.left + this.data.dnd.cw) {
+								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
+								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft += $.vakata.dnd.scroll_spd; }, cnt), 100);
+							}
+							else if(e.pageX - 24 < this.data.dnd.cof.left) {
+								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
+								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft -= $.vakata.dnd.scroll_spd; }, cnt), 100);
+							}
+							else {
+								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
+							}
+
+							// Vertical scroll
+							if(e.pageY + 24 > this.data.dnd.cof.top + this.data.dnd.ch) {
+								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
+								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop += $.vakata.dnd.scroll_spd; }, cnt), 100);
+							}
+							else if(e.pageY - 24 < this.data.dnd.cof.top) {
+								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
+								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop -= $.vakata.dnd.scroll_spd; }, cnt), 100);
+							}
+							else {
+								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
+							}
+
+						}
+					}, this))
+				.bind("scroll.jstree", $.proxy(function (e) { 
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && m && ml) {
+							m.hide();
+							ml.hide();
+						}
+					}, this))
+				.delegate("a", "mousedown.jstree", $.proxy(function (e) { 
+						if(e.which === 1) {
+							this.start_drag(e.currentTarget, e);
+							return false;
+						}
+					}, this))
+				.delegate("a", "mouseenter.jstree", $.proxy(function (e) { 
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							this.dnd_enter(e.currentTarget);
+						}
+					}, this))
+				.delegate("a", "mousemove.jstree", $.proxy(function (e) { 
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							if(!r || !r.length || r.children("a")[0] !== e.currentTarget) {
+								this.dnd_enter(e.currentTarget);
+							}
+							if(typeof this.data.dnd.off.top === "undefined") { this.data.dnd.off = $(e.target).offset(); }
+							this.data.dnd.w = (e.pageY - (this.data.dnd.off.top || 0)) % this.data.core.li_height;
+							if(this.data.dnd.w < 0) { this.data.dnd.w += this.data.core.li_height; }
+							this.dnd_show();
+						}
+					}, this))
+				.delegate("a", "mouseleave.jstree", $.proxy(function (e) { 
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							if(e.relatedTarget && e.relatedTarget.id && e.relatedTarget.id === "jstree-marker-line") {
+								return false; 
+							}
+								if(m) { m.hide(); }
+								if(ml) { ml.hide(); }
+							/*
+							var ec = $(e.currentTarget).closest("li"), 
+								er = $(e.relatedTarget).closest("li");
+							if(er[0] !== ec.prev()[0] && er[0] !== ec.next()[0]) {
+								if(m) { m.hide(); }
+								if(ml) { ml.hide(); }
+							}
+							*/
+							this.data.dnd.mto = setTimeout( 
+								(function (t) { return function () { t.dnd_leave(e); }; })(this),
+							0);
+						}
+					}, this))
+				.delegate("a", "mouseup.jstree", $.proxy(function (e) { 
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
+							this.dnd_finish(e);
+						}
+					}, this));
+
+			$(document)
+				.bind("drag_stop.vakata", $.proxy(function () {
+						if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
+						if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
+						if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
+						if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
+						this.data.dnd.after		= false;
+						this.data.dnd.before	= false;
+						this.data.dnd.inside	= false;
+						this.data.dnd.off		= false;
+						this.data.dnd.prepared	= false;
+						this.data.dnd.w			= false;
+						this.data.dnd.to1		= false;
+						this.data.dnd.to2		= false;
+						this.data.dnd.i1		= false;
+						this.data.dnd.i2		= false;
+						this.data.dnd.active	= false;
+						this.data.dnd.foreign	= false;
+						if(m) { m.css({ "top" : "-2000px" }); }
+						if(ml) { ml.css({ "top" : "-2000px" }); }
+					}, this))
+				.bind("drag_start.vakata", $.proxy(function (e, data) {
+						if(data.data.jstree) { 
+							var et = $(data.event.target);
+							if(et.closest(".jstree").hasClass("jstree-" + this.get_index())) {
+								this.dnd_enter(et);
+							}
+						}
+					}, this));
+				/*
+				.bind("keydown.jstree-" + this.get_index() + " keyup.jstree-" + this.get_index(), $.proxy(function(e) {
+						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && !this.data.dnd.foreign) {
+							var h = $.vakata.dnd.helper.children("ins");
+							if(e[this._get_settings().dnd.copy_modifier + "Key"] && h.hasClass("jstree-ok")) {
+								h.parent().html(h.parent().html().replace(/ \(Copy\)$/, "") + " (Copy)");
+							} 
+							else {
+								h.parent().html(h.parent().html().replace(/ \(Copy\)$/, ""));
+							}
+						}
+					}, this)); */
+
+
+
+			var s = this._get_settings().dnd;
+			if(s.drag_target) {
+				$(document)
+					.delegate(s.drag_target, "mousedown.jstree-" + this.get_index(), $.proxy(function (e) {
+						o = e.target;
+						$.vakata.dnd.drag_start(e, { jstree : true, obj : e.target }, "<ins class='jstree-icon'></ins>" + $(e.target).text() );
+						if(this.data.themes) { 
+							if(m) { m.attr("class", "jstree-" + this.data.themes.theme); }
+							if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
+							$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
+						}
+						$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
+						var cnt = this.get_container();
+						this.data.dnd.cof = cnt.offset();
+						this.data.dnd.cw = parseInt(cnt.width(),10);
+						this.data.dnd.ch = parseInt(cnt.height(),10);
+						this.data.dnd.foreign = true;
+						e.preventDefault();
+					}, this));
+			}
+			if(s.drop_target) {
+				$(document)
+					.delegate(s.drop_target, "mouseenter.jstree-" + this.get_index(), $.proxy(function (e) {
+							if(this.data.dnd.active && this._get_settings().dnd.drop_check.call(this, { "o" : o, "r" : $(e.target), "e" : e })) {
+								$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
+							}
+						}, this))
+					.delegate(s.drop_target, "mouseleave.jstree-" + this.get_index(), $.proxy(function (e) {
+							if(this.data.dnd.active) {
+								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
+							}
+						}, this))
+					.delegate(s.drop_target, "mouseup.jstree-" + this.get_index(), $.proxy(function (e) {
+							if(this.data.dnd.active && $.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
+								this._get_settings().dnd.drop_finish.call(this, { "o" : o, "r" : $(e.target), "e" : e });
+							}
+						}, this));
+			}
+		},
+		defaults : {
+			copy_modifier	: "ctrl",
+			check_timeout	: 100,
+			open_timeout	: 500,
+			drop_target		: ".jstree-drop",
+			drop_check		: function (data) { return true; },
+			drop_finish		: $.noop,
+			drag_target		: ".jstree-draggable",
+			drag_finish		: $.noop,
+			drag_check		: function (data) { return { after : false, before : false, inside : true }; }
+		},
+		_fn : {
+			dnd_prepare : function () {
+				if(!r || !r.length) { return; }
+				this.data.dnd.off = r.offset();
+				if(this._get_settings().core.rtl) {
+					this.data.dnd.off.right = this.data.dnd.off.left + r.width();
+				}
+				if(this.data.dnd.foreign) {
+					var a = this._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : r });
+					this.data.dnd.after = a.after;
+					this.data.dnd.before = a.before;
+					this.data.dnd.inside = a.inside;
+					this.data.dnd.prepared = true;
+					return this.dnd_show();
+				}
+				this.prepare_move(o, r, "before");
+				this.data.dnd.before = this.check_move();
+				this.prepare_move(o, r, "after");
+				this.data.dnd.after = this.check_move();
+				if(this._is_loaded(r)) {
+					this.prepare_move(o, r, "inside");
+					this.data.dnd.inside = this.check_move();
+				}
+				else {
+					this.data.dnd.inside = false;
+				}
+				this.data.dnd.prepared = true;
+				return this.dnd_show();
+			},
+			dnd_show : function () {
+				if(!this.data.dnd.prepared) { return; }
+				var o = ["before","inside","after"],
+					r = false,
+					rtl = this._get_settings().core.rtl,
+					pos;
+				if(this.data.dnd.w < this.data.core.li_height/3) { o = ["before","inside","after"]; }
+				else if(this.data.dnd.w <= this.data.core.li_height*2/3) {
+					o = this.data.dnd.w < this.data.core.li_height/2 ? ["inside","before","after"] : ["inside","after","before"];
+				}
+				else { o = ["after","inside","before"]; }
+				$.each(o, $.proxy(function (i, val) { 
+					if(this.data.dnd[val]) {
+						$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
+						r = val;
+						return false;
+					}
+				}, this));
+				if(r === false) { $.vakata.dnd.helper.children("ins").attr("class","jstree-invalid"); }
+				
+				pos = rtl ? (this.data.dnd.off.right - 18) : (this.data.dnd.off.left + 10);
+				switch(r) {
+					case "before":
+						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top - 6) + "px" }).show();
+						if(ml) { ml.css({ "left" : (pos + 8) + "px", "top" : (this.data.dnd.off.top - 1) + "px" }).show(); }
+						break;
+					case "after":
+						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 6) + "px" }).show();
+						if(ml) { ml.css({ "left" : (pos + 8) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 1) + "px" }).show(); }
+						break;
+					case "inside":
+						m.css({ "left" : pos + ( rtl ? -4 : 4) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height/2 - 5) + "px" }).show();
+						if(ml) { ml.hide(); }
+						break;
+					default:
+						m.hide();
+						if(ml) { ml.hide(); }
+						break;
+				}
+				last_pos = r;
+				return r;
+			},
+			dnd_open : function () {
+				this.data.dnd.to2 = false;
+				this.open_node(r, $.proxy(this.dnd_prepare,this), true);
+			},
+			dnd_finish : function (e) {
+				if(this.data.dnd.foreign) {
+					if(this.data.dnd.after || this.data.dnd.before || this.data.dnd.inside) {
+						this._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : r, "p" : last_pos });
+					}
+				}
+				else {
+					this.dnd_prepare();
+					this.move_node(o, r, last_pos, e[this._get_settings().dnd.copy_modifier + "Key"]);
+				}
+				o = false;
+				r = false;
+				m.hide();
+				if(ml) { ml.hide(); }
+			},
+			dnd_enter : function (obj) {
+				if(this.data.dnd.mto) { 
+					clearTimeout(this.data.dnd.mto);
+					this.data.dnd.mto = false;
+				}
+				var s = this._get_settings().dnd;
+				this.data.dnd.prepared = false;
+				r = this._get_node(obj);
+				if(s.check_timeout) { 
+					// do the calculations after a minimal timeout (users tend to drag quickly to the desired location)
+					if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
+					this.data.dnd.to1 = setTimeout($.proxy(this.dnd_prepare, this), s.check_timeout); 
+				}
+				else { 
+					this.dnd_prepare(); 
+				}
+				if(s.open_timeout) { 
+					if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
+					if(r && r.length && r.hasClass("jstree-closed")) { 
+						// if the node is closed - open it, then recalculate
+						this.data.dnd.to2 = setTimeout($.proxy(this.dnd_open, this), s.open_timeout);
+					}
+				}
+				else {
+					if(r && r.length && r.hasClass("jstree-closed")) { 
+						this.dnd_open();
+					}
+				}
+			},
+			dnd_leave : function (e) {
+				this.data.dnd.after		= false;
+				this.data.dnd.before	= false;
+				this.data.dnd.inside	= false;
+				$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
+				m.hide();
+				if(ml) { ml.hide(); }
+				if(r && r[0] === e.target.parentNode) {
+					if(this.data.dnd.to1) {
+						clearTimeout(this.data.dnd.to1);
+						this.data.dnd.to1 = false;
+					}
+					if(this.data.dnd.to2) {
+						clearTimeout(this.data.dnd.to2);
+						this.data.dnd.to2 = false;
+					}
+				}
+			},
+			start_drag : function (obj, e) {
+				o = this._get_node(obj);
+				if(this.data.ui && this.is_selected(o)) { o = this._get_node(null, true); }
+				var dt = o.length > 1 ? this._get_string("multiple_selection") : this.get_text(o),
+					cnt = this.get_container();
+				if(!this._get_settings().core.html_titles) { dt = dt.replace(/</ig,"<").replace(/>/ig,">"); }
+				$.vakata.dnd.drag_start(e, { jstree : true, obj : o }, "<ins class='jstree-icon'></ins>" + dt );
+				if(this.data.themes) { 
+					if(m) { m.attr("class", "jstree-" + this.data.themes.theme); }
+					if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
+					$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
+				}
+				this.data.dnd.cof = cnt.offset();
+				this.data.dnd.cw = parseInt(cnt.width(),10);
+				this.data.dnd.ch = parseInt(cnt.height(),10);
+				this.data.dnd.active = true;
+			}
+		}
+	});
+	$(function() {
+		var css_string = '' + 
+			'#vakata-dragged ins { display:block; text-decoration:none; width:16px; height:16px; margin:0 0 0 0; padding:0; position:absolute; top:4px; left:4px; ' + 
+			' -moz-border-radius:4px; border-radius:4px; -webkit-border-radius:4px; ' +
+			'} ' + 
+			'#vakata-dragged .jstree-ok { background:green; } ' + 
+			'#vakata-dragged .jstree-invalid { background:red; } ' + 
+			'#jstree-marker { padding:0; margin:0; font-size:12px; overflow:hidden; height:12px; width:8px; position:absolute; top:-30px; z-index:10001; background-repeat:no-repeat; display:none; background-color:transparent; text-shadow:1px 1px 1px white; color:black; line-height:10px; } ' + 
+			'#jstree-marker-line { padding:0; margin:0; line-height:0%; font-size:1px; overflow:hidden; height:1px; width:100px; position:absolute; top:-30px; z-index:10000; background-repeat:no-repeat; display:none; background-color:#456c43; ' + 
+			' cursor:pointer; border:1px solid #eeeeee; border-left:0; -moz-box-shadow: 0px 0px 2px #666; -webkit-box-shadow: 0px 0px 2px #666; box-shadow: 0px 0px 2px #666; ' + 
+			' -moz-border-radius:1px; border-radius:1px; -webkit-border-radius:1px; ' +
+			'}' + 
+			'';
+		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
+		m = $("<div />").attr({ id : "jstree-marker" }).hide().html("»")
+			.bind("mouseleave mouseenter", function (e) { 
+				m.hide();
+				ml.hide();
+				e.preventDefault(); 
+				e.stopImmediatePropagation(); 
+				return false; 
+			})
+			.appendTo("body");
+		ml = $("<div />").attr({ id : "jstree-marker-line" }).hide()
+			.bind("mouseup", function (e) { 
+				if(r && r.length) { 
+					r.children("a").trigger(e); 
+					e.preventDefault(); 
+					e.stopImmediatePropagation(); 
+					return false; 
+				} 
+			})
+			.bind("mouseleave", function (e) { 
+				var rt = $(e.relatedTarget);
+				if(rt.is(".jstree") || rt.closest(".jstree").length === 0) {
+					if(r && r.length) { 
+						r.children("a").trigger(e); 
+						m.hide();
+						ml.hide();
+						e.preventDefault(); 
+						e.stopImmediatePropagation(); 
+						return false; 
+					}
+				}
+			})
+			.appendTo("body");
+		$(document).bind("drag_start.vakata", function (e, data) {
+			if(data.data.jstree) { m.show(); if(ml) { ml.show(); } }
+		});
+		$(document).bind("drag_stop.vakata", function (e, data) {
+			if(data.data.jstree) { m.hide(); if(ml) { ml.hide(); } }
+		});
+	});
+})(jQuery);
+//*/
+
+/*
+ * jsTree checkbox plugin
+ * Inserts checkboxes in front of every node
+ * Depends on the ui plugin
+ * DOES NOT WORK NICELY WITH MULTITREE DRAG'N'DROP
+ */
+(function ($) {
+	$.jstree.plugin("checkbox", {
+		__init : function () {
+			this.data.checkbox.noui = this._get_settings().checkbox.override_ui;
+			if(this.data.ui && this.data.checkbox.noui) {
+				this.select_node = this.deselect_node = this.deselect_all = $.noop;
+				this.get_selected = this.get_checked;
+			}
+
+			this.get_container()
+				.bind("open_node.jstree create_node.jstree clean_node.jstree refresh.jstree", $.proxy(function (e, data) { 
+						this._prepare_checkboxes(data.rslt.obj);
+					}, this))
+				.bind("loaded.jstree", $.proxy(function (e) {
+						this._prepare_checkboxes();
+					}, this))
+				.delegate( (this.data.ui && this.data.checkbox.noui ? "a" : "ins.jstree-checkbox") , "click.jstree", $.proxy(function (e) {
+						e.preventDefault();
+						if(this._get_node(e.target).hasClass("jstree-checked")) { this.uncheck_node(e.target); }
+						else { this.check_node(e.target); }
+						if(this.data.ui && this.data.checkbox.noui) {
+							this.save_selected();
+							if(this.data.cookies) { this.save_cookie("select_node"); }
+						}
+						else {
+							e.stopImmediatePropagation();
+							return false;
+						}
+					}, this));
+		},
+		defaults : {
+			override_ui : false,
+			two_state : false,
+			real_checkboxes : false,
+			checked_parent_open : true,
+			real_checkboxes_names : function (n) { return [ ("check_" + (n[0].id || Math.ceil(Math.random() * 10000))) , 1]; }
+		},
+		__destroy : function () {
+			this.get_container()
+				.find("input.jstree-real-checkbox").removeClass("jstree-real-checkbox").end()
+				.find("ins.jstree-checkbox").remove();
+		},
+		_fn : {
+			_checkbox_notify : function (n, data) {
+				if(data.checked) {
+					this.check_node(n, false);
+				}
+			},
+			_prepare_checkboxes : function (obj) {
+				obj = !obj || obj == -1 ? this.get_container().find("> ul > li") : this._get_node(obj);
+				if(obj === false) { return; } // added for removing root nodes
+				var c, _this = this, t, ts = this._get_settings().checkbox.two_state, rc = this._get_settings().checkbox.real_checkboxes, rcn = this._get_settings().checkbox.real_checkboxes_names;
+				obj.each(function () {
+					t = $(this);
+					c = t.is("li") && (t.hasClass("jstree-checked") || (rc && t.children(":checked").length)) ? "jstree-checked" : "jstree-unchecked";
+					t.find("li").addBack().each(function () {
+						var $t = $(this), nm;
+						$t.children("a" + (_this.data.languages ? "" : ":eq(0)") ).not(":has(.jstree-checkbox)").prepend("<ins class='jstree-checkbox'> </ins>").parent().not(".jstree-checked, .jstree-unchecked").addClass( ts ? "jstree-unchecked" : c );
+						if(rc) {
+							if(!$t.children(":checkbox").length) {
+								nm = rcn.call(_this, $t);
+								$t.prepend("<input type='checkbox' class='jstree-real-checkbox' id='" + nm[0] + "' name='" + nm[0] + "' value='" + nm[1] + "' />");
+							}
+							else {
+								$t.children(":checkbox").addClass("jstree-real-checkbox");
+							}
+						}
+						if(!ts) {
+							if(c === "jstree-checked" || $t.hasClass("jstree-checked") || $t.children(':checked').length) {
+								$t.find("li").addBack().addClass("jstree-checked").children(":checkbox").prop("checked", true);
+							}
+						}
+						else {
+							if($t.hasClass("jstree-checked") || $t.children(':checked').length) {
+								$t.addClass("jstree-checked").children(":checkbox").prop("checked", true);
+							}
+						}
+					});
+				});
+				if(!ts) {
+					obj.find(".jstree-checked").parent().parent().each(function () { _this._repair_state(this); }); 
+				}
+			},
+			change_state : function (obj, state) {
+				obj = this._get_node(obj);
+				var coll = false, rc = this._get_settings().checkbox.real_checkboxes;
+				if(!obj || obj === -1) { return false; }
+				state = (state === false || state === true) ? state : obj.hasClass("jstree-checked");
+				if(this._get_settings().checkbox.two_state) {
+					if(state) { 
+						obj.removeClass("jstree-checked").addClass("jstree-unchecked"); 
+						if(rc) { obj.children(":checkbox").prop("checked", false); }
+					}
+					else { 
+						obj.removeClass("jstree-unchecked").addClass("jstree-checked"); 
+						if(rc) { obj.children(":checkbox").prop("checked", true); }
+					}
+				}
+				else {
+					if(state) { 
+						coll = obj.find("li").addBack();
+						if(!coll.filter(".jstree-checked, .jstree-undetermined").length) { return false; }
+						coll.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked"); 
+						if(rc) { coll.children(":checkbox").prop("checked", false); }
+					}
+					else { 
+						coll = obj.find("li").addBack();
+						if(!coll.filter(".jstree-unchecked, .jstree-undetermined").length) { return false; }
+						coll.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked"); 
+						if(rc) { coll.children(":checkbox").prop("checked", true); }
+						if(this.data.ui) { this.data.ui.last_selected = obj; }
+						this.data.checkbox.last_selected = obj;
+					}
+					obj.parentsUntil(".jstree", "li").each(function () {
+						var $this = $(this);
+						if(state) {
+							if($this.children("ul").children("li.jstree-checked, li.jstree-undetermined").length) {
+								$this.parentsUntil(".jstree", "li").addBack().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
+								if(rc) { $this.parentsUntil(".jstree", "li").addBack().children(":checkbox").prop("checked", false); }
+								return false;
+							}
+							else {
+								$this.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked");
+								if(rc) { $this.children(":checkbox").prop("checked", false); }
+							}
+						}
+						else {
+							if($this.children("ul").children("li.jstree-unchecked, li.jstree-undetermined").length) {
+								$this.parentsUntil(".jstree", "li").addBack().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
+								if(rc) { $this.parentsUntil(".jstree", "li").addBack().children(":checkbox").prop("checked", false); }
+								return false;
+							}
+							else {
+								$this.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked");
+								if(rc) { $this.children(":checkbox").prop("checked", true); }
+							}
+						}
+					});
+				}
+				if(this.data.ui && this.data.checkbox.noui) { this.data.ui.selected = this.get_checked(); }
+				this.__callback(obj);
+				return true;
+			},
+			check_node : function (obj) {
+				if(this.change_state(obj, false)) { 
+					obj = this._get_node(obj);
+					if(this._get_settings().checkbox.checked_parent_open) {
+						var t = this;
+						obj.parents(".jstree-closed").each(function () { t.open_node(this, false, true); });
+					}
+					this.__callback({ "obj" : obj }); 
+				}
+			},
+			uncheck_node : function (obj) {
+				if(this.change_state(obj, true)) { this.__callback({ "obj" : this._get_node(obj) }); }
+			},
+			check_all : function () {
+				var _this = this, 
+					coll = this._get_settings().checkbox.two_state ? this.get_container_ul().find("li") : this.get_container_ul().children("li");
+				coll.each(function () {
+					_this.change_state(this, false);
+				});
+				this.__callback();
+			},
+			uncheck_all : function () {
+				var _this = this,
+					coll = this._get_settings().checkbox.two_state ? this.get_container_ul().find("li") : this.get_container_ul().children("li");
+				coll.each(function () {
+					_this.change_state(this, true);
+				});
+				this.__callback();
+			},
+
+			is_checked : function(obj) {
+				obj = this._get_node(obj);
+				return obj.length ? obj.is(".jstree-checked") : false;
+			},
+			get_checked : function (obj, get_all) {
+				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
+				return get_all || this._get_settings().checkbox.two_state ? obj.find(".jstree-checked") : obj.find("> ul > .jstree-checked, .jstree-undetermined > ul > .jstree-checked");
+			},
+			get_unchecked : function (obj, get_all) { 
+				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
+				return get_all || this._get_settings().checkbox.two_state ? obj.find(".jstree-unchecked") : obj.find("> ul > .jstree-unchecked, .jstree-undetermined > ul > .jstree-unchecked");
+			},
+
+			show_checkboxes : function () { this.get_container().children("ul").removeClass("jstree-no-checkboxes"); },
+			hide_checkboxes : function () { this.get_container().children("ul").addClass("jstree-no-checkboxes"); },
+
+			_repair_state : function (obj) {
+				obj = this._get_node(obj);
+				if(!obj.length) { return; }
+				if(this._get_settings().checkbox.two_state) {
+					obj.find('li').addBack().not('.jstree-checked').removeClass('jstree-undetermined').addClass('jstree-unchecked').children(':checkbox').prop('checked', true);
+					return;
+				}
+				var rc = this._get_settings().checkbox.real_checkboxes,
+					a = obj.find("> ul > .jstree-checked").length,
+					b = obj.find("> ul > .jstree-undetermined").length,
+					c = obj.find("> ul > li").length;
+				if(c === 0) { if(obj.hasClass("jstree-undetermined")) { this.change_state(obj, false); } }
+				else if(a === 0 && b === 0) { this.change_state(obj, true); }
+				else if(a === c) { this.change_state(obj, false); }
+				else { 
+					obj.parentsUntil(".jstree","li").addBack().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
+					if(rc) { obj.parentsUntil(".jstree", "li").addBack().children(":checkbox").prop("checked", false); }
+				}
+			},
+			reselect : function () {
+				if(this.data.ui && this.data.checkbox.noui) { 
+					var _this = this,
+						s = this.data.ui.to_select;
+					s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
+					this.deselect_all();
+					$.each(s, function (i, val) { _this.check_node(val); });
+					this.__callback();
+				}
+				else { 
+					this.__call_old(); 
+				}
+			},
+			save_loaded : function () {
+				var _this = this;
+				this.data.core.to_load = [];
+				this.get_container_ul().find("li.jstree-closed.jstree-undetermined").each(function () {
+					if(this.id) { _this.data.core.to_load.push("#" + this.id); }
+				});
+			}
+		}
+	});
+	$(function() {
+		var css_string = '.jstree .jstree-real-checkbox { display:none; } ';
+		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree XML plugin
+ * The XML data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
+ */
+(function ($) {
+	$.vakata.xslt = function (xml, xsl, callback) {
+		var r = false, p, q, s;
+		// IE9
+		if(r === false && window.ActiveXObject) {
+			try {
+				r = new ActiveXObject("Msxml2.XSLTemplate");
+				q = new ActiveXObject("Msxml2.DOMDocument");
+				q.loadXML(xml);
+				s = new ActiveXObject("Msxml2.FreeThreadedDOMDocument");
+				s.loadXML(xsl);
+				r.stylesheet = s;
+				p = r.createProcessor();
+				p.input = q;
+				p.transform();
+				r = p.output;
+			}
+			catch (e) { }
+		}
+		xml = $.parseXML(xml);
+		xsl = $.parseXML(xsl);
+		// FF, Chrome
+		if(r === false && typeof (XSLTProcessor) !== "undefined") {
+			p = new XSLTProcessor();
+			p.importStylesheet(xsl);
+			r = p.transformToFragment(xml, document);
+			r = $('<div />').append(r).html();
+		}
+		// OLD IE
+		if(r === false && typeof (xml.transformNode) !== "undefined") {
+			r = xml.transformNode(xsl);
+		}
+		callback.call(null, r);
+	};
+	var xsl = {
+		'nest' : '<' + '?xml version="1.0" encoding="utf-8" ?>' + 
+			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
+			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/html" />' + 
+			'<xsl:template match="/">' + 
+			'	<xsl:call-template name="nodes">' + 
+			'		<xsl:with-param name="node" select="/root" />' + 
+			'	</xsl:call-template>' + 
+			'</xsl:template>' + 
+			'<xsl:template name="nodes">' + 
+			'	<xsl:param name="node" />' + 
+			'	<ul>' + 
+			'	<xsl:for-each select="$node/item">' + 
+			'		<xsl:variable name="children" select="count(./item) > 0" />' + 
+			'		<li>' + 
+			'			<xsl:attribute name="class">' + 
+			'				<xsl:if test="position() = last()">jstree-last </xsl:if>' + 
+			'				<xsl:choose>' + 
+			'					<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
+			'					<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
+			'					<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
+			'				</xsl:choose>' + 
+			'				<xsl:value-of select="@class" />' + 
+			'			</xsl:attribute>' + 
+			'			<xsl:for-each select="@*">' + 
+			'				<xsl:if test="name() != \'class\' and name() != \'state\' and name() != \'hasChildren\'">' + 
+			'					<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
+			'				</xsl:if>' + 
+			'			</xsl:for-each>' + 
+			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
+			'			<xsl:for-each select="content/name">' + 
+			'				<a>' + 
+			'				<xsl:attribute name="href">' + 
+			'					<xsl:choose>' + 
+			'					<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
+			'					<xsl:otherwise>#</xsl:otherwise>' + 
+			'					</xsl:choose>' + 
+			'				</xsl:attribute>' + 
+			'				<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
+			'				<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
+			'				<xsl:for-each select="@*">' + 
+			'					<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
+			'						<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
+			'					</xsl:if>' + 
+			'				</xsl:for-each>' + 
+			'					<ins>' + 
+			'						<xsl:attribute name="class">jstree-icon ' + 
+			'							<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
+			'						</xsl:attribute>' + 
+			'						<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
+			'						<xsl:text>&#xa0;</xsl:text>' + 
+			'					</ins>' + 
+			'					<xsl:copy-of select="./child::node()" />' + 
+			'				</a>' + 
+			'			</xsl:for-each>' + 
+			'			<xsl:if test="$children or @hasChildren"><xsl:call-template name="nodes"><xsl:with-param name="node" select="current()" /></xsl:call-template></xsl:if>' + 
+			'		</li>' + 
+			'	</xsl:for-each>' + 
+			'	</ul>' + 
+			'</xsl:template>' + 
+			'</xsl:stylesheet>',
+
+		'flat' : '<' + '?xml version="1.0" encoding="utf-8" ?>' + 
+			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
+			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/xml" />' + 
+			'<xsl:template match="/">' + 
+			'	<ul>' + 
+			'	<xsl:for-each select="//item[not(@parent_id) or @parent_id=0 or not(@parent_id = //item/@id)]">' + /* the last `or` may be removed */
+			'		<xsl:call-template name="nodes">' + 
+			'			<xsl:with-param name="node" select="." />' + 
+			'			<xsl:with-param name="is_last" select="number(position() = last())" />' + 
+			'		</xsl:call-template>' + 
+			'	</xsl:for-each>' + 
+			'	</ul>' + 
+			'</xsl:template>' + 
+			'<xsl:template name="nodes">' + 
+			'	<xsl:param name="node" />' + 
+			'	<xsl:param name="is_last" />' + 
+			'	<xsl:variable name="children" select="count(//item[@parent_id=$node/attribute::id]) > 0" />' + 
+			'	<li>' + 
+			'	<xsl:attribute name="class">' + 
+			'		<xsl:if test="$is_last = true()">jstree-last </xsl:if>' + 
+			'		<xsl:choose>' + 
+			'			<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
+			'			<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
+			'			<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
+			'		</xsl:choose>' + 
+			'		<xsl:value-of select="@class" />' + 
+			'	</xsl:attribute>' + 
+			'	<xsl:for-each select="@*">' + 
+			'		<xsl:if test="name() != \'parent_id\' and name() != \'hasChildren\' and name() != \'class\' and name() != \'state\'">' + 
+			'		<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
+			'		</xsl:if>' + 
+			'	</xsl:for-each>' + 
+			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
+			'	<xsl:for-each select="content/name">' + 
+			'		<a>' + 
+			'		<xsl:attribute name="href">' + 
+			'			<xsl:choose>' + 
+			'			<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
+			'			<xsl:otherwise>#</xsl:otherwise>' + 
+			'			</xsl:choose>' + 
+			'		</xsl:attribute>' + 
+			'		<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
+			'		<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
+			'		<xsl:for-each select="@*">' + 
+			'			<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
+			'				<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
+			'			</xsl:if>' + 
+			'		</xsl:for-each>' + 
+			'			<ins>' + 
+			'				<xsl:attribute name="class">jstree-icon ' + 
+			'					<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
+			'				</xsl:attribute>' + 
+			'				<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
+			'				<xsl:text>&#xa0;</xsl:text>' + 
+			'			</ins>' + 
+			'			<xsl:copy-of select="./child::node()" />' + 
+			'		</a>' + 
+			'	</xsl:for-each>' + 
+			'	<xsl:if test="$children">' + 
+			'		<ul>' + 
+			'		<xsl:for-each select="//item[@parent_id=$node/attribute::id]">' + 
+			'			<xsl:call-template name="nodes">' + 
+			'				<xsl:with-param name="node" select="." />' + 
+			'				<xsl:with-param name="is_last" select="number(position() = last())" />' + 
+			'			</xsl:call-template>' + 
+			'		</xsl:for-each>' + 
+			'		</ul>' + 
+			'	</xsl:if>' + 
+			'	</li>' + 
+			'</xsl:template>' + 
+			'</xsl:stylesheet>'
+	},
+	escape_xml = function(string) {
+		return string
+			.toString()
+			.replace(/&/g, '&')
+			.replace(/</g, '<')
+			.replace(/>/g, '>')
+			.replace(/"/g, '"')
+			.replace(/'/g, ''');
+	};
+	$.jstree.plugin("xml_data", {
+		defaults : { 
+			data : false,
+			ajax : false,
+			xsl : "flat",
+			clean_node : false,
+			correct_state : true,
+			get_skip_empty : false,
+			get_include_preamble : true
+		},
+		_fn : {
+			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_xml(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
+			_is_loaded : function (obj) { 
+				var s = this._get_settings().xml_data;
+				obj = this._get_node(obj);
+				return obj == -1 || !obj || (!s.ajax && !$.isFunction(s.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
+			},
+			load_node_xml : function (obj, s_call, e_call) {
+				var s = this.get_settings().xml_data,
+					error_func = function () {},
+					success_func = function () {};
+
+				obj = this._get_node(obj);
+				if(obj && obj !== -1) {
+					if(obj.data("jstree_is_loading")) { return; }
+					else { obj.data("jstree_is_loading",true); }
+				}
+				switch(!0) {
+					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
+					case ($.isFunction(s.data)):
+						s.data.call(this, obj, $.proxy(function (d) {
+							this.parse_xml(d, $.proxy(function (d) {
+								if(d) {
+									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
+									if(d.length > 10) {
+										d = $(d);
+										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
+										else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.removeData("jstree_is_loading"); }
+										if(s.clean_node) { this.clean_node(obj); }
+										if(s_call) { s_call.call(this); }
+									}
+									else {
+										if(obj && obj !== -1) { 
+											obj.children("a.jstree-loading").removeClass("jstree-loading");
+											obj.removeData("jstree_is_loading");
+											if(s.correct_state) { 
+												this.correct_state(obj);
+												if(s_call) { s_call.call(this); } 
+											}
+										}
+										else {
+											if(s.correct_state) { 
+												this.get_container().children("ul").empty();
+												if(s_call) { s_call.call(this); } 
+											}
+										}
+									}
+								}
+							}, this));
+						}, this));
+						break;
+					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
+						if(!obj || obj == -1) {
+							this.parse_xml(s.data, $.proxy(function (d) {
+								if(d) {
+									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
+									if(d.length > 10) {
+										d = $(d);
+										this.get_container().children("ul").empty().append(d.children());
+										if(s.clean_node) { this.clean_node(obj); }
+										if(s_call) { s_call.call(this); }
+									}
+								}
+								else { 
+									if(s.correct_state) { 
+										this.get_container().children("ul").empty(); 
+										if(s_call) { s_call.call(this); }
+									}
+								}
+							}, this));
+						}
+						break;
+					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
+						error_func = function (x, t, e) {
+							var ef = this.get_settings().xml_data.ajax.error; 
+							if(ef) { ef.call(this, x, t, e); }
+							if(obj !== -1 && obj.length) {
+								obj.children("a.jstree-loading").removeClass("jstree-loading");
+								obj.removeData("jstree_is_loading");
+								if(t === "success" && s.correct_state) { this.correct_state(obj); }
+							}
+							else {
+								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
+							}
+							if(e_call) { e_call.call(this); }
+						};
+						success_func = function (d, t, x) {
+							d = x.responseText;
+							var sf = this.get_settings().xml_data.ajax.success; 
+							if(sf) { d = sf.call(this,d,t,x) || d; }
+							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
+								return error_func.call(this, x, t, "");
+							}
+							this.parse_xml(d, $.proxy(function (d) {
+								if(d) {
+									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
+									if(d.length > 10) {
+										d = $(d);
+										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
+										else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.removeData("jstree_is_loading"); }
+										if(s.clean_node) { this.clean_node(obj); }
+										if(s_call) { s_call.call(this); }
+									}
+									else {
+										if(obj && obj !== -1) { 
+											obj.children("a.jstree-loading").removeClass("jstree-loading");
+											obj.removeData("jstree_is_loading");
+											if(s.correct_state) { 
+												this.correct_state(obj);
+												if(s_call) { s_call.call(this); } 
+											}
+										}
+										else {
+											if(s.correct_state) { 
+												this.get_container().children("ul").empty();
+												if(s_call) { s_call.call(this); } 
+											}
+										}
+									}
+								}
+							}, this));
+						};
+						s.ajax.context = this;
+						s.ajax.error = error_func;
+						s.ajax.success = success_func;
+						if(!s.ajax.dataType) { s.ajax.dataType = "xml"; }
+						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
+						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
+						$.ajax(s.ajax);
+						break;
+				}
+			},
+			parse_xml : function (xml, callback) {
+				var s = this._get_settings().xml_data;
+				$.vakata.xslt(xml, xsl[s.xsl], callback);
+			},
+			get_xml : function (tp, obj, li_attr, a_attr, is_callback) {
+				var result = "", 
+					s = this._get_settings(), 
+					_this = this,
+					tmp1, tmp2, li, a, lang;
+				if(!tp) { tp = "flat"; }
+				if(!is_callback) { is_callback = 0; }
+				obj = this._get_node(obj);
+				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
+				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
+				if(!is_callback && this.data.types && $.inArray(s.types.type_attr, li_attr) === -1) { li_attr.push(s.types.type_attr); }
+
+				a_attr = $.isArray(a_attr) ? a_attr : [ ];
+
+				if(!is_callback) { 
+					if(s.xml_data.get_include_preamble) { 
+						result += '<' + '?xml version="1.0" encoding="UTF-8"?' + '>'; 
+					}
+					result += "<root>"; 
+				}
+				obj.each(function () {
+					result += "<item";
+					li = $(this);
+					$.each(li_attr, function (i, v) { 
+						var t = li.attr(v);
+						if(!s.xml_data.get_skip_empty || typeof t !== "undefined") {
+							result += " " + v + "=\"" + escape_xml((" " + (t || "")).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + "\""; 
+						}
+					});
+					if(li.hasClass("jstree-open")) { result += " state=\"open\""; }
+					if(li.hasClass("jstree-closed")) { result += " state=\"closed\""; }
+					if(tp === "flat") { result += " parent_id=\"" + escape_xml(is_callback) + "\""; }
+					result += ">";
+					result += "<content>";
+					a = li.children("a");
+					a.each(function () {
+						tmp1 = $(this);
+						lang = false;
+						result += "<name";
+						if($.inArray("languages", s.plugins) !== -1) {
+							$.each(s.languages, function (k, z) {
+								if(tmp1.hasClass(z)) { result += " lang=\"" + escape_xml(z) + "\""; lang = z; return false; }
+							});
+						}
+						if(a_attr.length) { 
+							$.each(a_attr, function (k, z) {
+								var t = tmp1.attr(z);
+								if(!s.xml_data.get_skip_empty || typeof t !== "undefined") {
+									result += " " + z + "=\"" + escape_xml((" " + t || "").replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + "\"";
+								}
+							});
+						}
+						if(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
+							result += ' icon="' + escape_xml(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + '"';
+						}
+						if(tmp1.children("ins").get(0).style.backgroundImage.length) {
+							result += ' icon="' + escape_xml(tmp1.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","").replace(/'/ig,"").replace(/"/ig,"")) + '"';
+						}
+						result += ">";
+						result += "<![CDATA[" + _this.get_text(tmp1, lang) + "]]>";
+						result += "</name>";
+					});
+					result += "</content>";
+					tmp2 = li[0].id || true;
+					li = li.find("> ul > li");
+					if(li.length) { tmp2 = _this.get_xml(tp, li, li_attr, a_attr, tmp2); }
+					else { tmp2 = ""; }
+					if(tp == "nest") { result += tmp2; }
+					result += "</item>";
+					if(tp == "flat") { result += tmp2; }
+				});
+				if(!is_callback) { result += "</root>"; }
+				return result;
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/*
+ * jsTree search plugin
+ * Enables both sync and async search on the tree
+ * DOES NOT WORK WITH JSON PROGRESSIVE RENDER
+ */
+(function ($) {
+	if($().jquery.split('.')[1] >= 8) {
+		$.expr[':'].jstree_contains = $.expr.createPseudo(function(search) {
+			return function(a) {
+				return (a.textContent || a.innerText || "").toLowerCase().indexOf(search.toLowerCase())>=0;
+			};
+		});
+		$.expr[':'].jstree_title_contains = $.expr.createPseudo(function(search) {
+			return function(a) {
+				return (a.getAttribute("title") || "").toLowerCase().indexOf(search.toLowerCase())>=0;
+			};
+		});
+	}
+	else {
+		$.expr[':'].jstree_contains = function(a,i,m){
+			return (a.textContent || a.innerText || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
+		};
+		$.expr[':'].jstree_title_contains = function(a,i,m) {
+			return (a.getAttribute("title") || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
+		};
+	}
+	$.jstree.plugin("search", {
+		__init : function () {
+			this.data.search.str = "";
+			this.data.search.result = $();
+			if(this._get_settings().search.show_only_matches) {
+				this.get_container()
+					.bind("search.jstree", function (e, data) {
+						$(this).children("ul").find("li").hide().removeClass("jstree-last");
+						data.rslt.nodes.parentsUntil(".jstree").addBack().show()
+							.filter("ul").each(function () { $(this).children("li:visible").eq(-1).addClass("jstree-last"); });
+					})
+					.bind("clear_search.jstree", function () {
+						$(this).children("ul").find("li").css("display","").end().end().jstree("clean_node", -1);
+					});
+			}
+		},
+		defaults : {
+			ajax : false,
+			search_method : "jstree_contains", // for case insensitive - jstree_contains
+			show_only_matches : false
+		},
+		_fn : {
+			search : function (str, skip_async) {
+				if($.trim(str) === "") { this.clear_search(); return; }
+				var s = this.get_settings().search, 
+					t = this,
+					error_func = function () { },
+					success_func = function () { };
+				this.data.search.str = str;
+
+				if(!skip_async && s.ajax !== false && this.get_container_ul().find("li.jstree-closed:not(:has(ul)):eq(0)").length > 0) {
+					this.search.supress_callback = true;
+					error_func = function () { };
+					success_func = function (d, t, x) {
+						var sf = this.get_settings().search.ajax.success; 
+						if(sf) { d = sf.call(this,d,t,x) || d; }
+						this.data.search.to_open = d;
+						this._search_open();
+					};
+					s.ajax.context = this;
+					s.ajax.error = error_func;
+					s.ajax.success = success_func;
+					if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, str); }
+					if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, str); }
+					if(!s.ajax.data) { s.ajax.data = { "search_string" : str }; }
+					if(!s.ajax.dataType || /^json/.exec(s.ajax.dataType)) { s.ajax.dataType = "json"; }
+					$.ajax(s.ajax);
+					return;
+				}
+				if(this.data.search.result.length) { this.clear_search(); }
+				this.data.search.result = this.get_container().find("a" + (this.data.languages ? "." + this.get_lang() : "" ) + ":" + (s.search_method) + "(" + this.data.search.str + ")");
+				this.data.search.result.addClass("jstree-search").parent().parents(".jstree-closed").each(function () {
+					t.open_node(this, false, true);
+				});
+				this.__callback({ nodes : this.data.search.result, str : str });
+			},
+			clear_search : function (str) {
+				this.data.search.result.removeClass("jstree-search");
+				this.__callback(this.data.search.result);
+				this.data.search.result = $();
+			},
+			_search_open : function (is_callback) {
+				var _this = this,
+					done = true,
+					current = [],
+					remaining = [];
+				if(this.data.search.to_open.length) {
+					$.each(this.data.search.to_open, function (i, val) {
+						if(val == "#") { return true; }
+						if($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
+						else { remaining.push(val); }
+					});
+					if(current.length) {
+						this.data.search.to_open = remaining;
+						$.each(current, function (i, val) { 
+							_this.open_node(val, function () { _this._search_open(true); }); 
+						});
+						done = false;
+					}
+				}
+				if(done) { this.search(this.data.search.str, true); }
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree contextmenu plugin
+ */
+(function ($) {
+	$.vakata.context = {
+		hide_on_mouseleave : false,
+
+		cnt		: $("<div id='vakata-contextmenu' />"),
+		vis		: false,
+		tgt		: false,
+		par		: false,
+		func	: false,
+		data	: false,
+		rtl		: false,
+		show	: function (s, t, x, y, d, p, rtl) {
+			$.vakata.context.rtl = !!rtl;
+			var html = $.vakata.context.parse(s), h, w;
+			if(!html) { return; }
+			$.vakata.context.vis = true;
+			$.vakata.context.tgt = t;
+			$.vakata.context.par = p || t || null;
+			$.vakata.context.data = d || null;
+			$.vakata.context.cnt
+				.html(html)
+				.css({ "visibility" : "hidden", "display" : "block", "left" : 0, "top" : 0 });
+
+			if($.vakata.context.hide_on_mouseleave) {
+				$.vakata.context.cnt
+					.one("mouseleave", function(e) { $.vakata.context.hide(); });
+			}
+
+			h = $.vakata.context.cnt.height();
+			w = $.vakata.context.cnt.width();
+			if(x + w > $(document).width()) { 
+				x = $(document).width() - (w + 5); 
+				$.vakata.context.cnt.find("li > ul").addClass("right"); 
+			}
+			if(y + h > $(document).height()) { 
+				y = y - (h + t[0].offsetHeight); 
+				$.vakata.context.cnt.find("li > ul").addClass("bottom"); 
+			}
+
+			$.vakata.context.cnt
+				.css({ "left" : x, "top" : y })
+				.find("li:has(ul)")
+					.bind("mouseenter", function (e) { 
+						var w = $(document).width(),
+							h = $(document).height(),
+							ul = $(this).children("ul").show(); 
+						if(w !== $(document).width()) { ul.toggleClass("right"); }
+						if(h !== $(document).height()) { ul.toggleClass("bottom"); }
+					})
+					.bind("mouseleave", function (e) { 
+						$(this).children("ul").hide(); 
+					})
+					.end()
+				.css({ "visibility" : "visible" })
+				.show();
+			$(document).triggerHandler("context_show.vakata");
+		},
+		hide	: function () {
+			$.vakata.context.vis = false;
+			$.vakata.context.cnt.attr("class","").css({ "visibility" : "hidden" });
+			$(document).triggerHandler("context_hide.vakata");
+		},
+		parse	: function (s, is_callback) {
+			if(!s) { return false; }
+			var str = "",
+				tmp = false,
+				was_sep = true;
+			if(!is_callback) { $.vakata.context.func = {}; }
+			str += "<ul>";
+			$.each(s, function (i, val) {
+				if(!val) { return true; }
+				$.vakata.context.func[i] = val.action;
+				if(!was_sep && val.separator_before) {
+					str += "<li class='vakata-separator vakata-separator-before'></li>";
+				}
+				was_sep = false;
+				str += "<li class='" + (val._class || "") + (val._disabled ? " jstree-contextmenu-disabled " : "") + "'><ins ";
+				if(val.icon && val.icon.indexOf("/") === -1) { str += " class='" + val.icon + "' "; }
+				if(val.icon && val.icon.indexOf("/") !== -1) { str += " style='background:url(" + val.icon + ") center center no-repeat;' "; }
+				str += "> </ins><a href='#' rel='" + i + "'>";
+				if(val.submenu) {
+					str += "<span style='float:" + ($.vakata.context.rtl ? "left" : "right") + ";'>»</span>";
+				}
+				str += val.label + "</a>";
+				if(val.submenu) {
+					tmp = $.vakata.context.parse(val.submenu, true);
+					if(tmp) { str += tmp; }
+				}
+				str += "</li>";
+				if(val.separator_after) {
+					str += "<li class='vakata-separator vakata-separator-after'></li>";
+					was_sep = true;
+				}
+			});
+			str = str.replace(/<li class\='vakata-separator vakata-separator-after'\><\/li\>$/,"");
+			str += "</ul>";
+			$(document).triggerHandler("context_parse.vakata");
+			return str.length > 10 ? str : false;
+		},
+		exec	: function (i) {
+			if($.isFunction($.vakata.context.func[i])) {
+				// if is string - eval and call it!
+				$.vakata.context.func[i].call($.vakata.context.data, $.vakata.context.par);
+				return true;
+			}
+			else { return false; }
+		}
+	};
+	$(function () {
+		var css_string = '' + 
+			'#vakata-contextmenu { display:block; visibility:hidden; left:0; top:-200px; position:absolute; margin:0; padding:0; min-width:180px; background:#ebebeb; border:1px solid silver; z-index:10000; *width:180px; } ' + 
+			'#vakata-contextmenu ul { min-width:180px; *width:180px; } ' + 
+			'#vakata-contextmenu ul, #vakata-contextmenu li { margin:0; padding:0; list-style-type:none; display:block; } ' + 
+			'#vakata-contextmenu li { line-height:20px; min-height:20px; position:relative; padding:0px; } ' + 
+			'#vakata-contextmenu li a { padding:1px 6px; line-height:17px; display:block; text-decoration:none; margin:1px 1px 0 1px; } ' + 
+			'#vakata-contextmenu li ins { float:left; width:16px; height:16px; text-decoration:none; margin-right:2px; } ' + 
+			'#vakata-contextmenu li a:hover, #vakata-contextmenu li.vakata-hover > a { background:gray; color:white; } ' + 
+			'#vakata-contextmenu li ul { display:none; position:absolute; top:-2px; left:100%; background:#ebebeb; border:1px solid gray; } ' + 
+			'#vakata-contextmenu .right { right:100%; left:auto; } ' + 
+			'#vakata-contextmenu .bottom { bottom:-1px; top:auto; } ' + 
+			'#vakata-contextmenu li.vakata-separator { min-height:0; height:1px; line-height:1px; font-size:1px; overflow:hidden; margin:0 2px; background:silver; /* border-top:1px solid #fefefe; */ padding:0; } ';
+		$.vakata.css.add_sheet({ str : css_string, title : "vakata" });
+		$.vakata.context.cnt
+			.delegate("a","click", function (e) { e.preventDefault(); })
+			.delegate("a","mouseup", function (e) {
+				if(!$(this).parent().hasClass("jstree-contextmenu-disabled") && $.vakata.context.exec($(this).attr("rel"))) {
+					$.vakata.context.hide();
+				}
+				else { $(this).blur(); }
+			})
+			.delegate("a","mouseover", function () {
+				$.vakata.context.cnt.find(".vakata-hover").removeClass("vakata-hover");
+			})
+			.appendTo("body");
+		$(document).bind("mousedown", function (e) { if($.vakata.context.vis && !$.contains($.vakata.context.cnt[0], e.target)) { $.vakata.context.hide(); } });
+		if(typeof $.hotkeys !== "undefined") {
+			$(document)
+				.bind("keydown", "up", function (e) { 
+					if($.vakata.context.vis) { 
+						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").prevAll("li:not(.vakata-separator)").first();
+						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").last(); }
+						o.addClass("vakata-hover");
+						e.stopImmediatePropagation(); 
+						e.preventDefault();
+					} 
+				})
+				.bind("keydown", "down", function (e) { 
+					if($.vakata.context.vis) { 
+						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").nextAll("li:not(.vakata-separator)").first();
+						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").first(); }
+						o.addClass("vakata-hover");
+						e.stopImmediatePropagation(); 
+						e.preventDefault();
+					} 
+				})
+				.bind("keydown", "right", function (e) { 
+					if($.vakata.context.vis) { 
+						$.vakata.context.cnt.find(".vakata-hover").children("ul").show().children("li:not(.vakata-separator)").removeClass("vakata-hover").first().addClass("vakata-hover");
+						e.stopImmediatePropagation(); 
+						e.preventDefault();
+					} 
+				})
+				.bind("keydown", "left", function (e) { 
+					if($.vakata.context.vis) { 
+						$.vakata.context.cnt.find(".vakata-hover").children("ul").hide().children(".vakata-separator").removeClass("vakata-hover");
+						e.stopImmediatePropagation(); 
+						e.preventDefault();
+					} 
+				})
+				.bind("keydown", "esc", function (e) { 
+					$.vakata.context.hide(); 
+					e.preventDefault();
+				})
+				.bind("keydown", "space", function (e) { 
+					$.vakata.context.cnt.find(".vakata-hover").last().children("a").click();
+					e.preventDefault();
+				});
+		}
+	});
+
+	$.jstree.plugin("contextmenu", {
+		__init : function () {
+			this.get_container()
+				.delegate("a", "contextmenu.jstree", $.proxy(function (e) {
+						e.preventDefault();
+						if(!$(e.currentTarget).hasClass("jstree-loading")) {
+							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY);
+						}
+					}, this))
+				.delegate("a", "click.jstree", $.proxy(function (e) {
+						if(this.data.contextmenu) {
+							$.vakata.context.hide();
+						}
+					}, this))
+				.bind("destroy.jstree", $.proxy(function () {
+						// TODO: move this to descruct method
+						if(this.data.contextmenu) {
+							$.vakata.context.hide();
+						}
+					}, this));
+			$(document).bind("context_hide.vakata", $.proxy(function () { this.data.contextmenu = false; }, this));
+		},
+		defaults : { 
+			select_node : false, // requires UI plugin
+			show_at_node : true,
+			items : { // Could be a function that should return an object like this one
+				"create" : {
+					"separator_before"	: false,
+					"separator_after"	: true,
+					"label"				: "Create",
+					"action"			: function (obj) { this.create(obj); }
+				},
+				"rename" : {
+					"separator_before"	: false,
+					"separator_after"	: false,
+					"label"				: "Rename",
+					"action"			: function (obj) { this.rename(obj); }
+				},
+				"remove" : {
+					"separator_before"	: false,
+					"icon"				: false,
+					"separator_after"	: false,
+					"label"				: "Delete",
+					"action"			: function (obj) { if(this.is_selected(obj)) { this.remove(); } else { this.remove(obj); } }
+				},
+				"ccp" : {
+					"separator_before"	: true,
+					"icon"				: false,
+					"separator_after"	: false,
+					"label"				: "Edit",
+					"action"			: false,
+					"submenu" : { 
+						"cut" : {
+							"separator_before"	: false,
+							"separator_after"	: false,
+							"label"				: "Cut",
+							"action"			: function (obj) { this.cut(obj); }
+						},
+						"copy" : {
+							"separator_before"	: false,
+							"icon"				: false,
+							"separator_after"	: false,
+							"label"				: "Copy",
+							"action"			: function (obj) { this.copy(obj); }
+						},
+						"paste" : {
+							"separator_before"	: false,
+							"icon"				: false,
+							"separator_after"	: false,
+							"label"				: "Paste",
+							"action"			: function (obj) { this.paste(obj); }
+						}
+					}
+				}
+			}
+		},
+		_fn : {
+			show_contextmenu : function (obj, x, y) {
+				obj = this._get_node(obj);
+				var s = this.get_settings().contextmenu,
+					a = obj.children("a:visible:eq(0)"),
+					o = false,
+					i = false;
+				if(s.select_node && this.data.ui && !this.is_selected(obj)) {
+					this.deselect_all();
+					this.select_node(obj, true);
+				}
+				if(s.show_at_node || typeof x === "undefined" || typeof y === "undefined") {
+					o = a.offset();
+					x = o.left;
+					y = o.top + this.data.core.li_height;
+				}
+				i = obj.data("jstree") && obj.data("jstree").contextmenu ? obj.data("jstree").contextmenu : s.items;
+				if($.isFunction(i)) { i = i.call(this, obj); }
+				this.data.contextmenu = true;
+				$.vakata.context.show(i, a, x, y, this, obj, this._get_settings().core.rtl);
+				if(this.data.themes) { $.vakata.context.cnt.attr("class", "jstree-" + this.data.themes.theme + "-context"); }
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree types plugin
+ * Adds support types of nodes
+ * You can set an attribute on each li node, that represents its type.
+ * According to the type setting the node may get custom icon/validation rules
+ */
+(function ($) {
+	$.jstree.plugin("types", {
+		__init : function () {
+			var s = this._get_settings().types;
+			this.data.types.attach_to = [];
+			this.get_container()
+				.bind("init.jstree", $.proxy(function () { 
+						var types = s.types, 
+							attr  = s.type_attr, 
+							icons_css = "", 
+							_this = this;
+
+						$.each(types, function (i, tp) {
+							$.each(tp, function (k, v) { 
+								if(!/^(max_depth|max_children|icon|valid_children)$/.test(k)) { _this.data.types.attach_to.push(k); }
+							});
+							if(!tp.icon) { return true; }
+							if( tp.icon.image || tp.icon.position) {
+								if(i == "default")	{ icons_css += '.jstree-' + _this.get_index() + ' a > .jstree-icon { '; }
+								else				{ icons_css += '.jstree-' + _this.get_index() + ' li[' + attr + '="' + i + '"] > a > .jstree-icon { '; }
+								if(tp.icon.image)	{ icons_css += ' background-image:url(' + tp.icon.image + '); '; }
+								if(tp.icon.position){ icons_css += ' background-position:' + tp.icon.position + '; '; }
+								else				{ icons_css += ' background-position:0 0; '; }
+								icons_css += '} ';
+							}
+						});
+						if(icons_css !== "") { $.vakata.css.add_sheet({ 'str' : icons_css, title : "jstree-types" }); }
+					}, this))
+				.bind("before.jstree", $.proxy(function (e, data) { 
+						var s, t, 
+							o = this._get_settings().types.use_data ? this._get_node(data.args[0]) : false, 
+							d = o && o !== -1 && o.length ? o.data("jstree") : false;
+						if(d && d.types && d.types[data.func] === false) { e.stopImmediatePropagation(); return false; }
+						if($.inArray(data.func, this.data.types.attach_to) !== -1) {
+							if(!data.args[0] || (!data.args[0].tagName && !data.args[0].jquery)) { return; }
+							s = this._get_settings().types.types;
+							t = this._get_type(data.args[0]);
+							if(
+								( 
+									(s[t] && typeof s[t][data.func] !== "undefined") || 
+									(s["default"] && typeof s["default"][data.func] !== "undefined") 
+								) && this._check(data.func, data.args[0]) === false
+							) {
+								e.stopImmediatePropagation();
+								return false;
+							}
+						}
+					}, this));
+			if(is_ie6) {
+				this.get_container()
+					.bind("load_node.jstree set_type.jstree", $.proxy(function (e, data) {
+							var r = data && data.rslt && data.rslt.obj && data.rslt.obj !== -1 ? this._get_node(data.rslt.obj).parent() : this.get_container_ul(),
+								c = false,
+								s = this._get_settings().types;
+							$.each(s.types, function (i, tp) {
+								if(tp.icon && (tp.icon.image || tp.icon.position)) {
+									c = i === "default" ? r.find("li > a > .jstree-icon") : r.find("li[" + s.type_attr + "='" + i + "'] > a > .jstree-icon");
+									if(tp.icon.image) { c.css("backgroundImage","url(" + tp.icon.image + ")"); }
+									c.css("backgroundPosition", tp.icon.position || "0 0");
+								}
+							});
+						}, this));
+			}
+		},
+		defaults : {
+			// defines maximum number of root nodes (-1 means unlimited, -2 means disable max_children checking)
+			max_children		: -1,
+			// defines the maximum depth of the tree (-1 means unlimited, -2 means disable max_depth checking)
+			max_depth			: -1,
+			// defines valid node types for the root nodes
+			valid_children		: "all",
+
+			// whether to use $.data
+			use_data : false, 
+			// where is the type stores (the rel attribute of the LI element)
+			type_attr : "rel",
+			// a list of types
+			types : {
+				// the default type
+				"default" : {
+					"max_children"	: -1,
+					"max_depth"		: -1,
+					"valid_children": "all"
+
+					// Bound functions - you can bind any other function here (using boolean or function)
+					//"select_node"	: true
+				}
+			}
+		},
+		_fn : {
+			_types_notify : function (n, data) {
+				if(data.type && this._get_settings().types.use_data) {
+					this.set_type(data.type, n);
+				}
+			},
+			_get_type : function (obj) {
+				obj = this._get_node(obj);
+				return (!obj || !obj.length) ? false : obj.attr(this._get_settings().types.type_attr) || "default";
+			},
+			set_type : function (str, obj) {
+				obj = this._get_node(obj);
+				var ret = (!obj.length || !str) ? false : obj.attr(this._get_settings().types.type_attr, str);
+				if(ret) { this.__callback({ obj : obj, type : str}); }
+				return ret;
+			},
+			_check : function (rule, obj, opts) {
+				obj = this._get_node(obj);
+				var v = false, t = this._get_type(obj), d = 0, _this = this, s = this._get_settings().types, data = false;
+				if(obj === -1) { 
+					if(!!s[rule]) { v = s[rule]; }
+					else { return; }
+				}
+				else {
+					if(t === false) { return; }
+					data = s.use_data ? obj.data("jstree") : false;
+					if(data && data.types && typeof data.types[rule] !== "undefined") { v = data.types[rule]; }
+					else if(!!s.types[t] && typeof s.types[t][rule] !== "undefined") { v = s.types[t][rule]; }
+					else if(!!s.types["default"] && typeof s.types["default"][rule] !== "undefined") { v = s.types["default"][rule]; }
+				}
+				if($.isFunction(v)) { v = v.call(this, obj); }
+				if(rule === "max_depth" && obj !== -1 && opts !== false && s.max_depth !== -2 && v !== 0) {
+					// also include the node itself - otherwise if root node it is not checked
+					obj.children("a:eq(0)").parentsUntil(".jstree","li").each(function (i) {
+						// check if current depth already exceeds global tree depth
+						if(s.max_depth !== -1 && s.max_depth - (i + 1) <= 0) { v = 0; return false; }
+						d = (i === 0) ? v : _this._check(rule, this, false);
+						// check if current node max depth is already matched or exceeded
+						if(d !== -1 && d - (i + 1) <= 0) { v = 0; return false; }
+						// otherwise - set the max depth to the current value minus current depth
+						if(d >= 0 && (d - (i + 1) < v || v < 0) ) { v = d - (i + 1); }
+						// if the global tree depth exists and it minus the nodes calculated so far is less than `v` or `v` is unlimited
+						if(s.max_depth >= 0 && (s.max_depth - (i + 1) < v || v < 0) ) { v = s.max_depth - (i + 1); }
+					});
+				}
+				return v;
+			},
+			check_move : function () {
+				if(!this.__call_old()) { return false; }
+				var m  = this._get_move(),
+					s  = m.rt._get_settings().types,
+					mc = m.rt._check("max_children", m.cr),
+					md = m.rt._check("max_depth", m.cr),
+					vc = m.rt._check("valid_children", m.cr),
+					ch = 0, d = 1, t;
+
+				if(vc === "none") { return false; } 
+				if($.isArray(vc) && m.ot && m.ot._get_type) {
+					m.o.each(function () {
+						if($.inArray(m.ot._get_type(this), vc) === -1) { d = false; return false; }
+					});
+					if(d === false) { return false; }
+				}
+				if(s.max_children !== -2 && mc !== -1) {
+					ch = m.cr === -1 ? this.get_container().find("> ul > li").not(m.o).length : m.cr.find("> ul > li").not(m.o).length;
+					if(ch + m.o.length > mc) { return false; }
+				}
+				if(s.max_depth !== -2 && md !== -1) {
+					d = 0;
+					if(md === 0) { return false; }
+					if(typeof m.o.d === "undefined") {
+						// TODO: deal with progressive rendering and async when checking max_depth (how to know the depth of the moved node)
+						t = m.o;
+						while(t.length > 0) {
+							t = t.find("> ul > li");
+							d ++;
+						}
+						m.o.d = d;
+					}
+					if(md - m.o.d < 0) { return false; }
+				}
+				return true;
+			},
+			create_node : function (obj, position, js, callback, is_loaded, skip_check) {
+				if(!skip_check && (is_loaded || this._is_loaded(obj))) {
+					var p  = (typeof position == "string" && position.match(/^before|after$/i) && obj !== -1) ? this._get_parent(obj) : this._get_node(obj),
+						s  = this._get_settings().types,
+						mc = this._check("max_children", p),
+						md = this._check("max_depth", p),
+						vc = this._check("valid_children", p),
+						ch;
+					if(typeof js === "string") { js = { data : js }; }
+					if(!js) { js = {}; }
+					if(vc === "none") { return false; } 
+					if($.isArray(vc)) {
+						if(!js.attr || !js.attr[s.type_attr]) { 
+							if(!js.attr) { js.attr = {}; }
+							js.attr[s.type_attr] = vc[0]; 
+						}
+						else {
+							if($.inArray(js.attr[s.type_attr], vc) === -1) { return false; }
+						}
+					}
+					if(s.max_children !== -2 && mc !== -1) {
+						ch = p === -1 ? this.get_container().find("> ul > li").length : p.find("> ul > li").length;
+						if(ch + 1 > mc) { return false; }
+					}
+					if(s.max_depth !== -2 && md !== -1 && (md - 1) < 0) { return false; }
+				}
+				return this.__call_old(true, obj, position, js, callback, is_loaded, skip_check);
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree HTML plugin
+ * The HTML data store. Datastores are build by replacing the `load_node` and `_is_loaded` functions.
+ */
+(function ($) {
+	$.jstree.plugin("html_data", {
+		__init : function () { 
+			// this used to use html() and clean the whitespace, but this way any attached data was lost
+			this.data.html_data.original_container_html = this.get_container().find(" > ul > li").clone(true);
+			// remove white space from LI node - otherwise nodes appear a bit to the right
+			this.data.html_data.original_container_html.find("li").addBack().contents().filter(function() { return this.nodeType == 3; }).remove();
+		},
+		defaults : { 
+			data : false,
+			ajax : false,
+			correct_state : true
+		},
+		_fn : {
+			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_html(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
+			_is_loaded : function (obj) { 
+				obj = this._get_node(obj); 
+				return obj == -1 || !obj || (!this._get_settings().html_data.ajax && !$.isFunction(this._get_settings().html_data.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
+			},
+			load_node_html : function (obj, s_call, e_call) {
+				var d,
+					s = this.get_settings().html_data,
+					error_func = function () {},
+					success_func = function () {};
+				obj = this._get_node(obj);
+				if(obj && obj !== -1) {
+					if(obj.data("jstree_is_loading")) { return; }
+					else { obj.data("jstree_is_loading",true); }
+				}
+				switch(!0) {
+					case ($.isFunction(s.data)):
+						s.data.call(this, obj, $.proxy(function (d) {
+							if(d && d !== "" && d.toString && d.toString().replace(/^[\s\n]+$/,"") !== "") {
+								d = $(d);
+								if(!d.is("ul")) { d = $("<ul />").append(d); }
+								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'> </ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
+								else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d).children("ul").find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'> </ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.removeData("jstree_is_loading"); }
+								this.clean_node(obj);
+								if(s_call) { s_call.call(this); }
+							}
+							else {
+								if(obj && obj !== -1) {
+									obj.children("a.jstree-loading").removeClass("jstree-loading");
+									obj.removeData("jstree_is_loading");
+									if(s.correct_state) { 
+										this.correct_state(obj);
+										if(s_call) { s_call.call(this); } 
+									}
+								}
+								else {
+									if(s.correct_state) { 
+										this.get_container().children("ul").empty();
+										if(s_call) { s_call.call(this); } 
+									}
+								}
+							}
+						}, this));
+						break;
+					case (!s.data && !s.ajax):
+						if(!obj || obj == -1) {
+							this.get_container()
+								.children("ul").empty()
+								.append(this.data.html_data.original_container_html)
+								.find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'> </ins>").end()
+								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
+							this.clean_node();
+						}
+						if(s_call) { s_call.call(this); }
+						break;
+					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
+						if(!obj || obj == -1) {
+							d = $(s.data);
+							if(!d.is("ul")) { d = $("<ul />").append(d); }
+							this.get_container()
+								.children("ul").empty().append(d.children())
+								.find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'> </ins>").end()
+								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
+							this.clean_node();
+						}
+						if(s_call) { s_call.call(this); }
+						break;
+					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
+						obj = this._get_node(obj);
+						error_func = function (x, t, e) {
+							var ef = this.get_settings().html_data.ajax.error; 
+							if(ef) { ef.call(this, x, t, e); }
+							if(obj != -1 && obj.length) {
+								obj.children("a.jstree-loading").removeClass("jstree-loading");
+								obj.removeData("jstree_is_loading");
+								if(t === "success" && s.correct_state) { this.correct_state(obj); }
+							}
+							else {
+								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
+							}
+							if(e_call) { e_call.call(this); }
+						};
+						success_func = function (d, t, x) {
+							var sf = this.get_settings().html_data.ajax.success; 
+							if(sf) { d = sf.call(this,d,t,x) || d; }
+							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
+								return error_func.call(this, x, t, "");
+							}
+							if(d) {
+								d = $(d);
+								if(!d.is("ul")) { d = $("<ul />").append(d); }
+								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'> </ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
+								else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d).children("ul").find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'> </ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.removeData("jstree_is_loading"); }
+								this.clean_node(obj);
+								if(s_call) { s_call.call(this); }
+							}
+							else {
+								if(obj && obj !== -1) {
+									obj.children("a.jstree-loading").removeClass("jstree-loading");
+									obj.removeData("jstree_is_loading");
+									if(s.correct_state) { 
+										this.correct_state(obj);
+										if(s_call) { s_call.call(this); } 
+									}
+								}
+								else {
+									if(s.correct_state) { 
+										this.get_container().children("ul").empty();
+										if(s_call) { s_call.call(this); } 
+									}
+								}
+							}
+						};
+						s.ajax.context = this;
+						s.ajax.error = error_func;
+						s.ajax.success = success_func;
+						if(!s.ajax.dataType) { s.ajax.dataType = "html"; }
+						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
+						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
+						$.ajax(s.ajax);
+						break;
+				}
+			}
+		}
+	});
+	// include the HTML data plugin by default
+	$.jstree.defaults.plugins.push("html_data");
+})(jQuery);
+//*/
+
+/* 
+ * jsTree themeroller plugin
+ * Adds support for jQuery UI themes. Include this at the end of your plugins list, also make sure "themes" is not included.
+ */
+(function ($) {
+	$.jstree.plugin("themeroller", {
+		__init : function () {
+			var s = this._get_settings().themeroller;
+			this.get_container()
+				.addClass("ui-widget-content")
+				.addClass("jstree-themeroller")
+				.delegate("a","mouseenter.jstree", function (e) {
+					if(!$(e.currentTarget).hasClass("jstree-loading")) {
+						$(this).addClass(s.item_h);
+					}
+				})
+				.delegate("a","mouseleave.jstree", function () {
+					$(this).removeClass(s.item_h);
+				})
+				.bind("init.jstree", $.proxy(function (e, data) { 
+						data.inst.get_container().find("> ul > li > .jstree-loading > ins").addClass("ui-icon-refresh");
+						this._themeroller(data.inst.get_container().find("> ul > li"));
+					}, this))
+				.bind("open_node.jstree create_node.jstree", $.proxy(function (e, data) { 
+						this._themeroller(data.rslt.obj);
+					}, this))
+				.bind("loaded.jstree refresh.jstree", $.proxy(function (e) {
+						this._themeroller();
+					}, this))
+				.bind("close_node.jstree", $.proxy(function (e, data) {
+						this._themeroller(data.rslt.obj);
+					}, this))
+				.bind("delete_node.jstree", $.proxy(function (e, data) {
+						this._themeroller(data.rslt.parent);
+					}, this))
+				.bind("correct_state.jstree", $.proxy(function (e, data) {
+						data.rslt.obj
+							.children("ins.jstree-icon").removeClass(s.opened + " " + s.closed + " ui-icon").end()
+							.find("> a > ins.ui-icon")
+								.filter(function() { 
+									return this.className.toString()
+										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
+										.indexOf("ui-icon-") === -1; 
+								}).removeClass(s.item_open + " " + s.item_clsd).addClass(s.item_leaf || "jstree-no-icon");
+					}, this))
+				.bind("select_node.jstree", $.proxy(function (e, data) {
+						data.rslt.obj.children("a").addClass(s.item_a);
+					}, this))
+				.bind("deselect_node.jstree deselect_all.jstree", $.proxy(function (e, data) {
+						this.get_container()
+							.find("a." + s.item_a).removeClass(s.item_a).end()
+							.find("a.jstree-clicked").addClass(s.item_a);
+					}, this))
+				.bind("dehover_node.jstree", $.proxy(function (e, data) {
+						data.rslt.obj.children("a").removeClass(s.item_h);
+					}, this))
+				.bind("hover_node.jstree", $.proxy(function (e, data) {
+						this.get_container()
+							.find("a." + s.item_h).not(data.rslt.obj).removeClass(s.item_h);
+						data.rslt.obj.children("a").addClass(s.item_h);
+					}, this))
+				.bind("move_node.jstree", $.proxy(function (e, data) {
+						this._themeroller(data.rslt.o);
+						this._themeroller(data.rslt.op);
+					}, this));
+		},
+		__destroy : function () {
+			var s = this._get_settings().themeroller,
+				c = [ "ui-icon" ];
+			$.each(s, function (i, v) {
+				v = v.split(" ");
+				if(v.length) { c = c.concat(v); }
+			});
+			this.get_container()
+				.removeClass("ui-widget-content")
+				.find("." + c.join(", .")).removeClass(c.join(" "));
+		},
+		_fn : {
+			_themeroller : function (obj) {
+				var s = this._get_settings().themeroller;
+				obj = (!obj || obj == -1) ? this.get_container_ul() : this._get_node(obj);
+				obj = (!obj || obj == -1) ? this.get_container_ul() : obj.parent();
+				obj
+					.find("li.jstree-closed")
+						.children("ins.jstree-icon").removeClass(s.opened).addClass("ui-icon " + s.closed).end()
+						.children("a").addClass(s.item)
+							.children("ins.jstree-icon").addClass("ui-icon")
+								.filter(function() { 
+									return this.className.toString()
+										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
+										.indexOf("ui-icon-") === -1; 
+								}).removeClass(s.item_leaf + " " + s.item_open).addClass(s.item_clsd || "jstree-no-icon")
+								.end()
+							.end()
+						.end()
+					.end()
+					.find("li.jstree-open")
+						.children("ins.jstree-icon").removeClass(s.closed).addClass("ui-icon " + s.opened).end()
+						.children("a").addClass(s.item)
+							.children("ins.jstree-icon").addClass("ui-icon")
+								.filter(function() { 
+									return this.className.toString()
+										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
+										.indexOf("ui-icon-") === -1; 
+								}).removeClass(s.item_leaf + " " + s.item_clsd).addClass(s.item_open || "jstree-no-icon")
+								.end()
+							.end()
+						.end()
+					.end()
+					.find("li.jstree-leaf")
+						.children("ins.jstree-icon").removeClass(s.closed + " ui-icon " + s.opened).end()
+						.children("a").addClass(s.item)
+							.children("ins.jstree-icon").addClass("ui-icon")
+								.filter(function() { 
+									return this.className.toString()
+										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
+										.indexOf("ui-icon-") === -1; 
+								}).removeClass(s.item_clsd + " " + s.item_open).addClass(s.item_leaf || "jstree-no-icon");
+			}
+		},
+		defaults : {
+			"opened"	: "ui-icon-triangle-1-se",
+			"closed"	: "ui-icon-triangle-1-e",
+			"item"		: "ui-state-default",
+			"item_h"	: "ui-state-hover",
+			"item_a"	: "ui-state-active",
+			"item_open"	: "ui-icon-folder-open",
+			"item_clsd"	: "ui-icon-folder-collapsed",
+			"item_leaf"	: "ui-icon-document"
+		}
+	});
+	$(function() {
+		var css_string = '' + 
+			'.jstree-themeroller .ui-icon { overflow:visible; } ' + 
+			'.jstree-themeroller a { padding:0 2px; } ' + 
+			'.jstree-themeroller .jstree-no-icon { display:none; }';
+		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
+	});
+})(jQuery);
+//*/
+
+/* 
+ * jsTree unique plugin
+ * Forces different names amongst siblings (still a bit experimental)
+ * NOTE: does not check language versions (it will not be possible to have nodes with the same title, even in different languages)
+ */
+(function ($) {
+	$.jstree.plugin("unique", {
+		__init : function () {
+			this.get_container()
+				.bind("before.jstree", $.proxy(function (e, data) { 
+						var nms = [], res = true, p, t;
+						if(data.func == "move_node") {
+							// obj, ref, position, is_copy, is_prepared, skip_check
+							if(data.args[4] === true) {
+								if(data.args[0].o && data.args[0].o.length) {
+									data.args[0].o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
+									res = this._check_unique(nms, data.args[0].np.find("> ul > li").not(data.args[0].o), "move_node");
+								}
+							}
+						}
+						if(data.func == "create_node") {
+							// obj, position, js, callback, is_loaded
+							if(data.args[4] || this._is_loaded(data.args[0])) {
+								p = this._get_node(data.args[0]);
+								if(data.args[1] && (data.args[1] === "before" || data.args[1] === "after")) {
+									p = this._get_parent(data.args[0]);
+									if(!p || p === -1) { p = this.get_container(); }
+								}
+								if(typeof data.args[2] === "string") { nms.push(data.args[2]); }
+								else if(!data.args[2] || !data.args[2].data) { nms.push(this._get_string("new_node")); }
+								else { nms.push(data.args[2].data); }
+								res = this._check_unique(nms, p.find("> ul > li"), "create_node");
+							}
+						}
+						if(data.func == "rename_node") {
+							// obj, val
+							nms.push(data.args[1]);
+							t = this._get_node(data.args[0]);
+							p = this._get_parent(t);
+							if(!p || p === -1) { p = this.get_container(); }
+							res = this._check_unique(nms, p.find("> ul > li").not(t), "rename_node");
+						}
+						if(!res) {
+							e.stopPropagation();
+							return false;
+						}
+					}, this));
+		},
+		defaults : { 
+			error_callback : $.noop
+		},
+		_fn : { 
+			_check_unique : function (nms, p, func) {
+				var cnms = [], ok = true;
+				p.children("a").each(function () { cnms.push($(this).text().replace(/^\s+/g,"")); });
+				if(!cnms.length || !nms.length) { return true; }
+				$.each(nms, function (i, v) {
+					if($.inArray(v, cnms) !== -1) {
+						ok = false;
+						return false;
+					}
+				});
+				if(!ok) {
+					this._get_settings().unique.error_callback.call(null, nms, p, func);
+				}
+				return ok;
+			},
+			check_move : function () {
+				if(!this.__call_old()) { return false; }
+				var p = this._get_move(), nms = [];
+				if(p.o && p.o.length) {
+					p.o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
+					return this._check_unique(nms, p.np.find("> ul > li").not(p.o), "check_move");
+				}
+				return true;
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+/*
+ * jsTree wholerow plugin
+ * Makes select and hover work on the entire width of the node
+ * MAY BE HEAVY IN LARGE DOM
+ */
+(function ($) {
+	$.jstree.plugin("wholerow", {
+		__init : function () {
+			if(!this.data.ui) { throw "jsTree wholerow: jsTree UI plugin not included."; }
+			this.data.wholerow.html = false;
+			this.data.wholerow.to = false;
+			this.get_container()
+				.bind("init.jstree", $.proxy(function (e, data) { 
+						this._get_settings().core.animation = 0;
+					}, this))
+				.bind("open_node.jstree create_node.jstree clean_node.jstree loaded.jstree", $.proxy(function (e, data) { 
+						this._prepare_wholerow_span( data && data.rslt && data.rslt.obj ? data.rslt.obj : -1 );
+					}, this))
+				.bind("search.jstree clear_search.jstree reopen.jstree after_open.jstree after_close.jstree create_node.jstree delete_node.jstree clean_node.jstree", $.proxy(function (e, data) { 
+						if(this.data.to) { clearTimeout(this.data.to); }
+						this.data.to = setTimeout( (function (t, o) { return function() { t._prepare_wholerow_ul(o); }; })(this,  data && data.rslt && data.rslt.obj ? data.rslt.obj : -1), 0);
+					}, this))
+				.bind("deselect_all.jstree", $.proxy(function (e, data) { 
+						this.get_container().find(" > .jstree-wholerow .jstree-clicked").removeClass("jstree-clicked " + (this.data.themeroller ? this._get_settings().themeroller.item_a : "" ));
+					}, this))
+				.bind("select_node.jstree deselect_node.jstree ", $.proxy(function (e, data) { 
+						data.rslt.obj.each(function () { 
+							var ref = data.inst.get_container().find(" > .jstree-wholerow li:visible:eq(" + ( parseInt((($(this).offset().top - data.inst.get_container().offset().top + data.inst.get_container()[0].scrollTop) / data.inst.data.core.li_height),10)) + ")");
+							// ref.children("a")[e.type === "select_node" ? "addClass" : "removeClass"]("jstree-clicked");
+							ref.children("a").attr("class",data.rslt.obj.children("a").attr("class"));
+						});
+					}, this))
+				.bind("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) { 
+						this.get_container().find(" > .jstree-wholerow .jstree-hovered").removeClass("jstree-hovered " + (this.data.themeroller ? this._get_settings().themeroller.item_h : "" ));
+						if(e.type === "hover_node") {
+							var ref = this.get_container().find(" > .jstree-wholerow li:visible:eq(" + ( parseInt(((data.rslt.obj.offset().top - this.get_container().offset().top + this.get_container()[0].scrollTop) / this.data.core.li_height),10)) + ")");
+							// ref.children("a").addClass("jstree-hovered");
+							ref.children("a").attr("class",data.rslt.obj.children(".jstree-hovered").attr("class"));
+						}
+					}, this))
+				.delegate(".jstree-wholerow-span, ins.jstree-icon, li", "click.jstree", function (e) {
+						var n = $(e.currentTarget);
+						if(e.target.tagName === "A" || (e.target.tagName === "INS" && n.closest("li").is(".jstree-open, .jstree-closed"))) { return; }
+						n.closest("li").children("a:visible:eq(0)").click();
+						e.stopImmediatePropagation();
+					})
+				.delegate("li", "mouseover.jstree", $.proxy(function (e) {
+						e.stopImmediatePropagation();
+						if($(e.currentTarget).children(".jstree-hovered, .jstree-clicked").length) { return false; }
+						this.hover_node(e.currentTarget);
+						return false;
+					}, this))
+				.delegate("li", "mouseleave.jstree", $.proxy(function (e) {
+						if($(e.currentTarget).children("a").hasClass("jstree-hovered").length) { return; }
+						this.dehover_node(e.currentTarget);
+					}, this));
+			if(is_ie7 || is_ie6) {
+				$.vakata.css.add_sheet({ str : ".jstree-" + this.get_index() + " { position:relative; } ", title : "jstree" });
+			}
+		},
+		defaults : {
+		},
+		__destroy : function () {
+			this.get_container().children(".jstree-wholerow").remove();
+			this.get_container().find(".jstree-wholerow-span").remove();
+		},
+		_fn : {
+			_prepare_wholerow_span : function (obj) {
+				obj = !obj || obj == -1 ? this.get_container().find("> ul > li") : this._get_node(obj);
+				if(obj === false) { return; } // added for removing root nodes
+				obj.each(function () {
+					$(this).find("li").addBack().each(function () {
+						var $t = $(this);
+						if($t.children(".jstree-wholerow-span").length) { return true; }
+						$t.prepend("<span class='jstree-wholerow-span' style='width:" + ($t.parentsUntil(".jstree","li").length * 18) + "px;'> </span>");
+					});
+				});
+			},
+			_prepare_wholerow_ul : function () {
+				var o = this.get_container().children("ul").eq(0), h = o.html();
+				o.addClass("jstree-wholerow-real");
+				if(this.data.wholerow.last_html !== h) {
+					this.data.wholerow.last_html = h;
+					this.get_container().children(".jstree-wholerow").remove();
+					this.get_container().append(
+						o.clone().removeClass("jstree-wholerow-real")
+							.wrapAll("<div class='jstree-wholerow' />").parent()
+							.width(o.parent()[0].scrollWidth)
+							.css("top", (o.height() + ( is_ie7 ? 5 : 0)) * -1 )
+							.find("li[id]").each(function () { this.removeAttribute("id"); }).end()
+					);
+				}
+			}
+		}
+	});
+	$(function() {
+		var css_string = '' + 
+			'.jstree .jstree-wholerow-real { position:relative; z-index:1; } ' + 
+			'.jstree .jstree-wholerow-real li { cursor:pointer; } ' + 
+			'.jstree .jstree-wholerow-real a { border-left-color:transparent !important; border-right-color:transparent !important; } ' + 
+			'.jstree .jstree-wholerow { position:relative; z-index:0; height:0; } ' + 
+			'.jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { width:100%; } ' + 
+			'.jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li, .jstree .jstree-wholerow a { margin:0 !important; padding:0 !important; } ' + 
+			'.jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { background:transparent !important; }' + 
+			'.jstree .jstree-wholerow ins, .jstree .jstree-wholerow span, .jstree .jstree-wholerow input { display:none !important; }' + 
+			'.jstree .jstree-wholerow a, .jstree .jstree-wholerow a:hover { text-indent:-9999px; !important; width:100%; padding:0 !important; border-right-width:0px !important; border-left-width:0px !important; } ' + 
+			'.jstree .jstree-wholerow-span { position:absolute; left:0; margin:0px; padding:0; height:18px; border-width:0; padding:0; z-index:0; }';
+		if(is_ff2) {
+			css_string += '' + 
+				'.jstree .jstree-wholerow a { display:block; height:18px; margin:0; padding:0; border:0; } ' + 
+				'.jstree .jstree-wholerow-real a { border-color:transparent !important; } ';
+		}
+		if(is_ie7 || is_ie6) {
+			css_string += '' + 
+				'.jstree .jstree-wholerow, .jstree .jstree-wholerow li, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow a { margin:0; padding:0; line-height:18px; } ' + 
+				'.jstree .jstree-wholerow a { display:block; height:18px; line-height:18px; overflow:hidden; } ';
+		}
+		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
+	});
+})(jQuery);
+//*/
+
+/*
+* jsTree model plugin
+* This plugin gets jstree to use a class model to retrieve data, creating great dynamism
+*/
+(function ($) {
+	var nodeInterface = ["getChildren","getChildrenCount","getAttr","getName","getProps"],
+		validateInterface = function(obj, inter) {
+			var valid = true;
+			obj = obj || {};
+			inter = [].concat(inter);
+			$.each(inter, function (i, v) {
+				if(!$.isFunction(obj[v])) { valid = false; return false; }
+			});
+			return valid;
+		};
+	$.jstree.plugin("model", {
+		__init : function () {
+			if(!this.data.json_data) { throw "jsTree model: jsTree json_data plugin not included."; }
+			this._get_settings().json_data.data = function (n, b) {
+				var obj = (n == -1) ? this._get_settings().model.object : n.data("jstree_model");
+				if(!validateInterface(obj, nodeInterface)) { return b.call(null, false); }
+				if(this._get_settings().model.async) {
+					obj.getChildren($.proxy(function (data) {
+						this.model_done(data, b);
+					}, this));
+				}
+				else {
+					this.model_done(obj.getChildren(), b);
+				}
+			};
+		},
+		defaults : {
+			object : false,
+			id_prefix : false,
+			async : false
+		},
+		_fn : {
+			model_done : function (data, callback) {
+				var ret = [], 
+					s = this._get_settings(),
+					_this = this;
+
+				if(!$.isArray(data)) { data = [data]; }
+				$.each(data, function (i, nd) {
+					var r = nd.getProps() || {};
+					r.attr = nd.getAttr() || {};
+					if(nd.getChildrenCount()) { r.state = "closed"; }
+					r.data = nd.getName();
+					if(!$.isArray(r.data)) { r.data = [r.data]; }
+					if(_this.data.types && $.isFunction(nd.getType)) {
+						r.attr[s.types.type_attr] = nd.getType();
+					}
+					if(r.attr.id && s.model.id_prefix) { r.attr.id = s.model.id_prefix + r.attr.id; }
+					if(!r.metadata) { r.metadata = { }; }
+					r.metadata.jstree_model = nd;
+					ret.push(r);
+				});
+				callback.call(null, ret);
+			}
+		}
+	});
+})(jQuery);
+//*/
+
+})();
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/script.js b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/script.js
new file mode 100644
index 0000000..2888d66
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/script.js
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function populateFooter(report) {
+    $("#gradleVersion").text(report.gradleVersion);
+    $("#generationDate").text(report.generationDate);
+}
+
+function initializeProjectPage(report) {
+    $(document).ready(function() {
+        // event handling to close the insight div
+        $('#insight').on('click', '#dismissInsight', function(event) {
+            $('#insight').fadeOut();
+            event.preventDefault();
+        });
+
+        // creates a node of a dependency tree
+        function createDependencyNode(dependency) {
+            var node = {
+                data : dependency.name,
+                state : "open",
+                attr : {'data-module' : dependency.module},
+                children : []
+            };
+            var classes = [];
+            if (dependency.alreadyRendered) {
+                classes.push('alreadyRendered');
+            }
+            if (dependency.hasConflict) {
+                classes.push('hasConflict');
+            }
+            if (!dependency.resolvable) {
+                classes.push('unresolvable');
+            }
+            if (classes.length > 0) {
+                node.attr['class'] = classes.join(' ');
+            }
+            $.each(dependency.children, function(index, dependency) {
+                var dependencyNode = createDependencyNode(dependency);
+                node.children.push(dependencyNode);
+            });
+            return node;
+        }
+
+        // finds the moduleInsight by module among the given moduleInsights and returns its insight
+        function findInsight(moduleInsights, module) {
+            for (var i = 0; i < moduleInsights.length; i++) {
+                if (moduleInsights[i].module == module) {
+                    return moduleInsights[i].insight;
+                }
+            }
+            return null;
+        }
+
+        // creates a node of the insight tree
+        function createInsightNode(dependency) {
+            var node = {
+                data : dependency.name + (dependency.description ? ' (' + dependency.description + ')' : ''),
+                state : "open",
+                attr : {},
+                children : []
+            }
+            var classes = [];
+            if (dependency.alreadyRendered) {
+                classes.push('alreadyRendered');
+            }
+            if (!dependency.resolvable) {
+                classes.push('unresolvable');
+            }
+            if (dependency.hasConflict) {
+                classes.push('hasConflict');
+            }
+            if (dependency.isLeaf) {
+                classes.push('leaf');
+            }
+            if (classes.length > 0) {
+                node.attr['class'] = classes.join(' ');
+            }
+            $.each(dependency.children, function(index, dependency) {
+                var dependencyNode = createInsightNode(dependency);
+                node.children.push(dependencyNode);
+            });
+            return node;
+        }
+
+        // generates a tree for the given module by finding the insight among the given moduleInsights,
+        // and displays the insight div
+        function showModuleInsight(module, moduleInsights) {
+            var $insightDiv = $('#insight');
+            $insightDiv.html('');
+            $insightDiv.append($('<i> </i>').attr('id', 'dismissInsight').attr('title', 'Close'));
+            $insightDiv.append($('<h3>Insight for module </h3>').append(module));
+            var $tree = $('<div>').addClass('insightTree');
+            var insight = findInsight(moduleInsights, module);
+            var nodes = [];
+            $.each(insight, function(index, dependency) {
+                var dependencyNode = createInsightNode(dependency);
+                nodes.push(dependencyNode);
+            });
+            $tree.append($('<img>').attr('src', 'throbber.gif')).append('Loading...');
+            $tree.jstree({
+                json_data : {
+                    data : nodes
+                },
+                themes : {
+                    url : 'tree.css',
+                    icons : false
+                },
+                plugins : ['json_data', 'themes']
+            }).bind("loaded.jstree", function (event, data) {
+                        $('li.unresolvable a').attr('title', 'This dependency could not be resolved');
+                        $('li.alreadyRendered a').attr('title', 'The children of this dependency are not displayed because they have already been displayed before');
+                    });
+            $insightDiv.append($tree);
+            $tree.on('click', 'a', function(event) {
+                event.preventDefault();
+            });
+            $insightDiv.fadeIn();
+        }
+
+        // generates the configuration dependeny trees
+        var $dependencies = $('#dependencies');
+        var project = report.project;
+
+        $dependencies.append($('<h2/>').text('Project ' + project.name));
+        if (project.description) {
+            $dependencies.append($('<p>').addClass('projectDescription').text(project.name));
+        }
+
+        $.each(project.configurations, function(index, configuration) {
+            var $configurationDiv = $('<div/>').addClass('configuration');
+            var $configurationTitle = $('<h3/>').addClass('closed').append($('<ins/>')).append(configuration.name);
+            if (configuration.description) {
+                $configurationTitle.append(' - ').append($('<span/>').addClass('configurationDescription').text(configuration.description));
+            }
+            $configurationDiv.append($configurationTitle);
+
+            var $contentDiv = $('<div/>').addClass('configurationContent').hide();
+            var $tree = $('<div>').addClass('dependencyTree');
+            $contentDiv.append($tree);
+            if (configuration.dependencies && configuration.dependencies.length > 0) {
+                var nodes = [];
+                $.each(configuration.dependencies, function(index, dependency) {
+                    var dependencyNode = createDependencyNode(dependency);
+                    nodes.push(dependencyNode);
+                });
+                $tree.append($('<img>').attr('src', 'throbber.gif')).append('Loading...');
+                $tree.jstree({
+                    json_data : {
+                        data : nodes
+                    },
+                    themes : {
+                        url : 'tree.css',
+                        icons : false
+                    },
+                    plugins : ['json_data', 'themes']
+                }).bind("loaded.jstree", function (event, data) {
+                            $('li.unresolvable a').attr('title', 'This dependency could not be resolved');
+                            $('li.alreadyRendered a').attr('title', 'The children of this dependency are not displayed because they have already been displayed before');
+                        });
+            }
+            else {
+                $tree.append($('<p/>').text("No dependency"));
+            }
+
+            $tree.on('click', 'a', function(event) {
+                event.preventDefault();
+                var module = $(this).closest('li').attr('data-module');
+                showModuleInsight(module, configuration.moduleInsights);
+            });
+
+            $configurationDiv.append($contentDiv);
+            $dependencies.append($configurationDiv);
+        });
+
+        // allows the titles of each dependency tree to toggle the visibility of their tree
+        $dependencies.on('click', 'h3', function(event) {
+            $('div.configurationContent', $(this).parent()).slideToggle();
+            $(this).toggleClass('closed');
+        });
+
+        $('#projectBreacrumb').text(project.name);
+        populateFooter(report);
+    });
+}
+
+function initializeIndexPage(report) {
+    $(document).ready(function() {
+        var $tbody = $('#projects tbody');
+        $.each(report.projects, function(index, project) {
+            var $tr = $('<tr></tr>');
+            var $pathTd = $('<td></td>');
+            $pathTd.append($('<a></a>').attr("href", project.file).text(project.path))
+            var $nameTd = $('<td></td>').text(project.name);
+            $tr.append($pathTd).append($nameTd);
+            var $descriptionTd = $('<td></td>').text(project.description ? project.description : '');
+            $tr.append($pathTd).append($nameTd).append($descriptionTd);
+            $tbody.append($tr);
+        });
+
+        populateFooter(report);
+    });
+}
+
+if (window.projectDependencyReport) {
+    initializeProjectPage(window.projectDependencyReport);
+}
+else if (window.mainDependencyReport) {
+    initializeIndexPage(window.mainDependencyReport);
+}
+
+
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/style.css b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/style.css
new file mode 100644
index 0000000..4915a29
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/style.css
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.dependencyTree, #insight {
+    border: solid 2px #d0d0d0;
+    border-radius: 10px;
+    behavior: url(css3-pie-1.0beta3.htc);
+    padding: 8px;
+    background-color: #FFFFEE
+}
+.alreadyRendered > a, .unresolvable > a {
+    border-radius: 4px;
+}
+.alreadyRendered > a {
+    border: solid 1px #EEEEEE;
+    background-color: #EEEEEE !IMPORTANT;
+}
+.hasConflict > a {
+    font-weight: bold !IMPORTANT;
+    color: #FAA732 !IMPORTANT;
+}
+.unresolvable > a {
+    border: solid 1px #DD514C;
+    background-color: #DD514C !IMPORTANT;
+    color: white !IMPORTANT;
+}
+.leaf a {
+    font-style: italic !IMPORTANT;
+}
+.configuration h3 {
+    cursor: pointer;
+}
+.configuration h3 ins {
+    background-image: url(d.png); background-repeat:no-repeat; background-color:white;
+    background-position: -18px 0;
+    display: inline-block;
+    width: 18px;
+    height: 18px;
+    position: relative;
+    top: 4px;
+}
+.configuration h3.closed  ins {
+    background-position: 0 0;
+}
+#dependencies {
+    margin-right: 30px;
+}
+#insight {
+    position: fixed;
+    right: 50px;
+    top: 50px;
+    display: none;
+    width: 70%;
+    box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.3);
+}
+#dismissInsight {
+    float: right;
+    background:url("d.png") -18px -53px no-repeat !important;
+    width: 20px;
+    height: 20px;
+    cursor: pointer;
+}
+.insightTree {
+    height: 450px;
+    overflow: auto;
+}
+.insightTree a {
+    cursor: default;
+}
+.configurationDescription {
+    font-size: small;
+}
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/template.html b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/template.html
new file mode 100644
index 0000000..b9a6808
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/template.html
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Dependency report</title>
+        <link href="base-style.css" rel="stylesheet" type="text/css">
+        <link href="style.css" rel="stylesheet" type="text/css">
+        <script src="jquery-1.10.1.min.js" charset="utf-8"></script>
+        <script src="jquery.jstree.js" charset="utf-8"></script>
+        <script src="@js@" charset="utf-8"></script>
+        <script src="script.js" charset="utf-8"></script>
+    </head>
+    <body>
+        <div id="content">
+            <h1>Dependency Report</h1>
+            <div class="breadcrumbs">
+                <a href="index.html">Projects</a> > <span id="projectBreacrumb"></span>
+            </div>
+            <div id="insight"></div>
+            <div id="dependencies"></div>
+            <div id="footer">
+                <p>Generated by <a id="gradleVersion" href="http://www.gradle.org"></a> at <span id="generationDate"></span></p>
+            </div>
+        </div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/throbber.gif b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/throbber.gif
new file mode 100644
index 0000000..5b33f7e
Binary files /dev/null and b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/throbber.gif differ
diff --git a/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/tree.css b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/tree.css
new file mode 100644
index 0000000..afbf8d3
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/org/gradle/api/tasks/diagnostics/htmldependencyreport/tree.css
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * jsTree default theme 1.0
+ * Supported features: dots/no-dots, icons/no-icons, focused, loading
+ * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
+ */
+
+.jstree-default li, 
+.jstree-default ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
+.jstree-default li { background-position:-90px 0; background-repeat:repeat-y; }
+.jstree-default li.jstree-last { background:transparent; }
+.jstree-default .jstree-open > ins { background-position:-72px 0; }
+.jstree-default .jstree-closed > ins { background-position:-54px 0; }
+.jstree-default .jstree-leaf > ins { background-position:-36px 0; }
+
+.jstree-default .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; }
+.jstree-default .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; }
+.jstree-default a .jstree-icon { background-position:-56px -19px; }
+.jstree-default a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }
+
+.jstree-default.jstree-focused { background:#ffffee; }
+
+.jstree-default .jstree-no-dots li, 
+.jstree-default .jstree-no-dots .jstree-leaf > ins { background:transparent; }
+.jstree-default .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
+.jstree-default .jstree-no-dots .jstree-closed > ins { background-position:0 0; }
+
+.jstree-default .jstree-no-icons a .jstree-icon { display:none; }
+
+.jstree-default .jstree-search { font-style:italic; }
+
+.jstree-default .jstree-no-icons .jstree-checkbox { display:inline-block; }
+.jstree-default .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
+.jstree-default .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
+.jstree-default .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
+.jstree-default .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
+.jstree-default .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; }
+.jstree-default .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
+.jstree-default .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }
+
+#vakata-dragged.jstree-default ins { background:transparent !important; }
+#vakata-dragged.jstree-default .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }
+#vakata-dragged.jstree-default .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }
+#jstree-marker.jstree-default { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }
+
+.jstree-default a.jstree-search { color:aqua; }
+.jstree-default .jstree-locked a { color:silver; cursor:default; }
+
+#vakata-contextmenu.jstree-default-context, 
+#vakata-contextmenu.jstree-default-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
+#vakata-contextmenu.jstree-default-context li { }
+#vakata-contextmenu.jstree-default-context a { color:black; }
+#vakata-contextmenu.jstree-default-context a:hover, 
+#vakata-contextmenu.jstree-default-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
+#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a, 
+#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
+#vakata-contextmenu.jstree-default-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
+#vakata-contextmenu.jstree-default-context li ul { margin-left:-4px; }
+
+/* IE6 BEGIN */
+.jstree-default li, 
+.jstree-default ins,
+#vakata-dragged.jstree-default .jstree-invalid, 
+#vakata-dragged.jstree-default .jstree-ok, 
+#jstree-marker.jstree-default { _background-image:url("d.gif"); }
+.jstree-default .jstree-open ins { _background-position:-72px 0; }
+.jstree-default .jstree-closed ins { _background-position:-54px 0; }
+.jstree-default .jstree-leaf ins { _background-position:-36px 0; }
+.jstree-default a ins.jstree-icon { _background-position:-56px -19px; }
+#vakata-contextmenu.jstree-default-context ins { _display:none; }
+#vakata-contextmenu.jstree-default-context li { _zoom:1; }
+.jstree-default .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; }
+.jstree-default .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; }
+.jstree-default .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; }
+/* IE6 END */
+
+/* customizations */
+.jstree a ins {
+    display: none;
+}
+.jstree li {
+    line-height: 24px;
+}
+.jstree ins {
+    position: relative;
+    top: 4px;
+}
\ No newline at end of file
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
index cdfccf8..203acf4 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/HelpTasksPluginSpec.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/HelpTasksPluginSpec.groovy
@@ -16,19 +16,15 @@
 
 package org.gradle.api.plugins
 
+import org.gradle.api.tasks.diagnostics.*
 import org.gradle.configuration.Help
-import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.TestUtil
 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()
+    final project = TestUtil.createRootProject()
 
     def "adds help tasks"() {
         when:
@@ -43,6 +39,19 @@ class HelpTasksPluginSpec extends Specification {
         hasHelpTask(PROPERTIES_TASK, PropertyReportTask)
     }
 
+    def "tasks description reflects whether project has sub-projects or not"() {
+        given:
+        def child = TestUtil.createChildProject(project, "child")
+
+        when:
+        project.apply(plugin: 'help-tasks')
+        child.apply(plugin: 'help-tasks')
+
+        then:
+        project.implicitTasks[TASKS_TASK].description == "Displays the tasks runnable from root project 'test' (some of the displayed tasks may belong to subprojects)."
+        child.implicitTasks[TASKS_TASK].description == "Displays the tasks runnable from project ':child'."
+    }
+
     def "configures tasks for java plugin"() {
         when:
         project.apply(plugin: 'help-tasks')
@@ -57,12 +66,13 @@ class HelpTasksPluginSpec extends Specification {
         project.implicitTasks[DEPENDENCY_INSIGHT_TASK].configuration == project.configurations.compile
     }
 
-    private void hasHelpTask(String name, Class type) {
+    private 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)
         }
+        return task
     }
 }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.groovy
new file mode 100644
index 0000000..0ca88eb
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Task
+import org.gradle.api.reporting.dependencies.HtmlDependencyReportTask
+import org.gradle.api.tasks.diagnostics.DependencyReportTask
+import org.gradle.api.tasks.diagnostics.PropertyReportTask
+import org.gradle.api.tasks.diagnostics.TaskReportTask
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
+import static org.hamcrest.Matchers.instanceOf
+
+public class ProjectReportsPluginTest extends Specification {
+    private final Project project = TestUtil.createRootProject()
+    private final ProjectReportsPlugin plugin = new ProjectReportsPlugin()
+
+    def appliesBaseReportingPluginAndAddsConventionObject() {
+        when:
+        plugin.apply(project)
+
+        then:
+        project.plugins.hasPlugin(ReportingBasePlugin.class)
+        project.convention.getPlugin(ProjectReportsPluginConvention.class)
+    }
+
+    def addsTasksToProject() {
+        when:
+        plugin.apply(project);
+
+        then:
+        Task taskReport = project.tasks.getByName(ProjectReportsPlugin.TASK_REPORT);
+        taskReport instanceOf(TaskReportTask.class)
+        taskReport.outputFile == new File(project.buildDir, "reports/project/tasks.txt")
+        taskReport.projects == [project] as Set
+
+        Task propertyReport = project.tasks.getByName(ProjectReportsPlugin.PROPERTY_REPORT)
+        propertyReport instanceOf(PropertyReportTask.class)
+        propertyReport.outputFile == new File(project.buildDir, "reports/project/properties.txt")
+        propertyReport.projects == [project] as Set
+
+        Task dependencyReport = project.tasks.getByName(ProjectReportsPlugin.DEPENDENCY_REPORT);
+        dependencyReport instanceOf(DependencyReportTask.class)
+        dependencyReport.outputFile == new File(project.getBuildDir(), "reports/project/dependencies.txt")
+        dependencyReport.projects == [project] as Set
+
+        Task htmlReport = project.tasks.getByName(ProjectReportsPlugin.HTML_DEPENDENCY_REPORT);
+        htmlReport instanceOf(HtmlDependencyReportTask.class)
+        htmlReport.reports.html.destination == new File(project.buildDir, "reports/project/dependencies")
+        htmlReport.projects == [project] as Set
+
+        Task projectReport = project.getTasks().getByName(ProjectReportsPlugin.PROJECT_REPORT);
+        projectReport dependsOn(ProjectReportsPlugin.TASK_REPORT, ProjectReportsPlugin.PROPERTY_REPORT, ProjectReportsPlugin.DEPENDENCY_REPORT, ProjectReportsPlugin.HTML_DEPENDENCY_REPORT)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
deleted file mode 100644
index c7ad6f0..0000000
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.plugins;
-
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.tasks.diagnostics.DependencyReportTask;
-import org.gradle.api.tasks.diagnostics.PropertyReportTask;
-import org.gradle.api.tasks.diagnostics.TaskReportTask;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import org.junit.Test;
-
-import java.io.File;
-
-import static org.gradle.util.Matchers.dependsOn;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-public class ProjectReportsPluginTest {
-    private final Project project = HelperUtil.createRootProject();
-    private final ProjectReportsPlugin plugin = new ProjectReportsPlugin();
-
-    @Test
-    public void appliesBaseReportingPluginAndAddsConventionObject() {
-        plugin.apply(project);
-
-        assertTrue(project.getPlugins().hasPlugin(ReportingBasePlugin.class));
-        assertThat(project.getConvention().getPlugin(ProjectReportsPluginConvention.class), notNullValue());
-    }
-
-    @Test
-    public void addsTasksToProject() {
-        plugin.apply(project);
-
-        Task task = project.getTasks().getByName(ProjectReportsPlugin.TASK_REPORT);
-        assertThat(task, instanceOf(TaskReportTask.class));
-        assertThat(task.property("outputFile"), equalTo((Object) new File(project.getBuildDir(), "reports/project/tasks.txt")));
-        assertThat(task.property("projects"), equalTo((Object) WrapUtil.toSet(project)));
-
-        task = project.getTasks().getByName(ProjectReportsPlugin.PROPERTY_REPORT);
-        assertThat(task, instanceOf(PropertyReportTask.class));
-        assertThat(task.property("outputFile"), equalTo((Object) new File(project.getBuildDir(), "reports/project/properties.txt")));
-        assertThat(task.property("projects"), equalTo((Object) WrapUtil.toSet(project)));
-
-        task = project.getTasks().getByName(ProjectReportsPlugin.DEPENDENCY_REPORT);
-        assertThat(task, instanceOf(DependencyReportTask.class));
-        assertThat(task.property("outputFile"), equalTo((Object) new File(project.getBuildDir(), "reports/project/dependencies.txt")));
-        assertThat(task.property("projects"), equalTo((Object) WrapUtil.toSet(project)));
-        
-        task = project.getTasks().getByName(ProjectReportsPlugin.PROJECT_REPORT);
-        assertThat(task, dependsOn(ProjectReportsPlugin.TASK_REPORT, ProjectReportsPlugin.PROPERTY_REPORT, ProjectReportsPlugin.DEPENDENCY_REPORT));
-    }
-}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
deleted file mode 100644
index 5748ae7..0000000
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.plugins
-
-import org.gradle.api.Project
-import org.gradle.api.reporting.ReportingExtension
-import org.gradle.testfixtures.ProjectBuilder
-import spock.lang.Specification
-
-// Note: ReportingBasePluginConvention has been deprecated
-public class ReportingBasePluginConventionTest extends Specification {
-
-    Project project = ProjectBuilder.builder().build()
-    ReportingExtension extension = new ReportingExtension(project)
-    ReportingBasePluginConvention convention = new ReportingBasePluginConvention(project, extension)
-
-    def "defaults to reports dir in build dir"() {
-        expect:
-        convention.reportsDirName == ReportingExtension.DEFAULT_REPORTS_DIR_NAME
-        convention.reportsDir == new File(project.buildDir, ReportingExtension.DEFAULT_REPORTS_DIR_NAME)
-    }
-
-    def "can set reports dir by name, relative to build dir"() {
-        when:
-        convention.reportsDirName = "something-else"
-        
-        then:
-        convention.reportsDir == new File(project.buildDir, "something-else")
-        
-        when:
-        project.buildDir = new File(project.buildDir, "new-build-dir")
-
-        then:
-        convention.reportsDir == new File(project.buildDir, "something-else")
-    }
-
-    def "calculates api doc title from project name and version"() {
-        expect:
-        project.version == Project.DEFAULT_VERSION
-
-        and:
-        convention.apiDocTitle == "$project.name API"
-
-        when:
-        project.version = "1.0"
-
-        then:
-        convention.apiDocTitle == "$project.name 1.0 API"
-    }
-
-}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
deleted file mode 100644
index 1e3ccde..0000000
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.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/diagnostics/src/test/groovy/org/gradle/api/reporting/dependencies/internal/StrictDependencyResultSpecTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/dependencies/internal/StrictDependencyResultSpecTest.groovy
new file mode 100644
index 0000000..d42d232
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/dependencies/internal/StrictDependencyResultSpecTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dependencies.internal
+
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newDependency
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newUnresolvedDependency
+/**
+ * Unit tests for <code>StrictDependencyResultSpec</code>
+ */
+class StrictDependencyResultSpecTest extends Specification {
+
+    def "knows matching dependencies"() {
+        expect:
+        new StrictDependencyResultSpec(moduleIdentifier).isSatisfiedBy(newDependency("org.foo", "foo-core", "1.0"))
+
+        where:
+        moduleIdentifier << [new DefaultModuleIdentifier('org.foo', 'foo-core')]
+    }
+
+    def "knows mismatching dependencies"() {
+        expect:
+        !new StrictDependencyResultSpec(moduleIdentifier).isSatisfiedBy(newDependency("org.foo", "foo-core", "1.0"))
+
+        where:
+        moduleIdentifier << [new DefaultModuleIdentifier('org.foobar', 'foo-core'),
+                             new DefaultModuleIdentifier('org.foo', 'foo-coreImpl')]
+    }
+
+    def "matches unresolved dependencies"() {
+        expect:
+        new StrictDependencyResultSpec(moduleIdentifier).isSatisfiedBy(newUnresolvedDependency("org.foo", "foo-core", "5.0"))
+
+        where:
+        moduleIdentifier << [new DefaultModuleIdentifier('org.foo', 'foo-core')]
+    }
+
+    def "does not match unresolved dependencies"() {
+        expect:
+        !new StrictDependencyResultSpec(moduleIdentifier).isSatisfiedBy(newUnresolvedDependency("org.foo", "foo-core", "5.0"))
+
+        where:
+        moduleIdentifier << [new DefaultModuleIdentifier('org.foobar', 'foo-core'),
+                             new DefaultModuleIdentifier('org.foo', 'foo-coreImpl')]
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
index 179fd4d..91f3013 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
@@ -20,7 +20,7 @@ import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
@@ -34,8 +34,8 @@ import org.junit.runner.RunWith;
 import java.io.File;
 import java.io.IOException;
 
-import static org.gradle.util.HelperUtil.createChildProject;
-import static org.gradle.util.HelperUtil.createRootProject;
+import static org.gradle.util.TestUtil.createChildProject;
+import static org.gradle.util.TestUtil.createRootProject;
 import static org.hamcrest.Matchers.notNullValue;
 
 @RunWith(JMock.class)
@@ -52,7 +52,7 @@ public class AbstractReportTaskTest {
     public void setUp() throws Exception {
         generator = context.mock(Runnable.class);
         renderer = context.mock(ReportRenderer.class);
-        task = HelperUtil.createTask(TestReportTask.class, project);
+        task = TestUtil.createTask(TestReportTask.class, project);
         task.setGenerator(generator);
         task.setRenderer(renderer);
         task.setProjects(WrapUtil.<Project>toSet(project));
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
index 86499a9..98bfbe6 100644
--- 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
@@ -18,16 +18,13 @@ package org.gradle.api.tasks.diagnostics
 
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.specs.Spec
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 9/20/12
- */
 class DependencyInsightReportTaskSpec extends Specification {
 
-    def project = HelperUtil.createRootProject()
-    def task = HelperUtil.createTask(DependencyInsightReportTask, project)
+    def project = TestUtil.createRootProject()
+    def task = TestUtil.createTask(DependencyInsightReportTask, project)
 
     def "fails if configuration missing"() {
         when:
@@ -38,7 +35,7 @@ class DependencyInsightReportTaskSpec extends Specification {
     }
 
     def "fails if dependency to include missing"() {
-        def conf = project.configurations.add("foo")
+        def conf = project.configurations.create("foo")
         task.configuration = conf
 
         when:
@@ -58,7 +55,7 @@ class DependencyInsightReportTaskSpec extends Specification {
 
     def "can set spec and configuration directly"() {
         when:
-        def conf = project.configurations.add("foo")
+        def conf = project.configurations.create("foo")
         task.configuration = conf
         task.dependencySpec = { true } as Spec
         then:
@@ -68,7 +65,7 @@ class DependencyInsightReportTaskSpec extends Specification {
 
     def "can set spec and configuration via methods"() {
         when:
-        project.configurations.add("foo")
+        project.configurations.create("foo")
         task.setConfiguration 'foo'
         task.setDependencySpec 'bar'
         then:
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
index a17431a..20a26cb 100644
--- 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
@@ -20,23 +20,22 @@ import org.gradle.api.artifacts.Configuration
 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 org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class DependencyReportTaskTest extends Specification {
     private Project project = new ProjectBuilder().build()
-    private DependencyReportTask task = HelperUtil.createTask(DependencyReportTask.class, project)
+    private DependencyReportTask task = TestUtil.createTask(DependencyReportTask.class, project)
     private DependencyReportRenderer renderer = Mock(DependencyReportRenderer)
-    private Configuration conf1 = project.configurations.add("conf1")
-    private Configuration conf2 = project.configurations.add("conf2")
+    private Configuration conf1 = project.configurations.create("conf1")
+    private Configuration conf2 = project.configurations.create("conf2")
 
     void setup() {
         task.renderer = renderer
     }
 
     def "task is configured correctly"() {
-        task = HelperUtil.createTask(DependencyReportTask.class);
+        task = TestUtil.createTask(DependencyReportTask.class);
 
         expect:
         task.renderer instanceof AsciiDependencyReportRenderer
@@ -61,8 +60,8 @@ class DependencyReportTaskTest extends Specification {
 
     def "rendering can be limited to specific configurations"() {
         given:
-        project.configurations.add("a")
-        def bConf = project.configurations.add("b")
+        project.configurations.create("a")
+        def bConf = project.configurations.create("b")
         task.configurations = [bConf] as Set
 
         when:
@@ -77,8 +76,8 @@ class DependencyReportTaskTest extends Specification {
 
     def "rendering can be limited to a single configuration, specified by name"() {
         given:
-        project.configurations.add("a")
-        def bConf = project.configurations.add("b")
+        project.configurations.create("a")
+        def bConf = project.configurations.create("b")
 
         when:
         task.configuration = "b"
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
index bb3d948..4993eb5 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
@@ -18,12 +18,12 @@ package org.gradle.api.tasks.diagnostics
 import org.gradle.api.Project
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.logging.TestStyledTextOutput
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class ProjectReportTaskTest extends Specification {
-    final ProjectInternal project = HelperUtil.createRootProject()
-    final ProjectReportTask task = HelperUtil.createTask(ProjectReportTask, project)
+    final ProjectInternal project = TestUtil.createRootProject()
+    final ProjectReportTask task = TestUtil.createTask(ProjectReportTask, project)
     final TestStyledTextOutput output = new TestStyledTextOutput().ignoreStyle()
 
     def setup() {
@@ -32,10 +32,10 @@ class ProjectReportTaskTest extends Specification {
 
     def rendersReportForRootProjectWithChildren() {
         project.description = 'this is the root project'
-        Project child1 = HelperUtil.createChildProject(project, "child1")
+        Project child1 = TestUtil.createChildProject(project, "child1")
         child1.description = 'this is a subproject'
-        HelperUtil.createChildProject(child1, "child1")
-        HelperUtil.createChildProject(project, "child2")
+        TestUtil.createChildProject(child1, "child1")
+        TestUtil.createChildProject(project, "child2")
 
         when:
         task.generate(project)
@@ -67,7 +67,7 @@ For example, try running gradle :tasks
     }
 
     def rendersReportForNonRootProjectWithNoChildren() {
-        Project child1 = HelperUtil.createChildProject(project, "child1")
+        Project child1 = TestUtil.createChildProject(project, "child1")
 
         when:
         task.generate(child1)
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
index 4e6f89e..58f54ae 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
@@ -18,7 +18,7 @@ package org.gradle.api.tasks.diagnostics;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.diagnostics.internal.PropertyReportRenderer;
 import org.gradle.util.GUtil;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
 import org.jmock.integration.junit4.JMock;
@@ -50,7 +50,7 @@ public class PropertyReportTaskTest {
             will(returnValue(null));
         }});
 
-        task = HelperUtil.createTask(PropertyReportTask.class);
+        task = TestUtil.createTask(PropertyReportTask.class);
         task.setRenderer(renderer);
     }
 
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
index d208996..b3168ef 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
@@ -23,7 +23,7 @@ import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.diagnostics.internal.TaskDetails;
 import org.gradle.api.tasks.diagnostics.internal.TaskReportRenderer;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.Path;
 import org.hamcrest.BaseMatcher;
@@ -71,7 +71,7 @@ public class TaskReportTaskTest {
             will(returnValue(toSet()));
         }});
 
-        task = HelperUtil.createTask(TaskReportTask.class);
+        task = TestUtil.createTask(TaskReportTask.class);
         task.setRenderer(renderer);
     }
 
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
index 7817b99..5c90751 100644
--- 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
@@ -19,9 +19,6 @@ 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()
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
index 8fbda25..b2b15b9 100644
--- 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
@@ -20,13 +20,13 @@ 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 org.gradle.util.TestUtil
 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()
+    private final Project project = TestUtil.createRootProject()
 
     def setup() {
         renderer.output = textOutput
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
index 9ba513a..7a62468 100644
--- 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
@@ -16,19 +16,16 @@
 
 package org.gradle.api.tasks.diagnostics.internal.dsl
 
+import org.gradle.api.InvalidUserDataException
 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.internal.typeconversion.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()
+    NotationParser<Object, Spec<DependencyResult>> parser = DependencyResultSpecNotationParser.create()
 
     def "accepts closures"() {
         given:
@@ -36,7 +33,7 @@ class DependencyResultSpecNotationParserSpec extends Specification {
         def other = ResolutionResultDataBuilder.newDependency('org.mockito', 'other')
 
         when:
-        def spec = parser.parseNotation( { it.requested.name == 'mockito-core' } )
+        def spec = parser.parseNotation( { it.requested.module == 'mockito-core' } )
 
         then:
         spec.isSatisfiedBy(mockito)
@@ -64,7 +61,7 @@ class DependencyResultSpecNotationParserSpec extends Specification {
         when:
         def spec = parser.parseNotation(new Spec<DependencyResult>() {
             boolean isSatisfiedBy(DependencyResult element) {
-                return element.getRequested().getName().equals('mockito-core')
+                return element.requested.module == 'mockito-core'
             }
         })
 
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
index ea241fc..bd09a92 100644
--- 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
@@ -16,15 +16,12 @@
 
 package org.gradle.api.tasks.diagnostics.internal.dsl
 
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentSelector
 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"() {
@@ -66,4 +63,12 @@ class DependencyResultSpecTest extends Specification {
         where:
         notation << ['1.+', '1.22', 'foo-core:1.+', 'foo-core:1.22', 'org.foo:foo-core:1.+', 'org.foo:foo-core:1.22']
     }
+
+    def "does not match for dependencies other than requested ModuleComponentSelector"() {
+        expect:
+        !new DependencyResultSpec(notation).isSatisfiedBy(newDependency(new DefaultProjectComponentSelector(":myPath"), "org.foo", "foo-core", "1.22"))
+
+        where:
+        notation << ['1.+']
+    }
 }
\ 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
index 9820b2b..de3211b 100644
--- 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
@@ -16,14 +16,11 @@
 
 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.internal.graph.GraphRenderer
 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()
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
index 82222e1..57d7bd5 100644
--- 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
@@ -16,36 +16,47 @@
 
 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.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentSelector
 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: 10/9/12
- */
 class AbstractRenderableDependencyResultSpec extends Specification {
 
-    def "renders name cleanly"() {
+    def "renders name for ModuleComponentSelector"() {
+        given:
+        def requested = DefaultModuleComponentSelector.newSelector('org.mockito', 'mockito-core', '1.0')
+
+        expect:
+        dep(requested, DefaultModuleComponentIdentifier.newId('org.mockito', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0'
+        dep(requested, DefaultModuleComponentIdentifier.newId('org.mockito', 'mockito-core', '2.0')).name == 'org.mockito:mockito-core:1.0 -> 2.0'
+        dep(requested, DefaultModuleComponentIdentifier.newId('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> org.mockito:mockito:1.0'
+        dep(requested, DefaultModuleComponentIdentifier.newId('com.mockito', 'mockito', '2.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
+        dep(requested, DefaultModuleComponentIdentifier.newId('com.mockito.other', 'mockito-core', '3.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito.other:mockito-core:3.0'
+        dep(requested, DefaultModuleComponentIdentifier.newId('com.mockito.other', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito.other:mockito-core:1.0'
+        dep(requested, DefaultProjectComponentIdentifier.newId(':a')).name == 'org.mockito:mockito-core:1.0 -> project :a'
+    }
+
+    def "renders name for ProjectComponentSelector"() {
         given:
-        def requested = newSelector('org.mockito', 'mockito-core', '1.0')
+        def requested = DefaultProjectComponentSelector.newSelector(':a')
 
         expect:
-        dep(requested, newId('org.mockito', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0'
-        dep(requested, newId('org.mockito', 'mockito-core', '2.0')).name == 'org.mockito:mockito-core:1.0 -> 2.0'
-        dep(requested, newId('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> org.mockito:mockito:1.0'
-        dep(requested, newId('com.mockito', 'mockito', '2.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
+        dep(requested, DefaultProjectComponentIdentifier.newId(':a')).name == 'project :a'
+        dep(requested, DefaultProjectComponentIdentifier.newId(':b')).name == 'project :a -> project :b'
+        dep(requested, DefaultModuleComponentIdentifier.newId('org.somegroup', 'module', '1.0')).name == 'project :a -> org.somegroup:module:1.0'
     }
 
-    private AbstractRenderableDependencyResult dep(ModuleVersionSelector requested, ModuleVersionIdentifier selected) {
+    private AbstractRenderableDependencyResult dep(ComponentSelector requested, ComponentIdentifier selected) {
         return new AbstractRenderableDependencyResult() {
-            ModuleVersionSelector getRequested() {
+            ComponentSelector getRequested() {
                 return requested
             }
 
-            ModuleVersionIdentifier getActual() {
+            ComponentIdentifier getActual() {
                 return selected
             }
 
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
index d085884..c9be130 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
@@ -15,17 +15,14 @@
  */
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes
 
-import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+import org.gradle.api.artifacts.result.ResolvedComponentResult
 import org.gradle.api.artifacts.result.ResolvedDependencyResult
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
 import spock.lang.Specification
 
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector.newSelector
 import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
 
-/**
- * by Szczepan Faber, created at: 10/9/12
- */
 class RenderableDependencyResultTest extends Specification {
 
     def "renders name"() {
@@ -43,7 +40,7 @@ class RenderableDependencyResultTest extends Specification {
         dep(requested, differentGroup).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
     }
 
-    private RenderableDependencyResult dep(ModuleVersionSelector requested, ResolvedModuleVersionResult selected) {
+    private RenderableDependencyResult dep(ModuleComponentSelector requested, ResolvedComponentResult selected) {
         ResolvedDependencyResult dependencyResult = Stub() {
             getRequested() >> requested
             getSelected() >> selected
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
index 8a18941..7ab4407 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes
 
-import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.component.ModuleComponentSelector
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult
 import spock.lang.Specification
 
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector.newSelector
 
 class RenderableUnresolvedDependencyResultTest extends Specification {
 
@@ -38,7 +38,7 @@ class RenderableUnresolvedDependencyResultTest extends Specification {
         dep(requested, differentGroup).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
     }
 
-    private RenderableUnresolvedDependencyResult dep(ModuleVersionSelector requested, ModuleVersionSelector attempted) {
+    private RenderableUnresolvedDependencyResult dep(ModuleComponentSelector requested, ModuleComponentSelector attempted) {
         UnresolvedDependencyResult dependencyResult = Stub() {
             getRequested() >> requested
             getAttempted() >> attempted
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
new file mode 100644
index 0000000..6ed1a4e
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier.newId;
+
+public class SimpleDependency implements RenderableDependency {
+
+    private final ModuleComponentIdentifier id;
+    private final String name;
+    private final boolean resolvable;
+    private final String description;
+    private final Set<RenderableDependency> children = new LinkedHashSet<RenderableDependency>();
+
+    public SimpleDependency(String name) {
+        this(name, true, null);
+    }
+
+    public SimpleDependency(String name, boolean resolvable, String description) {
+        this.name = name;
+        this.resolvable = resolvable;
+        this.description = description;
+        this.id = newId(name, name, "1.0");
+    }
+
+    public ModuleComponentIdentifier getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isResolvable() {
+        return resolvable;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        return children;
+    }
+}
\ No newline at end of file
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
index 9a37f3a..003725d 100644
--- 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
@@ -16,28 +16,28 @@
 
 package org.gradle.api.tasks.diagnostics.internal.insight
 
-import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
+import org.gradle.api.artifacts.result.ComponentSelectionReason
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ExactVersionMatcher
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionMatcher
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.api.internal.artifacts.result.DefaultResolvedComponentResult
 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 {
+    VersionMatcher versionMatcher = new ExactVersionMatcher()
 
     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);
+        def sorted = new DependencyInsightReporter().prepare(dependencies, versionMatcher);
 
         then:
         sorted.size() == 5
@@ -62,7 +62,7 @@ class DependencyInsightReporterSpec extends Specification {
         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);
+        def sorted = new DependencyInsightReporter().prepare(dependencies, versionMatcher);
 
         then:
         sorted.size() == 4
@@ -84,7 +84,7 @@ class DependencyInsightReporterSpec extends Specification {
         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);
+        def sorted = new DependencyInsightReporter().prepare(dependencies, versionMatcher);
 
         then:
         sorted.size() == 3
@@ -99,10 +99,10 @@ class DependencyInsightReporterSpec extends Specification {
         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),
+    private dep(String group, String name, String requested, String selected = requested, ComponentSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
+        def selectedModule = new DefaultResolvedComponentResult(newId(group, name, selected), selectionReason, new DefaultModuleComponentIdentifier(group, name, selected))
+        new DefaultResolvedDependencyResult(DefaultModuleComponentSelector.newSelector(group, name, requested),
                 selectedModule,
-                new DefaultResolvedModuleVersionResult(newId("a", "root", "1")))
+                new DefaultResolvedComponentResult(newId("a", "root", "1"), VersionSelectionReasons.REQUESTED, new DefaultModuleComponentIdentifier(group, name, selected)))
     }
 }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy
index 4d49297..9dae2b8 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy
@@ -16,68 +16,212 @@
 
 package org.gradle.api.tasks.diagnostics.internal.insight
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentSelector
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentIdentifier
+import org.gradle.api.internal.artifacts.component.DefaultProjectComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy
 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.DependencyEdge
 import spock.lang.Specification
+import spock.lang.Unroll
 
-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 DependencyResultSorterSpec extends Specification {
-    def "sorts by requested version and prefers exact matching selected version over inexact"() {
-        def d1 = newDependency(newSelector("org.aha", "aha", "1.0"), newId("org.gradle", "zzzz", "3.0"))
+    def matcher = new ResolverStrategy().versionMatcher
 
-        def d2 = newDependency(newSelector("org.gradle", "core", "2.0"), newId("org.gradle", "core", "2.0"))
-        def d3 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
-        def d4 = newDependency(newSelector("org.gradle", "core", "1.5"), newId("org.gradle", "core", "2.0"))
+    @Unroll
+    def "throws exception if dependencyt or requested component selector is null (#d1, #d2)"() {
+        when:
+        DependencyResultSorter.sort([d1, d2], matcher)
 
-        def d5 = newDependency(newSelector("org.gradle", "xxxx", "1.0"), newId("org.gradle", "xxxx", "1.0"))
+        then:
+        Throwable t = thrown(IllegalArgumentException)
+        t.message == "Dependency edge or the requested component selector may not be null"
+
+        where:
+        d1           | d2
+        null         | null
+        null         | newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0")) | null
+        newDependency(null, DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0")) | newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0")) | newDependency(null, DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+    }
 
-        def d6 = newDependency(newSelector("org.gradle", "zzzz", "1.5"), newId("org.gradle", "zzzz", "3.0"))
-        def d7 = newDependency(newSelector("org.gradle", "zzzz", "2.0"), newId("org.gradle", "zzzz", "3.0"))
+    def "sorts by comparing ProjectComponentSelector on left and ModuleComponentSelector on right"() {
+        def d1 = newDependency(new DefaultProjectComponentSelector(":hisProject"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
 
         when:
-        def sorted = DependencyResultSorter.sort([d5, d3, d6, d1, d2, d7, d4])
+        def sorted = DependencyResultSorter.sort([d1, d2], matcher)
 
         then:
-        sorted == [d1, d2, d3, d4, d5, d6, d7]
+        sorted == [d1, d2]
     }
 
-    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"))
+    def "sorts by comparing ModuleComponentSelector on left and ProjectComponentSelector on right"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        def d2 = newDependency(new DefaultProjectComponentSelector(":hisProject"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
 
         when:
-        def sorted = DependencyResultSorter.sort([d1, d2])
+        def sorted = DependencyResultSorter.sort([d1, d2], matcher)
 
         then:
         sorted == [d2, d1]
     }
 
-    def "sorts by from when requested version is the same"() {
-        def d1 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.a", "a", "1.0"))
-        def d2 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "a", "1.0"))
-        def d3 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "b", "0.8"))
-        def d4 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "b", "1.12"))
-        def d5 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "b", "2.0"))
-        def d6 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "c", "0.9"))
-        def d7 = newDependency(newSelector("org.gradle", "other", "1.0"), newId("org.gradle", "other", "1.0"), newId("org.b", "a", "0.9"))
-        def d8 = newDependency(newSelector("org.gradle", "other", "1.0"), newId("org.gradle", "other", "1.0"), newId("org.b", "a", "0.9.1"))
+    def "sorts by requested ModuleComponentSelector by version"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.aha", "aha", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "0.8"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d3 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d4 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.5"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+
+        def d5 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "xxxx", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "xxxx", "1.0"))
+
+        def d6 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "zzzz", "1.5"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        def d7 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "zzzz", "2.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d5, d3, d6, d1, d2, d7, d4], matcher)
+
+        then:
+        sorted == [d1, d2, d3, d4, d5, d6, d7]
+    }
+
+    def "for a given module prefers dependency where selected exactly matches requested"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "2.2"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.2"))
+        def d3 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d4 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.5"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d5 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "3.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.2"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d3, d1, d5, d2, d4], matcher)
+
+        then:
+        sorted == [d1, d2, d3, d4, d5]
+    }
+
+    def "semantically compares versions for ModuleComponentSelector"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "0.8"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0-alpha"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d3 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d4 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.2"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d5 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.11"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d6 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.11.2"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d7 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.11.11"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d8 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "2"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d4, d7, d1, d6, d8, d5, d3, d2], matcher)
+
+        then:
+        sorted == [d1, d2, d3, d4, d5, d6, d7, d8]
+    }
+
+    def "orders a mix of dynamic and static versions for ModuleComponentSelector"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "not-a-dynamic-selector"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d3 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "0.8"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d4 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d5 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "(,2.0]"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d6 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.2+"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d7 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "[1.2,)"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d8 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "latest.integration"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d9 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "latest.zzz"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
 
         when:
-        def sorted = DependencyResultSorter.sort([d7, d8, d1, d3, d5, d2, d4, d6])
+        def sorted = DependencyResultSorter.sort([d4, d7, d1, d6, d8, d5, d3, d9, d2], matcher)
+
+        then:
+        sorted == [d1, d2, d3, d4, d5, d6, d7, d8, d9]
+    }
+
+    def "sorts by from when requested ModuleComponentSelector version is the same"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.a", "a", "1.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.b", "a", "1.0"))
+        def d3 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.b", "b", "0.8"))
+        def d4 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.b", "b", "1.12"))
+        def d5 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.b", "b", "2.0"))
+        def d6 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"), DefaultModuleComponentIdentifier.newId("org.b", "c", "0.9"))
+        def d7 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "other", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "other", "1.0"), DefaultModuleComponentIdentifier.newId("org.b", "a", "0.9"))
+        def d8 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "other", "1.0"), DefaultModuleComponentIdentifier.newId("org.gradle", "other", "1.0"), DefaultModuleComponentIdentifier.newId("org.b", "a", "0.9.1"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d7, d8, d1, d3, d5, d2, d4, d6], matcher)
 
 
         then:
         sorted == [d1, d2, d3, d4, d5, d6, d7, d8]
     }
 
-    private newDependency(ModuleVersionSelector requested, ModuleVersionIdentifier selected, ModuleVersionIdentifier from = newId("org", "a", "1.0")) {
+    def "sorts by requested ProjectComponentSelector by project path"() {
+        def d1 = newDependency(new DefaultProjectComponentSelector(":hisProject"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+
+        def d2 = newDependency(new DefaultProjectComponentSelector(":myPath"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d3 = newDependency(new DefaultProjectComponentSelector(":newPath"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+        def d4 = newDependency(new DefaultProjectComponentSelector(":path2:path6"), DefaultModuleComponentIdentifier.newId("org.gradle", "core", "2.0"))
+
+        def d5 = newDependency(new DefaultProjectComponentSelector(":path3:path2"), DefaultModuleComponentIdentifier.newId("org.gradle", "xxxx", "1.0"))
+
+        def d6 = newDependency(new DefaultProjectComponentSelector(":project2"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        def d7 = newDependency(new DefaultProjectComponentSelector(":project5"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d5, d3, d6, d1, d2, d7, d4], matcher)
+
+        then:
+        sorted == [d1, d2, d3, d4, d5, d6, d7]
+    }
+
+    def "sorts by from for just ProjectComponentIdentifiers"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project6"), DefaultProjectComponentIdentifier.newId(":project2"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project5"), DefaultProjectComponentIdentifier.newId(":project1"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d1, d2], matcher)
+
+        then:
+        sorted == [d2, d1]
+    }
+
+    def "sorts by from for just ModuleComponentIdentifiers"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project5"), DefaultModuleComponentIdentifier.newId("org.gradle", "zzzz", "3.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project6"), DefaultModuleComponentIdentifier.newId("org.gradle", "xxxx", "1.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d1, d2], matcher)
+
+        then:
+        sorted == [d2, d1]
+    }
+
+    def "sorts by from for left ProjectComponentIdentifier and right ModuleComponentIdentifier"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project5"), DefaultProjectComponentIdentifier.newId(":project1"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project6"), DefaultModuleComponentIdentifier.newId("org.gradle", "xxxx", "1.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d1, d2], matcher)
+
+        then:
+        sorted == [d1, d2]
+    }
+
+    def "sorts by from for left ModuleComponentIdentifier and right ProjectComponentIdentifier"() {
+        def d1 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project6"), DefaultModuleComponentIdentifier.newId("org.gradle", "xxxx", "1.0"))
+        def d2 = newDependency(DefaultModuleComponentSelector.newSelector("org.gradle", "core", "1.0"), DefaultProjectComponentIdentifier.newId(":project5"), DefaultProjectComponentIdentifier.newId(":project1"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d1, d2], matcher)
+
+        then:
+        sorted == [d2, d1]
+    }
+
+    private newDependency(ComponentSelector requested, ComponentIdentifier selected, ComponentIdentifier from = DefaultModuleComponentIdentifier.newId("org", "a", "1.0")) {
         return Stub(DependencyEdge) {
+            toString() >> "$requested -> $selected"
             getRequested() >> requested
             getActual() >> selected
             getFrom() >> from
diff --git a/subprojects/distributions/distributions.gradle b/subprojects/distributions/distributions.gradle
index 00ccf8f..18c38d3 100644
--- a/subprojects/distributions/distributions.gradle
+++ b/subprojects/distributions/distributions.gradle
@@ -30,21 +30,13 @@ tasks.withType(AbstractArchiveTask) {
 }
 
 dependencies {
-    groovy libraries.groovy
+    testCompile libraries.groovy
 }
 
 configurations {
     dists
 }
 
-integTestTasks.all {
-    if (project.hasProperty("noDistTests")) {
-        gradle.startParameter.excludedTaskNames << it.path
-    } else {
-        inputs.files buildDists
-    }
-}
-
 daemonIntegTest.enabled = false
 
 evaluationDependsOn ":docs"
@@ -68,15 +60,17 @@ ext {
         into('lib') {
             from rootProject.configurations.runtime
             into('plugins') {
-                from rootProject.configurations.plugins - rootProject.configurations.runtime
+                from rootProject.configurations.gradlePlugins - rootProject.configurations.runtime
             }
         }
     }
 
     allDistImage = copySpec {
         with binDistImage
-        into('src') {
-            from groovyProjects.collect {project -> project.sourceSets.main.allSource }
+        groovyProjects.each { project ->
+            into("src/$project.projectDir.name") {
+                from project.sourceSets.main.allSource
+            }
         }
         into('docs') {
             from project(':docs').outputs.docs
@@ -133,3 +127,7 @@ task outputsZip(type: Zip) {
 artifacts {
     dists allZip, binZip, srcZip
 }
+
+integTest {
+    inputs.files allZip, binZip, srcZip
+}
\ No newline at end of file
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
index b954efc..b5f581e 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
@@ -36,10 +36,11 @@ class AllDistributionIntegrationSpec extends DistributionIntegrationSpec {
         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()
+        contentsDir.file('src').eachFile { TestFile file -> file.assertIsDir() }
+        contentsDir.file('src/core/org/gradle/api/Project.java').assertIsFile()
+        contentsDir.file('src/core/org/gradle/initialization/buildsrc/defaultBuildSourceScript.txt').assertIsFile()
+        contentsDir.file('src/ui/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java').assertIsFile()
+        contentsDir.file('src/wrapper/org/gradle/wrapper/WrapperExecutor.java').assertIsFile()
 
         // Samples
         contentsDir.file('samples/java/quickstart/build.gradle').assertIsFile()
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
index 4233b87..1037dc6 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
@@ -45,7 +45,7 @@ abstract class DistributionIntegrationSpec extends AbstractIntegrationSpec {
         when:
         def entries = zipFile.entries().toList()
         def entriesByPath = entries.groupBy { ZipEntry zipEntry -> zipEntry.name }
-        def dupes = entriesByPath.findAll() { it.value.size() > 1 }
+        def dupes = entriesByPath.findAll { it.value.size() > 1 && !it.key.contains('/META-INF/services/') }
         def dupesWithCount = dupes.collectEntries { [it.key, it.value.size()]}
 
         then:
@@ -76,11 +76,8 @@ abstract class DistributionIntegrationSpec extends AbstractIntegrationSpec {
 
         // Core libs
         def coreLibs = contentsDir.file("lib").listFiles().findAll { it.name.startsWith("gradle-") }
-        assert coreLibs.size() == 11
+        assert coreLibs.size() == 13
         coreLibs.each { assertIsGradleJar(it) }
-        def wrapperJar = contentsDir.file("lib/gradle-wrapper-${version}.jar")
-        wrapperJar.assertIsFile()
-        assert wrapperJar.length() < 20 * 1024; // wrapper needs to be small. Let's check it's smaller than some arbitrary 'small' limit
 
         def toolingApiJar = contentsDir.file("lib/gradle-tooling-api-${version}.jar")
         toolingApiJar.assertIsFile()
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
index be98e7a..892972d 100755
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -18,6 +18,7 @@ import org.gradle.build.docs.UserGuideTransformTask
 import org.gradle.build.docs.ExtractSnippetsTask
 import org.gradle.build.docs.AssembleSamplesDocTask
 import org.gradle.build.docs.Docbook2Xhtml
+import org.gradle.build.docs.dsl.source.GenerateDefaultImportsTask
 import org.gradle.build.docs.dsl.docbook.AssembleDslDocTask
 import org.gradle.build.docs.dsl.source.ExtractDslMetaDataTask
 import org.gradle.build.docs.releasenotes.*
@@ -40,12 +41,16 @@ repositories {
             ivy '[organisation]/v[revision]/ivy(.[ext])'
         }
     }
+
+    maven { url 'http://repo.gradle.org/gradle/gradle-build-internal' }
 }
 
 configurations {
+    groovydocGroovy {}
     userGuideStyleSheets
     userGuideTask
     jquery
+    jqueryTipTip
     fonts
 }
 
@@ -58,6 +63,7 @@ dependencies {
 
     userGuideStyleSheets 'docbook:docbook-xsl:1.75.2 at zip'
     jquery "jquery:jquery.min:1.8.0 at js"
+    jqueryTipTip "com.drewwilson.code:jquery.tipTip:1.3:minified at js"
 
     fonts \
         "lato:regular:6:v0SdcGFAl2aezM9Vq_aFTQ at ttf",
@@ -69,7 +75,9 @@ dependencies {
         "ubuntumono:bold:3:ceqTZGKHipo8pJj4molytp_TkvowlIOtbR7ePgFOpF4 at ttf",
         "ubuntumono:bold-italic:3:n_d8tv_JOIiYyMXR4eaV9WsGzsqhEorxQDpu60nfWEc at ttf"
 
-    groovy libraries.groovy
+    groovydocGroovy libraries.groovy
+
+    testCompile libraries.groovy
     testCompile "org.pegdown:pegdown:1.1.0"
     testCompile libraries.jsoup
     testCompile "org.gebish:geb-spock:0.9.0-RC-1"
@@ -154,10 +162,14 @@ ext.cssFiles = fileTree(css.destinationDir) {
 task samples(type: ExtractSnippetsTask) {
     source samplesSrcDir
     exclude 'userguideOutput/**'
-    exclude 'userguide/tutorial/antLoadfileResources/**'
     exclude '**/readme.xml'
     exclude '**/build/**'
     exclude '**/.gradle/**'
+
+    // Resources that should not be filtered
+    exclude 'userguide/tutorial/antLoadfileResources/**'
+    exclude 'native-binaries/cunit/lib/**'
+
     destDir = samplesDir
     snippetsDir = new File(buildDir, 'snippets')
     doLast {
@@ -165,6 +177,7 @@ task samples(type: ExtractSnippetsTask) {
             from samplesSrcDir
             into samplesDir
             include 'userguide/tutorial/antLoadfileResources/**'
+            include 'native-binaries/cunit/lib/**'
         }
     }
 }
@@ -214,6 +227,24 @@ task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook])
     dsldocUrl = '.'
 }
 
+task defaultImports(type: GenerateDefaultImportsTask, dependsOn: dslMetaData) {
+    metaDataFile = dslMetaData.destFile
+    destFile = new File(generatedResourcesDir, "default-imports.txt")
+    // These are part of the API, but not the DSL
+    excludePackage 'org.gradle.tooling.**'
+    excludePackage 'org.gradle.testfixtures.**'
+
+    // Tweak the imports due to some inconsistencies introduced before we automated the default-imports generation
+    excludePackage 'org.gradle.plugins.ide.eclipse.model'
+    excludePackage 'org.gradle.plugins.ide.idea.model'
+    excludePackage 'org.gradle.api.tasks.testing.logging'
+    extraPackage 'org.gradle.util'
+
+    // TODO - rename some incubating types to remove collisions and then remove these exclusions
+    excludePackage 'org.gradle.plugins.binaries.model'
+    excludePackage 'org.gradle.ide.cdt.model'
+}
+
 task dslHtml(type: Docbook2Xhtml) {
     source dslStandaloneDocbook
     destDir = new File(docsDir, 'dsl')
@@ -224,12 +255,12 @@ task dslHtml(type: Docbook2Xhtml) {
 }
 
 // This is used in the distribution and for the online version
-task userguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
+task userguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook, defaultImports]) {
     destFile = new File(docbookSrc, 'userguide.xml')
 }
 
 // This is used for the PDF, where we need absolute links to the javadoc etc.
-task pdfUserguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
+task pdfUserguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook, defaultImports]) {
     destFile = new File(docbookSrc, 'remoteUserguide.xml')
 }
 
@@ -293,10 +324,15 @@ task javadocAll(type: Javadoc) {
     include 'org/gradle/api/**'
     include 'org/gradle/*'
     include 'org/gradle/external/javadoc/**'
+    include 'org/gradle/language/**'
+    include 'org/gradle/ide/**'
+    include 'org/gradle/nativebinaries/**'
     include 'org/gradle/process/**'
     include 'org/gradle/plugins/**'
     include 'org/gradle/testfixtures/**'
     include 'org/gradle/tooling/**'
+    include 'org/gradle/testing/jacoco/**'
+    include 'org/gradle/buildinit/**'
     exclude '**/internal/**'
     options.links(javaApiUrl, groovyApiUrl, "http://maven.apache.org/ref/2.2.1/maven-core/apidocs",
         "http://maven.apache.org/ref/2.2.1/maven-model/apidocs")
@@ -329,7 +365,7 @@ task groovydocAll(type: Groovydoc, dependsOn: configureGroovydocAll) {
         windowTitle = "Gradle API $version"
         docTitle = windowTitle
     }
-    groovyClasspath = project(':core').configurations.groovy
+    groovyClasspath = configurations.groovydocGroovy
     doLast {
         def index = new File(destinationDir, "index.html")
         index.text = index.text.replace("{todo.title}", windowTitle) // workaround groovydoc bug
@@ -343,7 +379,7 @@ task checkstyleApi(type: Checkstyle) {
     reports.xml.destination = file("$checkstyle.reportsDir/checkstyle-api.xml")
 }
 
-task userguideFragmentSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples]) {
+task userguideFragmentSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets]) {
     tags << 'standalone'
     sourceFile = new File(userguideSrcDir, 'installation.xml')
     destFile = new File(docbookSrc, 'installation.xml')
@@ -392,6 +428,8 @@ task viewReleaseNotes(dependsOn: releaseNotes) {
     }
 }
 
+sourceSets.main.output.dir generatedResourcesDir, builtBy: defaultImports
+
 test {
     dependsOn releaseNotes
     systemProperty "org.gradle.docs.releasenotes.source", releaseNotesMarkdown.source.singleFile
@@ -399,10 +437,6 @@ test {
 
 }
 
-if (project.hasProperty('noDocsTests')) {
-    gradle.startParameter.excludedTaskNames << test.path
-}
-
 task docs {
     dependsOn javadocAll, groovydocAll, userguide, distDocs, samplesDocs, dslHtml, releaseNotes
     description = 'Generates all documentation'
diff --git a/subprojects/docs/src/docs/css/release-notes.css b/subprojects/docs/src/docs/css/release-notes.css
index 476dd9b..1c41eaa 100644
--- a/subprojects/docs/src/docs/css/release-notes.css
+++ b/subprojects/docs/src/docs/css/release-notes.css
@@ -42,30 +42,25 @@ button.display-toggle {
   cursor: pointer;
 }
 
-h3.incubating {
-    display: inline-block;
-}
-
 a.incubating-marker {
-    display: inline-block;
-    clear: right;
+    display: inline;
     color: white;
     font-style: italic;
-    font-size: 1.0em;
-    text-shadow: 0 0 1px rgba(0, 0, 0, 0.4);
+    font-size: 0.6em;
+    text-shadow: none;
     margin-left: 0.6em;
-    cursor: help;
     border-radius: 6px;
     background-color: rgb(160, 160, 160);
-    padding: 0 5px;
-    box-shadow: 1px 1px 3px 0 black;
-    vertical-align: text-bottom;
+    border: 1px solid rgb(150, 150, 150);
+    padding: 1px 5px;
+    box-shadow: none;
+    cursor: pointer;
+    vertical-align: 1px;
 }
 
-
 a.incubating-marker:hover {
     text-decoration: none;
-    border-bottom: none
+    border: 1px solid rgb(150, 150, 150);
 }
 
 section.footer {
@@ -124,12 +119,12 @@ section.footer {
 
 #tiptip_content {
     font-size: 13px;
-    color: #8B8B8B;
+    color: #717171;
     padding: 6px 10px;
-    border: 2px solid #8B8B8B;
+    border: 1px solid #8B8B8B;
     background-color: #FFF;
     border-radius: 8px;
-    box-shadow: 0 0 4px #acacac;
+    box-shadow: 2px 2px 4px #acacac;
     text-align: center;
 }
 
diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml
index d045910..767e49b 100644
--- a/subprojects/docs/src/docs/dsl/dsl.xml
+++ b/subprojects/docs/src/docs/dsl/dsl.xml
@@ -149,6 +149,9 @@
                 <td>org.gradle.api.tasks.SourceSetOutput</td>
             </tr>
             <tr>
+                <td>org.gradle.api.tasks.incremental.IncrementalTaskInputs</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.artifacts.Configuration</td>
             </tr>
             <tr>
@@ -167,6 +170,18 @@
                 <td>org.gradle.api.publish.PublishingExtension</td>
             </tr>
             <tr>
+                <td>org.gradle.api.reporting.Report</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.reporting.Reporting</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.reporting.ReportContainer</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.reporting.ReportingExtension</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.publish.ivy.IvyPublication</td>
             </tr>
             <tr>
@@ -256,9 +271,6 @@
                 <td>org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds</td>
             </tr>
             <tr>
-                <td>org.gradle.api.tasks.compile.Compile</td>
-            </tr>
-            <tr>
                 <td>org.gradle.api.tasks.Copy</td>
             </tr>
             <tr>
@@ -298,6 +310,15 @@
                 <td>org.gradle.api.tasks.javadoc.Groovydoc</td>
             </tr>
             <tr>
+                <td>org.gradle.api.reporting.dependencies.HtmlDependencyReportTask</td>
+            </tr>
+            <tr>
+                <td>org.gradle.testing.jacoco.tasks.JacocoReport</td>
+            </tr>
+            <tr>
+                <td>org.gradle.testing.jacoco.tasks.JacocoMerge</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.bundling.Jar</td>
             </tr>
             <tr>
@@ -337,6 +358,9 @@
                 <td>org.gradle.api.tasks.scala.ScalaDoc</td>
             </tr>
             <tr>
+                <td>org.gradle.buildinit.tasks.InitBuild</td>
+            </tr>
+            <tr>
                 <td>org.gradle.plugins.signing.Sign</td>
             </tr>
             <tr>
@@ -448,4 +472,120 @@
         </table>
     </section>
 
+    <section>
+        <title>Native binary core types</title>
+        <para>Used to configure components developed with native code.</para>
+        <table>
+            <title>Native component core types</title>
+            <tr>
+                <td>org.gradle.nativebinaries.ProjectNativeComponent</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.Executable</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.Library</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.NativeBinary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.ProjectNativeBinary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.ExecutableBinary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.SharedLibraryBinary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.StaticLibraryBinary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.BuildType</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.Flavor</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.platform.Platform</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.toolchain.ToolChain</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.toolchain.Gcc</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.toolchain.Clang</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.toolchain.VisualCpp</td>
+            </tr>
+            <tr>
+                <td>org.gradle.language.assembler.AssemblerSourceSet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.language.c.CSourceSet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.language.cpp.CppSourceSet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.language.objectivec.ObjectiveCSourceSet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.language.objectivecpp.ObjectiveCppSourceSet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.language.rc.WindowsResourceSet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.ide.visualstudio.VisualStudioProject</td>
+            </tr>
+            <tr>
+                <td>org.gradle.ide.visualstudio.VisualStudioSolution</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.test.cunit.CUnitTestSuite</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Native binary task types</title>
+        <para>Tasks used to build native binary components.</para>
+        <table>
+            <title>Native component task types</title>
+            <tr>
+                <td>org.gradle.nativebinaries.language.cpp.tasks.CppCompile</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.language.c.tasks.CCompile</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.language.assembler.tasks.Assemble</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.language.rc.tasks.WindowsResourceCompile</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.tasks.LinkExecutable</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.tasks.LinkSharedLibrary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.tasks.CreateStaticLibrary</td>
+            </tr>
+            <tr>
+                <td>org.gradle.nativebinaries.tasks.InstallExecutable</td>
+            </tr>
+        </table>
+    </section>
 </book>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.Task.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.Task.xml
index 82369a8..7388dcc 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.Task.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.Task.xml
@@ -44,6 +44,12 @@
                 <td>dependsOn</td>
             </tr>
             <tr>
+                <td>mustRunAfter</td>
+            </tr>
+            <tr>
+                <td>finalizedBy</td>
+            </tr>
+            <tr>
                 <td>state</td>
             </tr>
             <tr>
@@ -75,6 +81,12 @@
                 <td>dependsOn</td>
             </tr>
             <tr>
+                <td>mustRunAfter</td>
+            </tr>
+            <tr>
+                <td>finalizedBy</td>
+            </tr>
+            <tr>
                 <td>onlyIf</td>
             </tr>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml
index 635cd25..1acf8f3 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml
@@ -7,6 +7,9 @@
                     <td>Name</td>
                 </tr>
             </thead>
+            <tr>
+                <td>components</td>
+            </tr>
         </table>
     </section>
     <section>
@@ -35,6 +38,9 @@
             <tr>
                 <td>localGroovy</td>
             </tr>
+            <tr>
+                <td>components</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml
index 27fabf5..74a90e7 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml
@@ -46,6 +46,9 @@
                 <td>mavenCentral</td>
             </tr>
             <tr>
+                <td>jcenter</td>
+            </tr>
+            <tr>
                 <td>mavenLocal</td>
             </tr>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml
index 98ddc78..d9912bb 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml
@@ -16,6 +16,10 @@
                 <td><literal>null</literal></td>
             </tr>
             <tr>
+                <td>applicationDefaultJvmArgs</td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
                 <td>applicationDistribution</td>
                 <td>A copy spec that; includes all of the contents of <literal>src/dist</literal>, copies the start scripts into <literal>bin</literal>, and copies the built jar and all dependencies into <literal>lib</literal></td>
             </tr>
@@ -29,4 +33,4 @@
             </thead>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.AbstractJettyRunTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.AbstractJettyRunTask.xml
index 91bf7f4..96aef57 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.AbstractJettyRunTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.AbstractJettyRunTask.xml
@@ -33,6 +33,14 @@
                 <td><literal>project.httpPort</literal></td>
             </tr>
             <tr>
+                <td>reload</td>
+                <td><literal>"automatic"</literal></td>
+            </tr>
+            <tr>
+                <td>scanIntervalSeconds</td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
                 <td>additionalRuntimeJars</td>
                 <td><literal>[]</literal></td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.JettyRunWar.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.JettyRunWar.xml
index 2f70fd1..5fea4a2 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.JettyRunWar.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.jetty.JettyRunWar.xml
@@ -12,6 +12,14 @@
                 <td>webApp</td>
                 <td><literal>project.war.archivePath</literal></td>
             </tr>
+            <tr>
+                <td>reload</td>
+                <td><literal>"automatic"</literal></td>
+            </tr>
+            <tr>
+                <td>scanIntervalSeconds</td>
+                <td><literal>0</literal></td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml
index 94773f1..b0babdf 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml
@@ -13,6 +13,18 @@
                 <td><literal>project.codenarc.configFile</literal></td>
             </tr>
             <tr>
+                <td>maxPriority1Violations</td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
+                <td>maxPriority2Violations</td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
+                <td>maxPriority3Violations</td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
                 <td>ignoreFailures</td>
                 <td><literal>project.codenarc.ignoreFailures</literal></td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml
index 3fdba2c..9c22989 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml
@@ -25,6 +25,21 @@
                 <td><literal>project.codeNarcConfigFile</literal></td>
             </tr>
             <tr>
+                <td>maxPriority1Violations</td>
+                <td><literal>0</literal></td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
+                <td>maxPriority2Violations</td>
+                <td><literal>0</literal></td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
+                <td>maxPriority3Violations</td>
+                <td><literal>0</literal></td>
+                <td><literal>0</literal></td>
+            </tr>
+            <tr>
                 <td>reportFormat</td>
                 <td><literal>html</literal></td>
                 <td><literal>project.codeNarcReportsFormat</literal></td>
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
index 5bf3ebb..f46d04e 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
@@ -17,9 +17,6 @@
                     <td>Name</td>
                 </tr>
             </thead>
-            <tr>
-                <td>add</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyPublication.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyPublication.xml
index 7eedb8e..8963f72 100644
--- 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
@@ -10,6 +10,21 @@
             <tr>
                 <td>descriptor</td>
             </tr>
+            <tr>
+                <td>artifacts</td>
+            </tr>
+            <tr>
+                <td>configurations</td>
+            </tr>
+            <tr>
+                <td>organisation</td>
+            </tr>
+            <tr>
+                <td>module</td>
+            </tr>
+            <tr>
+                <td>revision</td>
+            </tr>
         </table>
     </section>
     <section>
@@ -21,8 +36,17 @@
                 </tr>
             </thead>
             <tr>
+                <td>from</td>
+            </tr>
+            <tr>
                 <td>descriptor</td>
             </tr>
+            <tr>
+                <td>artifact</td>
+            </tr>
+            <tr>
+                <td>configurations</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
index c3412b4..70ff391 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
@@ -13,6 +13,15 @@
             <tr>
                 <td>artifacts</td>
             </tr>
+            <tr>
+                <td>groupId</td>
+            </tr>
+            <tr>
+                <td>artifactId</td>
+            </tr>
+            <tr>
+                <td>version</td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml
index ad43461..6d74ac4 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml
@@ -24,9 +24,6 @@
                 </tr>
             </thead>
             <tr>
-                <td>inputReportsFiles</td>
-            </tr>
-            <tr>
                 <td>reports</td>
             </tr>
         </table>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.Report.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.Report.xml
new file mode 100644
index 0000000..2bfd69f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.Report.xml
@@ -0,0 +1,37 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>destination</td>
+            </tr>
+            <tr>
+                <td>displayName</td>
+            </tr>
+            <tr>
+                <td>name</td>
+            </tr>
+            <tr>
+                <td>outputType</td>
+            </tr>
+            <tr>
+                <td>enabled</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.reporting.ReportContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.ReportContainer.xml
new file mode 100644
index 0000000..933a075
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.ReportContainer.xml
@@ -0,0 +1,25 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>enabled</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.reporting.Reporting.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.Reporting.xml
new file mode 100644
index 0000000..a154f34
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.Reporting.xml
@@ -0,0 +1,28 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.ReportingExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.ReportingExtension.xml
new file mode 100644
index 0000000..def7c35
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.ReportingExtension.xml
@@ -0,0 +1,28 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>baseDir</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>file</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.dependencies.HtmlDependencyReportTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.dependencies.HtmlDependencyReportTask.xml
new file mode 100644
index 0000000..b81cb15
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.dependencies.HtmlDependencyReportTask.xml
@@ -0,0 +1,27 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>project-report</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>projects</td>
+                <td><literal>[project]</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.AbstractCopyTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml
index 9245214..05f9422 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml
@@ -36,6 +36,10 @@
                 <td>includeEmptyDirs</td>
                 <td><literal>true</literal></td>
             </tr>
+	    <tr>
+	        <td>duplicatesStrategy</td>
+	        <td><literal>null</literal></td>
+	    </tr>
         </table>
     </section>
     <section>
@@ -73,6 +77,12 @@
             <tr>
                 <td>eachFile</td>
             </tr>
+            <tr>
+                <td>filesMatching</td>
+            </tr>
+            <tr>
+                <td>filesNotMatching</td>
+            </tr>
         </table>
     </section>
 </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.Copy.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.Copy.xml
index d01de1a..28973e0 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.Copy.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.Copy.xml
@@ -24,4 +24,4 @@
             </thead>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.GroovyRuntime.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.GroovyRuntime.xml
new file mode 100644
index 0000000..85f9881
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.GroovyRuntime.xml
@@ -0,0 +1,25 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>inferGroovyClasspath</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.ScalaRuntime.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.ScalaRuntime.xml
new file mode 100644
index 0000000..4d62cda
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.ScalaRuntime.xml
@@ -0,0 +1,31 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>inferScalaClasspath</td>
+            </tr>
+            <tr>
+                <td>findScalaJar</td>
+            </tr>
+            <tr>
+                <td>getScalaVersion</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.application.CreateStartScripts.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.application.CreateStartScripts.xml
index 84602cb..7773624 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.application.CreateStartScripts.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.application.CreateStartScripts.xml
@@ -8,6 +8,7 @@
             <tr><td>applicationName</td></tr>
             <tr><td>mainClassName</td></tr>
             <tr><td>classpath</td></tr>
+            <tr><td>defaultJvmOpts</td></tr>
             <tr><td>outputDir</td></tr>
             <tr><td>optsEnvironmentVar</td></tr>
         </table>
@@ -20,4 +21,4 @@
             </thead>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml
index 74c7ae5..29ce9d7 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml
@@ -5,7 +5,7 @@
             <thead>
                 <tr>
                     <td>Name</td>
-                    <td>Default with <literal>java</literal> plugin</td>
+                    <td>Default</td>
                 </tr>
             </thead>
             <tr>
@@ -16,6 +16,10 @@
                 <td>extension</td>
                 <td><literal>zip</literal></td>
             </tr>
+            <tr>
+                <td>zip64</td>
+                <td><literal>false</literal></td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml
index 98c4272..33dca71 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml
@@ -22,7 +22,7 @@
             </tr>
             <tr>
                 <td>groovyClasspath</td>
-                <td><literal>project.configurations.groovy</literal></td>
+                <td>Groovy library found on <literal>classpath</literal></td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.incremental.IncrementalTaskInputs.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.incremental.IncrementalTaskInputs.xml
new file mode 100644
index 0000000..0ca5d88
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.incremental.IncrementalTaskInputs.xml
@@ -0,0 +1,25 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>incremental</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>outOfDate</td></tr>
+            <tr><td>removed</td></tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.incremental.InputFile.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.incremental.InputFile.xml
new file mode 100644
index 0000000..c0c0a9f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.incremental.InputFile.xml
@@ -0,0 +1,26 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>file</td></tr>
+            <tr><td>added</td></tr>
+            <tr><td>modified</td></tr>
+            <tr><td>removed</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 2f47edc..09714bb 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
@@ -10,7 +10,7 @@
             </thead>
             <tr>
                 <td>scalaClasspath</td>
-                <td><literal>project.configurations.scalaTools</literal></td>
+                <td><literal>scala-compiler</literal> dependency matching the <literal>scala-library</literal> version found on <literal>classpath</literal></td>
             </tr>
             <tr>
                 <td>options</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml
index 313f7c1..a5d9622 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml
@@ -18,7 +18,7 @@
             </tr>
             <tr>
                 <td>scalaClasspath</td>
-                <td><literal>project.configurations.scalaTools</literal></td>
+                <td><literal>scala-compiler</literal> dependency matching the <literal>scala-library</literal> version found on <literal>classpath</literal></td>
             </tr>
             <tr>
                 <td>scalaDocOptions</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
index 923e8af..ecf3e50 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
@@ -10,7 +10,7 @@
             </thead>
             <tr>
                 <td>testClassesDir</td>
-                <td><literal>project.sourceSets.test.classesDir</literal></td>
+                <td><literal>project.sourceSets.test.output.classesDir</literal></td>
             </tr>
             <tr>
                 <td>testResultsDir</td>
@@ -25,6 +25,10 @@
                 <td><literal>project.testReportDir</literal></td>
             </tr>
             <tr>
+                <td>reports</td>
+                <td><literal>project.testReportDir</literal></td>
+            </tr>
+            <tr>
                 <td>classpath</td>
                 <td><literal>project.sourceSets.test.runtimeClasspath</literal></td>
             </tr>
@@ -69,6 +73,10 @@
                 <td><literal>null</literal></td>
             </tr>
             <tr>
+                <td>minHeapSize</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
                 <td>jvmArgs</td>
                 <td><literal>[]</literal></td>
             </tr>
@@ -182,6 +190,9 @@
                 <td>testLogging</td>
             </tr>
             <tr>
+                <td>reports</td>
+            </tr>
+            <tr>
                 <td>options</td>
             </tr>
         </table>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml
index bf35017..df692d2 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml
@@ -38,9 +38,9 @@
             </tr>
             <tr>
                 <td>distributionUrl</td>
-                <td><literal>'http\://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip'</literal>
+                <td><literal>"http\://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip"</literal>
                     (or
-                    <literal>'http\://services.gradle.org/distributions-snapshots/gradle-${gradleVersion}-bin.zip'</literal>
+                    <literal>"http\://services.gradle.org/distributions-snapshots/gradle-${gradleVersion}-bin.zip"</literal>
                     for snapshot versions).
                 </td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.buildinit.tasks.InitBuild.xml b/subprojects/docs/src/docs/dsl/org.gradle.buildinit.tasks.InitBuild.xml
new file mode 100644
index 0000000..b4cea87
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.buildinit.tasks.InitBuild.xml
@@ -0,0 +1,43 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>type</td>
+                <td><literal><replaceable>empty</replaceable> (or <replaceable>pom</replaceable> if a pom.xml file exists in the project directory)</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.ide.visualstudio.VisualStudioExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.ide.visualstudio.VisualStudioExtension.xml
new file mode 100644
index 0000000..aece588
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.ide.visualstudio.VisualStudioExtension.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>projects</td>
+            </tr>
+            <tr>
+                <td>solutions</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.ide.visualstudio.VisualStudioProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.ide.visualstudio.VisualStudioProject.xml
new file mode 100644
index 0000000..d994702
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.ide.visualstudio.VisualStudioProject.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>projectFile</td>
+            </tr>
+            <tr>
+                <td>filtersFile</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.ide.visualstudio.VisualStudioSolution.xml b/subprojects/docs/src/docs/dsl/org.gradle.ide.visualstudio.VisualStudioSolution.xml
new file mode 100644
index 0000000..47288cb
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.ide.visualstudio.VisualStudioSolution.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>projects</td>
+            </tr>
+            <tr>
+                <td>solutionFile</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.language.DependentSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.DependentSourceSet.xml
new file mode 100644
index 0000000..58c7341
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.DependentSourceSet.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>libs</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>lib</td>
+            </tr>
+            <tr>
+                <td>dependency</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.HeaderExportingSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.HeaderExportingSourceSet.xml
new file mode 100644
index 0000000..dceece0
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.HeaderExportingSourceSet.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>exportedHeaders</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>exportedHeaders</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.assembler.AssemblerSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.assembler.AssemblerSourceSet.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.assembler.AssemblerSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.base.BinaryContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.base.BinaryContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.base.BinaryContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.base.FunctionalSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.base.FunctionalSourceSet.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.base.FunctionalSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.base.LanguageSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.base.LanguageSourceSet.xml
new file mode 100644
index 0000000..46f00a0
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.base.LanguageSourceSet.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>source</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>source</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.base.ProjectSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.base.ProjectSourceSet.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.base.ProjectSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.c.CSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.c.CSourceSet.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.c.CSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.cpp.CppSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.cpp.CppSourceSet.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.cpp.CppSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.objectivec.ObjectiveCSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.objectivec.ObjectiveCSourceSet.xml
new file mode 100644
index 0000000..85af316
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.objectivec.ObjectiveCSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2014 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.objectivecpp.ObjectiveCppSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.objectivecpp.ObjectiveCppSourceSet.xml
new file mode 100644
index 0000000..85af316
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.objectivecpp.ObjectiveCppSourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2014 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.rc.WindowsResourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.rc.WindowsResourceSet.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.rc.WindowsResourceSet.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.BuildType.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.BuildType.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.BuildType.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.BuildTypeContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.BuildTypeContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.BuildTypeContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Executable.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Executable.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Executable.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ExecutableBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ExecutableBinary.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ExecutableBinary.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ExecutableContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ExecutableContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ExecutableContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Flavor.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Flavor.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Flavor.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.FlavorContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.FlavorContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.FlavorContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Library.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Library.xml
new file mode 100644
index 0000000..e02acc6
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Library.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>api</td>
+            </tr>
+            <tr>
+                <td>shared</td>
+            </tr>
+            <tr>
+                <td>static</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.nativebinaries.LibraryBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.LibraryBinary.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.LibraryBinary.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.LibraryContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.LibraryContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.LibraryContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.NativeBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.NativeBinary.xml
new file mode 100644
index 0000000..f241af4
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.NativeBinary.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>flavor</td>
+            </tr>
+            <tr>
+                <td>targetPlatform</td>
+            </tr>
+            <tr>
+                <td>buildType</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.nativebinaries.ProjectNativeBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ProjectNativeBinary.xml
new file mode 100644
index 0000000..2fbfb60
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ProjectNativeBinary.xml
@@ -0,0 +1,68 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>component</td>
+            </tr>
+            <tr>
+                <td>buildable</td>
+            </tr>
+            <tr>
+                <td>toolChain</td>
+            </tr>
+            <tr>
+                <td>source</td>
+            </tr>
+            <tr>
+                <td>libs</td>
+            </tr>
+            <tr>
+                <td>linker</td>
+            </tr>
+            <tr>
+                <td>staticLibArchiver</td>
+            </tr>
+            <tr>
+                <td>tasks</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>source</td>
+            </tr>
+            <tr>
+                <td>lib</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ProjectNativeComponent.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ProjectNativeComponent.xml
new file mode 100644
index 0000000..780bd65
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.ProjectNativeComponent.xml
@@ -0,0 +1,53 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>binaries</td>
+            </tr>
+            <tr>
+                <td>baseName</td>
+            </tr>
+            <tr>
+                <td>source</td>
+            </tr>
+            <tr>
+                <td>displayName</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>source</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.SharedLibraryBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.SharedLibraryBinary.xml
new file mode 100644
index 0000000..1ea92dd
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.SharedLibraryBinary.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>sharedLibraryFile</td>
+            </tr>
+            <tr>
+                <td>sharedLibraryLinkFile</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.nativebinaries.StaticLibraryBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.StaticLibraryBinary.xml
new file mode 100644
index 0000000..4905452
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.StaticLibraryBinary.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>staticLibraryFile</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.nativebinaries.TargetedNativeComponent.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.TargetedNativeComponent.xml
new file mode 100644
index 0000000..e81e1a1
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.TargetedNativeComponent.xml
@@ -0,0 +1,48 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>targetFlavors</td>
+            </tr>
+            <tr>
+                <td>targetBuildTypes</td>
+            </tr>
+            <tr>
+                <td>targetPlatforms</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Tool.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Tool.xml
new file mode 100644
index 0000000..91457f1
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.Tool.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>args</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>args</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.PreprocessingTool.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.PreprocessingTool.xml
new file mode 100644
index 0000000..8f16ea6
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.PreprocessingTool.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>macros</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>define</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.assembler.tasks.Assemble.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.assembler.tasks.Assemble.xml
new file mode 100644
index 0000000..f14571a
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.assembler.tasks.Assemble.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>objectFileDir</td>
+            </tr>
+            <tr>
+                <td>assemblerArgs</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>source</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompileTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompileTask.xml
new file mode 100644
index 0000000..feebdd0
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompileTask.xml
@@ -0,0 +1,68 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>positionIndependentCode</td>
+            </tr>
+            <tr>
+                <td>objectFileDir</td>
+            </tr>
+            <tr>
+                <td>macros</td>
+            </tr>
+            <tr>
+                <td>compilerArgs</td>
+            </tr>
+            <tr>
+                <td>toolChain</td>
+            </tr>
+            <tr>
+                <td>targetPlatform</td>
+            </tr>
+            <tr>
+                <td>source</td>
+            </tr>
+            <tr>
+                <td>includes</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>includes</td>
+            </tr>
+            <tr>
+                <td>source</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.c.tasks.CCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.c.tasks.CCompile.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.c.tasks.CCompile.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppExeConventionPlugin.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppExeConventionPlugin.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppExeConventionPlugin.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppLibConventionPlugin.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppLibConventionPlugin.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppLibConventionPlugin.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppPlugin.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppPlugin.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.plugins.CppPlugin.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.tasks.CppCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.tasks.CppCompile.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.cpp.tasks.CppCompile.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile.xml
new file mode 100644
index 0000000..85af316
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.objectivec.tasks.ObjectiveCCompile.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2014 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile.xml
new file mode 100644
index 0000000..85af316
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.objectivecpp.tasks.ObjectiveCppCompile.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2014 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.rc.tasks.WindowsResourceCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.rc.tasks.WindowsResourceCompile.xml
new file mode 100644
index 0000000..decd489
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.language.rc.tasks.WindowsResourceCompile.xml
@@ -0,0 +1,65 @@
+<!--
+~ Copyright 2013 the original author or authors.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~      http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT 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>toolChain</td>
+            </tr>
+            <tr>
+                <td>targetPlatform</td>
+            </tr>
+            <tr>
+                <td>outputDir</td>
+            </tr>
+            <tr>
+                <td>source</td>
+            </tr>
+            <tr>
+                <td>includes</td>
+            </tr>
+            <tr>
+                <td>macros</td>
+            </tr>
+            <tr>
+                <td>compilerArgs</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>includes</td>
+            </tr>
+            <tr>
+                <td>source</td>
+            </tr>
+        </table>
+    </section>
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.platform.Platform.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.platform.Platform.xml
new file mode 100644
index 0000000..91d2d7e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.platform.Platform.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>architecture</td>
+            </tr>
+            <tr>
+                <td>operatingSystem</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>architecture</td>
+            </tr>
+            <tr>
+                <td>operatingSystem</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.platform.PlatformContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.platform.PlatformContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.platform.PlatformContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.AbstractLinkTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.AbstractLinkTask.xml
new file mode 100644
index 0000000..b895322
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.AbstractLinkTask.xml
@@ -0,0 +1,59 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>source</td>
+            </tr>
+            <tr>
+                <td>libs</td>
+            </tr>
+            <tr>
+                <td>linkerArgs</td>
+            </tr>
+            <tr>
+                <td>outputFile</td>
+            </tr>
+            <tr>
+                <td>toolChain</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>source</td>
+            </tr>
+            <tr>
+                <td>lib</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.CreateStaticLibrary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.CreateStaticLibrary.xml
new file mode 100644
index 0000000..89a702f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.CreateStaticLibrary.xml
@@ -0,0 +1,53 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>source</td>
+            </tr>
+            <tr>
+                <td>outputFile</td>
+            </tr>
+            <tr>
+                <td>toolChain</td>
+            </tr>
+            <tr>
+                <td>staticLibArgs</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>source</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.InstallExecutable.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.InstallExecutable.xml
new file mode 100644
index 0000000..386ba3d
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.InstallExecutable.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>destinationDir</td>
+            </tr>
+            <tr>
+                <td>executable</td>
+            </tr>
+            <tr>
+                <td>libs</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>lib</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.LinkExecutable.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.LinkExecutable.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.LinkExecutable.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.LinkSharedLibrary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.LinkSharedLibrary.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.tasks.LinkSharedLibrary.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.ProjectComponentTestSuite.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.ProjectComponentTestSuite.xml
new file mode 100644
index 0000000..2424106
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.ProjectComponentTestSuite.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>testedComponent</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.nativebinaries.test.TestSuite.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuite.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuite.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuiteContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuiteContainer.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuiteContainer.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuiteExecutableBinary.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuiteExecutableBinary.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.TestSuiteExecutableBinary.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.cunit.CUnitTestSuite.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.cunit.CUnitTestSuite.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.test.cunit.CUnitTestSuite.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.Clang.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.Clang.xml
new file mode 100644
index 0000000..c2b9e7e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.Clang.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>path</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>path</td></tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.Gcc.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.Gcc.xml
new file mode 100644
index 0000000..c2b9e7e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.Gcc.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>path</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>path</td></tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.PlatformConfigurableToolChain.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.PlatformConfigurableToolChain.xml
new file mode 100644
index 0000000..90075bd
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.PlatformConfigurableToolChain.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>assembler</td></tr>
+            <tr><td>cCompiler</td></tr>
+            <tr><td>cppCompiler</td></tr>
+            <tr><td>linker</td></tr>
+            <tr><td>staticLibArchiver</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>addPlatformConfiguration</td></tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.ToolChain.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.ToolChain.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.ToolChain.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.ToolChainRegistry.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.ToolChainRegistry.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.ToolChainRegistry.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.VisualCpp.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.VisualCpp.xml
new file mode 100644
index 0000000..57d62f3
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.VisualCpp.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>installDir</td></tr>
+            <tr><td>windowsSdkDir</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.nativebinaries.toolchain.plugins.GppCompilerPlugin.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.plugins.GppCompilerPlugin.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.plugins.GppCompilerPlugin.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.plugins.MicrosoftVisualCppPlugin.xml b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.plugins.MicrosoftVisualCppPlugin.xml
new file mode 100644
index 0000000..659d719
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.nativebinaries.toolchain.plugins.MicrosoftVisualCppPlugin.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
index a5dde5f..fc85d08 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
@@ -11,7 +11,7 @@
             </thead>
             <tr>
                 <td>modules</td>
-                <td><literal>project.allprojects*idea.module</literal></td>
+                <td><literal>project.allprojects.idea.module</literal></td>
                 <td/>
             </tr>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.plugins.JacocoPluginExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.plugins.JacocoPluginExtension.xml
new file mode 100644
index 0000000..b846737
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.plugins.JacocoPluginExtension.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>toolVersion</td>
+                <td><literal>0.6.2.201302030002</literal></td>
+            </tr>
+            <tr>
+                <td>reportsDir</td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/jacoco</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>applyTo</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.plugins.JacocoTaskExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.plugins.JacocoTaskExtension.xml
new file mode 100644
index 0000000..e292057
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.plugins.JacocoTaskExtension.xml
@@ -0,0 +1,77 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>destinationFile</td>
+            </tr>
+            <tr>
+                <td>append</td>
+            </tr>
+            <tr>
+                <td>includes</td>
+            </tr>
+            <tr>
+                <td>excludes</td>
+            </tr>
+            <tr>
+                <td>excludeClassLoaders</td>
+            </tr>
+            <tr>
+                <td>sessionId</td>
+            </tr>
+            <tr>
+                <td>dumpOnExit</td>
+            </tr>
+            <tr>
+                <td>output</td>
+            </tr>
+            <tr>
+                <td>address</td>
+            </tr>
+            <tr>
+                <td>port</td>
+            </tr>
+            <tr>
+                <td>classDumpFile</td>
+            </tr>
+            <tr>
+                <td>jmx</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>getAsJvmArg</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoBase.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoBase.xml
new file mode 100644
index 0000000..5374067
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoBase.xml
@@ -0,0 +1,45 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>jacoco</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>jacocoClasspath</td>
+                <td>
+                    <literal>project.configurations.jacocoAnt</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.testing.jacoco.tasks.JacocoMerge.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoMerge.xml
new file mode 100644
index 0000000..ac2d736
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoMerge.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>jacoco</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>executionData</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>destinationFile</td>
+                <td><filename><replaceable>buildDir</replaceable>/jacoco/<replaceable>task.name</replaceable>.exec</filename></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>executionData</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml
new file mode 100644
index 0000000..82e4548
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml
@@ -0,0 +1,69 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>jacoco</literal>
+                        plugin
+                    </td>
+                </tr>
+            </thead>
+            <tr>
+                <td>executionData</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>sourceDirectories</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>classDirectories</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>additionalClassDirs</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>additionalSourceDirs</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>reports</td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>executionData</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/plugins.xml b/subprojects/docs/src/docs/dsl/plugins.xml
index 8920812..e6121ad 100755
--- a/subprojects/docs/src/docs/dsl/plugins.xml
+++ b/subprojects/docs/src/docs/dsl/plugins.xml
@@ -73,4 +73,45 @@
     <plugin id="jdepend" description="JDepend Plugin">
         <extends targetClass="org.gradle.api.Project" id="jdepend" extensionClass="org.gradle.api.plugins.quality.JDependExtension"/>
     </plugin>
+    <plugin id="jacoco" description="JaCoCo Plugin">
+        <extends targetClass="org.gradle.api.Project" id="jacoco" extensionClass="org.gradle.testing.jacoco.plugins.JacocoPluginExtension"/>
+        <extends targetClass="org.gradle.api.tasks.testing.Test" id="jacoco" extensionClass="org.gradle.testing.jacoco.plugins.JacocoTaskExtension"/>
+    </plugin>
+    <plugin id="LanguageBasePlugin" description="Language Base Plugin">
+        <extends targetClass="org.gradle.api.Project" id="binaries" extensionClass="org.gradle.language.base.BinaryContainer"/>
+        <extends targetClass="org.gradle.api.Project" id="sources" extensionClass="org.gradle.language.base.ProjectSourceSet"/>
+    </plugin>
+    <plugin id="native-binaries" description="Native Binaries Model Plugin">
+        <extends targetClass="org.gradle.api.Project" id="executables" extensionClass="org.gradle.nativebinaries.ExecutableContainer"/>
+        <extends targetClass="org.gradle.api.Project" id="libraries" extensionClass="org.gradle.nativebinaries.LibraryContainer"/>
+        <extends targetClass="org.gradle.api.Project" id="binaries" extensionClass="org.gradle.language.base.BinaryContainer"/>
+        <extends targetClass="org.gradle.api.Project" id="toolChains" extensionClass="org.gradle.nativebinaries.toolchain.ToolChainRegistry"/>
+        <extends targetClass="org.gradle.api.Project" id="buildTypes" extensionClass="org.gradle.nativebinaries.BuildTypeContainer"/>
+        <extends targetClass="org.gradle.api.Project" id="platforms" extensionClass="org.gradle.nativebinaries.platform.PlatformContainer"/>
+        <extends targetClass="org.gradle.api.Project" id="flavors" extensionClass="org.gradle.nativebinaries.FlavorContainer"/>
+    </plugin>
+    <plugin id="cpp" description="C++ Plugin">
+        <extends targetClass="org.gradle.nativebinaries.NativeBinary" id="cppCompiler" extensionClass="org.gradle.nativebinaries.language.PreprocessingTool"/>
+    </plugin>
+    <plugin id="c" description="C Plugin">
+        <extends targetClass="org.gradle.nativebinaries.NativeBinary" id="cCompiler" extensionClass="org.gradle.nativebinaries.language.PreprocessingTool"/>
+    </plugin>
+    <plugin id="assembler" description="Assembler Plugin">
+        <extends targetClass="org.gradle.nativebinaries.NativeBinary" id="assembler" extensionClass="org.gradle.nativebinaries.Tool"/>
+    </plugin>
+    <plugin id="objective-c" description="Objective-C Plugin">
+        <extends targetClass="org.gradle.nativebinaries.NativeBinary" id="objectiveCCompiler" extensionClass="org.gradle.nativebinaries.language.PreprocessingTool"/>
+    </plugin>
+    <plugin id="objective-cpp" description="Objective-C++ Plugin">
+        <extends targetClass="org.gradle.nativebinaries.NativeBinary" id="objectiveCppCompiler" extensionClass="org.gradle.nativebinaries.language.PreprocessingTool"/>
+    </plugin>
+    <plugin id="windows-resources" description="Windows Resources Plugin">
+        <extends targetClass="org.gradle.nativebinaries.NativeBinary" id="rcCompiler" extensionClass="org.gradle.nativebinaries.language.PreprocessingTool"/>
+    </plugin>
+    <plugin id="visual-studio" description="Visual Studio Plugin">
+        <extends targetClass="org.gradle.api.Project" id="visualStudio" extensionClass="org.gradle.ide.visualstudio.VisualStudioExtension"/>
+    </plugin>
+    <plugin id="cunit" description="CUnit Test Plugin">
+        <extends targetClass="org.gradle.api.Project" id="testSuites" extensionClass="org.gradle.nativebinaries.test.TestSuiteContainer"/>
+    </plugin>
 </plugins>
diff --git a/subprojects/docs/src/docs/release/content/script.js b/subprojects/docs/src/docs/release/content/script.js
index 0d6d9d3..9978212 100644
--- a/subprojects/docs/src/docs/release/content/script.js
+++ b/subprojects/docs/src/docs/release/content/script.js
@@ -1,25 +1,3 @@
-/*
- * TipTip
- * Copyright 2010 Drew Wilson
- * www.drewwilson.com
- * code.drewwilson.com/entry/tiptip-jquery-plugin
- *
- * Version 1.3   -   Updated: Mar. 23, 2010
- *
- * This Plug-In will create a custom tooltip to replace the default
- * browser tooltip. It is extremely lightweight and very smart in
- * that it detects the edges of the browser window and will make sure
- * the tooltip stays within the current window size. As a result the
- * tooltip will adjust itself to be displayed above, below, to the left
- * or to the right depending on what is necessary to stay within the
- * browser window. It is completely customizable as well via CSS.
- *
- * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
- *   http://www.opensource.org/licenses/mit-license.php
- *   http://www.gnu.org/licenses/gpl.html
- */
-(function($){$.fn.tipTip=function(options){var defaults={activation:"hover",keepAlive:false,maxWidth:"200px",edgeOffset:3,defaultPosition:"bottom",delay:400,fadeIn:200,fadeOut:200,attribute:"title",content:false,enter:function(){},exit:function(){}};var opts=$.extend(defaults,options);if($("#tiptip_holder").length<=0){var tiptip_holder=$('<div id="tiptip_holder" style="max-width:'+opts.maxWidth+';"></div>');var tiptip_content=$('<div id="tiptip_content"></div>');var tiptip_arrow=$('<div  [...]
-
 $(function() {
   function elementInViewport(el) {
     var rect = el.getBoundingClientRect();
@@ -121,11 +99,11 @@ $(function() {
     "Retrieving the known issue information for @versionBase@", 
     function(i) {
       if (i > 0) {
-        return i + " issues have been fixed in Gradle @versionBase at .";
+        return i + " issues are known to affect Gradle @versionBase at .";
       } else {
         return "There are no known issues of Gradle @versionBase@ at this time.";
       }
-    } 
+    }
   );
 
   $("section.major-detail").each(function() {
diff --git a/subprojects/docs/src/docs/release/notes-template.md b/subprojects/docs/src/docs/release/notes-template.md
index 18a21d5..438cd29 100644
--- a/subprojects/docs/src/docs/release/notes-template.md
+++ b/subprojects/docs/src/docs/release/notes-template.md
@@ -19,18 +19,6 @@ The following are the features that have been promoted in this Gradle release.
 
 ## Fixed issues
 
-## Incubating features
-
-Incubating features are intended to be used, but not yet guaranteed to be backwards compatible.
-By giving early access to new features, real world feedback can be incorporated into their design.
-See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
-
-The following are the new incubating features or changes to existing incubating features in this Gradle release.
-
-<!--
-### Example incubating feature
--->
-
 ## Deprecations
 
 Features that have become superseded or irrelevant due to the natural evolution of Gradle become *deprecated*, and scheduled to be removed
@@ -53,7 +41,7 @@ The following are the newly deprecated items in this Gradle release. If you have
 We would like to thank the following community members for making contributions to this release of Gradle.
 
 <!--
-* Some Person - fixed some issue (GRADLE-1234)
+* [Some person](https://github.com/some-person) - fixed some issue (GRADLE-1234)
 -->
 
 We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index a4aaf63..c0189d8 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -1,388 +1,311 @@
-Continuing on with the performance improvements delivered in recent Gradle versions, 1.5 brings wide reaching optimizations to dependency resolution as well as important 
-improvements to two recent features; configure-on-demand and parallel execution. 
-Gradle continues to embrace the challenges of large scale build automation. Along with the performance improvements comes the usual mix of new features, bug fixes, usability improvements and refinements. 
-
-The dependency resolve rule feature introduced in 1.4 gains new capabilities in 1.5. Trouble dependencies can now be completely substituted at resolution time which enables solving 
-some very tricky issues with complex dependency graphs. On the publication side, the new (incubating) Maven and Ivy publishing plugins gain new capabilities making them able 
-to solve more publishing use cases with elegance.
-
-A very exciting trend is the increase in code and documentation contributions from the community, both large and small. Some very useful new features such as the "build dashboard" 
-and "distributions" plugins added in this release, to name a few, came by way of contribution. If you'd like to get involved and contribute to Gradle, a great place to 
-start is [gradle.org/contribute](http://www.gradle.org/contribute).
-
-The Gradle team is also excited to announce the first ever [“Gradle Summit”](http://gradlesummit.com/) (Sponsored by [Gradleware](http://gradleware.com/)),
-held June 13th - 14th in Santa Clara, California. The summit will be two fully packed days of technical sessions given by Gradle core developers ranging from introductory 
-to deep dive as well as informational sessions by several large organizations on how they get the most out of Gradle. In between sessions there'll be plenty of opportunity 
-for talking to other Gradle users and the Gradle development team. This is an event not to miss. 
-Registration is [now open](http://gradlesummit.com/conference/santa_clara/2013/06/gradle/event_register).
-    
-Read on for more details on why you should upgrade to Gradle 1.5. As always, please share your feedback and experiences with Gradle 1.5 via the Gradle Forums.
+Gradle 1.12 brings some nice universal performance improvements, useful new features and a slew of bug and compatibility fixes.
 
-## New and noteworthy
+In the native space, improvements have been made to the Clang support as well as improved integration with Visual Studio.
+Gradle also now provides support for unit testing your native code via CUnit.
 
-Here are the new features introduced in this Gradle release.
+A considerable amount of work in this release went in to the dependency management engine, preparing for new APIs that allow more control over the dependency resolution process.
+The visible result of this work in Gradle 1.12 is the ability to specify rules that identify _changing_ components (e.g. snapshots).
 
-### Performance improvements 
+Another significant area of investment in 1.12 is in the area of IDE integration, via the [Tooling API](userguide/embedding.html).
+Integration with IntelliJ IDEA in particular has been improved, along with general improvements that will offer tooling providers more integration capabilities.
+Optimizations have also been made to the parts of the codebase that deal with IDE import, making importing Gradle 1.12 projects into IDEs faster than any previous Gradle version.
 
-This release brings performance improvements for JUnit and TestNG test execution as well as dependency resolution improvements for all builds that use dependency resolution.
+Gradle 1.12 set a new high watermark for contributions.
+It includes contributions from 16 different people.
+Contributions range from documentation improvements, to Ant import improvements, to support for building large zips,
+to Sonar improvements, to test reporting improvements and more.
+Thank you to all of the contributors.
 
-#### Test execution performance
+Gradle 1.12 will be the last release in the Gradle 1.x line.
+The next release will be Gradle 2.0.
+For information on what this means for you and for the future of Gradle, please see [the Gradle 2.0 announcement](http://forums.gradle.org/gradle/topics/after_1_12_comes_2_0).
 
-Test execution has been further optimized in this release, continuing on the work in Gradle 1.4. The test report is now generated more efficiently, so that it take less
-time and heap space to generate the HTML report. In addition, for those projects that use the TestNG framework, the test JVM now starts more quickly, meaning that test
-execution starts earlier than it did in previous Gradle releases.
+## New and noteworthy
 
-#### Dependency resolution performance
+Here are the new features introduced in this Gradle release.
 
-Gradle's [dependency cache](userguide/dependency_management.html#sec:dependency_cache) is multi process safe, which requires the use of locking mechanisms.
-Improvements to the way the locks are utilised in this release have increased the dependency resolution speed by up to 30%
-for builds that use local repositories or maven local.
-Builds that don't use local repositories should also exhibit slightly faster dependency resolution.
-Every build that resolves dependencies benefits from this improvement.
+### CUnit integration (i)
 
-### Substituting dependencies via dependency resolve rules (i)
+The new Gradle `cunit` plugin provides support for compiling and executing [CUnit](http://cunit.sourceforge.net) tests in your native-binary project.
 
-Gradle 1.4 [introduced the ability](http://www.gradle.org/docs/1.4/release-notes#dependency-resolve-rules) to dynamically change the version of a dependency to be resolved via dependency resolve rules.
-It is now possible to change the group, name and/or version of a requested dependency, allowing a dependency to be substituted with a completely
-different dependency during resolution.
+You simply need to include your CUnit test sources, and provide a hook for Gradle to register the suites and tests defined.
 
-    configurations.all {
-        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
-            if (details.requested.name == 'groovy-all') {
-                details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
-            }
-        }
+    #include <CUnit/Basic.h>
+    #include "gradle_cunit_register.h"
+    #include "operator_tests.h"
+
+    void gradle_cunit_register() {
+        CU_pSuite mySuite = CU_add_suite("operator tests", suite_init, suite_clean);
+        CU_add_test(mySuite, "test plus", test_plus);
+        CU_add_test(mySuite, "test minus", test_minus);
     }
 
-Dependency resolve rules can now be used to solve some interesting dependency resolution problems:
+Gradle will then generate the required boiler-plate CUnit code, build a test executable, and run your tests.
 
-- Substituting an alternative implementation for some module. For example, replace all usages of `log4j` with a compatible version of `log4j-over-slf4j`.
-- Dealing with conflicting implementations of some module. For example, replace all usages of the various `slf4j` bindings with `slf4j-simple`.
-- Dealing with conflicting packaging of some module. For example, replace all usages of `groovy-all` with `groovy`.
-- Dealing with modules that have changed their (group, module) identifier. For example, replace `ant:ant:*` with `org.apache.ant:ant:1.7.0` and let conflict resolution take care of the rest.
-- Substituting different implementations at different stages. For example, substitute all servlet API dependencies with `'javax.servlet:servlet-api:2.4'` at compile time and the jetty implementation at test runtime.
+<pre><tt>> gradle -q runFailingOperatorsTest
 
-For more information, including more code samples, please refer to [the user guide](userguide/dependency_management.html#sec:dependency_resolve_rules).
+There were test failures:
+  1. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c:6  - plus(0, -2) == -2
+  2. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c:7  - plus(2, 2) == 4
 
-### New Sonar Runner plugin (i)
+BUILD FAILED</tt></pre>
 
-Gradle 1.5 ships with a new `sonar-runner` plugin that is set to replace the existing Sonar plugin. As its name indicates,
-the new plugin is based on the [Sonar Runner](http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner),
-the new and official way to integrate with Sonar. Unlike the old Sonar plugin, the new Sonar Runner plugin
-is compatible with the latest Sonar versions (3.4 and above). To learn more, check out the [Sonar Runner Plugin](userguide/sonar_runner_plugin.html)
-chapter in the Gradle user guide, and the `sonarRunner` samples in the full Gradle distribution.
+See the [user guide chapter](userguide/nativeBinaries.html#native_binaries:cunit) and the cunit sample (`samples/native-binaries/cunit`)
+in the distribution to learn more. Expect deeper integration with CUnit (and other native testing tools) in the future.
 
-### IDEA plugin is now Scala aware
+### Component metadata rules can control whether a component version is considered changing (i)
 
-When the IDEA plugin encounters a Scala project, it will now add additional configuration to make the
-project compile in IntelliJ IDEA out of the box. In particular, the plugin adds a Scala facet and
-a Scala compiler library that matches the Scala version used on the project's class path.
+Component metadata rules ([introduced in Gradle 1.8](http://www.gradle.org/docs/1.8/release-notes#component-metadata-rules)) can now be used to specify whether a component version is considered _changing_.
 
-### Configure-on-demand improvements (i)
+A _changing_ component is expected to change over time without a change to the version number.
+A commonly used and well understood example of a changing component is a “`-SNAPSHOT`” dependency from an Apache Maven repository (which Gradle implicitly considers to be changing).
 
-Gradle 1.4 introduced a [new operational mode called “configure-on-demand”](http://www.gradle.org/docs/1.4/release-notes#improved-scalability-via-configuration-on-demand) designed
-to improve Gradle performance on large projects.  This release brings the following improvements to this new feature:
+This new feature makes it possible to implement custom strategies for deciding if a component version is changing.
+In the following example, every component version whose group is “`my.company`” and whose version number ends in “`-dev`” will be considered changing:
 
-* [Tooling API](userguide/embedding.html) compatibility.
-* [buildSrc](userguide/organizing_build_logic.html#sec:build_sources) is now fully supported.
-* Task dependencies declared via task path are now supported.
+    dependencies {
+        components {
+            eachComponent { ComponentMetadataDetails details ->
+                details.changing =
+                    details.id.group == "my.company" &&
+                        details.id.version.endsWith("-dev")
+            }
+        }
+    }
 
-Enabling this new mode is now more convenient. It can be enabled at invocation time via the new `--configure-on-demand` flag, or via the `org.gradle.configureondemand` project property.
-The project property can be set permanently for a project, or permanently for a user just like other [build settings](userguide/build_environment.html#sec:gradle_configuration_properties).
+This feature is especially useful when dealing with Ivy repositories, as it is a generalized form of Ivy's `changingPattern` concept.
 
-For example, by adding a `gradle.properties` file to root of the project with the following content Gradle will always use configure-on-demand mode for the project.
+See [ComponentMetadataHandler](javadoc/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.html) for more information.
 
-    #gradle.properties file
-    org.gradle.configureondemand=true
+### Tooling API exposes information on a project's publications (i)
 
-For more information on configure-on-demand please consult [the user guide](userguide/multi_project_builds.html#sec:configuration_on_demand).
+The [Tooling API](userguide/embedding.html) is a mechanism for embedding Gradle and/or driving Gradle programmatically.
+The new [`ProjectPublications`](javadoc/org/gradle/tooling/model/gradle/ProjectPublications.html) Tooling API model type provides basic information about a project's publications.
 
-### Parallel execution improvements (i)
+The following example demonstrates using the `ProjectPublications` model to print out the group/name/version of each publication.
 
-Gradle 1.2 introduced a [parallel execution](userguide/multi_project_builds.html#sec:parallel_execution) mode for multi-project builds.
-This release brings significantly improved utilisation of the parallel workers.
+    def projectConnection = ...
+    def publications = projectConnection.getModel(ProjectPublications)
+    for (publication in project.publications) {
+        println publication.id.group
+        println publication.id.name
+        println publication.id.version
+    }
 
-Previously, workers where statically assigned to projects and often waited for the upstream dependencies to be built.
-This caused workers to stay idle when there was work they could be doing. The distribution of work is now more dynamic which has resulted in highly parallelizable builds building up to 30% faster.
+Both publications declared in the old (`artifacts` block, `Upload` task) and new (`publishing.publications` block) way are reflected in the result.
 
-It is also now possible to enable parallel building via a [build setting](userguide/build_environment.html#sec:gradle_configuration_properties).
-For example, by adding a `gradle.properties` file to root of the project with the following content Gradle will always build the project in parallel.
+### Tooling API exposes more information on how to launch a Gradle build (i)
 
-    //gradle.properties file
-    org.gradle.parallel=true
+The [Tooling API](userguide/embedding.html) is a mechanism for embedding Gradle and/or driving Gradle programmatically.
+The new [`BuildInvocations`](javadoc/org/gradle/tooling/model/gradle/BuildInvocations.html) Tooling API model type provides information about the possible ways to invoke the build.
 
-### New distribution plugin (i)
+It provides the invokable tasks of a project, and importantly also its applicable task _selectors_.
+A task selector effectively refers to all of the tasks with a given name in a project and its child projects.
+For example, it is common in a multi project build for all projects to have a `build` task.
+Invoking the build via the Tooling API with the `build` task _selector_ would run the `build` task in every project, effectively building the entire multi project build.
+In contrast, invoking the build with the `build` _task_ would only build the root project (or which ever project is being targeted).
 
-Thanks to a contribution from [Sébastien Cogneau](https://github.com/scogneau), a new `distribution` plugin has been added. This plugin adds general-purpose for support bundling and installing distributions.
+This new capability makes it easier for integrators to provide more powerful interfaces for invoking Gradle builds.
 
-This plugin adds a `main` distribution, and you can add additional distributions. For each distribution, tasks are added to create a ZIP or TAR file for the distribution and
-to install the distribution.
+### Easier to identify ignored tests in HTML test report
 
-You can define multiple distributions:
+The HTML test report now has a dedicated tab for ignored tests, at the overview and package level.
+This makes it much easier to see which tests were ignored at a glance.
 
-    distributions {
-        enterprise
-        community
-    }
+Thanks to [Paul Merlin](https://github.com/eskatos) for this improvement.
 
-To build the additional distributions you can run the generated `Zip` tasks `enterpriseDistZip` and `communityDistZip`. For more information, please consult the 
-[user guide](userguide/distribution_plugin.html).
+### Support for building large zips
 
-### Improved Java library distribution plugin (i)
+It is now possible to build zips with the [Zip64 extension](http://en.wikipedia.org/wiki/Zip_\(file_format\)#ZIP64), enabling the building of large zip files.
 
-The Java library distribution plugin now extends the newly introduced distribution plugin. Thanks to this, you can now create tar files and install Java library distributions.
+    task largeZip(type: Zip) {
+        from 'lotsOfLargeFiles'
+        zip64 = true
+    }
 
-For more information, please consult the [user guide](userguide/javaLibraryDistribution_plugin.html).
+The zip standard does not support containing more than 65535 files, containing any file greater than 4GB or being greater than 4GB compressed.
+If your zip file meets any of these criteria, then the zip must be built with the
+[`zip64` property](dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:zip64) set to `true` (it is `false` by default).
+This flag also applies to all JARs, WARs, EARs and anything else that uses the Zip format.
 
-### Improved usability of project dependencies
+However, not all Zip readers support the Zip64 extensions.
+Notably, the `ZipInputStream` JDK class does not support Zip64 for versions earlier than Java 7.
+This means you should not enable this property if you are building JARs to be used with Java 6 and earlier runtimes.
 
-Project dependencies at configuration time are now fully supported.
-Prior to this change, any resolution of a project dependency at configuration time may have led to confusing behavior as the target project may not have been configured yet.
-Now the resolution of the project dependency implies configuration of the target project.
-This means that the order in which projects are configured may now be different (i.e. it will be correct).
-This change should not cause any trouble in existing builds and it fixes up the confusing behavior with project dependencies.
+Thanks to [Jason Gauci](https://github.com/MisterTea) for this improvement.
 
-### Improvements to the new '`maven-publish`' and '`ivy-publish`' plugins (i)
+### Support for consuming Apache Maven POMs with active profiles
 
-The '`maven-publish`' and '`ivy-publish`' plugins gain new features and capabilities in this Gradle release.
+Gradle now respects POM profiles that are [active by default](https://maven.apache.org/pom.html#Activation), during dependency resolution.
+More specifically, the properties, dependencies and dependency management information is now respected.
 
-#### Easy publication of software components
+### Customise Clang compiler tool chain (i)
 
-Gradle 1.5 introduces the concept of a “Software Component”, which defines something that can be produced by a Gradle project such as a Java library or a web application.
-Both the '`ivy-publish`' and '`maven-publish`' plugins are component-aware, simplifying the process of publishing a module. The component defines the set of artifacts and dependencies for publishing.
+In earlier Gradle versions, it was possible to customize the GCC compiler tool chain in various ways. For example, you could use the `cCompiler.executable` property
+to specify a custom C compiler to use.
 
-Presently, the set of components available for publishing is limited to '`java`' and '`web`', added by the '`java`' and '`war`' plugins respectively. In the future it will be possible to
-create new components and new component types.
+Now you can customize the Clang tool chain in exactly the same way as the GCC tool chain.
 
-Publishing the '`web`' component will result in the war file being published with no runtime dependencies (dependencies are bundled in the war):
+For more details see [Clang](dsl/org.gradle.nativebinaries.toolchain.Clang.html) and [GCC](dsl/org.gradle.nativebinaries.toolchain.Gcc.html).
 
-    apply plugin: 'war'
-    apply plugin: 'maven-publish'
+### Improved Visual Studio project file generation (i)
 
-    group = 'group'
-    version = '1.0'
+Gradle 1.11 added support for [generating Visual Studio configuration files](http://www.gradle.org/docs/current/release-notes#generate-visual-studio-configuration-for-a-native-binary-project).
+This feature has been improved in the following ways in Gradle 1.12:
 
-    // … declare dependencies and other config on how to build
+* Visual studio log files are generated into `.vs` instead of the project directory
+* Project files are named by `ProjectNativeComponent.name` instead of `ProjectNativeComponent.baseName`
+* Header files co-located with source files are now include in the generated project
 
-    publishing {
-        repositories {
-            maven { url 'http://mycompany.org/mavenRepo' }
-            ivy { url 'http://mycompany.org/ivyRepo' }
-        }
-        publications {
-            mavenWeb(MavenPublication) {
-                from components.web
-            }
-            ivyWeb(IvyPublication) {
-                from components.java // Include the standard java artifacts
-            }
-        }
-    }
+### Updated mapping of dependencies to IDEA classpath scopes
 
-#### Publishing custom artifacts
+There were changes in IDEA project mapping related to bug reports [GRADLE-2017] and [GRADLE-2231]. Projects generated by the [IDEA plugin](userguide/idea_plugin.html) now
+better map project dependencies to classpath scopes in IDEA modules.
 
-This release introduces the ability to customize the set of artifacts to publish to a Maven repository or an Ivy repository.
-This gives complete control over which artifacts are published, and the classifier/extension used to publish them.
+### Easier debugging of JVM `Test` and `JavaExec` processes (i)
 
-Due to differences in the capabilities of Ivy vs Maven repositories, the DSL is slightly different for each repository format.
+The [`Test`](dsl/org.gradle.api.tasks.testing.Test.html) and [`JavaExec`](dsl/org.gradle.api.tasks.JavaExec.html) tasks both now support a `--debug-jvm` invocation time switch, which is equivalent
+to setting the `debug` property of these tasks to `true`.
 
-    apply plugin: 'java'
-    apply plugin: 'maven-publish'
-    apply plugin: 'ivy-publish'
+This makes it easy, for example, to launch the application in debug mode when using the [Application plugin](userguide/application_plugin.html)…
 
-    group = 'group'
-    version = '1.0'
+<pre><tt>gradle run --debug-jvm</tt></pre>
 
-    // … declare dependencies and other config on how to build
+This starts the JVM process in debug mode, and halts the process until a debugger attaches on port 5005.
+The same can be done for any [`Test`](dsl/org.gradle.api.tasks.testing.Test.html) task.
 
-    task sourceJar(type: Jar) {
-        from sourceSets.main.allJava
-        classifier "source"
-    }
+### Source and Javadoc artifacts declared in ivy.xml are recognised by IDE plugins
 
-    publishing {
-        repositories {
-            maven { url 'http://mycompany.org/mavenRepo' }
-            ivy { url 'http://mycompany.org/ivyRepo' }
-        }
-        publications {
-            mavenCustom(MavenPublication) {
-                from components.java // Include the standard java artifacts
-                artifact sourceJar {
-                    classifier "source"
-                }
-                artifact("project-docs.htm") {
-                    classifier "docs"
-                    extension "html"
-                    builtBy myDocsTask
-                }
-            }
-            ivyCustom(IvyPublication) {
-                from components.java // Include the standard java artifacts
-                artifact(sourceJar) {
-                    type "source"
-                    conf "runtime"
-                    classifier "source"
-                }
-                artifact("project-docs.htm") {
-                    classifier "docs"
-                    extension "html"
-                    builtBy myDocsTask
-                }
-            }
-        }
-    }
+The Gradle `eclipse` and `idea` plugins are able to find and download the source artifacts for external dependencies, and link
+these artifacts into the generated IDE files.
 
-Be sure to check out the DSL reference for [MavenPublication](dsl/org.gradle.api.publish.maven.MavenPublication.html) and [IvyPublication](dsl/org.gradle.api.publish.ivy.IvyPublication.html)
-for complete details on how the set of artifacts can be customized.
+In addition to the conventional classifier-based scheme for locating source and javadoc artifacts, Gradle will now recognise
+artifacts declared in a specific configuration of an `ivy.xml` file.
 
-For more information about using the new '`maven-publish`' and '`ivy-publish`' plugins in general, please consult the user guide ([maven](userguide/publishing_maven.html)) ([ivy](userguide/publishing_ivy.html)).
+For an IDE project that references an external module located in an `ivy` repository, Gradle will now include:
 
-#### Generate POM file without publishing
+* Source artifacts declared in a `sources` configuration in `ivy.xml`
+* Javadoc artifacts declared in a `javadoc` configuration in `ivy.xml`
+* Source artifacts conventionally named with a `sources` classifier: eg. `module-1.0-sources.jar`
+* Javadoc artifacts conventionally named with a `javadoc` classifier: eg. `module-1.0-javadoc.jar`
 
-POM file generation has been moved into a separate task, so that it is now possible to generate the POM file without actually publishing your project. All details of
-the publishing model are still considered in POM generation, including `components`, custom `artifacts`, and any modifications made via `pom.withXml`.
+### HTTPS wrapper downloads
 
-The task for generating the POM file is of type [`GenerateMavenPom`](dsl/org.gradle.api.publish.maven.tasks.GenerateMavenPom.html), and is given a name based on the name
-of the publication: `generatePomFileFor<publication-name>Publication`. So in the above example where the publication is named '`mavenCustom`',
-the task will be named `generatePomFileForMavenCustomPublication`.
+The [Gradle Wrapper](userguide/gradle_wrapper.html) is now downloaded over HTTPS.
+The `gradle-wrapper.properties` file created by the `wrapper` task will now specify a HTTPS URL as the location of the Gradle distribution to download.
 
-#### Full support for Unicode in publication identifiers
+Existing projects should consider updating the `gradle/wrapper/gradle-wrapper.properties` file to use `https` instead of `http` for the `distributionUrl` property value.
+No other change to the URL is necessary.
 
-Where supported by the underlying metadata format, Gradle will now handle any valid Unicode character in module group, name and version as well as artifact name, extension and classifier.
+## Fixed issues
 
-The only values that are explicitly prohibited are '\\', '/' and any ISO control character. Supplied values are validated early in publication. 
+## Deprecations
 
-A couple of caveats to the Unicode support:
+Features that have become superseded or irrelevant due to the natural evolution of Gradle become *deprecated*, and scheduled to be removed
+in the next major Gradle version (Gradle 2.0). See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
 
-- Maven restricts '`groupId`' and '`artifactId`' to a limited character set (`[A-Za-z0-9_\\-.]+`) and Gradle enforces this restriction.
-- Certain repositories will not be able to handle all supported characters. For example, the '`:`' character cannot be used
-  as an identifier when publishing to a filesystem-backed repository on Windows.
+The following are the newly deprecated items in this Gradle release. If you have concerns about a deprecation, please raise it via the [Gradle Forums](http://forums.gradle.org).
 
-### Support for Ivy dynamic resolve mode (i)
+### Tooling API version compatibility
 
-It is now possible to enable the equivalent of Ivy's _dynamic resolve_ mode when resolving dependencies. This is only supported for Ivy repositories.
+The [Tooling API](userguide/embedding.html) is a mechanism for embedding Gradle and/or driving Gradle programmatically.
+It is used by IDEs and other _tooling_ to integrate with Gradle.
 
-See the [user guide](userguide/dependency_management.html#ivy_dynamic_resolve_mode) for examples and further details.
+* Connecting to 1.0-milestone-8 and earlier providers is now deprecated and will not be supported in Gradle 2.0. This means that the Gradle 2.0 Tooling API client will
+not be able to run builds that use Gradle 1.0-milestone-8 and earlier.
+* Client versions older than 1.2 are now deprecated and will not be supported in Gradle 2.0. This means that the Gradle 1.2 and earlier Tooling API clients will not
+be able to run builds that use Gradle 2.0 and later.
 
-### New build dashboard Plugin (i)
+If your project is building with Gradle 1.0-milestone-8 or earlier, you are __strongly__ encouraged to upgrade to a more recent Gradle version.
+All versions of integrating tools released since the release of Gradle 1.2 (September 2012) should be using a Tooling API client newer than version 1.2.
 
-Thanks to a contribution from [Marcin Erdmann](https://github.com/erdi), a new `build-dashboard` plugin has been added. This plugin adds a task to projects to generate a build dashboard HTML report which contains
-references to all reports that were generated during the build. In the following example, the `build-dashboard` plugin is added to a project which has also the `groovy` and
-the `codenarc` plugin applied:
+### Deprecated method in Tooling API
 
-    apply plugin: 'groovy'
-    apply plugin: 'build-dashboard'
-    apply plugin: 'codenarc'
+The <a href="javadoc/org/gradle/tooling/model/Task.html#getProject()">`org.gradle.tooling.model.Task.getProject()`</a> method is now deprecated and
+can throw `UnsupportedMethodException`. There is no replacement as it is expected that the caller has a reference to project prior calling to this method.
 
-By running the `buildDashboard` task after other tasks that generate reports (e.g. by running `gradle check buildDashboard`), the generated build dashboard contains links to the 
-`codenarc` reports. This version of the build dashboard does not include links to test reports. This plugin is in the early stages of development and will be significantly improved in future Gradle releases.
+## Potential breaking changes
 
-More information on the `build-dashboard` plugin can be found in the [user guide](userguide/buildDashboard_plugin.html).
-  
-## Fixed issues
+### Incremental Scala compilation
 
-## Deprecations
+The version of the Scala incremental compiler, Zinc, that Gradle uses has been upgraded to a version 0.3.0.
+This might be a breaking change for users who explicitly configured an early version of zinc in their build scripts.
+There should be very few such users, if any.
 
-Features that have become superseded or irrelevant due to the natural evolution of Gradle become *deprecated*, and scheduled to be removed
-in the next major Gradle version (Gradle 2.0). See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
+### Changes to incubating native support
 
-The following are the newly deprecated items in this Gradle release. If you have concerns about a deprecation, please raise it via the [Gradle Forums](http://forums.gradle.org).
+* '-Xlinker' is no longer automatically added to linker args for GCC or Clang. If you want to pass an argument directly to 'ld' you need to add this escape yourself.
+* Tasks for Windows resource compilation are now named 'compileXXXX' instead of 'resourceCompileXXX'.
 
-### `ArtifactRepositoryContainer.getResolvers()`
+### Change to JUnit XML file for skipped tests
 
-This method exposes internal implementation details that will be subject to change in the future. Its use should be avoided.
+The way that skipped/ignored tests are represented in the JUnit XML output file produced by the `Test` task.
+Gradle now produces the same output, with regard to skipped tests, as Apache Ant and Apache Maven.
+This format is accepted, and expected, by all major Continuous Integration servers.
 
-## Potential breaking changes
+This change is described as follows:
 
-### Changes to incubating Maven publishing support
+1. The `testsuite` element now contains a `skipped` attribute, indicating the number of skipped tests (may be 0)
+2. The element representing a test case is now always named `testcase` (previously it was named `ignored-testcase` if it was a skipped test)
+3. If a test case was skipped, a child `<skipped/>` element will be present
 
-Breaking changes have been made to the incubating '`maven-publish`' plugin, which provides an alternative means to publish to Maven repositories.
+No changes are necessary to build scripts or Continuous Integration server configuration to accommodate this change.
 
-- A MavenPublication must be explicitly added to the `publications` container; no publication is added implicitly by the `maven-publish` plugin.
-    - If no `MavenPublication` is configured then nothing will be published.
-- A `MavenPublication` does not include any artifacts or dependencies by default; these must be added directly or via a `SoftwareComponent`.
-    - If no artifacts are configured, a Maven POM file will be published with no artifacts or dependencies declared.
-- The `groupId`, `artifactId` and `version` in the published pom cannot be changed via `MavenPom.withXml()`:
-   it was previously possible change these values, but any interproject dependency would not pick up these changes.
-    - In the future Gradle will provide a robust mechanism for modifying publication identity prior to publication.
-- Identifiers used in Maven publications (`groupId`, `artifactId`, `version`, `ext`, `classifier`) have new character restrictions:
-  these identifiers may not contain '`/`', '`\`' or any ISO Control Characters. Using these values generally made it impossible to resolve these modules, so this is now prevented
-  at the time of publication.
-   - `groupId` and `artifactId` are further restricted to "`[A-Za-z0-9_\-.]+`": this is a Maven restriction, so it is enforced at the time of publication.
-- The `GenerateMavenPom` task for a publication is not created until the publishing extension is first accessed. Any attempt to configure a `GenerateMavenPom` task
-  should be enclosed within a `publishing` block.
-- Once the publishing extension is accessed as a property, it is no longer possible to further configure the extension using a `publishing` block.
+### Ordering of dependencies in imported Ant builds
 
-Be sure to check out the [Maven Publishing User Guide Chapter](userguide/publishing_maven.html) and the [MavenPublication DSL reference](dsl/org.gradle.api.publish.maven.MavenPublication.html)
-for complete description and examples of the new Maven Publishing support.
+The ordering of Ant target dependencies is now respected when possible.
+This may cause tasks of imported Ant builds to executed in a different order from this version of Gradle on.
 
-### Changes to incubating Ivy publishing support
+Given…
 
-Breaking changes have been made to the incubating '`ivy-publish`' plugin, which provides an alternative means to publish to Ivy repositories.
+    <target name='a' depends='d,c,b'/>
 
-- An `IvyPublication` must be explicitly added to the `publications` container; no publication is added implicitly by the `ivy-publish` plugin.
-    - If no `IvyPublication` is configured then nothing will be published.
-- An `IvyPublication` does not include any artifacts or dependencies by default; these must be added directly or via a `SoftwareComponent`.
-    - If no artifacts are configured, an `ivy.xml` file will be published with no artifacts or dependencies declared.
-- The `organisation`, `name` and `revision` cannot be changed via `IvyDescriptor.withXml()`:
-   it was previously possible to do this, although it did not change the actual identity of the published module.
-    - In the future Gradle will provide a robust mechanism for modifying publication identity prior to publication.
-- Identifiers in ivy modules (`organisation`, `module`, `revision`) and artifacts (`name`, `ext`, `type`, `classifier`) have new character restrictions:
-  these identifiers may not contain '`/`', '`\`' or any ISO Control Characters. Using these values generally made it impossible to resolve these modules, so this is now prevented
-  at the time of publication.
-- Removed `GenerateIvyDescriptor.xmlAction` property. The `ivy.descriptor.withXml()` method provides a way to customise the generated module descriptor.
-- The `GenerateIvyDescriptor` task for a publication is not created until the publishing extension is first accessed. Any attempt to configure a `GenerateIvyDescriptor`
-  should be enclosed within a `publishing` block.
-- Once the publishing extension is accessed as a property, it is no longer possible to further configure the extension using a `publishing` block.
+A shouldRunAfter [task ordering](userguide/more_about_tasks.html#sec:ordering_tasks) will be applied to the dependencies so that,
+`c.shouldRunAfter d` and `b.shouldRunAfter c`.
 
-Be sure to check out the [Ivy Publishing User Guide Chapter](userguide/publishing_ivy.html) and the [IvyPublication DSL reference](dsl/org.gradle.api.publish.ivy.IvyPublication.html)
-for complete description and examples of the new Ivy Publishing support.
+This is in alignment with Ant's ordering of target dependencies and resolves a long standing Gradle enhancement request [GRADLE-1102].
 
-### Project configuration order
+### Invalid large zip files now fail the build
 
-Improving the usability of project dependencies (see the section above) might change the order in which projects are configured.
-This is not expected to cause problems in existing builds, but is mentioned for completeness.
+If a zip file is built without the `zip64` flag (new in this release) set to true that surpasses the file size and count limits
+of the zip format, Gradle will now fail the build.
+Previously, it may have silently created an invalid zip.
 
-### Optimized order of task execution in parallel execution mode
+To allow the large zip to be correctly built, set the `zip64` property of the task to `true`.
 
-Parallel builds are now much faster due to better utilisation of parallel workers. However, this means that tasks may be executed in different order in parallel builds.
-This will not cause problems in a correctly [decoupled build](userguide/multi_project_builds.html#sec:decoupled_projects) but may bring problems to light in builds that are not properly decoupled.
+### Change to signature of `Test.filter(Closure)`
 
-### Changes to the incubating Java library distribution plugin
+The incubating `Test.filter(Closure)` method introduced in 1.10 for configuring the `TestFilter` has been changed to be more consistent with other configuration methods.
+This method now accepts an `Action` and no longer returns the `TestFilter`.
+This change should not require any adjustments to build scripts as this method can still be called with a `Closure`, upon which it will be implicitly converted into an `Action`.
 
-The `distribution` extension that is added by the `java-library-distribution` plugin was removed. The `main` distribution is now accessible using the `distributions` extension:
+### Change to signature of `IdeaModule.singleEntryLibraries`
 
-    distributions {
-        main {
-            ...
-        }
-    }
+The property `IdeaModule.singleEntryLibraries` was previously declared as a `Map` with values of type `Collection<File>`. The values are in fact `Iterable<File>` and
+the type signature has been updated.
 
 ## External contributions
 
-We would like to thank the following community members for making excellent contributions to this release of Gradle.
+We would like to thank the following community members for making contributions to this release of Gradle.
 
-* [Joe Sortelli](https://github.com/sortelli) - Fixed incorrect handling of `ivy.xml` where dependency configuration contained wildcard values (GRADLE-2352)
-* [David M. Carr](https://github.com/davidmc24)
-    * When JUnit tests have assumption failures, treat them as "skipped" (GRADLE-2454)
-    * Documentation cleanups.
-* [Sébastien Cogneau](https://github.com/scogneau) - Introduce the distribution plugin
-* [Kenny Stridh](https://github.com/kensi)
-    * Allow specifying `targetJdk` for PMD code analysis (GRADLE-2106)
-    * Added support for PMD version 5.0.+
 * [Marcin Erdmann](https://github.com/erdi)
-    * Add`build-dashboard` plugin
-    * Make notify-send notifications transient in Gnome Shell
-* [Michael R. Maletich](https://github.com/HawaiianSpork)
-    * Add `maxHeapSize` property to `FindBugs` task to allow setting the max heap size for spawned FindBugs java process
-    * Add `contentsCompression` property to the `Zip` task type to specify the compression level of the archive
-* [Barry Pitman](https://github.com/barrypitman) - Fixed Maven conversion problem (GRADLE-2645)
-* [Kallin Nagelberg](https://github.com/Kallin) - Fixed grammar in the `SourceSetOutput` documentation
-* [Klaus Illusioni](https://github.com/illusioni) - Fixed Eclipse wtp component generation issue (GRADLE-2653)
-* [Alex Birmingham](https://github.com/abirmingham) - Fixed PMD Javadoc
-* [Matthieu Leclercq](https://github.com/mleclercq) - Fixed the nested configuration resolution issue (GRADLE-2477)
-* [Dan Stine](https://github.com/dstine) - Userguide cleanups.
+    * dependency ordering of imported Ant targets [GRADLE-1102]
+    * fixes for excluding tasks [GRADLE-2974] & [GRADLE-3031]
+* [Jesse Glick](https://github.com/jglick) - enabling newlines in option values passed to build
+* [Zeeke](https://github.com/zeeke) - documentation improvements
+* [Kamil Szymański](https://github.com/kamilszymanski) - documentation improvements
+* [Jakub Kubryński](https://github.com/jkubrynski) - handling of empty string proxy system property values
+* [Lee Symes](https://github.com/leesdolphin) & [Greg Temchenko](https://github.com/soid) - fix skipped test representation in JUnit XML result files [GRADLE-2731]
+* [Ivan Vyshnevskyi](https://github.com/sainaen) - Fixes to HTML test report
+* [Vincent Cantin](https://github.com/green-coder) - documentation improvements
+* [Sterling Greene](https://github.com/big-guy) - Support for developing Gradle in Eclipse
+* [Matthew Michihara](https://github.com/matthewmichihara) - Documentation improvements
+* [Andrew Oberstar](https://github.com/ajoberstar) - Improved Sonar support on Java 1.5 [GRADLE-3005]
+* [Mark Johnson](https://github.com/elucify) - documentation improvements
+* [Paul Merlin](https://github.com/eskatos) - ignored tests tab for HTML test report
+* [Jason Gauci](https://github.com/MisterTea) - Support for large zips
+* [Zsolt Kúti](https://github.com/tinca) - Fixes for Gradle on FreeBSD
+* [Rich Jones](https://github.com/Miserlou) - HTTPS URLs for wrapper downloads
 
 We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
 
diff --git a/subprojects/docs/src/docs/stylesheets/dslHtml.xsl b/subprojects/docs/src/docs/stylesheets/dslHtml.xsl
index b232faa..c0ac085 100644
--- a/subprojects/docs/src/docs/stylesheets/dslHtml.xsl
+++ b/subprojects/docs/src/docs/stylesheets/dslHtml.xsl
@@ -34,14 +34,14 @@
 
     <xsl:template name="formal.object.heading"></xsl:template>
 
-    <!-- customise the stylesheets to add to the <head> element -->
+    <!-- customize 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="docs.css" rel="stylesheet" type="text/css"/>
         <link href="dsl.css" rel="stylesheet" type="text/css"/>
     </xsl:template>
 
-    <!-- Customise the page titles -->
+    <!-- customize the page titles -->
     <xsl:template match="book" mode="object.title.markup.textonly">
         <xsl:value-of select="bookinfo/titleabbrev"/>
         <xsl:text> Version </xsl:text>
@@ -54,7 +54,7 @@
         <xsl:apply-templates select="/book" mode="object.title.markup.textonly"/>
     </xsl:template>
 
-    <!-- customise the layout of the html page -->
+    <!-- customize the layout of the html page -->
     <xsl:template name="chunk-element-content">
         <xsl:param name="prev"/>
         <xsl:param name="next"/>
@@ -149,7 +149,7 @@
     </xsl:template>
 
     <!--
-      - Customised header for property and method detail sections
+      - Customized header for property and method detail sections
       -->
 
     <xsl:template match="section[@role='detail']/title" mode="titlepage.mode">
@@ -170,7 +170,7 @@
     </xsl:template>
 
     <!--
-      - Customised <segmentedlist> formats
+      - Customized <segmentedlist> formats
       -->
     <xsl:template match="segmentedlist">
         <div>
diff --git a/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl b/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
index e8ea7e8..098e053 100644
--- a/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
+++ b/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
@@ -88,7 +88,7 @@
     
     <!-- BOOK TITLEPAGE -->
 
-    <!-- Customise the contents of the book titlepage -->
+    <!-- Customize the contents of the book titlepage -->
     <xsl:template name="book.titlepage">
         <div class="titlepage">
             <div class="title">
diff --git a/subprojects/docs/src/docs/userguide/applicationPlugin.xml b/subprojects/docs/src/docs/userguide/applicationPlugin.xml
index e3cd639..e0a14bf 100644
--- a/subprojects/docs/src/docs/userguide/applicationPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/applicationPlugin.xml
@@ -32,6 +32,7 @@
 
         <para>Then, you can run the application by running <userinput>gradle run</userinput>. Gradle will take care of building the application classes,
             along with their runtime dependencies, and starting the application with the correct classpath.
+            You can launch the application in debug mode with <userinput>gradle run --debug-jvm</userinput> (see <apilink class="org.gradle.api.tasks.JavaExec" method="setDebug(boolean)" />).
         </para>
 
         <para>The plugin can also build a distribution for your application. The distribution will package up the runtime dependencies of the application
@@ -40,6 +41,15 @@
             <filename>build/install/<replaceable>projectName</replaceable></filename>. You can run <userinput>gradle distZip</userinput> to create a
             ZIP containing the distribution.
         </para>
+
+        <para>
+            If your Java application requires a specific set of JVM settings or system properties, you can configure the <literal>applicationDefaultJvmArgs</literal> property.
+            These JVM arguments are applied to the <literal>run</literal> task and also considered in the generated start scripts of your distribution.
+        </para>
+        <sample id="configureApplicationDefaultJvmArgs" dir="application" title="Configure default JVM settings">
+            <sourcefile file="build.gradle" snippet="application-defaultjvmargs"/>
+        </sample>
+
     </section>
 
     <section>
@@ -113,7 +123,7 @@
             <para>The application plugin adds some properties to the project, which you can use to configure its behaviour. See <apilink class="org.gradle.api.Project"/>.
             </para>
     </section>
-    
+
     <section id="application_distribution_resources">
             <title>Including other resources in the distribution</title>
             <para>
diff --git a/subprojects/docs/src/docs/userguide/artifactMngmt.xml b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
index 604efb2..226c93a 100644
--- a/subprojects/docs/src/docs/userguide/artifactMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
@@ -18,7 +18,7 @@
     <note>
         <para>
             This chapter describes the <emphasis>original</emphasis> publishing mechanism available in Gradle 1.0: in Gradle 1.3 a new mechanism for publishing was introduced.
-            While this new mechanism is <emphasis>incubating</emphasis> and not yet complete, it introduces some new concepts and features that do (and will) make Gradle publishing even more powerful.
+            While this new mechanism is <link linkend="feature_lifecycle">incubating</link> and not yet complete, it introduces some new concepts and features that do (and will) make Gradle publishing even more powerful.
         </para>
         <para>
             You can read about the new publishing plugins in <xref linkend="publishing_ivy"/> and <xref linkend="publishing_maven"/>. Please try them out and give us feedback.
@@ -69,7 +69,7 @@
             </sample>
             <para>Gradle will figure out the properties of the artifact based on the name of the file. You can customize these properties:</para>
             <sample id="fileArtifact" dir="userguide/artifacts/uploading" title="Customizing an artifact">
-                <sourcefile file="build.gradle" snippet="customised-file-artifact"/>
+                <sourcefile file="build.gradle" snippet="customized-file-artifact"/>
             </sample>
             <para>There is a map-based syntax for defining an artifact using a file. The map must include a <literal>file</literal> entry that
                 defines the file. The map may include other artifact properties:
diff --git a/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml b/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
deleted file mode 100644
index 837e740..0000000
--- a/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
+++ /dev/null
@@ -1,88 +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.
-  -->
-<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 fb9d064..6e46ac3 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 incubating (see <xref linkend="sec:incubating_state"/>).
+            The build announcements plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
         </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/buildDashboardPlugin.xml b/subprojects/docs/src/docs/userguide/buildDashboardPlugin.xml
index 47a64a6..ddf8f17 100644
--- a/subprojects/docs/src/docs/userguide/buildDashboardPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/buildDashboardPlugin.xml
@@ -18,10 +18,13 @@
     <title>The Build Dashboard Plugin</title>
     <note>
         <para>
-            The Build Dashboard plugin is incubating (see <xref linkend="sec:incubating_state"/>).
+            The build dashboard plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
         </para>
     </note>
-    <para>The Build Dashboard plugin adds a task to projects which generates build dashboard report.
+
+    <para>
+        The Build Dashboard plugin can be used to generate a single HTML dashboard that provides a single point of
+        access to all of the reports generated by a build.
     </para>
 
     <section>
@@ -30,8 +33,17 @@
         <sample id="useBuildDashboardPlugin" dir="buildDashboard" title="Using the Build Dashboard plugin">
             <sourcefile file="build.gradle" snippet="use-build-dashboard-plugin"/>
         </sample>
-        <para>You can then generate the report by running the <userinput>buildDashboard</userinput> task after running any tasks that
-            generate reports, for example: <userinput>gradle check buildDashboard</userinput>.</para>
+        <para>
+            Applying the plugin adds the <literal>buildDashboard</literal> task to your project.
+            The task aggregates the reports for all tasks that implement the <apilink class="org.gradle.api.reporting.Reporting" />
+            interface from <emphasis>all projects</emphasis> in the build.
+            It is typically only applied to the root project.
+        </para>
+        <para>
+            The <literal>buildDashboard</literal> task does not depend on any other tasks. It will only aggregate the reporting tasks that are independently
+            being executed as part of the build run. To generate the build dashboard, simply include the <literal>buildDashboard</literal> task in the list of tasks to execute.
+            For example, <userinput>gradle buildDashboard build</userinput> will generate a dashboard for all of the reporting tasks that are dependents of the <literal>build</literal> task.
+        </para>
     </section>
 
     <section>
diff --git a/subprojects/docs/src/docs/userguide/buildInitPlugin.xml b/subprojects/docs/src/docs/userguide/buildInitPlugin.xml
new file mode 100644
index 0000000..41a24a2
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/buildInitPlugin.xml
@@ -0,0 +1,222 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<chapter id='build_init_plugin'>
+    <title>Build Init Plugin</title>
+    <note>
+        <para>
+            The Build Init plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
+        </para>
+    </note>
+    <para>
+        The Gradle Build Init plugin can be used to bootstrap the process of creating a new Gradle build. It supports creating brand new projects of different types
+        as well as converting existing builds (e.g. An Apache Maven build) to be Gradle builds.
+    </para>
+    <para>
+        Gradle plugins typically need to be
+        <firstterm>applied</firstterm>
+        to a project before they can be used (see <xref linkend="sec:using_plugins"/>).
+        The Build Init plugin is an automatically applied plugin, which means you do not need to apply it explicitly.
+        To use the plugin, simply execute the task named
+        <literal>init</literal>
+        where you would like to create the Gradle build.
+        There is no need to create a “stub”
+        <literal>build.gradle</literal>
+        file in order to apply the plugin.
+    </para>
+    <para>
+        It also leverages the
+        <literal>wrapper</literal>
+        task from the Wrapper plugin (see <xref linkend='wrapper_plugin'/>),
+        which means that the Gradle Wrapper will also be installed into the project.
+    </para>
+    <section>
+        <title>Tasks</title>
+        <para>The plugin adds the following tasks to the project:</para>
+        <table>
+            <title>Build Init plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>init</literal>
+                </td>
+                <td>
+                    <literal>wrapper</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.buildinit.tasks.InitBuild"/>
+                </td>
+                <td>Generates a Gradle project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>wrapper</literal>
+                </td>
+                <td>-</td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/>
+                </td>
+                <td>Generates Gradle wrapper files.
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>What to set up</title>
+        <para>The
+            <literal>init</literal>
+            supports different build setup <firstterm>types</firstterm>. The type is specified by supplying a
+            <literal>--type</literal>
+            argument value. For example, to create a Java library project simply execute:
+            <literal>gradle init --type java-library</literal>.
+        </para>
+        <para>
+            If a
+            <literal>--type</literal>
+            parameter is not supplied, Gradle will attempt to infer the type from the environment. For example, it will
+            infer a type value of "<literal>pom</literal>" if it finds a
+            <literal>pom.xml</literal>
+            to convert to a Gradle build.
+        </para>
+        <para>
+            If the type could not be inferred, the type "<literal>basic</literal>" will be used.
+        </para>
+        <para>All build setup types include the setup of the Gradle Wrapper.</para>
+    </section>
+    <section>
+        <title>Build init types</title>
+        <note>
+            As this plugin is currently <link linkend="feature_lifecycle">incubating</link>, only 3 build init types are currently supported.
+            More types will be added in future Gradle releases.
+        </note>
+        <section>
+            <title>"<literal>pom</literal>" (Maven conversion)
+            </title>
+            <para>
+                The "<literal>pom</literal>" type can be used to convert an Apache Maven build to a Gradle build.
+                This works by converting the POM to one or more Gradle files.
+                It is only able to be used if there is a valid "<literal>pom.xml</literal>"
+                file in the directory that the
+                <literal>init</literal>
+                task is invoked in. This type will be automatically inferred if such a file exists.
+            </para>
+            <para>
+                The Maven conversion implementation was inspired by the
+                <ulink url="https://github.com/jbaruch/maven2gradle">maven2gradle tool</ulink>
+                that was originally developed by Gradle community members.
+            </para>
+            <para>
+                The conversion process has the following features:
+            </para>
+            <itemizedlist>
+                <listitem>Uses effective POM and effective settings (support for POM inheritance, dependency management, properties)</listitem>
+                <listitem>Supports both single module and 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>
+        </section>
+        <section>
+            <title>"<literal>java-library</literal>"
+            </title>
+            <para>
+                The "<literal>java-library</literal>" build init type is not inferable. It must be explicitly specified.
+            </para>
+            <para>
+                It has the following features:
+            </para>
+            <itemizedlist>
+                <listitem>Uses the "<literal>java</literal>" plugin
+                </listitem>
+                <listitem>Uses the "
+                    <literal>mavenCentral()</literal>
+                    dependency repository
+                </listitem>
+                <listitem>Uses <ulink url="http://junit.org">JUnit</ulink>
+                 for testing</listitem>
+                <listitem>Has directories in the conventional locations for source code</listitem>
+                <listitem>Contains a sample class and unit test, if there are no existing source or test files</listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>"<literal>scala-library</literal>"
+            </title>
+            <para>
+                The "<literal>scala-library</literal>" build init type is not inferable. It must be explicitly specified.
+            </para>
+            <para>
+                It has the following features:
+            </para>
+            <itemizedlist>
+                <listitem>Uses the "<literal>scala</literal>" plugin
+                </listitem>
+                <listitem>Uses the "
+                    <literal>mavenCentral()</literal>
+                    dependency repository
+                </listitem>
+                <listitem>Uses Scala 2.10</listitem>
+                <listitem>Uses <ulink url="http://www.scalatest.org">ScalaTest</ulink> is used for testing</listitem>
+                <listitem>Has directories in the conventional locations for source code</listitem>
+                <listitem>Contains a sample scala class and an according ScalaTest test suite, if there are no existing source or test files</listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>"<literal>groovy-library</literal>"
+            </title>
+            <para>
+                The "<literal>groovy-library</literal>" build init type is not inferable. It must be explicitly specified.
+            </para>
+            <para>
+                It has the following features:
+            </para>
+            <itemizedlist>
+                <listitem>Uses the "<literal>groovy</literal>" plugin
+                </listitem>
+                <listitem>Uses the "
+                    <literal>mavenCentral()</literal>
+                    dependency repository
+                </listitem>
+                <listitem>Uses Groovy 2.x </listitem>
+                <listitem>Uses <ulink url="http://code.google.com/p/spock/">Spock testing framework</ulink> for testing</listitem>
+                <listitem>Has directories in the conventional locations for source code</listitem>
+                <listitem>Contains a sample groovy class and an according Spock specification, if there are no existing source or test files</listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>"basic"</title>
+            <para>
+                The "<literal>basic</literal>" build init type is useful for creating a fresh new Gradle project.
+                It creates a sample
+                <literal>build.gradle</literal>
+                file, with comments and links to help get started.
+            </para>
+            <para>This type is used when no type was explicitly specified, and no type could be inferred.</para>
+        </section>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/buildLifecycle.xml b/subprojects/docs/src/docs/userguide/buildLifecycle.xml
index 32cbd88..5aa0300 100644
--- a/subprojects/docs/src/docs/userguide/buildLifecycle.xml
+++ b/subprojects/docs/src/docs/userguide/buildLifecycle.xml
@@ -45,7 +45,7 @@
                 <listitem>
                     <para>During this phase the project objects are configured.
                         The build scripts of <emphasis>all</emphasis> projects which are part of the build are
-                        executed. Gradle 1.4 introduces an incubating opt-in feature called 'configuration on demand'.
+                        executed. Gradle 1.4 introduces an <link linkend="feature_lifecycle">incubating</link> opt-in feature called <firstterm>configuration on demand</firstterm>.
                         In this mode, Gradle configures only relevant projects (see <xref linkend="sec:configuration_on_demand"/>).
                     </para>
                 </listitem>
@@ -66,7 +66,7 @@
         <title>Settings file</title>
         <para>Beside the build script files, Gradle defines a settings file. The settings file is determined by Gradle
             via a naming convention. The default name for this file is <filename>settings.gradle</filename>. Later in
-            this chapter we explain, how Gradle looks for a settings file.
+            this chapter we explain how Gradle looks for a settings file.
         </para>
         <para>The settings file gets executed during the initialization phase. A multiproject build must have a
             <filename>settings.gradle</filename>
@@ -118,9 +118,9 @@
                 </sample>
                 <para>The <literal>include</literal> method takes project paths as arguments.
                     The project path is assumed to be equal to the relative physical file system path.
-                    For example a path 'services:api' by default is mapped to a folder 'services/api'
-                    (relative from the project root). You only need to specify the leafs of the tree.
-                    This means that the inclusion of path 'services:hotels:api' will result in creating 3 projects:
+                    For example, a path 'services:api' is mapped by default to a folder 'services/api'
+                    (relative from the project root). You only need to specify the leaves of the tree.
+                    This means that the inclusion of the path 'services:hotels:api' will result in creating 3 projects:
                     'services', 'services:hotels' and 'services:hotels:api'.
                 </para>
             </section>
@@ -130,7 +130,7 @@
                     <sourcefile file="settings.gradle" snippet="flat-layout"/>
                 </sample>
                 <para>The <literal>includeFlat</literal> method takes directory names as an argument. Those directories
-                    need to exist at the same level as the root project directory. The location of those directories
+                    need to exist as siblings of the root project directory. The location of those directories
                     are considered as child projects of the root project in the multi-project tree.
                 </para>
             </section>
diff --git a/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml b/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
index f8a37c1..530b0b2 100644
--- a/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
@@ -13,14 +13,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='tutorial_using_tasks' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='tutorial_using_tasks'>
     <title>Build Script Basics</title>
     <section><title>Projects and tasks</title>
     <para>Everything in Gradle sits on top of two basic concepts: <firstterm>projects</firstterm> and <firstterm>tasks</firstterm>.</para>
     <para>
-        Every Gradle build is made up of one or more <firstterm>projects</firstterm>. A project represents
-        some component of your software which can be built. What this means exactly depends on what it is that you are
-        building. For example, a project might represent a library JAR or a web application. It might represent a
+        Every Gradle build is made up of one or more <firstterm>projects</firstterm>.
+        What a project represents depends on what it is that you are doing with Gradle.
+        For example, a project might represent a library JAR or a web application. It might represent a
         distribution ZIP assembled from the JARs produced by other projects. A project does not necessarily represent
         a thing to be built. It might represent a thing to be done, such as deploying your application to staging or
         production environments. Don't worry if this seems a little vague for now. Gradle's build-by-convention support adds
@@ -114,7 +114,8 @@
             <literal>taskY</literal> is defined. This is very important for multi-project builds. Task dependencies are
             discussed in more detail in <xref linkend='sec:adding_dependencies_to_tasks'/>.
         </para>
-        <para>Please notice, that you can't use a shortcut notation (see <xref linkend='sec:shortcut_notations' />) when referring to task, which is not defined yet.</para>
+        <para>Please notice that you can't use shortcut notation (see <xref linkend='sec:shortcut_notations' />)
+        when referring to a task that is not yet defined.</para>
     </section>
     <section>
         <title>Dynamic tasks</title>
@@ -172,10 +173,10 @@
     </section>
     <section>
         <title>Using Ant Tasks</title>
-        <para>Ant tasks are first-class citizens in Gradle. Gradle provides excellent integration for Ant tasks simply
-            by relying on Groovy. Groovy is shipped with the fantastic <literal>AntBuilder</literal>. Using Ant tasks
+        <para>Ant tasks are first-class citizens in Gradle. Gradle provides excellent integration for Ant tasks by simply
+            relying on Groovy. Groovy is shipped with the fantastic <literal>AntBuilder</literal>. Using Ant tasks
             from Gradle is as convenient and more powerful than using Ant tasks from a <filename>build.xml</filename>
-            file. From below example you can learn how to execute ant tasks and how to access ant properties:
+            file. From the example below, you can learn how to execute ant tasks and how to access ant properties:
         </para>
         <sample id="antLoadfile" dir="userguide/tutorial/antLoadfile" title="Using AntBuilder to execute ant.loadfile target">
             <sourcefile file="build.gradle"/>
@@ -213,21 +214,20 @@
     </section>
     <section id="configure-by-dag">
         <title>Configure by DAG</title>
-        <para>As we describe in full detail later (See <xref linkend='build_lifecycle'/>) Gradle has a
-            configuration phase and an execution phase. After the configuration phase Gradle knows all tasks that should
+        <para>As we later describe in full detail (see <xref linkend='build_lifecycle'/>), Gradle has a
+            configuration phase and an execution phase. After the configuration phase, Gradle knows all tasks that should
             be executed. Gradle offers you a hook to make use of this information. A use-case for this would be to check
-            if the release task is part of the tasks to be executed. Depending on this you can assign different values
+            if the release task is among the tasks to be executed. Depending on this, you can assign different values
             to some variables.
         </para>
-        <para>In the following example, execution of <literal>distribution</literal> and <literal>release</literal> tasks results in different value of <literal>version</literal> variable.</para>
+        <para>In the following example, execution of the <literal>distribution</literal> and <literal>release</literal> tasks results in different value of the <literal>version</literal> variable.</para>
         <sample id="configByDagNoRelease" dir="userguide/tutorial/configByDag" title="Different outcomes of build depending on chosen tasks">
             <sourcefile file="build.gradle"/>
             <output args="-q distribution"/>
             <output args="-q release" outputFile="configByDag.out"/>
         </sample>
-        <para>The important thing is, that the fact that the release task has been chosen, has an effect
-            <emphasis>before</emphasis> the release task gets executed. Nor has the release task to be the
-            <emphasis>primary</emphasis> task (i.e. the task passed to the <command>gradle</command> command).
+        <para>The important thing is that <literal>whenReady</literal> affects the release task <emphasis>before</emphasis> the release task is executed.
+        This works even when the release task is not the <emphasis>primary</emphasis> task (i.e., the task passed to the <command>gradle</command> command).
         </para>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
index 1d72e07..733ea45 100644
--- a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
@@ -154,6 +154,18 @@
                 <output args="-q tasks --all"/>
             </sample>
         </section>
+        <section id="sec:show_task_details">
+            <title>Show task usage details</title>
+            <para>Running <userinput>gradle help --task someTask</userinput> gives you detailed information about a specific task or multiple
+                tasks matching the given task name in your multiproject build <!--TODO RG ref multiproject-->
+                Below is an example of this detailed information:
+            </para>
+            <sample id="taskHelp" dir="userguide/tutorial/projectReports" title="Obtaining detailed help for tasks">
+                <output args="-q help --task libs"/>
+            </sample>
+            <para>This information includes the full task path, the task type, possible commandline options and the description of the given task.
+            </para>
+        </section>
         <section id="sec:listing_dependencies">
             <title>Listing project dependencies</title>
             <para id="para:commandline_dependency_report">Running <userinput>gradle dependencies</userinput>
diff --git a/subprojects/docs/src/docs/userguide/comparingBuilds.xml b/subprojects/docs/src/docs/userguide/comparingBuilds.xml
index 8d99896..06622b5 100644
--- a/subprojects/docs/src/docs/userguide/comparingBuilds.xml
+++ b/subprojects/docs/src/docs/userguide/comparingBuilds.xml
@@ -18,7 +18,7 @@
     <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.
+            Build comparison support is an <link linkend="feature_lifecycle">incubating</link> 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>
@@ -127,7 +127,7 @@
     <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.
+            As this is an <link linkend="feature_lifecycle">incubating</link> feature, a limited set of the eventual functionality has been implemented at this time.
         </para>
         <section>
             <title>Supported builds</title>
@@ -179,7 +179,7 @@
 }
         </programlisting>
         <para>
-            The above example configures a comparison between two different projects using two different Gradle versions.
+            The example above configures a comparison between two different projects using two different Gradle versions.
         </para>
         <section>
             <title>Trying Gradle upgrades</title>
@@ -230,4 +230,4 @@ compareGradleBuilds {
         </section>
 
     </section>
-</chapter>
\ No newline at end of file
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/cpp.xml b/subprojects/docs/src/docs/userguide/cpp.xml
deleted file mode 100755
index bee4ccc..0000000
--- a/subprojects/docs/src/docs/userguide/cpp.xml
+++ /dev/null
@@ -1,188 +0,0 @@
-<!--
-  ~ Copyright 2010 the original author or authors.
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<chapter id='cpp'>
-    <title>C++ Support</title>
-
-    <note>
-        <para>
-            The Gradle C++ support is in very early stages of development. Please be aware that the DSL and other configuration may change in later Gradle versions.
-        </para>
-    </note>
-
-    <para>
-        The C++ plugins add support for building software comprised of C++ source code, and managing the process of building “native” software in general.
-        While many excellent build tools exist for this space of software development, Gradle brings the dependency management practices more traditionally
-        found in the JVM development space to C++ developers.
-    </para>
-    <para>
-        The following platforms are supported:
-    </para>
-    <table>
-        <thread>
-            <tr><td>Operating System</td><td>Compiler</td><td>Notes</td></tr>
-        </thread>
-        <tr>
-            <td>Linux</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td><td>Tested with GCC 4.6.1 on Ubuntu 11.10</td>
-        </tr>
-        <tr>
-            <td>Mac OS X</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td><td>Tested with XCode 4.2.1 on OS X 10.7</td>
-        </tr>
-        <tr>
-            <td>Windows</td><td><ulink url="http://www.microsoft.com/visualstudio/en-us">Visual C++</ulink></td><td>Tested with Windows 7 and Visual C++ 2010</td>
-        </tr>
-        <tr>
-            <td>Windows</td><td><ulink url="http://www.mingw.org/">MinGW</ulink></td><td>Tested with Windows 7 and MinGW 4.6.2. Note: G++ support is currently broken under cygwin</td>
-        </tr>
-    </table>
-    <para>
-        Currently, there is no direct support for creating multiple variants of the same binary (e.g. 32 bit vs. 64 bit) and there is no direct
-        support for cross platform source configuration (à la <ulink url="http://www.gnu.org/s/autoconf/">autoconf</ulink>) at this time. Support for different
-        compiler chains, managing multiple variants and cross platform source configuration will be added over time, making Gradle a fully capable build tool for C++
-        (and other “native” language) projects.
-    </para>
-
-    <section>
-        <title>Usage</title>
-        <para>
-            The build scripts DSLs, model elements and tasks used to manage C++ projects are added by the <literal>cpp</literal> plugin. However, it is typically
-            more convenient to use either the <literal>cpp-lib</literal> or <literal>cpp-exe</literal> plugins that sit on top of the <literal>cpp</literal>
-            plugin to preconfigure the project to build either a shared library or executable binary respectively.
-        </para>
-        <sample id="useCppExePlugin" dir="cpp/dependencies" title="Using the 'cpp-exe' plugin">
-            <sourcefile file="build.gradle" snippet="use-plugin-exe"/>
-        </sample>
-        <sample id="useCppLibPlugin" dir="cpp/dependencies" title="Using the 'cpp-lib' plugin">
-            <sourcefile file="build.gradle" snippet="use-plugin-lib"/>
-        </sample>
-        <para>
-            The <literal>cpp-exe</literal> plugin configures the project to build a single executable (at <filename><replaceable>$buildDir</replaceable>/binaries/<replaceable>$project.name</replaceable></filename>) and
-            the <literal>cpp-lib</literal> plugin configures the project to build a single shared library (at <filename><replaceable>$buildDir</replaceable>/binaries/lib<replaceable>$project.name</replaceable>.so</filename>).
-        </para>
-    </section>
-
-    <section>
-        <title>Source code locations</title>
-        <para>
-            Both plugins configure the project to look for <filename>.cpp</filename> and <filename>.c</filename> source files in <filename>src/main/cpp</filename> and use the <filename>src/main/headers</filename>
-            directory as a header include root. For a library, the header files in <filename>src/main/headers</filename> are considered the “public” or “exported” headers.
-            Header files that should not be exported (but are used internally) should be placed inside the <filename>src/main/cpp</filename> directory (though be aware that
-            such header files should always be referenced in a manner relative to the file including them).
-        </para>
-        <para>
-            The <literal>cpp</literal> plugin is also very flexible in where it looks for source and header files, aand you can configure the above conventions to look however you
-            like.
-        </para>
-    </section>
-
-    <section>
-        <title>Compiling</title>
-        <para>
-            For both the <literal>cpp-lib</literal> and <literal>cpp-exe</literal> plugins, you can run <userinput>gradle compileMain</userinput> to compile and link the binary.
-        </para>
-        <section>
-            <title>Compiling on UNIX</title>
-            <para>
-                The UNIX C++ support is currently based on the <literal>g++</literal> tool which must be installed and on the <literal>PATH</literal>
-                for the Gradle process.
-            </para>
-        </section>
-        <section>
-            <title>Compiling on Windows</title>
-            <para>
-                The Windows C++ support can use either the MinGW <literal>g++</literal> or the Microsoft Visual C++ <literal>cl</literal> tool, either of which must be installed and on the
-                <literal>PATH</literal> for the Gradle process. Gradle searches first for Microsoft Visual C++, and then MinGW.
-            </para>
-        </section>
-    </section>
-
-    <section>
-        <title>Configuring the compiler</title>
-        <para>Arbitrary arguments can be provided to the compiler by using the following syntax:</para>
-        <sample id="gppArgs" dir="cpp/exe" title="Supplying arbitrary args to the compiler">
-            <sourcefile file="build.gradle" snippet="args"/>
-        </sample>
-        <para>
-            The above example applies to the <literal>cpp-exe</literal> plugin, to supply arguments for the <literal>cpp-lib</literal> plugin replace
-            “<literal>executables</literal>” with “<literal>libraries</literal>”.
-        </para>
-    </section>
-
-    <section>
-        <title>Working with shared libraries</title>
-        <para>
-            The C++ plugin provides an <literal>installMain</literal> task, which creates a development install of the executable, along with the shared libraries it requires.
-            This allows you to run the executable without needing to install the shared libraries in their final locations.
-        </para>
-    </section>
-
-    <section>
-        <title>Dependencies</title>
-        <para>
-            Dependencies for C++ projects are binary libraries that export header files. The header files are used during compilation, with the compiled
-            binary dependency being used during the linking.
-        </para>
-        <section>
-            <title>External Dependencies</title>
-            <para>
-                External dependencies (i.e. from a repository, not a subproject) must be specified using the following syntax:
-            </para>
-            <sample id="gppArgs" dir="cpp/dependencies" title="Declaring dependencies">
-                <sourcefile file="build.gradle" snippet="declaring-dependencies"/>
-            </sample>
-            <para>
-                Each dependency must be specified with the <literal>dependency</literal> method as above and must be declared as part of the source set. The
-                <literal>group</literal>, <literal>name</literal> and <literal>version</literal> arguments <emphasis>must</emphasis> be supplied.
-            </para>
-            <para>
-                For each declared dependency, two actual dependencies are created. One with the classifier “<literal>headers</literal>” and extension
-                “<literal>zip</literal>” which is a zip file of the exported headers, and another with the classifier “<literal>so</literal>” and extension
-                “<literal>so</literal>” which is the compiled library binary to link against (which is supplied as a direct input to the g++ link operation).
-            </para>
-        </section>
-        <section>
-            <title>Project Dependencies</title>
-            <para>
-                The notation for project dependencies is slightly different.
-            </para>
-            <sample id="cppProjectDependencies" dir="cpp/exewithlib" title="Declaring project dependencies">
-                <sourcefile file="build.gradle" snippet="project-dependencies"/>
-            </sample>
-        </section>
-    </section>
-
-    <section>
-        <title>Publishing</title>
-        <para>
-            The <literal>cpp-exe</literal> and <literal>cpp-lib</literal> plugins configure their respective output binaries to be publishable as part of the
-            <literal>archives</literal> configuration. To publish, simply configure the <literal>uploadArchives</literal> task as per usual.
-        </para>
-        <sample id="cppPublish" dir="cpp/dependencies" title="Uploading exe or lib">
-            <sourcefile file="build.gradle" snippet="upload"/>
-        </sample>
-        <para>
-            The <literal>cpp-exe</literal> plugin publishes a single artifact with extension “<literal>exe</literal>”. The <literal>cpp-lib</literal> plugin
-            publishes two artifacts; one with classifier “<literal>headers</literal>” and extension “<literal>zip</literal>”, and one with classifier
-            “<literal>so</literal>” and extension “<literal>so</literal>” (which is the format used when consuming dependencies).
-        </para>
-        <note>
-            <para>
-                Currently, there is no support for publishing the dependencies of artifacts in POM or Ivy files. Future versions will support this.
-            </para>
-        </note>
-    </section>
-
-</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/customPlugins.xml b/subprojects/docs/src/docs/userguide/customPlugins.xml
index 5281c33..3b16711 100644
--- a/subprojects/docs/src/docs/userguide/customPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/customPlugins.xml
@@ -130,7 +130,7 @@
         <para>
             In this example, we configure the <literal>greet</literal> task <literal>destination</literal> property as a closure, which is evaluated with
             the <apilink class="org.gradle.api.Project" method="file(java.lang.Object)"/> method to turn the return value of the closure into a file object
-            at the last minute. You will notice that in the above example we specify the <literal>greetingFile</literal> property value after we have 
+            at the last minute. You will notice that in the example above we specify the <literal>greetingFile</literal> property value after we have 
             configured to use it for the task. This kind of lazy evaluation is a key benefit of accepting any value when setting a file property, then 
             resolving that value when reading the property.
         </para>
diff --git a/subprojects/docs/src/docs/userguide/customTasks.xml b/subprojects/docs/src/docs/userguide/customTasks.xml
index c8578ae..94afb48 100644
--- a/subprojects/docs/src/docs/userguide/customTasks.xml
+++ b/subprojects/docs/src/docs/userguide/customTasks.xml
@@ -158,4 +158,146 @@
             </sample>
         </section>
     </section>
+    <section id='incremental_tasks'>
+        <title>Incremental tasks</title>
+        <note>
+            <para>
+                Incremental tasks are an <link linkend="feature_lifecycle">incubating</link> feature.
+            </para>
+            <para>
+                Since the introduction of the implementation described above (early in the Gradle 1.6 release cycle), discussions within the Gradle community have produced
+                superior ideas for exposing the information about changes to task implementors to what is described below. As such, the API for this feature will almost certainly
+                change in upcoming releases. However, please do experiment with the current implementation and share your experiences with the Gradle community.
+            </para>
+            <para>
+                The feature incubation process, which is part of the Gradle feature lifecycle (see <xref linkend="feature_lifecycle"/>), exists for this purpose of ensuring high quality
+                final implementation through incorporation of early user feedback.
+            </para>
+        </note>
+        <para>
+            With Gradle, it's very simple to implement a task that gets skipped when all of it's inputs and outputs are up to date (see <xref linkend="sec:up_to_date_checks"/>).
+            However, there are times when only a few input files have changed since the last execution, and you'd like to avoid reprocessing all of the unchanged inputs.
+            This can be particularly useful for a transformer task, that converts input files to output files on a 1:1 basis.
+        </para>
+        <para>
+            If you'd like to optimise your build so that only out-of-date inputs are processed, you can do so with an <firstterm>incremental task</firstterm>.
+        </para>
+        <section>
+            <title>Implementing an incremental task</title>
+            <para>
+                For a task to process inputs incrementally, that task must contain an <firstterm>incremental task action</firstterm>. This is a task action method that contains a
+                single <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs"/> parameter, which indicates to Gradle that the action will process the changed inputs only.
+            </para>
+            <para>
+                The incremental task action may supply an <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="outOfDate"/> action for processing any input file that is out-of-date,
+                and a <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="removed"/> action that executes for any input file that has been removed since the previous execution.
+            </para>
+            <sample id="taskDefinition" dir="userguide/tasks/incrementalTask" title="Defining an incremental task action" includeLocation="true">
+                <sourcefile file="build.gradle" snippet="incremental-task" />
+            </sample>
+            <para>
+                For a simple transformer task like this, the task action simply needs to generate output files for any out-of-date inputs,
+                and delete output files for any removed inputs.
+            </para>
+            <para>
+                A task may only contain a single incremental task action.
+            </para>
+        </section>
+        <section>
+            <title>Which inputs are considered out of date?</title>
+            <para>
+                When Gradle has history of a previous task execution, and the only changes to the task execution context since that execution are to input files,
+                then Gradle is able to determine which input files need to be reprocessed by the task.
+                In this case, the <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="outOfDate"/> action will be executed for any input file that was <emphasis>added</emphasis> or <emphasis>modified</emphasis>,
+                and the <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="removed"/> action will be executed for any <emphasis>removed</emphasis> input file.
+            </para>
+            <para>
+                However, there are many cases where Gradle is unable to determine which input files need to be reprocessed. Examples include:
+            </para>
+            <itemizedlist>
+                <listitem>There is no history available from a previous execution.</listitem>
+                <listitem>You are building with a different version of Gradle. Currently, Gradle does not use task history from a different version.</listitem>
+                <listitem>An <literal>upToDateWhen</literal> criteria added to the task returns <literal>false</literal>.</listitem>
+                <listitem>An input property has changed since the previous execution.</listitem>
+                <listitem>One or more output files have changed since the previous execution.</listitem>
+            </itemizedlist>
+            <para>
+                In any of these cases, Gradle will consider all of the input files to be <literal>outOfDate</literal>.
+                The <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="outOfDate"/> action will be executed for every input file, and the
+                <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="removed"/> action will not be executed at all.
+            </para>
+            <para>
+                You can check if Gradle was able to determine the incremental changes to input files with <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="isIncremental"/>.
+            </para>
+        </section>
+        <section>
+            <title>An incremental task in action</title>
+            <para>
+                Given the incremental task implementation <link linkend="taskDefinition">above</link>, we can explore the various change scenarios by example.
+                Note that the various mutation tasks ('updateInputs', 'removeInput', etc) are only present for demonstration purposes: these would not normally be part of your build script.
+            </para>
+            <para>
+                First, consider the <literal>IncrementalReverseTask</literal> executed against a set of inputs for the first time.
+                In this case, all inputs will be considered "out of date":
+            </para>
+            <sample id="incrementalTaskFirstRun" dir="userguide/tasks/incrementalTask" title="Running the incremental task for the first time">
+                <sourcefile file="build.gradle" snippet="reverse"/>
+                <layout after="originalInputs">
+                    build.gradle
+                    inputs/
+                    inputs/1.txt
+                    inputs/2.txt
+                    inputs/3.txt
+                </layout>
+                <output args="-q incrementalReverse" ignoreLineOrder="true"/>
+            </sample>
+            <para>
+                Naturally when the task is executed again with no changes, then task itself is up to date and no files are reported to the task action:
+            </para>
+            <sample id="incrementalTaskNoChange" dir="userguide/tasks/incrementalTask" title="Running the incremental task with unchanged inputs">
+                <test args="-q originalInputs incrementalReverse"/>
+                <output args="-q incrementalReverse"/>
+            </sample>
+
+            <para>
+                When an input file is modified in some way or a new input file is added, then re-executing the task results in those files being reported to <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="outOfDate"/>:
+            </para>
+            <sample id="incrementalTaskUpdatedInputs" dir="userguide/tasks/incrementalTask" title="Running the incremental task with updated input files">
+                <sourcefile file="build.gradle" snippet="updated-inputs" />
+                <test args="-q originalInputs incrementalReverse"/>
+                <output args="-q updateInputs incrementalReverse" ignoreLineOrder="true"/>
+            </sample>
+
+            <para>
+                When an existing input file is removed, then re-executing the task results that file being reported to <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="removed"/>:
+            </para>
+            <sample id="incrementalTaskRemovedInput" dir="userguide/tasks/incrementalTask" title="Running the incremental task with an input file removed">
+                <sourcefile file="build.gradle" snippet="removed-input" />
+                <test args="-q originalInputs incrementalReverse"/>
+                <output args="-q removeInput incrementalReverse" ignoreLineOrder="true"/>
+            </sample>
+
+            <para>
+                When an output file is deleted (or modified), then Gradle is unable to determine which input files are out of date.
+                In this case, <emphasis>all</emphasis> input files are reported to the <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="outOfDate"/> action,
+                and no input files are reported to the <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="removed"/> action:
+            </para>
+            <sample id="incrementalTaskRemovedOutput" dir="userguide/tasks/incrementalTask" title="Running the incremental task with an output file removed">
+                <sourcefile file="build.gradle" snippet="removed-output" />
+                <test args="-q originalInputs incrementalReverse"/>
+                <output args="-q removeOutput incrementalReverse" ignoreLineOrder="true"/>
+            </sample>
+
+            <para>
+                When a task input property modified, Gradle is not able to determine how this property impacted the task outputs, so all input files are assumed to be out of date.
+                So similar to the changed output file example, <emphasis>all</emphasis> input files are reported to
+                the <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="outOfDate"/> action,
+                and no input files are reported to the <apilink class="org.gradle.api.tasks.incremental.IncrementalTaskInputs" method="removed"/> action:
+            </para>
+            <sample id="incrementalTaskChangedProperty" dir="userguide/tasks/incrementalTask" title="Running the incremental task with an input property changed">
+                <test args="-q originalInputs incrementalReverse"/>
+                <output args="-q -PtaskInputProperty=changed incrementalReverse" ignoreLineOrder="true"/>
+            </sample>
+        </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 df42f66..7fe1cf6 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -19,7 +19,7 @@
         <title>Introduction</title>
         <para>Dependency management is a critical feature of every build, and Gradle has placed an emphasis on offering first-class dependency management that is both easy-to-understand and
             compatible with a wide variety of approaches. If you are familiar with the approach used by either Maven or Ivy you will be delighted to learn that Gradle is fully compatible with both
-            approaches in addition to being flexible enough to support fully-customised approaches.
+            approaches in addition to being flexible enough to support fully-customized approaches.
         </para>
 
         <para>Here are the major highlights of Gradle's support for dependency management:</para>
@@ -38,7 +38,7 @@
                 </para>
             </listitem>
             <listitem>
-                <para><emphasis>A fully customisable approach to Dependency Resolution</emphasis>: Gradle provides you with the ability to customize resolution rules making dependency substitution
+                <para><emphasis>A fully customizable approach to Dependency Resolution</emphasis>: Gradle provides you with the ability to customize resolution rules making dependency substitution
                     easy.
                 </para>
             </listitem>
@@ -87,7 +87,7 @@
             <para>Both tools rely on descriptor XML files, which contain information about the dependencies of a 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. Both have emerged as standards for solving dependency conflicts, and while
                 Gradle originally used Ivy under the hood for its dependency management. Gradle has replaced this direct dependency on Ivy with a native Gradle dependency resolution engine which
-                supports a range of approached to dependency resolution including both POM and Ivy descriptor files.
+                supports a range of approaches to dependency resolution including both POM and Ivy descriptor files.
             </para>
         </section>
     </section>
@@ -153,27 +153,26 @@
                 and the often accidental order of the classpath will determine what version of a dependency will win. On a large project with many developers changing dependencies, successful builds
                 will be few and far between as the order of dependencies may directly affect whether a build succeeds or fails (or whether a bug appears or disappears in production).
             </para>
-            <para>If you haven't had to deal with the curse of conflicting versions of jars on a classpath, here's a small example of the fun that awaits you. Consider a large project with 30
-                submodules, adding a dependency with a particular version to a subproject changes the order of a classpath, swapping an old version of Spring 2.4 for a newer version Spring 2.5. While
-                the build may continue to work, developers are starting to notice all sorts of surprising (and surprisingly awful) bugs in production. Worse yet, this unintentional downgrade of Spring
-                introduced several security vulnerabilities into the system which now require a full security audit throughout the organization.
+            <para>If you haven't had to deal with the curse of conflicting versions of jars on a classpath, here is a small anecdote of the fun that awaits you. In a large project with 30
+                submodules, adding a dependency to a subproject changed the order of a classpath, swapping Spring 2.5 for an older 2.4 version. While
+                the build continued to work, developers were starting to notice all sorts of surprising (and surprisingly awful) bugs in production. Worse yet, this unintentional downgrade of Spring
+                introduced several security vulnerabilities into the system, which now required a full security audit throughout the organization.
             </para>
-            <para>In short, version conflicts are bad, manage your transitive dependencies to avoid them. You might also want to learn where conflicting versions are used and consolidate on a
-                particular version of a dependency across your organization. With a good conflict reporting tool like Gradle that information can be used to communicate with the entire organization
-                and standardise on a single version.
+            <para>In short, version conflicts are bad, and you should manage your transitive dependencies to avoid them. You might also want to learn where conflicting versions are used and consolidate on a
+                particular version of a dependency across your organization. With a good conflict reporting tool like Gradle, that information can be used to communicate with the entire organization
+                and standardize on a single version.
                 <emphasis>If you think version conflicts don't happen to you, think again.</emphasis>
                 It is very common for different first-level dependencies to rely on a range of different overlapping versions for other dependencies, and the JVM doesn't yet offer an 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:</para>
+            <para>Gradle offers the following conflict resolution strategies:</para>
             <itemizedlist>
                 <listitem>
-                    <emphasis>Newest</emphasis> - used by default by Gradle - the newest version of the dependency is used. This has been Gradle's approach since the beginning of the project, and
-                    while it isn't appropriate in every situation, this is why Gradle provides you with various options for resolving conflicts.
+                    <emphasis>Newest</emphasis>: The newest version of the dependency is used. This is Gradle's default strategy, and is often an appropriate choice as long as versions are backwards-compatible.
                 </listitem>
                 <listitem>
-                    <emphasis>Fail</emphasis> - fail eagerly on version conflict. Useful if you need extra control over dependencies and if you need to manage version conflicts manually. See
-                    <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/> for reference on managing the conflict resolution strategies.
+                    <emphasis>Fail</emphasis>: A version conflict results in a build failure. This strategy enforces that all version conflicts are resolved explicitly in the build script. See
+                    <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/> for details on how to explicitly choose a particular version.
                 </listitem>
             </itemizedlist>
             <para>While the strategies introduced above are usually enough to solve most conflicts, Gradle provides more fine-grained mechanisms to resolve version conflicts:</para>
@@ -188,7 +187,7 @@
                     See examples in <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
                 </listitem>
                 <listitem>
-                    Dependency resolve rules are an incubating feature introduced in Gradle 1.4 which give you fine-grained control over the version selected for a particular dependency.
+                    Dependency resolve rules are an <link linkend="feature_lifecycle">incubating</link> feature introduced in Gradle 1.4 which give you fine-grained control over the version selected for a particular dependency.
                 </listitem>
             </itemizedlist>
             <para>To deal with problems due to version conflicts, reports with dependency graphs are also very helpful. Such reports are another feature of dependency management.</para>
@@ -220,7 +219,7 @@
             Many Gradle plugin add pre-defined configurations to your project. The Java plugin, for example,
             adds some configurations to represent the various classpaths it needs. see
             <xref linkend='sec:java_plugin_and_dependency_management'/>
-            for details. Of course you can add your add custom configurations on top of that. There are many use cases
+            for details. Of course you can add custom configurations on top of that. There are many use cases
             for custom configurations. This is very handy for example for adding dependencies not needed for
             building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution).
         </para>
@@ -306,47 +305,43 @@
             <sample id="moduleDependencies" dir="userguide/artifacts/externalDependencies" title="Module dependencies">
                 <sourcefile file="build.gradle" snippet="module-dependencies"/>
             </sample>
-            <para>Please see the
-                <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
-                for more examples and complete reference. Please read on to get thorough understanding of the Gradle's dependency management.
+            <para>See <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
+                for more examples and a complete reference.
             </para>
             <para>Gradle provides different notations for module dependencies. There is a string notation and
                 a map notation. A module dependency has an API which allows for further configuration. Have a look at
                 <apilink class='org.gradle.api.artifacts.ExternalModuleDependency'/>
                 to learn all about the API.
                 This API provides properties and configuration methods. Via the string notation you can define a subset
-                the properties. With the map notation you can define all properties. To have access to the complete API,
+                of the properties. With the map notation you can define all properties. To have access to the complete API,
                 either with the map or with the string notation, you can assign a single dependency to a configuration
                 together with a closure.
             </para>
             <para>If you declare a module dependency, Gradle looks for a corresponding module descriptor file (<filename>pom.xml</filename> or
                 <filename>ivy.xml</filename>) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of
-                this module (e.g.<filename>hibernate-3.0.5.jar</filename>) as well as its dependencies (e.g. cglib) are downloaded. If no such
+                this module (e.g. <filename>hibernate-3.0.5.jar</filename>) as well as its dependencies (e.g. cglib) are downloaded. If no such
                 module descriptor file exists, Gradle looks for a file called <filename>hibernate-3.0.5.jar</filename>
-                to retrieve. In Maven a module can only have one and only one artifact. In Gradle and Ivy a module can have multiple artifacts.
+                to retrieve. In Maven, a module can have one and only one artifact. In Gradle and Ivy, a module can have multiple artifacts.
                 Each artifact can have a different set of dependencies.
             </para>
             <section id='ssub:multi_artifact_dependencies'>
                 <title>Depending on modules with multiple artifacts</title>
-                As mentioned earlier, a Maven module has only one artifact. So, when your project depends on a Maven module it's obvious what artifact is the actual dependency.
-                With Gradle or Ivy the case is different. Ivy model of dependencies (<filename>ivy.xml</filename>) can declare multiple artifacts.
-                For more information, see Ivy reference for<filename>ivy.xml</filename>.
-                In Gradle, when you declare a dependency on an ivy module you actually declare dependency on the '<literal>default</literal>' configuration of that module.
-                So the actual list of artifacts (typically jars) your project depends on, are all artifacts that are attached to the
-                <literal>default</literal> configuration of that module.
-                This is very important in following exemplary use cases:
+                As mentioned earlier, a Maven module has only one artifact. Hence, when your project depends on a Maven module, it's obvious what its artifact is.
+                With Gradle or Ivy, the case is different. Ivy's dependency descriptor (<filename>ivy.xml</filename>) can declare multiple artifacts.
+                For more information, see the Ivy reference for <filename>ivy.xml</filename>.
+                In Gradle, when you declare a dependency on an Ivy module, you actually declare a dependency on the <literal>default</literal> configuration of that module.
+                So the actual set of artifacts (typically jars) you depend on is the set of artifacts that are associated with the
+                <literal>default</literal> configuration of that module. Here are some situations where this matters:
                 <itemizedlist>
-                    <listitem>The <literal>default</literal> configuration of some module contains some artifacts
-                        you don't want on the classpath. You might need to configure a dependency on specific artifact(s) of given module,
-                        rather than pulling all artifacts of the <literal>default</literal> dependency
+                    <listitem>The <literal>default</literal> configuration of a module contains undesired artifacts. Rather than depending on the
+                        whole configuration, a dependency on just the desired artifacts is declared.
                     </listitem>
-                    <listitem>The artifact you need on the classpath has been published in a different configuration
-                        than the <literal>default</literal> one. This means this artifact will not be pulled in by Gradle.
-                        Unless you explicitly declare what configuration of the module you depend on.
+                    <listitem>The desired artifact belongs to a configuration other than <literal>default</literal>. That configuration is explicitly named
+                        as part of the dependency declaration.
                     </listitem>
                 </itemizedlist>
-                There are other situations where it is necessary to fine-tune the dependency declaration.
-                Please see the <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/> for examples and complete reference on declaring dependencies.
+                There are other situations where it is necessary to fine-tune dependency declarations.
+                Please see <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/> for examples and a complete reference for declaring dependencies.
             </section>
             <section id='ssub:artifact_dependencies'>
                 <title>Artifact only notation</title>
@@ -369,7 +364,7 @@
                 <para>The Maven dependency management has the notion of classifiers.
                     <footnote>
                         <para>
-                            <ulink url='http://www.sonatype.com/books/maven-book/reference/pom-relationships-sect-project-relationships.html'/>
+                            <ulink url='http://books.sonatype.com/mvnref-book/reference/pom-relationships-sect-project-relationships.html'/>
                         </para>
                     </footnote>
                     Gradle supports this. To retrieve classified dependencies from a Maven repository you can write:
@@ -377,11 +372,11 @@
                 <sample id="classifier" dir="userguide/artifacts/excludesAndClassifiers" title="Dependency with classifier">
                     <sourcefile file="build.gradle" snippet="classifier"/>
                 </sample>
-                <para>As you can see in the example, classifiers can be used together with setting an explicit extension (artifact only notation).
+                <para>As can be seen in the first line above, classifiers can be used together with artifact only notation.
                 </para>
             </section>
-            <para>To use the external dependencies of a configuration:</para>
-            <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Usage of external dependency of a configuration">
+            <para>It is easy to iterate over the dependency artifacts of a configuration:</para>
+            <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Iterating over a configuration">
                 <sourcefile file="build.gradle" snippet="use-configuration"/>
                 <output args="-q listJars"/>
             </sample>
@@ -389,13 +384,13 @@
 
         <section id='sub:client_module_dependencies'>
             <title>Client module dependencies</title>
-            <para>Client module dependencies enable you to declare <emphasis>transitive</emphasis> dependencies directly in your build script. They are a replacement for a module descriptor
-                XML file in an external repository.
+            <para>Client module dependencies allow to declare <emphasis>transitive</emphasis> dependencies directly in the build script. They are a replacement for a module descriptor
+                in an external repository.
             </para>
             <sample id="client-modules" dir="userguide/artifacts/externalDependencies" title="Client module dependencies - transitive dependencies">
                 <sourcefile file="build.gradle" snippet="client-modules"/>
             </sample>
-            <para>This declares a dependency of your project on Groovy. Groovy itself has dependencies. But Gradle does
+            <para>This declares a dependency on Groovy. Groovy itself has dependencies. But Gradle does
                 not look for an XML descriptor to figure them out but gets the information from the build file. The
                 dependencies of a client module can be normal module dependencies or artifact dependencies or another
                 client module. Have also a look at the API documentation:
@@ -554,7 +549,7 @@
             </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 dependency reports (see the previous paragraph) are using this API under the covers.
                 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
@@ -658,6 +653,12 @@
             </tr>
             <tr>
                 <td>
+                    <link linkend="sub:maven_jcenter">Maven JCenter repository</link>
+                </td>
+                <td>A pre-configured repository that looks for dependencies in Bintray's JCenter.</td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend="sub:maven_local">Maven local repository</link>
                 </td>
                 <td>A pre-configured repository that looks for dependencies in the local Maven repository.</td>
@@ -693,6 +694,19 @@
             </para>
         </section>
 
+        <section id='sub:maven_jcenter'>
+            <title>Maven JCenter repository</title>
+            <para><ulink url='http://bintray.com'>Bintray</ulink>'s JCenter is an up-to-date collection of all popular Maven OSS artifacts, including artifacts published directly to Bintray.
+            </para>
+            <para>To add the JCenter Maven repository (<ulink url='http://jcenter.bintray.com'/>) simply add this to your build script:
+            </para>
+            <sample id="mavenJcenter" dir="userguide/artifacts/defineRepository" title="Adding Bintray's JCenter Maven repository">
+                <sourcefile file="build.gradle" snippet="maven-jcenter"/>
+            </sample>
+            <para>Now Gradle will look for your dependencies in the JCenter repository.
+            </para>
+        </section>
+
         <section id='sub:maven_local'>
             <title>Local Maven repository</title>
             <para>To use the local Maven cache as a repository you can do:</para>
@@ -918,7 +932,7 @@ someroot/[artifact]-[revision].[ext]
         <section id='sec:dependency_resolve_rules'>
             <title>Using dependency resolve rules</title>
             <para>A dependency resolve rule is executed for each resolved dependency, and offers a powerful api for manipulating a requested dependency prior to that dependency being resolved.
-                This feature is incubating, but currently offers the ability to change the group, name and/or version of a requested dependency,
+                This feature is <link linkend="feature_lifecycle">incubating</link>, but currently offers the ability to change the group, name and/or version of a requested dependency,
                 allowing a dependency to be substituted with a completely different module during resolution.
             </para>
             <para>
@@ -1007,6 +1021,38 @@ someroot/[artifact]-[revision].[ext]
                 <sourcefile file="build.gradle" snippet="ivy-repo-dynamic-mode"/>
             </sample>
         </section>
+        <section id="component_metadata_rules">
+            <title>Component metadata rules</title>
+            <para>Each module (also called <emphasis>component</emphasis>) has metadata associated with it, such as its group, name, version, dependencies, and so on.
+                This metadata typically originates in the module's descriptor. Metadata rules allow certain parts of a module's metadata to be manipulated
+                from within the build script. They take effect after a module's descriptor has been downloaded, but before it has been selected among all candidate versions.
+                This makes metadata rules another instrument for customizing dependency resolution.
+            </para>
+            <para>
+                One piece of module metadata that Gradle understands is a module's <emphasis>status scheme</emphasis>. This concept, also known from Ivy, models the different
+                levels of maturity that a module transitions through over time. The default status scheme, ordered from least to most mature status, is <literal>integration</literal>,
+                <literal>milestone</literal>, <literal>release</literal>. Apart from a status scheme, a module also has a (current) <emphasis>status</emphasis>, which must be one of
+                the values in its status scheme. If not specified in the (Ivy) descriptor, the status defaults to <literal>integration</literal> for Ivy modules and Maven snapshot modules,
+                and <literal>release</literal> for Maven modules that aren't snapshots.
+            </para>
+            <para>
+                A module's status and status scheme are taken into consideration when a <literal>latest</literal> version selector is resolved. Specifically, <literal>latest.someStatus</literal>
+                will resolve to the highest module version that has status <literal>someStatus</literal> or a more mature status. For example, with the default status scheme in place,
+                <literal>latest.integration</literal> will select the highest module version regardless of its status (because <literal>integration</literal> is the least mature status),
+                whereas <literal>latest.release</literal> will select the highest module version with status <literal>release</literal>. Here is what this looks like in code:
+            </para>
+            <sample id="latestSelector" dir="userguide/artifacts/componentMetadata" title="'Latest' version selector">
+                <sourcefile file="build.gradle" snippet="latest-selector"/>
+                <output args="-q listFish"/>
+            </sample>
+            <para>
+                The next example demonstrates <literal>latest</literal> selectors based on a custom status scheme declared in a module metadata rule:
+            </para>
+            <sample id="customStatusScheme" dir="userguide/artifacts/componentMetadata" title="Custom status scheme">
+                <sourcefile file="build.gradle" snippet="custom-status-scheme"/>
+                <output args="-q listBirds"/>
+            </sample>
+        </section>
     </section>
     <section id='sec:dependency_cache'>
         <title>The dependency cache</title>
@@ -1112,7 +1158,7 @@ someroot/[artifact]-[revision].[ext]
                 </para>
                 <para>The <literal>--refresh-dependencies</literal> option tells Gradle to ignore all cached entries for resolved modules and artifacts.
                     A fresh resolve will be performed against all configured repositories, with dynamic versions recalculated, modules refreshed, and artifacts downloaded.
-                    However, where possible Gradle will attempt to if the previously downloaded artifacts are valid before downloading again.
+                    However, where possible Gradle will check if the previously downloaded artifacts are valid before downloading again.
                     This is done by comparing published SHA1 values in the repository with the SHA1 values for existing downloaded artifacts.
                 </para>
             </section>
@@ -1186,6 +1232,10 @@ someroot/[artifact]-[revision].[ext]
             libraries and you need a backup. It is another layer of indirection. Another source of information
             you have to lookup. All this is not really a big deal but in its sum it has an impact. Repository Manager like
             Artifactory or Nexus make this easier. But for example open source projects don't usually have a host for those products.
+            This is changing with new services like <ulink url='http://bintray.com'>Bintray</ulink> that let developers host and
+            distribute their release binaries using a self-service repository platform. Bintray also supports sharing approved artifacts
+            though the <ulink url='http://jcenter.bintray.com'>JCenter</ulink> public repository to provide a single resolution address for
+            all popular OSS java artifacts (see <xref linkend="sub:maven_jcenter"/>).
         </para>
         <para>This is a reason why some projects prefer to store their libraries in their version control system. This
             approach is fully supported by Gradle. The libraries can be stored in a flat directory without any XML module
diff --git a/subprojects/docs/src/docs/userguide/distributionPlugin.xml b/subprojects/docs/src/docs/userguide/distributionPlugin.xml
index 5d453b9..355db07 100644
--- a/subprojects/docs/src/docs/userguide/distributionPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/distributionPlugin.xml
@@ -18,11 +18,13 @@
     <title>The Distribution Plugin</title>
     <note>
         <para>
-            The distribution plugin is incubating (see <xref linkend="sec:incubating_state"/>).
+            The distribution plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
         </para>
     </note>
-    <para>The distribution plugin extends the language plugins with common distribution related tasks.
-	It allows bundling a project including binaries, sources and documentation.
+
+    <para>
+        The distribution plugin facilitates building archives that serve as distributions of the project.
+        Distribution archives typically contain then executable application and other supporting files, such as documentation.
 	</para>
 
     <section>
@@ -31,18 +33,24 @@
         <sample id="useDistributionPlugin" dir="userguide/distribution" title="Using the distribution plugin">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
-        <para>To define the name for the distribution you have to set the <literal>baseName</literal> property as shown below</para>
-        <sample id="configureDistributionName" dir="userguide/distribution" title="Configure the distribution name">
-            <sourcefile file="build.gradle" snippet="name-conf"/>
-        </sample>
-        <para>The plugin build a distribution for your project. You can run <userinput>gradle distZip</userinput> to create a
-            ZIP containing the distribution.  Given that the project name is myproject and version is 1.2, then running gradle customDistZip will produce a ZIP file called myproject-1.2.zip
+        <para>
+            The plugin adds an extension named "<literal>distributions</literal>" of type <apilink class="org.gradle.api.distribution.DistributionContainer" /> to the project.
+            It also creates a single distribution in the distributions container extension named "<literal>main</literal>".
+            If your build only produces one distribution you only need to configure this distribution (or use the defaults).
+        </para>
+        <para>
+            You can run "<userinput>gradle distZip</userinput>" to package the main distribution as a ZIP, or "<userinput>gradle distTar</userinput>" to create
+            a GZip compressed TAR file.
+            The files will be created at "<literal><replaceable>$buildDir</replaceable>/distributions/<replaceable>$project.name</replaceable>-<replaceable>$project.version</replaceable>.<replaceable>«ext»</replaceable></literal>".
+        </para>
+        <para>
+            You can run "<userinput>gradle installDist</userinput>" to assembles the distribution content, uncompressed, into "<literal><replaceable>$buildDir</replaceable>/install/<replaceable>main</replaceable></literal>".
         </para>
     </section>
 
     <section>
         <title>Tasks</title>
-        <para>The Distribution plugin adds the following tasks to the project.</para>
+        <para>The Distribution plugin adds the following tasks to the project:</para>
         <table>
             <title>Distribution plugin - tasks</title>
             <thead>
@@ -53,71 +61,137 @@
                     <td>Description</td>
                 </tr>
             </thead>
-		<tr>
-            <td>
-                <literal>distZip</literal>
-            </td>
-            <td>
-                <literal>-</literal>
-            </td>
-            <td>
-                <apilink class="org.gradle.api.tasks.bundling.Zip"/>
-            </td>
-                <td>Creates a full distribution ZIP archive.</td>
+            <tr>
+                <td>
+                    <literal>distZip</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.bundling.Zip"/>
+                </td>
+                <td>
+                    Creates a ZIP archive of the distribution contents
+                </td>
             </tr>
-        <tr>
-            <td>
-               <literal>distTar</literal>
-            </td>
-            <td>
-               <literal>-</literal>
-            </td>
-            <td>
-                <apilink class="org.gradle.api.tasks.bundling.Tar"/>
-            </td>
-                <td>Creates a full distribution TAR archive.</td>
+            <tr>
+                <td>
+                   <literal>distTar</literal>
+                </td>
+                <td>
+                   <literal>-</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.bundling.Tar"/>
+                </td>
+                <td>
+                    Creates a ZIP archive of the distribution contents
+                </td>
             </tr>
-        <tr>
-            <td>
-                <literal>installDist</literal>
-            </td>
-            <td>
-                <literal>-</literal>
-            </td>
-            <td>
-                <apilink class="org.gradle.api.tasks.Sync"/>
-            </td>
-            <td>Install distribution contents.</td>
+            <tr>
+                <td>
+                    <literal>installDist</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.Sync"/>
+                </td>
+                <td>
+                    Assembles the distribution content and installs it on the current machine
+                </td>
             </tr>
-
         </table>
-    </section>
-
-    <section>
-        <title>Configure distributions</title>
-        <para>The distribution plugin allow to configure distributions to include custom files and to change distribution baseName.
-        </para>
-        <sample id="customDistribution" dir="userguide/distribution" title="Declare multiple distributions">
+        <para>For each extra distribution set you add to the project, the distribution plugin adds the following tasks:</para>
+        <table>
+            <title>Multiple distributions - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal><replaceable>${distribution.name}</replaceable>DistZip</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.bundling.Zip"/>
+                </td>
+                <td>
+                    Creates a ZIP archive of the distribution contents
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><replaceable>${distribution.name}</replaceable>DistTar</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.bundling.Tar"/>
+                </td>
+                <td>
+                    Creates a TAR archive of the distribution contents
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>install<replaceable>${distribution.name.capitalize()}</replaceable>Dist</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.Sync"/>
+                </td>
+                <td>
+                    Assembles the distribution content and installs it on the current machine
+                </td>
+            </tr>
+        </table>
+        <sample id="multipleDistribution" dir="userguide/distribution" title="Adding extra distributions">
             <sourcefile file="build.gradle" snippet="custom-distribution"/>
         </sample>
+        <para>
+            This will add following tasks to the project:
+            <itemizedlist>
+                <listitem>customDistZip</listitem>
+                <listitem>customDistTar</listitem>
+                <listitem>installCustomDist</listitem>
+            </itemizedlist>
+        </para>
+        <para>
+            Given that the project name is "<literal>myproject</literal>" and version "<literal>1.2</literal>", running "<userinput>gradle customDistZip</userinput>" will produce a
+            ZIP file named "<literal>myproject-custom-1.2.zip</literal>".
+        </para>
+        <para>
+            Running "<userinput>gradle installCustomDist</userinput>" will install the distribution contents into "<literal><replaceable>$buildDir</replaceable>/install/custom</literal>".
+        </para>
     </section>
-
     <section>
-        <title>Multiple distributions</title>
-        <para>The distribution plugin allow to generate multiple distributions.
-
+      <title>Distribution contents</title>
+        <para>
+            All of the files in the "<literal>src/<replaceable>$distribution.name</replaceable>/dist</literal>" directory will automatically be included in the distribution.
+            You can add additional files by configuring the <apilink class="org.gradle.api.distribution.Distribution" /> object that is part of the container.
         </para>
-        <sample id="multipleDistribution" dir="userguide/distribution" title="Declare multiple distributions">
-            <sourcefile file="build.gradle" snippet="declare-distribution"/>
+        <sample id="configureDistribution" dir="userguide/distribution" title="Configuring the main distribution">
+            <sourcefile file="build.gradle" snippet="configure-distribution"/>
         </sample>
-        <para>This will following tasks to the project : customDistZip, customDistTar, installcustomDist. Given that the project name is myproject, then running gradle customDistZip will produce a ZIP file called myproject-custom-1.2.zip
-            and running customDistTar will produce myproject-custom-1.2.tar. Running installcustomDist will install distribution contents into buildDir/install/custom.</para>
-    </section>
-
-    <section>
-            <title>Extension properties</title>
-            <para>The distribution plugin add an extension to the project, which you can use to configure its behaviour. See <apilink class="org.gradle.api.Project"/>.
-            </para>
+        <para>
+            In the example above, the content of the "<literal>src/readme</literal>" directory will be included in the distribution
+            (along with the files in the "<literal>src/dist/main</literal>" directory which are added by default).
+        </para>
+        <para>
+            The "<literal>baseName</literal>" property has also been changed. This will cause the distribution archives to be created with a different name.
+        </para>
     </section>
-
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/eclipsePlugin.xml b/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
index 3354d6f..e0e9c59 100644
--- a/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
+++ b/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
@@ -271,7 +271,7 @@
     <section>
         <title>Customizing the generated files</title>
         <para>
-            The Eclipse plugin allows you to customise the generated metadata files. The plugin provides a DSL for configuring model objects
+            The Eclipse plugin allows you to customize the generated metadata files. The plugin provides a DSL for configuring model objects
             that model the Eclipse view of the project. These model objects are then merged with the existing Eclipse XML metadata to ultimately
             generate new metadata. The model objects provide lower level hooks for working with domain objects representing the file content 
             before and after merging with the model configuration. They also provide a very low level hook for working directly with the raw 
@@ -301,7 +301,7 @@
                     <listitem>The <code>beforeMerged</code> hook is executed with a domain object representing the existing file</listitem>
                     <listitem>The existing content is merged with the configuration inferred from the Gradle build or defined explicitly in the eclipse DSL</listitem>
                     <listitem>The <code>whenMerged</code> hook is executed with a domain object representing contents of the file to be persisted</listitem>
-                    <listitem>The <code>withXml</code> hook is executed with a raw representation of the xml that will be persisted</listitem>
+                    <listitem>The <code>withXml</code> hook is executed with a raw representation of the XML that will be persisted</listitem>
                     <listitem>The final XML is persisted</listitem>
                 </orderedlist>
                 The following table lists the domain object used for each of the Eclipse model types:
diff --git a/subprojects/docs/src/docs/userguide/gradleDaemon.xml b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
index 696c2a7..bd945f5 100644
--- a/subprojects/docs/src/docs/userguide/gradleDaemon.xml
+++ b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
@@ -50,8 +50,8 @@
         <para>
             The Tooling API (see <xref linkend="embedding"/>)
             uses the daemon all the time, e.g. you cannot officially use the Tooling API without the daemon.
-            This means that if you use the STS Gradle plugin for Eclipse or new Intellij IDEA plugin (IDEA>10)
-            the daemon acts behind the hood.
+            This means that whenever you are using the STS Gradle plugin for Eclipse or the Gradle support in Intellij IDEA,
+            you're already using the Gradle Daemon.
         </para>
         <para>In future the daemon will offer more features:
             <itemizedlist>
diff --git a/subprojects/docs/src/docs/userguide/gradleWrapper.xml b/subprojects/docs/src/docs/userguide/gradleWrapper.xml
index 0f73982..3c1b519 100644
--- a/subprojects/docs/src/docs/userguide/gradleWrapper.xml
+++ b/subprojects/docs/src/docs/userguide/gradleWrapper.xml
@@ -57,33 +57,24 @@
     </para>
     <section id='sec:configuration'>
         <title>Configuration</title>
-        <para>If you run Gradle with <command>gradlew</command>, Gradle checks if a Gradle distribution for the wrapper
+        <para>If you run Gradle with <command>gradlew</command>, the wrapper checks if a Gradle distribution for the wrapper
             is available. If not it tries to download it, otherwise it delegates to the <command>gradle</command>
             command of this distribution with all the arguments passed originally to the <command>gradlew</command>
             command.
         </para>
-        <para>You can specify where the wrapper files should be stored (within your project directory):</para>
-        <sample id="wrapperCustomized" dir="userguide/wrapper/customized" title="Configuration of wrapper task">
-            <sourcefile file="build.gradle"/>
-            <layout after="wrapper">
-                gradlew
-                gradlew.bat
-                wrapper/wrapper.jar
-                wrapper/wrapper.properties
-            </layout>
-        </sample>
         <para>
-            You can specify the download URL of the wrapper distribution. You can also specify where the wrapper distribution
-            should be stored and unpacked (either within the project or within the Gradle user home dir). If the wrapper
-            is run and there is local archive of the wrapper distribution Gradle tries to download it and stores it at
-            the specified place. If there is no unpacked wrapper distribution Gradle unpacks the local archive of the
-            wrapper distribution at the specified place. All the configuration options have defaults except the version of the wrapper distribution.</para>
+            When you configure the <literal>Wrapper</literal> task, you can specify the Gradle version you wish to use. The <command>gradlew</command>
+            command will download the appropriate distribution from the Gradle repository.
+            Alternatively, you can specify the download URL of the Gradle distribution. The <command>gradlew</command> command will use this URL to download
+            the distribution.
+            If you specify neither a Gradle version or download URL, the <command>gradlew</command> command will by default download whichever version
+            of Gradle was used to generate the wrapper files.
+        </para>
         <para>For the details on how to configure the wrapper, see <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/>
         </para>
-        <para>If you don't
-            want any download to happen when your project is build via <command>gradlew</command>, simply add the Gradle
+        <para>If you don't want any download to happen when your project is build via <command>gradlew</command>, simply add the Gradle
             distribution zip to your version control at the location specified by your wrapper configuration.
-            Relative url is supported - you can specify a distribution file relative to the location of <literal>gradle-wrapper.properties</literal> file.
+            A relative URL is supported - you can specify a distribution file relative to the location of <literal>gradle-wrapper.properties</literal> file.
         </para>
         <para>If you build via the wrapper, any existing Gradle distribution installed on the machine is ignored.
         </para>
@@ -95,39 +86,4 @@
             What should always work is to execute <literal>sh gradlew</literal>.
         </para>
     </section>
-    <section id='sec:environment_variable'>
-        <title>Environment variable</title>
-        <para>Some rather exotic use cases might occur when working with the Gradle Wrapper. For example the continuous
-            integration server goes down during unzipping the Gradle distribution. As the distribution directory exists
-            <command>gradlew</command>
-            delegates to it but the distribution is corrupt. Or the zip-distribution was not properly downloaded. When
-            you have no admin right on the continuous integration server to remove the corrupt files, Gradle offers a
-            solution via environment variables.
-        </para>
-        <table>
-            <title>Gradle wrapper environment variables</title>
-            <thead>
-                <tr>
-                    <td>Variable Name</td>
-                    <td>Meaning</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>GRADLE_WRAPPER_ALWAYS_UNPACK</td>
-                <td>If set to <literal>true</literal>, the distribution directory gets always deleted when
-                    <command>gradlew</command>
-                    is run and the distribution zip is freshly unpacked. If the zip is not there, Gradle tries to
-                    download it.
-                </td>
-            </tr>
-            <tr>
-                <td>GRADLE_WRAPPER_ALWAYS_DOWNLOAD</td>
-                <td>If set to <literal>true</literal>, the distribution directory and the distribution zip gets always
-                    deleted when <command>gradlew</command>
-                    is run and the distribution zip is freshly downloaded.
-                </td>
-            </tr>
-
-        </table>
-    </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/groovyPlugin.xml b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
index b33bb14..8754dab 100644
--- a/subprojects/docs/src/docs/userguide/groovyPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
@@ -153,21 +153,13 @@
 
     <section>
         <title>Dependency management</title>
-        <para>Because Gradle itself is partly implemented in Groovy, and its build language is also based on Groovy, Gradle already ships
-            with a Groovy library (1.8.6 as of Gradle 1.3). Nevertheless, Groovy projects need to explicitly add a Groovy dependency to
-            the appropriate configuration(s). This dependency, which can be the same or a different Groovy version than the one used internally by Gradle,
-            will then be used as a compile and runtime dependency for the project's Groovy code. It will also be used to execute the Groovy
-            compiler and Groovydoc tool, respectively.
-            <footnote>
-                More precisely, if a <literal>GroovyCompile</literal> or <literal>Groovydoc</literal> task's <literal>groovyClasspath</literal>
-                is empty, the plugin searches the task's <literal>classpath</literal> for a
-                <literal>groovy-all(-indy)</literal> or <literal>groovy(-indy)</literal> artifact. If it finds the former, it adds the same artifact
-                to <literal>groovyClasspath</literal>. If it finds the latter, and the project has at least one repository declared, the plugin adds
-                a corresponding repository dependency to <literal>groovyClasspath</literal>.
-            </footnote>
+        <para>Because Gradle's build language is based on Groovy, and parts of Gradle are implemented in Groovy, Gradle already ships
+            with a Groovy library (1.8.6 as of Gradle 1.6). Nevertheless, Groovy projects need to explicitly declare a Groovy dependency.
+            This dependency will then be used on compile and runtime class paths. It will also be used to get hold of the Groovy compiler
+            and Groovydoc tool, respectively.
         </para>
         <para>
-            If Groovy is used both for production code, the Groovy dependency should be added to the <literal>compile</literal>
+            If Groovy is used for production code, the Groovy dependency should be added to the <literal>compile</literal>
             configuration:
         </para>
         <sample id="quickstartGroovyDependency" dir="groovy/quickstart" title="Configuration of Groovy dependency">
@@ -175,26 +167,19 @@
         </sample>
         <para>
             If Groovy is only used for test code, the Groovy dependency should be added to the <literal>testCompile</literal>
-            (but not the <literal>compile</literal>) configuration:
+            configuration:
         </para>
         <sample id="groovyTestDependency" dir="userguide/groovy/groovyDependency" title="Configuration of Groovy test dependency">
             <sourcefile file="build.gradle" snippet="groovy-test-dependency"/>
         </sample>
         <para>
-            To use the same Groovy library that ships with Gradle, declare a <literal>localGroovy()</literal> dependency. Note that
+            To use the Groovy library that ships with Gradle, declare a <literal>localGroovy()</literal> dependency. Note that
             different Gradle versions ship with different Groovy versions; as such, using <literal>localGroovy()</literal> is less
-            safe then explicitly choosing a Groovy version.
+            safe then declaring a regular Groovy dependency.
         </para>
         <sample id="bundledGroovyDependency" dir="userguide/groovy/groovyDependency" title="Configuration of bundled Groovy dependency">
             <sourcefile file="build.gradle" snippet="bundled-groovy-dependency"/>
         </sample>
-        <para>
-            In earlier Gradle versions, the Groovy dependency was instead added to the <literal>groovy</literal> configuration.
-            This is no longer the preferred approach, but is still supported for backwards compatibility.
-        </para>
-        <sample id="groovyConfiguration" dir="userguide/groovy/groovyDependency" title="Configuration of Groovy configuration">
-            <sourcefile file="build.gradle" snippet="groovy-configuration"/>
-        </sample>
         <para>The Groovy library doesn't necessarily have to come from a remote repository. It could also come from a local
             <literal>lib</literal> directory, perhaps checked in to source control:</para>
         <sample id="groovyFileDependency" dir="userguide/tutorial/groovyWithFlatDir" title="Configuration of Groovy file dependency">
@@ -204,29 +189,32 @@
     </section>
 
     <section>
-        <title>Adding custom GroovyCompile and Groovydoc tasks</title>
-        <para>
-            When adding custom <literal>GroovyCompile</literal> and <literal>Groovydoc</literal> tasks, it's important to understand
-            that these tasks consume Groovy in two ways: on their <literal>classpath</literal>, and on their <literal>groovyClasspath</literal>.
-            The former is the regular class path required by these tools to locate referenced classes, and will typically contain more than
-            just the Groovy library. The latter is used to load the Groovy compiler and Groovydoc tool, respectively, and shouldn't contain anything
-            other than the Groovy library and its dependencies.
-        </para>
+        <title>Automatic configuration of groovyClasspath</title>
         <para>
-            Unless <literal>groovyClasspath</literal> is explicitly configured for a task, the Groovy (base) plugin will try to infer
-            the Groovy library to be used from the task's<literal>classpath</literal>. For example, if <literal>classpath</literal> contains
-            <literal>groovy-all-2.0.5.jar</literal>, the plugin will add the same dependency to <literal>groovyClasspath</literal>. If the project
-            has at least one repository defined, an external dependency will be added (e.g. <literal>"org.codehaus.groovy:groovy-all:2.0.5"</literal>);
-            otherwise, a file dependency will be added.
+            <literal>GroovyCompile</literal> and <literal>Groovydoc</literal> tasks consume Groovy in two ways: on their <literal>classpath</literal>,
+            and on their <literal>groovyClasspath</literal>. The former is used to locate classes referenced by the source code, and will typically
+            contain the Groovy library along with other libraries. The latter is used to load and execute the Groovy compiler and Groovydoc tool,
+            respectively, and should only contain the Groovy library and its dependencies.
         </para>
         <para>
-            Note: When using the <literal>groovy</literal> rather than the <literal>groovy-all</literal> artifact, automatic configuration of
-            <literal>groovyClasspath</literal> will only work correctly if the project declares a repository that contains the <literal>groovy</literal>
-            artifact along with a descriptor (<filename>pom.xml</filename> or <filename>ivy.xml</filename>) listing its dependencies. Otherwise, only the
-            artifact itself will be added to <literal>groovyClasspath</literal>, which will likely result in a <literal>NoClassDefFoundError</literal>
-            during compilation.
+            Unless a task's <literal>groovyClasspath</literal> is configured explicitly, the Groovy (base) plugin will try to infer it
+            from the task's <literal>classpath</literal>. This is done as follows:
+            <itemizedlist>
+                <listitem>
+                    If a <literal>groovy-all(-indy)</literal> Jar is found on <literal>classpath</literal>, the same Jar will be added to
+                    <literal>groovyClasspath</literal>.
+                </listitem>
+                <listitem>
+                    If a <literal>groovy(-indy)</literal> Jar is found on <literal>classpath</literal>, and the project has at least one repository declared,
+                    a corresponding <literal>groovy(-indy)</literal> repository dependency will be added to <literal>groovyClasspath</literal>.
+                </listitem>
+                <listitem>
+                    Otherwise, execution of the task will fail with a message saying that <literal>groovyClasspath</literal> could not be inferred.
+                </listitem>
+            </itemizedlist>
         </para>
     </section>
+
     <section>
         <title>Convention properties</title>
         <para>The Groovy plugin does not add any convention properties to the project.</para>
diff --git a/subprojects/docs/src/docs/userguide/groovyTutorial.xml b/subprojects/docs/src/docs/userguide/groovyTutorial.xml
index e1349c7..9ad21f1 100644
--- a/subprojects/docs/src/docs/userguide/groovyTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/groovyTutorial.xml
@@ -38,9 +38,9 @@
         <para>To use the groovy compilation tasks, you must also declare the Groovy version to use and where to find the
             Groovy libraries. You do this by adding a dependency to the <literal>groovy</literal> configuration.
             The <literal>compile</literal> configuration inherits this dependency, so the groovy libraries will
-            be included in classpath when compiling Groovy and Java source.  For our sample, we will use Groovy 1.7.10
+            be included in classpath when compiling Groovy and Java source.  For our sample, we will use Groovy 2.2.0
             from the public Maven repository:</para>
-        <sample id="groovyQuickstart" dir="groovy/quickstart" title="Dependency on Groovy 1.7.10">
+        <sample id="groovyQuickstart" dir="groovy/quickstart" title="Dependency on Groovy 2.2.0">
             <sourcefile file="build.gradle" snippet="groovy-dependency"/>
         </sample>
         <para>Here is our complete build file:</para>
diff --git a/subprojects/docs/src/docs/userguide/ideSupport.xml b/subprojects/docs/src/docs/userguide/ideSupport.xml
index b1710a3..9e83f4d 100644
--- a/subprojects/docs/src/docs/userguide/ideSupport.xml
+++ b/subprojects/docs/src/docs/userguide/ideSupport.xml
@@ -52,7 +52,7 @@
         </para>
         <figure>
             <title>gradle-imports</title>
-            <programlisting><xi:include href='../../../../../subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt' parse='text'/></programlisting>
+            <programlisting><xi:include href='../../../build/generated-resources/main/default-imports.txt' parse='text'/></programlisting>
         </figure>
     </section>
 </appendix>
diff --git a/subprojects/docs/src/docs/userguide/ideaPlugin.xml b/subprojects/docs/src/docs/userguide/ideaPlugin.xml
index bd4326a..9948f06 100644
--- a/subprojects/docs/src/docs/userguide/ideaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/ideaPlugin.xml
@@ -192,7 +192,7 @@
                     <apilink class="org.gradle.plugins.ide.idea.model.IdeaWorkspace"/>
                 </td>
                 <td><literal>idea.workspace</literal></td>
-                <td>Allows configuring the workspace xml</td>
+                <td>Allows configuring the workspace XML</td>
             </tr>
         </table>
     </section>
@@ -226,7 +226,7 @@
                     <listitem>The <code>beforeMerged</code> hook is executed with a domain object representing the existing file</listitem>
                     <listitem>The existing content is merged with the configuration inferred from the Gradle build or defined explicitly in the eclipse DSL</listitem>
                     <listitem>The <code>whenMerged</code> hook is executed with a domain object representing contents of the file to be persisted</listitem>
-                    <listitem>The <code>withXml</code> hook is executed with a raw representation of the xml that will be persisted</listitem>
+                    <listitem>The <code>withXml</code> hook is executed with a raw representation of the XML that will be persisted</listitem>
                     <listitem>The final XML is persisted</listitem>
                 </orderedlist>
                 The following table lists the domain object used for each of the model types:
diff --git a/subprojects/docs/src/docs/userguide/img/jacocoHtmlReport.png b/subprojects/docs/src/docs/userguide/img/jacocoHtmlReport.png
new file mode 100644
index 0000000..15fe295
Binary files /dev/null and b/subprojects/docs/src/docs/userguide/img/jacocoHtmlReport.png differ
diff --git a/subprojects/docs/src/docs/userguide/initscripts.xml b/subprojects/docs/src/docs/userguide/initscripts.xml
index f50a124..2b8c208 100644
--- a/subprojects/docs/src/docs/userguide/initscripts.xml
+++ b/subprojects/docs/src/docs/userguide/initscripts.xml
@@ -46,7 +46,7 @@
                     </para>
                 </listitem>
                 <listitem>
-                    <para>Register build loggers.  You might wish to customise how Gradle logs the events that it generates.
+                    <para>Register build loggers.  You might wish to customize how Gradle logs the events that it generates.
                     </para>
                 </listitem>
             </itemizedlist>
@@ -127,4 +127,21 @@
             <output args="--init-script init.gradle -q doNothing"/>
         </sample>
     </section>
+    <section>
+            <title>Init script plugins</title>
+            <para>Similar to a Gradle build script or a Gradle settings file, plugins can be applied on init scripts.
+            </para>
+            <sample id="usePluginsInInitScripts" dir="userguide/initScripts/plugins" title="Using plugins in init scripts">
+                    <sourcefile file="init.gradle" snippet="init-script-plugin"/>
+                    <sourcefile file="build.gradle" snippet="show-repos-task"/>
+                    <output args="-q -I init.gradle showRepositories"/>
+            </sample>
+            <para>The plugin in the sample init scripts ensures, that only a specified repository is used when running the build.
+            </para>
+            <para>When applying plugins within the init script, Gradle instantiates the plugin and calls the plugin
+                instance's <apilink class="org.gradle.api.Plugin" method="apply"/> method. The <literal>gradle</literal> object is passed as a parameter,
+                which can be used to configure all aspects of a build.
+                Of course, the applied plugin can be resolved as external dependency as described in <xref linkend='sec:custom_classpath'/>
+            </para>
+        </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/jacocoPlugin.xml b/subprojects/docs/src/docs/userguide/jacocoPlugin.xml
new file mode 100644
index 0000000..26e49ac
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/jacocoPlugin.xml
@@ -0,0 +1,248 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="jacoco_plugin">
+    <title>The JaCoCo Plugin</title>
+    <note>
+        <para>
+            The JaCoCo plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
+        </para>
+    </note>
+
+    <para>The JaCoCo plugin provides code coverage metrics for Java code via integration with <ulink url="http://www.eclemma.org/jacoco/">JaCoCo</ulink>.
+    </para>
+    <section>
+        <title>Getting Started</title>
+        <para>To get started, apply the JaCoCo plugin to the project you want to calculate code coverage for.</para>
+        <sample id="applyJacoco" dir="testing/jacoco/quickstart" title="Applying the JaCoCo plugin">
+            <sourcefile file="build.gradle" snippet="apply-plugin"/>
+        </sample>
+        <para>
+            If the Java plugin is also applied to your project, a new task named
+            <literal>jacocoTestReport</literal>
+            is created that depends on the
+            <literal>test</literal>
+            task.
+            The report is available at
+            <filename><replaceable>$buildDir</replaceable>/reports/jacoco/test</filename>. By default, a HTML report is generated.
+        </para>
+    </section>
+
+    <section>
+        <title>Configuring the JaCoCo Plugin</title>
+        <para>
+            The JaCoCo plugin adds a project extension named <literal>jacoco</literal> of type <apilink class="org.gradle.testing.jacoco.plugins.JacocoPluginExtension"/>,
+            which allows configuring defaults for JaCoCo usage in your build.
+        </para>
+        <sample id="configJacoco" dir="testing/jacoco/quickstart" title="Configuring JaCoCo plugin settings">
+            <sourcefile file="build.gradle" snippet="jacoco-configuration"/>
+        </sample>
+        <table>
+            <title>Gradle defaults for JaCoCo properties</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Gradle default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reportsDir</td>
+                <td>"<replaceable>$buildDir</replaceable>/reports/jacoco"
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>JaCoCo Report configuration</title>
+        <para>The
+            <apilink class="org.gradle.testing.jacoco.tasks.JacocoReport"/>
+            task can be used to generate code coverage reports in different formats.
+            It implements the standard Gradle type <apilink class="org.gradle.api.reporting.Reporting"/> and exposes a report container of
+            type <apilink class="org.gradle.testing.jacoco.tasks.JacocoReportsContainer" />.
+        </para>
+        <sample id="configJacocoReport" dir="testing/jacoco/quickstart" title="Configuring test task">
+            <sourcefile file="build.gradle" snippet="report-configuration"/>
+        </sample>
+        <imageobject>
+            <imagedata fileref="img/jacocoHtmlReport.png" width="903px" depth="277px"/>
+        </imageobject>
+    </section>
+
+    <section>
+        <title>JaCoCo specific task configuration</title>
+        <para>The JaCoCo plugin adds a
+            <apilink class="org.gradle.testing.jacoco.plugins.JacocoTaskExtension"/>
+            extension to all tasks of type
+            <apilink class="org.gradle.api.tasks.testing.Test"/>.
+            This extension allows the configuration of the JaCoCo specific properties of the test task.
+        </para>
+        <sample id="jacocotesttast" dir="testing/jacoco/quickstart" title="Configuring test task">
+            <sourcefile file="build.gradle" snippet="testtask-configuration"/>
+        </sample>
+        <table>
+            <title>Default values of the JaCoCo Task extension</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Gradle default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>enabled</td>
+                <td>true</td>
+            </tr>
+            <tr>
+                <td>destPath</td>
+                <td><replaceable>$buildDir</replaceable>/jacoco
+                </td>
+            </tr>
+            <tr>
+                <td>append</td>
+                <td>true</td>
+            </tr>
+            <tr>
+                <td>includes</td>
+                <td>[]</td>
+            </tr>
+            <tr>
+                <td>excludes</td>
+                <td>[]</td>
+            </tr>
+            <tr>
+                <td>excludeClassLoaders</td>
+                <td>[]</td>
+            </tr>
+            <tr>
+                <td>sessionId</td>
+                <td>
+                    <literal>auto-generated</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>dumpOnExit</td>
+                <td>
+                    <literal>true</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>output</td>
+                <td>
+                    <literal>Output.FILE</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>address</td>
+                <td>
+                    <literal>-</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>port</td>
+                <td>
+                    <literal>-</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>classDumpPath</td>
+                <td>
+                    <literal>-</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>jmx</td>
+                <td>
+                    <literal>false</literal>
+                </td>
+            </tr>
+        </table>
+        <para>While all tasks of type
+            <apilink class="org.gradle.api.tasks.testing.Test"/>
+            are automatically enhanced to provide coverage information when the <literal>java</literal> plugin has been applied,
+            any task that implements <apilink class="org.gradle.process.JavaForkOptions"/> can be enhanced by the JaCoCo plugin.
+            That is, any task that forks Java processes can be used to generate coverage information.
+        </para>
+        <para>
+            For example you can configure your build to generate code coverage using the <literal>application</literal> plugin.
+        </para>
+        <sample id="jacoco-application-setup" dir="testing/jacoco/application" includeLocation="true" title="Using application plugin to generate code coverage data">
+            <sourcefile file="build.gradle" snippet="application-configuration"/>
+        </sample>
+        <sample id="jacoco-application-output" dir="testing/jacoco/application" title="Coverage reports generated by applicationCodeCoverageReport">
+            <layout after='run applicationCodeCoverageReport'>
+                build/jacoco/run.exec
+                build/reports/jacoco/applicationCodeCoverageReport/html/index.html
+            </layout>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>For projects that also apply the Java Plugin, The JaCoCo plugin automatically adds the following tasks:</para>
+        <table>
+            <title>JaCoCo plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>jacocoTestReport</literal>
+                </td>
+                <td>-</td>
+                <td>
+                    <apilink class="org.gradle.testing.jacoco.tasks.JacocoReport"/>
+                </td>
+                <td>Generates code coverage report for the test task.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The JaCoCo plugin adds the following dependency configurations:</para>
+        <table>
+            <title>JaCoCo plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>jacocoAnt</filename>
+                </td>
+                <td>The JaCoCo Ant library used for running the
+                    <literal>JacocoReport</literal>
+                    and
+                    <literal>JacocoMerge</literal>
+                    tasks.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <filename>jacocoAgent</filename>
+                </td>
+                <td>The JaCoCo agent library used for instrumenting the code under test.</td>
+            </tr>
+        </table>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml b/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
index c695b8a..28806f3 100644
--- a/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
@@ -2,9 +2,10 @@
     <title>The Java Library Distribution Plugin</title>
     <note>
         <para>
-            The Java library distribution plugin is incubating (see <xref linkend="sec:incubating_state"/>).
+            The Java library distribution plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
         </para>
     </note>
+
     <para>The Java library distribution plugin adds support for building a distribution ZIP for a Java library. The distribution contains the
         JAR file for the library and its dependencies.
     </para>
diff --git a/subprojects/docs/src/docs/userguide/javaPlugin.xml b/subprojects/docs/src/docs/userguide/javaPlugin.xml
index 9b177d0..7c79347 100644
--- a/subprojects/docs/src/docs/userguide/javaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaPlugin.xml
@@ -1118,10 +1118,7 @@
                 API allows you some control over how this happens.
             </para>
             <para>There are a number of properties which control how the test process is launched. This includes
-                things such as system properties, JVM arguments, and the Java executable to use. The task also provides a
-                <literal>debug</literal> property, which when set to true, starts the test process in debug mode,
-                suspended and listening on port 5005. This makes it very easy to debug your tests.  You may also enable
-                this using a system property as specified below.
+                things such as system properties, JVM arguments, and the Java executable to use.
             </para>
             <para>You can specify whether or not to execute your tests in parallel. Gradle provides parallel test execution
                 by running multiple test processes concurrently. Each test process executes only a single test at a time, so you
@@ -1151,17 +1148,99 @@
         </section>
 
         <section>
-            <title>System properties</title>
-            <para>There are two system properties that can affect test execution.  Both of these are based off
-                  of the name of the test task with a suffix.
+            <title>Debugging</title>
+            <para>
+                The test task provides a <apilink class="org.gradle.api.tasks.testing.Test" method="getDebug()" /> property that can be set to launch to make the JVM wait for a
+                debugger to attach to port 5005 before proceeding with test execution.
+            </para>
+            <para>
+                This can also be enabled at invocation time via the <userinput>--debug-jvm</userinput> task option.
+            </para>
+        </section>
+
+        <section id="test_filtering">
+
+            <title>Test filtering</title>
+
+            <para>Starting with Gradle 1.10, it is possible to include only specific tests, based on the test name pattern.
+                Filtering is a different mechanism than test class inclusion / exclusion that will be described in the next few paragraphs
+                (<literal>-Dtest.single</literal>, <literal>test.include</literal> and friends).
+                The latter is based on files, e.g. the physical location of the test implementation class.
+                File-level test selection does not support many interesting scenarios that are possible with test-level filtering.
+                Some of them Gradle handles now and some will be satisfied in the future releases:
+                <itemizedlist>
+                    <listitem>Filtering at the level of specific test methods; executing a single test method</listitem>
+                    <listitem>Filtering based on custom annotations (future)</listitem>
+                    <listitem>Filtering based on test hierarchy; executing all tests that extend ceratain base class (future)</listitem>
+                    <listitem>Filtering based on some custom runtime rule, e.g. particular value of a system property or some static state (future)</listitem>
+                </itemizedlist>
+            </para>
+
+            <para>
+                Test filtering feature has following characteristic:
+                <itemizedlist>
+                    <listitem>Fully qualified class name or fully qualified method name is supported,
+                        e.g. "org.gradle.SomeTest", "org.gradle.SomeTest.someMethod"</listitem>
+                    <listitem>Wildcard '*' is supported for matching any characters</listitem>
+                    <listitem>Command line option "--tests" is provided to conveniently set the test filter.
+                        Especially useful for the classic 'single test method execution' use case.
+                        When the command line option is used, the inclusion filters declared in the build script are ignored.
+                    </listitem>
+                    <listitem>Gradle tries best to filter the tests given limitations of particular test framework API.
+                        Some advanced, synthetic tests may not be fully compatible with filtering.
+                        However, vast majority of tests and use cases should be handled neatly.
+                    </listitem>
+                    <listitem>Test filtering supersedes the file-based test selection. The latter may be completely replaced in future.
+                        We will grow the the test filtering api and add more kinds of filters.
+                    </listitem>
+                </itemizedlist>
+            </para>
+
+            <para>
+                <sample id="testfiltering" dir="testing/filtering" title="Filtering tests in the build script">
+                    <sourcefile file="build.gradle" snippet="test-filtering"/>
+                </sample>
+                For more details and examples please see the <apilink class="org.gradle.api.tasks.testing.TestFilter"/> reference.
+            </para>
+
+            <para>
+                Some examples of using the command line option:
+
+                <itemizedlist>
+                    <listitem>
+                        <para><literal>gradle test --tests org.gradle.SomeTest.someSpecificFeature</literal></para>
+                    </listitem>
+                    <listitem>
+                        <para><literal>gradle test --tests *SomeTest.someSpecificFeature</literal></para>
+                    </listitem>
+                    <listitem>
+                        <para><literal>gradle test --tests *SomeSpecificTest</literal></para>
+                    </listitem>
+                    <listitem>
+                        <para><literal>gradle test --tests all.in.specific.package*</literal></para>
+                    </listitem>
+                    <listitem>
+                        <para><literal>gradle test --tests *IntegTest</literal></para>
+                    </listitem>
+                    <listitem>
+                        <para><literal>gradle test --tests *IntegTest*ui*</literal></para>
+                    </listitem>
+                    <listitem>
+                        <para><literal>gradle someTestTask --tests *UiTest someOtherTestTask --tests *WebTest*ui</literal></para>
+                    </listitem>
+                </itemizedlist>
             </para>
+        </section>
+        <section>
+            <title>Single test execution via System Properties</title>
+            <note>This mechanism has been superseded by 'Test Filtering', described above.</note>
             <para>Setting a system property of <replaceable>taskName.single</replaceable> = <replaceable>testNamePattern</replaceable>
                   will only execute tests that match the specified <replaceable>testNamePattern</replaceable>.
                   The <replaceable>taskName</replaceable> can be a full multi-project path like ":sub1:sub2:test"
                   or just the task name.  The <replaceable>testNamePattern</replaceable> will be used to form an include
                   pattern of "**/testNamePattern*.class".
                   If no tests with this pattern can be found an exception is thrown. This is to shield you from false security.
-                  If tests of more then one subproject are executed, the pattern is applied to each subproject. An exception
+                  If tests of more than one subproject are executed, the pattern is applied to each subproject. An exception
                   is thrown if no tests can be found for a particular subproject. In such a case you can use the path notation of the
                   pattern, so that the pattern is applied only to the test task of a specific subproject. Alternatively you can specify the fully
                   qualified task name to be executed. You can also specify multiple patterns. Examples:
@@ -1183,10 +1262,6 @@
                       </listitem>
                   </itemizedlist>
             </para>
-            <para>Setting a system property of <literal>taskName.debug</literal> will run the tests in debug mode,
-                  suspended and listening on port 5005.  For example:
-                  <literal>gradle test -Dtest.single=ThisUniquelyNamedTest -Dtest.debug</literal>
-            </para>
         </section>
 
         <section>
@@ -1229,6 +1304,30 @@
             </para>
         </section>
 
+        <section id="test_grouping">
+            <title>Test grouping</title>
+            <para>JUnit and TestNG allows sophisticated groupings of test methods. </para>
+            <para>For grouping JUnit test classes and methods JUnit 4.8 introduces the concept of categories.
+                <footnote>
+                    <para>The JUnit wiki contains a detailed description on how to work with JUnit categories:
+                        <ulink url='https://github.com/junit-team/junit/wiki/Categories'/>.
+                    </para>
+                </footnote>
+                The <literal>test</literal> task allows the specification of the JUnit categories you want to include and exclude.
+            </para>
+            <sample id="junitcategories" dir="testing/junit/categories" title="JUnit Categories">
+                <sourcefile file="build.gradle" snippet="test-categories"/>
+            </sample>
+            <para>The TestNG framework has a quite similar concept. In TestNG you can specify different test groups.
+                <footnote>
+                    <para>The TestNG documentation contains more details about test groups:
+                        <ulink url='http://testng.org/doc/documentation-main.html#test-groups'/>.
+                    </para>
+                </footnote> The test groups that should be included or excluded from the test execution can be configured in the test task. </para>
+            <sample id="testnggrouping" dir="testing/testng/groups" title="Grouping TestNG tests">
+                <sourcefile file="build.gradle" snippet="test-config"/>
+            </sample>
+        </section>
         <section id="test_reporting">
             <title>Test reporting</title>
 
@@ -1254,10 +1353,23 @@
             </sample>
 
             <para>You should note that the <literal>TestReport</literal> type combines the results from multiple test
-                tasks, but it does not aggregate the results of individual test classes. This means that if a given test class is executed by
-                multiple test tasks, then the test report will include only one execution of that class and discard the other executions of that
-                class. This will be addressed in a future Gradle version.
+                tasks and needs to aggregate the results of individual test classes. This means that if a given test class is executed by
+                multiple test tasks, then the test report will include executions of that class, but it can be hard to distinguish individual
+                executions of that class and their output.
             </para>
+            <section id="testNgParameterizedReporting">
+                <title>TestNG parameterized methods and reporting</title>
+                <para>
+                    TestNG supports <ulink url="http://testng.org/doc/documentation-main.html#parameters">parameterizing test methods</ulink>,
+                    allowing a particular test method to be executed multiple times with different inputs. Gradle includes the parameter values
+                    in its reporting of the test method execution.
+                </para>
+                <para>
+                    Given a parameterized test method named <literal>aParameterizedTestMethod</literal> that takes two parameters, it will be reported with the name:
+                    <literal>aParameterizedTestMethod(toStringValueOfParam1, toStringValueOfParam2)</literal>. This makes identifying the parameter values for a
+                    particular iteration easy.
+                </para>
+            </section>
         </section>
 
         <section>
diff --git a/subprojects/docs/src/docs/userguide/javaTutorial.xml b/subprojects/docs/src/docs/userguide/javaTutorial.xml
index edef18d..d1e7c98 100644
--- a/subprojects/docs/src/docs/userguide/javaTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/javaTutorial.xml
@@ -130,27 +130,27 @@
         </section>
 
         <section>
-            <title>Customising the project</title>
+            <title>customizing the project</title>
             <para>The Java plugin adds a number of properties to your project. These properties have default values
                 which are usually sufficient to get started. It's easy to change these values if they don't suit. Let's
                 look at this for our sample. Here we will specify the version number for our Java project, along
                 with the Java version our source is written in. We also add some attributes to the JAR manifest.
             </para>
             <sample id="javaQuickstart" dir="java/quickstart" title="Customization of MANIFEST.MF">
-                <sourcefile file="build.gradle" snippet="customisation"/>
+                <sourcefile file="build.gradle" snippet="customization"/>
             </sample>
             <tip>
                 <title>What properties are available?</title>
                 <para>You can use <userinput>gradle properties</userinput> to list the properties of a project. This will allow
                 you to see the properties added by the Java plugin, and their default values.</para></tip>
             <para>The tasks which the Java plugin adds are regular tasks, exactly the same as if they were declared in
-                the build file. This means you can use any of the mechanisms shown in earlier chapters to customise
+                the build file. This means you can use any of the mechanisms shown in earlier chapters to customize
                 these tasks. For example, you can set the properties of a task, add behaviour to a task, change the
                 dependencies of a task, or replace a task entirely. In our sample, we will configure the
                 <literal>test</literal> task, which is of type <apilink class="org.gradle.api.tasks.testing.Test"/>, to
                 add a system property when the tests are executed: </para>
             <sample id="javaQuickstart" dir="java/quickstart" title="Adding a test system property">
-                <sourcefile file="build.gradle" snippet="task-customisation"/>
+                <sourcefile file="build.gradle" snippet="task-customization"/>
             </sample>
         </section>
 
diff --git a/subprojects/docs/src/docs/userguide/jettyPlugin.xml b/subprojects/docs/src/docs/userguide/jettyPlugin.xml
index 60d1feb..ac6775b 100644
--- a/subprojects/docs/src/docs/userguide/jettyPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/jettyPlugin.xml
@@ -103,6 +103,20 @@
             </thead>
             <tr>
                 <td>
+                    <literal>contextPath</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    WAR file base name
+                </td>
+                <td>
+                    The application deployment location within the Jetty container.
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <literal>httpPort</literal>
                 </td>
                 <td>
diff --git a/subprojects/docs/src/docs/userguide/logging.xml b/subprojects/docs/src/docs/userguide/logging.xml
index d929950..3b632e3 100644
--- a/subprojects/docs/src/docs/userguide/logging.xml
+++ b/subprojects/docs/src/docs/userguide/logging.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='logging' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='logging'>
     <title>Logging</title>
     <para>The log is the main 'UI' of a build tool. If it is too verbose, real warnings and problems are easily hidden
         by this. On the other hand you need the relevant information for figuring out if things have gone wrong. Gradle
@@ -182,8 +182,9 @@
         <para>You can replace much of Gradle's logging UI with your own. You might do this, for example, if you want to
             customize the UI in some way - to log more or less information, or to change the formatting. You replace
             the logging using the <apilink class="org.gradle.api.invocation.Gradle" method="useLogger"/> method. This
-            is accessible from a build script, or an init script, or via the embedding API. Below is an example
-            init script which changes how task execution and build completion is logged.
+            is accessible from a build script, or an init script, or via the embedding API.
+            Note that this completely disables Gradle's default output.
+            Below is an example init script which changes how task execution and build completion is logged.
         </para>
         <sample id="custom_logging_ui" dir="userguide/initScripts/customLogger" title="Customizing what Gradle logs">
             <sourcefile file="init.gradle"/>
diff --git a/subprojects/docs/src/docs/userguide/mavenPlugin.xml b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
index 7148821..4848515 100644
--- a/subprojects/docs/src/docs/userguide/mavenPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
@@ -156,7 +156,7 @@
             </sample>
             <para>That is all. Calling the
                 <literal>uploadArchives</literal>
-                task will generate the POM and deploys the artifact and the pom to the specified repository.
+                task will generate the POM and deploys the artifact and the POM to the specified repository.
             </para>
             <para>There is some more work to do if you need support for other protocols than <literal>file</literal>. In
                 this case the native Maven code we delegate to needs additional libraries. Which libraries depend on the
diff --git a/subprojects/docs/src/docs/userguide/multiproject.xml b/subprojects/docs/src/docs/userguide/multiproject.xml
index 7ce1e20..061b6fa 100644
--- a/subprojects/docs/src/docs/userguide/multiproject.xml
+++ b/subprojects/docs/src/docs/userguide/multiproject.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='multi_project_builds' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='multi_project_builds'>
     <title>Multi-project Builds</title>
     <para>The powerful support for multi-project builds is one of Gradle's unique selling points. This topic is also the
         most intellectually challenging.
@@ -465,7 +465,7 @@
                 distribution out of them.
                 <footnote>
                     <para>The real use case we had, was using <ulink url='http://lucene.apache.org/solr'/>, where you
-                        need a separate war for each index your are accessing. That was one reason why we have created a
+                        need a separate war for each index you are accessing. That was one reason why we have created a
                         distribution of webapps. The Resin servlet container allows us, to let such a distribution point
                         to a base installation of the servlet container.
                     </para>
diff --git a/subprojects/docs/src/docs/userguide/nativeBinaries.xml b/subprojects/docs/src/docs/userguide/nativeBinaries.xml
new file mode 100755
index 0000000..d42f5eb
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/nativeBinaries.xml
@@ -0,0 +1,682 @@
+<!--
+  ~ Copyright 2010 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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='nativeBinaries'>
+    <title>Building native binaries</title>
+
+    <note>
+        <para>
+            The Gradle support for building native binaries is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
+        </para>
+    </note>
+
+    <para>
+        The various native binary plugins add support for building native software components from C++, C, Objective-C, Objective-C++ and Assembler sources.
+        While many excellent build tools exist for this space of software development, Gradle offers developers it's trademark power and flexibility
+        together with the dependency management practices more traditionally found in the JVM development space.
+    </para>
+
+    <section id="native-binaries:tool-chain-support">
+        <title>Tool chain support</title>
+        <para>
+            Gradle offers the ability to execute the same build using different tool chains. You can control which tool chain will be used to build
+            by changing the operating system PATH to include the desired tool chain compiler. Alternatively, you can configure the tool chains directly,
+            as described in the `Native Binary Variants` section, below.
+        </para>
+        <para>
+            The following tool chains are supported:
+        </para>
+        <table>
+            <thread>
+                <tr><td>Operating System</td><td>Tool Chain</td><td>Notes</td></tr>
+            </thread>
+            <tr>
+                <td>Linux</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td> <td></td>
+            </tr>
+            <tr>
+                <td>Linux</td><td><ulink url="http://clang.llvm.org">Clang</ulink></td><td></td>
+            </tr>
+            <tr>
+                <td>Mac OS X</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td><td>Using GCC distributed with XCode.</td>
+            </tr>
+            <tr>
+                <td>Mac OS X</td><td><ulink url="http://clang.llvm.org">Clang</ulink></td><td>Using Clang distributed with XCode.</td>
+            </tr>
+            <tr>
+                <td>Windows</td><td><ulink url="http://www.microsoft.com/visualstudio/en-us">Visual C++</ulink></td><td>Windows XP and later, Visual C++ 2010 and later.</td>
+            </tr>
+            <tr>
+                <td>Windows</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td><td>Windows XP and later, using GCC distributed with Cygwin.</td>
+            </tr>
+            <tr>
+                <td>Windows</td><td><ulink url="http://www.mingw.org/">MinGW</ulink></td><td>Windows XP and later.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Component model</title>
+        <para>
+            A native binary project defines a set of <apilink class="org.gradle.nativebinaries.Executable"/> and <apilink class="org.gradle.nativebinaries.Library"/> components,
+            each of which Gradle maps to a number of <apilink class="org.gradle.nativebinaries.NativeBinary"/> outputs.
+            For each <literal>executable</literal> or <literal>library</literal> defined, Gradle adds a <apilink class="org.gradle.language.base.FunctionalSourceSet"/> with the same name.
+            Each of these functional source sets will contain a language-specific source set for each of the languages supported by the project.
+        </para>
+        <para>
+            To build either a static or shared native library binary,
+            a <apilink class="org.gradle.nativebinaries.Library"/> component is added to the <literal>libraries</literal> container.
+            Each <literal>library</literal> component can produce at least one <apilink class="org.gradle.nativebinaries.SharedLibraryBinary"/>
+            and at least one <apilink class="org.gradle.nativebinaries.StaticLibraryBinary"/>.
+        </para>
+        <sample id="cppLibraries" dir="native-binaries/c" title="Defining a library component">
+            <sourcefile file="build.gradle" snippet="libraries"/>
+        </sample>
+        <para>
+            To build an executable binary,
+            an <apilink class="org.gradle.nativebinaries.Executable"/> component is added to the <literal>executables</literal> container
+            and associated with a set of sources.
+        </para>
+        <sample id="cppExecutables" dir="native-binaries/c" title="Defining executable components">
+            <sourcefile file="build.gradle" snippet="executables"/>
+        </sample>
+        <para>
+            In many cases, more than one native binary can be produced for a component.
+            These binaries may vary based on the tool chain used to build, the compiler/linker flags supplied, the dependencies
+            provided, or additional source files provided. Each native binary produced for a component is referred to as <literal>variant</literal>.
+            Binary variants are discussed in detail below.
+        </para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>
+            For each <apilink class="org.gradle.nativebinaries.NativeBinary"/> that can be produced by a build,
+            a single <firstterm>lifecycle task</firstterm> is constructed that can be used to create that binary, together with a set of other tasks that do the actual
+            work of compiling, linking or assembling the binary.
+        </para>
+        <table>
+            <thread>
+                <tr><td>Component Type</td><td>Native Binary Type</td><td>Lifecycle task</td><td>Location of created binary</td></tr>
+            </thread>
+            <tr>
+                <td><apilink class="org.gradle.nativebinaries.Executable"/></td><td><apilink class="org.gradle.nativebinaries.ExecutableBinary"/></td><td><literal><replaceable>$component.name</replaceable>Executable</literal></td><td><filename><replaceable>$buildDir</replaceable>/binaries/<replaceable>$binary.name</replaceable>/<replaceable>$component.name</replaceable></filename></td>
+            </tr>
+            <tr>
+                <td><apilink class="org.gradle.nativebinaries.Library"/></td><td><apilink class="org.gradle.nativebinaries.SharedLibraryBinary"/></td><td><literal><replaceable>$component.name</replaceable>SharedLibrary</literal></td><td><filename><replaceable>$buildDir</replaceable>/binaries/<replaceable>$binary.name</replaceable>/lib<replaceable>$component.name</replaceable>.so</filename></td>
+            </tr>
+            <tr>
+                <td><apilink class="org.gradle.nativebinaries.Library"/></td><td><apilink class="org.gradle.nativebinaries.StaticLibraryBinary"/></td><td><literal><replaceable>$component.name</replaceable>StaticLibrary</literal></td><td><filename><replaceable>$buildDir</replaceable>/binaries/<replaceable>$binary.name</replaceable>/<replaceable>$component.name</replaceable>.a</filename></td>
+            </tr>
+        </table>
+
+        <section>
+            <title>Working with shared libraries</title>
+            <para>
+                For each executable binary produced, the <literal>cpp</literal> plugin provides an <literal>install${binary.name}</literal> task,
+                which creates a development install of the executable, along with the shared libraries it requires.
+                This allows you to run the executable without needing to install the shared libraries in their final locations.
+            </para>
+        </section>
+    </section>
+
+    <section id="native_binaries:languages">
+        <title>Core language support: C, C++, Assembler, Objective-C and Objective-C++ </title>
+        <para>
+            Presently, Gradle supports building native binaries from any combination of C++, C, Assembler, Objective-C and Objective-C++ sources.
+            A native binary project will contain one or more named <literal>FunctionalSourceSet</literal> instances (eg 'main', 'test', etc),
+            each of which can contain <literal>LanguageSourceSet</literal>s containing C++, C, Assembler, Objective-C or Objective-C++ source files.
+        </para>
+
+        <section>
+            <title>C++ sources</title>
+            <para>
+                C++ language support is provided by means of the <literal>'cpp'</literal> plugin.
+            </para>
+            <sample id="cppPlugin" dir="native-binaries/cpp" title="The 'cpp' plugin">
+                <sourcefile file="build.gradle" snippet="apply-plugin"/>
+            </sample>
+            <para>
+                C++ sources to be included in a native binary are provided via a <apilink class="org.gradle.language.cpp.CppSourceSet"/>,
+                which defines a set of C++ source files and optionally a set of exported header files (for a library).
+                By default, for any named component the <apilink class="org.gradle.language.cpp.CppSourceSet"/> contains
+                <filename>.cpp</filename> source files in <filename>src/${name}/cpp</filename>,
+                and header files in <filename>src/${name}/headers</filename>.
+            </para>
+            <para>
+                While the <literal>cpp</literal> plugin defines these default locations for each <apilink class="org.gradle.language.cpp.CppSourceSet"/>,
+                it is possible to extend or override these defaults to allow for a different project layout.
+            </para>
+            <sample id="cppSourceSet" dir="native-binaries/custom-layout" title="C++ source set">
+                <sourcefile file="build.gradle" snippet="cpp-sources"/>
+            </sample>
+            <para>
+                For a library named 'main', files in <filename>src/main/headers</filename> are considered the “public” or “exported” headers.
+                Header files that should not be exported (but are used internally) should be placed inside the <filename>src/main/cpp</filename> directory (though be aware that
+                such header files should always be referenced in a manner relative to the file including them).
+            </para>
+        </section>
+
+        <section>
+            <title>C sources</title>
+            <para>
+                C language support is provided by means of the <literal>'c'</literal> plugin.
+            </para>
+            <sample id="cPlugin" dir="native-binaries/c" title="The 'c' plugin">
+                <sourcefile file="build.gradle" snippet="apply-plugin"/>
+            </sample>
+            <para>
+                C sources to be included in a native binary are provided via a <apilink class="org.gradle.language.c.CSourceSet"/>,
+                which defines a set of C source files and optionally a set of exported header files (for a library).
+                By default, for any named component the <apilink class="org.gradle.language.c.CSourceSet"/> contains
+                <filename>.c</filename> source files in <filename>src/${name}/c</filename>,
+                and header files in <filename>src/${name}/headers</filename>.
+            </para>
+            <para>
+                While the <literal>c</literal> plugin defines these default locations for each <apilink class="org.gradle.language.c.CSourceSet"/>,
+                it is possible to extend or override these defaults to allow for a different project layout.
+            </para>
+            <sample id="cSourceSet" dir="native-binaries/custom-layout" title="C source set">
+                <sourcefile file="build.gradle" snippet="c-sources"/>
+            </sample>
+            <para>
+                For a library named 'main', files in <filename>src/main/headers</filename> are considered the “public” or “exported” headers.
+                Header files that should not be exported (but are used internally) should be placed inside the <filename>src/main/c</filename> directory (though be aware that
+                such header files should always be referenced in a manner relative to the file including them).
+            </para>
+        </section>
+
+        <section>
+            <title>Assembler sources</title>
+            <para>
+                Assembly language support is provided by means of the <literal>'assembler'</literal> plugin.
+            </para>
+            <sample id="assemblerPlugin" dir="native-binaries/assembler" title="The 'assembler' plugin">
+                <sourcefile file="build.gradle" snippet="apply-plugin"/>
+            </sample>
+            <para>
+                Assembler sources to be included in a native binary are provided via a <apilink class="org.gradle.language.assembler.AssemblerSourceSet"/>,
+                which defines a set of Assembler source files.
+                By default, for any named component the <apilink class="org.gradle.language.assembler.AssemblerSourceSet"/> contains
+                <filename>.s</filename> source files under <filename>src/${name}/asm</filename>.
+            </para>
+        </section>
+
+        <section>
+            <title>Objective-C sources</title>
+            <para>
+                Objective-C language support is provided by means of the <literal>'objective-c'</literal> plugin.
+            </para>
+            <sample id="objectiveCPlugin" dir="native-binaries/objective-c" title="The 'objective-c' plugin">
+                <sourcefile file="build.gradle" snippet="apply-plugin"/>
+            </sample>
+            <para>
+                Objective-C sources to be included in a native binary are provided via a <apilink class="org.gradle.language.objectivec.ObjectiveCSourceSet"/>,
+                which defines a set of Objective-C source files.
+                By default, for any named component the <apilink class="org.gradle.language.objectivec.ObjectiveCSourceSet"/> contains
+                <filename>.m</filename> source files under <filename>src/${name}/objectiveC</filename>.
+            </para>
+        </section>
+
+        <section>
+            <title>Objective-C++ sources</title>
+            <para>
+                Objective-C++ language support is provided by means of the <literal>'objective-cpp'</literal> plugin.
+            </para>
+            <sample id="objectiveCppPlugin" dir="native-binaries/objective-cpp" title="The 'objective-cpp' plugin">
+                <sourcefile file="build.gradle" snippet="apply-plugin"/>
+            </sample>
+            <para>
+                Objective-C++ sources to be included in a native binary are provided via a <apilink class="org.gradle.language.objectivecpp.ObjectiveCppSourceSet"/>,
+                which defines a set of Objective-C++ source files.
+                By default, for any named component the <apilink class="org.gradle.language.objectivecpp.ObjectiveCppSourceSet"/> contains
+                <filename>.mm</filename> source files under <filename>src/${name}/objectiveCpp</filename>.
+            </para>
+        </section>
+    </section>
+
+    <section>
+        <title>Configuring the compiler, assembler and linker</title>
+        <para>
+            Each binary to be produced is associated with a set of compiler and linker settings, which include command-line arguments as well as macro definitions.
+            These settings can be applied to all binaries, an individual binary, or selectively to a group of binaries based on some criteria.
+        </para>
+        <sample id="allBinarySettings" dir="native-binaries/cpp" title="Settings that apply to all binaries">
+            <sourcefile file="build.gradle" snippet="all-binaries"/>
+        </sample>
+        <para>
+            Each binary is associated with a particular <apilink class="org.gradle.nativebinaries.toolchain.ToolChain"/>, allowing settings to be targeted based on
+            this value.
+        </para>
+        <para>
+            It is easy to apply settings to all binaries of a particular type:
+        </para>
+        <sample id="allSharedLibraryBinarySettings" dir="native-binaries/c" title="Settings that apply to all shared libraries">
+            <sourcefile file="build.gradle" snippet="all-shared-libraries"/>
+        </sample>
+        <para>
+            Furthermore, it is possible to specify settings that apply to all binaries produces for a particular <literal>executable</literal>
+            or <literal>library</literal> component:
+        </para>
+        <sample id="componentBinarySettings" dir="native-binaries/assembler" title="Settings that apply to all binaries produced for the 'main' executable component">
+            <sourcefile file="build.gradle" snippet="assembler-args"/>
+        </sample>
+        <para>
+            The example above will apply the supplied configuration to all <literal>executable</literal> binaries built.
+        </para>
+        <para>
+            Similarly, settings can be specified to target binaries for a component that are of a particular type:
+            eg all <literal>shared libraries</literal> for the <literal>main library</literal> component.
+        </para>
+        <sample id="sharedLibraryArgs" dir="native-binaries/cpp-lib" title="Settings that apply only to shared libraries produced for the 'main' library component">
+            <sourcefile file="build.gradle" snippet="args"/>
+        </sample>
+    </section>
+
+    <section id="native_binaries:windows-resources">
+        <title>Windows Resources</title>
+        <para>
+            When using the <apilink class="org.gradle.nativebinaries.toolchain.VisualCpp"/> tool chain, Gradle is able to compile Window Resource (<literal>rc</literal>)
+            files and link them into a native binary. This functionality is provided by the <literal>'windows-resources'</literal> plugin.
+        </para>
+        <sample id="windowsResourcesPlugin" dir="native-binaries/windows-resources" title="The 'windows-resources' plugin">
+            <sourcefile file="build.gradle" snippet="apply-plugin"/>
+        </sample>
+        <para>
+            Windows resources to be included in a native binary are provided via a <apilink class="org.gradle.language.rc.WindowsResourceSet"/>,
+            which defines a set of Windows Resource source files.
+            By default, for any named component the <apilink class="org.gradle.language.rc.WindowsResourceSet"/> contains
+            <filename>.rc</filename> source files under <filename>src/${name}/rc</filename>.
+        </para>
+        <para>
+            As with other source types, you can configure the location of the windows resources that should in included in the binary.
+        </para>
+        <sample id="windowsResourceSet" dir="native-binaries/windows-resources" title="Configuring the location of Windows resource sources">
+            <sourcefile file="build-resource-only-dll.gradle" snippet="windows-resource-set"/>
+        </sample>
+        <para>
+            You are able to construct a resource-only library by providing Windows Resource sources with no other language sources,
+            and configure the linker as appropriate:
+        </para>
+        <sample id="resourceOnlyDll" dir="native-binaries/windows-resources" title="Building a resource-only dll">
+            <sourcefile file="build-resource-only-dll.gradle" snippet="resource-only-library"/>
+        </sample>
+        <para>
+            The example above also demonstrates the mechanism of passing extra command-line arguments to the resource compiler.
+            The <literal>rcCompiler</literal> extension is of type <apilink class="org.gradle.nativebinaries.language.PreprocessingTool"/>.
+        </para>
+    </section>
+
+    <section>
+        <title>Library Dependencies</title>
+        <para>
+            Dependencies for C++ projects are binary libraries that export header files. The header files are used during compilation, with the compiled
+            binary dependency being used during the linking.
+        </para>
+        <section>
+           <title>Dependencies within the same project</title>
+           <para>
+               A set of sources may depend on header files provided by another binary component within the same project.
+               A common example is a native executable component that uses functions provided by a separate native library component.
+           </para>
+           <para>
+               Such a library dependency can be easily provided to source set associated with the <literal>executable</literal> component:
+           </para>
+           <sample id="cppSourceLibrary" dir="native-binaries/cpp" title="Providing a library dependency to the source set">
+               <sourcefile file="build.gradle" snippet="source-library"/>
+           </sample>
+           <para>
+               Alternatively, a library dependency can be provided directly to the <literal>ExecutableBinary</literal> for the <literal>executable</literal>.
+           </para>
+           <sample id="cppBinaryLibrary" dir="native-binaries/custom-layout" title="Providing a library dependency to the binary">
+               <sourcefile file="build.gradle" snippet="binary-library"/>
+           </sample>
+        </section>
+        <section>
+            <title>Project Dependencies</title>
+            <para>
+                For a component produced in a different Gradle project, the notation is similar.
+            </para>
+            <sample id="cppProjectDependencies" dir="native-binaries/multi-project" title="Declaring project dependencies">
+                <sourcefile file="build.gradle" snippet="project-dependencies"/>
+            </sample>
+        </section>
+    </section>
+
+    <section id="native_binaries:variants">
+        <title>Native Binary Variants</title>
+        <para>
+            For each executable or library defined, Gradle is able to build a number of different native binary variants.
+            Examples of different variants include debug vs release binaries, 32-bit vs 64-bit binaries, and binaries produced
+            with different custom preprocessor flags.
+        </para>
+        <para>
+            Binaries produced by Gradle can be differentiated on
+                <link linkend="native_binaries:build_type">build type</link>,
+                <link linkend="native_binaries:platform">platform</link> and
+                <link linkend="native_binaries:flavor">flavor</link>.
+            For each of these 'variant dimensions', it is possible to specify a set of available values as well as target each component at
+            one, some or all of these. For example, a plugin may define a range of support platforms, but you
+            may choose to only target Windows-x86 for a particular component.
+        </para>
+
+        <section id="native_binaries:build_type">
+            <title>Build types</title>
+            <para>
+                A <literal>build type</literal> determines various non-functional aspects of a binary, such as whether debug information is included,
+                or what optimisation level the binary is compiled with. Typical build types are 'debug' and 'release', but a project
+                is free to define any set of build types.
+            </para>
+            <sample id="buildTypes" dir="native-binaries/variants" title="Defining build types">
+                <sourcefile file="build.gradle" snippet="build-types"/>
+            </sample>
+            <para>
+                If no build types are defined in a project, then a single, default build type called 'debug' is added.
+            </para>
+            <para>
+                For a build type, a Gradle project will typically define a set of compiler/linker flags per tool chain.
+            </para>
+            <sample id="buildTypeConfig" dir="native-binaries/variants" title="Configuring debug binaries">
+                <sourcefile file="build.gradle" snippet="build-type-config"/>
+            </sample>
+            <note>
+                At this stage, it is completely up to the build script to configure the relevant compiler/linker flags for each
+                build type. Future versions of Gradle will automatically include the appropriate debug flags for any 'debug' build
+                type, and may be aware of various levels of optimisation as well.
+            </note>
+        </section>
+
+        <section id="native_binaries:platform">
+            <title>Platform</title>
+            <para>
+                An executable or library can be built to run on different operating systems and cpu architectures, with a variant being
+                produced for each platform. Gradle defines each OS/architecture combination as a <apilink class="org.gradle.nativebinaries.platform.Platform"/>,
+                and a project may define any number of platforms.
+                If no platforms are defined in a project, then a single, default platform 'current' is added.
+            </para>
+            <note>
+                Presently, a <literal>Platform</literal> consists of a defined operating system and architecture. As we continue to develop the
+                native binary support in Gradle, the concept of Platform will be extended to include things like C-runtime version, Windows SDK, ABI, etc.
+                Sophisticated builds may use the extensibility of Gradle to apply additional attributes to each platform, which can then be queried to
+                specify particular includes, preprocessor macros or compiler arguments for a native binary.
+            </note>
+            <sample id="platforms" dir="native-binaries/variants" title="Defining platforms">
+                <sourcefile file="build.gradle" snippet="platforms"/>
+            </sample>
+            <para>
+                For a given variant, Gradle will attempt to find a <apilink class="org.gradle.nativebinaries.toolchain.ToolChain"/> that is able to build
+                for the target platform. Available tool chains are searched in the order defined.
+                See the <link linkend="native_binaries:tool_chain">tool chain</link> section below for more details.
+            </para>
+        </section>
+
+        <section id="native_binaries:flavor">
+            <title>Flavor</title>
+            <para>
+                Each component can have a set of named <literal>flavors</literal>, and a separate binary variant can be produced for each flavor.
+                While the <literal>build type</literal> and <literal>target platform</literal> variant dimensions have a defined meaning in Gradle,
+                each project is free to define any number of flavors and apply meaning to them in any way.
+            </para>
+            <para>
+                An example of component flavors might differentiate between 'demo', 'paid' and 'enterprise' editions of the component,
+                where the same set of sources is used to produce binaries with different functions.
+            </para>
+            <sample id="flavors" dir="native-binaries/flavors" title="Defining flavors">
+                <sourcefile file="build.gradle" snippet="flavors"/>
+            </sample>
+            <para>
+                In the example above, a library is defined with a 'english' and 'french' flavor. When compiling the 'french'
+                variant, a separate macro is defined which leads to a different binary being produced.
+            </para>
+            <para>
+                If no flavor is defined for a component, then a single default flavor named 'default' is used.
+            </para>
+        </section>
+        <section>
+            <title>Selecting the build types, platforms and flavors for a component</title>
+            <para>
+                For a default component, Gradle will attempt to create a native binary variant for each and every combination of <literal>buildType</literal>,
+                <literal>platform</literal> and <literal>flavor</literal> defined for the project. It is possible to override this on a per-component
+                basis, by specifying the set of <literal>targetBuildTypes</literal>, <literal>targetPlatforms</literal> and/or <literal>targetFlavors</literal>.
+            </para>
+            <sample id="targets" dir="native-binaries/variants" title="Targeting a component at particular platforms">
+                <sourcefile file="build.gradle" snippet="target-platforms"/>
+            </sample>
+            <para>
+                Here you can see that the <apilink class="org.gradle.nativebinaries.TargetedNativeComponent" method="targetPlatforms"/> method is used to
+                select the set of platforms to target for <literal>executables.main</literal>.
+            </para>
+            <para>
+                A similar mechanism exists for selecting <apilink class="org.gradle.nativebinaries.TargetedNativeComponent" method="targetBuildTypes"/>
+                and <apilink class="org.gradle.nativebinaries.TargetedNativeComponent" method="targetFlavors"/>.
+            </para>
+        </section>
+        <section>
+            <title>Building all possible variants</title>
+            <para>
+                When a set of build types, target platforms, and flavors is defined for a component,
+                a <apilink class="org.gradle.nativebinaries.NativeBinary"/> model element is created for every possible
+                combination of these. However, in many cases it is not possible to build a particular variant, perhaps because
+                no tool chain is available to build for a particular platform.
+            </para>
+            <para>
+                If a binary variant cannot be built for any reason, then the <apilink class="org.gradle.nativebinaries.NativeBinary"/>
+                associated with that variant will not be <literal>buildable</literal>. It is possible to use this property to create a task
+                to generate all possible variants on a particular machine.
+            </para>
+            <sample id="buildable" dir="native-binaries/tool-chains" title="Building all possible variants">
+                <sourcefile file="build.gradle" snippet="buildable"/>
+            </sample>
+        </section>
+    </section>
+
+    <section id="native_binaries:tool_chain">
+        <title>Tool chains</title>
+        <para>
+            A single build may utilize different tool chains to build variants for different platforms. To this end,
+            the core 'native-binary' plugins will attempt to locate and make available supported tool chains. However, the set
+            of tool chains for a project may also be explicitly defined, allowing additional cross-compilers to be configured
+            as well as allowing the install directories to be specified.
+        </para>
+        <section>
+            <title>Defining tool chains</title>
+            <para>
+                The supported tool chain types are:
+            </para>
+            <itemizedlist>
+                <listitem>
+                    <para><apilink class="org.gradle.nativebinaries.toolchain.Gcc"/></para>
+                </listitem>
+                <listitem>
+                    <para><apilink class="org.gradle.nativebinaries.toolchain.Clang"/></para>
+                </listitem>
+                <listitem>
+                    <para><apilink class="org.gradle.nativebinaries.toolchain.VisualCpp"/></para>
+                </listitem>
+            </itemizedlist>
+            <sample id="toolChains" dir="native-binaries/tool-chains" title="Defining tool chains">
+                <sourcefile file="build.gradle" snippet="toolChains"/>
+            </sample>
+            <para>
+                Each tool chain implementation allows for a certain degree of configuration (see the API documentation for more details).
+            </para>
+        </section>
+
+        <section>
+            <title>Using tool chains</title>
+            <para>
+                It is not necessary or possible to specify the tool chain that should be used to build.
+                For a given variant, Gradle will attempt to locate a <apilink class="org.gradle.nativebinaries.toolchain.ToolChain"/> that is able to build
+                for the target platform. Available tool chains are searched in the order defined.
+            </para>
+            <note>
+                When a platform does not define an architecture or operating system, the default target of the tool chain
+                is assumed. So if a platform does not define a value for <literal>operatingSystem</literal>,
+                Gradle will find the first available tool chain that can build for the specified <literal>architecture</literal>.
+            </note>
+            <para>
+                The core Gradle tool chains are able to target the following architectures out of the box. In each case, the tool chain
+                will target the current operating system. See the next section for information on cross-compiling for other operating systems.
+            </para>
+            <table>
+                <thread>
+                    <tr><td>Tool Chain</td><td>Architectures</td></tr>
+                </thread>
+                <tr>
+                    <td>GCC</td><td>x86, x86_64</td>
+                </tr>
+                <tr>
+                    <td>Clang</td><td>x86, x86_64</td>
+                </tr>
+                <tr>
+                    <td>Visual C++</td><td>x86, x86_64, ia-64</td>
+                </tr>
+            </table>
+            <para>
+                So for GCC running on linux, the supported target platforms are 'linux/x86' and 'linux/x86_64'.
+                For GCC running on Windows via Cygwin, platforms 'windows/x86' and 'windows/x86_64' are supported.
+                (The Cygwin runtime is not yet modelled as part of the Platform, but will be in the future.)
+            </para>
+            <para>
+                If no target platforms are defined for a project, then all binaries are built to target a default platform named 'current'.
+                This default platform does not specify any <literal>architecture</literal> or <literal>operatingSystem</literal> value,
+                hence using the default values of the first available tool chain.
+            </para>
+        </section>
+
+        <section>
+            <title>Cross-compiling with GCC</title>
+            <para>
+                Cross-compiling is possible with the <apilink class="org.gradle.nativebinaries.toolchain.Gcc"/> and <apilink class="org.gradle.nativebinaries.toolchain.Clang"/> tool chains,
+                by programmatically adding support for additional target platforms.
+                This is done using the <apilink class="org.gradle.nativebinaries.toolchain.PlatformConfigurableToolChain"/> API.
+                Each added <apilink class="org.gradle.nativebinaries.toolchain.TargetPlatformConfiguration"/> defines support for a particular target platform,
+                and supplies additional tool arguments that are required to target this platform.
+            </para>
+        </section>
+    </section>
+    <section id="native_binaries:visual_studio">
+        <title>Visual Studio IDE integration</title>
+        <para>
+            Gradle has the ability to generate Visual Studio project and solution files for the native components defined in your build.
+            This ability is added by the <literal>visual-studio</literal> plugin. For a multi-project build, all projects with native components
+            should have this plugin applied.
+        </para>
+        <para>
+            When the <literal>visual-studio</literal> plugin is applied, a task name <literal>${component.name}VisualStudio</literal> is created
+            for each defined component. This task will generate a Visual Studio Solution file for the named component. This solution will include
+            a Visual Studio Project for that component, as well as linking to project files for each depended-on binary.
+        </para>
+        <para>
+            The content of the generated visual studio files can be modified via programmatic hooks, provided by the <literal>visualStudio</literal>
+            extension. Take a look at the 'visual-studio' sample, or see <apilink class="org.gradle.ide.visualstudio.VisualStudioExtension" method="getProjects"/>
+            and <apilink class="org.gradle.ide.visualstudio.VisualStudioExtension" method="getSolutions"/> for more details.
+        </para>
+    </section>
+    <section id="native_binaries:cunit">
+        <title>CUnit support</title>
+        <para>
+            The Gradle <literal>cunit</literal> plugin provides support for compiling and executing CUnit tests in your native-binary project.
+            For each <apilink class="org.gradle.nativebinaries.Executable"/> and <apilink class="org.gradle.nativebinaries.Library"/>
+            defined in your project, Gradle will create a matching <apilink class="org.gradle.nativebinaries.test.cunit.CUnitTestSuite"/> component,
+            named <literal>${component.name}Test</literal>.
+        </para>
+        <section>
+            <title>CUnit sources</title>
+            <para>
+                Gradle will create a <apilink class="org.gradle.language.c.CSourceSet"/> named 'cunit' for each <apilink class="org.gradle.nativebinaries.test.cunit.CUnitTestSuite"/> component
+                in the project. This source set should contain the cunit test files for the component sources. Source files can be located in the conventional location
+                (<literal>src/${component.name}Test/cunit</literal>) or can be configured like any other source set.
+            </para>
+            <para>
+                The job of initialising the CUnit test registry and executing the tests is performed by Gradle, via some generated CUnit launcher sources.
+                Gradle will expect and call a function with the signature <literal>void gradle_cunit_register()</literal> that you can use to configure the
+                actual CUnit suites and tests to execute.
+            </para>
+            <sample id="cunitSources" dir="native-binaries/cunit/src/operatorsTest/cunit" title="Registering CUnit tests">
+                <sourcefile file="suite_operators.c"/>
+            </sample>
+            <note>
+                Due to this mechanism, your CUnit sources may not contain a <literal>main</literal> method since this will clash with the method provided by Gradle.
+            </note>
+        </section>
+
+        <section>
+            <title>Building CUnit executables</title>
+            <para>
+                A <apilink class="org.gradle.nativebinaries.test.cunit.CUnitTestSuite"/> component has an associated
+                <apilink class="org.gradle.nativebinaries.Executable"/> or <apilink class="org.gradle.nativebinaries.Library"/> component.
+                For each <apilink class="org.gradle.nativebinaries.ProjectNativeBinary"/> configured for the main component, a matching
+                <apilink class="org.gradle.nativebinaries.test.TestSuiteExecutableBinary"/> will be configured on the test suite component.
+                These test suite binaries can be configured in a similar way to any other binary instance:
+            </para>
+            <sample id="cunitSources" dir="native-binaries/cunit" title="Registering CUnit tests">
+                <sourcefile file="build.gradle" snippet="configure-test-binary"/>
+            </sample>
+            <note>
+                Both the CUnit sources provided by your project and the generated launcher require the core CUnit headers and libraries.
+                Presently, this library dependency must be provided by your project for each <apilink class="org.gradle.nativebinaries.test.TestSuiteExecutableBinary"/>.
+            </note>
+        </section>
+
+        <section>
+            <title>Running CUnit tests</title>
+            <para>
+                For each <apilink class="org.gradle.nativebinaries.test.TestSuiteExecutableBinary"/>, Gradle will create a task to execute this binary,
+                which will run all of the registered CUnit tests.
+                The generated test results will be located in the <literal><replaceable>${build.dir}</replaceable>/test-results</literal> directory.
+            </para>
+            <sample id="completeCUnitExample" dir="native-binaries/cunit" title="Running CUnit tests" includeLocation="true">
+                <sourcefile file="build.gradle" snippet="complete-example"/>
+                <!--<output args='runFailingOperatorsTestCUnitExe' expectFailure="true"/>-->
+            </sample>
+            <!-- Cannot presently use generated output since the actual output varies from Windows and Linux,
+                 and UserGuideSamplesIntegrationTest doesn't permit this difference. -->
+            <screen>
+> gradle -q runFailingOperatorsTestCUnitExe
+
+There were test failures:
+  1. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c:6  - plus(0, -2) == -2
+  2. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c:7  - plus(2, 2) == 4
+
+:runFailingOperatorsTestCUnitExe FAILED
+
+BUILD FAILED
+
+Total time: 1 secs
+            </screen>
+        </section>
+
+        <note>
+            <para>
+                The current support for CUnit is quite rudimentary. Plans for future integration include:
+            </para>
+            <itemizedlist>
+                <listitem>
+                    <para>Allow tests to be declared with javadoc-style annotations.</para>
+                </listitem>
+                <listitem>
+                    <para>Improved HTML reporting, similar to that available for JUnit.</para>
+                </listitem>
+                <listitem>
+                    <para>Real-time feedback for test execution.</para>
+                </listitem>
+                <listitem>
+                    <para>Support for additional test frameworks.</para>
+                </listitem>
+            </itemizedlist>
+        </note>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml b/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
index ae993c9..4681cd8 100644
--- a/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
+++ b/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
@@ -118,7 +118,7 @@
         <para>Listed below is the default build script that Gradle applies to the <filename>buildSrc</filename> project:</para>
         <figure>
             <title>Default buildSrc build script</title>
-            <programlisting><xi:include href='../../../../../subprojects/core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt' parse='text'/></programlisting>
+            <programlisting><xi:include href='../../../../../subprojects/core/src/main/resources/org/gradle/initialization/buildsrc/defaultBuildSourceScript.txt' parse='text'/></programlisting>
         </figure>
         <para>
             This means that you can just put you build source code in this directory and stick to the layout convention for a 
diff --git a/subprojects/docs/src/docs/userguide/plugins.xml b/subprojects/docs/src/docs/userguide/plugins.xml
index 54f8777..3b985e2 100644
--- a/subprojects/docs/src/docs/userguide/plugins.xml
+++ b/subprojects/docs/src/docs/userguide/plugins.xml
@@ -123,7 +123,7 @@
             <output args="-q show"/>
         </sample>
         <para>
-            In the above example, we applied the Java plugin which, among other things, did the following:
+            In the example above, we applied the Java plugin which, among other things, did the following:
         </para>
         <itemizedlist>
             <listitem>Added a new domain object type: <apilink class="org.gradle.api.tasks.SourceSet" /></listitem>
@@ -131,7 +131,7 @@
             <listitem>Configured supporting tasks to use these properties to perform work</listitem>
         </itemizedlist>
         <para>
-            All of this happened during the <literal>apply plugin: "java"</literal> step. In the example above we <emphasis>changed</emphasis>
+            All of this happened during the <literal>apply plugin: "java"</literal> step. In the example above, we <emphasis>changed</emphasis>
             the desired location of the class files after this conventional configuration had been performed. Notice by the output with the example
             that the value for <literal>compileJava.destinationDir</literal> also changed to reflect the configuration change.
         </para>
@@ -143,7 +143,7 @@
         <para>
             This ability to configure properties of objects to reflect the value of another object's task at all times (i.e. even when it changes) is
             known as “<emphasis>convention mapping</emphasis>”. It allows Gradle to provide conciseness through convention-over-configuration and
-            sensible defaults yet not require complete reconfiguration if a conventional default needs to be changed. Without this, in the above example
+            sensible defaults yet not require complete reconfiguration if a conventional default needs to be changed. Without this, in the example above,
             we would have had to reconfigure every object that needs to work with the class files.
         </para>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/projectReports.xml b/subprojects/docs/src/docs/userguide/projectReports.xml
index e32f66e..d1e48f9 100644
--- a/subprojects/docs/src/docs/userguide/projectReports.xml
+++ b/subprojects/docs/src/docs/userguide/projectReports.xml
@@ -19,7 +19,7 @@
     <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
-        <userinput>gradle properties</userinput> (see<xref linkend="sec:obtaining_information_about_your_build"/>).
+        <userinput>gradle properties</userinput> (see <xref linkend="sec:obtaining_information_about_your_build"/>).
         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>
@@ -58,6 +58,14 @@ apply plugin: 'project-report'
             </tr>
             <tr>
                 <td>
+                    <literal>htmlDependencyReport</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.reporting.dependencies.HtmlDependencyReportTask"/></td>
+                <td>Generates an HTML dependency and dependency insight report for the project or a set of projects.</td>
+            </tr>
+            <tr>
+                <td>
                     <literal>propertyReport</literal>
                 </td>
                 <td>-</td>
@@ -77,7 +85,7 @@ apply plugin: 'project-report'
                     <literal>projectReport</literal>
                 </td>
                 <td>
-                    <literal>dependencyReport</literal>, <literal>propertyReport</literal>, <literal>taskReport</literal>
+                    <literal>dependencyReport</literal>, <literal>propertyReport</literal>, <literal>taskReport</literal>, <literal>htmlDependencyReport</literal>
                 </td>
                 <td><apilink class="org.gradle.api.Task"/></td>
                 <td>Generates all project reports.</td>
diff --git a/subprojects/docs/src/docs/userguide/publishingIvy.xml b/subprojects/docs/src/docs/userguide/publishingIvy.xml
index a00837d..35278e3 100644
--- a/subprojects/docs/src/docs/userguide/publishingIvy.xml
+++ b/subprojects/docs/src/docs/userguide/publishingIvy.xml
@@ -18,11 +18,11 @@
     <title>Ivy Publishing (new)</title>
     <note>
         <para>
-            This chapter describes the new <emphasis>incubating</emphasis> Ivy publishing support provided by the “<literal>ivy-publish</literal>”
+            This chapter describes the new <link linkend="feature_lifecycle">incubating</link> Ivy publishing support provided by the “<literal>ivy-publish</literal>”
             plugin. Eventually this new publishing support will replace publishing via the <literal>Upload</literal> task.
         </para>
         <para>
-            If you are looking for documentation on 'traditional' Ivy publishing using the <literal>Upload</literal> task please see
+            If you are looking for documentation on the original Ivy publishing support using the <literal>Upload</literal> task please see
             <xref linkend="artifact_management"/>.
         </para>
     </note>
@@ -82,7 +82,7 @@
         <para>
             For the “<literal>ivy-publish</literal>” plugin to have any effect, a <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> must be added to the set of publications.
             This publication determines which artifacts are actually published as well as the details included in the associated Ivy module descriptor file.
-            A publication can be configured by adding components, customising artifacts, and by modifying the generated module descriptor file directly.
+            A publication can be configured by adding components, customizing artifacts, and by modifying the generated module descriptor file directly.
         </para>
         <section>
             <title>Publishing a Software Component</title>
@@ -137,13 +137,13 @@
                 <sourcefile file="build.gradle" snippet="publish-custom-artifact" />
             </sample>
             <para>
-                See <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> for more detailed documentation on how artifacts can be customised.
+                See <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> for more detailed documentation on how artifacts can be customized.
             </para>
         </section>
         <section>
             <title>Identity values for the published project</title>
             <para>
-                The generated Ivy module descriptor file contains an<literal><info></literal> tag that identifies the module.
+                The generated Ivy module descriptor file contains an <literal><info></literal> tag that identifies the module.
                 The default identity values are derived from the following project properties:
             </para>
             <itemizedlist>
@@ -152,14 +152,19 @@
                 <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>
+                Overriding the default identity values is easy: simply specify the <literal>organisation</literal>, <literal>module</literal>
+                or <literal>revision</literal> attributes when configuring the <literal>IvyPublication</literal>.
+            </para>
+            <sample dir="ivy-publish/multiple-publications" id="publishing_ivy:publish-customize-identity" title="customizing the publication identity">
+                <sourcefile file="build.gradle" snippet="customize-identity" />
+            </sample>
             <tip>
                 Certain repositories are not able to handle all supported characters.
                 For example, the ':' character cannot be used as an identifier when publishing to a filesystem-backed repository on Windows.
             </tip>
             <para>
-                Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
-            </para>
-            <para>
                 Gradle will handle any valid Unicode character for organisation, module and revision (as well as artifact name, extension and classifier).
                 The only values that are explicitly prohibited are '<literal>\</literal>', '<literal>/</literal>' and any ISO control character. The supplied values are validated early in publication.
             </para>
@@ -189,6 +194,20 @@
                 The identifier (organisation, module, revision) of the published module is an exception; these values cannot be modified in the descriptor using the `withXML` hook.
             </para>
         </section>
+        <section>
+            <title>Publishing multiple modules</title>
+            <para>
+                Sometimes it's useful to publish multiple modules from your Gradle build, without creating a separate Gradle subproject.
+                An example is publishing a separate API and implementation jar for your library. With Gradle this is simple:
+            </para>
+            <sample dir="ivy-publish/multiple-publications" id="publishing_ivy:publish-multiple-publications" title="Publishing multiple modules from a single project">
+                <sourcefile file="build.gradle" snippet="multiple-publications" />
+            </sample>
+            <para>
+                If a project defines multiple publications then Gradle will publish each of these to the defined repositories. Each publication
+                must be given a unique identity as described above.
+            </para>
+        </section>
     </section>
     <section id="publishing_ivy:repositories">
         <title>Repositories</title>
@@ -244,8 +263,8 @@
         <para>
             The “<literal>ivy-publish</literal>” plugin automatically wires in one <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor" /> task
             for each registered <apilink class="org.gradle.api.publish.ivy.IvyPublication" />.
-            This task is given a name based on the name of the publication:  "<literal>generate«<emphasis>NAME OF PUBLICATION</emphasis>»IvyModuleDescriptor</literal>".
-            So in the above example where the publication is named "<literal>ivyJava</literal>", the task will be named "<literal>generateIvyJavaIvyModuleDescriptor</literal>".
+            This task is given a name based on the name of the publication:  "<literal>generateDescriptorFileFor«<emphasis>NAME OF PUBLICATION</emphasis>»Publication</literal>".
+            So in the example above where the publication is named "<literal>ivyJava</literal>", the task will be named "<literal>generateDescriptorFileForIvyJavaPublication</literal>".
         </para>
         <para>
             You can specify where the generated Ivy file will be located by setting the <literal>destination</literal> property on the generate task.
@@ -253,14 +272,14 @@
         </para>
         <sample dir="ivy-publish/descriptor-customization" id="publishingIvyGenerateDescriptor" title="Generating the Ivy module descriptor file">
             <sourcefile file="build.gradle" snippet="generate" />
-            <output args="generateIvyCustomIvyModuleDescriptor"/>
+            <output args="generateDescriptorFileForIvyCustomPublication"/>
         </sample>
         <note>
             <para>
                 The “<literal>ivy-publish</literal>” plugin leverages some experimental support for late plugin configuration,
                 and the <literal>GenerateIvyDescriptor</literal> task will not be constructed until the publishing extension is configured.
                 The simplest way to ensure that the publishing plugin is configured when you attempt to access the <literal>GenerateIvyDescriptor</literal> task
-                is to place the access inside a <literal>publishing</literal> block, as the above example demonstrates.
+                is to place the access inside a <literal>publishing</literal> block, as the example above demonstrates.
             </para>
             <para>
                 The same applies to any attempt to access publication-specific tasks like <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" />.
@@ -298,12 +317,12 @@
     <section id="publishing_ivy:future">
         <title>Future features</title>
         <para>
-            The “<literal>ivy-publish</literal>” plugin functionality as described above is incomplete, as the feature is still <firstterm>incubating</firstterm>.
+            The “<literal>ivy-publish</literal>” plugin functionality as described above is incomplete, as the feature is still <link linkend="feature_lifecycle">incubating</link>.
             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>Convenient customisation of dependencies reported in <literal>module descriptor</literal>.</listitem>
+            <listitem>Convenient customization of module attributes (<literal>module</literal>, <literal>organisation</literal> etc.)</listitem>
+            <listitem>Convenient customization of dependencies reported in <literal>module descriptor</literal>.</listitem>
             <listitem>Multiple discreet publications per project</listitem>
         </itemizedlist>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/publishingMaven.xml b/subprojects/docs/src/docs/userguide/publishingMaven.xml
index a1a9f07..a766178 100644
--- a/subprojects/docs/src/docs/userguide/publishingMaven.xml
+++ b/subprojects/docs/src/docs/userguide/publishingMaven.xml
@@ -18,11 +18,11 @@
     <title>Maven Publishing (new)</title>
     <note>
         <para>
-            This chapter describes the new <emphasis>incubating</emphasis> Maven publishing support provided by the “<literal>maven-publish</literal>”
+            This chapter describes the new <link linkend="feature_lifecycle">incubating</link> Maven publishing support provided by the “<literal>maven-publish</literal>”
             plugin. Eventually this new publishing support will replace publishing via the <literal>Upload</literal> task.
         </para>
         <para>
-            If you are looking for documentation on 'traditional' Maven publishing using the <literal>Upload</literal> task please see
+            If you are looking for documentation on the original Maven publishing support using the <literal>Upload</literal> task please see
             <xref linkend="artifact_management"/>.
         </para>
     </note>
@@ -81,7 +81,7 @@
         <para>
             For the “<literal>maven-publish</literal>” plugin to have any effect, a <apilink class="org.gradle.api.publish.maven.MavenPublication" /> must be added to the set of publications.
             This publication determines which artifacts are actually published as well as the details included in the associated POM file.
-            A publication can be configured by adding components, customising artifacts, and by modifying the generated POM file directly.
+            A publication can be configured by adding components, customizing artifacts, and by modifying the generated POM file directly.
         </para>
         <section>
             <title>Publishing a Software Component</title>
@@ -136,7 +136,7 @@
                 <sourcefile file="build.gradle" snippet="publish-custom-artifact" />
             </sample>
             <para>
-                See <apilink class="org.gradle.api.publish.maven.MavenPublication" /> for more detailed documentation on how artifacts can be customised.
+                See <apilink class="org.gradle.api.publish.maven.MavenPublication" /> for more detailed documentation on how artifacts can be customized.
             </para>
         </section>
 
@@ -150,14 +150,19 @@
                 <listitem><literal>artifactId</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
                 <listitem><literal>version</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
             </itemizedlist>
+
+            <para>
+                Overriding the default identity values is easy: simply specify the <literal>groupId</literal>, <literal>artifactId</literal>
+                or <literal>version</literal> attributes when configuring the <literal>MavenPublication</literal>.
+            </para>
+            <sample dir="maven-publish/multiple-publications" id="publishing_maven:publish-customize-identity" title="customizing the publication identity">
+                <sourcefile file="build.gradle" snippet="customize-identity" />
+            </sample>
             <tip>
                 Certain repositories will not be able to handle all supported characters.
                 For example, the ':' character cannot be used as an identifier when publishing to a filesystem-backed repository on Windows.
             </tip>
             <para>
-                Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
-            </para>
-            <para>
                 Maven restricts 'groupId' and 'artifactId' to a limited character set (<literal>[A-Za-z0-9_\\-.]+</literal>) and Gradle enforces this restriction.
                 For 'version' (as well as artifact 'extension' and 'classifier'), Gradle will handle any valid Unicode character.
             </para>
@@ -191,6 +196,20 @@
                 The identifier (groupId, artifactId, version) of the published module is an exception; these values cannot be modified in the POM using the `withXML` hook.
             </para>
         </section>
+        <section>
+             <title>Publishing multiple modules</title>
+             <para>
+                 Sometimes it's useful to publish multiple modules from your Gradle build, without creating a separate Gradle subproject.
+                 An example is publishing a separate API and implementation jar for your library. With Gradle this is simple:
+             </para>
+             <sample dir="maven-publish/multiple-publications" id="publishing_maven:publish-multiple-publications" title="Publishing multiple modules from a single project">
+                 <sourcefile file="build.gradle" snippet="multiple-publications" />
+             </sample>
+             <para>
+                 If a project defines multiple publications then Gradle will publish each of these to the defined repositories. Each publication
+                 must be given a unique identity as described above.
+             </para>
+         </section>
     </section>
 
     <section id="publishing_maven:repositories">
@@ -257,8 +276,8 @@
         </para>
         <para>
             The task for generating the POM file is of type <apilink class="org.gradle.api.publish.maven.tasks.GenerateMavenPom"/>, and it is given a name based on the name
-            of the publication: "<literal>generatePomFileFor«<emphasis>NAME OF PUBLICATION</emphasis>»Publication</literal>". So in the below example where the publication is named
-            "<literal>mavenCustom</literal>",
+            of the publication: "<literal>generatePomFileFor«<emphasis>NAME OF PUBLICATION</emphasis>»Publication</literal>". So in the example below,
+            where the publication is named "<literal>mavenCustom</literal>",
             the task will be named "<literal>generatePomFileForMavenCustomPublication</literal>".
         </para>
         <sample dir="maven-publish/pomCustomization" id="publishingMavenGeneratePom" title="Generate a POM file without publishing">
@@ -274,7 +293,7 @@
                 The “<literal>maven-publish</literal>” plugin leverages some experimental support for late plugin configuration,
                 and any <literal>GenerateMavenPom</literal> tasks will not be constructed until the publishing extension is configured.
                 The simplest way to ensure that the publishing plugin is configured when you attempt to access the <literal>GenerateMavenPom</literal> task
-                is to place the access inside a <literal>publishing</literal> block, as the above example demonstrates.
+                is to place the access inside a <literal>publishing</literal> block, as the example above demonstrates.
             </para>
             <para>
                 The same applies to any attempt to access publication-specific tasks like <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository"/>.
diff --git a/subprojects/docs/src/docs/userguide/scalaPlugin.xml b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
index 6f84347..f44f371 100644
--- a/subprojects/docs/src/docs/userguide/scalaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
@@ -160,14 +160,10 @@
     <section>
         <title>Dependency management</title>
         <para>
-            Scala projects need to add a <literal>scala-library</literal> dependency to the appropriate configuration(s). This dependency will then
-            be used as a compile and runtime dependency for the project's Scala code. It will also be used to infer a <literal>scala-compiler</literal>
-            dependency for executing the Scala compiler and Scaladoc tool.
+            Scala projects need to declare a <literal>scala-library</literal> dependency. This dependency will then be used on compile and
+            runtime class paths. It will also be used to get hold of the Scala compiler and Scaladoc tool, respectively.
             <footnote>
-                More precisely, if a <literal>ScalaCompile</literal> or <literal>ScalaDoc</literal> task's <literal>scalaClasspath</literal> is empty,
-                and the project has at least one repository declared, the plugin searches the task's <literal>classpath</literal> for a
-                <literal>scala-library</literal> artifact, and adds a corresponding <literal>scala-compiler</literal> repository dependency to the
-                task's <literal>scalaClasspath</literal>.
+                <para>See <xref linkend="sec:configure_scala_classpath"/>.</para>
             </footnote>
         </para>
         <para>
@@ -179,16 +175,33 @@
         </sample>
         <para>
             If Scala is only used for test code, the <literal>scala-library</literal> dependency should be added to the <literal>testCompile</literal>
-            (but not the <literal>compile</literal>)
             configuration:
         </para>
         <sample id="declareScalaTestDependency" dir="userguide/scala/scalaDependency" title="Declaring a Scala dependency for test code">
             <sourcefile file="build.gradle" snippet="scala-test-dependency"/>
         </sample>
+    </section>
+
+    <section id="sec:configure_scala_classpath">
+        <title>Automatic configuration of scalaClasspath</title>
+        <para>
+            <literal>ScalaCompile</literal> and <literal>ScalaDoc</literal> tasks consume Scala in two ways: on their <literal>classpath</literal>,
+            and on their <literal>scalaClasspath</literal>. The former is used to locate classes referenced by the source code, and will typically
+            contain <literal>scala-library</literal> along with other libraries. The latter is used to load and execute the Scala compiler
+            and Scaladoc tool, respectively, and should only contain the <literal>scala-compiler</literal> library and its dependencies.
+        </para>
         <para>
-            In earlier Gradle versions, it was necessary to add a <literal>scala-compiler</literal> dependency to the <literal>scalaTools</literal>
-            configuration. Although typically no longer necessary (because the compiler dependency is inferred), this is still supported for
-            backwards compatibility.
+            Unless a task's <literal>scalaClasspath</literal> is configured explicitly, the Scala (base) plugin will try to infer it
+            from the task's <literal>classpath</literal>. This is done as follows:
+            <itemizedlist>
+                <listitem>
+                    If a <literal>scala-library</literal> Jar is found on <literal>classpath</literal>, and the project has at least one repository declared,
+                    a corresponding <literal>scala-compiler</literal> repository dependency will be added to <literal>scalaClasspath</literal>.
+                </listitem>
+                <listitem>
+                    Otherwise, execution of the task will fail with a message saying that <literal>scalaClasspath</literal> could not be inferred.
+                </listitem>
+            </itemizedlist>
         </para>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/sonarPlugin.xml b/subprojects/docs/src/docs/userguide/sonarPlugin.xml
index 755e62e..3e2ce40 100644
--- a/subprojects/docs/src/docs/userguide/sonarPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/sonarPlugin.xml
@@ -165,35 +165,35 @@
             The following properties can alternatively be set from the command line, as task parameters of the <literal>sonarAnalyze</literal> task.
             A task parameter will override any corresponding value set in the build script.
         </para>
-        <ul>
-            <li>
+        <itemizedlist>
+            <listitem>
                 <literal>server.url</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>database.url</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>database.driverClassName</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>database.username</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>database.password</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>showSql</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>showSqlResults</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>verbose</literal>
-            </li>
-            <li>
+            </listitem>
+            <listitem>
                 <literal>forceAnalysis</literal>
-            </li>
-        </ul>
+            </listitem>
+        </itemizedlist>
         <para>
             Here is a complete example:
         </para>
diff --git a/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml b/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
index b2a9100..e1fb5d6 100644
--- a/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
@@ -17,7 +17,7 @@
     <title>The Sonar Runner Plugin</title>
     <note>
         <para>
-            The Sonar Runner plugin is <link linkend="sec:incubating_state">incubating</link>.
+            The Sonar runner plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
         </para>
     </note>
     <para>The Sonar Runner plugin provides integration with <ulink url="http://www.sonarsource.org">Sonar</ulink>,
@@ -86,7 +86,7 @@
             <sourcefile file="build.gradle" snippet="connection-settings"/>
         </sample>
         <para>
-            For a complete list of standard Sonar properties, consult the<ulink url="http://docs.codehaus.org/display/SONAR/Analysis+Parameters">Sonar documentation</ulink>.
+            For a complete list of standard Sonar properties, consult the <ulink url="http://docs.codehaus.org/display/SONAR/Analysis+Parameters">Sonar documentation</ulink>.
             If you happen to use additional Sonar plugins, consult their documentation.
         </para>
         <para>
@@ -180,6 +180,10 @@
                 <td>sonar.surefire.reportsPath</td>
                 <td>test.testResultsDir (if the directory exists)</td>
             </tr>
+            <tr>
+                <td>sonar.junit.reportsPath</td>
+                <td>test.testResultsDir (if the directory exists)</td>
+            </tr>
         </table>
     </section>
 
@@ -317,4 +321,4 @@
             </tr>
         </table>
     </section>
-</chapter>
\ No newline at end of file
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/standardPlugins.xml b/subprojects/docs/src/docs/userguide/standardPlugins.xml
index 53ac590..ee34559 100644
--- a/subprojects/docs/src/docs/userguide/standardPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/standardPlugins.xml
@@ -1,25 +1,10 @@
-<!--
-  ~ Copyright 2010 the original author or authors.
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT 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="standard_plugins">
     <title>Standard Gradle plugins</title>
     <para>There are a number of plugins included in the Gradle distribution. These are listed below.
     </para>
     <section>
         <title>Language plugins</title>
-        <para>These plugins add support for various languages which can be compiled and executed in the JVM.</para>
+        <para>These plugins add support for various languages which can be compiled for and executed in the JVM.</para>
         <table>
             <title>Language plugins</title>
             <thead>
@@ -107,49 +92,81 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='cpp'>
-                        <literal>cpp</literal>
+                    <link linkend='nativeBinaries'>
+                        <literal>assembler</literal>
                     </link>
                 </td>
                 <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Adds C++ source compilation capabilities to a project.</para>
+                    <para>Adds native assembly language capabilities to a project.</para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='cpp'>
-                        <literal>cpp-exe</literal>
+                    <link linkend='nativeBinaries'>
+                        <literal>c</literal>
                     </link>
                 </td>
+                <td>-</td>
+                <td>-</td>
                 <td>
-                    <literal>cpp</literal>
+                    <para>Adds C source compilation capabilities to a project.</para>
                 </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='nativeBinaries'>
+                        <literal>cpp</literal>
+                    </link>
+                </td>
+                <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Adds C++ executable compilation and linking capabilities to a project.</para>
+                    <para>Adds C++ source compilation capabilities to a project.</para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='cpp'>
-                        <literal>cpp-lib</literal>
+                    <link linkend='nativeBinaries'>
+                        <literal>objective-c</literal>
                     </link>
                 </td>
+                <td>-</td>
+                <td>-</td>
                 <td>
-                    <literal>cpp</literal>
+                    <para>Adds Objective-C source compilation capabilities to a project.</para>
                 </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='nativeBinaries'>
+                        <literal>objective-cpp</literal>
+                    </link>
+                </td>
+                <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Adds C++ library compilation and linking capabilities to a project.</para>
+                    <para>Adds Objective-C++ source compilation capabilities to a project.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='nativeBinaries'>
+                        <literal>windows-resources</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for including Windows resources in native binaries.</para>
                 </td>
             </tr>
         </table>
     </section>
     <section>
         <title>Integration plugins</title>
-        <para>These plugins provide some integration with various build and runtime technologies.</para>
+        <para>These plugins provide some integration with various runtime technologies.</para>
         <table>
             <title>Integration plugins</title>
             <thead>
@@ -162,18 +179,6 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='announce_plugin'>
-                        <literal>announce</literal>
-                    </link>
-                </td>
-                <td>-</td>
-                <td>-</td>
-                <td>
-                    <para>Publish messages to your favourite platforms, such as Twitter or Growl.</para>
-                </td>
-            </tr>
-            <tr>
-                <td>
                     <link linkend='application_plugin'>
                         <literal>application</literal>
                     </link>
@@ -189,18 +194,6 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='build_announcements_plugin'>
-                        <literal>build-announcements</literal>
-                    </link>
-                </td>
-                <td>announce</td>
-                <td>-</td>
-                <td>
-                    <para>Sends local announcements to your desktop about interesting events in the build lifecycle.</para>
-                </td>
-            </tr>
-            <tr>
-                <td>
                     <link linkend='ear_plugin'>
                         <literal>ear</literal>
                     </link>
@@ -277,7 +270,7 @@
     </section>
     <section>
         <title>Incubating integration plugins</title>
-        <para>These plugins provide some integration with various build and runtime technologies.</para>
+        <para>These plugins provide some integration with various runtime technologies.</para>
         <table>
             <title>Incubating integration plugins</title>
             <thead>
@@ -290,14 +283,27 @@
             </thead>
             <tr>
                 <td>
+                    <link linkend="distribution_plugin">
+                        <literal>distribution</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for building ZIP and TAR distributions.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend="javaLibraryDistribution_plugin">
                         <literal>java-library-distribution</literal>
                     </link>
                 </td>
-                <td><literal>java</literal></td>
+                <td><literal>java</literal>, <literal>distribution</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Adds support for building a ZIP distribution for a Java library.
+                    <para>Adds support for building ZIP and TAR distributions for a Java library.
                     </para>
                 </td>
             </tr>
@@ -308,7 +314,7 @@
                     </link>
                 </td>
                 <td>-</td>
-                <td>-</td>
+                <td><literal>java</literal>, <literal>war</literal></td>
                 <td>
                     <para>This plugin provides a new DSL to support publishing artifacts to Ivy repositories, which improves on the existing DSL.
                     </para>
@@ -327,47 +333,45 @@
                     </para>
                 </td>
             </tr>
+        </table>
+    </section>
+    <section>
+        <title>Software development plugins</title>
+        <para>These plugins provide help with your software development process.</para>
+        <table>
+            <title>Software development plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
             <tr>
                 <td>
-                    <link linkend="bootstrap_plugin">
-                        <literal>maven2Gradle</literal>
+                    <link linkend='announce_plugin'>
+                        <literal>announce</literal>
                     </link>
                 </td>
                 <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Adds support for converting an existing Maven build into a Gradle project.
-                    </para>
+                    <para>Publish messages to your favourite platforms, such as Twitter or Growl.</para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend="buildDashboard_plugin">
-                        <literal>build-dashboard</literal>
+                    <link linkend='build_announcements_plugin'>
+                        <literal>build-announcements</literal>
                     </link>
                 </td>
-                <td>reporting-base</td>
+                <td>announce</td>
                 <td>-</td>
                 <td>
-                    <para>Generates build dashboard report.
-                    </para>
+                    <para>Sends local announcements to your desktop about interesting events in the build lifecycle.</para>
                 </td>
             </tr>
-        </table>
-    </section>
-    <section>
-        <title>Software development plugins</title>
-        <para>These plugins provide help with your software development process.</para>
-        <table>
-            <title>Software development plugins</title>
-            <thead>
-                <tr>
-                    <td>Plugin Id</td>
-                    <td>Automatically applies</td>
-                    <td>Works with</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
             <tr>
                 <td>
                     <link linkend='checkstyle_plugin'>
@@ -537,7 +541,7 @@
                     </link>
                 </td>
                 <td>-</td>
-                <td>-</td>
+                <td>java-base, java, jacoco</td>
                 <td>
                     <para>Provides integration with the
                         <ulink url="http://www.sonarsource.org">Sonar</ulink>
@@ -562,16 +566,95 @@
             </thead>
             <tr>
                 <td>
+                    <link linkend="buildDashboard_plugin">
+                        <literal>build-dashboard</literal>
+                    </link>
+                </td>
+                <td>reporting-base</td>
+                <td>-</td>
+                <td>
+                    <para>Generates build dashboard report.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="build_init_plugin">
+                        <literal>build-init</literal>
+                    </link>
+                </td>
+                <td>wrapper</td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for initializing a new Gradle build. Handles converting a Maven build to a Gradle build.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="nativeBinaries">
+                        <literal>cunit</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for running <ulink url="http://cunit.sourceforge.net">CUnit</ulink> tests.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='jacoco_plugin'>
+                        <literal>jacoco</literal>
+                    </link>
+                </td>
+                <td>reporting-base</td>
+                <td>java</td>
+                <td>
+                    <para>Provides integration with the
+                        <ulink url="http://www.eclemma.org/jacoco/">JaCoCo</ulink>
+                            code coverage library for Java.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend='sonar_runner_plugin'>
                         <literal>sonar-runner</literal>
                     </link>
                 </td>
                 <td>-</td>
-                <td>-</td>
+                <td>java-base, java, jacoco</td>
                 <td>
                     <para>Provides integration with the
                         <ulink url="http://www.sonarsource.org">Sonar</ulink>
-                        code quality platform. Superceeds the <link linkend='sonar_plugin'><literal>sonar</literal></link> plugin.
+                        code quality platform. Supersedes the <link linkend='sonar_plugin'><literal>sonar</literal></link> plugin.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="nativeBinaries">
+                        <literal>visual-studio</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>native language plugins</td>
+                <td>
+                    <para>Adds integration with Visual Studio.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="wrapper_plugin">
+                        <literal>wrapper</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds a <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/> task for generating Gradle wrapper files.
                     </para>
                 </td>
             </tr>
@@ -655,4 +738,4 @@
             <ulink url="http://wiki.gradle.org/display/GRADLE/Plugins">wiki</ulink>.
         </para>
     </section>
-</chapter>
\ No newline at end of file
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/tasks.xml b/subprojects/docs/src/docs/userguide/tasks.xml
index b98232a..015d13e 100644
--- a/subprojects/docs/src/docs/userguide/tasks.xml
+++ b/subprojects/docs/src/docs/userguide/tasks.xml
@@ -32,7 +32,7 @@
             <sourcefile file="build.gradle"/>
         </sample>
         <para>You can also use strings for the task names:</para>
-        <sample id="defineUsingStringTaskNames" dir="userguide/tasks/defineUsingStringTaskNames" title="Defining tasks - using strings">
+        <sample id="defineUsingStringTaskNames" dir="userguide/tasks/defineUsingStringTaskNames" title="Defining tasks - using strings for task names">
             <sourcefile file="build.gradle"/>
         </sample>
         <para>There is an alternative syntax for defining tasks, which you may prefer to use:</para>
@@ -40,13 +40,13 @@
             <sourcefile file="build.gradle"/>
         </sample>
         <para>Here we add tasks to the <literal>tasks</literal> collection. Have a look at
-            <apilink class="org.gradle.api.tasks.TaskContainer"/> for more variations of the <literal>add()</literal>
+            <apilink class="org.gradle.api.tasks.TaskContainer"/> for more variations of the <literal>create()</literal>
             method.</para>
     </section>
     <section>
         <title>Locating tasks</title>
         <para>You often need to locate the tasks that you have defined in the build file, for example, to configure them
-            or use them for dependencies. There are a number of ways you can do this. Firstly, each task is available as
+            or use them for dependencies. There are a number of ways of doing this. Firstly, each task is available as
             a property of the project, using the task name as the property name:
         </para>
         <sample id="accessAsProperty" dir="userguide/tasks/accessAsProperty" title="Accessing tasks as properties">
@@ -84,17 +84,7 @@
             (<literal>myCopy</literal>) in the configuration statement every time. This is a redundancy and not very
             nice to read.
         </para>
-        <para>There is a more convenient way of doing this.
-        </para>
-        <sample id="configureUsingLiterateStyle" dir="userguide/tasks/configureUsingLiterateStyle" title="Configuring a task - fluent interface">
-            <sourcefile file="build.gradle"/>
-        </sample>
-        <para>You might know this approach from the Hibernates Criteria Query API or JMock. Of course the API of a task
-            has to support this. The <literal>from</literal>, <literal>to</literal> and <literal>include</literal>
-            methods all return an object that may be used to chain to additional configuration methods. Gradle's built-in tasks usually
-            support this configuration style.
-        </para>
-        <para>But there is yet another way of configuring a task. It also preserves the context and it is arguably the
+        <para>There is another way of configuring a task. It also preserves the context and it is arguably the
             most readable. It is usually our favorite.
         </para>
         <sample id="configureUsingClosure" dir="userguide/tasks/configureUsingClosure" title="Configuring a task - with closure">
@@ -102,14 +92,8 @@
         </sample>
         <para>This works for <emphasis>any</emphasis> task. Line 3 of the example is just a shortcut for the
             <literal>tasks.getByName()</literal> method. It is important to note that if you pass a closure to the
-            <literal>getByName()</literal> method, this closure is applied to <emphasis>configure</emphasis> the task.
-        </para>
-        <para>There is a slightly different ways of doing this.</para>
-        <sample id="configureUsingConfigure" dir="userguide/tasks/configureUsingConfigure" title="Configuring a task - with configure() method">
-            <sourcefile file="build.gradle"/>
-        </sample>
-        <para>Every task has a <literal>configure()</literal> method, which you can pass a closure for configuring the task.
-            Gradle uses this style for configuring objects in many places, not just for tasks.
+            <literal>getByName()</literal> method, this closure is applied to <emphasis>configure</emphasis> the task, not when
+            the task executes.
         </para>
         <para>You can also use a configuration closure when you define a task.</para>
         <sample id="defineAndConfigure" dir="userguide/tasks/defineAndConfigure" title="Defining a task with closure">
@@ -151,6 +135,64 @@
         </sample>
         <para>For more information about task dependencies, see the <apilink class="org.gradle.api.Task"/> API.</para>
     </section>
+    <section id="sec:ordering_tasks">
+        <title>Ordering tasks</title>
+        <note>
+            <para>
+                Task ordering is an <link linkend="feature_lifecycle">incubating</link> feature. Please be aware that this feature may change in later Gradle versions.
+            </para>
+        </note>
+        <para>
+            In some cases it is useful to control the <emphasis>order</emphasis> in which 2 tasks will execute, without introducing an explicit dependency between those tasks.
+            The primary difference between a task <emphasis>ordering</emphasis> and a task <emphasis>dependency</emphasis> is that an ordering rule does not influence which tasks
+            will be executed, only the order in which they will be executed.
+        </para>
+        <para>
+            Task ordering can be useful in a number of scenarios:
+        </para>
+        <itemizedlist>
+            <listitem>Enforce sequential ordering of tasks: eg. 'build' never runs before 'clean'.</listitem>
+            <listitem>Run build validations early in the build: eg. validate I have the correct credentials before starting the work for a release build.</listitem>
+            <listitem>Get feedback faster by running quick verification tasks before long verification tasks: eg. unit tests should run before integration tests.</listitem>
+            <listitem>A task that aggregates the results of all tasks of a particular type: eg. test report task combines the outputs of all executed test tasks.</listitem>
+        </itemizedlist>
+        <para>
+            There are two ordering rules available: "<emphasis>must run after</emphasis>" and "<emphasis>should run after</emphasis>".
+        </para>
+        <para>By using 'must run after" ordering rule you can specify that <literal>taskB</literal> must always run after <literal>taskA</literal>,
+            whenever both <literal>taskA</literal> and <literal>taskB</literal> are scheduled for execution. This is expressed as <literal>taskB.mustRunAfter(taskA)</literal>. The 'should run after' ordering rule is similar but less strict as it will be ignored in two situations. Firstly if using that rule introduces an ordering cycle. Secondly when using parallel execution and all dependencies of a task have been satisfied apart from should run after then this task will be run regardles [...]
+        </para>
+        <para>
+            With these rules present it is still possible to execute <literal>taskA</literal> without <literal>taskB</literal> and vice-versa.
+        </para>
+        <sample id="mustRunAfter" dir="userguide/tasks/mustRunAfter" title="Adding a 'must run after' task ordering">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskY taskX"/>
+        </sample>
+        <sample id="shouldRunAfter" dir="userguide/tasks/shouldRunAfter" title="Adding a 'should run after' task ordering">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskY taskX"/>
+        </sample>
+        <para>In the examples above, it is still possible to execute <literal>taskY</literal> without causing <literal>taskX</literal> to run:</para>
+        <sample id="mustRunAfterSingleTask" dir="userguide/tasks/mustRunAfter" title="Task ordering does not imply task execution">
+            <output args="-q taskY"/>
+        </sample>
+        <para>To specify a "must run after" or "should run after" ordering between 2 tasks, you use the <apilink class="org.gradle.api.Task" method="mustRunAfter"/> and <apilink class="org.gradle.api.Task" method="shouldRunAfter"/> methods.
+            These method accept a task instance, a task name or any other input accepted by <apilink class="org.gradle.api.Task" method="dependsOn"/>.
+        </para>
+        <para>
+            Note that "<literal>B.mustRunAfter(A)</literal>" or "<literal>B.shouldRunAfter(A)</literal>" does not imply any execution dependency between the tasks:
+        </para>
+        <itemizedlist>
+            <listitem>It is possible to execute tasks <literal>A</literal> and <literal>B</literal> independently. The ordering rule only has an effect when both tasks are scheduled for execution.</listitem>
+            <listitem>When run with <literal>--continue</literal>, it is possible for <literal>B</literal> to execute in the event that <literal>A</literal> fails.</listitem>
+        </itemizedlist>
+        <para>As mentioned before 'should run after' ordering rule will be ignored if it introduces an ordering cycle:</para>
+        <sample id="shouldRunAfterWithCycle" dir="userguide/tasks/shouldRunAfterWithCycle" title="A 'should run after' task ordering is ignored if it introduces an ordering cycle">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+    </section>
     <section>
         <title>Adding a description to a task</title>
         <para>You can add a description to your task. This description is for example displayed when executing
@@ -228,7 +270,7 @@
         </section>
     </section>
 
-    <section>
+    <section id="sec:up_to_date_checks">
         <title>Skipping tasks that are up-to-date</title>
         <para>If you are using one of the tasks that come with Gradle, such as a task added by the Java plugin,
             you might have noticed that Gradle will skip tasks that are up-to-date. This behaviour is also available
@@ -261,10 +303,20 @@
                 <output args="transform" ignoreExtraLines="true"/>
             </sample>
             <para>Now, Gradle knows which files to check to determine whether the task is up-to-date or not.</para>
-
-            <para>The task's <literal>inputs</literal> property is of type <apilink class="org.gradle.api.tasks.TaskInputs"/>.
+            <para>
+                The task's <literal>inputs</literal> property is of type <apilink class="org.gradle.api.tasks.TaskInputs"/>.
                 The task's <literal>outputs</literal> property is of type <apilink class="org.gradle.api.tasks.TaskOutputs"/>.
             </para>
+            <para>
+                A task with no defined outputs will <emphasis>never</emphasis> be considered up-to-date.
+                For scenarios where the outputs of a task are not files, or for more complex scenarios, the
+                <apilink class="org.gradle.api.tasks.TaskOutputs" method="upToDateWhen(groovy.lang.Closure)" /> method allows you to calculate programmatically if
+                the tasks outputs should be considered up to date.
+            </para>
+            <para>
+                A task with only outputs defined will be considered up-to-date if those outputs are unchanged since the previous build.
+            </para>
+
         </section>
         <section>
             <title>How does it work?</title>
@@ -272,8 +324,7 @@
                 Before a task is executed for the first time, Gradle takes a snapshot of the inputs. This snapshot contains
                 the set of input files and a hash of the contents of each file. Gradle then executes the task. If the
                 task completes successfully, Gradle takes a snapshot of the outputs. This snapshot contains the set of
-                output files and a hash of the contents of each file. Gradle takes note of any files created, changed or
-                deleted in the output directories of the task. Gradle persists both snapshots for next time the task
+                output files and a hash of the contents of each file. Gradle persists both snapshots for next time the task
                 is executed.
             </para>
             <para>
@@ -282,12 +333,17 @@
                 date and skips the task. If they are not the same, Gradle executes the task. Gradle persists both snapshots
                 for next time the task is executed.
             </para>
+            <para>
+                Note that if a task has an output directory specified, any files added to that directory since the last time it was executed
+                are ignored and will NOT cause the task to be out of date. This is so unrelated tasks may share an output directory without interfering with each other.
+                If this is not the behaviour you want for some reason, consider using <apilink class="org.gradle.api.tasks.TaskOutputs" method="upToDateWhen(groovy.lang.Closure)" />
+            </para>
         </section>
     </section>
 
     <section>
         <title>Task rules</title>
-        <para>Sometimes you want to have a task which behavior depends on a large or infinite number value range
+        <para>Sometimes you want to have a task whose behavior depends on a large or infinite number value range
             of parameters. A very nice and expressive way to provide such tasks are task rules:
         </para>
         <sample id="taskRule" dir="userguide/tasks/addRules" title="Task rule">
@@ -297,7 +353,7 @@
         <para>The String parameter is used as a description for the rule. This description is shown when running
             for example <userinput>gradle tasks</userinput>.
         </para>
-        <para>Rules not just work for calling tasks from the command line. You can also create dependsOn relations
+        <para>Rules not just work when calling tasks from the command line. You can also create dependsOn relations
             on rule based tasks:
         </para>
         <sample id="taskRuleDependsOn" dir="userguide/tasks/addRules" title="Dependency on rule based tasks">
@@ -305,6 +361,34 @@
             <output args="-q groupPing"/>
         </sample>
     </section>
+
+    <section>
+        <title>Finalizer tasks</title>
+        <note>
+            <para>
+                Finalizers tasks are an <firstterm>incubating</firstterm> feature (see <xref linkend="sec:incubating_state"/>).
+            </para>
+        </note>
+        <para>Finalizer tasks are automatically added to the task graph when the finalized task is scheduled to run.</para>
+        <sample id="taskFinalizers" dir="userguide/tasks/finalizers" title="Adding a task finalizer">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+        <para>Finalizer task will be executed even if the finalized task fails.</para>
+        <sample id="taskFinalizersWithFailure" dir="userguide/tasks/finalizersWithFailure" title="Task finalizer for a failing task">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+        <para>On the other hand, finalizer tasks are not executed if the finalized task didn't do any work, for example due to failed
+            task dependency or if it's considered up to date.</para>
+        <para>Finalizer tasks are useful in situations where build creates a resource that has to be cleaned up regardless
+            of the build failing or succeeding. An example of such resource is a web container started before an integration test task
+            and which should be always shut down, even if some of the tests fail.</para>
+        <para>To specify a finalizer task you use the <apilink class="org.gradle.api.Task" method="finalizedBy"/> method.
+            This method accepts a task instance, a task name or any other input accepted by <apilink class="org.gradle.api.Task" method="dependsOn"/>.
+        </para>
+    </section>
+
     <section id='sec:the_idea_behind_gradle_tasks'>
         <title>Summary</title>
         <para>If you are coming from Ant, such an enhanced Gradle task as <emphasis>Copy</emphasis> looks like a mixture
diff --git a/subprojects/docs/src/docs/userguide/thisAndThat.xml b/subprojects/docs/src/docs/userguide/thisAndThat.xml
index e15074d..cff91bc 100644
--- a/subprojects/docs/src/docs/userguide/thisAndThat.xml
+++ b/subprojects/docs/src/docs/userguide/thisAndThat.xml
@@ -59,14 +59,15 @@
             </footnote>
             If the environment property follows the pattern
             <literal>ORG_GRADLE_PROJECT_<replaceable>propertyName</replaceable>=somevalue</literal>,
-            <literal>propertyName</literal> is added to your project object. If in the future CI servers support Gradle
-            directly, they might start Gradle via its main method. Therefore we already support the same mechanism for
+            <literal>propertyName</literal> is added to your project object. We also support the same mechanism for
             system properties. The only difference is the pattern, which is
             <literal>org.gradle.project.<replaceable>propertyName</replaceable></literal>.
         </para>
         <para>With the <filename>gradle.properties</filename> files you can also set system properties. If a property
             in such a file has the prefix <literal>systemProp.</literal> the property and its value are added to the 
             system properties, without the prefix.
+            In a multi project build, <literal>systemProp.</literal> properties set in any project except the root will be ignored.
+            That is, only <literal>systemProp.</literal> in the root project's <filename>gradle.properties</filename> file will be set as system properties.
         </para>
         <sample id="properties" dir="userguide/tutorial/properties" title="Setting properties with a gradle.properties file">
             <sourcefile file="gradle.properties"/>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
index 46a99e3..db76876 100755
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -60,6 +60,7 @@
     <xi:include href='findBugsPlugin.xml'/>
     <xi:include href='jdependPlugin.xml'/>
     <xi:include href='pmdPlugin.xml'/>
+    <xi:include href='jacocoPlugin.xml'/>
     <xi:include href='sonarPlugin.xml'/>
     <xi:include href='sonarRunnerPlugin.xml'/>
     <xi:include href='osgi.xml'/>
@@ -72,13 +73,14 @@
     <xi:include href='distributionPlugin.xml'/>
     <xi:include href='applicationPlugin.xml'/>
     <xi:include href='javaLibraryDistributionPlugin.xml'/>
-    <xi:include href='bootstrapPlugin.xml'/>
+    <xi:include href='buildInitPlugin.xml'/>
+    <xi:include href='wrapperPlugin.xml'/>
     <xi:include href='buildDashboardPlugin.xml'/>
-	<xi:include href='depMngmt.xml'/>
+    <xi:include href='depMngmt.xml'/>
     <xi:include href='artifactMngmt.xml'/>
     <xi:include href='mavenPlugin.xml'/>
     <xi:include href='signingPlugin.xml'/>
-    <xi:include href='cpp.xml'/>
+    <xi:include href='nativeBinaries.xml'/>
     <xi:include href='buildLifecycle.xml'/>
     <xi:include href='multiproject.xml'/>
     <xi:include href='customTasks.xml'/>
diff --git a/subprojects/docs/src/docs/userguide/warPlugin.xml b/subprojects/docs/src/docs/userguide/warPlugin.xml
index 15a0be8..d8a9029 100644
--- a/subprojects/docs/src/docs/userguide/warPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/warPlugin.xml
@@ -175,7 +175,7 @@
         <title>Customizing</title>
         <para>Here is an example with the most important customization options:
         </para>
-        <sample id="webproject" dir="webApplication/customised" title="Customization of war plugin">
+        <sample id="webproject" dir="webApplication/customized" title="Customization of war plugin">
             <sourcefile file="build.gradle" snippet="customization"/>
         </sample>
         <para>Of course one can configure the different file-sets with a closure to define excludes and includes.
diff --git a/subprojects/docs/src/docs/userguide/workingWithFiles.xml b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
index 6e7ef94..1866072 100644
--- a/subprojects/docs/src/docs/userguide/workingWithFiles.xml
+++ b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
@@ -168,7 +168,7 @@
         <para>Many objects in Gradle have properties which accept a set of input files. For example, the
             <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.
+            supported by the <link linkend="sec:file_collections">files()</link> method, which was shown above.
             This means you can set the property using, for example, a <classname>File</classname>, <classname>String</classname>,
             collection, <classname>FileCollection</classname> or even a closure.
             Here are some examples:
@@ -188,14 +188,14 @@
     <section id="sec:copying_files">
         <title>Copying files</title>
         <para>You can use the <apilink class="org.gradle.api.tasks.Copy"/> task to copy files. The copy task is very flexible, and allows
-            you to, for example, filter the contents of the files as they are copied, and to map the files names.
+            you to, for example, filter the contents of the files as they are copied, and map to the file names.
         </para>
         <para>To use the <literal>Copy</literal> task, you must provide a set of source files to copy, and a destination directory to copy
             the files to. You may also specify how to transform the files as they are copied. You do all this using a
             <firstterm>copy spec</firstterm>. A copy spec is represented by the <apilink class="org.gradle.api.file.CopySpec"/> interface. The
             <literal>Copy</literal> task implements this interface.
             You specify the source files using the <apilink class="org.gradle.api.file.CopySpec" method="from(java.lang.Object...)"/>
-            method. To specify the destination directory, you use the <apilink class="org.gradle.api.file.CopySpec" method="into(java.lang.Object)"/>
+            method. To specify the destination directory, use the <apilink class="org.gradle.api.file.CopySpec" method="into(java.lang.Object)"/>
             method.
         </para>
         <sample id="copy" dir="userguide/files/copy" title="Copying files using the copy task">
@@ -206,7 +206,9 @@
             <link linkend="sec:file_collections">files()</link> method does. When an argument resolves to a directory,
             everything under that directory (but not the directory itself) is recursively copied into the destination
             directory. When an argument resolves to a file, that file is copied into the destination directory.
-            When an argument resolves to a non-existing file, that argument is ignored.
+            When an argument resolves to a non-existing file, that argument is ignored. If the argument is
+            a task, the output files (i.e. the files the task creates) of the task are copied and the task is automatically
+            added as a dependency of the <literal>Copy</literal> task.
             The <literal>into()</literal> accepts
             any of the arguments that the <link linkend="sec:locating_files">file()</link> method does. Here is another
             example:
@@ -219,10 +221,26 @@
             <sourcefile file="build.gradle" snippet="copy-task-with-patterns"/>
         </sample>
         <para>You can also use the <apilink class="org.gradle.api.Project" method="copy"/> method to copy files. It works the
-            same way as the task.</para>
-        <sample id="copy" dir="userguide/files/copy" title="Copying files using the copy() method">
+            same way as the task with some major limitations though. First, the <literal>copy()</literal> is not incremental
+            (see <xref linkend="sec:up_to_date_checks" />).
+        </para>
+        <sample id="copy" dir="userguide/files/copy" title="Copying files using the copy() method without up-to-date check">
             <sourcefile file="build.gradle" snippet="copy-method"/>
         </sample>
+        <para>Secondly, the <literal>copy()</literal> method can not honor task dependencies when a task is used as a copy source
+            (i.e. as an argument to <literal>from()</literal>) because it's a method and not a task.
+            As such, if you are using the <literal>copy()</literal> method as part of a task action, you must explicitly
+            declare all inputs and outputs in order to get the correct behavior.
+        </para>
+        <sample id="copy" dir="userguide/files/copy" title="Copying files using the copy() method with up-to-date check">
+            <sourcefile file="build.gradle" snippet="copy-method-with-dependency"/>
+        </sample>
+        <para>
+            It is preferable to use the <literal>Copy</literal> task wherever possible, as it support incremental building and task dependency inference
+            without any extra effort on your part. The <literal>copy()</literal> method can be used to copy files as <emphasis>part</emphasis> of a task's
+            implementation. That is, the copy method is intended to be used by custom tasks (see <xref linkend="custom_tasks" />) that need to copy files
+            as part of their function. In such a scenario, the custom task should sufficiently declare the inputs/outputs relevant to the copy action.
+        </para>
         <section>
             <title>Renaming files</title>
             <sample id="renameOnCopy" dir="userguide/files/copy" title="Renaming files as they are copied">
@@ -238,7 +256,7 @@
         <section>
             <title>Using the <classname>CopySpec</classname> class</title>
             <para>Copy specs form a hierarchy. A copy spec inherits its destination path, include patterns, exclude patterns, copy actions,
-                name mappings, filters.</para>
+                name mappings and filters.</para>
             <sample id="nestedCopySpecs" dir="userguide/files/copy" title="Nested copy specs">
                 <sourcefile file="build.gradle" snippet="nested-specs"/>
             </sample>
@@ -267,8 +285,9 @@
             Archives are created using the various archive tasks:
             <apilink class="org.gradle.api.tasks.bundling.Zip"/>,
             <apilink class="org.gradle.api.tasks.bundling.Tar"/>,
-            <apilink class="org.gradle.api.tasks.bundling.Jar"/>, and
-            <apilink class="org.gradle.api.tasks.bundling.War"/>.
+            <apilink class="org.gradle.api.tasks.bundling.Jar"/>,
+            <apilink class="org.gradle.api.tasks.bundling.War"/>, and
+            <apilink class="org.gradle.plugins.ear.Ear"/>.
             They all work the same way, so let's look at how you create a ZIP file.
         </para>
         <sample id="createZip" dir="userguide/files/archives" title="Creating a ZIP archive">
@@ -389,7 +408,7 @@
 
         <section>
             <title>Sharing content between multiple archives</title>
-            <para>Using the <apilink class="org.gradle.api.Project" method="copySpec"/> method to share content between archives.</para>
+            <para>You can use the <apilink class="org.gradle.api.Project" method="copySpec"/> method to share content between archives.</para>
         </section>
 
         <para>Often you will want to publish an archive, so that it is usable from another project. This process is
@@ -398,4 +417,4 @@
 
     </section>
 
-</chapter>
\ No newline at end of file
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/wrapperPlugin.xml b/subprojects/docs/src/docs/userguide/wrapperPlugin.xml
new file mode 100644
index 0000000..9ad70bc
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/wrapperPlugin.xml
@@ -0,0 +1,59 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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='wrapper_plugin'>
+    <title>Wrapper Plugin</title>
+
+    <note>
+        <para>
+            The wrapper plugin is currently <link linkend="feature_lifecycle">incubating</link>. Please be aware that the DSL and other configuration may change in later Gradle versions.
+        </para>
+    </note>
+
+    <para>The Gradle wrapper plugin allows the generation of Gradle wrapper files by adding a <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/>
+        task, that generates all files needed to run the build using the Gradle Wrapper.
+        Details about the Gradle Wrapper can be found in the according chapter <xref linkend='gradle_wrapper'/>
+    </para>
+    <section>
+        <title>Usage</title>
+    <para>
+        Without modifying the <literal>build.gradle</literal> file, the wrapper plugin can be auto-applied to the rootproject of the current build by running
+        <literal>gradle wrapper</literal> from the commandline. This applies the plugin if no task named <literal>wrapper</literal> is already defined in the build.
+    </para>
+    </section>
+    <section>
+        <title>Tasks</title>
+    <para>The wrapper plugin adds the following tasks to the project:</para>
+            <table>
+                <title>Wrapper plugin - tasks</title>
+                <thead>
+                    <tr>
+                        <td>Task name</td>
+                        <td>Depends on</td>
+                        <td>Type</td>
+                        <td>Description</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td>
+                        <literal>wrapper</literal>
+                    </td>
+                    <td>-</td>
+                    <td><apilink class="org.gradle.api.tasks.wrapper.Wrapper"/></td>
+                    <td>Generates Gradle wrapper files.</td>
+                </tr>
+            </table>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/writingBuildScripts.xml b/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
index 8bdeff3..4ed644b 100644
--- a/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
+++ b/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
@@ -179,7 +179,7 @@
         <title>Some Groovy basics</title>
         <para>Groovy provides plenty of features for creating DSLs, and the Gradle build language takes advantage of these.
             Understanding how the build language works will help you when you write your build script, and in particular,
-            when you start to write customs plugins and tasks.
+            when you start to write custom plugins and tasks.
         </para>
         <section>
             <title>Groovy JDK</title>
diff --git a/subprojects/docs/src/samples/application/build.gradle b/subprojects/docs/src/samples/application/build.gradle
index e0dbfa6..5b30d64 100644
--- a/subprojects/docs/src/samples/application/build.gradle
+++ b/subprojects/docs/src/samples/application/build.gradle
@@ -9,6 +9,10 @@ version = '1.0.2'
 mainClassName = "org.gradle.sample.Main"
 // END SNIPPET mainClassName-conf
 
+// START SNIPPET application-defaultjvmargs
+applicationDefaultJvmArgs = ["-Dgreeting.language=en"]
+// END SNIPPET application-defaultjvmargs
+
 // START SNIPPET distribution-spec
 task createDocs {
     def docs = file("$buildDir/docs")
diff --git a/subprojects/docs/src/samples/application/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/application/src/main/java/org/gradle/sample/Main.java
index 315288a..8aa3672 100644
--- a/subprojects/docs/src/samples/application/src/main/java/org/gradle/sample/Main.java
+++ b/subprojects/docs/src/samples/application/src/main/java/org/gradle/sample/Main.java
@@ -4,6 +4,10 @@ import org.apache.commons.collections.list.GrowthList;
 public class Main {
     public static void main(String[] args) {
         GrowthList l = new GrowthList();
-        System.out.println("Greetings from the sample application.");
+        if(System.getProperty("greeting.language").equals("en")){
+            System.out.println("Greetings from the sample application.");
+        }else{
+            System.out.println("Bonjour, monde!");
+        }
     }
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/buildDashboard/build.gradle b/subprojects/docs/src/samples/buildDashboard/build.gradle
index f57e409..486877d 100755
--- a/subprojects/docs/src/samples/buildDashboard/build.gradle
+++ b/subprojects/docs/src/samples/buildDashboard/build.gradle
@@ -25,6 +25,6 @@ repositories {
 }
 
 dependencies {
-    groovy localGroovy()
+    compile localGroovy()
     testCompile 'junit:junit:4.11'
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/buildDashboard/src/main/java/org/gradle/sample/Person.java b/subprojects/docs/src/samples/buildDashboard/src/main/java/org/gradle/sample/Person.java
new file mode 100755
index 0000000..5ff1d1a
--- /dev/null
+++ b/subprojects/docs/src/samples/buildDashboard/src/main/java/org/gradle/sample/Person.java
@@ -0,0 +1,15 @@
+package org.gradle.sample;
+
+import java.lang.String;
+
+class Person {
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/codeQuality/build.gradle b/subprojects/docs/src/samples/codeQuality/build.gradle
index 63d401d..f8a58f3 100755
--- a/subprojects/docs/src/samples/codeQuality/build.gradle
+++ b/subprojects/docs/src/samples/codeQuality/build.gradle
@@ -21,6 +21,6 @@ repositories {
 }
 
 dependencies {
-    groovy localGroovy()
+    compile localGroovy()
     testCompile 'junit:junit:4.11'
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/dependencies/build.gradle b/subprojects/docs/src/samples/cpp/dependencies/build.gradle
deleted file mode 100644
index b96b63f..0000000
--- a/subprojects/docs/src/samples/cpp/dependencies/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-apply plugin: "base"
-
-project(":lib") {
-    archivesBaseName = "some-lib"
-
-    // START SNIPPET use-plugin-lib
-    apply plugin: "cpp-lib"
-    // END SNIPPET use-plugin-lib
-    apply plugin: "eclipse-cdt"
-    apply plugin: 'maven'
-
-
-// START SNIPPET upload
-group = "some-org"
-archivesBaseName = "some-lib"
-version = 1.0
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri("${buildDir}/repo"))
-        }
-    }
-}
-// END SNIPPET upload
-
-}
-
-project(":exe") {
-    // START SNIPPET use-plugin-exe
-    apply plugin: "cpp-exe"
-    // END SNIPPET use-plugin-exe
-    apply plugin: "eclipse-cdt"
-    apply plugin: "maven"
-
-    version = 1.0
-    
-    repositories {
-        maven {
-            url new File(project(":lib").buildDir, "repo")
-        }
-    }
-
-// START SNIPPET declaring-dependencies
-cpp {
-    sourceSets {
-        main {
-            dependency group: "some-org", name: "some-lib", version: "1.0"
-        }
-    }
-}
-// END SNIPPET declaring-dependencies
-    
-    uploadArchives {
-        repositories {
-            mavenDeployer {
-                repository(url: uri("${buildDir}/repo"))
-            }
-        }
-    }
-}
-
-task build(dependsOn: project(":exe").compileMain)
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/cpp/hello.cpp
deleted file mode 100644
index 1ac707f..0000000
--- a/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/cpp/hello.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include <iostream>
-
-void hello () {
-  std::cout << "Hello, World!\n";
-}
diff --git a/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/headers/hello.h b/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/headers/hello.h
deleted file mode 100644
index 14e73eb..0000000
--- a/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/headers/hello.h
+++ /dev/null
@@ -1 +0,0 @@
-void hello();
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exe/build.gradle b/subprojects/docs/src/samples/cpp/exe/build.gradle
deleted file mode 100644
index bcbc79e..0000000
--- a/subprojects/docs/src/samples/cpp/exe/build.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-apply plugin: "cpp-exe"
-
-// START SNIPPET args
-executables {
-    main {
-        spec {
-            args "-fno-access-control", "-fconserve-space"
-        }
-    }
-}
-// END SNIPPET args
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/build.gradle b/subprojects/docs/src/samples/cpp/exewithlib/build.gradle
deleted file mode 100644
index 1bce29e..0000000
--- a/subprojects/docs/src/samples/cpp/exewithlib/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-// START SNIPPET project-dependencies
-project(":lib") {
-    apply plugin: "cpp-lib"
-}
-
-project(":exe") {
-    apply plugin: "cpp-exe"
-    cpp {
-        sourceSets {
-            main {
-                libs << project(":lib").libraries.main
-            }
-        }
-    }
-}
-// END SNIPPET project-dependencies
-
-task build(dependsOn: project(":exe").compileMain)
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/settings.gradle b/subprojects/docs/src/samples/cpp/exewithlib/settings.gradle
deleted file mode 100644
index 462618d..0000000
--- a/subprojects/docs/src/samples/cpp/exewithlib/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include "exe", "lib"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
index d47ed71..693a875 100644
--- a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
+++ b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
@@ -37,7 +37,7 @@ class ProductPlugin implements Plugin<Project> {
             configurations {
                 runtime
             }
-            tasks.add(name: 'dist', type: Zip)
+            tasks.create(name: 'dist', type: Zip)
             artifacts {
                 archives dist
             }
diff --git a/subprojects/docs/src/samples/customDistribution/plugin/build.gradle b/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
index f046720..e20c41f 100644
--- a/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
+++ b/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
@@ -12,7 +12,7 @@ configurations {
 
 dependencies {
     gradleApi gradleApi()
-    groovy localGroovy()
+    compile localGroovy()
     compile 'com.google.guava:guava:11.0.2'
 }
 
diff --git a/subprojects/docs/src/samples/customPlugin/plugin/build.gradle b/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
index 5eb4b90..028f3f7 100644
--- a/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
+++ b/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
@@ -8,7 +8,7 @@ dependencies {
     compile gradleApi()
 // END SNIPPET gradle-api-dependencies
 // START SNIPPET local-groovy-dependencies
-    groovy localGroovy()
+    compile localGroovy()
 // START SNIPPET gradle-api-dependencies
 }
 // END SNIPPET gradle-api-dependencies
diff --git a/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle b/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle
index a703a01..1c528d2 100644
--- a/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle
+++ b/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+    compile 'org.codehaus.groovy:groovy-all:2.2.0'
     testCompile 'junit:junit:4.11'
 }
 
diff --git a/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle b/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
index 637227e..5184826 100644
--- a/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
+++ b/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
@@ -6,6 +6,6 @@ repositories {
 }
 
 dependencies {
-    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+    compile 'org.codehaus.groovy:groovy-all:2.2.0'
     testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
index 36494f7..e671acf 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:2.0.5'
+    compile 'org.codehaus.groovy:groovy-all:2.2.0'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
index b6a43dd..eb281c8 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 {
-    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+    compile 'org.codehaus.groovy:groovy-all:2.2.0'
     compile project(':groovycDetector')
     testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java
index 1200e2c..df1e06d 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java
@@ -3,9 +3,6 @@ package org.gradle;
 import java.util.Properties;
 import java.io.IOException;
 
-/**
- * @author Hans Dockter
- */
 public class GroovyJavaPerson {
     public String readProperty() throws IOException {
         Properties properties = new Properties();
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy
index 28e1759..01197ab 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy
@@ -1,8 +1,5 @@
 package org.gradle
 
-/**
- * @author Hans Dockter
- */
 class GroovyPerson {
     String readProperty() throws IOException {
         Properties properties = new Properties()
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java
index df8099f..aa47c45 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java
@@ -3,9 +3,6 @@ package org.gradle;
 import java.util.Properties;
 import java.io.IOException;
 
-/**
- * @author Hans Dockter
- */
 public class JavaPerson {
     public String readProperty() throws IOException {
         Properties properties = new Properties();
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 e352ea8..f1f0573 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 versionShouldBe2_0_5() {
-    assertEquals("2.0.5", groovycVersion)
+  void versionShouldBe2_2_0() {
+    assertEquals("2.2.0", groovycVersion)
   }
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/quickstart/build.gradle b/subprojects/docs/src/samples/groovy/quickstart/build.gradle
index 59018f1..efc14c7 100644
--- a/subprojects/docs/src/samples/groovy/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/groovy/quickstart/build.gradle
@@ -9,7 +9,7 @@ repositories {
 }
 
 dependencies {
-    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+    compile 'org.codehaus.groovy:groovy-all:2.2.0'
 // END SNIPPET groovy-dependency
     testCompile 'junit:junit:4.11'
 // START SNIPPET groovy-dependency
diff --git a/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
index b86fdda..ad7ce48 100644
--- a/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
+++ b/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
@@ -15,7 +15,7 @@ class PersonTest {
     }
 
     @Test public void usingCorrectVersionOfGroovy() {
-        assertEquals('2.0.5', GroovySystem.version)
+        assertEquals('2.2.0', GroovySystem.version)
     }
     
     @Test public void testResourcesAreAvailable() {
diff --git a/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle b/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle
index ef79270..c5d852a 100644
--- a/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle
+++ b/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle
@@ -15,13 +15,13 @@ publishing {
 // END SNIPPET customize-descriptor
     repositories {
         ivy {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
         }
     }
 }
 // START SNIPPET generate
-publishing {
-    generateIvyCustomIvyModuleDescriptor {
+model {
+    tasks.generateDescriptorFileForIvyCustomPublication {
         destination = file("$buildDir/generated-ivy.xml")
     }
 }
diff --git a/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle b/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle
index 0d833d9..0d7cfdf 100644
--- a/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle
+++ b/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle
@@ -38,7 +38,7 @@ subprojects {
 // END SNIPPET publish-custom-artifact
         repositories {
             ivy {
-                url "file://${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo
+                url "${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo
             }
         }
 // START SNIPPET publish-custom-artifact
diff --git a/subprojects/docs/src/samples/ivy-publish/multiple-publications/build.gradle b/subprojects/docs/src/samples/ivy-publish/multiple-publications/build.gradle
new file mode 100644
index 0000000..31aae76
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/multiple-publications/build.gradle
@@ -0,0 +1,83 @@
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'ivy-publish'
+
+    repositories {
+        mavenCentral()
+    }
+
+    publishing {
+        repositories {
+            ivy {
+                url "${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo
+            }
+        }
+    }
+}
+
+project(":project1") {
+    dependencies {
+       compile 'junit:junit:4.11'
+    }
+
+
+    // START SNIPPET customize-identity
+    publishing {
+        publications {
+            ivy(IvyPublication) {
+                organisation 'org.gradle.sample'
+                module 'project1-sample'
+                revision '1.1'
+                descriptor.status = 'milestone'
+
+                from components.java
+            }
+        }
+    }
+    // END SNIPPET customize-identity
+}
+
+project(":project2") {
+    // START SNIPPET multiple-publications
+    task apiJar(type: Jar) {
+        baseName "publishing-api"
+        from sourceSets.main.output
+        exclude '**/impl/**'
+    }
+    // END SNIPPET multiple-publications
+
+    dependencies {
+       compile 'commons-collections:commons-collections:3.1', project(':project1')
+    }
+
+    // START SNIPPET multiple-publications
+    publishing {
+        publications {
+            impl(IvyPublication) {
+                organisation 'org.gradle.sample.impl'
+                module 'project2-impl'
+                revision '2.3'
+
+                from components.java
+            }
+            api(IvyPublication) {
+    // END SNIPPET multiple-publications
+                configurations {
+                    it.default {
+                        extend "runtime"
+                    }
+                    runtime {}
+                }
+                artifact(apiJar) {
+                    conf "runtime"
+                }
+
+    // START SNIPPET multiple-publications
+                organisation 'org.gradle.sample'
+                module 'project2-api'
+                revision '2'
+            }
+        }
+    }
+    // END SNIPPET multiple-publications
+}
diff --git a/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project1.ivy.xml b/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project1.ivy.xml
new file mode 100644
index 0000000..f74cff2
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project1.ivy.xml
@@ -0,0 +1,15 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0">
+  <info organisation="org.gradle.sample" module="project1-sample" revision="1.1" status="milestone" publication="«PUBLICATION-TIME-STAMP»"/>
+  <configurations>
+    <conf name="default" visibility="public" extends="runtime"/>
+    <conf name="runtime" visibility="public"/>
+  </configurations>
+  <publications>
+    <artifact name="project1-sample" type="jar" ext="jar" conf="runtime"/>
+  </publications>
+  <dependencies>
+    <dependency org="junit" name="junit" rev="4.11" conf="runtime->default"/>
+  </dependencies>
+</ivy-module>
diff --git a/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project2-api.ivy.xml b/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project2-api.ivy.xml
new file mode 100644
index 0000000..b695d6a
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project2-api.ivy.xml
@@ -0,0 +1,13 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0">
+  <info organisation="org.gradle.sample" module="project2-api" revision="2" status="integration" publication="«PUBLICATION-TIME-STAMP»"/>
+  <configurations>
+    <conf name="default" visibility="public" extends="runtime"/>
+    <conf name="runtime" visibility="public"/>
+  </configurations>
+  <publications>
+    <artifact name="project2-api" type="jar" ext="jar" conf="runtime"/>
+  </publications>
+  <dependencies/>
+</ivy-module>
diff --git a/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project2-impl.ivy.xml b/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project2-impl.ivy.xml
new file mode 100644
index 0000000..30e4e54
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/multiple-publications/output/project2-impl.ivy.xml
@@ -0,0 +1,16 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0">
+  <info organisation="org.gradle.sample.impl" module="project2-impl" revision="2.3" status="integration" publication="«PUBLICATION-TIME-STAMP»"/>
+  <configurations>
+    <conf name="default" visibility="public" extends="runtime"/>
+    <conf name="runtime" visibility="public"/>
+  </configurations>
+  <publications>
+    <artifact name="project2-impl" type="jar" ext="jar" conf="runtime"/>
+  </publications>
+  <dependencies>
+    <dependency org="commons-collections" name="commons-collections" rev="3.1" conf="runtime->default"/>
+    <dependency org="org.gradle.sample" name="project1-sample" rev="1.1" conf="runtime->default"/>
+  </dependencies>
+</ivy-module>
diff --git a/subprojects/docs/src/samples/ivy-publish/multiple-publications/settings.gradle b/subprojects/docs/src/samples/ivy-publish/multiple-publications/settings.gradle
new file mode 100644
index 0000000..611129e
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/multiple-publications/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'ivy-publish-multiple'
+include 'project1', 'project2'
diff --git a/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle b/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle
index 0d62680..e596ec3 100644
--- a/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle
@@ -17,7 +17,7 @@ publishing {
 // START SNIPPET repositories
     repositories {
         ivy {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
         }
     }
 // END SNIPPET repositories
diff --git a/subprojects/docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java b/subprojects/docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java
index 320d999..e09236c 100644
--- a/subprojects/docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java
+++ b/subprojects/docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java
@@ -2,9 +2,6 @@ package org.gradle.webservice;
 
 import junit.framework.TestCase;
 
-/**
- * @author Hans Dockter
- */
 public class TestTestTest extends TestCase {
     public void testClasspath() {
         new TestTest().method();
diff --git a/subprojects/docs/src/samples/java/quickstart/build.gradle b/subprojects/docs/src/samples/java/quickstart/build.gradle
index f1b1d1d..8b5fa71 100644
--- a/subprojects/docs/src/samples/java/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/java/quickstart/build.gradle
@@ -5,7 +5,7 @@ apply plugin: 'java'
 apply plugin: 'eclipse'
 // END SNIPPET use-eclipse-plugin
 
-// START SNIPPET customisation
+// START SNIPPET customization
 sourceCompatibility = 1.5
 version = '1.0'
 jar {
@@ -13,7 +13,7 @@ jar {
         attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
     }
 }
-// END SNIPPET customisation
+// END SNIPPET customization
 
 // START SNIPPET repo
 repositories {
@@ -28,11 +28,11 @@ dependencies {
 }
 // END SNIPPET dependencies
 
-// START SNIPPET task-customisation
+// START SNIPPET task-customization
 test {
     systemProperties 'property': 'value'
 }
-// END SNIPPET task-customisation
+// END SNIPPET task-customization
 
 // START SNIPPET upload
 uploadArchives {
diff --git a/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle b/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle
index d4bc19b..b615a76 100644
--- a/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle
+++ b/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle
@@ -30,7 +30,7 @@ publishing {
 // END SNIPPET publish-custom-artifact
     repositories {
         maven {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
         }
     }
 // START SNIPPET publish-custom-artifact
diff --git a/subprojects/docs/src/samples/maven-publish/multiple-publications/build.gradle b/subprojects/docs/src/samples/maven-publish/multiple-publications/build.gradle
new file mode 100644
index 0000000..8d9c491
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/multiple-publications/build.gradle
@@ -0,0 +1,69 @@
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'maven-publish'
+
+    repositories {
+        mavenCentral()
+    }
+
+    publishing {
+        repositories {
+            maven {
+                url "${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo
+            }
+        }
+    }
+}
+
+project(":project1") {
+    dependencies {
+       compile 'org.slf4j:slf4j-api:1.7.5'
+    }
+
+    // START SNIPPET customize-identity
+    publishing {
+        publications {
+            maven(MavenPublication) {
+                groupId 'org.gradle.sample'
+                artifactId 'project1-sample'
+                version '1.1'
+
+                from components.java
+            }
+        }
+    }
+    // END SNIPPET customize-identity
+}
+
+project(":project2") {
+    dependencies {
+       compile 'commons-collections:commons-collections:3.1', project(':project1')
+    }
+
+    // START SNIPPET multiple-publications
+    task apiJar(type: Jar) {
+        baseName "publishing-api"
+        from sourceSets.main.output
+        exclude '**/impl/**'
+    }
+
+    publishing {
+        publications {
+            impl(MavenPublication) {
+                groupId 'org.gradle.sample.impl'
+                artifactId 'project2-impl'
+                version '2.3'
+
+                from components.java
+            }
+            api(MavenPublication) {
+                groupId 'org.gradle.sample'
+                artifactId 'project2-api'
+                version '2'
+
+                artifact apiJar
+            }
+        }
+    }
+    // END SNIPPET multiple-publications
+}
diff --git a/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project1.pom.xml b/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project1.pom.xml
new file mode 100644
index 0000000..d5e2f42
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project1.pom.xml
@@ -0,0 +1,17 @@
+<!-- Example of the Maven POM this build will produce -->
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.gradle.sample</groupId>
+  <artifactId>project1-sample</artifactId>
+  <version>1.1</version>
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.7.5</version>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+</project>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project2-api.pom.xml b/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project2-api.pom.xml
new file mode 100644
index 0000000..0c39c1b
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project2-api.pom.xml
@@ -0,0 +1,9 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.gradle.sample</groupId>
+  <artifactId>project2-api</artifactId>
+  <version>2</version>
+</project>
diff --git a/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project2-impl.pom.xml b/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project2-impl.pom.xml
new file mode 100644
index 0000000..07d71c8
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/multiple-publications/output/project2-impl.pom.xml
@@ -0,0 +1,23 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.gradle.sample.impl</groupId>
+  <artifactId>project2-impl</artifactId>
+  <version>2.3</version>
+  <dependencies>
+    <dependency>
+      <groupId>commons-collections</groupId>
+      <artifactId>commons-collections</artifactId>
+      <version>3.1</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.gradle.sample</groupId>
+      <artifactId>project1-sample</artifactId>
+      <version>1.1</version>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/subprojects/docs/src/samples/maven-publish/multiple-publications/settings.gradle b/subprojects/docs/src/samples/maven-publish/multiple-publications/settings.gradle
new file mode 100644
index 0000000..efcca68
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/multiple-publications/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'maven-publish-multiple'
+include 'project1', 'project2'
diff --git a/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle b/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle
index b0fc28b..9adc23c 100644
--- a/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle
+++ b/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle
@@ -8,20 +8,20 @@ publishing {
     publications {
         mavenCustom(MavenPublication) {
             pom.withXml {
-                asNode().appendNode('description', 'A demonstration of maven pom customisation')
+                asNode().appendNode('description', 'A demonstration of maven POM customization')
             }
         }
     }
 // END SNIPPET pom-modification
     repositories {
         maven {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
         }
     }
 }
 // START SNIPPET generate
-publishing {
-    generatePomFileForMavenCustomPublication {
+model {
+    tasks.generatePomFileForMavenCustomPublication {
         destination = file("$buildDir/generated-pom.xml")
     }
 }
diff --git a/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle b/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle
index 853a142..770938e 100644
--- a/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle
@@ -17,7 +17,7 @@ publishing {
 // START SNIPPET repositories
     repositories {
         maven {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
         }
     }
 // END SNIPPET repositories
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/compile-1.0.jar b/subprojects/docs/src/samples/maven/pomGeneration/lib/compile-1.0.jar
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/compile-1.0.jar differ
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/providedCompile-1.0.jar b/subprojects/docs/src/samples/maven/pomGeneration/lib/providedCompile-1.0.jar
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/providedCompile-1.0.jar differ
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/providedRuntime-1.0.zip b/subprojects/docs/src/samples/maven/pomGeneration/lib/providedRuntime-1.0.zip
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/providedRuntime-1.0.zip differ
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/providedRuntime-util-1.0.war b/subprojects/docs/src/samples/maven/pomGeneration/lib/providedRuntime-util-1.0.war
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/providedRuntime-util-1.0.war differ
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/runtime-1.0.jar b/subprojects/docs/src/samples/maven/pomGeneration/lib/runtime-1.0.jar
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/runtime-1.0.jar differ
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/testCompile-1.0.jar b/subprojects/docs/src/samples/maven/pomGeneration/lib/testCompile-1.0.jar
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/testCompile-1.0.jar differ
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/lib/testRuntime-1.0.jar b/subprojects/docs/src/samples/maven/pomGeneration/lib/testRuntime-1.0.jar
new file mode 100644
index 0000000..dae8f82
Binary files /dev/null and b/subprojects/docs/src/samples/maven/pomGeneration/lib/testRuntime-1.0.jar differ
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle
index c2bafa7..1222b30 100644
--- a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle
@@ -2,7 +2,7 @@ subprojects {
   apply plugin: "groovy"
 
   dependencies {
-    groovy localGroovy()
+    compile localGroovy()
     compile gradleApi()
   }
 
diff --git a/subprojects/docs/src/samples/native-binaries/assembler/build.gradle b/subprojects/docs/src/samples/native-binaries/assembler/build.gradle
new file mode 100644
index 0000000..fe6803e
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/assembler/build.gradle
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// START SNIPPET apply-plugin
+apply plugin: 'assembler'
+// END SNIPPET apply-plugin
+apply plugin: 'c'
+
+model {
+    platforms {
+        x86 {
+            architecture "i386"
+        }
+    }
+}
+
+sources {
+    i386_masm {
+        asm {
+            source.srcDir "src/main/asm_i386_masm"
+        }
+    }
+    i386_gcc {
+        asm {
+            source.srcDir "src/main/asm_i386_gcc"
+        }
+    }
+}
+
+// START SNIPPET assembler-args
+executables {
+    main {
+        binaries.all {
+            if (toolChain in VisualCpp) {
+// END SNIPPET assembler-args
+                source sources.i386_masm.asm
+// START SNIPPET assembler-args
+                assembler.args "/Zi"
+            } else {
+// END SNIPPET assembler-args
+                source sources.i386_gcc.asm
+// START SNIPPET assembler-args
+                assembler.args "-g"
+            }
+        }
+    }
+}
+// END SNIPPET assembler-args
+
diff --git a/subprojects/docs/src/samples/native-binaries/assembler/src/main/asm_i386_gcc/sum.s b/subprojects/docs/src/samples/native-binaries/assembler/src/main/asm_i386_gcc/sum.s
new file mode 100644
index 0000000..bfd9155
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/assembler/src/main/asm_i386_gcc/sum.s
@@ -0,0 +1,6 @@
+    .text
+    .globl  _sum
+_sum:
+    movl    8(%esp), %eax
+    addl    4(%esp), %eax
+    ret
diff --git a/subprojects/docs/src/samples/native-binaries/assembler/src/main/asm_i386_masm/sum.s b/subprojects/docs/src/samples/native-binaries/assembler/src/main/asm_i386_masm/sum.s
new file mode 100644
index 0000000..b1a1fe1
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/assembler/src/main/asm_i386_masm/sum.s
@@ -0,0 +1,12 @@
+    .386
+    .model    flat
+
+PUBLIC    _sum
+_TEXT     SEGMENT
+_sum    PROC
+    mov    eax, DWORD PTR 4[esp]
+    add    eax, DWORD PTR 8[esp]
+    ret    0
+_sum    ENDP
+_TEXT   ENDS
+END
diff --git a/subprojects/docs/src/samples/native-binaries/assembler/src/main/c/main.c b/subprojects/docs/src/samples/native-binaries/assembler/src/main/c/main.c
new file mode 100644
index 0000000..eb4dda0
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/assembler/src/main/c/main.c
@@ -0,0 +1,8 @@
+#include <stdio.h>
+#include "sum.h"
+
+int main () {
+  int x = sum(5, 7);
+  printf("5 + 7 = %d\n", x);
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/assembler/src/main/headers/sum.h b/subprojects/docs/src/samples/native-binaries/assembler/src/main/headers/sum.h
new file mode 100755
index 0000000..91a60c2
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/assembler/src/main/headers/sum.h
@@ -0,0 +1,6 @@
+// Ensure that function name is consistently mapped to assembler
+#ifdef _MSC_VER
+int sum(int a, int b);
+#else
+extern int sum(int a, int b) asm("_sum");
+#endif
diff --git a/subprojects/docs/src/samples/native-binaries/c/build.gradle b/subprojects/docs/src/samples/native-binaries/c/build.gradle
new file mode 100644
index 0000000..83a0d3a
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/c/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// START SNIPPET apply-plugin
+apply plugin: 'c'
+// END SNIPPET apply-plugin
+
+// START SNIPPET libraries
+libraries {
+    hello {}
+}
+// END SNIPPET libraries
+
+// START SNIPPET executables
+executables {
+    main {}
+}
+// END SNIPPET executables
+
+// START SNIPPET source-library
+sources.main.c.lib libraries.hello
+// END SNIPPET source-library
+
+// START SNIPPET compiler-args
+binaries.all {
+    // Define toolchain-specific compiler and linker options
+    if (toolChain in Gcc) {
+        cCompiler.args "-O2"
+        linker.args "-Xlinker", "-S"
+    }
+    if (toolChain in VisualCpp) {
+        cCompiler.args "/Zi"
+        linker.args "/DEBUG"
+    }
+}
+// END SNIPPET compiler-args
+
+// START SNIPPET all-shared-libraries
+// For any shared library binaries built with Visual C++, define the DLL_EXPORT macro
+binaries.withType(SharedLibraryBinary) {
+    if (toolChain in VisualCpp) {
+        cCompiler.args "/Zi"
+        cCompiler.define "DLL_EXPORT"
+    }
+}
+// END SNIPPET all-shared-libraries
+
+
diff --git a/subprojects/docs/src/samples/native-binaries/c/src/hello/c/hello.c b/subprojects/docs/src/samples/native-binaries/c/src/hello/c/hello.c
new file mode 100755
index 0000000..1d8c48f
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/c/src/hello/c/hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+#include "hello.h"
+
+void LIB_FUNC hello () {
+  printf("Hello world!");
+}
diff --git a/subprojects/docs/src/samples/native-binaries/c/src/hello/headers/hello.h b/subprojects/docs/src/samples/native-binaries/c/src/hello/headers/hello.h
new file mode 100755
index 0000000..b4a1ec2
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/c/src/hello/headers/hello.h
@@ -0,0 +1,8 @@
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
+
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/c/src/main/c/main.c
similarity index 100%
copy from subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
copy to subprojects/docs/src/samples/native-binaries/c/src/main/c/main.c
diff --git a/subprojects/docs/src/samples/native-binaries/cpp-exe/build.gradle b/subprojects/docs/src/samples/native-binaries/cpp-exe/build.gradle
new file mode 100644
index 0000000..6b9a3c3
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp-exe/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: "cpp"
+
+executables {
+    main {
+        binaries.all {
+            // Define a preprocessor macro
+            cppCompiler.define "NDEBUG"
+            // Add some additional compiler arguments
+            if (toolChain in Gcc) {
+                cppCompiler.args "-fno-access-control", "-fconserve-space"
+            }
+        }
+    }
+}
+
+binaries.withType(ExecutableBinary) { binary ->
+    def linkTask = binary.tasks.link
+    def stripTask = task("strip${binary.name.capitalize()}") {
+        dependsOn linkTask
+        doFirst {
+            if (binary.toolChain in Gcc) {
+                ["strip", linkTask.outputFile].execute()
+            }
+        }
+    }
+    binary.builtBy stripTask
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cpp-exe/settings.gradle b/subprojects/docs/src/samples/native-binaries/cpp-exe/settings.gradle
new file mode 100644
index 0000000..7bd15c8
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp-exe/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = "sampleExe"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exe/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/cpp-exe/src/main/cpp/hello.cpp
similarity index 100%
rename from subprojects/docs/src/samples/cpp/exe/src/main/cpp/hello.cpp
rename to subprojects/docs/src/samples/native-binaries/cpp-exe/src/main/cpp/hello.cpp
diff --git a/subprojects/docs/src/samples/native-binaries/cpp-lib/build.gradle b/subprojects/docs/src/samples/native-binaries/cpp-lib/build.gradle
new file mode 100644
index 0000000..41079c8
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp-lib/build.gradle
@@ -0,0 +1,14 @@
+// START SNIPPET use-plugin
+apply plugin: "cpp"
+// END SNIPPET use-plugin
+
+// START SNIPPET args
+libraries {
+    main {
+        binaries.withType(SharedLibraryBinary) {
+            // Define a preprocessor macro that only applies to shared libraries
+            cppCompiler.define "DLL_EXPORT"
+        }
+    }
+}
+// END SNIPPET args
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/cpp-lib/settings.gradle b/subprojects/docs/src/samples/native-binaries/cpp-lib/settings.gradle
new file mode 100644
index 0000000..cd1f30d
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp-lib/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = "sampleLib"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/cpp-lib/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/cpp-lib/src/main/cpp/hello.cpp
new file mode 100644
index 0000000..b849636
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp-lib/src/main/cpp/hello.cpp
@@ -0,0 +1,14 @@
+#include <iostream>
+#ifdef _WIN32
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#endif
+#endif
+#ifndef LIB_FUNC
+#define LIB_FUNC
+#endif
+
+
+void LIB_FUNC hello () {
+  std::cout << "Hello, World!\n";
+}
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h b/subprojects/docs/src/samples/native-binaries/cpp-lib/src/main/headers/hello.h
similarity index 100%
copy from subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h
copy to subprojects/docs/src/samples/native-binaries/cpp-lib/src/main/headers/hello.h
diff --git a/subprojects/docs/src/samples/native-binaries/cpp/build.gradle b/subprojects/docs/src/samples/native-binaries/cpp/build.gradle
new file mode 100644
index 0000000..bd5a4a9
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp/build.gradle
@@ -0,0 +1,51 @@
+// START SNIPPET apply-plugin
+apply plugin: 'cpp'
+// END SNIPPET apply-plugin
+
+// START SNIPPET libraries
+libraries {
+    hello {}
+}
+// END SNIPPET libraries
+
+// START SNIPPET executables
+executables {
+    main {}
+}
+// END SNIPPET executables
+
+// START SNIPPET source-library
+sources {
+    main {
+        cpp {
+            lib libraries.hello
+        }
+    }
+}
+// END SNIPPET source-library
+
+// START SNIPPET all-binaries
+binaries.all {
+    // Define a preprocessor macro for every binary
+    cppCompiler.define "NDEBUG"
+
+    // Define toolchain-specific compiler and linker options
+    if (toolChain in Gcc) {
+        cppCompiler.args "-O2", "-fno-access-control"
+        linker.args "-Xlinker", "-S"
+    }
+    if (toolChain in VisualCpp) {
+        cppCompiler.args "/Zi"
+        linker.args "/DEBUG"
+    }
+}
+// END SNIPPET all-binaries
+
+// For any shared library binaries built with Visual C++, define the DLL_EXPORT macro
+binaries.withType(SharedLibraryBinary) {
+    if (toolChain in VisualCpp) {
+        cppCompiler.define "DLL_EXPORT"
+    }
+}
+
+
diff --git a/subprojects/docs/src/samples/native-binaries/cpp/src/hello/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/cpp/src/hello/cpp/hello.cpp
new file mode 100755
index 0000000..dc7b045
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp/src/hello/cpp/hello.cpp
@@ -0,0 +1,6 @@
+#include <iostream>
+#include "hello.h"
+
+void LIB_FUNC hello () {
+  std::cout << "Hello world!" << std::endl;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cpp/src/hello/headers/hello.h b/subprojects/docs/src/samples/native-binaries/cpp/src/hello/headers/hello.h
new file mode 100755
index 0000000..51b48fd
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cpp/src/hello/headers/hello.h
@@ -0,0 +1,7 @@
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/cpp/src/main/cpp/main.cpp
similarity index 100%
rename from subprojects/docs/src/samples/cpp/exewithlib/exe/src/main/cpp/main.cpp
rename to subprojects/docs/src/samples/native-binaries/cpp/src/main/cpp/main.cpp
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/README.md b/subprojects/docs/src/samples/native-binaries/cunit/README.md
new file mode 100644
index 0000000..bdee2bc
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/README.md
@@ -0,0 +1,7 @@
+## CUnit Sample Limitations
+
+Currently, the Gradle model for Platform does not allow us to differentiate between different c-runtime, ABI or other binary variants.
+This means that it is not possible to differentiate between a prebuilt library binary compatible with VS2010 vs VS2013.
+
+As such, this sample will only work without modification on Windows with Visual Studio 2010. Uncomment the relevant line in the
+build script to enable building with Visual Studio 2013, Cygwin or MinGW.
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/build.gradle b/subprojects/docs/src/samples/native-binaries/cunit/build.gradle
new file mode 100644
index 0000000..9efb6e2
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/build.gradle
@@ -0,0 +1,47 @@
+// START SNIPPET complete-example
+apply plugin: "c"
+apply plugin: "cunit"
+
+model {
+    flavors {
+        passing
+        failing
+    }
+    repositories {
+        libs(PrebuiltLibraries) {
+            cunit {
+                headers.srcDir "lib/cunit/2.1-2/include"
+                binaries.withType(StaticLibraryBinary) {
+                    staticLibraryFile = file("lib/cunit/2.1-2/lib/" + findCUnitLibForPlatform(targetPlatform))
+                }
+            }
+        }
+    }
+}
+
+libraries {
+    operators {}
+}
+// START SNIPPET configure-test-binary
+binaries.withType(TestSuiteExecutableBinary) {
+    lib library: "cunit", linkage: "static"
+
+    if (flavor == flavors.failing) {
+        cCompiler.define "PLUS_BROKEN"
+    }
+}
+// END SNIPPET configure-test-binary
+// END SNIPPET complete-example
+
+def findCUnitLibForPlatform(Platform platform) {
+    if (platform.operatingSystem.windows) {
+        return "vs2010/cunit.lib"
+//        return "vs2013/cunit.lib"
+//        return "cygwin/cunit.lib"
+//        return "mingw/cunit.lib"
+    } else if (platform.operatingSystem.macOsX) {
+        return "osx/libcunit.a"
+    } else {
+        return "linux/libcunit.a"
+    }
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Automated.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Automated.h
new file mode 100644
index 0000000..2acc694
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Automated.h
@@ -0,0 +1,90 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Automated Interface (generates HTML Report Files).
+ *
+ *  Feb 2002      Initial implementation (AK)
+ *
+ *  13-Feb-2002   Single interface to automated_run_tests. (AK)
+ *
+ *  20-Jul-2004   New interface, doxygen comments. (JDS)
+ */
+
+/** @file
+ * Automated testing interface with xml output (user interface).
+ */
+/** @addtogroup Automated
+ * @{
+ */
+                                       
+#ifndef CUNIT_AUTOMATED_H_SEEN
+#define CUNIT_AUTOMATED_H_SEEN
+
+#include "CUnit.h"
+#include "TestDB.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CU_EXPORT void CU_automated_run_tests(void);
+/**< 
+ *  Runs CUnit tests using the automated interface.
+ *  This function sets appropriate callback functions, initializes the 
+ *  test output files, and calls the appropriate functions to list the 
+ *  tests and run them.  If an output file name root has not been 
+ *  specified using CU_set_output_filename(), a generic root will be 
+ *  applied.  It is an error to call this function before the CUnit
+ *  test registry has been initialized (check by assertion).
+ */
+
+CU_EXPORT CU_ErrorCode CU_list_tests_to_file(void);
+/**< 
+ *  Generates an xml file containing a list of all tests in all suites 
+ *  in the active registry.  The output file will be named according to 
+ *  the most recent call to CU_set_output_filename(), or a default if 
+ *  not previously set.
+ *
+ *  @return An error code indicating the error status.
+ */
+
+CU_EXPORT void CU_set_output_filename(const char* szFilenameRoot);
+/**< 
+ *  Sets the root file name for automated test output files.
+ *  The strings "-Listing.xml" and "-Results.xml" are appended to the 
+ *  specified root to generate the filenames.  If szFilenameRoot is 
+ *  empty, the default root ("CUnitAutomated") is used.
+ *
+ *  @param szFilenameRoot String containing root to use for file names.
+ */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+/** Deprecated (version 1). @deprecated Use CU_automated_run_tests(). */
+#define automated_run_tests() CU_automated_run_tests()
+/** Deprecated (version 1). @deprecated Use CU_set_output_filename(). */
+#define set_output_filename(x) CU_set_output_filename((x))
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_AUTOMATED_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Basic.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Basic.h
new file mode 100644
index 0000000..d8a638d
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Basic.h
@@ -0,0 +1,113 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2004-2006  Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Interface for simple test runner.
+ *
+ *  11-Aug-2004   Initial implementation of basic test runner interface. (JDS)
+ */
+
+/** @file
+ * Basic interface with output to stdout.
+ */
+/** @addtogroup Basic
+ * @{
+ */
+
+#ifndef CUNIT_BASIC_H_SEEN
+#define CUNIT_BASIC_H_SEEN
+
+#include "CUnit.h"
+#include "TestDB.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Run modes for the basic interface. */
+typedef enum {
+  CU_BRM_NORMAL = 0,  /**< Normal mode - failures and run summary are printed [default]. */
+  CU_BRM_SILENT,      /**< Silent mode - no output is printed except framework error messages. */
+  CU_BRM_VERBOSE      /**< Verbose mode - maximum output of run details. */
+} CU_BasicRunMode;
+
+CU_EXPORT CU_ErrorCode CU_basic_run_tests(void);
+/**< 
+ *  Runs all registered CUnit tests using the basic interface.
+ *  The default CU_BasicRunMode is used unless it has been
+ *  previously changed using CU_basic_set_mode().  The CUnit test
+ *  registry must have been initialized before calling this function.
+ *
+ *  @return A CU_ErrorCode indicating the framework error condition, including
+ *          CUE_NOREGISTRY - Registry has not been initialized.
+ */
+
+CU_EXPORT CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite);
+/**< 
+ *  Runs all tests for a specific suite in the basic interface.
+ *  If pSuite is NULL, the function returns without taking any
+ *  action. The default CU_BasicRunMode is used unless it has
+ *  been changed using CU_basic_set_mode().
+ *
+ *  @param pSuite The CU_Suite to run.
+ *  @return A CU_ErrorCode indicating the framework error condition, including
+ *          CUE_NOSUITE - pSuite was NULL.
+ */
+
+CU_EXPORT CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest);
+/**<
+ *  Runs a single test in a specific suite in the basic interface.
+ *  If pSuite or pTest is NULL, the function returns without
+ *  taking any action.  The default CU_BasicRunMode is used unless
+ *  it has been changed using CU_basic_set_mode.
+ *
+ *  @param pSuite The CU_Suite holding the CU_Test to run.
+ *  @param pTest  The CU_Test to run.
+ *  @return A CU_ErrorCode indicating the framework error condition, including
+ *          CUE_NOSUITE - pSuite was NULL.
+ *          CUE_NOTEST  - pTest was NULL.
+ */
+
+CU_EXPORT void CU_basic_set_mode(CU_BasicRunMode mode);
+/**< Sets the run mode for the basic interface.
+ *  @param mode The new CU_BasicRunMode for subsequent test
+ *              runs using the basic interface.
+ */
+
+CU_EXPORT CU_BasicRunMode CU_basic_get_mode(void);
+/**< Retrieves the current run mode for the basic interface.
+ *  @return The current CU_BasicRunMode setting for test
+ *              runs using the basic interface.
+ */
+
+CU_EXPORT void CU_basic_show_failures(CU_pFailureRecord pFailure);
+/**<
+ *  Prints a summary of run failures to stdout.
+ *  This is provided for user convenience upon request, and does 
+ *  not take into account the current run mode.  The failures are 
+ *  printed to stdout independent of the most recent run mode.
+ *
+ *  @param pFailure List of CU_pFailureRecord's to output.
+ */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_BASIC_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUError.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUError.h
new file mode 100644
index 0000000..c9943c1
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUError.h
@@ -0,0 +1,199 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains CUnit error codes which can be used externally.
+ *
+ *  Aug 2001      Initial implementation.  (AK)
+ *
+ *  02/Oct/2001   Added proper Eror Codes. (AK)
+ *
+ *  13-Oct-2001   Added Error Codes for Duplicate TestGroup and Test. (AK)
+ *
+ *  03-Aug-2004   Converted error code macros to an enum, doxygen comments, moved
+ *                error handing code here, changed file name from Errno.h, added
+ *                error codes for file open errors, added error action selection. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ */
+
+/** @file
+ *  Error handling functions (user interface).
+ *  CUnit uses a simple (and conventional) error handling strategy.
+ *  Functions that can generate errors set (and usually return) an
+ *  error code to indicate the run status.  The error code can be
+ *  inspected using the CU_get_error() function.  A descriptive
+ *  error message can be retrieved using CU_get_error_msg().
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_CUERROR_H_SEEN
+#define CUNIT_CUERROR_H_SEEN
+
+#include <errno.h>
+
+/*------------------------------------------------------------------------*/
+/** CUnit error codes.
+ *  If codes are added or removed, be sure to make a change to the
+ *  error messages in CUError.c/get_error_desc().
+ *  @see CU_set_error()
+ *  @see CU_get_error()
+ *  @see CU_get_error_msg()
+ */
+typedef enum {
+  /* basic errors */
+  CUE_SUCCESS           = 0,  /**< No error condition. */
+  CUE_NOMEMORY          = 1,  /**< Memory allocation failed. */
+
+  /* Test Registry Level Errors */
+  CUE_NOREGISTRY        = 10,  /**< Test registry not initialized. */
+  CUE_REGISTRY_EXISTS   = 11,  /**< Attempt to CU_set_registry() without CU_cleanup_registry(). */
+
+  /* Test Suite Level Errors */
+  CUE_NOSUITE           = 20,  /**< A required CU_pSuite pointer was NULL. */
+  CUE_NO_SUITENAME      = 21,  /**< Required CU_Suite name not provided. */
+  CUE_SINIT_FAILED      = 22,  /**< Suite initialization failed. */
+  CUE_SCLEAN_FAILED     = 23,  /**< Suite cleanup failed. */
+  CUE_DUP_SUITE         = 24,  /**< Duplicate suite name not allowed. */
+  CUE_SUITE_INACTIVE    = 25,  /**< Test run initiated for an inactive suite. */
+
+  /* Test Case Level Errors */
+  CUE_NOTEST            = 30,  /**< A required CU_pTest or CU_TestFunc pointer was NULL. */
+  CUE_NO_TESTNAME       = 31,  /**< Required CU_Test name not provided. */
+  CUE_DUP_TEST          = 32,  /**< Duplicate test case name not allowed. */
+  CUE_TEST_NOT_IN_SUITE = 33,  /**< Test not registered in specified suite. */
+  CUE_TEST_INACTIVE     = 34,  /**< Test run initiated for an inactive test. */
+
+  /* File handling errors */
+  CUE_FOPEN_FAILED      = 40,  /**< An error occurred opening a file. */
+  CUE_FCLOSE_FAILED     = 41,  /**< An error occurred closing a file. */
+  CUE_BAD_FILENAME      = 42,  /**< A bad filename was requested (NULL, empty, nonexistent, etc.). */
+  CUE_WRITE_ERROR       = 43   /**< An error occurred during a write to a file. */
+} CU_ErrorCode;
+
+/*------------------------------------------------------------------------*/
+/** CUnit error action codes.
+ *  These are used to set the action desired when an error
+ *  condition is detected in the CUnit framework.
+ *  @see CU_set_error_action()
+ *  @see CU_get_error_action()
+ */
+typedef enum CU_ErrorAction {
+  CUEA_IGNORE,    /**< Runs should be continued when an error condition occurs (if possible). */
+  CUEA_FAIL,      /**< Runs should be stopped when an error condition occurs. */
+  CUEA_ABORT      /**< The application should exit() when an error conditions occurs. */
+} CU_ErrorAction;
+
+/* Error handling & reporting functions. */
+
+#include "CUnit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CU_EXPORT CU_ErrorCode   CU_get_error(void);
+/**<
+ *  Retrieves the current CUnit framework error code.
+ *  CUnit implementation functions set the error code to indicate the
+ *  status of the most recent operation.  In general, the CUnit functions
+ *  will clear the code to CUE_SUCCESS, then reset it to a specific error
+ *  code if an exception condition is encountered.  Some functions
+ *  return the code, others leave it to the user to inspect if desired.
+ *
+ *  @return The current error condition code.
+ *  @see CU_get_error_msg()
+ *  @see CU_ErrorCode
+ */
+
+CU_EXPORT const char*    CU_get_error_msg(void);
+/**<
+ *  Retrieves a message corresponding to the current framework error code.
+ *  CUnit implementation functions set the error code to indicate the
+ *  of the most recent operation.  In general, the CUnit functions will
+ *  clear the code to CUE_SUCCESS, then reset it to a specific error
+ *  code if an exception condition is encountered.  This function allows
+ *  the user to retrieve a descriptive error message corresponding to the
+ *  error code set by the last operation.
+ *
+ *  @return A message corresponding to the current error condition.
+ *  @see CU_get_error()
+ *  @see CU_ErrorCode
+ */
+
+CU_EXPORT void           CU_set_error_action(CU_ErrorAction action);
+/**<
+ *  Sets the action to take when a framework error condition occurs.
+ *  This function should be used to specify the action to take
+ *  when an error condition is encountered.  The default action is
+ *  CUEA_IGNORE, which results in errors being ignored and test runs
+ *  being continued (if possible).  A value of CUEA_FAIL causes test
+ *  runs to stop as soon as an error condition occurs, while
+ *  CU_ABORT causes the application to exit on any error.
+ *
+ *  @param action CU_ErrorAction indicating the new error action.
+ *  @see CU_get_error_action()
+ *  @see CU_set_error()
+ *  @see CU_ErrorAction
+ */
+
+CU_EXPORT CU_ErrorAction CU_get_error_action(void);
+/**<
+ *  Retrieves the current framework error action code.
+ *
+ *  @return The current error action code.
+ *  @see CU_set_error_action()
+ *  @see CU_set_error()
+ *  @see CU_ErrorAction
+ */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_CUError(void);
+#endif
+
+/* Internal function - users should not generally call this function */
+CU_EXPORT void CU_set_error(CU_ErrorCode error);
+/**<
+ *  Sets the CUnit framework error code.
+ *  This function is used internally by CUnit implementation functions
+ *  when an error condition occurs within the framework.  It should
+ *  not generally be called by user code.  NOTE that if the current
+ *  error action is CUEA_ABORT, then calling this function will
+ *  result in exit() being called for the current application.
+ *
+ *  @param error CU_ErrorCode indicating the current error condition.
+ *  @see CU_get_error()
+ *  @see CU_get_error_msg()
+ *  @see CU_ErrorCode
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+/** Deprecated (version 1). @deprecated Use CU_get_error_msg(). */
+#define get_error() CU_get_error_msg()
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#endif  /*  CUNIT_CUERROR_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUnit.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUnit.h
new file mode 100644
index 0000000..9592eda
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUnit.h
@@ -0,0 +1,383 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  ASSERT Macro definitions and general CUnit configuration definitions.
+ *
+ *  09/Aug/2001   ASSERT definitions. (AK)
+ *
+ *  12/Mar/2003   New Assert definitions. (AK)
+ *
+ *  27/Jul/2003   Modified ASSERT_XXX Macro definitions. (AK)
+ *
+ *  15-Jul-2004   New interface, changed action on assert failure to not
+ *                return, provided _FATAL versions of assertions to return
+ *                from test function on failure. (JDS)
+ *
+ *  01-Sep-2004   Modified assertions for setjmp/longjmp mechanism of 
+ *                aborting test runs, added CU_FAIL and CU_PASS macros. (JDS)
+ *
+ *  07-May-2005   Added CU_ prefix to remaining CUnit defines (BOOL, TRUE, 
+ *                FALSE, MAX_...).  Added CU_UNREFERENCED_PARAMETER() define. (JDS)
+ */
+
+/** @file
+ * Basic CUnit include file for user and system code.
+ * Defines macros for assertions for use in user test cases.
+ * Basic system macro definitions also appear here.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_CUNIT_H_SEEN
+#define CUNIT_CUNIT_H_SEEN
+
+#include <string.h>
+#include <math.h>
+
+/** CUnit version number. */
+#define CU_VERSION "2.1-2"
+
+/*  Max string lengths for names (includes terminating NULL. */
+/** Maximum length of a test name string. */
+#define CU_MAX_TEST_NAME_LENGTH 256
+/** Maximim length of a suite name string. */
+#define CU_MAX_SUITE_NAME_LENGTH 256
+
+/* Global type Definitions to be used for boolean operators. */
+#ifndef CU_BOOL
+  /** Boolean type for CUnit use. */
+  #define CU_BOOL int
+#endif
+
+#ifndef CU_TRUE
+  /** Boolean TRUE for CUnit use. */
+  #define CU_TRUE 1
+#endif
+
+#ifndef CU_FALSE
+  /** Boolean FALSE for CUnit use. */
+  #define CU_FALSE 0
+#endif
+
+#ifndef CU_UNREFERENCED_PARAMETER
+  /** Consistent approach to referencing unused parameters. */
+  #define CU_UNREFERENCED_PARAMETER(x) (void)x
+#endif
+
+#ifndef CU_MAX
+#  define CU_MAX(a,b) (((a) >= (b)) ? (a) : (b))
+#endif
+
+#ifndef CU_MIN
+#  define CU_MIN(a,b) (((a) >= (b)) ? (b) : (a))
+#endif
+
+#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__)
+#  ifdef CU_DLL
+#    ifdef CU_BUILD_DLL
+#      define CU_EXPORT __declspec(dllexport)
+#    else
+#      define CU_EXPORT __declspec(dllimport)
+#    endif
+#  else
+#    define CU_EXPORT
+#  endif
+#  ifdef _MSC_VER
+#    define snprintf _snprintf
+#  endif
+#else
+#  define CU_EXPORT
+#endif  /* WIN32 */
+
+#include "CUError.h"
+#include "TestDB.h"   /* not needed here - included for user convenience */
+#include "TestRun.h"  /* not needed here - include (after BOOL define) for user convenience */
+
+/** Record a pass condition without performing a logical test. */
+#define CU_PASS(msg) \
+  { CU_assertImplementation(CU_TRUE, __LINE__, ("CU_PASS(" #msg ")"), __FILE__, "", CU_FALSE); }
+
+/** Simple assertion.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_FALSE); }
+
+/** Simple assertion.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_FATAL(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_TRUE); }
+
+/** Simple assertion.
+ *  Reports failure with no other action.
+ */
+#define CU_TEST(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_FALSE); }
+
+/** Simple assertion.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_TEST_FATAL(value) \
+  { CU_assertImplementation((value), __LINE__, #value, __FILE__, "", CU_TRUE); }
+
+/** Record a failure without performing a logical test. */
+#define CU_FAIL(msg) \
+  { CU_assertImplementation(CU_FALSE, __LINE__, ("CU_FAIL(" #msg ")"), __FILE__, "", CU_FALSE); }
+
+/** Record a failure without performing a logical test, and abort test. */
+#define CU_FAIL_FATAL(msg) \
+  { CU_assertImplementation(CU_FALSE, __LINE__, ("CU_FAIL_FATAL(" #msg ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that value is CU_TRUE.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_TRUE(value) \
+  { CU_assertImplementation((value), __LINE__, ("CU_ASSERT_TRUE(" #value ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that value is CU_TRUE.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_TRUE_FATAL(value) \
+  { CU_assertImplementation((value), __LINE__, ("CU_ASSERT_TRUE_FATAL(" #value ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that value is CU_FALSE.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_FALSE(value) \
+  { CU_assertImplementation(!(value), __LINE__, ("CU_ASSERT_FALSE(" #value ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that value is CU_FALSE.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_FALSE_FATAL(value) \
+  { CU_assertImplementation(!(value), __LINE__, ("CU_ASSERT_FALSE_FATAL(" #value ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that actual == expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_EQUAL(actual, expected) \
+  { CU_assertImplementation(((actual) == (expected)), __LINE__, ("CU_ASSERT_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that actual == expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((actual) == (expected)), __LINE__, ("CU_ASSERT_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that actual != expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_NOT_EQUAL(actual, expected) \
+  { CU_assertImplementation(((actual) != (expected)), __LINE__, ("CU_ASSERT_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that actual != expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_NOT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((actual) != (expected)), __LINE__, ("CU_ASSERT_NOT_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointers actual == expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_EQUAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) == (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointers actual == expected.
+ * Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) == (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointers actual != expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_NOT_EQUAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) != (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointers actual != expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_NOT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(((void*)(actual) != (void*)(expected)), __LINE__, ("CU_ASSERT_PTR_NOT_EQUAL_FATAL(" #actual "," #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointer value is NULL.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_NULL(value) \
+  { CU_assertImplementation((NULL == (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NULL(" #value")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointer value is NULL.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_NULL_FATAL(value) \
+  { CU_assertImplementation((NULL == (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NULL_FATAL(" #value")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that pointer value is not NULL.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_PTR_NOT_NULL(value) \
+  { CU_assertImplementation((NULL != (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NOT_NULL(" #value")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that pointer value is not NULL.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_PTR_NOT_NULL_FATAL(value) \
+  { CU_assertImplementation((NULL != (void*)(value)), __LINE__, ("CU_ASSERT_PTR_NOT_NULL_FATAL(" #value")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual == expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_STRING_EQUAL(actual, expected) \
+  { CU_assertImplementation(!(strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_EQUAL(" #actual ","  #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual == expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_STRING_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation(!(strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_EQUAL_FATAL(" #actual ","  #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual != expected.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_STRING_NOT_EQUAL(actual, expected) \
+  { CU_assertImplementation((strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_NOT_EQUAL(" #actual ","  #expected ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual != expected.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_STRING_NOT_EQUAL_FATAL(actual, expected) \
+  { CU_assertImplementation((strcmp((const char*)(actual), (const char*)(expected))), __LINE__, ("CU_ASSERT_STRING_NOT_EQUAL_FATAL(" #actual ","  #expected ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual == expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_NSTRING_EQUAL(actual, expected, count) \
+  { CU_assertImplementation(!(strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual == expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_NSTRING_EQUAL_FATAL(actual, expected, count) \
+  { CU_assertImplementation(!(strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_EQUAL_FATAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that string actual != expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_NSTRING_NOT_EQUAL(actual, expected, count) \
+  { CU_assertImplementation((strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_NOT_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that string actual != expected with length specified.
+ *  The comparison is limited to count characters.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(actual, expected, count) \
+  { CU_assertImplementation((strncmp((const char*)(actual), (const char*)(expected), (size_t)(count))), __LINE__, ("CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(" #actual ","  #expected "," #count ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that double actual == expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion passes.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_DOUBLE_EQUAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) <= fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that double actual == expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion passes.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_DOUBLE_EQUAL_FATAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) <= fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_EQUAL_FATAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_TRUE); }
+
+/** Asserts that double actual != expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion fails.
+ *  Reports failure with no other action.
+ */
+#define CU_ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) > fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_NOT_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_FALSE); }
+
+/** Asserts that double actual != expected within the specified tolerance.
+ *  If actual is within granularity of expected, the assertion fails.
+ *  Reports failure and causes test to abort.
+ */
+#define CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(actual, expected, granularity) \
+  { CU_assertImplementation(((fabs((double)(actual) - (expected)) > fabs((double)(granularity)))), __LINE__, ("CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", CU_TRUE); }
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+
+#ifndef BOOL
+  /** Deprecated (version 2.0-2). @deprecated Use CU_BOOL. */
+  #define BOOL int
+#endif
+
+#ifndef TRUE
+  /** Deprecated (version 2.0-2). @deprecated Use CU_TRUE. */
+  #define TRUE 1
+#endif
+
+#ifndef FALSE
+  /** Deprecated (version 2.0-2). @deprecated Use CU_FALSE. */
+  #define FALSE	0
+#endif
+
+/** Deprecated (version 2.0-2). @deprecated Use CU_MAX_TEST_NAME_LENGTH. */
+#define MAX_TEST_NAME_LENGTH	256
+/** Deprecated (version 2.0-2). @deprecated Use CU_MAX_SUITE_NAME_LENGTH. */
+#define MAX_SUITE_NAME_LENGTH	256
+
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_FATAL. */
+#define ASSERT(value) { if (FALSE == (int)(value)) { CU_assertImplementation((BOOL)value, __LINE__, #value, __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_TRUE_FATAL. */
+#define ASSERT_TRUE(value) { if (FALSE == (value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_TRUE(" #value ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_FALSE_FATAL. */
+#define ASSERT_FALSE(value) { if (FALSE != (value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_FALSE(" #value ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_EQUAL_FATAL. */
+#define ASSERT_EQUAL(actual, expected) { if ((actual) != (expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_NOT_EQUAL_FATAL. */
+#define ASSERT_NOT_EQUAL(actual, expected) { if ((void*)(actual) == (void*)(expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_EQUAL_FATAL. */
+#define ASSERT_PTR_EQUAL(actual, expected) { if ((void*)(actual) != (void*)(expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_NOT_EQUAL_FATAL. */
+#define ASSERT_PTR_NOT_EQUAL(actual, expected) { if ((void*)(actual) == (void*)(expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_NOT_EQUAL(" #actual "," #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_NULL_FATAL. */
+#define ASSERT_PTR_NULL(value)  { if (NULL != (void*)(value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_NULL(" #value")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_PTR_NOT_NULL_FATAL. */
+#define ASSERT_PTR_NOT_NULL(value) { if (NULL == (void*)(value)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_PTR_NOT_NULL(" #value")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_STRING_EQUAL_FATAL. */
+#define ASSERT_STRING_EQUAL(actual, expected) { if (strcmp((const char*)actual, (const char*)expected)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_STRING_EQUAL(" #actual ","  #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_STRING_NOT_EQUAL_FATAL. */
+#define ASSERT_STRING_NOT_EQUAL(actual, expected) { if (!strcmp((const char*)actual, (const char*)expected)) { CU_assertImplementation(TRUE, __LINE__, ("ASSERT_STRING_NOT_EQUAL(" #actual ","  #expected ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_NSTRING_EQUAL_FATAL. */
+#define ASSERT_NSTRING_EQUAL(actual, expected, count) { if (strncmp((const char*)actual, (const char*)expected, (size_t)count)) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_NSTRING_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_NSTRING_NOT_EQUAL_FATAL. */
+#define ASSERT_NSTRING_NOT_EQUAL(actual, expected, count) { if (!strncmp((const char*)actual, (const char*)expected, (size_t)count)) { CU_assertImplementation(TRUE, __LINE__, ("ASSERT_NSTRING_NOT_EQUAL(" #actual ","  #expected "," #count ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_DOUBLE_EQUAL_FATAL. */
+#define ASSERT_DOUBLE_EQUAL(actual, expected, granularity) { if ((fabs((double)actual - expected) > fabs((double)granularity))) { CU_assertImplementation(FALSE, __LINE__, ("ASSERT_DOUBLE_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", FALSE); return; }}
+/** Deprecated (version 1). @deprecated Use CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL. */
+#define ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity) { if ((fabs((double)actual - expected) <= fabs((double)granularity))) { CU_assertImplementation(TRUE, __LINE__, ("ASSERT_DOUBLE_NOT_EQUAL(" #actual ","  #expected "," #granularity ")"), __FILE__, "", FALSE); return; }}
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#endif  /*  CUNIT_CUNIT_H_SEEN  */
+
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUnit_intl.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUnit_intl.h
new file mode 100644
index 0000000..813917b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/CUnit_intl.h
@@ -0,0 +1,62 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2006  Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Internationalization support
+ *
+ *  05-May-2006   Initial implementation.  (JDS)
+ */
+
+/** @file
+ *  Internal CUnit header supporting internationalization of
+ *  CUnit framework & interfaces.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_CUNIT_INTL_H_SEEN
+#define CUNIT_CUNIT_INTL_H_SEEN
+
+/* activate these when source preparation is complete
+#include <libintl.h>
+#ifndef _
+#  define _(String) gettext (String)
+#endif
+#ifndef gettext_noop
+#  define gettext_noop(String) String
+#endif
+#ifndef N_
+#  define N_(String) gettext_noop (String)
+#endif
+*/
+
+/* deactivate these when source preparation is complete */
+#undef _
+#define _(String) (String)
+#undef N_
+#define N_(String) String
+#undef textdomain
+#define textdomain(Domain)
+#undef bindtextdomain
+#define bindtextdomain(Package, Directory)
+
+#endif  /*  CUNIT_CUNIT_INTL_H_SEEN  */
+
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Console.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Console.h
new file mode 100644
index 0000000..f5b3769
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Console.h
@@ -0,0 +1,60 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Interface for console Run tests.
+ *
+ *  Aug 2001      Initial implementation. (AK)
+ *
+ *  09/Aug/2001   Single interface to Console_run_tests. (AK)
+ *
+ *  20-Jul-2004   New interface, doxygen comments. (JDS)
+ */
+
+/** @file
+ * Console interface with interactive output (user interface).
+ */
+/** @addtogroup Console
+ * @{
+ */
+
+#ifndef CUNIT_CONSOLE_H_SEEN
+#define CUNIT_CONSOLE_H_SEEN
+
+#include "CUnit.h"
+#include "TestDB.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CU_EXPORT void CU_console_run_tests(void);
+/**< Run registered CUnit tests using the console interface. */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+/** Deprecated (version 1). @deprecated Use CU_console_run_tests(). */
+#define console_run_tests() CU_console_run_tests()
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_CONSOLE_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/MyMem.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/MyMem.h
new file mode 100644
index 0000000..88a7e62
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/MyMem.h
@@ -0,0 +1,104 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Memory Related Defines to use internal routines to detect Memory Leak
+ *  in Debug Versions
+ *
+ *  18/Jun/2002   Memory Debug Functions. (AK)
+ *
+ *  17-Jul-2004   New interface for global function names. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ */
+
+/** @file
+ *  Memory management functions (user interface).
+ *  Two versions of memory allocation/deallocation are available.
+ *  If compiled with MEMTRACE defined, CUnit keeps track of all
+ *  system allocations & deallocations.  The memory record can
+ *  then be reported using CU_CREATE_MEMORY_REPORT.  Otherwise,
+ *  standard system memory allocation is used without tracing.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_MYMEM_H_SEEN
+#define CUNIT_MYMEM_H_SEEN
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef MEMTRACE
+  void* CU_calloc(size_t nmemb, size_t size, unsigned int uiLine, const char* szFileName);
+  void* CU_malloc(size_t size, unsigned int uiLine, const char* szFileName);
+  void  CU_free(void *ptr, unsigned int uiLine, const char* szFileName);
+  void* CU_realloc(void *ptr, size_t size, unsigned int uiLine, const char* szFileName);
+  CU_EXPORT void CU_dump_memory_usage(const char*);
+
+  /** c-allocate with memory tracking. */
+  #define CU_CALLOC(x, y)         CU_calloc((x), (y), __LINE__, __FILE__)
+  /** m-allocate with memory tracking. */
+  #define CU_MALLOC(x)            CU_malloc((x), __LINE__, __FILE__)
+  /** Free with memory tracking. */
+  #define CU_FREE(x)              CU_free((x), __LINE__, __FILE__)
+  /** Reallocate with memory tracking. */
+  #define CU_REALLOC(x, y)        CU_realloc((x), (y), __LINE__, __FILE__)
+  /** Generate report on tracked memory. */
+  #define CU_CREATE_MEMORY_REPORT(x) CU_dump_memory_usage((x))
+  /** Generate report on tracked memory (old macro). */
+  #define CU_DUMP_MEMORY_USAGE(x) CU_dump_memory_usage((x))
+#else   /* MEMTRACE */
+  /** Standard calloc() if MEMTRACE not defined. */
+  #define CU_CALLOC(x, y)         calloc((x), (y))
+  /** Standard malloc() if MEMTRACE not defined. */
+  #define CU_MALLOC(x)            malloc((x))
+  /** Standard free() if MEMTRACE not defined. */
+  #define CU_FREE(x)              free((x))
+  /** Standard realloc() if MEMTRACE not defined. */
+  #define CU_REALLOC(x, y)        realloc((x), (y))
+  /** No-op if MEMTRACE not defined. */
+  #define CU_CREATE_MEMORY_REPORT(x)
+  /** No-op if MEMTRACE not defined. */
+  #define CU_DUMP_MEMORY_USAGE(x)
+#endif  /* MEMTRACE */
+
+#ifdef CUNIT_BUILD_TESTS
+/** Disable memory allocation for testing purposes. */
+void test_cunit_deactivate_malloc(void);
+/** Enable memory allocation for testing purposes. */
+void test_cunit_activate_malloc(void);
+/** Retrieve number of memory events for a given pointer */
+unsigned int test_cunit_get_n_memevents(void* pLocation);
+/** Retrieve number of allocations for a given pointer */
+unsigned int test_cunit_get_n_allocations(void* pLocation);
+/** Retrieve number of deallocations for a given pointer */
+unsigned int test_cunit_get_n_deallocations(void* pLocation);
+
+void test_cunit_MyMem(void);
+#endif  /* CUNIT_BUILD_TESTS */
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_MYMEM_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/TestDB.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/TestDB.h
new file mode 100644
index 0000000..ec07f92
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/TestDB.h
@@ -0,0 +1,914 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains all the Type Definitions and functions declarations
+ *  for the CUnit test database maintenance.
+ *
+ *  Aug 2001      Initial implementation. (AK)
+ *
+ *  09/Aug/2001   Added Preprocessor conditionals for the file. (AK)
+ *
+ *  24/aug/2001   Made the linked list from SLL to DLL(doubly linked list). (AK)
+ *
+ *  31-Aug-2004   Restructured to eliminate global variables error_number, 
+ *                g_pTestRegistry; new interface, support for deprecated 
+ *                version 1 interface, moved error handling code to 
+ *                CUError.[ch], moved test run counts and _TestResult out 
+ *                of TestRegistry to TestRun.h. (JDS)
+ *
+ *  01-Sep-2004   Added jmp_buf to CU_Test. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ *
+ *  15-Apr-2006   Removed constraint that suites/tests be uniquely named.
+ *                Added ability to turn individual tests/suites on or off.
+ *                Moved doxygen comments for public API here to header. (JDS)
+ */
+
+/** @file
+ *  Management functions for tests, suites, and the test registry.
+ *  Unit testing in CUnit follows the common structure of unit
+ *  tests aggregated in suites, which are themselves aggregated
+ *  in a test registry.  This module provides functions and
+ *  typedef's to support the creation, registration, and manipulation
+ *  of test cases, suites, and the registry.
+ */
+/** @addtogroup Framework
+ *  @{
+ */
+
+#ifndef CUNIT_TESTDB_H_SEEN
+#define CUNIT_TESTDB_H_SEEN
+
+#include <setjmp.h>   /* jmp_buf */
+
+#include "CUnit.h"
+#include "CUError.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*=================================================================
+ *  Typedefs and Data Structures
+ *=================================================================*/
+
+typedef int  (*CU_InitializeFunc)(void);  /**< Signature for suite initialization function. */
+typedef int  (*CU_CleanupFunc)(void);     /**< Signature for suite cleanup function. */
+typedef void (*CU_TestFunc)(void);        /**< Signature for a testing function in a test case. */
+
+/*-----------------------------------------------------------------
+ * CU_Test, CU_pTest
+ *-----------------------------------------------------------------*/
+/** CUnit test case data type.
+ *  CU_Test is a double linked list of unit tests.  Each test has
+ *  a name, a callable test function, and a flag for whether the 
+ *  test is active and thus executed during a  test run.  A test 
+ *  also holds links to the next and previous tests in the list, 
+ *  as well as a jmp_buf reference for use in implementing fatal 
+ *  assertions.<br /><br />
+ *
+ *  Generally, the linked list includes tests which are associated 
+ *  with each other in a CU_Suite.  As a result, tests are run in 
+ *  the order in which they are added to a suite (see CU_add_test()).
+ *  <br /><br />
+ *
+ *  It is recommended that the name of each CU_Test in a suite have
+ *  a unique name.  Otherwise, only the first-registered test having 
+ *  a given name will be accessible by that name.  There are no 
+ *  restrictions on the test function.  This means that the same 
+ *  function could, in principle, be called more than once from 
+ *  different tests.
+ *
+ *  @see CU_Suite
+ *  @see CU_TestRegistry
+ */
+typedef struct CU_Test
+{
+  char*           pName;      /**< Test name. */
+  CU_BOOL         fActive;    /**< Flag for whether test is executed during a run. */
+  CU_TestFunc     pTestFunc;  /**< Pointer to the test function. */
+  jmp_buf*        pJumpBuf;   /**< Jump buffer for setjmp/longjmp test abort mechanism. */
+
+  struct CU_Test* pNext;      /**< Pointer to the next test in linked list. */
+  struct CU_Test* pPrev;      /**< Pointer to the previous test in linked list. */
+
+} CU_Test;
+typedef CU_Test* CU_pTest;    /**< Pointer to a CUnit test case. */
+
+/*-----------------------------------------------------------------
+ *  CU_Suite, CU_pSuite
+ *-----------------------------------------------------------------*/
+/** CUnit suite data type.
+ *  CU_Suite is a linked list of CU_Test containers.  Each suite has 
+ *  a name, a count of registered unit tests, and a flag for whether 
+ *  the suite is active during test runs. It also holds pointers to 
+ *  optional initialization and cleanup functions.  If non-NULL, these 
+ *  are called before and after running the suite's tests, respectively.
+ *  In addition, the suite holds a pointer to the head of the linked 
+ *  list of associated CU_Test objects.  Finally, pointers to the next 
+ *  and previous suites in the linked list are maintained.<br /><br />
+ *
+ *  Generally, the linked list includes suites which are associated with 
+ *  each other in a CU_TestRegistry.  As a result, suites are run in the 
+ *  order in which they are registered (see CU_add_suite()).<br /><br />
+ *
+ *  It is recommended that name of each CU_Suite in a test registry have 
+ *  a unique name.  Otherwise, only the first-registered suite having a 
+ *  given name will be accessible by name.  There are no restrictions on 
+ *  the contained tests.  This means that the same CU_Test could, in 
+ *  principle, be run more than once fron different suites.
+ *
+ *  @see CU_Test
+ *  @see CU_TestRegistry
+ */
+typedef struct CU_Suite
+{
+  char*             pName;            /**< Suite name. */
+  CU_BOOL           fActive;          /**< Flag for whether suite is executed during a run. */
+  CU_pTest          pTest;            /**< Pointer to the 1st test in the suite. */
+  CU_InitializeFunc pInitializeFunc;  /**< Pointer to the suite initialization function. */
+  CU_CleanupFunc    pCleanupFunc;     /**< Pointer to the suite cleanup function. */
+
+  unsigned int      uiNumberOfTests;  /**< Number of tests in the suite. */
+  struct CU_Suite*  pNext;            /**< Pointer to the next suite in linked list. */
+  struct CU_Suite*  pPrev;            /**< Pointer to the previous suite in linked list. */
+
+} CU_Suite;
+typedef CU_Suite* CU_pSuite;          /**< Pointer to a CUnit suite. */
+
+/*-----------------------------------------------------------------
+ *  CU_TestRegistry, CU_pTestRegistry
+ *-----------------------------------------------------------------*/
+/** CUnit test registry data type.
+ *  CU_TestRegisty is the repository for suites containing unit tests.  
+ *  The test registry maintains a count of the number of CU_Suite 
+ *  objects contained in the registry, as well as a count of the total 
+ *  number of CU_Test objects associated with those suites.  It also 
+ *  holds a pointer to the head of the linked list of CU_Suite objects.
+ *  <br /><br />
+ *
+ *  With this structure, the user will normally add suites implictly to 
+ *  the internal test registry using CU_add_suite(), and then add tests 
+ *  to each suite using CU_add_test().  Test runs are then initiated 
+ *  using one of the appropriate functions in TestRun.c via one of the 
+ *  user interfaces.<br /><br />
+ *
+ *  Automatic creation and destruction of the internal registry and its 
+ *  objects is available using CU_initialize_registry() and 
+ *  CU_cleanup_registry(), respectively.  For internal and testing 
+ *  purposes, the internal registry can be retrieved and assigned.  
+ *  Functions are also provided for creating and destroying independent 
+ *  registries.<br /><br />
+ *
+ *  Note that earlier versions of CUnit also contained a pointer to a 
+ *  linked list of CU_FailureRecord objects (termed _TestResults).  
+ *  This has been removed from theregistry and relocated to TestRun.c.
+ *
+ *  @see CU_Test
+ *  @see CU_Suite
+ *  @see CU_initialize_registry()
+ *  @see CU_cleanup_registry()
+ *  @see CU_get_registry()
+ *  @see CU_set_registry()
+ *  @see CU_create_new_registry()
+ *  @see CU_destroy_existing_registry()
+ */
+typedef struct CU_TestRegistry
+{
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+  /** Union to support v1.1-1 member name. */
+  union {
+    unsigned int uiNumberOfSuites;  /**< Number of suites in the test registry. */
+    unsigned int uiNumberOfGroups;  /**< Deprecated (version 1). @deprecated Use uiNumberOfSuites. */
+  };
+  unsigned int uiNumberOfTests;     /**< Number of tests in the test registry. */
+  /** Union to support v1.1-1 member name. */
+  union {
+    CU_pSuite    pSuite;            /**< Pointer to the 1st suite in the test registry. */
+    CU_pSuite    pGroup;            /**< Deprecated (version 1). @deprecated Use pSuite. */
+  };
+#else
+  unsigned int uiNumberOfSuites;    /**< Number of registered suites in the registry. */
+  unsigned int uiNumberOfTests;     /**< Total number of registered tests in the registry. */
+  CU_pSuite    pSuite;              /**< Pointer to the 1st suite in the test registry. */
+#endif
+} CU_TestRegistry;
+typedef CU_TestRegistry* CU_pTestRegistry;  /**< Pointer to a CUnit test registry. */
+
+/*=================================================================
+ *  Public interface functions
+ *=================================================================*/
+
+CU_EXPORT 
+CU_ErrorCode CU_initialize_registry(void);
+/**<
+ *  Initializes the framework test registry.
+ *  Any existing registry is freed, including all stored suites 
+ *  and associated tests.  It is not necessary to explicitly call
+ *  CU_cleanup_registry() before reinitializing the framework.
+ *  The most recent stored test results are also cleared.<br /><br />
+ *
+ *  <B>This function must not be called during a test run (checked
+ *  by assertion)</B>
+ *
+ *  @return  CUE_NOMEMORY if memory for the new registry cannot 
+ *           be allocated, CUE_SUCCESS otherwise.
+ *  @see CU_cleanup_registry
+ *  @see CU_get_registry
+ *  @see CU_set_registry
+ *  @see CU_registry_initialized
+ */
+
+CU_EXPORT 
+void CU_cleanup_registry(void);
+/**<
+ *  Clears the test registry.
+ *  The active test registry is freed, including all stored suites 
+ *  and associated tests.  The most recent stored test results are 
+ *  also cleared.  After calling this function, CUnit suites cannot 
+ *  be added until CU_initialize_registry() or CU_set_registry() is 
+ *  called.  Further, any pointers to suites or test cases held by 
+ *  the user will be invalidated by calling this function.<br /><br />
+ *
+ *  This function may be called multiple times without generating 
+ *  an error condition.  However, <B>this function must not be 
+ *  called during a test run (checked by assertion)</B></P>.
+ *
+ *  @see CU_initialize_registry
+ *  @see CU_get_registry
+ *  @see CU_set_registry
+ */
+
+CU_EXPORT CU_BOOL CU_registry_initialized(void);
+/**<
+ *  Checks whether the test registry has been initialized.
+ *
+ *  @return  CU_TRUE if the registry has been initialized,
+ *           CU_FALSE otherwise.
+ *  @see CU_initialize_registry
+ *  @see CU_cleanup_registry
+ */
+
+CU_EXPORT 
+CU_pSuite CU_add_suite(const char *strName, 
+                       CU_InitializeFunc pInit, 
+                       CU_CleanupFunc pClean);
+/**<
+ *  Creates a new test suite and adds it to the test registry.
+ *  This function creates a new test suite having the specified
+ *  name and initialization/cleanup functions and adds it to the
+ *  test registry.  The new suite will be active and able to be
+ *  executed during a test run.  The test registry must be
+ *  initialized before calling this function (checked by assertion).
+ *  pInit and pClean may be NULL, in which case no corresponding
+ *  initialization of cleanup function will be called when the suite
+ *  is run.  strName may be empty ("") but may not be NULL.<br /><br />
+ *
+ *  The return value is a pointer to the newly-created suite, or 
+ *  NULL if there was a problem with the suite creation or addition.  
+ *  An error code is also set for the framework. Note that if the 
+ *  name specified for the new suite is a duplicate, the suite will 
+ *  be created and added but the error code will be set to CUE_DUP_SUITE.  
+ *  The duplicate suite will not be accessible by name.<br /><br />
+ *
+ *  NOTE - the CU_pSuite pointer returned should NOT BE FREED BY
+ *  THE USER.  The suite is freed by the CUnit system when
+ *  CU_cleanup_registry() is called.  <b>This function must not 
+ *  be called during a test run (checked by assertion)</b>. <br /><br />
+ *
+ *  CU_add_suite() sets the following error codes:
+ *  - CUE_SUCCESS if no errors occurred.
+ *  - CUE_NOREGISTRY if the registry has not been initialized.
+ *  - CUE_NO_SUITENAME if strName is NULL.
+ *  - CUE_DUP_SUITE if a suite having strName is already registered.
+ *  - CUE_NOMEMORY if a memory allocation failed.
+ *
+ *  @param strName Name for the new test suite (non-NULL).
+ *  @param pInit   Initialization function to call before running suite.
+ *  @param pClean  Cleanup function to call after running suite.
+ *  @return A pointer to the newly-created suite (NULL if creation failed)
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_active(CU_pSuite pSuite, CU_BOOL fNewActive);
+/**<
+ *  Activates or deactivates a suite.
+ *  Only activated suites can be executed during a test run.  
+ *  By default a suite is active upon creation, but can be deactivated
+ *  by passing it along with CU_FALSE to this function.  The suite
+ *  can be reactivated by passing it along with CU_TRUE.  The current
+ *  value of the active flag is available as pSuite->fActive.  If pSuite 
+ *  is NULL then error code CUE_NOSUITE is returned.
+ *
+ *  @param pSuite     Pointer to the suite to modify (non-NULL).
+ *  @param fNewActive If CU_TRUE then the suite will be activated; 
+ *                    if CU_FALSE it will be deactivated.
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, CUE_SUCCESS if all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_name(CU_pSuite pSuite, const char *strNewName);
+/**<
+ *  Modifies the name of a suite.
+ *  This function allows the name associated with a suite to
+ *  be changed.  It is not recommended that a suite name be changed,
+ *  nor should it be necessary under most circumstances.  However,
+ *  this function is provided for those clients who need to change
+ *  a suite's name.  The current value of the suite's name 
+ *  is available as pSuite->pName.  CUE_SUCCESS is returned if the
+ *  function succeeds in changing the name.  CUE_NOSUITE is returned if
+ *  pSuite is NULL, and CUE_NO_SUITENAME if strNewName is NULL.
+ *
+ *  @param pSuite     Pointer to the suite to modify (non-NULL).
+ *  @param strNewName Pointer to string containing new suite name (non-NULL).
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, CUE_NO_SUITENAME if
+ *          strNewName is NULL, and CUE_SUCCESS if all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_initfunc(CU_pSuite pSuite, CU_InitializeFunc pNewInit);
+/**<
+ *  Modifies the initialization function of a suite.
+ *  This function allows the initialization function associated with 
+ *  a suite to be changed.  This is neither recommended nor should it 
+ *  be necessary under most circumstances.  However, this function is 
+ *  provided for those clients who need to change the function.  The 
+ *  current value of the function is available as pSuite->pInitializeFunc.
+ *  CUE_SUCCESS is returned if the function succeeds, or CUE_NOSUITE if
+ *  pSuite is NULL.  pNewInit may be NULL, which indicates the suite has
+ *  no initialization function.
+ *
+ *  @param pSuite   Pointer to the suite to modify (non-NULL).
+ *  @param pNewInit Pointer to function to use to initialize suite.
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, and CUE_SUCCESS if 
+ *          all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_suite_cleanupfunc(CU_pSuite pSuite, CU_CleanupFunc pNewClean);
+/**<
+ *  Modifies the cleanup function of a suite.
+ *  This function allows the cleanup function associated with a suite to 
+ *  be changed.  This is neither recommended nor should it be necessary 
+ *  under most circumstances.  However, this function is provided for those 
+ *  clients who need to change the function.  The current value of the 
+ *  function is available as pSuite->pCleanupFunc.  CUE_SUCCESS is returned 
+ *  if the function succeeds, or CUE_NOSUITE if pSuite is NULL.  pNewClean 
+ *  may be NULL, which indicates the suite has no cleanup function.
+ *
+ *  @param pSuite    Pointer to the suite to modify (non-NULL).
+ *  @param pNewClean Pointer to function to use to clean up suite.
+ *  @return Returns CUE_NOSUITE if pSuite is NULL, and CUE_SUCCESS if 
+ *          all is well.
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite(const char* strName);
+/**<
+ *  Retrieves the suite having the specified name.  
+ *  Searches the active test registry and returns a pointer to the 1st 
+ *  suite found.  NULL is returned if no suite having the specified name 
+ *  is found.  In addition, the framework error state is set to CUE_NOREGISTRY 
+ *  if the registry is not initialized or to CUE_NO_SUITENAME if strName is NULL.  
+ *  If the return value is NULL and framework error state is CUE_SUCCESS, then 
+ *  the search simply failed to find the specified name.  
+ *  Use CU_get_suite_at_pos() to retrieve a suite by position rather than name.
+ *
+ *  @param strName The name of the suite to search for (non-NULL).
+ *  @return Returns a pointer to the suite, or NULL if not found or an error occurred.
+ *  @see CU_get_suite_at_pos()
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite_at_pos(unsigned int pos);
+/**<
+ *  Retrieves the suite at the specified position.
+ *  Iterates the active test registry and returns a pointer to the suite at 
+ *  position pos.  pos is a 1-based index having valid values 
+ *  [1 .. CU_get_registry()->uiNumberOfSuites] and corresponds to the order in
+ *  which suites were registered.  If pos is invalid or an error occurs, 0 is 
+ *  returned.  In addition, the framework error state is set to CUE_NOREGISTRY if 
+ *  the registry is not initialized, or CUE_SUCCESS if pos was invalid.  Use 
+ *  CU_get_suite() to retrieve a suite by name rather than position.
+ *
+ *  @param pos The 1-based position of the suite to fetch.
+ *  @return Returns a pointer to the suite, or 0 if not found or an error occurred.
+ *  @see CU_get_suite()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_suite_pos(CU_pSuite pSuite);
+/**<
+ *  Looks up the position of the specified suite.
+ *  The position is a 1-based index of suites in the active test registry which 
+ *  corresponds to the order in which suites were registered.  If pSuite is not 
+ *  found or an error occurs, 0 is returned.  In addition, the framework error 
+ *  state is set to CUE_NOREGISTRY if the registry is not initialized, or 
+ *  CUE_NOSUITE if pSuite is NULL.  The returned position may be used to retrieve 
+ *  the suite using CU_get_suite_by_pos().
+ *
+ *  @param pSuite Pointer to the suite to find (non-NULL).
+ *  @return Returns the 1-based position of pSuite in the registry, or NULL if
+ *         not found or an error occurred.
+ *  @see CU_get_suite_by_pos()
+ *  @see CU_get_suite_pos_by_name()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_suite_pos_by_name(const char* strName);
+/**<
+ *  Looks up the position of the suite having the specified name.
+ *  The position is a 1-based index of suites in the active test registry which 
+ *  corresponds to the order in which suites were registered.  If no suite has the
+ *  specified name or an error occurs, 0 is returned.  In addition, the framework error 
+ *  state is set to CUE_NOREGISTRY if the registry is not initialized, or 
+ *  CUE_NO_SUITENAME if strName is NULL.  The search ends at the 1st suite found having 
+ *  name strName.  The returned position may be used to retrieve the suite using 
+ *  CU_get_suite_by_pos().
+ *
+ *  @param strName Name of the suite to find (non-NULL).
+ *  @return Returns the 1-based position of pSuite in the registry, or NULL if
+ *          not found or an error occurred.
+ *  @see CU_get_suite_by_pos()
+ *  @see CU_get_suite_pos_by_name()
+ */
+
+CU_EXPORT 
+CU_pTest CU_add_test(CU_pSuite pSuite, const char* strName, CU_TestFunc pTestFunc);
+/**<
+ *  This function creates a new test having the specified name 
+ *  and function, and adds it to the specified suite.  The new test 
+ *  is active and able to be executed during a test run.  At present, 
+ *  there is no mechanism for creating a test case independent of a 
+ *  suite.  Neither pSuite, strName, nor pTestFunc may be NULL.
+ *
+ *  The return value is a pointer to the newly-created test, or 
+ *  NULL if there was a problem with the test creation or addition.  
+ *  An error code is also set for the framework. Note that if the 
+ *  name specified for the new test is a duplicate within pSuite, 
+ *  the test will be created and added but the error code will be 
+ *  set to CUE_DUP_TEST.  The duplicate test will not be accessible 
+ *  by name.<br /><br />
+ *
+ *  NOTE - the CU_pTest pointer returned should NOT BE FREED BY
+ *  THE USER.  The test is freed by the CUnit system when
+ *  CU_cleanup_registry() is called.  <b>This function must not 
+ *  be called during a test run (checked by assertion)</b>. <br /><br />
+
+ *  CU_add_test() sets the following error codes:
+ *  - CUE_SUCCESS if no errors occurred.
+ *  - CUE_NOREGISTRY if the registry has not been initialized.
+ *  - CUE_NOSUITE if pSuite is NULL.
+ *  - CUE_NO_TESTNAME if strName is NULL.
+ *  - CUE_NOTEST if pTestFunc is NULL.
+ *  - CUE_DUP_TEST if a test having strName is already registered to pSuite.
+ *  - CUE_NOMEMORY if a memory allocation failed.<br /><br />
+ *
+ *  @param pSuite  Test suite to which to add new test (non-NULL).
+ *  @param strName Name for the new test case (non-NULL).
+ *  @param pTest   Function to call when running the test (non-NULL).
+ *  @return A pointer to the newly-created test (NULL if creation failed)
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_test_active(CU_pTest pTest, CU_BOOL fNewActive);
+/**<
+ *  Activates or deactivates a specific test.
+ *  Only activated tests can be executed during a test run.  
+ *  By default a test is active upon creation, but can be deactvated
+ *  by passing it along with CU_FALSE to this function.  The test
+ *  can be reactivated by passing it along with CU_TRUE.  
+ *  The current value of the active flag is available as pTest->fActive.
+ *  If pTest is NULL then error code CUE_NOTEST is returned.  Otherwise
+ *  CUE_SUCCESS is returned.
+ *
+ *  @param pTest      Pointer to the test to modify (non-NULL).
+ *  @param fNewActive If CU_TRUE then test will be activated; 
+ *                    if CU_FALSE it will be deactivated.
+ *  @return Returns CUE_NOTEST if pTest is NULL, CUE_SUCCESS if all is well.
+*/
+
+CU_EXPORT 
+CU_ErrorCode CU_set_test_name(CU_pTest pTest, const char *strNewName);
+/**<
+ *  Modifies the name of a test.
+ *  This function allows the name associated with a test to
+ *  be changed.  It is not recommended that a test name be changed,
+ *  nor should it be necessary under most circumstances.  However,
+ *  this function is provided for those clients who need to change
+ *  a test's name.  The current value of the test's name is
+ *  available as pTest->pName.  CUE_SUCCESS is returned if the
+ *  function succeeds in changing the name.  CUE_NOTEST is returned if
+ *  pTest is NULL, and CUE_NO_TESTNAME if strNewName is NULL.
+ *
+ *  @param pTest      Pointer to the test to modify (non-NULL).
+ *  @param strNewName Pointer to string containing new test name (non-NULL).
+ *  @return Returns CUE_NOTEST if pTest is NULL, CUE_NO_TESTNAME if
+ *          strNewName is NULL, and CUE_SUCCESS if all is well.
+ */
+
+CU_EXPORT 
+CU_ErrorCode CU_set_test_func(CU_pTest pTest, CU_TestFunc pNewFunc);
+/**<
+ *  Modifies the test function of a test.
+ *  This function allows the test function associated with a test to be 
+ *  changed.  This is neither recommended nor should it be necessary under 
+ *  most circumstances.  However, this function is provided for those 
+ *  clients who need to change the test function.  The current value of 
+ *  the test function is available as pTest->pTestFunc.  CUE_SUCCESS is 
+ *  returned if the function succeeds, or CUE_NOTEST if either pTest or
+ *  pNewFunc is NULL.
+ *
+ *  @param pTest    Pointer to the test to modify (non-NULL).
+ *  @param pNewFunc Pointer to function to use for test function (non-NULL).
+ *  @return Returns CUE_NOTEST if pTest or pNewFunc is NULL, and CUE_SUCCESS 
+ *          if all is well.
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test(CU_pSuite pSuite, const char *strName);
+/**<
+ *  Retrieves the test having the specified name.  
+ *  Searches pSuite and returns a pointer to the 1st test found named strName.  
+ *  NULL is returned if no test having the specified name is found in pSuite.  
+ *  In addition, the framework error state is set as follows:
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *    - CUE_NO_TESTNAME if strName is NULL.
+ *  
+ *  If the return value is NULL and framework error state is CUE_SUCCESS, then 
+ *  the search simply failed to find the specified name.  Use CU_get_test_at_pos() 
+ *  to retrieve a test by position rather than name.
+ *
+ *  @param pSuite  Pointer to the suite to search (non-NULL).
+ *  @param strName The name of the test to search for (non-NULL).
+ *  @return Returns a pointer to the test, or NULL if not found or an error occurred.
+ *  @see CU_get_test_at_pos()
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test_at_pos(CU_pSuite pSuite, unsigned int pos);
+/**<
+ *  Retrieves the test at the specified position in pSuite.
+ *  Iterates the tests registered in pSuite and returns a pointer to the 
+ *  test at position pos.  pos is a 1-based index having valid values 
+ *  [1 .. pSuite->uiNumberOfTests] and corresponds to the order in
+ *  which tests were added to pSuite.  If pos is invalid or an error occurs, 0 is 
+ *  returned.  In addition, the framework error state is set as follows: 
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *  Use CU_get_test() to retrieve a test by name rather than position.
+ *
+ *  @param pSuite  Pointer to the suite to search (non-NULL).
+ *  @param pos     The 1-based position of the test to fetch.
+ *  @return Returns a pointer to the test, or 0 if not found or an error occurred.
+ *  @see CU_get_test()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_test_pos(CU_pSuite pSuite, CU_pTest pTest);
+/**<
+ *  Looks up the position of the specified test in pSuite.
+ *  The position is a 1-based index of tests in pSuite which corresponds to the 
+ *  order in which tests were added.  If pTest is not found or an error occurs, 
+ *  0 is returned.  In addition, the framework error state is set as follows:  
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *    - CUE_NOTEST if pTest is NULL
+ *
+ *  The returned position may be used to retrieve the test using CU_get_test_by_pos().
+ *
+ *  @param pSuite Pointer to the suite to search (non-NULL).
+ *  @param pTest  Pointer to the test to find (non-NULL).
+ *  @return Returns the 1-based position of pTest in pSuite, or NULL if
+ *         not found or an error occurred.
+ *  @see CU_get_test_by_pos()
+ *  @see CU_get_test_pos_by_name()
+ */
+
+CU_EXPORT 
+unsigned int CU_get_test_pos_by_name(CU_pSuite pSuite, const char *strName);
+/**<
+ *  Looks up the position of the test having the specified name in pSuite.
+ *  The position is a 1-based index of tests in pSuite which corresponds to the order 
+ *  in which tests were added.  If no test has the specified name or an error occurs, 
+ *  0 is returned.  In addition, the framework error state is set as follows:
+ *    - CUE_NOREGISTRY if the registry is not initialized
+ *    - CUE_NOSUITE if pSuite is NULL
+ *    - CUE_NO_TESTNAME if strName is NULL
+ *  The search ends at the 1st test found having name strName.  The returned position 
+ *  may be used to retrieve the suite using CU_get_test_by_pos().
+ *
+ *  @param pSuite  Pointer to the suite to search (non-NULL).
+ *  @param strName Name of the test to find (non-NULL).
+ *  @return Returns the 1-based position of pTest in pSuite, or NULL if
+ *          not found or an error occurred.
+ *  @see CU_get_test_by_pos()
+ *  @see CU_get_test_pos_by_name()
+ */
+
+#define CU_ADD_TEST(suite, test) (CU_add_test(suite, #test, (CU_TestFunc)test))
+/**< Shortcut macro for adding a test to a suite. */
+
+/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
+/*  This section is based conceptually on code
+ *  Copyright (C) 2004  Aurema Pty Ltd.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  Derived from code contributed by K. Cheung and Aurema Pty Ltd. (thanks!)
+ *    test_case_t, test_group_t, test_suite_t
+ */
+
+/** 
+ *  Test case parameters structure.
+ *  This data type is provided to assist CUnit users manage collections of 
+ *  tests and suites.  It is intended to be used to build arrays of test case 
+ *  parameters that can be then be referred to in a CU_suite_info_t variable.
+ */
+typedef struct CU_TestInfo {
+	char       *pName;      /**< Test name. */
+	CU_TestFunc pTestFunc;  /**< Test function. */
+} CU_TestInfo;
+typedef CU_TestInfo* CU_pTestInfo;  /**< Pointer to CU_TestInfo type. */
+
+/** 
+ *  Suite parameters.
+ *  This data type is provided to assist CUnit users manage collections of 
+ *  tests and suites.  It is intended to be used to build arrays of suite 
+ *  parameters that can be passed to a bulk registration function such as 
+ *  CU_register_suite() or CU_register_suites().
+ */
+typedef struct CU_SuiteInfo {
+	char             *pName;         /**< Suite name. */
+	CU_InitializeFunc pInitFunc;     /**< Suite initialization function. */
+	CU_CleanupFunc    pCleanupFunc;  /**< Suite cleanup function */
+	CU_TestInfo      *pTests;        /**< Test case array - must be NULL terminated. */
+} CU_SuiteInfo;
+typedef CU_SuiteInfo* CU_pSuiteInfo;  /**< Pointer to CU_SuiteInfo type. */
+
+#define CU_TEST_INFO_NULL { NULL, NULL }
+/**< NULL CU_test_info_t to terminate arrays of tests. */
+#define CU_SUITE_INFO_NULL { NULL, NULL, NULL, NULL }
+/**< NULL CU_suite_info_t to terminate arrays of suites. */
+
+
+CU_EXPORT CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[]);
+/**<
+ *  Registers the suites in a single CU_SuiteInfo array.
+ *  Multiple arrays can be registered using CU_register_nsuites().
+ *
+ *  @param	suite_info NULL-terminated array of CU_SuiteInfo items to register.
+ *  @return A CU_ErrorCode indicating the error status.
+ *  @see CU_register_suites()
+ */
+CU_EXPORT CU_ErrorCode CU_register_nsuites(int suite_count, ...);
+/**<
+ *  Registers multiple suite arrays in CU_SuiteInfo format.
+ *  The function accepts a variable number of suite arrays to be registered.  
+ *  The number of arrays is indicated by the value of the 1st argument, 
+ *  suite_count.  Each suite in each array is registered with the CUnit test 
+ *  registry, along with all of the associated tests.
+ *
+ *  @param	suite_count The number of CU_SuiteInfo* arguments to follow.
+ *  @param ...          suite_count number of CU_SuiteInfo* arguments.  NULLs are ignored.
+ *  @return A CU_ErrorCode indicating the error status.
+ *  @see CU_register_suites()
+ */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+typedef CU_TestInfo test_case_t;    /**< Deprecated (version 1). @deprecated Use CU_TestInfo. */
+typedef CU_SuiteInfo test_group_t;  /**< Deprecated (version 1). @deprecated Use CU_SuiteInfo. */
+
+/** Deprecated (version 1). @deprecated Use CU_SuiteInfo and CU_TestInfo. */
+typedef struct test_suite {
+	char *name;            /**< Suite name.  Currently not used. */
+	test_group_t *groups;  /**< Test groups.  This must be a NULL terminated array. */
+} test_suite_t;
+
+/** Deprecated (version 1). @deprecated Use CU_TEST_INFO_NULL. */
+#define TEST_CASE_NULL { NULL, NULL }
+/** Deprecated (version 1). @deprecated Use CU_TEST_GROUP_NULL. */
+#define TEST_GROUP_NULL { NULL, NULL, NULL, NULL }
+
+/** Deprecated (version 1). @deprecated Use CU_register_suites(). */
+#define test_group_register(tg) CU_register_suites(tg)
+
+/** Deprecated (version 1). @deprecated Use CU_SuiteInfo and CU_register_suites(). */
+CU_EXPORT int test_suite_register(test_suite_t *ts)
+{
+	test_group_t *tg;
+	int error;
+
+	for (tg = ts->groups; tg->pName; tg++)
+		if ((error = CU_register_suites(tg)) != CUE_SUCCESS)
+			return error;
+
+	return CUE_SUCCESS;
+}
+#endif    /* USE_DEPRECATED_CUNIT_NAMES */
+/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+typedef CU_InitializeFunc InitializeFunc; /**< Deprecated (version 1). @deprecated Use CU_InitializeFunc. */
+typedef CU_CleanupFunc CleanupFunc;       /**< Deprecated (version 1). @deprecated Use CU_CleanupFunc. */
+typedef CU_TestFunc TestFunc;             /**< Deprecated (version 1). @deprecated Use CU_TestFunc. */
+
+typedef CU_Test _TestCase;                /**< Deprecated (version 1). @deprecated Use CU_Test. */
+typedef CU_pTest PTestCase;               /**< Deprecated (version 1). @deprecated Use CU_pTest. */
+
+typedef CU_Suite  _TestGroup;             /**< Deprecated (version 1). @deprecated Use CU_Suite. */
+typedef CU_pSuite PTestGroup;             /**< Deprecated (version 1). @deprecated Use CU_pSuite. */
+
+typedef CU_TestRegistry  _TestRegistry;   /**< Deprecated (version 1). @deprecated Use CU_TestRegistry. */
+typedef CU_pTestRegistry PTestRegistry;   /**< Deprecated (version 1). @deprecated Use CU_pTestRegistry. */
+
+/* Public interface functions */
+/** Deprecated (version 1). @deprecated Use CU_initialize_registry(). */
+#define initialize_registry() CU_initialize_registry()
+/** Deprecated (version 1). @deprecated Use CU_cleanup_registry(). */
+#define cleanup_registry() CU_cleanup_registry()
+/** Deprecated (version 1). @deprecated Use CU_add_suite(). */
+#define add_test_group(name, init, clean) CU_add_suite(name, init, clean)
+/** Deprecated (version 1). @deprecated Use CU_add_test(). */
+#define add_test_case(group, name, test) CU_add_test(group, name, test)
+
+/* private internal CUnit testing functions */
+/** Deprecated (version 1). @deprecated Use CU_get_registry(). */
+#define get_registry() CU_get_registry()
+/** Deprecated (version 1). @deprecated Use CU_set_registry(). */
+#define set_registry(reg) CU_set_registry((reg))
+
+/** Deprecated (version 1). @deprecated Use CU_get_suite_by_name(). */
+#define get_group_by_name(group, reg) CU_get_suite_by_name(group, reg)
+/** Deprecated (version 1). @deprecated Use CU_get_test_by_name(). */
+#define get_test_by_name(test, group) CU_get_test_by_name(test, group)
+
+/** Deprecated (version 1). @deprecated Use ADD_TEST_TO_SUITE. */
+#define ADD_TEST_TO_GROUP(group, test) (CU_add_test(group, #test, (CU_TestFunc)test))
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+/*=================================================================
+ *  Internal CUnit system functions.  
+ *  Should not be routinely called by users.
+ *=================================================================*/
+
+CU_EXPORT CU_pTestRegistry CU_get_registry(void);
+/**<
+ *  Retrieves a pointer to the current test registry.
+ *  Returns NULL if the registry has not been initialized using
+ *  CU_initialize_registry().  Directly accessing the registry
+ *  should not be necessary for most users.  This function is
+ *  provided primarily for internal and testing purposes.
+ *
+ *  @return A pointer to the current registry (NULL if uninitialized).
+ *  @see CU_initialize_registry
+ *  @see CU_set_registry
+ */
+
+CU_EXPORT CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry);
+/**<
+ *  Sets the registry to an existing CU_pTestRegistry instance.
+ *  A pointer to the original registry is returned.  Note that the
+ *  original registry is not freed, and it becomes the caller's
+ *  responsibility to do so.  Directly accessing the registry
+ *  should not be necessary for most users.  This function is
+ *  provided primarily for internal and testing purposes.<br /><br />
+ *
+ *  <B>This function must not be called during a test run (checked
+ *  by assertion)</B>.
+ *
+ *  @return A pointer to the original registry that was replaced.
+ *  @see CU_initialize_registry
+ *  @see CU_cleanup_registry
+ *  @see CU_get_registry
+ */
+
+CU_EXPORT CU_pTestRegistry CU_create_new_registry(void);
+/**< 
+ *  Creates and initializes a new test registry.
+ *  Returns a pointer to a new, initialized registry (NULL if memory could 
+ *  not be allocated).  It is the caller's responsibility to destroy and free 
+ *  the new registry (unless it is made the active test registry using
+ *  CU_set_registry()).
+ */
+
+CU_EXPORT 
+void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry);
+/**< 
+ *  Destroys and frees all memory for an existing test registry.
+ *  The active test registry is destroyed by the CUnit system in 
+ *  CU_cleanup_registry(), so only call this function on registries created
+ *  or held independently of the internal CUnit system.<br /><br />
+ *
+ *  Once a registry is made the active test registry using CU_set_registry(), 
+ *  its destruction will be handled by the framework.  ppRegistry may not be 
+ *  NULL (checked by assertion), but *ppRegistry can be NULL (in which case the
+ *  function has no effect).  Note that *ppRegistry will be set to NULL on return.
+ *
+ *  @param ppRegistry Address of a pointer to the registry to destroy (non-NULL).
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite_by_name(const char *szSuiteName, CU_pTestRegistry pRegistry);
+/**<
+ *  Retrieves a pointer to the suite having the specified name.
+ *  Scans the pRegistry and returns a pointer to the first suite located 
+ *  having the specified name.  Neither szSuiteName nor pRegistry may be
+ *  NULL (checked by assertion).  Clients should normally use CU_get_suite()
+ *  instead, which automatically searches the active test registry.
+ *
+ *  @param szSuiteName The name of the suite to locate (non-NULL).
+ *  @param pRegistry   The registry to scan (non-NULL).
+ *  @return Pointer to the first suite having the specified name,
+ *          NULL if not found.
+ *  @see CU_get_suite()
+ */
+
+CU_EXPORT 
+CU_pSuite CU_get_suite_by_index(unsigned int index, CU_pTestRegistry pRegistry);
+/**<
+ *  Retrieves a pointer to the suite at the specified (1-based) index.
+ *  Iterates pRegistry and returns a pointer to the suite located at the 
+ *  specified index.  pRegistry may not be NULL (checked by assertion).  
+ *  Clients should normally use CU_get_suite_at_pos() instead, which 
+ *  automatically searches the active test registry.
+ *
+ *  @param index     The 1-based index of the suite to find.
+ *  @param pRegistry The registry to scan (non-NULL).
+ *  @return Pointer to the suite at the specified index, or
+ *          NULL if index is invalid.
+ *  @see CU_get_suite_at_pos()
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test_by_name(const char* szTestName, CU_pSuite pSuite);
+/**< 
+ *  Retrieves a pointer to the test case in pSuite having the specified name.
+ *  The first test case in pSuite having the specified name is returned, or
+ *  NULL if not found.  Neither szSuiteName nor pSuite may be NULL (checked 
+ *  by assertion).  Clients should normally use CU_get_test() instead.
+ *
+ *  @param szTestName The name of the test case to locate (non-NULL).
+ *  @param pSuite     The suite to scan (non-NULL).
+ *  @return Pointer to the first test case having the specified name,
+ *          NULL if not found.
+ *  @see CU_get_test()
+ */
+
+CU_EXPORT 
+CU_pTest CU_get_test_by_index(unsigned int index, CU_pSuite pSuite);
+/**<
+ *  Retrieves a pointer to the test at the specified (1-based) index.
+ *  Iterates pSuite and returns a pointer to the test located at the 
+ *  specified index.  pSuite may not be NULL (checked by assertion).  
+ *  Clients should normally use CU_get_test_at_pos() instead, which 
+ *  automatically searches the active test registry.
+ *
+ *  @param index     The 1-based index of the test to find.
+ *  @param pRegistry The registry to scan (non-NULL).
+ *  @return Pointer to the test at the specified index, or
+ *          NULL if index is invalid.
+ *  @see CU_get_test_at_pos()
+ */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_TestDB(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_TESTDB_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/TestRun.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/TestRun.h
new file mode 100644
index 0000000..51072f4
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/TestRun.h
@@ -0,0 +1,444 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Interface to Run tests.
+ *
+ *  Aug 2001      Initial implementation. (AK)
+ *
+ *  09/Aug/2001   Contains generic run tests interface which can be used
+ *                for any type of frontend interface framework. (AK)
+ *
+ *  24/Nov/2001   Added Handler for Group Initialization failure condition. (AK)
+ *
+ *  05-Aug-2004   New interface.  Since these should be internal functions,
+ *                no support for deprecated version 1 names provided now,
+ *                eliminated global variables for current test & suite,
+ *                moved (renamed) _TestResult here from TestDB.h. (JDS)
+ *
+ *  05-Sep-2004   Added internal test interface. (JDS)
+ *
+ *  23-Apr-2006   Moved doxygen comments into header.
+ *                Added type marker to CU_FailureRecord.
+ *                Added support for tracking inactive suites/tests. (JDS)
+ *
+ *  08-May-2006   Moved CU_print_run_results() functionality from
+ *                console/basic test complete handler.  (JDS)
+ *
+ *  24-May-2006   Added callbacks for suite start and complete events.
+ *                Added tracking/reported of elapsed time.  (JDS)
+ */
+
+/** @file
+ *  Test run management functions (user interface).
+ *  The TestRun module implements functions supporting the running
+ *  of tests elements (suites and tests).  This includes functions for
+ *  running suites and tests, retrieving the number of tests/suites run,
+ *  and managing callbacks during the run process.<br /><br />
+ *
+ *  The callback mechanism works as follows.  The CUnit runtime system
+ *  supports the registering and calling of functions at the start and end
+ *  of each test, when all tests are complete, and when a suite
+ *  initialialization function returns an error.  This allows clients to
+ *  perform actions associated with these events such as output formatting
+ *  and reporting.
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_TESTRUN_H_SEEN
+#define CUNIT_TESTRUN_H_SEEN
+
+#include "CUnit.h"
+#include "CUError.h"
+#include "TestDB.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Types of failures occurring during test runs. */
+typedef enum CU_FailureTypes
+{
+  CUF_SuiteInactive = 1,    /**< Inactive suite was run. */
+  CUF_SuiteInitFailed,      /**< Suite initialization function failed. */
+  CUF_SuiteCleanupFailed,   /**< Suite cleanup function failed. */
+  CUF_TestInactive,         /**< Inactive test was run. */
+  CUF_AssertFailed          /**< CUnit assertion failed during test run. */
+} CU_FailureType;           /**< Failure type. */
+
+/* CU_FailureRecord type definition. */
+/** Data type for holding assertion failure information (linked list). */
+typedef struct CU_FailureRecord
+{
+  CU_FailureType  type;           /**< Failure type. */
+  unsigned int    uiLineNumber;   /**< Line number of failure. */
+  char*           strFileName;    /**< Name of file where failure occurred. */
+  char*           strCondition;   /**< Test condition which failed. */
+  CU_pTest        pTest;          /**< Test containing failure. */
+  CU_pSuite       pSuite;         /**< Suite containing test having failure. */
+
+  struct CU_FailureRecord* pNext; /**< Pointer to next record in linked list. */
+  struct CU_FailureRecord* pPrev; /**< Pointer to previous record in linked list. */
+
+} CU_FailureRecord;
+typedef CU_FailureRecord* CU_pFailureRecord;  /**< Pointer to CU_FailureRecord. */
+
+/* CU_RunSummary type definition. */
+/** Data type for holding statistics and assertion failures for a test run. */
+typedef struct CU_RunSummary
+{
+  unsigned int nSuitesRun;        /**< Number of suites completed during run. */
+  unsigned int nSuitesFailed;     /**< Number of suites for which initialization failed. */
+  unsigned int nSuitesInactive;   /**< Number of suites which were inactive. */
+  unsigned int nTestsRun;         /**< Number of tests completed during run. */
+  unsigned int nTestsFailed;      /**< Number of tests containing failed assertions. */
+  unsigned int nTestsInactive;    /**< Number of tests which were inactive (in active suites). */
+  unsigned int nAsserts;          /**< Number of assertions tested during run. */
+  unsigned int nAssertsFailed;    /**< Number of failed assertions. */
+  unsigned int nFailureRecords;   /**< Number of failure records generated. */
+  double       ElapsedTime;       /**< Elapsed time for run in seconds. */
+} CU_RunSummary;
+typedef CU_RunSummary* CU_pRunSummary;  /**< Pointer to CU_RunSummary. */
+
+/*-------------------------------------------------------------------- 
+ * Type Definitions for Message Handlers.
+ *--------------------------------------------------------------------*/
+typedef void (*CU_SuiteStartMessageHandler)(const CU_pSuite pSuite);
+/**< Message handler called at the start of a suite. pSuite will not be null. */
+
+typedef void (*CU_TestStartMessageHandler)(const CU_pTest pTest, const CU_pSuite pSuite);
+/**< Message handler called at the start of a test.
+ *  The parameters are the test and suite being run.  The test run is 
+ *  considered in progress when the message handler is called.  
+ *  Neither pTest nor pSuite may be null.
+ */
+
+typedef void (*CU_TestCompleteMessageHandler)(const CU_pTest pTest, const CU_pSuite pSuite,
+                                              const CU_pFailureRecord pFailure);
+/**< Message handler called at the completion of a test.
+ *  The parameters are the test and suite being run, plus a pointer to 
+ *  the first failure record applicable to this test.  If the test did 
+ *  not have any assertion failures, pFailure will be NULL.  The test run 
+ *  is considered in progress when the message handler is called.
+ */
+
+typedef void (*CU_SuiteCompleteMessageHandler)(const CU_pSuite pSuite,
+                                               const CU_pFailureRecord pFailure);
+/**< Message handler called at the completion of a suite.
+ *  The parameters are suite being run, plus a pointer to the first failure 
+ *  record applicable to this suite.  If the suite and it's tests did not 
+ *  have any failures, pFailure will be NULL.  The test run is considered 
+ *  in progress when the message handler is called.
+ */
+
+typedef void (*CU_AllTestsCompleteMessageHandler)(const CU_pFailureRecord pFailure);
+/**< Message handler called at the completion of a test run.
+ *  The parameter is a pointer to the linked list holding the failure 
+ *  records for the test run.  The test run is considered completed 
+ *  when the message handler is called.
+ */
+
+typedef void (*CU_SuiteInitFailureMessageHandler)(const CU_pSuite pSuite);
+/**< Message handler called when a suite initializer fails.
+ *  The test run is considered in progress when the message handler is called.
+ */
+
+typedef void (*CU_SuiteCleanupFailureMessageHandler)(const CU_pSuite pSuite);
+/**< Message handler called when a suite cleanup function fails.
+ *  The test run is considered in progress when the message handler is called.
+ */
+
+/*-------------------------------------------------------------------- 
+ * Get/Set functions for Message Handlers
+ *--------------------------------------------------------------------*/
+CU_EXPORT void CU_set_suite_start_handler(CU_SuiteStartMessageHandler pSuiteStartMessage);
+/**< Sets the message handler to call before each suite is run. */
+CU_EXPORT void CU_set_test_start_handler(CU_TestStartMessageHandler pTestStartMessage);
+/**< Sets the message handler to call before each test is run. */
+CU_EXPORT void CU_set_test_complete_handler(CU_TestCompleteMessageHandler pTestCompleteMessage);
+/**< Sets the message handler to call after each test is run. */
+CU_EXPORT void CU_set_suite_complete_handler(CU_SuiteCompleteMessageHandler pSuiteCompleteMessage);
+/**< Sets the message handler to call after each suite is run. */
+CU_EXPORT void CU_set_all_test_complete_handler(CU_AllTestsCompleteMessageHandler pAllTestsCompleteMessage);
+/**< Sets the message handler to call after all tests have been run. */
+CU_EXPORT void CU_set_suite_init_failure_handler(CU_SuiteInitFailureMessageHandler pSuiteInitFailureMessage);
+/**< Sets the message handler to call when a suite initialization function returns an error. */
+CU_EXPORT void CU_set_suite_cleanup_failure_handler(CU_SuiteCleanupFailureMessageHandler pSuiteCleanupFailureMessage);
+/**< Sets the message handler to call when a suite cleanup function returns an error. */
+
+CU_EXPORT CU_SuiteStartMessageHandler          CU_get_suite_start_handler(void);
+/**< Retrieves the message handler called before each suite is run. */
+CU_EXPORT CU_TestStartMessageHandler           CU_get_test_start_handler(void);
+/**< Retrieves the message handler called before each test is run. */
+CU_EXPORT CU_TestCompleteMessageHandler        CU_get_test_complete_handler(void);
+/**< Retrieves the message handler called after each test is run. */
+CU_EXPORT CU_SuiteCompleteMessageHandler       CU_get_suite_complete_handler(void);
+/**< Retrieves the message handler called after each suite is run. */
+CU_EXPORT CU_AllTestsCompleteMessageHandler    CU_get_all_test_complete_handler(void);
+/**< Retrieves the message handler called after all tests are run. */
+CU_EXPORT CU_SuiteInitFailureMessageHandler    CU_get_suite_init_failure_handler(void);
+/**< Retrieves the message handler called when a suite initialization error occurs. */
+CU_EXPORT CU_SuiteCleanupFailureMessageHandler CU_get_suite_cleanup_failure_handler(void);
+/**< Retrieves the message handler called when a suite cleanup error occurs. */
+
+/*-------------------------------------------------------------------- 
+ * Functions for running registered tests and suites.
+ *--------------------------------------------------------------------*/
+CU_EXPORT CU_ErrorCode CU_run_all_tests(void);
+/**< 
+ *  Runs all tests in all suites registered in the test registry.
+ *  The suites are run in the order registered in the test registry.
+ *  For each suite, it is first checked to make sure it is active.
+ *  Any initialization function is then called, the suite is run 
+ *  using run_single_suite(), and finally any suite cleanup function 
+ *  is called.  If an error condition (other than CUE_NOREGISTRY) 
+ *  occurs during the run, the action depends on the current error 
+ *  action (see CU_set_error_action()).  An inactive suite is not
+ *  considered an error for this function.  Note that the run
+ *  statistics (counts of tests, successes, failures) are cleared 
+ *  each time this function is run, even if it is unsuccessful.
+ *
+ *  @return A CU_ErrorCode indicating the first error condition
+ *          encountered while running the tests.
+ *  @see CU_run_suite() to run the tests in a specific suite.
+ *  @see CU_run_test() for run a specific test only.
+ */
+
+CU_EXPORT CU_ErrorCode CU_run_suite(CU_pSuite pSuite);
+/**< 
+ *  Runs all tests in a specified suite.
+ *  The suite need not be registered in the test registry to be 
+ *  run.  It does, however, need to have its fActive flag set to 
+ *  CU_TRUE.<br /><br />
+ *
+ *  Any initialization function for the suite is first called,
+ *  then the suite is run using run_single_suite(), and any suite 
+ *  cleanup function is called.  Note that the run statistics 
+ *  (counts of tests, successes, failures) are initialized each 
+ *  time this function is called even if it is unsuccessful.  If 
+ *  an error condition occurs during the run, the action depends 
+ *  on the  current error action (see CU_set_error_action()).
+ *
+ *  @param pSuite The suite containing the test (non-NULL)
+ *  @return A CU_ErrorCode indicating the first error condition
+ *          encountered while running the suite.  CU_run_suite()
+ *          sets and returns CUE_NOSUITE if pSuite is NULL, or 
+ *          CUE_SUITE_INACTIVE if the requested suite is not 
+ *          activated.  Other error codes can be set during suite 
+ *          initialization or cleanup or during test runs.
+ *  @see CU_run_all_tests() to run all suites.
+ *  @see CU_run_test() to run a single test in a specific suite.
+ */
+
+CU_EXPORT CU_ErrorCode CU_run_test(CU_pSuite pSuite, CU_pTest pTest);
+/**< 
+ *  Runs a specific test in a specified suite.
+ *  The suite need not be registered in the test registry to be run,
+ *  although the test must be registered in the specified suite.
+ *  Any initialization function for the suite is first
+ *  called, then the test is run using run_single_test(), and
+ *  any suite cleanup function is called.  Note that the
+ *  run statistics (counts of tests, successes, failures)
+ *  will be initialized each time this function is called even
+ *  if it is not successful.  Both the suite and test specified
+ *  must be active for the test to be run.  The suite is not
+ *  considered to be run, although it may be counted as a failed
+ *  suite if the intialization or cleanup functions fail.
+ *
+ *  @param pSuite The suite containing the test (non-NULL)
+ *  @param pTest  The test to run (non-NULL)
+ *  @return A CU_ErrorCode indicating the first error condition
+ *          encountered while running the suite.  CU_run_test()
+ *          sets and returns CUE_NOSUITE if pSuite is NULL,
+ *          CUE_NOTEST if pTest is NULL, CUE_SUITE_INACTIVE if
+ *          pSuite is not active, CUE_TEST_NOT_IN_SUITE
+ *          if pTest is not registered in pSuite, and CU_TEST_INACTIVE
+ *          if pTest is not active.  Other error codes can be set during 
+ *          suite initialization or cleanup or during the test run.
+ *  @see CU_run_all_tests() to run all tests/suites.
+ *  @see CU_run_suite() to run all tests in a specific suite.
+ */
+
+/*-------------------------------------------------------------------- 
+ * Functions for setting runtime behavior.
+ *--------------------------------------------------------------------*/
+CU_EXPORT void CU_set_fail_on_inactive(CU_BOOL new_inactive);
+/**<
+ *  Sets whether an inactive suite or test is treated as a failure.
+ *  If CU_TRUE, then failure records will be generated for inactive 
+ *  suites or tests encountered during a test run.  The default is 
+ *  CU_TRUE so that the client is reminded that the framewrork 
+ *  contains inactive suites/tests.  Set to CU_FALSE to turn off 
+ *  this behavior.
+ *
+ *  @param new_inactive New setting for whether to treat inactive
+ *                      suites and tests as failures during a test 
+ *                      run (CU_TRUE) or not (CU_FALSE).
+ *  @see CU_get_fail_on_failure()
+ */
+ 
+CU_EXPORT CU_BOOL CU_get_fail_on_inactive(void);
+/**<
+ *  Retrieves the current setting for whether inactive suites/tests 
+ *  are treated as failures.  If CU_TRUE then failure records will 
+ *  be generated for inactive suites encountered during a test run.
+ *
+ *  @return CU_TRUE if inactive suites/tests are failures, CU_FALSE if not.
+ *  @see CU_set_fail_on_inactive()
+ */
+
+/*-------------------------------------------------------------------- 
+ * Functions for getting information about the previous test run. 
+ *--------------------------------------------------------------------*/
+CU_EXPORT unsigned int CU_get_number_of_suites_run(void);
+/**< Retrieves the number of suites completed during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_suites_failed(void);
+/**< Retrieves the number of suites which failed to initialize during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_suites_inactive(void);
+/**< Retrieves the number of inactive suites found during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_tests_run(void);
+/**< Retrieves the number of tests completed during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_tests_failed(void);
+/**< Retrieves the number of tests containing failed assertions during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_tests_inactive(void);
+/**< Retrieves the number of inactive tests found during the previous run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_asserts(void);
+/**< Retrieves the number of assertions processed during the last run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_successes(void);
+/**< Retrieves the number of successful assertions during the last run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_failures(void);
+/**< Retrieves the number of failed assertions during the last run (reset each run). */
+CU_EXPORT unsigned int CU_get_number_of_failure_records(void);
+/**< 
+ *  Retrieves the number failure records created during the previous run (reset each run).  
+ *  Note that this may be more than the number of failed assertions, since failure 
+ *  records may also be created for failed suite initialization and cleanup.
+ */
+CU_EXPORT double CU_get_elapsed_time(void);
+/**< 
+ *  Retrieves the elapsed time for the last run in seconds (reset each run).
+ *  This function will calculate the current elapsed time if the test run has not
+ *  yet completed.  This is in contrast to the run summary returned by
+ *  CU_get_run_summary(), for which the elapsed time is not updated until the
+ *  end of the run.
+ */
+CU_EXPORT CU_pFailureRecord CU_get_failure_list(void);
+/**< 
+ *  Retrieves the head of the linked list of failures which occurred during the 
+ *  last run (reset each run).  Note that the pointer returned is invalidated 
+ *  when the client initiates a run using CU_run_all_tests(), CU_run_suite(), 
+ *  or CU_run_test().
+ */
+CU_EXPORT CU_pRunSummary CU_get_run_summary(void);
+/**< 
+ *  Retrieves the entire run summary for the last test run (reset each run).
+ *  The run counts and stats contained in the run summary are updated 
+ *  throughout a test run.  Note, however, that the elapsed time is not 
+ *  updated until after all suites/tests are run but before the "all tests 
+ *  complete"  message handler is called (if any).  To get the elapsed 
+ *  time during a test run, use CU_get_elapsed_time() instead.
+ */
+
+CU_EXPORT char * CU_get_run_results_string(void);
+/**<
+ *  Creates a string and fills it with a summary of the current run results.
+ *  The run summary presents data for the suites, tests, and assertions 
+ *  encountered during the run, as well as the elapsed time.  The data 
+ *  presented include the number of registered, run, passed, failed, and 
+ *  inactive entities for each, as well as the elapsed time.  This function 
+ *  can be called at any time, although the test registry must have been 
+ *  initialized (checked by assertion).  The returned string is owned by 
+ *  the caller and should be deallocated using CU_FREE().  NULL is returned 
+ *  if there is an error allocating the new string.
+ *
+ *  @return A new string containing the run summary (owned by caller).
+ */
+ 
+CU_EXPORT void CU_print_run_results(FILE *file);
+/**<
+ *  Prints a summary of the current run results to file.
+ *  The run summary is the same as returned by CU_get_run_results_string().
+ *  Note that no newlines are printed before or after the report, so any
+ *  positioning must be performed before/after calling this function.  The
+ *  report itself extends over several lines broken by '\n' characters.
+ *  file may not be NULL (checked by assertion).
+ *
+ *  @param file Pointer to stream to receive the printed summary (non-NULL).
+ */
+
+/*-------------------------------------------------------------------- 
+ * Functions for internal & testing use.
+ *--------------------------------------------------------------------*/
+CU_EXPORT CU_pSuite CU_get_current_suite(void);
+/**< Retrieves a pointer to the currently-running suite (NULL if none). */
+CU_EXPORT CU_pTest  CU_get_current_test(void);
+/**< Retrievea a pointer to the currently-running test (NULL if none). */
+CU_EXPORT CU_BOOL   CU_is_test_running(void);
+/**< Returns <CODE>CU_TRUE</CODE> if a test run is in progress,
+ *  <CODE>CU_TRUE</CODE> otherwise.
+ */
+
+CU_EXPORT void      CU_clear_previous_results(void);
+/**< 
+ *  Initializes the run summary information stored from the previous test run.  
+ *  Resets the run counts to zero, and frees any memory associated with 
+ *  failure records.  Calling this function multiple times, while inefficient, 
+ *  will not cause an error condition.
+ *  @see clear_previous_results()
+ */
+
+CU_EXPORT CU_BOOL CU_assertImplementation(CU_BOOL bValue,
+                                          unsigned int uiLine,
+                                          const char *strCondition,
+                                          const char *strFile,
+                                          const char *strFunction,
+                                          CU_BOOL bFatal);
+/**< 
+ *  Assertion implementation function.
+ *  All CUnit assertions reduce to a call to this function.  It should only be
+ *  called during an active test run (checked by assertion).  This means that CUnit
+ *  assertions should only be used in registered test functions during a test run.
+ *
+ *  @param bValue        Value of the assertion (CU_TRUE or CU_FALSE).
+ *  @param uiLine        Line number of failed test statement.
+ *  @param strCondition  String containing logical test that failed.
+ *  @param strFile       Source file where test statement failed.
+ *  @param strFunction   Function where test statement failed.
+ *  @param bFatal        CU_TRUE to abort test (via longjmp()), CU_FALSE to continue test.
+ *  @return As a convenience, returns the value of the assertion (i.e. bValue).
+ */
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+typedef CU_FailureRecord  _TestResult;  /**< @deprecated Use CU_FailureRecord. */
+typedef CU_pFailureRecord PTestResult;  /**< @deprecated Use CU_pFailureRecord. */
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_TestRun(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif  /*  CUNIT_TESTRUN_H_SEEN  */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Util.h b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Util.h
new file mode 100644
index 0000000..c03085b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/include/CUnit/Util.h
@@ -0,0 +1,158 @@
+/*
+ *  CUnit - A Unit testing framework library for C.
+ *  Copyright (C) 2001       Anil Kumar
+ *  Copyright (C) 2004-2006  Anil Kumar, Jerry St.Clair
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  Contains Type Definitions for some generic functions used across
+ *  CUnit project files.
+ *
+ *  13/Oct/2001   Moved some of the generic functions declarations from
+ *                other files to this one so as to use the functions 
+ *                consitently. This file is not included in the distribution 
+ *                headers because it is used internally by CUnit. (AK)
+ *
+ *  20-Jul-2004   New interface, support for deprecated version 1 names. (JDS)
+ *
+ *  5-Sep-2004    Added internal test interface. (JDS)
+ *
+ *  17-Apr-2006   Added CU_translated_strlen() and CU_number_width().
+ *                Removed CUNIT_MAX_STRING_LENGTH - dangerous since not enforced.
+ *                Fixed off-by-1 error in CU_translate_special_characters(), 
+ *                modifying implementation & results in some cases.  User can 
+ *                now tell if conversion failed. (JDS)
+ */
+
+/** @file
+ *  Utility functions (user interface).
+ */
+/** @addtogroup Framework
+ * @{
+ */
+
+#ifndef CUNIT_UTIL_H_SEEN
+#define CUNIT_UTIL_H_SEEN
+
+#include "CUnit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CUNIT_MAX_ENTITY_LEN 5
+/**< Maximum number of characters in a translated xml entity. */
+
+CU_EXPORT size_t CU_translate_special_characters(const char *szSrc, char *szDest, size_t maxlen);
+/**< 
+ *  Converts special characters in szSrc to xml entity codes and stores 
+ *  result in szDest.  Currently conversion of '&', '<', and '>' is supported.
+ *  Note that conversion to entities increases the length of the converted 
+ *  string.  The greatest conversion size increase would be a string 
+ *  consisting entirely of entity characters of converted length 
+ *  CUNIT_MAX_ENTITY_LEN.  Neither szSrc nor szDest may be NULL 
+ *  (checked by assertion).<br /><br />
+ *
+ *  maxlen gives the maximum number of characters in the translated string.
+ *  If szDest does not have enough room to hold the converted string, the
+ *  return value will be zero and szDest will contain an empty string.
+ *  If this occurs, the remaining characters in szDest may be overwritten
+ *  in an unspecified manner.  It is the caller's responsibility to make 
+ *  sure there is sufficient room in szDest to hold the converted string.  
+ *  CU_translated_strlen() may be used to calculate the length of buffer 
+ *  required (remember to add 1 for the terminating \0).  
+ *
+ *  @param szSrc  Source string to convert (non-NULL).
+ *  @param szDest Location to hold the converted string (non-NULL).
+ *  @param maxlen Maximum number of characters szDest can hold.
+ *  @return   The number of special characters converted (always 0 if
+ *            szDest did not have enough room to hold converted string).
+ */
+
+CU_EXPORT size_t CU_translated_strlen(const char *szSrc);
+/**< 
+ *  Calculates the length of a translated string.
+ *  This function calculates the buffer length required to hold a string
+ *  after processing with CU_translate_special_characters().  The returned
+ *  length does not include space for the terminating '\0' character.  
+ *  szSrc may not be NULL (checked by assertion).
+ *
+ *  @param szSrc  Source string to analyze (non-NULL).
+ *  @return The number of characters szSrc will expand to when converted.
+ */
+
+CU_EXPORT int CU_compare_strings(const char *szSrc, const char *szDest);
+/**<
+ *  Case-insensitive string comparison.  Neither string pointer
+ *  can be NULL (checked by assertion).
+ *
+ *  @param szSrc  1st string to compare (non-NULL).
+ *  @param szDest 2nd string to compare (non-NULL).
+ *  @return  0 if the strings are equal, non-zero otherwise.
+ */
+
+CU_EXPORT void CU_trim_left(char *szString);
+/**<
+ *  Trims leading whitespace from the specified string.
+ *  @param szString  The string to trim.
+ */
+
+CU_EXPORT void CU_trim_right(char *szString);
+/**< 
+ *  Trims trailing whitespace from the specified string.
+ *  @param szString  The string to trim.
+ */
+
+CU_EXPORT void CU_trim(char *szString);
+/**< 
+ *  Trims leading and trailing whitespace from the specified string.
+ *  @param szString  The string to trim.
+ */
+
+CU_EXPORT size_t CU_number_width(int number);
+/**< 
+ *  Calulates the number of places required to display 
+ *  number in decimal.
+ */
+
+#ifdef CUNIT_BUILD_TESTS
+void test_cunit_Util(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef USE_DEPRECATED_CUNIT_NAMES
+#define CUNIT_MAX_STRING_LENGTH	1024
+/**< Maximum string length. */
+#define translate_special_characters(src, dest, len) CU_translate_special_characters(src, dest, len)
+/**< Deprecated (version 1). @deprecated Use CU_translate_special_characters(). */
+#define compare_strings(src, dest) CU_compare_strings(src, dest)
+/**< Deprecated (version 1). @deprecated Use CU_compare_strings(). */
+
+#define trim_left(str) CU_trim_left(str)
+/**< Deprecated (version 1). @deprecated Use CU_trim_left(). */
+#define trim_right(str) CU_trim_right(str)
+/**< Deprecated (version 1). @deprecated Use CU_trim_right(). */
+#define trim(str) CU_trim(str)
+/**< Deprecated (version 1). @deprecated Use CU_trim(). */
+
+#endif  /* USE_DEPRECATED_CUNIT_NAMES */
+
+#endif /* CUNIT_UTIL_H_SEEN */
+/** @} */
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/cygwin/cunit.lib b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/cygwin/cunit.lib
new file mode 100755
index 0000000..047d5d3
Binary files /dev/null and b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/cygwin/cunit.lib differ
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/linux/libcunit.a b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/linux/libcunit.a
new file mode 100644
index 0000000..8cc73ef
Binary files /dev/null and b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/linux/libcunit.a differ
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/mingw/cunit.lib b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/mingw/cunit.lib
new file mode 100755
index 0000000..411d432
Binary files /dev/null and b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/mingw/cunit.lib differ
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/osx/libcunit.a b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/osx/libcunit.a
new file mode 100644
index 0000000..16a84bb
Binary files /dev/null and b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/osx/libcunit.a differ
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/vs2010/cunit.lib b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/vs2010/cunit.lib
new file mode 100644
index 0000000..b4cda57
Binary files /dev/null and b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/vs2010/cunit.lib differ
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/vs2013/cunit.lib b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/vs2013/cunit.lib
new file mode 100755
index 0000000..a5c8b36
Binary files /dev/null and b/subprojects/docs/src/samples/native-binaries/cunit/lib/cunit/2.1-2/lib/vs2013/cunit.lib differ
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operators/c/minus.c b/subprojects/docs/src/samples/native-binaries/cunit/src/operators/c/minus.c
new file mode 100644
index 0000000..01fb74b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operators/c/minus.c
@@ -0,0 +1,5 @@
+#include "operators.h"
+
+int minus(int a, int b) {
+    return a - b;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operators/c/plus.c b/subprojects/docs/src/samples/native-binaries/cunit/src/operators/c/plus.c
new file mode 100644
index 0000000..1a2f3d6
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operators/c/plus.c
@@ -0,0 +1,9 @@
+#include "operators.h"
+
+int plus(int a, int b) {
+#ifdef PLUS_BROKEN
+    return 2;
+#else
+    return a + b;
+#endif
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operators/headers/operators.h b/subprojects/docs/src/samples/native-binaries/cunit/src/operators/headers/operators.h
new file mode 100644
index 0000000..d27b808
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operators/headers/operators.h
@@ -0,0 +1,2 @@
+int plus(int a, int b);
+int minus(int a, int b);
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/suite_operators.c b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/suite_operators.c
new file mode 100644
index 0000000..98df414
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/suite_operators.c
@@ -0,0 +1,17 @@
+#include <CUnit/Basic.h>
+#include "gradle_cunit_register.h"
+#include "test_operators.h"
+
+int suite_init(void) {
+    return 0;
+}
+
+int suite_clean(void) {
+    return 0;
+}
+
+void gradle_cunit_register() {
+    CU_pSuite pSuiteMath = CU_add_suite("operator tests", suite_init, suite_clean);
+    CU_add_test(pSuiteMath, "test_plus", test_plus);
+    CU_add_test(pSuiteMath, "test_minus", test_minus);
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/test_minus.c b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/test_minus.c
new file mode 100644
index 0000000..400cb83
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/test_minus.c
@@ -0,0 +1,8 @@
+#include <CUnit/Basic.h>
+#include "operators.h"
+
+void test_minus() {
+  CU_ASSERT(minus(2, 0) == 2);
+  CU_ASSERT(minus(0, -2) == 2);
+  CU_ASSERT(minus(2, 2) == 0);
+}
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c
new file mode 100644
index 0000000..2bd47b4
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c
@@ -0,0 +1,8 @@
+#include <CUnit/Basic.h>
+#include "operators.h"
+
+void test_plus() {
+  CU_ASSERT(plus(0, 2) == 2);
+  CU_ASSERT(plus(0, -2) == -2);
+  CU_ASSERT(plus(2, 2) == 4);
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/headers/test_operators.h b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/headers/test_operators.h
new file mode 100644
index 0000000..29b7dff
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/cunit/src/operatorsTest/headers/test_operators.h
@@ -0,0 +1,2 @@
+void test_plus();
+void test_minus();
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/custom-layout/build.gradle b/subprojects/docs/src/samples/native-binaries/custom-layout/build.gradle
new file mode 100644
index 0000000..4b07368
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/custom-layout/build.gradle
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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: 'cpp'
+apply plugin: 'c'
+
+libraries {
+    hello {}
+}
+
+// START SNIPPET binary-library
+executables {
+    main {
+        binaries.all {
+            // Each executable binary produced uses the 'hello' static library binary
+            lib libraries.hello.static
+        }
+    }
+}
+// END SNIPPET binary-library
+
+// START SNIPPET cpp-sources
+sources {
+    main {
+        cpp {
+            source {
+                srcDir "src/source"
+                include "**/*.cpp"
+            }
+        }
+    }
+}
+// END SNIPPET cpp-sources
+// START SNIPPET c-sources
+sources {
+    hello {
+        c {
+            source {
+                srcDir "src/source"
+                include "**/*.c"
+            }
+            exportedHeaders {
+                srcDir "src/include"
+            }
+        }
+    }
+}
+// END SNIPPET c-sources
+
diff --git a/subprojects/docs/src/samples/native-binaries/custom-layout/src/include/hello.h b/subprojects/docs/src/samples/native-binaries/custom-layout/src/include/hello.h
new file mode 100755
index 0000000..51b48fd
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/custom-layout/src/include/hello.h
@@ -0,0 +1,7 @@
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
diff --git a/subprojects/docs/src/samples/native-binaries/custom-layout/src/source/hello.c b/subprojects/docs/src/samples/native-binaries/custom-layout/src/source/hello.c
new file mode 100755
index 0000000..1d8c48f
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/custom-layout/src/source/hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+#include "hello.h"
+
+void LIB_FUNC hello () {
+  printf("Hello world!");
+}
diff --git a/subprojects/docs/src/samples/native-binaries/custom-layout/src/source/main.cpp b/subprojects/docs/src/samples/native-binaries/custom-layout/src/source/main.cpp
new file mode 100644
index 0000000..d3e79dd
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/custom-layout/src/source/main.cpp
@@ -0,0 +1,8 @@
+extern "C" {
+    #include "hello.h"
+}
+
+int main () {
+  hello();
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/flavors/build.gradle b/subprojects/docs/src/samples/native-binaries/flavors/build.gradle
new file mode 100644
index 0000000..39328db
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/flavors/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'cpp'
+
+sources {
+    exe {}
+    lib {}
+}
+// START SNIPPET flavors
+model {
+    flavors {
+        english
+        french
+    }
+}
+
+libraries {
+    hello {
+        binaries.all {
+            if (flavor == flavors.french) {
+                cppCompiler.define "FRENCH"
+            }
+        }
+        source sources.lib
+    }
+}
+// END SNIPPET flavors
+binaries.withType(SharedLibraryBinary) {
+    cppCompiler.define "DLL_EXPORT"
+}
+
+executables {
+    main {
+        source sources.exe
+        binaries.all {
+            lib libraries.hello
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/flavors/src/exe/cpp/main.cpp
similarity index 100%
copy from subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
copy to subprojects/docs/src/samples/native-binaries/flavors/src/exe/cpp/main.cpp
diff --git a/subprojects/docs/src/samples/native-binaries/flavors/src/lib/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/flavors/src/lib/cpp/hello.cpp
new file mode 100755
index 0000000..733d15a
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/flavors/src/lib/cpp/hello.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+#include "hello.h"
+
+void LIB_FUNC hello () {
+  #ifdef FRENCH
+  std::cout << "Bonjour monde!" << std::endl;
+  #else
+  std::cout << "Hello world!" << std::endl;
+  #endif
+}
diff --git a/subprojects/docs/src/samples/native-binaries/flavors/src/lib/headers/hello.h b/subprojects/docs/src/samples/native-binaries/flavors/src/lib/headers/hello.h
new file mode 100755
index 0000000..79b608b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/flavors/src/lib/headers/hello.h
@@ -0,0 +1,10 @@
+#ifdef _WIN32
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#endif
+#endif
+#ifndef LIB_FUNC
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
diff --git a/subprojects/docs/src/samples/native-binaries/idl/build.gradle b/subprojects/docs/src/samples/native-binaries/idl/build.gradle
new file mode 100644
index 0000000..53e9260
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/idl/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'c'
+
+task idl(type: DummyIdlCompiler) {
+    idlFiles = project.fileTree("src/main/idl")
+    headerDir = project.file("${buildDir}/src/generated/headers")
+    sourceDir = project.file("${buildDir}/src/generated/c")
+}
+
+sources {
+    main {
+        idlOutput(CSourceSet) {
+            generatedBy tasks.idl
+        }
+    }
+}
+sources.main.c.lib sources.main.idlOutput
+
+executables {
+    main {}
+}
+
+class DummyIdlCompiler extends DefaultTask {
+    @InputFiles FileCollection idlFiles
+    @OutputDirectory File headerDir
+    @OutputDirectory File sourceDir
+
+    @TaskAction
+    void processIdlFiles() {
+        idlFiles.files.each { File idlFile ->
+            def baseName = idlFile.name - '.idl'
+            File headerFile = new File(headerDir, "${baseName}.h")
+            File sourceFile = new File(sourceDir, "${baseName}.c")
+            processIdlFile(idlFile, headerFile, sourceFile)
+        }
+    }
+    
+    def processIdlFile(File idlFile, File headerFile, File sourceFile) {
+        def pattern = ~/(?s).*HEADER <<<(.*)>>>.*SOURCE <<<(.*)>>>.*/
+        def matcher = pattern.matcher(idlFile.text)
+        assert matcher.matches()
+        headerFile.text = matcher.group(1)
+        sourceFile.text = matcher.group(2)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/idl/src/main/c/main.c b/subprojects/docs/src/samples/native-binaries/idl/src/main/c/main.c
new file mode 100644
index 0000000..7f1b16d
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/idl/src/main/c/main.c
@@ -0,0 +1,6 @@
+#include "hello.h"
+
+int main () {
+    hello();
+    return 0;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/idl/src/main/idl/hello.idl b/subprojects/docs/src/samples/native-binaries/idl/src/main/idl/hello.idl
new file mode 100644
index 0000000..9a43ed4
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/idl/src/main/idl/hello.idl
@@ -0,0 +1,16 @@
+This is not a real IDL file, just a dummy file that is used to demonstrate generated sources from idl inputs.
+This file contains a HEADER section and a SOURCE section, which are parsed to generate the actual header and source files.
+The generated files have the same name as the idl file, with the extension replaced by '.h' or '.c'.
+
+HEADER <<<
+void hello();
+>>>
+--------------
+SOURCE <<<
+#include <stdio.h>
+#include "hello.h"
+
+void hello () {
+  printf("Hello from generated source!!\n");
+}
+>>>
diff --git a/subprojects/docs/src/samples/native-binaries/multi-project/build.gradle b/subprojects/docs/src/samples/native-binaries/multi-project/build.gradle
new file mode 100644
index 0000000..2a3964c
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/multi-project/build.gradle
@@ -0,0 +1,24 @@
+// START SNIPPET project-dependencies
+project(":lib") {
+    apply plugin: "cpp"
+    libraries {
+        main {}
+    }
+}
+
+project(":exe") {
+    apply plugin: "cpp"
+
+    executables {
+        main {}
+    }
+
+    sources {
+        main {
+            cpp {
+                lib project: ':lib', library: 'main'
+            }
+        }
+    }
+}
+// END SNIPPET project-dependencies
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/multi-project/exe/src/main/cpp/main.cpp
similarity index 100%
copy from subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
copy to subprojects/docs/src/samples/native-binaries/multi-project/exe/src/main/cpp/main.cpp
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/multi-project/lib/src/main/cpp/hello.cpp
similarity index 100%
rename from subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/cpp/hello.cpp
rename to subprojects/docs/src/samples/native-binaries/multi-project/lib/src/main/cpp/hello.cpp
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h b/subprojects/docs/src/samples/native-binaries/multi-project/lib/src/main/headers/hello.h
similarity index 100%
rename from subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h
rename to subprojects/docs/src/samples/native-binaries/multi-project/lib/src/main/headers/hello.h
diff --git a/subprojects/docs/src/samples/cpp/dependencies/settings.gradle b/subprojects/docs/src/samples/native-binaries/multi-project/settings.gradle
similarity index 100%
rename from subprojects/docs/src/samples/cpp/dependencies/settings.gradle
rename to subprojects/docs/src/samples/native-binaries/multi-project/settings.gradle
diff --git a/subprojects/docs/src/samples/native-binaries/objective-c/build.gradle b/subprojects/docs/src/samples/native-binaries/objective-c/build.gradle
new file mode 100644
index 0000000..51d7e9b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/objective-c/build.gradle
@@ -0,0 +1,23 @@
+// START SNIPPET apply-plugin
+apply plugin: 'objective-c'
+// END SNIPPET apply-plugin
+
+// START SNIPPET executables
+executables {
+    main {}
+}
+// END SNIPPET executables
+
+
+// START SNIPPET all-binaries
+binaries.all {
+    objcCompiler.args "-I/usr/include/GNUstep", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+
+    //on osx we need different settings than on linux or windows
+    if(targetPlatform.operatingSystem.isMacOsX()){
+        linker.args "-framework", "Foundation"
+    }else{
+        linker.args "-lgnustep-base", "-lobjc"
+    }
+}
+// END SNIPPET all-binaries
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/objective-c/src/main/objc/main.m b/subprojects/docs/src/samples/native-binaries/objective-c/src/main/objc/main.m
new file mode 100644
index 0000000..18d5703
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/objective-c/src/main/objc/main.m
@@ -0,0 +1,10 @@
+#import <Foundation/Foundation.h>
+
+int main (int argc, const char * argv[])
+{
+    NSString *helloWorld = @"Hello world!\n";
+    NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+    NSData *strData = [helloWorld dataUsingEncoding: NSASCIIStringEncoding];
+    [stdout writeData: strData];
+    return 0;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/objective-cpp/build.gradle b/subprojects/docs/src/samples/native-binaries/objective-cpp/build.gradle
new file mode 100644
index 0000000..16a73de
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/objective-cpp/build.gradle
@@ -0,0 +1,23 @@
+// START SNIPPET apply-plugin
+apply plugin: 'objective-cpp'
+// END SNIPPET apply-plugin
+
+// START SNIPPET executables
+executables {
+    main {}
+}
+// END SNIPPET executables
+
+
+// START SNIPPET all-binaries
+binaries.all {
+    objcppCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
+
+    //on osx we need different linker settings than on linux or windows
+    if(targetPlatform.operatingSystem.isMacOsX()){
+        linker.args "-framework", "Foundation"
+    }else{
+        linker.args "-lgnustep-base", "-lobjc"
+    }
+}
+// END SNIPPET all-binaries
diff --git a/subprojects/docs/src/samples/native-binaries/objective-cpp/src/main/objcpp/main.mm b/subprojects/docs/src/samples/native-binaries/objective-cpp/src/main/objcpp/main.mm
new file mode 100644
index 0000000..1c97154
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/objective-cpp/src/main/objcpp/main.mm
@@ -0,0 +1,14 @@
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+#import <Foundation/Foundation.h>
+#include <iostream>
+
+int main (int argc, const char * argv[])
+{
+    NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
+    NSData *strData = [@"Hello " dataUsingEncoding: NSASCIIStringEncoding];
+    [stdout writeData: strData];
+    std::cout << "world!" << std::endl;
+
+    return 0;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/boost_1_55_0/README.txt b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/boost_1_55_0/README.txt
new file mode 100644
index 0000000..2198b7a
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/boost_1_55_0/README.txt
@@ -0,0 +1,2 @@
+This is a placeholder for a boost distribution, demonstrating how boost could be incorporated into a Gradle project
+as a header-only library. For this example, only a single header file (boost/version.hpp) is required.
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/boost_1_55_0/boost/version.hpp b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/boost_1_55_0/boost/version.hpp
new file mode 100644
index 0000000..0df7b27
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/boost_1_55_0/boost/version.hpp
@@ -0,0 +1,32 @@
+//  Boost version.hpp configuration header file  ------------------------------//
+
+//  (C) Copyright John maddock 1999. Distributed under the Boost
+//  Software License, Version 1.0. (See accompanying file
+//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+//  See http://www.boost.org/libs/config for documentation
+
+#ifndef BOOST_VERSION_HPP
+#define BOOST_VERSION_HPP
+
+//
+//  Caution, this is the only boost header that is guarenteed
+//  to change with every boost release, including this header
+//  will cause a recompile every time a new boost version is
+//  released.
+//
+//  BOOST_VERSION % 100 is the patch level
+//  BOOST_VERSION / 100 % 1000 is the minor version
+//  BOOST_VERSION / 100000 is the major version
+
+#define BOOST_VERSION 105500
+
+//
+//  BOOST_LIB_VERSION must be defined to be the same as BOOST_VERSION
+//  but as a *string* in the form "x_y[_z]" where x is the major version
+//  number, y is the minor version number, and z is the patch level if not 0.
+//  This is used by <config/auto_link.hpp> to select which library version to link to.
+
+#define BOOST_LIB_VERSION "1_55"
+
+#endif
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/README.txt b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/README.txt
new file mode 100644
index 0000000..7e7762d
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/README.txt
@@ -0,0 +1,2 @@
+To demonstrate the integration with prebuilt libraries available on the file system, this 'util' library must first be
+built. The fact that it is built by Gradle is not important to this example.
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/build.gradle b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/build.gradle
new file mode 100644
index 0000000..2e6a47b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'cpp'
+model {
+    buildTypes {
+        debug
+        release
+    }
+}
+libraries {
+    util {
+        binaries.all {
+            if (buildType == buildTypes.debug) {
+                cppCompiler.define 'DEBUG'
+                if (toolChain in VisualCpp) {
+                    cppCompiler.args '/Zi'
+                    linker.args '/DEBUG'
+                } else {
+                    cppCompiler.args "-g"
+                }
+            }
+        }
+    }
+}
+task buildLibraries {
+    dependsOn binaries.withType(LibraryBinary)
+}
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/settings.gradle b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/settings.gradle
new file mode 100644
index 0000000..226302f
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = "util"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/src/util/cpp/util.cpp b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/src/util/cpp/util.cpp
new file mode 100644
index 0000000..4c3e406
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/src/util/cpp/util.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+#include "util.h"
+
+void LIB_FUNC printBuildType () {
+#ifdef DEBUG
+  std::cout << "Util build type: DEBUG" << std::endl;
+#else
+  std::cout << "Util build type: RELEASE" << std::endl;
+#endif
+}
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/src/util/headers/util.h b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/src/util/headers/util.h
new file mode 100755
index 0000000..93fece4
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/3rd-party-lib/util/src/util/headers/util.h
@@ -0,0 +1,7 @@
+#ifdef _WIN32
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC printBuildType();
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/build.gradle b/subprojects/docs/src/samples/native-binaries/prebuilt/build.gradle
new file mode 100644
index 0000000..6d274cf
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'cpp'
+model {
+    buildTypes {
+        debug
+        release
+    }
+    repositories {
+        libs(PrebuiltLibraries) {
+            boost {
+                headers.srcDir "3rd-party-lib/boost_1_55_0/boost"
+            }
+            util {
+                headers.srcDir "3rd-party-lib/util/src/util/headers"
+                binaries.withType(StaticLibraryBinary) {
+                    def libName = targetPlatform.operatingSystem.windows ? 'util.lib' : 'libutil.a'
+                    staticLibraryFile = file("3rd-party-lib/util/build/binaries/utilStaticLibrary/${buildType.name}/${libName}")
+                }
+                binaries.withType(SharedLibraryBinary) {
+                    def os = targetPlatform.operatingSystem
+                    def baseDir = "3rd-party-lib/util/build/binaries/utilSharedLibrary/${buildType.name}"
+                    if (os.windows) {
+                        // Windows uses a .dll file, with a different link file if it exists (not Cygwin or MinGW)
+                        sharedLibraryFile = file("${baseDir}/util.dll")
+                        if (file("${baseDir}/util.lib").exists()) {
+                            sharedLibraryLinkFile = file("${baseDir}/util.lib")
+                        }
+                    } else if (os.macOsX) {
+                        sharedLibraryFile = file("${baseDir}/libhello.dylib")
+                    } else {
+                        sharedLibraryFile = file("${baseDir}/libutil.so")
+                    }
+                }
+            }
+        }
+    }
+}
+executables {
+    main {}
+}
+sources.main.cpp.lib library: 'boost', linkage: 'api'
+sources.main.cpp.lib library: 'util', linkage: 'static'
+task buildExecutables {
+    dependsOn binaries.withType(ExecutableBinary)
+}
+
diff --git a/subprojects/docs/src/samples/native-binaries/prebuilt/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/prebuilt/src/main/cpp/main.cpp
new file mode 100644
index 0000000..fe2ff9d
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/prebuilt/src/main/cpp/main.cpp
@@ -0,0 +1,9 @@
+#include <iostream>
+#include "version.hpp"
+#include "util.h"
+
+int main () {
+  std::cout << "Built with Boost version: " << BOOST_LIB_VERSION << std::endl;
+  printBuildType();
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/tool-chains/build.gradle b/subprojects/docs/src/samples/native-binaries/tool-chains/build.gradle
new file mode 100644
index 0000000..00e10fe
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/tool-chains/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'cpp'
+
+// START SNIPPET toolChains
+model {
+    toolChains {
+        visualCpp(VisualCpp) {
+            // Specify the installDir if Visual Studio cannot be located by default
+            // installDir "C:/Apps/Microsoft Visual Studio 10.0"
+        }
+        gcc(Gcc) {
+            // Uncomment to use a GCC install that is not in the PATH
+            // path "/usr/bin/gcc"
+        }
+        clang(Clang)
+    }
+}
+// END SNIPPET toolChains
+
+executables {
+    main {}
+}
+
+// START SNIPPET buildable
+task buildAllExecutables {
+    dependsOn binaries.withType(ExecutableBinary).matching {
+        it.buildable
+    }
+}
+// END SNIPPET buildable
diff --git a/subprojects/docs/src/samples/native-binaries/tool-chains/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/tool-chains/src/main/cpp/main.cpp
new file mode 100644
index 0000000..d0e685c
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/tool-chains/src/main/cpp/main.cpp
@@ -0,0 +1,18 @@
+#include <iostream>
+
+int main () {
+    #if defined(__clang__)
+        std::cout << "Hello from clang!" << std::endl;
+    #elif defined(__GNUC__) && defined(__MINGW32__)
+        std::cout << "Hello from mingw!" << std::endl;
+    #elif defined(__GNUC__) && defined(__CYGWIN__)
+        std::cout << "Hello from gcc cygwin!" << std::endl;
+    #elif defined(__GNUC__)
+        std::cout << "Hello from gcc!" << std::endl;
+    #elif defined(_MSC_VER)
+        std::cout << "Hello from visual c++!" << std::endl;
+    #else
+        std::cout << "Hello from an unrecognised tool chain!" << std::endl;
+    #endif
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/variants/build.gradle b/subprojects/docs/src/samples/native-binaries/variants/build.gradle
new file mode 100644
index 0000000..96508a0
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/variants/build.gradle
@@ -0,0 +1,76 @@
+apply plugin: 'cpp'
+
+// START SNIPPET platforms
+model {
+    platforms {
+        x86 {
+            architecture "x86"
+        }
+        x64 {
+            architecture "x86_64"
+        }
+        itanium {
+            architecture "ia-64"
+        }
+    }
+}
+// END SNIPPET platforms
+
+// START SNIPPET build-types
+model {
+    buildTypes {
+        debug
+        release
+    }
+}
+// END SNIPPET build-types
+
+// START SNIPPET target-platforms
+executables {
+    main {
+        targetPlatforms "x86", "x64"
+    }
+}
+// END SNIPPET target-platforms
+libraries {
+    hello {}
+}
+sources.main.cpp.lib libraries.hello.static
+
+// Apply arguments for debug binaries (these options are not yet set automatically)
+// START SNIPPET build-type-config
+binaries.all {
+    if (toolChain in Gcc && buildType == buildTypes.debug) {
+        cppCompiler.args "-g"
+    }
+    if (toolChain in VisualCpp && buildType == buildTypes.debug) {
+        cppCompiler.args '/Zi'
+        cppCompiler.define 'DEBUG'
+        linker.args '/DEBUG'
+    }
+}
+// END SNIPPET build-type-config
+
+// Apply custom arguments for different target platforms
+binaries.all {
+    if (toolChain in Gcc && targetPlatform == platforms.x86) {
+        cppCompiler.args '-O3'
+    }
+}
+
+// Tasks to build all binaries for a tool chain
+task gccExecutables {
+    dependsOn binaries.withType(ExecutableBinary).matching {
+        it.toolChain in Gcc
+    }
+}
+task visualCppExecutables {
+    dependsOn binaries.withType(ExecutableBinary).matching {
+        it.toolChain in VisualCpp
+    }
+}
+task buildExecutables {
+    dependsOn binaries.withType(ExecutableBinary).matching {
+        it.buildable
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/variants/src/hello/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/variants/src/hello/cpp/hello.cpp
new file mode 100755
index 0000000..733d15a
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/variants/src/hello/cpp/hello.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+#include "hello.h"
+
+void LIB_FUNC hello () {
+  #ifdef FRENCH
+  std::cout << "Bonjour monde!" << std::endl;
+  #else
+  std::cout << "Hello world!" << std::endl;
+  #endif
+}
diff --git a/subprojects/docs/src/samples/native-binaries/variants/src/hello/headers/hello.h b/subprojects/docs/src/samples/native-binaries/variants/src/hello/headers/hello.h
new file mode 100755
index 0000000..79b608b
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/variants/src/hello/headers/hello.h
@@ -0,0 +1,10 @@
+#ifdef _WIN32
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#endif
+#endif
+#ifndef LIB_FUNC
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/variants/src/main/cpp/main.cpp
similarity index 100%
copy from subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
copy to subprojects/docs/src/samples/native-binaries/variants/src/main/cpp/main.cpp
diff --git a/subprojects/docs/src/samples/native-binaries/visual-studio/build.gradle b/subprojects/docs/src/samples/native-binaries/visual-studio/build.gradle
new file mode 100644
index 0000000..38f7465
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/visual-studio/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'cpp'
+// START SNIPPET apply-plugin
+apply plugin: 'visual-studio'
+// END SNIPPET apply-plugin
+
+// START SNIPPET configure-locations
+model {
+    visualStudio {
+        projects.all {
+            projectFile.location = "vs/${name}.vcxproj"
+            filtersFile.location = "vs/${name}.vcxproj.filters"
+        }
+        solutions.all {
+            solutionFile.location = "vs/${name}.sln"
+        }
+    }
+}
+// END SNIPPET configure-locations
+
+// START SNIPPET customize-project-files
+model {
+    visualStudio {
+        projects.all { project ->
+            projectFile.withXml {
+                asNode().appendNode('PropertyGroup', [Label: 'Custom'])
+                        .appendNode('ProjectDetails', "Project is named ${project.name}")
+            }
+        }
+    }
+}
+// END SNIPPET customize-project-files
+// START SNIPPET customize-solution-files
+model {
+    visualStudio {
+        solutions.all { solution ->
+            solutionFile.withContent { content ->
+                def sourceControlSection = """
+    GlobalSection(SolutionNotes) = postSolution
+        NumNotes = 2
+        Name1 = FirstNote
+        Issue1 = N
+        Text1 = This is a shared note.
+        Name2 = SecondNote
+        Issue2 = N
+        Text2 = The projects in this solution are ${solution.projects*.name}.
+    EndGlobalSection
+"""
+                def insertPos = content.text.lastIndexOf("EndGlobal")
+                content.asBuilder().insert(insertPos, sourceControlSection)
+            }
+        }
+    }
+}
+// END SNIPPET customize-solution-files
+
+executables {
+    main {}
+}
+libraries {
+    hello {}
+}
+sources.main.cpp.lib libraries.hello
+
+// For any shared library binaries built with Visual C++, define the DLL_EXPORT macro
+binaries.withType(SharedLibraryBinary) {
+    if (toolChain in VisualCpp) {
+        cppCompiler.define "DLL_EXPORT"
+    }
+}
+
+
diff --git a/subprojects/docs/src/samples/native-binaries/visual-studio/src/hello/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/visual-studio/src/hello/cpp/hello.cpp
new file mode 100755
index 0000000..dc7b045
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/visual-studio/src/hello/cpp/hello.cpp
@@ -0,0 +1,6 @@
+#include <iostream>
+#include "hello.h"
+
+void LIB_FUNC hello () {
+  std::cout << "Hello world!" << std::endl;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/visual-studio/src/hello/headers/hello.h b/subprojects/docs/src/samples/native-binaries/visual-studio/src/hello/headers/hello.h
new file mode 100755
index 0000000..51b48fd
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/visual-studio/src/hello/headers/hello.h
@@ -0,0 +1,7 @@
+#ifdef DLL_EXPORT
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/visual-studio/src/main/cpp/main.cpp
similarity index 100%
copy from subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
copy to subprojects/docs/src/samples/native-binaries/visual-studio/src/main/cpp/main.cpp
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/build-resource-only-dll.gradle b/subprojects/docs/src/samples/native-binaries/windows-resources/build-resource-only-dll.gradle
new file mode 100644
index 0000000..33a820c
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/build-resource-only-dll.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'windows-resources'
+
+// START SNIPPET resource-only-library
+libraries {
+    helloRes {
+        binaries.all {
+            rcCompiler.args "/v"
+            linker.args "/noentry", "/machine:x86"
+        }
+    }
+}
+// END SNIPPET resource-only-library
+
+// START SNIPPET windows-resource-set
+sources {
+    helloRes {
+        rc {
+            source {
+                srcDirs "src/hello/rc"
+            }
+            exportedHeaders {
+                srcDirs "src/hello/headers"
+            }
+        }
+    }
+}
+// END SNIPPET windows-resource-set
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/build.gradle b/subprojects/docs/src/samples/native-binaries/windows-resources/build.gradle
new file mode 100644
index 0000000..3a44053
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'cpp'
+// START SNIPPET apply-plugin
+apply plugin: 'windows-resources'
+// END SNIPPET apply-plugin
+
+libraries {
+    hello {}
+}
+
+executables {
+    main {}
+}
+
+sources {
+    main {
+        cpp {
+            lib libraries.hello
+        }
+    }
+}
+
+binaries.all {
+    linker.args "user32.lib"
+}
+
+// For any shared library binaries built with Visual C++, define the DLL_EXPORT macro
+binaries.withType(SharedLibraryBinary) {
+    if (toolChain in VisualCpp) {
+        cppCompiler.define "DLL_EXPORT"
+    }
+}
+
+
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/cpp/hello.cpp
new file mode 100755
index 0000000..6c2f01e
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/cpp/hello.cpp
@@ -0,0 +1,19 @@
+#include <iostream>
+#include <windows.h>
+#include <string>
+#include "hello.h"
+#include "resources.h"
+
+std::string LoadStringFromResource(UINT stringID)
+{
+    HINSTANCE instance = GetModuleHandle("hello");
+    WCHAR * pBuf = NULL;
+    int len = LoadStringW(instance, stringID, reinterpret_cast<LPWSTR>(&pBuf), 0);
+    std::wstring wide = std::wstring(pBuf, len);
+    return std::string(wide.begin(), wide.end());
+}
+
+void DLL_FUNC hello() {
+    std::string hello = LoadStringFromResource(IDS_HELLO);
+    std::cout << hello << std::endl;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/headers/hello.h b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/headers/hello.h
new file mode 100755
index 0000000..e6507ad
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/headers/hello.h
@@ -0,0 +1,7 @@
+#ifdef DLL_EXPORT
+#define DLL_FUNC __declspec(dllexport)
+#else
+#define DLL_FUNC
+#endif
+
+void DLL_FUNC hello();
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/headers/resources.h b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/headers/resources.h
new file mode 100644
index 0000000..47143e2
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/headers/resources.h
@@ -0,0 +1 @@
+#define IDS_HELLO    111
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/rc/resources.rc b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/rc/resources.rc
new file mode 100644
index 0000000..e9b7aac
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/src/hello/rc/resources.rc
@@ -0,0 +1,6 @@
+#include "resources.h"
+
+STRINGTABLE
+{
+    IDS_HELLO,   "Hello world!"
+} 
diff --git a/subprojects/docs/src/samples/native-binaries/windows-resources/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/windows-resources/src/main/cpp/main.cpp
new file mode 100644
index 0000000..7f1b16d
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/windows-resources/src/main/cpp/main.cpp
@@ -0,0 +1,6 @@
+#include "hello.h"
+
+int main () {
+    hello();
+    return 0;
+}
diff --git a/subprojects/docs/src/samples/osgi/build.gradle b/subprojects/docs/src/samples/osgi/build.gradle
index dba106b..16c7cec 100644
--- a/subprojects/docs/src/samples/osgi/build.gradle
+++ b/subprojects/docs/src/samples/osgi/build.gradle
@@ -14,8 +14,8 @@ repositories {
 }
 
 dependencies {
-    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.10'
-    compile group: 'org.eclipse', name: 'osgi', version: '3.5.0.v20090520'
+    compile 'org.codehaus.groovy:groovy-all:2.2.0'
+    compile 'org.eclipse:osgi:3.5.0.v20090520'
 }
 
 jar {
diff --git a/subprojects/docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy b/subprojects/docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy
index a5116ff..15f0e0d 100644
--- a/subprojects/docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy
+++ b/subprojects/docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy
@@ -3,11 +3,6 @@ package org.gradle
 import org.osgi.framework.BundleActivator
 import org.osgi.framework.BundleContext
 
-/**
-* A sample OSGi Activator written in Groovy.
-*
-* @author Hamlet D'Arcy
-*/ 
 public class GradleActivator implements BundleActivator {
 
     public void start(BundleContext context) {
diff --git a/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle b/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle
index f79cf7c..1d43c6c 100644
--- a/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle
+++ b/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle
@@ -6,8 +6,8 @@ sonarRunner {
         property "sonar.host.url", "http://my.server.com"
         property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
         property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
-        property "sonar.username", "Fred Flintstone"
-        property "sonar.password", "very clever"
+        property "sonar.jdbc.username", "Fred Flintstone"
+        property "sonar.jdbc.password", "very clever"
     }
 }
 // END SNIPPET global-configuration-settings
diff --git a/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle b/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle
index c52cf83..10e7a73 100644
--- a/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle
@@ -10,8 +10,8 @@ sonarRunner {
         property "sonar.host.url", "http://my.server.com"
         property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
         property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
-        property "sonar.username", "Fred Flintstone"
-        property "sonar.password", "very clever"
+        property "sonar.jdbc.username", "Fred Flintstone"
+        property "sonar.jdbc.password", "very clever"
     }
 }
 // END SNIPPET connection-settings
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/src/main/cpp/library/cpp/main.cpp
similarity index 100%
rename from subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
rename to subprojects/docs/src/samples/src/main/cpp/library/cpp/main.cpp
diff --git a/subprojects/docs/src/samples/testing/filtering/build.gradle b/subprojects/docs/src/samples/testing/filtering/build.gradle
new file mode 100644
index 0000000..eb4ce7c
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/filtering/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.11'
+}
+
+// START SNIPPET test-filtering
+test {
+    filter {
+        //include specific method in any of the tests
+        includeTestsMatching "*UiCheck"
+
+        //include all tests from package
+        includeTestsMatching "org.gradle.internal.*"
+
+        //include all integration tests
+        includeTestsMatching "*IntegTest"
+    }
+}
+// END SNIPPET test-filtering
diff --git a/subprojects/docs/src/samples/testing/filtering/src/test/java/SomeIntegTest.java b/subprojects/docs/src/samples/testing/filtering/src/test/java/SomeIntegTest.java
new file mode 100644
index 0000000..a6a8fda
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/filtering/src/test/java/SomeIntegTest.java
@@ -0,0 +1,6 @@
+import org.junit.*;
+
+public class SomeIntegTest {
+    @Test public void test1() {}
+    @Test public void test2() {}
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/filtering/src/test/java/SomeOtherTest.java b/subprojects/docs/src/samples/testing/filtering/src/test/java/SomeOtherTest.java
new file mode 100644
index 0000000..2598cfd
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/filtering/src/test/java/SomeOtherTest.java
@@ -0,0 +1,6 @@
+import org.junit.*;
+
+public class SomeOtherTest {
+    @Test public void quickUiCheck() {}
+    @Test public void quickServerCheck() {}
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/jacoco/application/build.gradle b/subprojects/docs/src/samples/testing/jacoco/application/build.gradle
new file mode 100644
index 0000000..943de86
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/jacoco/application/build.gradle
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// START SNIPPET application-configuration
+apply plugin: "application"
+apply plugin: "jacoco"
+
+mainClassName = "org.gradle.MyMain"
+
+jacoco {
+    applyTo run
+}
+
+task applicationCodeCoverageReport(type:JacocoReport){
+    executionData run
+    sourceSets sourceSets.main
+}
+// END SNIPPET application-configuration
+
+repositories {
+    mavenCentral()
+}
diff --git a/subprojects/docs/src/samples/testing/jacoco/application/src/main/java/org/gradle/MyMain.java b/subprojects/docs/src/samples/testing/jacoco/application/src/main/java/org/gradle/MyMain.java
new file mode 100644
index 0000000..8b2b531
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/jacoco/application/src/main/java/org/gradle/MyMain.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import java.lang.String;
+
+public class MyMain{
+
+    public static void main(String... args){
+        new MyMain().someMethod();
+    }
+
+    private void someMethod(){
+        System.out.println("Some output from 'MyMain'");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle b/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle
new file mode 100644
index 0000000..0c41485
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+
+// START SNIPPET apply-plugin
+apply plugin: "jacoco"
+// END SNIPPET apply-plugin
+
+// START SNIPPET jacoco-configuration
+jacoco {
+    toolVersion = "0.6.2.201302030002"
+    reportsDir = file("$buildDir/customJacocoReportDir")
+}
+// END SNIPPET jacoco-configuration
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.+"
+}
+
+// START SNIPPET testtask-configuration
+test {
+    jacoco {
+        append = false
+        destinationFile = file("$buildDir/jacoco/jacocoTest.exec")
+        classDumpFile = file("$buildDir/jacoco/classpathdumps")
+    }
+}
+// END SNIPPET testtask-configuration
+
+
+// START SNIPPET report-configuration
+jacocoTestReport {
+    reports {
+        xml.enabled false
+        csv.enabled false
+        html.destination "${buildDir}/jacocoHtml"
+    }
+}
+// END SNIPPET report-configuration
diff --git a/subprojects/docs/src/samples/testing/jacoco/quickstart/src/main/java/org/gradle/Person.java b/subprojects/docs/src/samples/testing/jacoco/quickstart/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..9c40af9
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/jacoco/quickstart/src/main/java/org/gradle/Person.java
@@ -0,0 +1,24 @@
+package org.gradle;
+
+import java.lang.String;
+
+public class Person{
+    String surname;
+    int age;
+
+    public String getSurname() {
+        return surname;
+    }
+
+    public void setSurname(String surname) {
+        this.surname = surname;
+    }
+
+    public int getAge() {
+        return age;
+    }
+
+    public void setAge(int age) {
+        this.age = age;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/jacoco/quickstart/src/test/java/org/gradle/PersonTest.java b/subprojects/docs/src/samples/testing/jacoco/quickstart/src/test/java/org/gradle/PersonTest.java
new file mode 100644
index 0000000..7864bd8
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/jacoco/quickstart/src/test/java/org/gradle/PersonTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.Before;
+
+import static org.junit.Assert.assertEquals;
+
+public class PersonTest{
+
+    Person person;
+    @Before public void setup(){
+        person = new Person();
+    }
+
+    @Test public void testAge() {
+        person.setAge(30);
+        assertEquals(30, person.getAge());
+    }
+
+
+    @Test public void testSurname() {
+        person.setSurname("Duke");
+        assertEquals("Duke", person.getSurname());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/junit/categories/build.gradle b/subprojects/docs/src/samples/testing/junit/categories/build.gradle
new file mode 100644
index 0000000..457a88b
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/junit/categories/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.11'
+}
+
+// START SNIPPET test-categories
+test {
+    useJUnit {
+        includeCategories 'org.gradle.junit.CategoryA'
+        excludeCategories 'org.gradle.junit.CategoryB'
+    }
+}
+// END SNIPPET test-categories
diff --git a/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategorizedJUnitTest.java b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategorizedJUnitTest.java
new file mode 100644
index 0000000..f30aa5f
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategorizedJUnitTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.junit;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+public class CategorizedJUnitTest {
+
+    @Category(CategoryA.class)
+    @Test
+    public void a() {
+        System.out.println("hello from CategorizedTest a.");
+    }
+
+    @Category(CategoryB.class)
+    @Test
+    public void b() {
+        System.out.println("hello from CategorizedTest b.");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategoryA.java b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategoryA.java
new file mode 100644
index 0000000..d80ba09
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategoryA.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.junit;
+
+public interface CategoryA{
+
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategoryB.java b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategoryB.java
new file mode 100644
index 0000000..e10eba9
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/CategoryB.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.junit;
+
+public interface CategoryB{
+
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/SimpleJUnitTest.java b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/SimpleJUnitTest.java
new file mode 100644
index 0000000..03588b4
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/junit/categories/src/test/java/org/gradle/junit/SimpleJUnitTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.junit;
+
+import org.junit.Test;
+import java.lang.System;
+
+public class SimpleJUnitTest {
+    @Test
+    public void ok() {
+        System.out.println("hello from SimpleJUnitTest.");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/testing/testReport/build.gradle b/subprojects/docs/src/samples/testing/testReport/build.gradle
index c9cdcf3..fb83a7d 100644
--- a/subprojects/docs/src/samples/testing/testReport/build.gradle
+++ b/subprojects/docs/src/samples/testing/testReport/build.gradle
@@ -14,7 +14,7 @@ subprojects {
 // START SNIPPET test-report
     // Disable the test report for the individual test task
     test {
-        testReport = false
+        reports.html.enabled = false
     }
 }
 
diff --git a/subprojects/docs/src/samples/testing/testng/groups/build.gradle b/subprojects/docs/src/samples/testing/testng/groups/build.gradle
new file mode 100644
index 0000000..d9bc57a
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testng/groups/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:6.3.1'
+}
+
+// START SNIPPET test-config
+test {
+    useTestNG {
+        excludeGroups 'integrationTests'
+        includeGroups 'unitTests'
+    }
+}
+// END SNIPPET test-config
diff --git a/subprojects/docs/src/samples/testing/testng/groups/src/test/java/org/gradle/testng/SimpleIntegrationTest.java b/subprojects/docs/src/samples/testing/testng/groups/src/test/java/org/gradle/testng/SimpleIntegrationTest.java
new file mode 100644
index 0000000..0fab222
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testng/groups/src/test/java/org/gradle/testng/SimpleIntegrationTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testng;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.annotations.AfterMethod;
+import static org.testng.Assert.*;
+
+public class SimpleIntegrationTest{
+    @Test(groups = { "integrationTests" })
+    public void simpleIntegrationTest(){
+        assertEquals(true, true);
+    }
+}
diff --git a/subprojects/docs/src/samples/testing/testng/groups/src/test/java/org/gradle/testng/SimpleUnitTest.java b/subprojects/docs/src/samples/testing/testng/groups/src/test/java/org/gradle/testng/SimpleUnitTest.java
new file mode 100644
index 0000000..8311ca9
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testng/groups/src/test/java/org/gradle/testng/SimpleUnitTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testng;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.annotations.AfterMethod;
+import static org.testng.Assert.*;
+
+public class SimpleUnitTest{
+    @Test(groups = { "unitTests" })
+    public void simpleUnitTest(){
+        assertEquals(true, true);
+    }
+}
diff --git a/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
index 06503c7..27641b2 100644
--- a/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
+++ b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
@@ -1,8 +1,5 @@
 package org.gradle.testng;
 
-/**
- * @author Tom Eyckmans
- */
 public interface User
 {
     String getFirstName();
diff --git a/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
index eb7bd74..96df209 100644
--- a/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
+++ b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
@@ -1,8 +1,5 @@
 package org.gradle.testng;
 
-/**
- * @author Tom Eyckmans
- */
 public class UserImpl implements User
 {
     private final String firstName;
diff --git a/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
index 99deeb8..2127fbb 100644
--- a/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
+++ b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
@@ -5,9 +5,6 @@ import org.testng.annotations.Test;
 import org.testng.annotations.AfterMethod;
 import static org.testng.Assert.*;
 
-/**
- * @author Tom Eyckmans
- */
 public class UserImplTest
 {
     private UserImpl user;
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/plugin/build.gradle b/subprojects/docs/src/samples/toolingApi/customModel/plugin/build.gradle
new file mode 100644
index 0000000..36078ff
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/plugin/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'java'
+apply plugin: 'ivy-publish'
+
+group 'org.gradle.sample'
+version '1.0'
+
+dependencies {
+    compile gradleApi()
+}
+
+publishing {
+    repositories {
+        ivy { url 'build/repo' }
+    }
+    publications {
+        ivy(IvyPublication) {
+            from components.java
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/CustomModel.java b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/CustomModel.java
new file mode 100644
index 0000000..efa736a
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/CustomModel.java
@@ -0,0 +1,14 @@
+package org.gradle.sample.plugin;
+
+import java.util.List;
+import org.gradle.tooling.model.Model;
+import org.gradle.tooling.model.DomainObjectSet;
+
+/**
+ * This is a custom tooling model. It must be assignable to {@link Model} and it must be an interface.
+ */
+public interface CustomModel extends Model {
+    String getName();
+
+    DomainObjectSet<String> getTasks();
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/CustomPlugin.java b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/CustomPlugin.java
new file mode 100644
index 0000000..18a4610
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/CustomPlugin.java
@@ -0,0 +1,39 @@
+package org.gradle.sample.plugin;
+
+import javax.inject.Inject;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+/**
+ * A plugin that exposes a custom tooling model.
+ */
+public class CustomPlugin implements Plugin<Project> {
+    private final ToolingModelBuilderRegistry registry;
+
+    /**
+     * Need to use a {@link ToolingModelBuilderRegistry} to register the custom tooling model, so inject this into
+     * the constructor.
+     */
+    @Inject
+    public CustomPlugin(ToolingModelBuilderRegistry registry) {
+        this.registry = registry;
+    }
+
+    public void apply(Project project) {
+        // Register a builder for the custom tooling model
+        registry.register(new CustomToolingModelBuilder());
+    }
+
+    private static class CustomToolingModelBuilder implements ToolingModelBuilder {
+        public boolean canBuild(String modelName) {
+            // The default name for a model is the name of the Java interface
+            return modelName.equals(CustomModel.class.getName());
+        }
+
+        public Object buildAll(String modelName, Project project) {
+            return new DefaultModel();
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/DefaultModel.java b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/DefaultModel.java
new file mode 100644
index 0000000..99d533b
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/java/org/gradle/sample/plugin/DefaultModel.java
@@ -0,0 +1,20 @@
+package org.gradle.sample.plugin;
+
+import java.io.Serializable;
+import java.lang.String;
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * This is the implementation of the custom tooling model. It must be serializable and must have methods and properties that
+ * are compatible with the custom tooling model interface. It may or may not implement the custom tooling model interface.
+ */
+public class DefaultModel implements Serializable {
+    public String getName() {
+        return "name";
+    }
+
+    public List<String> getTasks() {
+        return Arrays.asList(":a", ":b", ":c");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/resources/META-INF/gradle-plugins/custom-plugin.properties b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/resources/META-INF/gradle-plugins/custom-plugin.properties
new file mode 100644
index 0000000..451e4ed
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/plugin/src/main/resources/META-INF/gradle-plugins/custom-plugin.properties
@@ -0,0 +1 @@
+implementation-class: org.gradle.sample.plugin.CustomPlugin
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/readme.xml b/subprojects/docs/src/samples/toolingApi/customModel/readme.xml
new file mode 100644
index 0000000..664199f
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A sample of how a plugin can expose its own custom tooling model to tooling API clients.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/sampleBuild/build.gradle b/subprojects/docs/src/samples/toolingApi/customModel/sampleBuild/build.gradle
new file mode 100644
index 0000000..273de5c
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/sampleBuild/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+    repositories {
+        ivy { url '../plugin/build/repo' }
+    }
+    dependencies {
+        classpath 'org.gradle.sample:plugin:1.0'
+    }
+}
+
+apply plugin: 'custom-plugin'
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/sampleBuild/settings.gradle b/subprojects/docs/src/samples/toolingApi/customModel/sampleBuild/settings.gradle
new file mode 100644
index 0000000..0f1d7f3
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/sampleBuild/settings.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/tooling/build.gradle b/subprojects/docs/src/samples/toolingApi/customModel/tooling/build.gradle
new file mode 100644
index 0000000..42a9952
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/tooling/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'application'
+
+def toolingApiVersion = gradle.gradleVersion
+
+repositories {
+    ivy {
+        url '../plugin/build/repo'
+    }
+    maven {
+        url 'http://repo.gradle.org/gradle/libs-releases-local'
+    }
+    mavenCentral()
+}
+
+dependencies {
+    compile 'org.gradle.sample:plugin:1.0'
+    compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
+    // Need an SLF4J implementation at runtime
+    runtime 'org.slf4j:slf4j-simple:1.7.5'
+}
+
+mainClassName = 'org.gradle.sample.Main'
diff --git a/subprojects/docs/src/samples/toolingApi/customModel/tooling/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/customModel/tooling/src/main/java/org/gradle/sample/Main.java
new file mode 100644
index 0000000..2550f2b
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/customModel/tooling/src/main/java/org/gradle/sample/Main.java
@@ -0,0 +1,39 @@
+package org.gradle.sample;
+
+import org.gradle.tooling.*;
+import org.gradle.sample.plugin.CustomModel;
+
+import java.io.File;
+import java.lang.Object;
+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();
+
+        if (args.length > 0) {
+            connector.useInstallation(new File(args[0]));
+            if (args.length > 1) {
+                connector.useGradleUserHomeDir(new File(args[1]));
+            }
+        }
+
+        connector.forProjectDirectory(new File("../sampleBuild"));
+
+        ProjectConnection connection = connector.connect();
+        try {
+            // Load the custom model for the project
+            CustomModel model = connection.getModel(CustomModel.class);
+            System.out.println("Project: " + model.getName());
+            System.out.println("Tasks: ");
+            for (String task : model.getTasks()) {
+                System.out.println("   " + task);
+            }
+        } 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 ede7af4..ae4c0fa 100644
--- a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.7.2'
+    runtime 'org.slf4j:slf4j-simple:1.7.5'
 }
 
 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 40e8d48..7458d67 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
@@ -12,7 +12,6 @@ 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]));
@@ -21,9 +20,7 @@ public class Main {
             }
         }
 
-        if (args.length > 0) {
-            connector.useInstallation(new File(args[0]));
-        }
+        connector.forProjectDirectory(new File("."));
 
         ProjectConnection connection = connector.connect();
         try {
diff --git a/subprojects/docs/src/samples/toolingApi/idea/build.gradle b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
index fbfa05a..764c1f7 100644
--- a/subprojects/docs/src/samples/toolingApi/idea/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.7.2'
+    runtime 'org.slf4j:slf4j-simple:1.7.5'
 }
 
 mainClassName = 'org.gradle.sample.Main'
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 f78bce2..65915b0 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
@@ -10,7 +10,6 @@ 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]));
@@ -19,6 +18,8 @@ public class Main {
             }
         }
 
+        connector.forProjectDirectory(new File("."));
+
         ProjectConnection connection = connector.connect();
         try {
             IdeaProject project = connection.getModel(IdeaProject.class);
diff --git a/subprojects/docs/src/samples/toolingApi/model/build.gradle b/subprojects/docs/src/samples/toolingApi/model/build.gradle
index fbfa05a..764c1f7 100644
--- a/subprojects/docs/src/samples/toolingApi/model/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/model/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.7.2'
+    runtime 'org.slf4j:slf4j-simple:1.7.5'
 }
 
 mainClassName = 'org.gradle.sample.Main'
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 4791e4f..32ad551 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
@@ -11,7 +11,6 @@ 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]));
@@ -20,9 +19,7 @@ public class Main {
             }
         }
 
-        if (args.length > 0) {
-            connector.useInstallation(new File(args[0]));
-        }
+        connector.forProjectDirectory(new File("."));
 
         ProjectConnection connection = connector.connect();
         try {
diff --git a/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle b/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
index fbfa05a..764c1f7 100644
--- a/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.7.2'
+    runtime 'org.slf4j:slf4j-simple:1.7.5'
 }
 
 mainClassName = 'org.gradle.sample.Main'
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
index dab318f..406c04d 100644
--- 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
@@ -20,9 +20,6 @@ public class Main {
         }
 
         connector.forProjectDirectory(new File("."));
-        if (args.length > 0) {
-            connector.useInstallation(new File(args[0]));
-        }
 
         ProjectConnection connection = connector.connect();
         try {
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/ant/useAntType/libs/test.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/ant/useAntType/libs/test.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/build.gradle
new file mode 100644
index 0000000..bae2a80
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/build.gradle
@@ -0,0 +1,41 @@
+repositories {
+    ivy {
+        url "$projectDir/repo"
+    }
+}
+
+configurations {
+    config1
+    config2
+    config3
+}
+
+// START SNIPPET latest-selector
+dependencies {
+    config1 "sea.fish:tuna:latest.integration"
+    config2 "sea.fish:tuna:latest.release"
+}
+
+task listFish << {
+    configurations.config1.each { println it.name }
+    println()
+    configurations.config2.each { println it.name}
+}
+// END SNIPPET latest-selector
+
+// START SNIPPET custom-status-scheme
+dependencies {
+    config3 "air.birds:albatros:latest.silver"
+    components {
+        eachComponent { ComponentMetadataDetails details ->
+            if (details.id.group == "air.birds") {
+                details.statusScheme = ["bronze", "silver", "gold", "platinum"]
+            }
+        }
+    }
+}
+
+task listBirds << {
+    configurations.config3.each { println it.name }
+}
+// END SNIPPET custom-status-scheme
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/main/webapp/WEB-INF/webapp.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/1.9/albatros-1.9.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/main/webapp/WEB-INF/webapp.xml
copy to subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/1.9/albatros-1.9.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/1.9/ivy-1.9.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/1.9/ivy-1.9.xml
new file mode 100644
index 0000000..5899994
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/1.9/ivy-1.9.xml
@@ -0,0 +1,9 @@
+<ivy-module version="2.0">
+    <info organisation="air.birds"
+          module="albatros"
+          revision="1.9"
+          status="silver"/>
+    <publications>
+        <artifact />
+    </publications>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/main/webapp/webapp.html b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/2.0/albatros-2.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/main/webapp/webapp.html
copy to subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/2.0/albatros-2.0.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/2.0/ivy-2.0.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/2.0/ivy-2.0.xml
new file mode 100644
index 0000000..d8accfe
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/air.birds/albatros/2.0/ivy-2.0.xml
@@ -0,0 +1,9 @@
+<ivy-module version="2.0">
+    <info organisation="air.birds"
+          module="albatros"
+          revision="2.0"
+          status="gold"/>
+    <publications>
+        <artifact />
+    </publications>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.3/ivy-1.3.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.3/ivy-1.3.xml
new file mode 100644
index 0000000..fc54db0
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.3/ivy-1.3.xml
@@ -0,0 +1,9 @@
+<ivy-module version="2.0">
+    <info organisation="sea.fish"
+          module="tuna"
+          revision="1.3"
+          status="milestone" />
+    <publications>
+        <artifact />
+    </publications>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/rootContent/root.txt b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.3/tuna-1.3.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/rootContent/root.txt
copy to subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.3/tuna-1.3.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.4/ivy-1.4.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.4/ivy-1.4.xml
new file mode 100644
index 0000000..3c13bbe
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.4/ivy-1.4.xml
@@ -0,0 +1,9 @@
+<ivy-module version="2.0">
+    <info organisation="sea.fish"
+          module="tuna"
+          revision="1.4"
+          status="release" />
+    <publications>
+        <artifact />
+    </publications>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.4/tuna-1.4.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.4/tuna-1.4.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.5/ivy-1.5.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.5/ivy-1.5.xml
new file mode 100644
index 0000000..fa235ac
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.5/ivy-1.5.xml
@@ -0,0 +1,9 @@
+<ivy-module version="2.0">
+    <info organisation="sea.fish"
+          module="tuna"
+          revision="1.5"
+          status="integration" />
+    <publications>
+        <artifact />
+    </publications>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.5/tuna-1.5.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/componentMetadata/repo/sea.fish/tuna/1.5/tuna-1.5.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/air.birds/albatros-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/air.birds/albatros-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/herring-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/herring-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/shark-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/shark-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/tuna-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/tuna-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/orca-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/orca-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/seal-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/seal-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/seal-2.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/seal-2.0.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
index a52abdc..0fdcd60 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
@@ -6,6 +6,12 @@ repositories {
 }
 //END SNIPPET maven-central
 
+//START SNIPPET maven-jcenter
+repositories {
+    jcenter()
+}
+//END SNIPPET maven-jcenter
+
 //START SNIPPET maven-central-jar-repo
 repositories {
     mavenCentral name: 'single-jar-repo', artifactUrls: ["http://repo.mycompany.com/jars"]
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.classifiers/service-1.0-jdk14.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.classifiers/service-1.0-jdk14.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.classifiers/service-1.0-jdk15.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.classifiers/service-1.0-jdk15.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/api-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/api-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/commons-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/commons-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/other-api-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/other-api-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/reports-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/reports-1.0.jar
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/shared-1.0.jar
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/shared-1.0.jar
diff --git a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
index 37722a8..6e16edc 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
@@ -59,19 +59,19 @@ dependencies {
 
 //START SNIPPET artifact-only
 dependencies {
-	runtime "org.groovy:groovy:2.0.5 at jar"
-    runtime group: 'org.groovy', name: 'groovy', version: '2.0.5', ext: 'jar'
+	runtime "org.groovy:groovy:2.2.0 at jar"
+    runtime group: 'org.groovy', name: 'groovy', version: '2.2.0', ext: 'jar'
 }
 //END SNIPPET artifact-only
 
 //START SNIPPET client-modules
 dependencies {
-    runtime module("org.codehaus.groovy:groovy-all:2.0.5") {
+    runtime module("org.codehaus.groovy:groovy-all:2.2.0") {
         dependency("commons-cli:commons-cli:1.0") {
             transitive = false
         }
-        module(group: 'org.apache.ant', name: 'ant', version: '1.8.4') {
-            dependencies "org.apache.ant:ant-launcher:1.8.4 at jar", "org.apache.ant:ant-junit:1.8.4"
+        module(group: 'org.apache.ant', name: 'ant', version: '1.9.3') {
+            dependencies "org.apache.ant:ant-launcher:1.9.3 at jar", "org.apache.ant:ant-junit:1.9.3"
         }
     }
 }
@@ -85,9 +85,9 @@ dependencies {
 //END SNIPPET file-dependencies
 
 //START SNIPPET list-grouping
-List groovy = ["org.codehaus.groovy:groovy-all:2.0.5 at jar",
+List groovy = ["org.codehaus.groovy:groovy-all:2.2.0 at jar",
                "commons-cli:commons-cli:1.0 at jar",
-               "org.apache.ant:ant:1.8.4 at jar"]
+               "org.apache.ant:ant:1.9.3 at jar"]
 List hibernate = ['org.hibernate:hibernate:3.0.5 at jar', 'somegroup:someorg:1.0 at jar']
 dependencies {
 	runtime groovy, hibernate
diff --git a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
index ef8e7c0..16bfeeb 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
@@ -56,7 +56,7 @@ configurations.all {
         }
         if (details.requested.name == 'log4j') {
             //prefer 'log4j-over-slf4j' over 'log4j', with fixed version:
-            details.useTarget "org.slf4j:log4j-over-slf4j:1.7.2"
+            details.useTarget "org.slf4j:log4j-over-slf4j:1.7.5"
         }
     }
 }
diff --git a/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle
index 55b4b94..2d4129e 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle
@@ -16,7 +16,7 @@ artifacts {
 }
 //END SNIPPET file-artifact
 
-//START SNIPPET customised-file-artifact
+//START SNIPPET customized-file-artifact
 task myTask(type:  MyTaskType) {
     destFile = file('build/somefile.txt')
 }
@@ -28,7 +28,7 @@ artifacts {
         builtBy myTask
     }
 }
-//END SNIPPET customised-file-artifact
+//END SNIPPET customized-file-artifact
 
 //START SNIPPET map-file-artifact
 task generate(type:  MyTaskType) {
diff --git a/subprojects/docs/src/samples/userguide/distribution/build.gradle b/subprojects/docs/src/samples/userguide/distribution/build.gradle
index 18ab403..e5f30ee 100755
--- a/subprojects/docs/src/samples/userguide/distribution/build.gradle
+++ b/subprojects/docs/src/samples/userguide/distribution/build.gradle
@@ -4,48 +4,24 @@ apply plugin: 'distribution'
 
 version = '1.0.0'
 
-// START SNIPPET name-conf
-distributions {
-    main {
-        baseName = 'my-name'
-    }
-}
-// END SNIPPET name-conf
-
-// START SNIPPET custom-distZip
-apply plugin: 'distribution'
-
-distributions {
-    custom
-}
-
-customDistZip {
-    from "custom/custom.txt"
-}
-// END SNIPPET custom-distZip
-
-// START SNIPPET custom-distribution
+// START SNIPPET configure-distribution
 apply plugin: 'distribution'
 
 distributions {
     main {
         baseName = 'someName'
         contents {
-            from { 'src/dist' }
+            from { 'src/readme' }
         }
     }
 }
-// END SNIPPET custom-distribution
+// END SNIPPET configure-distribution
 
-// START SNIPPET declare-distribution
+// START SNIPPET custom-distribution
 apply plugin: 'distribution'
 
 version = '1.2'
 distributions {
-    custom {
-        contents {
-            from { 'src/dist' }
-        }
-    }
+    custom {}
 }
-// END SNIPPET declare-distribution
+// END SNIPPET custom-distribution
diff --git a/subprojects/docs/src/samples/userguide/files/copy/build.gradle b/subprojects/docs/src/samples/userguide/files/copy/build.gradle
index 22078a3..c1fb029 100644
--- a/subprojects/docs/src/samples/userguide/files/copy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/files/copy/build.gradle
@@ -47,6 +47,20 @@ task copyMethod << {
 }
 // END SNIPPET copy-method
 
+// START SNIPPET copy-method-with-dependency
+task copyMethodWithExplicitDependencies{
+    inputs.file copyTask // up-to-date check for inputs, plus add copyTask as dependency
+    outputs.dir 'some-dir' // up-to-date check for outputs
+    doLast{
+        copy {
+            // Copy the output of copyTask
+            from copyTask
+            into 'some-dir'
+        }
+    }
+}
+// END SNIPPET copy-method-with-dependency
+
 configurations { runtime }
 
 // START SNIPPET nested-specs
@@ -99,4 +113,5 @@ task filter(type: Copy) {
 task test {
     dependsOn tasks.withType(Copy)
     dependsOn copyMethod
+    dependsOn copyMethodWithExplicitDependencies
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/files/copy/src/main/assets.zip b/subprojects/docs/src/samples/userguide/files/copy/src/main/assets.zip
new file mode 100644
index 0000000..79f2b89
Binary files /dev/null and b/subprojects/docs/src/samples/userguide/files/copy/src/main/assets.zip differ
diff --git a/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle b/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle
index ab87fe3..e36b137 100644
--- a/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle
+++ b/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle
@@ -6,16 +6,10 @@ repositories {
 
 // START SNIPPET groovy-test-dependency
 dependencies {
-    testCompile "org.codehaus.groovy:groovy-all:2.0.5"
+    testCompile "org.codehaus.groovy:groovy-all:2.2.0"
 }
 // END SNIPPET groovy-test-dependency
 
-// START SNIPPET groovy-configuration
-dependencies {
-    groovy "org.codehaus.groovy:groovy-all:2.0.5"
-}
-// END SNIPPET groovy-configuration
-
 // START SNIPPET bundled-groovy-dependency
 dependencies {
     compile localGroovy()
diff --git a/subprojects/docs/src/samples/userguide/initScripts/customLogger/init.gradle b/subprojects/docs/src/samples/userguide/initScripts/customLogger/init.gradle
index 3e01609..712857f 100644
--- a/subprojects/docs/src/samples/userguide/initScripts/customLogger/init.gradle
+++ b/subprojects/docs/src/samples/userguide/initScripts/customLogger/init.gradle
@@ -12,5 +12,8 @@ class CustomEventLogger extends BuildAdapter implements TaskExecutionListener {
     
     public void buildFinished(BuildResult result) {
         println 'build completed'
+        if (result.failure != null) {
+            result.failure.printStackTrace()
+        }
     }
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/initScripts/plugins/build.gradle b/subprojects/docs/src/samples/userguide/initScripts/plugins/build.gradle
new file mode 100644
index 0000000..e4fa489
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/initScripts/plugins/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// START SNIPPET show-repos-task
+repositories{
+    mavenCentral()
+}
+
+ task showRepositories << {
+    repositories.each{
+        println "repository: ${it.name} ('${it.url}')"
+    }
+}
+// END SNIPPET show-repos-task
diff --git a/subprojects/docs/src/samples/userguide/initScripts/plugins/init.gradle b/subprojects/docs/src/samples/userguide/initScripts/plugins/init.gradle
new file mode 100644
index 0000000..b9f8f68
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/initScripts/plugins/init.gradle
@@ -0,0 +1,34 @@
+import org.gradle.api.artifacts.repositories.ArtifactRepository
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+
+// START SNIPPET init-script-plugin
+
+apply plugin:EnterpriseRepositoryPlugin
+
+class EnterpriseRepositoryPlugin implements Plugin<Gradle> {
+
+    private static String ENTERPRISE_REPOSITORY_URL = "http://repo.gradle.org/gradle/repo"
+
+    void apply(Gradle gradle) {
+        // ONLY USE ENTERPRISE REPO FOR DEPENDENCIES
+        gradle.allprojects{ project ->
+            project.repositories {
+
+                //remove all repositories not pointing to the enterprise repository url
+                all { ArtifactRepository repo ->
+                    if (!(repo instanceof MavenArtifactRepository) || repo.url.toString() != ENTERPRISE_REPOSITORY_URL) {
+                        project.logger.lifecycle "Repository ${repo.url} removed. Only $ENTERPRISE_REPOSITORY_URL is allowed"
+                        remove repo
+                    }
+                }
+
+                // add the enterprise repository
+                maven {
+                    name "STANDARD_ENTERPRISE_REPO"
+                    url ENTERPRISE_REPOSITORY_URL
+                }
+            }
+        }
+    }
+}
+// END SNIPPET init-script-plugin
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle
index b552cba..a7e89a7 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle
@@ -1,4 +1,3 @@
 task action << {
-    println("Consuming message: " +
-            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+    println("Consuming message: ${rootProject.producerMessage}")
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle
index 9ca9261..8cf6c17 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle
@@ -1,6 +1,6 @@
 evaluationDependsOn(':producer')
 
-message = rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'
+message = rootProject.producerMessage
 
 task consume << {
     println("Consuming message: " + message)
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle
index d36e857..7d89691 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle
@@ -1,4 +1,3 @@
 task consume << {
-    println("Consuming message: " +
-            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+    println("Consuming message: ${rootProject.producerMessage}")
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle
index d1ba74e..c4cef37 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle
@@ -1,4 +1,4 @@
-message = rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'
+message = rootProject.producerMessage
 
 task consume << {
     println("Consuming message: " + message)
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle
index b552cba..a7e89a7 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle
@@ -1,4 +1,3 @@
 task action << {
-    println("Consuming message: " +
-            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+    println("Consuming message: ${rootProject.producerMessage}")
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle
index 8abffac..89bf7d4 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle
@@ -1,4 +1,3 @@
 task consume(dependsOn: ':producer:produce') << {
-    println("Consuming message: " +
-            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+    println("Consuming message: ${rootProject.producerMessage}")
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/build.gradle
new file mode 100644
index 0000000..36e2695
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/build.gradle
@@ -0,0 +1 @@
+ext.producerMessage = null
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
index 2e70b12..4bb5354 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
@@ -1,4 +1,3 @@
 task action(dependsOn: ":producer:action") << {
-    println("Consuming message: " +
-            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+    println("Consuming message: ${rootProject.producerMessage}")
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle b/subprojects/docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle
index e898abc..959a186 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle
@@ -1,5 +1,5 @@
 //START SNIPPET hierarchical-layout
-include 'project1', 'project2', 'project2:child1'
+include 'project1', 'project2:child', 'project3:child1'
 //END SNIPPET hierarchical-layout
 
 //START SNIPPET flat-layout
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle
index 8917df9..736d3f0 100644
--- a/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle
+++ b/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle
@@ -3,7 +3,7 @@ configurations {
 }
 
 dependencies {
-    ftpAntTask("org.apache.ant:ant-commons-net:1.8.4") {
+    ftpAntTask("org.apache.ant:ant-commons-net:1.9.3") {
         module("commons-net:commons-net:1.4.1") {
             dependencies "oro:oro:2.0.8:jar"
         }
diff --git a/subprojects/docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle b/subprojects/docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle
index e8e6a5e..153c1a7 100644
--- a/subprojects/docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle
@@ -1,8 +1,8 @@
-tasks.add(name: 'hello') << {
+tasks.create(name: 'hello') << {
     println "hello"
 }
 
-tasks.add(name: 'copy', type: Copy) {
+tasks.create(name: 'copy', type: Copy) {
     from(file('srcDir'))
     into(buildDir)
 }
diff --git a/subprojects/docs/src/samples/userguide/tasks/configureUsingConfigure/build.gradle b/subprojects/docs/src/samples/userguide/tasks/configureUsingConfigure/build.gradle
deleted file mode 100644
index cbf1a53..0000000
--- a/subprojects/docs/src/samples/userguide/tasks/configureUsingConfigure/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-task myCopy(type: Copy)
-
-myCopy.configure {
-   from('source')
-   into('target')
-   include('**/*.txt', '**/*.xml', '**/*.properties')
-}
diff --git a/subprojects/docs/src/samples/userguide/tasks/configureUsingLiterateStyle/build.gradle b/subprojects/docs/src/samples/userguide/tasks/configureUsingLiterateStyle/build.gradle
deleted file mode 100644
index b713f2e..0000000
--- a/subprojects/docs/src/samples/userguide/tasks/configureUsingLiterateStyle/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-task(myCopy, type: Copy)
-    .from('resources')
-    .into('target')
-    .include('**/*.txt', '**/*.xml', '**/*.properties')
diff --git a/subprojects/docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle b/subprojects/docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle
index 70ae299..b5854db 100644
--- a/subprojects/docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle
@@ -8,7 +8,7 @@ task greeting(type: GreetingTask) {
 }
 
 class GreetingTask extends DefaultTask {
-    def String greeting = 'hello from GreetingTask'
+    String greeting = 'hello from GreetingTask'
 
     @TaskAction
     def greet() {
diff --git a/subprojects/docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle b/subprojects/docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle
index 5a6919a..d7f31f3 100644
--- a/subprojects/docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle
@@ -1,7 +1,7 @@
 // START SNIPPET no-description
 task copy(type: Copy) {
 // END SNIPPET no-description
-   description = 'Copies the resource directory to the target directory.'
+   description 'Copies the resource directory to the target directory.'
 // START SNIPPET no-description
    from 'resources'
    into 'target'
diff --git a/subprojects/docs/src/samples/userguide/tasks/finalizers/build.gradle b/subprojects/docs/src/samples/userguide/tasks/finalizers/build.gradle
new file mode 100644
index 0000000..d0ffc52
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/finalizers/build.gradle
@@ -0,0 +1,8 @@
+task taskX << {
+    println 'taskX'
+}
+task taskY << {
+    println 'taskY'
+}
+
+taskX.finalizedBy taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tasks/finalizersWithFailure/build.gradle b/subprojects/docs/src/samples/userguide/tasks/finalizersWithFailure/build.gradle
new file mode 100644
index 0000000..72a43f4
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/finalizersWithFailure/build.gradle
@@ -0,0 +1,9 @@
+task taskX << {
+    println 'taskX'
+    throw new RuntimeException()
+}
+task taskY << {
+    println 'taskY'
+}
+
+taskX.finalizedBy taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle b/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle
new file mode 100644
index 0000000..bb4b1d1
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle
@@ -0,0 +1,66 @@
+task originalInputs() << {
+    file('inputs').mkdir()
+    file('inputs/1.txt').text = "Content for file 1."
+    file('inputs/2.txt').text = "Content for file 2."
+    file('inputs/3.txt').text = "Content for file 3."
+}
+
+// START SNIPPET updated-inputs
+task updateInputs() << {
+    file('inputs/1.txt').text = "Changed content for existing file 1."
+    file('inputs/4.txt').text = "Content for new file 4."
+}
+// END SNIPPET updated-inputs
+
+// START SNIPPET removed-input
+task removeInput() << {
+    file('inputs/3.txt').delete()
+}
+// END SNIPPET removed-input
+
+// START SNIPPET removed-output
+task removeOutput() << {
+    file("$buildDir/outputs/1.txt").delete()
+}
+// END SNIPPET removed-output
+
+// START SNIPPET reverse
+task incrementalReverse(type: IncrementalReverseTask) {
+    inputDir = file('inputs')
+    outputDir = file("$buildDir/outputs")
+    inputProperty = project.properties['taskInputProperty'] ?: "original"
+}
+// END SNIPPET reverse
+
+// START SNIPPET incremental-task
+class IncrementalReverseTask extends DefaultTask {
+    @InputDirectory
+    def File inputDir
+
+    @OutputDirectory
+    def File outputDir
+
+    @Input
+    def inputProperty
+
+    @TaskAction
+    void execute(IncrementalTaskInputs inputs) {
+        println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date"
+        // START SNIPPET out-of-date-inputs
+        inputs.outOfDate { change ->
+            println "out of date: ${change.file.name}"
+            def targetFile = new File(outputDir, change.file.name)
+            targetFile.text = change.file.text.reverse()
+        }
+        // END SNIPPET out-of-date-inputs
+
+        // START SNIPPET removed-inputs
+        inputs.removed { change ->
+            println "removed: ${change.file.name}"
+            def targetFile = new File(outputDir, change.file.name)
+            targetFile.delete()
+        }
+        // END SNIPPET removed-inputs
+    }
+}
+// END SNIPPET incremental-task
diff --git a/subprojects/docs/src/samples/userguide/tasks/mustRunAfter/build.gradle b/subprojects/docs/src/samples/userguide/tasks/mustRunAfter/build.gradle
new file mode 100644
index 0000000..744b150
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/mustRunAfter/build.gradle
@@ -0,0 +1,7 @@
+task taskX << {
+    println 'taskX'
+}
+task taskY << {
+    println 'taskY'
+}
+taskY.mustRunAfter taskX
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tasks/shouldRunAfter/build.gradle b/subprojects/docs/src/samples/userguide/tasks/shouldRunAfter/build.gradle
new file mode 100644
index 0000000..cc167ca
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/shouldRunAfter/build.gradle
@@ -0,0 +1,7 @@
+task taskX << {
+    println 'taskX'
+}
+task taskY << {
+    println 'taskY'
+}
+taskY.shouldRunAfter taskX
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tasks/shouldRunAfterWithCycle/build.gradle b/subprojects/docs/src/samples/userguide/tasks/shouldRunAfterWithCycle/build.gradle
new file mode 100644
index 0000000..70b2f6f
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/shouldRunAfterWithCycle/build.gradle
@@ -0,0 +1,12 @@
+task taskX << {
+    println 'taskX'
+}
+task taskY << {
+    println 'taskY'
+}
+task taskZ << {
+    println 'taskZ'
+}
+taskX.dependsOn taskY
+taskY.dependsOn taskZ
+taskZ.shouldRunAfter taskX
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
index 63fccdf..1c2366c 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
@@ -1,17 +1,21 @@
 apply plugin: 'groovy'
 
+repositories {
+    mavenCentral()
+}
+
 // START SNIPPET groovy-dependency
 repositories {
     flatDir { dirs 'lib' }
 }
 
 dependencies {
-    groovy module(':groovy:1.6.0') {
+    compile module('org.codehaus.groovy:groovy:1.6.0') {
         dependency('asm:asm-all:2.2.3')
         dependency('antlr:antlr:2.7.7')
         dependency('commons-cli:commons-cli:1.2')
-        module(':ant:1.7.0') {
-            dependencies(':ant-junit:1.7.0:jar', ':ant-launcher:1.7.0')
+        module('org.apache.ant:ant:1.9.3') {
+            dependencies('org.apache.ant:ant-junit:1.9.3 at jar', 'org.apache.ant:ant-launcher:1.9.3')
         }
     }
 }
diff --git a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
index 419149a..acdab3a 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
@@ -64,7 +64,7 @@ project(':api') {
 description = 'The shared API for the application'
 // END SNIPPET project-description
     dependencies {
-        compile "org.codehaus.groovy:groovy-all:2.0.5"
+        compile "org.codehaus.groovy:groovy-all:2.2.0"
         testCompile "junit:junit:4.11"
     }
 }
diff --git a/subprojects/docs/src/samples/userguide/tutorial/properties/gradle.properties b/subprojects/docs/src/samples/userguide/tutorial/properties/gradle.properties
index 1bd056f..898b668 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
+systemProjectProp=shouldBeOverWrittenBySystemProp
 envProjectProp=shouldBeOverWrittenByEnvProp
 systemProp.system=systemValue
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/wrapper/customized/build.gradle b/subprojects/docs/src/samples/userguide/wrapper/customized/build.gradle
deleted file mode 100644
index eed403b..0000000
--- a/subprojects/docs/src/samples/userguide/wrapper/customized/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-task wrapper(type: Wrapper) {
-    gradleVersion = '0.9'
-    jarFile = 'wrapper/wrapper.jar'
-}
diff --git a/subprojects/docs/src/samples/userguide/wrapper/simple/build.gradle b/subprojects/docs/src/samples/userguide/wrapper/simple/build.gradle
index ad082e6..e9e26b3 100644
--- a/subprojects/docs/src/samples/userguide/wrapper/simple/build.gradle
+++ b/subprojects/docs/src/samples/userguide/wrapper/simple/build.gradle
@@ -1,3 +1,3 @@
 task wrapper(type: Wrapper) {
-    gradleVersion = '0.9'
+    gradleVersion = '1.4'
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out b/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
index 45cee23..520ccb1 100644
--- a/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
+++ b/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
@@ -1,5 +1,5 @@
 orca-1.0.jar
 shark-1.0.jar
 tuna-1.0.jar
-seal-2.0.jar
-herring-1.0.jar
\ No newline at end of file
+herring-1.0.jar
+seal-2.0.jar
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/customStatusScheme.out b/subprojects/docs/src/samples/userguideOutput/customStatusScheme.out
new file mode 100644
index 0000000..79e65a6
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/customStatusScheme.out
@@ -0,0 +1 @@
+albatros-2.0.jar
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out b/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
index 117017d..6d63730 100644
--- a/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
@@ -1,3 +1,3 @@
-org.codehaus.groovy:groovy-all:2.0.5
-\--- projectReports:api:1.0-SNAPSHOT
+org.codehaus.groovy:groovy-all:2.2.0
+\--- project :api
      \--- 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 94f2c4f..2572e33 100644
--- a/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
@@ -10,7 +10,7 @@ Project :api - The shared API for the application
 ------------------------------------------------------------
 
 compile
-\--- org.codehaus.groovy:groovy-all:2.0.5
+\--- org.codehaus.groovy:groovy-all:2.2.0
 
 testCompile
 \--- junit:junit:4.11
@@ -21,8 +21,8 @@ Project :webapp - The Web application implementation
 ------------------------------------------------------------
 
 compile
-+--- projectReports:api:1.0-SNAPSHOT
-|    \--- org.codehaus.groovy:groovy-all:2.0.5
++--- project :api
+|    \--- org.codehaus.groovy:groovy-all:2.2.0
 \--- commons-io:commons-io:1.2
 
 testCompile
diff --git a/subprojects/docs/src/samples/userguideOutput/externalDependencies.out b/subprojects/docs/src/samples/userguideOutput/externalDependencies.out
index 65061e7..bd07146 100644
--- a/subprojects/docs/src/samples/userguideOutput/externalDependencies.out
+++ b/subprojects/docs/src/samples/userguideOutput/externalDependencies.out
@@ -2,7 +2,7 @@ hibernate-core-3.6.7.Final.jar
 antlr-2.7.6.jar
 commons-collections-3.1.jar
 dom4j-1.6.1.jar
-slf4j-api-1.6.1.jar
 hibernate-commons-annotations-3.2.0.Final.jar
 hibernate-jpa-2.0-api-1.0.1.Final.jar
-jta-1.1.jar
\ No newline at end of file
+jta-1.1.jar
+slf4j-api-1.6.1.jar
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/incrementalTaskChangedProperty.out b/subprojects/docs/src/samples/userguideOutput/incrementalTaskChangedProperty.out
new file mode 100644
index 0000000..9e20258
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/incrementalTaskChangedProperty.out
@@ -0,0 +1,4 @@
+ALL inputs considered out of date
+out of date: 1.txt
+out of date: 2.txt
+out of date: 3.txt
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/incrementalTaskFirstRun.out b/subprojects/docs/src/samples/userguideOutput/incrementalTaskFirstRun.out
new file mode 100644
index 0000000..9e20258
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/incrementalTaskFirstRun.out
@@ -0,0 +1,4 @@
+ALL inputs considered out of date
+out of date: 1.txt
+out of date: 2.txt
+out of date: 3.txt
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/userguideOutput/incrementalTaskNoChange.out
similarity index 100%
copy from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
copy to subprojects/docs/src/samples/userguideOutput/incrementalTaskNoChange.out
diff --git a/subprojects/docs/src/samples/userguideOutput/incrementalTaskRemovedInput.out b/subprojects/docs/src/samples/userguideOutput/incrementalTaskRemovedInput.out
new file mode 100644
index 0000000..73aaed0
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/incrementalTaskRemovedInput.out
@@ -0,0 +1,2 @@
+CHANGED inputs considered out of date
+removed: 3.txt
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/incrementalTaskRemovedOutput.out b/subprojects/docs/src/samples/userguideOutput/incrementalTaskRemovedOutput.out
new file mode 100644
index 0000000..9e20258
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/incrementalTaskRemovedOutput.out
@@ -0,0 +1,4 @@
+ALL inputs considered out of date
+out of date: 1.txt
+out of date: 2.txt
+out of date: 3.txt
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/incrementalTaskUpdatedInputs.out b/subprojects/docs/src/samples/userguideOutput/incrementalTaskUpdatedInputs.out
new file mode 100644
index 0000000..ad2dc90
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/incrementalTaskUpdatedInputs.out
@@ -0,0 +1,3 @@
+CHANGED inputs considered out of date
+out of date: 1.txt
+out of date: 4.txt
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/latestSelector.out b/subprojects/docs/src/samples/userguideOutput/latestSelector.out
new file mode 100644
index 0000000..979e2ef
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/latestSelector.out
@@ -0,0 +1,3 @@
+tuna-1.5.jar
+
+tuna-1.4.jar
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/mustRunAfter.out b/subprojects/docs/src/samples/userguideOutput/mustRunAfter.out
new file mode 100644
index 0000000..6c41820
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/mustRunAfter.out
@@ -0,0 +1,2 @@
+taskX
+taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/mustRunAfterSingleTask.out b/subprojects/docs/src/samples/userguideOutput/mustRunAfterSingleTask.out
new file mode 100644
index 0000000..0c11d0d
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/mustRunAfterSingleTask.out
@@ -0,0 +1 @@
+taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out
index a7c07c3..10031f7 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out
@@ -1,4 +1,4 @@
-:generateIvyCustomIvyModuleDescriptor
+:generateDescriptorFileForIvyCustomPublication
 
 BUILD SUCCESSFUL
 
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
index 9bd41b5..c9ae2f4 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
@@ -1,4 +1,4 @@
-:generateIvyJavaIvyModuleDescriptor
+:generateDescriptorFileForIvyJavaPublication
 :compileJava UP-TO-DATE
 :processResources UP-TO-DATE
 :classes UP-TO-DATE
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
index 74218e8..b48f4fe 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
@@ -1,4 +1,4 @@
-:generateIvyJavaIvyModuleDescriptor
+:generateDescriptorFileForIvyJavaPublication
 :compileJava UP-TO-DATE
 :processResources UP-TO-DATE
 :classes UP-TO-DATE
diff --git a/subprojects/docs/src/samples/userguideOutput/shouldRunAfter.out b/subprojects/docs/src/samples/userguideOutput/shouldRunAfter.out
new file mode 100644
index 0000000..6c41820
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/shouldRunAfter.out
@@ -0,0 +1,2 @@
+taskX
+taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/shouldRunAfterWithCycle.out b/subprojects/docs/src/samples/userguideOutput/shouldRunAfterWithCycle.out
new file mode 100644
index 0000000..58af3bf
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/shouldRunAfterWithCycle.out
@@ -0,0 +1,3 @@
+taskZ
+taskY
+taskX
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/taskFinalizers.out b/subprojects/docs/src/samples/userguideOutput/taskFinalizers.out
new file mode 100644
index 0000000..6c41820
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/taskFinalizers.out
@@ -0,0 +1,2 @@
+taskX
+taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/taskFinalizersWithFailure.out b/subprojects/docs/src/samples/userguideOutput/taskFinalizersWithFailure.out
new file mode 100644
index 0000000..6c41820
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/taskFinalizersWithFailure.out
@@ -0,0 +1,2 @@
+taskX
+taskY
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/taskHelp.out b/subprojects/docs/src/samples/userguideOutput/taskHelp.out
new file mode 100644
index 0000000..64b121c
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/taskHelp.out
@@ -0,0 +1,12 @@
+Detailed task information for libs
+
+Paths
+     :api:libs
+     :webapp:libs
+
+Type
+     Task (org.gradle.api.Task)
+
+Description
+     Builds the JAR
+
diff --git a/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out b/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
index 9b537c0..31b5846 100644
--- a/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
@@ -17,6 +17,11 @@ api:libs - Builds the JAR
 webapp:libs - Builds the JAR [api:libs]
     webapp:compile - Compiles the source files
 
+Build Setup tasks
+-----------------
+init - Initializes a new Gradle build. [incubating]
+wrapper - Generates Gradle wrapper files. [incubating]
+
 Help tasks
 ----------
 dependencies - Displays all dependencies declared in root project 'projectReports'.
diff --git a/subprojects/docs/src/samples/userguideOutput/taskListReport.out b/subprojects/docs/src/samples/userguideOutput/taskListReport.out
index 16ff74c..4811131 100644
--- a/subprojects/docs/src/samples/userguideOutput/taskListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/taskListReport.out
@@ -11,6 +11,11 @@ clean - Deletes the build directory (build)
 dists - Builds the distribution
 libs - Builds the JAR
 
+Build Setup tasks
+-----------------
+init - Initializes a new Gradle build. [incubating]
+wrapper - Generates Gradle wrapper files. [incubating]
+
 Help tasks
 ----------
 dependencies - Displays all dependencies declared in root project 'projectReports'.
diff --git a/subprojects/docs/src/samples/userguideOutput/usePluginsInInitScripts.out b/subprojects/docs/src/samples/userguideOutput/usePluginsInInitScripts.out
new file mode 100644
index 0000000..f58eafb
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/usePluginsInInitScripts.out
@@ -0,0 +1 @@
+repository: STANDARD_ENTERPRISE_REPO ('http://repo.gradle.org/gradle/repo')
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/readme.xml b/subprojects/docs/src/samples/webApplication/customised/readme.xml
deleted file mode 100755
index ad69e48..0000000
--- a/subprojects/docs/src/samples/webApplication/customised/readme.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<sample>
-    <para>Web application with customized WAR contents.</para>
-</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customized/additionalLibs/additional-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/additionalLibs/additional-1.0.jar
new file mode 100644
index 0000000..c75ef21
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/additionalLibs/additional-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customised/build.gradle b/subprojects/docs/src/samples/webApplication/customized/build.gradle
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/build.gradle
rename to subprojects/docs/src/samples/webApplication/customized/build.gradle
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/compile-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/compile-1.0.jar
new file mode 100644
index 0000000..bac908d
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/compile-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/compile-transitive-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/compile-transitive-1.0.jar
new file mode 100644
index 0000000..b97ca3e
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/compile-transitive-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/otherLib-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/otherLib-1.0.jar
new file mode 100644
index 0000000..c75ef21
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/otherLib-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/providedCompile-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/providedCompile-1.0.jar
new file mode 100644
index 0000000..2d246b4
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/providedCompile-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/providedCompile-transitive-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/providedCompile-transitive-1.0.jar
new file mode 100644
index 0000000..6eea655
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/providedCompile-transitive-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/providedRuntime-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/providedRuntime-1.0.jar
new file mode 100644
index 0000000..b7040d0
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/providedRuntime-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/lib/runtime-1.0.jar b/subprojects/docs/src/samples/webApplication/customized/lib/runtime-1.0.jar
new file mode 100644
index 0000000..07549cf
Binary files /dev/null and b/subprojects/docs/src/samples/webApplication/customized/lib/runtime-1.0.jar differ
diff --git a/subprojects/docs/src/samples/webApplication/customized/readme.xml b/subprojects/docs/src/samples/webApplication/customized/readme.xml
new file mode 100755
index 0000000..bd523a5
--- /dev/null
+++ b/subprojects/docs/src/samples/webApplication/customized/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Web application with customized WAR contents.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/docs/src/samples/webApplication/customized/src/additionalWebInf/additional.xml
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
rename to subprojects/docs/src/samples/webApplication/customized/src/additionalWebInf/additional.xml
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/main/java/org/gradle/HelloServlet.java b/subprojects/docs/src/samples/webApplication/customized/src/main/java/org/gradle/HelloServlet.java
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/main/java/org/gradle/HelloServlet.java
rename to subprojects/docs/src/samples/webApplication/customized/src/main/java/org/gradle/HelloServlet.java
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/main/java/org/gradle/MyClass.java b/subprojects/docs/src/samples/webApplication/customized/src/main/java/org/gradle/MyClass.java
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/main/java/org/gradle/MyClass.java
rename to subprojects/docs/src/samples/webApplication/customized/src/main/java/org/gradle/MyClass.java
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/main/webapp/WEB-INF/webapp.xml b/subprojects/docs/src/samples/webApplication/customized/src/main/webapp/WEB-INF/webapp.xml
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/main/webapp/WEB-INF/webapp.xml
rename to subprojects/docs/src/samples/webApplication/customized/src/main/webapp/WEB-INF/webapp.xml
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/main/webapp/webapp.html b/subprojects/docs/src/samples/webApplication/customized/src/main/webapp/webapp.html
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/main/webapp/webapp.html
rename to subprojects/docs/src/samples/webApplication/customized/src/main/webapp/webapp.html
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/rootContent/root.txt b/subprojects/docs/src/samples/webApplication/customized/src/rootContent/root.txt
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/rootContent/root.txt
rename to subprojects/docs/src/samples/webApplication/customized/src/rootContent/root.txt
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/someWeb.xml b/subprojects/docs/src/samples/webApplication/customized/src/someWeb.xml
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/someWeb.xml
rename to subprojects/docs/src/samples/webApplication/customized/src/someWeb.xml
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/gradle/MyClassTest.java b/subprojects/docs/src/samples/webApplication/customized/src/test/java/org/gradle/MyClassTest.java
similarity index 100%
rename from subprojects/docs/src/samples/webApplication/customised/src/test/java/org/gradle/MyClassTest.java
rename to subprojects/docs/src/samples/webApplication/customized/src/test/java/org/gradle/MyClassTest.java
diff --git a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
index eae6162..c7608c9 100644
--- a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
+++ b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
@@ -31,10 +31,10 @@ import spock.lang.Shared
 @IgnoreIf({ !canReachServices() })
 class FunctionalReleaseNotesTest extends GebReportingSpec {
 
-    static private final String FIXED_ISSUES_URL = "http://services.gradle.org/fixed-issues/${GradleVersion.current().versionBase}"
-    static private final String KNOWN_ISSUES_URL = "http://services.gradle.org/known-issues/${GradleVersion.current().versionBase}"
+    static private final String FIXED_ISSUES_URL = "http://services.gradle.org/fixed-issues/${GradleVersion.current().baseVersion.version}"
+    static private final String KNOWN_ISSUES_URL = "http://services.gradle.org/known-issues/${GradleVersion.current().baseVersion.version}"
 
-    private String version = GradleVersion.current().versionBase
+    private String version = GradleVersion.current().baseVersion.version
 
     static boolean canReachServices() {
         try {
diff --git a/subprojects/docs/src/transforms/release-notes.gradle b/subprojects/docs/src/transforms/release-notes.gradle
index 3a23dda..ddeb589 100644
--- a/subprojects/docs/src/transforms/release-notes.gradle
+++ b/subprojects/docs/src/transforms/release-notes.gradle
@@ -10,16 +10,16 @@ buildscript {
 }
 
 import org.jsoup.nodes.Element
+import org.jsoup.nodes.TextNode
 import org.jsoup.select.Elements
 import com.uwyn.jhighlight.renderer.XhtmlRendererFactory
 
-ext {
-    baseStyleFile = project.file("$project.cssFiles.dir/base.css")
-    releaseNotesStyleFile = project.file("$project.cssFiles.dir/release-notes.css")
-    scriptFile = project.file("src/docs/release/content/script.js")
-}
+def baseStyleFile = project.file("$project.cssFiles.dir/base.css")
+def releaseNotesStyleFile = project.file("$project.cssFiles.dir/release-notes.css")
+def scriptFile = project.file("src/docs/release/content/script.js")
+def incubatingMarker = " (i)"
 
-inputs.files([project.cssFiles, baseStyleFile, releaseNotesStyleFile, project.configurations.jquery, scriptFile])
+inputs.files([project.cssFiles, baseStyleFile, releaseNotesStyleFile, project.configurations.jquery, project.configurations.jqueryTipTip, scriptFile])
 
 transformDocument {
     outputSettings().indentAmount(2).prettyPrint(true)
@@ -32,8 +32,9 @@ transformDocument {
 
     head().append("<style>p{}</style>").children().last().childNode(0).attr("data", baseStyleFile.text + releaseNotesStyleFile.text)
 
-    head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", project.configurations.jquery.singleFile.text)
-    head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", scriptFile.text)
+    [project.configurations.jquery.singleFile, project.configurations.jqueryTipTip.singleFile, scriptFile].each {
+        head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", it.text)
+    }
 }
 
 // wrap each h2 section in section.topic
@@ -62,12 +63,13 @@ transformDocument {
     }
 }
 
+// replace the incubating marker in h3 with class
 transformDocument {
-    def incubatingMarker = " (i)"
     for (heading in body().select(".topic").select("h3")) {
-        if (heading.text().endsWith(incubatingMarker)) {
-            heading.text(heading.text() - incubatingMarker).addClass("incubating")
-            heading.after("<a class='incubating-marker' href='userguide/feature_lifecycle.html' title='“incubating” features are not yet guaranteed to be backwards compatible<br />(click for more information)'>incubating feature</a>")
+        def textNode = heading.childNodes().last()
+        if (textNode instanceof TextNode && textNode.text().endsWith(incubatingMarker)) {
+            textNode.text(textNode.text() - incubatingMarker)
+            heading.addClass("incubating")
         }
     }
 }
@@ -147,12 +149,18 @@ transformDocument {
         if (subs) {
             def sublist = toc.children().last().append("<ul class='toc-sub'/>").children().last()
             subs.each {
-                def subName = it.text()
+                def subName = it.html()
                 def subAnchorName = it.attr("id")
-                sublist.append("<li><a/></li>").children().last().select("a").first().text(subName).attr("href", "#$subAnchorName")
+                sublist.append("<li><a/></li>").children().last().select("a").first().html(subName).attr("href", "#$subAnchorName")
             }
         }
+    }
+}
 
+// add a tooltip to all h3 marked incubating
+transformDocument {
+    for (heading in body().select(".incubating").select("h3")) {
+        heading.append(" <a class='incubating-marker' href='userguide/feature_lifecycle.html' title='“incubating” features are not yet guaranteed to be backwards compatible<br />(click for more information)'>incubating feature</a>")
     }
 }
 
diff --git a/subprojects/ear/ear.gradle b/subprojects/ear/ear.gradle
index 145c352..d44049d 100644
--- a/subprojects/ear/ear.gradle
+++ b/subprojects/ear/ear.gradle
@@ -15,7 +15,6 @@
  */
 
 dependencies {
-    groovy libraries.groovy
     compile project(':core')
     compile project(":plugins")
     compile libraries.inject
diff --git a/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy b/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy
index dd74fe3..e116dc4 100644
--- a/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy
+++ b/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy
@@ -17,12 +17,14 @@
 package org.gradle.plugins.ear
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.test.fixtures.archive.JarTestFixture
+import org.hamcrest.Matchers
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 
-/**
- * @author: Szczepan Faber, created at: 6/3/11
- */
+import static org.testng.Assert.assertEquals
+
 class EarPluginIntegrationTest extends AbstractIntegrationTest {
 
     @Before
@@ -46,13 +48,13 @@ dependencies {
 """)
         //when
         executer.withTasks('assemble').run()
-        file("build/libs/root.ear").unzipTo(file("unzipped"))
 
         //then
-        file("unzipped/rootLib.jar").assertExists()
-        file("unzipped/META-INF/MANIFEST.MF").assertExists()
-        file("unzipped/META-INF/application.xml").assertExists()
-        file("unzipped/lib/earLib.jar").assertExists()
+        def ear = new JarTestFixture(file('build/libs/root.ear'))
+        ear.assertContainsFile("META-INF/MANIFEST.MF")
+        ear.assertContainsFile("META-INF/application.xml")
+        ear.assertContainsFile("rootLib.jar")
+        ear.assertContainsFile("lib/earLib.jar")
     }
 
     @Test
@@ -75,11 +77,35 @@ ear {
 """)
         //when
         executer.withTasks('assemble').run()
+
+        //then
+        def ear = new JarTestFixture(file('build/libs/root.ear'))
+        ear.assertContainsFile("CUSTOM/lib/earLib.jar")
+        ear.assertFileContent("META-INF/application.xml", Matchers.containsString("cool ear"))
+    }
+
+    @Test
+    void "includes modules in deployment descriptor"() {
+        file('moduleA.jar').createFile()
+        file('moduleB.war').createFile()
+
+        file("build.gradle").write("""
+apply plugin: 'ear'
+
+dependencies {
+    deploy files('moduleA.jar', 'moduleB.war')
+}
+""")
+        //when
+        executer.withTasks('assemble').run()
         file("build/libs/root.ear").unzipTo(file("unzipped"))
 
         //then
-        file("unzipped/CUSTOM/lib/earLib.jar").assertExists()
-        assert file("unzipped/META-INF/application.xml").text.contains('cool ear')
+        def appXml = new XmlSlurper().parse(
+                file('unzipped/META-INF/application.xml'))
+        def modules = appXml.module
+        assertEquals(modules[0].ejb.text(), 'moduleA.jar')
+        assertEquals(modules[1].web.'web-uri'.text(), 'moduleB.war')
     }
 
     @Test
@@ -103,12 +129,12 @@ ear {
 
         //when
         executer.withTasks('assemble').run()
-        file("build/libs/root.ear").unzipTo(file("unzipped"))
 
         //then
-        assert file("unzipped/someOtherFile.txt").assertExists()
-        assert file("unzipped/META-INF/stuff/yetAnotherFile.txt").assertExists()
-        assert file("unzipped/META-INF/application.xml").text == applicationXml
+        def ear = new JarTestFixture(file('build/libs/root.ear'))
+        ear.assertContainsFile("someOtherFile.txt")
+        ear.assertContainsFile("META-INF/stuff/yetAnotherFile.txt")
+        ear.assertFileContent("META-INF/application.xml", applicationXml)
     }
 
     @Test
@@ -133,11 +159,60 @@ ear {
 
         //when
         executer.withTasks('assemble').run()
-        file("build/libs/root.ear").unzipTo(file("unzipped"))
 
         //then
-        assert file("unzipped/someOtherFile.txt").assertExists()
-        assert file("unzipped/META-INF/stuff/yetAnotherFile.txt").assertExists()
-        assert file("unzipped/META-INF/application.xml").text == applicationXml
+        def ear = new JarTestFixture(file('build/libs/root.ear'))
+        ear.assertContainsFile("someOtherFile.txt")
+        ear.assertContainsFile("META-INF/stuff/yetAnotherFile.txt")
+        ear.assertFileContent("META-INF/application.xml", applicationXml)
+    }
+
+    @Test @Ignore
+    void "exclude duplicates: deploymentDescriptor has priority over metaInf"() {
+        file('bad-meta-inf/application.xml').createFile().write('bad descriptor')
+        file('build.gradle').write('''
+apply plugin: 'ear'
+ear {
+   duplicatesStrategy = 'exclude'
+   metaInf {
+       from 'bad-meta-inf'
+   }
+   deploymentDescriptor {
+       applicationName = 'good'
+   }
+}''')
+
+        // when
+        executer.withTasks('assemble').run();
+
+        // then
+        def ear = new JarTestFixture(file('build/libs/root.ear'))
+        ear.assertFileContent("META-INF/application.xml", Matchers.not(Matchers.containsString("bad descriptor")))
     }
+
+    @Test
+    void "exclude duplicates: lib has priority over other files"() {
+        file('bad-lib/file.txt').createFile().write('bad')
+        file('good-lib/file.txt').createFile().write('good')
+
+        file('build.gradle').write('''
+apply plugin: 'ear'
+ear {
+   duplicatesStrategy = 'exclude'
+   into('lib') {
+       from 'bad-lib'
+   }
+   lib {
+       from 'good-lib'
+   }
+}''')
+
+        // when
+        executer.withTasks('assemble').run();
+
+        // then
+        def ear = new JarTestFixture(file('build/libs/root.ear'))
+        ear.assertFileContent("lib/file.txt", "good")
+    }
+
 }
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy
index a5bac93..a8497b0 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy
@@ -16,23 +16,21 @@
 
 package org.gradle.plugins.ear
 
-import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
-import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
-import org.gradle.plugins.ear.descriptor.EarModule
-import org.gradle.plugins.ear.descriptor.internal.DefaultEarModule
-import org.gradle.plugins.ear.descriptor.internal.DefaultEarWebModule
-
-import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.file.CopySpec
 import org.gradle.api.file.FileCopyDetails
 import org.gradle.api.internal.file.collections.FileTreeAdapter
 import org.gradle.api.internal.file.collections.MapFileTree
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.internal.nativeplatform.filesystem.Chmod
+import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
+import org.gradle.plugins.ear.descriptor.EarModule
+import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
+import org.gradle.plugins.ear.descriptor.internal.DefaultEarModule
+import org.gradle.plugins.ear.descriptor.internal.DefaultEarWebModule
 import org.gradle.util.ConfigureUtil
 
 /**
  * Assembles an EAR archive.
- *
- * @author David Gileadi
  */
 class Ear extends Jar {
     public static final String EAR_EXTENSION = 'ear'
@@ -51,32 +49,32 @@ class Ear extends Jar {
 
     Ear() {
         extension = EAR_EXTENSION
-        lib = copyAction.rootSpec.addChild().into {
+        lib = rootSpec.addChildBeforeSpec(mainSpec).into {
             getLibDirName()
         }
-        copyAction.mainSpec.eachFile { FileCopyDetails details ->
-            if (deploymentDescriptor && details.path.equalsIgnoreCase('META-INF/' + deploymentDescriptor.fileName)) {
+        mainSpec.eachFile { FileCopyDetails details ->
+            if (this.deploymentDescriptor && details.path.equalsIgnoreCase('META-INF/' + this.deploymentDescriptor.fileName)) {
                 // the deployment descriptor already exists; no need to generate it
-                deploymentDescriptor = null
+                this.deploymentDescriptor = null
             }
             // since we might generate the deployment descriptor, record each top-level module
-            if (deploymentDescriptor && details.path.lastIndexOf('/') <= 0) {
+            if (this.deploymentDescriptor && details.path.lastIndexOf('/') <= 0) {
                 EarModule module
                 if (details.path.toLowerCase().endsWith(".war")) {
                     module = new DefaultEarWebModule(details.path, details.path.substring(0, details.path.lastIndexOf('.')))
                 } else {
                     module = new DefaultEarModule(details.path)
                 }
-                if (!deploymentDescriptor.modules.contains(module)) {
-                    deploymentDescriptor.modules.add module
+                if (!this.deploymentDescriptor.modules.contains(module)) {
+                    this.deploymentDescriptor.modules.add module
                 }
             }
         }
         // create our own metaInf which runs after mainSpec's files
         // this allows us to generate the deployment descriptor after recording all modules it contains
-        def metaInf = copyAction.mainSpec.addChild().into('META-INF')
+        def metaInf = mainSpec.addChild().into('META-INF')
         metaInf.addChild().from {
-            MapFileTree descriptorSource = new MapFileTree(temporaryDirFactory)
+            MapFileTree descriptorSource = new MapFileTree(temporaryDirFactory, services.get(Chmod))
             final DeploymentDescriptor descriptor = deploymentDescriptor
             if (descriptor) {
                 if (!descriptor.libraryDirectory) {
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 126d317..87caba6 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
@@ -41,8 +41,6 @@ import java.util.concurrent.Callable;
  * <p>
  * A {@link Plugin} with tasks which assemble a web application into a EAR file.
  * </p>
- *
- * @author David Gileadi, Hans Dockter
  */
 public class EarPlugin implements Plugin<Project> {
 
@@ -125,7 +123,7 @@ public class EarPlugin implements Plugin<Project> {
     }
 
     private void setupEarTask(final Project project, EarPluginConvention convention) {
-        Ear ear = project.getTasks().add(EAR_TASK_NAME, Ear.class);
+        Ear ear = project.getTasks().create(EAR_TASK_NAME, Ear.class);
         ear.setDescription("Generates a ear archive with all the modules, the application descriptor and the libraries.");
         DeploymentDescriptor deploymentDescriptor = convention.getDeploymentDescriptor();
         if (deploymentDescriptor != null) {
@@ -172,9 +170,9 @@ public class EarPlugin implements Plugin<Project> {
     private void configureConfigurations(final Project project) {
 
         ConfigurationContainer configurations = project.getConfigurations();
-        Configuration moduleConfiguration = configurations.add(DEPLOY_CONFIGURATION_NAME).setVisible(false)
+        Configuration moduleConfiguration = configurations.create(DEPLOY_CONFIGURATION_NAME).setVisible(false)
                 .setTransitive(false).setDescription("Classpath for deployable modules, not transitive.");
-        Configuration earlibConfiguration = configurations.add(EARLIB_CONFIGURATION_NAME).setVisible(false)
+        Configuration earlibConfiguration = configurations.create(EARLIB_CONFIGURATION_NAME).setVisible(false)
                 .setDescription("Classpath for module dependencies.");
 
         configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
index adbff5d..3883a87 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
@@ -26,8 +26,6 @@ import java.util.Set;
 
 /**
  * A deployment descriptor such as application.xml.
- * 
- * @author David Gileadi
  */
 public interface DeploymentDescriptor {
 
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java
index 42a22fc..1d243d4 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java
@@ -19,8 +19,6 @@ import groovy.util.Node;
 
 /**
  * A module element in a deployment descriptor like application.xml.
- * 
- * @author David Gileadi
  */
 public interface EarModule {
 
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java
index 1ec1873..84c9103 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java
@@ -17,8 +17,6 @@ package org.gradle.plugins.ear.descriptor;
 
 /**
  * A security-role element in a deployment descriptor like application.xml.
- * 
- * @author David Gileadi
  */
 public interface EarSecurityRole {
 
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java
index f34425d..be32226 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java
@@ -17,8 +17,6 @@ package org.gradle.plugins.ear.descriptor;
 
 /**
  * A module element in a deployment descriptor like application.xml that has a web child element.
- * 
- * @author David Gileadi
  */
 public interface EarWebModule extends EarModule {
 
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 7c17ee1..2a35435 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
@@ -27,9 +27,6 @@ import org.gradle.plugins.ear.descriptor.EarModule
 import org.gradle.plugins.ear.descriptor.EarSecurityRole
 import org.gradle.plugins.ear.descriptor.EarWebModule
 
-/**
- * @author David Gileadi
- */
 class DefaultDeploymentDescriptor implements DeploymentDescriptor {
 
     private String fileName = "application.xml"
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy
index 14e1a19..9b94742 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ear.descriptor.internal
 import groovy.xml.QName
 import org.gradle.plugins.ear.descriptor.EarModule
 
-/**
- * @author David Gileadi
- */
 class DefaultEarModule implements EarModule {
 
     String path;
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy
index 0bc4965..f1168ca 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ear.descriptor.internal
 
 import org.gradle.plugins.ear.descriptor.EarSecurityRole
 
-/**
- * @author David Gileadi
- */
 class DefaultEarSecurityRole implements EarSecurityRole {
 
     String description
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy
index 76640c7..0c77f87 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ear.descriptor.internal
 
 import org.gradle.plugins.ear.descriptor.EarWebModule;
 
-/**
- * @author David Gileadi
- */
 class DefaultEarWebModule extends DefaultEarModule implements EarWebModule {
 
     String contextRoot
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 399878d..a53e8ea 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
@@ -23,20 +23,18 @@ import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Dependency
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.BasePlugin
+import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.WarPlugin
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Before
 import org.junit.Test
-import static org.gradle.util.Matchers.dependsOn
+
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.api.plugins.JavaBasePlugin
 
-/**
- * @author David Gileadi
- */
 class EarPluginTest {
     private ProjectInternal project
     private static final String TEST_APP_XML = toPlatformLineSeparators('<?xml version="1.0" encoding="UTF-8"?>\n' +
@@ -56,7 +54,7 @@ class EarPluginTest {
 
     @Before
     public void setUp() {
-        project = HelperUtil.createRootProject()
+        project = TestUtil.createRootProject()
     }
 
     @Test public void appliesBasePluginAndAddsConvention() {
@@ -105,7 +103,7 @@ class EarPluginTest {
     @Test public void dependsOnEarlibConfig() {
         project.plugins.apply(EarPlugin)
 
-        Project childProject = HelperUtil.createChildProject(project, 'child')
+        Project childProject = TestUtil.createChildProject(project, 'child')
         JavaPlugin javaPlugin = new JavaPlugin()
         javaPlugin.apply(childProject)
 
@@ -195,14 +193,12 @@ class EarPluginTest {
     }
 
     @Test public void supportsRenamingLibDir() {
-        Project childProject = HelperUtil.createChildProject(project, 'child')
+        Project childProject = TestUtil.createChildProject(project, 'child')
         childProject.file("src/main/resources").mkdirs()
         childProject.file("src/main/resources/test.txt").createNewFile()
         JavaPlugin javaPlugin = new JavaPlugin()
         javaPlugin.apply(childProject)
 
-        execute childProject.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
-
         project.plugins.apply(EarPlugin)
         project.convention.plugins.ear.libDirName = "APP-INF/lib"
         project.dependencies {
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 1a4746f..edb6be7 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
@@ -16,16 +16,14 @@
 
 package org.gradle.plugins.ear
 
-import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
-import org.gradle.api.tasks.bundling.AbstractArchiveTaskTest
 import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.api.tasks.bundling.AbstractArchiveTaskTest
+import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
 import org.junit.Before
 import org.junit.Test
+
 import static org.junit.Assert.assertEquals
 
-/**
- * @author David Gileadi
- */
 class EarTest extends AbstractArchiveTaskTest {
 
     Ear ear
diff --git a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
index 66c024a..30757fd 100644
--- a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
@@ -25,9 +25,6 @@ import javax.xml.parsers.DocumentBuilderFactory
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
-/**
- * @author: Szczepan Faber, created at: 6/3/11
- */
 class DefaultDeploymentDescriptorTest extends Specification {
     def descriptor = new DefaultDeploymentDescriptor({ it } as FileResolver)
     @Rule TestNameTestDirectoryProvider tmpDir
diff --git a/subprojects/ide/ide.gradle b/subprojects/ide/ide.gradle
index 1e4eab0..030a18e 100644
--- a/subprojects/ide/ide.gradle
+++ b/subprojects/ide/ide.gradle
@@ -13,11 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+sourceSets.main.java.srcDirs = []
+sourceSets.main.groovy.srcDirs = ['src/main/java', 'src/main/groovy']
+
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':scala')
     compile project(':core')
+    compile project(':launcher')
     compile project(':plugins')
     compile project(':ear')
     compile project(':toolingApi')
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationSpec.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationSpec.groovy
new file mode 100644
index 0000000..fd016dd
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationSpec.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+abstract class AbstractIdeIntegrationSpec extends AbstractIntegrationSpec {
+
+    protected File getFile(Map options, String filename) {
+        def file = options?.project ? file(options.project, filename) : file(filename)
+        if (options?.print) { println file.text }
+        file
+    }
+
+    protected parseFile(Map options = [:], String filename) {
+        def file = getFile(options, filename)
+        new XmlSlurper().parse(file)
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractSourcesAndJavadocJarsIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractSourcesAndJavadocJarsIntegrationTest.groovy
new file mode 100644
index 0000000..8d4548f
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractSourcesAndJavadocJarsIntegrationTest.groovy
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide
+
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.maven.HttpArtifact
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+abstract class AbstractSourcesAndJavadocJarsIntegrationTest extends AbstractIdeIntegrationSpec {
+    @Rule HttpServer server
+
+    def setup() {
+        server.start()
+        executer.requireOwnGradleUserHomeDir()
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << baseBuildScript
+    }
+
+    private useIvyRepo(def repo) {
+        buildFile << """repositories { ivy { url "$repo.uri" } }"""
+    }
+
+    private useMavenRepo(def repo) {
+        buildFile << """repositories { maven { url "$repo.uri" } }"""
+    }
+
+    def "sources and javadoc jars from maven repositories are not downloaded if not required"() {
+        def repo = mavenHttpRepo
+        def module = repo.module("some", "module", "1.0").withSourceAndJavadoc().publish()
+
+        when:
+        useMavenRepo(repo)
+
+        and:
+        module.pom.expectGet()
+        module.artifact.expectGet()
+
+        then:
+        succeeds "resolve"
+    }
+
+    def "sources and javadoc jars from maven repositories are resolved, attached and cached"() {
+        def repo = mavenHttpRepo
+        def module = repo.module("some", "module", "1.0")
+        module.artifact(classifier: "sources")
+        module.artifact(classifier: "javadoc")
+        module.publish()
+        module.allowAll()
+
+        when:
+        useMavenRepo(repo)
+        succeeds ideTask
+
+        then:
+        ideFileContainsSourcesAndJavadocEntry()
+
+        when:
+        server.resetExpectations()
+        succeeds ideTask
+
+        then:
+        ideFileContainsSourcesAndJavadocEntry()
+    }
+
+    def "ignores missing sources and javadoc jars in maven repositories"() {
+        def repo = mavenHttpRepo
+        repo.module("some", "module", "1.0").publish().allowAll()
+
+        when:
+        useMavenRepo(repo)
+        succeeds ideTask
+
+        then:
+        ideFileContainsNoSourcesAndJavadocEntry()
+    }
+
+    void "ignores broken source or javadoc artifacts in maven repository"() {
+        def repo = mavenHttpRepo
+        def module = repo.module("some", "module", "1.0")
+        final sourceArtifact = module.artifact(classifier: "sources")
+        final javadocArtifact = module.artifact(classifier: "javadoc")
+        module.publish()
+
+        when:
+        useMavenRepo(repo)
+
+        and:
+        module.pom.expectGet()
+        module.artifact.expectGet()
+        sourceArtifact.expectGetBroken()
+        javadocArtifact.expectGetBroken()
+
+        expectBehaviorAfterBrokenMavenArtifact(sourceArtifact)
+        expectBehaviorAfterBrokenMavenArtifact(javadocArtifact)
+
+        then:
+        succeeds ideTask
+        ideFileContainsNoSourcesAndJavadocEntry()
+    }
+
+    def "sources and javadoc jars from ivy repositories are not downloaded if not required"() {
+        def repo = ivyHttpRepo
+        def module = repo.module("some", "module", "1.0")
+        addCompleteConfigurations(module)
+        module.publish()
+
+        when:
+        useIvyRepo(repo)
+
+        and:
+        module.ivy.expectGet()
+        module.jar.expectGet()
+
+        then:
+        succeeds "resolve"
+    }
+
+    def "sources and javadoc jars from ivy repositories are resolved, attached and cached"() {
+        def repo = ivyHttpRepo
+        def module = repo.module("some", "module", "1.0")
+        addCompleteConfigurations(module)
+        module.publish()
+        module.allowAll()
+
+        when:
+        useIvyRepo(repo)
+        succeeds ideTask
+
+        then:
+        ideFileContainsSourcesAndJavadocEntry("my-sources", "my-javadoc")
+
+        when:
+        server.resetExpectations()
+        succeeds ideTask
+
+        then:
+        ideFileContainsSourcesAndJavadocEntry("my-sources", "my-javadoc")
+    }
+
+    def "ignores missing sources and javadoc jars in ivy repositories"() {
+        def repo = ivyHttpRepo
+        final module = repo.module("some", "module", "1.0")
+        addCompleteConfigurations(module)
+        module.publish()
+
+        when:
+        useIvyRepo(repo)
+        module.getArtifact(classifier: "my-sources").expectGetMissing()
+        module.getArtifact(classifier: "my-javadoc").expectGetMissing()
+        module.allowAll()
+
+        then:
+        succeeds ideTask
+        ideFileContainsNoSourcesAndJavadocEntry()
+    }
+
+    void "ignores broken source or javadoc artifacts in ivy repository"() {
+        def repo = ivyHttpRepo
+        def module = repo.module("some", "module", "1.0")
+        addCompleteConfigurations(module)
+        module.publish()
+
+        when:
+        useIvyRepo(repo)
+
+        and:
+        module.ivy.expectGet()
+        module.jar.expectGet()
+        def sourceArtifact = module.getArtifact(classifier: "my-sources")
+        def javadocArtifact = module.getArtifact(classifier: "my-javadoc")
+        sourceArtifact.expectGetBroken()
+        javadocArtifact.expectGetBroken()
+
+        expectBehaviorAfterBrokenIvyArtifact(sourceArtifact)
+        expectBehaviorAfterBrokenIvyArtifact(javadocArtifact)
+
+        then:
+        succeeds ideTask
+        ideFileContainsNoSourcesAndJavadocEntry()
+    }
+
+    def "sources and javadoc jars stored with maven scheme in ivy repositories are resolved and attached"() {
+        def repo = ivyHttpRepo
+        def module = repo.module("some", "module", "1.0")
+        module.configuration("default")
+        module.artifact(conf: "default")
+        module.undeclaredArtifact(classifier: "sources", ext: "jar")
+        module.undeclaredArtifact(classifier: "javadoc", ext: "jar")
+        module.publish()
+        module.allowAll()
+
+        when:
+        useIvyRepo(repo)
+        succeeds ideTask
+
+        then:
+        ideFileContainsSourcesAndJavadocEntry("sources", "javadoc")
+    }
+
+    def "sources and javadoc jars from flatdir repositories are resolved and attached"() {
+        file("repo/module-1.0.jar").createFile()
+        file("repo/module-1.0-sources.jar").createFile()
+        file("repo/module-1.0-javadoc.jar").createFile()
+
+        when:
+        buildFile << """repositories { flatDir { dir "repo" } }"""
+        succeeds ideTask
+
+        then:
+        ideFileContainsSourcesAndJavadocEntry()
+    }
+
+    private static void addCompleteConfigurations(IvyHttpModule module) {
+        module.configuration("default")
+        module.configuration("sources")
+        module.configuration("javadoc")
+        module.artifact(conf: "default")
+        // use uncommon sources and javadoc classifiers to prove that artifact names don't matter
+        module.artifact(type: "source", classifier: "my-sources", ext: "jar", conf: "sources")
+        module.artifact(type: "javadoc", classifier: "my-javadoc", ext: "jar", conf: "javadoc")
+    }
+
+    MavenHttpRepository getMavenHttpRepo() {
+        return new MavenHttpRepository(server, "/repo", mavenRepo)
+    }
+
+    IvyHttpRepository getIvyHttpRepo() {
+        return new IvyHttpRepository(server, "/repo", ivyRepo)
+    }
+
+    String getBaseBuildScript() {
+        """
+apply plugin: "java"
+apply plugin: "idea"
+apply plugin: "eclipse"
+
+dependencies {
+    compile("some:module:1.0")
+}
+
+idea {
+    module {
+        downloadJavadoc = true
+    }
+}
+
+eclipse {
+    classpath {
+        downloadJavadoc = true
+    }
+}
+
+task resolve << {
+    configurations.compile.each { println it }
+}
+"""
+    }
+
+    abstract String getIdeTask()
+
+    abstract void ideFileContainsSourcesAndJavadocEntry()
+    abstract void ideFileContainsSourcesAndJavadocEntry(String sourcesClassifier, String javadocClassifier)
+    abstract void ideFileContainsNoSourcesAndJavadocEntry()
+    abstract void expectBehaviorAfterBrokenMavenArtifact(HttpArtifact httpArtifact)
+    abstract void expectBehaviorAfterBrokenIvyArtifact(HttpArtifact httpArtifact)
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy
index 80da222..2cc4497 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.plugins.ide
 import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
 import org.junit.Test
 
-/**
- * @author Szczepan Faber, created at: 3/27/11
- */
 class AutoTestedSamplesIntegrationTest extends AbstractAutoTestedSamplesTest {
 
     @Test
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
index 8287099..845e9b1 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
 import org.gradle.test.fixtures.file.TestFile
 
 import java.util.regex.Pattern
@@ -84,33 +84,39 @@ class EclipseClasspathFixture {
         }
 
         void assertHasCachedJar(String group, String module, String version) {
-            assert entry. at path ==~ cachePath(group, module, version, "jar") + Pattern.quote("${module}-${version}.jar")
+            assert entry. at path ==~ cachePath(group, module, version) + Pattern.quote("${module}-${version}.jar")
         }
 
         void assertHasSource(File jar) {
             assert entry. at sourcepath == jar.absolutePath.replace(File.separator, '/')
         }
 
+        String getSourcePath() {
+            entry. at sourcepath
+        }
+
         void assertHasSource(String jar) {
             assert entry. at sourcepath == jar
         }
 
         void assertHasCachedSource(String group, String module, String version) {
-            assert entry. at sourcepath ==~ cachePath(group, module, version, "source") + Pattern.quote("${module}-${version}-sources.jar")
-        }
-
-        private String cachePath(String group, String module, String version, String type) {
-            return Pattern.quote("${userHomeDir.absolutePath.replace(File.separator, '/')}") + "/caches/artifacts-${artifactCacheVersion}/filestore/" + Pattern.quote("${group}/${module}/${version}/${type}/") + "\\w+/"
+            assert entry. at sourcepath ==~ cachePath(group, module, version) + Pattern.quote("${module}-${version}-sources.jar")
         }
 
-        private def getArtifactCacheVersion() {
-            return DefaultCacheLockingManager.CACHE_LAYOUT_VERSION;
+        private String cachePath(String group, String module, String version) {
+            return Pattern.quote("${userHomeDir.absolutePath.replace(File.separator, '/')}") + "/caches/${CacheLayout.ROOT.getKey()}/${CacheLayout.FILE_STORE.getKey()}/" + Pattern.quote("${group}/${module}/${version}/") + "\\w+/"
         }
 
         void assertHasNoSource() {
             assert !entry. at sourcepath
         }
 
+        String getJavadocLocation() {
+            assert entry.attributes
+            assert entry.attributes[0].attribute[0]. at name == 'javadoc_location'
+            entry.attributes[0].attribute[0]. at value
+        }
+
         void assertHasJavadoc(File file) {
             assert entry.attributes
             assert entry.attributes[0].attribute[0]. at name == 'javadoc_location'
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 a4f758c..b427c79 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
@@ -20,9 +20,6 @@ import org.junit.Rule
 import org.junit.Test
 import spock.lang.Issue
 
-/**
- * @author Szczepan Faber
- */
 class EclipseClasspathIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule
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
deleted file mode 100644
index acad7b3..0000000
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.ide.eclipse
-
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.test.fixtures.maven.MavenHttpRepository
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-class EclipseClasspathRemoteResolutionIntegrationTest extends AbstractEclipseIntegrationTest {
-
-    @Rule public final HttpServer server = new HttpServer()
-    @Rule public final TestResources testResources = new TestResources(testDirectoryProvider)
-    final def repo = new MavenHttpRepository(server, "/repo", mavenRepo)
-
-    @Before
-    void "setup"() {
-        executer.requireOwnGradleUserHomeDir()
-    }
-
-    @Test
-    void "does not break when source or javadoc artifacts are missing or broken"() {
-//        given:
-        def projectA = repo.module('group', 'projectA', '1.0').publish()
-        def projectB = repo.module('group', 'projectB', '1.0').publish()
-        server.start()
-
-//        when:
-        server.resetExpectations()
-        projectA.pom.expectGet()
-        projectA.artifact.expectGet()
-        projectA.artifact(classifier: 'sources').expectGetMissing()
-        projectA.artifact(classifier: 'javadoc').expectGetMissing()
-        projectB.pom.expectGet()
-        projectB.artifact.expectGet()
-        projectB.artifact(classifier: 'sources').expectGetBroken()
-        projectB.artifact(classifier: 'javadoc').expectGetBroken()
-
-//        and:
-        def result = runEclipseTask """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url "http://localhost:${server.port}/repo" }
-}
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0'
-}
-eclipse {
-    classpath.downloadJavadoc = true
-}
-"""
-//        then:
-        result.error == ''
-    }
-}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
deleted file mode 100644
index d9e0292..0000000
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,108 +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.ide.eclipse
-
-import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
-import org.junit.Rule
-
-class EclipseClasspathResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
-
-    def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA', '1.0')
-        projectA.artifact(classifier: 'sources')
-        projectA.artifact(classifier: 'javadoc')
-        projectA.publish()
-        def sourceJar = projectA.artifactFile(classifier: 'sources')
-        def javadocJar = projectA.artifactFile(classifier: 'javadoc')
-
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-dependencies {
-    compile 'group:projectA:1.0'
-}
-eclipse { classpath { downloadJavadoc = true } }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        then:
-        succeeds 'listJars'
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-sources.jar', sourceJar)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar', javadocJar)
-
-        then:
-        succeeds 'eclipseClasspath'
-    }
-
-    def "only attempts to download missing artifacts from HTTP Maven repository once"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA', '1.0')
-        projectA.publish()
-
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-dependencies {
-    compile 'group:projectA:1.0'
-}
-eclipse { classpath { downloadJavadoc = true } }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-sources.jar')
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar')
-
-        then:
-        succeeds 'eclipseClasspath'
-
-        when:
-        server.resetExpectations()
-
-        then:
-        succeeds 'eclipseClasspath'
-    }
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy
index 5e44ba1..f0d6343 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse
 
 import org.junit.Test
 
-/**
- * @author Szczepan Faber
- */
 class EclipseEarIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
index 00d4240..3c10693 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
@@ -20,9 +20,6 @@ import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Szczepan Faber, @date 01.03.11
- */
 class EclipseMultiModuleIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
     public final TestResources testResources = new TestResources(testDirectoryProvider)
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
index 970eaff..6531d59 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Szczepan Faber
- */
 class EclipseProjectIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseSourcesAndJavadocJarsIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseSourcesAndJavadocJarsIntegrationTest.groovy
new file mode 100644
index 0000000..df46e81
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseSourcesAndJavadocJarsIntegrationTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.eclipse
+
+import org.gradle.plugins.ide.AbstractSourcesAndJavadocJarsIntegrationTest
+import org.gradle.test.fixtures.maven.HttpArtifact
+
+class EclipseSourcesAndJavadocJarsIntegrationTest extends AbstractSourcesAndJavadocJarsIntegrationTest {
+    @Override
+    String getIdeTask() {
+        return "eclipseClasspath"
+    }
+
+    void ideFileContainsSourcesAndJavadocEntry(String sourcesClassifier = "sources", String javadocClassifier = "javadoc") {
+        def classpath = new EclipseClasspathFixture(testDirectory, executer.gradleUserHomeDir)
+        def lib = classpath.libs[0]
+        assert lib.sourcePath.endsWith("/module-1.0-${sourcesClassifier}.jar")
+        assert lib.javadocLocation.endsWith("/module-1.0-${javadocClassifier}.jar!/")
+    }
+
+    void ideFileContainsNoSourcesAndJavadocEntry() {
+        def classpath = new EclipseClasspathFixture(testDirectory, executer.gradleUserHomeDir)
+        def lib = classpath.libs[0]
+        lib.assertHasNoSource()
+        lib.assertHasNoJavadoc()
+    }
+
+    @Override
+    void expectBehaviorAfterBrokenMavenArtifact(HttpArtifact httpArtifact) {
+        httpArtifact.expectHead()
+    }
+
+    @Override
+    void expectBehaviorAfterBrokenIvyArtifact(HttpArtifact httpArtifact) {}
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
index d031ac9..e0cb1d2 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
@@ -22,9 +22,6 @@ import org.junit.Rule
 import org.junit.Test
 import spock.lang.Issue
 
-/**
- * @author Szczepan Faber, created at: 4/19/11
- */
 class EclipseWtpModelIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
index d53c9da..114017f 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
@@ -18,11 +18,11 @@ package org.gradle.plugins.ide.idea
 
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
+import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.gradle.test.fixtures.file.TestFile
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -95,15 +95,15 @@ apply plugin: 'idea'
         assertHasExpectedContents('root.iml')
     }
 
-    @Ignore
     @Test
     void addsScalaFacetAndCompilerLibraries() {
         executer.withTasks('idea').run()
 
-        assertHasExpectedContents('root.ipr')
-        assertHasExpectedContents('project1/project1.iml')
-        assertHasExpectedContents('project2/project2.iml')
-        assertHasExpectedContents('project3/project3.iml')
+        hasProjectLibrary('root.ipr', 'scala-compiler-2.9.2', ['scala-compiler-2.9.2.jar', 'scala-library-2.9.2.jar'], [], [])
+        hasProjectLibrary('root.ipr', 'scala-compiler-2.10.0', ['scala-compiler-2.10.0.jar', 'scala-library-2.10.0.jar', 'scala-reflect-2.10.0.jar'], [], [])
+        hasScalaFacet('project1/project1.iml', 'scala-compiler-2.9.2')
+        hasScalaFacet('project2/project2.iml', 'scala-compiler-2.10.0')
+        hasScalaFacet('project3/project3.iml', 'scala-compiler-2.9.2')
     }
 
     @Test
@@ -346,6 +346,24 @@ apply plugin: "idea"
         failure.output.contains("Perhaps this file was tinkered with?")
     }
 
+    @Test
+    void canAddProjectLibraries() {
+        runTask("idea", """
+apply plugin: 'idea'
+
+idea.project {
+    def lib = new org.gradle.plugins.ide.idea.model.ProjectLibrary()
+    lib.name = "someLib"
+    lib.classes << file("someClasses.jar")
+    lib.javadoc << file("someJavadoc.jar")
+    lib.sources << file("someSources.jar")
+    projectLibraries << lib
+}
+""")
+
+        hasProjectLibrary("root.ipr", "someLib", ["someClasses.jar"], ["someJavadoc.jar"], ["someSources.jar"])
+    }
+
     private void assertHasExpectedContents(String path) {
         TestFile file = testDirectory.file(path).assertIsFile()
         TestFile expectedFile = testDirectory.file("expectedFiles/${path}.xml").assertIsFile()
@@ -353,7 +371,7 @@ apply plugin: "idea"
         def expectedXml = expectedFile.text
 
         def homeDir = executer.gradleUserHomeDir.absolutePath.replace(File.separator, '/')
-        def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
+        def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/${CacheLayout.ROOT.getKey()}/${CacheLayout.FILE_STORE.getKey()}/([^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
         def actualXml = file.text.replaceAll(pattern, '@CACHE_DIR@/$1/@SHA1@/')
 
         Diff diff = new Diff(expectedXml, actualXml)
@@ -370,6 +388,51 @@ apply plugin: "idea"
         }
     }
 
+    private void hasProjectLibrary(String iprFileName, String libraryName, List<String> classesLibs, List<String> javadocLibs, List<String> sourcesLibs) {
+        def project = new XmlSlurper().parse(file(iprFileName))
+        def libraryTable = project.component.find { it. at name == "libraryTable" }
+        assert libraryTable
+
+        def library = libraryTable.library.find { it. at name == libraryName }
+        assert library
+
+        def classesRoots = library.CLASSES.root
+        assert classesRoots.size() == classesLibs.size()
+        classesLibs.each {
+            assert classesRoots. at url.text().contains(it)
+        }
+
+        def javadocRoots = library.JAVADOC.root
+        assert javadocRoots.size() == javadocLibs.size()
+        javadocLibs.each {
+            assert javadocRoots. at url.text().contains(it)
+        }
+
+        def sourcesRoots = library.SOURCES.root
+        assert sourcesRoots.size() == sourcesLibs.size()
+        sourcesLibs.each {
+            assert sourcesRoots. at url.text().contains(it)
+        }
+    }
+
+    private void hasScalaFacet(String imlFileName, String libraryName) {
+        def module = new XmlSlurper().parse(file(imlFileName))
+        def facetManager = module.component.find { it. at name == "FacetManager" }
+        assert facetManager
+
+        def facet = facetManager.facet.find { it. at name == "Scala" }
+        assert facet
+        assert facet. at type == "scala"
+
+        def compilerLibraryLevel = facet.configuration.option.find { it. at name == "compilerLibraryLevel" }
+        assert compilerLibraryLevel
+        assert compilerLibraryLevel. at value == "Project"
+
+        def compilerLibraryName = facet.configuration.option.find { it. at name == "compilerLibraryName" }
+        assert compilerLibraryName
+        assert compilerLibraryName. at value == libraryName
+    }
+
     private containsDir(path, urls) {
         urls.any { it.endsWith(path) }
     }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
index 08a2bf8..29487ac 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
@@ -367,4 +367,202 @@ project(':impl') {
         assert content.count("someApiProject") == 1
         assert content.count("unresolved dependency - i.dont Exist 1.0") == 1
     }
+
+    @Issue("GRADLE-2017")
+    @Test
+    void "create external dependency in more scopes when needed"() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("org.gradle", "api-artifact").publish()
+        maven(repoDir).module("org.gradle", "impl-artifact").publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+dependencies {
+    compile 'org.gradle:api-artifact:1.0'
+    testCompile 'org.gradle:impl-artifact:1.0'
+    runtime 'org.gradle:impl-artifact:1.0'
+}
+"""
+        def iml = parseFile(print: true, 'root.iml')
+
+        //then
+        assert iml.component.orderEntry. at scope.collect { it.text() == ['RUNTIME', 'TEST'] }
+
+        def orderEntries = iml.component.orderEntry
+        assert orderEntries.findAll { it. at type == 'module-library' }.size() == 3
+        assert orderEntries.any { it. at type == 'module-library' && // no scope means COMPILE
+                it.library.CLASSES.root. at url.text().contains ('api-artifact-1.0.jar') }
+        assert orderEntries.any { it. at type == 'module-library' && it. at scope == 'RUNTIME' &&
+                it.library.CLASSES.root. at url.text().contains ('impl-artifact-1.0.jar') }
+        assert orderEntries.any { it. at type == 'module-library' && it. at scope == 'TEST' &&
+                it.library.CLASSES.root. at url.text().contains ('impl-artifact-1.0.jar') }
+    }
+
+    @Test
+    void "provided wins over compile scope for compile configuration"() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("org.gradle", "api-artifact").publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+dependencies {
+    compile 'org.gradle:api-artifact:1.0'
+}
+
+idea {
+  module {
+    scopes.PROVIDED.plus += configurations.compile
+  }
+}
+"""
+        def iml = parseFile(print: true, 'root.iml')
+
+        //then
+        assert iml.component.orderEntry. at scope.collect { it.text() == ['PROVIDED'] }
+
+        def orderEntries = iml.component.orderEntry
+        assert orderEntries.findAll { it. at type == 'module-library' }.size() == 1
+        assert orderEntries.any { it. at type == 'module-library' && it. at scope == 'PROVIDED' &&
+                it.library.CLASSES.root. at url.text().contains ('api-artifact-1.0.jar') }
+    }
+
+    @Test
+    void "custom configuration gets first scope"() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("org.gradle", "api-artifact").publish()
+        maven(repoDir).module("foo", "bar").publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+configurations {
+  myCustom
+}
+
+dependencies {
+    myCustom 'foo:bar:1.0'
+    compile 'org.gradle:api-artifact:1.0'
+}
+
+idea {
+  module {
+    scopes.PROVIDED.plus += configurations.myCustom
+    scopes.COMPILE.plus += configurations.myCustom
+  }
+}
+"""
+        def iml = parseFile(print: true, 'root.iml')
+
+        //then
+        assert iml.component.orderEntry. at scope.collect { it.text() == ['PROVIDED'] }
+
+        def orderEntries = iml.component.orderEntry
+        assert orderEntries.findAll { it. at type == 'module-library' }.size() == 2
+        assert orderEntries.any { it. at type == 'module-library' && it. at scope == 'PROVIDED' &&
+                it.library.CLASSES.root. at url.text().contains ('bar-1.0.jar') }
+        assert orderEntries.any { it. at type == 'module-library' &&
+                it.library.CLASSES.root. at url.text().contains ('api-artifact-1.0.jar') }
+    }
+
+    @Test
+    void "custom configuration can be added to TEST and RUNTIME"() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("org.gradle", "api-artifact").publish()
+        maven(repoDir).module("foo", "bar").publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+configurations {
+  myCustom
+}
+
+dependencies {
+    myCustom 'foo:bar:1.0'
+    compile 'org.gradle:api-artifact:1.0'
+}
+
+idea {
+  module {
+    scopes.RUNTIME_TEST = [:]
+    scopes.RUNTIME_TEST.plus = [configurations.myCustom]
+    // scopes.TEST.plus += configurations.myCustom
+    // scopes.RUNTIME.plus += configurations.myCustom
+  }
+}
+"""
+        def iml = parseFile(print: true, 'root.iml')
+
+        //then
+        assert iml.component.orderEntry. at scope.collect { it.text() == ['TEST', 'RUNTIME'] }
+
+        def orderEntries = iml.component.orderEntry
+        assert orderEntries.findAll { it. at type == 'module-library' }.size() == 3
+        assert orderEntries.any { it. at type == 'module-library' &&
+                it.library.CLASSES.root. at url.text().contains ('api-artifact-1.0.jar') }
+        assert orderEntries.any { it. at type == 'module-library' && it. at scope == 'TEST' &&
+                it.library.CLASSES.root. at url.text().contains ('bar-1.0.jar') }
+        assert orderEntries.any { it. at type == 'module-library' && it. at scope == 'RUNTIME' &&
+                it.library.CLASSES.root. at url.text().contains ('bar-1.0.jar') }
+    }
+
+    @Test
+    void "no libraries generated without java plugin"() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("org.gradle", "api-artifact").publish()
+        maven(repoDir).module("foo", "bar").publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+configurations {
+  compile
+}
+
+dependencies {
+    compile 'org.gradle:api-artifact:1.0'
+}
+"""
+        def iml = parseFile(print: true, 'root.iml')
+
+        //then
+        def orderEntries = iml.component.orderEntry
+        assert orderEntries.findAll { it. at type == 'module-library' }.size() == 0
+    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy
index 773e9c6..260771f 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy
@@ -20,9 +20,6 @@ import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
 class IdeaMultiModuleIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
     public final TestResources testResources = new TestResources(testDirectoryProvider)
@@ -302,4 +299,54 @@ allprojects {
         String content = getFile(project: 'master', 'master.ipr').text
         assert content.count('filepath="$PROJECT_DIR$/api/api.iml"') == 1
     }
+
+    @Test
+    void buildsCorrectModuleDependenciesWithScopes() {
+        def settingsFile = file("master/settings.gradle")
+        settingsFile << """
+include 'api'
+include 'impl'
+include 'app'
+"""
+
+        def buildFile = file("master/build.gradle")
+        buildFile << """
+allprojects {
+    apply plugin: 'java'
+    apply plugin: 'idea'
+}
+
+project(':impl') {
+    dependencies {
+        compile project(':api')
+    }
+}
+
+project(':app') {
+    dependencies {
+        compile project(':api')
+        testCompile project(':impl')
+        runtime project(':impl')
+    }
+}
+"""
+
+        //when
+        executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withTasks("ideaModule").run()
+        def iml = parseFile(project: 'master/app', print: true, 'app.iml')
+
+        //then
+        assert iml.component.orderEntry. at scope.collect { it.text() == ['RUNTIME', 'TEST'] }
+
+        def orderEntries = iml.component.orderEntry
+        assert orderEntries.size() == 5
+        assert orderEntries.any { it. at type == 'inheritedJdk' }
+        assert orderEntries.any { it. at type == 'sourceFolder' }
+        assert orderEntries.any { it. at type == 'module' &&
+                it.'@module-name'.text().contains ('api') }
+        assert orderEntries.any { it. at type == 'module' && it. at scope == 'RUNTIME' &&
+                it.'@module-name'.text().contains ('impl') }
+        assert orderEntries.any { it. at type == 'module' && it. at scope == 'TEST' &&
+                it.'@module-name'.text().contains ('impl') }
+    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaSourcesAndJavadocJarsIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaSourcesAndJavadocJarsIntegrationTest.groovy
new file mode 100644
index 0000000..f76d16c
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaSourcesAndJavadocJarsIntegrationTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.idea
+
+import org.gradle.plugins.ide.AbstractSourcesAndJavadocJarsIntegrationTest
+import org.gradle.test.fixtures.maven.HttpArtifact
+
+class IdeaSourcesAndJavadocJarsIntegrationTest extends AbstractSourcesAndJavadocJarsIntegrationTest {
+    @Override
+    String getIdeTask() {
+        return "ideaModule"
+    }
+
+    void ideFileContainsSourcesAndJavadocEntry(String sourcesClassifier = "sources", String javadocClassifier = "javadoc") {
+        def iml = parseFile("root.iml")
+
+        def sourcesUrl = iml.component.orderEntry.library.SOURCES.root. at url[0].text()
+        assert sourcesUrl.endsWith("/module-1.0-${sourcesClassifier}.jar!/")
+
+        def javadocUrl = iml.component.orderEntry.library.JAVADOC.root. at url[0].text()
+        assert javadocUrl.endsWith("/module-1.0-${javadocClassifier}.jar!/")
+    }
+
+    void ideFileContainsNoSourcesAndJavadocEntry() {
+        def iml = parseFile("root.iml")
+
+        assert iml.component.orderEntry.library.SOURCES.root.size() == 0
+
+        assert iml.component.orderEntry.library.JAVADOC.root.size() == 0
+    }
+
+    @Override
+    void expectBehaviorAfterBrokenMavenArtifact(HttpArtifact httpArtifact) {
+        httpArtifact.expectHead()
+        httpArtifact.expectGet()
+    }
+
+    @Override
+    void expectBehaviorAfterBrokenIvyArtifact(HttpArtifact httpArtifact) {
+        httpArtifact.expectGet()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy
index 5fb85ba..5716992 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.plugins.ide.idea
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Test
 
-/**
- * @author: Szczepan Faber, created at: 6/9/11
- */
 class IdeaWorkspaceIntegrationTest extends AbstractIdeIntegrationTest {
 
     @Test
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
index 2122871..33179ec 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
@@ -6,13 +6,13 @@
 	<classpathentry kind="src" path="src/test/resources"/>
 	<classpathentry kind="src" path="src/test/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar" kind="lib"
-					path="@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar" exported="true">
+	<classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2-sources.jar" kind="lib"
+					path="@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar" exported="true">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true">
+	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar" exported="true">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
index b643e50..d4e5d41 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
@@ -6,7 +6,7 @@
 	<classpathentry kind="src" path="src/test/resources"/>
 	<classpathentry kind="src" path="src/test/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar" kind="lib"
-					path="@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar" exported="true"/>
-	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true"/>
+	<classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2-sources.jar" kind="lib"
+					path="@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar" exported="true"/>
+	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
index a28206f..63eb179 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
@@ -3,11 +3,11 @@
 	<classpathentry kind="src" path="src/main/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
 	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
-	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/source/@SHA1@/commons-lang-2.5-sources.jar" kind="var"
-					path="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/jar/@SHA1@/commons-lang-2.5.jar" exported="true">
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5-sources.jar" kind="var"
+					path="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5.jar" exported="true">
 		<attributes>
-			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/javadoc/@SHA1@/commons-lang-2.5-javadoc.jar!/"/>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true"/>
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/@SHA1@/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
index 4fd25c0..392f91b 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
@@ -2,7 +2,7 @@
 	<wb-module deploy-name="webAppWithVars">
 		<property name="context-root" value="webAppWithVars"/>
 		<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
-		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/var/GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/jar/@SHA1@/commons-lang-2.5.jar">
+		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/var/GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5.jar">
 			<dependency-type>uses</dependency-type>
 		</dependent-module>
 	</wb-module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
index e075b38..60f544f 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
@@ -4,17 +4,17 @@
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
 	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
 	<classpathentry kind="src" path="/api" exported="true"/>
-	<classpathentry sourcepath="@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/source/@SHA1@/slf4j-api-1.5.8-sources.jar" kind="lib" path="@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/jar/@SHA1@/slf4j-api-1.5.8.jar" exported="true"/>
-	<classpathentry sourcepath="@CACHE_DIR@/commons-lang/commons-lang/2.5/source/@SHA1@/commons-lang-2.5-sources.jar" kind="lib" path="@CACHE_DIR@/commons-lang/commons-lang/2.5/jar/@SHA1@/commons-lang-2.5.jar"
+	<classpathentry sourcepath="@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/@SHA1@/slf4j-api-1.5.8-sources.jar" kind="lib" path="@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/@SHA1@/slf4j-api-1.5.8.jar" exported="true"/>
+	<classpathentry sourcepath="@CACHE_DIR@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5-sources.jar" kind="lib" path="@CACHE_DIR@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5.jar"
 					exported="true">
 		<attributes>
-			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/javadoc/@SHA1@/commons-lang-2.5-javadoc.jar!/"/>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="@CACHE_DIR@/commons-io/commons-io/1.2/source/@SHA1@/commons-io-1.2-sources.jar" kind="lib" path="@CACHE_DIR@/commons-io/commons-io/1.2/jar/@SHA1@/commons-io-1.2.jar" exported="true">
+	<classpathentry sourcepath="@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2-sources.jar" kind="lib" path="@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2.jar" exported="true">
 		<attributes>
-			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-io/commons-io/1.2/javadoc/@SHA1@/commons-io-1.2-javadoc.jar!/"/>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true"/>
+	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
index 3974e56..e60b197 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
@@ -5,7 +5,7 @@
 		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/resource/api/api">
 			<dependency-type>uses</dependency-type>
 		</dependent-module>
-		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib/@CACHE_DIR@/commons-lang/commons-lang/2.5/jar/@SHA1@/commons-lang-2.5.jar">
+		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib/@CACHE_DIR@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5.jar">
 			<dependency-type>uses</dependency-type>
 		</dependent-module>
 	</wb-module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
index 32cad06..55e556e 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
@@ -1,3 +1,5 @@
+import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
+
 import java.util.regex.Pattern
 import junit.framework.AssertionFailedError
 import org.custommonkey.xmlunit.Diff
@@ -130,9 +132,9 @@ String getExpectedXml(File file) {
 
 String getActualXml(File file) {
     def homeDir = gradle.gradleUserHomeDir.absolutePath.replace(File.separator, '/')
-    def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
+    def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/${CacheLayout.ROOT.getKey()}/${CacheLayout.FILE_STORE.getKey()}/([^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
     def text = file.text.replaceAll(pattern, '@CACHE_DIR@/$1/@SHA1@/')
-    pattern = Pattern.compile("GRADLE_USER_HOME/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
+    pattern = Pattern.compile("GRADLE_USER_HOME/${CacheLayout.ROOT.getKey()}/${CacheLayout.FILE_STORE.getKey()}/([^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
     text = text.replaceAll(pattern, 'GRADLE_USER_HOME/@CACHE@/$1/@SHA1@/')
 
     //remove trailing slashes for windows paths
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project1/project1.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project1/project1.iml.xml
deleted file mode 100644
index e430939..0000000
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project1/project1.iml.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module relativePaths="true" type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
-    <exclude-output/>
-    <orderEntry type="inheritedJdk"/>
-    <content url="file://$MODULE_DIR$/">
-      <excludeFolder url="file://$MODULE_DIR$/build"/>
-      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
-    </content>
-    <orderEntry type="sourceFolder" forTests="false"/>
-    <orderEntry type="module-library" exported="">
-      <library>
-        <CLASSES>
-          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/jar/@SHA1@/scala-library-2.9.2.jar!/"/>
-        </CLASSES>
-        <JAVADOC/>
-        <SOURCES>
-          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/source/@SHA1@/scala-library-2.9.2-sources.jar!/"/>
-        </SOURCES>
-      </library>
-    </orderEntry>
-  </component>
-  <component name="ModuleRootManager"/>
-  <component name="FacetManager">
-    <facet type="scala" name="Scala">
-      <configuration>
-        <option name="compilerLibraryLevel" value="Project"/>
-        <option name="compilerLibraryName" value="scala-compiler-2.9.2"/>
-      </configuration>
-    </facet>
-  </component>
-</module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project2/project2.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project2/project2.iml.xml
deleted file mode 100644
index d9f8fb8..0000000
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project2/project2.iml.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module relativePaths="true" type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
-    <exclude-output/>
-    <orderEntry type="inheritedJdk"/>
-    <content url="file://$MODULE_DIR$/">
-      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
-      <excludeFolder url="file://$MODULE_DIR$/build"/>
-    </content>
-    <orderEntry type="sourceFolder" forTests="false"/>
-    <orderEntry type="module-library" exported="">
-      <library>
-        <CLASSES>
-          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.10.0/jar/@SHA1@/scala-library-2.10.0.jar!/"/>
-        </CLASSES>
-        <JAVADOC/>
-        <SOURCES>
-          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.10.0/source/@SHA1@/scala-library-2.10.0-sources.jar!/"/>
-        </SOURCES>
-      </library>
-    </orderEntry>
-  </component>
-  <component name="ModuleRootManager"/>
-  <component name="FacetManager">
-    <facet type="scala" name="Scala">
-      <configuration>
-        <option name="compilerLibraryLevel" value="Project"/>
-        <option name="compilerLibraryName" value="scala-compiler-2.10.0"/>
-      </configuration>
-    </facet>
-  </component>
-</module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project3/project3.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project3/project3.iml.xml
deleted file mode 100644
index e430939..0000000
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project3/project3.iml.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module relativePaths="true" type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
-    <exclude-output/>
-    <orderEntry type="inheritedJdk"/>
-    <content url="file://$MODULE_DIR$/">
-      <excludeFolder url="file://$MODULE_DIR$/build"/>
-      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
-    </content>
-    <orderEntry type="sourceFolder" forTests="false"/>
-    <orderEntry type="module-library" exported="">
-      <library>
-        <CLASSES>
-          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/jar/@SHA1@/scala-library-2.9.2.jar!/"/>
-        </CLASSES>
-        <JAVADOC/>
-        <SOURCES>
-          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/source/@SHA1@/scala-library-2.9.2-sources.jar!/"/>
-        </SOURCES>
-      </library>
-    </orderEntry>
-  </component>
-  <component name="ModuleRootManager"/>
-  <component name="FacetManager">
-    <facet type="scala" name="Scala">
-      <configuration>
-        <option name="compilerLibraryLevel" value="Project"/>
-        <option name="compilerLibraryName" value="scala-compiler-2.9.2"/>
-      </configuration>
-    </facet>
-  </component>
-</module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/root.ipr.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/root.ipr.xml
deleted file mode 100644
index e8f66e8..0000000
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/root.ipr.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="CompilerConfiguration">
-    <option name="DEFAULT_COMPILER" value="Javac"/>
-    <resourceExtensions>
-      <entry name=".+\.(properties|xml|html|dtd|tld)"/>
-      <entry name=".+\.(gif|png|jpeg|jpg)"/>
-    </resourceExtensions>
-    <wildcardResourcePatterns>
-      <entry name="!?*.groovy"/>
-      <entry name="!?*.java"/>
-    </wildcardResourcePatterns>
-    <annotationProcessing enabled="false" useClasspath="true"/>
-  </component>
-  <component name="CopyrightManager" default="">
-    <module2copyright/>
-  </component>
-  <component name="DependencyValidationManager">
-    <option name="SKIP_IMPORT_STATEMENTS" value="false"/>
-  </component>
-  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"/>
-  <component name="GradleUISettings">
-    <setting name="root"/>
-  </component>
-  <component name="GradleUISettings2">
-    <setting name="root"/>
-  </component>
-  <component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C"/>
-  <component name="JavadocGenerationManager">
-    <option name="OUTPUT_DIRECTORY"/>
-    <option name="OPTION_SCOPE" value="protected"/>
-    <option name="OPTION_HIERARCHY" value="true"/>
-    <option name="OPTION_NAVIGATOR" value="true"/>
-    <option name="OPTION_INDEX" value="true"/>
-    <option name="OPTION_SEPARATE_INDEX" value="true"/>
-    <option name="OPTION_DOCUMENT_TAG_USE" value="false"/>
-    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false"/>
-    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false"/>
-    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true"/>
-    <option name="OPTION_DEPRECATED_LIST" value="true"/>
-    <option name="OTHER_OPTIONS" value=""/>
-    <option name="HEAP_SIZE"/>
-    <option name="LOCALE"/>
-    <option name="OPEN_IN_BROWSER" value="true"/>
-  </component>
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/root.iml" filepath="$PROJECT_DIR$/root.iml"/>
-      <module fileurl="file://$PROJECT_DIR$/project3/project3.iml" filepath="$PROJECT_DIR$/project3/project3.iml"/>
-      <module fileurl="file://$PROJECT_DIR$/project2/project2.iml" filepath="$PROJECT_DIR$/project2/project2.iml"/>
-      <module fileurl="file://$PROJECT_DIR$/project1/project1.iml" filepath="$PROJECT_DIR$/project1/project1.iml"/>
-    </modules>
-  </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.7">
-    <output url="file://$PROJECT_DIR$/out"/>
-  </component>
-  <component name="SvnBranchConfigurationManager">
-    <option name="mySupportsUserInfoFilter" value="true"/>
-  </component>
-  <component name="VcsDirectoryMappings">
-    <mapping directory="" vcs=""/>
-  </component>
-  <component name="masterDetails">
-    <states>
-      <state key="ArtifactsStructureConfigurable.UI">
-        <UIState>
-          <splitter-proportions>
-            <SplitterProportionsDataImpl/>
-          </splitter-proportions>
-          <settings/>
-        </UIState>
-      </state>
-      <state key="Copyright.UI">
-        <UIState>
-          <splitter-proportions>
-            <SplitterProportionsDataImpl/>
-          </splitter-proportions>
-        </UIState>
-      </state>
-      <state key="ProjectJDKs.UI">
-        <UIState>
-          <splitter-proportions>
-            <SplitterProportionsDataImpl>
-              <option name="proportions">
-                <list>
-                  <option value="0.2"/>
-                </list>
-              </option>
-            </SplitterProportionsDataImpl>
-          </splitter-proportions>
-          <last-edited>1.6</last-edited>
-        </UIState>
-      </state>
-      <state key="ScopeChooserConfigurable.UI">
-        <UIState>
-          <splitter-proportions>
-            <SplitterProportionsDataImpl/>
-          </splitter-proportions>
-          <settings/>
-        </UIState>
-      </state>
-    </states>
-  </component>
-  <component name="libraryTable">
-    <library name="scala-compiler-2.9.2">
-      <CLASSES>
-        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-compiler/2.9.2/jar/@SHA1@/scala-compiler-2.9.2.jar!/"/>
-        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/jar/@SHA1@/scala-library-2.9.2.jar!/"/>
-      </CLASSES>
-      <JAVADOC/>
-      <SOURCES/>
-    </library>
-    <library name="scala-compiler-2.10.0">
-      <CLASSES>
-        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-compiler/2.10.0/jar/@SHA1@/scala-compiler-2.10.0.jar!/"/>
-        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.10.0/jar/@SHA1@/scala-library-2.10.0.jar!/"/>
-        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-reflect/2.10.0/jar/@SHA1@/scala-reflect-2.10.0.jar!/"/>
-      </CLASSES>
-      <JAVADOC/>
-      <SOURCES/>
-    </library>
-  </component>
-</project>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml
index 2ad650e..c094665 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml
@@ -12,22 +12,22 @@
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" scope="TEST">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml
index 771a48d..e9f0b81 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml
@@ -4,18 +4,18 @@
     <orderEntry type="inheritedJdk"/>
     <content url="file://$MODULE_DIR$/">
       <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false"/>
-      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
       <excludeFolder url="file://$MODULE_DIR$/build"/>
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
     </content>
     <orderEntry type="sourceFolder" forTests="false"/>
-    <orderEntry type="module-library" exported="">
+    <orderEntry type="module-library" scope="PROVIDED">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/jar/@SHA1@/slf4j-api-1.5.8.jar!/"/>
+          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/@SHA1@/slf4j-api-1.5.8.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/source/@SHA1@/slf4j-api-1.5.8-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/@SHA1@/slf4j-api-1.5.8-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
@@ -32,37 +32,37 @@
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/jar/@SHA1@/commons-lang-2.4.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/@SHA1@/commons-lang-2.4.jar!/"/>
         </CLASSES>
         <JAVADOC>
-          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/javadoc/@SHA1@/commons-lang-2.4-javadoc.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/@SHA1@/commons-lang-2.4-javadoc.jar!/"/>
         </JAVADOC>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/source/@SHA1@/commons-lang-2.4-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/@SHA1@/commons-lang-2.4-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/jar/@SHA1@/commons-io-1.2.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2.jar!/"/>
         </CLASSES>
         <JAVADOC>
-          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/javadoc/@SHA1@/commons-io-1.2-javadoc.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2-javadoc.jar!/"/>
         </JAVADOC>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/source/@SHA1@/commons-io-1.2-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" scope="TEST">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml
index 39d78c7..3012f22 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml
@@ -10,22 +10,22 @@
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
index 17e063a..18432c7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
@@ -23,8 +23,6 @@ import org.gradle.listener.ActionBroadcast
  * Models the generation/parsing/merging capabilities.
  * <p>
  * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
- *
- * @author: Szczepan Faber, created at: 4/21/11
  */
 class FileContentMerger {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java
index 8654da6..7058bf0 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java
@@ -60,13 +60,14 @@ public class GeneratorTask<T> extends ConventionTask {
     @SuppressWarnings("UnusedDeclaration")
     @TaskAction
     void generate() {
-        if (getInputFile().exists()) {
+        File inputFile = getInputFile();
+        if (inputFile != null && inputFile.exists()) {
             try {
-                domainObject = generator.read(getInputFile());
+                domainObject = generator.read(inputFile);
             } catch (RuntimeException e) {
                 throw new GradleException(String.format("Cannot parse file '%s'.\n"
                         + "       Perhaps this file was tinkered with? In that case try delete this file and then retry.",
-                        getInputFile()), e);
+                        inputFile), e);
             }
         } else {
             domainObject = generator.defaultInstance();
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
index fe58c62..ad9f0ac 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
@@ -19,11 +19,9 @@ package org.gradle.plugins.ide.api
 import org.gradle.api.internal.xml.XmlTransformer
 
 /**
- * Models the generation/parsing/merging capabilities. Adds xml-related hooks.
+ * Models the generation/parsing/merging capabilities. Adds XML-related hooks.
  * <p>
  * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
- *
- * @author: Szczepan Faber, created at: 4/21/11
  */
 class XmlFileContentMerger extends FileContentMerger {
 
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 7d41b65..3a14788 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
@@ -17,11 +17,11 @@ 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
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.scala.ScalaBasePlugin
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ear.EarPlugin
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.eclipse.internal.EclipseNameDeduper
@@ -35,8 +35,6 @@ import javax.inject.Inject
 
 /**
  * <p>A plugin which generates Eclipse files.</p>
- *
- * @author Hans Dockter
  */
 class EclipsePlugin extends IdePlugin {
     static final String ECLIPSE_TASK_NAME = "eclipse"
@@ -178,7 +176,7 @@ class EclipsePlugin extends IdePlugin {
 
     private void maybeAddTask(Project project, IdePlugin plugin, String taskName, Class taskType, Closure action) {
         if (project.tasks.findByName(taskName)) { return }
-        def task = project.tasks.add(taskName, taskType)
+        def task = project.tasks.create(taskName, taskType)
         project.configure(task, action)
         plugin.addWorker(task)
     }
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 373d7e4..a05c003 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
@@ -19,19 +19,16 @@ package org.gradle.plugins.ide.eclipse
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.WarPlugin
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ear.EarPlugin
+import org.gradle.plugins.ide.eclipse.model.*
 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
- */
 class EclipseWtpPlugin extends IdePlugin {
 
     static final String ECLIPSE_WTP_COMPONENT_TASK_NAME = "eclipseWtpComponent"
@@ -213,7 +210,7 @@ class EclipseWtpPlugin extends IdePlugin {
 
     private void maybeAddTask(Project project, IdePlugin plugin, String taskName, Class taskType, Closure action) {
         if (project.tasks.findByName(taskName)) { return }
-        def task = project.tasks.add(taskName, taskType)
+        def task = project.tasks.create(taskName, taskType)
         project.configure(task, action)
         plugin.addWorker(task)
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy
index 020c7b2..ba77fd3 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy
@@ -26,8 +26,6 @@ import org.gradle.util.DeprecationLogger
  * Generates an Eclipse <code>.classpath</code> file. If you want to fine tune the eclipse configuration
  * <p>
  * At this moment nearly all configuration is done via {@link EclipseClasspath}.
- *
- * @author Hans Dockter
  */
 class GenerateEclipseClasspath extends XmlGeneratorTask<Classpath> {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
index 784f13b..cc1992f 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
@@ -25,8 +25,6 @@ import org.gradle.plugins.ide.eclipse.model.Project
  * Generates an Eclipse <code>.project</code> file. If you want to fine tune the eclipse configuration
  * <p>
  * At this moment nearly all configuration is done via {@link EclipseProject}.
- *
- * @author Hans Dockter
  */
 class GenerateEclipseProject extends XmlGeneratorTask<Project> {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
index 4afadc0..5632f4c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
@@ -30,8 +30,6 @@ import org.gradle.util.DeprecationLogger
  * If you want to fine tune the eclipse configuration
  * <p>
  * At this moment nearly all configuration is done via {@link EclipseWtpComponent}.
- *
- * @author Hans Dockter
  */
 class GenerateEclipseWtpComponent extends XmlGeneratorTask<WtpComponent> {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
index e45956e..8adf9ef 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
@@ -26,8 +26,6 @@ import org.gradle.plugins.ide.eclipse.model.WtpFacet
  * If you want to fine tune the eclipse configuration
  * <p>
  * At this moment nearly all configuration is done via {@link EclipseWtpFacet}.
- *
- * @author Hans Dockter
  */
 class GenerateEclipseWtpFacet extends XmlGeneratorTask<WtpFacet> {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy
index 6907897..424339a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy
@@ -20,9 +20,6 @@ import org.gradle.plugins.ide.eclipse.EclipsePlugin
 import org.gradle.plugins.ide.internal.configurer.DeduplicationTarget
 import org.gradle.plugins.ide.internal.configurer.ProjectDeduper
 
-/**
- * @author Szczepan Faber, @date 11.03.11
- */
 class EclipseNameDeduper {
 
     void configureRoot(Project rootProject) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy
index 017c677..55fc3b7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy
@@ -16,13 +16,10 @@
 
 package org.gradle.plugins.ide.eclipse.internal
 
+import org.gradle.api.Project
 import org.gradle.plugins.ide.eclipse.model.Link
 import org.gradle.plugins.ide.eclipse.model.internal.SourceFoldersCreator
-import org.gradle.api.Project
 
-/**
- * @author: Szczepan Faber, created at: 4/22/11
- */
 class LinkedResourcesCreator {
 
     Set<Link> links(Project project) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy
index 1c12689..be6b334 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.PathUtil
 
-/**
- * @author Hans Dockter
- */
 // TODO: consider entryAttributes in equals, hashCode, and toString
 abstract class AbstractClasspathEntry implements ClasspathEntry {
     private static final String NATIVE_LIBRARY_ATTRIBUTE = 'org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY'
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
index 2bb9703..f19fad5 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
@@ -19,9 +19,6 @@ import org.gradle.api.Nullable
 import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
-/**
- * @author Hans Dockter
- */
 abstract class AbstractLibrary extends AbstractClasspathEntry {
     FileReference sourcePath
     FileReference javadocPath
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AccessRule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AccessRule.groovy
index 39068cb..edaa202 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AccessRule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AccessRule.groovy
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-/**
- * @author Hans Dockter
- */
 class AccessRule {
     String kind
     String pattern
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/BuildCommand.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/BuildCommand.groovy
index f84cde8..5ac290a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/BuildCommand.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/BuildCommand.groovy
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-/**
- * @author Hans Dockter
- */
 class BuildCommand implements Serializable {
     String name
     Map arguments
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
index eac13e8..40f5993 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
@@ -16,13 +16,11 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.internal.xml.XmlTransformer
-import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
  * Represents the customizable elements of an eclipse classpath file. (via XML hooks everything is customizable).
- *
- * @author Hans Dockter
  */
 class Classpath extends XmlPersistableConfigurationObject {
     private final FileReferenceFactory fileReferenceFactory
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathEntry.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathEntry.java
index e4349b2..f569847 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathEntry.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathEntry.java
@@ -19,8 +19,6 @@ import groovy.util.Node;
 
 /**
  * Represents an entry in the Eclipse classpath.
- * 
- * @author Hans Dockter
  */
 public interface ClasspathEntry {
     String getKind();
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy
index 2bd8c7e..c8a4f2b 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-/**
- * @author Hans Dockter
- */
 class Container extends AbstractClasspathEntry {
     Container(Node node) {
         super(node)
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 d8abbe3..4e92eb3 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
@@ -82,7 +82,7 @@ import org.gradle.util.ConfigureUtil
  * eclipse {
  *   classpath {
  *     file {
- *       //if you want to mess with the resulting xml in whatever way you fancy
+ *       //if you want to mess with the resulting XML in whatever way you fancy
  *       withXml {
  *         def node = it.asNode()
  *         node.appendNode('xml', 'is what I love')
@@ -103,8 +103,6 @@ import org.gradle.util.ConfigureUtil
  *   }
  * }
  * </pre>
- *
- * @author Szczepan Faber, created at: 4/16/11
  */
 class EclipseClasspath {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseDomainModel.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseDomainModel.groovy
index 0e2ecce..8390c05 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseDomainModel.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseDomainModel.groovy
@@ -18,8 +18,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 /**
  * For now, we only need the Project. However this class will contain more domain objects soon.
- *
- * @author Szczepan Faber, @date: 17.03.11
  */
 class EclipseDomainModel {
     Project project
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy
index b6f81f1..df89f78 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy
@@ -53,8 +53,6 @@ import org.gradle.util.ConfigureUtil
  *   }
  * }
  * </pre>
- *
- * @author: Szczepan Faber, created at: 4/20/11
  */
 class EclipseJdt {
 
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 5dd379b..618e54f 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
@@ -44,8 +44,6 @@ import org.gradle.util.ConfigureUtil
  * </pre>
  *
  * More examples in docs for {@link EclipseProject}, {@link EclipseClasspath}, {@link EclipseWtp}
- *
- * @author: Szczepan Faber, created at: 4/13/11
  */
 class EclipseModel {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy
index 7e5d2f3..1ead6ab 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy
@@ -61,7 +61,7 @@ import org.gradle.util.ConfigureUtil
  * }
  * </pre>
  *
- * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * For tackling edge cases users can perform advanced configuration on resulting XML file.
  * It is also possible to affect the way eclipse plugin merges the existing configuration
  * via beforeMerged and whenMerged closures.
  * <p>
@@ -77,7 +77,7 @@ import org.gradle.util.ConfigureUtil
  *   project {
  *
  *     file {
- *       //if you want to mess with the resulting xml in whatever way you fancy
+ *       //if you want to mess with the resulting XML in whatever way you fancy
  *       withXml {
  *         def node = it.asNode()
  *         node.appendNode('xml', 'is what I love')
@@ -99,8 +99,6 @@ import org.gradle.util.ConfigureUtil
  *   }
  * }
  * </pre>
- *
- * @author Szczepan Faber, created at: 4/13/11
  */
 class EclipseProject {
 
@@ -235,7 +233,7 @@ class EclipseProject {
     }
 
     /**
-     * Enables advanced configuration like tinkering with the output xml
+     * Enables advanced configuration like tinkering with the output XML
      * or affecting the way existing .project content is merged with gradle build information
      * <p>
      * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link Project}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy
index 8d65c5c..66a16b7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy
@@ -45,8 +45,6 @@ import org.gradle.util.ConfigureUtil
  * }
  *
  * </pre>
- *
- * @author: Szczepan Faber, created at: 4/19/11
  */
 class EclipseWtp {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy
index 6879b25..89c7680 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy
@@ -73,7 +73,7 @@ import org.gradle.util.ConfigureUtil
  * }
  * </pre>
  *
- * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * For tackling edge cases users can perform advanced configuration on resulting XML file.
  * It is also possible to affect the way eclipse plugin merges the existing configuration
  * via beforeMerged and whenMerged closures.
  * <p>
@@ -91,7 +91,7 @@ import org.gradle.util.ConfigureUtil
  *   wtp {
  *     component {
  *       file {
- *         //if you want to mess with the resulting xml in whatever way you fancy
+ *         //if you want to mess with the resulting XML in whatever way you fancy
  *         withXml {
  *           def node = it.asNode()
  *           node.appendNode('xml', 'is what I love')
@@ -113,8 +113,6 @@ import org.gradle.util.ConfigureUtil
  *   }
  * }
  * </pre>
- *
- * @author: Szczepan Faber, created at: 4/20/11
  */
 class EclipseWtpComponent {
 
@@ -229,7 +227,7 @@ class EclipseWtpComponent {
     String libDeployPath = "/WEB-INF/lib"
 
     /**
-     * Enables advanced configuration like tinkering with the output xml
+     * Enables advanced configuration like tinkering with the output XML
      * or affecting the way existing wtp component file content is merged with gradle build information
      * <p>
      * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link WtpComponent}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy
index 3f9f327..2b51ce4 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy
@@ -36,7 +36,7 @@ import org.gradle.util.ConfigureUtil
  *       facet name: 'someCoolFacet', version: '1.3'
  *
  *       file {
- *         //if you want to mess with the resulting xml in whatever way you fancy
+ *         //if you want to mess with the resulting XML in whatever way you fancy
  *         withXml {
  *           def node = it.asNode()
  *           node.appendNode('xml', 'is what I love')
@@ -62,8 +62,6 @@ import org.gradle.util.ConfigureUtil
  * }
  *
  * </pre>
- *
- * @author: Szczepan Faber, created at: 4/20/11
  */
 class EclipseWtpFacet {
 
@@ -87,7 +85,7 @@ class EclipseWtpFacet {
     }
 
     /**
-     * Enables advanced configuration like tinkering with the output xml
+     * Enables advanced configuration like tinkering with the output XML
      * or affecting the way existing wtp facet file content is merged with gradle build information
      * <p>
      * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link WtpFacet}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy
index facfc09..f028835 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-/**
- * @author Hans Dockter
- */
 
 class Facet {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy
index 55791f7..1e7b7d1 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
-/**
- * @author Hans Dockter
- */
 class Library extends AbstractLibrary {
     Library(Node node, FileReferenceFactory fileReferenceFactory) {
         super(node, fileReferenceFactory)
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Link.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Link.groovy
index a8e60b2..7b9fe1f 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Link.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Link.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.PathUtil
 
-/**
- * @author Hans Dockter
- */
 class Link {
     String name
     String type
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Output.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Output.groovy
index 5a1b22c..e738008 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Output.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Output.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.PathUtil
 
-/**
- * @author Hans Dockter
- */
 
 class Output implements ClasspathEntry {
     String path
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
index 5247ab8..e43f0aa 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
@@ -20,8 +20,6 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
 
 /**
  * Represents the customizable elements of an eclipse project file. (via XML hooks everything is customizable).
- *
- * @author Hans Dockter
  */
 class Project extends XmlPersistableConfigurationObject {
     public static final String PROJECT_FILE_NAME = ".project";
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy
index 8837062..a00df7c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-/**
- * @author Hans Dockter
- */
 class ProjectDependency extends AbstractClasspathEntry {
 
     String gradlePath
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy
index 02cafa7..7beef2d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy
@@ -17,8 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 /**
  * SourceFolder.path contains only project relative path.
- *
- * @author Hans Dockter
  */
 class SourceFolder extends AbstractClasspathEntry {
     String output
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy
index f2d2965..b1498a0 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
-/**
- * @author Hans Dockter
- */
 class Variable extends AbstractLibrary {
     Variable(Node node, FileReferenceFactory fileReferenceFactory) {
         super(node, fileReferenceFactory)
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModule.groovy
index 48077b6..01e21fd 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModule.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.PathUtil
 
-/**
- * @author Hans Dockter
- */
 
 class WbDependentModule {
     String deployPath
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbProperty.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbProperty.groovy
index 8a44f01..680d3e9 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbProperty.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbProperty.groovy
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-/**
- * @author Hans Dockter
- */
 
 class WbProperty {
     String name
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbResource.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbResource.groovy
index baa3ced..d117594 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbResource.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WbResource.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.plugins.ide.eclipse.model.internal.PathUtil
 
-/**
- * @author Hans Dockter
- */
 
 class WbResource {
     String deployPath
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy
index a4fd1e8..eb61741 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy
@@ -20,8 +20,6 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
 
 /**
  * Creates the .settings/org.eclipse.wst.common.component file for WTP projects.
- *
- * @author Hans Dockter
  */
 class WtpComponent extends XmlPersistableConfigurationObject {
     String deployName
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
index ca5e285..0d4355d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
@@ -20,8 +20,6 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
 
 /**
  * Creates the .settings/org.eclipse.wst.common.project.facet.core.xml file for WTP projects.
- *
- * @author Hans Dockter
  */
 class WtpFacet extends XmlPersistableConfigurationObject {
     List facets = [] // TODO: turn into Set?
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy
index 6df3a5b..99c6ff4 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy
@@ -21,8 +21,6 @@ import org.gradle.plugins.ide.eclipse.model.Library
 
 /**
  * Eclipse calls them 'class folders' on java build path->libraries tab
- *
- * @author: Szczepan Faber, created at: 5/6/11
  */
 class ClassFoldersCreator {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
index 8b53c6c..19740b3 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
@@ -16,15 +16,12 @@
 package org.gradle.plugins.ide.eclipse.model.internal
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
-import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeLocalFileDependency
-import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeProjectDependency
-import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeRepoFileDependency
 import org.gradle.plugins.ide.eclipse.model.*
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
+import org.gradle.plugins.ide.internal.resolver.model.IdeLocalFileDependency
+import org.gradle.plugins.ide.internal.resolver.model.IdeProjectDependency
+import org.gradle.plugins.ide.internal.resolver.model.IdeExtendedRepoFileDependency
 
-/**
- * @author Hans Dockter
- */
 class ClasspathFactory {
 
     private final ClasspathEntryBuilder outputCreator = new ClasspathEntryBuilder() {
@@ -45,7 +42,7 @@ class ClasspathFactory {
 
     private final ClasspathEntryBuilder projectDependenciesCreator = new ClasspathEntryBuilder() {
         void update(List<ClasspathEntry> entries, EclipseClasspath eclipseClasspath) {
-            entries.addAll(dependenciesExtractor.extractProjectDependencies(eclipseClasspath.plusConfigurations, eclipseClasspath.minusConfigurations)
+            entries.addAll(dependenciesExtractor.extractProjectDependencies(eclipseClasspath.project, eclipseClasspath.plusConfigurations, eclipseClasspath.minusConfigurations)
                 .collect { IdeProjectDependency it -> new ProjectDependencyBuilder().build(it.project, it.declaredConfiguration.name) })
         }
     }
@@ -53,8 +50,8 @@ class ClasspathFactory {
     private final ClasspathEntryBuilder librariesCreator = new ClasspathEntryBuilder() {
         void update(List<ClasspathEntry> entries, EclipseClasspath classpath) {
             dependenciesExtractor.extractRepoFileDependencies(
-                    classpath.project.configurations, classpath.plusConfigurations, classpath.minusConfigurations, classpath.downloadSources, classpath.downloadJavadoc)
-            .each { IdeRepoFileDependency it ->
+                    classpath.project.dependencies, classpath.plusConfigurations, classpath.minusConfigurations, classpath.downloadSources, classpath.downloadJavadoc)
+            .each { IdeExtendedRepoFileDependency it ->
                 entries << createLibraryEntry(it.file, it.sourceFile, it.javadocFile, it.declaredConfiguration.name, classpath, it.id)
             }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy
index c626e02..465200d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy
@@ -20,9 +20,6 @@ import org.gradle.plugins.ide.eclipse.model.AbstractLibrary
 import org.gradle.plugins.ide.eclipse.model.ClasspathEntry
 import org.gradle.plugins.ide.eclipse.model.ProjectDependency
 
-/**
- * @author: Szczepan Faber, created at: 7/4/11
- */
 class ExportedEntriesUpdater {
     void updateExported(List<ClasspathEntry> classpathEntries, List<String> noExportConfigNames) {
         classpathEntries.each {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/PathUtil.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/PathUtil.groovy
index edccbb3..c18a65f 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/PathUtil.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/PathUtil.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model.internal
 
 import org.apache.commons.io.FilenameUtils
 
-/**
- * @author Hans Dockter
- */
 class PathUtil {
   static String normalizePath(String path) {
         FilenameUtils.separatorsToUnix(path)
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
index b242a39..cb66486 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
@@ -19,9 +19,6 @@ import org.gradle.api.Project
 import org.gradle.plugins.ide.eclipse.EclipsePlugin
 import org.gradle.plugins.ide.eclipse.model.ProjectDependency
 
-/**
- * @author Szczepan Faber, @date: 11.03.11
- */
 class ProjectDependencyBuilder {
     ProjectDependency build(Project project, String declaredConfigurationName) {
         def name
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy
index 83d5f28..114036f 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy
@@ -22,9 +22,6 @@ import org.gradle.plugins.ide.eclipse.model.ClasspathEntry
 import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
 import org.gradle.plugins.ide.eclipse.model.SourceFolder
 
-/**
- * @author: Szczepan Faber, created at: 4/22/11
- */
 class SourceFoldersCreator {
     void populateForClasspath(List<ClasspathEntry> entries, EclipseClasspath classpath) {
         def provideRelativePath = { classpath.project.relativePath(it) }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
index 6e061fd..933a57a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
@@ -23,9 +23,6 @@ import org.gradle.plugins.ide.eclipse.model.WbResource
 import org.gradle.plugins.ide.eclipse.model.WtpComponent
 import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
 
-/**
- * @author Hans Dockter
- */
 class WtpComponentFactory {
     void configure(EclipseWtpComponent wtp, WtpComponent component) {
         def entries = getEntriesFromSourceDirs(wtp)
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy
index e6e8efa..6205f28 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy
@@ -25,8 +25,6 @@ import org.gradle.plugins.ide.idea.model.Module
  * Please refer to interesting examples on idea configuration in {@link IdeaModule}.
  * <p>
  * At this moment nearly all configuration is done via {@link IdeaModule}.
- *
- * @author Hans Dockter
  */
 public class GenerateIdeaModule extends XmlGeneratorTask<Module> {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy
index 754e039..c10348a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy
@@ -23,8 +23,6 @@ import org.gradle.plugins.ide.idea.model.Project
  * Generates an IDEA project file for root project *only*. If you want to fine tune the idea configuration
  * <p>
  * At this moment nearly all configuration is done via {@link IdeaProject}.
- *
- * @author Hans Dockter
  */
 public class GenerateIdeaProject extends XmlGeneratorTask<Project> {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy
index 0a146ec..f9c5de5 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy
@@ -22,8 +22,6 @@ import org.gradle.plugins.ide.idea.model.Workspace
 /**
  * Generates an IDEA workspace file *only* for root project.
  * There's little you can configure about workspace generation at the moment.
- *
- * @author Hans Dockter
  */
 public class GenerateIdeaWorkspace extends XmlGeneratorTask<Workspace> {
 
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 46ed86a..ef3cee8 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
@@ -18,20 +18,25 @@ package org.gradle.plugins.ide.idea
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.scala.ScalaBasePlugin
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.idea.internal.IdeaNameDeduper
 import org.gradle.plugins.ide.idea.internal.IdeaScalaConfigurer
+import org.gradle.plugins.ide.idea.model.IdeaLanguageLevel
+import org.gradle.plugins.ide.idea.model.IdeaModel
+import org.gradle.plugins.ide.idea.model.IdeaModule
+import org.gradle.plugins.ide.idea.model.IdeaModuleIml
+import org.gradle.plugins.ide.idea.model.IdeaProject
+import org.gradle.plugins.ide.idea.model.IdeaWorkspace
+import org.gradle.plugins.ide.idea.model.PathFactory
 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.
- *
- *  @author Hans Dockter
  */
 class IdeaPlugin extends IdePlugin {
     private final Instantiator instantiator
@@ -153,12 +158,13 @@ class IdeaPlugin extends IdePlugin {
         project.ideaModule {
             module.conventionMapping.sourceDirs = { project.sourceSets.main.allSource.srcDirs as LinkedHashSet }
             module.conventionMapping.testSourceDirs = { project.sourceSets.test.allSource.srcDirs as LinkedHashSet }
+            module.scopes = [:]
             def configurations = project.configurations
             module.scopes = [
                     PROVIDED: [plus: [], minus: []],
-                    COMPILE: [plus: [configurations.compile], minus: []],
-                    RUNTIME: [plus: [configurations.runtime], minus: [configurations.compile]],
-                    TEST: [plus: [configurations.testRuntime], minus: [configurations.runtime]]
+                    COMPILE: [plus: [], minus: []],
+                    RUNTIME: [plus: [], minus: []],
+                    TEST: [plus: [], minus: []]
             ]
             module.conventionMapping.singleEntryLibraries = {
                 [
@@ -173,6 +179,10 @@ class IdeaPlugin extends IdePlugin {
     }
 
     private void configureForScalaPlugin() {
+        project.plugins.withType(ScalaBasePlugin) {
+            //see IdeaScalaConfigurer
+            project.tasks.ideaModule.dependsOn(project.rootProject.tasks.ideaProject)
+        }
         if (isRoot(project)) {
             new IdeaScalaConfigurer(project).configure()
         }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy
index c7d3a1b..f28f0b7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy
@@ -20,9 +20,6 @@ import org.gradle.plugins.ide.idea.IdeaPlugin
 import org.gradle.plugins.ide.internal.configurer.DeduplicationTarget
 import org.gradle.plugins.ide.internal.configurer.ProjectDeduper
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
 class IdeaNameDeduper {
 
     void configureRoot(Project rootProject) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy
index 84f77bf..a155c55 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy
@@ -34,11 +34,10 @@ class IdeaScalaConfigurer {
     void configure() {
         rootProject.gradle.projectsEvaluated {
             def scalaProjects = findProjectsApplyingIdeaAndScalaPlugins()
-            scalaProjects.tasks.ideaModule*.dependsOn(rootProject.tasks.ideaProject)
             Map<String, ProjectLibrary> scalaCompilerLibraries = [:]
 
             rootProject.ideaProject.doFirst {
-                scalaCompilerLibraries = resolveScalaCompilerLibraries(project, scalaProjects)
+                scalaCompilerLibraries = resolveScalaCompilerLibraries(scalaProjects)
                 declareUniqueProjectLibraries(scalaCompilerLibraries.values() as Set)
             }
 
@@ -50,11 +49,10 @@ class IdeaScalaConfigurer {
         }
     }
 
-    private Map<String, ProjectLibrary> resolveScalaCompilerLibraries(Project rootProject, Collection<Project> scalaProjects) {
+    private Map<String, ProjectLibrary> resolveScalaCompilerLibraries(Collection<Project> scalaProjects) {
         def scalaCompilerLibraries = [:]
 
         for (scalaProject in scalaProjects) {
-            def scalaPlugin = scalaProject.plugins.getPlugin(ScalaBasePlugin)
             IdeaModule ideaModule = scalaProject.idea.module
 
             // could make resolveDependencies() cache its result for later use by GenerateIdeaModule
@@ -63,9 +61,10 @@ class IdeaScalaConfigurer {
             def filePaths = moduleLibraries.collectMany { it.classes.findAll { it instanceof FilePath } }
             def files = filePaths.collect { it.file }
 
-            def scalaClasspath = scalaPlugin.inferScalaCompilerClasspath(files)
-            def compilerJar = scalaPlugin.findScalaJar(scalaClasspath, "compiler")
-            def version = compilerJar == null ? "?" : scalaPlugin.getScalaVersion(compilerJar)
+            def runtime = scalaProject.scalaRuntime
+            def scalaClasspath = runtime.inferScalaClasspath(files)
+            def compilerJar = runtime.findScalaJar(scalaClasspath, "compiler")
+            def version = compilerJar == null ? "?" : runtime.getScalaVersion(compilerJar)
             def library = createProjectLibrary("scala-compiler-$version", scalaClasspath)
             def duplicate = scalaCompilerLibraries.values().find { it.classes == library.classes }
             scalaCompilerLibraries[scalaProject.path] = duplicate ?: library
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Dependency.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Dependency.java
index 87442ba..a3cbf58 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Dependency.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Dependency.java
@@ -19,8 +19,6 @@ import groovy.util.Node;
 
 /**
  * Represents a dependency of an IDEA module.
- *
- * @author Hans Dockter
  */
 public interface Dependency {
     void addToNode(Node parentNode);
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy
index 9cb1b9f..3d8b7a2 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy
@@ -20,8 +20,6 @@ import org.gradle.api.JavaVersion
 
 /**
  * Java language level used by IDEA projects.
- *
- * @author: Szczepan Faber, created at: 7/14/11
  */
 class IdeaLanguageLevel {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy
index 87eb0dc..16cd629 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy
@@ -24,7 +24,6 @@ import org.gradle.util.ConfigureUtil
  * <p>
  * See the examples in docs for {@link IdeaModule} or {@link IdeaProject}.
  * <p>
- * @author Szczepan Faber, created at: 3/31/11
  */
 class IdeaModel {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
index 5170bc6..8beec63 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
@@ -82,7 +82,7 @@ import org.gradle.util.ConfigureUtil
  * }
  * </pre>
  *
- * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * For tackling edge cases users can perform advanced configuration on resulting XML file.
  * It is also possible to affect the way the IDEA plugin merges the existing configuration
  * via beforeMerged and whenMerged closures.
  * <p>
@@ -100,7 +100,7 @@ import org.gradle.util.ConfigureUtil
  *       //if you like to keep *.iml in a secret folder
  *       generateTo = file('secret-modules-folder')
  *
- *       //if you want to mess with the resulting xml in whatever way you fancy
+ *       //if you want to mess with the resulting XML in whatever way you fancy
  *       withXml {
  *         def node = it.asNode()
  *         node.appendNode('iLoveGradle', 'true')
@@ -124,8 +124,6 @@ import org.gradle.util.ConfigureUtil
  * }
  *
  * </pre>
- *
- * @author Szczepan Faber, created at: 3/31/11
  */
 class IdeaModule {
 
@@ -268,7 +266,7 @@ class IdeaModule {
     final IdeaModuleIml iml
 
     /**
-     * Enables advanced configuration like tinkering with the output xml
+     * Enables advanced configuration like tinkering with the output XML
      * or affecting the way existing *.iml content is merged with gradle build information.
      * <p>
      * For example see docs for {@link IdeaModule}.
@@ -318,7 +316,7 @@ class IdeaModule {
      */
     boolean offline
 
-    Map<String, Collection<File>> singleEntryLibraries
+    Map<String, Iterable<File>> singleEntryLibraries
 
     IdeaModule(org.gradle.api.Project project, IdeaModuleIml iml) {
         this.project = project
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
index b74914d..8da5886 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
@@ -23,8 +23,6 @@ import org.gradle.plugins.ide.api.XmlFileContentMerger
  * Models the generation/parsing/merging capabilities of an IDEA module.
  * <p>
  * For examples, see docs for {@link IdeaModule}.
- *
- * @author: Szczepan Faber, created at: 4/5/11
  */
 class IdeaModuleIml extends XmlFileContentMerger {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
index b4bf3fb..39d456f 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
@@ -53,7 +53,7 @@ import org.gradle.util.ConfigureUtil
  * }
  * </pre>
  *
- * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * For tackling edge cases users can perform advanced configuration on resulting XML file.
  * It is also possible to affect the way IDEA plugin merges the existing configuration
  * via beforeMerged and whenMerged closures.
  * <p>
@@ -89,8 +89,6 @@ import org.gradle.util.ConfigureUtil
  *   }
  * }
  * </pre>
- *
- * @author Szczepan Faber, created at: 4/4/11
  */
 class IdeaProject {
 
@@ -149,7 +147,7 @@ class IdeaProject {
     }
 
     /**
-     * Enables advanced configuration like tinkering with the output xml
+     * Enables advanced configuration like tinkering with the output XML
      * or affecting the way existing *.ipr content is merged with Gradle build information.
      * <p>
      * See the examples in the docs for {@link IdeaProject}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy
index 98541e8..d9c9588 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy
@@ -32,13 +32,11 @@ import org.gradle.util.ConfigureUtil
  *     provider.asNode().appendNode('gradleRocks', 'true')
  * }
  * </pre>
- *
- * @author Szczepan Faber, created at: 06/09/11
  */
 class IdeaWorkspace {
 
     /**
-     * Enables advanced manipulation of the output xml.
+     * Enables advanced manipulation of the output XML.
      * <p>
      * For example see docs for {@link IdeaWorkspace}
      *
@@ -49,7 +47,7 @@ class IdeaWorkspace {
     }
 
     /**
-     * Enables advanced manipulation of the output xml.
+     * Enables advanced manipulation of the output XML.
      * <p>
      * For example see docs for {@link IdeaWorkspace}
      */
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/JarDirectory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/JarDirectory.groovy
index 4719078..69f62b3 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/JarDirectory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/JarDirectory.groovy
@@ -17,8 +17,6 @@ package org.gradle.plugins.ide.idea.model
 
 /**
  * Represents a jar directory element of an idea module library.
- *
- * @author Hans Dockter
  */
 class JarDirectory {
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy
index b681913..3c11c20 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy
@@ -18,8 +18,6 @@ package org.gradle.plugins.ide.idea.model
 /**
  * Represents information for the project Java SDK. This translates to attributes of the ProjectRootManager
  * element in the ipr.
- * 
- * @author Hans Dockter
  */
 class Jdk {
     boolean assertKeyword
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
index 19a0288..078a54b 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
@@ -21,8 +21,6 @@ import org.gradle.util.DeprecationLogger
 
 /**
  * Represents the customizable elements of an iml (via XML hooks everything of the iml is customizable).
- *
- * @author Hans Dockter
  */
 class Module extends XmlPersistableConfigurationObject {
     static final String INHERITED = "inherited"
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleDependency.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleDependency.groovy
index 7c56c10..6ce9fe0 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleDependency.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleDependency.groovy
@@ -16,9 +16,7 @@
 package org.gradle.plugins.ide.idea.model
 
 /**
- * Represents an orderEntry of type module in the iml xml.
- *
- * @author Hans Dockter
+ * Represents an orderEntry of type module in the iml XML.
  */
 class ModuleDependency implements Dependency {
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
index bf5d0f6..f6724cd 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
@@ -16,9 +16,7 @@
 package org.gradle.plugins.ide.idea.model
 
 /**
- * Represents an orderEntry of type module-library in the iml xml.
- *
- * @author Hans Dockter
+ * Represents an orderEntry of type module-library in the iml XML.
  */
 class ModuleLibrary implements Dependency {
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
index 25a03ae..3152312 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
@@ -17,8 +17,6 @@ package org.gradle.plugins.ide.idea.model
 
 /**
  * Represents a path in a format as used often in ipr and iml files.
- *
- * @author Hans Dockter
  */
 class Path {
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
index a4ce258..94eb950 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
@@ -21,8 +21,6 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
 
 /**
  * Represents the customizable elements of an ipr (via XML hooks everything of the ipr is customizable).
- *
- * @author Hans Dockter
  */
 class Project extends XmlPersistableConfigurationObject {
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy
index a47f59c..db14efb 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy
@@ -20,8 +20,6 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
 
 /**
  * Represents the customizable elements of an ipr (via XML hooks everything of the ipr is customizable).
- *
- * @author Hans Dockter
  */
 
 class Workspace extends XmlPersistableConfigurationObject {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/GeneratedIdeaScope.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/GeneratedIdeaScope.java
new file mode 100644
index 0000000..77dd1d6
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/GeneratedIdeaScope.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.idea.model.internal;
+
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * An enumeration of possible mappings used to assign Idea classpath scope to Gradle dependency.
+ */
+public enum GeneratedIdeaScope {
+    PROVIDED_TEST("PROVIDED", "TEST"),
+    PROVIDED("PROVIDED"),
+    COMPILE("COMPILE"),
+    RUNTIME_TEST("RUNTIME", "TEST"),
+    RUNTIME("RUNTIME"),
+    TEST("TEST");
+
+    public final Set<String> scopes;
+
+    private GeneratedIdeaScope(String ... scopes) {
+        this.scopes = Collections.unmodifiableSet(Sets.newHashSet(scopes));
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
deleted file mode 100644
index 2044150..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.plugins.ide.idea.model.internal
-
-import org.gradle.api.Project
-import org.gradle.plugins.ide.idea.model.IdeaModule
-import org.gradle.plugins.ide.idea.model.SingleEntryModuleLibrary
-import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
-
-/**
- * @author Szczepan Faber, created at: 4/1/11
- */
-class IdeaDependenciesProvider {
-
-    private final IdeDependenciesExtractor dependenciesExtractor = new IdeDependenciesExtractor()
-    Closure getPath;
-
-    Set<org.gradle.plugins.ide.idea.model.Dependency> provide(IdeaModule ideaModule) {
-        getPath = { File file -> file? ideaModule.pathFactory.path(file) : null }
-
-        Set result = new LinkedHashSet()
-        ideaModule.singleEntryLibraries.each { scope, files ->
-            files.each {
-                if (it && it.isDirectory()) {
-                    result << new SingleEntryModuleLibrary(getPath(it), scope)
-                }
-            }
-        }
-
-        ideaModule.scopes.each { scopeName, scopeMap ->
-            result.addAll(getModuleLibraries(ideaModule, scopeName, scopeMap))
-            result.addAll(getModules(ideaModule.project, scopeName, scopeMap))
-            result
-        }
-
-        return result
-    }
-
-    protected Set getModules(Project project, String scopeName, Map scopeMap) {
-        if (!scopeMap) {
-            return []
-        }
-        return dependenciesExtractor.extractProjectDependencies(scopeMap.plus, scopeMap.minus).collect {
-                new ModuleDependencyBuilder().create(it.project, scopeName)
-        }
-    }
-
-    protected Set getModuleLibraries(IdeaModule ideaModule, String scopeName, Map scopeMap) {
-        if (!scopeMap) {
-            return []
-        }
-
-        LinkedHashSet moduleLibraries = []
-
-        if (!ideaModule.offline) {
-            def repoFileDependencies = dependenciesExtractor.extractRepoFileDependencies(
-                    ideaModule.project.configurations, scopeMap.plus, scopeMap.minus, 
-                    ideaModule.downloadSources, ideaModule.downloadJavadoc)
-
-            repoFileDependencies.each {
-                def library = new SingleEntryModuleLibrary(
-                        getPath(it.file), getPath(it.javadocFile), getPath(it.sourceFile), scopeName)
-                library.moduleVersion = it.id
-                moduleLibraries << library
-            }
-        }
-
-        dependenciesExtractor.extractLocalFileDependencies(scopeMap.plus, scopeMap.minus).each {
-            moduleLibraries << new SingleEntryModuleLibrary(getPath(it.file), scopeName)
-        }
-        moduleLibraries
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.java
new file mode 100644
index 0000000..e972dea
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.idea.model.internal;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.*;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.plugins.ide.idea.model.Dependency;
+import org.gradle.plugins.ide.idea.model.FilePath;
+import org.gradle.plugins.ide.idea.model.IdeaModule;
+import org.gradle.plugins.ide.idea.model.SingleEntryModuleLibrary;
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor;
+import org.gradle.plugins.ide.internal.resolver.model.IdeDependencyKey;
+import org.gradle.plugins.ide.internal.resolver.model.IdeExtendedRepoFileDependency;
+import org.gradle.plugins.ide.internal.resolver.model.IdeLocalFileDependency;
+import org.gradle.plugins.ide.internal.resolver.model.IdeProjectDependency;
+
+import java.io.File;
+import java.util.*;
+
+public class IdeaDependenciesProvider {
+
+    private final IdeDependenciesExtractor dependenciesExtractor;
+    private Function<File, FilePath> getPath;
+
+    /**
+     * List of mappings used to assign IDEA classpath scope to project dependencies.
+     *
+     * Applied in order: if a dependency is found in all listed configurations it is provided as
+     * a dependency in given scope(s).
+     */
+    Map<GeneratedIdeaScope, List<IdeaScopeMappingRule>> scopeMappings = new EnumMap<GeneratedIdeaScope, List<IdeaScopeMappingRule>>(GeneratedIdeaScope.class);
+
+    public IdeaDependenciesProvider() {
+        this(new IdeDependenciesExtractor());
+    }
+
+    IdeaDependenciesProvider(IdeDependenciesExtractor dependenciesExtractor) {
+        this.dependenciesExtractor = dependenciesExtractor;
+        scopeMappings.put(GeneratedIdeaScope.PROVIDED_TEST,
+                Collections.singletonList(new IdeaScopeMappingRule("providedRuntime", "test")));
+        scopeMappings.put(GeneratedIdeaScope.PROVIDED,
+                Lists.newArrayList(new IdeaScopeMappingRule("providedCompile"), new IdeaScopeMappingRule("providedRuntime")));
+        scopeMappings.put(GeneratedIdeaScope.COMPILE,
+                Collections.singletonList(new IdeaScopeMappingRule("compile")));
+        scopeMappings.put(GeneratedIdeaScope.RUNTIME_TEST,
+                Collections.singletonList(new IdeaScopeMappingRule("testCompile", "runtime")));
+        scopeMappings.put(GeneratedIdeaScope.RUNTIME,
+                Collections.singletonList(new IdeaScopeMappingRule("runtime")));
+        scopeMappings.put(GeneratedIdeaScope.TEST,
+                Lists.newArrayList(new IdeaScopeMappingRule("testCompile"), new IdeaScopeMappingRule("testRuntime")));
+    }
+
+    Set<Dependency> provide(final IdeaModule ideaModule) {
+        getPath = new Function<File, FilePath>() {
+            @Nullable
+            public FilePath apply(File file) {
+                return file != null ? ideaModule.getPathFactory().path(file) : null;
+            }
+        };
+
+        Set<Dependency> result = new LinkedHashSet<Dependency>();
+        if (ideaModule.getSingleEntryLibraries() != null) {
+            for (Map.Entry<String, Iterable<File>> singleEntryLibrary : ideaModule.getSingleEntryLibraries().entrySet()) {
+                String scope = singleEntryLibrary.getKey();
+                for (File file : singleEntryLibrary.getValue()) {
+                    if (file != null && file.isDirectory()) {
+                        result.add(new SingleEntryModuleLibrary(getPath.apply(file), scope));
+                    }
+                }
+            }
+        }
+        result.addAll(provideFromScopeRuleMappings(ideaModule));
+        return result;
+    }
+
+    private Set<Dependency> provideFromScopeRuleMappings(IdeaModule ideaModule) {
+        Multimap<IdeDependencyKey<?, Dependency>, String> dependencyToConfigurations = LinkedHashMultimap.create();
+        for (Configuration configuration : ideaConfigurations(ideaModule)) {
+            // project dependencies
+            Collection<IdeProjectDependency> ideProjectDependencies = dependenciesExtractor.extractProjectDependencies(
+                    ideaModule.getProject(), Collections.singletonList(configuration), Collections.<Configuration>emptyList());
+            for (IdeProjectDependency ideProjectDependency : ideProjectDependencies) {
+                IdeDependencyKey<?, Dependency> key = IdeDependencyKey.forProjectDependency(
+                        ideProjectDependency,
+                        new IdeDependencyKey.DependencyBuilder<IdeProjectDependency, Dependency>() {
+                            public Dependency buildDependency(IdeProjectDependency dependency, String scope) {
+                                return new ModuleDependencyBuilder().create(dependency.getProject(), scope);
+                            }});
+                dependencyToConfigurations.put(key, configuration.getName());
+            }
+            // repository dependencies
+            if (!ideaModule.isOffline()) {
+                Collection<IdeExtendedRepoFileDependency> ideRepoFileDependencies = dependenciesExtractor.extractRepoFileDependencies(
+                        ideaModule.getProject().getDependencies(), Collections.singletonList(configuration), Collections.<Configuration>emptyList(),
+                        ideaModule.isDownloadSources(), ideaModule.isDownloadJavadoc());
+                for (IdeExtendedRepoFileDependency ideRepoFileDependency : ideRepoFileDependencies) {
+                    IdeDependencyKey<?, Dependency> key = IdeDependencyKey.forRepoFileDependency(
+                            ideRepoFileDependency,
+                            new IdeDependencyKey.DependencyBuilder<IdeExtendedRepoFileDependency, Dependency>() {
+                                public Dependency buildDependency(IdeExtendedRepoFileDependency dependency, String scope) {
+                                    SingleEntryModuleLibrary library = new SingleEntryModuleLibrary(
+                                            getPath.apply(dependency.getFile()), getPath.apply(dependency.getJavadocFile()), getPath.apply(dependency.getSourceFile()), scope);
+                                    library.setModuleVersion(dependency.getId());
+                                    return library;
+                                }});
+                    dependencyToConfigurations.put(key, configuration.getName());
+                }
+            }
+            // file dependencies
+            Collection<IdeLocalFileDependency> ideLocalFileDependencies = dependenciesExtractor.extractLocalFileDependencies(
+                    Collections.singletonList(configuration), Collections.<Configuration>emptyList());
+            for (IdeLocalFileDependency fileDependency : ideLocalFileDependencies) {
+                IdeDependencyKey<?, Dependency> key = IdeDependencyKey.forLocalFileDependency(
+                        fileDependency,
+                        new IdeDependencyKey.DependencyBuilder<IdeLocalFileDependency, Dependency>() {
+                            public Dependency buildDependency(IdeLocalFileDependency dependency, String scope) {
+                                return new SingleEntryModuleLibrary(getPath.apply(dependency.getFile()), scope);
+                            }});
+                dependencyToConfigurations.put(key, configuration.getName());
+            }
+        }
+
+        Set<Dependency> dependencies = new LinkedHashSet<Dependency>();
+        for (GeneratedIdeaScope scope : GeneratedIdeaScope.values()) {
+            Map<String, Collection<Configuration>> plusMinusConfigurations = ideaModule.getScopes().get(scope.name());
+            if (plusMinusConfigurations == null) {
+                if (shouldProcessScope(scope, ideaModule.getScopes())) {
+                    plusMinusConfigurations = Collections.emptyMap();
+                } else {
+                    continue;
+                }
+            }
+            Collection<Configuration> minusConfigurations = plusMinusConfigurations.get("minus");
+            Collection<String> minusConfigurationNames = minusConfigurations != null
+                    ? Lists.newArrayList(Iterables.transform(
+                            minusConfigurations,
+                            new Function<Configuration, String>() {
+                                public String apply(Configuration configuration) {
+                                    return configuration.getName();
+                                }
+                            }
+                    ))
+                    : Collections.<String>emptyList();
+
+            for (IdeaScopeMappingRule scopeMappingRule : scopeMappings.get(scope)) {
+                Collection<IdeDependencyKey<?, Dependency>> matchingDependencies =
+                        extractDependencies(dependencyToConfigurations, scopeMappingRule.configurationNames, minusConfigurationNames);
+                for (final IdeDependencyKey<?, Dependency> dependencyKey : matchingDependencies) {
+                    dependencies.addAll(Lists.newArrayList(Iterables.transform(
+                            scope.scopes,
+                            scopeToDependency(dependencyKey))));
+                }
+            }
+            if (plusMinusConfigurations.containsKey("plus")) {
+                for (Configuration plusConfiguration : plusMinusConfigurations.get("plus")) {
+                    Collection<IdeDependencyKey<?, Dependency>> matchingDependencies =
+                            extractDependencies(dependencyToConfigurations, Collections.singletonList(plusConfiguration.getName()), minusConfigurationNames);
+                    for (IdeDependencyKey<?, Dependency> dependencyKey : matchingDependencies) {
+                        dependencies.addAll(Lists.newArrayList(Iterables.transform(
+                                scope.scopes,
+                                scopeToDependency(dependencyKey))));
+                    }
+                }
+            }
+        }
+        return dependencies;
+    }
+
+    private boolean shouldProcessScope(GeneratedIdeaScope scope, Map<String, Map<String, Collection<Configuration>>> scopes) {
+        // composite scopes are not present in IdeaModule.scopes - check their mapped scope names
+        for (String scopeName : scope.scopes) {
+            if (!scopes.containsKey(scopeName)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static Function<String, Dependency> scopeToDependency(final IdeDependencyKey<?, Dependency> dependencyKey) {
+        return new Function<String, Dependency>() {
+            @Nullable
+            public Dependency apply(String s) {
+                return dependencyKey.buildDependency(s);
+            }
+        };
+    }
+
+    private Iterable<Configuration> ideaConfigurations(final IdeaModule ideaModule) {
+        Set<Configuration> configurations = Sets.newHashSet(ideaModule.getProject().getConfigurations());
+        for (Map<String, Collection<Configuration>> scopeMap : ideaModule.getScopes().values()) {
+            for (Configuration cfg : Iterables.concat(scopeMap.values())) {
+                configurations.add(cfg);
+            }
+        }
+        return Iterables.filter(
+                configurations,
+                new Predicate<Configuration>() {
+                    public boolean apply(Configuration input) {
+                        return isMappedToIdeaScope(input, ideaModule);
+                    }
+                });
+    }
+
+    private boolean isMappedToIdeaScope(final Configuration configuration, IdeaModule ideaModule) {
+        Iterable<IdeaScopeMappingRule> rules = Iterables.concat(scopeMappings.values());
+        boolean matchesRule = Iterables.any(rules, new Predicate<IdeaScopeMappingRule>() {
+            public boolean apply(IdeaScopeMappingRule ideaScopeMappingRule) {
+                return ideaScopeMappingRule.configurationNames.contains(configuration.getName());
+            }
+        });
+        if (matchesRule) {
+            return true;
+        }
+        for (Map<String, Collection<Configuration>> scopeMap : ideaModule.getScopes().values()) {
+            Iterable<Configuration> configurations = Iterables.concat(scopeMap.values());
+            if (Iterables.any(configurations, Predicates.equalTo(configuration))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Looks for dependencies contained in all configurations to remove them from multimap and return as result. */
+    List<IdeDependencyKey<?, Dependency>> extractDependencies(Multimap<IdeDependencyKey<?, Dependency>, String> dependenciesToConfigs,
+                            Collection<String> configurations, Collection<String> minusConfigurations) {
+        List<IdeDependencyKey<?, Dependency>> deps = new ArrayList<IdeDependencyKey<?, Dependency>>();
+        List<IdeDependencyKey<?, Dependency>> minusDeps = new ArrayList<IdeDependencyKey<?, Dependency>>();
+        for (IdeDependencyKey<?, Dependency> dependencyKey : dependenciesToConfigs.keySet()) {
+            if (dependenciesToConfigs.get(dependencyKey).containsAll(configurations)) {
+                boolean isInMinus = false;
+                for (String minusConfiguration : minusConfigurations) {
+                    if (dependenciesToConfigs.get(dependencyKey).contains(minusConfiguration)) {
+                        isInMinus = true;
+                        break;
+                    }
+                }
+                if (!isInMinus) {
+                    deps.add(dependencyKey);
+                } else {
+                    minusDeps.add(dependencyKey);
+                }
+            }
+        }
+        for (IdeDependencyKey<?, Dependency> key : Iterables.concat(deps, minusDeps)) {
+            dependenciesToConfigs.removeAll(key);
+        }
+        return deps;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaScopeMappingRule.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaScopeMappingRule.java
new file mode 100644
index 0000000..30d0db8
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaScopeMappingRule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.idea.model.internal;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * A description how to assign IDEA classpath scopes to Gradle dependencies.
+ *
+ * Rules are applied in their order.
+ * If a dependency is found in listed configuration(s) it is assigned specified scope or scopes in IDEA project.
+ */
+class IdeaScopeMappingRule {
+
+    final Set<String> configurationNames;
+
+    IdeaScopeMappingRule(String ... configurationNames) {
+        this.configurationNames = Sets.newHashSet(configurationNames);
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaScopeMappingRule{"
+                + "configurationNames=" + configurationNames
+                + '}';
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.groovy
deleted file mode 100644
index 4930688..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.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.plugins.ide.idea.model.internal
-
-import org.gradle.api.Project
-import org.gradle.plugins.ide.idea.IdeaPlugin
-import org.gradle.plugins.ide.idea.model.ModuleDependency
-
-/**
- * @author Szczepan Faber, @date: 19.03.11
- */
-class ModuleDependencyBuilder {
-    ModuleDependency create(Project project, String scope) {
-        if (project.plugins.hasPlugin(IdeaPlugin)) {
-            new ModuleDependency(project.idea.module.name, scope)
-        } else {
-            new ModuleDependency(project.name, scope)
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.java
new file mode 100644
index 0000000..1b93f6f
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.idea.model.internal;
+
+import org.gradle.api.Project;
+import org.gradle.plugins.ide.idea.IdeaPlugin;
+import org.gradle.plugins.ide.idea.model.IdeaModel;
+import org.gradle.plugins.ide.idea.model.ModuleDependency;
+
+class ModuleDependencyBuilder {
+    public ModuleDependency create(Project project, String scope) {
+        if (project.getPlugins().hasPlugin(IdeaPlugin.class)) {
+            return new ModuleDependency(((IdeaModel) project.getExtensions().getByName("idea")).getModule().getName(), scope);
+        } else {
+            return new ModuleDependency(project.getName(), scope);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
index beb65d7..dbff246 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
@@ -16,124 +16,155 @@
 
 package org.gradle.plugins.ide.internal
 
+import com.google.common.collect.LinkedHashMultimap
+import com.google.common.collect.Multimap
 import org.gradle.api.Project
-import org.gradle.api.specs.Spec
-import org.gradle.api.artifacts.*
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.api.artifacts.resolution.JvmLibrary
+import org.gradle.api.artifacts.resolution.JvmLibraryJavadocArtifact
+import org.gradle.api.artifacts.resolution.JvmLibrarySourcesArtifact
+import org.gradle.api.internal.artifacts.component.DefaultModuleComponentIdentifier
+import org.gradle.plugins.ide.internal.resolver.DefaultIdeDependencyResolver
+import org.gradle.plugins.ide.internal.resolver.IdeDependencyResolver
+import org.gradle.plugins.ide.internal.resolver.model.IdeExtendedRepoFileDependency
+import org.gradle.plugins.ide.internal.resolver.model.IdeLocalFileDependency
+import org.gradle.plugins.ide.internal.resolver.model.IdeProjectDependency
+import org.gradle.plugins.ide.internal.resolver.model.UnresolvedIdeRepoFileDependency
 
-/**
- * @author: Szczepan Faber, created at: 7/1/11
- */
 class IdeDependenciesExtractor {
+    private final IdeDependencyResolver ideDependencyResolver = new DefaultIdeDependencyResolver()
 
-    static class IdeDependency {
-        Configuration declaredConfiguration
-    }
-
-    static class IdeLocalFileDependency extends IdeDependency {
-        File file
-    }
-
-    static class IdeRepoFileDependency extends IdeDependency {
-        File file
-        File sourceFile
-        File javadocFile
-        ModuleVersionIdentifier id
-    }
-
-    static class UnresolvedIdeRepoFileDependency extends IdeRepoFileDependency {
-        Exception problem
-    }
-
-    static class IdeProjectDependency extends IdeDependency {
-        Project project
-    }
+    Collection<IdeProjectDependency> extractProjectDependencies(Project project, Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<Project, IdeProjectDependency> deps = [:]
 
-    List<IdeProjectDependency> extractProjectDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        LinkedHashMap<ProjectDependency, Configuration> depToConf = [:]
         for (plusConfiguration in plusConfigurations) {
-            for (ProjectDependency dependency in plusConfiguration.allDependencies.findAll({ it instanceof ProjectDependency })) {
-                depToConf[dependency] = plusConfiguration
+            for(IdeProjectDependency dep in ideDependencyResolver.getIdeProjectDependencies(plusConfiguration, project)) {
+                deps[dep.project] = dep
             }
         }
+
         for (minusConfiguration in minusConfigurations) {
-            for(minusDep in minusConfiguration.allDependencies.findAll({ it instanceof ProjectDependency })) {
-                depToConf.remove(minusDep)
+            for(IdeProjectDependency dep in ideDependencyResolver.getIdeProjectDependencies(minusConfiguration, project)) {
+                deps.remove(dep.project)
             }
         }
-        return depToConf.collect { projectDependency, conf ->
-            new IdeProjectDependency(project: projectDependency.dependencyProject, declaredConfiguration: conf)
+
+        deps.values()
+    }
+
+    Collection<IdeExtendedRepoFileDependency> extractRepoFileDependencies(
+            DependencyHandler dependencyHandler,
+            Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations,
+            boolean downloadSources, boolean downloadJavadoc) {
+
+        // can have multiple IDE dependencies with same component identifier (see GRADLE-1622)
+        Multimap<ComponentIdentifier, IdeExtendedRepoFileDependency> resolvedDependencies = LinkedHashMultimap.create()
+        for (dep in resolvedExternalDependencies(plusConfigurations, minusConfigurations)) {
+            resolvedDependencies.put(toComponentIdentifier(dep.id), dep)
         }
+
+        downloadSourcesAndJavadoc(dependencyHandler, resolvedDependencies, downloadSources, downloadJavadoc)
+
+        def unresolvedDependencies = unresolvedExternalDependencies(plusConfigurations, minusConfigurations)
+        return resolvedDependencies.values() + unresolvedDependencies
     }
 
-    List<IdeRepoFileDependency> extractRepoFileDependencies(ConfigurationContainer confContainer,
-                                                           Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations,
-                                                           boolean downloadSources, boolean downloadJavadoc) {
-        def out = []
+    private ModuleComponentIdentifier toComponentIdentifier(ModuleVersionIdentifier id) {
+        new DefaultModuleComponentIdentifier(id.group, id.name, id.version)
+    }
 
-        def downloader = new JavadocAndSourcesDownloader(confContainer, plusConfigurations, minusConfigurations, downloadSources, downloadJavadoc)
+    private void downloadSourcesAndJavadoc(DependencyHandler dependencyHandler,
+                                           Multimap<ComponentIdentifier, IdeExtendedRepoFileDependency> dependencies,
+                                           boolean downloadSources, boolean downloadJavadoc) {
 
-        resolvedExternalDependencies(plusConfigurations, minusConfigurations).each { IdeRepoFileDependency dependency ->
-            dependency.sourceFile = downloader.sourceFor(dependency.file.name)
-            dependency.javadocFile = downloader.javadocFor(dependency.file.name)
-            out << dependency
+        if (!downloadSources && !downloadJavadoc) {
+            return
         }
 
-        out.addAll(unresolvedExternalDependencies(plusConfigurations, minusConfigurations))
+        def query = dependencyHandler.createArtifactResolutionQuery()
+        query.forComponents(dependencies.keySet());
+        if (downloadSources) {
+            query.withArtifacts(JvmLibrary, JvmLibrarySourcesArtifact)
+        }
+        if (downloadJavadoc) {
+            query.withArtifacts(JvmLibrary, JvmLibraryJavadocArtifact)
+        }
 
-        out
+        def jvmLibraries = query.execute().getComponents(JvmLibrary)
+        for (jvmLibrary in jvmLibraries) {
+            for (dependency in dependencies.get(jvmLibrary.id)) {
+                for (sourcesArtifact in jvmLibrary.sourcesArtifacts) {
+                    if (sourcesArtifact.failure == null) {
+                        dependency.sourceFile = sourcesArtifact.file
+                    }
+                }
+                for (javadocArtifact in jvmLibrary.javadocArtifacts) {
+                    if (javadocArtifact.failure == null) {
+                        dependency.javadocFile = javadocArtifact.file
+                    }
+                }
+            }
+        }
     }
 
     private Collection<UnresolvedIdeRepoFileDependency> unresolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def unresolved = new LinkedHashMap<String, UnresolvedIdeRepoFileDependency>()
+        def unresolved = new LinkedHashMap<File, UnresolvedIdeRepoFileDependency>()
+
         for (c in plusConfigurations) {
-            def deps = c.resolvedConfiguration.lenientConfiguration.unresolvedModuleDependencies
+            def deps = ideDependencyResolver.getUnresolvedIdeRepoFileDependencies(c)
+
             deps.each {
-                unresolved[it.selector] = new UnresolvedIdeRepoFileDependency(
-                    file: new File(unresolvedFileName(it)), declaredConfiguration: c)
+                unresolved[it.file] = it
             }
         }
+
         for (c in minusConfigurations) {
-            def deps = c.resolvedConfiguration.lenientConfiguration.unresolvedModuleDependencies
-            deps.each { unresolved.remove(it.selector) }
+            def deps = ideDependencyResolver.getUnresolvedIdeRepoFileDependencies(c)
+
+            deps.each {
+                unresolved.remove(it.file)
+            }
         }
-        unresolved.values()
-    }
 
-    private String unresolvedFileName(UnresolvedDependency dep) {
-        "unresolved dependency - $dep.selector.group $dep.selector.name $dep.selector.version"
+        unresolved.values()
     }
 
-    List<IdeLocalFileDependency> extractLocalFileDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        LinkedHashMap<File, Configuration> fileToConf = [:]
-        def filter = { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}
+    Collection<IdeLocalFileDependency> extractLocalFileDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<File, IdeLocalFileDependency> fileToConf = [:]
 
         for (plusConfiguration in plusConfigurations) {
-            def deps = plusConfiguration.allDependencies.findAll(filter)
-            def files = deps.collect { it.resolve() }.flatten()
-            files.each { fileToConf[it] = plusConfiguration }
+            for(IdeLocalFileDependency localFileDependency in ideDependencyResolver.getIdeLocalFileDependencies(plusConfiguration)) {
+                fileToConf[localFileDependency.file] = localFileDependency
+            }
         }
         for (minusConfiguration in minusConfigurations) {
-            def deps = minusConfiguration.allDependencies.findAll(filter)
-            def files = deps.collect { it.resolve() }.flatten()
-            files.each { fileToConf.remove(it) }
-        }
-        return fileToConf.collect { file, conf ->
-            new IdeLocalFileDependency( file: file, declaredConfiguration: conf)
+            for(IdeLocalFileDependency localFileDependency in ideDependencyResolver.getIdeLocalFileDependencies(minusConfiguration)) {
+                fileToConf.remove(localFileDependency.file)
+            }
         }
+
+        fileToConf.values()
     }
 
-    public Collection<IdeRepoFileDependency> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        LinkedHashMap<File, IdeRepoFileDependency> out = [:]
+    Collection<IdeExtendedRepoFileDependency> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<File, IdeExtendedRepoFileDependency> out = [:]
+
         for (plusConfiguration in plusConfigurations) {
-            for (artifact in plusConfiguration.resolvedConfiguration.lenientConfiguration.getArtifacts({ it instanceof ExternalDependency } as Spec)) {
-                out[artifact.file] = new IdeRepoFileDependency( file: artifact.file, declaredConfiguration: plusConfiguration, id: artifact.moduleVersion.id)
+            for (artifact in ideDependencyResolver.getIdeRepoFileDependencies(plusConfiguration)) {
+                out[artifact.file] = artifact
             }
         }
+
         for (minusConfiguration in minusConfigurations) {
-            for (artifact in minusConfiguration.resolvedConfiguration.lenientConfiguration.getArtifacts({ it instanceof ExternalDependency } as Spec)) {
+            for (artifact in ideDependencyResolver.getIdeRepoFileDependencies(minusConfiguration)) {
                 out.remove(artifact.file)
             }
         }
+
         out.values()
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy
index 12e0228..1d381ae 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy
@@ -55,7 +55,7 @@ public abstract class IdePlugin implements Plugin<Project> {
 
     protected void addWorker(Task worker, boolean includeInClean = true) {
         lifecycleTask.dependsOn(worker)
-        Delete cleanWorker = project.getTasks().add(cleanName(worker.getName()), Delete.class)
+        Delete cleanWorker = project.tasks.create(cleanName(worker.name), Delete.class)
         cleanWorker.delete(worker.getOutputs().getFiles())
         if (includeInClean) {
             cleanTask.dependsOn(cleanWorker)
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/JavadocAndSourcesDownloader.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/JavadocAndSourcesDownloader.groovy
deleted file mode 100644
index e0e6d35..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/JavadocAndSourcesDownloader.groovy
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.plugins.ide.internal
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.Dependency
-import org.gradle.api.artifacts.ResolvedDependency
-import org.gradle.api.artifacts.ExternalDependency
-import org.gradle.api.specs.Spec
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.api.specs.Specs
-import org.gradle.api.artifacts.ConfigurationContainer
-
-/**
- * by Szczepan Faber, created at: 1/25/13
- */
-class JavadocAndSourcesDownloader {
-
-    private Map<String, File> sourceFiles
-    private Map<String, File> javadocFiles
-
-    JavadocAndSourcesDownloader(ConfigurationContainer confContainer, Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations,
-                                boolean downloadSources, boolean downloadJavadoc) {
-        if (!downloadJavadoc && !downloadSources) {
-            return
-        }
-
-        def allResolvedDependencies = resolveDependencies(plusConfigurations, minusConfigurations)
-
-        if (downloadSources) {
-            Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-                addSourceArtifact(dependency)
-            }
-
-            sourceFiles = getFiles(confContainer.detachedConfiguration(sourceDependencies as Dependency[]), "sources")
-        }
-
-        if (downloadJavadoc) {
-            Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-                addJavadocArtifact(dependency)
-            }
-
-            javadocFiles = getFiles(confContainer.detachedConfiguration(javadocDependencies as Dependency[]), "javadoc")
-        }
-    }
-
-    File sourceFor(String name) {
-        sourceFiles?.get(name)
-    }
-
-    File javadocFor(String name) {
-        javadocFiles?.get(name)
-    }
-
-    private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(getAllDeps(plusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
-        }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(getAllDeps(minusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
-        }
-        result
-    }
-
-    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
-        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
-            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
-                    resolvedDependency.configuration)
-            dependency.transitive = false
-            configureClosure.call(dependency)
-            dependency
-        }
-    }
-
-    private void addSourceArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'source'
-            artifact.extension = 'jar'
-            artifact.classifier = 'sources'
-        }
-    }
-
-    private void addJavadocArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'javadoc'
-            artifact.extension = 'jar'
-            artifact.classifier = 'javadoc'
-        }
-    }
-
-    private Map getFiles(Configuration configuration, String classifier) {
-        return (Map) configuration.resolvedConfiguration.lenientConfiguration.getFiles(Specs.satisfyAll()).inject([:]) { result, sourceFile ->
-            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
-            result[key] = sourceFile
-            result
-        }
-    }
-
-    private Set getAllDeps(Collection deps, Set allDeps = new LinkedHashSet()) {
-        deps.each { ResolvedDependency resolvedDependency ->
-            def notSeenBefore = allDeps.add(resolvedDependency)
-            if (notSeenBefore) { // defend against circular dependencies
-                getAllDeps(resolvedDependency.children, allDeps)
-            }
-        }
-        allDeps
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTarget.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTarget.groovy
index 31fbe7a..504a381 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTarget.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTarget.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ide.internal.configurer
 
 import org.gradle.api.Project
 
-/**
- * @author Szczepan Faber, @date: 14.03.11
- */
  class DeduplicationTarget {
 
      def String moduleName
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduper.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduper.groovy
index eba77bf..b0ead6b 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduper.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduper.groovy
@@ -18,7 +18,6 @@ package org.gradle.plugins.ide.internal.configurer
 /**
  * Able to deduplicate names. Useful for IDE plugins to make sure module names (IDEA) or project names (Eclipse) are unique.
  * <p>
- * @author Szczepan Faber, @date 11.03.11
  */
 class ModuleNameDeduper {
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduper.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduper.groovy
index fceb77f..68f6b01 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduper.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduper.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ide.internal.configurer
 
 import org.gradle.api.Project
 
-/**
- * @author Szczepan Faber, @date: 14.03.11
- */
 class ProjectDeduper {
 
     def moduleNameDeduper = new ModuleNameDeduper()
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/BasicIdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/BasicIdeaModelBuilder.java
new file mode 100644
index 0000000..1c4e40d
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/BasicIdeaModelBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import org.gradle.api.Project;
+import org.gradle.plugins.ide.internal.tooling.idea.DefaultIdeaProject;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+public class BasicIdeaModelBuilder implements ToolingModelBuilder {
+    private final IdeaModelBuilder ideaModelBuilder;
+
+    public BasicIdeaModelBuilder(IdeaModelBuilder ideaModelBuilder) {
+        this.ideaModelBuilder = ideaModelBuilder;
+    }
+
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.idea.BasicIdeaProject");
+    }
+
+    public DefaultIdeaProject buildAll(String modelName, Project project) {
+        return ideaModelBuilder
+                .setOfflineDependencyResolution(true)
+                .buildAll(modelName, project);
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/BuildInvocationsBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/BuildInvocationsBuilder.java
new file mode 100644
index 0000000..0b28697
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/BuildInvocationsBuilder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.tooling.internal.gradle.DefaultBuildInvocations;
+import org.gradle.tooling.internal.impl.LaunchableGradleTask;
+import org.gradle.tooling.internal.impl.LaunchableGradleTaskSelector;
+import org.gradle.tooling.model.internal.ProjectSensitiveToolingModelBuilder;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+public class BuildInvocationsBuilder extends ProjectSensitiveToolingModelBuilder {
+    private final GradleProjectBuilder gradleProjectBuilder;
+
+    public BuildInvocationsBuilder(GradleProjectBuilder gradleProjectBuilder) {
+        this.gradleProjectBuilder = gradleProjectBuilder;
+    }
+
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.gradle.BuildInvocations");
+    }
+
+    public DefaultBuildInvocations<LaunchableGradleTask> buildAll(String modelName, Project project) {
+        if (!canBuild(modelName)) {
+            throw new GradleException("Unknown model name " + modelName);
+        }
+        List<LaunchableGradleTaskSelector> selectors = Lists.newArrayList();
+        Set<String> aggregatedTasks = Sets.newHashSet();
+        findTasks(project, aggregatedTasks);
+        for (String selectorName : aggregatedTasks) {
+            selectors.add(new LaunchableGradleTaskSelector().
+                    setName(selectorName).
+                    setTaskName(selectorName).
+                    setProjectPath(project.getPath()).
+                    setDescription(project.getParent() != null
+                            ? String.format("%s:%s task selector", project.getPath(), selectorName)
+                            : String.format("%s task selector", selectorName)).
+                    setDisplayName(String.format("%s in %s and subprojects.", selectorName, project.toString())));
+        }
+        return new DefaultBuildInvocations<LaunchableGradleTask>()
+                .setSelectors(selectors)
+                .setTasks(convertTasks(gradleProjectBuilder.buildAll(project).findByPath(project.getPath()).getTasks()));
+    }
+
+    public DefaultBuildInvocations buildAll(String modelName, Project project, boolean implicitProject) {
+        return buildAll(modelName, implicitProject ? project.getRootProject() : project);
+    }
+
+    // build tasks without project reference
+    private List<LaunchableGradleTask> convertTasks(Iterable<LaunchableGradleTask> tasks) {
+        List<LaunchableGradleTask> convertedTasks = Lists.newArrayList();
+        for (LaunchableGradleTask task : tasks) {
+            convertedTasks.add(new LaunchableGradleTask()
+                    .setPath(task.getPath())
+                    .setName(task.getName())
+                    .setDisplayName(task.toString())
+                    .setDescription(task.getDescription()));
+        }
+        return convertedTasks;
+    }
+
+    private void findTasks(Project project, Collection<String> tasks) {
+        for (Project child : project.getSubprojects()) {
+            findTasks(child, tasks);
+        }
+        for (Task task : project.getTasks()) {
+            tasks.add(task.getName());
+        }
+    }
+
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/EclipseModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/EclipseModelBuilder.java
new file mode 100644
index 0000000..190b8ae
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/EclipseModelBuilder.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.plugins.ide.eclipse.EclipsePlugin;
+import org.gradle.plugins.ide.eclipse.model.*;
+import org.gradle.plugins.ide.internal.tooling.eclipse.*;
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.*;
+
+public class EclipseModelBuilder implements ToolingModelBuilder {
+    private final GradleProjectBuilder gradleProjectBuilder;
+
+    private boolean projectDependenciesOnly;
+    private DefaultEclipseProject result;
+    private final Map<String, DefaultEclipseProject> projectMapping = new HashMap<String, DefaultEclipseProject>();
+    private TasksFactory tasksFactory;
+    private DefaultGradleProject<?> rootGradleProject;
+    private Project currentProject;
+
+    public EclipseModelBuilder(GradleProjectBuilder gradleProjectBuilder) {
+        this.gradleProjectBuilder = gradleProjectBuilder;
+    }
+
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.eclipse.EclipseProject")
+                || modelName.equals("org.gradle.tooling.model.eclipse.HierarchicalEclipseProject");
+    }
+
+    public DefaultEclipseProject buildAll(String modelName, Project project) {
+        boolean includeTasks = modelName.equals("org.gradle.tooling.model.eclipse.EclipseProject");
+        tasksFactory = new TasksFactory(includeTasks);
+        projectDependenciesOnly = modelName.equals("org.gradle.tooling.model.eclipse.HierarchicalEclipseProject");
+        currentProject = project;
+        Project root = project.getRootProject();
+        rootGradleProject = gradleProjectBuilder.buildAll(project);
+        tasksFactory.collectTasks(root);
+        applyEclipsePlugin(root);
+        buildHierarchy(root);
+        populate(root);
+        return result;
+    }
+
+    private void applyEclipsePlugin(Project root) {
+        Set<Project> allProjects = root.getAllprojects();
+        for (Project p : allProjects) {
+            p.getPlugins().apply(EclipsePlugin.class);
+        }
+        root.getPlugins().getPlugin(EclipsePlugin.class).makeSureProjectNamesAreUnique();
+    }
+
+    private void addProject(Project project, DefaultEclipseProject eclipseProject) {
+        if (project == currentProject) {
+            result = eclipseProject;
+        }
+        projectMapping.put(project.getPath(), eclipseProject);
+    }
+
+    private void populate(Project project) {
+        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
+        EclipseClasspath classpath = eclipseModel.getClasspath();
+
+        classpath.setProjectDependenciesOnly(projectDependenciesOnly);
+        List<ClasspathEntry> entries = classpath.resolveDependencies();
+
+        final List<ExternalDependencyVersion1> externalDependencies = new LinkedList<ExternalDependencyVersion1>();
+        final List<EclipseProjectDependencyVersion2> projectDependencies = new LinkedList<EclipseProjectDependencyVersion2>();
+        final List<EclipseSourceDirectoryVersion1> sourceDirectories = new LinkedList<EclipseSourceDirectoryVersion1>();
+
+        for (ClasspathEntry entry : entries) {
+            //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();
+                final File source = library.getSourcePath() == null ? null : library.getSourcePath().getFile();
+                final File javadoc = library.getJavadocPath() == null ? null : library.getJavadocPath().getFile();
+                externalDependencies.add(new DefaultEclipseExternalDependency(file, javadoc, source, library.getModuleVersion()));
+            } else if (entry instanceof ProjectDependency) {
+                final ProjectDependency projectDependency = (ProjectDependency) entry;
+                final String path = StringUtils.removeStart(projectDependency.getPath(), "/");
+                projectDependencies.add(new DefaultEclipseProjectDependency(path, projectMapping.get(projectDependency.getGradlePath())));
+            } else if (entry instanceof SourceFolder) {
+                String path = ((SourceFolder) entry).getPath();
+                sourceDirectories.add(new DefaultEclipseSourceDirectory(path, project.file(path)));
+            }
+        }
+
+        DefaultEclipseProject eclipseProject = projectMapping.get(project.getPath());
+        eclipseProject.setClasspath(externalDependencies);
+        eclipseProject.setProjectDependencies(projectDependencies);
+        eclipseProject.setSourceDirectories(sourceDirectories);
+
+        List<DefaultEclipseLinkedResource> linkedResources = new LinkedList<DefaultEclipseLinkedResource>();
+        for(Link r: eclipseModel.getProject().getLinkedResources()) {
+            linkedResources.add(new DefaultEclipseLinkedResource(r.getName(), r.getType(), r.getLocation(), r.getLocationUri()));
+        }
+        eclipseProject.setLinkedResources(linkedResources);
+
+        List<EclipseTaskVersion1> tasks = new ArrayList<EclipseTaskVersion1>();
+        for (Task t : tasksFactory.getTasks(project)) {
+            tasks.add(new DefaultEclipseTask(eclipseProject, t.getPath(), t.getName(), t.getDescription()));
+        }
+        eclipseProject.setTasks(tasks);
+
+        for (Project childProject : project.getChildProjects().values()) {
+            populate(childProject);
+        }
+    }
+
+    private DefaultEclipseProject buildHierarchy(Project project) {
+        List<DefaultEclipseProject> children = new ArrayList<DefaultEclipseProject>();
+        for (Project child : project.getChildProjects().values()) {
+            children.add(buildHierarchy(child));
+        }
+
+        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
+        org.gradle.plugins.ide.eclipse.model.EclipseProject internalProject = eclipseModel.getProject();
+        String name = internalProject.getName();
+        String description = GUtil.elvis(internalProject.getComment(), null);
+        DefaultEclipseProject eclipseProject =
+                new DefaultEclipseProject(name, project.getPath(), description, project.getProjectDir(), children)
+                .setGradleProject(rootGradleProject.findByPath(project.getPath()));
+
+        for (DefaultEclipseProject child : children) {
+            child.setParent(eclipseProject);
+        }
+        addProject(project, eclipseProject);
+        return eclipseProject;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java
new file mode 100644
index 0000000..bc9a10d
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import org.gradle.api.Project;
+import org.gradle.tooling.internal.gradle.BasicGradleProject;
+import org.gradle.tooling.internal.gradle.DefaultGradleBuild;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class GradleBuildBuilder implements ToolingModelBuilder {
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.gradle.GradleBuild");
+    }
+
+    public DefaultGradleBuild buildAll(String modelName, Project target) {
+        Map<Project, BasicGradleProject> convertedProjects = new LinkedHashMap<Project, BasicGradleProject>();
+        BasicGradleProject rootProject = convert(target.getRootProject(), convertedProjects);
+        DefaultGradleBuild model = new DefaultGradleBuild().setRootProject(rootProject);
+        for (Project project : target.getRootProject().getAllprojects()) {
+            model.addProject(convertedProjects.get(project));
+        }
+        return model;
+    }
+
+    private BasicGradleProject convert(Project project, Map<Project, BasicGradleProject> convertedProjects) {
+        BasicGradleProject converted = new BasicGradleProject().setName(project.getName()).setPath(project.getPath());
+        converted.setProjectDirectory(project.getProjectDir());
+        if (project.getParent() != null) {
+            converted.setParent(convertedProjects.get(project.getParent()));
+        }
+        convertedProjects.put(project, converted);
+        for (Project child : project.getChildProjects().values()) {
+            converted.addChild(convert(child, convertedProjects));
+        }
+        return converted;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/GradleProjectBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/GradleProjectBuilder.java
new file mode 100644
index 0000000..bfdbccb
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/GradleProjectBuilder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.impl.LaunchableGradleProjectTask;
+import org.gradle.tooling.internal.impl.LaunchableGradleTask;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Builds the GradleProject that contains the project hierarchy and task information
+ */
+public class GradleProjectBuilder implements ToolingModelBuilder {
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.GradleProject");
+    }
+
+    public Object buildAll(String modelName, Project project) {
+        return buildHierarchy(project.getRootProject());
+    }
+
+    public DefaultGradleProject buildAll(Project project) {
+        return buildHierarchy(project.getRootProject());
+    }
+
+    private DefaultGradleProject<LaunchableGradleTask> buildHierarchy(Project project) {
+        List<DefaultGradleProject<LaunchableGradleTask>> children = new ArrayList<DefaultGradleProject<LaunchableGradleTask>>();
+        for (Project child : project.getChildProjects().values()) {
+            children.add(buildHierarchy(child));
+        }
+
+        DefaultGradleProject<LaunchableGradleTask> gradleProject = new DefaultGradleProject<LaunchableGradleTask>()
+                .setPath(project.getPath())
+                .setName(project.getName())
+                .setDescription(project.getDescription())
+                .setChildren(children);
+
+        gradleProject.getBuildScript().setSourceFile(project.getBuildFile());
+        gradleProject.setTasks(tasks(gradleProject, project.getTasks()));
+
+        for (DefaultGradleProject child : children) {
+            child.setParent(gradleProject);
+        }
+
+        return gradleProject;
+    }
+
+    private static List<LaunchableGradleTask> tasks(DefaultGradleProject owner, TaskContainer tasks) {
+        List<LaunchableGradleTask> out = new LinkedList<LaunchableGradleTask>();
+
+        for (Task t : tasks) {
+            out.add(new LaunchableGradleProjectTask()
+                    .setProject(owner)
+                    .setPath(t.getPath())
+                    .setName(t.getName())
+                    .setDisplayName(t.toString())
+                    .setDescription(t.getDescription())
+                    );
+        }
+
+        return out;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/IdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/IdeaModelBuilder.java
new file mode 100644
index 0000000..a11c6bb
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/IdeaModelBuilder.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import org.gradle.api.Project;
+import org.gradle.plugins.ide.idea.IdeaPlugin;
+import org.gradle.plugins.ide.idea.model.*;
+import org.gradle.plugins.ide.internal.tooling.idea.*;
+import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.model.idea.IdeaSourceDirectory;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+import java.io.File;
+import java.util.*;
+
+public class IdeaModelBuilder implements ToolingModelBuilder {
+    private final GradleProjectBuilder gradleProjectBuilder;
+
+    private boolean offlineDependencyResolution;
+
+    public IdeaModelBuilder(GradleProjectBuilder gradleProjectBuilder) {
+        this.gradleProjectBuilder = gradleProjectBuilder;
+    }
+
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.idea.IdeaProject");
+    }
+
+    public DefaultIdeaProject buildAll(String modelName, Project project) {
+        Project root = project.getRootProject();
+        applyIdeaPlugin(root);
+        DefaultGradleProject<?> rootGradleProject = gradleProjectBuilder.buildAll(project);
+        return build(root, rootGradleProject);
+    }
+
+    private void applyIdeaPlugin(Project root) {
+        Set<Project> allProjects = root.getAllprojects();
+        for (Project p : allProjects) {
+            p.getPlugins().apply(IdeaPlugin.class);
+        }
+        root.getPlugins().getPlugin(IdeaPlugin.class).makeSureModuleNamesAreUnique();
+    }
+
+    private DefaultIdeaProject build(Project project, DefaultGradleProject rootGradleProject) {
+        IdeaModel ideaModel = project.getPlugins().getPlugin(IdeaPlugin.class).getModel();
+        IdeaProject projectModel = ideaModel.getProject();
+
+        DefaultIdeaProject out = new DefaultIdeaProject()
+                .setName(projectModel.getName())
+                .setJdkName(projectModel.getJdkName())
+                .setLanguageLevel(new DefaultIdeaLanguageLevel(projectModel.getLanguageLevel().getLevel()));
+
+        Map<String, DefaultIdeaModule> modules = new HashMap<String, DefaultIdeaModule>();
+        for (IdeaModule module : projectModel.getModules()) {
+            appendModule(modules, module, out, rootGradleProject);
+        }
+        for (IdeaModule module : projectModel.getModules()) {
+            buildDependencies(modules, module);
+        }
+        out.setChildren(new LinkedList<DefaultIdeaModule>(modules.values()));
+
+        return out;
+    }
+
+    private void buildDependencies(Map<String, DefaultIdeaModule> modules, IdeaModule ideaModule) {
+        ideaModule.setOffline(offlineDependencyResolution);
+        Set<Dependency> resolved = ideaModule.resolveDependencies();
+        List<DefaultIdeaDependency> dependencies = new LinkedList<DefaultIdeaDependency>();
+        for (Dependency dependency : resolved) {
+            if (dependency instanceof SingleEntryModuleLibrary) {
+                SingleEntryModuleLibrary d = (SingleEntryModuleLibrary) dependency;
+                DefaultIdeaSingleEntryLibraryDependency defaultDependency = new org.gradle.tooling.internal.idea.DefaultIdeaSingleEntryLibraryDependency()
+                        .setFile(d.getLibraryFile())
+                        .setSource(d.getSourceFile())
+                        .setJavadoc(d.getJavadocFile())
+                        .setScope(new DefaultIdeaDependencyScope(d.getScope()))
+                        .setExported(d.getExported());
+
+                if (d.getModuleVersion() != null) {
+                    defaultDependency.setGradleModuleVersion(new DefaultGradleModuleVersion(d.getModuleVersion()));
+                }
+                dependencies.add(defaultDependency);
+            } else if (dependency instanceof ModuleDependency) {
+                ModuleDependency d = (ModuleDependency) dependency;
+                DefaultIdeaModuleDependency defaultDependency = new org.gradle.tooling.internal.idea.DefaultIdeaModuleDependency()
+                        .setExported(d.getExported())
+                        .setScope(new DefaultIdeaDependencyScope(d.getScope()))
+                        .setDependencyModule(modules.get(d.getName()));
+                dependencies.add(defaultDependency);
+            }
+        }
+        modules.get(ideaModule.getName()).setDependencies(dependencies);
+    }
+
+    private void appendModule(Map<String, DefaultIdeaModule> modules, IdeaModule ideaModule, DefaultIdeaProject ideaProject, DefaultGradleProject rootGradleProject) {
+        DefaultIdeaContentRoot contentRoot = new DefaultIdeaContentRoot()
+            .setRootDirectory(ideaModule.getContentRoot())
+            .setSourceDirectories(srcDirs(ideaModule.getSourceDirs()))
+            .setTestDirectories(srcDirs(ideaModule.getTestSourceDirs()))
+            .setExcludeDirectories(ideaModule.getExcludeDirs());
+
+        DefaultIdeaModule defaultIdeaModule = new DefaultIdeaModule()
+                .setName(ideaModule.getName())
+                .setParent(ideaProject)
+                .setGradleProject(rootGradleProject.findByPath(ideaModule.getProject().getPath()))
+                .setContentRoots(Collections.singletonList(contentRoot))
+                .setCompilerOutput(new DefaultIdeaCompilerOutput()
+                    .setInheritOutputDirs(ideaModule.getInheritOutputDirs() != null ? ideaModule.getInheritOutputDirs() : false)
+                    .setOutputDir(ideaModule.getOutputDir())
+                    .setTestOutputDir(ideaModule.getTestOutputDir())
+                );
+
+        modules.put(ideaModule.getName(), defaultIdeaModule);
+    }
+
+    private Set<IdeaSourceDirectory> srcDirs(Set<File> sourceDirs) {
+        Set<IdeaSourceDirectory> out = new LinkedHashSet<IdeaSourceDirectory>();
+        for (File s : sourceDirs) {
+            out.add(new DefaultIdeaSourceDirectory().setDirectory(s));
+        }
+        return out;
+    }
+
+    public IdeaModelBuilder setOfflineDependencyResolution(boolean offlineDependencyResolution) {
+        this.offlineDependencyResolution = offlineDependencyResolution;
+        return this;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/PublicationsBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/PublicationsBuilder.java
new file mode 100644
index 0000000..71b9aaa
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/PublicationsBuilder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.Project;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublication;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry;
+import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
+import org.gradle.tooling.internal.gradle.DefaultGradlePublication;
+import org.gradle.tooling.internal.gradle.DefaultProjectPublications;
+import org.gradle.tooling.model.internal.ProjectSensitiveToolingModelBuilder;
+
+import java.util.List;
+import java.util.Set;
+
+class PublicationsBuilder extends ProjectSensitiveToolingModelBuilder {
+    private final ProjectPublicationRegistry publicationRegistry;
+
+    PublicationsBuilder(ProjectPublicationRegistry publicationRegistry) {
+        this.publicationRegistry = publicationRegistry;
+    }
+
+    public boolean canBuild(String modelName) {
+        return modelName.equals("org.gradle.tooling.model.gradle.ProjectPublications");
+    }
+
+    public Object buildAll(String modelName, Project project) {
+        return new DefaultProjectPublications().setPublications(publications(project.getPath()));
+    }
+
+    private List<DefaultGradlePublication> publications(String projectPath) {
+        List<DefaultGradlePublication> gradlePublications = Lists.newArrayList();
+
+        Set<ProjectPublication> projectPublications = publicationRegistry.getPublications(projectPath);
+        for (ProjectPublication projectPublication : projectPublications) {
+            gradlePublications.add(new DefaultGradlePublication()
+                    .setId(new DefaultGradleModuleVersion(projectPublication.getId())));
+        }
+
+        return gradlePublications;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/TasksFactory.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/TasksFactory.java
new file mode 100644
index 0000000..d606d0b
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/TasksFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
+
+public class TasksFactory {
+    Map<Project, Set<Task>> allTasks;
+    private final boolean includeTasks;
+
+    public TasksFactory(boolean includeTasks) {
+        this.includeTasks = includeTasks;
+    }
+
+    public void collectTasks(Project root) {
+        allTasks = root.getAllTasks(true);
+    }
+
+    public Set<Task> getTasks(Project project) {
+        if (includeTasks) {
+            return allTasks.get(project);
+        } else {
+            return emptySet();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/ToolingRegistrationAction.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/ToolingRegistrationAction.java
new file mode 100644
index 0000000..bdf0030
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/ToolingRegistrationAction.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.plugins.ide.internal.tooling;
+
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.configuration.project.ProjectConfigureAction;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+public class ToolingRegistrationAction implements ProjectConfigureAction {
+    public void execute(ProjectInternal project) {
+        ToolingModelBuilderRegistry modelBuilderRegistry = project.getServices().get(ToolingModelBuilderRegistry.class);
+        ProjectPublicationRegistry projectPublicationRegistry = project.getServices().get(ProjectPublicationRegistry.class);
+
+        GradleProjectBuilder gradleProjectBuilder  = new GradleProjectBuilder();
+        IdeaModelBuilder ideaModelBuilder = new IdeaModelBuilder(gradleProjectBuilder);
+        modelBuilderRegistry.register(new EclipseModelBuilder(gradleProjectBuilder));
+        modelBuilderRegistry.register(ideaModelBuilder);
+        modelBuilderRegistry.register(gradleProjectBuilder);
+        modelBuilderRegistry.register(new GradleBuildBuilder());
+        modelBuilderRegistry.register(new BasicIdeaModelBuilder(ideaModelBuilder));
+        modelBuilderRegistry.register(new BuildInvocationsBuilder(gradleProjectBuilder));
+        modelBuilderRegistry.register(new PublicationsBuilder(projectPublicationRegistry));
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseExternalDependency.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseExternalDependency.java
new file mode 100644
index 0000000..d12c39c
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseExternalDependency.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling.eclipse;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
+import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+import org.gradle.tooling.model.GradleModuleVersion;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultEclipseExternalDependency implements ExternalDependencyVersion1, Serializable {
+    private final File file;
+    private final File javadoc;
+    private final File source;
+    private final GradleModuleVersion moduleVersion;
+
+    public DefaultEclipseExternalDependency(File file, File javadoc, File source, ModuleVersionIdentifier identifier) {
+        this.file = file;
+        this.javadoc = javadoc;
+        this.source = source;
+        moduleVersion = (identifier == null)? null : new DefaultGradleModuleVersion(identifier);
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public File getJavadoc() {
+        return javadoc;
+    }
+
+    public File getSource() {
+        return source;
+    }
+
+    public GradleModuleVersion getGradleModuleVersion() {
+        return moduleVersion;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseLinkedResource.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseLinkedResource.java
new file mode 100644
index 0000000..0ff1f7b
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseLinkedResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseLinkedResourceVersion1;
+
+import java.io.Serializable;
+
+public class DefaultEclipseLinkedResource implements Serializable, EclipseLinkedResourceVersion1 {
+
+    private String name;
+    private String type;
+    private String location;
+    private String locationUri;
+
+    public DefaultEclipseLinkedResource(String name, String type, String location, String locationUri) {
+        this.name = name;
+        this.type = type;
+        this.location = location;
+        this.locationUri = locationUri;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public String getLocationUri() {
+        return locationUri;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProject.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProject.java
new file mode 100644
index 0000000..9f13cd0
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProject.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling.eclipse;
+
+import com.google.common.collect.Lists;
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.gradle.GradleProjectIdentity;
+import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.*;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class DefaultEclipseProject implements EclipseProjectVersion3, Serializable, GradleProjectIdentity {
+    private final String name;
+    private final String path;
+    private EclipseProjectVersion3 parent;
+    private List<ExternalDependencyVersion1> classpath;
+    private final List<EclipseProjectVersion3> children;
+    private List<EclipseSourceDirectoryVersion1> sourceDirectories;
+    private List<EclipseProjectDependencyVersion2> projectDependencies;
+    private final String description;
+    private final File projectDirectory;
+    private Iterable<? extends EclipseTaskVersion1> tasks;
+    private Iterable<? extends EclipseLinkedResourceVersion1> linkedResources;
+    private DefaultGradleProject gradleProject;
+
+    public DefaultEclipseProject(String name, String path, String description, File projectDirectory, Iterable<? extends EclipseProjectVersion3> children) {
+        this.name = name;
+        this.path = path;
+        this.description = description;
+        this.projectDirectory = projectDirectory;
+        this.tasks = Collections.emptyList();
+        this.children = Lists.newArrayList(children);
+        this.classpath = Collections.emptyList();
+        this.sourceDirectories = Collections.emptyList();
+        this.projectDependencies = Collections.emptyList();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("project '%s'", path);
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public EclipseProjectVersion3 getParent() {
+        return parent;
+    }
+
+    public File getProjectDirectory() {
+        return projectDirectory;
+    }
+
+    public void setParent(EclipseProjectVersion3 parent) {
+        this.parent = parent;
+    }
+
+    public List<EclipseProjectVersion3> getChildren() {
+        return children;
+    }
+
+    public Iterable<? extends EclipseSourceDirectoryVersion1> getSourceDirectories() {
+        return sourceDirectories;
+    }
+
+    public void setSourceDirectories(List<EclipseSourceDirectoryVersion1> sourceDirectories) {
+        this.sourceDirectories = sourceDirectories;
+    }
+
+    public Iterable<? extends EclipseProjectDependencyVersion2> getProjectDependencies() {
+        return projectDependencies;
+    }
+
+    public void setProjectDependencies(List<EclipseProjectDependencyVersion2> projectDependencies) {
+        this.projectDependencies = projectDependencies;
+    }
+
+    public List<ExternalDependencyVersion1> getClasspath() {
+        return classpath;
+    }
+    public void setClasspath(List<ExternalDependencyVersion1> classpath) {
+        this.classpath = classpath;
+    }
+
+    public Iterable<? extends EclipseTaskVersion1> getTasks() {
+        return tasks;
+    }
+
+    public void setTasks(Iterable<? extends EclipseTaskVersion1> tasks) {
+        this.tasks = tasks;
+    }
+
+    public Iterable<? extends EclipseLinkedResourceVersion1> getLinkedResources() {
+        return linkedResources;
+    }
+
+    public void setLinkedResources(Iterable<? extends EclipseLinkedResourceVersion1> linkedResources) {
+        this.linkedResources = linkedResources;
+    }
+
+    public DefaultGradleProject<?> getGradleProject() {
+        return gradleProject;
+    }
+
+    public DefaultEclipseProject setGradleProject(DefaultGradleProject gradleProject) {
+        this.gradleProject = gradleProject;
+        return this;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProjectDependency.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProjectDependency.java
new file mode 100644
index 0000000..f042081
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProjectDependency.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
+import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
+
+import java.io.Serializable;
+
+public class DefaultEclipseProjectDependency implements EclipseProjectDependencyVersion2, Serializable {
+    private final String path;
+    private final HierarchicalEclipseProjectVersion1 target;
+
+    public DefaultEclipseProjectDependency(String path, HierarchicalEclipseProjectVersion1 target) {
+        this.target = target;
+        this.path = path;
+    }
+
+    public HierarchicalEclipseProjectVersion1 getTargetProject() {
+        return target;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("project dependency %s (%s)", path, target);
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseSourceDirectory.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseSourceDirectory.java
new file mode 100644
index 0000000..f6ba5b2
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseSourceDirectory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultEclipseSourceDirectory implements EclipseSourceDirectoryVersion1, Serializable {
+    private final String path;
+    private final File directory;
+
+    public DefaultEclipseSourceDirectory(String path, File directory) {
+        this.path = path;
+        this.directory = directory;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("source directory '%s'", path);
+    }
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public String getPath() {
+        return path;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseTask.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseTask.java
new file mode 100644
index 0000000..cebd4bb
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseTask.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
+
+import java.io.Serializable;
+
+public class DefaultEclipseTask implements EclipseTaskVersion1, Serializable {
+    private final EclipseProjectVersion3 project;
+    private final String path;
+    private final String name;
+    private final String description;
+
+    public DefaultEclipseTask(EclipseProjectVersion3 project, String path, String name, String description) {
+        this.project = project;
+        this.path = path;
+        this.name = name;
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("task '%s'", path);
+    }
+
+    public EclipseProjectVersion3 getProject() {
+        return project;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaCompilerOutput.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaCompilerOutput.java
new file mode 100644
index 0000000..1fe7763
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaCompilerOutput.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.idea.IdeaCompilerOutput;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultIdeaCompilerOutput implements IdeaCompilerOutput, Serializable {
+
+    private boolean inheritOutputDirs;
+    private File outputDir;
+    private File testOutputDir;
+
+    public boolean getInheritOutputDirs() {
+        return inheritOutputDirs;
+    }
+
+    public DefaultIdeaCompilerOutput setInheritOutputDirs(boolean inheritOutputDirs) {
+        this.inheritOutputDirs = inheritOutputDirs;
+        return this;
+    }
+
+    public File getOutputDir() {
+        return outputDir;
+    }
+
+    public DefaultIdeaCompilerOutput setOutputDir(File outputDir) {
+        this.outputDir = outputDir;
+        return this;
+    }
+
+    public File getTestOutputDir() {
+        return testOutputDir;
+    }
+
+    public DefaultIdeaCompilerOutput setTestOutputDir(File testOutputDir) {
+        this.testOutputDir = testOutputDir;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaCompilerOutput{"
+                + "inheritOutputDirs=" + inheritOutputDirs
+                + ", outputDir=" + outputDir
+                + ", testOutputDir=" + testOutputDir
+                + '}';
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaContentRoot.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaContentRoot.java
new file mode 100644
index 0000000..b463182
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaContentRoot.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.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.idea.IdeaContentRoot;
+import org.gradle.tooling.model.idea.IdeaSourceDirectory;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultIdeaContentRoot implements IdeaContentRoot, Serializable {
+
+    File rootDirectory;
+    Set<IdeaSourceDirectory> sourceDirectories = new LinkedHashSet<IdeaSourceDirectory>();
+    Set<IdeaSourceDirectory> testDirectories = new LinkedHashSet<IdeaSourceDirectory>();
+    Set<File> excludeDirectories = new LinkedHashSet<File>();
+
+    public File getRootDirectory() {
+        return rootDirectory;
+    }
+
+    public DefaultIdeaContentRoot setRootDirectory(File rootDirectory) {
+        this.rootDirectory = rootDirectory;
+        return this;
+    }
+
+    public DomainObjectSet<IdeaSourceDirectory> getSourceDirectories() {
+        return new ImmutableDomainObjectSet<IdeaSourceDirectory>(sourceDirectories);
+    }
+
+    public DefaultIdeaContentRoot setSourceDirectories(Set<IdeaSourceDirectory> sourceDirectories) {
+        this.sourceDirectories = sourceDirectories;
+        return this;
+    }
+
+    public DomainObjectSet<IdeaSourceDirectory> getTestDirectories() {
+        return new ImmutableDomainObjectSet<IdeaSourceDirectory>(testDirectories);
+    }
+
+    public DefaultIdeaContentRoot setTestDirectories(Set<IdeaSourceDirectory> testDirectories) {
+        this.testDirectories = testDirectories;
+        return this;
+    }
+
+    public Set<File> getExcludeDirectories() {
+        return excludeDirectories;
+    }
+
+    public DefaultIdeaContentRoot setExcludeDirectories(Set<File> excludeDirectories) {
+        this.excludeDirectories = excludeDirectories;
+        return this;
+    }
+
+    public String toString() {
+        return "IdeaContentRoot{"
+                + "rootDirectory=" + rootDirectory
+                + ", sourceDirectories count=" + sourceDirectories.size()
+                + ", testDirectories count=" + testDirectories.size()
+                + ", excludeDirectories count=" + excludeDirectories.size()
+                + '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaDependency.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaDependency.java
new file mode 100644
index 0000000..38d0416
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaDependency.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import java.io.Serializable;
+
+public class DefaultIdeaDependency implements Serializable {
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaDependencyScope.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaDependencyScope.java
new file mode 100644
index 0000000..205d09a
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaDependencyScope.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.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.idea.IdeaDependencyScope;
+
+import java.io.Serializable;
+
+public class DefaultIdeaDependencyScope implements IdeaDependencyScope, Serializable {
+
+    String scope;
+
+    public DefaultIdeaDependencyScope(String scope) {
+        this.scope = scope;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaDependencyScope{"
+                + "scope='" + scope + '\''
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultIdeaDependencyScope)) {
+            return false;
+        }
+
+        DefaultIdeaDependencyScope that = (DefaultIdeaDependencyScope) o;
+
+        if (scope != null ? !scope.equals(that.scope) : that.scope != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return scope != null ? scope.hashCode() : 0;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaLanguageLevel.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaLanguageLevel.java
new file mode 100644
index 0000000..5057431
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaLanguageLevel.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.idea.IdeaLanguageLevel;
+
+import java.io.Serializable;
+
+public class DefaultIdeaLanguageLevel implements IdeaLanguageLevel, Serializable {
+
+    private final String level;
+
+    public DefaultIdeaLanguageLevel(String level) {
+        this.level = level;
+    }
+
+    public boolean isJDK_1_4() {
+        return "JDK_1_4".equals(level);
+    }
+
+    public boolean isJDK_1_5() {
+        return "JDK_1_5".equals(level);
+    }
+
+    public boolean isJDK_1_6() {
+        return "JDK_1_6".equals(level);
+    }
+
+    public boolean isJDK_1_7() {
+        return "JDK_1_7".equals(level);
+    }
+
+    public boolean isJDK_1_8() {
+        return "JDK_1_8".equals(level);
+    }
+
+    public String getLevel() {
+        return level;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaLanguageLevel{level='" + level + "'}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultIdeaLanguageLevel)) {
+            return false;
+        }
+
+        DefaultIdeaLanguageLevel that = (DefaultIdeaLanguageLevel) o;
+
+        if (level != null ? !level.equals(that.level) : that.level != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return level != null ? level.hashCode() : 0;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaModule.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaModule.java
new file mode 100644
index 0000000..9843782
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaModule.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.gradle.GradleProjectIdentity;
+import org.gradle.tooling.model.idea.IdeaCompilerOutput;
+import org.gradle.tooling.model.idea.IdeaContentRoot;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DefaultIdeaModule implements Serializable, GradleProjectIdentity {
+    private String name;
+    private List<? extends IdeaContentRoot> contentRoots = new LinkedList<IdeaContentRoot>();
+    private DefaultIdeaProject parent;
+
+    private List<DefaultIdeaDependency> dependencies = new LinkedList<DefaultIdeaDependency>();
+    private DefaultGradleProject<?> gradleProject;
+
+    private IdeaCompilerOutput compilerOutput;
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultIdeaModule setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public Collection<? extends IdeaContentRoot> getContentRoots() {
+        return contentRoots;
+    }
+
+    public DefaultIdeaModule setContentRoots(List<? extends IdeaContentRoot> contentRoots) {
+        this.contentRoots = contentRoots;
+        return this;
+    }
+
+    public DefaultIdeaProject getParent() {
+        return parent;
+    }
+
+    public DefaultIdeaProject getProject() {
+        return parent;
+    }
+
+    public DefaultIdeaModule setParent(DefaultIdeaProject parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    public Collection<DefaultIdeaDependency> getDependencies() {
+        return dependencies;
+    }
+
+    public DefaultIdeaModule setDependencies(List<DefaultIdeaDependency> dependencies) {
+        this.dependencies = dependencies;
+        return this;
+    }
+
+    public Collection<Object> getChildren() {
+        return Collections.emptySet();
+    }
+
+    public String getDescription() {
+        return null;
+    }
+
+    public DefaultGradleProject getGradleProject() {
+        return gradleProject;
+    }
+
+    public DefaultIdeaModule setGradleProject(DefaultGradleProject gradleProject) {
+        this.gradleProject = gradleProject;
+        return this;
+    }
+
+    public String getPath() {
+        return gradleProject.getPath();
+    }
+
+    public IdeaCompilerOutput getCompilerOutput() {
+        return compilerOutput;
+    }
+
+    public DefaultIdeaModule setCompilerOutput(IdeaCompilerOutput compilerOutput) {
+        this.compilerOutput = compilerOutput;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaModule{"
+                + "name='" + name + '\''
+                + ", gradleProject='" + gradleProject + '\''
+                + ", contentRoots=" + contentRoots
+                + ", compilerOutput=" + compilerOutput
+                + ", dependencies count=" + dependencies.size()
+                + '}';
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaModuleDependency.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaModuleDependency.java
new file mode 100644
index 0000000..0ae71ec
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaModuleDependency.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.idea.IdeaDependencyScope;
+
+public class DefaultIdeaModuleDependency extends DefaultIdeaDependency {
+    private IdeaDependencyScope scope;
+    private DefaultIdeaModule dependencyModule;
+    private boolean exported;
+
+    public IdeaDependencyScope getScope() {
+        return scope;
+    }
+
+    public DefaultIdeaModuleDependency setScope(IdeaDependencyScope scope) {
+        this.scope = scope;
+        return this;
+    }
+
+    public DefaultIdeaModule getDependencyModule() {
+        return dependencyModule;
+    }
+
+    public DefaultIdeaModuleDependency setDependencyModule(DefaultIdeaModule dependencyModule) {
+        this.dependencyModule = dependencyModule;
+        return this;
+    }
+
+    public boolean getExported() {
+        return exported;
+    }
+
+    public DefaultIdeaModuleDependency setExported(boolean exported) {
+        this.exported = exported;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultIdeaModuleDependency{"
+                 + "scope='" + scope + '\''
+                 + ", dependencyModule name='" + dependencyModule.getName() + '\''
+                 + ", exported=" + exported
+                 + '}';
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaProject.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaProject.java
new file mode 100644
index 0000000..aaf0234
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaProject.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.internal.protocol.InternalIdeaProject;
+import org.gradle.tooling.model.HierarchicalElement;
+import org.gradle.tooling.model.idea.IdeaLanguageLevel;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedList;
+
+public class DefaultIdeaProject implements InternalIdeaProject, Serializable {
+    private String name;
+    private String description;
+    private Collection<DefaultIdeaModule> children = new LinkedList<DefaultIdeaModule>();
+    private IdeaLanguageLevel languageLevel;
+    private String jdkName;
+
+    public IdeaLanguageLevel getLanguageLevel() {
+        return languageLevel;
+    }
+
+    public DefaultIdeaProject setLanguageLevel(IdeaLanguageLevel languageLevel) {
+        this.languageLevel = languageLevel;
+        return this;
+    }
+
+    public String getJdkName() {
+        return jdkName;
+    }
+
+    public DefaultIdeaProject setJdkName(String jdkName) {
+        this.jdkName = jdkName;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultIdeaProject setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public DefaultIdeaProject setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public HierarchicalElement getParent() {
+        return null;
+    }
+
+    public File getProjectDirectory() {
+        throw new UnsupportedOperationException("This method should not be used.");
+    }
+
+    public String getPath() {
+        throw new UnsupportedOperationException("This method should not be used.");
+    }
+
+    public DefaultIdeaProject setChildren(Collection<? extends DefaultIdeaModule> children) {
+        this.children.clear();
+        this.children.addAll(children);
+        return this;
+    }
+
+    public Collection<DefaultIdeaModule> getChildren() {
+        return children;
+    }
+
+    public Collection<DefaultIdeaModule> getModules() {
+        return children;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultIdeaProject{"
+                + " name='" + name + '\''
+                + ", description='" + description + '\''
+                + ", children count=" + children.size()
+                + ", languageLevel='" + languageLevel + '\''
+                + ", jdkName='" + jdkName + '\''
+                + '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaSingleEntryLibraryDependency.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaSingleEntryLibraryDependency.java
new file mode 100644
index 0000000..b51bdd0
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaSingleEntryLibraryDependency.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.GradleModuleVersion;
+import org.gradle.tooling.model.idea.IdeaDependencyScope;
+
+import java.io.File;
+
+public class DefaultIdeaSingleEntryLibraryDependency extends DefaultIdeaDependency {
+    private File file;
+    private File source;
+    private File javadoc;
+    private Boolean exported;
+    private IdeaDependencyScope scope;
+    private GradleModuleVersion moduleVersion;
+
+    public File getFile() {
+        return file;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setFile(File file) {
+        this.file = file;
+        return this;
+    }
+
+    public File getSource() {
+        return source;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setSource(File source) {
+        this.source = source;
+        return this;
+    }
+
+    public File getJavadoc() {
+        return javadoc;
+    }
+
+    public GradleModuleVersion getGradleModuleVersion() {
+        return moduleVersion;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setJavadoc(File javadoc) {
+        this.javadoc = javadoc;
+        return this;
+    }
+
+    public boolean getExported() {
+        return exported;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setExported(Boolean exported) {
+        this.exported = exported;
+        return this;
+    }
+
+    public IdeaDependencyScope getScope() {
+        return scope;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setScope(IdeaDependencyScope scope) {
+        this.scope = scope;
+        return this;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setGradleModuleVersion(GradleModuleVersion moduleVersion) {
+        this.moduleVersion = moduleVersion;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaLibraryDependency{"
+                + "file=" + file
+                + ", source=" + source
+                + ", javadoc=" + javadoc
+                + ", exported=" + exported
+                + ", scope='" + scope + '\''
+                + ", id='" + moduleVersion + '\''
+                + '}';
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaSourceDirectory.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaSourceDirectory.java
new file mode 100644
index 0000000..d6685cd
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/idea/DefaultIdeaSourceDirectory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling.idea;
+
+import org.gradle.tooling.model.idea.IdeaSourceDirectory;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultIdeaSourceDirectory implements IdeaSourceDirectory, Serializable {
+
+    private File directory;
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public DefaultIdeaSourceDirectory setDirectory(File directory) {
+        this.directory = directory;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultIdeaSourceDirectory{"
+                + "directory=" + directory
+                + '}';
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java
new file mode 100644
index 0000000..ca6fd46
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.idea;
+
+import org.gradle.tooling.provider.model.internal.LegacyConsumerInterface;
+
+/**
+ * This is here because consumers <= 1.0-milestone-5 expected the implementation class to have a particular name.
+ */
+ at Deprecated
+ at LegacyConsumerInterface("org.gradle.tooling.model.idea.IdeaModuleDependency")
+public class DefaultIdeaModuleDependency extends org.gradle.plugins.ide.internal.tooling.idea.DefaultIdeaModuleDependency {
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
new file mode 100644
index 0000000..dbc50d0
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.idea;
+
+import org.gradle.tooling.provider.model.internal.LegacyConsumerInterface;
+
+/**
+ * This is here because consumers <= 1.0-milestone-5 expected the implementation class to have a particular name.
+ */
+ at LegacyConsumerInterface("org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency")
+ at Deprecated
+public class DefaultIdeaSingleEntryLibraryDependency extends org.gradle.plugins.ide.internal.tooling.idea.DefaultIdeaSingleEntryLibraryDependency {
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BasicIdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BasicIdeaModelBuilder.java
deleted file mode 100644
index 7f251c2..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BasicIdeaModelBuilder.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.provider;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.tooling.internal.protocol.InternalBasicIdeaProject;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-
-/**
- * @author: Szczepan Faber, created at: 7/23/11
- */
-public class BasicIdeaModelBuilder implements BuildsModel {
-    public boolean canBuild(Class<?> type) {
-        return type == InternalBasicIdeaProject.class;
-    }
-
-    public ProjectVersion3 buildAll(GradleInternal gradle) {
-        return new IdeaModelBuilder()
-                .setOfflineDependencyResolution(true)
-                .buildAll(gradle);
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
deleted file mode 100644
index 5e0fc4b..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
+++ /dev/null
@@ -1,88 +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.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.api.Action;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-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;
-
-import static java.util.Arrays.asList;
-
-public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
-    private final BuildsModel builder;
-    private final boolean runTasks;
-    private ProjectVersion3 model;
-
-    public BuildModelAction(Class<?> type, boolean runTasks) {
-        this.runTasks = runTasks;
-        List<? extends BuildsModel> modelBuilders = asList(
-                new NullResultBuilder(),
-                new EclipseModelBuilder(),
-                new IdeaModelBuilder(),
-                new GradleProjectBuilder(),
-                new BasicIdeaModelBuilder(),
-                new ProjectOutcomesModelBuilder());
-
-        for (BuildsModel builder : modelBuilders) {
-            if (builder.canBuild(type)) {
-                this.builder = builder;
-                return;
-            }
-        }
-
-        throw new UnsupportedOperationException(String.format("I don't know how to build a model of type '%s'.", type.getSimpleName()));
-    }
-
-    public BuildResult run(GradleLauncher launcher) {
-
-        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) {
-                    ensureAllProjectsEvaluated(gradle);
-                    model = builder.buildAll(gradle);
-                }
-            });
-            return launcher.getBuildAnalysis();
-        }
-    }
-
-    private void ensureAllProjectsEvaluated(GradleInternal gradle) {
-        gradle.getRootProject().allprojects((Action) new Action<ProjectInternal>() {
-            public void execute(ProjectInternal projectInternal) {
-                projectInternal.evaluate();
-            }
-        });
-    }
-
-    public ProjectVersion3 getResult() {
-        return model;
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildsModel.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildsModel.java
deleted file mode 100644
index 9bdc3b0..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildsModel.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.provider;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-
-/**
-* @author: Szczepan Faber, created at: 7/23/11
-*/
-public interface BuildsModel {
-    boolean canBuild(Class<?> type);
-    ProjectVersion3 buildAll(GradleInternal gradle);
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
deleted file mode 100644
index 9947d1f..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
+++ /dev/null
@@ -1,162 +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.apache.commons.lang.StringUtils;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.plugins.ide.eclipse.EclipsePlugin;
-import org.gradle.plugins.ide.eclipse.model.*;
-import org.gradle.tooling.internal.eclipse.*;
-import org.gradle.tooling.internal.protocol.BuildableProjectVersion1;
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.util.GUtil;
-import org.gradle.util.ReflectionUtil;
-
-import java.io.File;
-import java.util.*;
-
-/**
-* @author Adam Murdoch, Szczepan Faber, @date: 17.03.11
-*/
-public class EclipseModelBuilder implements BuildsModel {
-    private boolean projectDependenciesOnly;
-    private EclipseProjectVersion3 currentProject;
-    private final Map<String, EclipseProjectVersion3> projectMapping = new HashMap<String, EclipseProjectVersion3>();
-    private GradleInternal gradle;
-    private TasksFactory tasksFactory;
-    private GradleProjectBuilder gradleProjectBuilder = new GradleProjectBuilder();
-    private GradleProject rootGradleProject;
-
-    public boolean canBuild(Class<?> type) {
-        if (type.isAssignableFrom(EclipseProjectVersion3.class)) {
-            //I don't like preparing the state in this method but for now lets leave it :/
-            boolean includeTasks = BuildableProjectVersion1.class.isAssignableFrom(type);
-            this.tasksFactory = new TasksFactory(includeTasks);
-            this.projectDependenciesOnly = !EclipseProjectVersion3.class.isAssignableFrom(type);
-            return true;
-        }
-        return false;
-    }
-
-    public EclipseProjectVersion3 buildAll(GradleInternal gradle) {
-        this.gradle = gradle;
-        rootGradleProject = gradleProjectBuilder.buildAll(gradle);
-        Project root = gradle.getRootProject();
-        tasksFactory.collectTasks(root);
-        applyEclipsePlugin(root);
-        buildHierarchy(root);
-        populate(root);
-        return currentProject;
-    }
-
-    private void applyEclipsePlugin(Project root) {
-        Set<Project> allprojects = root.getAllprojects();
-        for (Project p : allprojects) {
-            p.getPlugins().apply(EclipsePlugin.class);
-        }
-        root.getPlugins().getPlugin(EclipsePlugin.class).makeSureProjectNamesAreUnique();
-    }
-
-    private void addProject(Project project, EclipseProjectVersion3 eclipseProject) {
-        if (project == gradle.getDefaultProject()) {
-            currentProject = eclipseProject;
-        }
-        projectMapping.put(project.getPath(), eclipseProject);
-    }
-
-    private void populate(Project project) {
-        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
-        EclipseClasspath classpath = eclipseModel.getClasspath();
-
-        classpath.setProjectDependenciesOnly(projectDependenciesOnly);
-        List<ClasspathEntry> entries = classpath.resolveDependencies();
-
-        final List<ExternalDependencyVersion1> externalDependencies = new LinkedList<ExternalDependencyVersion1>();
-        final List<EclipseProjectDependencyVersion2> projectDependencies = new LinkedList<EclipseProjectDependencyVersion2>();
-        final List<EclipseSourceDirectoryVersion1> sourceDirectories = new LinkedList<EclipseSourceDirectoryVersion1>();
-
-        for (ClasspathEntry entry : entries) {
-            //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();
-                final File source = library.getSourcePath() == null ? null : library.getSourcePath().getFile();
-                final File javadoc = library.getJavadocPath() == null ? null : library.getJavadocPath().getFile();
-                externalDependencies.add(new DefaultEclipseExternalDependency(file, javadoc, source, library.getModuleVersion()));
-            } else if (entry instanceof ProjectDependency) {
-                final ProjectDependency projectDependency = (ProjectDependency) entry;
-                final String path = StringUtils.removeStart(projectDependency.getPath(), "/");
-                projectDependencies.add(new DefaultEclipseProjectDependency(path, projectMapping.get(projectDependency.getGradlePath())));
-            } else if (entry instanceof SourceFolder) {
-                String path = ((SourceFolder) entry).getPath();
-                sourceDirectories.add(new DefaultEclipseSourceDirectory(path, project.file(path)));
-            }
-        }
-
-        final EclipseProjectVersion3 eclipseProject = projectMapping.get(project.getPath());
-        ReflectionUtil.setProperty(eclipseProject, "classpath", externalDependencies);
-        ReflectionUtil.setProperty(eclipseProject, "projectDependencies", projectDependencies);
-        ReflectionUtil.setProperty(eclipseProject, "sourceDirectories", sourceDirectories);
-
-        if (ReflectionUtil.hasProperty(eclipseProject, "linkedResources")) {
-            List<DefaultEclipseLinkedResource> linkedResources = new LinkedList<DefaultEclipseLinkedResource>();
-            for(Link r: eclipseModel.getProject().getLinkedResources()) {
-                linkedResources.add(new DefaultEclipseLinkedResource(r.getName(), r.getType(), r.getLocation(), r.getLocationUri()));
-            }
-            ReflectionUtil.setProperty(eclipseProject, "linkedResources", linkedResources);
-        }
-
-        List<EclipseTaskVersion1> out = new ArrayList<EclipseTaskVersion1>();
-        for (final Task t : tasksFactory.getTasks(project)) {
-            out.add(new DefaultEclipseTask(eclipseProject, t.getPath(), t.getName(), t.getDescription()));
-        }
-        ReflectionUtil.setProperty(eclipseProject, "tasks", out);
-
-        for (Project childProject : project.getChildProjects().values()) {
-            populate(childProject);
-        }
-    }
-
-    private EclipseProjectVersion3 buildHierarchy(Project project) {
-        List<EclipseProjectVersion3> children = new ArrayList<EclipseProjectVersion3>();
-        for (Project child : project.getChildProjects().values()) {
-            children.add(buildHierarchy(child));
-        }
-
-        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
-        org.gradle.plugins.ide.eclipse.model.EclipseProject internalProject = eclipseModel.getProject();
-        String name = internalProject.getName();
-        String description = GUtil.elvis(internalProject.getComment(), null);
-        EclipseProjectVersion3 eclipseProject =
-                new DefaultEclipseProject(name, project.getPath(), description, project.getProjectDir(), children)
-                .setGradleProject(rootGradleProject.findByPath(project.getPath()));
-
-        for (Object child : children) {
-            ReflectionUtil.setProperty(child, "parent", eclipseProject);
-        }
-        addProject(project, eclipseProject);
-        return eclipseProject;
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/FileOutcomeIdentifier.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/FileOutcomeIdentifier.java
deleted file mode 100644
index 24b7e3c..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/FileOutcomeIdentifier.java
+++ /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.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/GradleProjectBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/GradleProjectBuilder.java
deleted file mode 100644
index fd8ddaa..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/GradleProjectBuilder.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.provider;
-
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.tasks.TaskContainer;
-import org.gradle.tooling.internal.gradle.DefaultGradleProject;
-import org.gradle.tooling.internal.gradle.DefaultGradleTask;
-import org.gradle.tooling.internal.protocol.InternalGradleProject;
-import org.gradle.tooling.model.GradleTask;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Builds the GradleProject that contains the project hierarchy and task information
- *
- * @author: Szczepan Faber, created at: 7/27/11
- */
-public class GradleProjectBuilder implements BuildsModel {
-    public boolean canBuild(Class<?> type) {
-        return type == InternalGradleProject.class;
-    }
-
-    public DefaultGradleProject buildAll(GradleInternal gradle) {
-        return buildHierarchy(gradle.getRootProject());
-    }
-
-    private DefaultGradleProject buildHierarchy(Project project) {
-        List<DefaultGradleProject> children = new ArrayList<DefaultGradleProject>();
-        for (Project child : project.getChildProjects().values()) {
-            children.add(buildHierarchy(child));
-        }
-
-        DefaultGradleProject gradleProject = new DefaultGradleProject()
-                .setPath(project.getPath())
-                .setName(project.getName())
-                .setDescription(project.getDescription())
-                .setChildren(children);
-
-        gradleProject.setTasks(tasks(gradleProject, project.getTasks()));
-
-        for (DefaultGradleProject child : children) {
-            child.setParent(gradleProject);
-        }
-
-        return gradleProject;
-    }
-
-    private List<GradleTask> tasks(DefaultGradleProject owner, TaskContainer tasks) {
-        List<GradleTask> out = new LinkedList<GradleTask>();
-
-        for (Task t : tasks) {
-            out.add(new DefaultGradleTask()
-                    .setPath(t.getPath())
-                    .setName(t.getName())
-                    .setDescription(t.getDescription())
-                    .setProject(owner));
-        }
-
-        return out;
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
deleted file mode 100644
index 02eb24a..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.provider;
-
-import org.gradle.api.Project;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.plugins.ide.idea.IdeaPlugin;
-import org.gradle.plugins.ide.idea.model.*;
-import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
-import org.gradle.tooling.internal.idea.*;
-import org.gradle.tooling.internal.protocol.InternalIdeaProject;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.tooling.model.idea.IdeaDependency;
-import org.gradle.tooling.model.idea.IdeaSourceDirectory;
-
-import java.io.File;
-import java.util.*;
-
-/**
- * @author: Szczepan Faber, created at: 7/23/11
- */
-public class IdeaModelBuilder implements BuildsModel {
-    public boolean canBuild(Class<?> type) {
-        return type == InternalIdeaProject.class;
-    }
-
-    private final GradleProjectBuilder gradleProjectBuilder = new GradleProjectBuilder();
-    private boolean offlineDependencyResolution;
-
-    public ProjectVersion3 buildAll(GradleInternal gradle) {
-        Project root = gradle.getRootProject();
-        applyIdeaPlugin(root);
-        GradleProject rootGradleProject = gradleProjectBuilder.buildAll(gradle);
-        return build(root, rootGradleProject);
-    }
-
-    private void applyIdeaPlugin(Project root) {
-        Set<Project> allprojects = root.getAllprojects();
-        for (Project p : allprojects) {
-            p.getPlugins().apply(IdeaPlugin.class);
-        }
-        root.getPlugins().getPlugin(IdeaPlugin.class).makeSureModuleNamesAreUnique();
-    }
-
-    private ProjectVersion3 build(Project project, GradleProject rootGradleProject) {
-        IdeaModel ideaModel = project.getPlugins().getPlugin(IdeaPlugin.class).getModel();
-        IdeaProject projectModel = ideaModel.getProject();
-
-        DefaultIdeaProject out = new DefaultIdeaProject()
-                .setName(projectModel.getName())
-                .setJdkName(projectModel.getJdkName())
-                .setLanguageLevel(new DefaultIdeaLanguageLevel(projectModel.getLanguageLevel().getLevel()));
-
-        Map<String, DefaultIdeaModule> modules = new HashMap<String, DefaultIdeaModule>();
-        for (IdeaModule module : projectModel.getModules()) {
-            appendModule(modules, module, out, rootGradleProject);
-        }
-        for (IdeaModule module : projectModel.getModules()) {
-            buildDependencies(modules, module);
-        }
-        out.setChildren(new LinkedList<DefaultIdeaModule>(modules.values()));
-
-        return out;
-    }
-
-    private void buildDependencies(Map<String, DefaultIdeaModule> modules, IdeaModule ideaModule) {
-        ideaModule.setOffline(offlineDependencyResolution);
-        Set<Dependency> resolved = ideaModule.resolveDependencies();
-        List<IdeaDependency> dependencies = new LinkedList<IdeaDependency>();
-        for (Dependency dependency : resolved) {
-            if (dependency instanceof SingleEntryModuleLibrary) {
-                SingleEntryModuleLibrary d = (SingleEntryModuleLibrary) dependency;
-                DefaultIdeaSingleEntryLibraryDependency defaultDependency = new DefaultIdeaSingleEntryLibraryDependency()
-                        .setFile(d.getLibraryFile())
-                        .setSource(d.getSourceFile())
-                        .setJavadoc(d.getJavadocFile())
-                        .setScope(new DefaultIdeaDependencyScope(d.getScope()))
-                        .setExported(d.getExported());
-
-                if (d.getModuleVersion() != null) {
-                    defaultDependency.setGradleModuleVersion(new DefaultGradleModuleVersion(d.getModuleVersion()));
-                }
-                dependencies.add(defaultDependency);
-            } else if (dependency instanceof ModuleDependency) {
-                ModuleDependency d = (ModuleDependency) dependency;
-                IdeaDependency defaultDependency = new DefaultIdeaModuleDependency()
-                        .setExported(d.getExported())
-                        .setScope(new DefaultIdeaDependencyScope(d.getScope()))
-                        .setDependencyModule(modules.get(d.getName()));
-                dependencies.add(defaultDependency);
-            }
-        }
-        modules.get(ideaModule.getName()).setDependencies(dependencies);
-    }
-
-    private void appendModule(Map<String, DefaultIdeaModule> modules, IdeaModule ideaModule, DefaultIdeaProject ideaProject, GradleProject rootGradleProject) {
-        DefaultIdeaContentRoot contentRoot = new DefaultIdeaContentRoot()
-            .setRootDirectory(ideaModule.getContentRoot())
-            .setSourceDirectories(srcDirs(ideaModule.getSourceDirs()))
-            .setTestDirectories(srcDirs(ideaModule.getTestSourceDirs()))
-            .setExcludeDirectories(ideaModule.getExcludeDirs());
-
-        DefaultIdeaModule defaultIdeaModule = new DefaultIdeaModule()
-                .setName(ideaModule.getName())
-                .setParent(ideaProject)
-                .setGradleProject(rootGradleProject.findByPath(ideaModule.getProject().getPath()))
-                .setContentRoots(Collections.singletonList(contentRoot))
-                .setCompilerOutput(new DefaultIdeaCompilerOutput()
-                    .setInheritOutputDirs(ideaModule.getInheritOutputDirs() != null ? ideaModule.getInheritOutputDirs() : false)
-                    .setOutputDir(ideaModule.getOutputDir())
-                    .setTestOutputDir(ideaModule.getTestOutputDir())
-                );
-
-        modules.put(ideaModule.getName(), defaultIdeaModule);
-    }
-
-    private Set<IdeaSourceDirectory> srcDirs(Set<File> sourceDirs) {
-        Set<IdeaSourceDirectory> out = new LinkedHashSet<IdeaSourceDirectory>();
-        for (File s : sourceDirs) {
-            out.add(new DefaultIdeaSourceDirectory().setDirectory(s));
-        }
-        return out;
-    }
-
-    public IdeaModelBuilder setOfflineDependencyResolution(boolean offlineDependencyResolution) {
-        this.offlineDependencyResolution = offlineDependencyResolution;
-        return this;
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/NullResultBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/NullResultBuilder.java
deleted file mode 100644
index 752770d..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/NullResultBuilder.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.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
deleted file mode 100644
index eaeaf58..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ProjectOutcomesModelBuilder.java
+++ /dev/null
@@ -1,71 +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.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
deleted file mode 100644
index 4c7c445..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformer.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.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/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java
deleted file mode 100644
index a1215a7..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.provider;
-
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-
-import java.util.Map;
-import java.util.Set;
-
-import static java.util.Collections.emptySet;
-
-public class TasksFactory {
-    Map<Project, Set<Task>> allTasks;
-    private final boolean includeTasks;
-
-    public TasksFactory(boolean includeTasks) {
-        this.includeTasks = includeTasks;
-    }
-
-    public void collectTasks(Project root) {
-        allTasks = root.getAllTasks(true);
-    }
-
-    public Set<Task> getTasks(Project project) {
-        if (includeTasks) {
-            return allTasks.get(project);
-        } else {
-            return emptySet();
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/DefaultIdeDependencyResolver.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/DefaultIdeDependencyResolver.java
new file mode 100644
index 0000000..42d39d0
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/DefaultIdeDependencyResolver.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+import org.gradle.api.artifacts.result.*;
+import org.gradle.api.specs.Spec;
+import org.gradle.plugins.ide.internal.resolver.model.IdeExtendedRepoFileDependency;
+import org.gradle.plugins.ide.internal.resolver.model.IdeLocalFileDependency;
+import org.gradle.plugins.ide.internal.resolver.model.IdeProjectDependency;
+import org.gradle.plugins.ide.internal.resolver.model.UnresolvedIdeRepoFileDependency;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
+    /**
+     * Gets IDE project dependencies.
+     *
+     * @param configuration Configuration
+     * @param project Project
+     * @return IDE project dependencies
+     */
+    public List<IdeProjectDependency> getIdeProjectDependencies(Configuration configuration, Project project) {
+        ResolutionResult result = getIncomingResolutionResult(configuration);
+        List<ResolvedComponentResult> projectComponents = findAllResolvedDependencyResults(result.getRoot().getDependencies(), ProjectComponentIdentifier.class);
+
+        List<IdeProjectDependency> ideProjectDependencies = new ArrayList<IdeProjectDependency>();
+
+        for (ResolvedComponentResult projectComponent : projectComponents) {
+            Project resolvedProject = project.project(((ProjectComponentIdentifier) projectComponent.getId()).getProjectPath());
+            ideProjectDependencies.add(new IdeProjectDependency(configuration, resolvedProject));
+        }
+
+        return ideProjectDependencies;
+    }
+
+    /**
+     * Finds all resolved components of the given type from the given set of dependency edges.
+     *
+     * @param dependencies Dependencies
+     * @return Resolved dependency results
+     */
+    private List<ResolvedComponentResult> findAllResolvedDependencyResults(Set<? extends DependencyResult> dependencies, Class<? extends ComponentIdentifier> type) {
+        List<ResolvedComponentResult> matches = new ArrayList<ResolvedComponentResult>();
+
+        for (DependencyResult dependencyResult : dependencies) {
+            if (dependencyResult instanceof ResolvedDependencyResult) {
+                ResolvedDependencyResult resolvedResult = (ResolvedDependencyResult) dependencyResult;
+                if (type.isInstance(resolvedResult.getSelected().getId())) {
+                    matches.add(resolvedResult.getSelected());
+                }
+            }
+        }
+
+        return matches;
+    }
+
+    /**
+     * Gets unresolved IDE repository file dependencies.
+     *
+     * @param configuration Configuration
+     * @return Unresolved IDE repositoru file dependencies
+     */
+    public List<UnresolvedIdeRepoFileDependency> getUnresolvedIdeRepoFileDependencies(Configuration configuration) {
+        List<ComponentSelector> componentSelectors = getUnresolvedComponentSelectors(configuration);
+        List<UnresolvedIdeRepoFileDependency> unresolvedIdeRepoFileDependencies = new ArrayList<UnresolvedIdeRepoFileDependency>();
+
+        for (ComponentSelector componentSelector : componentSelectors) {
+            UnresolvedIdeRepoFileDependency unresolvedIdeRepoFileDependency = new UnresolvedIdeRepoFileDependency(configuration, new File(unresolvedFileName(componentSelector)));
+            unresolvedIdeRepoFileDependencies.add(unresolvedIdeRepoFileDependency);
+        }
+
+        return unresolvedIdeRepoFileDependencies;
+    }
+
+    /**
+     * Creates unresolved file name.
+     *
+     * @param componentSelector Component selector
+     * @return Unresolved file name
+     */
+    private String unresolvedFileName(ComponentSelector componentSelector) {
+        return "unresolved dependency - " + componentSelector.getDisplayName().replaceAll(":", " ");
+    }
+
+    /**
+     * Gets IDE local file dependencies.
+     *
+     * @param configuration Configuration
+     * @return IDE local file dependencies
+     */
+    public List<IdeExtendedRepoFileDependency> getIdeRepoFileDependencies(Configuration configuration) {
+        ResolutionResult result = getIncomingResolutionResult(configuration);
+        List<ResolvedComponentResult> resolvedDependencies = findAllResolvedDependencyResults(result.getAllDependencies(), ModuleComponentIdentifier.class);
+        Set<ModuleVersionIdentifier> mappedResolvedDependencies = mapResolvedDependencies(resolvedDependencies);
+        Set<ResolvedArtifact> artifacts = getExternalArtifacts(configuration);
+
+        List<IdeExtendedRepoFileDependency> externalDependencies = new ArrayList<IdeExtendedRepoFileDependency>();
+
+        for (ResolvedArtifact artifact : artifacts) {
+            if (mappedResolvedDependencies.contains(artifact.getModuleVersion().getId())) {
+                IdeExtendedRepoFileDependency ideRepoFileDependency = new IdeExtendedRepoFileDependency(configuration, artifact.getFile());
+                ideRepoFileDependency.setId(artifact.getModuleVersion().getId());
+                externalDependencies.add(ideRepoFileDependency);
+            }
+        }
+
+        return externalDependencies;
+    }
+
+    /**
+     * Maps resolved dependencies by module version identifier.
+     *
+     * @param components Resolved dependencies
+     * @return Mapped, resolved dependencies
+     */
+    private Set<ModuleVersionIdentifier> mapResolvedDependencies(List<ResolvedComponentResult> components) {
+        Set<ModuleVersionIdentifier> mappedResolvedDependencies = new LinkedHashSet<ModuleVersionIdentifier>();
+        for (ResolvedComponentResult component : components) {
+            mappedResolvedDependencies.add(component.getModuleVersion());
+        }
+        return mappedResolvedDependencies;
+    }
+
+    /**
+     * Gets IDE local file dependencies.
+     *
+     * @param configuration Configuration
+     * @return IDE local file dependencies
+     */
+    public List<IdeLocalFileDependency> getIdeLocalFileDependencies(Configuration configuration) {
+        List<SelfResolvingDependency> externalDependencies = findAllExternalDependencies(configuration);
+        List<IdeLocalFileDependency> ideLocalFileDependencies = new ArrayList<IdeLocalFileDependency>();
+
+        for (SelfResolvingDependency externalDependency : externalDependencies) {
+            Set<File> resolvedFiles = externalDependency.resolve();
+
+            for (File resolvedFile : resolvedFiles) {
+                IdeLocalFileDependency ideLocalFileDependency = new IdeLocalFileDependency(configuration, resolvedFile);
+                ideLocalFileDependencies.add(ideLocalFileDependency);
+            }
+        }
+
+        return ideLocalFileDependencies;
+    }
+
+    /**
+     * Finds all external dependencies.
+     *
+     * @param configuration Configuration
+     * @return External dependencies
+     */
+    private List<SelfResolvingDependency> findAllExternalDependencies(Configuration configuration) {
+        List<SelfResolvingDependency> externalDependencies = new ArrayList<SelfResolvingDependency>();
+
+        for (Dependency dependency : configuration.getAllDependencies()) {
+            if (dependency instanceof SelfResolvingDependency && !(dependency instanceof ProjectDependency)) {
+                externalDependencies.add((SelfResolvingDependency) dependency);
+            }
+        }
+
+        return externalDependencies;
+    }
+
+    /**
+     * Gets incoming resolution result for a given configuration.
+     *
+     * @param configuration Configuration
+     * @return Incoming resolution result
+     */
+    private ResolutionResult getIncomingResolutionResult(Configuration configuration) {
+        return configuration.getIncoming().getResolutionResult();
+    }
+
+    /**
+     * Gets unresolved component selectors for a given configuration.
+     *
+     * @param configuration Configuration
+     * @return List of unresolved component selectors
+     */
+    private List<ComponentSelector> getUnresolvedComponentSelectors(Configuration configuration) {
+        ResolutionResult result = getIncomingResolutionResult(configuration);
+        List<UnresolvedDependencyResult> unresolvedDependencies = findAllUnresolvedDependencyResults(result.getRoot().getDependencies());
+        List<ComponentSelector> componentSelectors = new ArrayList<ComponentSelector>();
+
+        for (UnresolvedDependencyResult unresolvedDependencyResult : unresolvedDependencies) {
+            componentSelectors.add(unresolvedDependencyResult.getAttempted());
+        }
+
+        return componentSelectors;
+    }
+
+    /**
+     * Finds all unresolved dependency results.
+     *
+     * @param dependencies Unfiltered dependencies
+     * @return Unresolved dependency results.
+     */
+    private List<UnresolvedDependencyResult> findAllUnresolvedDependencyResults(Set<? extends DependencyResult> dependencies) {
+        List<UnresolvedDependencyResult> unresolvedDependencyResults = new ArrayList<UnresolvedDependencyResult>();
+
+        for (DependencyResult dependencyResult : dependencies) {
+            if (dependencyResult instanceof UnresolvedDependencyResult) {
+                unresolvedDependencyResults.add((UnresolvedDependencyResult) dependencyResult);
+            }
+        }
+
+        return unresolvedDependencyResults;
+    }
+
+    /**
+     * Gets all external artifacts.
+     *
+     * @param configuration Configuration
+     * @return External artifacts
+     */
+    private Set<ResolvedArtifact> getExternalArtifacts(Configuration configuration) {
+        return configuration.getResolvedConfiguration().getLenientConfiguration().getArtifacts(new ExternalDependencySpec());
+    }
+
+    private class ExternalDependencySpec implements Spec<Dependency> {
+        public boolean isSatisfiedBy(Dependency element) {
+            return element instanceof ExternalDependency;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/IdeDependencyResolver.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/IdeDependencyResolver.java
new file mode 100644
index 0000000..7d63e57
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/IdeDependencyResolver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.plugins.ide.internal.resolver.model.IdeExtendedRepoFileDependency;
+import org.gradle.plugins.ide.internal.resolver.model.IdeLocalFileDependency;
+import org.gradle.plugins.ide.internal.resolver.model.IdeProjectDependency;
+import org.gradle.plugins.ide.internal.resolver.model.UnresolvedIdeRepoFileDependency;
+
+import java.util.List;
+
+public interface IdeDependencyResolver {
+    /**
+     * Gets IDE project dependencies for a given configuration.
+     *
+     * @param configuration Configuration
+     * @param project Project
+     * @return List of resolved IDE project dependencies
+     */
+    List<IdeProjectDependency> getIdeProjectDependencies(Configuration configuration, Project project);
+
+    /**
+     * Gets unresolved IDE repository file dependencies for a given configuration.
+     *
+     * @param configuration Configuration
+     * @return List of unresolved IDE repository file dependencies
+     */
+    List<UnresolvedIdeRepoFileDependency> getUnresolvedIdeRepoFileDependencies(Configuration configuration);
+
+    /**
+     * Gets IDE repository file dependencies for a given configuration.
+     *
+     * @param configuration Configuration
+     * @return List of IDE repository file dependencies
+     */
+    List<IdeExtendedRepoFileDependency> getIdeRepoFileDependencies(Configuration configuration);
+
+    /**
+     * Gets IDE local file dependencies for a given configuration.
+     *
+     * @param configuration Configuration
+     * @return List of IDE local file dependencies
+     */
+    List<IdeLocalFileDependency> getIdeLocalFileDependencies(Configuration configuration);
+
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeDependency.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeDependency.java
new file mode 100644
index 0000000..f39229d
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeDependency.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import org.gradle.api.artifacts.Configuration;
+
+public class IdeDependency {
+    private final Configuration declaredConfiguration;
+
+    public IdeDependency(Configuration declaredConfiguration) {
+        this.declaredConfiguration = declaredConfiguration;
+    }
+
+    public Configuration getDeclaredConfiguration() {
+        return declaredConfiguration;
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeDependencyKey.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeDependencyKey.java
new file mode 100644
index 0000000..7f6a500
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeDependencyKey.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import org.gradle.plugins.ide.idea.model.Dependency;
+
+/**
+ * Key used to merge same dependency extracted from different configurations.
+ */
+public abstract class IdeDependencyKey<T extends IdeDependency, R> {
+
+
+    public static IdeDependencyKey<IdeExtendedRepoFileDependency, Dependency> forRepoFileDependency(
+            IdeExtendedRepoFileDependency dependency, DependencyBuilder<IdeExtendedRepoFileDependency, Dependency> dependencyBuilder) {
+        return new RepoFileDependencyKey<Dependency>(dependency, dependencyBuilder);
+    }
+
+    public interface DependencyBuilder<T extends IdeDependency, R> {
+        R buildDependency(T dependency, String scope);
+    }
+
+    public static <D> IdeDependencyKey<IdeProjectDependency, D> forProjectDependency(
+            IdeProjectDependency dependency, DependencyBuilder<IdeProjectDependency, D> dependencyBuilder) {
+        return new ProjectDependencyKey(dependency, dependencyBuilder);
+    }
+
+    public static <D> IdeDependencyKey<IdeLocalFileDependency, D> forLocalFileDependency(
+            IdeLocalFileDependency dependency, DependencyBuilder<IdeLocalFileDependency, D> dependencyBuilder) {
+        return new LocalFileDependencyKey(dependency, dependencyBuilder);
+    }
+
+    protected final T ideDependency;
+    private final DependencyBuilder<T, ? extends R> dependencyBuilder;
+
+    protected IdeDependencyKey(T ideDependency, DependencyBuilder<T, R> dependencyBuilder) {
+        this.ideDependency = Preconditions.checkNotNull(ideDependency);
+        this.dependencyBuilder = Preconditions.checkNotNull(dependencyBuilder);
+    }
+
+    public R buildDependency(String scope) {
+        return dependencyBuilder.buildDependency(ideDependency, scope);
+    }
+
+    protected abstract boolean isSameDependency(IdeDependency otherDependency);
+    protected abstract int dependencyHashCode();
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof IdeDependencyKey)) {
+            return false;
+        }
+
+        IdeDependencyKey that = (IdeDependencyKey) o;
+
+        if (!isSameDependency(that.ideDependency)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return dependencyHashCode();
+    }
+
+    private static class LocalFileDependencyKey<R> extends IdeDependencyKey<IdeLocalFileDependency, R> {
+        private LocalFileDependencyKey(IdeLocalFileDependency dependency, DependencyBuilder<IdeLocalFileDependency, R> dependencyBuilder) {
+            super(dependency, dependencyBuilder);
+        }
+
+        @Override
+        protected int dependencyHashCode() {
+            return ideDependency.getFile().hashCode();
+        }
+
+        @Override
+        protected boolean isSameDependency(IdeDependency otherDependency) {
+            if (!(otherDependency instanceof IdeLocalFileDependency)) {
+                return false;
+            }
+            return Objects.equal(ideDependency.getFile(), ((IdeLocalFileDependency) otherDependency).getFile());
+        }
+
+        public String toString() {
+            return "LocalFileDependencyKey{" + ideDependency.getFile() + "}";
+        }
+    }
+
+    private static class ProjectDependencyKey<R> extends IdeDependencyKey<IdeProjectDependency, R> {
+        private ProjectDependencyKey(IdeProjectDependency dependency, DependencyBuilder<IdeProjectDependency, R> dependencyBuilder) {
+            super(dependency, dependencyBuilder);
+        }
+
+        @Override
+        protected int dependencyHashCode() {
+            return ideDependency.getProject().hashCode();
+        }
+
+        @Override
+        protected boolean isSameDependency(IdeDependency otherDependency) {
+            if (!(otherDependency instanceof IdeProjectDependency)) {
+                return false;
+            }
+            return Objects.equal(ideDependency.getProject(), ((IdeProjectDependency) otherDependency).getProject());
+        }
+
+        public String toString() {
+            return "ProjectDependencyKey{" + ideDependency.getProject() + "}";
+        }
+    }
+
+    private static class RepoFileDependencyKey<R> extends IdeDependencyKey<IdeExtendedRepoFileDependency, R> {
+        private RepoFileDependencyKey(IdeExtendedRepoFileDependency dependency, DependencyBuilder<IdeExtendedRepoFileDependency, R> dependencyBuilder) {
+            super(dependency, dependencyBuilder);
+        }
+
+        @Override
+        protected int dependencyHashCode() {
+            int hash = ideDependency.getFile().hashCode();
+            hash = 31 * hash + (ideDependency.getId() != null ? ideDependency.getId().hashCode() : 1);
+            return hash;
+        }
+
+        @Override
+        protected boolean isSameDependency(IdeDependency otherDependency) {
+            if (!(otherDependency instanceof IdeRepoFileDependency)) {
+                return false;
+            }
+            IdeRepoFileDependency that = (IdeRepoFileDependency) otherDependency;
+            return Objects.equal(ideDependency.getFile(), that.getFile()) && Objects.equal(ideDependency.getId(), that.getId());
+        }
+
+        public String toString() {
+            return "RepoFileDependencyKey{" + ideDependency.getId() + "}";
+        }
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeExtendedRepoFileDependency.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeExtendedRepoFileDependency.java
new file mode 100644
index 0000000..39f0f86
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeExtendedRepoFileDependency.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+
+public class IdeExtendedRepoFileDependency extends IdeRepoFileDependency {
+    private File sourceFile;
+    private File javadocFile;
+
+    public IdeExtendedRepoFileDependency(Configuration declaredConfiguration, File file) {
+        super(declaredConfiguration, file);
+    }
+
+    public File getSourceFile() {
+        return sourceFile;
+    }
+
+    public void setSourceFile(File sourceFile) {
+        this.sourceFile = sourceFile;
+    }
+
+    public File getJavadocFile() {
+        return javadocFile;
+    }
+
+    public void setJavadocFile(File javadocFile) {
+        this.javadocFile = javadocFile;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeLocalFileDependency.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeLocalFileDependency.java
new file mode 100644
index 0000000..612fcd4
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeLocalFileDependency.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+
+public class IdeLocalFileDependency extends IdeDependency {
+    private final File file;
+
+    public IdeLocalFileDependency(Configuration declaredConfiguration, File file) {
+        super(declaredConfiguration);
+        this.file = file;
+    }
+
+    public File getFile() {
+        return file;
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeProjectDependency.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeProjectDependency.java
new file mode 100644
index 0000000..9727dbb
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeProjectDependency.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+
+public class IdeProjectDependency extends IdeDependency {
+    private final Project project;
+
+    public IdeProjectDependency(Configuration declaredConfiguration, Project project) {
+        super(declaredConfiguration);
+        this.project = project;
+    }
+
+    public Project getProject() {
+        return project;
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeRepoFileDependency.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeRepoFileDependency.java
new file mode 100644
index 0000000..f09d916
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/IdeRepoFileDependency.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.io.File;
+
+public class IdeRepoFileDependency extends IdeDependency {
+    private final File file;
+    private ModuleVersionIdentifier id;
+
+    public IdeRepoFileDependency(Configuration declaredConfiguration, File file) {
+        super(declaredConfiguration);
+        this.file = file;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
+    }
+
+    public void setId(ModuleVersionIdentifier id) {
+        this.id = id;
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/UnresolvedIdeRepoFileDependency.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/UnresolvedIdeRepoFileDependency.java
new file mode 100644
index 0000000..6f70303
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/model/UnresolvedIdeRepoFileDependency.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.model;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+
+public class UnresolvedIdeRepoFileDependency extends IdeExtendedRepoFileDependency {
+    private Exception problem;
+
+    public UnresolvedIdeRepoFileDependency(Configuration declaredConfiguration, File file) {
+        super(declaredConfiguration, file);
+    }
+
+    public Exception getProblem() {
+        return problem;
+    }
+
+    public void setProblem(Exception problem) {
+        this.problem = problem;
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/translator/ExternalModuleDependencyTranslator.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/translator/ExternalModuleDependencyTranslator.java
new file mode 100644
index 0000000..70aad86
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/resolver/translator/ExternalModuleDependencyTranslator.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.resolver.translator;
+
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.artifacts.ResolvedDependency;
+
+import java.util.List;
+import java.util.Set;
+
+public interface ExternalModuleDependencyTranslator {
+    List<ExternalDependency> translate(Set<ResolvedDependency> resolvedDependencies);
+}
diff --git a/subprojects/ide/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction b/subprojects/ide/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
new file mode 100644
index 0000000..5d07db7
--- /dev/null
+++ b/subprojects/ide/src/main/resources/META-INF/services/org.gradle.configuration.project.ProjectConfigureAction
@@ -0,0 +1 @@
+org.gradle.plugins.ide.internal.tooling.ToolingRegistrationAction
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginTest.groovy
index 572fc28..5d51212 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
@@ -23,14 +23,11 @@ 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 org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class EclipsePluginTest extends Specification {
-    private final DefaultProject project = HelperUtil.createRootProject()
+    private final DefaultProject project = TestUtil.createRootProject()
     private final EclipsePlugin eclipsePlugin = new EclipsePlugin(project.services.get(Instantiator))
 
     def applyToBaseProject_shouldOnlyHaveEclipseProjectTask() {
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 5864bf4..1214ce0 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
@@ -21,16 +21,13 @@ 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
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Issue
 import spock.lang.Specification
 
-/**
- * @author: Szczepan Faber, created at: 6/28/11
- */
 class EclipseWtpPluginTest extends Specification {
 
-    private final DefaultProject project = HelperUtil.createRootProject()
+    private final DefaultProject project = TestUtil.createRootProject()
     private final EclipseWtpPlugin wtpPlugin = new EclipseWtpPlugin(project.services.get(Instantiator))
 
     def "has description"() {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspathTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspathTest.groovy
index b71a31e..cdad8c7 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspathTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspathTest.groovy
@@ -20,9 +20,6 @@ import org.gradle.api.internal.ConventionTask
 import org.gradle.api.tasks.AbstractSpockTaskTest
 import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
 
-/**
- * @author Hans Dockter
- */
 public class GenerateEclipseClasspathTest extends AbstractSpockTaskTest {
 
     private GenerateEclipseClasspath eclipseClasspath;
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy
index b4a6a59..7022ecb 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent
 import org.gradle.plugins.ide.eclipse.model.WbProperty
 import org.gradle.plugins.ide.eclipse.model.WbResource
 
-/**
- * @author Hans Dockter
- */
 public class GenerateEclipseWtpComponentTest extends AbstractSpockTaskTest {
     private eclipseComponent = createTask(GenerateEclipseWtpComponent)
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy
index daec953..11d1384 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ide.eclipse
 import org.gradle.api.tasks.AbstractSpockTaskTest
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet
 
-/**
- * @author Hans Dockter
- */
 public class GenerateEclipseWtpFacetTest extends AbstractSpockTaskTest {
     private eclipseFacet = createTask(GenerateEclipseWtpFacet)
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
index d560d8c..197ef1b 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class ClasspathTest extends Specification {
     final fileReferenceFactory = new FileReferenceFactory()
     final customEntries = [
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy
index c09942c..49cce22 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class ContainerTest extends Specification {
     final static String XML_TEXT = '''
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy
index 6de0370..475d865 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, created at: 4/19/11
- */
 class EclipseModelTest extends Specification {
 
     EclipseModel model = new EclipseModel(classpath: new EclipseClasspath(null))
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy
index d8c4106..ce0afb5 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.plugins.ide.eclipse.model
 import org.gradle.api.InvalidUserDataException
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, created at: 4/17/11
- */
 class EclipseProjectTest extends Specification {
 
     def eclipseProject = new EclipseProject()
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy
index 89298dd..7ae7036 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy
@@ -15,14 +15,9 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-import org.gradle.plugins.ide.eclipse.model.Facet.FacetType;
-
+import org.gradle.plugins.ide.eclipse.model.Facet.FacetType
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
-
 class FacetTest extends Specification {
     final static String XML_TEXT = '<installed facet="jst.web" version="2.4"/>'
     final static String FIXED_XML_TEXT = '<fixed facet="jst.web"/>'
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy
index ea5c99c..958995e 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 import org.gradle.util.Matchers
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class LibraryTest extends Specification {
     final static String XML_TEXT_TEMPLATE = '''
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/OutputTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/OutputTest.groovy
index 6d5c7c6..fc7aa76 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/OutputTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/OutputTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class OutputTest extends Specification {
     final static String XML_TEXT = '<classpathentry kind="output" path="somePath"/>'
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy
index bc9c15c..3d82775 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class ProjectDependencyTest extends Specification {
     final static String XML_TEXT = '''
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy
index 39f0a07..74130f7 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class ProjectTest extends Specification {
     def static final CUSTOM_REFERENCED_PROJECTS = ['refProject'] as LinkedHashSet
     def static final CUSTOM_BUILD_COMMANDS = [new BuildCommand('org.eclipse.jdt.core.scalabuilder', [climate: 'cold'])]
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy
index ba9e92c..45bea65 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class SourceFolderTest extends Specification {
     final static String XML_TEXT = '''
                 <classpathentry including="**/Test1*|**/Test2*" excluding="**/Test3*|**/Test4*" kind="src" output="bin2" path="src">
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy
index 3a4e2a9..4a8f9a6 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 import org.gradle.util.Matchers
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class VariableTest extends Specification {
     final static String XML_TEXT_TEMPLATE = '''
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModuleTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModuleTest.groovy
index 82ea561..cb6f1c4 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModuleTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbDependentModuleTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class WbDependentModuleTest extends Specification {
     final static String XML_TEXT = '''
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbPropertyTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbPropertyTest.groovy
index cbdd953..30467b3 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbPropertyTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbPropertyTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class WbPropertyTest extends Specification {
     final static String XML_TEXT = '<property name="java-output-path" value="/build/classes"/>'
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbResourceTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbResourceTest.groovy
index 12dec70..afaa718 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbResourceTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WbResourceTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.eclipse.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class WbResourceTest extends Specification {
     final static String XML_TEXT = '<wb-resource deploy-path="/" source-path="src/main/webapp"/>'
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy
index ea49d02..7232eb8 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class WtpComponentTest extends Specification {
     private static final List CUSTOM_WB_MODULE_ENTRIES = [
             new WbDependentModule('/WEB-INF/lib', "module:/classpath/myapp-1.0.0.jar"),
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
index 97affe7..86c3bd1 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
@@ -22,9 +22,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class WtpFacetTest extends Specification {
     private static final List CUSTOM_FACETS = [new Facet(FacetType.fixed, 'jst.java', null), new Facet(FacetType.fixed, 'jst.web', null), new Facet(FacetType.installed, 'jst.web', '2.4'), new Facet(FacetType.installed, 'jst.java', '1.4')]
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy
index ef31bd0..5abe135 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy
@@ -16,15 +16,12 @@
 package org.gradle.plugins.ide.eclipse.model.internal
 
 import org.gradle.api.Project
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, @date: 11.03.11
- */
 class ProjectDependencyBuilderTest extends Specification {
 
-    def Project project = HelperUtil.createRootProject()
+    def Project project = TestUtil.createRootProject()
     def ProjectDependencyBuilder builder = new ProjectDependencyBuilder()
 
     def "should create dependency using project name"() {
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 8046c28..65e804b 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	
@@ -17,17 +17,14 @@ package org.gradle.plugins.ide.idea
 
 import org.gradle.api.Project
 import org.gradle.api.internal.project.DefaultProject
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
 class GenerateIdeaModuleTest extends Specification {
 
-    DefaultProject project = HelperUtil.createRootProject()
-    Project childProject = HelperUtil.createChildProject(project, "child", new File("."))
-    Project grandChildProject = HelperUtil.createChildProject(childProject, "grandChild", new File("."))
+    DefaultProject project = TestUtil.createRootProject()
+    Project childProject = TestUtil.createChildProject(project, "child", new File("."))
+    Project grandChildProject = TestUtil.createChildProject(childProject, "grandChild", new File("."))
 
     def "moduleName controls outputFile"() {
         given:
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 2935806..8afaf86 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
@@ -19,17 +19,15 @@ import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.plugins.scala.ScalaPlugin
 import org.gradle.api.tasks.Delete
 import org.gradle.plugins.ide.idea.model.IdeaLanguageLevel
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class IdeaPluginTest extends Specification {
-    private final DefaultProject project = HelperUtil.createRootProject()
-    private final Project childProject = HelperUtil.createChildProject(project, "child", new File("."))
+    private final DefaultProject project = TestUtil.createRootProject()
+    private final Project childProject = TestUtil.createChildProject(project, "child", new File("."))
 
     def "adds 'ideaProject' task to root project"() {
         when:
@@ -87,12 +85,12 @@ class IdeaPluginTest extends Specification {
         then:
         project.idea.project.languageLevel.level == new IdeaLanguageLevel(project.sourceCompatibility).level
 
-        def configurations = project.configurations
+        // def configurations = project.configurations
         project.idea.module.scopes == [
-                COMPILE: [plus: [configurations.compile], minus: []],
-                RUNTIME: [plus: [configurations.runtime], minus: [configurations.compile]],
-                TEST: [plus: [configurations.testRuntime], minus: [configurations.runtime]],
-                PROVIDED: [plus: [], minus: []]
+                PROVIDED: [plus: [], minus: []],
+                COMPILE: [plus: [], minus: []],
+                RUNTIME: [plus: [], minus: []],
+                TEST: [plus: [], minus: []],
         ]
     }
 
@@ -136,6 +134,21 @@ class IdeaPluginTest extends Specification {
         test.any { it.name.contains('test-resources') }
      }
 
+    def "makes scala modules depend on root's project"() {
+        applyPluginToProjects()
+
+        when:
+        childProject.plugins.apply(ScalaPlugin)
+
+        then:
+        def parentIdeaProject = project.tasks.ideaProject
+        def parentIdeaModule = project.tasks.ideaModule
+        def childIdeaModule = childProject.tasks.ideaModule
+
+        childIdeaModule.taskDependencies.getDependencies(childIdeaModule).contains(parentIdeaProject)
+        !parentIdeaModule.taskDependencies.getDependencies(parentIdeaModule).contains(parentIdeaProject)
+    }
+
     private void assertThatIdeaModuleIsProperlyConfigured(Project project) {
         GenerateIdeaModule ideaModuleTask = project.ideaModule
         assert ideaModuleTask instanceof GenerateIdeaModule
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.groovy
index cd3eb87..6c02a47 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ide.idea.model
 import org.gradle.api.JavaVersion
 import spock.lang.Specification
 
-/**
- * @author: Szczepan Faber, created at: 7/14/11
- */
 class IdeaLanguageLevelTest extends Specification {
 
     def "formats language level in IDEA fancy format"() {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleDependencyTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleDependencyTest.groovy
index da03e65..27ba3d0 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleDependencyTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleDependencyTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.idea.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class ModuleDependencyTest extends Specification {
     def equality() {
         expect:
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleLibraryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleLibraryTest.groovy
index 4019a41..3503b5e 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleLibraryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleLibraryTest.groovy
@@ -17,9 +17,6 @@ package org.gradle.plugins.ide.idea.model
 
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class ModuleLibraryTest extends Specification {
     def equality() {
         expect:
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
index 24454af..0470bcd 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.api.JavaVersion
 import org.gradle.api.internal.xml.XmlTransformer
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class ModuleTest extends Specification {
     final PathFactory pathFactory = new PathFactory()
     final XmlTransformer xmlTransformer = new XmlTransformer()
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy
index 61909d3..2563ab5 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy
@@ -16,11 +16,14 @@
 
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
 
 import spock.lang.Specification
 
 class ProjectLibraryTest extends Specification {
+    @Rule TestNameTestDirectoryProvider testDirProvider
+
     def "has friendly defaults"() {
         def library = new ProjectLibrary()
 
@@ -46,7 +49,7 @@ class ProjectLibraryTest extends Specification {
     }
 
     def "generates correct XML"() {
-        def userHome = new File(SystemProperties.javaIoTmpDir)
+        def userHome = testDirProvider.testDirectory
 
         def lib = new ProjectLibrary(name: "lib",
                 classes: [new File(userHome, "class/one.jar"), new File(userHome, "class/two.jar")] as LinkedHashSet,
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
index 12b7c46..c8cd474 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.api.JavaVersion
 import org.gradle.api.internal.xml.XmlTransformer
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class ProjectTest extends Specification {
     final PathFactory pathFactory = new PathFactory()
     final customModules = [path('file://$PROJECT_DIR$/gradle-idea-plugin.iml')]
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProviderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProviderTest.groovy
new file mode 100644
index 0000000..cc61e67
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProviderTest.groovy
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.idea.model.internal
+
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.plugins.ide.idea.IdeaPlugin
+import org.gradle.plugins.ide.idea.model.Dependency
+import org.gradle.plugins.ide.idea.model.SingleEntryModuleLibrary
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+public class IdeaDependenciesProviderTest extends Specification {
+    private final DefaultProject project = TestUtil.createRootProject()
+    private final DefaultProject childProject = TestUtil.createChildProject(project, "child", new File("."))
+
+    def "no dependencies test"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        module.offline = true
+
+        when:
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.isEmpty()
+    }
+
+    def "common dependencies"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        module.offline = true
+
+        when:
+        project.dependencies.add('compile', project.files('lib/guava.jar'))
+        project.dependencies.add('testCompile', project.files('lib/mockito.jar'))
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.size() == 2
+        result.findAll { SingleEntryModuleLibrary it ->
+            it.scope == 'COMPILE' &&
+            it.libraryFile.path.endsWith('guava.jar')
+        }.size() == 1
+        result.findAll { SingleEntryModuleLibrary it ->
+            it.scope == 'TEST' &&
+            it.libraryFile.path.endsWith('mockito.jar')
+        }.size() == 1
+    }
+
+    def "dependency is excluded if added to minus configuration"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        project.configurations.create('excluded')
+        module.offline = true
+
+        when:
+        project.dependencies.add('compile', project.files('lib/guava.jar'))
+        project.dependencies.add('excluded', project.files('lib/guava.jar'))
+        module.scopes.COMPILE.minus += project.configurations.getByName('excluded')
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.size() == 0
+    }
+
+    def "dependency is excluded if added to any minus configuration"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        project.configurations.create('excluded1')
+        project.configurations.create('excluded2')
+        module.offline = true
+
+        when:
+        project.dependencies.add('compile', project.files('lib/guava.jar'))
+        project.dependencies.add('compile', project.files('lib/slf4j-api.jar'))
+        project.dependencies.add('excluded1', project.files('lib/guava.jar'))
+        project.dependencies.add('excluded2', project.files('lib/slf4j-api.jar'))
+        module.scopes.COMPILE.minus += project.configurations.getByName('excluded1')
+        module.scopes.COMPILE.minus += project.configurations.getByName('excluded2')
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.size() == 0
+    }
+
+    def "dependency is added from plus detached configuration"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module
+        def extraDependency = project.dependencies.create(project.files('lib/guava.jar'))
+        def detachedCfg = project.configurations.detachedConfiguration(extraDependency)
+        module.offline = true
+
+        when:
+        module.scopes.RUNTIME.plus += detachedCfg
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.size() == 1
+        result.findAll { Dependency it -> it.scope == 'RUNTIME' }.size() == 1
+    }
+
+    def "compile dependency on child project"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+        childProject.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        module.offline = true
+
+        when:
+        project.dependencies.add('compile', childProject)
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.size() == 1
+        result.findAll { it.scope == 'COMPILE' }.size() == 1
+    }
+
+    def "test and runtime scope for the same dependency"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesProvider = new IdeaDependenciesProvider()
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        module.offline = true
+
+        when:
+        project.dependencies.add('compile', project.files('lib/foo-api.jar'))
+        project.dependencies.add('testCompile', project.files('lib/foo-impl.jar'))
+        project.dependencies.add('runtime', project.files('lib/foo-impl.jar'))
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        result.size() == 3
+        result.findAll { SingleEntryModuleLibrary it ->
+            it.scope == 'COMPILE' &&
+                    it.libraryFile.path.endsWith('foo-api.jar')
+        }.size() == 1
+        result.findAll { SingleEntryModuleLibrary it ->
+            it.scope == 'TEST' &&
+                    it.libraryFile.path.endsWith('foo-impl.jar')
+        }.size() == 1
+        result.findAll { SingleEntryModuleLibrary it ->
+            it.scope == 'RUNTIME' &&
+                    it.libraryFile.path.endsWith('foo-impl.jar')
+        }.size() == 1
+    }
+
+    def "ignore unknown configurations"() {
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        def dependenciesExtractor = Spy(IdeDependenciesExtractor)
+        def dependenciesProvider = new IdeaDependenciesProvider(dependenciesExtractor)
+        def module = project.ideaModule.module // Mock(IdeaModule)
+        module.offline = true
+        def extraConfiguration = project.configurations.create('extraConfiguration')
+
+        when:
+        project.dependencies.add('testCompile', project.files('lib/mockito.jar'))
+        def result = dependenciesProvider.provide(module)
+
+        then:
+        // only for compile, runtime, testCompile, testRuntime
+        4 * dependenciesExtractor.extractProjectDependencies(_, { !it.contains(extraConfiguration) }, _)
+        4 * dependenciesExtractor.extractLocalFileDependencies({ !it.contains(extraConfiguration) }, _)
+        // offline: 4 * dependenciesExtractor.extractRepoFileDependencies(_, { !it.contains(extraConfiguration) }, _, _, _)
+        0 * dependenciesExtractor._
+        result.size() == 1
+        result.findAll { it.scope == 'TEST' }.size() == 1
+    }
+
+    private applyPluginToProjects() {
+        project.apply plugin: IdeaPlugin
+        childProject.apply plugin: IdeaPlugin
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy
index 5f6035d..6cba7a3 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy
@@ -16,15 +16,12 @@
 
 package org.gradle.plugins.ide.idea.model.internal
 
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, @date: 19.03.11
- */
 class ModuleDependencyBuilderTest extends Specification {
 
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
     def builder = new ModuleDependencyBuilder()
 
     def "builds dependency for nonIdea project"() {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
index eb21192..cd7d850 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.plugins.ide.internal
 import org.gradle.plugins.ide.api.GeneratorTask
 import org.gradle.plugins.ide.internal.generator.generator.Generator
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -27,7 +27,7 @@ class GeneratorTaskTest extends Specification {
     final Generator<TestConfigurationObject> generator = Mock()
     final File inputFile = tmpDir.file('input')
     final File outputFile = tmpDir.file('output')
-    final GeneratorTask<TestConfigurationObject> task = HelperUtil.createTask(GeneratorTask)
+    final GeneratorTask<TestConfigurationObject> task = TestUtil.createTask(GeneratorTask)
 
     def setup() {
         task.inputFile = inputFile
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdePluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdePluginTest.groovy
index 6a1e00f..9ddf703 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdePluginTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdePluginTest.groovy
@@ -19,11 +19,11 @@ import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.tasks.Delete
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class IdePluginTest extends Specification {
-    final Project project = HelperUtil.createRootProject()
+    final Project project = TestUtil.createRootProject()
 
     def addsLifecycleTasks() {
         when:
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTargetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTargetTest.groovy
index 4533691..b2d0173 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTargetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/DeduplicationTargetTest.groovy
@@ -16,20 +16,17 @@
 
 package org.gradle.plugins.ide.internal.configurer
 
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, @date: 14.03.11
- */
 class DeduplicationTargetTest extends Specification {
 
     def "knows candidate names"() {
         when:
-        def project = HelperUtil.createRootProject()
+        def project = TestUtil.createRootProject()
         assert project.name == 'test'
-        def childProject = HelperUtil.createChildProject(project, "child", new File("."))
-        def grandChildProject = HelperUtil.createChildProject(childProject, "grandChild", new File("."))
+        def childProject = TestUtil.createChildProject(project, "child", new File("."))
+        def grandChildProject = TestUtil.createChildProject(childProject, "grandChild", new File("."))
 
         then:
         new DeduplicationTarget(project: project, moduleName: 'test' ).candidateNames == ['test']
@@ -39,9 +36,9 @@ class DeduplicationTargetTest extends Specification {
 
     def "uses passed module name instead of project name"() {
         when:
-        def project = HelperUtil.createRootProject()
+        def project = TestUtil.createRootProject()
         assert project.name == 'test'
-        def childProject = HelperUtil.createChildProject(project, "child", new File("."))
+        def childProject = TestUtil.createChildProject(project, "child", new File("."))
 
         then:
         new DeduplicationTarget(project: project, moduleName: 'ROOT' ).candidateNames == ['ROOT']
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduperTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduperTest.groovy
index 0be9b55..2d3f40d 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduperTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ModuleNameDeduperTest.groovy
@@ -18,9 +18,6 @@ package org.gradle.plugins.ide.internal.configurer
 
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, @date: 14.03.11
- */
 class ModuleNameDeduperTest extends Specification {
 
     public static class TargetStub extends DeduplicationTarget {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduperTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduperTest.groovy
index ee425d9..552bbb9 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduperTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/configurer/ProjectDeduperTest.groovy
@@ -18,17 +18,14 @@
 
 package org.gradle.plugins.ide.internal.configurer
 
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, @date: 14.03.11
- */
 class ProjectDeduperTest extends Specification {
 
-    def project = HelperUtil.createRootProject()
-    def childProject = HelperUtil.createChildProject(project, "child", new File("."))
-    def grandChildProject = HelperUtil.createChildProject(childProject, "grandChild", new File("."))
+    def project = TestUtil.createRootProject()
+    def childProject = TestUtil.createChildProject(project, "child", new File("."))
+    def grandChildProject = TestUtil.createChildProject(childProject, "grandChild", new File("."))
 
     def deduper = new ProjectDeduper()
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/BuildInvocationsBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/BuildInvocationsBuilderTest.groovy
new file mode 100644
index 0000000..b816718
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/BuildInvocationsBuilderTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling
+
+import org.gradle.api.DefaultTask
+import org.gradle.tooling.internal.impl.LaunchableGradleTaskSelector
+import org.gradle.tooling.model.gradle.BuildInvocations
+import org.gradle.util.TestUtil
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class BuildInvocationsBuilderTest extends Specification {
+    def builder = new BuildInvocationsBuilder(new GradleProjectBuilder())
+    @Shared def project = TestUtil.builder().withName("root").build()
+    @Shared def child1 = TestUtil.builder().withName("child1").withParent(project).build()
+    @Shared def child1a = TestUtil.builder().withName("child1a").withParent(child1).build()
+    @Shared def child1b = TestUtil.builder().withName("child1b").withParent(child1).build()
+
+    def setupSpec() {
+        child1a.tasks.create('t1', DefaultTask)
+        child1b.tasks.create('t1', DefaultTask)
+        child1b.tasks.create('t2', DefaultTask)
+        child1.tasks.create('t2', DefaultTask)
+        project.tasks.create('t3', DefaultTask)
+    }
+
+    def "can build model"() {
+        expect:
+        builder.canBuild(BuildInvocations.name)
+    }
+
+    @Unroll("builds model for #startProject")
+    def "builds model"() {
+        expect:
+        def model = builder.buildAll("org.gradle.tooling.model.gradle.BuildInvocations", startProject)
+        model.taskSelectors*.name as Set == selectorNames as Set
+        model.taskSelectors*.projectPath as Set == [startProject.path] as Set
+        // model.taskSelectors.find { it.name == 't1' }?.tasks == t1Tasks as Set
+
+        where:
+        startProject | selectorNames
+        project      | ['t1', 't2', 't3']
+        child1       | ['t1', 't2']
+        child1a      | ['t1']
+    }
+
+    def "builds recursive model"() {
+        when:
+        def model = builder.buildAll("org.gradle.tooling.model.gradle.BuildInvocations", project, true)
+
+        then:
+        model.taskSelectors.size() == 3
+        model.taskSelectors.each { it ->
+            assert it.projectPath == ':'
+            assert it.name != null
+            assert it.displayName != null
+            assert it.description != null
+        }
+        def t1Selector = model.taskSelectors.find { LaunchableGradleTaskSelector it ->
+            it.name == 't1' && it.description.startsWith("t1")
+        }
+        model.taskSelectors*.name as Set == ['t1', 't2', 't3'] as Set
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy
new file mode 100644
index 0000000..35fdc9a
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling
+
+import org.gradle.util.TestUtil
+import spock.lang.Shared
+import spock.lang.Specification
+
+class GradleBuildBuilderTest extends Specification {
+    def builder = new GradleBuildBuilder()
+    @Shared def project = TestUtil.builder().withName("root").build()
+    @Shared def child1 = TestUtil.builder().withName("child1").withParent(project).build()
+    @Shared def child2 = TestUtil.builder().withName("child2").withParent(project).build()
+
+    def "builds model"() {
+        expect:
+        def model = builder.buildAll("org.gradle.tooling.model.gradle.GradleBuild", startProject)
+        model.rootProject.path == ":"
+        model.rootProject.name == "root"
+        model.rootProject.parent == null
+        model.rootProject.projectDirectory == project.projectDir
+        model.rootProject.children.size() == 2
+        model.rootProject.children.every { it.parent == model.rootProject }
+        model.projects*.name == ["root", "child1", "child2"]
+        model.projects*.path == [":", ":child1", ":child2"]
+
+        where:
+        startProject | _
+        project      | _
+        child2       | _
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleProjectBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleProjectBuilderTest.groovy
new file mode 100644
index 0000000..f4f72a0
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleProjectBuilderTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+class GradleProjectBuilderTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir
+    def builder = new GradleProjectBuilder()
+
+    def "builds basics for project"() {
+        def buildFile = tmpDir.file("build.gradle") << "//empty"
+        def project = TestUtil.builder().withName("test").withProjectDir(tmpDir.testDirectory).build()
+        project.description = 'a test project'
+
+        when:
+        def model = builder.buildAll(project)
+
+        then:
+        model.path == ':'
+        model.name == 'test'
+        model.description == 'a test project'
+        model.buildScript.sourceFile == buildFile
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/ProjectPublicationsBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/ProjectPublicationsBuilderTest.groovy
new file mode 100644
index 0000000..4a78eac
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/ProjectPublicationsBuilderTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal.tooling
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublication
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.tooling.model.gradle.ProjectPublications
+import org.gradle.util.TestUtil
+import org.junit.Rule;
+import spock.lang.Specification;
+
+public class ProjectPublicationsBuilderTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir
+    def publicationRegistry = Stub(ProjectPublicationRegistry) {
+        getPublications(":") >> [Stub(ProjectPublication) {
+            getId() >> Stub(ModuleVersionIdentifier) {
+                getGroup() >> "group"
+                getName() >> "name"
+                getVersion() >> "version"
+            }
+        }]
+    }
+    def builder = new PublicationsBuilder(publicationRegistry)
+
+    def "builds basics for project"() {
+        def project = TestUtil.builder().withName("test").withProjectDir(tmpDir.testDirectory).build()
+        project.description = 'a test project'
+
+        when:
+        def model = builder.buildAll(ProjectPublications.name, project)
+
+        then:
+        def publication = model.publications.iterator().next()
+        publication.id.group == "group"
+        publication.id.name == "name"
+        publication.id.version == "version"
+    }
+
+    def "can build model"() {
+        expect:
+        builder.canBuild(ProjectPublications.name)
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/TasksFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/TasksFactoryTest.groovy
new file mode 100644
index 0000000..e55ec83
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/TasksFactoryTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling
+
+import org.gradle.api.Project
+import org.gradle.api.internal.AbstractTask
+import org.gradle.plugins.ide.internal.tooling.eclipse.DefaultEclipseProject
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class TasksFactoryTest extends Specification {
+    final Project project = Mock()
+    final org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3 eclipseProject = new DefaultEclipseProject(null, null, null, null, [])
+    final task = TestUtil.createTask(AbstractTask)
+
+    def "does not return tasks"() {
+        TasksFactory factory = new TasksFactory(false)
+
+        when:
+        factory.allTasks = [:]
+        factory.allTasks.put(project, [task] as Set)
+        def tasks = factory.getTasks(project)
+
+        then:
+        tasks.empty
+    }
+
+    def "returns tasks"() {
+        TasksFactory factory = new TasksFactory(true)
+
+        when:
+        factory.allTasks = [:]
+        factory.allTasks.put(project, [task] as Set)
+        def tasks = factory.getTasks(project)
+
+        then:
+        tasks.size() == 1
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProjectTest.groovy
new file mode 100644
index 0000000..b0c6eda
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/eclipse/DefaultEclipseProjectTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.internal.tooling.eclipse
+
+import spock.lang.Specification
+
+class DefaultEclipseProjectTest extends Specification {
+    def usesPathForToStringValue() {
+        def project = new DefaultEclipseProject("name", ":path", null, null, [])
+
+        expect:
+        project.toString() == "project ':path'"
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformerTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformerTest.groovy
deleted file mode 100644
index b21daf6..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformerTest.groovy
+++ /dev/null
@@ -1,86 +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 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/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.groovy
deleted file mode 100644
index eae6dbe..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.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.tooling.internal.provider
-
-import org.gradle.api.Project
-import org.gradle.api.internal.AbstractTask
-import org.gradle.tooling.internal.eclipse.DefaultEclipseProject
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class TasksFactoryTest extends Specification {
-    final Project project = Mock()
-    final org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3 eclipseProject = new DefaultEclipseProject(null, null, null, null, [])
-    final task = HelperUtil.createTask(AbstractTask)
-
-    def "does not return tasks"() {
-        TasksFactory factory = new TasksFactory(false)
-
-        when:
-        factory.allTasks = [:]
-        factory.allTasks.put(project, [task] as Set)
-        def tasks = factory.getTasks(project)
-
-        then:
-        tasks.empty
-    }
-
-    def "returns tasks"() {
-        TasksFactory factory = new TasksFactory(true)
-
-        when:
-        factory.allTasks = [:]
-        factory.allTasks.put(project, [task] as Set)
-        def tasks = factory.getTasks(project)
-
-        then:
-        tasks.size() == 1
-    }
-}
diff --git a/subprojects/integ-test/integ-test.gradle b/subprojects/integ-test/integ-test.gradle
index 8be66bf..9056c5f 100644
--- a/subprojects/integ-test/integ-test.gradle
+++ b/subprojects/integ-test/integ-test.gradle
@@ -1,11 +1,10 @@
 dependencies {
-    groovy libraries.groovy
+    integTestCompile libraries.groovy
 
     integTestCompile project(':toolingApi')
     integTestCompile project(':launcher')
     integTestCompile project(':coreImpl')
     integTestCompile libraries.ant
-    integTestCompile libraries.xmlunit
     integTestCompile libraries.jsoup
 
     integTestRuntime rootProject.configurations.testRuntime.allDependencies
@@ -19,7 +18,6 @@ integTestTasks.all {
     doFirst {
         systemProperties['integTest.userGuideInfoDir'] = project(':docs').docbookSrc
         systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
-        forkEvery = 15
     }
 
     // You can filter the userguide samples to be run by specifying this system property.
@@ -27,10 +25,6 @@ integTestTasks.all {
     systemProperty "org.gradle.userguide.samples.filter", System.getProperty("org.gradle.userguide.samples.filter")
 }
 
-daemonIntegTest {
-    exclude "**/CrossVersionCompatibilityIntegrationTest.class" //ignored just in case to avoid old daemon implementation
-}
-
 parallelIntegTest {
     systemProperty "org.gradle.userguide.samples.exclude", "multiProjectBuildSrc,multiprojectMessagesHack"
 }
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
deleted file mode 100644
index f7f8d78..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
+++ /dev/null
@@ -1,252 +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.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("Problem configuring task :tasks from command line. Unknown command-line option '-l'.")
-    }
-
-    @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/debug/GradleBuildRunner.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleBuildRunner.groovy
index fc64f1a..aac6770 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleBuildRunner.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleBuildRunner.groovy
@@ -20,8 +20,6 @@ import org.gradle.launcher.Main
 
 /**
  * Use this class to run/debug any gradle build from the IDE. Especially useful for learning & debugging Gradle internals.
- *
- * @author: Szczepan Faber, created at: 4/30/11
  */
 class GradleBuildRunner {
     public static void main(String[] args) {
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 4973cab..6b0d396 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
@@ -21,8 +21,6 @@ import org.gradle.launcher.Main
 /**
  * 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
  */
 class GradleRunConfiguration {
     public static void main(String[] args) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
index d146909..9a583a8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
@@ -156,4 +156,67 @@ ant.importBuild('build.xml')
         failure.assertHasDescription('Execution failed for task \':target1\'.')
         failure.assertHasCause('broken')
     }
+
+    @Test
+    public void targetDependenciesAreOrderedBasedOnDeclarationSequence() {
+        testFile('build.xml') << """
+<project>
+    <target name='a' depends='d,c,b'/>
+    <target name='b'/>
+    <target name='c'/>
+    <target name='d'/>
+    <target name='e' depends='g,f'/>
+    <target name='f'/>
+    <target name='g'/>
+    <target name='h' depends='i'/>
+    <target name='i'/>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild('build.xml')
+"""
+        inTestDirectory().withTasks('a', 'e', 'h').run().assertTasksExecuted(':d', ':c', ':b', ':a', ':g', ':f', ':e', ':i', ':h')
+    }
+
+    @Test
+    public void targetDependenciesOrderDoesNotCreateCycle() {
+        testFile('build.xml') << """
+<project>
+    <target name='a' depends='c,b'/>
+    <target name='b'/>
+    <target name='c' depends='b'/>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild('build.xml')
+"""
+        inTestDirectory().withTasks('a').run().assertTasksExecuted(':b', ':c', ':a')
+    }
+
+    @Test
+    public void unknownDependencyProducesUsefulMessage() {
+        testFile('build.xml') << """
+<project>
+    <target name='a' depends='b'/>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild('build.xml')
+"""
+        inTestDirectory().withTasks('a').runWithFailure().assertHasCause("Imported Ant target 'a' depends on target or task 'b' which does not exist")
+    }
+
+    @Test
+    public void canHandleDependencyOrderingBetweenNonExistentTasks() {
+        testFile('build.xml') << """
+<project>
+    <target name='a' depends='b,c'/>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild('build.xml')
+"""
+        // Testing that we don't get some obscure error message trying to set c.shouldRunAfter b
+        inTestDirectory().withTasks('a').runWithFailure().assertHasCause("Imported Ant target 'a' depends on target or task 'b' which does not exist")
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy
index ac87fb6..9864d0e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy
@@ -97,7 +97,7 @@ class ApplicationIntegrationSpec extends AbstractIntegrationSpec {
         and:
         def distBase = file("build/install/app")
         distBase.file("docs").directory
-        distBase.file("docs/readme.txt").exists() == false
+        !distBase.file("docs/readme.txt").exists()
         distBase.file("docs/READ-ME.txt").text == "Read me!!!"
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
index b0cbb6b..d852b53 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
@@ -67,6 +67,152 @@ class Main {
         result.assertNormalExitValue()
     }
 
+    def canUseDefaultJvmArgsToPassMultipleOptionsToJvmWhenRunningScript() {
+        file("build.gradle") << '''
+apply plugin: 'application'
+mainClassName = 'org.gradle.test.Main'
+applicationName = 'application'
+applicationDefaultJvmArgs = ['-DtestValue=value', '-DtestValue2=some value', '-DtestValue3=some value']
+'''
+        file('src/main/java/org/gradle/test/Main.java') << '''
+package org.gradle.test;
+
+class Main {
+    public static void main(String[] args) {
+        if (!"value".equals(System.getProperty("testValue"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+        if (!"some value".equals(System.getProperty("testValue2"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+        if (!"some value".equals(System.getProperty("testValue3"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+    }
+}
+'''
+
+        when:
+        run 'install'
+
+        def builder = new ScriptExecuter()
+        builder.workingDir file('build/install/application/bin')
+        builder.executable "application"
+
+        def result = builder.run()
+
+        then:
+        result.assertNormalExitValue()
+    }
+
+    def canUseBothDefaultJvmArgsAndEnvironmentVariableToPassOptionsToJvmWhenRunningScript() {
+        file("build.gradle") << '''
+apply plugin: 'application'
+mainClassName = 'org.gradle.test.Main'
+applicationName = 'application'
+applicationDefaultJvmArgs = ['-Dvar1=value1', '-Dvar2=some value2']
+'''
+        file('src/main/java/org/gradle/test/Main.java') << '''
+package org.gradle.test;
+
+class Main {
+    public static void main(String[] args) {
+        if (!"value1".equals(System.getProperty("var1"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+        if (!"some value2".equals(System.getProperty("var2"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+        if (!"value3".equals(System.getProperty("var3"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+    }
+}
+'''
+
+        when:
+        run 'install'
+
+        def builder = new ScriptExecuter()
+        builder.workingDir file('build/install/application/bin')
+        builder.executable "application"
+        builder.environment('APPLICATION_OPTS', '-Dvar3=value3')
+
+        def result = builder.run()
+
+        then:
+        result.assertNormalExitValue()
+    }
+
+    def canUseDefaultJvmArgsToPassMultipleOptionsWithShellMetacharactersToJvmWhenRunningScript() {
+        //even in single-quoted multi-line strings, backslashes must still be quoted
+        file("build.gradle") << '''
+apply plugin: 'application'
+mainClassName = 'org.gradle.test.Main'
+applicationName = 'application'
+applicationDefaultJvmArgs = ['-DtestValue=value',
+                             /-DtestValue2=s\\o"me val'ue/ + '$PATH',
+                             /-DtestValue3=so\\"me value%PATH%/,
+                            ]
+'''
+        file('src/main/java/org/gradle/test/Main.java') << '''
+package org.gradle.test;
+
+class Main {
+    public static void main(String[] args) {
+        if (!"value".equals(System.getProperty("testValue"))) {
+            throw new RuntimeException("Expected system property not specified (testValue)");
+        }
+        if (!"s\\\\o\\"me val'ue$PATH".equals(System.getProperty("testValue2"))) {
+            throw new RuntimeException("Expected system property not specified (testValue2)");
+        }
+        if (!"so\\\\\\"me value%PATH%".equals(System.getProperty("testValue3"))) {
+            throw new RuntimeException("Expected system property not specified (testValue3)");
+        }
+    }
+}
+'''
+
+        when:
+        run 'install'
+
+        def builder = new ScriptExecuter()
+        builder.workingDir file('build/install/application/bin')
+        builder.executable "application"
+
+        def result = builder.run()
+
+        then:
+        result.assertNormalExitValue()
+    }
+
+    def canUseDefaultJvmArgsInRunTask() {
+            file("build.gradle") << '''
+    apply plugin: 'application'
+    mainClassName = 'org.gradle.test.Main'
+    applicationName = 'application'
+    applicationDefaultJvmArgs = ['-Dvar1=value1', '-Dvar2=value2']
+    '''
+            file('src/main/java/org/gradle/test/Main.java') << '''
+    package org.gradle.test;
+
+    class Main {
+        public static void main(String[] args) {
+            if (!"value1".equals(System.getProperty("var1"))) {
+                throw new RuntimeException("Expected system property not specified (var1)");
+            }
+            if (!"value2".equals(System.getProperty("var2"))) {
+                throw new RuntimeException("Expected system property not specified (var2)");
+            }
+        }
+    }
+    '''
+
+            expect:
+            run 'run'
+        }
+
+
     def "can customize application name"() {
         file('settings.gradle') << 'rootProject.name = "application"'
         file('build.gradle') << '''
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
index 8a8eb23..7eaaaaa 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
@@ -18,7 +18,9 @@ package org.gradle.integtests
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.executer.ExecutionFailure
 import org.gradle.test.fixtures.file.TestFile
+import org.hamcrest.Matchers
 import org.junit.Test
+import spock.lang.Issue
 
 class BuildAggregationIntegrationTest extends AbstractIntegrationTest {
 
@@ -77,7 +79,7 @@ class BuildAggregationIntegrationTest extends AbstractIntegrationTest {
         ExecutionFailure failure = executer.withTasks('build').runWithFailure()
         failure.assertHasFileName("Build file '${other}'")
         failure.assertHasLineNumber(2)
-        failure.assertHasDescription('A problem occurred evaluating root project')
+        failure.assertThatDescription(Matchers.startsWith('A problem occurred evaluating root project'))
         failure.assertHasCause('broken')
     }
 
@@ -85,6 +87,26 @@ class BuildAggregationIntegrationTest extends AbstractIntegrationTest {
     public void reportsBuildSrcFailure() {
         file('buildSrc/src/main/java/Broken.java') << 'broken!'
         ExecutionFailure failure = executer.runWithFailure()
-        failure.assertHasDescription('Execution failed for task \':compileJava\'')
+        failure.assertHasDescription('Execution failed for task \':compileJava\'.')
+    }
+
+    @Test
+    @Issue("http://issues.gradle.org//browse/GRADLE-3052")
+    void buildTaskCanHaveInputsAndOutputs() {
+        def message = "This is from the hello task"
+        file("build.gradle") << """
+            task hello << { println "$message" }
+
+            task build(type: GradleBuild) {
+              tasks = ["hello"]
+              startParameter.searchUpwards = false
+              outputs.file "build.gradle" // having an output (or input) triggers the bug
+            }
+        """
+
+        def run = executer.withTasks("build").run()
+        def output = run.output
+
+        assert output.contains(message)
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
index 950ce2f..2353e68 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
@@ -183,14 +183,13 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
                 "include 'child'"
         );
         testFile("build.gradle").writelns(
-                "assert gradle.scriptClassLoader == buildscript.classLoader.parent",
                 "buildscript {",
                 "    repositories { flatDir { dirs 'repo' }}",
                 "    dependencies { classpath name: 'test', version: '1.3' }",
                 "}"
         );
         testFile("child/build.gradle").writelns(
-                "assert parent.buildscript.classLoader == buildscript.classLoader.parent",
+                "assert parent.buildscript.classLoader == buildscript.classLoader",
                 "task hello << ",
                 "{",
                 "    new org.gradle.test.BuildClass()",
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
deleted file mode 100644
index 7a68f75..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.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.integtests;
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.executer.ExecutionFailure;
-import org.gradle.test.fixtures.file.TestFile;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.gradle.util.Matchers.containsLine;
-
-public class BuildScriptErrorIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void reportsProjectEvaluationFailsWithGroovyException() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns("", "createTakk('do-stuff')");
-        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
-
-        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription("A problem occurred evaluating root project 'reportsProjectEvaluationFailsWithGroovyException");
-        failure.assertHasCause("Could not find method createTakk() for arguments [do-stuff] on root project 'reportsProjectEvaluationFailsWithGroovyException");
-    }
-
-    @Test
-    public void reportsScriptCompilationException() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-            "// a comment",
-            "import org.gradle.unknown.Unknown",
-            "new Unknown()");
-        ExecutionFailure failure = inTestDirectory().runWithFailure();
-        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription(String.format("Could not compile build file '%s'.", buildFile));
-        failure.assertThatCause(containsLine(String.format("build file '%s': 2: unable to resolve class org.gradle.unknown.Unknown", buildFile)));
-    }
-
-    @Test
-    public void reportsNestedProjectEvaluationFailsWithRuntimeException() {
-        testFile("settings.gradle").write("include 'child'");
-
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "evaluationDependsOn 'child'",
-                "task t");
-
-        TestFile childBuildFile = testFile("child/build.gradle");
-        childBuildFile.writelns(
-                "def broken = { ->",
-                "    throw new RuntimeException('failure') }",
-                "broken()");
-        ExecutionFailure failure = inTestDirectory().withTasks("t").runWithFailure();
-
-        failure.assertHasFileName(String.format("Build file '%s'", childBuildFile));
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription("A problem occurred evaluating project ':child'");
-        failure.assertHasCause("failure");
-    }
-
-    @Test
-    public void reportsTaskGraphReadyEventFailsWithRuntimeException() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "gradle.taskGraph.whenReady {",
-                "throw new RuntimeException('broken closure')",
-                "}",
-                "task a");
-
-        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("a").runWithFailure();
-
-        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription("broken closure");
-        failure.assertHasNoCause();
-    }
-
-    @Test @Ignore
-    public void reportsTaskDependencyClosureFailsWithRuntimeException() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "task a",
-                "a.dependsOn {",
-                "throw new RuntimeException('broken')",
-                "}");
-
-        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("a").runWithFailure();
-
-        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
-        failure.assertHasLineNumber(3);
-        failure.assertHasDescription("??");
-        failure.assertHasCause("broken");
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
index cb5cd9a..4dbf474 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
@@ -41,7 +41,6 @@ assert "${buildScript.absolutePath.replace("\\", "\\\\")}" == buildscript.source
 assert "${buildScript.toURI()}" == buildscript.sourceURI as String
 assert buildscript.classLoader == getClass().classLoader.parent
 assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-assert gradle.scriptClassLoader == buildscript.classLoader.parent
 Gradle.class.classLoader.loadClass('${implClassName}')
 try {
     buildscript.classLoader.loadClass('${implClassName}')
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 7dd6d35..84b1522 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
@@ -16,12 +16,12 @@
 
 package org.gradle.integtests
 
-import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.groovy.scripts.UriScriptSource
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.maven.MavenRepository
+import org.gradle.test.fixtures.maven.MavenHttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
 import org.junit.Before
@@ -30,9 +30,6 @@ import org.junit.Test
 
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
     static final String TEST_FILE = "build/test.txt"
 
@@ -45,7 +42,7 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
     TestFile classFile
     TestFile artifactsCache
 
-    MavenRepository repo
+    MavenHttpRepository repo
 
     @Before
     public void setUp() {
@@ -62,11 +59,10 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
         classFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/classes/${source.className}.class")
         artifactsCache = projectDir.file(".gradle/$version/taskArtifacts/taskArtifacts.bin")
 
-        def repoDir = file("repo")
-        repo = maven(repoDir)
-        server.allowGetOrHead("/repo", repo.rootDir)
-        repo.module("commons-io", "commons-io", "1.4").publish()
-        repo.module("commons-lang", "commons-lang", "2.6").publish()
+        repo = new MavenHttpRepository(server, mavenRepo)
+
+        repo.module("commons-io", "commons-io", "1.4").publish().allowAll()
+        repo.module("commons-lang", "commons-lang", "2.6").publish().allowAll()
 
         server.start()
     }
@@ -146,8 +142,7 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
     }
 
     private TestFile findDependencyCacheDir() {
-        def cacheVersion = DefaultCacheLockingManager.CACHE_LAYOUT_VERSION
-        def resolverArtifactCache = new TestFile(userHomeDir.file("caches/artifacts-${cacheVersion}/filestore"))
+        def resolverArtifactCache = new TestFile(userHomeDir.file("caches/${CacheLayout.ROOT.getKey()}/${CacheLayout.FILE_STORE.getKey()}"))
         return resolverArtifactCache.file("commons-io/commons-io/")
     }
 
@@ -167,7 +162,7 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
         String content = """
 repositories {
     maven{
-        url "http://localhost:${server.port}/repo"
+        url "${repo.uri}"
     }
 }
 configurations { compile }
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 2195d06..00c6f2a 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
@@ -19,7 +19,6 @@ import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionFailure
 import org.gradle.internal.jvm.Jvm
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.PreconditionVerifier
@@ -106,7 +105,7 @@ public class CommandLineIntegrationTest extends AbstractIntegrationTest {
             binary = new File("/bin/$command")
         }
         assert binary.exists()
-        FileSystems.default.createSymbolicLink(binDir.file(command), binary.absoluteFile)
+        binDir.file(command).createLink(binary)
     }
 
     @Test
@@ -119,7 +118,9 @@ public class CommandLineIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void checkDefaultGradleUserHome() {
         // the actual testing is done in the build script.
-        executer.withGradleUserHomeDir(null).withTasks("checkDefaultGradleUserHome").run();
+        File userHome = file('customUserHome')
+        executer.withUserHomeDir(userHome).withGradleUserHomeDir(null).withTasks("checkDefaultGradleUserHome").run();
+        assert userHome.file(".gradle").exists()
     }
 
     @Test
@@ -179,7 +180,7 @@ public class CommandLineIntegrationTest extends AbstractIntegrationTest {
     public void resolvesLinksWhenDeterminingHomeDirectory() {
         def script = file('bin/my app')
         script.parentFile.createDir()
-        FileSystems.default.createSymbolicLink(script, distribution.gradleHomeDir.file('bin/gradle'))
+        script.createLink(distribution.gradleHomeDir.file('bin/gradle'))
 
         def result = executer.usingExecutable(script.absolutePath).withTasks("help").run()
         assert result.output.contains("my app")
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
index aa729ba..57d576c 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
@@ -155,7 +155,7 @@ apply plugin: 'groovy'
 repositories { mavenCentral() }
 dependencies {
     compile gradleApi()
-    groovy localGroovy()
+    compile localGroovy()
     testCompile 'junit:junit:4.11'
 }
 """
@@ -196,7 +196,7 @@ apply plugin: 'groovy'
 repositories { mavenCentral() }
 dependencies {
     compile gradleApi()
-    groovy localGroovy()
+    compile localGroovy()
     testCompile 'junit:junit:4.11'
 }
 """
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 d1dd28d..56d95e2 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
@@ -18,11 +18,11 @@ package org.gradle.integtests
 
 import org.gradle.util.DistributionLocator
 import org.gradle.util.GradleVersion
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import spock.lang.Specification
 
-/**
- * @author: Szczepan Faber, created at: 8/20/11
- */
+ at Requires(TestPrecondition.ONLINE)
 class DistributionLocatorIntegrationTest extends Specification {
 
     def locator = new DistributionLocator()
@@ -36,7 +36,7 @@ class DistributionLocatorIntegrationTest extends Specification {
 
     def "locates snapshot versions"() {
         expect:
-        urlExist(locator.getDistributionFor(GradleVersion.version("1.3-20120919220026+0000")))
+        urlExist(locator.getDistributionFor(GradleVersion.version("1.6-20130321190822+0000")))
     }
 
     void urlExist(URI url) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
deleted file mode 100755
index 22f3df7..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.gradle.integtests.fixtures.executer.ExecutionFailure
-import org.gradle.test.fixtures.file.TestFile
-import org.junit.Test
-
-import static org.hamcrest.Matchers.containsString
-
-class ExternalScriptErrorIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void reportsScriptEvaluationFailsWithGroovyException() {
-        testFile('build.gradle') << '''
-apply { from 'other.gradle' }
-'''
-        TestFile script = testFile('other.gradle') << '''
-
-doStuff()
-'''
-
-        ExecutionFailure failure = inTestDirectory().runWithFailure()
-
-        failure.assertHasFileName("Script '${script}'");
-        failure.assertHasLineNumber(3);
-        failure.assertHasDescription('A problem occurred evaluating script.');
-        failure.assertHasCause('Could not find method doStuff() for arguments [] on root project');
-    }
-
-    @Test
-    public void reportsScriptCompilationException() {
-        testFile('build.gradle') << '''
-apply { from 'other.gradle' }
-'''
-        TestFile script = testFile('other.gradle')
-        script.text = 'import org.gradle()'
-
-        ExecutionFailure failure = inTestDirectory().runWithFailure()
-        failure.assertHasFileName("Script '${script}'");
-        failure.assertHasLineNumber(1);
-        failure.assertHasDescription("Could not compile script '${script}'");
-        failure.assertThatCause(containsString("script '${script}': 1: unexpected token: ("))
-    }
-
-    @Test
-    public void reportsMissingScript() {
-        TestFile buildScript = testFile('build.gradle') << '''
-apply { from 'unknown.gradle' }
-'''
-        TestFile script = testFile('unknown.gradle')
-
-        ExecutionFailure failure = inTestDirectory().runWithFailure()
-        failure.assertHasFileName("Build file '${buildScript}");
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription("A problem occurred evaluating root project");
-        failure.assertHasCause("Could not read script '${script}' as it does not exist.");
-    }
-
-    @Test
-    public void reportsTaskExecutionFailsWithRuntimeException() {
-        testFile('build.gradle') << '''
-apply { from 'other.gradle' }
-'''
-        TestFile script = testFile('other.gradle') << '''
-task doStuff << {
-    throw new RuntimeException('fail')
-}
-'''
-
-        ExecutionFailure failure = inTestDirectory().withTasks('doStuff').runWithFailure()
-
-        failure.assertHasFileName("Script '${script}'");
-        failure.assertHasLineNumber(3);
-        failure.assertHasDescription('Execution failed for task \':doStuff\'');
-        failure.assertHasCause('fail');
-    }
-
-}
-
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 11831c7..973a556 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
@@ -56,7 +56,6 @@ assert "${externalScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sou
 assert "${externalScript.toURI()}" == buildscript.sourceURI as String
 assert buildscript.classLoader == getClass().classLoader.parent
 assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-assert gradle.scriptClassLoader == buildscript.classLoader.parent
 assert project.buildscript.classLoader != buildscript.classLoader
 Gradle.class.classLoader.loadClass('${implClassName}')
 try {
@@ -93,8 +92,6 @@ new BuildSrcClass()
 assert 'doStuff' == name
 assert buildscript.classLoader == getClass().classLoader.parent
 assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-assert project.gradle.scriptClassLoader == buildscript.classLoader.parent
-assert project.buildscript.classLoader != buildscript.classLoader
 ext.someProp = 'value'
 '''
         testFile('build.gradle') << '''
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy
index f02eff3..716b889 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy
@@ -26,7 +26,7 @@ class IncrementalGroovyProjectBuildIntegrationTest extends AbstractIntegrationTe
         file("src/main/groovy/BuildClass.java") << 'public class BuildClass { }'
         file("build.gradle") << '''
             apply plugin: 'groovy'
-            dependencies { groovy localGroovy() }
+            dependencies { compile localGroovy() }
             groovydoc {
                 link('http://download.oracle.com/javase/1.5.0/docs/api', 'java.,org.xml.,javax.,org.xml.')
             }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTasksIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTasksIntegrationTest.groovy
new file mode 100644
index 0000000..036cdde
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTasksIntegrationTest.groovy
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec {
+    def "setup"() {
+        buildFile << buildFileBase
+        buildFile << """
+    task incremental(type: IncrementalTask) {
+        inputDir = project.mkdir('inputs')
+        outputDir = project.mkdir('outputs')
+        prop = 'foo'
+    }
+"""
+        file('inputs/file0.txt') << "inputFile0"
+        file('inputs/file1.txt') << "inputFile1"
+        file('inputs/file2.txt') << "inputFile2"
+
+        file('outputs/file1.txt') << "outputFile1"
+        file('outputs/file2.txt') << "outputFile2"
+    }
+
+    private static String getBuildFileBase() {
+        """
+    class BaseIncrementalTask extends DefaultTask {
+        @InputDirectory
+        def File inputDir
+
+        @TaskAction
+        void execute(IncrementalTaskInputs inputs) {
+            assert !(inputs instanceof ExtensionAware)
+
+            if (project.hasProperty('forceFail')) {
+                throw new RuntimeException('failed')
+            }
+
+            incrementalExecution = inputs.incremental
+
+            inputs.outOfDate { change ->
+                if (change.added) {
+                    addedFiles << change.file
+                } else {
+                    changedFiles << change.file
+                }
+            }
+
+            inputs.removed { change ->
+                removedFiles << change.file
+            }
+
+            touchOutputs()
+        }
+
+        def touchOutputs() {
+        }
+
+        def addedFiles = []
+        def changedFiles = []
+        def removedFiles = []
+        def incrementalExecution
+    }
+
+    class IncrementalTask extends BaseIncrementalTask {
+        @Input
+        def String prop
+
+        @OutputDirectory
+        def File outputDir
+
+        @Override
+        def touchOutputs() {
+            outputDir.eachFile {
+                it << "more content"
+            }
+        }
+    }
+
+    ext {
+        incrementalExecution = true
+        added = []
+        changed = []
+        removed = []
+    }
+
+    task incrementalCheck(dependsOn: "incremental") << {
+        assert incremental.incrementalExecution == project.ext.incrementalExecution
+        assert incremental.addedFiles.collect({ it.name }).sort() == project.ext.added
+        assert incremental.changedFiles.collect({ it.name }).sort() == project.ext.changed
+        assert incremental.removedFiles.collect({ it.name }).sort() == project.ext.removed
+    }
+"""
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when run for the first time"() {
+        expect:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is skipped when run with no changes since last execution"() {
+        given:
+        previousExecution()
+
+        when:
+        run "incremental"
+
+        then:
+        ":incremental" in skippedTasks
+    }
+
+    def "incremental task is informed of 'out-of-date' files when input file modified"() {
+        given:
+        previousExecution()
+
+        when:
+        file('inputs/file1.txt') << "changed content"
+
+        then:
+        executesWithIncrementalContext("ext.changed = ['file1.txt']");
+    }
+
+    def "incremental task is informed of 'out-of-date' files when input file added"() {
+        given:
+        previousExecution()
+
+        when:
+        file('inputs/file3.txt') << "file3 content"
+
+        then:
+        executesWithIncrementalContext("ext.added = ['file3.txt']")
+    }
+
+    def "incremental task is informed of 'out-of-date' files when input file removed"() {
+        given:
+        previousExecution()
+
+        when:
+        file('inputs/file2.txt').delete()
+
+        then:
+        executesWithIncrementalContext("ext.removed = ['file2.txt']")
+    }
+
+    def "incremental task is informed of 'out-of-date' files when all input files removed"() {
+        given:
+        previousExecution()
+
+        when:
+        file('inputs/file1.txt').delete()
+        file('inputs/file2.txt').delete()
+
+        then:
+        executesWithIncrementalContext("ext.removed = ['file1.txt', 'file2.txt']")
+    }
+
+    def "incremental task is informed of 'out-of-date' files with added, removed and modified files"() {
+        given:
+        previousExecution()
+
+        when:
+        file('inputs/file1.txt') << "changed content"
+        file('inputs/file2.txt').delete()
+        file('inputs/file3.txt') << "new file 3"
+        file('inputs/file4.txt') << "new file 4"
+
+        then:
+        executesWithIncrementalContext("""
+ext.changed = ['file1.txt']
+ext.removed = ['file2.txt']
+ext.added = ['file3.txt', 'file4.txt']
+""")
+    }
+
+    def "incremental task is informed of 'out-of-date' files when task has no declared outputs or properties"() {
+        given:
+        buildFile.text = buildFileBase
+        buildFile << """
+    task incremental(type: BaseIncrementalTask) {
+        inputDir = project.mkdir('inputs')
+    }
+"""
+        and:
+        previousExecution()
+
+        when:
+        file('inputs/file3.txt') << "file3 content"
+
+        then:
+        executesWithIncrementalContext("ext.added = ['file3.txt']")
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when input property has changed"() {
+        given:
+        previousExecution()
+
+        when:
+        buildFile << "incremental.prop = 'changed'"
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when task class has changed"() {
+        given:
+        previousExecution()
+
+        when:
+        buildFile.text = buildFileBase
+        buildFile << """
+    class IncrementalTask2 extends BaseIncrementalTask {}
+    task incremental(type: IncrementalTask2) {
+        inputDir = project.mkdir('inputs')
+    }
+"""
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when output directory is changed"() {
+        given:
+        previousExecution()
+
+        when:
+        buildFile << "incremental.outputDir = project.mkdir('new-outputs')"
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when output file has changed"() {
+        given:
+        previousExecution()
+
+        when:
+        file("outputs/file1.txt") << "further change"
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when output file has been removed"() {
+        given:
+        previousExecution()
+
+        when:
+        file("outputs/file1.txt").delete()
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when all output files have been removed"() {
+        given:
+        previousExecution()
+
+        when:
+        file("outputs").deleteDir()
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when Task.upToDate() is false"() {
+        given:
+        previousExecution()
+
+        when:
+        buildFile << "incremental.outputs.upToDateWhen { false }"
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed that all input files are 'out-of-date' when gradle is executed with --rerun-tasks"() {
+        given:
+        previousExecution()
+
+        when:
+        executer.withArgument("--rerun-tasks")
+
+        then:
+        executesWithRebuildContext()
+    }
+
+    def "incremental task is informed of 'out-of-date' files since previous successful execution"() {
+        given:
+        previousExecution()
+
+        and:
+        file('inputs/file1.txt') << "changed content"
+
+        when:
+        failedExecution()
+
+        then:
+        executesWithIncrementalContext("ext.changed = ['file1.txt']");
+    }
+    /*
+     7. Sad-day cases
+         - Incremental task has input files declared
+         - Incremental task action throws exception
+         - Incremental task action processes outOfDate files multiple times
+         - Attempt to process removed files without first processing outOfDate files
+     */
+
+    def previousExecution() {
+        run "incremental"
+    }
+
+    def failedExecution() {
+        executer.withArgument("-PforceFail=yep")
+        assert fails("incremental")
+        executer.withArguments()
+    }
+
+    def executesWithIncrementalContext(String fileChanges) {
+        buildFile << fileChanges
+        succeeds "incrementalCheck"
+    }
+
+    def executesWithRebuildContext() {
+        buildFile << """
+    ext.changed = ['file0.txt', 'file1.txt', 'file2.txt']
+    ext.incrementalExecution = false
+"""
+        succeeds "incrementalCheck"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
deleted file mode 100644
index 80c9a7b..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.*
-
-class IncrementalTestIntegrationTest extends AbstractIntegrationTest {
-
-    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
-
-    @Before
-    public void before() {
-        executer.noExtraLogging()
-    }
-
-    @Test
-    public void doesNotRunStaleTests() {
-        executer.withTasks('test').runWithFailure().assertTestsFailed()
-
-        file('src/test/java/Broken.java').assertIsFile().delete()
-
-        executer.withTasks('test').run()
-    }
-
-    @Test
-    public void executesTestsWhenSourceChanges() {
-        executer.withTasks('test').run()
-
-        // Change a production class
-        file('src/main/java/MainClass.java').assertIsFile().copyFrom(file('NewMainClass.java'))
-
-        executer.withTasks('test').run().assertTasksNotSkipped(':compileJava', ':classes', ':compileTestJava', ':testClasses', ':test')
-        executer.withTasks('test').run().assertTasksNotSkipped()
-        
-        // Change a test class
-        file('src/test/java/Ok.java').assertIsFile().copyFrom(file('NewOk.java'))
-
-        executer.withTasks('test').run().assertTasksNotSkipped(':compileTestJava', ':testClasses', ':test')
-        executer.withTasks('test').run().assertTasksNotSkipped()
-    }
-
-    @Test
-    public void executesTestsWhenSelectedTestsChange() {
-        executer.withTasks('test').run()
-
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
-        result.assertTestClassesExecuted('JUnitTest')
-
-        // Include more tests
-        file('build.gradle').append 'test.include "**/*Extra*"\n'
-
-        executer.withTasks('test').run().assertTasksNotSkipped(':test')
-        result.assertTestClassesExecuted('JUnitTest', 'JUnitExtra')
-
-        executer.withTasks('test').run().assertTasksNotSkipped()
-
-        // Use single test execution
-        executer.withTasks('test').withArguments('-Dtest.single=Ok').run().assertTasksNotSkipped(':test')
-        executer.withTasks('test').run().assertTasksNotSkipped(':test')
-        executer.withTasks('test').run().assertTasksNotSkipped()
-
-        // Switch test framework
-        file('build.gradle').append 'test.useTestNG()\n'
-
-        //TODO this exposes a possible problem: When changing the test framework stale xml result files from former test framework are still present.
-        executer.withTasks('cleanTest', 'test').run().assertTasksNotSkipped(':cleanTest',':test')
-
-        result = new JUnitXmlTestExecutionResult(testDirectory)
-        result.assertTestClassesExecuted('TestNGTest')
-
-        executer.withTasks('test').run().assertTasksNotSkipped()
-    }
-
-    @Test @Ignore
-    public void executesTestsWhenPropertiesChange() {
-        Assert.fail()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
deleted file mode 100644
index e4c7416..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests;
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.executer.ExecutionFailure;
-import org.gradle.test.fixtures.file.TestFile;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.containsString;
-
-public class InitScriptErrorIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void reportsInitScriptEvaluationFailsWithGroovyException() {
-        TestFile initScript = testFile("init.gradle");
-        initScript.write("\ncreateTakk('do-stuff')");
-        ExecutionFailure failure = inTestDirectory().usingInitScript(initScript).runWithFailure();
-
-        failure.assertHasFileName(String.format("Initialization script '%s'", initScript));
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription("A problem occurred evaluating initialization script.");
-        failure.assertHasCause("Could not find method createTakk() for arguments [do-stuff] on build.");
-    }
-
-    @Test
-    public void reportsGroovyCompilationException() {
-        TestFile initScript = testFile("init.gradle");
-        initScript.writelns(
-            "// a comment",
-            "import org.gradle.unknown.Unknown",
-            "new Unknown()");
-        ExecutionFailure failure = inTestDirectory().usingInitScript(initScript).runWithFailure();
-        failure.assertHasFileName(String.format("Initialization script '%s'", initScript));
-        failure.assertHasLineNumber(2);
-        failure.assertHasDescription(String.format("Could not compile initialization script '%s'.", initScript));
-        failure.assertThatCause(containsString(String.format("initialization script '%s': 2: unable to resolve class org.gradle.unknown.Unknown", initScript)));
-    }
-}
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 28112e4..9605e00 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
@@ -73,7 +73,6 @@ println 'error message'
 assert gradle != null
 assert initscript.classLoader == getClass().classLoader.parent
 assert initscript.classLoader == Thread.currentThread().contextClassLoader
-assert scriptClassLoader == initscript.classLoader.parent
 Gradle.class.classLoader.loadClass('${implClassName}')
 try {
     initscript.classLoader.loadClass('${implClassName}')
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
index bb2b47f..6b60495 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
@@ -15,11 +15,9 @@
  */
 
 package org.gradle.integtests
-
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.executer.ExecutionFailure
 import org.gradle.test.fixtures.file.TestFile
-import org.junit.Ignore
 import org.junit.Test
 
 class JavaProjectIntegrationTest extends AbstractIntegrationTest {
@@ -31,7 +29,7 @@ class JavaProjectIntegrationTest extends AbstractIntegrationTest {
 
         ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
 
-        failure.assertHasDescription("Execution failed for task ':compileJava'");
+        failure.assertHasDescription("Execution failed for task ':compileJava'.");
         failure.assertHasCause("Compilation failed; see the compiler error output for details.");
     }
 
@@ -44,7 +42,7 @@ class JavaProjectIntegrationTest extends AbstractIntegrationTest {
 
         ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
 
-        failure.assertHasDescription("Execution failed for task ':compileTestJava'");
+        failure.assertHasDescription("Execution failed for task ':compileTestJava'.");
         failure.assertHasCause("Compilation failed; see the compiler error output for details.");
     }
 
@@ -67,7 +65,7 @@ public class NotATest {}"""
 
         ExecutionFailure failure = usingBuildFile(buildFile).withTasks("javadoc").runWithFailure();
 
-        failure.assertHasDescription("Execution failed for task ':javadoc'");
+        failure.assertHasDescription("Execution failed for task ':javadoc'.");
         failure.assertHasCause("Javadoc generation failed.");
     }
 
@@ -121,8 +119,6 @@ version = ''
         testFile("build/libs/empty.jar").assertIsFile();
     }
 
-    // TODO: translate to new source set/packaging model
-    @Ignore
     @Test
     public void "task registered as a builder of resources is executed"() {
         TestFile buildFile = testFile("build.gradle");
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedNativeAndJvmProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedNativeAndJvmProjectIntegrationTest.groovy
new file mode 100644
index 0000000..d53e6e7
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedNativeAndJvmProjectIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+public class MixedNativeAndJvmProjectIntegrationTest extends AbstractIntegrationSpec {
+
+    def "can combine java, cpp-exe and cpp-lib plugins in a single project"() {
+        settingsFile << "rootProject.name = 'test'"
+        buildFile << """
+            apply plugin: "java"
+            apply plugin: "cpp"
+
+            executables { main {} }
+            libraries { main {} }
+
+            task checkBinaries << {
+                assert binaries.mainClasses instanceof ClassDirectoryBinary
+                assert binaries.mainExecutable instanceof ExecutableBinary
+                assert binaries.mainSharedLibrary instanceof SharedLibraryBinary
+            }
+"""
+        expect:
+        succeeds "checkBinaries"
+    }
+}
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
index 6d04f14..ed4d7dd 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
@@ -18,10 +18,9 @@ package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.hamcrest.Matchers
 import spock.lang.IgnoreIf
 
-import static org.hamcrest.Matchers.containsString
-
 public class MultiProjectDependencyIntegrationTest extends AbstractIntegrationSpec {
 
     def setup() {
@@ -159,7 +158,7 @@ project(':c') {
 
         then:
         failure.assertHasNoCause()
-        failure.assertThatDescription(containsString("Circular dependency between tasks. Cycle includes [task ':a:compileJava', task ':a:jar']."))
+        failure.assertThatDescription(Matchers.startsWith("Circular dependency between the following tasks:"))
     }
 
     def "project dependency a->b->c->d and c fails"() {
@@ -247,15 +246,12 @@ project(':$from') {
     }
 
     def failingBuild(def project) {
+        file("$project/src/main/java/Foo.java") << "class Foo {}"
         buildFile << """
 project(':$project') {
-    //fail needs to have a dependendency on compileJava
-    //this way all the java project dependencies are built first in paralle mode
-    //if 'fail 'does not have any dependencies it will be scheduled to execute very early in parallel mode
-    task fail(dependsOn: compileJava) << {
+    compileJava.doFirst {
         throw new RuntimeException('failure in $project')
     }
-    jar.dependsOn fail
 }
 """
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
index 6e332b9..7be6389 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -28,9 +28,6 @@ import java.util.jar.Manifest
 import static org.junit.Assert.assertEquals
 import static org.junit.Assert.assertTrue
 
-/**
- * @author Hans Dockter
- */
 class OsgiProjectSampleIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'osgi')
@@ -51,7 +48,7 @@ class OsgiProjectSampleIntegrationTest extends AbstractIntegrationTest {
     static void checkManifest(Manifest manifest, start) {
         assertEquals('Example Gradle Activator', manifest.mainAttributes.getValue('Bundle-Name'))
         assertEquals('2', manifest.mainAttributes.getValue('Bundle-ManifestVersion'))
-        assertEquals('Bnd-1.50.0', manifest.mainAttributes.getValue('Tool'))
+        assertEquals('Bnd-2.1.0.20130426-122213', manifest.mainAttributes.getValue('Tool'))
         assertTrue(start <= Long.parseLong(manifest.mainAttributes.getValue('Bnd-LastModified')))
         assertEquals('1.0.0', manifest.mainAttributes.getValue('Bundle-Version'))
         assertEquals('gradle_tooling.osgi', manifest.mainAttributes.getValue('Bundle-SymbolicName'))
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
index 556ab0b..513b9a0 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package org.gradle.integtests;
+package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.test.fixtures.server.http.BlockingHttpServer
 import org.junit.Rule
+import spock.lang.IgnoreIf
 
+ at IgnoreIf({GradleContextualExecuter.parallel}) // no point, always runs in parallel
 public class ParallelProjectExecutionIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule public final BlockingHttpServer blockingServer = new BlockingHttpServer()
@@ -31,23 +34,28 @@ public class ParallelProjectExecutionIntegrationTest extends AbstractIntegration
         buildFile << """
 assert gradle.startParameter.parallelThreadCount != 0
 allprojects {
-    task pingServer << {
-        URL url = new URL("http://localhost:${blockingServer.port}/" + project.path)
-        println url.openConnection().getHeaderField('RESPONSE')
+    tasks.addRule("ping<>") { String name ->
+        if (name.startsWith("ping")) {
+            tasks.create(name) {
+                doLast {
+                    URL url = new URL("http://localhost:${blockingServer.port}/" + path)
+                    println url.openConnection().getHeaderField('RESPONSE')
+                }
+            }
+        }
     }
 }
 """
-        executer.withArgument('--parallel')
+        executer.withArgument('--parallel-threads=3') // needs to be set to the maximum number of expectConcurrentExecution() calls
         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')
+        blockingServer.expectConcurrentExecution(':b:pingServer', ':c:pingServer', ':d:pingServer')
+        blockingServer.expectConcurrentExecution(':a:pingServer')
 
         run ':a:pingServer'
     }
@@ -59,9 +67,9 @@ allprojects {
         projectDependency from: 'c', to: ['d']
 
         expect:
-        blockingServer.expectConcurrentExecution(':d')
-        blockingServer.expectConcurrentExecution(':b', ':c')
-        blockingServer.expectConcurrentExecution(':a')
+        blockingServer.expectConcurrentExecution(':d:pingServer')
+        blockingServer.expectConcurrentExecution(':b:pingServer', ':c:pingServer')
+        blockingServer.expectConcurrentExecution(':a:pingServer')
 
         run ':a:pingServer'
     }
@@ -72,7 +80,7 @@ allprojects {
         failingBuild 'c'
 
         when:
-        blockingServer.expectConcurrentExecution(':b', ':c')
+        blockingServer.expectConcurrentExecution(':b:pingServer', ':c:pingServer')
 
         fails ':a:pingServer'
 
@@ -81,6 +89,33 @@ allprojects {
         failure.error =~ 'c failed'
     }
 
+    def "tasks are executed when they are ready and not necessarily alphabetically"() {
+        buildFile << """
+            tasks.getByPath(':b:pingA').dependsOn(':a:pingA')
+            tasks.getByPath(':b:pingC').dependsOn([':b:pingA', ':b:pingB'])
+        """
+
+        expect:
+        //project a and b are both executed even though alphabetically more important task is blocked
+        blockingServer.expectConcurrentExecution(':b:pingB', ':a:pingA')
+        blockingServer.expectConcurrentExecution(':b:pingA')
+        blockingServer.expectConcurrentExecution(':b:pingC')
+
+        run 'b:pingC'
+    }
+
+    void 'tasks with should run after ordering rules are preferred when running over an idle worker thread'() {
+        buildFile << """
+            tasks.getByPath(':a:pingA').shouldRunAfter(':b:pingB')
+            tasks.getByPath(':b:pingB').dependsOn(':b:pingA')
+        """
+
+        expect:
+        blockingServer.expectConcurrentExecution(':a:pingA', ':b:pingA')
+        blockingServer.expectConcurrentExecution(':b:pingB')
+
+        run 'a:pingA', 'b:pingB'
+    }
 
     def projectDependency(def link) {
         def from = link['from']
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/PluginCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/PluginCrossVersionIntegrationTest.groovy
deleted file mode 100644
index 1a87b0c..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/PluginCrossVersionIntegrationTest.groovy
+++ /dev/null
@@ -1,72 +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
-
-import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
-
-import org.gradle.integtests.fixtures.TargetVersions
-
- at TargetVersions('0.9-rc-3+')
-class PluginCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
-    def "can use plugin compiled using previous Gradle version"() {
-        given:
-        file("producer/build.gradle") << """
-apply plugin: 'groovy'
-dependencies {
-    groovy localGroovy()
-    compile gradleApi()
-}
-"""
-        file("producer/src/main/groovy/SomePlugin.groovy") << """
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.*
-import org.gradle.api.internal.ConventionTask
-
-class SomePlugin implements Plugin<Project> {
-    void apply(Project p) {
-        p.tasks.add('do-stuff', CustomTask)
-        p.tasks.add('customConventionTask', CustomConventionTask)
-        p.tasks.add('customSourceTask', CustomSourceTask)
-    }
-}
-
-class CustomTask extends DefaultTask {
-    @TaskAction void go() { }
-}
-
-// ConventionTask leaks a lot of internal API so test for compatibility
-class CustomConventionTask extends ConventionTask {}
-
-// Same reason here, but less direct
-class CustomSourceTask extends SourceTask {}
-
-"""
-
-        buildFile << """
-buildscript {
-    dependencies { classpath fileTree(dir: "producer/build/libs", include: '*.jar') }
-}
-
-apply plugin: SomePlugin
-"""
-
-        expect:
-        version previous withTasks 'assemble' inDirectory(file("producer")) run()
-        version current withTasks 'do-stuff' run()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
deleted file mode 100644
index 923585e..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Document
-
-class ProfilingIntegrationTest extends AbstractIntegrationSpec {
-    def "can generate profiling report"() {
-        file('settings.gradle') << 'include "a", "b", "c"'
-        buildFile << '''
-allprojects {
-    apply plugin: 'java'
-}
-'''
-        when:
-        executer.withArguments("--profile").withTasks("build").run()
-
-        then:
-        def reportFile = file('build/reports/profile').listFiles().find { it.name ==~ /profile-.+.html/ }
-        Document document = Jsoup.parse(reportFile, null);
-        !document.select("TD:contains(:jar)").isEmpty()
-        !document.select("TD:contains(:a:jar)").isEmpty()
-        !document.select("TD:contains(:b:jar)").isEmpty()
-        !document.select("TD:contains(:c:jar)").isEmpty()
-    }
-}
\ No newline at end of file
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 55769d0..bdef399 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
@@ -16,7 +16,7 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -24,7 +24,8 @@ import org.junit.Test
 
 class ProjectLayoutIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
+    @Rule
+    public final TestResources resources = new TestResources(testDirectoryProvider)
 
     @Test
     public void canHaveSomeSourceAndResourcesInSameDirectoryAndSomeInDifferentDirectories() {
@@ -164,12 +165,24 @@ sourceSets.main.java {
 
     @Test
     public void canUseANonStandardBuildDir() {
-        executer.withTasks('build').withArguments('-i').run()
+        executer.withTasks('build').run()
 
         file('build').assertDoesNotExist()
 
-        JUnitXmlTestExecutionResult results = new JUnitXmlTestExecutionResult(file(), 'target')
+        def results = new DefaultTestExecutionResult(file(), 'target')
         results.assertTestClassesExecuted('PersonTest')
         results.testClass('PersonTest').assertTestsExecuted('ok')
     }
+
+    @Test
+    public void projectPathsResolvedRelativeToRoot() {
+        file('relative/a/build.gradle') << """
+            task someTask
+        """
+        file('settings.gradle') << '''
+        include ':a'
+        project(':a').projectDir = new File('relative/a')
+        '''
+        executer.inDirectory(file('relative/a')).withTasks(':a:someTask').run()
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
deleted file mode 100644
index 078541d..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.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.integtests;
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.executer.ExecutionFailure;
-import org.gradle.test.fixtures.file.TestFile;
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class SettingsScriptErrorIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void reportsSettingsScriptEvaluationFailsWithRuntimeException() throws IOException {
-        TestFile settingsFile = testFile("some settings.gradle");
-        settingsFile.writelns("", "", "throw new RuntimeException('<failure message>')");
-
-        ExecutionFailure failure = executer.usingSettingsFile(settingsFile).runWithFailure();
-
-        failure.assertHasFileName(String.format("Settings file '%s'", settingsFile));
-        failure.assertHasLineNumber(3);
-        failure.assertHasDescription("A problem occurred evaluating settings 'reportsSettingsScriptEvaluationFailsWithRuntimeException");
-        failure.assertHasCause("<failure message>");
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
index 05e2c79..67de4a7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
@@ -44,7 +44,6 @@ println 'error message'
 assert settings != null
 assert buildscript.classLoader == getClass().classLoader.parent
 assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-assert gradle.scriptClassLoader.parents[0] == buildscript.classLoader.parent.parent
 Gradle.class.classLoader.loadClass('${implClassName}')
 try {
     buildscript.classLoader.loadClass('${implClassName}')
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationSpec.groovy
new file mode 100644
index 0000000..7f0cf6b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationSpec.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class TaskDefinitionIntegrationSpec extends AbstractIntegrationSpec {
+
+    def "unsupported task parameter fails with decent error message"() {
+        buildFile << "task a(Type:Copy)"
+        when:
+        fails 'a'
+        then:
+        failure.assertHasCause("Could not create task 'a': Unknown argument(s) in task definition: [Type]")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
index 9c98581..2859836 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
@@ -33,7 +33,7 @@ class TaskErrorExecutionIntegrationTest extends AbstractIntegrationTest {
 
         failure.assertHasFileName(String.format("Build file '%s'", buildFile))
         failure.assertHasLineNumber(3)
-        failure.assertHasDescription("Execution failed for task ':do-stuff'")
+        failure.assertHasDescription("Execution failed for task ':do-stuff'.")
         failure.assertHasCause("broken")
     }
 
@@ -48,7 +48,7 @@ class TaskErrorExecutionIntegrationTest extends AbstractIntegrationTest {
 
         failure.assertHasFileName(String.format("Build file '%s'", buildFile))
         failure.assertHasLineNumber(2)
-        failure.assertHasDescription("Execution failed for task ':brokenClosure'")
+        failure.assertHasDescription("Execution failed for task ':brokenClosure'.")
         failure.assertHasCause("broken closure")
     }
 
@@ -74,7 +74,7 @@ class TaskErrorExecutionIntegrationTest extends AbstractIntegrationTest {
 
         ExecutionFailure failure = usingBuildFile(buildFile).withTasks("brokenJavaTask").runWithFailure()
 
-        failure.assertHasDescription("Execution failed for task ':brokenJavaTask'")
+        failure.assertHasDescription("Execution failed for task ':brokenJavaTask'.")
         failure.assertHasCause("broken action")
     }
 
@@ -93,7 +93,7 @@ class TaskErrorExecutionIntegrationTest extends AbstractIntegrationTest {
 
         failure.assertHasFileName(String.format("Build file '%s'", buildFile))
         failure.assertHasLineNumber(3)
-        failure.assertHasDescription("Execution failed for task ':a:a")
+        failure.assertHasDescription("Execution failed for task ':a:a'.")
         failure.assertHasCause("broken")
     }
 
@@ -115,4 +115,31 @@ task custom(type: CustomTask)
         failure.assertHasCause("No value has been specified for property 'srcFile'.")
         failure.assertHasCause("No value has been specified for property 'destFile'.")
     }
+
+    @Test
+    public void reportsUnknownTask() {
+        def settingsFile = testFile("settings.gradle")
+        settingsFile << """
+rootProject.name = 'test'
+include 'a', 'b'
+"""
+        def buildFile = testFile('build.gradle')
+        buildFile << """
+allprojects { task someTask }
+project(':a') { task someTaskA }
+project(':b') { task someTaskB }
+"""
+
+        def failure = inTestDirectory().withTasks("someTest").runWithFailure()
+        failure.assertHasDescription("Task 'someTest' not found in root project 'test'. Some candidates are: 'someTask', 'someTaskA', 'someTaskB'.")
+        failure.assertHasResolution("Run gradle tasks to get a list of available tasks. Run with --info or --debug option to get more log output.")
+
+        failure = inTestDirectory().withTasks(":someTest").runWithFailure()
+        failure.assertHasDescription("Task 'someTest' not found in root project 'test'. Some candidates are: 'someTask'.")
+        failure.assertHasResolution("Run gradle tasks to get a list of available tasks. Run with --info or --debug option to get more log output.")
+
+        failure = inTestDirectory().withTasks("a:someTest").runWithFailure()
+        failure.assertHasDescription("Task 'someTest' not found in project ':a'. Some candidates are: 'someTask', 'someTaskA'.")
+        failure.assertHasResolution("Run gradle tasks to get a list of available tasks. Run with --info or --debug option to get more log output.")
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..02a2166
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.groovy
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TextUtil
+import org.junit.Test
+import spock.lang.Issue
+
+import static org.hamcrest.Matchers.startsWith
+
+public class TaskExecutionIntegrationTest extends AbstractIntegrationSpec {
+    
+    def taskCanAccessTaskGraph() {
+        buildFile << """
+    boolean notified = false
+    task a(dependsOn: 'b') << { task ->
+        assert notified
+        assert gradle.taskGraph.hasTask(task)
+        assert gradle.taskGraph.hasTask(':a')
+        assert gradle.taskGraph.hasTask(a)
+        assert gradle.taskGraph.hasTask(':b')
+        assert gradle.taskGraph.hasTask(b)
+        assert gradle.taskGraph.allTasks.contains(task)
+        assert gradle.taskGraph.allTasks.contains(tasks.getByName('b'))
+    }
+    task b
+    gradle.taskGraph.whenReady { graph ->
+        assert graph.hasTask(':a')
+        assert graph.hasTask(a)
+        assert graph.hasTask(':b')
+        assert graph.hasTask(b)
+        assert graph.allTasks.contains(a)
+        assert graph.allTasks.contains(b)
+        notified = true
+    }
+"""
+        when:
+        succeeds "a"
+        
+        then:
+        result.assertTasksExecuted(":b", ":a");
+    }
+
+    def executesAllTasksInASingleBuildAndEachTaskAtMostOnce() {
+        buildFile << """
+    gradle.taskGraph.whenReady { assert !project.hasProperty('graphReady'); ext.graphReady = true }
+    task a << { task -> project.ext.executedA = task }
+    task b << { 
+        assert a == project.executedA
+        assert gradle.taskGraph.hasTask(':a')
+    }
+    task c(dependsOn: a)
+    task d(dependsOn: a)
+    task e(dependsOn: [a, d]);
+"""
+        expect:
+        run("a", "b").assertTasksExecuted(":a", ":b");
+        run("a", "a").assertTasksExecuted(":a");
+        run("c", "a").assertTasksExecuted(":a", ":c");
+        run("c", "e").assertTasksExecuted(":a", ":c", ":d", ":e");
+    }
+
+    def executesMultiProjectsTasksInASingleBuildAndEachTaskAtMostOnce() {
+        settingsFile << "include 'child1', 'child2', 'child1-2', 'child1-2-2'"
+        buildFile << """
+    task a
+    allprojects {
+        task b
+        task c(dependsOn: ['b', ':a'])
+    };
+"""
+        
+        expect:
+        run("a", "c").assertTasksExecuted(":a", ":b", ":c", ":child1:b", ":child1:c", ":child1-2:b", ":child1-2:c", ":child1-2-2:b", ":child1-2-2:c", ":child2:b", ":child2:c");
+        run("b", ":child2:c").assertTasksExecuted(":b", ":child1:b", ":child1-2:b", ":child1-2-2:b", ":child2:b", ":a", ":child2:c");
+    }
+
+    @Test
+    def executesMultiProjectDefaultTasksInASingleBuildAndEachTaskAtMostOnce() {
+        settingsFile << "include 'child1', 'child2'"
+        buildFile << """
+    defaultTasks 'a', 'b'
+    task a
+    subprojects {
+        task a(dependsOn: ':a')
+        task b(dependsOn: ':a')
+    }
+"""
+
+        expect:
+        run().assertTasksExecuted(":a", ":child1:a", ":child2:a", ":child1:b", ":child2:b");
+    }
+
+    def executesProjectDefaultTasksWhenNoneSpecified() {
+        buildFile << """
+    task a
+    task b(dependsOn: a)
+    defaultTasks 'b'
+"""
+        expect:
+        run().assertTasksExecuted(":a", ":b");
+    }
+    
+    def doesNotExecuteTaskActionsWhenDryRunSpecified() {
+        buildFile << """
+    task a << { fail() }
+    task b(dependsOn: a) << { fail() }
+    defaultTasks 'b'
+"""
+
+        expect:
+        // project defaults
+        executer.withArguments("-m").run().assertTasksExecuted(":a", ":b");
+        // named tasks
+        executer.withArguments("-m").withTasks("b").run().assertTasksExecuted(":a", ":b");
+    }
+
+    def executesTaskActionsInCorrectEnvironment() {
+        buildFile << """
+    // An action attached to built-in task
+    task a << { assert Thread.currentThread().contextClassLoader == getClass().classLoader }
+
+    // An action defined by a custom task
+    task b(type: CustomTask)
+    class CustomTask extends DefaultTask {
+        @TaskAction def go() {
+            assert Thread.currentThread().contextClassLoader == getClass().classLoader
+        }
+    }
+
+    // An action implementation
+    task c
+    c.doLast new Action<Task>() {
+        void execute(Task t) {
+            assert Thread.currentThread().contextClassLoader == getClass().classLoader
+        }
+    }
+"""
+        expect:
+        succeeds("a", "b", "c")
+    }
+
+    def excludesTasksWhenExcludePatternSpecified() {
+        settingsFile << "include 'sub'"
+        buildFile << """
+    task a
+    task b(dependsOn: a)
+    task c(dependsOn: [a, b])
+    task d(dependsOn: c)
+    defaultTasks 'd'
+"""
+        file("sub/build.gradle") << """
+    task c
+    task d(dependsOn: c)
+"""
+
+        expect:
+        // Exclude entire branch
+        executer.withTasks(":d").withArguments("-x", "c").run().assertTasksExecuted(":d");
+        // Exclude direct dependency
+        executer.withTasks(":d").withArguments("-x", "b").run().assertTasksExecuted(":a", ":c", ":d");
+        // Exclude using paths and multi-project
+        executer.withTasks("d").withArguments("-x", "c").run().assertTasksExecuted(":d", ":sub:d");
+        executer.withTasks("d").withArguments("-x", "sub:c").run().assertTasksExecuted(":a", ":b", ":c", ":d", ":sub:d");
+        executer.withTasks("d").withArguments("-x", ":sub:c").run().assertTasksExecuted(":a", ":b", ":c", ":d", ":sub:d");
+        executer.withTasks("d").withArguments("-x", "d").run().assertTasksExecuted();
+        // Project defaults
+        executer.withArguments("-x", "b").run().assertTasksExecuted(":a", ":c", ":d", ":sub:c", ":sub:d");
+        // Unknown task
+        executer.withTasks("d").withArguments("-x", "unknown").runWithFailure().assertThatDescription(startsWith("Task 'unknown' not found in root project"));
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2974")
+    @Issue("http://issues.gradle.org/browse/GRADLE-3031")
+    def 'excluding a task that is a dependency of multiple tasks'() {
+        settingsFile << "include 'sub'"
+        buildFile << """
+    task a
+    task b(dependsOn: a)
+    task c(dependsOn: a)
+    task d(dependsOn: [b, c])
+"""
+        file("sub/build.gradle") << """
+    task a
+"""
+
+        expect:
+        executer.withTasks("d").withArguments("-x", "a").run().assertTasksExecuted(":b", ":c", ":d");
+        executer.withTasks("b", "a").withArguments("-x", ":a").run().assertTasksExecuted(":b", ":sub:a");
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2022")
+    def tryingToInstantiateTaskDirectlyFailsWithGoodErrorMessage() {
+        buildFile << """
+    new DefaultTask()
+"""
+        when:
+        fails "tasks"
+
+        then:
+        failure.assertHasCause("Task of type 'org.gradle.api.DefaultTask' has been instantiated directly which is not supported")
+    }
+
+    def "sensible error message for circular task dependency"() {
+        buildFile << """
+    task a(dependsOn: 'b')
+    task b(dependsOn: 'a')
+"""
+        when:
+        fails 'b'
+
+        then:
+        failure.assertHasDescription TextUtil.toPlatformLineSeparators("""Circular dependency between the following tasks:
+:a
+\\--- :b
+     \\--- :a (*)
+
+(*) - details omitted (listed previously)""")
+    }
+
+    def "placeolder actions not triggered when not requested"() {
+        when:
+        buildFile << """
+        task a
+        tasks.addPlaceholderAction("b") {
+            throw new RuntimeException()
+        }
+"""
+        then:
+        succeeds 'a'
+    }
+
+    def "explicit tasks are preferred over placeholder actions"() {
+        buildFile << """
+        task someTask << {println "explicit sometask"}
+        tasks.addPlaceholderAction("someTask"){
+            println  "placeholder action triggered"
+            task someTask << {println "placeholder sometask"}
+        }
+"""
+        when:
+        succeeds 'sometask'
+
+        then:
+        output.contains("explicit sometask")
+        !output.contains("placeholder action triggered")
+
+        when:
+        succeeds 'someT'
+
+        then:
+        output.contains("explicit sometask")
+        !output.contains("placeholder action triggered")
+    }
+
+
+    def "honours mustRunAfter task ordering"() {
+        buildFile << """
+    task a {
+        mustRunAfter 'b'
+    }
+    task b
+    task c(dependsOn: ['a', 'b'])
+    task d
+    c.mustRunAfter d
+
+"""
+        when:
+        succeeds 'c', 'd'
+
+        then:
+        result.assertTasksExecuted(':d', ':b', ':a', ':c')
+    }
+
+    def "finalizer task is executed if a finalized task is executed"() {
+        buildFile << """
+    task a
+    task b {
+        doLast {}
+        finalizedBy a
+    }
+"""
+        when:
+        succeeds 'b'
+
+        then:
+        ":a" in executedTasks
+    }
+
+    def "finalizer task is executed even if the finalised task fails"() {
+        buildFile << """
+    task a
+    task b  {
+        doLast { throw new RuntimeException() }
+        finalizedBy a
+    }
+"""
+        when:
+        fails 'b'
+
+        then:
+        ":a" in executedTasks
+    }
+
+    def "finalizer task is not executed if the finalized task does not run"() {
+        buildFile << """
+    task a {
+        doLast { throw new RuntimeException() }
+    }
+    task b
+    task c {
+        doLast {}
+        dependsOn a
+        finalizedBy b
+        onlyIf { false }
+    }
+"""
+        when:
+        fails 'c'
+
+        then:
+        !(":b" in executedTasks)
+    }
+
+    def "sensible error message for circular task dependency due to mustRunAfter"() {
+        buildFile << """
+    task a {
+        mustRunAfter 'b'
+    }
+    task b(dependsOn: 'a')
+"""
+        when:
+        fails 'b'
+
+        then:
+        failure.assertHasDescription TextUtil.toPlatformLineSeparators("""Circular dependency between the following tasks:
+:a
+\\--- :b
+     \\--- :a (*)
+
+(*) - details omitted (listed previously)""")
+    }
+
+    def "checked exceptions thrown by tasks are reported correctly"() {
+        buildFile << """
+            class SomeTask extends DefaultTask {
+
+                @TaskAction
+                void explode() {
+                    throw new Exception("I am the checked exception")
+                }
+            }
+
+            task explode(type: SomeTask) {
+
+            }
+        """
+
+        when:
+        fails "explode"
+
+        then:
+        failure.assertHasCause "java.lang.Exception: I am the checked exception"
+    }
+
+    def "honours shouldRunAfter task ordering"() {
+        buildFile << """
+    task a {
+        dependsOn 'b'
+    }
+    task b {
+        shouldRunAfter 'c'
+    }
+    task c
+    task d {
+        dependsOn 'c'
+    }
+"""
+        when:
+        succeeds 'a', 'd'
+
+        then:
+        executedTasks == [':c', ':b', ':a', ':d']
+    }
+
+    def "multiple should run after ordering can be ignored for one execution plan"() {
+        buildFile << """
+    task a {
+        dependsOn 'b', 'h'
+    }
+    task b {
+        dependsOn 'c'
+    }
+    task c {
+        dependsOn 'g'
+        shouldRunAfter 'd'
+    }
+    task d {
+        finalizedBy 'e'
+        dependsOn 'f'
+    }
+    task e
+    task f {
+        dependsOn 'c'
+    }
+    task g {
+        shouldRunAfter 'h'
+    }
+    task h {
+        dependsOn 'b'
+    }
+"""
+
+        when:
+        succeeds 'a', 'd'
+
+        then:
+        executedTasks == [':g', ':c', ':b', ':h', ':a', ':f', ':d', ':e']
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
deleted file mode 100644
index 35c532c..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests;
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.test.fixtures.file.TestFile;
-import org.junit.Test;
-import spock.lang.Issue;
-
-import static org.hamcrest.Matchers.startsWith;
-
-public class TaskExecutionIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void taskCanAccessTaskGraph() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "boolean notified = false",
-                "task a(dependsOn: 'b') << { task ->",
-                "    assert notified",
-                "    assert gradle.taskGraph.hasTask(task)",
-                "    assert gradle.taskGraph.hasTask(':a')",
-                "    assert gradle.taskGraph.hasTask(a)",
-                "    assert gradle.taskGraph.hasTask(':b')",
-                "    assert gradle.taskGraph.hasTask(b)",
-                "    assert gradle.taskGraph.allTasks.contains(task)",
-                "    assert gradle.taskGraph.allTasks.contains(tasks.getByName('b'))",
-                "}",
-                "task b",
-                "gradle.taskGraph.whenReady { graph ->",
-                "    assert graph.hasTask(':a')",
-                "    assert graph.hasTask(a)",
-                "    assert graph.hasTask(':b')",
-                "    assert graph.hasTask(b)",
-                "    assert graph.allTasks.contains(a)",
-                "    assert graph.allTasks.contains(b)",
-                "    notified = true",
-                "}");
-        usingBuildFile(buildFile).withTasks("a").run().assertTasksExecuted(":b", ":a");
-    }
-
-    @Test
-    public void executesAllTasksInASingleBuildAndEachTaskAtMostOnce() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "gradle.taskGraph.whenReady { assert !project.hasProperty('graphReady'); ext.graphReady = true }",
-                "task a << { task -> project.ext.executedA = task }",
-                "task b << { ",
-                "    assert a == project.executedA",
-                "    assert gradle.taskGraph.hasTask(':a')",
-                "}",
-                "task c(dependsOn: a)",
-                "task d(dependsOn: a)",
-                "task e(dependsOn: [a, d])");
-        usingBuildFile(buildFile).withTasks("a", "b").run().assertTasksExecuted(":a", ":b");
-        usingBuildFile(buildFile).withTasks("a", "a").run().assertTasksExecuted(":a");
-        usingBuildFile(buildFile).withTasks("c", "a").run().assertTasksExecuted(":a", ":c");
-        usingBuildFile(buildFile).withTasks("c", "e").run().assertTasksExecuted(":a", ":c", ":d", ":e");
-    }
-
-    @Test
-    public void executesMultiProjectsTasksInASingleBuildAndEachTaskAtMostOnce() {
-        testFile("settings.gradle").writelns("include 'child1', 'child2'");
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "task a",
-                "allprojects {",
-                "    task b",
-                "    task c(dependsOn: ['b', ':a'])",
-                "}");
-        usingBuildFile(buildFile).withTasks("a", "c").run().assertTasksExecuted(":a", ":b", ":c", ":child1:b",
-                ":child1:c", ":child2:b", ":child2:c");
-        usingBuildFile(buildFile).withTasks("b", ":child2:c").run().assertTasksExecuted(":b", ":child1:b", ":child2:b",
-                ":a", ":child2:c");
-    }
-
-    @Test
-    public void executesMultiProjectDefaultTasksInASingleBuildAndEachTaskAtMostOnce() {
-        testFile("settings.gradle").writelns("include 'child1', 'child2'");
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns("defaultTasks 'a', 'b'", "task a", "subprojects {", "    task a(dependsOn: ':a')",
-                "    task b(dependsOn: ':a')", "}");
-        usingBuildFile(buildFile).run().assertTasksExecuted(":a", ":child1:a", ":child2:a", ":child1:b", ":child2:b");
-    }
-
-    @Test
-    public void executesProjectDefaultTasksWhenNoneSpecified() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "task a",
-                "task b(dependsOn: a)",
-                "defaultTasks 'b'"
-        );
-        usingBuildFile(buildFile).withTasks().run().assertTasksExecuted(":a", ":b");
-    }
-    
-    @Test
-    public void doesNotExecuteTaskActionsWhenDryRunSpecified() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "task a << { fail() }",
-                "task b(dependsOn: a) << { fail() }",
-                "defaultTasks 'b'"
-        );
-
-        // project defaults
-        usingBuildFile(buildFile).withArguments("-m").run().assertTasksExecuted(":a", ":b");
-        // named tasks
-        usingBuildFile(buildFile).withArguments("-m").withTasks("b").run().assertTasksExecuted(":a", ":b");
-    }
-
-    @Test
-    public void executesTaskActionsInCorrectEnvironment() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                // An action attached to built-in task
-                "task a << { assert Thread.currentThread().contextClassLoader == getClass().classLoader }",
-                // An action defined by a custom task
-                "task b(type: CustomTask)",
-                "class CustomTask extends DefaultTask { @TaskAction def go() { assert Thread.currentThread().contextClassLoader == getClass().classLoader } } ",
-                // An action implementation
-                "task c; c.doLast new Action<Task>() { void execute(Task t) { assert Thread.currentThread().contextClassLoader == getClass().classLoader } }"
-        );
-
-        usingBuildFile(buildFile).withTasks("a", "b", "c").run();
-    }
-
-    @Test
-    public void excludesTasksWhenExcludePatternSpecified() {
-        testFile("settings.gradle").write("include 'sub'");
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "task a",
-                "task b(dependsOn: a)",
-                "task c(dependsOn: [a, b])",
-                "task d(dependsOn: c)",
-                "defaultTasks 'd'"
-        );
-        testFile("sub/build.gradle").writelns(
-                "task c",
-                "task d(dependsOn: c)"
-        );
-
-        // Exclude entire branch
-        usingBuildFile(buildFile).withTasks(":d").withArguments("-x", "c").run().assertTasksExecuted(":d");
-        // Exclude direct dependency
-        usingBuildFile(buildFile).withTasks(":d").withArguments("-x", "b").run().assertTasksExecuted(":a", ":c", ":d");
-        // Exclude using paths and multi-project
-        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "c").run().assertTasksExecuted(":d", ":sub:d");
-        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "sub:c").run().assertTasksExecuted(":a", ":b", ":c", ":d", ":sub:d");
-        usingBuildFile(buildFile).withTasks("d").withArguments("-x", ":sub:c").run().assertTasksExecuted(":a", ":b", ":c", ":d", ":sub:d");
-        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "d").run().assertTasksExecuted();
-        // Project defaults
-        usingBuildFile(buildFile).withArguments("-x", "b").run().assertTasksExecuted(":a", ":c", ":d", ":sub:c", ":sub:d");
-        // Unknown task
-        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "unknown").runWithFailure().assertThatDescription(startsWith("Task 'unknown' not found in root project"));
-    }
-
-    @Test
-    @Issue("http://issues.gradle.org/browse/GRADLE-2022")
-    public void tryingToInstantiateTaskDirectlyFailsWithGoodErrorMessage() {
-        usingBuildFile(testFile("build.gradle").write("new DefaultTask()")).
-        withTasks("tasks").
-        runWithFailure().
-        assertHasCause("Task of type 'org.gradle.api.DefaultTask' has been instantiated directly which is not supported");
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskSubclassingBinaryCompatibilityCrossVersionSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskSubclassingBinaryCompatibilityCrossVersionSpec.groovy
new file mode 100644
index 0000000..47b3514
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskSubclassingBinaryCompatibilityCrossVersionSpec.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.integtests
+
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.util.GradleVersion
+
+/**
+ * Tests that task classes compiled against earlier versions of Gradle are still compatible.
+ */
+ at TargetVersions('0.9-rc-3+')
+class TaskSubclassingBinaryCompatibilityCrossVersionSpec extends CrossVersionIntegrationSpec {
+    def "can use task subclass compiled using previous Gradle version"() {
+        given:
+        def taskClasses = [
+                "DefaultTask", "SourceTask", "ConventionTask",
+                "Copy", "Sync", "Zip", "Jar", "Tar", "War", "Ear"
+        ]
+
+        // Ear plugin added in m4.
+        if (previous.version < GradleVersion.version("1.0-milestone-4")) {
+            taskClasses.remove("Ear")
+        }
+
+        Map<String, String> subclasses = taskClasses.collectEntries { ["custom" + it, it] }
+
+        file("producer/build.gradle") << """
+            apply plugin: 'groovy'
+            dependencies {
+                ${previous.version < GradleVersion.version("1.4-rc-1") ? "groovy" : "compile" } localGroovy()
+                compile gradleApi()
+            }
+        """
+
+        file("producer/src/main/groovy/SomePlugin.groovy") << """
+            import org.gradle.api.Plugin
+            import org.gradle.api.Project
+            import org.gradle.api.DefaultTask
+            import org.gradle.api.tasks.*
+            import org.gradle.api.tasks.bundling.*
+            ${taskClasses.contains("Ear") ? "import org.gradle.plugins.ear.Ear" : ""}
+            import org.gradle.api.internal.ConventionTask
+
+            class SomePlugin implements Plugin<Project> {
+                void apply(Project p) { """ <<
+                subclasses.collect { "p.tasks.add('${it.key}', ${it.key.capitalize()})" }.join("\n") << """
+                }
+            }
+            """ <<
+
+                subclasses.collect {
+                    "class ${it.key.capitalize()} extends ${it.value} {}"
+                }.join("\n")
+
+        buildFile << """
+buildscript {
+    dependencies { classpath fileTree(dir: "producer/build/libs", include: '*.jar') }
+}
+
+apply plugin: SomePlugin
+"""
+
+        expect:
+        version previous withTasks 'assemble' inDirectory(file("producer")) run()
+        version current withTasks 'tasks' run()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
index 68b74d6..9b5f2cf 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
@@ -25,9 +25,6 @@ import org.junit.Test
 
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 class WaterProjectIntegrationTest {
     final static String NL = SystemProperties.lineSeparator
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
index d8acaa5..d183533 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
@@ -34,7 +34,7 @@ public class WebProjectIntegrationTest extends AbstractIntegrationTest {
     }
 
     @Test
-    public void canCustomiseArchiveNamesUsingConventionProperties() {
+    public void canCustomizeArchiveNamesUsingConventionProperties() {
         testFile("settings.gradle").writelns("rootProject.name = 'test'");
         TestFile buildFile = testFile("build.gradle");
         buildFile.writelns(
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 ab3ca6a..db712cb 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
@@ -25,9 +25,6 @@ import org.gradle.util.TextUtil
 import spock.lang.IgnoreIf
 import spock.lang.Issue
 
-/**
- * @author: Szczepan Faber, created at: 8/11/11
- */
 class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
     def "canonicalizes working directory"() {
         given:
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
index 517a83e..cc7544a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
@@ -21,9 +21,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 3/14/12
- */
 class TempDirIsUniquePerTestSpec extends Specification {
 
     @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider();
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
index 6f20307..68759d2 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
@@ -26,9 +26,6 @@ import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
 class LoggingIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
@@ -154,7 +151,7 @@ class LoggingIntegrationTest extends AbstractIntegrationTest {
     }}
 
     private final LogOutput brokenBuild = new LogOutput() {{
-        error('FAILURE: Could not determine which tasks to execute.')
+        error('FAILURE: Build failed with an exception.')
     }}
 
     @Test
@@ -247,39 +244,6 @@ class LoggingIntegrationTest extends AbstractIntegrationTest {
         ExecutionResult result = run.call(level)
         level.checkOuts(result)
     }
-
-    @Test
-    public void deprecatedLogging() {
-        LogLevel deprecated = new LogLevel(
-            args: [],
-            infoMessages: [['A deprecation warning']],
-            errorMessages: [],
-            allMessages: []
-        )
-
-        resources.maybeCopy('LoggingIntegrationTest/deprecated')
-        ExecutionResult result = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
-        deprecated.checkOuts(result)
-
-        // Ensure warnings are logged the second time
-        ExecutionResult secondResult = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
-        deprecated.checkOuts(secondResult)
-    }
-
-    @Test
-    public void deprecatedLoggingIsNotDisplayedWithQuietFlag() {
-        LogLevel deprecated = new LogLevel(
-            args: ['-q'],
-            infoMessages: [],
-            errorMessages: [],
-            allMessages: [['A deprecation warning']]
-        )
-
-        resources.maybeCopy('LoggingIntegrationTest/deprecated')
-        ExecutionResult result = executer.withArguments(deprecated.args).withTasks('log').run()
-
-        deprecated.checkOuts(result)
-    }
 }
 
 class LogLevel {
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
deleted file mode 100644
index 6719171..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,334 +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.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
-import org.gradle.test.fixtures.file.TestFile
-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.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 = new ProgressLoggingFixture(executer, temporaryFolder)
-
-    private IvyModule 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'
-version = '2'
-group = 'org.gradle'
-
-uploadArchives {
-    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 'uploadArchives'
-
-        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'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    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 'uploadArchives'
-
-        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'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    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 'uploadArchives'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \'archives\'')
-        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'
-uploadArchives {
-    repositories {
-        ivy {
-            url "${repositoryUrl}"
-        }
-    }
-}
-"""
-
-        when:
-        server.addBroken("/")
-
-        then:
-        fails 'uploadArchives'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \'archives\'')
-        failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
-
-        when:
-        server.stop()
-
-        then:
-        fails 'uploadArchives'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \'archives\'')
-        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'
-    version = '2'
-    group = 'org.gradle'
-    uploadArchives {
-        repositories {
-            ivy {
-                artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
-                artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
-                ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
-                ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
-            }
-        }
-    }
-    """
-
-        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 'uploadArchives'
-
-        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'
-version = '2'
-group = 'org.gradle'
-
-configurations {
-    tools
-}
-artifacts {
-    tools(file('$toolsJar')) {
-        name 'tools'
-    }
-}
-
-uploadTools {
-    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 'uploadTools'
-
-        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'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    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 'uploadArchives'
-
-        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/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
deleted file mode 100644
index 0de942a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.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'
-
-group = 'org.gradle.test'
-version = '1.9'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    compile "commons-collections:commons-collections:3.2.1"
-    runtime "commons-io:commons-io:1.4"
-}
-
-uploadArchives {
-    repositories {
-        ivy {
-            url '${ivyRepo.uri}'
-        }
-    }
-}
-"""
-
-        when:
-        run "uploadArchives"
-
-        then:
-        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
-        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.jar")
-
-        with (ivyModule.ivy) {
-            dependencies.size() == 2
-            dependencies["commons-collections:commons-collections:3.2.1"].hasConf("compile->default")
-            dependencies["commons-io:commons-io:1.4"].hasConf("runtime->default")
-        }
-    }
-}
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
deleted file mode 100644
index 26dbd40..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,116 +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.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.spockframework.util.TextUtil
-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'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    repositories {
-        ivy {
-            url "${ivyRepo.uri}"
-        }
-    }
-}
-"""
-        when:
-        succeeds 'uploadArchives'
-
-        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'
-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.
-        //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/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 6247e45..0995405 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
@@ -48,13 +48,18 @@ class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
             }
         }
         """
-        when:
 
+        and:
+        executer.withDeprecationChecksDisabled()
+
+        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")
@@ -81,6 +86,10 @@ class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
             }
         }
         """
+
+        and:
+        executer.withDeprecationChecksDisabled()
+
         when:
         fails "uploadArchives"
 
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
deleted file mode 100644
index fdaf12a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,128 +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.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-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"
-
-group = "org.gradle.test"
-version = 1.9
-
-configurations { publish }
-
-task jar1(type: Jar) {
-    baseName = "jar1"
-    from "file1"
-}
-
-task jar2(type: Jar) {
-    baseName = "jar2"
-    from "file2"
-}
-
-artifacts {
-    publish jar1, jar2
-}
-
-uploadPublish {
-    repositories {
-        ivy {
-            url "${ivyRepo.uri}"
-        }
-    }
-}
-        """
-
-        when:
-        run "uploadPublish"
-
-        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", "publish"]
-        ivyDescriptor.expectArtifact("jar2").conf == ["publish"]
-    }
-
-    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 {
-        ivy {
-            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/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
deleted file mode 100644
index 13cc507..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,37 +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.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.Sample
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationTest {
-
-    @Rule
-    public final Sample sample = new Sample(testDirectoryProvider, "ivypublish")
-
-    @Test
-    public void testPublish() {
-        // the actual testing is done in the build script.
-        File projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks("uploadArchives").run()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
deleted file mode 100644
index 68151a1..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,361 +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.publish.maven
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-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()
-
-    def "can publish a project with dependency in mapped and unmapped configuration"() {
-        given:
-        settingsFile << "rootProject.name = 'root'"
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'maven'
-group = 'group'
-version = '1.0'
-repositories { mavenCentral() }
-configurations { custom }
-dependencies {
-    custom 'commons-collections:commons-collections:3.2'
-    runtime 'commons-collections:commons-collections:3.2'
-}
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: "${mavenRepo.uri}")
-        }
-    }
-}
-"""
-        when:
-        succeeds 'uploadArchives'
-
-        then:
-        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'"
-        buildFile << """
-apply plugin: 'base'
-apply plugin: 'maven'
-
-group = 'group'
-version = 1.0
-
-task sourceJar(type: Jar) {
-    classifier = 'source'
-}
-artifacts {
-    archives sourceJar
-}
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: "${mavenRepo.uri}")
-        }
-    }
-}
-"""
-        when:
-        succeeds 'uploadArchives'
-
-        then:
-        def module = mavenRepo.module('group', 'root', 1.0)
-        module.assertArtifactsPublished('root-1.0-source.jar')
-    }
-
-    def "can publish a project with metadata artifacts"() {
-        given:
-        settingsFile << "rootProject.name = 'root'"
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'maven'
-group = 'group'
-version = 1.0
-
-task signature {
-    ext.destFile = file("\$buildDir/signature.sig")
-    doLast {
-        destFile.text = 'signature'
-    }
-}
-
-import org.gradle.api.artifacts.maven.MavenDeployment
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-
-artifacts {
-    archives new DefaultPublishArtifact(jar.baseName, "jar.sig", "jar.sig", null, new Date(), signature.destFile, signature)
-}
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri("${mavenRepo.uri}"))
-            beforeDeployment { MavenDeployment deployment ->
-                assert deployment.pomArtifact.file.isFile()
-                assert deployment.pomArtifact.name == 'root'
-                assert deployment.mainArtifact.file == jar.archivePath
-                assert deployment.mainArtifact.name == 'root'
-                assert deployment.artifacts.size() == 3
-                assert deployment.artifacts.contains(deployment.pomArtifact)
-                assert deployment.artifacts.contains(deployment.mainArtifact)
-
-                def pomSignature = file("\${buildDir}/pom.sig")
-                pomSignature.text = 'signature'
-                deployment.addArtifact new DefaultPublishArtifact(deployment.pomArtifact.name, "pom.sig", "pom.sig", null, new Date(), pomSignature)
-            }
-        }
-    }
-}
-"""
-        when:
-        succeeds 'uploadArchives'
-
-        then:
-        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')
-    }
-
-    def "can publish a snapshot version"() {
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'maven'
-
-group = 'org.gradle'
-version = '1.0-SNAPSHOT'
-archivesBaseName = 'test'
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            snapshotRepository(url: "${mavenRepo.uri}")
-        }
-    }
-}
-"""
-
-        when:
-        succeeds 'uploadArchives'
-
-        then:
-        def module = mavenRepo.module('org.gradle', 'test', '1.0-SNAPSHOT')
-        module.assertArtifactsPublished("test-${module.publishArtifactVersion}.jar", "test-${module.publishArtifactVersion}.pom")
-    }
-
-    def "can publish multiple deployments with attached artifacts"() {
-        given:
-        server.start()
-        settingsFile << "rootProject.name = 'someCoolProject'"
-        buildFile << """
-apply plugin:'java'
-apply plugin:'maven'
-
-version = 1.0
-group =  "org.test"
-
-task sourcesJar(type: Jar) {
-        from sourceSets.main.allSource
-        classifier = 'sources'
-}
-
-task testJar(type: Jar) {
-        baseName = project.name + '-tests'
-}
-
-task testSourcesJar(type: Jar) {
-        baseName = project.name + '-tests'
-        classifier = 'sources'
-}
-
-artifacts { archives sourcesJar, testJar, testSourcesJar }
-
-uploadArchives {
-    repositories{
-        mavenDeployer {
-            repository(url: "http://localhost:${server.port}/repo") {
-               authentication(userName: "testuser", password: "secret")
-            }
-            addFilter('main') {artifact, file ->
-                !artifact.name.endsWith("-tests")
-            }
-            addFilter('tests') {artifact, file ->
-                artifact.name.endsWith("-tests")
-            }
-        }
-    }
-}
-"""
-        when:
-        def module = mavenRepo.module('org.test', 'someCoolProject')
-        def moduleDir = module.moduleDir
-        moduleDir.mkdirs()
-
-        and:
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0.pom")
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0.jar")
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0-sources.jar")
-        server.expectGetMissing("/repo/org/test/someCoolProject/maven-metadata.xml")
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject", "maven-metadata.xml")
-
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0.pom")
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0.jar")
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0-sources.jar")
-        server.expectGetMissing("/repo/org/test/someCoolProject-tests/maven-metadata.xml")
-        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests", "maven-metadata.xml")
-
-        then:
-        succeeds 'uploadArchives'
-    }
-
-    def "can publish to an unauthenticated HTTP repository"() {
-        given:
-        server.start()
-        settingsFile << "rootProject.name = 'root'"
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'maven'
-group = 'org.test'
-version = '1.0'
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: "http://localhost:${server.port}/repo")
-        }
-    }
-}
-"""
-        when:
-        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")
-        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.jar")
-        server.expectGetMissing("/repo/org/test/root/maven-metadata.xml")
-        expectPublishArtifact(moduleDir, "/repo/org/test/root", "maven-metadata.xml")
-
-        then:
-        succeeds 'uploadArchives'
-
-        and:
-        module.assertArtifactsPublished('root-1.0.pom', 'root-1.0.jar', 'maven-metadata.xml')
-    }
-
-    private def expectPublishArtifact(def moduleDir, def path, def name) {
-        server.expectPut("$path/$name", moduleDir.file("$name"))
-        server.expectPut("$path/${name}.md5", moduleDir.file("${name}.md5"))
-        server.expectPut("$path/${name}.sha1", moduleDir.file("${name}.sha1"))
-    }
-
-    @Unroll
-    def "can publish to an authenticated HTTP repository using #authScheme auth"() {
-        given:
-        def username = 'testuser'
-        def password = 'password'
-        server.start()
-        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().version))
-        settingsFile << "rootProject.name = 'root'"
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'maven'
-group = 'org.test'
-version = '1.0'
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: "http://localhost:${server.port}/repo") {
-               authentication(userName: "${username}", password: "${password}")
-            }
-        }
-    }
-}
-"""
-        when:
-        server.authenticationScheme = authScheme
-
-        and:
-        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)
-        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.pom", username, password)
-        server.expectGetMissing("/repo/org/test/root/maven-metadata.xml")
-        expectPublishArtifact(moduleDir, "/repo/org/test/root", "maven-metadata.xml", username, password)
-
-        then:
-        succeeds 'uploadArchives'
-
-        and:
-        module.assertArtifactsPublished('root-1.0.pom', 'root-1.0.jar', 'maven-metadata.xml')
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-        // TODO: Does not work with DIGEST authentication
-    }
-
-    private def expectPublishArtifact(def moduleDir, def path, def name, def username, def password) {
-        server.expectPut("$path/$name", username, password, moduleDir.file("$name"))
-        server.expectPut("$path/${name}.md5", username, password, moduleDir.file("${name}.md5"))
-        server.expectPut("$path/${name}.sha1", username, password, moduleDir.file("${name}.sha1"))
-    }
-}
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
deleted file mode 100644
index 29598c4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.publish.maven
-
-import groovy.text.SimpleTemplateEngine
-import org.custommonkey.xmlunit.Diff
-import org.custommonkey.xmlunit.XMLAssert
-import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.internal.SystemProperties
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.util.Resources
-import org.hamcrest.Matchers
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-class SamplesMavenPomGenerationIntegrationTest extends AbstractIntegrationTest {
-    private TestFile pomProjectDir
-
-    @Rule public Resources resources = new Resources();
-    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'maven/pomGeneration')
-
-    @Before
-    void setUp() {
-        pomProjectDir = sample.dir
-    }
-    
-    @Test
-    void "can deploy to local repository"() {
-        def repo = maven(pomProjectDir.file('pomRepo'))
-        def module = repo.module('deployGroup', 'mywar', '1.0MVN')
-
-        executer.inDirectory(pomProjectDir).withTasks('uploadArchives').withArguments("--stacktrace").run()
-
-        compareXmlWithIgnoringOrder(expectedPom('1.0MVN', "deployGroup"), module.pomFile.text)
-        module.moduleDir.file("mywar-1.0MVN.war").assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
-
-        pomProjectDir.file('build').assertDoesNotExist()
-        pomProjectDir.file('target').assertIsDir()
-    }
-
-    @Test
-    void "can install to local repository"() {
-        def repo = maven(new TestFile("$SystemProperties.userHome/.m2/repository"))
-        def module = repo.module('installGroup', 'mywar', '1.0MVN')
-        module.moduleDir.deleteDir()
-
-        executer.inDirectory(pomProjectDir).withTasks('install').run()
-
-        pomProjectDir.file('build').assertDoesNotExist()
-        pomProjectDir.file('target').assertIsDir()
-
-        TestFile installedFile = module.moduleDir.file("mywar-1.0MVN.war")
-        TestFile installedJavadocFile = module.moduleDir.file("mywar-1.0MVN-javadoc.zip")
-        TestFile installedPom = module.moduleDir.file("mywar-1.0MVN.pom")
-
-        installedFile.assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
-        installedJavadocFile.assertIsCopyOf(pomProjectDir.file("target/distributions/mywar-1.0-javadoc.zip"))
-        installedPom.assertIsFile()
-
-        compareXmlWithIgnoringOrder(expectedPom("1.0MVN", "installGroup"), installedPom.text)
-    }
-
-    @Test
-    void writeNewPom() {
-        executer.inDirectory(pomProjectDir).withTasks('writeNewPom').run()
-        compareXmlWithIgnoringOrder(expectedPom(null, null, 'pomGeneration/expectedNewPom.txt'), pomProjectDir.file("target/newpom.xml").text)
-    }
-
-    @Test
-    void writeDeployerPom() {
-        String version = '1.0MVN'
-        String groupId = "deployGroup"
-        executer.inDirectory(pomProjectDir).withTasks('writeDeployerPom').run()
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomProjectDir.file("target/deployerpom.xml").text)
-    }
-    
-    private String expectedPom(String version, String groupId, String path = 'pomGeneration/expectedPom.txt') {
-        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
-        String text = resources.getResource(path).text
-        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
-    }
-
-    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
-        Diff diff = new Diff(expectedXml, actualXml)
-        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
-        XMLAssert.assertXMLEqual(diff, true);
-        Assert.assertThat(actualXml, Matchers.startsWith(String.format('<?xml version="1.0" encoding="UTF-8"?>')))
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
deleted file mode 100755
index f40ad61..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
+++ /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.integtests.publish.maven
-
-import groovy.text.SimpleTemplateEngine
-import org.custommonkey.xmlunit.Diff
-import org.custommonkey.xmlunit.XMLAssert
-import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.internal.SystemProperties
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.util.Resources
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-class SamplesMavenQuickstartIntegrationTest extends AbstractIntegrationTest {
-    @Rule public Resources resources = new Resources();
-    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'maven/quickstart')
-
-    private TestFile pomProjectDir
-
-    @Before
-    void setUp() {
-        pomProjectDir = sample.dir
-    }
-
-    @Test
-    void "can publish to a local repository"() {
-        executer.inDirectory(pomProjectDir).withTasks('uploadArchives').run()
-
-        def repo = 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)
-        module.moduleDir.file("quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
-    }
-
-    @Test
-    void "can install to local repository"() {
-        def repo = maven(new TestFile("$SystemProperties.userHome/.m2/repository"))
-        def module = repo.module('gradle', 'quickstart', '1.0')
-        module.moduleDir.deleteDir()
-
-        executer.inDirectory(pomProjectDir).withTasks('install').run()
-
-        module.moduleDir.file("quickstart-1.0.jar").assertIsFile()
-        module.moduleDir.file("quickstart-1.0.pom").assertIsFile()
-        module.moduleDir.file("quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
-
-        compareXmlWithIgnoringOrder(expectedPom('1.0', 'gradle'), module.pomFile.text)
-    }
-
-    private String expectedPom(String version, String groupId) {
-        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
-        String text = resources.getResource('pomGeneration/expectedQuickstartPom.txt').text
-        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
-    }
-
-    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
-        Diff diff = new Diff(expectedXml, actualXml)
-        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
-        XMLAssert.assertXMLEqual(diff, true);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy
index baf8110..b3633d3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.integtests.samples
 import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
 import org.junit.Test
 
-/**
- * @author Szczepan Faber, created at: 3/28/11
- */
 class AutoTestedSamplesCoreIntegrationTest extends AbstractAutoTestedSamplesTest {
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy
index 44e5f1b..e63ad62 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.integtests.samples
 import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
 import org.junit.Test
 
-/**
- * @author Szczepan Faber, created at: 3/28/11
- */
 class AutoTestedSamplesPluginsIntegrationTest extends AbstractAutoTestedSamplesTest {
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/JUnitSamplesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/JUnitSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..9e7dcf7
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/JUnitSamplesIntegrationTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+public class JUnitSamplesIntegrationTest extends AbstractIntegrationTest {
+
+    @Rule
+    public final Sample sample = new Sample(testDirectoryProvider, 'testing/junit')
+
+    @Test
+    public void categegoriesSample() {
+        TestFile projectDir = sample.dir.file("categories")
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
+
+        // Check tests have run
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.junit.CategorizedJUnitTest')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
index c0afe98..4c0786f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -34,7 +34,7 @@ class SamplesAntlrIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
+        def result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.GrammarTest')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
index e603b85..a743894 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
 class SamplesCodeQualityIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'codeQuality')
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
index de6acc3..f0910d7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
@@ -36,7 +36,7 @@ class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
         executer.inDirectory(producerDir).withTasks('check').run()
 
         then:
-        def result = new JUnitXmlTestExecutionResult(producerDir)
+        def result = new DefaultTestExecutionResult(producerDir)
         result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
index 365d1a8..5d9589f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
@@ -25,9 +25,6 @@ import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.not
 import static org.junit.Assert.assertThat
 
-/**
- * @author Hans Dockter
- */
 class SamplesExcludesAndClassifiersIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'userguide/artifacts/excludesAndClassifiers')
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
index a7407d9..873e2ef 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -33,7 +33,7 @@ class SamplesGroovyCustomizedLayoutIntegrationTest extends AbstractIntegrationTe
         executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(groovyProjectDir)
+        def result = new DefaultTestExecutionResult(groovyProjectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
index cf34f36..3536ccd 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
@@ -24,9 +24,6 @@ import org.junit.Test
 
 import static org.hamcrest.Matchers.containsString
 
-/**
- * @author Hans Dockter
- */
 class SamplesGroovyMultiProjectIntegrationTest extends AbstractIntegrationTest {
     static final String TEST_PROJECT_NAME = 'testproject'
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
index e5c2a28..2738b7f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -33,7 +33,7 @@ class SamplesGroovyQuickstartIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(groovyProjectDir)
+        def result = new DefaultTestExecutionResult(groovyProjectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJUnitIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJUnitIntegrationTest.groovy
new file mode 100644
index 0000000..432e832
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJUnitIntegrationTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+public class SamplesJUnitIntegrationTest extends AbstractIntegrationTest {
+
+    @Rule
+    public final Sample sample = new Sample(testDirectoryProvider, 'testing/junit')
+
+    @Test
+    public void categoriesSample() {
+        TestFile projectDir = sample.dir.file("categories")
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.junit.CategorizedJUnitTest')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
index 6313f15..2ab46d7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
@@ -17,16 +17,12 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
-
 class SamplesJavaBaseIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/base')
@@ -39,7 +35,7 @@ class SamplesJavaBaseIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir.file('test'))
+        def result = new DefaultTestExecutionResult(javaprojectDir.file('test'))
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
index 7424337..cb145b9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
@@ -17,16 +17,12 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
-
 class SamplesJavaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/customizedLayout')
@@ -39,7 +35,7 @@ class SamplesJavaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir)
+        def result = new DefaultTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
index aafa6f5..f90e45d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
@@ -19,7 +19,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
@@ -28,9 +28,6 @@ import org.junit.Test
 
 import static org.hamcrest.Matchers.containsString
 
-/**
- * @author Hans Dockter
- */
 class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
 
     static final String JAVA_PROJECT_NAME = 'java/multiproject'
@@ -64,7 +61,7 @@ class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
         TestFile buildSrcDir = javaprojectDir.file('buildSrc')
 
         buildSrcDir.file('build/libs/buildSrc.jar').assertIsFile()
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(buildSrcDir)
+        def result = new DefaultTestExecutionResult(buildSrcDir)
         result.assertTestClassesExecuted('org.gradle.buildsrc.BuildSrcClassTest')
     }
 
@@ -85,10 +82,10 @@ class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
         assertExists(javaprojectDir, WEBAPP_PATH, packagePrefix, WEBAPP_NAME, 'TestTest.class')
 
         // Check test results and report
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir.file(SHARED_NAME))
+        def result = new DefaultTestExecutionResult(javaprojectDir.file(SHARED_NAME))
         result.assertTestClassesExecuted('org.gradle.shared.PersonTest')
 
-        result = new JUnitXmlTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
+        result = new DefaultTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
         result.assertTestClassesExecuted('org.gradle.webservice.TestTestTest')
 
         // Check contents of shared jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
index da03b3c..cf075f6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
@@ -17,15 +17,12 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
 class SamplesJavaProjectWithIntTestsIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/withIntegrationTests')
@@ -38,7 +35,7 @@ class SamplesJavaProjectWithIntTestsIntegrationTest extends AbstractIntegrationT
         executer.inDirectory(javaprojectDir).withTasks('clean', 'integrationTest').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir)
+        def result = new DefaultTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonIntegrationTest')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
index c5cfe15..5963c21 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -28,9 +28,6 @@ import java.util.jar.Manifest
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
-/**
- * @author Hans Dockter
- */
 class SamplesJavaQuickstartIntegrationTest extends  AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/quickstart')
@@ -43,7 +40,7 @@ class SamplesJavaQuickstartIntegrationTest extends  AbstractIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir)
+        def result = new DefaultTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
index 41c20b4..ed60179 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -35,7 +35,7 @@ class SamplesMixedJavaAndGroovyIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
+        def result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
deleted file mode 100644
index 19af0b6..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.samples
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.test.fixtures.file.TestFile
-import org.junit.Rule
-import org.junit.Test
-
-import static org.hamcrest.Matchers.containsString
-
-class SamplesMixedJavaAndScalaIntegrationTest extends AbstractIntegrationTest {
-
-    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/mixedJavaAndScala')
-
-    @Test
-    public void canBuildJar() {
-        TestFile projectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
-
-        // Check contents of Jar
-        TestFile jarContents = file('jar')
-        projectDir.file("build/libs/mixedJavaAndScala-1.0.jar").unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/sample/Person.class',
-                'org/gradle/sample/impl/JavaPerson.class',
-                'org/gradle/sample/impl/PersonImpl.class',
-                'org/gradle/sample/impl/PersonList.class'
-        )
-    }
-
-    @Test
-    public void canBuildDocs() {
-        TestFile projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'scaladoc').run()
-
-        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
-        javadocsDir.file("index.html").assertIsFile()
-        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
-        javadocsDir.file("org/gradle/sample/Person.html").assertIsFile()
-        javadocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
-
-        TestFile scaladocsDir = projectDir.file("build/docs/scaladoc")
-        scaladocsDir.file("index.html").assertIsFile()
-        scaladocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
-        scaladocsDir.file("org/gradle/sample/impl/PersonImpl.html").assertIsFile()
-        scaladocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
-        scaladocsDir.file("org/gradle/sample/impl/PersonList.html").assertIsFile()
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
index 83a82d8..a95fef0 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
@@ -24,9 +24,6 @@ import org.junit.Test
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
-/**
- * @author Hans Dockter
- */
 class SamplesRepositoriesIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'userguide/artifacts/defineRepository')
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
deleted file mode 100644
index 069a7a7..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.samples
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.test.fixtures.file.TestFile
-import org.junit.Rule
-import org.junit.Test
-
-class SamplesScalaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
-
-    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/customizedLayout')
-
-    @Test
-    public void canBuildJar() {
-        TestFile projectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
-
-        // Check contents of Jar
-        TestFile jarContents = file('jar')
-        projectDir.file("build/libs/customizedLayout.jar").unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/sample/api/Person.class',
-                'org/gradle/sample/impl/PersonImpl.class'
-        )
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
deleted file mode 100644
index 9ff9711..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.samples
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.test.fixtures.file.TestFile
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static org.hamcrest.Matchers.containsString
-
-class SamplesScalaQuickstartIntegrationTest extends AbstractIntegrationTest {
-
-    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/quickstart')
-
-    private TestFile projectDir
-
-    @Before
-    void setUp() {
-        projectDir = sample.dir
-    }
-
-    @Test
-    public void canBuildJar() {
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
-
-        // Check contents of Jar
-        TestFile jarContents = file('jar')
-        projectDir.file("build/libs/quickstart.jar").unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/sample/api/Person.class',
-                'org/gradle/sample/impl/PersonImpl.class'
-        )
-    }
-
-    @Test
-    public void canBuildScalaDoc() {
-        executer.inDirectory(projectDir).withTasks('clean', 'scaladoc').run()
-
-        projectDir.file('build/docs/scaladoc/index.html').assertExists()
-        projectDir.file('build/docs/scaladoc/org/gradle/sample/api/Person.html').assertContents(containsString("Defines the interface for a person."))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
index 2ea1387..ca5c187 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
@@ -21,13 +21,10 @@ import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 
-/**
- * @author Hans Dockter
- */
 class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
-    static final String WEB_PROJECT_NAME = 'customised'
+    static final String WEB_PROJECT_NAME = 'customized'
 
-    @Rule public final Sample sample = new Sample(temporaryFolder, 'webApplication/customised')
+    @Rule public final Sample sample = new Sample(temporaryFolder, 'webApplication/customized')
 
     def "can build war"() {
         when:
@@ -36,7 +33,7 @@ class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
         
         then:
         TestFile tmpDir = file('unjar')
-        sample.dir.file("build/libs/customised-1.0.war").unzipTo(tmpDir)
+        sample.dir.file("build/libs/customized-1.0.war").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'root.txt',
                 'META-INF/MANIFEST.MF',
@@ -74,7 +71,7 @@ task runWarTest(dependsOn: jettyRunWar) << {
 }
 
 private void callServlet() {
-    URL url = new URL("http://localhost:\$httpPort/customised/hello")
+    URL url = new URL("http://localhost:\$httpPort/customized/hello")
     println url.text
     jettyStop.execute()
 }
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 03bdf3e..7e131e0 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
@@ -23,9 +23,6 @@ import org.junit.Rule
 import spock.lang.Timeout
 import spock.lang.Unroll
 
-/**
- * @author Hans Dockter
- */
 class SamplesWebQuickstartIntegrationTest extends AbstractIntegrationSpec {
     @Rule public final Sample sample = new Sample(temporaryFolder, 'webApplication/quickstart')
 
@@ -86,7 +83,7 @@ task sayHearthyGoodbye << {
 
         //starting jetty
         sample sample
-        def runJetty = executer.withTasks(jettyStartTask, "sayHearthyGoodbye").withArguments("-d").start()
+        def runJetty = executer.withTasks(jettyStartTask, "sayHearthyGoodbye").start()
 
         //jetty is started
         available("http://localhost:$httpPort/quickstart")
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
index 1950843..681f6fd 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
@@ -12,6 +12,7 @@ task execTask(type: Exec) {
     doLast {
         assert testFile.exists()
     }
+    assert delegate instanceof ExtensionAware
 }
 
 task execByMethod {
@@ -19,8 +20,9 @@ task execByMethod {
     ext.testFile = file("$buildDir/$name")
     doFirst {
         exec {
-            executable = Jvm.current().getJavaExecutable()
+            executable Jvm.current().getJavaExecutable()
             args '-cp', sourceSets.main.runtimeClasspath.asPath, 'org.gradle.TestMain', projectDir, testFile
+            assert !(delegate instanceof ExtensionAware)
         }
     }
     doLast {
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
index 1e3a688..9edead9 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
@@ -10,14 +10,16 @@ task javaexecTask(type: JavaExec, dependsOn: classes) {
     doLast {
         assert testFile.exists()
     }
+    assert delegate instanceof ExtensionAware
 }
 
 task javaexecByMethod(dependsOn: classes) {
     ext.testFile = file("$buildDir/$name")
     doFirst {
         javaexec {
+            assert !(delegate instanceof ExtensionAware)
             classpath(sourceSets.main.output.classesDir)
-            main = 'org.gradle.TestMain'
+            main 'org.gradle.TestMain'
             args projectDir, testFile
         }
     }
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/deprecated/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/deprecated/build.gradle
deleted file mode 100644
index 597f51c..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/deprecated/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-
-task log {
-    DeprecationLogger.nagUserWith("A deprecation warning")
-    doLast {
-        DeprecationLogger.nagUserWith("A deprecation warning")
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project1/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project1/build.gradle
index ca46ce9..1d34dc4 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project1/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project1/build.gradle
@@ -41,11 +41,9 @@ task logInfo {
 }
 // END SNIPPET task-capture-stdout
 
-task nestedBuildLog << {
-    def startParam = project.gradle.startParameter.newBuild()
-    startParam.currentDir = rootProject.file('nestedBuild')
-    startParam.taskNames = ['log']
-    GradleLauncher.newInstance(startParam).run().rethrowFailure()
+task nestedBuildLog(type: GradleBuild) {
+    startParameter.currentDir = rootProject.file('nestedBuild')
+    startParameter.taskNames = ['log']
 }
 
 task log(dependsOn: [logInfo, logLifecycle, nestedBuildLog]) << {
diff --git a/subprojects/internal-integ-testing/internal-integ-testing.gradle b/subprojects/internal-integ-testing/internal-integ-testing.gradle
index 61cd919..dff0de9 100644
--- a/subprojects/internal-integ-testing/internal-integ-testing.gradle
+++ b/subprojects/internal-integ-testing/internal-integ-testing.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
     compile project(":internalTesting")
     compile project(':cli')
     compile project(':launcher')
@@ -25,48 +25,33 @@ dependencies {
         dependency "org.jboss.netty:netty:3.2.4.Final"
     }
     compile "org.apache.sshd:sshd-core:0.6.0"
+    compile libraries.gson
 }
 
 useTestFixtures(sourceSet: 'main')
 useClassycle()
 
 task prepareVersionsInfo(type: PrepareVersionsInfo) {
-   url = "http://services.gradle.org/versions/all"
-   destDir = file("$buildDir/generated-resources/main")
-   destFileName = "all-released-versions.json"
-   offline = gradle.startParameter.offline
+    destFile = new File(generatedResourcesDir, "all-released-versions.properties")
+    versions = releasedVersions.allVersions
+    mostRecent = releasedVersions.mostRecentFinalRelease
 }
 
-sourceSets.main.output.dir prepareVersionsInfo.destDir, builtBy: prepareVersionsInfo
+sourceSets.main.output.dir generatedResourcesDir, builtBy: prepareVersionsInfo
 
 class PrepareVersionsInfo extends DefaultTask {
-   File destDir
-   String destFileName
-   String url
-   boolean offline
-
-   @TaskAction void prepareVersions() {
-       if (offline) {
-           logger.warn("Versions information will not be downloaded because --offline switch is used.\n"
-                   + "Without the version information certain integration tests may fail or use outdated version details.")
-           return
-       }
-       logger.info "Downloading the released versions from: $url"
-
-       def theUrl = "http://services.gradle.org/versions/all"
-       def json
-       try {
-           json = new URL(theUrl).text
-       } catch (UnknownHostException e) {
-           throw new GradleException("Unable to acquire versions info. I've tried this url: '$theUrl'.\n"
-                   + "If you don't have the network connection please run with '--offline' or exclude this task from execution via '-x'."
-                   , e)
-       }
-
-       def destFile = new File(destDir, destFileName)
-       assert destDir.mkdirs() || destDir.exists() : "Problems creating output directory for $name. Attempted to create $destDir"
-       destFile.text = json
+    @OutputFile File destFile
+    @Input String mostRecent
+    @Input List<String> versions
+
+    @TaskAction void prepareVersions() {
+        def properties = new Properties()
+        properties.mostRecent = mostRecent
+        properties.versions = versions.join(' ')
+        destFile.withOutputStream {
+            properties.store(it, "")
+        }
+    }
+}
 
-       logger.info "Saved released versions information in: $destFile"
-   }
-}
\ No newline at end of file
+apply from: "$rootDir/gradle/ideaTestSourcesWorkaround.gradle"
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
index 9d536a8..5f5dade 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
@@ -16,9 +16,6 @@
 
 package org.gradle.integtests.fixtures
 
-/**
- * @author Szczepan Faber, created at: 4/2/11
- */
 class AbstractAutoTestedSamplesTest extends AbstractIntegrationTest {
 
      def util = new AutoTestedSamplesUtil()
@@ -29,7 +26,7 @@ class AbstractAutoTestedSamplesTest extends AbstractIntegrationTest {
             def buildFile = testFile('build.gradle')
             buildFile.text = sample
 
-            usingBuildFile(buildFile).withQuietLogging().withTasks('help').withArguments("-s").run()
+            usingBuildFile(buildFile).withTasks('help').withArguments("-s").run()
         }
     }
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
index bbee5a9..6b61948 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
@@ -22,8 +22,6 @@ import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistributio
 import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.os.OperatingSystem;
-import org.gradle.test.fixtures.file.TestDirectoryProvider;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.GradleVersion;
 
@@ -40,26 +38,25 @@ import static org.gradle.util.CollectionUtils.*;
 public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestRunner {
 
     private static final String VERSIONS_SYSPROP_NAME = "org.gradle.integtest.versions";
-    private final TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider();
     protected final GradleDistribution current = new UnderDevelopmentGradleDistribution();
     protected final List<GradleDistribution> previous;
+    protected final boolean implicitVersion;
 
     protected AbstractCompatibilityTestRunner(Class<?> target) {
-        this(target, null);
+        this(target, System.getProperty(VERSIONS_SYSPROP_NAME, "latest"));
     }
 
-    protected AbstractCompatibilityTestRunner(Class<?> target, String versionStr) {
+    private AbstractCompatibilityTestRunner(Class<?> target, String versionStr) {
         super(target);
         validateTestName(target);
 
         previous = new ArrayList<GradleDistribution>();
-        if (versionStr == null) {
-            versionStr = System.getProperty(VERSIONS_SYSPROP_NAME, "latest");
-        }
         final ReleasedVersionDistributions previousVersions = new ReleasedVersionDistributions();
         if (versionStr.equals("latest")) {
             previous.add(previousVersions.getMostRecentFinalRelease());
+            implicitVersion = true;
         } else if (versionStr.equals("all")) {
+            implicitVersion = true;
             List<GradleDistribution> all = previousVersions.getAll();
             for (GradleDistribution previous : all) {
                 if (!previous.worksWith(Jvm.current())) {
@@ -73,14 +70,11 @@ public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestR
                 this.previous.add(previous);
             }
         } else if (versionStr.matches("^\\d.*$")) {
+            implicitVersion = false;
             String[] versions = versionStr.split(",");
             List<GradleVersion> gradleVersions = CollectionUtils.sort(collect(Arrays.asList(versions), new Transformer<GradleVersion, String>() {
                 public GradleVersion transform(String versionString) {
-                    GradleVersion gradleVersion = GradleVersion.version(versionString);
-                    if (!gradleVersion.isValid()) {
-                        throw new RuntimeException("Specified Gradle version '" + versionString + "' is not a valid Gradle version");
-                    }
-                    return gradleVersion;
+                    return GradleVersion.version(versionString);
                 }
             }), Collections.reverseOrder());
 
@@ -126,7 +120,7 @@ public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestR
         }
 
         @Override
-        protected boolean isEnabled() {
+        protected boolean isTestEnabled(TestDetails testDetails) {
             return false;
         }
 
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 2ee4618..d3cb29c 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
@@ -22,6 +22,7 @@ import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.test.fixtures.ivy.IvyFileRepository
 import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenLocalRepository
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -46,6 +47,11 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
         testDirectory.file('build.gradle')
     }
 
+    protected TestFile buildScript(String script) {
+        buildFile.text = script
+        buildFile
+    }
+
     protected TestFile getSettingsFile() {
         testDirectory.file('settings.gradle')
     }
@@ -55,6 +61,9 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
     }
 
     protected TestFile file(Object... path) {
+        if (path.length == 1 && path[0] instanceof TestFile) {
+            return path[0] as TestFile
+        }
         getTestDirectory().file(path);
     }
 
@@ -127,7 +136,26 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
             assert !skippedTasks.contains(it)
         }
     }
-    
+
+    protected void skipped(String... tasks) {
+        tasks.each {
+            assert it in executedTasks
+            assert skippedTasks.contains(it)
+        }
+    }
+
+    protected void notExecuted(String... tasks) {
+        tasks.each {
+            assert !(it in executedTasks)
+        }
+    }
+
+    protected void executed(String... tasks) {
+        tasks.each {
+            assert (it in executedTasks)
+        }
+    }
+
     protected void failureHasCause(String cause) {
         failure.assertHasCause(cause)
     }
@@ -158,6 +186,10 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
         return new MavenFileRepository(file(repo))
     }
 
+    public MavenLocalRepository mavenLocal(Object repo) {
+        return new MavenLocalRepository(file(repo))
+    }
+
     public MavenFileRepository getMavenRepo() {
         if (mavenRepo == null) {
             mavenRepo = new MavenFileRepository(file("maven-repo"))
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractMultiTestRunner.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractMultiTestRunner.java
index d02941f..15e43c2 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
@@ -15,11 +15,14 @@
  */
 package org.gradle.integtests.fixtures;
 
-import org.junit.internal.runners.ErrorReportingRunner;
+import org.gradle.api.Nullable;
+import org.gradle.internal.UncheckedException;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 import org.junit.runner.Runner;
-import org.junit.runner.manipulation.*;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.NoTestsRemainException;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
 import org.junit.runner.notification.RunNotifier;
@@ -28,15 +31,18 @@ import org.junit.runners.Suite;
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.RunnerBuilder;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
 import java.util.*;
 
 /**
  * A base class for those test runners which execute a test multiple times.
  */
-public abstract class AbstractMultiTestRunner extends Runner implements Filterable, Sortable {
+public abstract class AbstractMultiTestRunner extends Runner implements Filterable {
     protected final Class<?> target;
-    private Description description;
     private final List<Execution> executions = new ArrayList<Execution>();
+    private Description description;
+    private Description templateDescription;
 
     protected AbstractMultiTestRunner(Class<?> target) {
         this.target = target;
@@ -64,19 +70,17 @@ public abstract class AbstractMultiTestRunner extends Runner implements Filterab
         invalidateDescription();
     }
 
-    public void sort(Sorter sorter) {
-        initExecutions();
-        for (Execution execution : executions) {
-            execution.sort(sorter);
-        }
-        invalidateDescription();
-    }
-
     private void initExecutions() {
         if (executions.isEmpty()) {
+            try {
+                Runner descriptionProvider = createRunnerFor(Arrays.asList(target), Collections.<Filter>emptyList());
+                templateDescription = descriptionProvider.getDescription();
+            } catch (InitializationError initializationError) {
+                throw UncheckedException.throwAsUncheckedException(initializationError);
+            }
             createExecutions();
             for (Execution execution : executions) {
-                execution.init(target);
+                execution.init(target, templateDescription);
             }
         }
     }
@@ -93,6 +97,7 @@ public abstract class AbstractMultiTestRunner extends Runner implements Filterab
 
     private void invalidateDescription() {
         description = null;
+        templateDescription = null;
     }
 
     protected abstract void createExecutions();
@@ -101,106 +106,122 @@ public abstract class AbstractMultiTestRunner extends Runner implements Filterab
         executions.add(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>();
-
-        final void init(Class<?> target) {
-            this.target = target;
-            if (isEnabled()) {
-                List<? extends Class<?>> targetClasses = loadTargetClasses();
-                RunnerBuilder runnerBuilder = new RunnerBuilder() {
-                    @Override
-                    public Runner runnerForClass(Class<?> testClass) {
+    private static Runner createRunnerFor(List<? extends Class<?>> targetClasses, final List<Filter> filters) throws InitializationError {
+        RunnerBuilder runnerBuilder = new RunnerBuilder() {
+            @Override
+            public Runner runnerForClass(Class<?> testClass) throws Throwable {
+                for (Class<?> candidate = testClass; candidate != null; candidate = candidate.getSuperclass()) {
+                    RunWith runWith = candidate.getAnnotation(RunWith.class);
+                    if (runWith != null && !AbstractMultiTestRunner.class.isAssignableFrom(runWith.value())) {
                         try {
-                            for (Class<?> candidate = testClass; candidate != null; candidate = candidate.getSuperclass()) {
-                                RunWith runWith = candidate.getAnnotation(RunWith.class);
-                                if (runWith != null && !AbstractMultiTestRunner.class.isAssignableFrom(runWith.value())) {
-                                    try {
-                                        return (Runner)runWith.value().getConstructors()[0].newInstance(testClass);
-                                    } catch (Exception e) {
-                                        return new ErrorReportingRunner(testClass, e);
-                                    }
-                                }
-                            }
-                            return new BlockJUnit4ClassRunner(testClass);
-                        } catch (InitializationError initializationError) {
-                            return new ErrorReportingRunner(testClass, initializationError);
+                            Runner r = (Runner) runWith.value().getConstructors()[0].newInstance(testClass);
+                            return filter(r);
+                        } catch (InvocationTargetException e) {
+                            throw e.getTargetException();
                         }
                     }
-                };
-                try {
-                    runner = new Suite(runnerBuilder, targetClasses.toArray(new Class<?>[targetClasses.size()]));
-                } catch (InitializationError initializationError) {
-                    runner = new ErrorReportingRunner(target, initializationError);
                 }
+                return filter(new BlockJUnit4ClassRunner(testClass));
+            }
+
+            //we need to filter at the level child runners because the suite is not doing the right thing here
+            private Runner filter(Runner r) {
+                for (Filter filter : filters) {
+                    try {
+                        ((Filterable)r).filter(filter);
+                    } catch (NoTestsRemainException e) {
+                        //ignore
+                    }
+                }
+                return r;
             }
+        };
+        return new Suite(runnerBuilder, targetClasses.toArray(new Class<?>[targetClasses.size()]));
+    }
+
+    protected static abstract class Execution implements Filterable {
+        protected Class<?> target;
+        private Description templateDescription;
+        private final Map<Description, Description> descriptionTranslations = new HashMap<Description, Description>();
+        private final Set<Description> enabledTests = new LinkedHashSet<Description>();
+        private final Set<Description> disabledTests = new LinkedHashSet<Description>();
+        private final List<Filter> filters = new LinkedList<Filter>();
+
+        final void init(Class<?> target, Description templateDescription) {
+            this.target = target;
+            this.templateDescription = templateDescription;
+        }
+
+        private Runner createExecutionRunner() throws InitializationError {
+            List<? extends Class<?>> targetClasses = loadTargetClasses();
+            return createRunnerFor(targetClasses, filters);
         }
 
         final void addDescriptions(Description parent) {
-            if (runner != null) {
-                map(runner.getDescription(), parent);
-            }
+            map(templateDescription, parent);
         }
 
         final void run(final RunNotifier notifier) {
-            if (runner == null) {
-                Description description = Description.createSuiteDescription(String.format("%s(%s)", getDisplayName(), target.getName()));
-                notifier.fireTestIgnored(description);
-                return;
-            }
-
             RunNotifier nested = new RunNotifier();
-            nested.addListener(new RunListener() {
-                @Override
-                public void testStarted(Description description) {
-                    Description translated = descriptionTranslations.get(description);
-                    notifier.fireTestStarted(translated);
-                }
+            NestedRunListener nestedListener = new NestedRunListener(notifier);
+            nested.addListener(nestedListener);
 
-                @Override
-                public void testFailure(Failure failure) {
-                    Description translated = descriptionTranslations.get(failure.getDescription());
-                    notifier.fireTestFailure(new Failure(translated, failure.getException()));
-                }
+            try {
+                runEnabledTests(nested);
+            } finally {
+                nestedListener.cleanup();
+            }
 
-                @Override
-                public void testAssumptionFailure(Failure failure) {
-                    Description translated = descriptionTranslations.get(failure.getDescription());
-                    notifier.fireTestAssumptionFailed(new Failure(translated, failure.getException()));
-                }
+            for (Description disabledTest : disabledTests) {
+                nested.fireTestStarted(disabledTest);
+                nested.fireTestIgnored(disabledTest);
+            }
+        }
 
-                @Override
-                public void testIgnored(Description description) {
-                    Description translated = descriptionTranslations.get(description);
-                    notifier.fireTestIgnored(translated);
-                }
+        private void runEnabledTests(RunNotifier nested) {
+            if (enabledTests.isEmpty()) {
+                return;
+            }
 
-                @Override
-                public void testFinished(Description description) {
-                    Description translated = descriptionTranslations.get(description);
-                    notifier.fireTestFinished(translated);
-                }
-            });
+            Runner runner;
+            try {
+                runner = createExecutionRunner();
+            } catch (Throwable t) {
+                runner = new CannotExecuteRunner(getDisplayName(), target, t);
+            }
 
-            before();
             try {
-                runner.run(nested);
-            } finally {
-                after();
+                if (!disabledTests.isEmpty()) {
+                    ((Filterable) runner).filter(new Filter() {
+                        @Override
+                        public boolean shouldRun(Description description) {
+                            return !disabledTests.contains(description);
+                        }
+
+                        @Override
+                        public String describe() {
+                            return "disabled tests";
+                        }
+                    });
+                }
+            } catch (NoTestsRemainException e) {
+                return;
             }
+
+            runner.run(nested);
         }
 
-        public void filter(Filter filter) throws NoTestsRemainException {
-            if (runner instanceof Filterable) {
-                ((Filterable) runner).filter(filter);
-            }
+        private Description translateDescription(Description description) {
+            return descriptionTranslations.containsKey(description) ? descriptionTranslations.get(description) : description;
         }
 
-        public void sort(Sorter sorter) {
-            if (runner instanceof Sortable) {
-                ((Sortable) runner).sort(sorter);
+        public void filter(Filter filter) throws NoTestsRemainException {
+            filters.add(filter);
+            for (Map.Entry<Description, Description> entry : descriptionTranslations.entrySet()) {
+                if (!filter.shouldRun(entry.getKey())) {
+                    enabledTests.remove(entry.getValue());
+                    disabledTests.remove(entry.getValue());
+                }
             }
         }
 
@@ -213,9 +234,14 @@ public abstract class AbstractMultiTestRunner extends Runner implements Filterab
         private void map(Description source, Description parent) {
             for (Description child : source.getChildren()) {
                 Description mappedChild;
-                if (child.getMethodName()!= null) {
+                if (child.getMethodName() != null) {
                     mappedChild = Description.createSuiteDescription(String.format("%s [%s](%s)", child.getMethodName(), getDisplayName(), child.getClassName()));
                     parent.addChild(mappedChild);
+                    if (!isTestEnabled(new TestDescriptionBackedTestDetails(source, child))) {
+                        disabledTests.add(child);
+                    } else {
+                        enabledTests.add(child);
+                    }
                 } else {
                     mappedChild = Description.createSuiteDescription(child.getClassName());
                 }
@@ -230,17 +256,134 @@ public abstract class AbstractMultiTestRunner extends Runner implements Filterab
         protected abstract String getDisplayName();
 
         /**
-         * Returns true if this execution should be executed, false if it should be ignored. Default is true.
+         * Returns true if the given test should be executed, false if it should be ignored. Default is true.
          */
-        protected boolean isEnabled() {
+        protected boolean isTestEnabled(TestDetails testDetails) {
             return true;
         }
 
         /**
+         * Checks that this execution can be executed, throwing an exception if not.
+         */
+        protected void assertCanExecute() {
+        }
+
+        /**
          * Loads the target classes for this execution. Default is the target class that this runner was constructed with.
          */
         protected List<? extends Class<?>> loadTargetClasses() {
             return Collections.singletonList(target);
         }
+
+        private static class CannotExecuteRunner extends Runner {
+            private final Description description;
+            private final Throwable failure;
+
+            public CannotExecuteRunner(String displayName, Class<?> testClass, Throwable failure) {
+                description = Description.createSuiteDescription(String.format("%s(%s)", displayName, testClass.getName()));
+                this.failure = failure;
+            }
+
+            @Override
+            public Description getDescription() {
+                return description;
+            }
+
+            @Override
+            public void run(RunNotifier notifier) {
+                Description description = getDescription();
+                notifier.fireTestStarted(description);
+                notifier.fireTestFailure(new Failure(description, failure));
+                notifier.fireTestFinished(description);
+            }
+        }
+
+        private class NestedRunListener extends RunListener {
+            private final RunNotifier notifier;
+            boolean started;
+            boolean complete;
+
+            public NestedRunListener(RunNotifier notifier) {
+                this.notifier = notifier;
+            }
+
+            @Override
+            public void testStarted(Description description) {
+                Description translated = translateDescription(description);
+                notifier.fireTestStarted(translated);
+                if (!started && !complete) {
+                    try {
+                        assertCanExecute();
+                        started = true;
+                        before();
+                    } catch (Throwable t) {
+                        notifier.fireTestFailure(new Failure(translated, t));
+                    }
+                }
+            }
+
+            @Override
+            public void testFailure(Failure failure) {
+                Description translated = translateDescription(failure.getDescription());
+                notifier.fireTestFailure(new Failure(translated, failure.getException()));
+            }
+
+            @Override
+            public void testAssumptionFailure(Failure failure) {
+                Description translated = translateDescription(failure.getDescription());
+                notifier.fireTestAssumptionFailed(new Failure(translated, failure.getException()));
+            }
+
+            @Override
+            public void testIgnored(Description description) {
+                Description translated = translateDescription(description);
+                notifier.fireTestIgnored(translated);
+            }
+
+            @Override
+            public void testFinished(Description description) {
+                Description translated = translateDescription(description);
+                notifier.fireTestFinished(translated);
+            }
+
+            public void cleanup() {
+                if (started) {
+                    after();
+                }
+                // Prevent further tests (ignored) from triggering start actions
+                complete = true;
+            }
+        }
+    }
+
+    public interface TestDetails {
+        /**
+         * Locates the given annotation for the test. May be inherited from test class.
+         */
+        @Nullable
+        <A extends Annotation> A getAnnotation(Class<A> type);
+    }
+
+    private static class TestDescriptionBackedTestDetails implements TestDetails {
+        private final Description parent;
+        private final Description test;
+
+        private TestDescriptionBackedTestDetails(Description parent, Description test) {
+            this.parent = parent;
+            this.test = test;
+        }
+
+        @Override
+        public String toString() {
+            return test.toString();
+        }
+
+        public <A extends Annotation> A getAnnotation(Class<A> type) {
+            A annotation = test.getAnnotation(type);
+            if (annotation != null) {
+                return annotation;
+            }
+            return parent.getAnnotation(type);
+        }
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
index 7edd8fb..3f8e99e 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
@@ -18,9 +18,6 @@ package org.gradle.integtests.fixtures
 
 import org.gradle.internal.SystemProperties
 
-/**
- * @author: Szczepan Faber, created at: 3/28/11
- */
 class AutoTestedSamplesUtil {
 
     String includes = '**/*.groovy **/*.java'
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
index 8d0308f..f72b871 100755
--- 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
@@ -15,12 +15,14 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.internal.jvm.Jre;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -51,12 +53,15 @@ abstract public class AvailableJavaHomes {
         }
 
         if (OperatingSystem.current().isMacOsX()) {
-            File registeredJvms = new File("/Library/Java/JavaVirtualMachines");
-            if (registeredJvms.isDirectory()) {
-                for (File candidate : registeredJvms.listFiles()) {
-                    javaHome = GFileUtils.canonicalise(new File(candidate, "Contents/Home"));
-                    if (!javaHome.equals(jvm.getJavaHome()) && javaHome.isDirectory() && new File(javaHome, "bin/java").isFile()) {
-                        return javaHome;
+            // Search in the install dir used by the Apple jvms, followed by the install dir used by the OpenJDK jvms
+            List<File> installDirs = Arrays.asList(new File("/System/Library/Java/JavaVirtualMachines"), new File("/Library/Java/JavaVirtualMachines"));
+            for (File installDir : installDirs) {
+                if (installDir.isDirectory()) {
+                    for (File candidate : installDir.listFiles()) {
+                        javaHome = GFileUtils.canonicalise(new File(candidate, "Contents/Home"));
+                        if (!javaHome.equals(jvm.getJavaHome()) && javaHome.isDirectory() && new File(javaHome, "bin/java").isFile()) {
+                            return javaHome;
+                        }
                     }
                 }
             }
@@ -105,25 +110,13 @@ abstract public class AvailableJavaHomes {
      */
     public static File getBestJre() {
         Jvm jvm = Jvm.current();
-        File 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;
-                }
-            }
-            if (jvm.getJavaVersion().isJava7()) {
-                jreHome = new File(jvm.getJavaHome().getParentFile(), "jre7");
-                if (jreHome.isDirectory() && new File(jreHome, "bin/java.exe").isFile()) {
-                    return jreHome;
-                }
-            }
+        Jre jre = jvm.getStandaloneJre();
+        if (jre != null) {
+            return jre.getHomeDir();
         }
-        jreHome = new File(jvm.getJavaHome(), "jre");
-        if (jreHome.isDirectory()) {
-            return jreHome;
+        jre = jvm.getJre();
+        if (jre != null) {
+            return jre.getHomeDir();
         }
         return null;
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
index 1839598..c267285 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
@@ -91,7 +91,7 @@ class CrossVersionTestRunner extends AbstractCompatibilityTestRunner {
         }
 
         @Override
-        protected boolean isEnabled() {
+        protected boolean isTestEnabled(AbstractMultiTestRunner.TestDetails testDetails) {
             return enabled
         }
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java
index 8ce0ca2..9b9abfb 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java
@@ -16,9 +16,6 @@
 
 package org.gradle.integtests.fixtures;
 
-/**
- * @author Szczepan Faber, @date: 25.03.11
- */
 public class IntegrationTestHint extends RuntimeException {
 
     public IntegrationTestHint(Throwable cause) {
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
index 1a2fb0f..87c9759 100644
--- 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
@@ -19,9 +19,6 @@ 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
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy
index c04872d..0d22fe2 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.fixtures
 
 /**
- * Runs the target test class against the versions specified in a {@link TargetVersions}
+ * Runs the target test class against the versions specified in a {@link TargetVersions} or {@link TargetCoverage}
  */
 class MultiVersionSpecRunner extends AbstractMultiTestRunner {
     MultiVersionSpecRunner(Class<?> target) {
@@ -27,10 +27,14 @@ class MultiVersionSpecRunner extends AbstractMultiTestRunner {
     @Override
     protected void createExecutions() {
         def versions = target.getAnnotation(TargetVersions)
-        if (versions == null) {
-            throw new RuntimeException("Target class '$target' is not annotated with @${TargetVersions.simpleName}.")
+        def coverage = target.getAnnotation(TargetCoverage)
+        if (versions != null) {
+            versions.value().each { add(new VersionExecution(it)) }
+        } else if (coverage != null) {
+            coverage.value().newInstance(target, target).call().each { add(new VersionExecution(it)) }
+        } else {
+            throw new RuntimeException("Target class '$target' is not annotated with @${TargetVersions.simpleName} nor with @${TargetCoverage.simpleName}.")
         }
-        versions.value().each { add(new VersionExecution(it)) }
     }
 
     private static class VersionExecution extends AbstractMultiTestRunner.Execution {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TargetCoverage.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TargetCoverage.java
new file mode 100644
index 0000000..e070b1d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TargetCoverage.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.integtests.fixtures;
+
+import groovy.lang.Closure;
+
+import java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+/**
+ * Similar to {@link TargetVersions} but accepts a closure and hence allows to manage versions more flexibly.
+ */
+public @interface TargetCoverage {
+    Class<Closure> value();
+}
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 0772dca..d57594b 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
@@ -20,16 +20,15 @@ import groovy.util.slurpersupport.GPathResult
 import org.gradle.test.fixtures.file.TestFile
 import org.hamcrest.Matcher
 
-/**
- * by Szczepan Faber, created at: 11/3/11
- */
 class TestNGExecutionResult implements TestExecutionResult {
     private final TestFile projectDir
     private GPathResult resultsXml
     public static final String DEFAULT_TESTNG_REPORT = "build/reports/tests"
+    private final String outputDirectory
 
-    def TestNGExecutionResult(projectDir) {
-        this.projectDir = projectDir;
+    TestNGExecutionResult(projectDir, String outputDirectory = DEFAULT_TESTNG_REPORT) {
+        this.projectDir = projectDir
+        this.outputDirectory = outputDirectory
     }
 
     boolean hasTestNGXmlResults() {
@@ -37,7 +36,7 @@ class TestNGExecutionResult implements TestExecutionResult {
     }
 
     boolean hasJUnitResultsGeneratedByTestNG() {
-        def dir = projectDir.file("$DEFAULT_TESTNG_REPORT/junitreports")
+        def dir = projectDir.file("$outputDirectory/junitreports")
         dir.isDirectory() && dir.list().length > 0
     }
 
@@ -49,17 +48,17 @@ class TestNGExecutionResult implements TestExecutionResult {
         parseResults()
         htmlReportFile().assertIsFile()
         def actualTestClasses = findTestClasses().keySet()
-        org.junit.Assert.assertThat(actualTestClasses, org.hamcrest.Matchers.equalTo(testClasses as Set))
+        assert actualTestClasses == testClasses as Set
         this
     }
 
     private TestFile htmlReportFile() {
-        projectDir.file('build/reports/tests/index.html')
+        projectDir.file("$outputDirectory/index.html")
     }
 
     TestClassExecutionResult testClass(String testClass) {
         parseResults()
-        return new org.gradle.integtests.fixtures.TestNgTestClassExecutionResult(testClass, findTestClass(testClass))
+        return new TestNgTestClassExecutionResult(testClass, findTestClass(testClass))
     }
 
     private void parseResults() {
@@ -67,13 +66,13 @@ class TestNGExecutionResult implements TestExecutionResult {
     }
 
     private TestFile xmlReportFile() {
-        projectDir.file("$DEFAULT_TESTNG_REPORT/testng-results.xml")
+        projectDir.file("$outputDirectory/testng-results.xml")
     }
 
     private def findTestClass(String testClass) {
         def testClasses = findTestClasses()
         if (!testClasses.containsKey(testClass)) {
-            org.junit.Assert.fail("Could not find test class ${testClass}. Found ${testClasses.keySet()}")
+            throw new AssertionError("Could not find test class ${testClass}. Found ${testClasses.keySet()}")
         }
         testClasses[testClass]
     }
@@ -102,13 +101,13 @@ private class TestNgTestClassExecutionResult implements TestClassExecutionResult
 
     TestClassExecutionResult assertTestsExecuted(String... testNames) {
         def actualTestMethods = findTestMethods().keySet()
-        org.junit.Assert.assertThat(actualTestMethods, org.hamcrest.Matchers.equalTo(testNames as Set))
+        assert actualTestMethods == testNames as Set
         this
     }
 
     TestClassExecutionResult assertTestPassed(String name) {
         def testMethodNode = findTestMethod(name)
-        org.junit.Assert.assertEquals('PASS', testMethodNode. at status as String)
+        assert testMethodNode. at status as String == 'PASS'
         this
     }
 
@@ -118,19 +117,19 @@ private class TestNgTestClassExecutionResult implements TestClassExecutionResult
 
     TestClassExecutionResult assertTestSkipped(String name) {
         def testMethodNode = findTestMethod(name)
-        org.junit.Assert.assertEquals('SKIP', testMethodNode. at status as String)
+        assert testMethodNode. at status as String == 'SKIP'
         this
     }
 
     TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
         def testMethodNode = findTestMethod(name)
-        org.junit.Assert.assertEquals('FAIL', testMethodNode. at status as String)
+        assert testMethodNode. at status as String == 'FAIL'
 
         def exceptions = testMethodNode.exception
-        org.junit.Assert.assertThat(exceptions.size(), org.hamcrest.Matchers.equalTo(messageMatchers.length))
+        assert exceptions.size() == messageMatchers.length
 
         for (int i = 0; i < messageMatchers.length; i++) {
-            org.junit.Assert.assertThat(exceptions[i].message[0].text().trim(), messageMatchers[i])
+            assert messageMatchers[i].matches(exceptions[i].message[0].text().trim())
         }
         this
     }
@@ -139,26 +138,34 @@ private class TestNgTestClassExecutionResult implements TestClassExecutionResult
         throw new UnsupportedOperationException();
     }
 
+    TestClassExecutionResult assertTestCaseStdout(String testCaseName, Matcher<? super String> matcher) {
+        throw new UnsupportedOperationException();
+    }
+
     TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
         throw new UnsupportedOperationException();
     }
 
+    TestClassExecutionResult assertTestCaseStderr(String testCaseName, Matcher<? super String> matcher) {
+        throw new UnsupportedOperationException();
+    }
+
     TestClassExecutionResult assertConfigMethodPassed(String name) {
         def testMethodNode = findConfigMethod(name)
-        org.junit.Assert.assertEquals('PASS', testMethodNode. at status as String)
+        assert testMethodNode. at status as String == 'PASS'
         this
     }
 
     TestClassExecutionResult assertConfigMethodFailed(String name) {
         def testMethodNode = findConfigMethod(name)
-        org.junit.Assert.assertEquals('FAIL', testMethodNode. at status as String)
+        assert testMethodNode. at status as String == 'FAIL'
         this
     }
 
     private def findConfigMethod(String testName) {
         def testMethods = findConfigMethods()
         if (!testMethods.containsKey(testName)) {
-            org.junit.Assert.fail("Could not find configuration method ${testClass}.${testName}. Found ${testMethods.keySet()}")
+            throw new AssertionError("Could not find configuration method ${testClass}.${testName}. Found ${testMethods.keySet()}")
         }
         testMethods[testName]
     }
@@ -174,7 +181,7 @@ private class TestNgTestClassExecutionResult implements TestClassExecutionResult
     private def findTestMethod(String testName) {
         def testMethods = findTestMethods()
         if (!testMethods.containsKey(testName)) {
-            org.junit.Assert.fail("Could not find test ${testClass}.${testName}. Found ${testMethods.keySet()}")
+            throw new AssertionError("Could not find test ${testClass}.${testName}. Found ${testMethods.keySet()}")
         }
         testMethods[testName]
     }
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 c7fe692..f39943b 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
@@ -99,6 +99,9 @@ class UserGuideSamplesRunner extends Runner {
             try {
                 cleanup(sampleRun)
                 for (run in sampleRun.runs) {
+                    if (run.brokenForParallel && GradleContextualExecuter.parallel) {
+                        continue
+                    }
                     runSample(run)
                 }
             } catch (Throwable t) {
@@ -130,7 +133,7 @@ class UserGuideSamplesRunner extends Runner {
                 def expectedResult = replaceWithPlatformNewLines(buildContext.userGuideOutputDir.file(run.outputFile).text)
                 expectedResult = replaceWithRealSamplesDir(expectedResult)
                 try {
-                    result.assertOutputEquals(expectedResult, run.ignoreExtraLines)
+                    result.assertOutputEquals(expectedResult, run.ignoreExtraLines, run.ignoreLineOrder)
                 } catch (AssertionError e) {
                     println 'Expected Result:'
                     println expectedResult
@@ -184,12 +187,16 @@ class UserGuideSamplesRunner extends Runner {
             def args = sample.'@args'
             def outputFile = sample.'@outputFile'
             boolean ignoreExtraLines = Boolean.valueOf(sample.'@ignoreExtraLines')
+            boolean ignoreLineOrder = Boolean.valueOf(sample.'@ignoreLineOrder')
+            boolean expectFailure = Boolean.valueOf(sample.'@expectFailure')
 
             def run = new GradleRun(id: id)
             run.subDir = dir
             run.args = args ? args.split('\\s+') as List : []
             run.outputFile = outputFile
             run.ignoreExtraLines = ignoreExtraLines as boolean
+            run.ignoreLineOrder = ignoreLineOrder as boolean
+            run.expectFailure = expectFailure as boolean
 
             sample.file.each { file -> run.files << file.'@path' }
             sample.dir.each { file -> run.dirs << file.'@path' }
@@ -201,6 +208,9 @@ class UserGuideSamplesRunner extends Runner {
         samplesByDir.get('userguide/tutorial/properties').each { it.envs['ORG_GRADLE_PROJECT_envProjectProp'] = 'envPropertyValue' }
         samplesByDir.get('userguide/buildlifecycle/taskExecutionEvents')*.expectFailure = true
         samplesByDir.get('userguide/buildlifecycle/buildProjectEvaluateEvents')*.expectFailure = true
+        samplesByDir.get('userguide/tasks/finalizersWithFailure')*.expectFailure = true
+        samplesByDir.get('userguide/multiproject/dependencies/firstMessages/messages')*.brokenForParallel = true
+        samplesByDir.get('userguide/multiproject/dependencies/messagesHack/messages')*.brokenForParallel = true
 
         Map<String, SampleRun> samplesById = new TreeMap<String, SampleRun>()
 
@@ -241,6 +251,8 @@ Please run 'gradle docs:userguideDocbook' first"""
         String outputFile
         boolean expectFailure
         boolean ignoreExtraLines
+        boolean ignoreLineOrder
+        boolean brokenForParallel
         List files = []
         List dirs = []
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java
index 8f761a4..28e7093 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java
@@ -18,6 +18,7 @@ package org.gradle.integtests.fixtures.executer;
 import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.initialization.DefaultClassLoaderScope;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.jvm.Jvm;
@@ -36,13 +37,14 @@ import java.nio.charset.Charset;
 import java.util.*;
 
 import static java.util.Arrays.asList;
-import static org.gradle.util.Matchers.*;
+import static org.gradle.util.Matchers.containsLine;
+import static org.gradle.util.Matchers.matchesRegexp;
 
 public abstract class AbstractGradleExecuter implements GradleExecuter {
 
     private final Logger logger;
 
-    private final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
+    protected final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
 
     private final List<String> args = new ArrayList<String>();
     private final List<String> tasks = new ArrayList<String>();
@@ -63,17 +65,18 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     private File settingsFile;
     private InputStream stdin;
     private String defaultCharacterEncoding;
-    private Integer daemonIdleTimeoutSecs = 10;
+    private int daemonIdleTimeoutSecs = 60;
     private File daemonBaseDir = buildContext.getDaemonBaseDir();
     private final List<String> gradleOpts = new ArrayList<String>();
     private boolean noDefaultJvmArgs;
     private boolean requireGradleHome;
 
     private boolean deprecationChecksOn = true;
+    private boolean eagerClassLoaderCreationChecksOn = true;
     private boolean stackTraceChecksOn = true;
 
     private final ActionBroadcast<GradleExecuter> beforeExecute = new ActionBroadcast<GradleExecuter>();
-    private final Set<Action> afterExecute = new LinkedHashSet<Action>();
+    private final Set<Action<? super GradleExecuter>> afterExecute = new LinkedHashSet<Action<? super GradleExecuter>>();
 
     private final TestDirectoryProvider testDirectoryProvider;
     private final GradleDistribution distribution;
@@ -200,6 +203,9 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         if (!deprecationChecksOn) {
             executer.withDeprecationChecksDisabled();
         }
+        if (!eagerClassLoaderCreationChecksOn) {
+            executer.withEagerClassLoaderCreationCheckDisabled();
+        }
         if (!stackTraceChecksOn) {
             executer.withStackTraceChecksDisabled();
         }
@@ -498,7 +504,7 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         }
 
         properties.put(GradleProperties.IDLE_TIMEOUT_PROPERTY, "" + (daemonIdleTimeoutSecs * 1000));
-        properties.put(GradleProperties.BASE_DIR_PROPERTY, daemonBaseDir.getAbsolutePath());
+        properties.put(GradleProperties.DAEMON_BASE_DIR_PROPERTY, daemonBaseDir.getAbsolutePath());
         properties.put(DeprecationLogger.ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME, "true");
 
         String tmpDirPath = getTmpDir().createDir().getAbsolutePath();
@@ -508,6 +514,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
 
         properties.put("file.encoding", getDefaultCharacterEncoding());
 
+        if (eagerClassLoaderCreationChecksOn) {
+            properties.put(DefaultClassLoaderScope.STRICT_MODE_PROPERTY, "true");
+        }
+
         return properties;
     }
 
@@ -624,6 +634,11 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
+    public GradleExecuter withEagerClassLoaderCreationCheckDisabled() {
+        eagerClassLoaderCreationChecksOn = false;
+        return this;
+    }
+
     public GradleExecuter withStackTraceChecksDisabled() {
         stackTraceChecksOn = false;
         return this;
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AnyOrderOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AnyOrderOutputMatcher.groovy
new file mode 100644
index 0000000..d5a710f
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AnyOrderOutputMatcher.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.executer
+
+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 AnyOrderOutputMatcher 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/executer/DaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java
index bf5e6eb..ac52891 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java
@@ -61,8 +61,9 @@ public class DaemonGradleExecuter extends ForkingGradleExecuter {
             // Instead of just adding these as standalone opts, we need to add to
             // -Dorg.gradle.jvmArgs in order for them to be effectual
             List<String> jvmArgs = new ArrayList<String>(4);
-            jvmArgs.add("-XX:MaxPermSize=256m");
+            jvmArgs.add("-XX:MaxPermSize=320m");
             jvmArgs.add("-XX:+HeapDumpOnOutOfMemoryError");
+            jvmArgs.add("-XX:HeapDumpPath=" + buildContext.getGradleUserHomeDir().getAbsolutePath());
             if (JavaVersion.current().isJava5()) {
                 jvmArgs.add("-XX:+CMSPermGenSweepingEnabled");
                 jvmArgs.add("-Dcom.sun.management.jmxremote");
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
index 61b9de6..0fd5660 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
@@ -21,6 +21,7 @@ import org.gradle.internal.os.OperatingSystem;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.GradleVersion;
+import org.gradle.util.VersionNumber;
 
 public class DefaultGradleDistribution implements GradleDistribution {
 
@@ -34,6 +35,11 @@ public class DefaultGradleDistribution implements GradleDistribution {
         this.binDistribution = binDistribution;
     }
 
+    @Override
+    public String toString() {
+        return version.toString();
+    }
+
     public TestFile getGradleHomeDir() {
         return gradleHomeDir;
     }
@@ -52,11 +58,11 @@ public class DefaultGradleDistribution implements GradleDistribution {
 
     public boolean worksWith(Jvm jvm) {
         // Milestone 4 was broken on the IBM jvm
-        if (jvm.isIbmJvm() && version == GradleVersion.version("1.0-milestone-4")) {
+        if (jvm.isIbmJvm() && isVersion("1.0-milestone-4")) {
             return false;
         }
         // 0.9-rc-1 was broken for Java 5
-        if (version == GradleVersion.version("0.9-rc-1")) {
+        if (isVersion("0.9-rc-1")) {
             return jvm.getJavaVersion().isJava6Compatible();
         }
 
@@ -66,7 +72,7 @@ public class DefaultGradleDistribution implements GradleDistribution {
     public boolean worksWith(OperatingSystem os) {
         // 1.0-milestone-5 was broken where jna was not available
         //noinspection SimplifiableIfStatement
-        if (version == GradleVersion.version("1.0-milestone-5")) {
+        if (isVersion("1.0-milestone-5")) {
             return os.isWindows() || os.isMacOsX() || os.isLinux();
         } else {
             return true;
@@ -75,7 +81,7 @@ public class DefaultGradleDistribution implements GradleDistribution {
 
     public boolean isDaemonSupported() {
         // Milestone 7 was broken on the IBM jvm
-        if (Jvm.current().isIbmJvm() && version == GradleVersion.version("1.0-milestone-7")) {
+        if (Jvm.current().isIbmJvm() && isVersion("1.0-milestone-7")) {
             return false;
         }
 
@@ -100,13 +106,32 @@ public class DefaultGradleDistribution implements GradleDistribution {
         return isSameOrNewer("1.0-milestone-3");
     }
 
-    public int getArtifactCacheLayoutVersion() {
-        if (isSameOrNewer("1.4-rc-1")) {
-            return 23;
+    public boolean isToolingApiNonAsciiOutputSupported() {
+        if (OperatingSystem.current().isWindows()) {
+            return !isVersion("1.0-milestone-7") && !isVersion("1.0-milestone-8") && !isVersion("1.0-milestone-8a");
+        }
+        return true;
+    }
+
+    public VersionNumber getArtifactCacheLayoutVersion() {
+        if (isSameOrNewer("1.12-rc-1")) {
+            return VersionNumber.parse("2.6");
+        } else if (isSameOrNewer("1.11-rc-1")) {
+            return VersionNumber.parse("2.2");
+        } else if (isSameOrNewer("1.9-rc-2")) {
+            return VersionNumber.parse("2.1");
+        } else if (isSameOrNewer("1.9-rc-1")) {
+            return VersionNumber.parse("1.31");
+        } else if (isSameOrNewer("1.7-rc-1")) {
+            return VersionNumber.parse("0.26");
+        } else if (isSameOrNewer("1.6-rc-1")) {
+            return VersionNumber.parse("0.24");
+        } else if (isSameOrNewer("1.4-rc-1")) {
+            return VersionNumber.parse("0.23");
         } else if (isSameOrNewer("1.3")) {
-            return 15;
+            return VersionNumber.parse("0.15");
         } else {
-            return 1;
+            return VersionNumber.parse("0.1");
         }
     }
 
@@ -146,7 +171,7 @@ public class DefaultGradleDistribution implements GradleDistribution {
 
     protected boolean isVersion(String otherVersionString) {
         GradleVersion otherVersion = GradleVersion.version(otherVersionString);
-        return version.compareTo(otherVersion) == 0 || (version.isSnapshot() && version.getVersionBase().equals(otherVersion.getVersionBase()));
+        return version.compareTo(otherVersion) == 0 || (version.isSnapshot() && version.getBaseVersion().equals(otherVersion.getBaseVersion()));
     }
 
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
index 8962401..305b553 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
@@ -19,9 +19,6 @@ package org.gradle.integtests.fixtures.executer
 import static org.gradle.util.Matchers.*;
 import org.hamcrest.Matcher
 
-/**
- * by Szczepan Faber, created at: 12/12/12
- */
 public class DependencyResolutionFailure {
     private final ExecutionFailure failure
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy
index af93967..29f8295 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy
@@ -18,9 +18,6 @@ package org.gradle.integtests.fixtures.executer
 
 import static org.hamcrest.Matchers.startsWith
 
-/**
- * by Szczepan Faber, created at: 11/26/12
- */
 class DetailedExecutionFailure {
     ExecutionFailure failure
 
@@ -31,6 +28,6 @@ class DetailedExecutionFailure {
     public assertTestsFailed() {
         failure
             .assertHasDescription("Execution failed for task ':test'.")
-            .assertThatCause(startsWith("There were failing tests."));
+            .assertThatCause(startsWith("There were failing tests"));
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.groovy
index 5000654..b945d79 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.groovy
@@ -20,10 +20,13 @@ import org.gradle.CacheUsage
 import org.gradle.api.Action
 import org.gradle.cache.PersistentCache
 import org.gradle.cache.internal.*
+import org.gradle.cache.internal.locklistener.NoOpFileLockContentionHandler
 import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.GradleVersion
 
+import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
+
 abstract class DownloadableGradleDistribution extends DefaultGradleDistribution {
 
     private static final CACHE_FACTORY = createCacheFactory()
@@ -34,7 +37,7 @@ abstract class DownloadableGradleDistribution extends DefaultGradleDistribution
                         new DefaultProcessMetaDataProvider(
                                 NativeServices.getInstance().get(org.gradle.internal.nativeplatform.ProcessEnvironment)),
                         20 * 60 * 1000 // allow up to 20 minutes to download a distribution
-                )).create()
+                , new NoOpFileLockContentionHandler()))
     }
 
     protected TestFile versionDir
@@ -64,7 +67,7 @@ abstract class DownloadableGradleDistribution extends DefaultGradleDistribution
                 super.binDistribution.usingNativeTools().unzipTo(versionDir)
             }
             //noinspection GrDeprecatedAPIUsage
-            cache = CACHE_FACTORY.open(versionDir, version.version, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, downloadAction as Action)
+            cache = CACHE_FACTORY.open(versionDir, version.version, CacheUsage.ON, null, [:], mode(FileLockManager.LockMode.Shared).useCrossVersionImplementation(), downloadAction as Action)
         }
 
         super.binDistribution.assertIsFile()
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java
index d3b1ff6..9a2f3f6 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java
@@ -33,8 +33,7 @@ import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import java.lang.management.ManagementFactory;
 
 class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
-
-    private final EmbeddedDaemonClientServices daemonClientServices = new EmbeddedDaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), false);
+    private final EmbeddedDaemonClientServices daemonClientServices = new EmbeddedDaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging());
 
     EmbeddedDaemonGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
         super(distribution, testDirectoryProvider);
@@ -90,7 +89,7 @@ class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
     }
 
     private LoggingManagerInternal createLoggingManager(StringBuilder output, StringBuilder error) {
-        LoggingManagerInternal loggingManager = daemonClientServices.getLoggingServices().newInstance(LoggingManagerInternal.class);
+        LoggingManagerInternal loggingManager = daemonClientServices.newInstance(LoggingManagerInternal.class);
         loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(output));
         loggingManager.addStandardErrorListener(new StreamBackedStandardOutputListener(error));
         return loggingManager;
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
index 5ef30e8..fa1e4f3 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
@@ -22,14 +22,31 @@ public interface ExecutionFailure extends ExecutionResult {
 
     ExecutionFailure assertHasFileName(String filename);
 
+    /**
+     * Asserts that the reported failure has the given cause (ie the bit after the description)
+     */
     ExecutionFailure assertHasCause(String description);
 
+    /**
+     * Asserts that the reported failure has the given cause (ie the bit after the description)
+     */
     ExecutionFailure assertThatCause(Matcher<String> matcher);
 
+    /**
+     * Asserts that the reported failure has the given description (ie the bit after '* What went wrong').
+     */
     ExecutionFailure assertHasDescription(String context);
 
+    /**
+     * Asserts that the reported failure has the given description (ie the bit after '* What went wrong').
+     */
     ExecutionFailure assertThatDescription(Matcher<String> matcher);
 
+    /**
+     * Asserts that the reported failure has the given resolution (ie the bit after '* Try').
+     */
+    ExecutionFailure assertHasResolution(String resolution);
+
     ExecutionFailure assertHasNoCause();
 
     ExecutionFailure assertTestsFailed();
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java
index 828d3ab..91aaf92 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java
@@ -23,7 +23,7 @@ public interface ExecutionResult {
 
     String getError();
 
-    ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines);
+    ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines, boolean ignoreLineOrder);
 
     /**
      * 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/executer/ForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleExecuter.java
index 54a49dd..4db0351 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleExecuter.java
@@ -17,16 +17,19 @@
 package org.gradle.integtests.fixtures.executer;
 
 import org.gradle.api.Action;
+import org.gradle.initialization.BuildLayoutParameters;
 import org.gradle.internal.Factory;
-import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
 import org.gradle.internal.os.OperatingSystem;
+import org.gradle.launcher.daemon.client.DaemonClientServices;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
-import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.process.internal.ExecHandleBuilder;
 import org.gradle.process.internal.JvmOptions;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import org.gradle.test.fixtures.file.TestFile;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -42,7 +45,9 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
     }
 
     public DaemonRegistry getDaemonRegistry() {
-        return new DaemonRegistryServices(getDaemonBaseDir()).get(DaemonRegistry.class);
+        DaemonParameters parameters = new DaemonParameters(new BuildLayoutParameters());
+        parameters.setBaseDir(getDaemonBaseDir());
+        return new DaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), parameters, new ByteArrayInputStream(new byte[0])).get(DaemonRegistry.class);
     }
 
     public void assertCanExecute() throws AssertionError {
@@ -174,9 +179,6 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
             }
             builder.environment("Path", String.format("%s\\bin;%s", gradleHome, path));
             builder.environment("GRADLE_EXIT_CONSOLE", "true");
-
-            getLogger().info("Initializing windows process so that child process will be fully detached...");
-            new WindowsHandlesManipulator().uninheritStandardStreams();
         }
     }
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
index 263aaae..00eaa31 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
@@ -20,6 +20,7 @@ import org.gradle.internal.os.OperatingSystem;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.GradleVersion;
+import org.gradle.util.VersionNumber;
 
 public interface GradleDistribution {
     /**
@@ -68,9 +69,14 @@ public interface GradleDistribution {
     boolean isToolingApiSupported();
 
     /**
+     * Returns true if the tooling API provider of this distribution correctly handles non-ASCII characters in logging output.
+     */
+    boolean isToolingApiNonAsciiOutputSupported();
+
+    /**
      * Returns the version of the artifact cache layout
      */
-    int getArtifactCacheLayoutVersion();
+    VersionNumber getArtifactCacheLayoutVersion();
 
     /**
      * Returns true if the open API is supported by this distribution.
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java
index 646bdb4..650ae74 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java
@@ -227,6 +227,11 @@ public interface GradleExecuter {
     GradleExecuter withDeprecationChecksDisabled();
 
     /**
+     * Disables asserting that class loaders were not eagerly created, potentially leading to performance problems.
+     */
+    GradleExecuter withEagerClassLoaderCreationCheckDisabled();
+
+    /**
      * Disables asserting that no unexpected stacktraces are present in the output.
      */
     GradleExecuter withStackTraceChecksDisabled();
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java
index 28a8100..4eab245 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java
@@ -16,7 +16,6 @@
 
 package org.gradle.integtests.fixtures.executer;
 
-import org.gradle.BuildListener;
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
@@ -25,26 +24,26 @@ import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionGraph;
 import org.gradle.api.execution.TaskExecutionGraphListener;
 import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.initialization.Settings;
-import org.gradle.api.internal.LocationAwareException;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.api.logging.LogLevel;
+import org.gradle.api.internal.file.TestFiles;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.api.tasks.TaskState;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
 import org.gradle.execution.MultipleBuildFailures;
+import org.gradle.initialization.BuildLayoutParameters;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
 import org.gradle.internal.Factory;
+import org.gradle.internal.exceptions.LocationAwareException;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.launcher.Main;
-import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer;
+import org.gradle.launcher.cli.converter.LayoutToPropertiesConverter;
+import org.gradle.launcher.cli.converter.PropertiesToStartParameterConverter;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.logging.ShowStacktrace;
 import org.gradle.process.internal.JavaExecHandleBuilder;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import org.gradle.util.DeprecationLogger;
@@ -58,8 +57,10 @@ import java.nio.charset.Charset;
 import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.regex.Pattern;
 
-import static org.gradle.util.Matchers.*;
+import static org.gradle.util.Matchers.hasMessage;
+import static org.gradle.util.Matchers.isEmpty;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
@@ -89,7 +90,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             throw new UnexpectedBuildFailure(e);
         }
         return assertResult(new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
-                outputListener.toString(), errorListener.toString()));
+                new OutputScrapingExecutionResult(outputListener.toString(), errorListener.toString())));
     }
 
     @Override
@@ -102,7 +103,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             throw new AssertionError("expected build to fail but it did not.");
         } catch (GradleException e) {
             return assertResult(new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
-                    outputListener.writer.toString(), errorListener.writer.toString(), e));
+                    new OutputScrapingExecutionFailure(outputListener.writer.toString(), errorListener.writer.toString()), e));
         }
     }
 
@@ -115,7 +116,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
     protected GradleHandle doStart() {
         return new ForkingGradleHandle(getResultAssertion(), getDefaultCharacterEncoding(), new Factory<JavaExecHandleBuilder>() {
             public JavaExecHandleBuilder create() {
-                JavaExecHandleBuilder builder = new JavaExecHandleBuilder(new IdentityFileResolver());
+                JavaExecHandleBuilder builder = new JavaExecHandleBuilder(TestFiles.resolver());
                 builder.workingDir(getWorkingDir());
                 Set<File> classpath = new DefaultModuleRegistry().getFullClasspath();
                 builder.classpath(classpath);
@@ -144,22 +145,30 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         System.getProperties().putAll(implicitJvmSystemProperties);
 
         StartParameter parameter = new StartParameter();
-        parameter.setLogLevel(LogLevel.INFO);
-        parameter.setSearchUpwards(true);
         parameter.setCurrentDir(getWorkingDir());
+        parameter.setShowStacktrace(ShowStacktrace.ALWAYS);
 
         CommandLineParser parser = new CommandLineParser();
         DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
         converter.configure(parser);
         ParsedCommandLine parsedCommandLine = parser.parse(getAllArgs());
-        converter.convert(parsedCommandLine, parameter);
 
-        //I'm not sure if below is safe
-        new GradlePropertiesConfigurer().configureStartParameter(parameter);
+        BuildLayoutParameters layout = converter.getLayoutConverter().convert(parsedCommandLine);
+
+        Map<String, String> properties = new HashMap<String, String>();
+        new LayoutToPropertiesConverter().convert(layout, properties);
+        converter.getSystemPropertiesConverter().convert(parsedCommandLine, properties);
+
+        new PropertiesToStartParameterConverter().convert(properties, parameter);
+        converter.convert(parsedCommandLine, parameter);
 
-        DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
+        DefaultGradleLauncherFactory factory = DeprecationLogger.whileDisabled(new Factory<DefaultGradleLauncherFactory>() {
+            public DefaultGradleLauncherFactory create() {
+                return (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
+            }
+        });
         factory.addListener(listener);
-        GradleLauncher gradleLauncher = GradleLauncher.newInstance(parameter);
+        GradleLauncher gradleLauncher = factory.newInstance(parameter);
         gradleLauncher.addStandardOutputListener(outputListener);
         gradleLauncher.addStandardErrorListener(errorListener);
         try {
@@ -195,7 +204,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         assertFalse(isRequireGradleHome());
     }
 
-    private static class BuildListenerImpl implements TaskExecutionGraphListener, BuildListener {
+    private static class BuildListenerImpl implements TaskExecutionGraphListener {
         private final List<String> executedTasks = new CopyOnWriteArrayList<String>();
         private final Set<String> skippedTasks = new CopyOnWriteArraySet<String>();
 
@@ -203,26 +212,6 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
             graph.addTaskExecutionListener(new TaskListenerImpl(planned, executedTasks, skippedTasks));
         }
-
-        public void buildStarted(Gradle gradle) {
-            DeprecationLogger.setLogTrace(true);
-        }
-
-        public void settingsEvaluated(Settings settings) {
-
-        }
-
-        public void projectsLoaded(Gradle gradle) {
-
-        }
-
-        public void projectsEvaluated(Gradle gradle) {
-
-        }
-
-        public void buildFinished(BuildResult result) {
-            DeprecationLogger.setLogTrace(false);
-        }
     }
 
     private static class OutputListenerImpl implements StandardOutputListener {
@@ -279,28 +268,25 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
     public static class InProcessExecutionResult implements ExecutionResult {
         private final List<String> plannedTasks;
         private final Set<String> skippedTasks;
-        private final String output;
-        private final String error;
+        private final OutputScrapingExecutionResult outputResult;
 
-        public InProcessExecutionResult(List<String> plannedTasks, Set<String> skippedTasks, String output,
-                                        String error) {
+        public InProcessExecutionResult(List<String> plannedTasks, Set<String> skippedTasks, OutputScrapingExecutionResult outputResult) {
             this.plannedTasks = plannedTasks;
             this.skippedTasks = skippedTasks;
-            this.output = output;
-            this.error = error;
+            this.outputResult = outputResult;
         }
 
         public String getOutput() {
-            return output;
+            return outputResult.getOutput();
         }
 
-        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
-            new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines, boolean ignoreLineOrder) {
+            outputResult.assertOutputEquals(expectedOutput, ignoreExtraLines, ignoreLineOrder);
             return this;
         }
 
         public String getError() {
-            return error;
+            return outputResult.getError();
         }
 
         public List<String> getExecutedTasks() {
@@ -310,6 +296,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         public ExecutionResult assertTasksExecuted(String... taskPaths) {
             List<String> expected = Arrays.asList(taskPaths);
             assertThat(plannedTasks, equalTo(expected));
+            outputResult.assertTasksExecuted(taskPaths);
             return this;
         }
 
@@ -320,11 +307,13 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         public ExecutionResult assertTasksSkipped(String... taskPaths) {
             Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
             assertThat(skippedTasks, equalTo(expected));
+            outputResult.assertTasksSkipped(taskPaths);
             return this;
         }
 
         public ExecutionResult assertTaskSkipped(String taskPath) {
             assertThat(skippedTasks, hasItem(taskPath));
+            outputResult.assertTaskSkipped(taskPath);
             return this;
         }
 
@@ -332,11 +321,13 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
             Set<String> notSkipped = getNotSkippedTasks();
             assertThat(notSkipped, equalTo(expected));
+            outputResult.assertTasksNotSkipped(taskPaths);
             return this;
         }
 
         public ExecutionResult assertTaskNotSkipped(String taskPath) {
             assertThat(getNotSkippedTasks(), hasItem(taskPath));
+            outputResult.assertTaskNotSkipped(taskPath);
             return this;
         }
 
@@ -348,22 +339,46 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
     }
 
     private static class InProcessExecutionFailure extends InProcessExecutionResult implements ExecutionFailure {
+        private static final Pattern LOCATION_PATTERN = Pattern.compile("(?m)^((\\w+ )+'.+') line: (\\d+)$");
+        private final OutputScrapingExecutionFailure outputFailure;
         private final GradleException failure;
+        private final String fileName;
+        private final String lineNumber;
+        private final String description;
 
-        public InProcessExecutionFailure(List<String> tasks, Set<String> skippedTasks, String output, String error,
+        public InProcessExecutionFailure(List<String> tasks, Set<String> skippedTasks, OutputScrapingExecutionFailure outputFailure,
                                          GradleException failure) {
-            super(tasks, skippedTasks, output, error);
+            super(tasks, skippedTasks, outputFailure);
+            this.outputFailure = outputFailure;
             this.failure = failure;
+
+            // Chop up the exception message into its expected parts
+            java.util.regex.Matcher matcher = LOCATION_PATTERN.matcher(failure.getMessage());
+            if (matcher.find()) {
+                fileName = matcher.group(1);
+                lineNumber = matcher.group(3);
+                description = failure.getMessage().substring(matcher.end()).trim();
+            } else {
+                fileName = "";
+                lineNumber = "";
+                description = failure.getMessage().trim();
+            }
         }
 
         public ExecutionFailure assertHasLineNumber(int lineNumber) {
-            assertThat(failure.getMessage(), containsString(String.format(" line: %d", lineNumber)));
+            assertThat(this.lineNumber, equalTo(String.valueOf(lineNumber)));
+            outputFailure.assertHasLineNumber(lineNumber);
             return this;
-
         }
 
         public ExecutionFailure assertHasFileName(String filename) {
-            assertThat(failure.getMessage(), startsWith(String.format("%s", filename)));
+            assertThat(this.fileName, equalTo(filename));
+            outputFailure.assertHasFileName(filename);
+            return this;
+        }
+
+        public ExecutionFailure assertHasResolution(String resolution) {
+            outputFailure.assertHasResolution(resolution);
             return this;
         }
 
@@ -376,6 +391,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             List<Throwable> causes = new ArrayList<Throwable>();
             extractCauses(failure, causes);
             assertThat(causes, Matchers.<Throwable>hasItem(hasMessage(matcher)));
+            outputFailure.assertThatCause(matcher);
             return this;
         }
 
@@ -399,16 +415,18 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             } else {
                 assertThat(failure.getCause(), nullValue());
             }
+            outputFailure.assertHasNoCause();
             return this;
         }
 
         public ExecutionFailure assertHasDescription(String context) {
-            assertThatDescription(startsWith(context));
+            assertThatDescription(equalTo(context));
             return this;
         }
 
         public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
-            assertThat(failure.getMessage(), containsLine(matcher));
+            assertThat(description, matcher);
+            outputFailure.assertThatDescription(matcher);
             return this;
         }
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy
index 4133565..c4b1c73 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy
@@ -16,14 +16,11 @@
 
 package org.gradle.integtests.fixtures.executer
 
-import org.junit.rules.MethodRule
 import org.gradle.test.fixtures.file.TestDirectoryProvider
-import org.junit.runners.model.Statement
+import org.junit.rules.MethodRule
 import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
 
-/**
- * by Szczepan Faber, created at: 2/1/13
- */
 abstract class InitScriptExecuterFixture implements MethodRule {
 
     GradleExecuter executer
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
index 94963b3..3bb8f00 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
@@ -17,30 +17,90 @@ package org.gradle.integtests.fixtures.executer;
 
 import org.hamcrest.Matcher;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.regex.Pattern;
 
-import static org.hamcrest.Matchers.not;
-import static org.gradle.util.Matchers.containsLine;
-import static org.gradle.util.Matchers.matchesRegexp;
-import static org.hamcrest.Matchers.containsString;
+import static org.gradle.util.Matchers.isEmpty;
+import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.startsWith;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 public class OutputScrapingExecutionFailure extends OutputScrapingExecutionResult implements ExecutionFailure {
-    private final Pattern causePattern = Pattern.compile("(?m)\\s*> ");
+    private static final Pattern CAUSE_PATTERN = Pattern.compile("(?m)(^\\s*> )");
+    private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("(?ms)^\\* What went wrong:$(.+)^\\* Try:$");
+    private static final Pattern LOCATION_PATTERN = Pattern.compile("(?ms)^\\* Where:((.+)'.+') line: (\\d+)$");
+    private static final Pattern RESOLUTION_PATTERN = Pattern.compile("(?ms)^\\* Try:$(.+)^\\* Exception is:$");
+    private final String description;
+    private final String lineNumber;
+    private final String fileName;
+    private final String resolution;
+    private final List<String> causes = new ArrayList<String>();
 
     public OutputScrapingExecutionFailure(String output, String error) {
         super(output, error);
+
+        java.util.regex.Matcher matcher = LOCATION_PATTERN.matcher(error);
+        if (matcher.find()) {
+            fileName = matcher.group(1).trim();
+            lineNumber = matcher.group(3);
+        } else {
+            fileName = "";
+            lineNumber = "";
+        }
+
+        matcher = DESCRIPTION_PATTERN.matcher(error);
+        String problem;
+        if (matcher.find()) {
+            problem = matcher.group(1);
+        } else {
+            problem = "";
+        }
+
+        matcher = CAUSE_PATTERN.matcher(problem);
+        if (!matcher.find()) {
+            description = problem.trim();
+        } else {
+            description = problem.substring(0, matcher.start()).trim();
+            while (true) {
+                int pos = matcher.end();
+                int prefix = matcher.group(1).length();
+                String prefixPattern = toPrefixPattern(prefix);
+                if (matcher.find(pos)) {
+                    String cause = problem.substring(pos, matcher.start()).trim().replaceAll(prefixPattern, "");
+                    causes.add(cause);
+                } else {
+                    String cause = problem.substring(pos).trim().replaceAll(prefixPattern, "");
+                    causes.add(cause);
+                    break;
+                }
+            }
+        }
+
+        matcher = RESOLUTION_PATTERN.matcher(error);
+        if (!matcher.find()) {
+            resolution = "";
+        } else {
+            resolution = matcher.group(1).trim();
+        }
+    }
+
+    private String toPrefixPattern(int prefix) {
+        StringBuilder builder = new StringBuilder("(?m)^");
+        for (int i = 0; i < prefix; i++) {
+            builder.append(' ');
+        }
+        return builder.toString();
     }
 
     public ExecutionFailure assertHasLineNumber(int lineNumber) {
-        assertThat(getError(), containsString(String.format(" line: %d", lineNumber)));
+        assertThat(this.lineNumber, equalTo(String.valueOf(lineNumber)));
         return this;
     }
 
     public ExecutionFailure assertHasFileName(String filename) {
-        assertThat(getError(), containsLine(startsWith(filename)));
+        assertThat(this.fileName, equalTo(filename));
         return this;
     }
 
@@ -50,42 +110,32 @@ public class OutputScrapingExecutionFailure extends OutputScrapingExecutionResul
     }
 
     public ExecutionFailure assertThatCause(Matcher<String> matcher) {
-        String error = getError();
-        java.util.regex.Matcher regExpMatcher = causePattern.matcher(error);
-        int pos = 0;
-        while (pos < error.length()) {
-            if (!regExpMatcher.find(pos)) {
-                break;
-            }
-            int start = regExpMatcher.end();
-            String cause;
-            if (regExpMatcher.find(start)) {
-                cause = error.substring(start, regExpMatcher.start());
-                pos = regExpMatcher.start();
-            } else {
-                cause = error.substring(start);
-                pos = error.length();
-            }
+        for (String cause : causes) {
             if (matcher.matches(cause)) {
                 return this;
             }
         }
-        fail(String.format("No matching cause found in '%s'", error));
+        fail(String.format("No matching cause found in %s", causes));
+        return this;
+    }
+
+    public ExecutionFailure assertHasResolution(String resolution) {
+        assertThat(this.resolution, equalTo(resolution));
         return this;
     }
 
     public ExecutionFailure assertHasNoCause() {
-        assertThat(getError(), not(matchesRegexp(causePattern)));
+        assertThat(causes, isEmpty());
         return this;
     }
 
     public ExecutionFailure assertHasDescription(String context) {
-        assertThatDescription(startsWith(context));
+        assertThatDescription(equalTo(context));
         return this;
     }
 
     public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
-        assertThat(getError(), containsLine(matcher));
+        assertThat(description, matcher);
         return this;
     }
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java
index ba3bc85..298814f 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java
@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertThat;
 
 public class OutputScrapingExecutionResult implements ExecutionResult {
@@ -36,7 +37,7 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
     private final Pattern skippedTaskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)\\s+((SKIPPED)|(UP-TO-DATE))");
 
     //for example: ':hey' or ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a FOO'
-    private final Pattern taskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)((\\s+SKIPPED)|(\\s+UP-TO-DATE)|(\\s*))");
+    private final Pattern taskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)((\\s+SKIPPED)|(\\s+UP-TO-DATE)|(\\s+FAILED)|(\\s*))");
 
     public OutputScrapingExecutionResult(String output, String error) {
         this.output = output;
@@ -47,8 +48,9 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
         return output;
     }
 
-    public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
-        new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+    public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines, boolean ignoreLineOrder) {
+        SequentialOutputMatcher matcher = ignoreLineOrder ? new AnyOrderOutputMatcher() : new SequentialOutputMatcher();
+        matcher.assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
         return this;
     }
 
@@ -66,6 +68,12 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
         return this;
     }
 
+    public ExecutionResult assertTaskNotExecuted(String taskPath) {
+        Set<String> tasks = new HashSet<String>(getExecutedTasks());
+        assertThat(String.format("Expected task %s found in process output:%n%s", taskPath, getOutput()), tasks, not(hasItem(taskPath)));
+        return this;
+    }
+
     public Set<String> getSkippedTasks() {
         return new HashSet<String>(grepTasks(skippedTaskPattern));
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
index e233b2b..fecee7f 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
@@ -68,8 +68,9 @@ public class ParallelForkingGradleHandle extends ForkingGradleHandle {
         }
 
         @Override
-        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
-            new ParallelOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines, boolean ignoreLineOrder) {
+            // We always ignore line order for matching out of parallel builds
+            new AnyOrderOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
             return this;
         }
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelOutputMatcher.groovy
deleted file mode 100644
index 4b06e58..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelOutputMatcher.groovy
+++ /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.integtests.fixtures.executer
-
-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/executer/ProgressLoggingFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
index d791ba6..9f98db9 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
@@ -62,10 +62,18 @@ class ProgressLoggingFixture extends InitScriptExecuterFixture {
         }
     }
 
+    boolean downloadProgressLogged(URI url) {
+        downloadProgressLogged(url.toString())
+    }
+
     boolean downloadProgressLogged(String url) {
         return progressLogged("Download", url)
     }
 
+    boolean uploadProgressLogged(URI url) {
+        uploadProgressLogged(url.toString())
+    }
+
     boolean uploadProgressLogged(String url) {
         return progressLogged("Upload", url)
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionJsonSource.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionJsonSource.java
deleted file mode 100644
index c778ae0..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionJsonSource.java
+++ /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.integtests.fixtures.versions;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.Factory;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-public class ClasspathVersionJsonSource implements Factory<Reader> {
-
-    private final String resourceName;
-    private final ClassLoader classLoader;
-
-    public ClasspathVersionJsonSource() {
-        this("all-released-versions.json", ClasspathVersionJsonSource.class.getClassLoader());
-    }
-
-    ClasspathVersionJsonSource(String resourceName, ClassLoader classLoader) {
-        this.resourceName = resourceName;
-        this.classLoader = classLoader;
-    }
-
-    public Reader create() {
-        URL resource = classLoader.getResource(resourceName);
-        if (resource == null) {
-            throw new RuntimeException(
-                    "Unable to find the released versions information.\n"
-                            + "The resource '" + resourceName + "' was not found.\n"
-                            + "Most likely, you haven't ran the 'prepareVersionsInfo' task.\n"
-                            + "If you have trouble running tests from your IDE, please run gradlew idea|eclipse first."
-            );
-        }
-
-        try {
-            return new InputStreamReader(resource.openStream(), "utf8");
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionSource.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionSource.java
new file mode 100644
index 0000000..d1b230b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionSource.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.integtests.fixtures.versions;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.Factory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+public class ClasspathVersionSource implements Factory<Properties> {
+
+    private final String resourceName;
+    private final ClassLoader classLoader;
+
+    public ClasspathVersionSource() {
+        this("all-released-versions.properties", ClasspathVersionSource.class.getClassLoader());
+    }
+
+    ClasspathVersionSource(String resourceName, ClassLoader classLoader) {
+        this.resourceName = resourceName;
+        this.classLoader = classLoader;
+    }
+
+    public Properties create() {
+        URL resource = classLoader.getResource(resourceName);
+        if (resource == null) {
+            throw new RuntimeException(
+                    "Unable to find the released versions information.\n"
+                            + "The resource '" + resourceName + "' was not found.\n"
+                            + "Most likely, you haven't ran the 'prepareVersionsInfo' task.\n"
+                            + "If you have trouble running tests from your IDE, please run gradlew idea|eclipse first."
+            );
+        }
+
+        try {
+            Properties properties = new Properties();
+            InputStream stream = resource.openStream();
+            try {
+                properties.load(stream);
+            } finally {
+                stream.close();
+            }
+            return properties;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpec.groovy
deleted file mode 100644
index d897f3a..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpec.groovy
+++ /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.integtests.fixtures.versions
-
-import org.gradle.api.specs.Spec
-import org.gradle.util.GradleVersion
-
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.NIGHTLY
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.RELEASE_CANDIDATE
-
-class IsTestableGradleVersionSpec implements Spec<ReleasedGradleVersion> {
-
-    def lowestInterestingVersion = GradleVersion.version("0.8")
-    def excludedVersions = ["0.9-rc-1", "0.9-rc-2", "1.0-milestone-4"].collect { GradleVersion.version(it) }
-    def currentVersion = GradleVersion.current()
-
-    boolean isSatisfiedBy(ReleasedGradleVersion releasedGradleVersion) {
-        return releasedGradleVersion.version != currentVersion &&
-                !(releasedGradleVersion.version in excludedVersions) &&
-                releasedGradleVersion.version >= lowestInterestingVersion &&
-                releasedGradleVersion.type != NIGHTLY &&
-                (releasedGradleVersion.type != RELEASE_CANDIDATE || releasedGradleVersion.current) // only the current/active RC if there is one
-    }
-
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedGradleVersion.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedGradleVersion.java
deleted file mode 100644
index 11d8b58..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedGradleVersion.java
+++ /dev/null
@@ -1,63 +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.versions;
-
-import org.gradle.util.GradleVersion;
-
-public class ReleasedGradleVersion implements Comparable<ReleasedGradleVersion> {
-
-    private final GradleVersion version;
-    private final Type type;
-    private final boolean current;
-
-    public ReleasedGradleVersion(GradleVersion version, Type type, boolean current) {
-        this.version = version;
-        this.type = type;
-        this.current = current;
-    }
-
-    @Override
-    public String toString() {
-        return "ReleasedGradleVersion{"
-                + "version=" + version
-                + ", type=" + type
-                + ", current=" + current
-                + '}';
-    }
-
-    public GradleVersion getVersion() {
-        return version;
-    }
-
-    public Type getType() {
-        return type;
-    }
-
-    public boolean isCurrent() {
-        return current;
-    }
-
-    public static enum Type {
-        NIGHTLY,
-        RELEASE_CANDIDATE,
-        FINAL
-    }
-
-    public int compareTo(ReleasedGradleVersion o) {
-        return this.getVersion().compareTo(o.getVersion());
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java
index a77c617..86c3094 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java
@@ -24,72 +24,55 @@ import org.gradle.internal.Factory;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.GradleVersion;
 
-import java.util.Collections;
 import java.util.List;
+import java.util.Properties;
 
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.FINAL;
-import static org.gradle.util.CollectionUtils.*;
+import static org.gradle.util.CollectionUtils.findFirst;
 
 /**
  * Provides access to {@link GradleDistribution}s for versions of Gradle that have been released.
  *
  * Only versions that are suitable for testing against are made available.
- *
- * @see IsTestableGradleVersionSpec
  */
 public class ReleasedVersionDistributions {
 
     private final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
 
-    private final Factory<List<ReleasedGradleVersion>> versionsFactory;
-    private List<ReleasedGradleVersion> versions;
+    private final Factory<Properties> versionsFactory;
+    private Properties properties;
     private List<GradleDistribution> distributions;
 
     public ReleasedVersionDistributions() {
-        this(new Factory<List<ReleasedGradleVersion>>() {
-            public List<ReleasedGradleVersion> create() {
-                return sort(
-                        filter(
-                                new VersionWebServiceJsonParser(new ClasspathVersionJsonSource()).create(),
-                                new IsTestableGradleVersionSpec()
-                        ),
-                        Collections.reverseOrder()
-                );
-            }
-        });
+        this(new ClasspathVersionSource());
     }
 
-    ReleasedVersionDistributions(Factory<List<ReleasedGradleVersion>> versionsFactory) {
+    ReleasedVersionDistributions(Factory<Properties> versionsFactory) {
         this.versionsFactory = versionsFactory;
     }
 
-    private List<ReleasedGradleVersion> getVersions() {
-        if (versions == null) {
-            versions = versionsFactory.create();
+    private Properties getProperties() {
+        if (properties == null) {
+            properties = versionsFactory.create();
         }
 
-        return versions;
+        return properties;
     }
 
     public GradleDistribution getMostRecentFinalRelease() {
-        ReleasedGradleVersion mostRecentFinal = findFirst(getVersions(), new Spec<ReleasedGradleVersion>() {
-            public boolean isSatisfiedBy(ReleasedGradleVersion element) {
-                return element.getType() == FINAL;
-            }
-        });
+        String mostRecentFinal = getProperties().getProperty("mostRecent");
 
         if (mostRecentFinal == null) {
             throw new RuntimeException("Unable to get the last version");
         }
 
-        return buildContext.distribution(mostRecentFinal.getVersion().getVersion());
+        return buildContext.distribution(mostRecentFinal);
     }
 
     public List<GradleDistribution> getAll() {
         if (distributions == null) {
-            distributions = CollectionUtils.collect(getVersions(), new Transformer<GradleDistribution, ReleasedGradleVersion>() {
-                public GradleDistribution transform(ReleasedGradleVersion releasedGradleVersion) {
-                    return buildContext.distribution(releasedGradleVersion.getVersion().getVersion());
+            distributions = CollectionUtils.collect(getProperties().getProperty("versions").split("\\s+"), new Transformer<GradleDistribution, String>() {
+                public GradleDistribution transform(String version) {
+                    return buildContext.distribution(version);
                 }
             });
         }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParser.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParser.java
deleted file mode 100644
index 855f483..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParser.java
+++ /dev/null
@@ -1,85 +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.versions;
-
-import groovy.json.JsonSlurper;
-import org.gradle.api.Transformer;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.Factory;
-import org.gradle.util.GradleVersion;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.util.List;
-import java.util.Map;
-
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.*;
-import static org.gradle.util.CollectionUtils.collect;
-
-// This can't be implemented in Groovy because it can't handle the generic interface implementation
-public class VersionWebServiceJsonParser implements Factory<List<ReleasedGradleVersion>> {
-
-    private final Factory<Reader> jsonSource;
-
-    public VersionWebServiceJsonParser(Factory<Reader> jsonSource) {
-        this.jsonSource = jsonSource;
-    }
-
-    public List<ReleasedGradleVersion> create() {
-        List<Map<String, ?>> list = readJson();
-        return collect(list, new Transformer<ReleasedGradleVersion, Map<String, ?>>() {
-            public ReleasedGradleVersion transform(Map<String, ?> jsonEntry) {
-                ReleasedGradleVersion.Type type;
-                boolean current;
-
-                boolean jsonNightly = (Boolean) jsonEntry.get("nightly");
-                String jsonRcFor = (String) jsonEntry.get("rcFor");
-                boolean jsonActiveRc = (Boolean) jsonEntry.get("activeRc");
-                boolean jsonCurrent = (Boolean) jsonEntry.get("current");
-                String jsonVersion = (String) jsonEntry.get("version");
-
-                if (jsonNightly) {
-                    type = NIGHTLY;
-                    current = true;
-                } else if (jsonRcFor != null && jsonRcFor.length() > 0) {
-                    type = RELEASE_CANDIDATE;
-                    current = jsonActiveRc;
-                } else {
-                    type = FINAL;
-                    current = jsonCurrent;
-                }
-
-                return new ReleasedGradleVersion(GradleVersion.version(jsonVersion), type, current);
-            }
-        });
-    }
-
-    private List<Map<String, ?>> readJson() {
-        Reader reader = jsonSource.create();
-        try {
-            //noinspection unchecked
-            return (List<Map<String, ?>>) new JsonSlurper().parse(reader);
-        } finally {
-            try {
-                reader.close();
-            } catch (IOException e) {
-                //noinspection ThrowFromFinallyBlock
-                throw new UncheckedIOException(e);
-            }
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/AbstractModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/AbstractModule.groovy
new file mode 100644
index 0000000..142e53d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/AbstractModule.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.internal.hash.HashUtil
+
+abstract class AbstractModule {
+    /**
+     * @param cl A closure that is passed a writer to use to generate the content.
+     */
+    protected void publish(TestFile file, Closure cl) {
+        def hashBefore = file.exists() ? getHash(file, "sha1") : null
+        def tmpFile = file.parentFile.file("${file.name}.tmp")
+
+        tmpFile.withWriter("utf-8") {
+            cl.call(it)
+        }
+
+        def hashAfter = getHash(tmpFile, "sha1")
+        if (hashAfter == hashBefore) {
+            // Already published
+            return
+        }
+
+        assert !file.exists() || file.delete()
+        assert tmpFile.renameTo(file)
+        onPublish(file)
+    }
+
+    protected abstract onPublish(TestFile file)
+
+    TestFile getSha1File(TestFile file) {
+        getHashFile(file, "sha1")
+    }
+
+    TestFile sha1File(TestFile file) {
+        hashFile(file, "sha1", 40)
+    }
+
+    TestFile getMd5File(TestFile file) {
+        getHashFile(file, "md5")
+    }
+
+    TestFile md5File(TestFile file) {
+        hashFile(file, "md5", 32)
+    }
+
+    private TestFile hashFile(TestFile file, String algorithm, int len) {
+        def hashFile = getHashFile(file, algorithm)
+        def hash = getHash(file, algorithm)
+        hashFile.text = String.format("%0${len}x", hash)
+        return hashFile
+    }
+
+    private TestFile getHashFile(TestFile file, String algorithm) {
+        file.parentFile.file("${file.name}.${algorithm}")
+    }
+
+    protected BigInteger getHash(TestFile file, String algorithm) {
+        HashUtil.createHash(file, algorithm.toUpperCase()).asBigInteger()
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/HttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/HttpModule.groovy
new file mode 100644
index 0000000..de33167
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/HttpModule.groovy
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+public interface HttpModule extends Module {
+    void allowAll()
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/HttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/HttpRepository.groovy
new file mode 100644
index 0000000..4d6aa57
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/HttpRepository.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+public interface HttpRepository extends Repository {
+    HttpModule module(String group, String module)
+
+    HttpModule module(String group, String module, Object version)
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/Module.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/Module.groovy
new file mode 100644
index 0000000..91ac17b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/Module.groovy
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+public interface Module {
+    // using return type `Module` results in compile error for AbstractMavenModule
+    // at first sight, this looks like a Groovy bug related to covariant return types
+    def publish()
+    def publishWithChangedContent()
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/Repository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/Repository.groovy
new file mode 100644
index 0000000..6dfc6f5
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/Repository.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+public interface Repository {
+    URI getUri()
+
+    Module module(String group, String module)
+
+    Module module(String group, String module, Object version)
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/bintray/BintrayApi.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/bintray/BintrayApi.groovy
new file mode 100644
index 0000000..ce24f54
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/bintray/BintrayApi.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bintray
+
+import org.gradle.test.fixtures.server.http.HttpServer
+
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+import static org.gradle.plugin.resolve.internal.JCenterPluginMapper.*
+import static org.gradle.test.fixtures.server.http.HttpServer.Utils.json
+
+class BintrayApi {
+
+    final HttpServer httpServer
+    private final String path
+
+    BintrayApi(HttpServer httpServer) {
+        // The bintray client seems hardcoded to issue requests relative to the root
+        // therefore we have to use the root as the path
+        // Waiting for one of the following issues to be resolved so we can debug into it:
+        // https://github.com/bintray/bintray-client-java/pull/2
+        // https://github.com/bintray/bintray-client-java/issues/3
+        this.path = ""
+        this.httpServer = httpServer
+    }
+
+    String getAddress() {
+        httpServer.address + path
+    }
+
+    void expectPackageSearch(String pluginId, FoundPackage... packages) {
+        httpServer.expect("$path/search/attributes/$GRADLE_PLUGINS_ORG/$GRADLE_PLUGINS_REPO", ["POST"], new HttpServer.ActionSupport("search action") {
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                def requestBody = json(request)
+                assert requestBody == json("[{\"${PLUGIN_ID_ATTRIBUTE_NAME}\":[\"$pluginId\"]}]")
+                json(response, packages*.asStruct())
+            }
+        })
+    }
+
+    static class FoundPackage {
+        List<String> systemIds
+        String latestVersion
+
+        FoundPackage(String latestVersion, String... systemIds) {
+            this.systemIds = systemIds.toList()
+            this.latestVersion = latestVersion
+        }
+
+        Map<String, Object> asStruct() {
+            [
+                    "name": "not-used",
+                    "repo": "not-used",
+                    "owner": "not-used",
+                    "desc": "not-used",
+                    "labels": [],
+                    "attribute_names": ["not-used"],
+                    "followers_count": 0,
+                    "created": "2013-11-18T12:31:31.147Z",
+                    "versions": [],
+                    "latest_version": latestVersion,
+                    "updated": "2013-11-18T14:39:20.023Z",
+                    "rating_count": 0,
+                    "system_ids": systemIds
+            ]
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/bintray/BintrayTestServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/bintray/BintrayTestServer.groovy
new file mode 100644
index 0000000..a4af182
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/bintray/BintrayTestServer.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bintray
+
+import org.gradle.api.Action
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.plugin.resolve.internal.JCenterPluginMapper
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.rules.ExternalResource
+
+class BintrayTestServer extends ExternalResource {
+
+    private final HttpServer http
+
+    final MavenHttpRepository jcenter
+    final MavenFileRepository repo
+    final BintrayApi api
+
+    BintrayTestServer(GradleExecuter executer, MavenFileRepository repo) {
+        this.http = new HttpServer()
+        this.repo = repo
+        this.jcenter = new MavenHttpRepository(http, repo)
+        this.api = new BintrayApi(http)
+
+        executer.beforeExecute(new Action<GradleExecuter>() {
+            void execute(GradleExecuter e) {
+                if (http.running) {
+                    e.withArguments(
+                            "-D$JCenterPluginMapper.BINTRAY_API_OVERRIDE_URL_PROPERTY=$api.address",
+                            "-D$BaseRepositoryFactory.JCENTER_REPO_OVERRIDE_URL_PROPERTY=$jcenter.uri"
+                    )
+                }
+            }
+        })
+    }
+
+    void start() {
+        http.start()
+    }
+
+    @Override
+    protected void after() {
+        http.stop()
+    }
+}
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
deleted file mode 100644
index 63528d6..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/AbstractIvyModule.groovy
+++ /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.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
index 2eeb441..1eb1373 100644
--- 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
@@ -90,7 +90,9 @@ class IvyDescriptor {
         expected.each {
             String key = StringUtils.substringBefore(it, "@")
             String conf = StringUtils.substringAfter(it, "@") + "->default"
+            assert dependencies.containsKey(key)
             assert dependencies[key].hasConf(conf)
         }
+        true
     }
 }
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
index c28754a..fad1431 100644
--- 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
@@ -16,13 +16,14 @@
 package org.gradle.test.fixtures.ivy
 
 import org.apache.ivy.core.IvyPatternHelper
+import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.Action
 import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.test.fixtures.AbstractModule
 import org.gradle.test.fixtures.file.TestFile
-import org.gradle.util.hash.HashUtil
 
-class IvyFileModule extends AbstractIvyModule {
+class IvyFileModule extends AbstractModule implements IvyModule {
     final String ivyPattern
     final String artifactPattern
     final TestFile moduleDir
@@ -32,6 +33,7 @@ class IvyFileModule extends AbstractIvyModule {
     final List dependencies = []
     final Map<String, Map> configurations = [:]
     final List artifacts = []
+    final Map extendsFrom = [:]
     String status = "integration"
     boolean noMetaData
     int publishCount = 1
@@ -44,13 +46,16 @@ class IvyFileModule extends AbstractIvyModule {
         this.organisation = organisation
         this.module = module
         this.revision = revision
-        artifact([:])
-        configurations['runtime'] = [extendsFrom: [], transitive: true]
-        configurations['default'] = [extendsFrom: ['runtime'], transitive: true]
+        configurations['runtime'] = [extendsFrom: [], transitive: true, visibility: 'public']
+        configurations['default'] = [extendsFrom: ['runtime'], transitive: true, visibility: 'public']
     }
 
-    IvyFileModule configuration(String name, List extendsFrom = []) {
-        configurations[name] = [extendsFrom: extendsFrom, transitive: true]
+    IvyDescriptor getParsedIvy() {
+        return new IvyDescriptor(ivyFile)
+    }
+
+    IvyFileModule configuration(Map<String, ?> options = [:], String name) {
+        configurations[name] = [extendsFrom: options.extendsFrom ?: [], transitive: options.transitive ?: true, visibility: options.visibility ?: 'public']
         return this
     }
 
@@ -64,17 +69,27 @@ class IvyFileModule extends AbstractIvyModule {
      * @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, conf: options.conf ?: '*']
+    IvyFileModule artifact(Map<String, ?> options = [:]) {
+        artifacts << toArtifact(options)
+        return this
+    }
+
+    IvyFileModule undeclaredArtifact(Map<String, ?> options) {
+        artifact(options + [undeclared: true])
         return this
     }
 
+    Map<String, ?> toArtifact(Map<String, ?> options = [:]) {
+        return [name: options.name ?: module, type: options.type ?: 'jar',
+                ext: options.ext ?: options.type ?: 'jar', classifier: options.classifier ?: null, conf: options.conf ?: '*']
+    }
+
     IvyFileModule dependsOn(String organisation, String module, String revision) {
         dependsOn([organisation: organisation, module: module, revision: revision])
         return this
     }
 
-    IvyFileModule dependsOn(Map<String, String> attributes) {
+    IvyFileModule dependsOn(Map<String, ?> attributes) {
         dependencies << attributes
         return this
     }
@@ -84,6 +99,12 @@ class IvyFileModule extends AbstractIvyModule {
         return this
     }
 
+    IvyFileModule extendsFrom(Map<String, ?> attributes) {
+        this.extendsFrom.clear()
+        this.extendsFrom.putAll(attributes)
+        return this
+    }
+
     IvyFileModule nonTransitive(String config) {
         configurations[config].transitive = false
         return this
@@ -100,75 +121,87 @@ class IvyFileModule extends AbstractIvyModule {
     }
 
     TestFile getIvyFile() {
-        def path = IvyPatternHelper.substitute(ivyPattern, ModuleRevisionId.newInstance(organisation, module, revision))
+        def path = IvyPatternHelper.substitute(ivyPattern, new ModuleRevisionId(new ModuleId(organisation, module), revision))
         return moduleDir.file(path)
     }
 
     TestFile getJarFile() {
-        def path = IvyPatternHelper.substitute(artifactPattern, ModuleRevisionId.newInstance(organisation, module, revision), null, "jar", "jar")
+        def path = IvyPatternHelper.substitute(artifactPattern, new ModuleRevisionId(new ModuleId(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() {
+    IvyFileModule publishWithChangedContent() {
         publishCount++
         publish()
     }
 
+    String getPublicationDate() {
+        return String.format("2010010112%04d", publishCount)
+    }
+
     /**
      * Publishes ivy.xml (if enabled) plus all artifacts
      */
-    IvyModule publish() {
+    IvyFileModule publish() {
         moduleDir.createDir()
 
+        if (artifacts.empty) {
+            artifact([:])
+        }
+
         artifacts.each { artifact ->
             def artifactFile = file(artifact)
-            publish(artifactFile) {
-                artifactFile.text = "${artifactFile.name} : $publishCount"
+            publish(artifactFile) { Writer writer ->
+                writer << "${artifactFile.name} : $artifactContent"
             }
         }
         if (noMetaData) {
             return this
         }
 
-        publish(ivyFile) {
-            transformer.transform(ivyFile, new Action<Writer>() {
+        publish(ivyFile) { Writer writer ->
+            transformer.transform(writer, new Action<Writer>() {
                 void execute(Writer ivyFileWriter) {
                     ivyFileWriter << """<?xml version="1.0" encoding="UTF-8"?>
 <ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
-    <!-- ${publishCount} -->
+    <!-- ${getArtifactContent()} -->
 	<info organisation="${organisation}"
 		module="${module}"
 		revision="${revision}"
 		status="${status}"
-	/>
+        publication="${getPublicationDate()}"
+	>"""
+        if (extendsFrom) {
+            ivyFileWriter << "<extends organisation='${extendsFrom.organisation}' module='${extendsFrom.module}' revision='${extendsFrom.revision}'"
+            if (extendsFrom.location) {
+                ivyFileWriter << " location='${extendsFrom.location}'"
+            }
+            ivyFileWriter << "/>"
+        }
+                    ivyFileWriter << """</info>
 	<configurations>"""
             configurations.each { name, config ->
-                ivyFileWriter << "<conf name='$name' visibility='public'"
+                ivyFileWriter << "<conf name='$name'"
                 if (config.extendsFrom) {
                     ivyFileWriter << " extends='${config.extendsFrom.join(',')}'"
                 }
                 if (!config.transitive) {
                     ivyFileWriter << " transitive='false'"
                 }
+                ivyFileWriter << " visibility='$config.visibility'"
                 ivyFileWriter << "/>"
             }
             ivyFileWriter << """</configurations>
 	<publications>
 """
             artifacts.each { artifact ->
-                ivyFileWriter << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.type}" conf="${artifact.conf}" m:classifier="${artifact.classifier ?: ''}"/>
+                if (!artifact.undeclared) {
+                    ivyFileWriter << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.ext}" conf="${artifact.conf}" m:classifier="${artifact.classifier ?: ''}"/>
 """
+                }
             }
             ivyFileWriter << """
 	</publications>
@@ -190,17 +223,19 @@ class IvyFileModule extends AbstractIvyModule {
         return this
     }
 
-    TestFile file(artifact) {
-        return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.type}")
+    TestFile file(Map<String, ?> options) {
+        def artifact = toArtifact(options)
+        return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.ext}")
     }
 
-    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")
+    @Override
+    protected onPublish(TestFile file) {
+        sha1File(file)
+    }
+
+    private String getArtifactContent() {
+        // Some content to include in each artifact, so that its size and content varies on each publish
+        return (0..publishCount).join("-")
     }
 
     /**
@@ -211,45 +246,50 @@ class IvyFileModule extends AbstractIvyModule {
         for (name in names) {
             allFileNames.addAll([name, "${name}.sha1"])
         }
+
         assert moduleDir.list() as Set == allFileNames
+        for (name in names) {
+            assertChecksumPublishedFor(moduleDir.file(name))
+        }
     }
 
     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()
+        assert new BigInteger(sha1File.text, 16) == getHash(testFile, "SHA1")
     }
 
     void assertNotPublished() {
         ivyFile.assertDoesNotExist()
     }
 
+    void assertIvyAndJarFilePublished() {
+        assertArtifactsPublished(ivyFile.name, jarFile.name)
+        assertPublished()
+    }
+
     void assertPublished() {
-        assert ivyFile.assertExists()
-        assert ivy.organisation == organisation
-        assert ivy.module == module
-        assert ivy.revision == revision
+        assert ivyFile.assertIsFile()
+        assert parsedIvy.organisation == organisation
+        assert parsedIvy.module == module
+        assert parsedIvy.revision == revision
     }
 
     void assertPublishedAsJavaModule() {
         assertPublished()
         assertArtifactsPublished("${module}-${revision}.jar", "ivy-${revision}.xml")
-        ivy.expectArtifact(module, "jar").hasAttributes("jar", "jar", ["runtime"], null)
+        parsedIvy.expectArtifact(module, "jar").hasAttributes("jar", "jar", ["runtime"], null)
     }
 
     void assertPublishedAsWebModule() {
         assertPublished()
         assertArtifactsPublished("${module}-${revision}.war", "ivy-${revision}.xml")
-        ivy.expectArtifact(module, "war").hasAttributes("war", "war", ["master"])
+        parsedIvy.expectArtifact(module, "war").hasAttributes("war", "war", ["master"])
     }
 
     void assertPublishedAsEarModule() {
         assertPublished()
         assertArtifactsPublished("${module}-${revision}.ear", "ivy-${revision}.xml")
-        ivy.expectArtifact(module, "ear").hasAttributes("ear", "ear", ["master"])
+        parsedIvy.expectArtifact(module, "ear").hasAttributes("ear", "ear", ["master"])
     }
 }
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
index 6abdc4d..9fd679f 100644
--- 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
@@ -16,6 +16,7 @@
 package org.gradle.test.fixtures.ivy
 
 import org.apache.ivy.core.IvyPatternHelper
+import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.test.fixtures.file.TestFile
 
@@ -59,8 +60,16 @@ class IvyFileRepository implements IvyRepository {
     }
 
     IvyFileModule module(String organisation, String module, Object revision = '1.0') {
+        return createModule(organisation, module, revision as String)
+    }
+
+    IvyFileModule module(String module) {
+        return createModule("org.gradle.test", module, '1.0')
+    }
+
+    private IvyFileModule createModule(String organisation, String module, String revision) {
         def revisionString = revision.toString()
-        def path = IvyPatternHelper.substitute(dirPattern, ModuleRevisionId.newInstance(organisation, module, revisionString))
+        def path = IvyPatternHelper.substitute(dirPattern, new ModuleRevisionId(new ModuleId(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
index 52dca48..eb9bae7 100644
--- 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
@@ -16,20 +16,40 @@
 
 package org.gradle.test.fixtures.ivy
 
+import org.gradle.test.fixtures.HttpModule
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.maven.HttpArtifact
 import org.gradle.test.fixtures.server.http.HttpServer
 
-class IvyHttpModule extends AbstractIvyModule {
+class IvyHttpModule implements IvyModule, HttpModule {
+    public final IvyHttpRepository repository
     private final IvyFileModule backingModule
     private final HttpServer server
     private final String prefix
 
-    IvyHttpModule(HttpServer server, String prefix, IvyFileModule backingModule) {
+    IvyHttpModule(IvyHttpRepository repository, HttpServer server, String prefix, IvyFileModule backingModule) {
+        this.repository = repository
         this.prefix = prefix
         this.server = server
         this.backingModule = backingModule
     }
 
+    String getOrganisation() {
+        return backingModule.organisation
+    }
+
+    String getModule() {
+        return backingModule.module
+    }
+
+    String getRevision() {
+        return backingModule.revision
+    }
+
+    IvyDescriptor getParsedIvy() {
+        return backingModule.parsedIvy
+    }
+
     IvyHttpModule publish() {
         backingModule.publish()
         return this
@@ -55,119 +75,86 @@ class IvyHttpModule extends AbstractIvyModule {
         return this
     }
 
-    IvyHttpModule dependsOn(Map<String, String> attributes) {
+    IvyHttpModule dependsOn(Map<String, ?> attributes) {
         backingModule.dependsOn(attributes)
         return this
     }
 
-    IvyHttpModule artifact(Map<String, ?> options) {
+    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 expectIvyGetBroken() {
-        server.expectGetBroken("$prefix/$ivyFile.name")
-    }
-
-    void expectIvyHead() {
-        server.expectHead("$prefix/$ivyFile.name", ivyFile)
+    IvyHttpModule undeclaredArtifact(Map<String, ?> options = [:]) {
+        backingModule.undeclaredArtifact(options)
+        return this
     }
 
-    void expectIvyHeadBroken() {
-        server.expectHeadBroken("$prefix/$ivyFile.name")
+    IvyHttpModule extendsFrom(Map<String, ?> attributes) {
+        backingModule.extendsFrom(attributes)
+        return this
     }
 
-    void expectIvySha1Get() {
-        server.expectGet("$prefix/${ivyFile.name}.sha1", backingModule.sha1File(ivyFile))
+    IvyHttpModule configuration(Map<String, ?> options = [:], String name) {
+        backingModule.configuration(options, name)
+        return this
     }
 
-    void expectIvySha1GetMissing() {
-        server.expectGetMissing("$prefix/${ivyFile.name}.sha1")
+    IvyHttpModule withXml(Closure action) {
+        backingModule.withXml(action)
+        return this
     }
 
-    void expectJarGet() {
-        server.expectGet("$prefix/$jarFile.name", jarFile)
+    TestFile getIvyFile() {
+        return backingModule.ivyFile
     }
 
-    void expectJarGetMissing() {
-        server.expectGetMissing("$prefix/$jarFile.name")
+    TestFile getJarFile() {
+        return backingModule.jarFile
     }
 
-    void expectJarGetBroken() {
-        server.expectGetBroken("$prefix/$jarFile.name")
+    IvyModuleHttpArtifact getArtifact(Map<String, ?> options) {
+        return new IvyModuleHttpArtifact(server, prefix, backingModule.file(options))
     }
 
-    void expectJarHead() {
-        server.expectHead("$prefix/$jarFile.name", jarFile)
+    void allowAll() {
+        server.allowGetOrHead(prefix, backingModule.moduleDir)
     }
 
-    void expectJarHeadMissing() {
-        server.expectHeadMissing("$prefix/$jarFile.name")
+    HttpArtifact getIvy() {
+        return new IvyModuleHttpArtifact(server, prefix, ivyFile)
     }
 
-    void expectJarSha1Get() {
-        server.expectGet("$prefix/${jarFile.name}.sha1", backingModule.sha1File(jarFile))
+    HttpArtifact getJar() {
+        return new IvyModuleHttpArtifact(server, prefix, jarFile)
     }
 
-    void expectJarSha1GetMissing() {
-        server.expectGetMissing("$prefix/${jarFile.name}.sha1")
+    void assertIvyAndJarFilePublished() {
+        backingModule.assertIvyAndJarFilePublished()
     }
 
-    void expectArtifactGet(String name) {
-        def artifactFile = backingModule.artifactFile(name)
-        server.expectGet("$prefix/$artifactFile.name", artifactFile)
-    }
+    private class IvyModuleHttpArtifact extends HttpArtifact {
+        final TestFile backingFile
 
-    void expectArtifactGet(Map options) {
-        def mappedOptions = [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
-        def artifactFile = backingModule.file(mappedOptions)
-        server.expectGet("$prefix/$artifactFile.name", artifactFile)
-    }
+        IvyModuleHttpArtifact(HttpServer server, String modulePath, TestFile backingFile) {
+            super(server, modulePath)
+            this.backingFile = backingFile
+        }
 
-    void expectPut(String username, String password, File dir, String... artifactNames) {
-        artifactNames.each {
-            server.expectPut("$prefix/$it", username, password, new File(dir, it))
+        @Override
+        TestFile getFile() {
+            return backingFile
         }
-    }
 
-    void expectArtifactHead(Map options) {
-        def mappedOptions = [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
-        def artifactFile = backingModule.file(mappedOptions)
-        server.expectHead("$prefix/$artifactFile.name", artifactFile)
-    }
+        @Override
+        protected TestFile getSha1File() {
+            return backingModule.getSha1File(backingFile)
+        }
 
-    void expectArtifactSha1Get(Map options) {
-        def mappedOptions = [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
-        def artifactFile = backingModule.file(mappedOptions)
-        server.expectGet("$prefix/${artifactFile.name}.sha1", backingModule.sha1File(artifactFile))
+        @Override
+        protected TestFile getMd5File() {
+            return backingModule.getMd5File(backingFile)
+        }
     }
 }
 
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
index ed777db..4f05bce 100644
--- 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
@@ -16,9 +16,10 @@
 
 package org.gradle.test.fixtures.ivy
 
+import org.gradle.test.fixtures.HttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 
-class IvyHttpRepository implements IvyRepository {
+class IvyHttpRepository implements IvyRepository, HttpRepository {
     private final HttpServer server
     private final IvyFileRepository backingRepository
     private final String contextPath
@@ -44,6 +45,10 @@ class IvyHttpRepository implements IvyRepository {
         return "$uri/${backingRepository.baseArtifactPattern}"
     }
 
+    void allowDirectoryListGet(String organisation, String module) {
+        server.allowGetDirectoryListing("$contextPath/$organisation/$module/", backingRepository.module(organisation, module, "1.0").moduleDir.parentFile)
+    }
+
     void expectDirectoryListGet(String organisation, String module) {
         server.expectGetDirectoryListing("$contextPath/$organisation/$module/", backingRepository.module(organisation, module, "1.0").moduleDir.parentFile)
     }
@@ -57,6 +62,6 @@ class IvyHttpRepository implements IvyRepository {
     }
 
     IvyHttpModule module(String organisation, String module, Object revision = "1.0") {
-        return new IvyHttpModule(server, "$contextPath/$organisation/$module/$revision", backingRepository.module(organisation, module, revision))
+        return new IvyHttpModule(this, 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
index 9d1e47b..76e43f7 100644
--- 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
@@ -16,11 +16,17 @@
 
 package org.gradle.test.fixtures.ivy;
 
+import groovy.lang.Closure;
+import org.gradle.test.fixtures.Module;
 import org.gradle.test.fixtures.file.TestFile;
 
 import java.util.Map;
 
-public interface IvyModule {
+public interface IvyModule extends Module {
+    String getOrganisation();
+    String getModule();
+    String getRevision();
+
     TestFile getIvyFile();
 
     TestFile getJarFile();
@@ -34,19 +40,38 @@ public interface IvyModule {
 
     IvyModule dependsOn(String organisation, String module, String revision);
 
-    IvyModule dependsOn(Map<String, String> attributes);
+    IvyModule extendsFrom(Map<String, ?> attributes);
+
+    IvyModule dependsOn(Map<String, ?> attributes);
 
     IvyModule artifact(Map<String, ?> options);
 
     /**
-     * Publishes ivy.xml plus all artifacts with different content to previous publication.
+     * Adds an artifact that is not declared in the ivy.xml file.
+     */
+    IvyModule undeclaredArtifact(Map<String, ?> options);
+
+    IvyModule withXml(Closure action);
+
+    IvyModule configuration(String name);
+
+    IvyModule configuration(Map<String, ?> options, String name);
+
+    /**
+     * Publishes ivy.xml plus all artifacts with different content (and size) to previous publication.
      */
     IvyModule publishWithChangedContent();
 
     /**
-     * Publishes ivy.xml plus all artifacts
+     * Publishes ivy.xml plus all artifacts. Publishes only those artifacts whose content has changed since the
+     * last call to {@code #publish()}.
      */
     IvyModule publish();
 
-    IvyDescriptor getIvy();
+    IvyDescriptor getParsedIvy();
+
+    /**
+     * Assert that exactly the ivy.xml and jar file for this module, plus checksum files, have been published.
+     */
+    void assertIvyAndJarFilePublished();
 }
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
index 5195945..b33b0cc 100644
--- 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
@@ -15,7 +15,9 @@
  */
 package org.gradle.test.fixtures.ivy
 
-interface IvyRepository {
+import org.gradle.test.fixtures.Repository
+
+interface IvyRepository extends Repository {
     URI getUri()
 
     String getArtifactPattern()
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/AbstractMavenModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/AbstractMavenModule.groovy
new file mode 100644
index 0000000..bc4080b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/AbstractMavenModule.groovy
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.maven
+
+import groovy.xml.MarkupBuilder
+import org.gradle.test.fixtures.AbstractModule
+import org.gradle.test.fixtures.file.TestFile
+
+import java.text.SimpleDateFormat
+
+abstract class AbstractMavenModule extends AbstractModule implements MavenModule {
+    protected static final String MAVEN_METADATA_FILE = "maven-metadata.xml"
+    final TestFile moduleDir
+    final String groupId
+    final String artifactId
+    final String version
+    String parentPomSection
+    String type = 'jar'
+    String packaging
+    int publishCount = 1
+    private final List dependencies = []
+    private final List artifacts = []
+    final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
+    final timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss")
+
+    AbstractMavenModule(TestFile moduleDir, String groupId, String artifactId, String version) {
+        this.moduleDir = moduleDir
+        this.groupId = groupId
+        this.artifactId = artifactId
+        this.version = version
+    }
+
+    MavenModule parent(String group, String artifactId, String version) {
+        parentPomSection = """
+<parent>
+  <groupId>${group}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <version>${version}</version>
+</parent>
+"""
+        return this
+    }
+
+    TestFile getArtifactFile(Map options = [:]) {
+        if (version.endsWith("-SNAPSHOT") && !metaDataFile.exists() && uniqueSnapshots) {
+            def artifact = toArtifact(options)
+            return moduleDir.file("${artifactId}-${version}${artifact.classifier ? "-${artifact.classifier}" : ""}.${artifact.type}")
+        }
+        return artifactFile(options)
+    }
+
+    abstract boolean getUniqueSnapshots()
+
+    String getPublishArtifactVersion() {
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            return "${version.replaceFirst('-SNAPSHOT$', '')}-${getUniqueSnapshotVersion()}"
+        }
+        return version
+    }
+
+    private String getUniqueSnapshotVersion() {
+        assert uniqueSnapshots && version.endsWith('-SNAPSHOT')
+        if (metaDataFile.isFile()) {
+            def metaData = new XmlParser().parse(metaDataFile.assertIsFile())
+            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
+            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
+            return "${timestamp}-${build}"
+        }
+        return "${timestampFormat.format(publishTimestamp)}-${publishCount}"
+    }
+
+    MavenModule dependsOn(String... dependencyArtifactIds) {
+        for (String id : dependencyArtifactIds) {
+            dependsOn(groupId, id, '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
+    }
+
+    MavenModule hasPackaging(String packaging) {
+        this.packaging = packaging
+        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
+    }
+
+    String getPackaging() {
+        return packaging
+    }
+
+    List getDependencies() {
+        return dependencies
+    }
+
+    List getArtifacts() {
+        return artifacts
+    }
+
+    void assertNotPublished() {
+        pomFile.assertDoesNotExist()
+    }
+
+    void assertPublished() {
+        assert pomFile.assertExists()
+        assert parsedPom.groupId == groupId
+        assert parsedPom.artifactId == artifactId
+        assert parsedPom.version == version
+    }
+
+    void assertPublishedAsPomModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == "pom"
+    }
+
+    void assertPublishedAsJavaModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.jar", "${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == null
+    }
+
+    void assertPublishedAsWebModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.war", "${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == 'war'
+    }
+
+    void assertPublishedAsEarModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.ear", "${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == 'ear'
+    }
+
+    /**
+     * Asserts that exactly the given artifacts have been deployed, along with their checksum files
+     */
+    void assertArtifactsPublished(String... names) {
+        def artifactNames = names as Set
+        if (publishesMetaDataFile()) {
+            artifactNames.add(MAVEN_METADATA_FILE)
+        }
+        assert moduleDir.isDirectory()
+        Set actual = moduleDir.list() as Set
+        for (name in artifactNames) {
+            assert actual.remove(name)
+
+            if(publishesHashFiles()) {
+                assert actual.remove("${name}.md5" as String)
+                assert actual.remove("${name}.sha1" as String)
+            }
+        }
+        assert actual.isEmpty()
+    }
+
+    //abstract String getPublishArtifactVersion()
+
+    MavenPom getParsedPom() {
+        return new MavenPom(pomFile)
+    }
+
+    DefaultMavenMetaData getRootMetaData() {
+        new DefaultMavenMetaData(rootMetaDataFile)
+    }
+
+    TestFile getPomFile() {
+        return moduleDir.file("$artifactId-${publishArtifactVersion}.pom")
+    }
+
+    TestFile getMetaDataFile() {
+        moduleDir.file(MAVEN_METADATA_FILE)
+    }
+
+    TestFile getRootMetaDataFile() {
+        moduleDir.parentFile.file(MAVEN_METADATA_FILE)
+    }
+
+    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)
+    }
+
+    MavenModule publishWithChangedContent() {
+        publishCount++
+        return publish()
+    }
+
+    protected 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
+    }
+
+    Date getPublishTimestamp() {
+        return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000)
+    }
+
+    MavenModule publishPom() {
+        moduleDir.createDir()
+        def rootMavenMetaData = getRootMetaDataFile()
+
+        updateRootMavenMetaData(rootMavenMetaData)
+
+        if (publishesMetaDataFile()) {
+            publish(metaDataFile) { Writer writer ->
+                writer << getMetaDataFileContent()
+            }
+        }
+
+        publish(pomFile) { Writer writer ->
+            def pomPackaging = packaging ?: type;
+            writer << """
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <!-- ${getArtifactContent()} -->
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <packaging>$pomPackaging</packaging>
+  <version>$version</version>
+  <description>Published on $publishTimestamp</description>"""
+
+            if (parentPomSection) {
+                writer << "\n$parentPomSection\n"
+            }
+
+            if (!dependencies.empty) {
+                writer << """
+  <dependencies>"""
+            }
+
+            dependencies.each { dependency ->
+                def typeAttribute = dependency['type'] == null ? "" : "<type>$dependency.type</type>"
+                writer << """
+    <dependency>
+      <groupId>$dependency.groupId</groupId>
+      <artifactId>$dependency.artifactId</artifactId>
+      <version>$dependency.version</version>
+      $typeAttribute
+    </dependency>"""
+            }
+
+            if (!dependencies.empty) {
+                writer << """
+  </dependencies>"""
+            }
+
+            writer << "\n</project>"
+        }
+        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) { Writer writer ->
+            def builder = new MarkupBuilder(writer)
+            builder.metadata {
+                groupId(groupId)
+                artifactId(artifactId)
+                version(allVersions.max())
+                versioning {
+                    versions {
+                        allVersions.each {currVersion ->
+                            version(currVersion)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    abstract String getMetaDataFileContent()
+
+    MavenModule publish() {
+
+        publishPom()
+
+        artifacts.each { artifact ->
+            publishArtifact(artifact)
+        }
+        if (type != 'pom') {
+            publishArtifact([:])
+        }
+
+        return this
+    }
+
+    File publishArtifact(Map<String, ?> artifact) {
+        def artifactFile = artifactFile(artifact)
+
+        publish(artifactFile) { Writer writer ->
+            writer << "${artifactFile.name} : $artifactContent"
+        }
+        return artifactFile
+    }
+
+    protected String getArtifactContent() {
+        // Some content to include in each artifact, so that its size and content varies on each publish
+        return (0..publishCount).join("-")
+    }
+
+    protected abstract boolean publishesMetaDataFile()
+    protected abstract boolean publishesHashFiles()
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy
index 80fba5b..b0a7f3b 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy
@@ -19,7 +19,7 @@ package org.gradle.test.fixtures.maven
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.server.http.HttpServer
 
-class BasicHttpResource extends HttpResource{
+class BasicHttpResource extends HttpResource {
     private final String path
     private final File file
 
@@ -30,11 +30,6 @@ class BasicHttpResource extends HttpResource{
     }
 
     @Override
-    void expectGetMissing() {
-        server.expectGetMissing(path)
-    }
-
-    @Override
     TestFile getFile() {
         return file
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy
index 8ee2afe..14fc453 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy
@@ -16,10 +16,9 @@
 
 package org.gradle.test.fixtures.maven
 
-import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.internal.hash.HashUtil
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.hash.HashUtil
 
 abstract class HttpArtifact extends HttpResource {
 
@@ -30,14 +29,6 @@ abstract class HttpArtifact extends HttpResource {
         this.modulePath = modulePath
     }
 
-    void expectHeadMissing() {
-        server.expectHeadMissing(path)
-    }
-
-    void expectGetMissing(PasswordCredentials passwordCredentials = null) {
-        server.expectGetMissing(path, passwordCredentials)
-    }
-
     HttpResource getMd5() {
         return new BasicHttpResource(server, getMd5File(), "${path}.md5")
     }
@@ -46,6 +37,10 @@ abstract class HttpArtifact extends HttpResource {
         return new BasicHttpResource(server, getSha1File(), "${path}.sha1")
     }
 
+    URI getUri() {
+        return new URI("http://localhost:${server.port}${path}")
+    }
+
     protected String getPath() {
         "${modulePath}/${file.name}"
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy
index be5b18d..734dcb8 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy
@@ -28,6 +28,10 @@ abstract class HttpResource {
         this.server = server
     }
 
+    void allowGetOrHead() {
+        server.allowGetOrHead(getPath(), file)
+
+    }
     void expectGet() {
         server.expectGet(getPath(), file)
     }
@@ -36,20 +40,34 @@ abstract class HttpResource {
         server.expectGetBroken(getPath())
     }
 
+    void expectGetMissing(PasswordCredentials credentials = null) {
+        server.expectGetMissing(getPath(), credentials)
+    }
+
     void expectHead() {
         server.expectHead(getPath(), file)
     }
 
+    void expectHeadMissing() {
+        server.expectHeadMissing(path)
+    }
+
+    void expectHeadBroken() {
+        server.expectHeadBroken(path)
+    }
+
     void expectPut(PasswordCredentials credentials) {
         expectPut(200, credentials)
     }
 
+    void expectPut(String username, String password) {
+        server.expectPut(getPath(), username, password, getFile())
+    }
+
     void expectPut(Integer statusCode = 200, PasswordCredentials credentials = null) {
         server.expectPut(getPath(), getFile(), statusCode, credentials)
     }
 
-    abstract void expectGetMissing();
-
     abstract TestFile getFile();
 
     abstract protected String getPath();
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
index 5dfde12..27818e0 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
@@ -37,11 +37,11 @@ class M2Installation implements Action<GradleExecuter> {
         globalSettingsFile = globalMavenDirectory.file("conf/settings.xml")
     }
 
-    MavenFileRepository mavenRepo() {
-        new MavenFileRepository(userM2Directory.file("repository"))
+    MavenLocalRepository mavenRepo() {
+        new MavenLocalRepository(userM2Directory.file("repository"))
     }
 
-    M2Installation generateUserSettingsFile(MavenFileRepository userRepository) {
+    M2Installation generateUserSettingsFile(MavenLocalRepository userRepository) {
         userSettingsFile.text = """
 <settings>
     <localRepository>${userRepository.rootDir.absolutePath}</localRepository>
@@ -49,7 +49,7 @@ class M2Installation implements Action<GradleExecuter> {
         return this
     }
 
-    M2Installation generateGlobalSettingsFile(MavenFileRepository globalRepository = mavenRepo()) {
+    M2Installation generateGlobalSettingsFile(MavenLocalRepository globalRepository = mavenRepo()) {
         globalSettingsFile.createFile().text = """
 <settings>
     <localRepository>${globalRepository.rootDir.absolutePath}</localRepository>
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy
index c439240..45ee411 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy
@@ -27,4 +27,9 @@ class MavenDependency {
         assert this.type == type
         return this
     }
+
+    @Override
+    public String toString() {
+        return String.format("MavenDependency %s:%s:%s:%s@%s", groupId, artifactId, version, classifier, type)
+    }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
index 9b33a43..7d95c54 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
@@ -15,220 +15,29 @@
  */
 package org.gradle.test.fixtures.maven
 
-import groovy.xml.MarkupBuilder
 import org.gradle.test.fixtures.file.TestFile
-import org.gradle.util.hash.HashUtil
 
-import java.text.SimpleDateFormat
-
-class MavenFileModule implements MavenModule {
-    private static final String MAVEN_METADATA_FILE = "maven-metadata.xml"
-    final TestFile moduleDir
-    final String groupId
-    final String artifactId
-    final String version
-    private 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 = []
+class MavenFileModule extends AbstractMavenModule {
     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 parent(String group, String artifactId, String version) {
-        parentPomSection = """
-<parent>
-  <groupId>${group}</groupId>
-  <artifactId>${artifactId}</artifactId>
-  <version>${version}</version>
-</parent>
-"""
-        return this
-    }
-
-    MavenFileModule dependsOn(String... dependencyArtifactIds) {
-        for (String id : dependencyArtifactIds) {
-            dependsOn(groupId, id, '1.0')
-        }
-        return this
+        super(moduleDir, groupId, artifactId, version)
     }
 
-    MavenFileModule dependsOn(String group, String artifactId, String version, String type = null) {
-        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
-        return this
+    boolean getUniqueSnapshots() {
+        return uniqueSnapshots
     }
 
-    MavenFileModule hasPackaging(String packaging) {
-        this.packaging = packaging
-        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() {
+    MavenModule withNonUniqueSnapshots() {
         uniqueSnapshots = false;
         return this;
     }
 
-    void assertNotPublished() {
-        pomFile.assertDoesNotExist()
-    }
-
-    void assertPublished() {
-        assert pomFile.assertExists()
-        assert parsedPom.groupId == groupId
-        assert parsedPom.artifactId == artifactId
-        assert parsedPom.version == version
-    }
-
-    void assertPublishedAsPomModule() {
-        assertPublished()
-        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.pom")
-        assert parsedPom.packaging == "pom"
-    }
-
-    void assertPublishedAsJavaModule() {
-        assertPublished()
-        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.jar", "${artifactId}-${publishArtifactVersion}.pom")
-        assert parsedPom.packaging == null
-    }
-
-    void assertPublishedAsWebModule() {
-        assertPublished()
-        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.war", "${artifactId}-${publishArtifactVersion}.pom")
-        assert parsedPom.packaging == 'war'
-    }
-
-    void assertPublishedAsEarModule() {
-        assertPublished()
-        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.ear", "${artifactId}-${publishArtifactVersion}.pom")
-        assert parsedPom.packaging == 'ear'
-    }
-
-    /**
-     * Asserts that exactly the given artifacts have been deployed, along with their checksum files
-     */
-    void assertArtifactsPublished(String... names) {
-        def artifactNames = names as Set
-        if (uniqueSnapshots && version.endsWith('-SNAPSHOT')) {
-            artifactNames.add(MAVEN_METADATA_FILE)
-        }
-        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 getParsedPom() {
-        return new MavenPom(pomFile)
-    }
-
-    DefaultMavenMetaData getRootMetaData() {
-        new DefaultMavenMetaData(rootMetaDataFile)
-    }
-
-    TestFile getPomFile() {
-        return moduleDir.file("$artifactId-${publishArtifactVersion}.pom")
-    }
-
-    TestFile getMetaDataFile() {
-        moduleDir.file(MAVEN_METADATA_FILE)
-    }
-
-    TestFile getRootMetaDataFile() {
-        moduleDir.parentFile.file(MAVEN_METADATA_FILE)
-    }
-
-    TestFile getArtifactFile(Map options = [:]) {
-        if (version.endsWith("-SNAPSHOT") && !metaDataFile.exists() && uniqueSnapshots) {
-            def artifact = toArtifact(options)
-            return moduleDir.file("${artifactId}-${version}${artifact.classifier ? "-${artifact.classifier}" : ""}.${artifact.type}")
-        }
-        return artifactFile(options)
-    }
-
-    TestFile getArtifactSha1File() {
-        return getSha1File(artifactFile)
-    }
-
-    TestFile getArtifactMd5File() {
-        return getMd5File(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$', '')}-${uniqueSnapshotVersion}"
-        }
-        return version
-    }
-
-    private String getUniqueSnapshotVersion() {
-        assert uniqueSnapshots && version.endsWith('-SNAPSHOT')
-        if (metaDataFile.isFile()) {
-            def metaData = new XmlParser().parse(metaDataFile.assertIsFile())
-            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
-            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
-            return "${timestamp}-${build}"
-        }
-        return "${timestampFormat.format(publishTimestamp)}-${publishCount}"
-    }
-
-    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")) {
-            publish(metaDataFile) {
-                metaDataFile.text = """
+    @Override
+    String getMetaDataFileContent() {
+        """
 <metadata>
-  <!-- $publishCount -->
+  <!-- ${getArtifactContent()} -->
   <groupId>$groupId</groupId>
   <artifactId>$artifactId</artifactId>
   <version>$version</version>
@@ -241,144 +50,21 @@ class MavenFileModule implements MavenModule {
   </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) {
+    @Override
+    protected onPublish(TestFile file) {
         sha1File(file)
         md5File(file)
     }
 
-    TestFile getSha1File(File file) {
-        getHashFile(file, "sha1")
-    }
-
-    TestFile sha1File(File file) {
-        hashFile(file, "sha1");
-    }
-
-    TestFile getMd5File(File file) {
-        getHashFile(file, "md5")
-    }
-
-    TestFile md5File(File file) {
-        hashFile(file, "md5")
-    }
-
-    private TestFile hashFile(TestFile file, String algorithm) {
-        def hashFile = getHashFile(file, algorithm)
-        hashFile.text = getHash(file, algorithm)
-        return hashFile
-    }
-
-    protected TestFile getHashFile(TestFile file, String algorithm) {
-        file.parentFile.file("${file.name}.${algorithm}")
+    @Override
+    protected boolean publishesMetaDataFile() {
+        uniqueSnapshots && version.endsWith("-SNAPSHOT")
     }
 
-    protected String getHash(TestFile file, String algorithm) {
-        HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
+    @Override
+    protected boolean publishesHashFiles() {
+        true
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy
index eef54df..30d457d 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-
 package org.gradle.test.fixtures.maven
 
 import org.gradle.test.fixtures.file.TestFile
 
 /**
- * A fixture for dealing with local Maven repositories.
+ * A fixture for dealing with file Maven repositories.
  */
 class MavenFileRepository implements MavenRepository {
     final TestFile rootDir
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy
index 7e747fe..dfe0ecb 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy
@@ -18,10 +18,11 @@
 
 package org.gradle.test.fixtures.maven
 
+import org.gradle.test.fixtures.HttpModule
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.server.http.HttpServer
 
-class MavenHttpModule implements MavenModule {
+class MavenHttpModule implements MavenModule, HttpModule {
     private final HttpServer server
     private final String moduleRootPath
     private final MavenFileModule backingModule
@@ -44,16 +45,27 @@ class MavenHttpModule implements MavenModule {
      * Adds an additional artifact to this module.
      * @param options Can specify any of: type or classifier
      */
-    HttpArtifact artifact(Map<String, ?> options) {
+    HttpArtifact artifact(Map<String, ?> options = [:]) {
         backingModule.artifact(options)
         return new MavenHttpArtifact(server, "${moduleRootPath}/${backingModule.version}", backingModule, options)
     }
 
+    MavenHttpModule withSourceAndJavadoc() {
+        artifact(classifier: "sources")
+        artifact(classifier: "javadoc")
+        return this
+    }
+
     MavenHttpModule publish() {
         backingModule.publish()
         return this
     }
 
+    MavenHttpModule publishPom() {
+        backingModule.publishPom()
+        return this
+    }
+
     MavenHttpModule publishWithChangedContent() {
         backingModule.publishWithChangedContent()
         return this
@@ -64,7 +76,7 @@ class MavenHttpModule implements MavenModule {
         return this
     }
 
-    MavenModule parent(String group, String artifactId, String version) {
+    MavenHttpModule parent(String group, String artifactId, String version) {
         backingModule.parent(group, artifactId, version)
         return this
     }
@@ -74,11 +86,21 @@ class MavenHttpModule implements MavenModule {
         return this
     }
 
-    MavenModule hasPackaging(String packaging) {
+    MavenHttpModule dependsOn(String group, String artifactId, String version, String type) {
+        backingModule.dependsOn(group, artifactId, version, type)
+        return this
+    }
+
+    MavenHttpModule hasPackaging(String packaging) {
         backingModule.hasPackaging(packaging)
         return this
     }
 
+    MavenHttpModule hasType(String type) {
+        backingModule.hasType(type)
+        return this
+    }
+
     TestFile getPomFile() {
         return backingModule.pomFile
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy
index ad63afa..2b087e2 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-
-
 package org.gradle.test.fixtures.maven
 
+import org.gradle.test.fixtures.HttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 
 /**
  * A fixture for dealing with remote HTTP Maven repositories.
  */
-class MavenHttpRepository {
+class MavenHttpRepository implements MavenRepository, HttpRepository {
     private final HttpServer server
     private final MavenFileRepository backingRepository
     private final String contextPath
@@ -41,14 +40,8 @@ class MavenHttpRepository {
         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 expectMetaDataGetMissing(String groupId, String artifactId) {
-        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
-        server.expectGetMissing("$contextPath/$path")
+    HttpResource getModuleMetaData(String groupId, String artifactId) {
+        return module(groupId, artifactId).rootMetaData
     }
 
     void expectDirectoryListGet(String groupId, String artifactId) {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenLocalModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenLocalModule.groovy
new file mode 100644
index 0000000..a0a8117
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenLocalModule.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.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+
+class MavenLocalModule extends AbstractMavenModule {
+    private boolean uniqueSnapshots = false;
+
+    MavenLocalModule(TestFile moduleDir, String groupId, String artifactId, String version) {
+        super(moduleDir, groupId, artifactId, version)
+    }
+
+    boolean getUniqueSnapshots() {
+        return uniqueSnapshots
+    }
+
+    MavenLocalModule withNonUniqueSnapshots() {
+        //NO-OP for mavenLocal cache.
+        this
+    }
+
+    @Override
+    String getMetaDataFileContent() {
+        """
+<metadata>
+  <!-- ${getArtifactContent()} -->
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <version>$version</version>
+  <versioning>
+    <snapshot>
+      <localCopy>true</localCopy>
+    </snapshot>
+    <lastUpdated>${updateFormat.format(publishTimestamp)}</lastUpdated>
+  </versioning>
+</metadata>
+"""
+    }
+
+    @Override
+    protected onPublish(TestFile file) {
+    }
+
+    @Override
+    protected boolean publishesMetaDataFile() {
+        version.endsWith("-SNAPSHOT")
+    }
+
+    @Override
+    protected boolean publishesHashFiles() {
+        false
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenLocalRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenLocalRepository.groovy
new file mode 100644
index 0000000..7d93ee4
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenLocalRepository.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.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+
+/**
+ * A fixture for dealing with the Maven Local cache.
+ */
+class MavenLocalRepository implements MavenRepository {
+
+    final TestFile rootDir
+
+    MavenLocalRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    MavenLocalModule module(String groupId, String artifactId, Object version = '1.0') {
+        def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version")
+        return new MavenLocalModule(artifactDir, groupId, artifactId, version as String)
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy
index 5a4dace..07e4118 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy
@@ -13,21 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.test.fixtures.maven
 
+import org.gradle.test.fixtures.Module
 import org.gradle.test.fixtures.file.TestFile
 
-interface MavenModule {
+interface MavenModule extends Module {
     /**
-     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module.
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module. Publishes only those artifacts whose content has
+     * changed since the last call to {@code #publish()}.
      */
     MavenModule publish()
 
     /**
-     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with changed content to any
+     * Publishes the pom.xml only
+     */
+    MavenModule publishPom()
+
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with different content (and size) to any
      * previous publication.
      */
     MavenModule publishWithChangedContent()
@@ -38,8 +42,15 @@ interface MavenModule {
 
     MavenModule dependsOn(String group, String artifactId, String version)
 
+    MavenModule dependsOn(String group, String artifactId, String version, String type)
+
     MavenModule hasPackaging(String packaging)
 
+    /**
+     * Sets the type of the main artifact for this module.
+     */
+    MavenModule hasType(String type)
+
     TestFile getPomFile()
 
     TestFile getArtifactFile()
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy
index 0a51d3b..86a7952 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy
@@ -15,10 +15,12 @@
  */
 package org.gradle.test.fixtures.maven
 
+import org.gradle.test.fixtures.Repository
+
 /**
  * A fixture for dealing with Maven repositories.
  */
-interface MavenRepository {
+interface MavenRepository extends Repository {
     URI getUri()
 
     MavenModule module(String groupId, String artifactId)
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
index b67f38a..161cd92 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
@@ -40,7 +40,7 @@ class MavenScope {
     MavenDependency expectDependency(String key) {
         final dependency = dependencies[key]
         if (dependency == null) {
-            throw new AssertionError("Could not find expected dependency $dep. Actual: ${dependencies.values()}")
+            throw new AssertionError("Could not find expected dependency $key. Actual: ${dependencies.values()}")
         }
         return dependency
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/plugin/PluginBuilder.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/plugin/PluginBuilder.groovy
new file mode 100644
index 0000000..c23af4d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/plugin/PluginBuilder.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.plugin
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.TextUtil
+
+class PluginBuilder {
+
+    final TestFile projectDir
+
+    String packageName = "org.gradle.test"
+
+    final Map<String, String> pluginIds = [:]
+    private final GradleExecuter executer
+
+    PluginBuilder(GradleExecuter executer, TestFile projectDir) {
+        this.executer = executer
+        this.projectDir = projectDir
+    }
+
+    TestFile file(String path) {
+        projectDir.file(path)
+    }
+
+    TestFile groovy(String path) {
+        file("src/main/groovy/${packageName.replaceAll("\\.", "/")}/$path")
+    }
+
+    @SuppressWarnings("GrMethodMayBeStatic")
+    String generateManagedBuildScript() {
+        """
+            apply plugin: "groovy"
+            dependencies {
+              compile localGroovy()
+              compile gradleApi()
+            }
+        """
+    }
+
+    String generateBuildScript(String additions = "") {
+        file("build.gradle").text = (generateManagedBuildScript() + additions)
+    }
+
+    void publishTo(TestFile testFile) {
+        generateBuildScript """
+            jar {
+                archiveName = "$testFile.name"
+                destinationDir = file("${TextUtil.escapeString(testFile.parentFile.absolutePath)}")
+            }
+        """
+
+        writePluginDescriptors(pluginIds)
+        executer.inDirectory(projectDir).withTasks("jar").run()
+    }
+
+    protected void writePluginDescriptors(Map<String, String> pluginIds) {
+        descriptorsDir.deleteDir()
+        pluginIds.each { id, className ->
+            descriptorsDir.file("${id}.properties") << "implementation-class=${packageName}.${className}"
+        }
+    }
+
+    TestFile getDescriptorsDir() {
+        file("src/main/resources/META-INF/gradle-plugins")
+    }
+
+    PluginBuilder addPlugin(String impl, String id = "test-plugin", String className = "TestPlugin") {
+        pluginIds[id] = className
+
+        groovy("${className}.groovy") << """
+            package $packageName
+
+            class $className implements $Plugin.name<$Project.name> {
+                void apply($Project.name project) {
+                    $impl
+                }
+            }
+        """
+        this
+    }
+
+    PluginBuilder addPluginWithPrintlnTask(String taskName, String message, String id = "test-plugin", String className = "TestPlugin") {
+        addPlugin("project.task(\"$taskName\") << { println \"$message\" }", id, className)
+        this
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/publish/Identifier.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/publish/Identifier.java
deleted file mode 100644
index cf46a7e..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/publish/Identifier.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.publish;
-
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.util.GUtil;
-
-public class Identifier {
-    private static final String PUNCTUATION_CHARS = "-'!@#$%^&*()_+=,.?{}[]<>";
-    private static final String NON_ASCII_CHARS = "-√æず∫ʙぴ₦ガき∆ç√∫";
-    private static final String FILESYSTEM_RESERVED_CHARS = "-./\\?%*:|\"<>";
-    private static final String XML_MARKUP_CHARS = "-<with>some<xml-markup/></with>";
-
-    private final String suffix;
-
-    public Identifier(String suffix) {
-        this.suffix = GUtil.elvis(suffix, "");
-    }
-
-    public Identifier withPunctuation() {
-        return new Identifier(suffix + PUNCTUATION_CHARS);
-    }
-
-    public Identifier withNonAscii() {
-        return new Identifier(suffix + NON_ASCII_CHARS);
-    }
-
-    public Identifier withReservedFileSystemChars() {
-        return new Identifier(suffix + FILESYSTEM_RESERVED_CHARS);
-    }
-
-    public Identifier withMarkup() {
-        return new Identifier(suffix + XML_MARKUP_CHARS);
-    }
-
-    public Identifier withWhiteSpace() {
-        return new Identifier(suffix + " with white space");
-    }
-
-    public Identifier safeForFileName() {
-        return without(getUnsupportedFileNameCharacters());
-    }
-
-    public Identifier without(String toRemove) {
-        String newSuffix = suffix;
-        for (char c : toRemove.toCharArray()) {
-            newSuffix = newSuffix.replace(c, '-');
-        }
-        return new Identifier(newSuffix);
-    }
-
-    private static String getUnsupportedFileNameCharacters() {
-        if (OperatingSystem.current().isWindows()) {
-            return "<>:\"/\\|?*";
-        }
-        return "/\\";
-    }
-
-    public String decorate(String prefix) {
-        return prefix + suffix;
-    }
-
-    @Override
-    public String toString() {
-        return suffix;
-    }
-
-    public static Identifier getPunctuation() {
-        return new Identifier("").withPunctuation();
-    }
-
-    public static Identifier getNonAscii() {
-        return new Identifier("").withNonAscii();
-    }
-
-    public static Identifier getFileSystemReserved() {
-        return new Identifier("").withReservedFileSystemChars();
-    }
-
-    public static Identifier getXmlMarkup() {
-        return new Identifier("").withMarkup();
-    }
-
-    public static Identifier getWhiteSpace() {
-        return new Identifier("").withWhiteSpace();
-    }
-}
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
index 289c797..c7cd0c2 100755
--- 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
@@ -15,25 +15,26 @@
  */
 package org.gradle.test.fixtures.server.http
 
+import com.google.gson.Gson
+import com.google.gson.JsonElement
 import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.internal.hash.HashUtil
 import org.gradle.test.matchers.UserAgentMatcher
 import org.gradle.util.GFileUtils
-import org.gradle.util.hash.HashUtil
 import org.hamcrest.Matcher
 import org.junit.rules.ExternalResource
+import org.mortbay.jetty.*
 import org.mortbay.jetty.bio.SocketConnector
 import org.mortbay.jetty.handler.AbstractHandler
 import org.mortbay.jetty.handler.HandlerCollection
+import org.mortbay.jetty.security.*
 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.*
+import java.security.Principal
+import java.util.zip.GZIPOutputStream
 
 class HttpServer extends ExternalResource {
 
@@ -103,10 +104,21 @@ class HttpServer extends ExternalResource {
         server.setHandler(handlers)
     }
 
+    String getAddress() {
+        if (!server.started) {
+            server.start()
+        }
+        "http://localhost:${port}"
+    }
+
     void start() {
         start(0)
     }
 
+    boolean isRunning() {
+        server.running
+    }
+
     void start(int port) {
         connector = new SocketConnector()
         connector.port = port
@@ -191,13 +203,6 @@ class HttpServer extends ExternalResource {
     }
 
     /**
-     * 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) {
@@ -374,7 +379,22 @@ class HttpServer extends ExternalResource {
     }
 
     /**
-     * Allows one GET request for the given URL, returning an apache-compatible directory listing with the given File names.
+     * Allows GET requests for the given URL, returning an apache-compatible directory listing with the given File names.
+     */
+    void allowGetDirectoryListing(String path, File directory) {
+        allow(path, false, ['GET'], new Action() {
+            String getDisplayName() {
+                return "return listing of directory $directory.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                sendDirectoryListing(response, directory)
+            }
+        })
+    }
+
+    /**
+     * Expects 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() {
@@ -408,13 +428,15 @@ class HttpServer extends ExternalResource {
         if (sendLastModified) {
             response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified ?: file.lastModified())
         }
-        response.setContentLength((contentLength ?: file.length()) as int)
+        def content = file.bytes
+        response.setContentLength((contentLength ?: content.length) as int)
         response.setContentType(new MimeTypes().getMimeByExtension(file.name).toString())
         if (sendSha1Header) {
-            response.addHeader("X-Checksum-Sha1", HashUtil.sha1(file).asHexString())
+            response.addHeader("X-Checksum-Sha1", HashUtil.sha1(content).asHexString())
         }
-        addEtag(response, file.bytes, etags)
-        response.outputStream << new FileInputStream(file)
+
+        addEtag(response, content, etags)
+        response.outputStream << content
     }
 
     private addEtag(HttpServletResponse response, byte[] bytes, etagStrategy) {
@@ -486,6 +508,7 @@ class HttpServer extends ExternalResource {
                     response.sendError(500, "unexpected username '${request.remoteUser}'")
                     return
                 }
+                destFile.parentFile.mkdirs()
                 destFile.bytes = request.inputStream.bytes
             }
         }))
@@ -534,14 +557,18 @@ class HttpServer extends ExternalResource {
         }
     }
 
-    private void expect(String path, boolean recursive, Collection<String> methods, Action action, PasswordCredentials credentials = null) {
+    void expect(String path, Collection<String> methods, PasswordCredentials passwordCredentials = null, Action action) {
+        expect(path, false, methods, action, passwordCredentials)
+    }
+
+    void expect(String path, boolean matchPrefix, Collection<String> methods, Action action, PasswordCredentials credentials = null) {
         if (credentials != null) {
             action = withAuthentication(path, credentials.username, credentials.password, action)
         }
 
         ExpectOne expectation = new ExpectOne(action, methods, path)
         expections << expectation
-        add(path, recursive, methods, new AbstractHandler() {
+        add(path, matchPrefix, methods, new AbstractHandler() {
             void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
                 if (expectation.run) {
                     return
@@ -553,8 +580,8 @@ class HttpServer extends ExternalResource {
         })
     }
 
-    private void allow(String path, boolean recursive, Collection<String> methods, Action action) {
-        add(path, recursive, methods, new AbstractHandler() {
+    private void allow(String path, boolean matchPrefix, Collection<String> methods, Action action) {
+        add(path, matchPrefix, methods, new AbstractHandler() {
             void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
                 action.handle(request, response)
                 request.handled = true
@@ -562,7 +589,7 @@ class HttpServer extends ExternalResource {
         })
     }
 
-    private void add(String path, boolean recursive, Collection<String> methods, Handler handler) {
+    private void add(String path, boolean matchPrefix, Collection<String> methods, Handler handler) {
         assert path.startsWith('/')
 //        assert path == '/' || !path.endsWith('/')
         def prefix = path == '/' ? '/' : path + '/'
@@ -571,7 +598,7 @@ class HttpServer extends ExternalResource {
                 if (methods != null && !methods.contains(request.method)) {
                     return
                 }
-                boolean match = request.pathInfo == path || (recursive && request.pathInfo.startsWith(prefix))
+                boolean match = request.pathInfo == path || (matchPrefix && request.pathInfo.startsWith(prefix))
                 if (match && !request.handled) {
                     handler.handle(target, request, response, dispatch)
                 }
@@ -612,6 +639,31 @@ class HttpServer extends ExternalResource {
         void handle(HttpServletRequest request, HttpServletResponse response)
     }
 
+    static abstract class ActionSupport implements Action {
+        final String displayName
+
+        ActionSupport(String displayName) {
+            this.displayName = displayName
+        }
+    }
+
+    static class Utils {
+        static JsonElement json(HttpServletRequest request) {
+            new Gson().fromJson(request.reader, JsonElement.class)
+        }
+
+        static JsonElement json(String json) {
+            new Gson().fromJson(json, JsonElement.class)
+        }
+
+        static void json(HttpServletResponse response, Object data) {
+            if (!response.contentType) {
+                response.setContentType("application/json")
+            }
+            new Gson().toJson(data, response.writer)
+        }
+    }
+
     abstract static class AuthSchemeHandler {
         public SecurityHandler createSecurityHandler(String path, TestUserRealm realm) {
             def constraintMapping = createConstraintMapping(path)
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ServletContainer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ServletContainer.groovy
new file mode 100644
index 0000000..ea5dcac
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ServletContainer.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mortbay.jetty.Server
+import org.mortbay.jetty.webapp.WebAppContext
+
+class ServletContainer {
+    private final Server webServer = new Server(0)
+    private final warFile
+
+    ServletContainer(File warFile) {
+        this.warFile = warFile
+    }
+
+    int getPort() {
+        webServer.connectors[0].localPort
+    }
+
+    void start() {
+        def context = new WebAppContext()
+        context.war = warFile
+        webServer.addHandler(context)
+        webServer.start()
+    }
+
+    void stop() {
+        webServer.stop()
+    }
+}
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
index aa909ad..65d22a8 100644
--- 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
@@ -16,8 +16,6 @@
 
 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
@@ -31,6 +29,7 @@ import org.apache.sshd.server.session.ServerSession
 import org.apache.sshd.server.sftp.SftpSubsystem
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.AvailablePortFinder
 import org.junit.rules.ExternalResource
 
 import java.security.PublicKey
@@ -44,13 +43,12 @@ class SFTPServer extends ExternalResource {
     private TestFile configDir
 
     private SshServer sshd;
-    private com.jcraft.jsch.Session session
 
     def fileRequests = [] as Set
 
     public SFTPServer(TestDirectoryProvider testDirectoryProvider) {
         this.testDirectoryProvider = testDirectoryProvider;
-        def portFinder = org.gradle.util.AvailablePortFinder.createPrivate()
+        def portFinder = AvailablePortFinder.createPrivate()
         port = portFinder.nextAvailable
         this.hostAddress = "127.0.0.1"
     }
@@ -61,44 +59,12 @@ class SFTPServer extends ExternalResource {
 
         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");
@@ -141,7 +107,7 @@ class SFTPServer extends ExternalResource {
 
     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) {
+        boolean authenticate(String username, String password, ServerSession session) {
             return username && password && username == password;
         }
     }
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpecTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpecTest.groovy
deleted file mode 100644
index f319c96..0000000
--- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpecTest.groovy
+++ /dev/null
@@ -1,59 +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.versions
-
-import spock.lang.Specification
-
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.*
-import static org.gradle.util.GradleVersion.current
-import static org.gradle.util.GradleVersion.version
-
-class IsTestableGradleVersionSpecTest extends Specification {
-
-    boolean isSatisfied(ReleasedGradleVersion releasedGradleVersion) {
-        new IsTestableGradleVersionSpec().isSatisfiedBy(releasedGradleVersion)
-    }
-
-    def "release candidates"() {
-        expect:
-        !isSatisfied(new ReleasedGradleVersion(version("1.0-rc-1"), RELEASE_CANDIDATE, false))
-        isSatisfied(new ReleasedGradleVersion(version("1.0-rc-1"), RELEASE_CANDIDATE, true))
-    }
-
-    def "excludes old versions"() {
-        expect:
-        !isSatisfied(new ReleasedGradleVersion(version("0.7"), FINAL, true))
-    }
-
-    def "excludes bad versions"() {
-        expect:
-        !isSatisfied(new ReleasedGradleVersion(version("0.9-rc-1"), FINAL, true))
-        !isSatisfied(new ReleasedGradleVersion(version("0.9-rc-2"), FINAL, true))
-        !isSatisfied(new ReleasedGradleVersion(version("1.0-milestone-4"), FINAL, true))
-    }
-
-    def "excludes current version"() {
-        expect:
-        !isSatisfied(new ReleasedGradleVersion(current(), FINAL, true))
-    }
-
-    def "does not accept nightly"() {
-        expect:
-        !isSatisfied(new ReleasedGradleVersion(current(), NIGHTLY, true))
-    }
-
-}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy
index ed7ff71..f8fecd2 100644
--- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy
@@ -19,23 +19,21 @@ package org.gradle.integtests.fixtures.versions
 import org.gradle.internal.Factory
 import spock.lang.Specification
 
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.FINAL
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.RELEASE_CANDIDATE
 import static org.gradle.util.GradleVersion.version
 
 class ReleasedVersionDistributionsTest extends Specification {
 
-    List<ReleasedGradleVersion> all = []
+    Properties props = new Properties()
 
     def versions() {
-        new ReleasedVersionDistributions(new Factory<List<ReleasedGradleVersion>>() {
-            List<ReleasedGradleVersion> create() {
-                all
+        new ReleasedVersionDistributions(new Factory<Properties>() {
+            Properties create() {
+                props
             }
         })
     }
 
-    // Will fail if the classpath resource is not available, see ClasspathVersionJsonSource
+    // Will fail if the classpath resource is not available, see ClasspathVersionSource
     def "can create from classpath"() {
         when:
         def versions = new ReleasedVersionDistributions()
@@ -47,8 +45,7 @@ class ReleasedVersionDistributionsTest extends Specification {
 
     def "get most recent final does that"() {
         when:
-        all << new ReleasedGradleVersion(version("1.3-rc-1"), RELEASE_CANDIDATE, true)
-        all << new ReleasedGradleVersion(version("1.2"), FINAL, true)
+        props.mostRecent = "1.2"
 
         then:
         versions().mostRecentFinalRelease.version == version("1.2")
@@ -56,8 +53,7 @@ class ReleasedVersionDistributionsTest extends Specification {
 
     def "get all final does that"() {
         when:
-        all << new ReleasedGradleVersion(version("1.3-rc-1"), RELEASE_CANDIDATE, true)
-        all << new ReleasedGradleVersion(version("1.2"), FINAL, true)
+        props.versions = "1.3-rc-1 1.2"
 
         then:
         versions().all*.version == [version("1.3-rc-1"), version("1.2")]
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParserTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParserTest.groovy
deleted file mode 100644
index e8938d9..0000000
--- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParserTest.groovy
+++ /dev/null
@@ -1,115 +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.versions
-
-import spock.lang.Specification
-
-import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.*
-
-class VersionWebServiceJsonParserTest extends Specification {
-
-    def "can read"() {
-        when:
-        def versions = parse('''[{
-	"version":"1.4-20130107230014+0000",
-	"buildTime":"20130107230014+0000",
-	"current":false,
-	"snapshot":true,
-	"nightly":true,
-	"activeRc":false,
-	"rcFor":"",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions-snapshots\\/gradle-1.4-20130107230014+0000-bin.zip"
-},{
-	"version":"1.3-rc-1",
-	"buildTime":"20121120113738+0000",
-	"current":false,
-	"snapshot":false,
-	"nightly":false,
-	"activeRc":true,
-	"rcFor":"1.3",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.3-rc-1-bin.zip"
-},{
-	"version":"1.2",
-	"buildTime":"20121115155343+0000",
-	"current":true,
-	"snapshot":false,
-	"nightly":false,
-	"activeRc":false,
-	"rcFor":"",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.2-bin.zip"
-},{
-	"version":"1.1",
-	"buildTime":"20120731132432+0000",
-	"current":false,
-	"snapshot":false,
-	"nightly":false,
-	"activeRc":false,
-	"rcFor":"",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.1-bin.zip"
-},{
-	"version":"1.1-rc-2",
-	"buildTime":"20120726075103+0000",
-	"current":false,
-	"snapshot":false,
-	"nightly":false,
-	"activeRc":false,
-	"rcFor":"1.1",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.1-rc-2-bin.zip"
-},{
-	"version":"1.1-rc-1",
-	"buildTime":"20120724134404+0000",
-	"current":false,
-	"snapshot":false,
-	"nightly":false,
-	"activeRc":false,
-	"rcFor":"1.1",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.1-rc-1-bin.zip"
-},{
-	"version":"1.0",
-	"buildTime":"20120612025621+0200",
-	"current":false,
-	"snapshot":false,
-	"nightly":false,
-	"activeRc":false,
-	"rcFor":"",
-	"broken":false,
-	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.0-bin.zip"
-}]''')
-
-        then:
-        versions.size() == 7
-        versions.findAll { it.type == FINAL }.size() == 3
-        versions.findAll { it.type == RELEASE_CANDIDATE }.size() == 3
-        versions.find { it.type == RELEASE_CANDIDATE && it.current }.version.version == "1.3-rc-1"
-        versions.find { it.type == FINAL && it.current }.version.version == "1.2"
-        versions.find { it.type == NIGHTLY }.current
-    }
-
-    def parse(String text) {
-        return new VersionWebServiceJsonParser(new org.gradle.internal.Factory<Reader>() {
-            public Reader create() {
-                return new StringReader(text);
-            }
-        }).create();
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/maven/MavenFileModuleTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/maven/MavenFileModuleTest.groovy
new file mode 100644
index 0000000..278da44
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/maven/MavenFileModuleTest.groovy
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.maven
+
+import org.gradle.api.Project
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class MavenFileModuleTest extends Specification {
+    Project project
+    TestFile testFile
+    MavenModule mavenFileModule
+    MavenModule snapshotMavenFileModule
+
+    def setup() {
+        project = TestUtil.createRootProject()
+        testFile = new TestFile(project.projectDir, "build/test")
+        mavenFileModule = new MavenFileModule(testFile, "my-company", "my-artifact", "1.0")
+        snapshotMavenFileModule = new MavenFileModule(testFile, "my-company", "my-artifact", "1.0-SNAPSHOT")
+    }
+
+    def "Get parent and its POM section"() {
+        when:
+        MavenFileModule parent = mavenFileModule.parent("my-company", "parent", "0.1")
+
+        then:
+        parent != null
+"""
+<parent>
+  <groupId>my-company</groupId>
+  <artifactId>parent</artifactId>
+  <version>0.1</version>
+</parent>
+""" == parent.parentPomSection
+    }
+
+    def "Add multiple dependencies without type"() {
+        when:
+        List dependencies = mavenFileModule.dependsOn("dep1", "dep2").dependencies
+
+        then:
+        dependencies != null
+        dependencies.size() == 2
+        dependencies.get(0).groupId == 'my-company'
+        dependencies.get(0).artifactId == 'dep1'
+        dependencies.get(0).type == null
+        dependencies.get(0).version == '1.0'
+        dependencies.get(1).groupId == 'my-company'
+        dependencies.get(1).artifactId == 'dep2'
+        dependencies.get(1).type == null
+        dependencies.get(1).version == '1.0'
+    }
+
+    def "Add single dependency"() {
+        when:
+        List dependencies = mavenFileModule.dependsOn('my-company', 'dep1', 'jar', '1.0').dependencies
+
+        then:
+        dependencies != null
+        dependencies.size() == 1
+        dependencies.get(0) == [groupId: 'my-company', artifactId: 'dep1', version: 'jar', type: '1.0']
+    }
+
+    def "Check packaging for set packaging"() {
+        when:
+        String packaging = mavenFileModule.hasPackaging('war').packaging
+
+        then:
+        packaging != null
+        packaging == 'war'
+    }
+
+    def "Check packaging for no set packaging"() {
+        when:
+        String packaging = mavenFileModule.packaging
+
+        then:
+        packaging == null
+    }
+
+    def "Check type for set type"() {
+        when:
+        String type = mavenFileModule.hasType('war').type
+
+        then:
+        type != null
+        type == 'war'
+    }
+
+    def "Check type for no set type"() {
+        when:
+        String type = mavenFileModule.type
+
+        then:
+        type != null
+        type == 'jar'
+    }
+
+    def "Provides unique snapshots by default"() {
+        when:
+        boolean uniqueSnapshots = mavenFileModule.uniqueSnapshots
+
+        then:
+        uniqueSnapshots
+    }
+
+    def "Sets non-unique snapshots"() {
+        when:
+        boolean uniqueSnapshots = mavenFileModule.withNonUniqueSnapshots().uniqueSnapshots
+
+        then:
+        !uniqueSnapshots
+    }
+
+    def "On publishing SHA1 and MD5 files are created"() {
+        given:
+        TestFile pomTestFile = new TestFile(project.projectDir, "build/test/pom.xml")
+        pomTestFile.createFile()
+
+        when:
+        mavenFileModule.onPublish(pomTestFile)
+        def testFiles = Arrays.asList(pomTestFile.parentFile.listFiles())
+
+        then:
+        testFiles*.name.containsAll('pom.xml.md5', 'pom.xml.sha1')
+    }
+
+    def "Get artifact file for non-snapshot"() {
+        when:
+        TestFile artifactFile = mavenFileModule.getArtifactFile([:])
+
+        then:
+        artifactFile != null
+        artifactFile.name == 'my-artifact-1.0.jar'
+    }
+
+    def "Get artifact file for snapshot"() {
+        when:
+        TestFile artifactFile = snapshotMavenFileModule.getArtifactFile()
+
+        then:
+        artifactFile != null
+        artifactFile.name == 'my-artifact-1.0-SNAPSHOT.jar'
+    }
+
+    def "Get publish artifact version for non-snapshot"() {
+        when:
+        String version = mavenFileModule.getPublishArtifactVersion()
+
+        then:
+        version == '1.0'
+    }
+
+    def "Get publish artifact version for unique snapshot"() {
+        when:
+        String version = snapshotMavenFileModule.getPublishArtifactVersion()
+
+        then:
+        version == '1.0-20100101.120001-1'
+    }
+
+    def "Get publish artifact version for non-unique snapshot"() {
+        given:
+        snapshotMavenFileModule.withNonUniqueSnapshots()
+
+        when:
+        String version = snapshotMavenFileModule.getPublishArtifactVersion()
+
+        then:
+        version == '1.0-SNAPSHOT'
+    }
+
+    def "Publish artifacts for non-snapshot"() {
+        when:
+        MavenModule mavenModule = mavenFileModule.publish()
+        def publishedFiles = Arrays.asList(testFile.listFiles())
+
+        then:
+        mavenModule != null
+        publishedFiles*.name.containsAll('my-artifact-1.0.jar', 'my-artifact-1.0.jar.sha1', 'my-artifact-1.0.jar.md5', 'my-artifact-1.0.pom', 'my-artifact-1.0.pom.sha1', 'my-artifact-1.0.pom.md5')
+        !publishedFiles.find { it.name == 'maven-metadata.xml' }
+        mavenFileModule.assertArtifactsPublished('my-artifact-1.0.jar', 'my-artifact-1.0.pom')
+    }
+
+    def "Publish artifacts for unique snapshot"() {
+        when:
+        MavenModule mavenModule = snapshotMavenFileModule.publish()
+        def publishedFiles = Arrays.asList(testFile.listFiles())
+
+        then:
+        mavenModule != null
+        publishedFiles*.name.containsAll('my-artifact-1.0-20100101.120001-1.jar', 'my-artifact-1.0-20100101.120001-1.jar.sha1', 'my-artifact-1.0-20100101.120001-1.jar.md5', 'my-artifact-1.0-20100101.120001-1.pom', 'my-artifact-1.0-20100101.120001-1.pom.sha1', 'my-artifact-1.0-20100101.120001-1.pom.md5')
+        publishedFiles.find { it.name == 'maven-metadata.xml' }.exists()
+        new XmlSlurper().parseText(publishedFiles.find { it.name == 'maven-metadata.xml' }.text).versioning.snapshot.timestamp.text() == '20100101.120001'
+        new XmlSlurper().parseText(publishedFiles.find { it.name == 'maven-metadata.xml' }.text).versioning.snapshot.buildNumber.text() == '1'
+        snapshotMavenFileModule.assertArtifactsPublished('my-artifact-1.0-20100101.120001-1.jar', 'my-artifact-1.0-20100101.120001-1.pom')
+    }
+
+    def "Publish artifacts for non-unique snapshot"() {
+        given:
+        snapshotMavenFileModule.withNonUniqueSnapshots()
+
+        when:
+        MavenModule mavenModule = snapshotMavenFileModule.publish()
+        def publishedFiles = Arrays.asList(testFile.listFiles())
+
+        then:
+        mavenModule != null
+        publishedFiles*.name.containsAll('my-artifact-1.0-SNAPSHOT.jar', 'my-artifact-1.0-SNAPSHOT.jar.sha1', 'my-artifact-1.0-SNAPSHOT.jar.md5', 'my-artifact-1.0-SNAPSHOT.pom', 'my-artifact-1.0-SNAPSHOT.pom.sha1', 'my-artifact-1.0-SNAPSHOT.pom.md5')
+        !publishedFiles.find { it.name == 'maven-metadata.xml' }
+        snapshotMavenFileModule.assertArtifactsPublished('my-artifact-1.0-SNAPSHOT.jar', 'my-artifact-1.0-SNAPSHOT.pom')
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/maven/MavenLocalModuleTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/maven/MavenLocalModuleTest.groovy
new file mode 100644
index 0000000..8abc8e9
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/maven/MavenLocalModuleTest.groovy
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.maven
+
+import org.gradle.api.Project
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class MavenLocalModuleTest extends Specification {
+    Project project
+    TestFile testFile
+    MavenModule mavenLocalModule
+    MavenModule snapshotMavenLocalModule
+
+    def setup() {
+        project = TestUtil.createRootProject()
+        testFile = new TestFile(project.projectDir, "build/test")
+        mavenLocalModule = new MavenLocalModule(testFile, "my-company", "my-artifact", "1.0")
+        snapshotMavenLocalModule = new MavenLocalModule(testFile, "my-company", "my-artifact", "1.0-SNAPSHOT")
+    }
+
+    def "Get parent and its POM section"() {
+        when:
+        MavenLocalModule parent = mavenLocalModule.parent("my-company", "parent", "0.1")
+
+        then:
+        parent != null
+        """
+<parent>
+  <groupId>my-company</groupId>
+  <artifactId>parent</artifactId>
+  <version>0.1</version>
+</parent>
+""" == parent.parentPomSection
+    }
+
+    def "Add multiple dependencies without type"() {
+        when:
+        List dependencies = mavenLocalModule.dependsOn("dep1", "dep2").dependencies
+
+        then:
+        dependencies != null
+        dependencies.size() == 2
+        dependencies.get(0).groupId == 'my-company'
+        dependencies.get(0).artifactId == 'dep1'
+        dependencies.get(0).type == null
+        dependencies.get(0).version == '1.0'
+        dependencies.get(1).groupId == 'my-company'
+        dependencies.get(1).artifactId == 'dep2'
+        dependencies.get(1).type == null
+        dependencies.get(1).version == '1.0'
+    }
+
+    def "Add single dependency"() {
+        when:
+        List dependencies = mavenLocalModule.dependsOn('my-company', 'dep1', 'jar', '1.0').dependencies
+
+        then:
+        dependencies != null
+        dependencies.size() == 1
+        dependencies.get(0) == [groupId: 'my-company', artifactId: 'dep1', version: 'jar', type: '1.0']
+    }
+
+    def "Check packaging for set packaging"() {
+        when:
+        String packaging = mavenLocalModule.hasPackaging('war').packaging
+
+        then:
+        packaging != null
+        packaging == 'war'
+    }
+
+    def "Check packaging for no set packaging"() {
+        when:
+        String packaging = mavenLocalModule.packaging
+
+        then:
+        packaging == null
+    }
+
+    def "Check type for set type"() {
+        when:
+        String type = mavenLocalModule.hasType('war').type
+
+        then:
+        type != null
+        type == 'war'
+    }
+
+    def "Check type for no set type"() {
+        when:
+        String type = mavenLocalModule.type
+
+        then:
+        type != null
+        type == 'jar'
+    }
+
+    def "Sets non-unique snapshots"() {
+        when:
+        MavenModule mavenModule = mavenLocalModule.withNonUniqueSnapshots()
+
+        then:
+        mavenModule != null
+    }
+
+    def "On publishing SHA1 and MD5 files are created"() {
+        given:
+        TestFile pomTestFile = new TestFile(project.projectDir, "build/test/pom.xml")
+        pomTestFile.createFile()
+
+        when:
+        mavenLocalModule.onPublish(pomTestFile)
+        def testFiles = Arrays.asList(pomTestFile.parentFile.listFiles())
+
+        then:
+        !testFiles*.name.containsAll('pom.xml.md5', 'pom.xml.sha1')
+    }
+
+    def "Get artifact file for non-snapshot"() {
+        when:
+        TestFile artifactFile = mavenLocalModule.getArtifactFile([:])
+
+        then:
+        artifactFile != null
+        artifactFile.name == 'my-artifact-1.0.jar'
+    }
+
+    def "Get artifact file for snapshot"() {
+        when:
+        TestFile artifactFile = snapshotMavenLocalModule.getArtifactFile()
+
+        then:
+        artifactFile != null
+        artifactFile.name == 'my-artifact-1.0-SNAPSHOT.jar'
+    }
+
+    def "Get publish artifact version for non-snapshot"() {
+        when:
+        String version = mavenLocalModule.getPublishArtifactVersion()
+
+        then:
+        version == '1.0'
+    }
+
+    def "Get publish artifact version for unique snapshot"() {
+        when:
+        String version = snapshotMavenLocalModule.getPublishArtifactVersion()
+
+        then:
+        version == '1.0-SNAPSHOT'
+    }
+
+    def "Get publish artifact version for non-unique snapshot"() {
+        given:
+        snapshotMavenLocalModule.withNonUniqueSnapshots()
+
+        when:
+        String version = snapshotMavenLocalModule.getPublishArtifactVersion()
+
+        then:
+        version == '1.0-SNAPSHOT'
+    }
+
+    def "Publish artifacts for non-snapshot"() {
+        when:
+        MavenModule mavenModule = mavenLocalModule.publish()
+        def publishedFiles = Arrays.asList(testFile.listFiles())
+
+        then:
+        mavenModule != null
+        publishedFiles*.name.containsAll('my-artifact-1.0.jar', 'my-artifact-1.0.pom')
+        !publishedFiles.find { it.name == 'maven-metadata.xml' }
+        mavenLocalModule.assertArtifactsPublished('my-artifact-1.0.jar', 'my-artifact-1.0.pom')
+    }
+
+    def "Publish artifacts for unique snapshot"() {
+        when:
+        MavenModule mavenModule = snapshotMavenLocalModule.publish()
+        def publishedFiles = Arrays.asList(testFile.listFiles())
+
+        then:
+        mavenModule != null
+        publishedFiles*.name.containsAll('my-artifact-1.0-SNAPSHOT.jar', 'my-artifact-1.0-SNAPSHOT.pom')
+        publishedFiles.find { it.name == 'maven-metadata.xml' }.exists()
+        new XmlSlurper().parseText(publishedFiles.find { it.name == 'maven-metadata.xml' }.text).versioning.snapshot.localCopy.text() == 'true'
+        snapshotMavenLocalModule.assertArtifactsPublished('my-artifact-1.0-SNAPSHOT.jar', 'my-artifact-1.0-SNAPSHOT.pom')
+    }
+
+    def "Publish artifacts for non-unique snapshot"() {
+        given:
+        snapshotMavenLocalModule.withNonUniqueSnapshots()
+
+        when:
+        MavenModule mavenModule = snapshotMavenLocalModule.publish()
+        def publishedFiles = Arrays.asList(testFile.listFiles())
+
+        then:
+        mavenModule != null
+        publishedFiles*.name.containsAll('my-artifact-1.0-SNAPSHOT.jar', 'my-artifact-1.0-SNAPSHOT.pom')
+        publishedFiles.find { it.name == 'maven-metadata.xml' }.exists()
+        new XmlSlurper().parseText(publishedFiles.find { it.name == 'maven-metadata.xml' }.text).versioning.snapshot.localCopy.text() == 'true'
+        snapshotMavenLocalModule.assertArtifactsPublished('my-artifact-1.0-SNAPSHOT.jar', 'my-artifact-1.0-SNAPSHOT.pom')
+    }
+}
diff --git a/subprojects/internal-testing/internal-testing.gradle b/subprojects/internal-testing/internal-testing.gradle
index 4fb6bf8..e8a9410 100644
--- a/subprojects/internal-testing/internal-testing.gradle
+++ b/subprojects/internal-testing/internal-testing.gradle
@@ -18,10 +18,11 @@
     Provides generally useful test utilities, used for unit and integration testing.
 */
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(":baseServices")
     compile project(":native")
+    compile libraries.guava
     compile libraries.commons_lang
     compile libraries.commons_io
     compile libraries.ant
@@ -32,4 +33,6 @@ dependencies {
     compile libraries.jsr305 //it is a guava dependency needed for compilation on ibm vm / windows
 }
 
-useClassycle()
\ No newline at end of file
+useClassycle()
+
+apply from: "$rootDir/gradle/ideaTestSourcesWorkaround.gradle"
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
index f73be8d..31aeb78 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
@@ -24,7 +24,7 @@ class DefaultTestExecutionResult implements TestExecutionResult {
     def results = []
 
     public DefaultTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
-        results << new HtmlTestExecutionResult(projectDir)
+        results << new HtmlTestExecutionResult(projectDir, "$buildDirName/reports/tests")
         results << new JUnitXmlTestExecutionResult(projectDir, buildDirName)
     }
 
@@ -48,7 +48,7 @@ class DefaultTestExecutionResult implements TestExecutionResult {
 
         TestClassExecutionResult assertTestsExecuted(String... testNames) {
             testClassResults*.assertTestsExecuted(testNames)
-            return this
+            this
         }
 
         TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
@@ -58,7 +58,7 @@ class DefaultTestExecutionResult implements TestExecutionResult {
 
         TestClassExecutionResult assertTestsSkipped(String... testNames) {
             testClassResults*.assertTestsSkipped(testNames)
-            return this
+            this
         }
 
         TestClassExecutionResult assertTestPassed(String name) {
@@ -73,6 +73,7 @@ class DefaultTestExecutionResult implements TestExecutionResult {
 
         TestClassExecutionResult assertTestSkipped(String name) {
             testClassResults*.assertTestSkipped(name)
+            this
         }
 
         TestClassExecutionResult assertConfigMethodPassed(String name) {
@@ -90,9 +91,19 @@ class DefaultTestExecutionResult implements TestExecutionResult {
             this
         }
 
+        TestClassExecutionResult assertTestCaseStdout(String testCaseName, Matcher<? super String> matcher) {
+            testClassResults*.assertTestCaseStdout(testCaseName, matcher)
+            this
+        }
+
         TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
             testClassResults*.assertStderr(matcher)
             this
         }
+
+        TestClassExecutionResult assertTestCaseStderr(String testCaseName, Matcher<? super String> matcher) {
+            testClassResults*.assertTestCaseStderr(testCaseName, matcher)
+            this
+        }
     }
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy
index 7f88634..d654f43 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy
@@ -15,20 +15,17 @@
  */
 package org.gradle.integtests.fixtures
 
+import org.gradle.internal.FileUtils
 import org.hamcrest.Matcher
 import org.jsoup.Jsoup
 import org.jsoup.nodes.Document
 
-import static junit.framework.Assert.assertTrue
-import static org.hamcrest.Matchers.equalTo
-import static org.junit.Assert.assertThat
-
 class HtmlTestExecutionResult implements TestExecutionResult {
 
     private File htmlReportDirectory
 
-    public HtmlTestExecutionResult(File projectDirectory, String testReportDirectory = "tests") {
-        this.htmlReportDirectory = new File(projectDirectory, "build/reports/$testReportDirectory");
+    public HtmlTestExecutionResult(File projectDirectory, String testReportDirectory = "build/reports/tests") {
+        this.htmlReportDirectory = new File(projectDirectory, testReportDirectory);
     }
 
     TestExecutionResult assertTestClassesExecuted(String... testClasses) {
@@ -37,31 +34,30 @@ class HtmlTestExecutionResult implements TestExecutionResult {
         return this
     }
 
-    def indexContainsTestClass(String... testClasses) {
+    private void indexContainsTestClass(String... expectedTestClasses) {
         def indexFile = new File(htmlReportDirectory, "index.html")
         assert indexFile.exists()
         Document html = Jsoup.parse(indexFile, null)
-        testClasses.each { testClass ->
-            assert html.select("a").find { it.text() == testClass } != null
-        }
+        def executedTestClasses = html.select("div:has(h2:contains(Classes)).tab a").collect { it.text() }
+        assert executedTestClasses.containsAll(expectedTestClasses)
     }
 
-    def assertHtmlReportForTestClassExists(String... classNames) {
+    private void assertHtmlReportForTestClassExists(String... classNames) {
         classNames.each {
-            assertTrue new File(htmlReportDirectory, "${it}.html").exists();
+            assert new File(htmlReportDirectory, "classes/${FileUtils.toSafeFileName(it)}.html").file
         }
     }
 
     TestClassExecutionResult testClass(String testClass) {
-        return new HtmlTestClassExecutionResult(new File(htmlReportDirectory, "${testClass}.html"));
+        return new HtmlTestClassExecutionResult(new File(htmlReportDirectory, "classes/${FileUtils.toSafeFileName(testClass)}.html"));
     }
 
     private static class HtmlTestClassExecutionResult implements TestClassExecutionResult {
         private File htmlFile
-        private List testsExecuted = []
-        private List testsSucceeded = []
-        private Map testsFailures = [:]
-        private Set testsSkipped = []
+        private List<String> testsExecuted = []
+        private List<String> testsSucceeded = []
+        private Map<String, List<String>> testsFailures = [:]
+        private Set<String> testsSkipped = []
         private Document html
 
         public HtmlTestClassExecutionResult(File htmlFile) {
@@ -70,31 +66,33 @@ class HtmlTestExecutionResult implements TestExecutionResult {
             parseTestClassFile()
         }
 
-        def parseTestClassFile() {
+        private void parseTestClassFile() {
             html.select("tr > td.success:eq(0)").each {
-                testsExecuted << it.text()
-                testsSucceeded << it.text()
+                def testName = it.textNodes().first().wholeText.trim()
+                testsExecuted << testName
+                testsSucceeded << testName
 
             }
             html.select("tr > td.failures:eq(0)").each {
-                testsExecuted << it.text()
-                String failure = getFailureMessage(it.text());
-                testsFailures[it.text()] = failure
+                def testName = it.textNodes().first().wholeText.trim()
+                testsExecuted << testName
+                def failures = getFailureMessages(testName);
+                testsFailures[it.text()] = failures
             }
 
             html.select("tr > td.skipped:eq(0)").each {
-                testsSkipped << it.text()
-                testsExecuted << it.text()
+                def testName = it.textNodes().first().wholeText.trim()
+                testsSkipped << testName
+                testsExecuted << testName
             }
-            return this
         }
 
-        String getFailureMessage(String testmethod) {
-            html.select("div#test:has(a[name=$testmethod]) > span > pre").text()
+        List<String> getFailureMessages(String testmethod) {
+            html.select("div.test:has(a[name=$testmethod]) > span > pre").collect { it.text().readLines().first() }
         }
 
         TestClassExecutionResult assertTestsExecuted(String... testNames) {
-            assertThat(testsExecuted - testsSkipped, equalTo(testNames as List))
+            assert testsExecuted - testsSkipped == testNames as List
             return this
         }
 
@@ -105,7 +103,7 @@ class HtmlTestExecutionResult implements TestExecutionResult {
         }
 
         TestClassExecutionResult assertTestsSkipped(String... testNames) {
-            assertThat(testsSkipped, equalTo(testNames as Set))
+            assert testsSkipped == testNames as Set
             return this
         }
 
@@ -115,8 +113,12 @@ class HtmlTestExecutionResult implements TestExecutionResult {
         }
 
         TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
-            def message = testsFailures[name];
-            messageMatchers.each { it.matches(message) }
+            assert testsFailures.containsKey(name)
+            def messages = testsFailures[name]
+            assert messages.size() == messageMatchers.length
+            for (int i = 0; i < messageMatchers.length; i++) {
+                assert messageMatchers[i].matches(messages[i])
+            }
             return this
         }
 
@@ -134,13 +136,26 @@ class HtmlTestExecutionResult implements TestExecutionResult {
         }
 
         TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
-            matcher.matches(html.select("div#tab2 > span > pre").text())
+            def tabs = html.select("div.tab")
+            def tab = tabs.find { it.select("h2").text() == 'Standard output' }
+            assert matcher.matches(tab ? tab.select("span > pre").first().textNodes().first().wholeText : "")
             return this;
         }
 
+        TestClassExecutionResult assertTestCaseStdout(String testCaseName, Matcher<? super String> matcher) {
+            throw new UnsupportedOperationException()
+        }
+
         TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
-            matcher.matches(html.select("div#tab3 > span > pre").text())
+            def tabs = html.select("div.tab")
+            def tab = tabs.find { it.select("h2").text() == 'Standard error' }
+            assert matcher.matches(tab ? tab.select("span > pre").first().textNodes().first().wholeText : "")
             return this;
         }
+
+        TestClassExecutionResult assertTestCaseStderr(String testCaseName, Matcher<? super String> matcher) {
+            throw new UnsupportedOperationException()
+        }
+
     }
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestClassExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestClassExecutionResult.groovy
new file mode 100644
index 0000000..40a5943
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestClassExecutionResult.groovy
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures
+
+import groovy.util.slurpersupport.GPathResult
+import groovy.util.slurpersupport.NodeChild
+import org.hamcrest.Matcher
+import org.hamcrest.Matchers
+import org.junit.Assert
+
+class JUnitTestClassExecutionResult implements TestClassExecutionResult {
+    GPathResult testClassNode
+    String testClassName
+    boolean checked
+    TestResultOutputAssociation outputAssociation
+
+    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName, TestResultOutputAssociation outputAssociation) {
+        this.outputAssociation = outputAssociation
+        this.testClassNode = testClassNode
+        this.testClassName = testClassName
+    }
+
+    def JUnitTestClassExecutionResult(String content, String testClassName, TestResultOutputAssociation outputAssociation) {
+        this(new XmlSlurper().parse(new StringReader(content)), testClassName, outputAssociation)
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        Map<String, Node> testMethods = findTests().findAll { name, element ->
+            element."skipped".size() == 0 // Exclude skipped test.
+        }
+        Assert.assertThat(testMethods.keySet(), Matchers.equalTo(testNames as Set))
+        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 assertTestCount(int tests, int skipped, int failures, int errors) {
+        assert testClassNode. at tests == tests
+        assert testClassNode. at skipped == skipped
+        assert testClassNode. at failures == failures
+        assert testClassNode. at errors == errors
+        this
+    }
+
+    TestClassExecutionResult withResult(Closure action) {
+        action(testClassNode)
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        Map<String, Node> testMethods = findTests()
+        Assert.assertThat(testMethods.keySet(), Matchers.hasItem(name))
+        Assert.assertThat(testMethods[name].failure.size(), Matchers.equalTo(0))
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+        Map<String, Node> testMethods = findTests()
+        Assert.assertThat(testMethods.keySet(), Matchers.hasItem(name))
+
+        def failures = testMethods[name].failure
+        Assert.assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), Matchers.equalTo(messageMatchers.length))
+
+        for (int i = 0; i < messageMatchers.length; i++) {
+            Assert.assertThat(failures[i]. at message.text(), messageMatchers[i])
+        }
+        this
+    }
+
+    TestClassExecutionResult assertTestSkipped(String name) {
+        throw new UnsupportedOperationException()
+    }
+
+    TestClassExecutionResult assertTestsSkipped(String... testNames) {
+        Map<String, Node> testMethods = findTests().findAll { name, element ->
+            element."skipped".size() > 0 // Include only skipped test.
+        }
+        
+        Assert.assertThat(testMethods.keySet(), Matchers.equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        def stdout = testClassNode.'system-out'[0].text();
+        Assert.assertThat(stdout, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        def stderr = testClassNode.'system-err'[0].text();
+        Assert.assertThat(stderr, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertTestCaseStderr(String testCaseName, Matcher<? super String> matcher) {
+        def stderr = testCase(testCaseName).'system-err'[0].text();
+        Assert.assertThat(stderr, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertTestCaseStdout(String testCaseName, Matcher<? super String> matcher) {
+        def stderr = testCase(testCaseName).'system-out'[0].text();
+        Assert.assertThat(stderr, matcher)
+        this
+    }
+
+    private NodeChild testCase(String name) {
+        testClassNode.testcase.find { it. at name == name }
+    }
+
+    private def findTests() {
+        if (!checked) {
+            Assert.assertThat(testClassNode.name(), Matchers.equalTo('testsuite'))
+            Assert.assertThat(testClassNode. at name.text(), Matchers.equalTo(testClassName))
+            Assert.assertThat(testClassNode. at tests.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode. at skipped.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode. at failures.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode. at errors.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode. at time.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode. at timestamp.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode. at hostname.text(), Matchers.not(Matchers.equalTo('')))
+            Assert.assertThat(testClassNode.properties.size(), Matchers.equalTo(1))
+            testClassNode.testcase.each { node ->
+                Assert.assertThat(node. at classname.text(), Matchers.equalTo(testClassName))
+                Assert.assertThat(node. at name.text(), Matchers.not(Matchers.equalTo('')))
+                Assert.assertThat(node. at time.text(), Matchers.not(Matchers.equalTo('')))
+                node.failure.each { failure ->
+                    Assert.assertThat(failure. at message.size(), Matchers.equalTo(1))
+                    Assert.assertThat(failure. at type.text(), Matchers.not(Matchers.equalTo('')))
+                    Assert.assertThat(failure.text(), Matchers.not(Matchers.equalTo('')))
+                }
+                def matcher = Matchers.equalTo(outputAssociation == TestResultOutputAssociation.WITH_TESTCASE ? 1 : 0)
+                Assert.assertThat(node.'system-err'.size(), matcher)
+                Assert.assertThat(node.'system-out'.size(), matcher)
+            }
+            if (outputAssociation == TestResultOutputAssociation.WITH_SUITE) {
+                Assert.assertThat(testClassNode.'system-out'.size(), Matchers.equalTo(1))
+                Assert.assertThat(testClassNode.'system-err'.size(), Matchers.equalTo(1))
+            }
+            checked = true
+        }
+        Map testMethods = [:]
+        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy
index 48ee3e1..aa92cda 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy
@@ -15,17 +15,21 @@
  */
 package org.gradle.integtests.fixtures
 
-import groovy.util.slurpersupport.GPathResult
 import org.gradle.test.fixtures.file.TestFile
-import org.hamcrest.Matcher
 
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
 class JUnitXmlTestExecutionResult implements TestExecutionResult {
     private final TestFile buildDir
+    private final TestResultOutputAssociation outputAssociation
 
     def JUnitXmlTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
+        this(projectDir, TestResultOutputAssociation.WITH_SUITE, buildDirName)
+    }
+
+    def JUnitXmlTestExecutionResult(TestFile projectDir, TestResultOutputAssociation outputAssociation, String buildDirName = 'build') {
+        this.outputAssociation = outputAssociation
         this.buildDir = projectDir.file(buildDirName)
     }
 
@@ -35,12 +39,18 @@ class JUnitXmlTestExecutionResult implements TestExecutionResult {
 
     TestExecutionResult assertTestClassesExecuted(String... testClasses) {
         Map<String, File> classes = findClasses()
-        assertThat(classes.keySet(), equalTo(testClasses as Set));
+        assertThat(classes.keySet(), equalTo(testClasses as Set))
         this
     }
 
+    def fromFileToTestClass(String s) {
+        s.replaceAll(/#([\d\w][\d\w])/){
+            (char)Integer.parseInt(it[1], 16)
+        }
+    }
+
     TestClassExecutionResult testClass(String testClass) {
-        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
+        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass, outputAssociation)
     }
 
     private def findTestClass(String testClass) {
@@ -56,9 +66,9 @@ class JUnitXmlTestExecutionResult implements TestExecutionResult {
 
         Map<String, File> classes = [:]
         buildDir.file('test-results').eachFile { File file ->
-            def matcher = (file.name =~ /TEST-(.+)\.xml/)
+            def matcher = (file.name=~/TEST-(.+)\.xml/)
             if (matcher.matches()) {
-                classes[matcher.group(1)] = file
+                classes[fromFileToTestClass(matcher.group(1))] = file
             }
         }
         return classes
@@ -69,116 +79,3 @@ class JUnitXmlTestExecutionResult implements TestExecutionResult {
     }
 }
 
-class JUnitTestClassExecutionResult implements TestClassExecutionResult {
-    GPathResult testClassNode
-    String testClassName
-    boolean checked
-
-    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
-        this.testClassNode = testClassNode
-        this.testClassName = testClassName
-    }
-
-    def JUnitTestClassExecutionResult(String content, String testClassName) {
-        this(new XmlSlurper().parse(new StringReader(content)), testClassName)
-    }
-
-    TestClassExecutionResult assertTestsExecuted(String... testNames) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        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))
-        assertThat(testMethods[name].failure.size(), equalTo(0))
-        this
-    }
-
-    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-
-        def failures = testMethods[name].failure
-        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
-
-        for (int i = 0; i < messageMatchers.length; i++) {
-            assertThat(failures[i]. at message.text(), messageMatchers[i])
-        }
-        this
-    }
-
-    TestClassExecutionResult assertTestSkipped(String name) {
-        throw new UnsupportedOperationException()
-    }
-
-    TestClassExecutionResult assertTestsSkipped(String... testNames) {
-        Map<String, Node> testMethods = findIgnoredTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertConfigMethodPassed(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertConfigMethodFailed(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
-        def stdout = testClassNode.'system-out'[0].text();
-        assertThat(stdout, matcher)
-        this
-    }
-
-    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
-        def stderr = testClassNode.'system-err'[0].text();
-        assertThat(stderr, matcher)
-        this
-    }
-
-    private def findTests() {
-        if (!checked) {
-            assertThat(testClassNode.name(), equalTo('testsuite'))
-            assertThat(testClassNode. at name.text(), equalTo(testClassName))
-            assertThat(testClassNode. at tests.text(), not(equalTo('')))
-            assertThat(testClassNode. at failures.text(), not(equalTo('')))
-            assertThat(testClassNode. at errors.text(), not(equalTo('')))
-            assertThat(testClassNode. at time.text(), not(equalTo('')))
-            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
-            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
-            assertThat(testClassNode.properties.size(), equalTo(1))
-            testClassNode.testcase.each { node ->
-                assertThat(node. at classname.text(), equalTo(testClassName))
-                assertThat(node. at name.text(), not(equalTo('')))
-                assertThat(node. at time.text(), not(equalTo('')))
-                node.failure.each { failure ->
-                    assertThat(failure. at message.size(), equalTo(1))
-                    assertThat(failure. at type.text(), not(equalTo('')))
-                    assertThat(failure.text(), not(equalTo('')))
-                }
-            }
-            assertThat(testClassNode.'system-out'.size(), equalTo(1))
-            assertThat(testClassNode.'system-err'.size(), equalTo(1))
-            checked = true
-        }
-        Map testMethods = [:]
-        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
-
-    private def findIgnoredTests() {
-        Map testMethods = [:]
-        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
index 027b1bd..270ca53 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
@@ -58,5 +58,9 @@ public interface TestClassExecutionResult {
 
     TestClassExecutionResult assertStdout(Matcher<? super String> matcher);
 
+    TestClassExecutionResult assertTestCaseStdout(String testCaseName, Matcher<? super String> matcher);
+
     TestClassExecutionResult assertStderr(Matcher<? super String> matcher);
+
+    TestClassExecutionResult assertTestCaseStderr(String testCaseName, Matcher<? super String> matcher);
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResultOutputAssociation.java b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResultOutputAssociation.java
new file mode 100644
index 0000000..39a90a4
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResultOutputAssociation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures;
+
+public enum TestResultOutputAssociation {
+    WITH_SUITE,
+    WITH_TESTCASE
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/ArchiveTestFixture.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/ArchiveTestFixture.groovy
new file mode 100644
index 0000000..c6b8b21
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/ArchiveTestFixture.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archive
+
+import com.google.common.collect.ArrayListMultimap
+import com.google.common.collect.ListMultimap
+import org.hamcrest.Matcher
+
+import static org.hamcrest.MatcherAssert.assertThat
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.hasItem
+import static org.junit.Assert.assertEquals
+
+class ArchiveTestFixture {
+    private final ListMultimap<String, String> filesByRelativePath = ArrayListMultimap.create()
+
+    protected void add(String relativePath, String content) {
+        filesByRelativePath.put(relativePath, content)
+    }
+
+    def assertContainsFile(String relativePath, int occurrences = 1) {
+        assertEquals(occurrences, filesByRelativePath.get(relativePath).size())
+        this
+    }
+
+    String content(String relativePath) {
+        List<String> files = filesByRelativePath.get(relativePath)
+        assertEquals(1, files.size())
+        files.get(0)
+    }
+
+    Integer countFiles(String relativePath) {
+        filesByRelativePath.get(relativePath).size()
+    }
+
+    def hasDescendants(String... relativePaths) {
+        assertThat(relativePaths as Set, equalTo(filesByRelativePath.keySet()))
+        def expectedCounts = ArrayListMultimap.create()
+        for (String fileName : relativePaths) {
+            expectedCounts.put(fileName, fileName)
+        }
+        for (String fileName : relativePaths) {
+            assertEquals(expectedCounts.get(fileName).size(), filesByRelativePath.get(fileName).size())
+        }
+        this
+    }
+
+    /**
+     * Asserts that there is exactly one file present with the given path, and that this file has the given content.
+     */
+    def assertFileContent(String relativePath, String fileContent) {
+        assertFileContent(relativePath, equalTo(fileContent))
+    }
+
+    def assertFileContent(String relativePath, Matcher contentMatcher) {
+        assertThat(content(relativePath), contentMatcher)
+        this
+    }
+
+    /**
+     * Asserts that there is a file present with the given path and content.
+     */
+    def assertFilePresent(String relativePath, String fileContent) {
+        assertThat(filesByRelativePath.get(relativePath), hasItem(fileContent))
+        this
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/JarTestFixture.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/JarTestFixture.groovy
new file mode 100644
index 0000000..6864ee2
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/JarTestFixture.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archive
+
+import org.apache.commons.io.IOUtils
+
+import java.util.jar.JarFile
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+
+class JarTestFixture extends ZipTestFixture {
+    File file
+
+    /**
+     * Asserts that the Jar file is well-formed
+     */
+     JarTestFixture(File file) {super(file)
+         this.file = file
+         isManifestPresentAndFirstEntry()
+     }
+
+    /**
+     * Asserts that the given service is defined in this jar file.
+     */
+    def hasService(String serviceName, String serviceImpl) {
+        assertFilePresent("META-INF/services/$serviceName", serviceImpl)
+    }
+
+    /**
+     * Asserts that the manifest file is present and first entry in this jar file.
+     */
+    void isManifestPresentAndFirstEntry() {
+        ZipInputStream zip = new ZipInputStream(new FileInputStream(file))
+        try {
+            ZipEntry zipEntry = zip.getNextEntry()
+
+            if (zipEntry.getName().equalsIgnoreCase("META-INF/")) {
+                zipEntry = zip.getNextEntry()
+            }
+
+            String firstEntryName = zipEntry.getName()
+            assert firstEntryName.equalsIgnoreCase(JarFile.MANIFEST_NAME)
+        }
+        finally {
+            IOUtils.closeQuietly(zip)
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/TarTestFixture.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/TarTestFixture.groovy
new file mode 100644
index 0000000..55a6f99
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/TarTestFixture.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archive
+
+import org.apache.tools.tar.TarEntry
+import org.apache.tools.tar.TarInputStream
+import org.gradle.test.fixtures.file.TestFile
+
+class TarTestFixture extends ArchiveTestFixture {
+    private final TestFile tarFile
+
+    public TarTestFixture(TestFile tarFile) {
+        this.tarFile = tarFile
+
+        tarFile.withInputStream { inputStream ->
+            TarInputStream tarInputStream = new TarInputStream(inputStream)
+            for (TarEntry tarEntry = tarInputStream.nextEntry; tarEntry != null; tarEntry = tarInputStream.nextEntry) {
+                if (tarEntry.directory) {
+                    continue
+                }
+                ByteArrayOutputStream stream = new ByteArrayOutputStream()
+                tarInputStream.copyEntryContents(stream)
+                add(tarEntry.name, new String(stream.toByteArray(), "utf-8"))
+            }
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/ZipTestFixture.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/ZipTestFixture.groovy
new file mode 100644
index 0000000..71a5ca9
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/archive/ZipTestFixture.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.archive
+
+import java.util.zip.ZipFile
+
+class ZipTestFixture extends ArchiveTestFixture {
+    ZipTestFixture(File file) {
+        def zipFile = new ZipFile(file)
+        try {
+            def entries = zipFile.entries()
+            while (entries.hasMoreElements()) {
+                def entry = entries.nextElement()
+                def content = zipFile.getInputStream(entry).text
+                if (!entry.directory) {
+                    add(entry.name, content)
+                }
+            }
+        } finally {
+            zipFile.close();
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy
index 88b9522..e5f75df 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy
@@ -29,19 +29,21 @@ import java.util.concurrent.Executor
  * Once the test threads have completed, you can make assertions about the ordering of the various instants relative to each other. You can also block until a given
  * instant has been reached.
  *
- * <p>The main test thread cannot define instants, except within a {@link #async} block.
+ * <p>The main test thread cannot define instants, except within an {@link #async} block.
  *
  * <p>NOTE: Be careful when using this class with Spock mock objects, as these mocks perform some synchronisation of their own. This means that you may not be testing
  * what you think you are testing.
  */
 @Timeout(60)
 class ConcurrentSpec extends Specification {
+    private final TestLogger logger = new TestLogger()
+
     /**
      * An object that allows instants to be defined and queried.
      *
      * @see NamedInstant
      */
-    final Instants instant = new Instants()
+    final Instants instant = new Instants(logger)
 
     /**
      * An object that allows operations to be defined and queried.
@@ -55,7 +57,7 @@ class ConcurrentSpec extends Specification {
      */
     final TestThread thread = new TestThread(instant)
 
-    private final TestExecutor executor = new TestExecutor(instant)
+    private final TestExecutor executor = new TestExecutor(logger)
     private final TestExecutorFactory executorFactory = new TestExecutorFactory(executor)
 
     /**
@@ -72,8 +74,16 @@ class ConcurrentSpec extends Specification {
         return executorFactory
     }
 
+    def setup() {
+        instant.mainThread(Thread.currentThread())
+    }
+
     def cleanup() {
-        executor.stop(new Date(System.currentTimeMillis() + 5000))
+        try {
+            executor.stop(new Date(System.currentTimeMillis() + 5000))
+        } finally {
+            instant.mainThread(null)
+        }
     }
 
     /**
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy
index 6edda6c..06a144c 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy
@@ -20,11 +20,24 @@ package org.gradle.test.fixtures.concurrent
  * A dynamic collection of {@link NamedInstant} objects. When a property of this object is accessed from a test thread, a new instant is defined. When
  * accessed from the main thread, queries an existing instant, asserting that it exists.
  */
-class Instants implements InstantFactory, TestThreadListener, OperationListener {
+class Instants implements InstantFactory, OperationListener {
     private final Object lock = new Object()
     private final Map<String, NamedInstant> timePoints = [:]
-    private final Set<Thread> testThreads = new HashSet<Thread>()
+    private Thread mainThread
+    private final TestLogger logger
     private int operations
+    private int instantTimeout
+
+    Instants(TestLogger logger) {
+        this.logger = logger
+        instantTimeout = 12000
+    }
+
+    void setTimeout(int instantTimeout) {
+        synchronized (lock) {
+            this.instantTimeout = instantTimeout
+        }
+    }
 
     @Override
     String toString() {
@@ -44,43 +57,29 @@ class Instants implements InstantFactory, TestThreadListener, OperationListener
         }
     }
 
-    void threadStarted(Thread thread) {
+    void mainThread(Thread thread) {
         synchronized (lock) {
-            testThreads.add(thread)
-        }
-    }
-
-    void threadFinished(Thread thread) {
-        synchronized (lock) {
-            testThreads.remove(thread)
-            lock.notifyAll()
+            mainThread = thread;
         }
     }
 
     void waitFor(String name) {
-        long expiry = System.currentTimeMillis() + 12000;
         synchronized (lock) {
+            long expiry = System.currentTimeMillis() + instantTimeout;
             while (!timePoints.containsKey(name) && System.currentTimeMillis() < expiry) {
-                println "* ${Thread.currentThread().name} waiting for instant '$name' ..."
-                if (testThreads.empty && operations == 0) {
-                    throw new IllegalStateException("Cannot wait for instant '$name', as it has not been defined and no test threads are currently running.")
-                }
-                if (testThreads.size() == 1 && testThreads.contains(Thread.currentThread()) && operations == 0) {
-                    throw new IllegalStateException("Cannot wait for instant '$name', as it has not been defined and no other test threads are currently running.")
-                }
+                logger.log "waiting for instant '$name' ..."
                 lock.wait(expiry - System.currentTimeMillis())
             }
             if (timePoints.containsKey(name)) {
                 return
             }
-            throw new IllegalStateException("Timeout waiting for instant '$name' to be defined by another test thread.")
+            throw new IllegalStateException("Timeout waiting for instant '$name' to be defined by another thread.")
         }
     }
 
     def getProperty(String name) {
         synchronized (lock) {
-            def testThread = testThreads.contains(Thread.currentThread())
-            if (testThread) {
+            if (Thread.currentThread() != mainThread) {
                 return now(name)
             } else {
                 return get(name)
@@ -108,7 +107,7 @@ class Instants implements InstantFactory, TestThreadListener, OperationListener
             time = new NamedInstant(name, now, timePoints.size())
             timePoints[name] = time
             lock.notifyAll()
-            println "* ${Thread.currentThread().name} instant $name reached"
+            logger.log "instant '$name' reached"
             return time
         }
     }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy
index 9bd2338..2e40fcb 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy
@@ -25,13 +25,13 @@ class TestExecutor implements Executor {
     private final Lock lock = new ReentrantLock()
     private final Condition condition = lock.newCondition()
     private final Set<Thread> threads = new HashSet<Thread>()
-    private final TestThreadListener listener
+    private final TestLogger logger
     private Thread owner
     private Throwable failure
     private int threadNum
 
-    TestExecutor(TestThreadListener listener) {
-        this.listener = listener
+    TestExecutor(TestLogger logger) {
+        this.logger = logger
     }
 
     void start() {
@@ -51,10 +51,10 @@ class TestExecutor implements Executor {
             @Override
             void run() {
                 try {
-                    println "* ${Thread.currentThread().name} running"
+                    logger.log "running"
                     runnable.run()
                 } catch (Throwable throwable) {
-                    println "* ${Thread.currentThread().name} failed"
+                    logger.log "failed"
                     lock.lock()
                     try {
                         if (failure == null) {
@@ -64,8 +64,7 @@ class TestExecutor implements Executor {
                         lock.unlock()
                     }
                 } finally {
-                    println "* ${Thread.currentThread().name} finished"
-                    listener.threadFinished(Thread.currentThread())
+                    logger.log "finished"
                     lock.lock()
                     try {
                         threads.remove(Thread.currentThread())
@@ -85,7 +84,6 @@ class TestExecutor implements Executor {
             lock.unlock()
         }
 
-        listener.threadStarted(thread)
         thread.start()
     }
 
@@ -93,7 +91,7 @@ class TestExecutor implements Executor {
         lock.lock()
         try {
             if (!threads.isEmpty()) {
-                println "* waiting for ${threads.size()} test threads to complete."
+                logger.log "waiting for ${threads.size()} test threads to complete."
             }
 
             while (!threads.isEmpty()) {
@@ -103,7 +101,7 @@ class TestExecutor implements Executor {
             }
 
             if (!threads.isEmpty()) {
-                println "* timeout waiting for ${threads.size()} threads to complete"
+                logger.log "timeout waiting for ${threads.size()} threads to complete"
                 threads.each { thread ->
                     IllegalStateException e = new IllegalStateException("Timeout waiting for ${thread.name} to complete.")
                     e.stackTrace = thread.stackTrace
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestLogger.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestLogger.groovy
new file mode 100644
index 0000000..d2fbf09
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestLogger.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.concurrent
+
+class TestLogger {
+    private final long startTime = System.currentTimeMillis()
+
+    void log(def msg) {
+        println "* [${System.currentTimeMillis() - startTime}ms] [${Thread.currentThread().name}] ${msg}"
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThreadListener.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThreadListener.groovy
deleted file mode 100644
index 8b288c1..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThreadListener.groovy
+++ /dev/null
@@ -1,22 +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.test.fixtures.concurrent
-
-interface TestThreadListener {
-    void threadStarted(Thread thread)
-    void threadFinished(Thread thread)
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/encoding/Identifier.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/encoding/Identifier.java
new file mode 100644
index 0000000..a781534
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/encoding/Identifier.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.encoding;
+
+import org.gradle.internal.os.OperatingSystem;
+
+public class Identifier {
+    private static final String PUNCTUATION_CHARS = "-'!@#$%^&*()_+=,.?{}[]<>";
+    private static final String NON_ASCII_CHARS = "-√æず∫ʙぴ₦ガき∆ç√∫";
+    private static final String FILESYSTEM_RESERVED_CHARS = "-./\\?%*:|\"<>";
+    private static final String XML_MARKUP_CHARS = "-<with>some<xml-markup/></with>";
+
+    private final String suffix;
+
+    public Identifier(String suffix) {
+        this.suffix = suffix == null ? "" : suffix;
+    }
+
+    public Identifier withPunctuation() {
+        return new Identifier(suffix + PUNCTUATION_CHARS);
+    }
+
+    public Identifier withNonAscii() {
+        return new Identifier(suffix + NON_ASCII_CHARS);
+    }
+
+    public Identifier withReservedFileSystemChars() {
+        return new Identifier(suffix + FILESYSTEM_RESERVED_CHARS);
+    }
+
+    public Identifier withMarkup() {
+        return new Identifier(suffix + XML_MARKUP_CHARS);
+    }
+
+    public Identifier withWhiteSpace() {
+        return new Identifier(suffix + " with white space");
+    }
+
+    public Identifier safeForFileName() {
+        return without(getUnsupportedFileNameCharacters());
+    }
+
+    public Identifier without(String toRemove) {
+        String newSuffix = suffix;
+        for (char c : toRemove.toCharArray()) {
+            newSuffix = newSuffix.replace(c, '-');
+        }
+        return new Identifier(newSuffix);
+    }
+
+    private static String getUnsupportedFileNameCharacters() {
+        if (OperatingSystem.current().isWindows()) {
+            return "<>:\"/\\|?*";
+        }
+        return "/\\";
+    }
+
+    public String decorate(String prefix) {
+        return prefix + suffix;
+    }
+
+    @Override
+    public String toString() {
+        return suffix;
+    }
+
+    public static Identifier getPunctuation() {
+        return new Identifier("").withPunctuation();
+    }
+
+    public static Identifier getNonAscii() {
+        return new Identifier("").withNonAscii();
+    }
+
+    public static Identifier getFileSystemReserved() {
+        return new Identifier("").withReservedFileSystemChars();
+    }
+
+    public static Identifier getXmlMarkup() {
+        return new Identifier("").withMarkup();
+    }
+
+    public static Identifier getWhiteSpace() {
+        return new Identifier("").withWhiteSpace();
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/ExecOutput.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/ExecOutput.groovy
new file mode 100644
index 0000000..2b1c77d
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/ExecOutput.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.file
+
+class ExecOutput {
+    ExecOutput(String rawOutput, String error) {
+        this.rawOutput = rawOutput
+        this.out = rawOutput.replaceAll("\r\n|\r", "\n")
+        this.error = error
+    }
+
+    String rawOutput
+    String out
+    String error
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java
index 13c3b34..a144917 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java
@@ -24,6 +24,8 @@ import org.apache.tools.ant.taskdefs.Tar;
 import org.apache.tools.ant.taskdefs.Zip;
 import org.apache.tools.ant.types.EnumeratedAttribute;
 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.gradle.internal.nativeplatform.filesystem.*;
+import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.hamcrest.Matcher;
 
 import java.io.*;
@@ -65,6 +67,16 @@ public class TestFile extends File {
         return new File(getAbsolutePath());
     }
 
+    @Override
+    public File getCanonicalFile() throws IOException {
+        return new File(getAbsolutePath()).getCanonicalFile();
+    }
+
+    @Override
+    public String getCanonicalPath() throws IOException {
+        return new File(getAbsolutePath()).getCanonicalPath();
+    }
+
     private static URI toUri(URL url) {
         try {
             return url.toURI();
@@ -101,6 +113,10 @@ public class TestFile extends File {
         return files;
     }
 
+    public TestFile withExtension(String extension) {
+        return getParentFile().file(getName().replaceAll("\\..*$", "." + extension));
+    }
+
     public TestFile writelns(String... lines) {
         return writelns(Arrays.asList(lines));
     }
@@ -336,9 +352,16 @@ public class TestFile extends File {
         }
     }
 
-    public TestFile assertPermissions(Matcher<String> matcher) {
-        assertThat(String.format("mismatched permissions for '%s'", this), getPermissions(), matcher);
-        return this;
+    public void createLink(File target) {
+        createLink(target.getAbsolutePath());
+    }
+
+    public void createLink(String target) {
+        try {
+            NativeServices.getInstance().get(FileSystem.class).createSymbolicLink(this, new File(target));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     public String readLink() {
@@ -513,6 +536,11 @@ public class TestFile extends File {
         assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash));
     }
 
+    public void assertContentsHaveChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertTrue(String.format("contents of %s have not changed", this), !Arrays.equals(now.hash, snapshot.hash));
+    }
+
     public void assertContentsHaveNotChangedSince(Snapshot snapshot) {
         Snapshot now = snapshot();
         assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
@@ -539,8 +567,12 @@ public class TestFile extends File {
         }
     }
     
-    public Map<String, ?> exec(Object... args) {
-        return new TestFileHelper(this).exec(args);
+    public ExecOutput exec(Object... args) {
+        return new TestFileHelper(this).execute(Arrays.asList(args), null);
+    }
+
+    public ExecOutput execute(List args, List env) {
+        return new TestFileHelper(this).execute(args, env);
     }
 
     public class Snapshot {
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy
index 1f09902..5be32bc 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy
@@ -115,7 +115,7 @@ class TestFileHelper {
             throw new RuntimeException("Could not list permissions for '$file': $error")
         }
         def perms = result.split()[0]
-        assert perms.matches("[d\\-][rwx\\-]{9}[@\\+]?")
+        assert perms.matches("[d\\-][rwx\\-]{9}[@\\.\\+]?")
         return perms.substring(1, 10)
     }
 
@@ -173,14 +173,18 @@ class TestFileHelper {
         return process.inputStream.text.trim()
     }
 
-    Map<String, ?> exec(Object... args) {
-        def process = ([file.absolutePath] + (args as List)).execute()
-        def output = process.inputStream.text
-        def error = process.errorStream.text
+    ExecOutput exec(List args) {
+        return execute(args, null)
+    }
+
+    ExecOutput execute(List args, List env) {
+        def process = ([file.absolutePath] + args).execute(env, null)
+        String output = process.inputStream.text
+        String error = process.errorStream.text
         if (process.waitFor() != 0) {
             throw new RuntimeException("Could not execute $file. Error: $error, Output: $output")
         }
-        return [out: output, error: error]
+        return new ExecOutput(output, error)
     }
 
     public void zipTo(TestFile zipFile, boolean nativeTools) {
@@ -213,4 +217,4 @@ class TestFileHelper {
             tar.execute()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/WorkspaceTest.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/WorkspaceTest.groovy
new file mode 100644
index 0000000..50061dc
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/WorkspaceTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.file
+
+import org.junit.Rule
+import spock.lang.Specification
+
+class WorkspaceTest extends Specification {
+
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    TestFile getTestDirectory() {
+        temporaryFolder.testDirectory
+    }
+
+    TestFile file(Object... path) {
+        getTestDirectory().file(path)
+    }
+
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/Network.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/Network.groovy
deleted file mode 100644
index 3475ac1..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/Network.groovy
+++ /dev/null
@@ -1,29 +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.testing.internal.util
-
-class Network {
-    
-    static boolean isOffline() {
-        try {
-            new URL("http://google.com").openConnection().openStream()
-            false
-        } catch (IOException) {
-            true
-        }
-    }
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy
index 3019184..c27fa81 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy
@@ -16,9 +16,6 @@
 
 package org.gradle.util;
 
-/**
- * by Szczepan Faber, created at: 12/18/12
- */
 class Assertions {
 
     private final Object source
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java
index 0adf6f3..1a8212e 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java
@@ -16,9 +16,6 @@
 
 package org.gradle.util;
 
-/**
- * @author Hans Dockter
- */
 
 import groovy.lang.Closure;
 import org.codehaus.groovy.runtime.InvokerInvocationException;
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Matchers.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Matchers.java
new file mode 100644
index 0000000..45d54f9
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Matchers.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.internal.ReturnDefaultValueAction;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertTrue;
+
+public class Matchers {
+    /**
+     * A reimplementation of hamcrest's isA() but without the broken generics.
+     */
+    @Factory
+    public static Matcher<Object> isA(final Class<?> type) {
+        return new BaseMatcher<Object>() {
+            public boolean matches(Object item) {
+                return type.isInstance(item);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("instanceof ").appendValue(type);
+            }
+        };
+    }
+
+    @Factory
+    public static <T> Matcher<T> reflectionEquals(T equalsTo) {
+        return new ReflectionEqualsMatcher<T>(equalsTo);
+    }
+
+    @Factory
+    public static <T, S extends Iterable<? extends T>> Matcher<S> hasSameItems(final S items) {
+        return new BaseMatcher<S>() {
+            public boolean matches(Object o) {
+                @SuppressWarnings("unchecked") Iterable<? extends T> iterable = (Iterable<? extends T>) o;
+                List<T> actual = new ArrayList<T>();
+                for (T t : iterable) {
+                    actual.add(t);
+                }
+                List<T> expected = new ArrayList<T>();
+                for (T t : items) {
+                    expected.add(t);
+                }
+
+                return expected.equals(actual);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an Iterable that has same items as ").appendValue(items);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends CharSequence> Matcher<T> matchesRegexp(final String pattern) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return Pattern.compile(pattern).matcher((CharSequence) o).matches();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends CharSequence> Matcher<T> matchesRegexp(final Pattern pattern) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return pattern.matcher((CharSequence) o).matches();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
+            }
+        };
+    }
+
+    @Factory
+    public static <T 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) {
+                return strictlyEquals(o, other);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an Object that strictly equals ").appendValue(other);
+            }
+        };
+    }
+
+    public static boolean strictlyEquals(Object a, Object b) {
+        if (!a.equals(b)) {
+            return false;
+        }
+        if (!b.equals(a)) {
+            return false;
+        }
+        if (!a.equals(a)) {
+            return false;
+        }
+        if (b.equals(null)) {
+            return false;
+        }
+        if (b.equals(new Object())) {
+            return false;
+        }
+        if (a.hashCode() != b.hashCode()) {
+            return false;
+        }
+        return true;
+
+    }
+
+    @Factory
+    @Deprecated
+    /**
+     * Please avoid using as the hamcrest way of reporting error wraps a multi-line
+     * text into a single line and makes hard to understand the problem.
+     * Instead, please try to use the spock/groovy assert and {@link #containsLine(String, String)}
+     */
+    public static Matcher<String> containsLine(final String line) {
+        return new BaseMatcher<String>() {
+            public boolean matches(Object o) {
+                return containsLine(equalTo(line)).matches(o);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a String that contains line ").appendValue(line);
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<String> containsLine(final Matcher<? super String> matcher) {
+        return new BaseMatcher<String>() {
+            public boolean matches(Object o) {
+                String str = (String) o;
+                return containsLine(str, matcher);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a String that contains line that is ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    public static boolean containsLine(String action, String expected) {
+        return containsLine(action, equalTo(expected));
+    }
+
+    public static boolean containsLine(String actual, Matcher<? super String> matcher) {
+        BufferedReader reader = new BufferedReader(new StringReader(actual));
+        String line;
+        try {
+            while ((line = reader.readLine()) != null) {
+                if (matcher.matches(line)) {
+                    return true;
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return false;
+    }
+
+    @Factory
+    public static Matcher<Iterable<?>> isEmpty() {
+        return new BaseMatcher<Iterable<?>>() {
+            public boolean matches(Object o) {
+                Iterable<?> iterable = (Iterable<?>) o;
+                return iterable != null && !iterable.iterator().hasNext();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty Iterable");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Map<?, ?>> isEmptyMap() {
+        return new BaseMatcher<Map<?, ?>>() {
+            public boolean matches(Object o) {
+                Map<?, ?> map = (Map<?, ?>) o;
+                return map != null && map.isEmpty();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty map");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Object> isSerializable() {
+        return new BaseMatcher<Object>() {
+            public boolean matches(Object o) {
+                try {
+                    new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(o);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+                return true;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("is serializable");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
+        return new BaseMatcher<Throwable>() {
+            public boolean matches(Object o) {
+                Throwable t = (Throwable) o;
+                return matcher.matches(t.getMessage());
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an exception with message that is ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    /**
+     * Returns a placeholder for a mock method parameter.
+     */
+    public static <T> Collector<T> collector() {
+        return new Collector<T>();
+    }
+
+    /**
+     * Returns an action which collects the first parameter into the given placeholder.
+     */
+    public static CollectAction collectTo(Collector<?> collector) {
+        return new CollectAction(collector);
+    }
+
+    public static class CollectAction implements Action {
+        private Action action = new ReturnDefaultValueAction();
+        private final Collector<?> collector;
+
+        public CollectAction(Collector<?> collector) {
+            this.collector = collector;
+        }
+
+        public Action then(Action action) {
+            this.action = action;
+            return this;
+        }
+
+        public void describeTo(Description description) {
+            description.appendText("collect parameter then ").appendDescriptionOf(action);
+        }
+
+        public Object invoke(Invocation invocation) throws Throwable {
+            collector.setValue(invocation.getParameter(0));
+            return action.invoke(invocation);
+        }
+    }
+
+    public static class Collector<T> {
+        private T value;
+        private boolean set;
+
+        public T get() {
+            assertTrue(set);
+            return value;
+        }
+
+        @SuppressWarnings("unchecked")
+        void setValue(Object parameter) {
+            value = (T) parameter;
+            set = true;
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/ReflectionEqualsMatcher.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/ReflectionEqualsMatcher.java
index db84653..66bd065 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/ReflectionEqualsMatcher.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/ReflectionEqualsMatcher.java
@@ -19,9 +19,6 @@ import org.apache.commons.lang.builder.EqualsBuilder;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 
-/**
- * @author Hans Dockter
-*/
 public class ReflectionEqualsMatcher<T> extends BaseMatcher<T> {
     private T toMatch;
 
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
index 774560e..c5e9db2 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
@@ -53,7 +53,7 @@ public class Resources implements MethodRule {
         if (resource == null) {
             return null;
         }
-        assertEquals("file", resource.getProtocol());
+        assertEquals(String.format("Cannot handle resource URI %s", resource), "file", resource.getProtocol());
         File file;
         try {
             file = new File(resource.toURI());
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestClassLoader.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestClassLoader.groovy
new file mode 100644
index 0000000..fb76246
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestClassLoader.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+/**
+ * A custom ClassLoader implementation. Used in various places to test that we work with things that are not a
+ * URLClassLoader.
+ */
+class TestClassLoader extends ClassLoader {
+    private final List<File> classpath
+
+    TestClassLoader(ClassLoader classLoader, List<File> classpath) {
+        super(classLoader)
+        this.classpath = classpath
+    }
+
+    @Override
+    protected URL findResource(String name) {
+        for (File file : classpath) {
+            if (file.directory) {
+                def classFile = new File(file, name)
+                if (classFile.exists()) {
+                    return classFile.toURI().toURL()
+                }
+            } else if (file.isFile()) {
+                def url = new URL("jar:${file.toURI().toURL()}!/${name}")
+                try {
+                    url.openStream().close()
+                    return url
+                } catch (FileNotFoundException) {
+                    // Ignore
+                }
+            }
+        }
+        return null
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        String resource = name.replace('.', '/') + '.class'
+        URL url = findResource(resource)
+        if (url == null) {
+            throw new ClassNotFoundException("Could not find class '${name}'")
+        }
+        def byteCode = url.bytes
+        return defineClass(name, byteCode, 0, byteCode.length)
+    }
+}
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 56fbc3a..98367f6 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
@@ -15,6 +15,7 @@
  */
 package org.gradle.util
 
+import org.gradle.api.JavaVersion
 import org.gradle.internal.os.OperatingSystem
 
 enum TestPrecondition {
@@ -69,6 +70,9 @@ enum TestPrecondition {
     LINUX({
         OperatingSystem.current().linux
     }),
+    NOT_LINUX({
+        !LINUX.fulfilled
+    }),
     UNIX({
         OperatingSystem.current().unix
     }),
@@ -79,26 +83,33 @@ enum TestPrecondition {
         !UNKNOWN_OS.fulfilled
     }),
     JDK5({
-        System.getProperty("java.version").startsWith("1.5")
+        JavaVersion.current().java5
+    }),
+    JDK6_OR_LATER({
+        JavaVersion.current() >= JavaVersion.VERSION_1_6
     }),
-    JDK6({
-        System.getProperty("java.version").startsWith("1.6")
+    JDK6_OR_EARLIER({
+        JavaVersion.current() <= JavaVersion.VERSION_1_6
     }),
-    JDK7({
-        System.getProperty("java.version").startsWith("1.7")
+    JDK7_OR_LATER({
+        JavaVersion.current() >= JavaVersion.VERSION_1_7
     }),
-    NOT_JDK5({
-        !JDK5.fulfilled
+    JDK7_POSIX({
+        JDK7_OR_LATER.fulfilled && NOT_WINDOWS.fulfilled
     }),
-    NOT_JDK7({
-        !JDK7.fulfilled
+    NOT_JDK_IBM({
+        System.getProperty('java.vm.vendor') != 'IBM Corporation'
     }),
-    CAN_RESOLVE_UNICODE_POM({
-        // Ivy parsing Maven POM files with unicode characters is broken in JDK1.6 on Linux (& Unknown OS)
-        !(JDK6.fulfilled && (LINUX.fulfilled || UNKNOWN_OS.fulfilled))
+    ONLINE({
+        try {
+            new URL("http://google.com").openConnection().openStream()
+            true
+        } catch (IOException) {
+            false
+        }
     }),
-    JDK7_POSIX({
-        JDK7.fulfilled && NOT_WINDOWS.fulfilled
+    CAN_INSTALL_EXECUTABLE({
+        FILE_PERMISSIONS.fulfilled || WINDOWS.fulfilled
     });
 
     /**
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy
index 858b89f..bbbcd69 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy
@@ -22,11 +22,11 @@ import org.spockframework.runtime.model.FeatureInfo
 class TestPreconditionExtension extends AbstractAnnotationDrivenExtension<Requires> {
     @Override
     void visitSpecAnnotation(Requires annotation, SpecInfo spec) {
-        spec.skipped = annotation.value().any() { !it.fulfilled }
+        spec.skipped |= annotation.value().any() { !it.fulfilled }
     }
 
     @Override
     void visitFeatureAnnotation(Requires annotation, FeatureInfo feature) {
-        feature.skipped = annotation.value().any() { !it.fulfilled }
+        feature.skipped |= annotation.value().any() { !it.fulfilled }
     }
 }
diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy
index aa989a1..7120b6c 100644
--- a/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy
+++ b/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy
@@ -230,7 +230,7 @@ class ConcurrentSpecTest extends ConcurrentSpec {
         e.message == "Operation 'doStuff' has not completed yet."
     }
 
-    def "can use test threads to define instants outside of an async { } block"() {
+    def "can use test threads to define instants"() {
         def worker = new Worker(executor)
 
         given:
@@ -246,13 +246,28 @@ class ConcurrentSpecTest extends ConcurrentSpec {
         instant.actionExecuted
     }
 
-    def "fails when waiting for an instant and no other test threads are running"() {
+    def "can use arbitrary thread to define an instant"() {
+        given:
+        def action = { instant.actionExecuted }
+        def thread = new Thread(action)
+
+        when:
+        thread.start()
+        thread.join()
+
+        then:
+        instant.actionExecuted
+    }
+
+    def "fails when waiting for an instant that is not defined by any thread"() {
+        instant.timeout = 100
+
         when:
         thread.blockUntil.unknown
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cannot wait for instant 'unknown', as it has not been defined and no test threads are currently running."
+        e.message == "Timeout waiting for instant 'unknown' to be defined by another thread."
 
         when:
         async {
@@ -261,7 +276,7 @@ class ConcurrentSpecTest extends ConcurrentSpec {
 
         then:
         e = thrown()
-        e.message == "Cannot wait for instant 'unknown', as it has not been defined and no other test threads are currently running."
+        e.message == "Timeout waiting for instant 'unknown' to be defined by another thread."
 
         when:
         start {
@@ -271,7 +286,7 @@ class ConcurrentSpecTest extends ConcurrentSpec {
 
         then:
         e = thrown()
-        e.message == "Cannot wait for instant 'unknown', as it has not been defined and no other test threads are currently running."
+        e.message == "Timeout waiting for instant 'unknown' to be defined by another thread."
     }
 
     def "async { } block rethrows test thread failures"() {
diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy
index a85c621..99aa43c 100644
--- a/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy
+++ b/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy
@@ -20,9 +20,6 @@ import spock.lang.Specification
 
 import static org.gradle.util.Assertions.assertThat
 
-/**
- * by Szczepan Faber, created at: 12/18/12
- */
 class AssertionsTest extends Specification {
 
     static class Foo {
diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy
index 5885d21..7f9bf4e 100644
--- a/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy
+++ b/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy
@@ -19,9 +19,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 3/14/12
- */
 class TempDirIsUniquePerTestSpec extends Specification {
 
     @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
diff --git a/subprojects/ivy/ivy.gradle b/subprojects/ivy/ivy.gradle
index 197b993..7edbb63 100644
--- a/subprojects/ivy/ivy.gradle
+++ b/subprojects/ivy/ivy.gradle
@@ -17,11 +17,12 @@
 
 
 dependencies {
-    groovy libraries.groovy
-    compile project(':core'),
-            project(':publish'),
-            project(':plugins') // for base plugin to get archives conf
+    compile project(':core')
+    compile project(':publish')
+    compile project(':plugins') // for base plugin to get archives conf
+    compile project(':coreImpl')
 
+    testCompile libraries.groovy
     integTestCompile project(":ear")
 }
 
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomisationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomisationIntegTest.groovy
deleted file mode 100644
index f1b645c..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomisationIntegTest.groovy
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.test.fixtures.ivy.IvyDescriptorArtifact
-
-class IvyPublishArtifactCustomisationIntegTest extends AbstractIvyPublishIntegTest {
-
-    def module = ivyRepo.module("org.gradle.test", "ivyPublish", "2.4")
-
-    public void "can publish custom artifacts"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    artifact "customFile.txt"
-                    artifact customDocsTask.outputFile
-                    artifact customJar
-                }
-            }
-""", """
-        publishing {
-            publishIvyPublicationToIvyRepository.dependsOn(customDocsTask)
-        }
-""")
-
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4.txt", "ivyPublish-2.4.html", "ivyPublish-2.4.jar")
-
-        and:
-        def ivy = module.ivy
-        ivy.expectArtifact('ivyPublish', 'txt').hasType("txt").hasConf(null)
-        ivy.expectArtifact('ivyPublish', 'html').hasType("html").hasConf(null)
-        ivy.expectArtifact('ivyPublish', 'jar').hasType("jar").hasConf(null)
-
-        and:
-        resolveArtifacts(module) == ["ivyPublish-2.4.html", "ivyPublish-2.4.jar", "ivyPublish-2.4.txt"]
-    }
-
-    def "can configure custom artifacts when creating"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    configurations {
-                        foo
-                        bar
-                        "default" {
-                            extend "foo"
-                        }
-                    }
-                    artifact("customFile.txt") {
-                        name "customFile"
-                        classifier "classified"
-                        conf "foo,bar"
-                    }
-                    artifact(customDocsTask.outputFile) {
-                        name "docs"
-                        extension "htm"
-                        builtBy customDocsTask
-                    }
-                    artifact(customJar) {
-                        extension "war"
-                        type "web-archive"
-                        conf "*"
-                    }
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "docs-2.4.htm", "customFile-2.4-classified.txt", "ivyPublish-2.4.war")
-
-        and:
-        def ivy = module.ivy
-        ivy.expectArtifact("ivyPublish", "war").hasType("web-archive").hasConf(["*"])
-        ivy.expectArtifact("docs", "htm").hasType("html").hasConf(null)
-        ivy.expectArtifact("customFile", "txt", "classified").hasType("txt").hasConf(["foo", "bar"])
-
-        and:
-        resolveArtifacts(module) == ["customFile-2.4-classified.txt", "docs-2.4.htm", "ivyPublish-2.4.war"]
-    }
-
-    def "can publish custom file artifacts with map notation"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    configurations {
-                        foo
-                        bar
-                        "default" {
-                            extend "foo"
-                        }
-                    }
-                    artifact source: "customFile.txt", name: "customFile", classifier: "classified", conf: "foo,bar"
-                    artifact source: customDocsTask.outputFile, name: "docs", extension: "htm", builtBy: customDocsTask
-                    artifact source: customJar, extension: "war", type: "web-archive", conf: "*"
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "docs-2.4.htm", "customFile-2.4-classified.txt", "ivyPublish-2.4.war")
-
-        and:
-        def ivy = module.ivy
-        ivy.expectArtifact("ivyPublish", "war").hasType("web-archive").hasConf(["*"])
-        ivy.expectArtifact("docs", "htm").hasType("html").hasConf(null)
-        ivy.expectArtifact("customFile", "txt", "classified").hasType("txt").hasConf(["foo", "bar"])
-
-        and:
-        resolveArtifacts(module) == ["customFile-2.4-classified.txt", "docs-2.4.htm", "ivyPublish-2.4.war"]
-    }
-
-    def "can set custom artifacts to override component artifacts"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    from components.java
-                    artifacts = ["customFile.txt", customDocsTask.outputFile, customJar]
-                }
-            }
-""", """
-            publishing {
-                publishIvyPublicationToIvyRepository.dependsOn(customDocsTask)
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4.txt", "ivyPublish-2.4.html", "ivyPublish-2.4.jar")
-        module.ivy.artifacts.collect({"${it.name}.${it.ext}"}) as Set == ["ivyPublish.txt", "ivyPublish.html", "ivyPublish.jar"] as Set
-    }
-
-    def "can configure custom artifacts post creation"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    configurations {
-                        mod_conf {}
-                        other {}
-                    }
-                    artifact source: "customFile.txt", name: "customFile"
-                    artifact source: customDocsTask.outputFile, name: "docs", builtBy: customDocsTask
-                    artifact source: customJar
-                }
-            }
-""", """
-            publishing.publications.ivy.artifacts.each {
-                it.extension = "mod"
-                it.conf = "mod_conf"
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "customFile-2.4.mod", "docs-2.4.mod", "ivyPublish-2.4.mod")
-
-        for (IvyDescriptorArtifact artifact : module.ivy.artifacts) {
-            artifact.ext == "mod"
-            artifact.conf == "mod-conf"
-        }
-    }
-
-    def "can publish artifact with no extension"() {
-        given:
-        file("no-extension") << "some content"
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    artifact source: 'no-extension', name: 'no-extension', type: 'ext-less'
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "no-extension-2.4")
-        module.ivy.expectArtifact("no-extension").hasAttributes("", "ext-less", null)
-
-        // TODO:DAZ Fix publication with empty extension so it can be resolved
-//        and:
-//        resolveArtifacts(module) == ["no-extension-2.4"]
-    }
-
-    def "can publish artifact with classifier"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    artifact source: customJar, classifier: "classy"
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4-classy.jar")
-        module.ivy.expectArtifact("ivyPublish").hasAttributes("jar", "jar", null, "classy")
-
-        and:
-        resolveArtifacts(module) == ["ivyPublish-2.4-classy.jar"]
-    }
-
-    def "can add custom configurations"() {
-        given:
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    configurations {
-                        runtime
-                        base {}
-                        custom {
-                            extend "runtime"
-                            extend "base"
-                        }
-                    }
-                }
-            }
-""")
-
-        when:
-        succeeds 'publish'
-
-        then:
-        module.assertPublished()
-        def ivy = module.ivy
-        ivy.configurations.keySet() == ["base", "custom", "runtime"] as Set
-        ivy.configurations["runtime"].extend == null
-        ivy.configurations["base"].extend == null
-        ivy.configurations["custom"].extend == ["runtime", "base"] as Set
-    }
-
-    def "reports failure publishing when validation fails"() {
-        given:
-        file("a-directory.dir").createDir()
-
-        createBuildScripts("""
-            publications {
-                ivy(IvyPublication) {
-                    artifact "a-directory.dir"
-                }
-            }
-""")
-        when:
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':publishIvyPublicationToIvyRepository'.")
-        failure.assertHasCause("Failed to publish publication 'ivy' to repository 'ivy'")
-        failure.assertHasCause("Invalid publication 'ivy': artifact file is a directory")
-    }
-
-    private createBuildScripts(def publications, def append = "") {
-        file("customFile.txt") << "some content"
-        settingsFile << "rootProject.name = 'ivyPublish'"
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            group = 'org.gradle.test'
-            version = '2.4'
-
-            task customDocsTask {
-                ext.outputFile = file('customDocs.html')
-                doLast {
-                    outputFile << '<html/>'
-                }
-            }
-
-            task customJar(type: Jar) {
-                from file("customFile.txt")
-                baseName "customJar"
-            }
-
-            publishing {
-                repositories {
-                    ivy { url "${ivyRepo.uri}" }
-                }
-                $publications
-            }
-
-            $append
-        """
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomizationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomizationIntegTest.groovy
new file mode 100644
index 0000000..d8e7830
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomizationIntegTest.groovy
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.test.fixtures.ivy.IvyDescriptorArtifact
+
+class IvyPublishArtifactCustomizationIntegTest extends AbstractIvyPublishIntegTest {
+
+    def module = ivyRepo.module("org.gradle.test", "ivyPublish", "2.4")
+
+    public void "can publish custom artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact "customFile.txt"
+                    artifact customDocsTask.outputFile
+                    artifact customJar
+                }
+            }
+""", """
+        model {
+            tasks.publishIvyPublicationToIvyRepository {
+              dependsOn "customDocsTask"
+            }
+        }
+""")
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4.txt", "ivyPublish-2.4.html", "ivyPublish-2.4.jar")
+
+        and:
+        def ivy = module.parsedIvy
+        ivy.expectArtifact('ivyPublish', 'txt').hasType("txt").hasConf(null)
+        ivy.expectArtifact('ivyPublish', 'html').hasType("html").hasConf(null)
+        ivy.expectArtifact('ivyPublish', 'jar').hasType("jar").hasConf(null)
+
+        and:
+        resolveArtifacts(module) == ["ivyPublish-2.4.html", "ivyPublish-2.4.jar", "ivyPublish-2.4.txt"]
+    }
+
+    def "can configure custom artifacts when creating"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        foo {}
+                        bar {}
+                        "default" {
+                            extend "foo"
+                        }
+                    }
+                    artifact("customFile.txt") {
+                        name "customFile"
+                        classifier "classified"
+                        conf "foo,bar"
+                    }
+                    artifact(customDocsTask.outputFile) {
+                        name "docs"
+                        extension "htm"
+                        builtBy customDocsTask
+                    }
+                    artifact(customJar) {
+                        extension "war"
+                        type "web-archive"
+                        conf "*"
+                    }
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "docs-2.4.htm", "customFile-2.4-classified.txt", "ivyPublish-2.4.war")
+
+        and:
+        def ivy = module.parsedIvy
+        ivy.expectArtifact("ivyPublish", "war").hasType("web-archive").hasConf(["*"])
+        ivy.expectArtifact("docs", "htm").hasType("html").hasConf(null)
+        ivy.expectArtifact("customFile", "txt", "classified").hasType("txt").hasConf(["foo", "bar"])
+
+        and:
+        resolveArtifacts(module) == ["customFile-2.4-classified.txt", "docs-2.4.htm", "ivyPublish-2.4.war"]
+    }
+
+    def "can publish custom file artifacts with map notation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        foo {}
+                        bar {}
+                        "default" {
+                            extend "foo"
+                        }
+                    }
+                    artifact source: "customFile.txt", name: "customFile", classifier: "classified", conf: "foo,bar"
+                    artifact source: customDocsTask.outputFile, name: "docs", extension: "htm", builtBy: customDocsTask
+                    artifact source: customJar, extension: "war", type: "web-archive", conf: "*"
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "docs-2.4.htm", "customFile-2.4-classified.txt", "ivyPublish-2.4.war")
+
+        and:
+        def ivy = module.parsedIvy
+        ivy.expectArtifact("ivyPublish", "war").hasType("web-archive").hasConf(["*"])
+        ivy.expectArtifact("docs", "htm").hasType("html").hasConf(null)
+        ivy.expectArtifact("customFile", "txt", "classified").hasType("txt").hasConf(["foo", "bar"])
+
+        and:
+        resolveArtifacts(module) == ["customFile-2.4-classified.txt", "docs-2.4.htm", "ivyPublish-2.4.war"]
+    }
+
+    def "can set custom artifacts to override component artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    from components.java
+                    artifacts = ["customFile.txt", customDocsTask.outputFile, customJar]
+                }
+            }
+""", """
+            model {
+                tasks.publishIvyPublicationToIvyRepository {
+                    dependsOn("customDocsTask")
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4.txt", "ivyPublish-2.4.html", "ivyPublish-2.4.jar")
+        module.parsedIvy.artifacts.collect({"${it.name}.${it.ext}"}) as Set == ["ivyPublish.txt", "ivyPublish.html", "ivyPublish.jar"] as Set
+    }
+
+    def "can configure custom artifacts post creation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        mod_conf {}
+                        other {}
+                    }
+                    artifact source: "customFile.txt", name: "customFile"
+                    artifact source: customDocsTask.outputFile, name: "docs", builtBy: customDocsTask
+                    artifact source: customJar
+                }
+            }
+""", """
+            publishing.publications.ivy.artifacts.each {
+                it.extension = "mod"
+                it.conf = "mod_conf"
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "customFile-2.4.mod", "docs-2.4.mod", "ivyPublish-2.4.mod")
+
+        for (IvyDescriptorArtifact artifact : module.parsedIvy.artifacts) {
+            artifact.ext == "mod"
+            artifact.conf == "mod-conf"
+        }
+    }
+
+    def "can publish artifact with no extension"() {
+        given:
+        file("no-extension") << "some content"
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact source: 'no-extension', name: 'no-extension', type: 'ext-less'
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "no-extension-2.4")
+        module.parsedIvy.expectArtifact("no-extension").hasAttributes("", "ext-less", null)
+
+        and:
+        resolveArtifacts(module) == ["no-extension-2.4"]
+    }
+
+    def "can publish artifact with classifier"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact source: customJar, classifier: "classy"
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4-classy.jar")
+        module.parsedIvy.expectArtifact("ivyPublish").hasAttributes("jar", "jar", null, "classy")
+
+        and:
+        resolveArtifacts(module) == ["ivyPublish-2.4-classy.jar"]
+    }
+
+    def "can add custom configurations"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        runtime {}
+                        base {}
+                        custom {
+                            extend "runtime"
+                            extend "base"
+                        }
+                    }
+                }
+            }
+""")
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        def ivy = module.parsedIvy
+        ivy.configurations.keySet() == ["base", "custom", "runtime"] as Set
+        ivy.configurations["runtime"].extend == null
+        ivy.configurations["base"].extend == null
+        ivy.configurations["custom"].extend == ["runtime", "base"] as Set
+    }
+
+    def "reports failure publishing when validation fails"() {
+        given:
+        file("a-directory.dir").createDir()
+
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact "a-directory.dir"
+                }
+            }
+""")
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishIvyPublicationToIvyRepository'.")
+        failure.assertHasCause("Failed to publish publication 'ivy' to repository 'ivy'")
+        failure.assertHasCause("Invalid publication 'ivy': artifact file is a directory")
+    }
+
+    private createBuildScripts(def publications, def append = "") {
+        file("customFile.txt") << "some content"
+        settingsFile << "rootProject.name = 'ivyPublish'"
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            group = 'org.gradle.test'
+            version = '2.4'
+
+            task customDocsTask {
+                ext.outputFile = file('customDocs.html')
+                doLast {
+                    outputFile << '<html/>'
+                }
+            }
+
+            task customJar(type: Jar) {
+                from file("customFile.txt")
+                baseName "customJar"
+            }
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                $publications
+            }
+
+            $append
+        """
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy
index dee5167..ac662fd 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy
@@ -68,11 +68,11 @@ public class IvyPublishBasicIntegTest extends AbstractIvyPublishIntegTest {
         module.assertArtifactsPublished("ivy-1.0.xml")
 
         and:
-        with (module.ivy) {
+        with (module.parsedIvy) {
             configurations.isEmpty()
             artifacts.isEmpty()
             dependencies.isEmpty()
-            status == "release"
+            status == "integration"
         }
 
         and:
@@ -91,7 +91,6 @@ public class IvyPublishBasicIntegTest extends AbstractIvyPublishIntegTest {
 
             group = 'group'
             version = '1.0'
-            status = 'integration'
 
             publishing {
                 repositories {
@@ -117,7 +116,7 @@ public class IvyPublishBasicIntegTest extends AbstractIvyPublishIntegTest {
 
         then: "jar is published to defined ivy repository"
         module.assertPublishedAsJavaModule()
-        module.ivy.status == 'integration'
+        module.parsedIvy.status == 'integration'
         module.moduleDir.file('root-1.0.jar').assertIsCopyOf(file('build/libs/root-1.0.jar'))
 
         and:
@@ -150,7 +149,7 @@ public class IvyPublishBasicIntegTest extends AbstractIvyPublishIntegTest {
         fails 'publish'
 
         then:
-        failure.assertHasDescription("A problem occurred configuring the 'publishing' extension")
+        failure.assertHasDescription("A problem occurred configuring root project 'bad-project'.")
         failure.assertHasCause("Ivy publication 'ivy' cannot include multiple components")
     }
 
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCoordinatesIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCoordinatesIntegTest.groovy
new file mode 100644
index 0000000..ed730fa
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCoordinatesIntegTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+public class IvyPublishCoordinatesIntegTest extends AbstractIvyPublishIntegTest {
+
+    def "can publish single jar with specified coordinates"() {
+        given:
+        def module = ivyRepo.module('org.custom', 'custom', '2.2')
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'ivy-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                        organisation "org.custom"
+                        module "custom"
+                        revision "2.2"
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        file('build/libs/root-1.0.jar').assertExists()
+
+        and:
+        module.assertPublishedAsJavaModule()
+        module.moduleDir.file('custom-2.2.jar').assertIsCopyOf(file('build/libs/root-1.0.jar'))
+
+        and:
+        resolveArtifacts(module) == ['custom-2.2.jar']
+    }
+
+    def "can produce multiple separate publications for single project"() {
+        given:
+        def module = ivyRepo.module('org.custom', 'custom', '2.2')
+        def apiModule = ivyRepo.module('org.custom', 'custom-api', '2')
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'ivy-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            task apiJar(type: Jar) {
+                from sourceSets.main.output
+                baseName "root-api"
+                exclude "**/impl/**"
+            }
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        organisation "org.custom"
+                        module "custom"
+                        revision "2.2"
+                        from components.java
+                    }
+                    ivyApi(IvyPublication) {
+                        organisation "org.custom"
+                        module "custom-api"
+                        revision "2"
+                        configurations {
+                            runtime {}
+                            "default" {
+                                extend "runtime"
+                            }
+                        }
+                        artifact(apiJar) {
+                            conf "runtime"
+                        }
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        file('build/libs').assertHasDescendants("root-1.0.jar", "root-api-1.0.jar")
+
+        and:
+        module.assertPublishedAsJavaModule()
+        module.moduleDir.file('custom-2.2.jar').assertIsCopyOf(file('build/libs/root-1.0.jar'))
+
+        and:
+        apiModule.assertPublishedAsJavaModule()
+        apiModule.moduleDir.file('custom-api-2.jar').assertIsCopyOf(file('build/libs/root-api-1.0.jar'))
+
+        and:
+        resolveArtifacts(module) == ['custom-2.2.jar']
+        resolveArtifacts(apiModule) == ['custom-api-2.jar']
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy
index 7ae9c64..1a2c2cd 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy
@@ -31,9 +31,9 @@ class IvyPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpec
         projectPublishedUsingMavenPublishPlugin('java')
 
         expect:
-        consumePublicationWithPreviousVersion('')
+        consumePublicationWithPreviousVersion()
 
-        file('build/resolved').assertHasDescendants("published-${publishedVersion}.jar", 'commons-collections-3.0.jar')
+        file('build/resolved').assertHasDescendants("published-1.9.jar", 'test-project-1.2.jar')
     }
 
     def "ivy war publication generated by ivy-publish plugin can be consumed by previous versions of Gradle"() {
@@ -41,12 +41,14 @@ class IvyPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpec
         projectPublishedUsingMavenPublishPlugin('web')
 
         expect:
-        consumePublicationWithPreviousVersion('@war')
+        consumePublicationWithPreviousVersion()
 
-        file('build/resolved').assertHasDescendants("published-${publishedVersion}.war")
+        file('build/resolved').assertHasDescendants("published-1.9.war")
     }
 
     def projectPublishedUsingMavenPublishPlugin(def componentToPublish) {
+        repo.module("org.gradle", "test-project", "1.2").publish()
+
         settingsFile.text = "rootProject.name = 'published'"
 
         buildFile.text = """
@@ -54,13 +56,13 @@ apply plugin: 'war'
 apply plugin: 'ivy-publish'
 
 group = 'org.gradle.crossversion'
-version = '${publishedVersion}'
+version = '1.9'
 
 repositories {
-    mavenCentral()
+    ivy { url "${repo.uri}" }
 }
 dependencies {
-    compile "commons-collections:commons-collections:3.0"
+    compile "org.gradle:test-project:1.2"
 }
 publishing {
     repositories {
@@ -68,7 +70,7 @@ publishing {
     }
     publications {
         ivy(IvyPublication) {
-            from components['${componentToPublish}']
+            from components.${componentToPublish}
         }
     }
 }
@@ -77,7 +79,7 @@ publishing {
         version current withTasks 'publish' run()
     }
 
-    def consumePublicationWithPreviousVersion(def artifact) {
+    def consumePublicationWithPreviousVersion() {
         settingsFile.text = "rootProject.name = 'consumer'"
 
         def repositoryDefinition
@@ -106,12 +108,10 @@ configurations {
     lib
 }
 repositories {
-    mavenCentral()
-
     $repositoryDefinition
 }
 dependencies {
-    lib 'org.gradle.crossversion:published:${publishedVersion}${artifact}'
+    lib 'org.gradle.crossversion:published:1.9'
 }
 task retrieve(type: Sync) {
     into 'build/resolved'
@@ -119,10 +119,6 @@ task retrieve(type: Sync) {
 }
 """
 
-        version previous withDeprecationChecksDisabled() withTasks 'retrieve' run()
-    }
-
-    def getPublishedVersion() {
-        "1.9"
+        version previous requireOwnGradleUserHomeDir() withDeprecationChecksDisabled() withTasks 'retrieve' run()
     }
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomisationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomisationIntegTest.groovy
deleted file mode 100644
index dc59e1e..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomisationIntegTest.groovy
+++ /dev/null
@@ -1,147 +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.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.test.fixtures.ivy.IvyDescriptor
-
-class IvyPublishDescriptorCustomisationIntegTest 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}" }
-                }
-                publications {
-                    ivy(IvyPublication) {
-                        from components.java
-                    }
-                }
-            }
-        """
-    }
-
-    def "can customise descriptor xml during publication"() {
-        when:
-        succeeds 'publish'
-
-        then:
-        ":jar" in executedTasks
-
-        and:
-        module.ivy.revision == "2"
-
-        when:
-        buildFile << """
-            publishing {
-                publications {
-                    ivy {
-                        descriptor {
-                            withXml {
-                                asNode().info[0].appendNode('description', 'Customized descriptor')
-                            }
-                        }
-                    }
-                }
-            }
-        """
-        succeeds 'publish'
-
-
-        then:
-        ":jar" in skippedTasks
-
-        and:
-        module.ivy.description == "Customized descriptor"
-    }
-
-    def "can generate ivy.xml without publishing"() {
-        given:
-        def moduleName = module.module
-        buildFile << """
-            publishing {
-                generateIvyModuleDescriptor {
-                    destination = 'generated-ivy.xml'
-                }
-            }
-        """
-
-        when:
-        succeeds 'generateIvyModuleDescriptor'
-
-        then:
-        file('generated-ivy.xml').assertIsFile()
-        IvyDescriptor ivy = new IvyDescriptor(file('generated-ivy.xml'))
-        ivy.expectArtifact(moduleName).hasAttributes("jar", "jar", ["runtime"])
-        module.ivyFile.assertDoesNotExist()
-    }
-
-    def "produces sensible error when withXML fails"() {
-        when:
-        buildFile << """
-            publishing {
-                publications {
-                    ivy {
-                        descriptor.withXml {
-                            asNode().foo = "3"
-                        }
-                    }
-                }
-            }
-        """
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':generateIvyModuleDescriptor'")
-        failure.assertHasCause("Could not apply withXml() to Ivy module descriptor")
-        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
-    }
-
-    def "produces sensible error when withXML modifies publication coordinates"() {
-        when:
-        buildFile << """
-            publishing {
-                publications {
-                    ivy {
-                        descriptor.withXml {
-                            asNode().info[0]. at revision = "2.1"
-                        }
-                    }
-                }
-            }
-        """
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':publishIvyPublicationToIvyRepository'")
-        failure.assertHasCause("Failed to publish publication 'ivy' to repository 'ivy'")
-        failure.assertHasCause("Invalid publication 'ivy': supplied revision does not match ivy descriptor (cannot edit revision directly in the ivy descriptor file).")
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomizationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomizationIntegTest.groovy
new file mode 100644
index 0000000..53c8957
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomizationIntegTest.groovy
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.test.fixtures.ivy.IvyDescriptor
+
+class IvyPublishDescriptorCustomizationIntegTest 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}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+    }
+
+    def "can customize descriptor xml during publication"() {
+        when:
+        succeeds 'publish'
+
+        then:
+        ":jar" in executedTasks
+
+        and:
+        module.parsedIvy.revision == "2"
+
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor {
+                            status "custom-status"
+                            withXml {
+                                asNode().info[0].appendNode('description', 'Customized descriptor')
+                            }
+                        }
+                    }
+                }
+            }
+        """
+        succeeds 'publish'
+
+
+        then:
+        ":jar" in skippedTasks
+
+        and:
+        module.parsedIvy.description == "Customized descriptor"
+        module.parsedIvy.status == "custom-status"
+    }
+
+    def "can generate ivy.xml without publishing"() {
+        given:
+        def moduleName = module.module
+        buildFile << """
+            model {
+                tasks.generateDescriptorFileForIvyPublication {
+                    destination = 'generated-ivy.xml'
+                }
+            }
+        """
+
+        when:
+        succeeds 'generateDescriptorFileForIvyPublication'
+
+        then:
+        file('generated-ivy.xml').assertIsFile()
+        IvyDescriptor ivy = new IvyDescriptor(file('generated-ivy.xml'))
+        ivy.expectArtifact(moduleName).hasAttributes("jar", "jar", ["runtime"])
+        module.ivyFile.assertDoesNotExist()
+    }
+
+    def "produces sensible error when withXML fails"() {
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor.withXml {
+                            asNode().foo = "3"
+                        }
+                    }
+                }
+            }
+        """
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':generateDescriptorFileForIvyPublication'.")
+        failure.assertHasFileName("Build file '${buildFile}'")
+        failure.assertHasLineNumber(23)
+        failure.assertHasCause("Could not apply withXml() to Ivy module descriptor")
+        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
+    }
+
+    def "produces sensible error when withXML modifies publication coordinates"() {
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor.withXml {
+                            asNode().info[0]. at revision = "2.1"
+                        }
+                    }
+                }
+            }
+        """
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishIvyPublicationToIvyRepository'.")
+        failure.assertHasCause("Failed to publish publication 'ivy' to repository 'ivy'")
+        failure.assertHasCause("Invalid publication 'ivy': supplied revision does not match ivy descriptor (cannot edit revision directly in the ivy descriptor file).")
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy
index 4547a3f..0853ffc 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy
@@ -68,7 +68,7 @@ class IvyPublishEarIntegTest extends AbstractIvyPublishIntegTest {
         ivyModule.assertPublishedAsEarModule()
 
         and: "correct configurations and dependencies declared"
-        with (ivyModule.ivy) {
+        with (ivyModule.parsedIvy) {
             configurations.keySet() == ["default", "master"] as Set
             configurations.default.extend == ["master"] as Set
             configurations.master.extend == null
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpIntegTest.groovy
index 054fd5c..4d2bc24 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpIntegTest.groovy
@@ -20,10 +20,10 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
 import org.gradle.internal.jvm.Jvm
 import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.ivy.IvyFileModule
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
-import org.gradle.util.TextUtil
 import org.hamcrest.Matchers
 import org.junit.Rule
 import org.mortbay.jetty.HttpStatus
@@ -38,16 +38,15 @@ credentials {
     password 'bad'
 }
 '''
-    @Rule
-    public final HttpServer server = new HttpServer()
-
     @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
+    @Rule HttpServer server = new HttpServer()
 
-    private IvyFileModule module
+    private IvyHttpModule module
+    private IvyHttpRepository ivyHttpRepo
 
     def setup() {
-        module = ivyRepo.module("org.gradle", "publish", "2")
-        module.moduleDir.mkdirs()
+        ivyHttpRepo = new IvyHttpRepository(server, ivyRepo)
+        module = ivyHttpRepo.module("org.gradle", "publish", "2")
         server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
     }
 
@@ -64,7 +63,7 @@ credentials {
 
             publishing {
                 repositories {
-                    ivy { url "http://localhost:${server.port}" }
+                    ivy { url "${ivyHttpRepo.uri}" }
                 }
                 publications {
                     ivy(IvyPublication) {
@@ -74,25 +73,24 @@ credentials {
             }
         """
 
-        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:
+        module.jar.expectPut()
+        module.jar.sha1.expectPut()
+        module.ivy.expectPut(HttpStatus.ORDINAL_201_Created)
+        module.ivy.sha1.expectPut(HttpStatus.ORDINAL_201_Created)
+
+        when:
         succeeds 'publish'
 
         then:
-        module.ivyFile.assertIsFile()
-        module.assertChecksumPublishedFor(module.ivyFile)
-
+        module.assertIvyAndJarFilePublished()
         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")
+        progressLogging.uploadProgressLogged(module.ivy.uri)
+        progressLogging.uploadProgressLogged(module.jar.uri)
     }
 
-
     @Unroll
     def "can publish to authenticated repository using #authScheme auth"() {
         given:
@@ -113,7 +111,7 @@ credentials {
                             username 'testuser'
                             password 'password'
                         }
-                        url "http://localhost:${server.port}"
+                        url "${ivyHttpRepo.uri}"
                     }
                 }
                 publications {
@@ -124,23 +122,23 @@ credentials {
             }
         """
 
-        when:
+        and:
         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')
+        module.jar.expectPut('testuser', 'password')
+        module.jar.sha1.expectPut('testuser', 'password')
+        module.ivy.expectPut('testuser', 'password')
+        module.ivy.sha1.expectPut('testuser', 'password')
 
-        then:
-        succeeds 'publish'
+        when:
+        run 'publish'
 
-        and:
-        module.ivyFile.assertIsFile()
-        module.assertChecksumPublishedFor(module.ivyFile)
+        then:
+        module.assertIvyAndJarFilePublished()
         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")
+        progressLogging.uploadProgressLogged(module.ivy.uri)
+        progressLogging.uploadProgressLogged(module.jar.uri)
 
         where:
         authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
@@ -151,7 +149,7 @@ credentials {
         given:
         server.start()
 
-        when:
+        and:
         settingsFile << 'rootProject.name = "publish"'
         buildFile << """
             apply plugin: 'java'
@@ -162,7 +160,7 @@ credentials {
                 repositories {
                     ivy {
                         $creds
-                        url "http://localhost:${server.port}"
+                        url "${ivyHttpRepo.uri}"
                     }
                 }
                 publications {
@@ -175,12 +173,12 @@ credentials {
 
         and:
         server.authenticationScheme = authScheme
-        server.allowPut('/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
+        server.allowPut('/repo/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
 
-        then:
+        when:
         fails 'publish'
 
-        and:
+        then:
         failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
         failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
         failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
@@ -206,7 +204,7 @@ credentials {
             publishing {
                 repositories {
                     ivy {
-                        url "${repositoryUrl}"
+                        url "${ivyHttpRepo.uri}"
                     }
                 }
                 publications {
@@ -217,13 +215,13 @@ credentials {
             }
         """
 
-        when:
+        and:
         server.addBroken("/")
 
-        then:
+        when:
         fails 'publish'
 
-        and:
+        then:
         failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
         failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
         failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
@@ -254,9 +252,9 @@ credentials {
             publishing {
                 repositories {
                     ivy {
-                        artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
+                        artifactPattern "${ivyHttpRepo.artifactPattern}"
                         artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
-                        ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
+                        ivyPattern "${ivyHttpRepo.ivyPattern}"
                         ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
                     }
                 }
@@ -268,22 +266,24 @@ credentials {
             }
         """
 
+        and:
+        module.jar.expectPut()
+        module.jar.sha1.expectPut()
+        module.ivy.expectPut()
+        module.ivy.sha1.expectPut()
+
         when:
-        expectUpload('/primary/publish/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
-        expectUpload('/primary-ivy/publish/ivy-2.xml', module, module.ivyFile)
+        run 'publish'
 
         then:
-        succeeds 'publish'
-
-        and:
-        module.ivyFile.assertIsFile()
+        module.assertIvyAndJarFilePublished()
         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)
+        def toolsJar = Jvm.current().toolsJar
 
         settingsFile << 'rootProject.name = "publish"'
         buildFile << """
@@ -299,15 +299,15 @@ credentials {
                             username 'testuser'
                             password 'password'
                         }
-                        url "http://localhost:${server.port}"
+                        url "${ivyHttpRepo.uri}"
                     }
                 }
                 publications {
                     ivy(IvyPublication) {
                         configurations {
                             runtime {
-                                artifact('$toolsJar') {
-                                    name 'tools'
+                                artifact('${toolsJar.toURI()}') {
+                                    name 'publish'
                                 }
                             }
                         }
@@ -316,18 +316,18 @@ credentials {
             }
         """
 
+        and:
+        module.jar.expectPut('testuser', 'password')
+        module.jar.sha1.expectPut('testuser', 'password')
+        module.ivy.expectPut('testuser', 'password')
+        module.ivy.sha1.expectPut('testuser', 'password')
+
         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')
+        run 'publish'
 
         then:
-        succeeds 'publish'
-
-        and:
         module.ivyFile.assertIsFile()
-        uploadedToolsJar.assertIsCopyOf(new TestFile(toolsJar));
-
+        module.jarFile.assertIsCopyOf(new TestFile(toolsJar))
     }
 
     public void "does not upload meta-data file if artifact upload fails"() {
@@ -345,7 +345,7 @@ credentials {
             publishing {
                 repositories {
                     ivy {
-                        url "http://localhost:${server.port}"
+                        url "${ivyHttpRepo.uri}"
                     }
                 }
                 publications {
@@ -355,24 +355,15 @@ credentials {
                 }
             }
         """
-        when:
-        server.expectPut("/org.gradle/publish/2/publish-2.jar", module.jarFile, HttpStatus.ORDINAL_500_Internal_Server_Error)
 
-        then:
+        and:
+        module.jar.expectPut(HttpStatus.ORDINAL_500_Internal_Server_Error)
+
+        when:
         fails ':publish'
 
-        and:
+        then:
         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))
-    }
-}
+}
\ No newline at end of file
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy
index 4a7f501..edafd59 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy
@@ -15,7 +15,7 @@
  */
 
 package org.gradle.api.publish.ivy
-import org.gradle.test.fixtures.publish.Identifier
+import org.gradle.test.fixtures.encoding.Identifier
 import spock.lang.Unroll
 
 class IvyPublishIdentifierValidationIntegTest extends AbstractIvyPublishIntegTest {
@@ -59,7 +59,7 @@ class IvyPublishIdentifierValidationIntegTest extends AbstractIvyPublishIntegTes
 
         then:
         module.assertPublished()
-        module.ivy.description == description.toString()
+        module.parsedIvy.description == description.toString()
 
         and:
         resolveArtifacts(module) == [moduleName + '-' + version + '.jar']
@@ -96,6 +96,11 @@ class IvyPublishIdentifierValidationIntegTest extends AbstractIvyPublishIntegTes
             group = '${sq(organisation)}'
             version = '${sq(version)}'
 
+            println "test build vm: file.encoding=" + System.getProperty("file.encoding")
+            println "test build vm: LANG=" + System.getenv("LANG")
+            println "test build vm: LC_ALL=" + System.getenv("LC_ALL")
+            println "test build vm: LC_CTYPE=" + System.getenv("LC_CTYPE")
+
             publishing {
                 repositories {
                     ivy { url "${ivyRepo.uri}" }
@@ -147,7 +152,7 @@ class IvyPublishIdentifierValidationIntegTest extends AbstractIvyPublishIntegTes
         fails 'publish'
 
         then:
-        failure.assertHasDescription "Execution failed for task ':publishIvyPublicationToIvyRepository'"
+        failure.assertHasDescription "Execution failed for task ':publishIvyPublicationToIvyRepository'."
         failure.assertHasCause "Failed to publish publication 'ivy' to repository 'ivy'"
         failure.assertHasCause "Invalid publication 'ivy': organisation cannot be empty."
     }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy
index b507d5b..203a1e2 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy
@@ -38,15 +38,14 @@ class IvyPublishJavaIntegTest extends AbstractIvyPublishIntegTest {
         then:
         ivyModule.assertPublishedAsJavaModule()
 
-        with (ivyModule.ivy) {
+        with (ivyModule.parsedIvy) {
             configurations.keySet() == ["default", "runtime"] as Set
             configurations["default"].extend == ["runtime"] as Set
             configurations["runtime"].extend == null
 
             expectArtifact("publishTest").hasAttributes("jar", "jar", ["runtime"])
         }
-        // TODO:DAZ For some reason this doesn't work inside the with block. Investigate.
-        ivyModule.ivy.assertDependsOn("commons-collections:commons-collections:3.2.1 at runtime", "commons-io:commons-io:1.4 at runtime")
+        ivyModule.parsedIvy.assertDependsOn("commons-collections:commons-collections:3.2.1 at runtime", "commons-io:commons-io:1.4 at runtime")
 
         and:
         resolveArtifacts(ivyModule) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9.jar"]
@@ -111,7 +110,7 @@ class IvyPublishJavaIntegTest extends AbstractIvyPublishIntegTest {
         ivyModule.assertPublished()
         ivyModule.assertArtifactsPublished("publishTest-1.9.jar", "publishTest-1.9-source.jar", "ivy-1.9.xml")
 
-        ivyModule.ivy.expectArtifact("publishTest", "jar", "source").hasAttributes("jar", "sources", ["runtime"], "source")
+        ivyModule.parsedIvy.expectArtifact("publishTest", "jar", "source").hasAttributes("jar", "sources", ["runtime"], "source")
 
         and:
         resolveArtifacts(ivyModule) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9-source.jar", "publishTest-1.9.jar"]
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy
index 0bb29d9..fdc2cd0 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy
@@ -29,18 +29,74 @@ class IvyPublishMultiProjectIntegTest extends AbstractIvyPublishIntegTest {
 
         then:
         project1.assertPublishedAsJavaModule()
-        project1.ivy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "org.gradle.test:project3:3.0 at runtime")
+        project1.parsedIvy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "org.gradle.test:project3:3.0 at runtime")
 
         project2.assertPublishedAsJavaModule()
-        project2.ivy.assertDependsOn("org.gradle.test:project3:3.0 at runtime")
+        project2.parsedIvy.assertDependsOn("org.gradle.test:project3:3.0 at runtime")
 
         project3.assertPublishedAsJavaModule()
-        project3.ivy.dependencies.isEmpty()
+        project3.parsedIvy.dependencies.isEmpty()
 
         and:
         resolveArtifacts(project1) == ['project1-1.0.jar', 'project2-2.0.jar', 'project3-3.0.jar']
     }
 
+    def "project dependencies reference publication identity of dependent project"() {
+        def project3 = ivyRepo.module("changed.org", "changed-module", "changed")
+
+        createBuildScripts("""
+project(":project3") {
+    publishing {
+        publications.ivy {
+            organisation "changed.org"
+            module "changed-module"
+            revision "changed"
+        }
+    }
+}
+""")
+
+        when:
+        run "publish"
+
+        then:
+        project1.assertPublishedAsJavaModule()
+        project1.parsedIvy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "changed.org:changed-module:changed at runtime")
+
+        project2.assertPublishedAsJavaModule()
+        project2.parsedIvy.assertDependsOn("changed.org:changed-module:changed at runtime")
+
+        project3.assertPublishedAsJavaModule()
+        project3.parsedIvy.dependencies.isEmpty()
+
+        and:
+        resolveArtifacts(project1) == ['changed-module-changed.jar', 'project1-1.0.jar', 'project2-2.0.jar']
+    }
+
+    def "reports failure when project dependency references a project with multiple publications"() {
+        createBuildScripts("""
+project(":project3") {
+    publishing {
+        publications {
+            extraIvy(IvyPublication) {
+                from components.java
+                organisation "extra.org"
+                module "extra-module"
+                revision "extra"
+            }
+        }
+    }
+}
+""")
+
+        when:
+        fails "publish"
+
+        then:
+        failure.assertHasDescription "A problem occurred configuring project ':project1'."
+        failure.assertHasCause "Publishing is not yet able to resolve a dependency on a project with multiple different publications."
+    }
+
     def "ivy-publish plugin does not take archivesBaseName into account"() {
         createBuildScripts("""
 project(":project2") {
@@ -53,14 +109,14 @@ project(":project2") {
 
         then:
         project1.assertPublishedAsJavaModule()
-        project1.ivy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "org.gradle.test:project3:3.0 at runtime")
+        project1.parsedIvy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "org.gradle.test:project3:3.0 at runtime")
 
         // published with the correct coordinates, even though artifact has different name
         project2.assertPublishedAsJavaModule()
-        project2.ivy.assertDependsOn("org.gradle.test:project3:3.0 at runtime")
+        project2.parsedIvy.assertDependsOn("org.gradle.test:project3:3.0 at runtime")
 
         project3.assertPublishedAsJavaModule()
-        project3.ivy.dependencies.isEmpty()
+        project3.parsedIvy.dependencies.isEmpty()
     }
 
     def "ivy-publish plugin uses target project name for project dependency when target project does not have ivy-publish plugin applied"() {
@@ -105,7 +161,7 @@ project(":project2") {
 
         then:
         project1.assertPublishedAsJavaModule()
-        project1.ivy.assertDependsOn("org.gradle.test:project2:1.0 at runtime")
+        project1.parsedIvy.assertDependsOn("org.gradle.test:project2:1.0 at runtime")
     }
 
 
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy
index f587aab..399d16a 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy
@@ -81,8 +81,8 @@ class IvyPublishMultipleRepositoriesIntegTest extends AbstractIntegrationSpec {
         repo2Module.jarFile.exists()
 
         and: // Modification applied to both
-        repo1Module.ivy.description == "test module"
-        repo2Module.ivy.description == "test module"
+        repo1Module.parsedIvy.description == "test module"
+        repo2Module.parsedIvy.description == "test module"
     }
 
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy
index 2994619..fd6e503 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy
@@ -64,7 +64,7 @@ class IvyPublishWarIntegTest extends AbstractIvyPublishIntegTest {
         ivyModule.assertPublishedAsWebModule()
 
         and: "correct configurations and depdendencies declared"
-        with (ivyModule.ivy) {
+        with (ivyModule.parsedIvy) {
             configurations.keySet() == ["default", "master"] as Set
             configurations.default.extend == ["master"] as Set
             configurations.master.extend == null
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
index 45ace6b..93b39a2 100644
--- 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
@@ -16,6 +16,7 @@
 package org.gradle.api.publish.ivy
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.ivy.IvyFileModule
 import org.gradle.util.TextUtil
 import org.junit.Rule
 
@@ -23,6 +24,7 @@ public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationSpec {
     @Rule public final Sample quickstart = new Sample(temporaryFolder, "ivy-publish/quickstart")
     @Rule public final Sample javaProject = new Sample(temporaryFolder, "ivy-publish/java-multi-project")
     @Rule public final Sample customization = new Sample(temporaryFolder, "ivy-publish/descriptor-customization")
+    @Rule public final Sample multiPublish = new Sample(temporaryFolder, "ivy-publish/multiple-publications")
 
     def "quickstart sample"() {
         given:
@@ -55,26 +57,22 @@ public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationSpec {
         project1module.assertPublished()
         project1module.assertArtifactsPublished("project1-1.0.jar", "project1-1.0-source.jar", "ivy-1.0.xml")
 
-        project1module.ivy.configurations.keySet() == ['default', 'runtime'] as Set
-        project1module.ivy.description == "The first project"
-        project1module.ivy.assertDependsOn("junit:junit:4.11 at runtime", "org.gradle.sample:project2:1.0 at runtime")
+        project1module.parsedIvy.configurations.keySet() == ['default', 'runtime'] as Set
+        project1module.parsedIvy.description == "The first project"
+        project1module.parsedIvy.assertDependsOn("junit:junit:4.11 at runtime", "org.gradle.sample:project2:1.0 at runtime")
 
         and:
         project2module.assertPublished()
         project2module.assertArtifactsPublished("project2-1.0.jar", "project2-1.0-source.jar", "ivy-1.0.xml")
 
-        project2module.ivy.configurations.keySet() == ['default', 'runtime'] as Set
-        project2module.ivy.description == "The second project"
-        project2module.ivy.assertDependsOn('commons-collections:commons-collections:3.1 at runtime')
+        project2module.parsedIvy.configurations.keySet() == ['default', 'runtime'] as Set
+        project2module.parsedIvy.description == "The second project"
+        project2module.parsedIvy.assertDependsOn('commons-collections:commons-collections:3.1 at runtime')
 
         def actualIvyXmlText = project1module.ivyFile.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
         actualIvyXmlText == getExpectedIvyOutput(javaProject.dir.file("output-ivy.xml"))
     }
 
-    String getExpectedIvyOutput(def outputFile) {
-        outputFile.readLines()[1..-1].join(TextUtil.getPlatformLineSeparator()).trim()
-    }
-
     def "descriptor-customization sample"() {
         given:
         sample customization
@@ -88,6 +86,42 @@ public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         module.assertPublished()
-        module.ivy.description == "A demonstration of ivy descriptor customization"
+        module.parsedIvy.description == "A demonstration of ivy descriptor customization"
+    }
+
+    def "multiple-publications sample"() {
+        given:
+        sample multiPublish
+
+        and:
+        def fileRepo = ivy(multiPublish.dir.file("build/repo"))
+        def project1sample = fileRepo.module("org.gradle.sample", "project1-sample", "1.1")
+        def project2api = fileRepo.module("org.gradle.sample", "project2-api", "2")
+        def project2impl = fileRepo.module("org.gradle.sample.impl", "project2-impl", "2.3")
+
+        when:
+        succeeds "publish"
+
+        then:
+        project1sample.assertPublishedAsJavaModule()
+        verifyIvyFile(project1sample, "output/project1.ivy.xml")
+
+        and:
+        project2api.assertPublishedAsJavaModule()
+        verifyIvyFile(project2api, "output/project2-api.ivy.xml")
+
+        and:
+        project2impl.assertPublishedAsJavaModule()
+        verifyIvyFile(project2impl, "output/project2-impl.ivy.xml")
+    }
+
+    private void verifyIvyFile(IvyFileModule project1sample, String outputFileName) {
+        def actualIvyXmlText = project1sample.ivyFile.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
+        assert actualIvyXmlText == getExpectedIvyOutput(multiPublish.dir.file(outputFileName))
+    }
+
+    String getExpectedIvyOutput(File outputFile) {
+        assert outputFile.file
+        outputFile.readLines()[1..-1].join(TextUtil.getPlatformLineSeparator()).trim()
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
rename to subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
new file mode 100644
index 0000000..d77bb23
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
+import org.gradle.util.Jvm
+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 ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
+    @Rule HttpServer server = new HttpServer()
+
+    private IvyHttpModule module
+    private IvyHttpRepository ivyHttpRepo
+
+    def setup() {
+        ivyHttpRepo = new IvyHttpRepository(server, ivyRepo)
+        module = ivyHttpRepo.module("org.gradle", "publish", "2")
+        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 {
+            url "${ivyHttpRepo.uri}"
+        }
+    }
+}
+"""
+        and:
+        module.jar.expectPut()
+        module.jar.sha1.expectPut()
+        module.ivy.expectPut(HttpStatus.ORDINAL_201_Created)
+        module.ivy.sha1.expectPut(HttpStatus.ORDINAL_201_Created)
+
+        when:
+        run 'uploadArchives'
+
+        then:
+        module.assertIvyAndJarFilePublished()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+
+        and:
+        progressLogging.uploadProgressLogged(module.ivy.uri)
+        progressLogging.uploadProgressLogged(module.jar.uri)
+    }
+
+    @Unroll
+    def "can publish to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            credentials {
+                username 'testuser'
+                password 'password'
+            }
+            url "${ivyHttpRepo.uri}"
+        }
+    }
+}
+"""
+
+        and:
+        server.authenticationScheme = authScheme
+        module.jar.expectPut('testuser', 'password')
+        module.jar.sha1.expectPut('testuser', 'password')
+        module.ivy.expectPut('testuser', 'password')
+        module.ivy.sha1.expectPut('testuser', 'password')
+
+        when:
+        run 'uploadArchives'
+
+        then:
+        module.assertIvyAndJarFilePublished()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+
+        and:
+        progressLogging.uploadProgressLogged(module.ivy.uri)
+        progressLogging.uploadProgressLogged(module.jar.uri)
+
+        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()
+
+        and:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            $creds
+            url "${ivyHttpRepo.uri}"
+        }
+    }
+}
+"""
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowPut('/repo/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
+
+        when:
+        fails 'uploadArchives'
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
+        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'
+uploadArchives {
+    repositories {
+        ivy {
+            url "${ivyHttpRepo.uri}"
+        }
+    }
+}
+"""
+
+        when:
+        server.addBroken("/")
+
+        then:
+        fails 'uploadArchives'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
+        failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
+
+        when:
+        server.stop()
+
+        then:
+        fails 'uploadArchives'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
+        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'
+    version = '2'
+    group = 'org.gradle'
+    uploadArchives {
+        repositories {
+            ivy {
+                artifactPattern "${ivyHttpRepo.artifactPattern}"
+                artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
+                ivyPattern "${ivyHttpRepo.ivyPattern}"
+                ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
+            }
+        }
+    }
+    """
+
+        and:
+        module.jar.expectPut()
+        module.jar.sha1.expectPut()
+        module.ivy.expectPut()
+        module.ivy.sha1.expectPut()
+
+        when:
+        run 'uploadArchives'
+
+        then:
+        module.assertIvyAndJarFilePublished()
+        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 = Jvm.current().toolsJar
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'base'
+version = '2'
+group = 'org.gradle'
+
+configurations {
+    tools
+}
+artifacts {
+    tools(file('${toolsJar.toURI()}')) {
+        name 'publish'
+    }
+}
+
+uploadTools {
+    repositories {
+        ivy {
+            credentials {
+                username 'testuser'
+                password 'password'
+            }
+            url "${ivyHttpRepo.uri}"
+        }
+    }
+}
+"""
+
+        and:
+        module.jar.expectPut('testuser', 'password')
+        module.jar.sha1.expectPut('testuser', 'password')
+        module.ivy.expectPut('testuser', 'password')
+        module.ivy.sha1.expectPut('testuser', 'password')
+
+        when:
+        run 'uploadTools'
+
+        then:
+        module.assertIvyAndJarFilePublished()
+        module.jarFile.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'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "${ivyHttpRepo.uri}"
+        }
+    }
+}
+"""
+        and:
+        module.jar.expectPut(HttpStatus.ORDINAL_500_Internal_Server_Error)
+
+        when:
+        fails 'uploadArchives'
+
+        then:
+        module.jarFile.assertExists()
+        module.ivyFile.assertDoesNotExist()
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..fbcf45c
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.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'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        ivy {
+            url '${ivyRepo.uri}'
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.jar")
+
+        with (ivyModule.parsedIvy) {
+            dependencies.size() == 2
+            dependencies["commons-collections:commons-collections:3.2.1"].hasConf("compile->default")
+            dependencies["commons-io:commons-io:1.4"].hasConf("runtime->default")
+        }
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
new file mode 100644
index 0000000..9f1335e
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.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.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.spockframework.util.TextUtil
+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'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "${ivyRepo.uri}"
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        module.assertIvyAndJarFilePublished()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+    }
+
+    @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.
+        //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/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..99ff107
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.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.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+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"
+
+group = "org.gradle.test"
+version = 1.9
+
+configurations { publish }
+
+task jar1(type: Jar) {
+    baseName = "jar1"
+    from "file1"
+}
+
+task jar2(type: Jar) {
+    baseName = "jar2"
+    from "file2"
+}
+
+artifacts {
+    publish jar1, jar2
+}
+
+uploadPublish {
+    repositories {
+        ivy {
+            url "${ivyRepo.uri}"
+        }
+    }
+}
+        """
+
+        when:
+        run "uploadPublish"
+
+        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.parsedIvy
+        ivyDescriptor.expectArtifact("jar1").conf == ["archives", "publish"]
+        ivyDescriptor.expectArtifact("jar2").conf == ["publish"]
+    }
+
+    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 {
+        ivy {
+            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.parsedIvy
+        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/integtests/publish/ivy/IvyUrlResolverPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyUrlResolverPublishIntegrationTest.groovy
new file mode 100644
index 0000000..f162e41
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyUrlResolverPublishIntegrationTest.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.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+public class IvyUrlResolverPublishIntegrationTest extends AbstractIntegrationSpec {
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
+    @Rule HttpServer server = new HttpServer()
+
+    private IvyHttpModule module
+    private IvyHttpRepository ivyHttpRepo
+
+    def setup() {
+        ivyHttpRepo = new IvyHttpRepository(server, ivyRepo)
+        module = ivyHttpRepo.module("org.gradle", "publish", "2")
+    }
+
+    public void canPublishToAnHttpRepository() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+
+uploadArchives {
+    repositories {
+        add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+            addIvyPattern("${ivyHttpRepo.ivyPattern}")
+            addArtifactPattern("${ivyHttpRepo.artifactPattern}")
+        }
+    }
+}
+"""
+
+        and:
+        module.jar.expectPut()
+        module.ivy.expectPut()
+
+        and:
+        executer.withDeprecationChecksDisabled()
+
+        when:
+        run 'uploadArchives'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+
+        and:
+        progressLogging.uploadProgressLogged(module.jar.uri)
+        progressLogging.uploadProgressLogged(module.ivy.uri)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
rename to subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
new file mode 100644
index 0000000..89ed11a
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.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.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+import org.junit.Test
+
+public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationTest {
+
+    @Rule
+    public final Sample sample = new Sample(testDirectoryProvider, "ivypublish")
+
+    @Test
+    public void testPublish() {
+        // the actual testing is done in the build script.
+        File projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks("uploadArchives").run()
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java
index 170d649..f56860b 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java
@@ -27,7 +27,7 @@ import org.gradle.api.Incubating;
  * <pre autoTested="true">
  * apply plugin: 'ivy-publish'
  *
- * def publication = publishing.publications.add("my-pub", IvyPublication)
+ * def publication = publishing.publications.create("my-pub", IvyPublication)
  * def artifacts = publication.artifacts
  *
  * artifacts.matching({
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java
index af5db87..245b023 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java
@@ -28,7 +28,7 @@ import org.gradle.api.NamedDomainObjectContainer;
  * <pre autoTested="true">
  * apply plugin: 'ivy-publish'
  *
- * def publication = publishing.publications.add("my-pub", IvyPublication)
+ * def publication = publishing.publications.create("my-pub", IvyPublication)
  * def configurations = publication.configurations
  *
  * configurations.create("extended", { extend "default"})
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java
index cfa641c..9275370 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java
@@ -17,7 +17,7 @@
 package org.gradle.api.publish.ivy;
 
 import org.gradle.api.Incubating;
-import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * A module dependency declared in an ivy dependency descriptor published as part of an {@link IvyPublication}.
@@ -27,6 +27,21 @@ import org.gradle.api.internal.HasInternalProtocol;
 public interface IvyDependency {
 
     /**
+     * The organisation value for this dependency.
+     */
+    String getOrganisation();
+
+    /**
+     * The module value for this dependency.
+     */
+    String getModule();
+
+    /**
+     * The revision value for this dependency.
+     */
+    String getRevision();
+
+    /**
      * The configuration mapping string that will be output for this dependency.
      * A null value indicates that no "conf" attribute will be written for this dependency.
      *
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
index f7022fa..1203950 100644
--- 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
@@ -19,7 +19,7 @@ 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 org.gradle.internal.HasInternalProtocol;
 
 /**
  * The descriptor of any Ivy publication.
@@ -69,4 +69,14 @@ public interface IvyModuleDescriptor {
      */
     void withXml(Action<? super XmlProvider> action);
 
+    /**
+     * Returns the status for this publication.
+     */
+    String getStatus();
+
+    /**
+     * Sets the status for this publication.
+     */
+    void setStatus(String status);
+
 }
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
index b517987..63ec200 100644
--- 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
@@ -19,7 +19,7 @@ package org.gradle.api.publish.ivy;
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
 import org.gradle.api.component.SoftwareComponent;
-import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.internal.HasInternalProtocol;
 import org.gradle.api.publish.Publication;
 
 /**
@@ -29,7 +29,9 @@ import org.gradle.api.publish.Publication;
  * <pre>
  * publishing {
  *   publications {
- *     myPublicationName(IvyPublication)
+ *     myPublicationName(IvyPublication) {
+ *       // Configure the publication here
+ *     }
  *   }
  * }
  * </pre>
@@ -148,7 +150,7 @@ public interface IvyPublication extends Publication {
      *   publications {
      *     ivy(IvyPublication) {
      *       configurations {
-     *           testCompile
+     *           testCompile {}
      *           testRuntime {
      *               extend "testCompile"
      *           }
@@ -189,7 +191,7 @@ public interface IvyPublication extends Publication {
      *   classifier "source"
      * }
      *
-     * task genDocs << {
+     * task genDocs << {
      *     // Generate 'my-docs-file.htm'
      * }
      *
@@ -222,7 +224,7 @@ public interface IvyPublication extends Publication {
      *   classifier "source"
      * }
 
-     * task genDocs << {
+     * task genDocs << {
      *     // Generate 'my-docs-file.htm'
      * }
      *
@@ -251,7 +253,10 @@ public interface IvyPublication extends Publication {
     IvyArtifact artifact(Object source, Action<? super IvyArtifact> config);
 
     /**
-     * Clears any previously added artifacts from {@link #getArtifacts} and creates artifacts from the specified sources.
+     * The complete set of artifacts for this publication.
+     *
+     * <p>
+     * Setting this property will clear any previously added artifacts and create artifacts from the specified sources.
      * Each supplied source is interpreted as per {@link #artifact(Object)}.
      *
      * For example, to exclude the dependencies declared by a component and instead use a custom set of artifacts:
@@ -273,14 +278,45 @@ public interface IvyPublication extends Publication {
      * }
      * </pre>
      *
+     * @return the artifacts.
+     */
+    IvyArtifactSet getArtifacts();
+
+    /**
+     * Sets the artifacts for this publication. Each supplied value is interpreted as per {@link #artifact(Object)}.
+     *
      * @param sources The set of artifacts for this publication.
      */
     void setArtifacts(Iterable<?> sources);
 
     /**
-     * Returns the complete set of artifacts for this publication.
-     * @return the artifacts.
+     * Returns the organisation for this publication.
      */
-    IvyArtifactSet getArtifacts();
+    String getOrganisation();
+
+    /**
+     * Sets the organisation for this publication.
+     */
+    void setOrganisation(String organisation);
+
+    /**
+     * Returns the module for this publication.
+     */
+    String getModule();
+
+    /**
+     * Sets the module for this publication.
+     */
+    void setModule(String module);
+
+    /**
+     * Returns the revision for this publication.
+     */
+    String getRevision();
+
+    /**
+     * Sets the revision for this publication.
+     */
+    void setRevision(String revision);
 
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationTasksModelRule.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationTasksModelRule.java
new file mode 100644
index 0000000..78314bb
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationTasksModelRule.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Project;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor;
+import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository;
+import org.gradle.api.publish.plugins.PublishingPlugin;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.model.ModelRule;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class IvyPublicationTasksModelRule extends ModelRule {
+    private final Project project;
+
+    public IvyPublicationTasksModelRule(Project project) {
+        this.project = project;
+    }
+
+    public void createTasks(TaskContainer tasks, PublishingExtension publishingExtension) {
+        PublicationContainer publications = publishingExtension.getPublications();
+        RepositoryHandler repositories = publishingExtension.getRepositories();
+
+        for (final IvyPublicationInternal publication : publications.withType(IvyPublicationInternal.class)) {
+
+            final String publicationName = publication.getName();
+            final String descriptorTaskName = String.format("generateDescriptorFileFor%sPublication", capitalize(publicationName));
+
+            GenerateIvyDescriptor descriptorTask = tasks.create(descriptorTaskName, GenerateIvyDescriptor.class);
+            descriptorTask.setDescription(String.format("Generates the Ivy Module Descriptor XML file for publication '%s'.", publication.getName()));
+            descriptorTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP);
+            descriptorTask.setDescriptor(publication.getDescriptor());
+
+            ConventionMapping descriptorTaskConventionMapping = new DslObject(descriptorTask).getConventionMapping();
+            descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
+                public Object call() throws Exception {
+                    return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
+                }
+            });
+
+            publication.setDescriptorFile(descriptorTask.getOutputs().getFiles());
+
+            for (IvyArtifactRepository repository : repositories.withType(IvyArtifactRepository.class)) {
+                final String repositoryName = repository.getName();
+                final String publishTaskName = String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
+
+                PublishToIvyRepository publishTask = tasks.create(publishTaskName, PublishToIvyRepository.class);
+                publishTask.setPublication(publication);
+                publishTask.setRepository(repository);
+                publishTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP);
+                publishTask.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'.", publicationName, repositoryName));
+
+                tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME).dependsOn(publishTask);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java
index 8b17fea..d9b1ef1 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java
@@ -93,4 +93,9 @@ public class DefaultIvyArtifact implements IvyArtifact {
     public TaskDependency getBuildDependencies() {
         return buildDependencies;
     }
+
+    @Override
+    public String toString() {
+        return String.format("%s %s:%s:%s:%s", getClass().getSimpleName(), getName(), getType(), getExtension(), getClassifier());
+    }
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java
index 9dd4c6b..2a74797 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java
@@ -20,7 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.DefaultDomainObjectSet;
 import org.gradle.api.internal.file.AbstractFileCollection;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
 import org.gradle.api.internal.tasks.TaskDependencyInternal;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
@@ -36,9 +36,9 @@ public class DefaultIvyArtifactSet extends DefaultDomainObjectSet<IvyArtifact> i
     private final String publicationName;
     private final TaskDependencyInternal builtBy = new ArtifactsTaskDependency();
     private final ArtifactsFileCollection files = new ArtifactsFileCollection();
-    private final NotationParser<IvyArtifact> ivyArtifactParser;
+    private final NotationParser<Object, IvyArtifact> ivyArtifactParser;
 
-    public DefaultIvyArtifactSet(String publicationName, NotationParser<IvyArtifact> ivyArtifactParser) {
+    public DefaultIvyArtifactSet(String publicationName, NotationParser<Object, IvyArtifact> ivyArtifactParser) {
         super(IvyArtifact.class);
         this.publicationName = publicationName;
         this.ivyArtifactParser = ivyArtifactParser;
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java
index 0f99230..b007992 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java
@@ -19,12 +19,13 @@ package org.gradle.api.publish.ivy.internal.artifact;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.publish.ivy.IvyArtifact;
 import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
 import org.gradle.api.tasks.bundling.AbstractArchiveTask;
@@ -33,26 +34,25 @@ import org.gradle.internal.reflect.Instantiator;
 
 import java.io.File;
 import java.util.Collection;
+import java.util.concurrent.Callable;
 
-public class IvyArtifactNotationParserFactory implements Factory<NotationParser<IvyArtifact>> {
+public class IvyArtifactNotationParserFactory implements Factory<NotationParser<Object, IvyArtifact>> {
     private final Instantiator instantiator;
     private final FileResolver fileResolver;
-    private final String defaultArtifactName;
+    private final IvyPublicationIdentity publicationIdentity;
 
     public IvyArtifactNotationParserFactory(Instantiator instantiator, FileResolver fileResolver, IvyPublicationIdentity publicationIdentity) {
         this.instantiator = instantiator;
         this.fileResolver = fileResolver;
-
-        // TODO - Need to handle name being modified after addition of artifacts, once we have that functionality.
-        this.defaultArtifactName = publicationIdentity.getModule();
+        this.publicationIdentity = publicationIdentity;
     }
 
-    public NotationParser<IvyArtifact> create() {
+    public NotationParser<Object, IvyArtifact> create() {
         FileNotationParser fileNotationParser = new FileNotationParser(fileResolver);
         ArchiveTaskNotationParser archiveTaskNotationParser = new ArchiveTaskNotationParser();
         PublishArtifactNotationParser publishArtifactNotationParser = new PublishArtifactNotationParser();
 
-        NotationParser<IvyArtifact> sourceNotationParser = new NotationParserBuilder<IvyArtifact>()
+        NotationParser<Object, IvyArtifact> sourceNotationParser = new NotationParserBuilder<IvyArtifact>()
                 .resultingType(IvyArtifact.class)
                 .parser(archiveTaskNotationParser)
                 .parser(publishArtifactNotationParser)
@@ -71,6 +71,20 @@ public class IvyArtifactNotationParserFactory implements Factory<NotationParser<
         return parserBuilder.toComposite();
     }
 
+    private DefaultIvyArtifact createDefaultIvyArtifact(File file, String extension, String type, String classifier) {
+        DefaultIvyArtifact ivyArtifact = instantiator.newInstance(
+                DefaultIvyArtifact.class,
+                file, null, extension, type, classifier
+        );
+        // TODO:DAZ Find a good way to handle this with lazy configuration
+        new DslObject(ivyArtifact).getConventionMapping().map("name", new Callable<String>() {
+            public String call() throws Exception {
+                return publicationIdentity.getModule();
+            }
+        });
+        return ivyArtifact;
+    }
+
     private class ArchiveTaskNotationParser extends TypedNotationParser<AbstractArchiveTask, IvyArtifact> {
         private ArchiveTaskNotationParser() {
             super(AbstractArchiveTask.class);
@@ -78,10 +92,8 @@ public class IvyArtifactNotationParserFactory implements Factory<NotationParser<
 
         @Override
         protected IvyArtifact parseType(AbstractArchiveTask archiveTask) {
-            DefaultIvyArtifact ivyArtifact = instantiator.newInstance(
-                    DefaultIvyArtifact.class,
-                    archiveTask.getArchivePath(), defaultArtifactName, archiveTask.getExtension(), archiveTask.getExtension(), archiveTask.getClassifier()
-            );
+            DefaultIvyArtifact ivyArtifact = createDefaultIvyArtifact(
+                    archiveTask.getArchivePath(), archiveTask.getExtension(), archiveTask.getExtension(), archiveTask.getClassifier());
             ivyArtifact.builtBy(archiveTask);
             return ivyArtifact;
         }
@@ -94,17 +106,15 @@ public class IvyArtifactNotationParserFactory implements Factory<NotationParser<
 
         @Override
         protected IvyArtifact parseType(PublishArtifact publishArtifact) {
-            DefaultIvyArtifact ivyArtifact = instantiator.newInstance(
-                    DefaultIvyArtifact.class,
-                    publishArtifact.getFile(), defaultArtifactName, publishArtifact.getExtension(), publishArtifact.getType(), publishArtifact.getClassifier()
-            );
+            DefaultIvyArtifact ivyArtifact = createDefaultIvyArtifact(
+                    publishArtifact.getFile(), publishArtifact.getExtension(), publishArtifact.getType(), publishArtifact.getClassifier());
             ivyArtifact.builtBy(publishArtifact.getBuildDependencies());
             return ivyArtifact;
         }
     }
 
-    private class FileNotationParser implements NotationParser<IvyArtifact> {
-        private final NotationParser<File> fileResolverNotationParser;
+    private class FileNotationParser implements NotationParser<Object, IvyArtifact> {
+        private final NotationParser<Object, File> fileResolverNotationParser;
 
         private FileNotationParser(FileResolver fileResolver) {
             this.fileResolverNotationParser = fileResolver.asNotationParser();
@@ -117,10 +127,7 @@ public class IvyArtifactNotationParserFactory implements Factory<NotationParser<
 
         protected IvyArtifact parseFile(File file) {
             String extension = StringUtils.substringAfterLast(file.getName(), ".");
-            return instantiator.newInstance(
-                    DefaultIvyArtifact.class,
-                    file, defaultArtifactName, extension, extension, null
-            );
+            return createDefaultIvyArtifact(file, extension, extension, null);
         }
 
         public void describe(Collection<String> candidateFormats) {
@@ -129,9 +136,9 @@ public class IvyArtifactNotationParserFactory implements Factory<NotationParser<
     }
 
     private class IvyArtifactMapNotationParser extends MapNotationParser<IvyArtifact> {
-        private final NotationParser<IvyArtifact> sourceNotationParser;
+        private final NotationParser<Object, IvyArtifact> sourceNotationParser;
 
-        private IvyArtifactMapNotationParser(NotationParser<IvyArtifact> sourceNotationParser) {
+        private IvyArtifactMapNotationParser(NotationParser<Object, IvyArtifact> sourceNotationParser) {
             this.sourceNotationParser = sourceNotationParser;
         }
 
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java
index 8fcfa5d..e354459 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java
@@ -16,22 +16,48 @@
 
 package org.gradle.api.publish.ivy.internal.dependency;
 
-import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 public class DefaultIvyDependency implements IvyDependencyInternal {
-    private ModuleDependency dependency;
-    private String confMapping;
+    private final String organisation;
+    private final String module;
+    private final String revision;
+    private final String confMapping;
+    private final List<DependencyArtifact> artifacts = new ArrayList<DependencyArtifact>();
 
-    public DefaultIvyDependency(ModuleDependency dependency, String confMapping) {
-        this.dependency = dependency;
+    public DefaultIvyDependency(String organisation, String module, String revision, String confMapping) {
+        this.organisation = organisation;
+        this.module = module;
+        this.revision = revision;
         this.confMapping = confMapping;
     }
 
-    public ModuleDependency getModuleDependency() {
-        return dependency;
+    public DefaultIvyDependency(String organisation, String module, String revision, String confMapping, Collection<DependencyArtifact> artifacts) {
+        this(organisation, module, revision, confMapping);
+        this.artifacts.addAll(artifacts);
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public String getRevision() {
+        return revision;
     }
 
     public String getConfMapping() {
         return confMapping;
     }
+
+    public Iterable<DependencyArtifact> getArtifacts() {
+        return artifacts;
+    }
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java
index b1a5a48..27af113 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java
@@ -16,9 +16,9 @@
 
 package org.gradle.api.publish.ivy.internal.dependency;
 
-import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.DependencyArtifact;
 import org.gradle.api.publish.ivy.IvyDependency;
 
 public interface IvyDependencyInternal extends IvyDependency {
-    ModuleDependency getModuleDependency();
+    Iterable<DependencyArtifact> getArtifacts();
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
deleted file mode 100644
index d5567c9..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.plugins;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.internal.ConventionMapping;
-import org.gradle.api.internal.plugins.DslObject;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal;
-import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor;
-
-import java.io.File;
-import java.util.concurrent.Callable;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class IvyPublicationDynamicDescriptorGenerationTaskCreator {
-
-    private final Project project;
-
-    public IvyPublicationDynamicDescriptorGenerationTaskCreator(Project project) {
-        this.project = project;
-    }
-
-    public void monitor(PublicationContainer publications) {
-        publications.withType(IvyPublicationInternal.class).all(new Action<IvyPublicationInternal>() {
-            public void execute(IvyPublicationInternal publication) {
-                create(publication);
-            }
-        });
-    }
-
-    private void create(final IvyPublicationInternal publication) {
-        String publicationName = publication.getName();
-
-        String descriptorTaskName = calculateDescriptorTaskName(publicationName);
-        GenerateIvyDescriptor descriptorTask = project.getTasks().add(descriptorTaskName, GenerateIvyDescriptor.class);
-        descriptorTask.setDescription(String.format("Generates the Ivy Module Descriptor XML file for publication '%s'.", publication.getName()));
-        descriptorTask.setDescriptor(publication.getDescriptor());
-
-        ConventionMapping descriptorTaskConventionMapping = new DslObject(descriptorTask).getConventionMapping();
-        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
-            public Object call() throws Exception {
-                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
-            }
-        });
-
-        publication.setDescriptorFile(descriptorTask.getOutputs().getFiles());
-    }
-
-    private String calculateDescriptorTaskName(String publicationName) {
-        return String.format(
-                "generate%sIvyModuleDescriptor",
-                publicationName.toLowerCase().equals("ivy") ? "" : capitalize(publicationName)
-        );
-    }
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreator.java
deleted file mode 100644
index 119fd8f..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreator.java
+++ /dev/null
@@ -1,90 +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.publish.ivy.internal.plugins;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectList;
-import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.ArtifactRepositoryContainer;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.ivy.internal.publication.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) {
-        final NamedDomainObjectSet<IvyPublicationInternal> ivyPublications = publications.withType(IvyPublicationInternal.class);
-        final NamedDomainObjectList<IvyArtifactRepository> ivyRepositories = repositories.withType(IvyArtifactRepository.class);
-
-        ivyPublications.all(new Action<IvyPublicationInternal>() {
-            public void execute(IvyPublicationInternal publication) {
-                for (IvyArtifactRepository repository : ivyRepositories) {
-                    maybeCreate(publication, repository);
-                }
-            }
-        });
-
-        ivyRepositories.all(new Action<IvyArtifactRepository>() {
-            public void execute(IvyArtifactRepository repository) {
-                for (IvyPublicationInternal publication : ivyPublications) {
-                    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(IvyPublicationInternal publication, IvyArtifactRepository repository) {
-        String publicationName = publication.getName();
-        String repositoryName = repository.getName();
-
-        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
-        if (tasks.findByName(publishTaskName) == null) {
-            PublishToIvyRepository publishTask = tasks.add(publishTaskName, PublishToIvyRepository.class);
-            publishTask.setPublication(publication);
-            publishTask.setRepository(repository);
-            publishTask.setGroup("publishing");
-            publishTask.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'.", publicationName, repositoryName));
-
-            publishLifecycleTask.dependsOn(publishTask);
-        }
-    }
-
-    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/internal/publication/DefaultIvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyModuleDescriptor.java
index 3b736eb..60da380 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyModuleDescriptor.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyModuleDescriptor.java
@@ -18,6 +18,7 @@ package org.gradle.api.publish.ivy.internal.publication;
 
 import org.gradle.api.Action;
 import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.UserCodeAction;
 import org.gradle.api.publish.ivy.IvyArtifact;
 import org.gradle.api.publish.ivy.IvyConfiguration;
@@ -31,7 +32,7 @@ public class DefaultIvyModuleDescriptor implements IvyModuleDescriptorInternal {
 
     private final ActionBroadcast<XmlProvider> xmlActions = new ActionBroadcast<XmlProvider>();
     private final IvyPublicationInternal ivyPublication;
-    private String status;
+    private String status = Module.DEFAULT_STATUS;
 
     public DefaultIvyModuleDescriptor(IvyPublicationInternal ivyPublication) {
         this.ivyPublication = ivyPublication;
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java
index 10967f0..0a4ddbe 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java
@@ -19,13 +19,17 @@ package org.gradle.api.publish.ivy.internal.publication;
 import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.component.SoftwareComponent;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.component.SoftwareComponentInternal;
 import org.gradle.api.internal.component.Usage;
 import org.gradle.api.internal.file.UnionFileCollection;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.api.publish.internal.ProjectDependencyPublicationResolver;
 import org.gradle.api.publish.ivy.IvyArtifact;
 import org.gradle.api.publish.ivy.IvyConfigurationContainer;
 import org.gradle.api.publish.ivy.IvyModuleDescriptor;
@@ -48,14 +52,17 @@ public class DefaultIvyPublication implements IvyPublicationInternal {
     private final IvyConfigurationContainer configurations;
     private final DefaultIvyArtifactSet ivyArtifacts;
     private final DefaultIvyDependencySet ivyDependencies;
+    private final ProjectDependencyPublicationResolver projectDependencyResolver;
     private FileCollection descriptorFile;
     private SoftwareComponentInternal component;
 
     public DefaultIvyPublication(
-            String name, Instantiator instantiator, IvyPublicationIdentity publicationIdentity, NotationParser<IvyArtifact> ivyArtifactNotationParser
+            String name, Instantiator instantiator, IvyPublicationIdentity publicationIdentity, NotationParser<Object, IvyArtifact> ivyArtifactNotationParser,
+            ProjectDependencyPublicationResolver projectDependencyResolver
     ) {
         this.name = name;
         this.publicationIdentity = publicationIdentity;
+        this.projectDependencyResolver = projectDependencyResolver;
         configurations = instantiator.newInstance(DefaultIvyConfigurationContainer.class, instantiator);
         ivyArtifacts = instantiator.newInstance(DefaultIvyArtifactSet.class, name, ivyArtifactNotationParser);
         ivyDependencies = instantiator.newInstance(DefaultIvyDependencySet.class);
@@ -98,11 +105,24 @@ public class DefaultIvyPublication implements IvyPublicationInternal {
             for (ModuleDependency dependency : usage.getDependencies()) {
                 // TODO: When we support multiple components or configurable dependencies, we'll need to merge the confs of multiple dependencies with same id.
                 String confMapping = String.format("%s->%s", conf, dependency.getConfiguration());
-                ivyDependencies.add(new DefaultIvyDependency(dependency, confMapping));
+                if (dependency instanceof ProjectDependency) {
+                    addProjectDependency((ProjectDependency) dependency, confMapping);
+                } else {
+                    addModuleDependency(dependency, confMapping);
+                }
             }
         }
     }
 
+    private void addProjectDependency(ProjectDependency dependency, String confMapping) {
+        ModuleVersionIdentifier identifier = projectDependencyResolver.resolve(dependency);
+        ivyDependencies.add(new DefaultIvyDependency(identifier.getGroup(), identifier.getName(), identifier.getVersion(), confMapping));
+    }
+
+    private void addModuleDependency(ModuleDependency dependency, String confMapping) {
+        ivyDependencies.add(new DefaultIvyDependency(dependency.getGroup(), dependency.getName(), dependency.getVersion(), confMapping, dependency.getArtifacts()));
+     }
+
     public void configurations(Action<? super IvyConfigurationContainer> config) {
         config.execute(configurations);
     }
@@ -130,6 +150,30 @@ public class DefaultIvyPublication implements IvyPublicationInternal {
         return ivyArtifacts;
     }
 
+    public String getOrganisation() {
+        return publicationIdentity.getOrganisation();
+    }
+
+    public void setOrganisation(String organisation) {
+        publicationIdentity.setOrganisation(organisation);
+    }
+
+    public String getModule() {
+        return publicationIdentity.getModule();
+    }
+
+    public void setModule(String module) {
+        publicationIdentity.setModule(module);
+    }
+
+    public String getRevision() {
+        return publicationIdentity.getRevision();
+    }
+
+    public void setRevision(String revision) {
+        publicationIdentity.setRevision(revision);
+    }
+
     public FileCollection getPublishableFiles() {
         return new UnionFileCollection(ivyArtifacts.getFiles(), descriptorFile);
     }
@@ -153,4 +197,7 @@ public class DefaultIvyPublication implements IvyPublicationInternal {
         return descriptorFile.getSingleFile();
     }
 
+    public ModuleVersionIdentifier getCoordinates() {
+        return new DefaultModuleVersionIdentifier(getOrganisation(), getModule(), getRevision());
+    }
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java
index 3279c7b..b075184 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java
@@ -37,8 +37,4 @@ public interface IvyModuleDescriptorInternal extends IvyModuleDescriptor {
     Set<IvyDependencyInternal> getDependencies();
 
     Action<XmlProvider> getXmlAction();
-
-    String getStatus();
-
-    void setStatus(String status);
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java
index 8552d14..c3e5926 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java
@@ -17,6 +17,7 @@
 package org.gradle.api.publish.ivy.internal.publication;
 
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.publish.internal.PublicationInternal;
 import org.gradle.api.publish.ivy.IvyPublication;
 import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
 import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication;
@@ -24,7 +25,7 @@ import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
 
 import java.util.Set;
 
-public interface IvyPublicationInternal extends IvyPublication {
+public interface IvyPublicationInternal extends IvyPublication, PublicationInternal {
 
     IvyPublicationIdentity getIdentity();
 
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ContextualizingIvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ContextualizingIvyPublisher.java
new file mode 100644
index 0000000..1af648a
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ContextualizingIvyPublisher.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.publisher;
+
+import org.apache.ivy.Ivy;
+import org.gradle.api.Action;
+import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+
+public class ContextualizingIvyPublisher implements IvyPublisher {
+    private final IvyPublisher ivyPublisher;
+    private final IvyContextManager ivyContextManager;
+
+    public ContextualizingIvyPublisher(IvyPublisher ivyPublisher, IvyContextManager ivyContextManager) {
+        this.ivyPublisher = ivyPublisher;
+        this.ivyContextManager = ivyContextManager;
+    }
+
+    public void publish(final IvyNormalizedPublication publication, final PublicationAwareRepository repository) {
+        ivyContextManager.withIvy(new Action<Ivy>() {
+            public void execute(Ivy ivy) {
+                ivyPublisher.publish(publication, repository);
+            }
+        });
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/DependencyResolverIvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/DependencyResolverIvyPublisher.java
index c53c8af..034c49d 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/DependencyResolverIvyPublisher.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/DependencyResolverIvyPublisher.java
@@ -19,36 +19,40 @@ package org.gradle.api.publish.ivy.internal.publisher;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.metadata.DefaultModuleVersionPublishMetaData;
 import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 import org.gradle.api.publish.ivy.IvyArtifact;
 import org.gradle.util.GUtil;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 public class DependencyResolverIvyPublisher implements IvyPublisher {
 
     public void publish(IvyNormalizedPublication publication, PublicationAwareRepository repository) {
-        DependencyResolver dependencyResolver = repository.createPublisher();
+        ModuleVersionPublisher publisher = repository.createPublisher();
         IvyPublicationIdentity projectIdentity = publication.getProjectIdentity();
-        Map<String, String> extraAttributes = Collections.emptyMap();
-        ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(projectIdentity.getOrganisation(), projectIdentity.getModule(), projectIdentity.getRevision(), extraAttributes);
+        ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(projectIdentity.getOrganisation(), projectIdentity.getModule(), projectIdentity.getRevision());
+        ModuleVersionIdentifier moduleVersionIdentifier = DefaultModuleVersionIdentifier.newId(moduleRevisionId);
+        DefaultModuleVersionPublishMetaData publishMetaData = new DefaultModuleVersionPublishMetaData(moduleVersionIdentifier);
 
         try {
-
             for (IvyArtifact publishArtifact : publication.getArtifacts()) {
                 Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleRevisionId);
-                dependencyResolver.publish(ivyArtifact, publishArtifact.getFile(), true);
+                publishMetaData.addArtifact(ivyArtifact, publishArtifact.getFile());
             }
 
             Artifact artifact = DefaultArtifact.newIvyArtifact(moduleRevisionId, null);
-            dependencyResolver.publish(artifact, publication.getDescriptorFile(), true);
+            publishMetaData.addArtifact(artifact, publication.getDescriptorFile());
+
+            publisher.publish(publishMetaData);
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java
index 277066b..8207cfc 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java
@@ -20,8 +20,6 @@ import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.XmlProvider;
 import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.xml.SimpleXmlWriter;
 import org.gradle.api.internal.xml.XmlTransformer;
 import org.gradle.api.publish.ivy.IvyArtifact;
@@ -93,7 +91,7 @@ public class IvyDescriptorFileGenerator {
     private void writeDescriptor(final Writer writer) throws IOException {
         OptionalAttributeXmlWriter xmlWriter = new OptionalAttributeXmlWriter(writer, "  ", IVY_FILE_ENCODING);
         xmlWriter.startElement("ivy-module").attribute("version", "2.0");
-        if (hasClassifier()) {
+        if (usesClassifier()) {
             xmlWriter.attribute("xmlns:m", "http://ant.apache.org/ivy/maven");
         }
 
@@ -110,14 +108,15 @@ public class IvyDescriptorFileGenerator {
         writeDependencies(xmlWriter);
         xmlWriter.endElement();
     }
-    private boolean hasClassifier() {
+
+    private boolean usesClassifier() {
         for (IvyArtifact artifact : artifacts) {
             if (artifact.getClassifier() != null) {
                 return true;
             }
         }
         for (IvyDependencyInternal dependency : this.dependencies) {
-            for (DependencyArtifact dependencyArtifact : dependency.getModuleDependency().getArtifacts()) {
+            for (DependencyArtifact dependencyArtifact : dependency.getArtifacts()) {
                 if (dependencyArtifact.getClassifier() != null) {
                     return true;
                 }
@@ -157,14 +156,13 @@ public class IvyDescriptorFileGenerator {
     private void writeDependencies(OptionalAttributeXmlWriter xmlWriter) throws IOException {
         xmlWriter.startElement("dependencies");
         for (IvyDependencyInternal dependency : dependencies) {
-            ModuleDependency dep = dependency.getModuleDependency();
             xmlWriter.startElement("dependency")
-                    .attribute("org", dep.getGroup())
-                    .attribute("name", getDependencyName(dep))
-                    .attribute("rev", dep.getVersion())
+                    .attribute("org", dependency.getOrganisation())
+                    .attribute("name", dependency.getModule())
+                    .attribute("rev", dependency.getRevision())
                     .attribute("conf", dependency.getConfMapping());
 
-            for (DependencyArtifact dependencyArtifact : dep.getArtifacts()) {
+            for (DependencyArtifact dependencyArtifact : dependency.getArtifacts()) {
                 printDependencyArtifact(dependencyArtifact, xmlWriter);
             }
             xmlWriter.endElement();
@@ -172,10 +170,6 @@ public class IvyDescriptorFileGenerator {
         xmlWriter.endElement();
     }
 
-    private String getDependencyName(ModuleDependency dep) {
-        return dep instanceof ProjectDependency ? ((ProjectDependency) dep).getDependencyProject().getName() : dep.getName();
-    }
-
     private void printDependencyArtifact(DependencyArtifact dependencyArtifact, OptionalAttributeXmlWriter xmlWriter) throws IOException {
         // TODO Use IvyArtifact here
         xmlWriter.startElement("artifact")
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java
index 0cd5202..7756965 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java
@@ -18,25 +18,25 @@ package org.gradle.api.publish.ivy.internal.publisher;
 
 import org.apache.commons.lang.ObjectUtils;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.plugins.parser.ParserSettings;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DisconnectedParserSettings;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DisconnectedDescriptorParseContext;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DisconnectedIvyXmlModuleDescriptorParser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParseException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
 import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 import org.gradle.api.publish.internal.PublicationFieldValidator;
 import org.gradle.api.publish.ivy.InvalidIvyPublicationException;
 import org.gradle.api.publish.ivy.IvyArtifact;
-import org.gradle.internal.UncheckedException;
 
 import java.io.File;
-import java.net.URL;
-import java.text.ParseException;
 import java.util.HashSet;
 import java.util.Set;
 
 public class ValidatingIvyPublisher implements IvyPublisher {
-    private final ParserSettings parserSettings = new DisconnectedParserSettings();
+    private final DescriptorParseContext parserSettings = new DisconnectedDescriptorParseContext();
     private final IvyPublisher delegate;
+    private final DisconnectedIvyXmlModuleDescriptorParser moduleDescriptorParser = new DisconnectedIvyXmlModuleDescriptorParser(new ResolverStrategy());
 
     public ValidatingIvyPublisher(IvyPublisher delegate) {
         this.delegate = delegate;
@@ -69,14 +69,10 @@ public class ValidatingIvyPublisher implements IvyPublisher {
     }
 
     private ModuleRevisionId parseIvyFile(IvyNormalizedPublication publication) {
-
         try {
-            URL ivyFileLocation = publication.getDescriptorFile().toURI().toURL();
-            return new IvyXmlModuleDescriptorParser().parseDescriptor(parserSettings, ivyFileLocation, true).getModuleRevisionId();
-        } catch (ParseException pe) {
+            return moduleDescriptorParser.parseMetaData(parserSettings, publication.getDescriptorFile()).getDescriptor().getModuleRevisionId();
+        } catch (MetaDataParseException pe) {
             throw new InvalidIvyPublicationException(publication.getName(), pe.getLocalizedMessage(), pe);
-        } catch (Exception ex) {
-            throw UncheckedException.throwAsUncheckedException(ex);
         }
     }
 
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
index 9f309fb..3e7f0d8 100644
--- 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
@@ -20,22 +20,19 @@ import org.gradle.api.*;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.publish.Publication;
+import org.gradle.api.publish.internal.ProjectDependencyPublicationResolver;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.api.publish.PublishingExtension;
-import org.gradle.api.publish.internal.PublicationContainerInternal;
-import org.gradle.api.publish.internal.PublicationFactory;
 import org.gradle.api.publish.ivy.IvyArtifact;
 import org.gradle.api.publish.ivy.IvyPublication;
+import org.gradle.api.publish.ivy.internal.IvyPublicationTasksModelRule;
 import org.gradle.api.publish.ivy.internal.artifact.IvyArtifactNotationParserFactory;
-import org.gradle.api.publish.ivy.internal.plugins.IvyPublicationDynamicDescriptorGenerationTaskCreator;
-import org.gradle.api.publish.ivy.internal.plugins.IvyPublishDynamicTaskCreator;
 import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublication;
 import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublicationIdentity;
 import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
 import org.gradle.api.publish.plugins.PublishingPlugin;
-import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.model.ModelRules;
 
 import javax.inject.Inject;
 
@@ -50,41 +47,34 @@ public class IvyPublishPlugin implements Plugin<Project> {
     private final Instantiator instantiator;
     private final DependencyMetaDataProvider dependencyMetaDataProvider;
     private final FileResolver fileResolver;
+    private final ModelRules modelRules;
+    private final ProjectDependencyPublicationResolver projectDependencyResolver;
 
     @Inject
-    public IvyPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver) {
+    public IvyPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver, ModelRules modelRules,
+                            ProjectDependencyPublicationResolver projectDependencyResolver) {
         this.instantiator = instantiator;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
         this.fileResolver = fileResolver;
+        this.modelRules = modelRules;
+        this.projectDependencyResolver = projectDependencyResolver;
     }
 
     public void apply(final Project project) {
         project.getPlugins().apply(PublishingPlugin.class);
 
-        // Create the default publication
+        // Can't move this to rules yet, because it has to happen before user deferred configurable actions
         project.getExtensions().configure(PublishingExtension.class, new Action<PublishingExtension>() {
-            public void execute(PublishingExtension publishingExtension) {
-                final PublishingExtension extension = project.getExtensions().getByType(PublishingExtension.class);
-
-                final PublicationContainerInternal publicationContainer = (PublicationContainerInternal) extension.getPublications();
-                publicationContainer.registerFactory(IvyPublication.class, new IvyPublicationFactory(dependencyMetaDataProvider, instantiator, fileResolver));
-
-                TaskContainer tasks = project.getTasks();
-
-                // Create generate descriptor tasks
-                IvyPublicationDynamicDescriptorGenerationTaskCreator descriptorGenerationTaskCreator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project);
-                descriptorGenerationTaskCreator.monitor(extension.getPublications());
-
-                // Create publish tasks automatically for any Ivy publication and repository combinations
-                Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
-                IvyPublishDynamicTaskCreator publishTaskCreator = new IvyPublishDynamicTaskCreator(tasks, publishLifecycleTask);
-                publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
+            public void execute(PublishingExtension extension) {
+                // Register factory for MavenPublication
+                extension.getPublications().registerFactory(IvyPublication.class, new IvyPublicationFactory(dependencyMetaDataProvider, instantiator, fileResolver));
             }
         });
-    }
 
+        modelRules.rule(new IvyPublicationTasksModelRule(project));
+    }
 
-    private class IvyPublicationFactory implements PublicationFactory {
+    private class IvyPublicationFactory implements NamedDomainObjectFactory<IvyPublication> {
         private final Instantiator instantiator;
         private final DependencyMetaDataProvider dependencyMetaDataProvider;
         private final FileResolver fileResolver;
@@ -95,16 +85,15 @@ public class IvyPublishPlugin implements Plugin<Project> {
             this.fileResolver = fileResolver;
         }
 
-        public Publication create(String name) {
+        public IvyPublication create(String name) {
             Module module = dependencyMetaDataProvider.getModule();
             IvyPublicationIdentity publicationIdentity = new DefaultIvyPublicationIdentity(module.getGroup(), module.getName(), module.getVersion());
-            NotationParser<IvyArtifact> notationParser = new IvyArtifactNotationParserFactory(instantiator, fileResolver, publicationIdentity).create();
-            DefaultIvyPublication ivyPublication = instantiator.newInstance(
+            NotationParser<Object, IvyArtifact> notationParser = new IvyArtifactNotationParserFactory(instantiator, fileResolver, publicationIdentity).create();
+            return instantiator.newInstance(
                     DefaultIvyPublication.class,
-                    name, instantiator, publicationIdentity, notationParser
+                    name, instantiator, publicationIdentity, notationParser, projectDependencyResolver
             );
-            ivyPublication.getDescriptor().setStatus(module.getStatus());
-            return ivyPublication;
         }
     }
+
 }
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
index 9eea44b..205a1a5 100644
--- 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
@@ -21,18 +21,15 @@ 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.Cast;
+import org.gradle.internal.Cast;
+import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager;
 import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 import org.gradle.api.publish.internal.PublishOperation;
 import org.gradle.api.publish.ivy.IvyPublication;
-import org.gradle.api.publish.ivy.internal.publisher.DependencyResolverIvyPublisher;
-import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication;
 import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal;
-import org.gradle.api.publish.ivy.internal.publisher.IvyPublisher;
-import org.gradle.api.publish.ivy.internal.publisher.ValidatingIvyPublisher;
+import org.gradle.api.publish.ivy.internal.publisher.*;
 import org.gradle.api.tasks.TaskAction;
 
-import javax.inject.Inject;
 import java.util.concurrent.Callable;
 
 /**
@@ -46,7 +43,6 @@ public class PublishToIvyRepository extends DefaultTask {
     private IvyPublicationInternal publication;
     private IvyArtifactRepository repository;
 
-    @Inject
     public PublishToIvyRepository() {
 
         // Allow the publication to participate in incremental build
@@ -142,6 +138,7 @@ public class PublishToIvyRepository extends DefaultTask {
                 IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
                 IvyPublisher publisher = new DependencyResolverIvyPublisher();
                 publisher = new ValidatingIvyPublisher(publisher);
+                publisher = new ContextualizingIvyPublisher(publisher, getServices().get(IvyContextManager.class));
                 publisher.publish(normalizedPublication, Cast.cast(PublicationAwareRepository.class, repository));
             }
         }.run();
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy
index 0f309cc..387e024 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy
@@ -15,22 +15,23 @@
  */
 
 package org.gradle.api.publish.ivy.internal.artifact
-
 import org.gradle.api.Task
 import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.internal.typeconversion.NotationParser
 import org.gradle.api.publish.ivy.IvyArtifact
 import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity
 import org.gradle.api.tasks.TaskDependency
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 public class IvyArtifactNotationParserFactoryTest extends Specification {
-    Instantiator instantiator = new DirectInstantiator()
+    Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
     def fileNotationParser = Mock(NotationParser)
     def taskDependency = Mock(TaskDependency)
     def publishArtifact = Stub(PublishArtifact) {
@@ -43,7 +44,7 @@ public class IvyArtifactNotationParserFactoryTest extends Specification {
     def task = Mock(Task)
     def dependencies = Collections.singleton(Mock(Task))
 
-    NotationParser<IvyArtifact> parser
+    NotationParser<Object, IvyArtifact> parser
 
     def "setup"() {
         def fileResolver = Stub(FileResolver) {
@@ -134,7 +135,7 @@ public class IvyArtifactNotationParserFactoryTest extends Specification {
 
     def "creates IvyArtifact for ArchivePublishArtifact"() {
         when:
-        def rootProject = HelperUtil.createRootProject()
+        def rootProject = TestUtil.createRootProject()
         def archive = rootProject.task(type: Jar, {})
         archive.setBaseName("base-name")
         archive.setExtension('extension')
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
deleted file mode 100644
index 0c5113a..0000000
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
+++ /dev/null
@@ -1,88 +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.publish.ivy.internal.plugins
-import org.gradle.api.publish.Publication
-import org.gradle.api.publish.PublishingExtension
-import org.gradle.api.publish.ivy.IvyModuleDescriptor
-import org.gradle.api.publish.ivy.IvyPublication
-import org.gradle.api.publish.ivy.internal.publication.IvyModuleDescriptorInternal
-import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
-import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor
-import org.gradle.api.publish.plugins.PublishingPlugin
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class IvyPublicationDynamicDescriptorGenerationTaskCreatorTest extends Specification {
-
-    def project = HelperUtil.createRootProject()
-    def creator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project)
-    PublishingExtension publishing
-
-    def setup() {
-        project.plugins.apply(PublishingPlugin)
-        publishing = project.extensions.getByType(PublishingExtension)
-        creator.monitor(publishing.publications)
-    }
-
-    def "creates tasks"() {
-        expect:
-        descriptorGeneratorTasks.size() == 0
-
-        when:
-        publishing.repositories.ivy { }
-        publishing.publications.add(publication("foo"))
-
-        then:
-        descriptorGeneratorTasks.size() == 0
-
-        when:
-        publishing.publications.add(ivyPublication("ivy"))
-
-        then:
-        descriptorGeneratorTasks.size() == 1
-        GenerateIvyDescriptor task = project.tasks.generateIvyModuleDescriptor
-        task.description != null
-
-        when:
-        publishing.publications.add(ivyPublication("other"))
-
-        then:
-        descriptorGeneratorTasks.size() == 2
-        def task2 = project.tasks.generateOtherIvyModuleDescriptor
-        task2
-    }
-
-    Publication publication(String name) {
-        Mock(Publication) {
-            getName() >> name
-        }
-    }
-
-    IvyPublication ivyPublication(String name) {
-        IvyModuleDescriptor moduleDescriptor = Mock(IvyModuleDescriptorInternal)
-        Mock(IvyPublicationInternal) {
-            getName() >> name
-            getDescriptor() >> moduleDescriptor
-            1 * setDescriptorFile({it.singleFile.path.contains name})
-        }
-    }
-
-    def getDescriptorGeneratorTasks() {
-        project.tasks.withType(GenerateIvyDescriptor)
-    }
-
-}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreatorTest.groovy
deleted file mode 100644
index 7d5df98..0000000
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreatorTest.groovy
+++ /dev/null
@@ -1,104 +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.publish.ivy.internal.plugins
-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.publication.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/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy
index a3cc01c..c74bbb8 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy
@@ -17,20 +17,23 @@
 package org.gradle.api.publish.ivy.internal.publication
 
 import org.gradle.api.InvalidUserDataException
-import org.gradle.api.artifacts.DependencySet
+import org.gradle.api.artifacts.DependencyArtifact
 import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.ProjectDependency
 import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.api.artifacts.PublishArtifactSet
 import org.gradle.api.internal.AsmBackedClassGenerator
 import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.component.SoftwareComponentInternal
 import org.gradle.api.internal.component.Usage
 import org.gradle.api.internal.file.collections.SimpleFileCollection
-import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.publish.internal.ProjectDependencyPublicationResolver
+import org.gradle.api.publish.internal.PublicationInternal
 import org.gradle.api.publish.ivy.IvyArtifact
 import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.typeconversion.NotationParser
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import spock.lang.Shared
@@ -42,6 +45,7 @@ class DefaultIvyPublicationTest extends Specification {
     Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
     def projectIdentity = Mock(IvyPublicationIdentity)
     def notationParser = Mock(NotationParser)
+    def projectDependencyResolver = Mock(ProjectDependencyPublicationResolver)
     File descriptorFile
     File artifactFile
 
@@ -59,6 +63,14 @@ class DefaultIvyPublicationTest extends Specification {
         publication.name == "pub-name"
     }
 
+    def "status property is defaults to 'integration'"() {
+        when:
+        def publication = createPublication()
+
+        then:
+        publication.descriptor.status == "integration"
+    }
+
     def "empty publishableFiles and artifacts when no component is added"() {
         when:
         def publication = createPublication()
@@ -72,23 +84,15 @@ class DefaultIvyPublicationTest extends Specification {
     def "adopts configurations, artifacts and publishableFiles from added component"() {
         given:
         def publication = createPublication()
-
-        def component = Mock(SoftwareComponentInternal)
-        def usage1 = Mock(Usage)
         def artifact = Mock(PublishArtifact)
         def ivyArtifact = createArtifact()
 
         when:
-        component.usages >> [usage1]
-        usage1.name >> "runtime"
-        usage1.artifacts >> [artifact]
-        usage1.dependencies >> []
-
         notationParser.parseNotation(artifact) >> ivyArtifact
         1 * ivyArtifact.setConf("runtime")
 
         and:
-        publication.from(component)
+        publication.from(componentWithArtifact(artifact))
 
         then:
         publication.publishableFiles.files == [descriptorFile, artifactFile] as Set
@@ -102,55 +106,74 @@ class DefaultIvyPublicationTest extends Specification {
         publication.dependencies.empty
     }
 
-    def "adopts dependencies from added component"() {
+    def "adopts module dependency from added component"() {
         given:
         def publication = createPublication()
-
-        def component = Mock(SoftwareComponentInternal)
-        def usage1 = Mock(Usage)
         def moduleDependency = Mock(ModuleDependency)
+        def artifact = Mock(DependencyArtifact)
 
         when:
-        component.usages >> [usage1]
-        usage1.name >> "runtime"
-        usage1.artifacts >> []
-        usage1.dependencies >> [moduleDependency]
-
+        moduleDependency.group >> "org"
+        moduleDependency.name >> "name"
+        moduleDependency.version >> "version"
         moduleDependency.configuration >> "dep-configuration"
+        moduleDependency.artifacts >> [artifact]
 
         and:
-        publication.from(component)
+        publication.from(componentWithDependency(moduleDependency))
 
         then:
         publication.publishableFiles.files == [descriptorFile] as Set
         publication.artifacts.empty
+
         and:
         publication.dependencies.size() == 1
         def ivyDependency = publication.dependencies.asList().first()
-        ivyDependency.moduleDependency == moduleDependency
-        ivyDependency.confMapping == "runtime->dep-configuration"
+
+        with (ivyDependency) {
+            organisation == "org"
+            module == "name"
+            revision == "version"
+            confMapping == "runtime->dep-configuration"
+            artifacts == [artifact]
+        }
     }
 
-    def "cannot add multiple components"() {
+    def "maps project dependency to ivy dependency"() {
         given:
         def publication = createPublication()
-        def component = Mock(SoftwareComponentInternal)
-        def usage = Mock(Usage)
-        def publishArtifactSet = Mock(PublishArtifactSet)
-        def dependencySet = Mock(DependencySet)
+        def projectDependency = Mock(ProjectDependency)
+
+        and:
+        projectDependencyResolver.resolve(projectDependency) >> DefaultModuleVersionIdentifier.newId("pub-org", "pub-module", "pub-revision")
+        projectDependency.configuration >> "dep-configuration"
 
         when:
-        publication.from(component)
+        publication.from(componentWithDependency(projectDependency))
 
         then:
-        component.usages >> [usage]
-        usage.name >> "runtime"
-        usage.artifacts >> publishArtifactSet
-        publishArtifactSet.iterator() >> [].iterator()
-        usage.dependencies >> dependencySet
-        dependencySet.iterator() >> [].iterator()
+        publication.publishableFiles.files == [descriptorFile] as Set
+        publication.artifacts.empty
+
+        and:
+        publication.dependencies.size() == 1
+        def ivyDependency = publication.dependencies.asList().first()
+
+        with (ivyDependency) {
+            organisation == "pub-org"
+            module == "pub-module"
+            revision == "pub-revision"
+            confMapping == "runtime->dep-configuration"
+            artifacts == []
+        }
+    }
+
+    def "cannot add multiple components"() {
+        given:
+        def publication = createPublication()
 
         when:
+        publication.from(createComponent([], []))
         publication.from(Mock(SoftwareComponentInternal))
 
         then:
@@ -233,7 +256,7 @@ class DefaultIvyPublicationTest extends Specification {
     }
 
     def createPublication() {
-        def publication = instantiator.newInstance(DefaultIvyPublication, "pub-name", instantiator, projectIdentity, notationParser)
+        def publication = instantiator.newInstance(DefaultIvyPublication, "pub-name", instantiator, projectIdentity, notationParser, projectDependencyResolver)
         publication.setDescriptorFile(new SimpleFileCollection(descriptorFile))
         return publication;
     }
@@ -244,4 +267,31 @@ class DefaultIvyPublicationTest extends Specification {
         }
         return artifact
     }
+
+    def componentWithDependency(ModuleDependency dependency) {
+        return createComponent([], [dependency])
+    }
+
+    def componentWithArtifact(def artifact) {
+        return createComponent([artifact], [])
+    }
+
+    def createComponent(def artifacts, def dependencies) {
+        def usage = Stub(Usage) {
+            getName() >> "runtime"
+            getArtifacts() >> artifacts
+            getDependencies() >> dependencies
+        }
+        def component = Stub(SoftwareComponentInternal) {
+            getUsages() >> [usage]
+        }
+        return component
+    }
+
+    def otherPublication(String name, String org, String module, String revision) {
+        def pub = Mock(PublicationInternal)
+        pub.name >> name
+        pub.coordinates >> new DefaultModuleVersionIdentifier(org, module, revision)
+        return pub
+    }
 }
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy
index 9b88a47..268d700 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy
@@ -16,11 +16,8 @@
 
 package org.gradle.api.publish.ivy.internal.publisher
 import org.gradle.api.Action
-import org.gradle.api.Project
 import org.gradle.api.XmlProvider
 import org.gradle.api.artifacts.DependencyArtifact
-import org.gradle.api.artifacts.ModuleDependency
-import org.gradle.api.artifacts.ProjectDependency
 import org.gradle.api.publish.ivy.internal.artifact.DefaultIvyArtifact
 import org.gradle.api.publish.ivy.internal.dependency.DefaultIvyDependency
 import org.gradle.api.publish.ivy.internal.publication.DefaultIvyConfiguration
@@ -28,7 +25,6 @@ import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublicationIden
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.CollectionUtils
 import org.gradle.util.TextUtil
 import spock.lang.Specification
 
@@ -136,33 +132,16 @@ class IvyDescriptorFileGeneratorTest extends Specification {
         }
     }
     def "writes supplied dependencies"() {
-        def projectDependency = Mock(ProjectDependency)
-        def moduleDependency = Mock(ModuleDependency)
         when:
-        projectDependency.artifacts >> new HashSet<DependencyArtifact>()
-        projectDependency.group >> "dep-group"
-        projectDependency.name >> "dep-name-1"
-        projectDependency.version >> "dep-version"
-        projectDependency.dependencyProject >> Stub(Project) {
-            getName() >> "project-name"
-        }
-
-        and:
-        moduleDependency.artifacts >> new HashSet<DependencyArtifact>()
-        moduleDependency.group >> "dep-group"
-        moduleDependency.name >> "dep-name-2"
-        moduleDependency.version >> "dep-version"
-
-        and:
-        generator.addDependency(new DefaultIvyDependency(projectDependency, "confMappingProject"))
-        generator.addDependency(new DefaultIvyDependency(moduleDependency, null))
+        generator.addDependency(new DefaultIvyDependency('dep-group', 'dep-name-1', 'dep-version', "confMappingProject"))
+        generator.addDependency(new DefaultIvyDependency('dep-group', 'dep-name-2', 'dep-version', null))
 
         then:
         with (ivyXml) {
             dependencies.dependency.size() == 2
             with (dependencies[0].dependency[0]) {
                 it. at org == "dep-group"
-                it. at name == "project-name"
+                it. at name == "dep-name-1"
                 it. at rev == "dep-version"
                 it. at conf == "confMappingProject"
             }
@@ -176,15 +155,10 @@ class IvyDescriptorFileGeneratorTest extends Specification {
     }
 
     def "writes dependency with artifacts"() {
-        def dependency = Mock(ModuleDependency)
         def artifact1 = Mock(DependencyArtifact)
         def artifact2 = Mock(DependencyArtifact)
 
         when:
-        dependency.artifacts >> CollectionUtils.toSet([artifact1, artifact2])
-        dependency.group >> "dep-group"
-        dependency.name >> "dep-name"
-        dependency.version >> "dep-version"
         artifact1.name >> "artifact-1"
         artifact1.type >> "type-1"
         artifact1.extension >> "ext-1"
@@ -194,7 +168,7 @@ class IvyDescriptorFileGeneratorTest extends Specification {
         artifact2.classifier >> "classy"
 
         and:
-        generator.addDependency(new DefaultIvyDependency(dependency, "confMapping"))
+        generator.addDependency(new DefaultIvyDependency('dep-group', 'dep-name', 'dep-version', "confMapping", [artifact1, artifact2]))
 
         then:
         includesMavenNamespace()
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy
index ab19830..f6fa348 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy
@@ -132,8 +132,7 @@ public class ValidatingIvyPublisherTest extends Specification {
 
         then:
         def e = thrown InvalidIvyPublicationException
-        e.message.startsWith "Invalid publication 'pub-name': Problem occurred while parsing ivy file: " +
-                "Cannot add artifact 'name.ext(type)' to configuration 'unknown' of module the-group#the-artifact;the-version because this configuration doesn't exist!"
+        e.message == "Invalid publication 'pub-name': Could not parse Ivy file ${ivyFile.toURI()}"
     }
 
     def "validates artifact attributes"() {
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
index 2a27d4e..a789ab0 100644
--- 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
@@ -15,18 +15,20 @@
  */
 
 package org.gradle.api.publish.ivy.plugins
-import org.gradle.api.Project
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.TaskContainerInternal
 import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.api.publish.PublishingExtension
 import org.gradle.api.publish.ivy.IvyPublication
 import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublication
 import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class IvyPublishPluginTest extends Specification {
 
-    Project project = HelperUtil.createRootProject()
+    ProjectInternal project = TestUtil.createRootProject()
     PublishingExtension publishing
 
     def setup() {
@@ -41,7 +43,7 @@ class IvyPublishPluginTest extends Specification {
 
     def "publication can be added"() {
         when:
-        publishing.publications.add("test", IvyPublication)
+        publishing.publications.create("test", IvyPublication)
 
         then:
         publishing.publications.size() == 1
@@ -50,8 +52,9 @@ class IvyPublishPluginTest extends Specification {
 
     def "creates publish task for publication and repository"() {
         when:
-        publishing.publications.add("test", IvyPublication)
+        publishing.publications.create("test", IvyPublication)
         publishing.repositories { ivy { url = "http://foo.com" } }
+        project.modelRegistry.get(TaskContainerInternal.MODEL_PATH, Object)
         def publishTask = project.tasks["publishTestPublicationToIvyRepository"]
 
         then:
@@ -63,10 +66,10 @@ class IvyPublishPluginTest extends Specification {
         when:
         project.group = "foo"
         project.version = 1.0
-        project.status = "integration"
+        project.status = "another"
 
         and:
-        publishing.publications.add("test", IvyPublication)
+        publishing.publications.create("test", IvyPublication)
 
         then:
         with (publishing.publications.test) {
@@ -89,7 +92,7 @@ class IvyPublishPluginTest extends Specification {
 
     def "can configure descriptor"() {
         given:
-        publishing.publications.add("ivy", IvyPublication)
+        publishing.publications.create("ivy", IvyPublication)
         IvyPublicationInternal publication = publishing.publications.ivy
 
         when:
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
index 191f145..3fe58ec 100644
--- 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
@@ -21,7 +21,7 @@ import org.gradle.api.artifacts.repositories.IvyArtifactRepository
 import org.gradle.api.publish.ivy.IvyPublication
 import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication
 import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class PublishToIvyRepositoryTest extends Specification {
@@ -38,7 +38,7 @@ class PublishToIvyRepositoryTest extends Specification {
     def repository = Mock(IvyArtifactRepository) {}
 
     def setup() {
-        project = HelperUtil.createRootProject()
+        project = TestUtil.createRootProject()
         publish = createPublish("publish")
     }
 
@@ -70,7 +70,7 @@ class PublishToIvyRepositoryTest extends Specification {
     }
 
     PublishToIvyRepository createPublish(String name) {
-        project.tasks.add(name, PublishToIvyRepository)
+        project.tasks.create(name, PublishToIvyRepository)
     }
 
 }
diff --git a/subprojects/jacoco/jacoco.gradle b/subprojects/jacoco/jacoco.gradle
new file mode 100644
index 0000000..b675202
--- /dev/null
+++ b/subprojects/jacoco/jacoco.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 {
+	compile libraries.groovy
+	compile project(':core')
+	compile project(':plugins')
+	compile project(':reporting')
+    testCompile libraries.jsoup
+}
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginGoodBehaviourTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..36bff84
--- /dev/null
+++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginGoodBehaviourTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+
+class JacocoPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    // The jacoco plugin does not add tasks to an empty project.
+    // We apply the java plugin too here in this test to check that
+    // the jacoco plugin behaves well when used together
+    // with the java plugin (a typical scenario).
+
+    def setup(){
+        buildFile << """
+            apply plugin:'java'
+        """
+    }
+}
diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy
new file mode 100644
index 0000000..3e440d1
--- /dev/null
+++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.testing.jacoco.tasks.JacocoMerge
+import spock.lang.Issue
+
+class JacocoPluginIntegrationTest extends AbstractIntegrationSpec {
+
+    private static final String REPORTING_BASE = "${Project.DEFAULT_BUILD_DIR_NAME}/${ReportingExtension.DEFAULT_REPORTS_DIR_NAME}"
+    private static final String REPORT_HTML_DEFAULT_PATH = "${REPORTING_BASE}/jacoco/test/html/index.html"
+    private static final String REPORT_XML_DEFAULT_PATH = "${REPORTING_BASE}/jacoco/test/jacocoTestReport.xml"
+    private static final String REPORT_CSV_DEFAULT_REPORT = "${REPORTING_BASE}/jacoco/test/jacocoTestReport.csv"
+
+    def setup() {
+        buildFile << """
+            apply plugin: "java"
+            apply plugin: "jacoco"
+
+            repositories {
+                mavenCentral()
+            }
+            dependencies {
+                testCompile 'junit:junit:4.11'
+            }
+        """
+        createTestFiles()
+    }
+
+    def "dependencies report shows default jacoco dependencies"() {
+        when: succeeds("dependencies", "--configuration", "jacocoAgent")
+        then: output.contains "org.jacoco:org.jacoco.agent:"
+
+        when: succeeds("dependencies", "--configuration", "jacocoAnt")
+        then: output.contains "org.jacoco:org.jacoco.ant:"
+    }
+
+    void "allows configuring tool dependencies explicitly"() {
+        when:
+        buildFile << """
+            dependencies {
+                //downgrade version:
+                jacocoAgent "org.jacoco:org.jacoco.agent:0.6.0.201210061924"
+                jacocoAnt "org.jacoco:org.jacoco.ant:0.6.0.201210061924"
+            }
+        """
+
+        succeeds("dependencies", "--configuration", "jacocoAgent")
+        then: output.contains "org.jacoco:org.jacoco.agent:0.6.0.201210061924"
+
+        when: succeeds("dependencies", "--configuration", "jacocoAnt")
+        then: output.contains "org.jacoco:org.jacoco.ant:0.6.0.201210061924"
+    }
+
+    void generatesHtmlReportOnlyAsDefault() {
+        when:
+        succeeds('test', 'jacocoTestReport')
+
+        then:
+        file(REPORTING_BASE).listFiles().collect { it.name } as Set == ["jacoco", "tests"] as Set
+        file(REPORT_HTML_DEFAULT_PATH).exists()
+        file("${REPORTING_BASE}/jacoco/test").listFiles().collect { it.name } == ["html"]
+        file("${REPORTING_BASE}/jacoco/test/html/org.gradle/Class1.java.html").exists()
+    }
+
+    void canConfigureReportsInJacocoTestReport() {
+        given:
+        buildFile << """
+            jacocoTestReport {
+                reports {
+                    xml.enabled true
+                    csv.enabled true
+                    html.destination "\${buildDir}/jacocoHtml"
+                }
+            }
+            """
+
+        when:
+        succeeds('test', 'jacocoTestReport')
+
+        then:
+        file("build/jacocoHtml/index.html").exists()
+        file(REPORT_XML_DEFAULT_PATH).exists()
+        file(REPORT_CSV_DEFAULT_REPORT).exists()
+    }
+
+    void respectsReportingBaseDir() {
+        given:
+        buildFile << """
+            jacocoTestReport {
+                reports.xml.enabled = true
+                reports.csv.enabled = true
+            }
+            reporting{
+                baseDir = "\$buildDir/customReports"
+            }"""
+
+        when:
+        succeeds('test', 'jacocoTestReport')
+
+        then:
+        file("build/customReports/jacoco/test/html/index.html").exists()
+        file("build/customReports/jacoco/test/jacocoTestReport.xml").exists()
+        file("build/customReports/jacoco/test/jacocoTestReport.csv").exists()
+    }
+
+    void canConfigureReportDirectory() {
+        given:
+        def customReportDirectory = "customJacocoReportDir"
+        buildFile << """
+            jacocoTestReport {
+                reports.xml.enabled = true
+                reports.csv.enabled = true
+            }
+            jacoco {
+                reportsDir = new File(buildDir, "$customReportDirectory")
+            }
+            """
+
+        when:
+        succeeds('test', 'jacocoTestReport')
+
+        then:
+        file("build/${customReportDirectory}/test/html/index.html").exists()
+        file("build/${customReportDirectory}/test/jacocoTestReport.xml").exists()
+        file("build/${customReportDirectory}/test/jacocoTestReport.csv").exists()
+    }
+
+    void jacocoReportIsIncremental() {
+        when:
+        succeeds('test', 'jacocoTestReport')
+
+        then:
+        file(REPORT_HTML_DEFAULT_PATH).exists()
+
+        when:
+        succeeds('jacocoTestReport')
+
+        then:
+        skippedTasks.contains(":jacocoTestReport")
+        file(REPORT_HTML_DEFAULT_PATH).exists()
+
+        when:
+        file("${REPORTING_BASE}/jacoco/test/html/.resources").deleteDir()
+        succeeds('test', 'jacocoTestReport')
+
+        then:
+        !skippedTasks.contains(":jacocoTestReport")
+        file(REPORT_HTML_DEFAULT_PATH).exists()
+    }
+
+    void jacocoTestReportIsSkippedIfNoCoverageDataAvailable() {
+        when:
+        def executionResult = succeeds('jacocoTestReport')
+        then:
+        executionResult.assertTaskSkipped(':jacocoTestReport')
+    }
+
+    void canUseCoverageDataFromPreviousRunForCoverageReport() {
+        when:
+        succeeds('jacocoTestReport')
+
+        then:
+        skippedTasks.contains(":jacocoTestReport")
+        !file(REPORT_HTML_DEFAULT_PATH).exists()
+
+        when:
+        succeeds('test')
+
+        and:
+        succeeds('jacocoTestReport')
+
+        then:
+        executedTasks.contains(":jacocoTestReport")
+        file(REPORT_HTML_DEFAULT_PATH).exists()
+    }
+
+    void canMergeCoverageData() {
+        given:
+        buildFile << """
+            task otherTests(type: Test) {
+                binResultsDir file("bin")
+                testSrcDirs = test.testSrcDirs
+                testClassesDir = test.testClassesDir
+                classpath = test.classpath
+            }
+
+            task jacocoMerge(type: ${JacocoMerge.name}) {
+                executionData test, otherTests
+            }
+        """
+        when:
+        succeeds 'jacocoMerge'
+
+        then:
+        ":jacocoMerge" in nonSkippedTasks
+        ":test" in nonSkippedTasks
+        ":otherTests" in nonSkippedTasks
+        file("build/jacoco/jacocoMerge.exec").exists()
+    }
+
+    @Issue("GRADLE-2917")
+    void "configures default jacoco dependencies even if the configuration was resolved before"() {
+        expect:
+        //dependencies task forces resolution of the configurations
+        succeeds "dependencies", "test", "jacocoTestReport"
+    }
+
+    private void createTestFiles() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }"
+    }
+}
+
diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy
new file mode 100644
index 0000000..86b29e9
--- /dev/null
+++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.jsoup.select.Elements
+import org.junit.Test
+import static org.junit.Assert.*
+
+ at TargetVersions(['0.6.0.201210061924', '0.6.2.201302030002'])
+class JacocoVersionIntegTest extends MultiVersionIntegrationSpec {
+
+    @Test
+    public void canRunVersions() {
+        given:
+        buildFile << """
+        apply plugin: "java"
+        apply plugin: "jacoco"
+
+        repositories {
+            mavenCentral()
+        }
+
+        dependencies {
+            testCompile 'junit:junit:4.10'
+        }
+        jacoco {
+            toolVersion = '$version'
+        }
+        """
+        createTestFiles();
+        when:
+        executer.withArgument("-d")
+        succeeds('test', 'jacocoTestReport')
+        then:
+        correctJacocoVersionUsed()
+        file("build/reports/jacoco/test/html/index.html").exists()
+    }
+
+    private void createTestFiles() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }"
+    }
+
+    def correctJacocoVersionUsed() {
+        Document parsedHtmlReport = Jsoup.parse(file("build/reports/jacoco/test/html/index.html"), "UTF-8")
+        Elements footer = parsedHtmlReport.select("div.footer:has(a[href=http://www.eclemma.org/jacoco])")
+        assertTrue footer.text().contains(version)
+        true
+    }
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/internal/jacoco/JacocoAgentJar.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/internal/jacoco/JacocoAgentJar.groovy
new file mode 100644
index 0000000..33a555b
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/internal/jacoco/JacocoAgentJar.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco
+
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+
+/**
+ * Helper to resolve the {@code jacocoagent.jar} from inside
+ * of the {@code org.jacoco.agent.jar}.
+ */
+class JacocoAgentJar {
+    private final Project project
+    private File agentJar
+    FileCollection agentConf
+
+    /**
+     * Constructs a new agent JAR wrapper.
+     * @param project a project that can be used to resolve files
+     * @param agentConf the configuration that the agent JAR is located in
+     */
+    JacocoAgentJar(Project project) {
+        this.project = project
+    }
+
+    /**
+     * Unzips the resolved {@code org.jacoco.agent.jar} to retrieve
+     * the {@code jacocoagent.jar}.
+     * @return a file pointing to the {@code jacocoagent.jar}
+     */
+    File getJar() {
+        if (!agentJar) {
+            agentJar = project.zipTree(getAgentConf().singleFile).filter { it.name == 'jacocoagent.jar' }.singleFile
+        }
+        return agentJar
+    }
+
+    boolean supportsJmx() {
+        def pre062 = getAgentConf().any {
+            it.name ==~ /org.jacoco.agent-([0]\.[0-6]\.[0-1\.].*)\.jar/
+        }
+        return !pre062
+    }
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/internal/jacoco/JacocoReportsContainerImpl.java b/subprojects/jacoco/src/main/groovy/org/gradle/internal/jacoco/JacocoReportsContainerImpl.java
new file mode 100644
index 0000000..672cfc4
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/internal/jacoco/JacocoReportsContainerImpl.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.ConfigurableReport;
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleDirectoryReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+import org.gradle.testing.jacoco.tasks.JacocoReportsContainer;
+
+public class JacocoReportsContainerImpl extends TaskReportContainer<Report> implements JacocoReportsContainer {
+
+    public JacocoReportsContainerImpl(Task task) {
+        super(ConfigurableReport.class, task);
+        add(TaskGeneratedSingleDirectoryReport.class, "html", task, "index.html");
+        add(TaskGeneratedSingleFileReport.class, "xml", task);
+        add(TaskGeneratedSingleFileReport.class, "csv", task);
+    }
+
+    public DirectoryReport getHtml() {
+        return (DirectoryReport)getByName("html");
+    }
+
+    public SingleFileReport getXml() {
+        return (SingleFileReport)getByName("xml");
+    }
+
+    public SingleFileReport getCsv() {
+        return (SingleFileReport)getByName("csv");
+    }
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoPlugin.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoPlugin.groovy
new file mode 100644
index 0000000..35c1d1f
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoPlugin.groovy
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.reporting.Report
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.api.tasks.testing.Test
+import org.gradle.internal.jacoco.JacocoAgentJar
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.testing.jacoco.tasks.JacocoBase
+import org.gradle.testing.jacoco.tasks.JacocoMerge
+import org.gradle.testing.jacoco.tasks.JacocoReport
+
+import javax.inject.Inject
+
+/**
+ * Plugin that provides support for generating Jacoco coverage data.
+ */
+ at Incubating
+class JacocoPlugin implements Plugin<Project> {
+    static final String AGENT_CONFIGURATION_NAME = 'jacocoAgent'
+    static final String ANT_CONFIGURATION_NAME = 'jacocoAnt'
+    static final String PLUGIN_EXTENSION_NAME = 'jacoco'
+    private final Instantiator instantiator
+
+    private Project project
+
+    /**
+     * Applies the plugin to the given project.
+     * @param project the project to apply to
+     */
+    @Inject
+    JacocoPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator
+    }
+
+    void apply(Project project) {
+        project.plugins.apply(ReportingBasePlugin)
+        this.project = project
+        addJacocoConfigurations()
+        JacocoAgentJar agent = instantiator.newInstance(JacocoAgentJar, project)
+        JacocoPluginExtension extension = project.extensions.create(PLUGIN_EXTENSION_NAME, JacocoPluginExtension, project, agent)
+        ReportingExtension reportingExtension = project.extensions.getByName(ReportingExtension.NAME)
+        extension.conventionMapping.reportsDir = { reportingExtension.file("jacoco") }
+
+        configureAgentDependencies(agent, extension)
+        configureTaskClasspathDefaults(extension)
+        applyToDefaultTasks(extension)
+        configureDefaultOutputPathForJacocoMerge()
+        configureJacocoReportDefaults(project, extension)
+        addDefaultReportTasks(extension)
+    }
+
+    private void configureJacocoReportDefaults(Project project, extension) {
+        project.tasks.withType(JacocoReport) { reportTask ->
+            reportTask.reports.all { report ->
+                report.conventionMapping.with {
+                    enabled = report.name == "html"
+                    if (report.outputType == Report.OutputType.DIRECTORY) {
+                        destination = { new File(extension.reportsDir, "${reportTask.name}/${report.name}") }
+                    } else {
+                        destination = { new File(extension.reportsDir, "${reportTask.name}/${reportTask.name}.${report.name}") }
+                    }
+                }
+            }
+        }
+    }
+
+    def configureDefaultOutputPathForJacocoMerge() {
+        project.tasks.withType(JacocoMerge) { task ->
+            task.conventionMapping.destinationFile = { new File(project.buildDir, "/jacoco/${task.name}.exec") }
+        }
+    }
+
+    /**
+     * Creates the configurations used by plugin.
+     * @param project the project to add the configurations to
+     */
+    private void addJacocoConfigurations() {
+        this.project.configurations.create(AGENT_CONFIGURATION_NAME).with {
+            visible = false
+            transitive = true
+            description = 'The Jacoco agent to use to get coverage data.'
+        }
+        this.project.configurations.create(ANT_CONFIGURATION_NAME).with {
+            visible = false
+            transitive = true
+            description = 'The Jacoco ant tasks to use to get execute Gradle tasks.'
+        }
+    }
+
+    /**
+     * Configures the agent dependencies using the 'jacocoAnt' configuration.
+     * Uses the version declared in 'toolVersion' of the Jacoco extension if no dependencies are explicitly declared.
+     * @param project the project to add the dependencies to
+     * @param extension the extension that has the tool version to use
+     */
+    private void configureAgentDependencies(JacocoAgentJar jacocoAgentJar, JacocoPluginExtension extension) {
+        def config = this.project.configurations[AGENT_CONFIGURATION_NAME]
+        jacocoAgentJar.conventionMapping.agentConf = { config }
+        config.incoming.beforeResolve {
+            if (config.dependencies.empty) {
+                config.dependencies.add(this.project.dependencies.create("org.jacoco:org.jacoco.agent:${extension.toolVersion}"))
+            }
+        }
+    }
+
+    /**
+     * Configures the classpath for Jacoco tasks using the 'jacocoAnt' configuration.
+     * Uses the version information declared in 'toolVersion' of the Jacoco extension if no dependencies are explicitly declared.
+     * @param extension the JacocoPluginExtension
+     */
+    private void configureTaskClasspathDefaults(JacocoPluginExtension extension) {
+        def config = this.project.configurations[ANT_CONFIGURATION_NAME]
+        this.project.tasks.withType(JacocoBase) { task ->
+            task.conventionMapping.jacocoClasspath = { config }
+        }
+        config.incoming.beforeResolve {
+            if (config.dependencies.empty) {
+                config.dependencies.add(this.project.dependencies.create("org.jacoco:org.jacoco.ant:${extension.toolVersion}"))
+            }
+        }
+    }
+
+    /**
+     * Applies the Jacoco agent to all tasks of type {@code Test}.
+     * @param extension the extension to apply Jacoco with
+     */
+    private void applyToDefaultTasks(JacocoPluginExtension extension) {
+        project.tasks.withType(Test) {
+            extension.applyTo(it)
+        }
+    }
+
+    /**
+     * Adds report tasks for specific default test tasks.
+     * @param extension the extension describing the test task names
+     */
+    private void addDefaultReportTasks(JacocoPluginExtension extension) {
+        this.project.plugins.withType(JavaPlugin) {
+            this.project.tasks.withType(Test) { task ->
+                if (task.name == JavaPlugin.TEST_TASK_NAME) {
+                    JacocoReport reportTask = this.project.tasks.create("jacoco${task.name.capitalize()}Report", JacocoReport)
+                    reportTask.executionData task
+                    reportTask.sourceSets(this.project.sourceSets.main)
+                    reportTask.conventionMapping.with {
+                        reportTask.reports.all { report ->
+                            report.conventionMapping.with {
+                                enabled = { true }
+                                if (report.outputType == Report.OutputType.DIRECTORY) {
+                                    destination = { new File(extension.reportsDir, "${task.name}/${report.name}") }
+                                } else {
+                                    destination = { new File(extension.reportsDir, "${task.name}/${reportTask.name}.${report.name}") }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginExtension.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginExtension.groovy
new file mode 100644
index 0000000..b7b8fe9
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginExtension.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Project
+import org.gradle.api.logging.Logger
+import org.gradle.api.tasks.TaskCollection
+import org.gradle.internal.jacoco.JacocoAgentJar
+import org.gradle.process.JavaForkOptions
+
+import static org.gradle.api.logging.Logging.getLogger
+
+/**
+ * Extension including common properties and methods for Jacoco.
+ */
+ at Incubating
+class JacocoPluginExtension {
+    static final String TASK_EXTENSION_NAME = 'jacoco'
+
+    Logger logger = getLogger(getClass())
+    /**
+     * Version of Jacoco JARs to use.
+     */
+    String toolVersion = '0.6.2.201302030002'
+
+    protected final Project project
+
+    /**
+     * The directory where reports will be generated.
+     */
+    File reportsDir
+
+    private final JacocoAgentJar agent
+
+    /**
+     * Creates a Jacoco plugin extension.
+     * @param project the project the extension is attached to
+     * @param agent the agent JAR to be used by Jacoco
+     */
+    JacocoPluginExtension(Project project, JacocoAgentJar agent) {
+        this.project = project
+        this.agent = agent
+    }
+
+    /**
+     * Applies Jacoco to the given task. Configuration options will be
+     * provided on a task extension named {@link #TASK_EXTENSION_NAME}.
+     * Jacoco will be run as an agent during the execution of the task.
+     * @param task the task to apply Jacoco to.
+     */
+    void applyTo(JavaForkOptions task) {
+        logger.debug "Applying Jacoco to $task.name"
+        JacocoTaskExtension extension = task.extensions.create(TASK_EXTENSION_NAME, JacocoTaskExtension, agent, task)
+        task.jacoco.conventionMapping.destinationFile = { project.file("${project.buildDir}/jacoco/${task.name}.exec") }
+        task.doFirst {
+            //add agent
+            if (extension.enabled) {
+                task.jvmArgs extension.getAsJvmArg()
+            }
+        }
+    }
+
+    /**
+     * Applies Jacoco to all of the given tasks.
+     * @param tasks the tasks to apply Jacoco to
+     * @see #applyTo(JavaForkOptions)
+     */
+    void applyTo(TaskCollection tasks) {
+        tasks.withType(JavaForkOptions) {
+            applyTo(it)
+        }
+    }
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoTaskExtension.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoTaskExtension.groovy
new file mode 100644
index 0000000..4139ea8
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/plugins/JacocoTaskExtension.groovy
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.internal.jacoco.JacocoAgentJar
+import org.gradle.process.JavaForkOptions
+import org.gradle.util.GFileUtils
+
+/**
+ * Extension for tasks that should run with a Jacoco agent
+ * to generate coverage execution data.
+ */
+ at Incubating
+class JacocoTaskExtension {
+    JacocoAgentJar agent
+
+    /**
+     * Whether or not the task should generate execution data.
+     * Defaults to {@code true}.
+     */
+    boolean enabled = true
+
+    /**
+     * The path for the execution data to be written to.
+     */
+    File destinationFile
+
+    /**
+     * Whether or not data should be appended if the {@code destinationFile}
+     * already exists. Defaults to {@code true}.
+     */
+    boolean append = true
+
+    /**
+     * List of class names that should be included in analysis. Names
+     * can use wildcards (* and ?). If left empty, all classes will
+     * be included. Defaults to an empty list.
+     */
+    List<String> includes = []
+
+    /**
+     * List of class names that should be excluded from analysis. Names
+     * can use wildcard (* and ?). Defaults to an empty list.
+     */
+    List<String> excludes = []
+
+    /**
+     * List of classloader names that should be excluded from analysis. Names
+     * can use wildcards (* and ?). Defaults to an empty list.
+     */
+    List<String> excludeClassLoaders = []
+
+    /**
+     * An identifier for the session written to the execution data. Defaults
+     * to an auto-generated identifier.
+     */
+    String sessionId
+
+    /**
+     * Whether or not to dump the coverage data at VM shutdown. Defaults to {@code true}.
+     */
+    boolean dumpOnExit = true
+
+    /**
+     * THe type of output to generate. Defaults to {@link Output#FILE}.
+     */
+    Output output = Output.FILE
+
+    /**
+     * IP address or hostname to use with {@link Output#TCP_SERVER} or
+     * {@link Output#TCP_CLIENT}. Defaults to localhost.
+     */
+    String address
+
+    /**
+     * Port to bind to for {@link Output#TCP_SERVER} or {@link Output#TCP_CLIENT}.
+     * Defaults to 6300.
+     */
+    int port
+
+    /**
+     * Path to dump all class files the agent sees are dumped to. Defaults to no dumps.
+     */
+    File classDumpFile
+
+    /**
+     * Whether or not to expose functionality via JMX under {@code org.jacoco:type=Runtime}.
+     * Defaults to {@code false}.
+     *
+     * The configuration of the jmx property is only taken into account if the used JaCoCo version
+     * supports this option (JaCoCo version >= 0.6.2)
+     */
+    boolean jmx = false
+
+    /**
+     * The task we extend
+      */
+    private JavaForkOptions task
+
+    /**
+     * Creates a Jacoco task extension.
+     * @param project the project the task is attached to
+     * @param agent the agent JAR to use for analysis
+     */
+    JacocoTaskExtension(JacocoAgentJar agent, JavaForkOptions task) {
+        this.agent = agent
+        this.task = task;
+    }
+
+    /**
+     * Gets all properties in the format expected of the agent JVM argument.
+     * @return state of extension in a JVM argument
+     */
+    String getAsJvmArg() {
+        StringBuilder builder = new StringBuilder()
+        boolean anyArgs = false
+        File workingDirectory = task.getWorkingDir()
+        Closure arg = { name, value ->
+            if (value instanceof Boolean || value) {
+                if (anyArgs) {
+                    builder << ','
+                }
+                builder << name
+                builder << '='
+                if (value instanceof Collection) {
+                    builder << value.join(':')
+                } else if (value instanceof File) {
+                    builder << GFileUtils.relativePath(workingDirectory, value)
+                } else {
+                    builder << value
+                }
+                anyArgs = true
+            }
+        }
+
+        builder << '-javaagent:'
+
+        builder << GFileUtils.relativePath(task.getWorkingDir(), agent.jar)
+        builder << '='
+        arg 'destfile', getDestinationFile()
+        arg 'append', getAppend()
+        arg 'includes', getIncludes()
+        arg 'excludes', getExcludes()
+        arg 'exclclassloader', getExcludeClassLoaders()
+        arg 'sessionid', getSessionId()
+        arg 'dumponexit', getDumpOnExit()
+        arg 'output', getOutput().asArg
+        arg 'address', getAddress()
+        arg 'port', getPort()
+        arg 'classdumpdir', classDumpFile
+
+        if (agent.supportsJmx()) {
+            arg 'jmx', getJmx()
+        }
+        return builder.toString()
+    }
+
+/**
+ * The types of output that the agent
+ * can use for execution data.
+ */
+    enum Output {
+        FILE,
+        TCP_SERVER,
+        TCP_CLIENT,
+        NONE
+
+        /**
+         * Gets type in format of agent argument.
+         */
+        String getAsArg() {
+            return toString().toLowerCase().replaceAll('_', '')
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoBase.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoBase.groovy
new file mode 100644
index 0000000..ec4768f
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoBase.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputFiles
+
+/**
+ * Base class for Jacoco tasks.
+ */
+ at Incubating
+abstract class JacocoBase extends DefaultTask {
+	/**
+	 * Classpath containing Jacoco classes for use by the task.
+	 */
+	@InputFiles
+	FileCollection jacocoClasspath
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoMerge.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoMerge.groovy
new file mode 100644
index 0000000..43df564
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoMerge.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.tasks
+
+import org.gradle.api.Incubating
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskCollection
+import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
+
+/**
+ * Task to merge multiple execution data files into one.
+ */
+ at Incubating
+class JacocoMerge extends JacocoBase {
+    /**
+     * Collection of execution data files to merge.
+     */
+    @InputFiles
+    FileCollection executionData
+
+    /**
+     * File to write merged execution data to.
+     */
+    @OutputFile
+    File destinationFile
+
+    @TaskAction
+    void merge() {
+        getAnt().taskdef(name: 'jacocoMerge', classname: 'org.jacoco.ant.MergeTask', classpath: getJacocoClasspath().asPath)
+        getAnt().jacocoMerge(destfile: getDestinationFile()) {
+            getExecutionData().addToAntBuilder(ant, 'resources')
+        }
+    }
+
+    /**
+     * Adds execution data files to be merged.
+     * @param files one or more files to merge
+     */
+    void executionData(Object... files) {
+        if (this.executionData == null) {
+            this.executionData = getProject().files(files)
+        } else {
+            this.executionData += getProject().files(files)
+        }
+    }
+
+    /**
+     * Adds execution data generated by a task to the list
+     * of those to merge. Only tasks
+     * with a {@link JacocoTaskExtension} will be included;
+     * all others will be ignored.
+     * @param tasks one or more tasks to merge
+     */
+    void executionData(Task... tasks) {
+        tasks.each { task ->
+            JacocoTaskExtension extension = task.extensions.findByType(JacocoTaskExtension)
+            if (extension != null) {
+                executionData(project.files(extension.destinationFile) {
+                    builtBy task
+                })
+            }
+        }
+    }
+
+    /**
+     * Adds execution data generated by the given tasks to
+     * the list of those merged.
+     * Only tasks with a {@link JacocoTaskExtension} will
+     * be included; all others will be ignored.
+     * @param tasks one or more tasks to merge
+     */
+    void executionData(TaskCollection tasks) {
+        tasks.all { executionData(it) }
+    }
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoReport.groovy b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoReport.groovy
new file mode 100644
index 0000000..d55ea27
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoReport.groovy
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.tasks
+
+import org.gradle.api.Incubating
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.reporting.Reporting
+import org.gradle.api.tasks.*
+import org.gradle.internal.jacoco.JacocoReportsContainerImpl
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
+
+import javax.inject.Inject
+
+/**
+ * Task to generate HTML, Xml and CSV reports of Jacoco coverage data.
+ */
+ at Incubating
+class JacocoReport extends JacocoBase implements Reporting<JacocoReportsContainer> {
+    /**
+     * Collection of execution data files to analyze.
+     */
+    @InputFiles
+    FileCollection executionData
+
+    /**
+     * Source sets that coverage should be reported for.
+     */
+    @InputFiles
+    FileCollection sourceDirectories
+
+    /**
+     * Source sets that coverage should be reported for.
+     */
+    @InputFiles
+    FileCollection classDirectories
+
+    /**
+     * Additional class dirs that coverage data should be reported for.
+     */
+    @Optional
+    @InputFiles
+    FileCollection additionalClassDirs
+
+    /**
+     * Additional source dirs for the classes coverage data is being reported for.
+     */
+    @Optional
+    @InputFiles
+    FileCollection additionalSourceDirs
+
+    @Nested
+    private final JacocoReportsContainerImpl reports
+
+    @Inject JacocoReport(Instantiator instantiator) {
+        reports = instantiator.newInstance(JacocoReportsContainerImpl, this)
+        onlyIf { getExecutionData().every { it.exists() } }
+    }
+
+    @TaskAction
+    void generate() {
+        getAnt().taskdef(name: 'jacocoReport', classname: 'org.jacoco.ant.ReportTask', classpath: getJacocoClasspath().asPath)
+        getAnt().jacocoReport {
+            executiondata {
+                getExecutionData().addToAntBuilder(getAnt(), 'resources')
+            }
+            structure(name: getProject().getName()) {
+                classfiles {
+                    getAllClassDirs().filter { it.exists() }.addToAntBuilder(getAnt(), 'resources')
+                }
+                sourcefiles {
+                    getAllSourceDirs().filter { it.exists() }.addToAntBuilder(getAnt(), 'resources')
+                }
+            }
+            if(reports.html.isEnabled()) {
+                html(destdir: reports.html.destination)
+            }
+            if(reports.xml.isEnabled()) {
+                xml(destfile: reports.xml.destination)
+            }
+            if(reports.csv.isEnabled()) {
+                csv(destfile: reports.csv.destination)
+            }
+        }
+    }
+
+    /**
+     * Adds execution data files to be used during coverage
+     * analysis.
+     * @param files one or more files to add
+     */
+    void executionData(Object... files) {
+        if (this.executionData == null) {
+            this.executionData = getProject().files(files)
+        } else {
+            this.executionData += getProject().files(files)
+        }
+    }
+
+    /**
+     * Adds execution data generated by a task to the list
+     * of those used during coverage analysis. Only tasks
+     * with a {@link JacocoTaskExtension} will be included;
+     * all others will be ignored.
+     * @param tasks one or more tasks to add
+     */
+    void executionData(Task... tasks) {
+        tasks.each { task ->
+            JacocoTaskExtension extension = task.extensions.findByType(JacocoTaskExtension)
+            if (extension != null) {
+                executionData({ extension.destinationFile })
+                mustRunAfter(task)
+            }
+        }
+    }
+
+    /**
+     * Adds execution data generated by the given tasks to
+     * the list of those used during coverage analysis.
+     * Only tasks with a {@link JacocoTaskExtension} will
+     * be included; all others will be ignored.
+     * @param tasks one or more tasks to add
+     */
+    void executionData(TaskCollection tasks) {
+        tasks.all { executionData(it) }
+    }
+
+    /**
+     * Gets the class directories that coverage will
+     * be reported for. All classes in these directories
+     * will be included in the report.
+     * @return class dirs to report coverage of
+     */
+    FileCollection getAllClassDirs() {
+        def additionalDirs = getAdditionalClassDirs()
+        if (additionalDirs == null) {
+            return classDirectories
+        }
+        return classDirectories + getAdditionalClassDirs()
+    }
+
+    /**
+     * Gets the source directories for the classes that will
+     * be reported on. Source will be obtained from these
+     * directories only for the classes included in the report.
+     * @return source directories for the classes reported on
+     * @see #getAllClassDirs()
+     */
+    FileCollection getAllSourceDirs() {
+        def additionalDirs = getAdditionalSourceDirs()
+        if (additionalDirs == null) {
+            return sourceDirectories
+        }
+        return sourceDirectories + getAdditionalSourceDirs()
+    }
+
+    /**
+     * Adds a source set to the list to be reported on. The
+     * output of this source set will be used as classes to
+     * include in the report. The source for this source set
+     * will be used for any classes included in the report.
+     * @param sourceSets one or more source sets to report on
+     */
+    void sourceSets(SourceSet... sourceSets) {
+        getProject().afterEvaluate {
+            sourceSets.each { sourceSet ->
+                if (this.sourceDirectories == null) {
+                    this.sourceDirectories = getProject().files(sourceSet.allJava.getSrcDirs())
+                } else {
+                    this.sourceDirectories = this.sourceDirectories + getProject().files(sourceSet.allJava.getSrcDirs())
+                }
+                if (this.classDirectories == null) {
+                    this.classDirectories = sourceSet.output
+                } else {
+                    this.classDirectories = this.classDirectories + sourceSet.output
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds additional class directories to those
+     * that will be included in the report.
+     * @param dirs one or more directories containing
+     * classes to report coverage of
+     */
+    void additionalClassDirs(File... dirs) {
+        additionalClassDirs(getProject().files(dirs))
+    }
+
+    /**
+     * Adds additional class directories to those
+     * that will be included in the report.
+     * @param dirs a {@code FileCollection} of directories
+     * containing classes to report coverage of
+     */
+    void additionalClassDirs(FileCollection dirs) {
+        if (this.additionalClassDirs == null) {
+            this.additionalClassDirs = dirs
+        } else {
+            this.additionalClassDirs += dirs
+        }
+    }
+
+    /**
+     * Adds additional source directories to be used
+     * for any classes included in the report.
+     * @param dirs one or more directories containing
+     * source files for the classes included in the report
+     */
+    void additionalSourceDirs(File... dirs) {
+        additionalSourceDirs(getProject().files(dirs))
+    }
+
+    /**
+     * Adds additional source directories to be used
+     * for any classes included in the report.
+     * @param dirs a {@code FileCollection} of directories
+     * containing source files for the classes included in
+     * the report
+     */
+    void additionalSourceDirs(FileCollection dirs) {
+        if (this.additionalSourceDirs == null) {
+            this.additionalSourceDirs = dirs
+        } else {
+            this.additionalSourceDirs += dirs
+        }
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     */
+    JacocoReportsContainer reports(Closure closure) {
+        reports.configure(closure)
+    }
+
+    /**
+     * Returns the reports to be generated by this task.
+     */
+    JacocoReportsContainer getReports() {
+        return reports
+    }
+
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoReportsContainer.java b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoReportsContainer.java
new file mode 100644
index 0000000..907af98
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/JacocoReportsContainer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.tasks;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.reporting.*;
+
+/**
+ * The reporting configuration for the the {@link JacocoReport} task.
+ */
+ at Incubating
+public interface JacocoReportsContainer extends ReportContainer<Report> {
+    /**
+     * The JaCoCo HTML report
+     *
+     * @return The JaCoCo HTML report
+     */
+    DirectoryReport getHtml();
+
+    /**
+     * The JaCoCo (single file) XML report
+     *
+     * @return The JaCoCo (single file) XML report
+     */
+    SingleFileReport getXml();
+
+    /**
+     * The JaCoCo (single file) CSV report
+     *
+     * @return The JaCoCo (single file) CSV report
+     */
+    SingleFileReport getCsv();
+}
diff --git a/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/package-info.java b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/package-info.java
new file mode 100644
index 0000000..67becd9
--- /dev/null
+++ b/subprojects/jacoco/src/main/groovy/org/gradle/testing/jacoco/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 to work with the JaCoCo code coverage library.
+ */
+package org.gradle.testing.jacoco.tasks;
\ No newline at end of file
diff --git a/subprojects/jacoco/src/main/resources/META-INF/gradle-plugins/jacoco.properties b/subprojects/jacoco/src/main/resources/META-INF/gradle-plugins/jacoco.properties
new file mode 100644
index 0000000..6d71042
--- /dev/null
+++ b/subprojects/jacoco/src/main/resources/META-INF/gradle-plugins/jacoco.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.testing.jacoco.plugins.JacocoPlugin
diff --git a/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy b/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy
new file mode 100644
index 0000000..4a2a990
--- /dev/null
+++ b/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy
@@ -0,0 +1,60 @@
+/* 
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.JavaExec
+import org.gradle.api.tasks.testing.Test
+import org.gradle.testing.jacoco.tasks.JacocoReport
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class JacocoPluginSpec extends Specification {
+    Project project = TestUtil.createRootProject()
+
+    def setup() {
+        project.apply plugin: 'jacoco'
+    }
+
+    def 'jacoco plugin adds coverage report for test task when java plugin applied'() {
+        project.file("src/main/java").mkdirs()
+        when:
+        project.apply plugin:"java"
+        and:
+        project.evaluate()
+        then:
+        project.test.extensions.getByType(JacocoTaskExtension) != null
+        project.jacocoTestReport instanceof JacocoReport
+        project.jacocoTestReport.sourceDirectories*.absolutePath == project.files("src/main/java")*.absolutePath
+        project.jacocoTestReport.classDirectories == project.sourceSets.main.output
+    }
+
+    def 'jacoco applied to specific JavaExec task'() {
+        given:
+        JavaExec task = project.tasks.create('exec', JavaExec)
+        when:
+        project.jacoco.applyTo(task)
+        then:
+        task.extensions.getByType(JacocoTaskExtension) != null
+    }
+
+    def 'jacoco applied to Test task'() {
+        given:
+        Test task = project.tasks.create('customTest', Test)
+        expect:
+        task.extensions.getByType(JacocoTaskExtension) != null
+    }
+}
diff --git a/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoTaskExtensionSpec.groovy b/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoTaskExtensionSpec.groovy
new file mode 100644
index 0000000..da8c566
--- /dev/null
+++ b/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoTaskExtensionSpec.groovy
@@ -0,0 +1,99 @@
+/* 
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jacoco.plugins
+
+import org.gradle.internal.jacoco.JacocoAgentJar
+import org.gradle.process.JavaForkOptions
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class JacocoTaskExtensionSpec extends Specification {
+    JacocoAgentJar agent = Mock()
+    JavaForkOptions task = Mock()
+    JacocoTaskExtension extension = new JacocoTaskExtension(agent, task)
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    def 'asJvmArg with default arguments assembles correct string'() {
+        setup:
+        agent.supportsJmx() >> true
+        agent.jar >> temporaryFolder.file('fakeagent.jar')
+        task.getWorkingDir() >> temporaryFolder.file(".")
+        expect:
+        extension.asJvmArg == "-javaagent:fakeagent.jar=append=true,dumponexit=true,output=file,jmx=false"
+    }
+
+    def 'supports jacocoagent with no jmx support'() {
+        given:
+        agent.supportsJmx() >> false
+        agent.jar >> temporaryFolder.file('fakeagent.jar')
+        task.getWorkingDir() >> temporaryFolder.file("workingDir")
+
+        expect:
+        extension.asJvmArg == "-javaagent:../fakeagent.jar=append=true,dumponexit=true,output=file"
+    }
+
+    def 'asJvmArg with all arguments assembles correct string'() {
+        given:
+        agent.supportsJmx() >> true
+        agent.jar >> temporaryFolder.file('workingDir/subfolder/fakeagent.jar')
+        task.getWorkingDir() >> temporaryFolder.file("workingDir")
+
+        extension.with {
+            destinationFile = temporaryFolder.file('build/jacoco/fake.exec')
+            append = false
+            includes = ['org.*', '*.?acoco*']
+            excludes = ['org.?joberstar']
+            excludeClassLoaders = ['com.sun.*', 'org.fak?.*']
+            sessionId = 'testSession'
+            dumpOnExit = false
+            output = JacocoTaskExtension.Output.TCP_SERVER
+            address = '1.1.1.1'
+            port = 100
+            classDumpFile = temporaryFolder.file('build/jacoco-dump')
+            jmx = true
+        }
+
+        def expected = new StringBuilder().with { builder ->
+            builder << "-javaagent:subfolder/fakeagent.jar="
+            builder << "destfile=../build/jacoco/fake.exec,"
+            builder << "append=false,"
+            builder << "includes=org.*:*.?acoco*,"
+            builder << "excludes=org.?joberstar,"
+            builder << "exclclassloader=com.sun.*:org.fak?.*,"
+            builder << "sessionid=testSession,"
+            builder << "dumponexit=false,"
+            builder << "output=tcpserver,"
+            builder << "address=1.1.1.1,"
+            builder << "port=100,"
+            builder << "classdumpdir=../build/jacoco-dump,"
+            builder << "jmx=true"
+            builder.toString()
+        }
+        expect:
+        extension.asJvmArg == expected
+    }
+
+    def 'asJvmArg fails if agent cannot extract the JAR'() {
+        given:
+        agent.jar >> { throw new Exception() }
+        when:
+        extension.asJvmArg
+        then:
+        thrown Exception
+    }
+
+}
diff --git a/subprojects/javascript/javascript.gradle b/subprojects/javascript/javascript.gradle
index ce4823b..ba7fa2c 100644
--- a/subprojects/javascript/javascript.gradle
+++ b/subprojects/javascript/javascript.gradle
@@ -15,10 +15,10 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile "org.mozilla:rhino:1.7R3"
-    compile "com.google.code.gson:gson:2.2.1" // used by JsHint
+    compile libraries.gson // used by JsHint
     compile "org.simpleframework:simple:4.1.21" // used by http package in envjs
     compile project(':core'), project(":plugins")
     compile libraries.inject
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java
index 011096c..82dbf63 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java
@@ -22,7 +22,7 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler;
 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.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.api.internal.artifacts.repositories.layout.PatternRepositoryLayout;
 
 public class JavaScriptRepositoriesExtension {
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy
index a20e82a..922e817 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy
@@ -54,7 +54,7 @@ class CoffeeScriptBasePlugin implements Plugin<Project> {
     }
 
     private Configuration addJsConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, CoffeeScriptExtension extension) {
-        Configuration configuration = configurations.add(CoffeeScriptExtension.JS_CONFIGURATION_NAME)
+        Configuration configuration = configurations.create(CoffeeScriptExtension.JS_CONFIGURATION_NAME)
         configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
             void execute(ResolvableDependencies resolvableDependencies) {
                 if (configuration.dependencies.empty) {
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 70de3d9..301bf91 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
@@ -83,7 +83,7 @@ class EnvJsPlugin implements Plugin<Project> {
     }
 
     Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, EnvJsExtension extension) {
-        Configuration configuration = configurations.add(EnvJsExtension.CONFIGURATION_NAME)
+        Configuration configuration = configurations.create(EnvJsExtension.CONFIGURATION_NAME)
         configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
             void execute(ResolvableDependencies resolvableDependencies) {
                 if (configuration.dependencies.empty) {
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java
index 6894e78..812b00e 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java
@@ -16,7 +16,7 @@
 
 package org.gradle.plugins.javascript.envjs.http.simple;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.plugins.javascript.envjs.http.HttpFileServer;
 
 import java.io.File;
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java
index 58760ce..8a4f692 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java
@@ -17,7 +17,7 @@
 package org.gradle.plugins.javascript.envjs.http.simple;
 
 import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.plugins.javascript.envjs.http.HttpFileServer;
 import org.gradle.plugins.javascript.envjs.http.HttpFileServerFactory;
 import org.gradle.plugins.javascript.envjs.http.simple.internal.SimpleFileServerContainer;
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy
index 30039b9..767ce78 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy
@@ -58,7 +58,7 @@ class JsHintPlugin implements Plugin<Project> {
     }
 
     Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, JsHintExtension extension) {
-        Configuration configuration = configurations.add(JsHintExtension.CONFIGURATION_NAME)
+        Configuration configuration = configurations.create(JsHintExtension.CONFIGURATION_NAME)
         configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
             void execute(ResolvableDependencies resolvableDependencies) {
                 if (configuration.dependencies.empty) {
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java
index 8f82bf6..8753122 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java
@@ -37,8 +37,11 @@ public class DefaultRhinoWorkerHandle<R extends Serializable, P extends Serializ
         CountDownLatch latch = new CountDownLatch(1);
         Receiver receiver = new Receiver(latch);
         workerProcess.start();
+
         workerProcess.getConnection().addIncoming(RhinoWorkerClientProtocol.class, receiver);
         @SuppressWarnings("unchecked") RhinoClientWorkerProtocol<P> worker = workerProcess.getConnection().addOutgoing(RhinoClientWorkerProtocol.class);
+        workerProcess.getConnection().connect();
+
         worker.process(payload);
 
         try {
@@ -47,6 +50,8 @@ public class DefaultRhinoWorkerHandle<R extends Serializable, P extends Serializ
             throw UncheckedException.throwAsUncheckedException(e);
         }
 
+        workerProcess.waitForStop();
+
         if (receiver.initialisationError != null) {
             throw UncheckedException.throwAsUncheckedException(receiver.initialisationError);
         }
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java
index dc04e0f..7de3316 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java
@@ -40,6 +40,7 @@ public class DefaultRhinoWorkerHandleFactory implements RhinoWorkerHandleFactory
 
     public <R extends Serializable, P extends Serializable> RhinoWorkerHandle<R, P> create(Iterable<File> rhinoClasspath, RhinoWorkerSpec<R, P> workerSpec, LogLevel logLevel, Action<JavaExecSpec> javaExecSpecAction) {
         WorkerProcessBuilder builder = workerProcessBuilderFactory.create();
+        builder.setBaseName("Gradle Rhino Worker");
         builder.setLogLevel(logLevel);
         builder.applicationClasspath(rhinoClasspath);
         builder.sharedPackages("org.mozilla.javascript");
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java
index 60e49cb..5f8b33f 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java
@@ -33,6 +33,7 @@ public class RhinoServer<R extends Serializable, P extends Serializable> impleme
 
     public void execute(WorkerProcessContext context) {
         RhinoWorkerClientProtocol clientHandle = context.getServerConnection().addOutgoing(RhinoWorkerClientProtocol.class);
+        context.getServerConnection().connect();
 
         RhinoWorker<R, P> action;
 
diff --git a/subprojects/jetty/jetty.gradle b/subprojects/jetty/jetty.gradle
index 6f54116..8b8e054 100644
--- a/subprojects/jetty/jetty.gradle
+++ b/subprojects/jetty/jetty.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
index 257ce8a..b7ab533 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
@@ -74,19 +74,12 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
      */
     private File overrideWebXml;
 
-    /**
-     * The interval in seconds to scan the webapp for changes and restart the context if necessary. Ignored if reload is enabled. Disabled by default.
-     */
     private int scanIntervalSeconds;
 
-    /**
-     * reload can be set to either 'automatic' or 'manual' <p/> if 'manual' then the context can be reloaded by a linefeed in the console if 'automatic' then traditional reloading on changed files is
-     * enabled.
-     */
     protected String reload;
 
     /**
-     * Location of a jetty xml configuration file whose contents will be applied before any plugin configuration. Optional.
+     * Location of a jetty XML configuration file whose contents will be applied before any plugin configuration. Optional.
      */
     private File jettyConfig;
 
@@ -200,10 +193,8 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
 
     public void startJettyInternal() {
         ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class);
-        ProgressLogger progressLogger = progressLoggerFactory.newOperation(AbstractJettyRunTask.class);
-        progressLogger.setDescription("Start Jetty server");
-        progressLogger.setShortDescription("Starting Jetty");
-        progressLogger.started();
+        ProgressLogger progressLogger = progressLoggerFactory.newOperation(AbstractJettyRunTask.class)
+                .start("Start Jetty server", "Starting Jetty");
         try {
             setServer(createServer());
 
@@ -268,10 +259,9 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
             progressLogger.completed();
         }
 
-        progressLogger = progressLoggerFactory.newOperation(AbstractJettyRunTask.class);
-        progressLogger.setDescription(String.format("Run Jetty at http://localhost:%d/%s", getHttpPort(), getContextPath()));
-        progressLogger.setShortDescription(String.format("Running at http://localhost:%d/%s", getHttpPort(), getContextPath()));
-        progressLogger.started();
+        progressLogger = progressLoggerFactory.newOperation(AbstractJettyRunTask.class)
+                .start(String.format("Run Jetty at http://localhost:%d/%s", getHttpPort(), getContextPath()),
+                        String.format("Running at http://localhost:%d/%s", getHttpPort(), getContextPath()));
         try {
             // keep the thread going if not in daemon mode
             server.join();
@@ -413,10 +403,22 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
         this.overrideWebXml = overrideWebXml;
     }
 
+    /**
+     * Returns the interval in seconds between scanning the web app for file changes.
+     * If file changes are detected, the web app is reloaded. Only relevant
+     * if {@code reload} is set to {@code "automatic"}. Defaults to {@code 0},
+     * which <em>disables</em> automatic reloading.
+     */
     public int getScanIntervalSeconds() {
         return scanIntervalSeconds;
     }
 
+    /**
+     * Sets the interval in seconds between scanning the web app for file changes.
+     * If file changes are detected, the web app is reloaded. Only relevant
+     * if {@code reload} is set to {@code "automatic"}. Defaults to {@code 0},
+     * which <em>disables</em> automatic reloading.
+     */
     public void setScanIntervalSeconds(int scanIntervalSeconds) {
         this.scanIntervalSeconds = scanIntervalSeconds;
     }
@@ -440,10 +442,30 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
         this.webAppConfig = webAppConfig;
     }
 
+    /**
+     * Returns the reload mode, which is either {@code "automatic"} or {@code "manual"}.
+     *
+     * <p>In automatic mode, the web app is scanned for file changes every n seconds, where n is
+     * determined by the {@code scanIntervalSeconds} property. (Note that {@code scanIntervalSeconds}
+     * defaults to {@code 0}, which <em>disables</em> automatic reloading.) If files changes are
+     * detected, the web app is reloaded.
+     *
+     * <p>In manual mode, the web app is reloaded whenever the Enter key is pressed.
+     */
     public String getReload() {
         return reload;
     }
 
+    /**
+     * Sets the reload mode, which is either {@code "automatic"} or {@code "manual"}.
+     *
+     * <p>In automatic mode, the web app is scanned for file changes every n seconds, where n is
+     * determined by the {@code scanIntervalSeconds} property. (Note that {@code scanIntervalSeconds}
+     * defaults to {@code 0}, which <em>disables</em> automatic reloading.) If files changes are
+     * detected, the web app is reloaded.
+     *
+     * <p>In manual mode, the web app is reloaded whenever the Enter key is pressed.
+     */
     public void setReload(String reload) {
         this.reload = reload;
     }
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
index f08091b..b21d1e8 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
@@ -31,8 +31,6 @@ import java.util.concurrent.Callable;
 /**
  * <p>A {@link Plugin} which extends the {@link WarPlugin} to add tasks which run the web application using an embedded
  * Jetty web container.</p>
- *
- * @author Hans Dockter
  */
 public class JettyPlugin implements Plugin<Project> {
     public static final String JETTY_RUN = "jettyRun";
@@ -74,13 +72,13 @@ public class JettyPlugin implements Plugin<Project> {
             }
         });
 
-        JettyRunWar jettyRunWar = project.getTasks().add(JETTY_RUN_WAR, JettyRunWar.class);
+        JettyRunWar jettyRunWar = project.getTasks().create(JETTY_RUN_WAR, JettyRunWar.class);
         jettyRunWar.setDescription("Assembles the webapp into a war and deploys it to Jetty.");
         jettyRunWar.setGroup(WarPlugin.WEB_APP_GROUP);
     }
 
     private void configureJettyStop(Project project, final JettyPluginConvention jettyConvention) {
-        JettyStop jettyStop = project.getTasks().add(JETTY_STOP, JettyStop.class);
+        JettyStop jettyStop = project.getTasks().create(JETTY_STOP, JettyStop.class);
         jettyStop.setDescription("Stops Jetty.");
         jettyStop.setGroup(WarPlugin.WEB_APP_GROUP);
         jettyStop.getConventionMapping().map("stopPort", new Callable<Object>() {
@@ -116,7 +114,7 @@ public class JettyPlugin implements Plugin<Project> {
             }
         });
 
-        JettyRun jettyRun = project.getTasks().add(JETTY_RUN, JettyRun.class);
+        JettyRun jettyRun = project.getTasks().create(JETTY_RUN, JettyRun.class);
         jettyRun.setDescription("Uses your files as and where they are and deploys them to Jetty.");
         jettyRun.setGroup(WarPlugin.WEB_APP_GROUP);
     }
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java
index 749b340..7021820 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java
@@ -17,8 +17,6 @@ package org.gradle.api.plugins.jetty;
 
 /**
  * Convention properties and methods added by the {@link org.gradle.api.plugins.jetty.JettyPlugin}.
- *
- * @author Hans Dockter
  */
 public class JettyPluginConvention {
     private Integer stopPort;
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
index 440ccde..18d0d29 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
@@ -53,8 +53,6 @@ import java.util.Set;
  * automatically performing a hot redeploy when necessary. This allows the developer to concentrate on coding changes to
  * the project using their IDE of choice and have those changes immediately and transparently reflected in the running
  * web container, eliminating development time that is wasted on rebuilding, reassembling and redeploying. </p>
- *
- * @author janb
  */
 public class JettyRun extends AbstractJettyRunTask {
     private static Logger logger = LoggerFactory.getLogger(JettyRun.class);
@@ -75,7 +73,7 @@ public class JettyRun extends AbstractJettyRunTask {
     private File webXml;
 
     /**
-     * Root directory for all html/jsp etc files.
+     * Root directory for all HTML/JSP etc files.
      */
     private File webAppSourceDirectory;
 
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
index 3456432..92ff0bd 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
@@ -48,10 +48,8 @@ public class JettyStop extends ConventionTask {
             throw new InvalidUserDataException("Please specify a valid stopKey");
         }
 
-        ProgressLogger progressLogger = getServices().get(ProgressLoggerFactory.class).newOperation(JettyStop.class);
-        progressLogger.setDescription("Stop Jetty server");
-        progressLogger.setShortDescription("Stopping Jetty");
-        progressLogger.started();
+        ProgressLogger progressLogger = getServices().get(ProgressLoggerFactory.class).newOperation(JettyStop.class)
+                .start("Stop Jetty server", "Stopping Jetty");
         try {
             Socket s = new Socket(InetAddress.getByName("127.0.0.1"), getStopPort());
             s.setSoLinger(false, 0);
diff --git a/subprojects/jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy b/subprojects/jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy
index 219ba03..58949c6 100644
--- a/subprojects/jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy
+++ b/subprojects/jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy
@@ -18,14 +18,15 @@ package org.gradle.api.plugins.jetty
 import org.gradle.api.Project
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.WarPlugin
-import org.gradle.util.HelperUtil
+import org.gradle.api.tasks.TaskDependencyMatchers
+import org.gradle.util.TestUtil
 import org.junit.Test
-import static org.gradle.util.Matchers.*
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
 public class JettyPluginTest {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
 
     @Test
     public void appliesWarPluginAndAddsConventionToProject() {
@@ -42,12 +43,12 @@ public class JettyPluginTest {
 
         def task = project.tasks[JettyPlugin.JETTY_RUN]
         assertThat(task, instanceOf(JettyRun))
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.httpPort, equalTo(project.httpPort))
 
         task = project.tasks[JettyPlugin.JETTY_RUN_WAR]
         assertThat(task, instanceOf(JettyRunWar))
-        assertThat(task, dependsOn(WarPlugin.WAR_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(WarPlugin.WAR_TASK_NAME))
         assertThat(task.httpPort, equalTo(project.httpPort))
 
         task = project.tasks[JettyPlugin.JETTY_STOP]
@@ -59,12 +60,12 @@ public class JettyPluginTest {
     public void addsMappingToNewJettyTasks() {
         new JettyPlugin().apply(project)
 
-        def task = project.tasks.add('customRun', JettyRun)
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        def task = project.tasks.create('customRun', JettyRun)
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.httpPort, equalTo(project.httpPort))
 
-        task = project.tasks.add('customWar', JettyRunWar)
-        assertThat(task, dependsOn(WarPlugin.WAR_TASK_NAME))
+        task = project.tasks.create('customWar', JettyRunWar)
+        assertThat(task, TaskDependencyMatchers.dependsOn(WarPlugin.WAR_TASK_NAME))
         assertThat(task.httpPort, equalTo(project.httpPort))
     }
 }
diff --git a/subprojects/language-base/language-base.gradle b/subprojects/language-base/language-base.gradle
new file mode 100644
index 0000000..290970f
--- /dev/null
+++ b/subprojects/language-base/language-base.gradle
@@ -0,0 +1,7 @@
+dependencies {
+    compile libraries.groovy
+    compile project(":core")
+}
+
+useClassycle()
+useTestFixtures()
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/Binary.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/Binary.java
new file mode 100644
index 0000000..7838a17
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/Binary.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A physical binary artifact, which can run on a particular platform or runtime.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface Binary extends BuildableModelElement, Named {
+    /**
+     * Returns a human-consumable display name for this binary.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/BinaryContainer.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/BinaryContainer.java
new file mode 100644
index 0000000..20d7f1a
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/BinaryContainer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base;
+
+import org.gradle.api.*;
+
+/**
+ * A container for project binaries, which represent physical artifacts that can run on a particular platform or runtime.
+ * Added to a project by the {@link org.gradle.language.base.plugins.LanguageBasePlugin}.
+ */
+ at Incubating
+public interface BinaryContainer extends ExtensiblePolymorphicDomainObjectContainer<Binary> {}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/BuildableModelElement.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/BuildableModelElement.java
new file mode 100644
index 0000000..6233355
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/BuildableModelElement.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Incubating;
+import org.gradle.api.Task;
+
+/**
+ * A model element that is directly buildable.
+ * Such an element mirrors a specified lifecycle task in the DAG, and can accept dependencies which are then associated with the lifecycle task.
+ */
+ at Incubating
+public interface BuildableModelElement extends Buildable {
+    /**
+     * Associates a 'lifecycle' task with the construction of this element.
+     */
+    void setLifecycleTask(Task lifecycleTask);
+
+    /**
+     * Adds a task that is required for the construction of this element.
+     * A task added this way is then added as a dependency of the associated lifecycle task.
+     */
+    void builtBy(Object... tasks);
+
+    boolean hasBuildDependencies();
+}
\ No newline at end of file
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/FunctionalSourceSet.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/FunctionalSourceSet.java
new file mode 100644
index 0000000..93a59eb
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/FunctionalSourceSet.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base;
+
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * A container holding {@link LanguageSourceSet}s with a similar function
+ * (production code, test code, etc.).
+ */
+ at Incubating
+public interface FunctionalSourceSet extends ExtensiblePolymorphicDomainObjectContainer<LanguageSourceSet>, Named {}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/LanguageSourceSet.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/LanguageSourceSet.java
new file mode 100644
index 0000000..e10639c
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/LanguageSourceSet.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.api.Task;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A set of sources for a programming language.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface LanguageSourceSet extends Named, BuildableModelElement {
+    // TODO: do we want to keep using SourceDirectorySet in the new API?
+    // would feel more natural if dirs could be added directly to LanguageSourceSet
+    // could also think about extending SourceDirectorySet
+
+    /**
+     * The source files.
+     */
+    SourceDirectorySet getSource();
+
+    /**
+     * Configure the sources
+     */
+    void source(Action<? super SourceDirectorySet> config);
+
+    // TODO:DAZ Maybe add this as an extension property, and only in domains where it is handled (currently in native-binaries)
+    void generatedBy(Task generatorTask);
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/ProjectSourceSet.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/ProjectSourceSet.java
new file mode 100644
index 0000000..fabe493
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/ProjectSourceSet.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * A container of {@link FunctionalSourceSet}s. Added to a project by the
+ * {@link org.gradle.language.base.plugins.LanguageBasePlugin}.
+ */
+ at Incubating
+public interface ProjectSourceSet extends NamedDomainObjectContainer<FunctionalSourceSet> {}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/AbstractBuildableModelElement.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/AbstractBuildableModelElement.java
new file mode 100644
index 0000000..206a2b8
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/AbstractBuildableModelElement.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.language.base.BuildableModelElement;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class AbstractBuildableModelElement implements BuildableModelElement {
+    private final DefaultTaskDependency buildDependencies = new DefaultTaskDependency();
+    private Task lifecycleTask;
+
+    public void setLifecycleTask(Task lifecycleTask) {
+        this.lifecycleTask = lifecycleTask;
+        lifecycleTask.dependsOn(buildDependencies);
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return new TaskDependency() {
+            public Set<? extends Task> getDependencies(Task other) {
+                if (lifecycleTask == null) {
+                    return buildDependencies.getDependencies(other);
+                }
+                return Collections.singleton(lifecycleTask);
+            }
+        };
+    }
+
+    public void builtBy(Object... tasks) {
+        buildDependencies.add(tasks);
+    }
+
+    public boolean hasBuildDependencies() {
+        // TODO:DAZ There must be a better way to get independent of a Task
+        return buildDependencies.getDependencies(lifecycleTask).size() > 0;
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/AbstractLanguageSourceSet.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/AbstractLanguageSourceSet.java
new file mode 100644
index 0000000..800ddcb
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/AbstractLanguageSourceSet.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.language.base.FunctionalSourceSet;
+
+public abstract class AbstractLanguageSourceSet extends AbstractBuildableModelElement implements LanguageSourceSetInternal {
+    private final String name;
+    private final String fullName;
+    private final String displayName;
+    private final SourceDirectorySet source;
+    private boolean generated;
+    private Task generatorTask;
+
+    public AbstractLanguageSourceSet(String name, FunctionalSourceSet parent, String typeName, SourceDirectorySet source) {
+        this.name = name;
+        this.fullName = parent.getName() + StringUtils.capitalize(name);
+        this.displayName = String.format("%s '%s:%s'", typeName, parent.getName(), name);
+        this.source = source;
+        super.builtBy(source.getBuildDependencies());
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getFullName() {
+        return fullName;
+    }
+
+    @Override
+    public void builtBy(Object... tasks) {
+        generated = true;
+        super.builtBy(tasks);
+    }
+
+    public void generatedBy(Task generatorTask) {
+        this.generatorTask = generatorTask;
+    }
+
+    public Task getGeneratorTask() {
+        return generatorTask;
+    }
+
+    public boolean getMayHaveSources() {
+        // TODO:DAZ This doesn't take into account build dependencies of the SourceDirectorySet.
+        // Should just ditch SourceDirectorySet from here since it's not really a great model, and drags in too much baggage.
+        return generated || !source.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public void source(Action<? super SourceDirectorySet> config) {
+        config.execute(getSource());
+    }
+
+    public SourceDirectorySet getSource() {
+        return source;
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryInternal.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryInternal.java
new file mode 100644
index 0000000..d45b4a5
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryInternal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal;
+
+import org.gradle.language.base.Binary;
+
+public interface BinaryInternal extends Binary {
+    BinaryNamingScheme getNamingScheme();
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryNamingScheme.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryNamingScheme.java
new file mode 100644
index 0000000..ff588bd
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryNamingScheme.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal;
+
+import org.gradle.api.Nullable;
+
+import java.util.List;
+
+public interface BinaryNamingScheme {
+    String getLifecycleTaskName();
+
+    String getTaskName(@Nullable String verb);
+
+    String getTaskName(@Nullable String verb, @Nullable String target);
+
+    String getOutputDirectoryBase();
+
+    String getDescription();
+
+    List<String> getVariantDimensions();
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryNamingSchemeBuilder.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryNamingSchemeBuilder.java
new file mode 100644
index 0000000..b805814
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/BinaryNamingSchemeBuilder.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.internal;
+
+public interface BinaryNamingSchemeBuilder {
+    BinaryNamingSchemeBuilder withComponentName(String name);
+
+    BinaryNamingSchemeBuilder withTypeString(String newTypeString);
+
+    BinaryNamingSchemeBuilder withVariantDimension(String dimension);
+
+    BinaryNamingScheme build();
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryContainer.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryContainer.java
new file mode 100644
index 0000000..33a9ed0
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.internal;
+
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.Binary;
+
+public class DefaultBinaryContainer extends DefaultPolymorphicDomainObjectContainer<Binary> implements BinaryContainer {
+    public DefaultBinaryContainer(Instantiator instantiator) {
+        super(Binary.class, instantiator);
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryNamingScheme.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryNamingScheme.java
new file mode 100644
index 0000000..9c8ce21
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryNamingScheme.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.util.GUtil;
+
+import java.util.List;
+
+public class DefaultBinaryNamingScheme implements BinaryNamingScheme {
+    final String parentName;
+    final String typeString;
+    final String dimensionPrefix;
+    final List<String> dimensions;
+
+    public DefaultBinaryNamingScheme(String parentName, String typeString, List<String> dimensions) {
+        this.parentName = parentName;
+        this.typeString = typeString;
+        this.dimensions = dimensions;
+        this.dimensionPrefix = createPrefix(dimensions);
+    }
+
+    private String createPrefix(List<String> dimensions) {
+        if (dimensions.isEmpty()) {
+            return "";
+        }
+        return makeName(dimensions.toArray(new String[dimensions.size()]));
+    }
+
+    public String getLifecycleTaskName() {
+        return getTaskName(null, null);
+    }
+
+    public String getOutputDirectoryBase() {
+        StringBuilder builder = new StringBuilder(makeName(parentName, typeString));
+        if (dimensionPrefix.length() > 0) {
+            builder.append('/');
+            builder.append(dimensionPrefix);
+        }
+        return builder.toString();
+    }
+
+    public String getDescription() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(GUtil.toWords(typeString));
+        builder.append(" '");
+        builder.append(parentName);
+        for (String dimension : dimensions) {
+            builder.append(':');
+            builder.append(dimension);
+        }
+        builder.append(':');
+        appendUncapitalized(builder, typeString);
+        builder.append("'");
+        return builder.toString();
+    }
+
+    public List<String> getVariantDimensions() {
+        return dimensions;
+    }
+
+    public String getTaskName(@Nullable String verb) {
+        return getTaskName(verb, null);
+    }
+
+    public String getTaskName(@Nullable String verb, @Nullable String target) {
+        return makeName(verb, dimensionPrefix, parentName, typeString, target);
+    }
+
+    public String makeName(String... words) {
+        StringBuilder builder = new StringBuilder();
+        for (String word : words) {
+            if (word == null || word.length() == 0) {
+                continue;
+            }
+            if (builder.length() == 0) {
+                appendUncapitalized(builder, word);
+            } else {
+                appendCapitalized(builder, word);
+            }
+        }
+        return builder.toString();
+    }
+
+    private void appendCapitalized(StringBuilder builder, String word) {
+        if (word.length() == 0) {
+            return;
+        }
+        builder.append(Character.toTitleCase(word.charAt(0))).append(word.substring(1));
+    }
+
+    private void appendUncapitalized(StringBuilder builder, String word) {
+        if (word.length() == 0) {
+            return;
+        }
+        builder.append(Character.toLowerCase(word.charAt(0))).append(word.substring(1));
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryNamingSchemeBuilder.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryNamingSchemeBuilder.java
new file mode 100644
index 0000000..92eb49d
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultBinaryNamingSchemeBuilder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultBinaryNamingSchemeBuilder implements BinaryNamingSchemeBuilder {
+    private final String parentName;
+    private final String typeString;
+    private final List<String> dimensions;
+
+    public DefaultBinaryNamingSchemeBuilder() {
+        this.parentName = null;
+        this.typeString = "";
+        this.dimensions = new ArrayList<String>();
+    }
+
+    public DefaultBinaryNamingSchemeBuilder(BinaryNamingScheme basis) {
+        assert basis instanceof DefaultBinaryNamingScheme;
+        DefaultBinaryNamingScheme clone = (DefaultBinaryNamingScheme) basis;
+        this.parentName = clone.parentName;
+        this.typeString = clone.typeString;
+        this.dimensions = clone.dimensions;
+    }
+
+    private DefaultBinaryNamingSchemeBuilder(String parentName, String typeString, List<String> dimensions) {
+        this.parentName = parentName;
+        this.typeString = typeString;
+        this.dimensions = dimensions;
+    }
+
+    public BinaryNamingSchemeBuilder withComponentName(String name) {
+        return new DefaultBinaryNamingSchemeBuilder(name, typeString, dimensions);
+    }
+
+    public BinaryNamingSchemeBuilder withTypeString(String newTypeString) {
+        return new DefaultBinaryNamingSchemeBuilder(parentName, newTypeString, dimensions);
+    }
+
+    public BinaryNamingSchemeBuilder withVariantDimension(String dimension) {
+        List<String> newDimensions = new ArrayList<String>(dimensions);
+        newDimensions.add(dimension);
+        return new DefaultBinaryNamingSchemeBuilder(parentName, typeString, newDimensions);
+    }
+
+    public BinaryNamingScheme build() {
+        return new DefaultBinaryNamingScheme(parentName, typeString, dimensions);
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java
new file mode 100644
index 0000000..f00b8c3
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.internal;
+
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultFunctionalSourceSet extends DefaultPolymorphicDomainObjectContainer<LanguageSourceSet> implements FunctionalSourceSet {
+    private final String name;
+
+    public DefaultFunctionalSourceSet(String name, Instantiator instantiator) {
+        super(LanguageSourceSet.class, instantiator);
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("source set '%s'", name);
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultProjectSourceSet.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultProjectSourceSet.java
new file mode 100644
index 0000000..4e7d9b4
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/DefaultProjectSourceSet.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.internal;
+
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultProjectSourceSet extends AbstractNamedDomainObjectContainer<FunctionalSourceSet> implements ProjectSourceSet {
+    public DefaultProjectSourceSet(Instantiator instantiator) {
+        super(FunctionalSourceSet.class, instantiator);
+    }
+
+    @Override
+    protected FunctionalSourceSet doCreate(String name) {
+        return getInstantiator().newInstance(DefaultFunctionalSourceSet.class, name, getInstantiator());
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/LanguageSourceSetInternal.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/LanguageSourceSetInternal.java
new file mode 100644
index 0000000..ecde133
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/internal/LanguageSourceSetInternal.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.internal;
+
+import org.gradle.api.Task;
+import org.gradle.language.base.LanguageSourceSet;
+
+public interface LanguageSourceSetInternal extends LanguageSourceSet {
+
+    // TODO:DAZ This doesn't feel right. Need some better modelling.
+    // TODO:DAZ Use this for ClassDirectoryBinary task names as well: need to hack the naming scheme to maintain back-compatibility
+    /**
+     * A unique name for this source set across all functional source sets.
+     */
+    String getFullName();
+
+    /**
+     * Return true if the source set contains sources, or if the source set is generated.
+     */
+    boolean getMayHaveSources();
+
+    // TODO:DAZ Maybe use an extension property
+    Task getGeneratorTask();
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/package-info.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/package-info.java
new file mode 100644
index 0000000..363df29
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * General purpose types for language support.
+ */
+ at Incubating
+package org.gradle.language.base;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/plugins/LanguageBasePlugin.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/plugins/LanguageBasePlugin.java
new file mode 100644
index 0000000..1a65640
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/plugins/LanguageBasePlugin.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.plugins;
+
+import org.gradle.api.*;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.internal.BinaryInternal;
+import org.gradle.language.base.internal.DefaultBinaryContainer;
+import org.gradle.language.base.internal.DefaultProjectSourceSet;
+import org.gradle.model.ModelRules;
+
+import javax.inject.Inject;
+
+/**
+ * Base plugin for language support.
+ *
+ * Adds a {@link org.gradle.language.base.BinaryContainer} named {@code binaries} to the project.
+ * Adds a {@link org.gradle.language.base.ProjectSourceSet} named {@code sources} to the project.
+ */
+ at Incubating
+public class LanguageBasePlugin implements Plugin<Project> {
+    public static final String BUILD_GROUP = "build";
+
+    private final Instantiator instantiator;
+    private final ModelRules modelRules;
+
+    @Inject
+    public LanguageBasePlugin(Instantiator instantiator, ModelRules modelRules) {
+        this.instantiator = instantiator;
+        this.modelRules = modelRules;
+    }
+
+    public void apply(final Project target) {
+        target.getExtensions().create("sources", DefaultProjectSourceSet.class, instantiator);
+        final BinaryContainer binaries = target.getExtensions().create("binaries", DefaultBinaryContainer.class, instantiator);
+
+        modelRules.register("binaries", BinaryContainer.class, new Factory<BinaryContainer>() {
+            public BinaryContainer create() {
+                return binaries;
+            }
+        });
+
+        binaries.withType(BinaryInternal.class).all(new Action<BinaryInternal>() {
+            public void execute(BinaryInternal binary) {
+                Task binaryLifecycleTask = target.task(binary.getNamingScheme().getLifecycleTaskName());
+                binaryLifecycleTask.setGroup(BUILD_GROUP);
+                binaryLifecycleTask.setDescription(String.format("Assembles %s.", binary));
+                binary.setLifecycleTask(binaryLifecycleTask);
+            }
+        });
+    }
+}
diff --git a/subprojects/language-base/src/main/groovy/org/gradle/language/base/plugins/package-info.java b/subprojects/language-base/src/main/groovy/org/gradle/language/base/plugins/package-info.java
new file mode 100644
index 0000000..32d27e2
--- /dev/null
+++ b/subprojects/language-base/src/main/groovy/org/gradle/language/base/plugins/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Base plugins for language support.
+ */
+ at Incubating
+package org.gradle.language.base.plugins;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/language-base/src/main/resources/META-INF/gradle-plugins/language-base.properties b/subprojects/language-base/src/main/resources/META-INF/gradle-plugins/language-base.properties
new file mode 100644
index 0000000..c8d282e
--- /dev/null
+++ b/subprojects/language-base/src/main/resources/META-INF/gradle-plugins/language-base.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.language.base.plugins.LanguageBasePlugin
diff --git a/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/BuildableModelElementTest.groovy b/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/BuildableModelElementTest.groovy
new file mode 100644
index 0000000..e4a582e
--- /dev/null
+++ b/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/BuildableModelElementTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.base.internal
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Task
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+public class BuildableModelElementTest extends Specification {
+
+    def element = new TestBuildableModelElement()
+    def dependedOn1 = Stub(Task)
+    def dependedOn2 = Stub(Task)
+    def lifecycleTask = TestUtil.createTask(DefaultTask)
+
+    def "has direct dependencies with no lifecycle task set"() {
+        when:
+        element.builtBy(dependedOn1, dependedOn2)
+
+        then:
+        element.getBuildDependencies().getDependencies(Stub(Task)) == [dependedOn1, dependedOn2] as Set
+    }
+
+    def "has intervening lifecycle task as dependency when set"() {
+        when:
+        element.builtBy(dependedOn1)
+        element.setLifecycleTask(lifecycleTask)
+        element.builtBy(dependedOn2)
+
+        then:
+        element.getBuildDependencies().getDependencies(Stub(Task)) == [lifecycleTask] as Set
+
+        and:
+        lifecycleTask.getTaskDependencies().getDependencies(Stub(Task)) == [dependedOn1, dependedOn2] as Set
+    }
+
+    class TestBuildableModelElement extends AbstractBuildableModelElement {
+
+    }
+}
diff --git a/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/DefaultBinaryNamingSchemeTest.groovy b/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/DefaultBinaryNamingSchemeTest.groovy
new file mode 100644
index 0000000..6f843d0
--- /dev/null
+++ b/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/DefaultBinaryNamingSchemeTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal
+
+import spock.lang.Specification
+
+class DefaultBinaryNamingSchemeTest extends Specification {
+    def "generates task names for native binaries"() {
+        expect:
+        def namingScheme = createNamingScheme(parentName, type, dimensions)
+        namingScheme.getTaskName(verb, target) == taskName
+
+        where:
+        parentName | type   | dimensions     | verb       | target    | taskName
+        "test"     | ""     | []             | null       | null      | "test"
+        "test"     | "type" | []             | null       | null      | "testType"
+        "test"     | "type" | []             | null       | "classes" | "testTypeClasses"
+        "test"     | ""     | []             | null       | "classes" | "testClasses"
+        "test"     | "type" | []             | "assemble" | null      | "assembleTestType"
+        "test"     | "type" | []             | "compile"  | "java"    | "compileTestTypeJava"
+        "test"     | "type" | ["one", "two"] | null       | null      | "oneTwoTestType"
+        "test"     | "type" | ["one", "two"] | null       | "classes" | "oneTwoTestTypeClasses"
+        "test"     | "type" | ["one", "two"] | "assemble" | null      | "assembleOneTwoTestType"
+        "test"     | "type" | ["one", "two"] | "compile"  | "java"    | "compileOneTwoTestTypeJava"
+    }
+
+    def "generates task name with extended inputs"() {
+        expect:
+        def namingScheme = createNamingScheme("theBinary", "theType", ['firstDimension', 'secondDimension'])
+        namingScheme.getTaskName("theVerb", "theTarget") == "theVerbFirstDimensionSecondDimensionTheBinaryTheTypeTheTarget"
+    }
+
+    def "generates base name and output directory"() {
+        def namingScheme = createNamingScheme(parentName, "", dimensions)
+
+        expect:
+        namingScheme.getLifecycleTaskName() == lifecycleName
+        namingScheme.getOutputDirectoryBase() == outputDir
+
+        where:
+        parentName    | dimensions                                 | lifecycleName                               | outputDir
+        "test"        | []                                         | "test"                                      | "test"
+        "test"        | ["one", "two"]                             | "oneTwoTest"                                | "test/oneTwo"
+        "mainLibrary" | ["enterpriseEdition", "osx_x64", "static"] | "enterpriseEditionOsx_x64StaticMainLibrary" | "mainLibrary/enterpriseEditionOsx_x64Static"
+        "mainLibrary" | ["EnterpriseEdition", "Osx_x64", "Static"] | "enterpriseEditionOsx_x64StaticMainLibrary" | "mainLibrary/enterpriseEditionOsx_x64Static"
+    }
+
+    def "generates description"() {
+        def namingScheme = createNamingScheme(parentName, typeName, dimensions)
+
+        expect:
+        namingScheme.getDescription() == lifecycleName
+
+        where:
+        parentName | typeName        | dimensions     | lifecycleName
+        "parent"   | "Executable"    | []             | "executable 'parent:executable'"
+        "parent"   | "SharedLibrary" | []             | "shared library 'parent:sharedLibrary'"
+        "parent"   | "SharedLibrary" | ["one"]        | "shared library 'parent:one:sharedLibrary'"
+        "parent"   | "SharedLibrary" | ["one", "two"] | "shared library 'parent:one:two:sharedLibrary'"
+    }
+
+    private BinaryNamingScheme createNamingScheme(def parentName, def type, def dimensions) {
+        return new DefaultBinaryNamingScheme(parentName, type, dimensions)
+    }
+}
diff --git a/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/DefaultFunctionalSourceSetTest.groovy b/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/DefaultFunctionalSourceSetTest.groovy
new file mode 100644
index 0000000..0b861b7
--- /dev/null
+++ b/subprojects/language-base/src/test/groovy/org/gradle/language/base/internal/DefaultFunctionalSourceSetTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.base.internal
+
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+class DefaultFunctionalSourceSetTest extends Specification {
+    def "has reasonable string representation"() {
+        def sourceSet = new DefaultFunctionalSourceSet("main", Stub(Instantiator))
+
+        expect:
+        sourceSet.toString() == /source set 'main'/
+    }
+}
diff --git a/subprojects/language-jvm/language-jvm.gradle b/subprojects/language-jvm/language-jvm.gradle
new file mode 100644
index 0000000..19b6518
--- /dev/null
+++ b/subprojects/language-jvm/language-jvm.gradle
@@ -0,0 +1,7 @@
+dependencies {
+    compile libraries.groovy
+    compile project(":languageBase")
+    compile project(":core")
+}
+
+useClassycle()
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/JavaSourceSet.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/JavaSourceSet.java
new file mode 100644
index 0000000..1b698a1
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/JavaSourceSet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.java;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.jvm.Classpath;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * A set of sources passed to the Java compiler.
+ */
+ at Incubating
+public interface JavaSourceSet extends LanguageSourceSet {
+    Classpath getCompileClasspath();
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/internal/DefaultJavaSourceSet.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/internal/DefaultJavaSourceSet.java
new file mode 100644
index 0000000..1d52355
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/internal/DefaultJavaSourceSet.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.java.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.internal.AbstractLanguageSourceSet;
+import org.gradle.language.java.JavaSourceSet;
+import org.gradle.language.jvm.Classpath;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DefaultJavaSourceSet extends AbstractLanguageSourceSet implements JavaSourceSet {
+    private final Classpath compileClasspath;
+
+    public DefaultJavaSourceSet(String name, SourceDirectorySet source, Classpath compileClasspath, FunctionalSourceSet parent) {
+        super(name, parent, "Java source", source);
+        this.compileClasspath = compileClasspath;
+    }
+
+    public Classpath getCompileClasspath() {
+        return compileClasspath;
+    }
+
+
+    public TaskDependency getBuildDependencies() {
+        return new TaskDependency() {
+            public Set<? extends Task> getDependencies(Task task) {
+                Set<Task> dependencies = new HashSet<Task>();
+                dependencies.addAll(compileClasspath.getBuildDependencies().getDependencies(task));
+                dependencies.addAll(getSource().getBuildDependencies().getDependencies(task));
+                return dependencies;
+            }
+        };
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/package-info.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/package-info.java
new file mode 100644
index 0000000..2acd1a6
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/java/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 for Java language support.
+ */
+ at Incubating
+package org.gradle.language.java;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/ClassDirectoryBinary.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/ClassDirectoryBinary.java
new file mode 100644
index 0000000..e8b852b
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/ClassDirectoryBinary.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm;
+
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.Incubating;
+import org.gradle.language.base.Binary;
+import org.gradle.language.base.LanguageSourceSet;
+
+import java.io.File;
+
+/**
+ * An exploded binary containing resources and compiled class files.
+ */
+// TODO: maybe we need to allow additional dirs like SourceSetOutput does
+// (esp. for backwards compatibility). Wonder if it's still necessary to distinguish
+// between classes and resources dirs, instead of just maintaining a collection of dirs.
+// As far as generated resources are concerned, it might be better to model
+// them as an additional (Buildable) ResourceSet.
+ at Incubating
+public interface ClassDirectoryBinary extends Binary {
+    File getClassesDir();
+    void setClassesDir(File dir);
+    File getResourcesDir();
+    void setResourcesDir(File dir);
+    DomainObjectCollection<LanguageSourceSet> getSource();
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/Classpath.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/Classpath.java
new file mode 100644
index 0000000..3603ba3
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/Classpath.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Incubating;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * A collection of files to be used as a class path.
+ */
+ at Incubating
+public interface Classpath extends Buildable {
+    FileCollection getFiles();
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/ResourceSet.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/ResourceSet.java
new file mode 100644
index 0000000..8e6a489
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/ResourceSet.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm;
+
+import org.gradle.api.Incubating;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * A set of resource files.
+ */
+ at Incubating
+public interface ResourceSet extends LanguageSourceSet {}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/ClassDirectoryBinaryNamingScheme.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/ClassDirectoryBinaryNamingScheme.java
new file mode 100644
index 0000000..e7fd064
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/ClassDirectoryBinaryNamingScheme.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.jvm.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.util.GUtil;
+
+import java.util.Collections;
+import java.util.List;
+
+class ClassDirectoryBinaryNamingScheme implements BinaryNamingScheme {
+    private final String baseName;
+    private final String collapsedName;
+
+    public ClassDirectoryBinaryNamingScheme(String baseName) {
+        this.baseName = baseName;
+        this.collapsedName = collapseMain(this.baseName);
+    }
+
+    private static String collapseMain(String name) {
+        return name.equals("main") ? "" : name;
+    }
+
+    public String getDescription() {
+        return String.format("classes '%s'", baseName);
+    }
+
+    public List<String> getVariantDimensions() {
+        return Collections.emptyList();
+    }
+
+    public String getLifecycleTaskName() {
+        return getTaskName(null, "classes");
+    }
+
+    public String getTaskName(@Nullable String verb) {
+        return getTaskName(verb, null);
+    }
+
+    public String getTaskName(@Nullable String verb, @Nullable String target) {
+        String name = baseName;
+        if (target != null) {
+            name = collapsedName;
+        }
+        return GUtil.toLowerCamelCase(String.format("%s %s %s", nullToEmpty(verb), name, nullToEmpty(target)));
+    }
+
+    private String nullToEmpty(String input) {
+        return input == null ? "" : input;
+    }
+
+    public String getOutputDirectoryBase() {
+        return baseName;
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultClassDirectoryBinary.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultClassDirectoryBinary.java
new file mode 100644
index 0000000..c7485c0
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultClassDirectoryBinary.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.internal;
+
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.AbstractBuildableModelElement;
+import org.gradle.language.base.internal.BinaryInternal;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.language.jvm.ClassDirectoryBinary;
+
+import java.io.File;
+
+public class DefaultClassDirectoryBinary extends AbstractBuildableModelElement implements ClassDirectoryBinary, BinaryInternal {
+    private final BinaryNamingScheme namingScheme;
+    private final DomainObjectCollection<LanguageSourceSet> source = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
+    private final String name;
+    private File classesDir;
+    private File resourcesDir;
+
+    public DefaultClassDirectoryBinary(String name) {
+        this.name = name;
+        this.namingScheme = new ClassDirectoryBinaryNamingScheme(removeClassesSuffix(name));
+    }
+
+    private String removeClassesSuffix(String name) {
+        if (name.endsWith("Classes")) {
+            return name.substring(0, name.length() - 7);
+        }
+        return name;
+    }
+
+    public BinaryNamingScheme getNamingScheme() {
+        return namingScheme;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public File getClassesDir() {
+        return classesDir;
+    }
+
+    public void setClassesDir(File classesDir) {
+        this.classesDir = classesDir;
+    }
+
+    public File getResourcesDir() {
+        return resourcesDir;
+    }
+
+    public void setResourcesDir(File resourcesDir) {
+        this.resourcesDir = resourcesDir;
+    }
+
+    public DomainObjectCollection<LanguageSourceSet> getSource() {
+        return source;
+    }
+
+    public String getDisplayName() {
+        return namingScheme.getDescription();
+    }
+
+    public String toString() {
+        return namingScheme.getDescription();
+    }
+
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultClasspath.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultClasspath.java
new file mode 100644
index 0000000..3c8385b
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultClasspath.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.language.jvm.Classpath;
+import org.gradle.api.tasks.TaskDependency;
+
+public class DefaultClasspath implements Classpath {
+    private final FileCollection files;
+
+    public DefaultClasspath(FileResolver fileResolver, TaskResolver taskResolver) {
+        files = new DefaultConfigurableFileCollection(fileResolver, taskResolver);
+    }
+
+    public FileCollection getFiles() {
+        return files;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return files.getBuildDependencies();
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultResourceSet.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultResourceSet.java
new file mode 100644
index 0000000..8cc82ad
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/DefaultResourceSet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.internal;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.internal.AbstractLanguageSourceSet;
+import org.gradle.language.jvm.ResourceSet;
+
+public class DefaultResourceSet extends AbstractLanguageSourceSet implements ResourceSet {
+
+    public DefaultResourceSet(String name, SourceDirectorySet source, FunctionalSourceSet parent) {
+        super(name, parent, "resources", source);
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/SimpleStaleClassCleaner.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/SimpleStaleClassCleaner.java
new file mode 100644
index 0000000..297da22
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/SimpleStaleClassCleaner.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.internal;
+
+import org.gradle.api.internal.TaskOutputsInternal;
+
+import java.io.File;
+
+public class SimpleStaleClassCleaner extends StaleClassCleaner {
+    private final TaskOutputsInternal taskOutputs;
+    private boolean didWork;
+
+    public SimpleStaleClassCleaner(TaskOutputsInternal taskOutputs) {
+        this.taskOutputs = taskOutputs;
+    }
+
+    @Override
+    public void execute() {
+        String prefix = getDestinationDir().getAbsolutePath() + File.separator;
+        for (File f : taskOutputs.getPreviousFiles()) {
+            if (f.getAbsolutePath().startsWith(prefix)) {
+                didWork |= f.delete();
+            }
+        }
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/StaleClassCleaner.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/StaleClassCleaner.java
new file mode 100644
index 0000000..e781dec
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/internal/StaleClassCleaner.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.internal;
+
+import org.gradle.api.file.FileCollection;
+
+import java.io.File;
+
+public abstract class StaleClassCleaner {
+    private File destinationDir;
+    FileCollection source;
+
+    public abstract void execute();
+
+    public FileCollection getSource() {
+        return source;
+    }
+
+    public void setSource(FileCollection source) {
+        this.source = source;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/package-info.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/package-info.java
new file mode 100644
index 0000000..84ff9f3
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 for support for JVM languages.
+ */
+ at Incubating
+package org.gradle.language.jvm;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/plugins/JvmLanguagePlugin.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/plugins/JvmLanguagePlugin.java
new file mode 100644
index 0000000..56c415f
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/plugins/JvmLanguagePlugin.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.tasks.Copy;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.language.base.internal.BinaryInternal;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.language.base.plugins.LanguageBasePlugin;
+import org.gradle.language.jvm.ClassDirectoryBinary;
+import org.gradle.language.jvm.ResourceSet;
+import org.gradle.language.jvm.internal.DefaultClassDirectoryBinary;
+import org.gradle.language.jvm.internal.DefaultResourceSet;
+import org.gradle.language.jvm.tasks.ProcessResources;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * Base plugin for JVM language support. Applies the {@link org.gradle.language.base.plugins.LanguageBasePlugin}.
+ * Registers the {@link org.gradle.language.jvm.ClassDirectoryBinary} element type for the {@link org.gradle.language.base.BinaryContainer}.
+ * Adds a lifecycle task named {@code classes} for each {@link org.gradle.language.jvm.ClassDirectoryBinary}.
+ * Registers the {@link org.gradle.language.jvm.ResourceSet} element type for each {@link org.gradle.language.base.FunctionalSourceSet} added to {@link org.gradle.language.base.ProjectSourceSet}.
+ * Adds a {@link Copy} task named {@code processXYZResources} for each {@link org.gradle.language.jvm.ResourceSet} added to a {@link org.gradle.language.jvm.ClassDirectoryBinary}.
+ */
+ at Incubating
+public class JvmLanguagePlugin implements Plugin<Project> {
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+
+    @Inject
+    public JvmLanguagePlugin(Instantiator instantiator, FileResolver fileResolver) {
+        this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
+    }
+
+    public void apply(final Project target) {
+        target.getPlugins().apply(LanguageBasePlugin.class);
+
+        ProjectSourceSet projectSourceSet = target.getExtensions().getByType(ProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(ResourceSet.class, new NamedDomainObjectFactory<ResourceSet>() {
+                    public ResourceSet create(String name) {
+                        return instantiator.newInstance(DefaultResourceSet.class, name,
+                                instantiator.newInstance(DefaultSourceDirectorySet.class, name, fileResolver), functionalSourceSet);
+                    }
+                });
+            }
+        });
+
+        BinaryContainer binaryContainer = target.getExtensions().getByType(BinaryContainer.class);
+        binaryContainer.registerFactory(ClassDirectoryBinary.class, new NamedDomainObjectFactory<ClassDirectoryBinary>() {
+            public ClassDirectoryBinary create(String name) {
+                return instantiator.newInstance(DefaultClassDirectoryBinary.class, name);
+            };
+        });
+
+        binaryContainer.withType(ClassDirectoryBinary.class).all(new Action<ClassDirectoryBinary>() {
+            public void execute(final ClassDirectoryBinary binary) {
+                final BinaryNamingScheme namingScheme = ((BinaryInternal) binary).getNamingScheme();
+                ConventionMapping conventionMapping = new DslObject(binary).getConventionMapping();
+                conventionMapping.map("classesDir", new Callable<File>() {
+                    public File call() throws Exception {
+                        return new File(new File(target.getBuildDir(), "classes"), namingScheme.getOutputDirectoryBase());
+                    }
+                });
+                binary.getSource().withType(ResourceSet.class).all(new Action<ResourceSet>() {
+                    public void execute(ResourceSet resourceSet) {
+                        // TODO: handle case where binary has multiple ResourceSet's
+                        Copy resourcesTask = target.getTasks().create(namingScheme.getTaskName("process", "resources"), ProcessResources.class);
+                        resourcesTask.setDescription(String.format("Processes %s.", resourceSet));
+                        new DslObject(resourcesTask).getConventionMapping().map("destinationDir", new Callable<File>() {
+                            public File call() throws Exception {
+                                return binary.getResourcesDir();
+                            }
+                        });
+                        binary.builtBy(resourcesTask);
+                        resourcesTask.from(resourceSet.getSource());
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/plugins/package-info.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/plugins/package-info.java
new file mode 100644
index 0000000..4465713
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/plugins/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Base plugins that add support for JVM languages.
+ */
+ at Incubating
+package org.gradle.language.jvm.plugins;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/tasks/ProcessResources.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/tasks/ProcessResources.java
new file mode 100644
index 0000000..a41e9de
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/tasks/ProcessResources.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.language.jvm.tasks;
+
+import org.gradle.api.tasks.Copy;
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner;
+import org.gradle.language.jvm.internal.StaleClassCleaner;
+
+/**
+ * Copies resources from their source to their target directory, potentially processing them.
+ * Makes sure no stale resources remain in the target directory.
+ */
+public class ProcessResources extends Copy {
+
+    @Override
+    protected void copy() {
+        StaleClassCleaner cleaner = new SimpleStaleClassCleaner(getOutputs());
+        cleaner.setDestinationDir(getDestinationDir());
+        cleaner.execute();
+        super.copy();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/tasks/package-info.java b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/tasks/package-info.java
new file mode 100644
index 0000000..74d24d7
--- /dev/null
+++ b/subprojects/language-jvm/src/main/groovy/org/gradle/language/jvm/tasks/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 support for JVM languages.
+ */
+ at Incubating
+package org.gradle.language.jvm.tasks;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/language-jvm/src/test/groovy/org/gradle/language/java/internal/DefaultJavaSourceSetTest.groovy b/subprojects/language-jvm/src/test/groovy/org/gradle/language/java/internal/DefaultJavaSourceSetTest.groovy
new file mode 100644
index 0000000..d55403e
--- /dev/null
+++ b/subprojects/language-jvm/src/test/groovy/org/gradle/language/java/internal/DefaultJavaSourceSetTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.java.internal
+
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.language.jvm.Classpath
+import org.gradle.language.base.FunctionalSourceSet
+import spock.lang.Specification
+
+class DefaultJavaSourceSetTest extends Specification {
+    def "has useful String representation"() {
+        def functionalSourceSet = Stub(FunctionalSourceSet) {
+            getName() >> "mainX"
+        }
+        def resourceSet = new DefaultJavaSourceSet("javaX", Stub(SourceDirectorySet), Stub(Classpath), functionalSourceSet)
+
+        expect:
+        resourceSet.toString() == "Java source 'mainX:javaX'"
+    }
+}
diff --git a/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/ClassDirectoryBinaryNamingSchemeTest.groovy b/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/ClassDirectoryBinaryNamingSchemeTest.groovy
new file mode 100644
index 0000000..c00313e
--- /dev/null
+++ b/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/ClassDirectoryBinaryNamingSchemeTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.language.jvm.internal
+
+import spock.lang.Specification
+
+class ClassDirectoryBinaryNamingSchemeTest extends Specification {
+
+    def "generates task names for class directory binaries"() {
+        expect:
+        def namer = new ClassDirectoryBinaryNamingScheme(name)
+        namer.getTaskName(verb, target) == taskName
+
+        where:
+        name   | verb      | target      | taskName
+        "main" | null      | null        | "main"
+        "main" | "compile" | null        | "compileMain"
+        "main" | null      | "resources" | "resources"
+        "main" | "compile" | "java"      | "compileJava"
+
+        "test" | null      | null        | "test"
+        "test" | "compile" | null        | "compileTest"
+        "test" | null      | "resources" | "testResources"
+        "test" | "compile" | "java"      | "compileTestJava"
+    }
+
+    def "generates base name and output directory"() {
+        def namer = new ClassDirectoryBinaryNamingScheme(baseName)
+
+        expect:
+        namer.getLifecycleTaskName() == lifecycleName
+        namer.getOutputDirectoryBase() == outputDir
+
+        where:
+        baseName | lifecycleName | outputDir
+        "main"   | "classes"     | "main"
+        "test"   | "testClasses" | "test"
+    }
+}
diff --git a/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/DefaultClassDirectoryBinaryTest.groovy b/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/DefaultClassDirectoryBinaryTest.groovy
new file mode 100644
index 0000000..19fcb12
--- /dev/null
+++ b/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/DefaultClassDirectoryBinaryTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.language.jvm.internal;
+
+import spock.lang.Specification;
+
+public class DefaultClassDirectoryBinaryTest extends Specification {
+    def "uses short task names for binary with name 'mainClasses'"() {
+        when:
+        def binary = new DefaultClassDirectoryBinary("mainClasses")
+
+        then:
+        binary.name == 'mainClasses'
+
+        and:
+        binary.namingScheme.lifecycleTaskName == 'classes'
+        binary.namingScheme.getTaskName(null, null) == 'main'
+        binary.namingScheme.getTaskName("compile", null) == 'compileMain'
+        binary.namingScheme.getTaskName(null, "groovy") == 'groovy'
+        binary.namingScheme.getTaskName("compile", "groovy") == 'compileGroovy'
+    }
+
+    def "uses medium task names for binary with name 'otherClasses'"() {
+        when:
+        def binary = new DefaultClassDirectoryBinary("otherClasses")
+
+        then:
+        binary.name == 'otherClasses'
+
+        and:
+        binary.namingScheme.lifecycleTaskName == 'otherClasses'
+        binary.namingScheme.getTaskName(null, null) == 'other'
+        binary.namingScheme.getTaskName("compile", null) == 'compileOther'
+        binary.namingScheme.getTaskName(null, "groovy") == 'otherGroovy'
+        binary.namingScheme.getTaskName("compile", "groovy") == 'compileOtherGroovy'
+    }
+
+    def "uses long task names for binary with name 'otherBinary'"() {
+        when:
+        def binary = new DefaultClassDirectoryBinary("otherBinary")
+
+        then:
+        binary.name == 'otherBinary'
+
+        and:
+        binary.namingScheme.lifecycleTaskName == 'otherBinaryClasses'
+        binary.namingScheme.getTaskName(null, null) == 'otherBinary'
+        binary.namingScheme.getTaskName("compile", null) == 'compileOtherBinary'
+        binary.namingScheme.getTaskName(null, "groovy") == 'otherBinaryGroovy'
+        binary.namingScheme.getTaskName("compile", "groovy") == 'compileOtherBinaryGroovy'
+    }
+
+    def "has a useful toString() representation"() {
+        expect:
+        def binary = new DefaultClassDirectoryBinary(name)
+        binary.toString() == displayName
+        binary.displayName == displayName
+
+        where:
+        name    | displayName
+        'mainClasses'  | 'classes \'main\''
+        'otherClasses' | 'classes \'other\''
+        'otherBinary' | 'classes \'otherBinary\''
+    }
+}
diff --git a/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/DefaultResourceSetTest.groovy b/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/DefaultResourceSetTest.groovy
new file mode 100644
index 0000000..04cfd61
--- /dev/null
+++ b/subprojects/language-jvm/src/test/groovy/org/gradle/language/jvm/internal/DefaultResourceSetTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.language.jvm.internal
+
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.language.base.FunctionalSourceSet
+import spock.lang.Specification
+
+class DefaultResourceSetTest extends Specification {
+    def "has useful String representation"() {
+        def functionalSourceSet = Stub(FunctionalSourceSet) {
+            getName() >> "mainX"
+        }
+        def resourceSet = new DefaultResourceSet("resourcesX", Stub(SourceDirectorySet), functionalSourceSet)
+
+        expect:
+        resourceSet.toString() == "resources 'mainX:resourcesX'"
+    }
+}
diff --git a/subprojects/launcher/launcher.gradle b/subprojects/launcher/launcher.gradle
index acdde9a..b8c43d9 100644
--- a/subprojects/launcher/launcher.gradle
+++ b/subprojects/launcher/launcher.gradle
@@ -3,8 +3,6 @@ configurations {
 }
 
 dependencies {
-    groovy libraries.groovy
-
     compile project(':baseServices')
     compile project(':core')
     compile project(':cli')
@@ -14,6 +12,8 @@ dependencies {
 
     compile libraries.slf4j_api
 
+    testCompile libraries.groovy
+
     startScriptGenerator project(':plugins')
 }
 
@@ -26,10 +26,6 @@ jar {
     }
 }
 
-test {
-    forkEvery = 10
-}
-
 task startScripts(type: StartScriptGenerator) {
     startScriptsDir = new File("$buildDir/startScripts")
     classpath = configurations.startScriptGenerator
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy
index 07371b1..8422e5b 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy
@@ -20,9 +20,6 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.ProjectLifecycleFixture
 import org.junit.Rule
 
-/**
- * by Szczepan Faber, created at: 11/21/12
- */
 class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule ProjectLifecycleFixture fixture = new ProjectLifecycleFixture(executer, temporaryFolder)
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy
index 56af0fc..eb4e769 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import spock.lang.IgnoreIf
 
-/**
- * by Szczepan Faber, created at: 11/21/12
- */
 @IgnoreIf({ GradleContextualExecuter.parallel })
 class EnablingParallelExecutionIntegrationTest extends AbstractIntegrationSpec {
 
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
index d5d3470..c8cfaba 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
@@ -20,15 +20,11 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.AvailableJavaHomes
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 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
- */
 @IgnoreIf( { GradleContextualExecuter.embedded })
 class GradleConfigurabilityIntegrationSpec extends AbstractIntegrationSpec {
 
@@ -57,7 +53,7 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta
         given:
         def javaHome = Jvm.current().javaHome
         def javaLink = file("javaLink")
-        FileSystems.default.createSymbolicLink(javaLink, javaHome)
+        javaLink.createLink(javaHome)
         file("tmp").deleteDir().createDir()
 
         String linkPath = TextUtil.escapeString(javaLink.absolutePath)
@@ -74,7 +70,6 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta
         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:
         file("gradle.properties") << 'org.gradle.jvmargs=-Dsome-prop="i have space"'
@@ -96,14 +91,31 @@ assert inputArgs.find { it.contains('-XX:HeapDumpPath=') }
 """
     }
 
-    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
-    def "honours java home specified in gradle.properties"() {
-        given:
+    def String useAlternativeJavaPath() {
         File javaHome = AvailableJavaHomes.bestAlternative
         String javaPath = TextUtil.escapeString(javaHome.canonicalPath)
         file("gradle.properties") << "org.gradle.java.home=$javaPath"
 
+        return javaPath
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "honours java home specified in gradle.properties"() {
+        given:
+        String javaPath = useAlternativeJavaPath()
+
         expect:
         buildSucceeds "assert System.getProperty('java.home').startsWith('$javaPath')"
     }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null || System.getProperty('java.runtime.version') == null})
+    def "does not alter java.runtime.version"() {
+        given:
+
+        useAlternativeJavaPath()
+        String javaRuntimeVersion = System.getProperty('java.runtime.version')
+
+        expect:
+        buildSucceeds "assert System.getProperty('java.runtime.version') != '${javaRuntimeVersion}'"
+    }
 }
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 41eec24..891f339 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
@@ -23,9 +23,6 @@ import spock.lang.Timeout
 
 import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
 
-/**
- * by Szczepan Faber, created at: 1/20/12
- */
 class DaemonFeedbackIntegrationSpec extends DaemonIntegrationSpec {
     def setup() {
         executer.requireIsolatedDaemons()
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
index 82249cc..8204eab 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
@@ -19,20 +19,21 @@ 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 org.junit.rules.ExternalResource
 import spock.lang.IgnoreIf
 import spock.lang.Issue
 
+import java.nio.ByteBuffer
+import java.nio.channels.ServerSocketChannel
+import java.nio.channels.SocketChannel
+
 import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
 
-/**
- * by Szczepan Faber, created at: 1/20/12
- */
 @IgnoreIf({ !KillProcessAvailability.CAN_KILL })
 class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegrationSpec {
 
-    @Rule HttpServer server = new HttpServer()
+    @Rule TestServer server = new TestServer()
 
     def cleanup() {
         stopDaemonsNow()
@@ -56,7 +57,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
         and:
         //starting some service on the daemon port
         poll {
-            server.start(daemon.port)
+            server.tryStart(daemon.port)
         }
 
         then:
@@ -76,7 +77,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
     }
 
     @Issue("GRADLE-2444")
-    def "stop() behaves if the registry contains connectable port without daemon on the other end"() {
+    def "stop behaves if the registry contains connectable port without daemon on the other end"() {
         when:
         buildSucceeds()
 
@@ -87,7 +88,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
         daemon.waitUntilIdle()
         daemon.kill()
         poll {
-            server.start(daemon.port)
+            server.tryStart(daemon.port)
         }
 
         then:
@@ -125,4 +126,40 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
         analyzer.daemons.size() == 2        //2 daemon participated
         analyzer.registry.all.size() == 1   //only one address in the registry
     }
+
+    private static class TestServer extends ExternalResource {
+        ServerSocketChannel socket;
+        Thread acceptor;
+
+        void tryStart(int port) {
+            socket = ServerSocketChannel.open()
+            socket.socket().bind(new InetSocketAddress(port))
+            acceptor = new Thread() {
+                @Override
+                void run() {
+                    while (true) {
+                        SocketChannel connection
+                        try {
+                            connection = socket.accept()
+                        } catch (IOException e) {
+                            return
+                        }
+                        try {
+                            connection.read(ByteBuffer.allocate(4096))
+                            connection.write(ByteBuffer.wrap("hello".bytes))
+                        } finally {
+                            connection.close()
+                        }
+                    }
+                }
+            }
+            acceptor.start()
+        }
+
+        @Override
+        protected void after() {
+            socket?.close()
+            acceptor?.join()
+        }
+    }
 }
\ 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 b9935db..f3b7ebc 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
@@ -21,9 +21,6 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
 import org.slf4j.LoggerFactory
 
-/**
- * by Szczepan Faber, created at: 2/1/12
- */
 class DaemonIntegrationSpec extends AbstractIntegrationSpec {
 
     String 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 adce874..141a5a3 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
@@ -95,7 +95,7 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
             """)
             builds << executer.start()
         }
-        //TODO SF - rewrite the lifecycle spec so that it uses the TestableDaemon
+        //TODO - rewrite the lifecycle spec so that it uses the TestableDaemon
     }
 
     void completeBuild(buildNum = 0) {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
index 19ce0df..20f43a6 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
@@ -16,9 +16,6 @@
 
 package org.gradle.launcher.daemon
 
-/**
- * by Szczepan Faber, created at: 1/20/12
- */
 class DispachingFailureIntegrationSpec extends DaemonIntegrationSpec {
 
     def "failing build does not make the daemon send corrupted message"() {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
index 9cd957c..c4652d8 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
@@ -22,7 +22,6 @@ import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
 import org.gradle.launcher.exec.DefaultBuildActionParameters
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.internal.provider.ConfiguringBuildAction
-import org.gradle.tooling.internal.provider.ExecuteBuildAction
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -65,7 +64,8 @@ class EmbeddedDaemonSmokeTest extends Specification {
     }
     
     def cleanup() {
-        daemonClientServices?.close()
+        // TODO:ADAM - switch this back on
+//        daemonClientServices?.close()
     }
 
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ExecuteBuildAction.java b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ExecuteBuildAction.java
new file mode 100644
index 0000000..e2d9871
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ExecuteBuildAction.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher.daemon;
+
+import org.gradle.initialization.BuildAction;
+import org.gradle.initialization.BuildController;
+
+import java.io.Serializable;
+
+public class ExecuteBuildAction implements BuildAction<Void>, Serializable {
+    public Void run(BuildController buildController) {
+        buildController.run();
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
index 8f9ad3a..5a193d2 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
@@ -19,6 +19,7 @@ package org.gradle.launcher.daemon
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.AvailableJavaHomes
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.launcher.daemon.client.SingleUseDaemonClient
 import org.gradle.util.TextUtil
 import org.spockframework.runtime.SpockAssertionError
 import org.spockframework.runtime.SpockTimeoutError
@@ -138,6 +139,6 @@ assert System.getProperty('some-prop') == 'some-value'
     }
 
     private def wasForked() {
-        result.output.contains('fork a new JVM')
+        result.output.contains(SingleUseDaemonClient.MESSAGE)
     }
 }
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
index a1ce784..48d8bb7 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy
@@ -19,9 +19,6 @@ package org.gradle.launcher.daemon
 import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.test.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:
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersIntegrationTest.groovy
new file mode 100644
index 0000000..71da04f
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersIntegrationTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher.daemon.configuration
+
+import org.gradle.initialization.BuildLayoutParameters
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class DaemonParametersIntegrationTest extends AbstractIntegrationSpec {
+
+    def honorsGradleUserHomeDir() {
+        setup:
+        def userHomeDir = testDirectory.createDir('userDir')
+        BuildLayoutParameters layoutParams = new BuildLayoutParameters()
+        layoutParams.gradleUserHomeDir = userHomeDir
+        DaemonParameters parametersWithBaseDir = new DaemonParameters(layoutParams)
+
+        when:
+        def result = parametersWithBaseDir.baseDir
+
+        then:
+        result == userHomeDir.file('daemon')
+    }
+}
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 8f44b57..c44a7c4 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
@@ -26,9 +26,6 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * by Szczepan Faber, created at: 2/13/12
- */
 public class DaemonContextParser {
     public static DaemonContext parseFrom(String source) {
         Pattern pattern = Pattern.compile("^.*DefaultDaemonContext\\[uid=([^\\n]+),javaHome=([^\\n]+),daemonRegistryDir=([^\\n]+),pid=([^\\n]+),idleTimeout=(.+?),daemonOpts=([^\\n]+)].*",
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
index 00cb479..943c49d 100644
--- 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
@@ -16,24 +16,27 @@
 
 package org.gradle.launcher.daemon.testing
 
+import org.gradle.initialization.BuildLayoutParameters
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.launcher.daemon.client.DaemonClientServices
+import org.gradle.launcher.daemon.configuration.DaemonParameters
 import org.gradle.launcher.daemon.registry.DaemonRegistry
-import org.gradle.launcher.daemon.registry.DaemonRegistryServices
+import org.gradle.logging.LoggingServiceRegistry
 
-/**
- * by Szczepan Faber, created at: 9/3/12
- */
 class DaemonLogsAnalyzer {
 
     private List<File> daemonLogs
     private File daemonBaseDir
-    private DaemonRegistryServices services
+    private ServiceRegistry 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)
+        DaemonParameters daemonParameters = new DaemonParameters(new BuildLayoutParameters())
+        daemonParameters.setBaseDir(daemonBaseDir)
+        services = new DaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), daemonParameters, new ByteArrayInputStream(new byte[0]))
     }
 
     List<TestableDaemon> getDaemons() {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
index 0a7e85f..5cf7090 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
@@ -18,7 +18,7 @@ package org.gradle.launcher.daemon.testing
 import org.gradle.launcher.daemon.registry.DaemonRegistry
 import org.gradle.internal.concurrent.DefaultExecutorFactory
 import org.gradle.internal.concurrent.StoppableExecutor
-import org.gradle.internal.Stoppable
+import org.gradle.internal.concurrent.Stoppable
 
 import java.util.concurrent.LinkedBlockingQueue
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java
index cb42a3a..3515c06 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java
@@ -18,9 +18,6 @@ package org.gradle.launcher;
 
 import org.gradle.launcher.bootstrap.ProcessBootstrap;
 
-/**
- * @author Steven Devijver, Hans Dockter
- */
 public class GradleMain {
     public static void main(String[] args) throws Exception {
         new ProcessBootstrap().run("org.gradle.launcher.Main", args);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java b/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
index 85b33be..001bead 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
@@ -15,16 +15,14 @@
  */
 package org.gradle.launcher;
 
-import org.gradle.launcher.cli.CommandLineActionFactory;
 import org.gradle.launcher.bootstrap.EntryPoint;
 import org.gradle.launcher.bootstrap.ExecutionListener;
+import org.gradle.launcher.cli.CommandLineActionFactory;
 
 import java.util.Arrays;
 
 /**
  * The main command-line entry-point for Gradle.
- *
- * @author Hans Dockter
  */
 public class Main extends EntryPoint {
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java
index 78b064c..c147c81 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java
@@ -20,9 +20,9 @@ import org.gradle.api.internal.DefaultClassPathProvider;
 import org.gradle.api.internal.DefaultClassPathRegistry;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
 import org.gradle.internal.classpath.ClassPath;
-import org.gradle.util.ClassLoaderFactory;
-import org.gradle.util.DefaultClassLoaderFactory;
-import org.gradle.util.MutableURLClassLoader;
+import org.gradle.internal.classloader.ClassLoaderFactory;
+import org.gradle.internal.classloader.DefaultClassLoaderFactory;
+import org.gradle.internal.classloader.MutableURLClassLoader;
 
 import java.lang.reflect.Method;
 
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 5a1f320..f4dbd76 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,16 +18,22 @@ 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.internal.Actions;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
+import org.gradle.cli.SystemPropertiesCommandLineConverter;
 import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.initialization.BuildLayoutParameters;
 import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.initialization.LayoutCommandLineConverter;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistryBuilder;
+import org.gradle.internal.service.scopes.GlobalScopeServices;
 import org.gradle.launcher.bootstrap.ExecutionListener;
+import org.gradle.launcher.cli.converter.*;
 import org.gradle.launcher.daemon.bootstrap.ForegroundDaemonMain;
 import org.gradle.launcher.daemon.client.DaemonClient;
 import org.gradle.launcher.daemon.client.DaemonClientServices;
@@ -36,46 +42,81 @@ import org.gradle.launcher.daemon.client.StopDaemonClientServices;
 import org.gradle.launcher.daemon.configuration.CurrentProcess;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.configuration.ForegroundDaemonConfiguration;
-import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer;
+import org.gradle.launcher.exec.BuildActionExecuter;
 import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
+import org.gradle.launcher.exec.InProcessBuildActionExecuter;
 
 import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
 
 class BuildActionsFactory implements CommandLineAction {
+
     private static final String FOREGROUND = "foreground";
-    private static final String DAEMON = "daemon";
-    private static final String NO_DAEMON = "no-daemon";
     private static final String STOP = "stop";
+
     private final ServiceRegistry loggingServices;
-    private final CommandLineConverter<StartParameter> startParameterConverter;
-    private final GradlePropertiesConfigurer propertiesConfigurer;
+    private final LayoutCommandLineConverter layoutConverter;
+
+    private final SystemPropertiesCommandLineConverter propertiesConverter;
+    private final LayoutToPropertiesConverter layoutToPropertiesConverter;
+
+    private final PropertiesToStartParameterConverter propertiesToStartParameterConverter;
+    private final DefaultCommandLineConverter commandLineConverter;
+
+    private final DaemonCommandLineConverter daemonConverter;
+    private final PropertiesToDaemonParametersConverter propertiesToDaemonParametersConverter;
 
     BuildActionsFactory(ServiceRegistry loggingServices) {
-        this(loggingServices, new DefaultCommandLineConverter(), new GradlePropertiesConfigurer());
+        this(loggingServices, new DefaultCommandLineConverter());
     }
 
-    BuildActionsFactory(ServiceRegistry loggingServices, CommandLineConverter<StartParameter> commandLineConverter,
-                        GradlePropertiesConfigurer propertiesConfigurer) {
+    BuildActionsFactory(ServiceRegistry loggingServices, DefaultCommandLineConverter commandLineConverter,
+                        DaemonCommandLineConverter daemonConverter, LayoutCommandLineConverter layoutConverter,
+                        SystemPropertiesCommandLineConverter propertiesConverter,
+                        LayoutToPropertiesConverter layoutToPropertiesConverter,
+                        PropertiesToStartParameterConverter propertiesToStartParameterConverter,
+                        PropertiesToDaemonParametersConverter propertiesToDaemonParametersConverter) {
         this.loggingServices = loggingServices;
-        this.startParameterConverter = commandLineConverter;
-        this.propertiesConfigurer = propertiesConfigurer;
+        this.commandLineConverter = commandLineConverter;
+        this.daemonConverter = daemonConverter;
+        this.layoutConverter = layoutConverter;
+        this.propertiesConverter = propertiesConverter;
+        this.layoutToPropertiesConverter = layoutToPropertiesConverter;
+        this.propertiesToStartParameterConverter = propertiesToStartParameterConverter;
+        this.propertiesToDaemonParametersConverter = propertiesToDaemonParametersConverter;
+    }
+
+    private BuildActionsFactory(ServiceRegistry loggingServices, DefaultCommandLineConverter commandLineConverter) {
+        this(loggingServices, commandLineConverter, new DaemonCommandLineConverter(),
+                commandLineConverter.getLayoutConverter(), commandLineConverter.getSystemPropertiesConverter(),
+                new LayoutToPropertiesConverter(), new PropertiesToStartParameterConverter(), new PropertiesToDaemonParametersConverter());
     }
 
     public void configureCommandLineParser(CommandLineParser parser) {
-        startParameterConverter.configure(parser);
+        commandLineConverter.configure(parser);
+        daemonConverter.configure(parser);
 
         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<? super ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
+        BuildLayoutParameters layout = new BuildLayoutParameters();
+        layoutConverter.convert(commandLine, layout);
+
+        Map<String, String> properties = new HashMap<String, String>();
+        layoutToPropertiesConverter.convert(layout, properties);
+        propertiesConverter.convert(commandLine, properties);
+
         StartParameter startParameter = new StartParameter();
-        startParameterConverter.convert(commandLine, startParameter);
-        DaemonParameters daemonParameters = propertiesConfigurer.configureParameters(startParameter);
+        propertiesToStartParameterConverter.convert(properties, startParameter);
+        commandLineConverter.convert(commandLine, startParameter);
+
+        DaemonParameters daemonParameters = new DaemonParameters(layout);
+        propertiesToDaemonParametersConverter.convert(properties, daemonParameters);
+        daemonConverter.convert(commandLine, daemonParameters);
+
         if (commandLine.hasOption(STOP)) {
             return stopAllDaemons(daemonParameters, loggingServices);
         }
@@ -84,7 +125,7 @@ class BuildActionsFactory implements CommandLineAction {
                     daemonParameters.getUid(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout());
             return Actions.toAction(new ForegroundDaemonMain(conf));
         }
-        if (useDaemon(commandLine, daemonParameters)) {
+        if (daemonParameters.isEnabled()) {
             return runBuildWithDaemon(startParameter, daemonParameters, loggingServices);
         }
         if (canUseCurrentProcess(daemonParameters)) {
@@ -99,13 +140,6 @@ class BuildActionsFactory implements CommandLineAction {
         return Actions.toAction(new StopDaemonAction(stopClient));
     }
 
-    private boolean useDaemon(ParsedCommandLine commandLine, DaemonParameters daemonParameters) {
-        boolean useDaemon = daemonParameters.isEnabled();
-        useDaemon = useDaemon || commandLine.hasOption(DAEMON);
-        useDaemon = useDaemon && !commandLine.hasOption(NO_DAEMON);
-        return useDaemon;
-    }
-
     private Action<? 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);
@@ -119,7 +153,13 @@ class BuildActionsFactory implements CommandLineAction {
     }
 
     private Action<? super ExecutionListener> runBuildInProcess(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
-        InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(new DefaultGradleLauncherFactory(loggingServices));
+        ServiceRegistry globalServices = ServiceRegistryBuilder.builder()
+                .displayName("Global services")
+                .parent(loggingServices)
+                .parent(NativeServices.getInstance())
+                .provider(new GlobalScopeServices(false))
+                .build();
+        InProcessBuildActionExecuter executer = new InProcessBuildActionExecuter(globalServices.get(GradleLauncherFactory.class));
         return daemonBuildAction(startParameter, daemonParameters, executer);
     }
 
@@ -139,7 +179,7 @@ class BuildActionsFactory implements CommandLineAction {
         return daemonBuildAction(startParameter, daemonParameters, client);
     }
 
-    private Action<? super ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, GradleLauncherActionExecuter<BuildActionParameters> executer) {
+    private Action<? super ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, BuildActionExecuter<BuildActionParameters> executer) {
         return Actions.toAction(
                 new RunBuildAction(executer, startParameter, SystemProperties.getCurrentDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
     }
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 53ffb1a..0dfcce3 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,7 +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.internal.Actions;
 import org.gradle.cli.CommandLineArgumentException;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.cli.CommandLineParser;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java
index cd49211..078237d 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java
@@ -15,30 +15,22 @@
  */
 package org.gradle.launcher.cli;
 
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.exec.InitializationAware;
+import org.gradle.initialization.BuildAction;
+import org.gradle.initialization.BuildController;
 
 import java.io.Serializable;
 
-public class ExecuteBuildAction implements GradleLauncherAction<Void>, InitializationAware, Serializable {
+public class ExecuteBuildAction implements BuildAction<Void>, Serializable {
     private final StartParameter startParameter;
 
     public ExecuteBuildAction(StartParameter startParameter) {
         this.startParameter = startParameter;
     }
 
-    public StartParameter configureStartParameter() {
-        return startParameter;
-    }
-
-    public BuildResult run(GradleLauncher launcher) {
-        return launcher.run();
-    }
-
-    public Void getResult() {
+    public Void run(BuildController buildController) {
+        buildController.setStartParameter(startParameter);
+        buildController.run();
         return null;
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
index fc3f9b0..93abb47 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,7 +17,7 @@
 package org.gradle.launcher.cli;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
 import org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
index 548fe47..4b98551 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
@@ -19,7 +19,7 @@ import org.gradle.StartParameter;
 import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.launcher.exec.BuildActionParameters;
 import org.gradle.launcher.exec.DefaultBuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.launcher.exec.BuildActionExecuter;
 import org.gradle.util.GUtil;
 
 import java.io.File;
@@ -27,7 +27,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 public class RunBuildAction implements Runnable {
-    private final GradleLauncherActionExecuter<BuildActionParameters> executer;
+    private final BuildActionExecuter<BuildActionParameters> executer;
     private final StartParameter startParameter;
     private final File currentDir;
     private final BuildClientMetaData clientMetaData;
@@ -35,7 +35,7 @@ public class RunBuildAction implements Runnable {
     private final Map<String, String> systemProperties;
     private final Map<String, String> envVariables;
 
-    public RunBuildAction(GradleLauncherActionExecuter<BuildActionParameters> executer, StartParameter startParameter, File currentDir, BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables) {
+    public RunBuildAction(BuildActionExecuter<BuildActionParameters> executer, StartParameter startParameter, File currentDir, BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables) {
         this.executer = executer;
         this.startParameter = startParameter;
         this.currentDir = currentDir;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/DaemonCommandLineConverter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/DaemonCommandLineConverter.java
new file mode 100644
index 0000000..80166cf
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/DaemonCommandLineConverter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter;
+
+import org.gradle.cli.AbstractCommandLineConverter;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.initialization.BuildLayoutParameters;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+
+public class DaemonCommandLineConverter extends AbstractCommandLineConverter<DaemonParameters> {
+
+    private static final String DAEMON = "daemon";
+    private static final String NO_DAEMON = "no-daemon";
+
+    protected DaemonParameters newInstance() {
+        return new DaemonParameters(new BuildLayoutParameters());
+    }
+
+    public DaemonParameters convert(ParsedCommandLine args, DaemonParameters target) throws CommandLineArgumentException {
+        if (args.hasOption(NO_DAEMON)) {
+            return target.setEnabled(false);
+        }
+        if (args.hasOption(DAEMON)) {
+            return target.setEnabled(true);
+        }
+        return target;
+    }
+
+    public void configure(CommandLineParser parser) {
+        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.allowOneOf(DAEMON, NO_DAEMON);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/LayoutToPropertiesConverter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/LayoutToPropertiesConverter.java
new file mode 100644
index 0000000..cc31c85
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/LayoutToPropertiesConverter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter;
+
+import org.gradle.api.Project;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.initialization.BuildLayoutParameters;
+import org.gradle.initialization.layout.BuildLayout;
+import org.gradle.initialization.layout.BuildLayoutFactory;
+import org.gradle.launcher.daemon.configuration.GradleProperties;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+public class LayoutToPropertiesConverter {
+    public Map<String, String> convert(BuildLayoutParameters layout, Map<String, String> properties) {
+        configureFromBuildDir(layout.getProjectDir(), layout.getSearchUpwards(), properties);
+        configureFromGradleUserHome(layout.getGradleUserHomeDir(), properties);
+        properties.putAll((Map) System.getProperties());
+        return properties;
+    }
+
+    private void configureFromGradleUserHome(File gradleUserHomeDir, Map<String, String> result) {
+        maybeConfigureFrom(new File(gradleUserHomeDir, Project.GRADLE_PROPERTIES), result);
+    }
+
+    private void configureFromBuildDir(File currentDir, boolean searchUpwards, Map<String, String> result) {
+        BuildLayoutFactory factory = new BuildLayoutFactory();
+        BuildLayout layout = factory.getLayoutFor(currentDir, searchUpwards);
+        maybeConfigureFrom(new File(layout.getRootDirectory(), Project.GRADLE_PROPERTIES), result);
+    }
+
+    private void maybeConfigureFrom(File propertiesFile, Map<String, String> result) {
+        if (!propertiesFile.isFile()) {
+            return;
+        }
+
+        Properties properties = new Properties();
+        try {
+            FileInputStream inputStream = new FileInputStream(propertiesFile);
+            try {
+                properties.load(inputStream);
+            } finally {
+                inputStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        for (Object key : properties.keySet()) {
+            if (GradleProperties.ALL.contains(key.toString())) {
+                result.put(key.toString(), properties.get(key).toString());
+            }
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/PropertiesToDaemonParametersConverter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/PropertiesToDaemonParametersConverter.java
new file mode 100644
index 0000000..33c425b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/PropertiesToDaemonParametersConverter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter;
+
+import org.gradle.api.GradleException;
+import org.gradle.internal.jvm.JavaHomeException;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.process.internal.JvmOptions;
+
+import java.io.File;
+import java.util.Map;
+
+import static org.gradle.launcher.daemon.configuration.GradleProperties.*;
+
+public class PropertiesToDaemonParametersConverter {
+    public void convert(Map<String, String> properties, DaemonParameters target) {
+        String prop = properties.get(IDLE_TIMEOUT_PROPERTY);
+        if (prop != null) {
+            try {
+                target.setIdleTimeout(new Integer(prop));
+            } catch (NumberFormatException e) {
+                throw new GradleException(String.format("Unable to parse %s property. The value should be an int but is: %s", IDLE_TIMEOUT_PROPERTY, prop));
+            }
+        }
+
+        prop = properties.get(JVM_ARGS_PROPERTY);
+        if (prop != null) {
+            target.setJvmArgs(JvmOptions.fromString(prop));
+        }
+
+        prop = properties.get(JAVA_HOME_PROPERTY);
+        if (prop != null) {
+            File javaHome = new File(prop);
+            if (!javaHome.isDirectory()) {
+                throw new GradleException(String.format("Java home supplied via '%s' is invalid. Invalid directory: %s", JAVA_HOME_PROPERTY, prop));
+            }
+            try {
+                Jvm.forHome(javaHome);
+            } catch (JavaHomeException e) {
+                throw new GradleException(String.format("Java home supplied via '%s' seems to be invalid: %s", JAVA_HOME_PROPERTY, prop));
+            }
+            target.setJavaHome(javaHome);
+        }
+
+        prop = properties.get(DAEMON_BASE_DIR_PROPERTY);
+        if (prop != null) {
+            target.setBaseDir(new File(prop));
+        }
+
+        target.setEnabled(isTrue(properties.get(DAEMON_ENABLED_PROPERTY)));
+        target.setDebug(isTrue(properties.get(DEBUG_MODE_PROPERTY)));
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/PropertiesToStartParameterConverter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/PropertiesToStartParameterConverter.java
new file mode 100644
index 0000000..361b87c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/converter/PropertiesToStartParameterConverter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter;
+
+import org.gradle.StartParameter;
+import org.gradle.launcher.daemon.configuration.GradleProperties;
+
+import java.util.Map;
+
+import static org.gradle.launcher.daemon.configuration.GradleProperties.isTrue;
+
+public class PropertiesToStartParameterConverter {
+    public StartParameter convert(Map<String, String> properties, StartParameter startParameter) {
+        startParameter.setConfigureOnDemand(isTrue(properties.get(GradleProperties.CONFIGURE_ON_DEMAND_PROPERTY)));
+
+        String parallel = properties.get(GradleProperties.PARALLEL_PROPERTY);
+        if (isTrue(parallel)) {
+            startParameter.setParallelThreadCount(-1);
+        }
+        return startParameter;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java
index 0686b98..634e4ff 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java
@@ -23,9 +23,6 @@ import org.gradle.process.internal.ExecHandleBuilder;
 import java.io.File;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 5/7/12
- */
 public class DaemonExecHandleBuilder {
 
     ExecHandleBuilder builder = new ExecHandleBuilder();
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java
index 54bfe35..eb49e9b 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java
@@ -22,9 +22,6 @@ import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.process.ExecResult;
 
-/**
- * by Szczepan Faber, created at: 1/19/12
- */
 public class DaemonGreeter {
     private final DocumentationRegistry documentationRegistry;
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java
index c1f7b36..9b623d4 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java
@@ -27,9 +27,6 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Scanner;
 
-/**
-* by Szczepan Faber, created at: 4/28/12
-*/
 public class DaemonOutputConsumer implements StreamsHandler {
 
     private final static Logger LOGGER = Logging.getLogger(DaemonOutputConsumer.class);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java
index fc76f56..3a8e61c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java
@@ -24,9 +24,6 @@ import org.gradle.launcher.daemon.logging.DaemonMessages;
 import java.io.File;
 import java.io.PrintStream;
 
-/**
- * by Szczepan Faber, created at: 4/10/12
- */
 public class DaemonStartupCommunication {
 
     private static final String DELIM = ";:"; //this very simple delim should be safe for any kind of path.
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 ad0bf4d..24958eb 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
@@ -19,7 +19,7 @@ import org.gradle.api.GradleException;
 import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.initialization.BuildAction;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.id.IdGenerator;
@@ -27,8 +27,8 @@ import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.protocol.*;
+import org.gradle.launcher.exec.BuildActionExecuter;
 import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.internal.OutputEvent;
 import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.messaging.remote.internal.Connection;
@@ -50,9 +50,10 @@ import java.util.Set;
  * <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 sends a {@link Finished} message once it has received the {@link Result} message.
+ *     It may no longer send any messages.</li>
  * <li>The client closes the connection.</li>
- * <li>The daemon closes the connection.</li>
+ * <li>The daemon closes the connection once it has received the {@link Finished} message.</li>
  * </ul>
  *
  * <p>To stop a daemon:</p>
@@ -61,15 +62,16 @@ import java.util.Set;
  * <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 sends a {@link Finished} message once it has received the {@link Result} message.
+ *     It may no longer send any messages.</li>
  * <li>The client closes the connection.</li>
- * <li>The daemon closes the connection.</li>
+ * <li>The daemon closes the connection once it has received the {@link Finished} message.</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> {
+public class DaemonClient implements BuildActionExecuter<BuildActionParameters> {
     private static final Logger LOGGER = Logging.getLogger(DaemonClient.class);
     private static final int STOP_TIMEOUT_SECONDS = 30;
     private final DaemonConnector connector;
@@ -79,7 +81,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
     private final ExecutorFactory executorFactory;
     private final IdGenerator<?> idGenerator;
 
-    //TODO SF - outputEventListener and buildStandardInput are per-build settings
+    //TODO - outputEventListener and buildStandardInput are per-build settings
     //so down the road we should refactor the code accordingly and potentially attach them to BuildActionParameters
     public DaemonClient(DaemonConnector connector, OutputEventListener outputEventListener, ExplainingSpec<DaemonContext> compatibilitySpec,
                         InputStream buildStandardInput, ExecutorFactory executorFactory, IdGenerator<?> idGenerator) {
@@ -140,7 +142,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
      * @param action The action
      * @throws org.gradle.launcher.exec.ReportedException On failure, when the failure has already been logged/reported.
      */
-    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
+    public <T> T execute(BuildAction<T> action, BuildActionParameters parameters) {
         Build build = new Build(idGenerator.generateId(), action, parameters);
         int saneNumberOfAttempts = 100; //is it sane enough?
         for (int i = 1; i < saneNumberOfAttempts; i++) {
@@ -154,7 +156,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
                 connection.stop();
             }
         }
-        //TODO SF if we want to keep below sanity it should include the errors that were accumulated above.
+        //TODO it would be nice if below includes 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 + ".");
     }
@@ -165,11 +167,11 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
             LOGGER.info("Connected to the daemon. Dispatching {} request.", build);
             connection.dispatch(build);
             result = connection.receive();
-        } catch (Exception e) {
-            LOGGER.debug("Unable to perform initial dispatch/receive with the daemon.", e);
+        } catch (StaleDaemonAddressException e) {
+            LOGGER.debug("Connected to a stale daemon address.", 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.");
+            throw new DaemonInitialConnectException("Connected to a stale daemon address.", e);
         }
         if (result == null) {
             throw new DaemonInitialConnectException("The first result from the daemon was empty. Most likely the process died immediately after connection.");
@@ -231,7 +233,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
     }
 
     private IllegalStateException invalidResponse(Object response, Build command) {
-        //TODO SF we could include diagnostics here (they might be available).
+        //TODO diagnostics could be included in the exception (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
index 5976309..42842f2 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.GradleException;
+import org.gradle.api.Nullable;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.messaging.remote.internal.Connection;
@@ -24,15 +24,16 @@ 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);
+    private final Connection<Object> connection;
+    private final String uid;
+    private final StaleAddressDetector staleAddressDetector;
+    private boolean hasReceived;
 
-    public DaemonClientConnection(Connection<Object> connection, String uid, Runnable onFailure) {
+    public DaemonClientConnection(Connection<Object> connection, String uid, StaleAddressDetector staleAddressDetector) {
         this.connection = connection;
         this.uid = uid;
-        this.onFailure = onFailure;
+        this.staleAddressDetector = staleAddressDetector;
     }
 
     public void requestStop() {
@@ -44,25 +45,31 @@ public class DaemonClientConnection implements Connection<Object> {
         return uid;
     }
 
-    public void dispatch(Object message) {
+    public void dispatch(Object message) throws DaemonConnectionException {
         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);
+            if (!hasReceived && staleAddressDetector.maybeStaleAddress(e)) {
+                throw new StaleDaemonAddressException("Could not dispatch a message to the daemon.", e);
+            }
+            throw new DaemonConnectionException("Could not dispatch a message to the daemon.", e);
         }
     }
 
-    public Object receive() {
+    @Nullable
+    public Object receive() throws DaemonConnectionException {
         try {
-            Object result = connection.receive();
-            return result;
+            return connection.receive();
         } 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);
+            if (!hasReceived && staleAddressDetector.maybeStaleAddress(e)) {
+                throw new StaleDaemonAddressException("Could not receive a message from the daemon.", e);
+            }
+            throw new DaemonConnectionException("Could not receive a message from the daemon.", e);
+        } finally {
+            hasReceived = true;
         }
     }
 
@@ -70,4 +77,11 @@ public class DaemonClientConnection implements Connection<Object> {
         LOG.debug("thread {}: connection stop", Thread.currentThread().getId());
         connection.stop();
     }
+
+    interface StaleAddressDetector {
+        /**
+         * @return true if the failure should be considered due to a stale address.
+         */
+        boolean maybeStaleAddress(Exception failure);
+    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
index bc7079c..bc51be4 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
@@ -15,20 +15,19 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.Action;
+import org.gradle.api.Nullable;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.io.TextStream;
 import org.gradle.launcher.daemon.protocol.CloseInput;
 import org.gradle.launcher.daemon.protocol.ForwardInput;
 import org.gradle.launcher.daemon.protocol.IoCommand;
 import org.gradle.messaging.dispatch.Dispatch;
 
 import java.io.InputStream;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Eagerly consumes from an input stream, sending line by line ForwardInput
@@ -39,76 +38,37 @@ public class DaemonClientInputForwarder implements Stoppable {
     private static final Logger LOGGER = Logging.getLogger(DaemonClientInputForwarder.class);
 
     public static final int DEFAULT_BUFFER_SIZE = 1024;
-
-    private final Lock lifecycleLock = new ReentrantLock();
-    private boolean started;
-
-    private final InputStream inputStream;
-    private final Dispatch<? super IoCommand> dispatch;
-    private final ExecutorFactory executorFactory;
-    private final IdGenerator<?> idGenerator;
-    private final int bufferSize;
-
-    private InputForwarder forwarder;
+    private final InputForwarder forwarder;
 
     public DaemonClientInputForwarder(InputStream inputStream, Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, IdGenerator<?> idGenerator) {
         this(inputStream, dispatch, executorFactory, idGenerator, DEFAULT_BUFFER_SIZE);
     }
 
-    public DaemonClientInputForwarder(InputStream inputStream, Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, IdGenerator<?> idGenerator, int bufferSize) {
-        this.inputStream = inputStream;
-        this.dispatch = dispatch;
-        this.executorFactory = executorFactory;
-        this.idGenerator = idGenerator;
-        this.bufferSize = bufferSize;
-    }
-
-    public DaemonClientInputForwarder start() {
-        lifecycleLock.lock();
-        try {
-            if (started) {
-                throw new IllegalStateException("DaemonClientInputForwarder already started");
+    public DaemonClientInputForwarder(InputStream inputStream, final Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, final IdGenerator<?> idGenerator, int bufferSize) {
+        TextStream handler = new TextStream() {
+            public void text(String input) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Forwarding input to daemon: '{}'", input.replace("\n", "\\n"));
+                }
+                dispatch.dispatch(new ForwardInput(idGenerator.generateId(), input.getBytes()));
             }
 
-            Action<String> dispatcher = new Action<String>() {
-                public void execute(String input) {
-                    if (LOGGER.isDebugEnabled()) {
-                        LOGGER.debug("Forwarding input to daemon: '{}'", input.replace("\n", "\\n"));
-                    }                    
-                    dispatch.dispatch(new ForwardInput(idGenerator.generateId(), input.getBytes()));
-                }
-            };
+            public void endOfStream(@Nullable Throwable failure) {
+                CloseInput message = new CloseInput(idGenerator.generateId());
+                LOGGER.debug("Dispatching close input message: {}", message);
+                dispatch.dispatch(message);
+            }
+        };
 
-            Runnable onFinish = new Runnable() {
-                public void run() {
-                    CloseInput message = new CloseInput(idGenerator.generateId());
-                    LOGGER.debug("Dispatching close input message: {}", message);
-                    dispatch.dispatch(message);
-                }
-            };
+        forwarder = new InputForwarder(inputStream, handler, executorFactory, bufferSize);
+    }
 
-            forwarder = new InputForwarder(inputStream, dispatcher, onFinish, executorFactory, bufferSize).start();
-            started = true;
-            return this;
-        } finally {
-            lifecycleLock.unlock();
-        }
+    public DaemonClientInputForwarder start() {
+        forwarder.start();
+        return this;
     }
 
     public void stop() {
-        lifecycleLock.lock();
-        try {
-            if (started) {
-                LOGGER.debug("input forwarder stop requested");
-                try {
-                    forwarder.stop();
-                } finally {
-                    forwarder = null;
-                    started = false;
-                }
-            }
-        } finally {
-            lifecycleLock.unlock();
-        }
+        forwarder.stop();
     }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
index 160e6ff..7dc6ff1 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
@@ -16,12 +16,22 @@
 package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.cache.internal.DefaultFileLockManager;
+import org.gradle.cache.internal.DefaultProcessMetaDataProvider;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.cache.internal.locklistener.DefaultFileLockContentionHandler;
+import org.gradle.cache.internal.locklistener.FileLockContentionHandler;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.launcher.daemon.bootstrap.DaemonGreeter;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.context.DaemonContextBuilder;
 import org.gradle.launcher.daemon.registry.DaemonDir;
 import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+import org.gradle.messaging.remote.internal.MessagingServices;
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
 
 import java.io.InputStream;
 
@@ -34,7 +44,30 @@ public class DaemonClientServices extends DaemonClientServicesSupport {
     public DaemonClientServices(ServiceRegistry loggingServices, DaemonParameters daemonParameters, InputStream buildStandardInput) {
         super(loggingServices, buildStandardInput);
         this.daemonParameters = daemonParameters;
-        add(new DaemonRegistryServices(daemonParameters.getBaseDir()));
+        addProvider(new DaemonRegistryServices(daemonParameters.getBaseDir()));
+    }
+
+    protected FileLockManager createFileLockManager(ProcessEnvironment processEnvironment, FileLockContentionHandler fileLockContentionHandler) {
+        return new DefaultFileLockManager(new DefaultProcessMetaDataProvider(processEnvironment), fileLockContentionHandler);
+    }
+
+    protected MessagingServices createMessagingServices() {
+        return new MessagingServices(getClass().getClassLoader());
+    }
+
+    protected FileLockContentionHandler createFileLockContentionHandler(ExecutorFactory executorFactory, MessagingServices messagingServices) {
+        return new DefaultFileLockContentionHandler(
+                executorFactory,
+                messagingServices.get(InetAddressFactory.class)
+        );
+    }
+
+    protected ExecutorFactory createExecutorFactory() {
+        return new DefaultExecutorFactory();
+    }
+
+    protected DocumentationRegistry createDocumentationRegistry() {
+        return new DocumentationRegistry();
     }
 
     public DaemonStarter createDaemonStarter() {
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 5df35c2..61642f2 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
@@ -15,12 +15,11 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.internal.DocumentationRegistry;
-import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.internal.concurrent.ExecutorFactory;
-import org.gradle.internal.id.*;
+import org.gradle.internal.id.CompositeIdGenerator;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.LongIdGenerator;
+import org.gradle.internal.id.UUIDGenerator;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.internal.service.DefaultServiceRegistry;
@@ -32,9 +31,6 @@ import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.messaging.remote.internal.OutgoingConnector;
 import org.gradle.messaging.remote.internal.inet.TcpOutgoingConnector;
-import org.gradle.internal.id.CompositeIdGenerator;
-import org.gradle.internal.id.LongIdGenerator;
-import org.gradle.internal.id.UUIDGenerator;
 
 import java.io.InputStream;
 
@@ -45,18 +41,11 @@ import java.io.InputStream;
  * @see EmbeddedDaemonClientServices
  */
 abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry {
-
-    private final ServiceRegistry loggingServices;
     private final InputStream buildStandardInput;
 
     public DaemonClientServicesSupport(ServiceRegistry loggingServices, InputStream buildStandardInput) {
-        this.loggingServices = loggingServices;
+        super(NativeServices.getInstance(), loggingServices);
         this.buildStandardInput = buildStandardInput;
-        add(NativeServices.getInstance());
-    }
-
-    public ServiceRegistry getLoggingServices() {
-        return loggingServices;
     }
 
     protected InputStream getBuildStandardInput() {
@@ -85,14 +74,6 @@ abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry
         
     }
 
-    protected OutputEventListener createOutputEventListener() {
-        return getLoggingServices().get(OutputEventListener.class);
-    }
-
-    protected ExecutorFactory createExecuterFactory() {
-        return new DefaultExecutorFactory();
-    }
-
     protected IdGenerator<?> createIdGenerator() {
         return new CompositeIdGenerator(new UUIDGenerator().generateId(), new LongIdGenerator());
     }
@@ -104,12 +85,4 @@ abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry
     protected DaemonConnector createDaemonConnector() {
         return new DefaultDaemonConnector(get(DaemonRegistry.class), get(OutgoingConnector.class), get(DaemonStarter.class));
     }
-
-    protected DocumentationRegistry createDocumentationRegistry() {
-        return new DocumentationRegistry(get(GradleDistributionLocator.class));
-    }
-
-    protected DefaultModuleRegistry createModuleRegistry() {
-        return new DefaultModuleRegistry();
-    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnectionException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnectionException.java
new file mode 100644
index 0000000..30c0969
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnectionException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Thrown when there is some problem using a daemon connection.
+ */
+public class DaemonConnectionException extends RuntimeException {
+    public DaemonConnectionException(String message) {
+        super(message);
+    }
+
+    public DaemonConnectionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
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 8a645c8..e509f33 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
@@ -15,11 +15,12 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.api.Nullable;
 import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.launcher.daemon.context.DaemonContext;
 
 /**
- * A daemon connector establishes a connection to either an already running daemon, or a newly started daemon.
+ * A daemon connector establishes a connection to a daemon.
  */
 public interface DaemonConnector {
 
@@ -28,6 +29,7 @@ public interface DaemonConnector {
      *
      * @return A connection to a matching daemon, or null if none running.
      */
+    @Nullable
     public DaemonClientConnection maybeConnect(ExplainingSpec<DaemonContext> constraint);
 
     /**
@@ -37,6 +39,9 @@ public interface DaemonConnector {
      */
     public DaemonClientConnection connect(ExplainingSpec<DaemonContext> constraint);
 
-    public DaemonClientConnection createConnection(ExplainingSpec<DaemonContext> constraint);
+    /**
+     * Starts a new daemon and returns a connection to it.
+     */
+    public DaemonClientConnection startDaemon(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 cc38063..e3ac62e 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,4 +21,8 @@ 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 2e38c85..1721131 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
@@ -15,7 +15,6 @@
  */
 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;
@@ -70,20 +69,20 @@ public class DefaultDaemonConnector implements DaemonConnector {
             return connection;
         }
 
-        return createConnection(constraint);
+        return startDaemon(constraint);
     }
 
     private DaemonClientConnection findConnection(List<DaemonInfo> daemonInfos, ExplainingSpec<DaemonContext> constraint) {
-        for (DaemonInfo daemonInfo : daemonInfos) {
+        for (final 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"
+                LOGGER.debug("Found daemon (address: {}, idle: {}) however its context does not match the desired criteria.\n"
                         + constraint.whyUnsatisfied(daemonInfo.getContext()) + "\n"
                         + "  Looking for a different daemon...", daemonInfo.getAddress(), daemonInfo.isIdle());
                 continue;
             }
 
             try {
-                return connectToDaemon(daemonInfo);
+                return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, true));
             } catch (ConnectException e) {
                 LOGGER.debug("Cannot connect to the daemon at " + daemonInfo.getAddress() + " due to " + e + ". Trying a different daemon...");
             }
@@ -91,24 +90,24 @@ public class DefaultDaemonConnector implements DaemonConnector {
         return null;
     }
 
-    public DaemonClientConnection createConnection(ExplainingSpec<DaemonContext> constraint) {
+    public DaemonClientConnection startDaemon(ExplainingSpec<DaemonContext> constraint) {
         LOGGER.info("Starting Gradle daemon");
         final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
         LOGGER.debug("Started Gradle Daemon: {}", startupInfo);
         long expiry = System.currentTimeMillis() + connectTimeout;
         do {
+            DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
+            if (daemonConnection != null) {
+                return daemonConnection;
+            }
             try {
                 Thread.sleep(200L);
             } catch (InterruptedException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
-            DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
-            if (daemonConnection != null) {
-                return daemonConnection;
-            }
         } while (System.currentTimeMillis() < expiry);
 
-        throw new GradleException("Timeout waiting to connect to Gradle daemon.\n" + startupInfo.describe());
+        throw new DaemonConnectionException("Timeout waiting to connect to the Gradle daemon.\n" + startupInfo.describe());
     }
 
     private DaemonClientConnection connectToDaemonWithId(DaemonStartupInfo startupInfo, ExplainingSpec<DaemonContext> constraint) throws ConnectException {
@@ -117,39 +116,43 @@ public class DefaultDaemonConnector implements DaemonConnector {
             if (daemonInfo.getContext().getUid().equals(startupInfo.getUid())) {
                 try {
                     if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
-                        throw new GradleException("The newly created daemon process has a different context than expected."
+                        throw new DaemonConnectionException("The newly created daemon process has a different context than expected."
                                 + "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
                                 + "\n" + constraint.whyUnsatisfied(daemonInfo.getContext()));
                     }
-                    return connectToDaemon(daemonInfo);
+                    return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
                 } catch (ConnectException e) {
-                    throw new GradleException("The forked daemon process died before we could connect.\n" + startupInfo.describe(), e);
+                    throw new DaemonConnectionException("Could not connect to the Gradle daemon.\n" + startupInfo.describe(), e);
                 }
             }
         }
         return null;
     }
 
-    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.
-                }
-            }
-        };
+    private DaemonClientConnection connectToDaemon(DaemonInfo daemonInfo, DaemonClientConnection.StaleAddressDetector staleAddressDetector) throws ConnectException {
         Connection<Object> connection;
         try {
-            connection = connector.connect(daemonInfo.getAddress(), getClass().getClassLoader());
+            connection = connector.connect(daemonInfo.getAddress()).create(getClass().getClassLoader());
         } catch (ConnectException e) {
-            onFailure.run();
+            staleAddressDetector.maybeStaleAddress(e);
             throw e;
         }
-        return new DaemonClientConnection(connection, daemonInfo.getContext().getUid(), onFailure);
+        return new DaemonClientConnection(connection, daemonInfo.getContext().getUid(), staleAddressDetector);
+    }
+
+    private class CleanupOnStaleAddress implements DaemonClientConnection.StaleAddressDetector {
+        private final DaemonInfo daemonInfo;
+        private final boolean exposeAsStale;
+
+        public CleanupOnStaleAddress(DaemonInfo daemonInfo, boolean exposeAsStale) {
+            this.daemonInfo = daemonInfo;
+            this.exposeAsStale = exposeAsStale;
+        }
+
+        public boolean maybeStaleAddress(Exception failure) {
+            LOGGER.info(DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE + daemonInfo);
+            daemonRegistry.remove(daemonInfo.getAddress());
+            return exposeAsStale;
+        }
     }
 }
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 665780e..a76c464 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
@@ -15,11 +15,13 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.initialization.BuildLayoutParameters;
+import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.internal.Factory;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.scopes.GlobalScopeServices;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.context.DaemonContextBuilder;
@@ -31,6 +33,7 @@ import org.gradle.launcher.daemon.server.DaemonServerConnector;
 import org.gradle.launcher.daemon.server.DaemonTcpServerConnector;
 import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter;
 import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter;
+import org.gradle.launcher.daemon.server.exec.NoOpDaemonCommandAction;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEvent;
@@ -43,11 +46,8 @@ import java.util.UUID;
  * Wires together the embedded daemon client.
  */
 public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
-
-    private final boolean displayOutput;
-
     public EmbeddedDaemonClientServices() {
-        this(LoggingServiceRegistry.newProcessLogging(), false);
+        this(LoggingServiceRegistry.newProcessLogging());
     }
 
     private class EmbeddedDaemonFactory implements Factory<Daemon> {
@@ -64,14 +64,14 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
     }
 
     protected DaemonCommandExecuter createDaemonCommandExecuter() {
-        LoggingManagerInternal mgr = getLoggingServices().getFactory(LoggingManagerInternal.class).create();
-        return new DefaultDaemonCommandExecuter(new DefaultGradleLauncherFactory(getLoggingServices()),
-                get(ProcessEnvironment.class), mgr, new File("dummy"));
+        LoggingManagerInternal mgr = newInstance(LoggingManagerInternal.class);
+        return new DefaultDaemonCommandExecuter(get(GradleLauncherFactory.class),
+                get(ProcessEnvironment.class), mgr, new File("dummy"), new NoOpDaemonCommandAction());
     }
 
-    public EmbeddedDaemonClientServices(ServiceRegistry loggingServices, boolean displayOutput) {
+    public EmbeddedDaemonClientServices(ServiceRegistry loggingServices) {
         super(loggingServices, System.in);
-        this.displayOutput = displayOutput;
+        addProvider(new GlobalScopeServices(false));
         add(EmbeddedDaemonFactory.class, new EmbeddedDaemonFactory());
     }
 
@@ -80,17 +80,13 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
     }
 
     protected OutputEventListener createOutputEventListener() {
-        if (displayOutput) {
-            return super.createOutputEventListener();
-        } else {
-            return new OutputEventListener() { public void onOutput(OutputEvent event) {} };
-        }
+        return new OutputEventListener() { public void onOutput(OutputEvent event) {} };
     }
 
     @Override
     protected void configureDaemonContextBuilder(DaemonContextBuilder builder) {
         builder.setUid(UUID.randomUUID().toString());
-        builder.setDaemonRegistryDir(new DaemonDir(new DaemonParameters().getBaseDir()).getRegistry());
+        builder.setDaemonRegistryDir(new DaemonDir(new DaemonParameters(new BuildLayoutParameters()).getBaseDir()).getRegistry());
     }
 
     protected DaemonServerConnector createDaemonServerConnector() {
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 c98470a..533e1ad 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
@@ -15,9 +15,9 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.Factory;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
 import org.gradle.launcher.daemon.server.Daemon;
 
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 b8dcd80..0c5533d 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
@@ -15,11 +15,11 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.Action;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.io.TextStream;
 import org.gradle.util.DisconnectableInputStream;
 import org.gradle.util.LineBufferingOutputStream;
 
@@ -38,8 +38,7 @@ import java.util.concurrent.locks.ReentrantLock;
 public class InputForwarder implements Stoppable {
 
     private final InputStream input;
-    private final Action<String> forwardTo;
-    private final Runnable onFinish;
+    private final TextStream handler;
     private final ExecutorFactory executorFactory;
     private final int bufferSize;
     private StoppableExecutor forwardingExecuter;
@@ -49,10 +48,9 @@ public class InputForwarder implements Stoppable {
     private boolean started;
     private boolean stopped;
 
-    public InputForwarder(InputStream input, Action<String> forwardTo, Runnable onFinish, ExecutorFactory executerFactory, int bufferSize) {
+    public InputForwarder(InputStream input, TextStream handler, ExecutorFactory executerFactory, int bufferSize) {
         this.input = input;
-        this.forwardTo = forwardTo;
-        this.onFinish = onFinish;
+        this.handler = handler;
         this.executorFactory = executerFactory;
         this.bufferSize = bufferSize;
     }
@@ -65,13 +63,14 @@ public class InputForwarder implements Stoppable {
             }
 
             disconnectableInput = new DisconnectableInputStream(input, bufferSize);
-            outputBuffer = new LineBufferingOutputStream(forwardTo, bufferSize);
+            outputBuffer = new LineBufferingOutputStream(handler, bufferSize);
 
             forwardingExecuter = executorFactory.create("forward input");
             forwardingExecuter.execute(new Runnable() {
                 public void run() {
                     byte[] buffer = new byte[bufferSize];
                     int readCount;
+                    Throwable readFailure = null;
                     try {
                         while (true) {
                             try {
@@ -82,27 +81,19 @@ public class InputForwarder implements Stoppable {
                             } catch (AsynchronousCloseException e) {
                                 break;
                             } catch (IOException e) {
-                                // Unsure what the best thing to do is here, should we forward the error?
-                                throw UncheckedException.throwAsUncheckedException(e);
+                                readFailure = e;
+                                break;
                             }
 
-                            try {
-                                outputBuffer.write(buffer, 0, readCount);
-                            } catch (IOException e) {
-                                // this shouldn't happen as outputBuffer will only throw if close has been called
-                                // and we own this object exclusively and will not have done that at this time
-                                throw UncheckedException.throwAsUncheckedException(e);
-                            }
+                            outputBuffer.write(buffer, 0, readCount);
                         }
+                        outputBuffer.flush(); // will flush any unterminated lines out synchronously
+                    } catch(IOException e) {
+                        // should not happen
+                        throw UncheckedException.throwAsUncheckedException(e);
                     } finally {
-                        try {
-                            outputBuffer.close(); // will flush any unterminated lines out synchronously
-                        } catch (IOException e) {
-                            throw UncheckedException.throwAsUncheckedException(e);
-                        }
+                        handler.endOfStream(readFailure);
                     }
-                    
-                    onFinish.run();
                 }
             });
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java
index 078d774..ad7ccf6 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java
@@ -18,9 +18,6 @@ package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.GradleException;
 
-/**
-* by Szczepan Faber, created at: 2/24/12
-*/
 public class NoUsableDaemonFoundException extends GradleException {
     public NoUsableDaemonFoundException(String message) {
         super(message);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java
index b103b94..748e2f1 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
@@ -19,7 +19,9 @@ package org.gradle.launcher.daemon.client;
 import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.api.internal.specs.ExplainingSpecs;
-import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.BuildAction;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.launcher.daemon.context.DaemonContext;
@@ -27,13 +29,12 @@ 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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.InputStream;
 
 public class SingleUseDaemonClient extends DaemonClient {
-    private static final Logger LOGGER = LoggerFactory.getLogger(SingleUseDaemonClient.class);
+    public static final String MESSAGE = "To honour the JVM settings for this build a new JVM will be forked.";
+    private static final Logger LOGGER = Logging.getLogger(SingleUseDaemonClient.class);
     private final DocumentationRegistry documentationRegistry;
 
     public SingleUseDaemonClient(DaemonConnector connector, OutputEventListener outputEventListener, ExplainingSpec<DaemonContext> compatibilitySpec, InputStream buildStandardInput,
@@ -43,13 +44,11 @@ public class SingleUseDaemonClient extends DaemonClient {
     }
 
     @Override
-    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
-        LOGGER.warn("Note: in order to honour the org.gradle.jvmargs and/or org.gradle.java.home values specified for this build, it is necessary to fork a new JVM.");
-        LOGGER.warn("To avoid the slowdown associated with this extra process, you might want to consider running Gradle with the daemon enabled.");
-        LOGGER.warn("Please see the user guide chapter on the daemon at {}.", documentationRegistry.getDocumentationFor("gradle_daemon"));
+    public <T> T execute(BuildAction<T> action, BuildActionParameters parameters) {
+        LOGGER.lifecycle("{} Please consider using the daemon: {}.", MESSAGE, documentationRegistry.getDocumentationFor("gradle_daemon"));
         Build build = new BuildAndStop(getIdGenerator().generateId(), action, parameters);
 
-        DaemonClientConnection daemonConnection = getConnector().createConnection(ExplainingSpecs.<DaemonContext>satisfyAll());
+        DaemonClientConnection daemonConnection = getConnector().startDaemon(ExplainingSpecs.<DaemonContext>satisfyAll());
 
         return (T) executeBuild(build, daemonConnection);
     }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StaleDaemonAddressException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StaleDaemonAddressException.java
new file mode 100644
index 0000000..a24dce6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StaleDaemonAddressException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Thrown when connected to a stale daemon address.
+ *
+ * This is thrown instead of using an initial handshake on the connection, to avoid the latency of a round trip to the
+ * daemon before starting the build.
+ */
+public class StaleDaemonAddressException extends DaemonConnectionException {
+    public StaleDaemonAddressException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
index 82284a3..2c0801b 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 the original author or authors.
+ * Copyright 2013 the original author 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/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 d77e259..7c665fc 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
@@ -25,9 +25,6 @@ import org.gradle.launcher.daemon.protocol.Result;
 import org.gradle.launcher.daemon.protocol.Stop;
 import org.gradle.messaging.remote.internal.Connection;
 
-/**
- * @author: Szczepan Faber, created at: 9/13/11
- */
 public class StopDispatcher {
     private static final Logger LOGGER = Logging.getLogger(StopDispatcher.class);
     private final IdGenerator<?> idGenerator;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
index f0758c2..6f2f025 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
@@ -17,6 +17,7 @@ package org.gradle.launcher.daemon.configuration;
 
 import org.gradle.StartParameter;
 import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.initialization.BuildLayoutParameters;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.internal.JvmOptions;
 import org.gradle.util.GUtil;
@@ -39,13 +40,10 @@ public class DaemonParameters {
     private boolean enabled;
     private File javaHome;
 
-    public DaemonParameters(String uid) {
-        this.uid = uid;
+    public DaemonParameters(BuildLayoutParameters layout) {
+        this.uid = UUID.randomUUID().toString();
         jvmOptions.setAllJvmArgs(getDefaultJvmArgs());
-    }
-
-    public DaemonParameters() {
-        this(UUID.randomUUID().toString());
+        baseDir = new File(layout.getGradleUserHomeDir(), "daemon");
     }
 
     List<String> getDefaultJvmArgs() {
@@ -126,24 +124,16 @@ public class DaemonParameters {
         jvmOptions.setAllJvmArgs(jvmArgs);
     }
 
-    public void configureFrom(GradleProperties gradleProperties) {
-        if (gradleProperties.getIdleTimeout() != null) {
-            idleTimeout = gradleProperties.getIdleTimeout();
-        }
-        if (gradleProperties.getJvmArgs() != null) {
-            setJvmArgs(JvmOptions.fromString(gradleProperties.getJvmArgs()));
-        }
-        if (gradleProperties.isDaemonEnabled()) {
-            enabled = true;
-        }
-        if (gradleProperties.getJavaHome() != null) {
-            javaHome = gradleProperties.getJavaHome();
-        }
-        if (gradleProperties.isDebugMode()) {
-            jvmOptions.setDebug(true);
-        }
-        if (gradleProperties.getDaemonBaseDir() != null) {
-            baseDir = gradleProperties.getDaemonBaseDir();
-        }
+    public void setDebug(boolean debug) {
+        jvmOptions.setDebug(debug);
+    }
+
+    public DaemonParameters setBaseDir(File baseDir) {
+        this.baseDir = baseDir;
+        return this;
+    }
+
+    public boolean getDebug() {
+        return jvmOptions.getDebug();
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java
index a91bcec..121011c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java
@@ -19,9 +19,6 @@ package org.gradle.launcher.daemon.configuration;
 import java.io.File;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 2/21/12
- */
 public interface DaemonServerConfiguration {
 
     File getBaseDir();
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java
index 8e0f898..3d4e706 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java
@@ -19,9 +19,6 @@ package org.gradle.launcher.daemon.configuration;
 import java.io.File;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 2/21/12
- */
 public class DefaultDaemonServerConfiguration implements DaemonServerConfiguration {
 
     private final String daemonUid;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java
index d5fd87f..b8e815c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java
@@ -18,9 +18,6 @@ package org.gradle.launcher.daemon.configuration;
 
 import java.io.File;
 
-/**
- * by Szczepan Faber, created at: 2/21/12
- */
 public class ForegroundDaemonConfiguration extends DefaultDaemonServerConfiguration {
     public ForegroundDaemonConfiguration(String daemonUid, File daemonBaseDir, int idleTimeoutMs) {
         // Foreground daemon cannot be 'told' what's his startup options as the client sits in the same process so we will infer the jvm opts from the inputArguments()
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java
index a8642f6..dc5bc84 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java
@@ -16,30 +16,14 @@
 
 package org.gradle.launcher.daemon.configuration;
 
-import org.gradle.StartParameter;
-import org.gradle.api.GradleException;
-import org.gradle.api.Nullable;
-import org.gradle.api.Project;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.initialization.layout.BuildLayout;
-import org.gradle.initialization.layout.BuildLayoutFactory;
-import org.gradle.internal.jvm.JavaHomeException;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.util.GFileUtils;
+import java.util.Set;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Map;
-import java.util.Properties;
+import static com.google.common.collect.Sets.newHashSet;
 
-/**
- * by Szczepan Faber, created at: 1/22/13
- */
 public class GradleProperties {
 
     public static final String IDLE_TIMEOUT_PROPERTY = "org.gradle.daemon.idletimeout";
-    public static final String BASE_DIR_PROPERTY = "org.gradle.daemon.registry.base";
+    public static final String DAEMON_BASE_DIR_PROPERTY = "org.gradle.daemon.registry.base";
     public static final String JVM_ARGS_PROPERTY = "org.gradle.jvmargs";
     public static final String JAVA_HOME_PROPERTY = "org.gradle.java.home";
     public static final String DAEMON_ENABLED_PROPERTY = "org.gradle.daemon";
@@ -47,155 +31,10 @@ public class GradleProperties {
     public static final String CONFIGURE_ON_DEMAND_PROPERTY = "org.gradle.configureondemand";
     public static final String PARALLEL_PROPERTY = "org.gradle.parallel";
 
-    private File daemonBaseDir;
-    private String jvmArgs;
-
-    private Integer idleTimeout;
-    private boolean daemonEnabled;
-    private File javaHome;
-    private boolean debugMode;
-    private boolean configureOnDemand;
-    private boolean parallelMode;
-
-    public boolean isDaemonEnabled() {
-        return daemonEnabled;
-    }
-
-    @Nullable
-    public File getDaemonBaseDir() {
-        return daemonBaseDir;
-    }
-
-    public Integer getIdleTimeout() {
-        return idleTimeout;
-    }
-
-    @Nullable
-    public File getJavaHome() {
-        return javaHome;
-    }
-
-    public String getJvmArgs() {
-        return jvmArgs;
-    }
-
-    public boolean isDebugMode() {
-        return debugMode;
-    }
-
-    private void setBaseDir(File baseDir) {
-        this.daemonBaseDir = GFileUtils.canonicalise(baseDir);
-    }
-
-    public GradleProperties configureFromGradleUserHome(File gradleUserHomeDir) {
-        setBaseDir(new File(gradleUserHomeDir, "daemon"));
-        maybeConfigureFrom(new File(gradleUserHomeDir, Project.GRADLE_PROPERTIES));
-        return this;
-    }
-
-    public GradleProperties configureFromSystemProperties(Map<?, ?> properties) {
-        Object propertyValue = properties.get(BASE_DIR_PROPERTY);
-        if (propertyValue != null) {
-            setBaseDir(new File(propertyValue.toString()));
-        }
-        configureFrom(properties);
-        return this;
-    }
-
-    public GradleProperties configureFromBuildDir(File currentDir, boolean searchUpwards) {
-        BuildLayoutFactory factory = new BuildLayoutFactory();
-        BuildLayout layout = factory.getLayoutFor(currentDir, searchUpwards);
-        maybeConfigureFrom(new File(layout.getRootDirectory(), Project.GRADLE_PROPERTIES));
-        return this;
-    }
-
-    private void maybeConfigureFrom(File propertiesFile) {
-        if (!propertiesFile.isFile()) {
-            return;
-        }
-
-        Properties properties = new Properties();
-        try {
-            FileInputStream inputStream = new FileInputStream(propertiesFile);
-            try {
-                properties.load(inputStream);
-            } finally {
-                inputStream.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-
-        configureFrom(properties);
-    }
-
-    GradleProperties configureFrom(Map<?, ?> properties) {
-        Object propertyValue = properties.get(IDLE_TIMEOUT_PROPERTY);
-        if (propertyValue != null) {
-            try {
-                idleTimeout = new Integer(propertyValue.toString());
-            } catch (NumberFormatException e) {
-                throw new GradleException(String.format("Unable to parse %s property. The value should be an int but is: %s", IDLE_TIMEOUT_PROPERTY, propertyValue));
-            }
-        }
-        propertyValue = properties.get(JVM_ARGS_PROPERTY);
-        if (propertyValue != null) {
-            jvmArgs = propertyValue.toString();
-        }
-        propertyValue = properties.get(DAEMON_ENABLED_PROPERTY);
-        if (propertyValue != null) {
-            daemonEnabled = isTrue(propertyValue);
-        }
-
-        propertyValue = properties.get(JAVA_HOME_PROPERTY);
-        if (propertyValue != null) {
-            javaHome = new File(propertyValue.toString());
-            if (!javaHome.isDirectory()) {
-                throw new GradleException(String.format("Java home supplied via '%s' is invalid. Dir does not exist: %s", JAVA_HOME_PROPERTY, propertyValue));
-            }
-            try {
-                Jvm.forHome(javaHome);
-            } catch (JavaHomeException e) {
-                throw new GradleException(String.format("Java home supplied via '%s' seems to be invalid: %s", JAVA_HOME_PROPERTY, propertyValue));
-            }
-        }
-
-        propertyValue = properties.get(DEBUG_MODE_PROPERTY);
-        if (propertyValue != null) {
-            debugMode = isTrue(propertyValue);
-        }
-
-        propertyValue = properties.get(CONFIGURE_ON_DEMAND_PROPERTY);
-        if (propertyValue != null) {
-            configureOnDemand = isTrue(propertyValue);
-        }
-
-        propertyValue = properties.get(PARALLEL_PROPERTY);
-        if (propertyValue != null) {
-            parallelMode = isTrue(propertyValue);
-        }
-
-        return this;
-    }
-
-    public void updateStartParameter(StartParameter startParameter) {
-        if (configureOnDemand) {
-            startParameter.setConfigureOnDemand(configureOnDemand);
-        }
-        if (parallelMode && !startParameter.isParallelThreadCountConfigured()) {
-            startParameter.setParallelThreadCount(-1);
-        }
-    }
-
-    public boolean isConfigureOnDemand() {
-        return configureOnDemand;
-    }
-
-    public boolean isParallelMode() {
-        return parallelMode;
-    }
+    public static final Set<String> ALL = newHashSet(IDLE_TIMEOUT_PROPERTY, DAEMON_BASE_DIR_PROPERTY, JVM_ARGS_PROPERTY,
+            JAVA_HOME_PROPERTY, DAEMON_ENABLED_PROPERTY, DEBUG_MODE_PROPERTY, CONFIGURE_ON_DEMAND_PROPERTY, PARALLEL_PROPERTY);
 
-    private static boolean isTrue(Object propertyValue) {
-        return propertyValue.toString().equalsIgnoreCase("true");
+    public static boolean isTrue(Object propertyValue) {
+        return propertyValue != null && propertyValue.toString().equalsIgnoreCase("true");
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurer.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurer.java
deleted file mode 100644
index 4948ca1..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.launcher.daemon.configuration;
-
-import org.gradle.StartParameter;
-
-import java.io.File;
-import java.util.Map;
-
-/**
- * by Szczepan Faber, created at: 1/22/13
- */
-public class GradlePropertiesConfigurer {
-
-    public GradleProperties prepareProperties(File projectDir, boolean searchUpwards, File gradleUserHomeDir, Map<?, ?> systemProperties) {
-        return new GradleProperties()
-            .configureFromBuildDir(projectDir, searchUpwards)
-            .configureFromGradleUserHome(gradleUserHomeDir)
-            .configureFromSystemProperties(systemProperties);
-    }
-
-    public DaemonParameters configureParameters(StartParameter startParameter) {
-        DaemonParameters out = new DaemonParameters();
-        GradleProperties properties = configureStartParameter(startParameter);
-        out.configureFrom(properties);
-        return out;
-    }
-
-    public GradleProperties configureStartParameter(StartParameter startParameter) {
-        GradleProperties properties = this.prepareProperties(startParameter.getCurrentDir(), startParameter.isSearchUpwards(), startParameter.getGradleUserHomeDir(), startParameter.getMergedSystemProperties());
-        properties.updateStartParameter(startParameter);
-        return properties;
-    }
-}
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 15282d4..8f29a35 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
@@ -23,8 +23,6 @@ import java.io.Serializable;
 
 /**
  * Contains some daemon diagnostics information useful for the client.
- * <p>
- * by Szczepan Faber, created at: 2/28/12
  */
 public class DaemonDiagnostics implements Serializable {
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java
index 4dfc94e..a1a8a6d 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java
@@ -16,9 +16,6 @@
 
 package org.gradle.launcher.daemon.diagnostics;
 
-/**
- * by Szczepan Faber, created at 4/6/12
- */
 public class DaemonStartupInfo {
 
     private String uid;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
index b167706..4c7115f 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
@@ -15,20 +15,20 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.initialization.BuildAction;
 import org.gradle.launcher.exec.BuildActionParameters;
 
 public class Build extends Command {
-    private final GradleLauncherAction<?> action;
+    private final BuildAction<?> action;
     private final BuildActionParameters parameters;
 
-    public Build(Object identifier, GradleLauncherAction<?> action, BuildActionParameters parameters) {
+    public Build(Object identifier, BuildAction<?> action, BuildActionParameters parameters) {
         super(identifier);
         this.action = action;
         this.parameters = parameters;
     }
 
-    public GradleLauncherAction<?> getAction() {
+    public BuildAction<?> getAction() {
         return action;
     }
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
index b428089..314a07c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
@@ -16,11 +16,11 @@
 
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.initialization.BuildAction;
 import org.gradle.launcher.exec.BuildActionParameters;
 
 public class BuildAndStop extends Build {
-    public BuildAndStop(Object identifier, GradleLauncherAction<?> action, BuildActionParameters parameters) {
+    public BuildAndStop(Object identifier, BuildAction<?> action, BuildActionParameters parameters) {
         super(identifier, action, parameters);
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java
index 8955cbb..60a5caf 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java
@@ -24,9 +24,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
-/**
- * @author: Szczepan Faber, created at: 8/29/11
- */
 public class DaemonRegistryContent implements Serializable {
 
     private Map<Address, DaemonInfo> infosMap = new HashMap<Address, DaemonInfo>();
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 27e05bb..32e3247 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
@@ -15,16 +15,11 @@
  */
 package org.gradle.launcher.daemon.registry;
 
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.cache.Cache;
 import org.gradle.api.internal.cache.CacheAccessSerializer;
 import org.gradle.api.internal.cache.MapBackedCache;
-import org.gradle.cache.internal.DefaultFileLockManager;
-import org.gradle.cache.internal.DefaultProcessMetaDataProvider;
 import org.gradle.cache.internal.FileLockManager;
-import org.gradle.internal.nativeplatform.ProcessEnvironment;
-import org.gradle.internal.nativeplatform.services.NativeServices;
-import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 import java.util.HashMap;
@@ -34,7 +29,7 @@ import java.util.Properties;
 /**
  * Takes care of instantiating and wiring together the services required for a daemon registry.
  */
-public class DaemonRegistryServices extends DefaultServiceRegistry {
+public class DaemonRegistryServices {
     private final File daemonBaseDir;
     private final Cache<File, DaemonRegistry> daemonRegistryCache;
 
@@ -45,32 +40,27 @@ public class DaemonRegistryServices extends DefaultServiceRegistry {
 
     public DaemonRegistryServices(File daemonBaseDir) {
         this(daemonBaseDir, REGISTRY_CACHE);
-        add(NativeServices.getInstance());
     }
 
-    protected DaemonRegistryServices(File daemonBaseDir, Cache<File, DaemonRegistry> daemonRegistryCache) {
+    DaemonRegistryServices(File daemonBaseDir, Cache<File, DaemonRegistry> daemonRegistryCache) {
         this.daemonBaseDir = daemonBaseDir;
         this.daemonRegistryCache = daemonRegistryCache;
     }
 
-    protected DaemonDir createDaemonDir() {
+    DaemonDir createDaemonDir() {
         return new DaemonDir(daemonBaseDir);
     }
 
-    protected FileLockManager createFileLockManager() {
-        return new DefaultFileLockManager(new DefaultProcessMetaDataProvider(get(ProcessEnvironment.class)));
-    }
-
-    protected DaemonRegistry createDaemonRegistry() {
-        final File daemonRegistryFile = get(DaemonDir.class).getRegistry();
+    DaemonRegistry createDaemonRegistry(DaemonDir daemonDir, final FileLockManager fileLockManager) {
+        final File daemonRegistryFile = daemonDir.getRegistry();
         return daemonRegistryCache.get(daemonRegistryFile, new Factory<DaemonRegistry>() {
             public DaemonRegistry create() {
-                return new PersistentDaemonRegistry(daemonRegistryFile, get(FileLockManager.class));
+                return new PersistentDaemonRegistry(daemonRegistryFile, fileLockManager);
             }
         });
     }
     
-    protected Properties createProperties() {
+    Properties createProperties() {
         return System.getProperties();
     }
 }
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 6bf935d..13677e6 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
@@ -18,7 +18,6 @@ package org.gradle.launcher.daemon.registry;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.cache.PersistentStateCache;
 import org.gradle.cache.internal.FileIntegrityViolationSuppressingPersistentStateCacheDecorator;
 import org.gradle.cache.internal.FileLockManager;
@@ -26,6 +25,7 @@ import org.gradle.cache.internal.OnDemandFileAccess;
 import org.gradle.cache.internal.SimpleStateCache;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.serialize.DefaultSerializer;
 
 import java.io.File;
 import java.util.LinkedList;
@@ -35,8 +35,6 @@ import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Access to daemon registry files. Useful also for testing.
- *
- * @author: Szczepan Faber, created at: 8/18/11
  */
 public class PersistentDaemonRegistry implements DaemonRegistry {
     private final PersistentStateCache<DaemonRegistryContent> cache;
@@ -128,12 +126,13 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
-                    assertCacheNotEmpty(oldValue);
-                    DaemonInfo daemonInfo = oldValue.getInfo(address);
-                    daemonInfo.setIdle(false);
+                    DaemonInfo daemonInfo = oldValue != null ? oldValue.getInfo(address) : null;
+                    if (daemonInfo != null) {
+                        daemonInfo.setIdle(false);
+                    }
+                    // Else, has been removed by something else - ignore
                     return oldValue;
-                }
-            });
+                }});
         } finally {
             lock.unlock();
         }
@@ -145,8 +144,11 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
-                    assertCacheNotEmpty(oldValue);
-                    oldValue.getInfo(address).setIdle(true);
+                    DaemonInfo daemonInfo = oldValue != null ? oldValue.getInfo(address) : null;
+                    if (daemonInfo != null) {
+                        daemonInfo.setIdle(true);
+                    }
+                    // Else, has been removed by something else - ignore
                     return oldValue;
                 }
             });
@@ -155,7 +157,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
         }
     }
 
-    public synchronized void store(final Address address, final DaemonContext daemonContext, final String password, final boolean idle) {
+    public 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 {
@@ -178,10 +180,4 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
     public String toString() {
         return String.format("PersistentDaemonRegistry[file=%s]", registryFile);
     }
-
-    private void assertCacheNotEmpty(Object value) {
-        if (value == null) {
-            throw new EmptyRegistryException("Registry is empty!");
-        }
-    }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java
index fc70cba..7a303bd 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,8 +17,8 @@ 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.CompositeStoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
@@ -187,4 +187,4 @@ public class Daemon implements Stoppable {
 
         stateCoordinator.stopOnIdleTimeout(idleTimeout, idleTimeoutUnits);
     }
-}
+}
\ No newline at end of file
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 9a7ed56..97ca8f3 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.launcher.daemon.server;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.messaging.remote.Address;
 
 /**
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 4c61fae..6302684 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
@@ -15,10 +15,10 @@
  */
 package org.gradle.launcher.daemon.server;
 
+import org.gradle.internal.service.scopes.GlobalScopeServices;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
@@ -30,6 +30,7 @@ import org.gradle.launcher.daemon.context.DaemonContextBuilder;
 import org.gradle.launcher.daemon.registry.DaemonDir;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+import org.gradle.launcher.daemon.server.exec.DaemonHygieneAction;
 import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 
@@ -41,22 +42,16 @@ import java.util.UUID;
  */
 public class DaemonServices extends DefaultServiceRegistry {
     private final DaemonServerConfiguration configuration;
-    private final ServiceRegistry loggingServices;
     private final LoggingManagerInternal loggingManager;
     private final static Logger LOGGER = Logging.getLogger(DaemonServices.class);
 
-    public DaemonServices(DaemonServerConfiguration configuration, ServiceRegistry loggingServices,
-                          LoggingManagerInternal loggingManager) {
+    public DaemonServices(DaemonServerConfiguration configuration, ServiceRegistry loggingServices, LoggingManagerInternal loggingManager) {
+        super(NativeServices.getInstance(), loggingServices);
         this.configuration = configuration;
-        this.loggingServices = loggingServices;
         this.loggingManager = loggingManager;
 
-        add(NativeServices.getInstance());
-        add(new DaemonRegistryServices(configuration.getBaseDir()));
-    }
-
-    protected ExecutorFactory createExecutorFactory() {
-        return new DefaultExecutorFactory();
+        addProvider(new DaemonRegistryServices(configuration.getBaseDir()));
+        addProvider(new GlobalScopeServices(true));
     }
 
     protected DaemonContext createDaemonContext() {
@@ -86,10 +81,10 @@ public class DaemonServices extends DefaultServiceRegistry {
                 get(DaemonContext.class),
                 "password",
                 new DefaultDaemonCommandExecuter(
-                        new DefaultGradleLauncherFactory(loggingServices),
+                        get(GradleLauncherFactory.class),
                         get(ProcessEnvironment.class),
                         loggingManager,
-                        getDaemonLogFile()),
+                        getDaemonLogFile(), new DaemonHygieneAction()),
                 get(ExecutorFactory.class));
     }
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java
index 735f429..81f3434 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
@@ -17,7 +17,7 @@
 package org.gradle.launcher.daemon.server;
 
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.server.exec.DaemonStateControl;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
index 7cedcc7..cc8f745 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
@@ -16,13 +16,12 @@
 package org.gradle.launcher.daemon.server;
 
 import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.internal.id.UUIDGenerator;
 import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.internal.Connection;
 import org.gradle.messaging.remote.ConnectionAcceptor;
+import org.gradle.messaging.remote.internal.ConnectCompletion;
 import org.gradle.messaging.remote.internal.IncomingConnector;
 import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
 import org.gradle.messaging.remote.internal.inet.TcpIncomingConnector;
@@ -62,13 +61,13 @@ public class DaemonTcpServerConnector implements DaemonServerConnector {
             // Hold the lock until we actually start accepting connections for the case when stop is called from another
             // thread while we are in the middle here.
 
-            Action<ConnectEvent<Connection<Object>>> connectEvent = new Action<ConnectEvent<Connection<Object>>>() {
-                public void execute(ConnectEvent<Connection<Object>> connectionConnectEvent) {
-                    handler.handle(new SynchronizedDispatchConnection<Object>(connectionConnectEvent.getConnection()));
+            Action<ConnectCompletion> connectEvent = new Action<ConnectCompletion>() {
+                public void execute(ConnectCompletion completion) {
+                    handler.handle(new SynchronizedDispatchConnection<Object>(completion.create(getClass().getClassLoader())));
                 }
             };
 
-            acceptor = incomingConnector.accept(connectEvent, getClass().getClassLoader(), false);
+            acceptor = incomingConnector.accept(connectEvent, false);
             started = true;
             return acceptor.getAddress();
         } finally {
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
index b12f0eb..1bb1daf 100644
--- 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
@@ -16,8 +16,8 @@
 
 package org.gradle.launcher.daemon.server;
 
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
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
index df71d2d..076b3cb 100644
--- 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
@@ -18,7 +18,7 @@ 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.concurrent.Stoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
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 20ff490..ae2e60c 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,15 +18,12 @@ 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.concurrent.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 implements Stoppable {
 
     private static final Logger LOGGER = Logging.getLogger(DomainRegistryUpdater.class);
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 255d460..8a8ee89 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
@@ -24,8 +24,6 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Connection decorator that synchronizes dispatching.
- * <p>
- * by Szczepan Faber, created at: 2/27/12
  */
 public class SynchronizedDispatchConnection<T> implements Connection<T> {
     private static final Logger LOGGER = LoggerFactory.getLogger(SynchronizedDispatchConnection.class);
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
index 2cf02b9..ec3df45 100644
--- 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
@@ -17,7 +17,7 @@
 package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.api.Nullable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.launcher.daemon.protocol.BuildStarted;
 import org.gradle.launcher.daemon.protocol.DaemonUnavailable;
 import org.gradle.launcher.daemon.protocol.Result;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonHygieneAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonHygieneAction.java
new file mode 100644
index 0000000..fb9b40d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonHygieneAction.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
+
+public class DaemonHygieneAction implements DaemonCommandAction {
+
+    private final long gcDelay;
+    private TimeProvider timeProvider;
+    private long nextGcHint;
+
+    public DaemonHygieneAction() {
+        //by default, don't hint for gc more often than once per 2 minutes
+        //because it is a full scan
+        this(1000 * 60 * 2, new TrueTimeProvider());
+    }
+
+    DaemonHygieneAction(long gcDelay, TimeProvider timeProvider) {
+        this.gcDelay = gcDelay;
+        this.timeProvider = timeProvider;
+    }
+
+    public void execute(DaemonCommandExecution execution) {
+        execution.proceed();
+        long time = timeProvider.getCurrentTime();
+        if (time > nextGcHint) {
+            gc();
+            nextGcHint = time + gcDelay;
+        }
+    }
+
+    void gc() {
+        System.gc();
+    }
+}
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 b05f1d6..c3e0adb 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
@@ -34,14 +34,17 @@ import java.util.List;
 public class DefaultDaemonCommandExecuter implements DaemonCommandExecuter {
     private final LoggingOutputInternal loggingOutput;
     private final GradleLauncherFactory launcherFactory;
+    private DaemonCommandAction hygieneAction;
     private final ProcessEnvironment processEnvironment;
     private final File daemonLog;
 
-    public DefaultDaemonCommandExecuter(GradleLauncherFactory launcherFactory, ProcessEnvironment processEnvironment, LoggingManagerInternal loggingOutput, File daemonLog) {
+    public DefaultDaemonCommandExecuter(GradleLauncherFactory launcherFactory, ProcessEnvironment processEnvironment,
+                                        LoggingManagerInternal loggingOutput, File daemonLog, DaemonCommandAction hygieneAction) {
         this.processEnvironment = processEnvironment;
         this.daemonLog = daemonLog;
         this.loggingOutput = loggingOutput;
         this.launcherFactory = launcherFactory;
+        this.hygieneAction = hygieneAction;
     }
 
     public void executeCommand(DaemonConnection connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl, Runnable commandAbandoned) {
@@ -59,6 +62,7 @@ public class DefaultDaemonCommandExecuter implements DaemonCommandExecuter {
         DaemonDiagnostics daemonDiagnostics = new DaemonDiagnostics(daemonLog, daemonContext.getPid());
         return new LinkedList<DaemonCommandAction>(Arrays.asList(
             new CatchAndForwardDaemonFailure(),
+            hygieneAction,
             new HandleStop(),
             new StartBuildOrRespondWithBusy(daemonDiagnostics),
             new EstablishBuildEnvironment(processEnvironment),
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 8609e4f..8fde07a 100755
--- 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
@@ -47,6 +47,7 @@ public class EstablishBuildEnvironment extends BuildCommandOnly {
 
         for (Map.Entry<String, String> entry : build.getParameters().getSystemProperties().entrySet()) {
             if (SystemProperties.getStandardProperties().contains(entry.getKey())) { continue; }
+            if (SystemProperties.getNonStandardImportantProperties().contains(entry.getKey())) { continue; }
             if (entry.getKey().startsWith("sun.")) { continue; }
             System.setProperty(entry.getKey(), entry.getValue());
         }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
index 093c5ef..bf7deb3 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
@@ -20,7 +20,7 @@ import org.gradle.api.logging.Logging;
 import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.protocol.Build;
-import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
+import org.gradle.launcher.exec.InProcessBuildActionExecuter;
 import org.gradle.launcher.exec.ReportedException;
 
 /**
@@ -40,7 +40,7 @@ public class ExecuteBuild extends BuildCommandOnly {
 
     protected void doBuild(DaemonCommandExecution execution, Build build) {
         LOGGER.info("Executing build with daemon context: {}", execution.getDaemonContext());
-        InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(launcherFactory);
+        InProcessBuildActionExecuter executer = new InProcessBuildActionExecuter(launcherFactory);
         try {
             execution.setResult(executer.execute(build.getAction(), build.getParameters()));
         } catch (ReportedException e) {
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 5dafad2..f36d81a 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,6 +15,7 @@
  */
 package org.gradle.launcher.daemon.server.exec;
 
+import org.apache.commons.io.IOUtils;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.UncheckedException;
@@ -73,7 +74,8 @@ public class ForwardClientInput implements DaemonCommandAction {
                 });
             } finally {
                 execution.getConnection().onStdin(null);
-                replacementStdin.close();
+                IOUtils.closeQuietly(replacementStdin);
+                IOUtils.closeQuietly(inputSource);
             }
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/NoOpDaemonCommandAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/NoOpDaemonCommandAction.java
new file mode 100644
index 0000000..411a9b1
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/NoOpDaemonCommandAction.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 NoOpDaemonCommandAction implements DaemonCommandAction {
+    public void execute(DaemonCommandExecution execution) {
+        execution.proceed();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionExecuter.java
new file mode 100644
index 0000000..d6cb1f7
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionExecuter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher.exec;
+
+import org.gradle.initialization.BuildAction;
+
+public interface BuildActionExecuter<P> {
+    /**
+     * Executes the given action, and returns the result.
+     *
+     * @param action The action
+     * @param <T> The result type
+     * @return The result.
+     */
+    <T> T execute(BuildAction<T> action, P actionParameters);
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/GradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/GradleLauncherActionExecuter.java
deleted file mode 100644
index 99ade21..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/GradleLauncherActionExecuter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.launcher.exec;
-
-import org.gradle.initialization.GradleLauncherAction;
-
-public interface GradleLauncherActionExecuter<P> {
-    /**
-     * Executes the given action, and returns the result. The given action may also implement {@link InitializationAware <T>}.
-     *
-     * @param action The action
-     * @param <T> The result type
-     * @return The result.
-     */
-    <T> T execute(GradleLauncherAction<T> action, P actionParameters);
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessBuildActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessBuildActionExecuter.java
new file mode 100644
index 0000000..4e07adf
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessBuildActionExecuter.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.launcher.exec;
+
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.initialization.BuildController;
+import org.gradle.initialization.BuildAction;
+import org.gradle.initialization.GradleLauncherFactory;
+
+public class InProcessBuildActionExecuter implements BuildActionExecuter<BuildActionParameters> {
+    private final GradleLauncherFactory gradleLauncherFactory;
+
+    public InProcessBuildActionExecuter(GradleLauncherFactory gradleLauncherFactory) {
+        this.gradleLauncherFactory = gradleLauncherFactory;
+    }
+
+    public <T> T execute(BuildAction<T> action, BuildActionParameters actionParameters) {
+        DefaultBuildController buildController = new DefaultBuildController(gradleLauncherFactory, actionParameters);
+        return action.run(buildController);
+    }
+
+    private static class DefaultBuildController implements BuildController {
+        private final BuildActionParameters actionParameters;
+        private final GradleLauncherFactory gradleLauncherFactory;
+        private GradleLauncher gradleLauncher;
+        private StartParameter startParameter = new StartParameter();
+
+        private DefaultBuildController(GradleLauncherFactory gradleLauncherFactory, BuildActionParameters actionParameters) {
+            this.gradleLauncherFactory = gradleLauncherFactory;
+            this.actionParameters = actionParameters;
+        }
+
+        public void setStartParameter(StartParameter startParameter) {
+            if (gradleLauncher != null) {
+                throw new IllegalStateException("Cannot change start parameter after launcher has been created.");
+            }
+            this.startParameter = startParameter;
+        }
+
+        public GradleLauncher getLauncher() {
+            if (gradleLauncher == null) {
+                gradleLauncher = gradleLauncherFactory.newInstance(startParameter, actionParameters.getBuildRequestMetaData());
+            }
+            return gradleLauncher;
+        }
+
+        public void run() {
+            check(getLauncher().run());
+        }
+
+        public void configure() {
+            check(getLauncher().getBuildAnalysis());
+        }
+
+        private void check(BuildResult buildResult) {
+            if (buildResult.getFailure() != null) {
+                throw new ReportedException(buildResult.getFailure());
+            }
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuter.java
deleted file mode 100644
index e9feb3e..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuter.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.launcher.exec;
-
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.initialization.GradleLauncherFactory;
-
-public class InProcessGradleLauncherActionExecuter implements GradleLauncherActionExecuter<BuildActionParameters> {
-    private final GradleLauncherFactory gradleLauncherFactory;
-
-    public InProcessGradleLauncherActionExecuter(GradleLauncherFactory gradleLauncherFactory) {
-        this.gradleLauncherFactory = gradleLauncherFactory;
-    }
-
-    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters actionParameters) {
-        StartParameter startParameter = new StartParameter();
-        if (action instanceof InitializationAware) {
-            InitializationAware initializationAware = (InitializationAware) action;
-            startParameter = initializationAware.configureStartParameter();
-        }
-        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter, actionParameters.getBuildRequestMetaData());
-        BuildResult buildResult = action.run(gradleLauncher);
-        Throwable failure = buildResult.getFailure();
-        if (failure != null) {
-            throw new ReportedException(failure);
-        }
-        return action.getResult();
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InitializationAware.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InitializationAware.java
deleted file mode 100644
index 87fdb09..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InitializationAware.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.launcher.exec;
-
-import org.gradle.StartParameter;
-
-public interface InitializationAware {
-    StartParameter configureStartParameter();
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleProjectTask.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleProjectTask.java
new file mode 100644
index 0000000..07c9c52
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleProjectTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.impl;
+
+import org.gradle.tooling.internal.gradle.PartialGradleProject;
+
+public class LaunchableGradleProjectTask extends LaunchableGradleTask {
+    private PartialGradleProject project;
+
+    public PartialGradleProject getProject() {
+        return project;
+    }
+
+    public LaunchableGradleProjectTask setProject(PartialGradleProject project) {
+        this.project = project;
+        return this;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTask.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTask.java
new file mode 100644
index 0000000..84752e7
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTask.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.impl;
+
+import java.io.Serializable;
+
+public class LaunchableGradleTask implements Serializable, LaunchableImplementation {
+
+    private String path;
+    private String name;
+    private String description;
+    private String displayName;
+
+    public String getPath() {
+        return path;
+    }
+
+    public LaunchableGradleTask setPath(String path) {
+        this.path = path;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public LaunchableGradleTask setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public LaunchableGradleTask setDisplayName(String displayName) {
+        this.displayName = displayName;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public LaunchableGradleTask setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public String getTaskName() {
+        return path;
+    }
+
+    public String getProjectPath() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{path='" + path + "'}";
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTaskSelector.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTaskSelector.java
new file mode 100644
index 0000000..1a35459
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTaskSelector.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.impl;
+
+import org.gradle.api.Nullable;
+import org.gradle.tooling.model.TaskSelector;
+
+import java.io.Serializable;
+
+/**
+ * Data used for {@link org.gradle.tooling.model.TaskSelector}.
+ */
+public class LaunchableGradleTaskSelector implements TaskSelector, LaunchableImplementation, Serializable {
+    private String name;
+    private String displayName;
+    private String description;
+    private String taskName;
+    private String projectPath;
+
+    public String getName() {
+        return name;
+    }
+
+    public LaunchableGradleTaskSelector setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    @Nullable
+    public String getDescription() {
+        return description;
+    }
+
+    public LaunchableGradleTaskSelector setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public LaunchableGradleTaskSelector setDisplayName(String displayName) {
+        this.displayName = displayName;
+        return this;
+    }
+
+    public String getTaskName() {
+        return taskName;
+    }
+
+    public LaunchableGradleTaskSelector setTaskName(String taskName) {
+        this.taskName = taskName;
+        return this;
+    }
+
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    public LaunchableGradleTaskSelector setProjectPath(String projectPath) {
+        this.projectPath = projectPath;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "LaunchableGradleTaskSelector{"
+                + "name='" + name + "' "
+                + "description='" + description + "'}";
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableImplementation.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableImplementation.java
new file mode 100644
index 0000000..52dd3dc
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableImplementation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.impl;
+
+import org.gradle.api.Nullable;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
+
+/**
+ * SPI for launchables providing information necessary to initiate the build.
+ *
+ * @since 1.12
+ */
+public interface LaunchableImplementation extends InternalLaunchable {
+    /** Task path for real tasks, selector name for task selectors. */
+    String getTaskName();
+    @Nullable String getProjectPath();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildActionResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildActionResult.java
new file mode 100644
index 0000000..3206cb5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildActionResult.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Nullable;
+
+import java.io.Serializable;
+
+public class BuildActionResult implements Serializable {
+    @Nullable
+    final SerializedPayload result;
+    @Nullable
+    final SerializedPayload failure;
+
+    public BuildActionResult(SerializedPayload result, SerializedPayload failure) {
+        this.result = result;
+        this.failure = failure;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildModelAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildModelAction.java
new file mode 100644
index 0000000..badc54b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildModelAction.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.initialization.*;
+import org.gradle.tooling.internal.protocol.InternalUnsupportedModelException;
+import org.gradle.tooling.model.internal.ProjectSensitiveToolingModelBuilder;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+import org.gradle.tooling.provider.model.UnknownModelException;
+
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class BuildModelAction implements BuildAction<BuildActionResult>, Serializable {
+    private final boolean runTasks;
+    private final String modelName;
+
+    public BuildModelAction(String modelName, boolean runTasks) {
+        this.modelName = modelName;
+        this.runTasks = runTasks;
+    }
+
+    public BuildActionResult run(BuildController buildController) {
+        DefaultGradleLauncher launcher = (DefaultGradleLauncher) buildController.getLauncher();
+        final PayloadSerializer payloadSerializer = launcher.getGradle().getServices().get(PayloadSerializer.class);
+
+        // The following is all very awkward because the contract for BuildController is still just a
+        // rough wrapper around GradleLauncher, which means we can only get at the model and various
+        // services by using listeners.
+
+        final AtomicReference<Object> model = new AtomicReference<Object>();
+        final AtomicReference<RuntimeException> failure = new AtomicReference<RuntimeException>();
+        final Action<GradleInternal> action = new Action<GradleInternal>() {
+            public void execute(GradleInternal gradle) {
+                ToolingModelBuilderRegistry builderRegistry = getToolingModelBuilderRegistry(gradle);
+                ToolingModelBuilder builder;
+                try {
+                    builder = builderRegistry.getBuilder(modelName);
+                } catch (UnknownModelException e) {
+                    failure.set((InternalUnsupportedModelException) (new InternalUnsupportedModelException().initCause(e)));
+                    return;
+                }
+                Object result;
+                if (builder instanceof ProjectSensitiveToolingModelBuilder) {
+                    result = ((ProjectSensitiveToolingModelBuilder) builder).buildAll(modelName, gradle.getDefaultProject(), true);
+                } else {
+                    result = builder.buildAll(modelName, gradle.getDefaultProject());
+                }
+                model.set(result);
+            }
+        };
+
+        if (runTasks) {
+            launcher.addListener(new TasksCompletionListener() {
+                public void onTasksFinished(GradleInternal gradle) {
+                    action.execute(gradle);
+                }
+            });
+            buildController.run();
+        } else {
+            launcher.addListener(new ModelConfigurationListener() {
+                public void onConfigure(GradleInternal gradle) {
+                    // Currently need to force everything to be configured
+                    ensureAllProjectsEvaluated(gradle);
+                    action.execute(gradle);
+                }
+            });
+            buildController.configure();
+        }
+
+        if (failure.get() != null) {
+            throw failure.get();
+        }
+        return new BuildActionResult(payloadSerializer.serialize(model.get()), null);
+    }
+
+    private ToolingModelBuilderRegistry getToolingModelBuilderRegistry(GradleInternal gradle) {
+        return gradle.getDefaultProject().getServices().get(ToolingModelBuilderRegistry.class);
+    }
+
+    private void ensureAllProjectsEvaluated(GradleInternal gradle) {
+        gradle.getRootProject().allprojects((Action) new Action<ProjectInternal>() {
+            public void execute(ProjectInternal projectInternal) {
+                projectInternal.evaluate();
+            }
+        });
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClassLoaderDetails.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClassLoaderDetails.java
new file mode 100644
index 0000000..2976587
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClassLoaderDetails.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.internal.classloader.ClassLoaderSpec;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class ClassLoaderDetails implements Serializable {
+    // TODO:ADAM - using a UUID means we create a ClassLoader hierarchy for each daemon process we talk to. Instead, use the spec to decide whether to reuse a ClassLoader
+    final UUID uuid;
+    final ClassLoaderSpec spec;
+    final List<ClassLoaderDetails> parents = new ArrayList<ClassLoaderDetails>();
+
+    public ClassLoaderDetails(UUID uuid, ClassLoaderSpec spec) {
+        this.uuid = uuid;
+        this.spec = spec;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClasspathInferer.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClasspathInferer.java
new file mode 100644
index 0000000..cd2c1f6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClasspathInferer.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.MapMaker;
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.api.GradleException;
+import org.gradle.internal.classloader.ClasspathUtil;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+ at ThreadSafe
+public class ClasspathInferer {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ClasspathInferer.class);
+    private final Lock lock = new ReentrantLock();
+    private final Map<Class<?>, Collection<URL>> classPathCache;
+
+    public ClasspathInferer() {
+        this.classPathCache = new MapMaker().weakKeys().makeMap();
+    }
+
+    public void getClassPathFor(Class<?> targetClass, Collection<URL> dest) {
+        lock.lock();
+        try {
+            Collection<URL> classPath = classPathCache.get(targetClass);
+            if (classPath == null) {
+                Set<Class<?>> visited = new HashSet<Class<?>>();
+                classPath = new LinkedHashSet<URL>();
+                find(targetClass, visited, classPath);
+                classPathCache.put(targetClass, classPath);
+            }
+            dest.addAll(classPath);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Locates the classpath required by the given target class. Traverses the dependency graph of classes used by the specified class and collects the result in the given collection.
+     */
+    private void find(Class<?> target, Collection<Class<?>> visited, Collection<URL> dest) {
+        ClassLoader targetClassLoader = target.getClassLoader();
+        if (targetClassLoader == null) {
+            // A system class, skip it
+            return;
+        }
+        if (!visited.add(target)) {
+            // Already seen this class, skip it
+            return;
+        }
+
+        String resourceName = target.getName().replace(".", "/") + ".class";
+        URL resource = targetClassLoader.getResource(resourceName);
+        try {
+            if (resource == null) {
+                LOGGER.warn("Could not determine classpath for {}", target);
+                return;
+            }
+
+            File classPathRoot = ClasspathUtil.getClasspathForResource(resource, resourceName);
+            dest.add(classPathRoot.toURI().toURL());
+
+            // To determine the dependencies of the class, load up the byte code and look for CONSTANT_Class entries in the constant pool
+
+            ClassReader reader;
+            InputStream inputStream = resource.openStream();
+            try {
+                reader = new ClassReader(inputStream);
+            } finally {
+                inputStream.close();
+            }
+
+            char[] charBuffer = new char[reader.getMaxStringLength()];
+            for (int i = 1; i < reader.getItemCount(); i++) {
+                int itemOffset = reader.getItem(i);
+                if (itemOffset > 0 && reader.readByte(itemOffset - 1) == 7) {
+                    // A CONSTANT_Class entry, read the class descriptor
+                    String classDescriptor = reader.readUTF8(itemOffset, charBuffer);
+                    Type type = Type.getObjectType(classDescriptor);
+                    while (type.getSort() == Type.ARRAY) {
+                        type = type.getElementType();
+                    }
+                    if (type.getSort() != Type.OBJECT) {
+                        // A primitive type
+                        continue;
+                    }
+                    String className = type.getClassName();
+                    if (className.equals(target.getName())) {
+                        // A reference to this class
+                        continue;
+                    }
+
+                    Class<?> cl;
+                    try {
+                        cl = Class.forName(className, false, targetClassLoader);
+                    } catch (ClassNotFoundException e) {
+                        // This is fine, just ignore it
+                        LOGGER.warn("Could not determine classpath for {}", target);
+                        continue;
+                    }
+                    find(cl, visited, dest);
+                }
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not determine the class-path for %s.", target), e);
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClientProvidedBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClientProvidedBuildAction.java
new file mode 100644
index 0000000..004925e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClientProvidedBuildAction.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Action;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.initialization.BuildAction;
+import org.gradle.initialization.BuildController;
+import org.gradle.initialization.DefaultGradleLauncher;
+import org.gradle.initialization.ModelConfigurationListener;
+import org.gradle.tooling.internal.protocol.InternalBuildAction;
+import org.gradle.tooling.internal.protocol.InternalBuildActionFailureException;
+import org.gradle.tooling.internal.protocol.InternalBuildController;
+
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicReference;
+
+class ClientProvidedBuildAction implements BuildAction<BuildActionResult>, Serializable {
+    private final SerializedPayload action;
+
+    public ClientProvidedBuildAction(SerializedPayload action) {
+        this.action = action;
+    }
+
+    public BuildActionResult run(final BuildController buildController) {
+        final DefaultGradleLauncher gradleLauncher = (DefaultGradleLauncher) buildController.getLauncher();
+        final PayloadSerializer payloadSerializer = gradleLauncher.getGradle().getServices().get(PayloadSerializer.class);
+        final InternalBuildAction<?> action = (InternalBuildAction<?>) payloadSerializer.deserialize(this.action);
+
+        // The following is all very awkward because the contract for BuildController is still just a
+        // rough wrapper around GradleLauncher, which means we can only get at the model and various
+        // services by using listeners.
+
+        final AtomicReference<Object> result = new AtomicReference<Object>();
+        final AtomicReference<RuntimeException> failure = new AtomicReference<RuntimeException>();
+
+        gradleLauncher.addListener(new ModelConfigurationListener() {
+            public void onConfigure(final GradleInternal gradle) {
+                // Currently need to force everything to be configured
+                ensureAllProjectsEvaluated(gradle);
+                InternalBuildController internalBuildController = new DefaultBuildController(gradle);
+                Object model = null;
+                try {
+                    model = action.execute(internalBuildController);
+                } catch (RuntimeException e) {
+                    failure.set(new InternalBuildActionFailureException(e));
+                }
+                result.set(model);
+            }
+        });
+        buildController.configure();
+
+        if (failure.get() != null) {
+            return new BuildActionResult(null, payloadSerializer.serialize(failure.get()));
+        }
+        return new BuildActionResult(payloadSerializer.serialize(result.get()), null);
+    }
+
+    private void ensureAllProjectsEvaluated(GradleInternal gradle) {
+        gradle.getRootProject().allprojects((Action) new Action<ProjectInternal>() {
+            public void execute(ProjectInternal projectInternal) {
+                projectInternal.evaluate();
+            }
+        });
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClientSidePayloadClassLoaderRegistry.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClientSidePayloadClassLoaderRegistry.java
new file mode 100644
index 0000000..643c916
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ClientSidePayloadClassLoaderRegistry.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 net.jcip.annotations.ThreadSafe;
+import org.gradle.internal.classloader.MutableURLClassLoader;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+ at ThreadSafe
+public class ClientSidePayloadClassLoaderRegistry implements PayloadClassLoaderRegistry {
+    private static final short CLIENT_CLASS_LOADER_ID = 1;
+    private final PayloadClassLoaderRegistry delegate;
+    private final Lock lock = new ReentrantLock();
+    private final ClasspathInferer classpathInferer;
+    private final Map<UUID, LocalClassLoader> classLoaders = new LinkedHashMap<UUID, LocalClassLoader>();
+
+    public ClientSidePayloadClassLoaderRegistry(PayloadClassLoaderRegistry delegate, ClasspathInferer classpathInferer) {
+        this.delegate = delegate;
+        this.classpathInferer = classpathInferer;
+    }
+
+    public SerializeMap newSerializeSession() {
+        final Set<ClassLoader> candidates = new LinkedHashSet<ClassLoader>();
+        final Set<URL> classPath = new LinkedHashSet<URL>();
+
+        return new SerializeMap() {
+            public short visitClass(Class<?> target) {
+                classpathInferer.getClassPathFor(target, classPath);
+                candidates.add(target.getClassLoader());
+                return CLIENT_CLASS_LOADER_ID;
+            }
+
+            public Map<Short, ClassLoaderDetails> getClassLoaders() {
+                lock.lock();
+                UUID uuid;
+                try {
+                    uuid = getUuid(candidates);
+                } finally {
+                    lock.unlock();
+                }
+                return Collections.singletonMap(CLIENT_CLASS_LOADER_ID, new ClassLoaderDetails(uuid, new MutableURLClassLoader.Spec(new ArrayList<URL>(classPath))));
+            }
+        };
+    }
+
+    public DeserializeMap newDeserializeSession() {
+        final DeserializeMap deserializeMap = delegate.newDeserializeSession();
+        return new DeserializeMap() {
+            public Class<?> resolveClass(ClassLoaderDetails classLoaderDetails, String className) throws ClassNotFoundException {
+                Set<ClassLoader> candidates;
+                lock.lock();
+                try {
+                    candidates = getClassLoaders(classLoaderDetails.uuid);
+                } finally {
+                    lock.unlock();
+                }
+                if (candidates != null) {
+                    // TODO:ADAM - This isn't quite right
+                    for (ClassLoader candidate : candidates) {
+                        try {
+                            return candidate.loadClass(className);
+                        } catch (ClassNotFoundException e) {
+                            // Ignore
+                        }
+                    }
+                    throw new UnsupportedOperationException("Unexpected class received in response.");
+                }
+                return deserializeMap.resolveClass(classLoaderDetails, className);
+            }
+        };
+    }
+
+    private Set<ClassLoader> getClassLoaders(UUID uuid) {
+        LocalClassLoader localClassLoader = classLoaders.get(uuid);
+        if (localClassLoader == null) {
+            return null;
+        }
+        Set<ClassLoader> candidates = new LinkedHashSet<ClassLoader>();
+        for (Reference<ClassLoader> reference : localClassLoader.classLoaders) {
+            ClassLoader classLoader = reference.get();
+            if (classLoader != null) {
+                candidates.add(classLoader);
+            }
+        }
+        return candidates;
+    }
+
+    private UUID getUuid(Set<ClassLoader> candidates) {
+        for (LocalClassLoader localClassLoader : new ArrayList<LocalClassLoader>(classLoaders.values())) {
+            Set<ClassLoader> localCandidates = new LinkedHashSet<ClassLoader>();
+            for (Reference<ClassLoader> reference : localClassLoader.classLoaders) {
+                ClassLoader cl = reference.get();
+                if (cl != null) {
+                    localCandidates.add(cl);
+                }
+            }
+            if (localCandidates.isEmpty()) {
+                classLoaders.remove(localClassLoader.uuid);
+                continue;
+            }
+            if (localCandidates.equals(candidates)) {
+                return localClassLoader.uuid;
+            }
+        }
+
+        LocalClassLoader details = new LocalClassLoader(UUID.randomUUID());
+        for (ClassLoader candidate : candidates) {
+            details.classLoaders.add(new WeakReference<ClassLoader>(candidate));
+        }
+        classLoaders.put(details.uuid, details);
+        return details.uuid;
+    }
+
+    private static class LocalClassLoader {
+        private final Set<Reference<ClassLoader>> classLoaders = new LinkedHashSet<Reference<ClassLoader>>();
+        private final UUID uuid;
+
+        private LocalClassLoader(UUID uuid) {
+            this.uuid = uuid;
+        }
+    }
+}
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 d1537d6..dce07f0 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
@@ -15,65 +15,108 @@
  */
 package org.gradle.tooling.internal.provider;
 
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.initialization.BuildAction;
+import org.gradle.initialization.BuildController;
 import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.daemon.configuration.GradleProperties;
-import org.gradle.launcher.exec.InitializationAware;
-import org.gradle.logging.ShowStacktrace;
+import org.gradle.launcher.cli.converter.PropertiesToStartParameterConverter;
+import org.gradle.tooling.internal.impl.LaunchableImplementation;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
 import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
 import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
+import org.gradle.tooling.model.UnsupportedMethodException;
 
 import java.io.File;
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+class ConfiguringBuildAction<T> implements BuildAction<T>, Serializable {
+    private static List<InternalLaunchable> getLaunchables(ProviderOperationParameters parameters) {
+        try {
+            return parameters.getLaunchables();
+        } catch (UnsupportedMethodException ume) {
+            // older consumer version
+            return null;
+        }
+    }
 
-class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, InitializationAware, Serializable {
     private LogLevel buildLogLevel;
     private List<String> arguments;
     private List<String> tasks;
-    private GradleLauncherAction<T> action;
+    private List<InternalLaunchable> launchables;
+    private BuildAction<? extends T> action;
     private File projectDirectory;
     private File gradleUserHomeDir;
     private Boolean searchUpwards;
+    private Map<String, String> properties = new HashMap<String, String>();
 
     // Important that this is constructed on the client so that it has the right gradleHomeDir internally
     private final StartParameter startParameterTemplate = new StartParameter();
-    private boolean configureOnDemand;
 
     public ConfiguringBuildAction() {}
 
-    public ConfiguringBuildAction(ProviderOperationParameters parameters, GradleLauncherAction<T> action, GradleProperties gradleProperties) {
-        this.configureOnDemand = gradleProperties.isConfigureOnDemand();
+    public ConfiguringBuildAction(ProviderOperationParameters parameters, BuildAction<? extends T> action, Map<String, String> properties) {
+        this.properties.putAll(properties);
         this.gradleUserHomeDir = parameters.getGradleUserHomeDir();
         this.projectDirectory = parameters.getProjectDir();
         this.searchUpwards = parameters.isSearchUpwards();
         this.buildLogLevel = parameters.getBuildLogLevel();
         this.arguments = parameters.getArguments(Collections.<String>emptyList());
         this.tasks = parameters.getTasks();
+        this.launchables = getLaunchables(parameters);
         this.action = action;
     }
 
-    public StartParameter configureStartParameter() {
+    StartParameter configureStartParameter() {
+        return configureStartParameter(new PropertiesToStartParameterConverter());
+    }
+
+    StartParameter configureStartParameter(PropertiesToStartParameterConverter propertiesToStartParameterConverter) {
         StartParameter startParameter = startParameterTemplate.newInstance();
+
         startParameter.setProjectDir(projectDirectory);
         if (gradleUserHomeDir != null) {
             startParameter.setGradleUserHomeDir(gradleUserHomeDir);
         }
-        if (searchUpwards != null) {
-            startParameter.setSearchUpwards(searchUpwards);
-        }
 
-        if (tasks != null) {
+        if (launchables != null) {
+            List<String> allTasks = new ArrayList<String>();
+            String projectPath = null;
+            for (InternalLaunchable launchable : launchables) {
+                if (launchable instanceof LaunchableImplementation) {
+                    LaunchableImplementation launchableImpl = (LaunchableImplementation) launchable;
+                    allTasks.add(launchableImpl.getTaskName());
+                    if (launchableImpl.getProjectPath() != null) {
+                        if (projectPath != null && !projectPath.equals(launchableImpl.getProjectPath())) {
+                            throw new InternalUnsupportedBuildArgumentException(
+                                    "Problem with provided launchable arguments: " + launchables + ". "
+                                            + "\nOnly selector from the same Gradle project can be built."
+                            );
+                        }
+                        projectPath = launchableImpl.getProjectPath();
+                    }
+                } else {
+                    throw new InternalUnsupportedBuildArgumentException(
+                            "Problem with provided launchable arguments: " + launchables + ". "
+                                    + "\nOnly objects from this provider can be built."
+                    );
+                }
+            }
+            if (projectPath != null) {
+                startParameter.setProjectPath(projectPath);
+            }
+            startParameter.setTaskNames(allTasks);
+        } else if (tasks != null) {
             startParameter.setTaskNames(tasks);
         }
 
-        startParameter.setConfigureOnDemand(configureOnDemand);
+        propertiesToStartParameterConverter.convert(properties, startParameter);
 
         if (arguments != null) {
             DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
@@ -91,19 +134,19 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
             }
         }
 
+        if (searchUpwards != null) {
+            startParameter.setSearchUpwards(searchUpwards);
+        }
+
         if (buildLogLevel != null) {
             startParameter.setLogLevel(buildLogLevel);
         }
 
-        startParameter.setShowStacktrace(ShowStacktrace.ALWAYS);
         return startParameter;
     }
 
-    public BuildResult run(GradleLauncher launcher) {
-        return action.run(launcher);
-    }
-
-    public T getResult() {
-        return action.getResult();
+    public T run(BuildController buildController) {
+        buildController.setStartParameter(configureStartParameter());
+        return action.run(buildController);
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConnectionScopeServices.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConnectionScopeServices.java
new file mode 100644
index 0000000..28055fd
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConnectionScopeServices.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.initialization.GradleLauncherFactory;
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.GlobalScopeServices;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+
+/**
+ * Shared services for a tooling API provider connection.
+ */
+public class ConnectionScopeServices {
+    private final LoggingServiceRegistry loggingServices;
+
+    public ConnectionScopeServices(LoggingServiceRegistry loggingServices) {
+        this.loggingServices = loggingServices;
+    }
+
+    void configure(ServiceRegistration serviceRegistration) {
+        serviceRegistration.add(LoggingServiceRegistry.class, loggingServices);
+        serviceRegistration.addProvider(new GlobalScopeServices(false));
+    }
+
+    ProviderConnection createProviderConnection(GradleLauncherFactory gradleLauncherFactory) {
+        return new ProviderConnection(
+                loggingServices,
+                gradleLauncherFactory,
+                new PayloadSerializer(
+                        new ClientSidePayloadClassLoaderRegistry(
+                                new DefaultPayloadClassLoaderRegistry(
+                                        new ModelClassLoaderFactory()),
+                                new ClasspathInferer()))
+        );
+    }
+
+    ProtocolToModelAdapter createProtocolToModelAdapter() {
+        return new ProtocolToModelAdapter();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonBuildActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonBuildActionExecuter.java
new file mode 100644
index 0000000..5c0086b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonBuildActionExecuter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.initialization.BuildAction;
+import org.gradle.internal.SystemProperties;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.exec.BuildActionExecuter;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.launcher.exec.ReportedException;
+import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
+
+public class DaemonBuildActionExecuter implements BuildActionExecuter<ProviderOperationParameters> {
+    private final BuildActionExecuter<BuildActionParameters> executer;
+    private final DaemonParameters parameters;
+
+    public DaemonBuildActionExecuter(BuildActionExecuter<BuildActionParameters> executer, DaemonParameters parameters) {
+        this.executer = executer;
+        this.parameters = parameters;
+    }
+
+    public <T> T execute(BuildAction<T> action, ProviderOperationParameters actionParameters) {
+        BuildActionParameters parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), actionParameters.getStartTime(),
+                this.parameters.getEffectiveSystemProperties(), System.getenv(), SystemProperties.getCurrentDir(), actionParameters.getBuildLogLevel());
+        try {
+            return executer.execute(action, parameters);
+        } catch (ReportedException e) {
+            throw new BuildExceptionVersion1(e.getCause());
+        }
+    }
+}
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
deleted file mode 100644
index d9cdc86..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.provider;
-
-import org.gradle.configuration.GradleLauncherMetaData;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.internal.SystemProperties;
-import org.gradle.launcher.daemon.configuration.DaemonParameters;
-import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.DefaultBuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.launcher.exec.ReportedException;
-import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
-import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
-
-public class DaemonGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
-    private final GradleLauncherActionExecuter<BuildActionParameters> executer;
-    private final DaemonParameters parameters;
-
-    public DaemonGradleLauncherActionExecuter(GradleLauncherActionExecuter<BuildActionParameters> executer, DaemonParameters parameters) {
-        this.executer = executer;
-        this.parameters = parameters;
-    }
-
-    public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
-        BuildActionParameters parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), actionParameters.getStartTime(),
-                this.parameters.getEffectiveSystemProperties(), System.getenv(), SystemProperties.getCurrentDir(), actionParameters.getBuildLogLevel());
-        try {
-            return executer.execute(action, parameters);
-        } catch (ReportedException e) {
-            throw new BuildExceptionVersion1(e.getCause());
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultBuildController.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultBuildController.java
new file mode 100644
index 0000000..38476a5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultBuildController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api.internal.project.ProjectInternal;
+import org.gradle.tooling.internal.gradle.GradleProjectIdentity;
+import org.gradle.tooling.internal.protocol.*;
+import org.gradle.tooling.internal.provider.connection.ProviderBuildResult;
+import org.gradle.tooling.model.internal.ProjectSensitiveToolingModelBuilder;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+import org.gradle.tooling.provider.model.UnknownModelException;
+
+class DefaultBuildController implements InternalBuildController {
+    private final GradleInternal gradle;
+
+    public DefaultBuildController(GradleInternal gradle) {
+        this.gradle = gradle;
+    }
+
+    public BuildResult<?> getBuildModel() throws BuildExceptionVersion1 {
+        return new ProviderBuildResult<Object>(gradle);
+    }
+
+    public BuildResult<?> getModel(Object target, ModelIdentifier modelIdentifier) throws BuildExceptionVersion1, InternalUnsupportedModelException {
+        ToolingModelBuilderRegistry modelBuilderRegistry;
+        ProjectInternal project;
+        boolean isImplicitProject;
+        if (target == null) {
+            project = gradle.getDefaultProject();
+            isImplicitProject = true;
+        } else if (target instanceof GradleProjectIdentity) {
+            GradleProjectIdentity gradleProject = (GradleProjectIdentity) target;
+            project = gradle.getRootProject().project(gradleProject.getPath());
+            isImplicitProject = false;
+        } else {
+            throw new IllegalArgumentException("Don't know how to build models for " + target);
+        }
+        modelBuilderRegistry = project.getServices().get(ToolingModelBuilderRegistry.class);
+
+        ToolingModelBuilder builder;
+        try {
+            builder = modelBuilderRegistry.getBuilder(modelIdentifier.getName());
+        } catch (UnknownModelException e) {
+            throw (InternalUnsupportedModelException) (new InternalUnsupportedModelException()).initCause(e);
+        }
+        Object model;
+        if (builder instanceof ProjectSensitiveToolingModelBuilder) {
+            model = ((ProjectSensitiveToolingModelBuilder) builder).buildAll(modelIdentifier.getName(), project, isImplicitProject);
+        } else {
+            model = builder.buildAll(modelIdentifier.getName(), project);
+        }
+        return new ProviderBuildResult<Object>(model);
+    }
+}
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 918f141..a2c19c8 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
@@ -15,173 +15,170 @@
  */
 package org.gradle.tooling.internal.provider;
 
-import org.gradle.StartParameter;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.internal.Factory;
-import org.gradle.launcher.daemon.client.DaemonClient;
-import org.gradle.launcher.daemon.client.DaemonClientServices;
-import org.gradle.launcher.daemon.configuration.DaemonParameters;
-import org.gradle.launcher.daemon.configuration.GradleProperties;
-import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer;
-import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistryBuilder;
 import org.gradle.logging.LoggingServiceRegistry;
-import org.gradle.logging.internal.OutputEventRenderer;
-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.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
 import org.gradle.tooling.internal.protocol.*;
+import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
 import org.gradle.tooling.internal.provider.connection.*;
-import org.gradle.util.GUtil;
+import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GradleVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.util.List;
+import java.io.IOException;
+import java.io.OutputStream;
 
-public class DefaultConnection implements InternalConnection, BuildActionRunner, ConfigurableConnection {
+public class DefaultConnection implements InternalConnection, BuildActionRunner, ConfigurableConnection, ModelBuilder, InternalBuildActionExecutor {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnection.class);
-    private final EmbeddedExecuterSupport embeddedExecuterSupport;
-    private final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter();
+    private final ProtocolToModelAdapter adapter;
+    private final ServiceRegistry services;
+    private final ProviderConnection connection;
 
+    /**
+     * This is used by consumers 1.0-milestone-3 and later
+     */
     public DefaultConnection() {
-        LOGGER.debug("Provider implementation created.");
-        //embedded use of the tooling api is not supported publicly so we don't care about its thread safety
-        //we can still keep this state:
-        embeddedExecuterSupport = new EmbeddedExecuterSupport();
-        LOGGER.debug("Embedded executer support created.");
+        LOGGER.debug("Tooling API provider {} created.", GradleVersion.current().getVersion());
+        LoggingServiceRegistry loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
+        services = ServiceRegistryBuilder.builder()
+                .displayName("Connection services")
+                .parent(loggingServices)
+                .parent(NativeServices.getInstance())
+                .provider(new ConnectionScopeServices(loggingServices)).build();
+        adapter = services.get(ProtocolToModelAdapter.class);
+        connection = services.get(ProviderConnection.class);
     }
 
+    /**
+     * This is used by consumers 1.2-rc-1 and later.
+     */
     public void configure(ConnectionParameters parameters) {
-        ProviderConnectionParameters providerConnectionParameters = new ProtocolToModelAdapter().adapt(ProviderConnectionParameters.class, parameters);
-        configureLogging(providerConnectionParameters.getVerboseLogging());
+        ProviderConnectionParameters providerConnectionParameters = adapter.adapt(ProviderConnectionParameters.class, parameters);
+        connection.configure(providerConnectionParameters);
     }
 
-    public void configureLogging(boolean verboseLogging) {
-        LogLevel providerLogLevel = verboseLogging? LogLevel.DEBUG : LogLevel.INFO;
-        LOGGER.debug("Configuring logging to level: {}", providerLogLevel);
-        LoggingManagerInternal loggingManager = embeddedExecuterSupport.getLoggingServices().newInstance(LoggingManagerInternal.class);
-        loggingManager.setLevel(providerLogLevel);
-        loggingManager.start();
+    /**
+     * This method was used by consumers 1.0-rc-1 through to 1.1. Later consumers use {@link #configure(org.gradle.tooling.internal.protocol.ConnectionParameters)} instead.
+     */
+    public void configureLogging(final boolean verboseLogging) {
+        ProviderConnectionParameters providerConnectionParameters = adapter.adapt(ProviderConnectionParameters.class, new VerboseLoggingOnlyConnectionParameters(verboseLogging));
+        connection.configure(providerConnectionParameters);
     }
 
+    /**
+     * This is used by consumers 1.0-milestone-3 and later
+     */
     public ConnectionMetaDataVersion1 getMetaData() {
-        return new ConnectionMetaDataVersion1() {
-            public String getVersion() {
-                return GradleVersion.current().getVersion();
-            }
-
-            public String getDisplayName() {
-                return String.format("Gradle %s", getVersion());
-            }
-        };
+        return new DefaultConnectionMetaData();
     }
 
+    /**
+     * This is used by consumers 1.0-milestone-3 and later
+     */
     public void stop() {
+        // TODO:ADAM - switch this on again. Need to add a new protocol method, as older consumers call `stop()` at the end of every operation.
+//        services.close();
     }
 
+    /**
+     * This is used by consumers 1.0-milestone-3 to 1.1.
+     */
     @Deprecated
     public void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
+        sendDeprecationMessage(operationParameters);
         logTargetVersion();
-        run(Void.class, new AdaptedOperationParameters(operationParameters, buildParameters.getTasks()));
+        connection.run(ModelIdentifier.NULL_MODEL, new AdaptedOperationParameters(operationParameters, buildParameters.getTasks()));
     }
 
+    /**
+     * This is used by consumers 1.0-milestone-3 to 1.0-milestone-7
+     */
     @Deprecated
     public ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 parameters) {
+        sendDeprecationMessage(parameters);
         logTargetVersion();
-        return run(type, new AdaptedOperationParameters(parameters));
+        return run(type, parameters);
     }
 
+    /**
+     * This is used by consumers 1.0-milestone-8 to 1.1
+     */
     @Deprecated
     public <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 parameters) {
+        sendDeprecationMessage(parameters);
         logTargetVersion();
-        return run(type, new AdaptedOperationParameters(parameters));
+        return run(type, parameters);
     }
 
+    private <T> T run(Class<T> type, BuildOperationParametersVersion1 parameters) {
+        String modelName = new ModelMapping().getModelNameFromProtocolType(type);
+        return (T) connection.run(modelName, new AdaptedOperationParameters(parameters));
+    }
+
+    /**
+     * This is used by consumers 1.2-rc-1 to 1.5
+     */
+    @Deprecated
     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);
+        ProviderOperationParameters providerParameters = toProviderParameters(buildParameters);
+        String modelName = new ModelMapping().getModelNameFromProtocolType(type);
+        T result = (T) connection.run(modelName, 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.");
-        }
-        GradleProperties gradleProperties = initGradleProperties(providerParameters);
-        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, gradleProperties);
-            DefaultBuildEnvironment out = new DefaultBuildEnvironment(
-                    GradleVersion.current().getVersion(),
-                    daemonParameters.getEffectiveJavaHome(),
-                    daemonParameters.getEffectiveJvmArgs());
-
-            return type.cast(out);
-        }
+    /**
+     * This is used by consumers 1.6-rc-1 and later
+     */
+    public BuildResult<?> getModel(ModelIdentifier modelIdentifier, BuildParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        logTargetVersion();
+        ProviderOperationParameters providerParameters = toProviderParameters(operationParameters);
+        Object result = connection.run(modelIdentifier.getName(), providerParameters);
+        return new ProviderBuildResult<Object>(result);
+    }
 
-        DelegatingBuildModelAction<T> action = new DelegatingBuildModelAction<T>(type, tasks != null);
-        return run(action, providerParameters, gradleProperties);
+    /**
+     * This is used by consumers 1.8-rc-1 and later.
+     */
+    public <T> BuildResult<T> run(InternalBuildAction<T> action, BuildParameters operationParameters) throws BuildExceptionVersion1, InternalUnsupportedBuildArgumentException, IllegalStateException {
+        logTargetVersion();
+        ProviderOperationParameters providerParameters = toProviderParameters(operationParameters);
+        Object results = connection.run(action, providerParameters);
+        return new ProviderBuildResult<T>((T) results);
     }
 
     private void logTargetVersion() {
-        LOGGER.info("Tooling API uses target gradle version:" + " {}.", GradleVersion.current().getVersion());
-    }
-
-    private <T> T run(GradleLauncherAction<T> action, ProviderOperationParameters operationParameters, GradleProperties gradleProperties) {
-        GradleLauncherActionExecuter<ProviderOperationParameters> executer = createExecuter(operationParameters);
-        ConfiguringBuildAction<T> configuringAction = new ConfiguringBuildAction<T>(operationParameters, action, gradleProperties);
-        return executer.execute(configuringAction, operationParameters);
-    }
-
-    private GradleLauncherActionExecuter<ProviderOperationParameters> createExecuter(ProviderOperationParameters operationParameters) {
-        LoggingServiceRegistry loggingServices;
-        DaemonParameters daemonParams = init(operationParameters, initGradleProperties(operationParameters));
-        GradleLauncherActionExecuter<BuildActionParameters> executer;
-        if (Boolean.TRUE.equals(operationParameters.isEmbedded())) {
-            loggingServices = embeddedExecuterSupport.getLoggingServices();
-            executer = embeddedExecuterSupport.getExecuter();
-        } else {
-            loggingServices = embeddedExecuterSupport.getLoggingServices().newLogging();
-            loggingServices.get(OutputEventRenderer.class).configure(operationParameters.getBuildLogLevel());
-            DaemonClientServices clientServices = new DaemonClientServices(loggingServices, daemonParams, operationParameters.getStandardInput(SafeStreams.emptyInput()));
-            executer = clientServices.get(DaemonClient.class);
-        }
-        Factory<LoggingManagerInternal> loggingManagerFactory = loggingServices.getFactory(LoggingManagerInternal.class);
-        return new LoggingBridgingGradleLauncherActionExecuter(new DaemonGradleLauncherActionExecuter(executer, daemonParams), loggingManagerFactory);
+        LOGGER.info("Tooling API is using target Gradle version: {}.", GradleVersion.current().getVersion());
     }
 
-    private DaemonParameters init(ProviderOperationParameters operationParameters, GradleProperties gradleProperties) {
-        DaemonParameters daemonParams = new DaemonParameters();
+    private void sendDeprecationMessage(BuildOperationParametersVersion1 parameters) {
+        OutputStream out = parameters.getStandardOutput();
+        if (out != null) {
+            try {
+                out.write(String.format("Connection from tooling API older than version 1.2 %s%n", DeprecationLogger.getDeprecationMessage()).getBytes());
+            } catch (IOException e) {
+                throw new RuntimeException("Cannot write to stream", e);
+            }
+        }
+    }
 
-        daemonParams.configureFrom(gradleProperties);
+    private ProviderOperationParameters toProviderParameters(BuildParameters buildParameters) {
+        return adapter.adapt(ProviderOperationParameters.class, buildParameters, BuildLogLevelMixIn.class);
+    }
 
-        //override the params with the explicit settings provided by the tooling api
-        List<String> defaultJvmArgs = daemonParams.getAllJvmArgs();
-        daemonParams.setJvmArgs(operationParameters.getJvmArguments(defaultJvmArgs));
-        File defaultJavaHome = daemonParams.getEffectiveJavaHome();
-        daemonParams.setJavaHome(operationParameters.getJavaHome(defaultJavaHome));
+    private static class VerboseLoggingOnlyConnectionParameters {
+        private final boolean verboseLogging;
 
-        if (operationParameters.getDaemonMaxIdleTimeValue() != null && operationParameters.getDaemonMaxIdleTimeUnits() != null) {
-            int idleTimeout = (int) operationParameters.getDaemonMaxIdleTimeUnits().toMillis(operationParameters.getDaemonMaxIdleTimeValue());
-            daemonParams.setIdleTimeout(idleTimeout);
+        public VerboseLoggingOnlyConnectionParameters(boolean verboseLogging) {
+            this.verboseLogging = verboseLogging;
         }
-        return daemonParams;
-    }
 
-    private GradleProperties initGradleProperties(ProviderOperationParameters operationParameters) {
-        File gradleUserHomeDir = GUtil.elvis(operationParameters.getGradleUserHomeDir(), StartParameter.DEFAULT_GRADLE_USER_HOME);
-        boolean searchUpwards = operationParameters.isSearchUpwards() != null ? operationParameters.isSearchUpwards() : true;
-        return new GradlePropertiesConfigurer()
-                .prepareProperties(operationParameters.getProjectDir(), searchUpwards, gradleUserHomeDir, System.getProperties());
+        public boolean getVerboseLogging() {
+            return verboseLogging;
+        }
     }
-
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnectionMetaData.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnectionMetaData.java
new file mode 100644
index 0000000..fda63f8
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnectionMetaData.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tooling.internal.protocol.ConnectionMetaDataVersion1;
+import org.gradle.util.GradleVersion;
+
+class DefaultConnectionMetaData implements ConnectionMetaDataVersion1 {
+    public String getVersion() {
+        return GradleVersion.current().getVersion();
+    }
+
+    public String getDisplayName() {
+        return String.format("Gradle %s", getVersion());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultPayloadClassLoaderRegistry.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultPayloadClassLoaderRegistry.java
new file mode 100644
index 0000000..4148ddc
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultPayloadClassLoaderRegistry.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.MapMaker;
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.internal.classloader.ClassLoaderSpec;
+import org.gradle.internal.classloader.ClassLoaderVisitor;
+import org.gradle.internal.classloader.MutableURLClassLoader;
+import org.gradle.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+ at ThreadSafe
+public class DefaultPayloadClassLoaderRegistry implements PayloadClassLoaderRegistry {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPayloadClassLoaderRegistry.class);
+    private final Lock lock = new ReentrantLock();
+    private final ModelClassLoaderFactory classLoaderFactory;
+    private Map<ClassLoader, ClassLoaderDetails> classLoaderDetails;
+    private Map<UUID, ClassLoader> classLoaderIds;
+
+    public DefaultPayloadClassLoaderRegistry(ModelClassLoaderFactory modelClassLoaderFactory) {
+        classLoaderDetails = new MapMaker().weakKeys().makeMap();
+        classLoaderIds = new MapMaker().weakValues().makeMap();
+        this.classLoaderFactory = modelClassLoaderFactory;
+    }
+
+    public SerializeMap newSerializeSession() {
+        return new SerializeMap() {
+            final Map<ClassLoader, Short> classLoaderIds = new HashMap<ClassLoader, Short>();
+            final Map<Short, ClassLoaderDetails> classLoaderDetails = new HashMap<Short, ClassLoaderDetails>();
+
+            public short visitClass(Class<?> target) {
+                ClassLoader classLoader = target.getClassLoader();
+                Short id = classLoaderIds.get(classLoader);
+                if (id != null) {
+                    return id;
+                }
+                if (classLoaderIds.size() == Short.MAX_VALUE) {
+                    throw new UnsupportedOperationException();
+                }
+                ClassLoaderDetails details = getDetails(classLoader);
+                id = (short) (classLoaderIds.size() + 1);
+
+                classLoaderIds.put(classLoader, id);
+                classLoaderDetails.put(id, details);
+
+                return id;
+            }
+
+            public Map<Short, ClassLoaderDetails> getClassLoaders() {
+                return classLoaderDetails;
+            }
+        };
+    }
+
+    public DeserializeMap newDeserializeSession() {
+        return new DeserializeMap() {
+            public Class<?> resolveClass(ClassLoaderDetails classLoaderDetails, String className) throws ClassNotFoundException {
+                ClassLoader classLoader = getClassLoader(classLoaderDetails);
+                return Class.forName(className, false, classLoader);
+            }
+        };
+    }
+
+    private ClassLoader getClassLoader(ClassLoaderDetails details) {
+        lock.lock();
+        try {
+            ClassLoader classLoader = classLoaderIds.get(details.uuid);
+            if (classLoader != null) {
+                return classLoader;
+            }
+
+            List<ClassLoader> parents = new ArrayList<ClassLoader>();
+            for (ClassLoaderDetails parentDetails : details.parents) {
+                parents.add(getClassLoader(parentDetails));
+            }
+            if (parents.isEmpty()) {
+                parents.add(classLoaderFactory.getClassLoaderFor(ClassLoaderSpec.SYSTEM_CLASS_LOADER, null));
+            }
+
+            LOGGER.info("Creating ClassLoader {} from {} and {}.", details.uuid, details.spec, parents);
+
+            classLoader = classLoaderFactory.getClassLoaderFor(details.spec, parents);
+            classLoaderIds.put(details.uuid, classLoader);
+            classLoaderDetails.put(classLoader, details);
+            return classLoader;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private ClassLoaderDetails getDetails(ClassLoader classLoader) {
+        lock.lock();
+        try {
+            ClassLoaderDetails details = classLoaderDetails.get(classLoader);
+            if (details != null) {
+                return details;
+            }
+
+            ClassLoaderSpecVisitor visitor = new ClassLoaderSpecVisitor(classLoader);
+            visitor.visit(classLoader);
+
+            if (visitor.spec == null) {
+                if (visitor.classPath == null) {
+                    visitor.spec = ClassLoaderSpec.SYSTEM_CLASS_LOADER;
+                } else {
+                    visitor.spec = new MutableURLClassLoader.Spec(CollectionUtils.toList(visitor.classPath));
+                }
+            }
+
+            UUID uuid = UUID.randomUUID();
+            details = new ClassLoaderDetails(uuid, visitor.spec);
+            for (ClassLoader parent : visitor.parents) {
+                details.parents.add(getDetails(parent));
+            }
+
+            classLoaderDetails.put(classLoader, details);
+            classLoaderIds.put(details.uuid, classLoader);
+            return details;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private static class ClassLoaderSpecVisitor extends ClassLoaderVisitor {
+        final ClassLoader classLoader;
+        final List<ClassLoader> parents = new ArrayList<ClassLoader>();
+        ClassLoaderSpec spec;
+        URL[] classPath;
+
+        public ClassLoaderSpecVisitor(ClassLoader classLoader) {
+            this.classLoader = classLoader;
+        }
+
+        @Override
+        public void visit(ClassLoader candidate) {
+            if (candidate == classLoader) {
+                super.visit(candidate);
+            } else {
+                parents.add(candidate);
+            }
+        }
+
+        @Override
+        public void visitClassPath(URL[] classPath) {
+            this.classPath = classPath;
+        }
+
+        @Override
+        public void visitSpec(ClassLoaderSpec spec) {
+            this.spec = spec;
+        }
+    }
+}
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
deleted file mode 100644
index 9cec903..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DelegatingBuildModelAction.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.tooling.internal.provider;
-
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.initialization.ClassLoaderRegistry;
-import org.gradle.initialization.DefaultGradleLauncher;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.internal.UncheckedException;
-
-import java.io.Serializable;
-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, boolean runTasks) {
-        this.type = type;
-        this.runTasks = runTasks;
-    }
-
-    public T getResult() {
-        return action.getResult();
-    }
-
-    public BuildResult run(GradleLauncher launcher) {
-        loadAction((DefaultGradleLauncher) launcher);
-        return action.run(launcher);
-    }
-
-    @SuppressWarnings("unchecked")
-    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, Boolean.TYPE).newInstance(type, runTasks);
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.unwrapAndRethrow(e);
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DeserializeMap.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DeserializeMap.java
new file mode 100644
index 0000000..0e185ad
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DeserializeMap.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public interface DeserializeMap {
+    /**
+     * Loads a serialized Class.
+     */
+    Class<?> resolveClass(ClassLoaderDetails classLoaderDetails, String className) throws ClassNotFoundException;
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
deleted file mode 100644
index da96a62..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.provider;
-
-import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
-import org.gradle.logging.LoggingServiceRegistry;
-
-/**
- * by Szczepan Faber, created at: 12/6/11
- */
-public class EmbeddedExecuterSupport {
-
-    private DefaultGradleLauncherFactory gradleLauncherFactory;
-    private LoggingServiceRegistry embeddedLogging;
-
-    public EmbeddedExecuterSupport() {
-        embeddedLogging = LoggingServiceRegistry.newEmbeddableLogging();
-        gradleLauncherFactory = new DefaultGradleLauncherFactory(embeddedLogging);
-    }
-
-    public GradleLauncherActionExecuter<BuildActionParameters> getExecuter() {
-        return new InProcessGradleLauncherActionExecuter(gradleLauncherFactory);
-    }
-
-    public LoggingServiceRegistry getLoggingServices() {
-        return embeddedLogging;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.java
deleted file mode 100644
index eeb2558..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.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.tooling.internal.provider;
-
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.initialization.GradleLauncherAction;
-
-import java.io.Serializable;
-
-public class ExecuteBuildAction implements GradleLauncherAction<Void>, Serializable {
-
-    public Void getResult() {
-        return null;
-    }
-
-    public BuildResult run(GradleLauncher gradleLauncher) {
-        return gradleLauncher.run();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingBuildActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingBuildActionExecuter.java
new file mode 100644
index 0000000..7ebe715
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingBuildActionExecuter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.initialization.BuildAction;
+import org.gradle.internal.Factory;
+import org.gradle.launcher.exec.BuildActionExecuter;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.internal.*;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
+
+/**
+ * A {@link org.gradle.launcher.exec.BuildActionExecuter} which routes Gradle logging to those listeners specified in the {@link ProviderOperationParameters} provided with a tooling api build
+ * request.
+ */
+public class LoggingBridgingBuildActionExecuter implements BuildActionExecuter<ProviderOperationParameters> {
+    private final Factory<LoggingManagerInternal> loggingManagerFactory;
+    private final BuildActionExecuter<ProviderOperationParameters> executer;
+
+    public LoggingBridgingBuildActionExecuter(BuildActionExecuter<ProviderOperationParameters> executer, Factory<LoggingManagerInternal> loggingManagerFactory) {
+        this.executer = executer;
+        this.loggingManagerFactory = loggingManagerFactory;
+    }
+
+    public <T> T execute(BuildAction<T> action, ProviderOperationParameters actionParameters) {
+        LoggingManagerInternal loggingManager = loggingManagerFactory.create();
+        if (actionParameters.getStandardOutput() != null) {
+            loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(actionParameters.getStandardOutput()));
+        }
+        if (actionParameters.getStandardError() != null) {
+            loggingManager.addStandardErrorListener(new StreamBackedStandardOutputListener(actionParameters.getStandardError()));
+        }
+        ProgressListenerVersion1 progressListener = actionParameters.getProgressListener();
+        OutputEventListenerAdapter listener = new OutputEventListenerAdapter(progressListener);
+        loggingManager.addOutputEventListener(listener);
+        loggingManager.setLevel(actionParameters.getBuildLogLevel());
+        loggingManager.start();
+        try {
+            return executer.execute(action, actionParameters);
+        } finally {
+            loggingManager.stop();
+        }
+    }
+
+    private static class OutputEventListenerAdapter implements OutputEventListener {
+        private final ProgressListenerVersion1 progressListener;
+
+        public OutputEventListenerAdapter(ProgressListenerVersion1 progressListener) {
+            this.progressListener = progressListener;
+        }
+
+        public void onOutput(OutputEvent event) {
+            if (event instanceof ProgressStartEvent) {
+                ProgressStartEvent startEvent = (ProgressStartEvent) event;
+                progressListener.onOperationStart(startEvent.getDescription());
+            } else if (event instanceof ProgressCompleteEvent) {
+                progressListener.onOperationEnd();
+            }
+        }
+    }
+}
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
deleted file mode 100644
index c05a704..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuter.java
+++ /dev/null
@@ -1,75 +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.initialization.GradleLauncherAction;
-import org.gradle.internal.Factory;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.internal.*;
-import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
-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
- * request.
- */
-public class LoggingBridgingGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
-    private final Factory<LoggingManagerInternal> loggingManagerFactory;
-    private final GradleLauncherActionExecuter<ProviderOperationParameters> executer;
-
-    public LoggingBridgingGradleLauncherActionExecuter(GradleLauncherActionExecuter<ProviderOperationParameters> executer, Factory<LoggingManagerInternal> loggingManagerFactory) {
-        this.executer = executer;
-        this.loggingManagerFactory = loggingManagerFactory;
-    }
-
-    public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
-        LoggingManagerInternal loggingManager = loggingManagerFactory.create();
-        if (actionParameters.getStandardOutput() != null) {
-            loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(actionParameters.getStandardOutput()));
-        }
-        if (actionParameters.getStandardError() != null) {
-            loggingManager.addStandardErrorListener(new StreamBackedStandardOutputListener(actionParameters.getStandardError()));
-        }
-        ProgressListenerVersion1 progressListener = actionParameters.getProgressListener();
-        OutputEventListenerAdapter listener = new OutputEventListenerAdapter(progressListener);
-        loggingManager.addOutputEventListener(listener);
-        loggingManager.setLevel(actionParameters.getBuildLogLevel());
-        loggingManager.start();
-        try {
-            return executer.execute(action, actionParameters);
-        } finally {
-            loggingManager.stop();
-        }
-    }
-
-    private static class OutputEventListenerAdapter implements OutputEventListener {
-        private final ProgressListenerVersion1 progressListener;
-
-        public OutputEventListenerAdapter(ProgressListenerVersion1 progressListener) {
-            this.progressListener = progressListener;
-        }
-
-        public void onOutput(OutputEvent event) {
-            if (event instanceof ProgressStartEvent) {
-                ProgressStartEvent startEvent = (ProgressStartEvent) event;
-                progressListener.onOperationStart(startEvent.getDescription());
-            } else if (event instanceof ProgressCompleteEvent) {
-                progressListener.onOperationEnd();
-            }
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ModelClassLoaderFactory.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ModelClassLoaderFactory.java
new file mode 100644
index 0000000..5315005
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ModelClassLoaderFactory.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.internal.classloader.*;
+import org.gradle.tooling.provider.model.internal.LegacyConsumerInterface;
+import org.objectweb.asm.*;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ModelClassLoaderFactory {
+    private final ClassLoader rootClassLoader;
+    private final ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory();
+
+    public ModelClassLoaderFactory() {
+        ClassLoader parent = getClass().getClassLoader();
+        FilteringClassLoader filter = new FilteringClassLoader(parent);
+        filter.allowPackage("org.gradle.tooling.internal.protocol");
+        rootClassLoader = filter;
+    }
+
+    public ClassLoader getClassLoaderFor(ClassLoaderSpec spec, List<? extends ClassLoader> parents) {
+        if (spec.equals(ClassLoaderSpec.SYSTEM_CLASS_LOADER)) {
+            return rootClassLoader;
+        }
+        if (spec instanceof MutableURLClassLoader.Spec) {
+            MutableURLClassLoader.Spec clSpec = (MutableURLClassLoader.Spec) spec;
+            if (parents.size() != 1) {
+                throw new IllegalStateException("Expected exactly one parent ClassLoader");
+            }
+            return new MixInClassLoader(parents.get(0), clSpec.getClasspath());
+        }
+        return classLoaderFactory.createClassLoader(spec, parents);
+    }
+
+    private static class MixInClassLoader extends TransformingClassLoader {
+        public MixInClassLoader(ClassLoader parent, List<URL> classPath) {
+            super(parent, classPath);
+        }
+
+        @Override
+        protected byte[] transform(byte[] bytes) {
+            // First scan for annotation, and short circuit transformation if not present
+            ClassReader classReader = new ClassReader(bytes);
+
+            AnnotationDetector detector = new AnnotationDetector();
+            classReader.accept(detector, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
+            if (!detector.found) {
+                return bytes;
+            }
+
+            if (findLoadedClass(detector.interfaceName) == null) {
+                // TODO:ADAM - need to do this earlier
+                ClassWriter emptyWriter = new ClassWriter(0);
+                emptyWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, detector.interfaceName.replace(".", "/"), null, Type.getType(Object.class).getInternalName(), null);
+                emptyWriter.visitEnd();
+                byte[] emptyBytecode = emptyWriter.toByteArray();
+                defineClass(detector.interfaceName, emptyBytecode, 0, emptyBytecode.length);
+            }
+
+            ClassWriter classWriter = new ClassWriter(0);
+            classReader.accept(new TransformingAdapter(classWriter, detector.interfaceName), 0);
+            bytes = classWriter.toByteArray();
+            return bytes;
+        }
+
+        private static class AnnotationDetector extends ClassVisitor {
+            private static final String ANNOTATION_DESCRIPTOR = Type.getType(LegacyConsumerInterface.class).getDescriptor();
+            String interfaceName;
+            private boolean found;
+
+            private AnnotationDetector() {
+                super(Opcodes.ASM4);
+            }
+
+            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+                if (desc.equals(ANNOTATION_DESCRIPTOR)) {
+                    found = true;
+                }
+                return new AnnotationVisitor(Opcodes.ASM4) {
+
+                    @Override
+                    public void visit(String name, Object value) {
+                        if (name.equals("value")) {
+                            interfaceName = value.toString();
+                        }
+                    }
+                };
+            }
+        }
+
+        private static class TransformingAdapter extends ClassVisitor {
+            private final String mixInInterface;
+
+            public TransformingAdapter(ClassWriter classWriter, String mixInInterface) {
+                super(Opcodes.ASM4, classWriter);
+                this.mixInInterface = mixInInterface;
+            }
+
+            @Override
+            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+                Set<String> allInterfaces = new LinkedHashSet<String>(Arrays.asList(interfaces));
+                allInterfaces.add(mixInInterface.replace(".", "/"));
+                super.visit(version, access, name, signature, superName, allInterfaces.toArray(new String[allInterfaces.size()]));
+            }
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/PayloadClassLoaderRegistry.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/PayloadClassLoaderRegistry.java
new file mode 100644
index 0000000..68fd217
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/PayloadClassLoaderRegistry.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 net.jcip.annotations.ThreadSafe;
+
+ at ThreadSafe
+public interface PayloadClassLoaderRegistry {
+    SerializeMap newSerializeSession();
+
+    DeserializeMap newDeserializeSession();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/PayloadSerializer.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/PayloadSerializer.java
new file mode 100644
index 0000000..ab4405c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/PayloadSerializer.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 net.jcip.annotations.ThreadSafe;
+import org.gradle.api.Transformer;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.jvm.Jvm;
+
+import java.io.*;
+import java.lang.reflect.Proxy;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+ at ThreadSafe
+public class PayloadSerializer {
+    private static final short SYSTEM_CLASS_LOADER_ID = (short) -1;
+    private static final Set<ClassLoader> SYSTEM_CLASS_LOADERS = new HashSet<ClassLoader>();
+    private final Transformer<ObjectStreamClass, Class<?>> classLookup;
+    private final PayloadClassLoaderRegistry classLoaderRegistry;
+
+    static {
+        for (ClassLoader cl = ClassLoader.getSystemClassLoader().getParent(); cl != null; cl = cl.getParent()) {
+            SYSTEM_CLASS_LOADERS.add(cl);
+        }
+    }
+
+    public PayloadSerializer(PayloadClassLoaderRegistry registry) {
+        classLoaderRegistry = registry;
+
+        // On Java 6, there is a public method to lookup a class descriptor given a class. On Java 5, we have to use reflection
+        // TODO:ADAM - move this into the service registry
+        if (Jvm.current().getJavaVersion().isJava6Compatible()) {
+            // Use the public method
+            try {
+                classLookup = (Transformer<ObjectStreamClass, Class<?>>) getClass().getClassLoader().loadClass("org.gradle.tooling.internal.provider.jdk6.Jdk6ClassLookup").newInstance();
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        } else {
+            // Use Java 5 fallback which uses reflection
+            classLookup = new ReflectionClassLookup();
+        }
+    }
+
+    public SerializedPayload serialize(Object payload) {
+        final SerializeMap map = classLoaderRegistry.newSerializeSession();
+        try {
+            ByteArrayOutputStream content = new ByteArrayOutputStream();
+            final ObjectOutputStream objectStream = new ObjectOutputStream(content) {
+                @Override
+                protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
+                    Class<?> targetClass = desc.forClass();
+                    writeClass(targetClass);
+                }
+
+                @Override
+                protected void annotateProxyClass(Class<?> cl) throws IOException {
+                    writeInt(cl.getInterfaces().length);
+                    for (Class<?> type : cl.getInterfaces()) {
+                        writeClass(type);
+                    }
+                }
+
+                private void writeClass(Class<?> targetClass) throws IOException {
+                    writeClassLoader(targetClass);
+                    writeUTF(targetClass.getName());
+                }
+
+                private void writeClassLoader(Class<?> targetClass) throws IOException {
+                    ClassLoader classLoader = targetClass.getClassLoader();
+                    if (classLoader == null || SYSTEM_CLASS_LOADERS.contains(classLoader)) {
+                        writeShort(SYSTEM_CLASS_LOADER_ID);
+                    } else {
+                        writeShort(map.visitClass(targetClass));
+                    }
+                }
+            };
+
+            objectStream.writeObject(payload);
+            objectStream.close();
+
+            Map<Short, ClassLoaderDetails> classLoaders = map.getClassLoaders();
+            if (classLoaders.containsKey(SYSTEM_CLASS_LOADER_ID)) {
+                throw new IllegalArgumentException("Unexpected ClassLoader id found");
+            }
+            return new SerializedPayload(classLoaders, content.toByteArray());
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public Object deserialize(SerializedPayload payload) {
+        final DeserializeMap map = classLoaderRegistry.newDeserializeSession();
+        try {
+            final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader().getParent();
+            final Map<Short, ClassLoaderDetails> classLoaderDetails = (Map<Short, ClassLoaderDetails>) payload.getHeader();
+
+            final ObjectInputStream objectStream = new ObjectInputStream(new ByteArrayInputStream(payload.getSerializedModel())) {
+                @Override
+                protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
+                    Class<?> aClass = readClass();
+                    ObjectStreamClass descriptor = classLookup.transform(aClass);
+                    if (descriptor == null) {
+                        throw new ClassNotFoundException(aClass.getName());
+                    }
+                    return descriptor;
+                }
+
+                @Override
+                protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+                    return desc.forClass();
+                }
+
+                private Class<?> readClass() throws IOException, ClassNotFoundException {
+                    short id = readShort();
+                    String className = readUTF();
+                    if (id == SYSTEM_CLASS_LOADER_ID) {
+                        return Class.forName(className, false, systemClassLoader);
+                    }
+                    ClassLoaderDetails classLoader = classLoaderDetails.get(id);
+                    return map.resolveClass(classLoader, className);
+                }
+
+                @Override
+                protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
+                    int count = readInt();
+                    Class<?>[] actualInterfaces = new Class<?>[count];
+                    for (int i = 0; i < count; i++) {
+                        actualInterfaces[i] = readClass();
+                    }
+                    return Proxy.getProxyClass(actualInterfaces[0].getClassLoader(), actualInterfaces);
+                }
+            };
+            return objectStream.readObject();
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ProviderConnection.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ProviderConnection.java
new file mode 100644
index 0000000..2fd6489
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ProviderConnection.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.initialization.BuildAction;
+import org.gradle.initialization.BuildLayoutParameters;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.internal.Factory;
+import org.gradle.launcher.cli.converter.LayoutToPropertiesConverter;
+import org.gradle.launcher.cli.converter.PropertiesToDaemonParametersConverter;
+import org.gradle.launcher.daemon.client.DaemonClient;
+import org.gradle.launcher.daemon.client.DaemonClientServices;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.exec.BuildActionExecuter;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.InProcessBuildActionExecuter;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.internal.OutputEventRenderer;
+import org.gradle.process.internal.streams.SafeStreams;
+import org.gradle.tooling.internal.build.DefaultBuildEnvironment;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.protocol.InternalBuildAction;
+import org.gradle.tooling.internal.protocol.InternalBuildEnvironment;
+import org.gradle.tooling.internal.protocol.ModelIdentifier;
+import org.gradle.tooling.internal.provider.connection.ProviderConnectionParameters;
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ProviderConnection {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ProviderConnection.class);
+    private final PayloadSerializer payloadSerializer;
+    private final LoggingServiceRegistry loggingServices;
+    private final GradleLauncherFactory gradleLauncherFactory;
+
+    public ProviderConnection(LoggingServiceRegistry loggingServices, GradleLauncherFactory gradleLauncherFactory, PayloadSerializer payloadSerializer) {
+        this.loggingServices = loggingServices;
+        this.gradleLauncherFactory = gradleLauncherFactory;
+        this.payloadSerializer = payloadSerializer;
+    }
+
+    public void configure(ProviderConnectionParameters parameters) {
+        LogLevel providerLogLevel = parameters.getVerboseLogging() ? LogLevel.DEBUG : LogLevel.INFO;
+        LOGGER.debug("Configuring logging to level: {}", providerLogLevel);
+        LoggingManagerInternal loggingManager = loggingServices.newInstance(LoggingManagerInternal.class);
+        loggingManager.setLevel(providerLogLevel);
+        loggingManager.start();
+    }
+
+    public Object run(String modelName, ProviderOperationParameters providerParameters) {
+        List<String> tasks = providerParameters.getTasks();
+        if (modelName.equals(ModelIdentifier.NULL_MODEL) && tasks == null) {
+            throw new IllegalArgumentException("No model type or tasks specified.");
+        }
+        Parameters params = initParams(providerParameters);
+        Class<?> type = new ModelMapping().getProtocolTypeFromModelName(modelName);
+        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.");
+            }
+            return new DefaultBuildEnvironment(
+                    GradleVersion.current().getVersion(),
+                    params.daemonParams.getEffectiveJavaHome(),
+                    params.daemonParams.getEffectiveJvmArgs());
+        }
+
+        BuildAction<BuildActionResult> action = new BuildModelAction(modelName, tasks != null);
+        return run(action, providerParameters, params.properties);
+    }
+
+    public Object run(InternalBuildAction<?> clientAction, ProviderOperationParameters providerParameters) {
+        SerializedPayload serializedAction = payloadSerializer.serialize(clientAction);
+        Parameters params = initParams(providerParameters);
+        BuildAction<BuildActionResult> action = new ClientProvidedBuildAction(serializedAction);
+        return run(action, providerParameters, params.properties);
+    }
+
+    private Object run(BuildAction<? extends BuildActionResult> action, ProviderOperationParameters operationParameters, Map<String, String> properties) {
+        BuildActionExecuter<ProviderOperationParameters> executer = createExecuter(operationParameters);
+        ConfiguringBuildAction<BuildActionResult> configuringAction = new ConfiguringBuildAction<BuildActionResult>(operationParameters, action, properties);
+        BuildActionResult result = executer.execute(configuringAction, operationParameters);
+        if (result.failure != null) {
+            throw (RuntimeException) payloadSerializer.deserialize(result.failure);
+        }
+        return payloadSerializer.deserialize(result.result);
+    }
+
+    private BuildActionExecuter<ProviderOperationParameters> createExecuter(ProviderOperationParameters operationParameters) {
+        LoggingServiceRegistry loggingServices;
+        Parameters params = initParams(operationParameters);
+        BuildActionExecuter<BuildActionParameters> executer;
+        if (Boolean.TRUE.equals(operationParameters.isEmbedded())) {
+            loggingServices = this.loggingServices;
+            executer = new InProcessBuildActionExecuter(gradleLauncherFactory);
+        } else {
+            loggingServices = this.loggingServices.newLogging();
+            loggingServices.get(OutputEventRenderer.class).configure(operationParameters.getBuildLogLevel());
+            DaemonClientServices clientServices = new DaemonClientServices(loggingServices, params.daemonParams, operationParameters.getStandardInput(SafeStreams.emptyInput()));
+            executer = clientServices.get(DaemonClient.class);
+        }
+        Factory<LoggingManagerInternal> loggingManagerFactory = loggingServices.getFactory(LoggingManagerInternal.class);
+        return new LoggingBridgingBuildActionExecuter(new DaemonBuildActionExecuter(executer, params.daemonParams), loggingManagerFactory);
+    }
+
+    private Parameters initParams(ProviderOperationParameters operationParameters) {
+        BuildLayoutParameters layout = new BuildLayoutParameters()
+                .setGradleUserHomeDir(GUtil.elvis(operationParameters.getGradleUserHomeDir(), StartParameter.DEFAULT_GRADLE_USER_HOME))
+                .setSearchUpwards(operationParameters.isSearchUpwards() != null ? operationParameters.isSearchUpwards() : true)
+                .setProjectDir(operationParameters.getProjectDir());
+
+        Map<String, String> properties = new HashMap<String, String>();
+        new LayoutToPropertiesConverter().convert(layout, properties);
+
+        DaemonParameters daemonParams = new DaemonParameters(layout);
+        new PropertiesToDaemonParametersConverter().convert(properties, daemonParams);
+
+        //override the params with the explicit settings provided by the tooling api
+        List<String> defaultJvmArgs = daemonParams.getAllJvmArgs();
+        daemonParams.setJvmArgs(operationParameters.getJvmArguments(defaultJvmArgs));
+        File defaultJavaHome = daemonParams.getEffectiveJavaHome();
+        daemonParams.setJavaHome(operationParameters.getJavaHome(defaultJavaHome));
+
+        if (operationParameters.getDaemonMaxIdleTimeValue() != null && operationParameters.getDaemonMaxIdleTimeUnits() != null) {
+            int idleTimeout = (int) operationParameters.getDaemonMaxIdleTimeUnits().toMillis(operationParameters.getDaemonMaxIdleTimeValue());
+            daemonParams.setIdleTimeout(idleTimeout);
+        }
+        return new Parameters(daemonParams, properties);
+    }
+
+    private static class Parameters {
+        DaemonParameters daemonParams;
+        Map<String, String> properties;
+
+        public Parameters(DaemonParameters daemonParams, Map<String, String> properties) {
+            this.daemonParams = daemonParams;
+            this.properties = properties;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ReflectionClassLookup.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ReflectionClassLookup.java
new file mode 100644
index 0000000..abeca6c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ReflectionClassLookup.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Transformer;
+import org.gradle.internal.UncheckedException;
+
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Method;
+
+public class ReflectionClassLookup implements Transformer<ObjectStreamClass, Class<?>> {
+    private final Method lookupMethod;
+
+    public ReflectionClassLookup() {
+        try {
+            lookupMethod = ObjectStreamClass.class.getDeclaredMethod("lookup", Class.class, Boolean.TYPE);
+            lookupMethod.setAccessible(true);
+        } catch (NoSuchMethodException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public ObjectStreamClass transform(Class<?> original) {
+        try {
+            return (ObjectStreamClass) lookupMethod.invoke(null, original, true);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/SerializeMap.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/SerializeMap.java
new file mode 100644
index 0000000..5509d4d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/SerializeMap.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.Map;
+
+public interface SerializeMap {
+    /**
+     * Visits a class to be serialized, returning the id of the deserialize ClassLoader to associate this class with.
+     * The id is unique only for this serialization.
+     *
+     * @return The ClassLoader id.
+     */
+    short visitClass(Class<?> target);
+
+    /**
+     * Returns the set of ClassLoaders to use in to deserialize the graph.
+     *
+     * @return The map from ClassLoader id to details to use create that ClassLoader.
+     */
+    Map<Short, ClassLoaderDetails> getClassLoaders();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/SerializedPayload.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/SerializedPayload.java
new file mode 100644
index 0000000..682ab6c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/SerializedPayload.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.io.Serializable;
+
+public class SerializedPayload implements Serializable {
+    private final byte[] serializedModel;
+    private final Object header;
+
+    public SerializedPayload(Object header, byte[] serializedModel) {
+        this.header = header;
+        this.serializedModel = serializedModel;
+    }
+
+    public Object getHeader() {
+        return header;
+    }
+
+    public byte[] getSerializedModel() {
+        return serializedModel;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ToolingGlobalScopeServices.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ToolingGlobalScopeServices.java
new file mode 100644
index 0000000..4c6738d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ToolingGlobalScopeServices.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+class ToolingGlobalScopeServices {
+    PayloadSerializer createPayloadSerializer() {
+        return new PayloadSerializer(
+                new DefaultPayloadClassLoaderRegistry(
+                        new ModelClassLoaderFactory()));
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ToolingServices.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ToolingServices.java
new file mode 100644
index 0000000..d494d7f
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ToolingServices.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider;
+
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.PluginServiceRegistry;
+
+public class ToolingServices implements PluginServiceRegistry {
+    public void registerGlobalServices(ServiceRegistration registration) {
+        registration.addProvider(new ToolingGlobalScopeServices());
+    }
+
+    public void registerBuildServices(ServiceRegistration registration) {
+    }
+
+    public void registerProjectServices(ServiceRegistration registration) {
+    }
+}
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
index 4c9f31a..92443de 100644
--- 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
@@ -17,9 +17,10 @@
 package org.gradle.tooling.internal.provider.connection;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.tooling.internal.adapter.CompatibleIntrospector;
 import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
 import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
-import org.gradle.tooling.internal.reflect.CompatibleIntrospector;
 
 import java.io.File;
 import java.io.InputStream;
@@ -28,9 +29,6 @@ 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;
@@ -123,4 +121,9 @@ public class AdaptedOperationParameters implements ProviderOperationParameters {
     public List<String> getTasks() {
         return tasks;
     }
+
+    public List<InternalLaunchable> getLaunchables() {
+        return maybeGet(null, "getLaunchables");
+    }
+
 }
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
index 0650cde..350be3e 100644
--- 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
@@ -17,6 +17,7 @@
 package org.gradle.tooling.internal.provider.connection;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
 import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
 
 import java.io.File;
@@ -27,8 +28,6 @@ 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);
@@ -64,4 +63,6 @@ public interface ProviderOperationParameters {
     List<String> getArguments(List<String> defaultArguments);
 
     List<String> getTasks();
+
+    List<InternalLaunchable> getLaunchables();
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/jdk6/Jdk6ClassLookup.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/jdk6/Jdk6ClassLookup.java
new file mode 100644
index 0000000..6dbdf40
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/jdk6/Jdk6ClassLookup.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.jdk6;
+
+import org.gradle.api.Transformer;
+
+import java.io.ObjectStreamClass;
+
+public class Jdk6ClassLookup implements Transformer<ObjectStreamClass, Class<?>> {
+    public ObjectStreamClass transform(Class<?> original) {
+        return ObjectStreamClass.lookupAny(original);
+    }
+}
diff --git a/subprojects/launcher/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry b/subprojects/launcher/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
new file mode 100644
index 0000000..af8bd56
--- /dev/null
+++ b/subprojects/launcher/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
@@ -0,0 +1 @@
+org.gradle.tooling.internal.provider.ToolingServices
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
index 8ba8b67..0a0d202 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
@@ -79,8 +79,8 @@ class MainTest extends Specification {
         main.run()
 
         then:
-        outputs.stdErr.contains('internal error')
-        outputs.stdErr.contains('java.lang.RuntimeException: broken')
+        outputs.stdErr.contains('FAILURE: Build failed with an exception')
+        outputs.stdErr.contains('broken')
         completedWithFailure
         failure == thrownFailure
     }
@@ -94,8 +94,8 @@ class MainTest extends Specification {
         main.run()
 
         then:
-        outputs.stdErr.contains('internal error')
-        outputs.stdErr.contains('java.lang.RuntimeException: broken')
+        outputs.stdErr.contains('FAILURE: Build failed with an exception')
+        outputs.stdErr.contains('broken')
         completedWithFailure
         failure == thrownFailure
     }
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 5ccf12e..7aebba1 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
@@ -15,17 +15,20 @@
  */
 package org.gradle.launcher.cli
 
-import org.gradle.StartParameter
-import org.gradle.cli.CommandLineConverter
 import org.gradle.cli.CommandLineParser
+import org.gradle.cli.SystemPropertiesCommandLineConverter
+import org.gradle.initialization.DefaultCommandLineConverter
+import org.gradle.initialization.LayoutCommandLineConverter
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.internal.service.ServiceRegistry
+import org.gradle.launcher.cli.converter.DaemonCommandLineConverter
+import org.gradle.launcher.cli.converter.LayoutToPropertiesConverter
+import org.gradle.launcher.cli.converter.PropertiesToDaemonParametersConverter
+import org.gradle.launcher.cli.converter.PropertiesToStartParameterConverter
 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.daemon.configuration.GradlePropertiesConfigurer
-import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter
+import org.gradle.launcher.exec.InProcessBuildActionExecuter
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.logging.internal.OutputEventListener
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
@@ -38,19 +41,21 @@ class BuildActionsFactoryTest extends Specification {
     public final SetSystemProperties sysProperties = new SetSystemProperties();
     @Rule
     TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    final CommandLineConverter<StartParameter> startParameterConverter = Mock()
-    final ServiceRegistry loggingServices = Mock()
-    final GradlePropertiesConfigurer configurer = Stub()
-    final BuildActionsFactory factory = new BuildActionsFactory(loggingServices, startParameterConverter, configurer)
+    ServiceRegistry loggingServices = Mock()
+    PropertiesToDaemonParametersConverter propertiesToDaemonParametersConverter = Stub()
 
-    def setup() {        
+    BuildActionsFactory factory = new BuildActionsFactory(
+            loggingServices, Stub(DefaultCommandLineConverter), new DaemonCommandLineConverter(),
+            Stub(LayoutCommandLineConverter), Stub(SystemPropertiesCommandLineConverter),
+            Stub(LayoutToPropertiesConverter), Stub(PropertiesToStartParameterConverter),
+            propertiesToDaemonParametersConverter)
+
+    def setup() {
         _ * loggingServices.get(OutputEventListener) >> Mock(OutputEventListener)
         _ * loggingServices.get(ProgressLoggerFactory) >> Mock(ProgressLoggerFactory)
     }
 
     def "executes build"() {
-        configurer.configureParameters(_ as StartParameter) >> new DaemonParameters()
-
         when:
         def action = convert('args')
 
@@ -59,8 +64,6 @@ class BuildActionsFactoryTest extends Specification {
     }
 
     def "by default daemon is not used"() {
-        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(false)
-
         when:
         def action = convert('args')
 
@@ -69,8 +72,6 @@ class BuildActionsFactoryTest extends Specification {
     }
 
     def "daemon is used when command line option is used"() {
-        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(false)
-
         when:
         def action = convert('--daemon', 'args')
 
@@ -78,19 +79,7 @@ class BuildActionsFactoryTest extends Specification {
         isDaemon action
     }
 
-    def "daemon is used when daemon parameters say so"() {
-        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(true)
-
-        when:
-        def action = convert('args')
-
-        then:
-        isDaemon action
-    }
-
     def "does not use daemon when no-daemon command line option issued"() {
-        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(true)
-
         when:
         def action = convert('--no-daemon', 'args')
 
@@ -99,8 +88,6 @@ class BuildActionsFactoryTest extends Specification {
     }
 
     def "stops daemon"() {
-        configurer.configureParameters(_) >> new DaemonParameters()
-
         when:
         def action = convert('--stop')
 
@@ -110,8 +97,6 @@ class BuildActionsFactoryTest extends Specification {
     }
 
     def "runs daemon in foreground"() {
-        configurer.configureParameters(_) >> new DaemonParameters()
-
         when:
         def action = convert('--foreground')
 
@@ -124,7 +109,7 @@ class BuildActionsFactoryTest extends Specification {
         given:
         def javaHome = tmpDir.createDir("javahome")
         javaHome.createFile(OperatingSystem.current().getExecutableName("bin/java"))
-        configurer.configureParameters(_) >> new DaemonParameters().setJavaHome(javaHome.canonicalFile)
+        propertiesToDaemonParametersConverter.convert(_, _) >> { args -> args[1].javaHome = javaHome }
 
         when:
         def action = convert()
@@ -149,7 +134,7 @@ class BuildActionsFactoryTest extends Specification {
     void isInProcess(def action) {
         // Relying on impl of Actions.toAction(Runnable)
         assert action.runnable instanceof RunBuildAction
-        assert action.runnable.executer instanceof InProcessGradleLauncherActionExecuter
+        assert action.runnable.executer instanceof InProcessBuildActionExecuter
     }
 
     void isSingleUseDaemon(def action) {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
index d504f7b..ea9f046 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
@@ -161,7 +161,7 @@ class CommandLineActionFactoryTest extends Specification {
         outputs.stdErr.contains('--some-option')
 
         and:
-        1 * loggingConfigurationConverter.configure(!null) >> {CommandLineParser parser -> parser.option("logging").hasArgument()}
+        1 * loggingConfigurationConverter.configure(!null) >> {CommandLineParser parser -> parser.option("logging").hasArgument(String)}
         1 * actionFactory1.configureCommandLineParser(!null) >> {CommandLineParser parser -> parser.option('some-option')}
         1 * executionListener.onFailure({it instanceof CommandLineArgumentException})
         0 * executionListener._
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
index eca6cc0..93e275c 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
@@ -18,12 +18,12 @@ package org.gradle.launcher.cli
 import org.gradle.StartParameter
 import org.gradle.initialization.BuildClientMetaData
 import org.gradle.launcher.exec.BuildActionParameters
-import org.gradle.launcher.exec.GradleLauncherActionExecuter
+import org.gradle.launcher.exec.BuildActionExecuter
 import spock.lang.Specification
 import org.gradle.api.logging.LogLevel
 
 class RunBuildActionTest extends Specification {
-    final GradleLauncherActionExecuter<BuildActionParameters> client = Mock()
+    final BuildActionExecuter<BuildActionParameters> client = Mock()
     final StartParameter startParameter = Mock()
     final BuildClientMetaData clientMetaData = Mock()
     final File currentDir = new File('current-dir')
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/DaemonCommandLineConverterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/DaemonCommandLineConverterTest.groovy
new file mode 100644
index 0000000..05873ef
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/DaemonCommandLineConverterTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter
+
+import org.gradle.cli.CommandLineParser
+import org.gradle.launcher.daemon.configuration.DaemonParameters
+import spock.lang.Specification
+
+class DaemonCommandLineConverterTest extends Specification {
+
+    def "converts daemon options"() {
+        expect:
+        !convert([]).enabled
+        !convert(['--no-daemon']).enabled
+        convert(['--daemon']).enabled
+        convert(['--no-daemon', '--daemon']).enabled
+    }
+
+    private DaemonParameters convert(Iterable args) {
+        CommandLineParser parser = new CommandLineParser()
+        def converter = new DaemonCommandLineConverter()
+        converter.configure(parser)
+        converter.convert(args)
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/LayoutToPropertiesConverterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/LayoutToPropertiesConverterTest.groovy
new file mode 100644
index 0000000..4327a2f
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/LayoutToPropertiesConverterTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter
+
+import org.gradle.initialization.BuildLayoutParameters
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.launcher.daemon.configuration.GradleProperties.IDLE_TIMEOUT_PROPERTY
+import static org.gradle.launcher.daemon.configuration.GradleProperties.JVM_ARGS_PROPERTY
+
+class LayoutToPropertiesConverterTest extends Specification {
+
+    @Rule SetSystemProperties sysProperties = new SetSystemProperties()
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    def converter = new LayoutToPropertiesConverter()
+    BuildLayoutParameters layout
+    Map<String, String> props = new HashMap<String, String>()
+
+    def setup() {
+        layout = new BuildLayoutParameters()
+            .setGradleUserHomeDir(temp.createDir("gradleHome"))
+            .setProjectDir(temp.createDir("projectDir"))
+            .setSearchUpwards(false)
+    }
+
+    def "only configures gradle properties"() {
+        when:
+        temp.file("gradleHome/gradle.properties") << "foo=bar"
+
+        then:
+        converter.convert(layout, props).foo == null
+    }
+
+    def "configures from gradle home dir"() {
+        when:
+        temp.file("gradleHome/gradle.properties") << "$JVM_ARGS_PROPERTY=-Xmx1024m -Dprop=value"
+
+        then:
+        converter.convert(layout, props).get(JVM_ARGS_PROPERTY) == '-Xmx1024m -Dprop=value'
+    }
+
+    def "configures from project dir"() {
+        when:
+        temp.file("projectDir/gradle.properties") << "$IDLE_TIMEOUT_PROPERTY=125"
+
+        then:
+        converter.convert(layout, props).get(IDLE_TIMEOUT_PROPERTY) == "125"
+    }
+
+    def "configures from root dir in a multiproject build"() {
+        when:
+        temp.file("projectDir/settings.gradle") << "include 'foo'"
+        temp.file("projectDir/gradle.properties") << "$JVM_ARGS_PROPERTY=-Xmx128m"
+        layout.setProjectDir(temp.file("projectDir/foo"))
+        layout.searchUpwards = true
+
+        then:
+        converter.convert(layout, props).get(JVM_ARGS_PROPERTY) == '-Xmx128m'
+    }
+
+    def "gradle home properties take precedence over project dir properties"() {
+        when:
+        temp.file("gradleHome/gradle.properties") << "$JVM_ARGS_PROPERTY=-Xmx1024m"
+        temp.file("projectDir/gradle.properties") << "$JVM_ARGS_PROPERTY=-Xmx512m"
+
+        then:
+        converter.convert(layout, props).get(JVM_ARGS_PROPERTY) == '-Xmx1024m'
+    }
+
+    def "system property takes precedence over gradle home"() {
+        when:
+        temp.file("gradleHome/gradle.properties") << "$JVM_ARGS_PROPERTY=-Xmx1024m"
+        System.setProperty(JVM_ARGS_PROPERTY, '-Xmx2048m')
+
+        then:
+        converter.convert(layout, props).get(JVM_ARGS_PROPERTY) == '-Xmx2048m'
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/PropertiesToDaemonParametersConverterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/PropertiesToDaemonParametersConverterTest.groovy
new file mode 100644
index 0000000..29c7085
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/PropertiesToDaemonParametersConverterTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter
+
+import org.gradle.api.GradleException
+import org.gradle.initialization.BuildLayoutParameters
+import org.gradle.internal.jvm.Jvm
+import org.gradle.launcher.daemon.configuration.DaemonParameters
+import org.gradle.launcher.daemon.configuration.GradleProperties
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.launcher.daemon.configuration.GradleProperties.*
+
+class PropertiesToDaemonParametersConverterTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def converter = new PropertiesToDaemonParametersConverter()
+    def params = new DaemonParameters(new BuildLayoutParameters())
+
+    def "can configure jvm args combined with a system property"() {
+        when:
+        converter.convert([(JVM_ARGS_PROPERTY): '-Xmx512m -Dprop=value'], params)
+
+        then:
+        params.effectiveJvmArgs.contains('-Xmx512m')
+        !params.effectiveJvmArgs.contains('-Dprop=value')
+
+        params.systemProperties == [prop: 'value']
+    }
+
+    def "supports 'empty' system properties"() {
+        when:
+        converter.convert([(JVM_ARGS_PROPERTY): "-Dfoo= -Dbar"], params)
+
+        then:
+        params.systemProperties == [foo: '', bar: '']
+    }
+
+    def "configures from gradle properties"() {
+        when:
+        converter.convert([
+                (JVM_ARGS_PROPERTY): '-Xmx256m',
+                (JAVA_HOME_PROPERTY): Jvm.current().javaHome.absolutePath,
+                (DAEMON_ENABLED_PROPERTY): "true",
+                (DAEMON_BASE_DIR_PROPERTY): new File("baseDir").absolutePath,
+                (IDLE_TIMEOUT_PROPERTY): "115",
+                (DEBUG_MODE_PROPERTY): "true",
+        ], params)
+
+        then:
+        params.effectiveJvmArgs.contains("-Xmx256m")
+        params.debug
+        params.effectiveJavaHome == Jvm.current().javaHome
+        params.enabled
+        params.baseDir == new File("baseDir").absoluteFile
+        params.idleTimeout == 115
+    }
+
+    def "shows nice message for dummy java home"() {
+        when:
+        converter.convert([(JAVA_HOME_PROPERTY) : "/invalid/path"], params)
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.java.home'
+        ex.message.contains '/invalid/path'
+    }
+
+    def "shows nice message for invalid java home"() {
+        def dummyDir = temp.createDir("foobar")
+        when:
+        converter.convert([(GradleProperties.JAVA_HOME_PROPERTY) : dummyDir.absolutePath], params)
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.java.home'
+        ex.message.contains 'foobar'
+    }
+
+    def "shows nice message for invalid idle timeout"() {
+        when:
+        converter.convert((GradleProperties.IDLE_TIMEOUT_PROPERTY): 'asdf', params)
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.daemon.idletimeout'
+        ex.message.contains 'asdf'
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/PropertiesToStartParameterConverterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/PropertiesToStartParameterConverterTest.groovy
new file mode 100644
index 0000000..5c93357
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/converter/PropertiesToStartParameterConverterTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.converter
+
+import org.gradle.StartParameter
+import spock.lang.Specification
+
+import static org.gradle.launcher.daemon.configuration.GradleProperties.CONFIGURE_ON_DEMAND_PROPERTY
+import static org.gradle.launcher.daemon.configuration.GradleProperties.PARALLEL_PROPERTY
+
+class PropertiesToStartParameterConverterTest extends Specification {
+
+    def converter = new PropertiesToStartParameterConverter()
+
+    def "converts"() {
+        expect:
+        converter.convert([(PARALLEL_PROPERTY): "true"], new StartParameter()).parallelThreadCount == -1
+        converter.convert([(PARALLEL_PROPERTY): "false"], new StartParameter()).parallelThreadCount == 0
+        converter.convert([(CONFIGURE_ON_DEMAND_PROPERTY): "TRUE"], new StartParameter()).configureOnDemand
+        !converter.convert([(CONFIGURE_ON_DEMAND_PROPERTY): "xxx"], new StartParameter()).configureOnDemand
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy
index 2fb3ff3..8303cb3 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy
@@ -20,9 +20,6 @@ import org.gradle.launcher.daemon.bootstrap.DaemonOutputConsumer
 import org.gradle.process.internal.ExecHandleBuilder
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 5/7/12
- */
 class DaemonExecHandleBuilderSpec extends Specification {
 
     def builder = Mock(ExecHandleBuilder)
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy
index 662ea81..686dd4a 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy
@@ -22,9 +22,6 @@ import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.process.ExecResult
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 4/10/12
- */
 class DaemonGreeterTest extends Specification {
 
     DocumentationRegistry registry = Mock()
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy
index 15beb53..3ad3084 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy
@@ -20,9 +20,6 @@ package org.gradle.launcher.daemon.bootstrap
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 4/28/12
- */
 class DaemonOutputConsumerTest extends Specification {
 
     def consumer = new DaemonOutputConsumer()
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy
index c9a682a..9afa0fc 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy
@@ -18,9 +18,6 @@ package org.gradle.launcher.daemon.bootstrap
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 4/10/12
- */
 class DaemonStartupCommunicationSpec extends Specification {
 
     def comm = new DaemonStartupCommunication()
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
index 75c177f..44f51c9 100644
--- 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
@@ -16,31 +16,29 @@
 
 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)
+    final staleAddressDetector = Mock(DaemonClientConnection.StaleAddressDetector)
+    final connection = new DaemonClientConnection(delegate, 'id', staleAddressDetector)
 
     def "stops"() {
         when:
         connection.stop()
+
         then:
         1 * delegate.stop()
-        0 * onFailure.run()
+        0 * staleAddressDetector._
 
         when:
         connection.requestStop()
+
         then:
         1 * delegate.requestStop()
-        0 * onFailure.run()
+        0 * staleAddressDetector._
     }
 
     def "dispatches messages"() {
@@ -49,7 +47,7 @@ class DaemonClientConnectionTest extends Specification {
 
         then:
         1 * delegate.dispatch("foo")
-        0 * onFailure.run()
+        0 * staleAddressDetector._
     }
 
     def "receives messages"() {
@@ -61,33 +59,75 @@ class DaemonClientConnectionTest extends Specification {
 
         then:
         "bar" == out
-        0 * onFailure.run()
+        0 * staleAddressDetector._
     }
 
-    def "handles failed dispatch"() {
+    def "treats failure to dispatch before receiving as a stale address"() {
+        def failure = new FooException()
+
         given:
-        delegate.dispatch("foo") >> { throw new FooException() }
+        delegate.dispatch("foo") >> { throw failure }
 
         when:
         connection.dispatch("foo")
 
         then:
-        def ex = thrown(GradleException)
-        ex.cause instanceof FooException
-        1 * onFailure.run()
+        def ex = thrown(StaleDaemonAddressException)
+        ex.cause == failure
+        1 * staleAddressDetector.maybeStaleAddress(failure) >> true
+        0 * staleAddressDetector._
+    }
+
+    def "handles failed dispatch"() {
+        def failure = new FooException()
+
+        given:
+        delegate.receive() >> "result"
+        delegate.dispatch("broken") >> { throw failure }
+
+        when:
+        connection.receive()
+        connection.dispatch("broken")
+
+        then:
+        def ex = thrown(DaemonConnectionException)
+        ex.class == DaemonConnectionException
+        ex.cause == failure
+        0 * staleAddressDetector._
+    }
+
+    def "treats failure to receive first message as a stale address"() {
+        def failure = new FooException()
+
+        given:
+        delegate.receive() >> { throw failure }
+
+        when:
+        connection.receive()
+
+        then:
+        def ex = thrown(StaleDaemonAddressException)
+        ex.cause == failure
+        1 * staleAddressDetector.maybeStaleAddress(failure) >> true
+        0 * staleAddressDetector._
     }
 
     def "handles failed receive"() {
+        def failure = new FooException()
+
         given:
-        delegate.receive() >> { throw new FooException() }
+        1 * delegate.receive() >> "first"
+        delegate.receive() >> { throw failure }
 
         when:
         connection.receive()
+        connection.receive()
 
         then:
-        def ex = thrown(GradleException)
-        ex.cause instanceof FooException
-        1 * onFailure.run()
+        def ex = thrown(DaemonConnectionException)
+        ex.class == DaemonConnectionException
+        ex.cause == failure
+        0 * staleAddressDetector._
     }
 
     class FooException extends RuntimeException {}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
index 101a226..b693af5 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
@@ -22,10 +22,11 @@ import org.gradle.logging.LoggingServiceRegistry
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.initialization.BuildLayoutParameters
 
 class DaemonClientServicesTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
-    final DaemonParameters parameters = new DaemonParameters(baseDir: tmp.testDirectory)
+    final DaemonParameters parameters = new DaemonParameters(new BuildLayoutParameters()).setBaseDir(tmp.testDirectory)
     final DaemonClientServices services = new DaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), parameters, System.in)
 
     def "makes a DaemonRegistry available"() {
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 2536cd7..256437c 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.launcher.daemon.client
 
-import org.gradle.initialization.GradleLauncherAction
+import org.gradle.initialization.BuildAction
 import org.gradle.internal.id.IdGenerator
 import org.gradle.launcher.daemon.context.DaemonCompatibilitySpec
 import org.gradle.launcher.exec.BuildActionParameters
@@ -91,7 +91,7 @@ class DaemonClientTest extends ConcurrentSpecification {
 
     def executesAction() {
         when:
-        def result = client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
+        def result = client.execute(Stub(BuildAction), Stub(BuildActionParameters))
 
         then:
         result == '[result]'
@@ -108,7 +108,7 @@ class DaemonClientTest extends ConcurrentSpecification {
         RuntimeException failure = new RuntimeException()
 
         when:
-        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
+        client.execute(Stub(BuildAction), Stub(BuildActionParameters))
 
         then:
         RuntimeException e = thrown()
@@ -122,15 +122,15 @@ class DaemonClientTest extends ConcurrentSpecification {
         0 * _
     }
     
-    def "tries to find a different daemon if getting the first result from the daemon fails"() {
+    def "tries to find a different daemon if connected to a stale daemon address"() {
         DaemonClientConnection connection2 = Mock()
 
         when:
-        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
+        client.execute(Stub(BuildAction), Stub(BuildActionParameters))
 
         then:
         2 * connector.connect(compatibilitySpec) >>> [connection, connection2]
-        1 * connection.dispatch({it instanceof Build}) >> { throw new RuntimeException("Boo!")}
+        1 * connection.dispatch({it instanceof Build}) >> { throw new StaleDaemonAddressException("broken", new RuntimeException())}
         1 * connection.stop()
         2 * connection2.receive() >>> [Stub(BuildStarted), new Success('')]
         0 * connection._
@@ -140,7 +140,7 @@ class DaemonClientTest extends ConcurrentSpecification {
         DaemonClientConnection connection2 = Mock()
 
         when:
-        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
+        client.execute(Stub(BuildAction), Stub(BuildActionParameters))
 
         then:
         2 * connector.connect(compatibilitySpec) >>> [connection, connection2]
@@ -156,7 +156,7 @@ class DaemonClientTest extends ConcurrentSpecification {
         DaemonClientConnection connection2 = Mock()
 
         when:
-        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
+        client.execute(Stub(BuildAction), Stub(BuildActionParameters))
 
         then:
         2 * connector.connect(compatibilitySpec) >>> [connection, connection2]
@@ -173,7 +173,7 @@ class DaemonClientTest extends ConcurrentSpecification {
         connection.receive() >> Mock(DaemonUnavailable)
 
         when:
-        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
+        client.execute(Stub(BuildAction), 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 708e958..f2c8522 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
@@ -15,7 +15,6 @@
  */
 package org.gradle.launcher.daemon.client
 
-import org.gradle.api.GradleException
 import org.gradle.api.internal.specs.ExplainingSpec
 import org.gradle.api.internal.specs.ExplainingSpecs
 import org.gradle.launcher.daemon.context.DaemonContext
@@ -23,9 +22,9 @@ import org.gradle.launcher.daemon.context.DefaultDaemonContext
 import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo
 import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry
 import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.ConnectCompletion
 import org.gradle.messaging.remote.internal.ConnectException
 import org.gradle.messaging.remote.internal.Connection
-import org.gradle.messaging.remote.internal.MessageSerializer
 import org.gradle.messaging.remote.internal.OutgoingConnector
 import spock.lang.Specification
 
@@ -36,15 +35,11 @@ class DefaultDaemonConnectorTest extends Specification {
     def daemonCounter = 0
 
     class OutgoingConnectorStub implements OutgoingConnector {
-        Connection connect(Address address, ClassLoader messageClassLoader) throws ConnectException {
+        ConnectCompletion connect(Address address) throws ConnectException {
             def connection = [:] as Connection
             // unsure why I can't add this as property in the map-mock above
             connection.metaClass.num = address.num
-            connection
-        }
-
-        Connection connect(Address address, MessageSerializer serializer) {
-            throw new UnsupportedOperationException()
+            return { connection } as ConnectCompletion
         }
     }
 
@@ -84,7 +79,7 @@ class DefaultDaemonConnectorTest extends Specification {
 
     def theConnector
 
-    def getConnector() {
+    def DefaultDaemonConnector getConnector() {
         if (theConnector == null) {
             theConnector = createConnector()
         }
@@ -115,22 +110,6 @@ class DefaultDaemonConnectorTest extends Specification {
         connection && connection.connection.num < 12
     }
 
-    def "suspect address is removed from the registry on connect failure"() {
-        given:
-        startIdleDaemon()
-        assert !registry.all.empty
-
-        connector.connector.connect(_ as Address, _) >> { throw new ConnectException("Problem!", new RuntimeException("foo")) }
-
-        when:
-        def connection = connector.maybeConnect( { true } as ExplainingSpec)
-
-        then:
-        !connection
-
-        registry.all.empty
-    }
-
     def "maybeConnect() returns null when no daemon matches spec"() {
         given:
         startIdleDaemon()
@@ -192,6 +171,22 @@ class DefaultDaemonConnectorTest extends Specification {
         connector.connect(ExplainingSpecs.satisfyNone())
 
         then:
-        thrown(GradleException)
+        thrown(DaemonConnectionException)
+    }
+
+    def "suspect address is removed from the registry on connect failure"() {
+        given:
+        startIdleDaemon()
+        assert !registry.all.empty
+
+        connector.connector.connect(_ as Address) >> { throw new ConnectException("Problem!", new RuntimeException("foo")) }
+
+        when:
+        def connection = connector.maybeConnect( { true } as ExplainingSpec)
+
+        then:
+        !connection
+
+        registry.all.empty
     }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy
index 7255863..22ab607 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy
@@ -15,14 +15,16 @@
  */
 package org.gradle.launcher.daemon.client
 
-import org.gradle.api.Action
+import org.gradle.api.Nullable
 import org.gradle.internal.concurrent.DefaultExecutorFactory
+import org.gradle.internal.io.TextStream
+import spock.lang.Specification
+import spock.util.concurrent.BlockingVariable
+
 import java.util.concurrent.LinkedBlockingQueue
 import java.util.concurrent.TimeUnit
-import static org.gradle.util.TextUtil.*
 
-import spock.lang.*
-import spock.util.concurrent.BlockingVariable
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class InputForwarderTest extends Specification {
 
@@ -35,21 +37,20 @@ class InputForwarderTest extends Specification {
     def received = new LinkedBlockingQueue()
     def finishedHolder = new BlockingVariable(2)
 
-    def action = action { received << it }
-    def onFinish = finished { finishedHolder.set(true) }
-
-    def action(Closure action) {
-        this.action = action as Action
-    }
+    def action = new TextStream() {
+        void text(String text) {
+            received << text
+        }
 
-    def finished(Closure runnable) {
-        this.onFinish = runnable
+        void endOfStream(@Nullable Throwable failure) {
+            finishedHolder.set(true)
+        }
     }
 
     def forwarder
 
     def createForwarder() {
-        forwarder = new InputForwarder(inputStream, action, onFinish, executerFactory, bufferSize)
+        forwarder = new InputForwarder(inputStream, action, executerFactory, bufferSize)
         forwarder.start()
     }
 
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy
index 86e9412..180e0cd 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy
@@ -16,13 +16,10 @@
 
 package org.gradle.launcher.daemon.client
 
-import org.gradle.messaging.remote.internal.Connection
 import org.gradle.internal.id.IdGenerator
+import org.gradle.messaging.remote.internal.Connection
 import spock.lang.Specification
 
-/**
- * @author: Szczepan Faber, created at: 9/13/11
- */
 public class StopDispatcherTest extends Specification {
 
     def dispatcher = new StopDispatcher({12} as IdGenerator)
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
index 3e6089e..8f8f529 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
@@ -22,6 +22,7 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.initialization.BuildLayoutParameters
 
 public class CurrentProcessTest extends Specification {
     @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
@@ -29,7 +30,7 @@ public class CurrentProcessTest extends Specification {
     private FileResolver fileResolver = Mock()
     private def currentJavaHome = tmpDir.file('java_home')
     private JvmOptions currentJvmOptions = new JvmOptions(fileResolver)
-    private DaemonParameters parameters = new DaemonParameters()
+    private DaemonParameters parameters = new DaemonParameters(new BuildLayoutParameters())
 
     def "can only run build with identical java home"() {
         when:
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
index e89cc05..be8f20b 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
@@ -16,13 +16,13 @@
 package org.gradle.launcher.daemon.configuration
 
 import org.gradle.StartParameter
-import org.gradle.internal.jvm.Jvm
+import org.gradle.initialization.BuildLayoutParameters
 import spock.lang.Specification
 
 import static java.lang.Boolean.parseBoolean
 
 class DaemonParametersTest extends Specification {
-    final DaemonParameters parameters = new DaemonParameters()
+    final DaemonParameters parameters = new DaemonParameters(new BuildLayoutParameters())
 
     def "has reasonable default values"() {
         expect:
@@ -31,7 +31,6 @@ class DaemonParametersTest extends Specification {
 
     def "uses default values when no specific gradle properties provided"() {
         expect:
-        parameters.configureFrom(new GradleProperties()) //empty gradle properties
         assertDefaultValues()
     }
 
@@ -49,38 +48,13 @@ class DaemonParametersTest extends Specification {
 
     def "configuring jvmargs replaces the defaults"() {
         when:
-        parameters.configureFrom(Stub(GradleProperties) {
-            getJvmArgs() >> "-Xmx17m"
-        })
+        parameters.setJvmArgs(["-Xmx17m"])
 
         then:
         parameters.effectiveJvmArgs.each { assert !parameters.defaultJvmArgs.contains(it) }
     }
 
-    def "can configure jvm args combined with a system property"() {
-        when:
-        parameters.configureFrom(Stub(GradleProperties) {
-            getJvmArgs() >> '-Xmx1024m -Dprop=value'
-        })
-
-        then:
-        parameters.effectiveJvmArgs.contains('-Xmx1024m')
-        !parameters.effectiveJvmArgs.contains('-Dprop=value')
-
-        parameters.systemProperties == [prop: 'value']
-    }
-
-    def "supports 'empty' system properties"() {
-        when:
-        parameters.configureFrom(Stub(GradleProperties) {
-            getJvmArgs() >> "-Dfoo= -Dbar"
-        })
-
-        then:
-        parameters.getSystemProperties() == [foo: '', bar: '']
-    }
-
-    def "knows if not using default jvm args"() {
+    def "knows if uses default jvm args"() {
         given:
         assert parameters.usingDefaultJvmArgs
 
@@ -91,35 +65,9 @@ class DaemonParametersTest extends Specification {
         !parameters.usingDefaultJvmArgs
     }
 
-    def "knows if not using default jvm args when configured"() {
-        given:
-        assert parameters.usingDefaultJvmArgs
-
-        when:
-        parameters.configureFrom(Stub(GradleProperties) {
-            getJvmArgs() >> "-Dfoo= -Dbar"
-        })
-
-        then:
-        !parameters.usingDefaultJvmArgs
-    }
-
-    def "knows if using default jvm args"() {
-        when:
-        parameters.configureFrom(Stub(GradleProperties) {
-            getJavaHome() >> Jvm.current().getJavaHome()
-            getJvmArgs() >> null
-        })
-
-        then:
-        parameters.usingDefaultJvmArgs
-    }
-
     def "can configure debug mode"() {
         when:
-        parameters.configureFrom(Stub (GradleProperties) {
-            isDebugMode() >> parseBoolean(flag)
-        })
+        parameters.setDebug(parseBoolean(flag))
 
         then:
         parameters.effectiveJvmArgs.contains("-Xdebug") == parseBoolean(flag)
@@ -128,24 +76,4 @@ class DaemonParametersTest extends Specification {
         where:
         flag << ["true", "false"]
     }
-
-    def "configures from gradle properties"() {
-        def props = Stub (GradleProperties) {
-            getJvmArgs() >> '-Xmx256m'
-            getJavaHome() >> new File("javaHome")
-            isDaemonEnabled() >> true
-            getDaemonBaseDir() >> new File("baseDir")
-            getIdleTimeout() >> 115
-        }
-
-        when:
-        parameters.configureFrom(props)
-
-        then:
-        parameters.effectiveJvmArgs.contains("-Xmx256m")
-        parameters.javaHome == new File("javaHome")
-        parameters.enabled
-        parameters.baseDir == new File("baseDir")
-        parameters.idleTimeout == 115
-    }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurerTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurerTest.groovy
deleted file mode 100644
index 51d4892..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurerTest.groovy
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.launcher.daemon.configuration
-
-import org.gradle.StartParameter
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-import static java.util.Collections.singletonMap
-
-/**
- * by Szczepan Faber, created at: 1/22/13
- */
-class GradlePropertiesConfigurerTest extends Specification {
-
-    @Rule private TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    private configurer = Spy(GradlePropertiesConfigurer)
-
-    def "prepares properties and configures parameters"() {
-        def param = Mock(StartParameter)
-        def properties = Mock(GradleProperties)
-
-        configurer.prepareProperties(_, false, _, _) >> properties
-
-        when:
-        def daemonParams = configurer.configureParameters(param);
-
-        then:
-        //daemon params created
-        daemonParams
-        //start parameter updated
-        1 * properties.updateStartParameter(param)
-        //daemon params configured from properties
-        properties.getIdleTimeout() >> 123
-        daemonParams.idleTimeout == 123
-    }
-
-    def "gradle home properties take precedence over project dir properties"() {
-        def projectDir = tmpDir.createDir("project")
-        projectDir.file("gradle.properties") << "$GradleProperties.IDLE_TIMEOUT_PROPERTY=100"
-        def gradleHome = tmpDir.createDir("gradleHome")
-        gradleHome.file("gradle.properties") << "$GradleProperties.IDLE_TIMEOUT_PROPERTY=200"
-
-        when:
-        def props = configurer.prepareProperties(projectDir, false, gradleHome, [:])
-
-        then:
-        props.idleTimeout == 200
-    }
-
-    def "system property takes precedence over gradle home"() {
-        def projectDir = tmpDir.createDir("project")
-        def gradleHome = tmpDir.createDir("gradleHome")
-        gradleHome.file("gradle.properties") << "$GradleProperties.IDLE_TIMEOUT_PROPERTY=200"
-
-        when:
-        def props = configurer.prepareProperties(projectDir, false, gradleHome, singletonMap(GradleProperties.IDLE_TIMEOUT_PROPERTY, '300'))
-
-        then:
-        props.idleTimeout == 300
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesTest.groovy
deleted file mode 100644
index 0b8b1aa..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesTest.groovy
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.launcher.daemon.configuration
-
-import org.gradle.api.GradleException
-import org.gradle.internal.jvm.Jvm
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-import org.gradle.StartParameter
-
-/**
- * by Szczepan Faber, created at: 1/22/13
- */
-class GradlePropertiesTest extends Specification {
-
-    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    private properties = new GradleProperties()
-
-    def "configures from gradle home dir (using jvm args as example)"() {
-        given:
-        tmpDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((GradleProperties.JVM_ARGS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
-        }
-
-        when:
-        properties.configureFromGradleUserHome(tmpDir.testDirectory)
-
-        then:
-        properties.jvmArgs == '-Xmx1024m -Dprop=value'
-    }
-
-    def "configures from project dir (using jvm args as example)"() {
-        given:
-        tmpDir.createFile("settings.gradle")
-        tmpDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((GradleProperties.JVM_ARGS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
-        }
-
-        when:
-        properties.configureFromBuildDir(tmpDir.testDirectory, true)
-
-        then:
-        properties.jvmArgs == '-Xmx1024m -Dprop=value'
-    }
-
-    def "configures from system properties (using jvm args as example)"() {
-        when:
-        properties.configureFromSystemProperties((GradleProperties.JVM_ARGS_PROPERTY):  '-Xmx1024m -Dprop=value')
-
-        then:
-        properties.jvmArgs == '-Xmx1024m -Dprop=value'
-    }
-
-    def "sets default daemon base dir when configuring from gradle user home"() {
-        def userHome = new File("some-dir")
-
-        when:
-        properties.configureFromGradleUserHome(userHome)
-
-        then:
-        properties.daemonBaseDir == new File(userHome, "daemon").canonicalFile
-    }
-
-    def "configures daemon base dir when configuring from system properties"() {
-        when:
-        properties.configureFromSystemProperties((GradleProperties.BASE_DIR_PROPERTY): 'some-dir')
-
-        then:
-        properties.daemonBaseDir == new File('some-dir').canonicalFile
-    }
-
-    def "configures idle timeout"() {
-        when:
-        properties.configureFrom((GradleProperties.IDLE_TIMEOUT_PROPERTY): '4000')
-
-        then:
-        properties.idleTimeout == 4000
-    }
-
-    def "shows nice message for invalid idle timeout"() {
-        when:
-        properties.configureFrom((GradleProperties.IDLE_TIMEOUT_PROPERTY): 'asdf')
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains 'org.gradle.daemon.idletimeout'
-        ex.message.contains 'asdf'
-    }
-
-    def "configures daemon mode"() {
-        when:
-        properties.configureFrom((GradleProperties.DAEMON_ENABLED_PROPERTY):  flag)
-
-        then:
-        properties.daemonEnabled.toString() == flag
-
-        where:
-        flag << ["true", "false"]
-    }
-
-    def "configures java home"() {
-        File jdk = Jvm.current().getJavaHome()
-
-        when:
-        properties.configureFrom([(GradleProperties.JAVA_HOME_PROPERTY) : jdk.toString()])
-
-        then:
-        properties.javaHome == jdk.canonicalFile
-    }
-
-    def "shows nice message for dummy java home"() {
-        when:
-        properties.configureFrom([(GradleProperties.JAVA_HOME_PROPERTY) : "/invalid/path"])
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains 'org.gradle.java.home'
-        ex.message.contains '/invalid/path'
-    }
-
-    def "shows nice message for invalid java home"() {
-        def dummyDir = tmpDir.createDir("foobar")
-        when:
-        properties.configureFrom([(GradleProperties.JAVA_HOME_PROPERTY) : dummyDir.absolutePath])
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains 'org.gradle.java.home'
-        ex.message.contains 'foobar'
-    }
-
-    def "configures debug mode"() {
-        when:
-        properties.configureFrom((GradleProperties.DEBUG_MODE_PROPERTY): flag)
-
-        then:
-        properties.debugMode.toString() == flag
-
-        where:
-        flag << ["true", "false"]
-    }
-
-    def "configures parallel mode"() {
-        when:
-        properties.configureFrom((GradleProperties.PARALLEL_PROPERTY): flag)
-
-        then:
-        properties.parallelMode.toString() == flag
-
-        where:
-        flag << ["true", "false"]
-    }
-
-    def "informs start parameter about configure on demand"() {
-        def param = Mock(StartParameter)
-
-        when:
-        properties.updateStartParameter(param)
-
-        then:
-        0 * param._
-
-        when:
-        properties.configureOnDemand = true
-        properties.updateStartParameter(param)
-
-        then:
-        1 * param.setConfigureOnDemand(true)
-        0 * _
-    }
-
-    def "informs start parameter about parallel mode"() {
-        def param = Mock(StartParameter)
-
-        when:
-        properties.updateStartParameter(param)
-
-        then:
-        0 * param._
-
-        when:
-        properties.parallelMode = true
-        properties.updateStartParameter(param)
-
-        then:
-        1 * param.setParallelThreadCount(-1)
-    }
-
-    def "does not set parallel mode when it was already configured even parallel mode is requested"() {
-        def param = Mock(StartParameter) {
-            isParallelThreadCountConfigured() >> true
-        }
-
-        when:
-        properties.parallelMode = true //requested
-        properties.updateStartParameter(param)
-
-        then:
-        0 * param.setParallelThreadCount(_)
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
index 72d2038..f480549 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
@@ -16,7 +16,6 @@
 package org.gradle.launcher.daemon.context
 
 import org.gradle.internal.nativeplatform.ProcessEnvironment
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.ConfigureUtil
 import org.gradle.util.Requires
@@ -78,11 +77,14 @@ class DaemonCompatibilitySpecSpec extends Specification {
 
     @Requires(TestPrecondition.SYMLINKS)
     def "contexts with symlinked javaHome are compatible"() {
-        File dir = tmp.createDir("a")
-        File link = new File(tmp.testDirectory, "link")
-        FileSystems.default.createSymbolicLink(link, dir)
+        def dir = new File(tmp.testDirectory, "a")
+        dir.mkdirs()
+        def link = new File(tmp.testDirectory, "link")
+//        new TestFile(link).createLink(dir)
+        ["ln", "-s", dir, link].execute().waitFor()
 
         assert dir != link
+        assert link.exists()
         assert dir.canonicalFile == link.canonicalFile
 
         client { javaHome = dir }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
index 1a9c3a3..9346c82 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
@@ -20,9 +20,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 4/10/12
- */
 class DaemonDiagnosticsTest extends Specification {
 
     @Rule TestNameTestDirectoryProvider temp
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 8c14629..2ed71c7 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
@@ -15,6 +15,13 @@
  */
 package org.gradle.launcher.daemon.registry
 
+import org.gradle.cache.internal.DefaultFileLockManager
+import org.gradle.cache.internal.FileLock
+import org.gradle.cache.internal.FileLockManager
+import org.gradle.cache.internal.ProcessMetaDataProvider
+import org.gradle.cache.internal.locklistener.FileLockContentionHandler
+import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.launcher.daemon.context.DefaultDaemonContext
 import org.gradle.messaging.remote.internal.inet.SocketInetAddress
 import org.gradle.test.fixtures.ConcurrentTestUtil
@@ -24,9 +31,12 @@ import spock.lang.Specification
 
 class DaemonRegistryServicesTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
+    def parent = Mock(ServiceRegistry) {
+        get(FileLockManager) >> new DefaultFileLockManager(Stub(ProcessMetaDataProvider), Stub(FileLockContentionHandler))
+    }
 
     def registry(baseDir) {
-        new DaemonRegistryServices(tmp.createDir(baseDir))
+        new DefaultServiceRegistry(parent).addProvider(new DaemonRegistryServices(tmp.createDir(baseDir)))
     }
 
     def "same daemon registry instance is used for same daemon registry file across service instances"() {
@@ -39,7 +49,7 @@ class DaemonRegistryServicesTest extends Specification {
     
     def "the registry can be concurrently written to"() {
         when:
-        def registry = registry("someDir").createDaemonRegistry()
+        def registry = registry("someDir").get(DaemonRegistry)
         5.times { idx ->
             concurrent.start {
                 def context = new DefaultDaemonContext("$idx", new File("$idx"), new File("$idx"), idx, 5000, [])
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 0e7be47..63cf3a9 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
@@ -22,9 +22,6 @@ import org.gradle.launcher.daemon.server.DomainRegistryUpdater
 import org.gradle.messaging.remote.Address
 import spock.lang.Specification
 
-/**
- * @author: Szczepan Faber, created at: 9/12/11
- */
 public class DomainRegistryUpdaterTest extends Specification {
 
     final DaemonRegistry registry = Mock()
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 449e3dd..49d0d25 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
@@ -29,17 +29,15 @@ import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.unlockU
 
 class PersistentDaemonRegistryTest extends Specification {
 
-    @Rule TestNameTestDirectoryProvider tmp
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
     
     int addressCounter = 0
     def lockManager = createDefaultFileLockManager()
+    def file = tmp.file("registry")
+    def registry = new PersistentDaemonRegistry(file, lockManager)
 
     def "corrupt registry file is ignored"() {
         given:
-        def file = tmp.file("registry")
-        def registry = new PersistentDaemonRegistry(file, lockManager)
-        
-        and:
         registry.store(address(), daemonContext(), "password", true)
 
         expect:
@@ -54,7 +52,6 @@ class PersistentDaemonRegistryTest extends Specification {
 
     def "safely removes from registry file"() {
         given:
-        def registry = new PersistentDaemonRegistry(tmp.file("registry"), lockManager)
         def address = address()
 
         and:
@@ -72,7 +69,6 @@ class PersistentDaemonRegistryTest extends Specification {
 
     def "safely removes if registry empty"() {
         given:
-        def registry = new PersistentDaemonRegistry(tmp.file("registry"), lockManager)
         def address = address()
 
         when:
@@ -81,7 +77,29 @@ class PersistentDaemonRegistryTest extends Specification {
         then:
         registry.all.empty
     }
-    
+
+    def "mark busy ignores entry that has been removed"() {
+        given:
+        def address = address()
+
+        when:
+        registry.markBusy(address)
+
+        then:
+        registry.all.empty
+    }
+
+    def "mark idle ignores entry that has been removed"() {
+        given:
+        def address = address()
+
+        when:
+        registry.markIdle(address)
+
+        then:
+        registry.all.empty
+    }
+
     DaemonContext daemonContext() {
         new DaemonContextBuilder([maybeGetPid: {null}] as ProcessEnvironment).with {
             daemonRegistryDir = tmp.createDir("daemons")
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 924b9e1..078f6b2 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
@@ -16,12 +16,11 @@
 
 package org.gradle.launcher.daemon.server
 
-import org.gradle.BuildResult
-import org.gradle.GradleLauncher
 import org.gradle.api.logging.LogLevel
 import org.gradle.configuration.GradleLauncherMetaData
-import org.gradle.initialization.DefaultGradleLauncherFactory
-import org.gradle.initialization.GradleLauncherAction
+import org.gradle.initialization.BuildAction
+import org.gradle.initialization.BuildController
+import org.gradle.initialization.GradleLauncherFactory
 import org.gradle.internal.nativeplatform.ProcessEnvironment
 import org.gradle.launcher.daemon.client.DaemonClient
 import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
@@ -30,24 +29,21 @@ import org.gradle.launcher.daemon.server.exec.DaemonCommandAction
 import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter
 import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter
 import org.gradle.launcher.daemon.server.exec.ForwardClientInput
+import org.gradle.launcher.daemon.server.exec.NoOpDaemonCommandAction
 import org.gradle.launcher.exec.DefaultBuildActionParameters
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 12/21/11
- */
 class DaemonServerExceptionHandlingTest extends Specification {
 
     @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
     def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), 0, new HashMap(System.properties), [:], temp.testDirectory, LogLevel.ERROR)
 
-    static class DummyLauncherAction implements GradleLauncherAction, Serializable {
+    static class DummyLauncherAction implements BuildAction, Serializable {
         Object someState
-        Object getResult() { null }
-        BuildResult run(GradleLauncher launcher) { null }
+        Object run(BuildController buildController) { null }
     }
 
     def "sends back failure when the daemon cannot receive the first command"() {
@@ -73,8 +69,9 @@ 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(ProcessEnvironment), loggingServices.getFactory(LoggingManagerInternal.class).create(), new File("dummy")) {
+                return new DefaultDaemonCommandExecuter(get(GradleLauncherFactory),
+                        get(ProcessEnvironment), getFactory(LoggingManagerInternal.class).create(),
+                        new File("dummy"), new NoOpDaemonCommandAction()) {
                     List<DaemonCommandAction> createActions(DaemonContext daemonContext) {
                         def actions = new LinkedList(super.createActions(daemonContext));
                         configureDeamonActions(actions);
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
index d6cc441..2ba0ce1 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
@@ -27,7 +27,6 @@ import spock.lang.Specification
 import static java.util.Arrays.asList
 
 class DaemonServicesTest extends Specification {
-
     @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
     final DaemonServices services = new DaemonServices(new DefaultDaemonServerConfiguration("uid", tmp.testDirectory, 100, asList()),
             LoggingServiceRegistry.newEmbeddableLogging(), Mock(LoggingManagerInternal))
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 1c92c0d..bf0b2db 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
@@ -19,9 +19,6 @@ import org.gradle.launcher.daemon.server.exec.DaemonUnavailableException
 import spock.lang.Specification
 
 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)
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/exec/DaemonHygieneActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/exec/DaemonHygieneActionTest.groovy
new file mode 100644
index 0000000..25fef4e
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/exec/DaemonHygieneActionTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.TimeProvider
+import spock.lang.Specification
+
+
+class DaemonHygieneActionTest extends Specification {
+
+    def "performs hygiene action"() {
+        def a = Spy(DaemonHygieneAction)
+
+        when:
+        a.execute(Mock(DaemonCommandExecution))
+        a.execute(Mock(DaemonCommandExecution))
+
+        then:
+        1 * a.gc()
+    }
+
+    def "does not trigger gc too often"() {
+        def timeProvider = Stub(TimeProvider) {
+            getCurrentTime() >>> [10, 100, 200]
+        }
+        def a = Spy(DaemonHygieneAction, constructorArgs: [100, timeProvider])
+
+        when:
+        //executed X3
+        a.execute(Mock(DaemonCommandExecution))
+        a.execute(Mock(DaemonCommandExecution))
+        a.execute(Mock(DaemonCommandExecution))
+
+        then:
+        //gc() called X2
+        2 * a.gc()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
index aba09df..6d55dca 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
@@ -14,16 +14,12 @@
  * limitations under the License.
  */
 
-package org.gradle.launcher.exec;
-
+package org.gradle.launcher.exec
 
+import org.gradle.api.logging.LogLevel
 import org.gradle.configuration.GradleLauncherMetaData
 import spock.lang.Specification
-import org.gradle.api.logging.LogLevel
 
-/**
- * @author: Szczepan Faber, created at: 9/6/11
- */
 public class DefaultBuildActionParametersTest extends Specification {
 
     def "is serializable"() {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessBuildActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessBuildActionExecuterTest.groovy
new file mode 100644
index 0000000..a3768a3
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessBuildActionExecuterTest.groovy
@@ -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.launcher.exec
+
+import org.gradle.initialization.BuildController
+import spock.lang.Specification
+import org.gradle.initialization.GradleLauncherFactory
+import org.gradle.initialization.BuildAction
+
+import org.gradle.initialization.BuildRequestMetaData
+import org.gradle.GradleLauncher
+import org.gradle.BuildResult
+
+import org.gradle.StartParameter
+
+class InProcessBuildActionExecuterTest extends Specification {
+    final GradleLauncherFactory factory = Mock()
+    final GradleLauncher launcher = Mock()
+    final BuildActionParameters param = Mock()
+    final BuildRequestMetaData metaData = Mock()
+    final BuildResult buildResult = Mock()
+    final InProcessBuildActionExecuter executer = new InProcessBuildActionExecuter(factory)
+
+    def setup() {
+        _ * param.buildRequestMetaData >> metaData
+    }
+
+    def "creates a launcher using a default StartParameter when the action does not specify any"() {
+        BuildAction<String> action = Mock()
+
+        when:
+        def result = executer.execute(action, param)
+
+        then:
+        result == '<result>'
+
+        and:
+        1 * factory.newInstance(!null, metaData) >> launcher
+        1 * action.run(!null) >> { BuildController controller ->
+            assert controller.launcher == launcher
+            return '<result>'
+        }
+    }
+
+    def "creates a launcher using StartParameter specified by the action"() {
+        BuildAction<String> action = Mock()
+        def startParam = new StartParameter()
+
+        when:
+        def result = executer.execute(action, param)
+
+        then:
+        result == '<result>'
+
+        and:
+        1 * factory.newInstance(startParam, metaData) >> launcher
+        1 * action.run(!null) >> { BuildController controller ->
+            controller.startParameter = startParam
+            assert controller.launcher == launcher
+            return '<result>'
+        }
+    }
+
+    def "cannot set start parameters after launcher created"() {
+        BuildAction<String> action = Mock()
+        def startParam = new StartParameter()
+
+        given:
+        _ * action.run(!null) >> { BuildController controller ->
+            controller.launcher
+            controller.startParameter = startParam
+        }
+        _ * factory.newInstance(!null, metaData) >> launcher
+
+        when:
+        executer.execute(action, param)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot change start parameter after launcher has been created.'
+    }
+
+    def "runs build when requested by action"() {
+        BuildAction<String> action = Mock()
+
+        when:
+        def result = executer.execute(action, param)
+
+        then:
+        result == '<result>'
+
+        and:
+        1 * factory.newInstance(!null, metaData) >> launcher
+        1 * launcher.run() >> buildResult
+        _ * buildResult.failure >> null
+        1 * action.run(!null) >> { BuildController controller ->
+            controller.run()
+            return '<result>'
+        }
+    }
+
+    def "configures build when requested by action"() {
+        BuildAction<String> action = Mock()
+
+        when:
+        def result = executer.execute(action, param)
+
+        then:
+        result == '<result>'
+
+        and:
+        1 * factory.newInstance(!null, metaData) >> launcher
+        1 * launcher.getBuildAnalysis() >> buildResult
+        _ * buildResult.failure >> null
+        1 * action.run(!null) >> { BuildController controller ->
+            controller.configure()
+            return '<result>'
+        }
+    }
+
+    def "wraps build failure"() {
+        def failure = new RuntimeException()
+        BuildAction<String> action = Mock()
+
+        given:
+        buildResult.failure >> failure
+
+        when:
+        executer.execute(action, param)
+
+        then:
+        ReportedException e = thrown()
+        e.cause == failure
+
+        and:
+        1 * factory.newInstance(!null, metaData) >> launcher
+        1 * launcher.run() >> buildResult
+        1 * action.run(!null) >> { BuildController controller ->
+            controller.run()
+        }
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuterTest.groovy
deleted file mode 100644
index 3193438..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuterTest.groovy
+++ /dev/null
@@ -1,100 +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.exec
-
-import spock.lang.Specification
-import org.gradle.initialization.GradleLauncherFactory
-import org.gradle.initialization.GradleLauncherAction
-
-import org.gradle.initialization.BuildRequestMetaData
-import org.gradle.GradleLauncher
-import org.gradle.BuildResult
-
-import org.gradle.StartParameter
-
-class InProcessGradleLauncherActionExecuterTest extends Specification {
-    final GradleLauncherFactory factory = Mock()
-    final GradleLauncher launcher = Mock()
-    final BuildActionParameters param = Mock()
-    final BuildRequestMetaData metaData = Mock()
-    final BuildResult buildResult = Mock()
-    final InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(factory)
-
-    def setup() {
-        _ * param.buildRequestMetaData >> metaData
-    }
-
-    def "executes the provided action using a default StartParameter"() {
-        GradleLauncherAction<String> action = Mock()
-
-        given:
-        buildResult.failure >> null
-        action.result >> '<result>'
-
-        when:
-        def result = executer.execute(action, param)
-
-        then:
-        result == '<result>'
-
-        and:
-        1 * factory.newInstance(!null, metaData) >> launcher
-        1 * action.run(launcher) >> buildResult
-    }
-
-    def "executes the provided action using the provided StartParameter"() {
-        TestAction action = Mock()
-        def startParam = new StartParameter()
-
-        given:
-        action.configureStartParameter() >> startParam
-        buildResult.failure >> null
-        action.result >> '<result>'
-
-        when:
-        def result = executer.execute(action, param)
-
-        then:
-        result == '<result>'
-
-        and:
-        1 * factory.newInstance(startParam, metaData) >> launcher
-        1 * action.run(launcher) >> buildResult
-    }
-
-    def "wraps build failure"() {
-        def failure = new RuntimeException()
-        GradleLauncherAction<String> action = Mock()
-
-        given:
-        buildResult.failure >> failure
-
-        when:
-        executer.execute(action, param)
-
-        then:
-        ReportedException e = thrown()
-        e.cause == failure
-
-        and:
-        1 * factory.newInstance(!null, metaData) >> launcher
-        1 * action.run(launcher) >> buildResult
-    }
-}
-
-interface TestAction extends GradleLauncherAction<String>, InitializationAware {
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/AbstractClassGraphSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/AbstractClassGraphSpec.groovy
new file mode 100644
index 0000000..246d80b
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/AbstractClassGraphSpec.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider
+
+import org.gradle.internal.classloader.ClasspathUtil
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestClassLoader
+import org.junit.Rule
+import spock.lang.Specification
+
+abstract class AbstractClassGraphSpec extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    /**
+     * Returns the classpath for the given classes.
+     */
+    List<File> originalClassPath(Class<?>... classes) {
+        return classes.collect { ClasspathUtil.getClasspathForClass(it) }
+    }
+
+    /**
+     * Makes a copy of the given classes and returns the classpath for these copies. Each class is added to its own classpath root.
+     */
+    List<File> isolatedClasses(Class<?>... classes) {
+        return classes.collect {
+            def name = it.name.replace('.', '/') + '.class'
+            def classPathRoot = tmpDir.file(it.name)
+            def classFile = classPathRoot.file(name)
+            def resource = it.classLoader.getResource(name)
+            classFile.parentFile.mkdirs()
+            classFile.bytes = resource.bytes
+            classPathRoot
+        }
+    }
+
+    /**
+     * Returns a URLClassLoader with the given classpath and parent. Parent defaults to system ClassLoader.
+     */
+    URLClassLoader urlClassLoader(ClassLoader parent = ClassLoader.systemClassLoader.parent, List<File> classpath) {
+        return new URLClassLoader(classpath.collect { it.toURI().toURL() } as URL[], parent)
+    }
+
+    /**
+     * Returns a custom ClassLoader with the given classpath and parent. Parent defaults to system ClassLoader.
+     */
+    ClassLoader customClassLoader(ClassLoader parent = ClassLoader.systemClassLoader.parent, List<File> classpath) {
+        return new TestClassLoader(parent, classpath)
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ClasspathInfererTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ClasspathInfererTest.groovy
new file mode 100644
index 0000000..fcd2cc3
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ClasspathInfererTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider
+
+import org.gradle.internal.classloader.MutableURLClassLoader
+import org.gradle.tooling.BuildAction
+
+class ClasspathInfererTest extends AbstractClassGraphSpec {
+    def factory = new ClasspathInferer()
+
+    def "determines action and tooling API classpath when loaded via a URLClassLoader"() {
+        def cl = urlClassLoader(toolingApiClassPath + isolatedClasses(CustomAction, CustomModel))
+        def actionClass = cl.loadClass(CustomAction.name)
+
+        expect:
+        def classpath = []
+        factory.getClassPathFor(actionClass, classpath)
+        def loader = new MutableURLClassLoader(ClassLoader.systemClassLoader.parent, classpath)
+        def action = loader.loadClass(CustomAction.name).newInstance()
+        action.execute(null)
+    }
+
+    def "determines action and tooling API classpath when loaded via custom ClassLoader implementation"() {
+        def cl = customClassLoader(toolingApiClassPath + isolatedClasses(CustomAction, CustomModel))
+        def actionClass = cl.loadClass(CustomAction.name)
+
+        expect:
+        def classpath = []
+        factory.getClassPathFor(actionClass, classpath)
+        def loader = new MutableURLClassLoader(ClassLoader.systemClassLoader.parent, classpath)
+        def action = loader.loadClass(CustomAction.name).newInstance()
+        action.execute(null)
+    }
+
+    def "determines action and tooling API classpath when loaded via multiple custom ClassLoader implementations"() {
+        def toolingCl = customClassLoader(toolingApiClassPath)
+        def cl = customClassLoader(toolingCl, isolatedClasses(CustomAction, CustomModel))
+        def actionClass = cl.loadClass(CustomAction.name)
+
+        expect:
+        def classpath = []
+        factory.getClassPathFor(actionClass, classpath)
+        def loader = new MutableURLClassLoader(ClassLoader.systemClassLoader.parent, classpath)
+        def action = loader.loadClass(CustomAction.name).newInstance()
+        action.execute(null)
+    }
+
+    private List<File> getToolingApiClassPath() {
+        originalClassPath(BuildAction)
+    }
+
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
index 5e8b6fc..0bcd189 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
@@ -18,13 +18,16 @@
 
 package org.gradle.tooling.internal.provider
 
+import org.gradle.launcher.cli.converter.PropertiesToStartParameterConverter
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.tooling.internal.impl.LaunchableImplementation
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 3/6/12
- */
+import static org.gradle.util.Matchers.isSerializable
+import static org.hamcrest.MatcherAssert.assertThat
+
 class ConfiguringBuildActionTest extends Specification {
     @Rule TestNameTestDirectoryProvider temp
 
@@ -70,18 +73,50 @@ class ConfiguringBuildActionTest extends Specification {
         def start = action.configureStartParameter()
 
         then:
-        !start.isSearchUpwards()
+        !start.searchUpwards
+    }
+
+    def "searchUpwards configured directly on the action wins over the command line setting"() {
+        when:
+        def action = new ConfiguringBuildAction(arguments: ['-u'], searchUpwards: true)
+        def start = action.configureStartParameter()
+
+        then:
+        start.searchUpwards
     }
 
-    def "can overwrite configure on demand via build arguments"() {
+    def "the start parameter is configured from properties"() {
+        given:
+        def converter = Mock(PropertiesToStartParameterConverter)
+        def action = new ConfiguringBuildAction(properties: [foo: 'bar'])
+
+        when:
+        action.configureStartParameter(converter)
+
+        then:
+        1 * converter.convert([foo: 'bar'], _)
+    }
+
+    def "is serializable"() {
         expect:
-        !new ConfiguringBuildAction().configureStartParameter().configureOnDemand
+        assertThat(new ConfiguringBuildAction({} as ProviderOperationParameters, null, [foo: 'bar']), isSerializable())
+    }
+
+    def "accepts launchables from consumer"() {
+        given:
+        def selector = Mock(LaunchableImplementation)
+        _ * selector.taskName >> 'myTask'
+        _ * selector.projectPath >> ':child'
+
+        ProviderOperationParameters providerParameters = Mock(ProviderOperationParameters)
+        _ * providerParameters.launchables >> [selector]
+        _ * providerParameters.tasks >> []
+        def action = new ConfiguringBuildAction(providerParameters, null, [:])
 
         when:
-        def action = new ConfiguringBuildAction(arguments: ['--configure-on-demand'])
         def start = action.configureStartParameter()
 
         then:
-        start.configureOnDemand
+        start.projectPath == ':child'
     }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConnectionScopeServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConnectionScopeServicesTest.groovy
new file mode 100644
index 0000000..1732422
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConnectionScopeServicesTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider
+
+import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.logging.LoggingServiceRegistry
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import spock.lang.Specification
+
+class ConnectionScopeServicesTest extends Specification {
+    def logging = LoggingServiceRegistry.newEmbeddableLogging()
+    def services = new DefaultServiceRegistry(logging).addProvider(new ConnectionScopeServices(logging))
+
+    def "provides a ProviderConnection implementation"() {
+        expect:
+        services.get(ProviderConnection) instanceof ProviderConnection
+    }
+
+    def "provides a ProtocolToModelAdapter implementation"() {
+        expect:
+        services.get(ProtocolToModelAdapter) instanceof ProtocolToModelAdapter
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomAction.java b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomAction.java
new file mode 100644
index 0000000..207b77e
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.GradleConnectionException;
+
+import java.util.List;
+
+public class CustomAction implements BuildAction<Object> {
+    // Some interesting type references
+    byte[][][] buffers;
+    BuildController[][] controllers;
+    List<String> values;
+
+    public Object execute(BuildController controller) throws GradleConnectionException {
+        return new CustomModel(this);
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomModel.java b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomModel.java
new file mode 100644
index 0000000..3dd4345
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomModel.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.io.Serializable;
+
+public class CustomModel implements Serializable {
+    public CustomModel(CustomAction customAction) {
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomPayload.java b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomPayload.java
new file mode 100644
index 0000000..ac5f931
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/CustomPayload.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.io.Serializable;
+
+public class CustomPayload implements Serializable, PayloadInterface {
+    Object value;
+    boolean ok;
+    Integer[] anArray = new Integer[0];
+
+    public String getValue() {
+        return value.toString();
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonBuildActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonBuildActionExecuterTest.groovy
new file mode 100644
index 0000000..0f6c1ca
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonBuildActionExecuterTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.provider
+
+import org.gradle.initialization.BuildAction
+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.connection.ProviderOperationParameters
+import spock.lang.Specification
+
+class DaemonBuildActionExecuterTest extends Specification {
+    final DaemonClient client = Mock()
+    final BuildAction<String> action = Mock()
+    final ProviderOperationParameters parameters = Mock()
+    final DaemonParameters daemonParameters = Mock()
+    final DaemonBuildActionExecuter executer = new DaemonBuildActionExecuter(client, daemonParameters)
+
+    def unpacksReportedException() {
+        def failure = new RuntimeException()
+
+        when:
+        executer.execute(action, parameters)
+
+        then:
+        BuildExceptionVersion1 e = thrown()
+        e.cause == failure
+        1 * client.execute(action, !null) >> { throw new ReportedException(failure) }
+        _ * daemonParameters.effectiveSystemProperties >> [:]
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy
deleted file mode 100644
index dc380e1..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.provider
-
-import org.gradle.initialization.GradleLauncherAction
-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.connection.ProviderOperationParameters
-import spock.lang.Specification
-
-class DaemonGradleLauncherActionExecuterTest extends Specification {
-    final DaemonClient client = Mock()
-    final GradleLauncherAction<String> action = Mock()
-    final ProviderOperationParameters parameters = Mock()
-    final DaemonParameters daemonParameters = Mock()
-    final DaemonGradleLauncherActionExecuter executer = new DaemonGradleLauncherActionExecuter(client, daemonParameters)
-
-    def unpacksReportedException() {
-        def failure = new RuntimeException()
-
-        when:
-        executer.execute(action, parameters)
-
-        then:
-        BuildExceptionVersion1 e = thrown()
-        e.cause == failure
-        1 * client.execute(action, !null) >> { throw new ReportedException(failure) }
-        _ * daemonParameters.effectiveSystemProperties >> [:]
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DefaultBuildControllerTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DefaultBuildControllerTest.groovy
new file mode 100644
index 0000000..acc466d
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DefaultBuildControllerTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api.internal.project.ProjectInternal
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.tooling.internal.gradle.GradleProjectIdentity
+import org.gradle.tooling.internal.protocol.InternalUnsupportedModelException
+import org.gradle.tooling.internal.protocol.ModelIdentifier
+import org.gradle.tooling.model.internal.ProjectSensitiveToolingModelBuilder
+import org.gradle.tooling.provider.model.ToolingModelBuilder
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+import org.gradle.tooling.provider.model.UnknownModelException
+import spock.lang.Specification
+
+class DefaultBuildControllerTest extends Specification {
+    def gradle = Stub(GradleInternal)
+    def registry = Stub(ToolingModelBuilderRegistry)
+    def project = Stub(ProjectInternal) {
+        getServices() >> Stub(ServiceRegistry) {
+            get(ToolingModelBuilderRegistry) >> registry
+        }
+    }
+    def modelId = Stub(ModelIdentifier) {
+        getName() >> 'some.model'
+    }
+    def modelBuilder = Stub(ToolingModelBuilder)
+    def controller = new DefaultBuildController(gradle)
+
+    def "adapts model not found exception to protocol exception"() {
+        def failure = new UnknownModelException("not found")
+
+        given:
+        _ * gradle.defaultProject >> project
+        _ * registry.getBuilder('some.model') >> { throw failure }
+
+        when:
+        controller.getModel(null, modelId)
+
+        then:
+        InternalUnsupportedModelException e = thrown()
+        e.cause == failure
+    }
+
+    def "uses builder for specified project"() {
+        def target = Stub(GradleProjectIdentity)
+        def rootProject = Stub(ProjectInternal)
+        def model = new Object()
+
+        given:
+        _ * target.path >> ":some:path"
+        _ * gradle.rootProject >> rootProject
+        _ * rootProject.project(":some:path") >> project
+        _ * registry.getBuilder("some.model") >> modelBuilder
+        _ * modelBuilder.buildAll("some.model", project) >> model
+
+        when:
+        def result = controller.getModel(target, modelId)
+
+        then:
+        result.getModel() == model
+    }
+
+    def "uses builder for default project when none specified"() {
+        def model = new Object()
+
+        given:
+        _ * gradle.defaultProject >> project
+        _ * registry.getBuilder("some.model") >> modelBuilder
+        _ * modelBuilder.buildAll("some.model", project) >> model
+
+        when:
+        def result = controller.getModel(null, modelId)
+
+        then:
+        result.getModel() == model
+    }
+
+    def "passes information about default project when context sensitive builder is used"() {
+        def contextModelBuilder = Stub(ProjectSensitiveToolingModelBuilder)
+        def model = new Object()
+
+        given:
+        _ * gradle.defaultProject >> project
+        _ * registry.getBuilder("some.model") >> contextModelBuilder
+        _ * contextModelBuilder.buildAll("some.model", project, true) >> model
+
+        when:
+        def result = controller.getModel(null, modelId)
+
+        then:
+        result.getModel() == model
+    }
+
+    def "passes information about specified project when context sensitive builder is used"() {
+        def contextModelBuilder = Stub(ProjectSensitiveToolingModelBuilder)
+        def model = new Object()
+        def target = Stub(GradleProjectIdentity)
+        def rootProject = Stub(ProjectInternal)
+
+        given:
+        _ * target.path >> ":some:path"
+        _ * gradle.rootProject >> rootProject
+        _ * rootProject.project(":some:path") >> project
+        _ * registry.getBuilder("some.model") >> contextModelBuilder
+        _ * contextModelBuilder.buildAll("some.model", project, false) >> model
+
+        when:
+        def result = controller.getModel(target, modelId)
+
+        then:
+        result.getModel() == model
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy
deleted file mode 100644
index b960551..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy
+++ /dev/null
@@ -1,37 +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.BuildResult
-import org.gradle.GradleLauncher
-import spock.lang.Specification
-
-class ExecuteBuildActionTest extends Specification {
-    final ExecuteBuildAction action = new ExecuteBuildAction()
-
-    def runsBuild() {
-        GradleLauncher launcher = Mock()
-        BuildResult buildResult = Mock()
-
-        when:
-        def result = action.run(launcher)
-
-        then:
-        result == buildResult
-        1 * launcher.run() >> buildResult
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingBuildActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingBuildActionExecuterTest.groovy
new file mode 100644
index 0000000..09beacb
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingBuildActionExecuterTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.provider
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.initialization.BuildAction
+import org.gradle.internal.Factory
+import org.gradle.launcher.exec.BuildActionExecuter
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters
+import spock.lang.Specification
+
+class LoggingBridgingBuildActionExecuterTest extends Specification {
+    final BuildActionExecuter<ProviderOperationParameters> target = Mock()
+    final Factory<LoggingManagerInternal> loggingManagerFactory = Mock()
+    final LoggingManagerInternal loggingManager = Mock()
+    final BuildAction<String> action = Mock()
+    final ProviderOperationParameters parameters = Mock()
+
+    //declared type-lessly to work around groovy eclipse plugin bug
+    final executer = new LoggingBridgingBuildActionExecuter(target, loggingManagerFactory)
+
+    def configuresLoggingWhileActionIsExecuting() {
+        when:
+        executer.execute(action, parameters)
+
+        then:
+        1 * loggingManagerFactory.create() >> loggingManager
+        1 * loggingManager.addOutputEventListener(!null)
+        1 * loggingManager.start()
+        1 * target.execute(action, parameters)
+        1 * loggingManager.stop()
+    }
+
+    def restoresLoggingWhenActionFails() {
+        def failure = new RuntimeException()
+
+        when:
+        executer.execute(action, parameters)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * loggingManagerFactory.create() >> loggingManager
+        1 * loggingManager.start()
+        1 * target.execute(action, parameters) >> {throw failure}
+        1 * loggingManager.stop()
+    }
+
+    def "sets log level accordingly"() {
+        given:
+        loggingManagerFactory.create() >> loggingManager
+        parameters.getBuildLogLevel() >> LogLevel.QUIET
+
+        when:
+        executer.execute(action, parameters)
+        
+        then:
+        1 * loggingManager.setLevel(LogLevel.QUIET)
+    }
+}
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
deleted file mode 100644
index dd15ebb..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.provider
-
-import org.gradle.api.logging.LogLevel
-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.connection.ProviderOperationParameters
-import spock.lang.Specification
-
-class LoggingBridgingGradleLauncherActionExecuterTest extends Specification {
-    final GradleLauncherActionExecuter<ProviderOperationParameters> target = Mock()
-    final Factory<LoggingManagerInternal> loggingManagerFactory = Mock()
-    final LoggingManagerInternal loggingManager = Mock()
-    final GradleLauncherAction<String> action = Mock()
-    final ProviderOperationParameters parameters = Mock()
-
-    //declared type-lessly to work around groovy eclipse plugin bug
-    final executer = new LoggingBridgingGradleLauncherActionExecuter(target, loggingManagerFactory)
-
-    def configuresLoggingWhileActionIsExecuting() {
-        when:
-        executer.execute(action, parameters)
-
-        then:
-        1 * loggingManagerFactory.create() >> loggingManager
-        1 * loggingManager.addOutputEventListener(!null)
-        1 * loggingManager.start()
-        1 * target.execute(action, parameters)
-        1 * loggingManager.stop()
-    }
-
-    def restoresLoggingWhenActionFails() {
-        def failure = new RuntimeException()
-
-        when:
-        executer.execute(action, parameters)
-
-        then:
-        RuntimeException e = thrown()
-        e == failure
-        1 * loggingManagerFactory.create() >> loggingManager
-        1 * loggingManager.start()
-        1 * target.execute(action, parameters) >> {throw failure}
-        1 * loggingManager.stop()
-    }
-
-    def "sets log level accordingly"() {
-        given:
-        loggingManagerFactory.create() >> loggingManager
-        parameters.getBuildLogLevel() >> LogLevel.QUIET
-
-        when:
-        executer.execute(action, parameters)
-        
-        then:
-        1 * loggingManager.setLevel(LogLevel.QUIET)
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ModelClassLoaderFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ModelClassLoaderFactoryTest.groovy
new file mode 100644
index 0000000..6be72e1
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ModelClassLoaderFactoryTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider
+
+import org.gradle.internal.classloader.MutableURLClassLoader
+import spock.lang.Specification
+
+class ModelClassLoaderFactoryTest extends Specification {
+    final ModelClassLoaderFactory registry = new ModelClassLoaderFactory()
+
+    def "creates ClassLoader for classpath"() {
+        def url1 = new URL("http://localhost/file1.jar")
+        def url2 = new URL("http://localhost/file2.jar")
+
+        when:
+        def cl = registry.getClassLoaderFor(new MutableURLClassLoader.Spec([url1, url2]), [null])
+
+        then:
+        cl instanceof MutableURLClassLoader
+        cl.URLs == [url1, url2] as URL[]
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/PayloadInterface.java b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/PayloadInterface.java
new file mode 100644
index 0000000..5fd22fc
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/PayloadInterface.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public interface PayloadInterface {
+    String getValue();
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/PayloadSerializerTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/PayloadSerializerTest.groovy
new file mode 100644
index 0000000..9d21b53
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/PayloadSerializerTest.groovy
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider
+
+import org.gradle.internal.classloader.FilteringClassLoader
+import org.junit.Assert
+import spock.lang.Ignore
+
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+
+class PayloadSerializerTest extends AbstractClassGraphSpec {
+    final PayloadSerializer originator = new PayloadSerializer(new DefaultPayloadClassLoaderRegistry(new ModelClassLoaderFactory()))
+    final PayloadSerializer receiver = new PayloadSerializer(new DefaultPayloadClassLoaderRegistry(new ModelClassLoaderFactory()))
+
+    def "can send an object between two parties"() {
+        expect:
+        def serialized = originator.serialize(source)
+        receiver.deserialize(serialized) == source
+
+        where:
+        source                            | _
+        null                              | _
+        "some value"                      | _
+    }
+
+    def "implementation classpath travels with object"() {
+        def payloadClass = isolated(CustomPayload, PayloadInterface).loadClass(CustomPayload.name)
+        def original = payloadClass.newInstance(value: 'value')
+
+        when:
+        def serialized = originator.serialize(original)
+        def received = receiver.deserialize(serialized)
+
+        then:
+        received.class != payloadClass
+        received.class != CustomPayload
+        received.class.name == CustomPayload.class.name
+        received.value == 'value'
+    }
+
+    def "uses system ClassLoader when original object is loaded by system ClassLoader"() {
+        when:
+        def serialized = originator.serialize(new StringBuilder('value'))
+        def received = receiver.deserialize(serialized)
+
+        then:
+        received.class == StringBuilder.class
+        received.toString() == 'value'
+    }
+
+    def "uses original ClassLoader when receiving a reply"() {
+        def payloadClass = isolated(CustomPayload, PayloadInterface).loadClass(CustomPayload.name)
+        def original = payloadClass.newInstance(value: 'value')
+
+        when:
+        def serialized = originator.serialize(original)
+        def received = receiver.deserialize(serialized)
+        def reply = originator.deserialize(receiver.serialize(received))
+
+        then:
+        received.class != CustomPayload.class
+        received.class != payloadClass
+        reply.class == payloadClass
+    }
+
+    def "handles nested objects which are not visible from root object ClassLoader"() {
+        def parent = isolated(WrapperPayload, PayloadInterface)
+        def wrapperClass = parent.loadClass(WrapperPayload.name)
+        def payloadClass = isolated(parent, CustomPayload).loadClass(CustomPayload.name)
+        assertNotVisible(wrapperClass, payloadClass)
+        def original = wrapperClass.newInstance(payload: payloadClass.newInstance(value: 'value'))
+
+        when:
+        def serialized = originator.serialize(original)
+        def received = receiver.deserialize(serialized)
+
+        then:
+        received.class != wrapperClass
+        received.class.name == WrapperPayload.name
+        received.payload.class != payloadClass
+        received.payload.class.name == CustomPayload.name
+        received.class.classLoader != received.payload.class.classLoader
+    }
+
+    @Ignore("work in progress")
+    def "handles objects in separate ClassLoaders with shared parent"() {
+        def filter = filter(PayloadInterface)
+        def wrapperClass = isolated(filter, WrapperPayload).loadClass(WrapperPayload.name)
+        def payloadClass = isolated(filter, CustomPayload).loadClass(CustomPayload.name)
+        assertNotVisible(wrapperClass, payloadClass)
+        assertNotVisible(payloadClass, wrapperClass)
+        def original = wrapperClass.newInstance(payload: payloadClass.newInstance(value: 'value'))
+
+        when:
+        def received = receiver.deserialize(originator.serialize(original))
+        def reply = originator.deserialize(receiver.serialize(received))
+
+        then:
+        received.class.classLoader.loadClass(PayloadInterface.name) == received.payload.class.classLoader.loadClass(PayloadInterface.name)
+        reply instanceof PayloadInterface
+        reply.value instanceof PayloadInterface
+        reply.class == wrapperClass
+        reply.payload.class == payloadClass
+    }
+
+    def "can send a dynamic proxy"() {
+        def cl = isolated(PayloadInterface)
+        def payloadClass = cl.loadClass(PayloadInterface.name)
+        def original = Proxy.newProxyInstance(cl, [payloadClass] as Class[], new GroovyInvocationHandler())
+
+        when:
+        def received = receiver.deserialize(originator.serialize(original))
+        def reply = originator.deserialize(receiver.serialize(received))
+
+        then:
+        received.class != original.class
+        !payloadClass.isInstance(received)
+        Proxy.getInvocationHandler(received).class != GroovyInvocationHandler.class
+        received.value == "result!"
+
+        payloadClass.isInstance(reply)
+        Proxy.getInvocationHandler(reply).class == GroovyInvocationHandler.class
+        reply.value == "result!"
+    }
+
+    def "can send a Class instance"() {
+        def cl = isolated(PayloadInterface).loadClass(PayloadInterface.name)
+
+        when:
+        def serialized = originator.serialize(cl)
+        def received = receiver.deserialize(serialized)
+
+        then:
+        received != cl
+        received.name == cl.name
+    }
+
+    def "reuses ClassLoaders for multiple invocations"() {
+        def cl = isolated(WrapperPayload, CustomPayload, PayloadInterface)
+        def wrapperClass = cl.loadClass(WrapperPayload.name)
+        def payloadClass = cl.loadClass(CustomPayload.name)
+        def original = wrapperClass.newInstance(payload: payloadClass.newInstance(value: 'value'))
+
+        when:
+        def received1 = receiver.deserialize(originator.serialize(original))
+        def reply1 = originator.deserialize(receiver.serialize(received1))
+        def received2 = receiver.deserialize(originator.serialize(original))
+        def reply2 = originator.deserialize(receiver.serialize(received2))
+
+        then:
+        received1.class.classLoader == received2.class.classLoader
+        received1.payload.class.classLoader == received2.payload.class.classLoader
+        reply1.class == wrapperClass
+        reply1.payload.class == payloadClass
+        reply2.class == wrapperClass
+        reply2.payload.class == payloadClass
+    }
+
+    void assertNotVisible(Class<?> from, Class<?> to) {
+        try {
+            from.classLoader.loadClass(to.name)
+            Assert.fail()
+        } catch (ClassNotFoundException) {
+            // expected
+        }
+    }
+
+    ClassLoader filter(Class<?> aClass) {
+        def filter = new FilteringClassLoader(aClass.classLoader)
+        filter.allowClass(aClass)
+        return filter
+    }
+
+    ClassLoader isolated(ClassLoader parent = ClassLoader.systemClassLoader.parent, Class<?>... classes) {
+        def classpath = isolatedClasses(classes)
+        def loader = urlClassLoader(parent, classpath)
+        for (Class<?> aClass : classes) {
+            assert loader.loadClass(aClass.name) != aClass
+        }
+        return loader
+    }
+
+    private static class GroovyInvocationHandler implements InvocationHandler, Serializable {
+        Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            return "result!"
+        }
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ToolingGlobalScopeServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ToolingGlobalScopeServicesTest.groovy
new file mode 100644
index 0000000..e9f8894
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ToolingGlobalScopeServicesTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider
+
+import org.gradle.internal.service.DefaultServiceRegistry
+import spock.lang.Specification
+
+class ToolingGlobalScopeServicesTest extends Specification {
+    def services = DefaultServiceRegistry.create(new ToolingGlobalScopeServices())
+
+    def "provides a PayloadSerializer"() {
+        expect:
+        services.get(PayloadSerializer) instanceof PayloadSerializer
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/WrapperPayload.java b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/WrapperPayload.java
new file mode 100644
index 0000000..d7b13e9
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/WrapperPayload.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.io.Serializable;
+
+public class WrapperPayload implements Serializable, PayloadInterface {
+    PayloadInterface payload;
+
+    public String getValue() {
+        return payload.getValue();
+    }
+}
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
index 7f73ec7..aa9da02 100644
--- 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
@@ -20,9 +20,6 @@ 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 {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy
index 105d527..4677732 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.tooling.internal.provider.connection
 import org.gradle.api.logging.LogLevel
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/18/13
- */
 class BuildLogLevelMixInTest extends Specification {
 
     final parameters = Mock(ProviderOperationParameters)
diff --git a/subprojects/maven/maven.gradle b/subprojects/maven/maven.gradle
index 5aed01a..fbe3fec 100644
--- a/subprojects/maven/maven.gradle
+++ b/subprojects/maven/maven.gradle
@@ -15,7 +15,7 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(':coreImpl')
@@ -28,6 +28,8 @@ dependencies {
     compile "org.sonatype.pmaven:pmaven-groovy:0.8-20100325 at jar"
     compile "org.codehaus.plexus:plexus-component-annotations:1.5.2 at jar"
 
+    testCompile libraries.xmlunit
+
     integTestCompile project(":ear")
 }
 
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
deleted file mode 100644
index 8233c72..0000000
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
+++ /dev/null
@@ -1,161 +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.plugins.maven
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-
-import static org.gradle.util.TextUtil.toPlatformLineSeparators
-
-/**
- * by Szczepan Faber, created at: 9/4/12
- */
-class MavenConversionIntegrationTest extends AbstractIntegrationSpec {
-
-    @Rule public final TestResources resources = new TestResources(temporaryFolder)
-
-    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 JUnitXmlTestExecutionResult(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 JUnitXmlTestExecutionResult(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()
-    }
-
-    def "enforcerplugin"() {
-        given:
-        file("build.gradle") << "apply plugin: 'maven2Gradle'"
-
-        when:
-        run 'maven2Gradle'
-
-        then:
-        noExceptionThrown()
-        and:
-        buildFile.text.contains("""configurations.all {
-it.exclude group: 'org.apache.maven'
-it.exclude group: 'org.apache.maven', module: 'badArtifact'
-it.exclude group: '*', module: 'badArtifact'
-}""")
-        when:
-        run 'clean', 'build'
-
-        then:
-        file("build/libs/enforcerExample-1.0.jar").exists()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy
index ab9f75b..16c3321 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy
@@ -13,22 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.api.publish.maven
+
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.maven.MavenFileModule
 
 class AbstractMavenPublishIntegTest extends AbstractIntegrationSpec {
 
-    protected def resolveArtifact(MavenFileModule module, def extension) {
-        doResolveArtifacts("""
-    dependencies {
-        resolve group: '${sq(module.groupId)}', name: '${sq(module.artifactId)}', version: '${sq(module.version)}', ext: '${sq(extension)}'
-    }
-""")
-    }
     protected def resolveArtifact(MavenFileModule module, def extension, def classifier) {
         doResolveArtifacts("""
     dependencies {
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomisationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomisationIntegTest.groovy
deleted file mode 100644
index 317f542..0000000
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomisationIntegTest.groovy
+++ /dev/null
@@ -1,228 +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.publish.maven
-
-class MavenPublishArtifactCustomisationIntegTest extends AbstractMavenPublishIntegTest {
-
-    def "can attach custom artifacts"() {
-        given:
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    artifact "customFile.txt"
-                    artifact customJar
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module("group", "projectText", "1.0")
-        module.assertPublished()
-        module.parsedPom.packaging == "txt"
-        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-customjar.jar")
-
-        and:
-        resolveArtifacts(module, [classifier: 'customjar']) == ["projectText-1.0-customjar.jar", "projectText-1.0.txt"]
-    }
-
-    def "can set custom artifacts to override component artifacts"() {
-        given:
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    from components.java
-                    artifacts = ["customFile.txt", customJar]
-                }
-            }
-
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module("group", "projectText", "1.0")
-        module.assertPublished()
-        module.parsedPom.packaging == "txt"
-        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-customjar.jar")
-
-        and:
-        resolveArtifacts(module, [classifier: 'customjar']) == ["projectText-1.0-customjar.jar", "projectText-1.0.txt"]
-    }
-
-    def "can configure custom artifacts when creating"() {
-        given:
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    artifact("customFile.txt") {
-                        classifier "output"
-                    }
-                    artifact(customFileTask.outputFile) {
-                        extension "htm"
-                        classifier "documentation"
-                        builtBy customFileTask
-                    }
-                    artifact customJar {
-                        classifier null
-                        extension "war"
-                    }
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module("group", "projectText", "1.0")
-        module.assertPublished()
-        module.parsedPom.packaging == "war"
-        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.war", "projectText-1.0-documentation.htm", "projectText-1.0-output.txt")
-
-        and:
-        resolveArtifacts(module, [classifier: 'documentation', type: 'htm'], [classifier: 'output', type: 'txt']) == ["projectText-1.0-documentation.htm", "projectText-1.0-output.txt", "projectText-1.0.war"]
-    }
-
-    def "can attach custom file artifacts with map notation"() {
-        given:
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    artifact source: "customFile.txt", classifier: "output"
-                    artifact source: customFileTask.outputFile, extension: "htm", classifier: "documentation", builtBy: customFileTask
-                    artifact source: customJar, extension: "war", classifier: null
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module("group", "projectText", "1.0")
-        module.assertPublished()
-        module.parsedPom.packaging == "war"
-        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.war", "projectText-1.0-documentation.htm", "projectText-1.0-output.txt")
-
-        and:
-        resolveArtifacts(module, [classifier: 'documentation', type: 'htm'], [classifier: 'output', type: 'txt']) == ["projectText-1.0-documentation.htm", "projectText-1.0-output.txt", "projectText-1.0.war"]
-    }
-
-    def "can configure custom artifacts post creation"() {
-        given:
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    artifact "customFile.txt"
-                    artifact customFileTask.outputFile
-                    artifact customJar
-                }
-            }
-""", """
-            publishing.publications.mavenCustom.artifacts.each {
-                if (it.extension == "html") {
-                    it.classifier = "docs"
-                    it.builtBy customFileTask
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module("group", "projectText", "1.0")
-        module.assertPublished()
-        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-docs.html", "projectText-1.0-customjar.jar")
-    }
-
-    def "can attach artifact with no extension"() {
-        given:
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    from components.java
-                    artifact source: "customFile.txt", extension: "", classifier: "classified"
-                }
-            }
-""")
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module("group", "projectText", "1.0")
-        module.assertPublished()
-        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.jar", "projectText-1.0-classified")
-
-        // TODO:DAZ Find a way to resolve Maven artifact with no extension
-//        and:
-//        resolveArtifact(module, '', 'classified') == ["projectText-1.0-classifier"]
-    }
-
-    def "reports failure publishing when validation fails"() {
-        given:
-        file("a-directory.dir").createDir()
-
-        createBuildScripts("""
-            publications {
-                mavenCustom(MavenPublication) {
-                    artifact "a-directory.dir"
-                }
-            }
-""")
-        when:
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':publishMavenCustomPublicationToMavenRepository'.")
-        failure.assertHasCause("Failed to publish publication 'mavenCustom' to repository 'maven'")
-        failure.assertHasCause("Invalid publication 'mavenCustom': artifact file is a directory")
-    }
-
-    private createBuildScripts(def publications, def append = "") {
-        settingsFile << "rootProject.name = 'projectText'"
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'maven-publish'
-
-            group = 'group'
-            version = '1.0'
-
-            file("customFile.txt") << 'some content'
-
-            task customFileTask {
-                ext.outputFile = file('customFile-1.0-docs.html')
-                doLast {
-                    outputFile << '<html/>'
-                }
-            }
-
-            task customJar(type: Jar) {
-                from file("customFile.txt")
-                classifier "customjar"
-            }
-
-            publishing {
-                repositories {
-                    maven { url "${mavenRepo.uri}" }
-                }
-                $publications
-            }
-
-            $append
-        """
-    }
-}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomizationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomizationIntegTest.groovy
new file mode 100644
index 0000000..9bad371
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomizationIntegTest.groovy
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven
+
+class MavenPublishArtifactCustomizationIntegTest extends AbstractMavenPublishIntegTest {
+
+    def "can attach custom artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact "customFile.txt"
+                    artifact customJar
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "txt"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-customjar.jar")
+
+        and:
+        resolveArtifacts(module, [classifier: 'customjar']) == ["projectText-1.0-customjar.jar", "projectText-1.0.txt"]
+    }
+
+    public void "can configure artifacts added from component"() {
+        given:
+        createBuildScripts("""
+            publications {
+                maven(MavenPublication) {
+                    from components.java
+                }
+            }
+            publications.maven.artifacts.each {
+                if (it.extension == 'jar') {
+                    it.classifier = 'classified'
+                }
+            }
+""")
+
+        when:
+        run "publish"
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.assertArtifactsPublished("projectText-1.0-classified.jar", "projectText-1.0.pom")
+
+        and:
+        resolveArtifact(module, 'jar', 'classified') == ["projectText-1.0-classified.jar"]
+    }
+
+    def "can set custom artifacts to override component artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    from components.java
+                    artifacts = ["customFile.txt", customJar]
+                }
+            }
+
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "txt"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-customjar.jar")
+
+        and:
+        resolveArtifacts(module, [classifier: 'customjar']) == ["projectText-1.0-customjar.jar", "projectText-1.0.txt"]
+    }
+
+    def "can configure custom artifacts when creating"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact("customFile.txt") {
+                        classifier "output"
+                    }
+                    artifact(customFileTask.outputFile) {
+                        extension "htm"
+                        classifier "documentation"
+                        builtBy customFileTask
+                    }
+                    artifact customJar {
+                        classifier null
+                        extension "war"
+                    }
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "war"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.war", "projectText-1.0-documentation.htm", "projectText-1.0-output.txt")
+
+        and:
+        resolveArtifacts(module, [classifier: 'documentation', type: 'htm'], [classifier: 'output', type: 'txt']) == ["projectText-1.0-documentation.htm", "projectText-1.0-output.txt", "projectText-1.0.war"]
+    }
+
+    def "can attach custom file artifacts with map notation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact source: "customFile.txt", classifier: "output"
+                    artifact source: customFileTask.outputFile, extension: "htm", classifier: "documentation", builtBy: customFileTask
+                    artifact source: customJar, extension: "war", classifier: null
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "war"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.war", "projectText-1.0-documentation.htm", "projectText-1.0-output.txt")
+
+        and:
+        resolveArtifacts(module, [classifier: 'documentation', type: 'htm'], [classifier: 'output', type: 'txt']) == ["projectText-1.0-documentation.htm", "projectText-1.0-output.txt", "projectText-1.0.war"]
+    }
+
+    def "can configure custom artifacts post creation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact "customFile.txt"
+                    artifact customFileTask.outputFile
+                    artifact customJar
+                }
+            }
+""", """
+            publishing.publications.mavenCustom.artifacts.each {
+                if (it.extension == "html") {
+                    it.classifier = "docs"
+                    it.builtBy customFileTask
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-docs.html", "projectText-1.0-customjar.jar")
+    }
+
+    def "can attach artifact with no extension"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    from components.java
+                    artifact source: "customFile.txt", extension: "", classifier: "classified"
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.jar", "projectText-1.0-classified")
+
+        // TODO:DAZ Find a way to resolve Maven artifact with no extension
+//        and:
+//        resolveArtifact(module, '', 'classified') == ["projectText-1.0-classifier"]
+    }
+
+    def "reports failure publishing when validation fails"() {
+        given:
+        file("a-directory.dir").createDir()
+
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact "a-directory.dir"
+                }
+            }
+""")
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishMavenCustomPublicationToMavenRepository'.")
+        failure.assertHasCause("Failed to publish publication 'mavenCustom' to repository 'maven'")
+        failure.assertHasCause("Invalid publication 'mavenCustom': artifact file is a directory")
+    }
+
+    private createBuildScripts(def publications, def append = "") {
+        settingsFile << "rootProject.name = 'projectText'"
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'maven-publish'
+
+            group = 'group'
+            version = '1.0'
+
+            file("customFile.txt") << 'some content'
+
+            task customFileTask {
+                ext.outputFile = file('customFile-1.0-docs.html')
+                doLast {
+                    outputFile << '<html/>'
+                }
+            }
+
+            task customJar(type: Jar) {
+                from file("customFile.txt")
+                classifier "customjar"
+            }
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                $publications
+            }
+
+            $append
+        """
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy
index 2c12e43..85a36b9 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.publish.maven
 
 import org.gradle.test.fixtures.maven.M2Installation
-import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenLocalRepository
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Ignore
@@ -27,11 +27,12 @@ import spock.lang.Ignore
 class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
     @Rule SetSystemProperties sysProp = new SetSystemProperties()
 
-    MavenFileRepository m2Repo
+    MavenLocalRepository localM2Repo
+    private M2Installation m2Installation
 
     def "setup"() {
-        def m2Installation = new M2Installation(testDirectory)
-        m2Repo = m2Installation.mavenRepo()
+        m2Installation = new M2Installation(testDirectory)
+        localM2Repo = m2Installation.mavenRepo()
         executer.beforeExecute m2Installation
     }
 
@@ -80,17 +81,14 @@ class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
 
         then:
         def module = mavenRepo.module('org.gradle.test', 'empty-project', '1.0')
-        module.assertPublished()
+        module.assertPublishedAsPomModule()
         module.parsedPom.scopes.isEmpty()
-
-        and:
-        resolveArtifacts(module) == []
     }
 
     def "can publish simple jar"() {
         given:
         def repoModule = mavenRepo.module('group', 'root', '1.0')
-        def localModule = m2Repo.module('group', 'root', '1.0')
+        def localModule = localM2Repo.module('group', 'root', '1.0')
 
         and:
         settingsFile << "rootProject.name = 'root'"
@@ -138,6 +136,37 @@ class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
         resolveArtifacts(repoModule) == ['root-1.0.jar']
     }
 
+    def "can publish to custom maven local repo defined in settings.xml"() {
+        given:
+        def customLocalRepo = new MavenLocalRepository(file("custom-maven-local"))
+        m2Installation.generateUserSettingsFile(customLocalRepo)
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publishToMavenLocal'
+
+        then:
+        !localM2Repo.module("group", "root", "1.0").artifactFile(type: "pom").exists()
+        customLocalRepo.module("group", "root", "1.0").assertPublishedAsJavaModule()
+    }
+
     def "can publish a snapshot version"() {
         settingsFile << 'rootProject.name = "snapshotPublish"'
         buildFile << """
@@ -196,11 +225,11 @@ class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
         fails 'publish'
 
         then:
-        failure.assertHasDescription("A problem occurred configuring the 'publishing' extension")
+        failure.assertHasDescription("A problem occurred configuring root project 'bad-project'.")
         failure.assertHasCause("Maven publication 'maven' cannot include multiple components")
     }
 
-    @Ignore("Not yet implemented - currently the second publication will overwrite") // TODO:DAZ fix in validation story
+    @Ignore("Not yet implemented - currently the second publication will overwrite")
     def "cannot publish multiple maven publications with the same identity"() {
         given:
         settingsFile << "rootProject.name = 'bad-project'"
@@ -229,7 +258,7 @@ class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
         fails 'publish'
 
         then:
-        failure.assertHasDescription("A problem occurred evaluating root project 'bad-project'")
+        failure.assertHasDescription("A problem occurred configuring root project 'bad-project'.")
         failure.assertHasCause("Publication with name 'mavenJava' already exists")
     }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCoordinatesIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCoordinatesIntegTest.groovy
new file mode 100644
index 0000000..9ae28e6
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCoordinatesIntegTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven
+
+import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.test.fixtures.maven.MavenLocalRepository
+
+class MavenPublishCoordinatesIntegTest extends AbstractMavenPublishIntegTest {
+    MavenLocalRepository m2Repo
+
+    def "setup"() {
+        def m2Installation = new M2Installation(testDirectory)
+        m2Repo = m2Installation.mavenRepo()
+        executer.beforeExecute m2Installation
+    }
+
+    def "can publish single jar with specified coordinates"() {
+        given:
+        def repoModule = mavenRepo.module('org.custom', 'custom', '2.2')
+        def localModule = m2Repo.module('org.custom', 'custom', '2.2')
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                        groupId 'org.custom'
+                        artifactId 'custom'
+                        version '2.2'
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publishToMavenLocal'
+
+        then: "jar is published to maven local repository"
+        repoModule.assertNotPublished()
+        localModule.assertPublishedAsJavaModule()
+
+        when:
+        succeeds 'publish'
+
+        then: "jar is published to defined maven repository"
+        file('build/libs/root-1.0.jar').assertExists()
+
+        and:
+        repoModule.assertPublishedAsJavaModule()
+
+        and:
+        resolveArtifacts(repoModule) == ['custom-2.2.jar']
+    }
+
+    def "can produce multiple separate publications for single project"() {
+        given:
+        def module = mavenRepo.module('org.custom', 'custom', '2.2')
+        def apiModule = mavenRepo.module('org.custom', 'custom-api', '2')
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            task apiJar(type: Jar) {
+                from sourceSets.main.output
+                baseName "root-api"
+                exclude "**/impl/**"
+            }
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    impl(MavenPublication) {
+                        groupId "org.custom"
+                        artifactId "custom"
+                        version "2.2"
+                        from components.java
+                    }
+                    api(MavenPublication) {
+                        groupId "org.custom"
+                        artifactId "custom-api"
+                        version "2"
+                        artifact(apiJar)
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        file('build/libs').assertHasDescendants("root-1.0.jar", "root-api-1.0.jar")
+
+        and:
+        module.assertPublishedAsJavaModule()
+        module.moduleDir.file('custom-2.2.jar').assertIsCopyOf(file('build/libs/root-1.0.jar'))
+
+        and:
+        apiModule.assertPublishedAsJavaModule()
+        apiModule.moduleDir.file('custom-api-2.jar').assertIsCopyOf(file('build/libs/root-api-1.0.jar'))
+
+        and:
+        resolveArtifacts(module) == ['custom-2.2.jar']
+        resolveArtifacts(apiModule) == ['custom-api-2.jar']
+    }
+
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
index cafeb28..6c3cc88 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
@@ -29,9 +29,9 @@ class MavenPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpe
         projectPublishedUsingMavenPublishPlugin('java')
 
         expect:
-        consumePublicationWithPreviousVersion('')
+        consumePublicationWithPreviousVersion()
 
-        file('build/resolved').assertHasDescendants('published-1.9.jar', 'commons-collections-3.0.jar')
+        file('build/resolved').assertHasDescendants('published-1.9.jar', 'test-project-1.2.jar')
     }
 
     def "maven war publication generated by maven-publish plugin can be consumed by previous versions of Gradle"() {
@@ -39,12 +39,14 @@ class MavenPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpe
         projectPublishedUsingMavenPublishPlugin('web')
 
         expect:
-        consumePublicationWithPreviousVersion('@war')
+        consumePublicationWithPreviousVersion()
 
         file('build/resolved').assertHasDescendants('published-1.9.war')
     }
 
     def projectPublishedUsingMavenPublishPlugin(def componentToPublish) {
+        repo.module("org.gradle", "test-project", "1.2").publish()
+
         settingsFile.text = "rootProject.name = 'published'"
 
         buildFile.text = """
@@ -55,10 +57,10 @@ group = 'org.gradle.crossversion'
 version = '1.9'
 
 repositories {
-    mavenCentral()
+    maven { url "${repo.uri}" }
 }
 dependencies {
-    compile "commons-collections:commons-collections:3.0"
+    compile "org.gradle:test-project:1.2"
 }
 publishing {
     repositories {
@@ -66,7 +68,7 @@ publishing {
     }
     publications {
         maven(MavenPublication) {
-            from components['${componentToPublish}']
+            from components.${componentToPublish}
         }
     }
 }
@@ -75,7 +77,7 @@ publishing {
         version current withTasks 'publish' run()
     }
 
-    def consumePublicationWithPreviousVersion(def artifact) {
+    def consumePublicationWithPreviousVersion() {
         settingsFile.text = "rootProject.name = 'consumer'"
 
         buildFile.text = """
@@ -83,11 +85,10 @@ configurations {
     lib
 }
 repositories {
-    mavenCentral()
     mavenRepo(urls: ['${repo.uri}'])
 }
 dependencies {
-    lib 'org.gradle.crossversion:published:1.9$artifact'
+    lib 'org.gradle.crossversion:published:1.9'
 }
 task retrieve(type: Sync) {
     into 'build/resolved'
@@ -95,6 +96,6 @@ task retrieve(type: Sync) {
 }
 """
 
-        version previous withDeprecationChecksDisabled() withTasks 'retrieve' run()
+        version previous requireOwnGradleUserHomeDir() withDeprecationChecksDisabled() withTasks 'retrieve' run()
     }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy
index 808a064..4fcca2f 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy
@@ -15,7 +15,7 @@
  */
 
 package org.gradle.api.publish.maven
-import org.gradle.test.fixtures.publish.Identifier
+import org.gradle.test.fixtures.encoding.Identifier
 import spock.lang.Unroll
 
 class MavenPublishIdentifierValidationIntegTest extends AbstractMavenPublishIntegTest {
@@ -142,7 +142,7 @@ class MavenPublishIdentifierValidationIntegTest extends AbstractMavenPublishInte
         fails 'publish'
 
         then:
-        failure.assertHasDescription "Execution failed for task ':publishMavenPublicationToMavenRepository'"
+        failure.assertHasDescription "Execution failed for task ':publishMavenPublicationToMavenRepository'."
         failure.assertHasCause "Failed to publish publication 'maven' to repository 'maven'"
         failure.assertHasCause "Invalid publication 'maven': groupId cannot be empty"
     }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy
index 192f69f..2c25eae 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy
@@ -108,4 +108,55 @@ publishing {
         succeeds "publish"
     }
 
+   @Issue("GRADLE-2837")
+   def "project is properly configured when it is the target of a project dependency"() {
+       given:
+       mavenRepo.module("org.gradle", "dep", "1.1").publish()
+
+       and:
+       settingsFile << "include ':main', ':util'"
+
+       buildFile << """
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'maven-publish'
+    group = 'my.org'
+    version = '1.0'
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+    publishing {
+        repositories {
+            maven { url "${mavenRepo.uri}" }
+        }
+        publications {
+            mavenJava(MavenPublication) {
+                from components.java
+            }
+        }
+    }
+}
+"""
+       file("main", "build.gradle") << """
+    dependencies {
+        compile project(':util')
+    }
+"""
+
+       file("util", "build.gradle") << """
+    dependencies {
+        compile 'org.gradle:dep:1.1'
+    }
+"""
+
+        when:
+        succeeds "publish"
+
+        then:
+        def mainPom = mavenRepo.module('my.org', 'main', '1.0').parsedPom
+        mainPom.scopes.runtime.expectDependency('my.org:util:1.0')
+
+        def utilPom = mavenRepo.module('my.org', 'util', '1.0').parsedPom
+        utilPom.scopes.runtime.expectDependency('org.gradle:dep:1.1')
+    }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
index 498b7a9..14ea632 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
@@ -70,38 +70,10 @@ class MavenPublishJavaIntegTest extends AbstractMavenPublishIntegTest {
         mavenModule.assertArtifactsPublished("publishTest-1.9.jar", "publishTest-1.9.pom", "publishTest-1.9-source.jar")
 
         and:
+        resolveArtifacts(mavenModule) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9.jar"]
         resolveArtifacts(mavenModule, [classifier: 'source']) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9-source.jar", "publishTest-1.9.jar"]
     }
 
-    public void "can configure artifacts added from component"() {
-        given:
-        createBuildScripts("""
-            publishing {
-                publications {
-                    maven(MavenPublication) {
-                        from components.java
-                    }
-                }
-                publications.maven.artifacts.each {
-                    if (it.extension == 'jar') {
-                        it.classifier = 'classified'
-                    }
-                }
-            }
-""")
-
-        when:
-        run "publish"
-
-        then:
-        def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
-        mavenModule.assertPublished()
-        mavenModule.assertArtifactsPublished("publishTest-1.9-classified.jar", "publishTest-1.9.pom")
-
-        and:
-        resolveArtifact(mavenModule, 'jar', 'classified') == ["publishTest-1.9-classified.jar"]
-    }
-
     def createBuildScripts(def append) {
         settingsFile << "rootProject.name = 'publishTest' "
 
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
index 1488ff4..10cfcc9 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
@@ -16,8 +16,6 @@
 
 package org.gradle.api.publish.maven
 
-import spock.lang.Ignore
-
 class MavenPublishMultiProjectIntegTest extends AbstractMavenPublishIntegTest {
     def project1 = mavenRepo.module("org.gradle.test", "project1", "1.0")
     def project2 = mavenRepo.module("org.gradle.test", "project2", "2.0")
@@ -33,6 +31,62 @@ class MavenPublishMultiProjectIntegTest extends AbstractMavenPublishIntegTest {
         projectsCorrectlyPublished()
     }
 
+    def "project dependencies reference publication identity of dependent project"() {
+        def project3 = mavenRepo.module("changed.group", "changed-artifact-id", "changed")
+
+        createBuildScripts("""
+project(":project3") {
+    publishing {
+        publications.maven {
+            groupId "changed.group"
+            artifactId "changed-artifact-id"
+            version "changed"
+        }
+    }
+}
+""")
+
+        when:
+        run "publish"
+
+        then:
+        project1.assertPublishedAsJavaModule()
+        project1.parsedPom.scopes.runtime.assertDependsOn("changed.group:changed-artifact-id:changed", "org.gradle.test:project2:2.0")
+
+        project2.assertPublishedAsJavaModule()
+        project2.parsedPom.scopes.runtime.assertDependsOn("changed.group:changed-artifact-id:changed")
+
+        project3.assertPublishedAsJavaModule()
+        project3.parsedPom.scopes.runtime == null
+
+        and:
+        resolveArtifacts(project1) == ['changed-artifact-id-changed.jar', 'project1-1.0.jar', 'project2-2.0.jar']
+    }
+
+    def "reports failure when project dependency references a project with multiple publications"() {
+        createBuildScripts("""
+project(":project3") {
+    publishing {
+        publications {
+            extraMaven(MavenPublication) {
+                from components.java
+                groupId "extra.group"
+                artifactId "extra-artifact"
+                version "extra"
+            }
+        }
+    }
+}
+""")
+
+        when:
+        fails "publish"
+
+        then:
+        failure.assertHasDescription "A problem occurred configuring project ':project1'."
+        failure.assertHasCause "Publishing is not yet able to resolve a dependency on a project with multiple different publications."
+    }
+
     def "maven-publish plugin does not take archivesBaseName into account when publishing"() {
         createBuildScripts("""
 project(":project2") {
@@ -132,36 +186,6 @@ project(":project2") {
         project1.parsedPom.scopes.runtime.assertDependsOn("org.gradle.test:project2:2.0")
     }
 
-    @Ignore("This does not work: fix this as part of making the project coordinates customisable via DSL (using new mechanism to set artifactId")
-    def "project dependency correctly reflected in POM if dependency publication pom is changed"() {
-        createBuildScripts("""
-project(":project1") {
-    dependencies {
-        compile project(":project2")
-    }
-}
-
-project(":project2") {
-    publishing {
-        publications {
-            maven {
-                pom.withXml {
-                    asNode().artifactId[0].value = "changed"
-                }
-            }
-        }
-    }
-}
-        """)
-
-        when:
-        run ":project1:publish"
-
-        then:
-        def pom = project1.parsedPom
-        pom.scopes.runtime.assertDependsOn("org.gradle.test:changed:1.9")
-    }
-
     private void createBuildScripts(String append = "") {
         settingsFile << """
 include "project1", "project2", "project3"
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
deleted file mode 100644
index a167328..0000000
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
+++ /dev/null
@@ -1,174 +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.publish.maven
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-
-/**
- * Tests maven POM customisation
- */
-class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
-    @Rule SetSystemProperties sysProp = new SetSystemProperties()
-
-    def "can customise pom xml"() {
-        given:
-        settingsFile << "rootProject.name = 'customisePom'"
-        buildFile << """
-            apply plugin: 'maven-publish'
-
-            group = 'org.gradle.test'
-            version = '1.0'
-
-            publishing {
-                repositories {
-                    maven { url "${mavenRepo.uri}" }
-                }
-                publications {
-                    mavenCustom(MavenPublication) {
-                        pom.withXml {
-                            asNode().appendNode('description', "custom-description")
-
-                            def dependency = asNode().appendNode('dependencies').appendNode('dependency')
-                            dependency.appendNode('groupId', 'junit')
-                            dependency.appendNode('artifactId', 'junit')
-                            dependency.appendNode('version', '4.11')
-                            dependency.appendNode('scope', 'runtime')
-                        }
-                    }
-                }
-            }
-        """
-        when:
-        succeeds 'publish'
-
-        then:
-        def module = mavenRepo.module('org.gradle.test', 'customisePom', '1.0')
-        module.assertPublished()
-        module.parsedPom.description == 'custom-description'
-        module.parsedPom.scopes.runtime.assertDependsOn("junit:junit:4.11")
-    }
-
-    def "can generate pom file without publishing"() {
-        given:
-        settingsFile << "rootProject.name = 'generatePom'"
-        buildFile << """
-            apply plugin: 'maven-publish'
-
-            group = 'org.gradle.test'
-            version = '1.0'
-
-            publishing {
-                repositories {
-                    maven { url "${mavenRepo.uri}" }
-                }
-                publications {
-                    emptyMaven(MavenPublication) {
-                        pom.withXml {
-                            asNode().appendNode('description', "Test for pom generation")
-                        }
-                    }
-                }
-                generatePomFileForEmptyMavenPublication {
-                    destination = 'build/generated-pom.xml'
-                }
-            }
-        """
-
-        when:
-        run "generatePomFileForEmptyMavenPublication"
-
-        then:
-        def mavenModule = mavenRepo.module("org.gradle.test", "generatePom", "1.0")
-        mavenModule.assertNotPublished()
-
-        and:
-        file('build/generated-pom.xml').assertIsFile()
-        def pom = new org.gradle.test.fixtures.maven.MavenPom(file('build/generated-pom.xml'))
-        pom.groupId == "org.gradle.test"
-        pom.artifactId == "generatePom"
-        pom.version == "1.0"
-        pom.description == "Test for pom generation"
-    }
-
-    def "has reasonable error message when withXml fails"() {
-        given:
-        settingsFile << "rootProject.name = 'root'"
-        buildFile << """
-            apply plugin: 'maven-publish'
-            apply plugin: 'java'
-
-            group = 'group'
-            version = '1.0'
-
-            publishing {
-                repositories {
-                    maven { url "${mavenRepo.uri}" }
-                }
-                publications {
-                    maven(MavenPublication) {
-                        pom.withXml {
-                            asNode().foo = 'this is not a real element'
-                        }
-                    }
-                }
-            }
-        """
-        when:
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':generatePomFileForMavenPublication'")
-        failure.assertHasCause("Could not apply withXml() to generated POM")
-        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
-    }
-
-    def "has reasonable error message when withXml produces invalid POM file"() {
-        given:
-        settingsFile << "rootProject.name = 'root'"
-        buildFile << """
-            apply plugin: 'maven-publish'
-            apply plugin: 'java'
-
-            group = 'group'
-            version = '1.0'
-
-            publishing {
-                repositories {
-                    maven { url "${mavenRepo.uri}" }
-                }
-                publications {
-                    maven(MavenPublication) {
-                        pom.withXml {
-                            asNode().appendNode('invalid-node', "This is not a valid node for a Maven POM")
-                        }
-                    }
-                }
-            }
-        """
-        when:
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToMavenRepository'")
-        failure.assertHasCause("Failed to publish publication 'maven' to repository 'maven'")
-        failure.assertHasCause("Invalid publication 'maven': POM file is invalid. Check any modifications you have made to the POM file.")
-    }
-}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomizationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomizationIntegTest.groovy
new file mode 100644
index 0000000..52ad0ca
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomizationIntegTest.groovy
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+
+/**
+ * Tests maven POM customization
+ */
+class MavenPublishPomCustomizationIntegTest extends AbstractIntegrationSpec {
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+
+    def "can customize pom xml"() {
+        given:
+        settingsFile << "rootProject.name = 'customizePom'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    mavenCustom(MavenPublication) {
+                        pom.packaging "custom-packaging"
+                        pom.withXml {
+                            asNode().appendNode('description', "custom-description")
+
+                            def dependency = asNode().appendNode('dependencies').appendNode('dependency')
+                            dependency.appendNode('groupId', 'junit')
+                            dependency.appendNode('artifactId', 'junit')
+                            dependency.appendNode('version', '4.11')
+                            dependency.appendNode('scope', 'runtime')
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module('org.gradle.test', 'customizePom', '1.0')
+        module.assertPublished()
+        module.parsedPom.description == 'custom-description'
+        module.parsedPom.packaging == 'custom-packaging'
+        module.parsedPom.scopes.runtime.assertDependsOn("junit:junit:4.11")
+    }
+
+    def "can generate pom file without publishing"() {
+        given:
+        settingsFile << "rootProject.name = 'generatePom'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    emptyMaven(MavenPublication) {
+                        pom.withXml {
+                            asNode().appendNode('description', "Test for pom generation")
+                        }
+                    }
+                }
+
+            }
+
+            model {
+                tasks.generatePomFileForEmptyMavenPublication {
+                    destination = 'build/generated-pom.xml'
+                }
+            }
+        """
+
+        when:
+        run "generatePomFileForEmptyMavenPublication"
+
+        then:
+        def mavenModule = mavenRepo.module("org.gradle.test", "generatePom", "1.0")
+        mavenModule.assertNotPublished()
+
+        and:
+        file('build/generated-pom.xml').assertIsFile()
+        def pom = new org.gradle.test.fixtures.maven.MavenPom(file('build/generated-pom.xml'))
+        pom.groupId == "org.gradle.test"
+        pom.artifactId == "generatePom"
+        pom.version == "1.0"
+        pom.description == "Test for pom generation"
+    }
+
+    def "has reasonable error message when withXml fails"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        pom.withXml {
+                            asNode().foo = 'this is not a real element'
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':generatePomFileForMavenPublication'.")
+        failure.assertHasFileName("Build file '$buildFile'")
+        failure.assertHasLineNumber(15)
+        failure.assertHasCause("Could not apply withXml() to generated POM")
+        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
+    }
+
+    def "has reasonable error message when withXml produces invalid POM file"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        pom.withXml {
+                            asNode().appendNode('invalid-node', "This is not a valid node for a Maven POM")
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToMavenRepository'.")
+        failure.assertHasCause("Failed to publish publication 'maven' to repository 'maven'")
+        failure.assertHasCause("Invalid publication 'maven': POM file is invalid. Check any modifications you have made to the POM file.")
+    }
+
+    def "has reasonable error message when withXML modifies publication coordinates"() {
+        when:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        groupId "group"
+                        artifactId "artifact"
+                        version "1.0"
+
+                        pom.withXml {
+                            asNode().version[0].value = "2.0"
+                        }
+                    }
+                }
+            }
+        """
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToMavenRepository'.")
+        failure.assertHasCause("Failed to publish publication 'maven' to repository 'maven'")
+        failure.assertHasCause("Invalid publication 'maven': supplied version does not match POM file (cannot edit version directly in the POM file).")
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
index cc8ec70..21ca57e 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
@@ -16,15 +16,19 @@
 
 
 package org.gradle.api.publish.maven
+
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.test.fixtures.maven.MavenFileModule
+import org.gradle.util.TextUtil
 import org.junit.Rule
 
 public class SamplesMavenPublishIntegrationTest extends AbstractIntegrationSpec {
     @Rule public final Sample quickstart = new Sample(temporaryFolder, "maven-publish/quickstart")
     @Rule public final Sample javaProject = new Sample(temporaryFolder, "maven-publish/javaProject")
     @Rule public final Sample pomCustomization = new Sample(temporaryFolder, "maven-publish/pomCustomization")
+    @Rule public final Sample multiPublish = new Sample(temporaryFolder, "maven-publish/multiple-publications")
 
     def quickstartPublish() {
         given:
@@ -95,6 +99,42 @@ public class SamplesMavenPublishIntegrationTest extends AbstractIntegrationSpec
 
         then:
         module.assertPublishedAsPomModule()
-        module.parsedPom.description == "A demonstration of maven pom customisation"
+        module.parsedPom.description == "A demonstration of maven POM customization"
+    }
+
+    def multiplePublications() {
+        given:
+        sample multiPublish
+
+        and:
+        def fileRepo = maven(multiPublish.dir.file("build/repo"))
+        def project1sample = fileRepo.module("org.gradle.sample", "project1-sample", "1.1")
+        def project2api = fileRepo.module("org.gradle.sample", "project2-api", "2")
+        def project2impl = fileRepo.module("org.gradle.sample.impl", "project2-impl", "2.3")
+
+        when:
+        succeeds "publish"
+
+        then:
+        project1sample.assertPublishedAsJavaModule()
+        verifyPomFile(project1sample, "output/project1.pom.xml")
+
+        and:
+        project2api.assertPublishedAsJavaModule()
+        verifyPomFile(project2api, "output/project2-api.pom.xml")
+
+        and:
+        project2impl.assertPublishedAsJavaModule()
+        verifyPomFile(project2impl, "output/project2-impl.pom.xml")
+    }
+
+    private void verifyPomFile(MavenFileModule module, String outputFileName) {
+        def actualIvyXmlText = module.pomFile.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
+        assert actualIvyXmlText == getExpectedIvyOutput(multiPublish.dir.file(outputFileName))
+    }
+
+    String getExpectedIvyOutput(File outputFile) {
+        assert outputFile.file
+        outputFile.readLines()[1..-1].join(TextUtil.getPlatformLineSeparator()).trim()
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
new file mode 100644
index 0000000..dbb5de0
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.maven.MavenLocalRepository
+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()
+
+    def "can publish a project with dependency in mapped and unmapped configuration"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = '1.0'
+repositories { mavenCentral() }
+configurations { custom }
+dependencies {
+    custom 'commons-collections:commons-collections:3.2'
+    runtime 'commons-collections:commons-collections:3.2'
+}
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "${mavenRepo.uri}")
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        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'"
+        buildFile << """
+apply plugin: 'base'
+apply plugin: 'maven'
+
+group = 'group'
+version = 1.0
+
+task sourceJar(type: Jar) {
+    classifier = 'source'
+}
+artifacts {
+    archives sourceJar
+}
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "${mavenRepo.uri}")
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def module = mavenRepo.module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0-source.jar')
+    }
+
+    def "can publish a project with metadata artifacts"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = 1.0
+
+task signature {
+    ext.destFile = file("\$buildDir/signature.sig")
+    doLast {
+        destFile.text = 'signature'
+    }
+}
+
+import org.gradle.api.artifacts.maven.MavenDeployment
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+
+artifacts {
+    archives new DefaultPublishArtifact(jar.baseName, "jar.sig", "jar.sig", null, new Date(), signature.destFile, signature)
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("${mavenRepo.uri}"))
+            beforeDeployment { MavenDeployment deployment ->
+                assert deployment.pomArtifact.file.isFile()
+                assert deployment.pomArtifact.name == 'root'
+                assert deployment.mainArtifact.file == jar.archivePath
+                assert deployment.mainArtifact.name == 'root'
+                assert deployment.artifacts.size() == 3
+                assert deployment.artifacts.contains(deployment.pomArtifact)
+                assert deployment.artifacts.contains(deployment.mainArtifact)
+
+                def pomSignature = file("\${buildDir}/pom.sig")
+                pomSignature.text = 'signature'
+                deployment.addArtifact new DefaultPublishArtifact(deployment.pomArtifact.name, "pom.sig", "pom.sig", null, new Date(), pomSignature)
+            }
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        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')
+    }
+
+    def "can publish a snapshot version"() {
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'org.gradle'
+version = '1.0-SNAPSHOT'
+archivesBaseName = 'test'
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            snapshotRepository(url: "${mavenRepo.uri}")
+        }
+    }
+}
+"""
+
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def module = mavenRepo.module('org.gradle', 'test', '1.0-SNAPSHOT')
+        module.assertArtifactsPublished("test-${module.publishArtifactVersion}.jar", "test-${module.publishArtifactVersion}.pom")
+    }
+
+    def "can publish multiple deployments with attached artifacts"() {
+        given:
+        server.start()
+        settingsFile << "rootProject.name = 'someCoolProject'"
+        buildFile << """
+apply plugin:'java'
+apply plugin:'maven'
+
+version = 1.0
+group =  "org.test"
+
+task sourcesJar(type: Jar) {
+        from sourceSets.main.allSource
+        classifier = 'sources'
+}
+
+task testJar(type: Jar) {
+        baseName = project.name + '-tests'
+}
+
+task testSourcesJar(type: Jar) {
+        baseName = project.name + '-tests'
+        classifier = 'sources'
+}
+
+artifacts { archives sourcesJar, testJar, testSourcesJar }
+
+uploadArchives {
+    repositories{
+        mavenDeployer {
+            repository(url: "http://localhost:${server.port}/repo") {
+               authentication(userName: "testuser", password: "secret")
+            }
+            addFilter('main') {artifact, file ->
+                !artifact.name.endsWith("-tests")
+            }
+            addFilter('tests') {artifact, file ->
+                artifact.name.endsWith("-tests")
+            }
+        }
+    }
+}
+"""
+        when:
+        def module = mavenRepo.module('org.test', 'someCoolProject')
+        def moduleDir = module.moduleDir
+        moduleDir.mkdirs()
+
+        and:
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0.pom")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0.jar")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0-sources.jar")
+        server.expectGetMissing("/repo/org/test/someCoolProject/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject", "maven-metadata.xml")
+
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0.pom")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0.jar")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0-sources.jar")
+        server.expectGetMissing("/repo/org/test/someCoolProject-tests/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests", "maven-metadata.xml")
+
+        then:
+        succeeds 'uploadArchives'
+    }
+
+    def "can publish to an unauthenticated HTTP repository"() {
+        given:
+        server.start()
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'org.test'
+version = '1.0'
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "http://localhost:${server.port}/repo")
+        }
+    }
+}
+"""
+        when:
+        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")
+        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.jar")
+        server.expectGetMissing("/repo/org/test/root/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/root", "maven-metadata.xml")
+
+        then:
+        succeeds 'uploadArchives'
+
+        and:
+        module.assertArtifactsPublished('root-1.0.pom', 'root-1.0.jar', 'maven-metadata.xml')
+    }
+
+    private def expectPublishArtifact(def moduleDir, def path, def name) {
+        server.expectPut("$path/$name", moduleDir.file("$name"))
+        server.expectPut("$path/${name}.md5", moduleDir.file("${name}.md5"))
+        server.expectPut("$path/${name}.sha1", moduleDir.file("${name}.sha1"))
+    }
+
+    def "can publish to custom maven local repo defined in settings.xml"() {
+        given:
+        def m2Installation = new M2Installation(testDirectory)
+        def localM2Repo = m2Installation.mavenRepo()
+        def customLocalRepo = new MavenLocalRepository(file("custom-maven-local"))
+        m2Installation.generateUserSettingsFile(customLocalRepo)
+        executer.beforeExecute(m2Installation)
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+        """
+
+        when:
+        args "-i"
+        succeeds 'install'
+
+        then:
+        !localM2Repo.module("group", "root", "1.0").artifactFile(type: "jar").exists()
+        customLocalRepo.module("group", "root", "1.0").assertPublishedAsJavaModule()
+    }
+
+    @Unroll
+    def "can publish to an authenticated HTTP repository using #authScheme auth"() {
+        given:
+        def username = 'testuser'
+        def password = 'password'
+        server.start()
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().version))
+        def repo = new MavenHttpRepository(server, mavenRepo)
+
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'org.test'
+version = '1.0'
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "${repo.uri}") {
+               authentication(userName: "${username}", password: "${password}")
+            }
+        }
+    }
+}
+"""
+        when:
+        server.authenticationScheme = authScheme
+
+        and:
+        def module = repo.module('org.test', 'root')
+        module.artifact.expectPut(username, password)
+        module.artifact.sha1.expectPut(username, password)
+        module.artifact.md5.expectPut(username, password)
+        module.pom.expectPut(username, password)
+        module.pom.sha1.expectPut(username, password)
+        module.pom.md5.expectPut(username, password)
+        module.rootMetaData.expectGetMissing()
+        module.rootMetaData.expectPut(username, password)
+        module.rootMetaData.sha1.expectPut(username, password)
+        module.rootMetaData.md5.expectPut(username, password)
+
+        then:
+        succeeds 'uploadArchives'
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+        // TODO: Does not work with DIGEST authentication
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy
rename to subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
new file mode 100644
index 0000000..17612a4
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.publish.maven
+
+import groovy.text.SimpleTemplateEngine
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.XMLAssert
+import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Resources
+import org.hamcrest.Matchers
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesMavenPomGenerationIntegrationTest extends AbstractIntegrationTest {
+    private TestFile pomProjectDir
+
+    @Rule public Resources resources = new Resources();
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'maven/pomGeneration')
+
+    @Before
+    void setUp() {
+        pomProjectDir = sample.dir
+    }
+    
+    @Test
+    void "can deploy to local repository"() {
+        def repo = maven(pomProjectDir.file('pomRepo'))
+        def module = repo.module('deployGroup', 'mywar', '1.0MVN')
+
+        executer.inDirectory(pomProjectDir).withTasks('uploadArchives').run()
+
+        compareXmlWithIgnoringOrder(expectedPom('1.0MVN', "deployGroup"), module.pomFile.text)
+        module.moduleDir.file("mywar-1.0MVN.war").assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
+
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+    }
+
+    @Test
+    void "can install to local repository"() {
+        def repo = maven(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def module = repo.module('installGroup', 'mywar', '1.0MVN')
+        module.moduleDir.deleteDir()
+
+        executer.inDirectory(pomProjectDir).withTasks('install').run()
+
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+
+        TestFile installedFile = module.moduleDir.file("mywar-1.0MVN.war")
+        TestFile installedJavadocFile = module.moduleDir.file("mywar-1.0MVN-javadoc.zip")
+        TestFile installedPom = module.moduleDir.file("mywar-1.0MVN.pom")
+
+        installedFile.assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
+        installedJavadocFile.assertIsCopyOf(pomProjectDir.file("target/distributions/mywar-1.0-javadoc.zip"))
+        installedPom.assertIsFile()
+
+        compareXmlWithIgnoringOrder(expectedPom("1.0MVN", "installGroup"), installedPom.text)
+    }
+
+    @Test
+    void writeNewPom() {
+        executer.inDirectory(pomProjectDir).withTasks('writeNewPom').run()
+        compareXmlWithIgnoringOrder(expectedPom(null, null, 'pomGeneration/expectedNewPom.txt'), pomProjectDir.file("target/newpom.xml").text)
+    }
+
+    @Test
+    void writeDeployerPom() {
+        String version = '1.0MVN'
+        String groupId = "deployGroup"
+        executer.inDirectory(pomProjectDir).withTasks('writeDeployerPom').run()
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomProjectDir.file("target/deployerpom.xml").text)
+    }
+    
+    private String expectedPom(String version, String groupId, String path = 'pomGeneration/expectedPom.txt') {
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
+        String text = resources.getResource(path).text
+        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
+    }
+
+    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+        Diff diff = new Diff(expectedXml, actualXml)
+        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
+        XMLAssert.assertXMLEqual(diff, true);
+        Assert.assertThat(actualXml, Matchers.startsWith(String.format('<?xml version="1.0" encoding="UTF-8"?>')))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
new file mode 100755
index 0000000..5259bac
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.publish.maven
+
+import groovy.text.SimpleTemplateEngine
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.XMLAssert
+import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Resources
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesMavenQuickstartIntegrationTest extends AbstractIntegrationTest {
+    @Rule public Resources resources = new Resources();
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'maven/quickstart')
+
+    private TestFile pomProjectDir
+
+    @Before
+    void setUp() {
+        pomProjectDir = sample.dir
+    }
+
+    @Test
+    void "can publish to a local repository"() {
+        executer.inDirectory(pomProjectDir).withTasks('uploadArchives').run()
+
+        def repo = 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)
+        module.moduleDir.file("quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
+    }
+
+    @Test
+    void "can install to local repository"() {
+        def repo = maven(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def module = repo.module('gradle', 'quickstart', '1.0')
+        module.moduleDir.deleteDir()
+
+        executer.inDirectory(pomProjectDir).withTasks('install').run()
+
+        module.moduleDir.file("quickstart-1.0.jar").assertIsFile()
+        module.moduleDir.file("quickstart-1.0.pom").assertIsFile()
+        module.moduleDir.file("quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
+
+        compareXmlWithIgnoringOrder(expectedPom('1.0', 'gradle'), module.pomFile.text)
+    }
+
+    private String expectedPom(String version, String groupId) {
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
+        String text = resources.getResource('pomGeneration/expectedQuickstartPom.txt').text
+        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
+    }
+
+    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+        Diff diff = new Diff(expectedXml, actualXml)
+        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
+        XMLAssert.assertXMLEqual(diff, true);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedNewPom.txt b/subprojects/maven/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedNewPom.txt
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedNewPom.txt
rename to subprojects/maven/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedNewPom.txt
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedPom.txt b/subprojects/maven/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedPom.txt
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedPom.txt
rename to subprojects/maven/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedPom.txt
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedQuickstartPom.txt b/subprojects/maven/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedQuickstartPom.txt
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedQuickstartPom.txt
rename to subprojects/maven/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedQuickstartPom.txt
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
index 566aaf4..300dd0b 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
@@ -20,8 +20,6 @@ import org.gradle.api.artifacts.Configuration;
 /**
  * An immutable mapping to map a dependency configuration to a Maven scope. This class has implemented equality and
  * hashcode based on its values not on object identity.
- *
- * @author Hans Dockter
  * @see org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
  */
 public class Conf2ScopeMapping {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
index 5171b9a..8859781 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
@@ -21,10 +21,8 @@ import java.util.Collection;
 import java.util.Map;
 
 /**
- * Defines a set of rules for how to map the Gradle dependencies to a pom. This mapping is based
+ * Defines a set of rules for how to map the Gradle dependencies to a POM. This mapping is based
  * on the configuration the dependencies belong to.
- *
- * @author Hans Dockter
  */
 public interface Conf2ScopeMappingContainer {
     String PROVIDED = "provided";
@@ -49,8 +47,8 @@ public interface Conf2ScopeMappingContainer {
 
     /**
      * Returns a scope that corresponds to the given configurations. Dependencies of different configurations can
-     * be equal. But only one of those equals dependencies (which might differ in content) can be mapped to a pom
-     * (due to the the nature of a Maven pom).
+     * be equal. But only one of those equals dependencies (which might differ in content) can be mapped to a POM
+     * (due to the the nature of a Maven POM).
      *
      * <p>Which scope is returned depends on the existing mappings. See {@link #addMapping(int, Configuration, String)}. If
      * only one configuration is mapped, this mapping is used to choose the scope. If more than one configuration of a
@@ -81,7 +79,7 @@ public interface Conf2ScopeMappingContainer {
 
     /**
      * Sets, whether unmapped configuration should be skipped or not. If this is set to
-     * false, dependencies belonging to unmapped configurations will be added to the Maven pom with no
+     * false, dependencies belonging to unmapped configurations will be added to the Maven POM with no
      * scope specified. This means they belong to the Maven default scope, which is 'compile'.
      */
     void setSkipUnmappedConfs(boolean skipDependenciesWithUnmappedConfiguration);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
index b04edfe..f027ba8 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
@@ -30,8 +30,6 @@ package org.gradle.api.artifacts.maven;
  *
  * This call set the repository object and also returns an instance of this object. If you use 'snapshotRepository'
  * instead of repository, the snapshot repository is build.
- *
- * @author Hans Dockter
  * @see MavenDeployer
  */
 public interface GroovyMavenDeployer extends MavenDeployer {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
index 0c7a609..f9cdd38 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
@@ -24,15 +24,13 @@ import java.util.Collection;
  * deployment, including snapshot deployment and metadata.xml manipulation.</p>
  *
  * <p>You have to specify at least one repository. Otherwise, if there is only one artifact, usually there is not more
- * to do. If there is more than one artifact you have to decide what to do about this, as a Maven pom can only deal with
+ * to do. If there is more than one artifact you have to decide what to do about this, as a Maven POM can only deal with
  * one artifact. There are two strategies. If you want to deploy only one artifact you have to specify a filter to
  * select this artifact. If you want to deploy more than one artifact, you have to specify filters which select each
- * artifact. Associated with each filter is a separate configurable pom.</p>
+ * artifact. Associated with each filter is a separate configurable POM.</p>
  *
  * <p>You can create an instance of this type via the {@link org.gradle.api.tasks.Upload#getRepositories()}
  * container</p>
- *
- * @author Hans Dockter
  */
 public interface MavenDeployer extends MavenResolver {
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
index 8bddc3c..356dcef 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.PublishArtifact;
 import java.util.Set;
 
 /**
- * Represents the artifacts which will be deployed to a maven repository. You can use this interface to modify the set
+ * Represents the artifacts which will be deployed to a Maven repository. You can use this interface to modify the set
  * of artifacts.
  */
 public interface MavenDeployment {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
index 95e4829..df10201 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
@@ -24,22 +24,20 @@ import java.io.Writer;
 import java.util.List;
 
 /**
- * Is used for generating a Maven pom file and customizing the generation.
- * To learn about the Maven pom see: <a href="http://maven.apache.org/pom.html">http://maven.apache.org/pom.html</a>
- *
- * @author Hans Dockter
+ * Is used for generating a Maven POM file and customizing the generation.
+ * To learn about the Maven POM see: <a href="http://maven.apache.org/pom.html">http://maven.apache.org/pom.html</a>
  */
 public interface MavenPom {
 
     String POM_FILE_ENCODING = "UTF-8";
 
     /**
-     * Returns the scope mappings used for generating this pom.
+     * Returns the scope mappings used for generating this POM.
      */
     Conf2ScopeMappingContainer getScopeMappings();
 
     /**
-     * Provides a builder for the Maven pom for adding or modifying properties of the Maven {@link #getModel()}.
+     * Provides a builder for the Maven POM for adding or modifying properties of the Maven {@link #getModel()}.
      * The syntax is exactly the same as used by polyglot Maven. For example:
      *
      * <pre>
@@ -155,38 +153,38 @@ public interface MavenPom {
     MavenPom setModel(Object model);
 
     /**
-     * Writes the {@link #getEffectivePom()} xml to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions. Closes the supplied
+     * Writes the {@link #getEffectivePom()} XML to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions. Closes the supplied
      * Writer when finished.
      *
-     * @param writer The writer to write the pom xml.
+     * @param writer The writer to write the POM to.
      * @return this
      */
     MavenPom writeTo(Writer writer);
 
     /**
-     * Writes the {@link #getEffectivePom()} xml to a file while applying the {@link #withXml(org.gradle.api.Action)} actions.
+     * Writes the {@link #getEffectivePom()} XML to a file while applying the {@link #withXml(org.gradle.api.Action)} actions.
      * The path is resolved as defined by {@link org.gradle.api.Project#files(Object...)}
      * The file will be encoded as UTF-8.
      *
-     * @param path The path of the file to write the pom xml into.
+     * @param path The path of the file to write the POM into.
      * @return this
      */
     MavenPom writeTo(Object path);
 
     /**
-     * <p>Adds a closure to be called when the pom has been configured. The pom is passed to the closure as a
+     * <p>Adds a closure to be called when the POM has been configured. The POM is passed to the closure as a
      * parameter.</p>
      *
-     * @param closure The closure to execute when the pom has been configured.
+     * @param closure The closure to execute when the POM has been configured.
      * @return this
      */
     MavenPom whenConfigured(Closure closure);
 
     /**
-     * <p>Adds an action to be called when the pom has been configured. The pom is passed to the action as a
+     * <p>Adds an action to be called when the POM has been configured. The POM is passed to the action as a
      * parameter.</p>
      *
-     * @param action The action to execute when the pom has been configured.
+     * @param action The action to execute when the POM has been configured.
      * @return this
      */
     MavenPom whenConfigured(Action<MavenPom> action);
@@ -210,20 +208,20 @@ public interface MavenPom {
     MavenPom withXml(Action<XmlProvider> action);
 
     /**
-     * Returns the configuration container used for mapping configurations to maven scopes.
+     * Returns the configuration container used for mapping configurations to Maven scopes.
      */
     ConfigurationContainer getConfigurations();
 
     /**
-     * Sets the configuration container used for mapping configurations to maven scopes.
+     * Sets the configuration container used for mapping configurations to Maven scopes.
      * @return this
      */
     MavenPom setConfigurations(ConfigurationContainer configurations);
 
     /**
-     * Returns a pom with the generated dependencies and the {@link #whenConfigured(org.gradle.api.Action)} actions applied.
+     * Returns a POM with the generated dependencies and the {@link #whenConfigured(org.gradle.api.Action)} actions applied.
      *
-     * @return the effective pom
+     * @return the effective POM
      */
     MavenPom getEffectivePom();
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
index 0cd890a..2816660 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
@@ -21,12 +21,10 @@ import org.gradle.api.artifacts.repositories.ArtifactRepository;
 
 /**
  * An {@link ArtifactRepository} which can be used to publish artifacts to Maven repositories.
- *
- * @author Hans Dockter
  */
 public interface MavenResolver extends ArtifactRepository, PomFilterContainer {
     /**
-     * Returns a maven settings object. This can be used for example to figure out where the local repository is
+     * Returns a Maven settings object. This can be used for example to figure out where the local repository is
      * located. This property is filled after publishing. Before this property is null.
      */
     Object getSettings();
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
index 263e57e..2051337 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
@@ -20,8 +20,6 @@ import org.gradle.api.publication.maven.internal.PomFilter;
 
 /**
  * Manages a set of {@link MavenPom} instances and their associated {@link PublishFilter} instances.
- *
- * @author Hans Dockter
  */
 public interface PomFilterContainer {
     String DEFAULT_ARTIFACT_POM_NAME = "default";
@@ -38,7 +36,7 @@ public interface PomFilterContainer {
      * If at least one custom filter has been added the default filter is not used any longer.</p>
      * <p>The default for this property is {@link PublishFilter#ALWAYS_ACCEPT}.
      * If there is only one artifact you are fine with this filter. If there is more than one artifact, deployment will lead to
-     * an exception, if you don't specify a filter that selects the artifact to deploy. If you want to deploy more than one artiact you have
+     * an exception, if you don't specify a filter that selects the artifact to deploy. If you want to deploy more than one artifact you have
      * to use the (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)} method.</p>
      *
      * @param defaultFilter
@@ -47,22 +45,22 @@ public interface PomFilterContainer {
     void setFilter(PublishFilter defaultFilter);
 
     /**
-     * Returns the pom property of the custom filter.
-     * The pom property can be used to customize the pom generation. By default the properties of such a pom object are all null.
-     * Null means that Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
-     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use those instead.
+     * Returns the POM property of the custom filter.
+     * The POM property can be used to customize the POM generation. By default the properties of such a POM object are all null.
+     * Null means that Gradle will use default values for generating the Maven POM. Those default values are derived from the deployable artifact
+     * and from the project type (e.g. java, war, ...). If you explicitly set a POM property, Gradle will use those instead.
      *
      * @return The Maven Pom
      */
     MavenPom getPom();
 
     /**
-     * <p>Sets the default pom to be used. This pom is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
-     * If at least one custom filter has been added the default pom is not used any longer.</p>
-     * <p>Usually you don't need to set this property as the default value provides you a pom object you might use for configuration.
-     * By default the properties of such a pom object are all null.
-     * If they are null, Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
-     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use this instead.</p>
+     * <p>Sets the default POM to be used. This POM is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
+     * If at least one custom filter has been added the default POM is not used any longer.</p>
+     * <p>Usually you don't need to set this property as the default value provides you a POM object you might use for configuration.
+     * By default the properties of such a POM object are all null.
+     * If they are null, Gradle will use default values for generating the Maven POM. Those default values are derived from the deployable artifact
+     * and from the project type (e.g. java, war, ...). If you explicitly set a POM property, Gradle will use this instead.</p>
      *
      * @param defaultPom
      */
@@ -70,12 +68,12 @@ public interface PomFilterContainer {
 
     /**
      * If you want to deploy more than one artifact you need to define filters to select each of those artifacts. The method
-     * returns a pom object associated with this filter, that allows you to customize the pom generation for the artifact selected
+     * returns a POM object associated with this filter, that allows you to customize the POM generation for the artifact selected
      * by the filter.
      *
      * @param name The name of the filter
      * @param publishFilter The filter to use
-     * @return The pom associated with the filter
+     * @return The POM associated with the filter
      */
     MavenPom addFilter(String name, PublishFilter publishFilter);
 
@@ -84,7 +82,7 @@ public interface PomFilterContainer {
      *
      * @param name   The name of the filter
      * @param filter The filter
-     * @return The Maven pom associated with the closure
+     * @return The Maven POM associated with the closure
      * @see PublishFilter
      * @see PomFilterContainer#addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)
      */
@@ -107,27 +105,27 @@ public interface PomFilterContainer {
     void filter(Closure filter);
 
     /**
-     * Returns the pom associated with a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
+     * Returns the POM associated with a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
      *
      * @param name The name of the filter.
      */
     MavenPom pom(String name);
 
     /**
-     * Configures a pom by a closure. The closure statements are delegated to the pom object associated with the given name.
+     * Configures a POM by a closure. The closure statements are delegated to the POM object associated with the given name.
      *
      * @param name
      * @param configureClosure
-     * @return The pom object associated with the given name.
+     * @return The POM object associated with the given name.
      * @see PomFilterContainer#pom(String)
      */
     MavenPom pom(String name, Closure configureClosure);
 
     /**
-     * Configures the default pom by a closure. The closure statements are delegated to the default pom.
+     * Configures the default POM by a closure. The closure statements are delegated to the default POM.
      *
      * @param configureClosure
-     * @return The default pom.
+     * @return The default POM.
      * @see PomFilterContainer#getPom()
      */
     MavenPom pom(Closure configureClosure);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
index 2461e95..eb2a03c 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
@@ -22,8 +22,6 @@ import java.io.File;
 
 /**
  * A filter for artifacts to be published.
- *
- * @author Hans Dockter
  */
 public interface PublishFilter {
     PublishFilter ALWAYS_ACCEPT = new PublishFilter() {
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 6fdaee1..eaa9fef 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
@@ -15,15 +15,24 @@
  */
 package org.gradle.api.plugins;
 
+import org.apache.maven.project.MavenProject;
 import org.gradle.api.Action;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectPublication;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -32,6 +41,7 @@ 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.configuration.project.ProjectConfigurationActionContainer;
 import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
@@ -40,8 +50,6 @@ 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>
- *
- * @author Hans Dockter
  */
 public class MavenPlugin implements Plugin<ProjectInternal> {
     public static final int COMPILE_PRIORITY = 300;
@@ -56,14 +64,22 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
 
     private final Factory<LoggingManagerInternal> loggingManagerFactory;
     private final FileResolver fileResolver;
+    private final ProjectPublicationRegistry publicationRegistry;
+    private final ProjectConfigurationActionContainer configurationActionContainer;
+
+    private Project project;
 
     @Inject
-    public MavenPlugin(Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver) {
+    public MavenPlugin(Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver,
+                       ProjectPublicationRegistry publicationRegistry, ProjectConfigurationActionContainer configurationActionContainer) {
         this.loggingManagerFactory = loggingManagerFactory;
         this.fileResolver = fileResolver;
+        this.publicationRegistry = publicationRegistry;
+        this.configurationActionContainer = configurationActionContainer;
     }
 
     public void apply(final ProjectInternal project) {
+        this.project = project;
         project.getPlugins().apply(BasePlugin.class);
 
         DefaultMavenFactory mavenFactory = new DefaultMavenFactory();
@@ -76,14 +92,9 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
                 project.getConfigurations(),
                 pluginConvention.getConf2ScopeMappings());
 
-        project.getTasks().withType(Upload.class, new Action<Upload>() {
-            public void execute(Upload upload) {
-                RepositoryHandler repositories = upload.getRepositories();
-                DefaultRepositoryHandler handler = (DefaultRepositoryHandler) repositories;
-                DefaultMavenRepositoryHandlerConvention repositoryConvention = new DefaultMavenRepositoryHandlerConvention(handler, deployerFactory);
-                new DslObject(repositories).getConvention().getPlugins().put("maven", repositoryConvention);
-            }
-        });
+        configureUploadTasks(deployerFactory);
+        configureUploadArchivesTask();
+
         PluginContainer plugins = project.getPlugins();
         plugins.withType(JavaPlugin.class, new Action<JavaPlugin>() {
             public void execute(JavaPlugin javaPlugin) {
@@ -98,6 +109,38 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
         });
     }
 
+    private void configureUploadTasks(final DefaultDeployerFactory deployerFactory) {
+        project.getTasks().withType(Upload.class, new Action<Upload>() {
+            public void execute(Upload upload) {
+                RepositoryHandler repositories = upload.getRepositories();
+                DefaultRepositoryHandler handler = (DefaultRepositoryHandler) repositories;
+                DefaultMavenRepositoryHandlerConvention repositoryConvention = new DefaultMavenRepositoryHandlerConvention(handler, deployerFactory);
+                new DslObject(repositories).getConvention().getPlugins().put("maven", repositoryConvention);
+            }
+        });
+    }
+
+    private void configureUploadArchivesTask() {
+        configurationActionContainer.add(new Action<Project>() {
+            public void execute(Project project) {
+                Upload uploadArchives = project.getTasks().withType(Upload.class).findByName(BasePlugin.UPLOAD_ARCHIVES_TASK_NAME);
+                if (uploadArchives == null) { return; }
+
+                ConfigurationInternal configuration = (ConfigurationInternal) uploadArchives.getConfiguration();
+                ModuleInternal module = configuration.getModule();
+                for (MavenResolver resolver : uploadArchives.getRepositories().withType(MavenResolver.class)) {
+                    MavenPom pom = resolver.getPom();
+                    ModuleVersionIdentifier publicationId = new DefaultModuleVersionIdentifier(
+                            pom.getGroupId().equals(MavenProject.EMPTY_PROJECT_GROUP_ID) ? module.getGroup() : pom.getGroupId(),
+                            pom.getArtifactId().equals(MavenProject.EMPTY_PROJECT_ARTIFACT_ID) ? module.getName() : pom.getArtifactId(),
+                            pom.getVersion().equals(MavenProject.EMPTY_PROJECT_VERSION) ? module.getVersion() : pom.getVersion()
+                    );
+                    publicationRegistry.registerPublication(project.getPath(), new DefaultProjectPublication(publicationId));
+                }
+            }
+        });
+    }
+
     private MavenPluginConvention addConventionObject(ProjectInternal project, MavenFactory mavenFactory) {
         MavenPluginConvention mavenConvention = new MavenPluginConvention(project, mavenFactory);
         Convention convention = project.getConvention();
@@ -124,11 +167,11 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
     }
 
     private void configureInstall(Project project) {
-        Upload installUpload = project.getTasks().add(INSTALL_TASK_NAME, Upload.class);
+        Upload installUpload = project.getTasks().create(INSTALL_TASK_NAME, Upload.class);
         Configuration configuration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
         installUpload.setConfiguration(configuration);
         MavenRepositoryHandlerConvention repositories = new DslObject(installUpload.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
         repositories.mavenInstaller();
-        installUpload.setDescription("Does a maven install of the archives artifacts into the local .m2 cache.");
+        installUpload.setDescription("Installs the 'archives' artifacts into the local Maven repository.");
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
index 60fe1a3..5eff207 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
@@ -31,8 +31,6 @@ import java.util.Collections;
 
 /**
  * Properties and methods added by the {@link org.gradle.api.plugins.MavenPlugin}.
- * 
- * @author Hans Dockter
  */
 public class MavenPluginConvention implements MavenPomMetaInfoProvider {
     private final ProjectInternal project;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
index 40076e6..fc02fe4 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
@@ -22,7 +22,7 @@ import org.gradle.api.artifacts.maven.MavenResolver;
 import java.util.Map;
 
 /**
- * Allows maven repositories for publishing artifacts to be defined. The maven plugin mixes-in this interface to the {@link org.gradle.api.artifacts.dsl.RepositoryHandler} associated with each
+ * Allows Maven repositories for publishing artifacts to be defined. The Maven plugin mixes-in this interface to the {@link org.gradle.api.artifacts.dsl.RepositoryHandler} associated with each
  * task of type {@link org.gradle.api.tasks.Upload}.
  */
 public interface MavenRepositoryHandlerConvention {
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
deleted file mode 100644
index 9697af0..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy
+++ /dev/null
@@ -1,54 +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.plugins.maven
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.Incubating
-import org.gradle.api.internal.artifacts.DependencyManagementServices
-import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
-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.util.SingleMessageLogger
-
-import javax.inject.Inject
-
-/**
- * by Szczepan Faber, created at: 8/1/12
- */
- at Incubating
-class ConvertMaven2Gradle extends DefaultTask {
-    private final MavenSettingsProvider settingsProvider
-
-    @Inject
-    ConvertMaven2Gradle(DependencyManagementServices managementServices) {
-        this.settingsProvider = managementServices.get(MavenSettingsProvider)
-    }
-
-    @TaskAction
-    void convertNow() {
-        SingleMessageLogger.informAboutIncubating("Maven to Gradle conversion")
-
-        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
deleted file mode 100644
index cb89b74..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/Maven2GradlePlugin.groovy
+++ /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.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
deleted file mode 100644
index 7c67fe2..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy
+++ /dev/null
@@ -1,552 +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.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.all {\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
deleted file mode 100644
index 5cce743..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java
+++ /dev/null
@@ -1,61 +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.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 this class attempts to mimic the behavior of the output of mvn help:effective-pom
-    //instead of this class we should walk the maven project object model (instead of parsing the xml!)
-
-    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 prepareXml(out.toString());
-    }
-
-    String prepareXml(String xml) {
-        return xml.replaceFirst("^<\\?xml.+?\\?>", "");
-    }
-}
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
deleted file mode 100644
index 214efa0..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreator.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.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/maven/internal/ArtifactPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPom.java
index 710a6f4..df4e600 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPom.java
@@ -22,9 +22,6 @@ import org.gradle.api.artifacts.maven.MavenPom;
 import java.io.File;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public interface ArtifactPom {
     /**
      * @return The main artifact, may be null.
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java
index 92a11fe..4b615b1 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java
@@ -21,9 +21,6 @@ import org.gradle.api.artifacts.maven.MavenDeployment;
 import java.io.File;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public interface ArtifactPomContainer {
     void addArtifact(Artifact artifact, File src);
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java
index c3761ac..e7ea210 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java
@@ -17,9 +17,6 @@ package org.gradle.api.publication.maven.internal;
 
 import org.gradle.api.artifacts.maven.MavenPom;
 
-/**
- * @author Hans Dockter
- */
 public interface ArtifactPomFactory {
     ArtifactPom createArtifactPom(MavenPom pom);
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java
index 4c631ef..f477259 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java
@@ -28,9 +28,6 @@ import org.gradle.util.WrapUtil;
 import java.util.HashMap;
 import java.util.Map;
 
-/**
- * @author Hans Dockter
- */
 public class BasePomFilterContainer implements PomFilterContainer {
     private Map<String, PomFilter> pomFilters = new HashMap<String, PomFilter>();
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomTaskFactoryDeployerFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomTaskFactoryDeployerFactory.java
deleted file mode 100644
index 19c2362..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomTaskFactoryDeployerFactory.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.publication.maven.internal;
-
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.publication.maven.internal.ant.CustomDeployTask;
-import org.gradle.api.publication.maven.internal.ant.DefaultGroovyMavenDeployer;
-import org.gradle.internal.Factory;
-import org.gradle.logging.LoggingManagerInternal;
-
-public class CustomTaskFactoryDeployerFactory extends DefaultDeployerFactory {
-    private final Factory<CustomDeployTask> taskFactory;
-
-    public CustomTaskFactoryDeployerFactory(MavenFactory mavenFactory, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMapping,
-                                            Factory<CustomDeployTask> taskFactory) {
-        super(mavenFactory, loggingManagerFactory, fileResolver, pomMetaInfoProvider, configurationContainer, scopeMapping);
-        this.taskFactory = taskFactory;
-    }
-
-    @Override
-    public DefaultGroovyMavenDeployer createMavenDeployer() {
-        DefaultGroovyMavenDeployer mavenDeployer = super.createMavenDeployer();
-        mavenDeployer.setDeployTaskFactory(taskFactory);
-        return mavenDeployer;
-
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java
index 6ed01b0..ed8a307 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java
@@ -20,7 +20,6 @@ import org.apache.commons.lang.ObjectUtils;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.maven.project.MavenProject;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.MavenPom;
 import org.gradle.api.internal.artifacts.publish.AbstractPublishArtifact;
@@ -28,9 +27,6 @@ import org.gradle.api.internal.artifacts.publish.AbstractPublishArtifact;
 import java.io.File;
 import java.util.*;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultArtifactPom implements ArtifactPom {
     private static final Set<String> PACKAGING_TYPES = Sets.newHashSet("war", "jar", "ear");
     private final MavenPom pom;
@@ -99,7 +95,7 @@ public class DefaultArtifactPom implements ArtifactPom {
     }
 
     private String getClassifier(Artifact artifact) {
-        return artifact.getExtraAttribute(Dependency.CLASSIFIER);
+        return artifact.getExtraAttribute("classifier");
     }
 
     private void assignArtifactValuesToPom(Artifact artifact, MavenPom pom, boolean setType) {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java
index 66b6383..987d16e 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java
@@ -27,9 +27,6 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultArtifactPomContainer implements ArtifactPomContainer {
     private Map<String, ArtifactPom> artifactPoms = new HashMap<String, ArtifactPom>();
     private final MavenPomMetaInfoProvider pomMetaInfoProvider;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java
index b9eb510..1667ce3 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java
@@ -17,9 +17,6 @@ package org.gradle.api.publication.maven.internal;
 
 import org.gradle.api.artifacts.maven.MavenPom;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultArtifactPomFactory implements ArtifactPomFactory {
     public ArtifactPom createArtifactPom(MavenPom pom) {
         return new DefaultArtifactPom(pom);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java
index c766fd1..71db496 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java
@@ -16,16 +16,13 @@
 package org.gradle.api.publication.maven.internal;
 
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.util.WrapUtil;
 
 import java.util.*;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultConf2ScopeMappingContainer implements Conf2ScopeMappingContainer {
     private Map<Configuration, Conf2ScopeMapping> mappings = new HashMap<Configuration, Conf2ScopeMapping>();
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenDeployment.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenDeployment.java
index 09ec43d..b109bbd 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenDeployment.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenDeployment.java
@@ -22,9 +22,6 @@ import org.gradle.api.artifacts.maven.MavenDeployment;
 import java.util.HashSet;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
-*/
 public class DefaultMavenDeployment implements MavenDeployment {
     private Set<PublishArtifact> attachedArtifacts;
     private final PublishArtifact pomArtifact;
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 31e5f1c..dea4fd9 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
@@ -27,10 +27,10 @@ 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.ClosureBackedAction;
-import org.gradle.api.internal.ErroringAction;
-import org.gradle.api.internal.IoActions;
-import org.gradle.api.internal.xml.XmlTransformer;
+import org.gradle.internal.ErroringAction;
+import org.gradle.internal.IoActions;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.xml.XmlTransformer;
 import org.gradle.listener.ActionBroadcast;
 
 import java.io.BufferedWriter;
@@ -39,9 +39,6 @@ import java.io.Writer;
 import java.util.Collections;
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultMavenPom implements MavenPom {
 
     private PomDependenciesConverter pomDependenciesConverter;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java
index 4df4826..146f9b1 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java
@@ -18,12 +18,9 @@ package org.gradle.api.publication.maven.internal;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.Factory;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultMavenPomFactory implements Factory<MavenPom> {
     private ConfigurationContainer configurationContainer;
     private Conf2ScopeMappingContainer conf2ScopeMappingContainer;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
index f5b12e5..f51b055 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
@@ -18,7 +18,7 @@ package org.gradle.api.publication.maven.internal;
 import groovy.lang.Closure;
 import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
 import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.internal.Actions;
+import org.gradle.internal.Actions;
 import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.ConfigureByMapAction;
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java
index 3f44c82..2194182 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java
@@ -18,9 +18,6 @@ package org.gradle.api.publication.maven.internal;
 import org.gradle.api.artifacts.maven.MavenPom;
 import org.gradle.api.artifacts.maven.PublishFilter;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultPomFilter implements PomFilter {
     private String name;
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java
index cc5743d..1702005 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java
@@ -18,9 +18,6 @@ package org.gradle.api.publication.maven.internal;
 import org.gradle.api.artifacts.ExcludeRule;
 
 
-/**
- * @author Hans Dockter
- */
 public interface ExcludeRuleConverter {
     Object convert(ExcludeRule excludeRule);
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java
index 735fb9a..04c38b3 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java
@@ -21,9 +21,6 @@ import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import java.util.List;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public interface PomDependenciesConverter {
     public List<?> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations);
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java
index 84f8010..96a6aaa 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java
@@ -18,9 +18,6 @@ package org.gradle.api.publication.maven.internal;
 import org.gradle.api.artifacts.maven.MavenPom;
 import org.gradle.api.artifacts.maven.PublishFilter;
 
-/**
- * @author Hans Dockter
- */
 public interface PomFilter {
     String getName();
 
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 9bd93e4..3ba0bd5 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
@@ -29,6 +29,7 @@ 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.core.settings.IvySettings;
 import org.apache.ivy.plugins.namespace.Namespace;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.plugins.resolver.ResolverSettings;
@@ -39,11 +40,15 @@ import org.apache.maven.artifact.ant.Pom;
 import org.apache.maven.settings.Settings;
 import org.apache.tools.ant.Project;
 import org.gradle.api.Action;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.*;
 import org.gradle.api.internal.ClosureBackedAction;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleVersionRepository;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.NoOpRepositoryCacheManager;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionArtifactPublishMetaData;
+import org.gradle.api.internal.artifacts.metadata.ModuleVersionPublishMetaData;
 import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
@@ -58,10 +63,7 @@ import java.text.ParseException;
 import java.util.Map;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractMavenResolver extends AbstractArtifactRepository implements MavenResolver, DependencyResolver {
+public abstract class AbstractMavenResolver extends AbstractArtifactRepository implements MavenResolver, DependencyResolver, ModuleVersionPublisher {
     
     private ArtifactPomContainer artifactPomContainer;
 
@@ -81,11 +83,11 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
         this.loggingManager = loggingManager;
     }
 
-    public IvyAwareModuleVersionRepository createResolver() {
+    public ConfiguredModuleVersionRepository createResolver() {
         throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
-    public DependencyResolver createPublisher() {
+    public ModuleVersionPublisher createPublisher() {
         return this;
     }
 
@@ -156,6 +158,30 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
     }
 
     public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public void publish(ModuleVersionPublishMetaData moduleVersion) {
+        ModuleVersionIdentifier id = moduleVersion.getId();
+        for (ModuleVersionArtifactPublishMetaData artifact : moduleVersion.getArtifacts()) {
+            collectArtifact(artifact.toIvyArtifact(), artifact.getFile());
+        }
+        publish();
+    }
+
+    private void collectArtifact(Artifact artifact, File src) {
         if (isIgnorable(artifact)) {
             return;
         }
@@ -166,15 +192,7 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
         return artifact.getType().equals("ivy");
     }
 
-    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
-        // do nothing
-    }
-
-    public void abortPublishTransaction() throws IOException {
-        // do nothing
-    }
-
-    public void commitPublishTransaction() throws IOException {
+    private void publish() {
         InstallDeployTaskSupport installDeployTaskSupport = createPreConfiguredTask(AntUtil.createProject());
         Set<MavenDeployment> mavenDeployments = getArtifactPomContainer().createDeployableFilesInfos();
         mavenSettingsSupplier.supply(installDeployTaskSupport);
@@ -217,6 +235,10 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
         // do nothing
     }
 
+    public void setSettings(IvySettings settings) {
+        // do nothing
+    }
+
     public RepositoryCacheManager getRepositoryCacheManager() {
         return new NoOpRepositoryCacheManager(getName());
     }
@@ -225,10 +247,6 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
         return artifactPomContainer;
     }
 
-    public void setArtifactPomContainer(ArtifactPomContainer artifactPomContainer) {
-        this.artifactPomContainer = artifactPomContainer;
-    }
-
     public Settings getSettings() {
         return settings;
     }
@@ -285,10 +303,6 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
         return pomFilterContainer;
     }
 
-    public void setPomFilterContainer(PomFilterContainer pomFilterContainer) {
-        this.pomFilterContainer = pomFilterContainer;
-    }
-
     public void beforeDeployment(Action<? super MavenDeployment> action) {
         beforeDeploymentActions.add(action);
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java
index bac0525..35b0e95 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java
@@ -24,7 +24,6 @@ import org.codehaus.plexus.PlexusContainerException;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.maven.MavenDeployer;
 import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.internal.Factory;
 import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
 import org.gradle.logging.LoggingManagerInternal;
 
@@ -33,16 +32,11 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-/**
- * @author Hans Dockter
- */
 public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDeployer {
     private RemoteRepository remoteRepository;
 
     private RemoteRepository remoteSnapshotRepository;
 
-    private Factory<CustomDeployTask> deployTaskFactory = new DefaultDeployTaskFactory();
-
     private Configuration configuration;
 
     // todo remove this property once configuration can handle normal file system dependencies
@@ -55,7 +49,7 @@ public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDep
     }
 
     protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
-        CustomDeployTask deployTask = deployTaskFactory.create();
+        CustomDeployTask deployTask = createTask();
         deployTask.setProject(project);
         deployTask.setUniqueVersion(isUniqueVersion());
         addProtocolProvider(deployTask);
@@ -63,6 +57,10 @@ public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDep
         return deployTask;
     }
 
+    protected CustomDeployTask createTask() {
+        return new CustomDeployTask();
+    }
+
     private void addProtocolProvider(CustomDeployTask deployTask) {
         PlexusContainer plexusContainer = deployTask.getContainer();
         for (File wagonProviderJar : getJars()) {
@@ -99,14 +97,6 @@ public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDep
         this.remoteSnapshotRepository = (RemoteRepository) remoteSnapshotRepository;
     }
 
-    public Factory<CustomDeployTask> getDeployTaskFactory() {
-        return deployTaskFactory;
-    }
-
-    public void setDeployTaskFactory(Factory<CustomDeployTask> deployTaskFactory) {
-        this.deployTaskFactory = deployTaskFactory;
-    }
-
     public void addProtocolProviderJars(Collection<File> jars) {
         protocolProviderJars.addAll(jars);
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java
index df93aac..a901694 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java
@@ -19,32 +19,22 @@ import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
 import org.apache.maven.artifact.ant.InstallTask;
 import org.apache.tools.ant.Project;
 import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.internal.Factory;
 import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
 import org.gradle.logging.LoggingManagerInternal;
 
-/**
- * @author Hans Dockter
- */
 public class BaseMavenInstaller extends AbstractMavenResolver {
-    private Factory<CustomInstallTask> installTaskFactory = new DefaultInstallTaskFactory();
-
     public BaseMavenInstaller(PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
         super(pomFilterContainer, artifactPomContainer, loggingManager);
         mavenSettingsSupplier = new MaybeUserMavenSettingsSupplier();
     }
 
     protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
-        InstallTask installTask = installTaskFactory.create();
+        InstallTask installTask = createTask();
         installTask.setProject(project);
         return installTask;
     }
 
-    public Factory<CustomInstallTask> getInstallTaskFactory() {
-        return installTaskFactory;
-    }
-
-    public void setInstallTaskFactory(Factory<CustomInstallTask> installTaskFactory) {
-        this.installTaskFactory = installTaskFactory;
+    protected CustomInstallTask createTask() {
+        return new CustomInstallTask();
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
index bd9499d..85521bf 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
@@ -21,8 +21,6 @@ import org.codehaus.plexus.PlexusContainer;
 
 /**
  * We could also use reflection to get hold of the container property. But this would make it harder to use a Mock for this class.
- *
- * @author Hans Dockter
  */
 public class CustomDeployTask extends DeployTask implements CustomInstallDeployTaskSupport {
     @Override
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
index afd72d8..40c3ea9 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
@@ -15,13 +15,10 @@
  */
 package org.gradle.api.publication.maven.internal.ant;
 
-import org.apache.maven.settings.Settings;
 import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.settings.Settings;
 import org.apache.tools.ant.Project;
 
-/**
- * @author Hans Dockter
- */
 public interface CustomInstallDeployTaskSupport {
     Settings getSettings();
     Project getProject();
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
index 9a87b04..15079c4 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
@@ -18,9 +18,6 @@ package org.gradle.api.publication.maven.internal.ant;
 import org.apache.maven.artifact.ant.InstallTask;
 import org.apache.maven.settings.Settings;
 
-/**
- * @author Hans Dockter
- */
 public class CustomInstallTask extends InstallTask implements CustomInstallDeployTaskSupport {
     @Override
     public synchronized Settings getSettings() {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactory.java
deleted file mode 100644
index 6ae4016..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.publication.maven.internal.ant;
-
-import org.gradle.internal.Factory;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultDeployTaskFactory implements Factory<CustomDeployTask> {
-    public CustomDeployTask create() {
-        return new CustomDeployTask();
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java
index 93160b5..2f0efef 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java
@@ -20,9 +20,6 @@ import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.publication.maven.internal.ExcludeRuleConverter;
 
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
     public Exclusion convert(ExcludeRule excludeRule) {
         if (isConvertable(excludeRule)) {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy
index 3fafaa0..819de15 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy
@@ -19,12 +19,8 @@ import org.codehaus.groovy.runtime.InvokerHelper
 import org.gradle.api.artifacts.maven.GroovyMavenDeployer
 import org.gradle.api.artifacts.maven.PomFilterContainer
 import org.gradle.api.publication.maven.internal.ArtifactPomContainer
-
 import org.gradle.logging.LoggingManagerInternal
 
-/**
- * @author Hans Dockter
- */
 class DefaultGroovyMavenDeployer extends BaseMavenDeployer implements GroovyMavenDeployer, PomFilterContainer {
     public static final String REPOSITORY_BUILDER = "repository"
     public static final String SNAPSHOT_REPOSITORY_BUILDER = 'snapshotRepository'
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultInstallTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultInstallTaskFactory.java
deleted file mode 100644
index ade162e..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultInstallTaskFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.publication.maven.internal.ant;
-
-import org.gradle.internal.Factory;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultInstallTaskFactory implements Factory<CustomInstallTask> {
-    public CustomInstallTask create() {
-        return new CustomInstallTask();
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
index aa8f6e9..e1a00b7 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
@@ -26,9 +26,6 @@ import org.gradle.api.publication.maven.internal.PomDependenciesConverter;
 
 import java.util.*;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultPomDependenciesConverter implements PomDependenciesConverter {
     private ExcludeRuleConverter excludeRuleConverter;
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java
index 2f7b1ac..5549dcb 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java
@@ -25,9 +25,6 @@ import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
 import java.io.File;
 import java.io.IOException;
 
-/**
- * @author Szczepan Faber, created at: 3/29/11
- */
 public class EmptyMavenSettingsSupplier implements MavenSettingsSupplier {
 
     private final TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java
index 759a5b0..248f839 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java
@@ -24,9 +24,6 @@ import org.codehaus.plexus.component.repository.exception.ComponentLookupExcepti
 
 import java.lang.reflect.Field;
 
-/**
- * @author Hans Dockter
- */
 public class LoggingHelper {
     public static void injectLogger(PlexusContainer container, Project project) {
         try {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java
index 8efe218..a8fb1c2 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java
@@ -18,9 +18,6 @@ package org.gradle.api.publication.maven.internal.ant;
 
 import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
 
-/**
- * @author Szczepan Faber, created at: 3/29/11
- */
 public interface MavenSettingsSupplier {
     void done();
     void supply(InstallDeployTaskSupport installDeployTaskSupport);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java
index 01fef7e..af4020b 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java
@@ -22,9 +22,6 @@ import org.gradle.api.internal.artifacts.mvnsettings.MavenFileLocations;
 
 import java.io.File;
 
-/**
- * @author Szczepan Faber, created at: 3/29/11
- */
 public class MaybeUserMavenSettingsSupplier implements MavenSettingsSupplier {
 
     MavenSettingsSupplier emptySettingsSupplier = new EmptyMavenSettingsSupplier();
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
deleted file mode 100644
index a5f5997..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
+++ /dev/null
@@ -1,56 +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.publication.maven.internal.ant;
-
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.apache.maven.artifact.repository.ArtifactRepository;
-import org.apache.maven.artifact.repository.DefaultArtifactRepository;
-import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
-import org.gradle.internal.Factory;
-
-import java.io.File;
-
-public class NoInstallDeployTaskFactory implements Factory<CustomDeployTask> {
-    private final Factory<File> temporaryDirFactory;
-
-    public NoInstallDeployTaskFactory(Factory<File> temporaryDirFactory) {
-        this.temporaryDirFactory = temporaryDirFactory;
-    }
-
-    public CustomDeployTask create() {
-        return new NoInstallDeployTask(temporaryDirFactory);
-    }
-
-    private static class NoInstallDeployTask extends CustomDeployTask {
-        private final Factory<File> tmpDirFactory;
-
-        public NoInstallDeployTask(Factory<File> tmpDirFactory) {
-            this.tmpDirFactory = tmpDirFactory;
-        }
-
-        @Override
-        protected ArtifactRepository createLocalArtifactRepository() {
-            ArtifactRepositoryLayout repositoryLayout = (ArtifactRepositoryLayout) lookup(ArtifactRepositoryLayout.ROLE, getLocalRepository().getLayout());
-            return new DefaultArtifactRepository("local", tmpDirFactory.create().toURI().toString(), repositoryLayout);
-        }
-
-        @Override
-        protected void updateRepositoryWithSettings(RemoteRepository repository) {
-            // Do nothing
-        }
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java
index 9e89767..988270d 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java
@@ -17,6 +17,7 @@
 package org.gradle.api.publication.maven.internal.ant;
 
 import com.google.common.collect.Lists;
+import org.apache.maven.project.MavenProject;
 import org.gradle.api.Nullable;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.ProjectDependency;
@@ -57,7 +58,7 @@ public class ProjectDependencyArtifactIdExtractorHack {
         Set<String> artifactIds = getArtifactIds(deployers);
         if (artifactIds.size() == 1) {
             String artifactId = artifactIds.iterator().next();
-            if (artifactId != null && !artifactId.equals("empty-project")) {
+            if (artifactId != null && !artifactId.equals(MavenProject.EMPTY_PROJECT_ARTIFACT_ID)) {
                 return artifactId;
             }
         }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java
index 110fc10..bff4576 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java
@@ -21,9 +21,6 @@ import org.apache.maven.artifact.ant.Proxy;
 import org.apache.maven.artifact.ant.RemoteRepository;
 import org.apache.maven.artifact.ant.RepositoryPolicy;
 
-/**
- * @author Hans Dockter
- */
 public class RepositoryBuilder extends FactoryBuilderSupport {
     public RepositoryBuilder() {
         registerFactory("repository", new RepositoryFactory(RemoteRepository.class));
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.java
index 7bf1586..e3517aa 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.java
@@ -22,9 +22,6 @@ import org.apache.maven.artifact.ant.Proxy;
 import org.apache.maven.artifact.ant.RemoteRepository;
 import org.apache.maven.artifact.ant.RepositoryPolicy;
 
-/**
- * @author Hans Dockter
- */
 public class RepositoryFactory extends BeanFactory {
     public RepositoryFactory(Class klass) {
         super(klass);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java
index db2b46f..d04f5d5 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java
@@ -27,7 +27,7 @@ import org.gradle.api.Incubating;
  * <pre autoTested="true">
  * apply plugin: 'maven-publish'
  *
- * def publication = publishing.publications.add("name", MavenPublication)
+ * def publication = publishing.publications.create("name", MavenPublication)
  * def artifacts = publication.artifacts
  *
  * artifacts.matching({
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenDependency.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenDependency.java
new file mode 100644
index 0000000..eda0264
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenDependency.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven;
+
+import org.gradle.api.Incubating;
+import org.gradle.internal.HasInternalProtocol;
+
+/**
+ * A dependency declared as part of an {@link MavenPublication}.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface MavenDependency {
+    /**
+     * The groupId value for this dependency.
+     */
+    String getGroupId();
+
+    /**
+     * The artifactId value for this dependency.
+     */
+    String getArtifactId();
+
+    /**
+     * The version value for this dependency.
+     */
+    String getVersion();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
index 9a711d3..2441635 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
@@ -19,7 +19,7 @@ package org.gradle.api.publish.maven;
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
 import org.gradle.api.XmlProvider;
-import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * The POM for a Maven publication.
@@ -42,7 +42,7 @@ public interface MavenPom {
      *   publications {
      *     maven(MavenPublication) {
      *       pom.withXml {
-     *         asNode().appendNode('description', 'A demonstration of maven pom customisation')
+     *         asNode().appendNode('description', 'A demonstration of Maven POM customization')
      *       }
      *     }
      *   }
@@ -63,4 +63,15 @@ public interface MavenPom {
      */
     void withXml(Action<? super XmlProvider> action);
 
+    /**
+     * Returns the packaging for this publication.
+     */
+    String getPackaging();
+
+    /**
+     * Sets the packaging for this publication.
+     */
+    void setPackaging(String packaging);
+
+
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
index 900f2dc..8b3e83b 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
@@ -19,7 +19,7 @@ package org.gradle.api.publish.maven;
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
 import org.gradle.api.component.SoftwareComponent;
-import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.internal.HasInternalProtocol;
 import org.gradle.api.publish.Publication;
 
 /**
@@ -29,7 +29,9 @@ import org.gradle.api.publish.Publication;
  * <pre>
  * publishing {
  *   publications {
- *     myPublicationName(MavenPublication)
+ *     myPublicationName(MavenPublication) {
+ *       // Configure the publication here
+ *     }
  *   }
  * }
  * </pre>
@@ -69,7 +71,7 @@ import org.gradle.api.publish.Publication;
  *         classifier "source"
  *       }
  *       pom.withXml {
- *         asNode().appendNode('description', 'A demonstration of maven pom customisation')
+ *         asNode().appendNode('description', 'A demonstration of Maven POM customization')
  *       }
  *     }
  *   }
@@ -109,7 +111,7 @@ public interface MavenPublication extends Publication {
      * Currently 2 types of component are supported: 'components.java' (added by the JavaPlugin) and 'components.web' (added by the WarPlugin).
      * For any individual MavenPublication, only a single component can be provided in this way.
      *
-     * The following example demonstrates how to publish the 'java' component to a maven repository.
+     * The following example demonstrates how to publish the 'java' component to a Maven repository.
      * <pre autoTested="true">
      * apply plugin: "java"
      * apply plugin: "maven-publish"
@@ -232,4 +234,34 @@ public interface MavenPublication extends Publication {
      */
     MavenArtifactSet getArtifacts();
 
+    /**
+     * Returns the groupId for this publication.
+     */
+    String getGroupId();
+
+    /**
+     * Sets the groupId for this publication.
+     */
+    void setGroupId(String groupId);
+
+    /**
+     * Returns the artifactId for this publication.
+     */
+    String getArtifactId();
+
+    /**
+     * Sets the artifactId for this publication.
+     */
+    void setArtifactId(String artifactId);
+
+    /**
+     * Returns the version for this publication.
+     */
+    String getVersion();
+
+    /**
+     * Sets the version for this publication.
+     */
+    void setVersion(String version);
+
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishTaskModelRule.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishTaskModelRule.java
new file mode 100644
index 0000000..72f9f71
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishTaskModelRule.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven.internal;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
+import org.gradle.api.publish.maven.tasks.GenerateMavenPom;
+import org.gradle.api.publish.maven.tasks.PublishToMavenLocal;
+import org.gradle.api.publish.maven.tasks.PublishToMavenRepository;
+import org.gradle.api.publish.plugins.PublishingPlugin;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.model.ModelRule;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class MavenPublishTaskModelRule extends ModelRule {
+
+    private final Project project;
+    private final Task publishLifecycleTask;
+    private final Task publishLocalLifecycleTask;
+
+    public MavenPublishTaskModelRule(Project project, Task publishLifecycleTask, Task publishLocalLifecycleTask) {
+        this.project = project;
+        this.publishLifecycleTask = publishLifecycleTask;
+        this.publishLocalLifecycleTask = publishLocalLifecycleTask;
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void realizePublishingTasks(TaskContainer tasks, PublishingExtension extension) {
+        // Create generatePom tasks for any Maven publication
+        PublicationContainer publications = extension.getPublications();
+
+        for (final MavenPublicationInternal publication : publications.withType(MavenPublicationInternal.class)) {
+            String publicationName = publication.getName();
+
+            createGeneratePomTask(publication, publicationName);
+            createLocalInstallTask(tasks, publication, publicationName);
+            createPublishTasksForEachMavenRepo(tasks, extension, publication, publicationName);
+        }
+    }
+
+    private void createPublishTasksForEachMavenRepo(TaskContainer tasks, PublishingExtension extension, MavenPublicationInternal publication, String publicationName) {
+        for (MavenArtifactRepository repository : extension.getRepositories().withType(MavenArtifactRepository.class)) {
+            String repositoryName = repository.getName();
+
+            String publishTaskName = String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
+
+            PublishToMavenRepository publishTask = tasks.create(publishTaskName, PublishToMavenRepository.class);
+            publishTask.setPublication(publication);
+            publishTask.setRepository(repository);
+            publishTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP);
+            publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven repository '%s'.", publicationName, repositoryName));
+
+            publishLifecycleTask.dependsOn(publishTask);
+        }
+    }
+
+    private void createLocalInstallTask(TaskContainer tasks, MavenPublicationInternal publication, String publicationName) {
+        String installTaskName = String.format("publish%sPublicationToMavenLocal", capitalize(publicationName));
+
+        PublishToMavenLocal publishLocalTask = tasks.create(installTaskName, PublishToMavenLocal.class);
+        publishLocalTask.setPublication(publication);
+        publishLocalTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP);
+        publishLocalTask.setDescription(String.format("Publishes Maven publication '%s' to the local Maven repository.", publicationName));
+
+        publishLocalLifecycleTask.dependsOn(installTaskName);
+    }
+
+    private void createGeneratePomTask(final MavenPublicationInternal publication, String publicationName) {
+        String descriptorTaskName = String.format("generatePomFileFor%sPublication", capitalize(publicationName));
+        GenerateMavenPom generatePomTask = project.getTasks().create(descriptorTaskName, GenerateMavenPom.class);
+        generatePomTask.setDescription(String.format("Generates the Maven POM file for publication '%s'.", publication.getName()));
+        generatePomTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP);
+        generatePomTask.setPom(publication.getPom());
+
+        ConventionMapping descriptorTaskConventionMapping = new DslObject(generatePomTask).getConventionMapping();
+        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/pom-default.xml");
+            }
+        });
+
+        // Wire the generated pom into the publication.
+        publication.setPomFile(generatePomTask.getOutputs().getFiles());
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java
index db4a67f..27ef454 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java
@@ -63,4 +63,9 @@ public class DefaultMavenArtifact implements MavenArtifact {
     public TaskDependency getBuildDependencies() {
         return buildDependencies;
     }
+
+    @Override
+    public String toString() {
+        return String.format("%s %s:%s", getClass().getSimpleName(), getExtension(), getClassifier());
+    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java
index 6f5ea60..d00393c 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java
@@ -19,7 +19,7 @@ import org.gradle.api.Action;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.DefaultDomainObjectSet;
 import org.gradle.api.internal.file.AbstractFileCollection;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
 import org.gradle.api.internal.tasks.TaskDependencyInternal;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
@@ -35,9 +35,9 @@ public class DefaultMavenArtifactSet extends DefaultDomainObjectSet<MavenArtifac
     private final String publicationName;
     private final TaskDependencyInternal builtBy = new ArtifactsTaskDependency();
     private final ArtifactsFileCollection files = new ArtifactsFileCollection();
-    private final NotationParser<MavenArtifact> mavenArtifactParser;
+    private final NotationParser<Object, MavenArtifact> mavenArtifactParser;
 
-    public DefaultMavenArtifactSet(String publicationName, NotationParser<MavenArtifact> mavenArtifactParser) {
+    public DefaultMavenArtifactSet(String publicationName, NotationParser<Object, MavenArtifact> mavenArtifactParser) {
         super(MavenArtifact.class);
         this.publicationName = publicationName;
         this.mavenArtifactParser = mavenArtifactParser;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java
index 14ca0f5..5cb9ed0 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java
@@ -19,12 +19,12 @@ package org.gradle.api.publish.maven.internal.artifact;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.UnsupportedNotationException;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.typeconversion.NotationParserBuilder;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.internal.typeconversion.MapKey;
+import org.gradle.internal.typeconversion.MapNotationParser;
+import org.gradle.internal.typeconversion.TypedNotationParser;
 import org.gradle.api.publish.maven.MavenArtifact;
 import org.gradle.api.tasks.bundling.AbstractArchiveTask;
 import org.gradle.internal.Factory;
@@ -33,7 +33,7 @@ import org.gradle.internal.reflect.Instantiator;
 import java.io.File;
 import java.util.Collection;
 
-public class MavenArtifactNotationParserFactory implements Factory<NotationParser<MavenArtifact>> {
+public class MavenArtifactNotationParserFactory implements Factory<NotationParser<Object, MavenArtifact>> {
     private final Instantiator instantiator;
     private final FileResolver fileResolver;
 
@@ -42,12 +42,12 @@ public class MavenArtifactNotationParserFactory implements Factory<NotationParse
         this.fileResolver = fileResolver;
     }
 
-    public NotationParser<MavenArtifact> create() {
+    public NotationParser<Object, MavenArtifact> create() {
         FileNotationParser fileNotationParser = new FileNotationParser(fileResolver);
         ArchiveTaskNotationParser archiveTaskNotationParser = new ArchiveTaskNotationParser();
         PublishArtifactNotationParser publishArtifactNotationParser = new PublishArtifactNotationParser();
 
-        NotationParser<MavenArtifact> sourceNotationParser = new NotationParserBuilder<MavenArtifact>()
+        NotationParser<Object, MavenArtifact> sourceNotationParser = new NotationParserBuilder<MavenArtifact>()
                 .resultingType(MavenArtifact.class)
                 .parser(archiveTaskNotationParser)
                 .parser(publishArtifactNotationParser)
@@ -96,8 +96,8 @@ public class MavenArtifactNotationParserFactory implements Factory<NotationParse
         }
     }
 
-    private class FileNotationParser implements NotationParser<MavenArtifact> {
-        private final NotationParser<File> fileResolverNotationParser;
+    private class FileNotationParser implements NotationParser<Object, MavenArtifact> {
+        private final NotationParser<Object, File> fileResolverNotationParser;
 
         private FileNotationParser(FileResolver fileResolver) {
             this.fileResolverNotationParser = fileResolver.asNotationParser();
@@ -119,9 +119,9 @@ public class MavenArtifactNotationParserFactory implements Factory<NotationParse
     }
 
     private class MavenArtifactMapNotationParser extends MapNotationParser<MavenArtifact> {
-        private final NotationParser<MavenArtifact> sourceNotationParser;
+        private final NotationParser<Object, MavenArtifact> sourceNotationParser;
 
-        private MavenArtifactMapNotationParser(NotationParser<MavenArtifact> sourceNotationParser) {
+        private MavenArtifactMapNotationParser(NotationParser<Object, MavenArtifact> sourceNotationParser) {
             this.sourceNotationParser = sourceNotationParser;
         }
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/dependencies/DefaultMavenDependency.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/dependencies/DefaultMavenDependency.java
new file mode 100644
index 0000000..8327c1a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/dependencies/DefaultMavenDependency.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven.internal.dependencies;
+
+import org.gradle.api.artifacts.DependencyArtifact;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class DefaultMavenDependency implements MavenDependencyInternal {
+    private final String groupId;
+    private final String artifactId;
+    private final String version;
+    private final List<DependencyArtifact> artifacts = new ArrayList<DependencyArtifact>();
+
+    public DefaultMavenDependency(String groupId, String artifactId, String version) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.version = version;
+    }
+
+    public DefaultMavenDependency(String groupId, String artifactId, String version, Collection<DependencyArtifact> artifacts) {
+        this(groupId, artifactId, version);
+        this.artifacts.addAll(artifacts);
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public Collection<DependencyArtifact> getArtifacts() {
+        return artifacts;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/dependencies/MavenDependencyInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/dependencies/MavenDependencyInternal.java
new file mode 100644
index 0000000..5153941
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/dependencies/MavenDependencyInternal.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven.internal.dependencies;
+
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.publish.maven.MavenDependency;
+
+import java.util.Collection;
+
+public interface MavenDependencyInternal extends MavenDependency {
+    Collection<DependencyArtifact> getArtifacts();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/GeneratePomTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/GeneratePomTaskCreator.java
deleted file mode 100644
index 3fa99c5..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/GeneratePomTaskCreator.java
+++ /dev/null
@@ -1,71 +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.publish.maven.internal.plugins;
-
-import  org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.internal.ConventionMapping;
-import org.gradle.api.internal.plugins.DslObject;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
-import org.gradle.api.publish.maven.tasks.GenerateMavenPom;
-
-import java.io.File;
-import java.util.concurrent.Callable;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class GeneratePomTaskCreator {
-
-    private final Project project;
-
-    public GeneratePomTaskCreator(Project project) {
-        this.project = project;
-    }
-
-    public void monitor(PublicationContainer publications) {
-        publications.withType(MavenPublicationInternal.class).all(new Action<MavenPublicationInternal>() {
-            public void execute(MavenPublicationInternal publication) {
-                create(publication);
-            }
-        });
-    }
-
-    private void create(final MavenPublicationInternal publication) {
-        String publicationName = publication.getName();
-
-        String descriptorTaskName = calculateDescriptorTaskName(publicationName);
-        GenerateMavenPom generatePomTask = project.getTasks().add(descriptorTaskName, GenerateMavenPom.class);
-        generatePomTask.setDescription(String.format("Generates the Maven POM file for publication '%s'.", publication.getName()));
-        generatePomTask.setPom(publication.getPom());
-
-        ConventionMapping descriptorTaskConventionMapping = new DslObject(generatePomTask).getConventionMapping();
-        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
-            public Object call() throws Exception {
-                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/pom-default.xml");
-            }
-        });
-
-        // Wire the generated pom into the publication.
-        publication.setPomFile(generatePomTask.getOutputs().getFiles());
-    }
-
-    private String calculateDescriptorTaskName(String publicationName) {
-        return String.format("generatePomFileFor%sPublication", capitalize(publicationName));
-    }
-
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishDynamicTaskCreator.java
deleted file mode 100644
index d5b88a7..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishDynamicTaskCreator.java
+++ /dev/null
@@ -1,86 +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.publish.maven.internal.plugins;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectList;
-import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.ArtifactRepositoryContainer;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
-import org.gradle.api.publish.maven.tasks.PublishToMavenRepository;
-import org.gradle.api.tasks.TaskContainer;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class MavenPublishDynamicTaskCreator {
-
-    final private TaskContainer tasks;
-    private final Task publishLifecycleTask;
-
-    public MavenPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
-        this.tasks = tasks;
-        this.publishLifecycleTask = publishLifecycleTask;
-    }
-
-    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
-        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
-        final NamedDomainObjectList<MavenArtifactRepository> mavenRepositories = repositories.withType(MavenArtifactRepository.class);
-
-        mavenPublications.all(new Action<MavenPublicationInternal>() {
-            public void execute(MavenPublicationInternal publication) {
-                for (MavenArtifactRepository repository : mavenRepositories) {
-                    maybeCreatePublishTask(publication, repository);
-                }
-            }
-        });
-
-        mavenRepositories.all(new Action<MavenArtifactRepository>() {
-            public void execute(MavenArtifactRepository repository) {
-                for (MavenPublicationInternal publication : mavenPublications) {
-                    maybeCreatePublishTask(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 maybeCreatePublishTask(MavenPublicationInternal publication, MavenArtifactRepository repository) {
-        String publicationName = publication.getName();
-        String repositoryName = repository.getName();
-
-        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
-        if (tasks.findByName(publishTaskName) == null) {
-            PublishToMavenRepository publishTask = tasks.add(publishTaskName, PublishToMavenRepository.class);
-            publishTask.setPublication(publication);
-            publishTask.setRepository(repository);
-            publishTask.setGroup("publishing");
-            publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven repository '%s'.", publicationName, repositoryName));
-
-            publishLifecycleTask.dependsOn(publishTask);
-        }
-    }
-
-    private String calculatePublishTaskName(String publicationName, String repositoryName) {
-        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishLocalDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishLocalDynamicTaskCreator.java
deleted file mode 100644
index 3709bc3..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishLocalDynamicTaskCreator.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.api.publish.maven.internal.plugins;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.Task;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
-import org.gradle.api.publish.maven.tasks.PublishToMavenLocal;
-import org.gradle.api.tasks.TaskContainer;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class MavenPublishLocalDynamicTaskCreator {
-
-    final private TaskContainer tasks;
-    private final Task publishToMavenLocalLifecycleTask;
-
-    public MavenPublishLocalDynamicTaskCreator(TaskContainer tasks, Task publishToMavenLocalLifecycleTask) {
-        this.tasks = tasks;
-        this.publishToMavenLocalLifecycleTask = publishToMavenLocalLifecycleTask;
-    }
-
-    public void monitor(final PublicationContainer publications) {
-        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
-
-        mavenPublications.all(new Action<MavenPublicationInternal>() {
-            public void execute(MavenPublicationInternal publication) {
-                createInstallTask(publication);
-            }
-        });
-        // Note: we aren't supporting removal of publications
-    }
-
-    private void createInstallTask(MavenPublicationInternal publication) {
-        String publicationName = publication.getName();
-        String installTaskName = calculatePublishLocalTaskName(publicationName);
-
-        PublishToMavenLocal publishTask = tasks.add(installTaskName, PublishToMavenLocal.class);
-        publishTask.setPublication(publication);
-        publishTask.setGroup("publishing");
-        publishTask.setDescription(String.format("Publishes Maven publication '%s' to the local Maven cache.", publicationName));
-
-        publishToMavenLocalLifecycleTask.dependsOn(publishTask);
-    }
-
-    private String calculatePublishLocalTaskName(String publicationName) {
-        return String.format("publish%sPublicationToMavenLocal", capitalize(publicationName));
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java
index 9c692f0..0beb2e9 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java
@@ -17,11 +17,12 @@
 package org.gradle.api.publish.maven.internal.publication;
 
 import org.gradle.api.Action;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.UserCodeAction;
 import org.gradle.api.XmlProvider;
+import org.gradle.api.internal.UserCodeAction;
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal;
 import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 import org.gradle.listener.ActionBroadcast;
+import org.gradle.util.GUtil;
 
 import java.util.Set;
 
@@ -29,6 +30,7 @@ public class DefaultMavenPom implements MavenPomInternal {
 
     private final ActionBroadcast<XmlProvider> xmlAction = new ActionBroadcast<XmlProvider>();
     private final MavenPublicationInternal mavenPublication;
+    private String packaging;
 
     public DefaultMavenPom(MavenPublicationInternal mavenPublication) {
         this.mavenPublication = mavenPublication;
@@ -43,14 +45,18 @@ public class DefaultMavenPom implements MavenPomInternal {
     }
 
     public String getPackaging() {
-        return mavenPublication.determinePackagingFromArtifacts();
+        return GUtil.elvis(packaging, mavenPublication.determinePackagingFromArtifacts());
+    }
+
+    public void setPackaging(String packaging) {
+        this.packaging = packaging;
     }
 
     public MavenProjectIdentity getProjectIdentity() {
         return mavenPublication.getMavenProjectIdentity();
     }
 
-    public Set<Dependency> getRuntimeDependencies() {
+    public Set<MavenDependencyInternal> getRuntimeDependencies() {
         return mavenPublication.getRuntimeDependencies();
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java
index 42d1638..8d349ce 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java
@@ -18,18 +18,24 @@ package org.gradle.api.publish.maven.internal.publication;
 
 import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.component.SoftwareComponent;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.component.SoftwareComponentInternal;
 import org.gradle.api.internal.component.Usage;
 import org.gradle.api.internal.file.UnionFileCollection;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.api.publish.internal.ProjectDependencyPublicationResolver;
 import org.gradle.api.publish.maven.MavenArtifact;
 import org.gradle.api.publish.maven.MavenArtifactSet;
 import org.gradle.api.publish.maven.MavenPom;
 import org.gradle.api.publish.maven.internal.artifact.DefaultMavenArtifactSet;
+import org.gradle.api.publish.maven.internal.dependencies.DefaultMavenDependency;
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal;
 import org.gradle.api.publish.maven.internal.publisher.MavenNormalizedPublication;
 import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 import org.gradle.internal.reflect.Instantiator;
@@ -45,15 +51,18 @@ public class DefaultMavenPublication implements MavenPublicationInternal {
     private final MavenPomInternal pom;
     private final MavenProjectIdentity projectIdentity;
     private final DefaultMavenArtifactSet mavenArtifacts;
-    private final Set<Dependency> runtimeDependencies = new LinkedHashSet<Dependency>();
+    private final Set<MavenDependencyInternal> runtimeDependencies = new LinkedHashSet<MavenDependencyInternal>();
+    private final ProjectDependencyPublicationResolver projectDependencyResolver;
     private FileCollection pomFile;
     private SoftwareComponentInternal component;
 
     public DefaultMavenPublication(
-            String name, MavenProjectIdentity projectIdentity, NotationParser<MavenArtifact> mavenArtifactParser, Instantiator instantiator
+            String name, MavenProjectIdentity projectIdentity, NotationParser<Object, MavenArtifact> mavenArtifactParser, Instantiator instantiator,
+            ProjectDependencyPublicationResolver projectDependencyResolver
     ) {
         this.name = name;
-        this.projectIdentity = projectIdentity;
+        this.projectDependencyResolver = projectDependencyResolver;
+        this.projectIdentity = new DefaultMavenProjectIdentity(projectIdentity.getGroupId(), projectIdentity.getArtifactId(), projectIdentity.getVersion());
         mavenArtifacts = instantiator.newInstance(DefaultMavenArtifactSet.class, name, mavenArtifactParser);
         pom = instantiator.newInstance(DefaultMavenPom.class, this);
     }
@@ -82,16 +91,31 @@ public class DefaultMavenPublication implements MavenPublicationInternal {
         this.component = (SoftwareComponentInternal) component;
 
         for (Usage usage : this.component.getUsages()) {
-            // TODO:DAZ Need a smarter way to map usage to artifact classifier
+            // TODO Need a smarter way to map usage to artifact classifier
             for (PublishArtifact publishArtifact : usage.getArtifacts()) {
                 artifact(publishArtifact);
             }
 
-            // TODO:DAZ Need a smarter way to map usage to scope
-            runtimeDependencies.addAll(usage.getDependencies());
+            // TODO Need a smarter way to map usage to scope
+            for (ModuleDependency dependency : usage.getDependencies()) {
+                if (dependency instanceof ProjectDependency) {
+                    addProjectDependency((ProjectDependency) dependency);
+                } else {
+                    addModuleDependency(dependency);
+                }
+            }
         }
     }
 
+    private void addProjectDependency(ProjectDependency dependency) {
+        ModuleVersionIdentifier identifier = projectDependencyResolver.resolve(dependency);
+        runtimeDependencies.add(new DefaultMavenDependency(identifier.getGroup(), identifier.getName(), identifier.getVersion()));
+    }
+
+    private void addModuleDependency(ModuleDependency dependency) {
+        runtimeDependencies.add(new DefaultMavenDependency(dependency.getGroup(), dependency.getName(), dependency.getVersion(), dependency.getArtifacts()));
+     }
+
     public MavenArtifact artifact(Object source) {
         return mavenArtifacts.artifact(source);
     }
@@ -111,6 +135,30 @@ public class DefaultMavenPublication implements MavenPublicationInternal {
         }
     }
 
+    public String getGroupId() {
+        return projectIdentity.getGroupId();
+    }
+
+    public void setGroupId(String groupId) {
+        projectIdentity.setGroupId(groupId);
+    }
+
+    public String getArtifactId() {
+        return projectIdentity.getArtifactId();
+    }
+
+    public void setArtifactId(String artifactId) {
+        projectIdentity.setArtifactId(artifactId);
+    }
+
+    public String getVersion() {
+        return projectIdentity.getVersion();
+    }
+
+    public void setVersion(String version) {
+        projectIdentity.setVersion(version);
+    }
+
     public FileCollection getPublishableFiles() {
         return new UnionFileCollection(mavenArtifacts.getFiles(), pomFile);
     }
@@ -119,7 +167,7 @@ public class DefaultMavenPublication implements MavenPublicationInternal {
         return projectIdentity;
     }
 
-    public Set<Dependency> getRuntimeDependencies() {
+    public Set<MavenDependencyInternal> getRuntimeDependencies() {
         return runtimeDependencies;
     }
 
@@ -142,4 +190,8 @@ public class DefaultMavenPublication implements MavenPublicationInternal {
         }
         return "pom";
     }
+
+    public ModuleVersionIdentifier getCoordinates() {
+        return new DefaultModuleVersionIdentifier(getGroupId(), getArtifactId(), getVersion());
+    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java
index 09f4013..848061f 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java
@@ -18,8 +18,8 @@ package org.gradle.api.publish.maven.internal.publication;
 
 import org.gradle.api.Action;
 import org.gradle.api.XmlProvider;
-import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal;
 import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 
 import java.util.Set;
@@ -28,9 +28,7 @@ public interface MavenPomInternal extends MavenPom {
 
     MavenProjectIdentity getProjectIdentity();
 
-    String getPackaging();
-
-    Set<Dependency> getRuntimeDependencies();
+    Set<MavenDependencyInternal> getRuntimeDependencies();
 
     Action<XmlProvider> getXmlAction();
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java
index fb3db51..7d30dee 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java
@@ -16,15 +16,16 @@
 
 package org.gradle.api.publish.maven.internal.publication;
 
-import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.publish.internal.PublicationInternal;
 import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal;
 import org.gradle.api.publish.maven.internal.publisher.MavenNormalizedPublication;
 import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 
 import java.util.Set;
 
-public interface MavenPublicationInternal extends MavenPublication {
+public interface MavenPublicationInternal extends MavenPublication, PublicationInternal {
 
     MavenPomInternal getPom();
 
@@ -34,7 +35,7 @@ public interface MavenPublicationInternal extends MavenPublication {
 
     MavenProjectIdentity getMavenProjectIdentity();
 
-    Set<Dependency> getRuntimeDependencies();
+    Set<MavenDependencyInternal> getRuntimeDependencies();
 
     MavenNormalizedPublication asNormalisedPublication();
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AbstractAntTaskBackedMavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AbstractAntTaskBackedMavenPublisher.java
new file mode 100644
index 0000000..6930e63
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AbstractAntTaskBackedMavenPublisher.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven.internal.publisher;
+
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.Pom;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.publication.maven.internal.ant.EmptyMavenSettingsSupplier;
+import org.gradle.api.publication.maven.internal.ant.MavenSettingsSupplier;
+import org.gradle.api.publish.maven.InvalidMavenPublicationException;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.AntUtil;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Set;
+
+abstract public class AbstractAntTaskBackedMavenPublisher<T extends InstallDeployTaskSupport> implements MavenPublisher {
+    private final Factory<LoggingManagerInternal> loggingManagerFactory;
+
+    private static Logger logger = LoggerFactory.getLogger(AbstractAntTaskBackedMavenPublisher.class);
+    protected final Factory<File> temporaryDirFactory;
+
+    public AbstractAntTaskBackedMavenPublisher(Factory<LoggingManagerInternal> loggingManagerFactory, Factory<File> temporaryDirFactory) {
+        this.loggingManagerFactory = loggingManagerFactory;
+        this.temporaryDirFactory = temporaryDirFactory;
+    }
+
+    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
+        logger.info("Publishing to repository {}", artifactRepository);
+        T deployTask = createDeployTask();
+        deployTask.setProject(AntUtil.createProject());
+
+        MavenSettingsSupplier mavenSettingsSupplier = new EmptyMavenSettingsSupplier();
+        mavenSettingsSupplier.supply(deployTask);
+
+        postConfigure(deployTask, artifactRepository);
+        addPomAndArtifacts(deployTask, publication);
+        execute(deployTask);
+
+        mavenSettingsSupplier.done();
+    }
+
+    abstract protected void postConfigure(T task, MavenArtifactRepository artifactRepository);
+
+    abstract protected T createDeployTask();
+
+    private void addPomAndArtifacts(InstallDeployTaskSupport installOrDeployTask, MavenNormalizedPublication publication) {
+        Pom pom = new Pom();
+        pom.setProject(installOrDeployTask.getProject());
+        pom.setFile(publication.getPomFile());
+        installOrDeployTask.addPom(pom);
+
+        MavenArtifact mainArtifact = determineMainArtifact(publication.getName(), publication.getArtifacts());
+        installOrDeployTask.setFile(mainArtifact == null ? publication.getPomFile() : mainArtifact.getFile());
+
+        for (MavenArtifact mavenArtifact : publication.getArtifacts()) {
+            if (mavenArtifact == mainArtifact) {
+                continue;
+            }
+            AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
+            attachedArtifact.setClassifier(GUtil.elvis(mavenArtifact.getClassifier(), ""));
+            attachedArtifact.setType(GUtil.elvis(mavenArtifact.getExtension(), ""));
+            attachedArtifact.setFile(mavenArtifact.getFile());
+        }
+    }
+
+    private MavenArtifact determineMainArtifact(String publicationName, Set<MavenArtifact> mavenArtifacts) {
+        Set<MavenArtifact> candidateMainArtifacts = CollectionUtils.filter(mavenArtifacts, new Spec<MavenArtifact>() {
+            public boolean isSatisfiedBy(MavenArtifact element) {
+                return element.getClassifier() == null || element.getClassifier().length() == 0;
+            }
+        });
+        if (candidateMainArtifacts.isEmpty()) {
+            return null;
+        }
+        if (candidateMainArtifacts.size() > 1) {
+            throw new InvalidMavenPublicationException(publicationName, "Cannot determine main artifact - multiple artifacts found with empty classifier.");
+        }
+        return candidateMainArtifacts.iterator().next();
+    }
+
+
+    private void execute(InstallDeployTaskSupport deployTask) {
+        LoggingManagerInternal loggingManager = loggingManagerFactory.create();
+        loggingManager.captureStandardOutput(LogLevel.INFO).start();
+        try {
+            deployTask.execute();
+        } finally {
+            loggingManager.stop();
+        }
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenLocalPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenLocalPublisher.java
new file mode 100644
index 0000000..2be6c47
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenLocalPublisher.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven.internal.publisher;
+
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.DefaultArtifactRepository;
+import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.publication.maven.internal.ant.CustomInstallTask;
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+
+import java.io.File;
+
+public class AntTaskBackedMavenLocalPublisher extends AbstractAntTaskBackedMavenPublisher<AntTaskBackedMavenLocalPublisher.MavenLocalInstallTask> {
+    public AntTaskBackedMavenLocalPublisher(Factory<LoggingManagerInternal> loggingManagerFactory, Factory<File> temporaryDirFactory) {
+        super(loggingManagerFactory, temporaryDirFactory);
+    }
+
+    @Override
+    protected void postConfigure(MavenLocalInstallTask task, MavenArtifactRepository artifactRepository) {
+        task.setRepoLocation(artifactRepository.getUrl().toString());
+    }
+
+    @Override
+    protected MavenLocalInstallTask createDeployTask() {
+        return new MavenLocalInstallTask();
+    }
+
+    public static class MavenLocalInstallTask extends CustomInstallTask {
+
+        private String repoLocation;
+
+        private void setRepoLocation(String repoLocation) {
+            this.repoLocation = repoLocation;
+        }
+
+        @Override
+        protected ArtifactRepository createLocalArtifactRepository() {
+            ArtifactRepositoryLayout repositoryLayout = (ArtifactRepositoryLayout) lookup(ArtifactRepositoryLayout.ROLE, getLocalRepository().getLayout());
+            return new DefaultArtifactRepository("local", repoLocation, repositoryLayout);
+        }
+
+        @Override
+        protected void updateRepositoryWithSettings(RemoteRepository repository) {
+            // Do nothing
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java
index 6ef2a0b..a22dbf4 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java
@@ -16,112 +16,53 @@
 
 package org.gradle.api.publish.maven.internal.publisher;
 
-import org.apache.maven.artifact.ant.AttachedArtifact;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-import org.apache.maven.artifact.ant.Pom;
 import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.DefaultArtifactRepository;
+import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.publication.maven.internal.ant.CustomDeployTask;
-import org.gradle.api.publication.maven.internal.ant.EmptyMavenSettingsSupplier;
-import org.gradle.api.publication.maven.internal.ant.MavenSettingsSupplier;
-import org.gradle.api.publication.maven.internal.ant.NoInstallDeployTaskFactory;
-import org.gradle.api.publish.maven.InvalidMavenPublicationException;
-import org.gradle.api.publish.maven.MavenArtifact;
-import org.gradle.api.specs.Spec;
 import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.util.AntUtil;
-import org.gradle.util.CollectionUtils;
-import org.gradle.util.GUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.util.Set;
-
-public class AntTaskBackedMavenPublisher implements MavenPublisher {
-    private final Factory<LoggingManagerInternal> loggingManagerFactory;
-
-    private static Logger logger = LoggerFactory.getLogger(AntTaskBackedMavenPublisher.class);
-    private final Factory<File> temporaryDirFactory;
 
+public class AntTaskBackedMavenPublisher extends AbstractAntTaskBackedMavenPublisher<CustomDeployTask> {
     public AntTaskBackedMavenPublisher(Factory<LoggingManagerInternal> loggingManagerFactory, Factory<File> temporaryDirFactory) {
-        this.loggingManagerFactory = loggingManagerFactory;
-        this.temporaryDirFactory = temporaryDirFactory;
+        super(loggingManagerFactory, temporaryDirFactory);
     }
 
-    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
-        logger.info("Publishing to repository {}", artifactRepository);
-        CustomDeployTask deployTask = createDeployTask();
-
-        MavenSettingsSupplier mavenSettingsSupplier = new EmptyMavenSettingsSupplier();
-        mavenSettingsSupplier.supply(deployTask);
-
-        addRepository(deployTask, artifactRepository);
-        addPomAndArtifacts(deployTask, publication);
-        execute(deployTask);
-
-        mavenSettingsSupplier.done();
+    protected void postConfigure(CustomDeployTask task, MavenArtifactRepository artifactRepository) {
+        addRepository(task, artifactRepository);
     }
 
-    private CustomDeployTask createDeployTask() {
-        Factory<CustomDeployTask> deployTaskFactory = new NoInstallDeployTaskFactory(temporaryDirFactory);
-        CustomDeployTask deployTask = deployTaskFactory.create();
-        deployTask.setProject(AntUtil.createProject());
+    protected CustomDeployTask createDeployTask() {
+        CustomDeployTask deployTask = new DeployTask(temporaryDirFactory);
         deployTask.setUniqueVersion(true);
         return deployTask;
     }
 
     private void addRepository(CustomDeployTask deployTask, MavenArtifactRepository artifactRepository) {
-        RemoteRepository mavenRepository = new MavenDeployerConfigurer(artifactRepository).createRepository();
+        RemoteRepository mavenRepository = new MavenRemoteRepositoryFactory(artifactRepository).create();
         deployTask.addRemoteRepository(mavenRepository);
     }
 
-    private void addPomAndArtifacts(InstallDeployTaskSupport installOrDeployTask, MavenNormalizedPublication publication) {
-        Pom pom = new Pom();
-        pom.setProject(installOrDeployTask.getProject());
-        pom.setFile(publication.getPomFile());
-        installOrDeployTask.addPom(pom);
-
-        MavenArtifact mainArtifact = determineMainArtifact(publication.getName(), publication.getArtifacts());
-        installOrDeployTask.setFile(mainArtifact == null ? publication.getPomFile() : mainArtifact.getFile());
+    private static class DeployTask extends CustomDeployTask {
+        private final Factory<File> tmpDirFactory;
 
-        for (MavenArtifact mavenArtifact : publication.getArtifacts()) {
-            if (mavenArtifact == mainArtifact) {
-                continue;
-            }
-            AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
-            attachedArtifact.setClassifier(GUtil.elvis(mavenArtifact.getClassifier(), ""));
-            attachedArtifact.setType(GUtil.elvis(mavenArtifact.getExtension(), ""));
-            attachedArtifact.setFile(mavenArtifact.getFile());
+        public DeployTask(Factory<File> tmpDirFactory) {
+            this.tmpDirFactory = tmpDirFactory;
         }
-    }
 
-    private MavenArtifact determineMainArtifact(String publicationName, Set<MavenArtifact> mavenArtifacts) {
-        Set<MavenArtifact> candidateMainArtifacts = CollectionUtils.filter(mavenArtifacts, new Spec<MavenArtifact>() {
-            public boolean isSatisfiedBy(MavenArtifact element) {
-                return element.getClassifier() == null || element.getClassifier().length() == 0;
-            }
-        });
-        if (candidateMainArtifacts.isEmpty()) {
-            return null;
-        }
-        if (candidateMainArtifacts.size() > 1) {
-            throw new InvalidMavenPublicationException(publicationName, "Cannot determine main artifact - multiple artifacts found with empty classifier.");
+        @Override
+        protected ArtifactRepository createLocalArtifactRepository() {
+            ArtifactRepositoryLayout repositoryLayout = (ArtifactRepositoryLayout) lookup(ArtifactRepositoryLayout.ROLE, getLocalRepository().getLayout());
+            return new DefaultArtifactRepository("local", tmpDirFactory.create().toURI().toString(), repositoryLayout);
         }
-        return candidateMainArtifacts.iterator().next();
-    }
 
-
-    private void execute(InstallDeployTaskSupport deployTask) {
-            LoggingManagerInternal loggingManager = loggingManagerFactory.create();
-            loggingManager.captureStandardOutput(LogLevel.INFO).start();
-            try {
-                deployTask.execute();
-            } finally {
-                loggingManager.stop();
-            }
+        @Override
+        protected void updateRepositoryWithSettings(RemoteRepository repository) {
+            // Do nothing
         }
-
+    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenDeployerConfigurer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenDeployerConfigurer.java
deleted file mode 100644
index a57c03f..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenDeployerConfigurer.java
+++ /dev/null
@@ -1,55 +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.publish.maven.internal.publisher;
-
-import org.apache.maven.artifact.ant.Authentication;
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.maven.MavenDeployer;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.artifacts.repositories.PasswordCredentials;
-
-class MavenDeployerConfigurer implements Action<MavenDeployer> {
-
-    private final MavenArtifactRepository artifactRepository;
-
-    public MavenDeployerConfigurer(MavenArtifactRepository artifactRepository) {
-        this.artifactRepository = artifactRepository;
-    }
-
-    public void execute(MavenDeployer deployer) {
-        deployer.setRepository(createRepository());
-    }
-
-    public RemoteRepository createRepository() {
-        RemoteRepository remoteRepository = new RemoteRepository();
-        remoteRepository.setUrl(artifactRepository.getUrl().toString());
-
-        PasswordCredentials credentials = artifactRepository.getCredentials();
-        String username = credentials.getUsername();
-        String password = credentials.getPassword();
-
-        if (username != null || password != null) {
-            Authentication authentication = new Authentication();
-            authentication.setUserName(username);
-            authentication.setPassword(password);
-            remoteRepository.addAuthentication(authentication);
-        }
-
-        return remoteRepository;
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemoteRepositoryFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemoteRepositoryFactory.java
new file mode 100644
index 0000000..1f07241
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemoteRepositoryFactory.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.publish.maven.internal.publisher;
+
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.internal.Factory;
+
+class MavenRemoteRepositoryFactory implements Factory<RemoteRepository> {
+
+    private final MavenArtifactRepository artifactRepository;
+
+    public MavenRemoteRepositoryFactory(MavenArtifactRepository artifactRepository) {
+        this.artifactRepository = artifactRepository;
+    }
+
+    public RemoteRepository create() {
+        RemoteRepository remoteRepository = new RemoteRepository();
+        remoteRepository.setUrl(artifactRepository.getUrl().toString());
+
+        PasswordCredentials credentials = artifactRepository.getCredentials();
+        String username = credentials.getUsername();
+        String password = credentials.getPassword();
+
+        if (username != null || password != null) {
+            Authentication authentication = new Authentication();
+            authentication.setUserName(username);
+            authentication.setPassword(password);
+            remoteRepository.addAuthentication(authentication);
+        }
+
+        return remoteRepository;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisher.java
index 5a263d1..36a60c5 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisher.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisher.java
@@ -17,17 +17,18 @@
 package org.gradle.api.publish.maven.internal.publisher;
 
 import org.apache.commons.lang.ObjectUtils;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 import org.gradle.api.publish.internal.PublicationFieldValidator;
 import org.gradle.api.publish.maven.InvalidMavenPublicationException;
 import org.gradle.api.publish.maven.MavenArtifact;
-import org.gradle.internal.UncheckedException;
 import org.gradle.mvn3.org.apache.maven.model.Model;
 import org.gradle.mvn3.org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 import org.gradle.mvn3.org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileReader;
+import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -63,17 +64,26 @@ public class ValidatingMavenPublisher implements MavenPublisher {
     }
 
     private Model parsePomFileIntoMavenModel(MavenNormalizedPublication publication) {
+        File pomFile = publication.getPomFile();
         try {
-            FileReader reader = new FileReader(publication.getPomFile());
-            Model model = new MavenXpp3Reader().read(reader);
-            model.setPomFile(publication.getPomFile());
+            Model model = readModelFromPom(pomFile);
+            model.setPomFile(pomFile);
             return model;
         } catch (XmlPullParserException parseException) {
             throw new InvalidMavenPublicationException(publication.getName(),
                     "POM file is invalid. Check any modifications you have made to the POM file.",
                     parseException);
-        } catch (Exception ex) {
-            throw UncheckedException.throwAsUncheckedException(ex);
+        } catch (IOException ex) {
+            throw new UncheckedIOException(ex);
+        }
+    }
+
+    private Model readModelFromPom(File pomFile) throws IOException, XmlPullParserException {
+        FileReader reader = new FileReader(pomFile);
+        try {
+            return new MavenXpp3Reader().read(reader);
+        } finally {
+            reader.close();
         }
     }
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGenerator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGenerator.java
index 4de704a..fc1a786 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGenerator.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGenerator.java
@@ -23,9 +23,9 @@ import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.XmlProvider;
 import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.xml.XmlTransformer;
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 
 import java.io.File;
 import java.io.IOException;
@@ -39,28 +39,47 @@ public class MavenPomFileGenerator {
     private MavenProject mavenProject = new MavenProject();
     private XmlTransformer xmlTransformer = new XmlTransformer();
 
-    public MavenPomFileGenerator() {
+    public MavenPomFileGenerator(MavenProjectIdentity identity) {
         mavenProject.setModelVersion(POM_VERSION);
+        Model model = getModel();
+        model.setGroupId(identity.getGroupId());
+        model.setArtifactId(identity.getArtifactId());
+        model.setVersion(identity.getVersion());
     }
 
-    public MavenPomFileGenerator setGroupId(String groupId) {
-        getModel().setGroupId(groupId);
+    public MavenPomFileGenerator setPackaging(String packaging) {
+        getModel().setPackaging(packaging);
         return this;
     }
 
-    public MavenPomFileGenerator setArtifactId(String artifactId) {
-        getModel().setArtifactId(artifactId);
-        return this;
+    private Model getModel() {
+        return mavenProject.getModel();
     }
 
-    public MavenPomFileGenerator setVersion(String version) {
-        getModel().setVersion(version);
-        return this;
+    public void addRuntimeDependency(MavenDependencyInternal dependency) {
+        addDependency(dependency, "runtime");
     }
 
-    public MavenPomFileGenerator setPackaging(String packaging) {
-        getModel().setPackaging(packaging);
-        return this;
+    private void addDependency(MavenDependencyInternal mavenDependency, String scope) {
+        if (mavenDependency.getArtifacts().size() == 0) {
+            addDependency(mavenDependency, mavenDependency.getArtifactId(), scope, null, null);
+        } else {
+            for (DependencyArtifact artifact : mavenDependency.getArtifacts()) {
+                addDependency(mavenDependency, artifact.getName(), scope, artifact.getType(), artifact.getClassifier());
+            }
+        }
+    }
+
+    private void addDependency(MavenDependencyInternal dependency, String artifactId, String scope, String type, String classifier) {
+        Dependency mavenDependency = new Dependency();
+        mavenDependency.setGroupId(dependency.getGroupId());
+        mavenDependency.setArtifactId(artifactId);
+        mavenDependency.setVersion(dependency.getVersion());
+        mavenDependency.setType(type);
+        mavenDependency.setScope(scope);
+        mavenDependency.setClassifier(classifier);
+
+        getModel().addDependency(mavenDependency);
     }
 
     public MavenPomFileGenerator withXml(final Action<XmlProvider> action) {
@@ -68,10 +87,6 @@ public class MavenPomFileGenerator {
         return this;
     }
 
-    private Model getModel() {
-        return mavenProject.getModel();
-    }
-
     public MavenPomFileGenerator writeTo(File file) {
         xmlTransformer.transform(file, POM_FILE_ENCODING, new Action<Writer>() {
             public void execute(Writer writer) {
@@ -85,40 +100,4 @@ public class MavenPomFileGenerator {
         return this;
     }
 
-    public void addRuntimeDependency(org.gradle.api.artifacts.Dependency dependency) {
-        if (dependency instanceof ModuleDependency) {
-            addDependency((ModuleDependency) dependency, "runtime");
-        }
-    }
-
-    private void addDependency(ModuleDependency moduleDependency, String scope) {
-        if (moduleDependency.getArtifacts().size() == 0) {
-            getModel().addDependency(createMavenDependency(moduleDependency, moduleDependency.getName(), null, scope, null));
-        } else {
-            for (DependencyArtifact artifact : moduleDependency.getArtifacts()) {
-                getModel().addDependency(createMavenDependency(moduleDependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier()));
-            }
-        }
-    }
-
-    private Dependency createMavenDependency(ModuleDependency dependency, String name, String type, String scope, String classifier) {
-        Dependency mavenDependency =  new Dependency();
-        mavenDependency.setGroupId(dependency.getGroup());
-        if (dependency instanceof ProjectDependency) {
-            mavenDependency.setArtifactId(determineProjectDependencyArtifactId((ProjectDependency) dependency));
-        } else {
-            mavenDependency.setArtifactId(name);
-        }
-        mavenDependency.setVersion(dependency.getVersion());
-        mavenDependency.setType(type);
-        mavenDependency.setScope(scope);
-        mavenDependency.setOptional(false);
-        mavenDependency.setClassifier(classifier);
-        return mavenDependency;
-    }
-
-    private String determineProjectDependencyArtifactId(ProjectDependency dependency) {
-        return dependency.getDependencyProject().getName();
-    }
-
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java
index 3ebb01f..6ce973a 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java
@@ -20,22 +20,20 @@ import org.gradle.api.*;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.publish.internal.ProjectDependencyPublicationResolver;
+import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.api.publish.PublishingExtension;
-import org.gradle.api.publish.internal.PublicationContainerInternal;
-import org.gradle.api.publish.internal.PublicationFactory;
 import org.gradle.api.publish.maven.MavenArtifact;
 import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.internal.MavenPublishTaskModelRule;
 import org.gradle.api.publish.maven.internal.artifact.MavenArtifactNotationParserFactory;
-import org.gradle.api.publish.maven.internal.plugins.GeneratePomTaskCreator;
-import org.gradle.api.publish.maven.internal.plugins.MavenPublishDynamicTaskCreator;
-import org.gradle.api.publish.maven.internal.plugins.MavenPublishLocalDynamicTaskCreator;
 import org.gradle.api.publish.maven.internal.publication.DefaultMavenProjectIdentity;
 import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication;
 import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 import org.gradle.api.publish.plugins.PublishingPlugin;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.model.ModelRules;
 
 import javax.inject.Inject;
 
@@ -52,12 +50,17 @@ public class MavenPublishPlugin implements Plugin<Project> {
     private final Instantiator instantiator;
     private final DependencyMetaDataProvider dependencyMetaDataProvider;
     private final FileResolver fileResolver;
+    private final ModelRules modelRules;
+    private final ProjectDependencyPublicationResolver projectDependencyResolver;
 
     @Inject
-    public MavenPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver) {
+    public MavenPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver, ModelRules modelRules,
+                              ProjectDependencyPublicationResolver projectDependencyResolver) {
         this.instantiator = instantiator;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
         this.fileResolver = fileResolver;
+        this.modelRules = modelRules;
+        this.projectDependencyResolver = projectDependencyResolver;
     }
 
     public void apply(final Project project) {
@@ -65,31 +68,22 @@ public class MavenPublishPlugin implements Plugin<Project> {
 
         final TaskContainer tasks = project.getTasks();
         final Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
-        final Task publishLocalLifecycleTask = tasks.add(PUBLISH_LOCAL_LIFECYCLE_TASK_NAME);
+        final Task publishLocalLifecycleTask = tasks.create(PUBLISH_LOCAL_LIFECYCLE_TASK_NAME);
         publishLocalLifecycleTask.setDescription("Publishes all Maven publications produced by this project to the local Maven cache.");
-        publishLocalLifecycleTask.setGroup("publishing");
+        publishLocalLifecycleTask.setGroup(PublishingPlugin.PUBLISH_TASK_GROUP);
 
+        // Can't move this to rules yet, because it has to happen before user deferred configurable actions
         project.getExtensions().configure(PublishingExtension.class, new Action<PublishingExtension>() {
             public void execute(PublishingExtension extension) {
-                final PublicationContainerInternal publicationContainer = (PublicationContainerInternal) extension.getPublications();
-                publicationContainer.registerFactory(MavenPublication.class, new MavenPublicationFactory(dependencyMetaDataProvider, instantiator, fileResolver));
-
-                // Create generatePom tasks for any Maven publication
-                GeneratePomTaskCreator descriptorGenerationTaskCreator = new GeneratePomTaskCreator(project);
-                descriptorGenerationTaskCreator.monitor(extension.getPublications());
-
-                // Create publish tasks automatically for any Maven publication and repository combinations
-                MavenPublishDynamicTaskCreator publishTaskCreator = new MavenPublishDynamicTaskCreator(tasks, publishLifecycleTask);
-                publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
-
-                // Create install tasks automatically for any Maven publication
-                MavenPublishLocalDynamicTaskCreator publishLocalTaskCreator = new MavenPublishLocalDynamicTaskCreator(tasks, publishLocalLifecycleTask);
-                publishLocalTaskCreator.monitor(extension.getPublications());
+                // Register factory for MavenPublication
+                extension.getPublications().registerFactory(MavenPublication.class, new MavenPublicationFactory(dependencyMetaDataProvider, instantiator, fileResolver));
             }
         });
+
+        modelRules.rule(new MavenPublishTaskModelRule(project, publishLifecycleTask, publishLocalLifecycleTask));
     }
 
-    private class MavenPublicationFactory implements PublicationFactory {
+    private class MavenPublicationFactory implements NamedDomainObjectFactory<MavenPublication> {
         private final Instantiator instantiator;
         private final DependencyMetaDataProvider dependencyMetaDataProvider;
         private final FileResolver fileResolver;
@@ -104,11 +98,11 @@ public class MavenPublishPlugin implements Plugin<Project> {
 
             Module module = dependencyMetaDataProvider.getModule();
             MavenProjectIdentity projectIdentity = new DefaultMavenProjectIdentity(module.getGroup(), module.getName(), module.getVersion());
-            NotationParser<MavenArtifact> artifactNotationParser = new MavenArtifactNotationParserFactory(instantiator, fileResolver).create();
+            NotationParser<Object, MavenArtifact> artifactNotationParser = new MavenArtifactNotationParserFactory(instantiator, fileResolver).create();
 
             return instantiator.newInstance(
                     DefaultMavenPublication.class,
-                    name, projectIdentity, artifactNotationParser, instantiator
+                    name, projectIdentity, artifactNotationParser, instantiator, projectDependencyResolver
             );
         }
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java
index 280fc47..e821dd4 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java
@@ -18,22 +18,20 @@ package org.gradle.api.publish.maven.tasks;
 
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.publish.maven.MavenPom;
-import org.gradle.api.publish.maven.internal.tasks.MavenPomFileGenerator;
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal;
 import org.gradle.api.publish.maven.internal.publication.MavenPomInternal;
-import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+import org.gradle.api.publish.maven.internal.tasks.MavenPomFileGenerator;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
 
 import javax.inject.Inject;
 import java.io.File;
-import java.util.Set;
 
 /**
- * Generates an Ivy XML Module Descriptor file.
+ * Generates a Maven module descriptor (POM) file.
  *
  * @since 1.4
  */
@@ -53,9 +51,9 @@ public class GenerateMavenPom extends DefaultTask {
     }
 
     /**
-     * The Maven pom.
+     * The Maven POM.
      *
-     * @return The Maven pom.
+     * @return The Maven POM.
      */
     public MavenPom getPom() {
         return pom;
@@ -66,9 +64,9 @@ public class GenerateMavenPom extends DefaultTask {
     }
 
     /**
-     * The file the pom will be written to.
+     * The file the POM will be written to.
      *
-     * @return The file the pom will be written to
+     * @return The file the POM will be written to
      */
     @OutputFile
     public File getDestination() {
@@ -90,25 +88,16 @@ public class GenerateMavenPom extends DefaultTask {
     public void doGenerate() {
         MavenPomInternal pomInternal = (MavenPomInternal) getPom();
 
-        MavenPomFileGenerator pomGenerator = new MavenPomFileGenerator();
-        copyIdentity(pomInternal, pomGenerator);
-        copyDependencies(pomInternal.getRuntimeDependencies(), pomGenerator);
+        MavenPomFileGenerator pomGenerator = new MavenPomFileGenerator(pomInternal.getProjectIdentity());
+        pomGenerator.setPackaging(pomInternal.getPackaging());
+
+        for (MavenDependencyInternal runtimeDependency : pomInternal.getRuntimeDependencies()) {
+            pomGenerator.addRuntimeDependency(runtimeDependency);
+        }
+
         pomGenerator.withXml(pomInternal.getXmlAction());
 
         pomGenerator.writeTo(getDestination());
     }
 
-    private void copyIdentity(MavenPomInternal pomInternal, MavenPomFileGenerator pom) {
-        MavenProjectIdentity projectIdentity = pomInternal.getProjectIdentity();
-        pom.setArtifactId(projectIdentity.getArtifactId());
-        pom.setGroupId(projectIdentity.getGroupId());
-        pom.setVersion(projectIdentity.getVersion());
-        pom.setPackaging(pomInternal.getPackaging());
-    }
-
-    private void copyDependencies(Set<Dependency> runtimeDependencies, MavenPomFileGenerator pom) {
-        for (Dependency runtimeDependency : runtimeDependencies) {
-            pom.addRuntimeDependency(runtimeDependency);
-        }
-    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
index a5d2b35..64e87f8 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
@@ -20,7 +20,12 @@ import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.ArtifactRepositoryContainer;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
-import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.api.publish.internal.PublishOperation;
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
+import org.gradle.api.publish.maven.internal.publisher.AntTaskBackedMavenLocalPublisher;
+import org.gradle.api.publish.maven.internal.publisher.MavenPublisher;
+import org.gradle.api.publish.maven.internal.publisher.StaticLockingMavenPublisher;
+import org.gradle.api.publish.maven.internal.publisher.ValidatingMavenPublisher;
 import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
@@ -33,12 +38,13 @@ import javax.inject.Inject;
  */
 @Incubating
 public class PublishToMavenLocal extends PublishToMavenRepository {
+
     private final BaseRepositoryFactory baseRepositoryFactory;
 
     @Inject
-    public PublishToMavenLocal(Factory<LoggingManagerInternal> loggingManagerFactory, DependencyResolutionServices dependencyResolutionServices) {
+    public PublishToMavenLocal(Factory<LoggingManagerInternal> loggingManagerFactory, BaseRepositoryFactory baseRepositoryFactory) {
         super(loggingManagerFactory);
-        this.baseRepositoryFactory = dependencyResolutionServices.getBaseRepositoryFactory();
+        this.baseRepositoryFactory = baseRepositoryFactory;
     }
 
     @Override
@@ -52,4 +58,17 @@ public class PublishToMavenLocal extends PublishToMavenRepository {
 
         return super.getRepository();
     }
+
+    @Override
+    protected void doPublish(final MavenPublicationInternal publication, final MavenArtifactRepository repository) {
+        new PublishOperation(publication, repository) {
+            @Override
+            protected void publish() throws Exception {
+                MavenPublisher antBackedPublisher = new AntTaskBackedMavenLocalPublisher(getLoggingManagerFactory(), getTemporaryDirFactory());
+                MavenPublisher staticLockingPublisher = new StaticLockingMavenPublisher(antBackedPublisher);
+                MavenPublisher validatingPublisher = new ValidatingMavenPublisher(staticLockingPublisher);
+                validatingPublisher.publish(publication.asNormalisedPublication(), repository);
+            }
+        }.run();
+    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
index 62a5166..8544184 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
@@ -139,11 +139,14 @@ public class PublishToMavenRepository extends DefaultTask {
         doPublish(publicationInternal, repository);
     }
 
-    private void doPublish(final MavenPublicationInternal publication, final MavenArtifactRepository repository) {
+    protected Factory<LoggingManagerInternal> getLoggingManagerFactory() {
+        return loggingManagerFactory;
+    }
+
+    protected void doPublish(final MavenPublicationInternal publication, final MavenArtifactRepository repository) {
         new PublishOperation(publication, repository) {
             @Override
             protected void publish() throws Exception {
-                // TODO:DAZ inject this
                 MavenPublisher antBackedPublisher = new AntTaskBackedMavenPublisher(loggingManagerFactory, getTemporaryDirFactory());
                 MavenPublisher staticLockingPublisher = new StaticLockingMavenPublisher(antBackedPublisher);
                 MavenPublisher validatingPublisher = new ValidatingMavenPublisher(staticLockingPublisher);
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
deleted file mode 100644
index a4ac47a..0000000
--- a/subprojects/maven/src/main/resources/META-INF/gradle-plugins/maven2Gradle.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=org.gradle.api.plugins.maven.Maven2GradlePlugin
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
index 43cbe4c..c1872a6 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
@@ -25,9 +25,6 @@ import org.junit.runners.JUnit4;
 
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JUnit4.class)
 public class Conf2ScopeMappingTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
index 1e9a54d..cab7d04 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
@@ -15,19 +15,15 @@
  */
 package org.gradle.api.plugins
 
-import org.gradle.api.internal.project.DefaultProject
-import org.gradle.util.HelperUtil
 import org.gradle.api.artifacts.maven.MavenPom
-
-import spock.lang.Specification
-import org.gradle.api.publication.maven.internal.MavenFactory
+import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.publication.maven.internal.DefaultMavenFactory
+import org.gradle.api.publication.maven.internal.MavenFactory
+import org.gradle.util.TestUtil
+import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class MavenPluginConventionTest extends Specification {
-    DefaultProject project = HelperUtil.createRootProject()
+    DefaultProject project = TestUtil.createRootProject()
     MavenFactory mavenFactory = new DefaultMavenFactory()
     MavenPluginConvention mavenPluginConvention = new MavenPluginConvention(project, mavenFactory)
 
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 3f745d5..f2e03b1 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
@@ -23,7 +23,7 @@ import org.gradle.api.artifacts.maven.MavenResolver;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.tasks.Upload;
-import org.gradle.util.HelperUtil;
+import org.gradle.util.TestUtil;
 
 import java.io.File;
 import java.util.Set;
@@ -33,11 +33,8 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class MavenPluginTest {
-    private final DefaultProject project = HelperUtil.createRootProject();
+    private final DefaultProject project = TestUtil.createRootProject();
 
     @org.junit.Test
     public void addsConventionToProject() {
@@ -115,7 +112,7 @@ public class MavenPluginTest {
         MavenRepositoryHandlerConvention convention = new DslObject(task.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
         assertThat(convention, notNullValue());
 
-        task = project.getTasks().add("customUpload", Upload.class);
+        task = project.getTasks().create("customUpload", Upload.class);
         convention = new DslObject(task.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
         assertThat(convention, notNullValue());
     }
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
deleted file mode 100644
index b94e43a..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/Maven2GradlePluginSpec.groovy
+++ /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.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/MavenProjectXmlWriterTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriterTest.groovy
deleted file mode 100644
index 3af2c89..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriterTest.groovy
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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
-
-/**
- * by Szczepan Faber, created at: 1/21/13
- */
-class MavenProjectXmlWriterTest extends Specification {
-
-    def writer = new MavenProjectXmlWriter()
-
-    def "removes xml element"() {
-        expect:
-        writer.prepareXml('<?xml encoding="UTF-8"?><project/>') == "<project/>"
-        writer.prepareXml('<?xml version="1.0" encoding="UTF-8"?><project/>') == "<project/>"
-        writer.prepareXml('<?xml  version="1.0"  encoding="UTF-8"  ?><project/>') == "<project/>"
-    }
-}
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
deleted file mode 100644
index 457a6c7..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy
+++ /dev/null
@@ -1,118 +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.plugins.maven.internal
-
-import org.gradle.api.GradleException
-import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider
-import org.gradle.api.internal.artifacts.mvnsettings.MavenFileLocations
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 10/15/12
- */
-class MavenProjectsCreatorSpec extends Specification {
-
-    @Rule TestNameTestDirectoryProvider 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/BasePomFilterContainerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainerTest.java
index 46241cf..5561a61 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainerTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainerTest.java
@@ -32,9 +32,6 @@ import java.util.Set;
 
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class BasePomFilterContainerTest {
     private static final String TEST_NAME = "testName";
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
index b903870..32ef920 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
@@ -17,13 +17,13 @@ package org.gradle.api.publication.maven.internal;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
@@ -44,9 +44,6 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultArtifactPomTest {
     private DefaultArtifactPom artifactPom;
     private MavenPom testPom;
@@ -240,7 +237,7 @@ public class DefaultArtifactPomTest {
         if (classifier != null) {
             extraAttributes.put(Dependency.CLASSIFIER, classifier);
         }
-        return new DefaultArtifact(ModuleRevisionId.newInstance("org", name, "1.0"), null, name, type, type, extraAttributes);
+        return new DefaultArtifact(IvyUtil.createModuleRevisionId("org", name, "1.0"), null, name, type, type, extraAttributes);
     }
 
     @Test
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java
index d27e17b..aed0e20 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java
@@ -32,9 +32,6 @@ import static java.util.Arrays.asList;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JUnit4.class)
 public class DefaultConf2ScopeMappingContainerTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy
index 406909a..74b7278 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy
@@ -13,17 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.publication.maven.internal;
-
+package org.gradle.api.publication.maven.internal
 
 import org.gradle.api.artifacts.ConfigurationContainer
-
 import org.gradle.api.internal.file.FileResolver
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 public class DefaultMavenPomFactoryTest extends Specification {
     def createMavenPom() {
         DefaultConf2ScopeMappingContainer scopeMappings = new DefaultConf2ScopeMappingContainer();
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
index 5973ea9..2076474 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
 import org.gradle.api.artifacts.maven.GroovyMavenDeployer
 import org.gradle.api.artifacts.maven.MavenResolver
-import org.gradle.api.internal.Actions
+import org.gradle.internal.Actions
 import org.gradle.api.internal.ClosureBackedAction
 import org.gradle.api.internal.ConfigureByMapAction
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java
index b99e204..d85d793 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java
@@ -26,9 +26,6 @@ import org.junit.runner.RunWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultPomFilterTest {
     private static final String TEST_NAME = "TEST_NAME";
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
deleted file mode 100644
index 734c43b..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
+++ /dev/null
@@ -1,241 +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.publication.maven.internal.ant;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.maven.artifact.ant.AttachedArtifact;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-import org.apache.maven.artifact.ant.Pom;
-import org.apache.maven.settings.Settings;
-import org.apache.tools.ant.Project;
-import org.codehaus.plexus.PlexusContainerException;
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.MavenDeployment;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.artifacts.maven.PublishFilter;
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
-import org.gradle.api.publication.maven.internal.DefaultMavenDeployment;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.util.AntUtil;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.jmock.Expectations;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Set;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractMavenResolverTest {
-    public static final String TEST_NAME = "name";
-    private static final Artifact TEST_IVY_ARTIFACT = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null);
-    private static final File TEST_IVY_FILE = new File("somepom.xml");
-    private static final File TEST_JAR_FILE = new File("somejar.jar");
-    private static final Artifact TEST_ARTIFACT = new DefaultArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null, TEST_NAME, "jar", "jar");
-    protected ArtifactPomContainer artifactPomContainerMock;
-    protected PomFilterContainer pomFilterContainerMock;
-    protected LoggingManagerInternal loggingManagerMock;
-
-    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {
-        {
-            setImposteriser(ClassImposteriser.INSTANCE);
-        }
-    };
-    protected MavenPom pomMock;
-
-    protected Settings mavenSettingsMock;
-
-    protected abstract AbstractMavenResolver getMavenResolver();
-
-    protected abstract InstallDeployTaskSupport getInstallDeployTask();
-
-    protected abstract PomFilterContainer createPomFilterContainerMock();
-
-    @Before
-    public void setUp() {
-        pomFilterContainerMock = createPomFilterContainerMock();
-        artifactPomContainerMock = context.mock(ArtifactPomContainer.class);
-        pomMock = context.mock(MavenPom.class);
-        mavenSettingsMock = context.mock(Settings.class);
-        loggingManagerMock = context.mock(LoggingManagerInternal.class);
-    }
-
-    @Test
-    public void deployOrInstall() throws IOException, PlexusContainerException {
-        getMavenResolver().mavenSettingsSupplier = context.mock(MavenSettingsSupplier.class);
-
-        PublishArtifact classifierArtifact = artifact(new File("classifier.jar"));
-        final DefaultMavenDeployment deployment1 = new DefaultMavenDeployment(artifact(new File("pom1.pom")), artifact(new File("artifact1.jar")), Collections.<PublishArtifact>emptySet());
-        final DefaultMavenDeployment deployment2 = new DefaultMavenDeployment(artifact(new File("pom2.pom")), artifact(new File("artifact2.jar")), WrapUtil.toSet(classifierArtifact));
-        final Set<DefaultMavenDeployment> testDefaultMavenDeployments = WrapUtil.toSet(
-                deployment1,
-                deployment2
-        );
-        final AttachedArtifact attachedArtifact = new AttachedArtifact();
-        @SuppressWarnings("unchecked")
-        final Action<MavenDeployment> action = context.mock(Action.class);
-
-        context.checking(new Expectations() {
-            {
-                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).clearAttachedArtifactsList();
-                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getSettings();
-                will(returnValue(mavenSettingsMock));
-                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getProject();
-                will(returnValue(AntUtil.createProject()));
-                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).createAttach();
-                will(returnValue(attachedArtifact));
-                one(artifactPomContainerMock).addArtifact(TEST_ARTIFACT, TEST_JAR_FILE);
-                allowing(artifactPomContainerMock).createDeployableFilesInfos();
-                will(returnValue(testDefaultMavenDeployments));
-                one(action).execute(deployment1);
-                one(action).execute(deployment2);
-            }
-        });
-
-        getMavenResolver().beforeDeployment(action);
-        getMavenResolver().publish(TEST_IVY_ARTIFACT, TEST_IVY_FILE, true);
-        getMavenResolver().publish(TEST_ARTIFACT, TEST_JAR_FILE, true);
-        checkTransaction(testDefaultMavenDeployments, attachedArtifact, classifierArtifact);
-        assertSame(mavenSettingsMock, getMavenResolver().getSettings());
-    }
-
-    private PublishArtifact artifact(File file) {
-        return new DefaultPublishArtifact("name", "ext", "type", null, null, file);
-    }
-
-    protected void checkTransaction(final Set<DefaultMavenDeployment> defaultMavenDeployments, final AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
-        context.checking(new Expectations() {
-            {
-                one(getInstallDeployTask()).setProject(with(any(Project.class)));
-                for (DefaultMavenDeployment defaultMavenDeployment : defaultMavenDeployments) {
-                    one(getInstallDeployTask()).setFile(defaultMavenDeployment.getMainArtifact().getFile());
-                    one(getInstallDeployTask()).addPom(with(pomMatcher(defaultMavenDeployment.getPomArtifact().getFile(), getInstallDeployTask().getProject())));
-                    one(loggingManagerMock).captureStandardOutput(LogLevel.INFO);
-                    will(returnValue(loggingManagerMock));
-                    one(loggingManagerMock).start();
-                    one(getInstallDeployTask()).execute();
-                    one(loggingManagerMock).stop();
-                    will(returnValue(loggingManagerMock));
-                }
-                one(getMavenResolver().mavenSettingsSupplier).supply(getInstallDeployTask());
-                one(getMavenResolver().mavenSettingsSupplier).done();
-            }
-        });
-        getMavenResolver().commitPublishTransaction();
-        assertThat(attachedArtifact.getFile(), equalTo(classifierArtifact.getFile()));
-        assertThat(attachedArtifact.getType(), equalTo(classifierArtifact.getType()));
-        assertThat(attachedArtifact.getClassifier(), equalTo(classifierArtifact.getClassifier()));
-    }
-
-    private static Matcher<Pom> pomMatcher(final File expectedPomFile, final Project expectedAntProject) {
-        return new BaseMatcher<Pom>() {
-            public void describeTo(Description description) {
-                description.appendText("matching pom");
-            }
-
-            public boolean matches(Object actual) {
-                Pom actualPom = (Pom) actual;
-                return actualPom.getFile().equals(expectedPomFile) && actualPom.getProject().equals(expectedAntProject);
-            }
-        };
-    }
-
-    @Test
-    public void setFilter() {
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).setFilter(publishFilterMock);
-        }});
-        getMavenResolver().setFilter(publishFilterMock);
-    }
-
-    @Test
-    public void getFilter() {
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            allowing(pomFilterContainerMock).getFilter();
-            will(returnValue(publishFilterMock));
-        }});
-        assertSame(publishFilterMock, getMavenResolver().getFilter());
-    }
-
-    @Test
-    public void setPom() {
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).setPom(pomMock);
-        }});
-        getMavenResolver().setPom(pomMock);
-    }
-
-    @Test
-    public void getPom() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterContainerMock).getPom();
-            will(returnValue(pomMock));
-        }});
-        assertSame(pomMock, getMavenResolver().getPom());
-    }
-
-    @Test
-    public void addFilter() {
-        final String testName = "somename";
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).addFilter(testName, publishFilterMock);
-        }});
-        getMavenResolver().addFilter(testName, publishFilterMock);
-    }
-
-    @Test
-    public void filter() {
-        final String testName = "somename";
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).filter(testName);
-            will(returnValue(publishFilterMock));
-        }});
-        assertSame(publishFilterMock, getMavenResolver().filter(testName));
-    }
-
-    @Test
-    public void pom() {
-        final String testName = "somename";
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).pom(testName);
-            will(returnValue(pomMock));
-        }});
-        assertSame(pomMock, getMavenResolver().pom(testName));
-    }
-}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployerTest.java
deleted file mode 100644
index 1c0a32b..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployerTest.java
+++ /dev/null
@@ -1,108 +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.publication.maven.internal.ant;
-
-import org.apache.maven.artifact.ant.AttachedArtifact;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.codehaus.plexus.PlexusContainer;
-import org.codehaus.plexus.PlexusContainerException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.internal.Factory;
-import org.gradle.api.publication.maven.internal.DefaultMavenDeployment;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Set;
-
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class BaseMavenDeployerTest extends AbstractMavenResolverTest {
-
-    private BaseMavenDeployer mavenDeployer = createMavenDeployer();
-
-    @SuppressWarnings("unchecked")
-    private Factory<CustomDeployTask> deployTaskFactoryMock = context.mock(Factory.class);
-    private CustomDeployTask deployTaskMock = context.mock(CustomDeployTask.class);
-
-    private PlexusContainer plexusContainerMock = context.mock(PlexusContainer.class);
-    private RemoteRepository testRepository = new RemoteRepository();
-    private RemoteRepository testSnapshotRepository = new RemoteRepository();
-
-    private Configuration configurationStub = context.mock(Configuration.class);
-
-    protected BaseMavenDeployer createMavenDeployer() {
-        return new BaseMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
-    }
-
-    protected AbstractMavenResolver getMavenResolver() {
-        return mavenDeployer;
-    }
-
-    protected InstallDeployTaskSupport getInstallDeployTask() {
-        return deployTaskMock;
-    }
-
-    protected PomFilterContainer createPomFilterContainerMock() {
-        return context.mock(PomFilterContainer.class);
-    }
-
-    public void setUp() {
-        super.setUp();
-        mavenDeployer = createMavenDeployer();
-        mavenDeployer.setDeployTaskFactory(deployTaskFactoryMock);
-        mavenDeployer.setRepository(testRepository);
-        mavenDeployer.setSnapshotRepository(testSnapshotRepository);
-        mavenDeployer.setConfiguration(configurationStub);
-        mavenDeployer.setUniqueVersion(false);
-    }
-
-    protected void checkTransaction(final Set<DefaultMavenDeployment> defaultMavenDeployments, AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
-        final Set<File> protocolJars = WrapUtil.toLinkedSet(new File("jar1"), new File("jar1"));
-        context.checking(new Expectations() {{
-                allowing(configurationStub).resolve();
-                will(returnValue(protocolJars));
-                allowing(deployTaskFactoryMock).create();
-                will(returnValue(getInstallDeployTask()));
-                allowing(deployTaskMock).getContainer();
-                will(returnValue(plexusContainerMock));
-                for (File protocolProviderJar : protocolJars) {
-                    one(plexusContainerMock).addJarResource(protocolProviderJar);
-                }
-                one(deployTaskMock).setUniqueVersion(mavenDeployer.isUniqueVersion());
-                one(deployTaskMock).addRemoteRepository(testRepository);
-                one(deployTaskMock).addRemoteSnapshotRepository(testSnapshotRepository);
-        }});
-        super.checkTransaction(defaultMavenDeployments, attachedArtifact, classifierArtifact);
-    }
-
-    @Test
-    public void init() {
-        mavenDeployer = new BaseMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
-        assertTrue(mavenDeployer.isUniqueVersion());
-    }
-
-}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstallerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstallerTest.java
deleted file mode 100644
index 4db25cb..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstallerTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.publication.maven.internal.ant;
-
-import org.apache.maven.artifact.ant.AttachedArtifact;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-import org.codehaus.plexus.PlexusContainerException;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.internal.Factory;
-import org.gradle.api.publication.maven.internal.DefaultMavenDeployment;
-import org.jmock.Expectations;
-
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class BaseMavenInstallerTest extends AbstractMavenResolverTest {
-    private BaseMavenInstaller mavenInstaller;
-
-    @SuppressWarnings("unchecked")
-    private Factory<CustomInstallTask> installTaskFactoryMock = context.mock(Factory.class);
-    private CustomInstallTask installTaskMock;
-
-    protected BaseMavenInstaller createMavenInstaller() {
-        return new BaseMavenInstaller(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
-    }
-
-    protected PomFilterContainer createPomFilterContainerMock() {
-        return context.mock(PomFilterContainer.class);
-    }
-
-    protected AbstractMavenResolver getMavenResolver() {
-        return mavenInstaller;
-    }
-
-    protected InstallDeployTaskSupport getInstallDeployTask() {
-        return installTaskMock;
-    }
-
-    public void setUp() {
-        super.setUp();
-        installTaskMock = context.mock(CustomInstallTask.class);
-        mavenInstaller = createMavenInstaller();
-        mavenInstaller.setInstallTaskFactory(installTaskFactoryMock);
-    }
-
-    protected void checkTransaction(final Set<DefaultMavenDeployment> deployableUnits, AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
-        context.checking(new Expectations() {
-            {
-                allowing(installTaskFactoryMock).create();
-                will(returnValue(getInstallDeployTask()));
-            }
-        });
-        super.checkTransaction(deployableUnits, attachedArtifact, classifierArtifact);
-    }
-}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactoryTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactoryTest.java
deleted file mode 100644
index fc35ae1..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactoryTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.publication.maven.internal.ant;
-
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultDeployTaskFactoryTest {
-    @Test
-    public void create() {
-        assertTrue(new DefaultDeployTaskFactory().create() instanceof CustomDeployTask);
-    }
-}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java
index fe1f57b..8746c9f 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java
@@ -23,9 +23,6 @@ import org.junit.Test;
 import static junit.framework.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultExcludeRuleConverterTest {
     private static final String TEST_ORG = "org";
     private static final String TEST_MODULE = "module";
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy
index bf9d6a8..dc3ac19 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy
@@ -17,32 +17,26 @@
 package org.gradle.api.publication.maven.internal.ant
 
 import org.gradle.api.artifacts.maven.PomFilterContainer
-
-import org.junit.Before
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.util.JUnit4GroovyMockery
 import org.junit.Test
 import org.junit.runner.RunWith
+
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 @RunWith (org.jmock.integration.junit4.JMock.class)
-class DefaultGroovyMavenDeployerTest extends BaseMavenDeployerTest {
-    private DefaultGroovyMavenDeployer groovyMavenDeployer;
+class DefaultGroovyMavenDeployerTest {
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    protected ArtifactPomContainer artifactPomContainerMock = context.mock(ArtifactPomContainer)
+    protected PomFilterContainer pomFilterContainerMock = context.mock(PomFilterContainer);
+    protected LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal);
+    private DefaultGroovyMavenDeployer groovyMavenDeployer = new DefaultGroovyMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock)
 
     protected PomFilterContainer createPomFilterContainerMock() {
         context.mock(PomFilterContainer.class);
     }
 
-    protected BaseMavenDeployer createMavenDeployer() {
-        groovyMavenDeployer = new DefaultGroovyMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock)
-    }
-
-    @Before
-    void setUp() {
-        super.setUp();
-    }
-
     @Test
     void repositoryBuilder() {
         checkRepositoryBuilder(DefaultGroovyMavenDeployer.REPOSITORY_BUILDER)
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy
index be6ce92..117fd73 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.publication.maven.internal.ant
 
-import java.lang.reflect.Proxy
 import org.gradle.api.artifacts.maven.MavenPom
 import org.gradle.api.artifacts.maven.PomFilterContainer
 import org.gradle.api.artifacts.maven.PublishFilter
@@ -29,11 +28,11 @@ import org.jmock.integration.junit4.JMock
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+
+import java.lang.reflect.Proxy
+
 import static org.junit.Assert.assertSame
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock)
 class DefaultGroovyPomFilterContainerTest extends BasePomFilterContainerTest {
     static final String TEST_NAME = "somename"
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java
index 6afac33..9a668fa 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java
@@ -41,9 +41,6 @@ import static org.gradle.util.WrapUtil.*;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class DefaultPomDependenciesConverterTest {
     private JUnit4Mockery context = new JUnit4GroovyMockery();
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy
index eafd010..6c92ac7 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.api.publication.maven.internal.ant
 import org.apache.maven.artifact.ant.InstallDeployTaskSupport
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, created at: 3/29/11
- */
 class EmptyMavenSettingsSupplierTest extends Specification {
 
     def EmptyMavenSettingsSupplier supplier = new EmptyMavenSettingsSupplier()
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy
index 9906e0a..eb24887 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy
@@ -18,12 +18,8 @@ package org.gradle.api.publication.maven.internal.ant
 
 import org.apache.maven.artifact.ant.InstallDeployTaskSupport
 import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenFileLocations
-
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber, created at: 3/29/11
- */
 class MaybeUserMavenSettingsSupplierTest extends Specification {
 
     InstallDeployTaskSupport support = Mock()
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
index d3701ec..0f3a272 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
@@ -16,17 +16,16 @@
 
 package org.gradle.api.publication.maven.internal.ant
 
-import org.gradle.util.HelperUtil
 import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency
 import org.gradle.api.plugins.BasePlugin
 import org.gradle.api.plugins.MavenPlugin
-
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 import spock.lang.Issue
 
 @Issue("GRADLE-443")
 class ProjectDependencyArtifactIdExtractorHackTest extends Specification {
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
     def extractor = new ProjectDependencyArtifactIdExtractorHack(new DefaultProjectDependency(project, null, true))
 
     def "artifact ID defaults to project name if neither archivesBaseName nor mavenDeployer.pom.artifactId is configured"() {
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy
index 1161a7e..73f5094 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy
@@ -19,13 +19,13 @@ package org.gradle.api.publish.maven.internal.artifact
 import org.gradle.api.Task
 import org.gradle.api.artifacts.PublishArtifact
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.internal.typeconversion.NotationParser
 import org.gradle.api.publish.maven.MavenArtifact
 import org.gradle.api.tasks.TaskDependency
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 public class MavenArtifactNotationParserFactoryTest extends Specification {
@@ -41,7 +41,7 @@ public class MavenArtifactNotationParserFactoryTest extends Specification {
     def task = Mock(Task)
     def dependencies = Collections.singleton(Mock(Task))
 
-    NotationParser<MavenArtifact> parser
+    NotationParser<Object, MavenArtifact> parser
 
     def "setup"() {
         def fileResolver = Stub(FileResolver) {
@@ -119,7 +119,7 @@ public class MavenArtifactNotationParserFactoryTest extends Specification {
 
     def "creates MavenArtifact for ArchivePublishArtifact"() {
         when:
-        def rootProject = HelperUtil.createRootProject()
+        def rootProject = TestUtil.createRootProject()
         def archive = rootProject.task(type: Jar, {})
         archive.setBaseName("baseName")
         archive.setExtension(archiveExtension)
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy
index 06a9c32..f9a9987 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy
@@ -14,21 +14,25 @@
  * limitations under the License.
  */
 package org.gradle.api.publish.maven.internal.publication
+
 import org.gradle.api.Action
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Task
-import org.gradle.api.artifacts.DependencySet
+import org.gradle.api.artifacts.DependencyArtifact
 import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.ProjectDependency
 import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.api.artifacts.PublishArtifactSet
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.component.SoftwareComponentInternal
 import org.gradle.api.internal.component.Usage
 import org.gradle.api.internal.file.collections.SimpleFileCollection
-import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.publish.internal.ProjectDependencyPublicationResolver
+import org.gradle.api.publish.internal.PublicationInternal
 import org.gradle.api.publish.maven.MavenArtifact
 import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity
 import org.gradle.api.tasks.TaskDependency
 import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.typeconversion.NotationParser
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
@@ -38,7 +42,8 @@ import spock.lang.Specification
 public class DefaultMavenPublicationTest extends Specification {
     @Shared TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
     def module = Mock(MavenProjectIdentity)
-    NotationParser<MavenArtifact> notationParser = Mock(NotationParser)
+    NotationParser<Object, MavenArtifact> notationParser = Mock(NotationParser)
+    def projectDependencyResolver = Mock(ProjectDependencyPublicationResolver)
     TestFile pomDir
     TestFile pomFile
     File artifactFile
@@ -61,7 +66,40 @@ public class DefaultMavenPublicationTest extends Specification {
 
         then:
         publication.name == "pub-name"
-        publication.mavenProjectIdentity == module
+        publication.mavenProjectIdentity.groupId == "group"
+        publication.mavenProjectIdentity.artifactId == "name"
+        publication.mavenProjectIdentity.version == "version"
+    }
+
+    def "changing publication coordinates does not effect those provided"() {
+        when:
+        module.artifactId >> "name"
+        module.groupId >> "group"
+        module.version >> "version"
+
+        and:
+        def publication = createPublication()
+
+        and:
+        publication.groupId = "group2"
+        publication.artifactId = "name2"
+        publication.version = "version2"
+
+        then:
+        module.groupId == "group"
+        module.artifactId == "name"
+        module.version == "version"
+
+        and:
+        publication.groupId == "group2"
+        publication.artifactId == "name2"
+        publication.version == "version2"
+
+        and:
+        publication.mavenProjectIdentity.groupId == "group2"
+        publication.mavenProjectIdentity.artifactId == "name2"
+        publication.mavenProjectIdentity.version == "version2"
+
     }
 
     def "packaging is taken from first added artifact without extension"() {
@@ -88,32 +126,25 @@ public class DefaultMavenPublicationTest extends Specification {
         publication.runtimeDependencies.empty
     }
 
-    def "artifacts and dependencies are taken from added component"() {
+    def "artifacts are taken from added component"() {
         given:
         def publication = createPublication()
-        def component = Mock(SoftwareComponentInternal)
-        def usage = Mock(Usage)
         def artifact = Mock(PublishArtifact)
-        def dependency = Mock(ModuleDependency)
         def publishArtifactDependencies = Mock(TaskDependency)
 
         def mavenArtifact = Mock(MavenArtifact)
 
         when:
-        component.usages >> [usage]
-        usage.artifacts >> [artifact]
-        usage.dependencies >> [dependency]
-
         notationParser.parseNotation(artifact) >> mavenArtifact
         mavenArtifact.file >> artifactFile
 
         and:
-        publication.from(component)
+        publication.from(componentWithArtifact(artifact))
 
         then:
         publication.publishableFiles.files == [pomFile, artifactFile] as Set
         publication.artifacts == [mavenArtifact] as Set
-        publication.runtimeDependencies == [dependency] as Set
+        publication.runtimeDependencies.empty
 
         when:
         def task = Mock(Task)
@@ -124,25 +155,58 @@ public class DefaultMavenPublicationTest extends Specification {
         publication.publishableFiles.buildDependencies.getDependencies(task) == [task] as Set
     }
 
-    def "cannot add multiple components"() {
+    def "adopts module dependency from added component"() {
         given:
         def publication = createPublication()
-        def component = Mock(SoftwareComponentInternal)
-        def usage = Mock(Usage)
-        def publishArtifactSet = Mock(PublishArtifactSet)
-        def dependencySet = Mock(DependencySet)
+        def moduleDependency = Mock(ModuleDependency)
+        def artifact = Mock(DependencyArtifact)
 
         when:
-        publication.from(component)
+        moduleDependency.group >> "group"
+        moduleDependency.name >> "name"
+        moduleDependency.version >> "version"
+        moduleDependency.artifacts >> [artifact]
+
+        and:
+        publication.from(componentWithDependency(moduleDependency))
 
         then:
-        component.usages >> [usage]
-        usage.artifacts >> publishArtifactSet
-        publishArtifactSet.iterator() >> [].iterator()
-        usage.dependencies >> dependencySet
-        dependencySet.iterator() >> [].iterator()
+        publication.runtimeDependencies.size() == 1
+        with (publication.runtimeDependencies.asList().first()) {
+            groupId == "group"
+            artifactId == "name"
+            version == "version"
+            artifacts == [artifact]
+        }
+    }
+
+    def "maps project dependency to ivy dependency"() {
+        given:
+        def publication = createPublication()
+        def projectDependency = Mock(ProjectDependency)
+
+        and:
+        projectDependencyResolver.resolve(projectDependency) >> DefaultModuleVersionIdentifier.newId("pub-group", "pub-name", "pub-version")
 
         when:
+        publication.from(componentWithDependency(projectDependency))
+
+        then:
+        publication.runtimeDependencies.size() == 1
+        with (publication.runtimeDependencies.asList().first()) {
+            groupId == "pub-group"
+            artifactId == "pub-name"
+            version == "pub-version"
+            artifacts == []
+        }
+    }
+
+    def "cannot add multiple components"() {
+        given:
+        def publication = createPublication()
+
+        when:
+        publication.from(createComponent([], []))
         publication.from(Mock(SoftwareComponentInternal))
 
         then:
@@ -219,7 +283,7 @@ public class DefaultMavenPublicationTest extends Specification {
     }
 
     def createPublication() {
-        def publication = new DefaultMavenPublication("pub-name", module, notationParser, new DirectInstantiator())
+        def publication = new DefaultMavenPublication("pub-name", module, notationParser, new DirectInstantiator(), projectDependencyResolver)
         publication.setPomFile(new SimpleFileCollection(pomFile))
         return publication;
     }
@@ -230,4 +294,31 @@ public class DefaultMavenPublicationTest extends Specification {
         }
         return artifact
     }
+
+    def componentWithDependency(ModuleDependency dependency) {
+        return createComponent([], [dependency])
+    }
+
+    def componentWithArtifact(def artifact) {
+        return createComponent([artifact], [])
+    }
+
+    def createComponent(def artifacts, def dependencies) {
+        def usage = Stub(Usage) {
+            getName() >> "runtime"
+            getArtifacts() >> artifacts
+            getDependencies() >> dependencies
+        }
+        def component = Stub(SoftwareComponentInternal) {
+            getUsages() >> [usage]
+        }
+        return component
+    }
+
+    def otherPublication(String name, String group, String artifactId, String version) {
+        def pub = Mock(PublicationInternal)
+        pub.name >> name
+        pub.coordinates >> new DefaultModuleVersionIdentifier(group, artifactId, version)
+        return pub
+    }
 }
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisherTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisherTest.groovy
index 9d3831a..092c4bf 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisherTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisherTest.groovy
@@ -37,8 +37,8 @@ public class ValidatingMavenPublisherTest extends Specification {
 
     def "delegates when publication is valid"() {
         when:
-        def projectIdentity = projectIdentity("the-group", "the-artifact", "the-version")
-        def publication = new MavenNormalizedPublication("pub-name", createPomFile("the-group", "the-artifact", "the-version"), projectIdentity, emptySet())
+        def projectIdentity = makeProjectIdentity("the-group", "the-artifact", "the-version")
+        def publication = new MavenNormalizedPublication("pub-name", createPomFile(projectIdentity), projectIdentity, emptySet())
         def repository = Mock(MavenArtifactRepository)
 
         and:
@@ -50,8 +50,8 @@ public class ValidatingMavenPublisherTest extends Specification {
 
     def "validates project coordinates"() {
         given:
-        def projectIdentity = projectIdentity(groupId, artifactId, version)
-        def publication = new MavenNormalizedPublication("pub-name", createPomFile(groupId, artifactId, version), projectIdentity, emptySet())
+        def projectIdentity = makeProjectIdentity(groupId, artifactId, version)
+        def publication = new MavenNormalizedPublication("pub-name", createPomFile(projectIdentity), projectIdentity, emptySet())
 
         def repository = Mock(MavenArtifactRepository)
 
@@ -78,8 +78,8 @@ public class ValidatingMavenPublisherTest extends Specification {
 
     def "project coordinates must match POM file"() {
         given:
-        def projectIdentity = projectIdentity("group", "artifact", "version")
-        def pomFile = createPomFile(groupId, artifactId, version)
+        def projectIdentity = makeProjectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(makeProjectIdentity(groupId, artifactId, version))
         def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, emptySet())
 
         def repository = Mock(MavenArtifactRepository)
@@ -99,8 +99,8 @@ public class ValidatingMavenPublisherTest extends Specification {
     }
 
     def "validates artifact attributes"() {
-        def projectIdentity = projectIdentity("group", "artifact", "version")
-        def pomFile = createPomFile("group", "artifact", "version")
+        def projectIdentity = makeProjectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(projectIdentity)
         def mavenArtifact = Stub(MavenArtifact) {
             getExtension() >> extension
             getClassifier() >> classifier
@@ -126,8 +126,8 @@ public class ValidatingMavenPublisherTest extends Specification {
 
     @Unroll
     def "cannot publish with file that #message"() {
-        def projectIdentity = projectIdentity("group", "artifact", "version")
-        def pomFile = createPomFile("group", "artifact", "version")
+        def projectIdentity = makeProjectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(projectIdentity)
         def mavenArtifact = Mock(MavenArtifact)
         def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([mavenArtifact]))
 
@@ -160,8 +160,8 @@ public class ValidatingMavenPublisherTest extends Specification {
             getClassifier() >> "classified"
             getFile() >> testDir.createFile('artifact2')
         }
-        def projectIdentity = projectIdentity("group", "artifact", "version")
-        def pomFile = createPomFile("group", "artifact", "version")
+        def projectIdentity = makeProjectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(projectIdentity)
         def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([artifact1, artifact2]))
 
         when:
@@ -179,8 +179,8 @@ public class ValidatingMavenPublisherTest extends Specification {
             getClassifier() >> null
             getFile() >> testDir.createFile('artifact1')
         }
-        def projectIdentity = projectIdentity("group", "artifact", "version")
-        def pomFile = createPomFile("group", "artifact", "version")
+        def projectIdentity = makeProjectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(projectIdentity)
         def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([artifact1]))
 
         when:
@@ -193,8 +193,8 @@ public class ValidatingMavenPublisherTest extends Specification {
 
     def "supplied POM file must be valid"() {
         given:
-        def projectIdentity = projectIdentity("group", "artifact", "version")
-        def pomFile = createPomFile("group", "artifact", "version", new Action<XmlProvider>() {
+        def projectIdentity = makeProjectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(projectIdentity, new Action<XmlProvider>() {
             void execute(XmlProvider xml) {
                 xml.asNode().appendNode("invalid", "This is not a valid pomFile element")
             }
@@ -213,7 +213,7 @@ public class ValidatingMavenPublisherTest extends Specification {
         e.cause.message =~ "Unrecognised tag: 'invalid' .*"
     }
 
-    private def projectIdentity(def groupId, def artifactId, def version) {
+    private def makeProjectIdentity(def groupId, def artifactId, def version) {
         return Stub(MavenProjectIdentity) {
             getGroupId() >> groupId
             getArtifactId() >> artifactId
@@ -221,12 +221,9 @@ public class ValidatingMavenPublisherTest extends Specification {
         }
     }
 
-    private def createPomFile(def groupId, def artifactId, def version, Action<XmlProvider> withXmlAction = null) {
+    private def createPomFile(MavenProjectIdentity projectIdentity, Action<XmlProvider> withXmlAction = null) {
         def pomFile = testDir.file("pom")
-        MavenPomFileGenerator pomFileGenerator = new MavenPomFileGenerator();
-        pomFileGenerator.groupId = groupId
-        pomFileGenerator.artifactId = artifactId
-        pomFileGenerator.version = version
+        MavenPomFileGenerator pomFileGenerator = new MavenPomFileGenerator(projectIdentity);
         if (withXmlAction != null) {
             pomFileGenerator.withXml(withXmlAction)
         }
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy
index 4f9de23..baf67ab 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy
@@ -15,13 +15,11 @@
  */
 
 package org.gradle.api.publish.maven.internal.tasks
-
 import org.gradle.api.Action
-import org.gradle.api.Project
 import org.gradle.api.XmlProvider
 import org.gradle.api.artifacts.DependencyArtifact
-import org.gradle.api.artifacts.ModuleDependency
-import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.publish.maven.internal.dependencies.MavenDependencyInternal
+import org.gradle.api.publish.maven.internal.publication.DefaultMavenProjectIdentity
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
@@ -31,7 +29,8 @@ import spock.lang.Specification
 
 class MavenPomFileGeneratorTest extends Specification {
     TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
-    MavenPomFileGenerator generator = new MavenPomFileGenerator()
+    def projectIdentity = new DefaultMavenProjectIdentity("group-id", "artifact-id", "1.0")
+    MavenPomFileGenerator generator = new MavenPomFileGenerator(projectIdentity)
 
     def "writes correct prologue and schema declarations"() {
         expect:
@@ -42,38 +41,32 @@ class MavenPomFileGeneratorTest extends Specification {
 """))
     }
 
-    def "writes empty pom with default values"() {
+    def "writes configured coordinates"() {
         expect:
         with (pom) {
-            modelVersion == "4.0.0"
-            groupId == "unknown"
-            artifactId == "empty-project"
-            version == "0"
-            dependencies.empty
+            groupId == "group-id"
+            artifactId == "artifact-id"
+            version == "1.0"
+            packaging.empty
         }
     }
 
-    def "writes configured coordinates"() {
+    def "writes packaging"() {
         when:
-        generator.groupId = "group-id"
-        generator.artifactId = "artifact-id"
-        generator.version = "1.0"
         generator.packaging = "pom"
 
         then:
         with (pom) {
-            groupId == "group-id"
-            artifactId == "artifact-id"
-            version == "1.0"
             packaging == "pom"
         }
     }
 
     def "encodes coordinates for XML and unicode"() {
         when:
-        generator.groupId = 'group-ぴ₦ガき∆ç√∫'
-        generator.artifactId = 'artifact-<tag attrib="value"/>-markup'
-        generator.version = 'version-&"'
+        def groupId = 'group-ぴ₦ガき∆ç√∫'
+        def artifactId = 'artifact-<tag attrib="value"/>-markup'
+        def version = 'version-&"'
+        generator = new MavenPomFileGenerator(new DefaultMavenProjectIdentity(groupId, artifactId, version))
 
         then:
         with (pom) {
@@ -84,14 +77,14 @@ class MavenPomFileGeneratorTest extends Specification {
     }
 
     def "writes regular dependency"() {
-        def dependency = Mock(ModuleDependency)
+        def dependency = Mock(MavenDependencyInternal)
         when:
         generator.addRuntimeDependency(dependency)
 
         then:
         dependency.artifacts >> new HashSet<DependencyArtifact>()
-        dependency.group >> "dep-group"
-        dependency.name >> "dep-name"
+        dependency.groupId >> "dep-group"
+        dependency.artifactId >> "dep-name"
         dependency.version >> "dep-version"
 
         and:
@@ -106,33 +99,8 @@ class MavenPomFileGeneratorTest extends Specification {
         }
     }
 
-    def "writes project dependency"() {
-        def dependency = Mock(ProjectDependency)
-        when:
-        generator.addRuntimeDependency(dependency)
-
-        then:
-        dependency.artifacts >> new HashSet<DependencyArtifact>()
-        dependency.group >> "dep-group"
-        dependency.version >> "dep-version"
-        dependency.dependencyProject >> Stub(Project) {
-            getName() >> "project-name"
-        }
-
-        and:
-        with (pom) {
-            dependencies.dependency.size() == 1
-            with (dependencies[0].dependency[0]) {
-                groupId == "dep-group"
-                artifactId == "project-name"
-                version == "dep-version"
-                scope == "runtime"
-            }
-        }
-    }
-
     def "writes dependency with artifacts"() {
-        def dependency = Mock(ModuleDependency)
+        def dependency = Mock(MavenDependencyInternal)
         def artifact1 = Mock(DependencyArtifact)
         def artifact2 = Mock(DependencyArtifact)
         
@@ -141,7 +109,7 @@ class MavenPomFileGeneratorTest extends Specification {
 
         then:
         dependency.artifacts >> CollectionUtils.toSet([artifact1, artifact2])
-        dependency.group >> "dep-group"
+        dependency.groupId >> "dep-group"
         dependency.version >> "dep-version"
         artifact1.name >> "artifact-1"
         artifact1.type >> "type-1"
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
index 46a1c02..dba3720 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
@@ -15,22 +15,23 @@
  */
 
 package org.gradle.api.publish.maven.plugins
+
 import org.gradle.api.artifacts.ArtifactRepositoryContainer
 import org.gradle.api.artifacts.PublishArtifactSet
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory
 import org.gradle.api.internal.component.SoftwareComponentInternal
 import org.gradle.api.publish.PublishingExtension
 import org.gradle.api.publish.maven.MavenPublication
 import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication
 import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
 import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class MavenPublishPluginTest extends Specification {
 
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
     PublishingExtension publishing
     def componentArtifacts = Mock(FileCollection)
     def component = Stub(SoftwareComponentInternal)
@@ -55,7 +56,7 @@ class MavenPublishPluginTest extends Specification {
 
     def "publication can be added"() {
         when:
-        publishing.publications.add("test", MavenPublication)
+        publishing.publications.create("test", MavenPublication)
 
         then:
         publishing.publications.size() == 1
@@ -64,8 +65,9 @@ class MavenPublishPluginTest extends Specification {
 
     def "creates publish tasks for publication and repository"() {
         when:
-        publishing.publications.add("test", MavenPublication)
+        publishing.publications.create("test", MavenPublication)
         publishing.repositories { maven { url = "http://foo.com" } }
+        closeTaskContainer()
 
         then:
         project.tasks["publishTestPublicationToMavenRepository"] != null
@@ -75,21 +77,23 @@ class MavenPublishPluginTest extends Specification {
 
     def "task is created for publishing to mavenLocal"() {
         given:
-        publishing.publications.add("test", MavenPublication)
+        publishing.publications.create("test", MavenPublication)
+        closeTaskContainer()
 
         expect:
         publishLocalTasks.size() == 1
         publishLocalTasks.first().name == "publishTestPublicationToMavenLocal"
         publishLocalTasks.first().repository.name == ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
-        publishLocalTasks.first().repository.url == project.getServices().get(DependencyResolutionServices).baseRepositoryFactory.createMavenLocalRepository().url
+        publishLocalTasks.first().repository.url == project.getServices().get(BaseRepositoryFactory).createMavenLocalRepository().url
     }
 
     def "can explicitly add mavenLocal as a publishing repository"() {
         given:
-        publishing.publications.add("test", MavenPublication)
+        publishing.publications.create("test", MavenPublication)
 
         when:
         def mavenLocal = publishing.repositories.mavenLocal()
+        closeTaskContainer()
 
         then:
         publishTasks.size() == 1
@@ -101,30 +105,18 @@ class MavenPublishPluginTest extends Specification {
 
     def "tasks are created for compatible publication / repo"() {
         given:
-        publishing.publications.add("test", MavenPublication)
-
-        expect:
-        publishTasks.size() == 0
+        publishing.publications.create("test", MavenPublication)
 
         when:
         def repo1 = publishing.repositories.maven { url "foo" }
-
-        then:
-        publishTasks.size() == 1
-        publishTasks.last().repository.is(repo1)
-        publishTasks.last().name == "publishTestPublicationToMavenRepository"
-
-        when:
-        publishing.repositories.ivy {}
-
-        then:
-        publishTasks.size() == 1
-
-        when:
         def repo2 = publishing.repositories.maven { url "foo"; name "other" }
+        publishing.repositories.ivy {}
+        closeTaskContainer()
 
         then:
         publishTasks.size() == 2
+        publishTasks.first().repository.is(repo1)
+        publishTasks.first().name == "publishTestPublicationToMavenRepository"
         publishTasks.last().repository.is(repo2)
         publishTasks.last().name == "publishTestPublicationToOtherRepository"
     }
@@ -133,6 +125,10 @@ class MavenPublishPluginTest extends Specification {
         project.tasks.withType(PublishToMavenLocal).sort { it.name }
     }
 
+    void closeTaskContainer() {
+        project.modelRegistry.get("tasks", Object)
+    }
+
     List<PublishToMavenRepository> getPublishTasks() {
         def allTasks = project.tasks.withType(PublishToMavenRepository).sort { it.name }
         allTasks.removeAll(publishLocalTasks)
@@ -145,7 +141,7 @@ class MavenPublishPluginTest extends Specification {
         project.version = "version"
 
         and:
-        publishing.publications.add("test", MavenPublication)
+        publishing.publications.create("test", MavenPublication)
 
         then:
         with(publishing.publications.test.mavenProjectIdentity) {
@@ -166,7 +162,8 @@ class MavenPublishPluginTest extends Specification {
 
     def "pom dir moves with build dir"() {
         when:
-        publishing.publications.add("test", MavenPublication)
+        publishing.publications.create("test", MavenPublication)
+        closeTaskContainer()
 
         then:
         project.tasks["generatePomFileForTestPublication"].destination == new File(project.buildDir, "publications/test/pom-default.xml")
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy
index 0167cfb..b673d71 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy
@@ -16,16 +16,16 @@
 
 package org.gradle.api.publish.maven.tasks
 
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class PublishToMavenRepositoryTest extends Specification {
 
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
 
     def "can instantiate"() {
         when:
-        project.tasks.add("task", PublishToMavenRepository)
+        project.tasks.create("task", PublishToMavenRepository)
 
         then:
         true
diff --git a/subprojects/messaging/messaging.gradle b/subprojects/messaging/messaging.gradle
index 48123b4..6e40f2e 100644
--- a/subprojects/messaging/messaging.gradle
+++ b/subprojects/messaging/messaging.gradle
@@ -1,9 +1,9 @@
 dependencies {
-    groovy libraries.groovy
     publishCompile project(':baseServices')
     publishCompile libraries.slf4j_api
     publishCompile libraries.guava
     publishCompile 'com.esotericsoftware.kryo:kryo:2.20'
+    testCompile libraries.groovy
 }
 
 useTestFixtures()
diff --git a/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy
index 87947a9..a715529 100644
--- a/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy
+++ b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.messaging.remote
 
 import org.gradle.api.Action
-import org.gradle.internal.CompositeStoppable
+import org.gradle.internal.concurrent.CompositeStoppable
 import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.messaging.remote.internal.MessagingServices
 import org.gradle.test.fixtures.concurrent.ConcurrentSpec
@@ -35,8 +35,14 @@ class UnicastMessagingIntegrationTest extends ConcurrentSpec {
         def server = new Server()
         def client = new Client(server.address)
 
-        when:
+        given:
+        server.connection.connect()
+
+        and:
         client.addIncoming(service)
+        client.connection.connect()
+
+        when:
         server.outgoingService1.doStuff("1")
         server.outgoingService1.doStuff("2")
         server.outgoingService1.doStuff("3")
@@ -61,8 +67,14 @@ class UnicastMessagingIntegrationTest extends ConcurrentSpec {
         def server = new Server()
         def client = new Client(server.address)
 
-        when:
+        given:
         server.addIncoming(service)
+        server.connection.connect()
+
+        and:
+        client.connection.connect()
+
+        when:
         client.outgoingService1.doStuff("1")
         client.outgoingService1.doStuff("2")
         client.outgoingService1.doStuff("3")
@@ -92,11 +104,13 @@ class UnicastMessagingIntegrationTest extends ConcurrentSpec {
         when:
         start {
             server.addIncoming(serverService)
+            server.connection.connect()
             server.outgoingService1.doStuff("from server 1")
             server.outgoingService1.doStuff("from server 2")
         }
         start {
             client.addIncoming(clientService)
+            client.connection.connect()
             client.outgoingService1.doStuff("from client 1")
             client.outgoingService1.doStuff("from client 2")
         }
@@ -125,11 +139,13 @@ class UnicastMessagingIntegrationTest extends ConcurrentSpec {
         when:
         start {
             server.addIncoming(serverService)
+            server.connection.connect()
             server.outgoingService2.doStuff("server1")
             server.outgoingService2.doStuff("server2")
         }
         start {
             client.addIncoming(clientService)
+            client.connection.connect()
             client.outgoingService1.doStuff("client1")
             client.outgoingService1.doStuff("client2")
         }
@@ -155,9 +171,12 @@ class UnicastMessagingIntegrationTest extends ConcurrentSpec {
 
         when:
         client = new Client(server.address)
+        client.connection.connect()
         client.outgoingService1.doStuff("1")
         client.outgoingService1.doStuff("2")
+
         server.addIncoming(service)
+        server.connection.connect()
         thread.blockUntil.received
         server.stop()
         client?.stop()
@@ -215,7 +234,7 @@ class UnicastMessagingIntegrationTest extends ConcurrentSpec {
             acceptor = server.accept({ event ->
                 lock.lock()
                 try {
-                    connection = event.connection
+                    connection = event
                     condition.signalAll()
                 } finally {
                     lock.unlock()
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java
index 329830c..746f3b5 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java
@@ -16,7 +16,7 @@
 
 package org.gradle.messaging.actor;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.ThreadSafe;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.DispatchException;
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 ac2cbb3..ab60b87 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
@@ -16,8 +16,8 @@
 
 package org.gradle.messaging.actor.internal;
 
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.internal.concurrent.ThreadSafe;
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java
index 21a8cc6..3704494 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.messaging.dispatch;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.TimeProvider;
 import org.gradle.internal.UncheckedException;
 
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
index 80bbbda..dd4dd2e 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
@@ -16,7 +16,7 @@
 
 package org.gradle.messaging.dispatch;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.slf4j.Logger;
 
 public class ExceptionTrackingFailureHandler implements DispatchFailureHandler<Object>, Stoppable {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/QueuingDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/QueuingDispatch.java
index 2bcd480..de5803d 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/QueuingDispatch.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/QueuingDispatch.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.messaging.dispatch;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.UncheckedException;
 
 import java.util.ArrayList;
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/Addressable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/Addressable.java
deleted file mode 100755
index a322287..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/Addressable.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.messaging.remote;
-
-public interface Addressable {
-    Address getLocalAddress();
-
-    Address getRemoteAddress();
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectEvent.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectEvent.java
deleted file mode 100644
index 991f502..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectEvent.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote;
-
-public class ConnectEvent<T> implements Addressable {
-    private final T connection;
-    private final Address localAddress;
-    private final Address remoteAddress;
-
-    public ConnectEvent(T connection, Address localAddress, Address remoteAddress) {
-        this.connection = connection;
-        this.localAddress = localAddress;
-        this.remoteAddress = remoteAddress;
-    }
-
-    public T getConnection() {
-        return connection;
-    }
-
-    public Address getLocalAddress() {
-        return localAddress;
-    }
-
-    public Address getRemoteAddress() {
-        return remoteAddress;
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingClient.java
index 413be55..d87aeef 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingClient.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingClient.java
@@ -20,7 +20,7 @@ package org.gradle.messaging.remote;
  */
 public interface MessagingClient {
     /**
-     * Returns the connection for this client.
+     * Creates a connection to the given address. Blocks until the connection has been established.
      *
      * @param address The address to connect to.
      */
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
index b4dce65..fe7bb92 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
@@ -19,14 +19,14 @@ package org.gradle.messaging.remote;
 import org.gradle.api.Action;
 
 /**
- * A {@code MessagingServer} allows the creation of multiple bi-directional uni-cast connections with some peer.
+ * A {@code MessagingServer} allows the creation of multiple bi-directional uni-cast connections.
  */
 public interface MessagingServer {
     /**
-     * Creates an endpoint which a single peer can connect to.
+     * Creates an endpoint that peers can connect to. Assigns an arbitrary address.
      *
-     * @param action The action to execute when the connection has been established.
+     * @param action The action to execute when a connection has been established.
      * @return The local address of the endpoint, for the peer to connect to.
      */
-    ConnectionAcceptor accept(Action<ConnectEvent<ObjectConnection>> action);
+    ConnectionAcceptor accept(Action<ObjectConnection> action);
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
index 5de98c5..2704990 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
@@ -16,38 +16,18 @@
 package org.gradle.messaging.remote;
 
 import org.gradle.internal.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
 
 /**
- * Manages a set of incoming and outgoing channels between 2 peers. Implementations must be thread-safe.
+ * Manages a set of incoming and outgoing channels between 2 peers.
+ *
+ * NOTE: This contract guarantees only partial thread-safety. Configuration and {@link #connect()} are not thread-safe and must be performed by the same thread,
+ * generally some configuration thread. Only the stop methods are thread-safe. The other methods will be made thread-safe (or moved somewhere else) later.
  */
-public interface ObjectConnection extends AsyncStoppable {
-    /**
-     * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
-     *
-     * @param type The type
-     * @return A sink. Method calls made on this object are sent as outgoing messages.
-     */
-    <T> T addOutgoing(Class<T> type);
-
-    /**
-     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
-     * thread-safe. Messages are delivered to the handler by a single thread.
-     *
-     * @param type The type.
-     * @param instance The handler instance. Incoming messages on the given type are delivered to this handler.
-     */
-    <T> void addIncoming(Class<T> type, T instance);
-
+public interface ObjectConnection extends AsyncStoppable, ObjectConnectionBuilder {
     /**
-     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
-     * thread-safe. Messages are delivered to the handler by a single thread.
-     *
-     * @param type The type.
-     * @param dispatch The handler instance. Incoming messages on the given type are delivered to this handler.
+     * Completes the connection. No further configuration can be done.
      */
-    void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch);
+    void connect();
 
     /**
      * Commences a graceful stop of this connection. Stops accepting outgoing messages. Requests that the peer stop
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnectionBuilder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnectionBuilder.java
new file mode 100755
index 0000000..47da994
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnectionBuilder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.messaging.remote;
+
+import org.gradle.messaging.serialize.kryo.StatefulSerializer;
+
+public interface ObjectConnectionBuilder {
+    /**
+     * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
+     *
+     * <p>Method invocations on the transmitter object are dispatched asynchronously to a corresponding handler in the peer. Method invocations are
+     * called on the handler in the same order that they were called on the transmitter object.</p>
+     *
+     * @param type The type
+     * @return A sink. Method calls made on this object are sent as outgoing messages.
+     */
+    <T> T addOutgoing(Class<T> type);
+
+    /**
+     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
+     * thread-safe. Messages are delivered to the handler by a single thread.
+     *
+     * <p>Method invocations are called on the given instance in the order that they were called on the transmitter object.</p>
+     *
+     * @param type The type.
+     * @param instance The handler instance. Incoming messages on the given type are delivered to this handler.
+     */
+    <T> void addIncoming(Class<T> type, T instance);
+
+    /**
+     * Use the specified serializer for all incoming and outgoing parameters.
+     */
+    void useParameterSerializer(StatefulSerializer<Object[]> serializer);
+
+    /**
+     * Use Java serialization for the parameters of incoming and outgoing method calls, with the specified ClassLoader used to deserialize incoming
+     * method parameters.
+     *
+     * <p>This method is generally not required as the ClassLoader is inferred from the incoming and outgoing types.</p>
+     *
+     * @param methodParamClassLoader The ClassLoader to use.
+     */
+    void useDefaultSerialization(ClassLoader methodParamClassLoader);
+}
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 9ae3551..c0f4807 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
@@ -15,8 +15,8 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.messaging.dispatch.*;
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ConnectCompletion.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ConnectCompletion.java
new file mode 100644
index 0000000..6dc5663
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ConnectCompletion.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 builder that allows a {@link Connection} to be created once the underlying transport with the peer has been
+ * established.
+ */
+public interface ConnectCompletion {
+    /**
+     * Creates the connection. Uses Java serialization for all messages.
+     *
+     * @param messageClassLoader The ClassLoader to use to deserialize incoming messages.
+     */
+    <T> Connection<T> create(ClassLoader messageClassLoader);
+
+    /**
+     * Creates the connection. Uses the specified serializer for all messages.
+     *
+     * @return The serializer to use.
+     */
+    <T> Connection<T> create(MessageSerializer<T> serializer);
+}
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 3d1acd7..82bfd2e 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
@@ -16,19 +16,18 @@
 package org.gradle.messaging.remote.internal;
 
 import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.messaging.dispatch.DiscardingFailureHandler;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.dispatch.ReflectionDispatch;
 import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.ConnectionAcceptor;
 import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
 import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
-import org.gradle.internal.id.IdGenerator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,7 +59,7 @@ public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
         connection.dispatchTo(new GroupMessageFilter(group, protocolStack.getBottom()));
         protocolStack.getBottom().dispatchTo(connection);
 
-        acceptor = incomingConnector.accept(new IncomingConnectionAction(), getClass().getClassLoader(), true);
+        acceptor = incomingConnector.accept(new IncomingConnectionAction(), true);
         address = acceptor.getAddress();
         hub = new MessageHub("incoming broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
 
@@ -84,9 +83,10 @@ public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
         CompositeStoppable.stoppable(acceptor, protocolStack, hub, executor).stop();
     }
 
-    private class IncomingConnectionAction implements Action<ConnectEvent<Connection<Message>>> {
-        public void execute(ConnectEvent<Connection<Message>> connectionConnectEvent) {
-            hub.addConnection(connectionConnectEvent.getConnection());
+    private class IncomingConnectionAction implements Action<ConnectCompletion> {
+        public void execute(ConnectCompletion completion) {
+            Connection<Message> connection = completion.create(getClass().getClassLoader());
+            hub.addConnection(connection);
         }
     }
 }
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 9f5f4a0..e76a837 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
@@ -15,9 +15,9 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.messaging.dispatch.DiscardingFailureHandler;
@@ -109,7 +109,7 @@ public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
                     lock.unlock();
                 }
 
-                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress, DiscoveryMessage.class.getClassLoader());
+                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress).create(DiscoveryMessage.class.getClassLoader());
                 hub.addConnection(syncConnection);
             }
         }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
index 9aa69ab..073e056 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
@@ -17,26 +17,15 @@
 package org.gradle.messaging.remote.internal;
 
 import org.gradle.api.Action;
-import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.ConnectionAcceptor;
 
 public interface IncomingConnector {
     /**
-     * Allocates a new incoming endpoint. Uses Java serialization.
+     * Starts listening for incoming connections. Assigns an arbitrary address for the endpoint.
      *
      * @param action the action to execute on incoming connection. The supplied action is not required to be thread-safe.
-     * @param messageClassLoader The ClassLoader to use to deserialize incoming messages.
      * @param allowRemote If true, only allow connections from remote machines. If false, allow only from the local machine.
      * @return the address of the endpoint which the connector is listening on.
      */
-    <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, ClassLoader messageClassLoader, boolean allowRemote);
-
-    /**
-     * Allocates a new incoming endpoint.
-     *
-     * @param action the action to execute on incoming connection. The supplied action is not required to be thread-safe.
-     * @param allowRemote If true, only allow connections from remote machines. If false, allow only from the local machine.
-     * @return the address of the endpoint which the connector is listening on.
-     */
-    <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, MessageSerializer<T> serializer, boolean allowRemote);
+    ConnectionAcceptor accept(Action<ConnectCompletion> action, boolean allowRemote);
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
index 56ffd0e..fe98bac 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
@@ -16,13 +16,24 @@
 
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.UncheckedException;
 import org.gradle.internal.io.ClassLoaderObjectInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.*;
 import java.lang.reflect.Constructor;
 
 public abstract class Message implements Serializable {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Message.class);
+
+    /**
+     * Serialize the <code>message</code> onto the provided <code>outputStream</code>, replacing all {@link Throwable}s in the object graph with a placeholder object that can be read back by {@link
+     * #receive(java.io.InputStream, ClassLoader)}.
+     *
+     * @param message object to serialize
+     * @param outputSteam stream to serialize onto
+     */
     public static void send(Object message, OutputStream outputSteam) throws IOException {
         ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
         try {
@@ -32,6 +43,14 @@ public abstract class Message implements Serializable {
         }
     }
 
+    /**
+     * Read back an object from the provided stream that has been serialized by a call to {@link #send(Object, java.io.OutputStream)}. Any {@link Throwable} that cannot be de-serialized (for whatever
+     * reason) will be replaced by a {@link PlaceholderException}.
+     *
+     * @param inputSteam stream to read the object from
+     * @param classLoader loader used to load exception classes
+     * @return the de-serialized object
+     */
     public static Object receive(InputStream inputSteam, ClassLoader classLoader)
             throws IOException, ClassNotFoundException {
         ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
@@ -39,90 +58,120 @@ public abstract class Message implements Serializable {
     }
 
     private static class ExceptionPlaceholder implements Serializable {
+        private final String type;
         private byte[] serializedException;
-        private String type;
         private String message;
         private String toString;
         private ExceptionPlaceholder cause;
         private StackTraceElement[] stackTrace;
-        private RuntimeException toStringRuntimeExec;
+        private Throwable toStringRuntimeExec;
+        private Throwable getMessageExec;
 
         public ExceptionPlaceholder(final Throwable throwable) throws IOException {
+            type = throwable.getClass().getName();
+
+            try {
+                stackTrace = throwable.getStackTrace();
+            } catch (Throwable ignored) {
+// TODO:ADAM - switch the logging back on. Need to make sending messages from daemon to client async wrt log event generation
+//                LOGGER.debug("Ignoring failure to extract throwable stack trace.", ignored);
+                stackTrace = new StackTraceElement[0];
+            }
+
+            try {
+                message = throwable.getMessage();
+            } catch (Throwable failure) {
+                getMessageExec = failure;
+            }
+
+            try {
+                toString = throwable.toString();
+            } catch (Throwable failure) {
+                toStringRuntimeExec = failure;
+            }
+
+            Throwable causeTmp;
+            try {
+                causeTmp = throwable.getCause();
+            } catch (Throwable ignored) {
+// TODO:ADAM - switch the logging back on.
+//                LOGGER.debug("Ignoring failure to extract throwable cause.", ignored);
+                causeTmp = null;
+            }
+            final Throwable causeFinal = causeTmp;
+
             ByteArrayOutputStream outstr = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outstr) {
+                boolean seenFirst;
                 @Override
                 protected Object replaceObject(Object obj) throws IOException {
-                    if (obj == throwable) {
-                        return throwable;
+                    if (!seenFirst) {
+                        seenFirst = true;
+                        return obj;
                     }
-                    // Don't serialize the cause - we'll serialize it separately later 
-                    if (obj == throwable.getCause()) {
+                    // Don't serialize the cause - we'll serialize it separately later
+                    if (obj == causeFinal) {
                         return new CausePlaceholder();
                     }
                     return super.replaceObject(obj);
                 }
             };
+
             try {
                 oos.writeObject(throwable);
                 oos.close();
                 serializedException = outstr.toByteArray();
-            } catch (NotSerializableException e) {
-                // Ignore
+            } catch (Throwable ignored) {
+// TODO:ADAM - switch the logging back on.
+//                LOGGER.debug("Ignoring failure to serialize throwable.", ignored);
             }
 
-            type = throwable.getClass().getName();
-            message = throwable.getMessage();
-            try {
-                toString = throwable.toString();
-            } catch (RuntimeException toStringRE) {
-                toString = null;
-                toStringRuntimeExec = toStringRE;
-            }
-            if (throwable.getCause() != null) {
-                cause = new ExceptionPlaceholder(throwable.getCause());
+            if (causeFinal != null) {
+                cause = new ExceptionPlaceholder(causeFinal);
             }
-            stackTrace = throwable.getStackTrace();
         }
 
         public Throwable read(ClassLoader classLoader) throws IOException {
             final Throwable causeThrowable = getCause(classLoader);
-            Throwable throwable = null;
+
             if (serializedException != null) {
-                try {
-                    final ExceptionReplacingObjectInputStream ois = new ExceptionReplacingObjectInputStream(new ByteArrayInputStream(serializedException), classLoader) {
-                        @Override
-                        protected Object resolveObject(Object obj) throws IOException {
-                            if (obj instanceof CausePlaceholder) {
-                                return causeThrowable;
-                            }
-                            return super.resolveObject(obj);
+                // try to deserialize the original exception
+                final ExceptionReplacingObjectInputStream ois = new ExceptionReplacingObjectInputStream(new ByteArrayInputStream(serializedException), classLoader) {
+                    @Override
+                    protected Object resolveObject(Object obj) throws IOException {
+                        if (obj instanceof CausePlaceholder) {
+                            return causeThrowable;
                         }
-                    };
-                    throwable = (Throwable) ois.readObject();
-                } catch (ClassNotFoundException e) {
-                    // Ignore
-                } catch (InvalidClassException e) {
-                    try {
-                        Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
-                        throwable = (Throwable) constructor.newInstance(message);
-                        throwable.initCause(causeThrowable);
-                        throwable.setStackTrace(stackTrace);
-                    } catch (ClassNotFoundException e1) {
-                        // Ignore
-                    } catch (NoSuchMethodException e1) {
-                        // Ignore
-                    } catch (Throwable t) {
-                        throw UncheckedException.throwAsUncheckedException(t);
+                        return super.resolveObject(obj);
                     }
+                };
+                try {
+                    return (Throwable) ois.readObject();
+                } catch (ClassNotFoundException ignored) {
+                    // Don't log
+                } catch (Throwable failure) {
+                    LOGGER.debug("Ignoring failure to de-serialize throwable.", failure);
                 }
             }
 
-            if (throwable == null) {
-                throwable = new PlaceholderException(type, message, toString, toStringRuntimeExec, causeThrowable);
-                throwable.setStackTrace(stackTrace);
+            try {
+                // try to reconstruct the exception
+                Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
+                Throwable reconstructed = (Throwable) constructor.newInstance(message);
+                reconstructed.initCause(causeThrowable);
+                reconstructed.setStackTrace(stackTrace);
+                return reconstructed;
+            } catch (ClassNotFoundException ignored) {
+                // Don't log
+            } catch (NoSuchMethodException ignored) {
+                // Don't log
+            } catch (Throwable ignored) {
+                LOGGER.debug("Ignoring failure to recreate throwable.", ignored);
             }
 
-            return throwable;
+            Throwable placeholder = new PlaceholderException(type, message, getMessageExec, toString, toStringRuntimeExec, causeThrowable);
+            placeholder.setStackTrace(stackTrace);
+            return placeholder;
         }
 
         private Throwable getCause(ClassLoader classLoader) throws IOException {
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 d467be6..45db5eb 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.concurrent.AsyncStoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
@@ -81,28 +81,6 @@ public class MessageHub implements AsyncStoppable {
         }
     }
 
-    public Dispatch<Object> addUnicastOutgoing(String channel) {
-        lock.lock();
-        try {
-            ProtocolStack<Message> outgoing = outgoingUnicasts.get(channel);
-            if (outgoing == null) {
-                Protocol<Message> unicastSendProtocol = new UnicastSendProtocol();
-                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
-                StoppableExecutor executor = executorFactory.create(displayName + " outgoing " + channel);
-                executors.add(executor);
-                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, unicastSendProtocol, sendProtocol);
-                outgoingUnicasts.put(channel, outgoing);
-
-                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
-                outgoing.getBottom().dispatchTo(outgoingEndpoint);
-                outgoingEndpoint.dispatchTo(outgoing.getBottom());
-            }
-            return new OutgoingMultiplex(channel, outgoing.getTop());
-        } finally {
-            lock.unlock();
-        }
-    }
-
     public Dispatch<Object> addMulticastOutgoing(String channel) {
         lock.lock();
         try {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
index fe15dfa..dde040e 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
@@ -15,27 +15,21 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.id.UUIDGenerator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.messaging.dispatch.DiscardingFailureHandler;
-import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.remote.MessagingClient;
 import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.internal.hub.InterHubMessageSerializer;
 import org.gradle.messaging.remote.internal.hub.MessageHubBackedClient;
 import org.gradle.messaging.remote.internal.hub.MessageHubBackedServer;
-import org.gradle.messaging.remote.internal.hub.MethodInvocationSerializer;
 import org.gradle.messaging.remote.internal.inet.*;
 import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
 import org.gradle.messaging.remote.internal.protocol.DiscoveryProtocolSerializer;
-import org.gradle.messaging.serialize.kryo.JavaSerializer;
-import org.gradle.messaging.serialize.kryo.TypeSafeSerializer;
 import org.slf4j.LoggerFactory;
 
 import java.net.InetAddress;
@@ -62,13 +56,6 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
     private final ClassLoader messageClassLoader;
     private final String broadcastGroup;
     private final SocketInetAddress broadcastAddress;
-    private MessagingClient messagingClient;
-    private IncomingConnector incomingConnector;
-    private DefaultExecutorFactory executorFactory;
-    private MessagingServer messagingServer;
-    private DefaultIncomingBroadcast incomingBroadcast;
-    private AsyncConnectionAdapter<DiscoveryMessage> multiCastConnection;
-    private DefaultOutgoingBroadcast outgoingBroadcast;
 
     public MessagingServices(ClassLoader messageClassLoader) {
         this(messageClassLoader, "gradle");
@@ -96,19 +83,6 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
         close();
     }
 
-    @Override
-    public void close() {
-        CompositeStoppable stoppable = new CompositeStoppable();
-        stoppable.add(incomingConnector);
-        stoppable.add(messagingClient);
-        stoppable.add(messagingServer);
-        stoppable.add(outgoingBroadcast);
-        stoppable.add(incomingBroadcast);
-        stoppable.add(multiCastConnection);
-        stoppable.add(executorFactory);
-        stoppable.stop();
-    }
-
     protected MessageOriginator createMessageOriginator() {
         String hostName = get(InetAddressFactory.class).getHostName();
         String nodeName = String.format("%s@%s", System.getProperty("user.name"), hostName);
@@ -116,8 +90,7 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
     }
 
     protected ExecutorFactory createExecutorFactory() {
-        executorFactory = new DefaultExecutorFactory();
-        return executorFactory;
+        return new DefaultExecutorFactory();
     }
 
     protected InetAddressFactory createInetAddressFactory() {
@@ -128,71 +101,53 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
         return new TcpOutgoingConnector();
     }
 
-    protected IncomingConnector createIncomingConnector() {
-        incomingConnector = new TcpIncomingConnector(
-                get(ExecutorFactory.class),
-                get(InetAddressFactory.class),
+    protected IncomingConnector createIncomingConnector(ExecutorFactory executorFactory, InetAddressFactory inetAddressFactory) {
+        return new TcpIncomingConnector(
+                executorFactory,
+                inetAddressFactory,
                 idGenerator
         );
-        return incomingConnector;
-    }
-
-    protected InterHubMessageSerializer createInterHubSerializer() {
-        return new InterHubMessageSerializer(
-                new TypeSafeSerializer<MethodInvocation>(
-                        MethodInvocation.class,
-                        new MethodInvocationSerializer(
-                                messageClassLoader,
-                                new JavaSerializer<Object[]>(
-                                        messageClassLoader))));
     }
 
-    protected MessagingClient createMessagingClient() {
-        messagingClient = new MessageHubBackedClient(
-                get(OutgoingConnector.class),
-                get(InterHubMessageSerializer.class),
-                get(ExecutorFactory.class));
-        return messagingClient;
+    protected MessagingClient createMessagingClient(OutgoingConnector outgoingConnector, ExecutorFactory executorFactory) {
+        return new MessageHubBackedClient(
+                outgoingConnector,
+                executorFactory);
     }
 
-    protected MessagingServer createMessagingServer() {
-        messagingServer = new MessageHubBackedServer(
-                get(IncomingConnector.class),
-                get(InterHubMessageSerializer.class),
-                get(ExecutorFactory.class));
-        return messagingServer;
+    protected MessagingServer createMessagingServer(IncomingConnector incomingConnector, ExecutorFactory executorFactory) {
+        return new MessageHubBackedServer(
+                incomingConnector,
+                executorFactory);
     }
 
-    protected IncomingBroadcast createIncomingBroadcast() {
-        incomingBroadcast = new DefaultIncomingBroadcast(
-                get(MessageOriginator.class),
+    protected IncomingBroadcast createIncomingBroadcast(MessageOriginator messageOriginator, AsyncConnection<DiscoveryMessage> asyncConnection, IncomingConnector incomingConnector, ExecutorFactory executorFactory) {
+        return new DefaultIncomingBroadcast(
+                messageOriginator,
                 broadcastGroup,
-                get(AsyncConnection.class),
-                createIncomingConnector(),
-                get(ExecutorFactory.class),
+                asyncConnection,
+                incomingConnector,
+                executorFactory,
                 idGenerator,
                 messageClassLoader);
-        return incomingBroadcast;
     }
 
-    protected OutgoingBroadcast createOutgoingBroadcast() {
-        outgoingBroadcast = new DefaultOutgoingBroadcast(
-                get(MessageOriginator.class),
+    protected OutgoingBroadcast createOutgoingBroadcast(MessageOriginator messageOriginator, AsyncConnection<DiscoveryMessage> asyncConnection, OutgoingConnector outgoingConnector, ExecutorFactory executorFactory) {
+        return new DefaultOutgoingBroadcast(
+                messageOriginator,
                 broadcastGroup,
-                get(AsyncConnection.class),
-                createOutgoingConnector(),
-                get(ExecutorFactory.class),
+                asyncConnection,
+                outgoingConnector,
+                executorFactory,
                 idGenerator,
                 messageClassLoader);
-        return outgoingBroadcast;
     }
 
-    protected AsyncConnection<DiscoveryMessage> createMulticastConnection() {
+    protected AsyncConnection<DiscoveryMessage> createMulticastConnection(ExecutorFactory executorFactory) {
         MulticastConnection<DiscoveryMessage> connection = new MulticastConnection<DiscoveryMessage>(broadcastAddress, new DiscoveryProtocolSerializer());
-        multiCastConnection = new AsyncConnectionAdapter<DiscoveryMessage>(
+        return new AsyncConnectionAdapter<DiscoveryMessage>(
                 connection,
                 new DiscardingFailureHandler<DiscoveryMessage>(LoggerFactory.getLogger(MulticastConnection.class)),
-                get(ExecutorFactory.class));
-        return multiCastConnection;
+                executorFactory);
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
index f823b6d..9d926ef 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
@@ -19,17 +19,9 @@ import org.gradle.messaging.remote.Address;
 
 public interface OutgoingConnector {
     /**
-     * Creates a connection to the given address. Uses default Java serialization for messages.
+     * Creates a connection to the given address. Blocks until the connection with the peer has been established.
      *
-     * @param messageClassLoader ClassLoader to use to load incoming messages.
      * @throws ConnectException when there is nothing listening on the remote address.
      */
-    <T> Connection<T> connect(Address destinationAddress, ClassLoader messageClassLoader) throws ConnectException;
-
-    /**
-     * Creates a connection to the given address. Uses the given serializer to convert messages between binary form and objects of type T.
-     *
-     * @throws ConnectException when there is nothing listening on the remote address
-     */
-    <T> Connection<T> connect(Address destinationAddress, MessageSerializer<T> serializer) throws ConnectException;
+    ConnectCompletion connect(Address destinationAddress) throws ConnectException;
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
index 3ec5c3f..172455c 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
@@ -15,17 +15,23 @@
  */
 package org.gradle.messaging.remote.internal;
 
+import org.gradle.api.Nullable;
+import org.gradle.internal.UncheckedException;
+
 /**
  * A {@code PlaceholderException} is used when an exception cannot be serialized or deserialized.
  */
 public class PlaceholderException extends RuntimeException {
     private final String exceptionClassName;
+    private final Throwable getMessageException;
     private final String toString;
-    private final RuntimeException toStringRuntimeEx;
+    private final Throwable toStringRuntimeEx;
 
-    public PlaceholderException(String exceptionClassName, String message, String toString, RuntimeException toStringException, Throwable cause) {
+    public PlaceholderException(String exceptionClassName, @Nullable String message, @Nullable Throwable getMessageException, @Nullable String toString,
+                                @Nullable Throwable toStringException, @Nullable Throwable cause) {
         super(message, cause);
         this.exceptionClassName = exceptionClassName;
+        this.getMessageException = getMessageException;
         this.toString = toString;
         this.toStringRuntimeEx = toStringException;
     }
@@ -34,9 +40,17 @@ public class PlaceholderException extends RuntimeException {
         return exceptionClassName;
     }
 
+    @Override
+    public String getMessage() {
+        if (getMessageException != null) {
+            throw UncheckedException.throwAsUncheckedException(getMessageException);
+        }
+        return super.getMessage();
+    }
+
     public String toString() {
-        if(toStringRuntimeEx !=null){
-            throw toStringRuntimeEx;
+        if (toStringRuntimeEx != null) {
+            throw UncheckedException.throwAsUncheckedException(toStringRuntimeEx);
         }
         return toString;
     }
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 9d20acd..df26ddd 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.TrueTimeProvider;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.AsyncStoppable;
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Router.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Router.java
index aa3bf04..394704d 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Router.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Router.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 import org.gradle.messaging.dispatch.AsyncDispatch;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.DispatchFailureHandler;
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java
index b97ead5..93508b0 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java
@@ -16,68 +16,73 @@
 
 package org.gradle.messaging.remote.internal.hub;
 
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.internal.MessageSerializer;
 import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier;
 import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage;
 import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
 import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.FlushableEncoder;
 import org.gradle.messaging.serialize.ObjectReader;
 import org.gradle.messaging.serialize.ObjectWriter;
-import org.gradle.messaging.serialize.kryo.KryoAwareSerializer;
+import org.gradle.messaging.serialize.kryo.StatefulSerializer;
+import org.gradle.messaging.serialize.kryo.KryoBackedDecoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedEncoder;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.HashMap;
 import java.util.Map;
 
 public class InterHubMessageSerializer implements MessageSerializer<InterHubMessage> {
-    private final KryoAwareSerializer<Object> payloadSerializer;
+    private static final byte CHANNEL_MESSAGE = 1;
+    private static final byte END_STREAM_MESSAGE = 2;
+    private final StatefulSerializer<Object> payloadSerializer;
 
-    public InterHubMessageSerializer(KryoAwareSerializer<Object> payloadSerializer) {
+    public InterHubMessageSerializer(StatefulSerializer<Object> payloadSerializer) {
         this.payloadSerializer = payloadSerializer;
     }
 
     public ObjectReader<InterHubMessage> newReader(InputStream inputStream, Address localAddress, Address remoteAddress) {
-        Input input = new Input(inputStream);
-        return new MessageReader(input, payloadSerializer.newReader(input));
+        Decoder decoder = new KryoBackedDecoder(inputStream);
+        return new MessageReader(decoder, payloadSerializer.newReader(decoder));
     }
 
     public ObjectWriter<InterHubMessage> newWriter(OutputStream outputStream) {
-        Output output = new Output(outputStream);
-        return new MessageWriter(output, payloadSerializer.newWriter(output));
+        FlushableEncoder encoder = new KryoBackedEncoder(outputStream);
+        return new MessageWriter(encoder, payloadSerializer.newWriter(encoder));
     }
 
     private static class MessageReader implements ObjectReader<InterHubMessage> {
         private final Map<Integer, ChannelIdentifier> channels = new HashMap<Integer, ChannelIdentifier>();
-        private final Input input;
+        private final Decoder decoder;
         private final ObjectReader<?> payloadReader;
 
-        public MessageReader(Input input, ObjectReader<?> payloadReader) {
-            this.input = input;
+        public MessageReader(Decoder decoder, ObjectReader<?> payloadReader) {
+            this.decoder = decoder;
             this.payloadReader = payloadReader;
         }
 
         public InterHubMessage read() throws Exception {
-            switch (input.readByte()) {
-                case 1:
+            switch (decoder.readByte()) {
+                case CHANNEL_MESSAGE:
                     ChannelIdentifier channelId = readChannelId();
                     Object payload = payloadReader.read();
                     return new ChannelMessage(channelId, payload);
-                case 2:
+                case END_STREAM_MESSAGE:
                     return new EndOfStream();
                 default:
                     throw new IllegalArgumentException();
             }
         }
 
-        private ChannelIdentifier readChannelId() {
-            int channelNum = input.readInt(true);
+        private ChannelIdentifier readChannelId() throws IOException {
+            int channelNum = decoder.readSmallInt();
             ChannelIdentifier channelId = channels.get(channelNum);
             if (channelId == null) {
-                String channel = input.readString();
+                String channel = decoder.readString();
                 channelId = new ChannelIdentifier(channel);
                 channels.put(channelNum, channelId);
             }
@@ -87,37 +92,37 @@ public class InterHubMessageSerializer implements MessageSerializer<InterHubMess
 
     private static class MessageWriter implements ObjectWriter<InterHubMessage> {
         private final Map<ChannelIdentifier, Integer> channels = new HashMap<ChannelIdentifier, Integer>();
-        private final Output output;
+        private final FlushableEncoder encoder;
         private final ObjectWriter<Object> payloadWriter;
 
-        public MessageWriter(Output output, ObjectWriter<Object> payloadWriter) {
-            this.output = output;
+        public MessageWriter(FlushableEncoder encoder, ObjectWriter<Object> payloadWriter) {
+            this.encoder = encoder;
             this.payloadWriter = payloadWriter;
         }
 
         public void write(InterHubMessage message) throws Exception {
             if (message instanceof ChannelMessage) {
                 ChannelMessage channelMessage = (ChannelMessage) message;
-                output.writeByte(1);
+                encoder.writeByte(CHANNEL_MESSAGE);
                 writeChannelId(channelMessage);
                 payloadWriter.write(channelMessage.getPayload());
             } else if (message instanceof EndOfStream) {
-                output.writeByte(2);
+                encoder.writeByte(END_STREAM_MESSAGE);
             } else {
                 throw new IllegalArgumentException();
             }
-            output.flush();
+            encoder.flush();
         }
 
-        private void writeChannelId(ChannelMessage channelMessage) {
+        private void writeChannelId(ChannelMessage channelMessage) throws IOException {
             Integer channelNum = channels.get(channelMessage.getChannel());
             if (channelNum == null) {
                 channelNum = channels.size();
                 channels.put(channelMessage.getChannel(), channelNum);
-                output.writeInt(channelNum, true);
-                output.writeString(channelMessage.getChannel().getName());
+                encoder.writeSmallInt(channelNum);
+                encoder.writeString(channelMessage.getChannel().getName());
             } else {
-                output.writeInt(channelNum, true);
+                encoder.writeSmallInt(channelNum);
             }
         }
     }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java
index 1c5bd32..b117557 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java
@@ -16,37 +16,22 @@
 
 package org.gradle.messaging.remote.internal.hub;
 
-import org.gradle.api.Action;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingClient;
 import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.MessageSerializer;
 import org.gradle.messaging.remote.internal.OutgoingConnector;
-import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class MessageHubBackedClient implements MessagingClient {
-    private static final Logger LOGGER = LoggerFactory.getLogger(MessageHubBackedClient.class);
     private final OutgoingConnector connector;
-    private final MessageSerializer<InterHubMessage> serializer;
     private final ExecutorFactory executorFactory;
 
-    public MessageHubBackedClient(OutgoingConnector connector, MessageSerializer<InterHubMessage> serializer, ExecutorFactory executorFactory) {
+    public MessageHubBackedClient(OutgoingConnector connector, ExecutorFactory executorFactory) {
         this.connector = connector;
-        this.serializer = serializer;
         this.executorFactory = executorFactory;
     }
 
     public ObjectConnection getConnection(Address address) {
-        Connection<InterHubMessage> connection = connector.connect(address, serializer);
-        MessageHub hub = new MessageHub(connection.toString(), executorFactory, new Action<Throwable>() {
-            public void execute(Throwable throwable) {
-                LOGGER.error("Unexpected exception thrown.", throwable);
-            }
-        });
-        return new MessageHubBackedObjectConnection(hub, connection);
+        return new MessageHubBackedObjectConnection(executorFactory, connector.connect(address));
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java
index 47b05c3..de5aea3 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java
@@ -16,44 +16,90 @@
 
 package org.gradle.messaging.remote.internal.hub;
 
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.api.Action;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.ThreadSafe;
-import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
 import org.gradle.messaging.dispatch.ReflectionDispatch;
 import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.ConnectCompletion;
 import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageSerializer;
 import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.serialize.kryo.JavaSerializer;
+import org.gradle.messaging.serialize.kryo.StatefulSerializer;
+import org.gradle.messaging.serialize.kryo.TypeSafeSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class MessageHubBackedObjectConnection implements ObjectConnection {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MessageHubBackedObjectConnection.class);
     private final MessageHub hub;
-    private final Connection<InterHubMessage> connection;
+    private ConnectCompletion completion;
+    private Connection<InterHubMessage> connection;
+    private ClassLoader methodParamClassLoader;
+    private StatefulSerializer<Object[]> paramSerializer;
 
-    public MessageHubBackedObjectConnection(MessageHub hub, Connection<InterHubMessage> connection) {
-        this.hub = hub;
-        this.connection = connection;
-        hub.addConnection(connection);
-    }
-
-    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-        hub.addHandler(type.getName(), dispatch);
+    public MessageHubBackedObjectConnection(ExecutorFactory executorFactory, ConnectCompletion completion) {
+        this.hub = new MessageHub(completion.toString(), executorFactory, new Action<Throwable>() {
+            public void execute(Throwable throwable) {
+                LOGGER.error("Unexpected exception thrown.", throwable);
+            }
+        });
+        this.completion = completion;
     }
 
     public <T> void addIncoming(Class<T> type, T instance) {
+        if (methodParamClassLoader == null) {
+            methodParamClassLoader = type.getClassLoader();
+        }
         hub.addHandler(type.getName(), new ReflectionDispatch(instance));
     }
 
     public <T> T addOutgoing(Class<T> type) {
+        if (methodParamClassLoader == null) {
+            methodParamClassLoader = type.getClassLoader();
+        }
         ProxyDispatchAdapter<T> adapter = new ProxyDispatchAdapter<T>(hub.getOutgoing(type.getName(), MethodInvocation.class), type, ThreadSafe.class);
         return adapter.getSource();
     }
 
+    public void useDefaultSerialization(ClassLoader methodParamClassLoader) {
+        this.methodParamClassLoader = methodParamClassLoader;
+    }
+
+    public void useParameterSerializer(StatefulSerializer<Object[]> serializer) {
+        this.paramSerializer = serializer;
+    }
+
+    public void connect() {
+        if (methodParamClassLoader == null) {
+            methodParamClassLoader = getClass().getClassLoader();
+        }
+
+        StatefulSerializer<Object[]> paramSerializer = this.paramSerializer;
+        if (paramSerializer == null) {
+            paramSerializer = new JavaSerializer<Object[]>(methodParamClassLoader);
+        }
+
+        MessageSerializer<InterHubMessage> serializer = new InterHubMessageSerializer(
+                new TypeSafeSerializer<MethodInvocation>(MethodInvocation.class, new MethodInvocationSerializer(
+                                methodParamClassLoader,
+                                paramSerializer)));
+
+        connection = completion.create(serializer);
+        hub.addConnection(connection);
+        completion = null;
+    }
+
     public void requestStop() {
         hub.requestStop();
     }
 
     public void stop() {
+        // TODO:ADAM - need to cleanup completion too, if not used
         CompositeStoppable.stoppable(hub, connection).stop();
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java
index 923dac9..0502aad 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java
@@ -18,46 +18,35 @@ package org.gradle.messaging.remote.internal.hub;
 
 import org.gradle.api.Action;
 import org.gradle.internal.concurrent.ExecutorFactory;
-import org.gradle.messaging.remote.*;
-import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.ConnectionAcceptor;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.ConnectCompletion;
 import org.gradle.messaging.remote.internal.IncomingConnector;
-import org.gradle.messaging.remote.internal.MessageSerializer;
-import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class MessageHubBackedServer implements MessagingServer {
-    private static final Logger LOGGER = LoggerFactory.getLogger(MessageHubBackedServer.class);
     private final IncomingConnector connector;
-    private final MessageSerializer<InterHubMessage> serializer;
     private final ExecutorFactory executorFactory;
 
-    public MessageHubBackedServer(IncomingConnector connector, MessageSerializer<InterHubMessage> serializer, ExecutorFactory executorFactory) {
+    public MessageHubBackedServer(IncomingConnector connector, ExecutorFactory executorFactory) {
         this.connector = connector;
-        this.serializer = serializer;
         this.executorFactory = executorFactory;
     }
 
-    public ConnectionAcceptor accept(Action<ConnectEvent<ObjectConnection>> action) {
-        return connector.accept(new ConnectEventAction(action), serializer, false);
+    public ConnectionAcceptor accept(Action<ObjectConnection> action) {
+        return connector.accept(new ConnectEventAction(action), false);
     }
 
-    private class ConnectEventAction implements Action<ConnectEvent<Connection<InterHubMessage>>> {
-        private final Action<ConnectEvent<ObjectConnection>> action;
+    private class ConnectEventAction implements Action<ConnectCompletion> {
+        private final Action<ObjectConnection> action;
 
-        public ConnectEventAction(Action<ConnectEvent<ObjectConnection>> action) {
+        public ConnectEventAction(Action<ObjectConnection> action) {
             this.action = action;
         }
 
-        public void execute(ConnectEvent<Connection<InterHubMessage>> connectEvent) {
-            Connection<InterHubMessage> connection = connectEvent.getConnection();
-            MessageHub hub = new MessageHub(connection.toString(), executorFactory, new Action<Throwable>() {
-                public void execute(Throwable throwable) {
-                    LOGGER.error("Unexpected exception thrown.", throwable);
-                }
-            });
-            MessageHubBackedObjectConnection objectConnection = new MessageHubBackedObjectConnection(hub, connection);
-            action.execute(new ConnectEvent<ObjectConnection>(objectConnection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
+        public void execute(ConnectCompletion completion) {
+            action.execute(new MessageHubBackedObjectConnection(executorFactory, completion));
         }
     }
+
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java
index 29e1fc1..462d998 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java
@@ -16,41 +16,42 @@
 
 package org.gradle.messaging.remote.internal.hub;
 
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
 import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
 import org.gradle.messaging.serialize.ObjectReader;
 import org.gradle.messaging.serialize.ObjectWriter;
-import org.gradle.messaging.serialize.kryo.KryoAwareSerializer;
+import org.gradle.messaging.serialize.kryo.StatefulSerializer;
 
+import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Map;
 
-public class MethodInvocationSerializer implements KryoAwareSerializer<MethodInvocation> {
+public class MethodInvocationSerializer implements StatefulSerializer<MethodInvocation> {
     private final ClassLoader classLoader;
-    private final KryoAwareSerializer<Object[]> argsSerializer;
+    private final StatefulSerializer<Object[]> argsSerializer;
 
-    public MethodInvocationSerializer(ClassLoader classLoader, KryoAwareSerializer<Object[]> argsSerializer) {
+    public MethodInvocationSerializer(ClassLoader classLoader, StatefulSerializer<Object[]> argsSerializer) {
         this.classLoader = classLoader;
         this.argsSerializer = argsSerializer;
     }
 
-    public ObjectReader<MethodInvocation> newReader(Input input) {
-        return new MethodInvocationReader(input, classLoader, argsSerializer.newReader(input));
+    public ObjectReader<MethodInvocation> newReader(Decoder decoder) {
+        return new MethodInvocationReader(decoder, classLoader, argsSerializer.newReader(decoder));
     }
 
-    public ObjectWriter<MethodInvocation> newWriter(Output output) {
-        return new MethodInvocationWriter(output, argsSerializer.newWriter(output));
+    public ObjectWriter<MethodInvocation> newWriter(Encoder encoder) {
+        return new MethodInvocationWriter(encoder, argsSerializer.newWriter(encoder));
     }
 
     private static class MethodInvocationWriter implements ObjectWriter<MethodInvocation> {
-        private final Output output;
+        private final Encoder encoder;
         private final ObjectWriter<Object[]> argsWriter;
         private final Map<Method, Integer> methods = new HashMap<Method, Integer>();
 
-        public MethodInvocationWriter(Output output, ObjectWriter<Object[]> argsWriter) {
-            this.output = output;
+        public MethodInvocationWriter(Encoder encoder, ObjectWriter<Object[]> argsWriter) {
+            this.encoder = encoder;
             this.argsWriter = argsWriter;
         }
 
@@ -66,20 +67,20 @@ public class MethodInvocationSerializer implements KryoAwareSerializer<MethodInv
             argsWriter.write(value.getArguments());
         }
 
-        private void writeMethod(Method method) {
+        private void writeMethod(Method method) throws IOException {
             Integer methodId = methods.get(method);
             if (methodId == null) {
                 methodId = methods.size();
                 methods.put(method, methodId);
-                output.writeInt(methodId, true);
-                output.writeString(method.getDeclaringClass().getName());
-                output.writeString(method.getName());
-                output.writeInt(method.getParameterTypes().length, true);
+                encoder.writeSmallInt(methodId);
+                encoder.writeString(method.getDeclaringClass().getName());
+                encoder.writeString(method.getName());
+                encoder.writeSmallInt(method.getParameterTypes().length);
                 for (Class<?> paramType : method.getParameterTypes()) {
-                    output.writeString(paramType.getName());
+                    encoder.writeString(paramType.getName());
                 }
             } else {
-                output.writeInt(methodId, true);
+                encoder.writeSmallInt(methodId);
             }
         }
     }
@@ -91,13 +92,13 @@ public class MethodInvocationSerializer implements KryoAwareSerializer<MethodInv
             PRIMITIVE_TYPES.put(Integer.TYPE.getName(), Integer.TYPE);
         }
 
-        private final Input input;
+        private final Decoder decoder;
         private final ClassLoader classLoader;
         private final ObjectReader<Object[]> argsReader;
         private final Map<Integer, Method> methods = new HashMap<Integer, Method>();
 
-        public MethodInvocationReader(Input input, ClassLoader classLoader, ObjectReader<Object[]> argsReader) {
-            this.input = input;
+        public MethodInvocationReader(Decoder decoder, ClassLoader classLoader, ObjectReader<Object[]> argsReader) {
+            this.decoder = decoder;
             this.classLoader = classLoader;
             this.argsReader = argsReader;
         }
@@ -112,13 +113,13 @@ public class MethodInvocationSerializer implements KryoAwareSerializer<MethodInv
             return argsReader.read();
         }
 
-        private Method readMethod() throws ClassNotFoundException, NoSuchMethodException {
-            int methodId = input.readInt(true);
+        private Method readMethod() throws ClassNotFoundException, NoSuchMethodException, IOException {
+            int methodId = decoder.readSmallInt();
             Method method = methods.get(methodId);
             if (method == null) {
                 Class<?> declaringClass = readType();
-                String methodName = input.readString();
-                int paramCount = input.readInt(true);
+                String methodName = decoder.readString();
+                int paramCount = decoder.readSmallInt();
                 Class<?>[] paramTypes = new Class<?>[paramCount];
                 for (int i = 0; i < paramTypes.length; i++) {
                     paramTypes[i] = readType();
@@ -129,8 +130,8 @@ public class MethodInvocationSerializer implements KryoAwareSerializer<MethodInv
             return method;
         }
 
-        private Class<?> readType() throws ClassNotFoundException {
-            String typeName = input.readString();
+        private Class<?> readType() throws ClassNotFoundException, IOException {
+            String typeName = decoder.readString();
             Class<?> paramType = PRIMITIVE_TYPES.get(typeName);
             if (paramType == null) {
                 paramType = classLoader.loadClass(typeName);
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
index 03e674c..83e3acc 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
@@ -34,7 +34,6 @@ public class InetAddressFactory {
     private final Object lock = new Object();
     private List<InetAddress> localAddresses;
     private List<InetAddress> remoteAddresses;
-    private Collection<InetAddress> allAddresses;
 
     /**
      * Determines the name of the local machine.
@@ -54,7 +53,7 @@ public class InetAddressFactory {
         try {
             synchronized (lock) {
                 init();
-                return allAddresses.contains(address);
+                return localAddresses.contains(address);
             }
         } catch (Exception e) {
             throw new RuntimeException("Could not determine the IP addresses for this machine.", e);
@@ -113,14 +112,13 @@ public class InetAddressFactory {
 
         localAddresses = new ArrayList<InetAddress>();
         remoteAddresses = new ArrayList<InetAddress>();
-        allAddresses = new HashSet<InetAddress>();
 
         Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
         while (interfaces.hasMoreElements()) {
             NetworkInterface networkInterface = interfaces.nextElement();
             LOGGER.debug("Adding IP addresses for network interface {}", networkInterface.getName());
             try {
-                Boolean isLoopbackInterface = null;
+                Boolean isLoopbackInterface;
                 try {
                     isLoopbackInterface = loopbackMethod == null ? null : (Boolean) loopbackMethod.invoke(networkInterface);
                 } catch (InvocationTargetException e) {
@@ -135,7 +133,6 @@ public class InetAddressFactory {
                 Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
                 while (candidates.hasMoreElements()) {
                     InetAddress candidate = candidates.nextElement();
-                    allAddresses.add(candidate);
                     if (isLoopbackInterface == null) {
                         // Don't know if this is a loopback interface or not
                         if (candidate.isLoopbackAddress()) {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnectCompletion.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnectCompletion.java
new file mode 100644
index 0000000..56e1ac1
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnectCompletion.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.inet;
+
+import org.gradle.messaging.remote.internal.ConnectCompletion;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+
+import java.nio.channels.SocketChannel;
+
+class SocketConnectCompletion implements ConnectCompletion {
+    private final SocketChannel socket;
+
+    public SocketConnectCompletion(SocketChannel socket) {
+        this.socket = socket;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s to %s", socket.socket().getLocalSocketAddress(), socket.socket().getRemoteSocketAddress());
+    }
+
+    public <T> Connection<T> create(ClassLoader messageClassLoader) {
+        return new SocketConnection<T>(socket, new DefaultMessageSerializer<T>(messageClassLoader));
+    }
+
+    public <T> Connection<T> create(MessageSerializer<T> serializer) {
+        return new SocketConnection<T>(socket, serializer);
+    }
+}
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 bcf40c2..ed15ca9 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
@@ -17,7 +17,7 @@
 package org.gradle.messaging.remote.internal.inet;
 
 import com.google.common.base.Objects;
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.internal.Connection;
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 37c40a3..5e0c4cd 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
@@ -17,18 +17,14 @@
 package org.gradle.messaging.remote.internal.inet;
 
 import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.ConnectionAcceptor;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
-import org.gradle.messaging.remote.internal.IncomingConnector;
-import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,11 +47,7 @@ public class TcpIncomingConnector implements IncomingConnector {
         this.idGenerator = idGenerator;
     }
 
-    public <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, ClassLoader classLoader, boolean allowRemote) {
-        return accept(action, new DefaultMessageSerializer<T>(classLoader), allowRemote);
-    }
-
-    public <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, MessageSerializer<T> serializer, boolean allowRemote) {
+    public ConnectionAcceptor accept(Action<ConnectCompletion> action, boolean allowRemote) {
         final ServerSocketChannel serverSocket;
         int localPort;
         try {
@@ -72,7 +64,7 @@ public class TcpIncomingConnector implements IncomingConnector {
         LOGGER.debug("Listening on {}.", address);
 
         final StoppableExecutor executor = executorFactory.create(String.format("Incoming %s TCP Connector on port %s", allowRemote ? "remote" : "local", localPort));
-        executor.execute(new Receiver<T>(serverSocket, action, serializer, allowRemote));
+        executor.execute(new Receiver(serverSocket, action, allowRemote));
 
         return new ConnectionAcceptor() {
             public Address getAddress() {
@@ -90,16 +82,14 @@ public class TcpIncomingConnector implements IncomingConnector {
         };
     }
 
-    private class Receiver<T> implements Runnable {
+    private class Receiver implements Runnable {
         private final ServerSocketChannel serverSocket;
-        private final Action<ConnectEvent<Connection<T>>> action;
-        private final MessageSerializer<T> serializer;
+        private final Action<ConnectCompletion> action;
         private final boolean allowRemote;
 
-        public Receiver(ServerSocketChannel serverSocket, Action<ConnectEvent<Connection<T>>> action, MessageSerializer<T> serializer, boolean allowRemote) {
+        public Receiver(ServerSocketChannel serverSocket, Action<ConnectCompletion> action, boolean allowRemote) {
             this.serverSocket = serverSocket;
             this.action = action;
-            this.serializer = serializer;
             this.allowRemote = allowRemote;
         }
 
@@ -107,7 +97,7 @@ public class TcpIncomingConnector implements IncomingConnector {
             try {
                 try {
                     while (true) {
-                        SocketChannel socket = serverSocket.accept();
+                        final SocketChannel socket = serverSocket.accept();
                         InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
                         InetAddress remoteInetAddress = remoteSocketAddress.getAddress();
                         if (!allowRemote && !addressFactory.isLocal(remoteInetAddress)) {
@@ -115,13 +105,8 @@ public class TcpIncomingConnector implements IncomingConnector {
                             socket.close();
                             continue;
                         }
-
-                        SocketConnection<T> connection = new SocketConnection<T>(socket, serializer);
-                        Address localAddress = connection.getLocalAddress();
-                        Address remoteAddress = connection.getRemoteAddress();
-
-                        LOGGER.debug("Accepted connection from {} to {}.", remoteAddress, localAddress);
-                        action.execute(new ConnectEvent<Connection<T>>(connection, localAddress, remoteAddress));
+                        LOGGER.debug("Accepted connection from {} to {}.", socket.socket().getRemoteSocketAddress(), socket.socket().getLocalSocketAddress());
+                        action.execute(new SocketConnectCompletion(socket));
                     }
                 } catch (ClosedChannelException e) {
                     // Ignore
@@ -132,5 +117,7 @@ public class TcpIncomingConnector implements IncomingConnector {
                 CompositeStoppable.stoppable(serverSocket).stop();
             }
         }
+
     }
+
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
index 5ba5761..364414b 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
@@ -17,7 +17,9 @@
 package org.gradle.messaging.remote.internal.inet;
 
 import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.*;
+import org.gradle.messaging.remote.internal.ConnectCompletion;
+import org.gradle.messaging.remote.internal.ConnectException;
+import org.gradle.messaging.remote.internal.OutgoingConnector;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,11 +32,7 @@ import java.util.List;
 public class TcpOutgoingConnector implements OutgoingConnector {
     private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
 
-    public <T> Connection<T> connect(Address destinationAddress, ClassLoader messageClassLoader) throws ConnectException {
-        return connect(destinationAddress, new DefaultMessageSerializer<T>(messageClassLoader));
-    }
-
-    public <T> Connection<T> connect(Address destinationAddress, MessageSerializer<T> serializer) throws ConnectException {
+    public ConnectCompletion connect(Address destinationAddress) throws ConnectException {
         if (!(destinationAddress instanceof InetEndpoint)) {
             throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
         }
@@ -58,8 +56,8 @@ public class TcpOutgoingConnector implements OutgoingConnector {
                     lastFailure = e;
                     continue;
                 }
-                LOGGER.debug("Connected to address {}.", candidate);
-                return new SocketConnection<T>(socketChannel, serializer);
+                LOGGER.debug("Connected to address {}.", socketChannel.socket().getRemoteSocketAddress());
+                return new SocketConnectCompletion(socketChannel);
             }
             throw new ConnectException(String.format("Could not connect to server %s. Tried addresses: %s.",
                     destinationAddress, candidateAddresses), lastFailure);
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractCollectionSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractCollectionSerializer.java
new file mode 100644
index 0000000..6ad1c56
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractCollectionSerializer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.util.Collection;
+
+public class AbstractCollectionSerializer<T> {
+    protected final Serializer<T> entrySerializer;
+
+    public AbstractCollectionSerializer(Serializer<T> entrySerializer) {
+        this.entrySerializer = entrySerializer;
+    }
+
+    protected void readValues(Decoder decoder, Collection<T> values) throws Exception {
+        int size = decoder.readInt();
+        for (int i = 0; i < size; i++) {
+            values.add(entrySerializer.read(decoder));
+        }
+    }
+
+    protected void writeValues(Encoder encoder, Collection<T> value) throws Exception {
+        encoder.writeInt(value.size());
+        for (T t : value) {
+            entrySerializer.write(encoder, t);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractDecoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractDecoder.java
new file mode 100644
index 0000000..8a32b56
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractDecoder.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class AbstractDecoder implements Decoder {
+    private DecoderStream stream;
+
+    public InputStream getInputStream() {
+        if (stream == null) {
+            stream = new DecoderStream();
+        }
+        return stream;
+    }
+
+    public void readBytes(byte[] buffer) throws IOException {
+        readBytes(buffer, 0, buffer.length);
+    }
+
+    public byte[] readBinary() throws EOFException, IOException {
+        int size = readSmallInt();
+        byte[] result = new byte[size];
+        readBytes(result);
+        return result;
+    }
+
+    public int readSmallInt() throws EOFException, IOException {
+        return readInt();
+    }
+
+    public long readSmallLong() throws EOFException, IOException {
+        return readLong();
+    }
+
+    public String readNullableString() throws EOFException, IOException {
+        if (readBoolean()) {
+            return readString();
+        } else {
+            return null;
+        }
+    }
+
+    public void skipBytes(long count) throws EOFException, IOException {
+        long remaining = count;
+        while (remaining > 0) {
+            long skipped = maybeSkip(remaining);
+            if (skipped <= 0) {
+                break;
+            }
+            remaining -= skipped;
+        }
+        if (remaining > 0) {
+            throw new EOFException();
+        }
+    }
+
+    protected abstract int maybeReadBytes(byte[] buffer, int offset, int count) throws IOException;
+
+    protected abstract long maybeSkip(long count) throws IOException;
+
+    private class DecoderStream extends InputStream {
+        byte[] buffer = new byte[1];
+
+        @Override
+        public long skip(long n) throws IOException {
+            return maybeSkip(n);
+        }
+
+        @Override
+        public int read() throws IOException {
+            int read = maybeReadBytes(buffer, 0, 1);
+            if (read <= 0) {
+                return read;
+            }
+            return buffer[0] & 0xff;
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            return maybeReadBytes(buffer, 0, buffer.length);
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int count) throws IOException {
+            return maybeReadBytes(buffer, offset, count);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractEncoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractEncoder.java
new file mode 100644
index 0000000..9e1b92c
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/AbstractEncoder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import org.gradle.api.Nullable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public abstract class AbstractEncoder implements Encoder {
+    private EncoderStream stream;
+
+    public OutputStream getOutputStream() {
+        if (stream == null) {
+            stream = new EncoderStream();
+        }
+        return stream;
+    }
+
+    public void writeBytes(byte[] bytes) throws IOException {
+        writeBytes(bytes, 0, bytes.length);
+    }
+
+    public void writeBinary(byte[] bytes) throws IOException {
+        writeBinary(bytes, 0, bytes.length);
+    }
+
+    public void writeBinary(byte[] bytes, int offset, int count) throws IOException {
+        writeSmallInt(count);
+        writeBytes(bytes, offset, count);
+    }
+
+    public void writeSmallInt(int value) throws IOException {
+        writeInt(value);
+    }
+
+    public void writeSmallLong(long value) throws IOException {
+        writeLong(value);
+    }
+
+    public void writeNullableString(@Nullable CharSequence value) throws IOException {
+        if (value == null) {
+            writeBoolean(false);
+        } else {
+            writeBoolean(true);
+            writeString(value.toString());
+        }
+    }
+
+    private class EncoderStream extends OutputStream {
+        @Override
+        public void write(byte[] buffer) throws IOException {
+            writeBytes(buffer);
+        }
+
+        @Override
+        public void write(byte[] buffer, int offset, int length) throws IOException {
+            writeBytes(buffer, offset, length);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            writeByte((byte)b);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/BaseSerializerFactory.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/BaseSerializerFactory.java
new file mode 100644
index 0000000..1ee5d01
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/BaseSerializerFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.io.File;
+
+public class BaseSerializerFactory {
+    public static final Serializer<String> STRING_SERIALIZER = new StringSerializer();
+    public static final Serializer LONG_SERIALIZER = new LongSerializer();
+    public static final Serializer FILE_SERIALIZER = new FileSerializer();
+
+    public <T> Serializer<T> getSerializerFor(Class<T> type) {
+        if (type.equals(String.class)) {
+            @SuppressWarnings("unchecked")
+            Serializer<T> stringSerializer = (Serializer<T>) STRING_SERIALIZER;
+            return stringSerializer;
+        }
+        if (type.equals(Long.class)) {
+            return LONG_SERIALIZER;
+        }
+        if (type.equals(File.class)) {
+            return FILE_SERIALIZER;
+        }
+        return new DefaultSerializer<T>(type.getClassLoader());
+    }
+
+    private static class LongSerializer implements Serializer<Long> {
+        public Long read(Decoder decoder) throws Exception {
+            return decoder.readLong();
+        }
+
+        public void write(Encoder encoder, Long value) throws Exception {
+            encoder.writeLong(value);
+        }
+    }
+
+    private static class StringSerializer implements Serializer<String> {
+        public String read(Decoder decoder) throws Exception {
+            return decoder.readString();
+        }
+
+        public void write(Encoder encoder, String value) throws Exception {
+            encoder.writeString(value);
+        }
+    }
+
+    private static class FileSerializer implements Serializer<File> {
+        public File read(Decoder decoder) throws Exception {
+            return new File(decoder.readString());
+        }
+
+        public void write(Encoder encoder, File value) throws Exception {
+            encoder.writeString(value.getPath());
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DataStreamBackedSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DataStreamBackedSerializer.java
deleted file mode 100644
index 9b40ce6..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DataStreamBackedSerializer.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.messaging.serialize;
-
-import java.io.*;
-
-public abstract class DataStreamBackedSerializer<T> implements Serializer<T> {
-    public T read(InputStream instr) throws Exception {
-        DataInputStream dataInputStream = new DataInputStream(instr);
-        return read((DataInput) dataInputStream);
-    }
-
-    public void write(OutputStream outstr, T value) throws Exception {
-        DataOutputStream output = new DataOutputStream(outstr);
-        write((DataOutput) output, value);
-        output.flush();
-    }
-
-    public abstract T read(DataInput dataInput) throws Exception;
-
-    public abstract void write(DataOutput dataOutput, T value) throws IOException;
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Decoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Decoder.java
new file mode 100644
index 0000000..1233a66
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Decoder.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import org.gradle.api.Nullable;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides a way to decode structured data from a backing byte stream.
+ */
+public interface Decoder {
+    /**
+     * Returns an InputStream which can be used to read raw bytes.
+     */
+    InputStream getInputStream();
+
+    /**
+     * Reads a signed 64 bit long value. Can read any value that was written using {@link Encoder#writeLong(long)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the long value can be fully read.
+     */
+    long readLong() throws EOFException, IOException;
+
+    /**
+     * Reads a signed 64 bit int value. Can read any value that was written using {@link Encoder#writeSmallLong(int)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the int value can be fully read.
+     */
+    long readSmallLong() throws EOFException, IOException;
+
+    /**
+     * Reads a signed 32 bit int value. Can read any value that was written using {@link Encoder#writeInt(int)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the int value can be fully read.
+     */
+    int readInt() throws EOFException, IOException;
+
+    /**
+     * Reads a signed 32 bit int value. Can read any value that was written using {@link Encoder#writeSmallInt(int)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the int value can be fully read.
+     */
+    int readSmallInt() throws EOFException, IOException;
+
+    /**
+     * Reads a boolean value. Can read any value that was written using {@link Encoder#writeBoolean(boolean)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the boolean value can be fully read.
+     */
+    boolean readBoolean() throws EOFException, IOException;
+
+    /**
+     * Reads a non-null string value. Can read any value that was written using {@link Encoder#writeString(CharSequence)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the string can be fully read.
+     */
+    String readString() throws EOFException, IOException;
+
+    /**
+     * Reads a nullable string value. Can reads any value that was written using {@link Encoder#writeNullableString(CharSequence)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the string can be fully read.
+     */
+    @Nullable
+    String readNullableString() throws EOFException, IOException;
+
+    /**
+     * Reads a byte value. Can read any byte value that was written using one of the raw byte methods on {@link Encoder}, such as {@link Encoder#writeByte(byte)} or {@link Encoder#getOutputStream()}
+     *
+     * @throws EOFException when the end of the byte stream is reached.
+     */
+    byte readByte() throws EOFException, IOException;
+
+    /**
+     * Reads bytes into the given buffer, filling the buffer. Can read any byte values that were written using one of the raw byte methods on {@link Encoder}, such as {@link
+     * Encoder#writeBytes(byte[])} or {@link Encoder#getOutputStream()}
+     *
+     * @throws EOFException when the end of the byte stream is reached before the buffer is full.
+     */
+    void readBytes(byte[] buffer) throws EOFException, IOException;
+
+    /**
+     * Reads the specified number of bytes into the given buffer. Can read any byte values that were written using one of the raw byte methods on {@link Encoder}, such as {@link
+     * Encoder#writeBytes(byte[])} or {@link Encoder#getOutputStream()}
+     *
+     * @throws EOFException when the end of the byte stream is reached before the specified number of bytes were read.
+     */
+    void readBytes(byte[] buffer, int offset, int count) throws EOFException, IOException;
+
+    /**
+     * Reads a byte array. Can read any byte array written using {@link Encoder#writeBinary(byte[])} or {@link Encoder#writeBinary(byte[], int, int)}.
+     *
+     * @throws EOFException when the end of the byte stream is reached before the byte array was fully read.
+     */
+    byte[] readBinary() throws EOFException, IOException;
+
+    /**
+     * Skips the given number of bytes. Can skip over any byte values that were written using one of the raw byte methods on {@link Encoder}.
+     */
+    void skipBytes(long count) throws EOFException, IOException;
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java
index cf9a115..2a3c7ef 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java
@@ -17,7 +17,9 @@ package org.gradle.messaging.serialize;
 
 import org.gradle.internal.io.ClassLoaderObjectInputStream;
 
-import java.io.*;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.StreamCorruptedException;
 
 public class DefaultSerializer<T> implements Serializer<T> {
     private ClassLoader classLoader;
@@ -38,16 +40,16 @@ public class DefaultSerializer<T> implements Serializer<T> {
         this.classLoader = classLoader;
     }
 
-    public T read(InputStream instr) throws Exception {
+    public T read(Decoder decoder) throws Exception {
         try {
-            return (T) new ClassLoaderObjectInputStream(instr, classLoader).readObject();
+            return (T) new ClassLoaderObjectInputStream(decoder.getInputStream(), classLoader).readObject();
         } catch (StreamCorruptedException e) {
             return null;
         }
     }
 
-    public void write(OutputStream outstr, T value) throws IOException {
-        ObjectOutputStream objectStr = new ObjectOutputStream(outstr);
+    public void write(Encoder encoder, T value) throws IOException {
+        ObjectOutputStream objectStr = new ObjectOutputStream(encoder.getOutputStream());
         objectStr.writeObject(value);
         objectStr.flush();
     }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializerRegistry.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializerRegistry.java
new file mode 100644
index 0000000..8b9be4d
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializerRegistry.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class DefaultSerializerRegistry<T> implements SerializerRegistry<T> {
+    private final Map<Class<?>, Serializer<?>> serializerMap = new TreeMap<Class<?>, Serializer<?>>(new Comparator<Class<?>>() {
+        public int compare(Class<?> o1, Class<?> o2) {
+            return o1.getName().compareTo(o2.getName());
+        }
+    });
+
+    public <U extends T> void register(Class<U> implementationType, Serializer<U> serializer) {
+        serializerMap.put(implementationType, serializer);
+    }
+
+    public Serializer<T> build() {
+        if (serializerMap.size() == 1) {
+            return (Serializer<T>) serializerMap.values().iterator().next();
+        }
+        TaggedTypeSerializer<T> serializer = new TaggedTypeSerializer<T>();
+        for (Map.Entry<Class<?>, Serializer<?>> entry : serializerMap.entrySet()) {
+            serializer.add(entry.getKey(), entry.getValue());
+        }
+        return serializer;
+    }
+
+    private static class TypeInfo {
+        final byte tag;
+        final Serializer serializer;
+
+        private TypeInfo(byte tag, Serializer serializer) {
+            this.tag = tag;
+            this.serializer = serializer;
+        }
+    }
+
+    private static class TaggedTypeSerializer<T> implements Serializer<T> {
+        private final Map<Class<?>, TypeInfo> serializersByType = new HashMap<Class<?>, TypeInfo>();
+        private final Map<Byte, TypeInfo> serializersByTag = new HashMap<Byte, TypeInfo>();
+
+        private <T> void add(Class<?> type, Serializer<?> serializer) {
+            TypeInfo typeInfo = new TypeInfo((byte) serializersByTag.size(), serializer);
+            serializersByType.put(type, typeInfo);
+            serializersByTag.put(typeInfo.tag, typeInfo);
+        }
+
+        public T read(Decoder decoder) throws Exception {
+            byte tag = decoder.readByte();
+            TypeInfo typeInfo = serializersByTag.get(tag);
+            if (typeInfo == null) {
+                throw new IllegalArgumentException(String.format("Unexpected type tag %d found.", tag));
+            }
+            return (T) typeInfo.serializer.read(decoder);
+        }
+
+        public void write(Encoder encoder, T value) throws Exception {
+            Class<?> targetType = value instanceof Throwable ? Throwable.class : value.getClass();
+            TypeInfo typeInfo = serializersByType.get(targetType);
+            if (typeInfo == null) {
+                throw new IllegalArgumentException(String.format("Don't know how to serialize an object of type %s.", value.getClass().getName()));
+            }
+            encoder.writeByte(typeInfo.tag);
+            typeInfo.serializer.write(encoder, value);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Encoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Encoder.java
new file mode 100644
index 0000000..c1b753e
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Encoder.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import org.gradle.api.Nullable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public interface Encoder {
+    /**
+     * Returns an OutputStream that can be used to write byte content to the stream.
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * Writes a raw byte value to the stream.
+     */
+    void writeByte(byte value) throws IOException;
+
+    /**
+     * Writes the given raw bytes to the stream. Does not encode any length information.
+     */
+    void writeBytes(byte[] bytes) throws IOException;
+
+    /**
+     * Writes the given raw bytes to the stream. Does not encode any length information.
+     */
+    void writeBytes(byte[] bytes, int offset, int count) throws IOException;
+
+    /**
+     * Writes the given byte array to the stream. Encodes the bytes and length information.
+     */
+    void writeBinary(byte[] bytes) throws IOException;
+
+    /**
+     * Writes the given byte array to the stream. Encodes the bytes and length information.
+     */
+    void writeBinary(byte[] bytes, int offset, int count) throws IOException;
+
+    /**
+     * Writes a signed 64 bit long value. The implementation may encode the value as a variable number of bytes, not necessarily as 8 bytes.
+     */
+    void writeLong(long value) throws IOException;
+
+    /**
+     * Writes a signed 64 bit long value whose value is likely to be small and positive but may not be. The implementation may encode the value in a way that is more efficient for small positive
+     * values.
+     */
+    void writeSmallLong(long value) throws IOException;
+
+    /**
+     * Writes a signed 32 bit int value. The implementation may encode the value as a variable number of bytes, not necessarily as 4 bytes.
+     */
+    void writeInt(int value) throws IOException;
+
+    /**
+     * Writes a signed 32 bit int value whose value is likely to be small and positive but may not be. The implementation may encode the value in a way that
+     * is more efficient for small positive values.
+     */
+    void writeSmallInt(int value) throws IOException;
+
+    /**
+     * Writes a boolean value.
+     */
+    void writeBoolean(boolean value) throws IOException;
+
+    /**
+     * Writes a non-null string value.
+     */
+    void writeString(CharSequence value) throws IOException;
+
+    /**
+     * Writes a nullable string value.
+     */
+    void writeNullableString(@Nullable CharSequence value) throws IOException;
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/FlushableEncoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/FlushableEncoder.java
new file mode 100644
index 0000000..ce0e7a7
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/FlushableEncoder.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.io.Flushable;
+import java.io.IOException;
+
+public interface FlushableEncoder extends Encoder, Flushable {
+    void flush() throws IOException;
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/InputStreamBackedDecoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/InputStreamBackedDecoder.java
new file mode 100644
index 0000000..8e762a9
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/InputStreamBackedDecoder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.io.*;
+
+public class InputStreamBackedDecoder extends AbstractDecoder implements Decoder, Closeable {
+    private final DataInputStream inputStream;
+
+    public InputStreamBackedDecoder(InputStream inputStream) {
+        this.inputStream = new DataInputStream(inputStream);
+    }
+
+    @Override
+    protected int maybeReadBytes(byte[] buffer, int offset, int count) throws IOException {
+        return inputStream.read(buffer, offset, count);
+    }
+
+    @Override
+    protected long maybeSkip(long count) throws IOException {
+        return inputStream.skip(count);
+    }
+
+    public long readLong() throws IOException {
+        return inputStream.readLong();
+    }
+
+    public int readInt() throws EOFException, IOException {
+        return inputStream.readInt();
+    }
+
+    public boolean readBoolean() throws EOFException, IOException {
+        return inputStream.readBoolean();
+    }
+
+    public String readString() throws EOFException, IOException {
+        return inputStream.readUTF();
+    }
+
+    public byte readByte() throws IOException {
+        return (byte)(inputStream.readByte() & 0xff);
+    }
+
+    public void readBytes(byte[] buffer, int offset, int count) throws IOException {
+        inputStream.readFully(buffer, offset, count);
+    }
+
+    public void close() throws IOException {
+        inputStream.close();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ListSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ListSerializer.java
new file mode 100644
index 0000000..f9f3c83
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ListSerializer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListSerializer<T> extends AbstractCollectionSerializer<T> implements Serializer<List<T>> {
+
+    public ListSerializer(Serializer<T> entrySerializer) {
+        super(entrySerializer);
+    }
+
+    public List<T> read(Decoder decoder) throws Exception {
+        List<T> values = new ArrayList<T>();
+        readValues(decoder, values);
+        return values;
+    }
+
+    public void write(Encoder encoder, List<T> value) throws Exception {
+        writeValues(encoder, value);
+    }
+
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/LongSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/LongSerializer.java
new file mode 100644
index 0000000..48a8a1d
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/LongSerializer.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.messaging.serialize;
+
+public class LongSerializer implements Serializer<Long> {
+    public Long read(Decoder decoder) throws Exception {
+        return decoder.readLong();
+    }
+
+    public void write(Encoder encoder, Long value) throws Exception {
+        if (value == null) {
+            throw new IllegalArgumentException("This serializer does not serialize null values.");
+        }
+        encoder.writeLong(value);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/MapSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/MapSerializer.java
new file mode 100644
index 0000000..6764ab2
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/MapSerializer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MapSerializer<U, V> implements Serializer<Map<U, V>> {
+    private final Serializer<U> keySerializer;
+    private final Serializer<V> valueSerializer;
+
+    public MapSerializer(Serializer<U> keySerializer, Serializer<V> valueSerializer) {
+        this.keySerializer = keySerializer;
+        this.valueSerializer = valueSerializer;
+    }
+
+    public Map<U, V> read(Decoder decoder) throws Exception {
+        int size = decoder.readInt();
+        Map<U, V> valueMap = new LinkedHashMap<U, V>(size);
+        for (int i = 0; i < size; i++) {
+            U key = keySerializer.read(decoder);
+            V value = valueSerializer.read(decoder);
+            valueMap.put(key, value);
+        }
+        return valueMap;
+    }
+
+    public void write(Encoder encoder, Map<U, V> value) throws Exception {
+        encoder.writeInt(value.size());
+        for (Map.Entry<U, V> entry : value.entrySet()) {
+            keySerializer.write(encoder, entry.getKey());
+            valueSerializer.write(encoder, entry.getValue());
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/NullSafeStringSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/NullSafeStringSerializer.java
new file mode 100644
index 0000000..a81747a
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/NullSafeStringSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+public class NullSafeStringSerializer implements Serializer<String> {
+    public String read(Decoder decoder) throws Exception {
+        return decoder.readNullableString();
+    }
+
+    public void write(Encoder encoder, String value) throws Exception {
+        encoder.writeNullableString(value);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/OutputStreamBackedEncoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/OutputStreamBackedEncoder.java
new file mode 100644
index 0000000..a70cbba
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/OutputStreamBackedEncoder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.io.Closeable;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class OutputStreamBackedEncoder extends AbstractEncoder implements Closeable, FlushableEncoder {
+    private final DataOutputStream outputStream;
+
+    public OutputStreamBackedEncoder(OutputStream outputStream) {
+        this.outputStream = new DataOutputStream(outputStream);
+    }
+
+    public void writeLong(long value) throws IOException {
+        outputStream.writeLong(value);
+    }
+
+    public void writeInt(int value) throws IOException {
+        outputStream.writeInt(value);
+    }
+
+    public void writeBoolean(boolean value) throws IOException {
+        outputStream.writeBoolean(value);
+    }
+
+    public void writeString(CharSequence value) throws IOException {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot encode a null string.");
+        }
+        outputStream.writeUTF(value.toString());
+    }
+
+    public void writeByte(byte value) throws IOException {
+        outputStream.writeByte(value);
+    }
+
+    public void writeBytes(byte[] bytes, int offset, int count) throws IOException {
+        outputStream.write(bytes, offset, count);
+    }
+
+    public void flush() throws IOException {
+        outputStream.flush();
+    }
+
+    public void close() throws IOException {
+        outputStream.close();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
index 9388801..9dbf770 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
@@ -15,18 +15,15 @@
  */
 package org.gradle.messaging.serialize;
 
-import java.io.InputStream;
-import java.io.OutputStream;
-
 public interface Serializer<T> {
     /**
      * Reads the next object from the given stream. The implementation must not perform any buffering, so that it reads only those bytes from the input stream that are
      * required to deserialize the next object.
      */
-    T read(InputStream instr) throws Exception;
+    T read(Decoder decoder) throws Exception;
 
     /**
      * Writes the given object to the given stream. The implementation must not perform any buffering.
      */
-    void write(OutputStream outstr, T value) throws Exception;
+    void write(Encoder encoder, T value) throws Exception;
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/SerializerRegistry.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/SerializerRegistry.java
new file mode 100644
index 0000000..1d8c05c
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/SerializerRegistry.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+public interface SerializerRegistry<T> {
+    <U extends T> void register(Class<U> implementationType, Serializer<U> serializer);
+
+    Serializer<T> build();
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/SetSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/SetSerializer.java
new file mode 100644
index 0000000..e7c1b74
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/SetSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class SetSerializer<T> extends AbstractCollectionSerializer<T> implements Serializer<Set<T>> {
+
+    public SetSerializer(Serializer<T> entrySerializer) {
+        super(entrySerializer);
+    }
+
+    public Set<T> read(Decoder decoder) throws Exception {
+        Set<T> values = new LinkedHashSet<T>();
+        readValues(decoder, values);
+        return values;
+    }
+
+    public void write(Encoder encoder, Set<T> value) throws Exception {
+        writeValues(encoder, value);
+    }
+
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java
index 86f4a06..cdbae50 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java
@@ -16,50 +16,50 @@
 
 package org.gradle.messaging.serialize.kryo;
 
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
 import org.gradle.messaging.remote.internal.Message;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
 import org.gradle.messaging.serialize.ObjectReader;
 import org.gradle.messaging.serialize.ObjectWriter;
 
-public class JavaSerializer<T> implements KryoAwareSerializer<T> {
+public class JavaSerializer<T> implements StatefulSerializer<T> {
     private final ClassLoader classLoader;
 
     public JavaSerializer(ClassLoader classLoader) {
         this.classLoader = classLoader;
     }
 
-    public ObjectReader<T> newReader(Input input) {
-        return new JavaReader<T>(input, classLoader);
+    public ObjectReader<T> newReader(Decoder decoder) {
+        return new JavaReader<T>(decoder, classLoader);
     }
 
-    public ObjectWriter<T> newWriter(Output output) {
-        return new JavaWriter<T>(output);
+    public ObjectWriter<T> newWriter(Encoder encoder) {
+        return new JavaWriter<T>(encoder);
     }
 
     private static class JavaReader<T> implements ObjectReader<T> {
-        private final Input input;
+        private final Decoder decoder;
         private final ClassLoader classLoader;
 
-        private JavaReader(Input input, ClassLoader classLoader) {
-            this.input = input;
+        private JavaReader(Decoder decoder, ClassLoader classLoader) {
+            this.decoder = decoder;
             this.classLoader = classLoader;
         }
 
         public T read() throws Exception {
-            return (T) Message.receive(input, classLoader);
+            return (T) Message.receive(decoder.getInputStream(), classLoader);
         }
     }
 
     private class JavaWriter<T> implements ObjectWriter<T> {
-        private final Output output;
+        private final Encoder encoder;
 
-        public JavaWriter(Output output) {
-            this.output = output;
+        public JavaWriter(Encoder encoder) {
+            this.encoder = encoder;
         }
 
         public void write(T value) throws Exception {
-            Message.send(value, output);
+            Message.send(value, encoder.getOutputStream());
         }
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoAwareSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoAwareSerializer.java
deleted file mode 100644
index 8d6608b..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoAwareSerializer.java
+++ /dev/null
@@ -1,28 +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.messaging.serialize.kryo;
-
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
-import org.gradle.messaging.serialize.ObjectReader;
-import org.gradle.messaging.serialize.ObjectWriter;
-
-public interface KryoAwareSerializer<T> {
-    ObjectReader<T> newReader(Input input);
-
-    ObjectWriter<T> newWriter(Output output);
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoBackedDecoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoBackedDecoder.java
new file mode 100644
index 0000000..2888262
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoBackedDecoder.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize.kryo;
+
+import com.esotericsoftware.kryo.KryoException;
+import com.esotericsoftware.kryo.io.Input;
+import org.gradle.messaging.serialize.AbstractDecoder;
+import org.gradle.messaging.serialize.Decoder;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Note that this decoder uses buffering, so will attempt to read beyond the end of the encoded data. This means you should use this type only when this decoder will be used to decode the entire
+ * stream.
+ */
+public class KryoBackedDecoder extends AbstractDecoder implements Decoder, Closeable {
+    private final Input input;
+    private final InputStream inputStream;
+    private long extraSkipped;
+
+    public KryoBackedDecoder(InputStream inputStream) {
+        this(inputStream, 4096);
+    }
+
+    public KryoBackedDecoder(InputStream inputStream, int bufferSize) {
+        this.inputStream = inputStream;
+        input = new Input(this.inputStream, bufferSize);
+    }
+
+    @Override
+    protected int maybeReadBytes(byte[] buffer, int offset, int count) {
+        return input.read(buffer, offset, count);
+    }
+
+    @Override
+    protected long maybeSkip(long count) throws IOException {
+        // Work around some bugs in Input.skip()
+        int remaining = input.limit() - input.position();
+        if (remaining == 0) {
+            long skipped = inputStream.skip(count);
+            if (skipped > 0) {
+                extraSkipped += skipped;
+            }
+            return skipped;
+        } else if (count <= remaining) {
+            input.setPosition(input.position() + (int) count);
+            return count;
+        } else {
+            input.setPosition(input.limit());
+            return remaining;
+        }
+    }
+
+    private RuntimeException maybeEndOfStream(KryoException e) throws EOFException {
+        if (e.getMessage().equals("Buffer underflow.")) {
+            throw (EOFException) (new EOFException().initCause(e));
+        }
+        throw e;
+    }
+
+    public byte readByte() throws EOFException {
+        try {
+            return input.readByte();
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public void readBytes(byte[] buffer, int offset, int count) throws EOFException {
+        try {
+            input.readBytes(buffer, offset, count);
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public long readLong() throws EOFException {
+        try {
+            return input.readLong();
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public long readSmallLong() throws EOFException, IOException {
+        try {
+            return input.readLong(true);
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public int readInt() throws EOFException {
+        try {
+            return input.readInt();
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public int readSmallInt() throws EOFException {
+        try {
+            return input.readInt(true);
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public boolean readBoolean() throws EOFException {
+        try {
+            return input.readBoolean();
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    public String readString() throws EOFException {
+        return readNullableString();
+    }
+
+    public String readNullableString() throws EOFException {
+        try {
+            return input.readString();
+        } catch (KryoException e) {
+            throw maybeEndOfStream(e);
+        }
+    }
+
+    /**
+     * Returns the total number of bytes consumed by this decoder. Some additional bytes may also be buffered by this decoder but have not been consumed.
+     */
+    public long getReadPosition() {
+        return input.total() + extraSkipped;
+    }
+
+    public void close() throws IOException {
+        input.close();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoBackedEncoder.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoBackedEncoder.java
new file mode 100644
index 0000000..86c4c46
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoBackedEncoder.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize.kryo;
+
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.api.Nullable;
+import org.gradle.messaging.serialize.AbstractEncoder;
+import org.gradle.messaging.serialize.FlushableEncoder;
+
+import java.io.Closeable;
+import java.io.OutputStream;
+
+public class KryoBackedEncoder extends AbstractEncoder implements FlushableEncoder, Closeable {
+    private final Output output;
+
+    public KryoBackedEncoder(OutputStream outputStream) {
+        this(outputStream, 4096);
+    }
+
+    public KryoBackedEncoder(OutputStream outputStream, int bufferSize) {
+        output = new Output(outputStream, bufferSize);
+    }
+
+    public void writeByte(byte value) {
+        output.writeByte(value);
+    }
+
+    public void writeBytes(byte[] bytes, int offset, int count) {
+        output.writeBytes(bytes, offset, count);
+    }
+
+    public void writeLong(long value) {
+        output.writeLong(value);
+    }
+
+    public void writeSmallLong(long value) {
+        output.writeLong(value, true);
+    }
+
+    public void writeInt(int value) {
+        output.writeInt(value);
+    }
+
+    public void writeSmallInt(int value) {
+        output.writeInt(value, true);
+    }
+
+    public void writeBoolean(boolean value) {
+        output.writeBoolean(value);
+    }
+
+    public void writeString(CharSequence value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot encode a null string.");
+        }
+        output.writeString(value);
+    }
+
+    public void writeNullableString(@Nullable CharSequence value) {
+        output.writeString(value);
+    }
+
+    /**
+     * Returns the total number of bytes written by this encoder, some of which is may still be buffered.
+     */
+    public int getWritePosition() {
+        return output.total();
+    }
+
+    public void flush() {
+        output.flush();
+    }
+
+    public void close() {
+        output.close();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoSerializer.java
deleted file mode 100644
index a0d5293..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoSerializer.java
+++ /dev/null
@@ -1,59 +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.messaging.serialize.kryo;
-
-import com.esotericsoftware.kryo.Kryo;
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
-import org.gradle.messaging.serialize.ObjectReader;
-import org.gradle.messaging.serialize.ObjectWriter;
-
-public class KryoSerializer<T> implements KryoAwareSerializer<T> {
-    public ObjectReader<T> newReader(Input input) {
-        return new KryoReader<T>(input);
-    }
-
-    public ObjectWriter<T> newWriter(Output output) {
-        return new KryoWriter<T>(output);
-    }
-
-    private static class KryoReader<T> implements ObjectReader<T> {
-        private final Input input;
-
-        private KryoReader(Input input) {
-            this.input = input;
-        }
-
-        public T read() throws Exception {
-            Kryo kryo = new Kryo();
-            return (T) kryo.readClassAndObject(input);
-        }
-    }
-
-    private class KryoWriter<T> implements ObjectWriter<T> {
-        private final Output output;
-
-        public KryoWriter(Output output) {
-            this.output = output;
-        }
-
-        public void write(T value) throws Exception {
-            Kryo kryo = new Kryo();
-            kryo.writeClassAndObject(output, value);
-        }
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/StatefulSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/StatefulSerializer.java
new file mode 100644
index 0000000..308fa67
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/StatefulSerializer.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.messaging.serialize.kryo;
+
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+
+public interface StatefulSerializer<T> {
+    ObjectReader<T> newReader(Decoder decoder);
+
+    ObjectWriter<T> newWriter(Encoder encoder);
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java
index 5af1c77..efe179c 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java
@@ -16,22 +16,22 @@
 
 package org.gradle.messaging.serialize.kryo;
 
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
 import org.gradle.messaging.serialize.ObjectReader;
 import org.gradle.messaging.serialize.ObjectWriter;
 
-public class TypeSafeSerializer<T> implements KryoAwareSerializer<Object> {
+public class TypeSafeSerializer<T> implements StatefulSerializer<Object> {
     private final Class<T> type;
-    private final KryoAwareSerializer<T> serializer;
+    private final StatefulSerializer<T> serializer;
 
-    public TypeSafeSerializer(Class<T> type, KryoAwareSerializer<T> serializer) {
+    public TypeSafeSerializer(Class<T> type, StatefulSerializer<T> serializer) {
         this.type = type;
         this.serializer = serializer;
     }
 
-    public ObjectReader<Object> newReader(Input input) {
-        final ObjectReader<T> reader = serializer.newReader(input);
+    public ObjectReader<Object> newReader(Decoder decoder) {
+        final ObjectReader<T> reader = serializer.newReader(decoder);
         return new ObjectReader<Object>() {
             public Object read() throws Exception {
                 return reader.read();
@@ -39,8 +39,8 @@ public class TypeSafeSerializer<T> implements KryoAwareSerializer<Object> {
         };
     }
 
-    public ObjectWriter<Object> newWriter(final Output output) {
-        final ObjectWriter<T> writer = serializer.newWriter(output);
+    public ObjectWriter<Object> newWriter(Encoder encoder) {
+        final ObjectWriter<T> writer = serializer.newWriter(encoder);
         return new ObjectWriter<Object>() {
             public void write(Object value) throws Exception {
                 writer.write(type.cast(value));
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
index 3aa7263..267125a 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
@@ -15,26 +15,86 @@
  */
 package org.gradle.messaging.remote.internal
 
-import spock.lang.Specification
+import org.gradle.internal.exceptions.AbstractMultiCauseException
 import spock.lang.Issue
-import spock.lang.Ignore
+import spock.lang.Specification
 
 class MessageTest extends Specification {
     GroovyClassLoader source = new GroovyClassLoader(getClass().classLoader)
     GroovyClassLoader dest = new GroovyClassLoader(getClass().classLoader)
 
-    def "replaces unserializable exception with placeholder"() {
+    def "can transport graph of exceptions"() {
+        def cause1 = new ExceptionWithState("nested-1", ["a", 1])
+        def cause2 = new IOException("nested-2")
+        def cause = new AbstractMultiCauseException("nested", cause1, cause2)
+        def original = new ExceptionWithExceptionField("message", cause)
+
+        when:
+        def transported = transport(new TestPayloadMessage(payload: original))
+
+        then:
+        transported.payload.class == ExceptionWithExceptionField
+        transported.payload.message == "message"
+
+        and:
+        transported.payload.throwable.class == AbstractMultiCauseException
+        transported.payload.throwable.message == "nested"
+
+        and:
+        transported.payload.throwable == transported.payload.cause
+
+        and:
+        transported.payload.throwable.causes.size() == 2
+        transported.payload.throwable.causes*.class == [ExceptionWithState, IOException]
+        transported.payload.throwable.causes*.message == ["nested-1", "nested-2"]
+
+        and:
+        transported.payload.throwable.causes[0].values == ["a", 1]
+    }
+
+    def "can transport exception with custom serialization"() {
+        def cause = new IOException("nested")
+        def original = new ExceptionWithCustomSerialization("message", cause)
+
+        when:
+        def transported = transport(original)
+
+        then:
+        transported.class == ExceptionWithCustomSerialization
+        transported.message == "message"
+
+        and:
+        transported.throwable.class == IOException
+        transported.throwable.message == "nested"
+    }
+
+    def "replaces exception with broken writeObject() method with placeholder"() {
         def cause = new RuntimeException("nested")
-        def original = new UnserializableException("message", cause)
+        def original = new BrokenWriteObjectException("message", cause)
 
         when:
         def transported = transport(original)
 
         then:
-        transported instanceof PlaceholderException
-        transported.message == original.message
-        transported.stackTrace == original.stackTrace
+        looksLike original, transported
+
+        and:
+        transported.cause.class == RuntimeException.class
+        transported.cause.message == "nested"
+        transported.cause.stackTrace == cause.getStackTrace()
+    }
+
+    def "replaces exception with field that cannot be serialized with placeholder"() {
+        def cause = new RuntimeException("nested")
+        def original = new ExceptionWithNonSerializableField("message", cause)
+
+        when:
+        def transported = transport(original)
+
+        then:
+        looksLike original, transported
 
+        and:
         transported.cause.class == RuntimeException.class
         transported.cause.message == "nested"
         transported.cause.stackTrace == cause.getStackTrace()
@@ -42,7 +102,7 @@ class MessageTest extends Specification {
 
     def "replaces nested unserializable exception with placeholder"() {
         def cause = new IOException("nested")
-        def original = new UnserializableException("message", cause)
+        def original = new BrokenWriteObjectException("message", cause)
         def outer = new RuntimeException("message", original)
 
         when:
@@ -54,10 +114,10 @@ class MessageTest extends Specification {
         transported.message == "message"
         transported.stackTrace == outer.stackTrace
 
-        transported.cause instanceof PlaceholderException
-        transported.cause.message == original.message
-        transported.cause.stackTrace == original.stackTrace
+        and:
+        looksLike original, transported.cause
 
+        and:
         transported.cause.cause.class == IOException
         transported.cause.cause.message == "nested"
         transported.cause.cause.stackTrace == cause.stackTrace
@@ -65,16 +125,15 @@ class MessageTest extends Specification {
 
     def "replaces undeserializable exception with placeholder"() {
         def cause = new RuntimeException("nested")
-        def original = new UndeserializableException("message", cause)
+        def original = new BrokenReadObjectException("message", cause)
 
         when:
         def transported = transport(original)
 
         then:
-        transported instanceof PlaceholderException
-        transported.message == original.message
-        transported.stackTrace == original.stackTrace
+        looksLike original, transported
 
+        and:
         transported.cause.class == RuntimeException.class
         transported.cause.message == "nested"
         transported.cause.stackTrace == cause.stackTrace
@@ -82,7 +141,7 @@ class MessageTest extends Specification {
 
     def "replaces nested undeserializable exception with placeholder"() {
         def cause = new RuntimeException("nested")
-        def original = new UndeserializableException("message", cause)
+        def original = new BrokenReadObjectException("message", cause)
         def outer = new RuntimeException("message", original)
 
         when:
@@ -94,10 +153,10 @@ class MessageTest extends Specification {
         transported.message == "message"
         transported.stackTrace == outer.stackTrace
 
-        transported.cause instanceof PlaceholderException
-        transported.cause.message == original.message
-        transported.cause.stackTrace == original.stackTrace
+        and:
+        looksLike original, transported.cause
 
+        and:
         transported.cause.cause.class == RuntimeException.class
         transported.cause.cause.message == "nested"
         transported.cause.cause.stackTrace == cause.stackTrace
@@ -105,7 +164,7 @@ class MessageTest extends Specification {
 
     def "replaces unserializable exception field with placeholder"() {
         def cause = new RuntimeException()
-        def original = new UndeserializableException("message", cause)
+        def original = new BrokenReadObjectException("message", cause)
         def outer = new ExceptionWithExceptionField("nested", original)
 
         when:
@@ -114,10 +173,10 @@ class MessageTest extends Specification {
         then:
         transported instanceof ExceptionWithExceptionField
 
-        transported.throwable instanceof PlaceholderException
-        transported.throwable.message == original.message
-        transported.throwable.stackTrace == original.stackTrace
+        and:
+        looksLike original, transported.throwable
 
+        and:
         transported.throwable == transported.cause
     }
 
@@ -156,39 +215,50 @@ class MessageTest extends Specification {
         def transported = transport(original)
 
         then:
-        transported instanceof PlaceholderException
-        transported.message == original.message
-        transported.stackTrace == original.stackTrace
+        looksLike original, transported
 
+        and:
         transported.cause.class == RuntimeException.class
         transported.cause.message == "nested"
         transported.cause.stackTrace == cause.stackTrace
     }
 
-    def "creates placeholder with toString() behaviour as original"() {
-        def cause = new IOException("nested")
-        def broken = new SerializableToStringException("message", cause)
+    def "transports exception with broken methods"() {
+        def broken = new CompletelyBrokenException()
 
         when:
         def transported = transport(broken)
-        transported.toString()
+
+        then:
+        transported.class == CompletelyBrokenException
+    }
+
+    def "transports unserializable exception with broken methods"() {
+        def broken = new CompletelyBrokenException() { def Object o = new Object() }
+
+        when:
+        def transported = transport(broken)
+
         then:
+        transported.class == PlaceholderException
+        transported.cause == null
+        transported.stackTrace.length == 0
 
-        def toStringException = thrown(RuntimeException)
-        toStringException.getMessage() == "broken toString"
+        when:
+        transported.message
+
+        then:
+        RuntimeException e = thrown()
+        e.message == 'broken getMessage()'
 
         when:
-        cause = new IOException("nested")
-        broken = new UnserializableToStringException("message", cause)
-        transported = transport(broken)
         transported.toString()
 
         then:
-        toStringException = thrown(PlaceholderException)
-        toStringException.getMessage() == "broken toString"
+        RuntimeException e2 = thrown()
+        e2.message == 'broken toString()'
     }
 
-    @Ignore
     @Issue("GRADLE-1996")
     def "can transport exception that implements writeReplace()"() {
         def original = new WriteReplaceException("original")
@@ -197,11 +267,28 @@ class MessageTest extends Specification {
         def transported = transport(original)
 
         then:
-        noExceptionThrown()
         transported instanceof WriteReplaceException
         transported.message == "replaced"
     }
 
+    def "can transport exception that implements readResolve()"() {
+        def original = new ReadReplaceException()
+
+        when:
+        def transported = transport(original)
+
+        then:
+        transported == ReadReplaceException.singleton
+    }
+
+    void looksLike(Throwable original, Throwable transported) {
+        assert transported instanceof PlaceholderException
+        assert transported.exceptionClassName == original.class.name
+        assert transported.message == original.message
+        assert transported.toString() == original.toString()
+        assert transported.stackTrace == original.stackTrace
+    }
+
     private Object transport(Object arg) {
         def outputStream = new ByteArrayOutputStream()
         Message.send(new TestPayloadMessage(payload: arg), outputStream)
@@ -215,6 +302,14 @@ class MessageTest extends Specification {
         def payload
     }
 
+    static class ExceptionWithNonSerializableField extends RuntimeException {
+        def canNotSerialize = new Object()
+
+        ExceptionWithNonSerializableField(String message, Throwable cause) {
+            super(message, cause)
+        }
+    }
+
     static class ExceptionWithExceptionField extends RuntimeException {
         Throwable throwable
 
@@ -224,8 +319,17 @@ class MessageTest extends Specification {
         }
     }
 
-    static class UnserializableException extends RuntimeException {
-        UnserializableException(String message, Throwable cause) {
+    static class ExceptionWithState extends RuntimeException {
+        List<?> values
+
+        ExceptionWithState(String message, List<?> values) {
+            super(message)
+            this.values = values
+        }
+    }
+
+    static class BrokenWriteObjectException extends RuntimeException {
+        BrokenWriteObjectException(String message, Throwable cause) {
             super(message, cause)
         }
 
@@ -234,37 +338,54 @@ class MessageTest extends Specification {
         }
     }
 
-    static class UnserializableToStringException extends RuntimeException {
-        UnserializableToStringException (String message, Throwable cause) {
-            super(message, cause)
+    static class CompletelyBrokenException extends RuntimeException {
+        @Override
+        String getMessage() {
+            throw new RuntimeException("broken getMessage()", null);
         }
 
+        @Override
         public String toString() {
-            throw new UnserializableException("broken toString", null);
+            throw new RuntimeException("broken toString()", null);
         }
 
-        private void writeObject(ObjectOutputStream outstr) throws IOException {
-            outstr.writeObject(new Object())
+        @Override
+        public Throwable getCause() {
+            throw new RuntimeException("broken getCause()", null);
+        }
+
+        @Override
+        StackTraceElement[] getStackTrace() {
+            throw new RuntimeException("broken getStackTrace()", null);
         }
     }
 
-    static class SerializableToStringException extends RuntimeException {
-        SerializableToStringException(String message, Throwable cause) {
+    static class BrokenReadObjectException extends RuntimeException {
+        BrokenReadObjectException(String message, Throwable cause) {
             super(message, cause)
         }
 
-        public String toString() {
-            throw new RuntimeException("broken toString", null);
+        private void readObject(ObjectInputStream outstr)  {
+            throw new RuntimeException("broken readObject()")
         }
     }
 
-    static class UndeserializableException extends RuntimeException {
-        UndeserializableException(String message, Throwable cause) {
-            super(message, cause)
+    static class ExceptionWithCustomSerialization extends RuntimeException {
+        def transient throwable
+
+        ExceptionWithCustomSerialization(String message, Throwable cause) {
+            super(message)
+            throwable = cause
         }
 
-        private void readObject(ObjectInputStream outstr) throws ClassNotFoundException {
-            throw new ClassNotFoundException()
+        private void writeObject(ObjectOutputStream outstr)  {
+            outstr.defaultWriteObject()
+            outstr.writeObject(throwable)
+        }
+
+        private void readObject(ObjectInputStream instr)  {
+            instr.defaultReadObject()
+            throwable = instr.readObject()
         }
     }
 
@@ -277,5 +398,13 @@ class MessageTest extends Specification {
             return new WriteReplaceException("replaced")
         }
     }
+
+    static class ReadReplaceException extends Exception {
+        static Exception singleton = new Exception("replaced")
+
+        private Object readResolve() {
+            return singleton
+        }
+    }
 }
 
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
index 315b7ff..f757d4d 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
@@ -22,7 +22,7 @@ import spock.lang.Issue
 class PlaceholderExceptionTest extends Specification {
     def "toString() generally produces same output as original exception"() {
         def original = new Exception("original exception")
-        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.toString(), null, original.cause)
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, null, original.toString(), null, original.cause)
         
         expect:
         placeholder.toString() == original.toString()
@@ -34,7 +34,7 @@ class PlaceholderExceptionTest extends Specification {
                 "fancy customized toString"
             }
         }
-        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.toString(), null, original.cause)
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, null, original.toString(), null, original.cause)
 
         expect:
         placeholder.toString() == original.toString()
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
index d2cca47..8ff9afe 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
@@ -67,7 +67,7 @@ class ProtocolStackTest extends ConcurrentSpecification {
         stack?.stop()
     }
 
-    @Timeout(5)
+    @Timeout(10)
     def "top protocol can dispatch incoming message during start"() {
         Protocol<String> protocol = Mock()
         def dispatched = startsAsyncAction()
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy
index 9966783..828be41 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy
@@ -20,11 +20,11 @@ import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier
 import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage
 import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream
 import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
-import org.gradle.messaging.serialize.kryo.KryoSerializer
+import org.gradle.messaging.serialize.kryo.JavaSerializer
 import spock.lang.Specification
 
 class InterHubMessageSerializerTest extends Specification {
-    final InterHubMessageSerializer serializer = new InterHubMessageSerializer(new KryoSerializer<Object>())
+    final InterHubMessageSerializer serializer = new InterHubMessageSerializer(new JavaSerializer<Object>())
 
     def "can serialise ChannelMessage"() {
         def channelId = new ChannelIdentifier("channel name")
@@ -38,7 +38,6 @@ class InterHubMessageSerializerTest extends Specification {
         result instanceof ChannelMessage
         result.channel == channelId
         result.payload == "payload"
-        serialized.length == 23
     }
 
     def "replaces a channel ID that has already been seen with an integer value"() {
@@ -46,6 +45,10 @@ class InterHubMessageSerializerTest extends Specification {
         def message1 = new ChannelMessage(channelId, "payload 1")
         def message2 = new ChannelMessage(channelId, "payload 2")
 
+        given:
+        def message1Serialized = serialize(message1)
+        def message2Serialized = serialize(message2)
+
         when:
         def serialized = serialize(message1, message2)
         def result = deserializeMultiple(serialized, 2)
@@ -57,7 +60,7 @@ class InterHubMessageSerializerTest extends Specification {
         result[1] instanceof ChannelMessage
         result[1].channel == channelId
         result[1].payload == "payload 2"
-        serialized.length == 38
+        serialized.length < message1Serialized.length + message2Serialized.length
     }
 
     def "can serialize messages for multiple channels"() {
@@ -81,7 +84,6 @@ class InterHubMessageSerializerTest extends Specification {
         result[2] instanceof ChannelMessage
         result[2].channel == channelId1
         result[2].payload == "payload 3"
-        serialized.length == 57
     }
 
     def "can serialise EndOfStream"() {
@@ -91,7 +93,6 @@ class InterHubMessageSerializerTest extends Specification {
 
         then:
         result instanceof EndOfStream
-        serialized.length == 1
     }
 
     def serialize(InterHubMessage... messages) {
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy
index d5ab039..663ef88 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy
@@ -21,8 +21,8 @@ package org.gradle.messaging.remote.internal.hub
 import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.internal.concurrent.StoppableExecutor
 import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.ConnectCompletion
 import org.gradle.messaging.remote.internal.Connection
-import org.gradle.messaging.remote.internal.MessageSerializer
 import org.gradle.messaging.remote.internal.OutgoingConnector
 import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
 import spock.lang.Specification
@@ -30,11 +30,11 @@ import spock.lang.Specification
 class MessageHubBackedClientTest extends Specification {
     final OutgoingConnector connector = Mock()
     final ExecutorFactory executorFactory = Mock()
-    final MessageSerializer<InterHubMessage> serializer = Mock()
-    final MessageHubBackedClient client = new MessageHubBackedClient(connector, serializer, executorFactory)
+    final MessageHubBackedClient client = new MessageHubBackedClient(connector, executorFactory)
 
     def "creates connection and cleans up on stop"() {
         Address address = Stub()
+        ConnectCompletion connectCompletion = Mock()
         Connection<InterHubMessage> backingConnection = Mock()
         StoppableExecutor executor = Mock()
 
@@ -42,8 +42,14 @@ class MessageHubBackedClientTest extends Specification {
         def objectConnection = client.getConnection(address)
 
         then:
-        1 * connector.connect(address, serializer) >> backingConnection
-        1 * executorFactory.create("${backingConnection} workers") >> executor
+        1 * connector.connect(address) >> connectCompletion
+        1 * executorFactory.create("${connectCompletion} workers") >> executor
+
+        when:
+        objectConnection.connect()
+
+        then:
+        1 * connectCompletion.create(_) >> backingConnection
 
         when:
         objectConnection.stop()
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy
index 835131a..c94fc67 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy
@@ -21,45 +21,46 @@ package org.gradle.messaging.remote.internal.hub
 import org.gradle.api.Action
 import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.internal.concurrent.StoppableExecutor
-import org.gradle.messaging.remote.Address
-import org.gradle.messaging.remote.ConnectEvent
 import org.gradle.messaging.remote.ConnectionAcceptor
+import org.gradle.messaging.remote.ObjectConnection
+import org.gradle.messaging.remote.internal.ConnectCompletion
 import org.gradle.messaging.remote.internal.Connection
 import org.gradle.messaging.remote.internal.IncomingConnector
-import org.gradle.messaging.remote.internal.MessageSerializer
 import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
 import spock.lang.Specification
 
 class MessageHubBackedServerTest extends Specification {
     final IncomingConnector connector = Mock()
     final ExecutorFactory executorFactory = Mock()
-    final MessageSerializer<InterHubMessage> serializer = Mock()
-    final MessageHubBackedServer server = new MessageHubBackedServer(connector, serializer, executorFactory)
+    final MessageHubBackedServer server = new MessageHubBackedServer(connector, executorFactory)
 
     def "creates connection and cleans up on stop"() {
-        Address remoteAddress = Stub()
-        Address localAddress = Stub()
-        ConnectionAcceptor acceptor = Mock() {
-            getAddress() >> localAddress
-        }
-        Action<ConnectEvent<Connection<InterHubMessage>>> connectAction = Mock()
+        ConnectionAcceptor acceptor = Mock()
+        Action<ObjectConnection> connectAction = Mock()
         Connection<InterHubMessage> backingConnection = Mock()
         StoppableExecutor executor = Mock()
-        def acceptAction
+        ConnectCompletion completion = Mock()
+        Action<ConnectCompletion> acceptAction
         def connection
 
         when:
         server.accept(connectAction)
 
         then:
-        1 * connector.accept(_, serializer, false) >> { acceptAction = it[0]; return acceptor }
+        1 * connector.accept(_, false) >> { acceptAction = it[0]; return acceptor }
 
         when:
-        acceptAction.execute(new ConnectEvent<Connection<InterHubMessage>>(backingConnection, localAddress, remoteAddress))
+        acceptAction.execute(completion)
 
         then:
-        1 * executorFactory.create("${backingConnection} workers") >> executor
-        1 * connectAction.execute(_) >> { ConnectEvent event -> connection = event.connection }
+        1 * executorFactory.create("${completion} workers") >> executor
+        1 * connectAction.execute(_) >> { ObjectConnection c -> connection = c }
+
+        when:
+        connection.connect()
+
+        then:
+        1 * completion.create(_) >> backingConnection
 
         when:
         connection.stop()
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy
index c99d5d5..ade55e1 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.messaging.remote.internal.hub
 
-import com.esotericsoftware.kryo.io.Input
-import com.esotericsoftware.kryo.io.Output
 import org.gradle.messaging.dispatch.MethodInvocation
-import org.gradle.messaging.serialize.kryo.KryoSerializer
+import org.gradle.messaging.serialize.kryo.JavaSerializer
+import org.gradle.messaging.serialize.kryo.KryoBackedDecoder
+import org.gradle.messaging.serialize.kryo.KryoBackedEncoder
 import spock.lang.Specification
 
 class MethodInvocationSerializerTest extends Specification {
     final classLoader = new GroovyClassLoader(getClass().classLoader)
-    final serializer = new MethodInvocationSerializer(classLoader, new KryoSerializer<Object[]>())
+    final serializer = new MethodInvocationSerializer(classLoader, new JavaSerializer<Object[]>(getClass().classLoader))
 
     def "serializes a method invocation with parameters"() {
         def method = String.class.getMethod("substring", Integer.TYPE, Integer.TYPE)
@@ -37,7 +37,6 @@ class MethodInvocationSerializerTest extends Specification {
         then:
         result.method == method
         result.arguments == [1, 2] as Object[]
-        serialized.length == 60
     }
 
     def "replaces a method that has already been seen with an integer ID"() {
@@ -46,6 +45,10 @@ class MethodInvocationSerializerTest extends Specification {
         def invocation1 = new MethodInvocation(method1, [1, 2] as Object[])
         def invocation2 = new MethodInvocation(method2, [3, 4] as Object[])
 
+        given:
+        def invocation1Serialized = serialize(invocation1)
+        def invocation2Serialized = serialize(invocation2)
+
         when:
         def serialized = serialize(invocation1, invocation2)
         def result = deserializeMultiple(serialized, 2)
@@ -55,7 +58,7 @@ class MethodInvocationSerializerTest extends Specification {
         result[0].arguments == [1, 2] as Object[]
         result[1].method == method1
         result[1].arguments == [3, 4] as Object[]
-        serialized.length == 88
+        serialized.length < invocation1Serialized.length + invocation2Serialized.length
     }
 
     def "serializes method invocations for multiple methods"() {
@@ -94,21 +97,21 @@ class MethodInvocationSerializerTest extends Specification {
 
     def serialize(MethodInvocation... messages) {
         def outStr = new ByteArrayOutputStream()
-        def output = new Output(outStr)
-        def writer = serializer.newWriter(output)
+        def encoder = new KryoBackedEncoder(outStr)
+        def writer = serializer.newWriter(encoder)
         messages.each {
             writer.write(it)
         }
-        output.flush()
+        encoder.flush()
         return outStr.toByteArray()
     }
 
     def deserialize(byte[] data) {
-        return serializer.newReader(new Input(data)).read()
+        return serializer.newReader(new KryoBackedDecoder(new ByteArrayInputStream(data))).read()
     }
 
     def deserializeMultiple(byte[] data, int count) {
-        def reader = serializer.newReader(new Input(data))
+        def reader = serializer.newReader(new KryoBackedDecoder(new ByteArrayInputStream(data)))
         def result = []
         count.times {
             result << reader.read()
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactoryTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactoryTest.groovy
new file mode 100644
index 0000000..8b985f7
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactoryTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.inet
+
+import spock.lang.Specification
+
+class InetAddressFactoryTest extends Specification {
+    def factory = new InetAddressFactory()
+
+    def "always contains at least one local address"() {
+        expect:
+        !factory.findLocalAddresses().empty
+    }
+
+    def "always contains at least one remote address"() {
+        expect:
+        !factory.findRemoteAddresses().empty
+    }
+
+    def "all local address is considered local"() {
+        expect:
+        factory.findLocalAddresses().every {
+            factory.isLocal(it)
+        }
+    }
+
+    def "no remote address is considered local"() {
+        expect:
+        factory.findRemoteAddresses().every {
+            !factory.isLocal(it)
+        }
+    }
+}
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 c18d67c..a222997 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
@@ -17,9 +17,8 @@ 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.ConnectCompletion
 import org.gradle.messaging.remote.internal.ConnectException
-import org.gradle.messaging.remote.internal.Connection
 import org.gradle.messaging.remote.internal.DefaultMessageSerializer
 import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 
@@ -34,8 +33,8 @@ class TcpConnectorTest extends ConcurrentSpec {
         Action action = Mock()
 
         when:
-        def acceptor = incomingConnector.accept(action, serializer, false)
-        def connection = outgoingConnector.connect(acceptor.address, serializer)
+        def acceptor = incomingConnector.accept(action, false)
+        def connection = outgoingConnector.connect(acceptor.address).create(serializer)
 
         then:
         connection != null
@@ -48,8 +47,8 @@ class TcpConnectorTest extends ConcurrentSpec {
         Action action = Mock()
 
         when:
-        def acceptor = incomingConnector.accept(action, serializer, true)
-        def connection = outgoingConnector.connect(acceptor.address, serializer)
+        def acceptor = incomingConnector.accept(action, true)
+        def connection = outgoingConnector.connect(acceptor.address).create(serializer)
 
         then:
         connection != null
@@ -62,8 +61,8 @@ class TcpConnectorTest extends ConcurrentSpec {
         Action action = Mock()
 
         when:
-        def acceptor = incomingConnector.accept(action, serializer, false)
-        outgoingConnector.connect(acceptor.address, serializer)
+        def acceptor = incomingConnector.accept(action, false)
+        outgoingConnector.connect(acceptor.address)
         thread.blockUntil.connected
 
         then:
@@ -77,7 +76,7 @@ class TcpConnectorTest extends ConcurrentSpec {
         def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost")])
 
         when:
-        outgoingConnector.connect(address, serializer)
+        outgoingConnector.connect(address)
 
         then:
         ConnectException e = thrown()
@@ -89,7 +88,7 @@ class TcpConnectorTest extends ConcurrentSpec {
         def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost"), InetAddress.getByName("127.0.0.1")])
 
         when:
-        outgoingConnector.connect(address, serializer)
+        outgoingConnector.connect(address)
 
         then:
         ConnectException e = thrown()
@@ -99,9 +98,9 @@ class TcpConnectorTest extends ConcurrentSpec {
 
     def "client cannot connect when server has requested stop"() {
         when:
-        def acceptor = incomingConnector.accept(Mock(Action), serializer, false)
+        def acceptor = incomingConnector.accept(Mock(Action), false)
         acceptor.requestStop()
-        outgoingConnector.connect(acceptor.address, serializer)
+        outgoingConnector.connect(acceptor.address)
 
         then:
         ConnectException e = thrown()
@@ -118,11 +117,11 @@ class TcpConnectorTest extends ConcurrentSpec {
         }
 
         when:
-        def acceptor = incomingConnector.accept(action, serializer, false)
+        def acceptor = incomingConnector.accept(action, false)
         async {
-            outgoingConnector.connect(acceptor.address, serializer)
+            outgoingConnector.connect(acceptor.address)
         }
-        outgoingConnector.connect(acceptor.address, serializer)
+        outgoingConnector.connect(acceptor.address)
 
         then:
         ConnectException e = thrown()
@@ -144,8 +143,8 @@ class TcpConnectorTest extends ConcurrentSpec {
         }
 
         when:
-        def acceptor = incomingConnector.accept(action, serializer, false)
-        outgoingConnector.connect(acceptor.address, serializer)
+        def acceptor = incomingConnector.accept(action, false)
+        outgoingConnector.connect(acceptor.address)
         thread.blockUntil.connected
         operation.stop {
             acceptor.stop()
@@ -162,14 +161,14 @@ class TcpConnectorTest extends ConcurrentSpec {
         // This is a test to simulate the messaging that the daemon does on build completion, in order to validate some assumptions
 
         when:
-        def acceptor = incomingConnector.accept({ ConnectEvent<Connection<Object>> event ->
-            def connection = event.connection
+        def acceptor = incomingConnector.accept({ ConnectCompletion event ->
+            def connection = event.create(serializer)
             connection.dispatch("bye")
             connection.stop()
             instant.closed
-        } as Action, serializer, false)
+        } as Action, false)
 
-        def connection = outgoingConnector.connect(acceptor.address, serializer)
+        def connection = outgoingConnector.connect(acceptor.address).create(serializer)
         thread.blockUntil.closed
 
         then:
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/AbstractCodecTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/AbstractCodecTest.groovy
new file mode 100644
index 0000000..6962351
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/AbstractCodecTest.groovy
@@ -0,0 +1,524 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import java.nio.CharBuffer
+
+abstract class AbstractCodecTest extends Specification {
+    def "can encode and decode raw bytes using Stream view"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.outputStream.write(Byte.MIN_VALUE)
+            encoder.outputStream.write(Byte.MAX_VALUE)
+            encoder.outputStream.write(-1)
+            encoder.outputStream.write([1, 2, 3, 4] as byte[])
+            encoder.outputStream.write([0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7] as byte[], 2, 4)
+        }
+        decode(bytes) { Decoder decoder ->
+            def buffer = new byte[3]
+            assert decoder.inputStream.read(buffer) == 3
+            assert buffer == [Byte.MIN_VALUE, Byte.MAX_VALUE, -1] as byte[]
+            assert decoder.inputStream.read(buffer, 1, 2) == 2
+            assert buffer[1] == 1
+            assert buffer[2] == 2
+            assert decoder.inputStream.read() == 3
+            assert decoder.inputStream.read() == 4
+            assert decoder.inputStream.read() == 0xc3
+            def content = decoder.inputStream.bytes
+            assert content == [0xc4, 0xc5, 0xc6] as byte[]
+
+            assert decoder.inputStream.read() == -1
+            assert decoder.inputStream.read(buffer) == -1
+            assert decoder.inputStream.read(buffer, 0, 1) == -1
+        }
+    }
+
+    def "ignores close on InputStream"() {
+        def inputStream = Mock(InputStream)
+
+        when:
+        decodeFrom(inputStream) { Decoder decoder ->
+            decoder.inputStream.close()
+        }
+
+        then:
+        0 * inputStream.close()
+    }
+
+    def "ignores close or flush on OutputStream"() {
+        def outputStream = Mock(OutputStream)
+
+        when:
+        encodeTo(outputStream) { Encoder encoder ->
+            encoder.outputStream.flush()
+            encoder.outputStream.close()
+        }
+
+        then:
+        0 * outputStream.close()
+        0 * outputStream.flush()
+    }
+
+    def "can encode and decode raw bytes"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeByte(Byte.MIN_VALUE)
+            encoder.writeByte(Byte.MAX_VALUE)
+            encoder.writeByte(-1 as byte)
+            encoder.writeBytes([1, 2, 3, 4] as byte[])
+            encoder.writeBytes([0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7] as byte[], 2, 1)
+        }
+        decode(bytes) { Decoder decoder ->
+            def buffer = new byte[2]
+            decoder.readBytes(buffer)
+            assert buffer == [Byte.MIN_VALUE, Byte.MAX_VALUE] as byte[]
+            assert decoder.readByte() == -1 as byte
+            decoder.readBytes(buffer, 0, 2)
+            assert buffer[0] == 1
+            assert buffer[1] == 2
+            assert decoder.readByte() == 3
+            assert decoder.readByte() == 4
+            assert decoder.readByte() == 0xc3 as byte
+        }
+    }
+
+    def "can encode and decode many bytes"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            10000.times {
+                encoder.writeByte(it as byte)
+            }
+        }
+        decode(bytes) { Decoder decoder ->
+            10000.times {
+                assert decoder.readByte() == it as byte
+            }
+        }
+    }
+
+    def "decode fails when requested number of raw bytes are not available"() {
+        given:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeBytes([0xc1, 0xc2, 0xc3] as byte[])
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readBytes(new byte[4])
+        }
+
+        then:
+        thrown(EOFException)
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readBytes(new byte[10], 0, 5)
+        }
+
+        then:
+        thrown(EOFException)
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readBytes(new byte[3], 0, 3)
+            decoder.readByte()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "can skip raw bytes"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeBytes([1, 2, 3, 4, 5, 6] as byte[])
+            encoder.writeBytes(new byte[4096])
+            encoder.writeBytes([7, 8] as byte[])
+        }
+        decode(bytes) { Decoder decoder ->
+            def buffer = new byte[2]
+            decoder.readBytes(buffer)
+            assert buffer == [1, 2] as byte[]
+            decoder.skipBytes(2)
+            assert decoder.readByte() == 5 as byte
+            assert decoder.readByte() == 6 as byte
+            decoder.skipBytes(2000)
+            decoder.skipBytes(2096)
+            assert decoder.readByte() == 7 as byte
+            assert decoder.readByte() == 8 as byte
+        }
+    }
+
+    def "can skip raw bytes using InputStream"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeBytes([1, 2, 3, 4, 5, 6] as byte[])
+        }
+        decode(bytes) { Decoder decoder ->
+            def buffer = new byte[2]
+            decoder.readBytes(buffer)
+            assert buffer == [1, 2] as byte[]
+            assert decoder.inputStream.skip(2) == 2
+            assert decoder.readByte() == 5 as byte
+            assert decoder.readByte() == 6 as byte
+            assert decoder.inputStream.skip(2) == 0
+        }
+    }
+
+    def "decode fails when too few bytes are available to skip"() {
+        when:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeBytes([1, 2] as byte[])
+        }
+        decode(bytes) { Decoder decoder ->
+            decoder.skipBytes(4)
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "can encode and decode byte array"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeBinary([] as byte[])
+            encoder.writeBinary([1, 2, 3, 4] as byte[])
+            encoder.writeBinary([0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7] as byte[], 2, 3)
+        }
+        decode(bytes) { Decoder decoder ->
+            assert decoder.readBinary() == [] as byte[]
+            assert decoder.readBinary() == [1, 2, 3, 4] as byte[]
+            assert decoder.readBinary() == [0xc3, 0xc4, 0xc5] as byte[]
+        }
+    }
+
+    def "decode fails when byte array cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeBinary([1, 2, 3, 4] as byte[])
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readBinary()
+        }
+
+        then:
+        thrown(EOFException)
+
+        when:
+        decode([] as byte[]) { Decoder decoder ->
+            decoder.readBinary()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    @Unroll
+    def "can encode and decode long #value"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeLong(value)
+        }
+        decode(bytes) { Decoder decoder ->
+            assert decoder.readLong() == value
+        }
+
+        where:
+        value               | _
+        0                   | _
+        12                  | _
+        -1                  | _
+        0xff                | _
+        0xffdd              | _
+        0xffddcc            | _
+        0xffddccbb          | _
+        0xffddccbbaa        | _
+        0xffddccbbaa99      | _
+        0xffddccbbaa9988    | _
+        0x7fddccbbaa998877  | _
+        -0xff               | _
+        -0xffdd             | _
+        -0xffddcc           | _
+        -0xffddccbb         | _
+        -0xffddccbbaa       | _
+        -0xffddccbbaa99     | _
+        -0xffddccbbaa9988   | _
+        -0x7fddccbbaa998877 | _
+        Long.MAX_VALUE      | _
+        Long.MIN_VALUE      | _
+    }
+
+    def "decode fails when long cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeLong(0xa40745f3L)
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readLong()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "can encode and decode a small long"() {
+        expect:
+        def bytesA = encode { Encoder encoder ->
+            encoder.writeSmallLong(a as long)
+        }
+        def bytesB = encode { Encoder encoder ->
+            encoder.writeSmallLong(b as long)
+        }
+        decode(bytesA) { Decoder decoder ->
+            assert decoder.readSmallLong() == a
+        }
+        decode(bytesB) { Decoder decoder ->
+            assert decoder.readSmallLong() == b
+        }
+        bytesA.length <= bytesB.length
+
+        where:
+        a                 | b
+        0                 | 0x1ff
+        0x2ff             | 0x1000
+        0x1000            | -1
+        Integer.MAX_VALUE | -1
+        Long.MAX_VALUE    | -1
+        Long.MAX_VALUE    | -0xc3412
+        Integer.MAX_VALUE | Integer.MIN_VALUE
+        Long.MAX_VALUE    | Long.MIN_VALUE
+    }
+
+    def "decode fails when small long cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeSmallLong(0xa40745f)
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readSmallLong()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    @Unroll
+    def "can encode and decode int #value"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeInt(value as int)
+        }
+        decode(bytes) { Decoder decoder ->
+            assert decoder.readInt() == value
+        }
+
+        where:
+        value             | _
+        0                 | _
+        12                | _
+        -1                | _
+        0xF               | _
+        0xFD              | _
+        0xFDD             | _
+        0xFFDD            | _
+        0xFFDDCC          | _
+        0x7FDDCCBB        | _
+        -0xFF             | _
+        Integer.MAX_VALUE | _
+        Integer.MIN_VALUE | _
+    }
+
+    def "decode fails when int cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeInt(0xa40745f)
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readInt()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "can encode and decode a small int"() {
+        expect:
+        def bytesA = encode { Encoder encoder ->
+            encoder.writeSmallInt(a as int)
+        }
+        def bytesB = encode { Encoder encoder ->
+            encoder.writeSmallInt(b as int)
+        }
+        decode(bytesA) { Decoder decoder ->
+            assert decoder.readSmallInt() == a
+        }
+        decode(bytesB) { Decoder decoder ->
+            assert decoder.readSmallInt() == b
+        }
+        bytesA.length <= bytesB.length
+
+        where:
+        a                 | b
+        0                 | 0x1ff
+        0x2ff             | 0x1000
+        0x1000            | -1
+        Integer.MAX_VALUE | -1
+        Integer.MAX_VALUE | -0xc3412
+        Integer.MAX_VALUE | Integer.MIN_VALUE
+    }
+
+    def "decode fails when small int cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeSmallInt(0xa40745f)
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readSmallInt()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "can encode and decode a boolean"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeBoolean(true)
+            encoder.writeBoolean(false)
+        }
+        decode(bytes) { Decoder decoder ->
+            assert decoder.readBoolean()
+            assert !decoder.readBoolean()
+        }
+    }
+
+    def "decode fails when boolean cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeBoolean(true)
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readBoolean()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "can encode and decode a string"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeString(value)
+        }
+        decode(bytes) { Decoder decoder ->
+            assert decoder.readString() == value.toString()
+        }
+
+        where:
+        value                            | _
+//        ""                               | _
+//        "all ascii"                      | _
+        "\u0000\u0101\u3100"             | _
+//        "${1 + 2}"                       | _
+//        new StringBuilder("some string") | _
+//        CharBuffer.wrap("a string")      | _
+//        (0..1000).join("-")              | _
+    }
+
+    def "decode fails when string cannot be fully read"() {
+        given:
+        def bytes = truncate { Encoder encoder ->
+            encoder.writeString("hi")
+        }
+
+        when:
+        decode(bytes) { Decoder decoder ->
+            decoder.readString()
+        }
+
+        then:
+        thrown(EOFException)
+    }
+
+    def "cannot encode a null string"() {
+        when:
+        encode { Encoder encoder ->
+            encoder.writeString(null)
+        }
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+
+    def "can encode and decode a nullable string"() {
+        expect:
+        def bytes = encode { Encoder encoder ->
+            encoder.writeNullableString(value)
+        }
+        decode(bytes) { Decoder decoder ->
+            assert decoder.readNullableString() == value?.toString()
+        }
+
+        where:
+        value                            | _
+        null                             | _
+        ""                               | _
+        "all ascii"                      | _
+        "\u0000\u0101\u3100"             | _
+        "${1 + 2}"                       | _
+        new StringBuilder("some string") | _
+    }
+
+    abstract void encodeTo(OutputStream outputStream, Closure<Encoder> closure)
+
+    byte[] encode(Closure<Encoder> closure) {
+        def bytes = new ByteArrayOutputStream()
+        encodeTo(bytes, closure)
+        return bytes.toByteArray()
+    }
+
+    byte[] truncate(Closure<Encoder> closure) {
+        def bytes = new ByteArrayOutputStream()
+        encodeTo(bytes, closure)
+        def result = bytes.toByteArray()
+        if (result.length < 2) {
+            return [] as byte[]
+        }
+        return result[0..result.length - 2] as byte[]
+    }
+
+    abstract void decodeFrom(InputStream inputStream, Closure<Decoder> closure)
+
+    void decode(byte[] bytes, Closure<Decoder> closure) {
+        decodeFrom(new ByteArrayInputStream(bytes), closure)
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/BaseSerializerFactoryTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/BaseSerializerFactoryTest.groovy
new file mode 100644
index 0000000..7660f0a
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/BaseSerializerFactoryTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+import spock.lang.Specification
+
+class BaseSerializerFactoryTest extends Specification {
+    def factory = new BaseSerializerFactory()
+
+    def "uses efficient serialization for Strings"() {
+        def encoder = Mock(Encoder)
+        def decoder = Mock(Decoder)
+
+        when:
+        def serializer = factory.getSerializerFor(String)
+        serializer.write(encoder, "hi")
+        serializer.read(decoder)
+
+        then:
+        1 * encoder.writeString("hi")
+        1 * decoder.readString() >> "bye"
+    }
+
+    def "uses efficient serialization for Files"() {
+        def encoder = Mock(Encoder)
+        def decoder = Mock(Decoder)
+
+        when:
+        def serializer = factory.getSerializerFor(File)
+        serializer.write(encoder, new File("some-file"))
+        def result = serializer.read(decoder)
+
+        then:
+        result == new File("some-file")
+        1 * encoder.writeString("some-file")
+        1 * decoder.readString() >> "some-file"
+    }
+
+    def "uses efficient serialization for Long"() {
+        def encoder = Mock(Encoder)
+        def decoder = Mock(Decoder)
+
+        when:
+        def serializer = factory.getSerializerFor(Long)
+        serializer.write(encoder, 123L)
+        serializer.read(decoder)
+
+        then:
+        1 * encoder.writeLong(123L)
+        1 * decoder.readLong() >> 456L
+    }
+
+    def "uses Java serialization for unknown type"() {
+        expect:
+        factory.getSerializerFor(Thing) instanceof DefaultSerializer
+    }
+
+    class Thing { }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerRegistryTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerRegistryTest.groovy
new file mode 100644
index 0000000..8d1e0b7
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerRegistryTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+class DefaultSerializerRegistryTest extends SerializerSpec {
+    def longSerializer = Stub(Serializer) {
+        read(_) >> { Decoder decoder ->
+            return decoder.readSmallLong()
+        }
+        write(_, _) >> { Encoder encoder, Long value ->
+            encoder.writeSmallLong(value)
+        }
+    }
+    def intSerializer = Stub(Serializer) {
+        read(_) >> { Decoder decoder ->
+            return decoder.readSmallInt()
+        }
+        write(_, _) >> { Encoder encoder, Integer value ->
+            encoder.writeSmallInt(value)
+        }
+    }
+
+    def "serializes type information with a value"() {
+        given:
+        def registry = new DefaultSerializerRegistry()
+        registry.register(Long, longSerializer)
+        registry.register(Integer, intSerializer)
+        def serializer = registry.build()
+
+        expect:
+        serialize(123L, serializer) == 123L
+        serialize(123, serializer) == 123
+    }
+
+    def "does not write type tag when there is only one registered type"() {
+        given:
+        def registry = new DefaultSerializerRegistry()
+        registry.register(Long, longSerializer)
+        def serializer1 = registry.build()
+        registry.register(Integer, intSerializer)
+        def serializer2 = registry.build()
+
+        expect:
+        toBytes(123L, serializer1).length + 1 == toBytes(123L, serializer2).length
+    }
+
+    def "type information is independent of the order that types are registered"() {
+        given:
+        def registry = new DefaultSerializerRegistry()
+        registry.register(Long, longSerializer)
+        registry.register(Integer, intSerializer)
+        def serializer1 = registry.build()
+
+        and:
+        registry = new DefaultSerializerRegistry()
+        registry.register(Integer, intSerializer)
+        registry.register(Long, longSerializer)
+        def serializer2 = registry.build()
+
+        expect:
+        fromBytes(toBytes(123L, serializer1), serializer2) == 123L
+        fromBytes(toBytes(123, serializer1), serializer2) == 123
+    }
+
+    def "cannot write value with type that has not been registered"() {
+        given:
+        def registry = new DefaultSerializerRegistry()
+        registry.register(Long, longSerializer)
+        registry.register(Integer, intSerializer)
+        def serializer = registry.build()
+
+        when:
+        toBytes(123.4, serializer)
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy
index 5215f06..69eee88 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy
@@ -15,9 +15,7 @@
  */
 package org.gradle.messaging.serialize
 
-import spock.lang.Specification
-
-class DefaultSerializerTest extends Specification {
+class DefaultSerializerTest extends SerializerSpec {
     def canSerializeAndDeserializeObject() {
         GroovyClassLoader classLoader = new GroovyClassLoader(getClass().classLoader)
         DefaultSerializer serializer = new DefaultSerializer(classLoader)
@@ -26,9 +24,7 @@ class DefaultSerializerTest extends Specification {
         Object o = cl.newInstance()
 
         when:
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
-        serializer.write(outputStream, o)
-        Object r = serializer.read(new ByteArrayInputStream(outputStream.toByteArray()))
+        def r = serialize(o, serializer)
 
         then:
         cl.isInstance(r)
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/ListSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/ListSerializerTest.groovy
new file mode 100644
index 0000000..81de1bf
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/ListSerializerTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+class ListSerializerTest extends SerializerSpec {
+
+    def stringSerializer = new NullSafeStringSerializer()
+
+    def "serialize list of strings"() {
+        when:
+        def serializer = new ListSerializer(stringSerializer)
+
+        then:
+        serialize(["one", "two", "three"], serializer) == ["one", "two", "three"]
+    }
+
+    def "serialize list of longs"() {
+        when:
+        def serializer = new ListSerializer(BaseSerializerFactory.LONG_SERIALIZER)
+
+        then:
+        serialize([10L, 5L, 99L], serializer) == [10L, 5L, 99L]
+    }
+
+    def "serialize null entry"() {
+        when:
+        def serializer = new ListSerializer(stringSerializer)
+
+        then:
+        serialize(["one", null, "three"], serializer) == ["one", null, "three"]
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/LongSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/LongSerializerTest.groovy
new file mode 100644
index 0000000..6701422
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/LongSerializerTest.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.messaging.serialize
+
+class LongSerializerTest extends SerializerSpec {
+
+    def serializer = new LongSerializer()
+
+    def "writes and reads Longs"() {
+        expect:
+        serialize(144L, serializer) == 144L
+    }
+
+    def "does not permit null"() {
+        when:
+        serializer.write(new OutputStreamBackedEncoder(new ByteArrayOutputStream()), null)
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/MapSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/MapSerializerTest.groovy
new file mode 100644
index 0000000..6f61569
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/MapSerializerTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+class MapSerializerTest extends SerializerSpec {
+
+    def stringSerializer = new NullSafeStringSerializer()
+
+    def "retains order of serialized entries"() {
+        when:
+        def serializer = new MapSerializer(BaseSerializerFactory.LONG_SERIALIZER, stringSerializer)
+        Map values = serialize([10L: "one", 2L: "two", 30L: "three"], serializer) as Map
+
+        then:
+        values.keySet() as List == [10L, 2L, 30L]
+    }
+
+    def "serialize map"() {
+        when:
+        def serializer = new MapSerializer(BaseSerializerFactory.LONG_SERIALIZER, stringSerializer)
+
+        then:
+        serialize([1L: "one", 2L: "two", 3L: "three"], serializer) == [1L: "one", 2L: "two", 3L: "three"]
+    }
+
+    def "serialize null value"() {
+        when:
+        def serializer = new MapSerializer(BaseSerializerFactory.LONG_SERIALIZER, stringSerializer)
+
+        then:
+        serialize([1L: "one", 2L: null], serializer) == [1L: "one", 2L: null]
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/SetSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/SetSerializerTest.groovy
new file mode 100644
index 0000000..dc77bf4
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/SetSerializerTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+class SetSerializerTest extends SerializerSpec {
+
+    def stringSerializer = new NullSafeStringSerializer()
+
+    def "serialize set of strings"() {
+        when:
+        def serializer = new SetSerializer(stringSerializer)
+
+        then:
+        serialize(["one", "two", "three"] as Set, serializer) as List == ["one", "two", "three"]
+    }
+
+    def "serialize set of longs"() {
+        when:
+        def serializer = new SetSerializer(BaseSerializerFactory.LONG_SERIALIZER)
+
+        then:
+        serialize([1L, 5L, 99L] as Set, serializer) as List == [1L, 5L, 99L]
+    }
+
+    def "serialize null entry"() {
+        when:
+        def serializer = new SetSerializer(stringSerializer)
+
+        then:
+        serialize(["one", null, "three"] as Set, serializer) as List == ["one", null, "three"]
+    }
+
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/StreamBackedCodecTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/StreamBackedCodecTest.groovy
new file mode 100644
index 0000000..c86b54d
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/StreamBackedCodecTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+class StreamBackedCodecTest extends AbstractCodecTest {
+    @Override
+    void encodeTo(OutputStream outputStream, Closure<Encoder> closure) {
+        def encoder = new OutputStreamBackedEncoder(outputStream)
+        closure.call(encoder)
+    }
+
+    @Override
+    void decodeFrom(InputStream inputStream, Closure<Decoder> closure) {
+        def decoder = new InputStreamBackedDecoder(inputStream)
+        closure.call(decoder)
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/kryo/KryoBackedCodecTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/kryo/KryoBackedCodecTest.groovy
new file mode 100644
index 0000000..bc9c2b1
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/kryo/KryoBackedCodecTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize.kryo
+
+import org.gradle.messaging.serialize.AbstractCodecTest
+import org.gradle.messaging.serialize.Decoder
+import org.gradle.messaging.serialize.Encoder
+
+class KryoBackedCodecTest extends AbstractCodecTest {
+    @Override
+    void encodeTo(OutputStream outputStream, Closure<Encoder> closure) {
+        def encoder = new KryoBackedEncoder(outputStream, 10)
+        closure.call(encoder)
+        encoder.flush()
+    }
+
+    @Override
+    void decodeFrom(InputStream inputStream, Closure<Decoder> closure) {
+        def decoder = new KryoBackedDecoder(inputStream, 10)
+        closure.call(decoder)
+    }
+
+    def "can query write and read positions"() {
+        def outstr = new ByteArrayOutputStream()
+        def encoder = new KryoBackedEncoder(outstr)
+
+        expect:
+        encoder.writePosition == 0
+
+        when:
+        encoder.writeBoolean(true)
+        encoder.writeByte(12 as byte)
+        encoder.writeLong(1234)
+
+        then:
+        encoder.writePosition == 10
+        outstr.size() == 0
+
+        when:
+        encoder.flush()
+
+        then:
+        encoder.writePosition == 10
+        outstr.size() == 10
+
+        when:
+        encoder.writeBytes(new byte[4098])
+
+        then:
+        encoder.writePosition == 4108
+        outstr.size() == 4106
+
+        when:
+        encoder.close()
+
+        then:
+        encoder.writePosition == 4108
+        outstr.size() == 4108
+
+        when:
+        def instr = new ByteArrayInputStream(outstr.toByteArray())
+        def decoder = new KryoBackedDecoder(instr)
+
+        then:
+        instr.available() == 4108
+        decoder.readPosition == 0
+
+        when:
+        decoder.readBoolean()
+        decoder.readByte()
+        decoder.readLong()
+
+        then:
+        instr.available() == 12 // decoder has buffered from instr
+        decoder.readPosition == 10
+
+        when:
+        decoder.skipBytes(4098)
+
+        then:
+        instr.available() == 0
+        decoder.readPosition == 4108
+    }
+}
diff --git a/subprojects/messaging/src/testFixtures/groovy/org/gradle/messaging/serialize/SerializerSpec.groovy b/subprojects/messaging/src/testFixtures/groovy/org/gradle/messaging/serialize/SerializerSpec.groovy
new file mode 100644
index 0000000..4d7fd6e
--- /dev/null
+++ b/subprojects/messaging/src/testFixtures/groovy/org/gradle/messaging/serialize/SerializerSpec.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.serialize
+
+import spock.lang.Specification
+
+class SerializerSpec extends Specification {
+    def serialize(def value, Serializer serializer) {
+        def bytes = toBytes(value, serializer)
+        return serializer.read(new InputStreamBackedDecoder(new ByteArrayInputStream(bytes)))
+    }
+
+    def fromBytes(def bytes, Serializer serializer) {
+        return serializer.read(new InputStreamBackedDecoder(new ByteArrayInputStream(bytes)))
+    }
+
+    def toBytes(def value, Serializer serializer) {
+        def bytes = new ByteArrayOutputStream()
+        def encoder = new OutputStreamBackedEncoder(bytes)
+        serializer.write(encoder, value)
+        encoder.flush()
+
+        return bytes.toByteArray()
+    }
+}
diff --git a/subprojects/native/native.gradle b/subprojects/native/native.gradle
index 04f1ac8..4677efe 100755
--- a/subprojects/native/native.gradle
+++ b/subprojects/native/native.gradle
@@ -2,8 +2,6 @@
     This project contains various native operating system integration utilities.
 */
 dependencies {
-    groovy libraries.groovy
-
     compile project(':baseServices')
     compile libraries.commons_io
     compile libraries.slf4j_api
@@ -17,11 +15,7 @@ dependencies {
     }
     compile libraries.guava
     compile libraries.jcip
-}
-
-if (!javaVersion.java7) {
-    sourceSets.main.java.exclude '**/jdk7/**'
-    sourceSets.test.groovy.exclude '**/jdk7/**'
+    testCompile libraries.groovy
 }
 
 useTestFixtures()
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java
index b853340..242da0b 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java
@@ -23,8 +23,6 @@ import java.util.Map;
 
 /**
  * Uses reflection to update private environment state
- *
- * @author: Szczepan Faber, created at: 9/7/11
  */
 public class ReflectiveEnvironment {
 
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 6a4b851..20ab029 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 extends Chmod {
+public interface FileSystem extends Chmod, Stat {
     /**
      * Default Unix permissions for directories, {@code 755}.
      */
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 40e36d8..7e23061 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
@@ -17,12 +17,12 @@
 package org.gradle.internal.nativeplatform.filesystem;
 
 import com.sun.jna.Native;
+import net.rubygrapefruit.platform.NativeIntegrationUnavailableException;
+import net.rubygrapefruit.platform.PosixFiles;
 import org.gradle.api.JavaVersion;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.nativeplatform.jna.LibC;
 import org.gradle.internal.os.OperatingSystem;
-import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.internal.service.ServiceRegistry;
 import org.jruby.ext.posix.BaseNativePOSIX;
 import org.jruby.ext.posix.JavaPOSIX;
 import org.jruby.ext.posix.POSIX;
@@ -31,38 +31,34 @@ import org.slf4j.LoggerFactory;
 
 public class FileSystemServices {
     private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemServices.class);
-    private static final ServiceRegistry SERVICES;
-
-    static {
-        DefaultServiceRegistry serviceRegistry = new DefaultServiceRegistry();
-        addServices(serviceRegistry);
-        SERVICES = serviceRegistry;
-    }
-
-    public static ServiceRegistry getServices() {
-        return SERVICES;
-    }
-
-    private static void addServices(DefaultServiceRegistry serviceRegistry) {
-        OperatingSystem operatingSystem = OperatingSystem.current();
 
+    @SuppressWarnings("UnusedDeclaration")
+    public FileSystem createFileSystem(OperatingSystem operatingSystem) {
         // Use no-op implementations for windows
         if (operatingSystem.isWindows()) {
-            serviceRegistry.add(Chmod.class, new EmptyChmod());
-            serviceRegistry.add(Stat.class, new FallbackStat());
-            serviceRegistry.add(Symlink.class, new FallbackSymlink());
-            return;
+            return new GenericFileSystem(new EmptyChmod(), new FallbackStat(), new FallbackSymlink());
+        }
+
+        try {
+            PosixFiles posixFiles = net.rubygrapefruit.platform.Native.get(PosixFiles.class);
+            Symlink symlink = new NativePlatformBackedSymlink(posixFiles);
+            Chmod chmod = new NativePlatformBackedChmod(posixFiles);
+            Stat stat = new NativePlatformBackedStat(posixFiles);
+            return new GenericFileSystem(chmod, stat, symlink);
+        } catch (NativeIntegrationUnavailableException ex) {
+            LOGGER.debug("Native-platform file system integration is not available. Continuing with fallback.");
         }
 
         LibC libC = loadLibC();
-        serviceRegistry.add(Symlink.class, createSymlink(libC));
+        Symlink symlink = symlink(libC);
 
-        // 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));
-            return;
+        // Use libc backed implementations on Linux, if libc available
+        POSIX posix = PosixUtil.current();
+        if ((libC != null && (operatingSystem.isLinux())) && posix instanceof BaseNativePOSIX) {
+            FilePathEncoder filePathEncoder = new DefaultFilePathEncoder(libC);
+            Chmod chmod = new LibcChmod(libC, filePathEncoder);
+            Stat stat = new LibCStat(libC, operatingSystem, (BaseNativePOSIX) posix, filePathEncoder);
+            return new GenericFileSystem(chmod, stat, symlink);
         }
 
         // Use java 7 APIs, if available
@@ -70,9 +66,7 @@ public class FileSystemServices {
             String jdkFilePermissionclass = "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler";
             try {
                 Object handler = FileSystemServices.class.getClassLoader().loadClass(jdkFilePermissionclass).newInstance();
-                serviceRegistry.add(Stat.class, (Stat) handler);
-                serviceRegistry.add(Chmod.class, (Chmod) handler);
-                return;
+                return new GenericFileSystem((Chmod) handler, (Stat) handler, symlink);
             } catch (ClassNotFoundException e) {
                 LOGGER.warn(String.format("Unable to load %s. Continuing with fallback.", jdkFilePermissionclass));
             } catch (Exception e) {
@@ -81,11 +75,10 @@ public class FileSystemServices {
         }
 
         // 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());
+        return new GenericFileSystem(chmod(libC), stat(), symlink);
     }
 
-    private static Symlink createSymlink(LibC libC) {
+    private Symlink symlink(LibC libC) {
         if (libC != null) {
             return new LibcSymlink(libC);
         }
@@ -93,7 +86,7 @@ public class FileSystemServices {
         return new FallbackSymlink();
     }
 
-    private static Stat createStat() {
+    private Stat stat() {
         POSIX posix = PosixUtil.current();
         if (posix instanceof JavaPOSIX) {
             return new FallbackStat();
@@ -102,21 +95,14 @@ public class FileSystemServices {
         }
     }
 
-    static Chmod createChmod(LibC libC) {
+    private Chmod chmod(LibC libC) {
         if (libC != null) {
-            return new LibcChmod(libC, createEncoder(libC));
+            return new LibcChmod(libC, new DefaultFilePathEncoder(libC));
         }
         LOGGER.debug("Using EmptyChmod implementation.");
         return new EmptyChmod();
     }
 
-    static FilePathEncoder createEncoder(LibC libC) {
-        if (OperatingSystem.current().isMacOsX()) {
-            return new MacFilePathEncoder();
-        }
-        return new DefaultFilePathEncoder(libC);
-    }
-
     private static LibC loadLibC() {
         try {
             return (LibC) Native.loadLibrary("c", LibC.class);
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java
deleted file mode 100644
index a9292fe..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.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.internal.nativeplatform.filesystem;
-
-import org.gradle.internal.service.ServiceRegistry;
-
-public abstract class FileSystems {
-    public static FileSystem getDefault() {
-        return DefaultFileSystem.INSTANCE;
-    }
-
-    private static class DefaultFileSystem {
-        static final FileSystem INSTANCE;
-
-        static {
-            ServiceRegistry services = FileSystemServices.getServices();
-            INSTANCE = new GenericFileSystem(services.get(Chmod.class), services.get(Stat.class), services.get(Symlink.class));
-        }
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
index 445a346..6327e41 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
@@ -26,7 +26,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.UUID;
 
-class GenericFileSystem implements FileSystem {
+public class GenericFileSystem implements FileSystem {
     private static final Logger LOGGER = LoggerFactory.getLogger(GenericFileSystem.class);
 
     final boolean caseSensitive;
@@ -73,7 +73,7 @@ class GenericFileSystem implements FileSystem {
         }
     }
 
-    GenericFileSystem(Chmod chmod, Stat stat, Symlink symlink) {
+    public GenericFileSystem(Chmod chmod, Stat stat, Symlink symlink) {
         this.stat = stat;
         this.symlink = symlink;
         this.chmod = chmod;
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/MacFilePathEncoder.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/MacFilePathEncoder.java
deleted file mode 100644
index 0b0ec11..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/MacFilePathEncoder.java
+++ /dev/null
@@ -1,37 +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.filesystem;
-
-import org.gradle.internal.UncheckedException;
-
-import java.io.File;
-import java.io.UnsupportedEncodingException;
-
-class MacFilePathEncoder implements FilePathEncoder {
-    public byte[] encode(File file) {
-        byte[] encoded;
-        try {
-            encoded = file.getAbsolutePath().getBytes("utf-8");
-        } catch (UnsupportedEncodingException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-        byte[] zeroTerminatedByteArray = new byte[encoded.length + 1];
-        System.arraycopy(encoded, 0, zeroTerminatedByteArray, 0, encoded.length);
-        zeroTerminatedByteArray[encoded.length] = 0;
-        return zeroTerminatedByteArray;
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedChmod.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedChmod.java
new file mode 100644
index 0000000..6c1cc31
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedChmod.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.nativeplatform.filesystem;
+
+import net.rubygrapefruit.platform.PosixFiles;
+
+import java.io.File;
+import java.io.IOException;
+
+class NativePlatformBackedChmod implements Chmod {
+    private final PosixFiles posixFiles;
+
+    public NativePlatformBackedChmod(PosixFiles posixFiles) {
+        this.posixFiles = posixFiles;
+    }
+
+    public void chmod(File file, int mode) throws IOException {
+        posixFiles.setMode(file, mode);
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedStat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedStat.java
new file mode 100644
index 0000000..984ff4f
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedStat.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.nativeplatform.filesystem;
+
+import net.rubygrapefruit.platform.PosixFiles;
+
+import java.io.File;
+import java.io.IOException;
+
+class NativePlatformBackedStat implements Stat {
+    private final PosixFiles posixFiles;
+
+    public NativePlatformBackedStat(PosixFiles posixFiles) {
+        this.posixFiles = posixFiles;
+    }
+
+    public int getUnixMode(File f) throws IOException {
+        return posixFiles.getMode(f);
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedSymlink.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedSymlink.java
new file mode 100644
index 0000000..5bae05e
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/NativePlatformBackedSymlink.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.nativeplatform.filesystem;
+
+import net.rubygrapefruit.platform.PosixFiles;
+
+import java.io.File;
+import java.io.IOException;
+
+class NativePlatformBackedSymlink implements Symlink {
+    private final PosixFiles posixFiles;
+
+    public NativePlatformBackedSymlink(PosixFiles posixFiles) {
+        this.posixFiles = posixFiles;
+    }
+
+    public void symlink(File link, File target) throws IOException {
+        link.getParentFile().mkdirs();
+        posixFiles.symlink(link, target.getPath());
+    }
+}
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 8b486f4..75035b2 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
@@ -25,9 +25,6 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
-/**
- * @author: Szczepan Faber, created at: 9/12/11
- */
 public class JnaBootPathConfigurer {
     /**
      * Attempts to find the jna library and copies it to a specified folder.
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/Kernel32.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/Kernel32.java
deleted file mode 100755
index 36c32e3..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/Kernel32.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.internal.nativeplatform.jna;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import com.sun.jna.Pointer;
-import com.sun.jna.PointerType;
-import com.sun.jna.win32.W32APIOptions;
-
-/**
-* Windows' Kernel32
-*/
-public interface Kernel32 extends Library {
-
-    //CHECKSTYLE:OFF
-
-    Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
-
-    int STD_INPUT_HANDLE = -10;
-    int STD_OUTPUT_HANDLE = -11;
-    int STD_ERROR_HANDLE = -12;
-    int HANDLE_FLAG_INHERIT = 1;
-    int ERROR_INVALID_HANDLE = 6;
-    int ERROR_INVALID_PARAMETER = 87;
-    HANDLE INVALID_HANDLE_VALUE = new HANDLE(new Pointer(-1));
-
-    int GetLastError();
-
-    boolean SetEnvironmentVariable(String lpName, String lpValue);
-
-    HANDLE GetStdHandle(int stdHandle);
-
-    boolean SetHandleInformation(HANDLE handle, int dwMask, int dwFlags);
-
-    boolean SetCurrentDirectory(String lpPathName);
-
-    int GetCurrentDirectory(int nBufferLength, char[] lpBuffer);
-
-    int GetCurrentProcessId();
-
-    class HANDLE extends PointerType {
-        public HANDLE() {
-        }
-
-        public HANDLE(Pointer p) {
-            super(p);
-        }
-    }
-
-    //CHECKSTYLE:ON
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsHandlesManipulator.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsHandlesManipulator.java
deleted file mode 100644
index 17960dc..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsHandlesManipulator.java
+++ /dev/null
@@ -1,86 +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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-import static org.gradle.internal.nativeplatform.jna.Kernel32.*;
-
-/**
- * Uses jna to make the stream handles 'uninheritable'. 
- * This way we can achieve a fully detached process on windows.
- * Without that, if the process was spawning child processes on windows (for example gradle build daemon) it waited until the child has completed.
- * This is undesired because sometimes the child process is a long-running process but the parent process is a short-running process.
- */
-public class WindowsHandlesManipulator {
-    private static final Logger LOGGER = LoggerFactory.getLogger(WindowsHandlesManipulator.class);
-
-    /**
-     * Makes the standard streams handles 'uninheritable' for the current process.
-     *
-     * @throws ProcessInitializationException when the operation fails
-     */
-    public void uninheritStandardStreams() throws ProcessInitializationException {
-        Kernel32 kernel32 = INSTANCE;
-
-        try {
-            uninheritStream(kernel32, Kernel32.STD_INPUT_HANDLE);
-            uninheritStream(kernel32, Kernel32.STD_OUTPUT_HANDLE);
-            uninheritStream(kernel32, Kernel32.STD_ERROR_HANDLE);
-        } catch (Exception e) {
-            throw new ProcessInitializationException("Failed to configure the standard stream handles to be 'uninheritable'.", e);
-        }
-    }
-
-    private void uninheritStream(Kernel32 kernel32, int stdInputHandle) throws IOException {
-        HANDLE streamHandle = kernel32.GetStdHandle(stdInputHandle);
-        if (streamHandle == null) {
-            // We're not attached to a stdio (eg Desktop application). Ignore.
-            return;
-        }
-        makeUninheritable(kernel32, streamHandle);
-    }
-
-    private void makeUninheritable(Kernel32 kernel32, HANDLE streamHandle) throws IOException {
-        if (streamHandle.equals(INVALID_HANDLE_VALUE)) {
-            throw new IOException("Invalid handle. Errno: " + kernel32.GetLastError());
-        }
-        boolean ok = kernel32.SetHandleInformation(streamHandle, HANDLE_FLAG_INHERIT, 0);
-        if (!ok) {
-            int setHandleError = kernel32.GetLastError();
-            if (setHandleError == ERROR_INVALID_PARAMETER) {
-                // Didn't get a valid handle: ignore.
-                LOGGER.debug("Invalid parameter attempting to uninherit stream - child process may remain attached.");
-                return;
-            }
-            if (setHandleError == ERROR_INVALID_HANDLE) {
-                LOGGER.debug("Invalid handle attempting to uninherit stream - child process may remain attached.");
-                return;
-            }
-            throw new IOException("Could not set flag on handle. Errno: " + kernel32.GetLastError());
-        }
-    }
-
-    public static class ProcessInitializationException extends RuntimeException {
-        public ProcessInitializationException(String message, Throwable cause) {
-            super(message, cause);
-        }
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
deleted file mode 100755
index 27b7576..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
+++ /dev/null
@@ -1,61 +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 com.sun.jna.Native;
-import org.gradle.internal.nativeplatform.NativeIntegrationException;
-import org.gradle.internal.nativeplatform.processenvironment.AbstractProcessEnvironment;
-
-import java.io.File;
-
-/**
- * Uses JNA to drive the functions provided by kernel32.dll
- */
-public class WindowsProcessEnvironment extends AbstractProcessEnvironment {
-    private static final int LOTS_OF_CHARS = 2048;
-    private final Kernel32 kernel32 = Kernel32.INSTANCE;
-
-    public void setNativeEnvironmentVariable(String name, String value) {
-        boolean retval = kernel32.SetEnvironmentVariable(name, value == null ? null : value);
-        if (!retval && (kernel32.GetLastError() != 203/*ERROR_ENVVAR_NOT_FOUND*/)) {
-            throw new NativeIntegrationException(String.format("Could not set environment variable '%s'. errno: %d", name, kernel32.GetLastError()));
-        }
-    }
-
-    public void removeNativeEnvironmentVariable(String name) {
-        setNativeEnvironmentVariable(name, null);
-    }
-
-    public void setNativeProcessDir(File dir) {
-        boolean retval = kernel32.SetCurrentDirectory(dir.getAbsolutePath());
-        if (!retval) {
-            throw new NativeIntegrationException(String.format("Could not set process working directory to '%s'. errno: %d", dir, kernel32.GetLastError()));
-        }
-    }
-
-    public File getProcessDir() {
-        char[] out = new char[LOTS_OF_CHARS];
-        int retval = kernel32.GetCurrentDirectory(out.length, out);
-        if (retval == 0) {
-            throw new NativeIntegrationException(String.format("Could not get process working directory. errno: %d", kernel32.GetLastError()));
-        }
-        return new File(Native.toString(out));
-    }
-
-    public Long getPid() {
-        return Long.valueOf(kernel32.GetCurrentProcessId());
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/FileSystems.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/FileSystems.java
new file mode 100644
index 0000000..984e46a
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/FileSystems.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.nativeplatform.services;
+
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+
+public abstract class FileSystems {
+    public static FileSystem getDefault() {
+        return NativeServices.getInstance().get(FileSystem.class);
+    }
+}
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 5b2f029..cefc2cd 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
@@ -17,17 +17,16 @@ package org.gradle.internal.nativeplatform.services;
 
 import com.sun.jna.Native;
 import net.rubygrapefruit.platform.*;
-import net.rubygrapefruit.platform.NativeIntegrationUnavailableException;
 import net.rubygrapefruit.platform.Process;
+import net.rubygrapefruit.platform.internal.DefaultProcessLauncher;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.jvm.Jvm;
-import org.gradle.internal.nativeplatform.*;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
 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.filesystem.FileSystemServices;
 import org.gradle.internal.nativeplatform.jna.*;
 import org.gradle.internal.nativeplatform.processenvironment.NativePlatformBackedProcessEnvironment;
 import org.gradle.internal.os.OperatingSystem;
@@ -36,6 +35,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 
 /**
  * Provides various native platform integration services.
@@ -70,6 +72,7 @@ public class NativeServices extends DefaultServiceRegistry {
     }
 
     private NativeServices() {
+        addProvider(new FileSystemServices());
     }
 
     @Override
@@ -81,32 +84,23 @@ public class NativeServices extends DefaultServiceRegistry {
         return OperatingSystem.current();
     }
 
-    protected FileSystem createFileSystem() {
-        return FileSystems.getDefault();
-    }
-
     protected Jvm createJvm() {
         return Jvm.current();
     }
 
-    protected ProcessEnvironment createProcessEnvironment() {
-        OperatingSystem operatingSystem = get(OperatingSystem.class);
+    protected ProcessEnvironment createProcessEnvironment(OperatingSystem operatingSystem) {
         if (useNativePlatform) {
             try {
                 net.rubygrapefruit.platform.Process process = net.rubygrapefruit.platform.Native.get(Process.class);
                 return new NativePlatformBackedProcessEnvironment(process);
             } catch (NativeIntegrationUnavailableException ex) {
                 LOGGER.debug("Native-platform process integration is not available. Continuing with fallback.");
-            } catch (NativeException ex) {
-                LOGGER.debug("Unable to load from native-platform backed ProcessEnvironment. Continuing with fallback. Failure: {}", format(ex));
             }
         }
 
         try {
             if (operatingSystem.isUnix()) {
                 return new LibCBackedProcessEnvironment(get(LibC.class));
-            } else if (operatingSystem.isWindows()) {
-                return new WindowsProcessEnvironment();
             } else {
                 return new UnsupportedEnvironment();
             }
@@ -117,7 +111,7 @@ public class NativeServices extends DefaultServiceRegistry {
         }
     }
 
-    protected ConsoleDetector createConsoleDetector() {
+    protected ConsoleDetector createConsoleDetector(OperatingSystem operatingSystem) {
         if (useNativePlatform) {
             try {
                 Terminals terminals = net.rubygrapefruit.platform.Native.get(Terminals.class);
@@ -129,7 +123,6 @@ public class NativeServices extends DefaultServiceRegistry {
             }
         }
 
-        OperatingSystem operatingSystem = get(OperatingSystem.class);
         try {
             if (operatingSystem.isWindows()) {
                 return new WindowsConsoleDetector();
@@ -142,6 +135,39 @@ public class NativeServices extends DefaultServiceRegistry {
         }
     }
 
+    protected WindowsRegistry createWindowsRegistry(OperatingSystem operatingSystem) {
+        if (useNativePlatform && operatingSystem.isWindows()) {
+            return net.rubygrapefruit.platform.Native.get(WindowsRegistry.class);
+        }
+        return notAvailable(WindowsRegistry.class);
+    }
+
+    protected SystemInfo createSystemInfo() {
+        if (useNativePlatform) {
+            try {
+                return net.rubygrapefruit.platform.Native.get(SystemInfo.class);
+            } catch (NativeIntegrationUnavailableException e) {
+                LOGGER.debug("Native-platform system info is not available. Continuing with fallback.");
+            }
+        }
+        return notAvailable(SystemInfo.class);
+    }
+
+    protected ProcessLauncher createProcessLauncher() {
+        if (useNativePlatform) {
+            try {
+                return net.rubygrapefruit.platform.Native.get(ProcessLauncher.class);
+            } catch (NativeIntegrationUnavailableException e) {
+                LOGGER.debug("Native-platform process launcher is not available. Continuing with fallback.");
+            }
+        }
+        return new DefaultProcessLauncher();
+    }
+
+    private <T> T notAvailable(Class<T> type) {
+        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new BrokenService(type.getSimpleName()));
+    }
+
     protected LibC createLibC() {
         return (LibC) Native.loadLibrary("c", LibC.class);
     }
@@ -157,4 +183,15 @@ public class NativeServices extends DefaultServiceRegistry {
         return builder.toString();
     }
 
+    private static class BrokenService implements InvocationHandler {
+        private final String type;
+
+        private BrokenService(String type) {
+            this.type = type;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            return new org.gradle.internal.nativeplatform.NativeIntegrationUnavailableException(String.format("%s is not supported on this operating system.", type));
+        }
+    }
 }
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
index 9a07b74..1b755b8 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
@@ -24,7 +25,7 @@ import spock.lang.Specification
 class CommonFileSystemTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmpDir
 
-    def fs = FileSystems.default
+    def fs = NativeServices.instance.get(FileSystem)
 
     def "unix permissions cannot be read on non existing file"() {
         when:
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
deleted file mode 100644
index 11462df..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
+++ /dev/null
@@ -1,45 +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.filesystem
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import spock.lang.Specification
-
- at Requires(TestPrecondition.LINUX)
-public class FileSystemServicesOnLinuxTest extends Specification {
-    @Rule TestNameTestDirectoryProvider temporaryFolder
-    final Chmod chmod = FileSystemServices.services.get(Chmod)
-    final Stat stat = FileSystemServices.services.get(Stat)
-    final Symlink symlink = FileSystemServices.services.get(Symlink)
-
-    def "creates LibCChmod on Linux"() {
-        expect:
-        chmod instanceof LibcChmod
-    }
-
-    def "creates LibCStat on Linux"() {
-        expect:
-        stat instanceof LibCStat
-    }
-
-    def "creates LibcSymlink on Linux"() {
-        expect:
-        symlink instanceof LibcSymlink
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
deleted file mode 100644
index 458518e..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
+++ /dev/null
@@ -1,45 +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.filesystem
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import spock.lang.Specification
-
- at Requires(TestPrecondition.MAC_OS_X)
-public class FileSystemServicesOnMacTest extends Specification {
-    @Rule TestNameTestDirectoryProvider temporaryFolder
-    final Chmod chmod = FileSystemServices.services.get(Chmod)
-    final Stat stat = FileSystemServices.services.get(Stat)
-    final Symlink symlink = FileSystemServices.services.get(Symlink)
-
-    def "creates LibCChmod on Mac"() {
-        expect:
-        chmod instanceof LibcChmod
-    }
-
-    def "creates LibCStat on Mac"() {
-        expect:
-        stat instanceof LibCStat
-    }
-
-    def "creates LibcSymlink on Mac"() {
-        expect:
-        symlink instanceof LibcSymlink
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
deleted file mode 100644
index 8af80bd..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
+++ /dev/null
@@ -1,55 +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.filesystem
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import spock.lang.Specification
-
- at Requires(TestPrecondition.UNKNOWN_OS)
-public class FileSystemServicesOnUnknownOsTest extends Specification {
-    @Rule TestNameTestDirectoryProvider temporaryFolder
-    final Chmod chmod = FileSystemServices.services.get(Chmod)
-    final Stat stat = FileSystemServices.services.get(Stat)
-    final Symlink symlink = FileSystemServices.services.get(Symlink)
-
-    @Requires(TestPrecondition.NOT_JDK7)
-    def "creates EmptyChmod when not on JDK7"() {
-        expect:
-        chmod instanceof EmptyChmod
-    }
-
-    @Requires(TestPrecondition.NOT_JDK7)
-    def "creates FallbackStat when not on JDK7"() {
-        expect:
-        stat instanceof FallbackStat
-    }
-
-    @Requires(TestPrecondition.NOT_JDK7)
-    def "creates FallbackSymlink when not on JDK7"() {
-        expect:
-        symlink instanceof FallbackSymlink
-    }
-
-    @Requires(TestPrecondition.JDK7)
-    def "creates Jdk7PosixFilePermissionHandler on JDK7"() {
-        expect:
-        chmod.class.name == "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler"
-        stat.class.name == "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler"
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
deleted file mode 100644
index e0259e2..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
+++ /dev/null
@@ -1,45 +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.filesystem
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import spock.lang.Specification
-
- at Requires(TestPrecondition.WINDOWS)
-public class FileSystemServicesOnWindowsTest extends Specification {
-    @Rule TestNameTestDirectoryProvider temporaryFolder
-    final Chmod chmod = FileSystemServices.services.get(Chmod)
-    final Stat stat = FileSystemServices.services.get(Stat)
-    final Symlink symlink = FileSystemServices.services.get(Symlink)
-
-    def "creates EmptyChmod instance on Windows OS"() {
-        expect:
-        chmod instanceof EmptyChmod
-    }
-
-    def "creates FallbackStat instance on Windows OS"() {
-        expect:
-        stat instanceof FallbackStat
-    }
-
-    def "creates FallbackSymlink on Windows OS"() {
-        expect:
-        symlink instanceof FallbackSymlink
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy
index aaf201e..f11bebe 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy
@@ -15,13 +15,14 @@
  */
 package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Specification
 
 @Requires(TestPrecondition.LINUX)
 class LinuxFileSystemTest extends Specification {
-    def fs = FileSystems.default
+    def fs = NativeServices.instance.get(FileSystem)
 
     def "is case sensitive"() {
         expect:
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy
index 045a7bc..9c3c73a 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy
@@ -15,13 +15,14 @@
  */
 package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Specification
 
 @Requires(TestPrecondition.MAC_OS_X)
 class MacOsFileSystemTest extends Specification {
-    def fs = FileSystems.default
+    def fs = NativeServices.instance.get(FileSystem)
 
     def "is not case sensitive"() {
         expect:
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy
index 981a514..df16c86 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy
@@ -16,13 +16,14 @@
 
 package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Specification
 
 @Requires(TestPrecondition.WINDOWS)
 class WindowsFileSystemTest extends Specification {
-    def fs = FileSystems.default
+    def fs = NativeServices.instance.get(FileSystem)
 
     def "windows file system is case insensitive"() {
         expect:
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy
index 11de586..5cc771e 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.util.TestPrecondition
 import spock.lang.Specification
 import static java.nio.file.attribute.PosixFilePermission.*
 
- at Requires(TestPrecondition.JDK7)
+ at Requires(TestPrecondition.JDK7_OR_LATER)
 class PosixFilePermissionConverterTest extends Specification {
     def "converts Set<PosixFilePermission to int representation"() {
 
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 829c172..3645ee4 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,9 +15,14 @@
  */
 package org.gradle.internal.nativeplatform.services
 
+import net.rubygrapefruit.platform.ProcessLauncher
+import net.rubygrapefruit.platform.SystemInfo
+import net.rubygrapefruit.platform.WindowsRegistry
 import org.gradle.internal.nativeplatform.console.ConsoleDetector
 import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.filesystem.Chmod
 import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.nativeplatform.filesystem.Stat
 import org.gradle.internal.os.OperatingSystem
 import spock.lang.Specification
 
@@ -37,10 +42,27 @@ class NativeServicesTest extends Specification {
     def "makes a FileSystem available"() {
         expect:
         services.get(FileSystem) != null
+        services.get(Chmod) != null
+        services.get(Stat) != null
     }
 
     def "makes a ConsoleDetector available"() {
         expect:
         services.get(ConsoleDetector) != null
     }
+
+    def "makes a WindowsRegistry available"() {
+        expect:
+        services.get(WindowsRegistry) != null
+    }
+
+    def "makes a SystemInfo available"() {
+        expect:
+        services.get(SystemInfo) != null
+    }
+
+    def "makes a ProcessLauncher available"() {
+        expect:
+        services.get(ProcessLauncher) != null
+    }
 }
diff --git a/subprojects/open-api/open-api.gradle b/subprojects/open-api/open-api.gradle
index 154ff39..99965bb 100644
--- a/subprojects/open-api/open-api.gradle
+++ b/subprojects/open-api/open-api.gradle
@@ -1,15 +1,8 @@
 dependencies {
-    groovy libraries.groovy
+    testCompile libraries.groovy
 
     integTestCompile libraries.slf4j_api
     integTestCompile libraries.commons_lang
 }
 
 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/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionBuilder.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionBuilder.java
index 71b7923..14a6ebe 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionBuilder.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionBuilder.java
@@ -18,6 +18,7 @@ package org.gradle.integtests.openapi;
 import org.gradle.openapi.external.foundation.GradleInterfaceVersion2;
 import org.gradle.openapi.external.foundation.ProjectVersion1;
 import org.gradle.openapi.external.foundation.RequestVersion1;
+import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
 import org.gradle.openapi.external.ui.SinglePaneUIVersion1;
 import org.gradle.openapi.external.ui.UIFactory;
 
@@ -63,6 +64,11 @@ public class CrossVersionBuilder {
 
         TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1());
         SinglePaneUIVersion1 singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), targetGradleHomeDir, testSingleDualPaneUIInteractionVersion1, false);
+        singlePane.addCommandLineArgumentAlteringListener(new CommandLineArgumentAlteringListenerVersion1() {
+            public String getAdditionalCommandLineArguments(String commandLineArguments) {
+                return "'-g=" + new File(currentDir, "gradle-user-home").getAbsolutePath() + "'";
+            }
+        });
 
         String actualVersion = ((GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).getVersion();
         assertEquals(version, actualVersion);
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
index 4fec1b2..72e4707 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
@@ -18,8 +18,8 @@ package org.gradle.integtests.openapi
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.GradleDistribution
-import org.gradle.util.ClasspathUtil
-import org.gradle.util.DefaultClassLoaderFactory
+import org.gradle.internal.classloader.ClasspathUtil
+import org.gradle.internal.classloader.DefaultClassLoaderFactory
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Assert
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
index a2b9402..d2c94c6 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
@@ -38,6 +38,7 @@ class GradleRunnerTest {
   static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
 
   private File javaprojectDir
+    File gradleUserHomeDir
 
   @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
   final GradleDistribution dist = new UnderDevelopmentGradleDistribution()
@@ -46,8 +47,14 @@ class GradleRunnerTest {
   @Before
   void setUp() {
       javaprojectDir = temporaryFolder.testDirectory
+      gradleUserHomeDir = temporaryFolder.file("gradle-user-home")
+
   }
 
+    String toCommand(String command) {
+        "'-g=$gradleUserHomeDir.absolutePath' $command"
+    }
+
   /**
    * We just want to make sure we can instantiate a GradleRunner here. That's all
   */
@@ -68,13 +75,14 @@ class GradleRunnerTest {
   @Test
   public void testExecution()
   {
+      def gradleUserHome = temporaryFolder.file("gradle-user-home")
     TestGradleRunnerInteractionVersion1 interaction = new TestGradleRunnerInteractionVersion1( javaprojectDir )
 
     GradleRunnerVersion1 runner = GradleRunnerFactory.createGradleRunner(getClass().getClassLoader(), dist.getGradleHomeDir(), interaction, true)
 
     Assert.assertNotNull( "Failed to instantiate runner", runner )
 
-    runner.executeCommand( "clean build" )
+    runner.executeCommand(toCommand("clean build"))
 
         //wait for it to complete
     int totalWaitTime = 0;
@@ -135,7 +143,7 @@ class GradleRunnerTest {
 
     Assert.assertNotNull( "Failed to instantiate runner", runner )
 
-    runner.executeCommand( "build" )
+    runner.executeCommand(toCommand("build"))
 
         //wait for it to complete
     int totalWaitTime = 0;
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
index 4bce8a3..a1c6a54 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
@@ -387,9 +387,16 @@ class OpenApiUiTest {
             throw new AssertionError('sample project missing. Expected it at: ' + gradleInterface.getCurrentDirectory())
         }
 
+        BlockingRequestObserver setupListener = new BlockingRequestObserver(RequestVersion1.REFRESH_TYPE)
+        gradleInterface.addRequestObserver(setupListener)
+
         //this starts the execution queue
         dualPane.aboutToShow()
 
+        // wait for the implicit refresh to complete
+        setupListener.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        gradleInterface.removeRequestObserver(setupListener)
+
         //add a request observer so we can observe when the command is finished. This allows us to
         //see what was actually executed.
         BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.REFRESH_TYPE)
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
index d3c6aeb..a6d9cf0 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
@@ -35,8 +35,6 @@ import static org.hamcrest.Matchers.startsWith
 
 /**
  * Tests aspects of the OutputUILord in OpenAPI
- *
- * @author mhunsicker
  */
 @Requires(TestPrecondition.SWING)
 class OutputUILordTest {
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestAlternateUIInteractionVersion1.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestAlternateUIInteractionVersion1.java
index f12e229..e6ea876 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestAlternateUIInteractionVersion1.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestAlternateUIInteractionVersion1.java
@@ -22,8 +22,6 @@ import java.io.File;
 /**
  * Implementation of AlternateUIInteractionVersion1 for testing purposes.
  * This would lend itself well for mocking. 
- *
- * @author mhunsicker
   */
 public class TestAlternateUIInteractionVersion1 implements AlternateUIInteractionVersion1 {
 
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSettingsNodeVersion1.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSettingsNodeVersion1.java
index 63cb847..9ba3cca 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSettingsNodeVersion1.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSettingsNodeVersion1.java
@@ -24,8 +24,6 @@ import java.util.List;
 
 /**
  * Implementation of settings node. It basically mirrors a DOM.
- *
- * @author mhunsicker
  */
 public class TestSettingsNodeVersion1 implements SettingsNodeVersion1 {
 
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSingleDualPaneUIInteractionVersion1.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSingleDualPaneUIInteractionVersion1.java
index 8871c29..96e8f82 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSingleDualPaneUIInteractionVersion1.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/TestSingleDualPaneUIInteractionVersion1.java
@@ -23,7 +23,6 @@ import org.gradle.openapi.external.ui.SinglePaneUIInteractionVersion1;
 /**
  * This is a test implementation of both SinglePaneUIInteractionVersion1 and DualPaneUIInteractionVersion1.
  * This is really just a container to hand off more complex interactions to the UI when asked for.
- * @author mhunsicker
  */
 public class TestSingleDualPaneUIInteractionVersion1 implements SinglePaneUIInteractionVersion1, DualPaneUIInteractionVersion1 {
 
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/foundation/BootstrapLoader.java b/subprojects/open-api/src/main/groovy/org/gradle/foundation/BootstrapLoader.java
deleted file mode 100644
index 1c30952..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/foundation/BootstrapLoader.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.foundation;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/*
- This handles the work of loading gradle dynamically. Due to jar version issues,
- you can't just load all jar files.
-
- This does NOT require any system or environment variables to be set.
-
- To use this, instantiate this, then call one of the initialize functions.
- Now you can get the class loader to load whatever classes you like.
-
- @author mhunsicker
-  */
-public class BootstrapLoader {
-    private URLClassLoader libClassLoader;
-
-    public void initialize(File gradleHome, boolean bootStrapDebug) throws Exception {
-        initialize(ClassLoader.getSystemClassLoader().getParent(), gradleHome, false, true, bootStrapDebug);
-    }
-
-    /*
-      Call this to initialize gradle.
-      @param  parentClassloader    a parent class loader. Probably whatever class loader
-                                   is used by the caller.
-      @param  gradleHome           the root directory where gradle is installed. This
-                                   directory should have a 'bin' child directory.
-      @param  useParentLastClassLoader true to use a class loader that will delegate
-                                   to the parent only if it can't find it locally. This
-                                   should only be true if you're trying to load gradle
-                                   dynamically from another application.
-      @param  loadOpenAPI          True to load the gradle open API, false not to.
-                                   If you're calling this from a tool using the OpenAPI,
-                                   then you've probably already loaded it, so pass in false
-                                   here, otherwise, pass in true.
-      @param  bootStrapDebug       true to output debug information about the loading
-                                   process.
-      @throws Exception            if something goes wrong.
-      @author mhunsicker
-   */
-    public void initialize(ClassLoader parentClassloader, File gradleHome, boolean useParentLastClassLoader, boolean loadOpenAPI, boolean bootStrapDebug) throws Exception {
-        if (gradleHome == null || !gradleHome.exists()) {
-            throw new RuntimeException("Gradle home not defined!");
-        }
-
-        if (bootStrapDebug) {
-            System.out.println("Gradle Home is declared by system property gradle.home to: " + gradleHome.getAbsolutePath());
-        }
-
-        System.setProperty("gradle.home", gradleHome.getAbsolutePath());
-
-        List<URL> loggingJars = toUrl(getLoggingJars());
-
-        List<File> nonLoggingJarFiles = getNonLoggingJars();
-        removeUnwantedJarFiles(nonLoggingJarFiles, loadOpenAPI);
-        List<URL> nonLoggingJars = toUrl(nonLoggingJarFiles);
-
-        if (bootStrapDebug) {
-            System.out.println("Parent Classloader of new context classloader is: " + parentClassloader);
-            System.out.println("Adding the following files to new logging classloader: " + loggingJars);
-            System.out.println("Adding the following files to new lib classloader: " + nonLoggingJars);
-        }
-
-        URLClassLoader loggingClassLoader = new URLClassLoader(loggingJars.toArray(new URL[loggingJars.size()]), parentClassloader);
-
-        if (useParentLastClassLoader) {
-            libClassLoader = new ParentLastClassLoader(nonLoggingJars.toArray(new URL[nonLoggingJars.size()]), loggingClassLoader);
-        } else {
-            libClassLoader = new URLClassLoader(nonLoggingJars.toArray(new URL[nonLoggingJars.size()]), loggingClassLoader);
-        }
-
-        if (bootStrapDebug) {
-            System.out.println("Logging class loader: " + loggingClassLoader);
-            System.out.println("Lib class loader: " + libClassLoader);
-        }
-    }
-
-    public static File[] getGradleHomeLibClasspath() {
-        File gradleHomeLib = new File(System.getProperty("gradle.home") + "/lib");
-        if (gradleHomeLib.isDirectory()) {
-            return gradleHomeLib.listFiles();
-        }
-        return new File[0];
-    }
-
-    public static List<File> getNonLoggingJars() {
-        List<File> pathElements = new ArrayList<File>();
-        for (File file : getGradleClasspath()) {
-            if (!isLogLib(file)) {
-                pathElements.add(file);
-            }
-        }
-        return pathElements;
-    }
-
-    public static List<File> getLoggingJars() {
-        List<File> pathElements = new ArrayList<File>();
-        for (File file : getGradleClasspath()) {
-            if (isLogLib(file)) {
-                pathElements.add(file);
-            }
-        }
-        return pathElements;
-    }
-
-    private static boolean isLogLib(File file) {
-        return file.getName().startsWith("logback") || file.getName().startsWith("slf4j");
-    }
-
-    public static List<File> getGradleClasspath() {
-        File customGradleBin = null;
-        List<File> pathElements = new ArrayList<File>();
-        if (System.getProperty("gradle.bootstrap.gradleBin") != null) {
-            customGradleBin = new File(System.getProperty("gradle.bootstrap.gradleBin"));
-            pathElements.add(customGradleBin);
-        }
-        for (File homeLibFile : getGradleHomeLibClasspath()) {
-            if (homeLibFile.isFile() && !(customGradleBin != null && homeLibFile.getName().startsWith("gradle-"))) {
-                pathElements.add(homeLibFile);
-            }
-        }
-        return pathElements;
-    }
-
-    /*
-      This removes unwanted jar files. At the time of this writing, we're only
-      interested in the open api jar.
-
-      @param  nonLoggingJarFiles a list of jar files
-      @param  loadOpenAPI        true to keep the open api jar, false to remove it.
-      @author mhunsicker
-   */
-    private void removeUnwantedJarFiles(List<File> nonLoggingJarFiles, boolean loadOpenAPI) {
-        if (loadOpenAPI) {
-            return;
-        }
-
-        Iterator<File> iterator = nonLoggingJarFiles.iterator();
-        while (iterator.hasNext()) {
-            File file = iterator.next();
-            if (file.getName().startsWith("gradle-open-api-")) {
-                iterator.remove();
-            }
-        }
-    }
-
-    /*
-      Call this to get the class loader you can use to load gradle classes.
-      @return a URLClassLoader
-      @author mhunsicker
-   */
-    public URLClassLoader getClassLoader() {
-        return libClassLoader;
-    }
-
-    public Class load(String classPath) throws Exception {
-        return libClassLoader.loadClass(classPath);
-    }
-
-    private static List<URL> toUrl(List<File> files) throws MalformedURLException {
-        List<URL> result = new ArrayList<URL>();
-        for (File file : files) {
-            result.add(file.toURI().toURL());
-        }
-        return result;
-    }
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/foundation/ParentLastClassLoader.java b/subprojects/open-api/src/main/groovy/org/gradle/foundation/ParentLastClassLoader.java
deleted file mode 100644
index 2cbcb49..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/foundation/ParentLastClassLoader.java
+++ /dev/null
@@ -1,73 +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.foundation;
-
-import java.net.URLClassLoader;
-import java.net.URL;
-import java.net.URLStreamHandlerFactory;
-
-/**
- * <p>This class loader delegates to the parent class loader ONLY if it cannot find it itself. This is meant to solve classloading issues when running something as, say, a plugin inside an application
- * that may have already loaded a different version of some required jars. This makes sure it looks locally first. This is the opposite of a ClassLoader's typical behavior, but it necessary when you
- * can't control the environment in which you're running.
- *
- * <p>Using this class can be very dangerous. You must carefully make sure you understand the ramifications of using this. You should also probably make this the first class loader between your plugin
- * and the plugin's owner.
- *
- * @author mhunsicker
- */
-public class ParentLastClassLoader extends URLClassLoader {
-    public ParentLastClassLoader(URL[] urls, ClassLoader parent) {
-        super(urls, parent);
-    }
-
-    public ParentLastClassLoader(URL[] urls) {
-        super(urls);
-    }
-
-    public ParentLastClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
-        super(urls, parent, factory);
-    }
-
-    /*
-    This has been overridden to look at the parent class loader last.
-    @author mhunsicker
-    */
-    @Override
-    public Class<?> loadClass(String name) throws ClassNotFoundException {
-        // First check whether it's already been loaded, if so use it
-        Class loadedClass = findLoadedClass(name);
-
-        // Not loaded, try to load it
-        if (loadedClass == null) {
-            try {
-                // Ignore parent delegation and just try to load locally
-                loadedClass = findClass(name);
-            } catch (ClassNotFoundException e) {
-                // Swallow exception - does not exist locally
-            }
-
-            // If not found locally, use normal parent delegation in URLClassloader
-            if (loadedClass == null) {
-                // throws ClassNotFoundException if not found in delegation hierarchy at all
-                loadedClass = super.loadClass(name);
-            }
-        }
-        // will never return null (ClassNotFoundException will be thrown)
-        return loadedClass;
-    }
-}
-
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
deleted file mode 100644
index a631e82..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external;
-
-import org.gradle.foundation.BootstrapLoader;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.regex.Pattern;
-
-/**
- * Utility functions required by the OpenAPI
- *
- * @author mhunsicker
- */
-public class ExternalUtility {
-    private static final Pattern GRADLE_CORE_PATTERN = Pattern.compile("^gradle-core-\\d.*\\.jar$");
-
-    /**
-     * Call this to get a classloader that has loaded gradle.
-     *
-     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
-     * @param gradleHomeDirectory the root directory of a gradle installation
-     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
-     * @return a classloader that has loaded gradle and all of its dependencies.
-     * @author mhunsicker
-     */
-
-    public static ClassLoader getGradleClassloader(ClassLoader parentClassLoader, File gradleHomeDirectory, boolean showDebugInfo) throws Exception {
-        File gradleJarFile = getGradleJar(gradleHomeDirectory);
-        if (gradleJarFile == null) {
-            throw new RuntimeException("Not a valid gradle home directory '" + gradleHomeDirectory.getAbsolutePath() + "'");
-        }
-
-        System.setProperty("gradle.home", gradleHomeDirectory.getAbsolutePath());
-
-        BootstrapLoader bootstrapLoader = new BootstrapLoader();
-        bootstrapLoader.initialize(parentClassLoader, gradleHomeDirectory, true, false, showDebugInfo);
-        return bootstrapLoader.getClassLoader();
-    }
-
-    /**
-     * This locates the gradle jar. We do NOT want the gradle-wrapper jar.
-     *
-     * @param gradleHomeDirectory the root directory of a gradle installation. We're expecting this to have a child directory named 'lib'.
-     * @return the gradle jar file. Null if we didn't find it.
-     * @author mhunsicker
-     */
-    public static File getGradleJar(File gradleHomeDirectory) {
-        File libDirectory = new File(gradleHomeDirectory, "lib");
-        if (!libDirectory.exists()) {
-            return null;
-        }
-
-        //try to get the gradle.jar. It'll be "gradle-[version].jar"
-        File[] files = libDirectory.listFiles(new FileFilter() {
-            public boolean accept(File file) {
-                return GRADLE_CORE_PATTERN.matcher(file.getName()).matches();
-            }
-        });
-
-        if (files == null || files.length == 0) {
-            return null;
-        }
-
-        //if they've given us a directory with multiple gradle jars, tell them. We won't know which one to use.
-        if (files.length > 1) {
-            throw new RuntimeException("Installation has multiple gradle jars. Cannot determine which one to use. Found files: " + createFileNamesString(files));
-        }
-
-        return files[0];
-    }
-
-    private static StringBuilder createFileNamesString(File[] files) {
-        StringBuilder fileNames = new StringBuilder();
-        for (File f : files) {
-            fileNames.append(f.getAbsolutePath() + ", ");
-        }
-        fileNames.delete(fileNames.length() - 2, fileNames.length()); // Remove the trailing ', '
-        return fileNames;
-    }
-
-    //just a function to help debugging. If we can't find the constructor we want, this dumps out what is available.
-
-    public static String dumpConstructors(Class classInQuestion) {
-        StringBuilder builder = new StringBuilder();
-        Constructor[] constructors = classInQuestion.getConstructors();
-        for (int index = 0; index < constructors.length; index++) {
-            Constructor constructor = constructors[index];
-            builder.append(constructor).append('\n');
-        }
-
-        return builder.toString();
-    }
-
-    public static String dumpMethods(Class classInQuestion) {
-        StringBuilder builder = new StringBuilder();
-
-        Method[] methods = classInQuestion.getMethods();
-        for (int index = 0; index < methods.length; index++) {
-            Method method = methods[index];
-            builder.append(method).append('\n');
-        }
-
-        return builder.toString();
-    }
-
-    /**
-     * This attempts to load the a class from the specified gradle home directory.
-     *
-     * @param classToLoad the full path to the class to load
-     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
-     * @param gradleHomeDirectory the root directory of a gradle installation
-     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
-     */
-    public static Class loadGradleClass(String classToLoad, ClassLoader parentClassLoader, File gradleHomeDirectory, boolean showDebugInfo) throws Exception {
-        ClassLoader bootStrapClassLoader = getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
-
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        try {
-            return bootStrapClassLoader.loadClass(classToLoad);
-        } catch (NoClassDefFoundError e) {  //might be a version mismatch
-            e.printStackTrace();
-            return null;
-        } catch (Throwable e) {  //might be a version mismatch
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * This wraps up invoking a static method into a single call.
-     *
-     * @param classToInvoke the class that has the method
-     * @param methodName the name of the method to invoke
-     * @param argumentsClasses the classes of the arguments (we can't determine this from the argumentValues because they can be of class A, but implement class B and B is be the argument type of the
-     * method in question
-     * @param argumentValues the values of the arguments.
-     * @return the return value of invoking the method.
-     */
-    public static Object invokeStaticMethod(Class classToInvoke, String methodName, Class[] argumentsClasses, Object... argumentValues) throws Exception {
-        Method method = null;
-        try {
-            method = classToInvoke.getDeclaredMethod(methodName, argumentsClasses);
-        } catch (NoSuchMethodException e) {
-            e.printStackTrace();
-            System.out.println("Dumping available methods on " + classToInvoke.getName() + "\n" + ExternalUtility.dumpMethods(classToInvoke));
-            throw e;
-        }
-        return method.invoke(null, argumentValues);
-    }
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java
deleted file mode 100644
index 6142d85..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.foundation;
-
-import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * This is an abstraction from Gradle that allows you to retrieve projects and views from it.
- *
- * This is a mirror of GradlePluginLord inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface GradleInterfaceVersion1 {
-
-    /**
-     * @return the root projects. It probably only has one.
-     */
-    public List<ProjectVersion1> getRootProjects();
-
-    /**
-     * This refreshes the projects and task list.
-     */
-    public void refreshTaskTree();
-
-    /**
-     * Determines if commands are currently being executed or not.
-     *
-     * @return true if we're busy, false if not.
-     */
-    public boolean isBusy();
-
-    /**
-     * Call this to execute the given gradle command.
-     *
-     * @param commandLineArguments the command line arguments to pass to gradle.
-     * @param displayName the name displayed in the UI for this command
-     */
-    public void executeCommand(String commandLineArguments, String displayName);
-
-    /**
-     * @return the root directory of your gradle project.
-     */
-    public File getCurrentDirectory();
-
-    /**
-     * @param currentDirectory the new root directory of your gradle project.
-     */
-    public void setCurrentDirectory(File currentDirectory);
-
-    /**
-     * @return the gradle home directory. Where gradle is installed.
-     */
-    public File getGradleHomeDirectory();
-
-    /**
-     * This is called to get a custom gradle executable file. If you don't run gradle.bat or gradle shell script to run gradle, use this to specify what you do run. Note: we're going to pass it the
-     * arguments that we would pass to gradle so if you don't like that, see alterCommandLineArguments. Normally, this should return null.
-     *
-     * @return the Executable to run gradle command or null to use the default
-     */
-    public File getCustomGradleExecutable();
-
-    /**
-     * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for
-     * example, an init script.
-     *
-     * @param listener the listener that modifies the command line arguments.
-     */
-    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
-
-    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java
deleted file mode 100644
index dbc817e..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.foundation;
-
-import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * This is an abstraction from Gradle that allows you to retrieve projects and views from it.
- *
- * This is a mirror of GradlePluginLord inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface GradleInterfaceVersion2 extends GradleInterfaceVersion1 {
-
-    /**
-     * @return the version of gradle being run. This is basically the version from the jar file.
-     */
-    public String getVersion();
-
-    /**
-     * This refreshes the projects and task list.
-     */
-    public RequestVersion1 refreshTaskTree2();
-
-    /**
-     * This refreshes the task tree. Useful if you know you've changed something behind gradle's back or when first displaying this UI.
-     *
-     * @param additionalCommandLineArguments additional command line arguments to be passed to gradle when refreshing the task tree.
-     * @return the request object. Useful if you want to track its completion via a RequestObserver
-     */
-    public RequestVersion1 refreshTaskTree2(String additionalCommandLineArguments);
-
-    /**
-     * Call this to execute the given gradle command.
-     *
-     * @param commandLineArguments the command line arguments to pass to gradle.
-     * @param displayName the name displayed in the UI for this command
-     * @return the request object. Useful if you want to track its completion via a RequestObserver
-     * @author mhunsicker
-     */
-    public RequestVersion1 executeCommand2(String commandLineArguments, String displayName);
-
-    /**
-     * Executes several favorites commands at once as a single command. This has the affect of simply concatenating all the favorite command lines into a single line.
-     *
-     * @param favorites a list of favorites. If just one favorite, it executes it normally. If multiple favorites, it executes them all at once as a single command. This blindly concatenates them so
-     * it may wind up with duplicate tasks on the command line.
-     * @return the request object. Useful if you want to track its completion via a RequestObserver
-     */
-    public RequestVersion1 executeFavorites(List<FavoriteTaskVersion1> favorites);
-
-    /**
-     * Sets a custom gradle executable. See getCustomGradleExecutable
-     *
-     * @param customGradleExecutor the path to an executable (or script/batch file)
-     */
-    public void setCustomGradleExecutable(File customGradleExecutor);
-
-    /**
-     * Adds an observer that is notified when Gradle commands are executed and completed.
-     *
-     * @param observer the observer that is notified
-     */
-    public void addRequestObserver(RequestObserverVersion1 observer);
-
-    /**
-     * Removes a request observer when you no longer wish to receive notifications about Gradle command being executed.
-     *
-     * @param observer the observer to remove
-     */
-    public void removeRequestObserver(RequestObserverVersion1 observer);
-}
\ No newline at end of file
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/ProjectVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/ProjectVersion1.java
deleted file mode 100644
index 366fe5c..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/ProjectVersion1.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.foundation;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * This is an abstraction of a Gradle project
- *
- * This is a mirror of ProjectView inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface ProjectVersion1 {
-
-    /**
-     * @return the name of this project
-     */
-    public String getName();
-
-    /**
-     * @return The full project name. This is just the project name if its off of the root. Otherwise, its all of its ancestors separated by colons with this project being last.
-     */
-    public String getFullProjectName();
-
-    /**
-     * @return the TaskVersion1 objects associated with this project
-     */
-    public List<TaskVersion1> getTasks();
-
-    /**
-     * @return the .gradle file this project is defined in
-     */
-    public File getFile();
-
-    /**
-     * @return the sub projects of this project
-     */
-    public List<ProjectVersion1> getSubProjects();
-
-    /**
-     * @return the parent of this project if this is a sub project. Otherwise, null
-     */
-    public ProjectVersion1 getParentProject();
-
-    /**
-     * @return a list of projects that this project depends on.
-     */
-    public List<ProjectVersion1> getDependantProjects();
-
-    public ProjectVersion1 getSubProject(String name);
-
-    public ProjectVersion1 getSubProjectFromFullPath(String fullProjectName);
-
-    public TaskVersion1 getTask(String name);
-
-    /**
-     * Builds a list of default tasks. These are defined by specifying
-     *
-     * defaultTasks 'task name'
-     *
-     * in the gradle file. There can be multiple default tasks. This only returns default tasks directly for this project and does not return them for subprojects.
-     *
-     * @return a list of default tasks or an empty list if none exist
-     */
-    public List<TaskVersion1> getDefaultTasks();
-
-    public TaskVersion1 getTaskFromFullPath(String fullTaskName);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestObserverVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestObserverVersion1.java
deleted file mode 100644
index 97a7c30..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestObserverVersion1.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.foundation;
-
-/**
- * This allows you to observer when Gradle commands are executed/complete. It is an abstraction of a GradlePluginLord.RequestObserver.
- *
- * <p>This is a mirror of GradlePluginLord.RequestObserver inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface RequestObserverVersion1 {
-
-    /**
-     * Notification that an execution request was added to the queue. This is the normal request that initiates a gradle command.
-     *
-     * @param request the request that was added
-     */
-    public void executionRequestAdded(RequestVersion1 request);
-
-    /**
-     * Notification that a refresh request was added to the queue. This type of request updates the task tree.
-     */
-    public void refreshRequestAdded(RequestVersion1 request);
-
-    /**
-     * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files. This is always called after a request has been added to the queue.
-     */
-    public void aboutToExecuteRequest(RequestVersion1 request);
-
-    /**
-     * Notification that a request has completed execution.
-     *
-     * @param request the original request containing the command that was executed
-     * @param result the result of the command
-     * @param output the output from gradle executing the command
-     */
-    public void requestExecutionComplete(RequestVersion1 request, int result, String output);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestVersion1.java
deleted file mode 100644
index 938e6d9..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestVersion1.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.foundation;
-
-/**
- * This represents an execution or refresh request sent to Gradle. Execution requests are
- * just Gradle commands (what would be the command line arguments). A refresh request is
- * what updates the task tree.
- * 
- * This is a mirror of Request inside Gradle, but this is meant to aid backward and forward compatibility by shielding you
- * from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface RequestVersion1 {
-
-    /**
-     * @return the full gradle command line of this request
-     */
-    public String getFullCommandLine();
-
-    /**
-     * @return the display name of this request. Often this is the same as the full
-     * command line, but favorites may specify something more user-friendly.
-     */
-    public String getDisplayName();
-
-    /**
-     * @return whether or not output should always be shown. If false, only show it when
-     * errors occur.
-     */
-    public boolean forceOutputToBeShown();
-
-    /**
-    * Cancels this request.
-    */
-    public boolean cancel();
-
-    public static final String EXECUTION_TYPE = "execution";
-    public static final String REFRESH_TYPE = "refresh";
-    public static final String UNKNOWN_TYPE_PREFIX = "[unknown]:";
-
-    /**
-     * @return the type of the request. Either EXECUTION, REFRESH, or something that
-     * starts with UNKNOWN_TYPE_PREFIX (followed by an internal identifier) if its not
-     * listed above.
-     */
-    public String getType();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/TaskVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/TaskVersion1.java
deleted file mode 100644
index e430b51..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/TaskVersion1.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.foundation;
-
-/**
- * This is an abstraction of a gradle task
- *
- * This is a mirror of TaskView inside Gradle, but this is meant
- * to aid backward and forward compatibility by shielding you from direct
- * changes within gradle.
- *
- * @author mhunsicker
- */
-public interface TaskVersion1 {
-
-    /**
-     * @return the project this task is associated with
-     */
-    public ProjectVersion1 getProject();
-
-    /**
-     * @return the name of this task
-     */
-    public String getName();
-
-    /**
-     * @return this tasks description
-     */
-    public String getDescription();
-
-    /**
-     * returns whether or not this is a default task for its parent project. These are defined by specifying
-     *
-     * defaultTasks 'task name'
-     *
-     * in the gradle file. There can be multiple default tasks.
-     *
-     * @return true if its a default task, false if not.
-     */
-    public boolean isDefault();
-
-    /**
-     * This generates this task's full name. This is a colon-separated string of this task and its parent projects.
-     *
-     * Example: root_project:sub_project:sub_sub_project:task.
-     */
-    public String getFullTaskName();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java
deleted file mode 100644
index 5b086f3..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.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.openapi.external.foundation.favorites;
-
-/**
- * This is an abstraction from Gradle that allows you to work with a 'favorite' task.
- *
- * This is a mirror of FavoriteTask inside Gradle, but this is meant
- * to aid backward and forward compatibility by shielding you from direct
- * changes within gradle.
- *
- * You should not implement this yourself. Only use an implementation coming from Gradle.
- *
- * @author mhunsicker
- */
-public interface FavoriteTaskVersion1 {
-    /**<!====== getFullCommandLine ============================================>
-       @return the command line that is executed
-       @author mhunsicker
-    <!=======================================================================>*/
-    public String getFullCommandLine();
-
-    /**<!====== getDisplayName ================================================>
-       @return a display name for this command
-       @author mhunsicker
-    <!=======================================================================>*/
-    public String getDisplayName();
-
-    /**<!====== alwaysShowOutput ==============================================>
-       @return true if executing this command should always show the output, false
-               to only show output if an error occurs.
-       @author mhunsicker
-    <!=======================================================================>*/
-    public boolean alwaysShowOutput();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java
deleted file mode 100644
index 3e45cde..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.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.openapi.external.foundation.favorites;
-
-import org.gradle.openapi.external.foundation.TaskVersion1;
-
-import java.awt.*;
-import java.util.List;
-
-/**
- * This is an abstraction from Gradle that allows you to obtain and edit favorites.
- *
- * <p>This is a mirror of FavoritesEditor inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface FavoritesEditorVersion1 {
-
-    /**
-     * Adds the specified favorite.
-     *
-     * @param fullCommandLine the command line that this favorite executes
-     * @param displayName a more user-friendly name for the command
-     * @param alwaysShowOutput true to always show output when this favorite is executed. False to only show output when errors occur.
-     * @return the favorite added
-     */
-    public FavoriteTaskVersion1 addFavorite(String fullCommandLine, String displayName, boolean alwaysShowOutput);
-
-    /**
-     * Sets new values on the specified favorite task. This provides a simple way to programmatically edit favorite tasks.
-     *
-     * @param favoriteTask the favorite to edit
-     * @param newFullCommandLine the new command line
-     * @param newDisplayName the new display name
-     * @param newAlwaysShowOutput the new value for whether or not to always show output (vs only showing it when an error occurs).
-     * @returns null if successful otherwise, an error suitable for displaying to the user.
-     */
-    public String editFavorite(FavoriteTaskVersion1 favoriteTask, String newFullCommandLine, String newDisplayName, boolean newAlwaysShowOutput);
-
-    /**
-     * @return a list of all favorites in the system
-     */
-    public List<FavoriteTaskVersion1> getFavoriteTasks();
-
-    /**
-     * Returns the favorite with the specified command line
-     *
-     * @param fullCommandLine the command line of the sought favorite
-     * @return the matching favorite or null if no match found.
-     */
-    public FavoriteTaskVersion1 getFavorite(String fullCommandLine);
-
-    /**
-     * Returns the favorite with the specified display name
-     *
-     * @param displayName the display name of the sought favorite
-     * @return the matching favorite or null if no match found.
-     */
-    public FavoriteTaskVersion1 getFavoriteByDisplayName(String displayName);
-
-    /**
-     * Returns the favorite with the specified task
-     *
-     * @param task the task of the sought favorite
-     * @return the matching favorite or null if no match found.
-     */
-    public FavoriteTaskVersion1 getFavorite(TaskVersion1 task);
-
-    /**
-     * Display a Swing dialog prompting the user to enter a favorite.
-     *
-     * @param parent the parent window of the dialog.
-     * @return the favorite that was added or null if the user canceled
-     */
-    public FavoriteTaskVersion1 promptUserToAddFavorite(Window parent);
-
-    /**
-     * Display a Swing dialog prompting the user to edit the specified favorite
-     *
-     * @param parent the parent window of the dialog
-     * @param favorite the favorite to edit
-     * @return true if the user made changes and accepted them, false if the user canceled.
-     */
-    public boolean promptUserToEditFavorite(Window parent, FavoriteTaskVersion1 favorite);
-
-    /**
-     * Removes the specified favorites.
-     *
-     * @param favoritesToRemove the favorites to remove
-     */
-    public void removeFavorites(List<FavoriteTaskVersion1> favoritesToRemove);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
deleted file mode 100644
index b705c56..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.runner;
-
-import org.gradle.openapi.external.ExternalUtility;
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-
-/**
- * This provides a simple way to execute gradle commands from an external process. call createGradleRunner to instantiate a gradle runner. You can then use that to execute commands.
- *
- * @author mhunsicker
- */
-public class GradleRunnerFactory {
-    /**
-     * Call this to instantiate an object that you can use to execute gradle commands directly.
-     *
-     * Note: this function is meant to be backward and forward compatible. So this signature should not change at all, however, it may take and return objects that implement ADDITIONAL interfaces. That
-     * is, it will always return a GradleRunnerVersion1, but it may also be an object that implements GradleRunnerVersion2 (notice the 2). The caller will need to dynamically determine that. The
-     * GradleRunnerInteractionVersion1 may take an object that also implements GradleRunnerInteractionVersion2. If so, we'll dynamically determine that and handle it. Of course, this all depends on
-     * what happens in the future.
-     *
-     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
-     * @param gradleHomeDirectory the root directory of a gradle installation
-     * @param interaction this is how we interact with the caller.
-     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
-     * @return a gradle runner
-     * @author mhunsicker
-     * @deprecated Use the tooling API instead.
-     */
-    @Deprecated
-    public static GradleRunnerVersion1 createGradleRunner(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        //much of this function is exception handling so if we can't obtain it via the newer factory method, then
-        //we'll try the old way, but we want to report the original exception if we can't do it either way.
-        Exception viaFactoryException = null;
-        GradleRunnerVersion1 gradleRunner = null;
-
-        //first, try it the new way
-        try {
-            gradleRunner = createGradleRunnerViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
-        } catch (Exception e) {
-            //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
-            //If not, this exception will be thrown at the end.
-            viaFactoryException = e;
-        }
-
-        //try it the old way
-        if (gradleRunner == null) {
-            gradleRunner = createGradleRunnerOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
-        }
-
-        //if we still don't have a gradle runner and we have an exception from using the factory, throw it. If we
-        //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
-        if (gradleRunner == null && viaFactoryException != null) {
-            throw viaFactoryException;
-        }
-
-        return gradleRunner;
-    }
-
-    /**
-     * This function uses a factory to instantiate a GradleRunner. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded
-     * to make decisions about how to instantiate the runner. This is needed as multiple versions of the runner are being used.
-     */
-    private static GradleRunnerVersion1 createGradleRunnerViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        Class soughtClass = ExternalUtility.loadGradleClass("org.gradle.openapi.wrappers.RunnerWrapperFactory", parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        if (soughtClass == null) {
-            return null;
-        }
-
-        Class[] argumentClasses = new Class[]{File.class, GradleRunnerInteractionVersion1.class, boolean.class};
-
-        Object gradleRunner = ExternalUtility.invokeStaticMethod(soughtClass, "createGradleRunner", argumentClasses, gradleHomeDirectory, interaction, showDebugInfo);
-        return (GradleRunnerVersion1) gradleRunner;
-    }
-
-    /**
-     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the GradleRunner and should no longer be used. It unfortunately is tied to a single wrapper class instance
-     * (which it tries to directly instantiate). This doesn't allow the GradleRunner to adaptively determine what to instantiate.
-     */
-    private static GradleRunnerVersion1 createGradleRunnerOldWay(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
-
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        Class soughtClass = null;
-        try {
-            soughtClass = bootStrapClassLoader.loadClass("org.gradle.openapi.wrappers.runner.GradleRunnerWrapper");
-        } catch (NoClassDefFoundError e) {  //might be a version mismatch
-            e.printStackTrace();
-            return null;
-        } catch (ClassNotFoundException e) {  //might be a version mismatch
-            e.printStackTrace();
-            return null;
-        }
-
-        if (soughtClass == null) {
-            return null;
-        }
-
-        //instantiate it.
-        Constructor constructor = null;
-        try {
-            constructor = soughtClass.getDeclaredConstructor(File.class, GradleRunnerInteractionVersion1.class);
-        } catch (NoSuchMethodException e) {
-            e.printStackTrace();
-            System.out.println("Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors(soughtClass));
-
-            throw e;
-        }
-
-        Object gradleRunner = constructor.newInstance(gradleHomeDirectory, interaction);
-
-        return (GradleRunnerVersion1) gradleRunner;
-    }
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java
deleted file mode 100644
index 1931e83..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.runner;
-
-import java.io.File;
-
-/**
- * .
- *
- * @author mhunsicker
- */
-public interface GradleRunnerInteractionVersion1 {
-    /**
-     * @return The root directory of your gradle project. The same directory Where you would run gradle from the command.
-     */
-    public File getWorkingDirectory();
-
-    public enum LogLevel {Quiet, Lifecycle, Debug}
-
-    public enum StackTraceLevel {InternalExceptions, Always, AlwaysFull}
-
-    /**
-     * @return the log level. This determines the detail level of information reported via reportLiveOutput and reportExecutionFinished.
-     */
-    public LogLevel getLogLevel();
-
-    /**
-     * @return the stack trace level. This determines the detail level of any stack traces should an exception occur.
-     */
-    public StackTraceLevel getStackTraceLevel();
-
-    /**
-     * Notification that overall execution has been started. This is only called once at the end.
-     */
-    public void reportExecutionStarted();
-
-    /**
-     * Notification of the total number of tasks that will be executed. This is called after reportExecutionStarted and before any tasks are executed.
-     *
-     * @param size the total number of tasks.
-     */
-    public void reportNumberOfTasksToExecute(int size);
-
-    /**
-     * Notification that a single task has completed. Note: the task you kicked off probably executes other tasks and this notifies you of those tasks and provides completion progress.
-     *
-     * @param currentTaskName the task being executed
-     * @param percentComplete the percent complete of all the tasks that make up the task you requested.
-     */
-    public void reportTaskStarted(String currentTaskName, float percentComplete);
-
-    public void reportTaskComplete(String currentTaskName, float percentComplete);
-
-    /**
-     * Report real-time output from gradle and its subsystems (such as ant).
-     *
-     * @param output a single line of text to show.
-     */
-    public void reportLiveOutput(String output);
-
-    public void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable);
-
-    /**
-     * This is called to get a custom gradle executable file. If you don't run gradle.bat or gradle shell script to run gradle, use this to specify what you do run. Note: we're going to pass it the
-     * arguments that we would pass to gradle so if you don't like that, see alterCommandLineArguments. Normaly, this should return null.
-     *
-     * @return the Executable to run gradle command or null to use the default
-     */
-    public File getCustomGradleExecutable();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerVersion1.java
deleted file mode 100644
index 3e04b04..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerVersion1.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.runner;
-
-/**
- * This executes gradle commands in an external process.
- *
- * @author mhunsicker
- */
-public interface GradleRunnerVersion1 {
-    /**
-     * Call this to execute the specified command line.
-     *
-     * @param commandLine the command to execute
-     */
-    public void executeCommand(String commandLine);
-
-    /**
-     * Call this to stop the gradle command. This is killing the process, not gracefully exiting.
-     */
-    public void killProcess();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java
deleted file mode 100644
index daf64ea..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-import java.io.File;
-
-/**
- * This is how the gradle UI panel interacts with the UI that is holding it.
- *
- * This is a mirror of AlternateUIInteraction inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface AlternateUIInteractionVersion1 {
-    /**
-     * Notification that you should open the specified file and go to the specified line. Its up to the application to determine if this file should be opened for editing or simply displayed. The
-     * difference comes into play for things like xml or html files where a user may want to open them in a browser vs a source code file where they may want to open it directly in an IDE.
-     *
-     * @param file the file to edit
-     * @param line the line to go to. -1 if no line is specified.
-     */
-    public void openFile(File file, int line);
-
-    /**
-     * This is called when we should open the specified file for editing. This version explicitly wants them edited versus just opened.
-     *
-     * @param file the file to open
-     * @param line the line to go to. -1 if no line is specified.
-     */
-    public void editFile(File file, int line);
-
-    /**
-     * Determines if we can call editFiles or openFile. This is not a dynamic answer and should always return either true of false. If you want to change the answer, return true and then handle the
-     * files differently in editFiles.
-     *
-     * @return true if support editing files, false otherwise.
-     */
-    public boolean doesSupportEditingOpeningFiles();
-
-    /**
-     * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files.
-     *
-     * @param fullCommandLine the command that's about to be executed.
-     */
-    public void aboutToExecuteCommand(String fullCommandLine);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java
deleted file mode 100644
index d251165..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java
+++ /dev/null
@@ -1,199 +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.openapi.external.ui;
-
-import org.gradle.openapi.external.foundation.GradleInterfaceVersion1;
-import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1;
-
-import javax.swing.*;
-import java.io.File;
-
-/**
- * This represents a basic gradle UI
- *
- * To use this, you'll want to get an instance of this from Gradle. Then setup your UI and add this to it via getComponent. Then call aboutToShow before you display your UI. Call close before you hide
- * your UI. You'll need to set the current directory (at any time) so gradle knows where your project is located.
- *
- * @author mhunsicker
- */
-public interface BasicGradleUIVersion1 {
-    /**
-     * Call this whenever you're about to show this panel. We'll do whatever initialization is necessary.
-     */
-    public void aboutToShow();
-
-    //
-    public interface CloseInteraction {
-        /**
-         * This is called if gradle tasks are being executed and you want to know if we can close. Ask the user.
-         *
-         * @return true if the user confirms cancelling the current tasks. False if not.
-         */
-        public boolean promptUserToConfirmClosingWhileBusy();
-    }
-
-    /**
-     * Call this to determine if you can close this pane. if we're busy, we'll ask the user if they want to close.
-     *
-     * @param closeInteraction allows us to interact with the user
-     * @return true if we can close, false if not.
-     */
-    public boolean canClose(CloseInteraction closeInteraction);
-
-    /**
-     * Call this before you close the pane. This gives it an opportunity to do cleanup. You probably should call canClose before this. It gives the app a chance to cancel if its busy.
-     */
-    public void close();
-
-    /**
-     * @return the root directory of your gradle project.
-     */
-    public File getCurrentDirectory();
-
-    /**
-     * @param currentDirectory the new root directory of your gradle project.
-     */
-    public void setCurrentDirectory(File currentDirectory);
-
-    /**
-     * @return the gradle home directory. Where gradle is installed.
-     */
-    public File getGradleHomeDirectory();
-
-    /**
-     * This is called to get a custom gradle executable file. If you don't run gradle.bat or gradle shell script to run gradle, use this to specify what you do run. Note: we're going to pass it the
-     * arguments that we would pass to gradle so if you don't like that, see alterCommandLineArguments. Normally, this should return null.
-     *
-     * @return the Executable to run gradle command or null to use the default
-     */
-    public File getCustomGradleExecutable();
-
-    /**
-     * Call this to add an additional tab to the gradle UI. You can call this at any time.
-     *
-     * @param index the index of where to add the tab.
-     * @param gradleTabVersion1 the tab to add.
-     */
-    public void addTab(int index, GradleTabVersion1 gradleTabVersion1);
-
-    /**
-     * Call this to remove one of your own tabs from this.
-     *
-     * @param gradleTabVersion1 the tab to remove
-     */
-    public void removeTab(GradleTabVersion1 gradleTabVersion1);
-
-    /**
-     * @return the total number of tabs.
-     */
-    public int getGradleTabCount();
-
-    /**
-     * @param index the index of the tab
-     * @return the name of the tab at the specified index.
-     */
-    public String getGradleTabName(int index);
-
-    /**
-     * Returns the index of the gradle tab with the specified name.
-     *
-     * @param name the name of the tab
-     * @return the index of the tab or -1 if not found
-     */
-    public int getGradleTabIndex(String name);
-
-    /**
-     * @return the currently selected tab
-     */
-    public int getCurrentGradleTab();
-
-    /**
-     * Makes the specified tab the current tab.
-     *
-     * @param index the index of the tab.
-     */
-    public void setCurrentGradleTab(int index);
-
-    /**
-     * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for
-     * example, an init script.
-     *
-     * @param listener the listener that modifies the command line arguments.
-     */
-    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
-
-    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
-
-    /**
-     * Call this to execute the given gradle command.
-     *
-     * @param commandLineArguments the command line arguments to pass to gradle.
-     * @param displayName the name displayed in the UI for this command
-     */
-    public void executeCommand(String commandLineArguments, String displayName);
-
-    /**
-     * This refreshes the task tree. Useful if you know you've changed something behind gradle's back or when first displaying this UI.
-     */
-    public void refreshTaskTree();
-
-    /**
-     * @return the output lord which shows the live output of all commands being executed. You can add observers to this as well as alter how it finds file links.
-     */
-    public OutputUILordVersion1 getOutputLord();
-
-    //these were moved to OutputUILordVersion1, but remain here for backward compatibility
-    public void addOutputObserver(OutputObserverVersion1 outputObserverVersion1);
-
-    public void removeOutputObserver(OutputObserverVersion1 outputObserverVersion1);
-
-    /**
-     * Determines if commands are currently being executed or not.
-     *
-     * @return true if we're busy, false if not.
-     */
-    public boolean isBusy();
-
-    /**
-     * Determines whether output is shown only when errors occur or always
-     *
-     * @return true to only show output if errors occur, false to show it always.
-     */
-    public boolean getOnlyShowOutputOnErrors();
-
-    /**
-     * This adds the specified component to the setup panel. It is added below the last 'default' item. You can only add 1 component here, so if you need to add multiple things, you'll have to handle
-     * adding that to yourself to the one component.
-     *
-     * @param component the component to add.
-     */
-    public void setCustomPanelToSetupTab(JComponent component);
-
-    /**
-     * This returns an object that works with lower level gradle and contains the current projects and tasks. You can also execute tasks from it and perform certain setup.
-     *
-     * @return a GradleInterfaceVersion1 object. It may also be GradleInterfaceVersion2 or a future version. You can check its type and then cast it as appropriate. This allows the caller to be
-     *         backward compatible.
-     */
-    public GradleInterfaceVersion1 getGradleInterfaceVersion1();
-
-    /**
-     * Returns a FavoritesEditor. This is useful for getting a list of all favorites or modifying them.
-     *
-     * @return a FavoritesEditorVersion1. Use this to interact with the favorites.
-     */
-    public FavoritesEditorVersion1 getFavoritesEditor();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java
deleted file mode 100644
index 50a8ef0..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-/**
- * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for example,
- * an init script.
- *
- * @author mhunsicker
- */
-public interface CommandLineArgumentAlteringListenerVersion1 {
-    /**
-     * This is called when you can add additional command line arguments. Return any additional arguments to add. This doesn't modify the existing commands.
-     *
-     * @param commandLineArguments the command line to execute.
-     * @return any command lines to add or null to leave it alone
-     */
-    public String getAdditionalCommandLineArguments(String commandLineArguments);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java
deleted file mode 100644
index ff16c62..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java
+++ /dev/null
@@ -1,29 +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.openapi.external.ui;
-
-/**
-   This interface holds onto our options and allows us to interact with the
-   caller. This is meant to interact with the Gradle UI across class loader
-   and version boundaries. That is, the open API has a single entry point
-   that shouldn't change across versions. New interfaces can be expected, but
-   we'll always allow 'version1'. This is to provide backward/forward compatibility.
-
-   @author mhunsicker
-*/
-public interface DualPaneUIInteractionVersion1 extends GradleUIInteractionVersion1
-{
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIVersion1.java
deleted file mode 100644
index 153cf1c..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIVersion1.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-import javax.swing.JComponent;
-import java.awt.Component;
-
-/**
-This is a gradle UI that is broken into two panels: one contains a tabbed pane
- of tasks, favorites, command line, etc. The other pane contains the output.
- This is meant to simplify how an IDE plugin can interact with gradle. Specifically,
- this allows the 'main' pane to be vertical and the output pane to be horizontal.
-
- To use this, you'll want to get an instance of this from Gradle. Then setup
- your UI and add this to it via getComponent. Then call aboutToShow before
- you display your UI. Call close before you hide your UI. You'll need to set
- the current directory (at any time) so gradle knows where your project is
- located.
- */
-public interface DualPaneUIVersion1 extends BasicGradleUIVersion1 {
-   /**
-      Returns a component that shows the task tree tab, favorites tab, etc.
-      suitable for inserting in your UI.
-      @return the main component
-    */
-   public JComponent getMainComponent();
-
-   /**
-      Returns a component that shows the output of the tasks being executed.
-      This is suitable for inserting in your UI.
-      @return the output component
-   */
-   public Component getOutputPanel();
-
-    /**
-     * This gets the number of opened output tabs. This is used by the Idea plugin
-     * to determine if it should close the entire output pane when a tab is closed
-     * This doesn't determine whether or not the tabs are busy. See
-     * GradleInterfaceVersion1.isBusy for that 
-     * @return the number of opened output tabs.
-     */
-   public int getNumberOfOpenedOutputTabs();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleTabVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleTabVersion1.java
deleted file mode 100644
index 02a1cf5..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleTabVersion1.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-import java.awt.Component;
-
-/**
-
- This represents a tab that the caller can add to the gradle UI.
-
-  This is a mirror of GradleTab inside Gradle, but this is meant to aid
-  backward and forward compatibility by shielding you from direct changes
-  within gradle.
-
- @author mhunsicker
-  */
-public interface GradleTabVersion1 {
-   /*
-      @return the name of this tab
-      @author mhunsicker
-   */
-   public String getName();
-
-   /*
-      This is where we should create your component.
-
-      @return the component
-      @author mhunsicker
-   */
-   public Component createComponent();
-
-   /*
-      Notification that this component is about to be shown. Do whatever
-      initialization you choose.
-      @author mhunsicker
-   */
-   public void aboutToShow();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java
deleted file mode 100644
index b0ff6e7..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-/**
- This interface holds onto our options and allows us to interact with the
- caller. This is meant to interact with the Gradle UI across class loader
- and version boundaries. That is, the open API has a single entry point
- that shouldn't change across versions. New interfaces can be expected, but
- we'll always allow 'version1'. This is to provide backward/forward compatibility.
-
- @author mhunsicker
-*/
-public interface GradleUIInteractionVersion1 {
-   /*
-      This is only called once and is how we get a hold of the AlternateUIInteraction.
-      @return an AlternateUIInteraction object. This cannot be null.
-      @author mhunsicker
-   */
-   public AlternateUIInteractionVersion1 instantiateAlternateUIInteraction();
-
-   /*
-      This is only called once and is how we get a hold of how the owner wants
-      to store preferences.
-      @return a settings object. This cannot be null.
-      @author mhunsicker
-   */
-   public SettingsNodeVersion1 instantiateSettings();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputObserverVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputObserverVersion1.java
deleted file mode 100644
index 8d90b9d..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputObserverVersion1.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-/**
- * <p>This interface informs you when the output pane is displaying requests. This is NOT for general output of gradle commands.
- *
- * <p>This is a mirror of OutputUILord.OutputObserver inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- */
-public interface OutputObserverVersion1 {
-    /**
-     * Notification that a request was added to the output. This means we've got some output that is useful to display. <!      Name             Description>
-     *
-     * @param requestID an ID you can use to identify this request when it is complete.
-     * @param fullCommandLine the command line for the request that was added
-     * @param displayName the display name of this command (often the same as the full command line)
-     * @param forceOutputToBeShown true if this request wants to force its output to be shown
-     */
-    public void executionRequestAdded(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown);
-
-    /**
-     * Notification that a refresh task list request was added to the output. This means we've got some output that is useful to display.
-     *
-     * @param requestID an ID you can use to identify this request when it is complete.
-     * @param forceOutputToBeShown true if this request wants to force its output to be shown
-     */
-    public void refreshRequestAdded(long requestID, boolean forceOutputToBeShown);
-
-    /**
-     * Notification that a request is complete. Note: if its canceled, you'll just get an outputTabClosed notification.
-     *
-     * @param requestID the ID of the request that is complete. It is given to you in executionRequestAdded or refreshRequestAdded.
-     * @param wasSuccessful true if was successful, false if not or was cancelled.
-     */
-    public void requestComplete(long requestID, boolean wasSuccessful);
-
-    /**
-     * Notification that an output tab was closed, possibly because it was canceled. You might want to know this if you want to close your IDE output window when all tabs are closed.
-     *
-     * @param requestID the ID of the request associated with this tab. It is given to you in executionRequestAdded or refreshRequestAdded.
-     */
-    public void outputTabClosed(long requestID);
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputUILordVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputUILordVersion1.java
deleted file mode 100644
index 42522d6..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputUILordVersion1.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.openapi.external.ui;
-
-import java.util.List;
-import java.awt.Font;
-
-/**
- * Provides access to aspects of gradle's output
- *
- * @author mhunsicker
- */
-public interface OutputUILordVersion1 {
-
-    public void setOutputTextFont(Font font);
-
-    public Font getOutputTextFont();
-
-    /**
-     * Call this to add file extensions to look for in the output. The files will be highlighted and are clickable by the user. This results in AlternateUIInteractionVersion1.editFile or openFile
-     * being called. This assumes the file path is the first thing on the line.
-     *
-     * @param extension the file extension
-     * @param lineNumberDelimiter optional delimiter text for line number. Whatever is after this will be assumed to be a line number. We'll only parse the numbers after this so there can be other
-     * stuff after the line number. Pass in null to ignore.
-     */
-    public void addFileExtension(String extension, String lineNumberDelimiter);
-
-    /**
-     * Creates a file link definition to find file paths in the output that have a known prefix and extension. The files will be highlighted and are clickable by the user. This results in
-     * AlternateUIInteractionVersion1.editFile or openFile being called. It also allows for an optional line number after a delimiter. This is useful if you know a certain message always precedes a
-     * file path.
-     *
-     * @param name the name of this file link definition. Used by tests mostly.
-     * @param prefix the text that is before the file path. It should be enough to make it fairly unique
-     * @param extension the expected file extension. If we don't find this extension, we do not consider the text a file's path. If there are multiple extensions, you'll have to add multiples of
-     * these.
-     * @param lineNumberDelimiter optional delimiter text for line number. Whatever is after this will be assumed to be a line number. We'll only parse the numbers after this so there can be other
-     * stuff after the line number. Pass in null to ignore.
-     */
-    public void addPrefixedFileLink(String name, String prefix, String extension, String lineNumberDelimiter);
-
-    /**
-     * @return a list of file extensions that are highlighted in the output
-     */
-    public List<String> getFileExtensions();
-
-    public void addOutputObserver(OutputObserverVersion1 outputObserverVersion1);
-
-    public void removeOutputObserver(OutputObserverVersion1 outputObserverVersion1);
-
-    /*
-    This re-executes the last execution command (ignores refresh commands).
-    This is potentially useful for IDEs to hook into (hotkey to execute last command).
-     */
-    public void reExecuteLastCommand();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SettingsNodeVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SettingsNodeVersion1.java
deleted file mode 100644
index 5cbba26..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SettingsNodeVersion1.java
+++ /dev/null
@@ -1,67 +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.openapi.external.ui;
-
-import java.util.List;
-
-/**
- * Abstraction of how settings are stored. If you're implementing this, see SettingsNode for more information.
- *
- * This is a mirror of SettingsNode inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
- *
- * @author mhunsicker
- */
-public interface SettingsNodeVersion1 {
-    public void setName(String name);
-
-    public String getName();
-
-    public void setValue(String value);
-
-    public String getValue();
-
-    public void setValueOfChild(String name, String value);
-
-    public String getValueOfChild(String name, String defaultValue);
-
-    public int getValueOfChildAsInt(String name, int defaultValue);
-
-    public void setValueOfChildAsInt(String name, int value);
-
-    public boolean getValueOfChildAsBoolean(String name, boolean defaultValue);
-
-    public void setValueOfChildAsBoolean(String name, boolean value);
-
-    public long getValueOfChildAsLong(String name, long defaultValue);
-
-    public void setValueOfChildAsLong(String name, long value);
-
-    public List<SettingsNodeVersion1> getChildNodes();
-
-    public List<SettingsNodeVersion1> getChildNodes(String name);
-
-    public SettingsNodeVersion1 addChild(String name);
-
-    public SettingsNodeVersion1 addChildIfNotPresent(String name);
-
-    public SettingsNodeVersion1 getChildNode(String name);
-
-    public SettingsNodeVersion1 getNodeAtPath(String... pathPortions);
-
-    public void removeFromParent();
-
-    public void removeAllChildren();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java
deleted file mode 100644
index 28eb3a0..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java
+++ /dev/null
@@ -1,29 +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.openapi.external.ui;
-
-/*
-   This interface holds onto our options and allows us to interact with the
-   caller. This is meant to interact with the Gradle UI across class loader
-   and version boundaries. That is, the open API has a single entry point
-   that shouldn't change across versions. New interfaces can be expected, but
-   we'll always allow 'version1'. This is to provide backward/forward compatibility.
-
-   @author mhunsicker
-*/
-public interface SinglePaneUIInteractionVersion1 extends GradleUIInteractionVersion1
-{
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java
deleted file mode 100644
index 4d452d7..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIVersion1.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.openapi.external.ui;
-
-import javax.swing.JComponent;
-
-/*
- This is a gradle UI that is entirely within a single panel (and only a panel;
- no dialog or frame). This is meant to simplify how a plugin can interact with
- gradle.
-
- To use this, you'll want to get an instance of this from Gradle. Then setup
- your UI and add this to it via getComponent. Then call aboutToShow before
- you display your UI. Call close before you hide your UI. You'll need to set
- the current directory (at any time) so gradle knows where your project is
- located.
-
- @author mhunsicker
-  */
-public interface SinglePaneUIVersion1 extends BasicGradleUIVersion1 {
-   /**
-   Returns this panel as a Swing object suitable for inserting in your UI.
-   @return the main component
-      */
-   public JComponent getComponent();
-}
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
deleted file mode 100644
index 8d97a88..0000000
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
+++ /dev/null
@@ -1,239 +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.openapi.external.ui;
-
-import org.gradle.openapi.external.ExternalUtility;
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-
-/**
- * This loads up the main gradle UI. This is intended to be used as a plugin inside another application (like an IDE) in a dynamic fashion. If you're always going to ship the entire plugin with the
- * entire Gradle dist, you don't need to use this. This is meant to dynamically load Gradle from its dist. The idea is that you point your plugin to a Gradle dist and then can always load the latest
- * version.
- *
- * @author mhunsicker
- */
-public class UIFactory {
-    private static final String UIWRAPPER_FACTORY_CLASS_NAME = "org.gradle.openapi.wrappers.UIWrapperFactory";
-
-    /**
-     * Call this to instantiate a self-contained gradle UI. That is, everything in the UI is in a single panel (versus 2 panels one for the tasks and one for the output). This will load gradle via
-     * reflection, instantiate the UI and all required gradle-related classes.
-     *
-     * <p>Note: this function is meant to be backward and forward compatible. So this signature should not change at all, however, it may take and return objects that implement ADDITIONAL interfaces.
-     * That is, it will always return SinglePaneUIVersion1, but it may also be an object that implements SinglePaneUIVersion2 (notice the 2). The caller will need to dynamically determine that. The
-     * SinglePaneUIInteractionVersion1 may take an object that also implements SinglePaneUIInteractionVersion2. If so, we'll dynamically determine that and handle it. Of course, this all depends on
-     * what happens in the future.
-     *
-     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
-     * @param gradleHomeDirectory the root directory of a gradle installation
-     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
-     * @return the UI object.
-     * @deprecated Use the tooling API instead.
-     */
-    @Deprecated
-    public static SinglePaneUIVersion1 createSinglePaneUI(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        //much of this function is exception handling so if we can't obtain it via the newer factory method, then
-        //we'll try the old way, but we want to report the original exception if we can't do it either way.
-        Exception viaFactoryException = null;
-        SinglePaneUIVersion1 gradleUI = null;
-
-        //first, try it the new way
-        try {
-            gradleUI = createSinglePaneUIViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
-        } catch (Exception e) {
-            //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
-            //If not, this exception will be thrown at the end.
-            viaFactoryException = e;
-        }
-
-        //try it the old way
-        if (gradleUI == null) {
-            gradleUI = createSinglePaneUIOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
-        }
-
-        //if we still don't have a gradle ui and we have an exception from using the factory, throw it. If we
-        //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
-        if (gradleUI == null && viaFactoryException != null) {
-            throw viaFactoryException;
-        }
-
-        return gradleUI;
-    }
-
-    /**
-     * This function uses a factory to instantiate the UI. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make
-     * decisions about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
-     */
-    private static SinglePaneUIVersion1 createSinglePaneUIViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        Class soughtClass = ExternalUtility.loadGradleClass(UIWRAPPER_FACTORY_CLASS_NAME, parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        if (soughtClass == null) {
-            return null;
-        }
-
-        Class[] argumentClasses = new Class[]{SinglePaneUIInteractionVersion1.class, boolean.class};
-
-        Object gradleUI = ExternalUtility.invokeStaticMethod(soughtClass, "createSinglePaneUI", argumentClasses, interaction, showDebugInfo);
-        return (SinglePaneUIVersion1) gradleUI;
-    }
-
-    /**
-     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the UI and should no longer be used. It unfortunately is tied to a single wrapper class instance (which it
-     * tries to directly instantiate). This doesn't allow the Gradle UI to adaptively determine what to instantiate.
-     */
-    private static SinglePaneUIVersion1 createSinglePaneUIOldWay(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 singlePaneUIArguments,
-                                                                 boolean showDebugInfo) throws Exception {
-        ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
-
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        Class soughtClass = null;
-        try {
-            soughtClass = bootStrapClassLoader.loadClass("org.gradle.openapi.wrappers.ui.SinglePaneUIWrapper");
-        } catch (NoClassDefFoundError e) {  //might be a version mismatch
-            e.printStackTrace();
-            return null;
-        } catch (ClassNotFoundException e) {  //might be a version mismatch
-            e.printStackTrace();
-        }
-        if (soughtClass == null) {
-            return null;
-        }
-
-        //instantiate it.
-        Constructor constructor = null;
-        try {
-            constructor = soughtClass.getDeclaredConstructor(SinglePaneUIInteractionVersion1.class);
-        } catch (NoSuchMethodException e) {
-            e.printStackTrace();
-            System.out.println("Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors(soughtClass));
-
-            throw e;
-        }
-        Object singlePaneUI = constructor.newInstance(singlePaneUIArguments);
-        return (SinglePaneUIVersion1) singlePaneUI;
-    }
-
-    /**
-     * Call this to instantiate a gradle UI that contains the main tab control separate from the output panel. This allows you to position the output however you like. For example: you can place the
-     * main pane along the side going vertically and you can place the output pane along the bottom going horizontally. This will load gradle via reflection, instantiate the UI and all required
-     * gradle-related classes.
-     *
-     * <p>Note: this function is meant to be backward and forward compatible. So this signature should not change at all, however, it may take and return objects that implement ADDITIONAL interfaces.
-     * That is, it will always return SinglePaneUIVersion1, but it may also be an object that implements SinglePaneUIVersion2 (notice the 2). The caller will need to dynamically determine that. The
-     * SinglePaneUIInteractionVersion1 may take an object that also implements SinglePaneUIInteractionVersion2. If so, we'll dynamically determine that and handle it. Of course, this all depends on
-     * what happens in the future.
-     *
-     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
-     * @param gradleHomeDirectory the root directory of a gradle installation
-     * @param interaction this is how we interact with the caller.
-     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
-     * @return the UI object.
-     * @author mhunsicker
-     * @deprecated Use the tooling API instead.
-     */
-    @Deprecated
-    public static DualPaneUIVersion1 createDualPaneUI(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        //much of this function is exception handling so if we can't obtain it via the newer factory method, then
-        //we'll try the old way, but we want to report the original exception if we can't do it either way.
-        Exception viaFactoryException = null;
-        DualPaneUIVersion1 gradleUI = null;
-
-        //first, try it the new way
-        try {
-            gradleUI = createDualPaneUIViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
-        } catch (Exception e) {
-            //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
-            //If not, this exception will be thrown at the end.
-            viaFactoryException = e;
-        }
-
-        //try it the old way
-        if (gradleUI == null) {
-            gradleUI = createDualPaneUIOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
-        }
-
-        //if we still don't have a gradle ui and we have an exception from using the factory, throw it. If we
-        //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
-        if (gradleUI == null && viaFactoryException != null) {
-            throw viaFactoryException;
-        }
-
-        return gradleUI;
-    }
-
-    /**
-     * This function uses a factory to instantiate the UI. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make
-     * decisions about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
-     * @deprecated Use the tooling API instead.
-     */
-    @Deprecated
-    public static DualPaneUIVersion1 createDualPaneUIViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        Class soughtClass = ExternalUtility.loadGradleClass(UIWRAPPER_FACTORY_CLASS_NAME, parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        if (soughtClass == null) {
-            return null;
-        }
-
-        Class[] argumentClasses = new Class[]{DualPaneUIInteractionVersion1.class, boolean.class};
-
-        Object gradleUI = ExternalUtility.invokeStaticMethod(soughtClass, "createDualPaneUI", argumentClasses, interaction, showDebugInfo);
-        return (DualPaneUIVersion1) gradleUI;
-    }
-
-    /**
-     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the UI and should no longer be used. It unfortunately is tied to a single wrapper class instance (which it
-     * tries to directly instantiate). This doesn't allow the Gradle UI to adaptively determine what to instantiate.
-     */
-    private static DualPaneUIVersion1 createDualPaneUIOldWay(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
-            throws Exception {
-        ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
-        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
-
-        //load the class in gradle that wraps our return interface and handles versioning issues.
-        Class soughtClass = null;
-        try {
-            soughtClass = bootStrapClassLoader.loadClass("org.gradle.openapi.wrappers.ui.DualPaneUIWrapper");
-        } catch (NoClassDefFoundError e) {  //might be a version mismatch
-            e.printStackTrace();
-            return null;
-        } catch (ClassNotFoundException e) {  //might be a version mismatch
-            e.printStackTrace();
-        }
-        if (soughtClass == null) {
-            return null;
-        }
-
-        //instantiate it.
-        Constructor constructor = null;
-        try {
-            constructor = soughtClass.getDeclaredConstructor(DualPaneUIInteractionVersion1.class);
-        } catch (NoSuchMethodException e) {
-            e.printStackTrace();
-            System.out.println("Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors(soughtClass));
-
-            throw e;
-        }
-        Object gradleUI = constructor.newInstance(interaction);
-        return (DualPaneUIVersion1) gradleUI;
-    }
-}
diff --git a/subprojects/open-api/src/main/java/org/gradle/foundation/BootstrapLoader.java b/subprojects/open-api/src/main/java/org/gradle/foundation/BootstrapLoader.java
new file mode 100644
index 0000000..7f02c5e
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/foundation/BootstrapLoader.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.foundation;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ This handles the work of loading gradle dynamically. Due to jar version issues,
+ you can't just load all jar files.
+
+ This does NOT require any system or environment variables to be set.
+
+ To use this, instantiate this, then call one of the initialize functions.
+ Now you can get the class loader to load whatever classes you like.
+
+ @deprecated No replacement
+  */
+ at Deprecated
+public class BootstrapLoader {
+    private URLClassLoader libClassLoader;
+
+    public void initialize(File gradleHome, boolean bootStrapDebug) throws Exception {
+        initialize(ClassLoader.getSystemClassLoader().getParent(), gradleHome, false, true, bootStrapDebug);
+    }
+
+    /*
+      Call this to initialize gradle.
+      @param  parentClassloader    a parent class loader. Probably whatever class loader
+                                   is used by the caller.
+      @param  gradleHome           the root directory where gradle is installed. This
+                                   directory should have a 'bin' child directory.
+      @param  useParentLastClassLoader true to use a class loader that will delegate
+                                   to the parent only if it can't find it locally. This
+                                   should only be true if you're trying to load gradle
+                                   dynamically from another application.
+      @param  loadOpenAPI          True to load the gradle open API, false not to.
+                                   If you're calling this from a tool using the OpenAPI,
+                                   then you've probably already loaded it, so pass in false
+                                   here, otherwise, pass in true.
+      @param  bootStrapDebug       true to output debug information about the loading
+                                   process.
+      @throws Exception            if something goes wrong.
+   */
+    public void initialize(ClassLoader parentClassloader, File gradleHome, boolean useParentLastClassLoader, boolean loadOpenAPI, boolean bootStrapDebug) throws Exception {
+        if (gradleHome == null || !gradleHome.exists()) {
+            throw new RuntimeException("Gradle home not defined!");
+        }
+
+        if (bootStrapDebug) {
+            System.out.println("Gradle Home is declared by system property gradle.home to: " + gradleHome.getAbsolutePath());
+        }
+
+        System.setProperty("gradle.home", gradleHome.getAbsolutePath());
+
+        List<URL> loggingJars = toUrl(getLoggingJars());
+
+        List<File> nonLoggingJarFiles = getNonLoggingJars();
+        removeUnwantedJarFiles(nonLoggingJarFiles, loadOpenAPI);
+        List<URL> nonLoggingJars = toUrl(nonLoggingJarFiles);
+
+        if (bootStrapDebug) {
+            System.out.println("Parent Classloader of new context classloader is: " + parentClassloader);
+            System.out.println("Adding the following files to new logging classloader: " + loggingJars);
+            System.out.println("Adding the following files to new lib classloader: " + nonLoggingJars);
+        }
+
+        URLClassLoader loggingClassLoader = new URLClassLoader(loggingJars.toArray(new URL[loggingJars.size()]), parentClassloader);
+
+        if (useParentLastClassLoader) {
+            libClassLoader = new ParentLastClassLoader(nonLoggingJars.toArray(new URL[nonLoggingJars.size()]), loggingClassLoader);
+        } else {
+            libClassLoader = new URLClassLoader(nonLoggingJars.toArray(new URL[nonLoggingJars.size()]), loggingClassLoader);
+        }
+
+        if (bootStrapDebug) {
+            System.out.println("Logging class loader: " + loggingClassLoader);
+            System.out.println("Lib class loader: " + libClassLoader);
+        }
+    }
+
+    public static File[] getGradleHomeLibClasspath() {
+        File gradleHomeLib = new File(System.getProperty("gradle.home") + "/lib");
+        if (gradleHomeLib.isDirectory()) {
+            return gradleHomeLib.listFiles();
+        }
+        return new File[0];
+    }
+
+    public static List<File> getNonLoggingJars() {
+        List<File> pathElements = new ArrayList<File>();
+        for (File file : getGradleClasspath()) {
+            if (!isLogLib(file)) {
+                pathElements.add(file);
+            }
+        }
+        return pathElements;
+    }
+
+    public static List<File> getLoggingJars() {
+        List<File> pathElements = new ArrayList<File>();
+        for (File file : getGradleClasspath()) {
+            if (isLogLib(file)) {
+                pathElements.add(file);
+            }
+        }
+        return pathElements;
+    }
+
+    private static boolean isLogLib(File file) {
+        return file.getName().startsWith("logback") || file.getName().startsWith("slf4j");
+    }
+
+    public static List<File> getGradleClasspath() {
+        File customGradleBin = null;
+        List<File> pathElements = new ArrayList<File>();
+        if (System.getProperty("gradle.bootstrap.gradleBin") != null) {
+            customGradleBin = new File(System.getProperty("gradle.bootstrap.gradleBin"));
+            pathElements.add(customGradleBin);
+        }
+        for (File homeLibFile : getGradleHomeLibClasspath()) {
+            if (homeLibFile.isFile() && !(customGradleBin != null && homeLibFile.getName().startsWith("gradle-"))) {
+                pathElements.add(homeLibFile);
+            }
+        }
+        return pathElements;
+    }
+
+    /*
+      This removes unwanted jar files. At the time of this writing, we're only
+      interested in the open api jar.
+
+      @param  nonLoggingJarFiles a list of jar files
+      @param  loadOpenAPI        true to keep the open api jar, false to remove it.
+   */
+    private void removeUnwantedJarFiles(List<File> nonLoggingJarFiles, boolean loadOpenAPI) {
+        if (loadOpenAPI) {
+            return;
+        }
+
+        Iterator<File> iterator = nonLoggingJarFiles.iterator();
+        while (iterator.hasNext()) {
+            File file = iterator.next();
+            if (file.getName().startsWith("gradle-open-api-")) {
+                iterator.remove();
+            }
+        }
+    }
+
+    /*
+      Call this to get the class loader you can use to load gradle classes.
+      @return a URLClassLoader
+   */
+    public URLClassLoader getClassLoader() {
+        return libClassLoader;
+    }
+
+    public Class load(String classPath) throws Exception {
+        return libClassLoader.loadClass(classPath);
+    }
+
+    private static List<URL> toUrl(List<File> files) throws MalformedURLException {
+        List<URL> result = new ArrayList<URL>();
+        for (File file : files) {
+            result.add(file.toURI().toURL());
+        }
+        return result;
+    }
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/foundation/ParentLastClassLoader.java b/subprojects/open-api/src/main/java/org/gradle/foundation/ParentLastClassLoader.java
new file mode 100644
index 0000000..0d95db5
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/foundation/ParentLastClassLoader.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.foundation;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLStreamHandlerFactory;
+
+/**
+ * <p>This class loader delegates to the parent class loader ONLY if it cannot find it itself. This is meant to solve classloading issues when running something as, say, a plugin inside an application
+ * that may have already loaded a different version of some required jars. This makes sure it looks locally first. This is the opposite of a ClassLoader's typical behavior, but it necessary when you
+ * can't control the environment in which you're running.
+ *
+ * <p>Using this class can be very dangerous. You must carefully make sure you understand the ramifications of using this. You should also probably make this the first class loader between your plugin
+ * and the plugin's owner.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public class ParentLastClassLoader extends URLClassLoader {
+    public ParentLastClassLoader(URL[] urls, ClassLoader parent) {
+        super(urls, parent);
+    }
+
+    public ParentLastClassLoader(URL[] urls) {
+        super(urls);
+    }
+
+    public ParentLastClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
+        super(urls, parent, factory);
+    }
+
+    /*
+    This has been overridden to look at the parent class loader last.
+    */
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        // First check whether it's already been loaded, if so use it
+        Class loadedClass = findLoadedClass(name);
+
+        // Not loaded, try to load it
+        if (loadedClass == null) {
+            try {
+                // Ignore parent delegation and just try to load locally
+                loadedClass = findClass(name);
+            } catch (ClassNotFoundException e) {
+                // Swallow exception - does not exist locally
+            }
+
+            // If not found locally, use normal parent delegation in URLClassloader
+            if (loadedClass == null) {
+                // throws ClassNotFoundException if not found in delegation hierarchy at all
+                loadedClass = super.loadClass(name);
+            }
+        }
+        // will never return null (ClassNotFoundException will be thrown)
+        return loadedClass;
+    }
+}
+
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ExternalUtility.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ExternalUtility.java
new file mode 100644
index 0000000..b4743ba
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ExternalUtility.java
@@ -0,0 +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.openapi.external;
+
+import org.gradle.foundation.BootstrapLoader;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.regex.Pattern;
+
+/**
+ * Utility functions required by the OpenAPI
+ * @deprecated No replacement
+ */
+ at Deprecated
+public class ExternalUtility {
+    private static final Pattern GRADLE_CORE_PATTERN = Pattern.compile("^gradle-core-\\d.*\\.jar$");
+
+    /**
+     * Call this to get a classloader that has loaded gradle.
+     *
+     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
+     * @param gradleHomeDirectory the root directory of a gradle installation
+     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
+     * @return a classloader that has loaded gradle and all of its dependencies.
+     */
+
+    public static ClassLoader getGradleClassloader(ClassLoader parentClassLoader, File gradleHomeDirectory, boolean showDebugInfo) throws Exception {
+        File gradleJarFile = getGradleJar(gradleHomeDirectory);
+        if (gradleJarFile == null) {
+            throw new RuntimeException("Not a valid gradle home directory '" + gradleHomeDirectory.getAbsolutePath() + "'");
+        }
+
+        System.setProperty("gradle.home", gradleHomeDirectory.getAbsolutePath());
+
+        BootstrapLoader bootstrapLoader = new BootstrapLoader();
+        bootstrapLoader.initialize(parentClassLoader, gradleHomeDirectory, true, false, showDebugInfo);
+        return bootstrapLoader.getClassLoader();
+    }
+
+    /**
+     * This locates the gradle jar. We do NOT want the gradle-wrapper jar.
+     *
+     * @param gradleHomeDirectory the root directory of a gradle installation. We're expecting this to have a child directory named 'lib'.
+     * @return the gradle jar file. Null if we didn't find it.
+     */
+    public static File getGradleJar(File gradleHomeDirectory) {
+        File libDirectory = new File(gradleHomeDirectory, "lib");
+        if (!libDirectory.exists()) {
+            return null;
+        }
+
+        //try to get the gradle.jar. It'll be "gradle-[version].jar"
+        File[] files = libDirectory.listFiles(new FileFilter() {
+            public boolean accept(File file) {
+                return GRADLE_CORE_PATTERN.matcher(file.getName()).matches();
+            }
+        });
+
+        if (files == null || files.length == 0) {
+            return null;
+        }
+
+        //if they've given us a directory with multiple gradle jars, tell them. We won't know which one to use.
+        if (files.length > 1) {
+            throw new RuntimeException("Installation has multiple gradle jars. Cannot determine which one to use. Found files: " + createFileNamesString(files));
+        }
+
+        return files[0];
+    }
+
+    private static StringBuilder createFileNamesString(File[] files) {
+        StringBuilder fileNames = new StringBuilder();
+        for (File f : files) {
+            fileNames.append(f.getAbsolutePath() + ", ");
+        }
+        fileNames.delete(fileNames.length() - 2, fileNames.length()); // Remove the trailing ', '
+        return fileNames;
+    }
+
+    //just a function to help debugging. If we can't find the constructor we want, this dumps out what is available.
+
+    public static String dumpConstructors(Class classInQuestion) {
+        StringBuilder builder = new StringBuilder();
+        Constructor[] constructors = classInQuestion.getConstructors();
+        for (int index = 0; index < constructors.length; index++) {
+            Constructor constructor = constructors[index];
+            builder.append(constructor).append('\n');
+        }
+
+        return builder.toString();
+    }
+
+    public static String dumpMethods(Class classInQuestion) {
+        StringBuilder builder = new StringBuilder();
+
+        Method[] methods = classInQuestion.getMethods();
+        for (int index = 0; index < methods.length; index++) {
+            Method method = methods[index];
+            builder.append(method).append('\n');
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * This attempts to load the a class from the specified gradle home directory.
+     *
+     * @param classToLoad the full path to the class to load
+     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
+     * @param gradleHomeDirectory the root directory of a gradle installation
+     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
+     */
+    public static Class loadGradleClass(String classToLoad, ClassLoader parentClassLoader, File gradleHomeDirectory, boolean showDebugInfo) throws Exception {
+        ClassLoader bootStrapClassLoader = getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        try {
+            return bootStrapClassLoader.loadClass(classToLoad);
+        } catch (NoClassDefFoundError e) {  //might be a version mismatch
+            e.printStackTrace();
+            return null;
+        } catch (Throwable e) {  //might be a version mismatch
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * This wraps up invoking a static method into a single call.
+     *
+     * @param classToInvoke the class that has the method
+     * @param methodName the name of the method to invoke
+     * @param argumentsClasses the classes of the arguments (we can't determine this from the argumentValues because they can be of class A, but implement class B and B is be the argument type of the
+     * method in question
+     * @param argumentValues the values of the arguments.
+     * @return the return value of invoking the method.
+     */
+    public static Object invokeStaticMethod(Class classToInvoke, String methodName, Class[] argumentsClasses, Object... argumentValues) throws Exception {
+        Method method = null;
+        try {
+            method = classToInvoke.getDeclaredMethod(methodName, argumentsClasses);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            System.out.println("Dumping available methods on " + classToInvoke.getName() + "\n" + ExternalUtility.dumpMethods(classToInvoke));
+            throw e;
+        }
+        return method.invoke(null, argumentValues);
+    }
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java
new file mode 100644
index 0000000..5d1d827
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation;
+
+import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This is an abstraction from Gradle that allows you to retrieve projects and views from it.
+ *
+ * This is a mirror of GradlePluginLord inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface GradleInterfaceVersion1 {
+
+    /**
+     * @return the root projects. It probably only has one.
+     */
+    public List<ProjectVersion1> getRootProjects();
+
+    /**
+     * This refreshes the projects and task list.
+     */
+    public void refreshTaskTree();
+
+    /**
+     * Determines if commands are currently being executed or not.
+     *
+     * @return true if we're busy, false if not.
+     */
+    public boolean isBusy();
+
+    /**
+     * Call this to execute the given gradle command.
+     *
+     * @param commandLineArguments the command line arguments to pass to gradle.
+     * @param displayName the name displayed in the UI for this command
+     */
+    public void executeCommand(String commandLineArguments, String displayName);
+
+    /**
+     * @return the root directory of your gradle project.
+     */
+    public File getCurrentDirectory();
+
+    /**
+     * @param currentDirectory the new root directory of your gradle project.
+     */
+    public void setCurrentDirectory(File currentDirectory);
+
+    /**
+     * @return the gradle home directory. Where gradle is installed.
+     */
+    public File getGradleHomeDirectory();
+
+    /**
+     * This is called to get a custom gradle executable file. If you don't run gradle.bat or gradle shell script to run gradle, use this to specify what you do run. Note: we're going to pass it the
+     * arguments that we would pass to gradle so if you don't like that, see alterCommandLineArguments. Normally, this should return null.
+     *
+     * @return the Executable to run gradle command or null to use the default
+     */
+    public File getCustomGradleExecutable();
+
+    /**
+     * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for
+     * example, an init script.
+     *
+     * @param listener the listener that modifies the command line arguments.
+     */
+    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
+
+    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java
new file mode 100644
index 0000000..6250aab
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation;
+
+import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This is an abstraction from Gradle that allows you to retrieve projects and views from it.
+ *
+ * This is a mirror of GradlePluginLord inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface GradleInterfaceVersion2 extends GradleInterfaceVersion1 {
+
+    /**
+     * @return the version of gradle being run. This is basically the version from the jar file.
+     */
+    public String getVersion();
+
+    /**
+     * This refreshes the projects and task list.
+     */
+    public RequestVersion1 refreshTaskTree2();
+
+    /**
+     * This refreshes the task tree. Useful if you know you've changed something behind gradle's back or when first displaying this UI.
+     *
+     * @param additionalCommandLineArguments additional command line arguments to be passed to gradle when refreshing the task tree.
+     * @return the request object. Useful if you want to track its completion via a RequestObserver
+     */
+    public RequestVersion1 refreshTaskTree2(String additionalCommandLineArguments);
+
+    /**
+     * Call this to execute the given gradle command.
+     *
+     * @param commandLineArguments the command line arguments to pass to gradle.
+     * @param displayName the name displayed in the UI for this command
+     * @return the request object. Useful if you want to track its completion via a RequestObserver
+     */
+    public RequestVersion1 executeCommand2(String commandLineArguments, String displayName);
+
+    /**
+     * Executes several favorites commands at once as a single command. This has the affect of simply concatenating all the favorite command lines into a single line.
+     *
+     * @param favorites a list of favorites. If just one favorite, it executes it normally. If multiple favorites, it executes them all at once as a single command. This blindly concatenates them so
+     * it may wind up with duplicate tasks on the command line.
+     * @return the request object. Useful if you want to track its completion via a RequestObserver
+     */
+    public RequestVersion1 executeFavorites(List<FavoriteTaskVersion1> favorites);
+
+    /**
+     * Sets a custom gradle executable. See getCustomGradleExecutable
+     *
+     * @param customGradleExecutor the path to an executable (or script/batch file)
+     */
+    public void setCustomGradleExecutable(File customGradleExecutor);
+
+    /**
+     * Adds an observer that is notified when Gradle commands are executed and completed.
+     *
+     * @param observer the observer that is notified
+     */
+    public void addRequestObserver(RequestObserverVersion1 observer);
+
+    /**
+     * Removes a request observer when you no longer wish to receive notifications about Gradle command being executed.
+     *
+     * @param observer the observer to remove
+     */
+    public void removeRequestObserver(RequestObserverVersion1 observer);
+}
\ No newline at end of file
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/ProjectVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/ProjectVersion1.java
new file mode 100644
index 0000000..16f00fb
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/ProjectVersion1.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This is an abstraction of a Gradle project
+ *
+ * This is a mirror of ProjectView inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface ProjectVersion1 {
+
+    /**
+     * @return the name of this project
+     */
+    public String getName();
+
+    /**
+     * @return The full project name. This is just the project name if its off of the root. Otherwise, its all of its ancestors separated by colons with this project being last.
+     */
+    public String getFullProjectName();
+
+    /**
+     * @return the TaskVersion1 objects associated with this project
+     */
+    public List<TaskVersion1> getTasks();
+
+    /**
+     * @return the .gradle file this project is defined in
+     */
+    public File getFile();
+
+    /**
+     * @return the sub projects of this project
+     */
+    public List<ProjectVersion1> getSubProjects();
+
+    /**
+     * @return the parent of this project if this is a sub project. Otherwise, null
+     */
+    public ProjectVersion1 getParentProject();
+
+    /**
+     * @return a list of projects that this project depends on.
+     */
+    public List<ProjectVersion1> getDependantProjects();
+
+    public ProjectVersion1 getSubProject(String name);
+
+    public ProjectVersion1 getSubProjectFromFullPath(String fullProjectName);
+
+    public TaskVersion1 getTask(String name);
+
+    /**
+     * Builds a list of default tasks. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks. This only returns default tasks directly for this project and does not return them for subprojects.
+     *
+     * @return a list of default tasks or an empty list if none exist
+     */
+    public List<TaskVersion1> getDefaultTasks();
+
+    public TaskVersion1 getTaskFromFullPath(String fullTaskName);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/RequestObserverVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/RequestObserverVersion1.java
new file mode 100644
index 0000000..da6a2aa
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/RequestObserverVersion1.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation;
+
+/**
+ * This allows you to observer when Gradle commands are executed/complete. It is an abstraction of a GradlePluginLord.RequestObserver.
+ *
+ * <p>This is a mirror of GradlePluginLord.RequestObserver inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface RequestObserverVersion1 {
+
+    /**
+     * Notification that an execution request was added to the queue. This is the normal request that initiates a gradle command.
+     *
+     * @param request the request that was added
+     */
+    public void executionRequestAdded(RequestVersion1 request);
+
+    /**
+     * Notification that a refresh request was added to the queue. This type of request updates the task tree.
+     */
+    public void refreshRequestAdded(RequestVersion1 request);
+
+    /**
+     * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files. This is always called after a request has been added to the queue.
+     */
+    public void aboutToExecuteRequest(RequestVersion1 request);
+
+    /**
+     * Notification that a request has completed execution.
+     *
+     * @param request the original request containing the command that was executed
+     * @param result the result of the command
+     * @param output the output from gradle executing the command
+     */
+    public void requestExecutionComplete(RequestVersion1 request, int result, String output);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/RequestVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/RequestVersion1.java
new file mode 100644
index 0000000..27678d4
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/RequestVersion1.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.openapi.external.foundation;
+
+/**
+ * This represents an execution or refresh request sent to Gradle. Execution requests are
+ * just Gradle commands (what would be the command line arguments). A refresh request is
+ * what updates the task tree.
+ * 
+ * This is a mirror of Request inside Gradle, but this is meant to aid backward and forward compatibility by shielding you
+ * from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface RequestVersion1 {
+
+    /**
+     * @return the full gradle command line of this request
+     */
+    public String getFullCommandLine();
+
+    /**
+     * @return the display name of this request. Often this is the same as the full
+     * command line, but favorites may specify something more user-friendly.
+     */
+    public String getDisplayName();
+
+    /**
+     * @return whether or not output should always be shown. If false, only show it when
+     * errors occur.
+     */
+    public boolean forceOutputToBeShown();
+
+    /**
+    * Cancels this request.
+    */
+    public boolean cancel();
+
+    public static final String EXECUTION_TYPE = "execution";
+    public static final String REFRESH_TYPE = "refresh";
+    public static final String UNKNOWN_TYPE_PREFIX = "[unknown]:";
+
+    /**
+     * @return the type of the request. Either EXECUTION, REFRESH, or something that
+     * starts with UNKNOWN_TYPE_PREFIX (followed by an internal identifier) if its not
+     * listed above.
+     */
+    public String getType();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/TaskVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/TaskVersion1.java
new file mode 100644
index 0000000..2934052
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/TaskVersion1.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation;
+
+/**
+ * This is an abstraction of a gradle task
+ *
+ * This is a mirror of TaskView inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface TaskVersion1 {
+
+    /**
+     * @return the project this task is associated with
+     */
+    public ProjectVersion1 getProject();
+
+    /**
+     * @return the name of this task
+     */
+    public String getName();
+
+    /**
+     * @return this tasks description
+     */
+    public String getDescription();
+
+    /**
+     * returns whether or not this is a default task for its parent project. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks.
+     *
+     * @return true if its a default task, false if not.
+     */
+    public boolean isDefault();
+
+    /**
+     * This generates this task's full name. This is a colon-separated string of this task and its parent projects.
+     *
+     * Example: root_project:sub_project:sub_sub_project:task.
+     */
+    public String getFullTaskName();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java
new file mode 100644
index 0000000..c5e3415
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation.favorites;
+
+/**
+ * This is an abstraction from Gradle that allows you to work with a 'favorite' task.
+ *
+ * This is a mirror of FavoriteTask inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ *
+ * You should not implement this yourself. Only use an implementation coming from Gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface FavoriteTaskVersion1 {
+    /**<!====== getFullCommandLine ============================================>
+       @return the command line that is executed
+    <!=======================================================================>*/
+    public String getFullCommandLine();
+
+    /**<!====== getDisplayName ================================================>
+       @return a display name for this command
+    <!=======================================================================>*/
+    public String getDisplayName();
+
+    /**<!====== alwaysShowOutput ==============================================>
+       @return true if executing this command should always show the output, false
+               to only show output if an error occurs.
+    <!=======================================================================>*/
+    public boolean alwaysShowOutput();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java
new file mode 100644
index 0000000..2e646d0
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.foundation.favorites;
+
+import org.gradle.openapi.external.foundation.TaskVersion1;
+
+import java.awt.*;
+import java.util.List;
+
+/**
+ * This is an abstraction from Gradle that allows you to obtain and edit favorites.
+ *
+ * <p>This is a mirror of FavoritesEditor inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface FavoritesEditorVersion1 {
+
+    /**
+     * Adds the specified favorite.
+     *
+     * @param fullCommandLine the command line that this favorite executes
+     * @param displayName a more user-friendly name for the command
+     * @param alwaysShowOutput true to always show output when this favorite is executed. False to only show output when errors occur.
+     * @return the favorite added
+     */
+    public FavoriteTaskVersion1 addFavorite(String fullCommandLine, String displayName, boolean alwaysShowOutput);
+
+    /**
+     * Sets new values on the specified favorite task. This provides a simple way to programmatically edit favorite tasks.
+     *
+     * @param favoriteTask the favorite to edit
+     * @param newFullCommandLine the new command line
+     * @param newDisplayName the new display name
+     * @param newAlwaysShowOutput the new value for whether or not to always show output (vs only showing it when an error occurs).
+     * @returns null if successful otherwise, an error suitable for displaying to the user.
+     */
+    public String editFavorite(FavoriteTaskVersion1 favoriteTask, String newFullCommandLine, String newDisplayName, boolean newAlwaysShowOutput);
+
+    /**
+     * @return a list of all favorites in the system
+     */
+    public List<FavoriteTaskVersion1> getFavoriteTasks();
+
+    /**
+     * Returns the favorite with the specified command line
+     *
+     * @param fullCommandLine the command line of the sought favorite
+     * @return the matching favorite or null if no match found.
+     */
+    public FavoriteTaskVersion1 getFavorite(String fullCommandLine);
+
+    /**
+     * Returns the favorite with the specified display name
+     *
+     * @param displayName the display name of the sought favorite
+     * @return the matching favorite or null if no match found.
+     */
+    public FavoriteTaskVersion1 getFavoriteByDisplayName(String displayName);
+
+    /**
+     * Returns the favorite with the specified task
+     *
+     * @param task the task of the sought favorite
+     * @return the matching favorite or null if no match found.
+     */
+    public FavoriteTaskVersion1 getFavorite(TaskVersion1 task);
+
+    /**
+     * Display a Swing dialog prompting the user to enter a favorite.
+     *
+     * @param parent the parent window of the dialog.
+     * @return the favorite that was added or null if the user canceled
+     */
+    public FavoriteTaskVersion1 promptUserToAddFavorite(Window parent);
+
+    /**
+     * Display a Swing dialog prompting the user to edit the specified favorite
+     *
+     * @param parent the parent window of the dialog
+     * @param favorite the favorite to edit
+     * @return true if the user made changes and accepted them, false if the user canceled.
+     */
+    public boolean promptUserToEditFavorite(Window parent, FavoriteTaskVersion1 favorite);
+
+    /**
+     * Removes the specified favorites.
+     *
+     * @param favoritesToRemove the favorites to remove
+     */
+    public void removeFavorites(List<FavoriteTaskVersion1> favoritesToRemove);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerFactory.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerFactory.java
new file mode 100644
index 0000000..02bad4e
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerFactory.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.runner;
+
+import org.gradle.openapi.external.ExternalUtility;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+
+/**
+ * This provides a simple way to execute gradle commands from an external process. call createGradleRunner to instantiate a gradle runner. You can then use that to execute commands.
+ * @deprecated Use the tooling API instead.
+ */
+ at Deprecated
+public class GradleRunnerFactory {
+    /**
+     * Call this to instantiate an object that you can use to execute gradle commands directly.
+     *
+     * Note: this function is meant to be backward and forward compatible. So this signature should not change at all, however, it may take and return objects that implement ADDITIONAL interfaces. That
+     * is, it will always return a GradleRunnerVersion1, but it may also be an object that implements GradleRunnerVersion2 (notice the 2). The caller will need to dynamically determine that. The
+     * GradleRunnerInteractionVersion1 may take an object that also implements GradleRunnerInteractionVersion2. If so, we'll dynamically determine that and handle it. Of course, this all depends on
+     * what happens in the future.
+     *
+     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
+     * @param gradleHomeDirectory the root directory of a gradle installation
+     * @param interaction this is how we interact with the caller.
+     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
+     * @return a gradle runner
+     * @deprecated Use the tooling API instead.
+     */
+    @Deprecated
+    public static GradleRunnerVersion1 createGradleRunner(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        //much of this function is exception handling so if we can't obtain it via the newer factory method, then
+        //we'll try the old way, but we want to report the original exception if we can't do it either way.
+        Exception viaFactoryException = null;
+        GradleRunnerVersion1 gradleRunner = null;
+
+        //first, try it the new way
+        try {
+            gradleRunner = createGradleRunnerViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+        } catch (Exception e) {
+            //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
+            //If not, this exception will be thrown at the end.
+            viaFactoryException = e;
+        }
+
+        //try it the old way
+        if (gradleRunner == null) {
+            gradleRunner = createGradleRunnerOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+        }
+
+        //if we still don't have a gradle runner and we have an exception from using the factory, throw it. If we
+        //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
+        if (gradleRunner == null && viaFactoryException != null) {
+            throw viaFactoryException;
+        }
+
+        return gradleRunner;
+    }
+
+    /**
+     * This function uses a factory to instantiate a GradleRunner. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded
+     * to make decisions about how to instantiate the runner. This is needed as multiple versions of the runner are being used.
+     */
+    private static GradleRunnerVersion1 createGradleRunnerViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        Class soughtClass = ExternalUtility.loadGradleClass("org.gradle.openapi.wrappers.RunnerWrapperFactory", parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        if (soughtClass == null) {
+            return null;
+        }
+
+        Class[] argumentClasses = new Class[]{File.class, GradleRunnerInteractionVersion1.class, boolean.class};
+
+        Object gradleRunner = ExternalUtility.invokeStaticMethod(soughtClass, "createGradleRunner", argumentClasses, gradleHomeDirectory, interaction, showDebugInfo);
+        return (GradleRunnerVersion1) gradleRunner;
+    }
+
+    /**
+     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the GradleRunner and should no longer be used. It unfortunately is tied to a single wrapper class instance
+     * (which it tries to directly instantiate). This doesn't allow the GradleRunner to adaptively determine what to instantiate.
+     */
+    private static GradleRunnerVersion1 createGradleRunnerOldWay(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        Class soughtClass = null;
+        try {
+            soughtClass = bootStrapClassLoader.loadClass("org.gradle.openapi.wrappers.runner.GradleRunnerWrapper");
+        } catch (NoClassDefFoundError e) {  //might be a version mismatch
+            e.printStackTrace();
+            return null;
+        } catch (ClassNotFoundException e) {  //might be a version mismatch
+            e.printStackTrace();
+            return null;
+        }
+
+        if (soughtClass == null) {
+            return null;
+        }
+
+        //instantiate it.
+        Constructor constructor = null;
+        try {
+            constructor = soughtClass.getDeclaredConstructor(File.class, GradleRunnerInteractionVersion1.class);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            System.out.println("Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors(soughtClass));
+
+            throw e;
+        }
+
+        Object gradleRunner = constructor.newInstance(gradleHomeDirectory, interaction);
+
+        return (GradleRunnerVersion1) gradleRunner;
+    }
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java
new file mode 100644
index 0000000..f06ddd3
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.runner;
+
+import java.io.File;
+
+/**
+ * .
+ * @deprecated Use the tooling API instead.
+ */
+ at Deprecated
+public interface GradleRunnerInteractionVersion1 {
+    /**
+     * @return The root directory of your gradle project. The same directory Where you would run gradle from the command.
+     */
+    public File getWorkingDirectory();
+
+    @Deprecated
+    public enum LogLevel {Quiet, Lifecycle, Debug}
+
+    @Deprecated
+    public enum StackTraceLevel {InternalExceptions, Always, AlwaysFull}
+
+    /**
+     * @return the log level. This determines the detail level of information reported via reportLiveOutput and reportExecutionFinished.
+     */
+    public LogLevel getLogLevel();
+
+    /**
+     * @return the stack trace level. This determines the detail level of any stack traces should an exception occur.
+     */
+    public StackTraceLevel getStackTraceLevel();
+
+    /**
+     * Notification that overall execution has been started. This is only called once at the end.
+     */
+    public void reportExecutionStarted();
+
+    /**
+     * Notification of the total number of tasks that will be executed. This is called after reportExecutionStarted and before any tasks are executed.
+     *
+     * @param size the total number of tasks.
+     */
+    public void reportNumberOfTasksToExecute(int size);
+
+    /**
+     * Notification that a single task has completed. Note: the task you kicked off probably executes other tasks and this notifies you of those tasks and provides completion progress.
+     *
+     * @param currentTaskName the task being executed
+     * @param percentComplete the percent complete of all the tasks that make up the task you requested.
+     */
+    public void reportTaskStarted(String currentTaskName, float percentComplete);
+
+    public void reportTaskComplete(String currentTaskName, float percentComplete);
+
+    /**
+     * Report real-time output from gradle and its subsystems (such as ant).
+     *
+     * @param output a single line of text to show.
+     */
+    public void reportLiveOutput(String output);
+
+    public void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable);
+
+    /**
+     * This is called to get a custom gradle executable file. If you don't run gradle.bat or gradle shell script to run gradle, use this to specify what you do run. Note: we're going to pass it the
+     * arguments that we would pass to gradle so if you don't like that, see alterCommandLineArguments. Normaly, this should return null.
+     *
+     * @return the Executable to run gradle command or null to use the default
+     */
+    public File getCustomGradleExecutable();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerVersion1.java
new file mode 100644
index 0000000..3397af8
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/runner/GradleRunnerVersion1.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.runner;
+
+/**
+ * This executes gradle commands in an external process.
+ * @deprecated Use the tooling API instead
+ */
+ at Deprecated
+public interface GradleRunnerVersion1 {
+    /**
+     * Call this to execute the specified command line.
+     *
+     * @param commandLine the command to execute
+     */
+    public void executeCommand(String commandLine);
+
+    /**
+     * Call this to stop the gradle command. This is killing the process, not gracefully exiting.
+     */
+    public void killProcess();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java
new file mode 100644
index 0000000..6defe7b
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import java.io.File;
+
+/**
+ * This is how the gradle UI panel interacts with the UI that is holding it.
+ *
+ * This is a mirror of AlternateUIInteraction inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface AlternateUIInteractionVersion1 {
+    /**
+     * Notification that you should open the specified file and go to the specified line. Its up to the application to determine if this file should be opened for editing or simply displayed. The
+     * difference comes into play for things like xml or html files where a user may want to open them in a browser vs a source code file where they may want to open it directly in an IDE.
+     *
+     * @param file the file to edit
+     * @param line the line to go to. -1 if no line is specified.
+     */
+    public void openFile(File file, int line);
+
+    /**
+     * This is called when we should open the specified file for editing. This version explicitly wants them edited versus just opened.
+     *
+     * @param file the file to open
+     * @param line the line to go to. -1 if no line is specified.
+     */
+    public void editFile(File file, int line);
+
+    /**
+     * Determines if we can call editFiles or openFile. This is not a dynamic answer and should always return either true of false. If you want to change the answer, return true and then handle the
+     * files differently in editFiles.
+     *
+     * @return true if support editing files, false otherwise.
+     */
+    public boolean doesSupportEditingOpeningFiles();
+
+    /**
+     * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files.
+     *
+     * @param fullCommandLine the command that's about to be executed.
+     */
+    public void aboutToExecuteCommand(String fullCommandLine);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java
new file mode 100644
index 0000000..127dd38
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import org.gradle.openapi.external.foundation.GradleInterfaceVersion1;
+import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1;
+
+import javax.swing.*;
+import java.io.File;
+
+/**
+ * This represents a basic gradle UI
+ *
+ * To use this, you'll want to get an instance of this from Gradle. Then setup your UI and add this to it via getComponent. Then call aboutToShow before you display your UI. Call close before you hide
+ * your UI. You'll need to set the current directory (at any time) so gradle knows where your project is located.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface BasicGradleUIVersion1 {
+    /**
+     * Call this whenever you're about to show this panel. We'll do whatever initialization is necessary.
+     */
+    public void aboutToShow();
+
+    //
+    public interface CloseInteraction {
+        /**
+         * This is called if gradle tasks are being executed and you want to know if we can close. Ask the user.
+         *
+         * @return true if the user confirms cancelling the current tasks. False if not.
+         */
+        public boolean promptUserToConfirmClosingWhileBusy();
+    }
+
+    /**
+     * Call this to determine if you can close this pane. if we're busy, we'll ask the user if they want to close.
+     *
+     * @param closeInteraction allows us to interact with the user
+     * @return true if we can close, false if not.
+     */
+    public boolean canClose(CloseInteraction closeInteraction);
+
+    /**
+     * Call this before you close the pane. This gives it an opportunity to do cleanup. You probably should call canClose before this. It gives the app a chance to cancel if its busy.
+     */
+    public void close();
+
+    /**
+     * @return the root directory of your gradle project.
+     */
+    public File getCurrentDirectory();
+
+    /**
+     * @param currentDirectory the new root directory of your gradle project.
+     */
+    public void setCurrentDirectory(File currentDirectory);
+
+    /**
+     * @return the gradle home directory. Where gradle is installed.
+     */
+    public File getGradleHomeDirectory();
+
+    /**
+     * This is called to get a custom gradle executable file. If you don't run gradle.bat or gradle shell script to run gradle, use this to specify what you do run. Note: we're going to pass it the
+     * arguments that we would pass to gradle so if you don't like that, see alterCommandLineArguments. Normally, this should return null.
+     *
+     * @return the Executable to run gradle command or null to use the default
+     */
+    public File getCustomGradleExecutable();
+
+    /**
+     * Call this to add an additional tab to the gradle UI. You can call this at any time.
+     *
+     * @param index the index of where to add the tab.
+     * @param gradleTabVersion1 the tab to add.
+     */
+    public void addTab(int index, GradleTabVersion1 gradleTabVersion1);
+
+    /**
+     * Call this to remove one of your own tabs from this.
+     *
+     * @param gradleTabVersion1 the tab to remove
+     */
+    public void removeTab(GradleTabVersion1 gradleTabVersion1);
+
+    /**
+     * @return the total number of tabs.
+     */
+    public int getGradleTabCount();
+
+    /**
+     * @param index the index of the tab
+     * @return the name of the tab at the specified index.
+     */
+    public String getGradleTabName(int index);
+
+    /**
+     * Returns the index of the gradle tab with the specified name.
+     *
+     * @param name the name of the tab
+     * @return the index of the tab or -1 if not found
+     */
+    public int getGradleTabIndex(String name);
+
+    /**
+     * @return the currently selected tab
+     */
+    public int getCurrentGradleTab();
+
+    /**
+     * Makes the specified tab the current tab.
+     *
+     * @param index the index of the tab.
+     */
+    public void setCurrentGradleTab(int index);
+
+    /**
+     * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for
+     * example, an init script.
+     *
+     * @param listener the listener that modifies the command line arguments.
+     */
+    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
+
+    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener);
+
+    /**
+     * Call this to execute the given gradle command.
+     *
+     * @param commandLineArguments the command line arguments to pass to gradle.
+     * @param displayName the name displayed in the UI for this command
+     */
+    public void executeCommand(String commandLineArguments, String displayName);
+
+    /**
+     * This refreshes the task tree. Useful if you know you've changed something behind gradle's back or when first displaying this UI.
+     */
+    public void refreshTaskTree();
+
+    /**
+     * @return the output lord which shows the live output of all commands being executed. You can add observers to this as well as alter how it finds file links.
+     */
+    public OutputUILordVersion1 getOutputLord();
+
+    //these were moved to OutputUILordVersion1, but remain here for backward compatibility
+    public void addOutputObserver(OutputObserverVersion1 outputObserverVersion1);
+
+    public void removeOutputObserver(OutputObserverVersion1 outputObserverVersion1);
+
+    /**
+     * Determines if commands are currently being executed or not.
+     *
+     * @return true if we're busy, false if not.
+     */
+    public boolean isBusy();
+
+    /**
+     * Determines whether output is shown only when errors occur or always
+     *
+     * @return true to only show output if errors occur, false to show it always.
+     */
+    public boolean getOnlyShowOutputOnErrors();
+
+    /**
+     * This adds the specified component to the setup panel. It is added below the last 'default' item. You can only add 1 component here, so if you need to add multiple things, you'll have to handle
+     * adding that to yourself to the one component.
+     *
+     * @param component the component to add.
+     */
+    public void setCustomPanelToSetupTab(JComponent component);
+
+    /**
+     * This returns an object that works with lower level gradle and contains the current projects and tasks. You can also execute tasks from it and perform certain setup.
+     *
+     * @return a GradleInterfaceVersion1 object. It may also be GradleInterfaceVersion2 or a future version. You can check its type and then cast it as appropriate. This allows the caller to be
+     *         backward compatible.
+     */
+    public GradleInterfaceVersion1 getGradleInterfaceVersion1();
+
+    /**
+     * Returns a FavoritesEditor. This is useful for getting a list of all favorites or modifying them.
+     *
+     * @return a FavoritesEditorVersion1. Use this to interact with the favorites.
+     */
+    public FavoritesEditorVersion1 getFavoritesEditor();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java
new file mode 100644
index 0000000..0a25472
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+/**
+ * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for example,
+ * an init script.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface CommandLineArgumentAlteringListenerVersion1 {
+    /**
+     * This is called when you can add additional command line arguments. Return any additional arguments to add. This doesn't modify the existing commands.
+     *
+     * @param commandLineArguments the command line to execute.
+     * @return any command lines to add or null to leave it alone
+     */
+    public String getAdditionalCommandLineArguments(String commandLineArguments);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java
new file mode 100644
index 0000000..609ff09
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+/**
+   This interface holds onto our options and allows us to interact with the
+   caller. This is meant to interact with the Gradle UI across class loader
+   and version boundaries. That is, the open API has a single entry point
+   that shouldn't change across versions. New interfaces can be expected, but
+   we'll always allow 'version1'. This is to provide backward/forward compatibility.
+
+   @deprecated No replacement
+*/
+ at Deprecated
+public interface DualPaneUIInteractionVersion1 extends GradleUIInteractionVersion1
+{
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/DualPaneUIVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/DualPaneUIVersion1.java
new file mode 100644
index 0000000..dec3f66
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/DualPaneUIVersion1.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import javax.swing.JComponent;
+import java.awt.Component;
+
+/**
+This is a gradle UI that is broken into two panels: one contains a tabbed pane
+ of tasks, favorites, command line, etc. The other pane contains the output.
+ This is meant to simplify how an IDE plugin can interact with gradle. Specifically,
+ this allows the 'main' pane to be vertical and the output pane to be horizontal.
+
+ To use this, you'll want to get an instance of this from Gradle. Then setup
+ your UI and add this to it via getComponent. Then call aboutToShow before
+ you display your UI. Call close before you hide your UI. You'll need to set
+ the current directory (at any time) so gradle knows where your project is
+ located.
+
+ @deprecated No replacement
+ */
+ at Deprecated
+public interface DualPaneUIVersion1 extends BasicGradleUIVersion1 {
+   /**
+      Returns a component that shows the task tree tab, favorites tab, etc.
+      suitable for inserting in your UI.
+      @return the main component
+    */
+   public JComponent getMainComponent();
+
+   /**
+      Returns a component that shows the output of the tasks being executed.
+      This is suitable for inserting in your UI.
+      @return the output component
+   */
+   public Component getOutputPanel();
+
+    /**
+     * This gets the number of opened output tabs. This is used by the Idea plugin
+     * to determine if it should close the entire output pane when a tab is closed
+     * This doesn't determine whether or not the tabs are busy. See
+     * GradleInterfaceVersion1.isBusy for that 
+     * @return the number of opened output tabs.
+     */
+   public int getNumberOfOpenedOutputTabs();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/GradleTabVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/GradleTabVersion1.java
new file mode 100644
index 0000000..64435de
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/GradleTabVersion1.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import java.awt.*;
+
+/**
+
+ This represents a tab that the caller can add to the gradle UI.
+
+  This is a mirror of GradleTab inside Gradle, but this is meant to aid
+  backward and forward compatibility by shielding you from direct changes
+  within gradle.
+ @deprecated No replacement
+  */
+ at Deprecated
+public interface GradleTabVersion1 {
+   /*
+      @return the name of this tab
+   */
+   public String getName();
+
+   /*
+      This is where we should create your component.
+
+      @return the component
+   */
+   public Component createComponent();
+
+   /*
+      Notification that this component is about to be shown. Do whatever
+      initialization you choose.
+   */
+   public void aboutToShow();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java
new file mode 100644
index 0000000..154ae7c
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.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.openapi.external.ui;
+
+/**
+ This interface holds onto our options and allows us to interact with the
+ caller. This is meant to interact with the Gradle UI across class loader
+ and version boundaries. That is, the open API has a single entry point
+ that shouldn't change across versions. New interfaces can be expected, but
+ we'll always allow 'version1'. This is to provide backward/forward compatibility.
+
+ @deprecated No replacement
+*/
+ at Deprecated
+public interface GradleUIInteractionVersion1 {
+   /*
+      This is only called once and is how we get a hold of the AlternateUIInteraction.
+      @return an AlternateUIInteraction object. This cannot be null.
+   */
+   public AlternateUIInteractionVersion1 instantiateAlternateUIInteraction();
+
+   /*
+      This is only called once and is how we get a hold of how the owner wants
+      to store preferences.
+      @return a settings object. This cannot be null.
+   */
+   public SettingsNodeVersion1 instantiateSettings();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/OutputObserverVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/OutputObserverVersion1.java
new file mode 100644
index 0000000..a9d81da
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/OutputObserverVersion1.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+/**
+ * <p>This interface informs you when the output pane is displaying requests. This is NOT for general output of gradle commands.
+ *
+ * <p>This is a mirror of OutputUILord.OutputObserver inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ *
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface OutputObserverVersion1 {
+    /**
+     * Notification that a request was added to the output. This means we've got some output that is useful to display. <!      Name             Description>
+     *
+     * @param requestID an ID you can use to identify this request when it is complete.
+     * @param fullCommandLine the command line for the request that was added
+     * @param displayName the display name of this command (often the same as the full command line)
+     * @param forceOutputToBeShown true if this request wants to force its output to be shown
+     */
+    public void executionRequestAdded(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown);
+
+    /**
+     * Notification that a refresh task list request was added to the output. This means we've got some output that is useful to display.
+     *
+     * @param requestID an ID you can use to identify this request when it is complete.
+     * @param forceOutputToBeShown true if this request wants to force its output to be shown
+     */
+    public void refreshRequestAdded(long requestID, boolean forceOutputToBeShown);
+
+    /**
+     * Notification that a request is complete. Note: if its canceled, you'll just get an outputTabClosed notification.
+     *
+     * @param requestID the ID of the request that is complete. It is given to you in executionRequestAdded or refreshRequestAdded.
+     * @param wasSuccessful true if was successful, false if not or was cancelled.
+     */
+    public void requestComplete(long requestID, boolean wasSuccessful);
+
+    /**
+     * Notification that an output tab was closed, possibly because it was canceled. You might want to know this if you want to close your IDE output window when all tabs are closed.
+     *
+     * @param requestID the ID of the request associated with this tab. It is given to you in executionRequestAdded or refreshRequestAdded.
+     */
+    public void outputTabClosed(long requestID);
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/OutputUILordVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/OutputUILordVersion1.java
new file mode 100644
index 0000000..3123d73
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/OutputUILordVersion1.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import java.awt.*;
+import java.util.List;
+
+/**
+ * Provides access to aspects of gradle's output
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface OutputUILordVersion1 {
+
+    public void setOutputTextFont(Font font);
+
+    public Font getOutputTextFont();
+
+    /**
+     * Call this to add file extensions to look for in the output. The files will be highlighted and are clickable by the user. This results in AlternateUIInteractionVersion1.editFile or openFile
+     * being called. This assumes the file path is the first thing on the line.
+     *
+     * @param extension the file extension
+     * @param lineNumberDelimiter optional delimiter text for line number. Whatever is after this will be assumed to be a line number. We'll only parse the numbers after this so there can be other
+     * stuff after the line number. Pass in null to ignore.
+     */
+    public void addFileExtension(String extension, String lineNumberDelimiter);
+
+    /**
+     * Creates a file link definition to find file paths in the output that have a known prefix and extension. The files will be highlighted and are clickable by the user. This results in
+     * AlternateUIInteractionVersion1.editFile or openFile being called. It also allows for an optional line number after a delimiter. This is useful if you know a certain message always precedes a
+     * file path.
+     *
+     * @param name the name of this file link definition. Used by tests mostly.
+     * @param prefix the text that is before the file path. It should be enough to make it fairly unique
+     * @param extension the expected file extension. If we don't find this extension, we do not consider the text a file's path. If there are multiple extensions, you'll have to add multiples of
+     * these.
+     * @param lineNumberDelimiter optional delimiter text for line number. Whatever is after this will be assumed to be a line number. We'll only parse the numbers after this so there can be other
+     * stuff after the line number. Pass in null to ignore.
+     */
+    public void addPrefixedFileLink(String name, String prefix, String extension, String lineNumberDelimiter);
+
+    /**
+     * @return a list of file extensions that are highlighted in the output
+     */
+    public List<String> getFileExtensions();
+
+    public void addOutputObserver(OutputObserverVersion1 outputObserverVersion1);
+
+    public void removeOutputObserver(OutputObserverVersion1 outputObserverVersion1);
+
+    /*
+    This re-executes the last execution command (ignores refresh commands).
+    This is potentially useful for IDEs to hook into (hotkey to execute last command).
+     */
+    public void reExecuteLastCommand();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SettingsNodeVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SettingsNodeVersion1.java
new file mode 100644
index 0000000..c2f1532
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SettingsNodeVersion1.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import java.util.List;
+
+/**
+ * Abstraction of how settings are stored. If you're implementing this, see SettingsNode for more information.
+ *
+ * This is a mirror of SettingsNode inside Gradle, but this is meant to aid backward and forward compatibility by shielding you from direct changes within gradle.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public interface SettingsNodeVersion1 {
+    public void setName(String name);
+
+    public String getName();
+
+    public void setValue(String value);
+
+    public String getValue();
+
+    public void setValueOfChild(String name, String value);
+
+    public String getValueOfChild(String name, String defaultValue);
+
+    public int getValueOfChildAsInt(String name, int defaultValue);
+
+    public void setValueOfChildAsInt(String name, int value);
+
+    public boolean getValueOfChildAsBoolean(String name, boolean defaultValue);
+
+    public void setValueOfChildAsBoolean(String name, boolean value);
+
+    public long getValueOfChildAsLong(String name, long defaultValue);
+
+    public void setValueOfChildAsLong(String name, long value);
+
+    public List<SettingsNodeVersion1> getChildNodes();
+
+    public List<SettingsNodeVersion1> getChildNodes(String name);
+
+    public SettingsNodeVersion1 addChild(String name);
+
+    public SettingsNodeVersion1 addChildIfNotPresent(String name);
+
+    public SettingsNodeVersion1 getChildNode(String name);
+
+    public SettingsNodeVersion1 getNodeAtPath(String... pathPortions);
+
+    public void removeFromParent();
+
+    public void removeAllChildren();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java
new file mode 100644
index 0000000..a047bd1
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+/*
+   This interface holds onto our options and allows us to interact with the
+   caller. This is meant to interact with the Gradle UI across class loader
+   and version boundaries. That is, the open API has a single entry point
+   that shouldn't change across versions. New interfaces can be expected, but
+   we'll always allow 'version1'. This is to provide backward/forward compatibility.
+
+   @deprecated No replacement
+*/
+ at Deprecated
+public interface SinglePaneUIInteractionVersion1 extends GradleUIInteractionVersion1
+{
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java
new file mode 100644
index 0000000..83b0967
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import javax.swing.*;
+
+/*
+ This is a gradle UI that is entirely within a single panel (and only a panel;
+ no dialog or frame). This is meant to simplify how a plugin can interact with
+ gradle.
+
+ To use this, you'll want to get an instance of this from Gradle. Then setup
+ your UI and add this to it via getComponent. Then call aboutToShow before
+ you display your UI. Call close before you hide your UI. You'll need to set
+ the current directory (at any time) so gradle knows where your project is
+ located.
+
+ @deprecated No replacement
+  */
+ at Deprecated
+public interface SinglePaneUIVersion1 extends BasicGradleUIVersion1 {
+   /**
+   Returns this panel as a Swing object suitable for inserting in your UI.
+   @return the main component
+      */
+   public JComponent getComponent();
+}
diff --git a/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/UIFactory.java b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/UIFactory.java
new file mode 100644
index 0000000..8b80bf4
--- /dev/null
+++ b/subprojects/open-api/src/main/java/org/gradle/openapi/external/ui/UIFactory.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.openapi.external.ui;
+
+import org.gradle.openapi.external.ExternalUtility;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+
+/**
+ * This loads up the main gradle UI. This is intended to be used as a plugin inside another application (like an IDE) in a dynamic fashion. If you're always going to ship the entire plugin with the
+ * entire Gradle dist, you don't need to use this. This is meant to dynamically load Gradle from its dist. The idea is that you point your plugin to a Gradle dist and then can always load the latest
+ * version.
+ * @deprecated No replacement
+ */
+ at Deprecated
+public class UIFactory {
+    private static final String UIWRAPPER_FACTORY_CLASS_NAME = "org.gradle.openapi.wrappers.UIWrapperFactory";
+
+    /**
+     * Call this to instantiate a self-contained gradle UI. That is, everything in the UI is in a single panel (versus 2 panels one for the tasks and one for the output). This will load gradle via
+     * reflection, instantiate the UI and all required gradle-related classes.
+     *
+     * <p>Note: this function is meant to be backward and forward compatible. So this signature should not change at all, however, it may take and return objects that implement ADDITIONAL interfaces.
+     * That is, it will always return SinglePaneUIVersion1, but it may also be an object that implements SinglePaneUIVersion2 (notice the 2). The caller will need to dynamically determine that. The
+     * SinglePaneUIInteractionVersion1 may take an object that also implements SinglePaneUIInteractionVersion2. If so, we'll dynamically determine that and handle it. Of course, this all depends on
+     * what happens in the future.
+     *
+     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
+     * @param gradleHomeDirectory the root directory of a gradle installation
+     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
+     * @return the UI object.
+     * @deprecated Use the tooling API instead.
+     */
+    @Deprecated
+    public static SinglePaneUIVersion1 createSinglePaneUI(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        //much of this function is exception handling so if we can't obtain it via the newer factory method, then
+        //we'll try the old way, but we want to report the original exception if we can't do it either way.
+        Exception viaFactoryException = null;
+        SinglePaneUIVersion1 gradleUI = null;
+
+        //first, try it the new way
+        try {
+            gradleUI = createSinglePaneUIViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+        } catch (Exception e) {
+            //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
+            //If not, this exception will be thrown at the end.
+            viaFactoryException = e;
+        }
+
+        //try it the old way
+        if (gradleUI == null) {
+            gradleUI = createSinglePaneUIOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+        }
+
+        //if we still don't have a gradle ui and we have an exception from using the factory, throw it. If we
+        //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
+        if (gradleUI == null && viaFactoryException != null) {
+            throw viaFactoryException;
+        }
+
+        return gradleUI;
+    }
+
+    /**
+     * This function uses a factory to instantiate the UI. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make
+     * decisions about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
+     */
+    private static SinglePaneUIVersion1 createSinglePaneUIViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        Class soughtClass = ExternalUtility.loadGradleClass(UIWRAPPER_FACTORY_CLASS_NAME, parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        if (soughtClass == null) {
+            return null;
+        }
+
+        Class[] argumentClasses = new Class[]{SinglePaneUIInteractionVersion1.class, boolean.class};
+
+        Object gradleUI = ExternalUtility.invokeStaticMethod(soughtClass, "createSinglePaneUI", argumentClasses, interaction, showDebugInfo);
+        return (SinglePaneUIVersion1) gradleUI;
+    }
+
+    /**
+     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the UI and should no longer be used. It unfortunately is tied to a single wrapper class instance (which it
+     * tries to directly instantiate). This doesn't allow the Gradle UI to adaptively determine what to instantiate.
+     */
+    private static SinglePaneUIVersion1 createSinglePaneUIOldWay(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 singlePaneUIArguments,
+                                                                 boolean showDebugInfo) throws Exception {
+        ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        Class soughtClass = null;
+        try {
+            soughtClass = bootStrapClassLoader.loadClass("org.gradle.openapi.wrappers.ui.SinglePaneUIWrapper");
+        } catch (NoClassDefFoundError e) {  //might be a version mismatch
+            e.printStackTrace();
+            return null;
+        } catch (ClassNotFoundException e) {  //might be a version mismatch
+            e.printStackTrace();
+        }
+        if (soughtClass == null) {
+            return null;
+        }
+
+        //instantiate it.
+        Constructor constructor = null;
+        try {
+            constructor = soughtClass.getDeclaredConstructor(SinglePaneUIInteractionVersion1.class);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            System.out.println("Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors(soughtClass));
+
+            throw e;
+        }
+        Object singlePaneUI = constructor.newInstance(singlePaneUIArguments);
+        return (SinglePaneUIVersion1) singlePaneUI;
+    }
+
+    /**
+     * Call this to instantiate a gradle UI that contains the main tab control separate from the output panel. This allows you to position the output however you like. For example: you can place the
+     * main pane along the side going vertically and you can place the output pane along the bottom going horizontally. This will load gradle via reflection, instantiate the UI and all required
+     * gradle-related classes.
+     *
+     * <p>Note: this function is meant to be backward and forward compatible. So this signature should not change at all, however, it may take and return objects that implement ADDITIONAL interfaces.
+     * That is, it will always return SinglePaneUIVersion1, but it may also be an object that implements SinglePaneUIVersion2 (notice the 2). The caller will need to dynamically determine that. The
+     * SinglePaneUIInteractionVersion1 may take an object that also implements SinglePaneUIInteractionVersion2. If so, we'll dynamically determine that and handle it. Of course, this all depends on
+     * what happens in the future.
+     *
+     * @param parentClassLoader Your classloader. Probably the classloader of whatever class is calling this.
+     * @param gradleHomeDirectory the root directory of a gradle installation
+     * @param interaction this is how we interact with the caller.
+     * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
+     * @return the UI object.
+     * @deprecated Use the tooling API instead.
+     */
+    @Deprecated
+    public static DualPaneUIVersion1 createDualPaneUI(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        //much of this function is exception handling so if we can't obtain it via the newer factory method, then
+        //we'll try the old way, but we want to report the original exception if we can't do it either way.
+        Exception viaFactoryException = null;
+        DualPaneUIVersion1 gradleUI = null;
+
+        //first, try it the new way
+        try {
+            gradleUI = createDualPaneUIViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+        } catch (Exception e) {
+            //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
+            //If not, this exception will be thrown at the end.
+            viaFactoryException = e;
+        }
+
+        //try it the old way
+        if (gradleUI == null) {
+            gradleUI = createDualPaneUIOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+        }
+
+        //if we still don't have a gradle ui and we have an exception from using the factory, throw it. If we
+        //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
+        if (gradleUI == null && viaFactoryException != null) {
+            throw viaFactoryException;
+        }
+
+        return gradleUI;
+    }
+
+    /**
+     * This function uses a factory to instantiate the UI. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make
+     * decisions about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
+     * @deprecated Use the tooling API instead.
+     */
+    @Deprecated
+    public static DualPaneUIVersion1 createDualPaneUIViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        Class soughtClass = ExternalUtility.loadGradleClass(UIWRAPPER_FACTORY_CLASS_NAME, parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        if (soughtClass == null) {
+            return null;
+        }
+
+        Class[] argumentClasses = new Class[]{DualPaneUIInteractionVersion1.class, boolean.class};
+
+        Object gradleUI = ExternalUtility.invokeStaticMethod(soughtClass, "createDualPaneUI", argumentClasses, interaction, showDebugInfo);
+        return (DualPaneUIVersion1) gradleUI;
+    }
+
+    /**
+     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the UI and should no longer be used. It unfortunately is tied to a single wrapper class instance (which it
+     * tries to directly instantiate). This doesn't allow the Gradle UI to adaptively determine what to instantiate.
+     */
+    private static DualPaneUIVersion1 createDualPaneUIOldWay(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
+            throws Exception {
+        ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader(parentClassLoader, gradleHomeDirectory, showDebugInfo);
+        Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+        //load the class in gradle that wraps our return interface and handles versioning issues.
+        Class soughtClass = null;
+        try {
+            soughtClass = bootStrapClassLoader.loadClass("org.gradle.openapi.wrappers.ui.DualPaneUIWrapper");
+        } catch (NoClassDefFoundError e) {  //might be a version mismatch
+            e.printStackTrace();
+            return null;
+        } catch (ClassNotFoundException e) {  //might be a version mismatch
+            e.printStackTrace();
+        }
+        if (soughtClass == null) {
+            return null;
+        }
+
+        //instantiate it.
+        Constructor constructor = null;
+        try {
+            constructor = soughtClass.getDeclaredConstructor(DualPaneUIInteractionVersion1.class);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            System.out.println("Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors(soughtClass));
+
+            throw e;
+        }
+        Object gradleUI = constructor.newInstance(interaction);
+        return (DualPaneUIVersion1) gradleUI;
+    }
+}
diff --git a/subprojects/osgi/osgi.gradle b/subprojects/osgi/osgi.gradle
index 57d7c1f..2e5d501 100644
--- a/subprojects/osgi/osgi.gradle
+++ b/subprojects/osgi/osgi.gradle
@@ -15,13 +15,13 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
     compile libraries.slf4j_api
 
-    compile module('biz.aQute:bndlib:1.50.0')
+    compile module('biz.aQute.bnd:bndlib:2.1.0')
 }
 
 useTestFixtures()
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 0c2f044..9ec248f 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
@@ -98,10 +98,4 @@ class OsgiPluginIntegrationSpec extends AbstractIntegrationSpec {
         then:
         ":jar" in nonSkippedTasks
     }
-
-    private waitForMinimumBndLastModifiedInterval() {
-
-        sleep 1000
-    }
-
 }
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.java
index 55746c9..18e4ce6 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.plugins.osgi;
 
-import aQute.lib.osgi.Analyzer;
+import aQute.bnd.osgi.Analyzer;
 
 public class ContainedVersionAnalyzer extends Analyzer {
 }
\ No newline at end of file
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
index 2ad3486..2054a8a 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
@@ -17,9 +17,6 @@ package org.gradle.api.internal.plugins.osgi;
 
 import org.gradle.internal.Factory;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultAnalyzerFactory implements Factory<ContainedVersionAnalyzer> {
     public ContainedVersionAnalyzer create() {
         return new ContainedVersionAnalyzer();
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 4f01491..973ca24 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.plugins.osgi;
 
-import aQute.lib.osgi.Analyzer;
+import aQute.bnd.osgi.Analyzer;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.java.archives.Attributes;
@@ -32,9 +32,6 @@ import java.io.IOException;
 import java.util.*;
 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.
@@ -130,15 +127,6 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
         analyzer.setClasspath(getClasspath().getFiles().toArray(new File[getClasspath().getFiles().size()]));
     }
 
-    private String instructionValueString(String instructionName) {
-        List<String> values = instructionValue(instructionName);
-        if (values == null || values.isEmpty()) {
-            return null;
-        } else {
-            return createPropertyStringFromList(values);
-        }
-    }
-
     public List<String> instructionValue(String instructionName) {
         if (instructionName.equals(Analyzer.BUNDLE_SYMBOLICNAME)) {
             return createListFromPropertyString(getSymbolicName());
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
index 9830064..8fccc6e 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
@@ -23,9 +23,6 @@ import java.util.StringTokenizer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * @author Hans Dockter
- */
 public class OsgiHelper {
     /**
      * Bundle-Version must match this pattern
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
index d593706..879669b 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
@@ -23,8 +23,6 @@ import java.util.Map;
 
 /**
  * Represents a manifest file for a JAR containing an OSGi bundle.
- * 
- * @author Hans Dockter
  */
 public interface OsgiManifest extends org.gradle.api.java.archives.Manifest {
     /**
@@ -73,7 +71,7 @@ public interface OsgiManifest extends org.gradle.api.java.archives.Manifest {
     OsgiManifest instructionReplace(String name, String... values);
 
     /**
-     * Returns all exisiting instruction.
+     * Returns all existing instruction.
      *
      * @return A map with instructions. The key of the map is the instruction name, the value a list of arguments.
      */
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
index 8da3158..53bb103 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
@@ -23,8 +23,6 @@ import org.gradle.api.plugins.JavaPlugin
 
 /**
  * A {@link Plugin} which extends the {@link JavaPlugin} to add OSGi meta-information to the project Jars.
- *
- * @author Hans Dockter
  */
 public class OsgiPlugin implements Plugin<Project> {
     public void apply(Project project) {
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
index d69aa58..37fc647 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
@@ -29,8 +29,6 @@ import java.util.concurrent.Callable;
 
 /**
  * Is mixed in into the project when applying the  {@link org.gradle.api.plugins.osgi.OsgiPlugin} .
- *
- * @author Hans Dockter
  */
 public class OsgiPluginConvention {
     private ProjectInternal project;
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java
index 64258a8..5ee2789 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java
@@ -16,11 +16,9 @@
 package org.gradle.api.internal.plugins.osgi;
 
 import org.junit.Test;
+
 import static org.junit.Assert.assertNotNull;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultAnalyzerFactoryTest {
     @Test
     public void create() {
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy
index a394af1..3e3024d 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy
@@ -15,8 +15,7 @@
  */
 package org.gradle.api.internal.plugins.osgi
 
-import aQute.lib.osgi.Analyzer
-import java.util.jar.Manifest
+import aQute.bnd.osgi.Analyzer
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.java.archives.Attributes
@@ -27,9 +26,8 @@ import spock.lang.Shared
 import spock.lang.Specification
 import spock.lang.Unroll
 
-/**
- * @author Hans Dockter
- */
+import java.util.jar.Manifest
+
 class DefaultOsgiManifestTest extends Specification {
     private static final String ARBITRARY_SECTION = "A-Different-Section"
     private static final String ARBITRARY_ATTRIBUTE = "Silly-Attribute"
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 59f5483..727f7f8 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
@@ -15,20 +15,16 @@
  */
 package org.gradle.api.plugins.osgi
 
-import org.gradle.util.HelperUtil
-import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.plugins.osgi.DefaultOsgiManifest
 import org.gradle.api.internal.plugins.osgi.OsgiHelper
+import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.plugins.JavaBasePlugin
-
-import spock.lang.Specification
+import org.gradle.util.TestUtil
 import spock.lang.Issue
+import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class OsgiPluginConventionTest extends Specification {
-    DefaultProject project = HelperUtil.createRootProject()
+    DefaultProject project = TestUtil.createRootProject()
     OsgiPluginConvention osgiPluginConvention = new OsgiPluginConvention(project)
 
     def setup() {
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
index f1c9201..4cd64a5 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
@@ -18,12 +18,12 @@ package org.gradle.api.plugins.osgi;
 
 import org.gradle.api.Project
 import org.gradle.api.plugins.JavaPlugin
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 import org.gradle.api.tasks.SourceSet
 
 public class OsgiPluginTest extends Specification {
-    private final Project project = HelperUtil.createRootProject();
+    private final Project project = TestUtil.createRootProject();
     private final OsgiPlugin osgiPlugin = new OsgiPlugin();
     
     public void appliesTheJavaPlugin() {
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
index e954b00..1945006 100644
--- a/subprojects/performance/performance.gradle
+++ b/subprojects/performance/performance.gradle
@@ -1,18 +1,39 @@
 apply from: 'src/generator.groovy'
+apply plugin: 'javascript-base'
 
 configurations {
     junit
-    plugin
+    reports
+}
+
+repositories {
+    javaScript.googleApis()
 }
 
 dependencies {
     junit 'junit:junit:4.11'
-    groovy libraries.groovy
+    reports "jquery:jquery.min:1.11.0 at js"
+    reports "flot:flot:0.8.1:min at js"
+
+    compile libraries.groovy
+    testFixturesCompile libraries.slf4j_api
     testFixturesCompile project(':internalIntegTesting')
-    plugin gradleApi()
+    testFixturesCompile 'com.googlecode.jatl:jatl:0.2.2'
+    testFixturesCompile 'org.eclipse.jgit:org.eclipse.jgit:3.0.0.201306101825-r'
+
+    testFixturesRuntime 'com.h2database:h2:1.3.171'
+
+    testCompile libraries.jsoup
 }
 
 useTestFixtures()
+useClassycle()
+
+task reportResources(type: Copy) {
+    from configurations.reports
+    into "$generatedResourcesDir/org/gradle/reporting"
+}
+sourceSets.main.output.dir generatedResourcesDir, builtBy: reportResources
 
 task small(type: ProjectGeneratorTask, description: 'Generates a small project') {
 }
@@ -91,83 +112,38 @@ task lotDependencies(type: ProjectGeneratorTask, description: 'Generates a small
 }
 
 task manyProjects(type: ProjectGeneratorTask) {
-    projects = 500
-    sourceFiles = 0
-}
-
-task singleProjectNoBuildScript(type: ProjectGeneratorTask) {
-    projects = 1
-    sourceFiles = 0
-    rootProjectTemplates = []
-    subProjectTemplates = []
-}
-
-task manyProjectNoBuildScript(type: ProjectGeneratorTask) {
-    projects = 500
+    projects = 100
     sourceFiles = 0
-    rootProjectTemplates = []
-    subProjectTemplates = []
 }
 
-task manyProjectMinimalBuildScript(type: ProjectGeneratorTask) {
-    projects = 500
-    sourceFiles = 0
-    rootProjectTemplates = ['minimal']
-    subProjectTemplates = ['minimal']
-}
-
-task manyProjectConfigInject(type: ProjectGeneratorTask) {
-    projects = 500
-    sourceFiles = 0
-    rootProjectTemplates = ['config-inject']
-    subProjectTemplates = []
-}
-
-task compilePlugin(type: JavaCompile) {
-    source 'src/configPlugin'
-    destinationDir file("$buildDir/configPlugin")
-    classpath = configurations.plugin
-}
-
-task manyProjectJavaConfig(type: ProjectGeneratorTask, dependsOn: compilePlugin) {
-    projects = 500
-    sourceFiles = 0
-    rootProjectTemplates = ['java-config']
-    subProjectTemplates = []
-}
-
-def generators = tasks.withType(ProjectGeneratorTask)
-generators.all {
+def generators = tasks.withType(ProjectGeneratorTask) {
     group = 'Project setup'
     testDependencies = configurations.junit
 }
-task all(dependsOn: generators)
-
-task prepareSamples(dependsOn: [small, multi, lotDependencies, withJUnit, withTestNG, withVerboseTestNG, withVerboseJUnit])
 
-integTestTasks.all {
-    if (buildTypes.isActive('performanceTest') || buildTypes.isActive('localPerformanceTest')) {
-        dependsOn prepareSamples
-    } else {
-        gradle.startParameter.excludedTaskNames << it.path
-    }
-    maxParallelForks = 1
-}
+task all(dependsOn: generators)
 
-tasks.integTest.testLogging {
-    showStandardStreams = true
-    lifecycle {
-        exceptionFormat 'full'
+task prepareSamples(dependsOn: [small, multi, lotDependencies, withJUnit, withTestNG, withVerboseTestNG, withVerboseJUnit, manyProjects])
+
+task report {
+    def reportDir = new File(buildDir, "performance-tests/report")
+    inputs.files sourceSets.testFixtures.runtimeClasspath
+    outputs.dir reportDir
+
+    doLast {
+        def cl = new URLClassLoader(sourceSets.testFixtures.runtimeClasspath.collect { it.toURI().toURL()} as URL[], ClassLoader.systemClassLoader.parent)
+        def store = cl.loadClass("org.gradle.performance.results.ResultsStore").newInstance()
+        try {
+            def generator = cl.loadClass("org.gradle.performance.results.ReportGenerator").newInstance()
+            generator.generate(store, reportDir)
+        } finally {
+            store.close()
+        }
     }
 }
 
-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')}
-        }
-    }
+integTest {
+    dependsOn prepareSamples
+    finalizedBy report
+    maxParallelForks = 1
 }
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
index 6863035..d0f3794 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
@@ -19,20 +19,17 @@ package org.gradle.performance
 import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
-import static org.gradle.performance.fixture.DataAmount.kbytes
-import static org.gradle.performance.fixture.Duration.millis
+import static org.gradle.performance.measure.Duration.millis
 
-/**
- * by Szczepan Faber, created at: 2/9/12
- */
 class CleanBuildPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' clean build")
     def "clean build"() {
         given:
+        runner.testId = "clean build $testProject"
         runner.testProject = testProject
         runner.tasksToRun = ['clean', 'build']
         runner.maxExecutionTimeRegression = maxExecutionTimeRegression
-        runner.maxMemoryRegression = kbytes(3000)
+        runner.targetVersions = ['1.0', '1.4', '1.8', 'last']
 
         when:
         def result = runner.run()
@@ -42,8 +39,8 @@ class CleanBuildPerformanceTest extends AbstractPerformanceTest {
 
         where:
         testProject       | maxExecutionTimeRegression
-        "small"           | millis(500)
-        "multi"           | millis(1000)
+        "small"           | millis(1000)
+        "multi"           | millis(1300)
         "lotDependencies" | millis(1000)
     }
 }
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/ConfigurationPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/ConfigurationPerformanceTest.groovy
new file mode 100644
index 0000000..05e5bff
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/ConfigurationPerformanceTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.AbstractPerformanceTest
+import spock.lang.Unroll
+
+import static org.gradle.performance.measure.Duration.millis
+
+class ConfigurationPerformanceTest extends AbstractPerformanceTest {
+    @Unroll("Project '#testProject' configuration")
+    def "configuration"() {
+        given:
+        runner.testId = "configuration $testProject"
+        runner.testProject = testProject
+        runner.tasksToRun = ['help']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.targetVersions = ['1.0', '1.1', 'last']
+
+        when:
+        def result = runner.run()
+
+        then:
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | maxExecutionTimeRegression
+        "small"           | millis(1200)
+        "multi"           | millis(1200)
+        "lotDependencies" | millis(1000)
+        "manyProjects"    | millis(1500)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
index 5b8aaff..9413fc6 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
@@ -19,20 +19,17 @@ package org.gradle.performance
 import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
-import static org.gradle.performance.fixture.DataAmount.kbytes
-import static org.gradle.performance.fixture.Duration.millis
+import static org.gradle.performance.measure.Duration.millis
 
-/**
- * by Szczepan Faber, created at: 2/9/12
- */
 class DependencyReportPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' dependency report")
     def "dependency report"() {
         given:
+        runner.testId = "dependencyReport $testProject"
         runner.testProject = testProject
         runner.tasksToRun = ['dependencyReport']
         runner.maxExecutionTimeRegression = maxExecutionTimeRegression
-        runner.maxMemoryRegression = maxMemoryRegression
+        runner.targetVersions = ['1.0', '1.8', 'last']
 
         when:
         def result = runner.run()
@@ -41,7 +38,9 @@ class DependencyReportPerformanceTest extends AbstractPerformanceTest {
         result.assertCurrentVersionHasNotRegressed()
 
         where:
-        testProject       | maxExecutionTimeRegression | maxMemoryRegression
-        "lotDependencies" | millis(7000)               | kbytes(3000)
+        testProject       | maxExecutionTimeRegression
+        "small"           | millis(1000)
+        "multi"           | millis(1000)
+        "lotDependencies" | millis(1250)
     }
 }
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
index ab5f057..98fec2a 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
@@ -78,8 +78,9 @@ task check << {
 }
         """
 
-                GradleExecuter executer = distribution.executer(workspace)
-                executer.requireGradleHome().requireOwnGradleUserHomeDir()
+                GradleExecuter executer = distribution.executer(workspace).
+                        requireGradleHome().
+                        withGradleUserHomeDir(workspace.file("user-home"))
                 10.times {
                     executer.inDirectory(buildDir).withArgument("--refresh-dependencies").withTasks('check').run()
                 }
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/FirstBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/FirstBuildPerformanceTest.groovy
new file mode 100644
index 0000000..c8fb6df
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/FirstBuildPerformanceTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.AbstractPerformanceTest
+import spock.lang.Unroll
+
+import static org.gradle.performance.measure.Duration.millis
+
+class FirstBuildPerformanceTest extends AbstractPerformanceTest {
+    @Unroll("Project '#testProject' first use")
+    def "build"() {
+        // This is just an approximation of first use. We simply recompile the scripts
+        given:
+        runner.testId = "first use $testProject"
+        runner.testProject = testProject
+        runner.tasksToRun = ['help']
+        runner.args = ['--recompile-scripts']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.targetVersions = ['1.0', '1.10', '1.12', 'last']
+
+        when:
+        def result = runner.run()
+
+        then:
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject    | maxExecutionTimeRegression
+        "manyProjects" | millis(500)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
index 1a8b65f..9ef2bda 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
@@ -19,20 +19,17 @@ package org.gradle.performance
 import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
-import static org.gradle.performance.fixture.DataAmount.kbytes
-import static org.gradle.performance.fixture.Duration.millis
+import static org.gradle.performance.measure.Duration.millis
 
-/**
- * by Szczepan Faber, created at: 2/9/12
- */
 class IdeIntegrationPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' eclipse")
     def "eclipse"() {
         given:
+        runner.testId = "eclipse $testProject"
         runner.testProject = testProject
         runner.tasksToRun = ['eclipse']
         runner.maxExecutionTimeRegression = maxExecutionTimeRegression
-        runner.maxMemoryRegression = kbytes(3000)
+        runner.targetVersions = ['1.0', '1.4', '1.8', '1.12', 'last']
 
         when:
         def result = runner.run()
@@ -42,7 +39,7 @@ class IdeIntegrationPerformanceTest extends AbstractPerformanceTest {
 
         where:
         testProject       | maxExecutionTimeRegression
-        "small"           | millis(500)
+        "small"           | millis(750)
         "multi"           | millis(1500)
         "lotDependencies" | millis(3000)
     }
@@ -50,10 +47,11 @@ class IdeIntegrationPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' idea")
     def "idea"() {
         given:
+        runner.testId = "idea $testProject"
         runner.testProject = testProject
         runner.tasksToRun = ['idea']
         runner.maxExecutionTimeRegression = maxExecutionTimeRegression
-        runner.maxMemoryRegression = kbytes(3000)
+        runner.targetVersions = ['1.0', '1.8', '1.10', '1.12', 'last']
 
         when:
         def result = runner.run()
@@ -63,7 +61,7 @@ class IdeIntegrationPerformanceTest extends AbstractPerformanceTest {
 
         where:
         testProject       | maxExecutionTimeRegression
-        "small"           | millis(500)
+        "small"           | millis(750)
         "multi"           | millis(1500)
         "lotDependencies" | millis(3000)
     }
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
index 6676d3a..6883bea 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
@@ -19,21 +19,18 @@ package org.gradle.performance
 import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
-import static org.gradle.performance.fixture.DataAmount.kbytes
-import static org.gradle.performance.fixture.Duration.millis
+import static org.gradle.performance.measure.Duration.millis
 
-/**
- * by Szczepan Faber, created at: 2/9/12
- */
 class TestExecutionPerformanceTest extends AbstractPerformanceTest {
-    @Unroll("Project '#testProject'")
+    @Unroll("Project '#testProject' test execution")
     def "test execution"() {
         given:
+        runner.testId = "test $testProject"
         runner.testProject = testProject
         runner.tasksToRun = ['cleanTest', 'test']
         runner.args = ['-q']
         runner.maxExecutionTimeRegression = maxExecutionTimeRegression
-        runner.maxMemoryRegression = kbytes(3000)
+        runner.targetVersions = ['1.0', '1.8', '1.11', 'last']
 
         when:
         def result = runner.run()
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
index 2441900..bd22cb0 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
@@ -19,20 +19,17 @@ package org.gradle.performance
 import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
-import static org.gradle.performance.fixture.DataAmount.kbytes
-import static org.gradle.performance.fixture.Duration.millis
+import static org.gradle.performance.measure.Duration.millis
 
-/**
- * by Szczepan Faber, created at: 2/9/12
- */
 class UpToDateBuildPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' up-to-date build")
     def "build"() {
         given:
+        runner.testId = "up-to-date build $testProject"
         runner.testProject = testProject
         runner.tasksToRun = ['build']
         runner.maxExecutionTimeRegression = maxExecutionTimeRegression
-        runner.maxMemoryRegression = kbytes(3000)
+        runner.targetVersions = ['1.0', '1.4', '1.8', 'last']
 
         when:
         def result = runner.run()
@@ -42,7 +39,7 @@ class UpToDateBuildPerformanceTest extends AbstractPerformanceTest {
 
         where:
         testProject       | maxExecutionTimeRegression
-        "small"           | millis(500)
+        "small"           | millis(1000)
         "multi"           | millis(1000)
         "lotDependencies" | millis(1000)
     }
diff --git a/subprojects/performance/src/templates/config-inject/build.gradle b/subprojects/performance/src/templates/config-inject/build.gradle
index 3bf7b0c..dc4fb1c 100644
--- a/subprojects/performance/src/templates/config-inject/build.gradle
+++ b/subprojects/performance/src/templates/config-inject/build.gradle
@@ -17,7 +17,7 @@ dependencies {
     compile 'commons-lang:commons-lang:2.5'
     compile "commons-httpclient:commons-httpclient:3.0"
     compile "commons-codec:commons-codec:1.2"
-    compile "org.slf4j:jcl-over-slf4j:1.6.6"
+    compile "org.slf4j:jcl-over-slf4j:1.7.5"
     compile "org.codehaus:groovy:groovy-all:2.0.5"
     compile "commons-codec:commons-codec:1.2"
     testCompile 'junit:junit:4.11'
diff --git a/subprojects/performance/src/templates/project-with-source/build.gradle b/subprojects/performance/src/templates/project-with-source/build.gradle
index 29561d0..e47923b 100644
--- a/subprojects/performance/src/templates/project-with-source/build.gradle
+++ b/subprojects/performance/src/templates/project-with-source/build.gradle
@@ -15,7 +15,7 @@ dependencies {
     compile 'commons-lang:commons-lang:2.5'
     compile "commons-httpclient:commons-httpclient:3.0"
     compile "commons-codec:commons-codec:1.2"
-    compile "org.slf4j:jcl-over-slf4j:1.6.6"
+    compile "org.slf4j:jcl-over-slf4j:1.7.5"
     compile "org.codehaus.groovy:groovy:2.0.5"
     compile "commons-codec:commons-codec:1.2"
     testCompile 'junit:junit:4.11'
@@ -53,5 +53,6 @@ tasks.withType(ScalaCompile) {
 <% } %>
 
 task dependencyReport(type: DependencyReportTask) {
+    outputs.upToDateWhen { false }
     outputFile = new File(buildDir, "dependencies.txt")
 }
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/ResultSpecification.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/ResultSpecification.groovy
new file mode 100644
index 0000000..ece5b64
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/ResultSpecification.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.PerformanceResults
+import org.gradle.performance.measure.Amount
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
+import org.gradle.performance.measure.MeasuredOperation
+import spock.lang.Specification
+
+class ResultSpecification extends Specification {
+    PerformanceResults results(Map<String, ?> options = [:]) {
+        def results = new PerformanceResults()
+        results.testId = "test-id"
+        results.testProject = "test-project"
+        results.tasks = ["clean", "build"]
+        results.args = []
+        results.operatingSystem = "some os"
+        results.jvm = "java 6"
+        results.versionUnderTest = "1.7-rc-1"
+        results.vcsBranch = "master"
+        options.each { key, value -> results."$key" = value }
+        return results
+    }
+
+    MeasuredOperation operation(Map<String, Object> args = [:]) {
+        def operation = new MeasuredOperation()
+        operation.executionTime = args.executionTime instanceof Amount ? args.executionTime : Duration.millis(args?.executionTime ?: 120)
+        operation.totalMemoryUsed = args.totalMemoryUsed instanceof Amount ? args.totalMemoryUsed : DataAmount.bytes(args?.totalMemoryUsed ?: 1024)
+        operation.totalHeapUsage = args.totalHeapUsage instanceof Amount ? args.totalHeapUsage : DataAmount.bytes(args?.totalHeapUsage ?: 4096)
+        operation.maxHeapUsage = args.maxHeapUsage instanceof Amount ? args.maxHeapUsage : DataAmount.bytes(args?.maxHeapUsage ?: 2000)
+        operation.maxUncollectedHeap = args.maxUncollectedHeap instanceof Amount ? args.maxUncollectedHeap : DataAmount.bytes(args?.maxUncollectedHeap ?: 120)
+        operation.maxCommittedHeap = args.maxCommittedHeap instanceof Amount ? args.maxCommittedHeap : DataAmount.bytes(args?.maxCommittedHeap ?: 3000)
+        operation.exception = args?.failure
+        return operation
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/AmountTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/AmountTest.groovy
deleted file mode 100644
index 5aa2e86..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/AmountTest.groovy
+++ /dev/null
@@ -1,231 +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.performance.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/performance/fixture/DurationTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/DurationTest.groovy
deleted file mode 100644
index f6e7b99..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/DurationTest.groovy
+++ /dev/null
@@ -1,59 +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.performance.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/performance/fixture/GCLoggingCollectorTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/GCLoggingCollectorTest.groovy
new file mode 100644
index 0000000..051f32c
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/GCLoggingCollectorTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.MeasuredOperation
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.Resources
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class GCLoggingCollectorTest extends Specification {
+    @Rule Resources resources = new Resources()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    def collector = new GCLoggingCollector()
+
+    @Unroll
+    def "parses GC Log #logName"() {
+        def operation = new MeasuredOperation()
+        def projectDir = tmpDir.createDir("project")
+        resources.getResource(logName).copyTo(projectDir.file("gc.txt"))
+
+        when:
+        collector.beforeExecute(projectDir, Stub(GradleExecuter))
+        collector.collect(projectDir, operation)
+
+        then:
+        operation.totalHeapUsage == DataAmount.kbytes(totalHeapUsage)
+        operation.maxHeapUsage == DataAmount.kbytes(maxHeapUsage)
+        operation.maxUncollectedHeap == DataAmount.kbytes(maxUncollectedHeap)
+        operation.maxCommittedHeap == DataAmount.kbytes(maxCommittedHeap)
+
+        where:
+        logName    | totalHeapUsage | maxHeapUsage | maxUncollectedHeap | maxCommittedHeap
+        "gc-1.txt" | 76639          | 33334        | 20002              | 44092
+        "gc-2.txt" | 140210         | 40427        | 34145              | 223360
+        "gc-3.txt" | 183544         | 119384       | 37982              | 295488
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy
index 30bf52d..396b7e5 100644
--- a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy
@@ -16,18 +16,18 @@
 
 package org.gradle.performance.fixture
 
-import spock.lang.Specification
+import org.gradle.performance.ResultSpecification
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
 
-import static org.gradle.performance.fixture.BaselineVersion.baseline
-
-class PerformanceResultsTest extends Specification {
-    def PerformanceResults result = new PerformanceResults()
+class PerformanceResultsTest extends ResultSpecification {
+    def PerformanceResults result = new PerformanceResults(testProject: "some-project", tasks: [])
 
     def "passes when average execution time for current release is smaller than average execution time for previous releases"() {
         given:
-        result.baselineVersions[0].results.add(operation(executionTime: 110))
-        result.baselineVersions[0].results.add(operation(executionTime: 100))
-        result.baselineVersions[0].results.add(operation(executionTime: 90))
+        result.baseline("1.0").results.add(operation(executionTime: 110))
+        result.baseline("1.0").results.add(operation(executionTime: 100))
+        result.baseline("1.0").results.add(operation(executionTime: 90))
 
         and:
         result.current.add(operation(executionTime: 90))
@@ -40,15 +40,14 @@ class PerformanceResultsTest extends Specification {
 
     def "passes when average execution time for current release is within specified range of average execution time for previous releases"() {
         given:
-        result.baselineVersions[0].maxExecutionTimeRegression = Duration.millis(10)
-        result.baselineVersions[0].results << operation(executionTime: 100)
-        result.baselineVersions[0].results << operation(executionTime: 100)
-        result.baselineVersions[0].results << operation(executionTime: 100)
+        result.baseline("1.0").maxExecutionTimeRegression = Duration.millis(10)
+        result.baseline("1.0").results << operation(executionTime: 100)
+        result.baseline("1.0").results << operation(executionTime: 100)
+        result.baseline("1.0").results << operation(executionTime: 100)
 
-        result.baselineVersions << baseline("1.3")
-        result.baselineVersions[1].results << operation(executionTime: 115)
-        result.baselineVersions[1].results << operation(executionTime: 105)
-        result.baselineVersions[1].results << operation(executionTime: 110)
+        result.baseline("1.3").results << operation(executionTime: 115)
+        result.baseline("1.3").results << operation(executionTime: 105)
+        result.baseline("1.3").results << operation(executionTime: 110)
 
         and:
         result.current << operation(executionTime: 110)
@@ -61,19 +60,15 @@ class PerformanceResultsTest extends Specification {
 
     def "fails when average execution time for current release is larger than average execution time for previous releases"() {
         given:
-        result.displayName = '<test>'
-
-        result.baselineVersions[0].version = '1.2' //fail
-        result.baselineVersions[0].maxExecutionTimeRegression = Duration.millis(10)
-        result.baselineVersions[0].results << operation(executionTime: 100)
-        result.baselineVersions[0].results << operation(executionTime: 100)
-        result.baselineVersions[0].results << operation(executionTime: 100)
+        result.baseline("1.0").maxExecutionTimeRegression = Duration.millis(10)
+        result.baseline("1.0").results << operation(executionTime: 100)
+        result.baseline("1.0").results << operation(executionTime: 100)
+        result.baseline("1.0").results << operation(executionTime: 100)
 
-        result.baselineVersions << baseline("1.3") //pass
-        result.baselineVersions[1].maxExecutionTimeRegression = Duration.millis(10)
-        result.baselineVersions[1].results << operation(executionTime: 101)
-        result.baselineVersions[1].results << operation(executionTime: 100)
-        result.baselineVersions[1].results << operation(executionTime: 100)
+        result.baseline("1.3").maxExecutionTimeRegression = Duration.millis(10)
+        result.baseline("1.3").results << operation(executionTime: 101)
+        result.baseline("1.3").results << operation(executionTime: 100)
+        result.baseline("1.3").results << operation(executionTime: 100)
 
         and:
         result.current << operation(executionTime: 110)
@@ -85,26 +80,25 @@ class PerformanceResultsTest extends Specification {
 
         then:
         AssertionError e = thrown()
-        e.message.startsWith("Speed <test>: we're slower than 1.2.")
+        e.message.startsWith("Speed ${result.displayName}: we're slower than 1.0.")
         e.message.contains('Difference: 10.333 ms slower (10.333 ms), 10.33%')
         !e.message.contains('1.3')
     }
 
     def "passes when average heap usage for current release is smaller than average heap usage for previous releases"() {
         given:
-        result.baselineVersions[0].results << operation(heapUsed: 1000)
-        result.baselineVersions[0].results << operation(heapUsed: 1000)
-        result.baselineVersions[0].results << operation(heapUsed: 1000)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000)
 
-        result.baselineVersions << baseline("1.3")
-        result.baselineVersions[1].results << operation(heapUsed: 800)
-        result.baselineVersions[1].results << operation(heapUsed: 1000)
-        result.baselineVersions[1].results << operation(heapUsed: 1200)
+        result.baseline("1.3").results << operation(totalMemoryUsed: 800)
+        result.baseline("1.3").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.3").results << operation(totalMemoryUsed: 1200)
 
         and:
-        result.current << operation(heapUsed: 1000)
-        result.current << operation(heapUsed: 1005)
-        result.current << operation(heapUsed: 994)
+        result.current << operation(totalMemoryUsed: 1000)
+        result.current << operation(totalMemoryUsed: 1005)
+        result.current << operation(totalMemoryUsed: 994)
 
         expect:
         result.assertCurrentVersionHasNotRegressed()
@@ -112,21 +106,20 @@ class PerformanceResultsTest extends Specification {
 
     def "passes when average heap usage for current release is slightly larger than average heap usage for previous releases"() {
         given:
-        result.baselineVersions[0].maxMemoryRegression = DataAmount.bytes(100)
-        result.baselineVersions[0].results << operation(heapUsed: 1000)
-        result.baselineVersions[0].results << operation(heapUsed: 1000)
-        result.baselineVersions[0].results << operation(heapUsed: 1000)
+        result.baseline("1.0").maxMemoryRegression = DataAmount.bytes(100)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000)
 
-        result.baselineVersions << baseline("1.3")
-        result.baselineVersions[1].maxMemoryRegression = DataAmount.bytes(100)
-        result.baselineVersions[1].results << operation(heapUsed: 900)
-        result.baselineVersions[1].results << operation(heapUsed: 1000)
-        result.baselineVersions[1].results << operation(heapUsed: 1100)
+        result.baseline("1.3").maxMemoryRegression = DataAmount.bytes(100)
+        result.baseline("1.3").results << operation(totalMemoryUsed: 900)
+        result.baseline("1.3").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.3").results << operation(totalMemoryUsed: 1100)
 
         and:
-        result.current << operation(heapUsed: 1100)
-        result.current << operation(heapUsed: 1100)
-        result.current << operation(heapUsed: 1100)
+        result.current << operation(totalMemoryUsed: 1100)
+        result.current << operation(totalMemoryUsed: 1100)
+        result.current << operation(totalMemoryUsed: 1100)
 
         expect:
         result.assertCurrentVersionHasNotRegressed()
@@ -134,68 +127,61 @@ class PerformanceResultsTest extends Specification {
 
     def "fails when average heap usage for current release is larger than average heap usage for previous releases"() {
         given:
-        result.displayName = '<test>'
-
-        result.baselineVersions[0].version = "1.1" //pass
-        result.baselineVersions[0].maxMemoryRegression = DataAmount.bytes(100)
-        result.baselineVersions[0].results << operation(heapUsed: 1001)
-        result.baselineVersions[0].results << operation(heapUsed: 1001)
-        result.baselineVersions[0].results << operation(heapUsed: 1001)
+        result.baseline("1.0").maxMemoryRegression = DataAmount.bytes(100)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1001)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1001)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1001)
 
-        result.baselineVersions << baseline("1.2") //fail
-        result.baselineVersions[1].maxMemoryRegression = DataAmount.bytes(100)
-        result.baselineVersions[1].results << operation(heapUsed: 1000)
-        result.baselineVersions[1].results << operation(heapUsed: 1000)
-        result.baselineVersions[1].results << operation(heapUsed: 1000)
+        result.baseline("1.2").maxMemoryRegression = DataAmount.bytes(100)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000)
 
         and:
-        result.current << operation(heapUsed: 1100)
-        result.current << operation(heapUsed: 1100)
-        result.current << operation(heapUsed: 1101)
+        result.current << operation(totalMemoryUsed: 1100)
+        result.current << operation(totalMemoryUsed: 1100)
+        result.current << operation(totalMemoryUsed: 1101)
 
         when:
         result.assertCurrentVersionHasNotRegressed()
 
         then:
         AssertionError e = thrown()
-        e.message.startsWith('Memory <test>: we need more memory than 1.2.')
+        e.message.startsWith("Memory ${result.displayName}: we need more memory than 1.2.")
         e.message.contains('Difference: 100.333 B more (100.333 B), 10.03%')
-        !e.message.contains('1.1')
+        !e.message.contains('than 1.0')
     }
 
     def "fails when both heap usage and execution time have regressed"() {
         given:
-        result.displayName = '<test>'
-        result.baselineVersions[0].version = '1.1' //pass
-        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 150)
-        result.baselineVersions[0].results << operation(heapUsed: 1000, executionTime: 100)
-        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 150)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1200, executionTime: 150)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1000, executionTime: 100)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1200, executionTime: 150)
 
-        result.baselineVersions << baseline("1.2") //fail
-        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 100)
-        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 100)
-        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 100)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000, executionTime: 100)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000, executionTime: 100)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000, executionTime: 100)
 
         and:
-        result.current << operation(heapUsed: 1100, executionTime: 110)
-        result.current << operation(heapUsed: 1100, executionTime: 110)
-        result.current << operation(heapUsed: 1101, executionTime: 111)
+        result.current << operation(totalMemoryUsed: 1100, executionTime: 110)
+        result.current << operation(totalMemoryUsed: 1100, executionTime: 110)
+        result.current << operation(totalMemoryUsed: 1101, executionTime: 111)
 
         when:
         result.assertCurrentVersionHasNotRegressed()
 
         then:
         AssertionError e = thrown()
-        e.message.contains("Speed <test>: we're slower than 1.2.")
+        e.message.contains("Speed ${result.displayName}: we're slower than 1.2.")
         e.message.contains('Difference: 10.333 ms slower (10.333 ms)')
-        e.message.contains('Memory <test>: we need more memory than 1.2.')
+        e.message.contains("Memory ${result.displayName}: we need more memory than 1.2.")
         e.message.contains('Difference: 100.333 B more (100.333 B)')
-        !e.message.contains('1.1')
+        !e.message.contains('than 1.0')
     }
 
     def "fails when a previous operation fails"() {
         given:
-        result.baselineVersions[0].results << operation(failure: new RuntimeException())
+        result.baseline("1.0").results << operation(failure: new RuntimeException())
         result.current.add(operation())
 
         when:
@@ -208,7 +194,7 @@ class PerformanceResultsTest extends Specification {
 
     def "fails when a current operation fails"() {
         given:
-        result.baselineVersions[0].results << operation()
+        result.baseline("1.0").results << operation()
         result.current.add(operation(failure: new RuntimeException()))
 
         when:
@@ -222,9 +208,8 @@ class PerformanceResultsTest extends Specification {
     def "fails when an operation fails"() {
         given:
         result.current.add(operation())
-        result.baselineVersions << baseline('oldVersion')
-        result.baselineVersions[0].results << operation()
-        result.baselineVersions[1].results << operation(failure: new RuntimeException())
+        result.baseline("1.0").results << operation()
+        result.baseline("oldVersion").results << operation(failure: new RuntimeException())
 
         when:
         result.assertCurrentVersionHasNotRegressed()
@@ -236,62 +221,67 @@ class PerformanceResultsTest extends Specification {
 
     def "fails if one of the baseline version is faster and the other needs less memory"() {
         given:
-        result.displayName = '<test>'
-        result.baselineVersions[0].version = '1.1' //fast
-        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 100)
-        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 100)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1200, executionTime: 100)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1200, executionTime: 100)
 
-        result.baselineVersions << baseline("1.2") //needs less memory
-        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 150)
-        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 150)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000, executionTime: 150)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1000, executionTime: 150)
 
         and:
-        result.current << operation(heapUsed: 1100, executionTime: 125)
-        result.current << operation(heapUsed: 1100, executionTime: 125)
+        result.current << operation(totalMemoryUsed: 1100, executionTime: 125)
+        result.current << operation(totalMemoryUsed: 1100, executionTime: 125)
 
         when:
         result.assertCurrentVersionHasNotRegressed()
 
         then:
         AssertionError e = thrown()
-        e.message.contains("Speed <test>: we're slower than 1.1.")
+        e.message.contains("Speed ${result.displayName}: we're slower than 1.0.")
         e.message.contains('Difference: 25 ms slower')
-        e.message.contains('Memory <test>: we need more memory than 1.2.')
+        e.message.contains("Memory ${result.displayName}: we need more memory than 1.2.")
         e.message.contains('Difference: 100 B more')
-        e.message.count('<test>') == 2
+        e.message.count(result.displayName) == 2
     }
 
     def "fails if all of the baseline versions are better in every respect"() {
         given:
-        result.displayName = '<test>'
-        result.baselineVersions[0].version = '1.1' //fast
-        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 120)
-        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 120)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1200, executionTime: 120)
+        result.baseline("1.0").results << operation(totalMemoryUsed: 1200, executionTime: 120)
 
-        result.baselineVersions << baseline("1.2") //needs less memory
-        result.baselineVersions[1].results << operation(heapUsed: 1100, executionTime: 150)
-        result.baselineVersions[1].results << operation(heapUsed: 1100, executionTime: 150)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1100, executionTime: 150)
+        result.baseline("1.2").results << operation(totalMemoryUsed: 1100, executionTime: 150)
 
         and:
-        result.current << operation(heapUsed: 1300, executionTime: 200)
-        result.current << operation(heapUsed: 1300, executionTime: 200)
+        result.current << operation(totalMemoryUsed: 1300, executionTime: 200)
+        result.current << operation(totalMemoryUsed: 1300, executionTime: 200)
 
         when:
         result.assertCurrentVersionHasNotRegressed()
 
         then:
         AssertionError e = thrown()
-        e.message.contains("Speed <test>: we're slower than 1.1.")
-        e.message.contains("Speed <test>: we're slower than 1.2.")
-        e.message.contains('Memory <test>: we need more memory than 1.1.')
-        e.message.contains('Memory <test>: we need more memory than 1.2.')
+        e.message.contains("Speed ${result.displayName}: we're slower than 1.0.")
+        e.message.contains("Speed ${result.displayName}: we're slower than 1.2.")
+        e.message.contains("Memory ${result.displayName}: we need more memory than 1.0.")
+        e.message.contains("Memory ${result.displayName}: we need more memory than 1.2.")
     }
 
-    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
+    def "can lookup the results for a baseline version"() {
+        expect:
+        def baseline = result.baseline("1.0")
+        baseline.version == "1.0"
+
+        and:
+        result.baseline("1.0") == baseline
+        result.version("1.0") == baseline
+    }
+
+    def "can lookup the current results using the branch as the version name"() {
+        given:
+        result.vcsBranch = 'master'
+
+        expect:
+        def version = result.version('master')
+        version.results.is(result.current)
     }
 }
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceTestRunnerTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceTestRunnerTest.groovy
new file mode 100644
index 0000000..49791fa
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceTestRunnerTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
+import org.gradle.performance.ResultSpecification
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
+import org.gradle.util.GradleVersion
+
+class PerformanceTestRunnerTest extends ResultSpecification {
+    final timer = Mock(OperationTimer)
+    final reporter = Mock(DataReporter)
+    final testProjectLocator = Stub(TestProjectLocator)
+    final dataCollector = Stub(DataCollector)
+    final currentGradle = Stub(GradleDistribution)
+    final mostRecentRelease = new ReleasedVersionDistributions().mostRecentFinalRelease.version.version
+    final currentVersionBase = GradleVersion.current().baseVersion.version
+
+    def "runs test and builds results"() {
+        given:
+        def runner = runner()
+        runner.testId = 'some-test'
+        runner.testProject = 'test1'
+        runner.targetVersions = ['1.0', '1.1']
+        runner.tasksToRun = ['clean', 'build']
+        runner.args = ['--arg1', '--arg2']
+        runner.warmUpRuns = 1
+        runner.runs = 4
+        runner.maxExecutionTimeRegression = Duration.millis(100)
+        runner.maxMemoryRegression = DataAmount.bytes(10)
+
+        when:
+        def results = runner.run()
+
+        then:
+        results.testId == 'some-test'
+        results.testProject == 'test1'
+        results.tasks == ['clean', 'build']
+        results.args == ['--arg1', '--arg2']
+        results.versionUnderTest
+        results.jvm
+        results.operatingSystem
+        results.current.size() == 4
+        results.current.executionTime.average == Duration.seconds(10)
+        results.current.totalMemoryUsed.average == DataAmount.kbytes(10)
+        results.baselineVersions*.version == ['1.0', '1.1']
+        results.baseline('1.0').results.size() == 4
+        results.baseline('1.1').results.size() == 4
+        results.baselineVersions.every { it.maxExecutionTimeRegression == runner.maxExecutionTimeRegression }
+        results.baselineVersions.every { it.maxMemoryRegression == runner.maxMemoryRegression }
+
+        and:
+        // warmup runs are discarded
+        3 * timer.measure(_) >> operation(executionTime: Duration.seconds(100), totalMemoryUsed: DataAmount.kbytes(100))
+        12 * timer.measure(_) >> operation(executionTime: Duration.seconds(10), totalMemoryUsed: DataAmount.kbytes(10))
+        1 * reporter.report(_)
+        0 * timer._
+        0 * reporter._
+    }
+
+    def "can use 'last' baseline version to refer to most recently released version"() {
+        given:
+        def runner = runner()
+        runner.targetVersions = ['1.0', 'last']
+
+        when:
+        def results = runner.run()
+
+        then:
+        results.baselineVersions*.version == ['1.0', mostRecentRelease]
+    }
+
+    def "ignores baseline version if it has the same base as the version under test"() {
+        given:
+        def runner = runner()
+        runner.targetVersions = ['1.0', currentVersionBase, mostRecentRelease, 'last']
+
+        when:
+        def results = runner.run()
+
+        then:
+        results.baselineVersions*.version == ['1.0', mostRecentRelease]
+    }
+
+    def runner() {
+        return new PerformanceTestRunner(testId: 'some-test',
+                timer: timer, testProjectLocator: testProjectLocator, dataCollector: dataCollector, current: currentGradle, reporter: reporter)
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy
index a7b3b23..64783c1 100644
--- a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy
@@ -16,13 +16,12 @@
 
 package org.gradle.performance.fixture
 
+import org.gradle.performance.measure.Amount
+import org.gradle.performance.measure.Duration
 import spock.lang.Specification
 
 import static org.gradle.performance.fixture.PrettyCalculator.percentChange
 
-/**
- * by Szczepan Faber, created at: 10/30/12
- */
 class PrettyCalculatorSpec extends Specification {
 
     def "knows percentage change"() {
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/UnitsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/UnitsTest.groovy
deleted file mode 100644
index ccd0b47..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/UnitsTest.groovy
+++ /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.performance.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/test/groovy/org/gradle/performance/measure/AmountTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/AmountTest.groovy
new file mode 100644
index 0000000..7f20c06
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/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.performance.measure
+
+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/performance/measure/DataSeriesTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/DataSeriesTest.groovy
new file mode 100644
index 0000000..2547730
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/DataSeriesTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.measure
+
+import spock.lang.Specification
+
+class DataSeriesTest extends Specification {
+    def "can calculate average and min and max"() {
+        def v1 = DataAmount.kbytes(10)
+        def v2 = DataAmount.kbytes(20)
+        def v3 = DataAmount.kbytes(30)
+        def series = new DataSeries([v1, v2, v3])
+
+        expect:
+        series.average == v2
+        series.min == v1
+        series.max == v3
+    }
+
+    def "ignores null values"() {
+        def v1 = DataAmount.kbytes(10)
+        def v2 = DataAmount.kbytes(20)
+        def v3 = DataAmount.kbytes(30)
+        def series = new DataSeries([v1, v2, null, v3, null])
+
+        expect:
+        series.size() == 3
+        series.average == v2
+        series.min == v1
+        series.max == v3
+    }
+
+    def "can be empty"() {
+        def series = new DataSeries([null, null])
+
+        expect:
+        series.empty
+        series.average == null
+        series.min == null
+        series.max == null
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/measure/DurationTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/DurationTest.groovy
new file mode 100644
index 0000000..04b68c2
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/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.performance.measure
+
+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/performance/measure/UnitsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/UnitsTest.groovy
new file mode 100644
index 0000000..3bb901f
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/measure/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.performance.measure
+
+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/test/groovy/org/gradle/performance/results/ReportGeneratorTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/results/ReportGeneratorTest.groovy
new file mode 100644
index 0000000..8b8953c
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/results/ReportGeneratorTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results
+
+import org.gradle.performance.ResultSpecification
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+
+class ReportGeneratorTest extends ResultSpecification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    final ReportGenerator generator = new ReportGenerator()
+    final dbFile = tmpDir.file("results")
+    final reportDir = tmpDir.file("report")
+
+    def "generates report"() {
+        setup:
+        def store = new ResultsStore(dbFile)
+        def result2 = results()
+        result2.current << operation()
+        result2.current << operation()
+        store.report(result2)
+        store.close()
+
+        when:
+        generator.generate(store, reportDir)
+
+        then:
+        reportDir.file("index.html").isFile()
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/results/ResultsStoreTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/results/ResultsStoreTest.groovy
new file mode 100644
index 0000000..da4a98d
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/results/ResultsStoreTest.groovy
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results
+
+import org.gradle.performance.ResultSpecification
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+
+import static org.gradle.performance.measure.DataAmount.kbytes
+import static org.gradle.performance.measure.Duration.minutes
+
+class ResultsStoreTest extends ResultSpecification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    final dbFile = tmpDir.file("results")
+
+    def "persists results"() {
+        def result1 = results(testId: "test1",
+                testProject: "test-project",
+                tasks: ["clean", "build"],
+                args: ["--arg1"],
+                operatingSystem: "some-os",
+                jvm: "java 6",
+                testTime: 10000,
+                versionUnderTest: "1.7-rc-1",
+                vcsBranch: "master",
+                vcsCommit: "1234")
+        def baseline1 = result1.baseline("1.0")
+        def baseline2 = result1.baseline("1.5")
+        result1.current << operation(executionTime: minutes(12),
+                totalMemoryUsed: kbytes(12.33),
+                totalHeapUsage: kbytes(5612.45),
+                maxHeapUsage: kbytes(124.01),
+                maxUncollectedHeap: kbytes(45.22),
+                maxCommittedHeap: kbytes(200))
+        baseline1.results << operation()
+        baseline2.results << operation()
+        baseline2.results << operation()
+        baseline2.results << operation()
+
+        def result2 = results(testId: "test2", testTime: 20000, versionUnderTest: "1.7-rc-2")
+        result2.current << operation()
+        result2.current << operation()
+        def baseline3 = result2.baseline("1.0")
+        baseline3.results << operation()
+
+        when:
+        def writeStore = new ResultsStore(dbFile)
+        writeStore.report(result1)
+        writeStore.report(result2)
+        writeStore.close()
+
+        then:
+        tmpDir.file("results.h2.db").exists()
+
+        when:
+        def readStore = new ResultsStore(dbFile)
+        def tests = readStore.testNames
+
+        then:
+        tests == ["test1", "test2"]
+
+        when:
+        def history = readStore.getTestResults("test1")
+
+        then:
+        history.baselineVersions == ["1.0", "1.5"]
+
+        and:
+        def results = history.results
+        results.size() == 1
+        results[0].testId == "test1"
+        results[0].displayName == "Results for test project 'test-project' with tasks clean, build"
+        results[0].testProject == "test-project"
+        results[0].tasks == ["clean", "build"]
+        results[0].args == ["--arg1"]
+        results[0].operatingSystem == "some-os"
+        results[0].jvm == "java 6"
+        results[0].testTime == 10000
+        results[0].versionUnderTest == '1.7-rc-1'
+        results[0].vcsBranch == 'master'
+        results[0].vcsCommit == '1234'
+        results[0].current.size() == 1
+        results[0].current[0].executionTime == minutes(12)
+        results[0].current[0].totalMemoryUsed == kbytes(12.33)
+        results[0].current[0].totalHeapUsage == kbytes(5612.45)
+        results[0].current[0].maxHeapUsage == kbytes(124.01)
+        results[0].current[0].maxUncollectedHeap == kbytes(45.22)
+        results[0].current[0].maxCommittedHeap == kbytes(200)
+        results[0].baselineVersions*.version == ["1.0", "1.5"]
+        results[0].baseline("1.0").results.size() == 1
+        results[0].baseline("1.5").results.size() == 3
+
+        when:
+        history = readStore.getTestResults("test2")
+        results = history.results
+        readStore.close()
+
+        then:
+        history.baselineVersions == ["1.0"]
+
+        and:
+        results.size() == 1
+        results[0].testId == "test2"
+        results[0].testTime == 20000
+        results[0].versionUnderTest == '1.7-rc-2'
+        results[0].current.size() == 2
+        results[0].baselineVersions*.version == ["1.0"]
+        results[0].baseline("1.0").results.size() == 1
+
+        cleanup:
+        writeStore?.close()
+        readStore?.close()
+    }
+
+    def "returns test names in ascending order"() {
+        given:
+        def writeStore = new ResultsStore(dbFile)
+        writeStore.report(results(testId: "test3"))
+        writeStore.report(results(testId: "test1"))
+        writeStore.report(results(testId: "test2"))
+        writeStore.close()
+
+        when:
+        def readStore = new ResultsStore(dbFile)
+        def tests = readStore.testNames
+
+        then:
+        tests == ["test1", "test2", "test3"]
+
+        cleanup:
+        writeStore?.close()
+        readStore?.close()
+    }
+
+    def "returns test executions in descending date order"() {
+        given:
+        def writeStore = new ResultsStore(dbFile)
+        writeStore.report(results(testId: "some test", testTime: 30000, versionUnderTest: "1.7-rc-3"))
+        writeStore.report(results(testId: "some test", testTime: 10000, versionUnderTest: "1.7-rc-1"))
+        writeStore.report(results(testId: "some test", testTime: 20000, versionUnderTest: "1.7-rc-2"))
+        writeStore.close()
+
+        when:
+        def readStore = new ResultsStore(dbFile)
+        def results = readStore.getTestResults("some test")
+
+        then:
+        results.results.size() == 3
+        results.results*.versionUnderTest == ["1.7-rc-3", "1.7-rc-2", "1.7-rc-1"]
+        results.resultsOldestFirst*.versionUnderTest == ["1.7-rc-1", "1.7-rc-2", "1.7-rc-3"]
+
+        cleanup:
+        writeStore?.close()
+        readStore?.close()
+    }
+
+    def "the known versions for a test is the union of all baseline versions in ascending order and the union of test branches"() {
+        given:
+        def writeStore = new ResultsStore(dbFile)
+
+        def results1 = results(vcsBranch: "master")
+        results1.baseline("1.8-rc-2").results << operation()
+        results1.baseline("1.0").results << operation()
+        def results2 = results(vcsBranch: "release")
+        results2.baseline("1.8-rc-1").results << operation()
+        results2.baseline("1.0").results << operation()
+        results2.baseline("1.10").results << operation()
+        def results3 = results(vcsBranch: "master")
+        results3.baseline("1.8").results << operation()
+        results3.baseline("1.10").results << operation()
+
+        writeStore.report(results1)
+        writeStore.report(results2)
+        writeStore.report(results3)
+        writeStore.close()
+
+        when:
+        def readStore = new ResultsStore(dbFile)
+        def results = readStore.getTestResults("test-id")
+
+        then:
+        results.baselineVersions == ["1.0", "1.8-rc-1", "1.8-rc-2", "1.8", "1.10"]
+        results.branches == ["master", "release"]
+        results.knownVersions == ["1.0", "1.8-rc-1", "1.8-rc-2", "1.8", "1.10", "master", "release"]
+
+        cleanup:
+        writeStore?.close()
+        readStore?.close()
+    }
+
+    def "the set of known versions is the union of all baseline versions and branches"() {
+        given:
+        def writeStore = new ResultsStore(dbFile)
+
+        def results1 = results(testId: "test-1", vcsBranch: "master")
+        results1.baseline("1.8-rc-2").results << operation()
+        results1.baseline("1.0").results << operation()
+        def results2 = results(testId: "test-2", vcsBranch: "release")
+        results2.baseline("1.8-rc-1").results << operation()
+        results2.baseline("1.0").results << operation()
+        results2.baseline("1.10").results << operation()
+        def results3 = results(testId: "test-3", vcsBranch: "release")
+        results3.baseline("1.8").results << operation()
+        results3.baseline("2.0").results << operation()
+
+        writeStore.report(results1)
+        writeStore.report(results2)
+        writeStore.report(results3)
+        writeStore.close()
+
+        when:
+        def readStore = new ResultsStore(dbFile)
+        def results = readStore.getVersions()
+
+        then:
+        results == ["1.0", "1.8-rc-1", "1.8-rc-2", "1.8", "1.10", "2.0", "master", "release"]
+
+        cleanup:
+        writeStore?.close()
+        readStore?.close()
+    }
+
+    def "returns empty results for unknown id"() {
+        given:
+        def store = new ResultsStore(dbFile)
+
+        expect:
+        store.getTestResults("unknown").baselineVersions.empty
+        store.getTestResults("unknown").results.empty
+
+        cleanup:
+        store?.close()
+    }
+}
diff --git a/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-1.txt b/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-1.txt
new file mode 100644
index 0000000..bf07a6d
--- /dev/null
+++ b/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-1.txt
@@ -0,0 +1,19 @@
+0.122: [GC 0.122: [DefNew: 2176K->256K(2432K), 0.0026630 secs] 2176K->581K(7936K), 0.0026880 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
+0.207: [GC 0.207: [DefNew: 2432K->256K(2432K), 0.0024440 secs] 2757K->910K(7936K), 0.0024650 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
+0.273: [GC 0.273: [DefNew: 2432K->255K(2432K), 0.0022840 secs] 3086K->1147K(7936K), 0.0023110 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
+1.344: [GC 1.344: [DefNew: 2424K->256K(2432K), 0.0023470 secs]1.346: [Tenured: 5860K->4659K(5888K), 0.0269100 secs] 7884K->4659K(8320K), [Perm : 12541K->12541K(21248K)], 0.0294080 secs] [Times: user=0.03 sys=0.01, real=0.03 secs]
+1.428: [GC 1.428: [DefNew: 3136K->384K(3520K), 0.0019360 secs] 7795K->5077K(11288K), 0.0019590 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
+2.219: [GC 2.219: [DefNew: 5119K->512K(5120K), 0.0036670 secs] 11901K->7829K(16364K), 0.0037020 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
+2.885: [GC 2.885: [DefNew: 5031K->273K(5120K), 0.0032980 secs]2.888: [Tenured: 11552K->11084K(11628K), 0.0628400 secs] 16221K->11084K(16748K), [Perm : 19743K->19743K(21248K)], 0.0662580 secs] [Times: user=0.11 sys=0.00, real=0.07 secs]
+4.565: [GC 4.565: [DefNew: 12288K->1045K(13760K), 0.0044820 secs] 30486K->19244K(44092K), 0.0045210 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
+4.717: [GC 4.717: [DefNew: 13333K->942K(13760K), 0.0078990 secs] 31532K->20002K(44092K), 0.0079420 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
+Heap
+ def new generation   total 13760K, used 12425K [0x00000007f2e00000, 0x00000007f3ce0000, 0x00000007f58a0000)
+  eden space 12288K,  94% used [0x00000007f2e00000, 0x00000007f394bf40, 0x00000007f3a00000)
+  from space 1472K,  58% used [0x00000007f3a00000, 0x00000007f3ad6718, 0x00000007f3b70000)
+  to   space 1472K,   0% used [0x00000007f3b70000, 0x00000007f3b70000, 0x00000007f3ce0000)
+ tenured generation   total 30332K, used 20909K [0x00000007f58a0000, 0x00000007f763f000, 0x00000007fae00000)
+   the space 30332K,  68% used [0x00000007f58a0000, 0x00000007f6d0b4f0, 0x00000007f6d0b600, 0x00000007f763f000)
+ compacting perm gen  total 24448K, used 24194K [0x00000007fae00000, 0x00000007fc5e0000, 0x0000000800000000)
+   the space 24448K,  98% used [0x00000007fae00000, 0x00000007fc5a0bc0, 0x00000007fc5a0c00, 0x00000007fc5e0000)
+No shared spaces configured.
diff --git a/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-2.txt b/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-2.txt
new file mode 100644
index 0000000..4aed31d
--- /dev/null
+++ b/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-2.txt
@@ -0,0 +1,16 @@
+0.415: [GC [PSYoungGen: 13568K->2221K(15808K)] 13568K->2511K(51968K), 0.0039800 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
+0.712: [GC [PSYoungGen: 15789K->2208K(15808K)] 16079K->3676K(51968K), 0.0048130 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
+0.956: [GC [PSYoungGen: 15776K->2240K(15808K)] 17244K->5196K(51968K), 0.0048400 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
+1.212: [GC [PSYoungGen: 15808K->2240K(29376K)] 18764K->7273K(65536K), 0.0089280 secs] [Times: user=0.05 sys=0.01, real=0.01 secs]
+1.772: [GC [PSYoungGen: 29376K->2240K(29376K)] 34409K->11683K(65536K), 0.0187800 secs] [Times: user=0.11 sys=0.02, real=0.02 secs]
+2.237: [GC [PSYoungGen: 29376K->4849K(60992K)] 38819K->14293K(97152K), 0.0090450 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
+7.397: [Full GC (System) [PSYoungGen: 9542K->0K(187200K)] [ParOldGen: 30885K->34145K(36160K)] 40427K->34145K(223360K) [PSPermGen: 32579K->32561K(65152K)], 0.2854620 secs] [Times: user=1.27 sys=0.02, real=0.29 secs]
+Heap
+ PSYoungGen      total 187200K, used 5532K [0x00000007ee560000, 0x00000007fad10000, 0x0000000800000000)
+  eden space 170304K, 3% used [0x00000007ee560000,0x00000007eeac7280,0x00000007f8bb0000)
+  from space 16896K, 0% used [0x00000007f9c90000,0x00000007f9c90000,0x00000007fad10000)
+  to   space 17088K, 0% used [0x00000007f8bb0000,0x00000007f8bb0000,0x00000007f9c60000)
+ ParOldGen       total 36160K, used 34145K [0x00000007cb000000, 0x00000007cd350000, 0x00000007ee560000)
+  object space 36160K, 94% used [0x00000007cb000000,0x00000007cd158520,0x00000007cd350000)
+ PSPermGen       total 65152K, used 32576K [0x00000007c5e00000, 0x00000007c9da0000, 0x00000007cb000000)
+  object space 65152K, 50% used [0x00000007c5e00000,0x00000007c7dd0218,0x00000007c9da0000)
diff --git a/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-3.txt b/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-3.txt
new file mode 100644
index 0000000..6b5a76a
--- /dev/null
+++ b/subprojects/performance/src/test/resources/org/gradle/performance/fixture/gc-3.txt
@@ -0,0 +1,16 @@
+0.705: [GC [PSYoungGen: 13568K->2226K(15808K)] 13568K->2623K(51904K), 0.0069900 secs] [Times: user=0.04 sys=0.01, real=0.00 secs] 
+0.975: [GC [PSYoungGen: 15794K->2224K(15808K)] 16191K->3532K(51904K), 0.0099290 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
+1.381: [GC [PSYoungGen: 15792K->2208K(15808K)] 17100K->5093K(51904K), 0.0070110 secs] [Times: user=0.04 sys=0.00, real=0.01 secs] 
+1.625: [GC [PSYoungGen: 15776K->2240K(29376K)] 18661K->6827K(65472K), 0.0053900 secs] [Times: user=0.03 sys=0.01, real=0.00 secs] 
+35.550: [Full GC [PSYoungGen: 9383K->0K(172352K)] [ParOldGen: 30417K->31827K(58048K)] 39800K->31827K(230400K) [PSPermGen: 34674K->34638K(69632K)], 0.1364430 secs] [Times: user=0.65 sys=0.01, real=0.14 secs]
+50.758: [GC [PSYoungGen: 87556K->6147K(237440K)] 119384K->37982K(295488K), 0.0126410 secs] [Times: user=0.04 sys=0.01, real=0.01 secs] 
+50.770: [Full GC (System) [PSYoungGen: 6147K->0K(237440K)] [ParOldGen: 31835K->31328K(58048K)] 37982K->31328K(295488K) [PSPermGen: 39021K->39019K(81536K)], 0.1088040 secs] [Times: user=0.39 sys=0.02, real=0.11 secs] 
+Heap
+ PSYoungGen      total 237440K, used 8742K [0x00000007ee600000, 0x00000007ff6e0000, 0x0000000800000000)
+  eden space 231232K, 3% used [0x00000007ee600000,0x00000007eee898f0,0x00000007fc7d0000)
+  from space 6208K, 0% used [0x00000007fe1c0000,0x00000007fe1c0000,0x00000007fe7d0000)
+  to   space 14592K, 0% used [0x00000007fe8a0000,0x00000007fe8a0000,0x00000007ff6e0000)
+ ParOldGen       total 58048K, used 31328K [0x00000007cb200000, 0x00000007ceab0000, 0x00000007ee600000)
+  object space 58048K, 53% used [0x00000007cb200000,0x00000007cd098078,0x00000007ceab0000)
+ PSPermGen       total 81536K, used 39034K [0x00000007c6000000, 0x00000007cafa0000, 0x00000007cb200000)
+  object space 81536K, 47% used [0x00000007c6000000,0x00000007c861eb48,0x00000007cafa0000)
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy
index 6abbd60..fdb61d1 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy
@@ -16,12 +16,37 @@
 
 package org.gradle.performance.fixture
 
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
+import org.gradle.performance.results.ResultsStore
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
 import spock.lang.Specification
 
 class AbstractPerformanceTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    static def resultStore = new ResultsStore()
+    static def textReporter = new TextFileDataReporter(new File("build/performance-tests/results.txt"))
+
     final def runner = new PerformanceTestRunner(
+            testDirectoryProvider: tmpDir,
+            current: new UnderDevelopmentGradleDistribution(),
             runs: 5,
             warmUpRuns: 1,
-            targetVersions: ['1.0', '1.4', 'last']
+            targetVersions: ['1.0', '1.4', '1.8', 'last'],
+            maxExecutionTimeRegression: Duration.millis(500),
+            maxMemoryRegression: DataAmount.mbytes(25)
     )
+
+    def setup() {
+        runner.reporter = new CompositeDataReporter([textReporter, resultStore])
+    }
+
+    static {
+        // TODO - find a better way to cleanup
+        System.addShutdownHook {
+            resultStore.close()
+        }
+    }
 }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Amount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Amount.java
deleted file mode 100644
index 7984ff8..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Amount.java
+++ /dev/null
@@ -1,162 +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.performance.fixture;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * 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("#.###", new DecimalFormatSymbols(Locale.US)).format(scaled), candidate.format(scaled));
-            }
-        }
-        return String.format("%s %s", new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.US)).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/performance/fixture/BaselineVersion.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/BaselineVersion.groovy
index 93a7bb5..319cfda 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/BaselineVersion.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/BaselineVersion.groovy
@@ -16,37 +16,39 @@
 
 package org.gradle.performance.fixture
 
-import static org.gradle.performance.fixture.PrettyCalculator.*
+import org.gradle.performance.measure.Amount
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
 
-/**
- * by Szczepan Faber, created at: 11/20/12
- */
-class BaselineVersion {
+import static org.gradle.performance.fixture.PrettyCalculator.*
 
-    String version
+class BaselineVersion implements VersionResults {
+    final String version
+    final MeasuredOperationList results = new MeasuredOperationList()
     Amount<Duration> maxExecutionTimeRegression = Duration.millis(0)
     Amount<DataAmount> maxMemoryRegression = DataAmount.bytes(0)
 
-    MeasuredOperationList results
+    BaselineVersion(String version) {
+        this.version = version
+        results.name = "Gradle $version"
+    }
 
     void clearResults() {
         results.clear()
     }
 
-    static BaselineVersion baseline(String version) {
-        new BaselineVersion(version: version, results: new MeasuredOperationList(name: "Gradle $version"))
-    }
-
     String getSpeedStatsAgainst(String displayName, MeasuredOperationList current) {
         def sb = new StringBuilder()
-        if (current.avgTime() > results.avgTime()) {
+        def thisVersionAverage = results.executionTime.average
+        def currentVersionAverage = current.executionTime.average
+        if (currentVersionAverage > thisVersionAverage) {
             sb.append "Speed $displayName: we're slower than $version.\n"
         } else {
             sb.append "Speed $displayName: AWESOME! we're faster than $version :D\n"
         }
-        def diff = current.avgTime() - results.avgTime()
+        def diff = currentVersionAverage - thisVersionAverage
         def desc = diff > Duration.millis(0) ? "slower" : "faster"
-        sb.append("Difference: ${prettyTime(diff.abs())} $desc (${toMillis(diff.abs())}), ${PrettyCalculator.percentChange(current.avgTime(), results.avgTime())}%, max regression: ${prettyTime(maxExecutionTimeRegression)}\n")
+        sb.append("Difference: ${diff.abs().format()} $desc (${toMillis(diff.abs())}), ${PrettyCalculator.percentChange(currentVersionAverage, thisVersionAverage)}%, max regression: ${maxExecutionTimeRegression.format()}\n")
         sb.append(current.speedStats)
         sb.append(results.speedStats)
         sb.append("\n")
@@ -55,14 +57,16 @@ class BaselineVersion {
 
     String getMemoryStatsAgainst(String displayName, MeasuredOperationList current) {
         def sb = new StringBuilder()
-        if (current.avgMemory() > results.avgMemory()) {
+        def currentVersionAverage = current.totalMemoryUsed.average
+        def thisVersionAverage = results.totalMemoryUsed.average
+        if (currentVersionAverage > thisVersionAverage) {
             sb.append("Memory $displayName: we need more memory than $version.\n")
         } else {
             sb.append("Memory $displayName: AWESOME! we need less memory than $version :D\n")
         }
-        def diff = current.avgMemory() - results.avgMemory()
+        def diff = currentVersionAverage - thisVersionAverage
         def desc = diff > DataAmount.bytes(0) ? "more" : "less"
-        sb.append("Difference: ${prettyBytes(diff.abs())} $desc (${toBytes(diff.abs())}), ${PrettyCalculator.percentChange(current.avgMemory(), results.avgMemory())}%, max regression: ${prettyBytes(maxMemoryRegression)}\n")
+        sb.append("Difference: ${diff.abs().format()} $desc (${toBytes(diff.abs())}), ${PrettyCalculator.percentChange(currentVersionAverage, thisVersionAverage)}%, max regression: ${maxMemoryRegression.format()}\n")
         sb.append(current.memoryStats)
         sb.append(results.memoryStats)
         sb.append("\n")
@@ -70,10 +74,10 @@ class BaselineVersion {
     }
 
     boolean usesLessMemoryThan(MeasuredOperationList current) {
-        current.avgMemory() - results.avgMemory() > maxMemoryRegression
+        current.totalMemoryUsed.average - results.totalMemoryUsed.average > maxMemoryRegression
     }
 
     boolean fasterThan(MeasuredOperationList current) {
-        current.avgTime() - results.avgTime() > maxExecutionTimeRegression
+        current.executionTime.average - results.executionTime.average > maxExecutionTimeRegression
     }
 }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/CompositeDataCollector.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/CompositeDataCollector.java
new file mode 100644
index 0000000..8774404
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/CompositeDataCollector.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture;
+
+import org.gradle.integtests.fixtures.executer.GradleExecuter;
+import org.gradle.performance.measure.MeasuredOperation;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+public class CompositeDataCollector implements DataCollector {
+    private final List<DataCollector> collectors;
+
+    public CompositeDataCollector(DataCollector... collectors) {
+        this.collectors = Arrays.asList(collectors);
+    }
+
+    public void beforeExecute(File testProjectDir, GradleExecuter executer) {
+        for (DataCollector collector : collectors) {
+            collector.beforeExecute(testProjectDir, executer);
+        }
+    }
+
+    public void collect(File testProjectDir, MeasuredOperation operation) {
+        for (DataCollector collector : collectors) {
+            collector.collect(testProjectDir, operation);
+        }
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/CompositeDataReporter.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/CompositeDataReporter.java
new file mode 100644
index 0000000..053ef1a
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/CompositeDataReporter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class CompositeDataReporter implements DataReporter {
+    private final List<DataReporter> reporters;
+    private final Set<String> testIds = new HashSet<String>();
+
+    public CompositeDataReporter(List<DataReporter> reporters) {
+        this.reporters = reporters;
+    }
+
+    public void report(PerformanceResults results) {
+        if (!testIds.add(results.getTestId())) {
+            throw new IllegalArgumentException(String.format("Multiple performance test executions with id '%s' found.", results.getTestId()));
+        }
+        for (DataReporter reporter : reporters) {
+            reporter.report(results);
+        }
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataAmount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataAmount.java
deleted file mode 100644
index 7d0707d..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataAmount.java
+++ /dev/null
@@ -1,37 +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.performance.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/performance/fixture/DataCollector.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataCollector.java
index 2b387d8..d01527d 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataCollector.java
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataCollector.java
@@ -16,12 +16,13 @@
 
 package org.gradle.performance.fixture;
 
+import org.gradle.integtests.fixtures.executer.GradleExecuter;
+import org.gradle.performance.measure.MeasuredOperation;
+
 import java.io.File;
 
-/**
- * by Szczepan Faber, created at: 8/14/12
- */
 public interface DataCollector {
+    void beforeExecute(File testProjectDir, GradleExecuter executer);
 
     void collect(File testProjectDir, MeasuredOperation operation);
 }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Duration.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Duration.java
deleted file mode 100644
index 90803c8..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Duration.java
+++ /dev/null
@@ -1,45 +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.performance.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/performance/fixture/GCLoggingCollector.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/GCLoggingCollector.java
new file mode 100644
index 0000000..86e9106
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/GCLoggingCollector.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture;
+
+import org.gradle.integtests.fixtures.executer.GradleExecuter;
+import org.gradle.performance.measure.DataAmount;
+import org.gradle.performance.measure.MeasuredOperation;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GCLoggingCollector implements DataCollector {
+    private File logFile;
+
+    public void beforeExecute(File testProjectDir, GradleExecuter executer) {
+        logFile = new File(testProjectDir, "gc.txt");
+        executer.withGradleOpts("-verbosegc", "-XX:+PrintGCDetails", "-Xloggc:" + logFile.getAbsolutePath());
+    }
+
+    public void collect(File testProjectDir, MeasuredOperation operation) {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(logFile));
+            try {
+                collect(reader, operation);
+            } finally {
+                reader.close();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not process garbage collector log %s.", logFile), e);
+        }
+    }
+
+    private void collect(BufferedReader reader, MeasuredOperation operation) throws IOException {
+        Pattern collectionEventPattern = Pattern.compile("\\d+\\.\\d+: \\[(?:(?:Full GC(?: [^\\s]+)?)|GC) (\\d+\\.\\d+: )?\\[.*\\] (\\d+)K->(\\d+)K\\((\\d+)K\\)");
+        Pattern memoryPoolPattern = Pattern.compile("([\\w\\s]+) total (\\d+)K, used (\\d+)K \\[.+");
+
+        long totalHeapUsage = 0;
+        long maxUsage = 0;
+        long maxUncollectedUsage = 0;
+        long maxCommittedUsage = 0;
+
+        // Process the garbage collection events
+
+        long usageAtPreviousCollection = 0;
+        int events = 0;
+
+        while (true) {
+            String line = reader.readLine();
+            if (line == null || line.equals("Heap")) {
+                break;
+            }
+
+            Matcher matcher = collectionEventPattern.matcher(line);
+            if (!matcher.lookingAt()) {
+                throw new IllegalArgumentException("Unrecognized garbage collection event found in garbage collection log: " + line);
+            }
+            events++;
+
+            long start = Long.parseLong(matcher.group(2));
+            long end = Long.parseLong(matcher.group(3));
+            long committed = Long.parseLong(matcher.group(4));
+
+            if (start < usageAtPreviousCollection) {
+                throw new IllegalArgumentException("Unexpected max heap size found in garbage collection event: " + line);
+            }
+
+            totalHeapUsage += start - usageAtPreviousCollection;
+            maxUsage = Math.max(maxUsage, start);
+            maxUncollectedUsage = Math.max(maxUncollectedUsage, end);
+            maxCommittedUsage = Math.max(maxCommittedUsage, committed);
+            usageAtPreviousCollection = end;
+        }
+
+        if (events == 0) {
+            throw new IllegalArgumentException("Did not find any garbage collection events in garbage collection log.");
+        }
+
+        // Process the heap usage summary at the end of the log
+
+        long finalHeapUsage = 0;
+        long finalCommittedHeap = 0;
+
+        while (true) {
+            String line = reader.readLine();
+            if (line == null) {
+                break;
+            }
+            Matcher matcher = memoryPoolPattern.matcher(line);
+            if (!matcher.lookingAt()) {
+                continue;
+            }
+
+            String pool = matcher.group(1).trim();
+            if (pool.toLowerCase().contains("perm gen") || pool.toLowerCase().contains("permgen")) {
+                continue;
+            }
+
+            long committed = Long.parseLong(matcher.group(2));
+            long usage = Long.parseLong(matcher.group(3));
+
+            finalHeapUsage += usage;
+            finalCommittedHeap += committed;
+        }
+
+        if (finalHeapUsage == 0) {
+            throw new IllegalArgumentException("Did not find any memory pool usage details in garbage collection log.");
+        }
+
+        if (finalHeapUsage < usageAtPreviousCollection) {
+            throw new IllegalArgumentException("Unexpected max heap size found in memory pool usage.");
+        }
+
+        totalHeapUsage += finalHeapUsage - usageAtPreviousCollection;
+        maxUsage = Math.max(maxUsage, finalHeapUsage);
+        maxCommittedUsage = Math.max(maxCommittedUsage, finalCommittedHeap);
+
+        operation.setTotalHeapUsage(DataAmount.kbytes(BigDecimal.valueOf(totalHeapUsage)));
+        operation.setMaxHeapUsage(DataAmount.kbytes(BigDecimal.valueOf(maxUsage)));
+        operation.setMaxUncollectedHeap(DataAmount.kbytes(BigDecimal.valueOf(maxUncollectedUsage)));
+        operation.setMaxCommittedHeap(DataAmount.kbytes(BigDecimal.valueOf(maxCommittedUsage)));
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Git.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Git.groovy
new file mode 100644
index 0000000..7fd66c5
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Git.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder
+
+class Git {
+    private static Git git
+    final String commitId
+    final String branchName
+
+    public static Git current() {
+        if (git == null) {
+            git = new Git()
+        }
+        return git
+    }
+
+    private Git() {
+        def repository = new FileRepositoryBuilder().findGitDir().build()
+        try {
+            branchName = repository.branch
+            commitId = repository.getRef(repository.fullBranch).objectId.name
+        } finally {
+            repository.close()
+        }
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperation.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperation.groovy
deleted file mode 100644
index f9da914..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperation.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.performance.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/performance/fixture/MeasuredOperationList.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperationList.groovy
index f49079b..e764f4b 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperationList.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperationList.groovy
@@ -16,50 +16,47 @@
 
 package org.gradle.performance.fixture
 
-import static org.gradle.performance.fixture.PrettyCalculator.prettyBytes
-import static org.gradle.performance.fixture.PrettyCalculator.prettyTime
+import org.gradle.performance.measure.*
 
-/**
- * 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()
+    DataSeries<DataAmount> getTotalMemoryUsed() {
+        return new DataSeries<DataAmount>(this.collect { it.totalMemoryUsed })
     }
 
-    Amount<DataAmount> minMemory() {
-        return collect { it.totalMemoryUsed }.min()
+    DataSeries<DataAmount> getTotalHeapUsage() {
+        return new DataSeries<DataAmount>(this.collect { it.totalHeapUsage })
     }
 
-    Amount<DataAmount> maxMemory() {
-        return collect { it.totalMemoryUsed }.max()
+    DataSeries<DataAmount> getMaxHeapUsage() {
+        return new DataSeries<DataAmount>(this.collect { it.maxHeapUsage })
     }
 
-    Amount<Duration> avgTime() {
-        def currentTimes = this.collect { it.executionTime }
-        currentTimes.sum() / currentTimes.size()
+    DataSeries<DataAmount> getMaxUncollectedHeap() {
+        return new DataSeries<DataAmount>(this.collect { it.maxUncollectedHeap })
     }
 
-    Amount<Duration> maxTime() {
-        return collect { it.executionTime }.max()
+    DataSeries<DataAmount> getMaxCommittedHeap() {
+        return new DataSeries<DataAmount>(this.collect { it.maxCommittedHeap })
     }
 
-    Amount<Duration> minTime() {
-        return collect { it.executionTime }.min()
+    DataSeries<Duration> getExecutionTime() {
+        return new DataSeries<Duration>(this.collect { it.executionTime })
     }
 
     String getSpeedStats() {
-        """  ${name} avg: ${prettyTime(avgTime())} ${collect { prettyTime(it.executionTime) }}
-  > min: ${prettyTime(minTime())}, max: ${prettyTime(maxTime())}
-"""
+        format(executionTime)
     }
 
     String getMemoryStats() {
-        """  ${name} avg: ${prettyBytes(avgMemory())} ${collect { prettyBytes(it.totalMemoryUsed) }}
-  > min: ${prettyBytes(minMemory())}, max: ${prettyBytes(maxMemory())}
+        format(totalMemoryUsed)
+    }
+
+    private String format(DataSeries<?> measurement) {
+        """  ${name} avg: ${measurement.average.format()} ${measurement.collect { it.format() }}
+  > min: ${measurement.min.format()}, max: ${measurement.max.format()}
 """
+
     }
 }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy
index 107794b..64eceab 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy
@@ -16,13 +16,16 @@
 
 package org.gradle.performance.fixture
 
-/**
- * by Szczepan Faber, created at: 8/14/12
- */
-public class MemoryInfoCollector implements DataCollector {
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.MeasuredOperation
 
+public class MemoryInfoCollector implements DataCollector {
     String outputFileName
 
+    public void beforeExecute(File testProjectDir, GradleExecuter executer) {
+    }
+
     public void collect(File testProjectDir, MeasuredOperation operation) {
         def file = new File(testProjectDir, outputFileName).canonicalFile
         if (!file.exists()) {
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/OperationTimer.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/OperationTimer.java
new file mode 100644
index 0000000..52e466f
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/OperationTimer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture;
+
+import groovy.lang.Closure;
+import org.gradle.performance.measure.Duration;
+import org.gradle.performance.measure.MeasuredOperation;
+
+public class OperationTimer {
+    public MeasuredOperation measure(Closure operation) {
+        MeasuredOperation result = new MeasuredOperation();
+        long start = System.currentTimeMillis();
+        try {
+            operation.call(result);
+        } catch (Exception e) {
+            result.setException(e);
+        }
+        long end = System.currentTimeMillis();
+        result.setExecutionTime(Duration.millis(end - start));
+        return result;
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy
index 10e36fa..281cc52 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy
@@ -18,36 +18,81 @@ package org.gradle.performance.fixture
 
 import org.gradle.api.logging.Logging
 
-import static org.gradle.performance.fixture.BaselineVersion.baseline
-
 public class PerformanceResults {
+    private final static LOGGER = Logging.getLogger(PerformanceResults.class)
 
-    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
-
-    List<BaselineVersion> baselineVersions = [ baseline("1.x")]
-    String displayName
+    String testId
+    String testProject
+    String[] args
+    String[] tasks
+    String jvm
+    String operatingSystem
+    long testTime
+    String versionUnderTest
+    String vcsBranch
+    String vcsCommit
 
-    final MeasuredOperationList current = new MeasuredOperationList(name:  "Current G.")
+    private final Map<String, BaselineVersion> baselineVersions = new LinkedHashMap<>()
+    final MeasuredOperationList current = new MeasuredOperationList(name: "Current Gradle")
+    private final results = new CurrentVersionResults(current)
 
     def clear() {
-        baselineVersions.each { it.clearResults() }
+        baselineVersions.values().each { it.clearResults() }
         current.clear()
     }
 
-    void assertEveryBuildSucceeds() {
-        LOGGER.info("Asserting all builds have succeeded...");
+    @Override
+    String toString() {
+        return displayName
+    }
+
+    String getDisplayName() {
+        return "Results for test project '$testProject' with tasks ${tasks.join(', ')}"
+    }
+
+    Collection<BaselineVersion> getBaselineVersions() {
+        return baselineVersions.values()
+    }
+
+    /**
+     * Locates the given baseline version, adding it if not present.
+     */
+    BaselineVersion baseline(String version) {
+        def baselineVersion = baselineVersions[version]
+        if (baselineVersion == null) {
+            baselineVersion = new BaselineVersion(version)
+            baselineVersions[version] = baselineVersion
+        }
+        return baselineVersion
+    }
+
+    /**
+     * Locates the given version. Can use either a baseline version or the current branch name.
+     */
+    VersionResults version(String version) {
+        if (version.equals(vcsBranch)) {
+            return results
+        }
+        return baseline(version)
+    }
+
+    List<MeasuredOperationList> getFailures() {
         def failures = []
-        baselineVersions.each {
+        baselineVersions.values().each {
             failures.addAll it.results.findAll { it.exception }
         }
         failures.addAll current.findAll { it.exception }
+        return failures
+    }
 
-        assert failures.collect { it.exception }.empty : "Some builds have failed."
+    void assertEveryBuildSucceeds() {
+        LOGGER.info("Asserting all builds have succeeded...");
+        assert failures.collect { it.exception }.empty: "Some builds have failed."
     }
 
     void assertCurrentVersionHasNotRegressed() {
-        def slower = checkBaselineVersion({it.fasterThan(current)},         {it.getSpeedStatsAgainst(displayName, current)})
-        def larger = checkBaselineVersion({it.usesLessMemoryThan(current)}, {it.getMemoryStatsAgainst(displayName, current)})
+        def slower = checkBaselineVersion({ it.fasterThan(current) }, { it.getSpeedStatsAgainst(displayName, current) })
+        def larger = checkBaselineVersion({ it.usesLessMemoryThan(current) }, { it.getMemoryStatsAgainst(displayName, current) })
         assertEveryBuildSucceeds()
         if (slower && larger) {
             throw new AssertionError("$slower\n$larger")
@@ -63,7 +108,7 @@ public class PerformanceResults {
     private String checkBaselineVersion(Closure fails, Closure provideMessage) {
         def failed = false
         def failure = new StringBuilder()
-        baselineVersions.each {
+        baselineVersions.values().each {
             String message = provideMessage(it)
             if (fails(it)) {
                 failed = true
@@ -73,4 +118,12 @@ public class PerformanceResults {
         }
         return failed ? failure.toString() : null
     }
+
+    private static class CurrentVersionResults implements VersionResults {
+        final MeasuredOperationList results
+
+        CurrentVersionResults(MeasuredOperationList results) {
+            this.results = results
+        }
+    }
 }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
index 4dca19b..7c43e3c 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
@@ -19,22 +19,33 @@ package org.gradle.performance.fixture
 import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.integtests.fixtures.executer.GradleExecuter
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
-import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
 import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.internal.jvm.Jvm
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.performance.measure.Amount
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
+import org.gradle.performance.measure.MeasuredOperation
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.util.GradleVersion
 
 public class PerformanceTestRunner {
-    def testDirectoryProvider = new TestNameTestDirectoryProvider()
-    def current = new UnderDevelopmentGradleDistribution()
-    def buildContext = new IntegrationTestBuildContext()
+    TestDirectoryProvider testDirectoryProvider
+    GradleDistribution current
+    IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
+    DataReporter reporter
+    OperationTimer timer = new OperationTimer()
+    TestProjectLocator testProjectLocator = new TestProjectLocator()
 
+    String testId
     String testProject
     int runs
     int warmUpRuns
 
     List<String> tasksToRun = []
-    DataCollector dataCollector = new MemoryInfoCollector(outputFileName: "build/totalMemoryUsed.txt")
-    DataReporter  reporter = new TextFileDataReporter()
+    DataCollector dataCollector = new CompositeDataCollector(
+            new MemoryInfoCollector(outputFileName: "build/totalMemoryUsed.txt"),
+            new GCLoggingCollector())
     List<String> args = []
 
     List<String> targetVersions = []
@@ -45,22 +56,38 @@ public class PerformanceTestRunner {
 
     PerformanceResults run() {
         assert !targetVersions.empty
+        assert testId
 
-        def mostRecentFinalRelease = new ReleasedVersionDistributions().mostRecentFinalRelease.version.version
+        results = new PerformanceResults(
+                testId: testId,
+                testProject: testProject,
+                tasks: tasksToRun,
+                args: args,
+                jvm: Jvm.current().toString(),
+                operatingSystem: OperatingSystem.current().toString(),
+                versionUnderTest: GradleVersion.current().getVersion(),
+                vcsBranch: Git.current().branchName,
+                vcsCommit: Git.current().commitId,
+                testTime: System.currentTimeMillis())
+
+        def releasedDistributions = new ReleasedVersionDistributions()
+        def releasedVersions = releasedDistributions.all*.version.version
+        def mostRecentFinalRelease = releasedDistributions.mostRecentFinalRelease.version.version
+        def currentBaseVersion = GradleVersion.current().getBaseVersion().version
         def allVersions = targetVersions.collect { (it == 'last') ? mostRecentFinalRelease : it }.unique()
-        def baselineVersions = []
+        allVersions.remove(currentBaseVersion)
+
+        // A target version may be something that is yet unreleased, so filter that out
+        allVersions.removeAll { !releasedVersions.contains(it) }
+
+        assert !allVersions.isEmpty()
+
         allVersions.each { it ->
-            baselineVersions << new BaselineVersion(version: it,
-                    maxExecutionTimeRegression: maxExecutionTimeRegression,
-                    maxMemoryRegression: maxMemoryRegression,
-                    results: new MeasuredOperationList(name: "Gradle $it")
-            )
+            def baselineVersion = results.baseline(it)
+            baselineVersion.maxExecutionTimeRegression = maxExecutionTimeRegression
+            baselineVersion.maxMemoryRegression = maxMemoryRegression
         }
 
-        results = new PerformanceResults(
-                baselineVersions: baselineVersions,
-                displayName: "Results for test project '$testProject' with tasks ${tasksToRun.join(', ')}")
-
         println "Running performance tests for test project '$testProject', no. of runs: $runs"
         warmUpRuns.times {
             println "Executing warm-up run #${it + 1}"
@@ -76,8 +103,8 @@ public class PerformanceTestRunner {
     }
 
     void runOnce() {
-        File projectDir = new TestProjectLocator().findProjectDir(testProject)
-        results.baselineVersions.reverse().each {
+        File projectDir = testProjectLocator.findProjectDir(testProject)
+        results.baselineVersions.each {
             println "Gradle ${it.version}..."
             runOnce(buildContext.distribution(it.version), projectDir, it.results)
         }
@@ -88,10 +115,13 @@ public class PerformanceTestRunner {
 
     void runOnce(GradleDistribution dist, File projectDir, MeasuredOperationList results) {
         def executer = this.executer(dist, projectDir)
-        def operation = MeasuredOperation.measure { MeasuredOperation operation ->
+        dataCollector.beforeExecute(projectDir, executer)
+        def operation = timer.measure { MeasuredOperation operation ->
             executer.run()
         }
-        dataCollector.collect(projectDir, operation)
+        if (operation.exception == null) {
+            dataCollector.collect(projectDir, operation)
+        }
         results.add(operation)
     }
 
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy
index 10d19ff..58681eb 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy
@@ -16,25 +16,18 @@
 
 package org.gradle.performance.fixture
 
+import org.gradle.performance.measure.Amount
+import org.gradle.performance.measure.DataAmount
+import org.gradle.performance.measure.Duration
+
 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"
     }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy
index a109dca..73e932c 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy
@@ -16,9 +16,6 @@
 
 package org.gradle.performance.fixture
 
-/**
- * by Szczepan Faber, created at: 2/10/12
- */
 class TestProjectLocator {
 
     File findProjectDir(String name) {
@@ -27,7 +24,7 @@ class TestProjectLocator {
         def dirs = locations.collect { new File(it).absoluteFile }
         for (File dir: dirs) {
             if (dir.isDirectory()) {
-                return dir
+                return dir.canonicalFile
             }
         }
         def message = "Looks like the test project '$name' was not generated.\nI've tried to find it at:\n"
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy
index ca16730..2b7141d 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy
@@ -17,14 +17,19 @@
 package org.gradle.performance.fixture
 
 class TextFileDataReporter implements DataReporter {
+    private final File outputFile
+
+    TextFileDataReporter(File outputFile) {
+        this.outputFile = outputFile
+    }
+
     void report(PerformanceResults results) {
-        File outFile = new File("build/performance-tests/results.txt")
-        outFile.parentFile.mkdirs()
+        outputFile.parentFile.mkdirs()
         results.baselineVersions.each {
-            outFile << it.getSpeedStatsAgainst(results.displayName, results.current)
+            outputFile << it.getSpeedStatsAgainst(results.displayName, results.current)
         }
         results.baselineVersions.each {
-            outFile << it.getMemoryStatsAgainst(results.displayName, results.current)
+            outputFile << it.getMemoryStatsAgainst(results.displayName, results.current)
         }
     }
 }
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Units.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Units.java
deleted file mode 100644
index 0d787ad..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Units.java
+++ /dev/null
@@ -1,193 +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.performance.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/performance/src/testFixtures/groovy/org/gradle/performance/fixture/VersionResults.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/VersionResults.java
new file mode 100644
index 0000000..097a8c7
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/VersionResults.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture;
+
+public interface VersionResults {
+    MeasuredOperationList getResults();
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Amount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Amount.java
new file mode 100644
index 0000000..8d39ef9
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Amount.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.performance.measure;
+
+import org.gradle.api.Nullable;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 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);
+    }
+
+    /**
+     * Returns null if the given value is null.
+     */
+    @Nullable
+    public static <Q> Amount<Q> valueOf(@Nullable BigDecimal value, Units<Q> units) {
+        if (value == null) {
+            return null;
+        }
+        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("#.###", new DecimalFormatSymbols(Locale.US)).format(scaled), candidate.format(scaled));
+            }
+        }
+        return String.format("%s %s", new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.US)).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;
+        }
+        @SuppressWarnings("unchecked")
+        Amount<Q> other = (Amount) 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/performance/measure/DataAmount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/DataAmount.java
new file mode 100644
index 0000000..1716bbe
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/DataAmount.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.performance.measure;
+
+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);
+    }
+
+    public static Amount<DataAmount> mbytes(BigDecimal value) {
+        return Amount.valueOf(value, MEGA_BYTES);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/DataSeries.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/DataSeries.java
new file mode 100644
index 0000000..3e7cd71
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/DataSeries.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.measure;
+
+import java.util.ArrayList;
+
+/**
+ * A collection of measurements of some given units.
+ */
+public class DataSeries<Q> extends ArrayList<Amount<Q>> {
+    private final Amount<Q> average;
+    private final Amount<Q> max;
+    private final Amount<Q> min;
+
+    public DataSeries(Iterable<? extends Amount<Q>> values) {
+        for (Amount<Q> value : values) {
+            if (value != null) {
+                add(value);
+            }
+        }
+
+        if (isEmpty()) {
+            average = null;
+            max = null;
+            min = null;
+            return;
+        }
+
+        Amount<Q> total = get(0);
+        Amount<Q> min = get(0);
+        Amount<Q> max = get(0);
+        for (int i = 1; i < size(); i++) {
+            Amount<Q> amount = get(i);
+            total = total.plus(amount);
+            min = min.compareTo(amount) <= 0 ? min : amount;
+            max = max.compareTo(amount) >= 0 ? max : amount;
+        }
+        average = total.div(size());
+        this.min = min;
+        this.max = max;
+    }
+
+    public Amount<Q> getAverage() {
+        return average;
+    }
+
+    public Amount<Q> getMin() {
+        return min;
+    }
+
+    public Amount<Q> getMax() {
+        return max;
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Duration.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Duration.java
new file mode 100644
index 0000000..9cd321d
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/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.performance.measure;
+
+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/performance/measure/MeasuredOperation.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/MeasuredOperation.groovy
new file mode 100644
index 0000000..0294b04
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/MeasuredOperation.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.measure
+
+public class MeasuredOperation {
+    Amount<Duration> executionTime
+    Exception exception
+    /** The non-collectable heap usage at the end of the build. This was the original metric used */
+    Amount<DataAmount> totalMemoryUsed
+    /** The total amount of heap used over the life of the operation. Does not include the perm gen. */
+    Amount<DataAmount> totalHeapUsage
+    /** The largest amount of heap remaining at the end of a garbage collection. Does not include the perm gen. */
+    Amount<DataAmount> maxUncollectedHeap
+    /** The largest amount of heap used at the start of a garbage collection. Does not include the perm gen. */
+    Amount<DataAmount> maxHeapUsage
+    /** The largest amount of committed heap (that is heap requested from the OS). Does not include the perm gen. */
+    Amount<DataAmount> maxCommittedHeap
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Units.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/Units.java
new file mode 100644
index 0000000..6e5a614
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/measure/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.performance.measure;
+
+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/performance/src/testFixtures/groovy/org/gradle/performance/results/FileRenderer.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/FileRenderer.java
new file mode 100644
index 0000000..423ac74
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/FileRenderer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import org.gradle.reporting.ReportRenderer;
+import org.gradle.util.GFileUtils;
+
+import java.io.*;
+
+public class FileRenderer {
+    public <T> void render(T model, ReportRenderer<T, Writer> renderer, File outputFile) throws IOException {
+        GFileUtils.parentMkdirs(outputFile);
+        Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"));
+        try {
+            renderer.render(model, writer);
+        } finally {
+            writer.close();
+        }
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/FormatSupport.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/FormatSupport.java
new file mode 100644
index 0000000..3761065
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/FormatSupport.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import org.gradle.performance.measure.Amount;
+import org.gradle.performance.measure.DataAmount;
+import org.gradle.performance.measure.Duration;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class FormatSupport {
+    private final DateFormat timeStampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+
+    public FormatSupport() {
+        timeStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    public String executionTimestamp() {
+        return timeStampFormat.format(new Date());
+    }
+
+    public String timestamp(Date date) {
+        return timeStampFormat.format(date);
+    }
+
+    public String date(Date date) {
+        return dateFormat.format(date);
+    }
+
+    public String seconds(Amount<Duration> duration) {
+        return duration.toUnits(Duration.SECONDS).getValue().toString();
+    }
+
+    public String megabytes(Amount<DataAmount> amount) {
+        return amount.toUnits(DataAmount.MEGA_BYTES).getValue().toString();
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/HtmlPageGenerator.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/HtmlPageGenerator.java
new file mode 100644
index 0000000..2b19386
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/HtmlPageGenerator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import com.googlecode.jatl.Html;
+import org.gradle.reporting.ReportRenderer;
+import org.gradle.util.GradleVersion;
+
+import java.io.Writer;
+
+public abstract class HtmlPageGenerator<T> extends ReportRenderer<T, Writer> {
+    protected final FormatSupport format = new FormatSupport();
+
+    protected void headSection(Html html) {
+        html.meta()
+                .httpEquiv("Content-Type")
+                .content("text/html; charset=utf-8");
+        html.link()
+                .rel("stylesheet")
+                .type("text/css")
+                .href("css/style.css")
+                .end();
+        html.script()
+                .src("js/jquery.min-1.11.0.js")
+                .end();
+        html.script()
+                .src("js/flot-0.8.1-min.js")
+                .end();
+        html.script()
+                .src("js/report.js")
+                .end();
+    }
+
+    protected void footer(Html html) {
+        html.div()
+                .id("footer")
+                .text(String.format("Generated at %s by %s", format.executionTimestamp(), GradleVersion.current()))
+                .end();
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/IndexPageGenerator.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/IndexPageGenerator.java
new file mode 100644
index 0000000..cfb31c9
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/IndexPageGenerator.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import com.googlecode.jatl.Html;
+import org.gradle.performance.fixture.PerformanceResults;
+import org.gradle.performance.fixture.VersionResults;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Date;
+import java.util.List;
+
+public class IndexPageGenerator extends HtmlPageGenerator<ResultsStore> {
+    @Override
+    public void render(final ResultsStore store, Writer writer) throws IOException {
+        new Html(writer) {{
+            List<String> versions = store.getVersions();
+            html();
+                head();
+                    headSection(this);
+                    title().text("Profile report").end();
+                end();
+                body();
+                div().id("content");
+                    h2().text("All tests").end();
+                    List<String> testNames = store.getTestNames();
+                    div().id("controls").end();
+                    table().classAttr("history");
+                    for (String testName : testNames) {
+                        TestExecutionHistory testHistory = store.getTestResults(testName);
+                        tr();
+                            th().colspan("6").classAttr("test-execution");
+                                text(testName);
+                            end();
+                        end();
+                        tr().classAttr("control-groups");
+                            th().colspan("3").end();
+                            th().colspan(String.valueOf(versions.size())).text("Average execution time").end();
+                            th().colspan(String.valueOf(versions.size())).text("Average heap usage").end();
+                        end();
+                        tr();
+                            th().text("Date").end();
+                            th().text("Test version").end();
+                            th().text("Branch").end();
+                            for (String version : versions) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            for (String version : versions) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                        end();
+                        for (int i = 0; i < testHistory.getResults().size() && i < 5; i++) {
+                            PerformanceResults performanceResults = testHistory.getResults().get(i);
+                            tr();
+                                td().text(format.timestamp(new Date(performanceResults.getTestTime()))).end();
+                                td().text(performanceResults.getVersionUnderTest()).end();
+                                td().text(performanceResults.getVcsBranch()).end();
+                                for (String version : versions) {
+                                    VersionResults versionResults = performanceResults.version(version);
+                                    td().classAttr("numeric");
+                                    if (versionResults.getResults().isEmpty()) {
+                                        text("");
+                                    } else {
+                                        text(versionResults.getResults().getExecutionTime().getAverage().format());
+                                    }
+                                    end();
+                                }
+                                for (String version : versions) {
+                                    VersionResults versionResults = performanceResults.version(version);
+                                    td().classAttr("numeric");
+                                    if (versionResults.getResults().isEmpty()) {
+                                        text("");
+                                    } else {
+                                        text(versionResults.getResults().getTotalMemoryUsed().getAverage().format());
+                                    }
+                                    end();
+                                }
+                            end();
+                        }
+                        tr();
+                            td().colspan("6");
+                                String url = testHistory.getId() + ".html";
+                                a().href(url).text("details...").end();
+                            end();
+                        end();
+                    }
+                    end();
+                end();
+                footer(this);
+            endAll();
+        }};
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ReportGenerator.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ReportGenerator.java
new file mode 100644
index 0000000..380bb90
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ReportGenerator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.net.URL;
+
+public class ReportGenerator {
+    void generate(final ResultsStore store, File outputDirectory) {
+        try {
+            FileRenderer fileRenderer = new FileRenderer();
+            TestPageGenerator testHtmlRenderer = new TestPageGenerator();
+            TestDataGenerator testDataRenderer = new TestDataGenerator();
+
+            fileRenderer.render(store, new IndexPageGenerator(), new File(outputDirectory, "index.html"));
+
+            for (String testName : store.getTestNames()) {
+                TestExecutionHistory testResults = store.getTestResults(testName);
+                fileRenderer.render(testResults, testHtmlRenderer, new File(outputDirectory, testResults.getId() + ".html"));
+                fileRenderer.render(testResults, testDataRenderer, new File(outputDirectory, testResults.getId() + ".json"));
+            }
+
+            copyResource("jquery.min-1.11.0.js", outputDirectory);
+            copyResource("flot-0.8.1-min.js", outputDirectory);
+            copyResource("style.css", outputDirectory);
+            copyResource("report.js", outputDirectory);
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not generate performance test report to '%s'.", outputDirectory), e);
+        }
+    }
+
+    private void copyResource(String resourceName, File outputDirectory) {
+        URL resource = getClass().getClassLoader().getResource("org/gradle/reporting/" + resourceName);
+        String dir = StringUtils.substringAfterLast(resourceName, ".");
+        GFileUtils.copyURLToFile(resource, new File(outputDirectory, dir + "/" + resourceName));
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ResultsStore.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ResultsStore.java
new file mode 100644
index 0000000..1d3c9d5
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ResultsStore.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.performance.fixture.BaselineVersion;
+import org.gradle.performance.fixture.DataReporter;
+import org.gradle.performance.fixture.MeasuredOperationList;
+import org.gradle.performance.fixture.PerformanceResults;
+import org.gradle.performance.measure.DataAmount;
+import org.gradle.performance.measure.Duration;
+import org.gradle.performance.measure.MeasuredOperation;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.sql.*;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * A {@link DataReporter} implementation that stores results in an H2 relational database.
+ */
+public class ResultsStore implements DataReporter {
+    private final File dbFile;
+    private Connection connection;
+    private final long ignoreV17Before;
+
+    public ResultsStore() {
+        this(new File(System.getProperty("user.home"), ".gradle-performance-test-data/results"));
+    }
+
+    public ResultsStore(File dbFile) {
+        this.dbFile = dbFile;
+
+        // Ignore some broken samples before the given date
+        DateFormat timeStampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        timeStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+        try {
+            ignoreV17Before = timeStampFormat.parse("2013-07-03 00:00:00").getTime();
+        } catch (ParseException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void report(final PerformanceResults results) {
+        try {
+            withConnection(new ConnectionAction<Void>() {
+                public Void execute(Connection connection) throws Exception {
+                    long testId;
+                    PreparedStatement statement = connection.prepareStatement("insert into testExecution(testId, executionTime, targetVersion, testProject, tasks, args, operatingSystem, jvm, vcsBranch, vcsCommit) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+                    try {
+                        statement.setString(1, results.getTestId());
+                        statement.setTimestamp(2, new Timestamp(results.getTestTime()));
+                        statement.setString(3, results.getVersionUnderTest());
+                        statement.setString(4, results.getTestProject());
+                        statement.setObject(5, results.getTasks());
+                        statement.setObject(6, results.getArgs());
+                        statement.setString(7, results.getOperatingSystem());
+                        statement.setString(8, results.getJvm());
+                        statement.setString(9, results.getVcsBranch());
+                        statement.setString(10, results.getVcsCommit());
+                        statement.execute();
+                        ResultSet keys = statement.getGeneratedKeys();
+                        keys.next();
+                        testId = keys.getLong(1);
+                    } finally {
+                        statement.close();
+                    }
+                    statement = connection.prepareStatement("insert into testOperation(testExecution, version, executionTimeMs, heapUsageBytes, totalHeapUsageBytes, maxHeapUsageBytes, maxUncollectedHeapBytes, maxCommittedHeapBytes) values (?, ?, ?, ?, ?, ?, ?, ?)");
+                    try {
+                        addOperations(statement, testId, null, results.getCurrent());
+                        for (BaselineVersion baselineVersion : results.getBaselineVersions()) {
+                            addOperations(statement, testId, baselineVersion.getVersion(), baselineVersion.getResults());
+                        }
+                    } finally {
+                        statement.close();
+                    }
+                    return null;
+                }
+            });
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not open results datastore '%s'.", dbFile), e);
+        }
+    }
+
+    private void addOperations(PreparedStatement statement, long testId, String version, MeasuredOperationList operations) throws SQLException {
+        for (MeasuredOperation operation : operations) {
+            statement.setLong(1, testId);
+            statement.setString(2, version);
+            statement.setBigDecimal(3, operation.getExecutionTime().toUnits(Duration.MILLI_SECONDS).getValue());
+            statement.setBigDecimal(4, operation.getTotalMemoryUsed().toUnits(DataAmount.BYTES).getValue());
+            statement.setBigDecimal(5, operation.getTotalHeapUsage().toUnits(DataAmount.BYTES).getValue());
+            statement.setBigDecimal(6, operation.getMaxHeapUsage().toUnits(DataAmount.BYTES).getValue());
+            statement.setBigDecimal(7, operation.getMaxUncollectedHeap().toUnits(DataAmount.BYTES).getValue());
+            statement.setBigDecimal(8, operation.getMaxCommittedHeap().toUnits(DataAmount.BYTES).getValue());
+            statement.execute();
+        }
+    }
+
+    public List<String> getTestNames() {
+        try {
+            return withConnection(new ConnectionAction<List<String>>() {
+                public List<String> execute(Connection connection) throws Exception {
+                    List<String> testNames = new ArrayList<String>();
+                    ResultSet testExecutions = connection.createStatement().executeQuery("select distinct testId from testExecution order by testId");
+                    while (testExecutions.next()) {
+                        testNames.add(testExecutions.getString(1));
+                    }
+                    return testNames;
+                }
+            });
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not load test history from datastore '%s'.", dbFile), e);
+        }
+    }
+
+    public List<String> getVersions() {
+        try {
+            return withConnection(new ConnectionAction<List<String>>() {
+                public List<String> execute(Connection connection) throws Exception {
+                    Set<String> allVersions = new TreeSet<String>(new Comparator<String>() {
+                        public int compare(String o1, String o2) {
+                            return GradleVersion.version(o1).compareTo(GradleVersion.version(o2));
+                        }
+                    });
+                    PreparedStatement uniqueVersions = connection.prepareStatement("select distinct version from testOperation");
+                    ResultSet versions = uniqueVersions.executeQuery();
+                    while (versions.next()) {
+                        String version = versions.getString(1);
+                        if (version != null) {
+                            allVersions.add(version);
+                        }
+                    }
+                    versions.close();
+                    uniqueVersions.close();
+
+                    ArrayList<String> result = new ArrayList<String>();
+                    result.addAll(allVersions);
+
+                    PreparedStatement uniqueBranches = connection.prepareStatement("select distinct vcsBranch from testExecution");
+                    ResultSet branches = uniqueBranches.executeQuery();
+                    Set<String> allBranches = new TreeSet<String>();
+                    while (branches.next()) {
+                        allBranches.add(branches.getString(1).trim());
+                    }
+                    branches.close();
+                    uniqueBranches.close();
+
+                    result.addAll(allBranches);
+
+                    return result;
+                }
+            });
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not load version list from datastore '%s'.", dbFile), e);
+        }
+    }
+
+    public TestExecutionHistory getTestResults(final String testName) {
+        try {
+            return withConnection(new ConnectionAction<TestExecutionHistory>() {
+                public TestExecutionHistory execute(Connection connection) throws Exception {
+                    List<PerformanceResults> results = new ArrayList<PerformanceResults>();
+                    Set<String> allVersions = new TreeSet<String>(new Comparator<String>() {
+                        public int compare(String o1, String o2) {
+                            return GradleVersion.version(o1).compareTo(GradleVersion.version(o2));
+                        }
+                    });
+                    Set<String> allBranches = new TreeSet<String>();
+                    PreparedStatement executionsForName = connection.prepareStatement("select id, executionTime, targetVersion, testProject, tasks, args, operatingSystem, jvm, vcsBranch, vcsCommit from testExecution where testId = ? order by executionTime desc");
+                    PreparedStatement operationsForExecution = connection.prepareStatement("select version, executionTimeMs, heapUsageBytes, totalHeapUsageBytes, maxHeapUsageBytes, maxUncollectedHeapBytes, maxCommittedHeapBytes from testOperation where testExecution = ?");
+                    executionsForName.setString(1, testName);
+                    ResultSet testExecutions = executionsForName.executeQuery();
+                    while (testExecutions.next()) {
+                        long id = testExecutions.getLong(1);
+                        PerformanceResults performanceResults = new PerformanceResults();
+                        performanceResults.setTestId(testName);
+                        performanceResults.setTestTime(testExecutions.getTimestamp(2).getTime());
+                        performanceResults.setVersionUnderTest(testExecutions.getString(3));
+                        performanceResults.setTestProject(testExecutions.getString(4));
+                        performanceResults.setTasks(toArray(testExecutions.getObject(5)));
+                        performanceResults.setArgs(toArray(testExecutions.getObject(6)));
+                        performanceResults.setOperatingSystem(testExecutions.getString(7));
+                        performanceResults.setJvm(testExecutions.getString(8));
+                        performanceResults.setVcsBranch(testExecutions.getString(9).trim());
+                        performanceResults.setVcsCommit(testExecutions.getString(10));
+
+                        results.add(performanceResults);
+                        allBranches.add(performanceResults.getVcsBranch());
+
+                        operationsForExecution.setLong(1, id);
+                        ResultSet builds = operationsForExecution.executeQuery();
+                        while (builds.next()) {
+                            String version = builds.getString(1);
+                            if ("1.7".equals(version) && performanceResults.getTestTime() <= ignoreV17Before) {
+                                // Ignore some broken samples
+                                continue;
+                            }
+                            MeasuredOperation operation = new MeasuredOperation();
+                            operation.setExecutionTime(Duration.millis(builds.getBigDecimal(2)));
+                            operation.setTotalMemoryUsed(DataAmount.bytes(builds.getBigDecimal(3)));
+                            operation.setTotalHeapUsage(DataAmount.bytes(builds.getBigDecimal(4)));
+                            operation.setMaxHeapUsage(DataAmount.bytes(builds.getBigDecimal(5)));
+                            operation.setMaxUncollectedHeap(DataAmount.bytes(builds.getBigDecimal(6)));
+                            operation.setMaxCommittedHeap(DataAmount.bytes(builds.getBigDecimal(7)));
+
+                            if (version == null) {
+                                performanceResults.getCurrent().add(operation);
+                            } else {
+                                BaselineVersion baselineVersion = performanceResults.baseline(version);
+                                baselineVersion.getResults().add(operation);
+                                allVersions.add(version);
+                            }
+                        }
+                    }
+                    testExecutions.close();
+                    operationsForExecution.close();
+                    executionsForName.close();
+
+                    return new TestExecutionHistory(testName, new ArrayList<String>(allVersions), new ArrayList<String>(allBranches), results);
+                }
+            });
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not load results from datastore '%s'.", dbFile), e);
+        }
+    }
+
+    private String[] toArray(Object object) {
+        Object[] value = (Object[]) object;
+        String[] result = new String[value.length];
+        for (int i = 0; i < value.length; i++) {
+            result[i] = value[i].toString();
+        }
+        return result;
+    }
+
+    public void close() {
+        if (connection != null) {
+            try {
+                connection.close();
+            } catch (SQLException e) {
+                throw new RuntimeException(String.format("Could not close datastore '%s'.", dbFile), e);
+            } finally {
+                connection = null;
+            }
+        }
+    }
+
+    private <T> T withConnection(ConnectionAction<T> action) throws Exception {
+        if (connection == null) {
+            dbFile.getParentFile().mkdirs();
+            Class.forName("org.h2.Driver");
+            connection = DriverManager.getConnection(String.format("jdbc:h2:%s", dbFile.getAbsolutePath()), "sa", "");
+            try {
+                initSchema(connection);
+            } catch (Exception e) {
+                connection.close();
+                connection = null;
+                throw e;
+            }
+        }
+        return action.execute(connection);
+    }
+
+    private void initSchema(Connection connection) throws Exception {
+        Statement statement = connection.createStatement();
+        statement.execute("create table if not exists testExecution (id bigint identity not null, testId varchar not null, executionTime timestamp not null, targetVersion varchar not null, testProject varchar not null, tasks array not null, args array not null, operatingSystem varchar not null, jvm varchar not null)");
+        statement.execute("create table if not exists testOperation (testExecution bigint not null, version varchar, executionTimeMs decimal not null, heapUsageBytes decimal not null, foreign key(testExecution) references testExecution(id))");
+        statement.execute("alter table testExecution add column if not exists vcsBranch varchar not null default 'master'");
+        statement.execute("alter table testExecution add column if not exists vcsCommit varchar");
+        statement.execute("alter table testOperation add column if not exists totalHeapUsageBytes decimal");
+        statement.execute("alter table testOperation add column if not exists maxHeapUsageBytes decimal");
+        statement.execute("alter table testOperation add column if not exists maxUncollectedHeapBytes decimal");
+        statement.execute("alter table testOperation add column if not exists maxCommittedHeapBytes decimal");
+        statement.close();
+    }
+
+    private interface ConnectionAction<T> {
+        T execute(Connection connection) throws Exception;
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestDataGenerator.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestDataGenerator.java
new file mode 100644
index 0000000..b77a26e
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestDataGenerator.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import org.gradle.api.Transformer;
+import org.gradle.performance.fixture.MeasuredOperationList;
+import org.gradle.performance.fixture.PerformanceResults;
+import org.gradle.reporting.ReportRenderer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Date;
+import java.util.List;
+
+public class TestDataGenerator extends ReportRenderer<TestExecutionHistory, Writer> {
+    protected final FormatSupport format = new FormatSupport();
+
+    @Override
+    public void render(TestExecutionHistory testHistory, Writer output) throws IOException {
+        PrintWriter out = new PrintWriter(output);
+        List<PerformanceResults> sortedResults = testHistory.getResultsOldestFirst();
+        out.println("{");
+        out.println("\"labels\": [");
+        for (int i = 0; i < sortedResults.size(); i++) {
+            PerformanceResults results = sortedResults.get(i);
+            if (i > 0) {
+                out.print(", ");
+            }
+            out.print("\"" + format.date(new Date(results.getTestTime())) + "\"");
+        }
+        out.println("],");
+        out.print("\"executionTime\":");
+        render(testHistory, new Transformer<String, MeasuredOperationList>() {
+            public String transform(MeasuredOperationList original) {
+                return format.seconds(original.getExecutionTime().getAverage());
+            }
+        }, out);
+        out.println(",");
+        out.print("\"heapUsage\":");
+        render(testHistory, new Transformer<String, MeasuredOperationList>() {
+            public String transform(MeasuredOperationList original) {
+                return format.megabytes(original.getTotalMemoryUsed().getAverage());
+            }
+        }, out);
+        out.println("}");
+        out.flush();
+    }
+
+    void render(TestExecutionHistory testHistory, Transformer<String, MeasuredOperationList> valueRenderer, PrintWriter out) {
+        List<PerformanceResults> sortedResults = testHistory.getResultsOldestFirst();
+        out.println("  [");
+        for (int i = 0; i < testHistory.getKnownVersions().size(); i++) {
+            String version = testHistory.getKnownVersions().get(i);
+            if (i > 0) {
+                out.println(",");
+            }
+            out.println("  {");
+            out.println("    \"label\": \"" + version + "\",");
+            out.print("\"data\": [");
+            boolean empty = true;
+            for (int j = 0; j < sortedResults.size(); j++) {
+                PerformanceResults results = sortedResults.get(j);
+                MeasuredOperationList measuredOperations = results.version(version).getResults();
+                if (!measuredOperations.isEmpty()) {
+                    if (!empty) {
+                        out.print(", ");
+                    }
+                    out.print("[" + j + ", " + valueRenderer.transform(measuredOperations) + "]");
+                    empty = false;
+                }
+            }
+            out.println("]");
+            out.print("  }");
+        }
+        out.println();
+        out.println("]");
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestExecutionHistory.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestExecutionHistory.java
new file mode 100644
index 0000000..8be02da
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestExecutionHistory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import org.gradle.performance.fixture.PerformanceResults;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TestExecutionHistory {
+    private final String name;
+    private final List<String> versions;
+    private final List<String> branches;
+    private final List<PerformanceResults> newestFirst;
+    private List<PerformanceResults> oldestFirst;
+    private List<String> knownVersions;
+
+    public TestExecutionHistory(String name, List<String> versions, List<String> branches, List<PerformanceResults> newestFirst) {
+        this.name = name;
+        this.versions = versions;
+        this.branches = branches;
+        this.newestFirst = newestFirst;
+    }
+
+    public String getId() {
+        return name.replaceAll("\\s+", "-");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<String> getBaselineVersions() {
+        return versions;
+    }
+
+    public List<String> getBranches() {
+        return branches;
+    }
+
+    public List<String> getKnownVersions() {
+        if (knownVersions == null) {
+            ArrayList<String> result = new ArrayList<String>();
+            result.addAll(versions);
+            result.addAll(branches);
+            knownVersions = result;
+        }
+        return knownVersions;
+    }
+
+    /**
+     * Returns results from most recent to least recent.
+     */
+    public List<PerformanceResults> getResults() {
+        return newestFirst;
+    }
+
+    /**
+     * Returns results from least recent to most recent.
+     */
+    public List<PerformanceResults> getResultsOldestFirst() {
+        if (oldestFirst == null) {
+            oldestFirst = new ArrayList<PerformanceResults>(newestFirst);
+            Collections.reverse(oldestFirst);
+        }
+        return oldestFirst;
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestPageGenerator.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestPageGenerator.java
new file mode 100644
index 0000000..5a7caf9
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/TestPageGenerator.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.results;
+
+import com.google.common.base.Joiner;
+import com.googlecode.jatl.Html;
+import org.gradle.api.Transformer;
+import org.gradle.performance.fixture.MeasuredOperationList;
+import org.gradle.performance.fixture.PerformanceResults;
+import org.gradle.performance.fixture.VersionResults;
+import org.gradle.performance.measure.Amount;
+import org.gradle.performance.measure.DataAmount;
+import org.gradle.performance.measure.DataSeries;
+import org.gradle.performance.measure.Duration;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class TestPageGenerator extends HtmlPageGenerator<TestExecutionHistory> {
+    @Override
+    public void render(final TestExecutionHistory testHistory, Writer writer) throws IOException {
+        new Html(writer) {{
+            html();
+                head();
+                    headSection(this);
+                    title().text(String.format("Profile test %s report", testHistory.getName())).end();
+                    script();
+                        text("$(function() {\n");
+                        text("$.ajax({ url:'" + testHistory.getId() + ".json\', dataType: 'json',");
+                        text("  success: function(data) {\n");
+                        text("    var labels = data.labels;\n");
+                        text("    var options = { series: { points: { show: true }, lines: { show: true } }, legend: { noColumns: 0, margin: 1 }, grid: { hoverable: true, clickable: true }, xaxis: { tickFormatter: function(index, value) { return labels[index]; } } };\n");
+                        text("    $.plot('#executionTimeChart', data.executionTime, options);\n");
+                        text("    $.plot('#heapUsageChart', data.heapUsage, options);\n");
+                        text("    $('#executionTimeChart').bind('plothover', function (event, pos, item) {\n");
+                        text("      if (!item) {\n");
+                        text("        $('#tooltip').hide();\n");
+                        text("      } else {\n");
+                        text("        var text = 'Version: ' + item.series.label + ', date: ' + labels[item.dataIndex] + ', execution time: ' + item.datapoint[1] + 's';\n");
+                        text("        $('#tooltip').html(text).css({top: item.pageY - 10, left: item.pageX + 10}).show();\n");
+                        text("      }\n");
+                        text("    });\n");
+                        text("    $('#heapUsageChart').bind('plothover', function (event, pos, item) {\n");
+                        text("      if (!item) {\n");
+                        text("        $('#tooltip').hide();\n");
+                        text("      } else {\n");
+                        text("        var text = 'Version: ' + item.series.label + ', date: ' + labels[item.dataIndex] + ', heap usage: ' + item.datapoint[1] + 'mb';\n");
+                        text("        $('#tooltip').html(text).css({top: item.pageY - 10, left: item.pageX + 10}).show();\n");
+                        text("      }\n");
+                        text("    });\n");
+                        text("  }\n");
+                        text("});\n");
+                        text("});");
+                    end();
+                end();
+                body();
+                div().id("content");
+                    h2().text(String.format("Test %s", testHistory.getName())).end();
+                    h3().text("Average execution time").end();
+                    div().id("executionTimeChart").classAttr("chart");
+                        p().text("Loading...").end();
+                    end();
+                    h3().text("Average heap usage").end();
+                    div().id("heapUsageChart").classAttr("chart");
+                        p().text("Loading...").end();
+                    end();
+                    div().id("tooltip").end();
+                    h3().text("Test history").end();
+                    div().id("controls").end();
+                    table().classAttr("history");
+                        tr().classAttr("control-groups");
+                            th().colspan("4").end();
+                            th().colspan(String.valueOf(testHistory.getKnownVersions().size())).text("Average execution time").end();
+                            th().colspan(String.valueOf(testHistory.getKnownVersions().size())).text("Average heap usage (old measurement)").end();
+                            th().colspan(String.valueOf(testHistory.getKnownVersions().size())).text("Average total heap usage").end();
+                            th().colspan(String.valueOf(testHistory.getKnownVersions().size())).text("Average max heap usage").end();
+                            th().colspan(String.valueOf(testHistory.getKnownVersions().size())).text("Average max uncollected heap").end();
+                            th().colspan(String.valueOf(testHistory.getKnownVersions().size())).text("Average max committed heap").end();
+                            th().colspan("4").text("Details").end();
+                        end();
+                        tr();
+                            th().text("Date").end();
+                            th().text("Test version").end();
+                            th().text("Branch").end();
+                            th().text("Git commit").end();
+                            for (String version : testHistory.getKnownVersions()) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            for (String version : testHistory.getKnownVersions()) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            for (String version : testHistory.getKnownVersions()) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            for (String version : testHistory.getKnownVersions()) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            for (String version : testHistory.getKnownVersions()) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            for (String version : testHistory.getKnownVersions()) {
+                                th().classAttr("numeric").text(version).end();
+                            }
+                            th().text("Test project").end();
+                            th().text("Tasks").end();
+                            th().text("Operating System").end();
+                            th().text("JVM").end();
+                        end();
+                        for (PerformanceResults performanceResults : testHistory.getResults()) {
+                            tr();
+                                td().text(format.timestamp(new Date(performanceResults.getTestTime()))).end();
+                                td().text(performanceResults.getVersionUnderTest()).end();
+                                td().text(performanceResults.getVcsBranch()).end();
+                                td().text(performanceResults.getVcsCommit()).end();
+                                renderMetricForVersions(testHistory, performanceResults, new Transformer<DataSeries<Duration>, MeasuredOperationList>() {
+                                    public DataSeries<Duration> transform(MeasuredOperationList original) {
+                                        return original.getExecutionTime();
+                                    }
+                                });
+                                renderMetricForVersions(testHistory, performanceResults, new Transformer<DataSeries<DataAmount>, MeasuredOperationList>() {
+                                    public DataSeries<DataAmount> transform(MeasuredOperationList original) {
+                                        return original.getTotalMemoryUsed();
+                                    }
+                                });
+                                renderMetricForVersions(testHistory, performanceResults, new Transformer<DataSeries<DataAmount>, MeasuredOperationList>() {
+                                    public DataSeries<DataAmount> transform(MeasuredOperationList original) {
+                                        return original.getTotalHeapUsage();
+                                    }
+                                });
+                                renderMetricForVersions(testHistory, performanceResults, new Transformer<DataSeries<DataAmount>, MeasuredOperationList>() {
+                                    public DataSeries<DataAmount> transform(MeasuredOperationList original) {
+                                        return original.getMaxHeapUsage();
+                                    }
+                                });
+                                renderMetricForVersions(testHistory, performanceResults, new Transformer<DataSeries<DataAmount>, MeasuredOperationList>() {
+                                    public DataSeries<DataAmount> transform(MeasuredOperationList original) {
+                                        return original.getMaxUncollectedHeap();
+                                    }
+                                });
+                                renderMetricForVersions(testHistory, performanceResults, new Transformer<DataSeries<DataAmount>, MeasuredOperationList>() {
+                                    public DataSeries<DataAmount> transform(MeasuredOperationList original) {
+                                        return original.getMaxCommittedHeap();
+                                    }
+                                });
+                                td().text(performanceResults.getTestProject()).end();
+                                td();
+                                    text(Joiner.on(", ").join(performanceResults.getArgs()));
+                                    text(" ");
+                                    text(Joiner.on(", ").join(performanceResults.getTasks()));
+                                end();
+                                td().text(performanceResults.getOperatingSystem()).end();
+                                td().text(performanceResults.getJvm()).end();
+                            end();
+                        }
+                    end();
+                end();
+                footer(this);
+            endAll();
+        }
+
+            private <T> void renderMetricForVersions(TestExecutionHistory testHistory, PerformanceResults testExecution, Transformer<DataSeries<T>, MeasuredOperationList> transformer) {
+                List<Amount<T>> values = new ArrayList<Amount<T>>();
+                Amount<T> min = null;
+                Amount<T> max = null;
+                for (String version : testHistory.getKnownVersions()) {
+                    VersionResults versionResults = testExecution.version(version);
+                    DataSeries<T> data = transformer.transform(versionResults.getResults());
+                    if (data.isEmpty()) {
+                        values.add(null);
+                    } else {
+                        Amount<T> value = data.getAverage();
+                        values.add(value);
+                        if (min == null || value.compareTo(min) < 0) {
+                            min = value;
+                        }
+                        if (max == null || value.compareTo(max) > 0) {
+                            max = value;
+                        }
+                    }
+                }
+                if (min != null && min.equals(max)) {
+                    min = null;
+                    max = null;
+                }
+
+                for (Amount<?> value : values) {
+                    if (value == null) {
+                        td().text("").end();
+                    } else {
+                        String classAttr = "numeric";
+                        if (value.equals(min)) {
+                            classAttr += " min-value";
+                        }
+                        if (value.equals(max)) {
+                            classAttr += " max-value";
+                        }
+                        td().classAttr(classAttr).text(value.format()).end();
+                    }
+                }
+            }
+        };
+    }
+
+}
diff --git a/subprojects/performance/src/testFixtures/resources/org/gradle/reporting/report.js b/subprojects/performance/src/testFixtures/resources/org/gradle/reporting/report.js
new file mode 100644
index 0000000..8aecac0
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/resources/org/gradle/reporting/report.js
@@ -0,0 +1,63 @@
+$(document).ready(function () {
+
+    // Attach controls for column groups in tables
+    var controls = $("div#controls");
+    var groups = [];
+    var slices = [];
+    $("tr.control-groups").closest("table").find("tr").each(function() {
+        var row = $(this);
+        if (row.hasClass('control-groups')) {
+            var currentCol = 0;
+            slices = [];
+            row.find("th").each(function(){
+                var e = $(this);
+                var title = e.text().trim();
+                var startCol = currentCol;
+                currentCol += parseInt(e.attr('colspan'));
+                var endCol = currentCol;
+                if (title.length == 0) {
+                    return;
+                }
+                var id = title.replace(/[^\w]/g, '-').toLowerCase();
+                if (groups.indexOf(id) < 0) {
+                    groups.push(id);
+                    var div = controls.append("<div/>");
+                    div.append("<label for='" + id + "'>" + title + "</label>");
+                    var checkbox = $("<input>", {type: "checkbox", id: id, checked: true});
+                    div.append(checkbox);
+                    checkbox.change(function () {
+                        if (checkbox.is(':checked')) {
+                            $("." + id).show();
+                        } else {
+                            $("." + id).hide();
+                        }
+                    });
+                }
+                e.addClass(id);
+                for (var i = startCol; i < endCol; i++) {
+                    slices[i] = id;
+                }
+            });
+        } else {
+            row.find("td,th").each(function(index){
+                $(this).addClass(slices[index]);
+            })
+        }
+    });
+
+    // Add alternate row styles for tables
+    $("table").each(function () {
+        var counter = 0;
+        $(this).find("tr").each(function () {
+            var e = $(this);
+            if (e.children("th").length > 0) {
+                counter = 0;
+                return;
+            }
+            if (counter % 2 == 0) {
+                e.addClass("table-row-even");
+            }
+            counter++;
+        })
+    });
+});
diff --git a/subprojects/performance/src/testFixtures/resources/org/gradle/reporting/style.css b/subprojects/performance/src/testFixtures/resources/org/gradle/reporting/style.css
new file mode 100644
index 0000000..7e5bf92
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/resources/org/gradle/reporting/style.css
@@ -0,0 +1,89 @@
+body {
+    font-family: sans-serif;
+    font-size: 12pt;
+    margin: 3em;
+    color: #444;
+}
+
+h2 {
+    font-size: 14pt;
+    margin-top: 2em;
+}
+
+#footer {
+    margin-top: 4em;
+    font-size: 8pt;
+}
+
+table {
+    border-collapse: collapse;
+}
+
+table.history {
+    margin-top: 20px;
+    font-size: 10pt;
+}
+
+th, td {
+    white-space: nowrap;
+    padding-top: 4px;
+    padding-bottom: 4px;
+    padding-left: 8px;
+    padding-right: 8px;
+    text-align: left;
+}
+
+th.numeric, td.numeric {
+    text-align: right;
+    min-width: 4em;
+}
+
+th.test-execution {
+    font-size: 14pt;
+    padding-top: 1em;
+}
+
+.table-row-even {
+    background-color: #ecf4fa;
+}
+
+#controls {
+    text-align: right;
+    font-size: 10pt;
+}
+
+.min-value {
+    color: #008000;
+}
+
+.max-value {
+    color: red;
+}
+
+#controls label {
+    font-weight: bold;
+    cursor: pointer;
+}
+
+.chart {
+    width: 1000px;
+    height: 280px;
+    margin-top: 20px;
+    margin-bottom: 20px;
+}
+
+.chart p {
+    font-size: 8pt;
+    color: #a0a0a0;
+    text-align: center;
+}
+
+#tooltip {
+    position: absolute;
+    display: none;
+    font-size: 10pt;
+    background-color: #fee;
+    border: solid 1px #fdd;
+    opacity: 0.80;
+    padding: 3px;
+}
\ No newline at end of file
diff --git a/subprojects/plugins/plugins.gradle b/subprojects/plugins/plugins.gradle
index e1b00be..cbb72d8 100644
--- a/subprojects/plugins/plugins.gradle
+++ b/subprojects/plugins/plugins.gradle
@@ -20,17 +20,14 @@ configurations {
     testFixtures
 }
 
-if (!javaVersion.java6Compatible) {
-    sourceSets.main.groovy.exclude '**/jdk6/**'
-}
-
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(':coreImpl')
-    compile project(':wrapper')
     compile project(':reporting')
+    compile project(':languageBase')
+    compile project(':languageJvm')
 
     compile libraries.ant
     compile libraries.asm
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 4df8b36..c50d179 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
@@ -41,7 +41,7 @@ class BuildSrcPluginTest extends AbstractIntegrationSpec {
             apply plugin: "groovy"
 
             dependencies {
-                groovy localGroovy()
+                compile localGroovy()
                 compile gradleApi()
             }
         """
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy
index 2a7c872..3d1f4fb 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.tasks.bundling
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import static org.hamcrest.Matchers.equalTo
+import org.gradle.test.fixtures.archive.JarTestFixture
 
 class JarIntegrationTest extends AbstractIntegrationSpec {
 
@@ -30,13 +30,13 @@ class JarIntegrationTest extends AbstractIntegrationSpec {
                     archiveName = 'test.jar'
         }
         """
+
         when:
         run 'jar'
+
         then:
-        def expandDir = file('expanded')
-        file('build/test.jar').unzipTo(expandDir)
-        expandDir.assertHasDescendants('META-INF/MANIFEST.MF')
-        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
+        def jar = new JarTestFixture(file('build/test.jar'))
+        jar.assertFileContent('META-INF/MANIFEST.MF', 'Manifest-Version: 1.0\r\n\r\n')
     }
 
     def canCreateAJarArchiveWithDefaultManifest() {
@@ -63,13 +63,15 @@ class JarIntegrationTest extends AbstractIntegrationSpec {
                 archiveName = 'test.jar'
             }
         """
+
         when:
         run 'jar'
+
         then:
-        def expandDir = file('expanded')
-        file('build/test.jar').unzipTo(expandDir)
-        expandDir.assertHasDescendants('META-INF/MANIFEST.MF', 'META-INF/file1.txt', 'META-INF/dir2/file2.txt', 'dir1/file1.txt')
-        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
+        def jar = new JarTestFixture(file('build/test.jar'))
+        jar.assertContainsFile('META-INF/file1.txt')
+        jar.assertContainsFile('META-INF/dir2/file2.txt')
+        jar.assertContainsFile('dir1/file1.txt')
     }
 
     def metaInfSpecsAreIndependentOfOtherSpec() {
@@ -107,17 +109,17 @@ class JarIntegrationTest extends AbstractIntegrationSpec {
                 archiveName = 'test.jar'
             }
         """
+
         when:
         run 'jar'
+
         then:
-        def expandDir = file('expanded')
-        file('build/test.jar').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'META-INF/dir2/file2.xml',
-                'META-INF/dir3/file2.txt',
-                'META-INF/dir3/file2.xml',
-                'dir1/file1.txt')
+        def jar = new JarTestFixture(file('build/test.jar'))
+        jar.assertContainsFile('META-INF/MANIFEST.MF')
+        jar.assertContainsFile('META-INF/dir2/file2.xml')
+        jar.assertContainsFile('META-INF/dir3/file2.txt')
+        jar.assertContainsFile('META-INF/dir3/file2.xml')
+        jar.assertContainsFile('dir1/file1.txt')
     }
 
     def usesManifestFromJarTaskWhenMergingJars() {
@@ -146,18 +148,173 @@ class JarIntegrationTest extends AbstractIntegrationSpec {
                 from zipTree(jar1.archivePath), zipTree(jar2.archivePath)
                 manifest { attributes(attr: 'value') }
                 destinationDir = buildDir
-                archiveName = 'test.zip'
+                archiveName = 'test.jar'
             }
             '''
+
         when:
         run 'jar'
+
         then:
-        def jar = file('build/test.zip')
+        def jar = file('build/test.jar')
         def manifest = jar.manifest
         manifest.mainAttributes.getValue('attr') == 'value'
 
-        def expandDir = file('expected')
-        jar.unzipTo(expandDir)
-        expandDir.assertHasDescendants('dir1/file1.txt', 'dir2/file2.txt', 'META-INF/MANIFEST.MF')
+        def jarFixture = new JarTestFixture(jar)
+        jarFixture.assertContainsFile('META-INF/MANIFEST.MF')
+        jarFixture.assertContainsFile('dir2/file2.txt')
+        jarFixture.assertContainsFile('dir2/file2.txt')
+    }
+
+    def excludeDuplicatesUseManifestOverMetaInf() {
+        createDir('meta-inf') {
+            file 'MANIFEST.MF'
+        }
+        buildFile << '''
+        task jar(type: Jar) {
+            duplicatesStrategy = 'exclude'
+            metaInf {
+                from 'meta-inf'
+            }
+            manifest {
+                attributes(attr: 'from manifest')
+            }
+            destinationDir = buildDir
+            archiveName = 'test.jar'
+        }
+
+        '''
+
+        when:
+        run 'jar'
+
+        then:
+        def jar = file('build/test.jar')
+        def manifest = jar.manifest
+        manifest.mainAttributes.getValue('attr') == 'from manifest'
+    }
+
+    def excludeDuplicatesUseMetaInfOverRegularFiles() {
+        createDir('meta-inf1') {
+            file 'file.txt'
+        }
+
+        createDir('meta-inf2') {
+            file 'file.txt'
+        }
+
+        file('meta-inf1/file.txt').text = 'good'
+        file('meta-inf2/file.txt').text = 'bad'
+
+
+        buildFile << '''
+        task jar(type: Jar) {
+            duplicatesStrategy = 'exclude'
+            // this should be excluded even though it comes first
+            into('META-INF') {
+                from 'meta-inf2'
+            }
+            metaInf {
+                from 'meta-inf1'
+            }
+            destinationDir = buildDir
+            archiveName = 'test.jar'
+        }
+
+        '''
+
+        when:
+        run 'jar'
+
+        then:
+        def jar = new JarTestFixture(file('build/test.jar'))
+        jar.assertFileContent('META-INF/file.txt', 'good')
     }
+
+    def duplicateServicesIncludedOthersExcluded() {
+        createParallelDirsWithServices()
+
+        given:
+        buildFile << '''
+        task jar(type: Jar) {
+            archiveName = 'test.jar'
+            from 'dir1'
+            from 'dir2'
+            eachFile {
+                it.duplicatesStrategy = it.relativePath.toString().startsWith('META-INF/services/') ? 'include' : 'exclude'
+            }
+        }
+
+        '''
+
+        when:
+        run 'jar'
+
+        then:
+        confirmDuplicateServicesPreserved()
+    }
+
+    def duplicatesExcludedByDefaultWithExceptionForServices() {
+        createParallelDirsWithServices()
+
+        given:
+        buildFile << '''
+        task jar(type: Jar) {
+            archiveName = 'test.jar'
+            from 'dir1'
+            from 'dir2'
+            duplicatesStrategy = 'exclude'
+            filesMatching ('META-INF/services/**') {
+                duplicatesStrategy = 'include'
+            }
+        }
+
+        '''
+
+        when:
+        run 'jar'
+
+        then:
+        confirmDuplicateServicesPreserved()
+    }
+
+    private def createParallelDirsWithServices() {
+        createDir('dir1') {
+            'META-INF' {
+                services {
+                    file('org.gradle.Service')
+                }
+            }
+            path {
+                file 'test.txt'
+            }
+        }
+        createDir('dir2') {
+            'META-INF' {
+                services {
+                    file('org.gradle.Service')
+                }
+            }
+            file {
+                file 'test.txt'
+            }
+        }
+
+        file('dir1/META-INF/services/org.gradle.Service').write('org.gradle.DefaultServiceImpl')
+        file('dir2/META-INF/services/org.gradle.Service').write('org.gradle.BetterServiceImpl')
+        file('dir1/test.txt').write('Content of first file')
+        file('dir2/test.txt').write('Content of second file')
+    }
+
+    private def confirmDuplicateServicesPreserved() {
+        def jar = new JarTestFixture(file('test.jar'))
+
+        assert 2 == jar.countFiles('META-INF/services/org.gradle.Service')
+        assert 1 == jar.countFiles('path/test.txt')
+
+        jar.assertFileContent('test.txt', 'Content of first file')
+        jar.hasService('org.gradle.Service', 'org.gradle.BetterServiceImpl')
+        jar.hasService('org.gradle.Service', 'org.gradle.DefaultServiceImpl')
+    }
+
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy
index 11354d7..0545606 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.tasks.bundling
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import static org.hamcrest.Matchers.equalTo
+import org.gradle.test.fixtures.archive.JarTestFixture
 
 class WarTaskIntegrationTest extends AbstractIntegrationSpec {
 
@@ -65,25 +65,27 @@ class WarTaskIntegrationTest extends AbstractIntegrationSpec {
                 archiveName = 'test.war'
             }
         """
+
         when:
         run "war"
+
         then:
-        def expandDir = file('expanded')
-        file('build/test.war').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'META-INF/metainf1/file2.txt',
-                'content1/file1.jsp',
-                'WEB-INF/lib/lib.jar',
-                'WEB-INF/classes/org/gradle/resource.txt',
-                'WEB-INF/classes/org/gradle/Person.class',
-                'WEB-INF/webinf1/file1.txt')
-
-        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
+        def war = new JarTestFixture(file('build/test.war'))
+        war.isManifestPresentAndFirstEntry()
+        war.assertContainsFile('META-INF/MANIFEST.MF')
+        war.assertContainsFile('META-INF/metainf1/file2.txt')
+        war.assertContainsFile('content1/file1.jsp')
+        war.assertContainsFile('WEB-INF/lib/lib.jar')
+        war.assertContainsFile('WEB-INF/classes/org/gradle/resource.txt')
+        war.assertContainsFile('WEB-INF/classes/org/gradle/Person.class')
+        war.assertContainsFile('WEB-INF/webinf1/file1.txt')
+
+        war.assertFileContent('META-INF/MANIFEST.MF', 'Manifest-Version: 1.0\r\n\r\n')
     }
 
     def canCreateAWarArchiveWithWebXml() {
-        given: file('some.xml') << '<web/>'
+        given:
+        def webXml = file('some.xml') << '<web/>'
         createDir('web-inf') {
             webinf1 {
                 file 'file1.txt'
@@ -101,15 +103,17 @@ class WarTaskIntegrationTest extends AbstractIntegrationSpec {
                 archiveName = 'test.war'
             }
         """
+
         when:
         run "war"
+
         then:
-        def expandDir = file('expanded')
-        file('build/test.war').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'WEB-INF/web.xml',
-                'WEB-INF/webinf1/file1.txt')
+        def war = new JarTestFixture(file('build/test.war'))
+        war.assertContainsFile('META-INF/MANIFEST.MF')
+        war.assertContainsFile('WEB-INF/web.xml')
+        war.assertContainsFile('WEB-INF/webinf1/file1.txt')
+
+        war.assertFileContent('WEB-INF/web.xml', webXml.text)
     }
 
     def canAddFilesToWebInfDir() {
@@ -139,14 +143,139 @@ class WarTaskIntegrationTest extends AbstractIntegrationSpec {
                 archiveName = 'test.war'
             }
         """
+
         when:
         run 'war'
+
+        then:
+        def war = new JarTestFixture(file('build/test.war'))
+        war.assertContainsFile('META-INF/MANIFEST.MF')
+        war.assertContainsFile('WEB-INF/webinf1/file1.txt')
+        war.assertContainsFile('WEB-INF/dir2/file2.txt')
+    }
+
+    def "exclude duplicates: webXml precedence over webInf"() {
+        given:
+        createDir('bad') {
+            file('web.xml')
+        }
+        file('good.xml')
+
+        file('bad/web.xml').text = 'bad'
+        file('good.xml').text = 'good'
+
+        buildFile << '''
+        task war(type: War) {
+            webInf {
+                from 'bad'
+            }
+            webXml = file('good.xml')
+            destinationDir = buildDir
+            archiveName = 'test.war'
+            duplicatesStrategy = 'exclude'
+        }
+        '''
+
+        when:
+        run "war"
+
+        then:
+        def war = new JarTestFixture(file('build/test.war'))
+        war.assertFileContent('WEB-INF/web.xml', 'good')
+    }
+
+    def "exclude duplicates: classpath precedence over webInf"() {
+        given:
+        createDir('bad') {
+            lib {
+                file('file.txt')
+            }
+        }
+        createDir('good') {
+            file('file.txt')
+        }
+
+        file('bad/lib/file.txt').text = 'bad'
+        file('good/file.txt').text = 'good'
+
+        buildFile << '''
+        task war(type: War) {
+            webInf {
+                from 'bad'
+            }
+            classpath 'good/file.txt'
+            destinationDir = buildDir
+            archiveName = 'test.war'
+            duplicatesStrategy = 'exclude'
+        }
+        '''
+
+        when:
+        run "war"
+
+        then:
+        def war = new JarTestFixture(file('build/test.war'))
+        war.assertFileContent('WEB-INF/lib/file.txt', 'good')
+    }
+
+    def "exclude duplicates: webInf over normal files"() {
+        given:
+        createDir('bad') {
+            file('file.txt')
+        }
+        createDir('good') {
+            file('file.txt')
+        }
+
+        file('bad/file.txt').text = 'bad'
+        file('good/file.txt').text = 'good'
+
+        buildFile << '''
+        task war(type: War) {
+            into('WEB-INF') {
+                from 'bad'
+            }
+            webInf {
+                from 'good'
+            }
+            destinationDir = buildDir
+            archiveName = 'test.war'
+            duplicatesStrategy = 'exclude'
+        }
+        '''
+
+        when:
+        run "war"
+
+        then:
+        def war = new JarTestFixture(file('build/test.war'))
+        war.assertFileContent('WEB-INF/file.txt', 'good')
+    }
+
+    def "exclude duplicates: webXml over normal files"() {
+        given:
+        file('originalWebXml.xml') << 'good'
+        file('some-dir/web.xml') << 'bad'
+
+        and:
+        buildFile << """
+            task war(type: War) {
+                duplicatesStrategy 'exclude'
+                from('some-dir') {
+                    into 'WEB-INF'
+                }
+                webXml = file('originalWebXml.xml')
+                destinationDir = buildDir
+                archiveName = 'test.war'
+            }
+        """
+
+        when:
+        run "war"
+
         then:
-        def expandDir = file('expanded')
-        file('build/test.war').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'WEB-INF/webinf1/file1.txt',
-                'WEB-INF/dir2/file2.txt')
+        def war = new JarTestFixture(file('build/test.war'))
+        war.assertContainsFile('WEB-INF/web.xml')
+        war.assertFileContent('WEB-INF/web.xml', 'good')
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy
index 629b864..63fb7d6 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy
@@ -18,6 +18,35 @@ package org.gradle.groovy
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class GroovyBasePluginIntegrationTest extends AbstractIntegrationSpec {
+    def "defaults groovyClasspath to 'groovy' configuration if the latter is non-empty"() {
+        executer.withDeprecationChecksDisabled()
+        file("build.gradle") << """
+apply plugin: "groovy-base"
+
+sourceSets {
+    custom
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy "org.codehaus.groovy:groovy-all:2.1.2"
+}
+
+task groovydoc(type: Groovydoc)
+
+task verify << {
+    assert compileCustomGroovy.groovyClasspath.is(configurations.groovy)
+    assert groovydoc.groovyClasspath.is(configurations.groovy)
+}
+"""
+
+        expect:
+        succeeds("verify")
+    }
+
     def "defaults Groovy class path to inferred Groovy dependency if Groovy configuration is empty"() {
         file("build.gradle") << """
 apply plugin: "groovy-base"
@@ -49,12 +78,12 @@ task verify << {
 
         where:
         dependency                                  | jarFile
-        "org.codehaus.groovy:groovy-all:2.0.5"      | "groovy-all-2.0.5.jar"
-        "org.codehaus.groovy:groovy:2.0.5"          | "groovy-2.0.5.jar"
-        "org.codehaus.groovy:groovy-all:2.0.5:indy" | "groovy-all-2.0.5-indy.jar"
+        "org.codehaus.groovy:groovy-all:2.1.2"      | "groovy-all-2.1.2.jar"
+        "org.codehaus.groovy:groovy:2.1.2"          | "groovy-2.1.2.jar"
+        "org.codehaus.groovy:groovy-all:2.1.2:indy" | "groovy-all-2.1.2-indy.jar"
     }
 
-    def "defaults groovyClasspath to (empty) Groovy configuration if Groovy library isn't found on class path"() {
+    def "only resolves source class path feeding into inferred Groovy class path if/when the latter is actually used (but not during autowiring)"() {
         file("build.gradle") << """
 apply plugin: "groovy-base"
 
@@ -66,15 +95,50 @@ repositories {
     mavenCentral()
 }
 
-task groovydoc(type: Groovydoc)
+dependencies {
+    customCompile "org.codehaus.groovy:groovy-all:2.1.2"
+}
+
+task groovydoc(type: Groovydoc) {
+    classpath = sourceSets.custom.runtimeClasspath
+}
 
 task verify << {
-    assert compileCustomGroovy.groovyClasspath.is(configurations.groovy)
-    assert groovydoc.groovyClasspath.is(configurations.groovy)
+    assert configurations.customCompile.state.toString() == "UNRESOLVED"
+    assert configurations.customRuntime.state.toString() == "UNRESOLVED"
 }
-"""
+        """
 
         expect:
         succeeds("verify")
     }
+
+    def "not specifying a groovy runtime produces decent error message"() {
+        given:
+        buildFile << """
+            apply plugin: "groovy-base"
+
+            sourceSets {
+                main {}
+            }
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "com.google.guava:guava:11.0.2"
+            }
+        """
+
+        file("src/main/groovy/Thing.groovy") << """
+            class Thing {}
+        """
+
+        when:
+        fails "compileGroovy"
+
+        then:
+        failure.assertHasDescription "Cannot infer Groovy class path because no Groovy Jar was found on class path: configuration ':compile'"
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyPluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyPluginIntegrationTest.groovy
new file mode 100644
index 0000000..118b8e2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyPluginIntegrationTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.groovy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class GroovyPluginIntegrationTest extends AbstractIntegrationSpec {
+
+    def groovyConfigurationCanStillBeUsedButIsDeprecated() {
+        given:
+        buildFile << """
+            apply plugin: "groovy"
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                groovy "org.codehaus.groovy:groovy-all:2.0.5"
+                compile "com.google.guava:guava:11.0.2"
+            }
+        """
+
+        file("src/main/groovy/Thing.groovy") << """
+            import com.google.common.base.Strings
+
+            class Thing {}
+        """
+
+        and:
+        executer.withDeprecationChecksDisabled()
+
+        when:
+        succeeds("build")
+
+        then:
+        output.contains("The groovy configuration has been deprecated")
+    }
+}
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 fb9b4ac..4881ba8 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
@@ -18,7 +18,7 @@ package org.gradle.groovy.compile
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.util.VersionNumber
 
- at TargetVersions(['1.6.9', '1.7.11', '1.8.8', '2.0.5'])
+ at TargetVersions(['1.6.9', '1.7.11', '1.8.8', '2.0.5', '2.1.0'])
 class AntForkingGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
 
     @Override
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
index fb20e74..19f6bce 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.groovy.compile
 
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 
 abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegrationSpec {
     def canEnableAndDisableIntegerOptimization() {
@@ -53,7 +53,7 @@ abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegratio
 
         then:
         noExceptionThrown()
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("Person")
         result.testClass("Person").assertTestPassed("testMe")
     }
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 6ccc619..a9cb48d 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
@@ -24,7 +24,7 @@ import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.VersionNumber
 import org.junit.Rule
 
- at TargetVersions(['1.5.8', '1.6.9', '1.7.11', '1.8.8', '2.0.5'])
+ at TargetVersions(['1.5.8', '1.6.9', '1.7.11', '1.8.8', '2.0.5', '2.1.0'])
 abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegrationSpec {
     @Rule TestResources resources = new TestResources(temporaryFolder)
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
index dbd8630..3fef919 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.groovy.compile
 
+import org.gradle.internal.jvm.Jvm
 import spock.lang.Issue
 
 abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrationSpec {
@@ -68,4 +69,15 @@ abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrat
         then:
         noExceptionThrown()
     }
+
+
+    def canJointCompileWithJavaCompilerExecutable() {
+        args("-PjdkHome=${Jvm.current().getExecutable('javac')}")
+
+        expect:
+        succeeds("compileGroovy")
+        !errorOutput
+        file("build/classes/main/GroovyCode.class").exists()
+        file("build/classes/main/JavaCode.class").exists()
+    }
 }
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 bfa0858..3576eda 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
@@ -20,7 +20,7 @@ import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.gradle.integtests.fixtures.TargetVersions
 
- at Requires(TestPrecondition.JDK7)
+ at Requires(TestPrecondition.JDK7_OR_LATER)
 @TargetVersions(['2.0.5:indy'])
 class InvokeDynamicGroovyCompilerSpec extends ApiGroovyCompilerIntegrationSpec {
     def canEnableAndDisableInvokeDynamicOptimization() {
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 591a545..78a0058 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
@@ -36,7 +36,7 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
                 println "Used JRE: ${jreJavaHome.absolutePath.replace(File.separator, '/')}"
                 apply plugin:'groovy'
                 dependencies{
-                    groovy localGroovy()
+                    compile localGroovy()
                 }
                 compileGroovy{
                     options.fork = ${forkMode}
@@ -50,8 +50,10 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
         file("build/classes/main/org/test/GroovyClazz.class").exists()
 
         where:
-        forkMode << [false, true, false]
-        useAnt << [false, false, true]
+        forkMode | useAnt
+        false    | false
+        false    | true
+        true     | false
     }
 
     @Requires(TestPrecondition.WINDOWS)
@@ -62,28 +64,29 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
         writeGroovyTestSource("src/main/groovy")
         file('build.gradle') << """
             apply plugin:'groovy'
-            dependencies{
-                groovy localGroovy()
+            dependencies {
+                compile localGroovy()
             }
-            compileGroovy{
+            compileGroovy {
                 options.fork = ${forkMode}
-                DeprecationLogger.whileDisabled {
-                    options.useAnt = ${useAnt}
-                    groovyOptions.useAnt = ${useAnt}
-                }
+                options.useAnt = ${useAnt}
+                groovyOptions.useAnt = ${useAnt}
             }
             """
         when:
         def envVars = System.getenv().findAll { !(it.key in ['GRADLE_OPTS', 'JAVA_HOME', 'Path']) }
         envVars.put("Path", "C:\\Windows\\System32")
-        executer.withEnvironmentVars(envVars).withTasks("compileGroovy").run()
+        executer.withEnvironmentVars(envVars).withDeprecationChecksDisabled().withTasks("compileGroovy").run()
 
         then:
         file("build/classes/main/org/test/JavaClazz.class").exists()
         file("build/classes/main/org/test/GroovyClazz.class").exists()
+
         where:
-        forkMode << [false, true, false]
-        useAnt << [false, false, true]
+        forkMode | useAnt
+        false    | false
+        false    | true
+        true     | false
     }
 
     private writeJavaTestSource(String srcDir, String clazzName = "JavaClazz") {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
index e004f1f..cad2d31 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
@@ -16,10 +16,32 @@
 package org.gradle.java
 
 import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import spock.lang.Issue
 
 class JavaPluginGoodBehaviourTest extends WellBehavedPluginTest {
     @Override
     String getMainTask() {
         return "build"
     }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2851")
+    def "changing debug flag does not produce deprecation warning"() {
+        // We are testing here that Groovy prefers the is*() variant over the get one.
+        // If it prefers the get one we'll get a deprecation warning
+        given:
+        applyPlugin()
+
+        when:
+        buildFile << """
+            compileJava {
+                configure(options) {
+                    def value = debug
+                    value = failOnError
+                }
+            }
+        """
+
+        then:
+        succeeds "tasks"
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompilationIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompilationIntegrationTest.groovy
new file mode 100644
index 0000000..c5ba87e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompilationIntegrationTest.groovy
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.java.compile
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class IncrementalJavaCompilationIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        buildFile << """
+            allprojects {
+                apply plugin: 'java'
+                compileJava.options.incremental = true
+            }
+
+            task cleanFiles(type: Delete) {
+                delete("changedFiles.txt", "unchangedFiles.txt")
+            }
+
+            compileJava {
+                dependsOn cleanFiles
+                def times = [:]
+                doFirst {
+                    fileTree("build/classes/main").each {
+                        if (it.file) {
+                            times[it] = it.lastModified()
+                        }
+                    }
+                }
+                doLast {
+                    sleep(1100) //lastModified granularity
+                    def changedFiles = ""
+                    def unchangedFiles = ""
+                    times.each { k,v ->
+                        if (k.lastModified() != v) {
+                            changedFiles += k.name + ","
+                        } else {
+                            unchangedFiles += k.name + ","
+                        }
+                    }
+                    file("changedFiles.txt").text = changedFiles
+                    file("unchangedFiles.txt").text = unchangedFiles
+                }
+            }
+        """
+
+        file("src/main/java/org/Person.java") << """package org;
+        public interface Person {
+            String getName();
+        }"""
+        file("src/main/java/org/PersonImpl.java") << """package org;
+        public class PersonImpl implements Person {
+            public String getName() { return "Szczepan"; }
+        }"""
+        file("src/main/java/org/AnotherPersonImpl.java") << """package org;
+        public class AnotherPersonImpl extends PersonImpl {
+            public String getName() { return "Szczepan Faber " + WithConst.X; }
+        }"""
+        file("src/main/java/org/WithConst.java") << """package org;
+        public class WithConst {
+            final static int X = 100;
+        }"""
+    }
+
+    Set getChangedFiles() {
+        file("changedFiles.txt").text.split(",").findAll { it.length() > 0 }.collect { it.replaceAll("\\.class", "")}
+    }
+
+    Set getUnchangedFiles() {
+        file("unchangedFiles.txt").text.split(",").findAll { it.length() > 0 }.collect { it.replaceAll("\\.class", "")}
+    }
+
+    def "compiles only a single class that was changed"() {
+        run "compileJava"
+
+        file("src/main/java/org/AnotherPersonImpl.java").text = """package org;
+        public class AnotherPersonImpl implements Person {
+            public String getName() { return "Hans"; }
+        }"""
+
+        when: run "compileJava"
+
+        then: changedFiles == ['AnotherPersonImpl'] as Set
+    }
+
+    def "refreshes the class dependencies with each run"() {
+        run "compileJava"
+
+        file("src/main/java/org/AnotherPersonImpl.java").text = """package org;
+        public class AnotherPersonImpl {}""" //remove the dependency to the interface
+
+        when: run "compileJava"
+
+        then: changedFiles == ['AnotherPersonImpl'] as Set
+
+        when:
+        file("src/main/java/org/Person.java").text = """package org;
+        public interface Person {
+            String getName();
+            String toString();
+        }"""
+        run "compileJava"
+
+        then: changedFiles == ['PersonImpl', 'Person'] as Set
+    }
+
+    def "detects class transitive dependents"() {
+        when: run "compileJava"
+
+        then:
+        changedFiles.empty
+        unchangedFiles.empty
+
+        when:
+        file("src/main/java/org/Person.java").text = """package org;
+        public interface Person {
+            String toString();
+        }"""
+
+        run "compileJava"
+
+        then:
+        changedFiles == ['AnotherPersonImpl', 'PersonImpl', 'Person'] as Set
+    }
+
+    def "is sensitive to deletion and change"() {
+        run "compileJava"
+
+        assert file("src/main/java/org/PersonImpl.java").delete()
+
+        file("src/main/java/org/AnotherPersonImpl.java").text = """package org;
+        public class AnotherPersonImpl implements Person {
+            public String getName() { return "Hans"; }
+        }"""
+
+        when: run "compileJava"
+
+        then:
+        !file("build/classes/main/org/PersonImpl.class").exists()
+        changedFiles == ['AnotherPersonImpl', 'PersonImpl'] as Set
+    }
+
+    def "is sensitive to inlined constants"() {
+        run "compileJava"
+
+        file("src/main/java/org/WithConst.java").text = """package org;
+        public class WithConst {
+            static final int X = 20;
+        }"""
+
+        when: run "compileJava"
+
+        then:
+        unchangedFiles.empty
+        changedFiles.containsAll(['WithConst', 'AnotherPersonImpl', 'PersonImpl', 'Person'])
+    }
+
+    def "is sensitive to source annotations"() {
+        file("src/main/java/org/ClassAnnotation.java").text = """package org; import java.lang.annotation.*;
+            @Retention(RetentionPolicy.RUNTIME) public @interface ClassAnnotation {}
+        """
+        file("src/main/java/org/SourceAnnotation.java").text = """package org; import java.lang.annotation.*;
+            @Retention(RetentionPolicy.SOURCE) public @interface SourceAnnotation {}
+        """
+        file("src/main/java/org/UsesClassAnnotation.java").text = """package org;
+            @ClassAnnotation public class UsesClassAnnotation {}
+        """
+        file("src/main/java/org/UsesSourceAnnotation.java").text = """package org;
+            @SourceAnnotation public class UsesSourceAnnotation {}
+        """
+        run "compileJava"
+
+        file("src/main/java/org/ClassAnnotation.java").text = """package org; import java.lang.annotation.*;
+            @Retention(RetentionPolicy.RUNTIME) public @interface ClassAnnotation {
+                String foo() default "foo";
+            }"""
+
+        when: run "compileJava"
+
+        then:
+        unchangedFiles.empty
+        changedFiles.containsAll(['WithConst', 'AnotherPersonImpl', 'PersonImpl', 'Person'])
+    }
+
+    def "removal of class causes deletion of inner classes"() {
+        file("src/main/java/org/B.java") << """package org;
+            public class B {
+                public static class InnerB {}
+            }
+        """
+
+        when: run "compileJava"
+
+        then:
+        def classes = [file('build/classes/main/org/B.class'), file('build/classes/main/org/B$InnerB.class')]
+        classes.each { assert it.exists() }
+
+        when:
+        assert file("src/main/java/org/B.java").delete()
+        run "compileJava"
+
+        then:
+        classes.each { assert !it.exists() }
+    }
+}
\ No newline at end of file
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 7b5dba7..efe9017 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,7 +25,7 @@ import spock.lang.Unroll
 
 class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
 
-    @IgnoreIf({ AvailableJavaHomes.bestJre == 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:
@@ -34,19 +34,21 @@ class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
         file('build.gradle') << """
         println "Used JRE: ${jreJavaHome.absolutePath.replace(File.separator, '/')}"
         apply plugin:'java'
-        compileJava{
+        compileJava {
             options.fork = ${forkMode}
-            DeprecationLogger.whileDisabled { options.useAnt = ${useAnt} }
+            options.useAnt = ${useAnt}
         }
         """
         when:
-        executer.withEnvironmentVars("JAVA_HOME": jreJavaHome.absolutePath).withTasks("compileJava").run().output
+        executer.withEnvironmentVars("JAVA_HOME": jreJavaHome.absolutePath).withDeprecationChecksDisabled().withTasks("compileJava").run().output
         then:
         file("build/classes/main/org/test/JavaClazz.class").exists()
 
         where:
-        forkMode << [false, true, false]
-        useAnt << [false, false, true]
+        forkMode | useAnt
+        false    | false
+        false    | true
+        true     | false
     }
 
     @Requires(TestPrecondition.WINDOWS)
@@ -55,21 +57,23 @@ class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
         given:
         writeJavaTestSource("src/main/java");
         file('build.gradle') << """
-                    apply plugin:'java'
-                    compileJava{
-                        options.fork = ${forkMode}
-                        DeprecationLogger.whileDisabled { options.useAnt = ${useAnt} }
+        apply plugin:'java'
+        compileJava {
+            options.fork = ${forkMode}
+            options.useAnt = ${useAnt}
         }
         """
         def envVars = System.getenv().findAll { !(it.key in ['GRADLE_OPTS', 'JAVA_HOME', 'Path']) }
         envVars.put("Path", "C:\\Windows\\System32")
         when:
-        executer.withEnvironmentVars(envVars).withTasks("compileJava").run()
+        executer.withEnvironmentVars(envVars).withDeprecationChecksDisabled().withTasks("compileJava").run()
         then:
         file("build/classes/main/org/test/JavaClazz.class").exists()
         where:
-            forkMode << [false, true, false]
-            useAnt << [false, false, true]
+        forkMode | useAnt
+        false    | false
+        false    | true
+        true     | false
     }
 
     private writeJavaTestSource(String srcDir) {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/IncrementalTestIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/IncrementalTestIntegrationTest.groovy
new file mode 100644
index 0000000..eaf938b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/IncrementalTestIntegrationTest.groovy
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.testing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+class IncrementalTestIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule public final TestResources resources = new TestResources(temporaryFolder)
+
+    def setup() {
+        executer.noExtraLogging()
+    }
+
+    def doesNotRunStaleTests() {
+        given:
+        fails('test').assertTestsFailed()
+
+        file('src/test/java/Broken.java').assertIsFile().delete()
+
+        expect:
+        succeeds('test')
+    }
+
+    def executesTestsWhenSourceChanges() {
+        given:
+        succeeds('test')
+
+        when:
+        // Change a production class
+        file('src/main/java/MainClass.java').assertIsFile().copyFrom(file('NewMainClass.java'))
+
+        then:
+        succeeds('test').assertTasksNotSkipped(':compileJava', ':classes', ':compileTestJava', ':testClasses', ':test')
+        succeeds('test').assertTasksNotSkipped()
+
+        when:
+        // Change a test class
+        file('src/test/java/Ok.java').assertIsFile().copyFrom(file('NewOk.java'))
+
+        then:
+        succeeds('test').assertTasksNotSkipped(':compileTestJava', ':testClasses', ':test')
+        succeeds('test').assertTasksNotSkipped()
+    }
+
+    def executesTestsWhenTestFrameworkChanges() {
+        given:
+        succeeds('test')
+
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('JUnitTest')
+
+        when:
+        // Switch test framework
+        file('build.gradle').append 'test.useTestNG()\n'
+
+        then:
+        succeeds('test').assertTasksNotSkipped(':test')
+
+        result.assertTestClassesExecuted('TestNGTest', 'JUnitTest') //previous result still present in the dir
+
+        succeeds('test').assertTasksNotSkipped()
+    }
+
+    def "test up-to-date status respects test name patterns"() {
+        file("src/test/java/FooTest.java") << """
+import org.junit.*;
+public class FooTest {
+    @Test public void test() {}
+}
+"""
+        file("src/test/java/BarTest.java") << """
+import org.junit.*;
+public class BarTest {
+    @Test public void test() {}
+}
+"""
+
+        file("build.gradle") << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.10' }
+            test.beforeTest { println "executed " + it }
+        """
+
+        when:
+        def result = executer.withTasks("test", "-Dtest.single=Foo").run()
+
+        then:
+        //asserting on output because test results are kept in between invocations
+        !result.output.contains("executed test test(BarTest)")
+        result.output.contains("executed test test(FooTest)")
+
+        when:
+        result = executer.withTasks("test", "-Dtest.single=Bar").run()
+
+        then:
+        result.output.contains("executed test test(BarTest)")
+        !result.output.contains("executed test test(FooTest)")
+
+        when:
+        result = executer.withTasks("test", "-Dtest.single=Bar").run()
+
+        then:
+        result.assertTaskSkipped(":test")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/SuiteTimestampIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/SuiteTimestampIntegrationTest.groovy
new file mode 100644
index 0000000..f554d88
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/SuiteTimestampIntegrationTest.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.testing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.junit.Test
+import spock.lang.Issue
+
+class SuiteTimestampIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2730")
+    @Test
+    void "test logging is included in XML results"() {
+        file("build.gradle") << """
+            apply plugin: 'java'
+                repositories { mavenCentral() }
+                dependencies { testCompile 'junit:junit:4.11' }
+        """
+
+        file("src/test/java/SomeTest.java") << """
+import org.junit.*;
+
+public class SomeTest {
+    @Test public void foo() {
+        System.out.println("foo");
+    }
+}
+"""
+        when:
+        run "test"
+
+        then:
+        new JUnitXmlTestExecutionResult(testDirectory).testClass("SomeTest").withResult { testClassNode ->
+            testClassNode. at timestamp.toString().startsWith(new GregorianCalendar().get(Calendar.YEAR).toString())
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
index ca02d4e..cca4a00 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
@@ -17,29 +17,33 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import org.junit.Rule
 
 class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
     @Rule public final TestResources resources = new TestResources(temporaryFolder)
 
+    @Requires(TestPrecondition.NOT_JDK_IBM)
     def canRunTestsWithCustomSystemClassLoader() {
         when:
         run 'test'
 
         then:
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
     }
 
+    @Requires(TestPrecondition.NOT_JDK_IBM)
     def canRunTestsWithCustomSystemClassLoaderAndJavaAgent() {
         when:
         run 'test'
 
         then:
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
     }
@@ -49,7 +53,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySecurityManagerIsUsed')
     }
@@ -59,7 +63,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JMockitTest')
         result.testClass('org.gradle.JMockitTest').assertTestPassed('testOk')
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
index 7216d7e..3b2d3c6 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
@@ -16,24 +16,20 @@
 
 package org.gradle.testing
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.HtmlTestExecutionResult
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.integtests.fixtures.*
 import org.junit.Rule
+import spock.lang.Issue
+import spock.lang.Unroll
 
-import static org.hamcrest.Matchers.contains
-import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.*
 
 class TestReportIntegrationTest extends AbstractIntegrationSpec {
     @Rule Sample sample = new Sample(temporaryFolder)
 
-    def "report includes results of each invocation"() {
+    def "report includes results of most recent invocation"() {
         given:
         buildFile << """
-apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'junit:junit:4.11' }
+$junitSetup
 test { systemProperty 'LogLessStuff', System.getProperty('LogLessStuff') }
 """
 
@@ -79,8 +75,368 @@ public class LoggingTest {
         run "testReport"
 
         then:
-        def htmlReport = new HtmlTestExecutionResult(sample.dir, "allTests")
-        htmlReport.testClass("org.gradle.sample.CoreTest").assertTestCount(1, 0, 0).assertTestPassed("ok").assertStdout(contains("hello from CoreTest."))
-        htmlReport.testClass("org.gradle.sample.UtilTest").assertTestCount(1, 0, 0).assertTestPassed("ok").assertStdout(contains("hello from UtilTest."))
+        def htmlReport = new HtmlTestExecutionResult(sample.dir, "build/reports/allTests")
+        htmlReport.testClass("org.gradle.sample.CoreTest").assertTestCount(1, 0, 0).assertTestPassed("ok").assertStdout(equalTo("hello from CoreTest.\n"))
+        htmlReport.testClass("org.gradle.sample.UtilTest").assertTestCount(1, 0, 0).assertTestPassed("ok").assertStdout(equalTo("hello from UtilTest.\n"))
     }
+
+    def "merges report with duplicated classes and methods"() {
+        given:
+        buildFile << """
+$junitSetup
+test {
+    ignoreFailures true
+    useJUnit {
+        excludeCategories 'org.gradle.testing.SuperClassTests'
+        excludeCategories 'org.gradle.testing.SubClassTests'
+    }
+}
+
+task superTest(type: Test) {
+    ignoreFailures true
+    systemProperty 'category', 'super'
+    useJUnit {
+        includeCategories 'org.gradle.testing.SuperClassTests'
+    }
+}
+
+task subTest(type: Test) {
+    ignoreFailures true
+    systemProperty 'category', 'sub'
+    useJUnit {
+        includeCategories 'org.gradle.testing.SubClassTests'
+    }
+}
+
+task testReport(type: TestReport) {
+    destinationDir = file("\$buildDir/reports/allTests")
+    reportOn test, superTest, subTest
+    tasks.build.dependsOn testReport
+}
+"""
+
+        and:
+        file("src/test/java/org/gradle/testing/UnitTest.java") << """
+$testFilePrelude
+public class UnitTest {
+    @Test public void foo() {
+        System.out.println("org.gradle.testing.UnitTest#foo");
+    }
+}
+"""
+        file("src/test/java/org/gradle/testing/SuperTest.java") << """
+$testFilePrelude
+public class SuperTest {
+    @Category(SuperClassTests.class) @Test public void failing() {
+        System.out.println("org.gradle.testing.SuperTest#failing");
+        fail("failing test");
+    }
+    @Category(SuperClassTests.class) @Test public void passing() {
+        System.out.println("org.gradle.testing.SuperTest#passing");
+    }
+}
+"""
+        file("src/test/java/org/gradle/testing/SubTest.java") << """
+$testFilePrelude
+public class SubTest {
+    @Category(SubClassTests.class) @Test public void onlySub() {
+        System.out.println("org.gradle.testing.SubTest#onlySub");
+        assertEquals("sub", System.getProperty("category"));
+    }
+    @Category(SubClassTests.class) @Test public void passing() {
+        System.out.println("org.gradle.testing.SubTest#passing");
+    }
+}
+"""
+        file("src/test/java/org/gradle/testing/SuperClassTests.java") << """
+$testFilePrelude
+public class SuperClassTests {
+}
+"""
+        file("src/test/java/org/gradle/testing/SubClassTests.java") << """
+$testFilePrelude
+public class SubClassTests extends SuperClassTests {
+}
+"""
+
+        when:
+        run "testReport"
+
+        then:
+        def htmlReport = new HtmlTestExecutionResult(testDirectory, 'build/reports/allTests')
+        htmlReport.testClass("org.gradle.testing.UnitTest").assertTestCount(1, 0, 0).assertTestPassed("foo").assertStdout(equalTo('org.gradle.testing.UnitTest#foo\n'))
+        htmlReport.testClass("org.gradle.testing.SuperTest").assertTestCount(2, 1, 0).assertTestPassed("passing")
+                .assertTestFailed("failing", equalTo('java.lang.AssertionError: failing test'))
+                .assertStdout(allOf(containsString('org.gradle.testing.SuperTest#failing\n'), containsString('org.gradle.testing.SuperTest#passing\n')))
+        htmlReport.testClass("org.gradle.testing.SubTest").assertTestCount(4, 1, 0).assertTestPassed("passing") // onlySub is passing once and failing once
+                .assertStdout(allOf(containsString('org.gradle.testing.SubTest#passing\n'), containsString('org.gradle.testing.SubTest#onlySub\n')))
+    }
+
+    @Issue("http://issues.gradle.org//browse/GRADLE-2821")
+    def "test report task can handle test tasks that did not run tests"() {
+        given:
+        buildScript """
+            apply plugin: 'java'
+
+             $junitSetup
+
+            task otherTests(type: Test) {
+                binResultsDir file("bin")
+                testSrcDirs = []
+                testClassesDir = file("blah")
+            }
+
+            task testReport(type: TestReport) {
+                reportOn test, otherTests
+                destinationDir reporting.file("tr")
+            }
+        """
+
+        and:
+        testClass("Thing")
+
+        when:
+        succeeds "testReport"
+
+        then:
+        ":otherTests" in skippedTasks
+        ":test" in nonSkippedTasks
+        new HtmlTestExecutionResult(testDirectory, "build/reports/tr").assertTestClassesExecuted("Thing")
+    }
+
+    @Issue("http://issues.gradle.org//browse/GRADLE-2915")
+    def "test report task can handle tests tasks not having been executed"() {
+        when:
+        buildScript """
+            apply plugin: 'java'
+
+             $junitSetup
+
+            task testReport(type: TestReport) {
+                testResultDirs = [test.binResultsDir]
+                destinationDir reporting.file("tr")
+            }
+        """
+
+        and:
+        testClass("Thing")
+
+        then:
+        succeeds "testReport"
+        succeeds "testReport"
+    }
+
+    def "test report task is skipped when there are no results"() {
+        given:
+        buildScript """
+            apply plugin: 'java'
+
+            task testReport(type: TestReport) {
+                reportOn test
+                destinationDir reporting.file("tr")
+            }
+        """
+
+        when:
+        succeeds "testReport"
+
+        then:
+        ":test" in skippedTasks
+        ":testReport" in skippedTasks
+    }
+
+    @Unroll
+    "#type report files are considered outputs"() {
+        given:
+        buildScript """
+            $junitSetup
+        """
+
+        and:
+        testClass "SomeTest"
+
+        when:
+        run "test"
+
+        then:
+        ":test" in nonSkippedTasks
+        file(reportsDir).exists()
+
+        when:
+        run "test"
+
+        then:
+        ":test" in skippedTasks
+        file(reportsDir).exists()
+
+        when:
+        file(reportsDir).deleteDir()
+        run "test"
+
+        then:
+        ":test" in nonSkippedTasks
+        file(reportsDir).exists()
+
+        where:
+        type   | reportsDir
+        "xml"  | "build/test-results"
+        "html" | "build/reports/tests"
+    }
+
+    def "results or reports are linked to in error output"() {
+        given:
+        buildScript """
+            $junitSetup
+            test {
+                reports.all { it.enabled = true }
+            }
+        """
+
+        and:
+        failingTestClass "SomeTest"
+
+        when:
+        fails "test"
+
+        then:
+        ":test" in nonSkippedTasks
+        errorOutput.contains("See the report at: ")
+
+        when:
+        buildFile << "\ntest.reports.html.enabled = false\n"
+        fails "test"
+
+        then:
+        ":test" in nonSkippedTasks
+        errorOutput.contains("See the results at: ")
+
+        when:
+        buildFile << "\ntest.reports.junitXml.enabled = false\n"
+        fails "test"
+
+        then:
+        ":test" in nonSkippedTasks
+        errorOutput.contains("There were failing tests")
+        !errorOutput.contains("See the")
+    }
+
+
+    def "output per test case flag invalidates outputs"() {
+        when:
+        buildScript """
+            $junitSetup
+            test.reports.junitXml.outputPerTestCase = false
+        """
+        testClass "SomeTest"
+        succeeds "test"
+
+        then:
+        ":test" in nonSkippedTasks
+
+        when:
+        buildFile << "\ntest.reports.junitXml.outputPerTestCase = true\n"
+        succeeds "test"
+
+        then:
+        ":test" in nonSkippedTasks
+    }
+
+    def "outputs over lifecycle"() {
+        when:
+        buildScript """
+            $junitSetup
+            test.reports.junitXml.outputPerTestCase = true
+        """
+
+        file("src/test/java/OutputLifecycleTest.java") << """
+            import org.junit.*;
+
+            public class OutputLifecycleTest {
+
+                public OutputLifecycleTest() {
+                    System.out.println("constructor out");
+                    System.err.println("constructor err");
+                }
+
+                @BeforeClass
+                public static void beforeClass() {
+                    System.out.println("beforeClass out");
+                    System.err.println("beforeClass err");
+                }
+
+                @Before
+                public void beforeTest() {
+                    System.out.println("beforeTest out");
+                    System.err.println("beforeTest err");
+                }
+
+                @Test public void m1() {
+                    System.out.println("m1 out");
+                    System.err.println("m1 err");
+                }
+
+                @Test public void m2() {
+                    System.out.println("m2 out");
+                    System.err.println("m2 err");
+                }
+
+                @After
+                public void afterTest() {
+                    System.out.println("afterTest out");
+                    System.err.println("afterTest err");
+                }
+
+                @AfterClass
+                public static void afterClass() {
+                    System.out.println("afterClass out");
+                    System.err.println("afterClass err");
+                }
+            }
+        """
+
+        succeeds "test"
+
+        then:
+        def xmlReport = new JUnitXmlTestExecutionResult(testDirectory)
+        def clazz = xmlReport.testClass("OutputLifecycleTest")
+        clazz.assertTestCaseStderr("m1", is("beforeTest err\nm1 err\nafterTest err\n"))
+        clazz.assertTestCaseStderr("m2", is("beforeTest err\nm2 err\nafterTest err\n"))
+        clazz.assertTestCaseStdout("m1", is("beforeTest out\nm1 out\nafterTest out\n"))
+        clazz.assertTestCaseStdout("m2", is("beforeTest out\nm2 out\nafterTest out\n"))
+        clazz.assertStderr(is("beforeClass err\nconstructor err\nconstructor err\nafterClass err\n"))
+        clazz.assertStdout(is("beforeClass out\nconstructor out\nconstructor out\nafterClass out\n"))
+    }
+
+    String getJunitSetup() {
+        """
+        apply plugin: 'java'
+        repositories { mavenCentral() }
+        dependencies { testCompile 'junit:junit:4.11' }
+        """
+    }
+
+    String getTestFilePrelude() {
+        """
+package org.gradle.testing;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+"""
+    }
+    void failingTestClass(String name) {
+        testClass(name, true)
+    }
+
+    void testClass(String name, boolean failing = false) {
+        file("src/test/java/${name}.java") << """
+            public class $name {
+                @org.junit.Test
+                public void test() {
+                    assert false == ${failing};
+                }
+            }
+        """
+    }
+
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy
new file mode 100644
index 0000000..4bcec9f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing;
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue;
+
+public class TestTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2702")
+    def "should not resolve configurations when there are no tests"() {
+        buildFile << """
+            apply plugin: 'java'
+
+            configure([configurations.testRuntime, configurations.testCompile]) {
+                incoming.beforeResolve { assert false : "should not be resolved" }
+            }
+        """
+
+        when:
+        run("build")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "test task is skipped when there are no tests"() {
+        buildFile << "apply plugin: 'java'"
+        file("src/test/java/not_a_test.txt")
+
+        when:
+        run("build")
+
+        then:
+        result.assertTaskSkipped(":test")
+    }
+}
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 b32c75c..2acab66 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
@@ -15,10 +15,15 @@
  */
 package org.gradle.testing
 
+import org.apache.commons.lang.RandomStringUtils
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.hamcrest.Matchers
+import spock.lang.IgnoreIf
 import spock.lang.Issue
-import spock.lang.Timeout
 import spock.lang.Unroll
 
 /**
@@ -26,7 +31,6 @@ import spock.lang.Unroll
  */
 class TestingIntegrationTest extends AbstractIntegrationSpec {
 
-    @Timeout(30)
     @Issue("http://issues.gradle.org/browse/GRADLE-1948")
     def "test interrupting its own thread does not kill test execution"() {
         given:
@@ -54,6 +58,124 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         ":test" in nonSkippedTasks
     }
 
+    def "fails cleanly even if an exception is thrown that doesn't serialize cleanly"() {
+        given:
+        file('src/test/java/ExceptionTest.java') << """
+            import org.junit.*;
+            import java.io.*;
+
+            public class ExceptionTest {
+
+                static class BadlyBehavedException extends Exception {
+                    BadlyBehavedException() {
+                        super("Broken writeObject()");
+                    }
+
+                    private void writeObject(ObjectOutputStream os) throws IOException {
+                        throw new IOException("Failed strangely");
+                    }
+                }
+
+                @Test
+                public void testThrow() throws Throwable {
+                    throw new BadlyBehavedException();
+                }
+            }
+        """
+        file('build.gradle') << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.10' }
+        """
+
+        when:
+        runAndFail "test"
+
+        then:
+        failureHasCause "There were failing tests"
+
+        and:
+        def results = new DefaultTestExecutionResult(file("."))
+        results.assertTestClassesExecuted("ExceptionTest")
+        results.testClass("ExceptionTest").assertTestFailed("testThrow", Matchers.equalTo('ExceptionTest$BadlyBehavedException: Broken writeObject()'))
+    }
+
+    def "fails cleanly even if an exception is thrown that doesn't de-serialize cleanly"() {
+        given:
+
+        file('src/test/java/ExceptionTest.java') << """
+            import org.junit.*;
+            import java.io.*;
+
+            public class ExceptionTest {
+                static class BadlyBehavedException extends Exception {
+                    BadlyBehavedException() {
+                        super("Broken readObject()");
+                    }
+
+                    private void readObject(ObjectInputStream os) throws IOException {
+                        throw new IOException("Failed strangely");
+                    }
+                }
+
+                @Test
+                public void testThrow() throws Throwable {
+                    throw new BadlyBehavedException();
+                }
+            }
+        """
+        file('build.gradle') << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies {
+                testCompile 'junit:junit:4.10'
+            }
+        """
+
+        when:
+        // an exception was thrown so we should fail here
+        runAndFail "test"
+
+        then:
+        failureHasCause "There were failing tests"
+
+        and:
+        def results = new DefaultTestExecutionResult(file("."))
+        results.assertTestClassesExecuted("ExceptionTest")
+        results.testClass("ExceptionTest").assertTestFailed("testThrow", Matchers.equalTo('ExceptionTest$BadlyBehavedException: Broken readObject()'))
+    }
+
+    @IgnoreIf({ OperatingSystem.current().isWindows() })
+    def "can use long paths for working directory"() {
+        given:
+        // windows can handle a path up to 260 characters
+        // we create a path that is 260 +1 (offset + "/" + randompath)
+        def pathoffset = 260 - testDirectory.getAbsolutePath().length()
+        def alphanumeric = RandomStringUtils.randomAlphanumeric(pathoffset)
+        def testWorkingDir = testDirectory.createDir("$alphanumeric")
+
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile "junit:junit:4.11" }
+            test.workingDir = "${testWorkingDir.toURI()}"
+        """
+
+        and:
+        file("src/test/java/SomeTest.java") << """
+            import org.junit.*;
+
+            public class SomeTest {
+                @Test public void foo() {
+                    System.out.println(new java.io.File(".").getAbsolutePath());
+                }
+            }
+        """
+
+        expect:
+        succeeds "test"
+    }
+
     @Issue("http://issues.gradle.org/browse/GRADLE-2313")
     @Unroll
     "can clean test after extracting class file with #framework"() {
@@ -81,7 +203,6 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         "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:
@@ -126,7 +247,64 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         when:
         run "othertestsTest"
         then:
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("TestCaseExtendsAbstractClass")
     }
+
+    @Requires(TestPrecondition.JDK6_OR_LATER) // Guava 15 requires JDK 6
+    @Issue("http://issues.gradle.org/browse/GRADLE-2962")
+    def "incompatible user versions of classes that we also use don't affect test execution"() {
+
+        // These dependencies are quite particular.
+        // Both jars contain 'com.google.common.collect.ImmutableCollection'
+        // 'google-collections' contains '$EmptyImmutableCollection' which extends '$AbstractImmutableCollection' which is also in guava 15.
+        // In the google-collections version '$EmptyImmutableCollection' overrides `toArray()`.
+        // In guava 15, this method is final.
+        // This causes a verifier error when loading $EmptyImmutableCollection (can't override final method).
+
+        // Our test infrastructure loads org.gradle.util.SystemProperties, which depends on $EmptyImmutableCollection from guava 14.
+        // The below test is testing that out infrastructure doesn't throw a VerifyError while bootstrapping.
+        // This is testing classloader isolation, but this was not the real problem that triggered GRADLE-2962.
+        // The problem was that we tried to load the user's $EmptyImmutableCollection in a class loader structure we wouldn't have used anyway,
+        // but this caused the infrastructure to fail with an internal error because of the VerifyError.
+        // In a nutshell, this tests that we don't even try to load classes that are there, but that we shouldn't see.
+
+        when:
+        buildScript """
+            apply plugin: 'java'
+            repositories {
+                mavenCentral()
+            }
+            configurations { first {}; last {} }
+            dependencies {
+                // guarantee ordering
+                first 'com.google.guava:guava:15.0'
+                last 'com.google.collections:google-collections:1.0'
+                compile configurations.first + configurations.last
+
+                testCompile 'junit:junit:4.11'
+            }
+        """
+
+        and:
+        file("src/test/java/TestCase.java") << """
+            import org.junit.Test;
+            public class TestCase {
+                @Test
+                public void test() throws Exception {
+                    getClass().getClassLoader().loadClass("com.google.common.collect.ImmutableCollection\$EmptyImmutableCollection");
+                }
+            }
+        """
+
+        then:
+        fails "test"
+
+        and:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.testClass("TestCase").with {
+            assertTestFailed("test", Matchers.containsString("java.lang.VerifyError"))
+            assertTestFailed("test", Matchers.containsString("\$EmptyImmutableCollection"))
+        }
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest.groovy
new file mode 100644
index 0000000..17da4f0
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.cucumberjvm
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Issue
+
+class CucumberJVMReportIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final TestResources resources = new TestResources(temporaryFolder)
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2739")
+    @Requires(TestPrecondition.JDK6_OR_LATER)
+    def testReportingSupportsCucumberStepsWithSlashes() {
+        when:
+        run "test"
+        then:
+        ":test" in nonSkippedTasks
+        and:
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted("RunCukesTest", "Scenario: Say hello /two/three")
+        result.testClass("Scenario: Say hello /two/three").assertTestPassed("Given I have a hello app with Howdy and /four")
+        result.testClass("Scenario: Say hello /two/three").assertTestPassed("Then it should answer with Howdy World")
+        result.testClass("Scenario: Say hello /two/three").assertTestPassed("When I ask it to say hi and /five/six/seven")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/AbstractTestFilteringIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/AbstractTestFilteringIntegrationTest.groovy
new file mode 100644
index 0000000..8348ba4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/AbstractTestFilteringIntegrationTest.groovy
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.fixture
+
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+
+abstract class AbstractTestFilteringIntegrationTest extends MultiVersionIntegrationSpec {
+
+    protected String framework
+    protected String dependency
+    protected String imports
+
+    abstract void configureFramework()
+
+    void setup() {
+        configureFramework()
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile '$dependency:$org.gradle.integtests.fixtures.MultiVersionIntegrationSpec.version' }
+            test { use${framework}() }
+        """
+    }
+
+    def "executes single method from a test class"() {
+        buildFile << """
+            test {
+              filter {
+                includeTestsMatching "FooTest.pass"
+              }
+            }
+        """
+        file("src/test/java/FooTest.java") << """import $imports;
+            public class FooTest {
+                @Test public void pass() {}
+                @Test public void fail() { throw new RuntimeException("Boo!"); }
+            }
+        """
+        file("src/test/java/OtherTest.java") << """import $imports;
+            public class OtherTest {
+                @Test public void pass() {}
+                @Test public void fail() { throw new RuntimeException("Boo!"); }
+            }
+        """
+
+        when:
+        run("test")
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted("FooTest")
+        result.testClass("FooTest").assertTestsExecuted("pass")
+    }
+
+    def "executes multiple methods from a test class"() {
+        buildFile << """
+            test {
+              include 'FooTest*'
+              def cls = "FooTest"
+              filter {
+                includeTestsMatching "\${cls}.passOne" //make sure GStrings work
+                includeTestsMatching "\${cls}.passTwo"
+              }
+            }
+        """
+        file("src/test/java/FooTest.java") << """import $imports;
+            public class FooTest {
+                @Test public void passOne() {}
+                @Test public void passTwo() {}
+                @Test public void fail() { throw new RuntimeException("Boo!"); }
+            }
+        """
+        file("src/test/java/OtherTest.java") << """import $imports;
+            public class OtherTest {
+                @Test public void passOne() {}
+                @Test public void fail() { throw new RuntimeException("Boo!"); }
+            }
+        """
+
+        when:
+        run("test")
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted("FooTest")
+        result.testClass("FooTest").assertTestCount(2, 0, 0)
+    }
+
+    def "executes multiple methods from different classes"() {
+        buildFile << """
+            test {
+              filter.setIncludePatterns 'Foo*.pass*'
+            }
+        """
+        file("src/test/java/Foo1Test.java") << """import $imports;
+            public class Foo1Test {
+                @Test public void pass1() {}
+                @Test public void boo() {}
+            }
+        """
+        file("src/test/java/Foo2Test.java") << """import $imports;
+            public class Foo2Test {
+                @Test public void pass2() {}
+                @Test public void boo() {}
+            }
+        """
+        file("src/test/java/Foo3Test.java") << """import $imports;
+            public class Foo3Test {
+                @Test public void boo() {}
+            }
+        """
+        file("src/test/java/OtherTest.java") << """import $imports;
+            public class OtherTest {
+                @Test public void pass3() {}
+                @Test public void boo() {}
+            }
+        """
+
+        when:
+        run("test")
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted("Foo1Test", "Foo2Test")
+        result.testClass("Foo1Test").assertTestsExecuted("pass1")
+        result.testClass("Foo2Test").assertTestsExecuted("pass2")
+    }
+
+    def "reports when no matching methods found"() {
+        file("src/test/java/FooTest.java") << """import $imports;
+            public class FooTest {
+                @Test public void pass() {}
+            }
+        """
+
+        //by command line
+        when: fails("test", "--tests", 'FooTest.missingMethod')
+        then: failure.assertHasCause("No tests found for given includes: [FooTest.missingMethod]")
+
+        //by build script
+        when:
+        buildFile << "test.filter.includeTestsMatching 'FooTest.missingMethod'"
+        fails("test")
+        then: failure.assertHasCause("No tests found for given includes: [FooTest.missingMethod]")
+    }
+
+    def "task is out of date when included methods change"() {
+        buildFile << """
+            test {
+              filter.includeTestsMatching 'FooTest.pass'
+            }
+        """
+        file("src/test/java/FooTest.java") << """import $imports;
+            public class FooTest {
+                @Test public void pass() {}
+                @Test public void pass2() {}
+            }
+        """
+
+        when: run("test")
+        then: new DefaultTestExecutionResult(testDirectory).testClass("FooTest").assertTestsExecuted("pass")
+
+        when: run("test")
+        then: result.skippedTasks.contains(":test") //up-to-date
+
+        when:
+        run("test", "--tests", "FooTest.pass*")
+
+        then:
+        !result.skippedTasks.contains(":test")
+        new DefaultTestExecutionResult(testDirectory).testClass("FooTest").assertTestsExecuted("pass", "pass2")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy
new file mode 100644
index 0000000..c7d738b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.fixture;
+
+class JUnitCoverage {
+    final static String NEWEST = '4.11'
+    final static String[] LARGE_COVERAGE = ['4.0', '4.4', '4.8.2', NEWEST]
+    final static String[] STANDARD_COVERAGE = ['4.4', NEWEST]
+    final static String[] ASSUMPTIONS = ['4.5', NEWEST]
+    final static String[] CATEGORIES = ['4.8', NEWEST]
+    final static String[] FILTERING = ['4.6', NEWEST]
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/TestNGCoverage.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/TestNGCoverage.groovy
new file mode 100644
index 0000000..b1d756b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/fixture/TestNGCoverage.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.fixture;
+
+class TestNGCoverage {
+    final static String NEWEST = '6.8.7'
+    final static String[] STANDARD_COVERAGE = ['5.14.10', NEWEST]
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest.groovy
new file mode 100644
index 0000000..7984559
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.testing.fixture.JUnitCoverage
+import org.junit.Rule
+
+ at TargetCoverage({JUnitCoverage.ASSUMPTIONS})
+public class JUnitAssumptionsIntegrationTest extends MultiVersionIntegrationSpec {
+
+    @Rule TestResources resources = new TestResources(temporaryFolder)
+
+    def supportsAssumptions() {
+        executer.noExtraLogging() //TODO SF check if this is still needed (and in other tests, too)
+        buildFile << "dependencies { testCompile 'junit:junit:$version' }"
+
+        when:
+        executer.withTasks('check').run()
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.TestWithAssumptions')
+        result.testClass('org.gradle.TestWithAssumptions')
+                .assertTestCount(2, 0, 0)
+                .assertTestsExecuted('assumptionSucceeded')
+                .assertTestPassed('assumptionSucceeded')
+                .assertTestsSkipped('assumptionFailed')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy
new file mode 100644
index 0000000..38aa186
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.testing.fixture.JUnitCoverage
+import org.junit.Rule
+
+ at TargetCoverage({JUnitCoverage.CATEGORIES})
+public class JUnitCategoriesCoverageIntegrationSpec extends MultiVersionIntegrationSpec {
+
+    @Rule TestResources resources = new TestResources(temporaryFolder)
+
+    def setup() {
+        executer.noExtraLogging()
+    }
+
+    def configureJUnit() {
+        buildFile << "dependencies { testCompile 'junit:junit:$version' }"
+    }
+
+    def canSpecifyIncludeAndExcludeCategories() {
+        configureJUnit()
+
+        when:
+        run('test')
+
+        then:
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.CatATests', 'org.gradle.CatBTests', 'org.gradle.CatADTests', 'org.gradle.MixedTests')
+        result.testClass("org.gradle.CatATests").assertTestCount(4, 0, 0)
+        result.testClass("org.gradle.CatATests").assertTestsExecuted('catAOk1', 'catAOk2', 'catAOk3', 'catAOk4')
+        result.testClass("org.gradle.CatBTests").assertTestCount(4, 0, 0)
+        result.testClass("org.gradle.CatBTests").assertTestsExecuted('catBOk1', 'catBOk2', 'catBOk3', 'catBOk4')
+        result.testClass("org.gradle.CatADTests").assertTestCount(2, 0, 0)
+        result.testClass("org.gradle.CatADTests").assertTestsExecuted('catAOk1', 'catAOk2')
+        result.testClass("org.gradle.MixedTests").assertTestCount(3, 0, 0)
+        result.testClass("org.gradle.MixedTests").assertTestsExecuted('catAOk1', 'catBOk2')
+        result.testClass("org.gradle.MixedTests").assertTestsSkipped('someIgnoredTest')
+    }
+
+    def canSpecifyExcludesOnly() {
+        configureJUnit()
+
+        when:
+        run('test')
+
+        then:
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.NoCatTests', 'org.gradle.SomeTests', 'org.gradle.SomeOtherCatTests')
+        result.testClass("org.gradle.SomeOtherCatTests").assertTestCount(2, 0, 0)
+        result.testClass("org.gradle.SomeOtherCatTests").assertTestsExecuted('someOtherOk1', 'someOtherOk2')
+        result.testClass("org.gradle.NoCatTests").assertTestCount(2, 0, 0)
+        result.testClass("org.gradle.NoCatTests").assertTestsExecuted('noCatOk1', 'noCatOk2')
+        result.testClass("org.gradle.SomeTests").assertTestCount(3, 0, 0)
+        result.testClass("org.gradle.SomeTests").assertTestsExecuted('noCatOk3', 'noCatOk4', 'someOtherCatOk2')
+    }
+
+    def canCombineCategoriesWithCustomRunner() {
+        configureJUnit()
+
+        when:
+        run('test')
+
+        then:
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.SomeLocaleTests')
+        result.testClass("org.gradle.SomeLocaleTests").assertTestCount(3, 0, 0)
+        result.testClass("org.gradle.SomeLocaleTests").assertTestsExecuted('ok1 [de]', 'ok1 [en]', 'ok1 [fr]')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec.groovy
new file mode 100644
index 0000000..a12334f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+import static org.hamcrest.Matchers.startsWith
+
+public class JUnitCategoriesIntegrationSpec extends AbstractIntegrationSpec {
+
+    @Rule TestResources resources = new TestResources(temporaryFolder)
+
+    def reportsUnloadableExcludeCategory() {
+        given:
+        resources.maybeCopy("JUnitCategoriesIntegrationSpec/reportsUnloadableCategories")
+        buildFile << "test.useJUnit { excludeCategories 'org.gradle.CategoryA' }"
+
+        when:
+        fails("test")
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.SomeTestClass')
+        result.testClass("org.gradle.SomeTestClass").assertTestCount(1, 1, 0)
+        result.testClass("org.gradle.SomeTestClass").assertTestFailed("initializationError", startsWith("org.gradle.api.InvalidUserDataException: Can't load category class [org.gradle.CategoryA]"))
+    }
+
+    def reportsUnloadableIncludeCategory() {
+        given:
+        resources.maybeCopy("JUnitCategoriesIntegrationSpec/reportsUnloadableCategories")
+        buildFile << "test.useJUnit { excludeCategories 'org.gradle.CategoryA' }"
+
+        when:
+        fails('test')
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.SomeTestClass')
+        result.testClass("org.gradle.SomeTestClass").assertTestCount(1, 1, 0)
+        result.testClass("org.gradle.SomeTestClass").assertTestFailed("initializationError", startsWith("org.gradle.api.InvalidUserDataException: Can't load category class [org.gradle.CategoryA]"))
+    }
+
+    def testTaskFailsIfCategoriesNotSupported() {
+        when: fails('test')
+        then: failure.error.contains("JUnit Categories defined but declared JUnit version does not support Categories.")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
deleted file mode 100644
index 2a5e8ae..0000000
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
+++ /dev/null
@@ -1,61 +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.testing.junit
-
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
-import org.gradle.integtests.fixtures.TargetVersions
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-import org.junit.Test
-
- at TargetVersions(['4,0', '4.4', '4.8.2', '4.11'])
-class JUnitCrossVersionIntegrationSpec extends MultiVersionIntegrationSpec {
-    @Rule
-    public final TestResources resources = new TestResources(temporaryFolder)
-
-
-    String junitDependency = "junit:junit:$version"
-
-    @Test
-    public void canRunTestsUsingJUnit() {
-        given:
-        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
-
-        configureJUnit();
-        when:
-        executer.withTasks('check').run()
-        then:
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest', 'org.gradle.CustomIgnoredTest')
-        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', 'assumptionFailed')
-        result.testClass('org.gradle.IgnoredTest').assertTestsSkipped('testIgnored')
-        result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
-    }
-
-    private void configureJUnit() {
-        buildFile << """
-        dependencies {
-        testCompile '${junitDependency.toString()}'
-        }"""
-    }
-}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitFilteringIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitFilteringIntegrationTest.groovy
new file mode 100644
index 0000000..17850c0
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitFilteringIntegrationTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.testing.fixture.AbstractTestFilteringIntegrationTest
+import org.gradle.testing.fixture.JUnitCoverage
+
+ at TargetCoverage({JUnitCoverage.FILTERING})
+public class JUnitFilteringIntegrationTest extends AbstractTestFilteringIntegrationTest {
+
+    void configureFramework() {
+        framework = "JUnit"
+        dependency = "junit:junit"
+        imports = "org.junit.*"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitFilteringSupportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitFilteringSupportIntegrationTest.groovy
new file mode 100644
index 0000000..e949b6e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitFilteringSupportIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+public class JUnitFilteringSupportIntegrationTest extends AbstractIntegrationSpec {
+
+    void "informs that we dont support filtering for lower versions of JUnit 4.x"() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.5' }
+            test.filter.includeTestsMatching "FooTest.pass"
+        """
+
+        file("src/test/java/FooTest.java") << """import org.junit.*;
+        public class FooTest {
+            @Test public void pass() {}
+        }
+        """
+
+        when: fails("test")
+
+        then:
+        failure.error.contains("Test filtering is not supported for given version of JUnit. Please upgrade JUnit version to at least 4.6.")
+    }
+
+    void "informs that we dont support filtering for JUnit 3.x"() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:3.8.1' }
+            test.filter.includeTestsMatching "FooTest.pass"
+        """
+
+        file("src/test/java/FooTest.java") << """
+            public class FooTest {
+                public void testPass() {}
+            }
+        """
+
+        when: fails("test")
+
+        then: failure.error.contains("Test filtering is not supported for given version of JUnit. Please upgrade JUnit version to at least 4.6.")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec.groovy
new file mode 100644
index 0000000..13e67c7
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.testing.fixture.JUnitCoverage
+import org.junit.Rule
+
+ at TargetCoverage({JUnitCoverage.STANDARD_COVERAGE})
+class JUnitIgnoreClassMultiVersionIntegrationSpec extends MultiVersionIntegrationSpec {
+
+    @Rule TestResources resources = new TestResources(temporaryFolder)
+
+    def canHandleClassLevelIgnoredTests() {
+        executer.noExtraLogging()
+        buildFile << """
+            dependencies { testCompile 'junit:junit:$version' }
+        """
+
+        when:
+        run('check')
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.IgnoredTest', 'org.gradle.CustomIgnoredTest')
+        result.testClass('org.gradle.IgnoredTest').assertTestCount(1, 0, 0).assertTestsSkipped("testIgnored")
+        result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
+    }
+}
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 698e91d..001b498 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
@@ -98,27 +98,6 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
     }
 
     @Test
-    public void canRunMixOfJunit3And4Tests() {
-        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
-        executer.withTasks('check').run()
-
-        def result = new DefaultTestExecutionResult(testDirectory)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest', 'org.gradle.CustomIgnoredTest')
-        result.testClass('org.gradle.Junit3Test')
-                .assertTestCount(1, 0, 0)
-                .assertTestsExecuted('testRenamesItself')
-                .assertTestPassed('testRenamesItself')
-        result.testClass('org.gradle.Junit4Test')
-                .assertTestCount(3, 0, 0)
-                .assertTestsExecuted('ok')
-                .assertTestPassed('ok')
-                .assertTestsSkipped('broken', 'assumptionFailed')
-        result.testClass('org.gradle.IgnoredTest').assertTestCount(1, 0, 0).assertTestsSkipped("testIgnored")
-        result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
-    }
-
-    @Test
     public void canRunTestsUsingJUnit3() {
         resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
         executer.withTasks('check').run()
@@ -359,8 +338,8 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         assert containsLine(result.getOutput(), "START [tests] [Test Run]");
         assert containsLine(result.getOutput(), "FINISH [tests] [Test Run] [FAILURE] [4]");
 
-        assert containsLine(result.getOutput(), "START [test process 'Gradle Worker 1'] [Gradle Worker 1]");
-        assert containsLine(result.getOutput(), "FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1] [FAILURE] [4]");
+        assert containsLine(result.getOutput(), "START [process 'Gradle Test Executor 1'] [Gradle Test Executor 1]");
+        assert containsLine(result.getOutput(), "FINISH [process 'Gradle Test Executor 1'] [Gradle Test Executor 1] [FAILURE] [4]");
 
         assert containsLine(result.getOutput(), "START [test class SomeOtherTest] [SomeOtherTest]");
         assert containsLine(result.getOutput(), "FINISH [test class SomeOtherTest] [SomeOtherTest] [SUCCESS] [1]");
@@ -467,4 +446,6 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         result.testClass("org.gradle.SomeSuite").assertStderr(containsString("stderr in TestSetup#setup"))
         result.testClass("org.gradle.SomeSuite").assertStderr(containsString("stderr in TestSetup#teardown"))
     }
+
+
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
index 46c3f4f..9055cf2 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.testing.junit
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.TextUtil
@@ -76,8 +76,7 @@ org.gradle.JUnit4StandardOutputTest > printTest STANDARD_OUT
         """)
     }
 
-    @Test
-    void "test logging is included in XML results"() {
+    def "test logging is included in XML results"() {
         file("build.gradle") << """
             apply plugin: 'java'
                 repositories { mavenCentral() }
@@ -104,7 +103,7 @@ public class EncodingTest {
         executer.withTasks("test").runWithFailure()
 
         then:
-        new JUnitXmlTestExecutionResult(testDirectory)
+        new DefaultTestExecutionResult(testDirectory)
                 .testClass("EncodingTest")
                 .assertTestPassed("encodesCdata")
                 .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]>'))
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitMultiVersionIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitMultiVersionIntegrationSpec.groovy
new file mode 100644
index 0000000..ac11e95
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitMultiVersionIntegrationSpec.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.testing.junit
+
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.testing.fixture.JUnitCoverage
+import org.junit.Rule
+
+ at TargetCoverage({ JUnitCoverage.LARGE_COVERAGE })
+class JUnitMultiVersionIntegrationSpec extends MultiVersionIntegrationSpec {
+
+    @Rule TestResources resources = new TestResources(temporaryFolder)
+
+    def canRunTestsUsingJUnit() {
+        given:
+        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
+        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
+
+        buildFile << "dependencies { testCompile 'junit:junit:$version' }"
+
+        when:
+        run('check')
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test')
+        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')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitTestFilteringSamplesIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitTestFilteringSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..74f1f10
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitTestFilteringSamplesIntegrationTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+
+public class JUnitTestFilteringSamplesIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule Sample sample = new Sample(temporaryFolder, 'testing/filtering')
+
+    def "uses test filter"() {
+        when:
+        inDirectory(sample.dir)
+        run("test")
+
+        then:
+        def result = new DefaultTestExecutionResult(sample.dir)
+        result.assertTestClassesExecuted("SomeIntegTest", "SomeOtherTest")
+        result.testClass("SomeIntegTest").assertTestsExecuted("test1", "test2")
+        result.testClass("SomeOtherTest").assertTestsExecuted("quickUiCheck")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
index 2d40e17..ee491cd 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.integtests.fixtures.*
 import org.junit.Rule
 import org.junit.Test
 
-/**
- * @author Tom Eyckmans
- */
 public class SampleTestNGIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider)
@@ -30,7 +27,7 @@ public class SampleTestNGIntegrationTest extends AbstractIntegrationTest {
     public void suiteXmlBuilder() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
-        def result = new JUnitXmlTestExecutionResult(sample.dir)
+        def result = new DefaultTestExecutionResult(sample.dir)
         result.assertTestClassesExecuted('org.gradle.testng.UserImplTest')
         result.testClass('org.gradle.testng.UserImplTest').assertTestsExecuted('testOkFirstName')
         result.testClass('org.gradle.testng.UserImplTest').assertTestPassed('testOkFirstName')
@@ -40,7 +37,7 @@ public class SampleTestNGIntegrationTest extends AbstractIntegrationTest {
     public void javaJdk14Passing() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
-        def result = new JUnitXmlTestExecutionResult(sample.dir)
+        def result = new DefaultTestExecutionResult(sample.dir)
         result.assertTestClassesExecuted('org.gradle.OkTest')
         result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGFilteringIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGFilteringIntegrationTest.groovy
new file mode 100644
index 0000000..d3b904c
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGFilteringIntegrationTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.TargetCoverage
+import org.gradle.testing.fixture.AbstractTestFilteringIntegrationTest
+import org.gradle.testing.fixture.TestNGCoverage
+
+ at TargetCoverage({TestNGCoverage.STANDARD_COVERAGE})
+public class TestNGFilteringIntegrationTest extends AbstractTestFilteringIntegrationTest {
+
+    void configureFramework() {
+        framework = "TestNG"
+        dependency = "org.testng:testng"
+        imports = "org.testng.annotations.*"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationProject.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationProject.groovy
deleted file mode 100644
index e9d9a53..0000000
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationProject.groovy
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.testing.testng
-
-import org.gradle.integtests.fixtures.TestNGExecutionResult
-
-/**
- * @author Tom Eyckmans
- */
-
-public class TestNGIntegrationProject {
-    String name
-    boolean expectFailure
-    Closure assertClosure
-
-    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, null, assertClosure)
-    }
-
-    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, nameSuffix, assertClosure)
-    }
-
-    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, null, assertClosure)
-    }
-
-    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, nameSuffix, assertClosure)
-    }
-
-    public TestNGIntegrationProject(String name, boolean expectFailure, String nameSuffix, assertClosure)
-    {
-        if ( nameSuffix == null ) {
-            this.name = name
-        } else {
-            this.name = name + nameSuffix
-        }
-        this.expectFailure = expectFailure
-        this.assertClosure = assertClosure
-    }
-
-    void doAssert(projectDir, result) {
-        if (assertClosure.maximumNumberOfParameters == 3) {
-            assertClosure(name, projectDir, new TestNGExecutionResult(projectDir))
-        } else {
-            assertClosure(name, projectDir, new TestNGExecutionResult(projectDir), result)
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
index 5b315f7..de12170 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestNGExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionResult
@@ -29,9 +29,6 @@ import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
-/**
- * @author Tom Eyckmans
- */
 class TestNGIntegrationTest extends AbstractIntegrationTest {
 
     @Rule public TestResources resources = new TestResources(testDirectoryProvider)
@@ -49,7 +46,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
         assertThat(result.error, not(containsString('stderr')))
         assertThat(result.error, not(containsString('a warning')))
 
-        new JUnitXmlTestExecutionResult(testDirectory).testClass('org.gradle.OkTest').assertTestPassed('ok')
+        new DefaultTestExecutionResult(testDirectory).testClass('org.gradle.OkTest').assertTestPassed('ok')
     }
 
     @Test
@@ -58,8 +55,8 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
 
         assert containsLine(result.getOutput(), "START [tests] [Test Run]");
         assert containsLine(result.getOutput(), "FINISH [tests] [Test Run]");
-        assert containsLine(result.getOutput(), "START [test process 'Gradle Worker 1'] [Gradle Worker 1]");
-        assert containsLine(result.getOutput(), "FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1]");
+        assert containsLine(result.getOutput(), "START [process 'Gradle Test Executor 1'] [Gradle Test Executor 1]");
+        assert containsLine(result.getOutput(), "FINISH [process 'Gradle Test Executor 1'] [Gradle Test Executor 1]");
         assert containsLine(result.getOutput(), "START [test 'Gradle test'] [Gradle test]");
         assert containsLine(result.getOutput(), "FINISH [test 'Gradle test'] [Gradle test]");
         assert containsLine(result.getOutput(), "START [test method pass(SomeTest)] [pass]");
@@ -76,7 +73,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     void groovyJdk15Failing() {
         executer.withTasks("test").runWithFailure().assertTestsFailed()
 
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest')
         result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
     }
@@ -85,7 +82,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     void groovyJdk15Passing() {
         executer.withTasks("test").run()
 
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.OkTest')
         result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
     }
@@ -94,7 +91,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     void javaJdk14Failing() {
         executer.withTasks("test").runWithFailure().assertTestsFailed()
 
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest')
         result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
     }
@@ -109,7 +106,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     private void doJavaJdk15Failing(String testNGVersion) {
         executer.withTasks("test").withArguments("-PtestNGVersion=$testNGVersion").runWithFailure().assertTestsFailed()
 
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest', 'org.gradle.TestWithBrokenSetup', 'org.gradle.BrokenAfterSuite', 'org.gradle.TestWithBrokenMethodDependency')
         result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
         result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('java.lang.RuntimeException: broken'))
@@ -160,7 +157,7 @@ test {
     @Test
     void supportsTestGroups() {
         executer.withTasks("test").run()
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.groups.SomeTest')
         result.testClass('org.gradle.groups.SomeTest').assertTestsExecuted("databaseTest")
     }
@@ -168,7 +165,7 @@ test {
     @Test
     void supportsTestFactory() {
         executer.withTasks("test").run()
-        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.factory.FactoryTest')
         result.testClass('org.gradle.factory.FactoryTest').assertTestCount(2, 0, 0)
         result.testClass('org.gradle.factory.FactoryTest').assertStdout(containsString('TestingFirst'))
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
index 10246a1..4eee33c 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
@@ -17,19 +17,70 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.TextUtil
-import org.junit.Rule
 
 // can make assumptions about order in which test methods of TestNGTest get executed
 // because the methods are chained with 'methodDependsOn'
 class TestNGLoggingIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources resources = new TestResources(temporaryFolder)
-    ExecutionResult result
 
     def setup() {
         executer.noExtraLogging().withStackTraceChecksDisabled().withTasks("test")
+
+        buildFile << """
+            apply plugin: "groovy"
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "org.codehaus.groovy:groovy-all:2.0.5"
+                testCompile "org.testng:testng:6.3.1"
+            }
+
+            test {
+                useTestNG()
+                testLogging {
+                    quiet {
+                        events "skipped", "failed"
+                        minGranularity 2
+                        maxGranularity -1
+                        displayGranularity 3
+                        exceptionFormat "full"
+                        stackTraceFilters "truncate", "groovy"
+                    }
+                }
+            }
+        """
+
+        file("src/test/groovy/org/gradle/TestNGTest.groovy") << """
+            package org.gradle
+
+            import org.testng.annotations.Test
+
+            class TestNGTest {
+                @Test
+                void goodTest() {}
+
+                @Test(dependsOnMethods = ["goodTest"])
+                void badTest() {
+                    beBad()
+                }
+
+                @Test(dependsOnMethods = ["badTest"])
+                void ignoredTest() {}
+
+                @Test(dependsOnMethods = ["goodTest"])
+                void printTest() {
+                    println "line 1\\nline 2"
+                    println "line 3"
+                }
+
+                private beBad() {
+                    throw new RuntimeException("bad")
+                }
+            }
+        """
     }
 
     def "defaultLifecycleLogging"() {
@@ -39,7 +90,7 @@ class TestNGLoggingIntegrationTest extends AbstractIntegrationSpec {
         then:
         outputContains("""
 Gradle test > org.gradle.TestNGTest.badTest FAILED
-    java.lang.RuntimeException at TestNGTest.groovy:40
+    java.lang.RuntimeException at TestNGTest.groovy:25
         """)
     }
 
@@ -51,8 +102,8 @@ Gradle test > org.gradle.TestNGTest.badTest FAILED
         outputContains("""
 org.gradle.TestNGTest.badTest FAILED
     java.lang.RuntimeException: bad
-        at org.gradle.TestNGTest.beBad(TestNGTest.groovy:40)
-        at org.gradle.TestNGTest.badTest(TestNGTest.groovy:27)
+        at org.gradle.TestNGTest.beBad(TestNGTest.groovy:25)
+        at org.gradle.TestNGTest.badTest(TestNGTest.groovy:12)
 
 org.gradle.TestNGTest.ignoredTest SKIPPED
 
@@ -61,6 +112,44 @@ Gradle test FAILED
     }
 
     def "standardOutputLogging"() {
+        given:
+        buildFile.text = """
+            apply plugin: "groovy"
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "org.codehaus.groovy:groovy-all:2.0.5"
+                testCompile "org.testng:testng:6.3.1"
+            }
+
+            test {
+                useTestNG()
+                testLogging {
+                    quiet {
+                        events "standardOut", "standardError"
+                    }
+                }
+            }
+        """
+
+        and:
+        file("src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy") << """
+            package org.gradle
+
+            import org.testng.annotations.Test
+
+            class TestNGStandardOutputTest {
+                @Test
+                void printTest() {
+                    println "line 1\\nline 2"
+                    println "line 3"
+                }
+            }
+        """
+
         when:
         result = executer.withArguments("-q").runWithFailure()
 
@@ -76,4 +165,6 @@ Gradle test > org.gradle.TestNGStandardOutputTest.printTest STANDARD_OUT
     private void outputContains(String text) {
         assert result.output.contains(TextUtil.toPlatformLineSeparators(text.trim()))
     }
+
+
 }
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
index dcd8ee0..8163c0a 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
@@ -22,6 +22,7 @@ package org.gradle.testing.testng
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestNGExecutionResult
+import spock.lang.Unroll
 
 public class TestNGProducesOldReportsIntegrationTest extends AbstractIntegrationSpec {
     def setup() {
@@ -50,7 +51,7 @@ repositories { mavenCentral() }
 dependencies { testCompile 'org.testng:testng:6.3.1' }
 
 test {
-    testReport = false
+    reports.html.enabled = false
     useTestNG()
 }
 """
@@ -62,7 +63,8 @@ test {
         new JUnitXmlTestExecutionResult(file(".")).hasJUnitXmlResults()
     }
 
-    def "can generate the old xml reports"() {
+    @Unroll
+    "can generate the old xml reports"() {
         given:
         file("src/test/java/org/SomeTest.java") << """package org;
 import org.testng.annotations.*;
@@ -77,21 +79,31 @@ apply plugin: 'java'
 repositories { mavenCentral() }
 dependencies { testCompile 'org.testng:testng:6.3.1' }
 test {
-  testReport = false
+  reports.html.enabled = false
+  $preConfig
   useTestNG(){
     useDefaultListeners = true
   }
+  $postConfig
 }
 """
         when:
-        executer.withTasks('test').run()
+        executer.withDeprecationChecksDisabled().withTasks('test').run()
 
         then:
         new JUnitXmlTestExecutionResult(file(".")).hasJUnitXmlResults()
 
-        def testNG = new TestNGExecutionResult(file("."))
+        def testNG = new TestNGExecutionResult(file("."), path)
         testNG.hasTestNGXmlResults()
         testNG.hasJUnitResultsGeneratedByTestNG()
         testNG.hasHtmlResults()
+
+        where:
+        preConfig                                | postConfig                                                                           | path
+        ""                                       | ""                                                                                   | TestNGExecutionResult.DEFAULT_TESTNG_REPORT
+        "testReportDir = file('xyz')"            | "reports.html.destination = file('abc')"                                             | "abc"
+        ""                                       | "testReportDir = file('xyz');reports.html.destination = file('abc')"                 | "abc"
+        "reports.html.destination = file('abc')" | "options.outputDirectory = file('xyz')"                                              | "xyz"
+        ""                                       | "options.outputDirectory = file('xyz');reports.html.destination = file('ignore me')" | "xyz"
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGSuiteInitialisationIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGSuiteInitialisationIntegrationTest.groovy
new file mode 100644
index 0000000..02ae8c5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGSuiteInitialisationIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.testng
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import spock.lang.Issue
+
+import static org.hamcrest.Matchers.startsWith
+
+class TestNGSuiteInitialisationIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-1710")
+    def "reports suite fatal failure"() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies {
+                testCompile "org.testng:testng:6.3.1"
+            }
+            test.useTestNG()
+        """
+        file("src/test/java/FooTest.java") << """
+import org.testng.annotations.*;
+
+public class FooTest {
+    public FooTest() { throw new NullPointerException(); }
+    @Test public void foo() {}
+}
+"""
+
+        expect:
+        fails("test")
+
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.testClass("Gradle Test Executor 1").assertTestFailed("execution failure",
+                startsWith("org.gradle.api.internal.tasks.testing.TestSuiteExecutionException"))
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGSuiteIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGSuiteIntegrationTest.groovy
new file mode 100644
index 0000000..ff2d8a5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGSuiteIntegrationTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.testing.fixture.TestNGCoverage
+import spock.lang.Issue
+
+ at TargetCoverage({TestNGCoverage.STANDARD_COVERAGE})
+public class TestNGSuiteIntegrationTest extends MultiVersionIntegrationSpec {
+
+    @Issue("GRADLE-3020")
+    def "can specify test suite by string"() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies {
+                testCompile 'org.testng:testng:$version'
+            }
+            test {
+              useTestNG {
+                suites "suite.xml"
+              }
+            }
+        """
+
+        file("src/test/java/FooTest.java") << """
+            public class FooTest {
+                @org.testng.annotations.Test public void pass() {}
+            }
+        """
+        file("src/test/java/BarTest.java") << """
+            public class BarTest {
+                @org.testng.annotations.Test public void pass() {}
+            }
+        """
+        file("src/test/java/BazTest.java") << """
+            public class BazTest {
+                @org.testng.annotations.Test public void pass() {}
+            }
+        """
+
+        file("suite.xml") << """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="AwesomeSuite" parallel="tests">
+  <test name="AwesomeTest" preserve-order="false">
+    <classes>
+      <class name="FooTest"/>
+      <class name="BarTest"/>
+    </classes>
+  </test>
+</suite>"""
+
+        when: run("test")
+        then: new DefaultTestExecutionResult(testDirectory).assertTestClassesExecuted('FooTest', 'BarTest')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy
index 28e1974..04ebfc5 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy
@@ -17,48 +17,75 @@
 
 package org.gradle.testing.testng
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.HtmlTestExecutionResult
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
-import org.gradle.integtests.fixtures.TestExecutionResult
+import org.gradle.integtests.fixtures.*
+import spock.lang.Shared
+import spock.lang.Unroll
 
+import static org.gradle.integtests.fixtures.TestResultOutputAssociation.WITH_SUITE
+import static org.gradle.integtests.fixtures.TestResultOutputAssociation.WITH_TESTCASE
 import static org.hamcrest.Matchers.*
 
+ at Unroll
 public class TestNGXmlResultAndHtmlReportIntegrationTest extends
         AbstractIntegrationSpec {
 
+    static class Mode {
+        String name
+        TestResultOutputAssociation outputAssociation
+        String config
+    }
+
+    @Shared Mode outputPerTestCase = new Mode(name: "output-per-testcase", outputAssociation: WITH_TESTCASE, config: "reports.junitXml.outputPerTestCase true")
+    @Shared Mode outputAtSuite = new Mode(name: "output-at-suite", outputAssociation: WITH_SUITE, config: "reports.junitXml.outputPerTestCase false")
+
+    @Shared List<Mode> modes = [outputAtSuite, outputPerTestCase]
+
     def setup() {
         executer.noExtraLogging()
         setupTestCases()
     }
 
-    def "produces JUnit xml results"() {
+    def "produces JUnit xml results - #mode.name"() {
         when:
-        runWithTestConfig("useTestNG()")
+        runWithTestConfig("useTestNG(); $mode.config")
+
         then:
-        verifyTestResultWith(new JUnitXmlTestExecutionResult(file(".")))
-        verifyTestResultWith(new HtmlTestExecutionResult(file(".")))
+        verify(mode)
+
+        where:
+        mode << modes
     }
 
-    def "produces JUnit xml results when running tests in parallel"() {
+    def "produces JUnit xml results when running tests in parallel - #mode.name"() {
         when:
-        runWithTestConfig("useTestNG(); maxParallelForks 2")
+        runWithTestConfig("useTestNG(); maxParallelForks 2; $mode.config")
+
         then:
-        verifyTestResultWith(new JUnitXmlTestExecutionResult(file(".")))
-        verifyTestResultWith(new HtmlTestExecutionResult(file(".")))
+        verify(mode)
+
+        where:
+        mode << modes
     }
 
-    def "produces JUnit xml results with aggressive forking"() {
+    def "produces JUnit xml results with aggressive forking - #mode.name"() {
         when:
-        runWithTestConfig("useTestNG(); forkEvery 1")
+        runWithTestConfig("useTestNG(); forkEvery 1; $mode.config")
+
         then:
-        verifyTestResultWith(new JUnitXmlTestExecutionResult(file(".")))
-        verifyTestResultWith(new HtmlTestExecutionResult(file(".")))
+        verify(mode)
+
+        where:
+        mode << modes
+    }
+
+    void verify(Mode mode) {
+        verifyTestResultWith(new JUnitXmlTestExecutionResult(file("."), mode.outputAssociation), mode.outputAssociation)
+        verifyTestResultWith(new HtmlTestExecutionResult(file(".")), mode.outputAssociation)
     }
 
     def runWithTestConfig(String testConfiguration) {
         def buildFile = file('build.gradle')
-        buildFile << """
+        buildFile.text = """
             apply plugin: 'java'
             repositories { mavenCentral() }
             dependencies { testCompile 'org.testng:testng:6.3.1' }
@@ -71,10 +98,10 @@ public class TestNGXmlResultAndHtmlReportIntegrationTest extends
         executer.withTasks('test').runWithFailure().assertTestsFailed()
     }
 
-    def verifyTestResultWith(TestExecutionResult executionResult) {
-        executionResult.assertTestClassesExecuted("org.FailingTest", "org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest", "org.EncodingTest")
+    def verifyTestResultWith(TestExecutionResult executionResult, TestResultOutputAssociation outputAssociation) {
+        executionResult.assertTestClassesExecuted("org.FailingTest", "org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest", "org.EncodingTest", "org.ParameterizedTest", "org.OutputLifecycleTest")
 
-        executionResult.testClass("org.MixedMethodsTest")
+        def mixedMethods = executionResult.testClass("org.MixedMethodsTest")
                 .assertTestCount(4, 2, 0)
                 .assertTestsExecuted("passing", "passing2", "failing", "failing2")
                 .assertTestFailed("failing", equalTo('java.lang.AssertionError: failing!'))
@@ -82,40 +109,140 @@ public class TestNGXmlResultAndHtmlReportIntegrationTest extends
                 .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.")))
 
-        executionResult.testClass("org.PassingTest")
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            mixedMethods
+                    .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.")))
+        } else {
+            mixedMethods
+                    .assertTestCaseStdout("passing", equalTo("out.pass\n"))
+                    .assertTestCaseStderr("passing", equalTo("err.pass\n"))
+                    .assertTestCaseStdout("failing", equalTo("out.fail\n"))
+                    .assertTestCaseStderr("failing", equalTo("err.fail\n"))
+                    .assertTestCaseStdout("passing2", equalTo("out.pass2\n"))
+                    .assertTestCaseStderr("passing2", equalTo("err.pass2\n"))
+                    .assertTestCaseStdout("failing2", equalTo("out.fail2\n"))
+                    .assertTestCaseStderr("failing2", equalTo("err.fail2\n"))
+        }
+
+        def passing = executionResult.testClass("org.PassingTest")
                 .assertTestCount(2, 0, 0)
                 .assertTestsExecuted("passing", "passing2")
                 .assertTestPassed("passing").assertTestPassed("passing2")
-                .assertStdout(equalTo("out\n"))
-                .assertStderr(equalTo(""))
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            passing
+                    .assertStdout(equalTo("out\n"))
+                    .assertStderr(equalTo(""))
+        } else {
+            passing
+                    .assertTestCaseStdout("passing", equalTo("out\n"))
+                    .assertTestCaseStderr("passing", equalTo(""))
+                    .assertTestCaseStdout("passing2", equalTo(""))
+                    .assertTestCaseStderr("passing2", equalTo(""))
+        }
 
-        executionResult.testClass("org.FailingTest")
+        def failing = executionResult.testClass("org.FailingTest")
                 .assertTestCount(2, 2, 0)
                 .assertTestsExecuted("failing", "failing2")
                 .assertTestFailed("failing", anything()).assertTestFailed("failing2", anything())
-                .assertStdout(equalTo(""))
-                .assertStderr(equalTo("err\n"))
 
-        executionResult.testClass("org.NoOutputsTest")
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            failing
+                    .assertStdout(equalTo(""))
+                    .assertStderr(equalTo("err\n"))
+        } else {
+            failing
+                    .assertTestCaseStdout("failing", equalTo(""))
+                    .assertTestCaseStderr("failing", equalTo("err\n"))
+                    .assertTestCaseStdout("failing2", equalTo(""))
+                    .assertTestCaseStderr("failing2", equalTo(""))
+
+        }
+
+        def noOutputs = executionResult.testClass("org.NoOutputsTest")
                 .assertTestCount(1, 0, 0)
                 .assertTestsExecuted("passing").assertTestPassed("passing")
-                .assertStdout(equalTo(""))
-                .assertStderr(equalTo(""))
 
-        executionResult.testClass("org.EncodingTest")
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            noOutputs
+                    .assertStdout(equalTo(""))
+                    .assertStderr(equalTo(""))
+        } else {
+            noOutputs
+                    .assertTestCaseStdout("passing", equalTo(""))
+                    .assertTestCaseStderr("passing", equalTo(""))
+        }
+
+        def encoding = executionResult.testClass("org.EncodingTest")
                 .assertTestCount(2, 1, 0)
                 .assertTestPassed("encodesCdata")
                 .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]> non-ascii: ż'))
-                .assertStdout(equalTo("""< html allowed, cdata closing token ]]> encoded!
+
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            encoding
+                    .assertStdout(equalTo("""< html allowed, cdata closing token ]]> encoded!
+no EOL, non-ascii char: ż
+xml entity: &
+"""))
+                    .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
+        } else {
+            encoding
+                    .assertTestCaseStdout("encodesCdata", equalTo("""< html allowed, cdata closing token ]]> encoded!
 no EOL, non-ascii char: ż
 xml entity: &
 """))
-                .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
+                    .assertTestCaseStderr("encodesCdata", equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
+        }
+
+        def parameterized = executionResult.testClass("org.ParameterizedTest")
+                .assertTestCount(6, 4, 0)
+                .assertTestsExecuted(
+                "p1[0](1, 2)", "p4[0](1, \">…Ú)", "p1[1](3, 4)", "p3[0]", "p3[1]", "p4[1](2, \">…Ú)"
+        )
+                .assertTestFailed("p1[1](3, 4)", anything())
+                .assertTestFailed("p3[0]", containsString("Parameter 2 of iteration 1 of method 'p3' toString() method threw exception"))
+                .assertTestFailed("p3[1]", containsString("Parameter 2 of iteration 2 of method 'p3' toString() method threw exception"))
+                .assertTestFailed("p4[1](2, \">…Ú)", anything())
+
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            parameterized
+                    .assertStdout(equalTo("var1 is: 1\nvar1 is: 3\n"))
+                    .assertStderr(equalTo("var2 is: 2\nvar2 is: 4\n"))
+        } else {
+            parameterized
+                    .assertTestCaseStdout("p1[0](1, 2)", equalTo("var1 is: 1\n"))
+                    .assertTestCaseStdout("p1[1](3, 4)", equalTo("var1 is: 3\n"))
+                    .assertTestCaseStderr("p1[0](1, 2)", equalTo("var2 is: 2\n"))
+                    .assertTestCaseStderr("p1[1](3, 4)", equalTo("var2 is: 4\n"))
+        }
+
+        def outputLifecycle = executionResult.testClass("org.OutputLifecycleTest")
+                .assertTestCount(2, 0, 0)
+                .assertTestsExecuted("m1", "m2")
+                .assertTestPassed("m1")
+                .assertTestPassed("m1")
+                .assertTestsSkipped()
+
+        if (executionResult instanceof HtmlTestExecutionResult || outputAssociation == WITH_SUITE) {
+            outputLifecycle
+                    .assertStdout(allOf(containsString("m1 out"), containsString("m2 out")))
+                    .assertStderr(allOf(containsString("m1 err"), containsString("m2 err")))
+
+                    // We don't capture anything outside of test methods for TestNG
+                    .assertStdout(not(anyOf(containsString("before"), containsString("after"), containsString("constructor"))))
+                    .assertStderr(not(anyOf(containsString("before"), containsString("after"), containsString("constructor"))))
+        } else {
+            outputLifecycle
+                    .assertTestCaseStdout("m1", equalTo("m1 out\n"))
+                    .assertTestCaseStderr("m1", equalTo("m1 err\n"))
+                    .assertTestCaseStdout("m2", equalTo("m2 out\n"))
+                    .assertTestCaseStderr("m2", equalTo("m2 err\n"))
+        }
+
+        true
     }
 
 
@@ -195,5 +322,105 @@ public class EncodingTest {
     }
 }
 """
+
+        file("src/test/java/org/ParameterizedTest.java") << """package org;
+
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class ParameterizedTest {
+
+    @Test(dataProvider = "1")
+	public void p1(String var1, String var2) {
+        System.out.println("var1 is: " + var1);
+        System.err.println("var2 is: " + var2);
+       	assertEquals(var1, "1");
+	}
+
+	@DataProvider(name = "1")
+	public Object[][] provider1() {
+		return new Object[][] {
+		   {"1", "2"},
+		   {"3", "4"}
+	    };
+	}
+
+    @Test(dataProvider = "3")
+	public void p3(int i, Object obj) {
+	    assertTrue(i == 1);
+	}
+
+	@DataProvider(name = "3")
+	public Object[][] provider3() {
+		return new Object[][] {
+		    {1, new Object() { public String toString() { throw new RuntimeException("bang!"); } } },
+		    {2, new Object() { public String toString() { throw new RuntimeException("bang!"); } } }
+	    };
+	}
+
+    @Test(dataProvider = "4")
+	public void p4(int i, Object obj) {
+	    assertTrue(i == 1);
+	}
+
+	@DataProvider(name = "4")
+	public Object[][] provider4() {
+		return new Object[][] {
+		    {1, "\\">…Ú" },
+		    {2, "\\">…Ú" }
+	    };
+	}
+
+}
+"""
+
+    file("src/test/java/org/OutputLifecycleTest.java") << """package org;
+
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class OutputLifecycleTest {
+
+    public OutputLifecycleTest() {
+        System.out.println("constructor out");
+        System.err.println("constructor err");
+    }
+
+    @BeforeClass
+    public static void beforeClass() {
+        System.out.println("beforeClass out");
+        System.err.println("beforeClass err");
+    }
+
+    @BeforeTest
+    public void beforeTest() {
+        System.out.println("beforeTest out");
+        System.err.println("beforeTest err");
+    }
+
+    @Test public void m1() {
+        System.out.println("m1 out");
+        System.err.println("m1 err");
+    }
+
+    @Test public void m2() {
+        System.out.println("m2 out");
+        System.err.println("m2 err");
+    }
+
+    @AfterTest
+    public void afterTest() {
+        System.out.println("afterTest out");
+        System.err.println("afterTest err");
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        System.out.println("afterClass out");
+        System.err.println("afterClass err");
+    }
+}
+"""
+
     }
 }
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
index 0aebc65..1949e8a 100644
--- 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
@@ -2,7 +2,7 @@ subprojects {
     apply plugin: "groovy"
 
     dependencies {
-        groovy localGroovy()
+        compile localGroovy()
     }
 
     compileJava.options.fork = true
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/build.gradle
new file mode 100644
index 0000000..694dadf
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+compileGroovy {
+    options.fork = true
+    options.forkOptions.executable = jdkHome
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/src/main/groovy/GroovyCode.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/src/main/groovy/GroovyCode.groovy
new file mode 100644
index 0000000..d2378ad
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/src/main/groovy/GroovyCode.groovy
@@ -0,0 +1 @@
+class GroovyCode {}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/src/main/groovy/JavaCode.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/src/main/groovy/JavaCode.java
new file mode 100644
index 0000000..8bbb6ac
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canJointCompileWithJavaCompilerExecutable/src/main/groovy/JavaCode.java
@@ -0,0 +1,3 @@
+public class JavaCode {
+    GroovyCode groovyCode;
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle
index 669c8e9..4a687c9 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle
@@ -5,6 +5,6 @@ repositories {
 }
 
 dependencies {
-    groovy localGroovy()
+    compile localGroovy()
     testCompile "junit:junit:4.10"
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
index 69928e4..6bfe32a 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
@@ -1,5 +1,5 @@
 apply plugin: 'groovy'
 
 dependencies {
-    groovy localGroovy()
+    compile localGroovy()
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
index 58fd49e..b501216 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: 'groovy'
 
 dependencies {
-    groovy localGroovy()
+    compile localGroovy()
 }
 
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitExtra.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/src/test/java/JUnitExtra.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitExtra.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/src/test/java/JUnitExtra.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/src/test/java/JUnitTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/src/test/java/JUnitTest.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/TestNGTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/src/test/java/TestNGTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/TestNGTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/executesTestsWhenTestFrameworkChanges/src/test/java/TestNGTest.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/shared/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/shared/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/build.gradle
new file mode 100644
index 0000000..0123629
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.11"
+    testCompile "info.cukes:cucumber-java:1.1.2"
+    testCompile "info.cukes:cucumber-junit:1.1.2"
+}
+
+test {
+    testLogging.showStandardStreams = true
+    testLogging.events 'started', 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
+    reports.junitXml.enabled = true
+    reports.html.enabled = true
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/java/HelloStepdefs.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/java/HelloStepdefs.java
new file mode 100644
index 0000000..956621e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/java/HelloStepdefs.java
@@ -0,0 +1,20 @@
+import cucumber.api.java.en.Given;
+import cucumber.api.java.en.Then;
+import cucumber.api.java.en.When;
+
+public class HelloStepdefs {
+    @Given("^I have a hello app with Howdy and /four")
+    public void I_have_a_hello_app_with() {
+        System.out.println("Given");
+    }
+
+    @When("^I ask it to say hi and /five/six/seven")
+    public void I_ask_it_to_say_hi() {
+        System.out.println("When");
+    }
+
+    @Then("^it should answer with Howdy World")
+    public void it_should_answer_with() {
+        System.out.println("Then");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/java/RunCukesTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/java/RunCukesTest.java
new file mode 100644
index 0000000..b6e893f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/java/RunCukesTest.java
@@ -0,0 +1,6 @@
+import cucumber.api.junit.Cucumber;
+import org.junit.runner.RunWith;
+
+ at RunWith(Cucumber.class)
+public class RunCukesTest {
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/resources/helloworld.feature b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/resources/helloworld.feature
new file mode 100644
index 0000000..49b777f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/cucumberjvm/CucumberJVMReportIntegrationTest/testReportingSupportsCucumberStepsWithSlashes/src/test/resources/helloworld.feature
@@ -0,0 +1,7 @@
+Feature: Hello World /one
+
+ at bar
+Scenario: Say hello /two/three
+Given I have a hello app with Howdy and /four
+When I ask it to say hi and /five/six/seven
+Then it should answer with Howdy World
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest/supportsAssumptions/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest/supportsAssumptions/build.gradle
new file mode 100644
index 0000000..1c9cc4d
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest/supportsAssumptions/build.gradle
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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'
+repositories { mavenCentral() }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest/supportsAssumptions/src/test/java/org/gradle/TestWithAssumptions.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest/supportsAssumptions/src/test/java/org/gradle/TestWithAssumptions.java
new file mode 100644
index 0000000..7fd3fc1
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitAssumptionsIntegrationTest/supportsAssumptions/src/test/java/org/gradle/TestWithAssumptions.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Assume;
+import org.junit.Test;
+
+public class TestWithAssumptions {
+    @Test
+    public void assumptionFailed() {
+        Assume.assumeTrue(false);
+    }
+
+    @Test
+    public void assumptionSucceeded() {
+        Assume.assumeTrue(true);
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/build.gradle
new file mode 100644
index 0000000..6b5b248
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/build.gradle
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+
+repositories {
+    mavenCentral()
+}
+
+test {
+    useJUnit {
+        excludeCategories 'org.gradle.CategoryA'
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/CategoryA.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/CategoryA.java
new file mode 100644
index 0000000..932dcf1
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/CategoryA.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface CategoryA {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/LocaleHolder.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/LocaleHolder.java
new file mode 100644
index 0000000..b42b587
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/LocaleHolder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import java.util.Locale;
+
+class LocaleHolder {
+    private static Locale locale;
+
+    public static Locale set(Locale locale) {
+        Locale old = LocaleHolder.locale;
+        LocaleHolder.locale = locale;
+        return old;
+    }
+    public static Locale get() {
+        return locale;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/Locales.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/Locales.java
new file mode 100644
index 0000000..f2730b4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/Locales.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.runner.Runner;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class Locales extends Suite {
+    private static final Iterable<Locale> localesToUse = Arrays.asList(Locale.FRENCH, Locale.GERMAN, Locale.ENGLISH);
+
+    public Locales(Class<?> klass) throws InitializationError {
+        super(klass, extracAndCreateRunners(klass));
+    }
+
+    private static List<Runner> extracAndCreateRunners(Class<?> klass) throws InitializationError {
+        List<Runner> runners = new ArrayList<Runner>();
+        for (Locale locale : localesToUse) {
+            runners.add(new LocalesRunner(locale, klass));
+        }
+        return runners;
+    }
+
+    private static class LocalesRunner extends BlockJUnit4ClassRunner {
+        private final Locale locale;
+
+        LocalesRunner(Locale locale, Class<?> klass) throws InitializationError {
+            super(klass);
+            this.locale = locale;
+        }
+
+        @Override
+        protected Statement methodBlock(final FrameworkMethod method) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    Locale oldLocale = LocaleHolder.set(locale);
+                    try {
+                        LocalesRunner.super.methodBlock(method).evaluate();
+                    } finally {
+                        LocaleHolder.set(oldLocale);
+                    }
+                }
+            };
+        }
+
+        @Override// The name of the test class
+        protected String getName() {
+            return String.format("%s [%s]", super.getName(), locale);
+        }
+
+        @Override// The name of the test method
+        protected String testName(final FrameworkMethod method) {
+            return String.format("%s [%s]", method.getName(), locale);
+        }
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/SomeLocaleTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/SomeLocaleTests.java
new file mode 100644
index 0000000..97b8122
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/SomeLocaleTests.java
@@ -0,0 +1,19 @@
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+ at RunWith(org.gradle.Locales.class)
+public class SomeLocaleTests {
+    @Test
+    public void ok1() {
+        System.out.println("Locale in use: " + LocaleHolder.get());
+    }
+
+    @Test
+    @Category(org.gradle.CategoryA.class)
+    public void ok2() {
+        System.out.println("Locale in use: " + LocaleHolder.get());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/SomeMoreLocalTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/SomeMoreLocalTests.java
new file mode 100644
index 0000000..e18620f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canCombineCategoriesWithCustomRunner/src/test/java/org/gradle/SomeMoreLocalTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+ at RunWith(org.gradle.Locales.class)
+ at Category(org.gradle.CategoryA.class)
+public class SomeMoreLocalTests {
+    @Test
+    public void someMoreTest1() {
+        System.out.println("Locale in use: " + LocaleHolder.get());
+    }
+
+    @Test
+    public void someMoreTest2() {
+        System.out.println("Locale in use: " + LocaleHolder.get());
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/build.gradle
new file mode 100644
index 0000000..72cfbd5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+
+repositories {
+    mavenCentral()
+}
+
+test {
+    useJUnit {
+        excludeCategories 'org.gradle.CategoryA'
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/CatATests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/CatATests.java
new file mode 100644
index 0000000..3abce2e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/CatATests.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+
+ at Category(org.gradle.CategoryA.class)
+public class CatATests {
+
+    @Test
+    public void catAOk1() {
+    }
+
+    @Test
+    public void catAOk2() {
+    }
+
+    @Test
+    public void catAOk3() {
+    }
+
+    @Test
+    public void catAOk4() {
+    }
+}
+
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/CategoryA.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/CategoryA.java
new file mode 100644
index 0000000..fe86403
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/CategoryA.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 CategoryA {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/NoCatTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/NoCatTests.java
new file mode 100644
index 0000000..31fe49e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/NoCatTests.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+
+public class NoCatTests {
+
+    @Test
+    public void noCatOk1() {
+    }
+
+    @Test
+    public void noCatOk2() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeOtherCat.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeOtherCat.java
new file mode 100644
index 0000000..f694f60
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeOtherCat.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface SomeOtherCat {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeOtherCatTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeOtherCatTests.java
new file mode 100644
index 0000000..68266a5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeOtherCatTests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category(org.gradle.SomeOtherCat.class)
+public class SomeOtherCatTests {
+
+    @Test
+    public void someOtherOk1() {
+    }
+
+    @Test
+    public void someOtherOk2() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeTests.java
new file mode 100644
index 0000000..dd69759
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyExcludesOnly/src/test/java/org/gradle/SomeTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+public class SomeTests {
+    @Category(org.gradle.CategoryA.class)
+    @Test
+    public void catAOk1() {
+    }
+
+    @Category(org.gradle.SomeOtherCat.class)
+    @Test
+    public void someOtherCatOk2() {
+    }
+
+    @Test
+    public void noCatOk3() {
+    }
+
+    @Test
+    public void noCatOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/build.gradle
new file mode 100644
index 0000000..34071d2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/build.gradle
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+
+repositories {
+    mavenCentral()
+}
+
+test {
+    useJUnit {
+        includeCategories 'org.gradle.CategoryA'
+        excludeCategories 'org.gradle.CategoryC'
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatACTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatACTests.java
new file mode 100644
index 0000000..c026a15
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatACTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category({org.gradle.CategoryA.class, org.gradle.CategoryC.class})
+public class CatACTests {
+
+    @Test
+    public void catABOk1() {
+    }
+
+    @Test
+    public void catABOk2() {
+    }
+
+    @Test
+    public void catABOk3() {
+    }
+
+    @Test
+    public void catABOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatADTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatADTests.java
new file mode 100644
index 0000000..6650d3b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatADTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+
+ at Category(org.gradle.CategoryA.class)
+public class CatADTests {
+
+    @Test
+    public void catAOk1() {
+    }
+
+    @Test
+    public void catAOk2() {
+    }
+
+    @Test
+    @Category(org.gradle.CategoryC.class)
+    public void catCOk3() {
+    }
+
+    @Test
+    @Category(org.gradle.CategoryD.class)
+    public void catDOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatATests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatATests.java
new file mode 100644
index 0000000..a8966b4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatATests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+
+ at Category(org.gradle.CategoryA.class)
+public class CatATests {
+
+    @Test
+    public void catAOk1() {
+    }
+
+    @Test
+    public void catAOk2() {
+    }
+
+    @Test
+    public void catAOk3() {
+    }
+
+    @Test
+    public void catAOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatBTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatBTests.java
new file mode 100644
index 0000000..a6dccd4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatBTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category(org.gradle.CategoryB.class)
+public class CatBTests {
+
+    @Test
+    public void catBOk1() {
+    }
+
+    @Test
+    public void catBOk2() {
+    }
+
+    @Test
+    public void catBOk3() {
+    }
+
+    @Test
+    public void catBOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatCBTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatCBTests.java
new file mode 100644
index 0000000..88617d4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatCBTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+
+ at Category(org.gradle.CategoryC.class)
+public class CatCBTests {
+
+    @Test
+    public void catCOk1() {
+    }
+
+    @Test
+    public void catCOk2() {
+    }
+
+    @Test
+    @Category(org.gradle.CategoryA.class)
+    public void catAOk3() {
+    }
+
+    @Test
+    @Category(org.gradle.CategoryB.class)
+    public void catBOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatCTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatCTests.java
new file mode 100644
index 0000000..6ff66fe
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatCTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category(CategoryC.class)
+public class CatCTests {
+
+    @Test
+    public void catCOk1() {
+    }
+
+    @Test
+    public void catCOk2() {
+    }
+
+    @Test
+    public void catCOk3() {
+    }
+
+    @Test
+    public void catCOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatDTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatDTests.java
new file mode 100644
index 0000000..f755acc
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatDTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category(CategoryD.class)
+public class CatDTests {
+
+    @Test
+    public void catDOk1() {
+    }
+
+    @Test
+    public void catDOk2() {
+    }
+
+    @Test
+    public void catDOk3() {
+    }
+
+    @Test
+    public void catDOk4() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatZTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatZTests.java
new file mode 100644
index 0000000..c95e596
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CatZTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category(CategoryZ.class)
+public class CatZTests {
+
+    @Test
+    public void catZOk1() {
+    }
+
+    @Test
+    public void catZOk2() {
+    }
+
+    @Test
+    public void catZOk3() {
+    }
+
+    @Test
+    public void catZOk4() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryA.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryA.java
new file mode 100644
index 0000000..87277ec
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryA.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface CategoryA{
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryB.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryB.java
new file mode 100644
index 0000000..355b5f8
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryB.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface CategoryB extends org.gradle.CategoryA{
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryC.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryC.java
new file mode 100644
index 0000000..ccee989
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryC.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface CategoryC{
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryD.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryD.java
new file mode 100644
index 0000000..c8f7024
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryD.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface CategoryD extends org.gradle.CategoryC{
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryZ.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryZ.java
new file mode 100644
index 0000000..a866cbc
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/CategoryZ.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 interface CategoryZ {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/MixedTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/MixedTests.java
new file mode 100644
index 0000000..fefb774
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/MixedTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+
+public class MixedTests {
+
+    @Category(org.gradle.CategoryA.class)
+    @Test
+    public void catAOk1() {
+    }
+
+    @Category(org.gradle.CategoryB.class)
+    @Test
+    public void catBOk2() {
+    }
+
+    @Category(org.gradle.CategoryA.class)
+    @Ignore
+    @Test
+    public void someIgnoredTest() {
+    }
+
+    @Test
+    public void noCatOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/NoCatTests.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/NoCatTests.java
new file mode 100644
index 0000000..a96e2c8
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canSpecifyIncludeAndExcludeCategories/src/test/java/org/gradle/NoCatTests.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.Test;
+
+public class NoCatTests {
+
+    @Test
+    public void noCatOk1() {
+    }
+
+    @Test
+    public void noCatOk2() {
+    }
+
+    @Test
+    public void noCatOk3() {
+    }
+
+    @Test
+    public void noCatOk4() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/reportsUnloadableCategories/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/reportsUnloadableCategories/build.gradle
new file mode 100644
index 0000000..1c5f193
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/reportsUnloadableCategories/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.11'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/reportsUnloadableCategories/src/test/java/org/gradle/SomeTestClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/reportsUnloadableCategories/src/test/java/org/gradle/SomeTestClass.java
new file mode 100644
index 0000000..dbb7f5f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/reportsUnloadableCategories/src/test/java/org/gradle/SomeTestClass.java
@@ -0,0 +1,13 @@
+package org.gradle;
+
+import org.junit.Test;
+
+public class SomeTestClass {
+    @Test
+    public void ok1() {
+    }
+
+    @Test
+    public void ok2() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/testTaskFailsIfCategoriesNotSupported/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/testTaskFailsIfCategoriesNotSupported/build.gradle
new file mode 100644
index 0000000..9136c73
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/testTaskFailsIfCategoriesNotSupported/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.4"
+}
+
+test {
+    useJUnit {
+        includeCategories 'org.gradle.CategoryA'
+        excludeCategories 'org.gradle.CategoryC'
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/testTaskFailsIfCategoriesNotSupported/src/test/java/org/gradle/SomeTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/testTaskFailsIfCategoriesNotSupported/src/test/java/org/gradle/SomeTest.java
new file mode 100644
index 0000000..0c8904e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesIntegrationSpec/testTaskFailsIfCategoriesNotSupported/src/test/java/org/gradle/SomeTest.java
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+
+public class SomeTest {
+    @Test
+    public void ok() {
+    }
+
+    public void helpermethod() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/build.gradle
new file mode 100644
index 0000000..f3986de
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/build.gradle
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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'
+
+repositories {
+    mavenCentral()
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/src/test/java/org/gradle/CustomIgnoredTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/src/test/java/org/gradle/CustomIgnoredTest.java
new file mode 100644
index 0000000..c89383e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/src/test/java/org/gradle/CustomIgnoredTest.java
@@ -0,0 +1,70 @@
+package org.gradle;
+
+import org.junit.Ignore;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+ at Ignore
+ at RunWith(org.gradle.CustomIgnoredTest.TheRunner.class)
+public class CustomIgnoredTest {
+    static int count = 0;
+
+    public boolean doSomething() {
+        return true;
+    }
+
+    public static class TheRunner extends Runner {
+        List descriptions = new ArrayList();
+        private final Class<? extends org.gradle.CustomIgnoredTest> testClass;
+        private final org.gradle.CustomIgnoredTest testContainingInstance;
+        private Description testSuiteDescription;
+
+        public TheRunner(Class<? extends org.gradle.CustomIgnoredTest> testClass) {
+            this.testClass = testClass;
+            testContainingInstance = reflectMeATestContainingInstance(testClass);
+            testSuiteDescription = Description.createSuiteDescription("Custom Test with Suite ");
+            testSuiteDescription.addChild(createTestDescription("first test run"));
+            testSuiteDescription.addChild(createTestDescription("second test run"));
+            testSuiteDescription.addChild(createTestDescription("third test run"));
+        }
+
+        @Override
+        public Description getDescription() {
+            return testSuiteDescription;
+        }
+
+        @Override
+        public void run(RunNotifier notifier) {
+            for (Description description : testSuiteDescription.getChildren()) {
+                notifier.fireTestStarted(description);
+                try {
+                    if (testContainingInstance.doSomething()) {
+                        notifier.fireTestFinished(description);
+                    } else {
+                        notifier.fireTestIgnored(description);
+                    }
+                } catch (Exception e) {
+                    notifier.fireTestFailure(new Failure(description, e));
+                }
+            }
+        }
+
+        private org.gradle.CustomIgnoredTest reflectMeATestContainingInstance(Class<? extends org.gradle.CustomIgnoredTest> testClass) {
+            try {
+                return testClass.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        private Description createTestDescription(String description) {
+            return Description.createTestDescription(testClass, description);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/IgnoredTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/src/test/java/org/gradle/IgnoredTest.java
similarity index 100%
rename from subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/IgnoredTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIgnoreClassMultiVersionIntegrationSpec/canHandleClassLevelIgnoredTests/src/test/java/org/gradle/IgnoredTest.java
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
index 76284da..ba9f816 100644
--- 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
@@ -1,7 +1,9 @@
 apply plugin: 'groovy'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
-dependencies { testCompile 'junit:junit:4.11'}
+dependencies {
+    testCompile localGroovy()
+    testCompile 'junit:junit:4.11'
+}
 test {
     testLogging {
         showStandardStreams = true
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
index 76284da..ba9f816 100644
--- 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
@@ -1,7 +1,9 @@
 apply plugin: 'groovy'
 repositories { mavenCentral() }
-dependencies { groovy localGroovy() }
-dependencies { testCompile 'junit:junit:4.11'}
+dependencies {
+    testCompile localGroovy()
+    testCompile 'junit:junit:4.11'
+}
 test {
     testLogging {
         showStandardStreams = true
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunTestsUsingJUnit3/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunTestsUsingJUnit3/build.gradle
new file mode 100644
index 0000000..464a61d
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunTestsUsingJUnit3/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:3.8"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/build.gradle
deleted file mode 100644
index 0f93806..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'java'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile "junit:junit:3.8"
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
deleted file mode 100644
index 7da1b0e..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'java'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile "junit:junit:4.11"
-}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/CustomIgnoredTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/CustomIgnoredTest.java
deleted file mode 100644
index 435cc7b..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/CustomIgnoredTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.gradle;
-
-import org.junit.Ignore;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runner.Runner;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
-
-import java.util.ArrayList;
-import java.util.List;
-
- at Ignore
- at RunWith(org.gradle.CustomIgnoredTest.TheRunner.class)
-public class CustomIgnoredTest {
-    static int count = 0;
-
-    public boolean doSomething() {
-        return true;
-    }
-
-    public static class TheRunner extends Runner {
-        List descriptions = new ArrayList();
-        private final Class<? extends org.gradle.CustomIgnoredTest> testClass;
-        private final org.gradle.CustomIgnoredTest testContainingInstance;
-        private Description testSuiteDescription;
-
-        public TheRunner(Class<? extends org.gradle.CustomIgnoredTest> testClass) {
-            this.testClass = testClass;
-            testContainingInstance = reflectMeATestContainingInstance(testClass);
-            testSuiteDescription = Description.createSuiteDescription("Custom Test with Suite ");
-            testSuiteDescription.addChild(createTestDescription("first test run"));
-            testSuiteDescription.addChild(createTestDescription("second test run"));
-            testSuiteDescription.addChild(createTestDescription("third test run"));
-        }
-
-        @Override
-        public Description getDescription() {
-            return testSuiteDescription;
-        }
-
-        @Override
-        public void run(RunNotifier notifier) {
-            for (Description description : testSuiteDescription.getChildren()) {
-                notifier.fireTestStarted(description);
-                try {
-                    if (testContainingInstance.doSomething()) {
-                        notifier.fireTestFinished(description);
-                    } else {
-                        notifier.fireTestIgnored(description);
-                    }
-                } catch (Exception e) {
-                    notifier.fireTestFailure(new Failure(description, e));
-                }
-            }
-
-        }
-
-        private org.gradle.CustomIgnoredTest reflectMeATestContainingInstance(Class<? extends org.gradle.CustomIgnoredTest> testClass) {
-            try {
-                return testClass.newInstance();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        private Description createTestDescription(String description) {
-            return Description.createTestDescription(testClass, description);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
index 857793d..9d0ba47 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
@@ -1,6 +1,5 @@
 package org.gradle;
 
-import org.junit.Assume;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -9,16 +8,12 @@ public class Junit4Test {
     public void ok() {
     }
 
-    @Test @Ignore
+    @Test
+    @Ignore
     public void broken() {
         throw new RuntimeException();
     }
 
-    public void helpermethod(){
-	}
-	
-    @Test
-    public void assumptionFailed() {
-        Assume.assumeTrue(false);
+    public void helpermethod() {
     }
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
index 9d27852..0fc8356 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
@@ -1,3 +1,7 @@
 apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'junit:junit:4.11' }
+repositories {
+    mavenCentral()
+}
+dependencies {
+    testCompile 'junit:junit:4.11'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/build.gradle
new file mode 100644
index 0000000..fd11e8d
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.11"
+}
+
+test {
+    useJUnit {
+        includeCategories 'org.gradle.CategoryA'
+        excludeCategories 'org.gradle.CategoryC'
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryA.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryA.java
new file mode 100644
index 0000000..d396068
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryA.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public interface CategoryA {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryB.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryB.java
new file mode 100644
index 0000000..00d73b6
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryB.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public interface CategoryB extends CategoryA{
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryC.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryC.java
new file mode 100644
index 0000000..e70c77f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/CategoryC.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public interface CategoryC {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/SomeTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/SomeTest.java
new file mode 100644
index 0000000..5e0ba2b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsTestCategories/src/test/java/org/gradle/SomeTest.java
@@ -0,0 +1,27 @@
+package org.gradle;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+ at Category(CategoryA.class)
+public class SomeTest {
+
+    @Test
+    public void testOk1() {
+    }
+
+    @Test
+    @Category(CategoryC.class)
+    public void testOk2() {
+    }
+
+    @Test
+    @Category(CategoryB.class)
+    public void testOk3() {
+    }
+
+    @Test
+    @Category({CategoryB.class, CategoryC.class})
+    public void testOk4() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitMultiVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle
similarity index 100%
rename from subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitMultiVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle
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
deleted file mode 100644
index fcabda8..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply plugin: "groovy"
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    compile "org.codehaus.groovy:groovy-all:2.0.5"
-    testCompile "org.testng:testng:6.3.1"
-}
-
-test {
-    useTestNG()
-    testLogging {
-        quiet {
-            events "skipped", "failed"
-            minGranularity 2
-            maxGranularity -1
-            displayGranularity 3
-            exceptionFormat "full"
-            stackTraceFilters "truncate", "groovy"
-        }
-    }
-}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/src/test/groovy/org/gradle/TestNGTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/src/test/groovy/org/gradle/TestNGTest.groovy
deleted file mode 100644
index 6fa79fe..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/src/test/groovy/org/gradle/TestNGTest.groovy
+++ /dev/null
@@ -1,42 +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
-
-import org.testng.annotations.Test
-
-class TestNGTest {
-    @Test
-    void goodTest() {}
-
-    @Test(dependsOnMethods = ["goodTest"])
-    void badTest() {
-        beBad()
-    }
-
-    @Test(dependsOnMethods = ["badTest"])
-    void ignoredTest() {}
-
-    @Test(dependsOnMethods = ["goodTest"])
-    void printTest() {
-        println "line 1\nline 2"
-        println "line 3"
-    }
-
-    private beBad() {
-        throw new RuntimeException("bad")
-    }
-}
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
deleted file mode 100644
index c104119..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
+++ /dev/null
@@ -1,35 +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: "groovy"
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    compile "org.codehaus.groovy:groovy-all:2.0.5"
-    testCompile "org.testng:testng:6.3.1"
-}
-
-test {
-    useTestNG()
-    testLogging {
-        quiet {
-            events "standardOut", "standardError"
-        }
-    }
-}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy
deleted file mode 100644
index becc609..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy
+++ /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
-
-import org.testng.annotations.Test
-
-class TestNGStandardOutputTest {
-    @Test
-    void printTest() {
-        println "line 1\nline 2"
-        println "line 3"
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java
index 2b63d98..701b78a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java
@@ -17,29 +17,33 @@
 package org.gradle.api.distribution;
 
 import org.gradle.api.Action;
-import org.gradle.api.Named;
 import org.gradle.api.Incubating;
+import org.gradle.api.Named;
 import org.gradle.api.file.CopySpec;
 
 /**
  * A distribution allow to bundle an application or a library including dependencies,sources...
- *
- * @author scogneau
  */
 @Incubating
 public interface Distribution extends Named {
+
     /**
      * The name of this distribution.
      */
     String getName();
 
     /**
-     * Returns the baseName of the distribution. This is used in file names for the distribution.
+     * The baseName of the distribution, used in naming the distribution archives.
+     * <p>
+     * If the {@link #getName()} of this distribution is "{@code main}" this defaults to the project's name.
+     * Otherwise it is "{@code $project.name-$this.name}".
      */
     String getBaseName();
 
     /**
-     * Set the baseName of the distribution. This is used in file names for the distribution.
+     * The baseName of the distribution.
+     * <p>
+     * Set to change the name of the distribution archives.
      */
     void setBaseName(String baseName);
 
@@ -50,6 +54,20 @@ public interface Distribution extends Named {
 
     /**
      * Configures the contents of the distribution.
+     * <p>
+     * Can be used to configure the contents of the distribution:
+     * <pre autoTested=''>
+     * apply plugin: "distribution"
+     *
+     * distributions {
+     *     main {
+     *         contents {
+     *             from "src/readme"
+     *         }
+     *     }
+     * }
+     * </pre>
+     * The DSL inside the {@code contents\{} } block is the same DSL used for Copy tasks.
      */
     CopySpec contents(Action<? super CopySpec> action);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java
index ead3990..bb3b1c8 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java
@@ -18,22 +18,18 @@ package org.gradle.api.distribution.internal;
 import org.gradle.api.Action;
 import org.gradle.api.distribution.Distribution;
 import org.gradle.api.file.CopySpec;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.copy.CopySpecImpl;
 
 /**
  * Allow user to declare a distribution.
- *
- * @author scogneau
  */
 public class DefaultDistribution implements Distribution {
     private final String name;
     private String baseName;
     private final CopySpec contents;
 
-    public DefaultDistribution(String name, FileResolver fileResolver) {
+    public DefaultDistribution(String name, CopySpec contents) {
         this.name = name;
-        this.contents = new CopySpecImpl(fileResolver);
+        this.contents = contents;
     }
 
     public String getName() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java
index 182d10e..b767e46 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java
@@ -18,23 +18,22 @@ package org.gradle.api.distribution.internal;
 import org.gradle.api.distribution.Distribution;
 import org.gradle.api.distribution.DistributionContainer;
 import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
-import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.Actions;
+import org.gradle.api.internal.file.FileOperations;
 import org.gradle.internal.reflect.Instantiator;
 
 /**
  * Default implementation for {@link org.gradle.api.distribution.DistributionContainer}
- *
- * @author scogneau
  */
 public class DefaultDistributionContainer extends AbstractNamedDomainObjectContainer<Distribution> implements DistributionContainer {
-    private final FileResolver fileResolver;
+    private final FileOperations fileOperations;
 
-    public DefaultDistributionContainer(Class<Distribution> type, Instantiator instantiator, FileResolver fileResolver) {
+    public DefaultDistributionContainer(Class<Distribution> type, Instantiator instantiator, FileOperations fileOperations) {
         super(type, instantiator);
-        this.fileResolver = fileResolver;
+        this.fileOperations = fileOperations;
     }
 
     protected Distribution doCreate(String name) {
-        return getInstantiator().newInstance(DefaultDistribution.class, name, fileResolver);
+        return getInstantiator().newInstance(DefaultDistribution.class, name, fileOperations.copySpec(Actions.doNothing()));
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/plugins/DistributionPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/plugins/DistributionPlugin.groovy
index 208d9ee..d8b504e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/plugins/DistributionPlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/plugins/DistributionPlugin.groovy
@@ -21,80 +21,74 @@ import org.gradle.api.Incubating
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.distribution.Distribution
-import org.gradle.api.distribution.DistributionContainer
 import org.gradle.api.distribution.internal.DefaultDistributionContainer
+import org.gradle.api.internal.file.FileOperations
+import org.gradle.api.plugins.BasePlugin
 import org.gradle.api.tasks.Sync
-import org.gradle.api.tasks.bundling.Zip
-import org.gradle.api.tasks.bundling.Tar
 import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.api.tasks.bundling.Tar
+import org.gradle.api.tasks.bundling.Zip
 import org.gradle.internal.reflect.Instantiator
 
 import javax.inject.Inject
-import org.gradle.api.plugins.BasePlugin
 
 /**
- * <p>A {@link Plugin} to package project as a distribution.</p>
- *
- * @author scogneau
- *
+ * Adds the ability to create distributions of the project.
  */
 @Incubating
 class DistributionPlugin implements Plugin<Project> {
-    /**
-     * Name of the main distribution
-     */
-    static final String MAIN_DISTRIBUTION_NAME = "main"
 
-    static final String DISTRIBUTION_GROUP = "distribution"
-    static final String TASK_DIST_ZIP_NAME = "distZip"
-    static final String TASK_DIST_TAR_NAME = "distTar"
-    static final String TASK_INSTALL_NAME = "installDist"
+    private static final String MAIN_DISTRIBUTION_NAME = "main"
+    private static final String DISTRIBUTION_GROUP = "distribution"
+    private static final String TASK_DIST_ZIP_NAME = "distZip"
+    private static final String TASK_DIST_TAR_NAME = "distTar"
+    private static final String TASK_INSTALL_NAME = "installDist"
 
-    private DistributionContainer extension
-    private Project project
-    private Instantiator instantiator
+    private final Instantiator instantiator
+    private final FileOperations fileOperations
 
     @Inject
-    public DistributionPlugin(Instantiator instantiator) {
-        this.instantiator = instantiator;
+    DistributionPlugin(Instantiator instantiator, FileOperations fileOperations) {
+        this.fileOperations = fileOperations
+        this.instantiator = instantiator
     }
 
-    public void apply(Project project) {
-        this.project = project
+    void apply(Project project) {
         project.plugins.apply(BasePlugin)
-        addPluginExtension()
-    }
 
-    void addPluginExtension() {
-        extension = project.extensions.create("distributions", DefaultDistributionContainer.class, Distribution.class, instantiator, project.fileResolver)
-        extension.all { dist ->
+        def distributions = project.extensions.create("distributions", DefaultDistributionContainer, Distribution, instantiator, fileOperations)
+
+        // TODO - refactor this action out so it can be unit tested
+        distributions.all { dist ->
             dist.baseName = dist.name == MAIN_DISTRIBUTION_NAME ? project.name : String.format("%s-%s", project.name, dist.name)
-            dist.contents.from("src/${dist.name}/dist")
-            addZipTask(dist)
-            addTarTask(dist)
-            addInstallTask(dist)
+            dist.contents.from("src/$dist.name/dist")
+
+            addZipTask(project, dist)
+            addTarTask(project, dist)
+            addInstallTask(project, dist)
         }
-        extension.create(DistributionPlugin.MAIN_DISTRIBUTION_NAME)
+
+        distributions.create(MAIN_DISTRIBUTION_NAME)
     }
 
-    void addZipTask(Distribution distribution) {
+    void addZipTask(Project project, Distribution distribution) {
         def taskName = TASK_DIST_ZIP_NAME
-        if (!MAIN_DISTRIBUTION_NAME.equals(distribution.name)) {
+        if (MAIN_DISTRIBUTION_NAME != distribution.name) {
             taskName = distribution.name + "DistZip"
         }
-        configureArchiveTask(taskName, distribution, Zip)
+        configureArchiveTask(project, taskName, distribution, Zip)
     }
 
-    void addTarTask(Distribution distribution) {
+    void addTarTask(Project project, Distribution distribution) {
         def taskName = TASK_DIST_TAR_NAME
-        if (!MAIN_DISTRIBUTION_NAME.equals(distribution.name)) {
+        if (MAIN_DISTRIBUTION_NAME != distribution.name) {
             taskName = distribution.name + "DistTar"
         }
-        configureArchiveTask(taskName, distribution, Tar)
+        configureArchiveTask(project, taskName, distribution, Tar)
     }
 
-    private <T extends AbstractArchiveTask> void configureArchiveTask(String taskName, Distribution distribution, Class<T> type) {
-        def archiveTask = project.tasks.add(taskName, type)
+    private <T extends AbstractArchiveTask> void configureArchiveTask(Project project, String taskName, Distribution distribution, Class<T> type) {
+        def archiveTask = project.tasks.create(taskName, type)
         archiveTask.description = "Bundles the project as a distribution."
         archiveTask.group = DISTRIBUTION_GROUP
         archiveTask.conventionMapping.baseName = {
@@ -109,12 +103,12 @@ class DistributionPlugin implements Plugin<Project> {
         }
     }
 
-    private void addInstallTask(Distribution distribution) {
+    private void addInstallTask(Project project, Distribution distribution) {
         def taskName = TASK_INSTALL_NAME
-        if (!MAIN_DISTRIBUTION_NAME.equals(distribution.name)) {
+        if (MAIN_DISTRIBUTION_NAME != distribution.name) {
             taskName = "install"+ distribution.name.capitalize() + "Dist"
         }
-        def installTask = project.tasks.add(taskName, Sync)
+        def installTask = project.tasks.create(taskName, Sync)
         installTask.description = "Installs the project as a JVM application along with libs and OS specific scripts."
         installTask.group = DISTRIBUTION_GROUP
         installTask.with distribution.contents
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java
index 55d1647..80cc86a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java
@@ -43,7 +43,7 @@ public class WebApplication implements SoftwareComponentInternal {
 
     private class WebArchiveUsage implements Usage {
         public String getName() {
-            return "master"; // TODO:DAZ Maybe come up with a better name
+            return "master";
         }
 
         public Set<PublishArtifact> getArtifacts() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java
index 6d2096c..ea4f9d9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java
@@ -44,7 +44,7 @@ public class BuildConfigurationRule extends AbstractRule {
             Configuration configuration = configurations.findByName(configurationName);
 
             if (configuration != null) {
-                Task task = tasks.add(taskName);
+                Task task = tasks.create(taskName);
                 task.dependsOn(configuration.getAllArtifacts());
                 task.setDescription(String.format("Builds the artifacts belonging to %s.", configuration));
             }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java
index 66fcc30..4266b35 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java
@@ -50,7 +50,7 @@ public class CleanRule extends AbstractRule {
             return;
         }
 
-        Delete clean = tasks.add(taskName, Delete.class);
+        Delete clean = tasks.create(taskName, Delete.class);
         clean.delete(task.getOutputs().getFiles());
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/ProcessResources.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/ProcessResources.java
index 3cb258e..231957a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/ProcessResources.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/ProcessResources.java
@@ -15,16 +15,11 @@
  */
 package org.gradle.api.internal.plugins;
 
-import org.gradle.api.internal.tasks.compile.SimpleStaleClassCleaner;
-import org.gradle.api.internal.tasks.compile.StaleClassCleaner;
-import org.gradle.api.tasks.Copy;
-
-public class ProcessResources extends Copy {
-    @Override
-    protected void copy() {
-        StaleClassCleaner cleaner = new SimpleStaleClassCleaner(getOutputs());
-        cleaner.setDestinationDir(getDestinationDir());
-        cleaner.execute();
-        super.copy();
-    }
-}
+/**
+ * Copies resources from their source to their target directory, potentially processing them.
+ * Makes sure no stale resources remain in the target directory.
+ *
+ * @deprecated use {@link org.gradle.language.jvm.tasks.ProcessResources} instead
+ */
+ at Deprecated
+public class ProcessResources extends org.gradle.language.jvm.tasks.ProcessResources {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
index be5601f..c92810c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
@@ -39,6 +39,8 @@ class StartScriptGenerator {
 
     String mainClassName
 
+    Iterable<String> defaultJvmOpts = []
+
     /**
      * The classpath, relative to the application home directory.
      */
@@ -64,9 +66,22 @@ class StartScriptGenerator {
 
     String generateUnixScriptContent() {
         def unixClassPath = classpath.collect { "\$APP_HOME/${it.replace('\\', '/')}" }.join(":")
+        def quotedDefaultJvmOpts = defaultJvmOpts.collect{
+            //quote ', ", \, $. Probably not perfect. TODO: identify non-working cases, fail-fast on them
+            it = it.replace('\\', '\\\\')
+            it = it.replace('"', '\\"')
+            it = it.replace(/'/, /'"'"'/)
+            it = it.replace(/`/, /'"`"'/)
+            it = it.replace('$', '\\$')
+            (/"${it}"/)
+        }
+        //put the whole arguments string in single quotes, unless defaultJvmOpts was empty,
+        // in which case we output "" to stay compatible with existing builds that scan the script for it
+        def defaultJvmOptsString = (quotedDefaultJvmOpts ? /'${quotedDefaultJvmOpts.join(' ')}'/ : '""')
         def binding = [applicationName: applicationName,
                 optsEnvironmentVar: optsEnvironmentVar,
                 mainClassName: mainClassName,
+                defaultJvmOpts: defaultJvmOptsString,
                 appNameSystemProperty: appNameSystemProperty,
                 appHomeRelativePath: appHomeRelativePath,
                 classpath: unixClassPath]
@@ -81,10 +96,32 @@ class StartScriptGenerator {
     String generateWindowsScriptContent() {
         def windowsClassPath = classpath.collect { "%APP_HOME%\\${it.replace('/', '\\')}" }.join(";")
         def appHome = appHomeRelativePath.replace('/', '\\')
+        //argument quoting:
+        // - " must be encoded as \"
+        // - % must be encoded as %%
+        // - pathological case: \" must be encoded as \\\", but other than that, \ MUST NOT be quoted
+        // - other characters (including ') will not be quoted
+        // - use a state machine rather than regexps
+        def quotedDefaultJvmOpts = defaultJvmOpts.collect {
+            def wasOnBackslash = false
+            it = it.collect { ch ->
+                def repl = ch
+                if (ch == '%') {
+                    repl = '%%'
+                } else if (ch == '"') {
+                    repl = (wasOnBackslash ? '\\' : '') + '\\"'
+                }
+                wasOnBackslash = (ch == '\\')
+                repl
+            }
+            (/"${it.join()}"/)
+        }
+        def defaultJvmOptsString = quotedDefaultJvmOpts.join(' ')
         def binding = [applicationName: applicationName,
                 optsEnvironmentVar: optsEnvironmentVar,
                 exitEnvironmentVar: exitEnvironmentVar,
                 mainClassName: mainClassName,
+                defaultJvmOpts: defaultJvmOptsString,
                 appNameSystemProperty: appNameSystemProperty,
                 appHomeRelativePath: appHome,
                 classpath: windowsClassPath]
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java
index 676b24e..242152a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java
@@ -47,7 +47,7 @@ public class UploadRule extends AbstractRule {
     }
 
     private Upload createUploadTask(String name, final Configuration configuration, final Project project) {
-        Upload upload = project.getTasks().add(name, Upload.class);
+        Upload upload = project.getTasks().create(name, Upload.class);
         upload.setDescription(String.format("Uploads all artifacts belonging to %s", configuration));
         upload.setGroup(BasePlugin.UPLOAD_GROUP);
         upload.setConfiguration(configuration);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/CompileServices.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/CompileServices.java
new file mode 100644
index 0000000..0cd3818
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/CompileServices.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerClientsManager;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonStarter;
+import org.gradle.api.internal.tasks.compile.daemon.InProcessCompilerDaemonFactory;
+import org.gradle.initialization.JdkToolsInitializer;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.PluginServiceRegistry;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+public class CompileServices implements PluginServiceRegistry {
+    public void registerGlobalServices(ServiceRegistration registration) {
+    }
+
+    public void registerBuildServices(ServiceRegistration registration) {
+        registration.addProvider(new BuildScopeCompileServices());
+    }
+
+    public void registerProjectServices(ServiceRegistration registration) {
+    }
+
+    private static class BuildScopeCompileServices {
+        void configure(ServiceRegistration registration, JdkToolsInitializer initializer) {
+            // Hackery
+            initializer.initializeJdkTools();
+        }
+
+        CompilerDaemonManager createCompilerDaemonManager(Factory<WorkerProcessBuilder> workerFactory, StartParameter startParameter) {
+            return new CompilerDaemonManager(new CompilerClientsManager(new CompilerDaemonStarter(workerFactory, startParameter)));
+        }
+
+        InProcessCompilerDaemonFactory createInProcessCompilerDaemonFactory() {
+            return new InProcessCompilerDaemonFactory();
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultBinariesContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultBinariesContainer.java
deleted file mode 100644
index 45f690a..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultBinariesContainer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.Named;
-import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.api.tasks.BinariesContainer;
-import org.gradle.internal.reflect.Instantiator;
-
-public class DefaultBinariesContainer extends DefaultNamedDomainObjectSet<Named> implements BinariesContainer {
-    public DefaultBinariesContainer(Instantiator instantiator) {
-        super(Named.class, instantiator);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClassDirectoryBinary.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClassDirectoryBinary.java
deleted file mode 100644
index 6090b1c..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClassDirectoryBinary.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.DomainObjectCollection;
-import org.gradle.api.Nullable;
-import org.gradle.api.Task;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.tasks.*;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-
-public class DefaultClassDirectoryBinary implements ClassDirectoryBinary {
-    private final String name;
-    private File classesDir;
-    private File resourcesDir;
-    private final DomainObjectCollection<LanguageSourceSet> source = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
-    private Task classesTask;
-    private Copy resourcesTask;
-    private AbstractCompile compileTask;
-
-    public DefaultClassDirectoryBinary(String name) {
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public File getClassesDir() {
-        return classesDir;
-    }
-
-    public void setClassesDir(File classesDir) {
-        this.classesDir = classesDir;
-    }
-
-    public File getResourcesDir() {
-        return resourcesDir;
-    }
-
-    public void setResourcesDir(File resourcesDir) {
-        this.resourcesDir = resourcesDir;
-    }
-
-    public DomainObjectCollection<LanguageSourceSet> getSource() {
-        return source;
-    }
-
-    public TaskDependency getBuildDependencies() {
-        return null;  //TODO
-    }
-
-    public Task getClassesTask() {
-        return classesTask;
-    }
-
-    public void setClassesTask(Task classesTask) {
-        this.classesTask = classesTask;
-    }
-
-    @Nullable
-    public Copy getResourcesTask() {
-        return resourcesTask;
-    }
-
-    public void setResourcesTask(Copy resourcesTask) {
-        this.resourcesTask = resourcesTask;
-    }
-
-    @Nullable
-    public AbstractCompile getCompileTask() {
-        return compileTask;
-    }
-
-    public void setCompileTask(AbstractCompile compileTask) {
-        this.compileTask = compileTask;
-    }
-
-    public String getTaskName(@Nullable String verb, @Nullable String target) {
-        if (verb == null) {
-            return StringUtils.uncapitalize(String.format("%s%s", getTaskBaseName(), StringUtils.capitalize(target)));
-        }
-        if (target == null) {
-            return StringUtils.uncapitalize(String.format("%s%s", verb, GUtil.toCamelCase(name)));
-        }
-        return StringUtils.uncapitalize(String.format("%s%s%s", verb, getTaskBaseName(), StringUtils.capitalize(target)));
-    }
-
-    public String getTaskBaseName() {
-        return name.equals("main") ? "" : GUtil.toCamelCase(name);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClasspath.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClasspath.java
deleted file mode 100644
index 44e0fb8..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClasspath.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.gradle.api.tasks.Classpath;
-import org.gradle.api.tasks.TaskDependency;
-
-public class DefaultClasspath implements Classpath {
-    private final FileCollection files;
-
-    public DefaultClasspath(FileResolver fileResolver, TaskResolver taskResolver) {
-        files = new DefaultConfigurableFileCollection(fileResolver, taskResolver);
-    }
-
-    public FileCollection getFiles() {
-        return files;
-    }
-
-    public TaskDependency getBuildDependencies() {
-        return files.getBuildDependencies();
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultFunctionalSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultFunctionalSourceSet.java
deleted file mode 100644
index 08fcbbe..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultFunctionalSourceSet.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.tasks.FunctionalSourceSet;
-import org.gradle.api.tasks.LanguageSourceSet;
-import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
-import org.gradle.internal.reflect.Instantiator;
-
-public class DefaultFunctionalSourceSet extends DefaultPolymorphicDomainObjectContainer<LanguageSourceSet> implements FunctionalSourceSet {
-    private final String name;
-
-    public DefaultFunctionalSourceSet(String name, Instantiator instantiator) {
-        super(LanguageSourceSet.class, instantiator);
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJavaSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJavaSourceSet.java
deleted file mode 100644
index baf13a9..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJavaSourceSet.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.Task;
-import org.gradle.api.file.SourceDirectorySet;
-import org.gradle.api.tasks.Classpath;
-import org.gradle.api.tasks.FunctionalSourceSet;
-import org.gradle.api.tasks.JavaSourceSet;
-import org.gradle.api.tasks.TaskDependency;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class DefaultJavaSourceSet implements JavaSourceSet {
-    private final String name;
-    private final SourceDirectorySet source;
-    private final Classpath compileClasspath;
-    private final FunctionalSourceSet parent;
-
-    public DefaultJavaSourceSet(String name, SourceDirectorySet source, Classpath compileClasspath, FunctionalSourceSet parent) {
-        this.name = name;
-        this.source = source;
-        this.compileClasspath = compileClasspath;
-        this.parent = parent;
-    }
-
-    public Classpath getCompileClasspath() {
-        return compileClasspath;
-    }
-
-    public SourceDirectorySet getSource() {
-        return source;
-    }
-
-    public FunctionalSourceSet getParent() {
-        return parent;
-    }
-
-    public TaskDependency getBuildDependencies() {
-        return new TaskDependency() {
-            public Set<? extends Task> getDependencies(Task task) {
-                Set<Task> dependencies = new HashSet<Task>();
-                dependencies.addAll(compileClasspath.getBuildDependencies().getDependencies(task));
-                dependencies.addAll(source.getBuildDependencies().getDependencies(task));
-                return dependencies;
-            }
-        };
-    }
-
-    public String getName() {
-        return name;
-    }
-
-
-    public String toString() {
-        return String.format("%s/%s source set", parent.getName(), name);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJvmBinaryContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJvmBinaryContainer.java
deleted file mode 100644
index bc51285..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJvmBinaryContainer.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
-import org.gradle.api.tasks.ClassDirectoryBinary;
-import org.gradle.api.tasks.JvmBinaryContainer;
-import org.gradle.internal.reflect.Instantiator;
-
-public class DefaultJvmBinaryContainer extends DefaultPolymorphicDomainObjectContainer<ClassDirectoryBinary> implements JvmBinaryContainer {
-    public DefaultJvmBinaryContainer(Instantiator instantiator) {
-        super(ClassDirectoryBinary.class, instantiator);
-    }
-
-    public String getName() {
-        return "jvm";
-    }
-
-    @Override
-    protected ClassDirectoryBinary doCreate(String name) {
-        return getInstantiator().newInstance(DefaultClassDirectoryBinary.class, name);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultProjectSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultProjectSourceSet.java
deleted file mode 100644
index a340827..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultProjectSourceSet.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
-import org.gradle.api.tasks.FunctionalSourceSet;
-import org.gradle.api.tasks.ProjectSourceSet;
-import org.gradle.internal.reflect.Instantiator;
-
-public class DefaultProjectSourceSet extends AbstractNamedDomainObjectContainer<FunctionalSourceSet> implements ProjectSourceSet {
-    public DefaultProjectSourceSet(Instantiator instantiator) {
-        super(FunctionalSourceSet.class, instantiator);
-    }
-
-    @Override
-    protected FunctionalSourceSet doCreate(String name) {
-        return getInstantiator().newInstance(DefaultFunctionalSourceSet.class, name, getInstantiator());
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultResourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultResourceSet.java
deleted file mode 100644
index 912f623..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultResourceSet.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import org.gradle.api.file.SourceDirectorySet;
-import org.gradle.api.tasks.FunctionalSourceSet;
-import org.gradle.api.tasks.ResourceSet;
-import org.gradle.api.tasks.TaskDependency;
-
-public class DefaultResourceSet implements ResourceSet {
-    private final String name;
-    private final SourceDirectorySet source;
-    private final FunctionalSourceSet parent;
-
-    public DefaultResourceSet(String name, SourceDirectorySet source, FunctionalSourceSet parent) {
-        this.name = name;
-        this.source = source;
-        this.parent = parent;
-    }
-
-    public SourceDirectorySet getSource() {
-        return source;
-    }
-
-    public FunctionalSourceSet getParent() {
-        return parent;
-    }
-
-    public TaskDependency getBuildDependencies() {
-        return source.getBuildDependencies();
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String toString() {
-        return String.format("%s/%s source set", parent.getName(), name);
-    }
-}
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 ef423c4..fcf78ed 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
@@ -73,7 +73,7 @@ public class DefaultSourceSet implements SourceSet {
 
     @Override
     public String toString() {
-        return String.format("source set %s", getDisplayName());
+        return String.format("source set '%s'", getDisplayName());
     }
 
     public String getDisplayName() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
index 0c3b012..f120e1d 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
@@ -22,6 +22,7 @@ import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.SourceSetContainer;
+import org.gradle.util.DeprecationLogger;
 
 public class DefaultSourceSetContainer extends AbstractNamedDomainObjectContainer<SourceSet> implements SourceSetContainer {
     private final FileResolver fileResolver;
@@ -44,10 +45,12 @@ public class DefaultSourceSetContainer extends AbstractNamedDomainObjectContaine
     }
 
     public SourceSet add(String name) {
+        DeprecationLogger.nagUserOfReplacedMethod("SourceSetContainer.add()", "create()");
         return create(name);
     }
 
     public SourceSet add(String name, Closure closure) {
+        DeprecationLogger.nagUserOfReplacedMethod("SourceSetContainer.add()", "create()");
         return create(name, closure);
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java
index 722326e..300947b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java
@@ -29,9 +29,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
-/**
- * @author: Szczepan Faber, created at: 5/4/11
- */
 public class DefaultSourceSetOutput extends CompositeFileCollection implements SourceSetOutput {
     private DefaultConfigurableFileCollection outputDirectories;
     private Object classesDir;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java
index 323f9c6..6f4176d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java
@@ -17,11 +17,12 @@ package org.gradle.api.internal.tasks;
 
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.tasks.*;
+import org.gradle.language.jvm.Classpath;
 
 public class SourceSetCompileClasspath implements Classpath {
-    private final org.gradle.api.tasks.SourceSet sourceSet;
+    private final SourceSet sourceSet;
 
-    public SourceSetCompileClasspath(org.gradle.api.tasks.SourceSet sourceSet) {
+    public SourceSetCompileClasspath(SourceSet sourceSet) {
         this.sourceSet = sourceSet;
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
index e71588e..562e407 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
@@ -17,15 +17,19 @@ package org.gradle.api.internal.tasks.compile
 
 import org.gradle.api.AntBuilder
 import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.compile.CompileOptions
 import org.gradle.internal.Factory
+import org.gradle.language.jvm.internal.StaleClassCleaner
 
 class AntDependsStaleClassCleaner extends StaleClassCleaner {
     private final Factory<AntBuilder> antBuilderFactory
+    private final CompileOptions compileOptions
 
     File dependencyCacheDir
 
-    AntDependsStaleClassCleaner(Factory<AntBuilder> antBuilderFactory) {
+    AntDependsStaleClassCleaner(Factory<AntBuilder> antBuilderFactory, CompileOptions compileOptions) {
         this.antBuilderFactory = antBuilderFactory;
+        this.compileOptions = compileOptions;
     }
 
     void execute() {
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 062e4c2..93a34f1 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
@@ -27,8 +27,6 @@ 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
  * added to the compile classpath.
  * See: http://jira.codehaus.org/browse/GROOVY-2717
- *
- * @author Hans Dockter
  */
 class AntGroovyCompiler implements org.gradle.api.internal.tasks.compile.Compiler<GroovyJavaJointCompileSpec> {
     private final IsolatedAntBuilder ant
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
index 213363d..0b02045 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
@@ -20,12 +20,10 @@ import org.gradle.api.AntBuilder
 import org.gradle.api.file.FileCollection
 import org.gradle.api.tasks.WorkResult
 import org.gradle.internal.Factory
+import org.gradle.internal.jvm.Jvm
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
-/**
- * @author Hans Dockter
- */
 class AntJavaCompiler implements org.gradle.api.internal.tasks.compile.Compiler<JavaCompileSpec> {
     private static final Logger LOGGER = LoggerFactory.getLogger(AntJavaCompiler)
     private static final String CLASSPATH_ID = 'compile.classpath'
@@ -48,6 +46,9 @@ class AntJavaCompiler implements org.gradle.api.internal.tasks.compile.Compiler<
                 target: spec.targetCompatibility,
                 source: spec.sourceCompatibility
         ]
+        if (spec.compileOptions.fork && !spec.compileOptions.forkOptions.executable) {
+            spec.compileOptions.forkOptions.executable = Jvm.current().javacExecutable
+        }
 
         Map options = otherArgs + spec.compileOptions.optionMap()
         LOGGER.info("Compiling with Ant javac task.")
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
index e91b72b..2ee42fd 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
@@ -23,10 +23,11 @@ import org.codehaus.groovy.control.CompilerConfiguration;
 import org.codehaus.groovy.control.messages.SimpleMessage;
 import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit;
 import org.codehaus.groovy.tools.javac.JavaCompiler;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.internal.classpath.DefaultClassPath;
-import org.gradle.util.FilteringClassLoader;
+import org.gradle.internal.classloader.FilteringClassLoader;
 
 import java.io.File;
 import java.io.Serializable;
@@ -65,7 +66,7 @@ public class ApiGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec>,
         // would end up on the compile class path of every compile task for that source set, which may not be desirable.
         spec.setClasspath(Iterables.concat(spec.getClasspath(), Collections.singleton(spec.getDestinationDir())));
 
-        URLClassLoader classPathLoader = new TransformingClassLoader(new DefaultClassPath(spec.getClasspath()));
+        URLClassLoader classPathLoader = new GroovyCompileTransformingClassLoader(new DefaultClassPath(spec.getClasspath()));
         GroovyClassLoader compileClasspathClassLoader = new GroovyClassLoader(classPathLoader, null);
 
         FilteringClassLoader groovyCompilerClassLoader = new FilteringClassLoader(GroovyClassLoader.class.getClassLoader());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java
index 47e0c3a..a630b63 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java
@@ -20,4 +20,6 @@ public interface ArgCollector {
     
     ArgCollector args(Object... args);
 
+    ArgCollector args(Iterable<?> args);
+
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java
index 6fb66a0..6d901cf 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java
@@ -79,4 +79,11 @@ public class ArgWriter implements ArgCollector {
         writer.println();
         return this;
     }
+
+    public ArgCollector args(Iterable<?> args) {
+        for (Object arg : args) {
+            args(arg);
+        }
+        return this;
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningGroovyCompiler.java
new file mode 100644
index 0000000..dcc284e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningGroovyCompiler.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.compile;
+
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner;
+import org.gradle.language.jvm.internal.StaleClassCleaner;
+
+public class CleaningGroovyCompiler extends CleaningJavaCompilerSupport<GroovyJavaJointCompileSpec> {
+    private final Compiler<GroovyJavaJointCompileSpec> compiler;
+    private final TaskOutputsInternal taskOutputs;
+
+    public CleaningGroovyCompiler(Compiler<GroovyJavaJointCompileSpec> compiler, TaskOutputsInternal taskOutputs) {
+        this.compiler = compiler;
+        this.taskOutputs = taskOutputs;
+    }
+
+    @Override
+    protected Compiler<GroovyJavaJointCompileSpec> getCompiler() {
+        return compiler;
+    }
+
+    @Override
+    protected StaleClassCleaner createCleaner(GroovyJavaJointCompileSpec spec) {
+        return new SimpleStaleClassCleaner(taskOutputs);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompiler.java
new file mode 100644
index 0000000..802cf7b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompiler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.compile;
+
+import org.gradle.api.AntBuilder;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner;
+import org.gradle.language.jvm.internal.StaleClassCleaner;
+
+public class CleaningJavaCompiler extends CleaningJavaCompilerSupport<JavaCompileSpec> implements Compiler<JavaCompileSpec> {
+    private final Compiler<JavaCompileSpec> compiler;
+    private final Factory<AntBuilder> antBuilderFactory;
+    private final TaskOutputsInternal taskOutputs;
+
+    public CleaningJavaCompiler(Compiler<JavaCompileSpec> compiler, Factory<AntBuilder> antBuilderFactory,
+                                TaskOutputsInternal taskOutputs) {
+        this.compiler = compiler;
+        this.antBuilderFactory = antBuilderFactory;
+        this.taskOutputs = taskOutputs;
+    }
+
+    @Override
+    protected Compiler<JavaCompileSpec> getCompiler() {
+        return compiler;
+    }
+
+    protected StaleClassCleaner createCleaner(JavaCompileSpec spec) {
+        if (spec.getCompileOptions().isUseDepend()) {
+            AntDependsStaleClassCleaner cleaner = new AntDependsStaleClassCleaner(antBuilderFactory, spec.getCompileOptions());
+            cleaner.setDependencyCacheDir(spec.getDependencyCacheDir());
+            return cleaner;
+        } else {
+            return new SimpleStaleClassCleaner(taskOutputs);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompilerSupport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompilerSupport.java
new file mode 100644
index 0000000..042a374
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompilerSupport.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.compile;
+
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.language.jvm.internal.StaleClassCleaner;
+
+/**
+ * Deletes stale classes before invoking the actual compiler
+ */
+public abstract class CleaningJavaCompilerSupport<T extends JavaCompileSpec> implements Compiler<T> {
+    public WorkResult execute(T spec) {
+        StaleClassCleaner cleaner = createCleaner(spec);
+
+        cleaner.setDestinationDir(spec.getDestinationDir());
+        cleaner.setSource(spec.getSource());
+        cleaner.execute();
+
+        Compiler<? super T> compiler = getCompiler();
+        return compiler.execute(spec);
+    }
+
+    protected abstract Compiler<T> getCompiler();
+
+    protected abstract StaleClassCleaner createCleaner(T spec);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
index 5ee946e..a974bbb 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.tasks.compile;
 
 import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.ExecHandle;
@@ -25,11 +26,12 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.Serializable;
 
 /**
  * Executes the Java command line compiler specified in {@code JavaCompileSpec.forkOptions.getExecutable()}.
  */
-public class CommandLineJavaCompiler implements Compiler<JavaCompileSpec> {
+public class CommandLineJavaCompiler implements Compiler<JavaCompileSpec>, Serializable {
     private static final Logger LOGGER = LoggerFactory.getLogger(CommandLineJavaCompiler.class);
 
     private final CompileSpecToArguments<JavaCompileSpec> argumentsGenerator;
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 7146cf3..6b554fb 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
@@ -20,14 +20,11 @@ import com.google.common.collect.Iterables;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.*;
 import java.util.Collections;
 import java.util.List;
 
-public class CommandLineJavaCompilerArgumentsGenerator implements CompileSpecToArguments<JavaCompileSpec> {
+public class CommandLineJavaCompilerArgumentsGenerator implements CompileSpecToArguments<JavaCompileSpec>, Serializable {
     private final TemporaryFileProvider tempFileProvider;
 
     public CommandLineJavaCompilerArgumentsGenerator(TemporaryFileProvider tempFileProvider) {
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 4fc2368..a4fb7f2 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
@@ -16,28 +16,33 @@
 package org.gradle.api.internal.tasks.compile;
 
 import org.gradle.api.AntBuilder;
+import org.gradle.api.internal.file.DefaultTemporaryFileProvider;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
 import org.gradle.api.internal.tasks.compile.daemon.DaemonJavaCompiler;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.tasks.compile.CompileOptions;
 import org.gradle.internal.Factory;
 
+import java.io.File;
+import java.io.Serializable;
+
 public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
     private final static Logger LOGGER = Logging.getLogger(DefaultJavaCompilerFactory.class);
 
     private final ProjectInternal project;
-    private final TemporaryFileProvider tempFileProvider;
     private final Factory<AntBuilder> antBuilderFactory;
     private final JavaCompilerFactory inProcessCompilerFactory;
+    private final CompilerDaemonManager compilerDaemonManager;
     private boolean jointCompilation;
 
-    public DefaultJavaCompilerFactory(ProjectInternal project, TemporaryFileProvider tempFileProvider, Factory<AntBuilder> antBuilderFactory, JavaCompilerFactory inProcessCompilerFactory){
+    public DefaultJavaCompilerFactory(ProjectInternal project, Factory<AntBuilder> antBuilderFactory, JavaCompilerFactory inProcessCompilerFactory, CompilerDaemonManager compilerDaemonManager){
         this.project = project;
-        this.tempFileProvider = tempFileProvider;
         this.antBuilderFactory = antBuilderFactory;
         this.inProcessCompilerFactory = inProcessCompilerFactory;
+        this.compilerDaemonManager = compilerDaemonManager;
     }
 
     /**
@@ -74,14 +79,30 @@ public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
 
     private Compiler<JavaCompileSpec> createTargetCompiler(CompileOptions options) {
         if (options.isFork() && options.getForkOptions().getExecutable() != null) {
-            return new CommandLineJavaCompiler(tempFileProvider, project.getProjectDir());
+            return new CommandLineJavaCompiler(createSerializableTempFileProvider(), project.getProjectDir());
         }
 
         Compiler<JavaCompileSpec> compiler = inProcessCompilerFactory.create(options);
         if (options.isFork() && !jointCompilation) {
-            return new DaemonJavaCompiler(project, compiler);
+            return new DaemonJavaCompiler(project, compiler, compilerDaemonManager);
         }
 
         return compiler;
     }
+
+    private TemporaryFileProvider createSerializableTempFileProvider() {
+        return new DefaultTemporaryFileProvider(new FileFactory(project.getBuildDir()));
+    }
+
+    private static class FileFactory implements Factory<File>, Serializable {
+        private final File file;
+
+        private FileFactory(File file) {
+            this.file = file;
+        }
+
+        public File create() {
+            return file;
+        }
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java
index c54832e..961f9ea 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java
@@ -29,4 +29,9 @@ public class ExecSpecBackedArgCollector implements ArgCollector {
         action.args(args);
         return this;
     }
+
+    public ArgCollector args(Iterable<?> args) {
+        action.args(args);
+        return this;
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileTransformingClassLoader.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileTransformingClassLoader.java
new file mode 100644
index 0000000..8f9a13b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileTransformingClassLoader.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.compile;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+import org.gradle.internal.classloader.TransformingClassLoader;
+import org.gradle.internal.classpath.ClassPath;
+import org.objectweb.asm.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transforms @GroovyASTTransformationClass(classes = {classLiterals}) into @GroovyASTTransformationClass([classNames]),
+ * to work around GROOVY-5416.
+ */
+class GroovyCompileTransformingClassLoader extends TransformingClassLoader {
+    private static final String ANNOTATION_DESCRIPTOR = Type.getType(GroovyASTTransformationClass.class).getDescriptor();
+
+    public GroovyCompileTransformingClassLoader(ClassPath classpath) {
+        super(null, classpath);
+    }
+
+    @Override
+    protected byte[] transform(byte[] bytes) {
+        // First scan for annotation, and short circuit transformation if not present
+        ClassReader classReader = new ClassReader(bytes);
+
+        AnnotationDetector detector = new AnnotationDetector();
+        classReader.accept(detector, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
+        if (!detector.found) {
+            return bytes;
+        }
+
+        ClassWriter classWriter = new ClassWriter(0);
+        classReader.accept(new TransformingAdapter(classWriter), 0);
+        bytes = classWriter.toByteArray();
+        return bytes;
+    }
+
+    private static class AnnotationDetector extends ClassVisitor {
+        private boolean found;
+
+        private AnnotationDetector() {
+            super(Opcodes.ASM4);
+        }
+
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            if (desc.equals(ANNOTATION_DESCRIPTOR)) {
+                found = true;
+            }
+            return null;
+        }
+    }
+
+    private static class TransformingAdapter extends ClassVisitor {
+        public TransformingAdapter(ClassWriter classWriter) {
+            super(Opcodes.ASM4, classWriter);
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            if (desc.equals(ANNOTATION_DESCRIPTOR)) {
+                return new AnnotationTransformingVisitor(super.visitAnnotation(desc, visible));
+            }
+            return super.visitAnnotation(desc, visible);
+        }
+
+        private static class AnnotationTransformingVisitor extends AnnotationVisitor {
+            private final List<String> names = new ArrayList<String>();
+
+            public AnnotationTransformingVisitor(AnnotationVisitor annotationVisitor) {
+                super(Opcodes.ASM4, annotationVisitor);
+            }
+
+            public AnnotationVisitor visitArray(String name) {
+                if (name.equals("classes")) {
+                    return new AnnotationVisitor(Opcodes.ASM4){
+                        @Override
+                        public void visit(String name, Object value) {
+                            Type type = (Type) value;
+                            names.add(type.getClassName());
+                        }
+                    };
+                } else if (name.equals("value")) {
+                    return new AnnotationVisitor(Opcodes.ASM4) {
+                        @Override
+                        public void visit(String name, Object value) {
+                            String type = (String) value;
+                            names.add(type);
+                        }
+                    };
+                } else {
+                    return super.visitArray(name);
+                }
+            }
+
+            public void visitEnd() {
+                if (!names.isEmpty()) {
+                    AnnotationVisitor visitor = super.visitArray("value");
+                    for (String name : names) {
+                        visitor.visit(null, name);
+                    }
+                    visitor.visitEnd();
+                }
+                super.visitEnd();
+            }
+        }
+    }
+}
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 b8990e5..b4d5e5d 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
@@ -36,12 +36,17 @@ public class GroovyCompilerFactory {
     private final IsolatedAntBuilder antBuilder;
     private final ClassPathRegistry classPathRegistry;
     private final DefaultJavaCompilerFactory javaCompilerFactory;
+    private final CompilerDaemonManager compilerDaemonManager;
+    private final InProcessCompilerDaemonFactory inProcessCompilerDaemonFactory;
 
-    public GroovyCompilerFactory(ProjectInternal project, IsolatedAntBuilder antBuilder, ClassPathRegistry classPathRegistry, DefaultJavaCompilerFactory javaCompilerFactory) {
+    public GroovyCompilerFactory(ProjectInternal project, IsolatedAntBuilder antBuilder, ClassPathRegistry classPathRegistry, DefaultJavaCompilerFactory javaCompilerFactory,
+                                 CompilerDaemonManager compilerDaemonManager, InProcessCompilerDaemonFactory inProcessCompilerDaemonFactory) {
         this.project = project;
         this.antBuilder = antBuilder;
         this.classPathRegistry = classPathRegistry;
         this.javaCompilerFactory = javaCompilerFactory;
+        this.compilerDaemonManager = compilerDaemonManager;
+        this.inProcessCompilerDaemonFactory = inProcessCompilerDaemonFactory;
     }
 
     Compiler<GroovyJavaJointCompileSpec> create(final GroovyCompileOptions groovyOptions, final CompileOptions javaOptions) {
@@ -65,9 +70,9 @@ public class GroovyCompilerFactory {
                 Compiler<GroovyJavaJointCompileSpec> groovyCompiler = new ApiGroovyCompiler(javaCompiler);
                 CompilerDaemonFactory daemonFactory;
                 if (groovyOptions.isFork()) {
-                    daemonFactory = CompilerDaemonManager.getInstance();
+                    daemonFactory = compilerDaemonManager;
                 } else {
-                    daemonFactory = InProcessCompilerDaemonFactory.getInstance();
+                    daemonFactory = inProcessCompilerDaemonFactory;
                 }
                 groovyCompiler = new DaemonGroovyCompiler(project, groovyCompiler, daemonFactory);
                 return new NormalizingGroovyCompiler(groovyCompiler);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
index 556e890..b91b6df 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
@@ -18,10 +18,10 @@ package org.gradle.api.internal.tasks.compile;
 import org.gradle.api.GradleException;
 import org.gradle.api.JavaVersion;
 import org.gradle.api.tasks.compile.CompileOptions;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 
 public class InProcessJavaCompilerFactory implements JavaCompilerFactory {
-    private static final boolean SUN_COMPILER_AVAILABLE = ReflectionUtil.isClassAvailable("com.sun.tools.javac.Main");
+    private static final boolean SUN_COMPILER_AVAILABLE = JavaReflectionUtil.isClassAvailable("com.sun.tools.javac.Main");
 
     public Compiler<JavaCompileSpec> create(CompileOptions options) {
         if (JavaVersion.current().isJava6Compatible()) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java
deleted file mode 100644
index 01eafc9..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks.compile;
-
-import org.gradle.api.internal.TaskOutputsInternal;
-
-public class IncrementalGroovyCompiler extends IncrementalJavaCompilerSupport<GroovyJavaJointCompileSpec> {
-    private final Compiler<GroovyJavaJointCompileSpec> compiler;
-    private final TaskOutputsInternal taskOutputs;
-
-    public IncrementalGroovyCompiler(Compiler<GroovyJavaJointCompileSpec> compiler, TaskOutputsInternal taskOutputs) {
-        this.compiler = compiler;
-        this.taskOutputs = taskOutputs;
-    }
-
-    @Override
-    protected Compiler<GroovyJavaJointCompileSpec> getCompiler() {
-        return compiler;
-    }
-
-    @Override
-    protected StaleClassCleaner createCleaner(GroovyJavaJointCompileSpec spec) {
-        return new SimpleStaleClassCleaner(taskOutputs);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java
deleted file mode 100644
index 2e40c72..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.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.internal.tasks.compile;
-
-import org.gradle.api.AntBuilder;
-import org.gradle.internal.Factory;
-import org.gradle.api.internal.TaskOutputsInternal;
-
-public class IncrementalJavaCompiler extends IncrementalJavaCompilerSupport<JavaCompileSpec> implements Compiler<JavaCompileSpec> {
-    private final Compiler<JavaCompileSpec> compiler;
-    private final Factory<AntBuilder> antBuilderFactory;
-    private final TaskOutputsInternal taskOutputs;
-
-    public IncrementalJavaCompiler(Compiler<JavaCompileSpec> compiler, Factory<AntBuilder> antBuilderFactory,
-                                   TaskOutputsInternal taskOutputs) {
-        this.compiler = compiler;
-        this.antBuilderFactory = antBuilderFactory;
-        this.taskOutputs = taskOutputs;
-    }
-
-    @Override
-    protected Compiler<JavaCompileSpec> getCompiler() {
-        return compiler;
-    }
-
-    protected StaleClassCleaner createCleaner(JavaCompileSpec spec) {
-        if (spec.getCompileOptions().isUseDepend()) {
-            AntDependsStaleClassCleaner cleaner = new AntDependsStaleClassCleaner(antBuilderFactory);
-            cleaner.setDependencyCacheDir(spec.getDependencyCacheDir());
-            return cleaner;
-        } else {
-            return new SimpleStaleClassCleaner(taskOutputs);
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerSupport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerSupport.java
deleted file mode 100644
index 90cecb3..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerSupport.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks.compile;
-
-import org.gradle.api.tasks.WorkResult;
-
-/**
- * A dumb incremental compiler. Deletes stale classes before invoking the actual compiler
- */
-public abstract class IncrementalJavaCompilerSupport<T extends JavaCompileSpec> implements Compiler<T> {
-    public WorkResult execute(T spec) {
-        StaleClassCleaner cleaner = createCleaner(spec);
-
-        cleaner.setDestinationDir(spec.getDestinationDir());
-        cleaner.setSource(spec.getSource());
-        cleaner.setCompileOptions(spec.getCompileOptions());
-        cleaner.execute();
-
-        Compiler<? super T> compiler = getCompiler();
-        return compiler.execute(spec);
-    }
-
-    protected abstract Compiler<T> getCompiler();
-
-    protected abstract StaleClassCleaner createCleaner(T spec);
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NoOpStaleClassCleaner.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NoOpStaleClassCleaner.java
index 0e91abd..d20b0b1 100644
--- 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
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.tasks.compile;
 
+import org.gradle.language.jvm.internal.StaleClassCleaner;
+
 public class NoOpStaleClassCleaner extends StaleClassCleaner {
     @Override
     public void execute() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
index 57e5d6b..ab744b1 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
@@ -19,6 +19,7 @@ import com.google.common.base.Joiner;
 import com.google.common.collect.Lists;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.specs.Spec;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java
index 21fdb2b..cfcc8f9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java
@@ -19,6 +19,7 @@ import com.google.common.base.Joiner;
 import com.google.common.collect.Lists;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.specs.Spec;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleaner.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleaner.java
deleted file mode 100644
index 82e3554..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleaner.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks.compile;
-
-import org.gradle.api.internal.TaskOutputsInternal;
-
-import java.io.File;
-
-public class SimpleStaleClassCleaner extends StaleClassCleaner {
-    private final TaskOutputsInternal taskOutputs;
-
-    public SimpleStaleClassCleaner(TaskOutputsInternal taskOutputs) {
-        this.taskOutputs = taskOutputs;
-    }
-
-    @Override
-    public void execute() {
-        String prefix = getDestinationDir().getAbsolutePath() + File.separator;
-        for (File f : taskOutputs.getPreviousFiles()) {
-            if (f.getAbsolutePath().startsWith(prefix)) {
-                f.delete();
-            }
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleWorkResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleWorkResult.java
deleted file mode 100644
index c1a8993..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleWorkResult.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks.compile;
-
-import org.gradle.api.tasks.WorkResult;
-
-public class SimpleWorkResult implements WorkResult {
-    private final boolean didWork;
-
-    public SimpleWorkResult(boolean didWork) {
-        this.didWork = didWork;
-    }
-
-    public boolean getDidWork() {
-        return didWork;
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/StaleClassCleaner.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/StaleClassCleaner.java
deleted file mode 100644
index 688f374..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/StaleClassCleaner.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks.compile;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.compile.CompileOptions;
-
-import java.io.File;
-
-public abstract class StaleClassCleaner {
-    private File destinationDir;
-    CompileOptions compileOptions;
-    FileCollection source;
-
-    public abstract void execute();
-
-    public CompileOptions getCompileOptions() {
-        return compileOptions;
-    }
-
-    public void setCompileOptions(CompileOptions compileOptions) {
-        this.compileOptions = compileOptions;
-    }
-
-    public FileCollection getSource() {
-        return source;
-    }
-
-    public void setSource(FileCollection source) {
-        this.source = source;
-    }
-
-    public void setDestinationDir(File destinationDir) {
-        this.destinationDir = destinationDir;
-    }
-
-    public File getDestinationDir() {
-        return destinationDir;
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java
index 4b15316..3c37109 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.tasks.compile;
 
 import com.sun.tools.javac.Main;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
 import org.gradle.api.tasks.WorkResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
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
deleted file mode 100644
index 21c75b5..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java
+++ /dev/null
@@ -1,152 +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.tasks.compile;
-
-import com.google.common.io.ByteStreams;
-import org.codehaus.groovy.transform.GroovyASTTransformationClass;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.classpath.ClassPath;
-import org.gradle.util.MutableURLClassLoader;
-import org.objectweb.asm.*;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Transforms @GroovyASTTransformationClass(classes = {classLiterals}) into @GroovyASTTransformationClass([classNames]),
- * to work around GROOVY-5416.
- */
-class TransformingClassLoader extends MutableURLClassLoader {
-    private static final String ANNOTATION_DESCRIPTOR = Type.getType(GroovyASTTransformationClass.class).getDescriptor();
-
-    public TransformingClassLoader(ClassPath classpath) {
-        super(null, classpath);
-    }
-
-    @Override
-    protected Class<?> findClass(String name) throws ClassNotFoundException {
-        URL resource = findResource(name.replace(".", "/") + ".class");
-        if (resource == null) {
-            throw new ClassNotFoundException(name);
-        }
-        try {
-            byte[] bytes = loadBytecode(resource);
-            bytes = transform(bytes);
-            return super.defineClass(name, bytes, 0, bytes.length);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    private byte[] loadBytecode(URL resource) throws IOException {
-        InputStream inputStream = resource.openStream();
-        try {
-            return ByteStreams.toByteArray(inputStream);
-        } finally {
-            inputStream.close();
-        }
-    }
-
-    private byte[] transform(byte[] bytes) {
-        // First scan for annotation, and short circuit transformation if not present
-        ClassReader classReader = new ClassReader(bytes);
-
-        AnnotationDetector detector = new AnnotationDetector();
-        classReader.accept(detector, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
-        if (!detector.found) {
-            return bytes;
-        }
-
-        ClassWriter classWriter = new ClassWriter(0);
-        classReader.accept(new TransformingAdapter(classWriter), 0);
-        bytes = classWriter.toByteArray();
-        return bytes;
-    }
-
-    private static class AnnotationDetector extends ClassVisitor {
-        private boolean found;
-
-        private AnnotationDetector() {
-            super(Opcodes.ASM4);
-        }
-
-        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-            if (desc.equals(ANNOTATION_DESCRIPTOR)) {
-                found = true;
-            }
-            return null;
-        }
-    }
-
-    private static class TransformingAdapter extends ClassVisitor {
-        public TransformingAdapter(ClassWriter classWriter) {
-            super(Opcodes.ASM4, classWriter);
-        }
-
-        @Override
-        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-            if (desc.equals(ANNOTATION_DESCRIPTOR)) {
-                return new AnnotationTransformingVisitor(super.visitAnnotation(desc, visible));
-            }
-            return super.visitAnnotation(desc, visible);
-        }
-
-        private static class AnnotationTransformingVisitor extends AnnotationVisitor {
-            private final List<String> names = new ArrayList<String>();
-
-            public AnnotationTransformingVisitor(AnnotationVisitor annotationVisitor) {
-                super(Opcodes.ASM4, annotationVisitor);
-            }
-
-            public AnnotationVisitor visitArray(String name) {
-                if (name.equals("classes")) {
-                    return new AnnotationVisitor(Opcodes.ASM4){
-                        @Override
-                        public void visit(String name, Object value) {
-                            Type type = (Type) value;
-                            names.add(type.getClassName());
-                        }
-                    };
-                } else if (name.equals("value")) {
-                    return new AnnotationVisitor(Opcodes.ASM4) {
-                        @Override
-                        public void visit(String name, Object value) {
-                            String type = (String) value;
-                            names.add(type);
-                        }
-                    };
-                } else {
-                    return super.visitArray(name);
-                }
-            }
-
-            public void visitEnd() {
-                if (!names.isEmpty()) {
-                    AnnotationVisitor visitor = super.visitArray("value");
-                    for (String name : names) {
-                        visitor.visit(null, name);
-                    }
-                    visitor.visitEnd();
-                }
-                super.visitEnd();
-            }
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerClientsManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerClientsManager.java
new file mode 100644
index 0000000..249bcce
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerClientsManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.compile.daemon;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.concurrent.CompositeStoppable;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class CompilerClientsManager {
+
+    private static final Logger LOGGER = Logging.getLogger(CompilerDaemonManager.class);
+
+    private final Object lock = new Object();
+    private final List<CompilerDaemonClient> allClients = new ArrayList<CompilerDaemonClient>();
+    private final List<CompilerDaemonClient> idleClients = new ArrayList<CompilerDaemonClient>();
+
+    private CompilerDaemonStarter compilerDaemonStarter;
+
+    public CompilerClientsManager(CompilerDaemonStarter compilerDaemonStarter) {
+        this.compilerDaemonStarter = compilerDaemonStarter;
+    }
+
+    public CompilerDaemonClient reserveIdleClient(DaemonForkOptions forkOptions) {
+        return reserveIdleClient(forkOptions, idleClients);
+    }
+
+    CompilerDaemonClient reserveIdleClient(DaemonForkOptions forkOptions, List<CompilerDaemonClient> clients) {
+        synchronized (lock) {
+            Iterator<CompilerDaemonClient> it = clients.iterator();
+            while(it.hasNext()) {
+                CompilerDaemonClient candidate = it.next();
+                if(candidate.isCompatibleWith(forkOptions)) {
+                    it.remove();
+                    return candidate;
+                }
+            }
+            return null;
+        }
+    }
+
+    public CompilerDaemonClient reserveNewClient(File workingDir, DaemonForkOptions forkOptions) {
+        //allow the daemon to be started concurrently
+        CompilerDaemonClient client = compilerDaemonStarter.startDaemon(workingDir, forkOptions);
+        synchronized (lock) {
+            allClients.add(client);
+        }
+        return client;
+    }
+
+    public void release(CompilerDaemonClient client) {
+        synchronized (lock) {
+            idleClients.add(client);
+        }
+    }
+
+    public void stop() {
+        synchronized (lock) {
+            LOGGER.debug("Stopping {} compiler daemon(s).", allClients.size());
+            CompositeStoppable.stoppable(allClients).stop();
+            LOGGER.info("Stopped {} compiler daemon(s).", allClients.size());
+            allClients.clear();
+        }
+    }
+}
\ No newline at end of file
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 072f8b9..5e4f512 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,26 +15,20 @@
  */
 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.concurrent.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 {
+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, WorkerProcess workerProcess, CompilerDaemonServerProtocol server) {
         this.forkOptions = forkOptions;
@@ -45,14 +39,11 @@ public class CompilerDaemonClient implements CompilerDaemon, CompilerDaemonClien
     public <T extends CompileSpec> CompileResult execute(Compiler<T> compiler, T 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();
         }
     }
 
@@ -61,13 +52,8 @@ public class CompilerDaemonClient implements CompilerDaemon, CompilerDaemonClien
     }
 
     public void stop() {
-        lock.lock();
-        try {
-            server.stop();
-            workerProcess.waitForStop();
-        } finally {
-            lock.unlock();
-        }
+        server.stop();
+        workerProcess.waitForStop();
     }
 
     public void executed(CompileResult result) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java
index 1e22f4b..e30bf49 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java
@@ -16,8 +16,9 @@
 
 package org.gradle.api.internal.tasks.compile.daemon;
 
-import org.gradle.api.internal.project.ProjectInternal;
+import java.io.File;
 
 public interface CompilerDaemonFactory {
-    CompilerDaemon getDaemon(ProjectInternal project, DaemonForkOptions forkOptions);
+    // TODO - workingDir should be injected into the implementation
+    CompilerDaemon getDaemon(File workingDir, DaemonForkOptions forkOptions);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java
index b779a83..80bb9e9 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
@@ -16,95 +16,39 @@
 package org.gradle.api.internal.tasks.compile.daemon;
 
 import net.jcip.annotations.ThreadSafe;
-
-import org.gradle.BuildAdapter;
-import org.gradle.BuildResult;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.process.internal.JavaExecHandleBuilder;
-import org.gradle.process.internal.WorkerProcess;
-import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.api.internal.tasks.compile.*;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Controls the lifecycle of the compiler daemon and provides access to it.
  */
 @ThreadSafe
 public class CompilerDaemonManager implements CompilerDaemonFactory {
-    private static final Logger LOGGER = Logging.getLogger(CompilerDaemonManager.class);
-    private static final CompilerDaemonManager INSTANCE = new CompilerDaemonManager();
-    
-    private final List<CompilerDaemonClient> clients = new ArrayList<CompilerDaemonClient>();
-
-    public static CompilerDaemonManager getInstance() {
-        return INSTANCE;
-    }
-
-    public synchronized CompilerDaemon getDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
-        if (clients.isEmpty()) {
-            registerStopOnBuildFinished(project);
-        }
-
-        for (CompilerDaemonClient client: clients) {
-            if (client.isCompatibleWith(forkOptions)) {
-                return client;
-            }
-        }
 
-        CompilerDaemonClient client = startDaemon(project, forkOptions);
-        clients.add(client);
-        return client;
-    }
+    private CompilerClientsManager clientsManager;
 
-    public synchronized void stop() {
-        LOGGER.info("Stopping {} Gradle compiler daemon(s).", clients.size());
-        CompositeStoppable.stoppable(clients).stop();
-        LOGGER.info("Stopped {} Gradle compiler daemon(s).", clients.size());
-        clients.clear();
+    public CompilerDaemonManager(CompilerClientsManager clientsManager) {
+        this.clientsManager = clientsManager;
     }
 
-    private void registerStopOnBuildFinished(ProjectInternal project) {
-        project.getGradle().addBuildListener(new BuildAdapter() {
-            @Override
-            public void buildFinished(BuildResult result) {
-                stop();
+    public CompilerDaemon getDaemon(final File workingDir, final DaemonForkOptions forkOptions) {
+        return new CompilerDaemon() {
+            public <T extends CompileSpec> CompileResult execute(org.gradle.api.internal.tasks.compile.Compiler<T> compiler, T spec) {
+                CompilerDaemonClient client = clientsManager.reserveIdleClient(forkOptions);
+                if (client == null) {
+                    client = clientsManager.reserveNewClient(workingDir, forkOptions);
+                }
+                try {
+                    return client.execute(compiler, spec);
+                } finally {
+                    clientsManager.release(client);
+                }
             }
-        });
+        };
     }
 
-    private CompilerDaemonClient startDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
-        LOGGER.info("Starting Gradle compiler daemon with fork options {}.", forkOptions);
-        if (LOGGER.isDebugEnabled()) {
-            LOGGER.debug(forkOptions.toString());
-        }
-
-        WorkerProcessBuilder builder = project.getServices().getFactory(WorkerProcessBuilder.class).create();
-        builder.setLogLevel(project.getGradle().getStartParameter().getLogLevel()); // NOTE: might make sense to respect per-compile-task log level
-        builder.applicationClasspath(forkOptions.getClasspath());
-        builder.sharedPackages(forkOptions.getSharedPackages());
-        File toolsJar = Jvm.current().getToolsJar();
-        if (toolsJar != null) {
-            builder.getApplicationClasspath().add(toolsJar); // for SunJavaCompiler
-        }
-        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
-        javaCommand.setMinHeapSize(forkOptions.getMinHeapSize());
-        javaCommand.setMaxHeapSize(forkOptions.getMaxHeapSize());
-        javaCommand.setJvmArgs(forkOptions.getJvmArgs());
-        javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
-        WorkerProcess process = builder.worker(new CompilerDaemonServer()).build();
-        process.start();
-        CompilerDaemonServerProtocol server = process.getConnection().addOutgoing(CompilerDaemonServerProtocol.class);
-        CompilerDaemonClient client = new CompilerDaemonClient(forkOptions, process, server);
-        process.getConnection().addIncoming(CompilerDaemonClientProtocol.class, client);
-
-        LOGGER.info("Started Gradle compiler daemon with fork options {}.", forkOptions);
-
-        return client;
+    public void stop() {
+        clientsManager.stop();
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java
index bd4ae6d..4b4e19d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java
@@ -35,9 +35,10 @@ public class CompilerDaemonServer implements Action<WorkerProcessContext>, Compi
     private volatile CountDownLatch stop;
     
     public void execute(WorkerProcessContext context) {
-        client = context.getServerConnection().addOutgoing(CompilerDaemonClientProtocol.class);
         stop = new CountDownLatch(1);
+        client = context.getServerConnection().addOutgoing(CompilerDaemonClientProtocol.class);
         context.getServerConnection().addIncoming(CompilerDaemonServerProtocol.class, this);
+        context.getServerConnection().connect();
         try {
             stop.await();
         } catch (InterruptedException e) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java
index c0b6823..3e85df3 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.tasks.compile.daemon;
 
 import org.gradle.api.internal.tasks.compile.CompileSpec;
 import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 
 /**
  * Server part of the compiler daemon protocol. Used to submit compilation jobs.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java
new file mode 100644
index 0000000..5b14f7d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.compile.daemon;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Factory;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.process.internal.WorkerProcess;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.util.Clock;
+
+import java.io.File;
+
+public class CompilerDaemonStarter {
+    private final static Logger LOG = Logging.getLogger(CompilerDaemonStarter.class);
+    private final Factory<WorkerProcessBuilder> workerFactory;
+    private final StartParameter startParameter;
+
+    public CompilerDaemonStarter(Factory<WorkerProcessBuilder> workerFactory, StartParameter startParameter) {
+        this.workerFactory = workerFactory;
+        this.startParameter = startParameter;
+    }
+
+    public CompilerDaemonClient startDaemon(File workingDir, DaemonForkOptions forkOptions) {
+        LOG.debug("Starting Gradle compiler daemon with fork options {}.", forkOptions);
+        Clock clock = new Clock();
+        WorkerProcessBuilder builder = workerFactory.create();
+        builder.setLogLevel(startParameter.getLogLevel()); // NOTE: might make sense to respect per-compile-task log level
+        builder.applicationClasspath(forkOptions.getClasspath());
+        builder.sharedPackages(forkOptions.getSharedPackages());
+        File toolsJar = Jvm.current().getToolsJar();
+        if (toolsJar != null) {
+            builder.getApplicationClasspath().add(toolsJar); // for SunJavaCompiler
+        }
+        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
+        javaCommand.setMinHeapSize(forkOptions.getMinHeapSize());
+        javaCommand.setMaxHeapSize(forkOptions.getMaxHeapSize());
+        javaCommand.setJvmArgs(forkOptions.getJvmArgs());
+        javaCommand.setWorkingDir(workingDir);
+        WorkerProcess process = builder.worker(new CompilerDaemonServer()).setBaseName("Gradle Compiler Daemon").build();
+        process.start();
+
+        CompilerDaemonServerProtocol server = process.getConnection().addOutgoing(CompilerDaemonServerProtocol.class);
+        CompilerDaemonClient client = new CompilerDaemonClient(forkOptions, process, server);
+        process.getConnection().addIncoming(CompilerDaemonClientProtocol.class, client);
+        process.getConnection().connect();
+
+        LOG.info("Started Gradle compiler daemon ({}) with fork options {}.", clock.getTime(), forkOptions);
+
+        return client;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java
index efda938..0a08231 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java
@@ -44,7 +44,7 @@ public class DaemonGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec
 
     public WorkResult execute(GroovyJavaJointCompileSpec spec) {
         DaemonForkOptions daemonForkOptions = createDaemonForkOptions(spec);
-        CompilerDaemon daemon = daemonFactory.getDaemon(project, daemonForkOptions);
+        CompilerDaemon daemon = daemonFactory.getDaemon(project.getRootProject().getProjectDir(), daemonForkOptions);
         CompileResult result = daemon.execute(delegate, spec);
         if (result.isSuccess()) {
             return result;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java
index 59fe52e..f2daf8f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java
@@ -28,10 +28,12 @@ import java.util.Collections;
 public class DaemonJavaCompiler implements Compiler<JavaCompileSpec> {
     private final ProjectInternal project;
     private final Compiler<JavaCompileSpec> delegate;
+    private final CompilerDaemonManager compilerDaemonManager;
 
-    public DaemonJavaCompiler(ProjectInternal project, Compiler<JavaCompileSpec> delegate) {
+    public DaemonJavaCompiler(ProjectInternal project, Compiler<JavaCompileSpec> delegate, CompilerDaemonManager compilerDaemonManager) {
         this.project = project;
         this.delegate = delegate;
+        this.compilerDaemonManager = compilerDaemonManager;
     }
 
     public WorkResult execute(JavaCompileSpec spec) {
@@ -39,7 +41,7 @@ public class DaemonJavaCompiler implements Compiler<JavaCompileSpec> {
         DaemonForkOptions daemonForkOptions = new DaemonForkOptions(
                 forkOptions.getMemoryInitialSize(), forkOptions.getMemoryMaximumSize(), forkOptions.getJvmArgs(),
                 Collections.<File>emptyList(), Collections.singleton("com.sun.tools.javac"));
-        CompilerDaemon daemon = CompilerDaemonManager.getInstance().getDaemon(project, daemonForkOptions);
+        CompilerDaemon daemon = compilerDaemonManager.getDaemon(project.getRootProject().getProjectDir(), daemonForkOptions);
         CompileResult result = daemon.execute(delegate, spec);
         if (result.isSuccess()) {
             return result;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
index 0378c04..6cbd8e8 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
@@ -16,15 +16,16 @@
 
 package org.gradle.api.internal.tasks.compile.daemon;
 
-import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.compile.CompileSpec;
 import org.gradle.api.internal.tasks.compile.Compiler;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.classloader.*;
 import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.internal.io.ClassLoaderObjectInputStream;
-import org.gradle.util.*;
+import org.gradle.util.GUtil;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.Serializable;
 import java.util.concurrent.Callable;
 
@@ -36,7 +37,7 @@ public class InProcessCompilerDaemonFactory implements CompilerDaemonFactory {
         return INSTANCE;
     }
 
-    public CompilerDaemon getDaemon(ProjectInternal project, final DaemonForkOptions forkOptions) {
+    public CompilerDaemon getDaemon(File workingDir, final DaemonForkOptions forkOptions) {
         return new CompilerDaemon() {
             public <T extends CompileSpec> CompileResult execute(Compiler<T> compiler, T spec) {
                 ClassLoader groovyClassLoader = classLoaderFactory.createIsolatedClassLoader(new DefaultClassPath(forkOptions.getClasspath()));
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/AllFromJarRebuildInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/AllFromJarRebuildInfo.java
new file mode 100644
index 0000000..23019d9
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/AllFromJarRebuildInfo.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class AllFromJarRebuildInfo extends DefaultRebuildInfo {
+    AllFromJarRebuildInfo(JarArchive jarContents) {
+        super(false, classesInJar(jarContents.contents));
+    }
+
+    private static Set<String> classesInJar(FileTree jarContents) {
+        final Set<String> out = new HashSet<String>();
+        jarContents.visit(new FileVisitor() {
+            public void visitDir(FileVisitDetails dirDetails) {}
+            public void visitFile(FileVisitDetails fileDetails) {
+                out.add(fileDetails.getPath().replaceAll("/", ".").replaceAll("\\.class$", ""));
+            }
+        });
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassDependents.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassDependents.java
new file mode 100644
index 0000000..6041b2d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassDependents.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.Serializable;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class ClassDependents implements Serializable {
+
+    private final Set<String> dependentClasses = new LinkedHashSet<String>();
+    private boolean dependentToAll;
+
+    public Set<String> getDependentClasses() {
+        return dependentClasses;
+    }
+
+    public boolean isDependentToAll() {
+        return dependentToAll;
+    }
+
+    public ClassDependents addClass(String className) {
+        dependentClasses.add(className);
+        return this;
+    }
+
+    public void setDependentToAll() {
+        dependentToAll = true;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassNameProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassNameProvider.java
new file mode 100644
index 0000000..84aa40f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassNameProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.File;
+
+import static org.gradle.util.GFileUtils.relativePath;
+
+public class ClassNameProvider {
+
+    private final File compiledClassesDir;
+
+    public ClassNameProvider(File compiledClassesDir) {
+        this.compiledClassesDir = compiledClassesDir;
+    }
+
+    public String provideName(File classFile) {
+        String path = relativePath(compiledClassesDir, classFile);
+        if (path.startsWith("/") || path.startsWith(".")) {
+            throw new IllegalArgumentException("Given input class file: '" + classFile + "' is not located inside of '" + compiledClassesDir + "'.");
+        }
+        return path.replaceAll("/", ".").replaceAll("\\.class", "");
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/DefaultRebuildInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/DefaultRebuildInfo.java
new file mode 100644
index 0000000..c3c1841
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/DefaultRebuildInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfo;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+class DefaultRebuildInfo implements RebuildInfo {
+
+    static final RebuildInfo NOTHING_TO_REBUILD = new DefaultRebuildInfo(false, Collections.<String>emptyList());
+    static final RebuildInfo FULL_REBUILD = new DefaultRebuildInfo(true, Collections.<String>emptyList());
+
+    private boolean fullRebuildRequired;
+    protected final Collection<String> changedClassesInJar;
+
+    DefaultRebuildInfo(boolean fullRebuildRequired, Collection<String> changedClassesInJar) {
+        this.fullRebuildRequired = fullRebuildRequired;
+        this.changedClassesInJar = changedClassesInJar;
+    }
+
+    public boolean isFullRebuildRequired() {
+        return fullRebuildRequired;
+    }
+
+    //TODO SF tests
+    public void configureCompilation(PatternSet changedSourceOnly, SelectiveJavaCompiler compiler, ClassDependencyInfo dependencyInfo) {
+        for (String c : changedClassesInJar) {
+            Set<String> actualDependents = dependencyInfo.getActualDependents(c);
+            for (String d : actualDependents) {
+                compiler.addStaleClass(d);
+                changedSourceOnly.include(d.replaceAll("\\.", "/").concat(".java"));
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/DummySerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/DummySerializer.java
new file mode 100644
index 0000000..84b5e4e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/DummySerializer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.*;
+
+import static org.apache.commons.io.IOUtils.closeQuietly;
+
+public class DummySerializer {
+    public static void writeTargetTo(File outputFile, Object target) {
+        try {
+            FileOutputStream out = new FileOutputStream(outputFile);
+            ObjectOutputStream objectStr = new ObjectOutputStream(out);
+            objectStr.writeObject(target);
+            objectStr.flush();
+            objectStr.close();
+            out.close();
+        } catch (IOException e) {
+            throw new RuntimeException("Problems writing to the output file " + outputFile, e);
+        }
+    }
+
+    public static Object readFrom(File inputFile) {
+        FileInputStream in = null;
+        ObjectInputStream objectStr = null;
+        try {
+            in = new FileInputStream(inputFile);
+            objectStr = new ObjectInputStream(in);
+            return objectStr.readObject();
+        } catch (Exception e) {
+            throw new RuntimeException("Problems reading the class tree to the output file " + inputFile, e);
+        } finally {
+            closeQuietly(in);
+            closeQuietly(objectStr);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationSupport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationSupport.java
new file mode 100644
index 0000000..52c90cf
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationSupport.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfo;
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoExtractor;
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoSerializer;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.util.Clock;
+
+public class IncrementalCompilationSupport {
+
+    private static final Logger LOG = Logging.getLogger(IncrementalCompilationSupport.class);
+    private JarSnapshotFeeder jarSnapshotFeeder;
+
+    public IncrementalCompilationSupport(JarSnapshotFeeder jarSnapshotFeeder) {
+        this.jarSnapshotFeeder = jarSnapshotFeeder;
+    }
+
+    public void compilationComplete(CompileOptions options,
+                                    ClassDependencyInfoExtractor extractor,
+                                    ClassDependencyInfoSerializer serializer,
+                                    Iterable<JarArchive> jarsOnClasspath) {
+        if (options.isIncremental()) {
+            Clock clock = new Clock();
+            ClassDependencyInfo info = extractor.extractInfo("");
+            serializer.writeInfo(info);
+            LOG.lifecycle("Performed class dependency analysis in {}, wrote results into {}", clock.getTime(), serializer);
+
+//            clock = new Clock();
+//            jarSnapshotFeeder.storeJarSnapshots(jarsOnClasspath);
+//            LOG.lifecycle("Wrote jar snapshots in {}.", clock.getTime());
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/InputOutputMapper.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/InputOutputMapper.java
new file mode 100644
index 0000000..d60ed46
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/InputOutputMapper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+
+import static java.lang.String.format;
+
+public class InputOutputMapper {
+
+    private Iterable<File> sourceDirs;
+    private File compileDestination;
+
+    public InputOutputMapper(Iterable<File> sourceDirs, File compileDestination) {
+        this.sourceDirs = sourceDirs;
+        this.compileDestination = compileDestination;
+    }
+
+    public JavaSourceClass toJavaSourceClass(File javaSourceClass) {
+        for (File sourceDir : sourceDirs) {
+            if (javaSourceClass.getAbsolutePath().startsWith(sourceDir.getAbsolutePath())) { //perf tweak only
+                String relativePath = GFileUtils.relativePath(sourceDir, javaSourceClass);
+                if (!relativePath.startsWith("..")) {
+                    return new JavaSourceClass(relativePath, compileDestination);
+                }
+            }
+        }
+        throw new IllegalArgumentException(format("Unable to find source java class: '%s' because it does not belong to any of the source dirs: '%s'",
+                javaSourceClass, sourceDirs));
+
+    }
+
+    public JavaSourceClass toJavaSourceClass(String className) {
+        String relativePath = className.replaceAll("\\.", "/").concat(".java");
+        for (File sourceDir : sourceDirs) {
+            File sourceFile = new File(sourceDir, relativePath);
+            if (sourceFile.isFile()) {
+                return new JavaSourceClass(relativePath, compileDestination);
+            }
+        }
+        throw new IllegalArgumentException(format("Unable to find source java class for '%s'. The source file '%s' was not found in source dirs: '%s'",
+                className, relativePath, sourceDirs));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarArchive.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarArchive.java
new file mode 100644
index 0000000..d29b4c9
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarArchive.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+
+public class JarArchive {
+    final File file;
+    final FileTree contents;
+    public JarArchive(File jar, FileTree contents) {
+        this.file = jar;
+        this.contents = contents.matching(new PatternSet().include("**/*.class"));
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarChangeProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarChangeProcessor.java
new file mode 100644
index 0000000..28523c0
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarChangeProcessor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+public class JarChangeProcessor {
+
+    private JarSnapshotFeeder jarSnapshotFeeder;
+
+    public JarChangeProcessor(JarSnapshotFeeder jarSnapshotFeeder) {
+        this.jarSnapshotFeeder = jarSnapshotFeeder;
+    }
+
+    //TODO SF coverage
+    public RebuildInfo processJarChange(InputFileDetails jarChangeDetails, JarArchive jarArchive) {
+        JarSnapshot existing = jarSnapshotFeeder.changedJar(jarChangeDetails.getFile());
+        if (jarChangeDetails.isAdded()) {
+            return DefaultRebuildInfo.NOTHING_TO_REBUILD;
+        }
+
+        if (jarChangeDetails.isRemoved()) {
+            if (existing != null) {
+                return new AllFromJarRebuildInfo(jarArchive);
+            } else {
+                return DefaultRebuildInfo.FULL_REBUILD;
+            }
+        }
+
+        if (jarChangeDetails.isModified()) {
+            if (existing != null) {
+                JarSnapshot newSnapshot = jarSnapshotFeeder.createSnapshot(jarArchive);
+                JarDelta jarDelta = existing.compareToSnapshot(newSnapshot);
+                return new SpecificClassesRebuildInfo(jarDelta);
+            } else {
+                return new AllFromJarRebuildInfo(jarArchive);
+            }
+        }
+
+        throw new IllegalArgumentException("Unknown input file details provided: " + jarChangeDetails);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarDelta.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarDelta.java
new file mode 100644
index 0000000..c310a95
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarDelta.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.util.Collection;
+
+interface JarDelta {
+    Collection<String> getChangedClasses();
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshot.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshot.java
new file mode 100644
index 0000000..a110c19
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshot.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.Serializable;
+import java.util.*;
+
+class JarSnapshot implements Serializable {
+
+    final Map<String, byte[]> classHashes;
+
+    JarSnapshot(Map<String, byte[]> classHashes) {
+        this.classHashes = classHashes;
+    }
+
+    JarDelta compareToSnapshot(JarSnapshot other) {
+        final List<String> changedClasses = new LinkedList<String>();
+        for (String thisCls : classHashes.keySet()) {
+            byte[] hash = other.classHashes.get(thisCls);
+            if (hash == null || !Arrays.equals(hash, classHashes.get(thisCls))) {
+                changedClasses.add(thisCls);
+            }
+        }
+        return new JarDelta() {
+            public Collection<String> getChangedClasses() {
+                return changedClasses;
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotCache.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotCache.java
new file mode 100644
index 0000000..e8cffc5
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotCache.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+//TODO SF this obviously needs to be replaced with a proper cache
+public class JarSnapshotCache {
+
+    private File sharedJarSnapshotCache;
+    private Map<File, JarSnapshot> snapshots;
+
+    public JarSnapshotCache(File sharedJarSnapshotCache) {
+        this.sharedJarSnapshotCache = sharedJarSnapshotCache;
+    }
+
+    public JarSnapshot getSnapshot(File jar) {
+        init();
+        return snapshots.get(jar);
+    }
+
+    public void putSnapshots(Map<File, JarSnapshot> newSnapshots) {
+        init();
+        this.snapshots.putAll(newSnapshots);
+        DummySerializer.writeTargetTo(sharedJarSnapshotCache, this.snapshots);
+    }
+
+    private void init() {
+        if (snapshots == null) {
+            if (sharedJarSnapshotCache.isFile()) {
+                snapshots = (Map) DummySerializer.readFrom(sharedJarSnapshotCache);
+            } else {
+                snapshots = new HashMap<File, JarSnapshot>();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotFeeder.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotFeeder.java
new file mode 100644
index 0000000..67fadc8
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotFeeder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class JarSnapshotFeeder {
+
+    private final JarSnapshotCache jarSnapshotCache;
+    private final Set<File> changedJars = new HashSet<File>();
+    private final JarSnapshotter jarSnapshotter;
+
+    public JarSnapshotFeeder(JarSnapshotCache jarSnapshotCache, JarSnapshotter jarSnapshotter) {
+        this.jarSnapshotCache = jarSnapshotCache;
+        this.jarSnapshotter = jarSnapshotter;
+    }
+
+    public JarSnapshot changedJar(File jarFile) {
+        JarSnapshot snapshot = jarSnapshotCache.getSnapshot(jarFile);
+        changedJars.add(jarFile);
+        return snapshot;
+    }
+
+    public void storeJarSnapshots(Iterable<JarArchive> jars) {
+        Map<File, JarSnapshot> newSnapshots = new HashMap<File, JarSnapshot>();
+        for (JarArchive jar : jars) {
+            if (!changedJars.contains(jar.file) && jarSnapshotCache.getSnapshot(jar.file) != null) {
+                //if jar was not changed and the the snapshot already exists, skip
+                continue;
+            }
+            newSnapshots.put(jar.file, jarSnapshotter.createSnapshot(jar.contents));
+        }
+        jarSnapshotCache.putSnapshots(newSnapshots);
+    }
+
+    public JarSnapshot createSnapshot(JarArchive jarArchive) {
+        return jarSnapshotter.createSnapshot(jarArchive.contents);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotter.java
new file mode 100644
index 0000000..5f94967
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.internal.hash.Hasher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JarSnapshotter {
+
+    private final Hasher hasher;
+
+    public JarSnapshotter(Hasher hasher) {
+        this.hasher = hasher;
+    }
+
+    JarSnapshot createSnapshot(FileTree archivedClasses) {
+        final Map<String, byte[]> hashes = new HashMap<String, byte[]>();
+        archivedClasses.visit(new FileVisitor() {
+            public void visitDir(FileVisitDetails dirDetails) {
+            }
+
+            public void visitFile(FileVisitDetails fileDetails) {
+                hashes.put(fileDetails.getPath().replaceAll("/", ".").replaceAll("\\.class$", ""), hasher.hash(fileDetails.getFile()));
+            }
+        });
+        return new JarSnapshot(hashes);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JavaSourceClass.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JavaSourceClass.java
new file mode 100644
index 0000000..0339ff3
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/JavaSourceClass.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.File;
+
+public class JavaSourceClass {
+    private final String relativePath;
+    private final File compileDestination;
+
+    public JavaSourceClass(String relativePath, File compileDestination) {
+        this.relativePath = relativePath;
+        this.compileDestination = compileDestination;
+    }
+
+    public String getRelativePath() {
+        return relativePath;
+    }
+
+    public String getClassName() {
+        return relativePath.replaceAll("/", ".").replaceAll("\\.java$", "");
+    }
+
+    public File getOutputFile() {
+        return new File(compileDestination, relativePath.replaceAll("\\.java$", ".class"));
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/OutputClassMapper.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/OutputClassMapper.java
new file mode 100644
index 0000000..20b694d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/OutputClassMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import java.io.File;
+
+public class OutputClassMapper {
+
+    private File destinationDir;
+
+    public OutputClassMapper(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    public File getOutputFile(String className) {
+        return new File(destinationDir, className.replaceAll("\\.", "/").concat(".class")); //TODO SF duplicated
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/RebuildInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/RebuildInfo.java
new file mode 100644
index 0000000..72458be
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/RebuildInfo.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfo;
+import org.gradle.api.tasks.util.PatternSet;
+
+interface RebuildInfo {
+    boolean isFullRebuildRequired();
+    void configureCompilation(PatternSet changedSourceOnly, SelectiveJavaCompiler compiler, ClassDependencyInfo dependencyInfo);
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompilation.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompilation.java
new file mode 100644
index 0000000..b2716e6
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompilation.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.internal.hash.DefaultHasher;
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfo;
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoSerializer;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.Clock;
+
+import java.io.File;
+import java.util.Set;
+
+public class SelectiveCompilation {
+    private final FileCollection source;
+    private final FileCollection classpath;
+    private final JarSnapshotFeeder jarSnapshotFeeder;
+    private SelectiveJavaCompiler compiler;
+    private static final Logger LOG = Logging.getLogger(SelectiveCompilation.class);
+    private String fullRebuildNeeded;
+    private boolean compilationNeeded = true;
+    private final FileOperations operations;
+
+    public SelectiveCompilation(IncrementalTaskInputs inputs, FileTree source, FileCollection compileClasspath, final File compileDestination,
+                                final ClassDependencyInfoSerializer dependencyInfoSerializer, final JarSnapshotCache jarSnapshotCache, final SelectiveJavaCompiler compiler,
+                                Iterable<File> sourceDirs, final FileOperations operations) {
+        this.operations = operations;
+        this.jarSnapshotFeeder = new JarSnapshotFeeder(jarSnapshotCache, new JarSnapshotter(new DefaultHasher()));
+        this.compiler = compiler;
+
+        Clock clock = new Clock();
+        final InputOutputMapper mapper = new InputOutputMapper(sourceDirs, compileDestination);
+
+        //load dependency info
+        final ClassDependencyInfo dependencyInfo = dependencyInfoSerializer.readInfo();
+
+        //including only source java classes that were changed
+        final PatternSet changedSourceOnly = new PatternSet();
+        InputFileDetailsAction action = new InputFileDetailsAction(mapper, compiler, changedSourceOnly, dependencyInfo);
+        inputs.outOfDate(action);
+        inputs.removed(action);
+        if (fullRebuildNeeded != null) {
+            LOG.lifecycle("Stale classes detection completed in {}. Rebuild needed: {}.", clock.getTime(), fullRebuildNeeded);
+            this.classpath = compileClasspath;
+            this.source = source;
+            return;
+        }
+
+        compiler.deleteStaleClasses();
+        Set<File> filesToCompile = source.matching(changedSourceOnly).getFiles();
+        if (filesToCompile.isEmpty()) {
+            this.compilationNeeded = false;
+            this.classpath = compileClasspath;
+            this.source = source;
+        } else {
+            this.classpath = compileClasspath.plus(new SimpleFileCollection(compileDestination));
+            this.source = source.matching(changedSourceOnly);
+        }
+        LOG.lifecycle("Stale classes detection completed in {}. Compile include patterns: {}.", clock.getTime(), changedSourceOnly.getIncludes());
+    }
+
+    public FileCollection getSource() {
+        return source;
+    }
+
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public boolean getCompilationNeeded() {
+        return compilationNeeded;
+    }
+
+    public boolean getFullRebuildRequired() {
+        return fullRebuildNeeded != null;
+    }
+
+    private class InputFileDetailsAction implements Action<InputFileDetails> {
+        private final InputOutputMapper mapper;
+        private final SelectiveJavaCompiler compiler;
+        private final PatternSet changedSourceOnly;
+        private final ClassDependencyInfo dependencyInfo;
+
+        public InputFileDetailsAction(InputOutputMapper mapper, SelectiveJavaCompiler compiler, PatternSet changedSourceOnly, ClassDependencyInfo dependencyInfo) {
+            this.mapper = mapper;
+            this.compiler = compiler;
+            this.changedSourceOnly = changedSourceOnly;
+            this.dependencyInfo = dependencyInfo;
+        }
+
+        public void execute(InputFileDetails inputFileDetails) {
+            if (fullRebuildNeeded != null) {
+                return;
+            }
+            File inputFile = inputFileDetails.getFile();
+            String name = inputFile.getName();
+            if (name.endsWith(".java")) {
+                JavaSourceClass source = mapper.toJavaSourceClass(inputFile);
+                compiler.addStaleClass(source);
+                changedSourceOnly.include(source.getRelativePath());
+                Set<String> actualDependents = dependencyInfo.getActualDependents(source.getClassName());
+                if (actualDependents == null) {
+                    fullRebuildNeeded = "change to " + source.getClassName() + " requires full rebuild";
+                    return;
+                }
+                for (String d : actualDependents) {
+                    JavaSourceClass dSource = mapper.toJavaSourceClass(d);
+                    compiler.addStaleClass(dSource);
+                    changedSourceOnly.include(dSource.getRelativePath());
+                }
+            }
+            if (name.endsWith(".jar")) {
+                fullRebuildNeeded = "change to " + inputFile + " requires full rebuild";
+                return;
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SelectiveJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SelectiveJavaCompiler.java
new file mode 100644
index 0000000..cdc9959
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SelectiveJavaCompiler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.Clock;
+
+import java.io.File;
+import java.util.Set;
+
+public class SelectiveJavaCompiler implements Compiler<JavaCompileSpec> {
+    private Compiler<JavaCompileSpec> compiler;
+    private final FileTree destinationDir;
+    private final PatternSet staleClasses = new PatternSet();
+    private final static Logger LOG = Logging.getLogger(SelectiveJavaCompiler.class);
+
+    public SelectiveJavaCompiler(Compiler<JavaCompileSpec> compiler, FileTree destinationDir) {
+        this.compiler = compiler;
+        this.destinationDir = destinationDir;
+    }
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        return compiler.execute(spec);
+    }
+
+    void deleteStaleClasses() {
+        Clock clock = new Clock();
+        Set<File> files = destinationDir.matching(staleClasses).getFiles();
+        for (File file : files) {
+            file.delete();
+        }
+        LOG.lifecycle("Deleting {} stale classes took {}", files.size(), clock.getTime());
+    }
+
+    public void addStaleClass(JavaSourceClass source) {
+        addStaleClass(source.getClassName());
+    }
+
+    public void addStaleClass(String className) {
+        String path = className.replaceAll("\\.", "/");
+        String cls = path.concat(".class");
+        String inner = path.concat("$*.class");
+        staleClasses.include(cls);
+        staleClasses.include(inner);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SpecificClassesRebuildInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SpecificClassesRebuildInfo.java
new file mode 100644
index 0000000..dabc133
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/SpecificClassesRebuildInfo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental;
+
+public class SpecificClassesRebuildInfo extends DefaultRebuildInfo {
+    public SpecificClassesRebuildInfo(JarDelta jarDelta) {
+        super(false, jarDelta.getChangedClasses());
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysis.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysis.java
new file mode 100644
index 0000000..ac2fd2d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysis.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+import java.util.List;
+
+public class ClassAnalysis {
+    private final List<String> classDependencies;
+    private boolean dependentToAll;
+
+    public ClassAnalysis(List<String> classDependencies, boolean dependentToAll) {
+        this.classDependencies = classDependencies;
+        this.dependentToAll = dependentToAll;
+    }
+
+    public List<String> getClassDependencies() {
+        return classDependencies;
+    }
+
+    public boolean isDependentToAll() {
+        return dependentToAll;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesAnalyzer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesAnalyzer.java
new file mode 100644
index 0000000..44686cc
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesAnalyzer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Type;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ClassDependenciesAnalyzer {
+
+    public ClassAnalysis getClassAnalysis(String className, InputStream input) throws IOException {
+        ClassRelevancyFilter filter = new ClassRelevancyFilter(className);
+        ClassReader reader = new ClassReader(input);
+        ClassDependenciesVisitor visitor = new ClassDependenciesVisitor();
+        reader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+
+        List<String> classDependencies = getClassDependencies(filter, reader);
+        return new ClassAnalysis(classDependencies, visitor.dependentToAll);
+    }
+
+    private List<String> getClassDependencies(ClassRelevancyFilter filter, ClassReader reader) {
+        List<String> out = new LinkedList<String>();
+        char[] charBuffer = new char[reader.getMaxStringLength()];
+        for (int i = 1; i < reader.getItemCount(); i++) {
+            int itemOffset = reader.getItem(i);
+            if (itemOffset > 0 && reader.readByte(itemOffset - 1) == 7) {
+                // A CONSTANT_Class entry, read the class descriptor
+                String classDescriptor = reader.readUTF8(itemOffset, charBuffer);
+                Type type = Type.getObjectType(classDescriptor);
+                while (type.getSort() == Type.ARRAY) {
+                    type = type.getElementType();
+                }
+                if (type.getSort() != Type.OBJECT) {
+                    // A primitive type
+                    continue;
+                }
+                String name = type.getClassName();
+                if (filter.isRelevant(name)) {
+                    out.add(name);
+                }
+            }
+        }
+        return out;
+    }
+
+    public ClassAnalysis getClassAnalysis(String className, File classFile) throws IOException {
+        FileInputStream input = new FileInputStream(classFile);
+        try {
+            return getClassAnalysis(className, input);
+        } finally {
+            input.close();
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesVisitor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesVisitor.java
new file mode 100644
index 0000000..274c243
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ClassDependenciesVisitor extends ClassVisitor {
+
+    private final static int API = Opcodes.ASM4;
+    boolean dependentToAll;
+
+    public ClassDependenciesVisitor() {
+        super(API);
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        if (isAnnotationType(interfaces)) {
+            dependentToAll = true;
+        }
+    }
+
+    private boolean isAnnotationType(String[] interfaces) {
+        return interfaces.length == 1 && interfaces[0].equals("java/lang/annotation/Annotation");
+    }
+
+    @Override
+    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+        if (isConstant(access) && !isPrivate(access)) {
+            dependentToAll = true; //non-private const
+        }
+        return null;
+    }
+
+    private static boolean isPrivate(int access) {
+        return (access & Opcodes.ACC_PRIVATE) != 0;
+    }
+
+    private static boolean isConstant(int access) {
+        return (access & Opcodes.ACC_FINAL) != 0 && (access & Opcodes.ACC_STATIC) != 0;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassRelevancyFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassRelevancyFilter.java
new file mode 100644
index 0000000..e163c2b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassRelevancyFilter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class ClassRelevancyFilter {
+
+    private String excludedClassName;
+
+    public ClassRelevancyFilter(String excludedClassName) {
+        this.excludedClassName = excludedClassName;
+    }
+
+    boolean isRelevant(String className) {
+        return !className.startsWith("java.") && !excludedClassName.equals(className);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfo.java
new file mode 100644
index 0000000..e5414a1
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfo.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.graph;
+
+import org.gradle.api.internal.tasks.compile.incremental.ClassDependents;
+
+import java.io.Serializable;
+import java.util.*;
+
+public class ClassDependencyInfo implements Serializable {
+
+    private final Map<String, ClassDependents> dependents;
+
+    public ClassDependencyInfo(Map<String, ClassDependents> dependents) {
+        this.dependents = dependents;
+    }
+
+    public Set<String> getActualDependents(String className) {
+        Set<String> out = new HashSet<String>();
+        Set<String> visited = new HashSet<String>();
+        ClassDependents deps = dependents.get(className);
+        if (deps == null) {
+            return Collections.emptySet();
+        }
+        if (deps.isDependentToAll()) {
+            return null;
+        }
+        recurseDependents(visited, out, className, deps);
+        out.remove(className);
+        return out;
+    }
+
+    private void recurseDependents(Set<String> visited, Collection<String> accumulator, String className, ClassDependents incomingDependents) {
+        if (!visited.add(className) || incomingDependents == null) {
+            return;
+        }
+        for (String dependent : incomingDependents.getDependentClasses()) {
+            if (!dependent.contains("$") && !dependent.equals(className)) { //naive
+                accumulator.add(dependent);
+            }
+            ClassDependents currentDependents = this.dependents.get(dependent);
+            recurseDependents(visited, accumulator, dependent, currentDependents);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoExtractor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoExtractor.java
new file mode 100644
index 0000000..dd2543a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoExtractor.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.graph;
+
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.internal.tasks.compile.incremental.ClassDependents;
+import org.gradle.api.internal.tasks.compile.incremental.ClassNameProvider;
+import org.gradle.api.internal.tasks.compile.incremental.analyzer.ClassAnalysis;
+import org.gradle.api.internal.tasks.compile.incremental.analyzer.ClassDependenciesAnalyzer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class ClassDependencyInfoExtractor {
+
+    private File classesDir;
+
+    public ClassDependencyInfoExtractor(File classesDir) {
+        this.classesDir = classesDir;
+    }
+
+    public ClassDependencyInfo extractInfo(String packagePrefix) {
+        Map<String, ClassDependents> dependents = new HashMap<String, ClassDependents>();
+        Iterator output = FileUtils.iterateFiles(classesDir, new String[]{"class"}, true);
+        ClassNameProvider nameProvider = new ClassNameProvider(classesDir);
+        while (output.hasNext()) {
+            File classFile = (File) output.next();
+            String className = nameProvider.provideName(classFile);
+            if (!className.startsWith(packagePrefix)) {
+                continue;
+            }
+            try {
+                ClassAnalysis analysis = new ClassDependenciesAnalyzer().getClassAnalysis(className, classFile);
+                for (String dependency : analysis.getClassDependencies()) {
+                    if (!dependency.equals(className) && dependency.startsWith(packagePrefix)) {
+                        getOrCreateDependentMapping(dependents, dependency).addClass(className);
+                    }
+                }
+                if (analysis.isDependentToAll()) {
+                    getOrCreateDependentMapping(dependents, className).setDependentToAll();
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Problems extracting class dependency from " + classFile, e);
+            }
+        }
+        return new ClassDependencyInfo(dependents);
+    }
+
+    private ClassDependents getOrCreateDependentMapping(Map<String, ClassDependents> dependents, String dependency) {
+        ClassDependents d = dependents.get(dependency);
+        if (d == null) {
+            d = new ClassDependents();
+            dependents.put(dependency, d);
+        }
+        return d;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoSerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoSerializer.java
new file mode 100644
index 0000000..cde3b1e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.graph;
+
+import org.gradle.api.internal.tasks.compile.incremental.DummySerializer;
+
+import java.io.File;
+
+public class ClassDependencyInfoSerializer {
+    private File storage;
+
+    public ClassDependencyInfoSerializer(File storage) {
+        this.storage = storage;
+    }
+
+    public void writeInfo(ClassDependencyInfo info) {
+        //TODO SF this needs to use our standard serialization machinery
+        DummySerializer.writeTargetTo(storage, info);
+    }
+
+    public ClassDependencyInfo readInfo() {
+        return (ClassDependencyInfo) DummySerializer.readFrom(storage);
+    }
+
+    public boolean isInfoAvailable() {
+        return storage.isFile();
+    }
+
+    @Override
+    public String toString() {
+        return storage.toString();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java
index 4ec606b..5baf594 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java
@@ -15,10 +15,14 @@
  */
 package org.gradle.api.internal.tasks.compile.jdk6;
 
-import org.gradle.api.internal.tasks.compile.*;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.internal.tasks.compile.CompilationFailedException;
 import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
+import org.gradle.api.internal.tasks.compile.JavaCompilerArgumentsBuilder;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.internal.jvm.Jvm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -26,6 +30,7 @@ import javax.tools.JavaCompiler;
 import javax.tools.JavaFileObject;
 import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
+import java.io.File;
 import java.io.Serializable;
 import java.nio.charset.Charset;
 import java.util.List;
@@ -34,7 +39,7 @@ public class Jdk6JavaCompiler implements Compiler<JavaCompileSpec>, Serializable
     private static final Logger LOGGER = LoggerFactory.getLogger(Jdk6JavaCompiler.class);
 
     public WorkResult execute(JavaCompileSpec spec) {
-        LOGGER.info("Compiling with JDK 6 Java compiler API.");
+        LOGGER.info("Compiling with JDK Java compiler API.");
 
         JavaCompiler.CompilationTask task = createCompileTask(spec);
         boolean success = task.call();
@@ -47,7 +52,7 @@ public class Jdk6JavaCompiler implements Compiler<JavaCompileSpec>, Serializable
 
     private JavaCompiler.CompilationTask createCompileTask(JavaCompileSpec spec) {
         List<String> options = new JavaCompilerArgumentsBuilder(spec).build();
-        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        JavaCompiler compiler = findCompiler();
         if(compiler==null){
             throw new RuntimeException("Cannot find System Java Compiler. Ensure that you have installed a JDK (not just a JRE) and configured your JAVA_HOME system variable to point to the according directory.");
         }
@@ -56,4 +61,19 @@ public class Jdk6JavaCompiler implements Compiler<JavaCompileSpec>, Serializable
         Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(spec.getSource());
         return compiler.getTask(null, null, null, options, null, compilationUnits);
     }
+
+    private static JavaCompiler findCompiler() {
+        File realJavaHome = Jvm.current().getJavaHome();
+        File javaHomeFromToolProvidersPointOfView = new File(System.getProperty("java.home"));
+        if (realJavaHome.equals(javaHomeFromToolProvidersPointOfView)) {
+            return ToolProvider.getSystemJavaCompiler();
+        }
+
+        System.setProperty("java.home", realJavaHome.getAbsolutePath());
+        try {
+            return ToolProvider.getSystemJavaCompiler();
+        } finally {
+            System.setProperty("java.home", javaHomeFromToolProvidersPointOfView.getAbsolutePath());
+        }
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultJUnitXmlReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultJUnitXmlReport.java
new file mode 100644
index 0000000..3fd7bf4
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultJUnitXmlReport.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleDirectoryReport;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.testing.JUnitXmlReport;
+
+public class DefaultJUnitXmlReport extends TaskGeneratedSingleDirectoryReport implements JUnitXmlReport {
+
+    private boolean outputPerTestCase;
+
+    public DefaultJUnitXmlReport(String name, Task task) {
+        super(name, task, null);
+    }
+
+    @Input
+    public boolean isOutputPerTestCase() {
+        return outputPerTestCase;
+    }
+
+    public void setOutputPerTestCase(boolean outputPerTestCase) {
+        this.outputPerTestCase = outputPerTestCase;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java
index 10dfd54..f2c807a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java
@@ -17,9 +17,6 @@ package org.gradle.api.internal.tasks.testing;
 
 import org.apache.commons.lang.StringUtils;
 
-/**
- * @author Tom Eyckmans
- */
 public class DefaultTestClassRunInfo implements TestClassRunInfo {
     private String testClassName;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java
index 3fa55ae..6952a83 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java
@@ -37,4 +37,32 @@ public class DefaultTestOutputEvent implements Serializable, TestOutputEvent {
     public String getMessage() {
         return message;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultTestOutputEvent that = (DefaultTestOutputEvent) o;
+
+        if (destination != that.destination) {
+            return false;
+        }
+        if (!message.equals(that.message)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = destination.hashCode();
+        result = 31 * result + message.hashCode();
+        return result;
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestTaskReports.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestTaskReports.java
new file mode 100644
index 0000000..131acb0
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestTaskReports.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.ConfigurableReport;
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleDirectoryReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.testing.TestTaskReports;
+
+public class DefaultTestTaskReports extends TaskReportContainer<Report> implements TestTaskReports {
+
+    public DefaultTestTaskReports(Task task) {
+        super(ConfigurableReport.class, task);
+
+        add(DefaultJUnitXmlReport.class, "junitXml", task);
+        add(TaskGeneratedSingleDirectoryReport.class, "html", task, "index.html");
+    }
+
+    public DirectoryReport getHtml() {
+        return (DirectoryReport) getByName("html");
+    }
+
+    @Nested
+    public DefaultJUnitXmlReport getJunitXml() {
+        return (DefaultJUnitXmlReport) getByName("junitXml");
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/NoMatchingTestsReporter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/NoMatchingTestsReporter.java
new file mode 100644
index 0000000..ffd578b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/NoMatchingTestsReporter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.TestListener;
+import org.gradle.api.tasks.testing.TestResult;
+
+public class NoMatchingTestsReporter implements TestListener {
+    private final String message;
+
+    public NoMatchingTestsReporter(String message) {
+        this.message = message;
+    }
+
+    public void beforeSuite(TestDescriptor suite) {}
+
+    public void afterSuite(TestDescriptor suite, TestResult result) {
+        if (suite.getParent() == null && result.getTestCount() == 0) {
+            throw new GradleException(message);
+        }
+    }
+
+    public void beforeTest(TestDescriptor testDescriptor) {}
+
+    public void afterTest(TestDescriptor testDescriptor, TestResult result) {}
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
index 42ec863..b80b085 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
@@ -33,10 +33,9 @@ public class SuiteTestClassProcessor implements TestClassProcessor {
     }
 
     public void startProcessing(TestResultProcessor testResultProcessor) {
-        resultProcessor = new AttachParentTestResultProcessor(testResultProcessor);
-        resultProcessor.started(suiteDescriptor, new TestStartEvent(timeProvider.getCurrentTime()));
-
         try {
+            resultProcessor = new AttachParentTestResultProcessor(testResultProcessor);
+            resultProcessor.started(suiteDescriptor, new TestStartEvent(timeProvider.getCurrentTime()));
             processor.startProcessing(resultProcessor);
         } catch (Throwable t) {
             resultProcessor.failure(suiteDescriptor.getId(), new TestSuiteExecutionException(String.format(
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
index 68a04dd..db80b27 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
@@ -16,12 +16,10 @@
 
 package org.gradle.api.internal.tasks.testing;
 
-import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.Stoppable;
 
 /**
  * A processor for executing tests. Implementations are not required to be thread-safe.
- *
- * @author Tom Eyckmans
  */
 public interface TestClassProcessor extends Stoppable {
     /**
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.java
index c553c4e..1dfee50 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.java
@@ -17,9 +17,6 @@ package org.gradle.api.internal.tasks.testing;
 
 import java.io.Serializable;
 
-/**
- * @author Tom Eyckmans
- */
 public interface TestClassRunInfo extends Serializable {
     String getTestClassName();
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.java
index ea1b323..7a2bfda 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing;
 
+import org.gradle.api.Nullable;
 import org.gradle.api.tasks.testing.TestResult;
 
 import java.io.Serializable;
@@ -37,6 +38,7 @@ public class TestCompleteEvent implements Serializable {
         return endTime;
     }
 
+    @Nullable
     public TestResult.ResultType getResultType() {
         return resultType;
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
index 6074f56..10a4bea 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
@@ -21,9 +21,6 @@ import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
 import org.gradle.api.tasks.testing.TestFrameworkOptions;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
-/**
- * @author Tom Eyckmans
- */
 public interface TestFramework {
 
     /**
@@ -36,7 +33,7 @@ public interface TestFramework {
 
     /**
      * Returns a factory which is used to create a {@link org.gradle.api.internal.tasks.testing.TestClassProcessor} in
-     * each worker process. This factory is serialized across to the worker process, and then it's {@link
+     * each worker process. This factory is serialized across to the worker process, and then its {@link
      * org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory#create(org.gradle.internal.service.ServiceRegistry)}
      * method is called to create the test processor.
      */
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java
index f762275..0ee93a0 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.tasks.testing;
 
+import org.gradle.api.Nullable;
+
 import java.io.Serializable;
 
 public class TestStartEvent implements Serializable {
@@ -35,6 +37,7 @@ public class TestStartEvent implements Serializable {
         return startTime;
     }
 
+    @Nullable
     public Object getParentId() {
         return parentId;
     }
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 ba6589b..88a438b 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
@@ -31,9 +31,6 @@ import java.io.FileInputStream;
 import java.io.InputStream;
 import java.util.*;
 
-/**
- * @author Tom Eyckmans
- */
 public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor> implements TestFrameworkDetector {
     protected static final String TEST_CASE = "junit/framework/TestCase";
     protected static final String GROOVY_TEST_CASE = "groovy/util/GroovyTestCase";
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
index 2088cd8..93e1d84 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
@@ -30,8 +30,6 @@ import java.util.*;
 
 /**
  * This class manages class file extraction from library jar files.
- *
- * @author Tom Eyckmans
  */
 public class ClassFileExtractionManager {
     private static final Logger LOGGER = LoggerFactory.getLogger(ClassFileExtractionManager.class);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
index 88b5254..7e5a168 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
@@ -28,8 +28,6 @@ import java.io.File;
 /**
  * The default test class scanner. Depending on the availability of a test framework detector,
  * a detection or filename scan is performed to find test classes.
- *
- * @author Tom Eyckmans
  */
 public class DefaultTestClassScanner implements Runnable {
     private final FileTree candidateClassFiles;
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 ed8ea46..5af69d8 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
@@ -33,8 +33,6 @@ import org.gradle.process.internal.WorkerProcessBuilder;
 
 /**
  * The default test class scanner factory.
- *
- * @author Tom Eyckmans
  */
 public class DefaultTestExecuter implements TestExecuter {
     private final Factory<WorkerProcessBuilder> workerFactory;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageListener.java
index 99e9dfa..71a88ae 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageListener.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageListener.java
@@ -16,9 +16,6 @@
 
 package org.gradle.api.internal.tasks.testing.detection;
 
-/**
- * @author Tom Eyckmans
- */
 public interface JarFilePackageListener {
     void receivePackage(String packageName);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageLister.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageLister.java
index 3bd4b96..fa3e984 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageLister.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/JarFilePackageLister.java
@@ -24,9 +24,6 @@ import java.util.Enumeration;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-/**
- * @author Tom Eyckmans
- */
 public class JarFilePackageLister {
     public void listJarPackages(File jarFile, JarFilePackageListener listener) {
         if (jarFile == null) {
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 f26fd7a..b8bcd5a 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
@@ -21,8 +21,6 @@ import org.objectweb.asm.Opcodes;
 
 /**
  * Base class for ASM test class scanners.
- *
- * @author Tom Eyckmans
  */
 public abstract class TestClassVisitor extends ClassVisitor {
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.java
index 012ea2f..8ab500b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.java
@@ -19,9 +19,6 @@ package org.gradle.api.internal.tasks.testing.detection;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.tasks.testing.Test;
 
-/**
- * @author Tom Eyckmans
- */
 public interface TestExecuter {
     void execute(Test testTask, TestResultProcessor testResultProcessor);
 }
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 5134366..92394d7 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
@@ -20,9 +20,6 @@ import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 
 import java.io.File;
 
-/**
- * @author Tom Eyckmans
- */
 public interface TestFrameworkDetector {
     void startDetection(TestClassProcessor testClassProcessor);
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/filter/DefaultTestFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/filter/DefaultTestFilter.java
new file mode 100644
index 0000000..18a2c8b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/filter/DefaultTestFilter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.filter;
+
+import com.google.common.collect.Sets;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.testing.TestFilter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DefaultTestFilter implements TestFilter {
+
+    private Set<String> testNames = new HashSet<String>();
+
+    private void validateName(String name) {
+        if (name == null || name.length() == 0) {
+            throw new InvalidUserDataException("Selected test name cannot be null or empty.");
+        }
+    }
+
+    public TestFilter includeTestsMatching(String testNamePattern) {
+        validateName(testNamePattern);
+        testNames.add(testNamePattern);
+        return this;
+    }
+
+    @Input
+    public Set<String> getIncludePatterns() {
+        return testNames;
+    }
+
+    public TestFilter setIncludePatterns(String... testNamePatterns) {
+        for (String name : testNamePatterns) {
+            validateName(name);
+        }
+        this.testNames = Sets.newHashSet(testNamePatterns);
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/filter/TestSelectionMatcher.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/filter/TestSelectionMatcher.java
new file mode 100644
index 0000000..e8d1d72
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/filter/TestSelectionMatcher.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.filter;
+
+import com.google.common.base.Splitter;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class TestSelectionMatcher {
+
+    private List<Pattern> includePatterns = new LinkedList<Pattern>();
+
+    public TestSelectionMatcher(Iterable<String> includedTests) {
+        for (String includedTest : includedTests) {
+            includePatterns.add(preparePattern(includedTest));
+        }
+    }
+
+    private Pattern preparePattern(String input) {
+        StringBuilder pattern = new StringBuilder();
+        Iterable<String> split = Splitter.on('*').split(input);
+        for (String s : split) {
+            if (s.equals("")) {
+                pattern.append(".*"); //replace wildcard '*' with '.*'
+            } else {
+                if (pattern.length() > 0) {
+                    pattern.append(".*"); //replace wildcard '*' with '.*'
+                }
+                pattern.append(Pattern.quote(s)); //quote everything else
+            }
+        }
+        return Pattern.compile(pattern.toString());
+    }
+
+    public boolean matchesTest(String className, String methodName) {
+        String fullName = className + "." + methodName;
+        for (Pattern pattern : includePatterns) {
+            if (methodName != null && pattern.matcher(fullName).matches()) {
+                return true;
+            }
+            if (pattern.matcher(className).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java
new file mode 100644
index 0000000..685da83
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.testing.junit;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This filter is used for filtering classes and methods that are annotated with the @Category annotation.
+ *
+ */
+class CategoryFilter extends Filter {
+    // the way filters are implemented makes this unnecessarily complicated,
+    // buggy, and difficult to specify.  A new way of handling filters could
+    // someday enable a better new implementation.
+    // https://github.com/junit-team/junit/issues/172
+
+    private final Set<Class<?>> inclusions;
+    private final Set<Class<?>> exclusions;
+
+    public CategoryFilter(final Set<Class<?>> inclusions, final Set<Class<?>> exclusions) {
+        this.inclusions = inclusions;
+        this.exclusions = exclusions;
+    }
+
+    @Override
+    public boolean shouldRun(final Description description) {
+        return shouldRun(description, description.isSuite() ? null : Description.createSuiteDescription(description.getTestClass()));
+    }
+
+    private boolean shouldRun(final Description description, final Description parent) {
+        final Set<Class<?>> categories = new HashSet<Class<?>>();
+        Category annotation = description.getAnnotation(Category.class);
+        if (annotation != null) {
+            categories.addAll(Arrays.asList(annotation.value()));
+        }
+
+        if (parent != null) {
+            annotation = parent.getAnnotation(Category.class);
+            if (annotation != null) {
+                categories.addAll(Arrays.asList(annotation.value()));
+            }
+        }
+
+        boolean result = inclusions.isEmpty();
+
+        for (Class<?> category : categories) {
+            if (matches(category, inclusions)) {
+                result = true;
+                break;
+            }
+        }
+
+        if (result) {
+            for (Class<?> category : categories) {
+                if (matches(category, exclusions)) {
+                    result = false;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+
+    private boolean matches(final Class<?> category, final Set<Class<?>> categories) {
+        for (Class<?> cls : categories) {
+            if (cls.isAssignableFrom(category)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public final String describe() {
+        StringBuilder sb = new StringBuilder();
+        if (!inclusions.isEmpty()) {
+            sb.append("(");
+            sb.append(StringUtils.join(inclusions, " OR "));
+            sb.append(")");
+            if (!exclusions.isEmpty()) {
+                sb.append(" AND ");
+            }
+        }
+        if (!exclusions.isEmpty()) {
+            sb.append("NOT (");
+            sb.append(StringUtils.join(exclusions, " OR "));
+            sb.append(")");
+        }
+
+        return sb.toString();
+    }
+}
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 a7fa601..2067040 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
@@ -23,9 +23,6 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 
-/**
- * @author Tom Eyckmans
- */
 public class JUnitDetector extends AbstractTestFrameworkDetector<JUnitTestClassDetecter> {
     private static final Logger LOGGER = LoggerFactory.getLogger(JUnitDetector.class);
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitSpec.java
new file mode 100644
index 0000000..18ba671
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitSpec.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.testing.junit;
+
+import org.gradle.api.tasks.testing.junit.JUnitOptions;
+
+import java.io.Serializable;
+import java.util.Set;
+
+public class JUnitSpec implements Serializable {
+
+    private final Set<String> includeCategories;
+    private final Set<String> excludeCategories;
+    private final Set<String> includedTests;
+
+    public JUnitSpec(final JUnitOptions options, Set<String> includedTests) {
+        this.includeCategories = options.getIncludeCategories();
+        this.excludeCategories = options.getExcludeCategories();
+        this.includedTests = includedTests;
+    }
+
+    public Set<String> getIncludeCategories() {
+        return includeCategories;
+    }
+
+    public Set<String> getExcludeCategories() {
+        return excludeCategories;
+    }
+
+    public boolean hasCategoryConfiguration() {
+        return !(excludeCategories.isEmpty() && includeCategories.isEmpty());
+    }
+
+    public Set<String> getIncludedTests() {
+        return includedTests;
+    }
+}
\ No newline at end of file
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 62c4204..e17160c 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
@@ -21,9 +21,6 @@ import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-/**
- * @author Tom Eyckmans
- */
 class JUnitTestClassDetecter extends TestClassVisitor {
     private boolean isAbstract;
     private String className;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
index f958a02..155ce5b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
@@ -16,7 +16,12 @@
 
 package org.gradle.api.internal.tasks.testing.junit;
 
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.tasks.testing.filter.TestSelectionMatcher;
 import org.gradle.internal.concurrent.ThreadSafe;
+import org.gradle.util.CollectionUtils;
+import org.junit.runner.Description;
 import org.junit.runner.Request;
 import org.junit.runner.Runner;
 import org.junit.runner.notification.RunListener;
@@ -25,12 +30,14 @@ import org.junit.runner.notification.RunNotifier;
 public class JUnitTestClassExecuter {
     private final ClassLoader applicationClassLoader;
     private final RunListener listener;
+    private final JUnitSpec options;
     private final TestClassExecutionListener executionListener;
 
-    public JUnitTestClassExecuter(ClassLoader applicationClassLoader, RunListener listener, TestClassExecutionListener executionListener) {
+    public JUnitTestClassExecuter(ClassLoader applicationClassLoader, JUnitSpec spec, RunListener listener, TestClassExecutionListener executionListener) {
         assert executionListener instanceof ThreadSafe;
         this.applicationClassLoader = applicationClassLoader;
         this.listener = listener;
+        this.options = spec;
         this.executionListener = executionListener;
     }
 
@@ -48,10 +55,53 @@ public class JUnitTestClassExecuter {
     }
 
     private void runTestClass(String testClassName) throws ClassNotFoundException {
-        Class<?> testClass = Class.forName(testClassName, true, applicationClassLoader);
-        Runner runner = Request.aClass(testClass).getRunner();
-        RunNotifier notifier = new RunNotifier();
-        notifier.addListener(listener);
-        runner.run(notifier);
+        final Class<?> testClass = Class.forName(testClassName, true, applicationClassLoader);
+        Request request = Request.aClass(testClass);
+        if (options.hasCategoryConfiguration()) {
+            Transformer<Class<?>, String> transformer = new Transformer<Class<?>, String>() {
+                public Class<?> transform(final String original) {
+                    try {
+                        return applicationClassLoader.loadClass(original);
+                    } catch (ClassNotFoundException e) {
+                        throw new InvalidUserDataException(String.format("Can't load category class [%s].", original), e);
+                    }
+                }
+            };
+            request = request.filterWith(new CategoryFilter(
+                    CollectionUtils.collect(options.getIncludeCategories(), transformer),
+                    CollectionUtils.collect(options.getExcludeCategories(), transformer)
+            ));
+        }
+
+        if (!options.getIncludedTests().isEmpty()) {
+            request = request.filterWith(new MethodNameFilter(options.getIncludedTests()));
+        }
+
+        Runner runner = request.getRunner();
+        //In case of no matching methods junit will return a ErrorReportingRunner for org.junit.runner.manipulation.Filter.class.
+        //Will be fixed with adding class filters
+        if (!org.junit.runner.manipulation.Filter.class.getName().equals(runner.getDescription().getDisplayName())) {
+            RunNotifier notifier = new RunNotifier();
+            notifier.addListener(listener);
+            runner.run(notifier);
+        }
+    }
+
+    private static class MethodNameFilter extends org.junit.runner.manipulation.Filter {
+
+        private final TestSelectionMatcher matcher;
+
+        public MethodNameFilter(Iterable<String> includedTests) {
+            matcher = new TestSelectionMatcher(includedTests);
+        }
+
+        @Override
+        public boolean shouldRun(Description description) {
+            return matcher.matchesTest(description.getClassName(), description.getMethodName());
+        }
+
+        public String describe() {
+            return "Includes matching test methods";
+        }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
index 1ff8140..537fbdf 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
@@ -30,22 +30,20 @@ import org.gradle.messaging.actor.ActorFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-
 public class JUnitTestClassProcessor implements TestClassProcessor {
     private static final Logger LOGGER = LoggerFactory.getLogger(JUnitTestClassProcessor.class);
-    private final File testResultsDir;
     private final IdGenerator<?> idGenerator;
     private final ActorFactory actorFactory;
     private final StandardOutputRedirector outputRedirector;
     private final TimeProvider timeProvider = new TrueTimeProvider();
+    private final JUnitSpec spec;
     private JUnitTestClassExecuter executer;
     private Actor resultProcessorActor;
 
-    public JUnitTestClassProcessor(File testResultsDir, IdGenerator<?> idGenerator, ActorFactory actorFactory,
+    public JUnitTestClassProcessor(JUnitSpec spec, IdGenerator<?> idGenerator, ActorFactory actorFactory,
                                    StandardOutputRedirector standardOutputRedirector) {
-        this.testResultsDir = testResultsDir;
         this.idGenerator = idGenerator;
+        this.spec = spec;
         this.actorFactory = actorFactory;
         this.outputRedirector = standardOutputRedirector;
     }
@@ -63,7 +61,7 @@ public class JUnitTestClassProcessor implements TestClassProcessor {
 
         // Build the JUnit adaptor stuff
         JUnitTestEventAdapter junitEventAdapter = new JUnitTestEventAdapter(threadSafeResultProcessor, timeProvider, idGenerator);
-        executer = new JUnitTestClassExecuter(applicationClassLoader, junitEventAdapter, threadSafeTestClassListener);
+        executer = new JUnitTestClassExecuter(applicationClassLoader, spec, junitEventAdapter, threadSafeTestClassListener);
     }
 
     public void processTestClass(TestClassRunInfo testClass) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
index 878ac76..3855fa2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
@@ -50,7 +50,7 @@ public class JUnitTestEventAdapter extends RunListener {
 
     @Override
     public void testStarted(Description description) throws Exception {
-        TestDescriptorInternal descriptor = descriptor(idGenerator.generateId(), description);
+        TestDescriptorInternal descriptor = nullSafeDescriptor(idGenerator.generateId(), description);
         synchronized (lock) {
             TestDescriptorInternal oldTest = executing.put(description, descriptor);
             assert oldTest == null : String.format("Unexpected start event for %s", description);
@@ -68,7 +68,7 @@ public class JUnitTestEventAdapter extends RunListener {
         if (testInternal == null) {
             // This can happen when, for example, a @BeforeClass or @AfterClass method fails
             needEndEvent = true;
-            testInternal = failureDescriptor(idGenerator.generateId(), failure.getDescription());
+            testInternal = nullSafeDescriptor(idGenerator.generateId(), failure.getDescription());
             resultProcessor.started(testInternal, startEvent());
         }
         resultProcessor.failure(testInternal.getId(), failure.getException());
@@ -132,7 +132,7 @@ public class JUnitTestEventAdapter extends RunListener {
         return new DefaultTestDescriptor(id, className(description), methodName(description));
     }
 
-    private TestDescriptorInternal failureDescriptor(Object id, Description description) {
+    private TestDescriptorInternal nullSafeDescriptor(Object id, Description description) {
         if (methodName(description) != null) {
             return new DefaultTestDescriptor(id, className(description), methodName(description));
         } else {
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 e2dbdda..30634b7 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,37 +17,73 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.Action;
+import org.gradle.api.GradleException;
 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.filter.DefaultTestFilter;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
-import java.io.File;
 import java.io.Serializable;
+import java.net.URLClassLoader;
 
-/**
- * @author Tom Eyckmans
- */
 public class JUnitTestFramework implements TestFramework {
     private JUnitOptions options;
     private JUnitDetector detector;
     private final Test testTask;
+    private DefaultTestFilter filter;
 
-    public JUnitTestFramework(Test testTask) {
+    public JUnitTestFramework(Test testTask, DefaultTestFilter filter) {
         this.testTask = testTask;
+        this.filter = filter;
         options = new JUnitOptions();
         detector = new JUnitDetector(new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
-        final File testResultsDir = testTask.getTestResultsDir();
-        return new TestClassProcessorFactoryImpl(testResultsDir);
+        verifyJUnitCategorySupport();
+        verifyJUnitFilteringSupport();
+        return new TestClassProcessorFactoryImpl(new JUnitSpec(options, filter.getIncludePatterns()));
+    }
+
+    private void verifyJUnitCategorySupport() {
+        if (!options.getExcludeCategories().isEmpty() || !options.getIncludeCategories().isEmpty()) {
+            try {
+                getTestClassLoader().loadClass("org.junit.experimental.categories.Category");
+            } catch (ClassNotFoundException e) {
+                throw new GradleException("JUnit Categories defined but declared JUnit version does not support Categories.");
+            }
+        }
+    }
+
+    private void verifyJUnitFilteringSupport() {
+        if (!filter.getIncludePatterns().isEmpty()) {
+            try {
+                Class<?> descriptionClass = getTestClassLoader().loadClass("org.junit.runner.Description");
+                descriptionClass.getMethod("getClassName");
+            } catch (ClassNotFoundException e) { //JUnit 3.8.1
+                filteringNotSupported();
+            } catch (NoSuchMethodException e) { //JUnit 4.5-
+                filteringNotSupported();
+            } catch (Exception e) {
+                throw new RuntimeException("Problem encountered when detecting support for JUnit filtering.", e);
+            }
+        }
+    }
+
+    private URLClassLoader getTestClassLoader() {
+        return new URLClassLoader(new DefaultClassPath(testTask.getClasspath()).getAsURLArray(), null);
+    }
+
+    private void filteringNotSupported() {
+        throw new GradleException("Test filtering is not supported for given version of JUnit. Please upgrade JUnit version to at least 4.6.");
     }
 
     public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
@@ -73,14 +109,14 @@ public class JUnitTestFramework implements TestFramework {
     }
 
     private static class TestClassProcessorFactoryImpl implements WorkerTestClassProcessorFactory, Serializable {
-        private final File testResultsDir;
+        private final JUnitSpec spec;
 
-        public TestClassProcessorFactoryImpl(File testResultsDir) {
-            this.testResultsDir = testResultsDir;
+        public TestClassProcessorFactoryImpl(JUnitSpec spec) {
+            this.spec = spec;
         }
 
         public TestClassProcessor create(ServiceRegistry serviceRegistry) {
-            return new JUnitTestClassProcessor(testResultsDir, serviceRegistry.get(IdGenerator.class), serviceRegistry.get(ActorFactory.class), new JULRedirector());
+            return new JUnitTestClassProcessor(spec, serviceRegistry.get(IdGenerator.class), serviceRegistry.get(ActorFactory.class), new JULRedirector());
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java
index ace1986..1b84358 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
@@ -19,9 +19,6 @@ import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-/**
- * @author Tom Eyckmans
- */
 class JUnitTestMethodDetecter extends MethodVisitor {
 
     private final JUnitTestClassDetecter testClassDetecter;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResults.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResults.java
index dc585ac..4752af9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResults.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResults.java
@@ -34,17 +34,22 @@ public class AllTestResults extends CompositeTestResults {
         return "Test Summary";
     }
 
+    @Override
+    public String getBaseUrl() {
+        return "index.html";
+    }
+
     public Collection<PackageTestResults> getPackages() {
         return packages.values();
     }
 
-    public TestResult addTest(String className, String testName, long duration) {
+    public TestResult addTest(long classId, String className, String testName, long duration) {
         PackageTestResults packageResults = addPackageForClass(className);
-        return addTest(packageResults.addTest(className, testName, duration));
+        return addTest(packageResults.addTest(classId, className, testName, duration));
     }
 
-    public ClassTestResults addTestClass(String className) {
-        return addPackageForClass(className).addClass(className);
+    public ClassTestResults addTestClass(long classId, String className) {
+        return addPackageForClass(className).addClass(classId, className);
     }
 
     private PackageTestResults addPackageForClass(String className) {
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 13320db..209f2fe 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
@@ -15,30 +15,33 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.gradle.api.internal.ErroringAction;
+import org.gradle.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+import org.gradle.api.internal.tasks.testing.junit.result.TestFailure;
 import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
 import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.internal.SystemProperties;
 import org.gradle.reporting.CodePanelRenderer;
-import org.gradle.api.internal.html.SimpleHtmlWriter;
+import org.gradle.util.GUtil;
 
 import java.io.IOException;
 
 class ClassPageRenderer extends PageRenderer<ClassTestResults> {
     private final CodePanelRenderer codePanelRenderer = new CodePanelRenderer();
     private final TestResultsProvider resultsProvider;
-    private final String className;
+    private final long classId;
 
-    public ClassPageRenderer(String className, TestResultsProvider provider) {
-        this.className = className;
+    public ClassPageRenderer(long classId, TestResultsProvider provider) {
+        this.classId = classId;
         this.resultsProvider = provider;
     }
 
     @Override
     protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
         htmlWriter.startElement("div").attribute("class", "breadcrumbs")
-            .startElement("a").attribute("href", "index.html").characters("all").endElement()
+            .startElement("a").attribute("href", getResults().getUrlTo(getResults().getParent().getParent())).characters("all").endElement()
             .characters(" > ")
-            .startElement("a").attribute("href", String.format("%s.html", getResults().getPackageResults().getName())).characters(getResults().getPackageResults().getName()).endElement()
+            .startElement("a").attribute("href", getResults().getUrlTo(getResults().getPackageResults())).characters(getResults().getPackageResults().getName()).endElement()
             .characters(String.format(" > %s", getResults().getSimpleName()))
         .endElement();
     }
@@ -70,7 +73,13 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
                 .startElement("a").attribute("name", test.getId().toString()).characters("").endElement() //browsers dont understand <a name="..."/>
                 .startElement("h3").attribute("class", test.getStatusClass()).characters(test.getName()).endElement();
             for (TestFailure failure : test.getFailures()) {
-                codePanelRenderer.render(failure.getStackTrace(), htmlWriter);
+                String message;
+                if (GUtil.isTrue(failure.getMessage()) && !failure.getStackTrace().contains(failure.getMessage())) {
+                    message = failure.getMessage() + SystemProperties.getLineSeparator() + SystemProperties.getLineSeparator() + failure.getStackTrace();
+                } else {
+                    message = failure.getStackTrace();
+                }
+                codePanelRenderer.render(message, htmlWriter);
             }
             htmlWriter.endElement();
         }
@@ -84,27 +93,27 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
                 renderTests(writer);
             }
         });
-        if (resultsProvider.hasOutput(className, TestOutputEvent.Destination.StdOut)) {
+        if (resultsProvider.hasOutput(classId, TestOutputEvent.Destination.StdOut)) {
             addTab("Standard output", new ErroringAction<SimpleHtmlWriter>() {
                 @Override
                 protected void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
                     htmlWriter.startElement("span").attribute("class", "code")
                         .startElement("pre")
                         .characters("");
-                    resultsProvider.writeOutputs(className, TestOutputEvent.Destination.StdOut, htmlWriter);
+                    resultsProvider.writeAllOutput(classId, TestOutputEvent.Destination.StdOut, htmlWriter);
                         htmlWriter.endElement()
                     .endElement();
                 }
             });
         }
-        if (resultsProvider.hasOutput(className, TestOutputEvent.Destination.StdErr)) {
+        if (resultsProvider.hasOutput(classId, TestOutputEvent.Destination.StdErr)) {
             addTab("Standard error", new ErroringAction<SimpleHtmlWriter>() {
                 @Override
                 protected void doExecute(SimpleHtmlWriter element) throws Exception {
                     element.startElement("span").attribute("class", "code")
                     .startElement("pre")
                         .characters("");
-                    resultsProvider.writeOutputs(className, TestOutputEvent.Destination.StdErr, element);
+                    resultsProvider.writeAllOutput(classId, TestOutputEvent.Destination.StdErr, element);
                     element.endElement()
                     .endElement();
                 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java
index 4dc2e2a..78b385d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.apache.commons.lang.StringUtils;
+import org.gradle.internal.FileUtils;
 
 import java.util.Collection;
 import java.util.Set;
@@ -25,21 +26,32 @@ import java.util.TreeSet;
  * Test results for a given class.
  */
 public class ClassTestResults extends CompositeTestResults {
+    private final long id;
     private final String name;
     private final PackageTestResults packageResults;
     private final Set<TestResult> results = new TreeSet<TestResult>();
 
-    public ClassTestResults(String name, PackageTestResults packageResults) {
+    public ClassTestResults(long id, String name, PackageTestResults packageResults) {
         super(packageResults);
+        this.id = id;
         this.name = name;
         this.packageResults = packageResults;
     }
 
+    public long getId() {
+        return id;
+    }
+
     @Override
     public String getTitle() {
         return String.format("Class %s", name);
     }
 
+    @Override
+    public String getBaseUrl() {
+        return String.format("classes/%s.html", FileUtils.toSafeFileName(name));
+    }
+
     public String getName() {
         return name;
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResults.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResults.java
index 6bc0a34..5fc1301 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResults.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResults.java
@@ -25,12 +25,52 @@ public abstract class CompositeTestResults extends TestResultModel {
     private final CompositeTestResults parent;
     private int tests;
     private final Set<TestResult> failures = new TreeSet<TestResult>();
+    private final Set<TestResult> ignored = new TreeSet<TestResult>();
     private long duration;
 
     protected CompositeTestResults(CompositeTestResults parent) {
         this.parent = parent;
     }
 
+    public CompositeTestResults getParent() {
+        return parent;
+    }
+
+    public abstract String getBaseUrl();
+
+    public String getUrlTo(CompositeTestResults model) {
+        String otherUrl = model.getBaseUrl();
+        String thisUrl = getBaseUrl();
+
+        int maxPos = Math.min(thisUrl.length(), otherUrl.length());
+        int endPrefix = 0;
+        while (endPrefix < maxPos) {
+            int endA = thisUrl.indexOf('/', endPrefix);
+            int endB = otherUrl.indexOf('/', endPrefix);
+            if (endA != endB || endA < 0) {
+                break;
+            }
+            if (!thisUrl.regionMatches(endPrefix, otherUrl, endPrefix, endA - endPrefix)) {
+                break;
+            }
+            endPrefix = endA + 1;
+        }
+
+        StringBuilder result = new StringBuilder();
+        int endA = endPrefix;
+        while (endA < thisUrl.length()) {
+            int pos = thisUrl.indexOf('/', endA);
+            if (pos < 0) {
+                break;
+            }
+            result.append("../");
+            endA = pos + 1;
+        }
+        result.append(otherUrl.substring(endPrefix));
+
+        return result.toString();
+    }
+
     public int getTestCount() {
         return tests;
     }
@@ -39,6 +79,14 @@ public abstract class CompositeTestResults extends TestResultModel {
         return failures.size();
     }
 
+    public int getIgnoredCount() {
+        return ignored.size();
+    }
+
+    public int getRunTestCount() {
+        return tests - getIgnoredCount();
+    }
+
     public long getDuration() {
         return duration;
     }
@@ -52,8 +100,18 @@ public abstract class CompositeTestResults extends TestResultModel {
         return failures;
     }
 
+    public Set<TestResult> getIgnored() {
+        return ignored;
+    }
+
     public ResultType getResultType() {
-        return failures.isEmpty() ? ResultType.SUCCESS : ResultType.FAILURE;
+        if (!failures.isEmpty()) {
+            return ResultType.FAILURE;
+        }
+        if (getIgnoredCount() > 0) {
+            return ResultType.SKIPPED;
+        }
+        return ResultType.SUCCESS;
     }
 
     public String getFormattedSuccessRate() {
@@ -65,14 +123,14 @@ public abstract class CompositeTestResults extends TestResultModel {
     }
 
     public Number getSuccessRate() {
-        if (getTestCount() == 0) {
+        if (getRunTestCount() == 0) {
             return null;
         }
 
-        BigDecimal tests = BigDecimal.valueOf(getTestCount());
-        BigDecimal successful = BigDecimal.valueOf(getTestCount() - getFailureCount());
+        BigDecimal runTests = BigDecimal.valueOf(getRunTestCount());
+        BigDecimal successful = BigDecimal.valueOf(getRunTestCount() - getFailureCount());
 
-        return successful.divide(tests, 2, BigDecimal.ROUND_DOWN).multiply(BigDecimal.valueOf(100)).intValue();
+        return successful.divide(runTests, 2, BigDecimal.ROUND_DOWN).multiply(BigDecimal.valueOf(100)).intValue();
     }
 
     protected void failed(TestResult failedTest) {
@@ -82,6 +140,13 @@ public abstract class CompositeTestResults extends TestResultModel {
         }
     }
 
+    protected void ignored(TestResult ignoredTest) {
+        ignored.add(ignoredTest);
+        if (parent != null) {
+            parent.ignored(ignoredTest);
+        }
+    }
+
     protected TestResult addTest(TestResult test) {
         tests++;
         duration += test.getDuration();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
index 46aeb74..d928f08 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.testing.junit.report;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.tasks.testing.junit.result.TestClassResult;
+import org.gradle.api.internal.tasks.testing.junit.result.TestFailure;
 import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult;
 import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
 import org.gradle.api.logging.Logger;
@@ -26,10 +27,10 @@ import org.gradle.reporting.HtmlReportRenderer;
 import org.gradle.util.Clock;
 
 import java.io.File;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.List;
 
+import static org.gradle.api.tasks.testing.TestResult.ResultType.SKIPPED;
+
 public class DefaultTestReport implements TestReporter {
     private final HtmlReportRenderer htmlRenderer = new HtmlReportRenderer();
     private final static Logger LOG = Logging.getLogger(DefaultTestReport.class);
@@ -47,23 +48,23 @@ public class DefaultTestReport implements TestReporter {
         Clock clock = new Clock();
         AllTestResults model = loadModelFromProvider(resultsProvider);
         generateFiles(model, resultsProvider, reportDir);
-        LOG.info("Finished generating test html results (" + clock.getTime() + ")");
+        LOG.info("Finished generating test html results ({}) into: {}", clock.getTime(), reportDir);
     }
 
     private AllTestResults loadModelFromProvider(TestResultsProvider resultsProvider) {
         final AllTestResults model = new AllTestResults();
         resultsProvider.visitClasses(new Action<TestClassResult>() {
             public void execute(TestClassResult classResult) {
-                model.addTestClass(classResult.getClassName());
+                model.addTestClass(classResult.getId(), classResult.getClassName());
                 List<TestMethodResult> collectedResults = classResult.getResults();
                 for (TestMethodResult collectedResult : collectedResults) {
-                    final TestResult testResult = model.addTest(classResult.getClassName(), collectedResult.getName(), collectedResult.getDuration());
-                    if (collectedResult.getResultType() == org.gradle.api.tasks.testing.TestResult.ResultType.SKIPPED) {
-                        testResult.ignored();
+                    final TestResult testResult = model.addTest(classResult.getId(), classResult.getClassName(), collectedResult.getName(), collectedResult.getDuration());
+                    if (collectedResult.getResultType() == SKIPPED) {
+                        testResult.setIgnored();
                     } else {
-                        List<Throwable> failures = collectedResult.getExceptions();
-                        for (Throwable throwable : failures) {
-                            testResult.addFailure(throwable.getMessage(), stackTrace(throwable));
+                        List<TestFailure> failures = collectedResult.getFailures();
+                        for (TestFailure failure : failures) {
+                            testResult.addFailure(failure);
                         }
                     }
                 }
@@ -72,29 +73,13 @@ public class DefaultTestReport implements TestReporter {
         return model;
     }
 
-    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 void generateFiles(AllTestResults model, TestResultsProvider resultsProvider, File reportDir) {
         try {
             generatePage(model, new OverviewPageRenderer(), new File(reportDir, "index.html"));
             for (PackageTestResults packageResults : model.getPackages()) {
-                generatePage(packageResults, new PackagePageRenderer(), new File(reportDir, packageResults.getName() + ".html"));
+                generatePage(packageResults, new PackagePageRenderer(), new File(reportDir, packageResults.getBaseUrl()));
                 for (ClassTestResults classResults : packageResults.getClasses()) {
-                    generatePage(classResults, new ClassPageRenderer(classResults.getName(), resultsProvider), new File(reportDir, classResults.getName() + ".html"));
+                    generatePage(classResults, new ClassPageRenderer(classResults.getId(), resultsProvider), new File(reportDir, classResults.getBaseUrl()));
                 }
             }
         } catch (Exception e) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormat.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormat.java
index c398f33..e51b64a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormat.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormat.java
@@ -20,9 +20,6 @@ import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.text.ParseException;
 
-/**
- * @author Szczepan Faber, @date: 09.03.11
- */
 public class LocaleSafeDecimalFormat {
 
     /**
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 69b9355..36f58a7 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.gradle.api.internal.ErroringAction;
+import org.gradle.internal.ErroringAction;
 import org.gradle.api.internal.html.SimpleHtmlWriter;
 
 import java.io.IOException;
@@ -25,6 +25,7 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
     @Override
     protected void registerTabs() {
         addFailuresTab();
+        addIgnoredTab();
         if (!getResults().getPackages().isEmpty()) {
             addTab("Packages", new ErroringAction<SimpleHtmlWriter>() {
                 @Override
@@ -51,6 +52,7 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
         htmlWriter.startElement("th").characters("Package").endElement();
         htmlWriter.startElement("th").characters("Tests").endElement();
         htmlWriter.startElement("th").characters("Failures").endElement();
+        htmlWriter.startElement("th").characters("Ignored").endElement();
         htmlWriter.startElement("th").characters("Duration").endElement();
         htmlWriter.startElement("th").characters("Success rate").endElement();
         htmlWriter.endElement();
@@ -59,12 +61,13 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
         for (PackageTestResults testPackage : getResults().getPackages()) {
             htmlWriter.startElement("tr");
             htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass());
-            htmlWriter.startElement("a").attribute("href", String.format("%s.html", testPackage.getName())).characters(testPackage.getName()).endElement();
+            htmlWriter.startElement("a").attribute("href", testPackage.getBaseUrl()).characters(testPackage.getName()).endElement();
             htmlWriter.endElement();
             htmlWriter.startElement("td").characters(Integer.toString(testPackage.getTestCount())).endElement();
             htmlWriter.startElement("td").characters(Integer.toString(testPackage.getFailureCount())).endElement();
+            htmlWriter.startElement("td").characters(Integer.toString(testPackage.getIgnoredCount())).endElement();
             htmlWriter.startElement("td").characters(testPackage.getFormattedDuration()).endElement();
-            htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass()).characters(testPackage.getFormattedDuration()).endElement();
+            htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass()).characters(testPackage.getFormattedSuccessRate()).endElement();
             htmlWriter.endElement();
         }
         htmlWriter.endElement();
@@ -78,6 +81,7 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
         htmlWriter.startElement("th").characters("Class").endElement();
         htmlWriter.startElement("th").characters("Tests").endElement();
         htmlWriter.startElement("th").characters("Failures").endElement();
+        htmlWriter.startElement("th").characters("Ignored").endElement();
         htmlWriter.startElement("th").characters("Duration").endElement();
         htmlWriter.startElement("th").characters("Success rate").endElement();
         htmlWriter.endElement();
@@ -88,9 +92,10 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
             for (ClassTestResults testClass : testPackage.getClasses()) {
                 htmlWriter.startElement("tr");
                 htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).endElement();
-                htmlWriter.startElement("a").attribute("href", String.format("%s.html", testClass.getName())).characters(testClass.getName()).endElement();
+                htmlWriter.startElement("a").attribute("href", asHtmlLinkEncoded(testClass.getBaseUrl())).characters(testClass.getName()).endElement();
                 htmlWriter.startElement("td").characters(Integer.toString(testClass.getTestCount())).endElement();
                 htmlWriter.startElement("td").characters(Integer.toString(testClass.getFailureCount())).endElement();
+                htmlWriter.startElement("td").characters(Integer.toString(testClass.getIgnoredCount())).endElement();
                 htmlWriter.startElement("td").characters(testClass.getFormattedDuration()).endElement();
                 htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).characters(testClass.getFormattedSuccessRate()).endElement();
                 htmlWriter.endElement();
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 707b1dd..4f63d3c 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.gradle.api.internal.ErroringAction;
+import org.gradle.internal.ErroringAction;
 import org.gradle.api.internal.html.SimpleHtmlWriter;
 
 import java.io.IOException;
@@ -25,7 +25,7 @@ class PackagePageRenderer extends PageRenderer<PackageTestResults> {
     @Override
     protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
         htmlWriter.startElement("div").attribute("class", "breadcrumbs");
-        htmlWriter.startElement("a").attribute("href", "index.html").characters("all").endElement();
+        htmlWriter.startElement("a").attribute("href", getResults().getUrlTo(getResults().getParent())).characters("all").endElement();
         htmlWriter.characters(String.format(" > %s", getResults().getName()));
         htmlWriter.endElement();
     }
@@ -38,6 +38,7 @@ class PackagePageRenderer extends PageRenderer<PackageTestResults> {
         htmlWriter.startElement("th").characters("Class").endElement();
         htmlWriter.startElement("th").characters("Tests").endElement();
         htmlWriter.startElement("th").characters("Failures").endElement();
+        htmlWriter.startElement("th").characters("Ignored").endElement();
         htmlWriter.startElement("th").characters("Duration").endElement();
         htmlWriter.startElement("th").characters("Success rate").endElement();
 
@@ -47,10 +48,11 @@ class PackagePageRenderer extends PageRenderer<PackageTestResults> {
         for (ClassTestResults testClass : getResults().getClasses()) {
             htmlWriter.startElement("tr");
             htmlWriter.startElement("td").attribute("class", testClass.getStatusClass());
-                htmlWriter.startElement("a").attribute("href", String.format("%s.html", testClass.getName())).characters(testClass.getSimpleName()).endElement();
+                htmlWriter.startElement("a").attribute("href", asHtmlLinkEncoded(getResults().getUrlTo(testClass))).characters(testClass.getSimpleName()).endElement();
             htmlWriter.endElement();
             htmlWriter.startElement("td").characters(Integer.toString(testClass.getTestCount())).endElement();
             htmlWriter.startElement("td").characters(Integer.toString(testClass.getFailureCount())).endElement();
+            htmlWriter.startElement("td").characters(Integer.toString(testClass.getIgnoredCount())).endElement();
             htmlWriter.startElement("td").characters(testClass.getFormattedDuration()).endElement();
             htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).characters(testClass.getFormattedSuccessRate()).endElement();
             htmlWriter.endElement();
@@ -61,6 +63,7 @@ class PackagePageRenderer extends PageRenderer<PackageTestResults> {
     @Override
     protected void registerTabs() {
         addFailuresTab();
+        addIgnoredTab();
         addTab("Classes", new ErroringAction<SimpleHtmlWriter>() {
             public void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
                 renderClasses(htmlWriter);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackageTestResults.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackageTestResults.java
index 916397b..ad0a351 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackageTestResults.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackageTestResults.java
@@ -37,6 +37,10 @@ public class PackageTestResults extends CompositeTestResults {
         return name.equals(DEFAULT_PACKAGE) ? "Default package" : String.format("Package %s", name);
     }
 
+    public String getBaseUrl() {
+        return String.format("packages/%s.html", name);
+    }
+
     public String getName() {
         return name;
     }
@@ -45,15 +49,15 @@ public class PackageTestResults extends CompositeTestResults {
         return classes.values();
     }
 
-    public TestResult addTest(String className, String testName, long duration) {
-        ClassTestResults classResults = addClass(className);
+    public TestResult addTest(long classId, String className, String testName, long duration) {
+        ClassTestResults classResults = addClass(classId, className);
         return addTest(classResults.addTest(testName, duration));
     }
 
-    public ClassTestResults addClass(String className) {
+    public ClassTestResults addClass(long classId, String className) {
         ClassTestResults classResults = classes.get(className);
         if (classResults == null) {
-            classResults = new ClassTestResults(className, this);
+            classResults = new ClassTestResults(classId, className, this);
             classes.put(className, classResults);
         }
         return classResults;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
index 909c4a5..c4b3d12 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.ErroringAction;
+import org.gradle.internal.ErroringAction;
 import org.gradle.api.internal.html.SimpleHtmlWriter;
 import org.gradle.reporting.ReportRenderer;
 import org.gradle.reporting.TabbedPageRenderer;
@@ -63,9 +63,31 @@ abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRe
         htmlWriter.startElement("ul").attribute("class", "linkList");
         for (TestResult test : results.getFailures()) {
             htmlWriter.startElement("li");
-            htmlWriter.startElement("a").attribute("href", String.format("%s.html", test.getClassResults().getName())).characters(test.getClassResults().getSimpleName()).endElement();
+            htmlWriter.startElement("a").attribute("href", asHtmlLinkEncoded(getResults().getUrlTo(test.getClassResults()))).characters(test.getClassResults().getSimpleName()).endElement();
             htmlWriter.characters(".");
-            htmlWriter.startElement("a").attribute("href", String.format("%s.html#%s", test.getClassResults().getName(), test.getName())).characters(test.getName()).endElement();
+            htmlWriter.startElement("a").attribute("href", String.format("%s#%s", asHtmlLinkEncoded(getResults().getUrlTo(test.getClassResults())), test.getName())).characters(test.getName()).endElement();
+            htmlWriter.endElement();
+        }
+        htmlWriter.endElement();
+    }
+    
+    protected void addIgnoredTab() {
+        if (!results.getIgnored().isEmpty()) {
+            addTab("Ignored tests", new ErroringAction<SimpleHtmlWriter>() {
+                public void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
+                    renderIgnoredTests(htmlWriter);
+                }
+            });
+        }
+    }
+
+    protected void renderIgnoredTests(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("ul").attribute("class", "linkList");
+        for (TestResult test : getResults().getIgnored()) {
+            htmlWriter.startElement("li");
+            htmlWriter.startElement("a").attribute("href", asHtmlLinkEncoded(getResults().getUrlTo(test.getClassResults()))).characters(test.getClassResults().getSimpleName()).endElement();
+            htmlWriter.characters(".");
+            htmlWriter.startElement("a").attribute("href", String.format("%s#%s", asHtmlLinkEncoded(getResults().getUrlTo(test.getClassResults())), test.getName())).characters(test.getName()).endElement();
             htmlWriter.endElement();
         }
         htmlWriter.endElement();
@@ -110,6 +132,12 @@ abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRe
                 htmlWriter.endElement();
                 htmlWriter.endElement();
                 htmlWriter.startElement("td");
+                htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "ignored");
+                htmlWriter.startElement("div").attribute("class", "counter").characters(Integer.toString(results.getIgnoredCount())).endElement();
+                htmlWriter.startElement("p").characters("ignored").endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.startElement("td");
                 htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "duration");
                 htmlWriter.startElement("div").attribute("class", "counter").characters(results.getFormattedDuration()).endElement();
                 htmlWriter.startElement("p").characters("duration").endElement();
@@ -144,4 +172,8 @@ abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRe
             }
         };
     }
+
+    protected String asHtmlLinkEncoded(String rawLink) {
+        return rawLink.replaceAll("#", "%23");
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestFailure.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestFailure.java
deleted file mode 100644
index b10b67e..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestFailure.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks.testing.junit.report;
-
-public class TestFailure {
-    private final String message;
-    private final String stackTrace;
-
-    public TestFailure(String message, String stackTrace) {
-        this.message = message;
-        this.stackTrace = stackTrace;
-    }
-
-    public String getMessage() {
-        return message;
-    }
-
-    public String getStackTrace() {
-        return stackTrace;
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResult.java
index 8a4c72a..4fa750f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResult.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResult.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
+import org.gradle.api.internal.tasks.testing.junit.result.TestFailure;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -25,7 +27,7 @@ public class TestResult extends TestResultModel implements Comparable<TestResult
     final ClassTestResults classResults;
     final List<TestFailure> failures = new ArrayList<TestFailure>();
     final String name;
-    private boolean ignored;
+    boolean ignored;
 
     public TestResult(String name, long duration, ClassTestResults classResults) {
         this.name = name;
@@ -46,6 +48,7 @@ public class TestResult extends TestResultModel implements Comparable<TestResult
         return String.format("Test %s", name);
     }
 
+    @Override
     public ResultType getResultType() {
         if (ignored) {
             return ResultType.SKIPPED;
@@ -53,6 +56,7 @@ public class TestResult extends TestResultModel implements Comparable<TestResult
         return failures.isEmpty() ? ResultType.SUCCESS : ResultType.FAILURE;
     }
 
+    @Override
     public long getDuration() {
         return duration;
     }
@@ -70,12 +74,17 @@ public class TestResult extends TestResultModel implements Comparable<TestResult
         return failures;
     }
 
-    public void addFailure(String message, String stackTrace) {
+    public boolean isIgnored() {
+        return ignored;
+    }
+
+    public void addFailure(TestFailure failure) {
         classResults.failed(this);
-        failures.add(new TestFailure(message, stackTrace));
+        failures.add(failure);
     }
 
-    public void ignored() {
+    public void setIgnored() {
+        classResults.ignored(this);
         ignored = true;
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
index f1debb0..70695bc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
@@ -16,47 +16,123 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result;
 
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
 import org.gradle.api.Action;
+import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.testing.TestOutputEvent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.gradle.internal.concurrent.CompositeStoppable;
 
-import java.io.File;
+import java.io.IOException;
 import java.io.Writer;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.gradle.util.CollectionUtils.any;
 
 public class AggregateTestResultsProvider implements TestResultsProvider {
-    private static final Logger LOGGER = LoggerFactory.getLogger(AggregateTestResultsProvider.class);
-    private final Iterable<File> binaryResultDirs;
-    private Map<String, TestResultsProvider> classOutputProviders;
+    private final Iterable<TestResultsProvider> providers;
+    private Multimap<Long, DelegateProvider> classOutputProviders;
 
-    public AggregateTestResultsProvider(Iterable<File> binaryResultDirs) {
-        this.binaryResultDirs = binaryResultDirs;
+    public AggregateTestResultsProvider(Iterable<TestResultsProvider> providers) {
+        this.providers = providers;
     }
 
     public void visitClasses(final Action<? super TestClassResult> visitor) {
-        classOutputProviders = new HashMap<String, TestResultsProvider>();
-        for (File dir : binaryResultDirs) {
-            final BinaryResultBackedTestResultsProvider provider = new BinaryResultBackedTestResultsProvider(dir);
+        final Map<String, OverlayedIdProxyingTestClassResult> aggregatedTestResults = new LinkedHashMap<String, OverlayedIdProxyingTestClassResult>();
+        classOutputProviders = ArrayListMultimap.create();
+        final AtomicLong newIdCounter = new AtomicLong(0L);
+        for (final TestResultsProvider provider : providers) {
             provider.visitClasses(new Action<TestClassResult>() {
-                public void execute(TestClassResult classResult) {
-                    if (classOutputProviders.containsKey(classResult.getClassName())) {
-                        LOGGER.warn("Discarding duplicate results for test class {}.", classResult.getClassName());
-                        return;
+                public void execute(final TestClassResult classResult) {
+                    OverlayedIdProxyingTestClassResult newTestResult = aggregatedTestResults.get(classResult.getClassName());
+                    if (newTestResult != null) {
+                        newTestResult.addTestClassResult(classResult);
+                    } else {
+                        long newId = newIdCounter.incrementAndGet();
+                        newTestResult = new OverlayedIdProxyingTestClassResult(newId, classResult);
+                        aggregatedTestResults.put(classResult.getClassName(), newTestResult);
                     }
-                    classOutputProviders.put(classResult.getClassName(), provider);
-                    visitor.execute(classResult);
+                    classOutputProviders.put(newTestResult.getId(), new DelegateProvider(classResult.getId(), provider));
                 }
             });
         }
+        for (OverlayedIdProxyingTestClassResult classResult : aggregatedTestResults.values()) {
+            visitor.execute(classResult);
+        }
+    }
+
+    private static class DelegateProvider {
+        private final long id;
+        private final TestResultsProvider provider;
+
+        private DelegateProvider(long id, TestResultsProvider provider) {
+            this.id = id;
+            this.provider = provider;
+        }
+    }
+
+    private static class OverlayedIdProxyingTestClassResult extends TestClassResult {
+        private final Map<Long, TestClassResult> delegates = new LinkedHashMap<Long, TestClassResult>();
+
+        public OverlayedIdProxyingTestClassResult(long id, TestClassResult delegate) {
+            super(id, delegate.getClassName(), delegate.getStartTime());
+            addTestClassResult(delegate);
+        }
+
+        void addTestClassResult(TestClassResult delegate) {
+            Preconditions.checkArgument(delegates.isEmpty() || delegates.values().iterator().next().getClassName().equals(delegate.getClassName()));
+            delegates.put(delegate.getId(), delegate);
+            for (TestMethodResult result : delegate.getResults()) {
+                add(result);
+            }
+            if (delegate.getStartTime() < getStartTime()) {
+                setStartTime(delegate.getStartTime());
+            }
+        }
     }
 
-    public boolean hasOutput(String className, TestOutputEvent.Destination destination) {
-        return classOutputProviders.get(className).hasOutput(className, destination);
+    public boolean hasOutput(long id, final TestOutputEvent.Destination destination) {
+        return Iterables.any(
+                classOutputProviders.get(id),
+                new Predicate<DelegateProvider>() {
+                    public boolean apply(DelegateProvider delegateProvider) {
+                        return delegateProvider.provider.hasOutput(delegateProvider.id, destination);
+                    }
+                });
+    }
+
+    public void writeAllOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        for (DelegateProvider delegateProvider : classOutputProviders.get(id)) {
+            delegateProvider.provider.writeAllOutput(delegateProvider.id, destination, writer);
+        }
+    }
+
+    public boolean isHasResults() {
+        return any(providers, new Spec<TestResultsProvider>() {
+            public boolean isSatisfiedBy(TestResultsProvider element) {
+                return element.isHasResults();
+            }
+        });
+    }
+    
+    public void writeNonTestOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        for (DelegateProvider delegateProvider : classOutputProviders.get(id)) {
+            delegateProvider.provider.writeNonTestOutput(delegateProvider.id, destination, writer);
+        }
+    }
+
+    public void writeTestOutput(long classId, long testId, TestOutputEvent.Destination destination, Writer writer) {
+        for (DelegateProvider delegateProvider : classOutputProviders.get(classId)) {
+            delegateProvider.provider.writeTestOutput(delegateProvider.id, testId, destination, writer);
+        }
     }
 
-    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
-        classOutputProviders.get(className).writeOutputs(className, destination, writer);
+    public void close() throws IOException {
+        CompositeStoppable.stoppable(providers).stop();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java
index 844ef3b..314b0d6 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java
@@ -22,6 +22,7 @@ import org.gradle.api.GradleException;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.util.Clock;
+import org.gradle.internal.FileUtils;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -37,17 +38,17 @@ public class Binary2JUnitXmlReportGenerator {
     JUnitXmlResultWriter saxWriter;
     private final static Logger LOG = Logging.getLogger(Binary2JUnitXmlReportGenerator.class);
 
-    public Binary2JUnitXmlReportGenerator(File testResultsDir, TestResultsProvider testResultsProvider) {
+    public Binary2JUnitXmlReportGenerator(File testResultsDir, TestResultsProvider testResultsProvider, TestOutputAssociation outputAssociation) {
         this.testResultsDir = testResultsDir;
         this.testResultsProvider = testResultsProvider;
-        this.saxWriter = new JUnitXmlResultWriter(getHostname(), testResultsProvider);
+        this.saxWriter = new JUnitXmlResultWriter(getHostname(), testResultsProvider, outputAssociation);
     }
 
     public void generate() {
         Clock clock = new Clock();
         testResultsProvider.visitClasses(new Action<TestClassResult>() {
             public void execute(TestClassResult result) {
-                File file = new File(testResultsDir, "TEST-" + result.getClassName() + ".xml");
+                File file = new File(testResultsDir, getReportFileName(result));
                 OutputStream output = null;
                 try {
                     output = new BufferedOutputStream(new FileOutputStream(file));
@@ -60,7 +61,11 @@ public class Binary2JUnitXmlReportGenerator {
                 }
             }
         });
-        LOG.info("Finished generating test XML results (" + clock.getTime() + ")");
+        LOG.info("Finished generating test XML results ({}) into: {}", clock.getTime(), testResultsDir);
+    }
+
+    private String getReportFileName(TestClassResult result) {
+        return "TEST-" + FileUtils.toSafeFileName(result.getClassName()) + ".xml";
     }
 
     private static String getHostname() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
index 217a4db..670b392 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
@@ -20,27 +20,43 @@ import org.gradle.api.Action;
 import org.gradle.api.tasks.testing.TestOutputEvent;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.Writer;
 
 public class BinaryResultBackedTestResultsProvider implements TestResultsProvider {
-    private final File resultsDir;
-    private final TestOutputSerializer outputSerializer;
-    private final TestResultSerializer resultSerializer = new TestResultSerializer();
+    private final TestOutputStore.Reader outputReader;
+    private final TestResultSerializer resultSerializer;
 
     public BinaryResultBackedTestResultsProvider(File resultsDir) {
-        this.resultsDir = resultsDir;
-        outputSerializer = new TestOutputSerializer(resultsDir);
+        this.outputReader = new TestOutputStore(resultsDir).reader();
+        this.resultSerializer = new TestResultSerializer(resultsDir);
     }
 
-    public boolean hasOutput(String className, TestOutputEvent.Destination destination) {
-        return outputSerializer.hasOutput(className, destination);
+    public boolean hasOutput(long id, TestOutputEvent.Destination destination) {
+        return outputReader.hasOutput(id, destination);
     }
 
-    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
-        outputSerializer.writeOutputs(className, destination, writer);
+    public void writeAllOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        outputReader.writeAllOutput(id, destination, writer);
+    }
+    
+    public boolean isHasResults() {
+        return resultSerializer.isHasResults();
+    }
+
+    public void writeNonTestOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        outputReader.writeNonTestOutput(id, destination, writer);
+    }
+
+    public void writeTestOutput(long classId, long testId, TestOutputEvent.Destination destination, Writer writer) {
+        outputReader.writeTestOutput(classId, testId, destination, writer);
     }
 
     public void visitClasses(final Action<? super TestClassResult> visitor) {
-        resultSerializer.read(resultsDir, visitor);
+        resultSerializer.read(visitor);
+    }
+
+    public void close() throws IOException {
+        outputReader.close();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
deleted file mode 100644
index 2d8841a..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
+++ /dev/null
@@ -1,100 +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.tasks.testing.junit.result;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-
-import java.io.*;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import static org.gradle.internal.CompositeStoppable.stoppable;
-
-/**
- * by Szczepan Faber, created at: 11/19/12
- */
-public class CachingFileWriter {
-
-    private final static Logger LOG = Logging.getLogger(CachingFileWriter.class);
-
-    final LinkedHashMap<File, Writer> openFiles = new LinkedHashMap<File, Writer>();
-    private final int maxOpenFiles;
-
-    public CachingFileWriter(int maxOpenFiles) {
-        this.maxOpenFiles = maxOpenFiles;
-    }
-
-    public void write(File file, String text) {
-        Writer out;
-        try {
-            try {
-                if (openFiles.containsKey(file)) {
-                    out = openFiles.get(file);
-                } else {
-                    out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file, true)), "UTF-8");
-                    openFiles.put(file, out);
-                    if (openFiles.size() > maxOpenFiles) {
-                        //remove first
-                        Iterator<Map.Entry<File, Writer>> iterator = openFiles.entrySet().iterator();
-                        close(iterator.next().getValue(), file.toString());
-                        iterator.remove();
-                    }
-                }
-                out.write(text);
-            } catch (IOException e) {
-                throw new UncheckedIOException("Problems writing to file: " + file, e);
-            }
-        } catch (UncheckedIOException e) {
-            cleanUpQuietly();
-            throw e;
-        }
-    }
-
-    public void closeAll() {
-        try {
-            for (Map.Entry<File, Writer> entry : openFiles.entrySet()) {
-                close(entry.getValue(), entry.getKey().toString());
-            }
-        } catch (UncheckedIOException e) {
-            cleanUpQuietly();
-            throw e;
-        } finally {
-            openFiles.clear();
-        }
-    }
-
-    private void cleanUpQuietly() {
-        try {
-            stoppable(openFiles.values()).stop();
-        } catch (Exception e) {
-            LOG.debug("Problems closing files", e);
-        } finally {
-            openFiles.clear();
-        }
-    }
-
-    private void close(Closeable c, String displayName) {
-        try {
-            c.close();
-        } catch (IOException e) {
-            throw new UncheckedIOException("Problems closing file: " + displayName, e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/InMemoryTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/InMemoryTestResultsProvider.java
new file mode 100644
index 0000000..6eee582
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/InMemoryTestResultsProvider.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.tasks.testing.junit.result;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+
+import java.io.IOException;
+import java.io.Writer;
+
+public class InMemoryTestResultsProvider implements TestResultsProvider {
+    private final Iterable<TestClassResult> results;
+    private final TestOutputStore.Reader outputReader;
+
+    public InMemoryTestResultsProvider(Iterable<TestClassResult> results, TestOutputStore.Reader outputReader) {
+        this.results = results;
+        this.outputReader = outputReader;
+    }
+
+    public boolean hasOutput(long id, TestOutputEvent.Destination destination) {
+        return outputReader.hasOutput(id, destination);
+    }
+
+    public void writeAllOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        outputReader.writeAllOutput(id, destination, writer);
+    }
+
+    public void writeNonTestOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        outputReader.writeNonTestOutput(id, destination, writer);
+    }
+
+    public void writeTestOutput(long classId, long testId, TestOutputEvent.Destination destination, Writer writer) {
+        outputReader.writeTestOutput(classId, testId, destination, writer);
+    }
+
+    public void visitClasses(final Action<? super TestClassResult> visitor) {
+        for (TestClassResult result : results) {
+            visitor.execute(result);
+        }
+    }
+
+    public boolean isHasResults() {
+        return results.iterator().hasNext();
+    }
+
+    public void close() throws IOException {
+        outputReader.close();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java
index b2a06ee..e46e215 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java
@@ -21,33 +21,32 @@ import org.gradle.api.internal.xml.SimpleXmlWriter;
 import org.gradle.api.tasks.testing.TestOutputEvent;
 import org.gradle.api.tasks.testing.TestResult;
 import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.remote.internal.PlaceholderException;
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 
-/**
- * by Szczepan Faber, created at: 11/13/12
- */
 public class JUnitXmlResultWriter {
 
     private final String hostName;
     private final TestResultsProvider testResultsProvider;
+    private final TestOutputAssociation outputAssociation;
 
-    public JUnitXmlResultWriter(String hostName, TestResultsProvider testResultsProvider) {
+    public JUnitXmlResultWriter(String hostName, TestResultsProvider testResultsProvider, TestOutputAssociation outputAssociation) {
         this.hostName = hostName;
         this.testResultsProvider = testResultsProvider;
+        this.outputAssociation = outputAssociation;
     }
 
     public void write(TestClassResult result, OutputStream output) {
         String className = result.getClassName();
+        long classId = result.getId();
+
         try {
             SimpleXmlWriter writer = new SimpleXmlWriter(output, "  ");
             writer.startElement("testsuite")
                     .attribute("name", className)
                     .attribute("tests", String.valueOf(result.getTestsCount()))
+                    .attribute("skipped", String.valueOf(result.getSkippedCount()))
                     .attribute("failures", String.valueOf(result.getFailuresCount()))
                     .attribute("errors", "0")
                     .attribute("timestamp", DateUtils.format(result.getStartTime(), DateUtils.ISO8601_DATETIME_PATTERN))
@@ -57,70 +56,69 @@ public class JUnitXmlResultWriter {
             writer.startElement("properties");
             writer.endElement();
 
-            writeTests(writer, result.getResults(), className);
+            writeTests(writer, result.getResults(), className, classId);
 
             writer.startElement("system-out");
-            writeOutputs(writer, className, TestOutputEvent.Destination.StdOut);
+            writeOutputs(writer, classId, outputAssociation.equals(TestOutputAssociation.WITH_SUITE), TestOutputEvent.Destination.StdOut);
             writer.endElement();
             writer.startElement("system-err");
-            writeOutputs(writer, className, TestOutputEvent.Destination.StdErr);
+            writeOutputs(writer, classId, outputAssociation.equals(TestOutputAssociation.WITH_SUITE), TestOutputEvent.Destination.StdErr);
             writer.endElement();
+
             writer.endElement();
         } catch (IOException e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
-    private void writeOutputs(SimpleXmlWriter writer, String className, TestOutputEvent.Destination destination) throws IOException {
+    private void writeOutputs(SimpleXmlWriter writer, long classId, boolean allClassOutput, TestOutputEvent.Destination destination) throws IOException {
         writer.startCDATA();
-        testResultsProvider.writeOutputs(className, destination, writer);
+        if (allClassOutput) {
+            testResultsProvider.writeAllOutput(classId, destination, writer);
+        } else {
+            testResultsProvider.writeNonTestOutput(classId, destination, writer);
+        }
         writer.endCDATA();
     }
 
-    private void writeTests(SimpleXmlWriter writer, Iterable<TestMethodResult> methodResults, String className) throws IOException {
+    private void writeOutputs(SimpleXmlWriter writer, long classId, long testId, TestOutputEvent.Destination destination) throws IOException {
+        writer.startCDATA();
+        testResultsProvider.writeTestOutput(classId, testId, destination, writer);
+        writer.endCDATA();
+    }
+
+    private void writeTests(SimpleXmlWriter writer, Iterable<TestMethodResult> methodResults, String className, long classId) throws IOException {
         for (TestMethodResult methodResult : methodResults) {
-            String testCase = methodResult.getResultType() == TestResult.ResultType.SKIPPED ? "ignored-testcase" : "testcase";
-            writer.startElement(testCase)
+            writer.startElement("testcase")
                     .attribute("name", methodResult.getName())
                     .attribute("classname", className)
                     .attribute("time", String.valueOf(methodResult.getDuration() / 1000.0));
 
-            for (Throwable failure : methodResult.getExceptions()) {
-                writer.startElement("failure")
-                        .attribute("message", failureMessage(failure))
-                        .attribute("type", failure.getClass().getName());
+            if (methodResult.getResultType() == TestResult.ResultType.SKIPPED) {
+                writer.startElement("skipped");
+                writer.endElement();
+            } else {
+                for (TestFailure failure : methodResult.getFailures()) {
+                    writer.startElement("failure")
+                            .attribute("message", failure.getMessage())
+                            .attribute("type", failure.getExceptionType());
 
-                writer.characters(stackTrace(failure));
+                    writer.characters(failure.getStackTrace());
 
-                writer.endElement();
+                    writer.endElement();
+                }
             }
-            writer.endElement();
-        }
-    }
 
-    private String failureMessage(Throwable throwable) {
-        try {
-            return throwable.toString();
-        } catch (Throwable t) {
-            String exceptionClassName = throwable instanceof PlaceholderException ? ((PlaceholderException)throwable).getExceptionClassName() : throwable.getClass().getName();
-            return String.format("Could not determine failure message for exception of type %s: %s",
-                    exceptionClassName, t);
-        }
-    }
+            if (outputAssociation.equals(TestOutputAssociation.WITH_TESTCASE)) {
+                writer.startElement("system-out");
+                writeOutputs(writer, classId, methodResult.getId(), TestOutputEvent.Destination.StdOut);
+                writer.endElement();
+                writer.startElement("system-err");
+                writeOutputs(writer, classId, methodResult.getId(), TestOutputEvent.Destination.StdErr);
+                writer.endElement();
+            }
 
-    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();
+            writer.endElement();
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java
index 75f7bd6..a9bde87 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java
@@ -21,20 +21,27 @@ import org.gradle.api.tasks.testing.TestResult;
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 11/13/12
- */
 public class TestClassResult {
     private final List<TestMethodResult> methodResults = new ArrayList<TestMethodResult>();
     private final String className;
-    private final long startTime;
+    private long startTime;
     private int failuresCount;
+    private int skippedCount;
+    private long id;
 
-    public TestClassResult(String className, long startTime) {
+    public TestClassResult(long id, String className, long startTime) {
+        if (id < 1) {
+            throw new IllegalArgumentException("id must be > 0");
+        }
+        this.id = id;
         this.className = className;
         this.startTime = startTime;
     }
 
+    public long getId() {
+        return id;
+    }
+
     public String getClassName() {
         return className;
     }
@@ -43,6 +50,9 @@ public class TestClassResult {
         if (methodResult.getResultType() == TestResult.ResultType.FAILURE) {
             failuresCount++;
         }
+        if(methodResult.getResultType() == TestResult.ResultType.SKIPPED) {
+            skippedCount++;
+        }
         methodResults.add(methodResult);
         return this;
     }
@@ -63,6 +73,11 @@ public class TestClassResult {
         return failuresCount;
     }
 
+
+    public int getSkippedCount() {
+        return skippedCount;
+    }
+
     public long getDuration() {
         long end = startTime;
         for (TestMethodResult m : methodResults) {
@@ -72,4 +87,8 @@ public class TestClassResult {
         }
         return end - startTime;
     }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestFailure.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestFailure.java
new file mode 100644
index 0000000..c605868
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestFailure.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+public class TestFailure {
+    private final String message;
+    private final String stackTrace;
+    private final String exceptionType;
+
+    public TestFailure(String message, String stackTrace, String exceptionType) {
+        this.message = message;
+        this.stackTrace = stackTrace;
+        this.exceptionType = exceptionType;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public String getStackTrace() {
+        return stackTrace;
+    }
+
+    public String getExceptionType() {
+        return exceptionType;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java
index 39121c2..1898d67 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java
@@ -18,40 +18,55 @@ package org.gradle.api.internal.tasks.testing.junit.result;
 
 import org.gradle.api.tasks.testing.TestResult;
 
+import java.util.ArrayList;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 11/13/12
- */
 public class TestMethodResult {
+    private final long id;
     private final String name;
-    private final TestResult.ResultType resultType;
-    private final long duration;
-    private final long endTime;
-    private final List<Throwable> exceptions;
+    private TestResult.ResultType resultType;
+    private long duration;
+    private long endTime;
+    private List<TestFailure> failures = new ArrayList<TestFailure>();
 
-    public TestMethodResult(String name, TestResult result) {
+    public TestMethodResult(long id, String name) {
+        this.id = id;
         this.name = name;
-        resultType = result.getResultType();
-        duration = result.getEndTime() - result.getStartTime();
-        endTime = result.getEndTime();
-        exceptions = result.getExceptions();
     }
 
-    public TestMethodResult(String name, TestResult.ResultType resultType, long duration, long endTime, List<Throwable> exceptions) {
+    public TestMethodResult(long id, String name, TestResult.ResultType resultType, long duration, long endTime) {
+        if (id < 1) {
+            throw new IllegalArgumentException("id must be > 0");
+        }
+        this.id = id;
         this.name = name;
         this.resultType = resultType;
         this.duration = duration;
         this.endTime = endTime;
-        this.exceptions = exceptions;
+    }
+
+    public TestMethodResult completed(TestResult result) {
+        resultType = result.getResultType();
+        duration = result.getEndTime() - result.getStartTime();
+        endTime = result.getEndTime();
+        return this;
+    }
+
+    public TestMethodResult addFailure(String message, String stackTrace, String exceptionType) {
+        this.failures.add(new TestFailure(message, stackTrace, exceptionType));
+        return this;
+    }
+
+    public long getId() {
+        return id;
     }
 
     public String getName() {
         return name;
     }
 
-    public List<Throwable> getExceptions() {
-        return exceptions;
+    public List<TestFailure> getFailures() {
+        return failures;
     }
 
     public TestResult.ResultType getResultType() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputAssociation.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputAssociation.java
new file mode 100644
index 0000000..40e4c83
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputAssociation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+public enum TestOutputAssociation {
+    WITH_SUITE,
+    WITH_TESTCASE
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializer.java
deleted file mode 100644
index 929394f..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializer.java
+++ /dev/null
@@ -1,90 +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.tasks.testing.junit.result;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-
-import java.io.*;
-
-/**
- * Assembles test results. Keeps a copy of the results in memory to provide them later and spools test output to file.
- *
- * by Szczepan Faber, created at: 11/13/12
- */
-public class TestOutputSerializer {
-    private final File resultsDir;
-    private final CachingFileWriter cachingFileWriter;
-
-    public TestOutputSerializer(File resultsDir) {
-        //TODO SF calculate number of open files based on parallel forks
-        this(resultsDir, new CachingFileWriter(10));
-    }
-
-    private TestOutputSerializer(File resultsDir, CachingFileWriter cachingFileWriter) {
-        this.resultsDir = resultsDir;
-        this.cachingFileWriter = cachingFileWriter;
-    }
-
-    private File outputsFile(String className, TestOutputEvent.Destination destination) {
-        return destination == TestOutputEvent.Destination.StdOut ? standardOutputFile(className) : standardErrorFile(className);
-    }
-
-    private File standardErrorFile(String className) {
-        return new File(resultsDir, className + ".stderr");
-    }
-
-    private File standardOutputFile(String className) {
-        return new File(resultsDir, className + ".stdout");
-    }
-
-    public boolean hasOutput(String className, TestOutputEvent.Destination destination){
-        return outputsFile(className, destination).exists();
-    }
-
-    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
-        final File file = outputsFile(className, destination);
-        if (!file.exists()) {
-            return;
-        }
-        try {
-            Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), "UTF-8");
-            try {
-                char[] buffer = new char[2048];
-                while (true) {
-                    int read = reader.read(buffer);
-                    if (read < 0) {
-                        return;
-                    }
-                    writer.write(buffer, 0, read);
-                }
-            } finally {
-                reader.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void finishOutputs() {
-        cachingFileWriter.closeAll();
-    }
-
-    public void onOutput(String className, TestOutputEvent.Destination destination, String message) {
-        cachingFileWriter.write(outputsFile(className, destination), message);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputStore.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputStore.java
new file mode 100644
index 0000000..1426ac4
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputStore.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.internal.io.RandomAccessFileInputStream;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.serialize.kryo.KryoBackedDecoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedEncoder;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class TestOutputStore {
+
+    private final File resultsDir;
+    private final Charset messageStorageCharset;
+
+    public TestOutputStore(File resultsDir) {
+        this.resultsDir = resultsDir;
+        this.messageStorageCharset = Charset.forName("UTF-8");
+    }
+
+    File getOutputsFile() {
+        return new File(resultsDir, "output.bin");
+    }
+
+    File getIndexFile() {
+        return new File(resultsDir, getOutputsFile().getName() + ".idx");
+    }
+
+    private static class Region {
+        long start;
+        long stop;
+
+        private Region() {
+            start = -1;
+            stop = -1;
+        }
+
+        private Region(long start, long stop) {
+            this.start = start;
+            this.stop = stop;
+        }
+    }
+
+    private static class TestCaseRegion {
+        Region stdOutRegion = new Region();
+        Region stdErrRegion = new Region();
+    }
+
+    public class Writer implements Closeable {
+        private final KryoBackedEncoder output;
+
+        private final Map<Long, Map<Long, TestCaseRegion>> index = new LinkedHashMap<Long, Map<Long, TestCaseRegion>>();
+
+        public Writer() {
+            try {
+                output = new KryoBackedEncoder(new FileOutputStream(getOutputsFile()));
+            } catch (FileNotFoundException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        public void close() {
+            output.close();
+            writeIndex();
+        }
+
+        public void onOutput(long classId, TestOutputEvent outputEvent) {
+            onOutput(classId, 0, outputEvent);
+        }
+
+        public void onOutput(long classId, long testId, TestOutputEvent outputEvent) {
+            boolean stdout = outputEvent.getDestination() == TestOutputEvent.Destination.StdOut;
+            mark(classId, testId, stdout);
+
+            output.writeBoolean(stdout);
+            output.writeSmallLong(classId);
+            output.writeSmallLong(testId);
+
+            byte[] bytes;
+            try {
+                bytes = outputEvent.getMessage().getBytes(messageStorageCharset.name());
+            } catch (UnsupportedEncodingException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+            output.writeSmallInt(bytes.length);
+            output.writeBytes(bytes, 0, bytes.length);
+        }
+
+        private void mark(long classId, long testId, boolean isStdout) {
+            if (!index.containsKey(classId)) {
+                index.put(classId, new LinkedHashMap<Long, TestCaseRegion>());
+            }
+
+            Map<Long, TestCaseRegion> testCaseRegions = index.get(classId);
+            if (!testCaseRegions.containsKey(testId)) {
+                TestCaseRegion region = new TestCaseRegion();
+                testCaseRegions.put(testId, region);
+            }
+
+            TestCaseRegion region = testCaseRegions.get(testId);
+
+            Region streamRegion = isStdout ? region.stdOutRegion : region.stdErrRegion;
+
+            int total = output.getWritePosition();
+            if (streamRegion.start < 0) {
+                streamRegion.start = total;
+            }
+            streamRegion.stop = total;
+        }
+
+        private void writeIndex() {
+            Output indexOutput;
+            try {
+                indexOutput = new Output(new FileOutputStream(getIndexFile()));
+            } catch (FileNotFoundException e) {
+                throw new UncheckedIOException(e);
+            }
+
+
+            try {
+                indexOutput.writeInt(index.size(), true);
+
+                for (Map.Entry<Long, Map<Long, TestCaseRegion>> classEntry : index.entrySet()) {
+                    Long classId = classEntry.getKey();
+                    Map<Long, TestCaseRegion> regions = classEntry.getValue();
+
+                    indexOutput.writeLong(classId, true);
+                    indexOutput.writeInt(regions.size(), true);
+
+                    for (Map.Entry<Long, TestCaseRegion> testCaseEntry : regions.entrySet()) {
+                        long id = testCaseEntry.getKey();
+                        TestCaseRegion region = testCaseEntry.getValue();
+                        indexOutput.writeLong(id, true);
+                        indexOutput.writeLong(region.stdOutRegion.start);
+                        indexOutput.writeLong(region.stdOutRegion.stop);
+                        indexOutput.writeLong(region.stdErrRegion.start);
+                        indexOutput.writeLong(region.stdErrRegion.stop);
+                    }
+                }
+            } finally {
+                indexOutput.close();
+            }
+        }
+    }
+
+    public Writer writer() {
+        return new Writer();
+    }
+
+    private static class Index {
+        final ImmutableMap<Long, Index> children;
+        final Region stdOut;
+        final Region stdErr;
+
+        private Index(Region stdOut, Region stdErr) {
+            this.children = ImmutableMap.of();
+            this.stdOut = stdOut;
+            this.stdErr = stdErr;
+        }
+
+        private Index(ImmutableMap<Long, Index> children, Region stdOut, Region stdErr) {
+            this.children = children;
+            this.stdOut = stdOut;
+            this.stdErr = stdErr;
+        }
+    }
+
+    private static class IndexBuilder {
+        final Region stdOut = new Region();
+        final Region stdErr = new Region();
+
+        private final ImmutableMap.Builder<Long, Index> children = ImmutableMap.builder();
+
+        void add(long key, Index index) {
+            if (stdOut.start < 0) {
+                stdOut.start = index.stdOut.start;
+            }
+            if (stdErr.start < 0) {
+                stdErr.start = index.stdErr.start;
+            }
+            if (index.stdOut.stop > stdOut.stop) {
+                stdOut.stop = index.stdOut.stop;
+            }
+            if (index.stdErr.stop > stdErr.stop) {
+                stdErr.stop = index.stdErr.stop;
+            }
+
+            children.put(key, index);
+        }
+
+        Index build() {
+            return new Index(children.build(), stdOut, stdErr);
+        }
+    }
+
+    public class Reader implements Closeable {
+        private final Index index;
+        private final RandomAccessFile dataFile;
+
+        public Reader() {
+            File indexFile = getIndexFile();
+            File outputsFile = getOutputsFile();
+
+            if (outputsFile.exists()) {
+                if (!indexFile.exists()) {
+                    throw new IllegalStateException(String.format("Test outputs data file '%s' exists but the index file '%s' does not", outputsFile, indexFile));
+                }
+
+                Input input;
+                try {
+                    input = new Input(new FileInputStream(indexFile));
+                } catch (FileNotFoundException e) {
+                    throw new UncheckedIOException(e);
+                }
+
+                IndexBuilder rootBuilder = null;
+                try {
+                    int numClasses = input.readInt(true);
+                    rootBuilder = new IndexBuilder();
+
+                    for (int classCounter = 0; classCounter < numClasses; ++classCounter) {
+                        long classId = input.readLong(true);
+                        IndexBuilder classBuilder = new IndexBuilder();
+
+                        int numEntries = input.readInt(true);
+                        for (int entryCounter = 0; entryCounter < numEntries; ++entryCounter) {
+                            long testId = input.readLong(true);
+                            Region stdOut = new Region(input.readLong(), input.readLong());
+                            Region stdErr = new Region(input.readLong(), input.readLong());
+                            classBuilder.add(testId, new Index(stdOut, stdErr));
+                        }
+
+                        rootBuilder.add(classId, classBuilder.build());
+                    }
+                } finally {
+                    input.close();
+                }
+
+                index = rootBuilder.build();
+
+                try {
+                    dataFile = new RandomAccessFile(getOutputsFile(), "r");
+                } catch (FileNotFoundException e) {
+                    throw new UncheckedIOException(e);
+                }
+            } else { // no outputs file
+                if (indexFile.exists()) {
+                    throw new IllegalStateException(String.format("Test outputs data file '%s' does not exist but the index file '%s' does", outputsFile, indexFile));
+                }
+
+                index = null;
+                dataFile = null;
+            }
+        }
+
+        public void close() throws IOException {
+            if (dataFile != null) {
+                dataFile.close();
+            }
+        }
+
+        public boolean hasOutput(long classId, TestOutputEvent.Destination destination) {
+            if (dataFile == null) {
+                return false;
+            }
+
+            Index classIndex = index.children.get(classId);
+            if (classIndex == null) {
+                return false;
+            } else {
+                Region region = destination == TestOutputEvent.Destination.StdOut ? classIndex.stdOut : classIndex.stdErr;
+                return region.start >= 0;
+            }
+        }
+
+        public void writeAllOutput(long classId, TestOutputEvent.Destination destination, java.io.Writer writer) {
+            doRead(classId, 0, true, destination, writer);
+        }
+
+        public void writeNonTestOutput(long classId, TestOutputEvent.Destination destination, java.io.Writer writer) {
+            doRead(classId, 0, false, destination, writer);
+        }
+
+        public void writeTestOutput(long classId, long testId, TestOutputEvent.Destination destination, java.io.Writer writer) {
+            doRead(classId, testId, false, destination, writer);
+        }
+
+        private void doRead(long classId, long testId, boolean allClassOutput, TestOutputEvent.Destination destination, java.io.Writer writer) {
+            if (dataFile == null) {
+                return;
+            }
+
+            Index targetIndex = index.children.get(classId);
+            if (targetIndex != null && testId != 0) {
+                targetIndex = targetIndex.children.get(testId);
+            }
+
+            if (targetIndex == null) {
+                return;
+            }
+
+            boolean stdout = destination == TestOutputEvent.Destination.StdOut;
+            Region region = stdout ? targetIndex.stdOut : targetIndex.stdErr;
+
+            if (region.start < 0) {
+                return;
+            }
+
+            boolean ignoreClassLevel = !allClassOutput && testId != 0;
+            boolean ignoreTestLevel = !allClassOutput && testId == 0;
+
+            try {
+                dataFile.seek(region.start);
+                long maxPos = region.stop - region.start;
+                KryoBackedDecoder decoder = new KryoBackedDecoder(new RandomAccessFileInputStream(dataFile));
+                while (decoder.getReadPosition() <= maxPos) {
+                    boolean readStdout = decoder.readBoolean();
+                    long readClassId = decoder.readSmallLong();
+                    long readTestId = decoder.readSmallLong();
+                    int readLength = decoder.readSmallInt();
+
+                    boolean isClassLevel = readTestId == 0;
+
+                    if (stdout != readStdout || classId != readClassId) {
+                        decoder.skipBytes(readLength);
+                        continue;
+                    }
+
+                    if (ignoreClassLevel && isClassLevel) {
+                        decoder.skipBytes(readLength);
+                        continue;
+                    }
+
+                    if (ignoreTestLevel && !isClassLevel) {
+                        decoder.skipBytes(readLength);
+                        continue;
+                    }
+
+                    if (testId == 0 || testId == readTestId) {
+                        byte[] stringBytes = new byte[readLength];
+                        decoder.readBytes(stringBytes);
+                        String message;
+                        try {
+                            message = new String(stringBytes, messageStorageCharset.name());
+                        } catch (UnsupportedEncodingException e) {
+                            // shouldn't happen
+                            throw UncheckedException.throwAsUncheckedException(e);
+                        }
+
+                        writer.write(message);
+                    } else {
+                        decoder.skipBytes(readLength);
+                    }
+                }
+            } catch (IOException e1) {
+                throw new UncheckedIOException(e1);
+            }
+        }
+    }
+
+    // IMPORTANT: return must be closed when done with.
+    public Reader reader() {
+        return new Reader();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
index 8b0ab17..99365df 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
@@ -16,62 +16,96 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result;
 
-import org.gradle.api.Action;
 import org.gradle.api.tasks.testing.*;
+import org.gradle.messaging.remote.internal.PlaceholderException;
 
-import java.io.File;
-import java.io.Writer;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Assembles test results. Keeps a copy of the results in memory to provide them later and spools test output to file.
- *
- * by Szczepan Faber, created at: 11/13/12
+ * Collects the test results into memory and spools the test output to file during execution (to avoid holding it all in memory).
  */
-public class TestReportDataCollector implements TestListener, TestOutputListener, TestResultsProvider {
-    private final Map<String, TestClassResult> results = new HashMap<String, TestClassResult>();
-    private final TestResultSerializer resultSerializer;
-    private final File resultsDir;
-    private final TestOutputSerializer outputSerializer;
+public class TestReportDataCollector implements TestListener, TestOutputListener {
 
-    public TestReportDataCollector(File resultsDir) {
-        this(resultsDir, new TestOutputSerializer(resultsDir), new TestResultSerializer());
-    }
+    private final Map<String, TestClassResult> results;
+    private final TestOutputStore.Writer outputWriter;
+    private final Map<TestDescriptor, TestMethodResult> currentTestMethods = new HashMap<TestDescriptor, TestMethodResult>();
+    private long internalIdCounter = 1;
 
-    TestReportDataCollector(File resultsDir, TestOutputSerializer outputSerializer, TestResultSerializer resultSerializer) {
-        this.resultsDir = resultsDir;
-        this.outputSerializer = outputSerializer;
-        this.resultSerializer = resultSerializer;
+    public TestReportDataCollector(Map<String, TestClassResult> results, TestOutputStore.Writer outputWriter) {
+        this.results = results;
+        this.outputWriter = outputWriter;
     }
 
     public void beforeSuite(TestDescriptor suite) {
     }
 
     public void afterSuite(TestDescriptor suite, TestResult result) {
-        if (suite.getParent() == null) {
-            outputSerializer.finishOutputs();
-            writeResults();
+        if (result.getResultType() == TestResult.ResultType.FAILURE && !result.getExceptions().isEmpty()) {
+            //there are some exceptions attached to the suite. Let's make sure they are reported to the user.
+            //this may happen for example when suite initialisation fails and no tests are executed
+            TestMethodResult methodResult = new TestMethodResult(internalIdCounter++, "execution failure");
+            for (Throwable throwable : result.getExceptions()) {
+                methodResult.addFailure(failureMessage(throwable), stackTrace(throwable), exceptionClassName(throwable));
+            }
+            methodResult.completed(result);
+            TestClassResult classResult = new TestClassResult(internalIdCounter++, suite.getName(), result.getStartTime());
+            classResult.add(methodResult);
+            results.put(suite.getName(), classResult);
         }
     }
 
-    private void writeResults() {
-        resultSerializer.write(results.values(), resultsDir);
-    }
-
     public void beforeTest(TestDescriptor testDescriptor) {
+        TestMethodResult methodResult = new TestMethodResult(internalIdCounter++, testDescriptor.getName());
+        currentTestMethods.put(testDescriptor, methodResult);
     }
 
     public void afterTest(TestDescriptor testDescriptor, TestResult result) {
-        if (!testDescriptor.isComposite()) {
-            String className = testDescriptor.getClassName();
-            TestMethodResult methodResult = new TestMethodResult(testDescriptor.getName(), result);
-            TestClassResult classResult = results.get(className);
-            if (classResult == null) {
-                classResult = new TestClassResult(className, result.getStartTime());
-                results.put(className, classResult);
-            }
-            classResult.add(methodResult);
+        String className = testDescriptor.getClassName();
+        TestMethodResult methodResult = currentTestMethods.remove(testDescriptor).completed(result);
+        for (Throwable throwable : result.getExceptions()) {
+            methodResult.addFailure(failureMessage(throwable), stackTrace(throwable), exceptionClassName(throwable));
+        }
+        TestClassResult classResult = results.get(className);
+        if (classResult == null) {
+            classResult = new TestClassResult(internalIdCounter++, className, result.getStartTime());
+            results.put(className, classResult);
+        } else if (classResult.getStartTime() == 0) {
+            //class results may be created earlier, where we don't yet have access to the start time
+            classResult.setStartTime(result.getStartTime());
+        }
+        classResult.add(methodResult);
+    }
+
+    private String failureMessage(Throwable throwable) {
+        try {
+            return throwable.toString();
+        } catch (Throwable t) {
+            String exceptionClassName = exceptionClassName(throwable);
+            return String.format("Could not determine failure message for exception of type %s: %s",
+                    exceptionClassName, t);
+        }
+    }
+
+    private String exceptionClassName(Throwable throwable) {
+        return throwable instanceof PlaceholderException ? ((PlaceholderException) throwable).getExceptionClassName() : throwable.getClass().getName();
+    }
+
+    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();
         }
     }
 
@@ -84,23 +118,18 @@ public class TestReportDataCollector implements TestListener, TestOutputListener
         }
         TestClassResult classResult = results.get(className);
         if (classResult == null) {
-            classResult = new TestClassResult(className, 0);
+            //it's possible that we receive an output for a suite here
+            //in this case we will create the test result for a suite that normally would not be created
+            //feels like this scenario should modelled more explicitly
+            classResult = new TestClassResult(internalIdCounter++, className, 0);
             results.put(className, classResult);
         }
-        outputSerializer.onOutput(className, outputEvent.getDestination(), outputEvent.getMessage());
-    }
 
-    public void visitClasses(Action<? super TestClassResult> visitor) {
-        for (TestClassResult classResult : results.values()) {
-            visitor.execute(classResult);
+        TestMethodResult methodResult = currentTestMethods.get(testDescriptor);
+        if (methodResult == null) {
+            outputWriter.onOutput(classResult.getId(), outputEvent);
+        } else {
+            outputWriter.onOutput(classResult.getId(), methodResult.getId(), outputEvent);
         }
     }
-
-    public boolean hasOutput(String className, TestOutputEvent.Destination destination) {
-        return outputSerializer.hasOutput(className, destination);
-    }
-
-    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
-        outputSerializer.writeOutputs(className, destination, writer);
-    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java
index 51a3f3a..0e86775 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java
@@ -16,31 +16,38 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result;
 
-import com.esotericsoftware.kryo.io.Input;
-import com.esotericsoftware.kryo.io.Output;
 import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.tasks.testing.TestResult;
 import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.remote.internal.Message;
+import org.gradle.messaging.serialize.Decoder;
+import org.gradle.messaging.serialize.Encoder;
+import org.gradle.messaging.serialize.FlushableEncoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedDecoder;
+import org.gradle.messaging.serialize.kryo.KryoBackedEncoder;
 
 import java.io.*;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 
 public class TestResultSerializer {
-    private static final int RESULT_VERSION = 1;
-    private static final String RESULTS_FILE_NAME = "results.bin";
+    private static final int RESULT_VERSION = 3;
 
-    public void write(Collection<TestClassResult> results, File outputDir) {
+    private final File resultsFile;
+
+    public TestResultSerializer(File resultsDir) {
+        this.resultsFile = new File(resultsDir, "results.bin");
+    }
+
+    public void write(Collection<TestClassResult> results) {
         try {
-            OutputStream outputStream = new FileOutputStream(new File(outputDir, RESULTS_FILE_NAME));
+            OutputStream outputStream = new FileOutputStream(resultsFile);
             try {
-                Output output = new Output(outputStream);
-                output.writeInt(RESULT_VERSION, true);
-                write(results, output);
-                output.flush();
+                if (!results.isEmpty()) { // only write if we have results, otherwise truncate
+                    FlushableEncoder encoder = new KryoBackedEncoder(outputStream);
+                    encoder.writeSmallInt(RESULT_VERSION);
+                    write(results, encoder);
+                    encoder.flush();
+                }
             } finally {
                 outputStream.close();
             }
@@ -49,45 +56,50 @@ public class TestResultSerializer {
         }
     }
 
-    private void write(Collection<TestClassResult> results, Output output) throws IOException {
-        output.writeInt(results.size(), true);
+    private void write(Collection<TestClassResult> results, Encoder encoder) throws IOException {
+        encoder.writeSmallInt(results.size());
         for (TestClassResult result : results) {
-            write(result, output);
+            write(result, encoder);
         }
     }
 
-    private void write(TestClassResult classResult, Output output) throws IOException {
-        output.writeString(classResult.getClassName());
-        output.writeLong(classResult.getStartTime());
-        output.writeInt(classResult.getResults().size(), true);
+    private void write(TestClassResult classResult, Encoder encoder) throws IOException {
+        encoder.writeSmallLong(classResult.getId());
+        encoder.writeString(classResult.getClassName());
+        encoder.writeLong(classResult.getStartTime());
+        encoder.writeSmallInt(classResult.getResults().size());
         for (TestMethodResult methodResult : classResult.getResults()) {
-            write(methodResult, output);
+            write(methodResult, encoder);
         }
     }
 
-    private void write(TestMethodResult methodResult, Output output) throws IOException {
-        output.writeString(methodResult.getName());
-        output.writeInt(methodResult.getResultType().ordinal(), true);
-        output.writeLong(methodResult.getDuration(), true);
-        output.writeLong(methodResult.getEndTime());
-        if (methodResult.getExceptions().isEmpty()) {
-            output.writeBoolean(false);
-        } else {
-            output.writeBoolean(true);
-            Message.send(methodResult.getExceptions(), output);
+    private void write(TestMethodResult methodResult, Encoder encoder) throws IOException {
+        encoder.writeSmallLong(methodResult.getId());
+        encoder.writeString(methodResult.getName());
+        encoder.writeSmallInt(methodResult.getResultType().ordinal());
+        encoder.writeSmallLong(methodResult.getDuration());
+        encoder.writeLong(methodResult.getEndTime());
+        encoder.writeSmallInt(methodResult.getFailures().size());
+        for (TestFailure testFailure : methodResult.getFailures()) {
+            encoder.writeString(testFailure.getExceptionType());
+            encoder.writeString(testFailure.getMessage());
+            encoder.writeString(testFailure.getStackTrace());
         }
     }
 
-    public void read(File inputDir, Action<? super TestClassResult> visitor) {
+    public void read(Action<? super TestClassResult> visitor) {
+        if (!isHasResults()) {
+            return;
+        }
         try {
-            InputStream inputStream = new FileInputStream(new File(inputDir, "results.bin"));
+            InputStream inputStream = new FileInputStream(resultsFile);
             try {
-                Input input = new Input(inputStream);
-                int version = input.readInt(true);
+                Decoder decoder = new KryoBackedDecoder(inputStream);
+                int version = decoder.readSmallInt();
                 if (version != RESULT_VERSION) {
-                    throw new IllegalArgumentException(String.format("Unexpected result file version %d found in %s.", version, inputDir));
+                    throw new IllegalArgumentException(String.format("Unexpected result file version %d found in %s.", version, resultsFile));
                 }
-                readResults(input, visitor);
+                readResults(decoder, visitor);
             } finally {
                 inputStream.close();
             }
@@ -96,38 +108,45 @@ public class TestResultSerializer {
         }
     }
 
-    private void readResults(Input input, Action<? super TestClassResult> visitor) throws ClassNotFoundException, IOException {
-        int classCount = input.readInt(true);
+    public boolean isHasResults() {
+        return resultsFile.exists() && resultsFile.length() > 0;
+    }
+
+    private void readResults(Decoder decoder, Action<? super TestClassResult> visitor) throws ClassNotFoundException, IOException {
+        int classCount = decoder.readSmallInt();
         for (int i = 0; i < classCount; i++) {
-            TestClassResult classResult = readClassResult(input);
+            TestClassResult classResult = readClassResult(decoder);
             visitor.execute(classResult);
         }
     }
 
-    private TestClassResult readClassResult(Input input) throws IOException, ClassNotFoundException {
-        String className = input.readString();
-        long startTime = input.readLong();
-        TestClassResult result = new TestClassResult(className, startTime);
-        int testMethodCount = input.readInt(true);
+    private TestClassResult readClassResult(Decoder decoder) throws IOException, ClassNotFoundException {
+        long id = decoder.readSmallLong();
+        String className = decoder.readString();
+        long startTime = decoder.readLong();
+        TestClassResult result = new TestClassResult(id, className, startTime);
+        int testMethodCount = decoder.readSmallInt();
         for (int i = 0; i < testMethodCount; i++) {
-            TestMethodResult methodResult = readMethodResult(input);
+            TestMethodResult methodResult = readMethodResult(decoder);
             result.add(methodResult);
         }
         return result;
     }
 
-    private TestMethodResult readMethodResult(Input input) throws ClassNotFoundException, IOException {
-        String name = input.readString();
-        TestResult.ResultType resultType = TestResult.ResultType.values()[input.readInt(true)];
-        long duration = input.readLong(true);
-        long endTime = input.readLong();
-        boolean hasFailures = input.readBoolean();
-        List<Throwable> failures;
-        if (hasFailures) {
-            failures = (List<Throwable>) Message.receive(input, getClass().getClassLoader());
-        } else {
-            failures = Collections.emptyList();
+    private TestMethodResult readMethodResult(Decoder decoder) throws ClassNotFoundException, IOException {
+        long id = decoder.readSmallLong();
+        String name = decoder.readString();
+        TestResult.ResultType resultType = TestResult.ResultType.values()[decoder.readSmallInt()];
+        long duration = decoder.readSmallLong();
+        long endTime = decoder.readLong();
+        TestMethodResult methodResult = new TestMethodResult(id, name, resultType, duration, endTime);
+        int failures = decoder.readSmallInt();
+        for (int i = 0; i < failures; i++) {
+            String exceptionType = decoder.readString();
+            String message = decoder.readString();
+            String stackTrace = decoder.readString();
+            methodResult.addFailure(message, stackTrace, exceptionType);
         }
-        return new TestMethodResult(name, resultType, duration, endTime, failures);
+        return methodResult;
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
index 9fc3550..4b2b163 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
@@ -19,21 +19,32 @@ package org.gradle.api.internal.tasks.testing.junit.result;
 import org.gradle.api.Action;
 import org.gradle.api.tasks.testing.TestOutputEvent;
 
+import java.io.Closeable;
 import java.io.Writer;
 
-/**
- * by Szczepan Faber, created at: 11/16/12
- */
-public interface TestResultsProvider {
+public interface TestResultsProvider extends Closeable {
     /**
      * Writes the output of the given test to the given writer. This method must be called only after {@link #visitClasses(org.gradle.api.Action)}.
+     *
+     * Writes all output for the test class.
      */
-    void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer);
+    void writeAllOutput(long id, TestOutputEvent.Destination destination, Writer writer);
+
+    void writeNonTestOutput(long id, TestOutputEvent.Destination destination, Writer writer);
+
+    /**
+     * Writes the output of the given test to the given writer. This method must be called only after {@link #visitClasses(org.gradle.api.Action)}.
+     *
+     * Write all output for the given test case name of the test class.
+     */
+    void writeTestOutput(long classId, long testId, TestOutputEvent.Destination destination, Writer writer);
 
     /**
      * Visits the results of each test class, in no specific order. Each class is visited exactly once.
      */
     void visitClasses(Action<? super TestClassResult> visitor);
 
-    boolean hasOutput(String className, TestOutputEvent.Destination destination);
+    boolean hasOutput(long id, TestOutputEvent.Destination destination);
+
+    boolean isHasResults();
 }
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 c1d9373..145a2cb 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
@@ -51,7 +51,7 @@ public abstract class AbstractTestLogger {
     protected void logEvent(TestDescriptor descriptor, TestLogEvent event, @Nullable String details) {
         StyledTextOutput output = textOutputFactory.create("TestEventLogger", logLevel);
         if (!descriptor.equals(lastSeenTestDescriptor) || event != lastSeenTestEvent) {
-            output.append(TextUtil.getPlatformLineSeparator() + getEventPath(descriptor));
+            output.println().append(getEventPath(descriptor));
             output.withStyle(getStyle(event)).println(event.toString());
         }
         lastSeenTestDescriptor = descriptor;
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 9784255..4a7d6e3 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
@@ -20,7 +20,7 @@ import org.gradle.internal.Factory;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.actor.Actor;
 import org.gradle.messaging.actor.ActorFactory;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java
index 17dc070..e892c40 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java
@@ -25,11 +25,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-/**
- * Extracted from the StateTrackingTestResultProcessor
- *
- * by Szczepan Faber, created at: 10/14/11
- */
 public class TestState {
     public final TestDescriptorInternal test;
     final TestStartEvent startEvent;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java
index 3962a70..8a9fc48 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java
@@ -15,12 +15,14 @@
  */
 package org.gradle.api.internal.tasks.testing.results;
 
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
 import org.gradle.api.tasks.testing.TestDescriptor;
 
-/**
- * by Szczepan Faber, created at: 1/8/12
- */
-public class UnknownTestDescriptor implements TestDescriptor {
+public class UnknownTestDescriptor implements TestDescriptorInternal {
+
+    public Object getId() {
+        return "Unknown test (possible bug, please report)";
+    }
 
     public String getName() {
         return "Unknown test (possible bug, please report)";
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 ae64537..1bd389c 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
@@ -23,9 +23,6 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 
-/**
- * @author Tom Eyckmans
- */
 class TestNGDetector extends AbstractTestFrameworkDetector<TestNGTestClassDetecter> {
     private static final Logger LOGGER = LoggerFactory.getLogger(TestNGDetector.class);
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java
index a801553..a145baf 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java
@@ -15,7 +15,8 @@
  */
 package org.gradle.api.internal.tasks.testing.testng;
 
-import org.gradle.util.ReflectionUtil;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.reflect.JavaMethod;
 import org.testng.ITestListener;
 
 import java.lang.reflect.InvocationHandler;
@@ -42,7 +43,7 @@ class TestNGListenerAdapterFactory {
 
         throw new UnsupportedOperationException("Neither found interface 'org.testng.IConfigurationListener2' nor interface 'org.testng.internal.IConfigurationListener'. Which version of TestNG are you using?");
     }
-    
+
     private Class<?> tryLoadClass(String name) {
         try {
             return classLoader.loadClass(name);
@@ -50,11 +51,23 @@ class TestNGListenerAdapterFactory {
             return null;
         }
     }
-    
+
     private ITestListener createProxy(Class<?> configListenerClass, final ITestListener listener) {
-        return (ITestListener) Proxy.newProxyInstance(classLoader, new Class<?>[] {ITestListener.class, configListenerClass}, new InvocationHandler() {
-            public Object invoke(Object proxy, Method method, Object[] args) {
-                return ReflectionUtil.invoke(listener, method.getName(), args);
+        return (ITestListener) Proxy.newProxyInstance(classLoader, new Class<?>[]{ITestListener.class, configListenerClass}, new InvocationHandler() {
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                Class<?> realReturnType = method.getReturnType();
+                Class<?> boxedReturnType = realReturnType;
+                if (!realReturnType.equals(void.class) && realReturnType.isPrimitive()) {
+                    boxedReturnType = JavaReflectionUtil.getWrapperTypeForPrimitiveType(realReturnType);
+                }
+
+                return invoke(listener.getClass(), listener, boxedReturnType, method, args);
+            }
+
+            private <T, R> R invoke(Class<T> listenerType, Object listener, Class<R> returnType, Method method, Object[] args) {
+                T listenerCast = listenerType.cast(listener);
+                JavaMethod<T, R> javaMethod = JavaReflectionUtil.method(listenerType, returnType, method.getName(), method.getParameterTypes());
+                return javaMethod.invoke(listenerCast, args);
             }
         });
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java
index d424dfd..1d4272f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing.testng;
 
+import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
 
 import java.io.Serializable;
@@ -36,8 +37,9 @@ public class TestNGSpec implements Serializable {
     private final Set<String> includeGroups;
     private final Set<String> excludeGroups;
     private final Set<String> listeners;
+    private final Set<String> includedTests;
 
-    public TestNGSpec(TestNGOptions options) {
+    public TestNGSpec(TestNGOptions options, DefaultTestFilter filter) {
         this.defaultSuiteName = options.getSuiteName();
         this.defaultTestName = options.getTestName();
         this.parallel = options.getParallel();
@@ -49,6 +51,7 @@ public class TestNGSpec implements Serializable {
         this.includeGroups = options.getIncludeGroups();
         this.excludeGroups = options.getExcludeGroups();
         this.listeners = options.getListeners();
+        this.includedTests = filter.getIncludePatterns();
     }
 
     public Set<String> getListeners() {
@@ -94,4 +97,8 @@ public class TestNGSpec implements Serializable {
     public String getDefaultSuiteName() {
         return defaultSuiteName;
     }
+
+    public Set<String> getIncludedTests() {
+        return includedTests;
+    }
 }
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 61e0e5a..ddb4b44 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
@@ -21,9 +21,6 @@ import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-/**
- * @author Tom Eyckmans
- */
 class TestNGTestClassDetecter extends TestClassVisitor {
     private boolean isAbstract;
     private String className;
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 ad884ec..f14ee1c 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
@@ -21,18 +21,20 @@ 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.processors.CaptureTestOutputTestResultProcessor;
+import org.gradle.api.internal.tasks.testing.filter.TestSelectionMatcher;
 import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.reflect.NoSuchMethodException;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.ReflectionUtil;
-import org.testng.ITestListener;
-import org.testng.TestNG;
+import org.testng.*;
 
 import java.io.File;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 public class TestNGTestClassProcessor implements TestClassProcessor {
     private final List<Class<?>> testClasses = new ArrayList<Class<?>>();
@@ -77,13 +79,9 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
         testNg.setParallel(options.getParallel());
         testNg.setThreadCount(options.getThreadCount());
         try {
-            final Method setAnnotations = TestNG.class.getMethod("setAnnotations");
-            setAnnotations.invoke(testNg, options.getAnnotations());
-            ReflectionUtil.invoke(testNg, "setAnnotations", options.getAnnotations());
+            JavaReflectionUtil.method(TestNG.class, Object.class, "setAnnotations").invoke(testNg, options.getAnnotations());
         } catch (NoSuchMethodException e) {
             /* do nothing; method has been removed in TestNG 6.3 */
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not configure TestNG annotations with value '%s'.", options.getAnnotations()), e);
         }
         if (options.getJavadocAnnotations()) {
             testNg.setSourcePath(CollectionUtils.join(File.pathSeparator, options.getTestResources()));
@@ -91,6 +89,9 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
 
         testNg.setUseDefaultListeners(options.getUseDefaultListeners());
         testNg.addListener((Object) adaptListener(testResultProcessor));
+        if (!options.getIncludedTests().isEmpty()) {
+            testNg.addListener(new SelectedTestsFilter(options.getIncludedTests()));
+        }
         testNg.setVerbose(0);
         testNg.setGroups(CollectionUtils.join(",", options.getIncludeGroups()));
         testNg.setExcludedGroups(CollectionUtils.join(",", options.getExcludeGroups()));
@@ -105,8 +106,7 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
         if (!suiteFiles.isEmpty()) {
             testNg.setTestSuites(GFileUtils.toPaths(suiteFiles));
         } else {
-            Class[] classes = testClasses.toArray(new Class[testClasses.size()]);
-            testNg.setTestClasses(classes);
+            testNg.setTestClasses(testClasses.toArray(new Class[testClasses.size()]));
         }
 
         testNg.run();
@@ -116,4 +116,23 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
         TestNGListenerAdapterFactory factory = new TestNGListenerAdapterFactory(applicationClassLoader);
         return factory.createAdapter(listener);
     }
-}
+
+    private static class SelectedTestsFilter implements IMethodInterceptor {
+
+        private final TestSelectionMatcher matcher;
+
+        public SelectedTestsFilter(Set<String> includedTests) {
+            matcher = new TestSelectionMatcher(includedTests);
+        }
+
+        public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+            List<IMethodInstance> filtered = new LinkedList<IMethodInstance>();
+            for (IMethodInstance candidate : methods) {
+                if (matcher.matchesTest(candidate.getMethod().getTestClass().getName(), candidate.getMethod().getMethodName())) {
+                    filtered.add(candidate);
+                }
+            }
+            return filtered;
+        }
+    }
+}
\ No newline at end of file
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 af43966..a438877 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,40 +18,53 @@ package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.Action;
 import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.plugins.DslObject;
 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.filter.DefaultTestFilter;
 import org.gradle.api.internal.tasks.testing.junit.JULRedirector;
+import org.gradle.api.reporting.DirectoryReport;
 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.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
 import java.io.File;
 import java.io.Serializable;
 import java.util.List;
+import java.util.concurrent.Callable;
 
-/**
- * @author Tom Eyckmans
- */
 public class TestNGTestFramework implements TestFramework {
     private TestNGOptions options;
     private TestNGDetector detector;
     final Test testTask;
+    private DefaultTestFilter filter;
 
-    public TestNGTestFramework(Test testTask) {
+    public TestNGTestFramework(Test testTask, DefaultTestFilter filter, Instantiator instantiator) {
         this.testTask = testTask;
-        options = new TestNGOptions(testTask.getProject().getProjectDir());
+        this.filter = filter;
+        options = instantiator.newInstance(TestNGOptions.class, testTask.getProject().getProjectDir());
         options.setAnnotationsOnSourceCompatibility(JavaVersion.toVersion(testTask.getProject().property("sourceCompatibility")));
+        conventionMapOutputDirectory(options, testTask.getReports().getHtml());
         detector = new TestNGDetector(new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
     }
 
+    private static void conventionMapOutputDirectory(TestNGOptions options, final DirectoryReport html) {
+        new DslObject(options).getConventionMapping().map("outputDirectory", new Callable<File>() {
+            public File call() {
+                return html.getDestination();
+            }
+        });
+    }
+
     public WorkerTestClassProcessorFactory getProcessorFactory() {
         options.setTestResources(testTask.getTestSrcDirs());
         List<File> suiteFiles = options.getSuites(testTask.getTemporaryDir());
-        return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), new TestNGSpec(options), suiteFiles);
+        return new TestClassProcessorFactoryImpl(options.getOutputDirectory(), new TestNGSpec(options, filter), suiteFiles);
     }
 
     public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
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 381a484..e7ef57b 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
@@ -22,9 +22,6 @@ import org.objectweb.asm.Opcodes;
 import java.util.HashSet;
 import java.util.Set;
 
-/**
- * @author Tom Eyckmans
- */
 class TestNGTestMethodDetecter extends MethodVisitor {
     private final TestNGTestClassDetecter testClassDetecter;
     private final Set<String> testMethodAnnotations = new HashSet<String>();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
index b669ac4..8d549a2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
@@ -70,7 +70,9 @@ public class TestNGTestResultProcessorAdapter implements ITestListener, TestNGCo
         TestDescriptorInternal testInternal;
         Object parentId;
         synchronized (lock) {
-            testInternal = new DefaultTestMethodDescriptor(idGenerator.generateId(), iTestResult.getTestClass().getName(), iTestResult.getName());
+            String name = calculateTestCaseName(iTestResult);
+
+            testInternal = new DefaultTestMethodDescriptor(idGenerator.generateId(), iTestResult.getTestClass().getName(), name);
             Object oldTestId = tests.put(iTestResult, testInternal.getId());
             assert oldTestId == null : "Apparently some other test has started but it hasn't finished. "
                     + "Expect the resultProcessor to break. "
@@ -80,6 +82,44 @@ public class TestNGTestResultProcessorAdapter implements ITestListener, TestNGCo
             assert parentId != null;
         }
         resultProcessor.started(testInternal, new TestStartEvent(iTestResult.getStartMillis(), parentId));
+
+        if (iTestResult.getThrowable() instanceof UnrepresentableParameterException) {
+            throw (UnrepresentableParameterException) iTestResult.getThrowable();
+        }
+    }
+
+    private String calculateTestCaseName(ITestResult iTestResult) {
+        Object[] parameters = iTestResult.getParameters();
+        String name = iTestResult.getName();
+        if (parameters != null && parameters.length > 0) {
+            StringBuilder builder = new StringBuilder(name).
+                    append("[").
+                    append(iTestResult.getMethod().getCurrentInvocationCount()).
+                    append("]");
+
+            StringBuilder paramsListBuilder = new StringBuilder("(");
+            int i = 0;
+            for (Object parameter : parameters) {
+                if (parameter == null) {
+                    paramsListBuilder.append("null");
+                } else {
+                    try {
+                        paramsListBuilder.append(parameter.toString());
+                    } catch (Exception e) {
+                        // This may be thrown by the caller of this method at a later time
+                        iTestResult.setThrowable(new UnrepresentableParameterException(iTestResult, i, e));
+                        return builder.toString();
+                    }
+                }
+                if (++i < parameters.length) {
+                    paramsListBuilder.append(", ");
+                }
+            }
+            paramsListBuilder.append(")");
+            return builder.append(paramsListBuilder.toString()).toString();
+        } else {
+            return name;
+        }
     }
 
     public void onTestSuccess(ITestResult iTestResult) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/UnrepresentableParameterException.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/UnrepresentableParameterException.java
new file mode 100644
index 0000000..bd3c319
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/UnrepresentableParameterException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.testng;
+
+import org.gradle.api.GradleException;
+import org.testng.ITestResult;
+
+public class UnrepresentableParameterException extends GradleException {
+
+    public UnrepresentableParameterException(ITestResult iTestResult, int paramIndex, Throwable cause) {
+        super(
+                String.format("Parameter %s of iteration %s of method '%s' toString() method threw exception",
+                        paramIndex + 1, iTestResult.getMethod().getCurrentInvocationCount() + 1, iTestResult.getName()
+                ),
+                cause
+        );
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
index c30c74c..ca3477f 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
@@ -17,11 +17,12 @@
 package org.gradle.api.internal.tasks.testing.worker;
 
 import org.gradle.api.Action;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.JavaForkOptions;
 import org.gradle.process.internal.WorkerProcess;
 import org.gradle.process.internal.WorkerProcessBuilder;
@@ -52,25 +53,33 @@ public class ForkingTestClassProcessor implements TestClassProcessor {
 
     public void processTestClass(TestClassRunInfo testClass) {
         if (remoteProcessor == null) {
-            WorkerProcessBuilder builder = workerFactory.create();
-            builder.applicationClasspath(classPath);
-            builder.setLoadApplicationInSystemClassLoader(true);
-            builder.worker(new TestWorker(processorFactory));
-            options.copyTo(builder.getJavaCommand());
-            buildConfigAction.execute(builder);
-            
-            workerProcess = builder.build();
-            workerProcess.start();
-
-            workerProcess.getConnection().addIncoming(TestResultProcessor.class, resultProcessor);
-            remoteProcessor = workerProcess.getConnection().addOutgoing(RemoteTestClassProcessor.class);
-
-            remoteProcessor.startProcessing();
+            remoteProcessor = forkProcess();
         }
 
         remoteProcessor.processTestClass(testClass);
     }
 
+    RemoteTestClassProcessor forkProcess() {
+        WorkerProcessBuilder builder = workerFactory.create();
+        builder.setBaseName("Gradle Test Executor");
+        builder.applicationClasspath(classPath);
+        builder.setLoadApplicationInSystemClassLoader(true);
+        builder.worker(new TestWorker(processorFactory));
+        options.copyTo(builder.getJavaCommand());
+        buildConfigAction.execute(builder);
+
+        workerProcess = builder.build();
+        workerProcess.start();
+
+        ObjectConnection connection = workerProcess.getConnection();
+        connection.useParameterSerializer(new TestEventSerializer());
+        connection.addIncoming(TestResultProcessor.class, resultProcessor);
+        RemoteTestClassProcessor remoteProcessor = connection.addOutgoing(RemoteTestClassProcessor.class);
+        connection.connect();
+        remoteProcessor.startProcessing();
+        return remoteProcessor;
+    }
+
     public void stop() {
         if (remoteProcessor != null) {
             remoteProcessor.stop();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestEventSerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestEventSerializer.java
new file mode 100644
index 0000000..6fc5f78
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestEventSerializer.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.worker;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.internal.id.CompositeIdGenerator;
+import org.gradle.messaging.remote.internal.Message;
+import org.gradle.messaging.serialize.*;
+import org.gradle.messaging.serialize.kryo.StatefulSerializer;
+
+public class TestEventSerializer implements StatefulSerializer<Object[]> {
+    private final Serializer<Object> paramSerializer;
+
+    public TestEventSerializer() {
+        DefaultSerializerRegistry<Object> registry = new DefaultSerializerRegistry<Object>();
+        registry.register(DefaultTestClassRunInfo.class, new DefaultTestClassRunInfoSerializer());
+        registry.register(CompositeIdGenerator.CompositeId.class, new IdSerializer());
+        registry.register(DefaultTestSuiteDescriptor.class, new DefaultTestSuiteDescriptorSerializer());
+        registry.register(WorkerTestClassProcessor.WorkerTestSuiteDescriptor.class, new WorkerTestSuiteDescriptorSerializer());
+        registry.register(DefaultTestClassDescriptor.class, new DefaultTestClassDescriptorSerializer());
+        registry.register(DefaultTestMethodDescriptor.class, new DefaultTestMethodDescriptorSerializer());
+        registry.register(DefaultTestDescriptor.class, new DefaultTestDescriptorSerializer());
+        registry.register(TestStartEvent.class, new TestStartEventSerializer());
+        registry.register(TestCompleteEvent.class, new TestCompleteEventSerializer());
+        registry.register(DefaultTestOutputEvent.class, new DefaultTestOutputEventSerializer());
+        registry.register(Throwable.class, new ThrowableSerializer());
+        paramSerializer = registry.build();
+    }
+
+    public ObjectReader<Object[]> newReader(final Decoder decoder) {
+        return new ObjectReader<Object[]>() {
+            public Object[] read() throws Exception {
+                int count = decoder.readSmallInt();
+                Object[] params = new Object[count];
+                for (int i = 0; i < params.length; i++) {
+                    params[i] = paramSerializer.read(decoder);
+                }
+                return params;
+            }
+        };
+    }
+
+    public ObjectWriter<Object[]> newWriter(final Encoder encoder) {
+        return new ObjectWriter<Object[]>() {
+            public void write(Object[] value) throws Exception {
+                encoder.writeSmallInt(value.length);
+                for (int i = 0; i < value.length; i++) {
+                    paramSerializer.write(encoder, value[i]);
+                }
+            }
+        };
+    }
+
+    private static class EnumSerializer<T extends Enum> implements Serializer<T> {
+        private final Class<T> type;
+
+        private EnumSerializer(Class<T> type) {
+            this.type = type;
+            if (type.getEnumConstants().length > Byte.MAX_VALUE) {
+                throw new IllegalArgumentException(String.format("Too many constants for enum %s", type.getName()));
+            }
+        }
+
+        public T read(Decoder decoder) throws Exception {
+            return type.getEnumConstants()[decoder.readByte()];
+        }
+
+        public void write(Encoder encoder, T value) throws Exception {
+            encoder.writeByte((byte) value.ordinal());
+        }
+    }
+
+    private static class NullableSerializer<T> implements Serializer<T> {
+        private final Serializer<T> serializer;
+
+        private NullableSerializer(Serializer<T> serializer) {
+            this.serializer = serializer;
+        }
+
+        public T read(Decoder decoder) throws Exception {
+            if (!decoder.readBoolean()) {
+                return null;
+            }
+            return serializer.read(decoder);
+        }
+
+        public void write(Encoder encoder, T value) throws Exception {
+            encoder.writeBoolean(value != null);
+            if (value != null) {
+                serializer.write(encoder, value);
+            }
+        }
+    }
+
+    private static class ThrowableSerializer implements Serializer<Throwable> {
+        public Throwable read(Decoder decoder) throws Exception {
+            return (Throwable) Message.receive(decoder.getInputStream(), getClass().getClassLoader());
+        }
+
+        public void write(Encoder encoder, Throwable value) throws Exception {
+            Message.send(value, encoder.getOutputStream());
+        }
+    }
+
+    private static class IdSerializer implements Serializer<CompositeIdGenerator.CompositeId> {
+        public CompositeIdGenerator.CompositeId read(Decoder decoder) throws Exception {
+            return new CompositeIdGenerator.CompositeId(decoder.readLong(), decoder.readLong());
+        }
+
+        public void write(Encoder encoder, CompositeIdGenerator.CompositeId value) throws Exception {
+            encoder.writeLong((Long) value.getScope());
+            encoder.writeLong((Long) value.getId());
+        }
+    }
+
+    private static class DefaultTestClassRunInfoSerializer implements Serializer<DefaultTestClassRunInfo> {
+        public DefaultTestClassRunInfo read(Decoder decoder) throws Exception {
+            return new DefaultTestClassRunInfo(decoder.readString());
+        }
+
+        public void write(Encoder encoder, DefaultTestClassRunInfo value) throws Exception {
+            encoder.writeString(value.getTestClassName());
+        }
+    }
+
+    private static class TestStartEventSerializer implements Serializer<TestStartEvent> {
+        final Serializer<CompositeIdGenerator.CompositeId> idSerializer = new NullableSerializer<CompositeIdGenerator.CompositeId>(new IdSerializer());
+
+        public TestStartEvent read(Decoder decoder) throws Exception {
+            long time = decoder.readLong();
+            Object id = idSerializer.read(decoder);
+            return new TestStartEvent(time, id);
+        }
+
+        public void write(Encoder encoder, TestStartEvent value) throws Exception {
+            encoder.writeLong(value.getStartTime());
+            idSerializer.write(encoder, (CompositeIdGenerator.CompositeId) value.getParentId());
+        }
+    }
+
+    private static class TestCompleteEventSerializer implements Serializer<TestCompleteEvent> {
+        private final Serializer<TestResult.ResultType> typeSerializer = new NullableSerializer<TestResult.ResultType>(new EnumSerializer<TestResult.ResultType>(TestResult.ResultType.class));
+
+        public TestCompleteEvent read(Decoder decoder) throws Exception {
+            long endTime = decoder.readLong();
+            TestResult.ResultType result = typeSerializer.read(decoder);
+            return new TestCompleteEvent(endTime, result);
+        }
+
+        public void write(Encoder encoder, TestCompleteEvent value) throws Exception {
+            encoder.writeLong(value.getEndTime());
+            typeSerializer.write(encoder, value.getResultType());
+        }
+    }
+
+    private static class DefaultTestOutputEventSerializer implements Serializer<DefaultTestOutputEvent> {
+        private final Serializer<TestOutputEvent.Destination> destinationSerializer = new EnumSerializer<TestOutputEvent.Destination>(TestOutputEvent.Destination.class);
+        
+        public DefaultTestOutputEvent read(Decoder decoder) throws Exception {
+            TestOutputEvent.Destination destination = destinationSerializer.read(decoder);
+            String message = decoder.readString();
+            return new DefaultTestOutputEvent(destination, message);
+        }
+
+        public void write(Encoder encoder, DefaultTestOutputEvent value) throws Exception {
+            destinationSerializer.write(encoder, value.getDestination());
+            encoder.writeString(value.getMessage());
+        }
+    }
+
+    private static class DefaultTestSuiteDescriptorSerializer implements Serializer<DefaultTestSuiteDescriptor> {
+        final Serializer<CompositeIdGenerator.CompositeId> idSerializer = new IdSerializer();
+
+        public DefaultTestSuiteDescriptor read(Decoder decoder) throws Exception {
+            Object id = idSerializer.read(decoder);
+            String name = decoder.readString();
+            return new DefaultTestSuiteDescriptor(id, name);
+        }
+
+        public void write(Encoder encoder, DefaultTestSuiteDescriptor value) throws Exception {
+            idSerializer.write(encoder, (CompositeIdGenerator.CompositeId) value.getId());
+            encoder.writeString(value.getName());
+        }
+    }
+
+    private static class WorkerTestSuiteDescriptorSerializer implements Serializer<WorkerTestClassProcessor.WorkerTestSuiteDescriptor> {
+        final Serializer<CompositeIdGenerator.CompositeId> idSerializer = new IdSerializer();
+
+        public WorkerTestClassProcessor.WorkerTestSuiteDescriptor read(Decoder decoder) throws Exception {
+            Object id = idSerializer.read(decoder);
+            String name = decoder.readString();
+            return new WorkerTestClassProcessor.WorkerTestSuiteDescriptor(id, name);
+        }
+
+        public void write(Encoder encoder, WorkerTestClassProcessor.WorkerTestSuiteDescriptor value) throws Exception {
+            idSerializer.write(encoder, (CompositeIdGenerator.CompositeId) value.getId());
+            encoder.writeString(value.getName());
+        }
+    }
+
+    private static class DefaultTestClassDescriptorSerializer implements Serializer<DefaultTestClassDescriptor> {
+        final Serializer<CompositeIdGenerator.CompositeId> idSerializer = new IdSerializer();
+
+        public DefaultTestClassDescriptor read(Decoder decoder) throws Exception {
+            Object id = idSerializer.read(decoder);
+            String name = decoder.readString();
+            return new DefaultTestClassDescriptor(id, name);
+        }
+
+        public void write(Encoder encoder, DefaultTestClassDescriptor value) throws Exception {
+            idSerializer.write(encoder, (CompositeIdGenerator.CompositeId) value.getId());
+            encoder.writeString(value.getName());
+        }
+    }
+
+    private static class DefaultTestDescriptorSerializer implements Serializer<DefaultTestDescriptor> {
+        final Serializer<CompositeIdGenerator.CompositeId> idSerializer = new IdSerializer();
+
+        public DefaultTestDescriptor read(Decoder decoder) throws Exception {
+            Object id = idSerializer.read(decoder);
+            String className = decoder.readString();
+            String name = decoder.readString();
+            return new DefaultTestDescriptor(id, className, name);
+        }
+
+        public void write(Encoder encoder, DefaultTestDescriptor value) throws Exception {
+            idSerializer.write(encoder, (CompositeIdGenerator.CompositeId) value.getId());
+            encoder.writeString(value.getClassName());
+            encoder.writeString(value.getName());
+        }
+    }
+
+    private static class DefaultTestMethodDescriptorSerializer implements Serializer<DefaultTestMethodDescriptor> {
+        final Serializer<CompositeIdGenerator.CompositeId> idSerializer = new IdSerializer();
+
+        public DefaultTestMethodDescriptor read(Decoder decoder) throws Exception {
+            Object id = idSerializer.read(decoder);
+            String className = decoder.readString();
+            String name = decoder.readString();
+            return new DefaultTestMethodDescriptor(id, className, name);
+        }
+
+        public void write(Encoder encoder, DefaultTestMethodDescriptor value) throws Exception {
+            idSerializer.write(encoder, (CompositeIdGenerator.CompositeId) value.getId());
+            encoder.writeString(value.getClassName());
+            encoder.writeString(value.getName());
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
index b75c3d0..c7b2e72 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
@@ -54,7 +54,7 @@ public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClass
     }
 
     public void execute(final WorkerProcessContext workerProcessContext) {
-        LOGGER.info("{} executing tests.", workerProcessContext.getDisplayName());
+        LOGGER.info("{} started executing tests.", workerProcessContext.getDisplayName());
 
         completed = new CountDownLatch(1);
 
@@ -88,8 +88,10 @@ public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClass
         processor = proxy.getSource();
 
         ObjectConnection serverConnection = workerProcessContext.getServerConnection();
+        serverConnection.useParameterSerializer(new TestEventSerializer());
         this.resultProcessor = serverConnection.addOutgoing(TestResultProcessor.class);
         serverConnection.addIncoming(RemoteTestClassProcessor.class, this);
+        serverConnection.connect();
     }
 
     public void startProcessing() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
index ad12819..0392e33 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
@@ -26,14 +26,14 @@ public class WorkerTestClassProcessor extends SuiteTestClassProcessor {
         super(new WorkerTestSuiteDescriptor(workerSuiteId, workerDisplayName), processor, timeProvider);
     }
 
-    private static class WorkerTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
-        private WorkerTestSuiteDescriptor(Object id, String name) {
+    public static class WorkerTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
+        public WorkerTestSuiteDescriptor(Object id, String name) {
             super(id, name);
         }
 
         @Override
         public String toString() {
-            return String.format("test process '%s'", getName());
+            return String.format("process '%s'", getName());
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.java
index 05b30a3..faba4ac 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.java
@@ -19,8 +19,6 @@ import java.util.Map;
 
 /**
  * Represent the attributes of a manifest section.
- *
- * @author Hans Dockter
  */
 public interface Attributes extends Map<String, Object> {
     
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java
index 0cf3cb6..d757f4e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java
@@ -20,8 +20,6 @@ import org.gradle.api.GradleException;
 /**
  * Is thrown in the case an operation is applied against a {@link org.gradle.api.java.archives.Manifest} that violates
  * the Manifest specification.
- * 
- * @author Hans Dockter
  */
 public class ManifestException extends GradleException {
     public ManifestException(String message) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java
index 631a7aa..3c9899f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java
@@ -23,9 +23,6 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultAttributes implements Attributes {
     protected Map<String, Object> attributes = new LinkedHashMap<String, Object>();
 
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 7463325..ad21bff 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,8 +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.internal.ErroringAction;
+import org.gradle.internal.IoActions;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.java.archives.Attributes;
 import org.gradle.api.java.archives.ManifestMergeSpec;
@@ -32,9 +32,6 @@ import org.gradle.util.ConfigureUtil;
 import java.io.*;
 import java.util.*;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultManifest implements org.gradle.api.java.archives.Manifest {
     private List<ManifestMergeSpec> manifestMergeSpecs = new ArrayList<ManifestMergeSpec>();
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java
index 4871cfe..d011aff 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java
@@ -17,9 +17,6 @@ package org.gradle.api.java.archives.internal;
 
 import org.gradle.api.java.archives.ManifestMergeDetails;
 
-/**
- * @author Hans Dockter
- */
 public class DefaultManifestMergeDetails implements ManifestMergeDetails {
     private String section;
     private String key;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
index 305fc76..4ab5f9f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
@@ -15,21 +15,19 @@
  */
 package org.gradle.api.plugins
 
+import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.file.CopySpec
 import org.gradle.api.tasks.JavaExec
 import org.gradle.api.tasks.Sync
 import org.gradle.api.tasks.application.CreateStartScripts
-import org.gradle.api.tasks.bundling.Zip
-import org.gradle.api.tasks.bundling.Tar
 import org.gradle.api.tasks.bundling.AbstractArchiveTask
-import org.gradle.api.GradleException
+import org.gradle.api.tasks.bundling.Tar
+import org.gradle.api.tasks.bundling.Zip
 
 /**
  * <p>A {@link Plugin} which runs a project as a Java Application.</p>
- *
- * @author Rene Groeschke
  */
 class ApplicationPlugin implements Plugin<Project> {
     static final String APPLICATION_PLUGIN_NAME = "application"
@@ -66,25 +64,27 @@ class ApplicationPlugin implements Plugin<Project> {
     }
 
     private void addRunTask() {
-        def run = project.tasks.add(TASK_RUN_NAME, JavaExec)
+        def run = project.tasks.create(TASK_RUN_NAME, JavaExec)
         run.description = "Runs this project as a JVM application"
         run.group = APPLICATION_GROUP
         run.classpath = project.sourceSets.main.runtimeClasspath
         run.conventionMapping.main = { pluginConvention.mainClassName }
+        run.conventionMapping.jvmArgs = { pluginConvention.applicationDefaultJvmArgs }
     }
 
     // @Todo: refactor this task configuration to extend a copy task and use replace tokens
     private void addCreateScriptsTask() {
-        def startScripts = project.tasks.add(TASK_START_SCRIPTS_NAME, CreateStartScripts)
+        def startScripts = project.tasks.create(TASK_START_SCRIPTS_NAME, CreateStartScripts)
         startScripts.description = "Creates OS specific scripts to run the project as a JVM application."
         startScripts.classpath = project.tasks[JavaPlugin.JAR_TASK_NAME].outputs.files + project.configurations.runtime
         startScripts.conventionMapping.mainClassName = { pluginConvention.mainClassName }
         startScripts.conventionMapping.applicationName = { pluginConvention.applicationName }
         startScripts.conventionMapping.outputDir = { new File(project.buildDir, 'scripts') }
+        startScripts.conventionMapping.defaultJvmOpts = { pluginConvention.applicationDefaultJvmArgs }
     }
 
     private void addInstallTask() {
-        def installTask = project.tasks.add(TASK_INSTALL_NAME, Sync)
+        def installTask = project.tasks.create(TASK_INSTALL_NAME, Sync)
         installTask.description = "Installs the project as a JVM application along with libs and OS specific scripts."
         installTask.group = APPLICATION_GROUP
         installTask.with pluginConvention.applicationDistribution
@@ -113,7 +113,7 @@ class ApplicationPlugin implements Plugin<Project> {
 	}
 
     private <T extends AbstractArchiveTask> void addArchiveTask(String name, Class<T> type) {
-        def archiveTask = project.tasks.add(name, type)
+        def archiveTask = project.tasks.create(name, type)
         archiveTask.description = "Bundles the project as a JVM application with libs and OS specific scripts."
         archiveTask.group = APPLICATION_GROUP
         archiveTask.conventionMapping.baseName = { pluginConvention.applicationName }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy
index 85f3f27..3efa1f2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy
@@ -20,8 +20,6 @@ import org.gradle.api.file.CopySpec
 
 /**
  * <p>A {@link Convention} used for the ApplicationPlugin.</p>
- *
- * @author Rene Groeschke
  */
 class ApplicationPluginConvention {
     /**
@@ -35,6 +33,11 @@ class ApplicationPluginConvention {
     String mainClassName
 
     /**
+     * Array of string arguments to pass to the JVM when running the application
+     */
+    Iterable<String> applicationDefaultJvmArgs = []
+
+    /**
      * <p>The specification of the contents of the distribution.</p>
      * <p>
      * Use this {@link org.gradle.api.file.CopySpec} to include extra files/resource in the application distribution.
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 ab10950..6d492e5 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
@@ -20,19 +20,26 @@ import org.gradle.api.Action;
 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.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectPublication;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry;
 import org.gradle.api.internal.plugins.BuildConfigurationRule;
 import org.gradle.api.internal.plugins.CleanRule;
 import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.internal.plugins.UploadRule;
 import org.gradle.api.tasks.Delete;
+import org.gradle.api.tasks.Upload;
 import org.gradle.api.tasks.bundling.AbstractArchiveTask;
 import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.configuration.project.ProjectConfigurationActionContainer;
+import org.gradle.language.base.plugins.LanguageBasePlugin;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.util.concurrent.Callable;
 
@@ -42,15 +49,26 @@ import java.util.concurrent.Callable;
 public class BasePlugin implements Plugin<Project> {
     public static final String CLEAN_TASK_NAME = "clean";
     public static final String ASSEMBLE_TASK_NAME = "assemble";
-    public static final String BUILD_GROUP = "build";
+    public static final String UPLOAD_ARCHIVES_TASK_NAME = "uploadArchives";
+    public static final String BUILD_GROUP = LanguageBasePlugin.BUILD_GROUP;
     public static final String UPLOAD_GROUP = "upload";
 
+    private final ProjectPublicationRegistry publicationRegistry;
+    private final ProjectConfigurationActionContainer configurationActionContainer;
+
+    @Inject
+    public BasePlugin(ProjectPublicationRegistry publicationRegistry, ProjectConfigurationActionContainer configurationActionContainer) {
+        this.publicationRegistry = publicationRegistry;
+        this.configurationActionContainer = configurationActionContainer;
+    }
+
     public void apply(Project project) {
         BasePluginConvention convention = new BasePluginConvention(project);
         project.getConvention().getPlugins().put("base", convention);
 
         configureBuildConfigurationRule(project);
         configureUploadRules(project);
+        configureUploadArchivesTask();
         configureArchiveDefaults(project, convention);
         configureConfigurations(project);
 
@@ -60,7 +78,7 @@ public class BasePlugin implements Plugin<Project> {
     }
 
     private void addAssemble(Project project) {
-        Task assembleTask = project.getTasks().add(ASSEMBLE_TASK_NAME);
+        Task assembleTask = project.getTasks().create(ASSEMBLE_TASK_NAME);
         assembleTask.setDescription("Assembles the outputs of this project.");
         assembleTask.setGroup(BUILD_GROUP);
         assembleTask.dependsOn(project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION).getAllArtifacts().getBuildDependencies());
@@ -103,7 +121,7 @@ public class BasePlugin implements Plugin<Project> {
     }
 
     private void addClean(final Project project) {
-        Delete clean = project.getTasks().add(CLEAN_TASK_NAME, Delete.class);
+        Delete clean = project.getTasks().create(CLEAN_TASK_NAME, Delete.class);
         clean.setDescription("Deletes the build directory.");
         clean.setGroup(BUILD_GROUP);
         clean.delete(new Callable<File>() {
@@ -125,14 +143,32 @@ public class BasePlugin implements Plugin<Project> {
         project.getTasks().addRule(new UploadRule(project));
     }
 
+    private void configureUploadArchivesTask() {
+        configurationActionContainer.add(new Action<Project>() {
+            public void execute(Project project) {
+                Upload uploadArchives = project.getTasks().withType(Upload.class).findByName(UPLOAD_ARCHIVES_TASK_NAME);
+                if (uploadArchives == null) { return; }
+
+                boolean hasIvyRepo = !uploadArchives.getRepositories().withType(IvyArtifactRepository.class).isEmpty();
+                if (!hasIvyRepo) { return; } // Maven repos are handled by MavenPlugin
+
+                ConfigurationInternal configuration = (ConfigurationInternal) uploadArchives.getConfiguration();
+                ModuleInternal module = configuration.getModule();
+                ModuleVersionIdentifier publicationId =
+                        new DefaultModuleVersionIdentifier(module.getGroup(), module.getName(), module.getVersion());
+                publicationRegistry.registerPublication(module.getProjectPath(), new DefaultProjectPublication(publicationId));
+            }
+        });
+    }
+
     private void configureConfigurations(final Project project) {
         ConfigurationContainer configurations = project.getConfigurations();
         project.setProperty("status", "integration");
 
-        Configuration archivesConfiguration = configurations.add(Dependency.ARCHIVES_CONFIGURATION).
+        Configuration archivesConfiguration = configurations.create(Dependency.ARCHIVES_CONFIGURATION).
                 setDescription("Configuration for archive artifacts.");
 
-        configurations.add(Dependency.DEFAULT_CONFIGURATION).
+        configurations.create(Dependency.DEFAULT_CONFIGURATION).
                 setDescription("Configuration for default artifacts.");
 
         final DefaultArtifactPublicationSet defaultArtifacts = project.getExtensions().create(
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy
index 2c273b6..7323e7d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy
@@ -16,6 +16,7 @@
 package org.gradle.api.plugins
 
 import org.gradle.api.Project
+import org.gradle.api.internal.file.FileLookup
 import org.gradle.api.internal.project.ProjectInternal
 
 public class BasePluginConvention {
@@ -49,7 +50,7 @@ public class BasePluginConvention {
      * @return The directory. Never returns null.
      */
     File getDistsDir() {
-        project.fileResolver.withBaseDir(project.buildDir).resolve(distsDirName)
+        project.services.get(FileLookup).getFileResolver(project.buildDir).resolve(distsDirName)
     }
 
     /**
@@ -58,6 +59,6 @@ public class BasePluginConvention {
      * @return The directory. Never returns null.
      */
     File getLibsDir() {
-        project.fileResolver.withBaseDir(project.buildDir).resolve(libsDirName)
+        project.services.get(FileLookup).getFileResolver(project.buildDir).resolve(libsDirName)
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
index 7b85715..92cc706 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
@@ -16,42 +16,49 @@
 
 package org.gradle.api.plugins;
 
-import com.google.common.collect.Lists;
 import org.gradle.api.Action;
-import org.gradle.api.Nullable;
 import org.gradle.api.Plugin;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DslObject;
-import org.gradle.api.internal.plugins.GroovyJarFile;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.DefaultGroovySourceSet;
 import org.gradle.api.internal.tasks.DefaultSourceSet;
 import org.gradle.api.reporting.ReportingExtension;
 import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.GroovyRuntime;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.compile.GroovyCompile;
 import org.gradle.api.tasks.javadoc.Groovydoc;
+import org.gradle.util.DeprecationLogger;
 
 import javax.inject.Inject;
 import java.io.File;
-import java.util.List;
 import java.util.concurrent.Callable;
 
 /**
  * Extends {@link org.gradle.api.plugins.JavaBasePlugin} to provide support for compiling and documenting Groovy
  * source files.
- *
- * @author Hans Dockter
  */
 public class GroovyBasePlugin implements Plugin<ProjectInternal> {
+    /**
+     * The name of the configuration holding the Groovy compiler and tools.
+     *
+     * @deprecated Typically, usages of {@code groovy} can simply be replaced with {@code compile}.
+     * In some cases, it may be necessary to additionally configure the {@code groovyClasspath} property
+     * of {@code GroovyCompile} and {@code Groovydoc} tasks.
+     */
+    @Deprecated
     public static final String GROOVY_CONFIGURATION_NAME = "groovy";
 
+    public static final String GROOVY_RUNTIME_EXTENSION_NAME = "groovyRuntime";
+
     private final FileResolver fileResolver;
+
     private ProjectInternal project;
+    private GroovyRuntime groovyRuntime;
 
     @Inject
     public GroovyBasePlugin(FileResolver fileResolver) {
@@ -62,20 +69,40 @@ public class GroovyBasePlugin implements Plugin<ProjectInternal> {
         this.project = project;
         JavaBasePlugin javaBasePlugin = project.getPlugins().apply(JavaBasePlugin.class);
 
-        project.getConfigurations().add(GROOVY_CONFIGURATION_NAME).setVisible(false).
-                setDescription("The Groovy libraries to be used for this Groovy project.");
-
+        configureConfigurations(project);
+        configureGroovyRuntimeExtension();
         configureCompileDefaults();
         configureSourceSetDefaults(javaBasePlugin);
 
         configureGroovydoc();
     }
 
+    private void configureConfigurations(ProjectInternal project) {
+        Configuration groovyConfiguration = project.getConfigurations().create(GROOVY_CONFIGURATION_NAME).setVisible(false).
+                setDescription("The Groovy libraries to be used for this Groovy project. (Deprecated)");
+        deprecateGroovyConfiguration(groovyConfiguration);
+    }
+
+    private void configureGroovyRuntimeExtension() {
+        groovyRuntime = project.getExtensions().create(GROOVY_RUNTIME_EXTENSION_NAME, GroovyRuntime.class, project);
+    }
+
+    private void deprecateGroovyConfiguration(Configuration groovyConfiguration) {
+        groovyConfiguration.getDependencies().whenObjectAdded(new Action<Dependency>() {
+            public void execute(Dependency dependency) {
+                DeprecationLogger.nagUserOfDiscontinuedConfiguration(GROOVY_CONFIGURATION_NAME, "Typically, usages of 'groovy' "
+                        + "can simply be replaced with 'compile'. In some cases, it may be necessary to additionally configure "
+                        + "the 'groovyClasspath' property of GroovyCompile and Groovydoc tasks.");
+            }
+        });
+    }
+
     private void configureCompileDefaults() {
         project.getTasks().withType(GroovyCompile.class, new Action<GroovyCompile>() {
-            public void execute(final GroovyCompile compile) {                                                                                                                                                                                                                                            compile.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
+            public void execute(final GroovyCompile compile) {
+                compile.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
                     public Object call() throws Exception {
-                        return getGroovyClasspath(compile.getClasspath());
+                        return groovyRuntime.inferGroovyClasspath(compile.getClasspath());
                     }
                 });
             }
@@ -98,7 +125,7 @@ public class GroovyBasePlugin implements Plugin<ProjectInternal> {
                 sourceSet.getAllSource().source(groovySourceSet.getGroovy());
 
                 String compileTaskName = sourceSet.getCompileTaskName("groovy");
-                GroovyCompile compile = project.getTasks().add(compileTaskName, GroovyCompile.class);
+                GroovyCompile compile = project.getTasks().create(compileTaskName, GroovyCompile.class);
                 javaBasePlugin.configureForSourceSet(sourceSet, compile);
                 compile.dependsOn(sourceSet.getCompileJavaTaskName());
                 compile.setDescription(String.format("Compiles the %s Groovy source.", sourceSet.getName()));
@@ -114,7 +141,7 @@ public class GroovyBasePlugin implements Plugin<ProjectInternal> {
             public void execute(final Groovydoc groovydoc) {
                 groovydoc.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
                     public Object call() throws Exception {
-                        return getGroovyClasspath(groovydoc.getClasspath());
+                        return groovyRuntime.inferGroovyClasspath(groovydoc.getClasspath());
                     }
                 });
                 groovydoc.getConventionMapping().map("destinationDir", new Callable<Object>() {
@@ -136,43 +163,7 @@ public class GroovyBasePlugin implements Plugin<ProjectInternal> {
         });
     }
 
-    private FileCollection getGroovyClasspath(FileCollection classpath) {
-        Configuration groovyConfiguration = project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME);
-        if (!groovyConfiguration.getDependencies().isEmpty()) { return groovyConfiguration; }
-
-        GroovyJarFile groovyJar = findGroovyJarFile(classpath);
-        if (groovyJar == null) { return groovyConfiguration; }
-
-        if (groovyJar.isGroovyAll()) {
-            return project.files(groovyJar.getFile());
-        }
-
-        if (project.getRepositories().isEmpty()) {
-            return groovyConfiguration;
-        }
-
-        String notation = groovyJar.getDependencyNotation();
-        List<Dependency> dependencies = Lists.newArrayList();
-        // project.getDependencies().create(String) seems to be the only feasible way to create a Dependency with a classifier
-        dependencies.add(project.getDependencies().create(notation));
-        if (groovyJar.getVersion().getMajor() >= 2) {
-            // add groovy-ant to bring in AntGroovyCompiler
-            dependencies.add(project.getDependencies().create(notation.replace(":groovy:", ":groovy-ant:")));
-        }
-        return project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[dependencies.size()]));
-    }
-
     private JavaPluginConvention java(Convention convention) {
         return convention.getPlugin(JavaPluginConvention.class);
     }
-
-    @Nullable
-    private GroovyJarFile findGroovyJarFile(Iterable<File> classpath) {
-        if (classpath == null) { return null; }
-        for (File file : classpath) {
-            GroovyJarFile groovyJar = GroovyJarFile.parse(file);
-            if (groovyJar != null) { return groovyJar; }
-        }
-        return null;
-    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
index a2fbe8f..8e90970 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
@@ -26,8 +26,6 @@ import org.gradle.api.tasks.javadoc.Groovydoc;
 /**
  * <p>A {@link Plugin} which extends the {@link JavaPlugin} to provide support for compiling and documenting Groovy
  * source files.</p>
- *
- * @author Hans Dockter
  */
 public class GroovyPlugin implements Plugin<Project> {
     public static final String GROOVYDOC_TASK_NAME = "groovydoc";
@@ -46,7 +44,7 @@ public class GroovyPlugin implements Plugin<Project> {
     }
 
     private void configureGroovydoc(final Project project) {
-        Groovydoc groovyDoc = project.getTasks().add(GROOVYDOC_TASK_NAME, Groovydoc.class);
+        Groovydoc groovyDoc = project.getTasks().create(GROOVYDOC_TASK_NAME, Groovydoc.class);
         groovyDoc.setDescription("Generates Groovydoc API documentation for the main source code.");
         groovyDoc.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
 
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 3a8b519..4dc2d17 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
@@ -23,19 +23,23 @@ import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.IConventionAware;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.DefaultJavaSourceSet;
-import org.gradle.api.internal.tasks.DefaultResourceSet;
 import org.gradle.api.internal.tasks.SourceSetCompileClasspath;
+import org.gradle.api.internal.tasks.testing.NoMatchingTestsReporter;
 import org.gradle.api.reporting.ReportingExtension;
-import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.compile.AbstractCompile;
 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.api.tasks.testing.*;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.language.java.internal.DefaultJavaSourceSet;
+import org.gradle.language.jvm.ClassDirectoryBinary;
+import org.gradle.language.jvm.Classpath;
+import org.gradle.language.jvm.ResourceSet;
+import org.gradle.language.jvm.internal.DefaultResourceSet;
 import org.gradle.util.WrapUtil;
 
 import javax.inject.Inject;
@@ -44,8 +48,6 @@ import java.util.concurrent.Callable;
 
 /**
  * <p>A {@link org.gradle.api.Plugin} which compiles and tests Java source, and assembles it into a JAR file.</p>
- *
- * @author Hans Dockter
  */
 public class JavaBasePlugin implements Plugin<Project> {
     public static final String CHECK_TASK_NAME = "check";
@@ -93,18 +95,18 @@ public class JavaBasePlugin implements Plugin<Project> {
 
                 Configuration compileConfiguration = configurations.findByName(sourceSet.getCompileConfigurationName());
                 if (compileConfiguration == null) {
-                    compileConfiguration = configurations.add(sourceSet.getCompileConfigurationName());
+                    compileConfiguration = configurations.create(sourceSet.getCompileConfigurationName());
                 }
                 compileConfiguration.setVisible(false);
-                compileConfiguration.setDescription(String.format("Classpath for compiling the %s sources.", sourceSet.getName()));
+                compileConfiguration.setDescription(String.format("Compile classpath for %s.", sourceSet));
 
                 Configuration runtimeConfiguration = configurations.findByName(sourceSet.getRuntimeConfigurationName());
                 if (runtimeConfiguration == null) {
-                    runtimeConfiguration = configurations.add(sourceSet.getRuntimeConfigurationName());
+                    runtimeConfiguration = configurations.create(sourceSet.getRuntimeConfigurationName());
                 }
                 runtimeConfiguration.setVisible(false);
                 runtimeConfiguration.extendsFrom(compileConfiguration);
-                runtimeConfiguration.setDescription(String.format("Classpath for running the compiled %s classes.", sourceSet.getName()));
+                runtimeConfiguration.setDescription(String.format("Runtime classpath for %s.", sourceSet));
 
                 sourceSet.setCompileClasspath(compileConfiguration);
                 sourceSet.setRuntimeClasspath(sourceSet.getOutput().plus(runtimeConfiguration));
@@ -133,8 +135,8 @@ public class JavaBasePlugin implements Plugin<Project> {
                 ResourceSet resourceSet = instantiator.newInstance(DefaultResourceSet.class, "resources", sourceSet.getResources(), functionalSourceSet);
                 functionalSourceSet.add(resourceSet);
 
-                JvmBinaryContainer jvmBinaryContainer = project.getPlugins().getPlugin(JvmLanguagePlugin.class).getJvmBinaryContainer();
-                ClassDirectoryBinary binary = jvmBinaryContainer.create(sourceSet.getName(), ClassDirectoryBinary.class);
+                BinaryContainer binaryContainer = project.getExtensions().getByType(BinaryContainer.class);
+                ClassDirectoryBinary binary = binaryContainer.create(String.format("%sClasses", sourceSet.getName()), ClassDirectoryBinary.class);
                 ConventionMapping conventionMapping = new DslObject(binary).getConventionMapping();
                 conventionMapping.map("classesDir", new Callable<File>() {
                     public File call() throws Exception {
@@ -150,8 +152,7 @@ public class JavaBasePlugin implements Plugin<Project> {
                 binary.getSource().add(javaSourceSet);
                 binary.getSource().add(resourceSet);
 
-                // TODO:DAZ review this
-                binary.getClassesTask().dependsOn(sourceSet.getOutput().getDirs());
+                binary.builtBy(sourceSet.getOutput().getDirs());
             }
         });
     }
@@ -219,13 +220,13 @@ public class JavaBasePlugin implements Plugin<Project> {
     }
 
     private void configureCheck(final Project project) {
-        Task checkTask = project.getTasks().add(CHECK_TASK_NAME);
+        Task checkTask = project.getTasks().create(CHECK_TASK_NAME);
         checkTask.setDescription("Runs all checks.");
         checkTask.setGroup(VERIFICATION_GROUP);
     }
 
     private void configureBuild(Project project) {
-        DefaultTask buildTask = project.getTasks().add(BUILD_TASK_NAME, DefaultTask.class);
+        DefaultTask buildTask = project.getTasks().create(BUILD_TASK_NAME, DefaultTask.class);
         buildTask.setDescription("Assembles and tests this project.");
         buildTask.setGroup(BasePlugin.BUILD_GROUP);
         buildTask.dependsOn(BasePlugin.ASSEMBLE_TASK_NAME);
@@ -233,14 +234,14 @@ public class JavaBasePlugin implements Plugin<Project> {
     }
 
     private void configureBuildNeeded(Project project) {
-        DefaultTask buildTask = project.getTasks().add(BUILD_NEEDED_TASK_NAME, DefaultTask.class);
+        DefaultTask buildTask = project.getTasks().create(BUILD_NEEDED_TASK_NAME, DefaultTask.class);
         buildTask.setDescription("Assembles and tests this project and all projects it depends on.");
         buildTask.setGroup(BasePlugin.BUILD_GROUP);
         buildTask.dependsOn(BUILD_TASK_NAME);
     }
 
     private void configureBuildDependents(Project project) {
-        DefaultTask buildTask = project.getTasks().add(BUILD_DEPENDENTS_TASK_NAME, DefaultTask.class);
+        DefaultTask buildTask = project.getTasks().create(BUILD_DEPENDENTS_TASK_NAME, DefaultTask.class);
         buildTask.setDescription("Assembles and tests this project and all projects that depend on it.");
         buildTask.setGroup(BasePlugin.BUILD_GROUP);
         buildTask.dependsOn(BUILD_TASK_NAME);
@@ -256,7 +257,7 @@ public class JavaBasePlugin implements Plugin<Project> {
             public void execute(Project project) {
                 project.getTasks().withType(Test.class, new Action<Test>() {
                     public void execute(Test test) {
-                        overwriteIncludesIfSinglePropertyIsSet(test);
+                        configureBasedOnSingleProperty(test);
                         overwriteDebugIfDebugPropertyIsSet(test);
                     }
                 });
@@ -276,9 +277,13 @@ public class JavaBasePlugin implements Plugin<Project> {
         }
     }
 
-    private void overwriteIncludesIfSinglePropertyIsSet(final Test test) {
+    private void configureBasedOnSingleProperty(final Test test) {
         String singleTest = getTaskPrefixedProperty(test, "single");
         if (singleTest == null) {
+            //configure inputs so that the test task is skipped when there are no source files.
+            //unfortunately, this only applies when 'test.single' is *not* applied
+            //We should fix this distinction, the behavior with 'test.single' or without it should be the same
+            test.getInputs().source(test.getCandidateClassFiles());
             return;
         }
         test.doFirst(new Action<Task>() {
@@ -287,29 +292,7 @@ public class JavaBasePlugin implements Plugin<Project> {
             }
         });
         test.setIncludes(WrapUtil.toSet(String.format("**/%s*.class", singleTest)));
-        failIfNoTestIsExecuted(test, singleTest);
-    }
-
-    private void failIfNoTestIsExecuted(Test test, final String pattern) {
-        test.addTestListener(new TestListener() {
-            public void beforeSuite(TestDescriptor suite) {
-                // do nothing
-            }
-
-            public void afterSuite(TestDescriptor suite, TestResult result) {
-                if (suite.getParent() == null && result.getTestCount() == 0) {
-                    throw new GradleException("Could not find matching test for pattern: " + pattern);
-                }
-            }
-
-            public void beforeTest(TestDescriptor testDescriptor) {
-                // do nothing
-            }
-
-            public void afterTest(TestDescriptor testDescriptor, TestResult result) {
-                // do nothing
-            }
-        });
+        test.addTestListener(new NoMatchingTestsReporter("Could not find matching test for pattern: " + singleTest));
     }
 
     private String getTaskPrefixedProperty(Task task, String propertyName) {
@@ -322,12 +305,15 @@ public class JavaBasePlugin implements Plugin<Project> {
     }
 
     private void configureTestDefaults(final Test test, Project project, final JavaPluginConvention convention) {
-        test.getConventionMapping().map("testResultsDir", new Callable<Object>() {
+        DslObject htmlReport = new DslObject(test.getReports().getHtml());
+        DslObject xmlReport = new DslObject(test.getReports().getJunitXml());
+
+        xmlReport.getConventionMapping().map("destination", new Callable<Object>() {
             public Object call() throws Exception {
                 return convention.getTestResultsDir();
             }
         });
-        test.getConventionMapping().map("testReportDir", new Callable<Object>() {
+        htmlReport.getConventionMapping().map("destination", new Callable<Object>() {
             public Object call() throws Exception {
                 return convention.getTestReportDir();
             }
@@ -339,4 +325,5 @@ public class JavaBasePlugin implements Plugin<Project> {
         });
         test.workingDir(project.getProjectDir());
     }
+
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java
index 8717fb7..00d5da7 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java
@@ -16,45 +16,56 @@
 package org.gradle.api.plugins;
 
 import org.gradle.api.*;
+import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.file.DefaultSourceDirectorySet;
-import org.gradle.api.internal.tasks.DefaultClasspath;
-import org.gradle.api.internal.tasks.DefaultJavaSourceSet;
-import org.gradle.api.internal.tasks.DefaultProjectSourceSet;
-import org.gradle.api.tasks.*;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.language.base.BinaryContainer;
+import org.gradle.language.base.internal.BinaryInternal;
+import org.gradle.language.base.internal.BinaryNamingScheme;
+import org.gradle.language.java.internal.DefaultJavaSourceSet;
+import org.gradle.language.java.JavaSourceSet;
+import org.gradle.api.tasks.compile.AbstractCompile;
 import org.gradle.api.tasks.compile.JavaCompile;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.language.jvm.internal.DefaultClasspath;
+import org.gradle.language.base.internal.DefaultProjectSourceSet;
+import org.gradle.language.jvm.ClassDirectoryBinary;
+import org.gradle.language.jvm.plugins.JvmLanguagePlugin;
 
 import javax.inject.Inject;
+import java.util.concurrent.Callable;
 
 /**
- * Plugin for compiling Java code. Applies the {@link JvmLanguagePlugin}.
- * Adds a {@link JavaCompile} task for each {@link JavaSourceSet} added to a {@link ClassDirectoryBinary}.
- * Registers the {@link JavaSourceSet} element type for each {@link FunctionalSourceSet} added to {@link ProjectSourceSet}.
+ * Plugin for compiling Java code. Applies the {@link org.gradle.language.jvm.plugins.JvmLanguagePlugin}.
+ * Adds a {@link JavaCompile} task for each {@link JavaSourceSet} added to a {@link org.gradle.language.jvm.ClassDirectoryBinary}.
+ * Registers the {@link JavaSourceSet} element type for each {@link org.gradle.language.base.FunctionalSourceSet} added to {@link org.gradle.language.base.ProjectSourceSet}.
  */
 @Incubating
 public class JavaLanguagePlugin implements Plugin<Project> {
     private final Instantiator instantiator;
+    private final FileResolver fileResolver;
 
     @Inject
-    public JavaLanguagePlugin(Instantiator instantiator) {
+    public JavaLanguagePlugin(Instantiator instantiator, FileResolver fileResolver) {
         this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
     }
 
     public void apply(final Project target) {
-        final JvmLanguagePlugin jvmLanguagePlugin = target.getPlugins().apply(JvmLanguagePlugin.class);
+        target.getPlugins().apply(JvmLanguagePlugin.class);
 
-        JvmBinaryContainer jvmBinaryContainer = jvmLanguagePlugin.getJvmBinaryContainer();
-        jvmBinaryContainer.all(new Action<ClassDirectoryBinary>() {
+        BinaryContainer jvmBinaryContainer = target.getExtensions().getByType(BinaryContainer.class);
+        jvmBinaryContainer.withType(ClassDirectoryBinary.class).all(new Action<ClassDirectoryBinary>() {
             public void execute(final ClassDirectoryBinary binary) {
+                final BinaryNamingScheme namingScheme = ((BinaryInternal) binary).getNamingScheme();
                 binary.getSource().withType(JavaSourceSet.class).all(new Action<JavaSourceSet>() {
                     public void execute(JavaSourceSet javaSourceSet) {
-                        JavaCompile compileTask = (JavaCompile) binary.getCompileTask(); // TODO: can't simply cast
-                        if (compileTask == null) {
-                            compileTask = target.getTasks().add(binary.getTaskName("compile", "java"), JavaCompile.class);
-                            jvmLanguagePlugin.configureCompileTask(compileTask, javaSourceSet, binary);
-                            binary.setCompileTask(compileTask);
-                            binary.getClassesTask().dependsOn(compileTask);
-                        }
+                        // TODO: handle case where binary has multiple JavaSourceSet's
+                        JavaCompile compileTask = target.getTasks().create(namingScheme.getTaskName("compile", "java"), JavaCompile.class);
+                        configureCompileTask(compileTask, javaSourceSet, binary);
+                        binary.builtBy(compileTask);
                     }
                 });
             }
@@ -66,11 +77,37 @@ public class JavaLanguagePlugin implements Plugin<Project> {
                 functionalSourceSet.registerFactory(JavaSourceSet.class, new NamedDomainObjectFactory<JavaSourceSet>() {
                     public JavaSourceSet create(String name) {
                         return instantiator.newInstance(DefaultJavaSourceSet.class, name,
-                                instantiator.newInstance(DefaultSourceDirectorySet.class),
-                                instantiator.newInstance(DefaultClasspath.class), functionalSourceSet);
+                                instantiator.newInstance(DefaultSourceDirectorySet.class, name, fileResolver),
+                                instantiator.newInstance(DefaultClasspath.class, fileResolver,
+                                        target.getTasks()), functionalSourceSet);
                     }
                 });
             }
         });
     }
+
+
+    /**
+     * Preconfigures the specified compile task based on the specified source set and class directory binary.
+     *
+     * @param compile the compile task to be preconfigured
+     * @param sourceSet the source set for the compile task
+     * @param binary the binary for the compile task
+     */
+    public void configureCompileTask(AbstractCompile compile, final JavaSourceSet sourceSet, final ClassDirectoryBinary binary) {
+        compile.setDescription(String.format("Compiles %s.", sourceSet));
+        compile.setSource(sourceSet.getSource());
+        compile.dependsOn(sourceSet);
+        ConventionMapping conventionMapping = compile.getConventionMapping();
+        conventionMapping.map("classpath", new Callable<Object>() {
+            public Object call() throws Exception {
+                return sourceSet.getCompileClasspath().getFiles();
+            }
+        });
+        conventionMapping.map("destinationDir", new Callable<Object>() {
+            public Object call() throws Exception {
+                return binary.getClassesDir();
+            }
+        });
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
index 090b856..9f20878 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
@@ -23,8 +23,6 @@ import org.gradle.api.distribution.plugins.DistributionPlugin
 
 /**
  * A {@link Plugin} which package a Java project as a distribution including the JAR and runtime dependencies.
- *
- * @author scogneau
  */
 @Incubating
 class JavaLibraryDistributionPlugin implements Plugin<Project> {
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 e306224..8b009da 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
@@ -42,8 +42,6 @@ import java.util.concurrent.Callable;
 
 /**
  * <p>A {@link Plugin} which compiles and tests Java source, and assembles it into a JAR file.</p>
- *
- * @author Hans Dockter
  */
 public class JavaPlugin implements Plugin<Project> {
     public static final String PROCESS_RESOURCES_TASK_NAME = "processResources";
@@ -79,9 +77,9 @@ public class JavaPlugin implements Plugin<Project> {
     private void configureSourceSets(final JavaPluginConvention pluginConvention) {
         final Project project = pluginConvention.getProject();
 
-        SourceSet main = pluginConvention.getSourceSets().add(SourceSet.MAIN_SOURCE_SET_NAME);
+        SourceSet main = pluginConvention.getSourceSets().create(SourceSet.MAIN_SOURCE_SET_NAME);
 
-        SourceSet test = pluginConvention.getSourceSets().add(SourceSet.TEST_SOURCE_SET_NAME);
+        SourceSet test = pluginConvention.getSourceSets().create(SourceSet.TEST_SOURCE_SET_NAME);
         test.setCompileClasspath(project.files(main.getOutput(), project.getConfigurations().getByName(TEST_COMPILE_CONFIGURATION_NAME)));
         test.setRuntimeClasspath(project.files(test.getOutput(), main.getOutput(), project.getConfigurations().getByName(TEST_RUNTIME_CONFIGURATION_NAME)));
     }
@@ -90,7 +88,7 @@ public class JavaPlugin implements Plugin<Project> {
         Project project = pluginConvention.getProject();
 
         SourceSet mainSourceSet = pluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
-        Javadoc javadoc = project.getTasks().add(JAVADOC_TASK_NAME, Javadoc.class);
+        Javadoc javadoc = project.getTasks().create(JAVADOC_TASK_NAME, Javadoc.class);
         javadoc.setDescription("Generates Javadoc API documentation for the main source code.");
         javadoc.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
         javadoc.setClasspath(mainSourceSet.getOutput().plus(mainSourceSet.getCompileClasspath()));
@@ -99,7 +97,7 @@ public class JavaPlugin implements Plugin<Project> {
     }
 
     private void configureArchivesAndComponent(final Project project, final JavaPluginConvention pluginConvention) {
-        Jar jar = project.getTasks().add(JAR_TASK_NAME, Jar.class);
+        Jar jar = project.getTasks().create(JAR_TASK_NAME, Jar.class);
         jar.getManifest().from(pluginConvention.getManifest());
         jar.setDescription("Assembles a jar archive containing the main classes.");
         jar.setGroup(BasePlugin.BUILD_GROUP);
@@ -146,7 +144,7 @@ public class JavaPlugin implements Plugin<Project> {
                 });
             }
         });
-        Test test = project.getTasks().add(TEST_TASK_NAME, Test.class);
+        Test test = project.getTasks().create(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 d8bfbb7..fb8abb0 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
@@ -17,6 +17,7 @@ package org.gradle.api.plugins
 
 import org.gradle.api.JavaVersion
 import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.file.FileLookup
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.tasks.DefaultSourceSetContainer
 import org.gradle.api.java.archives.Manifest
@@ -30,8 +31,6 @@ import org.gradle.util.ConfigureUtil
 /**
  * Is mixed in into the project when applying the {@link org.gradle.api.plugins.JavaBasePlugin} or the
  * {@link org.gradle.api.plugins.JavaPlugin}.
- *
- * @author Hans Dockter
  */
 class JavaPluginConvention {
     ProjectInternal project
@@ -112,28 +111,28 @@ class JavaPluginConvention {
     }
 
     File getDependencyCacheDir() {
-        project.fileResolver.withBaseDir(project.buildDir).resolve(dependencyCacheDirName)
+        project.services.get(FileLookup).getFileResolver(project.buildDir).resolve(dependencyCacheDirName)
     }
 
     /**
      * Returns a file pointing to the root directory supposed to be used for all docs.
      */
     File getDocsDir() {
-        project.fileResolver.withBaseDir(project.buildDir).resolve(docsDirName)
+        project.services.get(FileLookup).getFileResolver(project.buildDir).resolve(docsDirName)
     }
 
     /**
      * Returns a file pointing to the root directory of the test results.
      */
     File getTestResultsDir() {
-        project.fileResolver.withBaseDir(project.buildDir).resolve(testResultsDirName)
+        project.services.get(FileLookup).getFileResolver(project.buildDir).resolve(testResultsDirName)
     }
 
     /**
      * Returns a file pointing to the root directory to be used for reports.
      */
     File getTestReportDir() {
-        project.fileResolver.withBaseDir(reportsDir).resolve(testReportDirName)
+        project.services.get(FileLookup).getFileResolver(reportsDir).resolve(testReportDirName)
     }
 
     private File getReportsDir() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JvmLanguagePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JvmLanguagePlugin.java
deleted file mode 100644
index 6618927..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JvmLanguagePlugin.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.*;
-import org.gradle.api.internal.ConventionMapping;
-import org.gradle.api.internal.plugins.DslObject;
-import org.gradle.api.internal.plugins.ProcessResources;
-import org.gradle.api.internal.tasks.DefaultBinariesContainer;
-import org.gradle.api.internal.tasks.DefaultClassDirectoryBinary;
-import org.gradle.api.internal.tasks.DefaultJvmBinaryContainer;
-import org.gradle.api.tasks.*;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.internal.reflect.Instantiator;
-
-import javax.inject.Inject;
-import java.io.File;
-import java.util.concurrent.Callable;
-
-/**
- * Base plugin for JVM language support. Applies the {@link LanguageBasePlugin}.
- * Adds a {@link JvmBinaryContainer} named {@code jvm} to the project's {@link BinariesContainer}.
- * Registers the {@link ClassDirectoryBinary} element type for that container.
- * Adds a lifecycle task named {@code classes} for each {@link ClassDirectoryBinary}.
- * Adds a {@link Copy} task named {@code processXYZResources} for each {@link ResourceSet} added to a {@link ClassDirectoryBinary}.
- */
- at Incubating
-public class JvmLanguagePlugin implements Plugin<Project> {
-    private final Instantiator instantiator;
-
-    private JvmBinaryContainer jvmBinaryContainer;
-
-    @Inject
-    public JvmLanguagePlugin(Instantiator instantiator) {
-        this.instantiator = instantiator;
-    }
-
-    public void apply(final Project target) {
-        target.getPlugins().apply(LanguageBasePlugin.class);
-
-        BinariesContainer binariesContainer = target.getExtensions().getByType(DefaultBinariesContainer.class);
-        jvmBinaryContainer = instantiator.newInstance(DefaultJvmBinaryContainer.class, instantiator);
-        binariesContainer.add(jvmBinaryContainer);
-
-        jvmBinaryContainer.registerFactory(ClassDirectoryBinary.class, new NamedDomainObjectFactory<ClassDirectoryBinary>() {
-            public ClassDirectoryBinary create(String name) {
-                return instantiator.newInstance(DefaultClassDirectoryBinary.class, name);
-            };
-        });
-
-        jvmBinaryContainer.all(new Action<ClassDirectoryBinary>() {
-            public void execute(final ClassDirectoryBinary binary) {
-                ConventionMapping conventionMapping = new DslObject(binary).getConventionMapping();
-                conventionMapping.map("classesDir", new Callable<File>() {
-                    public File call() throws Exception {
-                        return new File(new File(target.getBuildDir(), "classes"), binary.getName());
-                    }
-                });
-                final Task classesTask = target.getTasks().add(binary.getTaskName(null, "classes"));
-                classesTask.setDescription(String.format("Assembles the %s classes.", binary.getName()));
-                binary.setClassesTask(classesTask);
-                binary.getSource().withType(ResourceSet.class).all(new Action<ResourceSet>() {
-                    public void execute(ResourceSet resourceSet) {
-                        Copy resourcesTask = binary.getResourcesTask();
-                        if (resourcesTask == null) {
-                            resourcesTask = target.getTasks().add(binary.getTaskName("process", "resources"), ProcessResources.class);
-                            resourcesTask.setDescription(String.format("Processes the %s resources.", binary.getName()));
-                            new DslObject(resourcesTask).getConventionMapping().map("destinationDir", new Callable<File>() {
-                                public File call() throws Exception {
-                                    return binary.getResourcesDir();
-                                }
-                            });
-                            binary.setResourcesTask(resourcesTask);
-                            classesTask.dependsOn(resourcesTask);
-                        }
-                        resourcesTask.from(resourceSet.getSource());
-                    }
-                });
-            }
-        });
-    }
-
-    /**
-     * Returns the {@code binaries.jvm} container that was added by this plugin to the project.
-     *
-     * @return the {@code binaries.jvm} container that was added by this plugin to the project
-     */
-    public JvmBinaryContainer getJvmBinaryContainer() {
-        return jvmBinaryContainer;
-    }
-
-    /**
-     * Preconfigures the specified compile task based on the specified source set and class directory binary.
-     *
-     * @param compile the compile task to be preconfigured
-     * @param sourceSet the source set for the compile task
-     * @param binary the binary for the compile task
-     */
-    public void configureCompileTask(AbstractCompile compile, final JvmLanguageSourceSet sourceSet, final ClassDirectoryBinary binary) {
-        compile.setDescription(String.format("Compiles the %s.", sourceSet));
-        compile.setSource(sourceSet.getSource());
-        compile.dependsOn(sourceSet);
-        ConventionMapping conventionMapping = compile.getConventionMapping();
-        conventionMapping.map("classpath", new Callable<Object>() {
-            public Object call() throws Exception {
-                return sourceSet.getCompileClasspath().getFiles();
-            }
-        });
-        conventionMapping.map("destinationDir", new Callable<Object>() {
-            public Object call() throws Exception {
-                return binary.getClassesDir();
-            }
-        });
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/LanguageBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/LanguageBasePlugin.java
deleted file mode 100644
index a62a71a..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/LanguageBasePlugin.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.*;
-import org.gradle.api.internal.file.DefaultSourceDirectorySet;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.tasks.*;
-import org.gradle.api.tasks.*;
-import org.gradle.internal.reflect.Instantiator;
-
-import javax.inject.Inject;
-
-/**
- * Base plugin for language support.
- * Adds a {@link BinariesContainer} named {@code binaries} to the project.
- * Adds a {@link ProjectSourceSet} named {@code sources} to the project.
- * Registers the {@link ResourceSet} element type for each {@link FunctionalSourceSet} added to {@link ProjectSourceSet}.
- */
- at Incubating
-public class LanguageBasePlugin implements Plugin<Project> {
-    private final Instantiator instantiator;
-    private final FileResolver fileResolver;
-
-    @Inject
-    public LanguageBasePlugin(Instantiator instantiator, FileResolver fileResolver) {
-        this.instantiator = instantiator;
-        this.fileResolver = fileResolver;
-    }
-
-    public void apply(Project target) {
-        target.getExtensions().create("binaries", DefaultBinariesContainer.class, instantiator);
-        ProjectSourceSet projectSourceSet = target.getExtensions().create("sources", DefaultProjectSourceSet.class, instantiator);
-
-        // TODO: move to JvmLanguagePlugin?
-        projectSourceSet.all(new Action<FunctionalSourceSet>() {
-            public void execute(final FunctionalSourceSet functionalSourceSet) {
-                functionalSourceSet.registerFactory(ResourceSet.class, new NamedDomainObjectFactory<ResourceSet>() {
-                    public ResourceSet create(String name) {
-                        return instantiator.newInstance(DefaultResourceSet.class, name,
-                                instantiator.newInstance(DefaultSourceDirectorySet.class, name, fileResolver), functionalSourceSet);
-                    }
-                });
-            }
-        });
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
index 040f7b0..d14541a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
@@ -24,8 +24,8 @@ import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
-import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.internal.java.WebApplication;
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.bundling.War;
 
@@ -34,8 +34,6 @@ import java.util.concurrent.Callable;
 /**
  * <p>A {@link Plugin} which extends the {@link JavaPlugin} to add tasks which assemble a web application into a WAR
  * file.</p>
- *
- * @author Hans Dockter
  */
 public class WarPlugin implements Plugin<Project> {
     public static final String PROVIDED_COMPILE_CONFIGURATION_NAME = "providedCompile";
@@ -73,7 +71,7 @@ public class WarPlugin implements Plugin<Project> {
             }
         });
         
-        War war = project.getTasks().add(WAR_TASK_NAME, War.class);
+        War war = project.getTasks().create(WAR_TASK_NAME, War.class);
         war.setDescription("Generates a war archive with all the compiled classes, the web-app content and the libraries.");
         war.setGroup(BasePlugin.BUILD_GROUP);
         ArchivePublishArtifact warArtifact = new ArchivePublishArtifact(war);
@@ -83,9 +81,9 @@ public class WarPlugin implements Plugin<Project> {
     }
 
     public void configureConfigurations(ConfigurationContainer configurationContainer) {
-        Configuration provideCompileConfiguration = configurationContainer.add(PROVIDED_COMPILE_CONFIGURATION_NAME).setVisible(false).
+        Configuration provideCompileConfiguration = configurationContainer.create(PROVIDED_COMPILE_CONFIGURATION_NAME).setVisible(false).
                 setDescription("Additional compile classpath for libraries that should not be part of the WAR archive.");
-        Configuration provideRuntimeConfiguration = configurationContainer.add(PROVIDED_RUNTIME_CONFIGURATION_NAME).setVisible(false).
+        Configuration provideRuntimeConfiguration = configurationContainer.create(PROVIDED_RUNTIME_CONFIGURATION_NAME).setVisible(false).
                 extendsFrom(provideCompileConfiguration).
                 setDescription("Additional runtime classpath for libraries that should not be part of the WAR archive.");
         configurationContainer.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME).extendsFrom(provideCompileConfiguration);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/BinariesContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/BinariesContainer.java
deleted file mode 100644
index 4951358..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/BinariesContainer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.*;
-
-/**
- * A container for binaries that in turn contains more specialized containers.
- * Added to a project by the {@link org.gradle.api.plugins.LanguageBasePlugin}.
- */
-// TODO: ideally this would be a container where each element type is only allowed once and elements can be looked up by type
-// for now I solved the lookup (usability) problem with a JvmLanguagePlugin.getJvmBinariesContainer() method; maybe that's good enough
- at Incubating
-public interface BinariesContainer extends NamedDomainObjectSet<Named> {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ClassDirectoryBinary.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ClassDirectoryBinary.java
deleted file mode 100644
index ae2a94a..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ClassDirectoryBinary.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.*;
-import org.gradle.api.tasks.compile.AbstractCompile;
-
-import java.io.File;
-
-/**
- * An exploded binary containing resources and compiled class files.
- */
-// TODO: maybe we need to allow additional dirs like SourceSetOutput does
-// (esp. for backwards compatibility). Wonder if it's still necessary to distinguish
-// between classes and resources dirs, instead of just maintaining a collection of dirs.
-// As far as generated resources are concerned, it might be better to model
-// them as an additional (Buildable) ResourceSet.
- at Incubating
-public interface ClassDirectoryBinary extends Named, Buildable {
-    File getClassesDir();
-    void setClassesDir(File dir);
-    File getResourcesDir();
-    void setResourcesDir(File dir);
-    DomainObjectCollection<LanguageSourceSet> getSource();
-    Task getClassesTask();
-    void setClassesTask(Task task);
-    @Nullable
-    Copy getResourcesTask();
-    void setResourcesTask(Copy task);
-    // TODO: having a single compile task won't work if multiple separately compiled langs are used
-    @Nullable
-    AbstractCompile getCompileTask();
-    void setCompileTask(AbstractCompile task);
-    String getTaskName(String verb, String target);
-    String getTaskBaseName();
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Classpath.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Classpath.java
deleted file mode 100644
index 6c6a4e4..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Classpath.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.Buildable;
-import org.gradle.api.Incubating;
-import org.gradle.api.file.FileCollection;
-
-/**
- * A collection of files to be used as a class path.
- */
- at Incubating
-public interface Classpath extends Buildable {
-    FileCollection getFiles();
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/FunctionalSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/FunctionalSourceSet.java
deleted file mode 100644
index c12c4d0..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/FunctionalSourceSet.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
-import org.gradle.api.Incubating;
-import org.gradle.api.Named;
-
-/**
- * A container holding {@link LanguageSourceSet}s with a similar function
- * (production code, test code, etc.).
- */
- at Incubating
-public interface FunctionalSourceSet extends ExtensiblePolymorphicDomainObjectContainer<LanguageSourceSet>, Named {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/GroovyRuntime.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/GroovyRuntime.java
new file mode 100644
index 0000000..cfca7e1
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/GroovyRuntime.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 com.google.common.collect.Lists;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection;
+import org.gradle.api.internal.plugins.GroovyJarFile;
+import org.gradle.api.plugins.GroovyBasePlugin;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Provides information related to the Groovy runtime(s) used in a project. Added by the
+ * {@link GroovyBasePlugin} as a project extension named {@code groovyRuntime}.
+ *
+ * <p>Example usage:
+ *
+ * <pre autoTested="">
+ *     apply plugin: "groovy"
+ *
+ *     repositories {
+ *         mavenCentral()
+ *     }
+ *
+ *     dependencies {
+ *         compile "org.codehaus.groovy:groovy-all:2.1.2"
+ *     }
+ *
+ *     def groovyClasspath = groovyRuntime.inferGroovyClasspath(configurations.compile)
+ *     // The returned class path can be used to configure the 'groovyClasspath' property of tasks
+ *     // such as 'GroovyCompile' or 'Groovydoc', or to execute these and other Groovy tools directly.
+ * </pre>
+ */
+ at Incubating
+public class GroovyRuntime {
+    private final Project project;
+
+    public GroovyRuntime(Project project) {
+        this.project = project;
+    }
+
+    /**
+     * Searches the specified class path for Groovy Jars ({@code groovy(-indy)}, {@code groovy-all(-indy)})
+     * and returns a corresponding class path for executing Groovy tools such as the Groovy compiler and Groovydoc tool.
+     * The tool versions will match those of the Groovy Jars found. If no Groovy Jars are found on the specified class
+     * path, a class path with the contents of the {@code groovy} configuration will be returned.
+     *
+     * <p>The returned class path may be empty, or may fail to resolve when asked for its contents.
+     *
+     * @param classpath a class path containing Groovy Jars
+     * @return a corresponding class path for executing Groovy tools such as the Groovy compiler and Groovydoc tool
+     */
+    public FileCollection inferGroovyClasspath(final Iterable<File> classpath) {
+        final Configuration groovyConfiguration = project.getConfigurations().getByName(GroovyBasePlugin.GROOVY_CONFIGURATION_NAME);
+        if (!groovyConfiguration.getDependencies().isEmpty()) {
+            return groovyConfiguration;
+        }
+
+        // alternatively, we could return project.files(Runnable)
+        // would differ in at least the following ways: 1. live 2. no autowiring
+        return new LazilyInitializedFileCollection() {
+            @Override
+            public FileCollection createDelegate() {
+                GroovyJarFile groovyJar = findGroovyJarFile(classpath);
+                if (groovyJar == null) {
+                    throw new GradleException(String.format("Cannot infer Groovy class path because no Groovy Jar was found on class path: %s", classpath));
+                }
+
+                if (groovyJar.isGroovyAll()) {
+                    return project.files(groovyJar.getFile());
+                }
+
+                if (project.getRepositories().isEmpty()) {
+                    throw new GradleException("Cannot infer Groovy class path because no repository is declared for the project.");
+                }
+
+                String notation = groovyJar.getDependencyNotation();
+                List<Dependency> dependencies = Lists.newArrayList();
+                // project.getDependencies().create(String) seems to be the only feasible way to create a Dependency with a classifier
+                dependencies.add(project.getDependencies().create(notation));
+                if (groovyJar.getVersion().getMajor() >= 2) {
+                    // add groovy-ant to bring in AntGroovyCompiler
+                    dependencies.add(project.getDependencies().create(notation.replace(":groovy:", ":groovy-ant:")));
+                }
+                return project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[dependencies.size()]));
+            }
+
+            // let's override this so that delegate isn't created at autowiring time (which would mean on every build)
+            @Override
+            public TaskDependency getBuildDependencies() {
+                if (classpath instanceof Buildable) {
+                    return ((Buildable) classpath).getBuildDependencies();
+                }
+                return new TaskDependency() {
+                    public Set<? extends Task> getDependencies(Task task) {
+                        return Collections.emptySet();
+                    }
+                };
+            }
+        };
+    }
+
+    private GroovyJarFile findGroovyJarFile(Iterable<File> classpath) {
+        if (classpath == null) { return null; }
+        for (File file : classpath) {
+            GroovyJarFile groovyJar = GroovyJarFile.parse(file);
+            if (groovyJar != null) { return groovyJar; }
+        }
+        return null;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JavaSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JavaSourceSet.java
deleted file mode 100644
index 163c7e7..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JavaSourceSet.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.Incubating;
-
-/**
- * A set of sources passed to the Java compiler.
- */
- at Incubating
-public interface JavaSourceSet extends JvmLanguageSourceSet {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmBinaryContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmBinaryContainer.java
deleted file mode 100644
index 0a59bb6..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmBinaryContainer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
-import org.gradle.api.Incubating;
-import org.gradle.api.Named;
-
-/**
- * A container for JVM binaries.
- */
- at Incubating
-public interface JvmBinaryContainer extends ExtensiblePolymorphicDomainObjectContainer<ClassDirectoryBinary>, Named {
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmLanguageSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmLanguageSourceSet.java
deleted file mode 100644
index 4a9c85e..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmLanguageSourceSet.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.Incubating;
-
-/**
- * A set of sources for a JVM language.
- */
- at Incubating
-public interface JvmLanguageSourceSet extends LanguageSourceSet {
-    Classpath getCompileClasspath();
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/LanguageSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/LanguageSourceSet.java
deleted file mode 100644
index 7fd9b13..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/LanguageSourceSet.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.Buildable;
-import org.gradle.api.Incubating;
-import org.gradle.api.Named;
-import org.gradle.api.file.SourceDirectorySet;
-
-/**
- * A set of sources for a programming language.
- */
- at Incubating
-public interface LanguageSourceSet extends Named, Buildable {
-    // TODO: do we want to keep using SourceDirectorySet in the new API?
-    // would feel more natural if dirs could be added directly to LanguageSourceSet
-    // could also think about extending SourceDirectorySet
-    SourceDirectorySet getSource();
-    FunctionalSourceSet getParent();
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ProjectSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ProjectSourceSet.java
deleted file mode 100644
index 6831762..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ProjectSourceSet.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.Incubating;
-import org.gradle.api.NamedDomainObjectContainer;
-
-/**
- * A container of {@link FunctionalSourceSet}s. Added to a project by the
- * {@link org.gradle.api.plugins.LanguageBasePlugin}.
- */
- at Incubating
-public interface ProjectSourceSet extends NamedDomainObjectContainer<FunctionalSourceSet> {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ResourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ResourceSet.java
deleted file mode 100644
index b40e94e..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ResourceSet.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.tasks;
-
-import org.gradle.api.Incubating;
-
-/**
- * A set of resource files.
- */
- at Incubating
-public interface ResourceSet extends LanguageSourceSet {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
index d2ceb64..e77b0de 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
@@ -30,7 +30,9 @@ public interface SourceSetContainer extends NamedDomainObjectContainer<SourceSet
      * @param name The name of the new source set.
      * @return The newly added source set.
      * @throws org.gradle.api.InvalidUserDataException when a source set with the given name already exists in this container.
+     * @deprecated use {@link #create(String)} instead
      */
+    @Deprecated
     SourceSet add(String name) throws InvalidUserDataException;
 
     /**
@@ -41,6 +43,8 @@ public interface SourceSetContainer extends NamedDomainObjectContainer<SourceSet
      * @param configureClosure The closure to use to configure the source set.
      * @return The newly added source set.
      * @throws InvalidUserDataException when a source set with the given name already exists in this container.
+     * @deprecated use {@link #create(String, groovy.lang.Closure)} instead
      */
+    @Deprecated
     SourceSet add(String name, Closure configureClosure) throws InvalidUserDataException;
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Upload.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Upload.java
new file mode 100644
index 0000000..2eeaf01
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Upload.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.ModuleInternal;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+import org.gradle.internal.Transformers;
+import org.gradle.util.ConfigureUtil;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.List;
+
+import static org.gradle.util.CollectionUtils.collect;
+
+/**
+ * Uploads the artifacts of a {@link Configuration} to a set of repositories.
+ */
+public class Upload extends ConventionTask {
+
+    private Configuration configuration;
+    private boolean uploadDescriptor;
+    private File descriptorDestination;
+    private RepositoryHandler repositories;
+
+    private final ArtifactPublicationServices publicationServices;
+
+    @Inject
+    public Upload(ArtifactPublicationServices publicationServices) {
+        this.publicationServices = publicationServices;
+        repositories = publicationServices.createRepositoryHandler();
+    }
+
+    @TaskAction
+    protected void upload() {
+        getLogger().info("Publishing configuration: " + configuration);
+        ModuleInternal module = ((ConfigurationInternal) configuration).getModule();
+
+        ArtifactPublisher artifactPublisher = publicationServices.createArtifactPublisher();
+        File descriptorDestination = isUploadDescriptor() ? getDescriptorDestination() : null;
+        List<PublicationAwareRepository> publishRepositories = collect(repositories, Transformers.cast(PublicationAwareRepository.class));
+
+        try {
+            artifactPublisher.publish(publishRepositories, module, configuration, descriptorDestination);
+        } catch (Exception e) {
+            throw new PublishException(String.format("Could not publish configuration '%s'", configuration.getName()), e);
+        }
+    }
+
+    /**
+     * Specifies whether the dependency descriptor should be uploaded.
+     */
+    public boolean isUploadDescriptor() {
+        return uploadDescriptor;
+    }
+
+    public void setUploadDescriptor(boolean uploadDescriptor) {
+        this.uploadDescriptor = uploadDescriptor;
+    }
+
+    /**
+     * Returns the path to generate the dependency descriptor to.
+     */
+    public File getDescriptorDestination() {
+        return descriptorDestination;
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setDescriptorDestination(File descriptorDestination) {
+        this.descriptorDestination = descriptorDestination;
+    }
+
+    /**
+     * Returns the repositories to upload to.
+     */
+    public RepositoryHandler getRepositories() {
+        return repositories;
+    }
+
+    /**
+     * Returns the configuration to upload.
+     */
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    /**
+     * Configures the set of repositories to upload to.
+     */
+    public RepositoryHandler repositories(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, repositories);
+    }
+
+    /**
+     * Returns the artifacts which will be uploaded.
+     *
+     * @return the artifacts.
+     */
+    @InputFiles
+    public FileCollection getArtifacts() {
+        Configuration configuration = getConfiguration();
+        return configuration == null ? null : configuration.getAllArtifacts().getFiles();
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
index 2b503b6..b1acfaf 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
@@ -18,16 +18,11 @@ package org.gradle.api.tasks.application
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.ConventionTask
 import org.gradle.api.internal.plugins.StartScriptGenerator
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.*
 import org.gradle.util.GUtil
 
 /**
  * <p>A {@link org.gradle.api.Task} for creating OS dependent start scripts.</p>
- *
- * @author Rene Groeschke
  */
 public class CreateStartScripts extends ConventionTask {
 
@@ -43,6 +38,13 @@ public class CreateStartScripts extends ConventionTask {
     String mainClassName
 
     /**
+     * The application's default JVM options.
+     */
+    @Input
+    @Optional
+    Iterable<String> defaultJvmOpts = []
+
+    /**
      * The application's name.
      */
     @Input
@@ -98,6 +100,7 @@ public class CreateStartScripts extends ConventionTask {
         def generator = new StartScriptGenerator()
         generator.applicationName = getApplicationName()
         generator.mainClassName = getMainClassName()
+        generator.defaultJvmOpts = getDefaultJvmOpts()
         generator.optsEnvironmentVar = getOptsEnvironmentVar()
         generator.exitEnvironmentVar = getExitEnvironmentVar()
         generator.classpath = getClasspath().collect { "lib/${it.name}" }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
index 6fc0a30..f64f02e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
@@ -17,39 +17,39 @@
 package org.gradle.api.tasks.bundling
 
 import org.gradle.api.file.CopySpec
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.collections.FileTreeAdapter
 import org.gradle.api.internal.file.collections.MapFileTree
+import org.gradle.api.internal.file.copy.DefaultCopySpec
+import org.gradle.api.java.archives.Manifest
 import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.internal.nativeplatform.filesystem.Chmod
 import org.gradle.util.ConfigureUtil
-import org.gradle.api.internal.file.copy.CopySpecImpl
-import org.gradle.api.file.FileCopyDetails
-import org.gradle.api.java.archives.Manifest
-import org.gradle.api.internal.file.collections.FileTreeAdapter
 
 /**
  * Assembles a JAR archive.
- *
- * @author Hans Dockter
  */
 public class Jar extends Zip {
     public static final String DEFAULT_EXTENSION = 'jar'
 
     private Manifest manifest
-    private final CopySpecImpl metaInf
+    private final DefaultCopySpec metaInf
 
     Jar() {
         extension = DEFAULT_EXTENSION
-        manifest = new DefaultManifest(project.fileResolver)
+        manifest = new DefaultManifest(getServices().get(FileResolver))
         // Add these as separate specs, so they are not affected by the changes to the main spec
-        metaInf = copyAction.rootSpec.addFirst().into('META-INF')
+        metaInf = rootSpec.addFirst().into('META-INF')
         metaInf.addChild().from {
-            MapFileTree manifestSource = new MapFileTree(temporaryDirFactory)
+            MapFileTree manifestSource = new MapFileTree(temporaryDirFactory, services.get(Chmod))
             manifestSource.add('MANIFEST.MF') {OutputStream outstr ->
                 Manifest manifest = getManifest() ?: new DefaultManifest(null)
                 manifest.writeTo(new OutputStreamWriter(outstr))
             }
             return new FileTreeAdapter(manifestSource)
         }
-        copyAction.mainSpec.eachFile { FileCopyDetails details ->
+        mainSpec.eachFile { FileCopyDetails details ->
             if (details.path.equalsIgnoreCase('META-INF/MANIFEST.MF')) {
                 details.exclude()
             }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
index edbec72..7d946a5 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
@@ -18,16 +18,14 @@ package org.gradle.api.tasks.bundling
 
 import org.gradle.api.file.CopySpec
 import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.copy.DefaultCopySpec
 import org.gradle.api.tasks.InputFile
 import org.gradle.api.tasks.InputFiles
 import org.gradle.api.tasks.Optional
 import org.gradle.util.ConfigureUtil
-import org.gradle.api.internal.file.copy.CopySpecImpl
 
 /**
  * Assembles a WAR archive.
- *
- * @author Hans Dockter
  */
 class War extends Jar {
     public static final String WAR_EXTENSION = 'war'
@@ -35,12 +33,13 @@ class War extends Jar {
     private File webXml
 
     private FileCollection classpath
-    private final CopySpecImpl webInf
+    private final DefaultCopySpec webInf
 
     War() {
         extension = WAR_EXTENSION
         // Add these as separate specs, so they are not affected by the changes to the main spec
-        webInf = copyAction.rootSpec.addChild().into('WEB-INF')
+
+        webInf = rootSpec.addChildBeforeSpec(mainSpec).into('WEB-INF')
         webInf.into('classes') {
             from {
                 def classpath = getClasspath()
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java
index d1c5847..c7655d4 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java
@@ -29,7 +29,6 @@ public abstract class AbstractCompile extends SourceTask {
     private String targetCompatibility;
     private FileCollection classpath;
 
-    @TaskAction
     protected abstract void compile();
 
     /**
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 dff02e1..163e8c9 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
@@ -29,8 +29,6 @@ import java.util.Map;
 
 /**
  * Base class for compilation-related options.
- *
- * @author Hans Dockter
  */
 public abstract class AbstractOptions implements Serializable {
     private static final long serialVersionUID = 0;
@@ -38,7 +36,7 @@ public abstract class AbstractOptions implements Serializable {
     public void define(@Nullable Map<String, Object> args) {
         if (args == null) { return; }
         for (Map.Entry<String, Object> arg: args.entrySet()) {
-            JavaReflectionUtil.writeProperty(this, arg.getKey(), arg.getValue());
+            JavaReflectionUtil.writeableProperty(getClass(), arg.getKey()).setValue(this, arg.getValue());
         }
     }
 
@@ -77,7 +75,7 @@ public abstract class AbstractOptions implements Serializable {
     }
 
     private void addValueToMapIfNotNull(Map<String, Object> map, Field field) {
-        Object value = JavaReflectionUtil.readProperty(this, field.getName());
+        Object value = JavaReflectionUtil.readableProperty(getClass(), field.getName()).getValue(this);
         if (value != null) {
             map.put(getAntPropertyName(field.getName()), getAntPropertyValue(field.getName(), value));
         }
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
index 2646237..50fbfbc 100644
--- 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
@@ -16,7 +16,6 @@
 package org.gradle.api.tasks.compile;
 
 import com.google.common.collect.Lists;
-
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.Optional;
 
@@ -25,8 +24,6 @@ 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;
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 1fa0a64..7eedf8d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
@@ -17,30 +17,52 @@
 package org.gradle.api.tasks.compile;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.internal.hash.DefaultHasher;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.compile.*;
 import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.internal.Factory;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
+import org.gradle.api.internal.tasks.compile.incremental.*;
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoExtractor;
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoSerializer;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 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.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.internal.Factory;
+import org.gradle.util.Clock;
 import org.gradle.util.DeprecationLogger;
+import org.gradle.util.SingleMessageLogger;
 
 import java.io.File;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static java.util.Collections.emptyList;
 
 /**
  * Compiles Java source files.
  *
- * @author Hans Dockter
  * @deprecated This class has been replaced by {@link JavaCompile}.
  */
 @Deprecated
 public class Compile extends AbstractCompile {
-    private Compiler<JavaCompileSpec> javaCompiler;
+
+    private static final Logger LOG = Logging.getLogger(Compile.class);
+
+    private Compiler<JavaCompileSpec> cleaningCompiler;
     private File dependencyCacheDir;
     private final CompileOptions compileOptions = new CompileOptions();
+    private final Compiler<JavaCompileSpec> javaCompiler;
+    private final IncrementalCompilationSupport incrementalCompilation;
 
     public Compile() {
         if (!(this instanceof JavaCompile)) {
@@ -49,24 +71,105 @@ public class Compile extends AbstractCompile {
         Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
         JavaCompilerFactory inProcessCompilerFactory = new InProcessJavaCompilerFactory();
         ProjectInternal projectInternal = (ProjectInternal) getProject();
-        TemporaryFileProvider tempFileProvider = projectInternal.getServices().get(TemporaryFileProvider.class);
-        JavaCompilerFactory defaultCompilerFactory = new DefaultJavaCompilerFactory(projectInternal, tempFileProvider, antBuilderFactory, inProcessCompilerFactory);
-        Compiler<JavaCompileSpec> delegatingCompiler = new DelegatingJavaCompiler(defaultCompilerFactory);
-        javaCompiler = new IncrementalJavaCompiler(delegatingCompiler, antBuilderFactory, getOutputs());
+        CompilerDaemonManager compilerDaemonManager = getServices().get(CompilerDaemonManager.class);
+        JavaCompilerFactory defaultCompilerFactory = new DefaultJavaCompilerFactory(projectInternal, antBuilderFactory, inProcessCompilerFactory, compilerDaemonManager);
+        javaCompiler = new DelegatingJavaCompiler(defaultCompilerFactory);
+        cleaningCompiler = new CleaningJavaCompiler(javaCompiler, antBuilderFactory, getOutputs());
+        JarSnapshotFeeder jarSnapshotFeeder = new JarSnapshotFeeder(getJarSnapshotCache(), new JarSnapshotter(new DefaultHasher()));
+        incrementalCompilation = new IncrementalCompilationSupport(jarSnapshotFeeder);
     }
 
+    private FileCollection compileClasspath; //TODO SF remove this hack
+
     @TaskAction
+    protected void compile(IncrementalTaskInputs inputs) {
+        if (!maybeCompileIncrementally(inputs)) {
+            compile();
+        }
+        incrementalCompilation.compilationComplete(compileOptions,
+                new ClassDependencyInfoExtractor(getDestinationDir()),
+                getDependencyInfoSerializer(), Collections.<JarArchive>emptyList());
+    }
+
+    private ClassDependencyInfoSerializer getDependencyInfoSerializer() {
+        return new ClassDependencyInfoSerializer(new File(getProject().getBuildDir(), "class-info.bin"));
+    }
+
+    private boolean maybeCompileIncrementally(IncrementalTaskInputs inputs) {
+        if (!compileOptions.isIncremental()) {
+            return false;
+        }
+        //hack
+        List<File> sourceDirs = getSourceDirs();
+        if (sourceDirs.isEmpty()) {
+            LOG.lifecycle("{} cannot run incrementally because Gradle cannot infer the source directories.", getPath());
+            return false;
+        }
+        if (!inputs.isIncremental()) {
+            LOG.lifecycle("{} is not incremental (e.g. outputs have changed, no previous execution, etc). Using regular compile.", getPath());
+            return false;
+        }
+        ClassDependencyInfoSerializer dependencyInfoSerializer = getDependencyInfoSerializer();
+        if (!dependencyInfoSerializer.isInfoAvailable()) {
+            //TODO SF let's unit test a scenario when after regular compilation incremental compilation is scheduled
+            LOG.lifecycle("{} is not incremental because there is no class dependency data left from previous incremental build.", getPath());
+            return false;
+        }
+
+        SingleMessageLogger.incubatingFeatureUsed("Incremental java compilation");
+
+        SelectiveJavaCompiler compiler = new SelectiveJavaCompiler(javaCompiler, getProject().fileTree(getDestinationDir()));
+        SelectiveCompilation selectiveCompilation = new SelectiveCompilation(inputs, getSource(), getClasspath(), getDestinationDir(),
+                dependencyInfoSerializer, getJarSnapshotCache(), compiler, sourceDirs, (FileOperations) getProject());
+
+        if (!selectiveCompilation.getCompilationNeeded()) {
+            LOG.lifecycle("{} does not require recompilation. Skipping the compiler.", getPath());
+            return true;
+        }
+
+        Clock clock = new Clock();
+        performCompilation(selectiveCompilation.getSource(), selectiveCompilation.getClasspath(), selectiveCompilation.getFullRebuildRequired()? cleaningCompiler : compiler);
+        LOG.lifecycle("{} - incremental compilation took {}", getPath(), clock.getTime());
+
+        return true;
+    }
+
+    private List<File> getSourceDirs() {
+        List<File> sourceDirs = new LinkedList<File>();
+        for (Object s : source) {
+            if (s instanceof SourceDirectorySet) {
+                sourceDirs.addAll(((SourceDirectorySet) s).getSrcDirs());
+            } else {
+                return emptyList();
+            }
+        }
+        return sourceDirs;
+    }
+
     protected void compile() {
+        FileTree source = getSource();
+        FileCollection classpath = getClasspath();
+
+        performCompilation(source, classpath, cleaningCompiler);
+    }
+
+    private void performCompilation(FileCollection source, FileCollection classpath, Compiler<JavaCompileSpec> compiler) {
         DefaultJavaCompileSpec spec = new DefaultJavaCompileSpec();
-        spec.setSource(getSource());
+        spec.setSource(source);
         spec.setDestinationDir(getDestinationDir());
-        spec.setClasspath(getClasspath());
+        spec.setClasspath(classpath);
         spec.setDependencyCacheDir(getDependencyCacheDir());
         spec.setSourceCompatibility(getSourceCompatibility());
         spec.setTargetCompatibility(getTargetCompatibility());
         spec.setCompileOptions(compileOptions);
-        WorkResult result = javaCompiler.execute(spec);
+        WorkResult result = compiler.execute(spec);
         setDidWork(result.getDidWork());
+        compileClasspath = classpath;
+    }
+
+    private JarSnapshotCache getJarSnapshotCache() {
+        //hack, needs fixing
+        return new JarSnapshotCache(new File(getProject().getRootProject().getProjectDir(), ".gradle/jar-snapshot-cache.bin"));
     }
 
     @OutputDirectory
@@ -88,11 +191,25 @@ public class Compile extends AbstractCompile {
         return compileOptions;
     }
 
+    /**
+     * This method was never intended to be used by the users.
+     *
+     * @return the compiler
+     */
+    @Deprecated
     public Compiler<JavaCompileSpec> getJavaCompiler() {
-        return javaCompiler;
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Compile.getJavaCompiler()");
+        return cleaningCompiler;
     }
 
+    /**
+     * This method was never intended to be used by the users.
+     *
+     * @param javaCompiler to set
+     */
+    @Deprecated
     public void setJavaCompiler(Compiler<JavaCompileSpec> javaCompiler) {
-        this.javaCompiler = javaCompiler;
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Compile.setJavaCompiler(Compiler<JavaCompileSpec>)");
+        this.cleaningCompiler = javaCompiler;
     }
 }
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 3a41797..b5844d5 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
@@ -18,24 +18,24 @@ package org.gradle.api.tasks.compile;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import org.gradle.api.Incubating;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.Optional;
 import org.gradle.util.DeprecationLogger;
+import org.gradle.util.SingleMessageLogger;
 
 import java.util.List;
 import java.util.Map;
 
 /**
  * Main options for Java compilation.
- *
- * @author Hans Dockter
  */
 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");
+            ImmutableSet.of("debugOptions", "forkOptions", "compilerArgs", "dependOptions", "useDepend", "useAnt", "incremental");
 
     private boolean failOnError = true;
 
@@ -74,20 +74,24 @@ public class CompileOptions extends AbstractOptions {
     private List<String> compilerArgs = Lists.newArrayList();
 
     private boolean useAnt;
+    private boolean incremental;
 
     /**
      * Tells whether to fail the build when compilation fails. Defaults to {@code true}.
      */
+    @Input
     public boolean isFailOnError() {
         return failOnError;
     }
 
     /**
-     * Tells whether to fail the build when compilation fails. Defaults to {@code true}.
+     * Deprecated.
+     *
+     * @deprecated use {@link #isFailOnError()}
      */
-    // @Input not recognized if there is only an "is" method
-    @Input
+    @Deprecated
     public boolean getFailOnError() {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("CompileOptions.getFailOnError()", "CompileOptions.isFailOnError()");
         return failOnError;
     }
 
@@ -189,7 +193,6 @@ public class CompileOptions extends AbstractOptions {
      *
      * @deprecated No replacement
      */
-    // @Input not recognized if there is only an "is" method
     @Input
     @Deprecated
     public boolean getOptimize() {
@@ -212,17 +215,19 @@ public class CompileOptions extends AbstractOptions {
      * Tells whether to include debugging information in the generated class files. Defaults
      * to {@code true}. See {@link DebugOptions#getDebugLevel()} for which debugging information will be generated.
      */
+    @Input
     public boolean isDebug() {
         return debug;
     }
 
     /**
-     * Tells whether to include debugging information in the generated class files. Defaults
-     * to {@code true}. See {@link DebugOptions#getDebugLevel()} for which debugging information will be generated.
+     * Deprecated.
+     *
+     * @deprecated use {@link #isDebug()}
      */
-    // @Input not recognized if there is only an "is" method
-    @Input
+    @Deprecated
     public boolean getDebug() {
+        DeprecationLogger.nagUserOfReplacedMethod("CompileOptions.getDebug()", "CompileOptions.isDebug()");
         return debug;
     }
 
@@ -354,7 +359,6 @@ public class CompileOptions extends AbstractOptions {
      *
      * @deprecated No replacement
      */
-    // @Input not recognized if there is only an "is" method
     @Input
     @Deprecated
     public boolean getIncludeJavaRuntime() {
@@ -479,6 +483,17 @@ public class CompileOptions extends AbstractOptions {
         return this;
     }
 
+    @Incubating
+    /**
+     * Configure the java compilation to be incremental (e.g. compiles only those java classes that were changed or that are dependencies to the changed classes).
+     * The feature is incubating and does not yet satisfies all compilation scenarios.
+     */
+    public CompileOptions setIncremental(boolean incremental) {
+        SingleMessageLogger.incubatingFeatureUsed("Incremental java compilation");
+        this.incremental = incremental;
+        return this;
+    }
+
     /**
      * Internal method.
      */
@@ -512,5 +527,13 @@ public class CompileOptions extends AbstractOptions {
         }
         return value;
     }
+
+    /**
+     * informs whether to use experimental incremental compilation feature. See {@link #setIncremental(boolean)}
+     */
+    @Incubating
+    public boolean isIncremental() {
+        return incremental;
+    }
 }
 
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 be6b558..6bdcccf 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
@@ -22,8 +22,6 @@ import org.gradle.api.tasks.Optional;
 /**
  * Debug options for Java compilation. Only take effect if {@link CompileOptions#debug}
  * is set to {@code true}.
- *
- * @author Hans Dockter
  */
 public class DebugOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
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 f87283b..ca8836b 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
@@ -29,8 +29,6 @@ import com.google.common.collect.ImmutableSet;
  * <p>The {@code srcDir}, {@code destDir}, and {@code cache} properties of the Ant task
  * are set automatically. The latter is replaced by a {@code useCache} option to enable/disable caching of
  * dependency information.
- *
- * @author Steve Appling
  */
 public class DependOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
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 e3d7b1e..00bfaa4 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
@@ -21,8 +21,6 @@ import org.gradle.api.tasks.Optional;
 
 /**
  * Fork options for Java compilation. Only take effect if {@code CompileOptions.fork} is {@code true}.
- *
- * @author Hans Dockter
  */
 public class ForkOptions extends BaseForkOptions {
     private static final long serialVersionUID = 0;
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 1e36dc5..1bfffb8 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
@@ -23,10 +23,13 @@ import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.project.IsolatedAntBuilder;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.compile.*;
 import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.*;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
+import org.gradle.api.internal.tasks.compile.daemon.InProcessCompilerDaemonFactory;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.internal.Factory;
 import org.gradle.util.GFileUtils;
@@ -35,8 +38,6 @@ import java.io.File;
 
 /**
  * Compiles Groovy source files, and optionally, Java source files.
- *
- * @author Hans Dockter
  */
 public class GroovyCompile extends AbstractCompile {
     private Compiler<GroovyJavaJointCompileSpec> compiler;
@@ -44,7 +45,7 @@ public class GroovyCompile extends AbstractCompile {
     private final CompileOptions compileOptions = new CompileOptions();
     private final GroovyCompileOptions groovyCompileOptions = new GroovyCompileOptions();
     private final TemporaryFileProvider tempFileProvider;
-    
+
     public GroovyCompile() {
         ProjectInternal projectInternal = (ProjectInternal) getProject();
         IsolatedAntBuilder antBuilder = getServices().get(IsolatedAntBuilder.class);
@@ -52,12 +53,15 @@ public class GroovyCompile extends AbstractCompile {
         Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
         JavaCompilerFactory inProcessCompilerFactory = new InProcessJavaCompilerFactory();
         tempFileProvider = projectInternal.getServices().get(TemporaryFileProvider.class);
-        DefaultJavaCompilerFactory javaCompilerFactory = new DefaultJavaCompilerFactory(projectInternal, tempFileProvider, antBuilderFactory, inProcessCompilerFactory);
-        GroovyCompilerFactory groovyCompilerFactory = new GroovyCompilerFactory(projectInternal, antBuilder, classPathRegistry, javaCompilerFactory);
+        CompilerDaemonManager compilerDaemonManager = getServices().get(CompilerDaemonManager.class);
+        InProcessCompilerDaemonFactory inProcessCompilerDaemonFactory = getServices().get(InProcessCompilerDaemonFactory.class);
+        DefaultJavaCompilerFactory javaCompilerFactory = new DefaultJavaCompilerFactory(projectInternal, antBuilderFactory, inProcessCompilerFactory, compilerDaemonManager);
+        GroovyCompilerFactory groovyCompilerFactory = new GroovyCompilerFactory(projectInternal, antBuilder, classPathRegistry, javaCompilerFactory, compilerDaemonManager, inProcessCompilerDaemonFactory);
         Compiler<GroovyJavaJointCompileSpec> delegatingCompiler = new DelegatingGroovyCompiler(groovyCompilerFactory);
-        compiler = new IncrementalGroovyCompiler(delegatingCompiler, getOutputs());
+        compiler = new CleaningGroovyCompiler(delegatingCompiler, getOutputs());
     }
 
+    @TaskAction
     protected void compile() {
         checkGroovyClasspathIsNonEmpty();
         DefaultGroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec();
@@ -80,7 +84,8 @@ public class GroovyCompile extends AbstractCompile {
 
     private void checkGroovyClasspathIsNonEmpty() {
         if (getGroovyClasspath().isEmpty()) {
-            throw new InvalidUserDataException("'" + getName() + ".groovyClasspath' must not be empty. It is either configured automatically by the 'groovy-base' plugin, or can be set manually.");
+            throw new InvalidUserDataException("'" + getName() + ".groovyClasspath' must not be empty. If a Groovy compile dependency is provided, "
+                    + "the 'groovy-base' plugin will attempt to configure 'groovyClasspath' automatically. Alternatively, you may configure 'groovyClasspath' explicitly.");
         }
     }
 
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 26812f8..4bb05ec 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
@@ -18,7 +18,6 @@ package org.gradle.api.tasks.compile;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
-
 import org.gradle.api.Incubating;
 import org.gradle.api.tasks.Input;
 import org.gradle.util.DeprecationLogger;
@@ -29,8 +28,6 @@ import java.util.Map;
 
 /**
  * Compilation options to be passed to the Groovy compiler.
- *
- * @author Hans Dockter
  */
 public class GroovyCompileOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
@@ -197,7 +194,7 @@ public class GroovyCompileOptions extends AbstractOptions {
      */
     @Deprecated
     public void setStacktrace(boolean stacktrace) {
-        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.stacktrack", "This property has no replacement.");
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.stacktrace", "This property has no replacement.");
         this.stacktrace = stacktrace;
     }
 
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 5aecce1..7e7b000 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
@@ -18,8 +18,6 @@ package org.gradle.api.tasks.compile;
 /**
  * Fork options for Groovy compilation. Only take effect if {@code GroovyCompileOptions.fork}
  * is {@code true}.
- *
- * @author Hans Dockter
  */
 public class GroovyForkOptions extends BaseForkOptions {
     private static final long serialVersionUID = 0;
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
index 4cff198..e9884f9 100644
--- 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
@@ -18,6 +18,14 @@ package org.gradle.api.tasks.compile;
 
 /**
  * Compiles Java source files.
+ *
+ * <pre autoTested=''>
+ *     apply plugin: 'java'
+ *     compileJava {
+ *         //enable compilation in a separate daemon process
+ *         options.fork = true
+ *     }
+ * </pre>
  */
 public class JavaCompile extends Compile {
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy
index 60c0c42..ccd8e45 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy
@@ -21,9 +21,6 @@ import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.ClassPathRegistry
 import org.gradle.api.internal.project.IsolatedAntBuilder
 
-/**
- * @author Hans Dockter
- */
 class AntGroovydoc {
     private final IsolatedAntBuilder ant
     private final ClassPathRegistry classPathRegistry
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy
index 0f48808..9446beb 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy
@@ -17,8 +17,9 @@
 package org.gradle.api.tasks.javadoc
 
 /**
- * @author Hans Dockter
+ * @deprecated No replacement
  */
+ at Deprecated
 class AntJavadoc {
     void execute(List<File> sourceDirs, File destDir, Set<File> classpathFiles, String windowTitle, String maxMemory,
                  List<String> includes, List<String> excludes, boolean verbose, AntBuilder ant) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
index 27f6667..19011e9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
@@ -34,8 +34,6 @@ import java.util.*;
  * some severe limitations at the moment (for example no doc for properties comments). The version of the Groovydoc that
  * is used, is the one from the Groovy defined in the build script. Please note also, that the Groovydoc tool prints to
  * System.out for many of its statements and does circumvents our logging currently.
- *
- * @author Hans Dockter
  */
 public class Groovydoc extends SourceTask {
     private FileCollection groovyClasspath;
@@ -184,14 +182,14 @@ public class Groovydoc extends SourceTask {
     /**
      * Sets title for the package index(first) page (optional).
      *
-     * @param docTitle the docTitle as html-code
+     * @param docTitle the docTitle as HTML
      */
     public void setDocTitle(String docTitle) {
         this.docTitle = docTitle;
     }
 
     /**
-     * Returns the html header for each page. Set to {@code null} when there is no header.
+     * Returns the HTML header for each page. Set to {@code null} when there is no header.
      */
     @Input @Optional
     public String getHeader() {
@@ -201,14 +199,14 @@ public class Groovydoc extends SourceTask {
     /**
      * Sets header text for each page (optional).
      *
-     * @param header the header as html-code
+     * @param header the header as HTML
      */
     public void setHeader(String header) {
         this.header = header;
     }
 
     /**
-     * Returns the html footer for each page. Set to {@code null} when there is no footer.
+     * Returns the HTML footer for each page. Set to {@code null} when there is no footer.
      */
     @Input @Optional
     public String getFooter() {
@@ -218,21 +216,21 @@ public class Groovydoc extends SourceTask {
     /**
      * Sets footer text for each page (optional).
      *
-     * @param footer the footer as html-code
+     * @param footer the footer as HTML
      */
     public void setFooter(String footer) {
         this.footer = footer;
     }
 
     /**
-     * Returns a html file to be used for overview documentation. Set to {@code null} when there is no overview file.
+     * Returns a HTML file to be used for overview documentation. Set to {@code null} when there is no overview file.
      */
     public String getOverview() {
         return overview;
     }
 
     /**
-     * Sets a html file to be used for overview documentation (optional).
+     * Sets a HTML file to be used for overview documentation (optional).
      */
     public void setOverview(String overview) {
         this.overview = overview;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
index 3da6c79..3b04541 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.tasks.javadoc;
 
+import groovy.lang.Closure;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.tasks.*;
@@ -23,11 +24,10 @@ import org.gradle.external.javadoc.MinimalJavadocOptions;
 import org.gradle.external.javadoc.StandardJavadocDocletOptions;
 import org.gradle.external.javadoc.internal.JavadocExecHandleBuilder;
 import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecActionFactory;
 import org.gradle.process.internal.ExecException;
 import org.gradle.util.GUtil;
 
-import groovy.lang.Closure;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -67,11 +67,9 @@ import java.util.List;
  *   options.addStringOption("jaxrscontext", "http://localhost:8080/myapp")
  * }
  * </pre>
- *
- * @author Hans Dockter
  */
 public class Javadoc extends SourceTask {
-    private JavadocExecHandleBuilder javadocExecHandleBuilder = new JavadocExecHandleBuilder();
+    private JavadocExecHandleBuilder javadocExecHandleBuilder = new JavadocExecHandleBuilder(getServices().get(ExecActionFactory.class));
 
     private File destinationDir;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/JUnitXmlReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/JUnitXmlReport.java
new file mode 100644
index 0000000..692c2dc
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/JUnitXmlReport.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.testing;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.reporting.DirectoryReport;
+
+/**
+ * The JUnit XML files, commonly used to communicate results to CI servers.
+ */
+public interface JUnitXmlReport extends DirectoryReport {
+
+    /**
+     * Should the output be associated with individual test cases instead of at the suite level.
+     */
+    @Incubating
+    boolean isOutputPerTestCase();
+
+    /**
+     * Should the output be associated with individual test cases instead of at the suite level.
+     */
+    @Incubating
+    void setOutputPerTestCase(boolean outputPerTestCase);
+
+}
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 2a4aa96..be295e0 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
@@ -17,6 +17,7 @@
 package org.gradle.api.tasks.testing;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.Incubating;
 import org.gradle.api.file.FileCollection;
@@ -24,19 +25,24 @@ import org.gradle.api.file.FileTree;
 import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.options.Option;
+import org.gradle.api.internal.tasks.testing.DefaultTestTaskReports;
+import org.gradle.api.internal.tasks.testing.NoMatchingTestsReporter;
 import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.detection.DefaultTestExecuter;
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter;
+import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter;
 import org.gradle.api.internal.tasks.testing.junit.JUnitTestFramework;
 import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
 import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
-import org.gradle.api.internal.tasks.testing.junit.result.Binary2JUnitXmlReportGenerator;
-import org.gradle.api.internal.tasks.testing.junit.result.TestReportDataCollector;
+import org.gradle.api.internal.tasks.testing.junit.result.*;
 import org.gradle.api.internal.tasks.testing.logging.*;
 import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
 import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Reporting;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.*;
 import org.gradle.api.tasks.testing.logging.TestLogging;
@@ -44,6 +50,7 @@ import org.gradle.api.tasks.testing.logging.TestLoggingContainer;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.internal.Factory;
+import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
@@ -57,15 +64,11 @@ import org.gradle.process.ProcessForkOptions;
 import org.gradle.process.internal.DefaultJavaForkOptions;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.util.ConfigureUtil;
+import org.gradle.util.DeprecationLogger;
 
 import javax.inject.Inject;
 import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static java.util.Arrays.asList;
+import java.util.*;
 
 /**
  * Executes JUnit (3.8.x or 4.x) or TestNG tests. Test are always run in (one or more) separate JVMs.
@@ -106,55 +109,69 @@ import static java.util.Arrays.asList;
  *   }
  * }
  * </pre>
- *
- * @author Hans Dockter
+ * <p>
+ * The test process can be started in debug mode (see {@link #getDebug()}) in an ad-hoc manner by supplying the `--debug-jvm` switch when invoking the build.
+ * <pre>
+ * gradle someTestTask --debug-jvm
+ * </pre>
+
  */
-public class Test extends ConventionTask implements JavaForkOptions, PatternFilterable, VerificationTask {
+public class Test extends ConventionTask implements JavaForkOptions, PatternFilterable, VerificationTask, Reporting<TestTaskReports> {
 
     private final ListenerBroadcast<TestListener> testListenerBroadcaster;
     private final ListenerBroadcast<TestOutputListener> testOutputListenerBroadcaster;
     private final StyledTextOutputFactory textOutputFactory;
+    private final Instantiator instantiator;
     private final ProgressLoggerFactory progressLoggerFactory;
     private final TestLoggingContainer testLogging;
-    private final DefaultJavaForkOptions options;
+    private final DefaultJavaForkOptions forkOptions;
+    private final DefaultTestFilter filter;
 
     private TestExecuter testExecuter;
     private List<File> testSrcDirs = new ArrayList<File>();
     private File testClassesDir;
-    private File testResultsDir;
     private File binResultsDir;
-    private File testReportDir;
     private PatternFilterable patternSet = new PatternSet();
     private boolean ignoreFailures;
     private FileCollection classpath;
     private TestFramework testFramework;
-    private boolean testReport = true;
     private boolean scanForTestClasses = true;
     private long forkEvery;
     private int maxParallelForks = 1;
     private TestReporter testReporter;
 
+    @Nested
+    private final DefaultTestTaskReports reports;
+
     @Inject
     public Test(ListenerManager listenerManager, StyledTextOutputFactory textOutputFactory, FileResolver fileResolver,
                 Factory<WorkerProcessBuilder> processBuilderFactory, ActorFactory actorFactory, Instantiator instantiator,
                 ProgressLoggerFactory progressLoggerFactory) {
         this.progressLoggerFactory = progressLoggerFactory;
+        this.instantiator = instantiator;
         testListenerBroadcaster = listenerManager.createAnonymousBroadcaster(TestListener.class);
         testOutputListenerBroadcaster = listenerManager.createAnonymousBroadcaster(TestOutputListener.class);
         this.textOutputFactory = textOutputFactory;
-        options = new DefaultJavaForkOptions(fileResolver);
-        options.setEnableAssertions(true);
+        forkOptions = new DefaultJavaForkOptions(fileResolver);
+        forkOptions.setEnableAssertions(true);
         testExecuter = new DefaultTestExecuter(processBuilderFactory, actorFactory);
         testLogging = instantiator.newInstance(DefaultTestLoggingContainer.class, instantiator);
         testReporter = new DefaultTestReport();
+
+        reports = instantiator.newInstance(DefaultTestTaskReports.class, this);
+        reports.getJunitXml().setEnabled(true);
+        reports.getHtml().setEnabled(true);
+
+        filter = instantiator.newInstance(DefaultTestFilter.class);
     }
 
     /**
      * ATM. for testing only
-     * */
-    void setTestReporter(TestReporter testReporter){
+     */
+    void setTestReporter(TestReporter testReporter) {
         this.testReporter = testReporter;
     }
+
     void setTestExecuter(TestExecuter testExecuter) {
         this.testExecuter = testExecuter;
     }
@@ -164,21 +181,21 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     @Input
     public File getWorkingDir() {
-        return options.getWorkingDir();
+        return forkOptions.getWorkingDir();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setWorkingDir(Object dir) {
-        options.setWorkingDir(dir);
+        forkOptions.setWorkingDir(dir);
     }
 
     /**
      * {@inheritDoc}
      */
     public Test workingDir(Object dir) {
-        options.workingDir(dir);
+        forkOptions.workingDir(dir);
         return this;
     }
 
@@ -187,14 +204,14 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     @Input
     public String getExecutable() {
-        return options.getExecutable();
+        return forkOptions.getExecutable();
     }
 
     /**
      * {@inheritDoc}
      */
     public Test executable(Object executable) {
-        options.executable(executable);
+        forkOptions.executable(executable);
         return this;
     }
 
@@ -202,7 +219,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public void setExecutable(Object executable) {
-        options.setExecutable(executable);
+        forkOptions.setExecutable(executable);
     }
 
     /**
@@ -210,21 +227,21 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     @Input
     public Map<String, Object> getSystemProperties() {
-        return options.getSystemProperties();
+        return forkOptions.getSystemProperties();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setSystemProperties(Map<String, ?> properties) {
-        options.setSystemProperties(properties);
+        forkOptions.setSystemProperties(properties);
     }
 
     /**
      * {@inheritDoc}
      */
     public Test systemProperties(Map<String, ?> properties) {
-        options.systemProperties(properties);
+        forkOptions.systemProperties(properties);
         return this;
     }
 
@@ -232,7 +249,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public Test systemProperty(String name, Object value) {
-        options.systemProperty(name, value);
+        forkOptions.systemProperty(name, value);
         return this;
     }
 
@@ -241,21 +258,21 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     @Input
     public FileCollection getBootstrapClasspath() {
-        return options.getBootstrapClasspath();
+        return forkOptions.getBootstrapClasspath();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setBootstrapClasspath(FileCollection classpath) {
-        options.setBootstrapClasspath(classpath);
+        forkOptions.setBootstrapClasspath(classpath);
     }
 
     /**
      * {@inheritDoc}
      */
     public Test bootstrapClasspath(Object... classpath) {
-        options.bootstrapClasspath(classpath);
+        forkOptions.bootstrapClasspath(classpath);
         return this;
     }
 
@@ -263,42 +280,42 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public String getMinHeapSize() {
-        return options.getMinHeapSize();
+        return forkOptions.getMinHeapSize();
     }
 
     /**
      * {@inheritDoc}
      */
     public String getDefaultCharacterEncoding() {
-        return options.getDefaultCharacterEncoding();
+        return forkOptions.getDefaultCharacterEncoding();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
-        options.setDefaultCharacterEncoding(defaultCharacterEncoding);
+        forkOptions.setDefaultCharacterEncoding(defaultCharacterEncoding);
     }
 
     /**
      * {@inheritDoc}
      */
     public void setMinHeapSize(String heapSize) {
-        options.setMinHeapSize(heapSize);
+        forkOptions.setMinHeapSize(heapSize);
     }
 
     /**
      * {@inheritDoc}
      */
     public String getMaxHeapSize() {
-        return options.getMaxHeapSize();
+        return forkOptions.getMaxHeapSize();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setMaxHeapSize(String heapSize) {
-        options.setMaxHeapSize(heapSize);
+        forkOptions.setMaxHeapSize(heapSize);
     }
 
     /**
@@ -306,21 +323,21 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     @Input
     public List<String> getJvmArgs() {
-        return options.getJvmArgs();
+        return forkOptions.getJvmArgs();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setJvmArgs(Iterable<?> arguments) {
-        options.setJvmArgs(arguments);
+        forkOptions.setJvmArgs(arguments);
     }
 
     /**
      * {@inheritDoc}
      */
     public Test jvmArgs(Iterable<?> arguments) {
-        options.jvmArgs(arguments);
+        forkOptions.jvmArgs(arguments);
         return this;
     }
 
@@ -328,7 +345,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public Test jvmArgs(Object... arguments) {
-        options.jvmArgs(arguments);
+        forkOptions.jvmArgs(arguments);
         return this;
     }
 
@@ -337,56 +354,57 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     @Input
     public boolean getEnableAssertions() {
-        return options.getEnableAssertions();
+        return forkOptions.getEnableAssertions();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setEnableAssertions(boolean enabled) {
-        options.setEnableAssertions(enabled);
+        forkOptions.setEnableAssertions(enabled);
     }
 
     /**
      * {@inheritDoc}
      */
     public boolean getDebug() {
-        return options.getDebug();
+        return forkOptions.getDebug();
     }
 
     /**
      * {@inheritDoc}
      */
+    @Option(option = "debug-jvm", description = "Enable debugging for the test process. The process is started suspended and listening on port 5005. [INCUBATING]")
     public void setDebug(boolean enabled) {
-        options.setDebug(enabled);
+        forkOptions.setDebug(enabled);
     }
 
     /**
      * {@inheritDoc}
      */
     public List<String> getAllJvmArgs() {
-        return options.getAllJvmArgs();
+        return forkOptions.getAllJvmArgs();
     }
 
     /**
      * {@inheritDoc}
      */
     public void setAllJvmArgs(Iterable<?> arguments) {
-        options.setAllJvmArgs(arguments);
+        forkOptions.setAllJvmArgs(arguments);
     }
 
     /**
      * {@inheritDoc}
      */
     public Map<String, Object> getEnvironment() {
-        return options.getEnvironment();
+        return forkOptions.getEnvironment();
     }
 
     /**
      * {@inheritDoc}
      */
     public Test environment(Map<String, ?> environmentVariables) {
-        options.environment(environmentVariables);
+        forkOptions.environment(environmentVariables);
         return this;
     }
 
@@ -394,7 +412,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public Test environment(String name, Object value) {
-        options.environment(name, value);
+        forkOptions.environment(name, value);
         return this;
     }
 
@@ -402,14 +420,14 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public void setEnvironment(Map<String, ?> environmentVariables) {
-        options.setEnvironment(environmentVariables);
+        forkOptions.setEnvironment(environmentVariables);
     }
 
     /**
      * {@inheritDoc}
      */
     public Test copyTo(ProcessForkOptions target) {
-        options.copyTo(target);
+        forkOptions.copyTo(target);
         return this;
     }
 
@@ -417,7 +435,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     public Test copyTo(JavaForkOptions target) {
-        options.copyTo(target);
+        forkOptions.copyTo(target);
         return this;
     }
 
@@ -429,12 +447,20 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         TestEventLogger eventLogger = new TestEventLogger(textOutputFactory, currentLevel, levelLogging, exceptionFormatter);
         addTestListener(eventLogger);
         addTestOutputListener(eventLogger);
+        if (!getFilter().getIncludePatterns().isEmpty()) {
+            addTestListener(new NoMatchingTestsReporter("No tests found for given includes: " + getFilter().getIncludePatterns()));
+        }
 
         File binaryResultsDir = getBinResultsDir();
         getProject().delete(binaryResultsDir);
         getProject().mkdir(binaryResultsDir);
 
-        TestReportDataCollector testReportDataCollector = new TestReportDataCollector(binaryResultsDir);
+        Map<String, TestClassResult> results = new HashMap<String, TestClassResult>();
+        TestOutputStore testOutputStore = new TestOutputStore(binaryResultsDir);
+
+        TestOutputStore.Writer outputWriter = testOutputStore.writer();
+        TestReportDataCollector testReportDataCollector = new TestReportDataCollector(results, outputWriter);
+
         addTestListener(testReportDataCollector);
         addTestOutputListener(testReportDataCollector);
 
@@ -447,17 +473,33 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         try {
             testExecuter.execute(this, resultProcessor);
         } finally {
-            testListenerBroadcaster.removeAll(asList(eventLogger, testReportDataCollector, testCountLogger));
-            testOutputListenerBroadcaster.removeAll(asList(eventLogger, testReportDataCollector));
+            testListenerBroadcaster.removeAll();
+            testOutputListenerBroadcaster.removeAll();
+            outputWriter.close();
         }
 
-        Binary2JUnitXmlReportGenerator binary2JUnitXmlReportGenerator = new Binary2JUnitXmlReportGenerator(getTestResultsDir(), testReportDataCollector);
-        binary2JUnitXmlReportGenerator.generate();
+        new TestResultSerializer(binaryResultsDir).write(results.values());
 
-        if (!isTestReport()) {
-            getLogger().info("Test report disabled, omitting generation of the HTML test report.");
-        } else {
-            testReporter.generateReport(testReportDataCollector, getTestReportDir());
+        TestResultsProvider testResultsProvider = new InMemoryTestResultsProvider(results.values(), testOutputStore.reader());
+
+        try {
+            JUnitXmlReport junitXml = reports.getJunitXml();
+            if (junitXml.isEnabled()) {
+                TestOutputAssociation outputAssociation = junitXml.isOutputPerTestCase()
+                        ? TestOutputAssociation.WITH_TESTCASE
+                        : TestOutputAssociation.WITH_SUITE;
+                Binary2JUnitXmlReportGenerator binary2JUnitXmlReportGenerator = new Binary2JUnitXmlReportGenerator(junitXml.getDestination(), testResultsProvider, outputAssociation);
+                binary2JUnitXmlReportGenerator.generate();
+            }
+
+            DirectoryReport html = reports.getHtml();
+            if (!html.isEnabled()) {
+                getLogger().info("Test report disabled, omitting generation of the HTML test report.");
+            } else {
+                testReporter.generateReport(testResultsProvider, html.getDestination());
+            }
+        } finally {
+            CompositeStoppable.stoppable(testResultsProvider).stop();
         }
 
         testFramework = null;
@@ -643,6 +685,20 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
+     * Sets the test name patterns to be included in execution.
+     * Classes or method names are supported, wildcard '*' is supported.
+     * For more information see the user guide chapter on testing.
+     *
+     * For more information on supported patterns see {@link TestFilter}
+     */
+    @Option(option = "tests", description = "Sets test class or method name to be included, '*' is supported.")
+    @Incubating
+    public Test setTestNameIncludePattern(String testNamePattern) {
+        filter.setIncludePatterns(testNamePattern);
+        return this;
+    }
+
+    /**
      * Returns the root folder for the compiled test sources.
      *
      * @return All test class directories to be used.
@@ -664,19 +720,24 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * Returns the root folder for the test results in XML format.
      *
      * @return the test result directory, containing the test results in XML format.
+     * @deprecated Replaced by {@code getReports().getJunitXml().getDestination()}
      */
-    @OutputDirectory
+    @Deprecated
     public File getTestResultsDir() {
-        return testResultsDir;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testResultsDir", "Test.getReports().getJunitXml().getDestination()");
+        return reports.getJunitXml().getDestination();
     }
 
     /**
      * Sets the root folder for the test results.
      *
      * @param testResultsDir The root folder
+     * @deprecated Replaced by {@code getReports().getJunitXml().setDestination()}
      */
+    @Deprecated
     public void setTestResultsDir(File testResultsDir) {
-        this.testResultsDir = testResultsDir;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testResultsDir", "Test.getReports().getJunitXml().setDestination()");
+        reports.getJunitXml().setDestination(testResultsDir);
     }
 
     /**
@@ -684,7 +745,8 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      *
      * @return the test result directory, containing the test results in binary format.
      */
-    @OutputDirectory @Incubating
+    @OutputDirectory
+    @Incubating
     public File getBinResultsDir() {
         return binResultsDir;
     }
@@ -703,19 +765,24 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * Returns the root folder for the test reports.
      *
      * @return the test report directory, containing the test report mostly in HTML form.
+     * @deprecated Replaced by {@code getReports().getHtml().getDestination()}
      */
-    @OutputDirectory
+    @Deprecated
     public File getTestReportDir() {
-        return testReportDir;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testReportDir", "Test.getReports().getHtml().getDestination()");
+        return reports.getHtml().getDestination();
     }
 
     /**
      * Sets the root folder for the test reports.
      *
      * @param testReportDir The root folder
+     * @deprecated Replaced by {@code getReports().getHtml().setDestination()}
      */
+    @Deprecated
     public void setTestReportDir(File testReportDir) {
-        this.testReportDir = testReportDir;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testReportDir", "Test.getReports().getHtml().getDestination()");
+        reports.getHtml().setDestination(testReportDir);
     }
 
     /**
@@ -802,7 +869,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      */
     public TestFrameworkOptions options(Closure testFrameworkConfigure) {
         TestFrameworkOptions options = getTestFramework().getOptions();
-        ConfigureUtil.configure(testFrameworkConfigure, testFramework.getOptions());
+        ConfigureUtil.configure(testFrameworkConfigure, options);
         return options;
     }
 
@@ -825,35 +892,37 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Specifies that JUnit should be used to execute the tests.
+     * Specifies that JUnit should be used to execute the tests. <p> To configure TestNG specific options, see {@link #useJUnit(groovy.lang.Closure)}.
      */
     public void useJUnit() {
         useJUnit(null);
     }
 
     /**
-     * Specifies that JUnit should be used to execute the tests.
+     * Specifies that JUnit should be used to execute the tests, configuring JUnit specific options. <p> The supplied closure configures an instance of {@link
+     * org.gradle.api.tasks.testing.junit.JUnitOptions}, which can be used to configure how JUnit runs.
      *
-     * @param testFrameworkConfigure A closure used to configure the JUnit options. This closure is passed an instance of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
+     * @param testFrameworkConfigure A closure used to configure the JUnit options.
      */
     public void useJUnit(Closure testFrameworkConfigure) {
-        useTestFramework(new JUnitTestFramework(this), testFrameworkConfigure);
+        useTestFramework(new JUnitTestFramework(this, filter), testFrameworkConfigure);
     }
 
     /**
-     * Specifies that TestNG should be used to execute the tests.
+     * Specifies that TestNG should be used to execute the tests. <p> To configure TestNG specific options, see {@link #useTestNG(Closure)}.
      */
     public void useTestNG() {
         useTestNG(null);
     }
 
     /**
-     * Specifies that TestNG should be used to execute the tests.
+     * Specifies that TestNG should be used to execute the tests, configuring TestNG specific options. <p> The supplied closure configures an instance of {@link
+     * org.gradle.api.tasks.testing.testng.TestNGOptions}, which can be used to configure how TestNG runs.
      *
-     * @param testFrameworkConfigure A closure used to configure the TestNG options. This closure is passed an instance of type {@link org.gradle.api.tasks.testing.testng.TestNGOptions}.
+     * @param testFrameworkConfigure A closure used to configure the TestNG options.
      */
     public void useTestNG(Closure testFrameworkConfigure) {
-        useTestFramework(new TestNGTestFramework(this), testFrameworkConfigure);
+        useTestFramework(new TestNGTestFramework(this, this.filter, instantiator), testFrameworkConfigure);
     }
 
     /**
@@ -870,22 +939,48 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
 
     /**
      * Specifies whether the test HTML report should be generated.
+     *
+     * @deprecated Replaced by {@code getReports().getHtml().isEnabled()}
      */
-    @Input
+    @Deprecated
     public boolean isTestReport() {
-        return testReport;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testReport", "Test.getReports().getHtml().isEnabled()");
+        return reports.getHtml().isEnabled();
     }
 
+    /**
+     * Sets whether the test HTML report should be generated.
+     *
+     * @deprecated Replaced by {@code getReports().getHtml().setEnabled()}
+     */
+    @Deprecated
     public void setTestReport(boolean testReport) {
-        this.testReport = testReport;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testReport", "Test.getReports().getHtml().setEnabled()");
+        reports.getHtml().setEnabled(testReport);
     }
 
+    /**
+     * Enables the HTML test report.
+     *
+     * @deprecated Replaced by {@code getReports().getHtml().setEnabled()}
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    @Deprecated
     public void enableTestReport() {
-        this.testReport = true;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testReport", "Test.getReports().getHtml().setEnabled()");
+        reports.getHtml().setEnabled(true);
     }
 
+    /**
+     * Disables the HTML test report.
+     *
+     * @deprecated Replaced by {@code getReports().getHtml().setEnabled()}
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    @Deprecated
     public void disableTestReport() {
-        this.testReport = false;
+        DeprecationLogger.nagUserOfReplacedProperty("Test.testReport", "Test.getReports().getHtml().setEnabled()");
+        reports.getHtml().setEnabled(false);
     }
 
     /**
@@ -961,7 +1056,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * @return The candidate class files.
      */
     @InputFiles
-    @Input // Also marked as input to force tests to run when the set of candidate class files changes 
+    @Input
     public FileTree getCandidateClassFiles() {
         return getProject().fileTree(getTestClassesDir()).matching(patternSet);
     }
@@ -992,6 +1087,49 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         ConfigureUtil.configure(closure, testLogging);
     }
 
+    /**
+     * The reports that this task potentially produces.
+     *
+     * @return The reports that this task potentially produces
+     */
+    public TestTaskReports getReports() {
+        return reports;
+    }
+
+    /**
+     * Configures the reports that this task potentially produces.
+     *
+     * @param closure The configuration
+     * @return The reports that this task potentially produces
+     */
+    public TestTaskReports reports(Closure closure) {
+        reports.configure(closure);
+        return reports;
+    }
+
+    /**
+     * Allows filtering tests for execution.
+     *
+     * @return filter object
+     * @since 1.10
+     */
+    @Incubating
+    @Nested
+    public TestFilter getFilter() {
+        return filter;
+    }
+
+    /**
+     * Executes the action against the {@link #getFilter()}.
+     *
+     * @param action configuration of the test filter
+     * @since 1.10
+     */
+    @Incubating
+    public void filter(Action<TestFilter> action) {
+        action.execute(filter);
+    }
+
     // only way I know of to determine current log level
     private LogLevel getCurrentLogLevel() {
         for (LogLevel level : LogLevel.values()) {
@@ -1014,8 +1152,20 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     private void handleTestFailures() {
-        String reportUrl = new ConsoleRenderer().asClickableFileUrl(new File(getTestReportDir(), "index.html"));
-        String message = "There were failing tests. See the report at: " + reportUrl;
+        String message = "There were failing tests";
+
+        DirectoryReport htmlReport = reports.getHtml();
+        if (htmlReport.isEnabled()) {
+            String reportUrl = new ConsoleRenderer().asClickableFileUrl(htmlReport.getEntryPoint());
+            message = message.concat(". See the report at: " + reportUrl);
+        } else {
+            DirectoryReport junitXmlReport = reports.getJunitXml();
+            if (junitXmlReport.isEnabled()) {
+                String resultsUrl = new ConsoleRenderer().asClickableFileUrl(junitXmlReport.getEntryPoint());
+                message = message.concat(". See the results at: " + resultsUrl);
+            }
+        }
+
         if (getIgnoreFailures()) {
             getLogger().warn(message);
         } else {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java
index 005c349..ebdf2db 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java
@@ -16,10 +16,13 @@
 
 package org.gradle.api.tasks.testing;
 
+import org.gradle.internal.HasInternalProtocol;
+
 /**
  * Describes a test. A test may be a single atomic test, such as the execution of a test method, or it may be a
  * composite test, made up of zero or more tests.
  */
+ at HasInternalProtocol
 public interface TestDescriptor {
     /**
      * Returns the name of the test.  Not guaranteed to be unique.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestFilter.java
new file mode 100644
index 0000000..5babf8e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestFilter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.testing;
+
+import org.gradle.api.Incubating;
+
+import java.util.Set;
+
+/**
+ * Allows filtering tests for execution. Some examples:
+ *
+ * <pre autoTested=''>
+ *   apply plugin: 'java'
+ *
+ *   test {
+ *       filter {
+ *          //specific test method
+ *          includeTestsMatching "org.gradle.SomeTest.someSpecificFeature"
+ *
+ *          //specific test method, use wildcard for packages
+ *          includeTestsMatching "*SomeTest.someSpecificFeature"
+ *
+ *          //specific test class
+ *          includeTestsMatching "org.gradle.SomeTest"
+ *
+ *          //specific test class, wildcard for packages
+ *          includeTestsMatching "*.SomeTest"
+ *
+ *          //all classes in package, recursively
+ *          includeTestsMatching "com.gradle.tooling.*"
+ *
+ *          //all integration tests, by naming convention
+ *          includeTestsMatching "*IntegTest"
+ *
+ *          //only ui tests from integration tests, by some naming convention
+ *          includeTestsMatching "*IntegTest*ui"
+ *       }
+ *   }
+ *
+ * </pre>
+ *
+ * @since 1.10
+ */
+ at Incubating
+public interface TestFilter {
+
+    /**
+     * Appends a test name pattern to the filter. Wildcard '*' is supported,
+     * either test method name or class name is supported.
+     * Examples of test names: "com.foo.FooTest.someMethod", "com.foo.FooTest", "*FooTest*", "com.foo*".
+     * See examples in the docs for {@link TestFilter}.
+     *
+     * @param testNamePattern test name pattern to include, can be class or method name, can contain wildcard '*'
+     * @return this filter object
+     */
+    TestFilter includeTestsMatching(String testNamePattern);
+
+    /**
+     * Returns the included test name patterns. They can be class or method names and may contain wildcard '*'.
+     * Test name patterns can be appended via {@link #includeTestsMatching(String)} or set via {@link #setIncludePatterns(String...)}.
+     *
+     * @return included test name patterns
+     */
+    Set<String> getIncludePatterns();
+
+    /**
+     * Sets the test name patterns to be included in the filter. Wildcard '*' is supported.
+     * Replaces any existing test name patterns.
+     *
+     * @param testNamePatterns class or method name patterns to set, may contain wildcard '*'
+     * @return this filter object
+     */
+    TestFilter setIncludePatterns(String... testNamePatterns);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java
index 385172c..284e8b7 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java
@@ -40,13 +40,13 @@ public interface TestListener {
     void afterSuite(TestDescriptor suite, TestResult result);
 
     /**
-     * Called before a test is started.
+     * Called before an atomic test is started.
      * @param testDescriptor The test which is about to be executed.
      */
     void beforeTest(TestDescriptor testDescriptor);
 
     /**
-     * Called after a test is finished.
+     * Called after an atomic test is finished.
      * @param testDescriptor The test which has finished executing.
      * @param result The test result.
      */
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java
index e4c65d4..eb4bb84 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java
@@ -18,10 +18,12 @@ package org.gradle.api.tasks.testing;
 
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Incubating;
+import org.gradle.api.Transformer;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.file.UnionFileCollection;
 import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
 import org.gradle.api.internal.tasks.testing.junit.result.AggregateTestResultsProvider;
+import org.gradle.api.internal.tasks.testing.junit.result.BinaryResultBackedTestResultsProvider;
 import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
@@ -30,8 +32,12 @@ import org.gradle.api.tasks.TaskAction;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 
+import static org.gradle.internal.concurrent.CompositeStoppable.stoppable;
+import static org.gradle.util.CollectionUtils.collect;
+
 /**
  * Generates an HTML test report from the results of one or more {@link Test} tasks.
  */
@@ -117,8 +123,35 @@ public class TestReport extends DefaultTask {
 
     @TaskAction
     void generateReport() {
-        TestResultsProvider resultsProvider = new AggregateTestResultsProvider(getTestResultDirs().getFiles());
-        DefaultTestReport testReport = new DefaultTestReport();
-        testReport.generateReport(resultsProvider, getDestinationDir());
+        TestResultsProvider resultsProvider = createAggregateProvider();
+        try {
+            if (resultsProvider.isHasResults()) {
+                DefaultTestReport testReport = new DefaultTestReport();
+                testReport.generateReport(resultsProvider, getDestinationDir());
+            } else {
+                setDidWork(false);
+            }
+        } finally {
+            stoppable(resultsProvider).stop();
+        }
+    }
+
+    private TestResultsProvider createAggregateProvider() {
+        List<TestResultsProvider> resultsProviders = new LinkedList<TestResultsProvider>();
+        try {
+            FileCollection resultDirs = getTestResultDirs();
+            if (resultDirs.getFiles().size() == 1) {
+                return new BinaryResultBackedTestResultsProvider(resultDirs.getSingleFile());
+            } else {
+                return new AggregateTestResultsProvider(collect(resultDirs, resultsProviders, new Transformer<TestResultsProvider, File>() {
+                    public TestResultsProvider transform(File dir) {
+                        return new BinaryResultBackedTestResultsProvider(dir);
+                    }
+                }));
+            }
+        } catch (RuntimeException e) {
+            stoppable(resultsProviders).stop();
+            throw e;
+        }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestTaskReports.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestTaskReports.java
new file mode 100644
index 0000000..efb2407
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestTaskReports.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.testing;
+
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.reporting.ReportContainer;
+
+/**
+ * The reports produced by the {@link Test} task.
+ */
+public interface TestTaskReports extends ReportContainer<Report> {
+
+    /**
+     * A HTML report indicate the results of the test execution.
+     *
+     * @return The HTML report
+     */
+    DirectoryReport getHtml();
+
+    /**
+     * The test results in “JUnit XML” format.
+     *
+     * @return The test results in “JUnit XML” format
+     */
+    JUnitXmlReport getJunitXml();
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.groovy
new file mode 100644
index 0000000..64c3342
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.testing.junit
+
+import org.gradle.api.Incubating;
+import org.gradle.api.tasks.testing.TestFrameworkOptions;
+
+/**
+ * The JUnit specific test options.
+ */
+public class JUnitOptions extends TestFrameworkOptions {
+
+    /**
+     * The set of categories to run.
+     */
+    @Incubating
+    Set<String> includeCategories = new HashSet<String>();
+
+    /**
+     * The set of categories to exclude.
+     */
+    @Incubating
+    Set<String> excludeCategories = new HashSet<String>();
+
+
+    @Incubating
+    JUnitOptions includeCategories(String... includeCategories) {
+        this.includeCategories.addAll(Arrays.asList(includeCategories));
+        this;
+    }
+
+    @Incubating
+    JUnitOptions excludeCategories(String... excludeCategories) {
+        this.excludeCategories.addAll(Arrays.asList(excludeCategories));
+        this;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.java
deleted file mode 100644
index 05db5ad..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.testing.junit;
-
-import org.gradle.api.tasks.testing.TestFrameworkOptions;
-
-/**
- * The JUnit specific test options.
- */
-public class JUnitOptions extends TestFrameworkOptions {
-}
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 1067436..9a42a74 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
@@ -16,17 +16,28 @@
 package org.gradle.api.tasks.testing.testng
 
 import groovy.xml.MarkupBuilder
+import org.gradle.api.Incubating
 import org.gradle.api.JavaVersion
+import org.gradle.api.tasks.OutputDirectory
 import org.gradle.api.tasks.testing.TestFrameworkOptions
 
-/**
- * @author Tom Eyckmans
- */
-public class TestNGOptions extends TestFrameworkOptions{
+class TestNGOptions extends TestFrameworkOptions {
+
     static final String JDK_ANNOTATIONS = 'JDK'
     static final String JAVADOC_ANNOTATIONS = 'Javadoc'
 
     /**
+     * The location to write TestNG's output.
+     * <p>
+     * Defaults to the owning test task's location for writing the HTML report.
+     *
+     * @since 1.11
+     */
+    @Incubating
+    @OutputDirectory
+    File outputDirectory
+
+    /**
      * When true, Javadoc annotations are used for these tests. When false, JDK annotations are used. If you use
      * Javadoc annotations, you will also need to specify "sourcedir".
      *
@@ -64,7 +75,7 @@ public class TestNGOptions extends TestFrameworkOptions{
      *
      * test {
      *   useTestNG() {
-     *     //creates emailable html file
+     *     //creates emailable HTML file
      *     //this reporter typically ships with TestNG library
      *     listeners << 'org.testng.reporters.EmailableReporter'
      *   }
@@ -74,7 +85,7 @@ public class TestNGOptions extends TestFrameworkOptions{
     Set<String> listeners = new LinkedHashSet<String>()
 
     /**
-     * The parallel mode to use for running the tests - either methods or tests.
+     * The parallel mode to use for running the tests - one of the following modes: methods, tests, classes or instances.
      *
      * Not required.
      *
@@ -105,25 +116,25 @@ public class TestNGOptions extends TestFrameworkOptions{
      *
      *   //turn off Gradle's HTML report to avoid replacing the
      *   //reports generated by TestNG library:
-     *   testReport = false
+     *   reports.html.enabled = false
      * }
      * </pre>
      *
      * Please refer to the documentation of your version of TestNG what are the default listeners.
      * At the moment of writing this documentation, the default listeners are a set of reporters that generate:
-     * TestNG variant of html results, TestNG variant of xml results in JUnit format, emailable html test report,
-     * xml results in TestNG format.
+     * TestNG variant of HTML results, TestNG variant of XML results in JUnit format, emailable HTML test report,
+     * XML results in TestNG format.
      *
      */
     boolean useDefaultListeners = false
 
     /**
-     * Sets the default name of the test suite, if one is not specified in a suite xml file or in the source code.
+     * Sets the default name of the test suite, if one is not specified in a suite XML file or in the source code.
      */
     String suiteName = 'Gradle suite'
 
     /**
-     * Sets the default name of the test, if one is not specified in a suite xml file or in the source code.
+     * Sets the default name of the test, if one is not specified in a suite XML file or in the source code.
      */
     String testName = 'Gradle test'
 
@@ -161,10 +172,15 @@ public class TestNGOptions extends TestFrameworkOptions{
      */
     void suites(String... suiteFiles) {
         suiteFiles.each {
-            suiteXmlFiles.add(new File(projectDir, it))
+            suiteXmlFiles.add(new File(this.getProjectDir(), it))
         }
     }
 
+    //needed otherwise GRADLE-3020
+    protected File getProjectDir() {
+        return projectDir;
+    }
+
     /**
      * Add suite files by File objects.
      */
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java
deleted file mode 100644
index 8c5d3bc..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.wrapper;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.GradleException;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.plugins.StartScriptGenerator;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.util.*;
-import org.gradle.wrapper.GradleWrapperMain;
-import org.gradle.wrapper.Install;
-import org.gradle.wrapper.WrapperExecutor;
-
-import java.io.File;
-import java.net.URL;
-import java.util.Properties;
-
-/**
- * <p>Generates scripts (for *nix and windows) which allow you to build your project with Gradle, without having to
- * install Gradle.
- *
- * <p>When a user executes a wrapper script the first time, the script downloads and installs the appropriate Gradle
- * distribution and runs the build against this downloaded distribution. Any installed Gradle distribution is ignored
- * when using the wrapper scripts.
- *
- * <p>The scripts generated by this task are intended to be committed to your version control system. This task also
- * generates a small {@code gradle-wrapper.jar} bootstrap JAR file and properties file which should also be committed to
- * your VCS. The scripts delegates to this JAR.
- *
- * @author Hans Dockter
- */
-public class Wrapper extends DefaultTask {
-    public static final String DEFAULT_DISTRIBUTION_PARENT_NAME = Install.DEFAULT_DISTRIBUTION_PATH;
-
-    private String distributionUrl;
-
-    /**
-     * Specifies how the wrapper path should be interpreted.
-     */
-    public enum PathBase {
-        PROJECT, GRADLE_USER_HOME
-    }
-
-    private Object scriptFile;
-    private Object jarFile;
-
-    @Input
-    private String distributionPath;
-
-    @Input
-    private PathBase distributionBase = PathBase.GRADLE_USER_HOME;
-
-    private GradleVersion gradleVersion;
-
-    @Input
-    private String archivePath;
-
-    @Input
-    private PathBase archiveBase = PathBase.GRADLE_USER_HOME;
-
-    private final DistributionLocator locator = new DistributionLocator();
-
-    public Wrapper() {
-        scriptFile = "gradlew";
-        jarFile = "gradle/wrapper/gradle-wrapper.jar";
-        distributionPath = DEFAULT_DISTRIBUTION_PARENT_NAME;
-        archivePath = DEFAULT_DISTRIBUTION_PARENT_NAME;
-        gradleVersion = GradleVersion.current();
-    }
-
-    @TaskAction
-    void generate() {
-        File jarFileDestination = getJarFile();
-        File unixScript = getScriptFile();
-        FileResolver resolver = getServices().get(FileResolver.class).withBaseDir(unixScript.getParentFile());
-        String jarFileRelativePath = resolver.resolveAsRelativePath(jarFileDestination);
-
-        writeProperties(getPropertiesFile());
-
-        URL jarFileSource = Wrapper.class.getResource("/gradle-wrapper.jar");
-        if (jarFileSource == null) {
-            throw new GradleException("Cannot locate wrapper JAR resource.");
-        }
-        GFileUtils.copyURLToFile(jarFileSource, jarFileDestination);
-
-        StartScriptGenerator generator = new StartScriptGenerator();
-        generator.setApplicationName("Gradle");
-        generator.setMainClassName(GradleWrapperMain.class.getName());
-        generator.setClasspath(WrapUtil.toList(jarFileRelativePath));
-        generator.setOptsEnvironmentVar("GRADLE_OPTS");
-        generator.setExitEnvironmentVar("GRADLE_EXIT_CONSOLE");
-        generator.setAppNameSystemProperty("org.gradle.appname");
-        generator.setScriptRelPath(unixScript.getName());
-        generator.generateUnixScript(unixScript);
-        generator.generateWindowsScript(getBatchScript());
-    }
-
-    private void writeProperties(File propertiesFileDestination) {
-        Properties wrapperProperties = new Properties();
-        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_URL_PROPERTY, getDistributionUrl());
-        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY, distributionBase.toString());
-        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY, distributionPath);
-        wrapperProperties.put(WrapperExecutor.ZIP_STORE_BASE_PROPERTY, archiveBase.toString());
-        wrapperProperties.put(WrapperExecutor.ZIP_STORE_PATH_PROPERTY, archivePath);
-        GUtil.saveProperties(wrapperProperties, propertiesFileDestination);
-    }
-
-    /**
-     * Returns the file to write the wrapper script to.
-     */
-    @OutputFile
-    public File getScriptFile() {
-        return getProject().file(scriptFile);
-    }
-
-    public void setScriptFile(Object scriptFile) {
-        this.scriptFile = scriptFile;
-    }
-
-    /**
-     * Returns the file to write the wrapper batch script to.
-     */
-    @OutputFile
-    public File getBatchScript() {
-        File scriptFile = getScriptFile();
-        return new File(scriptFile.getParentFile(), scriptFile.getName().replaceFirst("(\\.[^\\.]+)?$", ".bat"));
-    }
-
-    /**
-     * Returns the file to write the wrapper jar file to.
-     */
-    @OutputFile
-    public File getJarFile() {
-        return getProject().file(jarFile);
-    }
-
-    public void setJarFile(Object jarFile) {
-        this.jarFile = jarFile;
-    }
-
-    /**
-     * Returns the file to write the wrapper properties to.
-     */
-    @OutputFile
-    public File getPropertiesFile() {
-        File jarFileDestination = getJarFile();
-        return new File(jarFileDestination.getParentFile(), jarFileDestination.getName().replaceAll("\\.jar$",
-                ".properties"));
-    }
-
-    /**
-     * Returns the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
-     * distribution base directory
-     *
-     * @see #setDistributionPath(String)
-     */
-    public String getDistributionPath() {
-        return distributionPath;
-    }
-
-    /**
-     * Sets the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
-     * distribution base directory
-     *
-     * @see #setDistributionPath(String)
-     */
-    public void setDistributionPath(String distributionPath) {
-        this.distributionPath = distributionPath;
-    }
-
-    /**
-     * Returns the gradle version for the wrapper.
-     *
-     * @see #setGradleVersion(String)
-     */
-    public String getGradleVersion() {
-        return gradleVersion.getVersion();
-    }
-
-    /**
-     * The version of the gradle distribution required by the wrapper. This is usually the same version of Gradle you
-     * use for building your project.
-     */
-    public void setGradleVersion(String gradleVersion) {
-        this.gradleVersion = GradleVersion.version(gradleVersion);
-    }
-
-    /**
-     * The URL to download the gradle distribution from.
-     *
-     * <p>If not set, the download URL is the default for the specified {@link #getGradleVersion()}.
-     *
-     * <p>If {@link #getGradleVersion()} is not set, will return null.
-     *
-     * <p>The wrapper downloads a certain distribution only once and caches it. If your distribution base is the
-     * project, you might submit the distribution to your version control system. That way no download is necessary at
-     * all. This might be in particular interesting, if you provide a custom gradle snapshot to the wrapper, because you
-     * don't need to provide a download server then.
-     */
-    @Input
-    public String getDistributionUrl() {
-        if (distributionUrl != null) {
-            return distributionUrl;
-        } else if (gradleVersion != null) {
-            return locator.getDistributionFor(gradleVersion).toString();
-        } else {
-            return null;
-        }
-    }
-
-    public void setDistributionUrl(String url) {
-        this.distributionUrl = url;
-    }
-
-    /**
-     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
-     * the gradle user home dir.
-     */
-    public PathBase getDistributionBase() {
-        return distributionBase;
-    }
-
-    /**
-     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
-     * the gradle user home dir.
-     */
-    public void setDistributionBase(PathBase distributionBase) {
-        this.distributionBase = distributionBase;
-    }
-
-    /**
-     * Returns the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is
-     * relative to the archive base directory.
-     */
-    public String getArchivePath() {
-        return archivePath;
-    }
-
-    /**
-     * Set's the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is relative
-     * to the parent dir specified with {@link #getArchiveBase()}.
-     */
-    public void setArchivePath(String archivePath) {
-        this.archivePath = archivePath;
-    }
-
-    /**
-     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
-     * gradle user home dir.
-     */
-    public PathBase getArchiveBase() {
-        return archiveBase;
-    }
-
-    /**
-     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
-     * gradle user home dir.
-     */
-    public void setArchiveBase(PathBase archiveBase) {
-        this.archiveBase = archiveBase;
-    }
-
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java
index fa51c72..cf49de0 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java
@@ -29,8 +29,6 @@ import java.util.List;
 
 /**
  * Provides the core Javadoc Options. That is, provides the options which are not doclet specific.
- *
- * @author Tom Eyckmans
  */
 public abstract class CoreJavadocOptions implements MinimalJavadocOptions {
     private final JavadocOptionFile optionFile;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java
index 337f955..ab06d8d 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java
@@ -18,8 +18,6 @@ package org.gradle.external.javadoc;
 
 /**
  * This enum maps to the -public, -protected, -package and -private options of the javadoc executable. 
- *
- * @author Tom Eyckmans
  */
 public enum JavadocMemberLevel {
     /**
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java
index be86bb1..e51618f 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java
@@ -20,8 +20,6 @@ package org.gradle.external.javadoc;
 /**
  * This class is used to hold the information that can be provided to the javadoc executable via the -linkoffline
  * option.
- *
- * @author Tom Eyckmans
  */
 public class JavadocOfflineLink {
     private final String extDocUrl;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOptionFileOption.java
index e824f6b..146ac56 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOptionFileOption.java
@@ -19,7 +19,6 @@ package org.gradle.external.javadoc;
 /**
  * Represents a Javadoc command-line option.
  *
- * @author Tom Eyckmans
  * @param <T> The type which this option represents.
  */
 public interface JavadocOptionFileOption<T> extends OptionLessJavadocOptionFileOption<T> {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java
index d7dcf43..742286c 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java
@@ -18,8 +18,6 @@ package org.gradle.external.javadoc;
 
 /**
  * This enum maps to the -verbose and -quiet options of the javadoc executable.
- *
- * @author Tom Eyckmans
  */
 public enum JavadocOutputLevel {
     /**
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java
index cec29b6..4a649f5 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java
@@ -27,8 +27,6 @@ import java.util.List;
 
 /**
  * Provides the core Javadoc options.
- *
- * @author Tom Eyckmans
  */
 public interface MinimalJavadocOptions {
     @Input @Optional
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/OptionLessJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/OptionLessJavadocOptionFileOption.java
index aced83b..e95b98a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/OptionLessJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/OptionLessJavadocOptionFileOption.java
@@ -23,7 +23,6 @@ import java.io.IOException;
 /**
  * Represents a Javadoc option.
  *
- * @author Tom Eyckmans
  * @param <T> The type which this option represents.
  */
 public interface OptionLessJavadocOptionFileOption<T> {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
index 26de0fa..5a78f55 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
@@ -16,17 +16,17 @@
 
 package org.gradle.external.javadoc;
 
-import org.gradle.external.javadoc.internal.JavadocOptionFile;
 import org.gradle.external.javadoc.internal.GroupsJavadocOptionFileOption;
+import org.gradle.external.javadoc.internal.JavadocOptionFile;
 import org.gradle.external.javadoc.internal.LinksOfflineJavadocOptionFileOption;
 
 import java.io.File;
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Provides the options for the standard Javadoc doclet.
- * 
- * @author Tom Eyckmans
  */
 public class StandardJavadocDocletOptions extends CoreJavadocOptions implements MinimalJavadocOptions {
 
@@ -257,7 +257,7 @@ public class StandardJavadocDocletOptions extends CoreJavadocOptions implements
     /**
      * -doctitle  title
      * Specifies the title to be placed near the top of the overview summary file. The title will be placed as a centered,
-     * level-one heading directly beneath the upper navigation bar. The title may contain html tags and white space,
+     * level-one heading directly beneath the upper navigation bar. The title may contain HTML tags and white space,
      * though if it does, it must be enclosed in quotes. Any internal quotation marks within title may have to be escaped.
      * C:> javadoc -doctitle "Java<sup><font size=\"-2\">TM</font></sup>" com.mypackage
      */
@@ -279,7 +279,7 @@ public class StandardJavadocDocletOptions extends CoreJavadocOptions implements
     /**
      * -footer  footer
      * Specifies the footer text to be placed at the bottom of each output file.
-     * The footer will be placed to the right of the lower navigation bar. footer may contain html tags and white space,
+     * The footer will be placed to the right of the lower navigation bar. footer may contain HTML tags and white space,
      * though if it does, it must be enclosed in quotes. Any internal quotation marks within footer may have to be escaped.
      */
     private final JavadocOptionFileOption<String> footer;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractJavadocOptionFileOption.java
index d269bd9..24f9185 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractJavadocOptionFileOption.java
@@ -21,7 +21,6 @@ import org.gradle.external.javadoc.JavadocOptionFileOption;
 /**
  * A base class for {@link org.gradle.external.javadoc.JavadocOptionFileOption} implementations.
  *
- * @author Tom Eyckmans
  * @param <T> The type which this option represents.
  */
 public abstract class AbstractJavadocOptionFileOption<T> implements JavadocOptionFileOption<T> {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractListJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractListJavadocOptionFileOption.java
index 5ff8a12..c56c88c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractListJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/AbstractListJavadocOptionFileOption.java
@@ -16,14 +16,13 @@
 
 package org.gradle.external.javadoc.internal;
 
-import java.util.List;
 import java.io.IOException;
+import java.util.List;
 
 /**
  * A base class for {@link org.gradle.external.javadoc.JavadocOptionFileOption} implementations whose value is a {@code List}.
  *
  * @param <T> The type which this option represents.
- * @author Tom Eyckmans
  */
 public abstract class AbstractListJavadocOptionFileOption<T extends List> extends AbstractJavadocOptionFileOption<T> {
     protected String joinBy;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOption.java
index 55ddb97..751e665 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOption.java
@@ -20,8 +20,6 @@ import java.io.IOException;
 
 /**
  * A {@link org.gradle.external.javadoc.JavadocOptionFileOption} whose value is a boolean.
- *
- * @author Tom Eyckmans
  */
 public class BooleanJavadocOptionFileOption extends AbstractJavadocOptionFileOption<Boolean> {
     protected BooleanJavadocOptionFileOption(String option) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOption.java
index 3284812..2c5ae4e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOption.java
@@ -36,7 +36,6 @@ import java.io.IOException;
 
 /**
  * @param <T> The type which this option represents.
- * @author Tom Eyckmans
  */
 public class EnumJavadocOptionFileOption<T> extends AbstractJavadocOptionFileOption<T> {
     public EnumJavadocOptionFileOption(String option) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOption.java
index 22c28d7..40ca88b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOption.java
@@ -21,8 +21,6 @@ import java.io.IOException;
 
 /**
  * A {@link org.gradle.external.javadoc.JavadocOptionFileOption} whose value is a file.
- *
- * @author Tom Eyckmans
  */
 public class FileJavadocOptionFileOption extends AbstractJavadocOptionFileOption<File> {
     protected FileJavadocOptionFileOption(String option) {
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 27f8d9c..a578647 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
@@ -26,8 +26,6 @@ import java.util.Map;
 /**
  * A {@link org.gradle.external.javadoc.JavadocOptionFileOption} which represents the -groups command line
  * option.
- *
- * @author Tom Eyckmans
  */
 public class GroupsJavadocOptionFileOption extends AbstractJavadocOptionFileOption<Map<String, List<String>>> {
     public GroupsJavadocOptionFileOption(String option) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java
index cdba757..651227a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java
@@ -19,22 +19,24 @@ package org.gradle.external.javadoc.internal;
 import org.gradle.api.GradleException;
 import org.gradle.external.javadoc.MinimalJavadocOptions;
 import org.gradle.internal.jvm.Jvm;
-import org.gradle.process.internal.DefaultExecAction;
 import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecActionFactory;
 import org.gradle.util.GUtil;
 
 import java.io.File;
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocExecHandleBuilder {
+    private final ExecActionFactory execActionFactory;
     private File execDirectory;
     private MinimalJavadocOptions options;
     private File optionsFile;
     private String executable;
 
+    public JavadocExecHandleBuilder(ExecActionFactory execActionFactory) {
+        this.execActionFactory = execActionFactory;
+    }
+
     public JavadocExecHandleBuilder execDirectory(File directory) {
         if (directory == null) {
             throw new IllegalArgumentException("execDirectory == null!");
@@ -68,10 +70,10 @@ public class JavadocExecHandleBuilder {
         try {
             options.write(optionsFile);
         } catch (IOException e) {
-            throw new GradleException("Faild to store javadoc options.", e);
+            throw new GradleException("Failed to store javadoc options.", e);
         }
 
-        ExecAction execAction = new DefaultExecAction();
+        ExecAction execAction = execActionFactory.newExecAction();
         execAction.workingDir(execDirectory);
         execAction.executable(GUtil.elvis(executable, Jvm.current().getJavadocExecutable()));
         execAction.args("@" + optionsFile.getAbsolutePath());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFile.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFile.java
index 1af9711..482f96c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFile.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFile.java
@@ -23,9 +23,6 @@ import java.io.File;
 import java.io.IOException;
 import java.util.*;
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocOptionFile {
     private final Map<String, JavadocOptionFileOption> options;
 
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 02d4761..40e5e67 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,8 +16,8 @@
 
 package org.gradle.external.javadoc.internal;
 
-import org.gradle.api.internal.ErroringAction;
-import org.gradle.api.internal.IoActions;
+import org.gradle.internal.ErroringAction;
+import org.gradle.internal.IoActions;
 import org.gradle.external.javadoc.JavadocOptionFileOption;
 
 import java.io.BufferedWriter;
@@ -26,9 +26,6 @@ import java.io.IOException;
 import java.util.Map;
 import java.util.TreeMap;
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocOptionFileWriter {
     private final JavadocOptionFile optionFile;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContext.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContext.java
index 925fbe1..0888bf7 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContext.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContext.java
@@ -17,14 +17,11 @@
 package org.gradle.external.javadoc.internal;
 
 import java.io.BufferedWriter;
-import java.io.IOException;
 import java.io.File;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Iterator;
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocOptionFileWriterContext {
     private final BufferedWriter writer;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOption.java
index 2c7f016..1eadf0e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOption.java
@@ -18,13 +18,10 @@ package org.gradle.external.javadoc.internal;
 
 import org.gradle.external.javadoc.JavadocOfflineLink;
 
-import java.util.List;
-import java.util.ArrayList;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
-/**
- * @author Tom Eyckmans
- */
 public class LinksOfflineJavadocOptionFileOption extends AbstractJavadocOptionFileOption<List<JavadocOfflineLink>> {
     public LinksOfflineJavadocOptionFileOption(String option) {
         super(option, new ArrayList<JavadocOfflineLink>());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOption.java
index 1ff9693..1309822 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOption.java
@@ -16,13 +16,10 @@
 
 package org.gradle.external.javadoc.internal;
 
-import java.util.List;
-import java.util.ArrayList;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
-/**
- * @author Melanie Pfautz
- */
 public class MultilineStringsJavadocOptionFileOption extends AbstractListJavadocOptionFileOption<List<String>> {
 
     // We should never attempt to join strings so if you see this, there's a problem
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOption.java
index 9f1cc5b..47b9e64 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOption.java
@@ -18,13 +18,10 @@ package org.gradle.external.javadoc.internal;
 
 import org.gradle.external.javadoc.OptionLessJavadocOptionFileOption;
 
-import java.util.List;
-import java.util.ArrayList;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
-/**
- * @author Tom Eyckmans
- */
 public class OptionLessStringsJavadocOptionFileOption implements OptionLessJavadocOptionFileOption<List<String>> {
     private List<String> value;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOption.java
index 599248c..d34d799 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOption.java
@@ -18,12 +18,9 @@ package org.gradle.external.javadoc.internal;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.List;
 import java.util.ArrayList;
+import java.util.List;
 
-/**
- * @author Tom Eyckmans
- */
 public class PathJavadocOptionFileOption extends AbstractListJavadocOptionFileOption<List<File>> {
 
     public PathJavadocOptionFileOption(String option, String joinBy) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOption.java
index 4b4cecc..e96c9db 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOption.java
@@ -18,9 +18,6 @@ package org.gradle.external.javadoc.internal;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class StringJavadocOptionFileOption extends AbstractJavadocOptionFileOption<String> {
     public StringJavadocOptionFileOption(String option) {
         super(option);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOption.java
index bb60153..daa28ed 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOption.java
@@ -16,13 +16,10 @@
 
 package org.gradle.external.javadoc.internal;
 
-import java.util.List;
-import java.util.ArrayList;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
-/**
- * @author Tom Eyckmans
- */
 public class StringsJavadocOptionFileOption extends AbstractListJavadocOptionFileOption<List<String>> {
     protected StringsJavadocOptionFileOption(String option, String joinBy) {
         super(option, new ArrayList<String>(), joinBy);
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties
index ca08f4c..6a30765 100644
--- a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties
@@ -1 +1 @@
-implementation-class=org.gradle.api.plugins.JvmLanguagePlugin
+implementation-class=org.gradle.language.jvm.plugins.JvmLanguagePlugin
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties
index 7507f57..c8d282e 100644
--- a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties
@@ -1 +1 @@
-implementation-class=org.gradle.api.plugins.LanguageBasePlugin
+implementation-class=org.gradle.language.base.plugins.LanguageBasePlugin
diff --git a/subprojects/plugins/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry b/subprojects/plugins/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
new file mode 100644
index 0000000..5eeadcc
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
@@ -0,0 +1 @@
+org.gradle.api.internal.tasks.CompileServices
\ 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
old mode 100644
new mode 100755
index 8732f7f..bec72be
--- 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
@@ -7,7 +7,7 @@
 ##############################################################################
 
 # Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+DEFAULT_JVM_OPTS=${defaultJvmOpts}
 
 APP_NAME="${applicationName}"
 APP_BASE_NAME=`basename "\$0"`
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
index 7e034c0..03f2bac 100644
--- a/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
+++ b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
@@ -9,7 +9,7 @@
 if "%OS%"=="Windows_NT" setlocal
 
 @rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS=${defaultJvmOpts}
 
 set DIRNAME=%~dp0
 if "%DIRNAME%" == "" set DIRNAME=.\
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css b/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css
index 2440a1f..346dff8 100644
--- a/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css
+++ b/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css
@@ -41,7 +41,7 @@
     border: solid 2px #d0d0d0;
     -moz-border-radius: 10px;
     border-radius: 10px;
-    behavior: url(css3-pie-1.0beta3.htc);
+    behavior: url(htc/css3-pie-1.0beta3.htc);
 }
 
 #successRate {
@@ -66,6 +66,10 @@ div.success, #successRate.success {
     color: #b60808;
 }
 
+.skipped, .skipped a {
+    color: #c09853;
+}
+
 div.failures, #successRate.failures {
     background-color: #ecdada;
     border-color: #b60808;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/distribution/plugins/DistributionPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/distribution/plugins/DistributionPluginTest.groovy
index aed4ace..33c2149 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/distribution/plugins/DistributionPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/distribution/plugins/DistributionPluginTest.groovy
@@ -21,11 +21,11 @@ import org.gradle.api.distribution.DistributionContainer
 import org.gradle.api.tasks.Sync
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.bundling.Tar
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class DistributionPluginTest extends Specification {
-    private final Project project = HelperUtil.builder().withName("test-project").build()
+    private final Project project = TestUtil.builder().withName("test-project").build()
 
     def "adds convention object and a main distribution"() {
         when:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy
index 708f46f..e327936 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy
@@ -70,4 +70,103 @@ class StartScriptGeneratorTest extends Specification {
         then:
         windowsScriptContent.split(TextUtil.windowsLineSeparator).length == 90
     }
+
+    def "defaultJvmOpts is expanded properly in windows script"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=bar', '-Xint']
+        generator.scriptRelPath = "bin"
+        when:
+        String windowsScriptContent = generator.generateWindowsScriptContent()
+        then:
+        windowsScriptContent.contains('set DEFAULT_JVM_OPTS="-Dfoo=bar" "-Xint"')
+    }
+
+    def "defaultJvmOpts is expanded properly in windows script -- spaces"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=bar baz', '-Xint']
+        generator.scriptRelPath = "bin"
+        when:
+        String windowsScriptContent = generator.generateWindowsScriptContent()
+        then:
+        windowsScriptContent.contains(/set DEFAULT_JVM_OPTS="-Dfoo=bar baz" "-Xint"/)
+    }
+
+    def "defaultJvmOpts is expanded properly in windows script -- double quotes"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=b"ar baz', '-Xi""nt', '-Xpatho\\"logical']
+        generator.scriptRelPath = "bin"
+        when:
+        String windowsScriptContent = generator.generateWindowsScriptContent()
+        then:
+        windowsScriptContent.contains(/set DEFAULT_JVM_OPTS="-Dfoo=b\"ar baz" "-Xi\"\"nt" "-Xpatho\\\"logical"/)
+    }
+
+    def "defaultJvmOpts is expanded properly in windows script -- backslashes and shell metacharacters"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=b\\ar baz', '-Xint%PATH%']
+        generator.scriptRelPath = "bin"
+        when:
+        String windowsScriptContent = generator.generateWindowsScriptContent()
+        then:
+        windowsScriptContent.contains(/set DEFAULT_JVM_OPTS="-Dfoo=b\ar baz" "-Xint%%PATH%%"/)
+    }
+
+    def "defaultJvmOpts is expanded properly in unix script"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=bar', '-Xint']
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains('DEFAULT_JVM_OPTS=\'"-Dfoo=bar" "-Xint"\'')
+    }
+
+    def "defaultJvmOpts is expanded properly in unix script -- spaces"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=bar baz', '-Xint']
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains(/DEFAULT_JVM_OPTS='"-Dfoo=bar baz" "-Xint"'/)
+    }
+
+    def "defaultJvmOpts is expanded properly in unix script -- double quotes"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=b"ar baz', '-Xi""nt']
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains(/DEFAULT_JVM_OPTS='"-Dfoo=b\"ar baz" "-Xi\"\"nt"'/)
+    }
+
+    def "defaultJvmOpts is expanded properly in unix script -- single quotes"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=b\'ar baz', '-Xi\'\'n`t']
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains(/DEFAULT_JVM_OPTS='"-Dfoo=b'"'"'ar baz" "-Xi'"'"''"'"'n'"`"'t"'/)
+    }
+
+    def "defaultJvmOpts is expanded properly in unix script -- backslashes and shell metacharacters"() {
+        given:
+        generator.defaultJvmOpts = ['-Dfoo=b\\ar baz', '-Xint$PATH']
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains(/DEFAULT_JVM_OPTS='"-Dfoo=b\\ar baz" "-Xint/ + '\\$PATH' + /"'/)
+    }
+
+    def "defaultJvmOpts is expanded properly in unix script -- empty list"() {
+        given:
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains(/DEFAULT_JVM_OPTS=""/)
+    }
 }
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 b00bc1e..72f69d2 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
@@ -37,7 +37,7 @@ class DefaultSourceSetTest {
     @Test
     public void hasUsefulDisplayName() {
         SourceSet sourceSet = sourceSet('int-test')
-        assertThat(sourceSet.toString(), equalTo('source set int test'));
+        assertThat(sourceSet.toString(), equalTo("source set 'int test'"));
     }
 
     @Test public void defaultValues() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompilerTest.groovy
new file mode 100644
index 0000000..0bcd196
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CleaningJavaCompilerTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.compile
+
+import org.gradle.language.jvm.internal.StaleClassCleaner
+import spock.lang.Specification
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.file.FileCollection
+
+class CleaningJavaCompilerTest extends Specification {
+    private final Compiler<JavaCompileSpec> target = Mock()
+    private final JavaCompileSpec spec = Mock()
+    private final StaleClassCleaner cleaner = Mock()
+    private final CleaningJavaCompilerSupport<JavaCompileSpec> compiler = new CleaningJavaCompilerSupport<JavaCompileSpec>() {
+        @Override
+        protected Compiler<JavaCompileSpec> getCompiler() {
+            return target
+        }
+
+        protected StaleClassCleaner createCleaner(JavaCompileSpec spec) {
+            return cleaner
+        }
+    }
+    
+    def cleansStaleClassesAndThenInvokesCompiler() {
+        WorkResult result = Mock()
+        File destDir = new File('dest')
+        FileCollection source = Mock()
+        _ * spec.destinationDir >> destDir
+        _ * spec.source >> source
+
+        when:
+        def r = compiler.execute(spec)
+
+        then:
+        r == result
+
+        and:
+        1 * cleaner.setDestinationDir(destDir)
+        1 * cleaner.setSource(source)
+
+        and:
+        1 * cleaner.execute()
+
+        and:
+        1 * target.execute(spec) >> result
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy
index dcc670e..51393bd 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy
@@ -16,9 +16,9 @@
 package org.gradle.api.internal.tasks.compile
 
 import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager
 import org.gradle.api.internal.tasks.compile.daemon.DaemonJavaCompiler
 import org.gradle.api.tasks.compile.CompileOptions
-import org.gradle.api.internal.file.TemporaryFileProvider
 import org.gradle.internal.Factory
 
 import spock.lang.Specification
@@ -26,7 +26,7 @@ import spock.lang.Specification
 class DefaultJavaCompilerFactoryTest extends Specification {
     def inProcessCompiler = Mock(Compiler)
     def inProcessCompilerFactory = Mock(JavaCompilerFactory)
-    def factory = new DefaultJavaCompilerFactory(Mock(ProjectInternal), Mock(TemporaryFileProvider), Mock(Factory), inProcessCompilerFactory)
+    def factory = new DefaultJavaCompilerFactory(Mock(ProjectInternal), Mock(Factory), inProcessCompilerFactory, Mock(CompilerDaemonManager))
     def options = new CompileOptions()
     
     def setup() {
@@ -67,7 +67,6 @@ class DefaultJavaCompilerFactoryTest extends Specification {
         options.forkOptions.executable = "/path/to/javac"
 
         expect:
-        expect:
         def compiler = factory.create(options)
         compiler instanceof NormalizingJavaCompiler
         compiler.delegate instanceof CommandLineJavaCompiler
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileTransformingClassLoaderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileTransformingClassLoaderTest.groovy
new file mode 100644
index 0000000..d51aa95
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileTransformingClassLoaderTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.compile
+
+import spock.lang.Specification
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+import org.gradle.internal.classloader.ClasspathUtil
+import org.gradle.internal.classpath.DefaultClassPath
+
+class GroovyCompileTransformingClassLoaderTest extends Specification {
+    GroovyCompileTransformingClassLoader loader
+    Class<?> classAnnotation
+
+    def setup() {
+        def classPath = new DefaultClassPath(ClasspathUtil.getClasspathForClass(getClass()), ClasspathUtil.getClasspathForClass(GroovyASTTransformationClass))
+        loader = new GroovyCompileTransformingClassLoader(classPath)
+        classAnnotation = loader.loadClass(GroovyASTTransformationClass.name)
+    }
+
+    def "loads class annotated with transformer name"() {
+        expect:
+        def cl = loader.loadClass(WithNameSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == ['some-type'] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer names"() {
+        expect:
+        def cl = loader.loadClass(WithNamesSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == ['some-type', 'some-other-type'] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer class"() {
+        expect:
+        def cl = loader.loadClass(WithClassSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == [Transformer.name] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer classes"() {
+        expect:
+        def cl = loader.loadClass(WithClassesSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == [Transformer.name, Runnable.name] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer names and classes"() {
+        expect:
+        def cl = loader.loadClass(WithBothSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() as Set == ["some-type", Transformer.name, Runnable.name] as Set
+        annotation.classes() == [] as Class[]
+    }
+
+    static class Transformer {
+    }
+}
+
+ at GroovyASTTransformationClass("some-type")
+ at interface WithNameSpecified {
+}
+
+ at GroovyASTTransformationClass(["some-type", "some-other-type"])
+ at interface WithNamesSpecified {
+}
+
+ at GroovyASTTransformationClass(classes = [GroovyCompileTransformingClassLoaderTest.Transformer])
+ at interface WithClassSpecified {
+}
+
+ at GroovyASTTransformationClass(classes = [GroovyCompileTransformingClassLoaderTest.Transformer, Runnable])
+ at interface WithClassesSpecified {
+}
+
+ at GroovyASTTransformationClass(value = "some-type", classes = [GroovyCompileTransformingClassLoaderTest.Transformer, Runnable])
+ at interface WithBothSpecified {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy
index 799a18f..c164608 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy
@@ -25,7 +25,7 @@ class InProcessJavaCompilerFactoryTest extends Specification {
     def factory = new InProcessJavaCompilerFactory()
     def options = new CompileOptions()
     
-    @Requires(TestPrecondition.JDK6)
+    @Requires(TestPrecondition.JDK6_OR_LATER)
     def "creates JDK 6 compiler on JDK 6"() {
         expect:
         factory.create(options).getClass().name == "org.gradle.api.internal.tasks.compile.jdk6.Jdk6JavaCompiler"
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerTest.groovy
deleted file mode 100644
index d39407a..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerTest.groovy
+++ /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.internal.tasks.compile
-
-import spock.lang.Specification
-import org.gradle.api.tasks.WorkResult
-import org.gradle.api.file.FileCollection
-
-class IncrementalJavaCompilerTest extends Specification {
-    private final Compiler<JavaCompileSpec> target = Mock()
-    private final JavaCompileSpec spec = Mock()
-    private final StaleClassCleaner cleaner = Mock()
-    private final IncrementalJavaCompilerSupport<JavaCompileSpec> compiler = new IncrementalJavaCompilerSupport<JavaCompileSpec>() {
-        @Override
-        protected Compiler<JavaCompileSpec> getCompiler() {
-            return target
-        }
-
-        protected StaleClassCleaner createCleaner(JavaCompileSpec spec) {
-            return cleaner
-        }
-    }
-    
-    def cleansStaleClassesAndThenInvokesCompiler() {
-        WorkResult result = Mock()
-        File destDir = new File('dest')
-        FileCollection source = Mock()
-        _ * spec.destinationDir >> destDir
-        _ * spec.source >> source
-
-        when:
-        def r = compiler.execute(spec)
-
-        then:
-        r == result
-
-        and:
-        1 * cleaner.setDestinationDir(destDir)
-        1 * cleaner.setSource(source)
-
-        and:
-        1 * cleaner.execute()
-
-        and:
-        1 * target.execute(spec) >> result
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
index 9c7feb2..67edfff 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.api.internal.tasks.compile
 
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.language.jvm.internal.SimpleStaleClassCleaner
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
@@ -38,6 +39,9 @@ class SimpleStaleClassCleanerTest extends Specification {
         !file1.exists()
         !file2.exists()
         1 * outputs.previousFiles >> { [iterator: { [file1, file2].iterator() }] as FileCollection }
+
+        and:
+        cleaner.didWork
     }
 
     def doesNotDeleteFilesWhichAreNotUnderTheDestinationDir() {
@@ -53,5 +57,21 @@ class SimpleStaleClassCleanerTest extends Specification {
         !file1.exists()
         file2.exists()
         1 * outputs.previousFiles >> { [iterator: { [file1, file2].iterator() }] as FileCollection }
+
+        and:
+        cleaner.didWork
+    }
+
+    def reportsWhenNoWorkDone() {
+        cleaner.destinationDir = tmpDir.file('dir')
+
+        when:
+        cleaner.execute()
+
+        then:
+        1 * outputs.previousFiles >> { [iterator: { [].iterator() }] as FileCollection }
+
+        and:
+        !cleaner.didWork
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
deleted file mode 100644
index 6213475..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
+++ /dev/null
@@ -1,96 +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.tasks.compile
-
-import spock.lang.Specification
-import org.codehaus.groovy.transform.GroovyASTTransformationClass
-import org.gradle.util.ClasspathUtil
-import org.gradle.internal.classpath.DefaultClassPath
-
-class TransformingClassLoaderTest extends Specification {
-    TransformingClassLoader loader
-    Class<?> classAnnotation
-
-    def setup() {
-        def classPath = new DefaultClassPath(ClasspathUtil.getClasspathForClass(getClass()), ClasspathUtil.getClasspathForClass(GroovyASTTransformationClass))
-        loader = new TransformingClassLoader(classPath)
-        classAnnotation = loader.loadClass(GroovyASTTransformationClass.name)
-    }
-
-    def "loads class annotated with transformer name"() {
-        expect:
-        def cl = loader.loadClass(WithNameSpecified.name)
-        def annotation = cl.getAnnotation(classAnnotation)
-        annotation.value() == ['some-type'] as String[]
-        annotation.classes() == [] as Class[]
-    }
-
-    def "loads class annotated with transformer names"() {
-        expect:
-        def cl = loader.loadClass(WithNamesSpecified.name)
-        def annotation = cl.getAnnotation(classAnnotation)
-        annotation.value() == ['some-type', 'some-other-type'] as String[]
-        annotation.classes() == [] as Class[]
-    }
-
-    def "loads class annotated with transformer class"() {
-        expect:
-        def cl = loader.loadClass(WithClassSpecified.name)
-        def annotation = cl.getAnnotation(classAnnotation)
-        annotation.value() == [Transformer.name] as String[]
-        annotation.classes() == [] as Class[]
-    }
-
-    def "loads class annotated with transformer classes"() {
-        expect:
-        def cl = loader.loadClass(WithClassesSpecified.name)
-        def annotation = cl.getAnnotation(classAnnotation)
-        annotation.value() == [Transformer.name, Runnable.name] as String[]
-        annotation.classes() == [] as Class[]
-    }
-
-    def "loads class annotated with transformer names and classes"() {
-        expect:
-        def cl = loader.loadClass(WithBothSpecified.name)
-        def annotation = cl.getAnnotation(classAnnotation)
-        annotation.value() as Set == ["some-type", Transformer.name, Runnable.name] as Set
-        annotation.classes() == [] as Class[]
-    }
-
-    static class Transformer {
-    }
-}
-
- at GroovyASTTransformationClass("some-type")
- at interface WithNameSpecified {
-}
-
- at GroovyASTTransformationClass(["some-type", "some-other-type"])
- at interface WithNamesSpecified {
-}
-
- at GroovyASTTransformationClass(classes = [TransformingClassLoaderTest.Transformer])
- at interface WithClassSpecified {
-}
-
- at GroovyASTTransformationClass(classes = [TransformingClassLoaderTest.Transformer, Runnable])
- at interface WithClassesSpecified {
-}
-
- at GroovyASTTransformationClass(value = "some-type", classes = [TransformingClassLoaderTest.Transformer, Runnable])
- at interface WithBothSpecified {
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerClientsManagerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerClientsManagerTest.groovy
new file mode 100644
index 0000000..290711b
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerClientsManagerTest.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.internal.tasks.compile.daemon
+
+import org.gradle.util.ConcurrentSpecification
+import spock.lang.Subject
+
+class CompilerClientsManagerTest extends ConcurrentSpecification {
+
+    def workingDir = new File("some-dir")
+
+    def options = Stub(DaemonForkOptions)
+    def starter = Stub(CompilerDaemonStarter)
+
+    @Subject manager = new CompilerClientsManager(starter)
+
+    def "does not reserve idle client when no clients"() {
+        expect:
+        manager.reserveIdleClient(options) == null
+    }
+
+    def "does not reserve idle client when no matching client found"() {
+        def noMatch = Stub(CompilerDaemonClient) {
+            isCompatibleWith(_) >> false
+        }
+
+        expect:
+        manager.reserveIdleClient(options, [noMatch]) == null
+    }
+
+    def "reserves idle client when match found"() {
+        def noMatch = Stub(CompilerDaemonClient) { isCompatibleWith(_) >> false }
+        def match = Stub(CompilerDaemonClient) { isCompatibleWith(_) >> true }
+        def input = [noMatch, match]
+
+        expect:
+        manager.reserveIdleClient(options, input) == match
+        input == [noMatch] //match removed from input
+    }
+
+    def "reserves new client"() {
+        def newClient = Stub(CompilerDaemonClient)
+        starter.startDaemon(workingDir, options) >> newClient
+
+        when:
+        def client = manager.reserveNewClient(workingDir, options)
+
+        then:
+        newClient == client
+    }
+
+    def "can stop all created clients"() {
+        def client1 = Mock(CompilerDaemonClient)
+        def client2 = Mock(CompilerDaemonClient)
+        starter.startDaemon(workingDir, options) >>> [client1, client2]
+
+        when:
+        manager.reserveNewClient(workingDir, options)
+        manager.reserveNewClient(workingDir, options)
+        manager.stop()
+
+        then:
+        1 * client1.stop()
+        1 * client2.stop()
+    }
+
+    def "clients can be released for further use"() {
+        def client = Mock(CompilerDaemonClient) { isCompatibleWith(_) >> true }
+        starter.startDaemon(workingDir, options) >> client
+
+        when:
+        manager.reserveNewClient(workingDir, options)
+
+        then:
+        manager.reserveIdleClient(options) == null
+
+        when:
+        manager.release(client)
+
+        then:
+        manager.reserveIdleClient(options) == client
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManagerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManagerTest.groovy
new file mode 100644
index 0000000..1f462bf
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManagerTest.groovy
@@ -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.internal.tasks.compile.daemon
+
+import org.gradle.api.internal.tasks.compile.CompileSpec
+import org.gradle.api.internal.tasks.compile.Compiler
+import spock.lang.Specification
+import spock.lang.Subject
+
+class CompilerDaemonManagerTest extends Specification {
+
+    def clientsManager = Mock(CompilerClientsManager)
+    def client = Mock(CompilerDaemonClient)
+
+    @Subject manager = new CompilerDaemonManager(clientsManager)
+
+    def workingDir = new File("some-dir")
+    def compiler = Stub(Compiler)
+    def options = Stub(DaemonForkOptions)
+    def compileSpec = Stub(CompileSpec)
+
+    def "getting a compiler daemon does not assume client use"() {
+        when:
+        manager.getDaemon(workingDir, options);
+
+        then:
+        0 * clientsManager._
+    }
+
+    def "new client is created when daemon is executed and no idle clients found"() {
+        when:
+        manager.getDaemon(workingDir, options).execute(compiler, compileSpec)
+
+        then:
+        1 * clientsManager.reserveIdleClient(options) >> null
+
+        then:
+        1 * clientsManager.reserveNewClient(workingDir, options) >> client
+
+        then:
+        1 * client.execute(compiler, compileSpec)
+
+        then:
+        1 * clientsManager.release(client)
+        0 * _._
+    }
+
+    def "idle client is reused when daemon is executed"() {
+        when:
+        manager.getDaemon(workingDir, options).execute(compiler, compileSpec)
+
+        then:
+        1 * clientsManager.reserveIdleClient(options) >> client
+
+        then:
+        1 * client.execute(compiler, compileSpec)
+
+        then:
+        1 * clientsManager.release(client)
+        0 * _._
+    }
+
+    def "client is released even if execution fails"() {
+        when:
+        manager.getDaemon(workingDir, options).execute(compiler, compileSpec)
+
+        then:
+        1 * clientsManager.reserveIdleClient(options) >> client
+
+        then:
+        1 * client.execute(compiler, compileSpec) >> { throw new RuntimeException("Boo!") }
+
+        then:
+        thrown(RuntimeException)
+        1 * clientsManager.release(client)
+        0 * _._
+    }
+
+    def "stops clients"() {
+        when:
+        manager.stop()
+
+        then:
+        clientsManager.stop()
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/AllFromJarRebuildInfoTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/AllFromJarRebuildInfoTest.groovy
new file mode 100644
index 0000000..b7aa720
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/AllFromJarRebuildInfoTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.api.internal.file.collections.DirectoryFileTree
+import org.gradle.api.internal.file.collections.FileTreeAdapter
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class AllFromJarRebuildInfoTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def "empty jar"() {
+        def classes = new FileTreeAdapter(new DirectoryFileTree(new File("missing")))
+
+        expect:
+        new AllFromJarRebuildInfo(new JarArchive(new File("j"), classes)).changedClassesInJar.isEmpty()
+    }
+
+    def "contains all classes"() {
+        temp.createFile("root/com/foo/Foo.class")
+        temp.createFile("root/com/Bar.class")
+        def classes = new FileTreeAdapter(new DirectoryFileTree(temp.file("root")))
+
+        expect:
+        new AllFromJarRebuildInfo(new JarArchive(new File("j"), classes)).changedClassesInJar == ["com.foo.Foo", "com.Bar"] as Set
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassNameProviderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassNameProviderTest.groovy
new file mode 100644
index 0000000..1ad12ab
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/ClassNameProviderTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Subject
+
+class ClassNameProviderTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    @Subject provider = new ClassNameProvider(temp.createDir("root/dir"))
+
+    def "provides class name"() {
+        expect:
+        "foo.bar.Foo" == provider.provideName(temp.file("root/dir/foo/bar/Foo.class"))
+        "Foo" == provider.provideName(temp.file("root/dir/Foo.class"))
+        'Foo$Bar' == provider.provideName(temp.file('root/dir/Foo$Bar.class'))
+    }
+
+    def "fails when class is outside of root"() {
+        when:
+        provider.provideName(temp.file("foo/Foo.class"))
+        then:
+        thrown(IllegalArgumentException)
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationSupportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationSupportTest.groovy
new file mode 100644
index 0000000..f77f7ab
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationSupportTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfo
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoExtractor
+import org.gradle.api.internal.tasks.compile.incremental.graph.ClassDependencyInfoSerializer
+import org.gradle.api.tasks.compile.CompileOptions
+import spock.lang.Specification
+import spock.lang.Subject
+
+class IncrementalCompilationSupportTest extends Specification {
+
+    def options = Mock(CompileOptions)
+    def extractor = Mock(ClassDependencyInfoExtractor)
+    def serializer = Mock(ClassDependencyInfoSerializer)
+    def feeder = Mock(JarSnapshotFeeder)
+
+    @Subject support = new IncrementalCompilationSupport(feeder)
+
+    def "analyzes class dependencies when incremental"() {
+        options.incremental >> true
+        def jars = [Mock(JarArchive)]
+
+        when: support.compilationComplete(options, extractor, serializer, jars)
+        then:
+        1 * extractor.extractInfo("") >> Stub(ClassDependencyInfo)
+        1 * serializer.writeInfo(_ as ClassDependencyInfo)
+//        1 * feeder.storeJarSnapshots(jars)
+    }
+
+    def "does nothing when not incremental"() {
+        options.incremental >> false
+
+        when: support.compilationComplete(options, extractor, serializer, [])
+        then: 0 * extractor._
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/InputOutputMapperTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/InputOutputMapperTest.groovy
new file mode 100644
index 0000000..c3d8354
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/InputOutputMapperTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Subject
+
+class InputOutputMapperTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    @Subject mapper = new InputOutputMapper([temp.file("src/main/java"), temp.file("src/main/java2")], temp.file("out"))
+
+    def "knows java source class relative path"() {
+        expect:
+        mapper.toJavaSourceClass(temp.file("src/main/java/Foo.java")).relativePath == "Foo.java"
+        mapper.toJavaSourceClass(temp.file("src/main/java/org/bar/Bar.java")).relativePath == "org/bar/Bar.java"
+        mapper.toJavaSourceClass(temp.file("src/main/java2/com/Com.java")).relativePath == "com/Com.java"
+
+        when: mapper.toJavaSourceClass(temp.file("src/main/unknown/Xxx.java"))
+        then: thrown(IllegalArgumentException)
+    }
+
+    def "infers java source class from name"() {
+        temp.createFile("src/main/java/Foo.java")
+        temp.createFile("src/main/java/org/bar/Bar.java")
+        temp.createFile("src/main/java2/com/Com.java")
+        temp.createFile("src/main/unknown/Xxx.java")
+
+        expect:
+        mapper.toJavaSourceClass("Foo").relativePath == "Foo.java"
+        mapper.toJavaSourceClass("org.bar.Bar").relativePath == "org/bar/Bar.java"
+        mapper.toJavaSourceClass("com.Com").relativePath == "com/Com.java"
+
+        when: mapper.toJavaSourceClass(temp.file("unknown.Xxx"))
+        then: thrown(IllegalArgumentException)
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotCacheTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotCacheTest.groovy
new file mode 100644
index 0000000..2811a5c
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotCacheTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Subject
+
+class JarSnapshotCacheTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    @Subject cache = new JarSnapshotCache(temp.file("cache.bin"))
+
+    def "empty cache"() {
+        expect:
+        !cache.getSnapshot(new File("foo.jar"))
+    }
+
+    def "caches snapshots"() {
+        when:
+        cache.putSnapshots([(new File("foo.jar")): new JarSnapshot(["Foo": "f".bytes])])
+
+        then:
+        cache.getSnapshot(new File("foo.jar")).classHashes == ["Foo": "f".bytes]
+    }
+
+    def "caches snapshots in file"() {
+        when:
+        cache.putSnapshots([(new File("foo.jar")): new JarSnapshot(["Foo": "f".bytes])])
+
+        then:
+        def cache2 = new JarSnapshotCache(temp.file("cache.bin"))
+        cache2.getSnapshot(new File("foo.jar")).classHashes == ["Foo": "f".bytes]
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotFeederTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotFeederTest.groovy
new file mode 100644
index 0000000..4e7d139
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotFeederTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.api.file.FileTree
+import spock.lang.Specification
+import spock.lang.Subject
+
+class JarSnapshotFeederTest extends Specification {
+
+    def cache = Mock(JarSnapshotCache)
+    def snapshotter = Mock(JarSnapshotter)
+
+    @Subject feeder = new JarSnapshotFeeder(cache, snapshotter)
+
+    def "stores jar snapshot"() {
+        def jar1 = new JarArchive(new File("jar1.jar"), Mock(FileTree))
+        def snapshot = Mock(JarSnapshot)
+
+        when:
+        feeder.storeJarSnapshots([jar1])
+
+        then:
+        1 * cache.getSnapshot(jar1.file)
+        1 * snapshotter.createSnapshot(jar1.contents) >> snapshot
+        1 * cache.putSnapshots([(jar1.file): snapshot])
+        0 * _
+    }
+
+    def "stores multiple snapshots"() {
+        def jar1 = new JarArchive(new File("jar1.jar"), Mock(FileTree))
+        def jar2 = new JarArchive(new File("jar2.jar"), Mock(FileTree))
+
+        when:
+        feeder.storeJarSnapshots([jar1, jar2])
+
+        then:
+        1 * snapshotter.createSnapshot(jar1.contents) >> Mock(JarSnapshot)
+        1 * snapshotter.createSnapshot(jar2.contents) >> Mock(JarSnapshot)
+        1 * cache.putSnapshots({ it.size() == 2})
+    }
+
+    def "avoids storing unchanged jar snapshots"() {
+        def jar1 = new JarArchive(new File("jar1.jar"), Mock(FileTree))
+        def jar2 = new JarArchive(new File("jar2.jar"), Mock(FileTree))
+
+        when:
+        feeder.changedJar(jar2.file)
+        feeder.storeJarSnapshots([jar1, jar2])
+
+        then:
+        1 * cache.getSnapshot(jar1.file) >> Mock(JarSnapshot)
+        1 * cache.getSnapshot(jar2.file) >> Mock(JarSnapshot)
+        1 * snapshotter.createSnapshot(jar2.contents) >> Mock(JarSnapshot)
+        1 * cache.putSnapshots({ it[jar2.file] })
+        0 * _
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotTest.groovy
new file mode 100644
index 0000000..136cbc7
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import spock.lang.Specification
+
+class JarSnapshotTest extends Specification {
+
+    def "knows when snapshots are the same"() {
+        JarSnapshot s1 = new JarSnapshot(["com.Foo": "f".bytes, "Bar": "b".bytes])
+        JarSnapshot s2 = new JarSnapshot(["com.Foo": "f".bytes, "Bar": "b".bytes])
+
+        expect:
+        s1.compareToSnapshot(s2).changedClasses.isEmpty()
+        s2.compareToSnapshot(s1).changedClasses.isEmpty()
+    }
+
+    def "knows when other snapshots have extra/missing classes"() {
+        JarSnapshot s1 = new JarSnapshot(["com.Foo": "f".bytes, "Bar": "b".bytes, "Car": "c".bytes])
+        JarSnapshot s2 = new JarSnapshot(["com.Foo": "f".bytes])
+
+        expect:
+        s1.compareToSnapshot(s2).changedClasses == ["Bar", "Car"]
+        s2.compareToSnapshot(s1).changedClasses == [] //ignore class additions
+    }
+
+    def "knows when other snapshots have class with different hash"() {
+        JarSnapshot s1 = new JarSnapshot(["com.Foo": "f".bytes, "Bar": "b".bytes, "Car": "c".bytes])
+        JarSnapshot s2 = new JarSnapshot(["Car": "xxx".bytes, "com.Foo": "f".bytes])
+
+        expect:
+        s1.compareToSnapshot(s2).changedClasses == ["Bar", "Car"]
+        s2.compareToSnapshot(s1).changedClasses == ["Car"]
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotterTest.groovy
new file mode 100644
index 0000000..b6fc15e
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JarSnapshotterTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.api.internal.file.collections.DirectoryFileTree
+import org.gradle.api.internal.file.collections.FileTreeAdapter
+import org.gradle.api.internal.hash.Hasher
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Subject
+
+class JarSnapshotterTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    def hasher = Mock(Hasher)
+    @Subject snapshotter = new JarSnapshotter(hasher);
+
+    def "creates snapshot of an empty jar"() {
+        expect:
+        def snapshot = snapshotter.createSnapshot(new FileTreeAdapter(new DirectoryFileTree(new File("missing"))))
+        snapshot.classHashes.isEmpty()
+    }
+
+    def "creates snapshot of a jar with classes"() {
+        def f1 = temp.createFile("foo/Foo.class")
+        def f2 = temp.createFile("foo/com/Foo2.class")
+
+        when:
+        def snapshot = snapshotter.createSnapshot(new FileTreeAdapter(new DirectoryFileTree(temp.file("foo"))))
+
+        then:
+        snapshot.classHashes.keySet() == ["Foo", "com.Foo2"] as Set
+        1 * hasher.hash(f1);
+        1 * hasher.hash(f2);
+        0 * _._
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JavaSourceClassTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JavaSourceClassTest.groovy
new file mode 100644
index 0000000..70e44d1
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/JavaSourceClassTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class JavaSourceClassTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def "knows output file"() {
+        expect:
+        new JavaSourceClass("com/Foo.java", temp.file("dir")).outputFile == temp.file("dir/com/Foo.class")
+        new JavaSourceClass("Foo.java", temp.file("dir")).outputFile == temp.file("dir/Foo.class")
+    }
+
+    def "knows class name"() {
+        expect:
+        new JavaSourceClass("com/Foo.java", temp.file("dir")).className == "com.Foo"
+        new JavaSourceClass("Foo.java", temp.file("dir")).className == "Foo"
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/OutputClassMapperTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/OutputClassMapperTest.groovy
new file mode 100644
index 0000000..fd6ef5b
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/OutputClassMapperTest.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental
+
+import spock.lang.Specification
+
+class OutputClassMapperTest extends Specification {
+
+    def "maps output classes"() {
+        expect:
+        new OutputClassMapper(new File("root")).getOutputFile("Foo") == new File("root/Foo.class")
+        new OutputClassMapper(new File("root")).getOutputFile("com.org.Bar") == new File("root/com/org/Bar.class")
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/AccessedFromPrivateClass.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/AccessedFromPrivateClass.java
new file mode 100644
index 0000000..a4660dc
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/AccessedFromPrivateClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class AccessedFromPrivateClass {
+
+    public String toString() {
+        return "foo";
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesAnalyzerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesAnalyzerTest.groovy
new file mode 100644
index 0000000..81abaaa
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassDependenciesAnalyzerTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer
+
+import org.gradle.api.internal.tasks.compile.incremental.analyzer.annotations.*
+import spock.lang.Specification
+import spock.lang.Subject
+
+class ClassDependenciesAnalyzerTest extends Specification {
+
+    @Subject analyzer = new ClassDependenciesAnalyzer()
+
+    private ClassAnalysis analyze(Class foo) {
+        analyzer.getClassAnalysis(foo.name, classStream(foo))
+    }
+
+    def "knows dependencies of a java class"() {
+        expect:
+        analyze(SomeOtherClass).classDependencies == [YetAnotherClass.name, SomeClass.name]
+    }
+
+    def "knows basic class dependencies of a groovy class"() {
+        def deps = analyze(ClassDependenciesAnalyzerTest).classDependencies
+
+        expect:
+        deps.contains(Specification.class.name)
+        //deps.contains(ClassDependenciesAnalyzer.class.name) // why this does not work (is it because of groovy)?
+    }
+
+    def "knows if a class have non-private constants"() {
+        expect:
+        analyze(HasNonPrivateConstants).classDependencies == [UsedByNonPrivateConstantsClass.name]
+        analyze(HasNonPrivateConstants).dependentToAll
+
+        analyze(HasPublicConstants).classDependencies == []
+        analyze(HasPublicConstants).dependentToAll
+
+        analyze(HasPrivateConstants).classDependencies == [HasNonPrivateConstants.name]
+        !analyze(HasPrivateConstants).dependentToAll
+    }
+
+    def "knows if a class uses annotations"() {
+        expect:
+        analyze(UsesRuntimeAnnotation).classDependencies == []
+        analyze(SomeRuntimeAnnotation).classDependencies == []
+        analyze(SomeRuntimeAnnotation).dependentToAll
+
+        analyze(UsesClassAnnotation).classDependencies == []
+        analyze(SomeClassAnnotation).classDependencies == []
+        analyze(SomeClassAnnotation).dependentToAll
+
+        analyze(UsesSourceAnnotation).classDependencies == [] //source annotations are wiped from the bytecode
+        analyze(SomeSourceAnnotation).classDependencies == []
+        analyze(SomeSourceAnnotation).dependentToAll
+    }
+
+    InputStream classStream(Class aClass) {
+        aClass.getResourceAsStream(aClass.getSimpleName() + ".class")
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasNonPrivateConstants.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasNonPrivateConstants.java
new file mode 100644
index 0000000..99a9ec5
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasNonPrivateConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class HasNonPrivateConstants extends UsedByNonPrivateConstantsClass {
+    final static int X = 1;
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasPrivateConstants.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasPrivateConstants.java
new file mode 100644
index 0000000..a666297
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasPrivateConstants.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class HasPrivateConstants {
+    private final static int X = 1;
+    private final static HasNonPrivateConstants C = new HasNonPrivateConstants();
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasPublicConstants.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasPublicConstants.java
new file mode 100644
index 0000000..a625094
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/HasPublicConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class HasPublicConstants {
+    public final static int X = 1;
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/SomeClass.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/SomeClass.java
new file mode 100644
index 0000000..aa446c8
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/SomeClass.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+import java.util.*;
+
+public class SomeClass {
+
+    List<Integer> field = new LinkedList<Integer>();
+
+    private Set<String> stuff(HashMap map) {
+        System.out.println(new Foo());
+        return new HashSet<String>();
+    }
+
+    private class Foo {
+        public String toString() {
+            return "" + new AccessedFromPrivateClass();
+        }
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/SomeOtherClass.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/SomeOtherClass.java
new file mode 100644
index 0000000..0d71da3
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/SomeOtherClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class SomeOtherClass extends SomeClass {
+
+    void foo() {
+        System.out.println(new YetAnotherClass());
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/UsedByNonPrivateConstantsClass.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/UsedByNonPrivateConstantsClass.java
new file mode 100644
index 0000000..3a8711a
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/UsedByNonPrivateConstantsClass.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class UsedByNonPrivateConstantsClass {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/YetAnotherClass.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/YetAnotherClass.java
new file mode 100644
index 0000000..0b9b1a6
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/YetAnotherClass.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer;
+
+public class YetAnotherClass {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeClassAnnotation.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeClassAnnotation.java
new file mode 100644
index 0000000..c9c19f4
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeClassAnnotation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+ at Retention(RetentionPolicy.CLASS)
+public @interface SomeClassAnnotation {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeRuntimeAnnotation.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeRuntimeAnnotation.java
new file mode 100644
index 0000000..2350ccc
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeRuntimeAnnotation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface SomeRuntimeAnnotation {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeSourceAnnotation.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeSourceAnnotation.java
new file mode 100644
index 0000000..9a9503a
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/SomeSourceAnnotation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+ at Retention(RetentionPolicy.SOURCE)
+public @interface SomeSourceAnnotation {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesAnnotationInField.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesAnnotationInField.java
new file mode 100644
index 0000000..79b9983
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesAnnotationInField.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+public class UsesAnnotationInField {
+    @SomeRuntimeAnnotation int x;
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesClassAnnotation.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesClassAnnotation.java
new file mode 100644
index 0000000..ac58389
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesClassAnnotation.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+ at SomeClassAnnotation public class UsesClassAnnotation {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesRuntimeAnnotation.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesRuntimeAnnotation.java
new file mode 100644
index 0000000..1c029dc
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesRuntimeAnnotation.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+ at SomeRuntimeAnnotation public class UsesRuntimeAnnotation {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesSourceAnnotation.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesSourceAnnotation.java
new file mode 100644
index 0000000..af809d7
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/analyzer/annotations/UsesSourceAnnotation.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.analyzer.annotations;
+
+ at SomeSourceAnnotation public class UsesSourceAnnotation {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoExtractorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoExtractorTest.groovy
new file mode 100644
index 0000000..089b151
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoExtractorTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.graph
+
+import org.gradle.api.internal.tasks.compile.incremental.analyzer.*
+import spock.lang.Specification
+
+class ClassDependencyInfoExtractorTest extends Specification {
+
+    def "knows recursive dependency tree"() {
+        def info = new ClassDependencyInfoExtractor(new File(ClassDependencyInfoExtractorTest.classLoader.getResource("").toURI())).extractInfo("org.gradle.api.internal.tasks.compile.incremental")
+        expect:
+        info.getActualDependents(SomeClass.name) == [SomeOtherClass.name] as Set
+        info.getActualDependents(SomeOtherClass.name) == [] as Set
+        info.getActualDependents(YetAnotherClass.name) == [SomeOtherClass.name] as Set
+        info.getActualDependents(AccessedFromPrivateClass.name) == [SomeClass.name, SomeOtherClass.name] as Set
+        info.getActualDependents(HasPrivateConstants.name) == [] as Set
+        info.getActualDependents(HasNonPrivateConstants.name) == null
+        info.getActualDependents(UsedByNonPrivateConstantsClass.name) == [HasNonPrivateConstants.name, HasPrivateConstants.name] as Set
+    }
+
+    //TODO SF tighten and refactor the coverage
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoSerializerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoSerializerTest.groovy
new file mode 100644
index 0000000..d1a6e8a
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/graph/ClassDependencyInfoSerializerTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.incremental.graph
+
+import org.gradle.api.internal.tasks.compile.incremental.ClassDependents
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ClassDependencyInfoSerializerTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+
+    def "stores dependency info"() {
+        def s = new ClassDependencyInfoSerializer(temp.file("foo.bin"))
+
+        when:
+        s.writeInfo(new ClassDependencyInfo(["foo.Foo": new ClassDependents().addClass("bar.Bar")]))
+        def info = s.readInfo()
+
+        then:
+        info.getActualDependents("foo.Foo") == ["bar.Bar"] as Set
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/AbstractTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/AbstractTestFrameworkTest.java
deleted file mode 100644
index acb6d0b..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/AbstractTestFrameworkTest.java
+++ /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.api.internal.tasks.testing;
-
-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.tasks.testing.Test;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @author Tom Eyckmans
- */
- at RunWith(JMock.class)
-public abstract class AbstractTestFrameworkTest {
-
-    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
-
-    protected Project projectMock;
-    protected Test testMock;
-
-    protected final File projectDir = new File("projectDir");
-    protected final File testClassesDir = new File("testClassesDir");
-    protected final List<File> testSrcDirs = Arrays.asList(new File("testSrcDirs"));
-    protected final File testResultsDir = new File("testResultsDir");
-    protected final File testReportDir = new File("testReportDir");
-    protected final File temporaryDir = new File("tempDir");
-    protected AntBuilder antBuilderMock;
-    protected FileCollection classpathMock;
-    protected FileTree classpathAsFileTreeMock;
-
-    protected void setUp() throws Exception {
-        context.setImposteriser(ClassImposteriser.INSTANCE);
-
-        projectMock = context.mock(Project.class);
-        testMock = context.mock(Test.class);
-        antBuilderMock = context.mock(AntBuilder.class);
-        classpathMock = context.mock(FileCollection.class);
-        classpathAsFileTreeMock = context.mock(FileTree.class);
-
-        context.checking(new Expectations(){{
-            allowing(testMock).getProject();
-            will(returnValue(projectMock));
-        }});
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/filter/DefaultTestFilterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/filter/DefaultTestFilterTest.groovy
new file mode 100644
index 0000000..58e2398
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/filter/DefaultTestFilterTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.filter
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+class DefaultTestFilterTest extends Specification {
+
+    def spec = new DefaultTestFilter()
+
+    def "allows configuring test names"() {
+        expect: spec.includePatterns.isEmpty()
+
+        when:
+        spec.includeTestsMatching("*fooMethod")
+        spec.includeTestsMatching("*.FooTest.*")
+
+        then: spec.includePatterns == ["*fooMethod", "*.FooTest.*"] as Set
+
+        when: spec.setIncludePatterns("x")
+
+        then: spec.includePatterns == ["x"] as Set
+    }
+
+    def "prevents empty names"() {
+        when: spec.includeTestsMatching(null)
+        then: thrown(InvalidUserDataException)
+
+        when: spec.includeTestsMatching("")
+        then: thrown(InvalidUserDataException)
+
+        when: spec.setIncludePatterns("ok", "")
+        then: thrown(InvalidUserDataException)
+    }
+
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/filter/TestSelectionMatcherTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/filter/TestSelectionMatcherTest.groovy
new file mode 100644
index 0000000..4c994f5
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/filter/TestSelectionMatcherTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.filter
+
+import spock.lang.Specification
+
+class TestSelectionMatcherTest extends Specification {
+
+    def "knows if test matches class"() {
+        expect: new TestSelectionMatcher(input).matchesTest(className, methodName) == match
+
+        where:
+        input                    | className                 | methodName            | match
+        ["FooTest"]              | "FooTest"                 | "whatever"            | true
+        ["FooTest"]              | "fooTest"                 | "whatever"            | false
+
+        ["com.foo.FooTest"]      | "com.foo.FooTest"         | "x"                   | true
+        ["com.foo.FooTest"]      | "FooTest"                 | "x"                   | false
+        ["com.foo.FooTest"]      | "com_foo_FooTest"         | "x"                   | false
+
+        ["com.foo.FooTest.*"]    | "com.foo.FooTest"         | "aaa"                 | true
+        ["com.foo.FooTest.*"]    | "com.foo.FooTest"         | "bbb"                 | true
+        ["com.foo.FooTest.*"]    | "com.foo.FooTestx"        | "bbb"                 | false
+
+        ["*.FooTest.*"]          | "com.foo.FooTest"         | "aaa"                 | true
+        ["*.FooTest.*"]          | "com.bar.FooTest"         | "aaa"                 | true
+        ["*.FooTest.*"]          | "FooTest"                 | "aaa"                 | false
+
+        ["com*FooTest"]          | "com.foo.FooTest"         | "aaa"                 | true
+        ["com*FooTest"]          | "com.FooTest"             | "bbb"                 | true
+        ["com*FooTest"]          | "FooTest"                 | "bbb"                 | false
+
+        ["*.foo.*"]              | "com.foo.FooTest"         | "aaaa"                | true
+        ["*.foo.*"]              | "com.foo.bar.BarTest"     | "aaaa"                | true
+        ["*.foo.*"]              | "foo.Test"                | "aaaa"                | false
+        ["*.foo.*"]              | "fooTest"                 | "aaaa"                | false
+        ["*.foo.*"]              | "foo"                     | "aaaa"                | false
+    }
+
+    def "knows if test matches"() {
+        expect: new TestSelectionMatcher(input).matchesTest(className, methodName) == match
+
+        where:
+        input                    | className                 | methodName            | match
+        ["FooTest.test"]         | "FooTest"                 | "test"                | true
+        ["FooTest.test"]         | "Footest"                 | "test"                | false
+        ["FooTest.test"]         | "FooTest"                 | "TEST"                | false
+        ["FooTest.test"]         | "com.foo.FooTest"         | "test"                | false
+        ["FooTest.test"]         | "Foo.test"                | ""                    | false
+
+        ["FooTest.*slow*"]       | "FooTest"                 | "slowUiTest"          | true
+        ["FooTest.*slow*"]       | "FooTest"                 | "veryslowtest"        | true
+        ["FooTest.*slow*"]       | "FooTest.SubTest"         | "slow"                | true
+        ["FooTest.*slow*"]       | "FooTest"                 | "a slow test"         | true
+        ["FooTest.*slow*"]       | "FooTest"                 | "aslow"               | true
+        ["FooTest.*slow*"]       | "com.foo.FooTest"         | "slowUiTest"          | false
+        ["FooTest.*slow*"]       | "FooTest"                 | "verySlowTest"        | false
+
+        ["com.FooTest***slow*"]  | "com.FooTest"             | "slowMethod"          | true
+        ["com.FooTest***slow*"]  | "com.FooTest2"            | "aslow"               | true
+        ["com.FooTest***slow*"]  | "com.FooTest.OtherTest"   | "slow"                | true
+        ["com.FooTest***slow*"]  | "FooTest"                 | "slowMethod"          | false
+    }
+
+    def "matches any of input"() {
+        expect: new TestSelectionMatcher(input).matchesTest(className, methodName) == match
+
+        where:
+        input                               | className                 | methodName            | match
+        ["FooTest.test", "FooTest.bar"]     | "FooTest"                 | "test"                | true
+        ["FooTest.test", "FooTest.bar"]     | "FooTest"                 | "bar"                 | true
+        ["FooTest.test", "FooTest.bar"]     | "FooTest"                 | "baz"                 | false
+        ["FooTest.test", "FooTest.bar"]     | "Footest"                 | "test"                | false
+
+        ["FooTest.test", "BarTest.*"]       | "FooTest"                 | "test"                | true
+        ["FooTest.test", "BarTest.*"]       | "BarTest"                 | "xxxx"                | true
+        ["FooTest.test", "BarTest.*"]       | "FooTest"                 | "xxxx"                | false
+
+        ["FooTest.test", "FooTest.*fast*"]  | "FooTest"                 | "test"                | true
+        ["FooTest.test", "FooTest.*fast*"]  | "FooTest"                 | "fast"                | true
+        ["FooTest.test", "FooTest.*fast*"]  | "FooTest"                 | "a fast test"         | true
+        ["FooTest.test", "FooTest.*fast*"]  | "FooTest"                 | "xxxx"                | false
+
+        ["FooTest", "*BarTest"]             | "FooTest"                 | "test"                | true
+        ["FooTest", "*BarTest"]             | "FooTest"                 | "xxxx"                | true
+        ["FooTest", "*BarTest"]             | "BarTest"                 | "xxxx"                | true
+        ["FooTest", "*BarTest"]             | "com.foo.BarTest"         | "xxxx"                | true
+        ["FooTest", "*BarTest"]             | "com.foo.FooTest"         | "xxxx"                | false
+    }
+
+    def "regexp chars are handled"() {
+        expect: new TestSelectionMatcher(input).matchesTest(className, methodName) == match
+
+        where:
+        input                               | className                 | methodName            | match
+        ["*Foo+Bar*"]                       | "Foo+Bar"                 | "test"                | true
+        ["*Foo+Bar*"]                       | "Foo+Bar"                 | "xxxx"                | true
+        ["*Foo+Bar*"]                       | "com.Foo+Bar"             | "xxxx"                | true
+        ["*Foo+Bar*"]                       | "FooBar"                  | "xxxx"                | false
+    }
+
+    def "handles null test method"() {
+        expect: new TestSelectionMatcher(input).matchesTest(className, methodName) == match
+
+        where:
+        input                               | className                 | methodName            | match
+        ["FooTest"]                         | "FooTest"                 | null                  | true
+        ["FooTest*"]                        | "FooTest"                 | null                  | true
+
+        ["FooTest.*"]                       | "FooTest"                 | null                  | false
+        ["FooTest"]                         | "OtherTest"               | null                  | false
+        ["FooTest.test"]                    | "FooTest"                 | null                  | false
+        ["FooTest.null"]                    | "FooTest"                 | null                  | false
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorData.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorData.groovy
new file mode 100644
index 0000000..99f6436
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorData.groovy
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.extensions.TestSetup
+import junit.framework.TestCase
+import junit.framework.TestSuite
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runner.Runner
+import org.junit.runner.notification.Failure
+import org.junit.runner.notification.RunNotifier
+
+import static org.junit.Assume.assumeTrue
+
+public class ATestClass {
+    @Test
+    public void ok() {
+    }
+}
+
+public class ATestClassWithIgnoredMethod {
+    @Test
+    @Ignore
+    public void ignored() {
+    }
+}
+
+public class ATestClassWithFailedTestAssumption {
+    @Test
+    public void assumed() {
+        assumeTrue(false)
+    }
+}
+
+ at Ignore
+public class AnIgnoredTestClass {
+    @Test
+    public void ignored() {
+    }
+
+    @Test
+    public void ignored2() {
+    }
+}
+
+public class ABrokenTestClass {
+    static RuntimeException failure = new RuntimeException()
+
+    @Test
+    public void broken() {
+        throw failure.fillInStackTrace()
+    }
+}
+
+public class ATestClassWithBrokenConstructor {
+    static RuntimeException failure = new RuntimeException()
+
+    def ATestClassWithBrokenConstructor() {
+        throw failure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public class ATestClassWithBrokenBeforeMethod {
+    static RuntimeException failure = new RuntimeException()
+
+    @Before
+    public void setup() {
+        throw failure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public class ATestClassWithBrokenBeforeAndAfterMethod {
+    static RuntimeException beforeFailure = new RuntimeException()
+    static RuntimeException afterFailure = new RuntimeException()
+
+    @Before
+    public void setup() {
+        throw beforeFailure.fillInStackTrace()
+    }
+
+    @After
+    public void teardown() {
+        throw afterFailure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public class ATestClassWithBrokenBeforeClassMethod {
+    static RuntimeException failure = new RuntimeException()
+
+    @BeforeClass
+    public static void setup() {
+        throw failure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public class AJunit3TestClass extends TestCase {
+    public void testOk() {
+    }
+}
+
+public class AJunit3TestThatRenamesItself extends TestCase {
+    public void testOk() {
+        setName('another test')
+    }
+}
+
+public class ABrokenJunit3TestClass extends TestCase {
+    static RuntimeException failure = new RuntimeException()
+
+    public void testBroken() {
+        throw failure.fillInStackTrace()
+    }
+}
+
+public class ATestClassWithSuiteMethod {
+    public static junit.framework.Test suite() {
+        return new junit.framework.TestSuite(AJunit3TestClass.class, AJunit3TestClass.class)
+    }
+}
+
+public class ATestClassWithBrokenSuiteMethod {
+    static RuntimeException failure = new RuntimeException('broken')
+
+    public static junit.framework.Test suite() {
+        throw failure
+    }
+}
+
+public class ATestSetUpWithBrokenSetUp extends TestSetup {
+    static RuntimeException failure = new RuntimeException('broken')
+
+    def ATestSetUpWithBrokenSetUp() {
+        super(new TestSuite(AJunit3TestClass.class))
+    }
+
+    protected void setUp() {
+        throw failure
+    }
+
+    public static junit.framework.Test suite() {
+        return new ATestSetUpWithBrokenSetUp()
+    }
+}
+
+ at RunWith(CustomRunner.class)
+public class ATestClassWithRunner {}
+
+public class CustomRunner extends Runner {
+    static RuntimeException failure = new RuntimeException('broken')
+    Class<?> type
+
+    def CustomRunner(Class<?> type) {
+        this.type = type
+    }
+
+    @Override
+    public Description getDescription() {
+        Description description = Description.createSuiteDescription(type)
+        description.addChild(Description.createTestDescription(type, 'ok1'))
+        description.addChild(Description.createTestDescription(type, 'ok2'))
+        return description
+    }
+
+    @Override
+    public void run(RunNotifier runNotifier) {
+        // Run tests in 'parallel'
+        Description test1 = Description.createTestDescription(type, 'broken')
+        Description test2 = Description.createTestDescription(type, 'ok')
+        runNotifier.fireTestStarted(test1)
+        runNotifier.fireTestStarted(test2)
+        runNotifier.fireTestFailure(new Failure(test1, failure.fillInStackTrace()))
+        runNotifier.fireTestFinished(test2)
+        runNotifier.fireTestFinished(test1)
+    }
+}
+
+ at RunWith(CustomRunnerWithBrokenConstructor.class)
+public class ATestClassWithUnconstructableRunner {}
+
+public class CustomRunnerWithBrokenConstructor extends Runner {
+    static RuntimeException failure = new RuntimeException()
+
+    def CustomRunnerWithBrokenConstructor(Class<?> type) {
+        throw failure.fillInStackTrace()
+    }
+
+    Description getDescription() {
+        throw new UnsupportedOperationException()
+    }
+
+    void run(RunNotifier notifier) {
+        throw new UnsupportedOperationException()
+    }
+}
+
+ at RunWith(CustomRunnerWithBrokenRunMethod.class)
+public class ATestClassWithBrokenRunner {}
+
+public class CustomRunnerWithBrokenRunMethod extends Runner {
+    static RuntimeException failure = new RuntimeException()
+    final Class<?> type
+
+    def CustomRunnerWithBrokenRunMethod(Class<?> type) {
+        this.type = type
+    }
+
+    Description getDescription() {
+        return Description.createSuiteDescription(type)
+    }
+
+    void run(RunNotifier notifier) {
+        throw failure.fillInStackTrace()
+    }
+}
+
+ at RunWith(CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests.class)
+public class ATestClassWithRunnerThatBreaksAfterRuningSomeTests {}
+
+public class CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests extends Runner {
+    static RuntimeException failure = new RuntimeException()
+    final Class<?> type
+
+    def CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests(Class<?> type) {
+        this.type = type
+    }
+
+    Description getDescription() {
+        return Description.createSuiteDescription(type)
+    }
+
+    void run(RunNotifier notifier) {
+        notifier.fireTestStarted(Description.createTestDescription(type, "ok1"))
+        notifier.fireTestFinished(Description.createTestDescription(type, "ok1"))
+        notifier.fireTestStarted(Description.createTestDescription(type, "broken"))
+        throw failure.fillInStackTrace()
+    }
+}
+
+public class ATestClassWhichCannotBeLoaded {
+    static {
+        throw new NoClassDefFoundError()
+    }
+}
+
+public class ATestClassWithSeveralMethods {
+    @Test public void pass() {}
+    @Test public void pass2() {}
+    @Test public void passSlowly() {}
+    @Test public void passSlowly2() {}
+    @Test public void fail() { throw new RuntimeException("Boo!") }
+}
+public class ATestClassWithSlowMethods {
+    @Test public void pass() {}
+    @Test public void passSlowly() {}
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
index d4c638d..b5ee48e 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,995 +13,261 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.gradle.api.internal.tasks.testing.junit
 
-import junit.extensions.TestSetup
-import junit.framework.TestCase
-import junit.framework.TestSuite
-import org.gradle.api.internal.tasks.testing.*
-import org.gradle.api.tasks.testing.TestResult
+import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.tasks.testing.junit.JUnitOptions
 import org.gradle.internal.id.LongIdGenerator
 import org.gradle.logging.StandardOutputRedirector
-import org.gradle.messaging.actor.ActorFactory
 import org.gradle.messaging.actor.TestActorFactory
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.*
-import org.junit.runner.Description
-import org.junit.runner.RunWith
-import org.junit.runner.Runner
-import org.junit.runner.notification.Failure
-import org.junit.runner.notification.RunNotifier
-
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-import static org.junit.Assume.assumeTrue
-
- at RunWith(JMock.class)
-class JUnitTestClassProcessorTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule
-    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private final ActorFactory actorFactory = new TestActorFactory()
-    private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.testDirectory, new LongIdGenerator(), actorFactory, {} as StandardOutputRedirector);
-
-    @Test
-    public void executesAJUnit4TestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ATestClass.class.name))
-                assertThat(suite.className, equalTo(ATestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ok'))
-                assertThat(test.className, equalTo(ATestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClass.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesABrokenJUnit4TestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ABrokenTestClass.class.name))
-                assertThat(suite.className, equalTo(ABrokenTestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('broken'))
-                assertThat(test.className, equalTo(ABrokenTestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).failure(2L, ABrokenTestClass.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ABrokenTestClass.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesAJUnit4TestClassWithIgnoredTest() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ATestClassWithIgnoredMethod.class.name))
-                assertThat(suite.className, equalTo(ATestClassWithIgnoredMethod.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ignored'))
-                assertThat(test.className, equalTo(ATestClassWithIgnoredMethod.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(TestResult.ResultType.SKIPPED))
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithIgnoredMethod.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesAJUnit4TestClassWithFailedTestAssumption() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ATestClassWithFailedTestAssumption.class.name))
-                assertThat(suite.className, equalTo(ATestClassWithFailedTestAssumption.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('assumed'))
-                assertThat(test.className, equalTo(ATestClassWithFailedTestAssumption.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(TestResult.ResultType.SKIPPED))
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithFailedTestAssumption.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesAnIgnoredJUnit4TestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(AnIgnoredTestClass.class.name))
-                assertThat(suite.className, equalTo(AnIgnoredTestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ignored2'))
-                assertThat(test.className, equalTo(AnIgnoredTestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(3L))
-                assertThat(test.name, equalTo('ignored'))
-                assertThat(test.className, equalTo(AnIgnoredTestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(TestResult.ResultType.SKIPPED))
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(AnIgnoredTestClass.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesAJUnit3TestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(AJunit3TestClass.class.name))
-                assertThat(suite.className, equalTo(AJunit3TestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('testOk'))
-                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(AJunit3TestClass.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesABrokenJUnit3TestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ABrokenJunit3TestClass.class.name))
-                assertThat(suite.className, equalTo(ABrokenJunit3TestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('testBroken'))
-                assertThat(test.className, equalTo(ABrokenJunit3TestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).failure(2L, ABrokenJunit3TestClass.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ABrokenJunit3TestClass.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesMultipleTestClasses() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ATestClass.class.name))
-                assertThat(suite.className, equalTo(ATestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ok'))
-                assertThat(test.className, equalTo(ATestClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(3L))
-                assertThat(suite.name, equalTo(AJunit3TestClass.class.name))
-                assertThat(suite.className, equalTo(AJunit3TestClass.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(4L))
-                assertThat(test.name, equalTo('testOk'))
-                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
-                assertThat(event.parentId, equalTo(3L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(4L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClass.class));
-        processor.processTestClass(testClass(AJunit3TestClass.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithRunWithAnnotation() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(ATestClassWithRunner.class.name))
-                assertThat(suite.className, equalTo(ATestClassWithRunner.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('broken'))
-                assertThat(test.className, equalTo(ATestClassWithRunner.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(3L))
-                assertThat(test.name, equalTo('ok'))
-                assertThat(test.className, equalTo(ATestClassWithRunner.class.name))
-            }
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).failure(2L, CustomRunner.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithRunner.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithASuiteMethod() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithSuiteMethod.class.name))
-                assertThat(suite.className, equalTo(ATestClassWithSuiteMethod.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('testOk'))
-                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(3L))
-                assertThat(test.name, equalTo('testOk'))
-                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
-            }
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithSuiteMethod.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithBrokenSuiteMethod() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithBrokenSuiteMethod.class.name))
-                assertThat(suite.className, equalTo(ATestClassWithBrokenSuiteMethod.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('initializationError'))
-                assertThat(test.className, equalTo(ATestClassWithBrokenSuiteMethod.class.name))
-            }
-            one(resultProcessor).failure(2L, ATestClassWithBrokenSuiteMethod.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithBrokenSuiteMethod.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithBrokenSetUp() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestSetUpWithBrokenSetUp.class.name))
-                assertThat(suite.className, equalTo(ATestSetUpWithBrokenSetUp.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo(AJunit3TestClass.name))
-                assertThat(test.className, equalTo(ATestSetUpWithBrokenSetUp.class.name))
-            }
-            one(resultProcessor).failure(2L, ATestSetUpWithBrokenSetUp.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestSetUpWithBrokenSetUp.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithBrokenBeforeMethod() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithBrokenBeforeMethod.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('test'))
-                assertThat(test.className, equalTo(ATestClassWithBrokenBeforeMethod.class.name))
-            }
-            one(resultProcessor).failure(2L, ATestClassWithBrokenBeforeMethod.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithBrokenBeforeMethod.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithBrokenBeforeAndAfterMethod() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithBrokenBeforeAndAfterMethod.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('test'))
-                assertThat(test.className, equalTo(ATestClassWithBrokenBeforeAndAfterMethod.class.name))
-            }
-            one(resultProcessor).failure(2L, ATestClassWithBrokenBeforeAndAfterMethod.beforeFailure)
-            one(resultProcessor).failure(2L, ATestClassWithBrokenBeforeAndAfterMethod.afterFailure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithBrokenBeforeAndAfterMethod.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithBrokenConstructor() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithBrokenConstructor.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('test'))
-                assertThat(test.className, equalTo(ATestClassWithBrokenConstructor.class.name))
-            }
-            one(resultProcessor).failure(2L, ATestClassWithBrokenConstructor.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(notNullValue()), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithBrokenConstructor.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithBrokenBeforeClassMethod() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithBrokenBeforeClassMethod.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('classMethod'))
-                assertThat(test.className, equalTo(ATestClassWithBrokenBeforeClassMethod.class.name))
-            }
-            one(resultProcessor).failure(2L, ATestClassWithBrokenBeforeClassMethod.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithBrokenBeforeClassMethod.class));
-        processor.stop();
-    }
-
-    @Test
-    public void executesATestClassWithRunnerThatCannotBeConstructed() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithUnconstructableRunner.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('initializationError'))
-                assertThat(test.className, equalTo(ATestClassWithUnconstructableRunner.class.name))
-            }
-            one(resultProcessor).failure(2L, CustomRunnerWithBrokenConstructor.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Subject
+import spock.lang.Unroll
 
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithUnconstructableRunner.class));
-        processor.stop();
-    }
+import static org.gradle.api.tasks.testing.TestResult.ResultType.SKIPPED
 
-    @Test
-    public void executesATestClassWithRunnerThatBreaksBeforeRunningAnyTests() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithBrokenRunner.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('initializationError'))
-                assertThat(test.className, equalTo(ATestClassWithBrokenRunner.class.name))
-            }
-            one(resultProcessor).failure(2L, CustomRunnerWithBrokenRunMethod.failure)
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
+class JUnitTestClassProcessorTest extends Specification {
+    
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
 
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithBrokenRunner.class));
-        processor.stop();
-    }
+    def processor = Mock(TestResultProcessor)
+    def spec = new JUnitSpec(new JUnitOptions(), [] as Set)
+    
+    @Subject classProcessor = withSpec(spec)
 
-    @Test
-    public void executesATestClassWithRunnerThatBreaksAfterRunningSomeTests() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('ok1'))
-                assertThat(test.className, equalTo(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class.name))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('broken'))
-                assertThat(test.className, equalTo(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class.name))
-            }
-            one(resultProcessor).failure(3L, CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests.failure)
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class));
-        processor.stop();
+    JUnitTestClassProcessor withSpec(spec) {
+        new JUnitTestClassProcessor(spec, new LongIdGenerator(), new TestActorFactory(), {} as StandardOutputRedirector)
     }
 
-    @Test
-    public void executesATestClassWhichCannotBeLoaded() {
-        String testClassName = 'org.gradle.api.internal.tasks.testing.junit.ATestClassWhichCannotBeLoaded'
-
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo(testClassName))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('initializationError'))
-                assertThat(test.className, equalTo(testClassName))
-            }
-            one(resultProcessor).failure(withParam(equalTo(2L)), withParam(instanceOf(NoClassDefFoundError)))
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(testClassName));
-        processor.stop();
+    void process(Class ... clazz) {
+        process(clazz*.name)
     }
 
-    @Test
-    public void executesAJUnit3TestClassThatRenamesItself() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo(AJunit3TestThatRenamesItself.class.name))
-                assertThat(suite.className, equalTo(AJunit3TestThatRenamesItself.class.name))
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('testOk'))
-                assertThat(test.className, equalTo(AJunit3TestThatRenamesItself.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
+    void process(Iterable<String> classNames) {
+        classProcessor.startProcessing(processor)
+        for (String c : classNames) {
+            classProcessor.processTestClass(new DefaultTestClassRunInfo(c))
         }
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(AJunit3TestThatRenamesItself.class));
-        processor.stop();
+        classProcessor.stop()
     }
 
-    private TestClassRunInfo testClass(Class<?> type) {
-        return testClass(type.name)
-    }
+    def executesAJUnit4TestClass() {
+        when: process(ATestClass)
 
-    private TestClassRunInfo testClass(String testClassName) {
-        TestClassRunInfo runInfo = context.mock(TestClassRunInfo.class, testClassName)
-        context.checking {
-            allowing(runInfo).getTestClassName()
-            will(returnValue(testClassName))
-        }
-        return runInfo;
+        then: 1 * processor.started({ it.id == 1 && it.name == ATestClass.name && it.className == ATestClass.name }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == "ok" && it.className == ATestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null }) //wondering why result type is null? Failures are notified via failure() method
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ATestClass {
-    @Test
-    public void ok() {
-    }
-}
+    def executesAJUnit4TestClassWithIgnoredTest() {
+        when: process(ATestClassWithIgnoredMethod)
 
-public class ATestClassWithIgnoredMethod {
-    @Test
-    @Ignore
-    public void ignored() {
+        then: 1 * processor.started({it.id == 1}, {it.parentId == null})
+        then: 1 * processor.started({ it.id == 2 && it.name == "ignored" && it.className == ATestClassWithIgnoredMethod.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == SKIPPED })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ATestClassWithFailedTestAssumption {
-    @Test
-    public void assumed() {
-        assumeTrue(false)
-    }
-}
-
- at Ignore
-public class AnIgnoredTestClass {
-    @Test
-    public void ignored() {
-    }
+    def executesAJUnit4TestClassWithFailedTestAssumption() {
+        when: process(ATestClassWithFailedTestAssumption)
 
-    @Test
-    public void ignored2() {
+        then: 1 * processor.started({it.id == 1}, {it.parentId == null})
+        then: 1 * processor.started({ it.id == 2 && it.name == "assumed" && it.className == ATestClassWithFailedTestAssumption.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == SKIPPED })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ABrokenTestClass {
-    static RuntimeException failure = new RuntimeException()
+    def executesAnIgnoredJUnit4TestClass() {
+        when: process(AnIgnoredTestClass)
 
-    @Test
-    public void broken() {
-        throw failure.fillInStackTrace()
+        then: 1 * processor.started({it.id == 1}, {it.parentId == null})
+        then: 1 * processor.started({ it.id == 2 && it.name == "ignored2" && it.className == AnIgnoredTestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == SKIPPED })
+        then: 1 * processor.started({ it.id == 3 && it.name == "ignored" && it.className == AnIgnoredTestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(3, { it.resultType == SKIPPED })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ATestClassWithBrokenConstructor {
-    static RuntimeException failure = new RuntimeException()
-
-    def ATestClassWithBrokenConstructor() {
-        throw failure.fillInStackTrace()
-    }
+    def executesAJUnit3TestClass() {
+        when: process(AJunit3TestClass)
 
-    @Test
-    public void test() {
+        then: 1 * processor.started({it.id == 1}, {it.parentId == null})
+        then: 1 * processor.started({ it.id == 2 && it.name == "testOk" && it.className == AJunit3TestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ATestClassWithBrokenBeforeMethod {
-    static RuntimeException failure = new RuntimeException()
+    def executesMultipleTestClasses() {
+        when: process(ATestClass, AJunit3TestClass)
 
-    @Before
-    public void setup() {
-        throw failure.fillInStackTrace()
-    }
+        then: 1 * processor.started({ it.id == 1 && it.name == ATestClass.name && it.className == ATestClass.name }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == "ok" && it.className == ATestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
 
-    @Test
-    public void test() {
+        then: 1 * processor.started({ it.id == 3 && it.name == AJunit3TestClass.name && it.className == AJunit3TestClass.name }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 4 && it.name == "testOk" && it.className == AJunit3TestClass.name }, { it.parentId == 3 })
+        then: 1 * processor.completed(4, { it.resultType == null })
+        then: 1 * processor.completed(3, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ATestClassWithBrokenBeforeAndAfterMethod {
-    static RuntimeException beforeFailure = new RuntimeException()
-    static RuntimeException afterFailure = new RuntimeException()
+    def executesATestClassWithRunWithAnnotation() {
+        when: process(ATestClassWithRunner)
 
-    @Before
-    public void setup() {
-        throw beforeFailure.fillInStackTrace()
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == "broken" && it.className == ATestClassWithRunner.name }, { it.parentId == 1 })
+        then: 1 * processor.started({ it.id == 3 && it.name == "ok" && it.className == ATestClassWithRunner.name }, { it.parentId == 1 })
+        then: 1 * processor.failure(2, CustomRunner.failure)
+        then: 1 * processor.completed(3, { it.resultType == null })
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
 
-    @After
-    public void teardown() {
-        throw afterFailure.fillInStackTrace()
-    }
+    def executesATestClassWithASuiteMethod() {
+        when: process(ATestClassWithSuiteMethod)
 
-    @Test
-    public void test() {
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == "testOk" && it.className == AJunit3TestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.started({ it.id == 3 && it.name == "testOk" && it.className == AJunit3TestClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(3, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
-public class ATestClassWithBrokenBeforeClassMethod {
-    static RuntimeException failure = new RuntimeException()
+    def executesATestClassWithBrokenBeforeAndAfterMethod() {
+        when: process(ATestClassWithBrokenBeforeAndAfterMethod)
 
-    @BeforeClass
-    public static void setup() {
-        throw failure.fillInStackTrace()
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == 'test' && it.className == ATestClassWithBrokenBeforeAndAfterMethod.name }, { it.parentId == 1 })
+        then: 1 * processor.failure(2, ATestClassWithBrokenBeforeAndAfterMethod.beforeFailure)
+        then: 1 * processor.failure(2, ATestClassWithBrokenBeforeAndAfterMethod.afterFailure)
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
 
-    @Test
-    public void test() {
-    }
-}
+    @Unroll
+    def "#testClass reports failure"() {
+        when: process(testClass)
 
-public class AJunit3TestClass extends TestCase {
-    public void testOk() {
-    }
-}
-
-public class AJunit3TestThatRenamesItself extends TestCase {
-    public void testOk() {
-        setName('another test')
-    }
-}
-
-public class ABrokenJunit3TestClass extends TestCase {
-    static RuntimeException failure = new RuntimeException()
-
-    public void testBroken() {
-        throw failure.fillInStackTrace()
-    }
-}
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == testMethodName && it.className == testClass.name }, { it.parentId == 1 })
+        then: 1 * processor.failure(2, failure)
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
 
-public class ATestClassWithSuiteMethod {
-    public static junit.framework.Test suite() {
-        return new junit.framework.TestSuite(AJunit3TestClass.class, AJunit3TestClass.class);
+        where:
+        testClass                               |testMethodName         |failure
+        ABrokenTestClass                        |'broken'           |ABrokenTestClass.failure
+        ABrokenJunit3TestClass                  |'testBroken'           |ABrokenJunit3TestClass.failure
+        ATestClassWithBrokenRunner              |'initializationError'  |CustomRunnerWithBrokenRunMethod.failure
+        ATestClassWithUnconstructableRunner     |'initializationError'  |CustomRunnerWithBrokenConstructor.failure
+        ATestClassWithBrokenBeforeClassMethod   |'classMethod'          |ATestClassWithBrokenBeforeClassMethod.failure
+        ATestClassWithBrokenConstructor         |'test'                 |ATestClassWithBrokenConstructor.failure
+        ATestClassWithBrokenBeforeMethod        |'test'                 |ATestClassWithBrokenBeforeMethod.failure
+        ATestClassWithBrokenSuiteMethod         |'initializationError'  |ATestClassWithBrokenSuiteMethod.failure
+        ATestSetUpWithBrokenSetUp               |AJunit3TestClass.name  |ATestSetUpWithBrokenSetUp.failure
     }
-}
-
-public class ATestClassWithBrokenSuiteMethod {
-    static RuntimeException failure = new RuntimeException('broken')
 
-    public static junit.framework.Test suite() {
-        throw failure
-    }
-}
+    def executesATestClassWithRunnerThatBreaksAfterRunningSomeTests() {
+        when: process(ATestClassWithRunnerThatBreaksAfterRuningSomeTests)
 
-public class ATestSetUpWithBrokenSetUp extends TestSetup {
-    static RuntimeException failure = new RuntimeException('broken')
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
 
-    def ATestSetUpWithBrokenSetUp() {
-        super(new TestSuite(AJunit3TestClass.class))
-    }
+        then: 1 * processor.started({ it.id == 2 && it.name == 'ok1' && it.className == ATestClassWithRunnerThatBreaksAfterRuningSomeTests.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null })
 
-    protected void setUp() {
-        throw failure
-    }
+        then: 1 * processor.started({ it.id == 3 && it.name == 'broken' && it.className == ATestClassWithRunnerThatBreaksAfterRuningSomeTests.name }, { it.parentId == 1 })
+        then: 1 * processor.failure(3, CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests.failure)
+        then: 1 * processor.completed(3, { it.resultType == null })
 
-    public static junit.framework.Test suite() {
-        return new ATestSetUpWithBrokenSetUp()
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
-
- at RunWith(CustomRunner.class)
-public class ATestClassWithRunner {}
 
-public class CustomRunner extends Runner {
-    static RuntimeException failure = new RuntimeException('broken')
-    Class<?> type
+    def executesATestClassWhichCannotBeLoaded() {
+        String testClassName = 'org.gradle.api.internal.tasks.testing.junit.ATestClassWhichCannotBeLoaded'
+        when: process([testClassName])
 
-    def CustomRunner(Class<?> type) {
-        this.type = type
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == 'initializationError' && it.className == testClassName }, { it.parentId == 1 })
+        then: 1 * processor.failure(2, _ as NoClassDefFoundError)
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
 
-    @Override
-    public Description getDescription() {
-        Description description = Description.createSuiteDescription(type)
-        description.addChild(Description.createTestDescription(type, 'ok1'))
-        description.addChild(Description.createTestDescription(type, 'ok2'))
-        return description
-    }
+    def executesAJUnit3TestClassThatRenamesItself() {
+        when: process(AJunit3TestThatRenamesItself)
 
-    @Override
-    public void run(RunNotifier runNotifier) {
-        // Run tests in 'parallel'
-        Description test1 = Description.createTestDescription(type, 'broken')
-        Description test2 = Description.createTestDescription(type, 'ok')
-        runNotifier.fireTestStarted(test1)
-        runNotifier.fireTestStarted(test2)
-        runNotifier.fireTestFailure(new Failure(test1, failure.fillInStackTrace()))
-        runNotifier.fireTestFinished(test2)
-        runNotifier.fireTestFinished(test1)
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == 'testOk' && it.className == AJunit3TestThatRenamesItself.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
-
- at RunWith(CustomRunnerWithBrokenConstructor.class)
-public class ATestClassWithUnconstructableRunner {}
 
-public class CustomRunnerWithBrokenConstructor extends Runner {
-    static RuntimeException failure = new RuntimeException()
+    def "executes specific method"() {
+        classProcessor = withSpec(new JUnitSpec(new JUnitOptions(), [ATestClassWithSeveralMethods.name + ".pass"] as Set))
 
-    def CustomRunnerWithBrokenConstructor(Class<?> type) {
-        throw failure.fillInStackTrace()
-    }
-
-    Description getDescription() {
-        throw new UnsupportedOperationException();
-    }
+        when: process(ATestClassWithSeveralMethods)
 
-    void run(RunNotifier notifier) {
-        throw new UnsupportedOperationException();
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == "pass" && it.className == ATestClassWithSeveralMethods.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
-}
 
- at RunWith(CustomRunnerWithBrokenRunMethod.class)
-public class ATestClassWithBrokenRunner {}
+    def "executes multiple specific methods"() {
+        classProcessor = withSpec(new JUnitSpec(new JUnitOptions(), [ATestClassWithSeveralMethods.name + ".pass",
+                ATestClassWithSeveralMethods.name + ".pass2"] as Set))
 
-public class CustomRunnerWithBrokenRunMethod extends Runner {
-    static RuntimeException failure = new RuntimeException()
-    final Class<?> type
+        when: process(ATestClassWithSeveralMethods)
 
-    def CustomRunnerWithBrokenRunMethod(Class<?> type) {
-        this.type = type
+        then:
+        1 * processor.started({ it.name == ATestClassWithSeveralMethods.name }, _)
+        1 * processor.started({ it.name == "pass" && it.className == ATestClassWithSeveralMethods.name }, _)
+        1 * processor.started({ it.name == "pass2" && it.className == ATestClassWithSeveralMethods.name }, _)
+        0 * processor.started(_, _)
     }
 
-    Description getDescription() {
-        return Description.createSuiteDescription(type)
-    }
-
-    void run(RunNotifier notifier) {
-        throw failure.fillInStackTrace();
-    }
-}
+    def "executes methods from multiple classes by pattern"() {
+        classProcessor = withSpec(new JUnitSpec(new JUnitOptions(), ["*Methods.*Slowly*"] as Set))
 
- at RunWith(CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests.class)
-public class ATestClassWithRunnerThatBreaksAfterRuningSomeTests {}
+        when: process(ATestClassWithSeveralMethods, ATestClassWithSlowMethods, ATestClass)
 
-public class CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests extends Runner {
-    static RuntimeException failure = new RuntimeException()
-    final Class<?> type
-
-    def CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests(Class<?> type) {
-        this.type = type
+        then:
+        1 * processor.started({ it.name == ATestClassWithSeveralMethods.name }, _)
+        1 * processor.started({ it.name == "passSlowly" && it.className == ATestClassWithSeveralMethods.name }, _)
+        1 * processor.started({ it.name == "passSlowly2" && it.className == ATestClassWithSeveralMethods.name }, _)
+        1 * processor.started({ it.name == ATestClassWithSlowMethods.name }, _)
+        1 * processor.started({ it.name == "passSlowly" && it.className == ATestClassWithSlowMethods.name }, _)
+        1 * processor.started({ it.name == ATestClass.name }, _)
+        0 * processor.started(_, _)
     }
 
-    Description getDescription() {
-        return Description.createSuiteDescription(type)
-    }
+    def "executes no methods when method name does not match"() {
+        classProcessor = withSpec(new JUnitSpec(new JUnitOptions(), ["does not exist"] as Set))
 
-    void run(RunNotifier notifier) {
-        notifier.fireTestStarted(Description.createTestDescription(type, "ok1"))
-        notifier.fireTestFinished(Description.createTestDescription(type, "ok1"))
-        notifier.fireTestStarted(Description.createTestDescription(type, "broken"))
-        throw failure.fillInStackTrace();
-    }
-}
+        when: process(ATestClassWithSeveralMethods)
 
-public class ATestClassWhichCannotBeLoaded {
-    static {
-        throw new NoClassDefFoundError()
+        then: 1 * processor.started({ it.id == 1 }, { it.parentId == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
deleted file mode 100644
index b4b4a82..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.junit;
-
-import org.gradle.api.AntBuilder;
-import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
-import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.api.tasks.testing.junit.JUnitOptions;
-import org.gradle.internal.Factory;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.messaging.actor.ActorFactory;
-import org.jmock.Expectations;
-import org.junit.Before;
-
-import java.io.File;
-
-import static junit.framework.Assert.assertNotNull;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Tom Eyckmans
- */
-public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
-    private JUnitTestFramework jUnitTestFramework;
-    private JUnitOptions jUnitOptionsMock;
-    private IdGenerator<?> idGenerator;
-    private ServiceRegistry serviceRegistry;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-
-        jUnitOptionsMock = context.mock(JUnitOptions.class);
-        idGenerator = context.mock(IdGenerator.class);
-        serviceRegistry = context.mock(ServiceRegistry.class);
-        final Factory<File> temporaryDirFactory = new Factory<File>() {
-            public File create() {
-                return temporaryDir;
-            }
-        };
-        context.checking(new Expectations() {{
-            allowing(testMock).getTestClassesDir();
-            will(returnValue(testClassesDir));
-            allowing(testMock).getClasspath();
-            will(returnValue(classpathMock));
-            allowing(testMock).getAnt();
-            will(returnValue(context.mock(AntBuilder.class)));
-            allowing(testMock).getTemporaryDirFactory();
-            will(returnValue(temporaryDirFactory));
-        }});
-    }
-
-    @org.junit.Test
-    public void testInitialize() {
-        jUnitTestFramework = new JUnitTestFramework(testMock);
-        setMocks();
-
-        assertNotNull(jUnitTestFramework.getOptions());
-    }
-
-    @org.junit.Test
-    public void testCreatesTestProcessor() {
-        jUnitTestFramework = new JUnitTestFramework(testMock);
-        setMocks();
-        final ActorFactory actorFactory = context.mock(ActorFactory.class);
-
-        context.checking(new Expectations() {{
-            one(testMock).getTestResultsDir();
-            will(returnValue(testResultsDir));
-            one(serviceRegistry).get(IdGenerator.class);
-            will(returnValue(idGenerator));
-            one(serviceRegistry).get(ActorFactory.class);
-            will(returnValue(actorFactory));
-        }});
-
-        TestClassProcessor testClassProcessor = jUnitTestFramework.getProcessorFactory().create(serviceRegistry);
-        assertThat(testClassProcessor, instanceOf(JUnitTestClassProcessor.class));
-    }
-
-    private void setMocks() {
-        jUnitTestFramework.setOptions(jUnitOptionsMock);
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResultsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResultsTest.groovy
index fc3b8ce..9ae6866 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResultsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/AllTestResultsTest.groovy
@@ -22,7 +22,7 @@ class AllTestResultsTest extends Specification {
 
     def addsTest() {
         when:
-        def test = results.addTest('org.gradle.Test', 'test', 90)
+        def test = results.addTest(1, 'org.gradle.Test', 'test', 90)
 
         then:
         test.name == 'test'
@@ -33,7 +33,7 @@ class AllTestResultsTest extends Specification {
     
     def addsTestInDefaultPackage() {
         when:
-        def test = results.addTest('Test', 'test', 90)
+        def test = results.addTest(1, 'Test', 'test', 90)
 
         then:
         test.name == 'test'
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResultsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResultsTest.groovy
index e546714..519fd94 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResultsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResultsTest.groovy
@@ -20,7 +20,7 @@ import spock.lang.Specification
 class ClassTestResultsTest extends Specification {
     def determinesSimpleName() {
         expect:
-        new ClassTestResults('org.gradle.Test', null).simpleName == 'Test'
-        new ClassTestResults('Test', null).simpleName == 'Test'
+        new ClassTestResults(1, 'org.gradle.Test', null).simpleName == 'Test'
+        new ClassTestResults(2, 'Test', null).simpleName == 'Test'
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy
index 6dd21b8..af8036c 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy
@@ -17,12 +17,19 @@ package org.gradle.api.internal.tasks.testing.junit.report
 
 import spock.lang.Specification
 
+import static org.gradle.api.tasks.testing.TestResult.ResultType.*
+
 class CompositeTestResultsTest extends Specification {
     final CompositeTestResults results = new CompositeTestResults(null) {
         @Override
         String getTitle() {
             throw new UnsupportedOperationException()
         }
+
+        @Override
+        String getBaseUrl() {
+            return "test/page.html"
+        }
     }
 
     def formatsSuccessRateWhenNoTests() {
@@ -50,6 +57,18 @@ class CompositeTestResultsTest extends Specification {
         results.formattedSuccessRate == '66%'
     }
 
+    def formatsSuccessRateWhenSomeTestsFailAndSomeTestsAreIgnored() {
+        def failed = results.addTest(test())
+        results.failed(failed)
+        results.addTest(test())
+        results.addTest(test())
+        results.ignored(test());
+
+        expect:
+        results.successRate == 50
+        results.formattedSuccessRate == '50%'
+    }
+
     def formatsDurationWhenNoTests() {
         expect:
         results.formattedDuration == '-'
@@ -62,7 +81,50 @@ class CompositeTestResultsTest extends Specification {
         results.formattedDuration == '0.045s'
     }
 
+    def computesResultTypeWhenOnlySuccess() {
+        results.addTest(test())
+
+        expect:
+        results.resultType == SUCCESS;
+    }
+
+    def computesResultTypeWhenSuccessAndIgnored() {
+        results.addTest(test())
+        results.addTest(test())
+        results.ignored(test())
+
+        expect:
+        results.resultType == SKIPPED;
+    }
+
+    def computesResultTypeWhenSuccessAndIgnoredAndFailed() {
+        results.addTest(test())
+        results.addTest(test())
+        results.ignored(test())
+        def failed = results.addTest(test())
+        results.failed(failed)
+
+        expect:
+        results.resultType == FAILURE;
+    }
+
+    def calculatesRelativePath() {
+        def other = Stub(CompositeTestResults) {
+            getBaseUrl() >> fromUrl
+        }
+
+        expect:
+        results.getUrlTo(other) == relativeUrl
+
+        where:
+        fromUrl                  | relativeUrl
+        "test/other.html"        | "other.html"
+        "other/other.html"       | "../other/other.html"
+        "index.html"             | "../index.html"
+        "test/subdir/other.html" | "subdir/other.html"
+    }
+
     private TestResult test() {
-        return new TestResult('test', 45, new ClassTestResults('test', null))
+        return new TestResult('test', 45, new ClassTestResults(1, 'test', null))
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
index bb8ba78..7d621fe 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
@@ -15,18 +15,12 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report
 
-import org.gradle.api.Action
-import org.gradle.api.internal.tasks.testing.junit.result.TestClassResult
-import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult
+import org.gradle.api.internal.tasks.testing.BuildableTestResultsProvider
+import org.gradle.api.internal.tasks.testing.junit.result.AggregateTestResultsProvider
 import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider
-import org.gradle.api.internal.tasks.testing.logging.SimpleTestResult
-import org.gradle.api.tasks.testing.TestOutputEvent
-import org.gradle.api.tasks.testing.TestResult
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.ConfigureUtil
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Document
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -49,158 +43,473 @@ class DefaultTestReportTest extends Specification {
         def index = results(indexFile)
         index.assertHasTests(0)
         index.assertHasFailures(0)
+        index.assertHasIgnored(0)
         index.assertHasNoDuration()
         index.assertHasNoSuccessRate()
         index.assertHasNoNavLinks()
     }
 
     TestResultsProvider buildResults(Closure closure) {
-        TestResultsBuilder builder = new TestResultsBuilder()
-        ConfigureUtil.configure(closure, builder)
-        return builder;
+        ConfigureUtil.configure(closure, new BuildableTestResultsProvider())
     }
 
-    def generatesReportWhichIncludesContentsOfEachTestResultFile() {
-        given:
-        def testTestResults = buildResults {
-            testClassResult("org.gradle.Test") {
-                testcase("test1") {
-                    duration = 1
+    TestResultsProvider passingBuildResults() {
+        buildResults {
+            testClassResult("org.gradle.passing.Passed") {
+                testcase("passed") {
+                    duration = 1000;
                 }
-                testcase("test2") {
-                    duration = 4
+            }
+            testClassResult("org.gradle.passing.subpackage.AlsoPassed") {
+                testcase("passedToo") {
+                    duration = 1000;
+                    stdout "this is\nstandard output"
+                    stderr "this is\nstandard error"
                 }
-                stdout = "this is\nstandard output"
-                stderr = "this is\nstandard error"
             }
-            testClassResult("org.gradle.Test2") {
-                testcase("test3") {
-                    duration = 102001
+        }
+    }
+
+    TestResultsProvider failingBuildResults() {
+        buildResults {
+            testClassResult("org.gradle.passing.Passed") {
+                testcase("passed") {
+                    duration = 1000;
                 }
             }
-            testClassResult("org.gradle.sub.Test") {
-                testcase("test4") {
-                    duration = 12900
+            testClassResult("org.gradle.passing.AlsoPassed") {
+                testcase("passedToo") {
+                    duration = 1000;
+                    stdout "this is\nstandard output"
+                    stderr "this is\nstandard error"
+                }
+            }
+            testClassResult("org.gradle.ignoring.SomeIgnoredSomePassed") {
+                testcase("passed") {
+                    duration = 1000;
+                }
+                testcase("ignored") {
+                    duration = 1000;
+                    ignore()
+                }
+            }
+            testClassResult("org.gradle.failing.SomeIgnoredSomePassedSomeFailed") {
+                testcase("passed") {
+                    duration = 1000;
+                }
+                testcase("ignored") {
+                    duration = 1000;
+                    ignore()
+                }
+                testcase("failed") {
+                    duration = 1000;
+                    failure("something failed", "this is the failure\nat someClass")
                 }
             }
         }
+    }
+
+
+    def generatesReportWithAggregatedIndexPageForBuildWithNoFailures() {
+        given:
+        def testTestResults = passingBuildResults()
 
         when:
         report.generateReport(testTestResults, reportDir)
 
         then:
         def index = results(indexFile)
-        index.assertHasTests(4)
+        index.assertHasTests(2)
         index.assertHasFailures(0)
+        index.assertHasIgnored(0)
         index.assertHasSuccessRate(100)
-        index.assertHasDuration("1m54.91s")
-        index.assertHasLinkTo('org.gradle')
-        index.assertHasLinkTo('org.gradle.sub')
-        index.assertHasLinkTo('org.gradle.Test', 'org.gradle.Test')
-
-        reportDir.file("style.css").assertIsFile()
-
-        def packageFile = results(reportDir.file('org.gradle.html'))
-        packageFile.assertHasTests(3)
-        packageFile.assertHasFailures(0)
-        packageFile.assertHasSuccessRate(100)
-        packageFile.assertHasDuration("1m42.01s")
-        packageFile.assertHasLinkTo('org.gradle.Test', 'Test')
-        packageFile.assertHasLinkTo('org.gradle.Test2', 'Test2')
-
-        def testClassFile = results(reportDir.file('org.gradle.Test.html'))
-        testClassFile.assertHasTests(2)
-        testClassFile.assertHasFailures(0)
-        testClassFile.assertHasSuccessRate(100)
-        testClassFile.assertHasDuration("0.005s")
-        testClassFile.assertHasTest('test1')
-        testClassFile.assertHasTest('test2')
-        testClassFile.assertHasStandardOutput('this is\nstandard output')
-        testClassFile.assertHasStandardError('this is\nstandard error')
-    }
+        index.assertHasDuration("2.000s")
+        index.assertHasOverallResult("success")
+        index.assertHasNoFailedTests()
+        index.assertHasNoIgnoredTests()
+
+        def passingPackageDetails = index.packageDetails("org.gradle.passing");
+        passingPackageDetails.assertNumberOfTests(1);
+        passingPackageDetails.assertNumberOfFailures(0);
+        passingPackageDetails.assertNumberOfIgnored(0);
+        passingPackageDetails.assertDuration("1.000s");
+        passingPackageDetails.assertSuccessRate("100%");
+        passingPackageDetails.assertPassed()
+        passingPackageDetails.assertLinksTo("packages/org.gradle.passing.html");
+
+        def passingSubPackageDetails = index.packageDetails("org.gradle.passing.subpackage");
+        passingSubPackageDetails.assertNumberOfTests(1);
+        passingSubPackageDetails.assertNumberOfFailures(0);
+        passingSubPackageDetails.assertNumberOfIgnored(0);
+        passingSubPackageDetails.assertDuration("1.000s");
+        passingSubPackageDetails.assertSuccessRate("100%");
+        passingSubPackageDetails.assertPassed()
+        passingSubPackageDetails.assertLinksTo("packages/org.gradle.passing.subpackage.html");
+
+        def passedClassDetails = index.classDetails("org.gradle.passing.Passed");
+        passedClassDetails.assertNumberOfTests(1);
+        passedClassDetails.assertNumberOfFailures(0);
+        passedClassDetails.assertNumberOfIgnored(0);
+        passedClassDetails.assertDuration("1.000s");
+        passedClassDetails.assertSuccessRate("100%");
+        passedClassDetails.assertPassed()
+        passedClassDetails.assertLinksTo("classes/org.gradle.passing.Passed.html");
+
+        def alsoPassedClassDetails = index.classDetails("org.gradle.passing.subpackage.AlsoPassed");
+        alsoPassedClassDetails.assertNumberOfTests(1);
+        alsoPassedClassDetails.assertNumberOfFailures(0);
+        alsoPassedClassDetails.assertNumberOfIgnored(0);
+        alsoPassedClassDetails.assertDuration("1.000s");
+        alsoPassedClassDetails.assertSuccessRate("100%");
+        alsoPassedClassDetails.assertPassed()
+        alsoPassedClassDetails.assertLinksTo("classes/org.gradle.passing.subpackage.AlsoPassed.html");
+
+    }
+
+
+    def generatesReportWithAggregatedIndexPageForFailingBuild() {
+        given:
+        def testTestResults = failingBuildResults()
 
-    def generatesReportWhenThereAreFailures() {
+        when:
+        report.generateReport(testTestResults, reportDir)
+
+        then:
+        def index = results(indexFile)
+        index.assertHasTests(7)
+        index.assertHasFailures(1)
+        index.assertHasIgnored(2)
+        index.assertHasSuccessRate(80)
+        index.assertHasDuration("7.000s")
+        index.assertHasOverallResult("failures")
+
+        index.assertHasFailedTest('classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed', 'failed')
+
+        index.assertHasIgnoredTest('classes/org.gradle.ignoring.SomeIgnoredSomePassed', 'ignored')
+        index.assertHasIgnoredTest('classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed', 'ignored')
+
+        def passingPackageDetails = index.packageDetails("org.gradle.passing");
+        passingPackageDetails.assertNumberOfTests(2);
+        passingPackageDetails.assertNumberOfFailures(0);
+        passingPackageDetails.assertNumberOfIgnored(0);
+        passingPackageDetails.assertDuration("2.000s");
+        passingPackageDetails.assertSuccessRate("100%");
+        passingPackageDetails.assertPassed()
+        passingPackageDetails.assertLinksTo("packages/org.gradle.passing.html");
+
+        def ignoringPackageDetails = index.packageDetails("org.gradle.ignoring");
+        ignoringPackageDetails.assertNumberOfTests(2);
+        ignoringPackageDetails.assertNumberOfFailures(0);
+        ignoringPackageDetails.assertNumberOfIgnored(1);
+        ignoringPackageDetails.assertDuration("2.000s");
+        ignoringPackageDetails.assertSuccessRate("100%");
+        ignoringPackageDetails.assertIgnored()
+        ignoringPackageDetails.assertLinksTo("packages/org.gradle.ignoring.html");
+
+        def failingPackageDetails = index.packageDetails("org.gradle.failing");
+        failingPackageDetails.assertNumberOfTests(3);
+        failingPackageDetails.assertNumberOfFailures(1);
+        failingPackageDetails.assertNumberOfIgnored(1);
+        failingPackageDetails.assertDuration("3.000s");
+        failingPackageDetails.assertSuccessRate("50%");
+        failingPackageDetails.assertFailed()
+        failingPackageDetails.assertLinksTo("packages/org.gradle.failing.html");
+
+        def passedClassDetails = index.classDetails("org.gradle.passing.Passed");
+        passedClassDetails.assertNumberOfTests(1);
+        passedClassDetails.assertNumberOfFailures(0);
+        passedClassDetails.assertNumberOfIgnored(0);
+        passedClassDetails.assertDuration("1.000s");
+        passedClassDetails.assertSuccessRate("100%");
+        passedClassDetails.assertPassed()
+        passedClassDetails.assertLinksTo("classes/org.gradle.passing.Passed.html");
+
+        def alsoPassedClassDetails = index.classDetails("org.gradle.passing.AlsoPassed");
+        alsoPassedClassDetails.assertNumberOfTests(1);
+        alsoPassedClassDetails.assertNumberOfFailures(0);
+        alsoPassedClassDetails.assertNumberOfIgnored(0);
+        alsoPassedClassDetails.assertDuration("1.000s");
+        alsoPassedClassDetails.assertSuccessRate("100%");
+        alsoPassedClassDetails.assertPassed()
+        alsoPassedClassDetails.assertLinksTo("classes/org.gradle.passing.AlsoPassed.html");
+
+        def someIgnoredClassDetails = index.classDetails("org.gradle.ignoring.SomeIgnoredSomePassed");
+        someIgnoredClassDetails.assertNumberOfTests(2);
+        someIgnoredClassDetails.assertNumberOfFailures(0);
+        someIgnoredClassDetails.assertNumberOfIgnored(1);
+        someIgnoredClassDetails.assertDuration("2.000s");
+        someIgnoredClassDetails.assertSuccessRate("100%");
+        someIgnoredClassDetails.assertIgnored()
+        someIgnoredClassDetails.assertLinksTo("classes/org.gradle.ignoring.SomeIgnoredSomePassed.html");
+
+        def someFailedClassDetails = index.classDetails("org.gradle.failing.SomeIgnoredSomePassedSomeFailed");
+        someFailedClassDetails.assertNumberOfTests(3);
+        someFailedClassDetails.assertNumberOfFailures(1);
+        someFailedClassDetails.assertNumberOfIgnored(1);
+        someFailedClassDetails.assertDuration("3.000s");
+        someFailedClassDetails.assertSuccessRate("50%");
+        someFailedClassDetails.assertFailed()
+        someFailedClassDetails.assertLinksTo("classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed.html");
+    }
+
+
+    def generatesReportWithAggregatedPackagePages() {
         given:
-        def testTestResults = buildResults {
-            testClassResult("org.gradle.Test") {
-                testcase("test1") {
-                    duration = 0
-                    failure("something failed", "this is the failure\nat someClass")
+        def testTestResults = failingBuildResults()
+
+        when:
+        report.generateReport(testTestResults, reportDir)
+
+        then:
+        def passingPackageFile = results(reportDir.file('packages/org.gradle.passing.html'))
+        passingPackageFile.assertHasTests(2)
+        passingPackageFile.assertHasFailures(0)
+        passingPackageFile.assertHasSuccessRate(100)
+        passingPackageFile.assertHasDuration("2.000s")
+        passingPackageFile.assertHasNoFailedTests()
+        passingPackageFile.assertHasNoIgnoredTests()
+        passingPackageFile.assertHasLinkTo('../index', 'all')
+
+        def passedClassDetails = passingPackageFile.classDetails("Passed");
+        passedClassDetails.assertNumberOfTests(1);
+        passedClassDetails.assertNumberOfFailures(0);
+        passedClassDetails.assertNumberOfIgnored(0);
+        passedClassDetails.assertDuration("1.000s");
+        passedClassDetails.assertSuccessRate("100%");
+        passedClassDetails.assertPassed()
+        passedClassDetails.assertLinksTo("classes/org.gradle.passing.Passed.html");
+
+        def alsoPassedClassDetails = passingPackageFile.classDetails("AlsoPassed");
+        alsoPassedClassDetails.assertNumberOfTests(1);
+        alsoPassedClassDetails.assertNumberOfFailures(0);
+        alsoPassedClassDetails.assertNumberOfIgnored(0);
+        alsoPassedClassDetails.assertDuration("1.000s");
+        alsoPassedClassDetails.assertSuccessRate("100%");
+        alsoPassedClassDetails.assertPassed()
+        alsoPassedClassDetails.assertLinksTo("classes/org.gradle.passing.AlsoPassed.html");
+
+        def ignoredPackageFile = results(reportDir.file('packages/org.gradle.ignoring.html'))
+        ignoredPackageFile.assertHasTests(2)
+        ignoredPackageFile.assertHasFailures(0)
+        ignoredPackageFile.assertHasIgnored(1)
+        ignoredPackageFile.assertHasSuccessRate(100)
+        ignoredPackageFile.assertHasDuration("2.000s")
+        passingPackageFile.assertHasNoFailedTests()
+        ignoredPackageFile.assertHasIgnoredTest('../classes/org.gradle.ignoring.SomeIgnoredSomePassed', 'ignored')
+        ignoredPackageFile.assertHasLinkTo('../index', 'all')
+
+        def someIgnoredClassDetails = ignoredPackageFile.classDetails("SomeIgnoredSomePassed");
+        someIgnoredClassDetails.assertNumberOfTests(2);
+        someIgnoredClassDetails.assertNumberOfFailures(0);
+        someIgnoredClassDetails.assertNumberOfIgnored(1);
+        someIgnoredClassDetails.assertDuration("2.000s");
+        someIgnoredClassDetails.assertSuccessRate("100%");
+        someIgnoredClassDetails.assertIgnored()
+        someIgnoredClassDetails.assertLinksTo("classes/org.gradle.passing.SomeIgnoredSomePassed.html");
+
+        def failingPackageFile = results(reportDir.file('packages/org.gradle.failing.html'))
+        failingPackageFile.assertHasTests(3)
+        failingPackageFile.assertHasFailures(1)
+        failingPackageFile.assertHasIgnored(1)
+        failingPackageFile.assertHasSuccessRate(50)
+        failingPackageFile.assertHasDuration("3.000s")
+        failingPackageFile.assertHasFailedTest('../classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed', 'failed')
+        failingPackageFile.assertHasIgnoredTest('../classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed', 'ignored')
+        failingPackageFile.assertHasLinkTo('../index', 'all')
+
+        def someFailedClassDetails = failingPackageFile.classDetails("SomeIgnoredSomePassedSomeFailed");
+        someFailedClassDetails.assertNumberOfTests(3);
+        someFailedClassDetails.assertNumberOfFailures(1);
+        someFailedClassDetails.assertNumberOfIgnored(1);
+        someFailedClassDetails.assertDuration("3.000s");
+        someFailedClassDetails.assertSuccessRate("50%");
+        someFailedClassDetails.assertFailed()
+        someFailedClassDetails.assertLinksTo("classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed.html");
+    }
+
+
+    def generatesReportWithClassPages() {
+        given:
+        def testTestResults = failingBuildResults()
+
+        when:
+        report.generateReport(testTestResults, reportDir)
+
+        then:
+        def passedClassFile = results(reportDir.file('classes/org.gradle.passing.Passed.html'))
+        passedClassFile.assertHasTests(1)
+        passedClassFile.assertHasFailures(0)
+        passedClassFile.assertHasIgnored(0)
+        passedClassFile.assertHasSuccessRate(100)
+        passedClassFile.assertHasDuration("1.000s")
+        passedClassFile.assertHasLinkTo('../index', 'all')
+        passedClassFile.assertHasLinkTo('../packages/org.gradle.passing', 'org.gradle.passing')
+
+        def passedTestDetails = passedClassFile.testDetails('passed')
+        passedTestDetails.assertDuration("1.000s")
+        passedTestDetails.assertPassed()
+
+        def alsoPassedClassFile = results(reportDir.file('classes/org.gradle.passing.AlsoPassed.html'))
+        alsoPassedClassFile.assertHasTests(1)
+        alsoPassedClassFile.assertHasFailures(0)
+        alsoPassedClassFile.assertHasIgnored(0)
+        alsoPassedClassFile.assertHasSuccessRate(100)
+        alsoPassedClassFile.assertHasDuration("1.000s")
+        alsoPassedClassFile.assertHasLinkTo('../index', 'all')
+        alsoPassedClassFile.assertHasLinkTo('../packages/org.gradle.passing', 'org.gradle.passing')
+        alsoPassedClassFile.assertHasStandardOutput('this is\nstandard output')
+        alsoPassedClassFile.assertHasStandardError('this is\nstandard error')
+
+        def alsoPassedTestDetails = alsoPassedClassFile.testDetails('passedToo')
+        alsoPassedTestDetails.assertDuration("1.000s")
+        alsoPassedTestDetails.assertPassed()
+
+        def someIgnoredClassFile = results(reportDir.file('classes/org.gradle.ignoring.SomeIgnoredSomePassed.html'))
+        someIgnoredClassFile.assertHasTests(2)
+        someIgnoredClassFile.assertHasFailures(0)
+        someIgnoredClassFile.assertHasIgnored(1)
+        someIgnoredClassFile.assertHasSuccessRate(100)
+        someIgnoredClassFile.assertHasDuration("2.000s")
+        someIgnoredClassFile.assertHasLinkTo('../index', 'all')
+        someIgnoredClassFile.assertHasLinkTo('../packages/org.gradle.ignoring', 'org.gradle.ignoring')
+
+        def passedInIgnoredTestDetails = someIgnoredClassFile.testDetails('passed')
+        passedInIgnoredTestDetails.assertDuration("1.000s")
+        passedInIgnoredTestDetails.assertPassed()
+
+        def ignoredTestDetails = someIgnoredClassFile.testDetails('ignored')
+        ignoredTestDetails.assertDuration("-") //is this right? it seems an ignored test may still have a duration?
+        ignoredTestDetails.assertIgnored()
+
+        def failingClassFile = results(reportDir.file('classes/org.gradle.failing.SomeIgnoredSomePassedSomeFailed.html'))
+        failingClassFile.assertHasTests(3)
+        failingClassFile.assertHasFailures(1)
+        failingClassFile.assertHasIgnored(1)
+        failingClassFile.assertHasSuccessRate(50)
+        failingClassFile.assertHasDuration("3.000s")
+        failingClassFile.assertHasLinkTo('../index', 'all')
+        failingClassFile.assertHasLinkTo('../packages/org.gradle.failing', 'org.gradle.failing')
+
+        def passedInFailingTestDetails = failingClassFile.testDetails('passed')
+        passedInFailingTestDetails.assertDuration("1.000s")
+        passedInFailingTestDetails.assertPassed()
+
+        def ignoredInFailingTestDetails = failingClassFile.testDetails('ignored')
+        ignoredInFailingTestDetails.assertDuration("-") //is this right? it seems an ignored test may still have a duration?
+        ignoredInFailingTestDetails.assertIgnored()
+
+        def failingTestDetails = failingClassFile.testDetails('failed')
+        failingTestDetails.assertDuration("1.000s")
+        failingTestDetails.assertFailed()
+
+        failingClassFile.assertHasFailure('failed', 'something failed\n\nthis is the failure\nat someClass\n')
+    }
+
+    TestResultsProvider aggregatedBuildResultsRun1() {
+        buildResults {
+            testClassResult("org.gradle.aggregation.FooTest") {
+                testcase("first") {
+                    duration = 1000;
                 }
-                testcase("test2") {
-                    duration = 0
-                    failure("a multi-line\nmessage\"", "this is a failure.")
+            }
+            testClassResult("org.gradle.aggregation.BarTest") {
+                testcase("second") {
+                    duration = 1000;
+                    stdout "this is\nstandard output"
+                    stderr "this is\nstandard error"
                 }
-                stdout = "this is\nstandard output"
-                stderr = "this is\nstandard error"
             }
+        }
+    }
 
-            testClassResult("org.gradle.Test2") {
-                testcase("test1") {
-                    duration = 0
+    TestResultsProvider aggregatedBuildResultsRun2(methodNameSuffix = "") {
+        buildResults {
+            testClassResult("org.gradle.aggregation.FooTest") {
+                testcase("first" + methodNameSuffix) {
+                    duration = 1000;
                 }
             }
-            testClassResult("org.gradle.sub.Test") {
-                testcase("test1") {
-                    duration = 0
+            testClassResult("org.gradle.aggregation.BarTest") {
+                testcase("second" + methodNameSuffix) {
+                    duration = 1100;
+                    stdout "failed on second run\nstandard output"
+                    stderr "failed on second run\nstandard error"
+                    failure("something failed", "this is the failure\nat someClass")
                 }
             }
         }
+    }
+
+    def aggregateSameTestsRunWithDifferentResults() {
+        given:
+        def firstTestResults = aggregatedBuildResultsRun1()
+        def secondTestResults = aggregatedBuildResultsRun2()
+
         when:
-        report.generateReport(testTestResults, reportDir)
+        report.generateReport(new AggregateTestResultsProvider([firstTestResults, secondTestResults]), reportDir)
 
         then:
-        def index = results(indexFile)
-        index.assertHasTests(4)
-        index.assertHasFailures(2)
-        index.assertHasSuccessRate(50)
-        index.assertHasFailedTest('org.gradle.Test', 'test1')
-
-        def packageFile = results(reportDir.file('org.gradle.html'))
-        packageFile.assertHasTests(3)
-        packageFile.assertHasFailures(2)
-        packageFile.assertHasSuccessRate(33)
-        packageFile.assertHasFailedTest('org.gradle.Test', 'test1')
-
-        def testClassFile = results(reportDir.file('org.gradle.Test.html'))
-        testClassFile.assertHasTests(2)
-        testClassFile.assertHasFailures(2)
-        testClassFile.assertHasSuccessRate(0)
-        testClassFile.assertHasTest('test1')
-        testClassFile.assertHasFailure('test1', 'this is the failure\nat someClass\n')
-        testClassFile.assertHasTest('test2')
-        testClassFile.assertHasFailure('test2', 'this is a failure.')
+        def passedClassFile = results(reportDir.file('classes/org.gradle.aggregation.FooTest.html'))
+        passedClassFile.assertHasTests(2)
+        passedClassFile.allTestDetails('first').size() == 2
+        passedClassFile.assertHasFailures(0)
+        passedClassFile.assertHasIgnored(0)
+        passedClassFile.assertHasSuccessRate(100)
+        passedClassFile.assertHasLinkTo('../index', 'all')
+        passedClassFile.assertHasLinkTo('../packages/org.gradle.aggregation', 'org.gradle.aggregation')
+
+        def mixedClassFile = results(reportDir.file('classes/org.gradle.aggregation.BarTest.html'))
+        mixedClassFile.assertHasTests(2)
+        mixedClassFile.assertHasFailures(1)
+        mixedClassFile.assertHasIgnored(0)
+        mixedClassFile.assertHasSuccessRate(50)
+        mixedClassFile.assertHasLinkTo('../index', 'all')
+        mixedClassFile.assertHasLinkTo('../packages/org.gradle.aggregation', 'org.gradle.aggregation')
+
+        def failingTestDetails = mixedClassFile.allTestDetails('second')
+        failingTestDetails.any { it -> it.failed() }
+
+        def failingPackageFile = results(reportDir.file('packages/org.gradle.aggregation.html'))
+        failingPackageFile.assertHasFailedTest('../classes/org.gradle.aggregation.BarTest', 'second')
+
+        mixedClassFile.assertHasFailure('second', 'something failed\n\nthis is the failure\nat someClass\n')
     }
 
-    def generatesReportWhenThereAreIgnoredTests() {
+    def aggregateSameTestsDifferentMethodsRunWithDifferentResults() {
         given:
-        def testTestResults = buildResults {
-            testClassResult("org.gradle.Test") {
-                testcase("test1") {
-                    resultType = TestResult.ResultType.SKIPPED
-                }
-            }
-        }
+        def firstTestResults = aggregatedBuildResultsRun1()
+        def secondTestResults = aggregatedBuildResultsRun2('Alternative')
+
         when:
-        report.generateReport(testTestResults, reportDir)
+        report.generateReport(new AggregateTestResultsProvider([firstTestResults, secondTestResults]), reportDir)
 
         then:
-        def index = results(indexFile)
-        index.assertHasTests(1)
-        index.assertHasFailures(0)
-        index.assertHasSuccessRate(100)
-
-        def packageFile = results(reportDir.file('org.gradle.html'))
-        packageFile.assertHasTests(1)
-        packageFile.assertHasFailures(0)
-        packageFile.assertHasSuccessRate(100)
-
-        def testClassFile = results(reportDir.file('org.gradle.Test.html'))
-        testClassFile.assertHasTests(1)
-        testClassFile.assertHasFailures(0)
-        testClassFile.assertHasSuccessRate(100)
-        testClassFile.assertHasTest('test1')
-        testClassFile.assertTestIgnored('test1')
+        def passedClassFile = results(reportDir.file('classes/org.gradle.aggregation.FooTest.html'))
+        passedClassFile.assertHasTests(2)
+        passedClassFile.testDetails('first').assertPassed()
+        passedClassFile.testDetails('firstAlternative').assertPassed()
+        passedClassFile.assertHasFailures(0)
+        passedClassFile.assertHasIgnored(0)
+        passedClassFile.assertHasSuccessRate(100)
+        passedClassFile.assertHasLinkTo('../index', 'all')
+        passedClassFile.assertHasLinkTo('../packages/org.gradle.aggregation', 'org.gradle.aggregation')
+
+        def mixedClassFile = results(reportDir.file('classes/org.gradle.aggregation.BarTest.html'))
+        mixedClassFile.assertHasTests(2)
+        mixedClassFile.assertHasFailures(1)
+        mixedClassFile.assertHasIgnored(0)
+        mixedClassFile.assertHasSuccessRate(50)
+        mixedClassFile.assertHasLinkTo('../index', 'all')
+        mixedClassFile.assertHasLinkTo('../packages/org.gradle.aggregation', 'org.gradle.aggregation')
+
+        def failingTestDetails = mixedClassFile.testDetails('secondAlternative')
+        failingTestDetails.assertDuration("1.100s");
+        failingTestDetails.assertFailed();
+
+        def failingPackageFile = results(reportDir.file('packages/org.gradle.aggregation.html'))
+        failingPackageFile.assertHasFailedTest('../classes/org.gradle.aggregation.BarTest', 'secondAlternative')
+
+        mixedClassFile.assertHasFailure('secondAlternative', 'something failed\n\nthis is the failure\nat someClass\n')
     }
 
     def reportsOnClassesInDefaultPackage() {
@@ -217,11 +526,11 @@ class DefaultTestReportTest extends Specification {
 
         then:
         def index = results(indexFile)
-        index.assertHasLinkTo('default-package')
-        index.assertHasLinkTo('Test')
+        index.assertHasLinkTo('packages/default-package', 'default-package')
+        index.assertHasLinkTo('classes/Test', 'Test')
 
-        def packageFile = results(reportDir.file('default-package.html'))
-        packageFile.assertHasLinkTo('Test')
+        def packageFile = results(reportDir.file('packages/default-package.html'))
+        packageFile.assertHasLinkTo('../classes/Test', 'Test')
     }
 
     def escapesHtmlContentInReport() {
@@ -229,18 +538,18 @@ class DefaultTestReportTest extends Specification {
         def testTestResults = buildResults {
             testClassResult("org.gradle.Test") {
                 testcase("test1 < test2") {
-                    duration = 0
-                    failure("something failed", "<a failure>")
+                    failure("<a failure>", "<a failure>")
+
+                    stdout "</html> & "
+                    stderr "</div> & "
                 }
-                stdout = "</html> & "
-                stderr = "</div> & "
             }
         }
         when:
         report.generateReport(testTestResults, reportDir)
 
         then:
-        def testClassFile = results(reportDir.file('org.gradle.Test.html'))
+        def testClassFile = results(reportDir.file('classes/org.gradle.Test.html'))
         testClassFile.assertHasTest('test1 < test2')
         testClassFile.assertHasFailure('test1 < test2', '<a failure>')
         testClassFile.assertHasStandardOutput('</html> & ')
@@ -253,23 +562,23 @@ class DefaultTestReportTest extends Specification {
             testClassResult("org.gradle.Test") {
                 testcase('\u0107') {
                     duration = 0
+                    stdout "out:\u0256"
+                    stderr "err:\u0102"
                 }
-                stdout = "out:\u0256"
-                stderr = "err:\u0102"
             }
         }
         when:
         report.generateReport(testTestResults, reportDir)
 
         then:
-        def testClassFile = results(reportDir.file('org.gradle.Test.html'))
+        def testClassFile = results(reportDir.file('classes/org.gradle.Test.html'))
         testClassFile.assertHasTest('\u0107')
         testClassFile.assertHasStandardOutput('out:\u0256')
         testClassFile.assertHasStandardError('err:\u0102')
     }
 
     def results(TestFile file) {
-        return new TestResultsFixture(file)
+        return new HtmlTestResultsFixture(file)
     }
 
     def emptyResultSet() {
@@ -277,201 +586,4 @@ class DefaultTestReportTest extends Specification {
     }
 }
 
-class TestResultsBuilder implements TestResultsProvider {
-    def testClasses = [:]
-
-    void testClassResult(String className, Closure configClosure) {
-        BuildableTestClassResult testSuite = new BuildableTestClassResult(className, System.currentTimeMillis())
-        ConfigureUtil.configure(configClosure, testSuite)
-
-        testClasses[className] = testSuite
-    }
-
-    void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
-        if (destination == TestOutputEvent.Destination.StdOut) {
-            writer.append(testClasses[className].stdout);
-        } else if (destination == TestOutputEvent.Destination.StdErr) {
-            writer.append(testClasses[className].stderr);
-        }
-    }
-
-    void visitClasses(Action<? super TestClassResult> visitor) {
-        testClasses.values().each {
-            visitor.execute(it)
-        }
-    }
-
-    boolean hasOutput(String className, TestOutputEvent.Destination destination) {
-        if (destination == TestOutputEvent.Destination.StdOut) {
-            return testClasses[className].stdout != null && testClasses[className].stdout.length() != 0
-        } else if (destination == TestOutputEvent.Destination.StdErr) {
-            return testClasses[className].stderr != null && testClasses[className].stderr.length() != 0
-        }
-    }
-
-    private static class BuildableTestClassResult extends TestClassResult {
-        String stderr;
-        String stdout;
-
-        BuildableTestClassResult(String className, long startTime) {
-            super(className, startTime)
-        }
-
-        TestMethodResult testcase(String name) {
-            BuildableTestMethodResult methodResult = new BuildableTestMethodResult(name, new SimpleTestResult())
-            add(methodResult)
-            return methodResult
-        }
-
-        def testcase(String name, Closure configClosure) {
-            BuildableTestMethodResult methodResult = testcase(name);
-            ConfigureUtil.configure(configClosure, methodResult)
-            return methodResult
-        }
-    }
-
-    private static class BuildableTestMethodResult extends TestMethodResult {
-        long duration
-        List<Throwable> exceptions = []
-        TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
-
-        BuildableTestMethodResult(String name, TestResult result) {
-            super(name, result)
-            duration = result.endTime - result.startTime;
-        }
-
-        void failure(String message, String text) {
-            exceptions.add(new ResultException(message, text));
-        }
-    }
-
-    private static class ResultException extends Exception {
-        private final String message
-        private final String text
-
-        public ResultException(String message, String text) {
-            this.text = text
-            this.message = message
-        }
-
-        public String toString() {
-            return message
-        }
-
-        public void printStackTrace(PrintWriter s) {
-            s.print(text);
-        }
-    }
-}
 
-class TestResultsFixture {
-    Document content
-
-    TestResultsFixture(TestFile file) {
-        file.assertIsFile()
-        content = Jsoup.parse(file, null)
-    }
-
-    void assertHasTests(int tests) {
-        def testDiv = content.select("div#tests")
-        assert testDiv != null
-        def counter = testDiv.select("div.counter")
-        assert counter != null
-        assert counter.text() == tests as String
-    }
-
-    void assertHasFailures(int tests) {
-        def testDiv = content.select("div#failures")
-        assert testDiv != null
-        def counter = testDiv.select("div.counter")
-        assert counter != null
-        assert counter.text() == tests as String
-    }
-
-    void assertHasDuration(String duration) {
-        def testDiv = content.select("div#duration")
-        assert testDiv != null
-        def counter = testDiv.select("div.counter")
-        assert counter != null
-        assert counter.text() == duration as String
-
-    }
-
-    void assertHasNoDuration() {
-        def testDiv = content.select("div#duration")
-        assert testDiv != null
-        def counter = testDiv.select("div.counter")
-        assert counter != null
-        assert counter.text() == "-"
-    }
-
-    void assertHasSuccessRate(int rate) {
-        def testDiv = content.select("div#successRate")
-        assert testDiv != null
-        def counter = testDiv.select("div.percent")
-        assert counter != null
-        assert counter.text() == "${rate}%"
-    }
-
-    void assertHasNoSuccessRate() {
-        def testDiv = content.select("div#successRate")
-        assert testDiv != null
-        def counter = testDiv.select("div.percent")
-        assert counter != null
-        assert counter.text() == "-"
-    }
-
-    void assertHasNoNavLinks() {
-        assert findTab('Packages').isEmpty()
-    }
-
-    void assertHasLinkTo(String target, String display = target) {
-        assert content.select("a[href=${target}.html]").find{it.text() == display}
-    }
-
-    void assertHasFailedTest(String className, String testName) {
-        def tab = findTab('Failed tests')
-        assert tab != null
-        assert tab.select("a[href=${className}.html#$testName]").find{it.text() == testName}
-    }
-
-    void assertHasTest(String testName) {
-        assert findTestDetails(testName)
-    }
-
-    void assertTestIgnored(String testName) {
-        def row = findTestDetails(testName)
-        assert row.select("tr > td:eq(2)").text() == 'ignored'
-    }
-
-    void assertHasFailure(String testName, String stackTrace) {
-        def detailsRow = findTestDetails(testName)
-        assert detailsRow.select("tr > td:eq(2)").text() == 'failed'
-        def tab = findTab('Failed tests')
-        assert tab != null && !tab.isEmpty()
-        assert tab.select("pre").find {it.text() == stackTrace.trim() }
-    }
-
-    private def findTestDetails(String testName) {
-        def tab = findTab('Tests')
-        def anchor = tab.select("TD").find {it.text() == testName}
-        return anchor?.parent()
-    }
-
-    void assertHasStandardOutput(String stdout) {
-        def tab = findTab('Standard output')
-        assert tab != null
-        assert tab.select("SPAN > PRE").find{it.text() == stdout.trim() }
-    }
-
-    void assertHasStandardError(String stderr) {
-        def tab = findTab('Standard error')
-        assert tab != null
-        assert tab.select("SPAN > PRE").find{it.text() == stderr.trim() }
-    }
-
-    private def findTab(String title) {
-        def tab = content.select("div.tab:has(h2:contains($title))")
-        return tab
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormatTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormatTest.java
index 17d0caa..76a032a 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormatTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/LocaleSafeDecimalFormatTest.java
@@ -23,9 +23,6 @@ import java.util.Locale;
 
 import static junit.framework.Assert.assertEquals;
 
-/**
- * @author Szczepan Faber, @date: 09.03.11
- */
 public class LocaleSafeDecimalFormatTest {
 
     Locale defaultLocale = Locale.getDefault();
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProviderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProviderTest.groovy
new file mode 100644
index 0000000..ea79128
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProviderTest.groovy
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.api.Action
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.api.tasks.testing.TestResult
+import spock.lang.Specification
+
+class AggregateTestResultsProviderTest extends Specification {
+    def provider1 = Mock(TestResultsProvider)
+    def provider2 = Mock(TestResultsProvider)
+    def provider = new AggregateTestResultsProvider([provider1, provider2])
+
+    def "visits classes from each provider and reassigns class ids"() {
+        def action = Mock(Action)
+        def class1 = Stub(TestClassResult) {
+            getClassName() >> 'class-1'
+        }
+        def class2 = Stub(TestClassResult) {
+            getClassName() >> 'class-2'
+        }
+
+        when:
+        provider.visitClasses(action)
+
+        then:
+        1 * provider1.visitClasses(_) >> { Action a -> a.execute(class1) }
+        1 * provider2.visitClasses(_) >> { Action a -> a.execute(class2) }
+        // TODO(radimk): should not assume order
+        1 * action.execute(_) >> { TestClassResult r ->
+            assert r.id == 1
+            assert r.className == 'class-1'
+        }
+        1 * action.execute(_) >> { TestClassResult r ->
+            assert r.id == 2
+            assert r.className == 'class-2'
+        }
+        0 * action._
+    }
+
+    def "maps class id to original id when fetching test output"() {
+        def writer = Stub(Writer)
+        def class1 = Stub(TestClassResult) {
+            getId() >> 12
+            getClassName() >> 'class-1'
+        }
+        def class2 = Stub(TestClassResult) {
+            getId() >> 12
+            getClassName() >> 'class-2'
+        }
+
+        when:
+        provider.visitClasses(Stub(Action))
+
+        then:
+        1 * provider1.visitClasses(_) >> { Action a -> a.execute(class1) }
+        1 * provider2.visitClasses(_) >> { Action a -> a.execute(class2) }
+
+        when:
+        provider.hasOutput(1, TestOutputEvent.Destination.StdOut)
+        provider.writeAllOutput(1, TestOutputEvent.Destination.StdOut, writer)
+        provider.writeTestOutput(1, 11, TestOutputEvent.Destination.StdOut, writer)
+        provider.writeNonTestOutput(1, TestOutputEvent.Destination.StdOut, writer)
+
+        then:
+        1 * provider1.hasOutput(12, TestOutputEvent.Destination.StdOut)
+        1 * provider1.writeAllOutput(12, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider1.writeTestOutput(12, 11, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider1.writeNonTestOutput(12, TestOutputEvent.Destination.StdOut, writer)
+
+        when:
+        provider.hasOutput(2, TestOutputEvent.Destination.StdOut)
+        provider.writeAllOutput(2, TestOutputEvent.Destination.StdOut, writer)
+
+        then:
+        1 * provider2.hasOutput(12, TestOutputEvent.Destination.StdOut)
+        1 * provider2.writeAllOutput(12, TestOutputEvent.Destination.StdOut, writer)
+    }
+
+    def "processes duplicate classes"() {
+        def action = Mock(Action)
+        def class1 = Stub(TestClassResult) {
+            getClassName() >> 'class-1'
+        }
+        def class2 = Stub(TestClassResult) {
+            getClassName() >> 'class-1'
+        }
+
+        when:
+        provider.visitClasses(action)
+
+        then:
+        1 * provider1.visitClasses(_) >> { Action a -> a.execute(class1) }
+        1 * provider2.visitClasses(_) >> { Action a -> a.execute(class2) }
+        1 * action.execute(_) >> { TestClassResult r ->
+            assert r.id == 1
+            assert r.className == 'class-1'
+        }
+        0 * action._
+    }
+
+    def "merge methods in duplicate classes"() {
+        final long startTimeSooner = 122000
+        final long startTimeLater = 123000
+        def action = Mock(Action)
+        def class1 = Stub(TestClassResult) {
+            getClassName() >> 'class-1'
+            getResults() >> Collections.singletonList(new TestMethodResult(101, 'methodFoo', TestResult.ResultType.SUCCESS, 10, 123456))
+            getStartTime() >> startTimeLater
+        }
+        def class2 = Stub(TestClassResult) {
+            getClassName() >> 'class-1'
+            getResults() >> Collections.singletonList(new TestMethodResult(101, 'methodFoo', TestResult.ResultType.FAILURE, 100, 123678))
+            getStartTime() >> startTimeSooner
+        }
+
+        when:
+        provider.visitClasses(action)
+
+        then:
+        1 * provider1.visitClasses(_) >> { Action a -> a.execute(class1) }
+        1 * provider2.visitClasses(_) >> { Action a -> a.execute(class2) }
+        1 * action.execute(_) >> { TestClassResult r ->
+            assert r.id == 1
+            assert r.className == 'class-1'
+            assert r.startTime == startTimeSooner
+            assert r.results.any { TestMethodResult m ->
+                m.name == 'methodFoo' && m.resultType == TestResult.ResultType.SUCCESS
+            }
+            assert r.results.any { TestMethodResult m ->
+                m.name == 'methodFoo' && m.resultType == TestResult.ResultType.FAILURE
+            }
+        }
+        0 * action._
+    }
+
+    def "maps class ids to original id when fetching test output for merged classes"() {
+        def writer = Stub(Writer)
+        def class1 = Stub(TestClassResult) {
+            getId() >> 12
+            getClassName() >> 'class-1'
+        }
+        def class2 = Stub(TestClassResult) {
+            getId() >> 12
+            getClassName() >> 'class-1'
+        }
+
+        when:
+        provider.visitClasses(Stub(Action))
+
+        then:
+        1 * provider1.visitClasses(_) >> { Action a -> a.execute(class1) }
+        1 * provider2.visitClasses(_) >> { Action a -> a.execute(class2) }
+
+        when:
+        provider.hasOutput(1, TestOutputEvent.Destination.StdOut)
+        provider.writeAllOutput(1, TestOutputEvent.Destination.StdOut, writer)
+        provider.writeTestOutput(1, 11, TestOutputEvent.Destination.StdOut, writer)
+        provider.writeNonTestOutput(1, TestOutputEvent.Destination.StdOut, writer)
+
+        then:
+        1 * provider1.hasOutput(12, TestOutputEvent.Destination.StdOut)
+        1 * provider1.writeAllOutput(12, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider1.writeTestOutput(12, 11, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider1.writeNonTestOutput(12, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider2.hasOutput(12, TestOutputEvent.Destination.StdOut)
+        1 * provider2.writeAllOutput(12, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider2.writeTestOutput(12, 11, TestOutputEvent.Destination.StdOut, writer)
+        1 * provider2.writeNonTestOutput(12, TestOutputEvent.Destination.StdOut, writer)
+    }
+
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy
index 1715eb5..80369f5 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy
@@ -16,33 +16,30 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result
 
+import org.gradle.api.Action
+import org.gradle.api.GradleException
 import org.gradle.api.tasks.testing.TestResult
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.GradleException
-import org.gradle.api.Action
 
-/**
- * by Szczepan Faber, created at: 11/19/12
- */
 class Binary2JUnitXmlReportGeneratorSpec extends Specification {
 
     @Rule private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
     private resultsProvider = Mock(TestResultsProvider)
-    private generator = new Binary2JUnitXmlReportGenerator(temp.testDirectory, resultsProvider)
+    private generator = new Binary2JUnitXmlReportGenerator(temp.testDirectory, resultsProvider, TestOutputAssociation.WITH_SUITE)
 
     def setup() {
         generator.saxWriter = Mock(JUnitXmlResultWriter)
     }
 
     def "writes results"() {
-        def fooTest = new TestClassResult('FooTest', 100)
-            .add(new TestMethodResult("foo", Mock(TestResult)))
+        def fooTest = new TestClassResult(1, 'FooTest', 100)
+            .add(new TestMethodResult(1, "foo"))
 
-        def barTest = new TestClassResult('BarTest', 100)
-            .add(new TestMethodResult("bar", Mock(TestResult)))
-            .add(new TestMethodResult("bar2", Mock(TestResult)))
+        def barTest = new TestClassResult(2, 'BarTest', 100)
+            .add(new TestMethodResult(2, "bar"))
+            .add(new TestMethodResult(3, "bar2"))
 
         resultsProvider.visitClasses(_) >> { Action action ->
             action.execute(fooTest)
@@ -59,8 +56,8 @@ class Binary2JUnitXmlReportGeneratorSpec extends Specification {
     }
 
     def "adds context information to the failure if something goes wrong"() {
-        def fooTest = new TestClassResult('FooTest', 100)
-                .add(new TestMethodResult("foo", Mock(TestResult)))
+        def fooTest = new TestClassResult(1, 'FooTest', 100)
+                .add(new TestMethodResult(1, "foo"))
 
         resultsProvider.visitClasses(_) >> { Action action ->
             action.execute(fooTest)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriterSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriterSpec.groovy
deleted file mode 100644
index aaf046e..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriterSpec.groovy
+++ /dev/null
@@ -1,83 +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.tasks.testing.junit.result
-
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 11/19/12
- */
-class CachingFileWriterSpec extends Specification {
-
-    private @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
-    private writer = new CachingFileWriter(2)
-
-    def cleanup() {
-        writer.closeAll()
-    }
-
-    def "keeps n files open"() {
-        when:
-        writer.write(temp.file("1.txt"), "1")
-
-        then:
-        writer.openFiles.keySet()*.name == ["1.txt"]
-
-        when:
-        writer.write(temp.file("2.txt"), "2")
-
-        then:
-        writer.openFiles.keySet()*.name == ["1.txt", "2.txt"]
-
-        when:
-        writer.write(temp.file("3.txt"), "3")
-
-        then:
-        writer.openFiles.keySet()*.name == ["2.txt", "3.txt"]
-
-        when:
-        writer.closeAll()
-
-        then:
-        writer.openFiles.isEmpty()
-    }
-
-    def "writes to files"() {
-        when:
-        writer.write(temp.file("1.txt"), "1")
-        writer.write(temp.file("2.txt"), "2")
-        writer.write(temp.file("3.txt"), "3")
-        writer.write(temp.file("4.txt"), "4")
-        writer.write(temp.file("1.txt"), "a")
-        writer.write(temp.file("2.txt"), "b")
-        writer.write(temp.file("3.txt"), "c")
-
-        and:
-        writer.closeAll()
-
-        then:
-        writer.openFiles.isEmpty()
-
-        and:
-        temp.file("1.txt").text == '1a'
-        temp.file("2.txt").text == '2b'
-        temp.file("3.txt").text == '3c'
-        temp.file("4.txt").text == '4'
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy
index 112b659..31a2cfc 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy
@@ -16,64 +16,68 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result
 
+import org.gradle.api.internal.tasks.testing.BuildableTestResultsProvider
 import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
 import org.gradle.integtests.fixtures.JUnitTestClassExecutionResult
+import org.gradle.integtests.fixtures.TestResultOutputAssociation
+import org.gradle.internal.SystemProperties
 import spock.lang.Specification
 
-import static java.util.Arrays.asList
+import static TestOutputAssociation.WITH_SUITE
+import static TestOutputAssociation.WITH_TESTCASE
 import static java.util.Collections.emptyList
 import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
 import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
 import static org.gradle.api.tasks.testing.TestResult.ResultType.*
 import static org.hamcrest.Matchers.equalTo
-import org.gradle.internal.SystemProperties
 
-/**
- * by Szczepan Faber, created at: 11/16/12
- */
 class JUnitXmlResultWriterSpec extends Specification {
 
     private provider = Mock(TestResultsProvider)
-    private generator = new JUnitXmlResultWriter("localhost", provider)
+    private mode = WITH_SUITE
+
+    protected JUnitXmlResultWriter getGenerator() {
+        new JUnitXmlResultWriter("localhost", provider, mode)
+    }
 
     private startTime = 1353344968049
 
     def "writes xml JUnit result"() {
-        TestClassResult result = new TestClassResult("com.foo.FooTest", startTime)
-        result.add(new TestMethodResult("some test", new DefaultTestResult(SUCCESS, startTime + 10, startTime + 25, 1, 1, 0, emptyList())))
-        result.add(new TestMethodResult("some test two", new DefaultTestResult(SUCCESS, startTime + 15, startTime + 30, 1, 1, 0, emptyList())))
-        result.add(new TestMethodResult("some failing test", new DefaultTestResult(FAILURE, startTime + 30, startTime + 40, 1, 0, 1, [new RuntimeException("Boo!")])))
-        result.add(new TestMethodResult("some skipped test", new DefaultTestResult(SKIPPED, startTime + 35, startTime + 45, 1, 0, 1, asList())))
+        TestClassResult result = new TestClassResult(1, "com.foo.FooTest", startTime)
+        result.add(new TestMethodResult(1, "some test", SUCCESS, 15, startTime + 25))
+        result.add(new TestMethodResult(2, "some test two", SUCCESS, 15, startTime + 30))
+        result.add(new TestMethodResult(3, "some failing test", FAILURE, 10, startTime + 40).addFailure("failure message", "[stack-trace]", "ExceptionType"))
+        result.add(new TestMethodResult(4, "some skipped test", SKIPPED, 10, startTime + 45))
 
-        provider.writeOutputs("com.foo.FooTest", StdOut, _) >> { args -> args[2].write("1st output message\n2nd output message\n") }
-        provider.writeOutputs("com.foo.FooTest", StdErr, _) >> { args -> args[2].write("err") }
+        provider.writeAllOutput(1, StdOut, _) >> { args -> args[2].write("1st output message\n2nd output message\n") }
+        provider.writeAllOutput(1, StdErr, _) >> { args -> args[2].write("err") }
 
         when:
         def xml = getXml(result)
 
         then:
-        new JUnitTestClassExecutionResult(xml, "com.foo.FooTest")
-            .assertTestCount(4, 1, 0)
-            .assertTestFailed("some failing test", equalTo('java.lang.RuntimeException: Boo!'))
-            .assertTestsSkipped("some skipped test")
-            .assertTestsExecuted("some test", "some test two", "some failing test")
-            .assertStdout(equalTo("""1st output message
+        new JUnitTestClassExecutionResult(xml, "com.foo.FooTest", TestResultOutputAssociation.WITH_SUITE)
+                .assertTestCount(4, 1, 1, 0)
+                .assertTestFailed("some failing test", equalTo('failure message'))
+                .assertTestsSkipped("some skipped test")
+                .assertTestsExecuted("some test", "some test two", "some failing test")
+                .assertStdout(equalTo("""1st output message
 2nd output message
 """))
-            .assertStderr(equalTo("err"))
+                .assertStderr(equalTo("err"))
 
         and:
-        xml.startsWith """<?xml version="1.1" encoding="UTF-8"?>
-<testsuite name="com.foo.FooTest" tests="4" failures="1" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.045">
+        xml == """<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="com.foo.FooTest" tests="4" skipped="1" failures="1" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.045">
   <properties/>
   <testcase name="some test" classname="com.foo.FooTest" time="0.015"/>
   <testcase name="some test two" classname="com.foo.FooTest" time="0.015"/>
   <testcase name="some failing test" classname="com.foo.FooTest" time="0.01">
-    <failure message="java.lang.RuntimeException: Boo!" type="java.lang.RuntimeException">java.lang.RuntimeException: Boo!"""
-
-        xml.endsWith """</failure>
+    <failure message="failure message" type="ExceptionType">[stack-trace]</failure>
+  </testcase>
+  <testcase name="some skipped test" classname="com.foo.FooTest" time="0.01">
+    <skipped/>
   </testcase>
-  <ignored-testcase name="some skipped test" classname="com.foo.FooTest" time="0.01"/>
   <system-out><![CDATA[1st output message
 2nd output message
 ]]></system-out>
@@ -83,16 +87,16 @@ class JUnitXmlResultWriterSpec extends Specification {
     }
 
     def "writes results with empty outputs"() {
-        TestClassResult result = new TestClassResult("com.foo.FooTest", startTime)
-        result.add(new TestMethodResult("some test", new DefaultTestResult(SUCCESS, startTime + 100, startTime + 300, 1, 1, 0, emptyList())))
-        _ * provider.writeOutputs(_, _, _)
+        TestClassResult result = new TestClassResult(1, "com.foo.FooTest", startTime)
+        result.add(new TestMethodResult(1, "some test").completed(new DefaultTestResult(SUCCESS, startTime + 100, startTime + 300, 1, 1, 0, emptyList())))
+        _ * provider.writeAllOutput(_, _, _)
 
         when:
         def xml = getXml(result)
 
         then:
-        xml == """<?xml version="1.1" encoding="UTF-8"?>
-<testsuite name="com.foo.FooTest" tests="1" failures="0" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.3">
+        xml == """<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="com.foo.FooTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.3">
   <properties/>
   <testcase name="some test" classname="com.foo.FooTest" time="0.2"/>
   <system-out><![CDATA[]]></system-out>
@@ -102,31 +106,31 @@ class JUnitXmlResultWriterSpec extends Specification {
     }
 
     def "encodes xml"() {
-        TestClassResult result = new TestClassResult("com.foo.FooTest", startTime)
-        result.add(new TestMethodResult("some test", new DefaultTestResult(FAILURE, 100, 300, 1, 1, 0, [new RuntimeException("<> encoded!")])))
-        provider.writeOutputs(_, StdErr, _) >> { args -> args[2].write("with CDATA end token: ]]> some ascii: ż") }
-        provider.writeOutputs(_, StdOut, _) >> { args -> args[2].write("with CDATA end token: ]]> some ascii: ż") }
+        TestClassResult result = new TestClassResult(1, "com.foo.FooTest", startTime)
+        result.add(new TestMethodResult(1, "some test", FAILURE, 200, 300).addFailure("<> encoded!", "<non ascii: \u0302>", "<Exception>"))
+        provider.writeAllOutput(_, StdErr, _) >> { args -> args[2].write("with CDATA end token: ]]> some ascii: ż") }
+        provider.writeAllOutput(_, StdOut, _) >> { args -> args[2].write("with CDATA end token: ]]> some ascii: ż") }
 
         when:
         def xml = getXml(result)
 
         then:
         //attribute and text is encoded:
-        xml.contains('message="java.lang.RuntimeException: <> encoded!" type="java.lang.RuntimeException">java.lang.RuntimeException: <> encoded!')
+        xml.contains('message="<> encoded!" type="<Exception>"><non ascii: \u0302>')
         //output encoded:
         xml.contains('<system-out><![CDATA[with CDATA end token: ]]]]><![CDATA[> some ascii: ż]]></system-out>')
         xml.contains('<system-err><![CDATA[with CDATA end token: ]]]]><![CDATA[> some ascii: ż]]></system-err>')
     }
 
     def "writes results with no tests"() {
-        TestClassResult result = new TestClassResult("com.foo.IgnoredTest", startTime)
+        TestClassResult result = new TestClassResult(1, "com.foo.IgnoredTest", startTime)
 
         when:
         def xml = getXml(result)
 
         then:
-        xml == """<?xml version="1.1" encoding="UTF-8"?>
-<testsuite name="com.foo.IgnoredTest" tests="0" failures="0" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.0">
+        xml == """<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="com.foo.IgnoredTest" tests="0" skipped="0" failures="0" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.0">
   <properties/>
   <system-out><![CDATA[]]></system-out>
   <system-err><![CDATA[]]></system-err>
@@ -134,6 +138,48 @@ class JUnitXmlResultWriterSpec extends Specification {
 """
     }
 
+    def "can generate with output per test"() {
+        given:
+        mode = WITH_TESTCASE
+        provider = new BuildableTestResultsProvider()
+        def testClass = provider.testClassResult("com.Foo")
+
+        when:
+        testClass.with {
+            stdout "class-out"
+            stderr "class-err"
+            testcase("m1").with {
+                stderr " m1-err-1"
+                stdout " m1-out-1"
+                stdout " m1-out-2"
+                stderr " m1-err-2"
+            }
+            testcase("m2").with {
+                stderr " m2-err-1"
+                stdout " m2-out-1"
+                stdout " m2-out-2"
+                stderr " m2-err-2"
+            }
+        }
+
+        then:
+        getXml(testClass) == """<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="com.Foo" tests="2" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00" hostname="localhost" time="1.0">
+  <properties/>
+  <testcase name="m1" classname="com.Foo" time="0.1">
+    <system-out><![CDATA[ m1-out-1 m1-out-2]]></system-out>
+    <system-err><![CDATA[ m1-err-1 m1-err-2]]></system-err>
+  </testcase>
+  <testcase name="m2" classname="com.Foo" time="0.1">
+    <system-out><![CDATA[ m2-out-1 m2-out-2]]></system-out>
+    <system-err><![CDATA[ m2-err-1 m2-err-2]]></system-err>
+  </testcase>
+  <system-out><![CDATA[class-out]]></system-out>
+  <system-err><![CDATA[class-err]]></system-err>
+</testsuite>
+"""
+    }
+
     def getXml(TestClassResult result) {
         def text = new ByteArrayOutputStream()
         generator.write(result, text)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy
index cd3bbe2..b2aa2a6 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy
@@ -20,19 +20,16 @@ import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
 import org.gradle.api.tasks.testing.TestResult
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 11/19/12
- */
 class TestClassResultSpec extends Specification {
 
     def "provides test class result information"() {
-        def result = new TestClassResult('class', 100)
+        def result = new TestClassResult(1, 'class', 100)
         assert result.duration == 0
 
         when:
-        result.add(new TestMethodResult("foo",   new DefaultTestResult(TestResult.ResultType.SUCCESS, 100, 200, 1, 1, 0, [])))
-        result.add(new TestMethodResult("fail",  new DefaultTestResult(TestResult.ResultType.FAILURE, 250, 300, 1, 0, 1, [new RuntimeException("bar")])))
-        result.add(new TestMethodResult("fail2", new DefaultTestResult(TestResult.ResultType.FAILURE, 300, 450, 1, 0, 1, [new RuntimeException("foo")])))
+        result.add(new TestMethodResult(1, "foo").completed(new DefaultTestResult(TestResult.ResultType.SUCCESS, 100, 200, 1, 1, 0, [])))
+        result.add(new TestMethodResult(2, "fail").completed(new DefaultTestResult(TestResult.ResultType.FAILURE, 250, 300, 1, 0, 1, [new RuntimeException("bar")])))
+        result.add(new TestMethodResult(3, "fail2").completed(new DefaultTestResult(TestResult.ResultType.FAILURE, 300, 450, 1, 0, 1, [new RuntimeException("foo")])))
 
         then:
         result.failuresCount == 2
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializerTest.groovy
deleted file mode 100644
index 8dd7729..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializerTest.groovy
+++ /dev/null
@@ -1,72 +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.tasks.testing.junit.result
-
-import org.gradle.api.tasks.testing.TestOutputEvent
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
-import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
-
-/**
- * by Szczepan Faber, created at: 11/19/12
- */
-class TestOutputSerializerTest extends Specification {
-    @Rule
-    private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
-    private serializer = new TestOutputSerializer(temp.testDirectory)
-
-    def "flushes all output when output finishes"() {
-        when:
-        serializer.onOutput("Class1", StdOut, "[out]")
-        serializer.onOutput("Class2", StdErr, "[err]")
-        serializer.onOutput("Class1", StdErr, "[err]")
-        serializer.onOutput("Class1", StdOut, "[out2]")
-        serializer.finishOutputs()
-
-        then:
-        collectOutput("Class1", StdOut) == "[out][out2]"
-        collectOutput("Class1", StdErr) == "[err]"
-        collectOutput("Class2", StdErr) == "[err]"
-    }
-
-    def "writes nothing for unknown test class"() {
-        when:
-        serializer.finishOutputs()
-
-        then:
-        collectOutput("Unknown", StdErr) == ""
-    }
-
-    def "can query whether output is available for a test class"() {
-        when:
-        serializer.onOutput("Class1", StdOut, "[out]")
-        serializer.finishOutputs()
-
-        then:
-        serializer.hasOutput("Class1", StdOut)
-        !serializer.hasOutput("Class1", StdErr)
-        !serializer.hasOutput("Unknown", StdErr)
-    }
-
-    String collectOutput(String className, TestOutputEvent.Destination destination) {
-        def writer = new StringWriter()
-        serializer.writeOutputs(className, destination, writer)
-        return writer.toString()
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputStoreSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputStoreSpec.groovy
new file mode 100644
index 0000000..f7e4b91
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputStoreSpec.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.api.internal.tasks.testing.junit.result
+
+import org.gradle.api.internal.tasks.testing.DefaultTestOutputEvent
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.test.fixtures.file.WorkspaceTest
+
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
+
+class TestOutputStoreSpec extends WorkspaceTest {
+
+    private output = new TestOutputStore(testDirectory)
+
+    TestDescriptorInternal descriptor(String className, Object testId) {
+        Stub(TestDescriptorInternal) {
+            getClassName() >> className
+            getId() >> testId
+        }
+    }
+
+    def "output for class includes all events with the given class id"() {
+        when:
+        def writer = output.writer()
+        writer.onOutput(1, output(StdOut, "[out-1]"))
+        writer.onOutput(1, 1, output(StdOut, "[out-2]"))
+        writer.onOutput(2, 1, output(StdErr, "[out-3]"))
+        writer.onOutput(1, 1, output(StdErr, "[out-4]"))
+        writer.onOutput(1, 1, output(StdOut, "[out-5]"))
+        writer.onOutput(1, 2, output(StdOut, "[out-6]"))
+        writer.close()
+        def reader = output.reader()
+
+        then:
+        collectAllOutput(reader, 1, StdOut) == "[out-1][out-2][out-5][out-6]"
+        collectAllOutput(reader, 1, StdErr) == "[out-4]"
+        collectAllOutput(reader, 2, StdErr) == "[out-3]"
+    }
+
+    def "output for test includes all events with the given class and method ids"() {
+        when:
+        def writer = output.writer()
+        writer.onOutput(1, output(StdOut, "[out-1]"))
+        writer.onOutput(1, 1, output(StdOut, "[out-2]"))
+        writer.onOutput(2, 1, output(StdErr, "[out-3]"))
+        writer.onOutput(1, 1, output(StdErr, "[out-4]"))
+        writer.onOutput(1, 1, output(StdOut, "[out-5]"))
+        writer.onOutput(1, 2, output(StdOut, "[out-6]"))
+        writer.close()
+        def reader = output.reader()
+
+        then:
+        collectOutput(reader, 1, 1, StdOut) == "[out-2][out-5]"
+        collectOutput(reader, 1, 1, StdErr) == "[out-4]"
+        collectOutput(reader, 1, 2, StdOut) == "[out-6]"
+    }
+
+    def "non-test output includes all events with the given class id and no method id"() {
+        when:
+        def writer = output.writer()
+        writer.onOutput(1, output(StdOut, "[out-1]"))
+        writer.onOutput(1, 1, output(StdOut, "[out-2]"))
+        writer.onOutput(1, output(StdErr, "[out-3]"))
+        writer.onOutput(1, output(StdErr, "[out-4]"))
+        writer.onOutput(1, output(StdOut, "[out-5]"))
+        writer.onOutput(1, 2, output(StdOut, "[out-6]"))
+        writer.onOutput(2, output(StdOut, "[out-6]"))
+        writer.close()
+        def reader = output.reader()
+
+        then:
+        collectOutput(reader, 1, StdOut) == "[out-1][out-5]"
+        collectOutput(reader, 1, StdErr) == "[out-3][out-4]"
+        collectOutput(reader, 2, StdOut) == "[out-6]"
+    }
+
+    def DefaultTestOutputEvent output(TestOutputEvent.Destination destination, String msg) {
+        new DefaultTestOutputEvent(destination, msg)
+    }
+
+    def "writes nothing for unknown test class"() {
+        when:
+        def writer = output.writer()
+        writer.close()
+
+        then:
+        def reader = output.reader()
+        collectAllOutput(reader, 20, StdErr) == ""
+    }
+
+    def "writes nothing for unknown test method"() {
+        when:
+        def writer = output.writer()
+        writer.onOutput(1, 1, output(StdOut, "[out]"))
+        writer.close()
+
+        then:
+        def reader = output.reader()
+        collectOutput(reader, 1, 10, StdOut) == ""
+    }
+
+    def "can query whether output is available for a test class"() {
+        when:
+        def writer = output.writer()
+        writer.onOutput(1, 1, output(StdOut, "[out]"))
+        writer.close()
+        def reader = output.reader()
+
+        then:
+        reader.hasOutput(1, StdOut)
+        !reader.hasOutput(1, StdErr)
+        !reader.hasOutput(2, StdErr)
+
+        cleanup:
+        reader.close()
+    }
+
+    def "can open empty reader"() {
+        // neither file
+        expect:
+        output.reader().close() // no exception
+    }
+
+    def "exception if no output file"() {
+        when:
+        output.indexFile.createNewFile()
+        def reader = output.reader()
+
+        then:
+        thrown(IllegalStateException)
+
+        cleanup:
+        reader?.close()
+    }
+
+    def "exception if no index file, but index"() {
+        when:
+        output.outputsFile.createNewFile()
+        def reader = output.reader()
+
+        then:
+        thrown(IllegalStateException)
+
+        cleanup:
+        reader?.close()
+    }
+
+    String collectAllOutput(TestOutputStore.Reader reader, long classId, TestOutputEvent.Destination destination) {
+        def writer = new StringWriter()
+        reader.writeAllOutput(classId, destination, writer)
+        return writer.toString()
+    }
+
+    String collectOutput(TestOutputStore.Reader reader, long classId, long testId, TestOutputEvent.Destination destination) {
+        def writer = new StringWriter()
+        reader.writeTestOutput(classId, testId, destination, writer)
+        return writer.toString()
+    }
+
+    String collectOutput(TestOutputStore.Reader reader, long classId, TestOutputEvent.Destination destination) {
+        def writer = new StringWriter()
+        reader.writeNonTestOutput(classId, destination, writer)
+        return writer.toString()
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
index dcee91b..c847000 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
@@ -16,12 +16,11 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result
 
-import org.gradle.api.Action
+import org.gradle.api.internal.tasks.testing.*
 import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
+import org.gradle.messaging.remote.internal.PlaceholderException
+import spock.lang.Issue
 import spock.lang.Specification
-import org.gradle.api.internal.tasks.testing.*
 
 import static java.util.Arrays.asList
 import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
@@ -29,60 +28,16 @@ import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
 import static org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE
 import static org.gradle.api.tasks.testing.TestResult.ResultType.SUCCESS
 
-/**
- * by Szczepan Faber, created at: 11/19/12
- */
 class TestReportDataCollectorSpec extends Specification {
-    @Rule
-    private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
-    private TestOutputSerializer outputSerializer = Mock()
-    private TestResultSerializer resultSerializer = Mock()
-    private collector = new TestReportDataCollector(temp.testDirectory, outputSerializer, resultSerializer)
-
-    def "closes output when root finishes"() {
-        def root = new DefaultTestSuiteDescriptor("1", "Suite")
-        def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "Class"), root)
-
-        def dummyResult = new DefaultTestResult(SUCCESS, 0, 0, 0, 0, 0, asList())
-
-        when:
-        collector.afterSuite(clazz, dummyResult)
-
-        then:
-        0 * outputSerializer.finishOutputs()
-
-        when:
-        collector.afterSuite(root, dummyResult)
-
-        then:
-        1 * outputSerializer.finishOutputs()
-    }
-
-    def "writes results when root finishes"() {
-        def root = new DefaultTestSuiteDescriptor("1", "Suite")
-        def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "Class"), root)
-
-        def dummyResult = new DefaultTestResult(SUCCESS, 0, 0, 0, 0, 0, asList())
-
-        when:
-        collector.afterSuite(clazz, dummyResult)
-
-        then:
-        0 * resultSerializer._
-
-        when:
-        collector.afterSuite(root, dummyResult)
-
-        then:
-        1 * resultSerializer.write(_, temp.testDirectory)
-        0 * resultSerializer._
-    }
+    def Map<String, TestClassResult> results = [:]
+    def TestOutputStore.Writer writer = Mock()
+    def collector = new TestReportDataCollector(results, writer)
 
     def "keeps track of test results"() {
         def root = new DefaultTestSuiteDescriptor("1", "Suite")
         def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "FooTest"), root)
         def test1 = new DecoratingTestDescriptor(new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod"), clazz)
-        def result1 = new DefaultTestResult(SUCCESS, 100, 200, 1, 1, 0, asList())
+        def result1 = new DefaultTestResult(SUCCESS, 100, 200, 1, 1, 0, [])
 
         def test2 = new DecoratingTestDescriptor(new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2"), clazz)
         def result2 = new DefaultTestResult(FAILURE, 250, 300, 1, 0, 1, asList(new RuntimeException("Boo!")))
@@ -97,14 +52,11 @@ class TestReportDataCollectorSpec extends Specification {
         collector.afterTest(test1, result1)
         collector.afterTest(test2, result2)
 
-        collector.afterSuite(root, new DefaultTestResult(FAILURE, 0, 500, 2, 1, 1, asList(new RuntimeException("Boo!"))))
-
-        def results = []
-        collector.visitClasses({ results << it } as Action)
+        collector.afterSuite(root, new DefaultTestResult(FAILURE, 0, 500, 2, 1, 1, []))
 
         then:
         results.size() == 1
-        def fooTest = results[0]
+        def fooTest = results.values().toList().first()
         fooTest.className == 'FooTest'
         fooTest.startTime == 100
         fooTest.testsCount == 2
@@ -115,29 +67,156 @@ class TestReportDataCollectorSpec extends Specification {
         fooTest.results.find { it.name == 'testMethod2' && it.endTime == 300 && it.duration == 50 }
     }
 
-    def "writes test outputs"() {
+    def "writes test outputs for interleaved tests"() {
         def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
         def test2 = new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2")
         def suite = new DefaultTestSuiteDescriptor("1", "Suite")
 
         when:
-        collector.onOutput(suite, new DefaultTestOutputEvent(StdOut, "out"))
-        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err"))
-        collector.onOutput(test2, new DefaultTestOutputEvent(StdOut, "out"))
+        collector.onOutput(suite, new DefaultTestOutputEvent(StdOut, "suite-out"))
+        collector.beforeTest(test)
+        collector.beforeTest(test2)
+        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err-1"))
+        collector.onOutput(test2, new DefaultTestOutputEvent(StdOut, "out-2"))
+        collector.onOutput(test, new DefaultTestOutputEvent(StdOut, "out-1"))
+
+        then:
+        1 * writer.onOutput(3, 1, new DefaultTestOutputEvent(StdErr, "err-1"))
+        1 * writer.onOutput(3, 2, new DefaultTestOutputEvent(StdOut, "out-2"))
+        1 * writer.onOutput(3, 1, new DefaultTestOutputEvent(StdOut, "out-1"))
+        0 * writer._
+    }
+
+    def "writes test outputs for class"() {
+        def testClass = new DefaultTestClassDescriptor("1.1.1", "FooTest")
+        def suite = new DefaultTestSuiteDescriptor("1", "Suite")
+
+        when:
+        collector.onOutput(suite, new DefaultTestOutputEvent(StdOut, "suite-out"))
+        collector.onOutput(testClass, new DefaultTestOutputEvent(StdErr, "err-1"))
+        collector.onOutput(testClass, new DefaultTestOutputEvent(StdErr, "err-2"))
+
+        then:
+        1 * writer.onOutput(1, new DefaultTestOutputEvent(StdErr, "err-1"))
+        1 * writer.onOutput(1, new DefaultTestOutputEvent(StdErr, "err-2"))
+        0 * writer._
+    }
+
+    def "collects failures for test"() {
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
+        def failure1 = new RuntimeException("failure1")
+        def failure2 = new IOException("failure2")
+        def result = new DefaultTestResult(SUCCESS, 0, 0, 1, 0, 1, [failure1, failure2])
+
+        when:
+        collector.beforeTest(test)
+        collector.afterTest(test, result)
 
         then:
-        1 * outputSerializer.onOutput("FooTest", StdErr, "err")
-        1 * outputSerializer.onOutput("FooTest", StdOut, "out")
-        0 * outputSerializer._
+        def failures = results["FooTest"].results[0].failures
+        failures.size() == 2
+        failures[0].exceptionType == RuntimeException.name
+        failures[0].message == failure1.toString()
+        failures[0].stackTrace.startsWith(failure1.toString())
+        failures[1].exceptionType == IOException.name
+        failures[1].message == failure2.toString()
+        failures[1].stackTrace.startsWith(failure2.toString())
     }
 
-    def "provides outputs"() {
-        def writer = new StringWriter()
+    def "handle PlaceholderExceptions for test failures"() {
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
+        def failure = new PlaceholderException("OriginalClassName", "failure2", null, "toString()", null, null)
+        def result = new DefaultTestResult(SUCCESS, 0, 0, 1, 0, 1, [failure])
+
+        when:
+        collector.beforeTest(test)
+        collector.afterTest(test, result)
+
+        then:
+        def failures = results["FooTest"].results[0].failures
+        failures.size() == 1
+        failures[0].exceptionType == "OriginalClassName"
+        failures[0].message == "toString()"
+        failures[0].stackTrace.startsWith("toString()")
+    }
+
+    def "handles exception whose toString() method fails"() {
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
+        def failure2 = new RuntimeException("failure2")
+        def failure1 = new RuntimeException("failure1") {
+            @Override
+            String toString() {
+                throw failure2
+            }
+        }
+        def result = new DefaultTestResult(SUCCESS, 0, 0, 1, 0, 1, [failure1])
+
+        when:
+        collector.beforeTest(test)
+        collector.afterTest(test, result)
+
+        then:
+        def failures = results["FooTest"].results[0].failures
+        failures.size() == 1
+        failures[0].message == "Could not determine failure message for exception of type ${failure1.class.name}: ${failure2.toString()}"
+        failures[0].stackTrace.startsWith(failure2.toString())
+    }
+
+    def "handles exception whose printStackTrace() method fails"() {
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
+        def failure2 = new RuntimeException("failure2")
+        def failure1 = new RuntimeException("failure1") {
+            @Override
+            void printStackTrace(PrintWriter s) {
+                throw failure2
+            }
+        }
+        def result = new DefaultTestResult(SUCCESS, 0, 0, 1, 0, 1, [failure1])
+
+        when:
+        collector.beforeTest(test)
+        collector.afterTest(test, result)
+
+        then:
+        def failures = results["FooTest"].results[0].failures
+        failures.size() == 1
+        failures[0].message == failure1.toString()
+        failures[0].stackTrace.startsWith(failure2.toString())
+    }
+
+    def "reports suite failures"() {
+        def root = new DefaultTestSuiteDescriptor("1", "Suite")
+        def testWorker = new DefaultTestSuiteDescriptor("2", "Test Worker 1")
+
+        when:
+        //simulating a scenario with suite failing badly enough so that no tests are executed
+        collector.beforeSuite(root)
+        collector.beforeSuite(testWorker)
+        collector.afterSuite(testWorker, new DefaultTestResult(FAILURE, 50, 450, 2, 1, 1, [new RuntimeException("Boo!")]))
+        collector.afterSuite(root, new DefaultTestResult(FAILURE, 0, 500, 2, 1, 1, []))
+
+        then:
+        results.size() == 1
+        def result = results.values().toList().first()
+        result.className == 'Test Worker 1'
+        result.startTime == 50
+        result.testsCount == 1
+        result.failuresCount == 1
+        result.duration == 400
+        result.results.size() == 1
+        result.results[0].failures.size() == 1
+    }
+
+    @Issue("GRADLE-2730")
+    def "test case timestamp is correct even if output received for given class"() {
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
 
         when:
-        collector.writeOutputs("TestClass", StdErr, writer)
+        collector.beforeTest(test)
+        collector.onOutput(test, new DefaultTestOutputEvent(StdOut, "suite-out"))
+        collector.afterTest(test, new DefaultTestResult(SUCCESS, 100, 200, 1, 1, 0, asList()))
 
         then:
-        1 * outputSerializer.writeOutputs("TestClass", StdErr, writer)
+        results.get("FooTest").startTime == 100
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy
index d554f26..ddb052e 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy
@@ -15,26 +15,23 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.result
 
+import org.gradle.api.Action
+import org.gradle.api.tasks.testing.TestResult
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.tasks.testing.TestResult
-import org.gradle.messaging.remote.internal.PlaceholderException
-import org.gradle.api.Action
 
 class TestResultSerializerTest extends Specification {
     @Rule
     private TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
-    final TestResultSerializer serializer = new TestResultSerializer()
 
     def "can write and read results"() {
-        def class1 = new TestClassResult('Class1', 1234)
-        def failure = new RuntimeException("broken")
-        def method1 = new TestMethodResult("method1", TestResult.ResultType.SUCCESS, 100, 2300, [])
-        def method2 = new TestMethodResult("method2", TestResult.ResultType.FAILURE, 200, 2700, [failure])
+        def class1 = new TestClassResult(1, 'Class1', 1234)
+        def method1 = new TestMethodResult(1, "method1", TestResult.ResultType.SUCCESS, 100, 2300)
+        def method2 = new TestMethodResult(2, "method2", TestResult.ResultType.FAILURE, 200, 2700).addFailure("message", "stack-trace", "ExceptionType")
         class1.add(method1)
         class1.add(method2)
-        def class2 = new TestClassResult('Class2', 5678)
+        def class2 = new TestClassResult(2, 'Class2', 5678)
         def results = [class1, class2]
 
         when:
@@ -52,17 +49,17 @@ class TestResultSerializerTest extends Specification {
         readMethod1.resultType == TestResult.ResultType.SUCCESS
         readMethod1.duration == 100
         readMethod1.endTime == 2300
-        readMethod1.exceptions.empty
+        readMethod1.failures.empty
 
         def readMethod2 = readClass1.results[1]
         readMethod2.name == 'method2'
         readMethod2.resultType == TestResult.ResultType.FAILURE
         readMethod2.duration == 200
         readMethod2.endTime == 2700
-        readMethod2.exceptions.size() == 1
-        readMethod2.exceptions[0].class == failure.class
-        readMethod2.exceptions[0].message == failure.message
-        readMethod2.exceptions[0].stackTrace == failure.stackTrace
+        readMethod2.failures.size() == 1
+        readMethod2.failures[0].exceptionType == "ExceptionType"
+        readMethod2.failures[0].message == "message"
+        readMethod2.failures[0].stackTrace == "stack-trace"
 
         def readClass2 = read[1]
         readClass2.className == 'Class2'
@@ -70,34 +67,11 @@ class TestResultSerializerTest extends Specification {
         readClass2.results.empty
     }
 
-    def "can write and read exceptions that are not serializable"() {
-        def class1 = new TestClassResult('Class1', 1234)
-        def failure = new RuntimeException("broken") {
-            final Object field = new Object()
-        }
-        def method1 = new TestMethodResult("method1", TestResult.ResultType.FAILURE, 200, 2700, [failure])
-        class1.add(method1)
-        def results = [class1]
-
-        when:
-        def read = serialize(results)
-
-        then:
-        read.size() == 1
-        def readClass1 = read[0]
-        def readMethod1 = readClass1.results[0]
-        readMethod1.exceptions.size() == 1
-        readMethod1.exceptions[0].class == PlaceholderException.class
-        readMethod1.exceptions[0].message == failure.message
-        readMethod1.exceptions[0].toString() == failure.toString()
-        readMethod1.exceptions[0].stackTrace == failure.stackTrace
-    }
-
     List<TestClassResult> serialize(Collection<TestClassResult> results) {
-        def dir = tmp.createDir("results")
-        serializer.write(results, dir)
+        def serializer = new TestResultSerializer(tmp.createDir("results"))
+        serializer.write(results)
         def result = []
-        serializer.read(dir, { result << it } as Action)
+        serializer.read({ result << it } as Action)
         return result
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
index 86a2bd3..14520e3 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
@@ -199,15 +199,15 @@ class FullExceptionFormatterTest extends Specification {
 """
     }
 
-    def "formats PlaceholderException's correctly"() {
+    def "formats PlaceholderException correctly"() {
         testLogging.getShowCauses() >> true
         testLogging.getShowStackTraces() >> true
         testLogging.getStackTraceFilters() >> EnumSet.of(TestStackTraceFilter.ENTRY_POINT)
 
-        def cause = new PlaceholderException(RuntimeException.name, "oops", "java.lang.RuntimeException: oops", null, null)
+        def cause = new PlaceholderException(RuntimeException.name, "oops", null, "java.lang.RuntimeException: oops", null, null)
         cause.stackTrace = createGroovyTrace()
 
-        def exception = new PlaceholderException(Exception.name, "ouch", "java.lang.Exception: ouch", null, cause)
+        def exception = new PlaceholderException(Exception.name, "ouch", null, "java.lang.Exception: ouch", null, cause)
         exception.stackTrace = createGroovyTrace()[1..-1]
 
         expect:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
index 83195fa..d38c0d3 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
@@ -58,8 +58,8 @@ class ShortExceptionFormatterTest extends Specification {
 """
     }
 
-    def "formats PlaceholderException's correctly"() {
-        def exception = new PlaceholderException(Exception.class.name, "oops", "java.lang.Exception: oops", null, null)
+    def "formats PlaceholderException correctly"() {
+        def exception = new PlaceholderException(Exception.class.name, "oops", null, "java.lang.Exception: oops", null, null)
         testDescriptor.className = getClass().name
 
         expect:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestResult.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestResult.groovy
deleted file mode 100644
index 9b4cdb4..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestResult.groovy
+++ /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.tasks.testing.logging
-
-import org.gradle.api.tasks.testing.TestResult
-
-class SimpleTestResult implements TestResult {
-    TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
-    List<Throwable> exceptions = []
-    Throwable exception = exceptions[0]
-    long startTime = System.currentTimeMillis()
-    long endTime = startTime + 100
-    long testCount = 1
-    long successfulTestCount = 1
-    long failedTestCount = 0
-    long skippedTestCount = 0
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy
index 2c4b407..35174fd 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy
@@ -16,12 +16,12 @@
 
 package org.gradle.api.internal.tasks.testing.logging
 
+import org.gradle.api.internal.tasks.testing.SimpleTestResult
 import org.gradle.api.logging.LogLevel
-import org.gradle.api.tasks.testing.logging.TestLogEvent
 import org.gradle.api.tasks.testing.TestResult
-
-import spock.lang.Specification
+import org.gradle.api.tasks.testing.logging.TestLogEvent
 import org.gradle.logging.TestStyledTextOutputFactory
+import spock.lang.Specification
 
 class TestEventLoggerTest extends Specification {
     def textOutputFactory = new TestStyledTextOutputFactory()
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy
index 8ad5615..9ea1ee6 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy
@@ -23,9 +23,6 @@ import org.gradle.api.internal.tasks.testing.TestStartEvent
 import org.gradle.api.tasks.testing.TestResult.ResultType
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 10/14/11
- */
 public class DefaultTestResultTest extends Specification {
 
     def "construct itself from the state"() {
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 5e71868..8a72df6 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,9 +17,10 @@
 package org.gradle.api.internal.tasks.testing.testng
 
 import org.gradle.api.GradleException
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo
 import org.gradle.api.internal.tasks.testing.TestResultProcessor
 import org.gradle.api.internal.tasks.testing.TestStartEvent
+import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter
 import org.gradle.api.tasks.testing.TestResult.ResultType
 import org.gradle.api.tasks.testing.testng.TestNGOptions
 import org.gradle.internal.id.LongIdGenerator
@@ -29,122 +30,134 @@ import org.junit.Rule
 import org.testng.annotations.*
 import spock.lang.Ignore
 import spock.lang.Specification
+import spock.lang.Subject
 
 class TestNGTestClassProcessorTest extends Specification {
 
     @Rule TestNameTestDirectoryProvider reportDir = new TestNameTestDirectoryProvider()
 
-    private resultProcessor = Mock(TestResultProcessor)
+    def processor = Mock(TestResultProcessor)
 
-    private TestNGSpec options
-    private TestNGTestClassProcessor processor
+    def options = Spy(TestNGSpec, constructorArgs:[new TestNGOptions(reportDir.testDirectory), new DefaultTestFilter()])
 
-    void setup(){
-        options = Spy(TestNGSpec, constructorArgs:[new TestNGOptions(reportDir.testDirectory)]);
-        processor = new TestNGTestClassProcessor(reportDir.testDirectory, options, [], new LongIdGenerator(), {} as StandardOutputRedirector);
+    @Subject classProcessor = new TestNGTestClassProcessor(reportDir.testDirectory, options, [], new LongIdGenerator(), {} as StandardOutputRedirector)
+
+    void process(Class ... clazz) {
+        classProcessor.startProcessing(processor)
+        for (String c : clazz*.name) {
+            classProcessor.processTestClass(new DefaultTestClassRunInfo(c))
+        }
+        classProcessor.stop()
     }
 
     void "executes the test class"() {
+        when: process(ATestNGClass)
+
+        then: 1 * processor.started({ it.id == 1 && it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == 'ok' && it.className == ATestNGClass.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == ResultType.SUCCESS })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
+    }
+
+    void "executes factory test class"() {
         when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGClass.class));
-        processor.stop();
+        process(ATestNGFactoryClass)
 
-        then:
-        1 * resultProcessor.started({ it.id == 1 && it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
-        then:
-        1 * resultProcessor.started({ it.id == 2 && it.name == 'ok' && it.className == ATestNGClass.class.name }, { it.parentId == 1 })
+        then: 1 * processor.started({ it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
+        then: 1 * processor.started({ it.name == 'ok' && it.className == ATestNGClass.name }, _ as TestStartEvent)
+        then: 1 * processor.completed(2, { it.resultType == ResultType.SUCCESS })
+        then: 1 * processor.completed(1, { it.resultType == null })
 
-        then:
-        1 * resultProcessor.completed(2, { it.resultType == ResultType.SUCCESS })
-        then:
-        1 * resultProcessor.completed(1, { it.resultType == null })
+        0 * processor._
+    }
+
+    void "executes selected included method"() {
+        options.getIncludedTests() >> [ATestNGClassWithManyMethods.name + ".another"]
 
-        0 * resultProcessor._
+        when: process(ATestNGClassWithManyMethods)
+
+        then: 1 * processor.started({ it.id == 1 && it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
+        then: 1 * processor.started({ it.id == 2 && it.name == 'another' && it.className == ATestNGClassWithManyMethods.name }, { it.parentId == 1 })
+        then: 1 * processor.completed(2, { it.resultType == ResultType.SUCCESS })
+        then: 1 * processor.completed(1, { it.resultType == null })
+
+        0 * processor._
     }
 
-    void "executes factory test class"() {
-        when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGFactoryClass.class));
-        processor.stop();
+    void "executes multiple included methods"() {
+        options.getIncludedTests() >> [ATestNGClassWithManyMethods.name + ".another", ATestNGClassWithManyMethods.name + ".yetAnother"]
+
+        when: process(ATestNGClassWithManyMethods)
 
         then:
-        1 * resultProcessor.started({ it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
-        1 * resultProcessor.started({ it.name == 'ok' && it.className == ATestNGClass.class.name }, _ as TestStartEvent)
+        1 * processor.started({ it.id == 1 && it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
+        1 * processor.started({ it.id == 2 && it.name == 'another' && it.className == ATestNGClassWithManyMethods.name }, { it.parentId == 1 })
+        1 * processor.started({ it.id == 3 && it.name == 'yetAnother' && it.className == ATestNGClassWithManyMethods.name }, { it.parentId == 1 })
+        0 * processor.started(_, _)
+    }
+
+    void "executes methods from multiple classes by pattern"() {
+        options.getIncludedTests() >> ["*Methods.ok*"]
 
-        1 * resultProcessor.completed(2, { it.resultType == ResultType.SUCCESS })
-        1 * resultProcessor.completed(1, { it.resultType == null })
+        when: process(ATestNGClassWithManyMethods)
 
-        0 * resultProcessor._
+        then:
+        1 * processor.started({ it.id == 1 && it.name == 'Gradle test' }, _)
+        1 * processor.started({ it.name == 'ok' && it.className == ATestNGClassWithManyMethods.name }, { it.parentId == 1 })
+        1 * processor.started({ it.name == 'ok2' && it.className == ATestNGClassWithManyMethods.name }, { it.parentId == 1 })
+        0 * processor.started(_, _)
     }
 
-    void "executes test with expected exception"() {
-        when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGClassWithExpectedException.class));
-        processor.stop();
+    void "executes not tests if none of the included test methods match"() {
+        options.getIncludedTests() >> [ATestNGClassWithManyMethods.name + "does not exist"]
 
-        then:
-        1 * resultProcessor.started({ it.id == 1} , _)
-        1 * resultProcessor.started({ it.name == 'ok' && it.className == ATestNGClassWithExpectedException.class.name }, _)
+        when: process(ATestNGClassWithManyMethods)
 
-        1 * resultProcessor.completed(2, { it.resultType == ResultType.SUCCESS })
-        1 * resultProcessor.completed(1, { it.resultType == null })
+        then: 1 * processor.started({ it.id == 1 && it.className == null }, { it.parentId == null })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
+    }
 
-        0 * resultProcessor._
+    void "executes test with expected exception"() {
+        when: process(ATestNGClassWithExpectedException)
+
+        then: 1 * processor.started({ it.id == 1} , _)
+        then: 1 * processor.started({ it.name == 'ok' && it.className == ATestNGClassWithExpectedException.name }, _)
+        then: 1 * processor.completed(2, { it.resultType == ResultType.SUCCESS })
+        then: 1 * processor.completed(1, { it.resultType == null })
+        0 * processor._
     }
 
     void "executes test with broken setup"() {
-        when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGClassWithBrokenSetupMethod.class));
-        processor.stop();
+        when: process(ATestNGClassWithBrokenSetupMethod)
 
-        then:
-        1 * resultProcessor.started({ it.id == 1} , _)
-        then:
-        1 * resultProcessor.started({ it.name == 'beforeMethod' && it.className == ATestNGClassWithBrokenSetupMethod.class.name }, _)
-
-        then:
-        1 * resultProcessor.failure(2, ATestNGClassWithBrokenSetupMethod.failure)
-        then:
-        1 * resultProcessor.completed(2, { it.resultType == ResultType.FAILURE })
+        then: 1 * processor.started({ it.id == 1} , _)
+        then: 1 * processor.started({ it.name == 'beforeMethod' && it.className == ATestNGClassWithBrokenSetupMethod.name }, _)
+        then: 1 * processor.failure(2, ATestNGClassWithBrokenSetupMethod.failure)
+        then: 1 * processor.completed(2, { it.resultType == ResultType.FAILURE })
 
-        then:
-        1 * resultProcessor.started({ it.name == 'test' && it.className == ATestNGClassWithBrokenSetupMethod.class.name }, _)
-        then:
-        1 * resultProcessor.completed(3, { it.resultType == ResultType.SKIPPED})
+        then: 1 * processor.started({ it.name == 'test' && it.className == ATestNGClassWithBrokenSetupMethod.name }, _)
+        then: 1 * processor.completed(3, { it.resultType == ResultType.SKIPPED})
 
-        then:
-        1 * resultProcessor.completed(1, { it.resultType == null})
-        0 * resultProcessor._
+        then: 1 * processor.completed(1, { it.resultType == null})
+        0 * processor._
     }
 
     void "executes test class with dependency method"() {
-        when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGClassWithBrokenDependencyMethod.class));
-        processor.stop();
+        when: process(ATestNGClassWithBrokenDependencyMethod)
 
-        then:
-        1 * resultProcessor.started({ it.id == 1} , _)
-        then:
-        1 * resultProcessor.started({ it.name == 'beforeMethod' && it.className == ATestNGClassWithBrokenDependencyMethod.class.name }, _)
+        then: 1 * processor.started({ it.id == 1} , _)
+        then: 1 * processor.started({ it.name == 'beforeMethod' && it.className == ATestNGClassWithBrokenDependencyMethod.name }, _)
 
-        then:
-        1 * resultProcessor.failure(2, ATestNGClassWithBrokenDependencyMethod.failure)
-        then:
-        1 * resultProcessor.completed(2, { it.resultType == ResultType.FAILURE })
+        then: 1 * processor.failure(2, ATestNGClassWithBrokenDependencyMethod.failure)
+        then: 1 * processor.completed(2, { it.resultType == ResultType.FAILURE })
 
-        then:
-        1 * resultProcessor.started({ it.name == 'test' && it.className == ATestNGClassWithBrokenDependencyMethod.class.name }, _)
-        then:
-        1 * resultProcessor.completed(3, { it.resultType == ResultType.SKIPPED})
+        then: 1 * processor.started({ it.name == 'test' && it.className == ATestNGClassWithBrokenDependencyMethod.name }, _)
+        then: 1 * processor.completed(3, { it.resultType == ResultType.SKIPPED})
 
-        then:
-        1 * resultProcessor.completed(1, { it.resultType == null})
-        0 * resultProcessor._
+        then: 1 * processor.completed(1, { it.resultType == null})
+        0 * processor._
     }
 
     void "includes and excludes groups"() {
@@ -152,105 +165,92 @@ class TestNGTestClassProcessorTest extends Specification {
         _ * options.getIncludeGroups() >> ['group1', 'group2']
         _ * options.getExcludeGroups() >> ['group3']
 
-        when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGClassWithGroups.class));
-        processor.stop();
+        when: process(ATestNGClassWithGroups)
 
         then:
-        1 * resultProcessor.started({ it.id == 1} , _)
-        1 * resultProcessor.started({ it.name == 'group1' && it.className == ATestNGClassWithGroups.class.name }, _)
-        1 * resultProcessor.started({ it.name == 'group2' && it.className == ATestNGClassWithGroups.class.name }, _)
-        3 * resultProcessor.completed(_, _)
-        0 * resultProcessor._
+        1 * processor.started({ it.id == 1} , _)
+        1 * processor.started({ it.name == 'group1' && it.className == ATestNGClassWithGroups.name }, _)
+        1 * processor.started({ it.name == 'group2' && it.className == ATestNGClassWithGroups.name }, _)
+        3 * processor.completed(_, _)
+        0 * processor._
     }
 
     @Ignore //not implemented yet
     void "executes class with broken constructor"() {
-        when:
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(testClass(ATestNGClassWithBrokenConstructor.class));
-        processor.stop();
+        when: process(ATestNGClassWithBrokenConstructor)
 
         then:
         //below needs to revisited when we attempt to fix the problem
         //e.g. decide what's the behavior we want in this scenario
-        1 * resultProcessor.started({ it.id == 1} , _)
-        1 * resultProcessor.started({ it.name == 'initializationError' && it.className == ATestNGClassWithBrokenConstructor.class.name }, _)
-        1 * resultProcessor.failure(1, ATestNGClassWithBrokenConstructor.failure)
-        1 * resultProcessor.completed(1, { it.resultType == ResultType.FAILURE})
-        0 * resultProcessor._
+        1 * processor.started({ it.id == 1} , _)
+        1 * processor.started({ it.name == 'initializationError' && it.className == ATestNGClassWithBrokenConstructor.name }, _)
+        1 * processor.failure(1, ATestNGClassWithBrokenConstructor.failure)
+        1 * processor.completed(1, { it.resultType == ResultType.FAILURE})
+        0 * processor._
     }
 
     void "fails early for unknown test class"() {
-        processor.startProcessing(resultProcessor)
+        classProcessor.startProcessing(processor)
 
         when:
-        processor.processTestClass(testClass('unknown'))
+        classProcessor.processTestClass(new DefaultTestClassRunInfo('unknown'))
 
         then:
         def ex = thrown(GradleException)
         ex.message == "Could not load test class \'unknown\'."
     }
 
-    private TestClassRunInfo testClass(Class<?> type) {
-        return testClass(type.name)
-    }
+    void "before and after methods are not triggered when all tests from a class are filtered"() {
+        options.getIncludedTests() >> [ATestNGClass.name]
+
+        when:
+        process(ATestNGClass, ATestNGClassWithBeforeAndAfter) //the latter is not matched
 
-    private TestClassRunInfo testClass(String testClassName) {
-        return { testClassName } as TestClassRunInfo
+        then:
+        then: 1 * processor.started({ it.id == 1 && it.name == 'Gradle test' && it.className == null }, _)
+        then: 1 * processor.started({ it.id == 2 && it.name == 'ok' && it.className == ATestNGClass.name }, _)
+        then: 1 * processor.completed(2, _)
+        then: 1 * processor.completed(1, _)
+        0 * processor._
     }
 }
 
 public class ATestNGClass {
-    @BeforeClass
-    public void beforeClass() {
-    }
-
-    @AfterClass
-    public void afterClass() {
-    }
-
-    @BeforeMethod
-    public void beforeMethod() {
-    }
-
-    @AfterMethod
-    public void afterMethod() {
-    }
-
-    @org.testng.annotations.Test
-    public void ok() {
-    }
+    @BeforeClass public void beforeClass() {}
+    @AfterClass public void afterClass() {}
+    @BeforeMethod public void beforeMethod() {}
+    @AfterMethod public void afterMethod() {}
+    @org.testng.annotations.Test public void ok() {}
+    @org.testng.annotations.Test(enabled = false) public void skipped() {}
+}
 
-    @org.testng.annotations.Test(enabled = false)
-    public void skipped() {
-    }
+public class ATestNGClassWithBeforeAndAfter {
+    @BeforeClass public void beforeClass() { assert false }
+    @AfterClass public void afterClass() { assert false }
+    @BeforeMethod public void beforeMethod() { assert false }
+    @AfterMethod public void afterMethod() { assert false }
+    @org.testng.annotations.Test public void ok() {}
 }
 
 public class ATestNGClassWithExpectedException {
-    @org.testng.annotations.Test(expectedExceptions = RuntimeException.class)
+    @org.testng.annotations.Test(expectedExceptions = RuntimeException)
     public void ok() {
         throw new RuntimeException()
     }
 }
 
-public class ATestNGClassWithGroups {
-    @org.testng.annotations.Test(groups="group1")
-    public void group1() {
-    }
-
-    @org.testng.annotations.Test(groups="group2")
-    public void group2() {
-    }
-
-    @org.testng.annotations.Test(groups="group2,group3")
-    public void excluded() {
-    }
+public class ATestNGClassWithManyMethods {
+    @org.testng.annotations.Test public void ok() {}
+    @org.testng.annotations.Test public void ok2() {}
+    @org.testng.annotations.Test public void another() {}
+    @org.testng.annotations.Test public void yetAnother() {}
+}
 
-    @org.testng.annotations.Test(groups="group4")
-    public void ignored() {
-    }
+public class ATestNGClassWithGroups {
+    @org.testng.annotations.Test(groups="group1") public void group1() {}
+    @org.testng.annotations.Test(groups="group2") public void group2() {}
+    @org.testng.annotations.Test(groups="group2,group3") public void excluded() {}
+    @org.testng.annotations.Test(groups="group4") public void ignored() {}
 }
 
 public class ATestNGFactoryClass {
@@ -262,38 +262,18 @@ public class ATestNGFactoryClass {
 
 public class ATestNGClassWithBrokenConstructor {
     static RuntimeException failure = new RuntimeException()
-
-    def ATestNGClassWithBrokenConstructor() {
-        throw failure
-    }
-
-    @org.testng.annotations.Test
-    public void test() {
-    }
+    def ATestNGClassWithBrokenConstructor() { throw failure }
+    @org.testng.annotations.Test public void test() {}
 }
 
 public class ATestNGClassWithBrokenSetupMethod {
     static RuntimeException failure = new RuntimeException()
-
-    @BeforeMethod
-    public void beforeMethod() {
-        throw failure
-    }
-
-    @org.testng.annotations.Test
-    public void test() {
-    }
+    @BeforeMethod public void beforeMethod() { throw failure }
+    @org.testng.annotations.Test public void test() {}
 }
 
 public class ATestNGClassWithBrokenDependencyMethod {
     static RuntimeException failure = new RuntimeException()
-
-    @org.testng.annotations.Test
-    public void beforeMethod() {
-        throw failure
-    }
-
-    @org.testng.annotations.Test(dependsOnMethods = 'beforeMethod')
-    public void test() {
-    }
+    @org.testng.annotations.Test public void beforeMethod() { throw failure }
+    @org.testng.annotations.Test(dependsOnMethods = 'beforeMethod') public void test() {}
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy
index aac8f74..60e2b81 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy
@@ -16,20 +16,25 @@
 
 package org.gradle.api.internal.tasks.testing.testng
 
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter
 import org.gradle.api.tasks.testing.Test
 import org.gradle.api.tasks.testing.testng.TestNGOptions
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.testfixtures.ProjectBuilder
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
+import spock.lang.Shared
 import spock.lang.Specification
 
-/**
- * @author Szczepan Faber
- */
 public class TestNGTestFrameworkTest extends Specification {
 
+    @Shared Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+
     private project = new ProjectBuilder().build()
-    Test testTask = HelperUtil.createTask(Test, project)
+    Test testTask = TestUtil.createTask(Test, project)
 
     void setup() {
         project.ext.sourceCompatibility = "1.7"
@@ -40,27 +45,30 @@ public class TestNGTestFrameworkTest extends Specification {
         project.ext.sourceCompatibility = "1.4"
 
         when:
-        def framework = new TestNGTestFramework(testTask);
+        def framework = createFramework()
 
         then:
         framework.options.annotations == TestNGOptions.JAVADOC_ANNOTATIONS
         framework.testTask == testTask
-        framework.options.projectDir == project.projectDir
         framework.detector
     }
 
     void "initializes for newer java"() {
         expect:
-        new TestNGTestFramework(testTask).options.annotations == TestNGOptions.JDK_ANNOTATIONS
+        createFramework().options.annotations == TestNGOptions.JDK_ANNOTATIONS
     }
 
     void "creates test class processor"() {
         when:
-        def framework = new TestNGTestFramework(testTask);
+        def framework = createFramework()
         def processor = framework.getProcessorFactory().create(Mock(ServiceRegistry))
 
         then:
         framework.options.testResources.is(testTask.testSrcDirs)
         processor instanceof TestNGTestClassProcessor
     }
+
+    TestNGTestFramework createFramework() {
+        new TestNGTestFramework(testTask, new DefaultTestFilter(), instantiator)
+    }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.groovy
new file mode 100755
index 0000000..20c7933
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.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.api.internal.tasks.testing.worker
+
+import org.gradle.api.Action
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory
+import org.gradle.internal.Factory
+import org.gradle.process.JavaForkOptions
+import spock.lang.Specification
+import spock.lang.Subject
+
+class ForkingTestClassProcessorTest extends Specification {
+
+    @Subject processor = Spy(ForkingTestClassProcessor, constructorArgs: [Mock(Factory), Mock(WorkerTestClassProcessorFactory), Mock(JavaForkOptions), [new File("classpath.jar")], Mock(Action)])
+
+    def "starts worker process on first test"() {
+        def test1 = Mock(TestClassRunInfo)
+        def test2 = Mock(TestClassRunInfo)
+        def remoteProcessor = Mock(RemoteTestClassProcessor)
+
+        when:
+        processor.processTestClass(test1)
+        processor.processTestClass(test2)
+
+        then:
+        1 * processor.forkProcess() >> remoteProcessor
+        1 * remoteProcessor.processTestClass(test1)
+        1 * remoteProcessor.processTestClass(test2)
+        0 * remoteProcessor._
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
deleted file mode 100755
index 819e852..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.worker;
-
-import org.gradle.api.Action;
-import org.gradle.internal.Factory;
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
-import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
-import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.process.JavaForkOptions;
-import org.gradle.process.internal.JavaExecHandleBuilder;
-import org.gradle.process.internal.WorkerProcess;
-import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.List;
-
-import static java.util.Arrays.asList;
-import static org.hamcrest.Matchers.notNullValue;
-
- at RunWith(JMock.class)
-public class ForkingTestClassProcessorTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final WorkerTestClassProcessorFactory processorFactory = context.mock(WorkerTestClassProcessorFactory.class);
-    @SuppressWarnings("unchecked")
-    private final Factory<WorkerProcessBuilder> workerFactory = context.mock(Factory.class);
-    private final WorkerProcess workerProcess = context.mock(WorkerProcess.class);
-    private final RemoteTestClassProcessor worker = context.mock(RemoteTestClassProcessor.class);
-    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
-    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
-    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private final List<File> appClassPath = asList(new File("classpath.jar"));
-    private final JavaForkOptions options = context.mock(JavaForkOptions.class);
-    @SuppressWarnings("unchecked")
-    private final Action<WorkerProcessBuilder> action = context.mock(Action.class);
-    private final ForkingTestClassProcessor processor = new ForkingTestClassProcessor(workerFactory, processorFactory, options, appClassPath, action);
-
-    @Test
-    public void onFirstTestCaseStartsWorkerProcess() {
-        expectWorkerProcessStarted();
-        context.checking(new Expectations() {{
-            one(worker).processTestClass(test1);
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-    }
-
-    @Test
-    public void onSubsequentTestCaseForwardsTestToWorkerProcess() {
-        expectWorkerProcessStarted();
-        context.checking(new Expectations() {{
-            one(worker).processTestClass(test1);
-            one(worker).processTestClass(test2);
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.processTestClass(test2);
-    }
-
-    @Test
-    public void onEndProcessingWaitsForWorkerProcessToStop() {
-        expectWorkerProcessStarted();
-        context.checking(new Expectations() {{
-            one(worker).processTestClass(test1);
-            one(worker).stop();
-            one(workerProcess).waitForStop();
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.stop();
-    }
-
-    @Test
-    public void onEndProcessingDoesNothingIfNoTestsProcessed() {
-        processor.startProcessing(resultProcessor);
-        processor.stop();
-    }
-
-    private void expectWorkerProcessStarted() {
-        context.checking(new Expectations() {{
-            WorkerProcessBuilder builder = context.mock(WorkerProcessBuilder.class);
-            ObjectConnection connection = context.mock(ObjectConnection.class);
-            JavaExecHandleBuilder javaCommandBuilder = context.mock(JavaExecHandleBuilder.class);
-
-            one(workerFactory).create();
-            will(returnValue(builder));
-
-            one(builder).worker(with(notNullValue(TestWorker.class)));
-
-            one(builder).applicationClasspath(appClassPath);
-
-            one(builder).setLoadApplicationInSystemClassLoader(true);
-
-            one(action).execute(builder);
-            
-            allowing(builder).getJavaCommand();
-            will(returnValue(javaCommandBuilder));
-
-            one(options).copyTo(javaCommandBuilder);
-
-            one(builder).build();
-            will(returnValue(workerProcess));
-
-            allowing(workerProcess).getConnection();
-            will(returnValue(connection));
-
-            one(connection).addIncoming(TestResultProcessor.class, resultProcessor);
-            
-            one(connection).addOutgoing(RemoteTestClassProcessor.class);
-            will(returnValue(worker));
-
-            one(workerProcess).start();
-
-            one(worker).startProcessing();
-        }});
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestEventSerializerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestEventSerializerTest.groovy
new file mode 100644
index 0000000..da9abea
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestEventSerializerTest.groovy
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.worker
+
+import org.gradle.api.GradleException
+import org.gradle.api.internal.tasks.testing.DefaultTestClassDescriptor
+import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo
+import org.gradle.api.internal.tasks.testing.DefaultTestDescriptor
+import org.gradle.api.internal.tasks.testing.DefaultTestMethodDescriptor
+import org.gradle.api.internal.tasks.testing.DefaultTestOutputEvent
+import org.gradle.api.internal.tasks.testing.DefaultTestSuiteDescriptor
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent
+import org.gradle.api.internal.tasks.testing.TestStartEvent
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.internal.id.CompositeIdGenerator
+import org.gradle.messaging.serialize.InputStreamBackedDecoder
+import org.gradle.messaging.serialize.OutputStreamBackedEncoder
+import spock.lang.Specification
+
+class TestEventSerializerTest extends Specification {
+    def serializer = new TestEventSerializer()
+
+    def "serializes DefaultTestClassRunInfo"() {
+        def info = new DefaultTestClassRunInfo("some-test")
+
+        when:
+        def result = serialize(info)
+
+        then:
+        result.length == 1
+        result[0] instanceof DefaultTestClassRunInfo
+        result[0].testClassName == "some-test"
+    }
+
+    def "serializes CompositeId"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+
+        when:
+        def result = serialize(id)
+
+        then:
+        result.length == 1
+        result[0] instanceof CompositeIdGenerator.CompositeId
+        result[0] == id
+    }
+
+    def "serializes DefaultTestSuiteDescriptor"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+        def descriptor = new DefaultTestSuiteDescriptor(id, "some-test")
+
+        when:
+        def result = serialize(descriptor)
+
+        then:
+        result.length == 1
+        result[0] instanceof DefaultTestSuiteDescriptor
+        result[0].id == id
+        result[0].name == "some-test"
+    }
+
+    def "serializes WorkerTestSuiteDescriptor"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+        def descriptor = new WorkerTestClassProcessor.WorkerTestSuiteDescriptor(id, "some-test")
+
+        when:
+        def result = serialize(descriptor)
+
+        then:
+        result.length == 1
+        result[0] instanceof WorkerTestClassProcessor.WorkerTestSuiteDescriptor
+        result[0].id == id
+        result[0].name == "some-test"
+    }
+
+    def "serializes DefaultTestClassDescriptor"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+        def descriptor = new DefaultTestClassDescriptor(id, "some-test")
+
+        when:
+        def result = serialize(descriptor)
+
+        then:
+        result.length == 1
+        result[0] instanceof DefaultTestClassDescriptor
+        result[0].id == id
+        result[0].name == "some-test"
+    }
+
+    def "serializes DefaultTestDescriptor"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+        def descriptor = new DefaultTestDescriptor(id, "some-class", "some-test")
+
+        when:
+        def result = serialize(descriptor)
+
+        then:
+        result.length == 1
+        result[0] instanceof DefaultTestDescriptor
+        result[0].id == id
+        result[0].className == "some-class"
+        result[0].name == "some-test"
+    }
+
+    def "serializes DefaultTestMethodDescriptor"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+        def descriptor = new DefaultTestMethodDescriptor(id, "some-class", "some-test")
+
+        when:
+        def result = serialize(descriptor)
+
+        then:
+        result.length == 1
+        result[0] instanceof DefaultTestMethodDescriptor
+        result[0].id == id
+        result[0].className == "some-class"
+        result[0].name == "some-test"
+    }
+
+    def "serializes TestStartEvent"() {
+        def id = new CompositeIdGenerator.CompositeId(1L, 2L)
+        def event1 = new TestStartEvent(123L, id)
+        def event2 = new TestStartEvent(456L)
+
+        when:
+        def result = serialize(event1, event2)
+
+        then:
+        result.length == 2
+        result[0] instanceof TestStartEvent
+        result[0].parentId == id
+        result[0].startTime == 123L
+        result[1] instanceof TestStartEvent
+        result[1].parentId == null
+        result[1].startTime == 456L
+    }
+
+    def "serializes TestCompleteEvent"() {
+        def event1 = new TestCompleteEvent(123L, TestResult.ResultType.SUCCESS)
+        def event2 = new TestCompleteEvent(123L, null)
+
+        when:
+        def result = serialize(event1, event2)
+
+        then:
+        result.length == 2
+        result[0] instanceof TestCompleteEvent
+        result[0].endTime == 123L
+        result[0].resultType == TestResult.ResultType.SUCCESS
+        result[1] instanceof TestCompleteEvent
+        result[1].endTime == 123L
+        result[1].resultType == null
+    }
+
+    def "serializes DefaultTestOutputEvent"() {
+        def event = new DefaultTestOutputEvent(TestOutputEvent.Destination.StdErr, "hi")
+
+        when:
+        def result = serialize(event)
+
+        then:
+        result.length == 1
+        result[0] instanceof DefaultTestOutputEvent
+        result[0].destination == TestOutputEvent.Destination.StdErr
+        result[0].message == "hi"
+    }
+
+    def "serializes Throwable"() {
+        def failure = new GradleException("broken", new RuntimeException("cause"))
+
+        when:
+        def result = serialize(failure)
+
+        then:
+        result.length == 1
+        result[0].class == GradleException
+        result[0].message == "broken"
+        result[0].cause.class == RuntimeException
+        result[0].cause.message == "cause"
+    }
+
+    def Object[] serialize(Object... source) {
+        def outstr = new ByteArrayOutputStream()
+        serializer.newWriter(new OutputStreamBackedEncoder(outstr)).write(source)
+
+        return serializer.newReader(new InputStreamBackedDecoder(new ByteArrayInputStream(outstr.toByteArray()))).read()
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy
index 154d4e1..267ae6c 100755
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy
@@ -80,6 +80,9 @@ public class TestWorkerTest extends MultithreadedTestCase {
                 }
             }
 
+            one(connection).useParameterSerializer(withParam(instanceOf(TestEventSerializer)))
+            one(connection).connect()
+
             ignoring(resultProcessor)
 
             one(processor).startProcessing(withParam(notNullValue()))
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy
index d9bdbe8..424cea0 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy
@@ -15,12 +15,9 @@
  */
 package org.gradle.api.java.archives.internal
 
-import spock.lang.Specification
 import org.gradle.api.java.archives.ManifestException
+import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class DefaultAttributesTest extends Specification {
     DefaultAttributes attributes = new DefaultAttributes();
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
index 8744feb..ba4974f 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
@@ -23,9 +23,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class DefaultManifestMergeSpecTest extends Specification {
     def static final MANIFEST_VERSION_MAP = ['Manifest-Version': '1.0']
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
index 9bf7616..80fdcc8 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
@@ -25,9 +25,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 
 class DefaultManifestTest extends Specification {
     def static final MANIFEST_VERSION_MAP = ['Manifest-Version': '1.0']
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
index 2851aaf..5a0987d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
@@ -16,20 +16,19 @@
 package org.gradle.api.plugins
 
 import org.gradle.api.Project
+import org.gradle.api.tasks.TaskDependencyMatchers
 import org.gradle.api.tasks.application.CreateStartScripts
-import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.JavaExec
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.bundling.Tar
-import org.gradle.util.HelperUtil
-import org.gradle.util.Matchers
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 import org.gradle.api.tasks.Sync
 import org.gradle.api.file.CopySpec
 
 class ApplicationPluginTest extends Specification {
-    private final Project project = HelperUtil.createRootProject();
+    private final Project project = TestUtil.createRootProject();
     private final ApplicationPlugin plugin = new ApplicationPlugin();
 
     def "applies JavaPlugin and adds convention object with default values"() {
@@ -41,6 +40,7 @@ class ApplicationPluginTest extends Specification {
         project.convention.getPlugin(ApplicationPluginConvention.class) != null
         project.applicationName == project.name
         project.mainClassName == null
+        project.applicationDefaultJvmArgs == []
         project.applicationDistribution instanceof CopySpec
     }
 
@@ -52,7 +52,7 @@ class ApplicationPluginTest extends Specification {
         def task = project.tasks[ApplicationPlugin.TASK_RUN_NAME]
         task instanceof JavaExec
         task.classpath == project.sourceSets[SourceSet.MAIN_SOURCE_SET_NAME].runtimeClasspath
-        task Matchers.dependsOn('classes')
+        task TaskDependencyMatchers.dependsOn('classes')
     }
 
     public void "adds startScripts task to project"() {
@@ -64,6 +64,7 @@ class ApplicationPluginTest extends Specification {
         task instanceof CreateStartScripts
         task.applicationName == project.applicationName
         task.outputDir == project.file('build/scripts')
+        task.defaultJvmOpts == []
     }
 
     public void "adds installApp task to project with default target"() {
@@ -131,4 +132,24 @@ class ApplicationPluginTest extends Specification {
         def startScripts = project.tasks[ApplicationPlugin.TASK_START_SCRIPTS_NAME]
         startScripts.mainClassName == "Acme"
     }
+
+    public void "applicationDefaultJvmArgs in project delegates to jvmArgs in run task"() {
+        when:
+        plugin.apply(project)
+        project.applicationDefaultJvmArgs = ['-Dfoo=bar', '-Xmx500m']
+
+        then:
+        def run = project.tasks[ApplicationPlugin.TASK_RUN_NAME]
+        run.jvmArgs == ['-Dfoo=bar', '-Xmx500m']
+    }
+
+    public void "applicationDefaultJvmArgs in project delegates to defaultJvmOpts in startScripts task"() {
+        when:
+        plugin.apply(project);
+        project.applicationDefaultJvmArgs = ['-Dfoo=bar', '-Xmx500m']
+
+        then:
+        def startScripts = project.tasks[ApplicationPlugin.TASK_START_SCRIPTS_NAME]
+        startScripts.defaultJvmOpts == ['-Dfoo=bar', '-Xmx500m']
+    }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
index b6de9c9..3d40dcb 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
@@ -17,16 +17,14 @@
 package org.gradle.api.plugins
 
 import org.gradle.api.internal.project.DefaultProject
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Before
 import org.junit.Test
+
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 class BasePluginConventionTest {
-    private DefaultProject project = HelperUtil.createRootProject()
+    private DefaultProject project = TestUtil.createRootProject()
     private File testDir = project.projectDir
     private BasePluginConvention convention
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
index d057909..1a3c62f 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
@@ -27,21 +27,18 @@ import org.gradle.api.tasks.Upload
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.bundling.Tar
 import org.gradle.api.tasks.bundling.Zip
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
-import static org.gradle.util.Matchers.dependsOn
+
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.hamcrest.Matchers.instanceOf
 
-/**
- * @author Hans Dockter
- */
 class BasePluginTest extends Specification {
-    private final Project project = HelperUtil.createRootProject()
-    private final BasePlugin plugin = new BasePlugin()
+    private final Project project = TestUtil.createRootProject()
 
     public void addsConventionObjects() {
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
         project.convention.plugins.base instanceof BasePluginConvention
@@ -50,7 +47,7 @@ class BasePluginTest extends Specification {
 
     public void createsTasksAndAppliesMappings() {
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
         def clean = project.tasks[BasePlugin.CLEAN_TASK_NAME]
@@ -65,10 +62,10 @@ class BasePluginTest extends Specification {
 
     public void assembleTaskBuildsThePublishedArtifacts() {
         given:
-        def someJar = project.tasks.add('someJar', Jar)
+        def someJar = project.tasks.create('someJar', Jar)
 
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
         project.artifacts.archives someJar
 
         then:
@@ -78,7 +75,7 @@ class BasePluginTest extends Specification {
 
     public void addsRulesWhenAConfigurationIsAdded() {
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
         !project.tasks.rules.empty
@@ -86,10 +83,10 @@ class BasePluginTest extends Specification {
 
     public void addsImplicitTasksForConfiguration() {
         given:
-        def someJar = project.tasks.add('someJar', Jar)
+        def someJar = project.tasks.create('someJar', Jar)
 
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
         project.artifacts.archives someJar
 
         then:
@@ -103,7 +100,7 @@ class BasePluginTest extends Specification {
         uploadArchives dependsOn('someJar')
 
         when:
-        project.configurations.add('conf')
+        project.configurations.create('conf')
         project.artifacts.conf someJar
 
         then:
@@ -124,7 +121,7 @@ class BasePluginTest extends Specification {
         test.outputs.files(project.buildDir)
 
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
         Task cleanTest = project.tasks['cleanTest']
@@ -138,7 +135,7 @@ class BasePluginTest extends Specification {
         project.task('12')
 
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
         project.tasks.findByName('cleantestTask') == null
@@ -149,23 +146,23 @@ class BasePluginTest extends Specification {
 
     public void appliesMappingsForArchiveTasks() {
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
         project.version = '1.0'
 
         then:
-        def someJar = project.tasks.add('someJar', Jar)
+        def someJar = project.tasks.create('someJar', Jar)
         someJar.destinationDir == project.libsDir
         someJar.version == project.version
         someJar.baseName == project.archivesBaseName
 
         and:
-        def someZip = project.tasks.add('someZip', Zip)
+        def someZip = project.tasks.create('someZip', Zip)
         someZip.destinationDir == project.distsDir
         someZip.version == project.version
         someZip.baseName == project.archivesBaseName
 
         and:
-        def someTar = project.tasks.add('someTar', Tar)
+        def someTar = project.tasks.create('someTar', Tar)
         someTar.destinationDir == project.distsDir
         someTar.version == project.version
         someTar.baseName == project.archivesBaseName
@@ -173,10 +170,10 @@ class BasePluginTest extends Specification {
 
     public void usesNullVersionWhenProjectVersionNotSpecified() {
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
-        def task = project.tasks.add('someJar', Jar)
+        def task = project.tasks.create('someJar', Jar)
         task.version == null
 
         when:
@@ -188,7 +185,7 @@ class BasePluginTest extends Specification {
 
     public void addsConfigurationsToTheProject() {
         when:
-        plugin.apply(project)
+        project.plugins.apply(BasePlugin)
 
         then:
         def defaultConfig = project.configurations[Dependency.DEFAULT_CONFIGURATION]
@@ -207,8 +204,8 @@ class BasePluginTest extends Specification {
         PublishArtifact artifact = Mock()
 
         when:
-        plugin.apply(project)
-        project.configurations.add("custom").artifacts.add(artifact)
+        project.plugins.apply(BasePlugin)
+        project.configurations.create("custom").artifacts.add(artifact)
 
         then:
         project.configurations[Dependency.ARCHIVES_CONFIGURATION].artifacts.contains(artifact)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
index f096025..8d39e4d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
@@ -21,22 +21,19 @@ import org.gradle.api.internal.artifacts.configurations.Configurations
 import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.compile.GroovyCompile
 import org.gradle.api.tasks.javadoc.Groovydoc
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Before
 import org.junit.Test
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 
 class GroovyBasePluginTest {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
 
     @Before
     void before() {
@@ -56,13 +53,13 @@ class GroovyBasePluginTest {
     }
 
     @Test void appliesMappingsToNewSourceSet() {
-        def sourceSet = project.sourceSets.add('custom')
+        def sourceSet = project.sourceSets.create('custom')
         assertThat(sourceSet.groovy.displayName, equalTo("custom Groovy source"))
         assertThat(sourceSet.groovy.srcDirs, equalTo(toLinkedSet(project.file("src/custom/groovy"))))
     }
 
     @Test void addsCompileTaskToNewSourceSet() {
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
 
         def task = project.tasks['compileCustomGroovy']
         assertThat(task, instanceOf(GroovyCompile.class))
@@ -71,7 +68,7 @@ class GroovyBasePluginTest {
     }
 
     @Test void dependenciesOfJavaPluginTasksIncludeGroovyCompileTasks() {
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         def task = project.tasks['customClasses']
         assertThat(task, dependsOn(hasItem('compileCustomGroovy')))
     }
@@ -82,22 +79,4 @@ class GroovyBasePluginTest {
         assertThat(task.docTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
         assertThat(task.windowTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
-
-    @Test void defaultsGroovyClasspathToGroovyConfigurationIfTheLatterIsNonEmpty() {
-        project.sourceSets.add('custom')
-        def configuration = project.configurations.groovy
-        project.dependencies {
-            groovy "org.codehaus.groovy:groovy-all:2.0.5"
-        }
-
-        def compileTask = project.tasks.compileCustomGroovy
-        assertSame(configuration, compileTask.groovyClasspath)
-
-        def groovydocTask = project.task('groovydoc', type: Groovydoc)
-        assertSame(configuration, groovydocTask.groovyClasspath)
-    }
-
-    // see GroovyBasePluginIntegrationTest
-    @Test void defaultsGroovyClasspathToInferredGroovyDependencyIfGroovyConfigurationIsEmpty() {
-    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
index 6e55872..1e99344 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
@@ -22,23 +22,20 @@ import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.compile.GroovyCompile
 import org.gradle.api.tasks.javadoc.Groovydoc
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Rule
 import org.junit.Test
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class GroovyPluginTest {
     @Rule
     public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final GroovyPlugin groovyPlugin = new GroovyPlugin()
 
     @Test public void appliesTheJavaPluginToTheProject() {
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 b7c10a6..07767f2 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
@@ -14,35 +14,30 @@
  * limitations under the License.
  */
 package org.gradle.api.plugins
-
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.reporting.ReportingExtension
-import org.gradle.api.tasks.ClassDirectoryBinary
 import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.TaskDependencyMatchers
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.compile.JavaCompile
 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.language.jvm.ClassDirectoryBinary
 import org.gradle.util.SetSystemProperties
+import org.gradle.util.TestUtil
 import org.junit.Rule
-
 import spock.lang.Specification
 
-import static org.gradle.util.Matchers.sameCollection
+import static org.gradle.api.file.FileCollectionMatchers.sameCollection
 import static org.gradle.util.WrapUtil.toLinkedSet
 
-/**
- * @author Hans Dockter
- */
-
 class JavaBasePluginTest extends Specification {
     @Rule
     public SetSystemProperties sysProperties = new SetSystemProperties()
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final JavaBasePlugin javaBasePlugin = new JavaBasePlugin(project.services.get(Instantiator))
 
     void appliesBasePluginsAndAddsConventionObject() {
@@ -59,45 +54,60 @@ class JavaBasePluginTest extends Specification {
     void createsTasksAndAppliesMappingsForNewSourceSet() {
         when:
         javaBasePlugin.apply(project)
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         
         then:
-        def set = project.sourceSets.custom
+        SourceSet set = project.sourceSets.custom
         set.java.srcDirs == toLinkedSet(project.file('src/custom/java'))
         set.resources.srcDirs == toLinkedSet(project.file('src/custom/resources'))
         set.output.classesDir == new File(project.buildDir, 'classes/custom')
-        Matchers.builtBy('customClasses').matches(set.output)
 
         def processResources = project.tasks['processCustomResources']
-        processResources.description == 'Processes the custom resources.'
+        processResources.description == "Processes resources 'custom:resources'."
         processResources instanceof Copy
-        Matchers.dependsOn().matches(processResources)
+        TaskDependencyMatchers.dependsOn().matches(processResources)
         processResources.destinationDir == project.sourceSets.custom.output.resourcesDir
         def resources = processResources.source
         resources sameCollection(project.sourceSets.custom.resources)
 
         def compileJava = project.tasks['compileCustomJava']
-        compileJava.description == 'Compiles the custom/java source set.'
+        compileJava.description == "Compiles Java source 'custom:java'."
         compileJava instanceof JavaCompile
-        Matchers.dependsOn().matches(compileJava)
+        TaskDependencyMatchers.dependsOn().matches(compileJava)
         compileJava.classpath.is(project.sourceSets.custom.compileClasspath)
         compileJava.destinationDir == project.sourceSets.custom.output.classesDir
+
         def sources = compileJava.source
         sources sameCollection(project.sourceSets.custom.java)
+
         def classes = project.tasks['customClasses']
-        classes.description == 'Assembles the custom classes.'
+        classes.description == "Assembles classes 'custom'."
         classes instanceof DefaultTask
-        Matchers.dependsOn('processCustomResources', 'compileCustomJava').matches(classes)
-        classes.dependsOn.contains project.sourceSets.custom.output.dirs
+        TaskDependencyMatchers.dependsOn('processCustomResources', 'compileCustomJava').matches(classes)
+        TaskDependencyMatchers.builtBy('customClasses').matches(project.sourceSets.custom.output)
     }
-    
+
+    void "wires generated resources task into classes task for sourceset"() {
+        when:
+        javaBasePlugin.apply(project)
+        project.sourceSets.create('custom')
+
+        and:
+        final someTask = project.task("someTask")
+        project.sourceSets.custom.output.dir('some-dir', builtBy: someTask)
+
+        then:
+        def customClasses = project.tasks['customClasses']
+        TaskDependencyMatchers.dependsOn('someTask', 'processCustomResources', 'compileCustomJava').matches(customClasses)
+    }
+
     void tasksReflectChangesToSourceSetConfiguration() {
         def classesDir = project.file('target/classes')
         def resourcesDir = project.file('target/resources')
 
         when:
         javaBasePlugin.apply(project)
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         project.sourceSets.custom.output.classesDir = classesDir
         project.sourceSets.custom.output.resourcesDir = resourcesDir
 
@@ -112,21 +122,21 @@ class JavaBasePluginTest extends Specification {
     void createsConfigurationsForNewSourceSet() {
         when:
         javaBasePlugin.apply(project)
-        def sourceSet = project.sourceSets.add('custom')
+        def sourceSet = project.sourceSets.create('custom')
 
         then:
         def compile = project.configurations.customCompile
         compile.transitive
         !compile.visible
         compile.extendsFrom == [] as Set
-        compile.description == 'Classpath for compiling the custom sources.'
+        compile.description == "Compile classpath for source set 'custom'."
 
         and:
         def runtime = project.configurations.customRuntime
         runtime.transitive
         !runtime.visible
         runtime.extendsFrom == [compile] as Set
-        runtime.description == 'Classpath for running the compiled custom classes.'
+        runtime.description == "Runtime classpath for source set 'custom'."
 
         and:
         def runtimeClasspath = sourceSet.runtimeClasspath
@@ -179,7 +189,7 @@ class JavaBasePluginTest extends Specification {
         def task = project.task('customJar', type: Jar)
 
         then:
-        Matchers.dependsOn().matches(task)
+        TaskDependencyMatchers.dependsOn().matches(task)
         task.destinationDir == project.libsDir
     }
 
@@ -189,18 +199,18 @@ class JavaBasePluginTest extends Specification {
 
         then:
         def build = project.tasks[JavaBasePlugin.BUILD_TASK_NAME]
-        Matchers.dependsOn(JavaBasePlugin.CHECK_TASK_NAME, BasePlugin.ASSEMBLE_TASK_NAME).matches(build)
+        TaskDependencyMatchers.dependsOn(JavaBasePlugin.CHECK_TASK_NAME, BasePlugin.ASSEMBLE_TASK_NAME).matches(build)
 
         def buildDependent = project.tasks[JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME]
-        Matchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME).matches(buildDependent)
+        TaskDependencyMatchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME).matches(buildDependent)
 
         def buildNeeded = project.tasks[JavaBasePlugin.BUILD_NEEDED_TASK_NAME]
-        Matchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME).matches(buildNeeded)
+        TaskDependencyMatchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME).matches(buildNeeded)
     }
 
     def configuresTestTaskWhenDebugSystemPropertyIsSet() {
         javaBasePlugin.apply(project)
-        def task = project.tasks.add('test', Test.class)
+        def task = project.tasks.create('test', Test.class)
 
         when:
         System.setProperty("test.debug", "true")
@@ -210,9 +220,9 @@ class JavaBasePluginTest extends Specification {
         task.debug
     }
 
-    def configuresTestTaskWhenSingleTestSystemPropertyIsSet() {
+    def "configures test task when test.single is used"() {
         javaBasePlugin.apply(project)
-        def task = project.tasks.add('test', Test.class)
+        def task = project.tasks.create('test', Test.class)
         task.include 'ignoreme'
 
         when:
@@ -221,6 +231,7 @@ class JavaBasePluginTest extends Specification {
 
         then:
         task.includes == ['**/pattern*.class'] as Set
+        task.inputs.getSourceFiles().empty
     }
 
     def "adds functional and language source sets for each source set added to the 'sourceSets' container"() {
@@ -267,7 +278,7 @@ class JavaBasePluginTest extends Specification {
         }
 
         then:
-        def binary = project.binaries.jvm.findByName("custom")
+        def binary = project.binaries.findByName("customClasses")
         binary instanceof ClassDirectoryBinary
         binary.classesDir == project.file("classes")
         binary.resourcesDir == project.file("resources")
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy
index 24c0c45..990bbec 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy
@@ -16,12 +16,16 @@
 package org.gradle.api.plugins
 
 import org.gradle.api.Project
-import org.gradle.util.HelperUtil
+import org.gradle.language.java.JavaSourceSet
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.language.jvm.ClassDirectoryBinary
+import org.gradle.language.jvm.plugins.JvmLanguagePlugin
+import org.gradle.util.TestUtil
 
 import spock.lang.Specification
 
 class JavaLanguagePluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(JavaLanguagePlugin)
@@ -32,8 +36,15 @@ class JavaLanguagePluginTest extends Specification {
         project.plugins.hasPlugin(JvmLanguagePlugin)
     }
 
-    // TODO once we have a DSL for adding source sets/binaries
     def "adds a JavaCompile task for every JavaSourceSet added to a ClassDirectoryBinary"() {
+        when:
+        project.sources.create("model").create("java", JavaSourceSet)
+        project.binaries.create("integTest", ClassDirectoryBinary)
+        project.binaries.integTest.source << project.sources.model.java
 
+        then:
+        def task = project.tasks.findByName("compileIntegTestJava")
+        task instanceof JavaCompile
+        task.description == "Compiles Java source 'model:java'."
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
index 2babac1..e87eaa5 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
@@ -19,11 +19,11 @@ import org.gradle.api.Project
 import org.gradle.api.distribution.DistributionContainer
 import org.gradle.api.distribution.plugins.DistributionPlugin
 import org.gradle.api.tasks.bundling.Zip
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class JavaLibraryDistributionPluginTest extends Specification {
-    private final Project project = HelperUtil.builder().withName("test-project").build()
+    private final Project project = TestUtil.builder().withName("test-project").build()
 
     def "applies JavaPlugin and adds convention object with default values"() {
         when:
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 82c3ac2..cbd6ca1 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
@@ -24,8 +24,8 @@ import org.gradle.api.java.archives.internal.DefaultManifest
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
 import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TestUtil
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -34,12 +34,9 @@ import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertEquals
 import static org.junit.Assert.assertThat
 
-/**
- * @author Hans Dockter
- */
 class JavaPluginConventionTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private DefaultProject project = HelperUtil.createRootProject()
+    private DefaultProject project = TestUtil.createRootProject()
     private Instantiator instantiator = project.services.get(Instantiator)
     private JavaPluginConvention convention
 
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 5bcbb76..2a6f333 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
@@ -20,33 +20,31 @@ import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Dependency
+import org.gradle.api.file.FileCollectionMatchers
 import org.gradle.api.internal.java.JavaLibrary
 import org.gradle.api.internal.plugins.EmbeddableJavaProject
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.TaskDependencyMatchers
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.api.tasks.javadoc.Javadoc
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 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
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class JavaPluginTest {
     @Rule
     public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final JavaPlugin javaPlugin = new JavaPlugin()
 
     @Test public void appliesBasePluginsAndAddsConventionObject() {
@@ -114,7 +112,7 @@ class JavaPluginTest {
         assertThat(set.compileClasspath, sameInstance(project.configurations.compile))
         assertThat(set.output.classesDir, equalTo(new File(project.buildDir, 'classes/main')))
         assertThat(set.output.resourcesDir, equalTo(new File(project.buildDir, 'resources/main')))
-        assertThat(set.output, builtBy(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(set.output, TaskDependencyMatchers.builtBy(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.runtime))
         assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/main')))
 
@@ -125,7 +123,7 @@ class JavaPluginTest {
         assertThat(set.compileClasspath, hasItem(new File(project.buildDir, 'classes/main')))
         assertThat(set.output.classesDir, equalTo(new File(project.buildDir, 'classes/test')))
         assertThat(set.output.resourcesDir, equalTo(new File(project.buildDir, 'resources/test')))
-        assertThat(set.output, builtBy(JavaPlugin.TEST_CLASSES_TASK_NAME))
+        assertThat(set.output, TaskDependencyMatchers.builtBy(JavaPlugin.TEST_CLASSES_TASK_NAME))
         assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.testRuntime))
         assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/main')))
         assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/test')))
@@ -134,13 +132,13 @@ class JavaPluginTest {
     @Test public void createsMappingsForCustomSourceSets() {
         javaPlugin.apply(project)
 
-        def set = project.sourceSets.add('custom')
+        def set = project.sourceSets.create('custom')
         assertThat(set.java.srcDirs, equalTo(toLinkedSet(project.file('src/custom/java'))))
         assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/custom/resources'))))
         assertThat(set.compileClasspath, sameInstance(project.configurations.customCompile))
         assertThat(set.output.classesDir, equalTo(new File(project.buildDir, 'classes/custom')))
-        assertThat(set.output, builtBy('customClasses'))
-        assertThat(set.runtimeClasspath, sameCollection(set.output + project.configurations.customRuntime))
+        assertThat(set.output, TaskDependencyMatchers.builtBy('customClasses'))
+        assertThat(set.runtimeClasspath, FileCollectionMatchers.sameCollection(set.output + project.configurations.customRuntime))
     }
 
     @Test public void createsStandardTasksAndAppliesMappings() {
@@ -148,80 +146,80 @@ class JavaPluginTest {
 
         def task = project.tasks[JavaPlugin.PROCESS_RESOURCES_TASK_NAME]
         assertThat(task, instanceOf(Copy))
-        assertThat(task, dependsOn())
-        assertThat(task.source, sameCollection(project.sourceSets.main.resources))
+        assertThat(task, TaskDependencyMatchers.dependsOn())
+        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.main.resources))
         assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_JAVA_TASK_NAME]
         assertThat(task, instanceOf(JavaCompile))
-        assertThat(task, dependsOn())
+        assertThat(task, TaskDependencyMatchers.dependsOn())
         assertThat(task.classpath, sameInstance(project.sourceSets.main.compileClasspath))
         assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.classesDir))
-        assertThat(task.source, sameCollection(project.sourceSets.main.java))
+        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.main.java))
         
         task = project.tasks[JavaPlugin.CLASSES_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, JavaPlugin.COMPILE_JAVA_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, JavaPlugin.COMPILE_JAVA_TASK_NAME))
 
         task = project.tasks[JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME]
         assertThat(task, instanceOf(Copy))
-        assertThat(task, dependsOn())
-        assertThat(task.source, sameCollection(project.sourceSets.test.resources))
+        assertThat(task, TaskDependencyMatchers.dependsOn())
+        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.test.resources))
         assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME]
         assertThat(task, instanceOf(JavaCompile))
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.classpath, sameInstance(project.sourceSets.test.compileClasspath))
         assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.classesDir))
-        assertThat(task.source, sameCollection(project.sourceSets.test.java))
+        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.test.java))
 
         task = project.tasks[JavaPlugin.TEST_CLASSES_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME))
 
         task = project.tasks[JavaPlugin.JAR_TASK_NAME]
         assertThat(task, instanceOf(Jar))
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.destinationDir, equalTo(project.libsDir))
-        assertThat(task.copyAction.mainSpec.sourcePaths, equalTo([project.sourceSets.main.output] as Set))
+        assertThat(task.mainSpec.sourcePaths, equalTo([project.sourceSets.main.output] as Set))
         assertThat(task.manifest, notNullValue())
         assertThat(task.manifest, not(sameInstance(project.manifest)))
         assertThat(task.manifest.mergeSpecs.size(), equalTo(1))
         assertThat(task.manifest.mergeSpecs[0].mergePaths[0], sameInstance(project.manifest))
 
         task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
-        assertThat(task, dependsOn(JavaPlugin.JAR_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.JAR_TASK_NAME))
 
         task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(JavaPlugin.TEST_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.TEST_TASK_NAME))
 
         project.sourceSets.main.java.srcDirs(tmpDir.getTestDirectory())
         tmpDir.file("SomeFile.java").touch()
         task = project.tasks[JavaPlugin.JAVADOC_TASK_NAME]
         assertThat(task, instanceOf(Javadoc))
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.source.files, equalTo(project.sourceSets.main.allJava.files))
-        assertThat(task.classpath, sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
+        assertThat(task.classpath, FileCollectionMatchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/javadoc")))
         assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
 
         task = project.tasks["buildArchives"]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(JavaPlugin.JAR_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.JAR_TASK_NAME))
 
         task = project.tasks[JavaBasePlugin.BUILD_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(BasePlugin.ASSEMBLE_TASK_NAME, JavaBasePlugin.CHECK_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(BasePlugin.ASSEMBLE_TASK_NAME, JavaBasePlugin.CHECK_TASK_NAME))
 
         task = project.tasks[JavaBasePlugin.BUILD_NEEDED_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
 
         task = project.tasks[JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
     }
 
     @Test void "configures test task"() {
@@ -232,7 +230,7 @@ class JavaPluginTest {
 
         //then
         assert task instanceof org.gradle.api.tasks.testing.Test
-        assertThat(task, dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, TaskDependencyMatchers.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
@@ -250,9 +248,9 @@ class JavaPluginTest {
     }
 
     @Test public void buildOtherProjects() {
-        DefaultProject commonProject = HelperUtil.createChildProject(project, "common");
-        DefaultProject middleProject = HelperUtil.createChildProject(project, "middle");
-        DefaultProject appProject = HelperUtil.createChildProject(project, "app");
+        DefaultProject commonProject = TestUtil.createChildProject(project, "common");
+        DefaultProject middleProject = TestUtil.createChildProject(project, "middle");
+        DefaultProject appProject = TestUtil.createChildProject(project, "app");
 
         javaPlugin.apply(project);
         javaPlugin.apply(commonProject);
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy
index 3ced8ca..f495ede 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy
@@ -14,43 +14,49 @@
  * limitations under the License.
  */
 package org.gradle.api.plugins
-
-import org.gradle.api.internal.plugins.ProcessResources
-import org.gradle.api.tasks.ClassDirectoryBinary
-import org.gradle.api.tasks.JvmBinaryContainer
-import org.gradle.api.tasks.ResourceSet
-import org.gradle.util.HelperUtil
-
+import org.gradle.language.jvm.ClassDirectoryBinary
+import org.gradle.language.jvm.ResourceSet
+import org.gradle.language.jvm.internal.DefaultClassDirectoryBinary
+import org.gradle.language.jvm.plugins.JvmLanguagePlugin
+import org.gradle.language.jvm.tasks.ProcessResources
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class JvmLanguagePluginTest extends Specification {
-    def project = HelperUtil.createRootProject()
+    def project = TestUtil.createRootProject()
     def jvmLanguagePlugin = project.plugins.apply(JvmLanguagePlugin)
 
-    def "adds a 'binaries.jvm' container to the project"() {
-        def binaries = project.extensions.findByName("binaries")
+    def "registers the 'ResourceSet' type for each functional source set added to the 'sources' container"() {
+        when:
+        project.sources.create("custom")
+        project.sources.custom.create("resources", ResourceSet)
 
-        expect:
-        binaries != null
-        binaries.jvm instanceof JvmBinaryContainer
+        then:
+        project.sources.custom.resources instanceof ResourceSet
     }
 
-    def "allows the JvmBinaryContainer to be looked up on the plugin"() {
+    def "registers the ClassDirectoryBinary type with the binaries container"() {
+        def binaries = project.extensions.findByName("binaries")
+        def binary = binaries.create("test", ClassDirectoryBinary)
+
         expect:
-        jvmLanguagePlugin.jvmBinaryContainer instanceof JvmBinaryContainer
+        binary != null
+        binary instanceof DefaultClassDirectoryBinary
     }
 
     def "adds a 'classes' task for every ClassDirectoryBinary added to the container"() {
         when:
-        def binary = project.binaries.jvm.create("prod", ClassDirectoryBinary)
+        def binary = project.binaries.create("prod", ClassDirectoryBinary)
 
         then:
         binary.classesDir == new File("$project.buildDir/classes/prod")
-        project.tasks.findByName("prodClasses") != null
+        def task = project.tasks.findByName("prodClasses")
+        task != null
+        task.description == "Assembles classes 'prod'."
     }
 
     def "adds a 'processResources' task for every ResourceSet added to a ClassDirectoryBinary"() {
-        ClassDirectoryBinary binary = project.binaries.jvm.create("prod", ClassDirectoryBinary)
+        ClassDirectoryBinary binary = project.binaries.create("prod", ClassDirectoryBinary)
         ResourceSet resources = project.sources.create("main").create("resources", ResourceSet)
 
         when:
@@ -58,6 +64,26 @@ class JvmLanguagePluginTest extends Specification {
 
         then:
         project.tasks.size() == old(project.tasks.size()) + 1
-        project.tasks.findByName("processProdResources") instanceof ProcessResources
+        def task = project.tasks.findByName("processProdResources")
+        task instanceof ProcessResources
+        task.description == "Processes resources 'main:resources'."
+    }
+
+    def "adds tasks based on short name when ClassDirectoryBinary has name ending in Classes"() {
+        when:
+        ClassDirectoryBinary binary = project.binaries.create("fooClasses", ClassDirectoryBinary)
+        ResourceSet resources = project.sources.create("main").create("resources", ResourceSet)
+        binary.source.add(resources)
+
+        then:
+        binary.classesDir == new File("$project.buildDir/classes/foo")
+        def task = project.tasks.findByName("fooClasses")
+        task != null
+        task.description == "Assembles classes 'foo'."
+
+        and:
+        def resourcesTask = project.tasks.findByName("processFooResources")
+        resourcesTask instanceof ProcessResources
+        resourcesTask.description == "Processes resources 'main:resources'."
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy
index 626f2ab..e227cf5 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 package org.gradle.api.plugins
-
 import org.gradle.api.Project
-import org.gradle.api.tasks.BinariesContainer
-import org.gradle.api.tasks.ProjectSourceSet
-import org.gradle.api.tasks.ResourceSet
-import org.gradle.util.HelperUtil
-
+import org.gradle.language.base.BinaryContainer
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.internal.BinaryInternal
+import org.gradle.language.base.internal.BinaryNamingScheme
+import org.gradle.language.base.plugins.LanguageBasePlugin
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class LanguageBasePluginTest extends Specification {
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
 
     def setup() {
         project.plugins.apply(LanguageBasePlugin)
@@ -32,7 +32,7 @@ class LanguageBasePluginTest extends Specification {
 
     def "adds a 'binaries' container to the project"() {
         expect:
-        project.extensions.findByName("binaries") instanceof BinariesContainer
+        project.extensions.findByName("binaries") instanceof BinaryContainer
     }
 
     def "adds a 'sources' container to the project"() {
@@ -40,12 +40,17 @@ class LanguageBasePluginTest extends Specification {
         project.extensions.findByName("sources") instanceof ProjectSourceSet
     }
 
-    def "registers the 'ResourceSet' type for each functional source set added to the 'sources' container"() {
+    def "creates a lifecycle task for each binary"() {
+        def binary = Mock(BinaryInternal)
+        def namingScheme = Mock(BinaryNamingScheme)
+
         when:
-        project.sources.create("custom")
-        project.sources.custom.create("resources", ResourceSet)
+        project.extensions.findByType(BinaryContainer).add(binary)
 
         then:
-        project.sources.custom.resources instanceof ResourceSet
+        _ * binary.name >> "binaryName"
+        1 * binary.namingScheme >> namingScheme
+        1 * namingScheme.lifecycleTaskName >> "lifecycle"
+        1 * binary.setLifecycleTask({it == project.tasks.findByName("lifecycle")})
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
index 0e1b351..40c0496 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
@@ -21,24 +21,22 @@ import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Dependency
 import org.gradle.api.internal.artifacts.configurations.Configurations
 import org.gradle.api.tasks.bundling.War
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Before
 import org.junit.Test
-import static org.gradle.util.Matchers.dependsOn
+
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.WrapUtil.toSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class WarPluginTest {
-    private Project project // = HelperUtil.createRootProject()
+    private Project project // = TestUtil.createRootProject()
     private WarPlugin warPlugin// = new WarPlugin()
 
     @Before
     public void setUp() {
-        project = HelperUtil.createRootProject()
+        project = TestUtil.createRootProject()
         warPlugin = new WarPlugin()
     }
 
@@ -88,7 +86,7 @@ class WarPluginTest {
     @Test public void dependsOnRuntimeConfig() {
         warPlugin.apply(project)
 
-        Project childProject = HelperUtil.createChildProject(project, 'child')
+        Project childProject = TestUtil.createChildProject(project, 'child')
         JavaPlugin javaPlugin = new JavaPlugin()
         javaPlugin.apply(childProject)
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/GroovyRuntimeTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/GroovyRuntimeTest.groovy
new file mode 100644
index 0000000..27cebdd
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/GroovyRuntimeTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks
+
+import org.gradle.api.GradleException
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection
+import org.gradle.api.plugins.GroovyBasePlugin
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class GroovyRuntimeTest extends Specification {
+    def project = TestUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(GroovyBasePlugin)
+    }
+
+    GroovyRuntime getGroovyRuntime() {
+        project.extensions.getByType(GroovyRuntime)
+    }
+
+    def "inferred Groovy class path uses same 'groovy-all' Jar that is found on class path"() {
+        when:
+        def classpath = project.groovyRuntime.inferGroovyClasspath([project.file("other.jar"), project.file("groovy-all-2.1.2${classifier}.jar")])
+
+        then:
+        classpath.singleFile == project.file("groovy-all-2.1.2${classifier}.jar")
+
+        where:
+        classifier << ["", "-indy"]
+    }
+
+    def "inferred Groovy class path uses repository dependency if 'groovy' Jar is found on class path (to get transitive dependencies right)"() {
+        project.repositories {
+            mavenCentral()
+        }
+
+        when:
+        def classpath = project.groovyRuntime.inferGroovyClasspath([project.file("other.jar"), project.file("groovy-2.1.2${classifier}.jar")])
+
+        then:
+        classpath instanceof LazilyInitializedFileCollection
+        with(classpath.delegate) {
+            it instanceof Configuration
+            state == Configuration.State.UNRESOLVED
+            dependencies.size() == 2
+            dependencies.any { it.group == "org.codehaus.groovy" && it.name == "groovy" && it.version == "2.1.2" } // not sure how to check classifier
+            dependencies.any { it.group == "org.codehaus.groovy" && it.name == "groovy-ant" && it.version == "2.1.2" } // not sure how to check classifier
+        }
+
+        where:
+        classifier << ["", "-indy"]
+    }
+
+    def "inferred Groovy class path falls back to contents of 'groovy' configuration if the latter is explicitly configured"() {
+        project.dependencies {
+            groovy project.files("my-groovy.jar")
+        }
+
+        when:
+        def classpath = project.groovyRuntime.inferGroovyClasspath([project.file("other.jar"), project.file("groovy-all-2.1.2.jar")])
+
+        then:
+        classpath.singleFile.name == "my-groovy.jar"
+    }
+
+    def "inferred Groovy class path falls back to contents of 'groovy' configuration if no Groovy Jar found on class path"() {
+        project.dependencies {
+            groovy project.files("my-groovy.jar")
+        }
+
+        when:
+        def classpath = project.groovyRuntime.inferGroovyClasspath([project.file("other.jar"), project.file("other2.jar")])
+
+        then:
+        classpath.singleFile.name == "my-groovy.jar"
+    }
+
+    def "inferred Groovy class path falls back to contents of 'groovy' configuration if 'groovy' Jar found and no repository declared"() {
+        project.dependencies {
+            groovy project.files("my-groovy.jar")
+        }
+
+        when:
+        def classpath = project.groovyRuntime.inferGroovyClasspath([project.file("other.jar"), project.file("groovy-2.1.2.jar")])
+
+        then:
+        classpath.singleFile.name == "my-groovy.jar"
+    }
+
+    def "useful error message is produced when no groovy runtime could be found on a classpath"() {
+        given:
+        def classpath = [project.file("other.jar")]
+
+        when:
+        groovyRuntime.inferGroovyClasspath(classpath).files
+
+        then:
+        def exception = thrown(GradleException)
+        exception.message.contains "no Groovy Jar was found on class path: $classpath"
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy
new file mode 100644
index 0000000..af4e4cd
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/UploadTest.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.tasks
+
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+class UploadTest extends Specification {
+
+    def "can create task"() {
+        when:
+        TestUtil.createTask(Upload)
+
+        then:
+        noExceptionThrown()
+    }
+
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/application/CreateStartScriptsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/application/CreateStartScriptsTest.groovy
index 9a7912a..9f95392 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/application/CreateStartScriptsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/application/CreateStartScriptsTest.groovy
@@ -16,10 +16,10 @@
 package org.gradle.api.tasks.application
 
 import spock.lang.Specification
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 
 class CreateStartScriptsTest extends Specification {
-    final CreateStartScripts task = HelperUtil.createTask(CreateStartScripts.class)
+    final CreateStartScripts task = TestUtil.createTask(CreateStartScripts.class)
 
     def scriptNameDefaultsToApplicationName() {
         task.outputDir = new File('output')
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 1216a14..1f9dd44 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
@@ -19,12 +19,10 @@ package org.gradle.api.tasks.bundling
 import org.gradle.api.java.archives.internal.DefaultManifest
 import org.junit.Before
 import org.junit.Test
+
 import static org.junit.Assert.assertEquals
 import static org.junit.Assert.assertNotNull
 
-/**
- * @author Hans Dockter
- */
 class JarTest extends AbstractArchiveTaskTest {
     Jar 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 5eae4de..c95e78a 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
@@ -18,11 +18,9 @@ package org.gradle.api.tasks.bundling
 
 import org.junit.Before
 import org.junit.Test
+
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 class WarTest extends AbstractArchiveTaskTest {
 
     War war
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 21d04a0..8eee899 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
@@ -18,12 +18,10 @@ package org.gradle.api.tasks.compile
 
 import org.junit.Before
 import org.junit.Test
-import static org.junit.Assert.*;
-import static org.gradle.util.Matchers.*
 
-/**
- * @author Hans Dockter
- */
+import static org.gradle.util.Matchers.isEmpty
+import static org.junit.Assert.*
+
 class CompileOptionsTest {
     static final Map TEST_DEBUG_OPTION_MAP = [someDebugOption: 'someDebugOptionValue']
     static final Map TEST_FORK_OPTION_MAP = [someForkOption: 'someForkOptionValue']
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 e0ba8aa..353aaa6 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
@@ -18,9 +18,6 @@ package org.gradle.api.tasks.compile
 
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
 class DebugOptionsTest {
     DebugOptions debugOptions = new DebugOptions()
 
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 e6a8991..d60fe05 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
@@ -16,13 +16,11 @@
  
 package org.gradle.api.tasks.compile
 
-import static org.junit.Assert.*
 import org.junit.Before
 import org.junit.Test
 
-/**
- * @author Hans Dockter
- */
+import static org.junit.Assert.assertEquals
+
 class ForkOptionsTest {
     static final List PROPS = ['executable', 'memoryInitialSize', 'memoryMaximumSize', 'tempDir']
     
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 f88fedf..c64fb7e 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
@@ -16,13 +16,11 @@
  
 package org.gradle.api.tasks.compile
 
-import org.junit.Test
 import org.junit.Before
+import org.junit.Test
+
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 class GroovyCompileOptionsTest {
     static final Map TEST_FORK_OPTION_MAP = [someForkOption: 'someForkOptionValue']
 
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 65efbe0..e4ed4f5 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
@@ -33,12 +33,9 @@ import org.junit.runner.RunWith;
 
 import java.io.File;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class GroovyCompileTest extends AbstractCompileTest {
 
@@ -91,10 +88,15 @@ public class GroovyCompileTest extends AbstractCompileTest {
         assertFalse(testObj.getDidWork());
     }
 
-    @Test(expected = InvalidUserDataException.class)
+    @Test
     public void testMoansIfGroovyClasspathIsEmpty() {
         setUpMocksAndAttributes(testObj, true);
-        testObj.compile();
+        try {
+            testObj.compile();
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), containsString("'testTask.groovyClasspath' must not be empty."));
+        }
     }
 
     void setUpMocksAndAttributes(GroovyCompile compile, final boolean groovyClasspathEmpty) {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
index 66ef05b..4b3a0b2 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
@@ -15,13 +15,11 @@
  */
 package org.gradle.api.tasks.compile
 
+import org.junit.Before
 import org.junit.Test
-import org.junit.Before;
+
 import static org.junit.Assert.*
 
-/**
- * @author Hans Dockter
- */
 public class GroovyForkOptionsTest {
     static final Map PROPS = [memoryInitialSize: 'memoryInitialSize', memoryMaximumSize: 'memoryMaximumSize']
 
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
index bd1afa7..1be1aba 100644
--- 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
@@ -34,9 +34,6 @@ import java.io.File;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-/**
- * @author Hans Dockter
- */
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class JavaCompileTest extends AbstractCompileTest {
     private JavaCompile 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 111e76d..a1f1ea2 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
@@ -15,8 +15,6 @@
  */
 package org.gradle.api.tasks.javadoc;
 
-import java.util.Set;
-
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
@@ -25,12 +23,11 @@ import org.gradle.util.WrapUtil;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Set;
+
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 
-/**
- * @author Hans Dockter
- */
 public class GroovydocTest extends AbstractConventionTaskTest {
     private Groovydoc groovydoc;
 
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 94c1737..b408421 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
@@ -76,7 +76,7 @@ public class JavadocTest extends AbstractConventionTaskTest {
             will(returnValue(javadocExecHandleBuilderMock));
             one(javadocExecHandleBuilderMock).options(task.getOptions());
             will(returnValue(javadocExecHandleBuilderMock));
-            one(javadocExecHandleBuilderMock).optionsFile(new File(getProject().getBuildDir(), "tmp/taskname/javadoc.options"));
+            one(javadocExecHandleBuilderMock).optionsFile(new File(getProject().getBuildDir(), "tmp/testTask/javadoc.options"));
             will(returnValue(javadocExecHandleBuilderMock));
             one(javadocExecHandleBuilderMock).getExecHandle();
             will(returnValue(execActionMock));
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java
index 75dd311..5a29ce6 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java
@@ -20,9 +20,6 @@ import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.lib.legacy.ClassImposteriser;
 
-/**
- * @author Tom Eyckmans
- */
 public class AbstractTestFrameworkOptionsTest<T extends TestFramework> {
     protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy
index 8db4e9d..f173d89 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy
@@ -16,13 +16,13 @@
 package org.gradle.api.tasks.testing
 
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
 class TestReportTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmp
-    def reportTask = HelperUtil.createTask(TestReport)
+    def reportTask = TestUtil.createTask(TestReport)
 
     def "infers dependencies and results dirs from input tests"() {
         def test1 = test("test1")
@@ -49,7 +49,7 @@ class TestReportTest extends Specification {
     }
 
     def test(String name) {
-        def test = HelperUtil.createTask(Test, HelperUtil.createRootProject(), name)
+        def test = TestUtil.createTask(Test, TestUtil.createRootProject(), name)
         test.binResultsDir = tmp.file(name)
         return test
     }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy
index 4ba1207..cdbe8ef 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy
@@ -21,12 +21,9 @@ import org.gradle.api.internal.tasks.testing.TestResultProcessor
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter
 import org.gradle.api.internal.tasks.testing.junit.report.TestReporter
 import org.gradle.listener.ListenerBroadcast
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 12/7/12
- */
 class TestTaskSpec extends Specification {
 
     private testExecuter = Mock(TestExecuter)
@@ -34,7 +31,7 @@ class TestTaskSpec extends Specification {
     private testListenerBroadcaster = Mock(ListenerBroadcast)
     private testOutputListenerBroadcaster = Mock(ListenerBroadcast)
 
-    private task = HelperUtil.createTask(Test, [testExecuter: testExecuter, testFramework: testFramework,
+    private task = TestUtil.createTask(Test, [testExecuter: testExecuter, testFramework: testFramework,
             testListenerBroadcaster: testListenerBroadcaster, testOutputListenerBroadcaster: testOutputListenerBroadcaster])
 
     public setup(){
@@ -43,19 +40,21 @@ class TestTaskSpec extends Specification {
     }
 
     def "adds listeners and removes after execution"() {
+        task.setTestNameIncludePattern("Foo")
+
         when:
         task.executeTests()
 
         then:
-        3 * testListenerBroadcaster.add(_)
+        4 * testListenerBroadcaster.add(_)
         2 * testOutputListenerBroadcaster.add(_)
 
         then:
         1 * testExecuter.execute(task, _ as TestResultProcessor)
 
         then:
-        1 * testListenerBroadcaster.removeAll({it.size() == 3})
-        1 * testOutputListenerBroadcaster.removeAll({it.size() == 2})
+        1 * testListenerBroadcaster.removeAll()
+        1 * testOutputListenerBroadcaster.removeAll()
     }
 
     def "removes listeners even if execution fails"() {
@@ -69,7 +68,7 @@ class TestTaskSpec extends Specification {
         ex.message == "Boo!"
 
         and:
-        1 * testListenerBroadcaster.removeAll({it.size() == 3})
-        1 * testOutputListenerBroadcaster.removeAll({it.size() == 2})
+        1 * testListenerBroadcaster.removeAll()
+        1 * testOutputListenerBroadcaster.removeAll()
     }
 }
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 5b69a28..37dd054 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
@@ -37,8 +37,8 @@ import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
 import org.gradle.api.tasks.AbstractConventionTaskTest;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.HelperUtil;
 import org.gradle.util.TestClosure;
+import org.gradle.util.TestUtil;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.api.Action;
@@ -62,9 +62,6 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 
-/**
- * @author Hans Dockter
- */
 @RunWith(JMock.class)
 public class TestTest extends AbstractConventionTaskTest {
     static final String TEST_PATTERN_1 = "pattern1";
@@ -95,6 +92,12 @@ public class TestTest extends AbstractConventionTaskTest {
         reportDir = tmpDir.createDir("report");
 
         test = createTask(Test.class);
+
+        context.checking(new Expectations() {{
+            TestFrameworkOptions testOptions = context.mock(TestFrameworkOptions.class);
+            allowing(testFrameworkMock).getOptions();
+            will(returnValue(testOptions));
+        }});
     }
 
     public ConventionTask getTask() {
@@ -242,7 +245,7 @@ public class TestTest extends AbstractConventionTaskTest {
     @org.junit.Test
     public void notifiesListenerBeforeSuite() {
         final TestClosure closure = context.mock(TestClosure.class);
-        test.beforeSuite(HelperUtil.toClosure(closure));
+        test.beforeSuite(TestUtil.toClosure(closure));
 
         final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
 
@@ -256,7 +259,7 @@ public class TestTest extends AbstractConventionTaskTest {
     @org.junit.Test
     public void notifiesListenerAfterSuite() {
         final TestClosure closure = context.mock(TestClosure.class);
-        test.afterSuite(HelperUtil.toClosure(closure));
+        test.afterSuite(TestUtil.toClosure(closure));
 
         final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
         final TestResult result = context.mock(TestResult.class);
@@ -271,7 +274,7 @@ public class TestTest extends AbstractConventionTaskTest {
     @org.junit.Test
     public void notifiesListenerBeforeTest() {
         final TestClosure closure = context.mock(TestClosure.class);
-        test.beforeTest(HelperUtil.toClosure(closure));
+        test.beforeTest(TestUtil.toClosure(closure));
 
         final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
 
@@ -285,7 +288,7 @@ public class TestTest extends AbstractConventionTaskTest {
     @org.junit.Test
     public void notifiesListenerAfterTest() {
         final TestClosure closure = context.mock(TestClosure.class);
-        test.afterTest(HelperUtil.toClosure(closure));
+        test.afterTest(TestUtil.toClosure(closure));
 
         final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
         final TestResult result = context.mock(TestResult.class);
@@ -313,24 +316,13 @@ public class TestTest extends AbstractConventionTaskTest {
         assertEquals(toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3), test.getExcludes());
     }
 
-    private void expectOptionsBuilt() {
-        context.checking(new Expectations() {{
-            TestFrameworkOptions testOptions = context.mock(TestFrameworkOptions.class);
-            allowing(testFrameworkMock).getOptions();
-            will(returnValue(testOptions));
-        }});
-    }
-
     private void expectTestsExecuted() {
-        expectOptionsBuilt();
         context.checking(new Expectations() {{
             one(testExecuterMock).execute(with(sameInstance(test)), with(notNullValue(TestListenerAdapter.class)));
         }});
     }
 
     private void expectTestsFail() {
-        expectOptionsBuilt();
-
         context.checking(new Expectations() {{
             final TestResult result = context.mock(TestResult.class);
             allowing(result).getResultType();
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy
index fe2a0c9..31b0993 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy
@@ -15,17 +15,14 @@
  */
 package org.gradle.api.tasks.testing.testng
 
-import org.junit.Before
-import org.junit.Test
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*
-import org.gradle.api.tasks.testing.AbstractTestFrameworkOptionsTest
 import org.gradle.api.JavaVersion
 import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework
+import org.gradle.api.tasks.testing.AbstractTestFrameworkOptionsTest
+import org.junit.Before
+import org.junit.Test
 
-/**
- * @author Tom Eyckmans
- */
+import static org.hamcrest.Matchers.hasItems
+import static org.junit.Assert.*
 
 public class TestNGOptionsTest extends AbstractTestFrameworkOptionsTest<TestNGTestFramework> {
 
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
deleted file mode 100644
index 508b20c..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.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.api.tasks.wrapper;
-
-import org.gradle.api.internal.AbstractTask;
-import org.gradle.api.tasks.AbstractTaskTest;
-import org.gradle.test.fixtures.file.TestFile;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.gradle.util.GUtil;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.WrapUtil;
-import org.gradle.wrapper.GradleWrapperMain;
-import org.gradle.wrapper.WrapperExecutor;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class WrapperTest extends AbstractTaskTest {
-
-    private Wrapper wrapper;
-    private String targetWrapperJarPath;
-    private TestFile expectedTargetWrapperJar;
-    private File expectedTargetWrapperProperties;
-    @Rule
-    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-
-    @Before
-    public void setUp() {
-        wrapper = createTask(Wrapper.class);
-        wrapper.setGradleVersion("1.0");
-        targetWrapperJarPath = "gradle/wrapper";
-        expectedTargetWrapperJar = new TestFile(getProject().getProjectDir(),
-                targetWrapperJarPath + "/gradle-wrapper.jar");
-        expectedTargetWrapperProperties = new File(getProject().getProjectDir(),
-                targetWrapperJarPath + "/gradle-wrapper.properties");
-        new File(getProject().getProjectDir(), targetWrapperJarPath).mkdirs();
-        wrapper.setDistributionPath("somepath");
-    }
-
-    public AbstractTask getTask() {
-        return wrapper;
-    }
-
-    @Test
-    public void testWrapperDefaults() {
-        wrapper = createTask(Wrapper.class);
-        assertEquals(new File(getProject().getProjectDir(), "gradle/wrapper/gradle-wrapper.jar"), wrapper.getJarFile());
-        assertEquals(new File(getProject().getProjectDir(), "gradlew"), wrapper.getScriptFile());
-        assertEquals(new File(getProject().getProjectDir(), "gradlew.bat"), wrapper.getBatchScript());
-        assertEquals(GradleVersion.current().getVersion(), wrapper.getGradleVersion());
-        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getDistributionPath());
-        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getArchivePath());
-        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getDistributionBase());
-        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getArchiveBase());
-        assertNotNull(wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDeterminesWindowsScriptPathFromUnixScriptPath() {
-        wrapper.setScriptFile("build/gradle.sh");
-        assertEquals(getProject().file("build/gradle.bat"), wrapper.getBatchScript());
-
-        wrapper.setScriptFile("build/gradle-wrapper");
-        assertEquals(getProject().file("build/gradle-wrapper.bat"), wrapper.getBatchScript());
-    }
-
-    @Test
-    public void testDeterminesPropertiesFilePathFromJarPath() {
-        wrapper.setJarFile("build/gradle-wrapper.jar");
-        assertEquals(getProject().file("build/gradle-wrapper.properties"), wrapper.getPropertiesFile());
-    }
-    
-    @Test
-    public void testDownloadsFromReleaseRepositoryForReleaseVersions() {
-        wrapper.setGradleVersion("0.9.1");
-        assertEquals("http://services.gradle.org/distributions/gradle-0.9.1-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromReleaseRepositoryForPreviewReleaseVersions() {
-        wrapper.setGradleVersion("1.0-milestone-1");
-        assertEquals("http://services.gradle.org/distributions/gradle-1.0-milestone-1-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromSnapshotRepositoryForSnapshotVersions() {
-        wrapper.setGradleVersion("0.9.1-20101224110000+1100");
-        assertEquals("http://services.gradle.org/distributions-snapshots/gradle-0.9.1-20101224110000+1100-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testUsesExplicitlyDefinedDistributionUrl() {
-        wrapper.setGradleVersion("0.9");
-        wrapper.setDistributionUrl("http://some-url");
-        assertEquals("http://some-url", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testExecuteWithNonExistingWrapperJarParentDir() throws IOException {
-        checkExecute();
-    }
-
-    @Test
-    public void testCheckInputs() throws IOException {
-        assertThat(wrapper.getInputs().getProperties().keySet(),
-                equalTo(WrapUtil.toSet("distributionBase", "distributionPath", "distributionUrl", "archiveBase", "archivePath")));
-    }
-
-    @Test
-    public void testExecuteWithExistingWrapperJarParentDirAndExistingWrapperJar() throws IOException {
-        File jarDir = new File(getProject().getProjectDir(), "lib");
-        jarDir.mkdirs();
-        File wrapperJar = new File(getProject().getProjectDir(), targetWrapperJarPath);
-        File parentFile = expectedTargetWrapperJar.getParentFile();
-        assertTrue(parentFile.isDirectory() || parentFile.mkdirs());
-        try {
-            assertTrue(expectedTargetWrapperJar.createNewFile());
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not create %s.", wrapperJar), e);
-        }
-        checkExecute();
-    }
-
-    private void checkExecute() throws IOException {
-        wrapper.execute();
-        TestFile unjarDir = tmpDir.createDir("unjar");
-        expectedTargetWrapperJar.unzipTo(unjarDir);
-        unjarDir.file(GradleWrapperMain.class.getName().replace(".", "/") + ".class").assertIsFile();
-        Properties properties = GUtil.loadProperties(expectedTargetWrapperProperties);
-        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY), wrapper.getDistributionUrl());
-        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY), wrapper.getDistributionBase().toString());
-        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY), wrapper.getDistributionPath());
-        assertEquals(properties.getProperty(WrapperExecutor.ZIP_STORE_BASE_PROPERTY), wrapper.getArchiveBase().toString());
-        assertEquals(properties.getProperty(WrapperExecutor.ZIP_STORE_PATH_PROPERTY), wrapper.getArchivePath());
-    }
-
-    private String toNative(String s) {
-        return s.replace("/", File.separator);
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
index 5ef1024..83e1342 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
@@ -16,23 +16,21 @@
 
 package org.gradle.external.javadoc;
 
+import org.gradle.external.javadoc.internal.GroupsJavadocOptionFileOption;
+import org.gradle.external.javadoc.internal.JavadocOptionFile;
 import org.gradle.external.javadoc.internal.LinksOfflineJavadocOptionFileOption;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.After;
-import static org.junit.Assert.*;
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
-import org.gradle.external.javadoc.internal.JavadocOptionFile;
-import org.gradle.external.javadoc.internal.GroupsJavadocOptionFileOption;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
-import java.util.*;
 import java.io.File;
+import java.util.*;
+
+import static org.junit.Assert.*;
 
-/**
- * @author Tom Eyckmans
- */
 public class StandardJavadocDocletOptionsTest {
 
     private final JUnit4Mockery context = new JUnit4Mockery();
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOptionTest.java
index 0d4ee00..5d0f84f 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/BooleanJavadocOptionFileOptionTest.java
@@ -16,17 +16,14 @@
 
 package org.gradle.external.javadoc.internal;
 
-import org.junit.Before;
-import org.junit.Test;
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class BooleanJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOptionTest.java
index 1388066..4e35abb 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/EnumJavadocOptionFileOptionTest.java
@@ -16,18 +16,15 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.gradle.external.javadoc.JavadocMemberLevel;
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
-import org.gradle.external.javadoc.JavadocMemberLevel;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class EnumJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOptionTest.java
index 1916bcb..57c8851 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/FileJavadocOptionFileOptionTest.java
@@ -16,18 +16,15 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.IOException;
 import java.io.File;
+import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class FileJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOptionTest.java
index e2dae37..948a02d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOptionTest.java
@@ -16,19 +16,16 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
 import java.util.Arrays;
+import java.util.List;
 
-/**
- * @author Tom Eyckmans
- */
 public class GroupsJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy
index 2f2261d..1bfd5fa 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy
@@ -14,21 +14,21 @@
  * limitations under the License.
  */
 
-package org.gradle.external.javadoc.internal;
-
+package org.gradle.external.javadoc.internal
 
+import org.gradle.external.javadoc.MinimalJavadocOptions
 import org.gradle.internal.jvm.Jvm
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.ExecActionFactory
 import org.junit.Test
 import spock.lang.Specification
+
 import static org.junit.Assert.assertTrue
-import org.gradle.external.javadoc.MinimalJavadocOptions
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocExecHandleBuilderTest extends Specification {
 
-    private JavadocExecHandleBuilder javadocExecHandleBuilder = new JavadocExecHandleBuilder()
+    private ExecActionFactory execActionFactory = Mock(ExecActionFactory)
+    private JavadocExecHandleBuilder javadocExecHandleBuilder = new JavadocExecHandleBuilder(execActionFactory)
 
     def setup() {
         MinimalJavadocOptions minimalJavadocOptions = Mock()
@@ -37,16 +37,27 @@ public class JavadocExecHandleBuilderTest extends Specification {
     }
 
     def testCheckExecutableAfterInit() {
-        expect:
-        javadocExecHandleBuilder.execHandle.executable == Jvm.current().javadocExecutable.absolutePath
+        def action = Mock(ExecAction)
+
+        when:
+        javadocExecHandleBuilder.execHandle
+
+        then:
+        1 * execActionFactory.newExecAction() >> action
+        1 * action.executable(Jvm.current().javadocExecutable)
     }
 
     def testCheckCustomExecutable() {
         String executable = "somepath"
+        def action = Mock(ExecAction)
+
+        when:
         javadocExecHandleBuilder.executable = executable
-        
-        expect:
-        javadocExecHandleBuilder.execHandle.executable == executable
+        javadocExecHandleBuilder.execHandle
+
+        then:
+        1 * execActionFactory.newExecAction() >> action
+        1 * action.executable(executable)
     }
 
     def testSetNullExecDirectory() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java
index a221d96..e35a515 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java
@@ -25,9 +25,6 @@ import org.junit.Test;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocOptionFileTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     @SuppressWarnings("unchecked")
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContextTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContextTest.java
index 097bf81..65a5eee 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContextTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterContextTest.java
@@ -16,19 +16,16 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
-import org.gradle.util.WrapUtil;
 
 import java.io.BufferedWriter;
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class JavadocOptionFileWriterContextTest {
 
     private final JUnit4Mockery context = new JUnit4Mockery();
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOptionTest.java
index 36ea5e5..95ba46e 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/LinksOfflineJavadocOptionFileOptionTest.java
@@ -16,18 +16,15 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.gradle.external.javadoc.JavadocOfflineLink;
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
-import org.gradle.external.javadoc.JavadocOfflineLink;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class LinksOfflineJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOptionTest.java
index 51a6b3b..324b7ec 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/MultilineStringsJavadocOptionFileOptionTest.java
@@ -16,9 +16,9 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -26,9 +26,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * @author Melanie Pfautz
- */
 public class MultilineStringsJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOptionTest.java
index eac4d65..afbf7e0 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/OptionLessStringsJavadocOptionFileOptionTest.java
@@ -16,17 +16,14 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class OptionLessStringsJavadocOptionFileOptionTest {
 
     private final JUnit4Mockery context = new JUnit4Mockery();
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOptionTest.java
index 35238bd..03c6dcb 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/PathJavadocOptionFileOptionTest.java
@@ -16,18 +16,15 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.IOException;
 import java.io.File;
+import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class PathJavadocOptionFileOptionTest {
 
     private final JUnit4Mockery context = new JUnit4Mockery();
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOptionTest.java
index 65c64b6..1ca2994 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringJavadocOptionFileOptionTest.java
@@ -16,17 +16,14 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class StringJavadocOptionFileOptionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private JavadocOptionFileWriterContext writerContextMock;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOptionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOptionTest.java
index e5e58a2..9bae650 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOptionTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/StringsJavadocOptionFileOptionTest.java
@@ -16,17 +16,14 @@
 
 package org.gradle.external.javadoc.internal;
 
+import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.Expectations;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 
-/**
- * @author Tom Eyckmans
- */
 public class StringsJavadocOptionFileOptionTest {
 
     private final JUnit4Mockery context = new JUnit4Mockery();
diff --git a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/BuildableTestMethodResult.groovy b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/BuildableTestMethodResult.groovy
new file mode 100644
index 0000000..7ff0f11
--- /dev/null
+++ b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/BuildableTestMethodResult.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing
+
+import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.api.tasks.testing.TestResult
+
+class BuildableTestMethodResult extends TestMethodResult {
+    List<Throwable> exceptions = []
+
+    TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
+
+    private final List<MethodTestOutputEvent> outputEvents
+
+    BuildableTestMethodResult(String name, List<MethodTestOutputEvent> outputEvents, TestResult result) {
+        super(name, result)
+        this.outputEvents = outputEvents
+    }
+
+    void failure(String message, String text = message) {
+        failure(new TestResultException(message, text))
+    }
+
+    void failure(Exception e) {
+        exceptions.add(e)
+        resultType = TestResult.ResultType.FAILURE
+    }
+
+    def stderr(String output) {
+        outputEvents << new MethodTestOutputEvent(name, new DefaultTestOutputEvent(TestOutputEvent.Destination.StdErr, output))
+    }
+
+    def stdout(String output) {
+        outputEvents << new MethodTestOutputEvent(name, new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, output))
+    }
+
+    static class TestResultException extends Exception {
+
+        private final String message
+        private final String text
+
+        TestResultException(String message, String text) {
+            super(message)
+            this.text = text
+        }
+
+        String toString() {
+            return message
+        }
+
+        public void printStackTrace(PrintWriter s) {
+            s.print(text);
+        }
+    }
+
+}
diff --git a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/BuildableTestResultsProvider.groovy b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/BuildableTestResultsProvider.groovy
new file mode 100644
index 0000000..1acdf83
--- /dev/null
+++ b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/BuildableTestResultsProvider.groovy
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing
+
+import org.gradle.api.Action
+import org.gradle.api.internal.tasks.testing.junit.result.TestClassResult
+import org.gradle.api.internal.tasks.testing.junit.result.TestFailure
+import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.util.ConfigureUtil
+
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
+
+class BuildableTestResultsProvider implements TestResultsProvider {
+
+    long timestamp = 0
+    Map<Long, BuildableTestClassResult> testClasses = [:]
+    long idCounter = 1
+
+    BuildableTestClassResult testClassResult(String className, Closure configClosure = {}) {
+        BuildableTestClassResult testSuite = new BuildableTestClassResult(idCounter++, className, timestamp)
+        testSuite.with(configClosure)
+        testClasses[testSuite.id] = testSuite
+    }
+
+    void writeAllOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        doWrite(id, 0, true, destination, writer)
+    }
+
+    void writeNonTestOutput(long id, TestOutputEvent.Destination destination, Writer writer) {
+        doWrite(id, 0, false, destination, writer)
+    }
+
+    void writeTestOutput(long classId, long testId, TestOutputEvent.Destination destination, Writer writer) {
+        doWrite(classId, testId, false, destination, writer)
+    }
+
+    void doWrite(long classId, long testId, boolean allClassOutput, TestOutputEvent.Destination destination, Writer writer) {
+        BuildableTestClassResult testCase = testClasses[classId]
+        testCase.outputEvents.each { BuildableOutputEvent event ->
+            if (event.testOutputEvent.destination == destination && (allClassOutput || testId == event.testId)) {
+                writer.append(event.testOutputEvent.message)
+            }
+        }
+    }
+
+    void visitClasses(Action<? super TestClassResult> visitor) {
+        testClasses.values().each {
+            visitor.execute(it)
+        }
+    }
+
+    boolean isHasResults() {
+        !testClasses.isEmpty()
+    }
+
+    boolean hasOutput(long classId, TestOutputEvent.Destination destination) {
+        testClasses[classId]?.outputEvents?.find { it.testOutputEvent.destination == destination }
+    }
+
+    static class BuildableOutputEvent {
+        long testId
+        TestOutputEvent testOutputEvent
+
+        BuildableOutputEvent(long testId, TestOutputEvent testOutputEvent) {
+            this.testId = testId
+            this.testOutputEvent = testOutputEvent
+        }
+    }
+
+    class BuildableTestClassResult extends TestClassResult {
+        List<BuildableOutputEvent> outputEvents = []
+
+        long duration = 1000
+
+        BuildableTestClassResult(long id, String className, long startTime) {
+            super(id, className, startTime)
+        }
+
+        BuildableTestMethodResult testcase(String name, Closure configClosure = {}) {
+            BuildableTestMethodResult methodResult = new BuildableTestMethodResult(idCounter++, name, outputEvents, new SimpleTestResult())
+            add(methodResult)
+            ConfigureUtil.configure(configClosure, methodResult)
+        }
+
+        BuildableTestMethodResult testcase(long id, String name, Closure configClosure = {}) {
+            BuildableTestMethodResult methodResult = new BuildableTestMethodResult(id, name, outputEvents, new SimpleTestResult())
+            add(methodResult)
+            ConfigureUtil.configure(configClosure, methodResult)
+        }
+
+        def stderr(String output) {
+            outputEvents << new BuildableOutputEvent(0, new DefaultTestOutputEvent(StdErr, output))
+        }
+
+        def stdout(String output) {
+            outputEvents << new BuildableOutputEvent(0, new DefaultTestOutputEvent(StdOut, output))
+        }
+
+        @Override
+        long getDuration() {
+            this.duration
+        }
+    }
+
+    static class BuildableTestMethodResult extends TestMethodResult {
+
+        long duration
+        List<TestFailure> failures = []
+
+        TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
+
+        private final List<BuildableOutputEvent> outputEvents
+
+        BuildableTestMethodResult(long id, String name, List<BuildableOutputEvent> outputEvents, TestResult result) {
+            super(id, name)
+            completed(result)
+            this.outputEvents = outputEvents
+            duration = result.endTime - result.startTime;
+        }
+
+        void failure(String message, String stackTrace) {
+            failures.add(new TestFailure(message, stackTrace, "ExceptionType"))
+            resultType = TestResult.ResultType.FAILURE
+        }
+        
+        void ignore() {
+            resultType = TestResult.ResultType.SKIPPED
+        }
+
+        def stderr(String output) {
+            outputEvents << new BuildableOutputEvent(getId(), new DefaultTestOutputEvent(StdErr, output))
+        }
+
+        def stdout(String output) {
+            outputEvents << new BuildableOutputEvent(getId(), new DefaultTestOutputEvent(StdOut, output))
+        }
+    }
+
+    void close() throws IOException {
+        // nothing
+    }
+}
+
+
+
diff --git a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/MethodTestOutputEvent.groovy b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/MethodTestOutputEvent.groovy
new file mode 100644
index 0000000..65a6249
--- /dev/null
+++ b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/MethodTestOutputEvent.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing
+
+import org.gradle.api.tasks.testing.TestOutputEvent
+
+class MethodTestOutputEvent {
+    String testMethodName
+    TestOutputEvent testOutputEvent
+
+    MethodTestOutputEvent(String testMethodName, TestOutputEvent testOutputEvent) {
+        this.testMethodName = testMethodName
+        this.testOutputEvent = testOutputEvent
+    }
+}
diff --git a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/SimpleTestResult.groovy b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/SimpleTestResult.groovy
new file mode 100644
index 0000000..0213e5c
--- /dev/null
+++ b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/SimpleTestResult.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.api.internal.tasks.testing
+
+import org.gradle.api.tasks.testing.TestResult
+
+class SimpleTestResult implements TestResult {
+    TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
+    List<Throwable> exceptions = []
+    Throwable exception = exceptions[0]
+    long startTime = 0
+    long endTime = startTime + 100
+    long testCount = 1
+    long successfulTestCount = 1
+    long failedTestCount = 0
+    long skippedTestCount = 0
+
+    SimpleTestResult(long endTime = 100) {
+        this.endTime = endTime
+    }
+}
diff --git a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/junit/report/HtmlTestResultsFixture.groovy b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/junit/report/HtmlTestResultsFixture.groovy
new file mode 100644
index 0000000..c33a6c8
--- /dev/null
+++ b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/internal/tasks/testing/junit/report/HtmlTestResultsFixture.groovy
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.report
+
+import org.gradle.test.fixtures.file.TestFile
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+
+class HtmlTestResultsFixture {
+    Document content
+
+
+    HtmlTestResultsFixture(TestFile file) {
+        file.assertIsFile()
+        content = Jsoup.parse(file, null)
+    }
+
+    void assertHasTests(int tests) {
+        def testDiv = content.select("div#tests")
+        assert testDiv != null
+        def counter = testDiv.select("div.counter")
+        assert counter != null
+        assert counter.text() == tests as String
+    }
+
+    void assertHasFailures(int tests) {
+        def testDiv = content.select("div#failures")
+        assert testDiv != null
+        def counter = testDiv.select("div.counter")
+        assert counter != null
+        assert counter.text() == tests as String
+    }
+
+    void assertHasIgnored(int tests) {
+        def testDiv = content.select("div#ignored")
+        assert testDiv != null
+        def counter = testDiv.select("div.counter")
+        assert counter != null
+        assert counter.text() == tests as String
+    }
+
+    void assertHasDuration(String duration) {
+        def testDiv = content.select("div#duration")
+        assert testDiv != null
+        def counter = testDiv.select("div.counter")
+        assert counter != null
+        assert counter.text() == duration as String
+
+    }
+
+    void assertHasNoDuration() {
+        def testDiv = content.select("div#duration")
+        assert testDiv != null
+        def counter = testDiv.select("div.counter")
+        assert counter != null
+        assert counter.text() == "-"
+    }
+
+    void assertHasSuccessRate(int rate) {
+        def testDiv = content.select("div#successRate")
+        assert testDiv != null
+        def counter = testDiv.select("div.percent")
+        assert counter != null
+        assert counter.text() == "${rate}%"
+    }
+
+    void assertHasOverallResult(String result) {
+        assert content.select("div#successRate").hasClass(result)
+    }
+
+    void assertHasNoSuccessRate() {
+        def testDiv = content.select("div#successRate")
+        assert testDiv != null
+        def counter = testDiv.select("div.percent")
+        assert counter != null
+        assert counter.text() == "-"
+    }
+
+    void assertHasNoFailedTests() {
+        def tab = findTab('Failed tests')
+        assert tab.isEmpty()
+    }
+
+    void assertHasNoIgnoredTests() {
+        def tab = findTab('Ignored tests')
+        assert tab.isEmpty()
+    }
+
+    void assertHasNoNavLinks() {
+        assert findTab('Packages').isEmpty()
+    }
+
+    void assertHasLinkTo(String target, String display = target) {
+        assert content.select("a[href=${target}.html]").find { it.text() == display }
+    }
+
+    void assertHasFailedTest(String target, String testName) {
+        def tab = findTab('Failed tests')
+        assert tab != null
+        assert tab.select("a[href=${target}.html#$testName]").find { it.text() == testName }
+    }
+
+    void assertHasIgnoredTest(String target, String testName) {
+        def tab = findTab('Ignored tests')
+        assert tab != null
+        assert tab.select("a[href=${target}.html#$testName]").find { it.text() == testName }
+    }
+
+    void assertHasTest(String testName) {
+        assert findTestDetails(testName)
+    }
+
+    HtmlTestResultsFixture.AggregateDetails packageDetails(String packageName) {
+        def packageElement = findPackageDetails(packageName)
+        assert packageElement != null
+        new HtmlTestResultsFixture.AggregateDetails(packageElement)
+    }
+
+    HtmlTestResultsFixture.AggregateDetails classDetails(String className) {
+        def classElement = findClassDetails(className)
+        assert classElement != null
+        new HtmlTestResultsFixture.AggregateDetails(classElement)
+    }
+
+    HtmlTestResultsFixture.TestDetails testDetails(String testName) {
+        def testElement = findTestDetails(testName)
+        assert testElement != null
+        new HtmlTestResultsFixture.TestDetails(testElement)
+    }
+
+    List<HtmlTestResultsFixture.TestDetails> allTestDetails(String testName) {
+        def testElements = findAllTestDetails(testName)
+        assert testElements != null
+        testElements.collect { new HtmlTestResultsFixture.TestDetails(it) }
+    }
+
+    void assertHasFailure(String testName, String stackTrace) {
+        def detailsRows = findAllTestDetails(testName)
+        assert detailsRows.any { it.select("tr > td:eq(2)").text() == 'failed' }
+        def tab = findTab('Failed tests')
+        assert tab != null && !tab.isEmpty()
+        assert tab.select("pre").find { it.text() == stackTrace.trim() }
+    }
+
+    private def findTestDetails(String testName) {
+        def tab = findTab('Tests')
+        def anchor = tab.select("TD").find { it.text() == testName }
+        return anchor?.parent()
+    }
+
+    private def findAllTestDetails(String testName) {
+        def tab = findTab('Tests')
+        def anchors = tab.select("TD").findAll { it.text() == testName }
+        return anchors.collect { it?.parent() }
+    }
+
+    private def findPackageDetails(String packageName) {
+        def tab = findTab('Packages')
+        def anchor = tab.select("TD").find { it.text() == packageName }
+        return anchor?.parent()
+    }
+
+    private def findClassDetails(String className) {
+        def tab = findTab('Classes')
+        def anchor = tab.select("TD").find { it.text() == className }
+        return anchor?.parent()
+    }
+
+    void assertHasStandardOutput(String stdout) {
+        def tab = findTab('Standard output')
+        assert tab != null
+        assert tab.select("SPAN > PRE").find { it.text() == stdout.trim() }
+    }
+
+    void assertHasStandardError(String stderr) {
+        def tab = findTab('Standard error')
+        assert tab != null
+        assert tab.select("SPAN > PRE").find { it.text() == stderr.trim() }
+    }
+
+    private def findTab(String title) {
+        def tab = content.select("div.tab:has(h2:contains($title))")
+        return tab
+    }
+
+
+
+    class AggregateDetails {
+        private final Element tableElement
+
+        AggregateDetails(Element tabElement) {
+            this.tableElement = tabElement
+        }
+
+        void assertNumberOfTests(int expected) {
+            assert tableElement.select("tr > td:eq(1)").text() == "${expected}"
+        }
+
+        void assertNumberOfFailures(int expected) {
+            assert tableElement.select("tr > td:eq(2)").text() == "${expected}"
+        }
+
+        void assertNumberOfIgnored(int expected) {
+            assert tableElement.select("tr > td:eq(3)").text() == "${expected}"
+        }
+
+        void assertDuration(String expected) {
+            assert tableElement.select("tr > td:eq(4)").text() == expected
+        }
+
+        void assertSuccessRate(String expected) {
+            assert tableElement.select("tr > td:eq(5)").text() == expected
+        }
+
+        void assertPassed() {
+            assertOverallResult('success')
+        }
+
+        void assertIgnored() {
+            assertOverallResult('skipped')
+        }
+
+        void assertFailed() {
+            assertOverallResult('failures')
+        }
+
+        private void assertOverallResult(String expected) {
+            assert tableElement.select("tr > td:eq(0)").hasClass(expected)
+            assert tableElement.select("tr > td:eq(5)").hasClass(expected)
+        }
+
+        void assertLinksTo(String target) {
+            assert tableElement.select("a[href=${target}.html]") != null
+        }
+    }
+
+    class TestDetails {
+        private final Element tableElement
+
+        TestDetails(Element tabElement) {
+            this.tableElement = tabElement
+        }
+
+        void assertDuration(String expected) {
+            assert tableElement.select("tr > td:eq(1)").text() == expected
+        }
+
+        void assertPassed() {
+            assertResult('passed', 'success')
+        }
+
+        void assertIgnored() {
+            assertResult('ignored', 'skipped')
+        }
+
+        void assertFailed() {
+            assertResult('failed', 'failures')
+        }
+
+        private void assertResult(String expectedValue, String expectedClass) {
+            assert tableElement.select("tr > td:eq(2)").listIterator().any { Element it -> it.text() == expectedValue }
+            assert tableElement.select("tr > td:eq(2)").hasClass(expectedClass)
+        }
+
+        boolean failed() {
+            return tableElement.select("tr > td:eq(2)").listIterator().any { Element it -> it.text() == 'failed' } &&
+              tableElement.select("tr > td:eq(2)").hasClass('failures')
+        }
+    }
+}
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 fd89800..9d6396e 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
@@ -25,12 +25,9 @@ import org.junit.Test;
 import java.io.File;
 import java.util.List;
 
-import static org.gradle.util.Matchers.*;
+import static org.gradle.util.Matchers.isEmpty;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 public abstract class AbstractCompileTest extends AbstractConventionTaskTest {
     public static final String TEST_PATTERN_1 = "pattern1";
     public static final String TEST_PATTERN_2 = "pattern2";
diff --git a/subprojects/publish/publish.gradle b/subprojects/publish/publish.gradle
index 6d7a3dc..a212189 100644
--- a/subprojects/publish/publish.gradle
+++ b/subprojects/publish/publish.gradle
@@ -15,8 +15,10 @@
  */
 
 dependencies {
-    groovy libraries.groovy
     compile project(':core')
+    compile project(':coreImpl')
+
+    testCompile libraries.groovy
 
     integTestRuntime project(":ivy")
     integTestRuntime project(":maven")
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java
index fc1c6ca..6e3a419 100644
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java
@@ -16,10 +16,7 @@
 
 package org.gradle.api.publish;
 
-import org.gradle.api.Action;
-import org.gradle.api.Incubating;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.*;
 
 /**
  * A {@code PublicationContainer} is responsible for creating and managing {@link Publication} instances.
@@ -30,48 +27,22 @@ import org.gradle.api.NamedDomainObjectSet;
  *     <li>The {@link org.gradle.api.publish.ivy.plugins.IvyPublishPlugin} makes it possible to create {@link org.gradle.api.publish.ivy.IvyPublication} instances.</li>
  * </ul>
  *
- * See the documentation for {@link PublishingExtension#publications(org.gradle.api.Action)} for more examples of how to create and configure publications.
+ *
+ * <pre autoTested="true">
+ * apply plugin: 'ivy-publish'
+ *
+ * publishing.publications.create('publication-name', IvyPublication) {
+ *     // Configure the ivy publication here
+ * }
+ * </pre>
+ *
+ * The usual way to add publications is via a configuration block.
+ * See the documentation for {@link PublishingExtension#publications(org.gradle.api.Action)} for examples of how to create and configure publications.
  *
  * @since 1.3
  * @see Publication
  * @see PublishingExtension
  */
 @Incubating
-public interface PublicationContainer extends NamedDomainObjectSet<Publication> {
-
-    /**
-     * Creates a publication with the specified name and type, adding it to the container.
-     *
-     * <pre autoTested="true">
-     * apply plugin: 'maven-publish'
-     *
-     * publishing.publications.add('publication-name', MavenPublication)
-     * </pre>
-     *
-     * @param name The publication name.
-     * @param type The publication type.
-     * @return The added publication
-     * @throws InvalidUserDataException If type is not a valid publication type, or if a publication named "name" already exists.
-     */
-    <T extends Publication> T add(String name, Class<T> type) throws InvalidUserDataException;
-
-    /**
-     * Creates a publication with the specified name and type, adding it to the container and configuring it with the supplied action.
-     * A {@link groovy.lang.Closure} can be supplied in place of an action, through type coercion.
-     *
-     * <pre autoTested="true">
-     * apply plugin: 'ivy-publish'
-     *
-     * publishing.publications.add('publication-name', IvyPublication) {
-     *     // Configure the ivy publication here
-     * }
-     * </pre>
-     *
-     * @param name The publication name.
-     * @param type The publication type.
-     * @param configuration The action or closure to configure the publication with.
-     * @return The added publication
-     * @throws InvalidUserDataException If type is not a valid publication type, or if a publication named "name" already exists.
-     */
-    <T extends Publication> T add(String name, Class<T> type, Action<? super T> configuration) throws InvalidUserDataException;
+public interface PublicationContainer extends ExtensiblePolymorphicDomainObjectContainer<Publication> {
 }
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java
index ccc34aa..299ced1 100644
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java
@@ -26,6 +26,29 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler;
  * 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.
  *
+ * <p>
+ * The PublishingExtension is a {@link org.gradle.api.plugins.DeferredConfigurable} model element, meaning that extension will be configured as late as possible in the build.
+ * So any 'publishing' configuration blocks are not evaluated until either:
+ * <ol>
+ *     <li>The project is about to execute, or</li>
+ *     <li>he publishing extension is referenced as an instance, as opposed to via a configuration closure.</li>
+ * </ol>
+ * <p>
+ * A 'publishing' configuration block does not need to dereference the publishing extension, and so will be evaluated late. eg:
+ * <pre>
+ *     publishing {
+ *         publications { ... }
+ *         repositories.maven { ... }
+ *     }
+ * </pre>
+ *
+ * <p>
+ * Any use that accesses the publishing extension as an instance does require the publishing extension to be realised, forcing all configuration blocks to be evaluated. eg:
+ * <pre>
+ *     publishing.publications { ... }
+ *     publishing.repositories.maven { ... }
+ * </pre>
+ *
  * @since 1.3
  */
 @Incubating
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/CompositePublicationFactory.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/CompositePublicationFactory.java
deleted file mode 100644
index 04fe530..0000000
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/CompositePublicationFactory.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.InvalidUserDataException;
-import org.gradle.api.publish.Publication;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class CompositePublicationFactory {
-    private final Map<Class<? extends Publication>, PublicationFactory> factories = new LinkedHashMap<Class<? extends Publication>, PublicationFactory>();
-
-    public void register(Class<? extends Publication> type, PublicationFactory factory) {
-        factories.put(type, factory);
-    }
-
-    public <T extends Publication> T create(Class<T> type, String name) {
-        for (Map.Entry<Class<? extends Publication>, PublicationFactory> entry : factories.entrySet()) {
-            if (type.isAssignableFrom(entry.getKey())) {
-                return type.cast(entry.getValue().create(name));
-            }
-        }
-        throw new InvalidUserDataException("Cannot create publications of type: " + type);
-    }
-}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java
index bd4442b..5d9cc94 100644
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java
@@ -16,16 +16,13 @@
 
 package org.gradle.api.publish.internal;
 
-import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
 import org.gradle.api.publish.Publication;
+import org.gradle.api.publish.PublicationContainer;
 import org.gradle.internal.reflect.Instantiator;
 
-public class DefaultPublicationContainer extends DefaultNamedDomainObjectSet<Publication> implements PublicationContainerInternal {
-
-    private final CompositePublicationFactory publicationFactories = new CompositePublicationFactory();
-
+public class DefaultPublicationContainer extends DefaultPolymorphicDomainObjectContainer<Publication> implements PublicationContainer {
     public DefaultPublicationContainer(Instantiator instantiator) {
         super(Publication.class, instantiator);
     }
@@ -34,20 +31,4 @@ public class DefaultPublicationContainer extends DefaultNamedDomainObjectSet<Pub
     protected void handleAttemptToAddItemWithNonUniqueName(Publication o) {
         throw new InvalidUserDataException(String.format("Publication with name '%s' added multiple times", o.getName()));
     }
-
-    public <T extends Publication> T add(String name, Class<T> type) {
-        T publication = publicationFactories.create(type, name);
-        add(publication);
-        return publication;
-    }
-
-    public <T extends Publication> T add(String name, Class<T> type, Action<? super T> action) {
-        T publication = add(name, type);
-        action.execute(publication);
-        return publication;
-    }
-
-    public void registerFactory(Class<? extends Publication> type, PublicationFactory publicationFactory) {
-        publicationFactories.register(type, publicationFactory);
-    }
 }
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/GroovyPublicationContainer.groovy b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/GroovyPublicationContainer.groovy
deleted file mode 100644
index 90efb2f..0000000
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/GroovyPublicationContainer.groovy
+++ /dev/null
@@ -1,35 +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.publish.internal
-
-import org.gradle.internal.reflect.Instantiator
-
-class GroovyPublicationContainer extends DefaultPublicationContainer {
-    GroovyPublicationContainer(Instantiator instantiator) {
-        super(instantiator)
-    }
-
-    def methodMissing(String name, args) {
-        if (args.length == 1 && args[0] instanceof Class) {
-            return add(name, args[0]);
-        }
-        if (args.length == 2 && args[0] instanceof Class && args[1] instanceof Closure) {
-            return add(name, args[0], args[1])
-        }
-        throw new MissingMethodException(name, this.class, args)
-    }
-}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/ProjectDependencyPublicationResolver.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/ProjectDependencyPublicationResolver.java
new file mode 100644
index 0000000..656ea91
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/ProjectDependencyPublicationResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Project;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.publish.PublishingExtension;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * The start of a service that will resolve a ProjectDependency into publication coordinates, to use for publishing.
+ * For now is a simple implementation, but at some point could utilise components in the dependency project, usage in the referencing project, etc.
+ */
+public class ProjectDependencyPublicationResolver {
+    public ModuleVersionIdentifier resolve(ProjectDependency dependency) {
+        Project dependencyProject = dependency.getDependencyProject();
+        ((ProjectInternal) dependencyProject).evaluate();
+
+        PublishingExtension publishing = dependencyProject.getExtensions().findByType(PublishingExtension.class);
+
+        if (publishing == null || publishing.getPublications().withType(PublicationInternal.class).isEmpty()) {
+            // Project does not apply publishing (or has no publications): simply use the project name in place of the dependency name
+            return new DefaultModuleVersionIdentifier(dependency.getGroup(), dependencyProject.getName(), dependency.getVersion());
+        }
+
+        // See if all publications have the same identifier
+        Set<? extends PublicationInternal> publications = publishing.getPublications().withType(PublicationInternal.class);
+        Iterator<? extends PublicationInternal> iterator = publications.iterator();
+        ModuleVersionIdentifier candidate = iterator.next().getCoordinates();
+        while (iterator.hasNext()) {
+            ModuleVersionIdentifier alternative = iterator.next().getCoordinates();
+            if (!candidate.equals(alternative)) {
+                throw new UnsupportedOperationException("Publishing is not yet able to resolve a dependency on a project with multiple different publications.");
+            }
+        }
+        return candidate;
+
+    }
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationContainerInternal.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationContainerInternal.java
deleted file mode 100644
index 3e45898..0000000
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationContainerInternal.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.publish.Publication;
-import org.gradle.api.publish.PublicationContainer;
-
-public interface PublicationContainerInternal extends PublicationContainer {
-    void registerFactory(Class<? extends Publication> type, PublicationFactory publicationFactory);
-}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFactory.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFactory.java
deleted file mode 100644
index 65ff686..0000000
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFactory.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.publish.Publication;
-
-public interface PublicationFactory {
-    Publication create(String name);
-}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationInternal.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationInternal.java
new file mode 100644
index 0000000..8df4b9a
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationInternal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.publish.Publication;
+
+public interface PublicationInternal extends Publication {
+    ModuleVersionIdentifier getCoordinates();
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublishServices.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublishServices.java
new file mode 100644
index 0000000..615cfaf
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublishServices.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.internal.service.ServiceRegistration;
+import org.gradle.internal.service.scopes.PluginServiceRegistry;
+
+public class PublishServices implements PluginServiceRegistry {
+    public void registerGlobalServices(ServiceRegistration registration) {
+    }
+
+    public void registerBuildServices(ServiceRegistration registration) {
+        registration.add(ProjectDependencyPublicationResolver.class);
+    }
+
+    public void registerProjectServices(ServiceRegistration registration) {
+    }
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/PublishingPlugin.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/PublishingPlugin.java
index adb19b8..dd5c5da 100644
--- a/subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/PublishingPlugin.java
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/PublishingPlugin.java
@@ -19,11 +19,18 @@ package org.gradle.api.publish.plugins;
 import org.gradle.api.*;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectPublication;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectPublicationRegistry;
+import org.gradle.api.publish.Publication;
 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.api.publish.internal.GroovyPublicationContainer;
+import org.gradle.api.publish.internal.PublicationInternal;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.model.ModelPath;
+import org.gradle.model.ModelRule;
+import org.gradle.model.ModelRules;
 
 import javax.inject.Inject;
 
@@ -35,31 +42,50 @@ import javax.inject.Inject;
 @Incubating
 public class PublishingPlugin implements Plugin<Project> {
 
+    public static final String PUBLISH_TASK_GROUP = "publishing";
     public static final String PUBLISH_LIFECYCLE_TASK_NAME = "publish";
 
     private final Instantiator instantiator;
+    private final ModelRules modelRules;
     private final ArtifactPublicationServices publicationServices;
+    private final ProjectPublicationRegistry publicationRegistry;
 
     @Inject
-    public PublishingPlugin(ArtifactPublicationServices publicationServices, Instantiator instantiator) {
+    public PublishingPlugin(ArtifactPublicationServices publicationServices, Instantiator instantiator, ModelRules modelRules, ProjectPublicationRegistry publicationRegistry) {
         this.publicationServices = publicationServices;
         this.instantiator = instantiator;
+        this.modelRules = modelRules;
+        this.publicationRegistry = publicationRegistry;
     }
 
-    public void apply(Project project) {
+    public void apply(final Project project) {
         RepositoryHandler repositories = publicationServices.createRepositoryHandler();
-        PublicationContainer publications = instantiator.newInstance(GroovyPublicationContainer.class, instantiator);
-        project.getExtensions().create(PublishingExtension.NAME, DefaultPublishingExtension.class, repositories, publications);
+        PublicationContainer publications = instantiator.newInstance(DefaultPublicationContainer.class, instantiator);
+
+        // TODO Registering an extension should register it with the model registry as well
+        final PublishingExtension extension = project.getExtensions().create(PublishingExtension.NAME, DefaultPublishingExtension.class, repositories, publications);
 
-        // Access the publishing extension to ensure that it is configured and tasks are created
         project.afterEvaluate(new Action<Project>() {
             public void execute(Project project) {
-                project.getExtensions().getByType(PublishingExtension.class);
+                for (Publication publication : extension.getPublications()) {
+                    PublicationInternal internalPublication = (PublicationInternal) publication;
+                    publicationRegistry.registerPublication(project.getPath(), new DefaultProjectPublication(internalPublication.getCoordinates()));
+                }
+            }
+        });
+
+        ModelPath extensionModelPath = ModelPath.path(PublishingExtension.NAME);
+
+        modelRules.register(extensionModelPath.toString(), extension);
+
+        modelRules.rule(new ModelRule() {
+            public void triggerDeferredConfigurables(PublishingExtension publishingExtension) {
+                project.getExtensions().getByType(DefaultPublishingExtension.class);
             }
         });
 
-        Task publishLifecycleTask = project.getTasks().add(PUBLISH_LIFECYCLE_TASK_NAME);
+        Task publishLifecycleTask = project.getTasks().create(PUBLISH_LIFECYCLE_TASK_NAME);
         publishLifecycleTask.setDescription("Publishes all publications produced by this project.");
-        publishLifecycleTask.setGroup("publishing");
+        publishLifecycleTask.setGroup(PUBLISH_TASK_GROUP);
     }
 }
diff --git a/subprojects/publish/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry b/subprojects/publish/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
new file mode 100644
index 0000000..268659c
--- /dev/null
+++ b/subprojects/publish/src/main/resources/META-INF/services/org.gradle.internal.service.scopes.PluginServiceRegistry
@@ -0,0 +1 @@
+org.gradle.api.publish.internal.PublishServices
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
index 5c8f84d..e94c1dd 100644
--- 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
@@ -16,6 +16,7 @@
 
 package org.gradle.api.publish.internal
 import org.gradle.api.InvalidUserDataException
+import org.gradle.api.NamedDomainObjectFactory
 import org.gradle.api.UnknownDomainObjectException
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.publish.Publication
@@ -25,7 +26,7 @@ import spock.lang.Specification
 class DefaultPublicationContainerTest extends Specification {
 
     Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
-    GroovyPublicationContainer container = instantiator.newInstance(GroovyPublicationContainer, instantiator)
+    DefaultPublicationContainer container = instantiator.newInstance(DefaultPublicationContainer, instantiator)
 
     def "exception is thrown on unknown access"() {
         given:
@@ -45,11 +46,11 @@ class DefaultPublicationContainerTest extends Specification {
     def "can add and configure publication with API"() {
         given:
         Publication pub = publication("test")
-        PublicationFactory factory = Mock()
+        NamedDomainObjectFactory<Publication> factory = Mock()
         container.registerFactory(Publication, factory)
 
         when:
-        container.add("name", Publication) {
+        container.create("name", Publication) {
             value = 2
         }
 
@@ -61,54 +62,19 @@ class DefaultPublicationContainerTest extends Specification {
         pub.value == 2
     }
 
-    def "can add publication with DSL"() {
-        given:
-        Publication testPub = publication("test")
-        PublicationFactory factory = Mock()
-        container.registerFactory(Publication, factory)
-
-        when:
-        container.publication_name(Publication)
-
-        then:
-        1 * factory.create("publication_name") >> testPub
-
-        and:
-        container.getByName("test") == testPub
-    }
-
-    def "can add and configure publication with DSL"() {
-        given:
-        TestPublication testPub = publication("test")
-        PublicationFactory factory = Mock()
-        container.registerFactory(TestPublication, factory)
-
-        when:
-        container.publication_name(TestPublication) {
-            value = 2
-        }
-
-        then:
-        1 * factory.create("publication_name") >> testPub
-
-        and:
-        container.getByName("test") == testPub
-        testPub.value == 2
-    }
-
     def "cannot add multiple publications with same name"() {
         given:
-        PublicationFactory factory = Mock()
+        NamedDomainObjectFactory<TestPublication> factory = Mock()
         container.registerFactory(TestPublication, factory)
 
         when:
-        container.publication_name(TestPublication)
+        container.create("publication_name", TestPublication)
 
         then:
         1 * factory.create("publication_name") >> publication("test")
 
         when:
-        container.publication_name(TestPublication)
+        container.create("publication_name", TestPublication)
 
         then:
         1 * factory.create("publication_name") >> publication("test")
diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ProjectDependencyPublicationResolverTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ProjectDependencyPublicationResolverTest.groovy
new file mode 100644
index 0000000..2fa9981
--- /dev/null
+++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ProjectDependencyPublicationResolverTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.plugins.ExtensionContainerInternal
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.internal.reflect.DirectInstantiator
+import spock.lang.Specification
+
+public class ProjectDependencyPublicationResolverTest extends Specification {
+    def projectDependency = Mock(ProjectDependency)
+    def project = Mock(ProjectInternal)
+    def extensions = Mock(ExtensionContainerInternal)
+    def publishing = Mock(PublishingExtension)
+    def publications = new DefaultPublicationContainer(new DirectInstantiator())
+    def publication = Mock(PublicationInternal)
+
+    def "resolves project coordinates if project does not have publishing extension"() {
+        when:
+        projectDependency.dependencyProject >> project
+        project.evaluate() >> project
+        project.extensions >> extensions
+        extensions.findByType(PublishingExtension) >> null
+
+        projectDependency.group >> "dep-group"
+        project.name >> "project-name"
+        projectDependency.version >> "dep-version"
+
+        then:
+        with (resolve()) {
+            group == "dep-group"
+            name == "project-name"
+            version == "dep-version"
+        }
+    }
+
+    def "uses project coordinates when dependent project has no publications"() {
+        when:
+        dependentProjectHasPublications()
+
+        projectDependency.group >> "dep-group"
+        project.name >> "project-name"
+        projectDependency.version >> "dep-version"
+
+        then:
+        with (resolve()) {
+            group == "dep-group"
+            name == "project-name"
+            version == "dep-version"
+        }
+    }
+
+    def "uses coordinates of single publication from dependent project"() {
+        when:
+        def publication = Mock(PublicationInternal)
+        publication.name >> 'mock'
+        publication.coordinates >> new DefaultModuleVersionIdentifier("pub-group", "pub-name", "pub-version")
+
+        dependentProjectHasPublications(publication)
+
+        then:
+        with (resolve()) {
+            group == "pub-group"
+            name == "pub-name"
+            version == "pub-version"
+        }
+    }
+
+    def "prefers coordinates of publication from dependent project where all publications share coordinates"() {
+        when:
+        def publication = pub('mock', "pub-group", "pub-name", "pub-version")
+        def publication2 = pub('pub2', "pub-group", "pub-name", "pub-version")
+
+        dependentProjectHasPublications(publication, publication2)
+
+        then:
+        with (resolve()) {
+            group == "pub-group"
+            name == "pub-name"
+            version == "pub-version"
+        }
+    }
+
+    def "fails if cannot resolve single publication"() {
+        when:
+        def publication = pub('mock', "pub-group", "pub-name", "pub-version")
+        def publication2 = pub('pub2', "other-group", "other-name", "other-version")
+
+        dependentProjectHasPublications(publication, publication2)
+
+        and:
+        resolve()
+
+        then:
+        def e = thrown(UnsupportedOperationException)
+        e.message == "Publishing is not yet able to resolve a dependency on a project with multiple different publications."
+    }
+
+    private ModuleVersionIdentifier resolve() {
+        new ProjectDependencyPublicationResolver().resolve(projectDependency)
+    }
+
+    private void dependentProjectHasPublications(PublicationInternal... added) {
+        projectDependency.dependencyProject >> project
+        project.evaluate() >> project
+        project.extensions >> extensions
+        extensions.findByType(PublishingExtension) >> publishing
+        publishing.publications >> publications
+        publications.addAll(added)
+    }
+
+    private PublicationInternal pub(def name, def group, def module, def version) {
+        def publication = Mock(PublicationInternal)
+        publication.name >> name
+        publication.coordinates >> new DefaultModuleVersionIdentifier(group, module, version)
+        return publication
+    }
+}
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
index 1ebcf6f..4a0dbd2 100644
--- 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
@@ -18,15 +18,15 @@ 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 org.gradle.api.publish.internal.PublicationInternal
+import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class PublishingPluginTest extends Specification {
 
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
     PublishingExtension extension
 
     def setup() {
@@ -56,9 +56,7 @@ class PublishingPluginTest extends Specification {
 
     def "can add publication"() {
         given:
-        def publication = new Publication() {
-            String getName() { "foo" }
-        }
+        def publication = Stub(PublicationInternal)
 
         when:
         extension.publications.add(publication)
diff --git a/subprojects/reporting/reporting.gradle b/subprojects/reporting/reporting.gradle
index e094d99..7b21d94 100644
--- a/subprojects/reporting/reporting.gradle
+++ b/subprojects/reporting/reporting.gradle
@@ -1,10 +1,11 @@
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
     compile project(':core')
     compile 'com.googlecode.jatl:jatl:0.2.2'
 
     testCompile libraries.jsoup
     integTestRuntime project(':codeQuality')
+    integTestRuntime project(':jacoco')
 }
 
 useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy b/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
similarity index 100%
rename from subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
rename to subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
diff --git a/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy b/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy
index cc4e082..6ce2f9d 100644
--- a/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy
+++ b/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy
@@ -19,55 +19,88 @@ package org.gradle.api.reporting.plugins
 import org.gradle.integtests.fixtures.WellBehavedPluginTest
 import org.gradle.test.fixtures.file.TestFile
 import org.jsoup.Jsoup
-
-import static org.gradle.api.reporting.plugins.BuildDashboardPlugin.BUILD_DASHBOARD_TASK_NAME
+import org.jsoup.nodes.Document
 
 class BuildDashboardPluginIntegrationTest extends WellBehavedPluginTest {
 
     void setup() {
         writeBuildFile()
-        writeProjectFiles(testDirectory)
     }
 
-    private void writeProjectFiles(TestFile root) {
-        root.file("src/main/groovy/org/gradle/class1.groovy") << "package org.gradle; class class1 { }"
-        root.file("config/codenarc/rulesets.groovy") << ""
+    private void goodCode(TestFile root = testDirectory) {
+        root.file("src/main/groovy/org/gradle/Class1.groovy") << "package org.gradle; class Class1 { }"
+        buildFile << """
+            allprojects {
+                apply plugin: 'groovy'
+
+                dependencies {
+                    compile localGroovy()
+                }
+            }
+        """
     }
 
-    private TestFile getBuildDashboardFile() {
-        file("build/reports/buildDashboard/index.html")
+    private void withTests() {
+        buildFile << """
+            allprojects {
+                dependencies{
+                    testCompile "junit:junit:4.11"
+                }
+            }
+"""
     }
 
-    private int getDashboardLinksCount() {
-        Jsoup.parse(buildDashboardFile, null).select('ul li a').size()
+    private void goodTests(TestFile root = testDirectory) {
+        root.file("src/test/groovy/org/gradle/Class1.groovy") << "package org.gradle; class TestClass1 { @org.junit.Test void ok() { } }"
+        withTests()
     }
 
-    private void writeBuildFile() {
-        buildFile << """
-            apply plugin: 'build-dashboard'
+    private void badTests(TestFile root = testDirectory) {
+        root.file("src/test/groovy/org/gradle/Class1.groovy") << "package org.gradle; class TestClass1 { @org.junit.Test void broken() { throw new RuntimeException() } }"
+        withTests()
+    }
 
+    private void withCodenarc(TestFile root = testDirectory) {
+        root.file("config/codenarc/rulesets.groovy") << """
+            ruleset {
+                ruleset('rulesets/naming.xml')
+            }
+        """
+        buildFile << """
             allprojects {
-                apply plugin: 'groovy'
                 apply plugin: 'codenarc'
 
                 codenarc {
                     configFile = file('config/codenarc/rulesets.groovy')
                 }
+            }
+"""
+    }
 
+    private void writeBuildFile() {
+        buildFile << """
+            apply plugin: 'build-dashboard'
+
+            allprojects {
                 repositories {
                     mavenCentral()
                 }
-
-                dependencies {
-                    codenarc 'org.codenarc:CodeNarc:0.16.1'
-                    groovy localGroovy()
-                }
             }
         """
     }
 
+    private void failingDependenciesForTestTask() {
+        buildFile << """
+            task failingTask << { throw new RuntimeException() }
+
+            test.dependsOn failingTask
+        """
+    }
+
     private void setupSubproject() {
-        writeProjectFiles(file('subproject'))
+        def subprojectDir = file('subproject')
+        goodCode(subprojectDir)
+        goodTests(subprojectDir)
         file('settings.gradle') << "include 'subproject'"
     }
 
@@ -76,55 +109,166 @@ class BuildDashboardPluginIntegrationTest extends WellBehavedPluginTest {
     }
 
     String getMainTask() {
-        BUILD_DASHBOARD_TASK_NAME
+        'buildDashboard'
+    }
+
+    void 'build dashboard for a project with no other reports lists just the dashboard'() {
+        when:
+        run('buildDashboard')
+
+        then:
+        reports.size() == 1
+        hasReport(':buildDashboard', 'html')
+        unavailableReports.empty
+    }
+
+    void 'build dashboard lists the enabled reports for the project'() {
+        given:
+        goodCode()
+        goodTests()
+
+        when:
+        run('check', 'buildDashboard')
+
+        then:
+        reports.size() == 3
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
+    }
+
+    void 'build dashboard lists the reports which have not been generated'() {
+        given:
+        goodCode()
+        goodTests()
+
+        when:
+        run('buildDashboard')
+
+        then:
+        reports.size() == 1
+        hasReport(':buildDashboard', 'html')
+        unavailableReports.size() == 2
+        hasUnavailableReport(':test', 'html')
+        hasUnavailableReport(':test', 'junitXml')
+    }
+
+    void 'build dashboard is always generated after report generating tasks have executed'() {
+        given:
+        goodCode()
+        goodTests()
+
+        when:
+        run('buildDashboard', 'check')
+
+        then:
+        reports.size() == 3
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
+    }
+
+    void 'running a report generating task also generates build dashboard'() {
+        given:
+        goodCode()
+        goodTests()
+
+        when:
+        run('test')
+
+        then:
+        reports.size() == 3
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
     }
 
-    void 'running buildDashboard task on its own generates a link to it in the dashboard'() {
+    void 'build dashboard is generated even if report generating task fails'() {
+        given:
+        goodCode()
+        badTests()
+
         when:
-        run(BUILD_DASHBOARD_TASK_NAME)
+        runAndFail('check')
 
         then:
-        dashboardLinksCount == 1
+        reports.size() == 3
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
     }
 
-    void 'running buildDashboard task after some report generating task generates link to it in the dashboard'() {
+    void 'build dashboard is not generated if a dependency of the report generating task fails'() {
+        given:
+        goodCode()
+        goodTests()
+        failingDependenciesForTestTask()
+
         when:
-        run('check', BUILD_DASHBOARD_TASK_NAME)
+        runAndFail('check')
 
         then:
-        dashboardLinksCount == 2
+        !buildDashboardFile.exists()
     }
 
-    void 'no report is generated if it is disabled'() {
+    void 'build dashboard is not generated if a dependency of the report generating task fails even with --continue'() {
         given:
+        goodCode()
+        goodTests()
+        failingDependenciesForTestTask()
+
+        when:
+        args('--continue')
+        runAndFail('check')
+
+        then:
+        !buildDashboardFile.exists()
+    }
+
+    void 'dashboard is not generated if it is disabled'() {
+        given:
+        goodCode()
+
         buildFile << """
-            $BUILD_DASHBOARD_TASK_NAME {
+            buildDashboard {
                 reports.html.enabled = false
             }
         """
 
         when:
-        run(BUILD_DASHBOARD_TASK_NAME)
+        run('buildDashboard')
 
         then:
         !buildDashboardFile.exists()
     }
 
     void 'buildDashboard is incremental'() {
+        given:
+        goodCode()
+
         expect:
-        run(BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
-        run(BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in skippedTasks
+        run('buildDashboard') && ':buildDashboard' in nonSkippedTasks
+        run('buildDashboard') && ':buildDashboard' in skippedTasks
 
         when:
         buildDashboardFile.delete()
 
         then:
-        run(BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
+        run('buildDashboard') && ':buildDashboard' in nonSkippedTasks
     }
 
     void 'enabling an additional report renders buildDashboard out-of-date'() {
-        expect:
-        run('check', BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
+        given:
+        goodCode()
+        withCodenarc()
+
+        when:
+        run('check') && ':buildDashboard' in nonSkippedTasks
+
+        then:
+        reports.size() == 2
+        hasReport(':buildDashboard', 'html')
+        hasReport(':codenarcMain', 'html')
 
         when:
         buildFile << """
@@ -133,19 +277,125 @@ class BuildDashboardPluginIntegrationTest extends WellBehavedPluginTest {
             }
         """
 
+        and:
+        run('check') && ':buildDashboard' in nonSkippedTasks
+
+        then:
+        reports.size() == 3
+        hasReport(':buildDashboard', 'html')
+        hasReport(':codenarcMain', 'html')
+        hasReport(':codenarcMain', 'text')
+    }
+
+    void 'generating a report that was previously not available renders buildDashboard out-of-date'() {
+        given:
+        goodCode()
+        goodTests()
+
+        when:
+        run('buildDashboard') && ':buildDashboard' in nonSkippedTasks
+
+        then:
+        reports.size() == 1
+        hasReport(':buildDashboard', 'html')
+        unavailableReports.size() == 2
+        hasUnavailableReport(':test', 'html')
+        hasUnavailableReport(':test', 'junitXml')
+
+        when:
+        run('test') && ':buildDashboard' in nonSkippedTasks
+
         then:
-        run('check', BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
-        dashboardLinksCount == 3
+        reports.size() == 3
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
+        unavailableReports.empty
     }
 
     void 'reports from subprojects are aggregated'() {
         given:
+        goodCode()
+        goodTests()
         setupSubproject()
 
         when:
-        run('check', BUILD_DASHBOARD_TASK_NAME)
+        run('buildDashboard', 'check')
+
+        then:
+        reports.size() == 5
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
+        hasReport(':subproject:test', 'html')
+        hasReport(':subproject:test', 'junitXml')
+    }
+
+    void 'dashboard includes JaCoCo reports'() {
+        given:
+        goodCode()
+        goodTests()
+        buildFile << """
+            apply plugin:'jacoco'
+        """
+
+        when:
+        run("test", "jacocoTestReport")
+
+        then:
+        reports.size() == 4
+        hasReport(':buildDashboard', 'html')
+        hasReport(':test', 'html')
+        hasReport(':test', 'junitXml')
+        hasReport(':jacocoTestReport', 'html')
+    }
+
+    void 'dashboard includes CodeNarc reports'() {
+        given:
+        goodCode()
+        withCodenarc()
+
+        when:
+        run("check")
 
         then:
-        dashboardLinksCount == 3
+        reports.size() == 2
+        hasReport(':buildDashboard', 'html')
+        hasReport(':codenarcMain', 'html')
     }
+
+    void hasReport(String task, String name) {
+        assert reports.contains("Report generated by task '$task' ($name)" as String)
+    }
+
+    List<String> getReports() {
+        dashboard.select("div#content li a")*.text()
+    }
+
+    void hasUnavailableReport(String task, String name) {
+        assert unavailableReports.contains("Report generated by task '$task' ($name)" as String)
+    }
+
+    List<String> getUnavailableReports() {
+        dashboard.select("div#content li span.unavailable")*.text()
+    }
+
+    private TestFile getBuildDashboardFile() {
+        file("build/reports/buildDashboard/index.html")
+    }
+
+    private Document doc
+    private boolean attached
+
+    Document getDashboard() {
+        if (doc == null) {
+            doc = Jsoup.parse(buildDashboardFile, "utf8")
+        }
+        if (!attached) {
+            executer.beforeExecute { doc = null }
+            attached = true
+        }
+        return doc
+    }
+
 }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java b/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
index 9ba14f8..bab1362 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.plugins;
 
+import org.gradle.api.internal.file.FileLookup;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.reporting.ReportingExtension;
 import org.gradle.util.DeprecationLogger;
@@ -69,7 +70,7 @@ public class ReportingBasePluginConvention {
         DeprecationLogger.nagUserOfReplacedProperty("reportsDirName", "reporting.baseDir");
         extension.setBaseDir(new Callable<File>() {
             public File call() throws Exception {
-                return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve(reportsDirName);
+                return project.getServices().get(FileLookup.class).getFileResolver(project.getBuildDir()).resolve(reportsDirName);
             }
         });
     }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java
index e899f26..aa3a57b 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java
@@ -22,11 +22,11 @@ import org.gradle.api.Incubating;
  * The reporting configuration for the the {@link GenerateBuildDashboard} task.
  */
 @Incubating
-public interface BuildDashboardReports extends ReportContainer<SingleFileReport> {
+public interface BuildDashboardReports extends ReportContainer<Report> {
     /**
-     * The build dashboard html report
+     * The build dashboard HTML report
      *
-     * @return The build dashboard html report
+     * @return The build dashboard HTML report
      */
-    SingleFileReport getHtml();
+    DirectoryReport getHtml();
 }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ConfigurableReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ConfigurableReport.java
new file mode 100644
index 0000000..b5980b6
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ConfigurableReport.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A file based report to be created with a configurable destination.
+ */
+ at Incubating
+public interface ConfigurableReport extends Report {
+    /**
+     * Sets the destination for the report.
+     *
+     * The file parameter is evaluated as per {@link org.gradle.api.Project#file(Object)}.
+     *
+     * @param file The destination for the report.
+     */
+    void setDestination(Object file);
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/DirectoryReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/DirectoryReport.java
new file mode 100644
index 0000000..b7976ba
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/DirectoryReport.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+
+/**
+ * A directory based report to be created.
+ */
+ at Incubating
+public interface DirectoryReport extends ConfigurableReport {
+
+    /**
+     * Returns the entry point of a directory based Report
+     *
+     * This can be the index.html file in a html report
+     *
+     * @return the entry point of the report or
+     * {@link org.gradle.api.reporting.DirectoryReport#getDestination()}
+     * if no entry point defined
+     *
+      */
+    File getEntryPoint();
+
+    /**
+     * Always returns {@link Report.OutputType#DIRECTORY}
+     *
+     * @return {@link Report.OutputType#DIRECTORY}
+     */
+    OutputType getOutputType();
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java
index 0b12c6b..083e372 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java
@@ -31,7 +31,9 @@ import org.gradle.util.CollectionUtils;
 
 import javax.inject.Inject;
 import java.io.File;
+import java.io.Serializable;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -40,8 +42,7 @@ import java.util.Set;
  */
 @Incubating
 public class GenerateBuildDashboard extends DefaultTask implements Reporting<BuildDashboardReports> {
-
-    private Set<Reporting> aggregated = new LinkedHashSet<Reporting>();
+    private Set<Reporting<? extends ReportContainer<?>>> aggregated = new LinkedHashSet<Reporting<? extends ReportContainer<?>>>();
 
     @Nested
     private final DefaultBuildDashboardReports reports;
@@ -52,30 +53,30 @@ public class GenerateBuildDashboard extends DefaultTask implements Reporting<Bui
         reports.getHtml().setEnabled(true);
     }
 
-    /**
-     * A set of report files that will be aggregated by the generated report.
-     * @return A set of input report files.
-     */
     @Input
-    public Set<File> getInputReportsFiles() {
-        return CollectionUtils.collect(getEnabledInputReports(), new Transformer<File, Report>() {
-            public File transform(Report original) {
-                return original.getDestination();
+    public Set<ReportState> getInputReports() {
+        Set<ReportState> inputs = new HashSet<ReportState>();
+        for (Report report : getEnabledInputReports()) {
+            if (getReports().contains(report)) {
+                // A report to be generated, ignore
+                continue;
             }
-        });
+            inputs.add(new ReportState(report.getDisplayName(), report.getDestination(), report.getDestination().exists()));
+        }
+        return inputs;
     }
 
     private Set<Report> getEnabledInputReports() {
-        Set<NamedDomainObjectSet<Report>> enabledReportSets = CollectionUtils.collect(aggregated, new Transformer<NamedDomainObjectSet<Report>, Reporting>() {
-            public NamedDomainObjectSet<Report> transform(Reporting reporting) {
+        Set<NamedDomainObjectSet<? extends Report>> enabledReportSets = CollectionUtils.collect(aggregated, new Transformer<NamedDomainObjectSet<? extends Report>, Reporting<? extends ReportContainer<?>>>() {
+            public NamedDomainObjectSet<? extends Report> transform(Reporting<? extends ReportContainer<?>> reporting) {
                 return reporting.getReports().getEnabled();
             }
         });
-        return new LinkedHashSet<Report>(CollectionUtils.flattenToList(Report.class, enabledReportSets));
+        return new LinkedHashSet<Report>(CollectionUtils.flattenCollections(Report.class, enabledReportSets));
     }
 
     /**
-     * Configures which reportings are to be aggregated in the build dashboard report generated by this task.
+     * Configures which reports are to be aggregated in the build dashboard report generated by this task.
      *
      * <pre>
      * buildDashboard {
@@ -85,7 +86,7 @@ public class GenerateBuildDashboard extends DefaultTask implements Reporting<Bui
      *
      * @param reportings an array of {@link Reporting} instances that are to be aggregated
      */
-    public void aggregate(Reporting... reportings) {
+    public void aggregate(Reporting<? extends ReportContainer<?>>... reportings) {
         aggregated.addAll(Arrays.asList(reportings));
     }
 
@@ -123,10 +124,33 @@ public class GenerateBuildDashboard extends DefaultTask implements Reporting<Bui
     @TaskAction
     void run() {
         if (getReports().getHtml().isEnabled()) {
-            BuildDashboardGenerator generator = new BuildDashboardGenerator(getEnabledInputReports(), reports.getHtml().getDestination());
+            BuildDashboardGenerator generator = new BuildDashboardGenerator(getEnabledInputReports(), reports.getHtml().getEntryPoint());
             generator.generate();
-        }else {
+        } else {
             setDidWork(false);
         }
     }
+
+    private static class ReportState implements Serializable {
+        private final String name;
+        private final File destination;
+        private final boolean available;
+
+        private ReportState(String name, File destination, boolean available) {
+            this.name = name;
+            this.destination = destination;
+            this.available = available;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            ReportState other = (ReportState) obj;
+            return name.equals(other.name) && destination.equals(other.destination) && available == other.available;
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode() ^ destination.hashCode();
+        }
+    }
 }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
index b371e8f..a223e41 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
@@ -24,6 +24,8 @@ import java.io.Serializable;
 
 /**
  * A file based report to be created.
+ * <p>
+ * Tasks that produce reports expose instances of this type for configuration via the {@link Reporting} interface.
  */
 public interface Report extends Serializable, Configurable<Report> {
     
@@ -35,13 +37,13 @@ public interface Report extends Serializable, Configurable<Report> {
 
     /**
      * The symbolic name of this report.
-     *
-     * The name of the report usually indicates the format (e.g. xml, html etc.) but can be anything.
-     *
+     * <p>
+     * The name of the report usually indicates the format (e.g. XML, HTML etc.) but can be anything.
+     * <p>
      * When part of a {@link ReportContainer}, reports are accessed via their name. That is, given a report container variable
      * named {@code reports} containing a report who's {@code getName()} returns {@code "html"}, the report could be accessed
      * via:
-     *
+     * <p>
      * <pre>
      * reports.html
      * </pre>
@@ -59,7 +61,7 @@ public interface Report extends Serializable, Configurable<Report> {
 
     /**
      * Whether or not this report should be generated by whatever generates it.
-     *
+     * <p>
      * If {@code true}, the generator of this report will generate it at the appropriate time.
      * If {@code false}, the generator of this report will not generate this report.
      *
@@ -77,10 +79,10 @@ public interface Report extends Serializable, Configurable<Report> {
 
     /**
      * The location on the filesystem of the report when it is generated.
-     *
+     * <p>
      * Depending on the {@link #getOutputType() output type} of the report, this may point to
      * a file or a directory.
-     *
+     * <p>
      * Subtypes may implement setters for the destination.
      *
      * @return The location on the filesystem of the report when it is generated
@@ -94,14 +96,14 @@ public interface Report extends Serializable, Configurable<Report> {
 
         /**
          * The report outputs a single file.
-         *
+         * <p>
          * That is, the {@link #getDestination()} file points a single file.
          */
         FILE,
 
         /**
          * The report outputs files into a directory.
-         *
+         * <p>
          * That is, the {@link #getDestination()} file points to a directory.
          */
         DIRECTORY
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportContainer.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
index 95aa5f9..15f2c7b 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
@@ -21,13 +21,13 @@ import org.gradle.api.NamedDomainObjectSet;
 import org.gradle.util.Configurable;
 
 /**
- * A container of potential reports.
- * 
+ * A container of {@link Report} objects, that represent potential reports.
+ * <p>
  * Things that produce reports (typically tasks) expose a report container that contains {@link Report} objects for each
  * possible report that they can produce. Each report object can be configured individually, including whether or not it should
  * be produced by way of its {@link Report#setEnabled(boolean) enabled} property.
- *
- * ReportContainer implementations are <b>immutable</b> in that standard collection methods such as {@code add()}, {@code remove()}
+ * <p>
+ * {@code ReportContainer} implementations are <b>immutable</b> in that standard collection methods such as {@code add()}, {@code remove()}
  * and {@code clear()} will throw an {@link ImmutableViolationException}. However, implementations may provide new methods that allow
  * the addition of new report object and/or the removal of existing report objects.
  * 
@@ -37,7 +37,7 @@ public interface ReportContainer<T extends Report> extends NamedDomainObjectSet<
 
     /**
      * The exception thrown when any of this container's mutation methods are called.
-     *
+     * <p>
      * This applies to the standard {@link java.util.Collection} methods such as {@code add()}, {@code remove()}
      * and {@code clear()}.
      */
@@ -48,8 +48,8 @@ public interface ReportContainer<T extends Report> extends NamedDomainObjectSet<
     }
 
     /**
-     * Returns an immutable collection of all the enabled reports.
-     *
+     * Returns an immutable collection of all the enabled {@link Report} objects in this container.
+     * <p>
      * The returned collection is live. That is, as reports are enabled/disabled the returned collection always
      * reflects the current set of enabled reports.
      *
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Reporting.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Reporting.java
index b3e907d..37c1016 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Reporting.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Reporting.java
@@ -19,14 +19,48 @@ package org.gradle.api.reporting;
 import groovy.lang.Closure;
 
 /**
- * An object that provides reporting options
+ * An object that provides reporting options.
+ * <p>
+ * Tasks that produce reports as part of their execution expose configuration options of those reports via these methods.
+ * The {@code Reporting} interface is parameterized, where the parameter denotes the specific type of reporting container
+ * that is exposed. The specific type of the reporting container denotes the different types of reports available.
+ * <p>
+ * For example, given a task such as:
+ * </p>
+ * <pre>
+ * class MyTask implements Reporting<MyReportContainer> {
+ *     // implementation
+ * }
+ *
+ * interface MyReportContainer extends ReportContainer<Report> {
+ *     Report getHtml();
+ *     Report getCsv();
+ * }
+ * </pre>
+ * <p>
+ * The reporting aspects of such a task can be configured as such:
+ * </p>
+ * <pre>
+ * task my(type: MyTask) {
+ *     reports {
+ *         html.enabled = true
+ *         csv.enabled = false
+ *     }
+ * }
+ * </pre>
+ * <p>
+ * See the documentation for the specific {@code ReportContainer} type for the task for information on report types and options.
+ * </p>
  *
  * @param <T> The base type of the report container
  */
 public interface Reporting<T extends ReportContainer> {
 
     /**
-     * Returns the report container.
+     * A {@link ReportContainer} instance.
+     * <p>
+     * Implementors specify a specific implementation of {@link ReportContainer} that describes the types of reports that
+     * are available.
      *
      * @return The report container
      */
@@ -35,8 +69,6 @@ public interface Reporting<T extends ReportContainer> {
     /**
      * Allow configuration of the report container by closure.
      *
-     * For example…
-     *
      * <pre>
      * reports {
      *   html {
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
index a3d9e42..aba9862 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
@@ -16,21 +16,25 @@
 package org.gradle.api.reporting;
 
 import org.gradle.api.Project;
+import org.gradle.api.internal.file.FileLookup;
 import org.gradle.api.internal.project.ProjectInternal;
 
 import java.io.File;
 import java.util.concurrent.Callable;
 
 /**
- * Adds base configuration for reporting tasks.
- *
+ * A project extension named "reporting" that provides basic reporting settings and utilities.
+ * <p>
  * Example usage:
- *
+ * <p>
  * <pre>
  * reporting {
  *     baseDir "$buildDir/our-reports"
  * }
  * </pre>
+ * <p>
+ * When implementing a task that produces reports, the location of where to generate reports should be obtained
+ * via the {@link #file(String)} method of this extension.
  */
 public class ReportingExtension {
 
@@ -52,8 +56,8 @@ public class ReportingExtension {
         this.project = (ProjectInternal)project;
         this.baseDir = new Callable<File>() {
             public File call() throws Exception {
-                return ReportingExtension.this.project.getFileResolver().
-                        withBaseDir(ReportingExtension.this.project.getBuildDir()).
+                return ReportingExtension.this.project.getServices().
+                        get(FileLookup.class).getFileResolver(ReportingExtension.this.project.getBuildDir()).
                         resolve(DEFAULT_REPORTS_DIR_NAME);
             }
         };
@@ -61,7 +65,7 @@ public class ReportingExtension {
 
     /**
      * The base directory for all reports
-     *
+     * <p>
      * This value can be changed, so any files derived from this should be calculated on demand.
      * 
      * @return The base directory for all reports
@@ -72,7 +76,7 @@ public class ReportingExtension {
 
     /**
      * Sets the base directory to use for all reports
-     * 
+     * <p>
      * The value will be converted to a {@code File} on demand via {@link Project#file(Object)}.
      *
      * @param baseDir The base directory to use for all reports
@@ -83,14 +87,14 @@ public class ReportingExtension {
 
     /**
      * Creates a file object for the given path, relative to {@link #getBaseDir()}.
-     *
+     * <p>
      * The reporting base dir can be changed, so users of this method should use it on demand where appropriate.
      *
      * @param path the relative path
      * @return a file object at the given path relative to {@link #getBaseDir()}
      */
     public File file(String path) {  // TODO should this take Object?
-        return this.project.getFileResolver().withBaseDir(getBaseDir()).resolve(path);
+        return this.project.getServices().get(FileLookup.class).getFileResolver(getBaseDir()).resolve(path);
     }
 
     // TODO this doesn't belong here, that java plugin should add an extension to this guy with this
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
index 037c78e..e82c796 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
@@ -19,17 +19,7 @@ package org.gradle.api.reporting;
 /**
  * A report that is a single file.
  */
-public interface SingleFileReport extends Report {
-
-    /**
-     * Sets the destination for the report.
-     * 
-     * The file parameter is evaluated as per {@link org.gradle.api.Project#file(Object)}.
-     * 
-     * @param file The destination for the report.
-     */
-    void setDestination(Object file);
-
+public interface SingleFileReport extends ConfigurableReport {
     /**
      * Always returns {@link Report.OutputType#FILE}
      *
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java
index e68023e..b5d0034 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java
@@ -18,80 +18,88 @@ package org.gradle.api.reporting.internal;
 
 import com.googlecode.jatl.Html;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.reporting.DirectoryReport;
 import org.gradle.api.reporting.Report;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.CollectionUtils;
 import org.gradle.util.GFileUtils;
+import org.gradle.util.GradleVersion;
 
 import java.io.*;
+import java.util.Comparator;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class BuildDashboardGenerator {
     private Set<Report> reports;
     private File outputFile;
 
     public BuildDashboardGenerator(Set<Report> reports, File outputFile) {
-        this.reports = reports;
+        this.reports = new TreeSet<Report>(new Comparator<Report>() {
+            public int compare(Report o1, Report o2) {
+                return o1.getDisplayName().compareTo(o2.getDisplayName());
+            }
+        });
+        this.reports.addAll(reports);
         this.outputFile = outputFile;
     }
 
     public void generate() {
-        BufferedWriter writer = null;
         try {
             GFileUtils.parentMkdirs(outputFile);
-            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"));
-            generate(writer);
+            Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"));
+            try {
+                generate(writer);
+            } finally {
+                writer.close();
+            }
             copyCss();
         } catch (IOException e) {
             throw new UncheckedIOException(e);
-        } finally {
-            if (writer != null) {
-                try {
-                    writer.close();
-                } catch (IOException e) {
-                    throw new UncheckedIOException(e);
-                }
-            }
         }
     }
 
     private void copyCss() {
-        File cssFile = new File(outputFile.getParent(), "base-style.css");
-        GFileUtils.copyURLToFile(getClass().getResource("/org/gradle/reporting/base-style.css"), cssFile);
+        GFileUtils.copyURLToFile(getClass().getResource("/org/gradle/reporting/base-style.css"), new File(outputFile.getParent(), "base-style.css"));
+        GFileUtils.copyURLToFile(getClass().getResource("style.css"), new File(outputFile.getParent(), "style.css"));
     }
 
-    private Set<Report> getReportsWithExistingDestination() {
-        return CollectionUtils.filter(reports, new Spec<Report>() {
-            public boolean isSatisfiedBy(Report report) {
-                File destination = report.getDestination();
-                return destination != null && destination.exists();
-            }
-        });
-    }
-
-    private void generate(BufferedWriter writer) {
+    private void generate(Writer writer) {
         new Html(writer) {{
             html();
                 head();
                     meta().httpEquiv("Content-Type").content("text/html; charset=utf-8");
-                    link().rel("stylesheet").type("text/css").href("base-style.css");
-                end(2);
+                    link().rel("stylesheet").type("text/css").href("base-style.css").end();
+                    link().rel("stylesheet").type("text/css").href("style.css").end();
+                    title().text("Build dashboard").end();
+                end();
                 body();
                 div().id("content");
-                    Set<Report> reports = getReportsWithExistingDestination();
                     if (reports.size() > 0) {
-                        h1().text("Available build reports:").end();
+                        h1().text("Build reports").end();
                         ul();
                         for (Report report : reports) {
                             li();
-                                a().href(GFileUtils.relativePath(outputFile.getParentFile(), report.getDestination())).text(report.getDisplayName());
+                            if (report.getDestination().exists()) {
+                                a().href(GFileUtils.relativePath(outputFile.getParentFile(), getHtmlLinkedFileFromReport(report))).text(report.getDisplayName());
+                            } else {
+                                span().classAttr("unavailable").text(report.getDisplayName());
+                            }
                             end(2);
                         }
                         end();
                     } else {
                         h1().text("There are no build reports available.").end();
                     }
+                end();
+                div().id("footer").text(String.format("Generated by %s", GradleVersion.current()));
             endAll();
         }};
     }
+
+    private File getHtmlLinkedFileFromReport(Report report) {
+        if(report instanceof DirectoryReport){
+            return ((DirectoryReport) report).getEntryPoint();
+        } else{
+            return report.getDestination();
+        }
+    }
 }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java
index 171c2d7..b15faa3 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java
@@ -18,17 +18,17 @@ package org.gradle.api.reporting.internal;
 
 import org.gradle.api.Task;
 import org.gradle.api.reporting.BuildDashboardReports;
-import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.Report;
 
-public class DefaultBuildDashboardReports extends TaskReportContainer<SingleFileReport> implements BuildDashboardReports {
+public class DefaultBuildDashboardReports extends TaskReportContainer<Report> implements BuildDashboardReports {
 
     public DefaultBuildDashboardReports(Task task) {
-        super(SingleFileReport.class, task);
-
-        add(TaskGeneratedSingleFileReport.class, "html", task);
+        super(DirectoryReport.class, task);
+        add(TaskGeneratedSingleDirectoryReport.class, "html", task, "index.html");
     }
 
-    public SingleFileReport getHtml() {
-        return getByName("html");
+    public DirectoryReport getHtml() {
+        return (DirectoryReport)getByName("html");
     }
 }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
index a141072..44574d9 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
@@ -19,12 +19,11 @@ package org.gradle.api.reporting.internal;
 import groovy.lang.Closure;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.internal.ConfigureDelegate;
 import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.reporting.Report;
 import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.specs.Spec;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
 import java.util.SortedMap;
@@ -54,7 +53,7 @@ public class DefaultReportContainer<T extends Report> extends DefaultNamedDomain
     }
 
     public ReportContainer<T> configure(Closure cl) {
-        ConfigureUtil.configure(cl, new ConfigureDelegate(cl.getOwner(), this), false);
+        ConfigureUtil.configure(cl, this, false);
         return this;
     }
     
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleDirectoryReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleDirectoryReport.java
new file mode 100644
index 0000000..532880a
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleDirectoryReport.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.DirectoryReport;
+
+import java.io.File;
+
+public class TaskGeneratedSingleDirectoryReport extends TaskGeneratedReport implements DirectoryReport {
+
+    private final String relativeEntryPath;
+
+    public TaskGeneratedSingleDirectoryReport(String name, Task task, String relativeEntryPath) {
+        super(name, OutputType.DIRECTORY, task);
+        this.relativeEntryPath = relativeEntryPath;
+    }
+
+    public File getEntryPoint() {
+        if (relativeEntryPath == null) {
+            return getDestination();
+        } else {
+            return new File(getDestination(), relativeEntryPath);
+        }
+    }
+
+    @Override
+    public void setDestination(Object destination) {
+        super.setDestination(destination);
+    }
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.groovy b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.groovy
deleted file mode 100644
index 22ccb22..0000000
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.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.api.reporting.plugins
-
-import org.gradle.api.Incubating
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.plugins.ReportingBasePlugin
-import org.gradle.api.reporting.GenerateBuildDashboard
-import org.gradle.api.reporting.Reporting
-import org.gradle.api.reporting.ReportingExtension
-import org.gradle.api.reporting.SingleFileReport
-
-/**
- * <p>A {@link Plugin} which allows to generate build dashboard report.</p>
- */
- at Incubating
-public class BuildDashboardPlugin implements Plugin<ProjectInternal> {
-    public static final String BUILD_DASHBOARD_TASK_NAME = "buildDashboard"
-
-    public void apply(ProjectInternal project) {
-        project.plugins.apply(ReportingBasePlugin)
-
-        GenerateBuildDashboard buildDashboardTask = project.tasks.add(BUILD_DASHBOARD_TASK_NAME, GenerateBuildDashboard)
-
-        project.allprojects.each { aggregateReportings(it, buildDashboardTask) }
-        addReportDestinationConventionMapping(project, buildDashboardTask.reports.html);
-    }
-
-    private void addReportDestinationConventionMapping(ProjectInternal project, SingleFileReport buildDashboardReport) {
-        buildDashboardReport.conventionMapping.map('destination') {
-            project.extensions.getByType(ReportingExtension).file('buildDashboard/index.html')
-        }
-    }
-
-    private void aggregateReportings(Project project, GenerateBuildDashboard buildDashboardTask) {
-        project.tasks.withType(Reporting).all {
-            buildDashboardTask.aggregate(it)
-        }
-    }
-}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.java
new file mode 100644
index 0000000..cece2c6
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.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.reporting.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.plugins.ReportingBasePlugin;
+import org.gradle.api.reporting.DirectoryReport;
+import org.gradle.api.reporting.GenerateBuildDashboard;
+import org.gradle.api.reporting.Reporting;
+import org.gradle.api.reporting.ReportingExtension;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Adds a task, "buildDashboard", that aggregates the output of all tasks executed during the build that produce reports.
+ */
+ at Incubating
+public class BuildDashboardPlugin implements Plugin<ProjectInternal> {
+
+    public static final String BUILD_DASHBOARD_TASK_NAME = "buildDashboard";
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(ReportingBasePlugin.class);
+
+        final GenerateBuildDashboard buildDashboardTask = project.getTasks().create(BUILD_DASHBOARD_TASK_NAME, GenerateBuildDashboard.class);
+
+        DirectoryReport htmlReport = buildDashboardTask.getReports().getHtml();
+        ConventionMapping htmlReportConventionMapping = new DslObject(htmlReport).getConventionMapping();
+        htmlReportConventionMapping.map("destination", new Callable<Object>() {
+            public Object call() throws Exception {
+                return project.getExtensions().getByType(ReportingExtension.class).file("buildDashboard");
+            }
+        });
+
+        Action<Task> captureReportingTasks = new Action<Task>() {
+            public void execute(Task task) {
+                if (!(task instanceof Reporting)) {
+                    return;
+                }
+
+                Reporting reporting = (Reporting) task;
+
+                buildDashboardTask.aggregate(reporting);
+
+                if (!task.equals(buildDashboardTask)) {
+                    task.finalizedBy(buildDashboardTask);
+                }
+            }
+        };
+
+        for (Project aProject : project.getAllprojects()) {
+            aProject.getTasks().all(captureReportingTasks);
+        }
+    }
+
+}
diff --git a/subprojects/reporting/src/main/resources/org/gradle/api/reporting/internal/style.css b/subprojects/reporting/src/main/resources/org/gradle/api/reporting/internal/style.css
new file mode 100644
index 0000000..d6272e4
--- /dev/null
+++ b/subprojects/reporting/src/main/resources/org/gradle/api/reporting/internal/style.css
@@ -0,0 +1,3 @@
+.unavailable {
+    color: #808080;
+}
\ No newline at end of file
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
new file mode 100644
index 0000000..431508c
--- /dev/null
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+// Note: ReportingBasePluginConvention has been deprecated
+public class ReportingBasePluginConventionTest extends Specification {
+
+    Project project = ProjectBuilder.builder().build()
+    ReportingExtension extension = new ReportingExtension(project)
+    ReportingBasePluginConvention convention = new ReportingBasePluginConvention(project, extension)
+
+    def "defaults to reports dir in build dir"() {
+        expect:
+        convention.reportsDirName == ReportingExtension.DEFAULT_REPORTS_DIR_NAME
+        convention.reportsDir == new File(project.buildDir, ReportingExtension.DEFAULT_REPORTS_DIR_NAME)
+    }
+
+    def "can set reports dir by name, relative to build dir"() {
+        when:
+        convention.reportsDirName = "something-else"
+
+        then:
+        convention.reportsDir == new File(project.buildDir, "something-else")
+
+        when:
+        project.buildDir = new File(project.buildDir, "new-build-dir")
+
+        then:
+        convention.reportsDir == new File(project.buildDir, "something-else")
+    }
+
+    def "calculates api doc title from project name and version"() {
+        expect:
+        project.version == Project.DEFAULT_VERSION
+
+        and:
+        convention.apiDocTitle == "$project.name API"
+
+        when:
+        project.version = "1.0"
+
+        then:
+        convention.apiDocTitle == "$project.name 1.0 API"
+    }
+
+}
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
new file mode 100644
index 0000000..087aa01
--- /dev/null
+++ b/subprojects/reporting/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.TestUtil
+import spock.lang.Specification
+
+public class ReportingBasePluginTest extends Specification {
+
+    Project project = TestUtil.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/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy
index debd8c7..1d1702c 100644
--- a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.reporting
 
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Test
 import spock.lang.Specification
 
@@ -24,7 +24,7 @@ class GenerateBuildDashboardSpec extends Specification {
     @Test
     def "does no work if html report is disabled"() {
         setup:
-        GenerateBuildDashboard task = HelperUtil.createTask(GenerateBuildDashboard)
+        GenerateBuildDashboard task = TestUtil.createTask(GenerateBuildDashboard)
         when:
         task.reports.html.enabled = false
         and:
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy
index 0a8889f..2fc4dad 100644
--- a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.api.reporting.internal
 
+import org.gradle.api.reporting.DirectoryReport
 import org.gradle.api.reporting.Report
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.jsoup.Jsoup
@@ -49,6 +50,15 @@ class BuildDashboardGeneratorSpec extends Specification {
         }
     }
 
+    Report mockDirectoryReport(String name, File destinationDirectory) {
+        Stub(DirectoryReport){
+            getDisplayName() >> name
+            getDestination() >> destinationDirectory
+            getEntryPoint() >> new File(destinationDirectory, "index.html")
+        }
+    }
+
+
     void 'appropriate message is displayed when there are no reports available'() {
         given:
         generatorFor([])
@@ -62,22 +72,28 @@ class BuildDashboardGeneratorSpec extends Specification {
 
     void 'links to reports are added to the generated markup'() {
         given:
+        def htmlFolder = tmpDir.createDir('htmlContent');
+        htmlFolder.createFile("index.html")
         generatorFor([
                 mockReport('a', tmpDir.createFile('report.html')),
                 mockReport('b', tmpDir.createDir('inner').createFile('otherReport.html')),
                 mockReport('c', tmpDir.file('idonotexist.html')),
-                mockReport('d', null)
+                mockDirectoryReport('d', htmlFolder),
+                mockReport('e', tmpDir.createDir('simpleDirectory')),
         ])
 
         when:
         generator.generate()
 
         then:
-        outputHtml.select('h1').text() == 'Available build reports:'
+        outputHtml.select('h1').text() == 'Build reports'
         with outputHtml.select('ul li'), {
-            size() == 2
+            size() == 5
             select('a[href=report.html]').text() == 'a'
             select('a[href=inner/otherReport.html]').text() == 'b'
+            select('span[class=unavailable]').text() == 'c'
+            select('a[href=htmlContent/index.html]').text() == 'd'
+            select('a[href=simpleDirectory]').text() == 'e'
         }
     }
 
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
index b0ed1e4..cca2d10 100644
--- 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
@@ -21,13 +21,13 @@ 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.internal.file.TestFiles
 import org.gradle.api.reporting.Report
 import org.gradle.api.reporting.ReportContainer
-import spock.lang.Specification
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.reflect.ObjectInstantiationException
+import spock.lang.Specification
 
 class DefaultReportContainerTest extends Specification {
 
@@ -39,7 +39,7 @@ class DefaultReportContainerTest extends Specification {
             
             c.delegate = new Object() {
                 Report createReport(String name) {
-                    add(SimpleReport, name, name, Report.OutputType.FILE, new IdentityFileResolver())
+                    add(SimpleReport, name, name, Report.OutputType.FILE, TestFiles.resolver())
                 }
             }
             
@@ -75,7 +75,7 @@ class DefaultReportContainerTest extends Specification {
 
     def "container is immutable"() {
         when:
-        container.add(new SimpleReport("d", "d", Report.OutputType.FILE, new IdentityFileResolver()))
+        container.add(new SimpleReport("d", "d", Report.OutputType.FILE, TestFiles.resolver()))
         
         then:
         thrown(ReportContainer.ImmutableViolationException)
diff --git a/subprojects/resources/resources.gradle b/subprojects/resources/resources.gradle
new file mode 100644
index 0000000..6474d53
--- /dev/null
+++ b/subprojects/resources/resources.gradle
@@ -0,0 +1,11 @@
+/*
+ * A set of general-purpose resource abstractions.
+ */
+dependencies {
+    compile libraries.slf4j_api
+    compile project(':baseServices')
+    testCompile libraries.groovy
+}
+
+useTestFixtures()
+useClassycle()
diff --git a/subprojects/resources/src/main/java/org/gradle/internal/filestore/FileStore.java b/subprojects/resources/src/main/java/org/gradle/internal/filestore/FileStore.java
new file mode 100644
index 0000000..7d12d92
--- /dev/null
+++ b/subprojects/resources/src/main/java/org/gradle/internal/filestore/FileStore.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.filestore;
+
+import org.gradle.api.Action;
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+
+import java.io.File;
+
+public interface FileStore<K> {
+
+    LocallyAvailableResource move(K key, File source);
+
+    LocallyAvailableResource copy(K key, File source);
+
+    void moveFilestore(File destination);
+
+    LocallyAvailableResource add(K key, Action<File> addAction);
+}
diff --git a/subprojects/resources/src/main/java/org/gradle/internal/filestore/FileStoreSearcher.java b/subprojects/resources/src/main/java/org/gradle/internal/filestore/FileStoreSearcher.java
new file mode 100644
index 0000000..44bbfb9
--- /dev/null
+++ b/subprojects/resources/src/main/java/org/gradle/internal/filestore/FileStoreSearcher.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.filestore;
+
+import org.gradle.internal.resource.local.LocallyAvailableResource;
+
+import java.util.Set;
+
+public interface FileStoreSearcher<S> {
+
+    Set<? extends LocallyAvailableResource> search(S key);
+
+}
diff --git a/subprojects/resources/src/main/java/org/gradle/internal/resource/local/AbstractLocallyAvailableResource.java b/subprojects/resources/src/main/java/org/gradle/internal/resource/local/AbstractLocallyAvailableResource.java
new file mode 100644
index 0000000..05ac6ff
--- /dev/null
+++ b/subprojects/resources/src/main/java/org/gradle/internal/resource/local/AbstractLocallyAvailableResource.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.resource.local;
+
+import org.gradle.internal.hash.HashUtil;
+import org.gradle.internal.hash.HashValue;
+
+public abstract class AbstractLocallyAvailableResource implements LocallyAvailableResource {
+    // Calculated on demand
+    private HashValue sha1;
+    private Long contentLength;
+    private Long lastModified;
+
+    protected AbstractLocallyAvailableResource() {
+    }
+
+    protected AbstractLocallyAvailableResource(HashValue sha1) {
+        this.sha1 = sha1;
+    }
+
+    public HashValue getSha1() {
+        if (sha1 == null) {
+            this.sha1 = HashUtil.sha1(getFile());
+        }
+        return sha1;
+    }
+
+    public long getContentLength() {
+        if (contentLength == null) {
+            contentLength = getFile().length();
+        }
+        return contentLength;
+    }
+
+    public long getLastModified() {
+        if (lastModified == null) {
+            lastModified = getFile().lastModified();
+        }
+        return lastModified;
+    }
+
+}
diff --git a/subprojects/resources/src/main/java/org/gradle/internal/resource/local/DefaultLocallyAvailableResource.java b/subprojects/resources/src/main/java/org/gradle/internal/resource/local/DefaultLocallyAvailableResource.java
new file mode 100644
index 0000000..95e30a7
--- /dev/null
+++ b/subprojects/resources/src/main/java/org/gradle/internal/resource/local/DefaultLocallyAvailableResource.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.resource.local;
+
+import org.gradle.internal.hash.HashValue;
+
+import java.io.File;
+
+public class DefaultLocallyAvailableResource extends AbstractLocallyAvailableResource {
+    private final File origin;
+
+    public DefaultLocallyAvailableResource(File origin) {
+        this.origin = origin;
+    }
+
+    public DefaultLocallyAvailableResource(File origin, HashValue sha1) {
+        super(sha1);
+        this.origin = origin;
+    }
+
+    @Override
+    public String toString() {
+        return origin.toString();
+    }
+
+    public File getFile() {
+        return origin;
+    }
+}
diff --git a/subprojects/resources/src/main/java/org/gradle/internal/resource/local/LocallyAvailableResource.java b/subprojects/resources/src/main/java/org/gradle/internal/resource/local/LocallyAvailableResource.java
new file mode 100644
index 0000000..ec65568
--- /dev/null
+++ b/subprojects/resources/src/main/java/org/gradle/internal/resource/local/LocallyAvailableResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.resource.local;
+
+import org.gradle.internal.hash.HashValue;
+
+import java.io.File;
+
+public interface LocallyAvailableResource {
+
+    File getFile();
+
+    HashValue getSha1();
+
+    long getLastModified();
+
+    long getContentLength();
+}
diff --git a/subprojects/resources/src/test/groovy/org/gradle/internal/resource/local/DefaultLocallyAvailableResourceTest.groovy b/subprojects/resources/src/test/groovy/org/gradle/internal/resource/local/DefaultLocallyAvailableResourceTest.groovy
new file mode 100644
index 0000000..ce15136
--- /dev/null
+++ b/subprojects/resources/src/test/groovy/org/gradle/internal/resource/local/DefaultLocallyAvailableResourceTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.resource.local
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.internal.hash.HashUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+public class DefaultLocallyAvailableResourceTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    def "uses value from origin file"() {
+        given:
+        def origin = tmpDir.file("origin")
+        origin << "some text"
+
+        when:
+        def DefaultLocallyAvailableResource resource = new DefaultLocallyAvailableResource(origin)
+
+        then:
+        resource.sha1 == HashUtil.createHash(origin, 'SHA1')
+        resource.contentLength == origin.length()
+        resource.lastModified == origin.lastModified()
+    }
+
+    def "sha1 content length and last modified do not change when file is subsequently modified"() {
+        given:
+        def origin = tmpDir.file("origin")
+        origin << "some text"
+
+
+        when:
+        def DefaultLocallyAvailableResource resource = new DefaultLocallyAvailableResource(origin)
+        def originalSha1 = resource.sha1
+        def originalContentLength = resource.contentLength
+        def originalLastModified = resource.lastModified
+
+        and:
+        origin << "some more text"
+        origin.setLastModified(11)
+
+        then:
+        resource.sha1 != HashUtil.createHash(origin, 'SHA1')
+        resource.contentLength != origin.length()
+        resource.lastModified != origin.lastModified()
+
+        and:
+        resource.sha1 == originalSha1
+        resource.contentLength == originalContentLength
+        resource.lastModified == originalLastModified
+    }
+}
diff --git a/subprojects/scala/scala.gradle b/subprojects/scala/scala.gradle
index d06a73a..e7e32f3 100644
--- a/subprojects/scala/scala.gradle
+++ b/subprojects/scala/scala.gradle
@@ -17,25 +17,22 @@
 apply from: "$rootDir/gradle/providedConfiguration.gradle"
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(":core")
+    compile project(":languageJvm")
     compile project(":plugins")
 
     // keep in sync with ScalaBasePlugin code
-    provided("com.typesafe.zinc:zinc:0.2.1")
+    provided("com.typesafe.zinc:zinc:0.3.0")
 
     testCompile libraries.slf4j_api
+
+    integTestRuntime project(":ide")
 }
 
 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/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy
rename to subprojects/scala/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
rename to subprojects/scala/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
new file mode 100644
index 0000000..3f16fae
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.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.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+import static org.hamcrest.Matchers.containsString
+
+class SamplesMixedJavaAndScalaIntegrationTest extends AbstractIntegrationTest {
+
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/mixedJavaAndScala')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        def result = new DefaultTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
+
+        // Check contents of Jar
+        TestFile jarContents = file('jar')
+        projectDir.file("build/libs/mixedJavaAndScala-1.0.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/Person.class',
+                'org/gradle/sample/impl/JavaPerson.class',
+                'org/gradle/sample/impl/PersonImpl.class',
+                'org/gradle/sample/impl/PersonList.class'
+        )
+    }
+
+    @Test
+    public void canBuildDocs() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'scaladoc').run()
+
+        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
+        javadocsDir.file("index.html").assertIsFile()
+        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
+        javadocsDir.file("org/gradle/sample/Person.html").assertIsFile()
+        javadocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
+
+        TestFile scaladocsDir = projectDir.file("build/docs/scaladoc")
+        scaladocsDir.file("index.html").assertIsFile()
+        scaladocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
+        scaladocsDir.file("org/gradle/sample/impl/PersonImpl.html").assertIsFile()
+        scaladocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
+        scaladocsDir.file("org/gradle/sample/impl/PersonList.html").assertIsFile()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..0d9df1b
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesScalaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
+
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/customizedLayout')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        def result = new DefaultTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
+
+        // Check contents of Jar
+        TestFile jarContents = file('jar')
+        projectDir.file("build/libs/customizedLayout.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..e2189f7
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static org.hamcrest.Matchers.containsString
+
+class SamplesScalaQuickstartIntegrationTest extends AbstractIntegrationTest {
+
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/quickstart')
+
+    private TestFile projectDir
+
+    @Before
+    void setUp() {
+        projectDir = sample.dir
+    }
+
+    @Test
+    public void canBuildJar() {
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        def result = new DefaultTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
+
+        // Check contents of Jar
+        TestFile jarContents = file('jar')
+        projectDir.file("build/libs/quickstart.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+
+    @Test
+    public void canBuildScalaDoc() {
+        executer.inDirectory(projectDir).withTasks('clean', 'scaladoc').run()
+
+        projectDir.file('build/docs/scaladoc/index.html').assertExists()
+        projectDir.file('build/docs/scaladoc/org/gradle/sample/api/Person.html').assertContents(containsString("Defines the interface for a person."))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
rename to subprojects/scala/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy
index a770434..cfd9484 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy
@@ -17,8 +17,11 @@ package org.gradle.scala
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
+import static org.hamcrest.Matchers.startsWith
+
 class ScalaBasePluginIntegrationTest extends AbstractIntegrationSpec {
-    def "defaults scalaClasspath to inferred Scala compiler dependency if scalaTools configuration is empty"() {
+    def "defaults scalaClasspath to 'scalaTools' configuration if the latter is non-empty"() {
+        executer.withDeprecationChecksDisabled()
         file("build.gradle") << """
 apply plugin: "scala-base"
 
@@ -29,8 +32,38 @@ sourceSets {
 repositories {
     mavenCentral()
 }
+
 dependencies {
-    customCompile "org.scala-lang:scala-library:2.9.2"
+    scalaTools "org.scala-lang:scala-compiler:2.10.1"
+}
+
+task scaladoc(type: ScalaDoc)
+
+task verify << {
+    assert compileCustomScala.scalaClasspath.is(configurations.scalaTools)
+    assert scalaCustomConsole.classpath.is(configurations.scalaTools)
+    assert scaladoc.scalaClasspath.is(configurations.scalaTools)
+}
+"""
+
+        expect:
+        succeeds("verify")
+    }
+
+    def "defaults scalaClasspath to inferred Scala compiler dependency if 'scalaTools' configuration is empty"() {
+        file("build.gradle") << """
+apply plugin: "scala-base"
+
+sourceSets {
+    custom
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    customCompile "org.scala-lang:scala-library:2.10.1"
 }
 
 task scaladoc(type: ScalaDoc) {
@@ -38,9 +71,9 @@ task scaladoc(type: ScalaDoc) {
 }
 
 task verify << {
-    assert compileCustomScala.scalaClasspath.files.any { it.name == "scala-compiler-2.9.2.jar" }
-    assert scalaCustomConsole.classpath.files.any { it.name == "scala-compiler-2.9.2.jar" }
-    assert scaladoc.scalaClasspath.files.any { it.name == "scala-compiler-2.9.2.jar" }
+    assert compileCustomScala.scalaClasspath.files.any { it.name == "scala-compiler-2.10.1.jar" }
+    assert scalaCustomConsole.classpath.files.any { it.name == "scala-compiler-2.10.1.jar" }
+    assert scaladoc.scalaClasspath.files.any { it.name == "scala-compiler-2.10.1.jar" }
 }
 """
 
@@ -48,7 +81,7 @@ task verify << {
         succeeds("verify")
     }
 
-    def "defaults scalaClasspath to (empty) scalaTools configuration if Scala compiler dependency isn't found on class path"() {
+    def "only resolves source class path feeding into inferred Scala class path if/when the latter is actually used (but not during autowiring)"() {
         file("build.gradle") << """
 apply plugin: "scala-base"
 
@@ -60,16 +93,51 @@ repositories {
     mavenCentral()
 }
 
-task scaladoc(type: ScalaDoc)
+dependencies {
+    customCompile "org.scala-lang:scala-library:2.10.1"
+}
+
+task scaladoc(type: ScalaDoc) {
+    classpath = sourceSets.custom.runtimeClasspath
+}
 
 task verify << {
-    assert compileCustomScala.scalaClasspath.is(configurations.scalaTools)
-    assert scalaCustomConsole.classpath.is(configurations.scalaTools)
-    assert scaladoc.scalaClasspath.is(configurations.scalaTools)
+    assert configurations.customCompile.state.toString() == "UNRESOLVED"
+    assert configurations.customRuntime.state.toString() == "UNRESOLVED"
 }
-"""
+        """
 
         expect:
         succeeds("verify")
     }
+
+    def "not specifying a scala runtime produces decent error message"() {
+        given:
+        buildFile << """
+            apply plugin: "scala-base"
+
+            sourceSets {
+                main {}
+            }
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "com.google.guava:guava:11.0.2"
+            }
+        """
+
+        file("src/main/scala/Thing.scala") << """
+            class Thing
+        """
+
+        when:
+        fails "compileScala"
+
+        then:
+        failure.assertThatDescription(startsWith("Cannot infer Scala class path because no Scala library Jar was found."))
+    }
+
 }
\ No newline at end of file
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
index b1939fb..42c481e 100644
--- 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
@@ -21,7 +21,7 @@ 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"])
+ at TargetVersions(["2.8.2", "2.9.2", "2.10.0", "2.10.3"])
 class ZincScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
     @Rule TestResources testResources = new TestResources(temporaryFolder)
 
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 bcccb82..3b9ea6d 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,6 +26,7 @@ import spock.lang.Unroll
 class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
 
     @IgnoreIf({ AvailableJavaHomes.bestJre == null})
+    @Requires(TestPrecondition.JDK6_OR_LATER)
     @Unroll
     def "scala java cross compilation works in forking mode = #forkMode when JAVA_HOME is set to JRE"() {
         given:
@@ -51,8 +52,9 @@ class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
                         compile 'org.scala-lang:scala-library:2.9.2'
                     }
 
-                    compileScala{
-                        options.fork = ${forkMode}
+                    compileScala {
+                        scalaCompileOptions.useAnt = ${useAnt}
+                        scalaCompileOptions.fork = ${forkMode}
                     }
                     """
         when:
@@ -62,7 +64,11 @@ class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
         file("build/classes/main/org/test/ScalaClazz.class").exists()
 
         where:
-        forkMode << [false, true]
+        forkMode | useAnt
+//        false    | false
+        false    | true
+        true     | false
+        true     | true
     }
 
     @Requires(TestPrecondition.WINDOWS)
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
index 75f38d1..07b9f98 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.scala.test
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
@@ -57,7 +57,7 @@ class MultiLineSuite extends FunSuite {
         then:
         succeeds("test")
 
-        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(testDirectory)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("org.gradle.MultiLineSuite")
 	    result.testClass("org.gradle.MultiLineSuite").assertTestPassed("This test method name\nspans many\nlines")
     }
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 3363d40..d624935 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
@@ -101,7 +101,7 @@ class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
         if (target <= VersionNumber.parse("1.5")) { return "jvm-${target.major}.${target.minor}" }
 
         def scalaVersion = sniffScalaVersion(spec.scalaClasspath)
-        if (scalaVersion >= VersionNumber.parse("2.10.0-M5") || scalaVersion == VersionNumber.parse("2.10.0")) {
+        if (scalaVersion >= VersionNumber.parse("2.10.0-M5")) {
             return "jvm-${target.major}.${target.minor}"
         }
 
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/CleaningScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/CleaningScalaCompiler.java
new file mode 100644
index 0000000..5299a59
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/CleaningScalaCompiler.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.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.language.jvm.internal.SimpleStaleClassCleaner;
+import org.gradle.language.jvm.internal.StaleClassCleaner;
+
+public class CleaningScalaCompiler extends CleaningJavaCompilerSupport<ScalaJavaJointCompileSpec>
+        implements Compiler<ScalaJavaJointCompileSpec> {
+    private final Compiler<ScalaJavaJointCompileSpec> compiler;
+    private final TaskOutputsInternal taskOutputs;
+
+    public CleaningScalaCompiler(Compiler<ScalaJavaJointCompileSpec> compiler, TaskOutputsInternal taskOutputs) {
+        this.compiler = compiler;
+        this.taskOutputs = taskOutputs;
+    }
+
+    @Override
+    protected Compiler<ScalaJavaJointCompileSpec> getCompiler() {
+        return compiler;
+    }
+
+    @Override
+    protected StaleClassCleaner createCleaner(ScalaJavaJointCompileSpec spec) {
+        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/DaemonScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DaemonScalaCompiler.java
index 00a35bf..ba97417 100644
--- 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
@@ -59,7 +59,7 @@ public class DaemonScalaCompiler implements org.gradle.api.internal.tasks.compil
 
     public WorkResult execute(ScalaJavaJointCompileSpec spec) {
         DaemonForkOptions daemonForkOptions = createDaemonForkOptions(spec);
-        CompilerDaemon daemon = daemonFactory.getDaemon(project, daemonForkOptions);
+        CompilerDaemon daemon = daemonFactory.getDaemon(project.getRootProject().getProjectDir(), daemonForkOptions);
         CompileResult result = daemon.execute(delegate, spec);
         if (result.isSuccess()) {
             return result;
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
deleted file mode 100644
index 43fb239..0000000
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.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.internal.tasks.scala;
-
-import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.internal.tasks.compile.*;
-import org.gradle.api.internal.tasks.compile.Compiler;
-
-public class IncrementalScalaCompiler extends IncrementalJavaCompilerSupport<ScalaJavaJointCompileSpec>
-        implements Compiler<ScalaJavaJointCompileSpec> {
-    private final Compiler<ScalaJavaJointCompileSpec> compiler;
-    private final TaskOutputsInternal taskOutputs;
-
-    public IncrementalScalaCompiler(Compiler<ScalaJavaJointCompileSpec> compiler, TaskOutputsInternal taskOutputs) {
-        this.compiler = compiler;
-        this.taskOutputs = taskOutputs;
-    }
-
-    @Override
-    protected Compiler<ScalaJavaJointCompileSpec> getCompiler() {
-        return compiler;
-    }
-
-    @Override
-    protected StaleClassCleaner createCleaner(ScalaJavaJointCompileSpec spec) {
-        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
index baf4054..acd789b 100644
--- 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
@@ -19,8 +19,10 @@ 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.SimpleWorkResult;
+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.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.tasks.WorkResult;
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
index 3dbbef6..6966569 100644
--- 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
@@ -23,7 +23,6 @@ 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;
@@ -33,11 +32,13 @@ public class ScalaCompilerFactory {
     private final ProjectInternal project;
     private final IsolatedAntBuilder antBuilder;
     private final Factory<AntBuilder> antBuilderFactory;
+    private final CompilerDaemonManager compilerDaemonManager;
 
-    public ScalaCompilerFactory(ProjectInternal project, IsolatedAntBuilder antBuilder, Factory<AntBuilder> antBuilderFactory) {
+    public ScalaCompilerFactory(ProjectInternal project, IsolatedAntBuilder antBuilder, Factory<AntBuilder> antBuilderFactory, CompilerDaemonManager compilerDaemonManager) {
         this.project = project;
         this.antBuilder = antBuilder;
         this.antBuilderFactory = antBuilderFactory;
+        this.compilerDaemonManager = compilerDaemonManager;
     }
 
     public org.gradle.api.internal.tasks.compile.Compiler<ScalaJavaJointCompileSpec> create(ScalaCompileOptions scalaOptions, CompileOptions javaOptions) {
@@ -61,8 +62,7 @@ public class ScalaCompilerFactory {
             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);
+        scalaCompiler = new DaemonScalaCompiler(project, scalaCompiler, compilerDaemonManager);
         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
index 0d1b980..b7fc85e 100644
--- 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
@@ -18,23 +18,20 @@ 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 com.typesafe.zinc.*;
 import org.gradle.api.GradleException;
 import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
 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 scala.Option;
 import xsbti.F0;
 
 import java.io.File;
@@ -63,7 +60,7 @@ public class ZincScalaCompiler implements Compiler<ScalaJavaJointCompileSpec>, S
             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", true);
+                    scalacOptions, javacOptions, spec.getScalaCompileOptions().getIncrementalOptions().getAnalysisFile(), spec.getAnalysisMap(), "mixed", getIncOptions(), true);
             if (LOGGER.isDebugEnabled()) {
                 Inputs.debug(inputs, logger);
             }
@@ -77,10 +74,25 @@ public class ZincScalaCompiler implements Compiler<ScalaJavaJointCompileSpec>, S
             return new SimpleWorkResult(true);
         }
 
+        private static IncOptions getIncOptions() {
+            //The values are based on what I have found in sbt-compiler-maven-plugin.googlecode.com and zinc documentation
+            //Hard to say what effect they have on the incremental build
+            int transitiveStep = 3;
+            double recompileAllFraction = 0.5d;
+            boolean relationsDebug = false;
+            boolean apiDebug = false;
+            int apiDiffContextSize = 5;
+            Option<File> apiDumpDirectory = Option.empty();
+            boolean transactional = false;
+            Option<File> backup = Option.empty();
+
+            return new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, apiDumpDirectory, transactional, backup);
+        }
+
         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());
+            Setup setup = Setup.create(scalaLocation, sbtJars, Jvm.current().getJavaHome(), true);
             if (LOGGER.isDebugEnabled()) {
                 Setup.debug(setup, logger);
             }
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 cf25d89..a69ed13 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,130 +15,86 @@
  */
 package org.gradle.api.plugins.scala
 
-import org.gradle.api.Incubating
-import org.gradle.api.Nullable;
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.file.FileCollection
+import org.gradle.api.artifacts.Configuration
 import org.gradle.api.file.FileTreeElement
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.tasks.DefaultScalaSourceSet
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPluginConvention
 import org.gradle.api.reporting.ReportingExtension
+import org.gradle.api.tasks.ScalaRuntime
 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
+import org.gradle.util.DeprecationLogger
 
 import javax.inject.Inject
-import java.util.regex.Pattern
 
 class ScalaBasePlugin implements Plugin<Project> {
-    // public configurations
+    /**
+     * The name of the configuration holding the Scala compiler and tools.
+     *
+     * @deprecated Typically, usages of {@code scalaTools} can simply be removed,
+     * and the Scala tools libraries to be used will be inferred from the Scala
+     * library found on the regular (compile) class path. In some cases, it may
+     * be necessary to additionally configure the {@code scalaClasspath} property
+     * of {@code ScalaCompile} and {@code ScalaDoc} tasks.
+     */
     static final String SCALA_TOOLS_CONFIGURATION_NAME = "scalaTools"
+
     static final String ZINC_CONFIGURATION_NAME = "zinc"
 
-    private static final String DEFAULT_ZINC_VERSION = "0.2.1"
-    private static final Pattern SCALA_JAR_PATTERN = Pattern.compile("scala-(\\w.*?)-(\\d.*).jar")
+    static final String SCALA_RUNTIME_EXTENSION_NAME = "scalaRuntime"
+
+    private static final String DEFAULT_ZINC_VERSION = "0.3.0"
 
-    private Project project
     private final FileResolver fileResolver
 
+    private Project project
+    private ScalaRuntime scalaRuntime
+
     @Inject
     ScalaBasePlugin(FileResolver fileResolver) {
         this.fileResolver = fileResolver
     }
 
-    /**
-     * Infers a Scala compiler class path (containing a 'scala-compiler' Jar and its dependencies)
-     * based on the 'scala-library' Jar found on the specified class path.
-     *
-     * <p>Falls back to returning the 'scalaTools' configuration if one of the following holds:
-     *
-     * <ol>
-     *     <li>The 'scalaTools' configuration is explicitly configured (ie. has dependencies declared).
-     *         This is important for backwards compatibility.</li>
-     *     <li>No repository is declared for the project.</li>
-     *     <li>A 'scala-library' Jar cannot be found on the specified class path, or its
-     *         version cannot be determined.</li>
-     * </ol>
-     *
-     * Note that the returned class path may be empty, or may fail to resolve when asked for its contents.
-     * If this happens at task execution time, it should usually be treated as a configuration error on part of the user.
-     *
-     * @param classpath a class path (supposedly) containing a 'scala-library' Jar
-     * @return a Scala compiler class path
-     */
-    @Incubating
-    FileCollection inferScalaCompilerClasspath(Iterable<File> classpath) {
-        def scalaTools = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
-        if (!scalaTools.dependencies.empty || project.repositories.empty) { return scalaTools }
-
-        def scalaLibraryJar = findScalaJar(classpath, "library")
-        if (scalaLibraryJar == null) { return scalaTools }
-
-        def scalaVersion = getScalaVersion(scalaLibraryJar)
-        if (scalaVersion == null) {
-            throw new AssertionError("Unexpectedly failed to determine version of Scala Jar file: $scalaLibraryJar")
-        }
-
-        return project.configurations.detachedConfiguration(
-                new DefaultExternalModuleDependency("org.scala-lang", "scala-compiler", scalaVersion))
-    }
-
-    /**
-     * Searches the specified class path for a Scala Jar file matching the specified
-     * module (compiler, library, jdbc, etc.).
-     *
-     * @param classpath the class path to search
-     * @param module the module to search for
-     * @return a matching Scala Jar file, or {@code null} if no match was found
-     */
-    @Nullable
-    @Incubating
-    File findScalaJar(Iterable<File> classpath, String module) {
-        for (file in classpath) {
-            def matcher = SCALA_JAR_PATTERN.matcher(file.name)
-            if (matcher.matches() && matcher.group(1) == module) {
-                return file
-            }
-        }
-        return null
-    }
-
-    /**
-     * Determines the version of a Scala Jar file (scala-compiler, scala-library, scala-jdbc, etc.).
-     * If the version cannot be determined, {@code null} is returned.
-     *
-     * <p>Implementation note: The version is determined by parsing the file name, which
-     * is expected to match the pattern 'scala-[component]-[version].jar'.
-     *
-     * @param scalaJar a Scala Jar file
-     * @return the version of the Jar file
-     */
-    @Nullable
-    @Incubating
-    String getScalaVersion(File scalaJar) {
-        def matcher = SCALA_JAR_PATTERN.matcher(scalaJar.name)
-        matcher.matches() ? matcher.group(2) : null
-    }
-
     void apply(Project project) {
         this.project = project
         def javaPlugin = project.plugins.apply(JavaBasePlugin.class)
 
-        project.configurations.add(SCALA_TOOLS_CONFIGURATION_NAME)
+        configureConfigurations(project)
+        configureScalaRuntimeExtension()
+        configureCompileDefaults()
+        configureSourceSetDefaults(javaPlugin)
+        configureScaladoc()
+    }
+
+    private void configureConfigurations(Project project) {
+        def scalaToolsConfiguration = project.configurations.create(SCALA_TOOLS_CONFIGURATION_NAME)
                 .setVisible(false)
-                .setDescription("The Scala tools libraries to be used for this Scala project.")
-        project.configurations.add(ZINC_CONFIGURATION_NAME)
+                .setDescription("The Scala tools libraries to be used for this Scala project. (Deprecated)")
+        deprecateScalaToolsConfiguration(scalaToolsConfiguration)
+
+        project.configurations.create(ZINC_CONFIGURATION_NAME)
                 .setVisible(false)
                 .setDescription("The Zinc incremental compiler to be used for this Scala project.")
+    }
 
-        configureCompileDefaults()
-        configureSourceSetDefaults(javaPlugin)
-        configureScaladoc()
+    private void deprecateScalaToolsConfiguration(Configuration scalaConfiguration) {
+        scalaConfiguration.dependencies.whenObjectAdded {
+            DeprecationLogger.nagUserOfDiscontinuedConfiguration(SCALA_TOOLS_CONFIGURATION_NAME,
+                    "Typically, usages of 'scalaTools' can simply be removed, and the Scala tools libraries " +
+                    "to be used will be inferred from the Scala library found on the regular (compile) class path. " +
+                    "In some cases, it may be necessary to additionally configure the 'scalaClasspath' property of " +
+                    "ScalaCompile and ScalaDoc tasks.");
+        }
+    }
+
+    private void configureScalaRuntimeExtension() {
+        scalaRuntime = project.extensions.create(SCALA_RUNTIME_EXTENSION_NAME, ScalaRuntime, project)
     }
 
     private void configureSourceSetDefaults(JavaBasePlugin javaPlugin) {
@@ -156,7 +112,7 @@ class ScalaBasePlugin implements Plugin<Project> {
 
     private void configureScalaCompile(JavaBasePlugin javaPlugin, SourceSet sourceSet) {
         def taskName = sourceSet.getCompileTaskName('scala')
-        def scalaCompile = project.tasks.add(taskName, ScalaCompile)
+        def scalaCompile = project.tasks.create(taskName, ScalaCompile)
         scalaCompile.dependsOn sourceSet.compileJavaTaskName
         javaPlugin.configureForSourceSet(sourceSet, scalaCompile)
         scalaCompile.description = "Compiles the $sourceSet.scala."
@@ -180,11 +136,11 @@ class ScalaBasePlugin implements Plugin<Project> {
 
     private void configureScalaConsole(SourceSet sourceSet) {
         def taskName = sourceSet.getTaskName("scala", "Console")
-        def scalaConsole = project.tasks.add(taskName, JavaExec)
+        def scalaConsole = project.tasks.create(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.conventionMapping.classpath = { inferScalaCompilerClasspath(sourceSet.runtimeClasspath) }
+        scalaConsole.conventionMapping.classpath = { scalaRuntime.inferScalaClasspath(sourceSet.runtimeClasspath) }
         scalaConsole.systemProperty("scala.usejavacp", true)
         scalaConsole.standardInput = System.in
         scalaConsole.conventionMapping.jvmArgs = { ["-classpath", sourceSet.runtimeClasspath.asPath] }
@@ -192,7 +148,7 @@ class ScalaBasePlugin implements Plugin<Project> {
 
     private void configureCompileDefaults() {
         project.tasks.withType(ScalaCompile.class) { ScalaCompile compile ->
-            compile.conventionMapping.scalaClasspath = { inferScalaCompilerClasspath(compile.classpath) }
+            compile.conventionMapping.scalaClasspath = { scalaRuntime.inferScalaClasspath(compile.classpath) }
             compile.conventionMapping.zincClasspath = {
                 def config = project.configurations[ZINC_CONFIGURATION_NAME]
                 if (!compile.scalaCompileOptions.useAnt && config.dependencies.empty) {
@@ -209,7 +165,7 @@ class ScalaBasePlugin implements Plugin<Project> {
         project.tasks.withType(ScalaDoc) { ScalaDoc scalaDoc ->
             scalaDoc.conventionMapping.destinationDir = { project.file("$project.docsDir/scaladoc") }
             scalaDoc.conventionMapping.title = { project.extensions.getByType(ReportingExtension).apiDocTitle }
-            scalaDoc.conventionMapping.scalaClasspath = { inferScalaCompilerClasspath(scalaDoc.classpath) }
+            scalaDoc.conventionMapping.scalaClasspath = { scalaRuntime.inferScalaClasspath(scalaDoc.classpath) }
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
index 0aa1beb..56fda28 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.plugins.scala;
 
-
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.plugins.JavaPlugin
@@ -38,8 +37,8 @@ public class ScalaPlugin implements Plugin<Project> {
             scalaDoc.conventionMapping.classpath = { project.sourceSets.main.output + project.sourceSets.main.compileClasspath }
             scalaDoc.source = project.sourceSets.main.scala
         }
-        ScalaDoc scalaDoc = project.tasks.add(SCALA_DOC_TASK_NAME, ScalaDoc.class)
-        scalaDoc.description = "Generates scaladoc for the main source code.";
+        ScalaDoc scalaDoc = project.tasks.create(SCALA_DOC_TASK_NAME, ScalaDoc.class)
+        scalaDoc.description = "Generates Scaladoc for the main source code.";
         scalaDoc.group = JavaBasePlugin.DOCUMENTATION_GROUP
     }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.groovy
new file mode 100644
index 0000000..ad3b144
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.groovy
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks
+
+import org.gradle.api.GradleException
+import org.gradle.api.Incubating
+import org.gradle.api.Nullable
+import org.gradle.api.Project
+import org.gradle.api.Buildable
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection
+import org.gradle.api.plugins.scala.ScalaBasePlugin
+
+import java.util.regex.Pattern
+
+/**
+ * Provides information related to the Scala runtime(s) used in a project. Added by the
+ * {@link ScalaBasePlugin} as a project extension named {@code scalaRuntime}.
+ *
+ * <p>Example usage:
+ *
+ * <pre autoTested="">
+ *     apply plugin: "scala"
+ *
+ *     repositories {
+ *         mavenCentral()
+ *     }
+ *
+ *     dependencies {
+ *         compile "org.scala-lang:scala-library:2.10.1"
+ *     }
+ *
+ *     def scalaClasspath = scalaRuntime.inferScalaClasspath(configurations.compile)
+ *     // The returned class path can be used to configure the 'scalaClasspath' property of tasks
+ *     // such as 'ScalaCompile' or 'ScalaDoc', or to execute these and other Scala tools directly.
+ * </pre>
+ */
+ at Incubating
+class ScalaRuntime {
+    private static final Pattern SCALA_JAR_PATTERN = Pattern.compile("scala-(\\w.*?)-(\\d.*).jar")
+
+    // should be private but Groovy can't handle this
+    protected final Project project
+
+    ScalaRuntime(Project project) {
+        this.project = project
+    }
+
+    /**
+     * Searches the specified class path for a 'scala-library' Jar, and returns a class path
+     * containing a corresponding (same version) 'scala-compiler' Jar and its dependencies.
+     *
+     * <p>If the (deprecated) 'scalaTools' configuration is explicitly configured, no repository
+     * is declared for the project, no 'scala-library' Jar is found on the specified class path,
+     * or its version cannot be determined, a class path with the contents of the 'scalaTools'
+     * configuration is returned.
+     *
+     * <p>The returned class path may be empty, or may fail to resolve when asked for its contents.
+     *
+     * @param classpath a class path containing a 'scala-library' Jar
+     * @return a class path containing a corresponding 'scala-compiler' Jar and its dependencies
+     */
+    FileCollection inferScalaClasspath(Iterable<File> classpath) {
+        def scalaTools = project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]
+        if (!scalaTools.dependencies.empty) {
+            return scalaTools
+        }
+
+        // alternatively, we could return project.files(Runnable)
+        // would differ in the following ways: 1. live (not sure if we want live here) 2. no autowiring (probably want autowiring here)
+        return new LazilyInitializedFileCollection() {
+            @Override
+            FileCollection createDelegate() {
+                if (project.repositories.empty) {
+                    throw new GradleException("Cannot infer Scala class path because no repository is declared in $project")
+                }
+
+                def scalaLibraryJar = findScalaJar(classpath, "library")
+                if (scalaLibraryJar == null) {
+                    throw new GradleException("Cannot infer Scala class path because no Scala library Jar was found. "
+                            + "Does $project declare dependency to scala-library? Searched classpath: $classpath.")
+                }
+
+                def scalaVersion = getScalaVersion(scalaLibraryJar)
+                if (scalaVersion == null) {
+                    throw new AssertionError("Unexpectedly failed to parse version of Scala Jar file: $scalaLibraryJar in $project")
+                }
+
+                return project.configurations.detachedConfiguration(
+                        new DefaultExternalModuleDependency("org.scala-lang", "scala-compiler", scalaVersion))
+            }
+
+            // let's override this so that delegate isn't created at autowiring time (which would mean on every build)
+            @Override
+            TaskDependency getBuildDependencies() {
+                if (classpath instanceof Buildable) {
+                    return classpath.buildDependencies
+                }
+                return new TaskDependency() {
+                    Set<? extends Task> getDependencies(Task task) {
+                        Collections.emptySet()
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Searches the specified class path for a Scala Jar file (scala-compiler, scala-library,
+     * scala-jdbc, etc.) with the specified appendix (compiler, library, jdbc, etc.).
+     * If no such file is found, {@code null} is returned.
+     *
+     * @param classpath the class path to search
+     * @param appendix the appendix to search for
+     * @return a Scala Jar file with the specified appendix
+     */
+    @Nullable
+    File findScalaJar(Iterable<File> classpath, String appendix) {
+        for (file in classpath) {
+            def matcher = SCALA_JAR_PATTERN.matcher(file.name)
+            if (matcher.matches() && matcher.group(1) == appendix) {
+                return file
+            }
+        }
+        return null
+    }
+
+    /**
+     * Determines the version of a Scala Jar file (scala-compiler, scala-library,
+     * scala-jdbc, etc.). If the version cannot be determined, or the file is not a Scala
+     * Jar file, {@code null} is returned.
+     *
+     * <p>Implementation note: The version is determined by parsing the file name, which
+     * is expected to match the pattern 'scala-[component]-[version].jar'.
+     *
+     * @param scalaJar a Scala Jar file
+     * @return the version of the Scala Jar file
+     */
+    @Nullable
+    String getScalaVersion(File scalaJar) {
+        def matcher = SCALA_JAR_PATTERN.matcher(scalaJar.name)
+        matcher.matches() ? matcher.group(2) : null
+    }
+}
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 3e36208..9afceca 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
@@ -18,7 +18,6 @@ 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.InvalidUserDataException;
 import org.gradle.api.Project;
@@ -26,12 +25,14 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.project.IsolatedAntBuilder;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
 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.TaskAction;
 import org.gradle.api.tasks.compile.AbstractCompile;
 import org.gradle.api.tasks.compile.CompileOptions;
 import org.gradle.internal.Factory;
@@ -60,9 +61,10 @@ public class ScalaCompile extends AbstractCompile {
         ProjectInternal projectInternal = (ProjectInternal) getProject();
         IsolatedAntBuilder antBuilder = getServices().get(IsolatedAntBuilder.class);
         Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
-        ScalaCompilerFactory scalaCompilerFactory = new ScalaCompilerFactory(projectInternal, antBuilder, antBuilderFactory);
+        CompilerDaemonManager compilerDaemonManager = getServices().get(CompilerDaemonManager.class);
+        ScalaCompilerFactory scalaCompilerFactory = new ScalaCompilerFactory(projectInternal, antBuilder, antBuilderFactory, compilerDaemonManager);
         Compiler<ScalaJavaJointCompileSpec> delegatingCompiler = new DelegatingScalaCompiler(scalaCompilerFactory);
-        compiler = new IncrementalScalaCompiler(delegatingCompiler, getOutputs());
+        compiler = new CleaningScalaCompiler(delegatingCompiler, getOutputs());
     }
 
     /**
@@ -113,7 +115,7 @@ public class ScalaCompile extends AbstractCompile {
         this.compiler = compiler;
     }
 
-    @Override
+    @TaskAction
     protected void compile() {
         checkScalaClasspathIsNonEmpty();
         DefaultScalaJavaJointCompileSpec spec = new DefaultScalaJavaJointCompileSpec();
@@ -135,7 +137,8 @@ public class ScalaCompile extends AbstractCompile {
 
     private void checkScalaClasspathIsNonEmpty() {
         if (getScalaClasspath().isEmpty()) {
-            throw new InvalidUserDataException("'" + getName() + ".scalaClasspath' must not be empty");
+            throw new InvalidUserDataException("'" + getName() + ".scalaClasspath' must not be empty. If a Scala compile dependency is provided, "
+                    + "the 'scala-base' plugin will attempt to configure 'scalaClasspath' automatically. Alternatively, you may configure 'scalaClasspath' explicitly.");
         }
     }
 
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 0cfb847..36bc97c 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
@@ -23,11 +23,11 @@ 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.gradle.util.TestUtil
+import org.junit.Before
 import org.junit.Test
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.Matchers.isEmpty
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
@@ -35,45 +35,25 @@ import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
 public class ScalaBasePluginTest {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
 
-    @Test void appliesTheJavaPluginToTheProject() {
+    @Before
+    void before() {
         project.plugins.apply(ScalaBasePlugin)
+    }
+
+    @Test void appliesTheJavaPluginToTheProject() {
         assertTrue(project.getPlugins().hasPlugin(JavaBasePlugin))
     }
 
     @Test void addsScalaToolsConfigurationToTheProject() {
-        project.plugins.apply(ScalaBasePlugin)
         def configuration = project.configurations.getByName(ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME)
         assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
         assertFalse(configuration.visible)
         assertTrue(configuration.transitive)
     }
 
-    @Test void defaultsScalaClasspathToScalaToolsConfigurationIfTheLatterIsNonEmpty() {
-        project.plugins.apply(ScalaBasePlugin)
-        project.sourceSets.add('custom')
-        def configuration = project.configurations.scalaTools
-        project.dependencies {
-            scalaTools "org.scala-lang:scala-compiler:2.10"
-        }
-
-        def compileTask = project.tasks.compileCustomScala
-        assertSame(configuration, compileTask.scalaClasspath)
-
-        def consoleTask = project.tasks.scalaCustomConsole
-        assertSame(configuration, consoleTask.classpath)
-
-        def scaladocTask = project.task("scaladoc", type: ScalaDoc)
-        assertSame(configuration, scaladocTask.scalaClasspath)
-    }
-
-    // see ScalaBasePluginIntegrationTest
-    @Test void defaultsScalaClasspathToInferredScalaCompilerDependencyIfScalaToolsConfigurationIsEmpty() {
-    }
-
     @Test void addsZincConfigurationToTheProject() {
-        project.plugins.apply(ScalaBasePlugin)
         def configuration = project.configurations.getByName(ScalaBasePlugin.ZINC_CONFIGURATION_NAME)
         assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
         assertFalse(configuration.visible)
@@ -81,8 +61,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void preconfiguresZincClasspathForCompileTasksThatUseZinc() {
-        project.plugins.apply(ScalaBasePlugin)
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         def task = project.tasks.compileCustomScala
         task.scalaCompileOptions.useAnt = false
         assert task.zincClasspath instanceof Configuration
@@ -90,8 +69,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void doesNotPreconfigureZincClasspathForCompileTasksThatUseAnt() {
-        project.plugins.apply(ScalaBasePlugin)
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         def task = project.tasks.compileCustomScala
         task.scalaCompileOptions.useAnt = true
         assert task.zincClasspath instanceof Configuration
@@ -99,31 +77,24 @@ public class ScalaBasePluginTest {
     }
 
     @Test void addsScalaConventionToNewSourceSet() {
-        project.plugins.apply(ScalaBasePlugin)
-
-        def sourceSet = project.sourceSets.add('custom')
+        def sourceSet = project.sourceSets.create('custom')
         assertThat(sourceSet.scala.displayName, equalTo("custom Scala source"))
         assertThat(sourceSet.scala.srcDirs, equalTo(toLinkedSet(project.file("src/custom/scala"))))
     }
 
     @Test void addsCompileTaskForNewSourceSet() {
-        project.plugins.apply(ScalaBasePlugin)
-
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         def task = project.tasks['compileCustomScala']
         assertThat(task, instanceOf(ScalaCompile.class))
         assertThat(task.description, equalTo('Compiles the custom Scala source.'))
         assertThat(task.classpath, equalTo(project.sourceSets.custom.compileClasspath))
-        assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
         assertThat(task.source as List, equalTo(project.sourceSets.custom.scala as List))
         assertThat(task, dependsOn('compileCustomJava'))
     }
 
     @Test void preconfiguresIncrementalCompileOptions() {
-        project.plugins.apply(ScalaBasePlugin)
-
-        project.sourceSets.add('custom')
-        project.tasks.add('customJar', Jar)
+        project.sourceSets.create('custom')
+        project.tasks.create('customJar', Jar)
         ScalaCompile task = project.tasks['compileCustomScala']
         project.gradle.buildListenerBroadcaster.projectsEvaluated(project.gradle)
 
@@ -132,10 +103,8 @@ public class ScalaBasePluginTest {
     }
 
     @Test void incrementalCompileOptionsCanBeOverridden() {
-        project.plugins.apply(ScalaBasePlugin)
-
-        project.sourceSets.add('custom')
-        project.tasks.add('customJar', Jar)
+        project.sourceSets.create('custom')
+        project.tasks.create('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")
@@ -146,96 +115,21 @@ public class ScalaBasePluginTest {
     }
     
     @Test void dependenciesOfJavaPluginTasksIncludeScalaCompileTasks() {
-        project.plugins.apply(ScalaBasePlugin)
-
-        project.sourceSets.add('custom')
+        project.sourceSets.create('custom')
         def task = project.tasks['customClasses']
         assertThat(task, dependsOn(hasItem('compileCustomScala')))
     }
 
     @Test void configuresCompileTasksDefinedByTheBuildScript() {
-        project.plugins.apply(ScalaBasePlugin)
-
         def task = project.task('otherCompile', type: ScalaCompile)
         assertThat(task.source, isEmpty())
-        assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
         assertThat(task, dependsOn())
     }
 
     @Test void configuresScalaDocTasksDefinedByTheBuildScript() {
-        project.plugins.apply(ScalaBasePlugin)
-
         def task = project.task('otherScaladoc', type: ScalaDoc)
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
         assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
-        assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
         assertThat(task, dependsOn())
     }
-
-    @Test void allowsToInferScalaCompilerClasspath() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        project.repositories {
-            mavenCentral()
-        }
-
-        def classpath = plugin.inferScalaCompilerClasspath([new File("other.jar"), new File("scala-library-2.10.0.jar")])
-
-        assert classpath instanceof Configuration
-        assert classpath.state == Configuration.State.UNRESOLVED
-        assert classpath.dependencies.size() == 1
-        classpath.dependencies.iterator().next().with {
-            assert group == "org.scala-lang"
-            assert name == "scala-compiler"
-            assert version == "2.10.0"
-        }
-    }
-
-    @Test void inferenceFallsBackToScalaToolsIfNoRepositoryDeclared() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        def classpath = plugin.inferScalaCompilerClasspath([new File("other.jar"), new File("scala-library-2.10.0.jar")])
-
-        assert classpath == project.configurations.scalaTools
-    }
-
-    @Test void inferenceFallsBackToScalaToolsIfScalaLibraryNotFoundOnClassPath() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        def classpath = plugin.inferScalaCompilerClasspath([new File("other.jar"), new File("other2.jar")])
-
-        assert classpath == project.configurations.scalaTools
-    }
-
-    @Test void allowsToFindScalaJarInClassPath() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        def file = plugin.findScalaJar([new File("other.jar"), new File("scala-jdbc-1.5.jar"), new File("scala-compiler-1.7.jar")], "jdbc")
-
-        assert file.name == "scala-jdbc-1.5.jar"
-    }
-
-    @Test void returnsNullIfScalaJarNotFound() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        def file = plugin.findScalaJar([new File("other.jar"), new File("scala-jdbc-1.5.jar"), new File("scala-compiler-1.7.jar")], "library")
-
-        assert file == null
-    }
-
-    @Test void allowsToDetermineVersionOfScalaJar() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        assert plugin.getScalaVersion(new File("scala-compiler-2.9.2.jar")) == "2.9.2"
-        assert plugin.getScalaVersion(new File("scala-jdbc-2.9.2.jar")) == "2.9.2"
-        assert plugin.getScalaVersion(new File("scala-library-2.10.0-SNAPSHOT.jar")) == "2.10.0-SNAPSHOT"
-        assert plugin.getScalaVersion(new File("scala-library-2.10.0-rc-3.jar")) == "2.10.0-rc-3"
-    }
-
-    @Test void returnsNullIfScalaVersionCannotBeDetermined() {
-        def plugin = project.plugins.apply(ScalaBasePlugin)
-
-        assert plugin.getScalaVersion(new File("scala-compiler.jar")) == null
-        assert plugin.getScalaVersion(new File("groovy-compiler-2.1.0.jar")) == null
-    }
 }
\ No newline at end of file
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
index 8c21a59..8248d11 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
@@ -16,22 +16,22 @@
 package org.gradle.api.plugins.scala
 
 import org.gradle.api.Project
+import org.gradle.api.file.FileCollectionMatchers
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.scala.ScalaCompile
 import org.gradle.api.tasks.scala.ScalaDoc
-import org.gradle.util.HelperUtil
-import org.gradle.util.Matchers
+import org.gradle.util.TestUtil
 import org.junit.Test
 
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.api.tasks.TaskDependencyMatchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 import static org.junit.Assert.assertTrue
 
 class ScalaPluginTest {
-    private final Project project = HelperUtil.createRootProject()
+    private final Project project = TestUtil.createRootProject()
     private final ScalaPlugin scalaPlugin = new ScalaPlugin()
 
     @Test void appliesTheJavaPluginToTheProject() {
@@ -87,7 +87,7 @@ class ScalaPluginTest {
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
         assertThat(task.source as List, equalTo(project.sourceSets.main.scala as List))
-        assertThat(task.classpath, Matchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
+        assertThat(task.classpath, FileCollectionMatchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
         assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
 
@@ -96,6 +96,6 @@ class ScalaPluginTest {
 
         def task = project.task('otherScaladoc', type: ScalaDoc)
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
-        assertThat(task.classpath, Matchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
+        assertThat(task.classpath, FileCollectionMatchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
     }
 }
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/ScalaRuntimeTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/ScalaRuntimeTest.groovy
new file mode 100644
index 0000000..9620fb1
--- /dev/null
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/ScalaRuntimeTest.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks
+
+import org.gradle.api.GradleException
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection
+import org.gradle.api.plugins.scala.ScalaBasePlugin
+import org.gradle.util.TestUtil
+
+import spock.lang.Specification
+
+class ScalaRuntimeTest extends Specification {
+    def project = TestUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(ScalaBasePlugin)
+    }
+
+    def "inferred Scala class path contains 'scala-compiler' repository dependency matching 'scala-library' Jar found on class path"() {
+        project.repositories {
+            mavenCentral()
+        }
+
+        when:
+        def classpath = project.scalaRuntime.inferScalaClasspath([new File("other.jar"), new File("scala-library-2.10.1.jar")])
+
+        then:
+        classpath instanceof LazilyInitializedFileCollection
+        with(classpath.delegate) {
+            it instanceof Configuration
+            it.state == Configuration.State.UNRESOLVED
+            it.dependencies.size() == 1
+            with(it.dependencies.iterator().next()) {
+                group == "org.scala-lang"
+                name == "scala-compiler"
+                version == "2.10.1"
+            }
+        }
+    }
+
+    def "inferred Scala class path falls back to 'scalaTools' configuration if the latter is non-empty"() {
+        project.dependencies {
+            scalaTools "org.scala-lang:scala-compiler:2.10.1"
+        }
+
+        when:
+        def classpath = project.scalaRuntime.inferScalaClasspath([new File("other.jar"), new File("scala-library-2.10.1.jar")])
+
+        then:
+        classpath == project.configurations.scalaTools
+    }
+
+    def "inference fails if 'scalaTools' configuration is empty and no repository declared"() {
+        when:
+        def scalaClasspath = project.scalaRuntime.inferScalaClasspath([new File("other.jar"), new File("scala-library-2.10.1.jar")])
+        scalaClasspath.files
+
+        then:
+        GradleException e = thrown()
+        e.message == "Cannot infer Scala class path because no repository is declared in $project"
+    }
+
+    def "inference fails if 'scalaTools' configuration is empty and no Scala library Jar is found on class path"() {
+        project.repositories {
+            mavenCentral()
+        }
+
+        when:
+        def scalaClasspath = project.scalaRuntime.inferScalaClasspath([new File("other.jar"), new File("other2.jar")])
+        scalaClasspath.files
+
+        then:
+        GradleException e = thrown()
+        e.message.startsWith("Cannot infer Scala class path because no Scala library Jar was found. Does root project 'test' declare dependency to scala-library? Searched classpath:")
+    }
+
+    def "allows to find Scala Jar on class path"() {
+        when:
+        def file = project.scalaRuntime.findScalaJar([new File("other.jar"), new File("scala-jdbc-1.5.jar"), new File("scala-compiler-1.7.jar")], "jdbc")
+
+        then:
+        file.name == "scala-jdbc-1.5.jar"
+    }
+
+    def "returns null if Scala Jar not found"() {
+        when:
+        def file = project.scalaRuntime.findScalaJar([new File("other.jar"), new File("scala-jdbc-1.5.jar"), new File("scala-compiler-1.7.jar")], "library")
+
+        then:
+        file == null
+    }
+
+    def "allows to determine version of Scala Jar"() {
+        expect:
+        with(project.scalaRuntime) {
+            getScalaVersion(new File("scala-compiler-2.9.2.jar")) == "2.9.2"
+            getScalaVersion(new File("scala-jdbc-2.9.2.jar")) == "2.9.2"
+            getScalaVersion(new File("scala-library-2.10.0-SNAPSHOT.jar")) == "2.10.0-SNAPSHOT"
+            getScalaVersion(new File("scala-library-2.10.0-rc-3.jar")) == "2.10.0-rc-3"
+        }
+    }
+
+    def "returns null if Scala version cannot be determined"() {
+        expect:
+        with(project.scalaRuntime) {
+            getScalaVersion(new File("scala-compiler.jar")) == null
+            getScalaVersion(new File("groovy-compiler-2.1.0.jar")) == null
+        }
+    }
+}
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 be14ffd..71db9f8 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
@@ -28,11 +28,15 @@ import org.hamcrest.core.IsNull;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.io.File;
 
 public class ScalaCompileTest extends AbstractCompileTest {
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
 
     private ScalaCompile scalaCompile;
 
@@ -71,13 +75,16 @@ public class ScalaCompileTest extends AbstractCompileTest {
         scalaCompile.compile();
     }
 
-    @Test(expected = InvalidUserDataException.class)
+    @Test
     public void testMoansIfScalaClasspathIsEmpty() {
         setUpMocksAndAttributes(scalaCompile);
         context.checking(new Expectations() {{
             allowing(scalaClasspath).isEmpty(); will(returnValue(true));
         }});
 
+        thrown.expect(InvalidUserDataException.class);
+        thrown.expectMessage("'testTask.scalaClasspath' must not be empty");
+
         scalaCompile.compile();
     }
 
diff --git a/subprojects/signing/signing.gradle b/subprojects/signing/signing.gradle
index dd4ae7f..16e1455 100644
--- a/subprojects/signing/signing.gradle
+++ b/subprojects/signing/signing.gradle
@@ -15,7 +15,7 @@
  */
 
 dependencies {
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile project(':core')
     compile project(":plugins")
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
index 265592b..43ec2a1 100644
--- a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
@@ -131,7 +131,7 @@ class SigningExtension {
         def configurations = project.configurations
         def configuration = configurations.findByName(DEFAULT_CONFIGURATION_NAME)
         if (configuration == null) {
-            configuration = configurations.add(DEFAULT_CONFIGURATION_NAME)
+            configuration = configurations.create(DEFAULT_CONFIGURATION_NAME)
         }
         configuration
     }
@@ -323,9 +323,9 @@ class SigningExtension {
     }
 
     /**
-     * Signs the POM artifact for the given maven deployment.
+     * Signs the POM artifact for the given Maven deployment.
      *
-     * <p>You can use this method to sign the generated pom when publishing to a maven repository with the maven plugin.
+     * <p>You can use this method to sign the generated POM when publishing to a Maven repository with the Maven plugin.
      * </p>
      * <pre autoTested=''>
      * uploadArchives {
@@ -344,7 +344,7 @@ class SigningExtension {
      * this method will silently do nothing. That is, a signature for the POM file will not be uploaded.
      *
      * @param mavenDeployment The deployment to sign the POM of
-     * @param closure the configuration of the underlying {@link SignOperation sign operation} for the pom (optional)
+     * @param closure the configuration of the underlying {@link SignOperation sign operation} for the POM (optional)
      * @return the generated signature artifact
      */
     Signature signPom(MavenDeployment mavenDeployment, Closure closure = null) {
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy
index 11ae0e1..aa00154 100644
--- a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy
@@ -22,6 +22,7 @@ import org.gradle.util.DeprecationLogger
 /**
  * @deprecated Use {@link SigningExtension}
  */
+ at Deprecated
 class SigningPluginConvention {
     
     private SigningExtension extension
@@ -33,6 +34,7 @@ class SigningPluginConvention {
     /**
      * @deprecated Use {@link SigningExtension#sign(PublishArtifact...) project.signing.sign(PublishArtifact...) }
      */
+    @Deprecated
     SignOperation sign(PublishArtifact... publishArtifacts) {
         DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
         extension.sign(publishArtifacts)
@@ -41,6 +43,7 @@ class SigningPluginConvention {
     /**
      * @deprecated Use {@link SigningExtension#sign(File...) project.signing.sign(File...) }
      */
+    @Deprecated
     SignOperation sign(File... files) {
         DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
         extension.sign(files)
@@ -49,6 +52,7 @@ class SigningPluginConvention {
     /**
      * @deprecated Use {@link SigningExtension#sign(String, File...) project.signing.sign(String, File...)}
      */
+    @Deprecated
     SignOperation sign(String classifier, File... files) {
         DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
         extension.sign(classifier, files)
@@ -57,6 +61,7 @@ class SigningPluginConvention {
     /**
      * @deprecated Use {@link SigningExtension#sign(Closure) project.signing.sign \{ } }
      */
+    @Deprecated
     SignOperation sign(Closure closure) {
         DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
         extension.sign(closure)
@@ -65,10 +70,9 @@ class SigningPluginConvention {
     /**
      * @deprecated Use {@link SigningExtension#signPom() project.signing.signPom}
      */
+    @Deprecated
     Signature signPom(MavenDeployment mavenDeployment, Closure closure = null) {
         DeprecationLogger.nagUserOfReplacedMethod("signPom()", "signing.signPom()")
         extension.signPom(mavenDeployment, closure)
     }
-    
-    
 }
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
index 5068fdf..1adabe3 100644
--- a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
@@ -18,7 +18,7 @@ package org.gradle.plugins.signing
 import org.gradle.api.Project
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.HelperUtil
+import org.gradle.util.TestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -28,7 +28,7 @@ class SigningProjectSpec extends Specification {
     
     @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
         
-    Project project = HelperUtil.createRootProject()
+    Project project = TestUtil.createRootProject()
     
     private assertProject() {
         assert project != null : "You haven't created a project"
diff --git a/subprojects/sonar/sonar.gradle b/subprojects/sonar/sonar.gradle
index 0a17a37..078aebe 100644
--- a/subprojects/sonar/sonar.gradle
+++ b/subprojects/sonar/sonar.gradle
@@ -19,6 +19,7 @@ apply from: "$rootDir/gradle/providedConfiguration.gradle"
 dependencies {
     compile project(":core")
     compile project(":plugins")
+    compile project(":jacoco")
     compile libraries.groovy
 
     // Sonar Runner plugin
@@ -42,4 +43,4 @@ dependencies {
 }
 
 useTestFixtures()
-
+useClassycle()
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
index 8076b66..c0341a5 100644
--- 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
@@ -19,11 +19,11 @@ package org.gradle.api.plugins.sonar
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.server.http.ServletContainer
 import org.gradle.util.AvailablePortFinder
-import org.gradle.util.ClasspathUtil
+import org.gradle.internal.classloader.ClasspathUtil
 import org.junit.Rule
-import org.mortbay.jetty.Server
-import org.mortbay.jetty.webapp.WebAppContext
+
 import spock.lang.AutoCleanup
 import spock.lang.Shared
 
@@ -32,7 +32,7 @@ class SonarSmokeIntegrationTest extends AbstractIntegrationSpec {
     AvailablePortFinder portFinder = AvailablePortFinder.createPrivate()
 
     @AutoCleanup("stop")
-    Server webServer = new Server(0)
+    ServletContainer container
 
     @Rule
     TestNameTestDirectoryProvider tempDir = new TestNameTestDirectoryProvider()
@@ -61,10 +61,8 @@ sonar.jdbc.url=jdbc:h2:mem:sonartest
 sonar.embeddedDatabase.port=$databasePort
         """.trim()
 
-        def context = new WebAppContext()
-        context.war = warFile
-        webServer.addHandler(context)
-        webServer.start()
+        container = new ServletContainer(warFile)
+        container.start()
     }
 
     def "can run Sonar analysis"() {
@@ -76,7 +74,7 @@ sonar.embeddedDatabase.port=$databasePort
         // the wrong class loader.
         when:
         executer.requireGradleHome()
-                .withArgument("-PserverUrl=http://localhost:${webServer.connectors[0].localPort}")
+                .withArgument("-PserverUrl=http://localhost:${container.port}")
                 .withArgument("-PdatabaseUrl=jdbc:h2:tcp://localhost:$databasePort/mem:sonartest")
                 .withTasks("build", "sonarAnalyze").run()
 
diff --git a/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy b/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
index 9d9b4ed..be3f96b 100644
--- a/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
+++ b/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
@@ -19,11 +19,11 @@ package org.gradle.api.sonar.runner
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.server.http.ServletContainer
 import org.gradle.util.AvailablePortFinder
-import org.gradle.util.ClasspathUtil
+import org.gradle.internal.classloader.ClasspathUtil
 import org.junit.Rule
-import org.mortbay.jetty.Server
-import org.mortbay.jetty.webapp.WebAppContext
+
 import spock.lang.AutoCleanup
 import spock.lang.Shared
 
@@ -32,7 +32,7 @@ class SonarRunnerSmokeIntegrationTest extends AbstractIntegrationSpec {
     AvailablePortFinder portFinder = AvailablePortFinder.createPrivate()
 
     @AutoCleanup("stop")
-    Server webServer = new Server(0)
+    ServletContainer container
 
     @Rule
     TestNameTestDirectoryProvider tempDir = new TestNameTestDirectoryProvider()
@@ -61,10 +61,8 @@ sonar.jdbc.url=jdbc:h2:mem:sonartest
 sonar.embeddedDatabase.port=$databasePort
         """.trim()
 
-        def context = new WebAppContext()
-        context.war = warFile
-        webServer.addHandler(context)
-        webServer.start()
+        container = new ServletContainer(warFile)
+        container.start()
     }
 
     def "execute 'sonarRunner' task"() {
@@ -74,7 +72,7 @@ sonar.embeddedDatabase.port=$databasePort
                 .withArgument("-i")
                 .withArgument("-PserverUrl=foo") // dummy value for configuring sonarAnalyze task
                 .withArgument("-PdatabaseUrl=bar") // dummy value for configuring sonarAnalyze task
-                .withArgument("-Dsonar.host.url=http://localhost:${webServer.connectors[0].localPort}")
+                .withArgument("-Dsonar.host.url=http://localhost:${container.port}")
                 .withArgument("-Dsonar.jdbc.url=jdbc:h2:tcp://localhost:$databasePort/mem:sonartest")
                 .withTasks("sonarRunner").run()
 
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/build.gradle
deleted file mode 100644
index 2d8cfa3..0000000
--- a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/build.gradle
+++ /dev/null
@@ -1,2 +0,0 @@
-description = "Sonar Test Java Project"
-
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/build.gradle
new file mode 100644
index 0000000..3f3cb77
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/build.gradle
@@ -0,0 +1,5 @@
+if(!org.gradle.internal.os.OperatingSystem.current().isWindows()){
+    apply plugin:'jacoco'
+}
+description = "Sonar Test Java Project"
+
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production1.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production1.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production10.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production10.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production2.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production2.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production3.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production3.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production4.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production4.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production5.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production5.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production6.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production6.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production7.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production7.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production8.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production8.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production9.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production9.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/resources/org/gradle/test/javaProject/productionResource.xml
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/resources/org/gradle/test/javaProject/productionResource.xml
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test1.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test1.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test10.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test10.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test2.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test2.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test3.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test3.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test4.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test4.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test5.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test5.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test6.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test6.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test7.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test7.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test8.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test8.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test9.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test9.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/resources/org/gradle/test/javaProject/testResource.xml
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/resources/org/gradle/test/javaProject/testResource.xml
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle
index fbb7d87..be3f4a3 100644
--- a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle
@@ -1,3 +1,3 @@
-include "javaProject", "skippedProject", "customizedProject", "nested:nested2:nestedProject"
+include "javaProjectWithJacoco", "skippedProject", "customizedProject", "nested:nested2:nestedProject"
 
 rootProject.name = "Sonar Test Build" // spaces are intentional
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/build.gradle
deleted file mode 100644
index 2d8cfa3..0000000
--- a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/build.gradle
+++ /dev/null
@@ -1,2 +0,0 @@
-description = "Sonar Test Java Project"
-
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/build.gradle
new file mode 100644
index 0000000..3f3cb77
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/build.gradle
@@ -0,0 +1,5 @@
+if(!org.gradle.internal.os.OperatingSystem.current().isWindows()){
+    apply plugin:'jacoco'
+}
+description = "Sonar Test Java Project"
+
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production1.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production1.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production10.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production10.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production2.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production2.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production3.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production3.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production4.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production4.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production5.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production5.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production6.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production6.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production7.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production7.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production8.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production8.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production9.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/java/org/gradle/test/javaProject/Production9.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/resources/org/gradle/test/javaProject/productionResource.xml
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/main/resources/org/gradle/test/javaProject/productionResource.xml
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test1.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test1.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test10.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test10.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test2.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test2.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test3.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test3.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test4.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test4.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test5.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test5.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test6.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test6.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test7.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test7.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test8.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test8.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test9.java
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/java/org/gradle/test/javaProject/Test9.java
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/resources/org/gradle/test/javaProject/testResource.xml
similarity index 100%
rename from subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml
rename to subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProjectWithJacoco/src/test/resources/org/gradle/test/javaProject/testResource.xml
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle
index ed2f902..cb7798c 100644
--- a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle
@@ -1,3 +1,3 @@
-include "javaProject", "groovyProject", "skippedProject", "customizedProject", "nested:nested2:nestedProject"
+include "javaProjectWithJacoco", "groovyProject", "skippedProject", "customizedProject", "nested:nested2:nestedProject"
 
 rootProject.name = "SonarTestBuild"
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 0f7a50b..d5e8ce4 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
@@ -18,7 +18,7 @@ package org.gradle.api.plugins.sonar
 import org.sonar.batch.bootstrapper.Bootstrapper
 import org.gradle.api.internal.ConventionTask
 import org.gradle.api.tasks.TaskAction
-import org.gradle.util.ClasspathUtil
+import org.gradle.internal.classloader.ClasspathUtil
 
 import org.gradle.api.plugins.sonar.model.SonarRootModel
 import org.gradle.util.GFileUtils
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 d744c34..51475ff 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
@@ -17,13 +17,14 @@ package org.gradle.api.plugins.sonar
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
-import org.gradle.util.GradleVersion
-import org.gradle.internal.jvm.Jvm
 import org.gradle.api.plugins.sonar.model.*
+import org.gradle.internal.jvm.Jvm
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.testing.jacoco.plugins.JacocoPlugin
+import org.gradle.util.GradleVersion
 
 import javax.inject.Inject
 
@@ -60,7 +61,7 @@ class SonarPlugin implements Plugin<ProjectInternal> {
     }
 
     private SonarAnalyze configureSonarTask(Project project) {
-        project.tasks.add(SONAR_ANALYZE_TASK_NAME, SonarAnalyze)
+        project.tasks.create(SONAR_ANALYZE_TASK_NAME, SonarAnalyze)
     }
 
     private SonarRootModel configureSonarRootModel(Project project) {
@@ -142,11 +143,15 @@ class SonarPlugin implements Plugin<ProjectInternal> {
                     }
                     libraries
                 }
-                testReportPath = { project.test.testResultsDir }
+                testReportPath = { project.test.reports.junitXml.destination }
                 language = { "java" }
             }
+            project.plugins.withType(JacocoPlugin) {
+                sonarProject.withProjectProperties { props ->
+                    props['sonar.jacoco.reportPath'] = project.test.jacoco.destinationFile
+                }
+            }
         }
-
         sonarProject
     }
 }
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy
index 391e507..9817644 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy
@@ -22,6 +22,7 @@ import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.tasks.SourceSet
 import org.gradle.internal.jvm.Jvm
+import org.gradle.testing.jacoco.plugins.JacocoPlugin
 
 /**
  * A plugin for analyzing projects with the
@@ -91,6 +92,8 @@ import org.gradle.internal.jvm.Jvm
  *     <dd>sourceSets.main.runtimeClasspath (filtering to only include files; {@code rt.jar} added if necessary)
  *     <dt>sonar.surefire.reportsPath
  *     <dd>test.testResultsDir (if the directory exists)
+ *     <dt>sonar.junit.reportsPath
+ *     <dd>test.testResultsDir (if the directory exists)
  * </dl>
  */
 @Incubating
@@ -100,7 +103,10 @@ class SonarRunnerPlugin implements Plugin<Project> {
 
     void apply(Project project) {
         targetProject = project
-        def sonarRunnerTask = project.tasks.add("sonarRunner", SonarRunner)
+        def sonarRunnerTask = project.tasks.create("sonarRunner", SonarRunner)
+        sonarRunnerTask.with {
+            description = "Analyzes $project and its subprojects with Sonar Runner."
+        }
         sonarRunnerTask.conventionMapping.with {
             sonarProperties = {
                 def properties = new Properties()
@@ -120,21 +126,27 @@ class SonarRunnerPlugin implements Plugin<Project> {
 
     void computeSonarProperties(Project project, Properties properties) {
         def extension = project.extensions.getByType(SonarRunnerExtension)
-        if (extension.skipProject) { return }
+        if (extension.skipProject) {
+            return
+        }
 
         Map<String, Object> rawProperties = [:]
         addGradleDefaults(project, rawProperties)
         extension.evaluateSonarPropertiesBlocks(rawProperties)
-        if (project == targetProject) { addSystemProperties(rawProperties) }
+        if (project == targetProject) {
+            addSystemProperties(rawProperties)
+        }
 
         def projectPrefix = project.path.substring(targetProject.path.size()).replace(":", ".")
         if (projectPrefix.startsWith(".")) {
             projectPrefix = projectPrefix.substring(1)
         }
         convertProperties(rawProperties, projectPrefix, properties)
-        
-        def enabledChildProjects = project.childProjects.values().findAll { !it.sonarRunner.skipProject }
-        if (enabledChildProjects.empty) { return }
+
+        def enabledChildProjects = project.childProjects.values().findAll { !it.sonarRunner.skipProject }.sort()
+        if (enabledChildProjects.empty) {
+            return
+        }
 
         properties[convertKey("sonar.modules", projectPrefix)] = convertValue(enabledChildProjects.name)
         for (childProject in enabledChildProjects) {
@@ -172,7 +184,15 @@ class SonarRunnerPlugin implements Plugin<Project> {
             properties["sonar.tests"] = test.allSource.srcDirs.findAll { it.exists() } ?: null
             properties["sonar.binaries"] = main.runtimeClasspath.findAll { it.directory } ?: null
             properties["sonar.libraries"] = getLibraries(main)
-            properties["sonar.surefire.reportsPath"] = project.test.testResultsDir.exists() ? project.test.testResultsDir : null
+            File testResultsDir = project.test.reports.junitXml.destination
+            File testResultsValue = testResultsDir.exists() ? testResultsDir : null
+            properties["sonar.surefire.reportsPath"] = testResultsValue
+            // added due to http://issues.gradle.org/browse/GRADLE-3005
+            properties["sonar.junit.reportsPath"] = testResultsValue
+
+            project.plugins.withType(JacocoPlugin) {
+                properties["sonar.jacoco.reportPath"] = project.test.jacoco.destinationFile.exists() ? project.test.jacoco.destinationFile : null
+            }
         }
 
         if (properties["sonar.sources"] == null) {
@@ -211,12 +231,13 @@ class SonarRunnerPlugin implements Plugin<Project> {
             }
         }
     }
-    
+
     private String convertKey(String key, String projectPrefix) {
         projectPrefix ? "${projectPrefix}.$key" : key
     }
-    
+
     private String convertValue(Object value) {
         value instanceof Iterable ? value.collect { convertValue(it) }.join(",") : value.toString()
     }
+
 }
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy
index 9898aab..bc2a197 100644
--- a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.plugins.sonar
 import spock.lang.Specification
 
 class SonarAnalyzeTest extends Specification {
-//    SonarAnalyze task = HelperUtil.createTask(SonarAnalyze)
+//    SonarAnalyze task = TestUtil.createTask(SonarAnalyze)
 //
 //    @Issue("GRADLE-1499")
 //    def "can configure project properties"() {
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy
index 94d68fc..6e49766 100644
--- a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy
@@ -15,22 +15,21 @@
  */
 package org.gradle.api.plugins.sonar
 
-import org.gradle.api.plugins.sonar.model.SonarRootModel
-import org.gradle.api.plugins.sonar.model.SonarProjectModel
-import org.gradle.api.plugins.sonar.model.SonarProject
 import org.gradle.api.Project
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
-import org.gradle.util.ConfigureUtil
-import org.gradle.util.HelperUtil
+import org.gradle.api.plugins.sonar.model.SonarProject
+import org.gradle.api.plugins.sonar.model.SonarProjectModel
+import org.gradle.api.plugins.sonar.model.SonarRootModel
 import org.gradle.internal.jvm.Jvm
-
-import spock.lang.Specification
+import org.gradle.util.ConfigureUtil
+import org.gradle.util.TestUtil
 import spock.lang.Issue
+import spock.lang.Specification
 
 class SonarPluginTest extends Specification {
     def "adds model and task to root project"() {
-        def project = HelperUtil.createRootProject()
+        def project = TestUtil.createRootProject()
 
         when:
         project.plugins.apply(SonarPlugin)
@@ -41,8 +40,8 @@ class SonarPluginTest extends Specification {
     }
 
     def "adds model to subprojects"() {
-        def project = HelperUtil.createRootProject()
-        def child = HelperUtil.createChildProject(project, "child")
+        def project = TestUtil.createRootProject()
+        def child = TestUtil.createChildProject(project, "child")
 
         when:
         project.plugins.apply(SonarPlugin)
@@ -53,7 +52,7 @@ class SonarPluginTest extends Specification {
     }
 
     def "provides defaults for global configuration"() {
-        def project = HelperUtil.createRootProject()
+        def project = TestUtil.createRootProject()
 
         when:
         project.plugins.apply(SonarPlugin)
@@ -110,7 +109,7 @@ class SonarPluginTest extends Specification {
         sonarProject.binaryDirs == [project.sourceSets.main.output.classesDir]
         sonarProject.libraries.files as List == [Jvm.current().runtimeJar]
 
-        sonarProject.testReportPath == project.test.testResultsDir
+        sonarProject.testReportPath == project.test.reports.junitXml.destination
         sonarProject.language == "java"
 
         where:
@@ -128,11 +127,11 @@ class SonarPluginTest extends Specification {
     }
 
     private Project createMultiProject(Closure commonConfig = {}) {
-        def root = HelperUtil.createRootProject()
+        def root = TestUtil.createRootProject()
         ConfigureUtil.configure(commonConfig, root)
         root.group = "group"
 
-        def child = HelperUtil.createChildProject(root, "child")
+        def child = TestUtil.createChildProject(root, "child")
         ConfigureUtil.configure(commonConfig, child)
         child.group = "group"
 
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy
index eb84184..8f82274 100644
--- a/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy
@@ -17,26 +17,26 @@ package org.gradle.api.sonar.runner
 
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.tasks.TaskDependencyMatchers
 import org.gradle.internal.jvm.Jvm
 import org.gradle.test.fixtures.file.TestFile
-import org.gradle.testfixtures.ProjectBuilder
 import org.gradle.util.SetSystemProperties
+import org.gradle.util.TestUtil
 import org.junit.Rule
 
 import spock.lang.Specification
 
 import static spock.util.matcher.HamcrestSupport.*
-import static org.gradle.util.Matchers.*
 import static org.hamcrest.Matchers.*
 
 class SonarRunnerPluginTest extends Specification {
     @Rule SetSystemProperties systemProperties
 
-    def rootProject = ProjectBuilder.builder().withName("root").build()
-    def parentProject = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
-    def childProject = ProjectBuilder.builder().withName("child").withParent(parentProject).build()
-    def childProject2 = ProjectBuilder.builder().withName("child2").withParent(parentProject).build()
-    def leafProject = ProjectBuilder.builder().withName("leaf").withParent(childProject).build()
+    def rootProject = TestUtil.builder().withName("root").build()
+    def parentProject = TestUtil.builder().withName("parent").withParent(rootProject).build()
+    def childProject = TestUtil.builder().withName("child").withParent(parentProject).build()
+    def childProject2 = TestUtil.builder().withName("child2").withParent(parentProject).build()
+    def leafProject = TestUtil.builder().withName("leaf").withParent(childProject).build()
 
     def setup() {
         parentProject.plugins.apply(SonarRunnerPlugin)
@@ -58,6 +58,8 @@ class SonarRunnerPluginTest extends Specification {
     def "adds a sonarRunner task to the target project"() {
         expect:
         parentProject.tasks.findByName("sonarRunner") instanceof SonarRunner
+        parentProject.tasks.sonarRunner.description == "Analyzes project ':parent' and its subprojects with Sonar Runner."
+
         childProject.tasks.findByName("sonarRunner") == null
     }
 
@@ -68,7 +70,7 @@ class SonarRunnerPluginTest extends Specification {
         childProject.plugins.apply(JavaPlugin)
 
         then:
-        expect(parentProject.tasks.sonarRunner, dependsOnPaths(containsInAnyOrder(":parent:test", ":parent:child:test")))
+        expect(parentProject.tasks.sonarRunner, TaskDependencyMatchers.dependsOnPaths(containsInAnyOrder(":parent:test", ":parent:child:test")))
     }
 
     def "doesn't make sonarRunner task depend on test task of skipped projects"() {
@@ -79,7 +81,7 @@ class SonarRunnerPluginTest extends Specification {
         childProject.sonarRunner.skipProject = true
 
         then:
-        expect(parentProject.tasks.sonarRunner, dependsOnPaths(contains(":parent:test")))
+        expect(parentProject.tasks.sonarRunner, TaskDependencyMatchers.dependsOnPaths(contains(":parent:test")))
     }
 
     def "adds default properties for target project and its subprojects"() {
@@ -179,6 +181,7 @@ class SonarRunnerPluginTest extends Specification {
         properties["sonar.binaries"].contains(new File(parentProject.buildDir, "out") as String)
         properties["sonar.libraries"].contains(new File(parentProject.projectDir, "lib/SomeLib.jar") as String)
         properties["sonar.surefire.reportsPath"] == new File(parentProject.buildDir, "test-results") as String
+        properties["sonar.junit.reportsPath"] == new File(parentProject.buildDir, "test-results") as String
     }
 
     def "only adds existing directories"() {
@@ -192,6 +195,7 @@ class SonarRunnerPluginTest extends Specification {
         !properties.containsKey("sonar.binaries")
         properties.containsKey("sonar.libraries") == (Jvm.current().getRuntimeJar() != null)
         !properties.containsKey("sonar.surefire.reportsPath")
+        !properties.containsKey("sonar.junit.reportsPath")
     }
 
     def "adds empty 'sonar.sources' property if no sources exist (because Sonar Runner 2.0 always expects this property to be set)"() {
@@ -240,17 +244,17 @@ class SonarRunnerPluginTest extends Specification {
         def properties = parentProject.tasks.sonarRunner.sonarProperties
 
         then:
-        properties["sonar.modules"] in ["child,child2", "child2,child"]
+        properties["sonar.modules"] == "child,child2"
         properties["child.sonar.modules"] == "leaf"
         !properties.containsKey("child2.sonar.modules")
         !properties.containsKey("child.leaf.sonar.modules")
     }
 
     def "handles 'modules' properties correctly if plugin is applied to root project"() {
-        def rootProject = ProjectBuilder.builder().withName("root").build()
-        def project = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
-        def project2 = ProjectBuilder.builder().withName("parent2").withParent(rootProject).build()
-        def childProject = ProjectBuilder.builder().withName("child").withParent(project).build()
+        def rootProject = TestUtil.builder().withName("root").build()
+        def project = TestUtil.builder().withName("parent").withParent(rootProject).build()
+        def project2 = TestUtil.builder().withName("parent2").withParent(rootProject).build()
+        def childProject = TestUtil.builder().withName("child").withParent(project).build()
 
         rootProject.plugins.apply(SonarRunnerPlugin)
 
@@ -258,7 +262,7 @@ class SonarRunnerPluginTest extends Specification {
         def properties = rootProject.tasks.sonarRunner.sonarProperties
 
         then:
-        properties["sonar.modules"] in ["parent,parent2", "parent2,parent"]
+        properties["sonar.modules"] == "parent,parent2"
         properties["parent.sonar.modules"] == "child"
         !properties.containsKey("parent2.sonar.modules")
         !properties.containsKey("parent.child.sonar.modules")
@@ -328,8 +332,8 @@ class SonarRunnerPluginTest extends Specification {
     }
 
     def "handles system properties correctly if plugin is applied to root project"() {
-        def rootProject = ProjectBuilder.builder().withName("root").build()
-        def project = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
+        def rootProject = TestUtil.builder().withName("root").build()
+        def project = TestUtil.builder().withName("parent").withParent(rootProject).build()
 
         rootProject.allprojects { version = 1.3 }
         rootProject.plugins.apply(SonarRunnerPlugin)
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
index 7ea9830..13a4ea4 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
@@ -20,14 +20,11 @@ import org.gradle.api.JavaVersion
 import org.gradle.integtests.fixtures.AutoTestedSamplesUtil
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.model.Element
-import org.gradle.util.ClasspathUtil
+import org.gradle.internal.classloader.ClasspathUtil
 import org.junit.Rule
 import spock.lang.IgnoreIf
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 1/5/12
- */
 @IgnoreIf({!JavaVersion.current().java6Compatible})
 public class AutoTestedSamplesToolingApiTest extends Specification {
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
index fd3500b..3f76c39 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
@@ -279,12 +279,12 @@ project.description = text
             return 'mock'
         }
 
-        ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory, File userHomeDir) {
             def o = progressLoggerFactory.newOperation("mock")
             operation(o)
             o.started()
             o.completed()
-            return delegate.getToolingImplementationClasspath(progressLoggerFactory)
+            return delegate.getToolingImplementationClasspath(progressLoggerFactory, userHomeDir)
         }
     }
 
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 e3a6665..5e9b1f4 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
@@ -77,9 +77,23 @@ class SamplesToolingApiIntegrationTest extends AbstractIntegrationSpec {
         result.output.contains("    build")
     }
 
-    private void tweakProject() {
-        def projectDir = sample.dir
+    @UsesSample('toolingApi/customModel')
+    def "can use tooling API to register custom model"() {
+        tweakPluginProject(sample.dir.file('plugin'))
+        tweakProject(sample.dir.file('tooling'))
 
+        when:
+        run('publish', sample.dir.file('plugin'))
+        def result = run('run', sample.dir.file('tooling'))
+
+        then:
+        result.output.contains("   :a")
+        result.output.contains("   :b")
+        result.output.contains("   :c")
+        noExceptionThrown()
+    }
+
+    private void tweakProject(File projectDir = sample.dir) {
         // Inject some additional configuration into the sample build script
         def buildFile = projectDir.file('build.gradle')
         def buildScript = buildFile.text
@@ -103,12 +117,30 @@ run {
         projectDir.file('settings.gradle').text = '// to stop search upwards'
     }
 
-    private ExecutionResult run() {
+    private void tweakPluginProject(File projectDir) {
+        // Inject some additional configuration into the sample build script
+        def buildFile = projectDir.file('build.gradle')
+        def buildScript = buildFile.text
+        def index = buildScript.indexOf('publishing {')
+        assert index >= 0
+        buildContext = new IntegrationTestBuildContext()
+        buildScript = buildScript.substring(0, index) + """
+repositories {
+    maven { url "${buildContext.libsRepo.toURI()}" }
+}
+""" + buildScript.substring(index)
+
+        buildFile.text = buildScript
+
+        // Add in an empty settings file to avoid searching up
+        projectDir.file('settings.gradle').text = '// to stop search upwards'
+    }
+
+    private ExecutionResult run(String task = 'run', File dir = sample.dir) {
         try {
             return new GradleContextualExecuter(distribution, temporaryFolder)
-                    .inDirectory(sample.dir)
-                    .withTasks('run')
-                    .withDaemonIdleTimeoutSecs(60)
+                    .inDirectory(dir)
+                    .withTasks(task)
                     .run()
         } catch (Exception e) {
             throw new IntegrationTestHint(e);
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy
index 8e2ae48..1a8d90f 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy
@@ -28,8 +28,11 @@ class ToolingApiClasspathIntegrationTest extends AbstractIntegrationSpec {
         ToolingApiDistribution resolve = resolver.resolve(distribution.getVersion().version)
 
         then:
-        resolve.classpath.files.size() == 2
-        resolve.classpath.files.any {it.name ==~ /slf4j-api-.*\.jar/}
-        resolve.classpath.files.find {it.name ==~ /gradle-tooling-api.*\.jar/}.size() < 1.5 * 1024 * 1024
+        resolve.classpath.size() == 2
+        resolve.classpath.any {it.name ==~ /slf4j-api-.*\.jar/}
+        resolve.classpath.find {it.name ==~ /gradle-tooling-api.*\.jar/}.size() < 1.6 * 1024 * 1024
+
+        cleanup:
+        resolver.stop()
     }
 }
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 be64f48..f934885 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
@@ -24,16 +24,18 @@ import org.gradle.integtests.tooling.fixture.TextUtil
 import org.gradle.integtests.tooling.fixture.ToolingApi
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.tooling.GradleConnector
-import org.gradle.tooling.UnsupportedVersionException
 import org.gradle.tooling.model.GradleProject
 import org.gradle.util.GradleVersion
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
 import spock.lang.Issue
 
 class ToolingApiIntegrationTest extends AbstractIntegrationSpec {
 
     final ToolingApi toolingApi = new ToolingApi(distribution, temporaryFolder)
     final GradleDistribution otherVersion = new ReleasedVersionDistributions().mostRecentFinalRelease
-    final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
+
+    @Rule SetSystemProperties properties
 
     TestFile projectDir
 
@@ -129,6 +131,7 @@ allprojects {
     }
 
     def "can specify a gradle version to use"() {
+        System.setProperty("javax.net.debug", "ssl,handshake")
         toolingApi.isEmbedded = false
         projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version.version}'"
 
@@ -142,23 +145,11 @@ allprojects {
         model != null
     }
 
-    def "tooling api reports an error when the specified gradle version does not support the tooling api"() {
-        def distroZip = buildContext.distribution('0.9.2').binDistribution
-
-        when:
-        toolingApi.withConnector { connector -> connector.useDistribution(distroZip.toURI()) }
-        toolingApi.maybeFailWithConnection { connection -> connection.getModel(GradleProject.class) }
-
-        then:
-        UnsupportedVersionException e = thrown()
-        e.message == "The specified Gradle distribution '${distroZip.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 startTimeoutMs = 90000
         def stateChangeTimeoutMs = 15000
         def stopTimeoutMs = 10000
         def retryIntervalMs = 500
@@ -253,7 +244,6 @@ allprojects {
         when:
         GradleHandle handle = executer.inDirectory(projectDir)
                 .withTasks('run')
-                .withDaemonIdleTimeoutSecs(60)
                 .start()
 
         then:
@@ -284,6 +274,7 @@ allprojects {
             if (System.currentTimeMillis() - stopMarkerAt > stopTimeoutMs) {
                 throw new Exception("timeout after placing stop marker (JVM might have been held open")
             }
+            sleep retryIntervalMs
         }
 
         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
index 6f4a779..57da953 100644
--- 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
@@ -24,8 +24,6 @@ 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()
@@ -33,23 +31,27 @@ class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
 
     void setup() {
         server.start()
-        settingsFile << "";
-        buildFile << "task hello << { println hello }"
     }
 
-    public void "downloads distribution with valid useragent information"() {
+    def "downloads distribution with valid user-agent 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)
+        def userHomeDir = file("user-home-dir")
+
+        given:
+        settingsFile << "";
+        buildFile << "task hello << { println hello }"
+
+        and:
+        server.expectGet("/custom-dist.zip", 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)
+            it.useDistribution(URI.create("http://localhost:${server.port}/custom-dist.zip"))
+            it.useGradleUserHomeDir(userHomeDir)
         }
 
+        when:
         ByteArrayOutputStream buildOutput = new ByteArrayOutputStream()
 
         toolingApi.withConnection { connection ->
@@ -59,6 +61,7 @@ class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
         }
 
         then:
-        assertThat(new String(buildOutput.toByteArray()), containsString('hello'))
+        buildOutput.toString().contains('hello')
+        userHomeDir.file("wrapper/dists/custom-dist").assertIsDir().listFiles().size() == 1
     }
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedVersionIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedVersionIntegrationTest.groovy
new file mode 100644
index 0000000..9b52144
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedVersionIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
+import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.integtests.tooling.r18.NullAction
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.model.GradleProject
+import org.gradle.util.GradleVersion
+
+class ToolingApiUnsupportedVersionIntegrationTest extends AbstractIntegrationSpec {
+    final ToolingApi toolingApi = new ToolingApi(distribution, temporaryFolder)
+    final GradleDistribution otherVersion = new ReleasedVersionDistributions().getDistribution(GradleVersion.version("0.9.2"))
+    final URI distroZip = otherVersion.binDistribution.toURI()
+
+    def setup() {
+        toolingApi.withConnector { connector -> connector.useDistribution(distroZip) }
+    }
+
+    def "tooling api reports an error when requesting a model using a gradle version that does not implement the tooling api"() {
+        when:
+        toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == "The specified Gradle distribution '${distroZip}' does not implement the tooling API. Support for the tooling API was added in Gradle 1.0-milestone-3 and is available in all later versions."
+    }
+
+    def "tooling api reports an error when running a build using a gradle version does not implement the tooling api"() {
+        when:
+        toolingApi.withConnection { connection -> connection.newBuild().run() }
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == "The specified Gradle distribution '${distroZip}' does not implement the tooling API. Support for the tooling API was added in Gradle 1.0-milestone-3 and is available in all later versions."
+    }
+
+    def "tooling api reports an error when running a build action using a gradle version does not implement the tooling api"() {
+        when:
+        toolingApi.withConnection { connection -> connection.action(new NullAction()).run() }
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == "The specified Gradle distribution '${distroZip}' does not implement the tooling API. Support for the tooling API was added in Gradle 1.0-milestone-3 and is available in all later versions."
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
index b20c450..22a4ecb 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
@@ -16,12 +16,11 @@
 
 package org.gradle.integtests.tooling.fixture
 
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.integtests.fixtures.executer.OutputScrapingExecutionResult
 import org.gradle.tooling.ModelBuilder
 import org.gradle.tooling.ProgressListener
 
-/**
- * by Szczepan Faber, created at: 12/19/11
- */
 class ConfigurableOperation {
 
     def operation
@@ -52,6 +51,10 @@ class ConfigurableOperation {
         return stderr.toString()
     }
 
+    ExecutionResult getResult() {
+        return new OutputScrapingExecutionResult(standardOutput, standardError)
+    }
+
     ConfigurableOperation setStandardInput(String input) {
         this.operation.standardInput = new ByteArrayInputStream(input.toString().bytes)
         return this
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy
index 22537c6..e8e8520 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy
@@ -16,23 +16,23 @@
 
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.util.DefaultClassLoaderFactory
-import org.gradle.api.file.FileCollection
+import org.gradle.internal.classloader.DefaultClassLoaderFactory
+import org.gradle.util.GradleVersion
 
 class ExternalToolingApiDistribution implements ToolingApiDistribution {
-    private final String version
-    private final FileCollection classpath
+    private final GradleVersion version
+    private final Collection<File> classpath
 
-    ExternalToolingApiDistribution(String version, FileCollection classpath) {
-        this.version = version
+    ExternalToolingApiDistribution(String version, Collection<File> classpath) {
+        this.version = GradleVersion.version(version)
         this.classpath = classpath
     }
 
-    String getVersion() {
+    GradleVersion getVersion() {
         version
     }
     
-    FileCollection getClasspath() {
+    Collection<File> getClasspath() {
         classpath
     }
     
@@ -42,6 +42,6 @@ class ExternalToolingApiDistribution implements ToolingApiDistribution {
     }
     
     String toString() {
-        "Tooling API $version"
+        "Tooling API $version.version"
     }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/IncludeAllPermutations.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/IncludeAllPermutations.java
deleted file mode 100644
index 3eca69f..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/IncludeAllPermutations.java
+++ /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.integtests.tooling.fixture;
-
-import java.lang.annotation.*;
-
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.TYPE)
- at Inherited
-public @interface IncludeAllPermutations {}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MaxTargetGradleVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MaxTargetGradleVersion.java
deleted file mode 100644
index f46b9d9..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MaxTargetGradleVersion.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.tooling.fixture;
-
-import java.lang.annotation.*;
-
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.TYPE)
- at Inherited
-public @interface MaxTargetGradleVersion {
-    String value();
-}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinTargetGradleVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinTargetGradleVersion.java
deleted file mode 100644
index 9128a76..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinTargetGradleVersion.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.tooling.fixture;
-
-import java.lang.annotation.*;
-
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.TYPE)
- at Inherited
-public @interface MinTargetGradleVersion {
-    String value();
-}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinToolingApiVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinToolingApiVersion.java
deleted file mode 100644
index 26f2008..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinToolingApiVersion.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.tooling.fixture;
-
-import java.lang.annotation.*;
-
- at Retention(RetentionPolicy.RUNTIME)
- at Target(ElementType.TYPE)
- at Inherited
-public @interface MinToolingApiVersion {
-    String value();
-}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TargetGradleVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TargetGradleVersion.java
new file mode 100644
index 0000000..8e623f7
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TargetGradleVersion.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.integtests.tooling.fixture;
+
+import java.lang.annotation.*;
+
+/**
+ * Specifies the range of target Gradle versions that the given tooling API test can work with.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Inherited
+public @interface TargetGradleVersion {
+    /**
+     * The requested target Gradle version. Can use '>=nnn', '<=nnn', '<nnn', '=nnn', 'current' or '!current' or space-separated list of patterns.
+     */
+    String value();
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy
index ab54a5b..92cc0c4 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy
@@ -17,14 +17,13 @@
 package org.gradle.integtests.tooling.fixture
 
 import org.gradle.util.GradleVersion
-import org.gradle.api.file.FileCollection
 
 class TestClasspathToolingApiDistribution implements ToolingApiDistribution {
-    String getVersion() {
-        GradleVersion.current().version
+    GradleVersion getVersion() {
+        GradleVersion.current()
     }
 
-    FileCollection getClasspath() {
+    Collection<File> getClasspath() {
         null
     }
 
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 24656e2..10367d4 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,14 +15,13 @@
  */
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.integtests.fixtures.IntegrationTestHint
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.tooling.GradleConnector
 import org.gradle.tooling.ProjectConnection
-import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.util.GradleVersion
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
@@ -60,26 +59,35 @@ class ToolingApi {
     }
 
     public <T> T withConnection(GradleConnector connector, Closure<T> cl) {
-        try {
-            return withConnectionRaw(connector, cl)
-        } catch (UnsupportedVersionException e) {
-            throw new IntegrationTestHint(e);
-        }
+        return withConnectionRaw(connector, cl)
     }
 
-    public void maybeFailWithConnection(Closure cl) {
-        GradleConnector connector = connector()
-        try {
-            withConnectionRaw(connector, cl)
-        } catch (Throwable e) {
-            throw e
+    private validate(Throwable throwable) {
+        if (dist.version != GradleVersion.current()) {
+            return
         }
+
+        // Verify that the exception carries the calling thread's stack information
+        def currentThreadStack = Thread.currentThread().stackTrace as List
+        while (!currentThreadStack.empty && (currentThreadStack[0].className != ToolingApi.name || currentThreadStack[0].methodName != 'withConnectionRaw')) {
+            currentThreadStack.remove(0)
+        }
+        assert currentThreadStack.size() > 1
+        currentThreadStack.remove(0)
+        String currentThreadStackStr = currentThreadStack.join("\n")
+
+        def throwableStack = throwable.stackTrace.join("\n")
+
+        assert throwableStack.endsWith(currentThreadStackStr)
     }
 
     private <T> T withConnectionRaw(GradleConnector connector, Closure<T> cl) {
         ProjectConnection connection = connector.connect()
         try {
             return cl.call(connection)
+        } catch (Throwable t) {
+            validate(t)
+            throw t
         } finally {
             connection.close()
         }
@@ -90,7 +98,7 @@ class ToolingApi {
         connector.useGradleUserHomeDir(userHomeDir)
         connector.forProjectDirectory(testWorkDirProvider.testDirectory)
         connector.searchUpwards(false)
-        connector.daemonMaxIdleTime(60, TimeUnit.SECONDS)
+        connector.daemonMaxIdleTime(120, TimeUnit.SECONDS)
         if (connector.metaClass.hasProperty(connector, 'verboseLogging')) {
             connector.verboseLogging = verboseLogging
         }
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 2cd1116..f5df166 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
@@ -15,44 +15,40 @@
  */
 package org.gradle.integtests.tooling.fixture
 
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
 import org.gradle.integtests.fixtures.AbstractCompatibilityTestRunner
 import org.gradle.integtests.fixtures.AbstractMultiTestRunner
 import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.internal.classloader.ClasspathUtil
+import org.gradle.internal.classloader.DefaultClassLoaderFactory
+import org.gradle.internal.classloader.MultiParentClassLoader
+import org.gradle.internal.classloader.MutableURLClassLoader
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.util.*
 
-/**
- * Executes instances of {@link ToolingApiSpecification} against all compatible versions of tooling API consumer
- * and provider, including the current Gradle version under test.
- *
- * <p>A test can be annotated with {@link MinToolingApiVersion} and {@link MinTargetGradleVersion} to indicate the
- * minimum tooling API or Gradle versions required for the test.
- */
 class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner {
     private static final Map<String, ClassLoader> TEST_CLASS_LOADERS = [:]
 
     ToolingApiCompatibilitySuiteRunner(Class<? extends ToolingApiSpecification> target) {
-        super(target, includesAllPermutations(target))
-    }
-
-    static String includesAllPermutations(Class target) {
-        if (target.getAnnotation(IncludeAllPermutations)) {
-            return "all";
-        } else {
-            return null; //just use whatever is the default
-        }
+        super(target)
     }
 
     @Override
     protected void createExecutions() {
-        ToolingApiDistributionResolver resolver = new ToolingApiDistributionResolver().withDefaultRepository()
-
-        add(new Permutation(resolver.resolve(current.version.version), current))
-        previous.each {
-            if (it.toolingApiSupported) {
-                add(new Permutation(resolver.resolve(current.version.version), it))
-                add(new Permutation(resolver.resolve(it.version.version), current))
+        def resolver = new ToolingApiDistributionResolver().withDefaultRepository()
+        try {
+            if (implicitVersion) {
+                add(new Permutation(resolver.resolve(current.version.version), current))
+            }
+            previous.each {
+                if (it.toolingApiSupported) {
+                    add(new Permutation(resolver.resolve(current.version.version), it))
+                    add(new Permutation(resolver.resolve(it.version.version), current))
+                }
             }
+        } finally {
+            resolver.stop()
         }
     }
 
@@ -67,7 +63,12 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
 
         @Override
         protected String getDisplayName() {
-            return "${displayName(GradleVersion.version(toolingApi.version))} -> ${displayName(gradle.version)}"
+            return "${displayName(toolingApi.version)} -> ${displayName(gradle.version)}"
+        }
+
+        @Override
+        String toString() {
+            return displayName
         }
 
         private String displayName(GradleVersion version) {
@@ -78,7 +79,7 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
         }
 
         @Override
-        protected boolean isEnabled() {
+        protected boolean isTestEnabled(AbstractMultiTestRunner.TestDetails testDetails) {
             if (!gradle.daemonSupported) {
                 return false
             }
@@ -89,33 +90,23 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
                 // So, for windows we'll only run tests against target gradle that supports ttl
                 return false
             }
-            MinToolingApiVersion minToolingApiVersion = target.getAnnotation(MinToolingApiVersion)
-            if (minToolingApiVersion && GradleVersion.version(toolingApi.version) < extractVersion(minToolingApiVersion)) {
+            ToolingApiVersion toolingApiVersion = testDetails.getAnnotation(ToolingApiVersion)
+            if (!toVersionSpec(toolingApiVersion).isSatisfiedBy(toolingApi.version)) {
                 return false
             }
-            MinTargetGradleVersion minTargetGradleVersion = target.getAnnotation(MinTargetGradleVersion)
-            if (minTargetGradleVersion && gradle.version < extractVersion(minTargetGradleVersion)) {
-                return false
-            }
-            MaxTargetGradleVersion maxTargetGradleVersion = target.getAnnotation(MaxTargetGradleVersion)
-            if (maxTargetGradleVersion && gradle.version > extractVersion(maxTargetGradleVersion)) {
+            TargetGradleVersion targetGradleVersion = testDetails.getAnnotation(TargetGradleVersion)
+            if (!toVersionSpec(targetGradleVersion).isSatisfiedBy(gradle.version)) {
                 return false
             }
 
             return true
         }
 
-        private GradleVersion extractVersion(annotation) {
-            if (GradleVersion.current().isSnapshot() && GradleVersion.current().version.startsWith(annotation.value())) {
-                //so that one can use an unreleased version in the annotation value
-                return GradleVersion.current()
-            }
-            if ("current".equals(annotation.value())) {
-                //so that one can use 'current' literal in the annotation value
-                //(useful if you don't know if the feature makes its way to the upcoming release)
-                return GradleVersion.current()
+        private Spec<GradleVersion> toVersionSpec(annotation) {
+            if (annotation == null) {
+                return Specs.SATISFIES_ALL
             }
-            return GradleVersion.version(annotation.value())
+            return GradleVersionSpec.toSpec(annotation.value())
         }
 
         @Override
@@ -152,6 +143,8 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
             sharedClassLoader.allowClass(OperatingSystem)
             sharedClassLoader.allowClass(Requires)
             sharedClassLoader.allowClass(TestPrecondition)
+            sharedClassLoader.allowClass(TargetGradleVersion)
+            sharedClassLoader.allowClass(ToolingApiVersion)
             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/ToolingApiDistribution.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistribution.groovy
index 0a69936..f1e16ee 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistribution.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistribution.groovy
@@ -16,11 +16,11 @@
 
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.api.file.FileCollection
 import org.gradle.api.Nullable
+import org.gradle.util.GradleVersion
 
 interface ToolingApiDistribution {
-    String getVersion()
-    @Nullable FileCollection getClasspath()
+    GradleVersion getVersion()
+    @Nullable Collection<File> getClasspath()
     ClassLoader getClassLoader()
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
index 8d1015a..551ecb0 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
@@ -20,18 +20,24 @@ import org.gradle.StartParameter
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Dependency
 import org.gradle.api.internal.artifacts.DependencyResolutionServices
-import org.gradle.api.internal.project.GlobalServicesRegistry
-import org.gradle.api.internal.project.ProjectInternalServiceRegistry
-import org.gradle.api.internal.project.TopLevelBuildServiceRegistry
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
-import org.gradle.util.HelperUtil
+import org.gradle.internal.concurrent.CompositeStoppable
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.internal.service.ServiceRegistryBuilder
+import org.gradle.internal.service.scopes.BuildScopeServices
+import org.gradle.internal.service.scopes.GlobalScopeServices
+import org.gradle.internal.service.scopes.ProjectScopeServices
+import org.gradle.logging.LoggingServiceRegistry
+import org.gradle.util.TestUtil
 
 class ToolingApiDistributionResolver {
     private final DependencyResolutionServices resolutionServices
     private final Map<String, ToolingApiDistribution> distributions = [:]
     private final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
     private boolean useExternalToolingApiDistribution = false;
+    private CompositeStoppable stopLater = new CompositeStoppable()
 
     ToolingApiDistributionResolver() {
         resolutionServices = createResolutionServices()
@@ -54,7 +60,7 @@ class ToolingApiDistributionResolver {
             } else {
                 Dependency toolingApiDep = resolutionServices.dependencyHandler.create("org.gradle:gradle-tooling-api:$toolingApiVersion")
                 Configuration toolingApiConfig = resolutionServices.configurationContainer.detachedConfiguration(toolingApiDep)
-                distributions[toolingApiVersion] = new ExternalToolingApiDistribution(toolingApiVersion, toolingApiConfig)
+                distributions[toolingApiVersion] = new ExternalToolingApiDistribution(toolingApiVersion, toolingApiConfig.files)
             }
         }
         distributions[toolingApiVersion]
@@ -67,16 +73,29 @@ class ToolingApiDistributionResolver {
     }
 
     private DependencyResolutionServices createResolutionServices() {
-        GlobalServicesRegistry globalRegistry = new GlobalServicesRegistry()
+        ServiceRegistry globalRegistry = ServiceRegistryBuilder.builder()
+                .parent(LoggingServiceRegistry.newEmbeddableLogging())
+                .parent(NativeServices.getInstance())
+                .provider(new GlobalScopeServices(false))
+                .build()
         StartParameter startParameter = new StartParameter()
         startParameter.gradleUserHomeDir = new IntegrationTestBuildContext().gradleUserHomeDir
-        TopLevelBuildServiceRegistry topLevelRegistry = new TopLevelBuildServiceRegistry(globalRegistry, startParameter)
-        ProjectInternalServiceRegistry projectRegistry = new ProjectInternalServiceRegistry(topLevelRegistry, HelperUtil.createRootProject())
-        projectRegistry.get(DependencyResolutionServices)
+        BuildScopeServices topLevelRegistry = new BuildScopeServices(globalRegistry, startParameter)
+        ProjectScopeServices projectRegistry = new ProjectScopeServices(topLevelRegistry, TestUtil.createRootProject())
+
+        stopLater.add(projectRegistry)
+        stopLater.add(topLevelRegistry)
+        stopLater.add(globalRegistry)
+
+        return projectRegistry.get(DependencyResolutionServices)
     }
 
     ToolingApiDistributionResolver withExternalToolingApiDistribution() {
         this.useExternalToolingApiDistribution = true
         this
     }
+
+    void stop() {
+        stopLater.stop()
+    }
 }
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 e0003cb..35cb671 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
@@ -24,12 +24,22 @@ import org.gradle.tooling.GradleConnector
 import org.gradle.util.GradleVersion
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
-import org.junit.internal.AssumptionViolatedException
 import org.junit.runner.RunWith
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
 
+/**
+ * A spec that executes tests against all compatible versions of tooling API consumer and provider, including the current Gradle version under test.
+ *
+ * <p>A test class or test method can be annotated with the following annotations to specify which versions the test is compatible with:
+ * </p>
+ *
+ * <ul>
+ *     <li>{@link ToolingApiVersion} - specifies the tooling API consumer versions that the test is compatible with.
+ *     <li>{@link TargetGradleVersion} - specifies the tooling API provider versions that the test is compatible with.
+ * </ul>
+ */
 @RunWith(ToolingApiCompatibilitySuiteRunner)
 abstract class ToolingApiSpecification extends Specification {
     static final Logger LOGGER = LoggerFactory.getLogger(ToolingApiSpecification)
@@ -52,10 +62,6 @@ abstract class ToolingApiSpecification extends Specification {
         def consumerGradle = GradleVersion.current()
         def target = GradleVersion.version(VERSION.get().version.version)
         LOGGER.info(" Using Tooling API consumer ${consumerGradle}, provider ${target}")
-        boolean accept = accept(consumerGradle, target)
-        if (!accept) {
-            throw new AssumptionViolatedException("Test class ${getClass().name} does not work with tooling API ${consumerGradle} and Gradle ${target}.")
-        }
         this.toolingApi.withConnector {
             if (consumerGradle.version != target.version) {
                 LOGGER.info("Overriding daemon tooling API provider to use installation: " + target);
@@ -65,11 +71,8 @@ abstract class ToolingApiSpecification extends Specification {
         }
     }
 
-    /**
-     * Returns true if this test class works with the given combination of tooling API consumer and provider.
-     */
-    protected boolean accept(GradleVersion toolingApi, GradleVersion targetGradle) {
-        return true
+    public void withConnector(Closure cl) {
+        toolingApi.withConnector(cl)
     }
 
     public <T> T withConnection(Closure<T> cl) {
@@ -102,10 +105,6 @@ abstract class ToolingApiSpecification extends Specification {
         toolingApi.connector()
     }
 
-    void maybeFailWithConnection(Closure cl) {
-        toolingApi.maybeFailWithConnection(cl)
-    }
-
     TestFile getProjectDir() {
         temporaryFolder.testDirectory
     }
@@ -114,6 +113,10 @@ abstract class ToolingApiSpecification extends Specification {
         file("build.gradle")
     }
 
+    TestFile getSettingsFile() {
+        file("settings.gradle")
+    }
+
     TestFile file(Object... path) {
         projectDir.file(path)
     }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiVersion.java
new file mode 100644
index 0000000..a05b648
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiVersion.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.integtests.tooling.fixture;
+
+import java.lang.annotation.*;
+
+/**
+ * Specifies the range of tooling API versions that the given tooling API test can work with.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Inherited
+public @interface ToolingApiVersion {
+    /**
+     * The requested tooling API version. Can use '>=nnn', '<=nnn', '<nnn', '=nnn', 'current' or '!current' or space-separated list of patterns.
+     */
+    String value();
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiLoggingCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiLoggingCrossVersionSpec.groovy
new file mode 100644
index 0000000..38ac731
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiLoggingCrossVersionSpec.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.m3
+
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.test.fixtures.ConcurrentTestUtil
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.ResultHandler
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+class ToolingApiLoggingCrossVersionSpec extends ToolingApiSpecification {
+    def setup() {
+        toolingApi.isEmbedded = false
+    }
+
+    def "logging is live"() {
+        def marker = file("marker.txt")
+
+        file("build.gradle") << """
+task log << {
+    println "waiting"
+    def marker = file('${marker.toURI()}')
+    long timeout = System.currentTimeMillis() + 10000
+    while (!marker.file && System.currentTimeMillis() < timeout) { Thread.sleep(200) }
+    if (!marker.file) { throw new RuntimeException("Timeout waiting for marker file") }
+    println "finished"
+}
+"""
+
+        when:
+        def resultHandler = new TestResultHandler()
+        def output = new TestOutputStream()
+        withConnection { ProjectConnection connection ->
+            def build = connection.newBuild()
+            build.standardOutput = output
+            build.forTasks("log")
+            build.run(resultHandler)
+            ConcurrentTestUtil.poll(10) { output.toString().contains("waiting") }
+            marker.text = 'go!'
+            resultHandler.finished()
+        }
+
+        then:
+        output.toString().contains("waiting")
+        output.toString().contains("finished")
+    }
+
+    class TestResultHandler implements ResultHandler<Object> {
+        final latch = new CountDownLatch(1)
+        def failure
+
+        void onComplete(Object result) {
+            latch.countDown()
+        }
+
+        void onFailure(GradleConnectionException failure) {
+            this.failure = failure
+            latch.countDown()
+        }
+
+        def finished() {
+            latch.await(10, TimeUnit.SECONDS)
+            if (failure != null) {
+                throw failure
+            }
+        }
+    }
+
+    class TestOutputStream extends OutputStream {
+        final buffer = new ByteArrayOutputStream()
+
+        @Override
+        void write(int b) throws IOException {
+            synchronized (buffer) {
+                buffer.write(b)
+            }
+        }
+
+        @Override
+        String toString() {
+            synchronized (buffer) {
+                return buffer.toString()
+            }
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
index e7350ca..2061b82 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
@@ -16,16 +16,13 @@
 
 package org.gradle.integtests.tooling.m4
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
 
-/**
- * @author: Szczepan Faber, created at: 6/11/11
- */
- at MinToolingApiVersion('1.0-milestone-4')
- at MinTargetGradleVersion('1.0-milestone-4')
+ at ToolingApiVersion('>=1.0-milestone-4')
+ at TargetGradleVersion('>=1.0-milestone-4')
 class ToolingApiEclipseLinkedResourcesCrossVersionSpec extends ToolingApiSpecification {
     def "can build linked resources"() {
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
index f9fdfd3..52a7e10 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.integtests.tooling.m4
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
 
- at MinTargetGradleVersion('1.0-milestone-4')
+ at TargetGradleVersion('>=1.0-milestone-4')
 class ToolingApiEclipseMinimalModelCrossVersionSpec extends ToolingApiSpecification {
     def "minimal Eclipse model does not attempt to resolve external dependencies"() {
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
index cc75efd..57ad728 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
@@ -16,12 +16,12 @@
 
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.eclipse.EclipseProject
 import spock.lang.Issue
 
- at MinTargetGradleVersion('1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class EclipseModelWithFlatRepoCrossVersionSpec extends ToolingApiSpecification {
 
     @Issue("GRADLE-1621")
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
index 5840fef..dbe9248 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
@@ -15,16 +15,16 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.BuildException
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.Task
 import org.gradle.tooling.model.eclipse.EclipseProject
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-5')
+ at ToolingApiVersion('>=1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class ToolingApiBuildExecutionCrossVersionSpec extends ToolingApiSpecification {
     def "can build the set of tasks for a project"() {
         file('build.gradle') << '''
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 3e00e10..d95aa5f 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
@@ -15,15 +15,15 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.eclipse.EclipseProject
 import spock.lang.Issue
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-5')
+ at ToolingApiVersion('>=1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class ToolingApiBuildableEclipseModelFixesCrossVersionSpec extends ToolingApiSpecification {
     @Issue("GRADLE-1529")
     //this is just one of the ways of fixing the problem. See the issue for details
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
index 88c223c..a511ac2 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -15,13 +15,11 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.eclipse.EclipseProject
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-3')
+ at ToolingApiVersion('>=1.0-milestone-5')
 class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
     def "eclipse project has access to gradle project and its tasks"() {
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
index 74a52b3..08cdd50 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
@@ -15,14 +15,12 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.GradleTask
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-3')
+ at ToolingApiVersion('>=1.0-milestone-5')
 class ToolingApiGradleProjectCrossVersionSpec extends ToolingApiSpecification {
 
     def "provides tasks of a project"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
index 49f4683..adbb978 100755
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.ProjectConnection
 import org.gradle.tooling.model.eclipse.EclipseProject
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-5')
+ at ToolingApiVersion('>=1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class ToolingApiHonorsProjectCustomizationsCrossVersionSpec extends ToolingApiSpecification {
 
     def "should honour reconfigured project names"() {
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 954aaee..9894ddd 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,14 +15,14 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.gradle.tooling.model.idea.*
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-5')
+ at ToolingApiVersion('>=1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class ToolingApiIdeaModelCrossVersionSpec extends ToolingApiSpecification {
 
     def "builds the model even if idea plugin not applied"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
index fc782a4..803a0f8 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.BuildException
 import org.gradle.tooling.model.GradleProject
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-5')
+ at ToolingApiVersion('>=1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class ToolingApiModelCrossVersionSpec extends ToolingApiSpecification {
     def "receives progress while the model is building"() {
         file('build.gradle') << '''
@@ -32,10 +32,10 @@ System.err.println 'this is stderr'
 
         when:
         def progress = withModel(GradleProject.class).progressMessages
+        progress.pop()
 
         then:
         progress.size() >= 2
-        progress.pop() == ''
         progress.every { it }
     }
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
index e7fd795..3820282 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
@@ -15,13 +15,13 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.model.GradleProject
 
- at MinToolingApiVersion('1.0-milestone-5')
- at MinTargetGradleVersion('1.0-milestone-5')
+ at ToolingApiVersion('>=1.0-milestone-5')
+ at TargetGradleVersion('>=1.0-milestone-5')
 class ToolingApiReceivingStandardStreamsCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/UnsupportedModelFeedbackCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/UnsupportedModelFeedbackCrossVersionSpec.groovy
new file mode 100644
index 0000000..2572087
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/UnsupportedModelFeedbackCrossVersionSpec.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.model.idea.BasicIdeaProject
+import org.gradle.tooling.model.idea.IdeaProject
+
+ at TargetGradleVersion('<=1.0-milestone-4')
+class UnsupportedModelFeedbackCrossVersionSpec extends ToolingApiSpecification {
+    def "fails gracefully when unsupported model requested"() {
+        when:
+        withConnection { it.getModel(model) }
+
+        then:
+        UnknownModelException e = thrown()
+        e.message.contains(model.simpleName)
+
+        where:
+        model << [IdeaProject, BasicIdeaProject]
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
index 14d9260..635bc0b 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
@@ -16,14 +16,14 @@
 
 package org.gradle.integtests.tooling.m8
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.build.BuildEnvironment
 
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-8')
+ at ToolingApiVersion('>=1.0-milestone-8')
+ at TargetGradleVersion('>=1.0-milestone-8')
 class BuildEnvironmentModelCrossVersionSpec extends ToolingApiSpecification {
 
     def "informs about build environment"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
index bf0e97c..3346da2 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.integtests.tooling.m8
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.ProjectConnection
 import org.gradle.tooling.model.GradleProject
 import spock.lang.Timeout
 
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-8')
+ at ToolingApiVersion('>=1.0-milestone-8')
+ at TargetGradleVersion('>=1.0-milestone-8')
 class ConsumingStandardInputCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesToolingApiCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesToolingApiCrossVersionSpec.groovy
deleted file mode 100644
index 4573b64..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesToolingApiCrossVersionSpec.groovy
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.tooling.m8
-
-import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
-import org.gradle.integtests.tooling.fixture.TextUtil
-import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.tooling.model.build.BuildEnvironment
-import spock.lang.IgnoreIf
-
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-8')
-class GradlePropertiesToolingApiCrossVersionSpec extends ToolingApiSpecification {
-
-    def setup() {
-        //this test does not make any sense in embedded mode
-        //as we don't own the process
-        toolingApi.isEmbedded = false
-    }
-
-    def "tooling api honours jvm args specified in gradle.properties"() {
-        file('build.gradle') << """
-assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
-assert System.getProperty('some-prop') == 'some-value'
-"""
-        file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
-
-        when:
-        BuildEnvironment env = toolingApi.withConnection { connection ->
-            connection.newBuild().run() //the assert
-            connection.getModel(BuildEnvironment.class)
-        }
-
-        then:
-        env.java.jvmArguments.contains('-Xmx16m')
-    }
-
-    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
-    def "tooling api honours java home specified in gradle.properties"() {
-        File javaHome = AvailableJavaHomes.bestAlternative
-        String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
-
-        file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
-
-        file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
-
-        when:
-        BuildEnvironment env = toolingApi.withConnection { connection ->
-            connection.newBuild().run() //the assert
-            connection.getModel(BuildEnvironment.class)
-        }
-
-        then:
-        env.java.javaHome == javaHome
-    }
-}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
index d822b72..e2d4468 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
@@ -16,16 +16,16 @@
 
 package org.gradle.integtests.tooling.m8
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.build.BuildEnvironment
 import spock.lang.Issue
 import spock.lang.Timeout
 
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-8')
+ at ToolingApiVersion('>=1.0-milestone-8')
+ at TargetGradleVersion('>=1.0-milestone-8')
 class JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
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 c9e400c..65ec7a0 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
@@ -17,19 +17,15 @@
 package org.gradle.integtests.tooling.m8
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.integtests.tooling.fixture.MaxTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
-import org.gradle.tooling.model.UnsupportedMethodException
 import org.gradle.tooling.model.build.BuildEnvironment
-import org.gradle.tooling.model.internal.Exceptions
 import spock.lang.IgnoreIf
 
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-3')
- at MaxTargetGradleVersion('1.0-milestone-7') //the configuration was not supported for old versions
+ at ToolingApiVersion('>=1.0-milestone-8')
+ at TargetGradleVersion('<=1.0-milestone-7')
 class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
@@ -42,7 +38,7 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
     def "fails eagerly when java home unsupported for model"() {
         def java = AvailableJavaHomes.bestAlternative
         when:
-        maybeFailWithConnection {
+        withConnection {
             def model = it.model(BuildEnvironment.class)
             model.setJavaHome(java)
             model.get()
@@ -57,7 +53,7 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
     def "fails eagerly when java home unsupported for build"() {
         def java = AvailableJavaHomes.bestAlternative
         when:
-        maybeFailWithConnection {
+        withConnection {
             def build = it.newBuild()
             build.setJavaHome(java)
             build.forTasks('tasks').run()
@@ -70,7 +66,7 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
 
     def "fails eagerly when java args unsupported"() {
         when:
-        maybeFailWithConnection {
+        withConnection {
             def model = it.model(BuildEnvironment.class)
             model.setJvmArguments("-Xmx512m")
             model.get()
@@ -83,7 +79,7 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
 
     def "fails eagerly when standard input unsupported"() {
         when:
-        maybeFailWithConnection {
+        withConnection {
             def model = it.model(BuildEnvironment.class)
             model.setStandardInput(new ByteArrayInputStream('yo!'.bytes))
             model.get()
@@ -95,11 +91,6 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
     }
 
     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)
-
-        //we don't really need that exception as a cause
-        //but one of the versions was released with it as a cause so to keep things compatible
-        assert actual.cause instanceof UnsupportedMethodException
     }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
index 68a2b9e..df96e11 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -15,15 +15,15 @@
  */
 package org.gradle.integtests.tooling.m8
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.eclipse.EclipseProject
 
- at MinToolingApiVersion('1.0-milestone-3')
- at MinTargetGradleVersion('1.0-milestone-8')
+ at ToolingApiVersion('>=1.0-milestone-3')
+ at TargetGradleVersion('>=1.0-milestone-8')
 class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
-    def "can customise model late in the configuration phase"() {
+    def "can customize model late in the configuration phase"() {
         projectDir.file('build.gradle').text = """
 apply plugin: 'java'
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
index 39ec358..f8c78ac 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
@@ -16,13 +16,14 @@
 
 package org.gradle.integtests.tooling.m8
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.internal.consumer.ConnectorServices
+import org.junit.Assume
 
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-8')
+ at ToolingApiVersion('>=1.0-milestone-8')
+ at TargetGradleVersion('>=1.0-milestone-8')
 class ToolingApiLoggingCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
@@ -35,7 +36,7 @@ class ToolingApiLoggingCrossVersionSpec extends ToolingApiSpecification {
         new ConnectorServices().reset()
     }
 
-    def "logs necessary information when verbose"() {
+    def "client receives same stdout and stderr when in verbose mode as if running from the command-line in debug mode"() {
         toolingApi.verboseLogging = true
 
         file("build.gradle") << """
@@ -73,42 +74,52 @@ project.logger.debug("debug logging yyy");
         shouldNotContainProviderLogging(err)
     }
 
-    def "logs necessary information"() {
+    def "client receives same standard output and standard error as if running from the command-line"() {
+        Assume.assumeTrue targetDist.toolingApiNonAsciiOutputSupported
         toolingApi.verboseLogging = false
 
         file("build.gradle") << """
-System.err.println "sys err logging xxx"
+System.err.println "System.err \u03b1\u03b2"
 
-println "println logging yyy"
+println "System.out \u03b1\u03b2"
 
-project.logger.error("error logging xxx");
-project.logger.warn("warn logging yyy");
-project.logger.lifecycle("lifecycle logging yyy");
-project.logger.quiet("quiet logging yyy");
-project.logger.info ("info logging yyy");
-project.logger.debug("debug logging yyy");
+project.logger.error("error logging \u03b1\u03b2");
+project.logger.warn("warn logging");
+project.logger.lifecycle("lifecycle logging \u03b1\u03b2");
+project.logger.quiet("quiet logging");
+project.logger.info ("info logging");
+project.logger.debug("debug logging");
 """
         when:
+        def commandLineResult = targetDist.executer(temporaryFolder).run();
+
+        and:
         def op = withBuild()
 
         then:
         def out = op.standardOutput
-        out.count("debug logging yyy") == 0
-        out.count("info logging yyy") == 0
-        out.count("quiet logging yyy") == 1
-        out.count("lifecycle logging yyy") == 1
-        out.count("warn logging yyy") == 1
-        out.count("println logging yyy") == 1
-        out.count("error logging xxx") == 0
-
-        shouldNotContainProviderLogging(out)
-
         def err = op.standardError
-        err.count("error logging") == 1
-        err.count("sys err") == 1
-        err.count("logging yyy") == 0
+        normaliseOutput(filterToolingApiSpecific(out)) == normaliseOutput(commandLineResult.output)
+        err == commandLineResult.error
+
+        and:
+        err.count("System.err \u03b1\u03b2") == 1
+        err.count("error logging \u03b1\u03b2") == 1
+
+        and:
+        out.count("lifecycle logging \u03b1\u03b2") == 1
+        out.count("warn logging") == 1
+        out.count("quiet logging") == 1
+        out.count("info") == 0
+        out.count("debug") == 0
+    }
 
-        shouldNotContainProviderLogging(err)
+    String normaliseOutput(String output) {
+        return output.replaceFirst("Total time: .+ secs", "Total time: 0 secs")
+    }
+
+    String filterToolingApiSpecific(String output) {
+        return output.replaceFirst("Connection from tooling API older than version 1.2 has been deprecated and is scheduled to be removed in Gradle 2.0" + System.getProperty("line.separator"), "")
     }
 
     void shouldNotContainProviderLogging(String output) {
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
deleted file mode 100644
index 413c4fc..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/UnknownModelFeedbackCrossVersionSpec.groovy
+++ /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.integtests.tooling.m8
-
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
-import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.tooling.UnknownModelException
-
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-3')
-class UnknownModelFeedbackCrossVersionSpec extends ToolingApiSpecification {
-
-    class UnknownModel {}
-
-    def "fails gracefully when building unknown model"() {
-        //this test exposes a daemon issue that appears in M5 and M6
-        //basically when we ask to build a model for a an unknown type for given provider
-        //the daemon breaks and does not return anything to the client making the client waiting forever.
-
-        when:
-        maybeFailWithConnection { it.getModel(UnknownModel.class) }
-
-        then:
-        UnknownModelException e = thrown()
-        e.message.contains('Unknown model: \'UnknownModel\'.')
-    }
-}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
index b8757d2..18ff125 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
@@ -16,16 +16,14 @@
 
 package org.gradle.integtests.tooling.m8
 
-import org.gradle.integtests.tooling.fixture.MaxTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.model.UnsupportedMethodException
 import org.gradle.tooling.model.build.BuildEnvironment
 
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-3')
- at MaxTargetGradleVersion('1.0-milestone-7')
+ at ToolingApiVersion('>=1.0-milestone-8')
+ at TargetGradleVersion('<=1.0-milestone-7')
 class VersionOnlyBuildEnvironmentCrossVersionSpec extends ToolingApiSpecification {
 
     def "informs about version"() {
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 6ccde3d..151d34a 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
@@ -16,15 +16,15 @@
 
 package org.gradle.integtests.tooling.m9
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.GradleConnectionException
 import spock.lang.Issue
 import spock.lang.Timeout
 
- at MinToolingApiVersion('1.0-milestone-9')
- at MinTargetGradleVersion('1.0-milestone-9')
+ at ToolingApiVersion('>=1.0-milestone-9')
+ at TargetGradleVersion('>=1.0-milestone-9')
 class DaemonErrorFeedbackCrossVersionSpec extends ToolingApiSpecification {
 
     @Issue("GRADLE-1799")
@@ -35,7 +35,7 @@ class DaemonErrorFeedbackCrossVersionSpec extends ToolingApiSpecification {
         toolingApi.isEmbedded = false
 
         when:
-        maybeFailWithConnection {
+        withConnection {
             it.newBuild()
                     .setJvmArguments("-Xasdf")
                     .run()
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/GradlePropertiesToolingApiCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/GradlePropertiesToolingApiCrossVersionSpec.groovy
new file mode 100644
index 0000000..fa5672f
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/GradlePropertiesToolingApiCrossVersionSpec.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.m9
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.TextUtil
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.model.build.BuildEnvironment
+import spock.lang.IgnoreIf
+
+ at ToolingApiVersion('>=1.0-milestone-9')
+ at TargetGradleVersion('>=1.0-milestone-9')
+class GradlePropertiesToolingApiCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //this test does not make any sense in embedded mode
+        //as we don't own the process
+        toolingApi.isEmbedded = false
+    }
+
+    def "tooling api honours jvm args specified in gradle.properties"() {
+        file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
+assert System.getProperty('some-prop') == 'some-value'
+"""
+        file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
+
+        when:
+        BuildEnvironment env = toolingApi.withConnection { connection ->
+            connection.newBuild().run() //the assert
+            connection.getModel(BuildEnvironment.class)
+        }
+
+        then:
+        env.java.jvmArguments.contains('-Xmx16m')
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "tooling api honours java home specified in gradle.properties"() {
+        File javaHome = AvailableJavaHomes.bestAlternative
+        String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
+
+        file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
+
+        file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
+
+        when:
+        BuildEnvironment env = toolingApi.withConnection { connection ->
+            connection.newBuild().run() //the assert
+            connection.getModel(BuildEnvironment.class)
+        }
+
+        then:
+        env.java.javaHome == javaHome
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy
index 9e3961a..94b183f 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
@@ -17,10 +17,10 @@
 package org.gradle.integtests.tooling.m9
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.TextUtil
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.GradleConnectionException
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.build.BuildEnvironment
@@ -29,8 +29,8 @@ import spock.lang.IgnoreIf
 import spock.lang.Issue
 import spock.lang.Timeout
 
- at MinToolingApiVersion('1.0-milestone-9')
- at MinTargetGradleVersion('1.0-milestone-8')
+ at ToolingApiVersion('>=1.0-milestone-9')
+ at TargetGradleVersion('>=1.0-milestone-9')
 class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
@@ -58,7 +58,7 @@ class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
         def dummyJdk = file("wrong jdk location").createDir()
 
         when:
-        maybeFailWithConnection {
+        withConnection {
             it.newBuild().setJavaHome(dummyJdk).run()
         }
 
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 3495b2c..5252f14 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
@@ -18,15 +18,15 @@
 
 package org.gradle.integtests.tooling.r10rc1
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.ProjectConnection
 import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
 import org.gradle.tooling.model.GradleProject
 
- at MinToolingApiVersion("1.0-rc-1")
- at MinTargetGradleVersion("1.0-rc-2")
+ at ToolingApiVersion(">=1.0")
+ at TargetGradleVersion(">=1.0")
 class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecification {
 
 //    We don't want to validate *all* command line options here, just enough to make sure passing through works.
@@ -107,7 +107,7 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "gives decent feedback for invalid option"() {
         when:
-        maybeFailWithConnection { ProjectConnection it ->
+        withConnection { ProjectConnection it ->
             it.newBuild().withArguments('--foreground').run()
         }
 
@@ -144,12 +144,11 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
         noExceptionThrown()
     }
 
-    def "can overwrite searchUpwards via build arguments"() {
+    def "can configure searchUpwards via build arguments"() {
         given:
         file('build.gradle') << "assert !gradle.startParameter.searchUpwards"
 
         when:
-        toolingApi.withConnector { it.searchUpwards(true) }
         withConnection {
             it.newBuild().withArguments('-u').run()
         }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/BuildInvocationsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/BuildInvocationsCrossVersionSpec.groovy
new file mode 100644
index 0000000..de6b15a
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/BuildInvocationsCrossVersionSpec.groovy
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.BuildLauncher
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.GradleTask
+import org.gradle.tooling.model.Launchable
+import org.gradle.tooling.model.Task
+import org.gradle.tooling.model.TaskSelector
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.tooling.model.gradle.BuildInvocations
+
+ at ToolingApiVersion(">=1.12")
+class BuildInvocationsCrossVersionSpec extends ToolingApiSpecification {
+    def setup() {
+        settingsFile << '''
+include 'a'
+include 'b'
+include 'b:c'
+rootProject.name = 'test'
+'''
+        buildFile << '''
+task t1 << {
+    println "t1 in $project.name"
+}
+
+project(':b') {
+    task t3 << {
+        println "t3 in $project.name"
+    }
+    task t2 << {
+        println "t2 in $project.name"
+    }
+}
+
+project(':b:c') {
+    task t1 << {
+        println "t1 in $project.name"
+    }
+    task t2 << {
+        println "t2 in $project.name"
+    }
+}'''
+    }
+
+    @TargetGradleVersion(">=1.8 <=1.11")
+    def "no task selectors when running action in older container"() {
+        when:
+        withConnection { connection -> connection.action(new FetchAllTaskSelectorsBuildAction()).run() }
+
+        then:
+        Exception e = thrown()
+        e.cause.message.startsWith('No model of type \'BuildInvocations\' is available in this build.')
+    }
+
+    @TargetGradleVersion(">=1.12")
+    def "can request task selectors in action"() {
+        when:
+        Map<String, Set<String>> result = withConnection { connection ->
+            connection.action(new FetchAllTaskSelectorsBuildAction()).run() }
+
+        then:
+        result != null
+        result.keySet() == ['test', 'a', 'b', 'c'] as Set
+        result['test'] == ['t1', 't2', 't3'] as Set
+        result['b'] == ['t1', 't2', 't3'] as Set
+        result['c'] == ['t1', 't2'] as Set
+        result['a'].isEmpty()
+    }
+
+    @TargetGradleVersion(">=1.12")
+    def "build task selectors from action"() {
+        given:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        when:
+        BuildInvocations projectSelectors = withConnection { connection ->
+            connection.action(new FetchTaskSelectorsBuildAction('b')).run() }
+        TaskSelector selector = projectSelectors.taskSelectors.find { it -> it.name == 't1'}
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(selector)
+        }
+
+        then:
+        result.result.assertTasksExecuted(':b:c:t1')
+
+        when:
+        BuildInvocations rootProjectSelectors = withConnection { connection ->
+            connection.action(new FetchTaskSelectorsBuildAction('test')).run() }
+        TaskSelector rootSelector = rootProjectSelectors.taskSelectors.find { it -> it.name == 't1'}
+        withBuild { BuildLauncher it ->
+            it.forLaunchables(selector, rootSelector)
+        }
+
+        then:
+        UnsupportedBuildArgumentException e = thrown()
+        e.message.contains('Problem with provided launchable arguments')
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "build task selectors from connection"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        BuildInvocations model = withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+        TaskSelector selector = model.taskSelectors.find { TaskSelector it ->
+            it.name == 't1'
+        }
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(selector)
+        }
+
+        then:
+        result.result.assertTasksExecuted(':t1', ':b:c:t1')
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "build task selectors from connection in specified order"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        BuildInvocations model = withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+        TaskSelector selectorT1 = model.taskSelectors.find {  it.name == 't1' }
+        TaskSelector selectorT2 = model.taskSelectors.find { it.name == 't2' }
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(selectorT1, selectorT2)
+        }
+        then:
+        result.result.assertTasksExecuted(':t1', ':b:c:t1', ':b:t2', ':b:c:t2')
+
+        when:
+        result = withBuild { BuildLauncher it ->
+            it.forLaunchables(selectorT2, selectorT1)
+        }
+        then:
+        result.result.assertTasksExecuted(':b:t2', ':b:c:t2', ':t1', ':b:c:t1')
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "can request task selectors for project"() {
+        given:
+        BuildInvocations model = withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+
+        when:
+        def selectors = model.taskSelectors.findAll { TaskSelector it ->
+            !it.description.startsWith(':') && it.name != 'setupBuild' // synthetic task in 1.6
+        }
+        then:
+        selectors*.name as Set == ['t1', 't2', 't3'] as Set
+    }
+
+    @TargetGradleVersion("<1.0-milestone-5")
+    def "cannot request BuildInvocations for old project"() {
+        when:
+        withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+
+        then:
+        UnknownModelException e = thrown()
+        e.message.contains('does not support building a model of type \'' + BuildInvocations.simpleName + '\'')
+    }
+
+    @TargetGradleVersion(">=1.12")
+    def "get tasks for projects"() {
+        when:
+        List<Task> tasks = withConnection { connection ->
+            connection.action(new FetchTasksBuildAction(':b')).run()
+        }
+
+        then:
+        tasks.size() == 2
+        tasks*.name as Set == ['t2', 't3'] as Set
+
+        when:
+        tasks[0].project
+        then:
+        UnsupportedMethodException e = thrown()
+        e != null
+    }
+
+    @TargetGradleVersion(">=1.12")
+    def "build tasks from BuildInvocations model as Launchable"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        List<Task> tasks = withConnection { connection ->
+            connection.action(new FetchTasksBuildAction(':b')).run()
+        }
+        Launchable task = tasks.find { it -> it.name == 't2'}
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(task)
+        }
+
+        then:
+        result.result.assertTasksExecuted(':b:t2')
+        result.result.assertTaskNotExecuted(':b:c:t2')
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "build task from connection as Launchable"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        BuildInvocations model = withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+        Task task = model.tasks.find { Task it ->
+            it.name == 't1'
+        }
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(task)
+        }
+
+        then:
+        result.result.assertTasksExecuted(':t1')
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "build tasks Launchables in order"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        GradleProject model = withConnection { connection ->
+            connection.getModel(GradleProject)
+        }
+        GradleTask taskT1 = model.tasks.find { it.name == 't1' }
+        GradleTask taskBT2 = model.findByPath(':b').tasks.find { it.name == 't2' }
+        GradleTask taskBCT1 = model.findByPath(':b:c').tasks.find { it.name == 't1' }
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(taskT1, taskBT2, taskBCT1)
+        }
+        then:
+        result.result.assertTasksExecuted(':t1', ':b:t2', ':b:c:t1')
+
+        when:
+        result = withBuild { BuildLauncher it ->
+            it.forLaunchables(taskBCT1, taskBT2, taskT1)
+        }
+        then:
+        result.result.assertTasksExecuted(':b:c:t1', ':b:t2', ':t1')
+    }
+
+    @TargetGradleVersion(">=1.12")
+    def "build tasks and selectors in order"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        GradleProject model = withConnection { connection ->
+            connection.getModel(GradleProject)
+        }
+        GradleTask taskT1 = model.tasks.find { it.name == 't1' }
+        BuildInvocations bSelectors = withConnection { connection ->
+            connection.action(new FetchTaskSelectorsBuildAction('b')).run()
+        }
+        TaskSelector selectorBT1 = bSelectors.taskSelectors.find { it.name == 't1' }
+        TaskSelector selectorBT3 = bSelectors.taskSelectors.find { it.name == 't3' }
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(selectorBT1, selectorBT3, taskT1)
+        }
+        then:
+        result.result.assertTasksExecuted(':b:c:t1', ':b:t3', ':t1')
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "build tasks and selectors in order cross version"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        GradleProject model = withConnection { connection ->
+            connection.getModel(GradleProject)
+        }
+        GradleTask taskT1 = model.tasks.find { it.name == 't1' }
+        GradleTask taskBT2 = model.findByPath(':b').tasks.find { it.name == 't2' }
+        BuildInvocations rootSelectors = withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+        TaskSelector selectorT1 = rootSelectors.taskSelectors.find { it.name == 't1' }
+        TaskSelector selectorT2 = rootSelectors.taskSelectors.find { it.name == 't2' }
+        def result = withBuild { BuildLauncher it ->
+            it.forLaunchables(taskT1, selectorT1, selectorT2, taskBT2)
+        }
+        then:
+        result.result.assertTasksExecuted(':t1', ':b:c:t1', ':b:t2', ':b:c:t2')
+    }
+
+    @TargetGradleVersion(">=1.12")
+    def "build fails with selectors from different projects"() {
+        when:
+        toolingApi.isEmbedded = false // to load launchables using correct classloader in integTest
+        BuildInvocations rootSelectors = withConnection { connection ->
+            connection.action(new FetchTaskSelectorsBuildAction('test')).run()
+        }
+        BuildInvocations bSelectors = withConnection { connection ->
+            connection.action(new FetchTaskSelectorsBuildAction('b')).run()
+        }
+        TaskSelector selectorT1 = rootSelectors.taskSelectors.find { it.name == 't1' }
+        TaskSelector selectorBT1 = bSelectors.taskSelectors.find { it.name == 't1' }
+        TaskSelector selectorBT3 = bSelectors.taskSelectors.find { it.name == 't3' }
+        withBuild { BuildLauncher it ->
+            it.forLaunchables(selectorBT1, selectorBT3, selectorT1)
+        }
+        then:
+        UnsupportedBuildArgumentException e = thrown()
+        e.message.contains 'Only selector from the same Gradle project can be built.'
+    }
+
+    @TargetGradleVersion(">=1.0-milestone-5")
+    def "can request tasks for root project"() {
+        // TODO make sure it is for root project if default project is different
+
+        given:
+        BuildInvocations model = withConnection { connection ->
+            connection.getModel(BuildInvocations)
+        }
+
+        expect:
+        model.tasks.count { it.name != 'setupBuild' } == 1
+
+        when:
+        def task = model.tasks.find { Task it -> it.name != 'setupBuild' }
+
+        then:
+        task.name == 't1'
+        task.path == ':t1'
+
+        when:
+        task.project
+
+        then:
+        UnsupportedMethodException e = thrown()
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchAllTaskSelectorsBuildAction.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchAllTaskSelectorsBuildAction.java
new file mode 100644
index 0000000..933c4ec
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchAllTaskSelectorsBuildAction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.TaskSelector;
+import org.gradle.tooling.model.gradle.BasicGradleProject;
+import org.gradle.tooling.model.gradle.BuildInvocations;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class FetchAllTaskSelectorsBuildAction implements BuildAction<Map<String, Set<String>>> {
+    public Map<String, Set<String>> execute(BuildController controller) {
+        Map<String, Set<String>> model = new HashMap<String, Set<String>>();
+        for (BasicGradleProject project: controller.getBuildModel().getProjects()) {
+            BuildInvocations entryPointsForProject = controller.getModel(project, BuildInvocations.class);
+            Set<String> selectorNames = new HashSet<String>();
+            for (TaskSelector selector : entryPointsForProject.getTaskSelectors()) {
+                selectorNames.add(selector.getName());
+            }
+            model.put(project.getName(), selectorNames);
+        }
+        return model;
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchTaskSelectorsBuildAction.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchTaskSelectorsBuildAction.java
new file mode 100644
index 0000000..a388ae5
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchTaskSelectorsBuildAction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.gradle.BasicGradleProject;
+import org.gradle.tooling.model.gradle.BuildInvocations;
+
+public class FetchTaskSelectorsBuildAction implements BuildAction<BuildInvocations> {
+    private final String projectName;
+
+    public FetchTaskSelectorsBuildAction(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public BuildInvocations execute(BuildController controller) {
+        for (BasicGradleProject project: controller.getBuildModel().getProjects()) {
+            if (project.getName().equals(projectName)) {
+                return controller.getModel(project, BuildInvocations.class);
+            }
+        }
+        return null;
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchTasksBuildAction.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchTasksBuildAction.java
new file mode 100644
index 0000000..693ca63
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/FetchTasksBuildAction.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.Task;
+import org.gradle.tooling.model.gradle.BasicGradleProject;
+import org.gradle.tooling.model.gradle.BuildInvocations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FetchTasksBuildAction implements BuildAction<List<Task>> {
+    private final String projectPath;
+
+    FetchTasksBuildAction(String projectPath) {
+        this.projectPath = projectPath;
+    }
+
+    public List<Task> execute(BuildController controller) {
+        BasicGradleProject project = null;
+        for (BasicGradleProject p : controller.getBuildModel().getProjects()) {
+            if (p.getPath().equals(projectPath)) {
+                project = p;
+                break;
+            }
+        }
+
+        return new ArrayList<Task>(controller.getModel(project, BuildInvocations.class).getTasks());
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/PublicationsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/PublicationsCrossVersionSpec.groovy
new file mode 100644
index 0000000..90fae77
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/PublicationsCrossVersionSpec.groovy
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.model.gradle.ProjectPublications
+
+ at ToolingApiVersion('>=1.12')
+ at TargetGradleVersion('>=1.12')
+class PublicationsCrossVersionSpec extends ToolingApiSpecification {
+    def "project without any configured publications"() {
+        buildFile << "apply plugin: 'java'"
+
+        when:
+        ProjectPublications publications = withConnection { connection ->
+            connection.getModel(ProjectPublications)
+        }
+
+        then:
+        publications.publications.empty
+    }
+
+    def "Ivy repository based publication"() {
+        settingsFile << "rootProject.name = 'test.project'"
+        buildFile <<
+"""
+apply plugin: "base"
+
+version = 1.0
+group = "test.group"
+
+uploadArchives {
+    repositories {
+        ivy { url uri("\$buildDir/ivy-repo") }
+    }
+}
+"""
+
+        when:
+        ProjectPublications publications = withConnection { connection ->
+            connection.getModel(ProjectPublications)
+        }
+
+        then:
+        publications.publications.size() == 1
+        with(publications.publications.iterator().next()) {
+            id.group == "test.group"
+            id.name == "test.project"
+            id.version == "1.0"
+        }
+    }
+
+    def "Maven repository based publication with coordinates inferred from project"() {
+        settingsFile << "rootProject.name = 'test.project'"
+        buildFile <<
+"""
+apply plugin: "maven"
+
+version = 1.0
+group = "test.group"
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("\$buildDir/maven-repo"))
+        }
+    }
+}
+"""
+
+        when:
+        ProjectPublications publications = withConnection { connection ->
+            connection.getModel(ProjectPublications)
+        }
+
+        then:
+        publications.publications.size() == 1
+        with(publications.publications.iterator().next()) {
+            id.group == "test.group"
+            id.name == "test.project"
+            id.version == "1.0"
+        }
+    }
+
+    def "Maven repository based publication with coordinates inferred from POM configuration"() {
+        settingsFile << "rootProject.name = 'test.project'"
+        buildFile <<
+                """
+apply plugin: "maven"
+
+version = 1.0
+group = "test.group"
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("\$buildDir/maven-repo"))
+            pom.groupId = "test.groupId"
+            pom.artifactId = "test.artifactId"
+            pom.version = "1.1"
+        }
+    }
+}
+"""
+
+        when:
+        ProjectPublications publications = withConnection { connection ->
+            connection.getModel(ProjectPublications)
+        }
+
+        then:
+        publications.publications.size() == 1
+        with(publications.publications.iterator().next()) {
+            id.group == "test.groupId"
+            id.name == "test.artifactId"
+            id.version == "1.1"
+        }
+    }
+
+    def "publishing.publications based publication"() {
+        settingsFile << "rootProject.name = 'test.project'"
+        buildFile <<
+                """
+apply plugin: "ivy-publish"
+apply plugin: "maven-publish"
+apply plugin: "java"
+
+version = 1.0
+group = "test.group"
+
+publishing {
+    repositories {
+        ivy { url uri("\$buildDir/ivy-repo") }
+        maven { url uri("\$buildDir/maven-repo") }
+    }
+    publications {
+        mainIvy(IvyPublication) {
+            from components.java
+            organisation 'test.org'
+            module 'test-module'
+            revision '1.1'
+        }
+        mainMaven(MavenPublication) {
+            from components.java
+            groupId 'test.groupId'
+            artifactId 'test-artifactId'
+            version '1.2'
+        }
+    }
+}
+"""
+
+        when:
+        ProjectPublications publications = withConnection { connection ->
+            connection.getModel(ProjectPublications)
+        }
+
+        then:
+        publications.publications.size() == 2
+
+        and:
+        def pub1 = publications.publications.find { it.id.group == "test.org" }
+        pub1 != null
+        pub1.id.name == "test-module"
+        pub1.id.version == "1.1"
+
+        and:
+        def pub2 = publications.publications.find { it.id.group == "test.groupId" }
+        pub2 != null
+        pub2.id.name == "test-artifactId"
+        pub2.id.version == "1.2"
+    }
+
+    @TargetGradleVersion('<1.12')
+    def "decent error message for Gradle version that doesn't expose publications"() {
+        when:
+        ProjectPublications publications = withConnection { connection ->
+            connection.getModel(ProjectPublications)
+        }
+        publications.publications
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message.contains('does not support building a model of type \'ProjectPublications\'.') || 
+        e.message.contains('No model of type \'ProjectPublications\' is available in this build.')
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/TaskDisplayNameCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/TaskDisplayNameCrossVersionSpec.groovy
new file mode 100644
index 0000000..bf9c62d
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/TaskDisplayNameCrossVersionSpec.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.model.GradleProject
+
+ at ToolingApiVersion('>=1.12')
+class TaskDisplayNameCrossVersionSpec extends ToolingApiSpecification {
+    @TargetGradleVersion('>=1.0-milestone-5') // Task.path is broken on 1.0-m3
+    def "can get task's display name"() {
+        file('build.gradle') << '''
+task a
+task b { description = 'this is task b' }
+'''
+
+        when:
+        GradleProject project = withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        def taskA = project.tasks.find { it.name == 'a' }
+        taskA != null
+        taskA.path == ':a'
+        taskA.displayName == /task ':a'/
+        taskA.description == null
+        taskA.project == project
+
+        def taskB = project.tasks.find { it.name == 'b' }
+        taskB != null
+        taskB.path == ':b'
+        taskB.displayName == /task ':b'/
+        taskB.description == 'this is task b'
+        taskB.project == project
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/TestFilteringCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/TestFilteringCrossVersionSpec.groovy
new file mode 100644
index 0000000..d74d829
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/TestFilteringCrossVersionSpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import spock.lang.Issue
+
+ at ToolingApiVersion(">=1.0")
+ at TargetGradleVersion(">=1.10")
+class TestFilteringCrossVersionSpec extends ToolingApiSpecification {
+    @Issue("GRADLE-2972")
+    def "tooling api support test filtering when tasks configured via command line"() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.11' }
+            compileTestJava.options.fork = true
+        """
+
+        file("src/test/java/FooTest.java") << """
+            public class FooTest {
+                @org.junit.Test public void passes() {}
+                @org.junit.Test public void fails() { throw new RuntimeException("Boo!"); }
+            }
+        """
+
+        when:
+        withConnection { it.newBuild().withArguments('test', '--tests', 'FooTest.passes').run() }
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/ToolingApiDeprecationsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/ToolingApiDeprecationsCrossVersionSpec.groovy
new file mode 100644
index 0000000..9758f89
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/ToolingApiDeprecationsCrossVersionSpec.groovy
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r112
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.eclipse.EclipseProject
+
+class ToolingApiDeprecationsCrossVersionSpec extends ToolingApiSpecification {
+    def setup() {
+        file("build.gradle") << """
+task noop << {
+    println "noop"
+}
+"""
+    }
+
+    @ToolingApiVersion(">=1.12")
+    @TargetGradleVersion("<1.0-milestone-8")
+    def "build shows deprecation warning for pre 1.0m8 providers"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def build = connection.newBuild()
+            build.standardOutput = output
+            build.forTasks("noop")
+            build.run()
+        }
+
+        then:
+        output.toString().contains(deprecationMessageProvider(targetDist.version.version))
+    }
+
+    @ToolingApiVersion(">=1.12")
+    @TargetGradleVersion("<1.0-milestone-8")
+    def "model retrieving shows deprecation warning for pre 1.0m8 providers"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def modelBuilder = connection.model(EclipseProject)
+            modelBuilder.standardOutput = output
+            modelBuilder.get()
+        }
+
+        then:
+        output.toString().contains(deprecationMessageProvider(targetDist.version.version))
+    }
+
+    @ToolingApiVersion(">=1.12")
+    @TargetGradleVersion(">=1.0-milestone-8")
+    def "build shows no deprecation warning for 1.0m8+ providers"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def build = connection.newBuild()
+            build.standardOutput = output
+            build.forTasks("noop")
+            build.run()
+        }
+
+        then:
+        !output.toString().contains(deprecationMessageProvider(targetDist.version.version))
+    }
+
+    @ToolingApiVersion(">=1.12")
+    @TargetGradleVersion(">=1.0-milestone-8")
+    def "model retrieving shows no deprecation warning for 1.0m8+ providers"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def modelBuilder = connection.model(GradleProject)
+            modelBuilder.standardOutput = output
+            modelBuilder.get()
+        }
+
+        then:
+        !output.toString().contains(deprecationMessageProvider(targetDist.version.version))
+    }
+
+    def deprecationMessageProvider(def version) {
+        "Connecting to Gradle version " + version + " from the Gradle tooling API has been deprecated and is scheduled to be removed in version 2.0 of the Gradle tooling API"
+    }
+
+    @ToolingApiVersion("<1.2")
+    @TargetGradleVersion(">=1.12")
+    def "provider shows deprecation warning when build is requested by old toolingApi"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def build = connection.newBuild()
+            build.standardOutput = output
+            build.forTasks("noop")
+            build.run()
+        }
+
+        then:
+        output.toString().contains(deprecationMessageApi(targetDist.version.version))
+    }
+
+    @ToolingApiVersion(">=1.2")
+    @TargetGradleVersion(">=1.12")
+    def "provider shows no deprecation warning when build is requested by supported toolingApi"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def build = connection.newBuild()
+            build.standardOutput = output
+            build.forTasks("noop")
+            build.run()
+        }
+
+        then:
+        !output.toString().contains(deprecationMessageApi(targetDist.version.version))
+    }
+
+    @ToolingApiVersion("<1.2")
+    @TargetGradleVersion(">=1.12")
+    def "provider shows deprecation warning when model is requested by old toolingApi"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def modelBuilder = connection.model(EclipseProject)
+            modelBuilder.standardOutput = output
+            modelBuilder.get()
+        }
+
+        then:
+        output.toString().contains(deprecationMessageApi(targetDist.version.version))
+    }
+
+    @ToolingApiVersion(">=1.2")
+    @TargetGradleVersion(">=1.12")
+    def "provider shows no deprecation warning when model is requested by supported toolingApi"() {
+        when:
+        def output = new ByteArrayOutputStream()
+        withConnection { ProjectConnection connection ->
+            def modelBuilder = connection.model(EclipseProject)
+            modelBuilder.standardOutput = output
+            modelBuilder.get()
+        }
+
+        then:
+        !output.toString().contains(deprecationMessageApi(targetDist.version.version))
+    }
+
+    def deprecationMessageApi(def version) {
+        "Connection from tooling API older than version 1.2 has been deprecated and is scheduled to be removed in Gradle 2.0"
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/UserHomeDirCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/UserHomeDirCrossVersionSpec.groovy
new file mode 100644
index 0000000..1237d37
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r112/UserHomeDirCrossVersionSpec.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.tooling.r112
+
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.BuildLauncher
+
+class UserHomeDirCrossVersionSpec extends ToolingApiSpecification {
+    def "build is executed using specified user home directory"() {
+        File userHomeDir = temporaryFolder.createDir('userhomedir')
+        projectDir.file('settings.gradle') << 'rootProject.name="test"'
+        projectDir.file('build.gradle') << """task gradleBuild << {
+    logger.lifecycle 'userHomeDir=' + gradle.gradleUserHomeDir
+}
+"""
+        ByteArrayOutputStream baos = new ByteArrayOutputStream()
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.useGradleUserHomeDir(userHomeDir)
+        }
+        // TODO radim: consider using smaller heap and shorter timeout when applicable to all supported versions
+        toolingApi.withConnection { connection ->
+            BuildLauncher build = connection.newBuild();
+            build.forTasks("gradleBuild");
+            build.standardOutput = baos
+            build.run()
+        }
+        def output = baos.toString("UTF-8")
+
+        then:
+        output.contains('userHomeDir=' + userHomeDir.absolutePath)
+    }
+}
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 997fc35..9997424 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,16 +15,16 @@
  */
 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.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.gradle.tooling.model.ExternalDependency
 import org.gradle.tooling.model.eclipse.EclipseProject
 import org.gradle.tooling.model.idea.IdeaProject
 
- at MinToolingApiVersion('1.1-rc-2')
- at MinTargetGradleVersion('1.1-rc-2')
+ at ToolingApiVersion('>=1.1')
+ at TargetGradleVersion('>=1.1')
 class DependencyMetaDataCrossVersionSpec extends ToolingApiSpecification {
 
     def "idea libraries contain gradle module information"() {
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
index bf87dac..eb314c2 100644
--- 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
@@ -16,14 +16,14 @@
 
 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.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 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")
+ at ToolingApiVersion(">=1.2")
+ at TargetGradleVersion(">=1.2")
 class BuildModelCrossVersionSpec extends ToolingApiSpecification {
     def "can run tasks before building Eclipse model"() {
         file('build.gradle').text = '''
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
index bdefe22..86f58a7 100644
--- 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
@@ -16,14 +16,14 @@
 
 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.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 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")
+ at ToolingApiVersion(">=1.2")
+ at TargetGradleVersion(">=1.2")
 class ProjectOutcomesModuleCrossVersionSpec extends ToolingApiSpecification {
     def "modelContainsAllArchivesOnTheArchivesConfiguration"() {
         given:
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
index 01dde7b..9c5ed54 100644
--- 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
@@ -16,19 +16,19 @@
 
 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.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 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")
+ at ToolingApiVersion(">=1.2")
+ at TargetGradleVersion("<=1.1")
 class UnsupportedOperationFeedbackCrossVersionSpec extends ToolingApiSpecification {
     def "fails when attempting to run tasks when building a model"() {
         when:
-        maybeFailWithConnection { ProjectConnection connection ->
+        withConnection { ProjectConnection connection ->
             connection.model(EclipseProject.class).forTasks('eclipse').get()
         }
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy
index b72c830..3eb919a 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.integtests.tooling.r14
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.test.fixtures.file.TestFile
@@ -26,7 +26,7 @@ import spock.lang.Issue
 /**
  * Tests that init scripts are used from the _clients_ GRADLE_HOME, not the daemon server's.
  */
- at MinTargetGradleVersion('1.4')
+ at TargetGradleVersion('>=1.4')
 @Issue("http://issues.gradle.org/browse/GRADLE-2408")
 class ToolingApiInitScriptCrossVersionIntegrationTest extends ToolingApiSpecification {
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy
index 1abe493..9393c5b 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy
@@ -20,13 +20,13 @@
 
 package org.gradle.integtests.tooling.r15
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import spock.lang.Issue
 
- at MinToolingApiVersion("1.0")
- at MinTargetGradleVersion("1.5")
+ at ToolingApiVersion(">=1.0")
+ at TargetGradleVersion(">=1.5")
 class CombiningCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecification {
 
     @Issue("GRADLE-2635")
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy
index daaf1e3..661fe84 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy
@@ -18,13 +18,13 @@
 
 package org.gradle.integtests.tooling.r15
 
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.model.GradleProject
 
- at MinToolingApiVersion("1.0-milestone-5") //because we acquire GradleProject model
- at MinTargetGradleVersion("1.5")
+ at ToolingApiVersion(">=1.0-milestone-5") //because we acquire GradleProject model
+ at TargetGradleVersion(">=1.5")
 class ToolingApiConfigurationOnDemandCrossVersionSpec extends ToolingApiSpecification {
 
     def setup() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/CustomModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/CustomModel.java
new file mode 100644
index 0000000..1c32d4f
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/CustomModel.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.integtests.tooling.r16;
+
+import java.util.Map;
+import java.util.Set;
+
+public interface CustomModel {
+    String getValue();
+
+    Set<Thing> getThings();
+
+    Map<String, Thing> getThingsByName();
+
+    interface Thing {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/CustomToolingModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/CustomToolingModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..0aa67b2
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/CustomToolingModelCrossVersionSpec.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r16
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import spock.lang.Ignore
+
+ at ToolingApiVersion(">=1.6")
+ at TargetGradleVersion(">=1.6")
+class CustomToolingModelCrossVersionSpec extends ToolingApiSpecification {
+    def "plugin can contribute a custom tooling model"() {
+        file('build.gradle') << """
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+import org.gradle.tooling.provider.model.ToolingModelBuilder
+import javax.inject.Inject
+
+apply plugin: CustomPlugin
+
+class CustomModel implements Serializable {
+    String getValue() { 'greetings' }
+    Set<CustomThing> getThings() { return [new CustomThing()] }
+    Map<String, CustomThing> getThingsByName() { return [thing: new CustomThing()] }
+}
+class CustomThing implements Serializable {
+}
+class CustomBuilder implements ToolingModelBuilder {
+    boolean canBuild(String modelName) {
+        return modelName == '${CustomModel.name}'
+    }
+    Object buildAll(String modelName, Project project) {
+        return new CustomModel()
+    }
+}
+class CustomPlugin implements Plugin<Project> {
+    @Inject
+    CustomPlugin(ToolingModelBuilderRegistry registry) {
+        registry.register(new CustomBuilder())
+    }
+
+    public void apply(Project project) {
+    }
+}
+"""
+
+        when:
+        def model = withConnection { connection ->
+            connection.model(CustomModel).get()
+        }
+
+        then:
+        model.value == 'greetings'
+        model.things.find { it instanceof CustomModel.Thing }
+        model.thingsByName.thing instanceof CustomModel.Thing
+    }
+
+    @Ignore("work in progress")
+    def "gives reasonable error message when model build fails"() {
+        expect: false
+    }
+
+    @Ignore("work in progress")
+    def "gives reasonable error message when model cannot be transported to consumer"() {
+        expect: false
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/UnknownCustomModelFeedbackCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/UnknownCustomModelFeedbackCrossVersionSpec.groovy
new file mode 100644
index 0000000..69c4cc3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r16/UnknownCustomModelFeedbackCrossVersionSpec.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r16
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.tooling.UnknownModelException
+
+class UnknownCustomModelFeedbackCrossVersionSpec extends ToolingApiSpecification {
+    @ToolingApiVersion("current")
+    @TargetGradleVersion(">=1.6")
+    def "fails gracefully when unknown model requested when custom models are supported by the target version"() {
+        when:
+        withConnection { it.getModel(CustomModel.class) }
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == "No model of type 'CustomModel' is available in this build."
+    }
+
+    @ToolingApiVersion("current")
+    @TargetGradleVersion("<1.6")
+    def "fails gracefully when unknown model requested when custom models are not supported by the target version"() {
+        when:
+        withConnection { it.getModel(CustomModel.class) }
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == "The version of Gradle you are using (${targetDist.version.version}) does not support building a model of type 'CustomModel'. Support for building custom tooling models was added in Gradle 1.6 and is available in all later versions."
+    }
+
+    @ToolingApiVersion("!current")
+    @TargetGradleVersion("current")
+    def "fails gracefully when unknown model requested by old tooling API version"() {
+        when:
+        withConnection { it.getModel(CustomModel.class) }
+
+        then:
+        GradleConnectionException e = thrown()
+        e.message.contains('CustomModel')
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BrokenAction.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BrokenAction.java
new file mode 100644
index 0000000..796d8d3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BrokenAction.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+
+class BrokenAction implements BuildAction<String> {
+    public String execute(BuildController controller) {
+        throw new CustomException();
+    }
+
+    static class CustomException extends RuntimeException {
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy
new file mode 100644
index 0000000..7417a13
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.BuildActionFailureException
+import org.gradle.tooling.BuildException
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.model.idea.IdeaProject
+
+ at ToolingApiVersion('>=1.8')
+ at TargetGradleVersion('>=1.8')
+class BuildActionCrossVersionSpec extends ToolingApiSpecification {
+    def "client receives the result of running a build action"() {
+        given:
+        file("settings.gradle") << 'rootProject.name="hello-world"'
+
+        when:
+        CustomModel customModel = withConnection { it.action(new FetchCustomModel()).run() }
+
+        then:
+        customModel.gradle.name == "hello-world"
+        customModel.eclipse.gradleProject.name == "hello-world"
+
+        when:
+        IdeaProject ideaModel = withConnection { it.action(new FetchIdeaModel()).run() }
+
+        then:
+        ideaModel.name == "hello-world"
+        ideaModel.modules.size() == 1
+
+        when:
+        def nullModel = withConnection { it.action(new NullAction()).run() }
+
+        then:
+        nullModel == null
+    }
+
+    def "client receives the exception thrown by the build action"() {
+        when:
+        withConnection { it.action(new BrokenAction()).run() }
+
+        then:
+        BuildActionFailureException e = thrown()
+        e.message == /The supplied build action failed with an exception./
+        e.cause instanceof BrokenAction.CustomException
+    }
+
+    def "client receives the exception thrown when action requests unknown model"() {
+        when:
+        withConnection { it.action(new FetchUnknownModel()).run() }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "client receives the exception thrown when build fails"() {
+        given:
+        buildFile << 'throw new RuntimeException("broken")'
+
+        when:
+        withConnection { it.action(new FetchCustomModel()).run() }
+
+        then:
+        // TODO:ADAM - clean this up
+        BuildException e = thrown()
+        e.message.startsWith('Could not run build action using')
+    }
+
+    def causes(Throwable throwable) {
+        def causes = []
+        for (def c = throwable.cause; c != null; c = c.cause) {
+            causes << c
+        }
+        return causes
+    }
+
+    @ToolingApiVersion('current')
+    @TargetGradleVersion('<1.8')
+    def "gives reasonable error message when target Gradle version does not support build actions"() {
+        when:
+        withConnection { it.action(new FetchCustomModel()).run() }
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == "The version of Gradle you are using (${targetDist.version.version}) does not support execution of build actions provided by the tooling API client. Support for this was added in Gradle 1.8 and is available in all later versions."
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BuildScriptModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BuildScriptModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..d9b4da8
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/BuildScriptModelCrossVersionSpec.groovy
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.idea.IdeaProject
+
+ at ToolingApiVersion('>=1.8')
+ at TargetGradleVersion('>=1.8')
+class BuildScriptModelCrossVersionSpec extends ToolingApiSpecification {
+    def "GradleProject provides details about the project's build script"() {
+        when:
+        buildFile << '//empty'
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        project.buildScript.sourceFile == buildFile
+
+        when:
+        def custom = file('gradle/my-project.gradle') << '//empty'
+        file('settings.gradle') << "rootProject.buildFileName = 'gradle/my-project.gradle'"
+        project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        project.buildScript.sourceFile == custom
+    }
+
+    @ToolingApiVersion('current')
+    @TargetGradleVersion('<1.8 >=1.0-milestone-5')
+    def "gives reasonable error message when target Gradle version does not provide build script details"() {
+        when:
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+        project.buildScript
+
+        then:
+        UnsupportedMethodException e = thrown()
+        e.message.startsWith('Unsupported method: GradleProject.getBuildScript().')
+
+        when:
+        EclipseProject eclipseProject = withConnection { it.getModel(EclipseProject.class) }
+        eclipseProject.gradleProject.buildScript
+
+        then:
+        e = thrown()
+        e.message.startsWith('Unsupported method: GradleProject.getBuildScript().')
+
+        when:
+        IdeaProject ideaProject = withConnection { it.getModel(IdeaProject.class) }
+        ideaProject.modules.each { it.gradleProject.buildScript }
+
+        then:
+        e = thrown()
+        e.message.startsWith('Unsupported method: GradleProject.getBuildScript().')
+    }
+
+    @ToolingApiVersion('current')
+    @TargetGradleVersion('<1.0-milestone-5')
+    def "gives reasonable error message when build script details not available via Eclipse model"() {
+        when:
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+        project.buildScript
+
+        then:
+        UnsupportedMethodException e = thrown()
+        e.message.startsWith('Unsupported method: GradleProject.getBuildScript().')
+
+        when:
+        EclipseProject eclipseProject = withConnection { it.getModel(EclipseProject.class) }
+        eclipseProject.gradleProject.buildScript
+
+        then:
+        e = thrown()
+        e.message.startsWith('Unsupported method: GradleProject.getBuildScript().')
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/CustomModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/CustomModel.java
new file mode 100644
index 0000000..c14fbd5
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/CustomModel.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+
+import java.io.Serializable;
+
+class CustomModel implements Serializable {
+    EclipseProject eclipse;
+    GradleProject gradle;
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchCustomModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchCustomModel.java
new file mode 100644
index 0000000..93d2189
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchCustomModel.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+
+class FetchCustomModel implements BuildAction<CustomModel> {
+    public CustomModel execute(BuildController controller) {
+        CustomModel model = new CustomModel();
+        model.gradle = controller.getModel(GradleProject.class);
+        model.eclipse = controller.getModel(EclipseProject.class);
+        return model;
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchIdeaModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchIdeaModel.java
new file mode 100644
index 0000000..8294742
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchIdeaModel.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.idea.IdeaProject;
+
+public class FetchIdeaModel implements BuildAction<IdeaProject> {
+    public IdeaProject execute(BuildController controller) {
+        return controller.getModel(IdeaProject.class);
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchUnknownModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchUnknownModel.java
new file mode 100644
index 0000000..ff946f0
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/FetchUnknownModel.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.UnknownModelException;
+
+public class FetchUnknownModel implements BuildAction<CustomModel> {
+    public CustomModel execute(BuildController controller) {
+        try {
+            controller.getModel(CustomModel.class);
+            throw new AssertionError("Expected model request to fail.");
+        } catch (UnknownModelException e) {
+            return null;
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/GradleBuildModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/GradleBuildModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..bfbf92a
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/GradleBuildModelCrossVersionSpec.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.tooling.model.gradle.GradleBuild
+
+ at ToolingApiVersion(">=1.8")
+class GradleBuildModelCrossVersionSpec extends ToolingApiSpecification {
+    def setup() {
+        file('settings.gradle') << '''
+include 'a'
+include 'b'
+include 'b:c'
+rootProject.name = 'test'
+'''
+        buildFile << """
+allprojects {
+    description = "project \$name"
+    task buildStuff
+}
+"""
+    }
+
+    @TargetGradleVersion("<1.8")
+    def "can request GradleBuild model"() {
+        when:
+        GradleBuild model = withConnection { connection -> connection.getModel(GradleBuild) }
+
+        then:
+        validateModel(model)
+
+        when:
+        model.rootProject.projectDirectory
+
+        then:
+        def e = thrown(UnsupportedMethodException)
+    }
+
+    @TargetGradleVersion(">=1.8")
+    def "can request GradleBuild model including projectDirectory"() {
+        when:
+        GradleBuild model = withConnection { connection -> connection.getModel(GradleBuild) }
+
+        then:
+        validateModel(model)
+        model.projects*.projectDirectory == [projectDir, file('a'), file('b'), file('b/c')]
+    }
+
+    def validateModel(GradleBuild model) {
+        assert model.rootProject.name == 'test'
+        assert model.rootProject.path == ':'
+        assert model.rootProject.parent == null
+        assert model.rootProject.children.size() == 2
+        assert model.rootProject.children.every { it.parent == model.rootProject }
+        assert model.projects*.name == ['test', 'a', 'b', 'c']
+        assert model.projects*.path == [':', ':a', ':b', ':b:c']
+        model
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/NullAction.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/NullAction.java
new file mode 100644
index 0000000..edc5ab5
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/NullAction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+
+public class NullAction implements BuildAction<Object> {
+    public Object execute(BuildController controller) {
+        return null;
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/ProjectLevelModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/ProjectLevelModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..e65dc47
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/ProjectLevelModelCrossVersionSpec.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18
+
+import org.gradle.integtests.tooling.fixture.TargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.integtests.tooling.r16.CustomModel
+
+ at ToolingApiVersion(">=1.8")
+class ProjectLevelModelCrossVersionSpec extends ToolingApiSpecification {
+    def setup() {
+        file('settings.gradle') << '''
+include 'a'
+include 'b'
+include 'b:c'
+rootProject.name = 'test'
+'''
+        file('b').mkdirs()
+
+        buildFile << """
+allprojects {
+    description = "project \$name"
+    apply plugin: CustomPlugin
+    task buildStuff
+}
+
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+import org.gradle.tooling.provider.model.ToolingModelBuilder
+import javax.inject.Inject
+
+class CustomModel implements Serializable {
+    String value
+}
+class CustomBuilder implements ToolingModelBuilder {
+    boolean canBuild(String modelName) {
+        return modelName == 'org.gradle.integtests.tooling.r16.CustomModel'
+    }
+    Object buildAll(String modelName, Project project) {
+        return new CustomModel(value: project.path)
+    }
+}
+class CustomPlugin implements Plugin<Project> {
+    @Inject
+    CustomPlugin(ToolingModelBuilderRegistry registry) {
+        registry.register(new CustomBuilder())
+    }
+
+    public void apply(Project project) {
+    }
+}
+"""
+    }
+
+    @TargetGradleVersion(">=1.8")
+    def "can use build model to request models for individual projects"() {
+        when:
+        Map<String, CustomModel> result = withConnection { connection -> connection.action(new UseGradleBuildToFetchProjectModel()).run() }
+
+        then:
+        result != null
+        result.keySet() == ['test', 'a', 'b', 'c'] as Set
+        result.values()*.value as Set == [':', ':a', ':b', ':b:c'] as Set
+
+        when:
+        withConnector { connector ->
+            connector.searchUpwards(true)
+            connector.forProjectDirectory(file("b"))
+        }
+        result = withConnection { connection -> connection.action(new UseGradleBuildToFetchProjectModel()).run() }
+
+        then:
+        result != null
+        result.keySet() == ['test', 'a', 'b', 'c'] as Set
+        result.values()*.value as Set == [':', ':a', ':b', ':b:c'] as Set
+
+        when:
+        file('gradle.properties') << 'org.gradle.configureondemand=true'
+        result = withConnection { connection -> connection.action(new UseGradleBuildToFetchProjectModel()).run() }
+
+        then:
+        result != null
+        result.keySet() == ['test', 'a', 'b', 'c'] as Set
+        result.values()*.value as Set == [':', ':a', ':b', ':b:c'] as Set
+    }
+
+    @TargetGradleVersion(">=1.8")
+    def "can request models using various element types"() {
+        when:
+        Map<String, CustomModel> result = withConnection { connection -> connection.action(new UseOtherTypesToFetchProjectModel()).run() }
+
+        then:
+        result != null
+        result.keySet() == ['test', 'a', 'b', 'c'] as Set
+        result.values()*.value as Set == [':', ':a', ':b', ':b:c'] as Set
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/UseGradleBuildToFetchProjectModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/UseGradleBuildToFetchProjectModel.java
new file mode 100644
index 0000000..b43de3a
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/UseGradleBuildToFetchProjectModel.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.integtests.tooling.r16.CustomModel;
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.gradle.BasicGradleProject;
+import org.gradle.tooling.model.gradle.GradleBuild;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class UseGradleBuildToFetchProjectModel implements BuildAction<Map<String, CustomModel>> {
+    public Map<String, CustomModel> execute(BuildController controller) {
+        GradleBuild gradleBuild = controller.getBuildModel();
+        Map<String, CustomModel> projects = new HashMap<String, CustomModel>();
+        for (BasicGradleProject project : gradleBuild.getProjects()) {
+            projects.put(project.getName(), controller.getModel(project, CustomModel.class));
+        }
+        return projects;
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/UseOtherTypesToFetchProjectModel.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/UseOtherTypesToFetchProjectModel.java
new file mode 100644
index 0000000..64be729
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r18/UseOtherTypesToFetchProjectModel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r18;
+
+import org.gradle.integtests.tooling.r16.CustomModel;
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.HierarchicalElement;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+import org.gradle.tooling.model.idea.IdeaModule;
+import org.gradle.tooling.model.idea.IdeaProject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class UseOtherTypesToFetchProjectModel implements BuildAction<Map<String, CustomModel>> {
+    public Map<String, CustomModel> execute(BuildController controller) {
+        // Use an IdeaModule to reference a project
+        IdeaProject ideaProject = controller.getModel(IdeaProject.class);
+        for (IdeaModule ideaModule : ideaProject.getModules()) {
+            visit(ideaModule, controller, new HashMap<String, CustomModel>());
+        }
+
+        // Use an EclipseProject to reference a project
+        EclipseProject eclipseProject = controller.getModel(EclipseProject.class);
+        visit(eclipseProject, controller, new HashMap<String, CustomModel>());
+
+        // Use a GradleProject to reference a project
+        GradleProject rootProject = controller.getModel(GradleProject.class);
+        Map<String, CustomModel> projects = new HashMap<String, CustomModel>();
+        visit(rootProject, controller, projects);
+        return projects;
+    }
+
+    void visit(HierarchicalElement element, BuildController buildController, Map<String, CustomModel> results) {
+        results.put(element.getName(), buildController.getModel(element, CustomModel.class));
+        for (HierarchicalElement child : element.getChildren()) {
+            visit(child, buildController, results);
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildAction.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildAction.java
new file mode 100644
index 0000000..d9f0bbf
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildAction.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling;
+
+import org.gradle.api.Incubating;
+
+import java.io.Serializable;
+
+/**
+ * An action that executes against a Gradle build and produces a result of type {@code T}.
+ *
+ * <p>You can execute a {@code BuildAction} using the {@link ProjectConnection#action(BuildAction)} method.</p>
+ *
+ * @param <T> The type of result produced by this action.
+ * @since 1.8
+ */
+ at Incubating
+public interface BuildAction<T> extends Serializable {
+    /**
+     * Executes this action and returns the result.
+     *
+     * @param controller The controller to use to access and control the build.
+     * @return The result
+     * @since 1.8
+     */
+    T execute(BuildController controller);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionExecuter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionExecuter.java
new file mode 100644
index 0000000..dfd7d5c
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionExecuter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
+
+/**
+ * Used to execute a {@link BuildAction} in the build process.
+ *
+ * @param <T> The type of result produced by this executer.
+ * @since 1.8
+ */
+ at Incubating
+public interface BuildActionExecuter<T> extends LongRunningOperation {
+    /**
+     * Runs the action, blocking until its result is available.
+     *
+     * @throws UnsupportedVersionException When the target Gradle version does not support build action execution.
+     * @throws UnsupportedOperationConfigurationException
+     *          When the target Gradle version does not support some requested configuration option such as
+     *          {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
+     *          {@link #setJvmArguments(String...)}.
+     * @throws UnsupportedBuildArgumentException When there is a problem with build arguments provided by {@link #withArguments(String...)}.
+     * @throws BuildActionFailureException When the build action fails with an exception.
+     * @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.8
+     */
+    T run() throws GradleConnectionException, IllegalStateException, UnsupportedOperationConfigurationException, UnsupportedVersionException, UnsupportedBuildArgumentException, BuildException, BuildActionFailureException;
+
+    /**
+     * Starts executing the action, passing the result to the given handler when complete. This method returns immediately, and the result is later passed to the given handler's {@link
+     * ResultHandler#onComplete(Object)} method.
+     *
+     * <p>If the operation fails, the handler's {@link ResultHandler#onFailure(GradleConnectionException)} method is called with the appropriate exception. See
+     * {@link #run()} for a description of the various exceptions that the operation may fail with.
+     *
+     * @param handler The handler to supply the result to.
+     * @throws IllegalStateException When the connection has been closed or is closing.
+     * @since 1.8
+     */
+    void run(ResultHandler<? super T> handler) throws IllegalStateException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionFailureException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionFailureException.java
new file mode 100644
index 0000000..845e1c5
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionFailureException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Thrown when a {@link BuildAction} fails.
+ *
+ * @since 1.8
+ */
+ at Incubating
+public class BuildActionFailureException extends GradleConnectionException {
+    public BuildActionFailureException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildController.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildController.java
new file mode 100644
index 0000000..0f1cfd7
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+import org.gradle.tooling.model.gradle.GradleBuild;
+import org.gradle.tooling.model.Model;
+
+/**
+ * Provides a {@link BuildAction} various ways to control a Gradle build and access information about the build.
+ *
+ * @since 1.8
+ */
+ at Incubating
+public interface BuildController {
+    /**
+     * Fetches a snapshot of the model of the given type for the default project. The default project is generally the
+     * project referenced when a {@link ProjectConnection} is created.
+     *
+     * <p>Any of following models types may be available, depending on the version of Gradle being used by the target
+     * build:
+     *
+     * <ul>
+     *     <li>{@link GradleBuild}</li>
+     *     <li>{@link org.gradle.tooling.model.build.BuildEnvironment}</li>
+     *     <li>{@link org.gradle.tooling.model.GradleProject}</li>
+     *     <li>{@link org.gradle.tooling.model.gradle.BuildInvocations}</li>
+     *     <li>{@link org.gradle.tooling.model.gradle.ProjectPublications}</li>
+     *     <li>{@link org.gradle.tooling.model.idea.IdeaProject}</li>
+     *     <li>{@link org.gradle.tooling.model.idea.BasicIdeaProject}</li>
+     *     <li>{@link org.gradle.tooling.model.eclipse.EclipseProject}</li>
+     *     <li>{@link org.gradle.tooling.model.eclipse.HierarchicalEclipseProject}</li>
+     * </ul>
+     *
+     * <p>A build may also expose additional custom tooling models. You can use this method to query these models.
+     *
+     * @param modelType The model type.
+     * @param <T> The model type.
+     * @return The model.
+     * @throws UnknownModelException When the default project does not support the requested model.
+     *
+     * @since 1.8
+     */
+    <T> T getModel(Class<T> modelType) throws UnknownModelException;
+
+    /**
+     * Fetches a snapshot of the model of the given type, if available.
+     *
+     * <p>See {@link #getModel(Class)} for more details.</p>
+     *
+     * @param modelType The model type.
+     * @param <T> The model type.
+     * @return The model, or null if not present.
+     */
+    @Nullable
+    <T> T findModel(Class<T> modelType);
+
+    /**
+     * Returns an overview of the Gradle build, including some basic details of the projects that make up the build.
+     * This is equivalent to calling {@code #getModel(GradleBuild.class)}.
+     *
+     * @return The model.
+     */
+    GradleBuild getBuildModel();
+
+    /**
+     * Fetches a snapshot of the model of the given type for the given element, usually a Gradle project.
+     *
+     * <p>The following elements are supported:
+     *
+     * <ul>
+     *     <li>Any {@link org.gradle.tooling.model.gradle.BasicGradleProject}</li>
+     *     <li>Any {@link org.gradle.tooling.model.GradleProject}</li>
+     *     <li>Any {@link org.gradle.tooling.model.eclipse.EclipseProject}</li>
+     *     <li>Any {@link org.gradle.tooling.model.idea.IdeaModule}</li>
+     * </ul>
+     *
+     * <p>See {@link #getModel(Class)} for more details.
+     *
+     * @param target The target element, usually a project.
+     * @param modelType The model type.
+     * @param <T> The model type.
+     * @return The model.
+     * @throws UnknownModelException When the target project does not support the requested model.
+     */
+    <T> T getModel(Model target, Class<T> modelType) throws UnknownModelException;
+
+    /**
+     * Fetches a snapshot of the model of the given type, if available.
+     *
+     * <p>See {@link #getModel(Model, Class)} for more details.</p>
+     *
+     * @param modelType The model type.
+     * @param <T> The model type.
+     * @return The model, or null if not present.
+     */
+    @Nullable
+    <T> T findModel(Model target, Class<T> modelType);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildException.java
index 2b912fa..57fcaa7 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildException.java
@@ -16,7 +16,9 @@
 package org.gradle.tooling;
 
 /**
- * Thrown when a Gradle build fails, or when a model cannot be built.
+ * Thrown when a Gradle build fails or when a model cannot be built.
+ *
+ * @since 1.0-milestone-3
  */
 public class BuildException extends GradleConnectionException {
     public BuildException(String message, Throwable throwable) {
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 4b73adb..341cb33 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
@@ -15,7 +15,10 @@
  */
 package org.gradle.tooling;
 
+import org.gradle.api.Incubating;
 import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
+import org.gradle.tooling.model.Launchable;
 import org.gradle.tooling.model.Task;
 
 import java.io.File;
@@ -104,8 +107,28 @@ public interface BuildLauncher extends LongRunningOperation {
     BuildLauncher forTasks(Iterable<? extends Task> tasks);
 
     /**
+     * Sets the launchables to execute. If no entries are specified, the project's default tasks are executed.
+     *
+     * @param launchables The launchables for this build.
+     * @return this
+     * @since 1.12
+     */
+    @Incubating
+    BuildLauncher forLaunchables(Launchable... launchables);
+
+    /**
+     * Sets the launchables to execute. If no entries are specified, the project's default tasks are executed.
+     *
+     * @param launchables The launchables for this build.
+     * @return this
+     * @since 1.12
+     */
+    @Incubating
+    BuildLauncher forLaunchables(Iterable<? extends Launchable> launchables);
+
+    /**
      * {@inheritDoc}
-     * @since 1.0-rc-1
+     * @since 1.0
      */
     BuildLauncher withArguments(String ... arguments);
 
@@ -148,23 +171,26 @@ public interface BuildLauncher extends LongRunningOperation {
     /**
      * Executes the build, blocking until it is complete.
      *
-     * @throws UnsupportedVersionException When the target Gradle version does not support the features required for this build.
-     * @throws org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
-     *          when you have configured the long running operation with a settings
-     *          like: {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
-     *          {@link #setJvmArguments(String...)} but those settings are not supported on the target Gradle.
+     * @throws UnsupportedVersionException When the target Gradle version does not support build execution.
+     * @throws UnsupportedOperationConfigurationException
+     *          When the target Gradle version does not support some requested configuration option such as
+     *          {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
+     *          {@link #setJvmArguments(String...)}.
+     * @throws UnsupportedBuildArgumentException When there is a problem with build arguments provided by {@link #withArguments(String...)}.
      * @throws BuildException On some failure executing the Gradle build.
      * @throws GradleConnectionException On some other failure using the connection.
-     * @throws UnsupportedBuildArgumentException When there is a problem with build arguments provided by {@link #withArguments(String...)}
      * @throws IllegalStateException When the connection has been closed or is closing.
      * @since 1.0-milestone-3
      */
     void run() throws GradleConnectionException, UnsupportedBuildArgumentException, IllegalStateException,
-            BuildException, UnsupportedVersionException;
+            BuildException, UnsupportedVersionException, UnsupportedOperationConfigurationException;
 
     /**
      * Launches the build. This method returns immediately, and the result is later passed to the given handler.
      *
+     * <p>If the operation fails, the handler's {@link ResultHandler#onFailure(GradleConnectionException)}
+     * method is called with the appropriate exception. See {@link #run()} for a description of the various exceptions that the operation may fail with.
+     *
      * @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
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnectionException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnectionException.java
index a9ffbe5..6e6c55d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnectionException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnectionException.java
@@ -17,6 +17,8 @@ package org.gradle.tooling;
 
 /**
  * Thrown when there is some problem using a Gradle connection.
+ *
+ * @since 1.0-milestone-3
  */
 public class GradleConnectionException extends RuntimeException {
     public GradleConnectionException(String message) {
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 856f2dd..7866b8a 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
@@ -29,9 +29,10 @@ import java.io.OutputStream;
  * <p>
  * Allows providing standard input that can be consumed by the gradle operation (useful for interactive builds).
  * <p>
- * Enables configuring the build run / model request with options like the Java home or jvm arguments.
+ * 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 {
@@ -40,7 +41,7 @@ public interface LongRunningOperation {
      * Sets the {@link java.io.OutputStream} which should receive standard output logging generated while running the operation.
      * The default is to discard the output.
      *
-     * @param outputStream The output stream.
+     * @param outputStream The output stream. The system default character encoding will be used to encode characters written to this stream.
      * @return this
      * @since 1.0-milestone-7
      */
@@ -50,31 +51,27 @@ public interface LongRunningOperation {
      * Sets the {@link OutputStream} which should receive standard error logging generated while running the operation.
      * The default is to discard the output.
      *
-     * @param outputStream The output stream.
+     * @param outputStream The output stream. The system default character encoding will be used to encode characters written to this stream.
      * @return this
      * @since 1.0-milestone-7
      */
     LongRunningOperation setStandardError(OutputStream outputStream);
 
     /**
-     * If the target Gradle version supports it you can use this setting
-     * to set the standard {@link java.io.InputStream} that will be used by builds.
-     * Useful when the tooling api drives interactive builds.
+     * Sets the {@link java.io.InputStream} that will be used as standard input for this operation.
+     * Defaults to an empty input stream.
      * <p>
-     * If the target Gradle version does not support it the long running operation will fail eagerly with
+     * If the target Gradle version does not support it the long running operation will fail with
      * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
-     * <p>
-     * If not configured or null passed the dummy input stream with zero bytes is used to avoid the build hanging problems.
      *
      * @param inputStream The input stream
      * @return this
-     * @since 1.0-milestone-7
+     * @since 1.0-milestone-8
      */
     LongRunningOperation setStandardInput(InputStream inputStream);
 
     /**
-     * If the target Gradle version supports it you can use this setting
-     * to specify the Java home directory to use for the long running operation.
+     * Specifies the Java home directory to use for this operation.
      * <p>
      * If the target Gradle version does not support it the long running operation will fail eagerly with
      * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
@@ -92,8 +89,7 @@ public interface LongRunningOperation {
     LongRunningOperation setJavaHome(File javaHome) throws IllegalArgumentException;
 
     /**
-     * If the target Gradle version supports it you can use this setting
-     * to specify the Java vm arguments to use for the long running operation.
+     * Specifies the Java VM arguments to use for this operation.
      * <p>
      * If the target Gradle version does not support it the long running operation will fail eagerly with
      * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
@@ -136,7 +132,7 @@ public interface LongRunningOperation {
      *
      * @param arguments Gradle command line arguments
      * @return this
-     * @since 1.0-rc-1
+     * @since 1.0
      */
     LongRunningOperation withArguments(String ... arguments);
 
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 942d571..645901b 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
@@ -16,14 +16,15 @@
 package org.gradle.tooling;
 
 import org.gradle.api.Incubating;
-import org.gradle.tooling.model.Model;
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
- * A {@code ModelBuilder} allows you to fetch a snapshot of the model for a project.
+ * A {@code ModelBuilder} allows you to fetch a snapshot of some model for a project or a build.
  * Instances of {@code ModelBuilder} are not thread-safe.
  * <p>
  * You use a {@code ModelBuilder} as follows:
@@ -67,11 +68,11 @@ import java.io.OutputStream;
  * @param <T> The type of model to build
  * @since 1.0-milestone-3
  */
-public interface ModelBuilder<T extends Model> extends LongRunningOperation {
+public interface ModelBuilder<T> extends LongRunningOperation {
 
     /**
      * {@inheritDoc}
-     * @since 1.0-rc-1
+     * @since 1.0
      */
     ModelBuilder<T> withArguments(String ... arguments);
 
@@ -126,20 +127,26 @@ public interface ModelBuilder<T extends Model> extends LongRunningOperation {
      * Fetch the model, blocking until it is available.
      *
      * @return The model.
-     * @throws UnsupportedVersionException When the target Gradle version does not support the features required to build this model.
-     * @throws org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
-     *          when you have configured the long running operation with a settings
-     *          like: {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
-     *          {@link #setJvmArguments(String...)} but those settings are not supported on the target Gradle.
+     * @throws UnsupportedVersionException When the target Gradle version does not support building models.
+     * @throws UnknownModelException When the target Gradle version or build does not support the requested model.
+     * @throws UnsupportedOperationConfigurationException
+     *          When the target Gradle version does not support some requested configuration option such as
+     *          {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
+     *          {@link #setJvmArguments(String...)}.
+     * @throws UnsupportedBuildArgumentException When there is a problem with build arguments provided by {@link #withArguments(String...)}.
      * @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;
+    T get() throws GradleConnectionException, UnsupportedVersionException, UnknownModelException, UnsupportedOperationConfigurationException, BuildException, IllegalStateException, UnsupportedBuildArgumentException;
 
     /**
-     * Starts fetching the build. This method returns immediately, and the result is later passed to the given handler.
+     * Starts fetching the model, passing the result to the given handler when complete. This method returns immediately, and the result is later passed to the given
+     * handler's {@link ResultHandler#onComplete(Object)} method.
+     *
+     * <p>If the operation fails, the handler's {@link ResultHandler#onFailure(GradleConnectionException)}
+     * method is called with the appropriate exception. See {@link #get()} for a description of the various exceptions that the operation may fail with.
      *
      * @param handler The handler to supply the result to.
      * @throws IllegalStateException When the connection has been closed or is closing.
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java
index f4e125f..848be09 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.tooling;
 
-import org.gradle.tooling.model.Model;
+import org.gradle.api.Incubating;
 
 /**
  * Represents a long-lived connection to a Gradle project. You obtain an instance of a {@code ProjectConnection} by using {@link org.gradle.tooling.GradleConnector#connect()}.
@@ -50,36 +50,38 @@ import org.gradle.tooling.model.Model;
  */
 public interface ProjectConnection {
     /**
-     * Fetches a snapshot of the model of the given type for this project.
+     * Fetches a snapshot of the model of the given type for this project. This method blocks until the model is available.
      *
-     * <p>This method blocks until the model is available.
+     * <p>This method is simply a convenience for calling {@code model(modelType).get()}</p>
      *
-     * @param viewType The model type.
+     * @param modelType The model type.
      * @param <T> The model type.
      * @return The model.
      * @throws UnsupportedVersionException When the target Gradle version does not support the given model.
-     * @throws UnknownModelException When you are building a model unknown to the Tooling API,
-     *  for example you attempt to build a model of a type does not come from the Tooling API.
+     * @throws UnknownModelException When the target Gradle version or build does not support the requested model.
      * @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;
+    <T> T getModel(Class<T> modelType) throws GradleConnectionException, IllegalStateException;
 
     /**
-     * Fetches a snapshot of the model for this project asynchronously. This method return immediately, and the result of the operation is passed to the supplied result handler.
+     * Starts fetching a snapshot of the given model, passing the result to the given handler when complete. This method returns immediately, and the result is later
+     * passed to the given handler's {@link ResultHandler#onComplete(Object)} method.
      *
-     * @param viewType The model type.
+     * <p>If the operation fails, the handler's {@link ResultHandler#onFailure(GradleConnectionException)} method is called with the appropriate exception.
+     * See {@link #getModel(Class)} for a description of the various exceptions that the operation may fail with.
+     *
+     * <p>This method is simply a convenience for calling {@code model(modelType).get(handler)}</p>
+     *
+     * @param modelType The model type.
      * @param handler The handler to pass the result to.
      * @param <T> The model type.
      * @throws IllegalStateException When this connection has been closed or is closing.
-     * @throws UnknownModelException When you are building a model unknown to the Tooling API,
-     *  for example you attempt to build a model of a type does not come from the Tooling API.
      * @since 1.0-milestone-3
      */
-    <T extends Model> void getModel(Class<T> viewType, ResultHandler<? super T> handler) throws IllegalStateException, UnknownModelException;
+    <T> void getModel(Class<T> modelType, ResultHandler<? super T> handler) throws IllegalStateException;
 
     /**
      * Creates a launcher which can be used to execute a build.
@@ -90,16 +92,43 @@ public interface ProjectConnection {
     BuildLauncher newBuild();
 
     /**
-     * Creates a builder which can be used to build the model of the given type.
+     * Creates a builder which can be used to query the model of the given type.
+     *
+     * <p>Any of following models types may be available, depending on the version of Gradle being used by the target
+     * build:
+     *
+     * <ul>
+     *     <li>{@link org.gradle.tooling.model.gradle.GradleBuild}</li>
+     *     <li>{@link org.gradle.tooling.model.build.BuildEnvironment}</li>
+     *     <li>{@link org.gradle.tooling.model.GradleProject}</li>
+     *     <li>{@link org.gradle.tooling.model.gradle.BuildInvocations}</li>
+     *     <li>{@link org.gradle.tooling.model.gradle.ProjectPublications}</li>
+     *     <li>{@link org.gradle.tooling.model.idea.IdeaProject}</li>
+     *     <li>{@link org.gradle.tooling.model.idea.BasicIdeaProject}</li>
+     *     <li>{@link org.gradle.tooling.model.eclipse.EclipseProject}</li>
+     *     <li>{@link org.gradle.tooling.model.eclipse.HierarchicalEclipseProject}</li>
+     * </ul>
+     *
+     * <p>A build may also expose additional custom tooling models. You can use this method to query these models.
      *
      * @param modelType The model type
      * @param <T> The model type.
      * @return The builder.
-     * @throws UnknownModelException When you are building a model unknown to the Tooling API,
-     *  for example you attempt to build a model of a type does not come from the Tooling API.
      * @since 1.0-milestone-3
      */
-    <T extends Model> ModelBuilder<T> model(Class<T> modelType) throws UnknownModelException;
+    <T> ModelBuilder<T> model(Class<T> modelType);
+
+    /**
+     * Creates an executer which can be used to run the given action. The action is serialized into the build
+     * process and executed, then its result is serialized back to the caller.
+     *
+     * @param buildAction The action to run.
+     * @param <T> The result type.
+     * @return The builder.
+     * @since 1.8
+     */
+    @Incubating
+    <T> BuildActionExecuter<T> action(BuildAction<T> buildAction);
 
     /**
      * Closes this connection. Blocks until any pending operations are complete. Once this method has returned, no more notifications will be delivered by any threads.
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java
index 7739fb3..e6857b3 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java
@@ -28,4 +28,8 @@ public class UnknownModelException extends UnsupportedVersionException {
     public UnknownModelException(String message) {
         super(message);
     }
+
+    public UnknownModelException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java
index 069fa5c..e1d4984 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java
@@ -17,6 +17,8 @@ package org.gradle.tooling;
 
 /**
  * Thrown when the target Gradle version does not support a particular feature.
+ *
+ * @since 1.0-milestone-3
  */
 public class UnsupportedVersionException extends GradleConnectionException {
     public UnsupportedVersionException(String message) {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java
index 007934d..5e85ddd 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java
@@ -19,9 +19,9 @@ package org.gradle.tooling.exceptions;
 import org.gradle.tooling.GradleConnectionException;
 
 /**
- * Thrown when the {@link org.gradle.tooling.BuildLauncher} has been configured
- * with unsupported build arguments. For more information see docs for
- * {@link org.gradle.tooling.BuildLauncher#withArguments(String...)} method.
+ * Thrown when the {@link org.gradle.tooling.LongRunningOperation} has been configured
+ * with unsupported build arguments. For more information see the
+ * {@link org.gradle.tooling.LongRunningOperation#withArguments(String...)} method.
  */
 public class UnsupportedBuildArgumentException extends GradleConnectionException {
     public UnsupportedBuildArgumentException(String message) {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/CollectionMapper.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/CollectionMapper.java
new file mode 100644
index 0000000..d488eef
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/CollectionMapper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter;
+
+import org.gradle.tooling.model.DomainObjectSet;
+
+import java.io.Serializable;
+import java.util.*;
+
+public class CollectionMapper implements Serializable {
+    Collection<Object> createEmptyCollection(Class<?> collectionType) {
+        if (collectionType.equals(DomainObjectSet.class)) {
+            return new ArrayList<Object>();
+        }
+        if (collectionType.isAssignableFrom(ArrayList.class)) {
+            return new ArrayList<Object>();
+        }
+        if (collectionType.isAssignableFrom(LinkedHashSet.class)) {
+            return new LinkedHashSet<Object>();
+        }
+        if (collectionType.isAssignableFrom(TreeSet.class)) {
+            return new TreeSet<Object>();
+        }
+        throw new UnsupportedOperationException(String.format("Cannot convert a Collection to type %s.", collectionType.getName()));
+    }
+
+    Map<Object, Object> createEmptyMap(Class<?> mapType) {
+        if (mapType.isAssignableFrom(LinkedHashMap.class)) {
+            return new LinkedHashMap<Object, Object>();
+        }
+        if (mapType.isAssignableFrom(TreeMap.class)) {
+            return new TreeMap<Object, Object>();
+        }
+        throw new UnsupportedOperationException(String.format("Cannot convert a Map to type %s.", mapType.getName()));
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/CompatibleIntrospector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/CompatibleIntrospector.java
new file mode 100644
index 0000000..abeb726
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/CompatibleIntrospector.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.adapter;
+
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Uses reflection to find out / call methods.
+ */
+public class CompatibleIntrospector {
+
+    private final Object target;
+
+    public CompatibleIntrospector(Object target) {
+        this.target = target;
+    }
+
+    private Method getMethod(String methodName) throws NoSuchMethodException {
+        Method[] methods = target.getClass().getDeclaredMethods();
+        for (Method m : methods) {
+            if (m.getName().equals(methodName)) {
+                return m;
+            }
+        }
+        throw new NoSuchMethodException("No such method: '" + methodName + "' on type: '" + target.getClass().getSimpleName() + "'.");
+    }
+
+    public <T> T getSafely(T defaultValue, String methodName) {
+        try {
+            Method method = getMethod(methodName);
+            method.setAccessible(true);
+            return (T) method.invoke(target);
+        } catch (NoSuchMethodException e) {
+            return defaultValue;
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to get value reflectively", e);
+        }
+    }
+
+    public void callSafely(String methodName, Object ... params) {
+        Method method;
+        try {
+            method = getMethod(methodName);
+        } catch (NoSuchMethodException e) {
+            return; // ignore
+        }
+
+        method.setAccessible(true);
+        try {
+            method.invoke(target, params);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to call method reflectively", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/MethodInvocation.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/MethodInvocation.java
new file mode 100644
index 0000000..c3c201d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/MethodInvocation.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.tooling.internal.adapter;
+
+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;
+    }
+
+    /**
+     * Marks the method as handled.
+     */
+    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/adapter/MethodInvoker.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/MethodInvoker.java
new file mode 100644
index 0000000..8387f5e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/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.adapter;
+
+public interface MethodInvoker {
+    void invoke(MethodInvocation invocation) throws Throwable;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/NoOpMethodInvoker.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/NoOpMethodInvoker.java
new file mode 100644
index 0000000..2bb45d9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/NoOpMethodInvoker.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter;
+
+import java.io.Serializable;
+
+public class NoOpMethodInvoker implements MethodInvoker, Serializable {
+    public void invoke(MethodInvocation invocation) throws Throwable {
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/ProtocolToModelAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/ProtocolToModelAdapter.java
new file mode 100644
index 0000000..986554c
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/ProtocolToModelAdapter.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter;
+
+import org.gradle.api.Action;
+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.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Adapts some source object to some target view type.
+ */
+public class ProtocolToModelAdapter implements Serializable {
+    private static final MethodInvoker NO_OP_HANDLER = new NoOpMethodInvoker();
+    private static final Action<SourceObjectMapping> NO_OP_MAPPER = new NoOpMapping();
+    private static final TargetTypeProvider IDENTITY_TYPE_PROVIDER = new TargetTypeProvider() {
+        public <T> Class<? extends T> getTargetType(Class<T> initialTargetType, Object protocolObject) {
+            return initialTargetType;
+        }
+    };
+    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;
+    private final CollectionMapper collectionMapper = new CollectionMapper();
+
+    public ProtocolToModelAdapter() {
+        this(IDENTITY_TYPE_PROVIDER);
+    }
+
+    public ProtocolToModelAdapter(TargetTypeProvider targetTypeProvider) {
+        this.targetTypeProvider = targetTypeProvider;
+    }
+
+    /**
+     * Adapts the source object to a view object.
+     */
+    public <T, S> T adapt(Class<T> targetType, S sourceObject) {
+        return adapt(targetType, sourceObject, NO_OP_MAPPER);
+    }
+
+    /**
+     * Adapts the source object to a view 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, final S sourceObject, final Class<?> mixInClass) {
+        return adapt(targetType, sourceObject, new Action<SourceObjectMapping>() {
+            public void execute(SourceObjectMapping mapping) {
+                if (mapping.getSourceObject() == sourceObject) {
+                    mapping.mixIn(mixInClass);
+                }
+            }
+        });
+    }
+
+    /**
+     * Adapts the source object to a view object.
+     *
+     * @param mapper An action that is invoked for each source object in the graph that is to be adapted. The action can influence how the source object is adapted via the provided
+     * {@link SourceObjectMapping}.
+     */
+    public <T, S> T adapt(Class<T> targetType, S sourceObject, Action<? super SourceObjectMapping> mapper) {
+        if (sourceObject == null) {
+            return null;
+        }
+        Class<? extends T> wrapperType = targetTypeProvider.getTargetType(targetType, sourceObject);
+        DefaultSourceObjectMapping mapping = new DefaultSourceObjectMapping(sourceObject, targetType, wrapperType);
+        mapper.execute(mapping);
+        wrapperType = mapping.wrapperType.asSubclass(targetType);
+        if (wrapperType.isInstance(sourceObject)) {
+            return wrapperType.cast(sourceObject);
+        }
+        MethodInvoker overrideMethodInvoker = mapping.overrideInvoker;
+        MixInMethodInvoker mixInMethodInvoker = null;
+        if (mapping.mixInType != null) {
+            mixInMethodInvoker = new MixInMethodInvoker(mapping.mixInType, new AdaptingMethodInvoker(mapper, new ReflectionMethodInvoker()));
+            overrideMethodInvoker = mixInMethodInvoker;
+        }
+        Object proxy = Proxy.newProxyInstance(wrapperType.getClassLoader(), new Class<?>[]{wrapperType}, new InvocationHandlerImpl(sourceObject, overrideMethodInvoker, mapper));
+        if (mixInMethodInvoker != null) {
+            mixInMethodInvoker.setProxy(proxy);
+        }
+        return wrapperType.cast(proxy);
+    }
+
+    /**
+     * Unpacks the source object from a given view object.
+     */
+    public Object unpack(Object viewObject) {
+        if (!Proxy.isProxyClass(viewObject.getClass()) || !(Proxy.getInvocationHandler(viewObject) instanceof InvocationHandlerImpl)) {
+            throw new IllegalArgumentException("The given object is not a view object");
+        }
+        InvocationHandlerImpl handler = (InvocationHandlerImpl) Proxy.getInvocationHandler(viewObject);
+        return handler.delegate;
+    }
+
+    private static class DefaultSourceObjectMapping implements SourceObjectMapping {
+        private final Object protocolObject;
+        private final Class<?> targetType;
+        private Class<?> wrapperType;
+        private Class<?> mixInType;
+        private MethodInvoker overrideInvoker = NO_OP_HANDLER;
+
+        public DefaultSourceObjectMapping(Object protocolObject, Class<?> targetType, Class<?> wrapperType) {
+            this.protocolObject = protocolObject;
+            this.targetType = targetType;
+            this.wrapperType = wrapperType;
+        }
+
+        public Object getSourceObject() {
+            return protocolObject;
+        }
+
+        public Class<?> getTargetType() {
+            return targetType;
+        }
+
+        public void mapToType(Class<?> type) {
+            if (!targetType.isAssignableFrom(type)) {
+                throw new IllegalArgumentException(String.format("requested wrapper type '%s' is not assignable to target type '%s'.", type.getSimpleName(), targetType.getSimpleName()));
+            }
+            wrapperType = type;
+        }
+
+        public void mixIn(Class<?> mixInBeanType) {
+            if (mixInType != null) {
+                throw new UnsupportedOperationException("Mixing in multiple beans is currently not supported.");
+            }
+            mixInType = mixInBeanType;
+        }
+
+        public void mixIn(MethodInvoker invoker) {
+            if (overrideInvoker != NO_OP_HANDLER) {
+                throw new UnsupportedOperationException("Mixing in multiple invokers is currently not supported.");
+            }
+            overrideInvoker = invoker;
+        }
+    }
+
+    private static class NoOpMapping implements Action<SourceObjectMapping>, Serializable {
+        public void execute(SourceObjectMapping mapping) {
+        }
+    }
+
+    private class InvocationHandlerImpl implements InvocationHandler, Serializable {
+        private final Object delegate;
+        private final MethodInvoker overrideMethodInvoker;
+        private final Action<? super SourceObjectMapping> mapper;
+        private transient Method equalsMethod;
+        private transient Method hashCodeMethod;
+        private transient MethodInvoker invoker;
+
+        public InvocationHandlerImpl(Object delegate, MethodInvoker overrideMethodInvoker, Action<? super SourceObjectMapping> mapper) {
+            this.delegate = delegate;
+            this.overrideMethodInvoker = overrideMethodInvoker;
+            this.mapper = mapper;
+            setup();
+        }
+
+        private void readObject(java.io.ObjectInputStream in)
+             throws IOException, ClassNotFoundException {
+            in.defaultReadObject();
+            setup();
+        }
+
+        private void setup() {
+            invoker = new SupportedPropertyInvoker(
+                    new SafeMethodInvoker(
+                            new PropertyCachingMethodInvoker(
+                                    new AdaptingMethodInvoker(mapper,
+                                            new ChainedMethodInvoker(
+                                                    overrideMethodInvoker,
+                                                    new ReflectionMethodInvoker())))));
+            try {
+                equalsMethod = Object.class.getMethod("equals", Object.class);
+                hashCodeMethod = Object.class.getMethod("hashCode");
+            } catch (NoSuchMethodException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+
+            InvocationHandlerImpl other = (InvocationHandlerImpl) o;
+            return delegate.equals(other.delegate);
+        }
+
+        @Override
+        public int hashCode() {
+            return delegate.hashCode();
+        }
+
+        public Object invoke(Object target, Method method, Object[] params) throws Throwable {
+            if (method.equals(equalsMethod)) {
+                Object param = params[0];
+                if (param == null || !Proxy.isProxyClass(param.getClass())) {
+                    return false;
+                }
+                InvocationHandler other = Proxy.getInvocationHandler(param);
+                return equals(other);
+            } else if (method.equals(hashCodeMethod)) {
+                return hashCode();
+            }
+
+            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();
+        }
+    }
+
+    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);
+            }
+        }
+    }
+
+    private class AdaptingMethodInvoker implements MethodInvoker {
+        private final Action<? super SourceObjectMapping> mapping;
+        private final MethodInvoker next;
+
+        private AdaptingMethodInvoker(Action<? super SourceObjectMapping> mapping, MethodInvoker next) {
+            this.mapping = mapping;
+            this.next = next;
+        }
+
+        public void invoke(MethodInvocation invocation) throws Throwable {
+            next.invoke(invocation);
+            if (invocation.found() && invocation.getResult() != null) {
+                invocation.setResult(convert(invocation.getResult(), invocation.getGenericReturnType()));
+            }
+        }
+
+        private Object convert(Object value, Type targetType) {
+            if (targetType instanceof ParameterizedType) {
+                ParameterizedType parameterizedTargetType = (ParameterizedType) targetType;
+                if (parameterizedTargetType.getRawType() instanceof Class) {
+                    Class<?> rawClass = (Class<?>) parameterizedTargetType.getRawType();
+                    if (Iterable.class.isAssignableFrom(rawClass)) {
+                        Type targetElementType = getElementType(parameterizedTargetType, 0);
+                        Collection<Object> convertedElements = collectionMapper.createEmptyCollection(rawClass);
+                        for (Object element : (Iterable<?>) value) {
+                            convertedElements.add(convert(element, targetElementType));
+                        }
+                        if (rawClass.equals(DomainObjectSet.class)) {
+                            return new ImmutableDomainObjectSet(convertedElements);
+                        } else {
+                            return convertedElements;
+                        }
+                    }
+                    if (Map.class.isAssignableFrom(rawClass)) {
+                        Type targetKeyType = getElementType(parameterizedTargetType, 0);
+                        Type targetValueType = getElementType(parameterizedTargetType, 1);
+                        Map<Object, Object> convertedElements = collectionMapper.createEmptyMap(rawClass);
+                        for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
+                            convertedElements.put(convert(entry.getKey(), targetKeyType), convert(entry.getValue(), targetValueType));
+                        }
+                        return convertedElements;
+                    }
+                }
+            }
+            if (targetType instanceof Class) {
+                if (((Class) targetType).isPrimitive()) {
+                    return value;
+                }
+                return adapt((Class) targetType, value, mapping);
+            }
+            throw new UnsupportedOperationException(String.format("Cannot convert object of %s to %s.", value.getClass(), targetType));
+        }
+
+        private Type getElementType(ParameterizedType type, int index) {
+            Type elementType = type.getActualTypeArguments()[index];
+            if (elementType instanceof WildcardType) {
+                WildcardType wildcardType = (WildcardType) elementType;
+                return wildcardType.getUpperBounds()[0];
+            }
+            return elementType;
+        }
+    }
+
+    private class ReflectionMethodInvoker implements MethodInvoker {
+        public void invoke(MethodInvocation invocation) throws Throwable {
+            // TODO - cache method lookup
+            Method targetMethod = locateMethod(invocation);
+            if (targetMethod == null) {
+                return;
+            }
+
+            Object returnValue;
+            try {
+                returnValue = targetMethod.invoke(invocation.getDelegate(), invocation.getParameters());
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+
+            invocation.setResult(returnValue);
+        }
+
+        private Method locateMethod(MethodInvocation invocation) {
+            Class<?> sourceClass = invocation.getDelegate().getClass();
+            Method match;
+            try {
+                match = sourceClass.getMethod(invocation.getName(), invocation.getParameterTypes());
+            } catch (NoSuchMethodException e) {
+                return null;
+            }
+
+            LinkedList<Class<?>> queue = new LinkedList<Class<?>>();
+            queue.add(sourceClass);
+            while (!queue.isEmpty()) {
+                Class<?> c = queue.removeFirst();
+                try {
+                    match = c.getMethod(invocation.getName(), invocation.getParameterTypes());
+                } catch (NoSuchMethodException e) {
+                    // ignore
+                }
+                for (Class<?> interfaceType : c.getInterfaces()) {
+                    queue.addFirst(interfaceType);
+                }
+                if (c.getSuperclass() != null) {
+                    queue.addFirst(c.getSuperclass());
+                }
+            }
+            match.setAccessible(true);
+            return match;
+        }
+    }
+
+    private 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/adapter/SourceObjectMapping.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/SourceObjectMapping.java
new file mode 100644
index 0000000..90b3147
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/SourceObjectMapping.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter;
+
+public interface SourceObjectMapping {
+    Object getSourceObject();
+
+    Class<?> getTargetType();
+
+    void mapToType(Class<?> type);
+
+    void mixIn(Class<?> mixInBeanType);
+
+    void mixIn(MethodInvoker invoker);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TargetTypeProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TargetTypeProvider.java
new file mode 100644
index 0000000..6942681
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TargetTypeProvider.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.adapter;
+
+import java.io.Serializable;
+
+public interface TargetTypeProvider extends Serializable {
+    /**
+     * Determines the model type to use to wrap the given protocol object.
+     */
+    <T> Class<? extends T> getTargetType(Class<T> initialTargetType, Object protocolObject);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java
index 03f354f..d723a3e 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java
@@ -17,37 +17,22 @@
 package org.gradle.tooling.internal.build;
 
 import org.gradle.tooling.internal.protocol.InternalBuildEnvironment;
-import org.gradle.tooling.model.build.BuildEnvironment;
-import org.gradle.tooling.model.build.GradleEnvironment;
 import org.gradle.tooling.model.build.JavaEnvironment;
 
 import java.io.File;
 import java.io.Serializable;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 12/17/11
- */
-public class DefaultBuildEnvironment implements BuildEnvironment, InternalBuildEnvironment, Serializable {
-
-    private final String gradleVersion;
+public class DefaultBuildEnvironment extends VersionOnlyBuildEnvironment implements InternalBuildEnvironment, Serializable {
     private final File javaHome;
     private final List<String> jvmArguments;
 
     public DefaultBuildEnvironment(String gradleVersion, File javaHome, List<String> jvmArguments) {
-        this.gradleVersion = gradleVersion;
+        super(gradleVersion);
         this.javaHome = javaHome;
         this.jvmArguments = jvmArguments;
     }
 
-    public GradleEnvironment getGradle() {
-        return new GradleEnvironment() {
-            public String getGradleVersion() {
-                return gradleVersion;
-            }
-        };
-    }
-
     public JavaEnvironment getJava() {
         return new JavaEnvironment() {
             public File getJavaHome() {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java
index 4925d81..f1c9488 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java
@@ -16,19 +16,20 @@
 
 package org.gradle.tooling.internal.build;
 
-import org.gradle.tooling.model.build.JavaEnvironment;
-import org.gradle.tooling.model.internal.Exceptions;
+import org.gradle.tooling.model.build.GradleEnvironment;
 
-/**
- * by Szczepan Faber, created at: 12/22/11
- */
-public class VersionOnlyBuildEnvironment extends DefaultBuildEnvironment {
+public class VersionOnlyBuildEnvironment {
+    private final String gradleVersion;
 
     public VersionOnlyBuildEnvironment(String gradleVersion) {
-        super(gradleVersion, null, null);
+        this.gradleVersion = gradleVersion;
     }
 
-    public JavaEnvironment getJava() {
-        throw Exceptions.unsupportedMethod("getJava()");
+    public GradleEnvironment getGradle() {
+        return new GradleEnvironment() {
+            public String getGradleVersion() {
+                return gradleVersion;
+            }
+        };
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AbstractLongRunningOperation.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AbstractLongRunningOperation.java
new file mode 100644
index 0000000..58c1279
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AbstractLongRunningOperation.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer;
+
+import org.gradle.tooling.LongRunningOperation;
+import org.gradle.tooling.ProgressListener;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public abstract class AbstractLongRunningOperation<T extends LongRunningOperation> implements LongRunningOperation {
+    protected final ConnectionParameters connectionParameters;
+    protected final ConsumerOperationParameters.Builder operationParamsBuilder;
+
+    protected AbstractLongRunningOperation(ConnectionParameters parameters) {
+        connectionParameters = parameters;
+        operationParamsBuilder = ConsumerOperationParameters.builder();
+    }
+
+    protected abstract T getThis();
+
+    protected final ConsumerOperationParameters getConsumerOperationParameters() {
+        ConnectionParameters connectionParameters = this.connectionParameters;
+        return operationParamsBuilder.setParameters(connectionParameters).build();
+    }
+
+    public T withArguments(String... arguments) {
+        operationParamsBuilder.setArguments(arguments);
+        return getThis();
+    }
+
+    public T setStandardOutput(OutputStream outputStream) {
+        operationParamsBuilder.setStdout(outputStream);
+        return getThis();
+    }
+
+    public T setStandardError(OutputStream outputStream) {
+        operationParamsBuilder.setStderr(outputStream);
+        return getThis();
+    }
+
+    public T setStandardInput(InputStream inputStream) {
+        operationParamsBuilder.setStdin(inputStream);
+        return getThis();
+    }
+
+    public T setJavaHome(File javaHome) {
+        operationParamsBuilder.setJavaHome(javaHome);
+        return getThis();
+    }
+
+    public T setJvmArguments(String... jvmArguments) {
+        operationParamsBuilder.setJvmArguments(jvmArguments);
+        return getThis();
+    }
+
+    public T addProgressListener(ProgressListener listener) {
+        operationParamsBuilder.addProgressListener(listener);
+        return getThis();
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java
index 516c3ef..5094d90 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java
@@ -19,6 +19,9 @@ import org.gradle.internal.UncheckedException;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.ResultHandler;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 
@@ -40,7 +43,7 @@ class BlockingResultHandler<T> implements ResultHandler<T> {
         }
 
         if (result instanceof Throwable) {
-            throw UncheckedException.throwAsUncheckedException((Throwable) result);
+            throw UncheckedException.throwAsUncheckedException(attachCallerThreadStackTrace((Throwable) result));
         }
         if (result == NULL) {
             return null;
@@ -48,6 +51,17 @@ class BlockingResultHandler<T> implements ResultHandler<T> {
         return resultType.cast(result);
     }
 
+    private Throwable attachCallerThreadStackTrace(Throwable failure) {
+        List<StackTraceElement> adjusted = new ArrayList<StackTraceElement>();
+        adjusted.addAll(Arrays.asList(failure.getStackTrace()));
+        List<StackTraceElement> currentThreadStack = Arrays.asList(Thread.currentThread().getStackTrace());
+        if (!currentThreadStack.isEmpty()) {
+            adjusted.addAll(currentThreadStack.subList(2, currentThreadStack.size()));
+        }
+        failure.setStackTrace(adjusted.toArray(new StackTraceElement[adjusted.size()]));
+        return failure;
+    }
+
     public void onComplete(T result) {
         queue.add(result == null ? NULL : result);
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
index c615983..753e863 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
@@ -17,17 +17,15 @@ package org.gradle.tooling.internal.consumer;
 
 import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.tooling.ProjectConnection;
-import org.gradle.tooling.internal.consumer.async.AsyncConnection;
-import org.gradle.tooling.internal.consumer.async.DefaultAsyncConnection;
-import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
-import org.gradle.tooling.internal.consumer.connection.LazyConnection;
-import org.gradle.tooling.internal.consumer.connection.LoggingInitializerConnection;
-import org.gradle.tooling.internal.consumer.connection.ProgressLoggingConnection;
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.async.DefaultAsyncConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.ConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.LazyConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.LoggingInitializerConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.ProgressLoggingConsumerActionExecutor;
 import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
-import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
 
 public class ConnectionFactory {
-    private final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter();
     private final ToolingImplementationLoader toolingImplementationLoader;
     private final DefaultExecutorFactory executorFactory = new DefaultExecutorFactory();
 
@@ -37,11 +35,11 @@ public class ConnectionFactory {
 
     public ProjectConnection create(Distribution distribution, ConnectionParameters parameters) {
         SynchronizedLogging synchronizedLogging = new SynchronizedLogging();
-        ConsumerConnection lazyConnection = new LazyConnection(distribution, toolingImplementationLoader, synchronizedLogging, parameters.getVerboseLogging());
-        ConsumerConnection progressLoggingConnection = new ProgressLoggingConnection(lazyConnection, synchronizedLogging);
-        ConsumerConnection initializingConnection = new LoggingInitializerConnection(progressLoggingConnection, synchronizedLogging);
-        AsyncConnection asyncConnection = new DefaultAsyncConnection(initializingConnection, executorFactory);
-        return new DefaultProjectConnection(asyncConnection, adapter, parameters);
+        ConsumerActionExecutor lazyConnection = new LazyConsumerActionExecutor(distribution, toolingImplementationLoader, synchronizedLogging, parameters);
+        ConsumerActionExecutor progressLoggingConnection = new ProgressLoggingConsumerActionExecutor(lazyConnection, synchronizedLogging);
+        ConsumerActionExecutor initializingConnection = new LoggingInitializerConsumerActionExecutor(progressLoggingConnection, synchronizedLogging);
+        AsyncConsumerActionExecutor asyncConnection = new DefaultAsyncConsumerActionExecutor(initializingConnection, executorFactory);
+        return new DefaultProjectConnection(asyncConnection, parameters);
     }
 
     ToolingImplementationLoader getToolingImplementationLoader() {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java
index a8dea70..6dd4406 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java
@@ -18,7 +18,7 @@ package org.gradle.tooling.internal.consumer;
 import java.io.File;
 import java.util.concurrent.TimeUnit;
 
-public interface ConnectionParameters {
+public interface ConnectionParameters extends org.gradle.tooling.internal.protocol.ConnectionParameters {
     File getProjectDir();
 
     /**
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
index be2c2e3..dc97a6d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
@@ -16,32 +16,27 @@
 
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.StartParameter;
-import org.gradle.internal.service.SynchronizedServiceRegistry;
-import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.tooling.internal.consumer.loader.CachingToolingImplementationLoader;
 import org.gradle.tooling.internal.consumer.loader.DefaultToolingImplementationLoader;
 import org.gradle.tooling.internal.consumer.loader.SynchronizedToolingImplementationLoader;
 import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
 
-/**
- * by Szczepan Faber, created at: 12/6/11
- */
 public class ConnectorServices {
 
-    private static ServiceRegistry singletonRegistry = new SynchronizedServiceRegistry(new ConnectorServiceRegistry());
+    private static ServiceRegistry singletonRegistry = new ConnectorServiceRegistry();
 
     public DefaultGradleConnector createConnector() {
         ConnectionFactory connectionFactory = new ConnectionFactory(singletonRegistry.get(ToolingImplementationLoader.class));
-        return new DefaultGradleConnector(connectionFactory, new DistributionFactory(StartParameter.DEFAULT_GRADLE_USER_HOME));
+        return new DefaultGradleConnector(connectionFactory, new DistributionFactory());
     }
 
     /**
      * Resets the state of connector services. Meant to be used only for testing!
      */
     public void reset() {
-        singletonRegistry = new SynchronizedServiceRegistry(new ConnectorServiceRegistry());
+        singletonRegistry = new ConnectorServiceRegistry();
     }
 
     private static class ConnectorServiceRegistry extends DefaultServiceRegistry {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildActionExecuter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildActionExecuter.java
new file mode 100644
index 0000000..0cdea44
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildActionExecuter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer;
+
+import org.gradle.tooling.*;
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+
+class DefaultBuildActionExecuter<T> extends AbstractLongRunningOperation<DefaultBuildActionExecuter<T>> implements BuildActionExecuter<T> {
+    private final BuildAction<T> buildAction;
+    private final AsyncConsumerActionExecutor connection;
+
+    public DefaultBuildActionExecuter(BuildAction<T> buildAction, AsyncConsumerActionExecutor connection, ConnectionParameters parameters) {
+        super(parameters);
+        this.buildAction = buildAction;
+        this.connection = connection;
+    }
+
+    @Override
+    protected DefaultBuildActionExecuter<T> getThis() {
+        return this;
+    }
+
+    public T run() throws GradleConnectionException {
+        BlockingResultHandler<Object> handler = new BlockingResultHandler<Object>(Object.class);
+        run(handler);
+        return (T) handler.getResult();
+    }
+
+    public void run(ResultHandler<? super T> handler) throws IllegalStateException {
+        final ConsumerOperationParameters operationParameters = getConsumerOperationParameters();
+        connection.run(new ConsumerAction<T>() {
+                           public ConsumerOperationParameters getParameters() {
+                               return operationParameters;
+                           }
+
+                           public T run(ConsumerConnection connection) {
+                               return connection.run(buildAction, operationParameters);
+                           }
+                       }, new ResultHandlerAdapter<T>(handler) {
+                           @Override
+                           protected String connectionFailureMessage(Throwable failure) {
+                               return String.format("Could not run build action using %s.", connection.getDisplayName());
+                           }
+                       }
+        );
+    }
+}
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 012561c..aa365da 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
@@ -15,33 +15,45 @@
  */
 package org.gradle.tooling.internal.consumer;
 
+import com.google.common.collect.Lists;
+import org.gradle.api.GradleException;
 import org.gradle.tooling.BuildLauncher;
-import org.gradle.tooling.ProgressListener;
 import org.gradle.tooling.ResultHandler;
-import org.gradle.tooling.internal.consumer.async.AsyncConnection;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.gradle.TaskListingLaunchable;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
+import org.gradle.tooling.model.Launchable;
 import org.gradle.tooling.model.Task;
+import org.gradle.tooling.model.TaskSelector;
 
-import java.io.File;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
-class DefaultBuildLauncher implements BuildLauncher {
-    private final AsyncConnection connection;
-    private ConsumerOperationParameters operationParameters;
+class DefaultBuildLauncher extends AbstractLongRunningOperation<DefaultBuildLauncher> implements BuildLauncher {
+    private final AsyncConsumerActionExecutor connection;
 
-    public DefaultBuildLauncher(AsyncConnection connection, ConnectionParameters parameters) {
-        operationParameters = new ConsumerOperationParameters(parameters);
-        operationParameters.setTasks(Collections.<String>emptyList());
+    public DefaultBuildLauncher(AsyncConsumerActionExecutor connection, ConnectionParameters parameters) {
+        super(parameters);
+        operationParamsBuilder.setTasks(Collections.<String>emptyList());
         this.connection = connection;
     }
 
+    @Override
+    protected DefaultBuildLauncher getThis() {
+        return this;
+    }
+
     public BuildLauncher forTasks(String... tasks) {
-        operationParameters.setTasks(Arrays.asList(tasks));
+        operationParamsBuilder.setTasks(Arrays.asList(tasks));
         return this;
     }
 
@@ -55,43 +67,45 @@ class DefaultBuildLauncher implements BuildLauncher {
         for (Task task : tasks) {
             taskPaths.add(task.getPath());
         }
-        operationParameters.setTasks(taskPaths);
-        return this;
-    }
-
-    public BuildLauncher withArguments(String... arguments) {
-        operationParameters.setArguments(arguments);
+        operationParamsBuilder.setTasks(taskPaths);
         return this;
     }
 
-    public DefaultBuildLauncher setStandardError(OutputStream outputStream) {
-        operationParameters.setStandardError(outputStream);
-        return this;
+    public BuildLauncher forLaunchables(Launchable... launchables) {
+        return forLaunchables(Arrays.asList(launchables));
     }
 
-    public DefaultBuildLauncher setStandardOutput(OutputStream outputStream) {
-        operationParameters.setStandardOutput(outputStream);
-        return this;
-    }
-
-    public DefaultBuildLauncher setStandardInput(InputStream inputStream) {
-        operationParameters.setStandardInput(inputStream);
-        return this;
-    }
-
-    public DefaultBuildLauncher setJavaHome(File javaHome) {
-        operationParameters.setJavaHome(javaHome);
-        return this;
-    }
-
-    public DefaultBuildLauncher setJvmArguments(String... jvmArguments) {
-        operationParameters.setJvmArguments(jvmArguments);
+    public BuildLauncher forLaunchables(Iterable<? extends Launchable> launchables) {
+        Set<String> taskPaths = new LinkedHashSet<String>();
+        List<InternalLaunchable> launchablesParams = Lists.newArrayList();
+        for (Launchable launchable : launchables) {
+            if (launchable instanceof Task) {
+                taskPaths.add(((Task) launchable).getPath());
+            } else if (launchable instanceof TaskListingLaunchable) {
+                taskPaths.addAll(((TaskListingLaunchable) launchable).getTaskNames());
+            } else if (!(launchable instanceof TaskSelector)) {
+                throw new GradleException("Only Task or TaskSelector instances are supported: "
+                        + (launchable != null ? launchable.getClass() : "null"));
+            }
+            maybeAddLaunchableParameter(launchablesParams, launchable);
+        }
+        operationParamsBuilder.setTasks(new ArrayList<String>(taskPaths));
+        operationParamsBuilder.setLaunchables(launchablesParams);
         return this;
     }
 
-    public DefaultBuildLauncher addProgressListener(ProgressListener listener) {
-        operationParameters.addProgressListener(listener);
-        return this;
+    private void maybeAddLaunchableParameter(List<InternalLaunchable> launchablesParams, Launchable launchable) {
+        Object original = launchable;
+        try {
+            if (Proxy.isProxyClass(launchable.getClass())) {
+                original = new ProtocolToModelAdapter().unpack(launchable);
+            }
+        } catch (IllegalArgumentException iae) {
+            // ignore: launchable created on consumer side for older provider
+        }
+        if (original instanceof InternalLaunchable) {
+            launchablesParams.add((InternalLaunchable) original);
+        }
     }
 
     public void run() {
@@ -101,11 +115,26 @@ class DefaultBuildLauncher implements BuildLauncher {
     }
 
     public void run(final ResultHandler<? super 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());
+        final ConsumerOperationParameters operationParameters = operationParamsBuilder.setParameters(connectionParameters).build();
+        connection.run(new ConsumerAction<Void>() {
+            public ConsumerOperationParameters getParameters() {
+                return operationParameters;
             }
-        });
+
+            public Void run(ConsumerConnection connection) {
+                return connection.run(Void.class, operationParameters);
+            }
+        }, new ResultHandlerAdapter(handler));
+    }
+
+    private class ResultHandlerAdapter extends org.gradle.tooling.internal.consumer.ResultHandlerAdapter<Void> {
+        public ResultHandlerAdapter(ResultHandler<? super Void> handler) {
+            super(handler);
+        }
+
+        @Override
+        protected String connectionFailureMessage(Throwable failure) {
+            return String.format("Could not execute build using %s.", connection.getDisplayName());
+        }
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java
index 344ad67..57984db 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java
@@ -15,71 +15,127 @@
  */
 package org.gradle.tooling.internal.consumer;
 
+import org.gradle.util.GradleVersion;
+
 import java.io.File;
 import java.util.concurrent.TimeUnit;
 
 public class DefaultConnectionParameters implements ConnectionParameters {
-    private File gradleUserHomeDir;
-    private File projectDir;
-    private Boolean searchUpwards;
-    private Boolean embedded;
-    private Integer daemonMaxIdleTimeValue;
-    private TimeUnit daemonMaxIdleTimeUnits;
-    private boolean verboseLogging;
+    private final File gradleUserHomeDir;
+    private final File projectDir;
+    private final Boolean searchUpwards;
+    private final Boolean embedded;
+    private final Integer daemonMaxIdleTimeValue;
+    private final TimeUnit daemonMaxIdleTimeUnits;
+    private final boolean verboseLogging;
+
+    public static Builder builder() {
+        return new Builder();
+    }
 
-    public File getGradleUserHomeDir() {
-        return gradleUserHomeDir;
+    public static Builder builder(ConnectionParameters connectionParameters) {
+        return new Builder().setDaemonMaxIdleTimeUnits(connectionParameters.getDaemonMaxIdleTimeUnits()).
+                setDaemonMaxIdleTimeValue(connectionParameters.getDaemonMaxIdleTimeValue()).
+                setEmbedded(connectionParameters.isEmbedded()).
+                setGradleUserHomeDir(connectionParameters.getGradleUserHomeDir()).
+                setProjectDir(connectionParameters.getProjectDir()).
+                setSearchUpwards(connectionParameters.isSearchUpwards()).
+                setVerboseLogging(connectionParameters.getVerboseLogging());
     }
 
-    public void setGradleUserHomeDir(File gradleUserHomeDir) {
+    public static class Builder {
+        private File gradleUserHomeDir;
+        private File projectDir;
+        private Boolean searchUpwards;
+        private Boolean embedded;
+        private Integer daemonMaxIdleTimeValue;
+        private TimeUnit daemonMaxIdleTimeUnits;
+        private boolean verboseLogging;
+
+        private Builder() {
+        }
+
+        public Builder setGradleUserHomeDir(File gradleUserHomeDir) {
+            this.gradleUserHomeDir = gradleUserHomeDir;
+            return this;
+        }
+
+        public Builder setProjectDir(File projectDir) {
+            this.projectDir = projectDir;
+            return this;
+        }
+
+        public Builder setSearchUpwards(Boolean searchUpwards) {
+            this.searchUpwards = searchUpwards;
+            return this;
+        }
+
+        public Builder setEmbedded(Boolean embedded) {
+            this.embedded = embedded;
+            return this;
+        }
+
+        public Builder setDaemonMaxIdleTimeValue(Integer daemonMaxIdleTimeValue) {
+            this.daemonMaxIdleTimeValue = daemonMaxIdleTimeValue;
+            return this;
+        }
+
+        public Builder setDaemonMaxIdleTimeUnits(TimeUnit daemonMaxIdleTimeUnits) {
+            this.daemonMaxIdleTimeUnits = daemonMaxIdleTimeUnits;
+            return this;
+        }
+
+        public Builder setVerboseLogging(boolean verboseLogging) {
+            this.verboseLogging = verboseLogging;
+            return this;
+        }
+
+        public DefaultConnectionParameters build() {
+            return new DefaultConnectionParameters(gradleUserHomeDir, projectDir, searchUpwards, embedded,
+                    daemonMaxIdleTimeValue, daemonMaxIdleTimeUnits, verboseLogging);
+        }
+    }
+
+    private DefaultConnectionParameters(File gradleUserHomeDir, File projectDir, Boolean searchUpwards, Boolean embedded,
+                                        Integer daemonMaxIdleTimeValue, TimeUnit daemonMaxIdleTimeUnits, boolean verboseLogging) {
         this.gradleUserHomeDir = gradleUserHomeDir;
+        this.projectDir = projectDir;
+        this.searchUpwards = searchUpwards;
+        this.embedded = embedded;
+        this.daemonMaxIdleTimeValue = daemonMaxIdleTimeValue;
+        this.daemonMaxIdleTimeUnits = daemonMaxIdleTimeUnits;
+        this.verboseLogging = verboseLogging;
     }
 
-    public File getProjectDir() {
-        return projectDir;
+    public File getGradleUserHomeDir() {
+        return gradleUserHomeDir;
     }
 
-    public void setProjectDir(File projectDir) {
-        this.projectDir = projectDir;
+    public File getProjectDir() {
+        return projectDir;
     }
 
     public Boolean isSearchUpwards() {
         return searchUpwards;
     }
 
-    public void setSearchUpwards(Boolean searchUpwards) {
-        this.searchUpwards = searchUpwards;
-    }
-
     public Boolean isEmbedded() {
         return embedded;
     }
 
-    public void setEmbedded(Boolean embedded) {
-        this.embedded = embedded;
-    }
-
     public Integer getDaemonMaxIdleTimeValue() {
         return daemonMaxIdleTimeValue;
     }
 
-    public void setDaemonMaxIdleTimeValue(Integer daemonMaxIdleTimeValue) {
-        this.daemonMaxIdleTimeValue = daemonMaxIdleTimeValue;
-    }
-
     public TimeUnit getDaemonMaxIdleTimeUnits() {
         return daemonMaxIdleTimeUnits;
     }
 
-    public void setDaemonMaxIdleTimeUnits(TimeUnit daemonMaxIdleTimeUnits) {
-        this.daemonMaxIdleTimeUnits = daemonMaxIdleTimeUnits;
+    public String getConsumerVersion() {
+        return GradleVersion.current().getVersion();
     }
 
     public boolean getVerboseLogging() {
         return verboseLogging;
     }
-
-    public void setVerboseLogging(boolean verboseLogging) {
-        this.verboseLogging = verboseLogging;
-    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java
index f414cac..110bd16 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java
@@ -32,7 +32,7 @@ public class DefaultGradleConnector extends GradleConnector {
     private final DistributionFactory distributionFactory;
     private Distribution distribution;
 
-    private final DefaultConnectionParameters connectionParameters = new DefaultConnectionParameters();
+    private final DefaultConnectionParameters.Builder connectionParamsBuilder = DefaultConnectionParameters.builder();
 
     public DefaultGradleConnector(ConnectionFactory connectionFactory, DistributionFactory distributionFactory) {
         this.connectionFactory = connectionFactory;
@@ -65,28 +65,28 @@ public class DefaultGradleConnector extends GradleConnector {
     }
 
     public GradleConnector forProjectDirectory(File projectDir) {
-        connectionParameters.setProjectDir(projectDir);
+        connectionParamsBuilder.setProjectDir(projectDir);
         return this;
     }
 
     public GradleConnector useGradleUserHomeDir(File gradleUserHomeDir) {
-        connectionParameters.setGradleUserHomeDir(gradleUserHomeDir);
+        connectionParamsBuilder.setGradleUserHomeDir(gradleUserHomeDir);
         return this;
     }
 
     public GradleConnector searchUpwards(boolean searchUpwards) {
-        connectionParameters.setSearchUpwards(searchUpwards);
+        connectionParamsBuilder.setSearchUpwards(searchUpwards);
         return this;
     }
 
     public GradleConnector embedded(boolean embedded) {
-        connectionParameters.setEmbedded(embedded);
+        connectionParamsBuilder.setEmbedded(embedded);
         return this;
     }
 
     public GradleConnector daemonMaxIdleTime(int timeoutValue, TimeUnit timeoutUnits) {
-        connectionParameters.setDaemonMaxIdleTimeValue(timeoutValue);
-        connectionParameters.setDaemonMaxIdleTimeUnits(timeoutUnits);
+        connectionParamsBuilder.setDaemonMaxIdleTimeValue(timeoutValue);
+        connectionParamsBuilder.setDaemonMaxIdleTimeUnits(timeoutUnits);
         return this;
     }
 
@@ -97,13 +97,14 @@ public class DefaultGradleConnector extends GradleConnector {
      * @return
      */
     public DefaultGradleConnector setVerboseLogging(boolean verboseLogging) {
-        connectionParameters.setVerboseLogging(verboseLogging);
+        connectionParamsBuilder.setVerboseLogging(verboseLogging);
         return this;
     }
 
     public ProjectConnection connect() throws GradleConnectionException {
         LOGGER.debug("Connecting from tooling API consumer version {}", GradleVersion.current().getVersion());
 
+        ConnectionParameters connectionParameters = connectionParamsBuilder.build();
         if (connectionParameters.getProjectDir() == null) {
             throw new IllegalStateException("A project directory must be specified before creating a connection.");
         }
@@ -116,8 +117,4 @@ public class DefaultGradleConnector extends GradleConnector {
     ConnectionFactory getConnectionFactory() {
         return connectionFactory;
     }
-
-    void setDistribution(Distribution distribution) {
-        this.distribution = distribution;
-    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultModelBuilder.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultModelBuilder.java
index a4ff873..1fb64b1 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,33 +15,31 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.tooling.*;
-import org.gradle.tooling.internal.consumer.async.AsyncConnection;
+import org.gradle.tooling.GradleConnectionException;
+import org.gradle.tooling.ModelBuilder;
+import org.gradle.tooling.ResultHandler;
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor;
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
-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;
 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> {
+public class DefaultModelBuilder<T> extends AbstractLongRunningOperation<DefaultModelBuilder<T>> implements ModelBuilder<T> {
     private final Class<T> modelType;
-    private final Class<P> protocolType;
-    private final AsyncConnection connection;
-    private final ProtocolToModelAdapter adapter;
-    private ConsumerOperationParameters operationParameters;
+    private final AsyncConsumerActionExecutor connection;
 
-    public DefaultModelBuilder(Class<T> modelType, Class<P> protocolType, AsyncConnection connection, ProtocolToModelAdapter adapter, ConnectionParameters parameters) {
-        operationParameters = new ConsumerOperationParameters(parameters);
+    public DefaultModelBuilder(Class<T> modelType, AsyncConsumerActionExecutor connection, ConnectionParameters parameters) {
+        super(parameters);
         this.modelType = modelType;
-        this.protocolType = protocolType;
         this.connection = connection;
-        this.adapter = adapter;
+    }
+
+    @Override
+    protected DefaultModelBuilder<T> getThis() {
+        return this;
     }
 
     public T get() throws GradleConnectionException {
@@ -51,73 +49,35 @@ 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.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());
-                if (!(failure instanceof UnsupportedMethodException)
-                        && failure instanceof UnsupportedOperationException) {
-                    message += "\n" + Exceptions.INCOMPATIBLE_VERSION_HINT;
-                }
-                return message;
+        final ConsumerOperationParameters operationParameters = getConsumerOperationParameters();
+        connection.run(new ConsumerAction<T>() {
+            public ConsumerOperationParameters getParameters() {
+                return operationParameters;
             }
-        });
-    }
-
-    public DefaultModelBuilder<T, P> withArguments(String... arguments) {
-        operationParameters.setArguments(arguments);
-        return this;
-    }
-
-    public DefaultModelBuilder<T, P> setStandardOutput(OutputStream outputStream) {
-        operationParameters.setStandardOutput(outputStream);
-        return this;
-    }
-
-    public DefaultModelBuilder<T, P> setStandardError(OutputStream outputStream) {
-        operationParameters.setStandardError(outputStream);
-        return this;
-    }
-
-    public DefaultModelBuilder<T, P> setStandardInput(InputStream inputStream) {
-        operationParameters.setStandardInput(inputStream);
-        return this;
-    }
-
-    public DefaultModelBuilder<T, P> setJavaHome(File javaHome) {
-        operationParameters.setJavaHome(javaHome);
-        return this;
-    }
-
-    public DefaultModelBuilder<T, P> setJvmArguments(String... jvmArguments) {
-        operationParameters.setJvmArguments(jvmArguments);
-        return this;
-    }
 
-    public DefaultModelBuilder<T, P> addProgressListener(ProgressListener listener) {
-        operationParameters.addProgressListener(listener);
-        return this;
+            public T run(ConsumerConnection connection) {
+                return connection.run(modelType, operationParameters);
+            }
+        }, new ResultHandlerAdapter<T>(handler));
     }
 
-    public DefaultModelBuilder<T, P> forTasks(String... tasks) {
-        operationParameters.setTasks(Arrays.asList(tasks));
+    public DefaultModelBuilder<T> forTasks(String... tasks) {
+        operationParamsBuilder.setTasks(Arrays.asList(tasks));
         return this;
     }
 
-    private class ProtocolToModelAdaptingHandler implements ResultHandler<P> {
-        private final ResultHandler<? super T> handler;
-
-        public ProtocolToModelAdaptingHandler(ResultHandler<? super T> handler) {
-            this.handler = handler;
+    private class ResultHandlerAdapter<T> extends org.gradle.tooling.internal.consumer.ResultHandlerAdapter<T> {
+        public ResultHandlerAdapter(ResultHandler<? super T> handler) {
+            super(handler);
         }
 
-        public void onComplete(P result) {
-            handler.onComplete(adapter.adapt(modelType, result, new ConsumerPropertyHandler(connection.getVersionDetails())));
-        }
-
-        public void onFailure(GradleConnectionException failure) {
-            handler.onFailure(failure);
+        @Override
+        protected String connectionFailureMessage(Throwable failure) {
+            String message = String.format("Could not fetch model of type '%s' using %s.", modelType.getSimpleName(), connection.getDisplayName());
+            if (!(failure instanceof UnsupportedMethodException) && failure instanceof UnsupportedOperationException) {
+                message += "\n" + Exceptions.INCOMPATIBLE_VERSION_HINT;
+            }
+            return message;
         }
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java
index 663ea7d..198a581 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java
@@ -16,52 +16,42 @@
 package org.gradle.tooling.internal.consumer;
 
 import org.gradle.tooling.*;
-import org.gradle.tooling.internal.consumer.async.AsyncConnection;
-import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
-import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
-import org.gradle.tooling.model.Model;
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor;
 
 class DefaultProjectConnection implements ProjectConnection {
-    private final AsyncConnection connection;
-    private final ModelMapping modelMapping = new ModelMapping();
-    private ProtocolToModelAdapter adapter;
+    private final AsyncConsumerActionExecutor connection;
     private final ConnectionParameters parameters;
 
-    public DefaultProjectConnection(AsyncConnection connection, ProtocolToModelAdapter adapter, ConnectionParameters parameters) {
+    public DefaultProjectConnection(AsyncConsumerActionExecutor connection, ConnectionParameters parameters) {
         this.connection = connection;
         this.parameters = parameters;
-        this.adapter = adapter;
     }
 
     public void close() {
         connection.stop();
     }
 
-    public <T extends Model> T getModel(Class<T> viewType) {
-        return model(viewType).get();
+    public <T> T getModel(Class<T> modelType) {
+        return model(modelType).get();
     }
 
-    public <T extends Model> void getModel(final Class<T> viewType, final ResultHandler<? super T> handler) {
-        model(viewType).get(handler);
+    public <T> void getModel(final Class<T> modelType, final ResultHandler<? super T> handler) {
+        model(modelType).get(handler);
     }
 
     public BuildLauncher newBuild() {
         return new DefaultBuildLauncher(connection, parameters);
     }
 
-    public <T extends Model> ModelBuilder<T> model(Class<T> modelType) {
-        return new DefaultModelBuilder<T, Class>(modelType, mapToProtocol(modelType), connection, adapter, parameters);
+    public <T> ModelBuilder<T> model(Class<T> modelType) {
+        if (!modelType.isInterface()) {
+            throw new IllegalArgumentException(String.format("Cannot fetch a model of type '%s' as this type is not an interface.", modelType.getName()));
+        }
+        return new DefaultModelBuilder<T>(modelType, connection, parameters);
     }
 
-    private Class mapToProtocol(Class<? extends Model> viewType) {
-        Class protocolViewType = modelMapping.getInternalType(viewType);
-        if (protocolViewType == null) {
-            throw new UnknownModelException(
-                    "Unknown model: '" + viewType.getSimpleName() + "'.\n"
-                        + "Most likely you are trying to acquire a model for a class that is not a valid Tooling API model class.\n"
-                        + "Review the documentation on the version of Tooling API you use to find out what models can be build."
-            );
-        }
-        return protocolViewType;
+    public <T> BuildActionExecuter<T> action(final BuildAction<T> buildAction) {
+        return new DefaultBuildActionExecuter<T>(buildAction, connection, parameters);
     }
+
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
index e04002f..b4eb157 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
@@ -18,8 +18,10 @@ package org.gradle.tooling.internal.consumer;
 import org.gradle.internal.classpath.ClassPath;
 import org.gradle.logging.ProgressLoggerFactory;
 
+import java.io.File;
+
 public interface Distribution {
     String getDisplayName();
 
-    ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory);
+    ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory, File userHomeDir);
 }
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 50b1dda..cdce02b 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
@@ -34,12 +34,6 @@ import java.util.LinkedHashSet;
 import java.util.Set;
 
 public class DistributionFactory {
-    private final File userHomeDir;
-
-    public DistributionFactory(File userHomeDir) {
-        this.userHomeDir = userHomeDir;
-    }
-
     /**
      * Returns the default distribution to use for the specified project.
      */
@@ -88,7 +82,7 @@ public class DistributionFactory {
         return getDistribution(distUri);
     }
 
-    private class ZippedDistribution implements Distribution {
+    private static class ZippedDistribution implements Distribution {
         private InstalledDistribution installedDistribution;
         private final WrapperConfiguration wrapperConfiguration;
 
@@ -100,11 +94,12 @@ public class DistributionFactory {
             return String.format("Gradle distribution '%s'", wrapperConfiguration.getDistribution());
         }
 
-        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory, File userHomeDir) {
             if (installedDistribution == null) {
                 File installDir;
                 try {
-                    Install install = new Install(new ProgressReportingDownload(progressLoggerFactory), new PathAssembler(userHomeDir));
+                    File realUserHomeDir = userHomeDir != null ? userHomeDir : GradleUserHomeLookup.gradleUserHome();
+                    Install install = new Install(new ProgressReportingDownload(progressLoggerFactory), new PathAssembler(realUserHomeDir));
                     installDir = install.createDist(wrapperConfiguration);
                 } catch (FileNotFoundException e) {
                     throw new IllegalArgumentException(String.format("The specified %s does not exist.", getDisplayName()), e);
@@ -113,7 +108,7 @@ public class DistributionFactory {
                 }
                 installedDistribution = new InstalledDistribution(installDir, getDisplayName(), getDisplayName());
             }
-            return installedDistribution.getToolingImplementationClasspath(progressLoggerFactory);
+            return installedDistribution.getToolingImplementationClasspath(progressLoggerFactory, userHomeDir);
         }
     }
 
@@ -151,7 +146,7 @@ public class DistributionFactory {
             return displayName;
         }
 
-        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory, File userHomeDir) {
             ProgressLogger progressLogger = progressLoggerFactory.newOperation(DistributionFactory.class);
             progressLogger.setDescription("Validate distribution");
             progressLogger.started();
@@ -188,7 +183,7 @@ public class DistributionFactory {
             return "Gradle classpath distribution";
         }
 
-        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory, File userHomeDir) {
             return new EffectiveClassPath(getClass().getClassLoader());
         }
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java
index 9bfb606..3e945f1 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java
@@ -19,9 +19,6 @@ package org.gradle.tooling.internal.consumer;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.ProgressLoggerFactory;
 
-/**
- * by Szczepan Faber, created at: 12/14/11
- */
 public interface LoggingProvider {
     ListenerManager getListenerManager();
     ProgressLoggerFactory getProgressLoggerFactory();
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ModelProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ModelProvider.java
deleted file mode 100644
index 5b498c8..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ModelProvider.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.consumer;
-
-import org.gradle.tooling.internal.build.VersionOnlyBuildEnvironment;
-import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
-import org.gradle.tooling.internal.consumer.converters.GradleProjectConverter;
-import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
-import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
-import org.gradle.tooling.internal.protocol.InternalBuildEnvironment;
-import org.gradle.tooling.internal.protocol.InternalGradleProject;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.tooling.model.internal.Exceptions;
-
-/**
- * by Szczepan Faber, created at: 12/21/11
- */
-public class ModelProvider {
-
-    public <T> T provide(ConsumerConnection connection, Class<T> type, ConsumerOperationParameters operationParameters) {
-        VersionDetails version = connection.getVersionDetails();
-
-        if (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() && 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
-            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.run(EclipseProjectVersion3.class, operationParameters);
-            GradleProject gradleProject = new GradleProjectConverter().convert(project);
-            return (T) gradleProject;
-        }
-        return connection.run(type, operationParameters);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
index 6797a1d..070ebfd 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
@@ -16,9 +16,9 @@
 
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.internal.concurrent.Synchronizer;
 import org.gradle.internal.Factory;
 import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.concurrent.Synchronizer;
 import org.gradle.listener.DefaultListenerManager;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.internal.DefaultProgressLoggerFactory;
@@ -26,8 +26,6 @@ import org.gradle.logging.internal.ProgressListener;
 
 /**
  * Thread safe logging provider that needs to be initialized before use.
- * <p>
- * by Szczepan Faber, created at: 12/14/11
  */
 public class SynchronizedLogging implements LoggingProvider {
 
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
deleted file mode 100644
index 707ff54..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConnection.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.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.ResultHandlerVersion1;
-
-public interface AsyncConnection {
-    <T> void run(Class<T> type, ConsumerOperationParameters operationParameters, ResultHandlerVersion1<? super T> handler) throws UnsupportedOperationException, IllegalStateException;
-
-    void stop();
-
-    String getDisplayName();
-    
-    VersionDetails getVersionDetails();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConsumerActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConsumerActionExecutor.java
new file mode 100644
index 0000000..3e69c76
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConsumerActionExecutor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.consumer.async;
+
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction;
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1;
+
+/**
+ * Implementations must be thread-safe.
+ */
+public interface AsyncConsumerActionExecutor {
+    /**
+     * Runs some operation asynchronously against a consumer connection. Notifies the provided handler when
+     * complete. Note that the action may have completed by the time this method returns.
+     *
+     * @throws IllegalStateException When this connection has been stopped or is stopping.
+     */
+    <T> void run(ConsumerAction<? extends T> action, ResultHandlerVersion1<? super T> handler);
+
+    /**
+     * Stops this connection, blocking until all operations on the connection have completed.
+     */
+    void stop();
+
+    String getDisplayName();
+}
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
deleted file mode 100644
index 75c06bd..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java
+++ /dev/null
@@ -1,88 +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.async;
-
-import org.gradle.internal.concurrent.ExecutorFactory;
-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.ResultHandlerVersion1;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Adapts a {@link ConsumerConnection} to an {@link AsyncConnection}.
- */
-public class DefaultAsyncConnection implements AsyncConnection {
-    private final ConsumerConnection connection;
-    private final StoppableExecutor executor;
-    private final AtomicBoolean closed = new AtomicBoolean();
-
-    public DefaultAsyncConnection(ConsumerConnection connection, ExecutorFactory executorFactory) {
-        this.connection = connection;
-        executor = executorFactory.create("Connection worker");
-    }
-
-    public String getDisplayName() {
-        return connection.getDisplayName();
-    }
-
-    public VersionDetails getVersionDetails() {
-        return connection.getVersionDetails();
-    }
-
-    public <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.run(type, operationParameters);
-            }
-        });
-    }
-
-    public void stop() {
-        closed.set(true);
-        executor.stop();
-        connection.stop();
-    }
-
-    private <T> void runLater(final ResultHandlerVersion1<? super T> handler, final ConnectionAction<T> action) {
-        onStartOperation();
-
-        executor.execute(new Runnable() {
-            public void run() {
-                T result;
-                try {
-                    result = action.run();
-                } catch (Throwable t) {
-                    handler.onFailure(t);
-                    return;
-                }
-                handler.onComplete(result);
-            }
-        });
-    }
-
-    private void onStartOperation() {
-        if (closed.get()) {
-            throw new IllegalStateException("This connection has been closed.");
-        }
-    }
-
-    private interface ConnectionAction<T> {
-        T run();
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConsumerActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConsumerActionExecutor.java
new file mode 100644
index 0000000..b3d5aed
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConsumerActionExecutor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.consumer.async;
+
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.ServiceLifecycle;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction;
+import org.gradle.tooling.internal.consumer.connection.ConsumerActionExecutor;
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1;
+
+/**
+ * Adapts a {@link ConsumerActionExecutor} to an {@link AsyncConsumerActionExecutor}.
+ */
+public class DefaultAsyncConsumerActionExecutor implements AsyncConsumerActionExecutor {
+    private final ConsumerActionExecutor actionExecutor;
+    private final StoppableExecutor executor;
+    private final ServiceLifecycle lifecycle;
+
+    public DefaultAsyncConsumerActionExecutor(ConsumerActionExecutor actionExecutor, ExecutorFactory executorFactory) {
+        this.actionExecutor = actionExecutor;
+        executor = executorFactory.create("Connection worker");
+        lifecycle = new ServiceLifecycle(actionExecutor.getDisplayName());
+    }
+
+    public String getDisplayName() {
+        return actionExecutor.getDisplayName();
+    }
+
+    public void stop() {
+        CompositeStoppable.stoppable(lifecycle, executor, actionExecutor).stop();
+    }
+
+    public <T> void run(final ConsumerAction<? extends T> action, final ResultHandlerVersion1<? super T> handler) {
+        lifecycle.use(new Runnable() {
+            public void run() {
+                executor.execute(new Runnable() {
+                    public void run() {
+                        T result;
+                        try {
+                            result = actionExecutor.run(action);
+                        } catch (Throwable t) {
+                            handler.onFailure(t);
+                            return;
+                        }
+                        handler.onComplete(result);
+                    }
+                });
+            }
+        });
+    }
+}
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
index ff11944..5bfbc57 100644
--- 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
@@ -16,15 +16,20 @@
 
 package org.gradle.tooling.internal.consumer.connection;
 
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
 import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
 import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.model.internal.Exceptions;
 
 public abstract class AbstractConsumerConnection implements ConsumerConnection {
     private final ConnectionVersion4 delegate;
+    private final VersionDetails providerMetaData;
 
-    public AbstractConsumerConnection(ConnectionVersion4 delegate) {
+    public AbstractConsumerConnection(ConnectionVersion4 delegate, VersionDetails providerMetaData) {
         this.delegate = delegate;
+        this.providerMetaData = providerMetaData;
     }
 
     public void stop() {
@@ -36,12 +41,16 @@ public abstract class AbstractConsumerConnection implements ConsumerConnection {
     }
 
     public VersionDetails getVersionDetails() {
-        return new VersionDetails(delegate.getMetaData().getVersion());
+        return providerMetaData;
     }
 
     public ConnectionVersion4 getDelegate() {
         return delegate;
     }
 
-    public abstract void configure(ConsumerConnectionParameters connectionParameters);
+    public abstract void configure(ConnectionParameters connectionParameters);
+
+    public <T> T run(BuildAction<T> action, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        throw Exceptions.unsupportedFeature("execution of build actions provided by the tooling API client", getVersionDetails().getVersion(), "1.8");
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractModelProducer.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractModelProducer.java
new file mode 100644
index 0000000..6b9785b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractModelProducer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+
+abstract class AbstractModelProducer implements ModelProducer{
+    protected final ProtocolToModelAdapter adapter;
+    protected final VersionDetails versionDetails;
+    protected final ModelMapping modelMapping;
+
+    public AbstractModelProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping) {
+        this.adapter = adapter;
+        this.versionDetails = versionDetails;
+        this.modelMapping = modelMapping;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractPost12ConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractPost12ConsumerConnection.java
new file mode 100644
index 0000000..843b38a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractPost12ConsumerConnection.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ConnectionParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.ConfigurableConnection;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+
+/**
+ * An adapter for a post Gradle 1.2 provider.
+ */
+public abstract class AbstractPost12ConsumerConnection extends AbstractConsumerConnection {
+    private final ConfigurableConnection configurableConnection;
+
+    public AbstractPost12ConsumerConnection(ConnectionVersion4 delegate, VersionDetails providerMetaData) {
+        super(delegate, providerMetaData);
+        configurableConnection = (ConfigurableConnection) delegate;
+    }
+
+    @Override
+    public void configure(ConnectionParameters connectionParameters) {
+        configurableConnection.configure(connectionParameters);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractPre12ConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractPre12ConsumerConnection.java
new file mode 100644
index 0000000..b8998e0
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractPre12ConsumerConnection.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter.CompatibleIntrospector;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.model.internal.Exceptions;
+
+/**
+ * An adapter to a pre 1.2 provider.
+ */
+public abstract class AbstractPre12ConsumerConnection extends AbstractConsumerConnection {
+    public AbstractPre12ConsumerConnection(ConnectionVersion4 delegate, VersionDetails providerMetaData, ProtocolToModelAdapter adapter) {
+        super(delegate, providerMetaData);
+    }
+
+    @Override
+    public void configure(ConnectionParameters connectionParameters) {
+        new CompatibleIntrospector(getDelegate()).callSafely("configureLogging", connectionParameters.getVerboseLogging());
+    }
+
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        if (type.equals(Void.class)) {
+            doRunBuild(operationParameters);
+            return null;
+        } else {
+            if (operationParameters.getTasks() != null) {
+                throw Exceptions.unsupportedOperationConfiguration("modelBuilder.forTasks()", getVersionDetails().getVersion());
+            }
+            return doGetModel(type, operationParameters);
+        }
+    }
+
+    protected abstract <T> T doGetModel(Class<T> modelType, ConsumerOperationParameters operationParameters);
+
+    protected void doRunBuild(ConsumerOperationParameters operationParameters) {
+        getDelegate().executeBuild(operationParameters, operationParameters);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ActionAwareConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ActionAwareConsumerConnection.java
new file mode 100644
index 0000000..1920bb5
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ActionAwareConsumerConnection.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BuildAction;
+import org.gradle.tooling.BuildActionFailureException;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.protocol.*;
+
+public class ActionAwareConsumerConnection extends ModelBuilderBackedConsumerConnection {
+    private final InternalBuildActionExecutor executor;
+    private final ProtocolToModelAdapter adapter;
+
+    public ActionAwareConsumerConnection(ConnectionVersion4 delegate, ModelMapping modelMapping, ProtocolToModelAdapter adapter) {
+        super(delegate, modelMapping, adapter);
+        this.adapter = adapter;
+        executor = (InternalBuildActionExecutor) delegate;
+    }
+
+    @Override
+    public <T> T run(final BuildAction<T> action, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        BuildResult<T> result;
+        try {
+            result = executor.run(new BuildActionAdapter<T>(action, adapter), operationParameters);
+        } catch (InternalBuildActionFailureException e) {
+            throw new BuildActionFailureException("The supplied build action failed with an exception.", e.getCause());
+        }
+        return result.getModel();
+    }
+
+    private static class BuildActionAdapter<T> implements InternalBuildAction<T> {
+        private final BuildAction<T> action;
+        private final ProtocolToModelAdapter adapter;
+
+        public BuildActionAdapter(BuildAction<T> action, ProtocolToModelAdapter adapter) {
+            this.action = action;
+            this.adapter = adapter;
+        }
+
+        public T execute(final InternalBuildController buildController) {
+            return action.execute(new BuildControllerAdapter(adapter, buildController, new ModelMapping()));
+        }
+    }
+
+}
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
deleted file mode 100644
index a14eb28..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AdaptedConnection.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.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.ConnectionVersion4;
-import org.gradle.tooling.internal.reflect.CompatibleIntrospector;
-
-/**
- * An implementation that wraps a protocol instance that has rigid compatibility policy.
- * <p>
- * by Szczepan Faber, created at: 12/22/11
- */
-public class AdaptedConnection extends AbstractConsumerConnection {
-
-    public AdaptedConnection(ConnectionVersion4 delegate) {
-        super(delegate);
-    }
-
-    public void configure(ConsumerConnectionParameters connectionParameters) {
-        new CompatibleIntrospector(getDelegate()).callSafely("configureLogging", connectionParameters.getVerboseLogging());
-    }
-
-    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
-        if (type.equals(Void.class)) {
-            doRunBuild(operationParameters);
-            return null;
-        } else {
-            return doGetModel(type, operationParameters);
-        }
-    }
-
-    protected  <T> T doGetModel(Class<T> type, ConsumerOperationParameters operationParameters) {
-        return (T) getDelegate().getModel((Class) type, operationParameters);
-    }
-
-    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
index b14db9e..a114aa3 100644
--- 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
@@ -16,25 +16,83 @@
 
 package org.gradle.tooling.internal.consumer.connection;
 
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
+import org.gradle.api.Action;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.adapter.SourceObjectMapping;
+import org.gradle.tooling.internal.consumer.converters.TaskPropertyHandlerFactory;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
 import org.gradle.tooling.internal.protocol.BuildActionRunner;
-import org.gradle.tooling.internal.protocol.ConfigurableConnection;
 import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.build.BuildEnvironment;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
+import org.gradle.tooling.model.idea.BasicIdeaProject;
+import org.gradle.tooling.model.idea.IdeaProject;
+import org.gradle.tooling.model.internal.Exceptions;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
 
-public class BuildActionRunnerBackedConsumerConnection extends AbstractConsumerConnection {
-    private final BuildActionRunner buildActionRunner;
+/**
+ * An adapter for a {@link BuildActionRunner} based provider.
+ */
+public class BuildActionRunnerBackedConsumerConnection extends AbstractPost12ConsumerConnection {
+    private final ModelProducer modelProducer;
 
-    public BuildActionRunnerBackedConsumerConnection(ConnectionVersion4 delegate) {
-        super(delegate);
-        buildActionRunner = (BuildActionRunner) delegate;
+    public BuildActionRunnerBackedConsumerConnection(ConnectionVersion4 delegate, ModelMapping modelMapping, ProtocolToModelAdapter adapter) {
+        super(delegate, new R12VersionDetails(delegate.getMetaData().getVersion()));
+        ModelProducer consumerConnectionBackedModelProducer = new BuildActionRunnerBackedModelProducer(adapter, getVersionDetails(), modelMapping,  (BuildActionRunner) delegate);
+        ModelProducer producerWithGradleBuild = new GradleBuildAdapterProducer(adapter, getVersionDetails(), modelMapping, consumerConnectionBackedModelProducer);
+        modelProducer = new BuildInvocationsAdapterProducer(adapter, getVersionDetails(), modelMapping, producerWithGradleBuild);
     }
 
-    public void configure(ConsumerConnectionParameters connectionParameters) {
-        ((ConfigurableConnection) buildActionRunner).configure(connectionParameters);
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        return modelProducer.produceModel(type, operationParameters);
     }
 
-    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
-        return buildActionRunner.run(type, operationParameters).getModel();
+    private static class R12VersionDetails extends VersionDetails {
+        public R12VersionDetails(String version) {
+            super(version);
+        }
+
+        @Override
+        public boolean supportsGradleProjectModel() {
+            return true;
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            return modelType.equals(ProjectOutcomes.class)
+                    || modelType.equals(HierarchicalEclipseProject.class)
+                    || modelType.equals(EclipseProject.class)
+                    || modelType.equals(IdeaProject.class)
+                    || modelType.equals(BasicIdeaProject.class)
+                    || modelType.equals(BuildEnvironment.class)
+                    || modelType.equals(GradleProject.class)
+                    || modelType.equals(Void.class);
+        }
+    }
+
+    private class BuildActionRunnerBackedModelProducer extends AbstractModelProducer {
+        private final BuildActionRunner buildActionRunner;
+        private final Action<SourceObjectMapping> mapper;
+
+        public BuildActionRunnerBackedModelProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, BuildActionRunner buildActionRunner) {
+            super(adapter, versionDetails, modelMapping);
+            this.buildActionRunner = buildActionRunner;
+            mapper = new TaskPropertyHandlerFactory().forVersion(versionDetails);
+        }
+
+        public <T> T produceModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+            if (!versionDetails.maySupportModel(type)) {
+                //don't bother asking the provider for this model
+                throw Exceptions.unsupportedModel(type, versionDetails.getVersion());
+
+            }
+            Class<?> protocolType = modelMapping.getProtocolType(type);
+            Object model = buildActionRunner.run(protocolType, operationParameters).getModel();
+            return adapter.adapt(type, model, mapper);
+        }
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildControllerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildControllerAdapter.java
new file mode 100644
index 0000000..e77afa4
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildControllerAdapter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BuildController;
+import org.gradle.tooling.UnknownModelException;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.protocol.BuildResult;
+import org.gradle.tooling.internal.protocol.InternalBuildController;
+import org.gradle.tooling.internal.protocol.InternalUnsupportedModelException;
+import org.gradle.tooling.internal.protocol.ModelIdentifier;
+import org.gradle.tooling.model.gradle.GradleBuild;
+import org.gradle.tooling.model.Model;
+import org.gradle.tooling.model.internal.Exceptions;
+
+class BuildControllerAdapter implements BuildController {
+    private final InternalBuildController buildController;
+    private final ProtocolToModelAdapter adapter;
+    private final ModelMapping modelMapping;
+
+    public BuildControllerAdapter(ProtocolToModelAdapter adapter, InternalBuildController buildController, ModelMapping modelMapping) {
+        this.adapter = adapter;
+        this.buildController = buildController;
+        this.modelMapping = modelMapping;
+    }
+
+    public <T> T getModel(Class<T> modelType) throws UnknownModelException {
+        return getModel(null, modelType);
+    }
+
+    public <T> T findModel(Class<T> modelType) {
+        try {
+            return getModel(modelType);
+        } catch (UnknownModelException e) {
+            // Ignore
+            return null;
+        }
+    }
+
+    public GradleBuild getBuildModel() {
+        return getModel(null, GradleBuild.class);
+    }
+
+    public <T> T findModel(Model target, Class<T> modelType) {
+        try {
+            return getModel(target, modelType);
+        } catch (UnknownModelException e) {
+            // Ignore
+            return null;
+        }
+    }
+
+    public <T> T getModel(Model target, Class<T> modelType) throws UnknownModelException {
+        ModelIdentifier modelIdentifier = modelMapping.getModelIdentifierFromModelType(modelType);
+        Object originalTarget = target == null ? null : adapter.unpack(target);
+
+        BuildResult<?> result;
+        try {
+            result = buildController.getModel(originalTarget, modelIdentifier);
+        } catch (InternalUnsupportedModelException e) {
+            throw Exceptions.unknownModel(modelType, e);
+        }
+
+        return adapter.adapt(modelType, result.getModel());
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildInvocationsAdapterProducer.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildInvocationsAdapterProducer.java
new file mode 100644
index 0000000..39b5078
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildInvocationsAdapterProducer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.converters.BuildInvocationsConverter;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.gradle.DefaultBuildInvocations;
+import org.gradle.tooling.internal.gradle.DefaultGradleTask;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.gradle.BuildInvocations;
+import org.gradle.tooling.model.internal.Exceptions;
+
+public class BuildInvocationsAdapterProducer extends AbstractModelProducer {
+    private final ModelProducer delegate;
+
+    public BuildInvocationsAdapterProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, ModelProducer delegate) {
+        super(adapter, versionDetails, modelMapping);
+        this.delegate = delegate;
+    }
+
+    public <T> T produceModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+        if (type.getName().equals(BuildInvocations.class.getName()) && !versionDetails.maySupportModel(type)) {
+            if (!versionDetails.maySupportModel(GradleProject.class)) {
+                throw Exceptions.unsupportedModel(type, versionDetails.getVersion());
+            }
+            GradleProject gradleProject = delegate.produceModel(GradleProject.class, operationParameters);
+            DefaultBuildInvocations<DefaultGradleTask> convert = new BuildInvocationsConverter().convert(gradleProject);
+            return adapter.adapt(type, convert);
+        }
+        return delegate.produceModel(type, operationParameters);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConnectionVersion4BackedConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConnectionVersion4BackedConsumerConnection.java
new file mode 100644
index 0000000..c7549d6
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConnectionVersion4BackedConsumerConnection.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.connection;
+
+import org.gradle.api.Action;
+import org.gradle.internal.Actions;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.adapter.SourceObjectMapping;
+import org.gradle.tooling.internal.build.VersionOnlyBuildEnvironment;
+import org.gradle.tooling.internal.consumer.converters.GradleProjectConverter;
+import org.gradle.tooling.internal.consumer.converters.PropertyHandlerFactory;
+import org.gradle.tooling.internal.consumer.converters.TaskPropertyHandlerFactory;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.build.BuildEnvironment;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
+import org.gradle.tooling.model.idea.BasicIdeaProject;
+import org.gradle.tooling.model.idea.IdeaProject;
+import org.gradle.tooling.model.internal.Exceptions;
+import org.gradle.util.GradleVersion;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An adapter that wraps a {@link ConnectionVersion4} based provider.
+ */
+public class ConnectionVersion4BackedConsumerConnection extends AbstractPre12ConsumerConnection {
+    private final ModelProducer modelProducer;
+
+    public ConnectionVersion4BackedConsumerConnection(ConnectionVersion4 delegate, ModelMapping modelMapping, ProtocolToModelAdapter adapter) {
+        super(delegate, getMetaData(delegate), adapter);
+        ModelProducer consumerConnectionBackedModelProducer = new ConnectionVersion4BackedModelProducer(adapter, getVersionDetails(), modelMapping, delegate);
+        ModelProducer gradleProjectAdapterProducer = new GradleProjectAdapterProducer(adapter, getVersionDetails(), modelMapping, consumerConnectionBackedModelProducer);
+        ModelProducer producerWithGradleBuild = new GradleBuildAdapterProducer(adapter, getVersionDetails(), modelMapping, gradleProjectAdapterProducer);
+        modelProducer = new BuildInvocationsAdapterProducer(adapter, getVersionDetails(), modelMapping, producerWithGradleBuild);
+    }
+
+    private static VersionDetails getMetaData(final ConnectionVersion4 delegate) {
+        GradleVersion version = GradleVersion.version(delegate.getMetaData().getVersion());
+        if (version.compareTo(GradleVersion.version("1.0-milestone-5")) < 0) {
+            return new R10M3VersionDetails(delegate);
+        } else {
+            return new R10M5VersionDetails(delegate);
+        }
+    }
+
+    @Override
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        VersionDetails versionDetails = getVersionDetails();
+        if (operationParameters.getJavaHome() != null) {
+            throw Exceptions.unsupportedOperationConfiguration("modelBuilder.setJavaHome() and buildLauncher.setJavaHome()", versionDetails.getVersion());
+        }
+        if (operationParameters.getJvmArguments() != null) {
+            throw Exceptions.unsupportedOperationConfiguration("modelBuilder.setJvmArguments() and buildLauncher.setJvmArguments()", versionDetails.getVersion());
+        }
+        if (operationParameters.getStandardInput() != null) {
+            throw Exceptions.unsupportedOperationConfiguration("modelBuilder.setStandardInput() and buildLauncher.setStandardInput()", versionDetails.getVersion());
+        }
+        OutputStream out = operationParameters.getStandardOutput();
+        if (out != null) {
+            try {
+                String deprecationMessage = String.format("Connecting to Gradle version %s from the Gradle tooling API has been deprecated and is scheduled to be removed in version 2.0 of the Gradle tooling API%n", versionDetails.getVersion());
+                out.write(deprecationMessage.getBytes());
+            } catch (IOException e) {
+                throw new RuntimeException("Cannot write to stream", e);
+            }
+        }
+        return super.run(type, operationParameters);
+    }
+
+    @Override
+    protected <T> T doGetModel(Class<T> modelType, ConsumerOperationParameters operationParameters) {
+        return modelProducer.produceModel(modelType, operationParameters);
+    }
+
+    private static class R10M3VersionDetails extends VersionDetails {
+        public R10M3VersionDetails(ConnectionVersion4 delegate) {
+            super(delegate.getMetaData().getVersion());
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            return modelType.equals(HierarchicalEclipseProject.class) || modelType.equals(EclipseProjectVersion3.class) || modelType.equals(EclipseProject.class) || modelType.equals(Void.class);
+        }
+    }
+
+    private static class R10M5VersionDetails extends VersionDetails {
+        public R10M5VersionDetails(ConnectionVersion4 delegate) {
+            super(delegate.getMetaData().getVersion());
+        }
+
+        @Override
+        public boolean supportsGradleProjectModel() {
+            return true;
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            return modelType.equals(HierarchicalEclipseProject.class)
+                    || modelType.equals(EclipseProject.class)
+                    || modelType.equals(IdeaProject.class)
+                    || modelType.equals(BasicIdeaProject.class)
+                    || modelType.equals(GradleProject.class)
+                    || modelType.equals(Void.class);
+        }
+    }
+
+    private class ConnectionVersion4BackedModelProducer extends AbstractModelProducer {
+        private final ConnectionVersion4 delegate;
+        private final Action<SourceObjectMapping> mapper;
+
+        public ConnectionVersion4BackedModelProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, ConnectionVersion4 delegate) {
+            super(adapter, versionDetails, modelMapping);
+            this.delegate = delegate;
+            mapper = Actions.composite(
+                    new PropertyHandlerFactory().forVersion(versionDetails),
+                    new TaskPropertyHandlerFactory().forVersion(versionDetails));
+        }
+
+        public <T> T produceModel(Class<T> modelType, ConsumerOperationParameters operationParameters) {
+            if (modelType == BuildEnvironment.class && !versionDetails.maySupportModel(BuildEnvironment.class)) {
+                //early versions of provider do not support BuildEnvironment model
+                //since we know the gradle version at least we can give back some result
+                return adapter.adapt(modelType, new VersionOnlyBuildEnvironment(versionDetails.getVersion()), mapper);
+            }
+            if (!versionDetails.maySupportModel(modelType)) {
+                //don't bother asking the provider for this model
+                throw Exceptions.unsupportedModel(modelType, versionDetails.getVersion());
+            }
+            Class<? extends ProjectVersion3> protocolType = modelMapping.getProtocolType(modelType).asSubclass(ProjectVersion3.class);
+            final ProjectVersion3 model = delegate.getModel(protocolType, operationParameters);
+            return adapter.adapt(modelType, model, mapper);
+        }
+    }
+
+    private class GradleProjectAdapterProducer extends AbstractModelProducer {
+        private final ModelProducer delegate;
+
+        public GradleProjectAdapterProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, ModelProducer delegate) {
+            super(adapter, versionDetails, modelMapping);
+            this.delegate = delegate;
+        }
+
+        public <T> T produceModel(Class<T> modelType, ConsumerOperationParameters operationParameters) {
+            if (modelType == GradleProject.class && !versionDetails.maySupportModel(GradleProject.class)) {
+                //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 = delegate.produceModel(EclipseProjectVersion3.class, operationParameters);
+                return adapter.adapt(modelType, new GradleProjectConverter().convert(project));
+            }
+            return delegate.produceModel(modelType, operationParameters);
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerAction.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerAction.java
new file mode 100644
index 0000000..47f1764
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerAction.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public interface ConsumerAction<T> {
+    ConsumerOperationParameters getParameters();
+
+    T run(ConsumerConnection connection);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerActionExecutor.java
new file mode 100644
index 0000000..ddc4f06
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerActionExecutor.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.tooling.internal.consumer.connection;
+
+/**
+ * Implementations must be thread-safe.
+ */
+public interface ConsumerActionExecutor {
+
+    void stop();
+    
+    String getDisplayName();
+
+    <T> T run(ConsumerAction<T> action) throws UnsupportedOperationException, IllegalStateException;
+}
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 3a7494e..49ab2b1 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
@@ -16,19 +16,19 @@
 
 package org.gradle.tooling.internal.consumer.connection;
 
+import org.gradle.tooling.BuildAction;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
-import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
 
 /**
- * by Szczepan Faber, created at: 12/22/11
+ * Implementations must be thread-safe.
  */
 public interface ConsumerConnection {
 
     void stop();
     
     String getDisplayName();
-    
-    VersionDetails getVersionDetails();
 
     <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException;
+
+    <T> T run(BuildAction<T> action, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException;
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnectionMetadata.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnectionMetadata.java
deleted file mode 100644
index cf5d37b..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnectionMetadata.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.consumer.connection;
-
-import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
-
-/**
- * by Szczepan Faber, created at: 1/13/12
- */
-public class ConsumerConnectionMetadata {
-
-    private final String displayName;
-    private final String version;
-
-    public ConsumerConnectionMetadata(String displayName, String version) {
-        this.displayName = displayName;
-        this.version = version;
-    }
-
-    public String getDisplayName() {
-        return displayName;
-    }
-
-    public VersionDetails getVersionDetails() {
-        if (version == null) {
-            throw new UnsupportedOperationException();
-        }
-        return new VersionDetails(version);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/GradleBuildAdapterProducer.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/GradleBuildAdapterProducer.java
new file mode 100644
index 0000000..1427507
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/GradleBuildAdapterProducer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.converters.GradleBuildConverter;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.gradle.DefaultGradleBuild;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.gradle.GradleBuild;
+
+public class GradleBuildAdapterProducer extends AbstractModelProducer {
+    private final ModelProducer delegate;
+
+    public GradleBuildAdapterProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, ModelProducer delegate) {
+        super(adapter, versionDetails, modelMapping);
+        this.delegate = delegate;
+    }
+
+    public <T> T produceModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+        if (type.getName().equals(GradleBuild.class.getName()) && !versionDetails.maySupportModel(type)) {
+            GradleProject gradleProject = delegate.produceModel(GradleProject.class, operationParameters);
+            final DefaultGradleBuild convert = new GradleBuildConverter().convert(gradleProject);
+            return adapter.adapt(type, convert);
+        }
+        return delegate.produceModel(type, operationParameters);
+    }
+}
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
index 26e7b05..c4258c5 100644
--- 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
@@ -16,20 +16,80 @@
 
 package org.gradle.tooling.internal.consumer.connection;
 
+import org.gradle.api.Action;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.adapter.SourceObjectMapping;
+import org.gradle.tooling.internal.consumer.converters.TaskPropertyHandlerFactory;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
 import org.gradle.tooling.internal.protocol.ConnectionVersion4;
 import org.gradle.tooling.internal.protocol.InternalConnection;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.build.BuildEnvironment;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
+import org.gradle.tooling.model.idea.BasicIdeaProject;
+import org.gradle.tooling.model.idea.IdeaProject;
+import org.gradle.tooling.model.internal.Exceptions;
 
-public class InternalConnectionBackedConsumerConnection extends AdaptedConnection {
-    private final InternalConnection connection;
+/**
+ * An adapter for a {@link InternalConnection} based provider.
+ */
+public class InternalConnectionBackedConsumerConnection extends AbstractPre12ConsumerConnection {
+    private final ModelProducer modelProducer;
 
-    public InternalConnectionBackedConsumerConnection(ConnectionVersion4 delegate) {
-        super(delegate);
-        connection = (InternalConnection) delegate;
+    public InternalConnectionBackedConsumerConnection(ConnectionVersion4 delegate, ModelMapping modelMapping, ProtocolToModelAdapter adapter) {
+        super(delegate, new R10M8VersionDetails(delegate.getMetaData().getVersion()), adapter);
+        ModelProducer consumerConnectionBackedModelProducer = new InternalConnectionBackedModelProducer(adapter, getVersionDetails(), modelMapping, (InternalConnection) delegate);
+        ModelProducer producerWithGradleBuild = new GradleBuildAdapterProducer(adapter, getVersionDetails(), modelMapping, consumerConnectionBackedModelProducer);
+        modelProducer = new BuildInvocationsAdapterProducer(adapter, getVersionDetails(), modelMapping, producerWithGradleBuild);
     }
 
     @Override
-    protected <T> T doGetModel(Class<T> type, ConsumerOperationParameters operationParameters) {
-        return connection.getTheModel(type, operationParameters);
+    protected <T> T doGetModel(Class<T> modelType, ConsumerOperationParameters operationParameters) {
+        return modelProducer.produceModel(modelType, operationParameters);
+    }
+
+    private static class R10M8VersionDetails extends VersionDetails {
+        public R10M8VersionDetails(String version) {
+            super(version);
+        }
+
+        @Override
+        public boolean supportsGradleProjectModel() {
+            return true;
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            return modelType.equals(Void.class)
+                    || modelType.equals(HierarchicalEclipseProject.class)
+                    || modelType.equals(EclipseProject.class)
+                    || modelType.equals(IdeaProject.class)
+                    || modelType.equals(BasicIdeaProject.class)
+                    || modelType.equals(GradleProject.class)
+                    || modelType.equals(BuildEnvironment.class);
+        }
+    }
+
+    private class InternalConnectionBackedModelProducer extends AbstractModelProducer {
+        private final InternalConnection delegate;
+        private final Action<SourceObjectMapping> mapper;
+
+        public InternalConnectionBackedModelProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, InternalConnection delegate) {
+            super(adapter, versionDetails, modelMapping);
+            this.delegate = delegate;
+            this.mapper = new TaskPropertyHandlerFactory().forVersion(versionDetails);
+        }
+
+        public <T> T produceModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+            if (!versionDetails.maySupportModel(type)) {
+                //don't bother asking the provider for this model
+                throw Exceptions.unsupportedModel(type, versionDetails.getVersion());
+            }
+            Class<?> protocolType = modelMapping.getProtocolType(type);
+            return adapter.adapt(type, delegate.getTheModel(protocolType, operationParameters), mapper);
+        }
     }
 }
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
deleted file mode 100644
index afd99f0..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConnection.java
+++ /dev/null
@@ -1,143 +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.connection;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.tooling.internal.consumer.Distribution;
-import org.gradle.tooling.internal.consumer.LoggingProvider;
-import org.gradle.tooling.internal.consumer.ModelProvider;
-import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
-import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
-import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Creates the actual connection implementation on demand.
- */
-public class LazyConnection implements ConsumerConnection {
-    private final Distribution distribution;
-    private final ToolingImplementationLoader implementationLoader;
-    private final LoggingProvider loggingProvider;
-
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private Set<Thread> executing = new HashSet<Thread>();
-    private boolean stopped;
-    private ConsumerConnection connection;
-
-    ConsumerConnectionParameters connectionParameters;
-
-    ModelProvider modelProvider = new ModelProvider();
-
-    public LazyConnection(Distribution distribution, ToolingImplementationLoader implementationLoader, LoggingProvider loggingProvider, boolean verboseLogging) {
-        this.distribution = distribution;
-        this.implementationLoader = implementationLoader;
-        this.loggingProvider = loggingProvider;
-        this.connectionParameters = new ConsumerConnectionParameters(verboseLogging);
-    }
-
-    public void stop() {
-        ConsumerConnection connection = null;
-        lock.lock();
-        try {
-            stopped = true;
-            while (!executing.isEmpty()) {
-                try {
-                    condition.await();
-                } catch (InterruptedException e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-            }
-            connection = this.connection;
-            this.connection = null;
-        } finally {
-            lock.unlock();
-        }
-        if (connection != null) {
-            connection.stop();
-        }
-    }
-
-    public ConsumerConnectionMetadata getMetaData() {
-        return new ConsumerConnectionMetadata(distribution.getDisplayName(), null);
-    }
-
-    public String getDisplayName() {
-        return distribution.getDisplayName();
-    }
-
-    public VersionDetails getVersionDetails() {
-        if (connection == null) {
-            throw new IllegalStateException("Cannot provide version details just yet. You need to execute build or acquire some model first.");
-        }
-        return connection.getVersionDetails();
-    }
-
-    public <T> T run(final Class<T> type, final ConsumerOperationParameters operationParameters) {
-        return withConnection(new ConnectionAction<T>() {
-            public T run(ConsumerConnection connection) {
-                return modelProvider.provide(connection, type, operationParameters);
-            }
-        });
-    }
-
-    private <T> T withConnection(ConnectionAction<T> action) {
-        try {
-            ConsumerConnection connection = onStartAction();
-            return action.run(connection);
-        } finally {
-            onEndAction();
-        }
-    }
-
-    private ConsumerConnection onStartAction() {
-        lock.lock();
-        try {
-            if (stopped) {
-                throw new IllegalStateException("This connection has been stopped.");
-            }
-            executing.add(Thread.currentThread());
-            if (connection == null) {
-                // Hold the lock while creating the connection. Not generally good form.
-                // In this instance, blocks other threads from creating the connection at the same time
-                connection = implementationLoader.create(distribution, loggingProvider.getProgressLoggerFactory(), connectionParameters);
-            }
-            return connection;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void onEndAction() {
-        lock.lock();
-        try {
-            executing.remove(Thread.currentThread());
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private interface ConnectionAction<T> {
-        T run(ConsumerConnection connection);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConsumerActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConsumerActionExecutor.java
new file mode 100644
index 0000000..ee6422e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConsumerActionExecutor.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.consumer.connection;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.LoggingProvider;
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Creates the actual executor implementation on demand.
+ */
+public class LazyConsumerActionExecutor implements ConsumerActionExecutor {
+    private final Distribution distribution;
+    private final ToolingImplementationLoader implementationLoader;
+    private final LoggingProvider loggingProvider;
+
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final Set<Thread> executing = new HashSet<Thread>();
+    private boolean stopped;
+    private ConsumerConnection connection;
+
+    private final ConnectionParameters connectionParameters;
+
+    public LazyConsumerActionExecutor(Distribution distribution, ToolingImplementationLoader implementationLoader, LoggingProvider loggingProvider, ConnectionParameters connectionParameters) {
+        this.distribution = distribution;
+        this.implementationLoader = implementationLoader;
+        this.loggingProvider = loggingProvider;
+        this.connectionParameters = connectionParameters;
+    }
+
+    public void stop() {
+        ConsumerConnection connection = null;
+        lock.lock();
+        try {
+            stopped = true;
+            while (!executing.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+            connection = this.connection;
+            this.connection = null;
+        } finally {
+            lock.unlock();
+        }
+        if (connection != null) {
+            connection.stop();
+        }
+    }
+
+    public String getDisplayName() {
+        return distribution.getDisplayName();
+    }
+
+    public <T> T run(ConsumerAction<T> action) throws UnsupportedOperationException, IllegalStateException {
+        try {
+            ConsumerConnection connection = onStartAction();
+            return action.run(connection);
+        } finally {
+            onEndAction();
+        }
+    }
+
+    private ConsumerConnection onStartAction() {
+        lock.lock();
+        try {
+            if (stopped) {
+                throw new IllegalStateException("This connection has been stopped.");
+            }
+            executing.add(Thread.currentThread());
+            if (connection == null) {
+                // Hold the lock while creating the connection. Not generally good form.
+                // In this instance, blocks other threads from creating the connection at the same time
+                connection = implementationLoader.create(distribution, loggingProvider.getProgressLoggerFactory(), connectionParameters);
+            }
+            return connection;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onEndAction() {
+        lock.lock();
+        try {
+            executing.remove(Thread.currentThread());
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+}
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
deleted file mode 100644
index 1090170..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConnection.java
+++ /dev/null
@@ -1,54 +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.connection;
-
-import org.gradle.tooling.internal.consumer.SynchronizedLogging;
-import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
-import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
-
-/**
- * The idea is to initialize the logging infrastructure before we actually build the model or run a build.
- * <p>
- * by Szczepan Faber, created at: 12/14/11
- */
-public class LoggingInitializerConnection implements ConsumerConnection {
-
-    private final ConsumerConnection connection;
-    private final SynchronizedLogging synchronizedLogging;
-
-    public LoggingInitializerConnection(ConsumerConnection connection, SynchronizedLogging synchronizedLogging) {
-        this.connection = connection;
-        this.synchronizedLogging = synchronizedLogging;
-    }
-
-    public void stop() {
-        connection.stop();
-    }
-
-    public String getDisplayName() {
-        return connection.getDisplayName();
-    }
-
-    public VersionDetails getVersionDetails() {
-        return connection.getVersionDetails();
-    }
-
-    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
-        synchronizedLogging.init();
-        return connection.run(type, operationParameters);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConsumerActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConsumerActionExecutor.java
new file mode 100644
index 0000000..5483805
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConsumerActionExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.SynchronizedLogging;
+
+/**
+ * The idea is to initialize the logging infrastructure before we actually build the model or run a build.
+ */
+public class LoggingInitializerConsumerActionExecutor implements ConsumerActionExecutor {
+
+    private final ConsumerActionExecutor actionExecutor;
+    private final SynchronizedLogging synchronizedLogging;
+
+    public LoggingInitializerConsumerActionExecutor(ConsumerActionExecutor actionExecutor, SynchronizedLogging synchronizedLogging) {
+        this.actionExecutor = actionExecutor;
+        this.synchronizedLogging = synchronizedLogging;
+    }
+
+    public void stop() {
+        actionExecutor.stop();
+    }
+
+    public String getDisplayName() {
+        return actionExecutor.getDisplayName();
+    }
+
+    public <T> T run(ConsumerAction<T> action) throws UnsupportedOperationException, IllegalStateException {
+        synchronizedLogging.init();
+        return actionExecutor.run(action);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedConsumerConnection.java
new file mode 100644
index 0000000..f83cabe
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedConsumerConnection.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.internal.protocol.ModelBuilder;
+import org.gradle.tooling.model.gradle.BuildInvocations;
+import org.gradle.tooling.model.gradle.GradleBuild;
+import org.gradle.util.GradleVersion;
+
+/**
+ * An adapter for a {@link ModelBuilder} based provider.
+ */
+public class ModelBuilderBackedConsumerConnection extends AbstractPost12ConsumerConnection {
+    private final ModelProducer modelProducer;
+
+    public ModelBuilderBackedConsumerConnection(ConnectionVersion4 delegate, ModelMapping modelMapping, ProtocolToModelAdapter adapter) {
+        super(delegate, getVersionDetails(delegate.getMetaData().getVersion()));
+        ModelBuilder builder = (ModelBuilder) delegate;
+        ModelProducer consumerConnectionBackedModelProducer = new ModelBuilderBackedModelProducer(adapter, getVersionDetails(), modelMapping, builder);
+        ModelProducer producerWithGradleBuild = new GradleBuildAdapterProducer(adapter, getVersionDetails(), modelMapping, consumerConnectionBackedModelProducer);
+        modelProducer = new BuildInvocationsAdapterProducer(adapter, getVersionDetails(), modelMapping, producerWithGradleBuild);
+    }
+
+    public static VersionDetails getVersionDetails(String versionString) {
+        GradleVersion version = GradleVersion.version(versionString);
+        if (version.compareTo(GradleVersion.version("1.11")) > 0) {
+            return new R112VersionDetails(version.getVersion());
+        }
+        if (version.compareTo(GradleVersion.version("1.8-rc-1")) >= 0) {
+            return new R18VersionDetails(version.getVersion());
+        }
+        return new R16VersionDetails(version.getVersion());
+    }
+
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        return modelProducer.produceModel(type, operationParameters);
+    }
+
+    private static class R16VersionDetails extends VersionDetails {
+        public R16VersionDetails(String version) {
+            super(version);
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            return modelType != BuildInvocations.class
+                    && modelType != GradleBuild.class;
+        }
+
+        @Override
+        public boolean supportsGradleProjectModel() {
+            return true;
+        }
+    }
+
+    private static class R18VersionDetails extends R16VersionDetails {
+        private R18VersionDetails(String version) {
+            super(version);
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            if (modelType == GradleBuild.class) {
+                return true;
+            }
+            return super.maySupportModel(modelType);
+        }
+    }
+
+    private static class R112VersionDetails extends R18VersionDetails {
+        private R112VersionDetails(String version) {
+            super(version);
+        }
+
+        @Override
+        public boolean maySupportModel(Class<?> modelType) {
+            if (modelType == BuildInvocations.class) {
+                return true;
+            }
+            return super.maySupportModel(modelType);
+        }
+
+        @Override
+        public boolean supportsTaskDisplayName() {
+            return true;
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedModelProducer.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedModelProducer.java
new file mode 100644
index 0000000..064dfe9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedModelProducer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api.Action;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.adapter.SourceObjectMapping;
+import org.gradle.tooling.internal.consumer.converters.TaskPropertyHandlerFactory;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildResult;
+import org.gradle.tooling.internal.protocol.InternalUnsupportedModelException;
+import org.gradle.tooling.internal.protocol.ModelBuilder;
+import org.gradle.tooling.internal.protocol.ModelIdentifier;
+import org.gradle.tooling.model.internal.Exceptions;
+
+public class ModelBuilderBackedModelProducer extends AbstractModelProducer {
+    private final ModelBuilder builder;
+    private final Action<SourceObjectMapping> mapper;
+
+    public ModelBuilderBackedModelProducer(ProtocolToModelAdapter adapter, VersionDetails versionDetails, ModelMapping modelMapping, ModelBuilder builder) {
+        super(adapter, versionDetails, modelMapping);
+        this.builder = builder;
+        mapper = new TaskPropertyHandlerFactory().forVersion(versionDetails);
+    }
+
+    public <T> T produceModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+        if (!versionDetails.maySupportModel(type)) {
+            throw Exceptions.unsupportedModel(type, versionDetails.getVersion());
+        }
+        final ModelIdentifier modelIdentifier = modelMapping.getModelIdentifierFromModelType(type);
+        BuildResult<?> result;
+        try {
+            result = builder.getModel(modelIdentifier, operationParameters);
+        } catch (InternalUnsupportedModelException e) {
+            throw Exceptions.unknownModel(type, e);
+        }
+        return adapter.adapt(type, result.getModel(), mapper);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelProducer.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelProducer.java
new file mode 100644
index 0000000..56fdefa
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ModelProducer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public interface ModelProducer {
+    <T> T produceModel(Class<T> type, ConsumerOperationParameters operationParameters);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/NoToolingApiConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/NoToolingApiConnection.java
new file mode 100644
index 0000000..aad5c14
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/NoToolingApiConnection.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BuildAction;
+import org.gradle.tooling.UnsupportedVersionException;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+
+public class NoToolingApiConnection implements ConsumerConnection {
+    private final Distribution distribution;
+
+    public NoToolingApiConnection(Distribution distribution) {
+        this.distribution = distribution;
+    }
+
+    public void stop() {
+    }
+
+    public String getDisplayName() {
+        return distribution.getDisplayName();
+    }
+
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        throw fail();
+    }
+
+    public <T> T run(BuildAction<T> action, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        throw fail();
+    }
+
+    private UnsupportedVersionException fail() {
+        return new UnsupportedVersionException(String.format("The specified %s does not implement the tooling API. Support for the tooling API was added in Gradle 1.0-milestone-3 and is available in all later versions.", distribution.getDisplayName()));
+    }
+}
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
deleted file mode 100644
index c60724d..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnection.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.consumer.connection;
-
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.ProgressLogger;
-import org.gradle.logging.internal.ProgressCompleteEvent;
-import org.gradle.logging.internal.ProgressEvent;
-import org.gradle.logging.internal.ProgressListener;
-import org.gradle.logging.internal.ProgressStartEvent;
-import org.gradle.tooling.internal.consumer.LoggingProvider;
-import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
-import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
-import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
-
-/**
- * Provides some high-level progress information.
- */
-public class ProgressLoggingConnection implements ConsumerConnection {
-    private final ConsumerConnection connection;
-    private final LoggingProvider loggingProvider;
-
-    public ProgressLoggingConnection(ConsumerConnection connection, LoggingProvider loggingProvider) {
-        this.connection = connection;
-        this.loggingProvider = loggingProvider;
-    }
-
-    public void stop() {
-        connection.stop();
-    }
-
-    public String getDisplayName() {
-        return connection.getDisplayName();
-    }
-
-    public VersionDetails getVersionDetails() {
-        return connection.getVersionDetails();
-    }
-
-    public <T> T run(final Class<T> type, final ConsumerOperationParameters operationParameters) {
-        return run("Build", operationParameters, new BuildAction<T>() {
-            public T run(ConsumerConnection connection) {
-                return connection.run(type, operationParameters);
-            }
-        });
-    }
-
-    private <T> T run(String description, BuildOperationParametersVersion1 parameters, BuildAction<T> action) {
-        ProgressListenerAdapter listener = new ProgressListenerAdapter(parameters.getProgressListener());
-        ListenerManager listenerManager = loggingProvider.getListenerManager();
-        listenerManager.addListener(listener);
-        try {
-            ProgressLogger progressLogger = loggingProvider.getProgressLoggerFactory().newOperation(ProgressLoggingConnection.class);
-            progressLogger.setDescription(description);
-            progressLogger.started();
-            try {
-                return action.run(connection);
-            } finally {
-                progressLogger.completed();
-            }
-        } finally {
-            listenerManager.removeListener(listener);
-        }
-    }
-
-    private interface BuildAction<T> {
-        T run(ConsumerConnection connection);
-    }
-
-    private static class ProgressListenerAdapter implements ProgressListener {
-        private final ProgressListenerVersion1 progressListener;
-
-        public ProgressListenerAdapter(ProgressListenerVersion1 progressListener) {
-            this.progressListener = progressListener;
-        }
-
-        public void started(ProgressStartEvent event) {
-            progressListener.onOperationStart(event.getDescription());
-        }
-
-        public void progress(ProgressEvent event) {
-        }
-
-        public void completed(ProgressCompleteEvent event) {
-            progressListener.onOperationEnd();
-        }
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConsumerActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConsumerActionExecutor.java
new file mode 100644
index 0000000..36e91aa
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConsumerActionExecutor.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.consumer.connection;
+
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.internal.ProgressCompleteEvent;
+import org.gradle.logging.internal.ProgressEvent;
+import org.gradle.logging.internal.ProgressListener;
+import org.gradle.logging.internal.ProgressStartEvent;
+import org.gradle.tooling.internal.consumer.LoggingProvider;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+
+/**
+ * Provides some high-level progress information.
+ */
+public class ProgressLoggingConsumerActionExecutor implements ConsumerActionExecutor {
+    private final ConsumerActionExecutor actionExecutor;
+    private final LoggingProvider loggingProvider;
+
+    public ProgressLoggingConsumerActionExecutor(ConsumerActionExecutor actionExecutor, LoggingProvider loggingProvider) {
+        this.actionExecutor = actionExecutor;
+        this.loggingProvider = loggingProvider;
+    }
+
+    public void stop() {
+        actionExecutor.stop();
+    }
+
+    public String getDisplayName() {
+        return actionExecutor.getDisplayName();
+    }
+
+    public <T> T run(ConsumerAction<T> action) throws UnsupportedOperationException, IllegalStateException {
+        ConsumerOperationParameters parameters = action.getParameters();
+        ProgressListenerAdapter listener = new ProgressListenerAdapter(parameters.getProgressListener());
+        ListenerManager listenerManager = loggingProvider.getListenerManager();
+        listenerManager.addListener(listener);
+        try {
+            ProgressLogger progressLogger = loggingProvider.getProgressLoggerFactory().newOperation(ProgressLoggingConsumerActionExecutor.class);
+            progressLogger.setDescription("Build");
+            progressLogger.started();
+            try {
+                return actionExecutor.run(action);
+            } finally {
+                progressLogger.completed();
+            }
+        } finally {
+            listenerManager.removeListener(listener);
+        }
+    }
+
+    private static class ProgressListenerAdapter implements ProgressListener {
+        private final ProgressListenerVersion1 progressListener;
+
+        public ProgressListenerAdapter(ProgressListenerVersion1 progressListener) {
+            this.progressListener = progressListener;
+        }
+
+        public void started(ProgressStartEvent event) {
+            progressListener.onOperationStart(event.getDescription());
+        }
+
+        public void progress(ProgressEvent event) {
+        }
+
+        public void completed(ProgressCompleteEvent event) {
+            progressListener.onOperationEnd();
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/BuildInvocationsConverter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/BuildInvocationsConverter.java
new file mode 100644
index 0000000..cfeacd9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/BuildInvocationsConverter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.gradle.tooling.internal.gradle.BasicGradleTaskSelector;
+import org.gradle.tooling.internal.gradle.DefaultBuildInvocations;
+import org.gradle.tooling.internal.gradle.DefaultGradleTask;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.GradleTask;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+public class BuildInvocationsConverter {
+    public DefaultBuildInvocations<DefaultGradleTask> convert(GradleProject project) {
+        GradleProject rootProject = project;
+        while (rootProject.getParent() != null) {
+            rootProject = rootProject.getParent();
+        }
+        List<BasicGradleTaskSelector> selectors = buildRecursively(rootProject);
+        return new DefaultBuildInvocations<DefaultGradleTask>()
+                .setSelectors(selectors)
+                .setTasks(convertTasks(rootProject.getTasks().iterator()));
+    }
+
+    private List<BasicGradleTaskSelector> buildRecursively(GradleProject project) {
+        Multimap<String, String> aggregatedTasks = ArrayListMultimap.create();
+        for (GradleProject childProject : project.getChildren()) {
+            List<BasicGradleTaskSelector> childSelectors = buildRecursively(childProject);
+            for (BasicGradleTaskSelector childSelector : childSelectors) {
+                aggregatedTasks.putAll(childSelector.getName(), childSelector.getTaskNames());
+            }
+        }
+        for (GradleTask task : project.getTasks()) {
+            aggregatedTasks.put(task.getName(), task.getPath());
+        }
+        List<BasicGradleTaskSelector> selectors = Lists.newArrayList();
+        for (String selectorName : aggregatedTasks.keySet()) {
+            SortedSet<String> selectorTasks = Sets.newTreeSet(new TaskNameComparator());
+            selectorTasks.addAll(aggregatedTasks.get(selectorName));
+            selectors.add(new BasicGradleTaskSelector().
+                    setName(selectorName).
+                    setTaskNames(selectorTasks).
+                    setDescription(project.getParent() != null
+                            ? String.format("%s:%s task selector", project.getPath(), selectorName)
+                            : String.format("%s task selector", selectorName)).
+                    setDisplayName(String.format("%s in %s and subprojects.", selectorName, project.getName())));
+        }
+        return selectors;
+    }
+
+    private List<DefaultGradleTask> convertTasks(Iterator<? extends GradleTask> tasks) {
+        return Lists.newArrayList(Iterators.transform(
+                tasks,
+                new Function<GradleTask, DefaultGradleTask>() {
+                    public DefaultGradleTask apply(GradleTask task) {
+                        return new DefaultGradleTask()
+                                .setName(task.getName())
+                                .setPath(task.getPath())
+                                .setDescription(task.getDescription());
+                    }
+                }
+        ));
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/ConsumerTargetTypeProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/ConsumerTargetTypeProvider.java
new file mode 100644
index 0000000..1930bff
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/ConsumerTargetTypeProvider.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.converters;
+
+import org.gradle.tooling.internal.adapter.TargetTypeProvider;
+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;
+
+public class ConsumerTargetTypeProvider implements TargetTypeProvider {
+
+    Map<String, Class<?>> configuredTargetTypes = new HashMap<String, Class<?>>();
+
+    public ConsumerTargetTypeProvider() {
+        configuredTargetTypes.put(IdeaSingleEntryLibraryDependency.class.getCanonicalName(), IdeaSingleEntryLibraryDependency.class);
+        configuredTargetTypes.put(IdeaModuleDependency.class.getCanonicalName(), IdeaModuleDependency.class);
+        configuredTargetTypes.put(GradleFileBuildOutcome.class.getCanonicalName(), GradleFileBuildOutcome.class);
+    }
+
+    public <T> Class<? extends T> getTargetType(Class<T> initialTargetType, Object protocolObject) {
+        Class<?>[] interfaces = protocolObject.getClass().getInterfaces();
+        for (Class<?> i : interfaces) {
+            if (configuredTargetTypes.containsKey(i.getName())) {
+                return configuredTargetTypes.get(i.getName()).asSubclass(initialTargetType);
+            }
+        }
+
+        return initialTargetType;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleBuildConverter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleBuildConverter.java
new file mode 100644
index 0000000..3219773
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleBuildConverter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters;
+
+import org.gradle.tooling.internal.gradle.DefaultGradleBuild;
+import org.gradle.tooling.internal.gradle.PartialBasicGradleProject;
+import org.gradle.tooling.model.GradleProject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class GradleBuildConverter {
+    public DefaultGradleBuild convert(GradleProject project) {
+        DefaultGradleBuild gradleBuild = new DefaultGradleBuild();
+        PartialBasicGradleProject rootProject = toPartialGradleProject(project);
+        gradleBuild.setRootProject(rootProject);
+        gradleBuild.addProject(rootProject);
+        convertChildren(gradleBuild, rootProject, project);
+        return gradleBuild;
+    }
+
+    private void convertChildren(DefaultGradleBuild gradleBuild, PartialBasicGradleProject rootProject, GradleProject project) {
+        final List<? extends GradleProject> childProjects = new ArrayList<GradleProject>(project.getChildren());
+        Collections.sort(childProjects, new Comparator<GradleProject>() {
+            public int compare(GradleProject gp1, GradleProject gp2) {
+                return gp1.getName().compareTo(gp2.getName());
+            }
+        });
+        for (GradleProject childProject : childProjects) {
+            PartialBasicGradleProject basicGradleProject = toPartialGradleProject(childProject);
+            gradleBuild.addProject(basicGradleProject);
+            basicGradleProject.setParent(rootProject);
+            rootProject.addChild(basicGradleProject);
+            convertChildren(gradleBuild, basicGradleProject, childProject);
+        }
+    }
+
+    private PartialBasicGradleProject toPartialGradleProject(GradleProject childProject) {
+        PartialBasicGradleProject basicGradleProject = new PartialBasicGradleProject();
+        basicGradleProject.setPath(childProject.getPath());
+        basicGradleProject.setName(childProject.getName());
+        return basicGradleProject;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java
index 18f50da..abe8400 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java
@@ -16,46 +16,45 @@
 
 package org.gradle.tooling.internal.consumer.converters;
 
-import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.gradle.DefaultConvertedGradleProject;
+import org.gradle.tooling.internal.gradle.DefaultGradleProjectTask;
 import org.gradle.tooling.internal.gradle.DefaultGradleTask;
+import org.gradle.tooling.internal.gradle.PartialGradleProject;
 import org.gradle.tooling.internal.protocol.TaskVersion1;
 import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.model.GradleTask;
 
 import java.util.LinkedList;
 import java.util.List;
 
-/**
- * by Szczepan Faber, created at: 3/27/12
- */
 public class GradleProjectConverter {
 
-    public DefaultGradleProject convert(EclipseProjectVersion3 project) {
+    public DefaultConvertedGradleProject convert(EclipseProjectVersion3 project) {
         //build children recursively
-        List<DefaultGradleProject> children = new LinkedList<DefaultGradleProject>();
+        List<DefaultConvertedGradleProject> children = new LinkedList<DefaultConvertedGradleProject>();
         for (EclipseProjectVersion3 p : project.getChildren()) {
             children.add(convert(p));
         }
         //build parent
-        DefaultGradleProject parent = new DefaultGradleProject()
+        DefaultConvertedGradleProject parent = new DefaultConvertedGradleProject()
                 .setPath(project.getPath())
                 .setName(project.getName())
                 .setChildren(children)
                 .setDescription(project.getDescription());
 
         //build tasks
-        List<GradleTask> tasks = new LinkedList<GradleTask>();
+        List<DefaultGradleTask> tasks = new LinkedList<DefaultGradleTask>();
         for (TaskVersion1 t : project.getTasks()) {
-            tasks.add(new DefaultGradleTask()
+            tasks.add(new DefaultGradleProjectTask()
+                    .setProject(parent)
                     .setName(t.getName())
                     .setPath(t.getPath())
                     .setDescription(t.getDescription())
-                    .setProject(parent));
+                    );
         }
         parent.setTasks(tasks);
 
         //apply parent to each child
-        for (DefaultGradleProject child : children) {
+        for (PartialGradleProject child : children) {
             child.setParent(parent);
         }
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectMixInHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectMixInHandler.java
new file mode 100644
index 0000000..36b326a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectMixInHandler.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.consumer.converters;
+
+import org.gradle.tooling.internal.adapter.MethodInvocation;
+import org.gradle.tooling.internal.adapter.MethodInvoker;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+
+public class GradleProjectMixInHandler implements MethodInvoker {
+    public void invoke(MethodInvocation invocation) throws Throwable {
+        if (invocation.getName().equals("getGradleProject")
+                && invocation.getDelegate() instanceof EclipseProjectVersion3) {
+            invocation.setResult(new GradleProjectConverter().convert((EclipseProjectVersion3) invocation.getDelegate()));
+        }
+     }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleTaskDisplayNameMixInHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleTaskDisplayNameMixInHandler.java
new file mode 100644
index 0000000..2e8b75e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleTaskDisplayNameMixInHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters;
+
+import org.gradle.tooling.model.GradleTask;
+
+public class GradleTaskDisplayNameMixInHandler {
+    private final GradleTask task;
+
+    public GradleTaskDisplayNameMixInHandler(GradleTask task) {
+        this.task = task;
+    }
+
+    public String getDisplayName() {
+        return String.format("task '%s'", task.getPath());
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/PropertyHandlerFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/PropertyHandlerFactory.java
new file mode 100644
index 0000000..8ed687b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/PropertyHandlerFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters;
+
+import org.gradle.api.Action;
+import org.gradle.tooling.internal.adapter.SourceObjectMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+
+import java.io.Serializable;
+
+public class PropertyHandlerFactory {
+    public Action<SourceObjectMapping> forVersion(VersionDetails versionDetails) {
+        return new ConsumerMapping(versionDetails);
+    }
+
+    private static class ConsumerMapping implements Action<SourceObjectMapping>, Serializable {
+        private final VersionDetails versionDetails;
+
+        public ConsumerMapping(VersionDetails versionDetails) {
+            this.versionDetails = versionDetails;
+        }
+
+        public void execute(SourceObjectMapping mapping) {
+            if (EclipseProject.class.isAssignableFrom(mapping.getTargetType()) && !versionDetails.supportsGradleProjectModel()) {
+                mapping.mixIn(new GradleProjectMixInHandler());
+            }
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/TaskNameComparator.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/TaskNameComparator.java
new file mode 100644
index 0000000..321ea32
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/TaskNameComparator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters;
+
+import java.util.Comparator;
+
+/**
+ * Compares task names to create ordering for selector launching.
+ */
+public class TaskNameComparator implements Comparator<String> {
+    public int compare(String taskName1, String taskName2) {
+        int depthDiff = getDepth(taskName1) - getDepth(taskName2);
+        if (depthDiff != 0) {
+            return depthDiff;
+        }
+        return compareSegments(taskName1, taskName2);
+    }
+
+    private int compareSegments(String taskName1, String taskName2) {
+        int colon1 = taskName1.indexOf(':');
+        int colon2 = taskName2.indexOf(':');
+        if (colon1 > 0 && colon2 > 0) {
+            int diff = taskName1.substring(0, colon1).compareTo(taskName2.substring(0, colon2));
+            if (diff != 0) {
+                return diff;
+            }
+        }
+        return colon1 == -1 ? taskName1.compareTo(taskName2) : compareSegments(taskName1.substring(colon1 + 1), taskName2.substring(colon2 + 1));
+    }
+
+    private int getDepth(String taskName) {
+        int counter = 0;
+        for (char c : taskName.toCharArray()) {
+            if (c == ':') {
+                counter++;
+            }
+        }
+        return counter;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/TaskPropertyHandlerFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/TaskPropertyHandlerFactory.java
new file mode 100644
index 0000000..6df4a5b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/TaskPropertyHandlerFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters;
+
+import org.gradle.api.Action;
+import org.gradle.tooling.internal.adapter.SourceObjectMapping;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.model.GradleTask;
+
+import java.io.Serializable;
+
+public class TaskPropertyHandlerFactory {
+    public Action<SourceObjectMapping> forVersion(VersionDetails versionDetails) {
+        return new ConsumerMapping(versionDetails);
+    }
+
+    private static class ConsumerMapping implements Action<SourceObjectMapping>, Serializable {
+        private final VersionDetails versionDetails;
+
+        public ConsumerMapping(VersionDetails versionDetails) {
+            this.versionDetails = versionDetails;
+        }
+
+        public void execute(SourceObjectMapping mapping) {
+            if (GradleTask.class.isAssignableFrom(mapping.getTargetType()) && !versionDetails.supportsTaskDisplayName()) {
+                mapping.mixIn(GradleTaskDisplayNameMixInHandler.class);
+            }
+        }
+    }
+}
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 7f93314..5da074c 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
@@ -17,9 +17,9 @@ package org.gradle.tooling.internal.consumer.loader;
 
 import org.gradle.internal.classpath.ClassPath;
 import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
 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;
@@ -32,8 +32,8 @@ public class CachingToolingImplementationLoader implements ToolingImplementation
         this.loader = loader;
     }
 
-    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters) {
-        ClassPath classpath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConnectionParameters connectionParameters) {
+        ClassPath classpath = distribution.getToolingImplementationClasspath(progressLoggerFactory, connectionParameters.getGradleUserHomeDir());
 
         ConsumerConnection connection = connections.get(classpath);
         if (connection == null) {
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 2050e04..dab8617 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
@@ -16,25 +16,25 @@
 package org.gradle.tooling.internal.consumer.loader;
 
 import org.gradle.internal.Factory;
+import org.gradle.internal.classloader.FilteringClassLoader;
+import org.gradle.internal.classloader.MultiParentClassLoader;
+import org.gradle.internal.classloader.MutableURLClassLoader;
 import org.gradle.internal.classpath.ClassPath;
 import org.gradle.internal.service.ServiceLocator;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.UnsupportedVersionException;
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
 import org.gradle.tooling.internal.consumer.Distribution;
 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.tooling.internal.protocol.InternalConnection;
-import org.gradle.util.FilteringClassLoader;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.MutableURLClassLoader;
+import org.gradle.tooling.internal.consumer.converters.ConsumerTargetTypeProvider;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.protocol.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.io.File;
 
 public class DefaultToolingImplementationLoader implements ToolingImplementationLoader {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultToolingImplementationLoader.class);
@@ -48,29 +48,33 @@ public class DefaultToolingImplementationLoader implements ToolingImplementation
         this.classLoader = classLoader;
     }
 
-    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters) {
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConnectionParameters connectionParameters) {
         LOGGER.debug("Using tooling provider from {}", distribution.getDisplayName());
-        ClassLoader classLoader = createImplementationClassLoader(distribution, progressLoggerFactory);
+        ClassLoader classLoader = createImplementationClassLoader(distribution, progressLoggerFactory, connectionParameters.getGradleUserHomeDir());
         ServiceLocator serviceLocator = new ServiceLocator(classLoader);
         try {
             Factory<ConnectionVersion4> factory = serviceLocator.findFactory(ConnectionVersion4.class);
             if (factory == null) {
-                Matcher m = Pattern.compile("\\w+Version(\\d+)").matcher(ConnectionVersion4.class.getSimpleName());
-                m.matches();
-                String protocolVersion = m.group(1);
-                throw new UnsupportedVersionException(String.format("The specified %s is not supported by this tooling API version (%s, protocol version %s)", distribution.getDisplayName(), GradleVersion.current().getVersion(), protocolVersion));
+                return new NoToolingApiConnection(distribution);
             }
             // ConnectionVersion4 is a part of the protocol and cannot be easily changed.
             ConnectionVersion4 connection = factory.create();
 
+            ProtocolToModelAdapter adapter = new ProtocolToModelAdapter(new ConsumerTargetTypeProvider());
+            ModelMapping modelMapping = new ModelMapping();
+
             // Adopting the connection to a refactoring friendly type that the consumer owns
             AbstractConsumerConnection adaptedConnection;
-            if (connection instanceof BuildActionRunner) {
-                adaptedConnection = new BuildActionRunnerBackedConsumerConnection(connection);
+            if (connection instanceof ModelBuilder && connection instanceof InternalBuildActionExecutor) {
+                adaptedConnection = new ActionAwareConsumerConnection(connection, modelMapping, adapter);
+            } else if (connection instanceof ModelBuilder) {
+                adaptedConnection = new ModelBuilderBackedConsumerConnection(connection, modelMapping, adapter);
+            } else if (connection instanceof BuildActionRunner) {
+                adaptedConnection = new BuildActionRunnerBackedConsumerConnection(connection, modelMapping, adapter);
             } else if (connection instanceof InternalConnection) {
-                adaptedConnection = new InternalConnectionBackedConsumerConnection(connection);
+                adaptedConnection = new InternalConnectionBackedConsumerConnection(connection, modelMapping, adapter);
             } else {
-                adaptedConnection = new AdaptedConnection(connection);
+                adaptedConnection = new ConnectionVersion4BackedConsumerConnection(connection, modelMapping, adapter);
             }
             adaptedConnection.configure(connectionParameters);
             return adaptedConnection;
@@ -81,10 +85,14 @@ public class DefaultToolingImplementationLoader implements ToolingImplementation
         }
     }
 
-    private ClassLoader createImplementationClassLoader(Distribution distribution, ProgressLoggerFactory progressLoggerFactory) {
-        ClassPath implementationClasspath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
+    private ClassLoader createImplementationClassLoader(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, File userHomeDir) {
+        ClassPath implementationClasspath = distribution.getToolingImplementationClasspath(progressLoggerFactory, userHomeDir);
         LOGGER.debug("Using tooling provider classpath: {}", implementationClasspath);
-        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(classLoader);
+        // On IBM JVM 5, ClassLoader.getResources() uses a combination of findResources() and getParent() and traverses the hierarchy rather than just calling getResources()
+        // Wrap our real classloader in one that hides the parent.
+        // TODO - move this into FilteringClassLoader
+        MultiParentClassLoader parentObfuscatingClassLoader = new MultiParentClassLoader(classLoader);
+        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(parentObfuscatingClassLoader);
         filteringClassLoader.allowPackage("org.gradle.tooling.internal.protocol");
         return new MutableURLClassLoader(filteringClassLoader, implementationClasspath.getAsURLArray());
     }
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 2bbb0fc..d16acf4 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
@@ -18,16 +18,13 @@ package org.gradle.tooling.internal.consumer.loader;
 
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
 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;
 
-/**
- * by Szczepan Faber, created at: 12/6/11
- */
 public class SynchronizedToolingImplementationLoader implements ToolingImplementationLoader {
 
     Lock lock = new ReentrantLock();
@@ -37,7 +34,7 @@ public class SynchronizedToolingImplementationLoader implements ToolingImplement
         this.delegate = delegate;
     }
 
-    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters) {
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConnectionParameters connectionParameters) {
         if (lock.tryLock()) {
             try {
                 return delegate.create(distribution, progressLoggerFactory, connectionParameters);
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 7389054..dcde1ba 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
@@ -16,10 +16,10 @@
 package org.gradle.tooling.internal.consumer.loader;
 
 import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
 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, ConsumerConnectionParameters connectionParameters);
+    ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConnectionParameters 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
deleted file mode 100644
index 3138a48..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerConnectionParameters.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.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 f3c84ad..06bb2a8 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
@@ -21,6 +21,7 @@ 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.InternalLaunchable;
 import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
 
 import java.io.File;
@@ -30,58 +31,117 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-/**
- * by Szczepan Faber, created at: 1/9/12
- */
 public class ConsumerOperationParameters implements BuildOperationParametersVersion1, BuildParametersVersion1, BuildParameters {
 
-    private final ProgressListenerAdapter progressListener = new ProgressListenerAdapter();
-    private final ConnectionParameters parameters;
-    private final long startTime = System.currentTimeMillis();
+    public static Builder builder() {
+        return new Builder();
+    }
 
-    private OutputStream stdout;
-    private OutputStream stderr;
-    private InputStream stdin;
+    public static class Builder {
+        private final ProgressListenerAdapter progressListener = new ProgressListenerAdapter();
+        private ConnectionParameters parameters;
+        private OutputStream stdout;
+        private OutputStream stderr;
+        private InputStream stdin;
+        private File javaHome;
+        private List<String> jvmArguments;
+        private List<String> arguments;
+        private List<String> tasks;
+        private List<InternalLaunchable> launchables;
 
-    private File javaHome;
-    private List<String> jvmArguments;
-    private List<String> arguments;
-    private List<String> tasks;
+        private Builder() {
+        }
 
-    public ConsumerOperationParameters(ConnectionParameters parameters) {
-        this.parameters = parameters;
-    }
+        public Builder setParameters(ConnectionParameters parameters) {
+            this.parameters = parameters;
+            return this;
+        }
 
-    public void setArguments(String[] arguments) {
-        this.arguments = rationalizeInput(arguments);
-    }
+        public Builder setStdout(OutputStream stdout) {
+            this.stdout = stdout;
+            return this;
+        }
 
-    private List<String> rationalizeInput(String[] arguments) {
-        return arguments != null && arguments.length > 0 ? Arrays.asList(arguments) : null;
-    }
+        public Builder setStderr(OutputStream stderr) {
+            this.stderr = stderr;
+            return this;
+        }
 
-    public void setStandardOutput(OutputStream outputStream) {
-        stdout = outputStream;
-    }
+        public Builder setStdin(InputStream stdin) {
+            this.stdin = stdin;
+            return this;
+        }
 
-    public void setStandardError(OutputStream outputStream) {
-        stderr = outputStream;
-    }
+        public Builder setJavaHome(File javaHome) {
+            validateJavaHome(javaHome);
+            this.javaHome = javaHome;
+            return this;
+        }
 
-    public void setStandardInput(InputStream inputStream) {
-        stdin = inputStream;
-    }
+        public Builder setJvmArguments(String... jvmArguments) {
+            this.jvmArguments = rationalizeInput(jvmArguments);
+            return this;
+        }
+
+        public Builder setArguments(String [] arguments) {
+            this.arguments = rationalizeInput(arguments);
+            return this;
+        }
 
-    public void addProgressListener(ProgressListener listener) {
-        progressListener.add(listener);
+        public Builder setTasks(List<String> tasks) {
+            this.tasks = tasks;
+            return this;
+        }
+
+        public Builder setLaunchables(List<InternalLaunchable> launchables) {
+            this.launchables = launchables;
+            return this;
+        }
+
+        public void addProgressListener(ProgressListener listener) {
+            progressListener.add(listener);
+        }
+
+        public ConsumerOperationParameters build() {
+            return new ConsumerOperationParameters(parameters, stdout, stderr, stdin,
+                    javaHome, jvmArguments, arguments, tasks, launchables, progressListener);
+        }
     }
 
-    public void setJavaHome(File javaHome) {
-        validateJavaHome(javaHome);
+    private final ProgressListenerAdapter progressListener;
+    private final ConnectionParameters parameters;
+    private final long startTime = System.currentTimeMillis();
+
+    private final OutputStream stdout;
+    private final OutputStream stderr;
+    private final InputStream stdin;
+
+    private final File javaHome;
+    private final List<String> jvmArguments;
+    private final List<String> arguments;
+    private final List<String> tasks;
+    private final List<InternalLaunchable> launchables;
+
+    private ConsumerOperationParameters(ConnectionParameters parameters, OutputStream stdout, OutputStream stderr, InputStream stdin,
+                                        File javaHome, List<String> jvmArguments, List<String> arguments, List<String> tasks,
+                                        List<InternalLaunchable> launchables, ProgressListenerAdapter listener) {
+        this.parameters = parameters;
+        this.stdout = stdout;
+        this.stderr = stderr;
+        this.stdin = stdin;
         this.javaHome = javaHome;
+        this.jvmArguments = jvmArguments;
+        this.arguments = arguments;
+        this.tasks = tasks;
+        this.launchables = launchables;
+        this.progressListener = listener;
+    }
+
+    private static List<String> rationalizeInput(String[] arguments) {
+        return arguments != null && arguments.length > 0 ? Arrays.asList(arguments) : null;
     }
 
-    private void validateJavaHome(File javaHome) {
+    private static void validateJavaHome(File javaHome) {
         if (javaHome == null) {
             return;
         }
@@ -90,10 +150,6 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
         }
     }
 
-    public void setJvmArguments(String... jvmArguments) {
-        this.jvmArguments = rationalizeInput(jvmArguments);
-    }
-
     public long getStartTime() {
         return startTime;
     }
@@ -158,7 +214,7 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
         return tasks;
     }
 
-    public void setTasks(List<String> tasks) {
-        this.tasks = tasks;
+    public List<InternalLaunchable> getLaunchables() {
+        return launchables;
     }
 }
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
deleted file mode 100644
index 996ac73..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ConsumerPropertyHandler.java
+++ /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.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
deleted file mode 100644
index c223d38..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvocation.java
+++ /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.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
deleted file mode 100644
index 2e4a473..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvoker.java
+++ /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.
- */
-
-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/ProtocolToModelAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapter.java
deleted file mode 100644
index 2428939..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapter.java
+++ /dev/null
@@ -1,358 +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.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) {
-        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, 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 Method equalsMethod;
-        private final Method hashCodeMethod;
-        private final MethodInvoker invoker;
-
-        public InvocationHandlerImpl(Object delegate, MethodInvoker overrideMethodInvoker) {
-            this.delegate = delegate;
-            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");
-            } catch (NoSuchMethodException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (o == null || o.getClass() != getClass()) {
-                return false;
-            }
-
-            InvocationHandlerImpl other = (InvocationHandlerImpl) o;
-            return delegate.equals(other.delegate);
-        }
-
-        @Override
-        public int hashCode() {
-            return delegate.hashCode();
-        }
-
-        public Object invoke(Object target, Method method, Object[] params) throws Throwable {
-            if (method.equals(equalsMethod)) {
-                Object param = params[0];
-                if (param == null || !Proxy.isProxyClass(param.getClass())) {
-                    return false;
-                }
-                InvocationHandler other = Proxy.getInvocationHandler(param);
-                return equals(other);
-            } else if (method.equals(hashCodeMethod)) {
-                return hashCode();
-            }
-
-            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();
-        }
-    }
-
-    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);
-            }
-        }
-    }
-
-    private class ReflectionMethodInvoker implements MethodInvoker {
-        private final MethodInvoker override;
-
-        private ReflectionMethodInvoker(MethodInvoker override) {
-            this.override = override;
-        }
-
-        public void invoke(MethodInvocation invocation) throws Throwable {
-            // TODO - cache method lookup
-            Method targetMethod = locateMethod(invocation);
-            if (targetMethod == null) {
-                return;
-            }
-
-            Object returnValue;
-            try {
-                returnValue = targetMethod.invoke(invocation.getDelegate(), invocation.getParameters());
-            } catch (InvocationTargetException e) {
-                throw e.getCause();
-            }
-
-            if (returnValue == null || invocation.getReturnType().isInstance(returnValue)) {
-                invocation.setResult(returnValue);
-                return;
-            }
-
-            invocation.setResult(convert(returnValue, invocation.getGenericReturnType()));
-        }
-
-        private Method locateMethod(MethodInvocation invocation) {
-            Class<?> sourceClass = invocation.getDelegate().getClass();
-            Method match;
-            try {
-                match = sourceClass.getMethod(invocation.getName(), invocation.getParameterTypes());
-            } catch (NoSuchMethodException e) {
-                return null;
-            }
-
-            LinkedList<Class<?>> queue = new LinkedList<Class<?>>();
-            queue.add(sourceClass);
-            while (!queue.isEmpty()) {
-                Class<?> c = queue.removeFirst();
-                try {
-                    match = c.getMethod(invocation.getName(), invocation.getParameterTypes());
-                } catch (NoSuchMethodException e) {
-                    // ignore
-                }
-                for (Class<?> interfaceType : c.getInterfaces()) {
-                    queue.addFirst(interfaceType);
-                }
-                if (c.getSuperclass() != null) {
-                    queue.addFirst(c.getSuperclass());
-                }
-            }
-            match.setAccessible(true);
-            return match;
-        }
-
-        private Object convert(Object value, Type targetType) {
-            if (targetType instanceof ParameterizedType) {
-                ParameterizedType parameterizedTargetType = (ParameterizedType) targetType;
-                if (parameterizedTargetType.getRawType().equals(DomainObjectSet.class)) {
-                    Type targetElementType = getElementType(parameterizedTargetType);
-                    List<Object> convertedElements = new ArrayList<Object>();
-                    for (Object element : (Iterable) value) {
-                        convertedElements.add(convert(element, targetElementType));
-                    }
-                    return new ImmutableDomainObjectSet(convertedElements);
-                }
-            }
-            if (targetType instanceof Class) {
-                if (((Class) targetType).isPrimitive()) {
-                    return value;
-                }
-                return adapt((Class) targetType, value, override);
-            }
-            throw new UnsupportedOperationException(String.format("Cannot convert object of %s to %s.", value.getClass(), targetType));
-        }
-
-        private Type getElementType(ParameterizedType type) {
-            Type elementType = type.getActualTypeArguments()[0];
-            if (elementType instanceof WildcardType) {
-                WildcardType wildcardType = (WildcardType) elementType;
-                return wildcardType.getUpperBounds()[0];
-            }
-            return elementType;
-        }
-    }
-
-    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
deleted file mode 100644
index 95e5be6..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/TargetTypeProvider.java
+++ /dev/null
@@ -1,55 +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.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;
-
-/**
- * by Szczepan Faber, created at: 4/2/12
- */
-public class TargetTypeProvider {
-
-    Map<String, Class<?>> configuredTargetTypes = new HashMap<String, Class<?>>();
-
-    public TargetTypeProvider() {
-        configuredTargetTypes.put(IdeaSingleEntryLibraryDependency.class.getCanonicalName(), IdeaSingleEntryLibraryDependency.class);
-        configuredTargetTypes.put(IdeaModuleDependency.class.getCanonicalName(), IdeaModuleDependency.class);
-        configuredTargetTypes.put(GradleFileBuildOutcome.class.getCanonicalName(), GradleFileBuildOutcome.class);
-    }
-
-    /**
-     * Occasionally we want to use preconfigured target type instead of passed target type.
-     *
-     * @param initialTargetType
-     * @param protocolObject
-     */
-    public <T, S> Class<T> getTargetType(Class<T> initialTargetType, S protocolObject) {
-        Class<?>[] interfaces = protocolObject.getClass().getInterfaces();
-        for (Class<?> i : interfaces) {
-            if (configuredTargetTypes.containsKey(i.getName())) {
-                return (Class<T>) configuredTargetTypes.get(i.getName());
-            }
-        }
-
-        return initialTargetType;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java
index 024cc26..eac9e6f 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
@@ -16,52 +16,130 @@
 
 package org.gradle.tooling.internal.consumer.versioning;
 
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import org.gradle.api.Nullable;
 import org.gradle.tooling.internal.protocol.*;
 import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
 import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
-import org.gradle.tooling.model.*;
+import org.gradle.tooling.model.gradle.GradleBuild;
+import org.gradle.tooling.model.GradleProject;
 import org.gradle.tooling.model.build.BuildEnvironment;
 import org.gradle.tooling.model.eclipse.EclipseProject;
 import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
 import org.gradle.tooling.model.idea.BasicIdeaProject;
 import org.gradle.tooling.model.idea.IdeaProject;
-import org.gradle.tooling.model.internal.TestModel;
 import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
 
 import java.util.HashMap;
 import java.util.Map;
 
-/**
- * by Szczepan Faber, created at: 1/13/12
- */
 public class ModelMapping {
-
-    private static final Map<Class<? extends Model>, Class> MODEL_TYPE_MAP = new HashMap<Class<? extends Model>, Class>();
+    private static final BiMap<Class<?>, Class<?>> MODEL_TO_PROTOCOL_MAP = HashBiMap.create();
+    private static final BiMap<Class<?>, String> MODEL_NAME_MAP = HashBiMap.create();
+    private static final Map<Class<?>, String> MODEL_VERSIONS = new HashMap<Class<?>, String>();
 
     static {
-        MODEL_TYPE_MAP.putAll(getModelsUpToM6());
-        MODEL_TYPE_MAP.putAll(getModelsPostM6());
+        addModelToProtocolMappings(MODEL_TO_PROTOCOL_MAP);
+        addModelNameMappings(MODEL_NAME_MAP);
+        addModelVersions(MODEL_VERSIONS);
+    }
+
+    private static void addModelVersions(Map<Class<?>, String> map) {
+        map.put(HierarchicalEclipseProject.class, "1.0-milestone-3");
+        map.put(EclipseProject.class, "1.0-milestone-3");
+        map.put(IdeaProject.class, "1.0-milestone-5");
+        map.put(GradleProject.class, "1.0-milestone-5");
+        map.put(BasicIdeaProject.class, "1.0-milestone-5");
+        map.put(BuildEnvironment.class, "1.0-milestone-8");
+        map.put(ProjectOutcomes.class, "1.2");
+        map.put(Void.class, "1.0-milestone-3");
+        map.put(GradleBuild.class, "1.8");
     }
 
-    static Map<Class<? extends Model>, Class> getModelsUpToM6() {
-        Map<Class<? extends Model>, Class> map = new HashMap<Class<? extends Model>, Class>();
+    static void addModelToProtocolMappings(Map<Class<?>, Class<?>> map) {
         map.put(HierarchicalEclipseProject.class, HierarchicalEclipseProjectVersion1.class);
         map.put(EclipseProject.class, EclipseProjectVersion3.class);
         map.put(IdeaProject.class, InternalIdeaProject.class);
         map.put(GradleProject.class, InternalGradleProject.class);
         map.put(BasicIdeaProject.class, InternalBasicIdeaProject.class);
-        return map;
-    }
-
-    private static Map<Class<? extends Model>, Class> getModelsPostM6() {
-        Map<Class<? extends Model>, Class> map = new HashMap<Class<? extends Model>, Class>();
         map.put(BuildEnvironment.class, InternalBuildEnvironment.class);
-        map.put(TestModel.class, InternalTestModel.class);
         map.put(ProjectOutcomes.class, InternalProjectOutcomes.class);
-        return map;
+        map.put(Void.class, Void.class);
+    }
+
+    static void addModelNameMappings(Map<Class<?>, String> map) {
+        map.put(HierarchicalEclipseProject.class, "org.gradle.tooling.model.eclipse.HierarchicalEclipseProject");
+        map.put(EclipseProject.class, "org.gradle.tooling.model.eclipse.EclipseProject");
+        map.put(IdeaProject.class, "org.gradle.tooling.model.idea.IdeaProject");
+        map.put(GradleProject.class, "org.gradle.tooling.model.GradleProject");
+        map.put(BasicIdeaProject.class, "org.gradle.tooling.model.idea.BasicIdeaProject");
+        map.put(BuildEnvironment.class, "org.gradle.tooling.model.build.BuildEnvironment");
+        map.put(ProjectOutcomes.class, "org.gradle.tooling.model.outcomes.ProjectOutcomes");
+        map.put(Void.class, Void.class.getName());
+    }
+
+    public ModelIdentifier getModelIdentifierFromModelType(final Class<?> modelType) {
+        if (modelType.equals(Void.class)) {
+            return new DefaultModelIdentifier(ModelIdentifier.NULL_MODEL);
+        }
+        String modelName = getModelName(modelType);
+        if (modelName != null) {
+            return new DefaultModelIdentifier(modelName);
+        }
+        return new DefaultModelIdentifier(modelType.getName());
+    }
+
+    @Nullable
+    public Class<?> getProtocolType(Class<?> modelType) {
+        if (MODEL_TO_PROTOCOL_MAP.containsValue(modelType)) {
+            return modelType;
+        }
+        return MODEL_TO_PROTOCOL_MAP.get(modelType);
     }
 
-    public Class getInternalType(Class<? extends Model> viewType) {
-        return MODEL_TYPE_MAP.get(viewType);
+    @Nullable
+    public String getModelName(Class<?> modelType) {
+        return MODEL_NAME_MAP.get(modelType);
+    }
+
+    @Nullable
+    public String getModelNameFromProtocolType(Class<?> protocolType) {
+        Class<?> modelType = MODEL_TO_PROTOCOL_MAP.inverse().get(protocolType);
+        if (modelType == null) {
+            return null;
+        }
+        return MODEL_NAME_MAP.get(modelType);
+    }
+
+    @Nullable
+    public Class<?> getProtocolTypeFromModelName(String name) {
+        Class<?> modelType = MODEL_NAME_MAP.inverse().get(name);
+        if (modelType == null) {
+            return null;
+        }
+        return getProtocolType(modelType);
+    }
+
+    @Nullable
+    public String getVersionAdded(Class<?> modelType) {
+        return MODEL_VERSIONS.get(modelType);
+    }
+
+    private static class DefaultModelIdentifier implements ModelIdentifier {
+        private final String model;
+
+        public DefaultModelIdentifier(String model) {
+            this.model = model;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("tooling model %s", model);
+        }
+
+        public String getName() {
+            return model;
+        }
     }
 }
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 9c644ff..89fac8c 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
@@ -16,56 +16,30 @@
 
 package org.gradle.tooling.internal.consumer.versioning;
 
-import org.gradle.util.GradleVersion;
-
-/**
- * by Szczepan Faber, created at: 1/13/12
- */
-public class VersionDetails {
-
-    private final GradleVersion gradleVersion;
-    private static final GradleVersion M5 = GradleVersion.version("1.0-milestone-5");
-    private static final GradleVersion M6 = GradleVersion.version("1.0-milestone-6");
-    private static final GradleVersion M7 = GradleVersion.version("1.0-milestone-7");
-    private static final GradleVersion V1_1 = GradleVersion.version("1.1");
+public abstract class VersionDetails {
+    private final String providerVersion;
 
     public VersionDetails(String version) {
-        gradleVersion = GradleVersion.version(version);
+        providerVersion = version;
     }
 
     public String getVersion() {
-        return gradleVersion.getVersion();
-    }
-
-    public boolean supportsCompleteBuildEnvironment() {
-        return gradleVersion.compareTo(M7) > 0;
-    }
-
-    public boolean clientHangsOnEarlyDaemonFailure() {
-        return gradleVersion.equals(M5) || gradleVersion.equals(M6);
+        return providerVersion;
     }
 
-    public boolean isPostM6Model(Class<?> internalModelType) {
-        return !ModelMapping.getModelsUpToM6().containsValue(internalModelType) && internalModelType != Void.class;
+    /**
+     * Returns true if this provider may support the given model type. Returns false if it is known that the
+     * provider does not support the given model type and <em>should not</em> be asked to provide it.
+     */
+    public boolean maySupportModel(Class<?> modelType) {
+        return false;
     }
 
-    public boolean supportsConfiguringJavaHome() {
-        return gradleVersion.compareTo(M7) > 0;
-    }
-
-    public boolean supportsConfiguringJvmArguments() {
-        return gradleVersion.compareTo(M7) > 0;
-    }
-
-    public boolean supportsConfiguringStandardInput() {
-        return gradleVersion.compareTo(M7) > 0;
-    }
-
-    public boolean supportsRunningTasksWhenBuildingModel() {
-        return gradleVersion.compareTo(V1_1) > 0;
+    public boolean supportsGradleProjectModel() {
+        return false;
     }
 
-    public boolean supportsGradleProjectModel() {
-        return gradleVersion.compareTo(M5) >= 0;
+    public boolean supportsTaskDisplayName() {
+        return false;
     }
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
deleted file mode 100644
index 0f1067c..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
+++ /dev/null
@@ -1,54 +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.eclipse;
-
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-import org.gradle.tooling.model.GradleModuleVersion;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class DefaultEclipseExternalDependency implements ExternalDependencyVersion1, Serializable {
-    private final File file;
-    private final File javadoc;
-    private final File source;
-    private final GradleModuleVersion moduleVersion;
-
-    public DefaultEclipseExternalDependency(File file, File javadoc, File source, ModuleVersionIdentifier identifier) {
-        this.file = file;
-        this.javadoc = javadoc;
-        this.source = source;
-        moduleVersion = (identifier == null)? null : new DefaultGradleModuleVersion(identifier);
-    }
-
-    public File getFile() {
-        return file;
-    }
-
-    public File getJavadoc() {
-        return javadoc;
-    }
-
-    public File getSource() {
-        return source;
-    }
-
-    public GradleModuleVersion getGradleModuleVersion() {
-        return moduleVersion;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseLinkedResource.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseLinkedResource.java
deleted file mode 100644
index d50f281..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseLinkedResource.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.eclipse;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseLinkedResourceVersion1;
-
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 6/11/11
- */
-public class DefaultEclipseLinkedResource implements Serializable, EclipseLinkedResourceVersion1 {
-
-    private String name;
-    private String type;
-    private String location;
-    private String locationUri;
-
-    public DefaultEclipseLinkedResource(String name, String type, String location, String locationUri) {
-        this.name = name;
-        this.type = type;
-        this.location = location;
-        this.locationUri = locationUri;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public String getLocation() {
-        return location;
-    }
-
-    public String getLocationUri() {
-        return locationUri;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProject.java
deleted file mode 100644
index 9a0e141..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProject.java
+++ /dev/null
@@ -1,134 +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.eclipse;
-
-import com.google.common.collect.Lists;
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-import org.gradle.tooling.internal.protocol.eclipse.*;
-import org.gradle.tooling.model.GradleProject;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-public class DefaultEclipseProject implements EclipseProjectVersion3, Serializable {
-    private final String name;
-    private final String path;
-    private EclipseProjectVersion3 parent;
-    private List<ExternalDependencyVersion1> classpath;
-    private final List<EclipseProjectVersion3> children;
-    private List<EclipseSourceDirectoryVersion1> sourceDirectories;
-    private List<EclipseProjectDependencyVersion2> projectDependencies;
-    private final String description;
-    private final File projectDirectory;
-    private Iterable<? extends EclipseTaskVersion1> tasks;
-    private Iterable<? extends EclipseLinkedResourceVersion1> linkedResources;
-    private GradleProject gradleProject;
-
-    public DefaultEclipseProject(String name, String path, String description, File projectDirectory, Iterable<? extends EclipseProjectVersion3> children) {
-        this.name = name;
-        this.path = path;
-        this.description = description;
-        this.projectDirectory = projectDirectory;
-        this.tasks = Collections.emptyList();
-        this.children = Lists.newArrayList(children);
-        this.classpath = Collections.emptyList();
-        this.sourceDirectories = Collections.emptyList();
-        this.projectDependencies = Collections.emptyList();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("project '%s'", path);
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public EclipseProjectVersion3 getParent() {
-        return parent;
-    }
-
-    public File getProjectDirectory() {
-        return projectDirectory;
-    }
-
-    public void setParent(EclipseProjectVersion3 parent) {
-        this.parent = parent;
-    }
-
-    public List<EclipseProjectVersion3> getChildren() {
-        return children;
-    }
-
-    public Iterable<? extends EclipseSourceDirectoryVersion1> getSourceDirectories() {
-        return sourceDirectories;
-    }
-
-    public void setSourceDirectories(List<EclipseSourceDirectoryVersion1> sourceDirectories) {
-        this.sourceDirectories = sourceDirectories;
-    }
-
-    public Iterable<? extends EclipseProjectDependencyVersion2> getProjectDependencies() {
-        return projectDependencies;
-    }
-
-    public void setProjectDependencies(List<EclipseProjectDependencyVersion2> projectDependencies) {
-        this.projectDependencies = projectDependencies;
-    }
-
-    public List<ExternalDependencyVersion1> getClasspath() {
-        return classpath;
-    }
-    public void setClasspath(List<ExternalDependencyVersion1> classpath) {
-        this.classpath = classpath;
-    }
-
-    public Iterable<? extends EclipseTaskVersion1> getTasks() {
-        return tasks;
-    }
-
-    public void setTasks(Iterable<? extends EclipseTaskVersion1> tasks) {
-        this.tasks = tasks;
-    }
-
-    public Iterable<? extends EclipseLinkedResourceVersion1> getLinkedResources() {
-        return linkedResources;
-    }
-
-    public void setLinkedResources(Iterable<? extends EclipseLinkedResourceVersion1> linkedResources) {
-        this.linkedResources = linkedResources;
-    }
-
-    public GradleProject getGradleProject() {
-        return gradleProject;
-    }
-
-    public DefaultEclipseProject setGradleProject(GradleProject gradleProject) {
-        this.gradleProject = gradleProject;
-        return this;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectDependency.java
deleted file mode 100644
index 6a97814..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectDependency.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.eclipse;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
-import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
-
-import java.io.Serializable;
-
-public class DefaultEclipseProjectDependency implements EclipseProjectDependencyVersion2, Serializable {
-    private final String path;
-    private final HierarchicalEclipseProjectVersion1 target;
-
-    public DefaultEclipseProjectDependency(String path, HierarchicalEclipseProjectVersion1 target) {
-        this.target = target;
-        this.path = path;
-    }
-
-    public HierarchicalEclipseProjectVersion1 getTargetProject() {
-        return target;
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("project dependency %s (%s)", path, target);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseSourceDirectory.java
deleted file mode 100644
index 4d5939e..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseSourceDirectory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.eclipse;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class DefaultEclipseSourceDirectory implements EclipseSourceDirectoryVersion1, Serializable {
-    private final String path;
-    private final File directory;
-
-    public DefaultEclipseSourceDirectory(String path, File directory) {
-        this.path = path;
-        this.directory = directory;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("source directory '%s'", path);
-    }
-
-    public File getDirectory() {
-        return directory;
-    }
-
-    public String getPath() {
-        return path;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseTask.java
deleted file mode 100644
index d00db49..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseTask.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.eclipse;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
-
-import java.io.Serializable;
-
-public class DefaultEclipseTask implements EclipseTaskVersion1, Serializable {
-    private final EclipseProjectVersion3 project;
-    private final String path;
-    private final String name;
-    private final String description;
-
-    public DefaultEclipseTask(EclipseProjectVersion3 project, String path, String name, String description) {
-        this.project = project;
-        this.path = path;
-        this.name = name;
-        this.description = description;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("task '%s'", path);
-    }
-
-    public EclipseProjectVersion3 getProject() {
-        return project;
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/BasicGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/BasicGradleProject.java
new file mode 100644
index 0000000..e39fc76
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/BasicGradleProject.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class BasicGradleProject extends PartialBasicGradleProject {
+    private File projectDirectory;
+    private Set<BasicGradleProject> children = new LinkedHashSet<BasicGradleProject>();
+
+
+    public File getProjectDirectory() {
+        return projectDirectory;
+    }
+
+    public BasicGradleProject setProjectDirectory(File projectDirectory) {
+        this.projectDirectory = projectDirectory;
+        return this;
+    }
+
+    public BasicGradleProject setPath(String path) {
+        super.setPath(path);
+        return this;
+    }
+
+    public BasicGradleProject setName(String name) {
+        super.setName(name);
+        return this;
+    }
+
+    public Set<? extends BasicGradleProject> getChildren() {
+        return children;
+    }
+
+    public BasicGradleProject addChild(BasicGradleProject child) {
+        children.add(child);
+        return this;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/BasicGradleTaskSelector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/BasicGradleTaskSelector.java
new file mode 100644
index 0000000..771232e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/BasicGradleTaskSelector.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import org.gradle.api.Nullable;
+import org.gradle.tooling.model.TaskSelector;
+
+import java.util.SortedSet;
+
+/**
+ * Data used for {@link org.gradle.tooling.model.TaskSelector} when created in consumer.
+ */
+public class BasicGradleTaskSelector implements TaskSelector, TaskListingLaunchable {
+    private String name;
+    private String displayName;
+    private String description;
+    private SortedSet<String> tasks;
+
+    public String getName() {
+        return name;
+    }
+
+    public BasicGradleTaskSelector setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    @Nullable
+    public String getDescription() {
+        return description;
+    }
+
+    public BasicGradleTaskSelector setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public BasicGradleTaskSelector setDisplayName(String displayName) {
+        this.displayName = displayName;
+        return this;
+    }
+
+    public SortedSet<String> getTaskNames() {
+        return tasks;
+    }
+
+    public BasicGradleTaskSelector setTaskNames(SortedSet<String> tasks) {
+        this.tasks = tasks;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "BasicGradleTaskSelector{"
+                + "name='" + name + "' "
+                + "description='" + description + "'}";
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultBuildInvocations.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultBuildInvocations.java
new file mode 100644
index 0000000..0c84ba0
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultBuildInvocations.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.gradle;
+
+
+import org.gradle.tooling.model.TaskSelector;
+
+import java.io.Serializable;
+import java.util.List;
+
+// used with LaunchingGradleTask from provider or with DefaultGradleTask from adapting consumer
+public class DefaultBuildInvocations<T> implements Serializable {
+    private List<? extends TaskSelector> selectors;
+    private List<T> tasks;
+
+    public DefaultBuildInvocations<T> setSelectors(List<? extends TaskSelector> selectors) {
+        this.selectors = selectors;
+        return this;
+    }
+
+    public List<? extends TaskSelector> getTaskSelectors() {
+        return selectors;
+    }
+
+    public DefaultBuildInvocations<T> setTasks(List<T> tasks) {
+        this.tasks = tasks;
+        return this;
+    }
+
+    public List<T> getTasks() {
+        return tasks;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultConvertedGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultConvertedGradleProject.java
new file mode 100644
index 0000000..9bf62e4
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultConvertedGradleProject.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DefaultConvertedGradleProject extends PartialGradleProject {
+    private List<DefaultGradleTask> tasks = new LinkedList<DefaultGradleTask>();
+
+    @Override
+    public DefaultConvertedGradleProject setName(String name) {
+        super.setName(name);
+        return this;
+    }
+
+    @Override
+    public DefaultConvertedGradleProject setPath(String path) {
+        super.setPath(path);
+        return this;
+    }
+
+    @Override
+    public DefaultConvertedGradleProject setDescription(String description) {
+        super.setDescription(description);
+        return this;
+    }
+
+    @Override
+    public DefaultConvertedGradleProject setChildren(List<? extends PartialGradleProject> children) {
+        super.setChildren(children);
+        return this;
+    }
+
+    public Collection<DefaultGradleTask> getTasks() {
+        return tasks;
+    }
+
+    public DefaultConvertedGradleProject setTasks(List<DefaultGradleTask> tasks) {
+        this.tasks = tasks;
+        return this;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleBuild.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleBuild.java
new file mode 100644
index 0000000..b558367
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleBuild.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.io.Serializable;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultGradleBuild implements Serializable {
+    private PartialBasicGradleProject rootProject;
+    private Set<PartialBasicGradleProject> projects = new LinkedHashSet<PartialBasicGradleProject>();
+
+    public PartialBasicGradleProject getRootProject() {
+        return rootProject;
+    }
+
+    public DefaultGradleBuild setRootProject(PartialBasicGradleProject rootProject) {
+        this.rootProject = rootProject;
+        return this;
+    }
+
+    public Set<? extends PartialBasicGradleProject> getProjects() {
+        return projects;
+    }
+
+    public void addProject(PartialBasicGradleProject project) {
+        projects.add(project);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java
index 203bb56..fb9e43b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java
@@ -21,9 +21,6 @@ import org.gradle.tooling.model.GradleModuleVersion;
 
 import java.io.Serializable;
 
-/**
- * by Szczepan Faber, created at: 5/11/12
- */
 public class DefaultGradleModuleVersion implements GradleModuleVersion, Serializable {
 
     private final String group;
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java
index 34db911..d28f624 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,111 +16,61 @@
 
 package org.gradle.tooling.internal.gradle;
 
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.tooling.model.GradleTask;
-import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+import org.gradle.tooling.internal.protocol.InternalGradleProject;
 
 import java.io.File;
 import java.io.Serializable;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 
-/**
- * @author: Szczepan Faber, created at: 7/27/11
- */
-public class DefaultGradleProject implements ProjectVersion3, GradleProject, Serializable {
-
-    private String name;
-    private String description;
-    private String path;
-    private GradleProject parent;
-    private List<? extends GradleProject> children = new LinkedList<GradleProject>();
-    private List<GradleTask> tasks = new LinkedList<GradleTask>();
-
-    public DefaultGradleProject() {}
-
-    public DefaultGradleProject(String path) {
-        this.path = path;
-    }
+public class DefaultGradleProject<T> extends PartialGradleProject implements InternalGradleProject, Serializable, GradleProjectIdentity {
+    private DefaultGradleScript buildScript = new DefaultGradleScript();
+    private List<T> tasks = new LinkedList<T>();
 
-    public String getName() {
-        return name;
-    }
-
-    public DefaultGradleProject setName(String name) {
-        this.name = name;
+    @Override
+    public DefaultGradleProject<T> setName(String name) {
+        super.setName(name);
         return this;
     }
 
-    public String getDescription() {
-        return description;
-    }
-
-    public DefaultGradleProject setDescription(String description) {
-        this.description = description;
+    @Override
+    public DefaultGradleProject<T> setPath(String path) {
+        super.setPath(path);
         return this;
     }
 
-    public GradleProject getParent() {
-        return parent;
-    }
-
-    public DefaultGradleProject setParent(GradleProject parent) {
-        this.parent = parent;
+    @Override
+    public DefaultGradleProject<T> setDescription(String description) {
+        super.setDescription(description);
         return this;
     }
 
-    public DomainObjectSet<? extends GradleProject> getChildren() {
-        return new ImmutableDomainObjectSet<GradleProject>(children);
-    }
-
-    public DefaultGradleProject setChildren(List<? extends GradleProject> children) {
-        this.children = children;
+    @Override
+    public DefaultGradleProject<T> setChildren(List<? extends PartialGradleProject> children) {
+        super.setChildren(children);
         return this;
     }
 
-    public DomainObjectSet<GradleTask> getTasks() {
-        return new ImmutableDomainObjectSet<GradleTask>(tasks);
+    public Collection<T> getTasks() {
+        return tasks;
     }
 
-    public DefaultGradleProject setTasks(List<GradleTask> tasks) {
+    public DefaultGradleProject<T> setTasks(List<T> tasks) {
         this.tasks = tasks;
         return this;
     }
 
-    public String getPath() {
-        return path;
-    }
-
-    public DefaultGradleProject setPath(String path) {
-        this.path = path;
-        return this;
+    public DefaultGradleScript getBuildScript() {
+        return buildScript;
     }
 
     public File getProjectDirectory() {
         throw new RuntimeException("ProjectVersion3 methods are deprecated.");
     }
 
-    public GradleProject findByPath(String path) {
-        if (path.equals(this.path)) {
-            return this;
-        }
-        for (GradleProject child : children) {
-            GradleProject found = child.findByPath(path);
-            if (found != null) {
-                return found;
-            }
-        }
-
-        return null;
-    }
-
-    public String toString() {
-        return "GradleProject{"
-                + "path='" + path + '\''
-                + "tasks='" + tasks + '\''
-                + '}';
+    @Override
+    public DefaultGradleProject<T> findByPath(String path) {
+        return (DefaultGradleProject<T>) super.findByPath(path);
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProjectTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProjectTask.java
new file mode 100644
index 0000000..b106d79
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProjectTask.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+public class DefaultGradleProjectTask extends DefaultGradleTask {
+    private PartialGradleProject project;
+
+    public PartialGradleProject getProject() {
+        return project;
+    }
+
+    public DefaultGradleProjectTask setProject(PartialGradleProject project) {
+        this.project = project;
+        return this;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradlePublication.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradlePublication.java
new file mode 100644
index 0000000..b93e232
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradlePublication.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import com.google.common.base.Objects;
+import org.gradle.tooling.model.GradleModuleVersion;
+
+import java.io.Serializable;
+
+public class DefaultGradlePublication implements Serializable {
+    private GradleModuleVersion id;
+
+    public GradleModuleVersion getId() {
+        return id;
+    }
+
+    public DefaultGradlePublication setId(GradleModuleVersion id) {
+        this.id = id;
+        return this;
+    }
+
+    public String toString() {
+        return Objects.toStringHelper(this)
+                .add("id", id)
+                .toString();
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleScript.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleScript.java
new file mode 100644
index 0000000..51ddbcd
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleScript.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import org.gradle.tooling.model.gradle.GradleScript;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultGradleScript implements GradleScript, Serializable {
+    private File sourceFile;
+
+    public File getSourceFile() {
+        return sourceFile;
+    }
+
+    public void setSourceFile(File sourceFile) {
+        this.sourceFile = sourceFile;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java
index bf0c39e..3e89809 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java
@@ -16,20 +16,17 @@
 
 package org.gradle.tooling.internal.gradle;
 
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.tooling.model.GradleTask;
+import com.google.common.collect.Sets;
+import org.gradle.tooling.internal.consumer.converters.TaskNameComparator;
 
-import java.io.Serializable;
+import java.util.SortedSet;
 
-/**
- * @author: Szczepan Faber, created at: 7/27/11
- */
-public class DefaultGradleTask implements GradleTask, Serializable {
+public class DefaultGradleTask implements TaskListingLaunchable {
 
-    String path;
-    String name;
-    String description;
-    GradleProject project;
+    private String path;
+    private String name;
+    private String description;
+    private String displayName;
 
     public String getPath() {
         return path;
@@ -49,6 +46,15 @@ public class DefaultGradleTask implements GradleTask, Serializable {
         return this;
     }
 
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public DefaultGradleTask setDisplayName(String displayName) {
+        this.displayName = displayName;
+        return this;
+    }
+
     public String getDescription() {
         return description;
     }
@@ -58,19 +64,18 @@ public class DefaultGradleTask implements GradleTask, Serializable {
         return this;
     }
 
-    public GradleProject getProject() {
-        return project;
-    }
-
-    public DefaultGradleTask setProject(GradleProject project) {
-        this.project = project;
-        return this;
+    public SortedSet<String> getTaskNames() {
+        // TODO use comparator
+        SortedSet result = Sets.newTreeSet(new TaskNameComparator());
+        result.add(getPath());
+        return result;
     }
 
     @Override
     public String toString() {
         return "GradleTask{"
                 + "name='" + name + '\''
+                + " path='" + path + '\''
                 + '}';
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultProjectPublications.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultProjectPublications.java
new file mode 100644
index 0000000..7bbbe12
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultProjectPublications.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class DefaultProjectPublications implements Serializable {
+    private List<DefaultGradlePublication> publications;
+
+    public List<DefaultGradlePublication> getPublications() {
+        return publications;
+    }
+
+    public DefaultProjectPublications setPublications(List<DefaultGradlePublication> publications) {
+        this.publications = publications;
+        return this;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/GradleProjectIdentity.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/GradleProjectIdentity.java
new file mode 100644
index 0000000..c5e9021
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/GradleProjectIdentity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+public interface GradleProjectIdentity {
+    String getPath();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/PartialBasicGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/PartialBasicGradleProject.java
new file mode 100644
index 0000000..3941529
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/PartialBasicGradleProject.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.io.Serializable;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class PartialBasicGradleProject implements Serializable, GradleProjectIdentity {
+    private String name;
+    private String path;
+    private PartialBasicGradleProject parent;
+    private Set<PartialBasicGradleProject> children = new LinkedHashSet<PartialBasicGradleProject>();
+
+    @Override
+    public String toString() {
+        return "GradleProject{path='" + path + "\'}";
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public PartialBasicGradleProject setPath(String path) {
+        this.path = path;
+        return this;
+    }
+
+    public PartialBasicGradleProject getParent() {
+        return parent;
+    }
+
+    public PartialBasicGradleProject setParent(PartialBasicGradleProject parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public PartialBasicGradleProject setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public Set<? extends PartialBasicGradleProject> getChildren() {
+        return children;
+    }
+
+    public PartialBasicGradleProject addChild(PartialBasicGradleProject child) {
+        children.add(child);
+        return this;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/PartialGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/PartialGradleProject.java
new file mode 100644
index 0000000..29fe7f6
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/PartialGradleProject.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PartialGradleProject implements Serializable {
+    private String name;
+    private String description;
+    private String path;
+    private PartialGradleProject parent;
+    private List<? extends PartialGradleProject> children = new LinkedList<PartialGradleProject>();
+
+    public String getName() {
+        return name;
+    }
+
+    public PartialGradleProject setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public PartialGradleProject setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public PartialGradleProject getParent() {
+        return parent;
+    }
+
+    public PartialGradleProject setParent(PartialGradleProject parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    public Collection<? extends PartialGradleProject> getChildren() {
+        return children;
+    }
+
+    public PartialGradleProject setChildren(List<? extends PartialGradleProject> children) {
+        this.children = children;
+        return this;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public PartialGradleProject setPath(String path) {
+        this.path = path;
+        return this;
+    }
+
+    public PartialGradleProject findByPath(String path) {
+        if (path.equals(this.path)) {
+            return this;
+        }
+        for (PartialGradleProject child : children) {
+            PartialGradleProject found = child.findByPath(path);
+            if (found != null) {
+                return found;
+            }
+        }
+
+        return null;
+    }
+
+    public String toString() {
+        return "GradleProject{"
+                + "path='" + path + '\''
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/TaskListingLaunchable.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/TaskListingLaunchable.java
new file mode 100644
index 0000000..787b182
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/TaskListingLaunchable.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.gradle;
+
+import java.util.SortedSet;
+
+public interface TaskListingLaunchable {
+    SortedSet<String> getTaskNames();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaCompilerOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaCompilerOutput.java
deleted file mode 100644
index e7d9856..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaCompilerOutput.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.idea;
-
-import org.gradle.tooling.model.idea.IdeaCompilerOutput;
-
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 8/5/11
- */
-public class DefaultIdeaCompilerOutput implements IdeaCompilerOutput, Serializable {
-
-    private boolean inheritOutputDirs;
-    private File outputDir;
-    private File testOutputDir;
-
-    public boolean getInheritOutputDirs() {
-        return inheritOutputDirs;
-    }
-
-    public DefaultIdeaCompilerOutput setInheritOutputDirs(boolean inheritOutputDirs) {
-        this.inheritOutputDirs = inheritOutputDirs;
-        return this;
-    }
-
-    public File getOutputDir() {
-        return outputDir;
-    }
-
-    public DefaultIdeaCompilerOutput setOutputDir(File outputDir) {
-        this.outputDir = outputDir;
-        return this;
-    }
-
-    public File getTestOutputDir() {
-        return testOutputDir;
-    }
-
-    public DefaultIdeaCompilerOutput setTestOutputDir(File testOutputDir) {
-        this.testOutputDir = testOutputDir;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "IdeaCompilerOutput{"
-                + "inheritOutputDirs=" + inheritOutputDirs
-                + ", outputDir=" + outputDir
-                + ", testOutputDir=" + testOutputDir
-                + '}';
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaContentRoot.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaContentRoot.java
deleted file mode 100644
index 6620ee5..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaContentRoot.java
+++ /dev/null
@@ -1,83 +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.idea;
-
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.idea.IdeaContentRoot;
-import org.gradle.tooling.model.idea.IdeaSourceDirectory;
-import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * @author: Szczepan Faber, created at: 8/3/11
- */
-public class DefaultIdeaContentRoot implements IdeaContentRoot, Serializable {
-
-    File rootDirectory;
-    Set<IdeaSourceDirectory> sourceDirectories = new LinkedHashSet<IdeaSourceDirectory>();
-    Set<IdeaSourceDirectory> testDirectories = new LinkedHashSet<IdeaSourceDirectory>();
-    Set<File> excludeDirectories = new LinkedHashSet<File>();
-
-    public File getRootDirectory() {
-        return rootDirectory;
-    }
-
-    public DefaultIdeaContentRoot setRootDirectory(File rootDirectory) {
-        this.rootDirectory = rootDirectory;
-        return this;
-    }
-
-    public DomainObjectSet<IdeaSourceDirectory> getSourceDirectories() {
-        return new ImmutableDomainObjectSet<IdeaSourceDirectory>(sourceDirectories);
-    }
-
-    public DefaultIdeaContentRoot setSourceDirectories(Set<IdeaSourceDirectory> sourceDirectories) {
-        this.sourceDirectories = sourceDirectories;
-        return this;
-    }
-
-    public DomainObjectSet<IdeaSourceDirectory> getTestDirectories() {
-        return new ImmutableDomainObjectSet<IdeaSourceDirectory>(testDirectories);
-    }
-
-    public DefaultIdeaContentRoot setTestDirectories(Set<IdeaSourceDirectory> testDirectories) {
-        this.testDirectories = testDirectories;
-        return this;
-    }
-
-    public Set<File> getExcludeDirectories() {
-        return excludeDirectories;
-    }
-
-    public DefaultIdeaContentRoot setExcludeDirectories(Set<File> excludeDirectories) {
-        this.excludeDirectories = excludeDirectories;
-        return this;
-    }
-
-    public String toString() {
-        return "IdeaContentRoot{"
-                + "rootDirectory=" + rootDirectory
-                + ", sourceDirectories count=" + sourceDirectories.size()
-                + ", testDirectories count=" + testDirectories.size()
-                + ", excludeDirectories count=" + excludeDirectories.size()
-                + '}';
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaDependencyScope.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaDependencyScope.java
deleted file mode 100644
index 815e4d2..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaDependencyScope.java
+++ /dev/null
@@ -1,67 +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.idea;
-
-import org.gradle.tooling.model.idea.IdeaDependencyScope;
-
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 8/3/11
- */
-public class DefaultIdeaDependencyScope implements IdeaDependencyScope, Serializable {
-
-    String scope;
-
-    public DefaultIdeaDependencyScope(String scope) {
-        this.scope = scope;
-    }
-
-    public String getScope() {
-        return scope;
-    }
-
-    @Override
-    public String toString() {
-        return "IdeaDependencyScope{"
-                + "scope='" + scope + '\''
-                + '}';
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof DefaultIdeaDependencyScope)) {
-            return false;
-        }
-
-        DefaultIdeaDependencyScope that = (DefaultIdeaDependencyScope) o;
-
-        if (scope != null ? !scope.equals(that.scope) : that.scope != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return scope != null ? scope.hashCode() : 0;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaLanguageLevel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaLanguageLevel.java
deleted file mode 100644
index fa0313a..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaLanguageLevel.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.idea;
-
-import org.gradle.tooling.model.idea.IdeaLanguageLevel;
-
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 7/30/11
- */
-public class DefaultIdeaLanguageLevel implements IdeaLanguageLevel, Serializable {
-
-    private final String level;
-
-    public DefaultIdeaLanguageLevel(String level) {
-        this.level = level;
-    }
-
-    public boolean isJDK_1_4() {
-        return "JDK_1_4".equals(level);
-    }
-
-    public boolean isJDK_1_5() {
-        return "JDK_1_5".equals(level);
-    }
-
-    public boolean isJDK_1_6() {
-        return "JDK_1_6".equals(level);
-    }
-
-    public boolean isJDK_1_7() {
-        return "JDK_1_7".equals(level);
-    }
-
-    public boolean isJDK_1_8() {
-        return "JDK_1_8".equals(level);
-    }
-
-    public String getLevel() {
-        return level;
-    }
-
-    @Override
-    public String toString() {
-        return "IdeaLanguageLevel{level='" + level + "'}";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof DefaultIdeaLanguageLevel)) {
-            return false;
-        }
-
-        DefaultIdeaLanguageLevel that = (DefaultIdeaLanguageLevel) o;
-
-        if (level != null ? !level.equals(that.level) : that.level != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return level != null ? level.hashCode() : 0;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
deleted file mode 100644
index aaf5dfe..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
+++ /dev/null
@@ -1,125 +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.idea;
-
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.tooling.model.HierarchicalElement;
-import org.gradle.tooling.model.Task;
-import org.gradle.tooling.model.idea.*;
-import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * @author: Szczepan Faber, created at: 7/25/11
- */
-public class DefaultIdeaModule implements Serializable, IdeaModule {
-
-    private String name;
-    private List<? extends IdeaContentRoot> contentRoots = new LinkedList<IdeaContentRoot>();
-    private IdeaProject parent;
-
-    private List<IdeaDependency> dependencies = new LinkedList<IdeaDependency>();
-    private GradleProject gradleProject;
-
-    private IdeaCompilerOutput compilerOutput;
-
-    public String getName() {
-        return name;
-    }
-
-    public DefaultIdeaModule setName(String name) {
-        this.name = name;
-        return this;
-    }
-
-    public DomainObjectSet<? extends IdeaContentRoot> getContentRoots() {
-        return new ImmutableDomainObjectSet<IdeaContentRoot>(contentRoots);
-    }
-
-    public DefaultIdeaModule setContentRoots(List<? extends IdeaContentRoot> contentRoots) {
-        this.contentRoots = contentRoots;
-        return this;
-    }
-
-    public IdeaProject getParent() {
-        return parent;
-    }
-
-    public IdeaProject getProject() {
-        return parent;
-    }
-
-    public DefaultIdeaModule setParent(IdeaProject parent) {
-        this.parent = parent;
-        return this;
-    }
-
-    public DomainObjectSet<IdeaDependency> getDependencies() {
-        return new ImmutableDomainObjectSet<IdeaDependency>(dependencies);
-    }
-
-    public DefaultIdeaModule setDependencies(List<IdeaDependency> dependencies) {
-        this.dependencies = dependencies;
-        return this;
-    }
-
-    public DomainObjectSet<? extends Task> getTasks() {
-        throw new RuntimeException("not yet implemented");
-    }
-
-    public DomainObjectSet<? extends HierarchicalElement> getChildren() {
-        return new ImmutableDomainObjectSet<HierarchicalElement>(Collections.<HierarchicalElement>emptySet());
-    }
-
-    public String getDescription() {
-        return null;
-    }
-
-    public GradleProject getGradleProject() {
-        return gradleProject;
-    }
-
-    public DefaultIdeaModule setGradleProject(GradleProject gradleProject) {
-        this.gradleProject = gradleProject;
-        return this;
-    }
-
-    public IdeaCompilerOutput getCompilerOutput() {
-        return compilerOutput;
-    }
-
-    public DefaultIdeaModule setCompilerOutput(IdeaCompilerOutput compilerOutput) {
-        this.compilerOutput = compilerOutput;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "IdeaModule{"
-                + "name='" + name + '\''
-                + ", gradleProject='" + gradleProject + '\''
-                + ", contentRoots=" + contentRoots
-                + ", compilerOutput=" + compilerOutput
-                + ", dependencies count=" + dependencies.size()
-                + '}';
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java
deleted file mode 100644
index f77ed76..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java
+++ /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.tooling.internal.idea;
-
-import org.gradle.tooling.model.idea.IdeaDependencyScope;
-import org.gradle.tooling.model.idea.IdeaModule;
-import org.gradle.tooling.model.idea.IdeaModuleDependency;
-
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 7/26/11
- */
-public class DefaultIdeaModuleDependency implements IdeaModuleDependency, Serializable {
-
-    private IdeaDependencyScope scope;
-    private IdeaModule dependencyModule;
-    private boolean exported;
-
-    public IdeaDependencyScope getScope() {
-        return scope;
-    }
-
-    public DefaultIdeaModuleDependency setScope(IdeaDependencyScope scope) {
-        this.scope = scope;
-        return this;
-    }
-
-    public IdeaModule getDependencyModule() {
-        return dependencyModule;
-    }
-
-    public DefaultIdeaModuleDependency setDependencyModule(IdeaModule dependencyModule) {
-        this.dependencyModule = dependencyModule;
-        return this;
-    }
-
-    public boolean getExported() {
-        return exported;
-    }
-
-    public DefaultIdeaModuleDependency setExported(boolean exported) {
-        this.exported = exported;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "DefaultIdeaModuleDependency{"
-                 + "scope='" + scope + '\''
-                 + ", dependencyModule name='" + dependencyModule.getName() + '\''
-                 + ", exported=" + exported
-                 + '}';
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
deleted file mode 100644
index 8afa04c..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.idea;
-
-import org.gradle.tooling.internal.protocol.InternalIdeaProject;
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.HierarchicalElement;
-import org.gradle.tooling.model.idea.IdeaLanguageLevel;
-import org.gradle.tooling.model.idea.IdeaModule;
-import org.gradle.tooling.model.idea.IdeaProject;
-import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.LinkedList;
-
-/**
- * @author: Szczepan Faber, created at: 7/25/11
- */
-public class DefaultIdeaProject implements InternalIdeaProject, IdeaProject, Serializable {
-
-//    public static final long serialVersionUID = 1L;
-
-    private String id;
-    private String name;
-    private String description;
-    private Collection<? extends IdeaModule> children = new LinkedList<IdeaModule>();
-    private IdeaLanguageLevel languageLevel;
-    private String jdkName;
-
-    public IdeaLanguageLevel getLanguageLevel() {
-        return languageLevel;
-    }
-
-    public DefaultIdeaProject setLanguageLevel(IdeaLanguageLevel languageLevel) {
-        this.languageLevel = languageLevel;
-        return this;
-    }
-
-    public String getJdkName() {
-        return jdkName;
-    }
-
-    public DefaultIdeaProject setJdkName(String jdkName) {
-        this.jdkName = jdkName;
-        return this;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public DefaultIdeaProject setName(String name) {
-        this.name = name;
-        return this;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public DefaultIdeaProject setDescription(String description) {
-        this.description = description;
-        return this;
-    }
-
-    public HierarchicalElement getParent() {
-        return null;
-    }
-
-    public File getProjectDirectory() {
-        throw new UnsupportedOperationException("This method should not be used.");
-    }
-
-    public String getPath() {
-        throw new UnsupportedOperationException("This method should not be used.");
-    }
-
-    public DefaultIdeaProject setChildren(Collection<? extends IdeaModule> children) {
-        this.children = children;
-        return this;
-    }
-
-    public DomainObjectSet<? extends IdeaModule> getChildren() {
-        return new ImmutableDomainObjectSet<IdeaModule>(children);
-    }
-
-    public DomainObjectSet<? extends IdeaModule> getModules() {
-        return new ImmutableDomainObjectSet<IdeaModule>(children);
-    }
-
-    @Override
-    public String toString() {
-        return "DefaultIdeaProject{"
-                + " name='" + name + '\''
-                + ", description='" + description + '\''
-                + ", children count=" + children.size()
-                + ", languageLevel='" + languageLevel + '\''
-                + ", jdkName='" + jdkName + '\''
-                + '}';
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
deleted file mode 100644
index 02221f9..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.internal.idea;
-
-import org.gradle.tooling.model.GradleModuleVersion;
-import org.gradle.tooling.model.idea.IdeaDependencyScope;
-import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency;
-
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 7/26/11
- */
-public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryLibraryDependency, Serializable {
-
-    private File file;
-    private File source;
-    private File javadoc;
-    private Boolean exported;
-    private IdeaDependencyScope scope;
-    private GradleModuleVersion moduleVersion;
-
-    public File getFile() {
-        return file;
-    }
-
-    public DefaultIdeaSingleEntryLibraryDependency setFile(File file) {
-        this.file = file;
-        return this;
-    }
-
-    public File getSource() {
-        return source;
-    }
-
-    public DefaultIdeaSingleEntryLibraryDependency setSource(File source) {
-        this.source = source;
-        return this;
-    }
-
-    public File getJavadoc() {
-        return javadoc;
-    }
-
-    public GradleModuleVersion getGradleModuleVersion() {
-        return moduleVersion;
-    }
-
-    public DefaultIdeaSingleEntryLibraryDependency setJavadoc(File javadoc) {
-        this.javadoc = javadoc;
-        return this;
-    }
-
-    public boolean getExported() {
-        return exported;
-    }
-
-    public DefaultIdeaSingleEntryLibraryDependency setExported(Boolean exported) {
-        this.exported = exported;
-        return this;
-    }
-
-    public IdeaDependencyScope getScope() {
-        return scope;
-    }
-
-    public DefaultIdeaSingleEntryLibraryDependency setScope(IdeaDependencyScope scope) {
-        this.scope = scope;
-        return this;
-    }
-
-    public DefaultIdeaSingleEntryLibraryDependency setGradleModuleVersion(GradleModuleVersion moduleVersion) {
-        this.moduleVersion = moduleVersion;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "IdeaLibraryDependency{"
-                + "file=" + file
-                + ", source=" + source
-                + ", javadoc=" + javadoc
-                + ", exported=" + exported
-                + ", scope='" + scope + '\''
-                + ", id='" + moduleVersion + '\''
-                + '}';
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSourceDirectory.java
deleted file mode 100644
index 72021d1..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSourceDirectory.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.idea;
-
-import org.gradle.tooling.model.idea.IdeaSourceDirectory;
-
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * @author: Szczepan Faber, created at: 7/27/11
- */
-public class DefaultIdeaSourceDirectory implements IdeaSourceDirectory, Serializable {
-
-    private File directory;
-
-    public File getDirectory() {
-        return directory;
-    }
-
-    public DefaultIdeaSourceDirectory setDirectory(File directory) {
-        this.directory = directory;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "DefaultIdeaSourceDirectory{"
-                + "directory=" + directory
-                + '}';
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleBuildOutcome.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleBuildOutcome.java
deleted file mode 100644
index 6bebd3d..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleBuildOutcome.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.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
deleted file mode 100644
index 64f480a..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleFileBuildOutcome.java
+++ /dev/null
@@ -1,42 +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.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
deleted file mode 100644
index 4e27335..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultProjectOutcomes.java
+++ /dev/null
@@ -1,80 +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.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
index 984f810..f6bdcb6 100644
--- 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
@@ -19,15 +19,31 @@ package org.gradle.tooling.internal.protocol;
 /**
  * Mixed into a provider connection, to run actions against a build.
  *
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.2-rc-1 to 1.5. It is also used by later consumers when the provider does not
+ * implement {@link ModelBuilder}.
+ * </p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.2-rc-1.</p>
+ *
  * @since 1.2-rc-1
+ * @deprecated 1.6-rc-1. Use {@link ModelBuilder} instead.
+ * @see ConnectionVersion4
  */
+ at Deprecated
 public interface BuildActionRunner extends InternalProtocolInterface {
     /**
      * Performs some action against a build and returns some result of the given type.
      *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.2-rc-1 to 1.5.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.2-rc-1.</p>
+     *
      * @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.
+     * @since 1.2-rc-1
+     * @deprecated 1.6-rc-1. Use {@link ModelBuilder#getModel(ModelIdentifier, BuildParameters)} instead.
      */
+    @Deprecated
     <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/BuildExceptionVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildExceptionVersion1.java
index 029b9e1..fff915f 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildExceptionVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildExceptionVersion1.java
@@ -16,9 +16,12 @@
 package org.gradle.tooling.internal.protocol;
 
 /**
- * A wrapper around a build failure, to distinguish it from an infrastructure failure.
+ * A wrapper around a build failure, to distinguish it from an infrastructure failure. The details of the
+ * failure are made available in the cause of this exception.
  *
  * DO NOT CHANGE THIS CLASS. It is part of the cross-version protocol.
+ *
+ * @since 1.0-milestone-3
  */
 public class BuildExceptionVersion1 extends RuntimeException {
     public BuildExceptionVersion1(Throwable throwable) {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java
index 853f6b2..69538d3 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java
@@ -20,36 +20,59 @@ import java.util.concurrent.TimeUnit;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 1.0-milestone-3
+ * @deprecated 1.2-rc-1. Use {@link BuildParameters} instead.
  */
+ at Deprecated
 public interface BuildOperationParametersVersion1 extends LongRunningOperationParametersVersion1 {
+    /**
+     * @since 1.0-milestone-3
+     */
     File getProjectDir();
 
     /**
      * Specifies whether to search for root project, or null to use default.
+     *
+     * @since 1.0-milestone-3
      */
     Boolean isSearchUpwards();
 
     /**
      * Returns the Gradle user home directory, or null to use default.
+     *
+     * @since 1.0-milestone-3
      */
     File getGradleUserHomeDir();
 
     /**
      * Specifies whether to run the build in this process, or null to use default.
+     *
+     * @since 1.0-milestone-3
      */
     Boolean isEmbedded();
 
     /**
      * Specifies the maximum idle time for any daemon process launched by the provider, or null to use the default.
+     *
+     * @since 1.0-milestone-3
      */
     Integer getDaemonMaxIdleTimeValue();
 
     /**
      * Specifies the units for the maximum idle time.
+     *
+     * @since 1.0-milestone-3
      */
     TimeUnit getDaemonMaxIdleTimeUnits();
 
+    /**
+     * @since 1.0-milestone-3
+     */
     long getStartTime();
 
+    /**
+     * @since 1.0-milestone-7
+     */
     boolean getVerboseLogging();
 }
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
index 8b90eb9..6f1edf1 100644
--- 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
@@ -19,8 +19,8 @@ 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.
+ * <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
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParametersVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParametersVersion1.java
index bd20a9e..e90f31a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParametersVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParametersVersion1.java
@@ -19,7 +19,14 @@ import java.util.List;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 1.0-milestone-3
+ * @deprecated 1.2-rc-1. Use {@link BuildParameters} instead.
  */
+ at Deprecated
 public interface BuildParametersVersion1 {
+    /**
+     * @since 1.0-milestone-3
+     */
     List<String> getTasks();
 }
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
index f99ddb1..bc1aac9 100644
--- 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
@@ -19,8 +19,22 @@ package org.gradle.tooling.internal.protocol;
 /**
  * Mixed into a provider connection, to allow the connection to be configured.
  *
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.2-rc-1.</p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.2-rc-1.</p>
+ *
  * @since 1.2-rc-1
+ * @see ConnectionVersion4
  */
 public interface ConfigurableConnection extends InternalProtocolInterface {
+    /**
+     * Configures this connection with the given parameters.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.2-rc-1.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.2-rc-1.</p>
+     *
+     * @since 1.2-rc-1
+     */
     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
index 7e24dcf..72da8a9 100644
--- 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
@@ -20,7 +20,7 @@ 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.
+ * and {@link org.gradle.tooling.internal.consumer.ConnectionParameters} to see the configuration provided by the consumer.
  *
  * @since 1.2-rc-1
  */
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 38e164b..6667a2d 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
@@ -16,51 +16,78 @@
 package org.gradle.tooling.internal.protocol;
 
 /**
- * <p>Represents a connection to a particular Gradle implementation.
+ * <p>Represents a connection to a Gradle implementation.
  *
  * <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.
+ * <li>Implementations should implement {@link ModelBuilder}. This is used by all consumer versions from 1.6-rc-1.
+ * <li>Implementations should implement {@link InternalBuildActionExecutor}. This is used by all consumer versions from 1.8-rc-1.
+ * <li>Implementations should implement {@link ConfigurableConnection}. This is used by all consumer versions from 1.2-rc-1.
+ * <li>Implementations should provide a zero-args constructor. This is used by all consumer versions from 1.0-milestone-3.
+ * <li>For backwards compatibility, implementations should implement {@link BuildActionRunner}. This is used by consumer versions from 1.2-rc-1 to 1.5.
+ * <li>For backwards compatibility, implementations should implement {@link InternalConnection}. This is used by consumer versions from 1.0-milestone-8 to 1.1.
+ * <li>For backwards compatibility, implementations should provide a {@code void configureLogging(boolean verboseLogging)} method. This is used by consumer versions
+ * 1.0-rc-1 to 1.1.
  * </ul>
  *
- * <p>
- * Changes to this interface may break the cross-version protocol.
- * If you change it, make sure you run the all tooling api tests to flush out compatibility issues.
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.0-milestone-3.</p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.0-milestone-3.</p>
+ *
+ * @since 1.0-milestone-3
  */
 public interface ConnectionVersion4 {
     /**
-     * Stops this connection, blocking until complete.
+     * <p>Stops this connection, blocking until complete.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.0-milestone-3.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.0-milestone-3.</p>
+     *
+     * @since 1.0-milestone-3
      */
     void stop();
 
     /**
-     * Returns the meta-data for this connection. The implementation of this method should be fast, and should continue to work after the connection has been stopped.
+     * <p>Returns the meta-data for this connection. The implementation of this method should be fast, and should continue to work after the connection has been stopped.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.0-milestone-3.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.0-milestone-3.</p>
+     *
      * @return The meta-data.
+     * @since 1.0-milestone-3
      */
     ConnectionMetaDataVersion1 getMetaData();
 
     /**
-     * Fetches a snapshot of the model for the project.
-     * <p>
+     * <p>Fetches a snapshot of the model for the project.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.0-milestone-3 to 1.0-milestone-7. It is also used by later consumers when the provider
+     * does not implement {@link InternalConnection} or {@link BuildActionRunner}
+     * </p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.0-milestone-3.</p>
      *
      * @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.
+     * @since 1.0-milestone-3
+     * @deprecated 1.0-milestone-8. Use {@link ModelBuilder#getModel(ModelIdentifier, BuildParameters)} instead.
      */
     @Deprecated
     ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) throws UnsupportedOperationException, IllegalStateException;
 
     /**
-     * Executes a build.
+     * <p>Executes a build.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.0-milestone-3 to 1.1. It is also used by later consumers when the provider
+     * does not implement {@link BuildActionRunner}
+     * </p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.0-milestone-3.</p>
      *
      * @param buildParameters The parameters for the build.
      * @throws IllegalStateException When this connection has been stopped.
-     * @deprecated Use {@link BuildActionRunner#run(Class, BuildParameters)} instead.
+     * @since 1.0-milestone-3
+     * @deprecated 1.2-rc-1. Use {@link ModelBuilder#getModel(ModelIdentifier, 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/InternalBasicIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBasicIdeaProject.java
index 6b4a3f4..3f39c9c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBasicIdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBasicIdeaProject.java
@@ -17,6 +17,8 @@
 package org.gradle.tooling.internal.protocol;
 
 /**
- * See {@link org.gradle.tooling.internal.protocol.InternalProtocolInterface}
+ * See {@link InternalProtocolInterface}
+ *
+ * @since 1.0-milestone-5
  */
 public interface InternalBasicIdeaProject extends ProjectVersion3, InternalProtocolInterface {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildAction.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildAction.java
new file mode 100644
index 0000000..d3d45d9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildAction.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.Serializable;
+
+/**
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is implemented by all consumer versions from 1.8-rc-1.</p>
+ * <p>Provider compatibility: This interface is consumed by all provider versions from 1.8-rc-1.</p>
+ *
+ * @since 1.8-rc-1
+ */
+public interface InternalBuildAction<T> extends InternalProtocolInterface, Serializable {
+    /**
+     * Performs some action against a build and returns a result.
+     *
+     * @since 1.8-rc-1
+     */
+    T execute(InternalBuildController buildController);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildActionExecutor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildActionExecutor.java
new file mode 100644
index 0000000..7064361
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildActionExecutor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
+
+/**
+ * Mixed into a provider connection, to allow client-provided build actions to be executed.
+ *
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.8-rc-1.</p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.8-rc-1.</p>
+ *
+ * @since 1.8-rc-1
+ * @see ConnectionVersion4
+ */
+public interface InternalBuildActionExecutor extends InternalProtocolInterface {
+    /**
+     * Performs some action against a build and returns the result.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.8-rc-1.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.8-rc-1.</p>
+     *
+     * @throws BuildExceptionVersion1 On build failure.
+     * @throws InternalUnsupportedBuildArgumentException When the specified command-line options are not supported.
+     * @throws InternalBuildActionFailureException When the action fails with an exception.
+     * @throws IllegalStateException When this connection has been stopped.
+     * @since 1.8-rc-1
+     */
+    <T> BuildResult<T> run(InternalBuildAction<T> action,
+                           BuildParameters operationParameters) throws
+            BuildExceptionVersion1,
+            InternalUnsupportedBuildArgumentException,
+            InternalBuildActionFailureException,
+            IllegalStateException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildActionFailureException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildActionFailureException.java
new file mode 100644
index 0000000..08e6269
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildActionFailureException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * A wrapper thrown when a build action fails with an exception. The failure will be attached as the cause of this exception.
+ *
+ * DO NOT CHANGE THIS CLASS. It is part of the cross-version protocol.
+ *
+ * @since 1.8-rc-1
+ */
+public class InternalBuildActionFailureException extends RuntimeException {
+    public InternalBuildActionFailureException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildController.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildController.java
new file mode 100644
index 0000000..1134c70
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.8-rc-1.</p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.8-rc-1.</p>
+ *
+ * @since 1.8-rc-1
+ */
+public interface InternalBuildController {
+    /**
+     * Returns the version-specific build model.
+     *
+     * <p>Consumer compatibility: This method is not used by any consumer versions.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.8-rc-1.</p>
+     *
+     * @throws BuildExceptionVersion1 On build failure.
+     * @since 1.8-rc-1
+     */
+    BuildResult<?> getBuildModel() throws BuildExceptionVersion1;
+
+    /**
+     * Returns the requested model for a target object.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.8-rc-1.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.8-rc-1.</p>
+     *
+     * @param target The target object. May be null, in which case a default target is used.
+     * @param modelIdentifier The identifier of the model to build.
+     * @throws BuildExceptionVersion1 On build failure.
+     * @throws InternalUnsupportedModelException When the requested model is not supported.
+     * @since 1.8-rc-1
+     */
+    BuildResult<?> getModel(Object target, ModelIdentifier modelIdentifier) throws BuildExceptionVersion1,
+            InternalUnsupportedModelException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java
index 332d79b..892b302 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java
@@ -20,6 +20,7 @@ package org.gradle.tooling.internal.protocol;
  * Marker interface for the internal protocol purposes.
  * Corresponding client facing model is {@link org.gradle.tooling.model.build.BuildEnvironment}
  * <p>
- * by Szczepan Faber, created at: 12/17/11
+ *
+ * @since 1.0-milestone-8
  */
 public interface InternalBuildEnvironment extends InternalProtocolInterface {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalConnection.java
index b716e72..a538431 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
@@ -17,9 +17,15 @@
 package org.gradle.tooling.internal.protocol;
 
 /**
- * by Szczepan Faber, created at: 1/1/12
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
  *
- * @deprecated Use {@link BuildActionRunner} instead.
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.0-milestone-8 to 1.1. It is also used by later consumers when the
+ * provider does not implement {@link BuildActionRunner} or {@link ModelBuilder}.</p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.0-milestone-8.</p>
+ *
+ * @since 1.0-milestone-8
+ * @deprecated 1.2-rc-1. Use {@link BuildActionRunner} instead.
+ * @see ConnectionVersion4
  */
 @Deprecated
 public interface InternalConnection extends ConnectionVersion4, InternalProtocolInterface {
@@ -29,9 +35,14 @@ public interface InternalConnection extends ConnectionVersion4, InternalProtocol
      * <p>
      * The other method on the interface, e.g. {@link #getModel(Class, BuildOperationParametersVersion1)} should be considered deprecated
      *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.0-milestone-8 to 1.1. It is also used by later consumers when the
+     * provider does not implement {@link BuildActionRunner} or {@link ModelBuilder}.</p>
+     * <p>Provider compatibility: This interface is implemented by all provider versions from 1.0-milestone-8.</p>
+     *
      * @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.
+     * @since 1.0-milestone-8
+     * @deprecated 1.2-rc-1 Use {@link ModelBuilder#getModel(ModelIdentifier, BuildParameters)} 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/InternalGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalGradleProject.java
index 3c03a83..e903a8d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalGradleProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalGradleProject.java
@@ -18,5 +18,7 @@ package org.gradle.tooling.internal.protocol;
 
 /**
  * See {@link InternalProtocolInterface}
+ *
+ * @since 1.0-milestone-5
  */
 public interface InternalGradleProject extends ProjectVersion3, InternalProtocolInterface {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java
index 4186fac..6457f6c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java
@@ -18,6 +18,8 @@ package org.gradle.tooling.internal.protocol;
 
 /**
  * See {@link InternalProtocolInterface}
+ *
+ * @since 1.0-milestone-5
  */
 public interface InternalIdeaProject extends ProjectVersion3, InternalProtocolInterface {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalLaunchable.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalLaunchable.java
new file mode 100644
index 0000000..270690f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalLaunchable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * A marker interface for launchables.
+ *
+ * The real implementation exists elsewhere and is only interesting for provider.
+ * Consumer will see public part of the contract from *.model.* packages and will send it back
+ * to provider as it is when a build is launched.
+ *
+ * @since 1.12
+ */
+public interface InternalLaunchable extends InternalProtocolInterface {
+}
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
index dd2172f..cc2f6dc 100644
--- 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
@@ -16,5 +16,10 @@
 
 package org.gradle.tooling.internal.protocol;
 
-public interface InternalProjectOutcomes extends ProjectVersion3, InternalProtocolInterface {
+/**
+ * <p>Consumer compatibility: This interface used to extend {@link ProjectVersion3} until 1.6-rc-1.</p>
+ *
+ * @since 1.2-rc-1
+ */
+public interface InternalProjectOutcomes extends InternalProtocolInterface {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java
index f97648d..521e0aa 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java
@@ -17,16 +17,10 @@
 package org.gradle.tooling.internal.protocol;
 
 /**
- * I needed this interface so that it is possible to develop new features incrementally.
- * In general I'd like to avoid growing VersionX interfaces
- * because we have an excellent test suite that tells the story of what has changed and when
- * <p>
- * A marker interface to document the problem consistently.
- * Might live only until we gradually remove old VersionX types.
- * <p>
- * If you make changes to inheritor of this interfaces make sure you run all compatibility tests.
+ * <p>A marker interface for some type that participates in the cross-version tooling protocol.
  *
- * @author: Szczepan Faber, created at: 8/5/11
+ * <p>Subtypes generally define only a few methods statically. Instances are usually inspected dynamically to
+ * determine which methods are actually supported.
  */
 public interface InternalProtocolInterface {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalTestModel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalTestModel.java
deleted file mode 100644
index 53fecb3..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalTestModel.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.tooling.internal.protocol;
-
-/**
- * For testing purposes only
- * <p>
- * by Szczepan Faber, created at: 12/21/11
- */
-public interface InternalTestModel extends ProjectVersion3 {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalUnsupportedModelException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalUnsupportedModelException.java
new file mode 100644
index 0000000..ebd2c4f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalUnsupportedModelException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * A wrapper thrown when some requested model is not available. Any details will be made available in the cause of
+ * the exception.
+ *
+ * <p>Consumer compatibility: Versions 1.6-rc-1 and later use this type.</p>
+ * <p>Provider compatibility: Versions prior to 1.8-rc-1 did not attach any cause to this exception.</p>
+ *
+ * DO NOT CHANGE THIS CLASS. It is part of the cross-version protocol.
+ *
+ * @since 1.6-rc-1
+ */
+public class InternalUnsupportedModelException extends RuntimeException {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java
index 5eec6bd..11a2b6c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java
@@ -20,12 +20,17 @@ import java.io.OutputStream;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 1.0-milestone-3
+ * @deprecated 1.2-rc-1. Use {@link BuildParameters} instead.
  */
+ at Deprecated
 public interface LongRunningOperationParametersVersion1 {
     /**
      * Returns the output stream to write stdout logging to.
      *
      * @return The output stream. May be null.
+     * @since 1.0-milestone-3
      */
     OutputStream getStandardOutput();
 
@@ -33,6 +38,7 @@ public interface LongRunningOperationParametersVersion1 {
      * Returns the output stream to write stderr logging to.
      *
      * @return The output stream. May be null.
+     * @since 1.0-milestone-3
      */
     OutputStream getStandardError();
 
@@ -40,6 +46,7 @@ public interface LongRunningOperationParametersVersion1 {
      * Returns the listener to receive progress events.
      *
      * @return The listener. Must not be null.
+     * @since 1.0-milestone-3
      */
     ProgressListenerVersion1 getProgressListener();
 
@@ -47,6 +54,7 @@ public interface LongRunningOperationParametersVersion1 {
      * Returns the input stream to that can be consumed.
      *
      * @return The input stream. May be null.
+     * @since 1.0-milestone-8
      */
     InputStream getStandardInput();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ModelBuilder.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ModelBuilder.java
new file mode 100644
index 0000000..b31ff5a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ModelBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.protocol;
+
+import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
+
+/**
+ * Mixed into a provider connection, to allow tooling models to be requested by the client.
+ *
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.6-rc-1.</p>
+ * <p>Provider compatibility: This interface is implemented by all provider versions from 1.6-rc-1.</p>
+ *
+ * @since 1.6-rc-1
+ * @see ConnectionVersion4
+ */
+public interface ModelBuilder extends InternalProtocolInterface {
+    /**
+     * Performs some action against a build and returns the requested model.
+     *
+     * <p>Consumer compatibility: This method is used by all consumer versions from 1.6-rc-1.</p>
+     * <p>Provider compatibility: This method is implemented by all provider versions from 1.6-rc-1.</p>
+     *
+     * @param modelIdentifier The identifier of the model to build.
+     * @throws BuildExceptionVersion1 On build failure.
+     * @throws InternalUnsupportedModelException When the requested model is not supported.
+     * @throws InternalUnsupportedBuildArgumentException When the specified command-line options are not supported.
+     * @throws IllegalStateException When this connection has been stopped.
+     * @since 1.6-rc-1
+     */
+    BuildResult<?> getModel(ModelIdentifier modelIdentifier, BuildParameters operationParameters) throws
+            BuildExceptionVersion1,
+            InternalUnsupportedModelException,
+            InternalUnsupportedBuildArgumentException,
+            IllegalStateException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ModelIdentifier.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ModelIdentifier.java
new file mode 100644
index 0000000..bc1464d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ModelIdentifier.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Identity information for a model.
+ *
+ * <p>DO NOT CHANGE THIS INTERFACE - it is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.6-rc-1.</p>
+ * <p>Provider compatibility: This interface is uses by all provider versions from 1.6-rc-1.</p>
+ *
+ * @since 1.6-rc-1
+ */
+public interface ModelIdentifier extends InternalProtocolInterface {
+    /**
+     * The name of the null model.
+     */
+    final String NULL_MODEL = Void.class.getName();
+
+    /**
+     * The name of the model.
+     *
+     * Note that the model name is not necessarily a class name. It simply uniquely identifies the model.
+     * Use {@link #NULL_MODEL} to indicate that no model is desired.
+     */
+    String getName();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ProjectVersion3.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ProjectVersion3.java
index 8a9378e..b28573e 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ProjectVersion3.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ProjectVersion3.java
@@ -19,13 +19,30 @@ import java.io.File;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * <p>Consumer compatibility: This interface is used by all consumer versions from 1.0-milestone-3 to 1.0-milestone-7 to represent the model objects. Later consumer versions don't require that the
+ * model objects implement this interface. </p>
+ *
+ * @since 1.0-milestone-3
  */
 public interface ProjectVersion3 {
+    /**
+     * @since 1.0-milestone-3
+     */
     String getPath();
 
+    /**
+     * @since 1.0-milestone-3
+     */
     String getName();
 
+    /**
+     * @since 1.0-milestone-3
+     */
     String getDescription();
 
+    /**
+     * @since 1.0-milestone-3
+     */
     File getProjectDirectory();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java
index 1e6eebe..00dfa05 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java
@@ -18,8 +18,6 @@ package org.gradle.tooling.internal.protocol.eclipse;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
- *
- * @author: Szczepan Faber, created at: 6/11/11
  */
 public interface EclipseLinkedResourceVersion1 {
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseProjectVersion3.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseProjectVersion3.java
index 1dcfe30..96aad9a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseProjectVersion3.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseProjectVersion3.java
@@ -20,6 +20,8 @@ import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 1.0-milestone-3
  */
 public interface EclipseProjectVersion3 extends HierarchicalEclipseProjectVersion1, BuildableProjectVersion1 {
     EclipseProjectVersion3 getParent();
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/HierarchicalEclipseProjectVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/HierarchicalEclipseProjectVersion1.java
index 2b5d798..c2f817b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/HierarchicalEclipseProjectVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/HierarchicalEclipseProjectVersion1.java
@@ -19,6 +19,8 @@ import org.gradle.tooling.internal.protocol.HierarchicalProjectVersion1;
 
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 1.0-milestone-3
  */
 public interface HierarchicalEclipseProjectVersion1 extends HierarchicalProjectVersion1 {
     HierarchicalEclipseProjectVersion1 getParent();
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/reflect/CompatibleIntrospector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/reflect/CompatibleIntrospector.java
deleted file mode 100644
index bb2a863..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/reflect/CompatibleIntrospector.java
+++ /dev/null
@@ -1,71 +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.reflect;
-
-import java.lang.reflect.Method;
-
-/**
- * Uses reflection to find out / call methods.
- *
- * by Szczepan Faber, created at: 12/9/11
- */
-public class CompatibleIntrospector {
-
-    private final Object target;
-
-    public CompatibleIntrospector(Object target) {
-        this.target = target;
-    }
-
-    private Method getMethod(String methodName) throws NoSuchMethodException {
-        Method[] methods = target.getClass().getDeclaredMethods();
-        for (Method m : methods) {
-            if (m.getName().equals(methodName)) {
-                return m;
-            }
-        }
-        throw new NoSuchMethodException("No such method: '" + methodName + "' on type: '" + target.getClass().getSimpleName() + "'.");
-    }
-
-    public <T> T getSafely(T defaultValue, String methodName) {
-        try {
-            Method method = getMethod(methodName);
-            method.setAccessible(true);
-            return (T) method.invoke(target);
-        } catch (NoSuchMethodException e) {
-            return defaultValue;
-        } catch (Exception e) {
-            throw new RuntimeException("Unable to get value reflectively", e);
-        }
-    }
-
-    public void callSafely(String methodName, Object ... params) {
-        Method method;
-        try {
-            method = getMethod(methodName);
-        } catch (NoSuchMethodException e) {
-            return; // ignore
-        }
-
-        method.setAccessible(true);
-        try {
-            method.invoke(target, params);
-        } catch (Exception e) {
-            throw new RuntimeException("Unable to call method reflectively", e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
index e8bf402..c4274b9 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
@@ -27,6 +27,7 @@ public interface BuildableElement extends Element {
      * Returns the tasks of this project.
      *
      * @return The tasks of this project.
+     * @since 1.0-milestone-5
      */
     DomainObjectSet<? extends Task> getTasks();
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
index 9529eaf..4623b84 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
@@ -29,6 +29,7 @@ public interface Element extends Model {
      * Returns the name of the element. Note that the name is not a unique identifier.
      *
      * @return The name of the element.
+     * @since 1.0-milestone-5
      */
     String getName();
 
@@ -36,6 +37,7 @@ public interface Element extends Model {
      * Returns the description of the element, or {@code null} if it has no description.
      *
      * @return The description of the element, or {@code null} if it has no description.
+     * @since 1.0-milestone-5
      */
     @Nullable
     String getDescription();
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 49afadb..b2cc4e2 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
@@ -53,7 +53,7 @@ public interface ExternalDependency extends Dependency {
      *
      * @return The Gradle module information for this dependency, or {@code null} if the dependency does not
      * originate from a remote repository.
-     * @since 1.1-rc-1
+     * @since 1.1
      */
     @Nullable
     @Incubating
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 4a79ad0..a12f417 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
@@ -21,7 +21,7 @@ import org.gradle.api.Incubating;
 /**
  * Informs about a module version, i.e. group, name, version.
  *
- * @since 1.1-rc-1
+ * @since 1.1
  */
 @Incubating
 public interface GradleModuleVersion {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
index 98b9968..01602ba 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
@@ -16,19 +16,19 @@
 
 package org.gradle.tooling.model;
 
+import org.gradle.api.Incubating;
 import org.gradle.api.Nullable;
+import org.gradle.tooling.model.gradle.GradleScript;
 
 /**
- * Gradle project.
+ * Represents a Gradle project.
  *
  * @since 1.0-milestone-5
  */
 public interface GradleProject extends HierarchicalElement, BuildableElement {
 
     /**
-     * Returns the tasks of this project.
-     *
-     * @return The tasks.
+     * {@inheritDoc}
      */
     DomainObjectSet<? extends GradleTask> getTasks();
 
@@ -43,17 +43,26 @@ public interface GradleProject extends HierarchicalElement, BuildableElement {
     DomainObjectSet<? extends GradleProject> getChildren();
 
     /**
-     * Returns Gradle path.
+     * Returns the path of this project. This is a unique identifier for this project.
      *
      * @return The path.
      */
     String getPath();
 
     /**
-     * Searches all descendants (children, grand children, etc.), including self, by given path.
+     * Searches all descendants (children, grand-children, etc.), including self, by given path.
      *
      * @return Gradle project with matching path or {@code null} if not found.
      */
     @Nullable
     GradleProject findByPath(String path);
+
+    /**
+     * Returns the build script for this project.
+     *
+     * @return The build script.
+     * @since 1.8
+     */
+    @Incubating
+    GradleScript getBuildScript();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
index 241483c..508449a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
@@ -27,6 +27,7 @@ public interface GradleTask extends Task {
      * Returns the Gradle project this task is defined in.
      *
      * @return The element.
+     * @since 1.0-milestone-5
      */
     GradleProject getProject();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
index d858c88..66cb93a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
@@ -29,6 +29,7 @@ public interface HierarchicalElement extends Element {
      * Returns the parent of this element, or {@code null} if there is no parent.
      *
      * @return The parent of this element, or {@code null} if there is no parent.
+     * @since 1.0-milestone-5
      */
     @Nullable
     HierarchicalElement getParent();
@@ -37,6 +38,7 @@ public interface HierarchicalElement extends Element {
      * Returns the child elements, or the empty set if there are no child elements.
      *
      * @return The child elements, or the empty set if there are no child elements.
+     * @since 1.0-milestone-5
      */
     DomainObjectSet<? extends HierarchicalElement> getChildren();
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Launchable.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Launchable.java
new file mode 100644
index 0000000..7e9a0d3
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Launchable.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.model;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+/**
+ * Represents an object that can be used to launch a Gradle build, such as a task.
+ *
+ * <p>To launch a build, you pass one or more {@link org.gradle.tooling.model.Launchable} instances
+ * to either {@link org.gradle.tooling.BuildLauncher#forTasks(Iterable)} or {@link org.gradle.tooling.BuildLauncher#forLaunchables(Iterable)}.</p>
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface Launchable {
+    /**
+     * Returns a human-consumable display name for this launchable.
+     *
+     * @return Display name of this launchable.
+     * @since 1.12
+     */
+    String getDisplayName();
+
+    /**
+     * Returns the description of this launchable, or {@code null} if it has no description.
+     *
+     * @return The description of this launchable, or {@code null} if it has no description.
+     * @since 1.12
+     */
+    @Nullable
+    String getDescription();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
index d01bf9d..f423257 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
@@ -16,6 +16,7 @@
 
 package org.gradle.tooling.model;
 
+// TODO:ADAM - Deprecate
 /**
  * A model that is buildable by the Tooling API. Models contain various information regarding the build.
  * Models are typically tailored to a specific domain, for example build environment or IDE.
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 02ddddb..9efac40 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
@@ -19,12 +19,17 @@ import org.gradle.api.Nullable;
 
 /**
  * Represents a task which is executable by Gradle.
+ *
+ * <p>Note: {@code Task} extends {@code Launchable} since 1.12.</p>
+ *
+ * @since 1.0-milestone-3
  */
-public interface Task {
+public interface Task extends Launchable {
     /**
      * Returns the path of this task. This is a fully qualified unique name for this task.
      *
      * @return The path of this task.
+     * @since 1.0-milestone-3
      */
     String getPath();
 
@@ -32,6 +37,7 @@ public interface Task {
      * Returns the name of this task. Note that the name is not necessarily a unique identifier for the task.
      *
      * @return The name of this task.
+     * @since 1.0-milestone-3
      */
     String getName();
 
@@ -39,6 +45,7 @@ public interface Task {
      * Returns the description of this task, or {@code null} if it has no description.
      *
      * @return The description of this task, or {@code null} if it has no description.
+     * @since 1.0-milestone-3
      */
     @Nullable
     String getDescription();
@@ -46,7 +53,12 @@ public interface Task {
     /**
      * Returns the element which this task belongs to.
      *
-     * @return The element which this task belongs to.
+     * @deprecated Do not use this method. It is assumed that the caller already has a reference to owning project.
+     * @return The element which this task belongs to. Since 1.12 some implementations can return {@code null}.
+     * @throws org.gradle.tooling.model.UnsupportedMethodException thrown from implementation like
+     * {@link org.gradle.tooling.model.gradle.BuildInvocations}.
+     * @since 1.0-milestone-3
      */
+    @Deprecated
     Element getProject();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/TaskSelector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/TaskSelector.java
new file mode 100644
index 0000000..11ac9f1
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/TaskSelector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.model;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Represents a {@link Launchable} that uses task name to select tasks executed from a given project and its sub-projects. This is
+ * roughly equivalent to running `gradle <task-name>` from the command-line.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface TaskSelector extends Launchable {
+    /**
+     * Returns the name that will be used to select tasks.
+     *
+     * @return The name used by this selector.
+     */
+    String getName();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
index 7319247..784944c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
@@ -42,13 +42,16 @@ import org.gradle.tooling.model.UnsupportedMethodException;
 public interface BuildEnvironment extends Model {
 
     /**
-     * Informs about the Gradle environment, for example the Gradle version.
+     * Returns information about the Gradle environment, for example the Gradle version.
+     *
+     * @since 1.0-milestone-8
      */
     GradleEnvironment getGradle();
 
     /**
-     * Informs about the Java environment, for example the Java home or the JVM args used.
+     * Returns information about the Java environment, for example the Java home or the JVM args used.
      *
+     * @since 1.0-milestone-8
      * @throws org.gradle.tooling.model.UnsupportedMethodException
      * when the Gradle version the tooling API is connected to does not support the Java environment information.
      */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
index f4fe35c..e96d5ad 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
@@ -27,6 +27,8 @@ public interface GradleEnvironment {
 
     /**
      * Informs about the Gradle version.
+     *
+     * @since 1.0-milestone-8
      */
     String getGradleVersion();
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
index 6571b69..f76c0de 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
@@ -29,6 +29,8 @@ public interface JavaEnvironment {
 
     /**
      * The Java home used for Gradle operations (for example running tasks or acquiring model information).
+     *
+     * @since 1.0-milestone-8
      */
     File getJavaHome();
 
@@ -37,6 +39,8 @@ public interface JavaEnvironment {
      * (for example running tasks or acquiring model information).
      * The returned arguments do not include system properties passed as -Dfoo=bar.
      * They may include implicitly immutable system properties like "file.encoding".
+     *
+     * @since 1.0-milestone-8
      */
     List<String> getJvmArguments();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
index 1b23dc3..1f4f3df 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Models the build environment information like Gradle or Java versions.
+ * Tooling models for the build environment, which includes information such as Gradle or Java versions.
  */
 package org.gradle.tooling.model.build;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java
index f007053..ebbcdcc 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java
@@ -26,7 +26,7 @@ public interface EclipseLinkedResource {
     /**
      * The project-relative path of the linked resource as it appears in the workspace.
      * <p>
-     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     * See the official Eclipse documentation for most up-to-date information on properties of a linked resource
      * <p>
      * For example, a linked resource to a file system folder /some/path/to/someFolder can have a name 'someFolder'
      *
@@ -43,9 +43,9 @@ public interface EclipseLinkedResource {
      * "1" for file or folder when 'locationUri' first segment is a workspace path variable (or path variable navigation element),
      * "2" for an eclipse virtual folder.
      * <p>
-     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     * See the official Eclipse documentation for most up-to-date information on properties of a linked resource
      *
-     * @return eclipse link type
+     * @return Eclipse link type
      */
     String getType();
 
@@ -53,7 +53,7 @@ public interface EclipseLinkedResource {
      * The local file system absolute path of the target of the linked resource. For example: '/path/to/somewhere'.
      * Mutually exclusive with 'locationUri'
      * <p>
-     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     * See the official Eclipse documentation for most up-to-date information on properties of a linked resource
      *
      * @return location
      */
@@ -67,7 +67,7 @@ public interface EclipseLinkedResource {
      * <p>
      * Used for virtual folders. In that case the value is: 'virtual:/virtual'
      * <p>
-     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     * See the official Eclipse documentation for most up-to-date information on properties of a linked resource
      *
      * @return location uri
      */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java
index 3d662c9..9160c1c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java
@@ -20,12 +20,12 @@ import org.gradle.tooling.model.ExternalDependency;
 import org.gradle.tooling.model.GradleProject;
 import org.gradle.tooling.model.HasGradleProject;
 
-import java.io.File;
-
 /**
  * The complete model of an Eclipse project.
  *
  * <p>Note that the names of Eclipse projects are unique, and can be used as an identifier for the project.
+ *
+ * @since 1.0-milestone-3
  */
 public interface EclipseProject extends HierarchicalEclipseProject, HasGradleProject {
     /**
@@ -40,7 +40,7 @@ public interface EclipseProject extends HierarchicalEclipseProject, HasGradlePro
 
     /**
      * The gradle project that is associated with this project.
-     * Typically, a single eclipse project corresponds to a single gradle project.
+     * Typically, a single Eclipse project corresponds to a single gradle project.
      * <p>
      * See {@link HasGradleProject}
      *
@@ -53,14 +53,7 @@ public interface EclipseProject extends HierarchicalEclipseProject, HasGradlePro
      * Returns the external dependencies which make up the classpath of this project.
      *
      * @return The dependencies. Returns an empty set if the project has no external dependencies.
+     * @since 1.0-milestone-3
      */
     DomainObjectSet<? extends ExternalDependency> getClasspath();
-
-    /**
-     * Returns the project directory for this project.
-     *
-     * @return The project directory.
-     */
-    File getProjectDirectory();
-
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java
index 8c3d7b2..4de0531 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java
@@ -18,7 +18,10 @@ package org.gradle.tooling.model.eclipse;
 import org.gradle.tooling.model.Task;
 
 /**
- * Deprecated because gradle tasks are not associated with eclipse projects.
+ * Deprecated because Gradle tasks are not associated with Eclipse projects.
+ *
+ * @deprecated Use {@link EclipseProject#getGradleProject()} to determine the associated Gradle project for an Eclipse project,
+ * then use {@link org.gradle.tooling.model.GradleProject#getTasks()} to determine the tasks for the Gradle project.
  */
 @Deprecated
 public interface EclipseTask extends Task {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java
index 1c5e460..3010fd0 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java
@@ -23,6 +23,8 @@ import java.io.File;
 
 /**
  * Represents the basic information about an Eclipse project.
+ *
+ * @since 1.0-milestone-3
  */
 public interface HierarchicalEclipseProject extends HierarchicalElement {
     /**
@@ -39,6 +41,7 @@ public interface HierarchicalEclipseProject extends HierarchicalElement {
      * Returns the project dependencies for this project.
      *
      * @return The project dependencies. Returns an empty set if the project has no project dependencies.
+     * @since 1.0-milestone-3
      */
     DomainObjectSet<? extends EclipseProjectDependency> getProjectDependencies();
 
@@ -46,6 +49,7 @@ public interface HierarchicalEclipseProject extends HierarchicalElement {
      * Returns the source directories for this project.
      *
      * @return The source directories. Returns an empty set if the project has no source directories.
+     * @since 1.0-milestone-3
      */
     DomainObjectSet<? extends EclipseSourceDirectory> getSourceDirectories();
 
@@ -64,6 +68,7 @@ public interface HierarchicalEclipseProject extends HierarchicalElement {
      * Returns the project directory for this project.
      *
      * @return The project directory.
+     * @since 1.0-milestone-9
      */
     File getProjectDirectory();
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/package-info.java
index a9281c3..2aaec75 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/package-info.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * An Eclipse-centric model of a Gradle build, provided by the Gradle tooling API.
+ * Eclipse-centric tooling models.
  */
 package org.gradle.tooling.model.eclipse;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/BasicGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/BasicGradleProject.java
new file mode 100644
index 0000000..85a79cd
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/BasicGradleProject.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.gradle;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.Model;
+
+import java.io.File;
+
+/**
+ * Provides some basic details about a Gradle project.
+ *
+ * @since 1.8
+ */
+ at Incubating
+public interface BasicGradleProject extends Model {
+    /**
+     * Returns the name of this project. Note that the name is not a unique identifier for the project.
+     *
+     * @return The name of this project.
+     */
+    String getName();
+
+    /**
+     * Returns the path of this project. The path can be used as a unique identifier for the project within a given build.
+     *
+     * @return The path of this project.
+     */
+    String getPath();
+
+    /**
+     * Returns the project directory for this project.
+     *
+     * @return The project directory.
+     */
+    File getProjectDirectory();
+
+    /**
+     * Returns the parent of this project, or {@code null} if this is the root project.
+     *
+     * @return The parent of this project, or {@code null} if this is the root project.
+     */
+    @Nullable
+    BasicGradleProject getParent();
+
+    /**
+     * Returns the child projects of this project, or the empty set if there are no child projects.
+     *
+     * @return The child projects of this project, or the empty set if there are no child projects.
+     * @since 1.0-milestone-5
+     */
+    DomainObjectSet<? extends BasicGradleProject> getChildren();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/BuildInvocations.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/BuildInvocations.java
new file mode 100644
index 0000000..8ab4e72
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/BuildInvocations.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.gradle;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.Task;
+import org.gradle.tooling.model.TaskSelector;
+
+/**
+ * A model providing access to {@link org.gradle.tooling.model.Launchable} instances that can be used
+ * to initiate Gradle build.
+ *
+ * <p>To launch a build, you pass one or more {@link org.gradle.tooling.model.Launchable} instances
+ * to either {@link org.gradle.tooling.BuildLauncher#forTasks(Iterable)} or {@link org.gradle.tooling.BuildLauncher#forLaunchables(Iterable)}.</p>
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface BuildInvocations {
+    /*
+     * Returns tasks selectors that can be used to execute a build.
+     *
+     * @return The task selectors.
+     * @since 1.12
+     */
+    @Incubating
+    DomainObjectSet<? extends TaskSelector> getTaskSelectors();
+
+    /*
+     * Returns the tasks that can be used to execute a build.
+     *
+     * @return The tasks.
+     * @since 1.12
+     */
+    @Incubating
+    DomainObjectSet<? extends Task> getTasks();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradleBuild.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradleBuild.java
new file mode 100644
index 0000000..136fe8a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradleBuild.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.gradle;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.model.DomainObjectSet;
+
+/**
+ * Provides information about the structure of a Gradle build.
+ *
+ * @since 1.8
+ */
+ at Incubating
+public interface GradleBuild {
+    /**
+     * Returns the root project for this build.
+     *
+     * @return The root project
+     */
+    BasicGradleProject getRootProject();
+
+    /**
+     * Returns the set of all projects for this build. Will include the root project and any ancestors.
+     *
+     * @return The set of all projects.
+     */
+    DomainObjectSet<? extends BasicGradleProject> getProjects();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradlePublication.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradlePublication.java
new file mode 100644
index 0000000..f136f34
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradlePublication.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.gradle;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.model.GradleModuleVersion;
+
+/**
+ * Represents some publication produced by a Gradle project, typically to a Maven or Ivy repository.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface GradlePublication {
+    /**
+     * Returns the unique identifier of the publication.
+     *
+     * @return the unique identifier of the publication
+     */
+    GradleModuleVersion getId();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradleScript.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradleScript.java
new file mode 100644
index 0000000..6632a12
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/GradleScript.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.gradle;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+/**
+ * Represents a Gradle script. A Gradle script may be a build script, settings script or initialization script.
+ *
+ * @since 1.8
+ */
+ at Incubating
+public interface GradleScript {
+    /**
+     * Returns the source file for this script, or {@code null} if this script has no associated source file.
+     * If this method returns a non-null value, the given source file will exist.
+     *
+     * @return The source file. Returns null if the script has no associated source file.
+     * @since 1.8
+     */
+    @Nullable
+    File getSourceFile();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/ProjectPublications.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/ProjectPublications.java
new file mode 100644
index 0000000..60c807e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/ProjectPublications.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.gradle;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.model.DomainObjectSet;
+
+/**
+ * A model providing information about the publications of a Gradle project.
+ *
+ * @since 1.12
+ */
+ at Incubating
+public interface ProjectPublications {
+
+    /**
+     * Returns the publications for this project.
+     */
+    DomainObjectSet<? extends GradlePublication> getPublications();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/package-info.java
new file mode 100644
index 0000000..b9a6afe
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/gradle/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 tooling models for Gradle builds and projects.
+ */
+package org.gradle.tooling.model.gradle;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
index 578ac5b..8007ac1 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
@@ -21,6 +21,8 @@ package org.gradle.tooling.model.idea;
  * Only project dependencies and local file dependencies are included on the modules' classpath.
  * <p>
  * Useful for 'previewing' the output model of IdeaProject because it is supposed to be fast (e.g. does not download dependencies from the web).
+ *
+ * @since 1.0-milestone-5
  */
 public interface BasicIdeaProject extends IdeaProject {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
index 8e8df96..6bf6770 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
@@ -33,7 +33,7 @@ public interface IdeaDependency extends Dependency {
     IdeaDependencyScope getScope();
 
     /**
-     * Allows to check if current dependency is transitive, i.e. is visible to the module which depends on module that has current dependency.
+     * Allows to check if current dependency is transitive, i.e.<!-- --> is visible to the module which depends on module that has current dependency.
      *
      * @return <code>true</code> if current dependency is transitive; <code>false</code> otherwise
      */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
index b354858..592fe26 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
@@ -32,6 +32,7 @@ public interface IdeaModule extends HierarchicalElement, HasGradleProject {
      * All content roots. Most idea modules have a single content root.
      *
      * @return content roots
+     * @since 1.0-milestone-5
      */
     DomainObjectSet<? extends IdeaContentRoot> getContentRoots();
 
@@ -42,6 +43,7 @@ public interface IdeaModule extends HierarchicalElement, HasGradleProject {
      * See {@link HasGradleProject}
      *
      * @return associated gradle project
+     * @since 1.0-milestone-5
      */
     GradleProject getGradleProject();
 
@@ -50,6 +52,7 @@ public interface IdeaModule extends HierarchicalElement, HasGradleProject {
      * Alias to {@link #getProject()}
      *
      * @return idea project
+     * @since 1.0-milestone-5
      */
     IdeaProject getParent();
 
@@ -58,11 +61,14 @@ public interface IdeaModule extends HierarchicalElement, HasGradleProject {
      * Alias to {@link #getParent()}
      *
      * @return idea project
+     * @since 1.0-milestone-5
      */
     IdeaProject getProject();
 
     /**
-     * information about idea compiler output (output dirs, inheritance of output dir, etc.)
+     * Returns information about idea compiler output (output dirs, inheritance of output dir, etc.)
+     *
+     * @since 1.0-milestone-5
      */
     IdeaCompilerOutput getCompilerOutput();
 
@@ -70,6 +76,7 @@ public interface IdeaModule extends HierarchicalElement, HasGradleProject {
      * dependencies of this module (i.e. module dependencies, library dependencies, etc.)
      *
      * @return dependencies
+     * @since 1.0-milestone-5
      */
     DomainObjectSet<? extends IdeaDependency> getDependencies();
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
index 5f43e3a..26bfb0f 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
@@ -17,7 +17,6 @@
 package org.gradle.tooling.model.idea;
 
 import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.Element;
 import org.gradle.tooling.model.HierarchicalElement;
 
 /**
@@ -25,12 +24,13 @@ import org.gradle.tooling.model.HierarchicalElement;
  *
  * @since 1.0-milestone-5
  */
-public interface IdeaProject extends HierarchicalElement, Element {
+public interface IdeaProject extends HierarchicalElement {
 
     /**
      * Returns the name of the JDK.
      *
      * @return The name of the JDK.
+     * @since 1.0-milestone-5
      */
     String getJdkName();
 
@@ -38,6 +38,7 @@ public interface IdeaProject extends HierarchicalElement, Element {
      * Returns the language level to use within the current project.
      *
      * @return The language level to use within the current project.
+     * @since 1.0-milestone-5
      */
     IdeaLanguageLevel getLanguageLevel();
 
@@ -46,6 +47,7 @@ public interface IdeaProject extends HierarchicalElement, Element {
      * Alias for {@link #getModules()}.
      *
      * @return The modules of this IDEA project.
+     * @since 1.0-milestone-5
      */
     DomainObjectSet<? extends IdeaModule> getChildren();
 
@@ -54,6 +56,7 @@ public interface IdeaProject extends HierarchicalElement, Element {
      * Alias for {@link #getChildren()}.
      *
      * @return The modules of this IDEA project.
+     * @since 1.0-milestone-5
      */
     DomainObjectSet<? extends IdeaModule> getModules();
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
index 42ca84b..1911501 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * IntelliJ IDEA related API of the tooling API.
+ * IntelliJ IDEA centric tooling models.
  */
 package org.gradle.tooling.model.idea;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/Exceptions.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/Exceptions.java
index 85409dd..019aa4b 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
@@ -16,17 +16,18 @@
 
 package org.gradle.tooling.model.internal;
 
+import org.gradle.tooling.UnknownModelException;
+import org.gradle.tooling.UnsupportedVersionException;
 import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.internal.protocol.InternalUnsupportedModelException;
 import org.gradle.tooling.model.UnsupportedMethodException;
 
-/**
- * by Szczepan Faber, created at: 12/22/11
- */
 public class Exceptions {
 
     public final static String INCOMPATIBLE_VERSION_HINT =
             "Most likely the model of that type is not supported in the target Gradle version."
-            + "\nTo resolve the problem you can change/upgrade the Gradle version the tooling api connects to.";
+                    + "\nTo resolve the problem you can change/upgrade the Gradle version the tooling api connects to.";
 
     public static UnsupportedMethodException unsupportedMethod(String method) {
         return new UnsupportedMethodException(formatUnsupportedModelMethod(method));
@@ -40,14 +41,32 @@ public class Exceptions {
                 , method);
     }
 
-    public static UnsupportedOperationConfigurationException unsupportedOperationConfiguration(String operation) {
-        //we only need that cause for backwards-compatibility.
-        UnsupportedMethodException cause = new UnsupportedMethodException(operation);
+    public static UnsupportedOperationConfigurationException unsupportedOperationConfiguration(String operation, String targetVersion) {
         return new UnsupportedOperationConfigurationException(String.format("Unsupported configuration: %s."
                 + "\nYou configured the LongRunningOperation (ModelBuilder or BuildLauncher) with an unsupported option."
-                + "\nThe version of Gradle you connect to does not support this configuration option."
-                + "\nTo resolve the problem you can change/upgrade the target version of Gradle you connect to."
-                + "\nAlternatively, you may stop using this configuration option."
-                , operation), cause);
+                + "\nThe version of Gradle are using (%s) does not support this configuration option."
+                + "\nTo resolve the problem you can change/upgrade the target version of Gradle."
+                , operation, targetVersion));
+    }
+
+    public static UnknownModelException unsupportedModel(Class<?> modelType, String targetVersion) {
+        ModelMapping modelMapping = new ModelMapping();
+        String versionAdded = modelMapping.getVersionAdded(modelType);
+        if (versionAdded != null) {
+            return new UnknownModelException(String.format("The version of Gradle you are using (%s) does not support building a model of type '%s'. Support for building '%s' models was added in Gradle %s and is available in all later versions.",
+                    targetVersion, modelType.getSimpleName(), modelType.getSimpleName(), versionAdded));
+        } else {
+            return new UnknownModelException(String.format("The version of Gradle you are using (%s) does not support building a model of type '%s'. Support for building custom tooling models was added in Gradle 1.6 and is available in all later versions.",
+                    targetVersion, modelType.getSimpleName()));
+        }
+    }
+
+    public static UnknownModelException unknownModel(Class<?> type, InternalUnsupportedModelException failure) {
+        return new UnknownModelException(String.format("No model of type '%s' is available in this build.", type.getSimpleName()), failure.getCause());
+    }
+
+    public static UnsupportedVersionException unsupportedFeature(String feature, String targetVersion, String versionAdded) {
+        return new UnsupportedVersionException(String.format("The version of Gradle you are using (%s) does not support %s. Support for this was added in Gradle %s and is available in all later versions.",
+                targetVersion, feature, versionAdded));
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ProjectSensitiveToolingModelBuilder.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ProjectSensitiveToolingModelBuilder.java
new file mode 100644
index 0000000..417c04f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ProjectSensitiveToolingModelBuilder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.model.internal;
+
+import org.gradle.api.Project;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+/**
+ * {@link ToolingModelBuilder} that can behave differently when called from
+ * a simple {@code ProjectConnection.getModel(Class)} and when called inside
+ * {@code BuildAction} with a passed Project parameter
+ */
+public abstract class ProjectSensitiveToolingModelBuilder implements ToolingModelBuilder{
+
+    /**
+     * Callback to create requested model.
+     *
+     * Default implementation uses fallback to {@link ToolingModelBuilder#buildAll(String, org.gradle.api.Project)}.
+     * @param modelName model name accepted bu {@link #canBuild(String)}
+     * @param project project used when calling {@link org.gradle.tooling.BuildController#getModel(org.gradle.tooling.model.Model, Class)} or default project
+     * @param implicitProject {@code true} if no project was specified when request for this model was made
+     * @return model
+     */
+    public Object buildAll(String modelName, Project project, boolean implicitProject) {
+        return buildAll(modelName, project);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/TestModel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/TestModel.java
deleted file mode 100644
index f93382e..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/TestModel.java
+++ /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.tooling.model.internal;
-
-import org.gradle.tooling.model.Element;
-
-/**
- * For testing purposes only
- * <p>
- * by Szczepan Faber, created at: 12/21/11
- */
-public interface TestModel extends Element {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/package-info.java
index 4280ecb..0ee0633 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/package-info.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * A UI-centric model of a Gradle build, provided by the Gradle tooling API.
+ * The general-purpose tooling model types, provided by the tooling API.
  */
 package org.gradle.tooling.model;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/integtests/tooling/fixture/GradleVersionSpecTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/integtests/tooling/fixture/GradleVersionSpecTest.groovy
new file mode 100644
index 0000000..9063ce9
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/integtests/tooling/fixture/GradleVersionSpecTest.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.fixture
+
+import org.gradle.util.GradleVersion
+import spock.lang.Specification
+
+class GradleVersionSpecTest extends Specification {
+    def "greater-than-or-equal version constraint matches all versions with specified base version and later"() {
+        def spec = GradleVersionSpec.toSpec(">=1.0")
+
+        expect:
+        spec.isSatisfiedBy(GradleVersion.version("1.0"))
+        spec.isSatisfiedBy(GradleVersion.version("1.0-rc-1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.0-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("1.1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.1-rc-7"))
+        spec.isSatisfiedBy(GradleVersion.version("1.1-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("2.56"))
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        !spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+        !spec.isSatisfiedBy(GradleVersion.version("0.5"))
+    }
+
+    def "less-than-or-equal version constraint matches all versions with specified base version and earlier"() {
+        def spec = GradleVersionSpec.toSpec("<=1.4")
+
+        expect:
+        spec.isSatisfiedBy(GradleVersion.version("1.4"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-rc-1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("1.3"))
+        spec.isSatisfiedBy(GradleVersion.version("1.3-rc-7"))
+        spec.isSatisfiedBy(GradleVersion.version("1.3-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.5"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-rc-1"))
+        !spec.isSatisfiedBy(GradleVersion.version("12.45"))
+    }
+
+    def "less-than version constraint matches versions earlier than specified version"() {
+        def spec = GradleVersionSpec.toSpec("<1.4")
+
+        expect:
+        spec.isSatisfiedBy(GradleVersion.version("1.3"))
+        spec.isSatisfiedBy(GradleVersion.version("1.3-rc-7"))
+        spec.isSatisfiedBy(GradleVersion.version("1.3-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.4"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.4-rc-1"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.4-12341010120000+1000"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-rc-1"))
+        !spec.isSatisfiedBy(GradleVersion.version("12.45"))
+    }
+
+    def "equals version constraint matches versions with same base version"() {
+        def spec = GradleVersionSpec.toSpec("=1.4")
+
+        expect:
+        spec.isSatisfiedBy(GradleVersion.version("1.4"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-rc-7"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-12341010120000+1000"))
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        !spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-rc-1"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-12341010120000+1000"))
+        !spec.isSatisfiedBy(GradleVersion.version("12.45"))
+    }
+
+    def "current version constraint matches current version"() {
+        def spec = GradleVersionSpec.toSpec("current")
+
+        expect:
+        spec.isSatisfiedBy(GradleVersion.current())
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.4-rc-7"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.4-12341010120000+1000"))
+        !spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-rc-1"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-12341010120000+1000"))
+        !spec.isSatisfiedBy(GradleVersion.version("12.45"))
+    }
+
+    def "not current version constraint matches everything other than current version"() {
+        def spec = GradleVersionSpec.toSpec("!current")
+
+        expect:
+        !spec.isSatisfiedBy(GradleVersion.current())
+
+        spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-rc-7"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+        spec.isSatisfiedBy(GradleVersion.version("1.5"))
+        spec.isSatisfiedBy(GradleVersion.version("1.5-rc-1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.5-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("12.45"))
+    }
+
+    def "range constraint matches all versions inside range"() {
+        def spec = GradleVersionSpec.toSpec(">=1.0 <=1.4")
+
+        expect:
+        spec.isSatisfiedBy(GradleVersion.version("1.0"))
+        spec.isSatisfiedBy(GradleVersion.version("1.0-rc-1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.0-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("1.1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.1-rc-1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.1-12341010120000+1000"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-rc-1"))
+        spec.isSatisfiedBy(GradleVersion.version("1.4-12341010120000+1000"))
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.0-milestone-9"))
+        !spec.isSatisfiedBy(GradleVersion.version("0.9.2"))
+        !spec.isSatisfiedBy(GradleVersion.version("0.5"))
+
+        !spec.isSatisfiedBy(GradleVersion.version("1.5"))
+        !spec.isSatisfiedBy(GradleVersion.version("1.5-rc-1"))
+        !spec.isSatisfiedBy(GradleVersion.version("12.45"))
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/CollectionMapperTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/CollectionMapperTest.groovy
new file mode 100644
index 0000000..ba47a16
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/CollectionMapperTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter
+
+import org.gradle.tooling.model.DomainObjectSet
+import spock.lang.Specification
+
+class CollectionMapperTest extends Specification {
+    final def mapper = new CollectionMapper()
+
+    def "maps collection types"() {
+        expect:
+        def collection = mapper.createEmptyCollection(sourceType)
+        collection.class == collectionType
+
+        where:
+        sourceType      | collectionType
+        Collection      | ArrayList
+        List            | ArrayList
+        DomainObjectSet | ArrayList
+        Set             | LinkedHashSet
+        SortedSet       | TreeSet
+    }
+
+    def "maps map types"() {
+        expect:
+        def map = mapper.createEmptyMap(sourceType)
+        map.getClass() == mapType
+
+        where:
+        sourceType | mapType
+        Map        | LinkedHashMap
+        SortedMap  | TreeMap
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/CompatibleIntrospectorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/CompatibleIntrospectorTest.groovy
new file mode 100644
index 0000000..ebe4072
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/CompatibleIntrospectorTest.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.tooling.internal.adapter
+
+import spock.lang.Specification
+
+class CompatibleIntrospectorTest extends Specification {
+    
+    class Foo {
+        
+        int number
+        
+        String getMessage() {
+            "Hello!"
+        }
+
+        String getBroken() {
+            throw new ArithmeticException("broken")
+        }
+
+        void setNumber(int number) {
+            this.number = number
+        }
+    }
+
+    def foo = new Foo()
+    def intro = new CompatibleIntrospector(foo)
+    
+    def "gets stuff safely"() {
+        expect:
+        'Hello!' == intro.getSafely('blah', 'getMessage')
+        'blah' == intro.getSafely('blah', 'doesNotExist')
+    }
+
+    def "propagates failure to get value"() {
+        when:
+        intro.getSafely('default', 'getBroken')
+
+        then:
+        ArithmeticException e = thrown()
+        e.message == 'broken'
+    }
+
+    def "calls methods safely"() {
+        when:
+        intro.callSafely('doesNotExist', 10)
+        then:
+        foo.number == 0
+
+        when:
+        intro.callSafely('setNumber', 10)
+        then:
+        foo.number == 10
+    }
+
+    def "propagates failure to call method"() {
+        when:
+        intro.callSafely('getBroken')
+
+        then:
+        ArithmeticException e = thrown()
+        e.message == 'broken'
+    }
+
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/ProtocolToModelAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/ProtocolToModelAdapterTest.groovy
new file mode 100644
index 0000000..d2f2f81
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/adapter/ProtocolToModelAdapterTest.groovy
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter
+
+import org.gradle.api.Action
+import org.gradle.messaging.remote.internal.Message
+import org.gradle.tooling.model.DomainObjectSet
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+import java.lang.reflect.InvocationHandler
+import java.nio.channels.ByteChannel
+import java.nio.channels.Channel
+
+class ProtocolToModelAdapterTest extends Specification {
+    final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter()
+
+    def mapsNullToNull() {
+        expect:
+        adapter.adapt(TestModel.class, null) == null
+    }
+
+    def createsProxyAdapterForProtocolModel() {
+        TestProtocolModel protocolModel = Mock()
+
+        expect:
+        adapter.adapt(TestModel.class, protocolModel) instanceof TestModel
+    }
+
+    def proxiesAreEqualWhenTargetProtocolObjectsAreEqual() {
+        TestProtocolModel protocolModel1 = Mock()
+        TestProtocolModel protocolModel2 = Mock()
+
+        def model = adapter.adapt(TestModel.class, protocolModel1)
+        def equal = adapter.adapt(TestModel.class, protocolModel1)
+        def different = adapter.adapt(TestModel.class, protocolModel2)
+
+        expect:
+        Matchers.strictlyEquals(model, equal)
+        model != different
+    }
+
+    def methodInvocationOnModelDelegatesToTheProtocolModelObject() {
+        TestProtocolModel protocolModel = Mock()
+        _ * protocolModel.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.name == 'name'
+    }
+
+    def createsProxyAdapterForMethodReturnValue() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getProject() >> protocolProject
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.project instanceof TestProject
+        model.project.name == 'name'
+    }
+
+    def doesNotAdaptNullReturnValue() {
+        TestProtocolModel protocolModel = Mock()
+        _ * protocolModel.getProject() >> null
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.project == null
+    }
+
+    def adaptsIterableToDomainObjectSet() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getChildren() >> [protocolProject]
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.children.size() == 1
+        model.children[0] instanceof TestProject
+        model.children[0].name == 'name'
+    }
+
+    def adaptsIterableToCollectionType() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getChildList() >> [protocolProject]
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.childList.size() == 1
+        model.childList[0] instanceof TestProject
+        model.childList[0].name == 'name'
+    }
+
+    def adaptsMapElements() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.project >> protocolProject
+        _ * protocolModel.getChildMap() >> Collections.singletonMap(protocolProject, protocolProject)
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.childMap.size() == 1
+        model.childMap[model.project] == model.project
+    }
+
+    def cachesPropertyValues() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getProject() >> protocolProject
+        _ * protocolModel.getChildren() >> [protocolProject]
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.project.is(model.project)
+        model.children.is(model.children)
+    }
+
+    def reportsMethodWhichDoesNotExistOnProtocolObject() {
+        PartialTestProtocolModel protocolModel = Mock()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.project
+
+        then:
+        UnsupportedMethodException e = thrown()
+        e.message.contains "TestModel.getProject()"
+    }
+
+    def propagatesExceptionThrownByProtocolObject() {
+        TestProtocolModel protocolModel = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+        model.name
+
+        then:
+        protocolModel.name >> { throw failure }
+        RuntimeException e = thrown()
+        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 "mapper can register method invoker to override getter method"() {
+        MethodInvoker methodInvoker = Mock()
+        Action mapper = Mock()
+        TestProtocolModel protocolModel = Mock()
+        TestProject project = Mock()
+
+        given:
+        mapper.execute(_) >> { SourceObjectMapping mapping ->
+            mapping.mixIn(methodInvoker)
+        }
+        methodInvoker.invoke({ it.name == 'getProject' }) >> { MethodInvocation method -> method.result = project }
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, mapper)
+
+        then:
+        model.project == project
+
+        and:
+        0 * protocolModel._
+    }
+
+    def "mapper can register method invoker to provide getter method implementation"() {
+        MethodInvoker methodInvoker = Mock()
+        Action mapper = Mock()
+        PartialTestProtocolModel protocolModel = Mock()
+        TestProject project = Mock()
+
+        given:
+        mapper.execute(_) >> { SourceObjectMapping mapping ->
+            mapping.mixIn(methodInvoker)
+        }
+        methodInvoker.invoke({ it.name == 'getProject' }) >> { MethodInvocation method -> method.result = project }
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, mapper)
+
+        then:
+        model.project == project
+
+        and:
+        0 * protocolModel._
+    }
+
+    def "adapts values returned by method invoker"() {
+        MethodInvoker methodInvoker = Mock()
+        Action mapper = Mock()
+
+        given:
+        mapper.execute(_) >> { SourceObjectMapping mapping ->
+            mapping.mixIn(methodInvoker)
+        }
+        methodInvoker.invoke({ it.name == 'getProject' }) >> { MethodInvocation method -> method.result = new Object() }
+
+        when:
+        def model = adapter.adapt(TestModel.class, new Object(), mapper)
+
+        then:
+        model.project
+    }
+
+    def "method invoker properties are cached"() {
+        MethodInvoker methodInvoker = Mock()
+        Action mapper = Mock()
+        PartialTestProtocolModel protocolModel = Mock()
+        TestProject project = Mock()
+
+        given:
+        mapper.execute(_) >> { SourceObjectMapping mapping ->
+            mapping.mixIn(methodInvoker)
+        }
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, mapper)
+        model.project
+        model.project
+
+        then:
+        1 * methodInvoker.invoke(!null) >> { MethodInvocation method -> method.result = project }
+        0 * methodInvoker._
+        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 values returned from mix in beans"() {
+        PartialTestProtocolModel protocolModel = Mock()
+
+        given:
+        protocolModel.name >> 'name'
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, ConfigMixin)
+
+        then:
+        model.project != null
+    }
+
+    def "mapper can mix in methods from another bean"() {
+        def mapper = Mock(Action)
+        def protocolModel = Mock(PartialTestProtocolModel)
+
+        given:
+        protocolModel.name >> 'name'
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, mapper)
+
+        then:
+        1 * mapper.execute({it.sourceObject == protocolModel}) >> { SourceObjectMapping mapping ->
+            mapping.mixIn(ConfigMixin)
+        }
+
+        and:
+        model.name == "[name]"
+        model.getConfig('default') == "[default]"
+    }
+
+    def "delegates to type provider to determine type to wrap an object in"() {
+        def typeProvider = Mock(TargetTypeProvider)
+        def adapter = new ProtocolToModelAdapter(typeProvider)
+        def sourceObject = new Object()
+
+        given:
+        _ * typeProvider.getTargetType(Channel, sourceObject) >> ByteChannel
+
+        when:
+        def result = adapter.adapt(Channel.class, sourceObject)
+
+        then:
+        result instanceof ByteChannel
+    }
+
+    def "mapper can specify the type to wrap an object in"() {
+        def mapper = Mock(Action)
+        def sourceObject = Mock(TestProtocolModel)
+        def sourceProject = Mock(TestProtocolProject)
+        def adapter = new ProtocolToModelAdapter()
+
+        given:
+        sourceObject.project >> sourceProject
+
+        when:
+        def result = adapter.adapt(TestModel.class, sourceObject, mapper)
+
+        then:
+        1 * mapper.execute({it.sourceObject == sourceObject})
+
+        when:
+        def project = result.project
+
+        then:
+        project instanceof TestExtendedProject
+
+        and:
+        1 * mapper.execute({it.sourceObject == sourceProject}) >> { SourceObjectMapping mapping ->
+            mapping.mapToType(TestExtendedProject)
+        }
+    }
+
+    def "view objects can be serialized"() {
+        def protocolModel = new TestProtocolProjectImpl()
+
+        given:
+        def model = adapter.adapt(TestProject.class, protocolModel)
+
+        expect:
+        def serialized = new ByteArrayOutputStream()
+        Message.send(model, serialized)
+        def copiedModel = Message.receive(new ByteArrayInputStream(serialized.toByteArray()), getClass().classLoader)
+        copiedModel instanceof TestProject
+        copiedModel != model
+        copiedModel.name == "name"
+    }
+
+    def "unpacks source object from view"() {
+        def source = new Object()
+
+        given:
+        def view = adapter.adapt(TestProject.class, source)
+
+        expect:
+        adapter.unpack(view).is(source)
+    }
+
+    def "fails when source object is not a view object"() {
+        when:
+        adapter.unpack("not a view")
+
+        then:
+        thrown(IllegalArgumentException)
+
+        when:
+        adapter.unpack(java.lang.reflect.Proxy.newProxyInstance(getClass().classLoader, [Runnable] as Class[], Stub(InvocationHandler)))
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+}
+
+interface TestModel {
+    String getName()
+
+    TestProject getProject()
+
+    boolean isConfigSupported()
+
+    String getConfig(String defaultValue)
+
+    DomainObjectSet<? extends TestProject> getChildren()
+
+    List<TestProject> getChildList()
+
+    Map<TestProject, TestProject> getChildMap()
+}
+
+interface TestProject {
+    String getName()
+}
+
+interface TestExtendedProject extends TestProject {
+}
+
+interface TestProtocolModel {
+    String getName()
+
+    TestProtocolProject getProject()
+
+    Iterable<? extends TestProtocolProject> getChildren()
+
+    Iterable<? extends TestProtocolProject> getChildList()
+
+    Map<String, ? extends TestProtocolProject> getChildMap()
+
+    String getConfig();
+}
+
+interface PartialTestProtocolModel {
+    String getName()
+}
+
+interface TestProtocolProject {
+    String getName()
+}
+
+class TestProtocolProjectImpl implements Serializable {
+    String name = "name"
+}
+
+class ConfigMixin {
+    TestModel model
+
+    ConfigMixin(TestModel model) {
+        this.model = model
+    }
+
+    Object getProject() {
+        return new Object()
+    }
+
+    String getConfig(String value) {
+        return "[${model.getConfig(value)}]"
+    }
+
+    String getName() {
+        return "[${model.name}]"
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy
index 5095c2d..11cc487 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy
@@ -17,10 +17,10 @@ package org.gradle.tooling.internal.consumer
 
 import org.gradle.listener.ListenerManager
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.tooling.internal.consumer.async.DefaultAsyncConnection
-import org.gradle.tooling.internal.consumer.connection.LazyConnection
-import org.gradle.tooling.internal.consumer.connection.LoggingInitializerConnection
-import org.gradle.tooling.internal.consumer.connection.ProgressLoggingConnection
+import org.gradle.tooling.internal.consumer.async.DefaultAsyncConsumerActionExecutor
+import org.gradle.tooling.internal.consumer.connection.LazyConsumerActionExecutor
+import org.gradle.tooling.internal.consumer.connection.LoggingInitializerConsumerActionExecutor
+import org.gradle.tooling.internal.consumer.connection.ProgressLoggingConsumerActionExecutor
 import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader
 import spock.lang.Specification
 
@@ -29,7 +29,7 @@ class ConnectionFactoryTest extends Specification {
     final ListenerManager listenerManager = Mock()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
     final Distribution distribution = Mock()
-    final ConnectionParameters parameters = new DefaultConnectionParameters()
+    final ConnectionParameters parameters = DefaultConnectionParameters.builder().build()
     final ConnectionFactory factory = new ConnectionFactory(implementationLoader)
 
     def usesImplementationLoaderToLoadConnectionFactory() {
@@ -38,10 +38,11 @@ class ConnectionFactoryTest extends Specification {
 
         then:
         result instanceof DefaultProjectConnection
-        result.connection instanceof DefaultAsyncConnection
-        result.connection.connection instanceof LoggingInitializerConnection
-        result.connection.connection.connection instanceof ProgressLoggingConnection
-        result.connection.connection.connection.connection instanceof LazyConnection
+        result.connection instanceof DefaultAsyncConsumerActionExecutor
+        result.connection.actionExecutor instanceof LoggingInitializerConsumerActionExecutor
+        result.connection.actionExecutor.actionExecutor instanceof ProgressLoggingConsumerActionExecutor
+        result.connection.actionExecutor.actionExecutor.actionExecutor instanceof LazyConsumerActionExecutor
+        _ * distribution.displayName >> "[some distribution]"
         0 * _._
     }
 }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy
index 8d15931..c5c581e 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy
@@ -19,9 +19,6 @@ package org.gradle.tooling.internal.consumer;
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 12/6/11
- */
 public class ConnectorServicesTest extends Specification {
 
     def "services sharing configuration"() {
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildActionExecuterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildActionExecuterTest.groovy
new file mode 100644
index 0000000..6384451
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildActionExecuterTest.groovy
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer
+
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import org.gradle.tooling.BuildAction
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.tooling.ResultHandler
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1
+import org.gradle.tooling.model.GradleProject
+
+class DefaultBuildActionExecuterTest extends ConcurrentSpec {
+    def asyncConnection = Mock(AsyncConsumerActionExecutor)
+    def connection = Mock(ConsumerConnection)
+    def parameters = Mock(ConnectionParameters)
+    def action = Mock(BuildAction)
+    def executer = new DefaultBuildActionExecuter(action, asyncConnection, parameters)
+
+    def "delegates to connection to run action"() {
+        ResultHandlerVersion1<GradleProject> adaptedHandler
+        ResultHandler<GradleProject> handler = Mock()
+        GradleProject result = Mock()
+
+        when:
+        executer.run(handler)
+
+        then:
+        1 * asyncConnection.run(!null, !null) >> {args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+        }
+        1 * connection.run(action, _) >> result
+
+        when:
+        adaptedHandler.onComplete(result)
+
+        then:
+        1 * handler.onComplete(result)
+        0 * _._
+    }
+
+    def "notifies handler of failure"() {
+        ResultHandlerVersion1<GradleProject> adaptedHandler
+        ResultHandler<GradleProject> handler = Mock()
+        RuntimeException failure = new RuntimeException()
+        GradleConnectionException wrappedFailure
+
+        when:
+        executer.run(handler)
+
+        then:
+        1 * asyncConnection.run(!null, !null) >> {args ->
+            adaptedHandler = args[1]
+            adaptedHandler.onFailure(failure)
+        }
+
+        and:
+        1 * handler.onFailure(!null) >> {args -> wrappedFailure = args[0] }
+        _ * asyncConnection.displayName >> '[connection]'
+        wrappedFailure.message == 'Could not run build action using [connection].'
+        wrappedFailure.cause.is(failure)
+        0 * _._
+    }
+
+    def "running action does not block"() {
+        GradleProject result = Mock()
+        ResultHandler<GradleProject> handler = Mock()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def wrappedHandler = args[1]
+            start {
+                thread.blockUntil.dispatched
+                instant.resultAvailable
+                wrappedHandler.onComplete(result)
+            }
+        }
+        handler.onComplete(result) >> {
+            instant.resultReceived
+        }
+
+        when:
+        async {
+            executer.run(handler)
+            instant.dispatched
+            thread.blockUntil.resultReceived
+        }
+
+        then:
+        instant.dispatched < instant.resultAvailable
+        instant.resultAvailable < instant.resultReceived
+    }
+
+    def "run() blocks until result is available"() {
+        GradleProject result = Mock()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def handler = args[1]
+            start {
+                thread.block()
+                instant.resultAvailable
+                handler.onComplete(result)
+            }
+        }
+
+        when:
+        def model
+        operation.fetchResult {
+            model = executer.run()
+        }
+
+        then:
+        model == result
+
+        and:
+        operation.fetchResult.end > instant.resultAvailable
+    }
+
+    def "run() blocks until request fails"() {
+        RuntimeException failure = new RuntimeException()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def handler = args[1]
+            start {
+                thread.block()
+                instant.failureAvailable
+                handler.onFailure(failure)
+            }
+        }
+
+        when:
+        operation.fetchResult {
+            executer.run()
+        }
+
+        then:
+        GradleConnectionException e = thrown()
+        e.cause.is(failure)
+
+        and:
+        operation.fetchResult.end > instant.failureAvailable
+    }
+
+}
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 a04f8b7..24a0366 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
@@ -15,140 +15,311 @@
  */
 package org.gradle.tooling.internal.consumer
 
+import com.google.common.collect.Sets
+import org.gradle.api.GradleException
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 import org.gradle.tooling.GradleConnectionException
 import org.gradle.tooling.ResultHandler
-import org.gradle.tooling.internal.consumer.async.AsyncConnection
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.gradle.BasicGradleTaskSelector
+import org.gradle.tooling.internal.protocol.InternalLaunchable
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.Launchable
 import org.gradle.tooling.model.Task
-import org.gradle.util.ConcurrentSpecification
+import org.gradle.tooling.model.TaskSelector
 
-class DefaultBuildLauncherTest extends ConcurrentSpecification {
-    final AsyncConnection protocolConnection = Mock()
+class DefaultBuildLauncherTest extends ConcurrentSpec {
+    final AsyncConsumerActionExecutor asyncConnection = Mock()
+    final ConsumerConnection connection = Mock()
     final ConnectionParameters parameters = Mock()
-    final DefaultBuildLauncher launcher = new DefaultBuildLauncher(protocolConnection, parameters)
+    final DefaultBuildLauncher launcher = new DefaultBuildLauncher(asyncConnection, parameters)
 
-    def buildDelegatesToProtocolConnection() {
+    def "requests consumer connection run build"() {
         ResultHandler<Void> handler = Mock()
+        ResultHandlerVersion1<Void> adaptedHandler
 
         when:
         launcher.run(handler)
 
         then:
-        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
-            def params = args[1]
+        1 * asyncConnection.run(!null, !null) >> { args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+            adaptedHandler.onComplete(null)
+        }
+        1 * connection.run(Void, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
             assert params.tasks == []
             assert params.standardOutput == null
             assert params.standardError == null
+            assert params.standardInput == null
+            assert params.javaHome == null
+            assert params.jvmArguments == null
+            assert params.arguments == null
             assert params.progressListener != null
-            def wrappedHandler = args[2]
-            wrappedHandler.onComplete(null)
+            return null
         }
         1 * handler.onComplete(null)
-        0 * protocolConnection._
+        0 * asyncConnection._
         0 * handler._
     }
 
-    def canConfigureTheOperation() {
+    def "can configure the operation"() {
         Task task1 = task(':task1')
         Task task2 = task(':task2')
+        ResultHandlerVersion1<Void> adaptedHandler
         ResultHandler<Void> handler = Mock()
+        OutputStream stdout = Stub()
+        OutputStream stderr = Stub()
 
         when:
-        launcher.forTasks(task1, task2).run(handler)
+        launcher.standardOutput = stdout
+        launcher.standardError = stderr
+        launcher.forTasks(task1, task2)
+        launcher.run(handler)
 
         then:
-        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
-            def params = args[1]
+        1 * asyncConnection.run(!null, !null) >> { args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+            adaptedHandler.onComplete(null)
+        }
+        1 * connection.run(Void, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
             assert params.tasks == [':task1', ':task2']
-            assert params.standardOutput == null
-            assert params.standardError == null
-            assert params.progressListener != null
-            def wrappedHandler = args[2]
-            wrappedHandler.onComplete(null)
+            assert params.standardOutput == stdout
+            assert params.standardError == stderr
+            return null
+        }
+        1 * handler.onComplete(null)
+        0 * asyncConnection._
+        0 * handler._
+    }
+
+    def "can configure task selector build operation for consumer generated selectors"() {
+        TaskSelector ts = Mock(BasicGradleTaskSelector)
+        _ * ts.name >> 'myTask'
+        _ * ts.taskNames >> Sets.newTreeSet([':a:myTask', ':b:myTask'])
+        ResultHandlerVersion1<Void> adaptedHandler
+        ResultHandler<Void> handler = Mock()
+        OutputStream stdout = Stub()
+        OutputStream stderr = Stub()
+
+        when:
+        launcher.standardOutput = stdout
+        launcher.standardError = stderr
+        launcher.forLaunchables(ts)
+        launcher.run(handler)
+
+        then:
+        1 * asyncConnection.run(!null, !null) >> { args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+            adaptedHandler.onComplete(null)
+        }
+        1 * connection.run(Void, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
+            assert params.tasks == [':a:myTask', ':b:myTask']
+            assert params.standardOutput == stdout
+            assert params.standardError == stderr
+            return null
+        }
+        1 * handler.onComplete(null)
+        0 * asyncConnection._
+        0 * handler._
+    }
+
+    static interface InternalTaskSelectorImplementation extends TaskSelector, InternalLaunchable {
+    }
+
+    def "can configure task selector build operation"() {
+        TaskSelector ts = Mock(InternalTaskSelectorImplementation)
+        _ * ts.name >> 'myTask'
+        ResultHandlerVersion1<Void> adaptedHandler
+        ResultHandler<Void> handler = Mock()
+        OutputStream stdout = Stub()
+        OutputStream stderr = Stub()
+
+        when:
+        launcher.standardOutput = stdout
+        launcher.standardError = stderr
+        launcher.forLaunchables(ts)
+        launcher.run(handler)
+
+        then:
+        1 * asyncConnection.run(!null, !null) >> { args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+            adaptedHandler.onComplete(null)
+        }
+        1 * connection.run(Void, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
+            assert params.launchables == [ts]
+            assert params.standardOutput == stdout
+            assert params.standardError == stderr
+            return null
         }
         1 * handler.onComplete(null)
-        0 * protocolConnection._
+        0 * asyncConnection._
         0 * handler._
     }
 
-    def canRedirectStandardOutputAndError() {
+    def "preserves task selectors order in build operation"() {
+        TaskSelector ts1 = Mock(BasicGradleTaskSelector)
+        _ * ts1.name >> 'firstTask'
+        _ * ts1.taskNames >> Sets.newTreeSet([':firstTask'])
+        TaskSelector ts2 = Mock(BasicGradleTaskSelector)
+        _ * ts2.name >> 'secondTask'
+        _ * ts2.taskNames >> Sets.newTreeSet([':secondTask'])
+        TaskSelector ts3 = Mock(BasicGradleTaskSelector)
+        _ * ts3.name >> 'thirdTask'
+        _ * ts3.taskNames >> Sets.newTreeSet([':thirdTask'])
+        ResultHandlerVersion1<Void> adaptedHandler
         ResultHandler<Void> handler = Mock()
-        OutputStream stdout = Mock()
-        OutputStream stderr = Mock()
+        OutputStream stdout = Stub()
+        OutputStream stderr = Stub()
 
         when:
         launcher.standardOutput = stdout
         launcher.standardError = stderr
+        launcher.forLaunchables(ts1, ts2, ts3)
         launcher.run(handler)
 
         then:
-        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
-            def params = args[1]
+        1 * asyncConnection.run(!null, !null) >> { args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+            adaptedHandler.onComplete(null)
+        }
+        1 * connection.run(Void, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
+            assert params.tasks == [':firstTask', ':secondTask', ':thirdTask']
             assert params.standardOutput == stdout
             assert params.standardError == stderr
-            def wrappedHandler = args[2]
-            wrappedHandler.onComplete(null)
+            return null
         }
+        1 * handler.onComplete(null)
+        0 * asyncConnection._
+        0 * handler._
     }
 
-    def notifiesHandlerOnFailure() {
+    def "notifies handler on failure"() {
         ResultHandler<Void> handler = Mock()
+        ResultHandlerVersion1<Void> adaptedHandler
         RuntimeException failure = new RuntimeException()
 
         when:
         launcher.run(handler)
 
         then:
-        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
-            def wrappedHandler = args[2]
-            wrappedHandler.onFailure(failure)
+        1 * asyncConnection.run(!null, !null) >> { args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+            adaptedHandler.onFailure(failure)
         }
         1 * handler.onFailure(!null) >> { args ->
             def e = args[0]
             assert e instanceof GradleConnectionException
             assert e.message == 'Could not execute build using <connection>.'
         }
-        _ * protocolConnection.displayName >> '<connection>'
-        0 * protocolConnection._
+        _ * asyncConnection.displayName >> '<connection>'
+        0 * asyncConnection._
         0 * handler._
     }
 
-    def buildBlocksUntilResultReceived() {
-        def supplyResult = waitsForAsyncCallback()
-        Task task = Mock()
+    def "running build does not block"() {
+        ResultHandler<Void> handler = Mock()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def wrappedHandler = args[1]
+            start {
+                thread.blockUntil.dispatched
+                instant.resultAvailable
+                wrappedHandler.onComplete(null)
+            }
+        }
+        handler.onComplete(_) >> {
+            instant.resultReceived
+        }
 
         when:
-        supplyResult.start {
-            launcher.forTasks(task).run()
+        async {
+            launcher.run(handler)
+            instant.dispatched
+            thread.blockUntil.resultReceived
         }
 
         then:
-        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
-            def handler = args[2]
-            supplyResult.callbackLater {
+        instant.dispatched < instant.resultAvailable
+        instant.resultAvailable < instant.resultReceived
+    }
+
+    def "run() blocks until build is finished"() {
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def handler = args[1]
+            start {
+                thread.block()
+                instant.resultAvailable
                 handler.onComplete(null)
             }
         }
-    }
-
-    def buildBlocksUntilFailureReceived() {
-        def supplyResult = waitsForAsyncCallback()
-        def failure = new RuntimeException()
-        Task task = Mock()
 
         when:
-        supplyResult.start {
-            launcher.forTasks(task).run()
+        operation.runBuild {
+            launcher.run()
         }
 
         then:
-        GradleConnectionException e = thrown()
-        e.cause == failure
-        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
-            def handler = args[2]
-            supplyResult.callbackLater {
+        operation.runBuild.end > instant.resultAvailable
+    }
+
+    def "run() blocks until build fails"() {
+        RuntimeException failure = new RuntimeException()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def handler = args[1]
+            start {
+                thread.block()
+                instant.failureAvailable
                 handler.onFailure(failure)
             }
         }
+
+        when:
+        operation.runBuild {
+            launcher.run()
+        }
+
+        then:
+        GradleConnectionException e = thrown()
+        e.cause.is(failure)
+
+        and:
+        operation.runBuild.end > instant.failureAvailable
+    }
+
+    def "rejects unknown Launchable"() {
+        Launchable task = Mock(Launchable)
+
+        when:
+        launcher.forLaunchables(task)
+
+        then:
+        def e = thrown(GradleException)
+        e != null
     }
 
     def task(String path) {
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy
index 1389e54..be123ef 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy
@@ -55,6 +55,24 @@ class DefaultGradleConnectorTest extends Specification {
         1 * connectionFactory.create(distribution, { it.gradleUserHomeDir == userDir }) >> connection
     }
 
+    def canSpecifyDistributionAndUserHomeDir() {
+        ProjectConnection connection = Mock()
+        URI gradleDist = new URI('http://server/dist.zip')
+        File userDir = new File('user-dir')
+
+        when:
+        def result = connector
+                .useDistribution(gradleDist)
+                .useGradleUserHomeDir(userDir).forProjectDirectory(projectDir).connect()
+
+        then:
+        result == connection
+
+        and:
+        1 * distributionFactory.getDistribution(gradleDist) >> distribution
+        1 * connectionFactory.create(distribution, { it.gradleUserHomeDir == userDir }) >> connection
+    }
+
     def canSpecifyAGradleInstallationToUse() {
         ProjectConnection connection = Mock()
         File gradleHome = new File('install-dir')
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 6e97958..5a18048 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
@@ -15,82 +15,91 @@
  */
 package org.gradle.tooling.internal.consumer
 
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 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.ConsumerPropertyHandler
-import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
 import org.gradle.tooling.internal.protocol.ProjectVersion3
 import org.gradle.tooling.internal.protocol.ResultHandlerVersion1
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.internal.Exceptions
-import org.gradle.util.ConcurrentSpecification
 
-class DefaultModelBuilderTest extends ConcurrentSpecification {
-    final AsyncConnection protocolConnection = Mock()
-    final ProtocolToModelAdapter adapter = Mock()
+class DefaultModelBuilderTest extends ConcurrentSpec {
+    final AsyncConsumerActionExecutor asyncConnection = Mock()
+    final ConsumerConnection connection = Mock()
     final ConnectionParameters parameters = Mock()
-    final DefaultModelBuilder<GradleProject, ProjectVersion3> builder = new DefaultModelBuilder<GradleProject, ProjectVersion3>(GradleProject, ProjectVersion3, protocolConnection, adapter, parameters)
+    final DefaultModelBuilder<GradleProject> builder = new DefaultModelBuilder<GradleProject>(GradleProject, asyncConnection, parameters)
 
-    def getModelDelegatesToProtocolConnectionToFetchModel() {
-        ResultHandlerVersion1<ProjectVersion3> adaptedHandler
+    def "requests model from consumer connection"() {
+        ResultHandlerVersion1<GradleProject> adaptedHandler
         ResultHandler<GradleProject> handler = Mock()
-        ProjectVersion3 result = Mock()
-        GradleProject adaptedResult = Mock()
+        GradleProject result = Mock()
 
         when:
         builder.get(handler)
 
         then:
-        1 * protocolConnection.run(ProjectVersion3, !null, !null) >> {args ->
-            def params = args[1]
+        1 * asyncConnection.run(!null, !null) >> {args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+        }
+        1 * connection.run(GradleProject, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
             assert params.standardOutput == null
             assert params.standardError == null
+            assert params.standardInput == null
+            assert params.javaHome == null
+            assert params.jvmArguments == null
+            assert params.arguments == null
             assert params.progressListener != null
             assert params.tasks == null
-            adaptedHandler = args[2]
+            return result
         }
 
         when:
         adaptedHandler.onComplete(result)
 
         then:
-        1 * protocolConnection.versionDetails
-        1 * adapter.adapt(GradleProject.class, result, _ as ConsumerPropertyHandler) >> adaptedResult
-        1 * handler.onComplete(adaptedResult)
+        1 * handler.onComplete(result)
         0 * _._
     }
 
-    def canConfigureTheOperation() {
+    def "can configure the operation"() {
         ResultHandler<GradleProject> handler = Mock()
         ResultHandlerVersion1<ProjectVersion3> adaptedHandler
-        ProjectVersion3 result = Mock()
-        GradleProject adaptedResult = Mock()
+        GradleProject result = Mock()
 
         when:
         builder.forTasks('a', 'b').get(handler)
 
         then:
-        1 * protocolConnection.run(ProjectVersion3, !null, !null) >> {args ->
-            def params = args[1]
+        1 * asyncConnection.run(!null, !null) >> {args ->
+            ConsumerAction<GradleProject> action = args[0]
+            action.run(connection)
+            adaptedHandler = args[1]
+        }
+        1 * connection.run(GradleProject, _) >> {args ->
+            ConsumerOperationParameters params = args[1]
             assert params.standardOutput == null
             assert params.standardError == null
             assert params.progressListener != null
             assert params.tasks == ['a', 'b']
-            adaptedHandler = args[2]
+            return result
         }
 
         when:
         adaptedHandler.onComplete(result)
 
         then:
-        1 * protocolConnection.versionDetails
-        1 * adapter.adapt(GradleProject.class, result, _ as ConsumerPropertyHandler) >> adaptedResult
-        1 * handler.onComplete(adaptedResult)
+        1 * handler.onComplete(result)
         0 * _._
     }
 
-    def getModelWrapsFailureToFetchModel() {
+    def "wraps failure to fetch model"() {
         ResultHandler<GradleProject> handler = Mock()
         ResultHandlerVersion1<ProjectVersion3> adaptedHandler
         RuntimeException failure = new RuntimeException()
@@ -100,14 +109,16 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         builder.get(handler)
 
         then:
-        1 * protocolConnection.run(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
+        1 * asyncConnection.run(!null, !null) >> {args ->
+            adaptedHandler = args[1]
+        }
 
         when:
         adaptedHandler.onFailure(failure)
 
         then:
         1 * handler.onFailure(!null) >> {args -> wrappedFailure = args[0] }
-        _ * protocolConnection.displayName >> '[connection]'
+        _ * asyncConnection.displayName >> '[connection]'
         wrappedFailure.message == 'Could not fetch model of type \'GradleProject\' using [connection].'
         wrappedFailure.cause.is(failure)
         0 * _._
@@ -123,7 +134,9 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         builder.get(handler)
 
         then:
-        1 * protocolConnection.run(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
+        1 * asyncConnection.run(!null, !null) >> {args ->
+            adaptedHandler = args[1]
+        }
 
         when:
         adaptedHandler.onFailure(failure)
@@ -134,47 +147,85 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         wrappedFailure.cause.is(failure)
     }
 
-    def getModelBlocksUntilResultReceivedFromProtocolConnection() {
-        def supplyResult = waitsForAsyncCallback()
-        ProjectVersion3 result = Mock()
-        GradleProject adaptedResult = Mock()
-        _ * adapter.adapt(GradleProject.class, result, _) >> adaptedResult
+    def "fetching model does not block"() {
+        GradleProject result = Mock()
+        ResultHandler<GradleProject> handler = Mock()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def wrappedHandler = args[1]
+            start {
+                thread.blockUntil.dispatched
+                instant.resultAvailable
+                wrappedHandler.onComplete(result)
+            }
+        }
+        handler.onComplete(result) >> {
+            instant.resultReceived
+        }
 
         when:
-        def model
-        supplyResult.start {
-            model = builder.get()
+        async {
+            builder.get(handler)
+            instant.dispatched
+            thread.blockUntil.resultReceived
         }
 
         then:
-        model == adaptedResult
-        1 * protocolConnection.run(!null, !null, !null) >> { args ->
-            def handler = args[2]
-            supplyResult.callbackLater {
+        instant.dispatched < instant.resultAvailable
+        instant.resultAvailable < instant.resultReceived
+    }
+
+    def "get() blocks until model is available"() {
+        GradleProject result = Mock()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def handler = args[1]
+            start {
+                thread.block()
+                instant.resultAvailable
                 handler.onComplete(result)
             }
         }
-    }
-
-    def getModelBlocksUntilFailureReceivedFromProtocolConnectionAndRethrowsFailure() {
-        def supplyResult = waitsForAsyncCallback()
-        RuntimeException failure = new RuntimeException()
 
         when:
         def model
-        supplyResult.start {
+        operation.fetchResult {
             model = builder.get()
         }
 
         then:
-        GradleConnectionException e = thrown()
-        e.cause.is(failure)
-        1 * protocolConnection.run(!null, !null, !null) >> { args ->
-            def handler = args[2]
-            supplyResult.callbackLater {
+        model == result
+
+        and:
+        operation.fetchResult.end > instant.resultAvailable
+    }
+
+    def "get() blocks until request fails"() {
+        RuntimeException failure = new RuntimeException()
+
+        given:
+        asyncConnection.run(!null, !null) >> { args ->
+            def handler = args[1]
+            start {
+                thread.block()
+                instant.failureAvailable
                 handler.onFailure(failure)
             }
         }
+
+        when:
+        operation.fetchResult {
+            builder.get()
+        }
+
+        then:
+        GradleConnectionException e = thrown()
+        e.cause.is(failure)
+
+        and:
+        operation.fetchResult.end > instant.failureAvailable
     }
 }
 
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy
index d18f2a0..d991431 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy
@@ -15,34 +15,32 @@
  */
 package org.gradle.tooling.internal.consumer
 
-import org.gradle.tooling.UnknownModelException
-import org.gradle.tooling.internal.consumer.async.AsyncConnection
-import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.async.AsyncConsumerActionExecutor
 import org.gradle.tooling.model.GradleProject
 import spock.lang.Specification
 
 class DefaultProjectConnectionTest extends Specification {
-    final AsyncConnection protocolConnection = Mock()
-    final ProtocolToModelAdapter adapter = Mock()
+    final AsyncConsumerActionExecutor protocolConnection = Mock()
     final ConnectionParameters parameters = Mock()
-    final DefaultProjectConnection connection = new DefaultProjectConnection(protocolConnection, adapter, parameters)
+    final DefaultProjectConnection connection = new DefaultProjectConnection(protocolConnection, parameters)
 
     def canCreateAModelBuilder() {
         expect:
         connection.model(GradleProject.class) instanceof DefaultModelBuilder
     }
 
-    def canCreateABuildLauncher() {
-        expect:
-        connection.newBuild() instanceof DefaultBuildLauncher
-    }
-    
-    def modelFailsForUnknownModelType() {
+    def modelTypeMustBeAnInterface() {
         when:
-        connection.model(TestBuild.class)
+        connection.model(String.class)
 
         then:
-        thrown(UnknownModelException)
+        IllegalArgumentException e = thrown()
+        e.message == "Cannot fetch a model of type 'java.lang.String' as this type is not an interface."
+    }
+
+    def canCreateABuildLauncher() {
+        expect:
+        connection.newBuild() instanceof DefaultBuildLauncher
     }
 
     def closeStopsBackingConnection() {
@@ -53,7 +51,3 @@ class DefaultProjectConnectionTest extends Specification {
         1 * protocolConnection.stop()
     }
 }
-
-interface TestBuild extends GradleProject {
-    
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
index aec9852..4be0d44 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
@@ -19,18 +19,18 @@ import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.testing.internal.util.Network
 import org.gradle.util.DistributionLocator
 import org.gradle.util.GradleVersion
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import org.junit.Rule
-import spock.lang.IgnoreIf
 import spock.lang.Specification
 
 class DistributionFactoryTest extends Specification {
     @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
     final ProgressLogger progressLogger = Mock()
-    final DistributionFactory factory = new DistributionFactory(tmpDir.file('userHome'))
+    final DistributionFactory factory = new DistributionFactory()
 
     def setup() {
         _ * progressLoggerFactory.newOperation(!null) >> progressLogger
@@ -71,7 +71,7 @@ class DistributionFactoryTest extends Specification {
 
         expect:
         def dist = factory.getDistribution(tmpDir.testDirectory)
-        dist.getToolingImplementationClasspath(progressLoggerFactory).asFiles as Set == [libA, libB] as Set
+        dist.getToolingImplementationClasspath(progressLoggerFactory, null).asFiles as Set == [libA, libB] as Set
     }
 
     def failsWhenInstallationDirectoryDoesNotExist() {
@@ -79,7 +79,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(distDir)
 
         when:
-        dist.getToolingImplementationClasspath(progressLoggerFactory)
+        dist.getToolingImplementationClasspath(progressLoggerFactory, null)
 
         then:
         IllegalArgumentException e = thrown()
@@ -91,7 +91,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(distDir)
 
         when:
-        dist.getToolingImplementationClasspath(progressLoggerFactory)
+        dist.getToolingImplementationClasspath(progressLoggerFactory, null)
 
         then:
         IllegalArgumentException e = thrown()
@@ -103,7 +103,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(distDir)
 
         when:
-        dist.getToolingImplementationClasspath(progressLoggerFactory)
+        dist.getToolingImplementationClasspath(progressLoggerFactory, null)
 
         then:
         IllegalArgumentException e = thrown()
@@ -127,10 +127,44 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(zipFile.toURI())
 
         expect:
-        dist.getToolingImplementationClasspath(progressLoggerFactory).asFiles.name as Set == ['a.jar', 'b.jar'] as Set
+        dist.getToolingImplementationClasspath(progressLoggerFactory, null).asFiles.name as Set == ['a.jar', 'b.jar'] as Set
+    }
+
+    def usesWrapperDistributionInstalledIntoSpecifiedUserHomeDirAsImplementationClasspath() {
+        File customUserHome = tmpDir.file('customUserHome')
+        def zipFile = createZip {
+            lib {
+                file("a.jar")
+                file("b.jar")
+            }
+        }
+        tmpDir.file('gradle/wrapper/gradle-wrapper.properties') << "distributionUrl=${zipFile.toURI()}"
+        def dist = factory.getDefaultDistribution(tmpDir.testDirectory, false)
+        def result = dist.getToolingImplementationClasspath(progressLoggerFactory, customUserHome)
+
+        expect:
+        result.asFiles.name as Set == ['a.jar', 'b.jar'] as Set
+        (result.asFiles.path as Set).every { it.contains('customUserHome')}
+    }
+
+    def usesZipDistributionInstalledIntoSpecifiedUserHomeDirAsImplementationClasspath() {
+        File customUserHome = tmpDir.file('customUserHome')
+        def zipFile = createZip {
+            lib {
+                file("a.jar")
+                file("b.jar")
+            }
+        }
+        def dist = factory.getDistribution(zipFile.toURI())
+        def result = dist.getToolingImplementationClasspath(progressLoggerFactory, customUserHome)
+
+        expect:
+        result.asFiles.name as Set == ['a.jar', 'b.jar'] as Set
+        (result.asFiles.path as Set).every { it.contains('customUserHome')}
     }
 
     def reportsZipDownload() {
+        File customUserHome = tmpDir.file('customUserHome')
         def zipFile = createZip {
             lib {
                 file("a.jar")
@@ -141,7 +175,7 @@ class DistributionFactoryTest extends Specification {
         ProgressLogger loggerTwo = Mock()
 
         when:
-        dist.getToolingImplementationClasspath(progressLoggerFactory)
+        dist.getToolingImplementationClasspath(progressLoggerFactory, customUserHome)
 
         then:
         2 * progressLoggerFactory.newOperation(DistributionFactory.class) >>> [loggerOne, loggerTwo]
@@ -157,7 +191,7 @@ class DistributionFactoryTest extends Specification {
         0 * _._
     }
 
-    @IgnoreIf({ Network.offline })
+    @Requires(TestPrecondition.ONLINE)
     def failsWhenDistributionZipDoesNotExist() {
         URI zipFile = new URI("http://google.com/does-not-exist/gradle-1.0.zip")
         def dist = factory.getDistribution(zipFile)
@@ -175,7 +209,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(zipFile.toURI())
 
         when:
-        dist.getToolingImplementationClasspath(progressLoggerFactory)
+        dist.getToolingImplementationClasspath(progressLoggerFactory, null)
 
         then:
         IllegalArgumentException e = thrown()
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
deleted file mode 100644
index fe93200..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProtocolToModelAdapterTest.groovy
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.consumer
-
-import org.gradle.tooling.model.DomainObjectSet
-
-interface TestModel {
-    String getName()
-
-    TestProject getProject()
-
-    boolean isConfigSupported()
-
-    String getConfig(String defaultValue)
-
-    DomainObjectSet<? extends TestProject> getChildren()
-}
-
-interface TestProject {
-    String getName()
-}
-
-interface TestProtocolModel {
-    String getName()
-
-    TestProtocolProject getProject()
-
-    Iterable<? extends TestProtocolProject> getChildren()
-
-    String getConfig();
-}
-
-interface PartialTestProtocolModel {
-    String getName()
-}
-
-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/SynchronizedLoggingTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
index edbb634..b21cf7c 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
@@ -22,9 +22,6 @@ import spock.lang.Specification
 
 import java.util.concurrent.CopyOnWriteArraySet
 
-/**
- * by Szczepan Faber, created at: 12/16/11
- */
 public class SynchronizedLoggingTest extends Specification {
 
     def logging = new SynchronizedLogging()
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/async/DefaultAsyncConsumerActionExecutorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/async/DefaultAsyncConsumerActionExecutorTest.groovy
new file mode 100644
index 0000000..62d8025
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/async/DefaultAsyncConsumerActionExecutorTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.async
+
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import org.gradle.tooling.internal.consumer.connection.ConsumerAction
+import org.gradle.tooling.internal.consumer.connection.ConsumerActionExecutor
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1
+
+class DefaultAsyncConsumerActionExecutorTest extends ConcurrentSpec {
+    def actionExecuter = Mock(ConsumerActionExecutor) {
+        getDisplayName() >> "[executer]"
+    }
+    def action = Mock(ConsumerAction)
+    def handler = Mock(ResultHandlerVersion1)
+    def connection = new DefaultAsyncConsumerActionExecutor(actionExecuter, executorFactory)
+
+    def cleanup() {
+        connection.stop()
+    }
+
+    def "runs action asynchronously"() {
+        when:
+        async {
+            connection.run(action, handler)
+            instant.dispatched
+        }
+
+        then:
+        1 * actionExecuter.run(action) >> {
+            thread.blockUntil.dispatched
+            instant.actionStarted
+            return "result"
+        }
+        1 * handler.onComplete("result") >> {
+            instant.resultReceived
+        }
+
+        and:
+        instant.actionStarted < instant.resultReceived
+    }
+
+    def "notifies handler on failure"() {
+        def failure = new RuntimeException()
+
+        when:
+        async {
+            connection.run(action, handler)
+        }
+
+        then:
+        1 * actionExecuter.run(action) >> {
+            throw failure
+        }
+        1 * handler.onFailure(failure)
+    }
+
+    def "cannot use connection after it has stopped"() {
+        when:
+        connection.stop()
+        connection.run(action, handler)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot use [executer] as it has been stopped.'
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ActionAwareConsumerConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ActionAwareConsumerConnectionTest.groovy
new file mode 100644
index 0000000..971b96f
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ActionAwareConsumerConnectionTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BuildAction
+import org.gradle.tooling.BuildActionFailureException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.protocol.*
+import spock.lang.Specification
+
+class ActionAwareConsumerConnectionTest extends Specification {
+    final target = Mock(TestModelBuilder) {
+        getMetaData() >> Stub(ConnectionMetaDataVersion1) {
+            getVersion() >> "1.8"
+        }
+    }
+    final adapter = Mock(ProtocolToModelAdapter)
+    final modelMapping = Stub(ModelMapping)
+    final connection = new ActionAwareConsumerConnection(target, modelMapping, adapter)
+
+    def "delegates to connection to run build action"() {
+        def action = Mock(BuildAction)
+        def parameters = Stub(ConsumerOperationParameters)
+        def buildController = Mock(InternalBuildController)
+
+        when:
+        def result = connection.run(action, parameters)
+
+        then:
+        result == 'result'
+
+        and:
+        1 * target.run(_, parameters) >> { InternalBuildAction protocolAction, def params ->
+            def actionResult = protocolAction.execute(buildController)
+            return Stub(BuildResult) {
+                getModel() >> actionResult
+            }
+        }
+        1 * action.execute({ it instanceof BuildControllerAdapter }) >> 'result'
+    }
+
+    def "adapts build action failure"() {
+        def action = Mock(BuildAction)
+        def parameters = Stub(ConsumerOperationParameters)
+        def failure = new RuntimeException()
+
+        when:
+        connection.run(action, parameters)
+
+        then:
+        BuildActionFailureException e = thrown()
+        e.message == /The supplied build action failed with an exception./
+        e.cause == failure
+
+        and:
+        1 * target.run(_, parameters) >> { throw new InternalBuildActionFailureException(failure) }
+    }
+
+    interface TestModelBuilder extends ModelBuilder, ConnectionVersion4, ConfigurableConnection, InternalBuildActionExecutor {
+    }
+}
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
deleted file mode 100644
index c15308f..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/AdaptedConnectionTest.groovy
+++ /dev/null
@@ -1,50 +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.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
index 0e5ffd8..f52e3e7 100644
--- 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
@@ -16,46 +16,127 @@
 
 package org.gradle.tooling.internal.consumer.connection
 
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
+import org.gradle.tooling.BuildAction
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.ConnectionParameters
 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 org.gradle.tooling.internal.consumer.versioning.CustomModel
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.protocol.*
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+import org.gradle.tooling.model.gradle.GradleBuild
+import org.gradle.tooling.model.idea.BasicIdeaProject
+import org.gradle.tooling.model.idea.IdeaProject
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
 import spock.lang.Specification
 
 class BuildActionRunnerBackedConsumerConnectionTest extends Specification {
-    final TestBuildActionRunner target = Mock()
-    final ConsumerOperationParameters parameters = Mock()
-    final BuildActionRunnerBackedConsumerConnection connection = new BuildActionRunnerBackedConsumerConnection(target)
+    final ConnectionMetaDataVersion1 metaData = Stub() {
+        getVersion() >> '1.2'
+    }
+    final TestBuildActionRunner target = Mock() {
+        getMetaData() >> metaData
+    }
+    final ConsumerOperationParameters parameters = Stub()
+    final ModelMapping modelMapping = Stub()
+    final ProtocolToModelAdapter adapter = Mock()
+    final BuildActionRunnerBackedConsumerConnection connection = new BuildActionRunnerBackedConsumerConnection(target, modelMapping, adapter)
+
+    def "describes capabilities of the provider"() {
+        given:
+        def details = connection.versionDetails
+
+        expect:
+        details.supportsGradleProjectModel()
+
+        and:
+        details.maySupportModel(HierarchicalEclipseProject)
+        details.maySupportModel(EclipseProject)
+        details.maySupportModel(IdeaProject)
+        details.maySupportModel(BasicIdeaProject)
+        details.maySupportModel(GradleProject)
+        details.maySupportModel(BuildEnvironment)
+        details.maySupportModel(ProjectOutcomes)
+        details.maySupportModel(Void)
+
+        and:
+        !details.maySupportModel(CustomModel)
+    }
 
     def "configures connection"() {
-        def parameters = new ConsumerConnectionParameters(false)
+        def parameters = Stub(ConnectionParameters)
 
         when:
         connection.configure(parameters)
 
         then:
         1 * target.configure(parameters)
+        0 * target._
     }
 
-    def "builds model using run() method"() {
-        BuildResult<String> result = Mock()
-
-        given:
-        result.model >> 'ok'
+    def "builds model using connection's run() method"() {
+        BuildResult<String> result = Stub()
+        GradleProject adapted = Stub()
 
         when:
-        def model = connection.run(String.class, parameters)
+        def model = connection.run(GradleProject.class, parameters)
 
         then:
-        model == 'ok'
+        model == adapted
 
         and:
-        1 * target.run(String.class, parameters) >> result
+        _ * modelMapping.getProtocolType(GradleProject.class) >> Integer.class
+        1 * target.run(Integer.class, parameters) >> result
+        _ * result.model >> 12
+        1 * adapter.adapt(GradleProject.class, 12, _) >> adapted
         0 * target._
     }
 
+    def "builds GradleBuild model by converting GradleProject"() {
+        BuildResult<GradleProject> result = Stub()
+        GradleProject adapted = Stub()
+        GradleBuild adaptedGradleBuild = Stub()
+
+        when:
+        def model = connection.run(GradleBuild.class, parameters)
+        then:
+        model == adaptedGradleBuild
+
+        and:
+        _ * modelMapping.getProtocolType(GradleProject.class) >> GradleProject.class
+        1 * target.run(GradleProject.class, parameters) >> result
+        _ * result.model >> Stub(GradleProject.class)
+        1 * adapter.adapt(GradleProject.class, _, _) >> adapted
+        1 * adapter.adapt(GradleBuild.class, _) >> adaptedGradleBuild
+        0 * target._
+    }
+
+    def "fails when unknown model is requested"() {
+        when:
+        connection.run(CustomModel.class, parameters)
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == /The version of Gradle you are using (1.2) does not support building a model of type 'CustomModel'. Support for building custom tooling models was added in Gradle 1.6 and is available in all later versions./
+    }
+
+    def "fails when build action requested"() {
+        given:
+        parameters.tasks >> ['a']
+
+        when:
+        connection.run(Stub(BuildAction), parameters)
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == /The version of Gradle you are using (1.2) does not support execution of build actions provided by the tooling API client. Support for this was added in Gradle 1.8 and is available in all later versions./
+    }
+
     interface TestBuildActionRunner extends ConnectionVersion4, BuildActionRunner, ConfigurableConnection {
     }
 }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/BuildControllerAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/BuildControllerAdapterTest.groovy
new file mode 100644
index 0000000..f0a173b
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/BuildControllerAdapterTest.groovy
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.UnknownModelException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.protocol.*
+import org.gradle.tooling.model.Element
+import org.gradle.tooling.model.gradle.GradleBuild
+import spock.lang.Specification
+
+class BuildControllerAdapterTest extends Specification {
+    def internalController = Mock(InternalBuildController)
+    def adapter = Mock(ProtocolToModelAdapter)
+    def mapping = Stub(ModelMapping) {
+        getModelIdentifierFromModelType(_) >> { Class type ->
+            return Stub(ModelIdentifier) {
+                getName() >> type.simpleName
+            }
+        }
+    }
+    def controller = new BuildControllerAdapter(adapter, internalController, mapping)
+
+    def "unpacks unsupported model exception"() {
+        def failure = new RuntimeException()
+
+        given:
+        _ * internalController.getModel(null, _) >> { throw new InternalUnsupportedModelException().initCause(failure) }
+
+        when:
+        controller.getModel(String)
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == /No model of type 'String' is available in this build./
+        e.cause == failure
+    }
+
+    def "fetches model for target object"() {
+        def model = new Object()
+        def targetElement = new Object()
+        def modelElement = Stub(Element)
+        def modelView = Stub(GradleBuild)
+
+        when:
+        def result = controller.getModel(modelElement, GradleBuild)
+
+        then:
+        result == modelView
+
+        and:
+        1 * adapter.unpack(modelElement) >> targetElement
+        1 * internalController.getModel(targetElement, _) >> { def target, ModelIdentifier identifier ->
+            assert identifier.name == 'GradleBuild'
+            return Stub(BuildResult) {
+                getModel() >> model
+            }
+        }
+        1 * adapter.adapt(GradleBuild, model) >> modelView
+    }
+
+    def "fetches missing model for target object"() {
+        def targetElement = new Object()
+        def modelElement = Stub(Element)
+
+        when:
+        def result = controller.findModel(modelElement, GradleBuild)
+
+        then:
+        result == null
+
+        and:
+        1 * adapter.unpack(modelElement) >> targetElement
+        1 * internalController.getModel(targetElement, _) >> { throw new InternalUnsupportedModelException() }
+    }
+
+    def "fetches build model"() {
+        def model = Stub(InternalProtocolInterface)
+        def modelView = Stub(GradleBuild)
+
+        when:
+        def result = controller.buildModel
+
+        then:
+        result == modelView
+
+        and:
+        1 * internalController.getModel(null, _) >> { def target, ModelIdentifier identifier ->
+            assert identifier.name == 'GradleBuild'
+            return Stub(BuildResult) {
+                getModel() >> model
+            }
+        }
+        1 * adapter.adapt(GradleBuild, model) >> modelView
+    }
+
+    def "fetches missing model"() {
+        when:
+        def result = controller.findModel(GradleBuild)
+
+        then:
+        result == null
+
+        and:
+        1 * internalController.getModel(null, _) >> { throw new InternalUnsupportedModelException() }
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ConnectionVersion4BackedConsumerConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ConnectionVersion4BackedConsumerConnectionTest.groovy
new file mode 100644
index 0000000..049d01a
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ConnectionVersion4BackedConsumerConnectionTest.groovy
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BuildAction
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.build.VersionOnlyBuildEnvironment
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.CustomModel
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.gradle.DefaultGradleBuild
+import org.gradle.tooling.internal.gradle.PartialGradleProject
+import org.gradle.tooling.internal.protocol.ConnectionMetaDataVersion1
+import org.gradle.tooling.internal.protocol.ConnectionVersion4
+import org.gradle.tooling.internal.protocol.ProjectVersion3
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+import org.gradle.tooling.model.gradle.GradleBuild
+import org.gradle.tooling.model.idea.BasicIdeaProject
+import org.gradle.tooling.model.idea.IdeaProject
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import spock.lang.Specification
+
+class ConnectionVersion4BackedConsumerConnectionTest extends Specification {
+    final ConnectionMetaDataVersion1 metaData = Stub()
+    final ConnectionVersion4 target = Mock() {
+        getMetaData() >> metaData
+    }
+    final ConsumerOperationParameters parameters = Mock()
+    final ModelMapping modelMapping = Stub()
+    final ProtocolToModelAdapter adapter = Mock()
+
+    def "describes capabilities of a 1.0-m3 provider"() {
+        given:
+        metaData.version >> "1.0-milestone-3"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+        def details = connection.versionDetails
+
+        expect:
+        !details.supportsGradleProjectModel()
+
+        and:
+        details.maySupportModel(HierarchicalEclipseProject)
+        details.maySupportModel(EclipseProject)
+        details.maySupportModel(Void)
+
+        and:
+        !details.maySupportModel(IdeaProject)
+        !details.maySupportModel(BasicIdeaProject)
+        !details.maySupportModel(GradleProject)
+        !details.maySupportModel(BuildEnvironment)
+        !details.maySupportModel(ProjectOutcomes)
+        !details.maySupportModel(CustomModel)
+        !details.maySupportModel(GradleBuild)
+    }
+
+    def "describes capabilities of a 1.0-m5 provider"() {
+        given:
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+        def details = connection.versionDetails
+
+        expect:
+        details.supportsGradleProjectModel()
+
+        and:
+        details.maySupportModel(HierarchicalEclipseProject)
+        details.maySupportModel(EclipseProject)
+        details.maySupportModel(IdeaProject)
+        details.maySupportModel(BasicIdeaProject)
+        details.maySupportModel(GradleProject)
+        details.maySupportModel(Void)
+
+        and:
+        !details.maySupportModel(BuildEnvironment)
+        !details.maySupportModel(ProjectOutcomes)
+        !details.maySupportModel(CustomModel)
+        !details.maySupportModel(GradleBuild)
+    }
+
+    def "builds model using connection's getModel() method"() {
+        ProjectVersion3 protocolModel = Mock()
+        GradleProject model = Mock()
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        def result = connection.run(GradleProject.class, parameters)
+
+        then:
+        result == model
+
+        and:
+        _ * modelMapping.getProtocolType(GradleProject.class) >> ProjectVersion3.class
+        1 * target.getModel(ProjectVersion3.class, parameters) >> protocolModel
+        1 * adapter.adapt(GradleProject.class, protocolModel, _) >> model
+        0 * target._
+    }
+
+    def "runs build using connection's executeBuild() method"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        connection.run(Void.class, parameters)
+
+        then:
+        1 * target.executeBuild(parameters, parameters)
+        0 * target._
+    }
+
+    def "builds partial BuildEnvironment model locally"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+        BuildEnvironment model = Stub()
+
+        when:
+        def result = connection.run(BuildEnvironment.class, parameters)
+
+        then:
+        result == model
+
+        and:
+        1 * adapter.adapt(BuildEnvironment.class, {it instanceof VersionOnlyBuildEnvironment}, _) >> model
+        0 * target._
+    }
+
+    def "builds partial GradleProject model using the Eclipse model for a 1.0-m3 provider"() {
+        metaData.version >> "1.0-milestone-3"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+        EclipseProjectVersion3 protocolModel = Stub()
+        GradleProject model = Stub()
+
+        when:
+        def result = connection.run(GradleProject.class, parameters)
+
+        then:
+        result == model
+
+        and:
+        _ * modelMapping.getProtocolType(EclipseProjectVersion3) >> EclipseProjectVersion3.class
+        1 * target.getModel(EclipseProjectVersion3.class, parameters) >> protocolModel
+        1 * adapter.adapt(EclipseProjectVersion3.class, _, _) >> protocolModel
+        1 * adapter.adapt(GradleProject.class, _) >> model
+        0 * target._
+    }
+
+    def "builds partial GradleBuild model using the Eclipse model for a 1.0-m3 provider"() {
+        metaData.version >> "1.0-milestone-3"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+        EclipseProjectVersion3 protocolModel = Stub()
+        GradleProject gradleProject = Stub()
+        GradleBuild model = Stub()
+
+        when:
+        def result = connection.run(GradleBuild.class, parameters)
+
+        then:
+        result == model
+
+        and:
+        _ * modelMapping.getProtocolType(EclipseProjectVersion3) >> EclipseProjectVersion3.class
+        1 * target.getModel(EclipseProjectVersion3.class, parameters) >> protocolModel
+        1 * adapter.adapt(EclipseProjectVersion3.class, _, _) >> protocolModel
+        1 * adapter.adapt(GradleProject.class, { it instanceof PartialGradleProject }) >> gradleProject
+        1 * adapter.adapt(GradleBuild.class, { it instanceof DefaultGradleBuild }) >> model
+        0 * adapter._
+        0 * target._
+    }
+
+    def "fails when unknown model is requested"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        connection.run(CustomModel.class, parameters)
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == /The version of Gradle you are using (1.0-milestone-5) does not support building a model of type 'CustomModel'. Support for building custom tooling models was added in Gradle 1.6 and is available in all later versions./
+    }
+
+    def "fails when unsupported model is requested"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        connection.run(ProjectOutcomes.class, parameters)
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == /The version of Gradle you are using (1.0-milestone-5) does not support building a model of type 'ProjectOutcomes'. Support for building 'ProjectOutcomes' models was added in Gradle 1.2 and is available in all later versions./
+    }
+
+    def "fails when both tasks and model requested"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        given:
+        parameters.tasks >> ['a']
+
+        when:
+        connection.run(GradleProject.class, parameters)
+
+        then:
+        UnsupportedOperationConfigurationException e = thrown()
+        e.message.startsWith("Unsupported configuration: modelBuilder.forTasks()")
+    }
+
+    def "fails when build action requested"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        given:
+        parameters.tasks >> ['a']
+
+        when:
+        connection.run(Stub(BuildAction), parameters)
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == /The version of Gradle you are using (1.0-milestone-5) does not support execution of build actions provided by the tooling API client. Support for this was added in Gradle 1.8 and is available in all later versions./
+    }
+
+    def "fails when stdin provided"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        given:
+        parameters.standardInput >> new ByteArrayInputStream("hi".bytes)
+
+        when:
+        connection.run(CustomModel.class, parameters)
+
+        then:
+        UnsupportedOperationConfigurationException e = thrown()
+        e.message.startsWith("Unsupported configuration: modelBuilder.setStandardInput()")
+    }
+
+    def "fails when Java home specified"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        given:
+        parameters.javaHome >> new File("java-home")
+
+        when:
+        connection.run(CustomModel.class, parameters)
+
+        then:
+        UnsupportedOperationConfigurationException e = thrown()
+        e.message.startsWith("Unsupported configuration: modelBuilder.setJavaHome()")
+    }
+
+    def "fails when JVM args specified"() {
+        metaData.version >> "1.0-milestone-5"
+        def connection = new ConnectionVersion4BackedConsumerConnection(target, modelMapping, adapter)
+
+        given:
+        parameters.jvmArguments >> ['-Dsome.arg']
+
+        when:
+        connection.run(CustomModel.class, parameters)
+
+        then:
+        UnsupportedOperationConfigurationException e = thrown()
+        e.message.startsWith("Unsupported configuration: modelBuilder.setJvmArguments()")
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/GradleBuildAdapterProducerTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/GradleBuildAdapterProducerTest.groovy
new file mode 100644
index 0000000..5755976
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/GradleBuildAdapterProducerTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails
+import org.gradle.tooling.internal.protocol.ModelBuilder
+import org.gradle.tooling.model.DomainObjectSet
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.gradle.GradleBuild
+import spock.lang.Specification
+
+class GradleBuildAdapterProducerTest extends Specification {
+    ProtocolToModelAdapter adapter = Mock(ProtocolToModelAdapter);
+    VersionDetails versionDetails = Mock(VersionDetails);
+    ModelMapping mapping = Mock(ModelMapping);
+    ModelBuilder builder = Mock(ModelBuilder);
+    ModelProducer delegate = Mock(ModelProducer)
+
+    GradleBuildAdapterProducer modelProducer = new GradleBuildAdapterProducer(adapter, versionDetails, mapping, delegate);
+
+    def "passes request to delegate when supported GradleBuild is requested"() {
+        setup:
+        1 * versionDetails.maySupportModel(GradleBuild.class) >> true
+        GradleBuild gradleBuild = Mock(GradleBuild)
+        ConsumerOperationParameters operationParameters = Mock(ConsumerOperationParameters)
+        when:
+        def model = modelProducer.produceModel(GradleBuild.class, operationParameters)
+        then:
+        1 * delegate.produceModel(GradleBuild, operationParameters) >> gradleBuild
+        model == gradleBuild
+    }
+
+    def "requests GradleProject on delegate when unsupported GradleBuild requested"() {
+        setup:
+        1 * versionDetails.maySupportModel(GradleBuild) >> false
+        GradleProject gradleProject = gradleProject()
+        ConsumerOperationParameters operationParameters = Mock(ConsumerOperationParameters)
+        adapter.adapt(GradleProject, gradleProject) >> gradleProject
+        adapter.adapt(GradleBuild, _) >> Mock(GradleBuild)
+        when:
+        def model = modelProducer.produceModel(GradleBuild, operationParameters)
+        then:
+        1 * delegate.produceModel(GradleProject, operationParameters) >> gradleProject
+        model instanceof GradleBuild
+    }
+
+    def "non GradleBuild model requests passed to delegate"() {
+        setup:
+        ConsumerOperationParameters operationParameters = Mock(ConsumerOperationParameters)
+        SomeModel someModel = new SomeModel()
+        when:
+        def returnValue = modelProducer.produceModel(SomeModel, operationParameters)
+        then:
+        1 * delegate.produceModel(SomeModel, operationParameters) >> someModel
+        returnValue == someModel
+        0 * versionDetails.maySupportModel(_)
+        0 * adapter.adapt(_, _)
+    }
+
+    def gradleProject() {
+        GradleProject gradleProject = Mock(GradleProject)
+        1 * gradleProject.children >> ([] as DomainObjectSet<GradleProject>)
+        1 * gradleProject.name >> "SomeProject"
+        1 * gradleProject.path >> ":"
+        gradleProject
+    }
+
+    static class SomeModel {
+    }
+}
+
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
index d5c0bf3..068aabf 100644
--- 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
@@ -15,28 +15,93 @@
  */
 package org.gradle.tooling.internal.consumer.connection
 
+import org.gradle.tooling.BuildAction
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.CustomModel
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.protocol.ConnectionMetaDataVersion1
 import org.gradle.tooling.internal.protocol.InternalConnection
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+import org.gradle.tooling.model.gradle.GradleBuild
+import org.gradle.tooling.model.idea.BasicIdeaProject
+import org.gradle.tooling.model.idea.IdeaProject
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
 import spock.lang.Specification
 
 class InternalConnectionBackedConsumerConnectionTest extends Specification {
-    final InternalConnection target = Mock()
+    final ConnectionMetaDataVersion1 metaData = Stub() {
+        getVersion() >> '1.0-milestone-8'
+    }
+    final InternalConnection target = Mock() {
+        getMetaData() >> metaData
+    }
     final ConsumerOperationParameters parameters = Mock()
-    final InternalConnectionBackedConsumerConnection connection = new InternalConnectionBackedConsumerConnection(target)
+    final ProtocolToModelAdapter adapter = Mock()
+    final ModelMapping modelMapping = Stub()
+    final InternalConnectionBackedConsumerConnection connection = new InternalConnectionBackedConsumerConnection(target, modelMapping, adapter)
+
+    def "describes capabilities of the provider"() {
+        given:
+        def details = connection.versionDetails
+
+        expect:
+        details.supportsGradleProjectModel()
+
+        and:
+        details.maySupportModel(HierarchicalEclipseProject)
+        details.maySupportModel(EclipseProject)
+        details.maySupportModel(IdeaProject)
+        details.maySupportModel(BasicIdeaProject)
+        details.maySupportModel(GradleProject)
+        details.maySupportModel(BuildEnvironment)
+        details.maySupportModel(Void)
+
+        and:
+        !details.maySupportModel(ProjectOutcomes)
+        !details.maySupportModel(CustomModel)
+        !details.maySupportModel(GradleBuild)
+    }
+
+    def "builds GradleBuild model by converting GradleProject"() {
+        def model = Stub(GradleBuild.class)
+        def gradleProject = Stub(GradleProject.class)
+        when:
+        def result = connection.run(GradleBuild.class, parameters)
+        then:
+        result == model
+        and:
+        _ * modelMapping.getProtocolType(GradleProject.class) >> GradleProject.class
+
+        1 * target.getTheModel(GradleProject.class, parameters) >> gradleProject
+        1 * adapter.adapt(GradleProject.class, gradleProject, _) >> gradleProject
+        1 * adapter.adapt(GradleBuild.class, _) >> model
+        0 * target._
+    }
+
+    def "builds model using connection's getTheModel() method"() {
+        def model = Stub(GradleProject)
 
-    def "builds model using getTheModel() method"() {
         when:
-        def result = connection.run(String.class, parameters)
+        def result = connection.run(GradleProject.class, parameters)
 
         then:
-        result == 'ok'
+        result == model
 
         and:
-        1 * target.getTheModel(String.class, parameters) >> 'ok'
+        _ * modelMapping.getProtocolType(GradleProject.class) >> Integer.class
+        1 * target.getTheModel(Integer.class, parameters) >> 12
+        1 * adapter.adapt(GradleProject.class, 12, _) >> model
         0 * target._
     }
 
-    def "runs build using executeBuild() method"() {
+    def "runs build using connection's executeBuild() method"() {
         when:
         connection.run(Void.class, parameters)
 
@@ -44,4 +109,37 @@ class InternalConnectionBackedConsumerConnectionTest extends Specification {
         1 * target.executeBuild(parameters, parameters)
         0 * target._
     }
+
+    def "fails when unknown model is requested"() {
+        when:
+        connection.run(CustomModel.class, parameters)
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == /The version of Gradle you are using (1.0-milestone-8) does not support building a model of type 'CustomModel'. Support for building custom tooling models was added in Gradle 1.6 and is available in all later versions./
+    }
+
+    def "fails when both tasks and model requested"() {
+        given:
+        parameters.tasks >> ['a']
+
+        when:
+        connection.run(GradleProject.class, parameters)
+
+        then:
+        UnsupportedOperationConfigurationException e = thrown()
+        e.message.startsWith("Unsupported configuration: modelBuilder.forTasks()")
+    }
+
+    def "fails when build action requested"() {
+        given:
+        parameters.tasks >> ['a']
+
+        when:
+        connection.run(Stub(BuildAction), parameters)
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == /The version of Gradle you are using (1.0-milestone-8) does not support execution of build actions provided by the tooling API client. Support for this was added in Gradle 1.8 and is available in all later versions./
+    }
 }
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
deleted file mode 100644
index 194a7b5..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConnectionTest.groovy
+++ /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.tooling.internal.consumer.connection
-
-import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.tooling.internal.consumer.Distribution
-import org.gradle.tooling.internal.consumer.LoggingProvider
-import org.gradle.tooling.internal.consumer.ModelProvider
-import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
-import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
-import spock.lang.Specification
-
-class LazyConnectionTest extends Specification {
-    final Distribution distribution = Mock()
-    final ToolingImplementationLoader implementationLoader = Mock()
-    final ConsumerOperationParameters params = Mock()
-    final ConsumerConnectionParameters connectionParams = Mock()
-    final ConsumerConnection consumerConnection = Mock()
-    final LoggingProvider loggingProvider = Mock()
-    final ProgressLoggerFactory progressLoggerFactory = Mock()
-    final LazyConnection connection = new LazyConnection(distribution, implementationLoader, loggingProvider, false)
-
-    static class SomeModel {}
-
-    def setup() {
-        connection.connectionParameters = connectionParams
-        connection.modelProvider = Mock(ModelProvider)
-    }
-
-    def createsConnectionOnDemandToBuildModel() {
-        when:
-        connection.run(SomeModel, params)
-
-        then:
-        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
-        1 * connection.modelProvider.provide(!null, SomeModel, params)
-        0 * _._
-    }
-
-    def reusesConnection() {
-        when:
-        connection.run(SomeModel, params)
-        connection.run(String, params)
-
-        then:
-        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
-        1 * connection.modelProvider.provide(consumerConnection, SomeModel, params)
-        1 * connection.modelProvider.provide(consumerConnection, String, params)
-        0 * _._
-    }
-
-    def stopsConnectionOnStop() {
-        when:
-        connection.run(SomeModel, params)
-        connection.stop()
-
-        then:
-        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
-        1 * connection.modelProvider.provide(consumerConnection, SomeModel, params)
-        1 * consumerConnection.stop()
-        0 * _._
-    }
-
-    def doesNotStopConnectionOnStopIfNotCreated() {
-        when:
-        connection.stop()
-
-        then:
-        0 * _._
-    }
-
-    def doesNotStopConnectionOnStopIfConnectionCouldNotBeCreated() {
-        def failure = new RuntimeException()
-
-        when:
-        connection.run(SomeModel, params)
-
-        then:
-        RuntimeException e = thrown()
-        e == failure
-        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> { throw failure }
-
-        when:
-        connection.stop()
-
-        then:
-        0 * _._
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConsumerActionExecutorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConsumerActionExecutorTest.groovy
new file mode 100644
index 0000000..faf3ab5
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConsumerActionExecutorTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.consumer.connection
+
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.ConnectionParameters
+import org.gradle.tooling.internal.consumer.Distribution
+import org.gradle.tooling.internal.consumer.LoggingProvider
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import spock.lang.Specification
+
+class LazyConsumerActionExecutorTest extends Specification {
+    final Distribution distribution = Mock()
+    final ToolingImplementationLoader implementationLoader = Mock()
+    final ConsumerOperationParameters params = Mock()
+    final ConsumerAction<String> action = Mock()
+    final ConnectionParameters connectionParams = Mock()
+    final ConsumerConnection consumerConnection = Mock()
+    final LoggingProvider loggingProvider = Mock()
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final LazyConsumerActionExecutor connection = new LazyConsumerActionExecutor(distribution, implementationLoader, loggingProvider, connectionParams)
+
+    def createsConnectionOnDemandToBuildModel() {
+        when:
+        connection.run(action)
+
+        then:
+        1 * loggingProvider.progressLoggerFactory >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
+        1 * action.run(consumerConnection)
+        0 * _._
+    }
+
+    def reusesConnection() {
+        def action2 = Mock(ConsumerAction)
+
+        when:
+        connection.run(action)
+        connection.run(action2)
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
+        1 * action.run(consumerConnection)
+        1 * action2.run(consumerConnection)
+        0 * _._
+    }
+
+    def stopsConnectionOnStop() {
+        when:
+        connection.run(action)
+        connection.stop()
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
+        1 * action.run(consumerConnection)
+        1 * consumerConnection.stop()
+        0 * _._
+    }
+
+    def doesNotStopConnectionOnStopIfNotCreated() {
+        when:
+        connection.stop()
+
+        then:
+        0 * _._
+    }
+
+    def doesNotStopConnectionOnStopIfConnectionCouldNotBeCreated() {
+        def failure = new RuntimeException()
+
+        when:
+        connection.run(action)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> { throw failure }
+
+        when:
+        connection.stop()
+
+        then:
+        0 * _._
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedConsumerConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedConsumerConnectionTest.groovy
new file mode 100644
index 0000000..e1719ca
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedConsumerConnectionTest.groovy
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BuildAction
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails
+import org.gradle.tooling.internal.protocol.*
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+import org.gradle.tooling.model.gradle.BuildInvocations
+import org.gradle.tooling.model.gradle.GradleBuild
+import org.gradle.tooling.model.idea.BasicIdeaProject
+import org.gradle.tooling.model.idea.IdeaProject
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import org.gradle.util.GradleVersion
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ModelBuilderBackedConsumerConnectionTest extends Specification {
+    final metaData = Stub(ConnectionMetaDataVersion1)
+    final parameters = Stub(ConsumerOperationParameters)
+    final target = Mock(TestModelBuilder) {
+        getMetaData() >> metaData
+    }
+    final adapter = Mock(ProtocolToModelAdapter)
+    final modelMapping = Mock(ModelMapping)
+
+    def "describes capabilities of a pre 1.8-rc-1 provider"() {
+        given:
+        metaData.version >> "1.2"
+        def connection = new ModelBuilderBackedConsumerConnection(target, modelMapping, adapter)
+        def details = connection.versionDetails
+
+        expect:
+        details.supportsGradleProjectModel()
+
+        and:
+        details.maySupportModel(HierarchicalEclipseProject)
+        details.maySupportModel(EclipseProject)
+        details.maySupportModel(IdeaProject)
+        details.maySupportModel(BasicIdeaProject)
+        details.maySupportModel(GradleProject)
+        details.maySupportModel(BuildEnvironment)
+        details.maySupportModel(ProjectOutcomes)
+        details.maySupportModel(Void)
+        details.maySupportModel(CustomModel)
+
+        and:
+        !details.maySupportModel(GradleBuild)
+    }
+
+    def "describes capabilities of a post 1.8-rc-1 provider"() {
+        given:
+        metaData.version >> "1.8-rc-1"
+        def connection = new ModelBuilderBackedConsumerConnection(target, modelMapping, adapter)
+        def details = connection.versionDetails
+
+        expect:
+        details.supportsGradleProjectModel()
+
+        and:
+        details.maySupportModel(HierarchicalEclipseProject)
+        details.maySupportModel(EclipseProject)
+        details.maySupportModel(IdeaProject)
+        details.maySupportModel(BasicIdeaProject)
+        details.maySupportModel(GradleProject)
+        details.maySupportModel(BuildEnvironment)
+        details.maySupportModel(ProjectOutcomes)
+        details.maySupportModel(Void)
+        details.maySupportModel(CustomModel)
+        details.maySupportModel(GradleBuild)
+    }
+
+    def "maps model type to model identifier"() {
+        def modelIdentifier = Stub(ModelIdentifier)
+
+        given:
+        metaData.version >> "1.2"
+        def connection = new ModelBuilderBackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        connection.run(GradleProject, parameters)
+
+        then:
+        1 * modelMapping.getModelIdentifierFromModelType(GradleProject) >> modelIdentifier
+        1 * target.getModel(modelIdentifier, parameters) >> Stub(BuildResult)
+    }
+
+    def "maps internal unknown model exception to API exception"() {
+        def modelIdentifier = Stub(ModelIdentifier)
+
+        given:
+        _ * modelMapping.getModelIdentifierFromModelType(GradleProject) >> modelIdentifier
+        _ * target.getModel(modelIdentifier, parameters) >> { throw new InternalUnsupportedModelException() }
+        _ * metaData.version >> "1.2"
+        def connection = new ModelBuilderBackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        connection.run(GradleProject, parameters)
+
+        then:
+        UnknownModelException e = thrown()
+        e.message == /No model of type 'GradleProject' is available in this build./
+    }
+
+    def "builds GradleBuild model by converting GradleProject"() {
+        def modelIdentifier = Stub(ModelIdentifier)
+        def model = Stub(GradleBuild.class)
+        def gradleProject = Stub(GradleProject.class)
+
+        given:
+        _ * metaData.version >> "1.2"
+        _ * modelMapping.getModelIdentifierFromModelType(GradleProject) >> modelIdentifier
+        def connection = new ModelBuilderBackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        def result = connection.run(GradleBuild.class, parameters)
+
+        then:
+        result == model
+
+        and:
+        1 * target.getModel(modelIdentifier, parameters) >> Stub(BuildResult) { getModel() >> gradleProject }
+        1 * adapter.adapt(GradleProject.class, gradleProject, _) >> gradleProject
+        1 * adapter.adapt(GradleBuild.class, _) >> model
+        0 * target._
+    }
+
+    def "fails when build action requested"() {
+        given:
+        parameters.tasks >> ['a']
+        metaData.version >> "1.2"
+        def connection = new ModelBuilderBackedConsumerConnection(target, modelMapping, adapter)
+
+        when:
+        connection.run(Stub(BuildAction), parameters)
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == /The version of Gradle you are using (1.2) does not support execution of build actions provided by the tooling API client. Support for this was added in Gradle 1.8 and is available in all later versions./
+    }
+
+    interface TestModelBuilder extends ModelBuilder, ConnectionVersion4, ConfigurableConnection {
+    }
+
+    @Unroll('VersionDetails for #versionString support expected models')
+    def "VersionDetails supports expected models"() {
+        when:
+        VersionDetails version = ModelBuilderBackedConsumerConnection.getVersionDetails(versionString)
+        def gradleVersion = GradleVersion.version(versionString)
+
+        then:
+        version.maySupportModel(GradleBuild) == gradleVersion.compareTo(GradleVersion.version("1.8")) >= 0
+        version.maySupportModel(BuildInvocations) == gradleVersion.compareTo(GradleVersion.version("1.11")) > 0
+        version.supportsGradleProjectModel() == gradleVersion.compareTo(GradleVersion.version("1.6")) >= 0
+        version.maySupportModel(ModelBuilderBackedConsumerConnectionTest.CustomModel)
+
+        where:
+        versionString << ['1.6', '1.7', '1.8', '1.9', '1.10', '1.11', '1.12']
+    }
+
+    static class CustomModel {
+
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedModelProducerTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedModelProducerTest.groovy
new file mode 100644
index 0000000..978c322
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ModelBuilderBackedModelProducerTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.UnknownModelException
+import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails
+import org.gradle.tooling.internal.protocol.BuildResult
+import org.gradle.tooling.internal.protocol.ModelBuilder
+import org.gradle.tooling.internal.protocol.ModelIdentifier
+import spock.lang.Specification
+
+class ModelBuilderBackedModelProducerTest extends Specification {
+
+    ProtocolToModelAdapter adapter = Mock(ProtocolToModelAdapter);
+    VersionDetails versionDetails = Mock(VersionDetails);
+    ModelMapping mapping = Mock(ModelMapping);
+    ModelBuilder builder = Mock(ModelBuilder);
+
+    ModelBuilderBackedModelProducer modelProducer = new ModelBuilderBackedModelProducer(adapter, versionDetails, mapping, builder);
+
+    def setup() {
+        _ * versionDetails.getVersion() >> "X.Y"
+
+    }
+
+    def "builder not triggered for unsupported Models"() {
+        setup:
+        1 * versionDetails.maySupportModel(SomeModel.class) >> false
+        when:
+        modelProducer.produceModel(SomeModel.class, Mock(ConsumerOperationParameters))
+        then:
+        0 * builder.getModel(_, _)
+        def e = thrown(UnknownModelException)
+        e.message == "The version of Gradle you are using (X.Y) does not support building a model of type 'SomeModel'. Support for building custom tooling models was added in Gradle 1.6 and is available in all later versions."
+    }
+
+    def "builder triggered for supported Models"() {
+        setup:
+        SomeModel returnValue = new SomeModel()
+        1 * versionDetails.maySupportModel(SomeModel.class) >> true
+        ModelIdentifier someModelIdentifier = Mock(ModelIdentifier)
+        1 * mapping.getModelIdentifierFromModelType(SomeModel.class) >> someModelIdentifier
+        BuildResult buildResult = Mock(BuildResult)
+        ConsumerOperationParameters operationParameters = Mock(ConsumerOperationParameters)
+        when:
+        SomeModel model = modelProducer.produceModel(SomeModel.class, operationParameters)
+        then:
+        1 * builder.getModel(someModelIdentifier, operationParameters) >> buildResult
+        1 * buildResult.model >> returnValue
+        1 * adapter.adapt(SomeModel.class, returnValue, _) >> returnValue
+        model != null
+    }
+
+    static class SomeModel {
+
+    }
+}
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
deleted file mode 100644
index 5c60bd2..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnectionTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.consumer.connection
-
-import org.gradle.listener.ListenerManager
-import org.gradle.logging.ProgressLogger
-import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.tooling.internal.consumer.LoggingProvider
-import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
-import org.gradle.tooling.internal.protocol.ProgressListenerVersion1
-import spock.lang.Specification
-
-class ProgressLoggingConnectionTest extends Specification {
-    final ConsumerConnection target = Mock()
-    final ConsumerOperationParameters params = Mock()
-    final ProgressListenerVersion1 listener = Mock()
-    final ProgressLogger progressLogger = Mock()
-    final ProgressLoggerFactory progressLoggerFactory = Mock()
-    final ListenerManager listenerManager = Mock()
-    final LoggingProvider loggingProvider = Mock()
-    final ProgressLoggingConnection connection = new ProgressLoggingConnection(target, loggingProvider)
-
-    static class SomeModel {}
-
-    def notifiesProgressListenerOfStartAndEndOfFetchingModel() {
-        when:
-        connection.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('Build')
-        1 * progressLogger.started()
-        1 * target.run(SomeModel, params)
-        1 * progressLogger.completed()
-        1 * listenerManager.removeListener(!null)
-        _ * params.progressListener >> listener
-        0 * _._
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConsumerActionExecutorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConsumerActionExecutorTest.groovy
new file mode 100644
index 0000000..e54567e
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConsumerActionExecutorTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.internal.consumer.connection
+
+import org.gradle.listener.ListenerManager
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.LoggingProvider
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1
+import spock.lang.Specification
+
+class ProgressLoggingConsumerActionExecutorTest extends Specification {
+    final ConsumerActionExecutor target = Mock()
+    final ConsumerAction<String> action = Mock()
+    final ConsumerOperationParameters params = Mock()
+    final ProgressListenerVersion1 listener = Mock()
+    final ProgressLogger progressLogger = Mock()
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final ListenerManager listenerManager = Mock()
+    final LoggingProvider loggingProvider = Mock()
+    final ProgressLoggingConsumerActionExecutor connection = new ProgressLoggingConsumerActionExecutor(target, loggingProvider)
+
+    def notifiesProgressListenerOfStartAndEndOfFetchingModel() {
+        when:
+        connection.run(action)
+
+        then:
+        1 * loggingProvider.listenerManager >> listenerManager
+        1 * loggingProvider.progressLoggerFactory >> progressLoggerFactory
+        1 * listenerManager.addListener(!null)
+        1 * progressLoggerFactory.newOperation(ProgressLoggingConsumerActionExecutor.class) >> progressLogger
+        1 * progressLogger.setDescription('Build')
+        1 * progressLogger.started()
+        1 * target.run(action)
+        1 * progressLogger.completed()
+        1 * listenerManager.removeListener(!null)
+        _ * params.progressListener >> listener
+        _ * action.parameters >> params
+        0 * _._
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/BuildInvocationsConverterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/BuildInvocationsConverterTest.groovy
new file mode 100644
index 0000000..42c4a1e
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/BuildInvocationsConverterTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters
+
+import org.gradle.tooling.internal.gradle.BasicGradleTaskSelector
+import org.gradle.tooling.internal.gradle.DefaultBuildInvocations
+import org.gradle.tooling.model.DomainObjectSet
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.GradleTask
+import spock.lang.Specification
+
+class BuildInvocationsConverterTest extends Specification {
+    def "converts empty single project"() {
+        setup:
+        GradleProject project = gradleProject()
+        _ * project.children >> ([] as DomainObjectSet)
+        _ * project.tasks >> ([] as DomainObjectSet)
+        when:
+        DefaultBuildInvocations builds = new BuildInvocationsConverter().convert(project)
+        then:
+        builds.selectors.isEmpty()
+    }
+
+    def "converts child projects"() {
+        setup:
+        GradleProject rootProject = gradleProject()
+        GradleProject sub1 = gradleProject("sub1", ":sub1")
+        GradleTask sub1T1 = gradleTask('t1', ':sub1:t1')
+        _ * sub1.children >> ([] as DomainObjectSet)
+        _ * sub1.tasks >> ([sub1T1] as DomainObjectSet)
+        _ * rootProject.children >> ([sub1] as DomainObjectSet)
+        _ * rootProject.tasks >> ([] as DomainObjectSet)
+        when:
+        DefaultBuildInvocations builds = new BuildInvocationsConverter().convert(rootProject)
+        then:
+        builds.taskSelectors.size() == 1
+        builds.taskSelectors*.name as Set == ['t1'] as Set
+    }
+
+    def "builds model with all selectors"() {
+        setup:
+        def project = gradleProject()
+        def child1 = gradleProject("child1", ":child1", project)
+        def child1a = gradleProject("child1a", ":child1:child1a", child1)
+        def child1b = gradleProject("child1b", ":child1:child1b", child1)
+        GradleTask t1a = gradleTask('t1', ':child1:child1a:t1')
+        GradleTask t1b = gradleTask('t1', ':child1:child1b:t1')
+        GradleTask t2b = gradleTask('t2', ':child1:child1b:t2')
+        _ * child1a.tasks >> ([t1a] as DomainObjectSet)
+        _ * child1b.tasks >> ([t1b, t2b] as DomainObjectSet)
+        _ * child1.tasks >> ([] as DomainObjectSet)
+        _ * project.tasks >> ([] as DomainObjectSet)
+        _ * child1a.children >> ([] as DomainObjectSet)
+        _ * child1b.children >> ([] as DomainObjectSet)
+        _ * child1.children >> ([child1a, child1b] as DomainObjectSet)
+        _ * project.children >> ([child1] as DomainObjectSet)
+
+        when:
+        DefaultBuildInvocations builds = new BuildInvocationsConverter().convert(project)
+
+        then:
+        builds.taskSelectors.size() == 2
+        builds.taskSelectors.find { BasicGradleTaskSelector it ->
+            it.name == 't1'
+        }?.taskNames == [':child1:child1a:t1', ':child1:child1b:t1'] as Set
+        builds.taskSelectors.find { BasicGradleTaskSelector it ->
+            it.name == 't2'
+        }?.taskNames == [':child1:child1b:t2'] as Set
+        builds.taskSelectors*.name.each { it != null }
+        builds.taskSelectors*.description.each { it != null }
+        builds.taskSelectors*.displayName.each { it != null }
+    }
+
+    GradleProject gradleProject(projectName = "rootProject", path = ":", parent = null) {
+        GradleProject gradleProject = Mock(GradleProject)
+        _ * gradleProject.path >> path
+        _ * gradleProject.name >> projectName
+        _ * gradleProject.parent >> parent
+        gradleProject
+    }
+    GradleTask gradleTask(name, path) {
+        GradleTask task = Mock(GradleTask)
+        _ * task.name >> name
+        _ * task.path >> path
+        task
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/GradleBuildConverterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/GradleBuildConverterTest.groovy
new file mode 100644
index 0000000..04c4f6f
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/GradleBuildConverterTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters
+
+import org.gradle.tooling.internal.gradle.DefaultGradleBuild
+import org.gradle.tooling.model.DomainObjectSet
+import org.gradle.tooling.model.GradleProject
+import spock.lang.Specification
+
+class GradleBuildConverterTest extends Specification {
+
+    DefaultGradleBuild gradleBuild;
+
+    def "converts rootproject"() {
+        setup:
+        GradleProject project = gradleProject()
+        _ * project.children >> ([] as DomainObjectSet)
+        when:
+
+        gradleBuild = new GradleBuildConverter().convert(project)
+        then:
+        rootProjectMapped()
+        gradleBuild.rootProject.children.size() == 0
+    }
+
+    def "converts child projects"() {
+        setup:
+        GradleProject rootProject = gradleProject()
+        GradleProject sub1 = gradleProject("sub1", ":sub1")
+        GradleProject sub2 = gradleProject("sub2", ":sub2")
+        _ * sub1.children >> ([] as DomainObjectSet)
+        _ * sub2.children >> ([] as DomainObjectSet)
+        _ * rootProject.children >> ([sub1, sub2] as DomainObjectSet)
+        when:
+        gradleBuild = new GradleBuildConverter().convert(rootProject)
+        then:
+        rootProjectMapped()
+        gradleBuild.rootProject.children.size() == 2
+        gradleBuild.rootProject.children*.name == ["sub1", "sub2"]
+        gradleBuild.rootProject.children*.path == [":sub1", ":sub2"]
+    }
+
+    def "converts nested child projects"() {
+        setup:
+        GradleProject rootProject = gradleProject()
+        GradleProject sub1 = gradleProject("sub1", ":sub1")
+        GradleProject sub2 = gradleProject("sub2", ":sub1:sub2")
+        _ * sub2.children >> ([] as DomainObjectSet)
+        _ * sub1.children >> ([sub2] as DomainObjectSet)
+        _ * rootProject.children >> ([sub1] as DomainObjectSet)
+        when:
+        gradleBuild = new GradleBuildConverter().convert(rootProject)
+        then:
+        rootProjectMapped()
+        gradleBuild.rootProject.children.size() == 1
+        gradleBuild.rootProject.children*.name == ["sub1"]
+        gradleBuild.rootProject.children*.path == [":sub1"]
+        gradleBuild.rootProject.children.asList()[0].children.size() == 1
+        gradleBuild.rootProject.children.asList()[0].children*.name == ['sub2']
+        gradleBuild.rootProject.children.asList()[0].children*.path == [':sub1:sub2']
+    }
+
+    def rootProjectMapped() {
+        assert gradleBuild.rootProject.name == "rootProject"
+        assert gradleBuild.rootProject.path == ":"
+        gradleBuild
+    }
+
+    GradleProject gradleProject(projectName = "rootProject", path = ":") {
+        GradleProject gradleProject = Mock(GradleProject)
+        _ * gradleProject.path >> path
+        _ * gradleProject.name >> projectName
+        gradleProject
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/TaskNameComparatorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/TaskNameComparatorTest.groovy
new file mode 100644
index 0000000..dc6b069
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/converters/TaskNameComparatorTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.consumer.converters
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class TaskNameComparatorTest extends Specification {
+    @Unroll('compares task names #first and #second')
+    def compare() {
+        def comparator = new TaskNameComparator()
+
+        expect:
+        comparator.compare(first, second) < 0
+        comparator.compare(second, first) > 0
+        comparator.compare(first, first) == 0
+        comparator.compare(second, second) == 0
+
+        where:
+        first | second
+        ':t1' | ':t2'
+        ':a:t1' | ':a:t2'
+        ':a:t1' | ':a1:t1'
+        ':t1' | ':a:t2'
+        ':b:t2' | ':b:c:t2'
+    }
+}
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 553949e..8db5b02 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
@@ -15,22 +15,23 @@
  */
 package org.gradle.tooling.internal.consumer.loader
 
+import org.gradle.internal.classpath.DefaultClassPath
 import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.ConnectionParameters
 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 ConnectionParameters params = Mock()
     final CachingToolingImplementationLoader loader = new CachingToolingImplementationLoader(target)
 
     def delegatesToTargetLoaderToCreateImplementation() {
         final Distribution distribution = Mock()
         final ConsumerConnection connection = Mock()
+        final File userHomeDir = Mock()
 
         when:
         def impl = loader.create(distribution, loggerFactory, params)
@@ -38,13 +39,15 @@ class CachingToolingImplementationLoaderTest extends Specification {
         then:
         impl == connection
         1 * target.create(distribution, loggerFactory, params) >> connection
-        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('a.jar'))
+        1 * params.getGradleUserHomeDir() >> userHomeDir
+        _ * distribution.getToolingImplementationClasspath(loggerFactory, userHomeDir) >> new DefaultClassPath(new File('a.jar'))
         0 * _._
     }
 
     def reusesImplementationWithSameClasspath() {
         final Distribution distribution = Mock()
         final ConsumerConnection connection = Mock()
+        final File userHomeDir = Mock()
 
         when:
         def impl = loader.create(distribution, loggerFactory, params)
@@ -54,7 +57,8 @@ class CachingToolingImplementationLoaderTest extends Specification {
         impl == connection
         impl2 == connection
         1 * target.create(distribution, loggerFactory, params) >> connection
-        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> { new DefaultClassPath(new File('a.jar')) }
+        2 * params.getGradleUserHomeDir() >> userHomeDir
+        _ * distribution.getToolingImplementationClasspath(loggerFactory, userHomeDir) >> { new DefaultClassPath(new File('a.jar')) }
         0 * _._
     }
 
@@ -73,8 +77,9 @@ class CachingToolingImplementationLoaderTest extends Specification {
         impl2 == 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'))
+        2 * params.getGradleUserHomeDir() >> null
+        _ * distribution1.getToolingImplementationClasspath(loggerFactory, null) >> new DefaultClassPath(new File('a.jar'))
+        _ * distribution2.getToolingImplementationClasspath(loggerFactory, null) >> 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 7b87549..1e42e04 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,32 +15,35 @@
  */
 package org.gradle.tooling.internal.consumer.loader
 
+import org.gradle.internal.classloader.ClasspathUtil
 import org.gradle.internal.classpath.DefaultClassPath
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.messaging.actor.ActorFactory
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-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.consumer.connection.*
+import org.gradle.tooling.internal.consumer.ConnectionParameters
 import org.gradle.tooling.internal.protocol.*
-import org.gradle.util.ClasspathUtil
+import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException
 import org.gradle.util.GradleVersion
 import org.junit.Rule
 import org.slf4j.Logger
 import spock.lang.Specification
 
 class DefaultToolingImplementationLoaderTest extends Specification {
-    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     Distribution distribution = Mock()
     ProgressLoggerFactory loggerFactory = Mock()
+    ConnectionParameters connectionParameters = Stub() {
+        getVerboseLogging() >> true
+    }
+    File userHomeDir = Mock()
+    final loader = new DefaultToolingImplementationLoader()
 
     def "locates connection implementation using meta-inf service then instantiates and configures the connection"() {
         given:
-        def loader = new DefaultToolingImplementationLoader()
-        distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(
+        distribution.getToolingImplementationClasspath(loggerFactory, userHomeDir) >> new DefaultClassPath(
                 getToolingApiResourcesDir(connectionImplementation),
                 ClasspathUtil.getClasspathForClass(TestConnection.class),
                 ClasspathUtil.getClasspathForClass(ActorFactory.class),
@@ -50,7 +53,7 @@ class DefaultToolingImplementationLoaderTest extends Specification {
                 ClasspathUtil.getClasspathForClass(GradleVersion.class))
 
         when:
-        def adaptedConnection = loader.create(distribution, loggerFactory, new ConsumerConnectionParameters(true))
+        def adaptedConnection = loader.create(distribution, loggerFactory, connectionParameters)
 
         then:
         adaptedConnection.delegate.class != connectionImplementation //different classloaders
@@ -61,10 +64,12 @@ class DefaultToolingImplementationLoaderTest extends Specification {
         adaptedConnection.class == adapter
 
         where:
-        connectionImplementation      | adapter
-        TestConnection.class          | BuildActionRunnerBackedConsumerConnection.class
-        TestOldConnection.class       | InternalConnectionBackedConsumerConnection.class
-        TestEvenOlderConnection.class | AdaptedConnection.class
+        connectionImplementation  | adapter
+        TestConnection.class      | ActionAwareConsumerConnection.class
+        TestR16Connection.class   | ModelBuilderBackedConsumerConnection.class
+        TestR12Connection.class   | BuildActionRunnerBackedConsumerConnection.class
+        TestR10M8Connection.class | InternalConnectionBackedConsumerConnection.class
+        TestR10M3Connection.class | ConnectionVersion4BackedConsumerConnection.class
     }
 
     private getToolingApiResourcesDir(Class implementation) {
@@ -76,78 +81,65 @@ class DefaultToolingImplementationLoaderTest extends Specification {
         return ClasspathUtil.getClasspathForResource(getClass().classLoader, "org/gradle/build-receipt.properties")
     }
 
-    def failsWhenNoImplementationDeclared() {
-        ClassLoader cl = new ClassLoader() {}
-        def loader = new DefaultToolingImplementationLoader(cl)
+    def "creates broken connection when resource not found"() {
+        def loader = new DefaultToolingImplementationLoader()
 
-        when:
-        loader.create(distribution, loggerFactory, new ConsumerConnectionParameters(true))
+        given:
+        distribution.getToolingImplementationClasspath(loggerFactory, userHomeDir) >> new DefaultClassPath()
 
-        then:
-        UnsupportedVersionException e = thrown()
-        e.message == "The specified <dist-display-name> is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
-        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath()
-        _ * distribution.displayName >> '<dist-display-name>'
+        expect:
+        loader.create(distribution, loggerFactory, connectionParameters) instanceof NoToolingApiConnection
     }
 }
 
-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()
+class TestMetaData implements ConnectionMetaDataVersion1 {
+    String getVersion() {
+        return "1.1"
     }
 
-    void stop() {
-        throw new UnsupportedOperationException()
-    }
-
-    ConnectionMetaDataVersion1 getMetaData() {
+    String getDisplayName() {
         throw new UnsupportedOperationException()
     }
+}
 
-    ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
+class TestConnection extends TestR16Connection implements InternalBuildActionExecutor {
+    def <T> BuildResult<T> run(InternalBuildAction<T> action, BuildParameters operationParameters) throws BuildExceptionVersion1, InternalUnsupportedBuildArgumentException, IllegalStateException {
         throw new UnsupportedOperationException()
     }
+}
 
-    void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
+class TestR16Connection extends TestR12Connection implements ModelBuilder {
+    BuildResult<Object> getModel(ModelIdentifier modelIdentifier, BuildParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
         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()
+class TestR12Connection extends TestR10M8Connection implements BuildActionRunner, ConfigurableConnection {
+    void configure(org.gradle.tooling.internal.protocol.ConnectionParameters parameters) {
+        configured = parameters.verboseLogging
     }
 
-    void stop() {
+    @Override
+    void configureLogging(boolean verboseLogging) {
         throw new UnsupportedOperationException()
     }
 
-    ConnectionMetaDataVersion1 getMetaData() {
+    def <T> BuildResult<T> run(Class<T> type, BuildParameters parameters) {
         throw new UnsupportedOperationException()
     }
+}
 
-    ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
+class TestR10M8Connection extends TestR10M3Connection implements InternalConnection {
+    def <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 operationParameters) {
         throw new UnsupportedOperationException()
     }
 
-    void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
-        throw new UnsupportedOperationException()
+    void configureLogging(boolean verboseLogging) {
+        configured = verboseLogging
     }
 }
 
-class TestEvenOlderConnection implements ConnectionVersion4 {
+class TestR10M3Connection implements ConnectionVersion4 {
     boolean configured
 
     void configureLogging(boolean verboseLogging) {
@@ -159,7 +151,7 @@ class TestEvenOlderConnection implements ConnectionVersion4 {
     }
 
     ConnectionMetaDataVersion1 getMetaData() {
-        throw new UnsupportedOperationException()
+        return new TestMetaData()
     }
 
     ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
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 1fec08d..49ee0de 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
@@ -19,22 +19,19 @@ package org.gradle.tooling.internal.consumer.loader
 import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.test.fixtures.ConcurrentTestUtil
+import org.gradle.tooling.internal.consumer.ConnectionParameters
 import org.gradle.tooling.internal.consumer.Distribution
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
 import spock.lang.Specification
 
 import java.util.concurrent.locks.Lock
 import java.util.concurrent.locks.ReentrantLock
 
-/**
- * by Szczepan Faber, created at: 12/15/11
- */
 public class SynchronizedToolingImplementationLoaderTest extends Specification {
 
     def factory = Mock(ProgressLoggerFactory)
     def distro = Mock(Distribution)
     def logger = Mock(ProgressLogger)
-    def params = Mock(ConsumerConnectionParameters)
+    def params = Mock(ConnectionParameters)
 
     def loader = new SynchronizedToolingImplementationLoader(Mock(ToolingImplementationLoader))
 
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy
index fd7f727..0725563 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy
@@ -18,31 +18,28 @@ package org.gradle.tooling.internal.consumer.parameters
 
 import spock.lang.Specification
 
-/**
- * by Szczepan Faber, created at: 3/12/12
- */
 class ConsumerOperationParametersTest extends Specification {
     
-    def params = new ConsumerOperationParameters(null)
+    def params = ConsumerOperationParameters.builder()
     
     def "null or empty arguments have the same meaning"() {
         when:
         params.arguments = null
 
         then:
-        params.arguments == null
+        params.build().arguments == null
 
         when:
         params.arguments = []
 
         then:
-        params.arguments == null
+        params.build().arguments == null
 
         when:
         params.arguments = ['-Dfoo']
 
         then:
-        params.arguments == ['-Dfoo']
+        params.build().arguments == ['-Dfoo']
     }
 
     def "null or empty jvm arguments have the same meaning"() {
@@ -50,18 +47,18 @@ class ConsumerOperationParametersTest extends Specification {
         params.jvmArguments = null
 
         then:
-        params.jvmArguments == null
+        params.build().jvmArguments == null
 
         when:
         params.jvmArguments = []
 
         then:
-        params.jvmArguments == null
+        params.build().jvmArguments == null
 
         when:
         params.jvmArguments = ['-Xmx']
 
         then:
-        params.jvmArguments == ['-Xmx']
+        params.build().jvmArguments == ['-Xmx']
     }
 }
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
deleted file mode 100644
index add0d55..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapterTest.groovy
+++ /dev/null
@@ -1,271 +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.idea.DefaultIdeaModuleDependency
-import org.gradle.tooling.internal.idea.DefaultIdeaSingleEntryLibraryDependency
-import org.gradle.tooling.model.UnsupportedMethodException
-import org.gradle.tooling.model.idea.IdeaDependency
-import org.gradle.tooling.model.idea.IdeaModuleDependency
-import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency
-import org.gradle.util.Matchers
-import spock.lang.Specification
-import org.gradle.tooling.internal.consumer.*
-
-/**
- * by Szczepan Faber, created at: 4/2/12
- */
-class ProtocolToModelAdapterTest extends Specification {
-    final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter()
-
-    def createsProxyAdapterForProtocolModel() {
-        TestProtocolModel protocolModel = Mock()
-
-        expect:
-        adapter.adapt(TestModel.class, protocolModel) instanceof TestModel
-    }
-
-    def proxiesAreEqualWhenTargetProtocolObjectsAreEqual() {
-        TestProtocolModel protocolModel1 = Mock()
-        TestProtocolModel protocolModel2 = Mock()
-
-        def model = adapter.adapt(TestModel.class, protocolModel1)
-        def equal = adapter.adapt(TestModel.class, protocolModel1)
-        def different = adapter.adapt(TestModel.class, protocolModel2)
-
-        expect:
-        Matchers.strictlyEquals(model, equal)
-        model != different
-    }
-
-    def methodInvocationOnModelDelegatesToTheProtocolModelObject() {
-        TestProtocolModel protocolModel = Mock()
-        _ * protocolModel.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.name == 'name'
-    }
-
-    def createsProxyAdapterForMethodReturnValue() {
-        TestProtocolModel protocolModel = Mock()
-        TestProtocolProject protocolProject = Mock()
-        _ * protocolModel.getProject() >> protocolProject
-        _ * protocolProject.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project instanceof TestProject
-        model.project.name == 'name'
-    }
-
-    def doesNotAdaptNullReturnValue() {
-        TestProtocolModel protocolModel = Mock()
-        _ * protocolModel.getProject() >> null
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project == null
-    }
-
-    def adaptsIterableToDomainObjectSet() {
-        TestProtocolModel protocolModel = Mock()
-        TestProtocolProject protocolProject = Mock()
-        _ * protocolModel.getChildren() >> [protocolProject]
-        _ * protocolProject.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.children.size() == 1
-        model.children[0] instanceof TestProject
-        model.children[0].name == 'name'
-    }
-
-    def cachesPropertyValues() {
-        TestProtocolModel protocolModel = Mock()
-        TestProtocolProject protocolProject = Mock()
-        _ * protocolModel.getProject() >> protocolProject
-        _ * protocolModel.getChildren() >> [protocolProject]
-        _ * protocolProject.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project.is(model.project)
-        model.children.is(model.children)
-    }
-
-    def reportsMethodWhichDoesNotExistOnProtocolObject() {
-        PartialTestProtocolModel protocolModel = Mock()
-
-        when:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project
-
-        then:
-        UnsupportedMethodException e = thrown()
-        e.message.contains "TestModel.getProject()"
-    }
-
-    def propagatesExceptionThrownByProtocolObject() {
-        TestProtocolModel protocolModel = Mock()
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.name
-
-        then:
-        protocolModel.name >> { throw failure }
-        RuntimeException e = thrown()
-        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)
-        def module  = adapter.adapt(IdeaDependency.class, moduleDep)
-
-        then:
-        library instanceof IdeaSingleEntryLibraryDependency
-        module instanceof IdeaModuleDependency
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/versioning/ModelMappingTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/versioning/ModelMappingTest.groovy
new file mode 100644
index 0000000..f8b7d58
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/versioning/ModelMappingTest.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.tooling.internal.consumer.versioning
+
+import org.gradle.tooling.internal.protocol.*
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3
+import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1
+import org.gradle.tooling.model.gradle.GradleBuild
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+import org.gradle.tooling.model.idea.BasicIdeaProject
+import org.gradle.tooling.model.idea.IdeaProject
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import spock.lang.Specification
+
+class ModelMappingTest extends Specification {
+    final mapping = new ModelMapping()
+
+    def "maps model type to protocol type"() {
+        expect:
+        mapping.getProtocolType(modelType) == protocolType
+
+        where:
+        modelType                  | protocolType
+        Void                       | Void
+        HierarchicalEclipseProject | HierarchicalEclipseProjectVersion1
+        EclipseProject             | EclipseProjectVersion3
+        IdeaProject                | InternalIdeaProject
+        GradleProject              | InternalGradleProject
+        BasicIdeaProject           | InternalBasicIdeaProject
+        BuildEnvironment           | InternalBuildEnvironment
+        ProjectOutcomes            | InternalProjectOutcomes
+    }
+
+    def "can use a protocol type as model type"() {
+        expect:
+        mapping.getProtocolType(modelType) == modelType
+
+        where:
+        modelType << [
+                HierarchicalEclipseProjectVersion1,
+                EclipseProjectVersion3,
+                InternalIdeaProject,
+                InternalGradleProject,
+                InternalBasicIdeaProject,
+                InternalBuildEnvironment,
+                InternalProjectOutcomes
+        ]
+    }
+
+    def "uses null protocol type for custom model type"() {
+        expect:
+        mapping.getProtocolType(CustomModel) == null
+    }
+
+    def "uses null protocol type for model types added after 1.6"() {
+        expect:
+        mapping.getProtocolType(GradleBuild) == null
+    }
+
+    def "maps model type to model identifier"() {
+        expect:
+        def id = mapping.getModelIdentifierFromModelType(modelType)
+        id.name == modelName
+
+        where:
+        modelType                  | modelName
+        Void                       | ModelIdentifier.NULL_MODEL
+        HierarchicalEclipseProject | "org.gradle.tooling.model.eclipse.HierarchicalEclipseProject"
+        EclipseProject             | "org.gradle.tooling.model.eclipse.EclipseProject"
+        IdeaProject                | "org.gradle.tooling.model.idea.IdeaProject"
+        GradleProject              | "org.gradle.tooling.model.GradleProject"
+        BasicIdeaProject           | "org.gradle.tooling.model.idea.BasicIdeaProject"
+        BuildEnvironment           | "org.gradle.tooling.model.build.BuildEnvironment"
+        ProjectOutcomes            | "org.gradle.tooling.model.outcomes.ProjectOutcomes"
+        GradleBuild                | "org.gradle.tooling.model.gradle.GradleBuild"
+        CustomModel                | CustomModel.name
+    }
+
+    def "maps model type to version it was added in"() {
+        expect:
+        mapping.getVersionAdded(modelType) == since
+
+        where:
+        modelType                  | since
+        Void                       | "1.0-milestone-3"
+        HierarchicalEclipseProject | "1.0-milestone-3"
+        EclipseProject             | "1.0-milestone-3"
+        IdeaProject                | "1.0-milestone-5"
+        GradleProject              | "1.0-milestone-5"
+        BasicIdeaProject           | "1.0-milestone-5"
+        BuildEnvironment           | "1.0-milestone-8"
+        ProjectOutcomes            | "1.2"
+        GradleBuild                | "1.8"
+        CustomModel                | null
+    }
+}
+
+interface CustomModel {}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectTest.groovy
deleted file mode 100644
index 5fdbc7f..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectTest.groovy
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.internal.eclipse
-
-import spock.lang.Specification
-
-class DefaultEclipseProjectTest extends Specification {
-    def usesPathForToStringValue() {
-        def project = new DefaultEclipseProject("name", ":path", null, null, [])
-
-        expect:
-        project.toString() == "project ':path'"
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy
index 3048ead..6cbd0ac 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy
@@ -20,14 +20,14 @@ import spock.lang.Specification
 class DefaultGradleProjectTest extends Specification {
 
     def "allows finding descendant by path"() {
-        def root = new DefaultGradleProject(":")
+        def root = new DefaultGradleProject().setPath(":")
 
-        def child1 = new DefaultGradleProject(":child1")
-        def child11 = new DefaultGradleProject(":child1:child11")
-        def child12 = new DefaultGradleProject(":child1:child12")
+        def child1 = new DefaultGradleProject().setPath(":child1")
+        def child11 = new DefaultGradleProject().setPath(":child1:child11")
+        def child12 = new DefaultGradleProject().setPath(":child1:child12")
 
-        def child2 = new DefaultGradleProject(":child2")
-        def child21 = new DefaultGradleProject(":child2:child21")
+        def child2 = new DefaultGradleProject().setPath(":child2")
+        def child21 = new DefaultGradleProject().setPath(":child2:child21")
 
         root.children = [child1, child2]
         child1.children = [child11, child12]
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/reflect/CompatibleIntrospectorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/reflect/CompatibleIntrospectorTest.groovy
deleted file mode 100644
index bd12f78..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/reflect/CompatibleIntrospectorTest.groovy
+++ /dev/null
@@ -1,59 +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.reflect
-
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 3/16/12
- */
-class CompatibleIntrospectorTest extends Specification {
-    
-    class Foo {
-        
-        int number
-        
-        String getMessage() {
-            "Hello!"
-        }
-        
-        void setNumber(int number) {
-            this.number = number
-        }
-    }
-
-    def foo = new Foo()
-    def intro = new CompatibleIntrospector(foo)
-    
-    def "gets stuff safely"() {
-        expect:
-        'Hello!' == intro.getSafely('blah', 'getMessage')
-        'blah' == intro.getSafely('blah', 'doesNotExist')
-    }
-
-    def "calls methods safely"() {
-        when:
-        intro.callSafely('doesNotExist', 10)
-        then:
-        foo.number == 0
-
-        when:
-        intro.callSafely('setNumber', 10)
-        then:
-        foo.number == 10
-    }
-}
diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/GradleVersionSpec.java b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/GradleVersionSpec.java
new file mode 100644
index 0000000..d915a14
--- /dev/null
+++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/GradleVersionSpec.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.fixture;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.GradleVersion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GradleVersionSpec {
+    public static Spec<GradleVersion> toSpec(String constraint) {
+        String trimmed = constraint.trim();
+        if (trimmed.equals("current")) {
+            return new Spec<GradleVersion>() {
+                public boolean isSatisfiedBy(GradleVersion element) {
+                    return element.equals(GradleVersion.current());
+                }
+            };
+        }
+        if (trimmed.equals("!current")) {
+            return new Spec<GradleVersion>() {
+                public boolean isSatisfiedBy(GradleVersion element) {
+                    return !element.equals(GradleVersion.current());
+                }
+            };
+        }
+        if (trimmed.startsWith("=")) {
+            final GradleVersion target = GradleVersion.version(trimmed.substring(1)).getBaseVersion();
+            return new Spec<GradleVersion>() {
+                public boolean isSatisfiedBy(GradleVersion element) {
+                    return element.getBaseVersion().equals(target);
+                }
+            };
+        }
+
+        List<Spec> specs = new ArrayList<Spec>();
+        String[] patterns = trimmed.split("\\s+");
+        for (String value : patterns) {
+            if (value.startsWith(">=")) {
+                final GradleVersion minVersion = GradleVersion.version(value.substring(2));
+                specs.add(new Spec<GradleVersion>() {
+                    public boolean isSatisfiedBy(GradleVersion element) {
+                        return element.getBaseVersion().compareTo(minVersion) >= 0;
+                    }
+                });
+            } else if (value.startsWith("<=")) {
+                final GradleVersion maxVersion = GradleVersion.version(value.substring(2));
+                specs.add(new Spec<GradleVersion>() {
+                    public boolean isSatisfiedBy(GradleVersion element) {
+                        return element.getBaseVersion().compareTo(maxVersion) <= 0;
+                    }
+                });
+            } else if (value.startsWith("<")) {
+                final GradleVersion maxVersion = GradleVersion.version(value.substring(1));
+                specs.add(new Spec<GradleVersion>() {
+                    public boolean isSatisfiedBy(GradleVersion element) {
+                        return element.getBaseVersion().compareTo(maxVersion) < 0;
+                    }
+                });
+            } else {
+                throw new RuntimeException(String.format("Unsupported version range '%s' specified in constraint '%s'. Supported formats: '>=nnn' or '<=nnn' or space-separate patterns", value, constraint));
+            }
+        }
+        return Specs.and(specs.toArray(new Spec[specs.size()]));
+    }
+
+}
diff --git a/subprojects/tooling-api/tooling-api.gradle b/subprojects/tooling-api/tooling-api.gradle
index e8e76a7..9e0b22d 100644
--- a/subprojects/tooling-api/tooling-api.gradle
+++ b/subprojects/tooling-api/tooling-api.gradle
@@ -2,15 +2,21 @@ import org.gradle.build.*
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
 
 dependencies {
-    groovy libraries.groovy
     compile project(path: ':core', configuration: "publishCompileWithProjectJar")
     compile project(':messaging')
     compile project(':wrapper')
     compile project(':baseServices')
     publishCompile libraries.slf4j_api
 
+    testFixturesCompile project(':baseServicesGroovy')
+    testCompile libraries.groovy
+
     // lots of integTest errors otherwise
     integTestRuntime project(':ide')
+    integTestRuntime project(':buildInit')
+    integTestRuntime project(':buildComparison')
+    integTestRuntime project(":ivy")
+    integTestRuntime project(":maven")
 }
 
 useTestFixtures()
@@ -35,7 +41,6 @@ task jarjarJar(type: JarJarJar) {
     exclude "META-INF/**"
     exclude "*classpath.properties"
 
-
     rule('org.gradle.**', '@0')
     rule('org.slf4j.**', '@0')
 
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
index 54cb773..1e99fb3 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
@@ -38,8 +38,6 @@ import java.util.List;
 
 /**
  * Performs integration tests on favorite tasks.
- *
- * @author mhunsicker
  */
 public class FavoritesIntegrationTest {
     @Rule
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
index b6a4cf1..59a9cc8 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
@@ -37,17 +37,9 @@ import java.util.concurrent.locks.ReentrantLock
 
 /**
 This tests the that live output is gathered while executing a task.
- at author mhunsicker
 */
 class LiveOutputIntegrationTest extends AbstractIntegrationTest {
 
-    static final String JAVA_PROJECT_NAME = 'javaproject'
-    static final String SHARED_NAME = 'shared'
-    static final String API_NAME = 'api'
-    static final String WEBAPP_NAME = 'webservice'
-    static final String SERVICES_NAME = 'services'
-    static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
-
     private File javaprojectDir
 
     @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/quickstart')
@@ -58,11 +50,9 @@ class LiveOutputIntegrationTest extends AbstractIntegrationTest {
     }
 
     /**
-This executes 'build' on the java multiproject sample. We want to make sure that
+This executes 'build' on the java quickstart sample. We want to make sure that
 we do get live output from gradle. We're not concerned with what it is, because
 that's likely to change over time. This version executes the command via GradlePlugin.
-
- at author mhunsicker
 */
 
     @Test
@@ -75,8 +65,6 @@ that's likely to change over time. This version executes the command via GradleP
         gradlePluginLord.setGradleHomeDirectory(distribution.gradleHomeDir);
         gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(executer.gradleUserHomeDir))
 
-        gradlePluginLord.startExecutionQueue(); //for tests, we'll need to explicitly start the execution queue (unless we do a refresh via the TestUtility).
-
         TestExecutionInteraction executionInteraction = new TestExecutionInteraction();
 
         //execute a command. We don't really care what the command is, just something that generates output
@@ -89,8 +77,6 @@ that's likely to change over time. This version executes the command via GradleP
 This executes 'build' on the java multiproject sample. We want to make sure that
 we do get live output from gradle. We're not concerned with what it is, because
 that's likely to change over time. This version executes the command via GradleRunner.
-
- at author mhunsicker
 */
     @Test
     public void liveOutputObtainedViaGradleRunner() {
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
index a16c95e..e77daf7 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
@@ -32,7 +32,6 @@ import java.util.concurrent.TimeUnit
 
 /**
  This tests the multiproject sample with the GradleView mechanism.
- @author mhunsicker
  */
 class MultiprojectProjectAndTaskListIntegrationTest extends AbstractIntegrationTest {
 
@@ -59,8 +58,6 @@ class MultiprojectProjectAndTaskListIntegrationTest extends AbstractIntegrationT
        (services:webservice). This isn't really interested in the actual tasks
        themselves (I fear those may change too often to worry with keeping the
        test up to date).
-
-       @author mhunsicker
     */
 
     @Test
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java b/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
index d2ce564..c5bd06f 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
@@ -23,8 +23,6 @@ import java.util.List;
 
 /**
  * Some helpful functions for manipulating command line arguments.
- *
- * @author mhunsicker
  */
 public class CommandLineAssistant {
     private final LoggingCommandLineConverter loggingCommandLineConverter = new LoggingCommandLineConverter();
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/PathParserPortion.java b/subprojects/ui/src/main/java/org/gradle/foundation/PathParserPortion.java
index 67f8d92..53afe34 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/PathParserPortion.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/PathParserPortion.java
@@ -17,8 +17,6 @@ package org.gradle.foundation;
 
 /**
  * Small helper class that aids walking a full task path which can be multiple projects deep with a task on the end.
- *
- * @author mhunsicker
  */
 public class PathParserPortion {
     private String firstPart;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ProjectConverter.java b/subprojects/ui/src/main/java/org/gradle/foundation/ProjectConverter.java
index fd3b2fc..92d96c0 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ProjectConverter.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ProjectConverter.java
@@ -24,8 +24,6 @@ import java.util.*;
 
 /**
  * This converts Gradle's projects into ProjectView objects. These can be safely reused unlike Gradle's projects.
- *
- * @author mhunsicker
  */
 public class ProjectConverter {
     private List<ProjectView> rootLevelResultingProjects = new ArrayList<ProjectView>();
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ProjectView.java b/subprojects/ui/src/main/java/org/gradle/foundation/ProjectView.java
index ff708a9..de3fe02 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ProjectView.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ProjectView.java
@@ -27,8 +27,6 @@ import java.util.List;
 /**
  * Analog to gradle's Project but more light-weight and is better suited for using the gradle API from an IDE plugin. It is also easily serializable for passing across a socket. A project is a
  * collection of source files that have tasks associated with them. The tasks build the project. Projects can contain other projects. This is immutable and ultimately comes from gradle files.
- *
- * @author mhunsicker
  */
 public class ProjectView implements Comparable<ProjectView>, Serializable {
     private final String name;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/TaskView.java b/subprojects/ui/src/main/java/org/gradle/foundation/TaskView.java
index 1df6805..f1fe7ea 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/TaskView.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/TaskView.java
@@ -22,8 +22,6 @@ import java.io.Serializable;
 /**
  * Analog to gradle's Task but more light-weight and better suited for using the gradle API from an IDE plugin. It is also easily serializable for passing across a socket. A task is something you can
  * execute and is part of a project. This is immutable and ultimately comes from gradle files.
- *
- * @author mhunsicker
  */
 public class TaskView implements Comparable<TaskView>, Serializable {
     private ProjectView project;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java b/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
index 14198a4..3f1f673 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
@@ -19,8 +19,6 @@ import java.util.*;
 
 /**
  * Utility class that allows lists to be reordered.
- *
- * @author Chris Hampton
  */
 public class ListReorderer {
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java b/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
index 58d5ae3..235998e 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
@@ -33,10 +33,7 @@ import java.util.List;
  * functions that call the add and remove functions here. Lastly, implement ObserverNotification and have it call the aforementioned observer interface appropriately. Note: you should actually
  * implement ObserverNotification for each "message" you want to send. Example: One that would tell a view a node was added. One that would tell a view a node was deleted, etc. While you have multiple
  * notification classes, you only need 1 (or few) actual observer interfaces, containing all the possible functions called by all notifications.
- *
- * @author mhunsicker
  */
-
 public class ObserverLord<E> {
     private List<E> regularObservers = new ArrayList<E>();
     private List<E> eventQueueObservers = new ArrayList<E>();
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java b/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
index f98fba0..59c952e 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
@@ -28,8 +28,6 @@ import java.util.*;
    Unfortunately, we can't use Java's Object.clone() method because it is
    protected and must be overridden as public to be used. So we can't call
    obj.clone() on an Object instance.
-
-   @author champton
 */
 
 public class ReorderableList<E> implements List<E> {
@@ -516,7 +514,6 @@ public class ReorderableList<E> implements List<E> {
 
     @param  elementsToMove the elements that were moved
     @return                an integer array of the items to select.
-    @author mhunsicker
     */
 
     public int[] getIndices(List<E> elementsToMove) {
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java
index ff75244..5cc8a62 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java
@@ -26,8 +26,6 @@ import java.net.Socket;
 /**
  * The client of what the ProcessLauncherServer launches. The client makes a connection to the server and sends messages to it. The server responds to those messages, but does not initiate
  * communications otherwise. You implement the Protocol interface to handle the specifics of the communications.
- *
- * @author mhunsicker
  */
 public class ClientProcess {
     private final Logger logger = Logging.getLogger(ClientProcess.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java
index 390bd52..30e0977 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java
@@ -18,12 +18,8 @@ package org.gradle.foundation.ipc.basic;
 import java.io.File;
 import java.util.HashMap;
 
-/**
- * @author mhunsicker
- */
 /*
    Fill this in with whatever you need to launch your external process.
-   @author mhunsicker
 */
 public interface ExecutionInfo {
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java
index 5bb9b09..c3834d8 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java
@@ -21,8 +21,6 @@ import java.io.Serializable;
 
 /**
  * A holder for a message that is sent over a socket.
- *
- * @author mhunsicker
  */
 public class MessageObject implements Serializable {
     private String messageType;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java
index 1f26353..4426671 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java
@@ -26,8 +26,6 @@ import java.net.SocketException;
 
 /**
  * Wrapper around a java.net.Socket just to simplify usage.
- *
- * @author mhunsicker
  */
 public class ObjectSocketWrapper {
     private Socket socket;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.java
index 8e50e37..a5c6914 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.java
@@ -28,8 +28,6 @@ import java.io.ByteArrayOutputStream;
 /**
  * This launches an application as a separate process then listens for messages from it. You implement the Protocol interface to handle the specifics of the communications. To use this, instantiate
  * it, then call start. When the communications are finished, call requestShutdown(). Your server's protocol can call sendMessage once communication is started to respond to client's messages.
- *
- * @author mhunsicker
  */
 public class ProcessLauncherServer extends Server<ProcessLauncherServer.Protocol, ProcessLauncherServer.ServerObserver> {
     private volatile ExecHandle externalProcess;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java
index 6cbe2aa..7ac6364 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java
@@ -27,8 +27,6 @@ import java.net.Socket;
 /**
  * This is a server that talks to a client via sockets (Rudimentary form of Inter-Process Communication (IPC)). This does the work of locating a free socket and starting the connection. To use this,
  * you really only have to define a Protocol that handles the actual messages. You'll want to make your client startup a ClientProcess object that implements a corresponding Protocol.
- *
- * @author mhunsicker
  */
 public class Server<P extends Server.Protocol, O extends Server.ServerObserver> {
     private final Logger logger = Logging.getLogger(Server.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
index f1c80dd..92c044e 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
@@ -45,8 +45,6 @@ import java.util.List;
  * This defines the basic behavior of all gradle protocols for interprocess communication. It manages handshaking, detecting if the client executed prematurely, as well as executing alternate external
  * processes. All you need to do is extend this, implement the abstract functions, and make sure you call setHasReceivedBuildCompleteNotification() when whatever you were doing is complete (so we know
  * any exiting is not premature).
- *
- * @author mhunsicker
  */
 public abstract class AbstractGradleServerProtocol implements ProcessLauncherServer.Protocol {
     private static final String INIT_SCRIPT_EXTENSION = ".gradle";
@@ -313,7 +311,7 @@ public abstract class AbstractGradleServerProtocol implements ProcessLauncherSer
     }
 
     /**
-     * Notification that the client has shutdown. Note: this can occur before communciations has ever started. You SHOULD get this notification before receiving serverExited, even if the client fails
+     * Notification that the client has shutdown. Note: this can occur before communications has ever started. You SHOULD get this notification before receiving serverExited, even if the client fails
      * to launch or locks up.
      *
      * @param returnCode the return code of the client application
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
index 3ab3ca5..55d6a30 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
@@ -40,8 +40,6 @@ import java.util.TimerTask;
 /**
  * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based inter-process communication. This is the client (gradle) side used when executing
  * commands (the most common case). We add gradle listeners and send their notifications as messages back to the server.
- *
- * @author mhunsicker
  */
 public class ExecuteGradleCommandClientProtocol implements ClientProcess.Protocol {
     private final Logger logger = Logging.getLogger(ExecuteGradleCommandClientProtocol.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
index 1a6172a..8af79a8 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
@@ -24,8 +24,6 @@ import java.io.File;
 /**
  * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based inter-process communication. This is the server side for executing a gradle command.
  * This listens for messages from the gradle client.
- *
- * @author mhunsicker
  */
 public class ExecuteGradleCommandServerProtocol extends AbstractGradleServerProtocol {
     private static final String INIT_SCRIPT_NAME = "execute-command-init-script";
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java
index 3bfd9c6..5218aa7 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java
@@ -21,8 +21,6 @@ import org.gradle.api.logging.Logging;
 
 /**
  * Just some convenience functions to startup a GradleClient. See GradleClient for more information.
- *
- * @author mhunsicker
  */
 public class IPCUtilities {
     private static final Logger LOGGER = Logging.getLogger(IPCUtilities.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java
index ace7156..de691d8 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java
@@ -22,8 +22,6 @@ import java.net.Socket;
 /**
  * This protocol is used by the process that launches gradle (the launching server - but in this case its the client) so that it can tell gradle to kill itself. This is used to cancel gradle
  * execution. There is no other clean way to do it but kill it. All this does is send a 'kill' message.
- *
- * @author mhunsicker
  */
 public class KillGradleClientProtocol implements ClientProcess.Protocol {
     private ClientProcess client;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java
index 910dd35..13323c0 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java
@@ -20,8 +20,6 @@ import org.gradle.foundation.ipc.basic.Server;
 
 /**
  * This protocol is used by a client that launches its own server. See KillGradleClientProtocol.
- *
- * @author mhunsicker
  */
 public class KillGradleServerProtocol implements Server.Protocol<Server> {
     private Server server;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java
index 45afd46..05d52ea 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java
@@ -17,8 +17,6 @@ package org.gradle.foundation.ipc.gradle;
 
 /**
  * Constants related to interprocess communication between gradle and the gradle UI.
- *
- * @author mhunsicker
  */
 public class ProtocolConstants {
     //these are the message types we'll receive from the client.
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java
index bfa894c..cff7b00 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java
@@ -36,8 +36,6 @@ import java.util.List;
 /**
  * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based inter-process communication. This is the client (gradle) side used to build a task
  * list (tree actually). We add gradle listeners and send their notifications as messages back to the server.
- *
- * @author mhunsicker
  */
 public class TaskListClientProtocol implements ClientProcess.Protocol {
     private final Logger logger = Logging.getLogger(TaskListClientProtocol.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
index 4963768..531afa9 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
@@ -26,8 +26,6 @@ import java.util.List;
 /**
  * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based inter-process communication. This is the server side for building a task list. This
  * listens for messages from the gradle client.
- *
- * @author mhunsicker
  */
 public class TaskListServerProtocol extends AbstractGradleServerProtocol {
     private static final String INIT_SCRIPT_NAME = "refresh-tasks-init-script";
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLink.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLink.java
index 30cd87e..5320964 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLink.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLink.java
@@ -22,8 +22,6 @@ import java.io.File;
 /**
  * This represents a link to a file inside gradle's output. This is so the gradle UI/plugins can open the file. This is useful for a user when gradle displays a build error, test failure or compile
  * error.
- *
- * @author mhunsicker
  */
 public class FileLink {
     private File file;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java
index 25f35b4..e5bd4a4 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java
@@ -23,8 +23,6 @@ import java.util.regex.Pattern;
 
 /**
  * This class holds on FileLinkDefinitions used for searching output.
- *
- * @author mhunsicker
  */
 public class FileLinkDefinitionLord {
     private List<String> extensions = new ArrayList<String>();
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
index 0fd04a0..3b02808 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
@@ -23,8 +23,6 @@ import java.util.List;
 /**
  * This is a special type of OutputParser. It handles tracking live output. The unique thing about live output is that we're not guaranteed to get whole lines. Also, we don't want to parse parts that
  * have already been parsed. This holds onto the output until a newline is reached, then parses it. It also tracks the overall index into the output (even though its only parsing a part of it).
- *
- * @author mhunsicker
  */
 public class LiveOutputParser {
     private OutputParser parser;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/OutputParser.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/OutputParser.java
index c92e7bb..87931be 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/OutputParser.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/OutputParser.java
@@ -27,8 +27,6 @@ import java.util.regex.Pattern;
  * easily. To accomplish this, this works with FileLinkDefinitions. They require a basic RegEx pattern to match some initial part of the a file link, however, they can be implemented to do more
  * advanced parsing of the text. The definitions are built-up and held by FileLinkDefinitionLord. This just handles the tedium of matching the all-inclusive pattern with text, managing indices, and
  * calling the matching FileLinkDefinition to refine the match.
- *
- * @author mhunsicker
  */
 public class OutputParser {
     private FileLinkDefinitionLord fileLinkDefinitionLord;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java
index 5553a14..fb3b9eb 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java
@@ -25,8 +25,6 @@ import java.util.List;
  * a delimiter after the path to specify a line number (the delimiter cannot be before the path).
  *
  * Here's a sample line output from a compile error: /home/someguy/path/etc/etc.java:186: cannot find symbol
- *
- * @author mhunsicker
  */
 public class ExtensionFileLinkDefinition implements FileLinkDefinition {
     private String expression;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.java
index 4629727..41e001e 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.java
@@ -21,8 +21,6 @@ import java.util.List;
 
 /**
  * .
- *
- * @author mhunsicker
  */
 public interface FileLinkDefinition {
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java
index 55a31c4..479a05e 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java
@@ -17,8 +17,6 @@ package org.gradle.foundation.output.definitions;
 
 /**
  * This is just like BasicFileLinkDefinition except that the line number delimiter is optional and it allows spaces between the file and the delimiter.
- *
- * @author mhunsicker
  */
 public class OptionalLineNumberFileLinkDefinition extends PrefixedFileLinkDefinition {
     public OptionalLineNumberFileLinkDefinition(String name, String prefix, String extension, String lineNumberDelimiter) {
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java
index ed5b346..655b57d 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java
@@ -27,8 +27,6 @@ import java.util.List;
  * Here's a sample line output from an ant compile error: [ant:javac] /home/someguy/path/etc/etc.java:186: cannot find symbol
  *
  * Here's a sample line output from gradle when it encounters an exception: Build file '/home/someguy/path/etc/etc/build.gradle'
- *
- * @author mhunsicker
  */
 public class PrefixedFileLinkDefinition implements FileLinkDefinition {
     private String expression;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java
index b2b68f8..b9dfa1c 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java
@@ -23,8 +23,6 @@ import java.util.List;
 /**
  * This is a special FileLinkDefinition for handling test reports. At the time of this writing, the test reports error message merely told you the directory to visit, not the actual file. So this
  * appends 'index.html' to the directory to generate the file.
- *
- * @author mhunsicker
  */
 public class TestReportFileLinkDefinition implements FileLinkDefinition {
     private String expression;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java b/subprojects/ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java
index 405ab68..d23d21f 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java
@@ -18,15 +18,11 @@ package org.gradle.foundation.queue;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
 
 /**
  * This class abstracts running multiple tasks consecutively. This exists because I'm not certain that Gradle is thread-safe and on Windows, running tasks that require lots of disk I/O get
  * considerably slower when run concurrently. This will allow requests to be made and they will run as soon as any previous requests have finished.
- *
- * @author mhunsicker
  */
 public class ExecutionQueue<R extends ExecutionQueue.Request> {
     private final Logger logger = Logging.getLogger(ExecutionQueue.class);
@@ -63,6 +59,10 @@ public class ExecutionQueue<R extends ExecutionQueue.Request> {
         public Type getType();
     }
 
+    public interface RequestCancellation {
+        void onCancel(Request request);
+    }
+
     public ExecutionQueue(ExecutionInteraction<R> executeInteraction) {
         executionThread = new Thread(new ExecutionThread(executeInteraction));
 
@@ -81,18 +81,10 @@ public class ExecutionQueue<R extends ExecutionQueue.Request> {
         requests.offer(request);
     }
 
-    public boolean removeRequestFromQueue(R request) {
+    public boolean removeRequestFromQueue(Request request) {
         return requests.remove(request);
     }
 
-    public boolean hasRequests() {
-        return !requests.isEmpty();
-    }
-
-    public List<R> getRequests() {
-        return new ArrayList<R>(requests);
-    }
-
     /**
      * This waits until the next request is available.
      *
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java
index 771582e..5574114 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java
@@ -26,13 +26,10 @@ import java.util.List;
 
 /**
  * This visits all projects and their subprojects and tasks in a hierarchal manner. Useful if you need to do some processing with each one.
- *
- * @author mhunsicker
  */
 public class AllProjectsAndTasksVisitor {
     /*
           Visitor allowing you to do whatever you like with a project or task.
-       @author mhunsicker
     */
 
     public interface Visitor<P, T> {
@@ -44,7 +41,6 @@ public class AllProjectsAndTasksVisitor {
             was passed into the visitPojectsAndTasks function.
            @return an object that will be handed back to you for each of this
                    project's tasks.
-           @author mhunsicker
         */
 
         public P visitProject(ProjectView project, P parentProjectObject);
@@ -55,7 +51,6 @@ public class AllProjectsAndTasksVisitor {
          @param  task               the task
          @param  tasksProject       the project for this task
          @param  userProjectObject  whatever you returned from the parent project's visitProject
-           @author mhunsicker
         */
 
         public T visitTask(TaskView task, ProjectView tasksProject, P userProjectObject);
@@ -68,8 +63,6 @@ public class AllProjectsAndTasksVisitor {
 
        This is the same as the other version of visitProjectsAndTasks except this
        one visits everything.
-
-       @author mhunsicker
     */
 
     public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor, P rootProjectObject) {
@@ -85,7 +78,6 @@ public class AllProjectsAndTasksVisitor {
        @param  filter     allows you to skip projects and tasks as specified by the filter.
        @param  rootProjectObject whatever you pass here will be passed to the
                 root-level projects as parentProjectObject.
-       @author mhunsicker
     */
 
     public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor, ProjectAndTaskFilter filter, P rootProjectObject) {
@@ -116,8 +108,6 @@ public class AllProjectsAndTasksVisitor {
 
     /*
        Add the list of tasks to the parent tree node.
-
-       @author mhunsicker
     */
 
     private static <P, T> List<T> visitTasks(Visitor<P, T> visitor, ProjectAndTaskFilter filter, ProjectView project, P userProjectObject) {
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
index 59869dc..d627d04 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
@@ -19,6 +19,7 @@ import org.gradle.foundation.ProjectView;
 import org.gradle.foundation.TaskView;
 import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
 import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GUtil;
 
 import java.util.*;
@@ -26,8 +27,6 @@ import java.util.*;
 /**
  * This visits each project and task in a hierarchical manner. This visitor is specifically meant to walk the projects first and tasks second for the purpose of populating a tree where the
  * projects/subprojects are first and the tasks are second.
- *
- * @author mhunsicker
  */
 public class TaskTreePopulationVisitor {
     public interface Visitor<P, T> {
@@ -40,7 +39,6 @@ public class TaskTreePopulationVisitor {
                  was passed into the visitPojectsAndTasks function.
            @return an object that will be handed back to you for each of this
                    project's tasks.
-           @author mhunsicker
         */
 
         public P visitProject(ProjectView project, int indexOfProject, P parentProjectObject);
@@ -52,7 +50,6 @@ public class TaskTreePopulationVisitor {
          @param indexOfTask
          @param  tasksProject       the project for this task
          @param  userProjectObject  whatever you returned from the parent project's visitProject
-           @author mhunsicker
         */
 
         public T visitTask(TaskView task, int indexOfTask, ProjectView tasksProject, P userProjectObject);
@@ -67,7 +64,6 @@ public class TaskTreePopulationVisitor {
                                        the project and task objects below
            @param  projectObjects      a list of whatever you returned from visitProject
            @param  taskObjects         a list of whatever you returned from visitTask
-           @author mhunsicker
         */
 
         public void completedVisitingProject(P parentProjectObject, List<P> projectObjects, List<T> taskObjects);
@@ -80,8 +76,6 @@ public class TaskTreePopulationVisitor {
 
        This is the same as the other version of visitProjectsAndTasks except this
        one visits everything.
-
-       @author mhunsicker
     */
 
     public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
@@ -98,7 +92,6 @@ public class TaskTreePopulationVisitor {
        @param  filter     allows you to skip projects and tasks as specified by the filter.
        @param  rootProjectObject whatever you pass here will be passed to the
                 root-level projects as parentProjectObject.
-       @author mhunsicker
     */
 
     public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
@@ -113,8 +106,7 @@ public class TaskTreePopulationVisitor {
                                                 List<ProjectView> sourceProjects, P parentProjectObject, Comparator<ProjectView> projectSorter, Comparator<TaskView> taskSorter) {
         List<P> projectObjects = new ArrayList<P>();
 
-        sourceProjects = new ArrayList<ProjectView>(sourceProjects);  //make a copy because we're going to sort them.
-        Collections.sort(sourceProjects, projectSorter);
+        sourceProjects = CollectionUtils.sort(sourceProjects, projectSorter);  //make a copy because we're going to sort them.
 
         Iterator<ProjectView> iterator = sourceProjects.iterator();
         int index = 0;
@@ -141,15 +133,12 @@ public class TaskTreePopulationVisitor {
 
     /*
        Add the list of tasks to the parent tree node.
-
-       @author mhunsicker
     */
 
     private static <P, T> List<T> visitTasks(Visitor<P, T> visitor, ProjectAndTaskFilter filter, ProjectView project,
                                              int startingIndex, P userProjectObject, Comparator<TaskView> taskSorter) {
         List<T> taskObjects = new ArrayList<T>();
-        List<TaskView> tasks = new ArrayList<TaskView>(project.getTasks()); //make a copy because we're going to sort them
-        Collections.sort(tasks, taskSorter);
+        List<TaskView> tasks = CollectionUtils.sort(project.getTasks(), taskSorter); //make a copy because we're going to sort them
 
         Iterator<TaskView> iterator = tasks.iterator();
         int index = startingIndex;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java
index ad09ed0..27de001 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java
@@ -17,15 +17,13 @@ package org.gradle.foundation.visitors;
 
 import org.gradle.foundation.ProjectView;
 import org.gradle.foundation.TaskView;
+import org.gradle.util.CollectionUtils;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
  * This visitor builds up a list of unqiuely named projects and tasks. The projects will be their full path, so they're all unique.
- *
- * @author mhunsicker
  */
 public class UniqueNameProjectAndTaskVisitor implements AllProjectsAndTasksVisitor.Visitor<Object, Object> {
     private List<String> taskNames = new ArrayList<String>();
@@ -40,15 +38,11 @@ public class UniqueNameProjectAndTaskVisitor implements AllProjectsAndTasksVisit
     }
 
     public List<String> getSortedTaskNames() {
-        ArrayList<String> tasks = new ArrayList<String>(taskNames);
-        Collections.sort(tasks);
-        return tasks;
+        return CollectionUtils.sort(taskNames);
     }
 
     public List<String> getSortedProjectNames() {
-        ArrayList<String> projects = new ArrayList<String>(projectNames);
-        Collections.sort(projects);
-        return projects;
+        return CollectionUtils.sort(projectNames);
     }
 
     /*
@@ -59,7 +53,6 @@ public class UniqueNameProjectAndTaskVisitor implements AllProjectsAndTasksVisit
                                it'll be whatever was passed into the
                                visitPojectsAndTasks function.
     @return always null
-    @author mhunsicker
     */
 
     public Object visitProject(ProjectView project, Object parentProjectObject) {
@@ -76,7 +69,6 @@ public class UniqueNameProjectAndTaskVisitor implements AllProjectsAndTasksVisit
     @param task              the task
     @param tasksProject      the project for this task
     @param userProjectObject always null.
-    @author mhunsicker
     */
 
     public Object visitTask(TaskView task, ProjectView tasksProject, Object userProjectObject) {
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java
index 542e2b8..18459ba 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java
@@ -18,8 +18,6 @@ package org.gradle.gradleplugin.foundation;
 /**
  * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is useful if you've customized your gradle build and need to specify, for example,
  * an init script.
- *
- * @author mhunsicker
  */
 public interface CommandLineArgumentAlteringListener {
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
index 1a72eb6..362af5d 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
@@ -32,8 +32,6 @@ import java.io.*;
 
 /**
  * This saves and reads thing to and from DOM structures.
- *
- * @author mhunsicker
  */
 public class DOM4JSerializer {
     private static final Logger LOGGER = Logging.getLogger(DOM4JSerializer.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java
index 25dbc32..e298f31 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java
@@ -25,7 +25,6 @@ import java.util.List;
 /*
  Just some utility functions that really should be in Dom4J already.
 
- @author mhunsicker
   */
 
 public class Dom4JUtility {
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java
index e29977b..6658cb4 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java
@@ -21,8 +21,6 @@ import java.io.File;
 /**
  * Man! Why doesn't this get put into java's standard library?! Well, they did, but it doesn't hide hidden files. By definition of them being hidden, you probably don't want to see them. <p/> While
  * FileFilter is technically a Swing class, it shouldn't be. The foundation needs to drive what is allowed in the UI.
- *
- * @author mhunsicker
  */
 public class ExtensionFileFilter extends FileFilter {
     private String extension;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
index f4e3470..bd6290b 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
@@ -16,7 +16,6 @@
 package org.gradle.gradleplugin.foundation;
 
 import org.codehaus.groovy.runtime.StackTraceUtils;
-import org.gradle.api.internal.LocationAwareException;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
@@ -34,19 +33,17 @@ import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
 import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
 import org.gradle.gradleplugin.foundation.request.Request;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.exceptions.LocationAwareException;
 import org.gradle.logging.ShowStacktrace;
 import org.gradle.util.GUtil;
 
 import java.io.File;
 import java.util.*;
-import java.util.concurrent.LinkedBlockingQueue;
 
 /**
  * This class has nothing to do with plugins inside of gradle, but are related to making a plugin that uses gradle, such as for an IDE. It is also used by the standalone IDE (that way the standalone
  * UI and plugin UIs are kept in synch). <p/> This is the class that stores most of the information that the Gradle plugin works directly with. It is meant to simplify creating a plugin that uses
  * gradle. It maintains a queue of commands to execute and executes them in a separate process due to some complexities with gradle and its dependencies classpaths and potential memory issues.
- *
- * @author mhunsicker
  */
 public class GradlePluginLord {
     private final Logger logger = Logging.getLogger(GradlePluginLord.class);
@@ -59,10 +56,7 @@ public class GradlePluginLord {
 
     private FavoritesEditor favoritesEditor;  //an editor for the current favorites. The user can edit this at any time, hence we're using an editor.
 
-    private ExecutionQueue<Request> executionQueue;
-    private LinkedBlockingQueue<Request> currentlyExecutingRequests = new LinkedBlockingQueue<Request>();
-
-    private boolean isStarted;  //this flag is mostly to prevent initialization from firing off repeated refresh requests.
+    private QueueManager queueManager = new QueueManager();
 
     private ShowStacktrace stackTraceLevel = ShowStacktrace.INTERNAL_EXCEPTIONS;
     private LogLevel logLevel = LogLevel.LIFECYCLE;
@@ -142,7 +136,6 @@ public class GradlePluginLord {
         favoritesEditor = new FavoritesEditor();
 
         //create the queue that executes the commands. The contents of this interaction are where we actually launch gradle.
-        executionQueue = new ExecutionQueue<Request>(new ExecutionQueueInteraction());
 
         currentDirectory = SystemProperties.getCurrentDir();
 
@@ -246,13 +239,6 @@ public class GradlePluginLord {
     }
 
     /**
-     * Call this to start execution. This is done after you've initialized everything.
-     */
-    public void startExecutionQueue() {
-        isStarted = true;
-    }
-
-    /**
      * This gives requests of the queue and then executes them by kicking off gradle in a separate process. Most of the code here is tedious setup code needed to start the server. The server is what
      * starts gradle and opens a socket for interprocess communication so we can receive messages back from gradle.
      */
@@ -263,10 +249,6 @@ public class GradlePluginLord {
          * @param request the request to execute.
          */
         public void execute(final Request request) {
-
-            //mark this request as being currently executed
-            currentlyExecutingRequests.add(request);
-
             notifyAboutToExecuteRequest(request);
 
             //I'm just putting these in temp variables for easier debugging
@@ -284,7 +266,7 @@ public class GradlePluginLord {
             //we need to know when this command is finished executing so we can mark it as complete and notify any observers.
             server.addServerObserver(new ProcessLauncherServer.ServerObserver() {
                 public void clientExited(int result, String output) {
-                    currentlyExecutingRequests.remove(request);
+                    queueManager.onComplete(request);
                     notifyRequestExecutionComplete(request, result, output);
                 }
 
@@ -404,27 +386,6 @@ public class GradlePluginLord {
     }
 
     /**
-     * This executes all the tasks together in a background thread. That is, all tasks are passed to a single gradle call at once. This creates or uses an existing OutputPanel to display the results.
-     *
-     * @param tasks the tasks to execute
-     * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
-     * @param additionCommandLineOptions additional command line options to exeucte.
-     */
-    public Request addExecutionRequestToQueue(final List<TaskView> tasks, boolean forceOutputToBeShown, String... additionCommandLineOptions) {
-
-        if (tasks == null || tasks.isEmpty()) {
-            return null;
-        }
-
-        if (tasks.size() == 1) { //if there's only 1, just treat it as one
-            return addExecutionRequestToQueue(tasks.get(0), forceOutputToBeShown, additionCommandLineOptions);
-        }
-
-        String singleCommandLine = CommandLineAssistant.combineTasks(tasks, additionCommandLineOptions);
-        return addExecutionRequestToQueue(singleCommandLine, tasks.get(0).getName() + "...", forceOutputToBeShown);
-    }
-
-    /**
      * Executes several favorites commands at once as a single command. This has the affect of simply concatenating all the favorite command lines into a single line.
      *
      * @param favorites a list of favorites. If just one favorite, it executes it normally. If multiple favorites, it executes them all at once as a single command.
@@ -458,10 +419,6 @@ public class GradlePluginLord {
      * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
      */
     public Request addExecutionRequestToQueue(String fullCommandLine, String displayName, boolean forceOutputToBeShown) {
-        if (!isStarted) {
-            return null;
-        }
-
         if (fullCommandLine == null) {
             return null;
         }
@@ -469,13 +426,13 @@ public class GradlePluginLord {
         //here we'll give the UI a chance to add things to the command line.
         fullCommandLine = alterCommandLine(fullCommandLine);
 
-        final ExecutionRequest request = new ExecutionRequest(getNextRequestID(), fullCommandLine, displayName, forceOutputToBeShown, executionQueue);
+        final ExecutionRequest request = new ExecutionRequest(getNextRequestID(), fullCommandLine, displayName, forceOutputToBeShown, queueManager);
         requestObserverLord.notifyObservers(new ObserverLord.ObserverNotification<RequestObserver>() {
             public void notify(RequestObserver observer) {
                 observer.executionRequestAdded(request);
             }
         });
-        executionQueue.addRequestToQueue(request);
+        queueManager.addRequestToQueue(request);
         return request;
     }
 
@@ -496,17 +453,9 @@ public class GradlePluginLord {
      * This will refresh the project/task tree. This version allows you to specify additional arguments to be passed to gradle during the refresh (such as -b to specify a build file)
      *
      * @param additionalCommandLineArguments the arguments to add, or null if none.
-     * @return the Request that was created. Null if no request created.
+     * @return the Request that was created.
      */
     public Request addRefreshRequestToQueue(String additionalCommandLineArguments) {
-        if (!isStarted) {
-            return null;
-        }
-
-        if (hasRequestOfType(RefreshTaskListRequest.TYPE)) {
-            return null; //we're already doing a refresh.
-        }
-
         //we'll request a task list since there is no way to do a no op. We're not really interested
         //in what's being executed, just the ability to get the task list (which must be populated as
         //part of executing anything).
@@ -519,8 +468,17 @@ public class GradlePluginLord {
         //here we'll give the UI a chance to add things to the command line.
         fullCommandLine = alterCommandLine(fullCommandLine);
 
-        final RefreshTaskListRequest request = new RefreshTaskListRequest(getNextRequestID(), fullCommandLine, executionQueue, this);
-        executionQueue.addRequestToQueue(request);
+        // Don't schedule again if already doing a refresh with the specified arguments
+        // TODO - fix this race condition - multiple threads may be requesting a refresh
+        List<Request> currentRequests = queueManager.findRequestsOfType(RefreshTaskListRequest.TYPE);
+        for (Request currentRequest : currentRequests) {
+            if (currentRequest.getFullCommandLine().equals(fullCommandLine)) {
+                return currentRequest;
+            }
+        }
+
+        final RefreshTaskListRequest request = new RefreshTaskListRequest(getNextRequestID(), fullCommandLine, queueManager, this);
+        queueManager.addRequestToQueue(request);
         // TODO - fix this race condition - request may already have completed
         requestObserverLord.notifyObservers(new ObserverLord.ObserverNotification<RequestObserver>() {
             public void notify(RequestObserver observer) {
@@ -605,7 +563,7 @@ public class GradlePluginLord {
             if (failure instanceof LocationAwareException) {
                 LocationAwareException scriptException = (LocationAwareException) failure;
                 formatter.format("%s%n%n", scriptException.getLocation());
-                formatter.format("%s", scriptException.getOriginalMessage());
+                formatter.format("%s", scriptException.getCause().getMessage());
 
                 for (Throwable cause : scriptException.getReportableCauses()) {
                     formatter.format("%nCause: %s", getMessage(cause));
@@ -659,33 +617,44 @@ public class GradlePluginLord {
      * @return true if this is busy, false if not.
      */
     public boolean isBusy() {
-        return hasRequestOfType(ExecutionRequest.TYPE);
+        return !queueManager.findRequestsOfType(ExecutionRequest.TYPE).isEmpty();
     }
 
-    /**
-     * Determines if we have an request of the specified type
-     *
-     * @param type the sought type of request.
-     * @return true if it has the request, false if not.
-     */
-    private boolean hasRequestOfType(Request.Type type) {
-        Iterator<Request> iterator = currentlyExecutingRequests.iterator();
-        while (iterator.hasNext()) {
-            ExecutionQueue.Request request = iterator.next();
-            if (request.getType() == type) {
-                return true;
+    private class QueueManager implements ExecutionQueue.RequestCancellation {
+        private final Object lock = new Object();
+        private final ExecutionQueue<Request> executionQueue = new ExecutionQueue<Request>(new ExecutionQueueInteraction());
+        private final Set<Request> currentlyExecutingRequests = new HashSet<Request>();
+
+        private List<Request> findRequestsOfType(Request.Type type) {
+            List<Request> requests = new ArrayList<Request>();
+            synchronized (lock) {
+                for (Request request : currentlyExecutingRequests) {
+                    if (request.getType() == type) {
+                        requests.add(request);
+                    }
+                }
             }
+            return requests;
         }
 
-        List<Request> pendingRequests = executionQueue.getRequests();
-        iterator = pendingRequests.iterator();
-        while (iterator.hasNext()) {
-            ExecutionQueue.Request request = iterator.next();
-            if (request.getType() == type) {
-                return true;
+        public void onCancel(ExecutionQueue.Request request) {
+            executionQueue.removeRequestFromQueue(request);
+            synchronized (lock) {
+                currentlyExecutingRequests.remove(request);
             }
         }
 
-        return false;
+        public void onComplete(Request request) {
+            synchronized (lock) {
+                currentlyExecutingRequests.remove(request);
+            }
+        }
+
+        public void addRequestToQueue(Request request) {
+            synchronized (lock) {
+                currentlyExecutingRequests.add(request);
+            }
+            executionQueue.addRequestToQueue(request);
+        }
     }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.java
index 777438e..58fde1c 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.java
@@ -18,8 +18,6 @@ package org.gradle.gradleplugin.foundation.favorites;
 /**
  * This holds onto a favorite task. Note: a user may define a favorite task, but it may or may be not present (or may be hidden because of a temporary error in gradle files). So we hold onto the full
  * name of the task so we can get to the task should it become available.
- *
- * @author mhunsicker
  */
 public class FavoriteTask {
     private String fullCommandLine;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
index d4b2f21..72f2b39 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
@@ -30,8 +30,6 @@ import java.util.List;
 
 /**
  * This holds onto and edits favorite tasks. 'Favorite tasks' provides a quick way to run frequently used tasks.
- *
- * @author mhunsicker
  */
 public class FavoritesEditor implements SettingsSerializable {
     private ReorderableList<FavoriteTask> favoriteTasks = new ReorderableList<FavoriteTask>();
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java
index 40053ea..e4a2eba 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java
@@ -25,10 +25,7 @@ import java.util.List;
 /**
  * Inner class that handles serializing favorites. You can either pass it a favorites list and serialize them out or use the default constructor and serialize it. This allows you to serialize a
  * favorites list with or without an editor.
- *
- * @author mhunsicker
- */
-class FavoritesSerializable implements SettingsSerializable {
+ */class FavoritesSerializable implements SettingsSerializable {
     private List<FavoriteTask> favorites;
 
     private static final String FAVORITE_ELEMENT_TAG = "favorite";
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java
index 4da8f88..2a50dce 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java
@@ -20,8 +20,6 @@ import org.gradle.foundation.TaskView;
 
 /**
  * This filter actually doesn't filter. It allows everything through.
- *
- * @author mhunsicker
  */
 public class AllowAllProjectAndTaskFilter implements ProjectAndTaskFilter {
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java
index 75b53fa..c15b4c5 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java
@@ -32,8 +32,6 @@ import java.util.List;
  During the editing process, you often need to fire off notifications that
  normally aren't required. This is where those notifications would come from.
  This also has some extra validation that isn't present in the filter itself.
-
- @author mhunsicker
   */
 
 public class BasicFilterEditor implements ProjectAndTaskFilter {
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java
index a3ea651..1ba9d70 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java
@@ -26,8 +26,6 @@ import java.util.List;
 
 /**
  * This is a basic filter where you can specify specific projects and tasks that will be filtered out.
- *
- * @author mhunsicker
  */
 public class BasicProjectAndTaskFilter implements ProjectAndTaskFilter, SettingsSerializable {
     private static final String BASIC_PROJECT_AND_TASK_FILTER = "basic-project-and-task-filter";
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java
index 44e806b..7eff1dc 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java
@@ -20,8 +20,6 @@ import org.gradle.foundation.TaskView;
 
 /**
  * Interface for a filter that weeds out certain projects and tasks. Useful when trying to walk the projects and tasks.
- *
- * @author mhunsicker
  */
 public interface ProjectAndTaskFilter {
     /**
@@ -37,7 +35,6 @@ public interface ProjectAndTaskFilter {
      *
      * @param task the task in question
      * @return true to allow it, false not to.
-     * @author mhunsicker
      */
     public boolean doesAllowTask(TaskView task);
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java
index 52af986..6a9905e 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java
@@ -22,24 +22,22 @@ import org.gradle.foundation.queue.ExecutionQueue;
 /**
  * This represents a basic reques to gradle that is executed in a separate process using the ProcessLauncherServer. This stores the command line to execute and has the ability to cancel itself by
  * either removing it from the queue if it hasn't started yet, or killing the external process.
- *
- * @author mhunsicker
  */
 public abstract class AbstractRequest implements Request {
     private long requestID;
     private String fullCommandLine;
     private String displayName;
     private boolean forceOutputToBeShown;
-    private ExecutionQueue executionQueue;
+    private ExecutionQueue.RequestCancellation cancellation;
     private ProcessLauncherServer server;
     protected ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction = new DummyExecutionInteraction();
 
-    public AbstractRequest(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown, ExecutionQueue executionQueue) {
+    public AbstractRequest(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown, ExecutionQueue.RequestCancellation cancellation) {
         this.requestID = requestID;
         this.fullCommandLine = fullCommandLine;
         this.displayName = displayName;
         this.forceOutputToBeShown = forceOutputToBeShown;
-        this.executionQueue = executionQueue;
+        this.cancellation = cancellation;
     }
 
     public long getRequestID() {
@@ -66,7 +64,7 @@ public abstract class AbstractRequest implements Request {
             server.killProcess();
         }
 
-        executionQueue.removeRequestFromQueue(this);
+        cancellation.onCancel(this);
         return true;
     }
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
index 3c19970..d5571e7 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
@@ -27,16 +27,14 @@ import java.io.File;
 /**
  * This represents a reques to gradle that is executed in a separate process using the ProcessLauncherServer. This version is for directly executing commands in gradle (the most common type of
  * request).
- *
- * @author mhunsicker
  */
 public class ExecutionRequest extends AbstractRequest {
 
     public static final Type TYPE = new Type() {
     };
 
-    public ExecutionRequest(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown, ExecutionQueue executionQueue) {
-        super(requestID, fullCommandLine, displayName, forceOutputToBeShown, executionQueue);
+    public ExecutionRequest(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown, ExecutionQueue.RequestCancellation cancellation) {
+        super(requestID, fullCommandLine, displayName, forceOutputToBeShown, cancellation);
     }
 
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
index 08bb430..96d6e87 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
@@ -29,8 +29,6 @@ import java.util.List;
 
 /**
  * This represents a request to gradle that is executed in a separate process using the ProcessLauncherServer. This is a special request where the results are to build up a project/task tree.
- *
- * @author mhunsicker
  */
 public class RefreshTaskListRequest extends AbstractRequest {
 
@@ -39,8 +37,8 @@ public class RefreshTaskListRequest extends AbstractRequest {
 
     private GradlePluginLord gradlePluginLord;
 
-    public RefreshTaskListRequest(long requestID, String fullCommandLine, ExecutionQueue executionQueue, GradlePluginLord gradlePluginLord) {
-        super(requestID, fullCommandLine, "Refresh", false, executionQueue);
+    public RefreshTaskListRequest(long requestID, String fullCommandLine, ExecutionQueue.RequestCancellation cancellation, GradlePluginLord gradlePluginLord) {
+        super(requestID, fullCommandLine, "Refresh", false, cancellation);
         this.gradlePluginLord = gradlePluginLord;
     }
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
index ea7c4ba..59609af 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
@@ -26,8 +26,6 @@ import java.io.File;
 
 /**
  * This represents a reques to gradle that is executed in a separate process using the ProcessLauncherServer.
- *
- * @author mhunsicker
  */
 public interface Request extends ExecutionQueue.Request {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
index c448d1d..87cb237 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
@@ -24,8 +24,6 @@ import java.io.File;
 
 /**
  * This executes a command line in an external process.
- *
- * @author mhunsicker
  */
 public class GradleRunner {
     private File currentDirectory;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java
index 5fea746..8931d97 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java
@@ -19,8 +19,6 @@ import java.util.regex.Pattern;
 
 /**
  * This is basic search criteria for searching text. This could be extended for more advanced searches.
- *
- * @author mhunsicker
  */
 public class BasicTextSearchCriteria {
     private String textToMatch = "";
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java
index c081b18..845dd1f 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java
@@ -24,8 +24,6 @@ import java.util.regex.Pattern;
 
 /**
  * This searches within a block of text. The bulk of the work is handled by RegEx. This builds a list of results.
- *
- * @author mhunsicker
  */
 public class TextBlockSearchEditor {
     private List<SearchResult> matchedResults = new ArrayList<SearchResult>();
@@ -83,7 +81,6 @@ public class TextBlockSearchEditor {
     /**
      * Information about a search's results.
      *
-     * @author mhunsicker
      */
     public static class SearchResult {
         private String matchedText;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java
index 188f8b7..b2be592 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java
@@ -23,8 +23,6 @@ import java.util.List;
 
 /**
  * An implementation of SettingsNode that uses DOM4J nodes as its actual storage medium.
- *
- * @author mhunsicker
  */
 public class DOM4JSettingsNode implements SettingsNode {
     public static final String TAG_NAME = "setting";
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java
index 4c19b96..3dabfd8 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java
@@ -45,8 +45,6 @@ import java.util.List;
  *
  * This has several convenience functions for setting and getting values from child nodes. These are meant to be used in more of a java preferences replacement. You should create your own root node of
  * your settings (by call addChildIfNotPresent from a node that is given to you) then you can use these functions and you only need to worry about uniqueness within your own node.
- *
- * @author mhunsicker
  */
 public interface SettingsNode {
     /**
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java
index 9d42132..434071b 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java
@@ -17,8 +17,6 @@ package org.gradle.gradleplugin.foundation.settings;
 
 /**
  * Something that can be serialized to an XML structure. This is meant to store any preferences or settings (lightweight and heavyweight).
- *
- * @author mhunsicker
  */
 public interface SettingsSerializable {
     /**
@@ -32,7 +30,6 @@ public interface SettingsSerializable {
      * Call this to read in this object's settings. The reverse of serializeOut.
      *
      * @param settings where you read your settings.
-     * @author mhunsicker
      */
     public void serializeIn(SettingsNode settings);
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java
index 4605c8c..467d7e2 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java
@@ -20,8 +20,6 @@ import java.io.File;
 /**
  * This allows this plugin to interact with alternative UIs. Specifically, this has callbacks for IDE's so tell it to edit a project file or the like. This is the 'alternate' UI interaction because it
  * interacts with other UIs (other than the built-in UI).
- *
- * @author mhunsicker
  */
 public interface AlternateUIInteraction {
 
@@ -40,7 +38,6 @@ public interface AlternateUIInteraction {
 
       @param  file      the file to open
       @param line the line to go to. -1 if no line is specified.
-      @author mhunsicker
    */
     public void editFile(File file, int line);
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java
index dd47c88..80a67c0 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java
@@ -25,8 +25,6 @@ import java.awt.geom.Rectangle2D;
 /**
  * This is button that has no border and only an image. It highlights when the user moves over it. This style was modeled after Idea. This was used because the borders on toolbars can get a little
  * busy and this looks a little cleaner.
- *
- * @author mhunsicker
  */
 public class BorderlessImageButton extends JButton {
     private Color oldBackgroundColor;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java
index 4756a02..f914b64 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java
@@ -26,8 +26,6 @@ import java.awt.geom.Rectangle2D;
 /**
  * This is button that has no border and only an image. It highlights when the user moves over it. This version is a toggle button. This style was modeled after Idea. This was used because the borders
  * on toolbars can get a little busy and this looks a little cleaner.
- *
- * @author mhunsicker
  */
 public class BorderlessImageToggleButton extends JToggleButton {
     public Border selectedBorder = BorderFactory.createLoweredBevelBorder();
@@ -78,7 +76,6 @@ public class BorderlessImageToggleButton extends JToggleButton {
      * into account our need to change the border depending on the selection state of the button. This overrides negates that effect causing the button to behave as intended.
      *
      * @param border The new border to set for this button the we disregard and replace with our own.
-     * @author wwhitaker
      */
     public void setBorder(Border border) {
         super.setBorder(BorderlessImageToggleButton.this.isSelected() ? selectedBorder : BorderlessUtility.DEFAULT_BORDER);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java
index 52c5968..343941b 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java
@@ -21,8 +21,6 @@ import java.awt.*;
 
 /**
  * Utility functions/constants for borderless buttons
- *
- * @author mhunsicker
  */
 public class BorderlessUtility {
     public static final Color ON_MOUSE_OVER_BACKGROUND = new Color(181, 190, 214);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java
index 40b890d..0d6348d 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java
@@ -25,8 +25,6 @@ import java.io.IOException;
 
 /**
  * This class just helps do some of the mundane tasks of saving and restoring the location of something.
- *
- * @author mhunsicker
  */
 public class PreferencesAssistant {
     private static final String WINDOW_X = "window_x";
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java
index 02f92b3..4afc264 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java
@@ -21,22 +21,10 @@ import org.gradle.gradleplugin.foundation.search.BasicTextSearchCriteria;
 import org.gradle.gradleplugin.foundation.search.TextBlockSearchEditor;
 import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
 
-import javax.swing.AbstractAction;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-import javax.swing.JTextField;
-import javax.swing.KeyStroke;
-import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
+import javax.swing.*;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
+import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
@@ -52,8 +40,6 @@ import java.util.concurrent.LinkedBlockingQueue;
  *
  * This performs a threaded search. Its not that the search will take a particularly long time (although it certainly could). Its that we're doing work and we can't do that in the Swing EDT. We'll be
  * searching every time a user types a letter, As such, we queue up our requests and then perform a search on the latest request.
- *
- * @author mhunsicker
  */
 public class SearchPanel {
     private final Logger logger = Logging.getLogger(SearchPanel.class);
@@ -341,7 +327,6 @@ public class SearchPanel {
      * <!      Name       Description>
      *
      * @param panel where to add your additional fields.
-     * @author mhunsicker
      */
     protected void addAdditionalFields(JPanel panel) {
 
@@ -350,7 +335,6 @@ public class SearchPanel {
     /**
      * Call this to hide this panel.
      *
-     * @author mhunsicker
      */
     public void hide() {
         if (this.searchInteraction != null) {
@@ -363,7 +347,6 @@ public class SearchPanel {
     /**
      * Call this to show this panel so that a search can begin. <!      Name              Description>
      *
-     * @author mhunsicker
      */
     public void show() {
         mainPanel.setVisible(true);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java
index 382cc09..9d60483 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java
@@ -18,7 +18,7 @@ package org.gradle.gradleplugin.userinterface.swing.common;
 import org.gradle.gradleplugin.foundation.search.TextBlockSearchEditor;
 import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
 
-import javax.swing.JTextPane;
+import javax.swing.*;
 import javax.swing.text.AttributeSet;
 import javax.swing.text.DefaultStyledDocument;
 import java.util.ArrayList;
@@ -30,8 +30,6 @@ import java.util.List;
  *
  * Note: there's something kind of goofy here. This draws and 'undraws' highlights on text (using AttributeSets). If you use this and you draw your own highlights in the JTextPane, you need to
  * override removeResultHighlights() and reset your AttributeSets. Otherwise, this assumes there is a default style that all non-highlighted text uses.
- *
- * @author mhunsicker
  */
 public class TextPaneSearchInteraction implements SearchPanel.SearchInteraction {
     private JTextPane textComponentToSearch;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java
index 2d936c2..a30b3c7 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java
@@ -26,8 +26,6 @@ import java.io.File;
 
 /**
  * A simple UI for gradle that is meant to be embedded into an IDE. This doesn't have it own output since most IDEs have their own mechanism for that.
- *
- * @author mhunsicker
  */
 public abstract class AbstractGradleUIInstance implements BasicGradleUI {
     protected MainGradlePanel gradlePanel;
@@ -169,7 +167,6 @@ public abstract class AbstractGradleUIInstance implements BasicGradleUI {
 
       @param  commandLineArguments the command line arguments to pass to gradle.
       @param displayName           the name displayed in the UI for this command
-      @author mhunsicker
    */
     public void executeCommand(String commandLineArguments, String displayName) {
         gradlePluginLord.addExecutionRequestToQueue(commandLineArguments, displayName);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java
index 37307b0..4b92bfe 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java
@@ -23,22 +23,18 @@ import java.awt.*;
 
 /**
  * .
- *
- * @author mhunsicker
  */
 public interface BasicGradleUI {
     public GradlePluginLord getGradlePluginLord();
 
     /*
        @return the panel for this pane. This can be inserted directly into your UI.
-       @author mhunsicker
     */
     public JComponent getComponent();
 
     /*
        Call this whenever you're about to show this panel. We'll do whatever
        initialization is necessary.
-       @author mhunsicker
     */
     public void aboutToShow();
 
@@ -48,7 +44,6 @@ public interface BasicGradleUI {
            This is called if gradle tasks are being executed and you want to know if
            we can close. Ask the user.
            @return true if the user confirms cancelling the current tasks. False if not.
-           @author mhunsicker
         */
         public boolean promptUserToConfirmClosingWhileBusy();
     }
@@ -59,7 +54,6 @@ public interface BasicGradleUI {
 
        @param  closeInteraction allows us to interact with the user
        @return true if we can close, false if not.
-       @author mhunsicker
     */
     public boolean canClose(CloseInteraction closeInteraction);
 
@@ -67,20 +61,17 @@ public interface BasicGradleUI {
        Call this before you close the pane. This gives it an opportunity to do
        cleanup. You probably should call canClose before this. It gives the
        app a chance to cancel if its busy.
-       @author mhunsicker
     */
     public void close();
 
     /*
        @return the total number of tabs.
-       @author mhunsicker
     */
     public int getGradleTabCount();
 
     /*
        @param  index      the index of the tab
        @return the name of the tab at the specified index.
-       @author mhunsicker
     */
     public String getGradleTabName(int index);
 
@@ -109,7 +100,6 @@ public interface BasicGradleUI {
 
        @param  commandLineArguments the command line arguments to pass to gradle.
        @param displayName           the name displayed in the UI for this command
-       @author mhunsicker
     */
     public void executeCommand(String commandLineArguments, String displayName);
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java
index 55d91a6..a1e9e8f 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java
@@ -24,8 +24,6 @@ import java.awt.*;
 /**
  * A simple UI for gradle. This has two panels that can be inserted into a stand-alone application or an IDE. This is meant to hide most of the complexities of gradle. The two panes are a tabbed pane
  * for executing tasks and an output pane.
- *
- * @author mhunsicker
  */
 public class DualPaneUIInstance extends AbstractGradleUIInstance {
     private OutputPanelLord outputPanelLord;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java
index 1493d00..7b235ae 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java
@@ -25,14 +25,11 @@ import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import java.awt.*;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
 /**
  * This is a tabbed pane meant to handle several tabs of gradle-related things. To use this, instantiate it, place it some Swing container (dialog, frame), then call aboutToShow() before you show the
  * parent container. You can also add your own tabs to this (just call addGradleTab before calling aboutToShow()). When you shut down, call aboutToClose() before doing so.
- *
- * @author mhunsicker
  */
 public class MainGradlePanel extends JPanel {
     private static final String CURRENT_TAB = "current-tab";
@@ -161,14 +158,9 @@ public class MainGradlePanel extends JPanel {
     public void aboutToShow() {
         setupUI();
 
-        Iterator<GradleTab> iterator = gradleTabs.iterator();
-        while (iterator.hasNext()) {
-            GradleTab gradleTab = iterator.next();
+        for (GradleTab gradleTab : gradleTabs) {
             gradleTab.aboutToShow();
         }
-
-        //now start up the plugin now that everything has been initialized
-        gradlePluginLord.startExecutionQueue();
     }
 
     /**
@@ -223,9 +215,7 @@ public class MainGradlePanel extends JPanel {
     }
 
     private void addTabs() {
-        Iterator<GradleTab> iterator = gradleTabs.iterator();
-        while (iterator.hasNext()) {
-            GradleTab gradleTab = iterator.next();
+        for (GradleTab gradleTab : gradleTabs) {
             tabbedPane.add(gradleTab.getName(), gradleTab.createComponent());
         }
     }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
index 2af8fc7..9a52d45 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
@@ -28,36 +28,20 @@ import org.gradle.gradleplugin.userinterface.swing.common.SearchPanel;
 import org.gradle.gradleplugin.userinterface.swing.common.TextPaneSearchInteraction;
 import org.gradle.logging.ShowStacktrace;
 
-import javax.swing.BorderFactory;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JProgressBar;
-import javax.swing.JTextPane;
-import javax.swing.JToggleButton;
-import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
-import javax.swing.JButton;
-import javax.swing.AbstractAction;
+import javax.swing.*;
 import javax.swing.text.AttributeSet;
 import javax.swing.text.StyleConstants;
 import javax.swing.text.StyleContext;
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Font;
+import java.awt.*;
+import java.awt.event.ActionEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.awt.event.ActionEvent;
+import java.io.File;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
-import java.io.File;
 
 /**
  * This is a panel that displays the results of executing a gradle command. It shows gradle's output as well as progress.
- *
- * @author mhunsicker
  */
 public class OutputPanel extends JPanel implements ExecuteGradleCommandServerProtocol.ExecutionInteraction {
 
@@ -582,7 +566,6 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
      * Report real-time output from gradle and its subsystems (such as ant).
      *
      * @param output a single line of text to show.
-     * @author mhunsicker
      */
     public void reportLiveOutput(String output) {
         appendGradleOutput(output);
@@ -591,7 +574,6 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
     /**
      * Determines if this panel is ready to be reused. Currently, if its not busy or pinned, it can be reused.
      *
-     * @author mhunsicker
      */
     public boolean canBeReusedNow() {
         return !isPending && !isBusy && !isPinned;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
index c3288b1..c10f1ae 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.gradleplugin.userinterface.swing.generic;
 
+import org.gradle.foundation.common.ObserverLord;
 import org.gradle.foundation.output.FileLinkDefinitionLord;
 import org.gradle.foundation.queue.ExecutionQueue;
-import org.gradle.foundation.common.ObserverLord;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
 import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
 import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
@@ -25,9 +25,7 @@ import org.gradle.gradleplugin.foundation.request.Request;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
 
 import javax.swing.*;
-import java.awt.BorderLayout;
-import java.awt.Point;
-import java.awt.Font;
+import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
@@ -37,8 +35,6 @@ import java.util.List;
 
 /**
  * This class manages displaying the results of a gradle execution in a panel inside a JTabbedPane. It can reuse existing tabs but creates new ones if you run multiple things concurrently.
- *
- * @author mhunsicker
  */
 public class OutputPanelLord implements OutputUILord, GradlePluginLord.RequestObserver, OutputPanel.OutputPanelParent {
 
@@ -375,7 +371,6 @@ public class OutputPanelLord implements OutputUILord, GradlePluginLord.RequestOb
      * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files.
      *
      * @param request the request to be executed
-     * @author mhunsicker
      */
     public void aboutToExecuteRequest(Request request) {
     }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
index b3c65a8..7264c57 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
@@ -20,24 +20,18 @@ import org.gradle.api.logging.Logging;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
 
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.ImageIcon;
 import javax.imageio.ImageIO;
-import java.awt.Component;
-import java.awt.image.BufferedImage;
+import javax.swing.*;
+import java.awt.*;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.io.InputStream;
+import java.awt.image.BufferedImage;
 import java.io.IOException;
+import java.io.InputStream;
 
 /**
  * This just wraps up an OutputPanel so it has a tab header that can be dynamic. The current (rather awkward) JTabbedPane implementation is to separate the tab contents from its component. This only
  * works with java 1.6 or later.
- *
- * @author mhunsicker
  */
 public class OutputTab extends OutputPanel {
 
@@ -124,7 +118,6 @@ public class OutputTab extends OutputPanel {
     /**
      * Call this before you use this tab. It resets its output as well as enabling buttons appropriately.
      *
-     * @author mhunsicker
      */
     @Override
     public void reset() {
@@ -155,7 +148,6 @@ public class OutputTab extends OutputPanel {
      * Overridden so we can indicate the pinned state.
      *
      * @param pinned whether or not we're pinned
-     * @author mhunsicker
      */
     @Override
     public void setPinned(boolean pinned) {
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java
index c2efdf2..5088b44 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java
@@ -30,8 +30,6 @@ import java.beans.PropertyChangeListener;
  * A simple UI for gradle. This is a single panel that can be inserted into a stand-alone application or an IDE. This is meant to hide most of the complexities of gradle. 'single pane' means that both
  * the tabbed pane and the output pane are contained within a single pane that this maintains. Meaning, you add this to a UI and its a self-contained gradle UI. This is opposed to a multi-pane concept
  * where the output would be separated from the tabbed pane.
- *
- * @author mhunsicker
  */
 public class SinglePaneUIInstance extends AbstractGradleUIInstance {
     private static final String SPLITTER_PREFERENCES_ID = "splitter-id";
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java
index 64e0db6..ba09798 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java
@@ -25,8 +25,6 @@ import java.util.List;
 
 /**
  * This handles prompting the user how to handle adding multiple tasks as favorites.
- *
- * @author mhunsicker
  */
 public class SwingAddMultipleFavoritesInteraction implements FavoritesEditor.AddMultipleFavoritesInteraction {
     private Window parent;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
index e6c0600..f73dbc5 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
@@ -18,18 +18,14 @@ package org.gradle.gradleplugin.userinterface.swing.generic;
 import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
 
 import javax.swing.*;
-import javax.swing.text.BadLocationException;
-import javax.swing.event.DocumentListener;
 import javax.swing.event.DocumentEvent;
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Window;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+import java.awt.*;
 import java.awt.event.*;
 
 /**
  * This edits the properties of a single favorite task.
- *
- * @author mhunsicker
  */
 public class SwingEditFavoriteInteraction implements FavoritesEditor.EditFavoriteInteraction {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
index 4b1d135..c84a789 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
@@ -17,17 +17,13 @@ package org.gradle.gradleplugin.userinterface.swing.generic;
 
 import org.gradle.gradleplugin.foundation.DOM4JSerializer;
 
-import javax.swing.JFileChooser;
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
+import javax.swing.*;
 import javax.swing.filechooser.FileFilter;
-import java.awt.Window;
+import java.awt.*;
 import java.io.File;
 
 /**
  * Swing implementation of ExportInteraction. This prompts the user for a file via the JFileChooser and handles reporting errors.
- *
- * @author mhunsicker
  */
 public class SwingExportInteraction implements DOM4JSerializer.ExportInteraction {
     private Window parent;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java
index 251d31f..77ff57f 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java
@@ -24,8 +24,6 @@ import java.io.File;
 
 /**
  * Swing implementation of ImportInteraction. This prompts the user for a file via the JFileChooser and handles reporting errors.
- *
- * @author mhunsicker
  */
 public class SwingImportInteraction implements DOM4JSerializer.ImportInteraction {
     private Window parent;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
index e2b765a..139bd64 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
@@ -33,8 +33,6 @@ import java.util.List;
 /**
  * This displays a tree of projects, subprojects, and tasks. You implement the Interaction to detemine how to handle right clicks and double clicking tasks. To use this, call populate and pass it a
  * filter (allows you to change exactly what is displayed). There are several functions to obtaining the selected items, plus you can get the tree directly for any advanced functionality.
- *
- * @author mhunsicker
  */
 public class TaskTreeComponent {
     private GradlePluginLord gradlePluginLord;
@@ -116,7 +114,7 @@ public class TaskTreeComponent {
 
     /**
      * This renders our projects and tasks. This removes the icon and optionally shows the description in a different color. Since there's quite a bit of code for handling rendering tree cells, I'm
-     * just going to mooch off of the DefaultTreeCellRenderer. I'll just modify it's behavior a little (I probably don't need that or the description since it's not going to draw a selection or
+     * just going to mooch off of the DefaultTreeCellRenderer. I'll just modify its behavior a little (I probably don't need that or the description since it's not going to draw a selection or
      * highlight).
      */
     private class Renderer implements TreeCellRenderer {
@@ -667,4 +665,4 @@ public class TaskTreeComponent {
             node.executeTask(isCtrlKeyDown);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
index d3a0ee3..0cece5c 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
@@ -21,22 +21,10 @@ import org.gradle.gradleplugin.userinterface.swing.common.BorderlessImageButton;
 import org.gradle.gradleplugin.userinterface.swing.common.BorderlessImageToggleButton;
 
 import javax.imageio.ImageIO;
-import javax.swing.Action;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.JTabbedPane;
-import javax.swing.JToggleButton;
-import javax.swing.JMenuItem;
+import javax.swing.*;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.JTextComponent;
-import java.awt.Component;
-import java.awt.Rectangle;
-import java.awt.Window;
+import java.awt.*;
 import java.awt.event.InputEvent;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
@@ -45,8 +33,6 @@ import java.lang.reflect.Method;
 
 /**
  * Just some utility functions.
- *
- * @author mhunsicker
  */
 public class Utility {
     private static final Logger LOGGER = Logging.getLogger(Utility.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java
index ec65697..33d8b0d 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java
@@ -27,8 +27,6 @@ import java.util.List;
 
 /**
  * This panel displays something that is filtered. Its really just a list with show/hide buttons. You populate it with whatever you like (in String form).
- *
- * @author mhunsicker
  */
 public abstract class AbstractFilterEditorPanel {
     private JPanel mainPanel;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java
index 7ace00b..90fa8f6 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java
@@ -33,8 +33,6 @@ import java.util.List;
 
 /**
  * This dialog allows you to edit what tasks and projects are visible when a filter is enabled. Filters are used to weed out rarely-used things to make finding things easier.
- *
- * @author mhunsicker
  */
 public class ProjectAndTaskFilterDialog {
     private JDialog dialog;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java
index 4a7d72a..80607b6 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java
@@ -27,8 +27,6 @@ import java.awt.event.KeyEvent;
 
 /**
  * A tab that allows you to just type a straight command line that is sent to Gradle.
- *
- * @author mhunsicker
  */
 public class CommandLineTab implements GradleTab {
     private GradlePluginLord gradlePluginLord;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
index 8d32a2e..47f2f79 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
@@ -36,8 +36,6 @@ import java.util.List;
 
 /**
  * This displays a list of favorites and allows the user to add/remove items as well as change their order.
- *
- * @author mhunsicker
  */
 public class FavoriteTasksTab implements GradleTab, GradlePluginLord.GeneralPluginObserver, FavoritesEditor.FavoriteTasksObserver {
     private GradlePluginLord gradlePluginLord;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java
index fd642bf..80c001e 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java
@@ -19,13 +19,10 @@ import java.awt.*;
 
 /**
  * Interface for a tab in the gradle UI.
- *
- * @author mhunsicker
  */
 public interface GradleTab {
     /**
      * @return the name of this tab
-     * @author mhunsicker
      */
     public String getName();
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
index b3b888c..80d079c 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
@@ -35,8 +35,6 @@ import java.util.*;
 
 /**
  * This tab contains general settings for the plugin.
- *
- * @author mhunsicker
  */
 public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
     private final Logger logger = Logging.getLogger(SetupTab.class);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java
index 94c67c7..39174fe 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java
@@ -48,8 +48,6 @@ import java.util.List;
 
 /**
  * This displays a tree of projects and tasks.
- *
- * @author mhunsicker
  */
 public class TaskTreeTab implements GradleTab, GradlePluginLord.GeneralPluginObserver, GradlePluginLord.RequestObserver {
     private final Logger logger = Logging.getLogger(TaskTreeTab.class);
@@ -135,15 +133,7 @@ public class TaskTreeTab implements GradleTab, GradlePluginLord.GeneralPluginObs
         resetShowDescription(); //make sure that our setting is pushed to the tree's setting.
 
         //when we start up, refresh our list.
-        SwingUtilities.invokeLater(new Runnable() {
-            public void run() {
-                if (gradlePluginLord.isSetupComplete()) {
-                    refresh();
-                } else {
-                    showTextInViewport("Cannot show tasks until configuration is complete. See Setup tab.");
-                }
-            }
-        });
+        refresh();
     }
 
     public void setupUI() {
@@ -290,7 +280,6 @@ public class TaskTreeTab implements GradleTab, GradlePluginLord.GeneralPluginObs
      * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files.
      *
      * @param request the request that's about to be executed.
-     * @author mhunsicker
      */
     public void aboutToExecuteRequest(Request request) {
         //we don't really care
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
index 7a3e958..751e819 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
@@ -36,8 +36,6 @@ import java.net.URI;
 /**
  * The main entry point for a stand-alone application for Gradle. The real work is not done here. This is just a UI containing components that are meant to be reuseable in other UIs (say an IDE
  * plugin). Those other components do the real work. Most of the work is wrapped inside SinglePaneUIInstance.
- *
- * @author mhunsicker
  */
 public class Application implements AlternateUIInteraction {
     private static final int DEFAULT_WIDTH = 800;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
index 42d6433..1cbd2c5 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
@@ -22,8 +22,6 @@ import java.lang.reflect.InvocationTargetException;
 
 /**
  * This is the same as Application, but this version blocks the calling thread until the Application shuts down.
- *
- * @author mhunsicker
  */
 public class BlockingApplication {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java
index 4b3f730..8ec718a 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java
@@ -29,8 +29,6 @@ import java.io.File;
  *
  * This class should not be moved or renamed, nor should its functions be renamed or have arguments added to/removed from them. This is to ensure forward/backward compatibility with multiple versions
  * of IDE plugins. Instead, consider changing the interaction that is passed to the functions as a means of having the caller provide different functionality.
- *
- * @author mhunsicker
  */
 public class RunnerWrapperFactory {
 
@@ -54,7 +52,6 @@ public class RunnerWrapperFactory {
                                    may be helpful diagnosing problems is this
                                    fails
       @return a gradle runner
-      @author mhunsicker
    */
     public static GradleRunnerVersion1 createGradleRunner(File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo) throws Exception {
         return new GradleRunnerWrapper(gradleHomeDirectory, interaction);
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java
index abdfaa2..138f497 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java
@@ -30,8 +30,6 @@ import org.gradle.openapi.wrappers.ui.SinglePaneUIWrapper;
  *
  * This class should not be moved or renamed, nor should its functions be renamed or have arguments added to/removed from them. This is to ensure forward/backward compatibility with multiple versions
  * of IDE plugins. Instead, consider changing the interaction that is passed to the functions as a means of having the caller provide different functionality.
- *
- * @author mhunsicker
  */
 public class UIWrapperFactory {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.java
index 82a78fe..35d363a 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.java
@@ -30,8 +30,6 @@ import java.util.Map;
 
 /**
  * Implementation of GradleInterfaceVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class GradleInterfaceWrapperVersion1 implements GradleInterfaceVersion1 {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java
index aaee959..c6b7e5a 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java
@@ -29,8 +29,6 @@ import java.util.List;
 
 /**
  * Implementation of GradleInterfaceVersion2 meant to help shield external users from internal changes. This adds new functionality to GradleInterfaceWrapperVersion1.
- *
- * @author mhunsicker
  */
 public class GradleInterfaceWrapperVersion2 extends GradleInterfaceWrapperVersion1 implements GradleInterfaceVersion2 {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java
index 080e84a..1168f8d 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java
@@ -27,8 +27,6 @@ import java.util.List;
 
 /**
  * Implementation of ProjectVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class ProjectWrapper implements ProjectVersion1 {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java
index 0bb15e2..854bd9f 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java
@@ -23,8 +23,6 @@ import org.gradle.openapi.external.foundation.RequestObserverVersion1;
 
 /**
  * * Implementation of RequestObserverVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class RequestObserverWrapper implements GradlePluginLord.RequestObserver {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java
index 561802f..aa8f56e 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java
@@ -22,8 +22,6 @@ import org.gradle.openapi.external.foundation.RequestVersion1;
 
 /**
  * Implementation of RequestVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class RequestWrapper implements RequestVersion1 {
     private Request request;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java
index c68ca1c..8f825c3 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java
@@ -25,8 +25,6 @@ import java.util.List;
 
 /**
  * Implementation of TaskVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class TaskWrapper implements TaskVersion1 {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.java
index 4c53ec7..6fd2d2d 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.java
@@ -20,8 +20,6 @@ import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
 
 /**
  * Implementation of FavoriteTaskVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class FavoriteTaskWrapper implements FavoriteTaskVersion1 {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
index 8d3c486..a7214a3 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
@@ -29,8 +29,6 @@ import java.util.List;
 
 /**
  * Implementation of FavoritesEditorVersion1 meant to help shield external users from internal changes.
- *
- * @author mhunsicker
  */
 public class FavoritesEditorWrapper implements FavoritesEditorVersion1 {
     private FavoritesEditor favoritesEditor;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
index 0e27749..11df951 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
@@ -24,8 +24,6 @@ import java.io.File;
 
 /**
  * Wrapper to shield version changes in GradleRunnerInteractionVersion1 from an external user of gradle open API.
- *
- * @author mhunsicker
  */
 public class GradleRunnerInteractionWrapper implements ExecuteGradleCommandServerProtocol.ExecutionInteraction {
     private GradleRunnerInteractionVersion1 interactionVersion1;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java
index 866df99..f3eb7bd 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java
@@ -23,8 +23,6 @@ import java.io.File;
 
 /**
  * Wrapper to shield version changes in GradleRunner from an external user of gradle open API.
- *
- * @author mhunsicker
  */
 public class GradleRunnerWrapper implements GradleRunnerVersion1 {
     private GradleRunner gradleRunner;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java
index ddc9c7c..2968cbd 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java
@@ -33,8 +33,6 @@ import java.util.Map;
 
 /**
  * Implementation of BasicGradleUI meant to help shield external users from internal changes. This also provides the basics for the UI regardless of whether the output is in a separate pane or not.
- *
- * @author mhunsicker
  */
 public abstract class AbstractOpenAPIUIWrapper<U extends BasicGradleUI> {
     private U basicGradleUI;
@@ -59,7 +57,6 @@ public abstract class AbstractOpenAPIUIWrapper<U extends BasicGradleUI> {
              for IDE's that may need to save their files.
 
              @param request the request that's about to be executed
-             @author mhunsicker
              */
             public void aboutToExecuteRequest(Request request) {
                 alternateUIInteractionVersionWrapper.aboutToExecuteCommand(request.getFullCommandLine());
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java
index 68be510..05b9d49 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java
@@ -23,8 +23,6 @@ import java.io.File;
 
 /**
  * Wrapper to shield version changes in AlternateUIInteraction from an external user of gradle open API.
- *
- * @author mhunsicker
  */
 public class AlternateUIInteractionVersionWrapper implements AlternateUIInteraction {
     private AlternateUIInteractionVersion1 alternateUIInteractionVersion1;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java
index 30f3603..96a4d78 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java
@@ -20,8 +20,6 @@ import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion
 
 /**
  * Wrapper to shield version changes in GradleTab from an external user of the gradle open API.
- *
- * @author mhunsicker
  */
 public class CommandLineArgumentAlteringListenerWrapper implements CommandLineArgumentAlteringListener {
     private CommandLineArgumentAlteringListenerVersion1 listenerVersion1;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java
index 0afeae6..7005120 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java
@@ -25,8 +25,6 @@ import java.awt.*;
 /**
  * This wraps a DualPaneUIVersion1 for the purpose of being instantiated for an external tool such an IDE plugin. It wraps several interfaces and uses delegation in an effort to make this backward and
  * forward compatible. Most of the work is done in AbstractOpenAPIUIWrapper
- *
- * @author mhunsicker
  */
 public class DualPaneUIWrapper extends AbstractOpenAPIUIWrapper<DualPaneUIInstance> implements DualPaneUIVersion1 {
     public DualPaneUIWrapper(DualPaneUIInteractionVersion1 dualPaneUIArguments) {
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.java
index b1b4353..ae30750 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.java
@@ -22,8 +22,6 @@ import java.awt.*;
 
 /**
  * Wrapper to shield version changes in GradleTab from an external user of the gradle open API.
- *
- * @author mhunsicker
  */
 public class GradleTabVersionWrapper implements GradleTab {
     private GradleTabVersion1 gradleTabVersion1;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java
index 8ea8e19..4be8c08 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java
@@ -23,8 +23,6 @@ import org.gradle.openapi.external.ui.OutputObserverVersion1;
 
 /**
  * Wrapper to shield version changes in OutputUILord.OutputObserver from an external user of the gradle open API.
- *
- * @author mhunsicker
  */
 public class OutputObserverWrapper implements OutputUILord.OutputObserver {
     private OutputObserverVersion1 outputObserverVersion1;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java
index 8e0e815..11cf86e 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java
@@ -26,8 +26,6 @@ import java.util.Map;
 
 /**
  * Wrapper to shield version changes in OutputUILord from an external user of gradle open API.
- *
- * @author mhunsicker
  */
 public class OutputUILordWrapper implements OutputUILordVersion1 {
 
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java
index bed33e9..d49e0db 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java
@@ -24,8 +24,6 @@ import java.util.List;
 
 /**
  * Wrapper to shield version changes in SettingsNode from an external user of gradle open API.
- *
- * @author mhunsicker
  */
 public class SettingsNodeVersionWrapper implements SettingsNode {
     private SettingsNodeVersion1 settingsNodeVersion1;
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java
index d9791b8..442f96a 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java
@@ -24,8 +24,6 @@ import javax.swing.*;
 /**
  * This wraps a SinglePaneUIVersion1 for the purpose of being instantiated for an external tool such an IDE plugin. It wraps several interfaces and uses delegation in an effort to make this backward
  * and forward compatible. Most of the work is done in AbstractOpenAPIUIWrapper
- *
- * @author mhunsicker
  */
 public class SinglePaneUIWrapper extends AbstractOpenAPIUIWrapper<SinglePaneUIInstance> implements SinglePaneUIVersion1 {
     public SinglePaneUIWrapper(SinglePaneUIInteractionVersion1 singlePaneUIArguments) {
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/BuildInformation.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/BuildInformation.java
index d3c2fb2..dee43fc 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/BuildInformation.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/BuildInformation.java
@@ -17,14 +17,12 @@ package org.gradle.foundation;
 
 import org.gradle.api.Project;
 
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Collections;
 
 /**
  * This provides a simple way to hold onto and obtain projects and tasks for testing purposes.
- *
- * @author mhunsicker
  */
 public class BuildInformation {
     private List<ProjectView> projects;
@@ -42,7 +40,6 @@ public class BuildInformation {
        Call this to get the root level project with the given name.
        @param  name       the name of the project.
        @return the project if it exists.
-       @author mhunsicker
     */
 
     public ProjectView getRootLevelProject(String name) {
@@ -65,7 +62,6 @@ public class BuildInformation {
        Locates the project that has the specified full path
        @param  fullProjectPath the full path of the sought project.
        @return a project or null.
-       @author mhunsicker
     */
 
     public ProjectView getProjectFromFullPath(String fullProjectPath) {
@@ -104,7 +100,6 @@ public class BuildInformation {
 
        @param  fullTaskName the full task name (root_project:sub_project:sub_sub_project:task.).
        @return the task or null if not found.
-       @author mhunsicker
     */
 
     public TaskView getTaskFromFullPath(String fullTaskName) {
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
index ec32f8b..4ed9331 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
@@ -22,8 +22,6 @@ import static org.gradle.foundation.CommandLineAssistant.breakUpCommandLine;
 
 /**
  * This tests aspects of command line parsing that the UI does.
- *
- * @author mhunsicker
  */
 public class CommandLineParsingTest extends TestCase {
     /**
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java
index dd2b52b..d644156 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java
@@ -27,8 +27,6 @@ import java.util.List;
 
 /**
  * Tests the DOM4JSettingsNode class.
- *
- * @author mhunsicker
  */
 public class DOM4JSettingsNodeTest extends TestCase {
     private DOM4JSettingsNode rootNode;
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
index e1ae29c..993438d 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
@@ -31,8 +31,6 @@ import java.util.List;
 
 /**
  * Tests aspects of favorite tasks and the favorites editor.
- *
- * @author mhunsicker
  */
 public class FavoritesTest extends TestCase {
     private BuildInformation buildInformation;
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java
index 4ff1dea..a5de7b4 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java
@@ -25,7 +25,6 @@ import java.util.List;
 
 /**
  These test several aspects of parsing output looking for files.
- @author mhunsicker
  */
 public class FileLinkTests extends TestCase {
     public static void parseOutputTest(String textToSearch, FileLink... expectedResults) {
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/FilterTest.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/FilterTest.java
index d84b750..2c924eb 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/FilterTest.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/FilterTest.java
@@ -30,8 +30,6 @@ import java.util.List;
 
 /**
  * Test various aspects of filtering out tasks and projects from the GradlePluginLord.
- *
- * @author mhunsicker
  */
 public class FilterTest extends TestCase {
     private BuildInformation buildInformation;
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
index 56a3c53..6f89d60 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
@@ -25,8 +25,6 @@ import java.util.List;
 
 /**
  * Tests aspects of LiveOutputParser. This finds FileLinks within text that is being output constantly.
- *
- * @author mhunsicker
  */
 public class LiveOutputParserTests extends TestCase {
     private LiveOutputParser parser;
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
index c0390ac..4ff7bd5 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.foundation;
 
-import junit.framework.Assert;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.tasks.TaskContainer;
@@ -28,6 +27,7 @@ import org.gradle.gradleplugin.foundation.request.Request;
 import org.gradle.internal.UncheckedException;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Assert;
 
 import javax.swing.filechooser.FileFilter;
 import java.io.File;
@@ -38,8 +38,6 @@ import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Utility class for initializing various test objects related.
- *
- * @author mhunsicker
  */
 public class TestUtility {
     private static long uniqueNameCounter = 1; //used to make unique names for JMock objects.
@@ -332,8 +330,6 @@ public class TestUtility {
 
     private static void refreshProjectsBlocking(GradlePluginLord gradlePluginLord, final ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction, int maximumWaitValue,
                                                 TimeUnit maximumWaitUnits) {
-        gradlePluginLord.startExecutionQueue();   //make sure its started
-
         final CountDownLatch complete = new CountDownLatch(1);
         final AtomicReference<String> errorOutput = new AtomicReference<String>();
 
@@ -393,8 +389,6 @@ public class TestUtility {
      */
     public static void executeBlocking(GradlePluginLord gradlePluginLord, String fullCommandLine, String displayName,
                                        final ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction, int maximumWaitSeconds) {
-        gradlePluginLord.startExecutionQueue();   //make sure its started
-
         final CountDownLatch complete = new CountDownLatch(1);
 
         GradlePluginLord.RequestObserver observer = new GradlePluginLord.RequestObserver() {
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java
index 500de05..0cbe8be 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java
@@ -21,8 +21,6 @@ import org.gradle.gradleplugin.foundation.search.TextBlockSearchEditor;
 
 /**
  * Tests for TextBlockSearchEditor
- *
- * @author mhunsicker
  */
 public class TextBlockSearchEditorTests extends TestCase {
     private final static String TEXT_1 = "blah blah\n" +                       //ends on index 9
diff --git a/subprojects/ui/ui.gradle b/subprojects/ui/ui.gradle
index 0683f61..d72449a 100644
--- a/subprojects/ui/ui.gradle
+++ b/subprojects/ui/ui.gradle
@@ -18,7 +18,7 @@ dependencies {
     compile project(':core')
     compile project(':openApi')
 
-    groovy libraries.groovy
+    compile libraries.groovy
 
     compile libraries.dom4j
     compile libraries.commons_io
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperConcurrentDownloadTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperConcurrentDownloadTest.groovy
new file mode 100644
index 0000000..2da94dd
--- /dev/null
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperConcurrentDownloadTest.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
+
+import org.apache.commons.io.IOUtils
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Rule
+import org.junit.rules.ExternalResource
+import org.mortbay.jetty.Connector
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.bio.SocketConnector
+import org.mortbay.jetty.handler.AbstractHandler
+import spock.lang.Issue
+
+import javax.servlet.ServletException
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+class WrapperConcurrentDownloadTest extends AbstractIntegrationSpec {
+    @Rule BlockingDownloadHttpServer server = new BlockingDownloadHttpServer(distribution.binDistribution)
+
+    def setup() {
+        executer.beforeExecute(new WrapperSetup())
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2699")
+    def "concurrent downloads do not stomp over each other"() {
+        given:
+        buildFile << """
+    wrapper {
+        distributionUrl = '${server.distUri}'
+    }
+"""
+
+        succeeds('wrapper')
+
+        when:
+        def results = [1..4].collect { executer.usingExecutable("gradlew").start() }*.waitForFinish()
+
+        then:
+        results.findAll { it.output.contains("Downloading") }.size() == 1
+    }
+
+    static class BlockingDownloadHttpServer extends ExternalResource {
+        private final Server server = new Server()
+        private final TestFile binZip
+
+        BlockingDownloadHttpServer(TestFile binZip) {
+            this.binZip = binZip
+        }
+
+        URI getDistUri() {
+            return new URI("http://localhost:${server.connectors[0].localPort}/gradle-bin.zip")
+        }
+
+        @Override
+        protected void before() throws Throwable {
+            server.connectors = [new SocketConnector()] as Connector[]
+            server.addHandler(new AbstractHandler() {
+                void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException {
+                    binZip.withInputStream { instr ->
+                        IOUtils.copy(instr, response.outputStream)
+                    }
+                    request.handled = true
+                }
+            })
+            server.start()
+        }
+
+        @Override
+        protected void after() {
+            server.stop()
+        }
+    }
+}
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperGenerationIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperGenerationIntegrationTest.groovy
new file mode 100644
index 0000000..91d6f3d
--- /dev/null
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperGenerationIntegrationTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TextUtil
+
+class WrapperGenerationIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        buildFile << """
+            wrapper {
+                distributionUrl = 'http://localhost:8080/gradlew/dist'
+            }
+        """
+    }
+
+    def "generated wrapper scripts use correct line separators"() {
+        when:
+        run "wrapper"
+
+        then:
+        file("gradlew").text.split(TextUtil.unixLineSeparator).length > 1
+        file("gradlew").text.split(TextUtil.windowsLineSeparator).length == 1
+        file("gradlew.bat").text.split(TextUtil.windowsLineSeparator).length > 1
+    }
+
+    def "wrapper jar is small"() {
+        when:
+        run "wrapper"
+
+        then:
+        // wrapper needs to be small. Let's check it's smaller than some arbitrary 'small' limit
+        file("gradle/wrapper/gradle-wrapper.jar").length() < 51 * 1024
+    }
+}
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperHttpIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperHttpIntegrationTest.groovy
new file mode 100644
index 0000000..7d22c49
--- /dev/null
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperHttpIntegrationTest.groovy
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.executer.GradleExecuter
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.test.fixtures.server.http.TestProxyServer
+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 WrapperHttpIntegrationTest extends AbstractIntegrationSpec {
+    @Rule HttpServer server = new HttpServer()
+    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
+
+    void setup() {
+        assert distribution.binDistribution.exists(): "bin distribution must exist to run this test, you need to run the :distributions:binZip task"
+        executer.beforeExecute(new WrapperSetup())
+        server.start()
+        server.expectUserAgent(matchesNameAndVersion("gradlew", GradleVersion.current().getVersion()))
+    }
+
+    GradleExecuter getWrapperExecuter() {
+        executer.usingExecutable('gradlew').inDirectory(testDirectory)
+    }
+
+    private prepareWrapper(String baseUrl) {
+        file("build.gradle") << """
+    wrapper {
+        distributionUrl = '${baseUrl}/gradlew/dist'
+    }
+
+    task hello << {
+        println 'hello'
+    }
+
+    task echoProperty << {
+        println "fooD=" + project.properties["fooD"]
+    }
+"""
+
+        executer.withTasks('wrapper').run()
+    }
+
+    public void "downloads wrapper from http server and caches"() {
+        given:
+        prepareWrapper("http://localhost:${server.port}")
+        server.expectGet("/gradlew/dist", distribution.binDistribution)
+
+        when:
+        def result = wrapperExecuter.withTasks('hello').run()
+
+        then:
+        assertThat(result.output, containsString('hello'))
+
+        when:
+        result = wrapperExecuter.withTasks('hello').run()
+
+        then:
+        assertThat(result.output, containsString('hello'))
+    }
+
+    public void "recovers from failed download"() {
+        given:
+        prepareWrapper("http://localhost:${server.port}")
+        server.addBroken("/")
+
+        when:
+        wrapperExecuter.withStackTraceChecksDisabled()
+        def failure = wrapperExecuter.runWithFailure()
+
+        then:
+        failure.error.contains('Server returned HTTP response code: 500')
+
+        when:
+        server.resetExpectations()
+        server.expectGet("/gradlew/dist", distribution.binDistribution)
+
+        and:
+        wrapperExecuter.run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    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}
+"""
+        server.expectGet("/gradlew/dist", distribution.binDistribution)
+
+        when:
+        def 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")
+        server.expectGet("/gradlew/dist", distribution.binDistribution)
+        file("gradle.properties") << """
+    systemProp.http.proxyHost=localhost
+    systemProp.http.proxyPort=${proxyServer.port}
+    systemProp.http.proxyUser=my_user
+    systemProp.http.proxyPassword=my_password
+"""
+
+        when:
+        def result = wrapperExecuter.withTasks('hello').run()
+
+        then:
+        assertThat(result.output, containsString('hello'))
+
+        and:
+        proxyServer.requestCount == 1
+    }
+}
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
index b142b66..0f4dd1a 100644
--- a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
@@ -17,49 +17,27 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.ExecutionFailure
-import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.integtests.fixtures.executer.GradleExecuter
-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 org.hamcrest.Matchers
 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()))
+        assert distribution.binDistribution.exists(): "bin distribution must exist to run this test, you need to run the :distributions:binZip task"
+        executer.beforeExecute(new WrapperSetup())
     }
 
     GradleExecuter getWrapperExecuter() {
         executer.usingExecutable('gradlew').inDirectory(testDirectory)
     }
 
-    private prepareWrapper(String baseUrl) {
-        assert distribution.binDistribution.exists(): "bin distribution must exist to run this test, you need to run the :binZip task"
-
+    private prepareWrapper() {
         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'
+    wrapper {
+        distributionUrl = '${distribution.binDistribution.toURI()}'
     }
 
     task hello << {
@@ -72,109 +50,28 @@ class WrapperProjectIntegrationTest extends AbstractIntegrationSpec {
 """
 
         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)
+        prepareWrapper()
 
         when:
-        ExecutionFailure failure = wrapperExecuter.withTasks('unknown').runWithFailure()
+        def 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
+        failure.assertThatDescription(Matchers.startsWith("Task 'unknown' not found in root project"))
     }
 
     @Issue("http://issues.gradle.org/browse/GRADLE-1871")
     public void "can specify project properties containing D"() {
         given:
-        prepareWrapper("http://localhost:${server.port}")
+        prepareWrapper()
 
         when:
-        ExecutionResult result = wrapperExecuter.withArguments("-PfooD=bar").withTasks('echoProperty').run()
+        def 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/integTest/groovy/org/gradle/integtests/WrapperSetup.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperSetup.groovy
new file mode 100644
index 0000000..8398ffc
--- /dev/null
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperSetup.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.api.Action
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+
+class WrapperSetup implements Action<GradleExecuter> {
+    void execute(GradleExecuter executer) {
+        executer.requireOwnGradleUserHomeDir()
+        executer.requireIsolatedDaemons()
+        executer.withEnvironmentVars(GRADLE_USER_HOME: executer.gradleUserHomeDir)
+    }
+}
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperUserHomeIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperUserHomeIntegrationTest.groovy
new file mode 100644
index 0000000..aac1714
--- /dev/null
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperUserHomeIntegrationTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * 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.test.fixtures.file.TestFile
+import spock.lang.Issue
+
+class WrapperUserHomeIntegrationTest extends AbstractIntegrationSpec {
+
+    void setup() {
+        assert distribution.binDistribution.exists() : "bin distribution must exist to run this test, you need to run the :distributions:binZip task"
+        executer.requireIsolatedDaemons()
+    }
+
+    private prepareWrapper() {
+        file("build.gradle") << """
+            wrapper {
+                distributionUrl = '${distribution.binDistribution.toURI()}'
+            }
+        """
+        executer.withTasks('wrapper').run()
+        executer.usingExecutable('gradlew').inDirectory(testDirectory).withGradleUserHomeDir(null)
+    }
+
+    private def installationIn(TestFile userHomeDir) {
+        def distDir = userHomeDir.file("wrapper/dists/gradle-${distribution.version.version}-bin").assertIsDir()
+        assert distDir.listFiles().length == 1
+        return distDir.listFiles()[0].file("gradle-${distribution.version.version}").assertIsDir()
+    }
+
+    void 'uses gradle user home set by -Dgradle.user.home'() {
+        given:
+        prepareWrapper()
+        def gradleUserHome = testDirectory.file('user-home')
+
+        when:
+        args "-Dgradle.user.home=$gradleUserHome.absolutePath"
+        succeeds()
+
+        then:
+        installationIn gradleUserHome exists()
+    }
+
+    @Issue('http://issues.gradle.org/browse/GRADLE-2802')
+    void 'uses gradle user home set by -g'() {
+        given:
+        prepareWrapper()
+        def gradleUserHome = testDirectory.file('user-home')
+
+        when:
+        args '-g', gradleUserHome.absolutePath
+        succeeds()
+
+        then:
+        installationIn gradleUserHome exists()
+    }
+}
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
index 05a3085..f3b02a6 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
@@ -20,9 +20,6 @@ import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
 
-/**
- * @author Hans Dockter
- */
 public class BootstrapMainStarter {
     public void start(String[] args, File gradleHome) throws Exception {
         File gradleJar = findLauncherJar(gradleHome);
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 e0d2210..39e6b4c 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
@@ -19,9 +19,6 @@ package org.gradle.wrapper;
 import java.io.*;
 import java.net.*;
 
-/**
- * @author Hans Dockter
- */
 public class Download implements IDownload {
     private static final int PROGRESS_CHUNK = 20000;
     private static final int BUFFER_SIZE = 10000;
@@ -41,11 +38,7 @@ public class Download implements IDownload {
     }
 
     public void download(URI address, File destination) throws Exception {
-        if (destination.exists()) {
-            return;
-        }
         destination.getParentFile().mkdirs();
-
         downloadInternal(address, destination);
     }
 
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/ExclusiveFileAccessManager.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/ExclusiveFileAccessManager.java
new file mode 100644
index 0000000..f63ef49
--- /dev/null
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/ExclusiveFileAccessManager.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.wrapper;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.concurrent.Callable;
+
+public class ExclusiveFileAccessManager {
+
+    public static final String LOCK_FILE_SUFFIX = ".lck";
+
+    private final int timeoutMs;
+    private final int pollIntervalMs;
+
+    public ExclusiveFileAccessManager(int timeoutMs, int pollIntervalMs) {
+        this.timeoutMs = timeoutMs;
+        this.pollIntervalMs = pollIntervalMs;
+    }
+
+    public <T> T access(File exclusiveFile, Callable<T> task) {
+        final File lockFile = new File(exclusiveFile.getParentFile(), exclusiveFile.getName() + LOCK_FILE_SUFFIX);
+        lockFile.getParentFile().mkdirs();
+        RandomAccessFile randomAccessFile = null;
+        FileChannel channel = null;
+        try {
+
+            long startAt = System.currentTimeMillis();
+            FileLock lock = null;
+
+            while (lock == null && System.currentTimeMillis() < startAt + timeoutMs) {
+                randomAccessFile = new RandomAccessFile(lockFile, "rw");
+                channel = randomAccessFile.getChannel();
+                lock = channel.tryLock();
+
+                if (lock == null) {
+                    maybeCloseQuietly(channel);
+                    maybeCloseQuietly(randomAccessFile);
+                    Thread.sleep(pollIntervalMs);
+                }
+            }
+
+            if (lock == null) {
+                throw new RuntimeException("Timeout of " + timeoutMs + " reached waiting for exclusive access to file: " + exclusiveFile.getAbsolutePath());
+            }
+
+            try {
+                return task.call();
+            } finally {
+                lock.release();
+
+                maybeCloseQuietly(channel);
+                channel = null;
+                maybeCloseQuietly(randomAccessFile);
+                randomAccessFile = null;
+            }
+        } catch (Exception e) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            } else {
+                throw new RuntimeException(e);
+            }
+        } finally {
+            maybeCloseQuietly(channel);
+            maybeCloseQuietly(randomAccessFile);
+        }
+    }
+
+    private static void maybeCloseQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (Exception ignore) {
+                //
+            }
+        }
+    }
+}
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleUserHomeLookup.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleUserHomeLookup.java
new file mode 100644
index 0000000..9929024
--- /dev/null
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleUserHomeLookup.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.wrapper;
+
+import java.io.File;
+
+public class GradleUserHomeLookup {
+    public static final String DEFAULT_GRADLE_USER_HOME = System.getProperty("user.home") + "/.gradle";
+    public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home";
+    public static final String GRADLE_USER_HOME_ENV_KEY = "GRADLE_USER_HOME";
+
+    public static File gradleUserHome() {
+        String gradleUserHome;
+        if ((gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY)) != null) {
+            return new File(gradleUserHome);
+        }
+        if ((gradleUserHome = System.getenv(GRADLE_USER_HOME_ENV_KEY)) != null) {
+            return new File(gradleUserHome);
+        }
+        return new File(DEFAULT_GRADLE_USER_HOME);
+    }
+}
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 bb03dbb..d2cb34b 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
@@ -17,50 +17,49 @@
 package org.gradle.wrapper;
 
 import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
 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;
 import java.util.Properties;
 
-/**
- * @author Hans Dockter
- */
 public class GradleWrapperMain {
-    public static final String DEFAULT_GRADLE_USER_HOME = System.getProperty("user.home") + "/.gradle";
-    public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home";
-    public static final String GRADLE_USER_HOME_ENV_KEY = "GRADLE_USER_HOME";
+    public static final String GRADLE_USER_HOME_OPTION = "g";
+    public static final String GRADLE_USER_HOME_DETAILED_OPTION = "gradle-user-home";
 
     public static void main(String[] args) throws Exception {
         File wrapperJar = wrapperJar();
         File propertiesFile = wrapperProperties(wrapperJar);
         File rootDir = rootDir(wrapperJar);
 
+        CommandLineParser parser = new CommandLineParser();
+        parser.allowUnknownOptions();
+        parser.option(GRADLE_USER_HOME_OPTION, GRADLE_USER_HOME_DETAILED_OPTION).hasArgument();
+
+        SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter();
+        converter.configure(parser);
+
+        ParsedCommandLine options = parser.parse(args);
+
         Properties systemProperties = System.getProperties();
-        systemProperties.putAll(parseSystemPropertiesFromArgs(args));
+        systemProperties.putAll(converter.convert(options));
 
-        addSystemProperties(rootDir);
+        File gradleUserHome = gradleUserHome(options);
+
+        addSystemProperties(gradleUserHome, rootDir);
 
         WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out);
         wrapperExecutor.execute(
                 args,
-                new Install(new Download("gradlew", wrapperVersion()), new PathAssembler(gradleUserHome())),
+                new Install(new Download("gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
                 new BootstrapMainStarter());
     }
 
-    private static Map<String, String> parseSystemPropertiesFromArgs(String[] args) {
-        SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter();
-        CommandLineParser commandLineParser = new CommandLineParser();
-        converter.configure(commandLineParser);
-        commandLineParser.allowUnknownOptions();
-        return converter.convert(commandLineParser.parse(args));
-    }
-
-    private static void addSystemProperties(File rootDir) {
-        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(gradleUserHome(), "gradle.properties")));
+    private static void addSystemProperties(File gradleHome, File rootDir) {
+        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(gradleHome, "gradle.properties")));
         System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(rootDir, "gradle.properties")));
     }
 
@@ -107,14 +106,10 @@ public class GradleWrapperMain {
         }
     }
 
-    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) {
-            return new File(gradleUserHome);
-        } else {
-            return new File(DEFAULT_GRADLE_USER_HOME);
+    private static File gradleUserHome(ParsedCommandLine options) {
+        if (options.hasOption(GRADLE_USER_HOME_OPTION)) {
+            return new File(options.option(GRADLE_USER_HOME_OPTION).getValue());
         }
+        return GradleUserHomeLookup.gradleUserHome();
     }
 }
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/IDownload.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/IDownload.java
index fbab4df..078490c 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/IDownload.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/IDownload.java
@@ -18,9 +18,6 @@ package org.gradle.wrapper;
 import java.io.File;
 import java.net.URI;
 
-/**
- * @author Hans Dockter
- */
 public interface IDownload {
     void download(URI address, File destination) throws Exception;
 }
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java
index 04938ac..144fcb0 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java
@@ -19,16 +19,15 @@ package org.gradle.wrapper;
 import java.io.*;
 import java.net.URI;
 import java.util.*;
+import java.util.concurrent.Callable;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-/**
- * @author Hans Dockter
- */
 public class Install {
     public static final String DEFAULT_DISTRIBUTION_PATH = "wrapper/dists";
     private final IDownload download;
     private final PathAssembler pathAssembler;
+    private final ExclusiveFileAccessManager exclusiveFileAccessManager = new ExclusiveFileAccessManager(120000, 200);
 
     public Install(IDownload download, PathAssembler pathAssembler) {
         this.download = download;
@@ -36,41 +35,53 @@ public class Install {
     }
 
     public File createDist(WrapperConfiguration configuration) throws Exception {
-        URI distributionUrl = configuration.getDistribution();
-        boolean alwaysDownload = configuration.isAlwaysDownload();
-        boolean alwaysUnpack = configuration.isAlwaysUnpack();
-
-        PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration);
-
-        File localZipFile = localDistribution.getZipFile();
-        boolean downloaded = false;
-        if (alwaysDownload || !localZipFile.exists()) {
-            File tmpZipFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".part");
-            tmpZipFile.delete();
-            System.out.println("Downloading " + distributionUrl);
-            download.download(distributionUrl, tmpZipFile);
-            tmpZipFile.renameTo(localZipFile);
-            downloaded = true;
-        }
+        final URI distributionUrl = configuration.getDistribution();
 
-        File distDir = localDistribution.getDistributionDir();
-        List<File> dirs = listDirs(distDir);
+        final PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration);
+        final File distDir = localDistribution.getDistributionDir();
+        final File localZipFile = localDistribution.getZipFile();
 
-        if (downloaded || alwaysUnpack || dirs.isEmpty()) {
-            for (File dir : dirs) {
-                System.out.println("Deleting directory " + dir.getAbsolutePath());
-                deleteDir(dir);
-            }
-            System.out.println("Unzipping " + localZipFile.getAbsolutePath() + " to " + distDir.getAbsolutePath());
-            unzip(localZipFile, distDir);
-            dirs = listDirs(distDir);
-            if (dirs.isEmpty()) {
-                throw new RuntimeException(String.format("Gradle distribution '%s' does not contain any directories. Expected to find exactly 1 directory.", distributionUrl));
+        return exclusiveFileAccessManager.access(localZipFile, new Callable<File>() {
+            public File call() throws Exception {
+                final File markerFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".ok");
+                if (distDir.isDirectory() && markerFile.isFile()) {
+                    return getDistributionRoot(distDir, distDir.getAbsolutePath());
+                }
+
+                boolean needsDownload = !localZipFile.isFile();
+
+                if (needsDownload) {
+                    File tmpZipFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".part");
+                    tmpZipFile.delete();
+                    System.out.println("Downloading " + distributionUrl);
+                    download.download(distributionUrl, tmpZipFile);
+                    tmpZipFile.renameTo(localZipFile);
+                }
+
+                List<File> topLevelDirs = listDirs(distDir);
+                for (File dir : topLevelDirs) {
+                    System.out.println("Deleting directory " + dir.getAbsolutePath());
+                    deleteDir(dir);
+                }
+                System.out.println("Unzipping " + localZipFile.getAbsolutePath() + " to " + distDir.getAbsolutePath());
+                unzip(localZipFile, distDir);
+
+                File root = getDistributionRoot(distDir, distributionUrl.toString());
+                setExecutablePermissions(root);
+                markerFile.createNewFile();
+
+                return root;
             }
-            setExecutablePermissions(dirs.get(0));
+        });
+    }
+
+    private File getDistributionRoot(File distDir, String distributionDescription) {
+        List<File> dirs = listDirs(distDir);
+        if (dirs.isEmpty()) {
+            throw new RuntimeException(String.format("Gradle distribution '%s' does not contain any directories. Expected to find exactly 1 directory.", distributionDescription));
         }
         if (dirs.size() != 1) {
-            throw new RuntimeException(String.format("Gradle distribution '%s' contains too many directories. Expected to find exactly 1 directory.", distributionUrl));
+            throw new RuntimeException(String.format("Gradle distribution '%s' contains too many directories. Expected to find exactly 1 directory.", distributionDescription));
         }
         return dirs.get(0);
     }
@@ -141,29 +152,34 @@ public class Install {
         return dir.delete();
     }
 
-    public void unzip(File zip, File dest) throws IOException {
+    private void unzip(File zip, File dest) throws IOException {
         Enumeration entries;
-        ZipFile zipFile;
+        ZipFile zipFile = new ZipFile(zip);
 
-        zipFile = new ZipFile(zip);
+        try {
+            entries = zipFile.entries();
 
-        entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
 
-        while (entries.hasMoreElements()) {
-            ZipEntry entry = (ZipEntry) entries.nextElement();
+                if (entry.isDirectory()) {
+                    (new File(dest, entry.getName())).mkdirs();
+                    continue;
+                }
 
-            if (entry.isDirectory()) {
-                (new File(dest, entry.getName())).mkdirs();
-                continue;
+                OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(new File(dest, entry.getName())));
+                try {
+                    copyInputStream(zipFile.getInputStream(entry), outputStream);
+                } finally {
+                    outputStream.close();
+                }
             }
-
-            copyInputStream(zipFile.getInputStream(entry),
-                    new BufferedOutputStream(new FileOutputStream(new File(dest, entry.getName()))));
+        } finally {
+            zipFile.close();
         }
-        zipFile.close();
     }
 
-    public void copyInputStream(InputStream in, OutputStream out) throws IOException {
+    private void copyInputStream(InputStream in, OutputStream out) throws IOException {
         byte[] buffer = new byte[1024];
         int len;
 
@@ -175,4 +191,5 @@ public class Install {
         out.close();
     }
 
+
 }
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
index eb7b6c9..d3c51bd 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
@@ -20,9 +20,6 @@ import java.math.BigInteger;
 import java.net.URI;
 import java.security.MessageDigest;
 
-/**
- * @author Hans Dockter
- */
 public class PathAssembler {
     public static final String GRADLE_USER_HOME_STRING = "GRADLE_USER_HOME";
     public static final String PROJECT_STRING = "PROJECT";
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
index e2d0ed1..a25f598 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
@@ -24,9 +24,6 @@ import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/**
- * @author Hans Dockter
- */
 public class SystemPropertiesHandler {
 
     public static Map<String, String> getSystemProperties(File propertiesFile) {
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java
index b7b1002..c5da7f0 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java
@@ -18,33 +18,12 @@ package org.gradle.wrapper;
 import java.net.URI;
 
 public class WrapperConfiguration {
-    public static final String ALWAYS_UNPACK_ENV = "GRADLE_WRAPPER_ALWAYS_UNPACK";
-    public static final String ALWAYS_DOWNLOAD_ENV = "GRADLE_WRAPPER_ALWAYS_DOWNLOAD";
-
-    private boolean alwaysUnpack = Boolean.parseBoolean(System.getenv(ALWAYS_UNPACK_ENV));
-    private boolean alwaysDownload = Boolean.parseBoolean(System.getenv(ALWAYS_DOWNLOAD_ENV));
     private URI distribution;
     private String distributionBase = PathAssembler.GRADLE_USER_HOME_STRING;
     private String distributionPath = Install.DEFAULT_DISTRIBUTION_PATH;
     private String zipBase = PathAssembler.GRADLE_USER_HOME_STRING;
     private String zipPath = Install.DEFAULT_DISTRIBUTION_PATH;
 
-    public boolean isAlwaysDownload() {
-        return alwaysDownload;
-    }
-
-    public void setAlwaysDownload(boolean alwaysDownload) {
-        this.alwaysDownload = alwaysDownload;
-    }
-
-    public boolean isAlwaysUnpack() {
-        return alwaysUnpack;
-    }
-
-    public void setAlwaysUnpack(boolean alwaysUnpack) {
-        this.alwaysUnpack = alwaysUnpack;
-    }
-
     public URI getDistribution() {
         return distribution;
     }
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java
index d7e58e7..aac4ff3 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java
@@ -24,9 +24,6 @@ import java.net.URISyntaxException;
 import java.util.Formatter;
 import java.util.Properties;
 
-/**
- * @author Hans Dockter
- */
 public class WrapperExecutor {
     public static final String DISTRIBUTION_URL_PROPERTY = "distributionUrl";
     public static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase";
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 57ba024..2c178b0 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
@@ -23,9 +23,6 @@ import org.junit.Test
 
 import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
 class DownloadTest {
     Download download
     File testDir
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
index 55f5d38..f1748c8 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
@@ -20,19 +20,11 @@ import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Before
 import org.junit.Rule
-import org.junit.Test
 import spock.lang.Specification
 
-import static org.junit.Assert.assertEquals
-
-/**
- * @author Hans Dockter
- */
 class InstallTest extends Specification {
     File testDir
     Install install
-    IDownload downloadMock
-    PathAssembler pathAssemblerMock;
     boolean downloadCalled
     File zip
     TestFile distributionDir
@@ -54,8 +46,6 @@ class InstallTest extends Specification {
         configuration.distributionBase = PathAssembler.GRADLE_USER_HOME_STRING
         configuration.distributionPath = 'someDistPath'
         configuration.distribution = new URI('http://server/gradle-0.9.zip')
-        configuration.alwaysDownload = false
-        configuration.alwaysUnpack = false
         distributionDir = new TestFile(testDir, 'someDistPath')
         gradleHomeDir = new TestFile(distributionDir, 'gradle-0.9')
         zipStore = new File(testDir, 'zips');
@@ -63,29 +53,6 @@ class InstallTest extends Specification {
         install = new Install(download, pathAssembler)
     }
 
-    IDownload createDownloadMock() {
-        [download: {URI url, File destination ->
-            assertEquals(configuration.distribution, url)
-            assertEquals(zipDestination.getAbsolutePath() + '.part', destination.getAbsolutePath())
-            zip = createTestZip()
-            downloadCalled = true
-        }] as IDownload
-    }
-
-    PathAssembler createPathAssemblerMock() {
-        [gradleHome: {String distBase, String distPath, URI distUrl ->
-            assertEquals(configuration.distributionBase, distBase)
-            assertEquals(configuration.distributionPath, distPath)
-            assertEquals(configuration.distribution, distUrl)
-            gradleHomeDir},
-         distZip: { String zipBase, String zipPath, URI distUrl ->
-            assertEquals(configuration.zipBase, zipBase)
-            assertEquals(configuration.zipPath, zipPath)
-             assertEquals(configuration.distribution, distUrl)
-            zipDestination
-        }] as PathAssembler
-    }
-
     void createTestZip(File zipDestination) {
         TestFile explodedZipDir = tmpDir.createDir('explodedZip')
         TestFile gradleScript = explodedZipDir.file('gradle-0.9/bin/gradle')
@@ -94,7 +61,12 @@ class InstallTest extends Specification {
         explodedZipDir.zipTo(new TestFile(zipDestination))
     }
 
-    public void testCreateDist() {
+    def "installs distribution and reuses on subsequent access"() {
+        given:
+        _ * pathAssembler.getDistribution(configuration) >> localDistribution
+        _ * localDistribution.distributionDir >> distributionDir
+        _ * localDistribution.zipFile >> zipDestination
+
         when:
         def homeDir = install.createDist(configuration)
 
@@ -105,77 +77,48 @@ class InstallTest extends Specification {
         zipDestination.assertIsFile()
 
         and:
-        1 * pathAssembler.getDistribution(configuration) >> localDistribution
-        _ * localDistribution.distributionDir >> distributionDir
-        _ * localDistribution.zipFile >> zipDestination
         1 * download.download(configuration.distribution, _) >> { createTestZip(it[1]) }
         0 * download._
-    }
-
-    @Test public void testCreateDistWithExistingDistribution() {
-        given:
-        zipDestination.createFile()
-        gradleHomeDir.file('some-file').createFile()
-        gradleHomeDir.createDir()
 
         when:
-        def homeDir = install.createDist(configuration)
+        homeDir = install.createDist(configuration)
 
         then:
         homeDir == gradleHomeDir
-        gradleHomeDir.assertIsDir()
-        gradleHomeDir.file('some-file').assertIsFile()
-        zipDestination.assertIsFile()
 
         and:
-        1 * pathAssembler.getDistribution(configuration) >> localDistribution
-        _ * localDistribution.distributionDir >> distributionDir
-        _ * localDistribution.zipFile >> zipDestination
         0 * download._
     }
 
-    @Test public void testCreateDistWithExistingDistAndZipAndAlwaysUnpackTrue() {
+    def "recovers from download failure"() {
+        def failure = new RuntimeException("broken")
+
         given:
-        createTestZip(zipDestination)
-        gradleHomeDir.file('garbage').createFile()
-        configuration.alwaysUnpack = true
+        _ * pathAssembler.getDistribution(configuration) >> localDistribution
+        _ * localDistribution.distributionDir >> distributionDir
+        _ * localDistribution.zipFile >> zipDestination
 
         when:
-        def homeDir = install.createDist(configuration)
+        install.createDist(configuration)
 
         then:
-        homeDir == gradleHomeDir
-        gradleHomeDir.assertIsDir()
-        gradleHomeDir.file('garbage').assertDoesNotExist()
-        zipDestination.assertIsFile()
+        RuntimeException e = thrown()
+        e == failure
 
         and:
-        1 * pathAssembler.getDistribution(configuration) >> localDistribution
-        _ * localDistribution.distributionDir >> distributionDir
-        _ * localDistribution.zipFile >> zipDestination
+        1 * download.download(configuration.distribution, _) >> {
+            it[1].text = 'broken!'
+            throw failure
+        }
         0 * download._
-    }
-
-    @Test public void testCreateDistWithExistingZipAndDistAndAlwaysDownloadTrue() {
-        given:
-        createTestZip(zipDestination)
-        gradleHomeDir.file('garbage').createFile()
-        configuration.alwaysDownload = true
 
         when:
         def homeDir = install.createDist(configuration)
 
         then:
         homeDir == gradleHomeDir
-        gradleHomeDir.assertIsDir()
-        gradleHomeDir.file("bin/gradle").assertIsFile()
-        gradleHomeDir.file('garbage').assertDoesNotExist()
-        zipDestination.assertIsFile()
 
         and:
-        1 * pathAssembler.getDistribution(configuration) >> localDistribution
-        _ * localDistribution.distributionDir >> distributionDir
-        _ * localDistribution.zipFile >> zipDestination
         1 * download.download(configuration.distribution, _) >> { createTestZip(it[1]) }
         0 * download._
     }
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
index 1e8adbf..e7fab02 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
@@ -25,9 +25,6 @@ import static org.gradle.util.Matchers.matchesRegexp;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.*;
 
-/**
- * @author Hans Dockter
- */
 public class PathAssemblerTest {
     public static final String TEST_GRADLE_USER_HOME = "someUserHome";
     private PathAssembler pathAssembler = new PathAssembler(new File(TEST_GRADLE_USER_HOME));
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
index bb7cf9e..124a137 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
@@ -19,9 +19,6 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-/**
- * @author Hans Dockter
- */
 class SystemPropertiesHandlerTest extends Specification {
     @Rule
     TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
diff --git a/subprojects/wrapper/wrapper.gradle b/subprojects/wrapper/wrapper.gradle
index e03ed74..8e1c896 100644
--- a/subprojects/wrapper/wrapper.gradle
+++ b/subprojects/wrapper/wrapper.gradle
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy
     compile project(":cli")
+
+    testCompile libraries.groovy
     testCompile libraries.ant
 
     integTestRuntime rootProject.configurations.testRuntime.allDependencies
diff --git a/version.txt b/version.txt
index 400122e..809bdcb 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.5
\ No newline at end of file
+1.12

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